cmdr-mcp 0.1.0 → 0.2.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 +1 -1
- package/README.md +31 -15
- package/docs/README.zh-CN.md +31 -10
- package/docs/agent-integration.md +15 -5
- package/docs/implementation.md +27 -9
- package/docs/iteration-plan-zcode.md +152 -0
- package/docs/long-running-collaboration.md +99 -0
- package/docs/publishing.md +26 -9
- package/docs/troubleshooting.md +88 -0
- package/marketplace.json +1 -1
- package/package.json +8 -7
- package/plugins/cmdr/.claude-plugin/plugin.json +1 -1
- package/plugins/cmdr/.codex-plugin/plugin.json +1 -1
- package/plugins/cmdr/.zcode-plugin/plugin.json +1 -1
- package/plugins/cmdr/README.md +6 -4
- package/plugins/cmdr/bin/cmdr +9 -1
- package/plugins/cmdr/bin/cmdr-check.mjs +97 -0
- package/plugins/cmdr/bin/cmdr-daemon +9 -1
- package/plugins/cmdr/bin/cmdr-hook +9 -1
- package/plugins/cmdr/bin/cmdr-mcp +9 -1
- package/plugins/cmdr/bin/cmdr-node +26 -3
- package/plugins/cmdr/commands/cmdr.md +4 -4
- package/plugins/cmdr/dist/cli.mjs +4943 -229
- package/plugins/cmdr/dist/daemon.mjs +1170 -131
- package/plugins/cmdr/dist/hook.mjs +86 -16
- package/plugins/cmdr/dist/integrity.json +25 -0
- package/plugins/cmdr/dist/mcp.mjs +142 -52
- package/plugins/cmdr/skills/cmdr-commander/SKILL.md +10 -8
- package/plugins/cmdr/skills/cmdr-executor/SKILL.md +9 -7
- package/plugins/cmdr/skills/using-cmdr/SKILL.md +7 -7
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Long-running channels, work and standby
|
|
2
|
+
|
|
3
|
+
Version 0.2.0 keeps the seven MCP tools and local Unix-socket/SQLite architecture. It separates transport delivery, command ownership and host wakeup. No exactly-once execution guarantee is made. The channel ID remains the existing squad ID; `squad` and `squad_name` remain the API parameter names.
|
|
4
|
+
|
|
5
|
+
## Join, claim and continue
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
join(role="executor", squad_name="my-project", standby="auto")
|
|
9
|
+
join(role="commander", squad_name="my-project", standby="auto")
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**Change from 0.1.x:** a named join without a role now defaults to executor, including the first joiner. A channel can exist and accept members/reports without a commander. An explicit commander claim is atomic; an occupied role requires `takeover=true`. The old commander becomes an executor. Reports and asks target the stable `squad:<channel id>` inbox. Only the current commander consumes it. Queued legacy messages addressed to a departing/replaced commander move into this inbox; history and correlated reply routes remain available.
|
|
13
|
+
|
|
14
|
+
A member has a stable `member_id`, exposed by join/list. After stopping the old host session, use the new session's real native ID and call:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
join(rebind="member:...", standby="auto")
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
This moves membership, unread messages, unfinished commands and reply routing to the new endpoint, preserving the member ID. Old endpoint registrations and reads are rejected, including after restart. The listener must be registered for the new endpoint. Rebinding invalidates cmdr access; it does not kill processes the old model already launched. Do not use it to run two copies of work.
|
|
21
|
+
|
|
22
|
+
Ordinary leave and SessionEnd do not close a channel. `leave(dissolve=true)` is explicit closure. Open channels/memberships and unfinished commands survive message retention. A departed owner remains visible while work is unfinished and can still send a correlated terminal report for its own command. Closing a channel stops its listeners without pretending its unfinished work succeeded. Explicit `purge --all` remains destructive.
|
|
23
|
+
|
|
24
|
+
## Commands and cancellation
|
|
25
|
+
|
|
26
|
+
| State | Evidence |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| queued | Command stored for its owner; send's IDs identify it |
|
|
29
|
+
| read | Consuming read delivered it, but no working report has accepted it |
|
|
30
|
+
| accepted | Owner reported working/blocked with the command ID as reply_to |
|
|
31
|
+
| completed / failed / cancelled | Owner reported done / failed / cancelled with reply_to |
|
|
32
|
+
|
|
33
|
+
`delivered_to` is retained for compatibility and means the same as `queued_to`: enqueued, not read or accepted. `pending` counts queued commands; `commands` includes all unfinished commands. `unacked_for` is seconds since dispatch until acknowledgement. `last_progress_at` describes the member's last report, while each command has its own updated_at. `presence=online/offline/cli` describes transport only; activity can also be unknown. CLI disconnection does not release ownership or erase busy state. A timestamp is evidence of the last observation, not a claim that a model is still running now.
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
send(to="tests", task_key="D69", message="Run checks and report results")
|
|
37
|
+
report(status="working", reply_to="COMMAND_ID", message="Accepted; starting checks")
|
|
38
|
+
report(status="done", reply_to="COMMAND_ID", message="Checks passed")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Sending another command to a member with unfinished work returns a warning. A duplicate active `task_key` is rejected across the channel. Free text cannot reliably identify the same ticket; use task_key. Uncorrelated ready/progress reports remain available but cannot complete a command. On upgrade, retained correlated reports restore legacy command states; absent/expired evidence is conservatively shown as unfinished and needs reconciliation. A terminal report from another member or a late attempt to reopen completed work is rejected. Each command reserves admission for the report that first makes it terminal: work state, report and any replacement release commit atomically even when the role inbox is full. This may exceed maxQueue by one report per completed command; ordinary and repeated reports remain subject to the queue cap and all reports retain sender rate limits.
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
send(type="cancel", to="tests", reply_to="COMMAND_ID", message="Stop at a safe checkpoint")
|
|
45
|
+
send(to="replacement", reassign="COMMAND_ID", message="Take over D69 after cancellation")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Cancel messages have reserved priority and capacity, ahead of ordinary commands. Their reads appear in the event stream. Cancellation is cooperative through hooks or explicit checkpoints; cmdr does not forcibly terminate a running model or process. Reassignment requests cancellation automatically and inherits the original task_key. Supplying a different key is rejected before cancellation. If the original was never read it is cancelled immediately; otherwise the replacement remains blocked until the original owner reports a terminal state. The daemon does not infer stopped execution from an offline connection or timeout. Verify actual work before deciding whether a replacement is still necessary after an original owner reports completed.
|
|
49
|
+
|
|
50
|
+
## Recovery and compact reads
|
|
51
|
+
|
|
52
|
+
On startup or after a wake, use ordinary `read` and `read(recover=true)`. Recovery lists all unfinished commands, including already-read and accepted work, without consuming anything. Reconcile files/processes before continuing an accepted command; do not blindly repeat it. `read(id="MESSAGE_ID")` is also non-consuming and returns the full message for that inbox. An ID lookup of queued replacement work returns REASSIGNMENT_PENDING until the original owner reports a terminal state, even with peek/history/recover/full options. After release, ID lookup remains non-consuming; delivered history stays readable after the predecessor expires. `peek` and `history` remain non-consuming. Cancelled waiting reads do not consume future arrivals.
|
|
53
|
+
|
|
54
|
+
`read` omits squad_summary by default; `list` omits repeated member boards and detailed session fields. Listings never include command bodies, including with full=true. Use read(id=...) for your own messages, or operator tail --full for observation. Use `full=true` / `--full` for expanded metadata and `limit` / `--limit` to bound reads. Do not pipe a consuming read into head: output lost after delivery is recoverable through history/ID lookup, but is no longer unread.
|
|
55
|
+
|
|
56
|
+
## Managed standby
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
cmdr standby start --session codex:REAL_THREAD_ID
|
|
60
|
+
cmdr standby status --session codex:REAL_THREAD_ID
|
|
61
|
+
cmdr standby stop --session codex:REAL_THREAD_ID
|
|
62
|
+
cmdr standby resume --session codex:REAL_THREAD_ID
|
|
63
|
+
# Optional explicit local host transport:
|
|
64
|
+
cmdr standby start --session codex:REAL_THREAD_ID --adapter codex --executable /absolute/path/to/codex --socket /absolute/path/to/control.sock
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`join(standby="auto")` registers the same listener. It requires confirmed native identity and channel membership. There is one persisted listener per endpoint, executed by the existing single-instance daemon. Enabled listeners prevent idle daemon exit and recover on daemon restart. Stop disables future checks; it does not retract a wake already accepted by the host. `list`, `status` and `doctor` expose listener health and the latest request. No per-session lockfile, script or private host database parsing is required.
|
|
68
|
+
|
|
69
|
+
The built-in Codex adapter uses `codex app-server proxy` against the already running shared local host. It requires runtime support for `thread/read`, `thread/resume`, `thread/queue/list`, `thread/queue/add`, `thread/queue/start` and `thread/turns/list`, including client user-message IDs. The shared control socket must already be exposed by the host; a CLI installation alone is insufficient. Use --socket when the host exposes a nondefault local control socket. The listener never bootstraps a separate host daemon. These experimental queue methods are feature-checked through actual calls; older runtimes or unsupported endpoints show an error and do not claim automatic response. See the [official app-server reference](https://learn.chatgpt.com/docs/app-server) for the public transport and thread status APIs. Queue shapes were checked against this development machine's generated CLI protocol; this is not a claim of support in every Codex release.
|
|
70
|
+
|
|
71
|
+
The adapter queues a metadata wake prompt for the registered existing thread, with a stable request ID, then asks the host to start that exact queued submission. An unloaded existing thread is resumed by its real ID through thread/resume, without supplying model, permission or sandbox overrides. It does not create threads or a separate app-server. Only actionable inbox messages and recoverable commands trigger a wake. working/ready reports and ordinary info do not; done/failed/blocked/cancelled/ask/answer/command do. Send `attention=true` for info that unblocks work. Busy sessions coalesce backlog, and urgent cancel is discovered at the next hook/tool checkpoint. No second competing model process is started.
|
|
72
|
+
|
|
73
|
+
Wake stages are persisted before external calls: requested → accepted → observed. Host queue/history reconciliation distinguishes a lost response from a missing request. A request that might have succeeded is **not automatically resent**. Unknown outcomes show uncertain; accepted wakes with no progress show stalled. Errors are persisted and emitted as lifecycle events. After inspecting the host, the operator can explicitly resolve:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
cmdr standby resume --session SID --resolve accepted
|
|
77
|
+
cmdr standby resume --session SID --resolve retry
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`retry` explicitly permits a fresh wake; it is not evidence the previous attempt failed. After a completed host turn with changed but unfinished work, the listener can issue a recovery wake. Repeated notifications or listener restart do not independently create duplicate requests. Host acceptance is never task acceptance: only the command's correlated report confirms that.
|
|
81
|
+
|
|
82
|
+
Claude, ZCode and other MCP hosts currently report manual. `can_auto_respond` is true only for an enabled healthy adapter. A queued host submission with unknown runtime state remains unhealthy with can_auto_respond=false; once idle is confirmed, the same submission can start without another enqueue. After registration, check list; starting is not confirmation. With a healthy listener, the model may end its idle turn. Otherwise the skill permits at most two recommended waits, then explains manual continuation. Never promise active wakeup based solely on hooks or a successful send.
|
|
83
|
+
|
|
84
|
+
## Lifecycle observation
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
cmdr tail --squad CHANNEL_ID --follow --json --full
|
|
88
|
+
cmdr tail --for MEMBER_SID --after 123 --follow --json --full
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Events have monotonically increasing `event_seq`, timestamp, channel, kind, sender/recipient, message ID, reply_to and applicable reason/data. The stream covers enqueue/read, command acceptance/progress/terminal state, cancellation/reassignment, membership/role changes, connection/hooks and wake requests/acceptance/errors. `--full` includes complete message bodies/data; default text summarizes bodies. `--json` is one JSON event per line. The cursor is an **event sequence**, not a message seq.
|
|
92
|
+
|
|
93
|
+
A follower subscribes before replay, deduplicates by event_seq, and reconnects with its last cursor. Observer calls never dequeue work. `--for` matches the recipient inbox (including its current commander role inbox); it is separate from observing the whole channel. Historical role-inbox events belong to the role, not permanently to a former commander's sid. Expired cursors produce `retention.gap`; observers can rebuild current work from list/recover. Cursors ahead of this database produce CURSOR_AHEAD instead of silently skipping events.
|
|
94
|
+
|
|
95
|
+
## Upgrade and verification boundary
|
|
96
|
+
|
|
97
|
+
Automatic version-triggered shutdown is disabled, including upgrade requests from old clients. A newer client reports UPGRADE_REQUIRED. The 0.2 daemon rejects clients older than 0.2.0 (and missing/invalid versions) with PROTOCOL_MISMATCH before registration, because the tool semantics changed even though the wire protocol remains 1. Refresh/reinstall stale plugin caches and restart their MCP connections. `cmdr daemon restart` first opens a consistent SQLite backup in a temporary directory with the new bundle, exercising its schema and record readers before stopping the live service. A failed check leaves the old daemon running. Doctor lists connected clients and their versions; cached plugins still need refreshing/reinstalling.
|
|
98
|
+
|
|
99
|
+
Tests exercise command recovery, role/member handover, cancellation gates, wake failure/reconciliation and CLI/daemon processes in disposable CMDR_HOME directories. They do not demonstrate that every host GUI grants hook trust or that real model turns will always acknowledge work. Real Codex queue execution and Claude/ZCode manual-continuation UX remain separate host checks. Windows, remote transport, new-agent creation and executor-to-executor messaging remain outside this implementation.
|
package/docs/publishing.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# npm 发布
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
本次发布版本为 `cmdr-mcp@0.2.0`。npm 包名是 `cmdr-mcp`,CLI、宿主插件和 marketplace 名称仍为 `cmdr`。安装后的包根目录是 `$(npm root -g)/cmdr-mcp`。
|
|
4
4
|
|
|
5
5
|
## 准备与验证
|
|
6
6
|
|
|
@@ -10,16 +10,18 @@
|
|
|
10
10
|
npm ci
|
|
11
11
|
npm run check
|
|
12
12
|
npm pack
|
|
13
|
-
npm publish ./cmdr-mcp-0.
|
|
13
|
+
npm publish ./cmdr-mcp-0.2.0.tgz --dry-run --access public --registry https://registry.npmjs.org/
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
涉及 ZCode 的发布另运行 `npm run verify:zcode`:从实际 tarball 安装到隔离缓存,核对完整性并移走安装源后验证 7 个工具;需要本机 ZCode runtime。
|
|
17
|
+
|
|
18
|
+
`npm run check` 包括格式、类型、构建、测试以及临时目录中的 npm 包离线安装验证:检查发布资源、安装后 marketplace 路径、CLI 和 7 个 MCP 工具。`npm pack` 通过 `prepack` 生成 4 个运行入口及第三方许可证声明,输出 `cmdr-mcp-0.2.0.tgz`。源码、测试和开发依赖不进入发布包。
|
|
17
19
|
|
|
18
20
|
检查 `git diff`,确认版本与预期一致,构建未意外修改宿主 manifests。发布前保留经过验证的源码提交;不要手改或提交 `plugins/cmdr/dist/`、`THIRD_PARTY_NOTICES.txt` 和 `.tgz`。
|
|
19
21
|
|
|
20
22
|
`--dry-run` 不发布包,也不能证明账号有发布权限或包名一定可注册。GUI 安装、信任提示和真实模型协作需单独验证,不能用自动化测试替代。
|
|
21
23
|
|
|
22
|
-
##
|
|
24
|
+
## 发布新版本
|
|
23
25
|
|
|
24
26
|
准备完成后,由发布者登录公共 npm,并确认账号和包名:
|
|
25
27
|
|
|
@@ -29,24 +31,39 @@ npm whoami --registry https://registry.npmjs.org/
|
|
|
29
31
|
npm view cmdr-mcp name version --registry https://registry.npmjs.org/
|
|
30
32
|
```
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
包已经存在,发布前核对账号的包所有权和目标版本是否尚未发布。网络或身份验证错误不能作为版本可用的依据。账号需要启用 2FA,按 CLI 提供的浏览器链接完成发布授权。
|
|
33
35
|
|
|
34
36
|
确认发布时,上传已检查的 tarball,并按 npm 提示完成账号验证:
|
|
35
37
|
|
|
36
38
|
```sh
|
|
37
|
-
npm publish ./cmdr-mcp-0.
|
|
39
|
+
npm publish ./cmdr-mcp-0.2.0.tgz --access public --registry https://registry.npmjs.org/
|
|
38
40
|
```
|
|
39
41
|
|
|
40
|
-
此命令会公开发布 `0.
|
|
42
|
+
此命令会公开发布 `0.2.0` 并使用默认的 `latest` 标签。相同包名和版本不能重复发布;后续修改需要提升版本并重新构建验证。发布使用 tarball,以保持上传内容与已检查产物一致。
|
|
41
43
|
|
|
42
44
|
## 发布后核对
|
|
43
45
|
|
|
44
46
|
```sh
|
|
45
|
-
npm view cmdr-mcp@0.
|
|
46
|
-
npm install --global cmdr-mcp@0.
|
|
47
|
+
npm view cmdr-mcp@0.2.0 name version dist.integrity --registry https://registry.npmjs.org/
|
|
48
|
+
npm install --global cmdr-mcp@0.2.0 --registry https://registry.npmjs.org/
|
|
47
49
|
cmdr --help
|
|
48
50
|
```
|
|
49
51
|
|
|
50
52
|
按照 [安装说明](README.zh-CN.md#安装) 注册安装后的包根目录。已安装宿主有插件缓存,升级后需要刷新或重装;同版本测试代码变动需要重启对应测试 daemon。
|
|
51
53
|
|
|
52
54
|
npm 命令行为参考:[npm publish 官方文档](https://docs.npmjs.com/cli/v11/commands/npm-publish/)。
|
|
55
|
+
|
|
56
|
+
## Marketplace 直接安装分发
|
|
57
|
+
|
|
58
|
+
开发分支继续忽略生成产物。`.github/workflows/marketplace.yml` 在 GitHub Release 发布时运行,也可手动运行 **Publish marketplace**。工作流先完成 `npm run check`,再通过 `npm pack` 生成并校验完整包,最后把解包内容提交到独立的 `marketplace` 分支。该分支只存分发文件,不含开发依赖;各 marketplace 的相对路径直接指向带 `dist/` 的插件目录。更新保留分支历史,不强制推送。
|
|
59
|
+
|
|
60
|
+
首次部署:提交并推送本次工作流及脚本,在 GitHub Actions 手动运行 **Publish marketplace**,确认成功后再向用户提供 `njugray/cmdr#marketplace`。后续发布 Release 自动更新。工作流需要仓库 `contents: write` 权限;分支保护如禁止自动推送,应由维护者调整。仅本地生成文件不会使远端安装地址生效。
|
|
61
|
+
|
|
62
|
+
本地验证分发目录(目标目录必须不存在):
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
npm run prepare:marketplace -- /tmp/cmdr-marketplace-release
|
|
66
|
+
npm run verify:zcode -- /tmp/cmdr-marketplace-release
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
ZCode 从带 `#marketplace` 的 GitHub 来源选择分支,然后按相对路径缓存完整插件。首次使用仍需要系统提供 Node.js ≥22.5;无需用户运行 npm 安装或构建。不要把默认开发分支的 Git 地址作为成品分发源。
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Installation and session troubleshooting
|
|
2
|
+
|
|
3
|
+
The npm package is `cmdr-mcp`; the plugin and commands are `cmdr`. Install the complete package, register its root as the marketplace, refresh/reinstall the host cache, then start a new session. Do not copy only `bin/` or symlink `dist` from another global package. Git checkouts require `npm ci && npm run build` before registration.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install -g cmdr-mcp
|
|
7
|
+
# Use this directory in the host's marketplace picker:
|
|
8
|
+
npm root -g
|
|
9
|
+
# Append /cmdr-mcp to the printed directory.
|
|
10
|
+
cmdr doctor
|
|
11
|
+
cmdr doctor --plugin-root /absolute/path/to/host/cache/cmdr
|
|
12
|
+
cmdr doctor --plugin-root /absolute/path/to/host/cache/cmdr --deep
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`--plugin-root` is the plugin directory containing `bin/`, `dist/` and the host manifests, not the marketplace root. A normal `doctor` inspects its own installation; a healthy global package does not prove the host cache is healthy. Target checks compare SHA-256 hashes and manifest versions against `dist/integrity.json`, and reject linked assets. This detects damage or mixed builds, not malicious replacement of both the files and checksums.
|
|
16
|
+
|
|
17
|
+
The checker lives outside the bundles. If the selected plugin's CLI bundle is missing, its `bin/cmdr doctor` can still report the damage with Node installed. Alternatively use an intact global CLI to inspect that cache. If Node is missing, the shell wrapper reports the Node requirement; install Node >=22.5 and retry. A missing checker requires reinstalling the complete package.
|
|
18
|
+
|
|
19
|
+
`doctor` exits nonzero for fatal installation failures. `--deep` additionally initializes MCP, verifies exactly seven tools, and calls `list` to check the daemon. It uses a temporary `CMDR_HOME`, an eight-second probe timeout and child cleanup. It does not join a squad or operate on the user's normal queues. Static checks do not start the normal daemon; reporting “daemon not running” is not itself an installation error.
|
|
20
|
+
|
|
21
|
+
The states are distinct:
|
|
22
|
+
|
|
23
|
+
| Observation | Meaning / next action |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| Plugin enabled | The host discovered configuration; this alone does not prove MCP connected. |
|
|
26
|
+
| Seven tools exposed | MCP initialized; call a tool to check daemon access. |
|
|
27
|
+
| No tools | Inspect the actual cache, reinstall from the built package and open a new session. |
|
|
28
|
+
| `hook-*` is `unknown` | No observation has been recorded here; configuration alone cannot prove hooks ran. |
|
|
29
|
+
| `hook-unavailable` | A hook could not reach a running daemon; hooks intentionally do not start it. |
|
|
30
|
+
| `identity-conflict` | A hook session ID disagreed with explicit `CMDR_SESSION_ID`; correct the configuration. |
|
|
31
|
+
| Protocol mismatch | Follow the message's client/daemon protocol information; update the cache, restart the matching daemon and reconnect the host. |
|
|
32
|
+
| Member offline | No member connection is held; queued messages and membership are retained. This says nothing about task execution; CLI-only members display cli. |
|
|
33
|
+
|
|
34
|
+
`CMDR_HOME/logs/diagnostics/` contains bounded snapshots with timestamps and metadata only. Hook observations describe the most recently recorded host for each event, not every session; activity from a different host is not evidence that your current host's hooks work. Snapshots are rate limited (10 seconds; bootstrap failures 60 seconds), use restricted permissions, and never include message bodies, hook input, credentials or environment dumps. Unknown is not equivalent to failure. Logs cannot block hooks if unwritable. Upgrade and reconnection snapshots are best-effort diagnostics, not actionable queue messages or delivery guarantees.
|
|
35
|
+
|
|
36
|
+
ZCode has four supported plugin hooks; SessionEnd is replaced by EOF detection. The PreToolUse event stamps its native `session_id` into `_cmdr_session`; the MCP bridge consumes it before forwarding. A shared MCP process requires a stamp on every call. Do not put one static `CMDR_SESSION_ID` on a shared process. With a dedicated process, its explicit ID must match hook events. No hooks means use a dedicated MCP process with a stable ID or the member CLI below; process/cwd cannot identify an arbitrary conversation.
|
|
37
|
+
|
|
38
|
+
## ZCode cache has no `dist/`
|
|
39
|
+
|
|
40
|
+
If `bin/cmdr-mcp` exists but `dist/mcp.mjs` is missing, the launcher exits before MCP initializes, so none of the seven tools can register. A working global `cmdr` uses a separate installation and does not repair this cache. Missing `dist/integrity.json` also prevents verification of otherwise present manifests; this alone does not prove those manifests were modified. Hooks fail open and record a rate-limited `bootstrap-runtime` diagnostic when possible, so a quiet hook does not establish a healthy installation.
|
|
41
|
+
|
|
42
|
+
Check the registered marketplace source first. An unbuilt Git checkout contains manifests and launchers but no generated runtime. The missing files alone cannot distinguish an unbuilt source from an incomplete cache copy.
|
|
43
|
+
|
|
44
|
+
1. In ZCode's marketplace settings, replace the source with `njugray/cmdr#marketplace` (available after the maintainer publishes that branch). This source includes the runtime.
|
|
45
|
+
2. Refresh/reinstall cmdr. Reinstalling from the same unbuilt source will reproduce the failure. For offline/local installation, use the complete installed npm package root or a built checkout.
|
|
46
|
+
3. Run `cmdr doctor --plugin-root /actual/zcode/cache/plugin --deep` against the resulting cache. Resolve static installation failures before expecting a successful deep probe.
|
|
47
|
+
4. Start a fresh ZCode session and confirm the cmdr tools appear. Do not link another installation's `dist/` into the cache or restart an unrelated shared daemon to repair missing plugin files.
|
|
48
|
+
|
|
49
|
+
The repository's `npm run verify:zcode` exercises installation from an npm tarball in temporary storage, checks cache integrity, and verifies seven MCP tools after removing the source directory. It does not inspect or repair an existing user's cache.
|
|
50
|
+
|
|
51
|
+
## Member CLI fallback
|
|
52
|
+
|
|
53
|
+
`cmdr session` provides the seven member operations using the same schemas, role checks and reply routing as MCP. It requires an actual stable native ID; never invent one to impersonate a different session. For CLI-only use, the operator may deliberately assign and consistently reuse a unique ID for that independent member.
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
cmdr session join --agent zcode --native-id YOUR_SESSION_ID --squad-name my-project
|
|
57
|
+
cmdr session report --agent zcode --native-id YOUR_SESSION_ID --status ready "Ready"
|
|
58
|
+
cmdr session read --agent zcode --native-id YOUR_SESSION_ID --wait 45
|
|
59
|
+
cmdr session ask --agent zcode --native-id YOUR_SESSION_ID --wait 45 "What next?"
|
|
60
|
+
cmdr session report --agent zcode --native-id YOUR_SESSION_ID --status done --reply-to COMMAND_ID "Done"
|
|
61
|
+
cmdr session leave --agent zcode --native-id YOUR_SESSION_ID
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Named joins default to executor; report/ask require that role. Explicitly claim command with `join --role commander --squad-name my-project`. The commander uses:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
cmdr session list --agent zcode --native-id COMMANDER_ID
|
|
68
|
+
cmdr session send --agent zcode --native-id COMMANDER_ID --to MEMBER_SID "Run checks"
|
|
69
|
+
cmdr session send --agent zcode --native-id COMMANDER_ID --to MEMBER_SID --type answer --reply-to ASK_ID "Proceed"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`CMDR_AGENT` and `CMDR_SESSION_ID` can supply identity instead of flags. Conflicting flags and environment are rejected. `--input` accepts the full operation's JSON schema, including `data`, `limit`, `since` and recipient arrays; `_cmdr_session` is reserved for MCP and is not accepted here. `--peek`, `--history`, `--all`, `--dissolve`, `--role` and `--squad` cover common cases. Options also provided as flags override matching JSON fields.
|
|
73
|
+
|
|
74
|
+
Every command writes a JSON result, or a JSON error on stderr with a nonzero exit code. `--timeout` bounds the operation (default wait+10 seconds, maximum 3600 seconds); SIGINT/SIGTERM cancel it. A cancelled waiting read does not consume later arrivals. A cancelled ask may already have been sent: requests are never automatically replayed, and a lost response is not proof that the mutation failed. Inspect history before manually retrying.
|
|
75
|
+
|
|
76
|
+
Short-lived commands display cli and retain task ownership after exit. They do not leave channels or imply success on read. Enable daemon-managed standby separately or through join --standby auto; unsupported hosts remain manual. Existing operator `cmdr send/read/list` commands retain their previous meaning. This CLI fallback still requires an intact runtime; it cannot compensate for all bundles being missing.
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## Long-running collaboration diagnostics
|
|
80
|
+
|
|
81
|
+
- Use `list` to inspect `commands`, `unacked_for` (seconds since dispatch until acceptance), `last_progress_at`, `hook_seen_at` and listener health. Do not reassign based on presence alone. Even list --full omits command bodies; use your own read --id or operator tail --full for those.
|
|
82
|
+
- Use `read --recover` for unfinished work and `read --id MESSAGE_ID` for a non-consuming full lookup. `--full` restores the expanded squad summary. Consuming read output should not be piped to head.
|
|
83
|
+
- `standby status --session SID` exposes the current wake ID and requested/accepted/observed/uncertain state. `stalled` means host acceptance did not produce progress. Check the host before `standby resume --session SID --resolve retry` (explicitly permits a new request) or `--resolve accepted` (retain the existing request). A missing lookup result is not proof that an earlier request failed. A queued submission with host_state=unknown remains unhealthy until runtime state can be verified; host queue acceptance alone does not prove that the listener can run a turn.
|
|
84
|
+
- `tail --follow --after EVENT_SEQ --for SID --json --full` replays and follows lifecycle events without reading work. `retention.gap` means the cursor predates retained events. Start a new cursor with `--after 0` only after checking CMDR_HOME if CURSOR_AHEAD is reported.
|
|
85
|
+
- `UPGRADE_REQUIRED` leaves the old daemon running. Run `cmdr daemon restart` from the new intact installation; it first validates a consistent database copy. If preflight fails, the old daemon is not stopped. `doctor` includes connected client versions; update old caches before reconnecting them. The 0.2 daemon rejects pre-0.2.0 clients with PROTOCOL_MISMATCH and a plugin-cache update hint, even when their wire protocol number matches.
|
|
86
|
+
- Missing runtime or Node now produces one stderr line from the fail-open hook wrapper as well as the bounded diagnostic snapshot.
|
|
87
|
+
|
|
88
|
+
See [long-running collaboration](long-running-collaboration.md) for adapter requirements and recovery examples.
|
package/marketplace.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cmdr-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local multi-agent squads for Claude Code, Codex, ZCode and any MCP host",
|
|
6
6
|
"engines": {
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
"typecheck": "tsc --noEmit",
|
|
12
12
|
"test": "vitest run",
|
|
13
13
|
"check": "npm run format:check && npm run typecheck && npm run build && npm test && npm run verify:package",
|
|
14
|
-
"format": "prettier --write src tests scripts package.json tsconfig.json .prettierrc.json vitest.config.ts",
|
|
15
|
-
"format:check": "prettier --check src tests scripts package.json tsconfig.json .prettierrc.json vitest.config.ts",
|
|
14
|
+
"format": "prettier --write src tests scripts plugins/cmdr/bin/*.mjs package.json tsconfig.json .prettierrc.json vitest.config.ts",
|
|
15
|
+
"format:check": "prettier --check src tests scripts plugins/cmdr/bin/*.mjs package.json tsconfig.json .prettierrc.json vitest.config.ts",
|
|
16
|
+
"prepare:marketplace": "node scripts/prepare-marketplace.mjs",
|
|
16
17
|
"verify:zcode": "node scripts/verify-zcode.mjs",
|
|
17
18
|
"prepack": "npm run build",
|
|
18
19
|
"verify:package": "node scripts/verify-package.mjs"
|
|
@@ -36,10 +37,10 @@
|
|
|
36
37
|
"docs/"
|
|
37
38
|
],
|
|
38
39
|
"bin": {
|
|
39
|
-
"cmdr": "plugins/cmdr/
|
|
40
|
-
"cmdr-mcp": "plugins/cmdr/
|
|
41
|
-
"cmdr-hook": "plugins/cmdr/
|
|
42
|
-
"cmdr-daemon": "plugins/cmdr/
|
|
40
|
+
"cmdr": "plugins/cmdr/bin/cmdr",
|
|
41
|
+
"cmdr-mcp": "plugins/cmdr/bin/cmdr-mcp",
|
|
42
|
+
"cmdr-hook": "plugins/cmdr/bin/cmdr-hook",
|
|
43
|
+
"cmdr-daemon": "plugins/cmdr/bin/cmdr-daemon"
|
|
43
44
|
},
|
|
44
45
|
"license": "MIT",
|
|
45
46
|
"repository": {
|
package/plugins/cmdr/README.md
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Connect local Claude Code, Codex, ZCode and other MCP-capable sessions as a squad. Requires macOS/Linux and Node.js ≥22.5 (24 recommended); npm distribution packages include all runtime dependencies. A source checkout requires `npm ci && npm run build` at the repository root before installation; generated `dist` files are not tracked by Git.
|
|
4
4
|
|
|
5
|
-
In each session enter `/cmdr my-project`.
|
|
5
|
+
In each session enter `/cmdr my-project`. Joining defaults to executor, even for a new channel. Explicitly use role="commander" to claim command; no commander is required to retain a channel. Without slash commands, ask the Agent to call `join(squad_name="my-project")`.
|
|
6
6
|
|
|
7
|
-
Tools: `join`, `list`, `send`, `report`, `ask`, `read`, `leave`. Executors report ready with capabilities/cwd, then read tasks, report results and ask when blocked. Commanders dispatch verifiable tasks and answer questions with `reply_to`.
|
|
7
|
+
Tools: `join`, `list`, `send`, `report`, `ask`, `read`, `leave`. Executors report ready with capabilities/cwd, then read tasks, report results and ask when blocked. Commanders dispatch verifiable tasks and answer questions with `reply_to`. Acknowledge each command with working + reply_to before executing it. Use read(recover=true) to find unfinished commands after interruptions. Offline never means stopped. Use join(standby="auto") and check listener health; with a healthy listener, end the idle turn. Manual fallback is limited to two recommended waits.
|
|
8
8
|
|
|
9
|
-
Install a built checkout or the unpacked/installed npm package root as a marketplace in Claude Code/Codex, or use ZCode Settings → Plugins → Create → Add plugin marketplace. Codex needs hooks enabled and five hook approvals; start a new session after installation. ZCode uses its native manifest and four supported hooks; MCP EOF handles offline state. Generic hosts can configure the absolute `bin/cmdr-mcp` executable and `CMDR_AGENT=<host>` with optional `CMDR_SESSION_ID=<unique native session>`.
|
|
9
|
+
Install a built checkout or the unpacked/installed npm package root as a marketplace in Claude Code/Codex, or use ZCode Settings → Plugins → Create → Add plugin marketplace. Codex needs hooks enabled and five hook approvals; start a new session after installation. For ZCode, use the published `njugray/cmdr#marketplace` source to install without a local build or global npm package. ZCode uses its native manifest and four supported hooks; MCP EOF handles offline state. Generic hosts can configure the absolute `bin/cmdr-mcp` executable and `CMDR_AGENT=<host>` with optional `CMDR_SESSION_ID=<unique native session>`.
|
|
10
10
|
|
|
11
11
|
`bin/cmdr config --agent zcode` prints native ZCode configuration; `bin/cmdr config --agent my-agent` prints generic MCP configuration. `bin/cmdr doctor` checks installation health. `bin/cmdr daemon restart` reloads same-version code changes. State lives in `~/.cmdr/` (override `CMDR_HOME`), shared by all sessions.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The daemon can wake existing Codex sessions through the shared public app-server transport; other hosts explicitly remain manual. It never creates agents or provides remote transport. Read marks delivery; report(working/done/failed/cancelled, reply_to) tracks work separately. cmdr standby manages listeners, and tail --after/--for supports non-consuming event replay. See [long-running collaboration](https://github.com/njugray/cmdr/blob/main/docs/long-running-collaboration.md).
|
|
14
14
|
|
|
15
15
|
[Installation and usage](https://github.com/njugray/cmdr#readme) · [Host integration](https://github.com/njugray/cmdr/blob/main/docs/agent-integration.md) · [Verification scope](https://github.com/njugray/cmdr/blob/main/docs/implementation.md)
|
|
16
|
+
|
|
17
|
+
The npm package is `cmdr-mcp`. Use `cmdr doctor --plugin-root <actual cache directory> --deep` to verify a cached installation. Static integrity checks run without the target CLI bundle; deep probes use temporary state. `cmdr session` provides all seven member operations with explicit host/native identity when MCP tools are unavailable. See [troubleshooting](https://github.com/njugray/cmdr/blob/main/docs/troubleshooting.md) for cancellation, identity and hook diagnostics.
|
package/plugins/cmdr/bin/cmdr
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
|
|
2
|
+
CMDR_SCRIPT=$0
|
|
3
|
+
while [ -L "$CMDR_SCRIPT" ]; do
|
|
4
|
+
CMDR_LINK=$(readlink "$CMDR_SCRIPT") || exit 1
|
|
5
|
+
case "$CMDR_LINK" in
|
|
6
|
+
/*) CMDR_SCRIPT=$CMDR_LINK ;;
|
|
7
|
+
*) CMDR_SCRIPT=$(dirname -- "$CMDR_SCRIPT")/$CMDR_LINK ;;
|
|
8
|
+
esac
|
|
9
|
+
done
|
|
10
|
+
CMDR_BIN_DIR=$(CDPATH= cd -- "$(dirname -- "$CMDR_SCRIPT")" && pwd)
|
|
3
11
|
export CMDR_BIN_DIR
|
|
4
12
|
exec "$CMDR_BIN_DIR/cmdr-node" cli "$@"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Kept outside dist so a damaged runtime can still be inspected with Node alone.
|
|
3
|
+
import { readFileSync, existsSync, lstatSync } from 'node:fs';
|
|
4
|
+
import { resolve, join } from 'node:path';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
const root = resolve(process.argv[2]);
|
|
7
|
+
const errors = [];
|
|
8
|
+
const read = (path) => JSON.parse(readFileSync(join(root, path), 'utf8'));
|
|
9
|
+
let manifest;
|
|
10
|
+
try {
|
|
11
|
+
manifest = read('dist/integrity.json');
|
|
12
|
+
} catch {
|
|
13
|
+
errors.push(
|
|
14
|
+
'Missing or invalid dist/integrity.json; reinstall this plugin from the built npm package.',
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
const files = {};
|
|
18
|
+
const required = [
|
|
19
|
+
'dist/cli.mjs',
|
|
20
|
+
'dist/mcp.mjs',
|
|
21
|
+
'dist/hook.mjs',
|
|
22
|
+
'dist/daemon.mjs',
|
|
23
|
+
'bin/cmdr',
|
|
24
|
+
'bin/cmdr-node',
|
|
25
|
+
'bin/cmdr-mcp',
|
|
26
|
+
'bin/cmdr-hook',
|
|
27
|
+
'bin/cmdr-daemon',
|
|
28
|
+
'bin/cmdr-check.mjs',
|
|
29
|
+
'hooks/hooks.json',
|
|
30
|
+
'.mcp.json',
|
|
31
|
+
'.claude-plugin/plugin.json',
|
|
32
|
+
'.codex-plugin/plugin.json',
|
|
33
|
+
'.zcode-plugin/plugin.json',
|
|
34
|
+
];
|
|
35
|
+
for (const path of new Set([...required, ...Object.keys(manifest?.files || {})])) {
|
|
36
|
+
if (
|
|
37
|
+
!required.includes(path) &&
|
|
38
|
+
!path.startsWith('skills/') &&
|
|
39
|
+
!path.startsWith('commands/') &&
|
|
40
|
+
path !== 'THIRD_PARTY_NOTICES.txt'
|
|
41
|
+
) {
|
|
42
|
+
errors.push(`Unexpected manifest path: ${path}`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (path.split('/').some((part) => part === '..' || part === '')) {
|
|
46
|
+
errors.push('Invalid manifest path');
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const full = join(root, path);
|
|
51
|
+
const linked = path
|
|
52
|
+
.split('/')
|
|
53
|
+
.some((_, i, parts) => lstatSync(join(root, ...parts.slice(0, i + 1))).isSymbolicLink());
|
|
54
|
+
const digest = createHash('sha256').update(readFileSync(full)).digest('hex');
|
|
55
|
+
files[path] = !linked && manifest?.files?.[path] === digest;
|
|
56
|
+
} catch {
|
|
57
|
+
files[path] = false;
|
|
58
|
+
}
|
|
59
|
+
if (!files[path]) errors.push(`Missing, modified or linked asset: ${path}`);
|
|
60
|
+
}
|
|
61
|
+
const versions = {};
|
|
62
|
+
for (const host of ['claude', 'codex', 'zcode']) {
|
|
63
|
+
try {
|
|
64
|
+
versions[host] = read(`.${host}-plugin/plugin.json`).version;
|
|
65
|
+
if (versions[host] !== manifest?.version) errors.push(`${host} version differs from build`);
|
|
66
|
+
} catch {
|
|
67
|
+
errors.push(`Invalid ${host} manifest`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
let hooks = {};
|
|
71
|
+
try {
|
|
72
|
+
hooks = read('hooks/hooks.json').hooks;
|
|
73
|
+
} catch {
|
|
74
|
+
errors.push('Invalid hooks configuration');
|
|
75
|
+
}
|
|
76
|
+
console.log(
|
|
77
|
+
JSON.stringify(
|
|
78
|
+
{
|
|
79
|
+
ok: errors.length === 0,
|
|
80
|
+
plugin_root: root,
|
|
81
|
+
version: manifest?.version,
|
|
82
|
+
node: process.version,
|
|
83
|
+
files,
|
|
84
|
+
versions,
|
|
85
|
+
hooks: {
|
|
86
|
+
configured: Object.keys(hooks || {}),
|
|
87
|
+
observed: 'unknown; configuration alone does not prove execution',
|
|
88
|
+
},
|
|
89
|
+
errors,
|
|
90
|
+
repair:
|
|
91
|
+
'npm install -g cmdr-mcp; register its package root, then refresh/reinstall the host plugin cache and start a new session. For a source checkout run npm ci && npm run build at repository root. Do not link dist from another installation.',
|
|
92
|
+
},
|
|
93
|
+
null,
|
|
94
|
+
2,
|
|
95
|
+
),
|
|
96
|
+
);
|
|
97
|
+
process.exitCode = errors.length ? 1 : 0;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
|
|
2
|
+
CMDR_SCRIPT=$0
|
|
3
|
+
while [ -L "$CMDR_SCRIPT" ]; do
|
|
4
|
+
CMDR_LINK=$(readlink "$CMDR_SCRIPT") || exit 1
|
|
5
|
+
case "$CMDR_LINK" in
|
|
6
|
+
/*) CMDR_SCRIPT=$CMDR_LINK ;;
|
|
7
|
+
*) CMDR_SCRIPT=$(dirname -- "$CMDR_SCRIPT")/$CMDR_LINK ;;
|
|
8
|
+
esac
|
|
9
|
+
done
|
|
10
|
+
CMDR_BIN_DIR=$(CDPATH= cd -- "$(dirname -- "$CMDR_SCRIPT")" && pwd)
|
|
3
11
|
export CMDR_BIN_DIR
|
|
4
12
|
exec "$CMDR_BIN_DIR/cmdr-node" daemon "$@"
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
|
|
2
|
+
CMDR_SCRIPT=$0
|
|
3
|
+
while [ -L "$CMDR_SCRIPT" ]; do
|
|
4
|
+
CMDR_LINK=$(readlink "$CMDR_SCRIPT") || exit 1
|
|
5
|
+
case "$CMDR_LINK" in
|
|
6
|
+
/*) CMDR_SCRIPT=$CMDR_LINK ;;
|
|
7
|
+
*) CMDR_SCRIPT=$(dirname -- "$CMDR_SCRIPT")/$CMDR_LINK ;;
|
|
8
|
+
esac
|
|
9
|
+
done
|
|
10
|
+
CMDR_BIN_DIR=$(CDPATH= cd -- "$(dirname -- "$CMDR_SCRIPT")" && pwd)
|
|
3
11
|
export CMDR_BIN_DIR
|
|
4
12
|
exec "$CMDR_BIN_DIR/cmdr-node" hook "$@"
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
|
|
2
|
+
CMDR_SCRIPT=$0
|
|
3
|
+
while [ -L "$CMDR_SCRIPT" ]; do
|
|
4
|
+
CMDR_LINK=$(readlink "$CMDR_SCRIPT") || exit 1
|
|
5
|
+
case "$CMDR_LINK" in
|
|
6
|
+
/*) CMDR_SCRIPT=$CMDR_LINK ;;
|
|
7
|
+
*) CMDR_SCRIPT=$(dirname -- "$CMDR_SCRIPT")/$CMDR_LINK ;;
|
|
8
|
+
esac
|
|
9
|
+
done
|
|
10
|
+
CMDR_BIN_DIR=$(CDPATH= cd -- "$(dirname -- "$CMDR_SCRIPT")" && pwd)
|
|
3
11
|
export CMDR_BIN_DIR
|
|
4
12
|
exec "$CMDR_BIN_DIR/cmdr-node" mcp "$@"
|
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
CMDR_ENTRY=$1
|
|
4
4
|
shift
|
|
5
5
|
CMDR_RUNTIME_HOME=${CMDR_HOME:-"$HOME/.cmdr"}
|
|
6
|
+
cmdr_failure() {
|
|
7
|
+
# One fixed-size record per failure type, rate limited across hook processes.
|
|
8
|
+
(umask 077
|
|
9
|
+
CMDR_LOG_DIR="$CMDR_RUNTIME_HOME/logs/diagnostics"
|
|
10
|
+
mkdir -p "$CMDR_LOG_DIR" || exit 0
|
|
11
|
+
CMDR_LOG_FILE="$CMDR_LOG_DIR/bootstrap-$1.json"
|
|
12
|
+
CMDR_NOW=$(date +%s)
|
|
13
|
+
CMDR_LAST=$(cat "$CMDR_LOG_DIR/bootstrap-$1.time" 2>/dev/null || true)
|
|
14
|
+
case "$CMDR_LAST" in ''|*[!0-9]*) CMDR_LAST=0 ;; esac
|
|
15
|
+
[ $((CMDR_NOW - CMDR_LAST)) -lt 60 ] && exit 0
|
|
16
|
+
printf '%s\n' "$CMDR_NOW" > "$CMDR_LOG_DIR/bootstrap-$1.time"
|
|
17
|
+
printf '{"code":"bootstrap-%s","at":%s000}\n' "$1" "$CMDR_NOW" > "$CMDR_LOG_FILE"
|
|
18
|
+
) 2>/dev/null || true
|
|
19
|
+
}
|
|
6
20
|
CMDR_NODE=''
|
|
7
21
|
if [ -r "$CMDR_RUNTIME_HOME/node-path" ]; then
|
|
8
22
|
IFS= read -r CMDR_NODE < "$CMDR_RUNTIME_HOME/node-path"
|
|
@@ -22,13 +36,22 @@ if [ -z "$CMDR_NODE" ]; then
|
|
|
22
36
|
fi
|
|
23
37
|
fi
|
|
24
38
|
if [ -z "$CMDR_NODE" ]; then
|
|
25
|
-
|
|
39
|
+
cmdr_failure node
|
|
40
|
+
[ "$CMDR_ENTRY" = hook ] && { echo "cmdr hook unavailable: Node >=22.5 required; run cmdr doctor." >&2; exit 0; }
|
|
26
41
|
echo 'cmdr requires Node.js >=22.5 (24 recommended). Install Node and retry.' >&2
|
|
27
42
|
exit 1
|
|
28
43
|
fi
|
|
29
44
|
if [ ! -f "$CMDR_BIN_DIR/../dist/$CMDR_ENTRY.mjs" ]; then
|
|
30
|
-
|
|
31
|
-
|
|
45
|
+
cmdr_failure runtime
|
|
46
|
+
[ "$CMDR_ENTRY" = hook ] && { echo "cmdr hook unavailable: missing dist runtime; build or reinstall the plugin and run cmdr doctor --plugin-root PATH." >&2; exit 0; }
|
|
47
|
+
if [ "$CMDR_ENTRY" = cli ] && [ "$1" = doctor ] && [ -f "$CMDR_BIN_DIR/cmdr-check.mjs" ]; then
|
|
48
|
+
exec "$CMDR_NODE" "$CMDR_BIN_DIR/cmdr-check.mjs" "$CMDR_BIN_DIR/.."
|
|
49
|
+
fi
|
|
50
|
+
echo 'cmdr runtime is not built. Run npm ci && npm run build in the repository, or install cmdr-mcp from npm, refresh/reinstall the host plugin cache and start a new session. Do not symlink another installation. Inspect this plugin with a working cmdr doctor --plugin-root PATH.' >&2
|
|
32
51
|
exit 1
|
|
33
52
|
fi
|
|
53
|
+
if [ "$CMDR_ENTRY" = hook ]; then
|
|
54
|
+
"$CMDR_NODE" --experimental-sqlite --disable-warning=ExperimentalWarning "$CMDR_BIN_DIR/../dist/hook.mjs" "$@" 2>/dev/null || cmdr_failure runtime
|
|
55
|
+
exit 0
|
|
56
|
+
fi
|
|
34
57
|
exec "$CMDR_NODE" --experimental-sqlite --disable-warning=ExperimentalWarning "$CMDR_BIN_DIR/../dist/$CMDR_ENTRY.mjs" "$@"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Create or join a
|
|
2
|
+
description: Create or join a persistent local agent channel.
|
|
3
3
|
argument-hint: <name>
|
|
4
4
|
---
|
|
5
|
-
Call
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Call join(squad_name="$ARGUMENTS", standby="auto") for atomic find-or-create. If no name was provided, ask for one; do not invent it. Joining defaults to executor, including a channel without a commander. Add role="commander" if the user explicitly requested command; use takeover=true only for requested handover.
|
|
6
|
+
Follow protocol_hint and the role skill. Executors report ready with cwd and capabilities. Give a brief translated user_reply preserving the join line. Check listener health: end the idle turn when can_auto_respond=true; otherwise use at most two recommended waits and explain manual continuation. Do not create a private wake script.
|
|
7
|
+
If tools are unavailable, follow using-cmdr diagnostics and member CLI fallback. Use the real host session ID, never an invented identity or raw socket workaround.
|