Merge pull request #29 from existedinnettw/refactor/layout
This commit is contained in:
commit
1c2e7f5e49
33
docs/arch.md
33
docs/arch.md
|
|
@ -77,7 +77,8 @@ flowchart
|
|||
s2
|
||||
s2
|
||||
n3 --> s3
|
||||
s2 -->|"poll through API to execute job"| s4
|
||||
s2
|
||||
s4
|
||||
s4 -->|"report execute status through API"| s2
|
||||
n1@{ label: "Rectangle" }
|
||||
n1["runners dashboard"]
|
||||
|
|
@ -90,4 +91,34 @@ flowchart
|
|||
s1["workflow dashboard"]
|
||||
end
|
||||
s1 --> s2
|
||||
n8["github polling"]
|
||||
n9["github webhook + webhook-websocket gateway"]
|
||||
s2 --> n8
|
||||
n8 --> s4
|
||||
s2 --> n9
|
||||
n9 --> s4
|
||||
```
|
||||
|
||||
## config option
|
||||
|
||||
* (issue) tracker
|
||||
* github(`gh`)
|
||||
* polling
|
||||
* `gh webhook forward`
|
||||
* webhook + smee.io(`gosmee`)
|
||||
* gitlab(`glab`)
|
||||
* webhook + smee.io
|
||||
* gitea(`tea`)
|
||||
* webhook + smee.io
|
||||
* openproject(`openproject-cli`)
|
||||
* polling
|
||||
* linear(`linear`)
|
||||
* SCM
|
||||
* github
|
||||
* gitlab
|
||||
* gitea
|
||||
* notifier
|
||||
* desktop
|
||||
* discord
|
||||
* slack
|
||||
* email
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'app_environment.dart';
|
||||
import 'config.dart';
|
||||
import 'issue_tracker_client.dart';
|
||||
import 'process_launcher.dart';
|
||||
|
||||
class CliResponderRunner {
|
||||
Future<ResponderResult> run({
|
||||
|
|
@ -27,12 +27,11 @@ class CliResponderRunner {
|
|||
};
|
||||
|
||||
final stdinPayload = renderTemplate(responder.stdinTemplate, context);
|
||||
final process = await Process.start(
|
||||
final process = await startExternalCommand(
|
||||
responder.command,
|
||||
responder.args,
|
||||
workingDirectory: workspacePath,
|
||||
environment: <String, String>{
|
||||
...AppEnvironment.variables,
|
||||
...responder.env,
|
||||
'CWS_PROJECT_KEY': project.key,
|
||||
'CWS_REPO': project.repo,
|
||||
|
|
|
|||
|
|
@ -647,13 +647,51 @@ String _expandHome(String path) {
|
|||
return p.normalize(p.absolute(path));
|
||||
}
|
||||
|
||||
final home = AppEnvironment.get('HOME');
|
||||
final home = _resolveHomeDirectory();
|
||||
if (home == null || home.isEmpty) {
|
||||
throw const FileSystemException('HOME is not set.');
|
||||
}
|
||||
return p.normalize(p.join(home, path.substring(2)));
|
||||
}
|
||||
|
||||
String? _resolveHomeDirectory() {
|
||||
final home = _readEnvironmentValue('HOME');
|
||||
if (home != null && home.isNotEmpty) {
|
||||
return home;
|
||||
}
|
||||
|
||||
final userProfile = _readEnvironmentValue('USERPROFILE');
|
||||
if (userProfile != null && userProfile.isNotEmpty) {
|
||||
return userProfile;
|
||||
}
|
||||
|
||||
final homeDrive = _readEnvironmentValue('HOMEDRIVE');
|
||||
final homePath = _readEnvironmentValue('HOMEPATH');
|
||||
if (homeDrive != null &&
|
||||
homeDrive.isNotEmpty &&
|
||||
homePath != null &&
|
||||
homePath.isNotEmpty) {
|
||||
return '$homeDrive$homePath';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _readEnvironmentValue(String key) {
|
||||
final direct = AppEnvironment.get(key);
|
||||
if (direct != null && direct.isNotEmpty) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
for (final entry in AppEnvironment.variables.entries) {
|
||||
if (entry.key.toLowerCase() == key.toLowerCase() &&
|
||||
entry.value.isNotEmpty) {
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _expandOptionalPath(String? path) {
|
||||
if (path == null || path.isEmpty) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:github/github.dart';
|
|||
import '../app_logger.dart';
|
||||
import '../config.dart';
|
||||
import '../issue_tracker_client.dart';
|
||||
import '../process_launcher.dart';
|
||||
import 'base.dart';
|
||||
|
||||
class GiteaGosmeeIssueEventSource implements IssueEventSource {
|
||||
|
|
@ -107,7 +108,7 @@ class GiteaGosmeeIssueEventSource implements IssueEventSource {
|
|||
'gitea/gosmee project=${project.key} start_client '
|
||||
'command="$gosmeeCommand client $publicUrl $localUrl"',
|
||||
);
|
||||
final process = await Process.start(gosmeeCommand, [
|
||||
final process = await startExternalCommand(gosmeeCommand, [
|
||||
'client',
|
||||
publicUrl,
|
||||
localUrl,
|
||||
|
|
@ -284,7 +285,7 @@ class GiteaGosmeeIssueEventSource implements IssueEventSource {
|
|||
}
|
||||
|
||||
Future<String> _createGosmeeChannelUrl(String localUrl) async {
|
||||
final result = await Process.run(gosmeeCommand, [
|
||||
final result = await runExternalCommand(gosmeeCommand, [
|
||||
'--output',
|
||||
'json',
|
||||
'client',
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:github/github.dart';
|
|||
import '../app_logger.dart';
|
||||
import '../config.dart';
|
||||
import '../issue_tracker_client.dart';
|
||||
import '../process_launcher.dart';
|
||||
import 'base.dart';
|
||||
|
||||
class GitHubGosmeeIssueEventSource implements IssueEventSource {
|
||||
|
|
@ -107,7 +108,7 @@ class GitHubGosmeeIssueEventSource implements IssueEventSource {
|
|||
'github/gosmee project=${project.key} start_client '
|
||||
'command="$gosmeeCommand client $publicUrl $localUrl"',
|
||||
);
|
||||
final process = await Process.start(gosmeeCommand, [
|
||||
final process = await startExternalCommand(gosmeeCommand, [
|
||||
'client',
|
||||
publicUrl,
|
||||
localUrl,
|
||||
|
|
@ -284,7 +285,7 @@ class GitHubGosmeeIssueEventSource implements IssueEventSource {
|
|||
}
|
||||
|
||||
Future<String> _createGosmeeChannelUrl(String localUrl) async {
|
||||
final result = await Process.run(gosmeeCommand, [
|
||||
final result = await runExternalCommand(gosmeeCommand, [
|
||||
'--output',
|
||||
'json',
|
||||
'client',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'dart:io';
|
|||
import '../app_logger.dart';
|
||||
import '../config.dart';
|
||||
import '../issue_tracker_client.dart';
|
||||
import '../process_launcher.dart';
|
||||
import 'base.dart';
|
||||
|
||||
class GitHubWebhookForwardIssueEventSource implements IssueEventSource {
|
||||
|
|
@ -80,7 +81,7 @@ class GitHubWebhookForwardIssueEventSource implements IssueEventSource {
|
|||
'github/webhook-forward project=${project.key} start_forwarder '
|
||||
'command="${issueTrackerClient.ghCommand} webhook forward --events=issues,issue_comment --repo=${project.repo} --url=$url"',
|
||||
);
|
||||
final process = await Process.start(issueTrackerClient.ghCommand, [
|
||||
final process = await startExternalCommand(issueTrackerClient.ghCommand, [
|
||||
'webhook',
|
||||
'forward',
|
||||
'--events=issues,issue_comment',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import 'dart:io';
|
|||
|
||||
import 'package:github/github.dart';
|
||||
|
||||
import 'process_launcher.dart';
|
||||
|
||||
import 'config_schema.dart';
|
||||
|
||||
class IssueTrackerClient {
|
||||
|
|
@ -319,7 +321,7 @@ class IssueTrackerClient {
|
|||
}
|
||||
|
||||
Future<Object?> _runJson(String command, List<String> arguments) async {
|
||||
final result = await Process.run(command, arguments);
|
||||
final result = await runExternalCommand(command, arguments);
|
||||
if (result.exitCode != 0) {
|
||||
throw ProcessException(
|
||||
command,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class _ResolvedCommand {
|
||||
_ResolvedCommand({
|
||||
required this.executable,
|
||||
required this.arguments,
|
||||
required this.usesBashShim,
|
||||
});
|
||||
|
||||
final String executable;
|
||||
final List<String> arguments;
|
||||
final bool usesBashShim;
|
||||
}
|
||||
|
||||
_ResolvedCommand _resolveCommand(String executable, List<String> arguments) {
|
||||
final extension = p.extension(executable).toLowerCase();
|
||||
final shouldUseBash = extension.isEmpty || extension == '.sh';
|
||||
|
||||
if (Platform.isWindows && shouldUseBash && File(executable).existsSync()) {
|
||||
final bashScriptPath = _toBashPath(executable);
|
||||
return _ResolvedCommand(
|
||||
executable: 'wsl.exe',
|
||||
arguments: ['--exec', 'bash', bashScriptPath, ...arguments],
|
||||
usesBashShim: true,
|
||||
);
|
||||
}
|
||||
|
||||
return _ResolvedCommand(
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
usesBashShim: false,
|
||||
);
|
||||
}
|
||||
|
||||
String _toBashPath(String windowsPath) {
|
||||
final normalized = windowsPath.replaceAll('\\', '/');
|
||||
final drivePrefixMatch = RegExp(r'^([A-Za-z]):/(.*)$').firstMatch(normalized);
|
||||
if (drivePrefixMatch == null) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
final driveLetter = drivePrefixMatch.group(1)!.toLowerCase();
|
||||
final rest = drivePrefixMatch.group(2)!;
|
||||
return '/mnt/$driveLetter/$rest';
|
||||
}
|
||||
|
||||
Future<ProcessResult> runExternalCommand(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
String? workingDirectory,
|
||||
Map<String, String>? environment,
|
||||
bool includeParentEnvironment = true,
|
||||
}) {
|
||||
final resolved = _resolveCommand(executable, arguments);
|
||||
final effectiveEnvironment = _effectiveEnvironmentForResolvedCommand(
|
||||
environment: environment,
|
||||
includeParentEnvironment: includeParentEnvironment,
|
||||
usesBashShim: resolved.usesBashShim,
|
||||
);
|
||||
return Process.run(
|
||||
resolved.executable,
|
||||
resolved.arguments,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: effectiveEnvironment,
|
||||
includeParentEnvironment: includeParentEnvironment,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Process> startExternalCommand(
|
||||
String executable,
|
||||
List<String> arguments, {
|
||||
String? workingDirectory,
|
||||
Map<String, String>? environment,
|
||||
bool includeParentEnvironment = true,
|
||||
bool runInShell = false,
|
||||
ProcessStartMode mode = ProcessStartMode.normal,
|
||||
}) {
|
||||
final resolved = _resolveCommand(executable, arguments);
|
||||
final effectiveEnvironment = _effectiveEnvironmentForResolvedCommand(
|
||||
environment: environment,
|
||||
includeParentEnvironment: includeParentEnvironment,
|
||||
usesBashShim: resolved.usesBashShim,
|
||||
);
|
||||
return Process.start(
|
||||
resolved.executable,
|
||||
resolved.arguments,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: effectiveEnvironment,
|
||||
includeParentEnvironment: includeParentEnvironment,
|
||||
runInShell: runInShell,
|
||||
mode: mode,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, String>? _effectiveEnvironmentForResolvedCommand({
|
||||
required Map<String, String>? environment,
|
||||
required bool includeParentEnvironment,
|
||||
required bool usesBashShim,
|
||||
}) {
|
||||
if (!usesBashShim || environment == null || environment.isEmpty) {
|
||||
return environment;
|
||||
}
|
||||
|
||||
final effectiveEnvironment = Map<String, String>.from(environment);
|
||||
final existingWslenvFromProcess = includeParentEnvironment
|
||||
? Platform.environment['WSLENV']
|
||||
: null;
|
||||
final existingWslenv =
|
||||
effectiveEnvironment['WSLENV'] ?? existingWslenvFromProcess;
|
||||
final forwardedKeys = <String>{
|
||||
...?existingWslenv?.split(':').where((entry) => entry.isNotEmpty),
|
||||
...effectiveEnvironment.keys.where((key) => key != 'WSLENV'),
|
||||
};
|
||||
effectiveEnvironment['WSLENV'] = forwardedKeys.join(':');
|
||||
return effectiveEnvironment;
|
||||
}
|
||||
10
pubspec.yaml
10
pubspec.yaml
|
|
@ -4,7 +4,7 @@ version: 1.0.0
|
|||
# repository: https://github.com/my_org/my_repo
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.4
|
||||
sdk: ^3.11.3
|
||||
|
||||
dependencies:
|
||||
args: ^2.7.0
|
||||
|
|
@ -30,3 +30,11 @@ dev_dependencies:
|
|||
json_serializable: ">=6.11.1 <6.12.0"
|
||||
lints: ^6.0.0
|
||||
test: ^1.25.6
|
||||
|
||||
platforms:
|
||||
# android:
|
||||
# ios:
|
||||
linux:
|
||||
macos:
|
||||
# web:
|
||||
windows:
|
||||
|
|
|
|||
|
|
@ -194,77 +194,77 @@
|
|||
"dart-api-design": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "d60beeedc91e1be7c51d7fea38acb636c0519b8140159402afcf69f31846cfba"
|
||||
"computedHash": "00f2a541ca05387967b6e44afbce8b3e235a45225e06a376ec98e91116b76c09"
|
||||
},
|
||||
"dart-async-programming": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "ebff987d2ce82a0c0eebab1b7ebc724d29bb925c56273ee55775550d634a3c72"
|
||||
"computedHash": "52f989f2cb1121b8c5122d4cf6c9dbe7283523e7144e67e1f86640d33a82ce28"
|
||||
},
|
||||
"dart-code-generation": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "259e371481a822984e666cc5214c97ac1581a0adbbdb4d7cab3997c9b1de2009"
|
||||
"computedHash": "815a84815bbe6681129a6d883278fa6d251d3f4e5a6509e8e1ca74cab4422279"
|
||||
},
|
||||
"dart-compilation-deployment": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "862ea7fd5efb9bd9760d9eab58880a1f08bb9b64125f171b778ba6918f79980f"
|
||||
"computedHash": "b327d665fd5514cd0e1a193c8c51745405d7476a5b8419c5d9228324b6afa7d9"
|
||||
},
|
||||
"dart-concurrency-isolates": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "829a819bb37342813eca318e0008d93db83aa313daa58be1b9a508d5b1674a9f"
|
||||
"computedHash": "a3e7ff69a58c462d3ea94d7092d1d03e21eeb43bdbb21174400a111da4a96721"
|
||||
},
|
||||
"dart-documentation": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "0016010518730eb2aeaabbf5046667deb724040be855a5892db4a9735608ca75"
|
||||
"computedHash": "5eb2845edf402683555208ede425b217484fc1fe21f63131fefe9b249f97697a"
|
||||
},
|
||||
"dart-effective-style": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "ceaa8cbe82090bf759d763cdc83aa80df79ac2461d0d5f223f63e72f60901ae4"
|
||||
"computedHash": "3b11a0ad963151c12bbf0302cd526dcfdfa6659e194455f168d8441fae60e895"
|
||||
},
|
||||
"dart-idiomatic-usage": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "ac383cc321ce8eeac6412eecc466e68adfcb131fd98c5b74af9d4b33c64381bf"
|
||||
"computedHash": "c45023b434e63316dd209afc094f7412fd99683869b917f140918d0c065406c8"
|
||||
},
|
||||
"dart-language-syntax": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "d1b1f4027530bb736247f62135845c06f69148cd3464fc235f942324499aee54"
|
||||
"computedHash": "457294ca1c8e21c0ceb643ded8dfa1a2df1a542bea5825c95d331b99d119bd25"
|
||||
},
|
||||
"dart-migration-versioning": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "c3fefb316b55d7794a49bc226cee17a2fafc00d75621e23aee1dd3f8c15def3a"
|
||||
"computedHash": "f94fb07b64c2a4f1922504bf4de531de927e076a74b3cbd291a75763c3a7d6cb"
|
||||
},
|
||||
"dart-native-interop-ffi": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "cac814c48f43b964471b41e99622b471203bb2900845bcc03eca02b2a3162ed5"
|
||||
"computedHash": "0e29e6b5e8cb803d7644c6f13340cc1e3265d23f068be4da0f0781954737b3c6"
|
||||
},
|
||||
"dart-package-management": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "a1fbb5422c3d9bf980a310b55f8cb938b969234eda60a27038f65833e5d0e805"
|
||||
"computedHash": "569d2a0bcb4b61a15fb8e0f3406d9c54ddc9e1caf44c92302047b91730bd9320"
|
||||
},
|
||||
"dart-static-analysis": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "f13d7c751ed2d026da741fe7b2535fb11a8d045c48ee91b48c0bf7b8147a3758"
|
||||
"computedHash": "6258439460032ed8204d18abb0f09112ad7969d2497221693b302ce800f12106"
|
||||
},
|
||||
"dart-testing": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "c6857b34f27d704eadbb770e40512693e18e04bab7345c23818386a6996dea6a"
|
||||
"computedHash": "2d8a5d92cfa90dd5ad13f8f38606bdcb6edec8fc344fde02f51475a29f05d87a"
|
||||
},
|
||||
"dart-web-development": {
|
||||
"source": "dart-lang/skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "2adc4b77a7d30a2c9c52fc96368df10a8242512c95c8bf2b3fc06abf208dc421"
|
||||
"computedHash": "db064015bed95259c6f60ba6eb5123a97d6341e68a0925a60a3d57a31bf71d6c"
|
||||
},
|
||||
"datanalysis-credit-risk": {
|
||||
"source": "github/awesome-copilot",
|
||||
|
|
@ -839,7 +839,7 @@
|
|||
"supabase-postgres-best-practices": {
|
||||
"source": "supabase/agent-skills",
|
||||
"sourceType": "github",
|
||||
"computedHash": "9c87c315aed143ee3b34bec8117100f5035e0df09e6b23e1ecc772cff434c9ad"
|
||||
"computedHash": "f4cd7570115d97af6bcb2946eb891a958c8d04a43b479871c9ccadbc670ad058"
|
||||
},
|
||||
"technology-stack-blueprint-generator": {
|
||||
"source": "github/awesome-copilot",
|
||||
|
|
@ -944,7 +944,7 @@
|
|||
"worktrunk": {
|
||||
"source": "max-sixty/worktrunk",
|
||||
"sourceType": "github",
|
||||
"computedHash": "12ed42480bc641bb017955e0884716ccf9d04389e744edad6dfcac4b01c60cca"
|
||||
"computedHash": "d480468e16c7de39a121e6e760788357bb178495f8912b05f05f55977468d72e"
|
||||
},
|
||||
"write-coding-standards-from-file": {
|
||||
"source": "github/awesome-copilot",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,40 @@ import 'package:drift/drift.dart' show driftRuntimeOptions;
|
|||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
String _resolveTestHomeDirectory() {
|
||||
String? getEnv(String key) {
|
||||
final direct = Platform.environment[key];
|
||||
if (direct != null && direct.isNotEmpty) {
|
||||
return direct;
|
||||
}
|
||||
for (final entry in Platform.environment.entries) {
|
||||
if (entry.key.toLowerCase() == key.toLowerCase() &&
|
||||
entry.value.isNotEmpty) {
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final home = getEnv('HOME');
|
||||
if (home != null) {
|
||||
return home;
|
||||
}
|
||||
|
||||
final userProfile = getEnv('USERPROFILE');
|
||||
if (userProfile != null) {
|
||||
return userProfile;
|
||||
}
|
||||
|
||||
final homeDrive = getEnv('HOMEDRIVE');
|
||||
final homePath = getEnv('HOMEPATH');
|
||||
if (homeDrive != null && homePath != null) {
|
||||
return '$homeDrive$homePath';
|
||||
}
|
||||
|
||||
throw StateError('No home directory environment variables are set.');
|
||||
}
|
||||
|
||||
void main() {
|
||||
driftRuntimeOptions.dontWarnAboutMultipleDatabases = true;
|
||||
|
||||
|
|
@ -358,16 +392,11 @@ projects:
|
|||
|
||||
// When loading the app config from disk.
|
||||
final config = await AppConfig.load(configFile.path);
|
||||
final home = _resolveTestHomeDirectory();
|
||||
|
||||
// Then the app exposes the configured data and worktree directories.
|
||||
expect(
|
||||
config.dataDir,
|
||||
p.join(Platform.environment['HOME']!, '.agent-orchestrator'),
|
||||
);
|
||||
expect(
|
||||
config.worktreeDir,
|
||||
p.join(Platform.environment['HOME']!, '.worktrees'),
|
||||
);
|
||||
expect(config.dataDir, p.join(home, '.agent-orchestrator'));
|
||||
expect(config.worktreeDir, p.join(home, '.worktrees'));
|
||||
|
||||
// And each project keeps the configured camelCase settings.
|
||||
expect(config.projects['sample'], isNotNull);
|
||||
|
|
@ -809,24 +838,19 @@ projects:
|
|||
// When writing the generated starter config to disk.
|
||||
await configFile.writeAsString(contents);
|
||||
final config = await AppConfig.load(configFile.path);
|
||||
final home = _resolveTestHomeDirectory();
|
||||
|
||||
// Then the file parses through the normal app config loader.
|
||||
expect(config.projects['sample'], isNotNull);
|
||||
|
||||
// And the required starter values stay available to users.
|
||||
expect(
|
||||
config.dataDir,
|
||||
p.join(Platform.environment['HOME']!, '.agent-orchestrator'),
|
||||
);
|
||||
expect(config.dataDir, p.join(home, '.agent-orchestrator'));
|
||||
expect(config.projects['sample']!.provider, IssueTrackerProvider.github);
|
||||
expect(
|
||||
config.worktreeDir,
|
||||
p.join(Platform.environment['HOME']!, '.worktrees'),
|
||||
);
|
||||
expect(config.worktreeDir, p.join(home, '.worktrees'));
|
||||
expect(config.projects['sample']!.repo, 'owner/repo');
|
||||
expect(
|
||||
config.projects['sample']!.path,
|
||||
p.join(Platform.environment['HOME']!, 'path/to/repo'),
|
||||
p.join(home, 'path', 'to', 'repo'),
|
||||
);
|
||||
expect(config.projects['sample']!.defaultBranch, 'main');
|
||||
expect(config.projects['sample']!.issueAssistant.mentionTriggers, [
|
||||
|
|
@ -5,7 +5,7 @@ import 'package:drift/drift.dart' show driftRuntimeOptions;
|
|||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'test_support.dart';
|
||||
import '../test_support.dart';
|
||||
|
||||
void registerIssueAssistantAppRunOnceConcurrencyTests() {
|
||||
/// ```gherkin
|
||||
|
|
@ -5,7 +5,7 @@ import 'package:drift/drift.dart' show driftRuntimeOptions;
|
|||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'test_support.dart';
|
||||
import '../test_support.dart';
|
||||
|
||||
void registerIssueAssistantAppRunOnceCoreTests() {
|
||||
/// ```gherkin
|
||||
|
|
@ -5,7 +5,7 @@ import 'package:drift/drift.dart' show driftRuntimeOptions;
|
|||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'test_support.dart';
|
||||
import '../test_support.dart';
|
||||
|
||||
void registerIssueAssistantAppRunOnceMultiProjectTests() {
|
||||
/// ```gherkin
|
||||
|
|
@ -6,7 +6,7 @@ import 'package:drift/drift.dart' show driftRuntimeOptions;
|
|||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'test_support.dart';
|
||||
import '../test_support.dart';
|
||||
|
||||
void registerIssueAssistantAppRunRetryTests() {
|
||||
/// ```gherkin
|
||||
|
|
@ -76,7 +76,7 @@ void registerIssueAssistantAppRunGosmeeTests() {
|
|||
final postedBody = File(p.join(sandbox.path, 'posted-body.md'));
|
||||
await teaScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf '%s\n' "\$*" >> "${teaLog.path}"
|
||||
printf '%s\n' "\$*" >> "${bashScriptPath(teaLog.path)}"
|
||||
|
||||
if [[ "\$1" == "api" && "\$2" == "user" ]]; then
|
||||
cat <<'JSON'
|
||||
|
|
@ -102,7 +102,7 @@ fi
|
|||
if [[ "\$1" == "api" && "\$4" == "repos/owner/sample/issues/12/comments" ]]; then
|
||||
for arg in "\$@"; do
|
||||
if [[ "\$arg" == body=* ]]; then
|
||||
printf '%s' "\${arg#body=}" > "${postedBody.path}"
|
||||
printf '%s' "\${arg#body=}" > "${bashScriptPath(postedBody.path)}"
|
||||
fi
|
||||
done
|
||||
cat <<'JSON'
|
||||
|
|
@ -125,7 +125,7 @@ fi
|
|||
echo "unexpected tea args: \$*" >&2
|
||||
exit 1
|
||||
''');
|
||||
await Process.run('chmod', ['+x', teaScript.path]);
|
||||
await makeScriptExecutable(teaScript);
|
||||
|
||||
// And a responder that emits a planning reply.
|
||||
final responderScript = File(p.join(sandbox.path, 'responder.sh'));
|
||||
|
|
@ -226,7 +226,7 @@ projects:
|
|||
final gosmeeLog = File(p.join(sandbox.path, 'gosmee-log.jsonl'));
|
||||
await gosmeeScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf '%s\n' "\$*" >> "${gosmeeLog.path}"
|
||||
printf '%s\n' "\$*" >> "${bashScriptPath(gosmeeLog.path)}"
|
||||
|
||||
if [[ "\$1" == "--output" && "\$2" == "json" && "\$3" == "client" && "\$4" == "--new-url" ]]; then
|
||||
printf '%s\n' 'https://gosmee.example.test/reconcile'
|
||||
|
|
@ -242,7 +242,7 @@ fi
|
|||
echo "unexpected gosmee args: \$*" >&2
|
||||
exit 1
|
||||
''');
|
||||
await Process.run('chmod', ['+x', gosmeeScript.path]);
|
||||
await makeScriptExecutable(gosmeeScript);
|
||||
|
||||
// And a tea script that returns a new issue only on a later reconciliation poll.
|
||||
final teaScript = File(p.join(sandbox.path, 'tea'));
|
||||
|
|
@ -272,7 +272,7 @@ exit 1
|
|||
];
|
||||
await teaScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf '%s\n' "\$*" >> "${teaLog.path}"
|
||||
printf '%s\n' "\$*" >> "${bashScriptPath(teaLog.path)}"
|
||||
|
||||
if [[ "\$1" == "api" && "\$2" == "user" ]]; then
|
||||
cat <<'JSON'
|
||||
|
|
@ -283,11 +283,11 @@ fi
|
|||
|
||||
if [[ "\$1" == "api" && "\$4" == repos/owner/sample/issues\\?state=open* ]]; then
|
||||
count=0
|
||||
if [[ -f "${issueListCount.path}" ]]; then
|
||||
count="\$(cat "${issueListCount.path}")"
|
||||
if [[ -f "${bashScriptPath(issueListCount.path)}" ]]; then
|
||||
count="\$(cat "${bashScriptPath(issueListCount.path)}")"
|
||||
fi
|
||||
count=\$((count + 1))
|
||||
printf '%s' "\$count" > "${issueListCount.path}"
|
||||
printf '%s' "\$count" > "${bashScriptPath(issueListCount.path)}"
|
||||
if [[ "\$count" -eq 1 ]]; then
|
||||
cat <<'JSON'
|
||||
[]
|
||||
|
|
@ -310,7 +310,7 @@ fi
|
|||
if [[ "\$1" == "api" && "\$4" == "repos/owner/sample/issues/18/comments" ]]; then
|
||||
for arg in "\$@"; do
|
||||
if [[ "\$arg" == body=* ]]; then
|
||||
printf '%s' "\${arg#body=}" > "${postedBody.path}"
|
||||
printf '%s' "\${arg#body=}" > "${bashScriptPath(postedBody.path)}"
|
||||
fi
|
||||
done
|
||||
cat <<'JSON'
|
||||
|
|
@ -333,7 +333,7 @@ fi
|
|||
echo "unexpected tea args: \$*" >&2
|
||||
exit 1
|
||||
''');
|
||||
await Process.run('chmod', ['+x', teaScript.path]);
|
||||
await makeScriptExecutable(teaScript);
|
||||
|
||||
// And a responder that emits a planning reply.
|
||||
final responderScript = File(p.join(sandbox.path, 'responder.sh'));
|
||||
|
|
@ -178,7 +178,7 @@ fi
|
|||
echo "unexpected gh args: \$*" >&2
|
||||
exit 1
|
||||
''');
|
||||
await Process.run('chmod', ['+x', ghScript.path]);
|
||||
await makeScriptExecutable(ghScript);
|
||||
|
||||
// And an orchestrator-style config that uses gh/webhook-forward.
|
||||
final configFile = File(p.join(sandbox.path, 'agent-orchestrator.yaml'));
|
||||
|
|
@ -284,7 +284,7 @@ projects:
|
|||
];
|
||||
await ghScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf '%s\n' "\$*" >> "${ghLog.path}"
|
||||
printf '%s\n' "\$*" >> "${bashScriptPath(ghLog.path)}"
|
||||
|
||||
if [[ "\$1" == "api" && "\$2" == "user" ]]; then
|
||||
cat <<'JSON'
|
||||
|
|
@ -295,11 +295,11 @@ fi
|
|||
|
||||
if [[ "\$1" == "api" && "\$4" == "search/issues" ]]; then
|
||||
count=0
|
||||
if [[ -f "${searchCountFile.path}" ]]; then
|
||||
count="\$(cat "${searchCountFile.path}")"
|
||||
if [[ -f "${bashScriptPath(searchCountFile.path)}" ]]; then
|
||||
count="\$(cat "${bashScriptPath(searchCountFile.path)}")"
|
||||
fi
|
||||
count=\$((count + 1))
|
||||
printf '%s' "\$count" > "${searchCountFile.path}"
|
||||
printf '%s' "\$count" > "${bashScriptPath(searchCountFile.path)}"
|
||||
if [[ "\$count" -eq 1 ]]; then
|
||||
cat <<'JSON'
|
||||
{"total_count":0,"incomplete_results":false,"items":[]}
|
||||
|
|
@ -326,7 +326,7 @@ fi
|
|||
if [[ "\$1" == "api" && "\$4" == "repos/owner/sample/issues/18/comments" ]]; then
|
||||
for arg in "\$@"; do
|
||||
if [[ "\$arg" == body=* ]]; then
|
||||
printf '%s' "\${arg#body=}" > "${postedBody.path}"
|
||||
printf '%s' "\${arg#body=}" > "${bashScriptPath(postedBody.path)}"
|
||||
fi
|
||||
done
|
||||
cat <<'JSON'
|
||||
|
|
@ -344,7 +344,7 @@ fi
|
|||
echo "unexpected gh args: \$*" >&2
|
||||
exit 1
|
||||
''');
|
||||
await Process.run('chmod', ['+x', ghScript.path]);
|
||||
await makeScriptExecutable(ghScript);
|
||||
|
||||
// And a responder that emits a planning reply.
|
||||
final responderScript = File(p.join(sandbox.path, 'responder.sh'));
|
||||
|
|
@ -7,7 +7,7 @@ import 'package:drift/drift.dart' show driftRuntimeOptions;
|
|||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'test_support.dart';
|
||||
import '../test_support.dart';
|
||||
|
||||
void registerIssueAssistantAppRunOnceNotificationTests() {
|
||||
/// ```gherkin
|
||||
|
|
@ -4,6 +4,54 @@ import 'dart:io';
|
|||
import 'package:git/git.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
Future<void> makeScriptExecutable(File script) async {
|
||||
if (Platform.isWindows) {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await Process.run('chmod', ['+x', script.path]);
|
||||
if (result.exitCode != 0) {
|
||||
throw ProcessException(
|
||||
'chmod',
|
||||
['+x', script.path],
|
||||
'${result.stdout}\n${result.stderr}',
|
||||
result.exitCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String bashScriptPath(String hostPath) {
|
||||
if (!Platform.isWindows) {
|
||||
return hostPath;
|
||||
}
|
||||
|
||||
final normalized = hostPath.replaceAll('\\', '/');
|
||||
final drivePrefixMatch = RegExp(r'^([A-Za-z]):/(.*)$').firstMatch(normalized);
|
||||
if (drivePrefixMatch == null) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
final driveLetter = drivePrefixMatch.group(1)!.toLowerCase();
|
||||
final rest = drivePrefixMatch.group(2)!;
|
||||
return '/mnt/$driveLetter/$rest';
|
||||
}
|
||||
|
||||
String hostPathFromScript(String scriptPath) {
|
||||
if (!Platform.isWindows) {
|
||||
return scriptPath;
|
||||
}
|
||||
|
||||
final trimmed = scriptPath.trim();
|
||||
final mntPathMatch = RegExp(r'^/mnt/([a-zA-Z])/(.*)$').firstMatch(trimmed);
|
||||
if (mntPathMatch == null) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
final driveLetter = mntPathMatch.group(1)!.toUpperCase();
|
||||
final rest = mntPathMatch.group(2)!.replaceAll('/', '\\');
|
||||
return '$driveLetter:\\$rest';
|
||||
}
|
||||
|
||||
Future<void> writeFakeGhScript({
|
||||
required File ghScript,
|
||||
required List<Map<String, Object?>> issueListResponse,
|
||||
|
|
@ -71,7 +119,7 @@ Future<void> writeFakeGhScript({
|
|||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${ghLog.path}"',
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +202,7 @@ Future<void> writeFakeGhScript({
|
|||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${postedBodyFile.path}"',
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
|
|
@ -179,7 +227,7 @@ Future<void> writeFakeGhScript({
|
|||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await Process.run('chmod', ['+x', ghScript.path]);
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
||||
Future<void> writeFlakySearchGhScript({
|
||||
|
|
@ -210,7 +258,7 @@ Future<void> writeFlakySearchGhScript({
|
|||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${ghLog.path}"',
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -224,8 +272,10 @@ Future<void> writeFlakySearchGhScript({
|
|||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
|
||||
..writeln(' if [[ ! -f "${failureStateFile.path}" ]]; then')
|
||||
..writeln(' touch "${failureStateFile.path}"')
|
||||
..writeln(
|
||||
' if [[ ! -f "${bashScriptPath(failureStateFile.path)}" ]]; then',
|
||||
)
|
||||
..writeln(' touch "${bashScriptPath(failureStateFile.path)}"')
|
||||
..writeln(r''' echo "error connecting to api.github.com" >&2''')
|
||||
..writeln(
|
||||
r''' echo "check your internet connection or https://githubstatus.com" >&2''',
|
||||
|
|
@ -256,7 +306,7 @@ Future<void> writeFlakySearchGhScript({
|
|||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await Process.run('chmod', ['+x', ghScript.path]);
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
||||
Future<void> writeFakeTeaScript({
|
||||
|
|
@ -287,7 +337,7 @@ Future<void> writeFakeTeaScript({
|
|||
if (teaLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${teaLog.path}"',
|
||||
'"${bashScriptPath(teaLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -352,7 +402,7 @@ Future<void> writeFakeTeaScript({
|
|||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${postedBodyFile.path}"',
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
|
|
@ -377,7 +427,7 @@ Future<void> writeFakeTeaScript({
|
|||
..writeln('exit 1');
|
||||
|
||||
await teaScript.writeAsString(buffer.toString());
|
||||
await Process.run('chmod', ['+x', teaScript.path]);
|
||||
await makeScriptExecutable(teaScript);
|
||||
}
|
||||
|
||||
Future<void> writeResponderScript(
|
||||
|
|
@ -395,7 +445,7 @@ ${jsonEncode(response)}
|
|||
JSON
|
||||
exit $exitCode
|
||||
''');
|
||||
await Process.run('chmod', ['+x', responderScript.path]);
|
||||
await makeScriptExecutable(responderScript);
|
||||
}
|
||||
|
||||
Future<void> writeJsonlResponderScript(
|
||||
|
|
@ -412,7 +462,7 @@ cat <<'JSON'
|
|||
{"type":"turn.completed"}
|
||||
JSON
|
||||
''');
|
||||
await Process.run('chmod', ['+x', responderScript.path]);
|
||||
await makeScriptExecutable(responderScript);
|
||||
}
|
||||
|
||||
Future<void> writeDelayedLoggingResponderScript(
|
||||
|
|
@ -429,15 +479,15 @@ import time
|
|||
print(int(time.time() * 1000))
|
||||
PY
|
||||
}
|
||||
printf 'start:%s:%s\n' "\$CWS_ISSUE_NUMBER" "\$(timestamp_ms)" >> "${eventLog.path}"
|
||||
printf 'start:%s:%s\n' "\$CWS_ISSUE_NUMBER" "\$(timestamp_ms)" >> "${bashScriptPath(eventLog.path)}"
|
||||
cat >/dev/null
|
||||
sleep ${delay.inSeconds}
|
||||
printf 'end:%s:%s\n' "\$CWS_ISSUE_NUMBER" "\$(timestamp_ms)" >> "${eventLog.path}"
|
||||
printf 'end:%s:%s\n' "\$CWS_ISSUE_NUMBER" "\$(timestamp_ms)" >> "${bashScriptPath(eventLog.path)}"
|
||||
cat <<'JSON'
|
||||
${jsonEncode(response)}
|
||||
JSON
|
||||
''');
|
||||
await Process.run('chmod', ['+x', responderScript.path]);
|
||||
await makeScriptExecutable(responderScript);
|
||||
}
|
||||
|
||||
Future<void> writeWebhookForwardGhScript({
|
||||
|
|
@ -478,7 +528,7 @@ Future<void> writeWebhookForwardGhScript({
|
|||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${ghLog.path}"',
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -521,7 +571,7 @@ Future<void> writeWebhookForwardGhScript({
|
|||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${postedBodyFile.path}"',
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
|
|
@ -567,7 +617,7 @@ Future<void> writeWebhookForwardGhScript({
|
|||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await Process.run('chmod', ['+x', ghScript.path]);
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
||||
Future<void> writeGitHubGosmeeGhScript({
|
||||
|
|
@ -601,7 +651,7 @@ Future<void> writeGitHubGosmeeGhScript({
|
|||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${ghLog.path}"',
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -668,7 +718,7 @@ Future<void> writeGitHubGosmeeGhScript({
|
|||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${postedBodyFile.path}"',
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
|
|
@ -698,7 +748,7 @@ Future<void> writeGitHubGosmeeGhScript({
|
|||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await Process.run('chmod', ['+x', ghScript.path]);
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
||||
Future<void> writeGosmeeForwardScript({
|
||||
|
|
@ -714,7 +764,7 @@ Future<void> writeGosmeeForwardScript({
|
|||
if (gosmeeLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${gosmeeLog.path}"',
|
||||
'"${bashScriptPath(gosmeeLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -757,7 +807,7 @@ Future<void> writeGosmeeForwardScript({
|
|||
..writeln('exit 1');
|
||||
|
||||
await gosmeeScript.writeAsString(buffer.toString());
|
||||
await Process.run('chmod', ['+x', gosmeeScript.path]);
|
||||
await makeScriptExecutable(gosmeeScript);
|
||||
}
|
||||
|
||||
Future<void> initGitRepo(Directory directory) async {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import 'package:code_work_spawner/code_work_spawner.dart';
|
|||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'test_support.dart';
|
||||
import '../test_support.dart';
|
||||
|
||||
void main() {
|
||||
/// ```gherkin
|
||||
|
|
@ -5,7 +5,7 @@ import 'package:drift/drift.dart' show driftRuntimeOptions;
|
|||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'test_support.dart';
|
||||
import '../test_support.dart';
|
||||
|
||||
void registerIssueAssistantAppRunOnceWorktreeTests() {
|
||||
/// ```gherkin
|
||||
|
|
@ -78,13 +78,13 @@ void registerIssueAssistantAppRunOnceWorktreeTests() {
|
|||
);
|
||||
await responderScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
pwd > "${cwdCapture.path}"
|
||||
cat > "${stdinCapture.path}"
|
||||
pwd > "${bashScriptPath(cwdCapture.path)}"
|
||||
cat > "${bashScriptPath(stdinCapture.path)}"
|
||||
cat <<'JSON'
|
||||
{"decision":"no_reply","mode":"planning","summary":"captured mention context"}
|
||||
JSON
|
||||
''');
|
||||
await Process.run('chmod', ['+x', responderScript.path]);
|
||||
await makeScriptExecutable(responderScript);
|
||||
|
||||
// And an orchestrator-style config pointing to the fake repo and responder.
|
||||
final configFile = File(
|
||||
|
|
@ -115,7 +115,7 @@ projects:
|
|||
await app.close();
|
||||
|
||||
// Then the responder runs from an ephemeral worktree instead of the source checkout.
|
||||
final capturedCwd = await cwdCapture.readAsString();
|
||||
final capturedCwd = hostPathFromScript(await cwdCapture.readAsString());
|
||||
expect(
|
||||
p.normalize(capturedCwd.trim()),
|
||||
isNot(p.normalize(repoDir.path)),
|
||||
|
|
@ -201,13 +201,13 @@ projects:
|
|||
);
|
||||
await responderScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
pwd > "${cwdCapture.path}"
|
||||
pwd > "${bashScriptPath(cwdCapture.path)}"
|
||||
cat >/dev/null
|
||||
cat <<'JSON'
|
||||
{"decision":"reply","mode":"planning","markdown":"planning reply","summary":"used project path"}
|
||||
JSON
|
||||
''');
|
||||
await Process.run('chmod', ['+x', responderScript.path]);
|
||||
await makeScriptExecutable(responderScript);
|
||||
|
||||
// And an orchestrator-style config pointing to the fake repo and responder.
|
||||
final configFile = File(p.join(sandbox.path, 'agent-orchestrator.yaml'));
|
||||
|
|
@ -237,7 +237,7 @@ projects:
|
|||
await app.close();
|
||||
|
||||
// Then the responder runs from an ephemeral worktree instead of the source checkout.
|
||||
final capturedCwd = await cwdCapture.readAsString();
|
||||
final capturedCwd = hostPathFromScript(await cwdCapture.readAsString());
|
||||
expect(p.normalize(capturedCwd.trim()), isNot(p.normalize(repoDir.path)));
|
||||
expect(
|
||||
p.isWithin(worktreeRoot.path, p.normalize(capturedCwd.trim())),
|
||||
Loading…
Reference in New Issue