@sema-agent/core 2.0.1 → 2.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/dist/agents/observer.d.ts +14 -0
- package/dist/agents/observer.js +66 -12
- package/dist/agents/send-message-tool.js +164 -88
- package/dist/agents/subagent.d.ts +11 -4
- package/dist/agents/subagent.js +166 -62
- package/dist/core/context-edit.js +16 -3
- package/dist/core/file-snapshot-store.js +10 -1
- package/dist/core/memory-engine/dual-root.js +2 -0
- package/dist/core/memory-engine/engine.d.ts +4 -0
- package/dist/core/memory-engine/engine.js +6 -1
- package/dist/core/runner/prepare-memory.d.ts +4 -0
- package/dist/core/runner/prepare-memory.js +4 -1
- package/dist/core/runner/prepare-task.d.ts +9 -2
- package/dist/core/runner/prepare-task.js +68 -13
- package/dist/core/runner/runtask.js +919 -865
- package/dist/core/runner/synthetic-tools.d.ts +1 -0
- package/dist/core/runner/synthetic-tools.js +18 -15
- package/dist/core/runner/turn-attachments.d.ts +27 -3
- package/dist/core/runner/turn-attachments.js +101 -12
- package/dist/core/task-registry-agent.d.ts +9 -1
- package/dist/core/task-registry-agent.js +23 -2
- package/dist/core/task-registry-monitor.js +79 -24
- package/dist/core/task-registry-shared.d.ts +13 -1
- package/dist/core/task-registry-shared.js +21 -0
- package/dist/core/task-registry.d.ts +2 -0
- package/dist/core/task-registry.js +24 -26
- package/dist/core/tool-result-budget.js +2 -2
- package/dist/core/tool-result-store.d.ts +2 -0
- package/dist/core/tool-result-store.js +27 -2
- package/dist/core/types.d.ts +4 -2
- package/dist/core/workflow-journal-store.d.ts +13 -0
- package/dist/engine/session/import-validate.js +29 -0
- package/dist/engine/session/memory-repo.js +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/workflow-size-guideline.d.ts +6 -1
- package/dist/orchestration/workflow-size-guideline.js +19 -9
- package/dist/orchestration/workflow.d.ts +1 -0
- package/dist/orchestration/workflow.js +44 -1
- package/dist/prompt-assembly/assemble.js +3 -7
- package/dist/prompt-assembly/event-registry.js +1 -1
- package/dist/prompt-assembly/packs/sema-default.js +8 -5
- package/dist/prompts/coordinator.d.ts +1 -1
- package/dist/prompts/coordinator.js +45 -0
- package/dist/prompts/default.d.ts +4 -5
- package/dist/prompts/default.js +16 -18
- package/dist/prompts/simple-sections.d.ts +3 -1
- package/dist/prompts/simple-sections.js +11 -1
- package/dist/stores/cc/task-list-store.js +3 -3
- package/dist/stores/file/memory-store.d.ts +3 -0
- package/dist/stores/file/memory-store.js +39 -12
- package/dist/stores/file/tool-result-store.js +16 -2
- package/dist/stores/file/workflow-journal-store.d.ts +23 -0
- package/dist/stores/file/workflow-journal-store.js +140 -3
- package/dist/tools/fs/bash-readonly-classifier.js +21 -2
- package/dist/tools/fs/fs-bash.d.ts +1 -0
- package/dist/tools/fs/fs-bash.js +10 -3
- package/dist/tools/fs/fs-read.js +12 -12
- package/dist/tools/fs/fs-search-tools.js +42 -7
- package/dist/tools/fs/fs-write.js +18 -6
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +2 -1
- package/dist/tools/fs/safety.d.ts +4 -0
- package/dist/tools/fs/safety.js +113 -10
- package/dist/tools/fs/search.d.ts +1 -0
- package/dist/tools/fs/search.js +23 -3
- package/dist/tools/monitor.js +1 -1
- package/dist/tools/task-list.d.ts +1 -0
- package/dist/tools/task-list.js +13 -2
- package/dist/tools/web.js +36 -6
- package/package.json +3 -2
package/dist/agents/subagent.js
CHANGED
|
@@ -19,12 +19,9 @@ import { BG_AGENT_REAP_STOP_ERROR } from "../core/task-registry.js";
|
|
|
19
19
|
import { extractErrorCode } from "../brain/errors.js";
|
|
20
20
|
import { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX } from "../config/defaults.js";
|
|
21
21
|
export { FORK_DEFAULT_MAX_TURNS, SESSION_BG_DEFAULT_TIMEOUT_SEC, RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX };
|
|
22
|
-
import { SUBAGENT_RESUME_CAP, SubagentRetainLedger,
|
|
23
|
-
export { SubagentRetainLedger, getSessionRetainLedger, releaseSessionRetainLedger };
|
|
24
|
-
export { AGENT_TRANSCRIPT_TOOL_NAME, createAgentTranscriptTool } from "./agent-transcript-tool.js";
|
|
25
|
-
export { SEND_MESSAGE_TOOL_NAME, createSendMessageTool } from "./send-message-tool.js";
|
|
22
|
+
import { SUBAGENT_RESUME_CAP, SubagentRetainLedger, getOrCreateSessionRetainLedger, ensureSessionReapHook, createResumePrompt, } from "./retain-ledger.js";
|
|
26
23
|
import { recordRosterSpawn } from "./roster-store.js";
|
|
27
|
-
import { ObserverDigestTap, ObserverPairing, createObserverReportToolSpec, markObserverTaskId, unmarkObserverTaskId, isObserverTaskId, observerFramingPrompt, observerSlug, resolveObserverDeclaration, } from "./observer.js";
|
|
24
|
+
import { ObserverDigestTap, ObserverPairing, createObserverReportToolSpec, markObserverTaskId, unmarkObserverTaskId, isObserverTaskId, ObserverResumeStateError, ObserverStoppedByUserError, observerFramingPrompt, observerSlug, resolveObserverDeclaration, } from "./observer.js";
|
|
28
25
|
import { SubagentStepRecorder } from "./subagent-steps.js";
|
|
29
26
|
const BG_AGENT_RESULT_MAX = 4_000;
|
|
30
27
|
const BG_AGENT_RESULT_FULL_MAX = 200_000;
|
|
@@ -68,6 +65,7 @@ export function inheritedManifestScopeFor(snapshot) {
|
|
|
68
65
|
export const DEFAULT_SUBAGENT_TOOL_NAME = "Agent";
|
|
69
66
|
export const LEGACY_SUBAGENT_TOOL_NAME = "Task";
|
|
70
67
|
export const EXTRA_TOOLS_MAX_FACTORY_CALLS_PER_TREE = 64;
|
|
68
|
+
export const DEFAULT_SUBAGENT_MAX_DEPTH = 3;
|
|
71
69
|
export const EXTRA_TOOLS_FAILED_NOTE = "note: extraTools evaluation failed — the injected tool set was skipped for this spawn.";
|
|
72
70
|
export const EXTRA_TOOLS_BUDGET_NOTE = `note: extraTools evaluation was skipped — this spawn tree exhausted its factory-call budget (${EXTRA_TOOLS_MAX_FACTORY_CALLS_PER_TREE}); no dynamic tools were injected for this spawn.`;
|
|
73
71
|
function countArgLines(v) {
|
|
@@ -131,6 +129,9 @@ function createToolStatsCounter(delegationToolName) {
|
|
|
131
129
|
snapshot() {
|
|
132
130
|
return t.readCount + t.searchCount + t.bashCount + t.editFileCount + t.otherToolCount > 0 ? { ...t } : undefined;
|
|
133
131
|
},
|
|
132
|
+
total() {
|
|
133
|
+
return t.readCount + t.searchCount + t.bashCount + t.editFileCount + t.otherToolCount;
|
|
134
|
+
},
|
|
134
135
|
};
|
|
135
136
|
}
|
|
136
137
|
export const FORK_SUBAGENT_TYPE = "fork";
|
|
@@ -580,6 +581,9 @@ function adaptLegacyForkArgs(args) {
|
|
|
580
581
|
}
|
|
581
582
|
return args;
|
|
582
583
|
}
|
|
584
|
+
export function normalizeSubagentType(value) {
|
|
585
|
+
return value.normalize("NFKC").toLowerCase().replace(/[\p{White_Space}\p{Pd}_]+/gu, "");
|
|
586
|
+
}
|
|
583
587
|
function foldGeneralPurposeAlias(args, shadowed) {
|
|
584
588
|
if (shadowed)
|
|
585
589
|
return args;
|
|
@@ -611,6 +615,31 @@ Guidelines (your directive may override any of these):
|
|
|
611
615
|
</fork-boilerplate>
|
|
612
616
|
|
|
613
617
|
Your directive: `;
|
|
618
|
+
export function forkWorktreeTranslationNote(parentCwd, worktreeDir) {
|
|
619
|
+
const where = parentCwd !== undefined ? parentCwd : "a different working directory";
|
|
620
|
+
return (`You've inherited the conversation context above from a parent agent working in ${where}. ` +
|
|
621
|
+
`You are operating in an isolated git worktree at ${worktreeDir} — same repository, same relative file structure, separate working copy. ` +
|
|
622
|
+
`Paths in the inherited context refer to the parent's working directory; translate them to your worktree root. ` +
|
|
623
|
+
`Re-read files before editing if the parent may have modified them since they appear in the context. ` +
|
|
624
|
+
`Your changes stay in this worktree and will not affect the parent's files.`);
|
|
625
|
+
}
|
|
626
|
+
export function asyncLaunchedReceipt(p) {
|
|
627
|
+
const noteLines = (p.notes ?? []).filter((n) => n !== undefined && n !== "");
|
|
628
|
+
const oneShotBlocked = p.notify && p.oneShot === true;
|
|
629
|
+
return (`Async agent launched successfully. (This tool result is internal metadata — never quote or paste any part of it, including the task_id below, into a user-facing reply.)\n` +
|
|
630
|
+
`task_id: ${p.taskId} (internal ID - do not mention to user. Use SendMessage with to: '${p.taskId}', summary: '<5-10 word recap>' to continue this agent.)\n` +
|
|
631
|
+
(oneShotBlocked
|
|
632
|
+
? `${p.workingLine} This is a ONE-SHOT submission — there is no later turn for a background notification to land in, so do NOT end your turn expecting one. Actively wait instead: TaskOutput(task_id: "${p.taskId}", block: true). If it is still running after the wait, wait again (bounded) rather than ending the turn, or write out your best available answer now if you are near your own time budget.\n`
|
|
633
|
+
: p.notify
|
|
634
|
+
? `${p.workingLine} You will be notified automatically when it completes. You know nothing about its results until that notification arrives — do not report, assume, or predict them; continue other work or respond to the user in the meantime.\n`
|
|
635
|
+
: `${p.workingLine} Its result is NOT pushed automatically — retrieve progress and results with TaskOutput(task_id) where mounted.\n`) +
|
|
636
|
+
noteLines.map((n) => `${n}\n`).join("") +
|
|
637
|
+
(oneShotBlocked
|
|
638
|
+
? `In your own words, briefly tell the user what you launched — do not echo this tool result. Do not assume a later message will deliver the result: this submission ends after this turn, so retrieve it now with the blocking TaskOutput wait above before writing your final answer.`
|
|
639
|
+
: p.notify
|
|
640
|
+
? `In your own words, briefly tell the user what you launched — do not echo this tool result. Agent results will arrive in a subsequent message. If the user asks for progress, say the agent is still running.`
|
|
641
|
+
: `In your own words, briefly tell the user what you launched — do not echo this tool result. The result will NOT arrive on its own — retrieve it with TaskOutput(task_id) where mounted before relying on it.`));
|
|
642
|
+
}
|
|
614
643
|
export function createSubagentTool(opts) {
|
|
615
644
|
const catalog = opts.runner.agentCatalog;
|
|
616
645
|
if (opts.agents === undefined && catalog?.agents)
|
|
@@ -683,7 +712,7 @@ export function agentWhenToUseText(def, lean = true) {
|
|
|
683
712
|
return (leanText || def.whenToUse) || undefined;
|
|
684
713
|
}
|
|
685
714
|
function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
686
|
-
const maxDepth = opts.maxDepth ??
|
|
715
|
+
const maxDepth = opts.maxDepth ?? DEFAULT_SUBAGENT_MAX_DEPTH;
|
|
687
716
|
const toolName = opts.name ?? DEFAULT_SUBAGENT_TOOL_NAME;
|
|
688
717
|
const deploymentNames = new Set((opts.agents ?? []).map((a) => a.name));
|
|
689
718
|
const builtins = opts.builtinAgents === false ? [] : builtinAgentDefinitions(toolName).filter((d) => !deploymentNames.has(d.name));
|
|
@@ -700,6 +729,13 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
700
729
|
];
|
|
701
730
|
const rosterNames = opts.models ? Object.keys(opts.models) : undefined;
|
|
702
731
|
const agentListing = [
|
|
732
|
+
...(generalPurposeShadowed
|
|
733
|
+
? []
|
|
734
|
+
: [{
|
|
735
|
+
name: GENERAL_PURPOSE_SUBAGENT_TYPE,
|
|
736
|
+
description: "General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you.",
|
|
737
|
+
tools: "All tools",
|
|
738
|
+
}]),
|
|
703
739
|
...available.map((a) => ({ name: a.name, description: agentWhenToUseText(a) ?? "", tools: agentToolsNote(a) })),
|
|
704
740
|
...(forkOffered
|
|
705
741
|
? [{
|
|
@@ -821,12 +857,29 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
821
857
|
details: { error: "unknown subagent_type" },
|
|
822
858
|
};
|
|
823
859
|
}
|
|
824
|
-
const
|
|
860
|
+
const requestedType = a.subagent_type ?? "";
|
|
861
|
+
let rawType = requestedType;
|
|
862
|
+
if (requestedType !== "") {
|
|
863
|
+
const accepted = acceptedSubagentTypes();
|
|
864
|
+
if (!accepted.includes(requestedType)) {
|
|
865
|
+
const wanted = normalizeSubagentType(requestedType);
|
|
866
|
+
const hits = wanted === "" ? [] : accepted.filter((n) => normalizeSubagentType(n) === wanted);
|
|
867
|
+
if (hits.length > 1) {
|
|
868
|
+
return {
|
|
869
|
+
isError: true,
|
|
870
|
+
content: `Sub-agent not started: subagent_type "${requestedType}" is ambiguous — it matches ${hits.join(", ")}. Use the exact name.`,
|
|
871
|
+
details: { error: "ambiguous subagent_type" },
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
if (hits.length === 1)
|
|
875
|
+
rawType = hits[0];
|
|
876
|
+
}
|
|
877
|
+
}
|
|
825
878
|
const wantsFork = forkOffered && rawType === FORK_SUBAGENT_TYPE;
|
|
826
879
|
if (reviveClaim !== undefined && wantsFork) {
|
|
827
880
|
return { isError: true, content: `Sub-agent not revived: a "${FORK_SUBAGENT_TYPE}" agent cannot be revived — launch a new agent with the needed context instead.`, details: { error: "revive.fork_unsupported" } };
|
|
828
881
|
}
|
|
829
|
-
const omitted = rawType === "";
|
|
882
|
+
const omitted = rawType === "" || (!generalPurposeShadowed && rawType === GENERAL_PURPOSE_SUBAGENT_TYPE);
|
|
830
883
|
const def = hasAgents && !wantsFork && !omitted ? agentMap.get(rawType) : undefined;
|
|
831
884
|
const spawnAgentType = wantsFork ? FORK_SUBAGENT_TYPE : omitted ? GENERAL_PURPOSE_SUBAGENT_TYPE : rawType;
|
|
832
885
|
if (!def && !wantsFork && !omitted) {
|
|
@@ -1060,9 +1113,17 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1060
1113
|
let observerTap;
|
|
1061
1114
|
let observerSessionId;
|
|
1062
1115
|
let observerFailure;
|
|
1116
|
+
let observerDegradedCount = 0;
|
|
1117
|
+
let observerDegradedFirst;
|
|
1063
1118
|
const noteObserverFailure = (err) => {
|
|
1064
|
-
|
|
1065
|
-
|
|
1119
|
+
const msg = (err instanceof Error ? err.message : String(err)).slice(0, REPORT_FIELD_MAX);
|
|
1120
|
+
if (observerPairing !== undefined && observerPairing.state === "armed") {
|
|
1121
|
+
observerDegradedCount++;
|
|
1122
|
+
if (observerDegradedFirst === undefined)
|
|
1123
|
+
observerDegradedFirst = msg;
|
|
1124
|
+
}
|
|
1125
|
+
else if (observerFailure === undefined) {
|
|
1126
|
+
observerFailure = msg;
|
|
1066
1127
|
}
|
|
1067
1128
|
try {
|
|
1068
1129
|
opts.onObserverError?.(err, {
|
|
@@ -1074,19 +1135,31 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1074
1135
|
}
|
|
1075
1136
|
};
|
|
1076
1137
|
const observedSteerRef = {};
|
|
1138
|
+
let pendingTrigger = [];
|
|
1139
|
+
const takePendingTrigger = () => {
|
|
1140
|
+
if (pendingTrigger.length === 0)
|
|
1141
|
+
return undefined;
|
|
1142
|
+
const joined = pendingTrigger.join("\n\n");
|
|
1143
|
+
pendingTrigger = [];
|
|
1144
|
+
return joined;
|
|
1145
|
+
};
|
|
1146
|
+
const injectObservedRequest = async (text, origin) => {
|
|
1147
|
+
const steer = observedSteerRef.steer;
|
|
1148
|
+
if (!steer)
|
|
1149
|
+
throw new Error("observed child has no steer lane (report undeliverable)");
|
|
1150
|
+
if (origin !== "observer")
|
|
1151
|
+
pendingTrigger.push(text);
|
|
1152
|
+
await steer(text);
|
|
1153
|
+
};
|
|
1077
1154
|
if (observerArm) {
|
|
1155
|
+
pendingTrigger.push(prompt);
|
|
1078
1156
|
const observerDef = observerArm.observerDefinition;
|
|
1079
1157
|
const envelopeName = observerSlug(childAgentName ?? def.name);
|
|
1080
|
-
|
|
1158
|
+
let sid = uuidv7();
|
|
1081
1159
|
observerSessionId = sid;
|
|
1082
1160
|
markObserverTaskId(sid);
|
|
1083
1161
|
const reportOpts = {
|
|
1084
|
-
queueReport: async (framed) =>
|
|
1085
|
-
const steer = observedSteerRef.steer;
|
|
1086
|
-
if (!steer)
|
|
1087
|
-
throw new Error("observed child has no steer lane (report undeliverable)");
|
|
1088
|
-
await steer(framed);
|
|
1089
|
-
},
|
|
1162
|
+
queueReport: async (framed) => injectObservedRequest(framed, "observer"),
|
|
1090
1163
|
};
|
|
1091
1164
|
const reportTool = createObserverReportToolSpec(reportOpts);
|
|
1092
1165
|
const observerSpec = (objectiveText, resume) => ({
|
|
@@ -1106,15 +1179,36 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1106
1179
|
});
|
|
1107
1180
|
const runObserverLeg = async (spec) => {
|
|
1108
1181
|
if (!isObserverTaskId(sid))
|
|
1109
|
-
throw new
|
|
1182
|
+
throw new ObserverStoppedByUserError("observer identity revoked — delivery refused");
|
|
1110
1183
|
const r = await opts.runner.runTask(spec);
|
|
1111
1184
|
if (r.status !== "completed") {
|
|
1185
|
+
if (r.errorCode === "resume.session_not_found") {
|
|
1186
|
+
throw new ObserverResumeStateError(`observer session no longer exists${r.errorMessage ? `: ${r.errorMessage}` : ""}`);
|
|
1187
|
+
}
|
|
1112
1188
|
throw new Error(`observer run ended ${r.status}${r.errorMessage ? `: ${r.errorMessage}` : ""}`);
|
|
1113
1189
|
}
|
|
1114
1190
|
};
|
|
1115
1191
|
const spawner = {
|
|
1116
1192
|
spawnFirstRun: ({ framingPrompt, digest }) => runObserverLeg(observerSpec(`${framingPrompt}\n\n${digest}`, false)),
|
|
1117
1193
|
deliver: ({ digest }) => runObserverLeg(observerSpec(digest, true)),
|
|
1194
|
+
restartFresh: async ({ framingPrompt, digest }) => {
|
|
1195
|
+
const dead = sid;
|
|
1196
|
+
const fresh = uuidv7();
|
|
1197
|
+
markObserverTaskId(fresh);
|
|
1198
|
+
sid = fresh;
|
|
1199
|
+
observerSessionId = fresh;
|
|
1200
|
+
try {
|
|
1201
|
+
await runObserverLeg(observerSpec(`${framingPrompt}\n\n${digest}`, false));
|
|
1202
|
+
}
|
|
1203
|
+
finally {
|
|
1204
|
+
unmarkObserverTaskId(dead);
|
|
1205
|
+
try {
|
|
1206
|
+
await opts.runner.sessions.release(dead);
|
|
1207
|
+
}
|
|
1208
|
+
catch {
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
},
|
|
1118
1212
|
};
|
|
1119
1213
|
observerPairing = new ObserverPairing({
|
|
1120
1214
|
observedEnvelopeName: envelopeName,
|
|
@@ -1125,7 +1219,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1125
1219
|
onError: noteObserverFailure,
|
|
1126
1220
|
});
|
|
1127
1221
|
reportOpts.pairing = observerPairing;
|
|
1128
|
-
observerTap = new ObserverDigestTap((activity) => observerPairing.enqueueSegment(activity));
|
|
1222
|
+
observerTap = new ObserverDigestTap((activity) => observerPairing.enqueueSegment(activity, takePendingTrigger()));
|
|
1129
1223
|
}
|
|
1130
1224
|
const closeObserverWindow = (status) => {
|
|
1131
1225
|
if (observerTap)
|
|
@@ -1152,9 +1246,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1152
1246
|
t.unref?.();
|
|
1153
1247
|
});
|
|
1154
1248
|
const outcome = await Promise.race([observerPairing.drain().then(() => "drained"), timeout]);
|
|
1249
|
+
observerPairing.retire(outcome === "timeout" ? "stopped" : "retired");
|
|
1155
1250
|
if (outcome === "timeout")
|
|
1156
1251
|
noteObserverFailure(new Error("observer unresponsive at settle (delivery drain timed out)"));
|
|
1157
|
-
observerPairing.retire(outcome === "timeout" ? "stopped" : "retired");
|
|
1158
1252
|
if (observerSessionId !== undefined) {
|
|
1159
1253
|
try {
|
|
1160
1254
|
await opts.runner.sessions.release(observerSessionId);
|
|
@@ -1183,7 +1277,13 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1183
1277
|
clearTimeout(timer);
|
|
1184
1278
|
return outcome;
|
|
1185
1279
|
};
|
|
1186
|
-
const observerNoteFor = (outcome) => observerFailure !== undefined
|
|
1280
|
+
const observerNoteFor = (outcome) => observerFailure !== undefined
|
|
1281
|
+
? " [observer: failed]"
|
|
1282
|
+
: outcome === "still_draining"
|
|
1283
|
+
? " [observer: still draining]"
|
|
1284
|
+
: observerDegradedCount > 0
|
|
1285
|
+
? ` [observer: degraded, ${observerDegradedCount} digest batch(es) lost]`
|
|
1286
|
+
: "";
|
|
1187
1287
|
const stepRecorder = new SubagentStepRecorder(ctx.toolCallId);
|
|
1188
1288
|
const toolStatsCounter = createToolStatsCounter(opts.name ?? DEFAULT_SUBAGENT_TOOL_NAME);
|
|
1189
1289
|
let assistantTextTail = "";
|
|
@@ -1440,7 +1540,8 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1440
1540
|
}
|
|
1441
1541
|
const forkInternals = { ...childInternals, insideFork: true };
|
|
1442
1542
|
stepRecorder.lockTo(forkedId);
|
|
1443
|
-
const
|
|
1543
|
+
const forkWorktreeNote = worktreeDir !== undefined ? forkWorktreeTranslationNote(ctx.parentCwd, worktreeDir) : undefined;
|
|
1544
|
+
const forkObjective = `${FORK_DIRECTIVE_FRAME}${prompt}${forkWorktreeNote !== undefined ? `\n\n${forkWorktreeNote}` : ""}`;
|
|
1444
1545
|
if (wantsBackground) {
|
|
1445
1546
|
const bg = opts.background;
|
|
1446
1547
|
const notify = ctx.onTaskNotification ?? bg.notify;
|
|
@@ -1674,25 +1775,22 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1674
1775
|
}
|
|
1675
1776
|
});
|
|
1676
1777
|
return {
|
|
1677
|
-
content:
|
|
1678
|
-
|
|
1679
|
-
`
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
`
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
`
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
(notify
|
|
1694
|
-
? `Briefly tell the user what you launched and end your response. Do not generate any other text — agent results will arrive in a subsequent message.`
|
|
1695
|
-
: `Briefly tell the user what you launched. The result will NOT arrive on its own — retrieve it with TaskOutput(task_id) where mounted before relying on it.`),
|
|
1778
|
+
content: asyncLaunchedReceipt({
|
|
1779
|
+
taskId,
|
|
1780
|
+
workingLine: `The forked agent inherits your full current context and works on its prompt in the background.`,
|
|
1781
|
+
notify: Boolean(notify),
|
|
1782
|
+
oneShot: ctx.oneShot === true,
|
|
1783
|
+
notes: [
|
|
1784
|
+
modelNote,
|
|
1785
|
+
extraToolsNote,
|
|
1786
|
+
worktreeDir !== undefined
|
|
1787
|
+
? `worktree: ${worktreeDir} (the agent works in its own detached worktree; auto-removed when it settles unchanged, kept when it made changes)`
|
|
1788
|
+
: undefined,
|
|
1789
|
+
sessionScopedBg
|
|
1790
|
+
? `It is session-scoped: it keeps running even after this task ends.`
|
|
1791
|
+
: `Note: it is stopped automatically (killed) if still running when this task ends — do not promise the user results beyond this task.`,
|
|
1792
|
+
],
|
|
1793
|
+
}),
|
|
1696
1794
|
details: { type: "agent", subagent_type: FORK_SUBAGENT_TYPE, status: "async_launched", isAsync: true, task_id: taskId, description: shortDesc, prompt },
|
|
1697
1795
|
};
|
|
1698
1796
|
}
|
|
@@ -2308,29 +2406,27 @@ task_id: ${taskId}
|
|
|
2308
2406
|
}
|
|
2309
2407
|
}
|
|
2310
2408
|
return {
|
|
2311
|
-
content:
|
|
2312
|
-
|
|
2313
|
-
`
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
`
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
`
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
(notify
|
|
2328
|
-
? `Briefly tell the user what you launched and end your response. Do not generate any other text — agent results will arrive in a subsequent message.`
|
|
2329
|
-
: `Briefly tell the user what you launched. The result will NOT arrive on its own — retrieve it with TaskOutput(task_id) where mounted before relying on it.`),
|
|
2409
|
+
content: asyncLaunchedReceipt({
|
|
2410
|
+
taskId,
|
|
2411
|
+
workingLine: `The agent is working in the background.`,
|
|
2412
|
+
notify: Boolean(notify),
|
|
2413
|
+
oneShot: ctx.oneShot === true,
|
|
2414
|
+
notes: [
|
|
2415
|
+
modelNote,
|
|
2416
|
+
extraToolsNote,
|
|
2417
|
+
worktreeDir !== undefined
|
|
2418
|
+
? `worktree: ${worktreeDir} (the agent works in its own detached worktree; auto-removed when it settles unchanged, kept when it made changes)`
|
|
2419
|
+
: undefined,
|
|
2420
|
+
sessionScopedBg
|
|
2421
|
+
? `It is session-scoped: it keeps running even after this task ends.`
|
|
2422
|
+
: `Note: it is stopped automatically (killed) if still running when this task ends — do not promise the user results beyond this task.`,
|
|
2423
|
+
],
|
|
2424
|
+
}),
|
|
2330
2425
|
details: { type: "agent", status: "async_launched", isAsync: true, task_id: taskId, description: shortDesc, prompt },
|
|
2331
2426
|
};
|
|
2332
2427
|
}
|
|
2333
2428
|
let child;
|
|
2429
|
+
const syncStartedAt = Date.now();
|
|
2334
2430
|
let retainEntry;
|
|
2335
2431
|
if (ctx.onSubagentSpawn || observerPairing) {
|
|
2336
2432
|
retainEntry = ctx.onSubagentSpawn ? await tryRetainChild(ctx.subagentRetain) : undefined;
|
|
@@ -2438,9 +2534,13 @@ task_id: ${taskId}
|
|
|
2438
2534
|
child.blockedReason ? `blocked_reason: ${inlineUntrusted(child.blockedReason, REPORT_FIELD_MAX)}` : "",
|
|
2439
2535
|
child.errorMessage ? `error: ${inlineUntrusted(child.errorMessage, REPORT_FIELD_MAX)}` : "",
|
|
2440
2536
|
errClass ? `error_kind: ${errClass.errorKind} (retryable: ${errClass.retryable})` : "",
|
|
2441
|
-
observerFailure !== undefined
|
|
2537
|
+
observerFailure !== undefined
|
|
2538
|
+
? `observer: failed (${inlineUntrusted(observerFailure, REPORT_FIELD_MAX)})`
|
|
2539
|
+
: observerDegradedCount > 0
|
|
2540
|
+
? `observer: degraded — ${observerDegradedCount} digest batch(es) never reached it (first: ${inlineUntrusted(observerDegradedFirst ?? "", REPORT_FIELD_MAX)})`
|
|
2541
|
+
: "",
|
|
2442
2542
|
`stats: turns=${child.stats.turns} tokens=${child.stats.tokens}`,
|
|
2443
|
-
retainEntry
|
|
2543
|
+
!retainEntry && failureRetained ? `transcript_id: ${child.sessionId} (internal ID - do not mention to user; inspect via AgentTranscript where mounted)` : "",
|
|
2444
2544
|
retainEntry ? `resumable: true` : "",
|
|
2445
2545
|
failureRetained
|
|
2446
2546
|
? `session_retained: this child's session is kept for a short time (~15 min) so its transcript stays inspectable via transcript_id; it is released automatically afterwards.`
|
|
@@ -2471,6 +2571,10 @@ task_id: ${taskId}
|
|
|
2471
2571
|
: "(no text result)",
|
|
2472
2572
|
"",
|
|
2473
2573
|
"Verify this result before concluding. If it leaves more to do, continue or delegate again.",
|
|
2574
|
+
retainEntry
|
|
2575
|
+
? `transcript_id: ${child.sessionId} (internal ID - do not mention to user. Transcript inspectable via AgentTranscript where mounted; this child is continued through the deployment's resume handle, not by SendMessage.)`
|
|
2576
|
+
: "",
|
|
2577
|
+
`<usage>subagent_tokens: ${child.stats.tokens}\ntool_uses: ${toolStatsCounter.total()}\nduration_ms: ${Date.now() - syncStartedAt}</usage>`,
|
|
2474
2578
|
].filter(Boolean);
|
|
2475
2579
|
return {
|
|
2476
2580
|
content: lines.join("\n"),
|
|
@@ -2,7 +2,20 @@ import { DEFAULT_CHARS_PER_TOKEN, estimateContextTokens, estimateTokens } from "
|
|
|
2
2
|
import { isToolResult } from "./message-utils.js";
|
|
3
3
|
const CLEARED_MARKER = "[tool result cleared to save context]";
|
|
4
4
|
const refNote = (ref) => `full text persisted; call ReadToolResult with ref "${ref}" to read it back`;
|
|
5
|
-
const mediaNote = (
|
|
5
|
+
const mediaNote = (blocks) => {
|
|
6
|
+
const byType = new Map();
|
|
7
|
+
for (const b of blocks) {
|
|
8
|
+
const t = b.type ?? "untyped";
|
|
9
|
+
byType.set(t, (byType.get(t) ?? 0) + 1);
|
|
10
|
+
}
|
|
11
|
+
const n = blocks.length;
|
|
12
|
+
if (byType.size === 1) {
|
|
13
|
+
const [t, c] = [...byType][0];
|
|
14
|
+
return `${c} ${t} attachment${c === 1 ? "" : "s"} no longer visible after this clear`;
|
|
15
|
+
}
|
|
16
|
+
const breakdown = [...byType].map(([t, c]) => `${c} ${t}`).join(", ");
|
|
17
|
+
return `${n} attachments (${breakdown}) no longer visible after this clear`;
|
|
18
|
+
};
|
|
6
19
|
const clearedMarker = (notes) => {
|
|
7
20
|
const present = notes.filter((n) => n !== undefined);
|
|
8
21
|
return present.length > 0 ? `[tool result cleared to save context — ${present.join("; ")}]` : CLEARED_MARKER;
|
|
@@ -91,8 +104,8 @@ export function clearStaleToolResults(messages, opts) {
|
|
|
91
104
|
}
|
|
92
105
|
}
|
|
93
106
|
}
|
|
94
|
-
const
|
|
95
|
-
const marker = clearedMarker([ref,
|
|
107
|
+
const mediaBlocks = rawContent.filter((c) => c?.type !== "text");
|
|
108
|
+
const marker = clearedMarker([ref, mediaBlocks.length > 0 ? mediaNote(mediaBlocks) : undefined]);
|
|
96
109
|
const cleared = {
|
|
97
110
|
...out[idx],
|
|
98
111
|
content: [{ type: "text", text: marker }],
|
|
@@ -204,9 +204,18 @@ export class InMemoryFileSnapshotStore {
|
|
|
204
204
|
try {
|
|
205
205
|
for (const hash of new Set(manifest.values())) {
|
|
206
206
|
written.add(hash);
|
|
207
|
+
if (hash === "" || hash === "." || hash === ".." || !/^[A-Za-z0-9_.-]+$/.test(hash)) {
|
|
208
|
+
return { ok: false, error: { code: "read_failed", message: `unsafe blob hash ${JSON.stringify(hash)}` } };
|
|
209
|
+
}
|
|
207
210
|
if (this.blobs.has(hash))
|
|
208
211
|
continue;
|
|
209
|
-
|
|
212
|
+
let bytes;
|
|
213
|
+
try {
|
|
214
|
+
bytes = await srcGetBlob(hash);
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
return { ok: false, error: { code: "read_failed", message: `source blob ${hash} fetch failed: ${err.message}` } };
|
|
218
|
+
}
|
|
210
219
|
if (!bytes)
|
|
211
220
|
return { ok: false, error: { code: "read_failed", message: `missing source blob ${hash}` } };
|
|
212
221
|
if (createHash("sha256").update(bytes).digest("hex") !== hash) {
|
|
@@ -34,11 +34,13 @@ export function mergeInjections(project, personal) {
|
|
|
34
34
|
const announcements = [...(project.announcements ?? []), ...(personal.announcements ?? [])];
|
|
35
35
|
const announceParts = [project.announceBlock, personal.announceBlock].filter((s) => Boolean(s && s.trim()));
|
|
36
36
|
const blockParts = [instruction, ...indexParts, ...announceParts];
|
|
37
|
+
const indexSeed = project.indexSeed ?? personal.indexSeed;
|
|
37
38
|
return {
|
|
38
39
|
instruction,
|
|
39
40
|
...(indexParts.length > 0 ? { index: indexParts.join("\n\n") } : {}),
|
|
40
41
|
...(announcements.length > 0 ? { announcements } : {}),
|
|
41
42
|
...(announceParts.length > 0 ? { announceBlock: announceParts.join("\n\n") } : {}),
|
|
43
|
+
...(indexSeed !== undefined ? { indexSeed } : {}),
|
|
42
44
|
block: blockParts.filter((s) => s && s.trim()).join("\n\n"),
|
|
43
45
|
};
|
|
44
46
|
}
|
|
@@ -162,10 +162,14 @@ export class MemoryEngine {
|
|
|
162
162
|
}
|
|
163
163
|
inject(handle) {
|
|
164
164
|
const instruction = handle.writeScope !== null ? buildMemoryInstruction(handle.writableRoot) : "";
|
|
165
|
-
const
|
|
165
|
+
const indexPath = join(handle.writableRoot, MEMORY_INDEX_FILENAME);
|
|
166
|
+
const onDisk = readSafe(indexPath);
|
|
166
167
|
const indexText = onDisk !== undefined && onDisk.trim() !== "" ? onDisk : handle.indexText;
|
|
167
168
|
const truncated = truncateIndex(indexText);
|
|
168
169
|
const index = composeMemoryBlock(truncated, handle.writeScope ?? handle.scopes[0] ?? "memory");
|
|
170
|
+
const indexSeed = handle.writeScope !== null && onDisk !== undefined && (onDisk.trim() === "" || truncated === onDisk)
|
|
171
|
+
? { path: indexPath, content: onDisk }
|
|
172
|
+
: undefined;
|
|
169
173
|
let announcements;
|
|
170
174
|
let announceBlock;
|
|
171
175
|
try {
|
|
@@ -183,6 +187,7 @@ export class MemoryEngine {
|
|
|
183
187
|
...(index !== undefined ? { index } : {}),
|
|
184
188
|
...(announcements !== undefined ? { announcements } : {}),
|
|
185
189
|
...(announceBlock !== undefined ? { announceBlock } : {}),
|
|
190
|
+
...(indexSeed !== undefined ? { indexSeed } : {}),
|
|
186
191
|
block,
|
|
187
192
|
};
|
|
188
193
|
}
|
|
@@ -12,5 +12,9 @@ export interface PrepareMemoryInput {
|
|
|
12
12
|
export interface PrepareMemoryResult {
|
|
13
13
|
memoryEngineSession: Prepared["memoryEngineSession"];
|
|
14
14
|
memoryBlock: string | undefined;
|
|
15
|
+
seedFiles?: ReadonlyArray<{
|
|
16
|
+
path: string;
|
|
17
|
+
content: string;
|
|
18
|
+
}>;
|
|
15
19
|
}
|
|
16
20
|
export declare function prepareMemory(input: PrepareMemoryInput): Promise<PrepareMemoryResult>;
|
|
@@ -178,6 +178,7 @@ export async function prepareMemory(input) {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
let memoryBlock;
|
|
181
|
+
let seedFiles;
|
|
181
182
|
if (memorySpec && memorySpec.enabled && !useMemoryEngine) {
|
|
182
183
|
const detail = "TaskSpec.memory is enabled but RunnerDeps.memoryBackend is not configured; memory is inactive for this task (the legacy memoryStore fallback was retired in design/138 S4 and the dep was removed in design/157 B19).";
|
|
183
184
|
deps.onError?.(new Error(detail), { phase: "config", sessionId });
|
|
@@ -186,6 +187,8 @@ export async function prepareMemory(input) {
|
|
|
186
187
|
const injection = memoryEngineSession.inject();
|
|
187
188
|
if (injection.block.trim())
|
|
188
189
|
memoryBlock = injection.block;
|
|
190
|
+
if (memoryBlock !== undefined && injection.indexSeed !== undefined)
|
|
191
|
+
seedFiles = [injection.indexSeed];
|
|
189
192
|
}
|
|
190
|
-
return { memoryEngineSession, memoryBlock };
|
|
193
|
+
return { memoryEngineSession, memoryBlock, ...(seedFiles !== undefined ? { seedFiles } : {}) };
|
|
191
194
|
}
|
|
@@ -4,7 +4,7 @@ import { type MaterializedMcp } from "../mcp.js";
|
|
|
4
4
|
import type { HarvestReport, MemorySessionHandle } from "../memory-engine/types.js";
|
|
5
5
|
import { StoredSession } from "../session.js";
|
|
6
6
|
import type { SessionStore } from "../session.js";
|
|
7
|
-
import { SubagentRetainLedger } from "../../agents/
|
|
7
|
+
import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
8
8
|
import type { OnAsk, ToolPolicy } from "../tool-policy.js";
|
|
9
9
|
import { type ActiveSkillFrame } from "./active-skill-scope.js";
|
|
10
10
|
import { type CutKillRegistry } from "./cut-kill.js";
|
|
@@ -114,6 +114,7 @@ export interface Prepared {
|
|
|
114
114
|
apply: (committedArtifactDigest?: string) => void;
|
|
115
115
|
} | undefined;
|
|
116
116
|
activeTools: Set<string>;
|
|
117
|
+
deferredToolNames?: ReadonlySet<string>;
|
|
117
118
|
memoryEngineSession?: {
|
|
118
119
|
engine: MemoryEngine;
|
|
119
120
|
handle: MemorySessionHandle;
|
|
@@ -207,6 +208,12 @@ export interface Prepared {
|
|
|
207
208
|
}>;
|
|
208
209
|
toolsDeltaRef?: {
|
|
209
210
|
pending: string[];
|
|
211
|
+
pendingRemoved: string[];
|
|
212
|
+
pendingReadded: string[];
|
|
213
|
+
pendingFailed: Array<{
|
|
214
|
+
name: string;
|
|
215
|
+
error?: string;
|
|
216
|
+
}>;
|
|
210
217
|
};
|
|
211
218
|
agentListing?: {
|
|
212
219
|
entries: ReadonlyArray<{
|
|
@@ -311,7 +318,7 @@ export interface RunInternals {
|
|
|
311
318
|
parentNotify?: (notification: TaskNotificationPayload, opts?: {
|
|
312
319
|
priority?: import("../task-notification.js").SystemInjectionPriority;
|
|
313
320
|
}) => void;
|
|
314
|
-
parentRetainLedger?: import("../../agents/
|
|
321
|
+
parentRetainLedger?: import("../../agents/retain-ledger.js").SubagentRetainLedger;
|
|
315
322
|
onNotifyInjectorReady?: (inject: (notification: TaskNotificationPayload, opts?: {
|
|
316
323
|
priority?: import("../task-notification.js").SystemInjectionPriority;
|
|
317
324
|
}) => Promise<"queued" | "parked" | "dropped_duplicate">) => void;
|