cmdr-mcp 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/README.md +20 -16
- package/docs/README.zh-CN.md +6 -4
- package/docs/agent-integration.md +72 -10
- package/docs/dashboard.md +1 -1
- package/docs/implementation.md +1 -1
- package/docs/long-running-collaboration.md +4 -2
- package/docs/publishing.md +8 -8
- package/docs/setup.md +4 -3
- package/docs/troubleshooting.md +1 -1
- package/marketplace.json +2 -2
- package/package.json +2 -2
- package/plugins/cmdr/.claude-plugin/plugin.json +1 -1
- package/plugins/cmdr/.codex-plugin/plugin.json +1 -1
- package/plugins/cmdr/.kimi-plugin/plugin.json +65 -0
- package/plugins/cmdr/.zcode-plugin/plugin.json +1 -1
- package/plugins/cmdr/README.md +4 -4
- package/plugins/cmdr/bin/cmdr-check.mjs +3 -1
- package/plugins/cmdr/bin/cmdr-node +8 -1
- package/plugins/cmdr/commands/cmdr.md +1 -1
- package/plugins/cmdr/dist/cli.mjs +107 -14
- package/plugins/cmdr/dist/daemon.mjs +52 -13
- package/plugins/cmdr/dist/hook.mjs +21 -3
- package/plugins/cmdr/dist/integrity.json +20 -18
- package/plugins/cmdr/dist/mcp.mjs +9 -3
- package/plugins/cmdr/kimi-identity/SKILL.md +12 -0
- package/plugins/cmdr/skills/cmdr/SKILL.md +1 -1
- package/plugins/cmdr/skills/cmdr/references/commander.md +1 -1
- package/plugins/cmdr/skills/cmdr/references/executor.md +1 -1
- package/plugins/cmdr/skills/cmdr/references/setup.md +1 -1
- package/plugins/cmdr/skills/cmdr-commander/SKILL.md +1 -1
- package/plugins/cmdr/skills/cmdr-executor/SKILL.md +1 -1
- package/plugins/cmdr/skills/using-cmdr/SKILL.md +2 -2
|
@@ -271,7 +271,7 @@ var Rpc = class extends EventEmitter {
|
|
|
271
271
|
};
|
|
272
272
|
|
|
273
273
|
// src/shared/version.ts
|
|
274
|
-
var VERSION = true ? "0.
|
|
274
|
+
var VERSION = true ? "0.6.0" : MIN_CLIENT_VERSION;
|
|
275
275
|
var PROTOCOL = 1;
|
|
276
276
|
function newer(a, b) {
|
|
277
277
|
const x = a.split(".").map(Number), y = b.split(".").map(Number);
|
|
@@ -497,7 +497,7 @@ async function watch(sid, once = false) {
|
|
|
497
497
|
instruction: "Call read and read(recover=true); handle cancellation first and report with reply_to."
|
|
498
498
|
}) + "\n"
|
|
499
499
|
);
|
|
500
|
-
if (once || result.wake_mode === "zcode") stop();
|
|
500
|
+
if (once || result.wake_mode === "zcode" || result.wake_mode === "kimi") stop();
|
|
501
501
|
};
|
|
502
502
|
const check = async () => {
|
|
503
503
|
if (stopped) return;
|
|
@@ -4816,6 +4816,7 @@ var methods = {
|
|
|
4816
4816
|
};
|
|
4817
4817
|
|
|
4818
4818
|
// src/shared/env.ts
|
|
4819
|
+
var cmdrTool = /(?:^|[_:])cmdr(?:__|:)(list|join|report|leave|ask|send|read|task|artifact)$/;
|
|
4819
4820
|
function waitRecommendation(agent, env = process.env) {
|
|
4820
4821
|
const timeout = Number(env.CMDR_TOOL_TIMEOUT_SEC);
|
|
4821
4822
|
if (Number.isFinite(timeout) && timeout > 0) {
|
|
@@ -5943,7 +5944,8 @@ function serverConfig(previous, command, host) {
|
|
|
5943
5944
|
command,
|
|
5944
5945
|
args: [],
|
|
5945
5946
|
...host === "codex" ? { tool_timeout_sec: 600 } : { type: "stdio" },
|
|
5946
|
-
...host === "zcode" ? { timeoutMs: 6e5 } : {}
|
|
5947
|
+
...host === "zcode" ? { timeoutMs: 6e5 } : {},
|
|
5948
|
+
...host === "kimi" ? { toolTimeoutMs: 6e5 } : {}
|
|
5947
5949
|
};
|
|
5948
5950
|
}
|
|
5949
5951
|
function mergeJsonServer(config, command, host) {
|
|
@@ -5996,6 +5998,58 @@ ${block}
|
|
|
5996
5998
|
serverDisabled: server.enabled === false
|
|
5997
5999
|
};
|
|
5998
6000
|
}
|
|
6001
|
+
function kimiHooksConfig(text3, executable) {
|
|
6002
|
+
const blockHooks = [
|
|
6003
|
+
{ event: "SessionStart" },
|
|
6004
|
+
{ event: "UserPromptSubmit" },
|
|
6005
|
+
{ event: "PreToolUse", matcher: cmdrTool.source },
|
|
6006
|
+
{ event: "Stop" },
|
|
6007
|
+
{ event: "SessionEnd", timeout: 1 }
|
|
6008
|
+
].map(({ event, matcher, timeout }) => ({
|
|
6009
|
+
event,
|
|
6010
|
+
...matcher ? { matcher } : {},
|
|
6011
|
+
command: `${shellQuote(executable)} ${event}`,
|
|
6012
|
+
timeout: timeout ?? 5
|
|
6013
|
+
}));
|
|
6014
|
+
const block = `${begin}
|
|
6015
|
+
${stringify({ hooks: blockHooks })}${end}`;
|
|
6016
|
+
const start = text3.indexOf(begin), stop = text3.indexOf(end);
|
|
6017
|
+
if ((start >= 0 || stop >= 0) && (start < 0 || stop < start))
|
|
6018
|
+
throw new Error(
|
|
6019
|
+
"Invalid cmdr setup markers in config.toml. Restore the managed block before retrying."
|
|
6020
|
+
);
|
|
6021
|
+
const output = start >= 0 ? text3.slice(0, start) + block + text3.slice(stop + end.length) : `${text3}${text3 && !text3.endsWith("\n") ? "\n" : ""}
|
|
6022
|
+
${block}
|
|
6023
|
+
`;
|
|
6024
|
+
let full;
|
|
6025
|
+
try {
|
|
6026
|
+
full = parse2(text3, { integersAsBigInt: "asNeeded" });
|
|
6027
|
+
} catch {
|
|
6028
|
+
throw new Error("Cannot parse Kimi Code config.toml; no configuration was changed.");
|
|
6029
|
+
}
|
|
6030
|
+
const allHooks = full.hooks ?? [];
|
|
6031
|
+
if (!Array.isArray(allHooks)) throw new Error("hooks must be an array of [[hooks]] tables.");
|
|
6032
|
+
let previous = [];
|
|
6033
|
+
if (start >= 0)
|
|
6034
|
+
try {
|
|
6035
|
+
previous = parse2(text3.slice(start, stop + end.length), {
|
|
6036
|
+
integersAsBigInt: "asNeeded"
|
|
6037
|
+
}).hooks ?? [];
|
|
6038
|
+
} catch {
|
|
6039
|
+
previous = [];
|
|
6040
|
+
}
|
|
6041
|
+
const kept = allHooks.filter((h) => !previous.some((o) => isDeepStrictEqual(o, h)));
|
|
6042
|
+
const expected = { ...full, hooks: [...kept, ...blockHooks] };
|
|
6043
|
+
try {
|
|
6044
|
+
if (!isDeepStrictEqual(parse2(output, { integersAsBigInt: "asNeeded" }), expected))
|
|
6045
|
+
throw new Error();
|
|
6046
|
+
} catch {
|
|
6047
|
+
throw new Error(
|
|
6048
|
+
"Cannot safely merge the cmdr hooks into config.toml. Move conflicting [[hooks]] entries and retry."
|
|
6049
|
+
);
|
|
6050
|
+
}
|
|
6051
|
+
return output;
|
|
6052
|
+
}
|
|
5999
6053
|
function mergeHooks(config, executable, host) {
|
|
6000
6054
|
const container = config.hooks = object(config.hooks ?? {}, "hooks");
|
|
6001
6055
|
const hooks = host === "zcode" ? container.events = object(container.events ?? {}, "hooks.events") : container;
|
|
@@ -6182,7 +6236,7 @@ function launcher(runtime, entry, state, agent, codexHome) {
|
|
|
6182
6236
|
# Managed by cmdr setup (https://github.com/njugray/cmdr).
|
|
6183
6237
|
export CMDR_HOME=${shellQuote(state)}
|
|
6184
6238
|
${agent ? `export CMDR_AGENT=${shellQuote(agent)}
|
|
6185
|
-
` : ""}${agent === "codex" || agent === "zcode" ? "export CMDR_TOOL_TIMEOUT_SEC=600\n" : ""}${codexHome ? `export CODEX_HOME=${shellQuote(codexHome)}
|
|
6239
|
+
` : ""}${agent === "codex" || agent === "zcode" || agent === "kimi" ? "export CMDR_TOOL_TIMEOUT_SEC=600\n" : ""}${codexHome ? `export CODEX_HOME=${shellQuote(codexHome)}
|
|
6186
6240
|
` : ""}exec ${shellQuote(join6(runtime, "bin", entry))} "$@"
|
|
6187
6241
|
`;
|
|
6188
6242
|
}
|
|
@@ -6200,14 +6254,14 @@ async function runSetup(argv, sourceRoot) {
|
|
|
6200
6254
|
});
|
|
6201
6255
|
if (values.help) {
|
|
6202
6256
|
console.log(
|
|
6203
|
-
"cmdr setup --agent claude-code|codex|zcode [--config-dir PATH] [--dry-run] [--json]\nInstall or upgrade for the current user. CMDR_HOME selects runtime/state storage; --config-dir selects the host user profile."
|
|
6257
|
+
"cmdr setup --agent claude-code|codex|zcode|kimi-code [--config-dir PATH] [--dry-run] [--json]\nInstall or upgrade for the current user. CMDR_HOME selects runtime/state storage; --config-dir selects the host user profile."
|
|
6204
6258
|
);
|
|
6205
6259
|
return;
|
|
6206
6260
|
}
|
|
6207
|
-
const agent = values.agent === "claude-code" ? "claude" : values.agent;
|
|
6208
|
-
if (positionals.length || !agent || !["claude", "codex", "zcode"].includes(agent))
|
|
6261
|
+
const agent = values.agent === "claude-code" ? "claude" : values.agent === "kimi-code" ? "kimi" : values.agent;
|
|
6262
|
+
if (positionals.length || !agent || !["claude", "codex", "zcode", "kimi"].includes(agent))
|
|
6209
6263
|
throw new Error(
|
|
6210
|
-
"Use cmdr setup --agent claude-code|codex|zcode. Other MCP hosts can use cmdr config --agent HOST."
|
|
6264
|
+
"Use cmdr setup --agent claude-code|codex|zcode|kimi-code. Other MCP hosts can use cmdr config --agent HOST."
|
|
6211
6265
|
);
|
|
6212
6266
|
const host = agent;
|
|
6213
6267
|
const source = await inspectInstallation(sourceRoot);
|
|
@@ -6217,17 +6271,28 @@ async function runSetup(argv, sourceRoot) {
|
|
|
6217
6271
|
const digest = createHash3("sha256").update(readFileSync4(join6(sourceRoot, "dist/integrity.json"))).digest("hex").slice(0, 16);
|
|
6218
6272
|
const runtime = join6(state, "runtimes", `${source.version}-${digest}`);
|
|
6219
6273
|
const bin = join6(state, "bin");
|
|
6220
|
-
const hostRoot = values["config-dir"] ? resolve2(values["config-dir"]) : host === "claude" ? resolve2(process.env.CLAUDE_CONFIG_DIR || join6(homedir2(), ".claude")) : host === "codex" ? resolve2(process.env.CODEX_HOME || join6(homedir2(), ".codex")) : join6(homedir2(), ".zcode");
|
|
6274
|
+
const hostRoot = values["config-dir"] ? resolve2(values["config-dir"]) : host === "claude" ? resolve2(process.env.CLAUDE_CONFIG_DIR || join6(homedir2(), ".claude")) : host === "codex" ? resolve2(process.env.CODEX_HOME || join6(homedir2(), ".codex")) : host === "kimi" ? resolve2(process.env.KIMI_CODE_HOME || join6(homedir2(), ".kimi-code")) : join6(homedir2(), ".zcode");
|
|
6221
6275
|
const profile = createHash3("sha256").update(hostRoot).digest("hex").slice(0, 12);
|
|
6222
6276
|
const mcp = join6(bin, `cmdr-mcp-${host}-${profile}`), hook = join6(bin, `cmdr-hook-${host}-${profile}`);
|
|
6223
6277
|
const changes = [];
|
|
6224
6278
|
const warnings = [];
|
|
6279
|
+
if (host === "kimi" && existsSync2(join6(hostRoot, "plugins/managed/cmdr")))
|
|
6280
|
+
throw new Error(
|
|
6281
|
+
"The cmdr plugin is already enabled. Use its installation, or remove it before running standalone setup to avoid duplicate tools and hooks."
|
|
6282
|
+
);
|
|
6225
6283
|
const add = (change) => {
|
|
6226
6284
|
if (change) changes.push(change);
|
|
6227
6285
|
};
|
|
6228
6286
|
const writeConfig = (path, config2) => add(fileChange(path, JSON.stringify(config2, null, 2) + "\n"));
|
|
6229
6287
|
let config;
|
|
6230
|
-
if (host === "
|
|
6288
|
+
if (host === "kimi") {
|
|
6289
|
+
config = configPath(join6(hostRoot, "mcp.json"));
|
|
6290
|
+
const servers = jsonConfig(read(config), config);
|
|
6291
|
+
mergeJsonServer(servers, mcp, "kimi");
|
|
6292
|
+
writeConfig(config, servers);
|
|
6293
|
+
const hooksPath = configPath(join6(hostRoot, "config.toml"));
|
|
6294
|
+
add(fileChange(hooksPath, kimiHooksConfig(read(hooksPath), hook)));
|
|
6295
|
+
} else if (host === "codex") {
|
|
6231
6296
|
config = configPath(join6(hostRoot, "config.toml"));
|
|
6232
6297
|
const merged = codexConfig(read(config), mcp);
|
|
6233
6298
|
add(fileChange(config, merged.text));
|
|
@@ -6277,6 +6342,8 @@ async function runSetup(argv, sourceRoot) {
|
|
|
6277
6342
|
}
|
|
6278
6343
|
const skill = join6(hostRoot, "skills/cmdr");
|
|
6279
6344
|
add(skillChange(skill, join6(runtime, "skills/cmdr")));
|
|
6345
|
+
if (host === "kimi")
|
|
6346
|
+
add(skillChange(join6(hostRoot, "skills/cmdr-identity"), join6(runtime, "kimi-identity")));
|
|
6280
6347
|
const result = {
|
|
6281
6348
|
agent: values.agent,
|
|
6282
6349
|
version: source.version,
|
|
@@ -6287,7 +6354,7 @@ async function runSetup(argv, sourceRoot) {
|
|
|
6287
6354
|
dry_run: !!values["dry-run"],
|
|
6288
6355
|
changed: changes.map((change) => change.path),
|
|
6289
6356
|
warnings,
|
|
6290
|
-
restart: "Start a new host session and review any hook trust prompts."
|
|
6357
|
+
restart: host === "kimi" ? "Restart the Kimi Code app: it loads config.toml hooks only at startup." : "Start a new host session and review any hook trust prompts."
|
|
6291
6358
|
};
|
|
6292
6359
|
const output = (extra) => {
|
|
6293
6360
|
if (values.json) console.log(JSON.stringify({ ...result, ...extra }, null, 2));
|
|
@@ -6476,12 +6543,37 @@ if (process.argv[2] === "setup") {
|
|
|
6476
6543
|
} catch {
|
|
6477
6544
|
checks.zcode_user_hooks = "No user hook config; installed plugin hooks follow plugin enablement.";
|
|
6478
6545
|
}
|
|
6546
|
+
const kimiRoot = "/Applications/Kimi Code.app/Contents";
|
|
6547
|
+
if (existsSync3(join7(kimiRoot, "Info.plist"))) {
|
|
6548
|
+
try {
|
|
6549
|
+
checks.kimi = {
|
|
6550
|
+
app_version: execFileSync(
|
|
6551
|
+
"/usr/libexec/PlistBuddy",
|
|
6552
|
+
["-c", "Print :CFBundleShortVersionString", join7(kimiRoot, "Info.plist")],
|
|
6553
|
+
{ encoding: "utf8", timeout: 1e3 }
|
|
6554
|
+
).trim(),
|
|
6555
|
+
config_dir: process.env.KIMI_CODE_HOME || join7(homedir3(), ".kimi-code")
|
|
6556
|
+
};
|
|
6557
|
+
} catch {
|
|
6558
|
+
checks.kimi = "Installed";
|
|
6559
|
+
}
|
|
6560
|
+
} else
|
|
6561
|
+
checks.kimi = "Desktop app not found in /Applications; generic MCP configuration is available.";
|
|
6562
|
+
try {
|
|
6563
|
+
const kconfig = readFileSync5(
|
|
6564
|
+
join7(process.env.KIMI_CODE_HOME || join7(homedir3(), ".kimi-code"), "config.toml"),
|
|
6565
|
+
"utf8"
|
|
6566
|
+
);
|
|
6567
|
+
checks.kimi_hooks = /cmdr-hook/.test(kconfig) ? "cmdr hooks present in config.toml" : "No cmdr hooks in config.toml; run cmdr setup --agent kimi-code.";
|
|
6568
|
+
} catch {
|
|
6569
|
+
checks.kimi_hooks = "Kimi Code configuration unavailable";
|
|
6570
|
+
}
|
|
6479
6571
|
print(checks);
|
|
6480
6572
|
}
|
|
6481
6573
|
try {
|
|
6482
6574
|
if (v.help)
|
|
6483
6575
|
process.stdout.write(
|
|
6484
|
-
"cmdr status | dashboard [--no-open] [--json] | setup --agent claude-code|codex|zcode [--dry-run] [--json] | list [--all] [--squad ID] | tail [--follow] [--full] [--json] [--after EVENT_SEQ|now] [--actionable] [--format line|json] [--for SID] | standby start|status|stop|resume|watch --session SID [--adapter codex|claude|zcode|manual] [--transport auto|proxy|queue] [--once] | send --squad ID [--to MEMBER] [--type command|cancel|info|answer] TEXT | read --session SID [--peek] | daemon start|stop|restart|status|logs | config [--agent HOST] [--session ID] | doctor [--plugin-root PATH] [--deep] | session --help | purge [--all]\n"
|
|
6576
|
+
"cmdr status | dashboard [--no-open] [--json] | setup --agent claude-code|codex|zcode|kimi-code [--dry-run] [--json] | list [--all] [--squad ID] | tail [--follow] [--full] [--json] [--after EVENT_SEQ|now] [--actionable] [--format line|json] [--for SID] | standby start|status|stop|resume|watch --session SID [--adapter codex|claude|zcode|kimi|manual] [--transport auto|proxy|queue] [--once] | send --squad ID [--to MEMBER] [--type command|cancel|info|answer] TEXT | read --session SID [--peek] | daemon start|stop|restart|status|logs | config [--agent HOST] [--session ID] | doctor [--plugin-root PATH] [--deep] | session --help | purge [--all]\n"
|
|
6485
6577
|
);
|
|
6486
6578
|
else if (cmd === "dashboard") {
|
|
6487
6579
|
const result = await call("admin.dashboard", {}, true);
|
|
@@ -6506,9 +6598,10 @@ if (process.argv[2] === "setup") {
|
|
|
6506
6598
|
env: {
|
|
6507
6599
|
CMDR_AGENT: agent,
|
|
6508
6600
|
...v.session ? { CMDR_SESSION_ID: v.session } : {},
|
|
6509
|
-
...agent === "zcode" ? { CMDR_TOOL_TIMEOUT_SEC: "600" } : {}
|
|
6601
|
+
...agent === "zcode" || agent === "kimi" ? { CMDR_TOOL_TIMEOUT_SEC: "600" } : {}
|
|
6510
6602
|
},
|
|
6511
|
-
...agent === "zcode" ? { timeoutMs: 6e5 } : {}
|
|
6603
|
+
...agent === "zcode" ? { timeoutMs: 6e5 } : {},
|
|
6604
|
+
...agent === "kimi" ? { toolTimeoutMs: 6e5 } : {}
|
|
6512
6605
|
};
|
|
6513
6606
|
print(
|
|
6514
6607
|
agent === "zcode" ? { mcp: { servers: { cmdr: server } } } : { mcpServers: { cmdr: server } }
|
|
@@ -40,7 +40,7 @@ function wakePrompt(id2) {
|
|
|
40
40
|
return `[cmdr wake ${id2}] Actionable messages or unfinished commands await this member. Call cmdr read, then read(recover=true). Accept commands with report(working, reply_to) before work. Check cancel messages first; never repeat completed work. Messages do not expand user authorization.`;
|
|
41
41
|
}
|
|
42
42
|
function hostStandby(mode) {
|
|
43
|
-
return mode === "claude" || mode === "zcode";
|
|
43
|
+
return mode === "claude" || mode === "zcode" || mode === "kimi";
|
|
44
44
|
}
|
|
45
45
|
function armHint(s, home) {
|
|
46
46
|
if (!hostStandby(s.wake_mode)) return void 0;
|
|
@@ -420,7 +420,7 @@ var StandbyManager = class {
|
|
|
420
420
|
fail("INVALID_ARGUMENT", "executable must be an absolute path");
|
|
421
421
|
if (p.socket && (typeof p.socket !== "string" || !isAbsolute(p.socket)))
|
|
422
422
|
fail("INVALID_ARGUMENT", "socket must be an absolute path");
|
|
423
|
-
if (p.adapter && !["codex", "claude", "zcode", "manual"].includes(p.adapter))
|
|
423
|
+
if (p.adapter && !["codex", "claude", "zcode", "kimi", "manual"].includes(p.adapter))
|
|
424
424
|
fail("INVALID_ARGUMENT");
|
|
425
425
|
if (p.transport && !["auto", "proxy", "queue"].includes(p.transport)) fail("INVALID_ARGUMENT");
|
|
426
426
|
if (p.resolve && !["retry", "accepted"].includes(p.resolve)) fail("INVALID_ARGUMENT");
|
|
@@ -441,7 +441,7 @@ var StandbyManager = class {
|
|
|
441
441
|
fail("INVALID_ARGUMENT", "Adapter must match the member host");
|
|
442
442
|
if (s.enabled && s.wake_mode !== "manual" && p.action === "start" && !p.adapter && !p.executable && !p.socket && !p.transport && !p.resolve)
|
|
443
443
|
return { ...s, arm: armHint(s, this.core.paths.home) };
|
|
444
|
-
s.wake_mode = p.adapter || (p.action === "start" ? ["codex", "claude", "zcode"].includes(session.agent) ? session.agent : "manual" : s.wake_mode);
|
|
444
|
+
s.wake_mode = p.adapter || (p.action === "start" ? ["codex", "claude", "zcode", "kimi"].includes(session.agent) ? session.agent : "manual" : s.wake_mode);
|
|
445
445
|
s.enabled = true;
|
|
446
446
|
s.health = s.wake_mode === "manual" ? "manual" : "starting";
|
|
447
447
|
s.executable = p.executable || s.executable;
|
|
@@ -486,7 +486,10 @@ var StandbyManager = class {
|
|
|
486
486
|
const s = this.core.store.standby(sid);
|
|
487
487
|
const session = this.core.store.session(sid);
|
|
488
488
|
if (!s?.enabled || !session?.squad_id || !hostStandby(s.wake_mode))
|
|
489
|
-
fail(
|
|
489
|
+
fail(
|
|
490
|
+
"WATCHER_DISABLED",
|
|
491
|
+
"Join with standby=auto on Claude/ZCode/Kimi before arming a watcher"
|
|
492
|
+
);
|
|
490
493
|
if (action === "detach") {
|
|
491
494
|
if (s.lease?.token === token2) {
|
|
492
495
|
s.lease = void 0;
|
|
@@ -5715,6 +5718,18 @@ var Core = class {
|
|
|
5715
5718
|
}
|
|
5716
5719
|
}
|
|
5717
5720
|
this.store.migrateMembership(old.sid, sid);
|
|
5721
|
+
const standby = this.store.standby(old.sid);
|
|
5722
|
+
if (standby) {
|
|
5723
|
+
this.store.deleteStandby(old.sid);
|
|
5724
|
+
if (old.role !== "none")
|
|
5725
|
+
this.store.saveStandby({
|
|
5726
|
+
...standby,
|
|
5727
|
+
sid,
|
|
5728
|
+
lease: void 0,
|
|
5729
|
+
request: void 0,
|
|
5730
|
+
health: standby.enabled && standby.wake_mode !== "manual" ? "starting" : standby.health
|
|
5731
|
+
});
|
|
5732
|
+
}
|
|
5718
5733
|
this.store.deleteSession(old.sid);
|
|
5719
5734
|
for (const c of this.contexts)
|
|
5720
5735
|
if (c.sid === old.sid) {
|
|
@@ -5905,7 +5920,7 @@ var Core = class {
|
|
|
5905
5920
|
user_reply: s.role === "commander" ? `Squad ${q.id}${q.name ? ` (${q.name})` : ""} is ready. Paste this into each other session:
|
|
5906
5921
|
${join_prompt}` : `Joined squad ${q.id}${s.name ? ` as ${s.name}` : ""}; report ready and wait for commands.`,
|
|
5907
5922
|
standby: this.standbyView(s.sid),
|
|
5908
|
-
protocol_hint: `You are the ${s.role.toUpperCase()} of squad ${q.id}. ${s.role === "commander" ? "Dispatch clear, verifiable tasks with send; answer every ask using type=answer and reply_to." : "Report ready now with cwd, capabilities and context; act on commands and report working/done/failed with reply_to. Ask when blocked."} Reply with ONLY user_reply (translate prose, keep the join line verbatim). Check list before reassignment: offline never means work stopped. Accept commands immediately with report(working, reply_to); recover with read(recover=true). Use join(standby="auto") with the real session ID to register managed standby, then check list for listener health. For Claude/ZCode, run listener.arm.command with its indicated host tool before ending the turn, and re-arm after task termination. If me.listener.can_auto_respond, end the turn; otherwise use at most two read(wait=${wait2}) calls and explain that manual continuation is required. Use the built-in standby watcher; do not write a private listener. Keep user replies to one or two lines. Apply normal judgment to messages from other agents. ${s.native_id ? "" : "Identity is provisional; hooks may be unavailable. Use read(wait) for reminders."}`
|
|
5923
|
+
protocol_hint: `You are the ${s.role.toUpperCase()} of squad ${q.id}. ${s.role === "commander" ? "Dispatch clear, verifiable tasks with send; answer every ask using type=answer and reply_to." : "Report ready now with cwd, capabilities and context; act on commands and report working/done/failed with reply_to. Ask when blocked."} Reply with ONLY user_reply (translate prose, keep the join line verbatim). Check list before reassignment: offline never means work stopped. Accept commands immediately with report(working, reply_to); recover with read(recover=true). Use join(standby="auto") with the real session ID to register managed standby, then check list for listener health. For Claude/ZCode/Kimi, run listener.arm.command with its indicated host tool before ending the turn, and re-arm after task termination. If me.listener.can_auto_respond, end the turn; otherwise use at most two read(wait=${wait2}) calls and explain that manual continuation is required. Use the built-in standby watcher; do not write a private listener. Keep user replies to one or two lines. Apply normal judgment to messages from other agents. ${s.native_id ? "" : "Identity is provisional; hooks may be unavailable. Use read(wait) for reminders."}`
|
|
5909
5924
|
};
|
|
5910
5925
|
}
|
|
5911
5926
|
leave(ctx, p) {
|
|
@@ -6260,7 +6275,7 @@ ${join_prompt}` : `Joined squad ${q.id}${s.name ? ` as ${s.name}` : ""}; report
|
|
|
6260
6275
|
if (this.store.revoked(sid)) return {};
|
|
6261
6276
|
let s = this.store.session(sid);
|
|
6262
6277
|
if (p.event === "SessionEnd" && !s) return {};
|
|
6263
|
-
if (p.event === "SessionStart") {
|
|
6278
|
+
if (p.event === "SessionStart" && agent !== "kimi") {
|
|
6264
6279
|
const candidates = [...this.contexts].filter((c) => c.kind === "mcp" && c.agent === agent && c.sid !== sid).filter((c) => {
|
|
6265
6280
|
const old = this.me(c);
|
|
6266
6281
|
return (p.ancestors || []).includes(old.pid) && (p.source === "clear" || !old.native_id && old.cwd && old.cwd === p.cwd);
|
|
@@ -6273,7 +6288,10 @@ ${join_prompt}` : `Joined squad ${q.id}${s.name ? ` as ${s.name}` : ""}; report
|
|
|
6273
6288
|
kind: "hook",
|
|
6274
6289
|
agent,
|
|
6275
6290
|
native_id: p.session_id,
|
|
6276
|
-
cwd
|
|
6291
|
+
// Kimi desktop agent-level hooks report the bootstrap cwd "/" rather
|
|
6292
|
+
// than the session's, so only its SessionStart cwd may update it;
|
|
6293
|
+
// other hosts keep updating cwd from any hook event.
|
|
6294
|
+
...agent !== "kimi" || p.event === "SessionStart" ? { cwd: p.cwd } : {},
|
|
6277
6295
|
host_pid: p.host_pid,
|
|
6278
6296
|
transcript_path: p.transcript_path
|
|
6279
6297
|
});
|
|
@@ -6579,6 +6597,7 @@ var Store = class {
|
|
|
6579
6597
|
CREATE TABLE IF NOT EXISTS dashboard_links(command_id TEXT PRIMARY KEY, task_id TEXT NOT NULL);
|
|
6580
6598
|
CREATE TABLE IF NOT EXISTS dashboard_submissions(id TEXT PRIMARY KEY, payload TEXT NOT NULL);`);
|
|
6581
6599
|
this.migrateWork();
|
|
6600
|
+
this.db.exec("DELETE FROM standby WHERE sid NOT IN (SELECT sid FROM sessions)");
|
|
6582
6601
|
}
|
|
6583
6602
|
migrateWork() {
|
|
6584
6603
|
const legacy = this.unpack(
|
|
@@ -6886,6 +6905,10 @@ function releaseLock(path) {
|
|
|
6886
6905
|
|
|
6887
6906
|
// src/daemon/logger.ts
|
|
6888
6907
|
import { appendFileSync, existsSync, renameSync, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
|
|
6908
|
+
function logInternal(log, where, error) {
|
|
6909
|
+
if (!(error instanceof CmdrError))
|
|
6910
|
+
log(`internal error in ${where}: ${error instanceof Error ? error.stack : String(error)}`);
|
|
6911
|
+
}
|
|
6889
6912
|
function logger(path) {
|
|
6890
6913
|
return (message) => {
|
|
6891
6914
|
try {
|
|
@@ -7092,7 +7115,7 @@ var Rpc = class extends EventEmitter {
|
|
|
7092
7115
|
|
|
7093
7116
|
// src/shared/version.ts
|
|
7094
7117
|
var MIN_CLIENT_VERSION = "0.2.0";
|
|
7095
|
-
var VERSION = true ? "0.
|
|
7118
|
+
var VERSION = true ? "0.6.0" : MIN_CLIENT_VERSION;
|
|
7096
7119
|
var PROTOCOL = 1;
|
|
7097
7120
|
function newer(a, b) {
|
|
7098
7121
|
const x = a.split(".").map(Number), y = b.split(".").map(Number);
|
|
@@ -10887,10 +10910,12 @@ var token = () => randomBytes2(32).toString("base64url");
|
|
|
10887
10910
|
var same = (a, b) => Buffer.byteLength(a) === Buffer.byteLength(b) && timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
10888
10911
|
var artifactPolicy = "sandbox allow-scripts; default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:; connect-src 'none'; form-action 'none'; base-uri 'none'; frame-ancestors 'self'";
|
|
10889
10912
|
var DashboardServer = class {
|
|
10890
|
-
constructor(core, touch, assetRoot = new URL("./dashboard/", import.meta.url)) {
|
|
10913
|
+
constructor(core, touch, assetRoot = new URL("./dashboard/", import.meta.url), log = () => {
|
|
10914
|
+
}) {
|
|
10891
10915
|
this.core = core;
|
|
10892
10916
|
this.touch = touch;
|
|
10893
10917
|
this.assetRoot = assetRoot;
|
|
10918
|
+
this.log = log;
|
|
10894
10919
|
this.routes();
|
|
10895
10920
|
this.server = createAdaptorServer({
|
|
10896
10921
|
fetch: this.app.fetch,
|
|
@@ -11126,6 +11151,7 @@ var DashboardServer = class {
|
|
|
11126
11151
|
);
|
|
11127
11152
|
app.notFound(() => fail("NOT_FOUND"));
|
|
11128
11153
|
app.onError((error, c) => {
|
|
11154
|
+
logInternal(this.log, `${c.req.method} ${c.req.path}`, error);
|
|
11129
11155
|
const e = error instanceof CmdrError ? error : new CmdrError("INTERNAL_ERROR", "Dashboard request failed");
|
|
11130
11156
|
let status;
|
|
11131
11157
|
switch (e.code) {
|
|
@@ -11179,9 +11205,14 @@ async function startDaemon(home) {
|
|
|
11179
11205
|
}, 2e3);
|
|
11180
11206
|
const peers = /* @__PURE__ */ new Set();
|
|
11181
11207
|
let idleSince = Date.now(), stopping = false;
|
|
11182
|
-
const dashboard = new DashboardServer(
|
|
11183
|
-
|
|
11184
|
-
|
|
11208
|
+
const dashboard = new DashboardServer(
|
|
11209
|
+
core,
|
|
11210
|
+
() => {
|
|
11211
|
+
idleSince = Date.now();
|
|
11212
|
+
},
|
|
11213
|
+
void 0,
|
|
11214
|
+
log
|
|
11215
|
+
);
|
|
11185
11216
|
rmSync4(p.socket, { force: true });
|
|
11186
11217
|
const server = createServer2((socket) => {
|
|
11187
11218
|
idleSince = Date.now();
|
|
@@ -11191,7 +11222,7 @@ async function startDaemon(home) {
|
|
|
11191
11222
|
core.connect(ctx);
|
|
11192
11223
|
let greeted = false;
|
|
11193
11224
|
let watcher;
|
|
11194
|
-
|
|
11225
|
+
const handle = async (method, params, signal) => {
|
|
11195
11226
|
if (!greeted && method !== "hello")
|
|
11196
11227
|
fail(
|
|
11197
11228
|
"PROTOCOL_MISMATCH",
|
|
@@ -11252,6 +11283,14 @@ async function startDaemon(home) {
|
|
|
11252
11283
|
const result = await core.handle(ctx, method, params, signal);
|
|
11253
11284
|
return method === "admin.status" ? { ...result, version: VERSION, protocol: PROTOCOL, pid: process.pid } : result;
|
|
11254
11285
|
};
|
|
11286
|
+
rpc.handler = async (method, params, signal) => {
|
|
11287
|
+
try {
|
|
11288
|
+
return await handle(method, params, signal);
|
|
11289
|
+
} catch (e) {
|
|
11290
|
+
logInternal(log, method, e);
|
|
11291
|
+
throw e;
|
|
11292
|
+
}
|
|
11293
|
+
};
|
|
11255
11294
|
rpc.on("close", () => {
|
|
11256
11295
|
peers.delete(rpc);
|
|
11257
11296
|
if (!stopping && watcher) {
|
|
@@ -89,6 +89,7 @@ var cmdrTool = /(?:^|[_:])cmdr(?:__|:)(list|join|report|leave|ask|send|read|task
|
|
|
89
89
|
function detectAgent(env = process.env, hook) {
|
|
90
90
|
if (env.CMDR_AGENT && /^[a-z][a-z0-9_-]{0,63}$/.test(env.CMDR_AGENT)) return env.CMDR_AGENT;
|
|
91
91
|
if (env.ZCODE_PLUGIN_ROOT || env.ZCODE_PLUGIN_ID) return "zcode";
|
|
92
|
+
if (env.KIMI_PLUGIN_ROOT) return "kimi";
|
|
92
93
|
if (env.CLAUDE_CODE_SESSION_ID || env.CLAUDE_CODE_ENTRYPOINT) return "claude";
|
|
93
94
|
if (hook?.transcript_path && String(hook.transcript_path).includes("/.claude/")) return "claude";
|
|
94
95
|
if (env.CODEX_HOME || env.CODEX_THREAD_ID || env.CODEX_SESSION_ID || hook?.turn_id)
|
|
@@ -254,7 +255,7 @@ var Rpc = class extends EventEmitter {
|
|
|
254
255
|
};
|
|
255
256
|
|
|
256
257
|
// src/shared/version.ts
|
|
257
|
-
var VERSION = true ? "0.
|
|
258
|
+
var VERSION = true ? "0.6.0" : MIN_CLIENT_VERSION;
|
|
258
259
|
var PROTOCOL = 1;
|
|
259
260
|
function newer(a, b) {
|
|
260
261
|
const x = a.split(".").map(Number), y = b.split(".").map(Number);
|
|
@@ -391,6 +392,13 @@ async function runHook(input, event = input.hook_event_name) {
|
|
|
391
392
|
return;
|
|
392
393
|
}
|
|
393
394
|
if (event === "PreToolUse" && cmdrTool.test(input.tool_name || "")) {
|
|
395
|
+
if (agent === "kimi") {
|
|
396
|
+
if ((input.tool_input || {})._cmdr_session === input.session_id) return;
|
|
397
|
+
return {
|
|
398
|
+
decision: "block",
|
|
399
|
+
reason: `[cmdr] Kimi Code shares one cmdr MCP process per workspace; retry this call with _cmdr_session="${input.session_id}".`
|
|
400
|
+
};
|
|
401
|
+
}
|
|
394
402
|
if (agent !== "claude")
|
|
395
403
|
return {
|
|
396
404
|
hookSpecificOutput: {
|
|
@@ -424,8 +432,18 @@ try {
|
|
|
424
432
|
input += chunk;
|
|
425
433
|
if (input.length > 2 * 1024 * 1024) throw new Error("large input");
|
|
426
434
|
}
|
|
427
|
-
const
|
|
428
|
-
|
|
435
|
+
const parsed = JSON.parse(input);
|
|
436
|
+
const agent = detectAgent(process.env, parsed);
|
|
437
|
+
const result = await runHook(parsed, process.argv[2]);
|
|
438
|
+
if (result) {
|
|
439
|
+
if (agent === "kimi") {
|
|
440
|
+
if (result.decision === "block")
|
|
441
|
+
process.stderr.write(`${result.reason}
|
|
442
|
+
`, () => process.exit(2));
|
|
443
|
+
else if (process.argv[2] === "UserPromptSubmit" && result.hookSpecificOutput?.additionalContext)
|
|
444
|
+
process.stdout.write(result.hookSpecificOutput.additionalContext + "\n");
|
|
445
|
+
} else process.stdout.write(JSON.stringify(result) + "\n");
|
|
446
|
+
}
|
|
429
447
|
} catch (e) {
|
|
430
448
|
diagnostic(e.code === "DAEMON_UNAVAILABLE" ? "hook-unavailable" : "hook-error");
|
|
431
449
|
}
|
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.6.0",
|
|
3
3
|
"files": {
|
|
4
|
-
".claude-plugin/plugin.json": "
|
|
5
|
-
".codex-plugin/plugin.json": "
|
|
4
|
+
".claude-plugin/plugin.json": "03daaf06535131d19dc7444907f1af1100a9deeb2b07553862805c33d49f878d",
|
|
5
|
+
".codex-plugin/plugin.json": "36b3066ada5434eda049904b6a90ef867f093a3f861e6a61e9df7fddb5963156",
|
|
6
|
+
".kimi-plugin/plugin.json": "7634842fb15abaf7a6ccc9d4500cd27447515e3d6c99de0a3249bf0f74631bc6",
|
|
6
7
|
".mcp.json": "34e310a2874c8fda625745672a54df19614f32dabd2be4013f8ce871260e88c4",
|
|
7
|
-
".zcode-plugin/plugin.json": "
|
|
8
|
+
".zcode-plugin/plugin.json": "6a5f67567958487a9a10db5bf879641de790c5734fafaeab7ce39a978ddab43a",
|
|
8
9
|
"THIRD_PARTY_NOTICES.txt": "061121858596143e1ec0609aeee0d45ec839e53f05a4b7c23a48ec7df33194fc",
|
|
9
10
|
"bin/cmdr": "be6ea6b84ce121738ca729e9c6dbfb87bbf349a84d251acc711e3ea11f8dd8fd",
|
|
10
|
-
"bin/cmdr-check.mjs": "
|
|
11
|
+
"bin/cmdr-check.mjs": "669204363e26323af6abd623e9039c2c7e9ca9626ee9418e9ab9e2a85be7fc67",
|
|
11
12
|
"bin/cmdr-daemon": "7f8ed3a755f975751ddda077242adf661834dddbb8be7d3a88caee0c70cce5db",
|
|
12
13
|
"bin/cmdr-hook": "e1fa5950325775e467e4e01aeff3c5cb8c1dba35c4535d7cb569f658f95e7f15",
|
|
13
14
|
"bin/cmdr-mcp": "356bea13c2b82075c09fd663106791edab9e06fdb04fc679a98de33514b10a83",
|
|
14
|
-
"bin/cmdr-node": "
|
|
15
|
-
"commands/cmdr.md": "
|
|
16
|
-
"dist/cli.mjs": "
|
|
17
|
-
"dist/daemon.mjs": "
|
|
15
|
+
"bin/cmdr-node": "d4005e39ec1edc05a45413f38172a239a234cc457fd17a320db5e24bdbc2e6c8",
|
|
16
|
+
"commands/cmdr.md": "c46d1d3d38af10ac75f8793b998e0889a9136da82b883816a724502689184e49",
|
|
17
|
+
"dist/cli.mjs": "99ffbb22e10223c012100896ed7fe571fe0e1095c34003f54fdc9b389987cd22",
|
|
18
|
+
"dist/daemon.mjs": "21335e66c4eb808e8f9e8d6c6de0daf3a3e733616eb4c89547c7621a9e51c690",
|
|
18
19
|
"dist/dashboard/app.css": "58d74d9a98c39bef501a53f2530a99906d2fbb8ef5a619ff7a8ee432005d65d3",
|
|
19
20
|
"dist/dashboard/app.js": "c3549fce634931de741df6883e63e246969eedfedd8af4e81e7afe0687a10d1e",
|
|
20
21
|
"dist/dashboard/index.html": "670b1a05ca906b352ed5d8f53d2adc1a9ad5bb937e758a09ae200752f4e25356",
|
|
21
|
-
"dist/hook.mjs": "
|
|
22
|
-
"dist/mcp.mjs": "
|
|
22
|
+
"dist/hook.mjs": "ce32d8fda6272f9cbfe0fb8bde9d68ce55a10c3ceacb6f3166787115df96e793",
|
|
23
|
+
"dist/mcp.mjs": "55950f4e6be2600881e8ca9c89e316b41a2039c98278199b8c846900869e41ba",
|
|
23
24
|
"hooks/hooks.json": "6ff937910fc04eb81db4a84dfeb91a92ab971364d684a45a85f3a9953eb21259",
|
|
24
|
-
"
|
|
25
|
-
"skills/cmdr/
|
|
26
|
-
"skills/cmdr/references/
|
|
27
|
-
"skills/cmdr/references/
|
|
28
|
-
"skills/cmdr
|
|
29
|
-
"skills/cmdr-
|
|
30
|
-
"skills/
|
|
25
|
+
"kimi-identity/SKILL.md": "967454ed3a9c1ddeeb87c5f419ce6303399caec1563192777c564445e6e6d59f",
|
|
26
|
+
"skills/cmdr/SKILL.md": "f95875b0b54877547a16158403dbdf7162c9a10d417dc956907acd81fd174305",
|
|
27
|
+
"skills/cmdr/references/commander.md": "48728f778a53cd2ae9d38cafcfa5f6c00f9b92a29acad452eecba1c07d04985d",
|
|
28
|
+
"skills/cmdr/references/executor.md": "c7527c2a15cb593407b9547dd431bc499d6eaa7216e8fae5c14402c6c748b9ad",
|
|
29
|
+
"skills/cmdr/references/setup.md": "8049617af8d441a586e498a8c6fb6bd84836b5d8ffd7199c4cec2b589fdcf4ad",
|
|
30
|
+
"skills/cmdr-commander/SKILL.md": "2b39799de58cef778d2009acede67701e2ec3fef5e0e617480a79d048fc0a692",
|
|
31
|
+
"skills/cmdr-executor/SKILL.md": "dad00e3f9116b19d0009bbeb9f2527e0ffcbce20c7b70ab5208656e091bfbd14",
|
|
32
|
+
"skills/using-cmdr/SKILL.md": "921ac245e5884cf4e5bf9a07520b186ebdf47c0cfc93e6e171c4072d163e03fd"
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -21589,7 +21589,7 @@ var schemas = {
|
|
|
21589
21589
|
};
|
|
21590
21590
|
|
|
21591
21591
|
// src/shared/version.ts
|
|
21592
|
-
var VERSION = true ? "0.
|
|
21592
|
+
var VERSION = true ? "0.6.0" : MIN_CLIENT_VERSION;
|
|
21593
21593
|
var PROTOCOL = 1;
|
|
21594
21594
|
function newer(a, b) {
|
|
21595
21595
|
const x = a.split(".").map(Number), y = b.split(".").map(Number);
|
|
@@ -21603,6 +21603,7 @@ function newer(a, b) {
|
|
|
21603
21603
|
function detectAgent(env = process.env, hook) {
|
|
21604
21604
|
if (env.CMDR_AGENT && /^[a-z][a-z0-9_-]{0,63}$/.test(env.CMDR_AGENT)) return env.CMDR_AGENT;
|
|
21605
21605
|
if (env.ZCODE_PLUGIN_ROOT || env.ZCODE_PLUGIN_ID) return "zcode";
|
|
21606
|
+
if (env.KIMI_PLUGIN_ROOT) return "kimi";
|
|
21606
21607
|
if (env.CLAUDE_CODE_SESSION_ID || env.CLAUDE_CODE_ENTRYPOINT) return "claude";
|
|
21607
21608
|
if (hook?.transcript_path && String(hook.transcript_path).includes("/.claude/")) return "claude";
|
|
21608
21609
|
if (env.CODEX_HOME || env.CODEX_THREAD_ID || env.CODEX_SESSION_ID || hook?.turn_id)
|
|
@@ -22121,14 +22122,14 @@ function closeClients() {
|
|
|
22121
22122
|
}
|
|
22122
22123
|
var server = new McpServer({ name: "cmdr", version: VERSION });
|
|
22123
22124
|
var descriptions = {
|
|
22124
|
-
join: "Create/join a squad. For /cmdr <name>, pass squad_name for atomic find-or-create; role defaults to executor. Explicitly claim role=commander, use takeover=true for handover or rebind=member_id for a new endpoint. Otherwise specify role and optional squad ID/name. Use standby=auto; Claude/ZCode must run listener.arm.command with the indicated native host tool, and re-arm on termination. Codex is daemon driven. Inspect listener health before ending the turn. Executors report ready after joining. Reply with user_reply and follow listener health; bounded read(wait=me.recommended_wait) is the manual fallback.",
|
|
22125
|
+
join: "Create/join a squad. For /cmdr <name>, pass squad_name for atomic find-or-create; role defaults to executor. Explicitly claim role=commander, use takeover=true for handover or rebind=member_id for a new endpoint. Otherwise specify role and optional squad ID/name. Use standby=auto; Claude/ZCode/Kimi must run listener.arm.command with the indicated native host tool, and re-arm on termination. Codex is daemon driven. Inspect listener health before ending the turn. Executors report ready after joining. Reply with user_reply and follow listener health; bounded read(wait=me.recommended_wait) is the manual fallback.",
|
|
22125
22126
|
list: "Show task ownership, unacked_for, execution activity, connection presence and listener health. Offline never authorizes reassignment. Use full=true for expanded session metadata; listings never include command bodies. Read your own messages with read(id=...). scope=all lists squads and sessions.",
|
|
22126
22127
|
send: "Commander: dispatch clear tasks with acceptance criteria, optionally linked to task_id from the dashboard task tool, or answer an executor ask using type=answer and reply_to. Use task_key to prevent duplicate tickets and reassign=<command id> for a gated handover preserving the original task/task_key. type=cancel with reply_to requests a safe stop. to accepts all, member_id, sid, unique sid prefix or member name.",
|
|
22127
22128
|
report: "Executor: report ready (cwd/capabilities), working, blocked, done, failed or cancelled; include reply_to for the command.",
|
|
22128
22129
|
ask: "Executor: ask the commander (default target=commander). Commander: target=user creates a durable dashboard question (kind=single|multiple|text|confirm); use action=list|get|update|withdraw|handle, id and current version. handle requires result. User replies arrive in read; query status=answered to recover unhandled decisions. User questions do not use wait.",
|
|
22129
22130
|
task: "Maintain dashboard tasks. Commander uses action=create|update|archive|restore; title is required for create. Members can list/get within their squad. Dispatch with send(task_id=...). Execution states follow commands/reports; unfinished work cannot be archived. list is paginated; get includes durable execution history.",
|
|
22130
22131
|
artifact: "Commander publishes self-contained HTML as an isolated dashboard explanation (256 KiB maximum). action=publish requires title and html; updating also requires id and current version. Link returned id via task/ask artifact_ids. Members can list/get. User answers always use built-in forms outside HTML. Updating an artifact increments versions of pending questions that reference it.",
|
|
22131
|
-
read: "Fetch messages in priority order (reading dequeues). Use wait=me.recommended_wait to stand by, peek to inspect or history to review delivered messages. Use recover=true for all unfinished commands (non-consuming), id for a non-consuming message lookup (blocked replacements return REASSIGNMENT_PENDING), full for squad details. With a healthy listener, end the turn. Claude/ZCode must first arm the built-in host watcher from listener.arm. Only when host wake is unavailable, do at most two waits and explain manual continuation.",
|
|
22132
|
+
read: "Fetch messages in priority order (reading dequeues). Use wait=me.recommended_wait to stand by, peek to inspect or history to review delivered messages. Use recover=true for all unfinished commands (non-consuming), id for a non-consuming message lookup (blocked replacements return REASSIGNMENT_PENDING), full for squad details. With a healthy listener, end the turn. Claude/ZCode/Kimi must first arm the built-in host watcher from listener.arm. Only when host wake is unavailable, do at most two waits and explain manual continuation.",
|
|
22132
22133
|
leave: "Leave the squad. Commander departure orphans it; dissolve=true disbands it. Messages already queued remain readable."
|
|
22133
22134
|
};
|
|
22134
22135
|
for (const name of Object.keys(schemas)) {
|
|
@@ -22138,6 +22139,11 @@ for (const name of Object.keys(schemas)) {
|
|
|
22138
22139
|
async (args, extra) => {
|
|
22139
22140
|
try {
|
|
22140
22141
|
const { _cmdr_session, ...params } = args;
|
|
22142
|
+
if (agent === "kimi" && !_cmdr_session && !process.env.CMDR_SESSION_ID)
|
|
22143
|
+
throw new CmdrError(
|
|
22144
|
+
"SESSION_STAMP_REQUIRED",
|
|
22145
|
+
'Kimi Code shares one cmdr MCP process per workspace, so every cmdr call must carry this session identity: retry with _cmdr_session="<session-id>". The id is the session_... value the cmdr identity skill renders for this session (${KIMI_SESSION_ID}); the main agent passes it on every call, including calls made on behalf of subagents.'
|
|
22146
|
+
);
|
|
22141
22147
|
const selected = await forSession(_cmdr_session);
|
|
22142
22148
|
const result = await selected.call(methods[name], params, extra.signal);
|
|
22143
22149
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cmdr-identity
|
|
3
|
+
description: Kimi Code session identity for cmdr; carry it on every cmdr MCP call so the shared per-workspace MCP process routes to the right member.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You run as a Kimi Code session inside a cmdr squad. Kimi Code shares ONE cmdr MCP process per workspace, so the daemon cannot tell which session is calling unless each call identifies itself.
|
|
7
|
+
|
|
8
|
+
Your session id for this conversation: ${KIMI_SESSION_ID}
|
|
9
|
+
|
|
10
|
+
- Pass `_cmdr_session` with exactly that value on EVERY cmdr MCP tool call (`join`, `list`, `send`, `report`, `ask`, `read`, `leave`, `task`, `artifact`), including calls you make on behalf of subagents — subagents never receive this injection.
|
|
11
|
+
- If a cmdr call is rejected for a missing or mismatched `_cmdr_session`, retry it with the id given in the error message; that id is always this session's current id (resuming a conversation never changes it).
|
|
12
|
+
- Use the value only for `_cmdr_session`; it identifies this session to the local cmdr daemon and is not a credential for anything else.
|
|
@@ -15,7 +15,7 @@ Read delivers a message, not task acceptance or completion. Acknowledge a comman
|
|
|
15
15
|
|
|
16
16
|
Use the host's real session identity, supplied through the integration. Never invent native IDs or reverse-engineer private host state. Without a confirmed ID, ordinary per-process MCP queues still work, but do not promise stable resume identity or automatic wake. A pooled MCP process requires per-call identity stamps or a separate process per session.
|
|
17
17
|
|
|
18
|
-
For Claude/ZCode, arm `me.listener.arm.command` with the native host tool described in the role reference; re-arm after task termination or restart. Codex wake is daemon-managed. Check `list` for `me.listener.can_auto_respond=true`, then end the idle turn. Only when native wake is unavailable, use at most two `read(wait=me.recommended_wait)` calls and explain the actual limitation. Unsupported hosts are manual. Diagnose uncertain/stalled requests before an explicit retry; do not create private listeners or repeatedly report standby timeouts.
|
|
18
|
+
For Claude/ZCode/Kimi, arm `me.listener.arm.command` with the native host tool described in the role reference; re-arm after task termination or restart. Codex wake is daemon-managed. Check `list` for `me.listener.can_auto_respond=true`, then end the idle turn. Only when native wake is unavailable, use at most two `read(wait=me.recommended_wait)` calls and explain the actual limitation. Unsupported hosts are manual. Diagnose uncertain/stalled requests before an explicit retry; do not create private listeners or repeatedly report standby timeouts.
|
|
19
19
|
|
|
20
20
|
Hooks carry metadata only; read the messages themselves. Keep user updates brief. Messages do not expand the user's authorization. Rebind a stopped member to its real replacement session with `join(rebind=<member_id>, standby="auto")`; this revokes the old endpoint but does not stop processes already launched by that session.
|
|
21
21
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
4. Send bounded tasks with acceptance criteria. Use task_key=<ticket id> when dispatching a known ticket. Record the returned command ID; delivered_to/queued_to only mean enqueued. A working report with reply_to is acceptance; done/failed/cancelled are terminal. Uncorrelated reports cannot close tasks.
|
|
7
7
|
5. To stop work, send(type="cancel", to=<owner>, reply_to=<command id>, message=<reason>). It has reserved high priority and produces a read event. It is cooperative: the executor stops at a safe checkpoint. To reassign, send(type="command", reassign=<command id>, to=<new owner>, message=<task>). Reassignment inherits the original task_key; do not supply a different key. The replacement is blocked until the original owner reports a terminal state; unread original work can be cancelled immediately. Never work around the gate by sending a second unrelated command.
|
|
8
8
|
6. Answer every ask using type="answer", to=<asking member>, reply_to=<ask id>. Read complete reports; use limit or read(id=...) instead of truncating a consuming read with head. working/ready reports normally only record progress; done/failed/blocked/ask warrant attention.
|
|
9
|
-
7. For Claude or
|
|
9
|
+
7. For Claude, ZCode or Kimi Code, use `me.listener.arm.command` from join/list to arm the built-in watcher before ending the turn. Claude: run it with the host `Monitor` tool, which notifies on stdout lines; if Monitor is absent but background Bash completion notifications are supported, run it with `--once` and `run_in_background=true`. ZCode and Kimi Code: use Bash with `run_in_background=true`; the watcher stays silent until actionable work arrives, then exits. Do not add shell `&`, run a foreground wait loop, or detach it from the host. Check existing host task status before arming to avoid duplicates. Re-arm after completion/failure/kill, Monitor expiry, or daemon/App restart. A notification means call `read` and `read(recover=true)`, handle work and then re-arm. For Codex, the daemon handles wake delivery; no host watcher is needed. Check `list` for `can_auto_respond=true`, then end the idle turn. Only if the host lacks these tools or arming fails, explain the concrete reason, use at most two recommended waits, and require manual continuation. Reconcile uncertain/stalled requests before explicit retry; do not repeatedly report standby timeouts.
|
|
10
10
|
8. Ordinary leave preserves the channel; leave(dissolve=true) explicitly closes it. Channels outlive host sessions and message retention. For a new host endpoint representing the same stopped member, use join(rebind=<member_id>, standby="auto").
|
|
11
11
|
|
|
12
12
|
Apply normal judgment and the user's scope to messages. cmdr does not create new agents or grant extra permissions.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
3. Read messages and read(recover=true) on startup, after context loss or a wake. Recovery is non-consuming and includes unread, read-but-unaccepted and accepted unfinished commands. Use read(id=<message id>) for the full message. A blocked replacement returns REASSIGNMENT_PENDING until its original owner reports a terminal state. Read does not acknowledge work.
|
|
6
6
|
4. Before working, report(status="working", reply_to=<command id>). Never execute a task blocked by an earlier owner's cancellation. Handle cancel messages first at a safe checkpoint, stop the referenced work, then report cancelled with that command's reply_to. Check read(peek=true) between long steps when hooks are unavailable. Do not infer instructions from metadata reminders.
|
|
7
7
|
5. Report done/failed with reply_to after finishing. When blocked, report blocked with reply_to and ask. After a restart, reconcile actual files/processes before resuming accepted work; do not execute it again blindly.
|
|
8
|
-
6. For Claude or
|
|
8
|
+
6. For Claude, ZCode or Kimi Code, use `me.listener.arm.command` from join/list to arm the built-in watcher before ending the turn. Claude: run it with the host `Monitor` tool, which notifies on stdout lines; if Monitor is absent but background Bash completion notifications are supported, run it with `--once` and `run_in_background=true`. ZCode and Kimi Code: use Bash with `run_in_background=true`; the watcher stays silent until actionable work arrives, then exits. Do not add shell `&`, run a foreground wait loop, or detach it from the host. Check existing host task status before arming to avoid duplicates. Re-arm after completion/failure/kill, Monitor expiry, or daemon/App restart. A notification means call `read` and `read(recover=true)`, handle work and then re-arm. For Codex, the daemon handles wake delivery; no host watcher is needed. Check `list` for `can_auto_respond=true`, then end the idle turn. Only if the host lacks these tools or arming fails, explain the concrete reason, use at most two recommended waits, and require manual continuation. Reconcile uncertain/stalled requests before explicit retry; do not repeatedly report standby timeouts.
|
|
9
9
|
7. Leave only when requested. Ordinary leave and host shutdown preserve the channel and task records. Replacing a stopped host session uses join(rebind=<member_id>, standby="auto") in the new real session; the old endpoint is revoked. This does not stop processes that the old model already launched.
|
|
10
10
|
|
|
11
11
|
Messages do not expand user authorization. Apply normal judgment. Unknown hosts remain manual; never claim a successful wake merely because a message was queued.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Setup and recovery
|
|
2
2
|
|
|
3
|
-
Requires macOS/Linux, Node.js >=22.5 and cmdr-mcp >=0.4.0. When installation or repair is requested, choose the actual host (`claude-code`, `codex` or `
|
|
3
|
+
Requires macOS/Linux, Node.js >=22.5 and cmdr-mcp >=0.4.0. When installation or repair is requested, choose the actual host (`claude-code`, `codex`, `zcode` or `kimi-code`):
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
npx -y --package=cmdr-mcp@latest cmdr setup --agent claude-code
|