external-subagent 0.5.5 → 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/cli/constants.mjs +1 -1
- package/cli/main.mjs +2 -2
- package/cli/rpc.mjs +48 -5
- package/npm/native/darwin-arm64/external-subagent-mcp +0 -0
- package/npm/native/darwin-arm64/external-subagentd +0 -0
- package/npm/native/darwin-arm64/payload.json +4 -4
- package/npm/native/linux-x64/external-subagent-mcp +0 -0
- package/npm/native/linux-x64/external-subagentd +0 -0
- package/npm/native/linux-x64/payload.json +5 -5
- package/package.json +1 -1
- package/schema/external-subagent-public-api.json +13 -7
package/cli/constants.mjs
CHANGED
|
@@ -12,7 +12,7 @@ const IS_DEBUG_VARIANT = process.env.EXTERNAL_SUBAGENT_VARIANT === 'debug';
|
|
|
12
12
|
|
|
13
13
|
export const PRODUCT_NAME = IS_DEBUG_VARIANT ? 'external-subagent-debug' : 'external-subagent';
|
|
14
14
|
export const PRODUCT_ID = IS_DEBUG_VARIANT ? 'external_subagent_debug' : 'external_subagent';
|
|
15
|
-
export const VERSION = '0.
|
|
15
|
+
export const VERSION = '0.6.0';
|
|
16
16
|
export const LAUNCH_AGENT_LABEL = IS_DEBUG_VARIANT ? 'com.external-subagent-debug.daemon' : 'com.external-subagent.daemon';
|
|
17
17
|
export const DAEMON_BIN_NAME = IS_DEBUG_VARIANT ? 'external-subagent-debugd' : 'external-subagentd';
|
|
18
18
|
export const MCP_BIN_NAME = IS_DEBUG_VARIANT ? 'external-subagent-debug-mcp' : 'external-subagent-mcp';
|
package/cli/main.mjs
CHANGED
|
@@ -24,7 +24,7 @@ import { parseProfileArgs, parseSpawnArgs, prepareSpawnInput, profileCommand } f
|
|
|
24
24
|
const SUPPORTED_PLATFORMS = new Set(['darwin', 'linux', 'win32']);
|
|
25
25
|
|
|
26
26
|
const HELP = `external-subagent ${VERSION}\n\nUsage: external-subagent <command> [options]\n\nCommands:\n help, version Show basic product information\n init [--dry-run] [--resume] [--skip-service-start]\n Install the standalone daemon service only; bind a\n host afterwards with install-plugin or install-mcp\n install-plugin [codex|zcode|dsh] [--dry-run|--uninstall] [--codex-home <path>]\n [--profile <name> [--dsh-home <path>]]\n Install or remove the managed host plugin (MCP + skill);\n the host defaults to codex, --codex-home is codex-only,\n --profile/--dsh-home are dsh-only (the dsh host appends\n one managed mcp-client entry to the profile's\n cordis.patch.yml under $DSH_HOME/profiles)\n install-mcp [--dry-run|--uninstall] [--codex-home <path>]\n Install or remove the direct Codex MCP TOML binding\n status, diagnose Inspect local service and runtime state\n start, stop Bootstrap or boot out the daemon service\n profile list | profile show <name>\n Inspect global spawn profiles (profiles/*.json);\n invalid files are reported as path-based warnings,\n and warned or duplicate files are excluded from the\n available name set\n backup --output <dir> Back up retained product data\n restore --input <dir> Verify and restore product data\n uninstall Release Codex/ZCode/DSH bindings; remove service registration; retain data\n purge --yes Explicitly delete new product data\n`;
|
|
27
|
-
const DAEMON_HELP = ` config get [key] | config set <key> <value>\n subagents list | subagents status [subagent] | subagents probe/models [subagent]\n create/spawn [--profile <name>], wait, list, send, respond, cancel, result, close, observe\n Daemon calls accept --json '<object>' or JSON stdin\n list JSON requires repository (workspace is an alias)\n observe JSON requires only agent_id\n`;
|
|
27
|
+
const DAEMON_HELP = ` config get [key] | config set <key> <value>\n subagents list | subagents status [subagent] | subagents probe/models [subagent]\n create/spawn [--profile <name>], wait (one agent_id or a JSON array of up to 16), list, send, respond, cancel, result, close, observe\n Daemon calls accept --json '<object>' or JSON stdin\n list JSON requires repository (workspace is an alias)\n observe JSON requires only agent_id\n`;
|
|
28
28
|
|
|
29
29
|
function structuredInput(args, parser) {
|
|
30
30
|
return args.length > 0 && !args[0].startsWith('--') ? parser(args) : parseDaemonInput(args);
|
|
@@ -308,7 +308,7 @@ async function diagnose(paths, args) {
|
|
|
308
308
|
query_request_id: snapshot.__request_id ?? null,
|
|
309
309
|
identifiers_complete: Boolean(snapshot.task?.turn_id || (Array.isArray(snapshot.pending_requests) && snapshot.pending_requests.some((request) => request.request_id))),
|
|
310
310
|
pending_request_count: Array.isArray(snapshot.pending_requests) ? snapshot.pending_requests.length : null,
|
|
311
|
-
result_available: snapshot.result_available ??
|
|
311
|
+
result_available: snapshot.result_available ?? (snapshot.result != null),
|
|
312
312
|
observed_at_ms: Date.now(),
|
|
313
313
|
};
|
|
314
314
|
} catch (error) {
|
package/cli/rpc.mjs
CHANGED
|
@@ -119,6 +119,26 @@ function methodFor(command, input) {
|
|
|
119
119
|
};
|
|
120
120
|
case 'wait': {
|
|
121
121
|
rejectUnknownFields('wait', input, new Set(['agent_id', 'wait_time', 'message_id']));
|
|
122
|
+
// agent_id accepts a scalar (historical shape) or a list of ids; the
|
|
123
|
+
// daemon's untagged WaitTarget mirrors the caller's form.
|
|
124
|
+
if (Array.isArray(input.agent_id)) {
|
|
125
|
+
if (input.agent_id.length === 0) {
|
|
126
|
+
throw new CliError('INVALID_ARGUMENT', 'wait agent_id list must contain at least one id', 2);
|
|
127
|
+
}
|
|
128
|
+
if (input.agent_id.length > 16) {
|
|
129
|
+
throw new CliError('INVALID_ARGUMENT', 'wait agent_id list supports at most 16 ids', 2);
|
|
130
|
+
}
|
|
131
|
+
if (input.message_id !== undefined && input.message_id !== null) {
|
|
132
|
+
throw new CliError('INVALID_ARGUMENT', 'wait message_id is only supported with a single agent_id', 2);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
method: 'task_wait',
|
|
136
|
+
params: {
|
|
137
|
+
agent_id: input.agent_id.map((id) => daemonTaskId(id)),
|
|
138
|
+
wait_time: input.wait_time ?? 290,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
122
142
|
return {
|
|
123
143
|
method: 'task_wait',
|
|
124
144
|
params: {
|
|
@@ -189,12 +209,12 @@ function publicTask(task) {
|
|
|
189
209
|
}
|
|
190
210
|
|
|
191
211
|
// The wait response carries only the task lifecycle header: admission
|
|
192
|
-
// provenance never
|
|
212
|
+
// provenance and the session id never change after spawn, so wait does not
|
|
213
|
+
// repeat them.
|
|
193
214
|
function publicTaskHeader(task) {
|
|
194
215
|
return {
|
|
195
216
|
agent_id: publicTaskId(task.agent_id),
|
|
196
217
|
status: task.status,
|
|
197
|
-
session_id: task.session_id ?? null,
|
|
198
218
|
};
|
|
199
219
|
}
|
|
200
220
|
|
|
@@ -207,11 +227,17 @@ function publicResult(result) {
|
|
|
207
227
|
offset: result.offset,
|
|
208
228
|
total_bytes: result.total_bytes,
|
|
209
229
|
next_offset: result.next_offset ?? null,
|
|
210
|
-
complete: result.complete,
|
|
211
230
|
reason_code: result.reason_code ?? null,
|
|
231
|
+
failure_message: result.failure_message ?? null,
|
|
212
232
|
};
|
|
213
233
|
}
|
|
214
234
|
|
|
235
|
+
// One per-agent wait body: identical in scalar and list mode.
|
|
236
|
+
function publicWaitBody(body) {
|
|
237
|
+
const { result: taskResult, task, activity, kind: _kind, ...rest } = body;
|
|
238
|
+
return { ...rest, task: publicTaskHeader(task), activity, result: publicResult(taskResult) };
|
|
239
|
+
}
|
|
240
|
+
|
|
215
241
|
export function projectDaemonResult(command, result) {
|
|
216
242
|
switch (command) {
|
|
217
243
|
case 'status': return result.status;
|
|
@@ -221,8 +247,25 @@ export function projectDaemonResult(command, result) {
|
|
|
221
247
|
case 'create': case 'spawn':
|
|
222
248
|
return { agent_id: publicTaskId(result.task.agent_id), status: result.task.status, session_id: result.task.session_id ?? null };
|
|
223
249
|
case 'wait': {
|
|
224
|
-
|
|
225
|
-
|
|
250
|
+
// List-mode response: kind=task_wait_many with a FLAT keyed object —
|
|
251
|
+
// one key per distinct id carrying the wait body, plus an `error` key
|
|
252
|
+
// collecting duplicated/unknown ids. Rekey by public numeric id.
|
|
253
|
+
if (result.kind === 'task_wait_many') {
|
|
254
|
+
const projected = {};
|
|
255
|
+
const { error, kind: _kind, ...bodies } = result;
|
|
256
|
+
for (const [id, body] of Object.entries(bodies)) {
|
|
257
|
+
projected[publicTaskId(id)] = publicWaitBody(body);
|
|
258
|
+
}
|
|
259
|
+
if (error && typeof error === 'object') {
|
|
260
|
+
const errorProjected = {};
|
|
261
|
+
for (const [id, marker] of Object.entries(error)) {
|
|
262
|
+
errorProjected[publicTaskId(id)] = marker;
|
|
263
|
+
}
|
|
264
|
+
projected.error = errorProjected;
|
|
265
|
+
}
|
|
266
|
+
return projected;
|
|
267
|
+
}
|
|
268
|
+
return publicWaitBody(result);
|
|
226
269
|
}
|
|
227
270
|
case 'list': return { tasks: result.tasks.map(publicTask), next_cursor: result.next_cursor ?? null };
|
|
228
271
|
case 'send': return { message_id: result.message_id, disposition: result.disposition };
|
|
Binary file
|
|
Binary file
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"product": "external-subagent",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"platform": "darwin-arm64",
|
|
6
6
|
"files": [
|
|
7
7
|
{
|
|
8
8
|
"name": "external-subagentd",
|
|
9
|
-
"bytes":
|
|
9
|
+
"bytes": 10454384,
|
|
10
10
|
"mode": "755",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "3c3363a340d30718a7cb50801467eab8be7e70856b3c6f8925f6c7e87243b3ce"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"name": "external-subagent-mcp",
|
|
15
15
|
"bytes": 935776,
|
|
16
16
|
"mode": "755",
|
|
17
|
-
"sha256": "
|
|
17
|
+
"sha256": "037d6041c379290284dadba0631b4f1e706a49e3315ef476952168f0ed3477ef"
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
20
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"product": "external-subagent",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"platform": "linux-x64",
|
|
6
6
|
"files": [
|
|
7
7
|
{
|
|
8
8
|
"name": "external-subagentd",
|
|
9
|
-
"bytes":
|
|
9
|
+
"bytes": 12008336,
|
|
10
10
|
"mode": "755",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "d2f9599c6982ff3900cdc9516ed9bc7ab3b8d7c23012b1ac44f63a79a4f5878e"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"name": "external-subagent-mcp",
|
|
15
|
-
"bytes":
|
|
15
|
+
"bytes": 1025520,
|
|
16
16
|
"mode": "755",
|
|
17
|
-
"sha256": "
|
|
17
|
+
"sha256": "5a84607f0e2abe6afc679b7ea39207d50c06084fbcc4f54444f8f55168bc2492"
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
20
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "external-subagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Manage bounded ZCode, DeepSeek Harness (DSH), Codex, and Google Antigravity (agy) subagent tasks through a local MCP service for any local host (Codex, ZCode, or a custom MCP client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -104,12 +104,18 @@
|
|
|
104
104
|
},
|
|
105
105
|
"wait": {
|
|
106
106
|
"type": "object",
|
|
107
|
-
"description": "Wait up to 290 seconds by default. Returns early only when an actionable pending request exists (permission requests need allow/deny; user-input requests carry the full embedded question and need answer with non-empty content) or the terminal result is available; ordinary progress, message receipts, already-responded and unsupported requests never wake it, and its timeout still returns timed_out=true. When the
|
|
107
|
+
"description": "agent_id accepts one agent id (number) or a list of 1..=16 ids. A single id returns the single wait object described below. A list blocks until every listed agent is terminal OR any one of them raises an actionable pending request, then returns a FLAT object: one key per distinct agent id carrying that agent's ordinary wait object (a repeated id is awaited once like any other), plus one \"error\" key mapping problematic ids to markers - duplicated for ids repeated in the input and unknown_agent for ids that do not exist; the remaining ids are still awaited normally. Recommended parallel pattern: spawn every subagent first, then call wait once with all ids. In list mode message_id is not supported (pass it only with a scalar id), every element carries message_receipt=null, and each element's timed_out flag follows the same shared deadline. Wait up to 290 seconds by default. Returns early only when an actionable pending request exists (permission requests need allow/deny; user-input requests carry the full embedded question and need answer with non-empty content) or the terminal result is available; ordinary progress, message receipts, already-responded and unsupported requests never wake it, and its timeout still returns timed_out=true. When the embedded result page carries next_offset=null it is the whole final result and no further result call is needed; otherwise continue reading with external_subagent_result from next_offset. A failed terminal result also carries failure_message: usually the daemon's bounded single-line JSON failure detail, but an early fail_claim row may be plain text, so treat it as an opaque bounded string and attempt JSON parsing defensively (null when the task has no persisted detail or the outcome is not a failure). Read failure_message before falling back to daemon logs or sqlite. The pending_requests projection stays capped at 100 records while the wake decision scans the full pending set, and the request that woke the wait is always part of the returned projection so its request_id can be answered directly. activity carries the text tail incrementally: each wait returns only the text appended since the previous wait (an empty tail means nothing new), every byte is delivered by wait at most once, and latest_text_truncated flags the case where undelivered bytes rolled out of the bounded window so this response degraded to the whole window (on the terminal response, bytes repeating the embedded result page verbatim are stripped so they are not shipped twice), tool calls started in the last 60 seconds, and telemetry status; activity also exposes last_activity_age_ms (null before any activity) and model_request_active; additionally in_flight_tool_count counts tracked in-flight tool calls (scheduled or started without a matching result, error or permission resolution; 0 when there is no snapshot or the adapter has no per-call lifecycle tracking, such as codex) and oldest_in_flight_tool_age_ms is the age since the oldest tracked in-flight call first appeared (null when none is tracked); use external_subagent_observe only when these liveness signals suggest a meaningless loop or a stuck command, to inspect recent tool-call detail.Wait up to 290 seconds by default. Returns early only when an actionable pending request exists (permission requests need allow/deny; user-input requests carry the full embedded question and need answer with non-empty content) or the terminal result is available; ordinary progress, message receipts, already-responded and unsupported requests never wake it, and its timeout still returns timed_out=true. When the embedded result page carries next_offset=null it is the whole final result and no further result call is needed; otherwise continue reading with external_subagent_result from next_offset. A failed terminal result also carries failure_message: usually the daemon's bounded single-line JSON failure detail, but an early fail_claim row may be plain text, so treat it as an opaque bounded string and attempt JSON parsing defensively (null when the task has no persisted detail or the outcome is not a failure). Read failure_message before falling back to daemon logs or sqlite. The pending_requests projection stays capped at 100 records while the wake decision scans the full pending set, and the request that woke the wait is always part of the returned projection so its request_id can be answered directly. activity carries the text tail incrementally: each wait returns only the text appended since the previous wait (an empty tail means nothing new), every byte is delivered by wait at most once, and latest_text_truncated flags the case where undelivered bytes rolled out of the bounded window so this response degraded to the whole window (on the terminal response, bytes repeating the embedded result page verbatim are stripped so they are not shipped twice), tool calls started in the last 60 seconds, and telemetry status; activity also exposes last_activity_age_ms (null before any activity) and model_request_active; additionally in_flight_tool_count counts tracked in-flight tool calls (scheduled or started without a matching result, error or permission resolution; 0 when there is no snapshot or the adapter has no per-call lifecycle tracking, such as codex) and oldest_in_flight_tool_age_ms is the age since the oldest tracked in-flight call first appeared (null when none is tracked); use external_subagent_observe only when these liveness signals suggest a meaningless loop or a stuck command, to inspect recent tool-call detail.",
|
|
108
108
|
"required": ["agent_id"],
|
|
109
109
|
"properties": {
|
|
110
|
-
"agent_id": {
|
|
110
|
+
"agent_id": {
|
|
111
|
+
"anyOf": [
|
|
112
|
+
{ "type": "integer", "minimum": 10000000, "maximum": 99999999 },
|
|
113
|
+
{ "type": "array", "minItems": 1, "maxItems": 16, "items": { "type": "integer", "minimum": 10000000, "maximum": 99999999 } }
|
|
114
|
+
],
|
|
115
|
+
"description": "One agent id (scalar response) or a list of 1..=16 ids awaited together (response is an object keyed by agent id, each value the ordinary wait output or {\"error\":\"duplicated\"}/{\"error\":\"unknown_agent\"})."
|
|
116
|
+
},
|
|
111
117
|
"wait_time": { "type": "integer", "minimum": 0, "maximum": 299, "default": 290 }
|
|
112
|
-
,"message_id": { "type": ["string", "null"] }
|
|
118
|
+
,"message_id": { "type": ["string", "null"], "description": "Only supported with a scalar agent_id." }
|
|
113
119
|
},
|
|
114
120
|
"additionalProperties": false
|
|
115
121
|
},
|
|
@@ -135,7 +141,7 @@
|
|
|
135
141
|
},
|
|
136
142
|
"result_projection": {
|
|
137
143
|
"type": "object",
|
|
138
|
-
"required": ["outcome", "final_text", "partial", "offset", "total_bytes", "next_offset", "
|
|
144
|
+
"required": ["outcome", "final_text", "partial", "offset", "total_bytes", "next_offset", "reason_code", "failure_message"],
|
|
139
145
|
"properties": {
|
|
140
146
|
"outcome": { "enum": ["COMPLETED", "FAILED", "CANCELLED", "TIMED_OUT", "RUNTIME_LOST", "RESULT_INVALID"] },
|
|
141
147
|
"final_text": { "type": "string" },
|
|
@@ -143,8 +149,8 @@
|
|
|
143
149
|
"offset": { "type": "integer", "minimum": 0 },
|
|
144
150
|
"total_bytes": { "type": "integer", "minimum": 0 },
|
|
145
151
|
"next_offset": { "type": ["integer", "null"], "minimum": 0 },
|
|
146
|
-
"
|
|
147
|
-
"
|
|
152
|
+
"reason_code": { "type": ["string", "null"] },
|
|
153
|
+
"failure_message": { "type": ["string", "null"] }
|
|
148
154
|
},
|
|
149
155
|
"additionalProperties": false
|
|
150
156
|
},
|
|
@@ -185,7 +191,7 @@
|
|
|
185
191
|
"properties": {
|
|
186
192
|
"external_subagent_status": { "input": [], "output": ["mcp_version", "components", "capabilities", "subagents"] },
|
|
187
193
|
"external_subagent_spawn": { "input": ["profile", "subagent", "repository", "permission_mode", "prompt", "model", "effort", "write_manifest"], "output": ["agent_id", "status", "session_id", "instruction"], "idempotent": false },
|
|
188
|
-
"external_subagent_wait": { "input": ["agent_id", "wait_time", "message_id"], "output": ["task", "pending_requests", "
|
|
194
|
+
"external_subagent_wait": { "input": ["agent_id", "wait_time", "message_id"], "output": ["task", "pending_requests", "activity", "result", "timed_out", "message_receipt"], "output_many": ["<agent_id> keys", "error"], "agent_id_forms": "scalar or array of 1..=16", "parallel_note": "spawn all subagents first, then wait once with the full id list" },
|
|
189
195
|
"external_subagent_observe": { "input": ["agent_id"], "output": ["tools", "reasoning", "coverage"], "idempotent": true },
|
|
190
196
|
"external_subagent_list": { "input": ["subagent", "repository", "phase", "outcome", "cursor", "limit"], "output": ["tasks", "next_cursor"] },
|
|
191
197
|
"external_subagent_send": { "input": ["agent_id", "message_id", "mode", "content"], "required": ["agent_id", "mode", "content"], "mode": ["queue", "steer"], "output": ["message_id", "disposition"] },
|