@cjhyy/code-shell-core 0.9.6 → 0.9.7
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/automation/scheduler.d.ts +3 -0
- package/dist/automation/scheduler.js +21 -0
- package/dist/context/manager.d.ts +8 -2
- package/dist/context/manager.js +20 -4
- package/dist/context/notes.d.ts +39 -0
- package/dist/context/notes.js +314 -0
- package/dist/engine/engine.d.ts +5 -4
- package/dist/engine/engine.js +79 -11
- package/dist/engine/run-tooling.d.ts +3 -0
- package/dist/engine/run-tooling.js +25 -23
- package/dist/engine/run-types.d.ts +4 -0
- package/dist/engine/subagent-spawner.d.ts +3 -0
- package/dist/engine/subagent-spawner.js +48 -17
- package/dist/engine/turn-loop.d.ts +10 -0
- package/dist/engine/turn-loop.js +91 -19
- package/dist/engine/types.d.ts +19 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/prompt/section-loader.js +1 -0
- package/dist/prompt/sections/browser.md +4 -2
- package/dist/prompt/sections/context-notes.md +9 -0
- package/dist/protocol/server.d.ts +1 -0
- package/dist/protocol/server.js +207 -19
- package/dist/protocol/types.d.ts +2 -0
- package/dist/session/session-manager.js +8 -7
- package/dist/session/transcript.d.ts +17 -0
- package/dist/session/transcript.js +271 -16
- package/dist/settings/schema.d.ts +9 -0
- package/dist/settings/schema.js +4 -0
- package/dist/themes/paths.js +20 -1
- package/dist/tool-system/browser-bridge.d.ts +3 -1
- package/dist/tool-system/browser-discovery.d.ts +6 -0
- package/dist/tool-system/browser-discovery.js +17 -0
- package/dist/tool-system/builtin/browser-tools.js +12 -8
- package/dist/tool-system/builtin/context-notes.d.ts +12 -0
- package/dist/tool-system/builtin/context-notes.js +188 -0
- package/dist/tool-system/builtin/index.js +47 -0
- package/dist/tool-system/builtin/mcp-tools.d.ts +5 -3
- package/dist/tool-system/builtin/mcp-tools.js +10 -10
- package/dist/tool-system/builtin/tool-search.js +15 -3
- package/dist/tool-system/context.d.ts +9 -0
- package/dist/tool-system/executor.js +5 -3
- package/dist/tool-system/mcp-compat.d.ts +3 -0
- package/dist/tool-system/mcp-compat.js +51 -0
- package/dist/tool-system/mcp-manager.d.ts +27 -26
- package/dist/tool-system/mcp-manager.js +273 -111
- package/dist/tool-system/mcp-workspace.d.ts +18 -0
- package/dist/tool-system/mcp-workspace.js +56 -0
- package/dist/tool-system/permission.d.ts +6 -0
- package/dist/tool-system/permission.js +45 -9
- package/dist/tool-system/plan-mode-allowlist.js +5 -0
- package/dist/tool-system/sandbox/seatbelt.js +71 -2
- package/dist/tool-system/session-tool-host.js +9 -1
- package/dist/types.d.ts +4 -2
- package/package.json +1 -1
package/dist/engine/engine.js
CHANGED
|
@@ -20,6 +20,8 @@ import { loadPluginHooks } from "../plugins/loadPluginHooks.js";
|
|
|
20
20
|
import { pluginAgentDirs } from "../plugins/installer/loadPluginAgents.js";
|
|
21
21
|
import { runShellHook, shellHookMatches } from "../hooks/shell-runner.js";
|
|
22
22
|
import { ContextManager } from "../context/manager.js";
|
|
23
|
+
import { SessionContextNotes } from "../context/notes.js";
|
|
24
|
+
import { loadSection } from "../prompt/section-loader.js";
|
|
23
25
|
import { CONTEXT_PACKAGE_MAX_OUTPUT_TOKENS, buildAnchoredSummaryMessage, buildContextPackagePromptFromSerialized, estimateTokens, groupMessagesByApiRound, serializeContextPackageMessages, clampContextRatios as clampContextRatiosImpl, } from "../context/compaction.js";
|
|
24
26
|
import { PromptComposer } from "../prompt/composer.js";
|
|
25
27
|
import { SessionManager, isEphemeralSessionState, sessionsRoot, } from "../session/session-manager.js";
|
|
@@ -699,6 +701,9 @@ export class Engine {
|
|
|
699
701
|
setBrowserBridge(bridge) {
|
|
700
702
|
this.config.browserBridge = bridge;
|
|
701
703
|
}
|
|
704
|
+
setChildHostBindings(factory) {
|
|
705
|
+
this.config.createChildHostBindings = factory;
|
|
706
|
+
}
|
|
702
707
|
/** Inject the host-backed workspace bridge after construction. */
|
|
703
708
|
setWorkspaceBridge(bridge) {
|
|
704
709
|
this.config.workspaceBridge = bridge;
|
|
@@ -994,6 +999,12 @@ export class Engine {
|
|
|
994
999
|
}
|
|
995
1000
|
return sessionProfile ?? explicitProfile;
|
|
996
1001
|
}
|
|
1002
|
+
resolveContextStrategy(profile) {
|
|
1003
|
+
return (this.config.contextStrategy ??
|
|
1004
|
+
this.getSettingsManager().get().context?.strategy ??
|
|
1005
|
+
profile?.contextStrategy ??
|
|
1006
|
+
"summary");
|
|
1007
|
+
}
|
|
997
1008
|
async runExclusive(task, options) {
|
|
998
1009
|
// Freeze permission context once, before the first await. Per-turn protocol
|
|
999
1010
|
// overrides live only for this run; persistent setPermissionMode/setPlanMode
|
|
@@ -1125,6 +1136,9 @@ export class Engine {
|
|
|
1125
1136
|
const { messages: openedMessages, freshImageMessage, resumedFromDisk, claimClientMessageId, releaseClientMessageId, } = openedResult.opened;
|
|
1126
1137
|
const session = openedResult.opened.session;
|
|
1127
1138
|
let messages = openedMessages;
|
|
1139
|
+
toolCtx.contextStrategy = this.resolveContextStrategy(profile);
|
|
1140
|
+
const contextNotes = toolCtx.contextStrategy === "notes" ? new SessionContextNotes(session.transcript) : undefined;
|
|
1141
|
+
toolCtx.contextNotes = contextNotes;
|
|
1128
1142
|
// A host-owned time/topic boundary must be applied only after the current
|
|
1129
1143
|
// user message exists in the transcript (it is the exclusive end anchor),
|
|
1130
1144
|
// yet before this turn assembles prompts or calls the model. Doing this as
|
|
@@ -1145,12 +1159,21 @@ export class Engine {
|
|
|
1145
1159
|
: {}),
|
|
1146
1160
|
};
|
|
1147
1161
|
const before = estimateTokens(messages);
|
|
1162
|
+
let archiveStrategy = "range";
|
|
1148
1163
|
if (options.archiveBeforeCurrentTurn.summary) {
|
|
1149
1164
|
await this.appendArchiveMarker(session.state.sessionId, {
|
|
1150
1165
|
...anchors,
|
|
1151
1166
|
summary: options.archiveBeforeCurrentTurn.summary,
|
|
1152
1167
|
});
|
|
1153
1168
|
}
|
|
1169
|
+
else if (!freshImageMessage &&
|
|
1170
|
+
contextNotes &&
|
|
1171
|
+
this.tryNotesRollover(contextNotes, messages)) {
|
|
1172
|
+
// The note covers its own cursor; any newer messages remain verbatim.
|
|
1173
|
+
this.compactedMessagesBySession.delete(session.state.sessionId);
|
|
1174
|
+
session.state.contextUsageAnchor = undefined;
|
|
1175
|
+
archiveStrategy = "notes";
|
|
1176
|
+
}
|
|
1154
1177
|
else {
|
|
1155
1178
|
await this.archiveTurnRange(session.state.sessionId, { start: 0, end: 0 }, anchors);
|
|
1156
1179
|
}
|
|
@@ -1164,7 +1187,7 @@ export class Engine {
|
|
|
1164
1187
|
if (archived.before > archived.after) {
|
|
1165
1188
|
options.onStream?.({
|
|
1166
1189
|
type: "context_compact",
|
|
1167
|
-
strategy:
|
|
1190
|
+
strategy: archiveStrategy,
|
|
1168
1191
|
before: archived.before,
|
|
1169
1192
|
after: archived.after,
|
|
1170
1193
|
});
|
|
@@ -1219,7 +1242,7 @@ export class Engine {
|
|
|
1219
1242
|
sessionProfileOverrides,
|
|
1220
1243
|
runPlanMode,
|
|
1221
1244
|
});
|
|
1222
|
-
const { llmClient, fullSystemPrompt, dynamicContextMsg, userContextMsg } = await this.assembleRunPrompts({
|
|
1245
|
+
const { llmClient, fullSystemPrompt, dynamicContextMsg, userContextMsg, retainedContextMessages, } = await this.assembleRunPrompts({
|
|
1223
1246
|
session,
|
|
1224
1247
|
messages,
|
|
1225
1248
|
hookMessages,
|
|
@@ -1269,6 +1292,7 @@ export class Engine {
|
|
|
1269
1292
|
releaseClientMessageId,
|
|
1270
1293
|
freshImageMessage,
|
|
1271
1294
|
dynamicContextMsg,
|
|
1295
|
+
retainedContextMessages,
|
|
1272
1296
|
});
|
|
1273
1297
|
let result;
|
|
1274
1298
|
let firstGoalTermination;
|
|
@@ -1524,17 +1548,21 @@ export class Engine {
|
|
|
1524
1548
|
promptComposer.buildDynamicContextMessage(),
|
|
1525
1549
|
]);
|
|
1526
1550
|
const fullSystemPrompt = composeRunSystemPrompt({
|
|
1527
|
-
baseSystemPrompt
|
|
1551
|
+
baseSystemPrompt: toolDefs.some((tool) => tool.name === "SaveContextNote")
|
|
1552
|
+
? `${baseSystemPrompt}\n\n${loadSection("context-notes")}`
|
|
1553
|
+
: baseSystemPrompt,
|
|
1528
1554
|
profile,
|
|
1529
1555
|
profileParams,
|
|
1530
1556
|
});
|
|
1531
1557
|
const userContextMsg = promptComposer.buildUserContextMessage();
|
|
1558
|
+
const priorMessages = new Set(messages);
|
|
1532
1559
|
assembleRunMessages({
|
|
1533
1560
|
messages,
|
|
1534
1561
|
userContextMsg,
|
|
1535
1562
|
hookMessages,
|
|
1536
1563
|
dynamicContextMsg,
|
|
1537
1564
|
});
|
|
1565
|
+
const retainedContextMessages = messages.filter((message) => !priorMessages.has(message) && message !== dynamicContextMsg);
|
|
1538
1566
|
this.lastSessionId = session.state.sessionId;
|
|
1539
1567
|
this.lastMessages = messages;
|
|
1540
1568
|
// Wire up LLM summarization for context compaction
|
|
@@ -1550,7 +1578,13 @@ export class Engine {
|
|
|
1550
1578
|
contextManager.initReplacementStateFromMessages(messages);
|
|
1551
1579
|
// Two summarizers with DIFFERENT quality needs (see the run loop wiring for
|
|
1552
1580
|
// the aux-model summarizer set up alongside the ModelFacade).
|
|
1553
|
-
return {
|
|
1581
|
+
return {
|
|
1582
|
+
llmClient,
|
|
1583
|
+
fullSystemPrompt,
|
|
1584
|
+
dynamicContextMsg,
|
|
1585
|
+
userContextMsg,
|
|
1586
|
+
retainedContextMessages,
|
|
1587
|
+
};
|
|
1554
1588
|
}
|
|
1555
1589
|
/**
|
|
1556
1590
|
* Seed the run's {@link ContextManager}, emit the early `session_started`
|
|
@@ -1651,6 +1685,7 @@ export class Engine {
|
|
|
1651
1685
|
// built-ins and product capabilities.
|
|
1652
1686
|
const subAgentSpawner = createSubAgentSpawner({
|
|
1653
1687
|
parentConfig: this.config,
|
|
1688
|
+
getParentSessionId: () => getSession().state.sessionId,
|
|
1654
1689
|
parentSandbox: sandboxConfig,
|
|
1655
1690
|
presetName: this.preset.name,
|
|
1656
1691
|
cwd,
|
|
@@ -1667,7 +1702,12 @@ export class Engine {
|
|
|
1667
1702
|
createChild: (config) => new Engine(config),
|
|
1668
1703
|
runChild: async (config, childTask, childOptions) => {
|
|
1669
1704
|
const child = new Engine(config);
|
|
1670
|
-
|
|
1705
|
+
try {
|
|
1706
|
+
return await child.run(childTask, childOptions);
|
|
1707
|
+
}
|
|
1708
|
+
finally {
|
|
1709
|
+
await child.dispose();
|
|
1710
|
+
}
|
|
1671
1711
|
},
|
|
1672
1712
|
},
|
|
1673
1713
|
});
|
|
@@ -1723,7 +1763,7 @@ export class Engine {
|
|
|
1723
1763
|
* phases consume cross back out.
|
|
1724
1764
|
*/
|
|
1725
1765
|
async wireRunLoop(args) {
|
|
1726
|
-
const { session, sid, task, cwd, options, profileMaxTurns, toolCtx, toolExecutor, contextManager, llmClient, auxSummaryClient, fullSystemPrompt, toolDefs, claimClientMessageId, releaseClientMessageId, freshImageMessage, dynamicContextMsg, } = args;
|
|
1766
|
+
const { session, sid, task, cwd, options, profileMaxTurns, toolCtx, toolExecutor, contextManager, llmClient, auxSummaryClient, fullSystemPrompt, toolDefs, claimClientMessageId, releaseClientMessageId, freshImageMessage, dynamicContextMsg, retainedContextMessages, } = args;
|
|
1727
1767
|
// eslint-disable-next-line prefer-const
|
|
1728
1768
|
let turnLoop;
|
|
1729
1769
|
const accounting = createRunUsageAccounting({
|
|
@@ -1846,6 +1886,7 @@ export class Engine {
|
|
|
1846
1886
|
normalizedGoal,
|
|
1847
1887
|
freshImageMessage,
|
|
1848
1888
|
dynamicContextMsg,
|
|
1889
|
+
retainedContextMessages,
|
|
1849
1890
|
usageBaseline,
|
|
1850
1891
|
getRunUsage,
|
|
1851
1892
|
recordCumulativeUsage,
|
|
@@ -2031,6 +2072,7 @@ export class Engine {
|
|
|
2031
2072
|
const mcpServers = this.config.mcpServers ?? {};
|
|
2032
2073
|
const mcpDisabled = profile?.disableMcp === true;
|
|
2033
2074
|
await connectRunMcp({
|
|
2075
|
+
toolContext: toolCtx,
|
|
2034
2076
|
mcpServers,
|
|
2035
2077
|
mcpDisabled,
|
|
2036
2078
|
getManager: () => this.mcpManager,
|
|
@@ -2094,13 +2136,17 @@ export class Engine {
|
|
|
2094
2136
|
* published through the `publishGoalJudgeContext` callback.
|
|
2095
2137
|
*/
|
|
2096
2138
|
buildTurnLoop(args) {
|
|
2097
|
-
const { modelFacade, toolExecutor, contextManager, session, fullSystemPrompt, toolDefs, sid, options, profileMaxTurns, cwd, claimClientMessageId, releaseClientMessageId, toolCtx, persistedRunGoal, goalHookHandler, normalizedGoal, freshImageMessage, dynamicContextMsg, usageBaseline, getRunUsage, recordCumulativeUsage, publishGoalJudgeContext, } = args;
|
|
2139
|
+
const { modelFacade, toolExecutor, contextManager, session, fullSystemPrompt, toolDefs, sid, options, profileMaxTurns, cwd, claimClientMessageId, releaseClientMessageId, toolCtx, persistedRunGoal, goalHookHandler, normalizedGoal, freshImageMessage, dynamicContextMsg, retainedContextMessages, usageBaseline, getRunUsage, recordCumulativeUsage, publishGoalJudgeContext, } = args;
|
|
2098
2140
|
// Surface compaction events to the UI so the user knows when context was trimmed.
|
|
2099
2141
|
// Buffer the most recent event so TurnLoop can drain it and emit the
|
|
2100
2142
|
// post_compact hook on the next turn (ContextManager itself doesn't
|
|
2101
2143
|
// know about HookRegistry — the buffer is the seam).
|
|
2102
2144
|
let pendingCompactInfo = null;
|
|
2103
2145
|
contextManager.setOnCompact((info) => {
|
|
2146
|
+
if (info.strategy === "notes") {
|
|
2147
|
+
session.state.contextUsageAnchor = undefined;
|
|
2148
|
+
this.compactedMessagesBySession.delete(sid);
|
|
2149
|
+
}
|
|
2104
2150
|
pendingCompactInfo = info;
|
|
2105
2151
|
options?.onStream?.({ type: "context_compact", ...info });
|
|
2106
2152
|
});
|
|
@@ -2109,6 +2155,7 @@ export class Engine {
|
|
|
2109
2155
|
model: modelFacade,
|
|
2110
2156
|
toolExecutor,
|
|
2111
2157
|
contextManager,
|
|
2158
|
+
contextNotes: toolCtx.contextNotes instanceof SessionContextNotes ? toolCtx.contextNotes : undefined,
|
|
2112
2159
|
hooks: this.hooks,
|
|
2113
2160
|
transcript: session.transcript,
|
|
2114
2161
|
systemPrompt: fullSystemPrompt,
|
|
@@ -2228,6 +2275,7 @@ export class Engine {
|
|
|
2228
2275
|
signal: options?.signal,
|
|
2229
2276
|
freshImageMessages: freshImageMessage ? [freshImageMessage] : undefined,
|
|
2230
2277
|
volatileContextMessages: dynamicContextMsg ? [dynamicContextMsg] : undefined,
|
|
2278
|
+
retainedContextMessages,
|
|
2231
2279
|
// Goal mode: the active goal is surfaced to the on_stop handler via
|
|
2232
2280
|
// ctx.data.goal; the GoalStopHook (registered above) judges it.
|
|
2233
2281
|
goal: normalizedGoal,
|
|
@@ -2849,10 +2897,17 @@ export class Engine {
|
|
|
2849
2897
|
cached.push({ role: "assistant", content });
|
|
2850
2898
|
}
|
|
2851
2899
|
}
|
|
2852
|
-
/**
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2900
|
+
/** Attempt the durable note path without issuing a separate model request. */
|
|
2901
|
+
tryNotesRollover(notes, messages) {
|
|
2902
|
+
try {
|
|
2903
|
+
notes.requestRollover();
|
|
2904
|
+
return notes.applyRollover(messages);
|
|
2905
|
+
}
|
|
2906
|
+
catch {
|
|
2907
|
+
return undefined;
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
/** Force context compaction and return token stats before/after. */
|
|
2856
2911
|
async forceCompact(sessionId) {
|
|
2857
2912
|
const effectiveSessionId = sessionId ?? this.lastSessionId;
|
|
2858
2913
|
if (!effectiveSessionId) {
|
|
@@ -2861,6 +2916,19 @@ export class Engine {
|
|
|
2861
2916
|
const session = this.sessionManager.resume(effectiveSessionId);
|
|
2862
2917
|
const sourceMessages = this.compactedMessagesBySession.get(effectiveSessionId) ?? session.transcript.toMessages();
|
|
2863
2918
|
const before = estimateTokens(sourceMessages);
|
|
2919
|
+
const profile = [...this.behaviorProfiles.values()].find((candidate) => candidate.activateForSessionKinds?.includes(session.state.kind ?? "work"));
|
|
2920
|
+
if (this.resolveContextStrategy(profile) === "notes") {
|
|
2921
|
+
const compacted = this.tryNotesRollover(new SessionContextNotes(session.transcript), sourceMessages);
|
|
2922
|
+
if (compacted) {
|
|
2923
|
+
this.compactedMessagesBySession.set(effectiveSessionId, compacted);
|
|
2924
|
+
session.state.contextUsageAnchor = undefined;
|
|
2925
|
+
this.lastContextManager = undefined;
|
|
2926
|
+
this.persistRunProgress(session.state);
|
|
2927
|
+
this.lastSessionId = effectiveSessionId;
|
|
2928
|
+
this.lastMessages = compacted;
|
|
2929
|
+
return { before, after: estimateTokens(compacted), strategy: "notes" };
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2864
2932
|
const contextManager = await this.prepareContextManagerForSession(effectiveSessionId, session, sourceMessages, "force_compact");
|
|
2865
2933
|
// Manual /compact emits its UI boundary at the protocol layer from the
|
|
2866
2934
|
// final before/after result. Capture the tier here, but avoid reusing a
|
|
@@ -64,6 +64,7 @@ export declare function connectRunMcp(args: {
|
|
|
64
64
|
runtimePool: MCPManager | undefined;
|
|
65
65
|
toolRegistry: ToolRegistry;
|
|
66
66
|
engineForConnect: Parameters<MCPManager["connectAll"]>[1];
|
|
67
|
+
toolContext?: Parameters<MCPManager["connectAll"]>[3];
|
|
67
68
|
emitNotificationHook: (payload: Record<string, unknown>) => void;
|
|
68
69
|
}): Promise<void>;
|
|
69
70
|
/**
|
|
@@ -74,12 +75,14 @@ export declare function connectRunMcp(args: {
|
|
|
74
75
|
*/
|
|
75
76
|
export interface ToolVisibilityInputs {
|
|
76
77
|
cwd: string;
|
|
78
|
+
contextStrategy?: "summary" | "notes";
|
|
77
79
|
workspace?: ToolContext["workspace"];
|
|
78
80
|
hasGoal: boolean;
|
|
79
81
|
sessionId?: string;
|
|
80
82
|
settingsScope?: SettingsScope;
|
|
81
83
|
/** Host identity a guard may require, e.g. Panel needs `"desktop"`. */
|
|
82
84
|
host?: string;
|
|
85
|
+
hasBrowserAutomation?: boolean;
|
|
83
86
|
isSubAgent?: boolean;
|
|
84
87
|
behaviorProfile?: string;
|
|
85
88
|
sessionMessageTargets?: readonly import("../session/session-message.js").SessionMessageTarget[];
|
|
@@ -126,30 +126,26 @@ export function buildRunPermissionPipeline(args) {
|
|
|
126
126
|
}
|
|
127
127
|
/** engine.ts L1986-2003 —— MCP 连接(Runtime 池优先,回退 per-Engine)。 */
|
|
128
128
|
export async function connectRunMcp(args) {
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const mcpDisabled = args.mcpDisabled;
|
|
136
|
-
if (!mcpDisabled && Object.keys(mcpServers).length > 0 && !args.getManager()) {
|
|
137
|
-
if (args.runtimePool) {
|
|
138
|
-
args.setManager(args.runtimePool);
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
args.setManager(new MCPManager(args.toolRegistry));
|
|
142
|
-
}
|
|
143
|
-
await args.getManager().connectAll(mcpServers, args.engineForConnect, (event) => {
|
|
144
|
-
// Fire-and-forget onto the notification hook: hosts/plugins learn
|
|
145
|
-
// about MCP availability changes without polling the pool.
|
|
146
|
-
args.emitNotificationHook({
|
|
147
|
-
kind: event.type,
|
|
148
|
-
server: event.server,
|
|
149
|
-
...(event.error ? { error: event.error } : {}),
|
|
150
|
-
});
|
|
151
|
-
});
|
|
129
|
+
// A pool may outlive a run and the engine registry may predate discovery.
|
|
130
|
+
// Refresh scope and the engine's MCP-only view on every run (including cwd
|
|
131
|
+
// and project changes); identical scopes still share a single transport.
|
|
132
|
+
const enabledServers = args.mcpDisabled ? {} : args.mcpServers;
|
|
133
|
+
if (!args.getManager() && Object.keys(enabledServers).length > 0) {
|
|
134
|
+
args.setManager(args.runtimePool ?? new MCPManager(args.toolRegistry));
|
|
152
135
|
}
|
|
136
|
+
const manager = args.getManager();
|
|
137
|
+
if (!manager)
|
|
138
|
+
return;
|
|
139
|
+
await manager.connectAll(enabledServers, args.engineForConnect, (event) => {
|
|
140
|
+
args.emitNotificationHook({
|
|
141
|
+
kind: event.type,
|
|
142
|
+
server: event.server,
|
|
143
|
+
...(event.error ? { error: event.error } : {}),
|
|
144
|
+
});
|
|
145
|
+
}, args.toolContext);
|
|
146
|
+
manager.syncToolsToRegistry(args.toolRegistry, args.toolContext, new Set(Object.entries(enabledServers)
|
|
147
|
+
.filter(([, config]) => config.enabled !== false)
|
|
148
|
+
.map(([name]) => name)));
|
|
153
149
|
}
|
|
154
150
|
/**
|
|
155
151
|
* Build the availability-guard context for a tool surface.
|
|
@@ -167,11 +163,15 @@ export async function connectRunMcp(args) {
|
|
|
167
163
|
export function buildToolVisibility(inputs) {
|
|
168
164
|
return {
|
|
169
165
|
cwd: inputs.cwd,
|
|
166
|
+
...(inputs.contextStrategy ? { contextStrategy: inputs.contextStrategy } : {}),
|
|
170
167
|
...(inputs.workspace ? { workspace: inputs.workspace } : {}),
|
|
171
168
|
hasGoal: inputs.hasGoal,
|
|
172
169
|
...(inputs.sessionId ? { sessionId: inputs.sessionId } : {}),
|
|
173
170
|
...(inputs.settingsScope ? { settingsScope: inputs.settingsScope } : {}),
|
|
174
171
|
...(inputs.host !== undefined ? { host: inputs.host } : {}),
|
|
172
|
+
...(inputs.hasBrowserAutomation !== undefined
|
|
173
|
+
? { hasBrowserAutomation: inputs.hasBrowserAutomation }
|
|
174
|
+
: {}),
|
|
175
175
|
...(inputs.isSubAgent !== undefined ? { isSubAgent: inputs.isSubAgent } : {}),
|
|
176
176
|
...(inputs.behaviorProfile !== undefined ? { behaviorProfile: inputs.behaviorProfile } : {}),
|
|
177
177
|
...(inputs.sessionMessageTargets?.length
|
|
@@ -188,11 +188,13 @@ export function assembleRunToolDefs(args) {
|
|
|
188
188
|
const guardCwd = args.guardCwd;
|
|
189
189
|
const toolVisibility = buildToolVisibility({
|
|
190
190
|
cwd: guardCwd,
|
|
191
|
+
contextStrategy: toolCtx.contextNotes ? toolCtx.contextStrategy : undefined,
|
|
191
192
|
workspace: toolCtx.workspace,
|
|
192
193
|
hasGoal: args.hasRunnableGoal,
|
|
193
194
|
sessionId: toolCtx.sessionId,
|
|
194
195
|
settingsScope: args.settingsScope,
|
|
195
196
|
host: args.builtinToolHost,
|
|
197
|
+
hasBrowserAutomation: toolCtx.browser !== undefined,
|
|
196
198
|
isSubAgent: args.isSubAgent,
|
|
197
199
|
behaviorProfile: args.behaviorProfileId,
|
|
198
200
|
sessionMessageTargets: toolCtx.sessionMessages?.targets,
|
|
@@ -22,6 +22,8 @@ export type RunBehaviorMode = string;
|
|
|
22
22
|
*/
|
|
23
23
|
export interface RunBehaviorProfile {
|
|
24
24
|
id: string;
|
|
25
|
+
/** Default context policy; explicit Engine/settings choices take precedence. */
|
|
26
|
+
contextStrategy?: "summary" | "notes";
|
|
25
27
|
/** Appended to the system prompt after config.appendSystemPrompt. */
|
|
26
28
|
systemPromptAppend?: string;
|
|
27
29
|
/** Hard tool allowlist for the run (model visibility + execution gate). */
|
|
@@ -186,6 +188,8 @@ export type AgentRuntimeProgressEvent = {
|
|
|
186
188
|
usage: TokenUsage;
|
|
187
189
|
};
|
|
188
190
|
export interface ChildEngineRuntime {
|
|
191
|
+
/** Release child-local resources after the entire supervised lifetime settles. */
|
|
192
|
+
dispose?(): void | Promise<void>;
|
|
189
193
|
run(task: string, options?: EngineRunOptions): Promise<EngineResult>;
|
|
190
194
|
setAgentControlStateListener(listener: ((state: LiveChildState) => void) | undefined): void;
|
|
191
195
|
setAgentDirectionsDeliveredListener?(listener: ((envelopeIds: string[]) => void) | undefined): void;
|
|
@@ -17,6 +17,9 @@ export declare function resolveChildMcpServers(allowlist: string[] | undefined,
|
|
|
17
17
|
export declare function wrapChildStream(destination: StreamCallback | undefined, agentId: string): StreamCallback | undefined;
|
|
18
18
|
export interface CreateSubAgentSpawnerDeps {
|
|
19
19
|
parentConfig: EngineConfig;
|
|
20
|
+
parentSessionId?: string;
|
|
21
|
+
/** Tool context is assembled before the run session is initialized. */
|
|
22
|
+
getParentSessionId?: () => string;
|
|
20
23
|
/** Fully resolved sandbox for this parent run, including settings layers. */
|
|
21
24
|
parentSandbox: SandboxConfig;
|
|
22
25
|
presetName: AgentPresetName;
|
|
@@ -252,7 +252,11 @@ export function createSubAgentSpawner(deps) {
|
|
|
252
252
|
maxContextTokens: deps.parentConfig.maxContextTokens ?? 200_000,
|
|
253
253
|
sessionStorageDir: deps.parentConfig.sessionStorageDir,
|
|
254
254
|
headless: deps.parentConfig.headless,
|
|
255
|
-
readOnlySession: request.readOnlySession,
|
|
255
|
+
readOnlySession: deps.parentConfig.readOnlySession || request.readOnlySession,
|
|
256
|
+
projectTrusted: deps.parentConfig.projectTrusted,
|
|
257
|
+
allowBackgroundShells: deps.parentConfig.allowBackgroundShells,
|
|
258
|
+
approvalBackend: deps.parentConfig.approvalBackend,
|
|
259
|
+
approvalRouter: deps.parentConfig.approvalRouter,
|
|
256
260
|
skillAllowlist: request.skillAllowlist,
|
|
257
261
|
sandbox: resolveChildSandbox(request.sandboxMode, deps.parentSandbox),
|
|
258
262
|
mcpServers: resolveChildMcpServers(request.mcpAllowlist, deps.parentConfig.mcpServers),
|
|
@@ -261,28 +265,55 @@ export function createSubAgentSpawner(deps) {
|
|
|
261
265
|
};
|
|
262
266
|
const destination = request.streamOverride ?? deps.parentStream;
|
|
263
267
|
const childSessionId = request.resumeSessionId ?? request.agentId;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
268
|
+
const parentSessionId = deps.getParentSessionId?.() ?? deps.parentSessionId;
|
|
269
|
+
const host = parentSessionId
|
|
270
|
+
? deps.parentConfig.createChildHostBindings?.({
|
|
271
|
+
parentSessionId,
|
|
272
|
+
sessionId: childSessionId,
|
|
273
|
+
signal: request.signal,
|
|
274
|
+
})
|
|
275
|
+
: undefined;
|
|
276
|
+
if (host) {
|
|
277
|
+
childConfig.browserBridge = host.browserBridge;
|
|
278
|
+
childConfig.askUser = host.askUser;
|
|
279
|
+
childConfig.injectCredentialToBrowser = host.injectCredentialToBrowser;
|
|
280
|
+
childConfig.approvalBackend = host.approvalBackend ?? childConfig.approvalBackend;
|
|
281
|
+
}
|
|
282
|
+
let runtime;
|
|
283
|
+
try {
|
|
284
|
+
if (deps.childRunner.createChild && request.runtimeGeneration !== undefined) {
|
|
285
|
+
runtime = deps.childRunner.createChild(childConfig);
|
|
286
|
+
const supervisor = new ChildRunSupervisor(runtime, childSessionId, request.runtimeGeneration);
|
|
287
|
+
const ownerToken = nanoid();
|
|
288
|
+
const bound = request.bindLiveControl?.(supervisor, ownerToken);
|
|
289
|
+
supervisor.setCloseIntake(() => request.closeLiveControl?.(supervisor, bound && bound !== true ? bound : undefined));
|
|
290
|
+
if (bound === false) {
|
|
291
|
+
throw new Error(`child session ${childSessionId} already has a live transcript writer`);
|
|
292
|
+
}
|
|
293
|
+
host?.activate?.();
|
|
294
|
+
return await supervisor.run(request.prompt, {
|
|
295
|
+
signal: request.signal,
|
|
296
|
+
onStream: wrapChildRuntimeStream(destination, request.agentId, request.onProgressEvent),
|
|
297
|
+
sessionId: childSessionId,
|
|
298
|
+
onAgentProgress: request.onAgentProgress,
|
|
299
|
+
});
|
|
272
300
|
}
|
|
273
|
-
|
|
301
|
+
host?.activate?.();
|
|
302
|
+
const result = await deps.childRunner.runChild(childConfig, request.prompt, {
|
|
274
303
|
signal: request.signal,
|
|
275
304
|
onStream: wrapChildRuntimeStream(destination, request.agentId, request.onProgressEvent),
|
|
276
305
|
sessionId: childSessionId,
|
|
277
|
-
onAgentProgress: request.onAgentProgress,
|
|
278
306
|
});
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
try {
|
|
311
|
+
host?.dispose();
|
|
312
|
+
}
|
|
313
|
+
finally {
|
|
314
|
+
await runtime?.dispose?.();
|
|
315
|
+
}
|
|
279
316
|
}
|
|
280
|
-
const result = await deps.childRunner.runChild(childConfig, request.prompt, {
|
|
281
|
-
signal: request.signal,
|
|
282
|
-
onStream: wrapChildRuntimeStream(destination, request.agentId, request.onProgressEvent),
|
|
283
|
-
sessionId: childSessionId,
|
|
284
|
-
});
|
|
285
|
-
return result;
|
|
286
317
|
},
|
|
287
318
|
};
|
|
288
319
|
}
|
|
@@ -11,6 +11,7 @@ import type { LiveChildState } from "../tool-system/builtin/agent-registry.js";
|
|
|
11
11
|
import { ModelFacade } from "./model-facade.js";
|
|
12
12
|
import { ToolExecutor } from "../tool-system/executor.js";
|
|
13
13
|
import { ContextManager } from "../context/manager.js";
|
|
14
|
+
import type { SessionContextNotes } from "../context/notes.js";
|
|
14
15
|
import { HookRegistry } from "../hooks/registry.js";
|
|
15
16
|
import type { GoalJudgeRuntimeContext } from "../hooks/goal-stop-hook.js";
|
|
16
17
|
import type { PromptCacheDiagnosticSample } from "./prompt-cache-diagnostics.js";
|
|
@@ -34,6 +35,8 @@ export interface TurnLoopConfig {
|
|
|
34
35
|
* summarized into durable history. Engine uses this for dynamicContext.
|
|
35
36
|
*/
|
|
36
37
|
volatileContextMessages?: Iterable<Message>;
|
|
38
|
+
/** Current instructions and lifecycle hooks, reattached after a notes transition. */
|
|
39
|
+
retainedContextMessages?: Iterable<Message>;
|
|
37
40
|
/**
|
|
38
41
|
* Fired after each turn boundary is recorded. Lets the engine flush an
|
|
39
42
|
* up-to-date snapshot to state.json mid-run, so a long run doesn't leave
|
|
@@ -66,6 +69,7 @@ export interface TurnLoopDeps {
|
|
|
66
69
|
model: ModelFacade;
|
|
67
70
|
toolExecutor: ToolExecutor;
|
|
68
71
|
contextManager: ContextManager;
|
|
72
|
+
contextNotes?: SessionContextNotes;
|
|
69
73
|
hooks: HookRegistry;
|
|
70
74
|
transcript: Transcript;
|
|
71
75
|
systemPrompt: string;
|
|
@@ -295,6 +299,12 @@ export declare class TurnLoop {
|
|
|
295
299
|
* to the tail when that boundary was rewritten or compaction changed length.
|
|
296
300
|
*/
|
|
297
301
|
private restoreVolatileAfterContextManagement;
|
|
302
|
+
private noteReminderIssued;
|
|
303
|
+
private readonly retainedHookMessages;
|
|
304
|
+
private readonly pendingContextReminders;
|
|
305
|
+
private appendContextReminder;
|
|
306
|
+
/** Called only before a model step or after every result in a tool batch is recorded. */
|
|
307
|
+
private applyNotesRollover;
|
|
298
308
|
private manageContextMessages;
|
|
299
309
|
private manageContextMessagesSync;
|
|
300
310
|
private markPendingImagesConsumed;
|