@agentproto/runtime 2.2.0 → 2.3.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/README.md +1 -0
- package/dist/index.d.ts +31 -2
- package/dist/index.mjs +151 -21
- package/dist/index.mjs.map +1 -1
- package/dist/resume-strategies.mjs.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ A per-boot bearer token is generated automatically and written into `<workspace>
|
|
|
42
42
|
| MCP | `POST /mcp` (Streamable HTTP) | Stateless mode; per-request transport |
|
|
43
43
|
| Conversations | `GET /conversations` / `GET /conversations/<id>` | Markdown bodies |
|
|
44
44
|
| Adapter discovery | `GET /adapters` / `POST /adapters/:slug/install` | When `listAgentAdapters` / `installAgentAdapter` is wired |
|
|
45
|
+
| App scope mounts | `POST /apps/:appId/apply` / `DELETE /apps/:appId/apply` / `GET /scopes/:scopeId/apps` | Mirrors MCP `app_apply` / `app_unapply` / `app_list_applied`; needs `appRegistry` |
|
|
45
46
|
| Sessions list | `GET /sessions` / `GET /sessions/:id` / `GET /sessions/summaries` | id-or-name in `:id`; summaries are lightweight + paginated |
|
|
46
47
|
| Agent spawn | `POST /sessions/agent` | Long-lived ACP agent (needs `resolveAgentAdapter`) |
|
|
47
48
|
| Interrupt turn | `POST /sessions/:id/interrupt` | Cancel the in-flight turn; session stays alive |
|
package/dist/index.d.ts
CHANGED
|
@@ -3753,8 +3753,14 @@ interface AuthOptions {
|
|
|
3753
3753
|
*/
|
|
3754
3754
|
|
|
3755
3755
|
interface SessionObserver {
|
|
3756
|
-
/** Record the outgoing message that opens a new turn.
|
|
3757
|
-
|
|
3756
|
+
/** Record the outgoing message that opens a new turn. `opts.source`, when
|
|
3757
|
+
* set, is the prompt's provenance — `agent:<sessionId>` for a prompt
|
|
3758
|
+
* injected by another session (`agent_prompt` from a supervisor), absent
|
|
3759
|
+
* for a human operator — so a transcript view can attribute the turn to
|
|
3760
|
+
* its real author instead of "you". */
|
|
3761
|
+
recordPrompt(sessionId: string, message: unknown, opts?: {
|
|
3762
|
+
source?: string;
|
|
3763
|
+
}): void;
|
|
3758
3764
|
/** Record one structured stream event (text-delta, tool-call, usage_update, …). */
|
|
3759
3765
|
recordEvent(sessionId: string, evt: AgentStreamEvent): void;
|
|
3760
3766
|
/** Record the durable turn-boundary / exit usage snapshot. */
|
|
@@ -3995,6 +4001,16 @@ interface AgentStreamEvent {
|
|
|
3995
4001
|
* but no `cost`). */
|
|
3996
4002
|
tokensIn?: number;
|
|
3997
4003
|
tokensOut?: number;
|
|
4004
|
+
/** "permission-resolved" outcome for the "agent-prompt" it answers (same
|
|
4005
|
+
* `toolCallId`) — mirrors `session:permission-resolved`'s `decision` so
|
|
4006
|
+
* the durable transcript can tell an answered ask from a still-pending
|
|
4007
|
+
* one. Not an ACP `StreamEvent` kind (synthesized by the registry, same
|
|
4008
|
+
* as "notice"/"turn-end" synthetics) — see registerPendingPermission's
|
|
4009
|
+
* docblock for the "agent-prompt" ask this resolves. */
|
|
4010
|
+
decision?: "approve" | "deny" | "cancelled";
|
|
4011
|
+
/** "permission-resolved" chosen option id, when the driver's offered
|
|
4012
|
+
* options included one (e.g. ACP's `allow_always`). */
|
|
4013
|
+
optionId?: string;
|
|
3998
4014
|
}
|
|
3999
4015
|
/**
|
|
4000
4016
|
* Env vars the registry injects into every process it spawns on a session's
|
|
@@ -4019,6 +4035,14 @@ interface AgentStreamEvent {
|
|
|
4019
4035
|
* assign-last rule is what makes that true by construction rather than by
|
|
4020
4036
|
* accident, and is what guarantees a child spawned from inside a session
|
|
4021
4037
|
* gets its OWN id rather than inheriting its parent's.
|
|
4038
|
+
*
|
|
4039
|
+
* `PARENT_SESSION_ID_ENV` is the third var, injected by `session-spawn.ts`
|
|
4040
|
+
* only (agent spawns, and only when the spawn resolved a parent): the
|
|
4041
|
+
* recorded `parentSessionId` lineage, so a child agent can know WHO spawned
|
|
4042
|
+
* it without a registry round-trip — the discovery half of the child→parent
|
|
4043
|
+
* report-back channel (`message_parent` is the delivery half). Same
|
|
4044
|
+
* assign-last/no-forgery rule as the other two: it mirrors the descriptor's
|
|
4045
|
+
* own `parentSessionId` field, never anything caller- or env-inherited.
|
|
4022
4046
|
*/
|
|
4023
4047
|
declare const SESSION_ID_ENV = "AGENTPROTO_SESSION_ID";
|
|
4024
4048
|
declare const WORKSPACE_SLUG_ENV = "AGENTPROTO_WORKSPACE_SLUG";
|
|
@@ -4943,6 +4967,7 @@ interface SessionsRegistry {
|
|
|
4943
4967
|
* the previous mid-turn rejection byte-for-byte. */
|
|
4944
4968
|
sendPrompt(id: string, message: unknown, opts?: {
|
|
4945
4969
|
interrupt?: boolean;
|
|
4970
|
+
source?: string;
|
|
4946
4971
|
}): Promise<void>;
|
|
4947
4972
|
/** Fire-and-forget variant of `sendPrompt` for the TURN ITSELF only.
|
|
4948
4973
|
* Admission (resume attempt + the missing/wrong-kind/dead/busy
|
|
@@ -4962,8 +4987,12 @@ interface SessionsRegistry {
|
|
|
4962
4987
|
* prompt is admitted + fired on the SAME live session. Ignored
|
|
4963
4988
|
* (identical to the default) when the session is idle. Omitted or
|
|
4964
4989
|
* `false` reproduces today's mid-turn rejection byte-for-byte. */
|
|
4990
|
+
/** `opts.source` on either prompt verb is the turn's provenance
|
|
4991
|
+
* (`agent:<sessionId>` when another session injected it — see
|
|
4992
|
+
* `SessionObserver.recordPrompt`); recording-only, never behavioral. */
|
|
4965
4993
|
enqueuePrompt(id: string, message: unknown, opts?: {
|
|
4966
4994
|
interrupt?: boolean;
|
|
4995
|
+
source?: string;
|
|
4967
4996
|
}): Promise<void>;
|
|
4968
4997
|
/** Eagerly resume ONE dead-but-resumable agent-cli session IN PLACE,
|
|
4969
4998
|
* WITHOUT a prompt — the boot-time counterpart to the lazy resume that
|
package/dist/index.mjs
CHANGED
|
@@ -310,11 +310,16 @@ function createTranscriptWriter(opts) {
|
|
|
310
310
|
}, DEBOUNCE_MS);
|
|
311
311
|
};
|
|
312
312
|
return {
|
|
313
|
-
recordPrompt(sessionId, message) {
|
|
313
|
+
recordPrompt(sessionId, message, opts2) {
|
|
314
314
|
const state = getState(sessionId);
|
|
315
315
|
flushBuffers(sessionId, state);
|
|
316
316
|
const text9 = typeof message === "string" ? message : JSON.stringify(message);
|
|
317
|
-
writeRecord(sessionId, state, {
|
|
317
|
+
writeRecord(sessionId, state, {
|
|
318
|
+
kind: "user-prompt",
|
|
319
|
+
sessionId,
|
|
320
|
+
text: text9,
|
|
321
|
+
...opts2?.source ? { source: opts2.source } : {}
|
|
322
|
+
});
|
|
318
323
|
},
|
|
319
324
|
recordEvent(sessionId, evt) {
|
|
320
325
|
const state = getState(sessionId);
|
|
@@ -404,6 +409,22 @@ function createTranscriptWriter(opts) {
|
|
|
404
409
|
options: evt.options
|
|
405
410
|
});
|
|
406
411
|
break;
|
|
412
|
+
// Durable counterpart to "agent-prompt" — recorded when
|
|
413
|
+
// `respondPermission` (or a dying session's cancel-in-flight sweep)
|
|
414
|
+
// resolves the ask, keyed by the same `toolCallId` so a reader can
|
|
415
|
+
// tell an answered request from a still-pending one without relying
|
|
416
|
+
// on the in-memory pending-permissions map (see sessions.ts's
|
|
417
|
+
// `resolvePendingPermission` / `cancelPendingPermissionsForSession`).
|
|
418
|
+
case "permission-resolved":
|
|
419
|
+
flushBuffers(sessionId, state);
|
|
420
|
+
writeRecord(sessionId, state, {
|
|
421
|
+
kind: "permission-resolved",
|
|
422
|
+
sessionId,
|
|
423
|
+
toolCallId: evt.toolCallId,
|
|
424
|
+
decision: evt.decision,
|
|
425
|
+
...evt.optionId ? { optionId: evt.optionId } : {}
|
|
426
|
+
});
|
|
427
|
+
break;
|
|
407
428
|
case "turn-end":
|
|
408
429
|
flushBuffers(sessionId, state);
|
|
409
430
|
writeRecord(sessionId, state, { kind: "turn-end", sessionId, reason: evt.reason });
|
|
@@ -5116,6 +5137,14 @@ async function spawnAgentSession(deps2, input) {
|
|
|
5116
5137
|
};
|
|
5117
5138
|
});
|
|
5118
5139
|
}
|
|
5140
|
+
if (mcpServers === void 0 && parentSessionId && buildOrchestratorMcp && input.orchestrator !== false && input.sandbox === void 0) {
|
|
5141
|
+
const injection = buildOrchestratorMcp({
|
|
5142
|
+
tools: ["message_parent"],
|
|
5143
|
+
role: role.name
|
|
5144
|
+
});
|
|
5145
|
+
mcpServers = [injection.entry];
|
|
5146
|
+
bindOrchestratorLifecycle = injection.bindLifecycle;
|
|
5147
|
+
}
|
|
5119
5148
|
const spawnDefaults = resolveSpawnDefaults(configDefaults, input.adapter, {
|
|
5120
5149
|
skills: input.skills,
|
|
5121
5150
|
options: input.options,
|
|
@@ -5277,9 +5306,8 @@ async function spawnAgentSession(deps2, input) {
|
|
|
5277
5306
|
details: { adapter: input.adapter, gateway }
|
|
5278
5307
|
};
|
|
5279
5308
|
}
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
${input.prompt}` : input.prompt;
|
|
5309
|
+
const parentContextLine = parentSessionId ? `You were spawned by session ${parentSessionId} (also available in the ${PARENT_SESSION_ID_ENV} env var). When you finish \u2014 or hit a blocker you cannot resolve \u2014 report back to it via the message_parent tool if one is available (no session id needed; the daemon resolves your parent).` : void 0;
|
|
5310
|
+
let effectivePrompt = input.prompt ? [composeRoleContext(role, input.promptAppend, roleRegistry), parentContextLine, input.prompt].filter((p) => !!p).join("\n\n") : input.prompt;
|
|
5283
5311
|
const explicitTitle = input.title?.trim() ? input.title.trim() : void 0;
|
|
5284
5312
|
const spawnLabel = input.label?.trim() ? input.label.trim() : void 0;
|
|
5285
5313
|
const initialTitle = explicitTitle ?? spawnLabel ?? (input.prompt ? deriveSessionTitle(input.prompt) : void 0);
|
|
@@ -5536,7 +5564,12 @@ ${asyncPrompt}`;
|
|
|
5536
5564
|
// collide with, so this is the entire env for this spawn.
|
|
5537
5565
|
env: {
|
|
5538
5566
|
[SESSION_ID_ENV]: mintedSessionId,
|
|
5539
|
-
[WORKSPACE_SLUG_ENV]: resolvedSlug
|
|
5567
|
+
[WORKSPACE_SLUG_ENV]: resolvedSlug,
|
|
5568
|
+
// Lineage (PARENT_SESSION_ID_ENV's doc, sessions.ts) — mirrors
|
|
5569
|
+
// the descriptor's `parentSessionId` so the child can discover
|
|
5570
|
+
// who spawned it without a registry round-trip. Absent on a
|
|
5571
|
+
// parentless root spawn.
|
|
5572
|
+
...parentSessionId ? { [PARENT_SESSION_ID_ENV]: parentSessionId } : {}
|
|
5540
5573
|
},
|
|
5541
5574
|
onActivity: () => {
|
|
5542
5575
|
if (liveSessionId) registry.pulseActivity(liveSessionId);
|
|
@@ -6721,8 +6754,8 @@ function composeSessionObservers(observers) {
|
|
|
6721
6754
|
);
|
|
6722
6755
|
};
|
|
6723
6756
|
return {
|
|
6724
|
-
recordPrompt(sessionId, message) {
|
|
6725
|
-
forEachSafe((o) => o.recordPrompt(sessionId, message));
|
|
6757
|
+
recordPrompt(sessionId, message, opts) {
|
|
6758
|
+
forEachSafe((o) => o.recordPrompt(sessionId, message, opts));
|
|
6726
6759
|
},
|
|
6727
6760
|
recordEvent(sessionId, evt) {
|
|
6728
6761
|
forEachSafe((o) => o.recordEvent(sessionId, evt));
|
|
@@ -6740,10 +6773,10 @@ function composeSessionObservers(observers) {
|
|
|
6740
6773
|
}
|
|
6741
6774
|
function filterSessionObserver(inner, shouldObserve) {
|
|
6742
6775
|
return {
|
|
6743
|
-
recordPrompt(sessionId, message) {
|
|
6776
|
+
recordPrompt(sessionId, message, opts) {
|
|
6744
6777
|
if (!shouldObserve(sessionId)) return;
|
|
6745
6778
|
try {
|
|
6746
|
-
inner.recordPrompt(sessionId, message);
|
|
6779
|
+
inner.recordPrompt(sessionId, message, opts);
|
|
6747
6780
|
} catch {
|
|
6748
6781
|
}
|
|
6749
6782
|
},
|
|
@@ -7446,6 +7479,7 @@ function mintSessionId() {
|
|
|
7446
7479
|
}
|
|
7447
7480
|
var SESSION_ID_ENV = "AGENTPROTO_SESSION_ID";
|
|
7448
7481
|
var WORKSPACE_SLUG_ENV = "AGENTPROTO_WORKSPACE_SLUG";
|
|
7482
|
+
var PARENT_SESSION_ID_ENV = "AGENTPROTO_PARENT_SESSION_ID";
|
|
7449
7483
|
var SessionNotAliveError = class extends Error {
|
|
7450
7484
|
sessionId;
|
|
7451
7485
|
status;
|
|
@@ -7748,6 +7782,11 @@ function createSessionsRegistry(opts) {
|
|
|
7748
7782
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
7749
7783
|
});
|
|
7750
7784
|
}
|
|
7785
|
+
transcriptWriter.recordEvent(rt.desc.id, {
|
|
7786
|
+
kind: "permission-resolved",
|
|
7787
|
+
toolCallId: id,
|
|
7788
|
+
decision: "cancelled"
|
|
7789
|
+
});
|
|
7751
7790
|
}
|
|
7752
7791
|
delete rt.desc.awaitingPermission;
|
|
7753
7792
|
};
|
|
@@ -7803,6 +7842,12 @@ function createSessionsRegistry(opts) {
|
|
|
7803
7842
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
7804
7843
|
});
|
|
7805
7844
|
}
|
|
7845
|
+
transcriptWriter.recordEvent(pending.sessionId, {
|
|
7846
|
+
kind: "permission-resolved",
|
|
7847
|
+
toolCallId: id,
|
|
7848
|
+
decision: input.decision,
|
|
7849
|
+
...chosenOptionId ? { optionId: chosenOptionId } : {}
|
|
7850
|
+
});
|
|
7806
7851
|
schedulePersist();
|
|
7807
7852
|
if (!okResolved) {
|
|
7808
7853
|
return { ok: true, permission: pending, decision: input.decision, ...chosenOptionId ? { optionId: chosenOptionId } : {} };
|
|
@@ -8435,7 +8480,7 @@ function createSessionsRegistry(opts) {
|
|
|
8435
8480
|
return;
|
|
8436
8481
|
}
|
|
8437
8482
|
}
|
|
8438
|
-
const runAgentTurn = async (rt, message) => {
|
|
8483
|
+
const runAgentTurn = async (rt, message, turnOpts) => {
|
|
8439
8484
|
if (!rt.agentSession) {
|
|
8440
8485
|
throw new Error("runAgentTurn: session has no agentSession");
|
|
8441
8486
|
}
|
|
@@ -8471,7 +8516,11 @@ ${message}`;
|
|
|
8471
8516
|
`\x1B[2m\u2500\u2500 \u25B6 ${typeof message === "string" ? message : JSON.stringify(message)} \u2500\u2500\x1B[0m`,
|
|
8472
8517
|
"stdout"
|
|
8473
8518
|
);
|
|
8474
|
-
transcriptWriter.recordPrompt(
|
|
8519
|
+
transcriptWriter.recordPrompt(
|
|
8520
|
+
rt.desc.id,
|
|
8521
|
+
message,
|
|
8522
|
+
turnOpts?.promptSource ? { source: turnOpts.promptSource } : void 0
|
|
8523
|
+
);
|
|
8475
8524
|
const wrapped = typeof message === "string" ? { type: "text", text: message } : message;
|
|
8476
8525
|
for await (const evt of rt.agentSession.send(wrapped)) {
|
|
8477
8526
|
transcriptWriter.recordEvent(rt.desc.id, evt);
|
|
@@ -8882,7 +8931,11 @@ ${message}`;
|
|
|
8882
8931
|
schedulePersist();
|
|
8883
8932
|
recordConversationLink(rt);
|
|
8884
8933
|
if (input.initialPrompt) {
|
|
8885
|
-
void runAgentTurn(
|
|
8934
|
+
void runAgentTurn(
|
|
8935
|
+
rt,
|
|
8936
|
+
input.initialPrompt,
|
|
8937
|
+
desc.parentSessionId ? { promptSource: `agent:${desc.parentSessionId}` } : void 0
|
|
8938
|
+
).catch((err) => {
|
|
8886
8939
|
appendLine(
|
|
8887
8940
|
rt,
|
|
8888
8941
|
`[turn error] ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -9010,7 +9063,11 @@ ${message}`;
|
|
|
9010
9063
|
schedulePersist();
|
|
9011
9064
|
recordConversationLink(rt);
|
|
9012
9065
|
if (outcome.initialPrompt) {
|
|
9013
|
-
void runAgentTurn(
|
|
9066
|
+
void runAgentTurn(
|
|
9067
|
+
rt,
|
|
9068
|
+
outcome.initialPrompt,
|
|
9069
|
+
rt.desc.parentSessionId ? { promptSource: `agent:${rt.desc.parentSessionId}` } : void 0
|
|
9070
|
+
).catch((err) => {
|
|
9014
9071
|
appendLine(
|
|
9015
9072
|
rt,
|
|
9016
9073
|
`[turn error] ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -9268,7 +9325,7 @@ ${message}`;
|
|
|
9268
9325
|
}
|
|
9269
9326
|
if (rtPre) await maybeResumeAgent(rtPre);
|
|
9270
9327
|
const rt = validateAgentTurn(id, "sendPrompt");
|
|
9271
|
-
await runAgentTurn(rt, message);
|
|
9328
|
+
await runAgentTurn(rt, message, opts2?.source ? { promptSource: opts2.source } : void 0);
|
|
9272
9329
|
},
|
|
9273
9330
|
async enqueuePrompt(id, message, opts2) {
|
|
9274
9331
|
const rtPre = sessions.get(id);
|
|
@@ -9280,7 +9337,7 @@ ${message}`;
|
|
|
9280
9337
|
}
|
|
9281
9338
|
await maybeResumeAgent(rtPre);
|
|
9282
9339
|
const rt = validateAgentTurn(id, "enqueuePrompt");
|
|
9283
|
-
void runAgentTurn(rt, message).catch((err) => {
|
|
9340
|
+
void runAgentTurn(rt, message, opts2?.source ? { promptSource: opts2.source } : void 0).catch((err) => {
|
|
9284
9341
|
appendLine(
|
|
9285
9342
|
rtPre,
|
|
9286
9343
|
`[error] ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -10707,12 +10764,17 @@ function registerFsTools(server, opts) {
|
|
|
10707
10764
|
const anchor = makeAnchor(opts.workspace);
|
|
10708
10765
|
server.tool(
|
|
10709
10766
|
"file_read",
|
|
10710
|
-
|
|
10711
|
-
{
|
|
10712
|
-
|
|
10767
|
+
`Read a file from the workspace. Defaults to UTF-8 text; pass encoding: "base64" for binary files (images, audio, video, \u2026) \u2014 UTF-8 decoding a non-text file replaces invalid byte sequences (e.g. a PNG's 0x89 header byte) with U+FFFD, corrupting the content.`,
|
|
10768
|
+
{
|
|
10769
|
+
path: z.string().describe("Workspace-relative path to the file."),
|
|
10770
|
+
encoding: z.enum(["utf8", "base64"]).optional().describe(
|
|
10771
|
+
'"utf8" (default) for text files, "base64" for binary files.'
|
|
10772
|
+
)
|
|
10773
|
+
},
|
|
10774
|
+
async ({ path, encoding }) => {
|
|
10713
10775
|
const abs = anchor(path);
|
|
10714
10776
|
const buf = await readFile(abs);
|
|
10715
|
-
return text(buf.toString("utf8"));
|
|
10777
|
+
return text(buf.toString(encoding === "base64" ? "base64" : "utf8"));
|
|
10716
10778
|
}
|
|
10717
10779
|
);
|
|
10718
10780
|
server.tool(
|
|
@@ -11288,8 +11350,10 @@ function registerAgentTools(server, opts) {
|
|
|
11288
11350
|
const sessionId = resolveSessionIdArg(input);
|
|
11289
11351
|
if (!sessionId) return missingSessionIdError("agent_prompt");
|
|
11290
11352
|
try {
|
|
11353
|
+
const promptSource = callerScope?.ownerSessionId ?? callerSessionId;
|
|
11291
11354
|
await registry.enqueuePrompt(sessionId, input.prompt, {
|
|
11292
|
-
interrupt: input.interrupt
|
|
11355
|
+
interrupt: input.interrupt,
|
|
11356
|
+
...promptSource ? { source: `agent:${promptSource}` } : {}
|
|
11293
11357
|
});
|
|
11294
11358
|
return {
|
|
11295
11359
|
content: [
|
|
@@ -11316,6 +11380,67 @@ function registerAgentTools(server, opts) {
|
|
|
11316
11380
|
}
|
|
11317
11381
|
}
|
|
11318
11382
|
);
|
|
11383
|
+
server.tool(
|
|
11384
|
+
"message_parent",
|
|
11385
|
+
"Report a message UP to the session that spawned you (your parent/supervisor) \u2014 a result, a progress update, or a blocker. No session id needed: the daemon resolves your recorded parent from your own session identity (also visible as the AGENTPROTO_PARENT_SESSION_ID env var). Delivered as a prompt when the parent is idle, or queued onto its next turn when it's mid-turn (never interrupts). Errors if this session has no recorded parent or the parent is gone.",
|
|
11386
|
+
{
|
|
11387
|
+
message: z.string().min(1).describe("The message to deliver to your parent session (plain text).")
|
|
11388
|
+
},
|
|
11389
|
+
async (input) => {
|
|
11390
|
+
const fail = (text9) => ({
|
|
11391
|
+
content: [{ type: "text", text: text9 }],
|
|
11392
|
+
isError: true
|
|
11393
|
+
});
|
|
11394
|
+
const selfId = callerScope?.ownerSessionId ?? callerSessionId;
|
|
11395
|
+
if (!selfId) {
|
|
11396
|
+
return fail(
|
|
11397
|
+
"message_parent: cannot identify the calling session \u2014 this tool needs gateway access attributed to a session (a scoped orchestrator gateway, or a daemon `/mcp` URL carrying `?callerSessionId=`). A human/root caller has no parent to message."
|
|
11398
|
+
);
|
|
11399
|
+
}
|
|
11400
|
+
const self = registry.get(selfId);
|
|
11401
|
+
if (!self) {
|
|
11402
|
+
return fail(`message_parent: calling session "${selfId}" is not in the registry.`);
|
|
11403
|
+
}
|
|
11404
|
+
const parentId = self.parentSessionId;
|
|
11405
|
+
if (!parentId || parentId === selfId) {
|
|
11406
|
+
return fail(
|
|
11407
|
+
"message_parent: this session has no recorded parent \u2014 it was spawned at the root, so there is no one to report up to."
|
|
11408
|
+
);
|
|
11409
|
+
}
|
|
11410
|
+
const parent = registry.get(parentId);
|
|
11411
|
+
if (!parent) {
|
|
11412
|
+
return fail(`message_parent: parent session "${parentId}" no longer exists.`);
|
|
11413
|
+
}
|
|
11414
|
+
if (parent.status !== "running" && parent.status !== "starting") {
|
|
11415
|
+
return fail(
|
|
11416
|
+
`message_parent: parent session "${parentId}" is not running (status: ${parent.status}) \u2014 the message cannot be delivered.`
|
|
11417
|
+
);
|
|
11418
|
+
}
|
|
11419
|
+
const who = self.label ?? selfId;
|
|
11420
|
+
const notice = `[child-message] ${who} (${selfId}): ${input.message}`;
|
|
11421
|
+
const done = (delivery) => ({
|
|
11422
|
+
content: [
|
|
11423
|
+
{
|
|
11424
|
+
type: "text",
|
|
11425
|
+
text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery }, null, 2)
|
|
11426
|
+
}
|
|
11427
|
+
]
|
|
11428
|
+
});
|
|
11429
|
+
if (!parent.busy) {
|
|
11430
|
+
try {
|
|
11431
|
+
await registry.enqueuePrompt(parentId, notice, {});
|
|
11432
|
+
return done("enqueued");
|
|
11433
|
+
} catch {
|
|
11434
|
+
}
|
|
11435
|
+
}
|
|
11436
|
+
if (!registry.stampPendingChildCrashNotice(parentId, notice)) {
|
|
11437
|
+
return fail(
|
|
11438
|
+
`message_parent: parent session "${parentId}" vanished mid-delivery.`
|
|
11439
|
+
);
|
|
11440
|
+
}
|
|
11441
|
+
return done("queued-next-turn");
|
|
11442
|
+
}
|
|
11443
|
+
);
|
|
11319
11444
|
server.tool(
|
|
11320
11445
|
"agent_output",
|
|
11321
11446
|
"Tail the recent output of a session. Returns the last N lines of the ring buffer (stdout + stderr inter-leaved, newest last). Use this to read an agent's reply after `agent_prompt`.",
|
|
@@ -27825,6 +27950,11 @@ var DEFAULT_ORCHESTRATOR_TOOLS = [
|
|
|
27825
27950
|
"agent_prompt",
|
|
27826
27951
|
"agent_output",
|
|
27827
27952
|
"agent_kill",
|
|
27953
|
+
// Child→parent report-back. NOT a delegation tool (takes no session id;
|
|
27954
|
+
// the daemon resolves the caller's own recorded parent, and it can reach
|
|
27955
|
+
// nothing else) — it's also the sole tool of the minimal report-only
|
|
27956
|
+
// scope `session-spawn.ts` mints for a gateway-less child with a parent.
|
|
27957
|
+
"message_parent",
|
|
27828
27958
|
"session_monitor",
|
|
27829
27959
|
"session_events_poll",
|
|
27830
27960
|
"session_list",
|