synomem 0.1.1 → 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/ARCHITECTURE.md +2 -2
- package/CHANGELOG.md +28 -0
- package/README.md +26 -13
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +280 -51
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +96 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +333 -76
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +8 -8
- package/dist/import.d.ts +207 -13
- package/dist/import.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +156 -31
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp-server.js +54 -8
- package/dist/mcp-server.js.map +1 -1
- package/dist/ports/repository.d.ts +18 -1
- package/dist/ports/repository.d.ts.map +1 -1
- package/dist/projections.d.ts +18 -1
- package/dist/projections.d.ts.map +1 -1
- package/dist/projections.js +137 -44
- package/dist/projections.js.map +1 -1
- package/dist/remote.d.ts +57 -9
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +43 -2
- package/dist/remote.js.map +1 -1
- package/dist/schemas.d.ts +302 -21
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +142 -26
- package/dist/schemas.js.map +1 -1
- package/dist/service.d.ts +57 -10
- package/dist/service.d.ts.map +1 -1
- package/dist/skill-install.d.ts +8 -2
- package/dist/skill-install.d.ts.map +1 -1
- package/dist/skill-install.js +6 -11
- package/dist/skill-install.js.map +1 -1
- package/dist/storage.d.ts +41 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +254 -36
- package/dist/storage.js.map +1 -1
- package/dist/types.d.ts +213 -38
- package/dist/types.d.ts.map +1 -1
- package/docs/cli.md +53 -20
- package/docs/examples.md +4 -4
- package/docs/mcp.md +12 -4
- package/docs/skill.md +10 -7
- package/docs/storage-format.md +4 -4
- package/openapi/synomem-v1.yaml +24 -24
- package/package.json +1 -1
- package/skills/synomem/SKILL.md +24 -8
- package/skills/synomem/agents/openai.yaml +1 -1
- package/skills/synomem/references/examples.md +1 -1
- package/src/cli.ts +444 -95
- package/src/client.ts +391 -79
- package/src/config.ts +8 -8
- package/src/index.ts +11 -1
- package/src/mcp/index.ts +189 -30
- package/src/mcp-server.ts +58 -8
- package/src/ports/repository.ts +16 -0
- package/src/projections.ts +139 -44
- package/src/remote.ts +118 -8
- package/src/schemas.ts +147 -26
- package/src/service.ts +59 -7
- package/src/skill-install.ts +14 -17
- package/src/storage.ts +326 -34
- package/src/types.ts +228 -39
package/ARCHITECTURE.md
CHANGED
|
@@ -44,7 +44,7 @@ reconstruct one requested aggregate. Generated Markdown is never a machine query
|
|
|
44
44
|
## Authorization
|
|
45
45
|
|
|
46
46
|
MCP servers bind one actor at startup, and tool inputs cannot override it. Humans have local
|
|
47
|
-
administrative authority. Agents can manage their own notes, recipient memo state, and
|
|
47
|
+
administrative authority. Agents can manage their own notes and todos, recipient memo state, and tasks they
|
|
48
48
|
created or received. System actors have no implicit agent or administrator authority. Actor
|
|
49
49
|
principals are keyed by both kind and ID; matching text IDs across kinds do not share author access.
|
|
50
50
|
|
|
@@ -55,7 +55,7 @@ The filesystem owner remains the ultimate local authority.
|
|
|
55
55
|
## Concurrency and durability
|
|
56
56
|
|
|
57
57
|
SQLite uses WAL, foreign keys, `synchronous=FULL`, a bounded busy handler, and `BEGIN IMMEDIATE`
|
|
58
|
-
transactions. Actor-scoped idempotency protects retries. Note and todo revisions use aggregate
|
|
58
|
+
transactions. Actor-scoped idempotency protects retries. Note, task, and todo revisions use aggregate
|
|
59
59
|
versions so stale writes fail with `REVISION_CONFLICT` rather than overwriting concurrent state.
|
|
60
60
|
|
|
61
61
|
V1 supports one machine and one filesystem owner. A future hosted service must enforce the same
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ All notable changes will be documented here. The format follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- The MCP server binds to an agent with `--agent-id`, reading the display name and actor kind from
|
|
11
|
+
the agent's profile. `synomem skill install --agent <id-or-alias>` resolves the agent before
|
|
12
|
+
writing anything, records a runtime binding for each runtime it installs, and generates a
|
|
13
|
+
registration command carrying only the canonical ID.
|
|
14
|
+
|
|
15
|
+
## [0.2.0] - 2026-09-05
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Private todos as a distinct domain: a todo belongs to the agent that wrote it, is visible to no
|
|
20
|
+
one else, and cannot be assigned. Work meant for another agent is a task.
|
|
21
|
+
- Optional responses when accepting a task and required responses when rejecting one, so a refusal
|
|
22
|
+
always tells the assigner why.
|
|
23
|
+
- Discovery for work that has stalled: tasks awaiting a response and tasks past their deadline.
|
|
24
|
+
- Case-insensitive agent aliases with `agent resolve`, which returns candidates instead of guessing
|
|
25
|
+
when several agents answer to one name, and `agent directory`.
|
|
26
|
+
- Advisory runtime bindings recording where an agent was registered to run, with `agent runtime
|
|
27
|
+
bind`, `list`, and `unbind`, and the matching `synomem_agent_resolve` and
|
|
28
|
+
`synomem_agent_directory` MCP tools.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Schema version 5 adds a normalized alias column with a unique index and an
|
|
33
|
+
`agent_runtime_bindings` table. Existing databases migrate in place.
|
|
34
|
+
- Aliases are stored folded to lowercase, so one name cannot be claimed twice in two casings.
|
|
35
|
+
|
|
8
36
|
## [0.1.1] - 2026-09-04
|
|
9
37
|
|
|
10
38
|
### Fixed
|
package/README.md
CHANGED
|
@@ -69,11 +69,17 @@ synomem note create --as gracie \
|
|
|
69
69
|
--title "Release invariant" \
|
|
70
70
|
--body "Never publish without explicit maintainer authorization."
|
|
71
71
|
|
|
72
|
-
synomem
|
|
72
|
+
synomem task create codex \
|
|
73
73
|
--from gracie --title "Review the migration" --due-date 2026-09-15
|
|
74
74
|
|
|
75
75
|
synomem inbox codex
|
|
76
|
-
synomem
|
|
76
|
+
synomem task accept <task-id> --as codex --response "Starting after the tests."
|
|
77
|
+
|
|
78
|
+
# A todo is private to the agent that wrote it; nobody else can assign one.
|
|
79
|
+
synomem todo create --as codex --title "Re-read the migration notes"
|
|
80
|
+
|
|
81
|
+
synomem agent resolve Mike
|
|
82
|
+
synomem agent directory
|
|
77
83
|
synomem list
|
|
78
84
|
```
|
|
79
85
|
|
|
@@ -87,15 +93,15 @@ terminal-capable agent. The Synomem package contains the portable
|
|
|
87
93
|
installer for the six named local harnesses.
|
|
88
94
|
|
|
89
95
|
```text
|
|
90
|
-
Set up Synomem for this agent and runtime. Synomem is a local-first coordination system for durable kudos, one-to-one memos, private agent notes,
|
|
96
|
+
Set up Synomem for this agent and runtime. Synomem is a local-first coordination system for durable kudos, one-to-one memos, private agent notes, consent-based assigned tasks, and private todos. It uses an append-only SQLite database under ~/.agents by default, an actor-bound stdio MCP server, and a portable Agent Skill. Multiple local agents may share the database, but every MCP server must be bound to its own stable identity.
|
|
91
97
|
|
|
92
98
|
Work autonomously through the safe, reversible steps below. Do not expose secrets, overwrite unrelated configuration, invent an identity, use --force without my explicit approval, or modify another agent's integration.
|
|
93
99
|
|
|
94
100
|
1. Verify Node.js 22.13+ and npm are available. Install or update the public package with `npm install --global synomem` if needed, then report `synomem --version`.
|
|
95
101
|
2. Preserve an existing `SYNOMEM_HOME`; otherwise use the default ~/.agents. Run `synomem init`, then `synomem doctor`. Never point tests or experiments at another Synomem home.
|
|
96
102
|
3. Run `synomem agent list`. Determine this agent's existing stable ID from the current harness or Synomem configuration and reuse it. If no identity is clearly established, ask me for the agent ID and display name before running `synomem agent create <id> --name <name>`. Never silently merge or rename identities.
|
|
97
|
-
4. Detect the current harness from actual local evidence and its installed CLI help. Use runtime `claude` for Claude Code, `codex` for Codex, `hermes` for Hermes, `openclaw` for OpenClaw, `cursor` for Cursor, or `grok` for local Grok Build (`grokbot` is accepted as an alias). Check `synomem skill install --help`, then preview with `synomem skill install --runtime <runtime> --
|
|
98
|
-
5. Inspect any actor-bound MCP registration command printed by the installer. Check the harness's existing MCP list/config first, then run the command only if `synomem` is absent or incorrect. Do not create duplicates. Cursor has no noninteractive MCP-add command: carefully merge a `synomem` stdio entry into its documented user `~/.cursor/mcp.json`, using command `synomem-mcp` and
|
|
103
|
+
4. Detect the current harness from actual local evidence and its installed CLI help. Use runtime `claude` for Claude Code, `codex` for Codex, `hermes` for Hermes, `openclaw` for OpenClaw, `cursor` for Cursor, or `grok` for local Grok Build (`grokbot` is accepted as an alias). Check `synomem skill install --help`, then preview with `synomem skill install --runtime <runtime> --agent <agent-id>`. Review the exact destination and apply the same command with `--yes`; it must report `current`. If the installed release does not yet list this runtime, locate the packaged source under the global npm root at `synomem/skills/synomem` and follow the verified destination and conflict rules in https://github.com/Coaden/synomem/blob/main/docs/skill.md instead. Do not guess a path, overwrite an existing skill, or create a fake harness home to make an unavailable runtime appear installed.
|
|
104
|
+
5. Inspect any actor-bound MCP registration command printed by the installer. Check the harness's existing MCP list/config first, then run the command only if `synomem` is absent or incorrect. Do not create duplicates. Cursor has no noninteractive MCP-add command: carefully merge a `synomem` stdio entry into its documented user `~/.cursor/mcp.json`, using command `synomem-mcp` and the single argument `--agent-id <agent-id>` (the display name and kind come from the agent's profile, so nothing in a shared config file asserts an identity); preserve every existing entry.
|
|
99
105
|
6. Verify the harness can discover the installed skill and MCP server using its own list/status commands, then run `synomem doctor`. Start a new agent session if that harness does not live-reload a newly created skills directory.
|
|
100
106
|
7. If this is hosted Grok Bot rather than local Grok Build, do not claim it shares the desktop's local SQLite database. Install the package and skill only inside a persistent terminal environment where `npm`, local stdio MCP, and ~/.grok are actually available. Otherwise provide the skill URL https://github.com/Coaden/synomem/blob/main/skills/synomem/SKILL.md and explain the unsupported boundary; do not expose the local database through a tunnel.
|
|
101
107
|
8. Report the package version, stable actor ID, storage home, installed skill path, MCP registration and verification status, whether a new session is needed, and every file or configuration changed. Do not print record contents or environment values beyond the non-secret actor identity and home path.
|
|
@@ -130,13 +136,20 @@ await client.notes.revise({
|
|
|
130
136
|
body: 'Never publish or create a release without explicit maintainer authorization.',
|
|
131
137
|
});
|
|
132
138
|
|
|
133
|
-
|
|
139
|
+
// A task is assigned to someone else and needs their consent.
|
|
140
|
+
await client.tasks.create({
|
|
134
141
|
assigneeAgentId: 'codex',
|
|
135
142
|
title: 'Review the migration',
|
|
136
143
|
due: { kind: 'date', date: '2026-09-15' },
|
|
137
144
|
});
|
|
138
145
|
|
|
139
|
-
|
|
146
|
+
// A todo is the agent's own reminder, visible to no one else.
|
|
147
|
+
await client.todos.create({
|
|
148
|
+
title: 'Re-read the migration notes',
|
|
149
|
+
due: { kind: 'date', date: '2026-09-14' },
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const page = await client.items.list({ kinds: ['memo', 'task'], limit: 10 });
|
|
140
153
|
const changes = await client.items.changes({ after: page.watermark });
|
|
141
154
|
|
|
142
155
|
await client.close();
|
|
@@ -147,7 +160,7 @@ The library performs no filesystem work at import time and never terminates its
|
|
|
147
160
|
## Context-safe reads
|
|
148
161
|
|
|
149
162
|
`client.items.list()` and MCP `synomem_list` return 10 compact summaries by default and at most 50.
|
|
150
|
-
Summaries omit message bodies, kudos reasons and evidence, note bodies, todo
|
|
163
|
+
Summaries omit message bodies, kudos reasons and evidence, note bodies, task and todo details, source,
|
|
151
164
|
and metadata. Fetch one authorized detail record with `items.get(id)` or `synomem_get`.
|
|
152
165
|
|
|
153
166
|
Incremental reads return at most 20 changes by default and 100 at most. List and change responses
|
|
@@ -176,9 +189,9 @@ bounded reads. See [the MCP guide](docs/mcp.md).
|
|
|
176
189
|
The package includes [`skills/synomem`](skills/synomem). Installation is explicit and dry-run first:
|
|
177
190
|
|
|
178
191
|
```bash
|
|
179
|
-
synomem skill install --runtime codex --
|
|
180
|
-
synomem skill install --runtime codex --
|
|
181
|
-
synomem skill install --runtime hermes --
|
|
192
|
+
synomem skill install --runtime codex --agent codex
|
|
193
|
+
synomem skill install --runtime codex --agent codex --yes
|
|
194
|
+
synomem skill install --runtime hermes --agent mycroft --yes
|
|
182
195
|
synomem skill status
|
|
183
196
|
```
|
|
184
197
|
|
|
@@ -197,8 +210,8 @@ are `claude`, `codex`, `hermes`, `openclaw`, `cursor`, and `grok`; `grokbot` ali
|
|
|
197
210
|
├── profile.json
|
|
198
211
|
├── WINS.md
|
|
199
212
|
├── MEMORY.md
|
|
200
|
-
├──
|
|
201
|
-
├── inbox/{kudos,memos,
|
|
213
|
+
├── TASKS.md
|
|
214
|
+
├── inbox/{kudos,memos,tasks}/
|
|
202
215
|
└── NOTES.md
|
|
203
216
|
```
|
|
204
217
|
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAKA,OAAO,EAAE,OAAO,EAA0B,MAAM,WAAW,CAAC;AAE5D,OAAO,EAA0C,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAIhG,OAAO,EAA4C,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE9F,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AASrB,OAAO,KAAK,EACV,aAAa,EAQd,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAkB,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1E,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE;QACjC,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,aAAa,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,eAAe,CAAC;KAClC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7D,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,aAAa,CAAC;QACrB,MAAM,EAAE,YAAY,CAAC;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,KAAK,OAAO,CAAC,aAAa,GAAG,YAAY,CAAC,CAAC;CAC7C;AAkND,wBAAgB,SAAS,CACvB,EAAE,GAAE,KAAiB,EACrB,cAAc,GAAE,qBAAgD,EAChE,YAAY,GAAE,eAAoB,GACjC,OAAO,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAKA,OAAO,EAAE,OAAO,EAA0B,MAAM,WAAW,CAAC;AAE5D,OAAO,EAA0C,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAIhG,OAAO,EAA4C,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE9F,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AASrB,OAAO,KAAK,EACV,aAAa,EAQd,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAkB,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1E,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE;QACjC,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,aAAa,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,eAAe,CAAC;KAClC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7D,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,aAAa,CAAC;QACrB,MAAM,EAAE,YAAY,CAAC;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,KAAK,OAAO,CAAC,aAAa,GAAG,YAAY,CAAC,CAAC;CAC7C;AAkND,wBAAgB,SAAS,CACvB,EAAE,GAAE,KAAiB,EACrB,cAAc,GAAE,qBAAgD,EAChE,YAAY,GAAE,eAAoB,GACjC,OAAO,CA8qDT;AAED,wBAAsB,MAAM,CAC1B,IAAI,WAAe,EACnB,EAAE,GAAE,KAAiB,EACrB,cAAc,GAAE,qBAAgD,EAChE,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,MAAM,CAAC,CAqBjB"}
|
package/dist/cli.js
CHANGED
|
@@ -62,7 +62,7 @@ function defaultActor(env, fallbackKind, fallbackId) {
|
|
|
62
62
|
return actor(fallbackKind, fallbackId);
|
|
63
63
|
return actor(env.SYNOMEM_ACTOR_KIND?.trim() || fallbackKind, id, env.SYNOMEM_ACTOR_NAME?.trim());
|
|
64
64
|
}
|
|
65
|
-
function
|
|
65
|
+
function taskDue(options) {
|
|
66
66
|
if (options.dueDate && options.dueAt)
|
|
67
67
|
throw new SynomemError('INVALID_INPUT', 'Use due-date or due-at, not both.');
|
|
68
68
|
if (options.dueDate)
|
|
@@ -444,32 +444,64 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
444
444
|
.option('--yes', 'apply the displayed plan', false)
|
|
445
445
|
.option('--force', 'replace a conflicting synomem directory', false)
|
|
446
446
|
.option('--link', 'symlink to the packaged skill instead of copying it', false)
|
|
447
|
-
.option('--
|
|
448
|
-
.
|
|
449
|
-
.action((options, command) => {
|
|
447
|
+
.option('--agent <id-or-alias>', 'bind this installation to an agent')
|
|
448
|
+
.action(async (options, command) => {
|
|
450
449
|
const global = globals(command);
|
|
450
|
+
const runtimes = skillRuntimes(options.runtime);
|
|
451
|
+
/*
|
|
452
|
+
* The agent is resolved BEFORE anything is written. An ambiguous or
|
|
453
|
+
* unknown name then stops the command with a name to fix, rather than
|
|
454
|
+
* leaving a skill installed and pointed at an agent that does not
|
|
455
|
+
* exist.
|
|
456
|
+
*/
|
|
457
|
+
let agentId;
|
|
458
|
+
if (options.agent) {
|
|
459
|
+
agentId = await withClient(global.home, defaultActor(env, 'system', 'cli'), async (client) => {
|
|
460
|
+
const resolution = await client.agents.resolve(options.agent);
|
|
461
|
+
if (!resolution.match) {
|
|
462
|
+
throw new SynomemError('AGENT_NOT_FOUND', resolution.candidates.length
|
|
463
|
+
? `"${options.agent}" matches ${resolution.candidates.length} agents: ${resolution.candidates
|
|
464
|
+
.map((candidate) => candidate.id)
|
|
465
|
+
.join(', ')}. Name one of them.`
|
|
466
|
+
: `Unknown agent: ${options.agent}`);
|
|
467
|
+
}
|
|
468
|
+
return resolution.match.id;
|
|
469
|
+
});
|
|
470
|
+
}
|
|
451
471
|
const result = installSkill({
|
|
452
|
-
runtimes:
|
|
472
|
+
...(runtimes ? { runtimes } : {}),
|
|
453
473
|
apply: options.yes,
|
|
454
474
|
force: options.force,
|
|
455
475
|
link: options.link,
|
|
456
|
-
|
|
457
|
-
actorName: options.actorName,
|
|
476
|
+
...(agentId ? { agentId } : {}),
|
|
458
477
|
});
|
|
478
|
+
// Bindings follow what was actually installed, and only on a real run:
|
|
479
|
+
// a dry run must not claim a binding it did not make, and a runtime
|
|
480
|
+
// whose harness is not present here is not somewhere this agent runs.
|
|
481
|
+
if (agentId && options.yes) {
|
|
482
|
+
const installed = result.locations
|
|
483
|
+
.filter((location) => location.state !== 'unavailable')
|
|
484
|
+
.map((location) => location.runtime);
|
|
485
|
+
if (installed.length) {
|
|
486
|
+
await withClient(global.home, defaultActor(env, 'system', 'cli'), async (client) => {
|
|
487
|
+
for (const runtime of installed) {
|
|
488
|
+
await client.agents.bindRuntime({ agentId, runtime });
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
459
493
|
output(io, global.json, result, formatSkillResult(result, 'install'));
|
|
460
494
|
});
|
|
461
495
|
skillCommand
|
|
462
496
|
.command('status')
|
|
463
497
|
.description('Show installed, stale, missing, or conflicting skill copies')
|
|
464
498
|
.option('--runtime <runtime>', `${skillRuntimeHelp} (repeatable)`, collect, [])
|
|
465
|
-
.option('--
|
|
466
|
-
.option('--actor-name <name>', 'display name used in MCP registration commands')
|
|
499
|
+
.option('--agent <id>', 'print the registration command for this agent')
|
|
467
500
|
.action((options, command) => {
|
|
468
501
|
const global = globals(command);
|
|
469
502
|
const result = skillStatus({
|
|
470
503
|
runtimes: skillRuntimes(options.runtime),
|
|
471
|
-
|
|
472
|
-
actorName: options.actorName,
|
|
504
|
+
...(options.agent ? { agentId: options.agent } : {}),
|
|
473
505
|
});
|
|
474
506
|
output(io, global.json, result, formatSkillResult(result, 'status'));
|
|
475
507
|
});
|
|
@@ -526,6 +558,84 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
526
558
|
}));
|
|
527
559
|
output(io, global.json, profile, `Updated ${profile.displayName} (${profile.id})`);
|
|
528
560
|
});
|
|
561
|
+
agentCommand
|
|
562
|
+
.command('resolve <name>')
|
|
563
|
+
.description('Resolve a name or alias to one agent, or list the candidates')
|
|
564
|
+
.action(async (name, _options, command) => {
|
|
565
|
+
const global = globals(command);
|
|
566
|
+
const resolution = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.resolve(name));
|
|
567
|
+
// An ambiguous name is a question, not a failure: exit zero and show the
|
|
568
|
+
// candidates so the caller can pick one.
|
|
569
|
+
const human = resolution.match
|
|
570
|
+
? `${resolution.match.displayName} (${resolution.match.id})`
|
|
571
|
+
: resolution.candidates.length
|
|
572
|
+
? `"${resolution.query}" is ambiguous. Candidates:\n${resolution.candidates
|
|
573
|
+
.map((profile) => ` ${profile.id} ${profile.displayName}`)
|
|
574
|
+
.join('\n')}`
|
|
575
|
+
: `No agent answers to "${resolution.query}".`;
|
|
576
|
+
output(io, global.json, resolution, human);
|
|
577
|
+
});
|
|
578
|
+
agentCommand
|
|
579
|
+
.command('directory')
|
|
580
|
+
.description('List agents with their runtime bindings')
|
|
581
|
+
.action(async (_options, command) => {
|
|
582
|
+
const global = globals(command);
|
|
583
|
+
const entries = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.directory());
|
|
584
|
+
const human = entries.length
|
|
585
|
+
? entries
|
|
586
|
+
.map((entry) => {
|
|
587
|
+
const runtimes = entry.runtimeBindings.length
|
|
588
|
+
? entry.runtimeBindings
|
|
589
|
+
.map((binding) => ` ${binding.runtime}${binding.profile ? `/${binding.profile}` : ''}` +
|
|
590
|
+
// Last-seen is advisory, so it is labelled as an
|
|
591
|
+
// observation rather than a status.
|
|
592
|
+
`${binding.lastSeenAt ? ` last seen ${binding.lastSeenAt}` : ' not yet seen'}`)
|
|
593
|
+
.join('\n')
|
|
594
|
+
: ' no runtime bindings';
|
|
595
|
+
return `${entry.profile.id} ${entry.profile.displayName}\n${runtimes}`;
|
|
596
|
+
})
|
|
597
|
+
.join('\n')
|
|
598
|
+
: 'No agents configured.';
|
|
599
|
+
output(io, global.json, { entries }, human);
|
|
600
|
+
});
|
|
601
|
+
const runtimeCommand = agentCommand.command('runtime').description('Record where an agent runs');
|
|
602
|
+
runtimeCommand
|
|
603
|
+
.command('bind <agent>')
|
|
604
|
+
.description('Bind an agent to a runtime')
|
|
605
|
+
.requiredOption('--runtime <name>', 'runtime family, e.g. claude-code')
|
|
606
|
+
.option('--profile <name>', 'named configuration within the runtime')
|
|
607
|
+
.option('--installation <id>', 'hosted installation this binding belongs to')
|
|
608
|
+
.action(async (agent, options, command) => {
|
|
609
|
+
const global = globals(command);
|
|
610
|
+
const binding = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.bindRuntime({
|
|
611
|
+
agentId: agent,
|
|
612
|
+
runtime: options.runtime,
|
|
613
|
+
...(options.profile ? { profile: options.profile } : {}),
|
|
614
|
+
...(options.installation ? { installationId: options.installation } : {}),
|
|
615
|
+
}));
|
|
616
|
+
output(io, global.json, binding, `Bound ${binding.agentId} to ${binding.runtime}${binding.profile ? `/${binding.profile}` : ''} (${binding.id})`);
|
|
617
|
+
});
|
|
618
|
+
runtimeCommand
|
|
619
|
+
.command('list <agent>')
|
|
620
|
+
.description('List an agent runtime bindings')
|
|
621
|
+
.action(async (agent, _options, command) => {
|
|
622
|
+
const global = globals(command);
|
|
623
|
+
const bindings = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.bindings(agent));
|
|
624
|
+
const human = bindings.length
|
|
625
|
+
? bindings
|
|
626
|
+
.map((binding) => `${binding.id} ${binding.runtime}${binding.profile ? `/${binding.profile}` : ''} bound ${binding.boundAt}`)
|
|
627
|
+
.join('\n')
|
|
628
|
+
: 'No runtime bindings.';
|
|
629
|
+
output(io, global.json, { bindings }, human);
|
|
630
|
+
});
|
|
631
|
+
runtimeCommand
|
|
632
|
+
.command('unbind <binding-id>')
|
|
633
|
+
.description('Remove a runtime binding')
|
|
634
|
+
.action(async (bindingId, _options, command) => {
|
|
635
|
+
const global = globals(command);
|
|
636
|
+
const removed = await withClient(global.home, defaultActor(env, 'system', 'cli'), (client) => client.agents.unbindRuntime(bindingId));
|
|
637
|
+
output(io, global.json, { removed }, removed ? `Removed binding ${bindingId}.` : `No binding ${bindingId}.`);
|
|
638
|
+
});
|
|
529
639
|
const kudosCommand = program.command('kudos').description('Give and manage agent recognition');
|
|
530
640
|
kudosCommand
|
|
531
641
|
.command('give <recipient>')
|
|
@@ -555,7 +665,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
555
665
|
});
|
|
556
666
|
program
|
|
557
667
|
.command('inbox [agent]')
|
|
558
|
-
.description('Show pending kudos, memos, and
|
|
668
|
+
.description('Show pending kudos, memos, and tasks for an agent')
|
|
559
669
|
.option('--as <agent-id>', 'defaults to the positional agent')
|
|
560
670
|
.option('--limit <number>', 'maximum results (default 10, maximum 50)', '10')
|
|
561
671
|
.option('--cursor <cursor>', 'opaque cursor returned by the previous page')
|
|
@@ -584,7 +694,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
584
694
|
program
|
|
585
695
|
.command('list')
|
|
586
696
|
.description('List compact summaries across all record types')
|
|
587
|
-
.option('--kind <kind>', 'kudos, memo, note, or
|
|
697
|
+
.option('--kind <kind>', 'kudos, memo, note, or task (repeatable)', collect, [])
|
|
588
698
|
.option('--participant <agent>')
|
|
589
699
|
.option('--actor <id>')
|
|
590
700
|
.option('--tag <tag>')
|
|
@@ -755,8 +865,100 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
755
865
|
}));
|
|
756
866
|
output(io, global.json, record, `Archived note ${id}.`);
|
|
757
867
|
});
|
|
758
|
-
const todoCommand = program
|
|
868
|
+
const todoCommand = program
|
|
869
|
+
.command('todo')
|
|
870
|
+
.description('Create and manage your own private reminders');
|
|
759
871
|
todoCommand
|
|
872
|
+
.command('create')
|
|
873
|
+
.requiredOption('--as <actor-id>')
|
|
874
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
875
|
+
.requiredOption('--title <title>')
|
|
876
|
+
.option('--details <text>', 'private working detail')
|
|
877
|
+
.option('--priority <number>', '1 highest, 4 lowest', '3')
|
|
878
|
+
.option('--due-date <date>')
|
|
879
|
+
.option('--due-at <datetime>')
|
|
880
|
+
.option('--time-zone <iana-zone>')
|
|
881
|
+
.option('--tag <tag>', 'tag (repeatable)', collect, [])
|
|
882
|
+
.option('--idempotency-key <key>')
|
|
883
|
+
.action(async (options, command) => {
|
|
884
|
+
const global = globals(command);
|
|
885
|
+
const result = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.todos.create({
|
|
886
|
+
title: options.title,
|
|
887
|
+
...(options.details ? { details: options.details } : {}),
|
|
888
|
+
priority: Number(options.priority),
|
|
889
|
+
...((due) => (due ? { due } : {}))(taskDue(options)),
|
|
890
|
+
...(options.tag.length ? { tags: options.tag } : {}),
|
|
891
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
892
|
+
}));
|
|
893
|
+
output(io, global.json, result, `${result.deduplicated ? 'Found existing' : 'Created'} private todo\nTitle: ${result.record.current.title}\nID: ${result.record.event.id}`);
|
|
894
|
+
});
|
|
895
|
+
todoCommand
|
|
896
|
+
.command('list')
|
|
897
|
+
.requiredOption('--as <actor-id>')
|
|
898
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
899
|
+
.option('--status <status>')
|
|
900
|
+
.option('--limit <number>', 'maximum results', '10')
|
|
901
|
+
.action(async (options, command) => {
|
|
902
|
+
const global = globals(command);
|
|
903
|
+
const page = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.todos.list({
|
|
904
|
+
...(options.status ? { status: options.status } : {}),
|
|
905
|
+
limit: Number(options.limit),
|
|
906
|
+
}));
|
|
907
|
+
output(io, global.json, page, page.items.length
|
|
908
|
+
? page.items
|
|
909
|
+
.map((item) => `${item.id} ${item.status.padEnd(9)} ${item.title}`)
|
|
910
|
+
.join('\n')
|
|
911
|
+
: 'No todos.');
|
|
912
|
+
});
|
|
913
|
+
todoCommand
|
|
914
|
+
.command('show <todo-id>')
|
|
915
|
+
.requiredOption('--as <actor-id>')
|
|
916
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
917
|
+
.action(async (id, options, command) => {
|
|
918
|
+
const global = globals(command);
|
|
919
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.todos.get(id));
|
|
920
|
+
output(io, global.json, record, `${record.current.title}\nStatus: ${record.status}\nPriority: ${record.current.priority}\nVersion: ${record.current.version}${record.current.details ? `\n\n${record.current.details}` : ''}`);
|
|
921
|
+
});
|
|
922
|
+
for (const operation of ['complete', 'reopen', 'cancel', 'archive']) {
|
|
923
|
+
todoCommand
|
|
924
|
+
.command(`${operation} <todo-id>`)
|
|
925
|
+
.requiredOption('--as <actor-id>')
|
|
926
|
+
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
927
|
+
.option('--note <text>')
|
|
928
|
+
.option('--reason <text>')
|
|
929
|
+
.option('--idempotency-key <key>')
|
|
930
|
+
.action(async (id, options, command) => {
|
|
931
|
+
const global = globals(command);
|
|
932
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => operation === 'complete'
|
|
933
|
+
? client.todos.complete({
|
|
934
|
+
todoId: id,
|
|
935
|
+
...(options.note ? { note: options.note } : {}),
|
|
936
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
937
|
+
})
|
|
938
|
+
: operation === 'cancel'
|
|
939
|
+
? client.todos.cancel({
|
|
940
|
+
todoId: id,
|
|
941
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
942
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
943
|
+
})
|
|
944
|
+
: operation === 'archive'
|
|
945
|
+
? client.todos.archive({
|
|
946
|
+
todoId: id,
|
|
947
|
+
...(options.idempotencyKey
|
|
948
|
+
? { idempotencyKey: options.idempotencyKey }
|
|
949
|
+
: {}),
|
|
950
|
+
})
|
|
951
|
+
: client.todos.reopen({
|
|
952
|
+
todoId: id,
|
|
953
|
+
...(options.idempotencyKey
|
|
954
|
+
? { idempotencyKey: options.idempotencyKey }
|
|
955
|
+
: {}),
|
|
956
|
+
}));
|
|
957
|
+
output(io, global.json, record, `Todo ${id} is now ${record.status}.`);
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
const taskCommand = program.command('task').description('Create and manage agent tasks');
|
|
961
|
+
taskCommand
|
|
760
962
|
.command('create <assignee>')
|
|
761
963
|
.requiredOption('--from <actor-id>')
|
|
762
964
|
.option('--actor-kind <kind>', 'human, agent, or system', 'agent')
|
|
@@ -771,39 +973,39 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
771
973
|
.option('--idempotency-key <key>')
|
|
772
974
|
.action(async (assignee, options, command) => {
|
|
773
975
|
const global = globals(command);
|
|
774
|
-
const result = await withClient(global.home, actor(options.actorKind, options.from), (client) => client.
|
|
976
|
+
const result = await withClient(global.home, actor(options.actorKind, options.from), (client) => client.tasks.create({
|
|
775
977
|
assigneeAgentId: assignee,
|
|
776
978
|
title: options.title,
|
|
777
979
|
...(options.description ? { description: options.description } : {}),
|
|
778
980
|
priority: Number(options.priority),
|
|
779
|
-
...((due) => (due ? { due } : {}))(
|
|
981
|
+
...((due) => (due ? { due } : {}))(taskDue(options)),
|
|
780
982
|
...(options.tag.length ? { tags: options.tag } : {}),
|
|
781
983
|
visibility: options.visibility,
|
|
782
984
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
783
985
|
}));
|
|
784
|
-
output(io, global.json, result, `${result.deduplicated ? 'Found existing' : 'Created'}
|
|
986
|
+
output(io, global.json, result, `${result.deduplicated ? 'Found existing' : 'Created'} task for ${result.record.event.assigneeDisplayName}\nTitle: ${result.record.current.title}\nID: ${result.record.event.id}`);
|
|
785
987
|
});
|
|
786
|
-
|
|
988
|
+
taskCommand
|
|
787
989
|
.command('list')
|
|
788
990
|
.option('--assignee <agent>')
|
|
789
991
|
.option('--status <status>')
|
|
790
992
|
.option('--limit <number>', 'maximum results', '10')
|
|
791
993
|
.action(async (options, command) => {
|
|
792
994
|
const global = globals(command);
|
|
793
|
-
const page = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.
|
|
995
|
+
const page = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.tasks.list({
|
|
794
996
|
...(options.assignee ? { participantAgentId: options.assignee } : {}),
|
|
795
997
|
...(options.status ? { status: options.status } : {}),
|
|
796
998
|
limit: Number(options.limit),
|
|
797
999
|
}));
|
|
798
|
-
output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No
|
|
1000
|
+
output(io, global.json, page, page.items.map(lineForItem).join('\n') || 'No tasks found.');
|
|
799
1001
|
});
|
|
800
|
-
|
|
1002
|
+
taskCommand.command('show <task-id>').action(async (id, _options, command) => {
|
|
801
1003
|
const global = globals(command);
|
|
802
|
-
const record = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.
|
|
1004
|
+
const record = await withClient(global.home, defaultActor(env, 'human', 'local-cli'), (client) => client.tasks.get(id));
|
|
803
1005
|
output(io, global.json, record, `${record.current.title}\nID: ${record.event.id}\nStatus: ${record.status}\nVersion: ${record.current.version}`);
|
|
804
1006
|
});
|
|
805
|
-
|
|
806
|
-
.command('update <
|
|
1007
|
+
taskCommand
|
|
1008
|
+
.command('update <task-id>')
|
|
807
1009
|
.requiredOption('--as <actor-id>')
|
|
808
1010
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
809
1011
|
.requiredOption('--expected-version <number>')
|
|
@@ -818,9 +1020,9 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
818
1020
|
.option('--idempotency-key <key>')
|
|
819
1021
|
.action(async (id, options, command) => {
|
|
820
1022
|
const global = globals(command);
|
|
821
|
-
const parsedDue =
|
|
822
|
-
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.
|
|
823
|
-
|
|
1023
|
+
const parsedDue = taskDue(options);
|
|
1024
|
+
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => client.tasks.update({
|
|
1025
|
+
taskId: id,
|
|
824
1026
|
expectedVersion: Number(options.expectedVersion),
|
|
825
1027
|
...(options.title ? { title: options.title } : {}),
|
|
826
1028
|
...(options.description !== undefined ? { description: options.description } : {}),
|
|
@@ -829,52 +1031,59 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
829
1031
|
...(options.visibility ? { visibility: options.visibility } : {}),
|
|
830
1032
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
831
1033
|
}));
|
|
832
|
-
output(io, global.json, record, `Updated
|
|
1034
|
+
output(io, global.json, record, `Updated task ${id} to version ${record.current.version}.`);
|
|
833
1035
|
});
|
|
834
1036
|
for (const operation of ['accept', 'reject', 'complete', 'reopen', 'cancel']) {
|
|
835
|
-
|
|
836
|
-
.command(`${operation} <
|
|
1037
|
+
const command_ = taskCommand
|
|
1038
|
+
.command(`${operation} <task-id>`)
|
|
837
1039
|
.requiredOption('--as <actor-id>')
|
|
838
1040
|
.option('--actor-kind <kind>', 'agent or human', 'agent')
|
|
839
1041
|
.option('--note <text>')
|
|
840
1042
|
.option('--reason <text>')
|
|
841
|
-
.option('--idempotency-key <key>')
|
|
842
|
-
|
|
1043
|
+
.option('--idempotency-key <key>');
|
|
1044
|
+
// Rejecting requires saying why; accepting may. Marked required at the
|
|
1045
|
+
// parser so the CLI refuses before touching the store.
|
|
1046
|
+
if (operation === 'reject') {
|
|
1047
|
+
command_.requiredOption('--response <text>', 'why the task is being refused');
|
|
1048
|
+
}
|
|
1049
|
+
else if (operation === 'accept') {
|
|
1050
|
+
command_.option('--response <text>', 'conditions, timing, or partial capability');
|
|
1051
|
+
}
|
|
1052
|
+
command_.action(async (id, options, command) => {
|
|
843
1053
|
const global = globals(command);
|
|
844
1054
|
const record = await withClient(global.home, actor(options.actorKind, options.as), (client) => operation === 'accept'
|
|
845
|
-
? client.
|
|
846
|
-
|
|
1055
|
+
? client.tasks.accept({
|
|
1056
|
+
taskId: id,
|
|
1057
|
+
...(options.response ? { response: options.response } : {}),
|
|
847
1058
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
848
1059
|
})
|
|
849
1060
|
: operation === 'reject'
|
|
850
|
-
? client.
|
|
851
|
-
|
|
852
|
-
|
|
1061
|
+
? client.tasks.reject({
|
|
1062
|
+
taskId: id,
|
|
1063
|
+
response: options.response ?? options.reason ?? '',
|
|
853
1064
|
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
854
1065
|
})
|
|
855
1066
|
: operation === 'complete'
|
|
856
|
-
? client.
|
|
857
|
-
|
|
1067
|
+
? client.tasks.complete({
|
|
1068
|
+
taskId: id,
|
|
858
1069
|
...(options.note ? { note: options.note } : {}),
|
|
859
|
-
...(options.idempotencyKey
|
|
860
|
-
? { idempotencyKey: options.idempotencyKey }
|
|
861
|
-
: {}),
|
|
1070
|
+
...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
|
|
862
1071
|
})
|
|
863
1072
|
: operation === 'cancel'
|
|
864
|
-
? client.
|
|
865
|
-
|
|
1073
|
+
? client.tasks.cancel({
|
|
1074
|
+
taskId: id,
|
|
866
1075
|
...(options.reason ? { reason: options.reason } : {}),
|
|
867
1076
|
...(options.idempotencyKey
|
|
868
1077
|
? { idempotencyKey: options.idempotencyKey }
|
|
869
1078
|
: {}),
|
|
870
1079
|
})
|
|
871
|
-
: client.
|
|
872
|
-
|
|
1080
|
+
: client.tasks.reopen({
|
|
1081
|
+
taskId: id,
|
|
873
1082
|
...(options.idempotencyKey
|
|
874
1083
|
? { idempotencyKey: options.idempotencyKey }
|
|
875
1084
|
: {}),
|
|
876
1085
|
}));
|
|
877
|
-
output(io, global.json, record, `
|
|
1086
|
+
output(io, global.json, record, `Task ${id} is ${record.status}.`);
|
|
878
1087
|
});
|
|
879
1088
|
}
|
|
880
1089
|
kudosCommand
|
|
@@ -1011,14 +1220,34 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
|
|
|
1011
1220
|
program
|
|
1012
1221
|
.command('mcp')
|
|
1013
1222
|
.description('Run the actor-bound MCP server over stdio')
|
|
1014
|
-
.
|
|
1015
|
-
.
|
|
1016
|
-
.option('--actor-
|
|
1223
|
+
.option('--agent-id <id>', 'bound agent, whose identity is read from Synomem')
|
|
1224
|
+
.option('--actor-id <id>', 'bound non-agent actor ID')
|
|
1225
|
+
.option('--actor-kind <kind>', 'human or system')
|
|
1226
|
+
.option('--actor-name <display-name>', 'display name for a non-agent actor')
|
|
1017
1227
|
.action(async (options, command) => {
|
|
1018
1228
|
const global = globals(command);
|
|
1229
|
+
// An agent's name comes from its profile, never from the command line:
|
|
1230
|
+
// the name is written into every event the session appends, and a
|
|
1231
|
+
// harness must not be able to sign another agent's name to work.
|
|
1232
|
+
const bound = options.agentId
|
|
1233
|
+
? await withClient(global.home, defaultActor(env, 'system', 'cli'), async (client) => {
|
|
1234
|
+
const resolution = await client.agents.resolve(options.agentId);
|
|
1235
|
+
if (!resolution.match) {
|
|
1236
|
+
throw new SynomemError('AGENT_NOT_FOUND', resolution.candidates.length
|
|
1237
|
+
? `"${options.agentId}" matches ${resolution.candidates.length} agents: ${resolution.candidates
|
|
1238
|
+
.map((candidate) => candidate.id)
|
|
1239
|
+
.join(', ')}. Name one of them.`
|
|
1240
|
+
: `Unknown agent: ${options.agentId}`);
|
|
1241
|
+
}
|
|
1242
|
+
return actor('agent', resolution.match.id, resolution.match.displayName);
|
|
1243
|
+
})
|
|
1244
|
+
: undefined;
|
|
1245
|
+
if (!bound && !(options.actorId && options.actorKind)) {
|
|
1246
|
+
throw new SynomemError('INVALID_INPUT', 'Specify --agent-id, or --actor-id with --actor-kind for a non-agent actor.');
|
|
1247
|
+
}
|
|
1019
1248
|
await startMcpServer({
|
|
1020
1249
|
...(global.home ? { home: global.home } : {}),
|
|
1021
|
-
actor: actor(options.actorKind, options.actorId, options.actorName),
|
|
1250
|
+
actor: bound ?? actor(options.actorKind, options.actorId, options.actorName),
|
|
1022
1251
|
});
|
|
1023
1252
|
});
|
|
1024
1253
|
return program;
|