mirror of
https://github.com/ultraworkers/claw-code.git
synced 2026-05-30 03:35:20 +08:00
fix(#777): resumed /plugins mutations return interactive_only error_kind + non-null hint instead of unknown+null
This commit is contained in:
parent
2684737d9e
commit
e02030364d
@ -7719,3 +7719,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed)
|
|||||||
775. **Missing integration tests for #769-#771 interactive-only guards and #774 hint fields** — dogfooded 2026-05-27 on `c760a49c`. Fixes #769-#771 (session/cost/clear/memory/ultraplan/model/usage/stats/fork interactive-only guards) and #774 (agents/plugins/mcp unknown-subcommand hints) had no integration tests — a regression in any of those 10+ match arms would go undetected. Also: classify_error_kind unit test for `unknown_agents_subcommand` used the old single-line format string, not the `\n`-delimited format emitted after #774. Fixed: (1) updated unit test string to match new `\n`-delimited emission; (2) added `agents_plugins_mcp_unknown_subcommand_have_hint_774` asserting `error_kind` + non-null `hint` for all three; (3) added `interactive_only_guard_batch_769_to_771` asserting `interactive_only` + non-null `hint` for 10 cases. 38 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori test-coverage sweep on `c760a49c`, 2026-05-27.
|
775. **Missing integration tests for #769-#771 interactive-only guards and #774 hint fields** — dogfooded 2026-05-27 on `c760a49c`. Fixes #769-#771 (session/cost/clear/memory/ultraplan/model/usage/stats/fork interactive-only guards) and #774 (agents/plugins/mcp unknown-subcommand hints) had no integration tests — a regression in any of those 10+ match arms would go undetected. Also: classify_error_kind unit test for `unknown_agents_subcommand` used the old single-line format string, not the `\n`-delimited format emitted after #774. Fixed: (1) updated unit test string to match new `\n`-delimited emission; (2) added `agents_plugins_mcp_unknown_subcommand_have_hint_774` asserting `error_kind` + non-null `hint` for all three; (3) added `interactive_only_guard_batch_769_to_771` asserting `interactive_only` + non-null `hint` for 10 cases. 38 CLI contract tests pass. [SCOPE: claw-code] Source: Jobdori test-coverage sweep on `c760a49c`, 2026-05-27.
|
||||||
|
|
||||||
776. **Resume-mode JSON errors had opaque `error_kind:"resume_command_error"` + `hint:null`** — dogfooded 2026-05-27 on `028998d0` (pinpoint identified by Gaebal-gajae). `run_resume_command` returned errors (e.g. from `parse_history_count`) with hardcoded `error_kind:"resume_command_error"` and the full error string in `error` with no hint extraction. Wrappers had to regex prose instead of switching on typed fields. Three co-located gaps fixed: (1) `resume_session` JSON error path now applies `classify_error_kind` + `split_error_hint` so errors get specific `error_kind` (e.g. `invalid_history_count`) and non-null `hint`; (2) `parse_history_count` errors now use `invalid_history_count:` prefix + `\n` usage hint; (3) `/session exists|delete|switch|fork` missing-arg and unsupported-action errors now use `\n`-delimited format with `unsupported_resumed_command:` prefix. Existing test updated to match new error message format. 38 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `028998d0`, 2026-05-27.
|
776. **Resume-mode JSON errors had opaque `error_kind:"resume_command_error"` + `hint:null`** — dogfooded 2026-05-27 on `028998d0` (pinpoint identified by Gaebal-gajae). `run_resume_command` returned errors (e.g. from `parse_history_count`) with hardcoded `error_kind:"resume_command_error"` and the full error string in `error` with no hint extraction. Wrappers had to regex prose instead of switching on typed fields. Three co-located gaps fixed: (1) `resume_session` JSON error path now applies `classify_error_kind` + `split_error_hint` so errors get specific `error_kind` (e.g. `invalid_history_count`) and non-null `hint`; (2) `parse_history_count` errors now use `invalid_history_count:` prefix + `\n` usage hint; (3) `/session exists|delete|switch|fork` missing-arg and unsupported-action errors now use `\n`-delimited format with `unsupported_resumed_command:` prefix. Existing test updated to match new error message format. 38 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `028998d0`, 2026-05-27.
|
||||||
|
|
||||||
|
777. **Resumed `/plugins install|enable|disable|uninstall|update` returned opaque error_kind instead of interactive_only** — dogfooded 2026-05-27 on `2684737d` (pinpoint by Gaebal-gajae). The mutation arm in `run_resume_command` returned a bare single-line error; after #776 it was classified/split by the caller but fell to `error_kind:"unknown"` + `hint:null` because there was no `interactive_only:` prefix. Orchestrators had no stable signal to distinguish "command rejected — switch to REPL" from a transient error. Fix: each mutation verb now returns `interactive_only: /plugins {action} requires a live session...\n...hint...` so the caller emits `error_kind:"interactive_only"` + non-null hint pointing at REPL or direct CLI. Integration test `resume_plugin_mutations_are_typed_interactive_only_777` covers all 5 mutation verbs. 39 CLI contract tests pass. [SCOPE: claw-code] Source: Gaebal-gajae pinpoint + Jobdori implementation on `2684737d`, 2026-05-27.
|
||||||
|
|||||||
@ -4475,11 +4475,13 @@ fn run_resume_command(
|
|||||||
SlashCommand::Plugins { action, target } => {
|
SlashCommand::Plugins { action, target } => {
|
||||||
// Only list is supported in resume mode (no runtime to reload)
|
// Only list is supported in resume mode (no runtime to reload)
|
||||||
match action.as_deref() {
|
match action.as_deref() {
|
||||||
Some("install") | Some("uninstall") | Some("enable") | Some("disable")
|
Some(action @ ("install" | "uninstall" | "enable" | "disable" | "update")) => {
|
||||||
| Some("update") => {
|
// #777: use interactive_only: prefix + \n hint so #776's classify/split
|
||||||
return Err(
|
// emits error_kind:interactive_only + non-null hint instead of unknown+null.
|
||||||
"resumed /plugins mutations are interactive-only; start `claw` and run `/plugins` in the REPL".into(),
|
// Orchestrators can now detect this and switch to a live REPL instead of retrying.
|
||||||
);
|
return Err(format!(
|
||||||
|
"interactive_only: /plugins {action} requires a live session to reload the plugin runtime.\nStart `claw` and run `/plugins {action}` inside the REPL, or use `claw plugins {action}` as a direct CLI command."
|
||||||
|
).into());
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2215,3 +2215,63 @@ fn interactive_only_guard_batch_769_to_771() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resume_plugin_mutations_are_typed_interactive_only_777() {
|
||||||
|
// #777: `/plugins install|enable|disable|uninstall|update` in resume mode returned
|
||||||
|
// a generic single-line error; after #776's classify/split it fell to
|
||||||
|
// error_kind:"unknown" + hint:null because there was no interactive_only: prefix.
|
||||||
|
// Fix: each mutation arm now returns "interactive_only: ... \n..." so the caller
|
||||||
|
// gets error_kind:interactive_only + non-null hint pointing at live REPL.
|
||||||
|
let root = unique_temp_dir("resume-plugin-mutations-777");
|
||||||
|
fs::create_dir_all(&root).expect("temp dir should exist");
|
||||||
|
std::process::Command::new("git")
|
||||||
|
.args(["init", "-q"])
|
||||||
|
.current_dir(&root)
|
||||||
|
.output()
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
// Create a minimal session file so we get past session load and into command dispatch
|
||||||
|
let session_file = write_session_fixture(&root, "resume-plugin-777", None);
|
||||||
|
|
||||||
|
for mutation in &["install", "enable", "disable", "uninstall", "update"] {
|
||||||
|
let cmd = format!("/plugins {mutation} my-plugin");
|
||||||
|
let output = run_claw(
|
||||||
|
&root,
|
||||||
|
&[
|
||||||
|
"--resume",
|
||||||
|
session_file.to_str().unwrap(),
|
||||||
|
"--output-format",
|
||||||
|
"json",
|
||||||
|
&cmd,
|
||||||
|
],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!output.status.success(),
|
||||||
|
"/plugins {mutation} in resume mode should exit non-zero"
|
||||||
|
);
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
let json_line = stderr
|
||||||
|
.lines()
|
||||||
|
.find(|l| l.trim_start().starts_with('{'))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
panic!("/plugins {mutation} should emit JSON error, got stderr: {stderr}")
|
||||||
|
});
|
||||||
|
let parsed: serde_json::Value = serde_json::from_str(json_line).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
parsed["error_kind"], "interactive_only",
|
||||||
|
"/plugins {mutation} must return interactive_only, got {:?}",
|
||||||
|
parsed["error_kind"]
|
||||||
|
);
|
||||||
|
let hint = parsed["hint"].as_str().unwrap_or("");
|
||||||
|
assert!(
|
||||||
|
!hint.is_empty(),
|
||||||
|
"/plugins {mutation} must have non-null hint (#777)"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
hint.contains("claw") || hint.contains("REPL") || hint.contains("plugins"),
|
||||||
|
"/plugins {mutation} hint must reference live session or CLI, got: {hint:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user