feat: ask CLI respond in plain text rather than json
This commit is contained in:
parent
ed94fde9e7
commit
5297251e00
|
|
@ -80,7 +80,17 @@ Start from [`examples/simple-github.yaml`](simple-github.yaml) for GitHub,
|
|||
This repo uses `dataDir`, `worktreeDir`, and `projects`, and uses
|
||||
`worktreeDir` for bug-verification worktrees.
|
||||
|
||||
## responder CLI mode
|
||||
|
||||
Configure responders in plain-text mode. Do not add JSON/output-format flags in
|
||||
`responders[].args` (for example `--json`, `--json-output`, `--output-format`,
|
||||
or `-f json`), because `code_work_spawner` owns response-format handling.
|
||||
|
||||
Prompts are passed through stdin via `stdinTemplate` (default: `{{prompt}}`).
|
||||
In most cases prompt flags are not needed in `responders[].args`, because the
|
||||
real prompt already comes from stdin. Only add a prompt flag if a specific CLI
|
||||
fails without it.
|
||||
|
||||
## tea + windows
|
||||
|
||||
The `tea` CLI has known severe issues on Windows due to lipgloss/v2 query console color. We had bypass it by read config file of `tea` directly, but only token login is supported. And need to config a default login through `tea login default <LOGIN>`.
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defaults:
|
|||
responders:
|
||||
- id: codex
|
||||
command: codex
|
||||
args: ["exec", "--json"]
|
||||
args: ["exec"]
|
||||
timeout: 10m
|
||||
|
||||
projects:
|
||||
|
|
|
|||
|
|
@ -25,15 +25,16 @@ defaults:
|
|||
# Replace this with a responder available on your machine.
|
||||
- id: codex
|
||||
command: codex
|
||||
args: ["-s", "danger-full-access", "exec", "--json"]
|
||||
args: ["-s", "danger-full-access", "exec"]
|
||||
timeout: 10m
|
||||
# - id: opencode
|
||||
# command: opencode
|
||||
# args: ["run", "-f", "json"]
|
||||
# args: ["run"]
|
||||
# timeout: 10m
|
||||
# - id: copilot-cli
|
||||
# command: copilot
|
||||
# args: ["-p", "", "--allow-all", "--autopilot", "--silent"]
|
||||
# # Prompt text is sent through stdin, so prompt flags are usually unnecessary.
|
||||
# args: ["--allow-all", "--autopilot", "--silent"]
|
||||
# timeout: 10m
|
||||
# notifications:
|
||||
# # Optional Discord notifications for assistant activity.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defaults:
|
|||
responders:
|
||||
- id: codex
|
||||
command: codex
|
||||
args: ["exec", "--json"]
|
||||
args: ["exec"]
|
||||
timeout: 10m
|
||||
|
||||
projects:
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defaults:
|
|||
responders:
|
||||
- id: codex
|
||||
command: codex
|
||||
args: ["exec", "--json"]
|
||||
args: ["exec"]
|
||||
timeout: 10m
|
||||
|
||||
projects:
|
||||
|
|
|
|||
|
|
@ -494,17 +494,24 @@ class CliResponderConfig {
|
|||
return CliResponderConfig(
|
||||
id: id,
|
||||
command: command,
|
||||
args: _normalizeArgs(document.args ?? const <String>[]),
|
||||
args: _validateArgs(id, document.args ?? const <String>[]),
|
||||
env: document.env ?? const <String, String>{},
|
||||
stdinTemplate: document.stdinTemplate ?? '{{prompt}}',
|
||||
timeout: _parseDuration(document.timeout ?? '10m'),
|
||||
);
|
||||
}
|
||||
|
||||
static List<String> _normalizeArgs(Iterable<String> args) {
|
||||
return args
|
||||
.map((arg) => arg == '--json-output' ? '--json' : arg)
|
||||
.toList(growable: false);
|
||||
static List<String> _validateArgs(String id, Iterable<String> args) {
|
||||
final normalized = args.toList(growable: false);
|
||||
for (final arg in normalized) {
|
||||
if (arg.toLowerCase().contains('json')) {
|
||||
throw FormatException(
|
||||
'Responder "$id" has forbidden arg "$arg": '
|
||||
'JSON/output-format args must not be configured in responders.args.',
|
||||
);
|
||||
}
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1082,15 +1089,15 @@ ${_commentBlock(defaultIssueAssistantPrompt, indent: ' # ')}
|
|||
# responders:
|
||||
# - id: codex
|
||||
# command: codex
|
||||
# args: ["exec", "--json"]
|
||||
# args: ["exec"]
|
||||
# timeout: 10m
|
||||
# - id: opencode
|
||||
# command: opencode
|
||||
# args: ["run", "-f", "json"]
|
||||
# args: ["run"]
|
||||
# timeout: 10m
|
||||
# - id: copilot-cli
|
||||
# command: copilot
|
||||
# args: ["-p", "", "--allow-all", "--autopilot", "--silent"]
|
||||
# args: ["--allow-all", "--autopilot", "--silent"]
|
||||
# timeout: 10m
|
||||
# notifications:
|
||||
# - type: discordWebhook
|
||||
|
|
|
|||
|
|
@ -241,13 +241,13 @@ projects:
|
|||
});
|
||||
|
||||
/// ```gherkin
|
||||
/// Scenario: Normalize legacy codex json flag
|
||||
/// Given an orchestrator config that still uses the legacy codex --json-output flag
|
||||
/// Scenario: Reject responder args that contain json output flags
|
||||
/// Given an orchestrator config that configures a responder json output argument
|
||||
/// When loading the app config from disk
|
||||
/// Then the responder args are normalized to the current codex --json flag
|
||||
/// Then loading fails with a format exception about forbidden args
|
||||
/// ```
|
||||
test('Normalize legacy codex json flag', () async {
|
||||
// Given an orchestrator config that still uses the legacy codex --json-output flag.
|
||||
test('Reject responder args that contain json output flags', () async {
|
||||
// Given an orchestrator config that configures a responder json output argument.
|
||||
final tempDir = await Directory.systemTemp.createTemp('cws-codex-args-');
|
||||
final configFile = File(p.join(tempDir.path, 'CWS_conf.yaml'));
|
||||
await configFile.writeAsString('''
|
||||
|
|
@ -263,13 +263,97 @@ projects:
|
|||
path: ${tempDir.path}
|
||||
''');
|
||||
|
||||
// When loading the app config from disk.
|
||||
final future = AppConfig.load(configFile.path);
|
||||
|
||||
// Then loading fails with a format exception about forbidden args.
|
||||
await expectLater(
|
||||
future,
|
||||
throwsA(
|
||||
isA<FormatException>().having(
|
||||
(error) => error.message,
|
||||
'message',
|
||||
contains('forbidden arg "--json-output"'),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
/// ```gherkin
|
||||
/// Scenario: Reject responder args with mixed-case json tokens
|
||||
/// Given an orchestrator config that configures a responder argument containing Json in mixed case
|
||||
/// When loading the app config from disk
|
||||
/// Then loading fails with a format exception about forbidden args
|
||||
/// ```
|
||||
test('Reject responder args with mixed-case json tokens', () async {
|
||||
// Given an orchestrator config that configures a responder argument containing Json in mixed case.
|
||||
final tempDir = await Directory.systemTemp.createTemp(
|
||||
'cws-codex-args-case-',
|
||||
);
|
||||
final configFile = File(p.join(tempDir.path, 'CWS_conf.yaml'));
|
||||
await configFile.writeAsString('''
|
||||
defaults:
|
||||
issueAssistant:
|
||||
responders:
|
||||
- id: codex
|
||||
command: codex
|
||||
args: ["exec", "--JsonOutput"]
|
||||
projects:
|
||||
sample:
|
||||
repo: owner/sample
|
||||
path: ${tempDir.path}
|
||||
''');
|
||||
|
||||
// When loading the app config from disk.
|
||||
final future = AppConfig.load(configFile.path);
|
||||
|
||||
// Then loading fails with a format exception about forbidden args.
|
||||
await expectLater(
|
||||
future,
|
||||
throwsA(
|
||||
isA<FormatException>().having(
|
||||
(error) => error.message,
|
||||
'message',
|
||||
contains('forbidden arg "--JsonOutput"'),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
/// ```gherkin
|
||||
/// Scenario: Keep non-format responder args
|
||||
/// Given an orchestrator config that only sets non-format responder args
|
||||
/// When loading the app config from disk
|
||||
/// Then those args remain available to the responder
|
||||
/// ```
|
||||
test('Keep non-format responder args', () async {
|
||||
// Given an orchestrator config that only sets non-format responder args.
|
||||
final tempDir = await Directory.systemTemp.createTemp(
|
||||
'cws-codex-args-keep-',
|
||||
);
|
||||
final configFile = File(p.join(tempDir.path, 'CWS_conf.yaml'));
|
||||
await configFile.writeAsString('''
|
||||
defaults:
|
||||
issueAssistant:
|
||||
responders:
|
||||
- id: codex
|
||||
command: codex
|
||||
args: ["-s", "danger-full-access", "exec", "--allow-all"]
|
||||
projects:
|
||||
sample:
|
||||
repo: owner/sample
|
||||
path: ${tempDir.path}
|
||||
''');
|
||||
|
||||
// When loading the app config from disk.
|
||||
final config = await AppConfig.load(configFile.path);
|
||||
|
||||
// Then the responder args are normalized to the current codex --json flag.
|
||||
// Then those args remain available to the responder.
|
||||
expect(config.projects['sample']!.issueAssistant.responders.single.args, [
|
||||
'-s',
|
||||
'danger-full-access',
|
||||
'exec',
|
||||
'--json',
|
||||
'--allow-all',
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -1223,10 +1307,7 @@ projects:
|
|||
.replaceFirst(' # responders:', ' responders:')
|
||||
.replaceFirst(' # - id: codex', ' - id: codex')
|
||||
.replaceFirst(' # command: codex', ' command: codex')
|
||||
.replaceFirst(
|
||||
' # args: ["exec", "--json"]',
|
||||
' args: ["exec", "--json"]',
|
||||
)
|
||||
.replaceFirst(' # args: ["exec"]', ' args: ["exec"]')
|
||||
.replaceFirst(' # timeout: 10m', ' timeout: 10m')
|
||||
.replaceFirst(' # issueAssistant:', ' issueAssistant:')
|
||||
.replaceFirst(' # enabled: true', ' enabled: true')
|
||||
|
|
|
|||
|
|
@ -395,7 +395,7 @@ projects:
|
|||
test('Decode Copilot CLI JSONL assistant messages', () async {
|
||||
// Given a responder command that emits Copilot CLI JSONL events.
|
||||
final sandbox = await Directory.systemTemp.createTemp(
|
||||
'cws-copilot-jsonl-',
|
||||
'cws-copilot-events-',
|
||||
);
|
||||
final scriptFile = File(p.join(sandbox.path, 'responder.dart'));
|
||||
await scriptFile.writeAsString('''
|
||||
|
|
|
|||
Loading…
Reference in New Issue