@cydm/pie 2.6.6 → 2.6.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/builtin/extensions/ask-user/index.js +3 -3
- package/dist/builtin/extensions/mcp-adapter/index.js +93 -38
- package/dist/builtin/extensions/plan-mode/index.js +3 -3
- package/dist/builtin/extensions/subagent/index.js +362 -6
- package/dist/builtin/extensions/todo/index.js +3 -3
- package/dist/builtin/skills/chrome-devtools-axi/chrome-devtools-axi-runtime.js +1 -1
- package/dist/builtin/skills/chrome-devtools-axi/{cli-QAILYGLM.js → cli-4ITIR57L.js} +31 -2
- package/dist/chunks/{chunk-G5B6D35K.js → chunk-7B4IIBVS.js} +10 -1
- package/dist/chunks/{chunk-KEARZX6F.js → chunk-PAC62XU4.js} +1 -1
- package/dist/chunks/{chunk-H6BAM2N4.js → chunk-T6GMEDIE.js} +476 -69
- package/dist/chunks/{chunk-OWSXALCG.js → chunk-VUKF7U7Q.js} +1 -1
- package/dist/chunks/{src-ZJXBS2DB.js → src-HPIBCE2I.js} +2 -2
- package/dist/chunks/{test-stream-TI2S25NY.js → test-stream-Y7WXELAJ.js} +1 -1
- package/dist/cli.js +2475 -2469
- package/package.json +2 -2
|
@@ -2,7 +2,7 @@ import { createRequire as __createRequire } from "node:module"; const require =
|
|
|
2
2
|
import {
|
|
3
3
|
Agent,
|
|
4
4
|
getToolCallerIdentity
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PAC62XU4.js";
|
|
6
6
|
import {
|
|
7
7
|
AssistantMessageEventStream,
|
|
8
8
|
FileSystemGateway,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
getPlatformConfig,
|
|
13
13
|
hashText,
|
|
14
14
|
streamSimple
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-7B4IIBVS.js";
|
|
16
16
|
import {
|
|
17
17
|
Type
|
|
18
18
|
} from "./chunk-XXGYUYXI.js";
|
|
@@ -1138,6 +1138,74 @@ function withSubagentResponseRecovery(stream, backoffMs = 2e3) {
|
|
|
1138
1138
|
};
|
|
1139
1139
|
}
|
|
1140
1140
|
|
|
1141
|
+
// ../../packages/shared-headless-capabilities/src/subagent-sessions.ts
|
|
1142
|
+
var SUBAGENT_SESSION_RECORD_VERSION = 1;
|
|
1143
|
+
var DEFAULT_MAX_LIVE_SESSIONS = 8;
|
|
1144
|
+
var DEFAULT_SESSION_STORE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1145
|
+
var DEFAULT_SESSION_STORE_MAX_RECORDS = 64;
|
|
1146
|
+
var SESSION_TRANSCRIPT_WARN_CHARS = 24e4;
|
|
1147
|
+
var SESSION_RECORD_MAX_CHARS = 4e6;
|
|
1148
|
+
function touchLiveSession(registry, id) {
|
|
1149
|
+
const session = registry.get(id);
|
|
1150
|
+
if (!session) return void 0;
|
|
1151
|
+
registry.delete(id);
|
|
1152
|
+
registry.set(id, session);
|
|
1153
|
+
return session;
|
|
1154
|
+
}
|
|
1155
|
+
function retainLiveSession(registry, session, cap) {
|
|
1156
|
+
registry.delete(session.id);
|
|
1157
|
+
registry.set(session.id, session);
|
|
1158
|
+
while (registry.size > Math.max(1, cap)) {
|
|
1159
|
+
let evicted = false;
|
|
1160
|
+
for (const [key, candidate] of registry) {
|
|
1161
|
+
if (candidate.busy) continue;
|
|
1162
|
+
registry.delete(key);
|
|
1163
|
+
evicted = true;
|
|
1164
|
+
break;
|
|
1165
|
+
}
|
|
1166
|
+
if (!evicted) {
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function toSessionRecord(session, messages) {
|
|
1172
|
+
return {
|
|
1173
|
+
version: SUBAGENT_SESSION_RECORD_VERSION,
|
|
1174
|
+
id: session.id,
|
|
1175
|
+
name: session.name,
|
|
1176
|
+
mode: session.mode,
|
|
1177
|
+
priority: session.priority,
|
|
1178
|
+
originalTask: session.originalTask,
|
|
1179
|
+
modelOverride: session.modelOverride,
|
|
1180
|
+
thinkingLevel: session.thinkingLevel,
|
|
1181
|
+
timeoutSeconds: session.timeoutSeconds,
|
|
1182
|
+
createdAt: session.createdAt,
|
|
1183
|
+
lastUsedAt: session.lastUsedAt,
|
|
1184
|
+
runCount: session.runCount,
|
|
1185
|
+
revision: session.revision,
|
|
1186
|
+
messages
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
function estimateTranscriptChars(messages) {
|
|
1190
|
+
let total = 0;
|
|
1191
|
+
for (const message of messages) {
|
|
1192
|
+
try {
|
|
1193
|
+
total += JSON.stringify(message)?.length ?? 0;
|
|
1194
|
+
} catch {
|
|
1195
|
+
total += SESSION_TRANSCRIPT_WARN_CHARS;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return total;
|
|
1199
|
+
}
|
|
1200
|
+
function transplantMessages(target, messages) {
|
|
1201
|
+
const mutable = target;
|
|
1202
|
+
if (typeof mutable.replaceMessages === "function") {
|
|
1203
|
+
mutable.replaceMessages(messages);
|
|
1204
|
+
} else {
|
|
1205
|
+
mutable.state.messages = messages;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1141
1209
|
// ../../packages/shared-headless-capabilities/src/bash-policy.ts
|
|
1142
1210
|
var DESTRUCTIVE_PATTERNS = [
|
|
1143
1211
|
{ pattern: /\bgit\s+reset\s+--hard\b/, warning: "may discard uncommitted changes", commandClass: "git_destructive" },
|
|
@@ -1623,6 +1691,23 @@ function buildSubagentPlan(deps, task, name, priority, timeoutSeconds, modelOver
|
|
|
1623
1691
|
function subagentOutputBudget(model) {
|
|
1624
1692
|
return Math.min(model.maxTokens || 8192, 16384);
|
|
1625
1693
|
}
|
|
1694
|
+
function buildSubagentSystemPrompt(mode, skills) {
|
|
1695
|
+
return `You are a specialized subagent focused on one bounded task.
|
|
1696
|
+
|
|
1697
|
+
Task boundary:
|
|
1698
|
+
- Work only on the assigned task.
|
|
1699
|
+
- Use available tools to gather evidence before making claims.
|
|
1700
|
+
${mode === "edit" ? "- You may edit files and run tests using the inherited parent tools and permissions. This is a shared workspace, not an isolated sandbox. Preserve existing user changes. Do not commit or automatically roll back changes. Report changed files, tests actually run, and remaining work for parent review." : "- Do not edit files; if writing is needed, say what the parent agent should change."}
|
|
1701
|
+
- Return a concise answer with evidence and uncertainty.
|
|
1702
|
+
|
|
1703
|
+
Output:
|
|
1704
|
+
- Answer
|
|
1705
|
+
- Evidence
|
|
1706
|
+
- Uncertainty: low, medium, or high
|
|
1707
|
+
- Parent follow-up needed, if any
|
|
1708
|
+
|
|
1709
|
+
${formatSkillsForPrompt(skills ?? [])}`;
|
|
1710
|
+
}
|
|
1626
1711
|
function createAgentInstance(deps, systemPrompt, tools, model, apiKey, thinkingLevel) {
|
|
1627
1712
|
const streamFn = withSubagentResponseRecovery((streamModel, context, options) => streamSimple(streamModel, context, {
|
|
1628
1713
|
...options,
|
|
@@ -1692,7 +1777,114 @@ function inferUncertainty(text, signals) {
|
|
|
1692
1777
|
function isParentHandoffRequired(task) {
|
|
1693
1778
|
return task.mode === "edit" || task.needsParentWrite || task.status === "timeout" || task.status === "failed" || task.confidenceSignals.some((signal) => signal.type === "tool_error" || signal.type === "no_tool_use" || signal.type === "no_text_report");
|
|
1694
1779
|
}
|
|
1695
|
-
|
|
1780
|
+
function shellFromRecord(record) {
|
|
1781
|
+
return {
|
|
1782
|
+
id: record.id,
|
|
1783
|
+
name: record.name,
|
|
1784
|
+
mode: record.mode,
|
|
1785
|
+
priority: record.priority,
|
|
1786
|
+
originalTask: record.originalTask,
|
|
1787
|
+
modelOverride: record.modelOverride,
|
|
1788
|
+
thinkingLevel: record.thinkingLevel,
|
|
1789
|
+
timeoutSeconds: record.timeoutSeconds,
|
|
1790
|
+
agent: null,
|
|
1791
|
+
pendingMessages: record.messages,
|
|
1792
|
+
createdAt: record.createdAt,
|
|
1793
|
+
lastUsedAt: record.lastUsedAt,
|
|
1794
|
+
runCount: record.runCount,
|
|
1795
|
+
// The restored record IS our durable base line: its revision (0 for
|
|
1796
|
+
// legacy records that predate the field) is both our current revision
|
|
1797
|
+
// and our known-persisted revision.
|
|
1798
|
+
revision: record.revision ?? 0,
|
|
1799
|
+
persistedRevision: record.revision ?? 0,
|
|
1800
|
+
busy: true
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
async function resolveRecallSession(deps, id, sessions, maxLiveSessions) {
|
|
1804
|
+
let live = sessions ? touchLiveSession(sessions, id) : void 0;
|
|
1805
|
+
if (live?.busy) throw new Error(`Subagent session "${id}" is busy with another task; wait for it to settle.`);
|
|
1806
|
+
let record;
|
|
1807
|
+
if (deps.sessionStore) {
|
|
1808
|
+
try {
|
|
1809
|
+
record = await deps.sessionStore.load(id);
|
|
1810
|
+
} catch (error) {
|
|
1811
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1812
|
+
deps.debugLog?.("session", "session store load failed", { id, error: detail });
|
|
1813
|
+
throw new Error(`Subagent session "${id}" could not be read from the session store (${detail}); refusing to recall without verifying session freshness. Retry the recall, or spawn a fresh task.`);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
if (record && typeof record.version === "number" && record.version !== SUBAGENT_SESSION_RECORD_VERSION) {
|
|
1817
|
+
throw new Error(`Subagent session "${id}" is persisted in record format version ${record.version}, which this build cannot read (supported: ${SUBAGENT_SESSION_RECORD_VERSION}); refusing to recall so the newer record is not overwritten. Use the matching Pie version, or spawn a fresh task.`);
|
|
1818
|
+
}
|
|
1819
|
+
if (record && (record.version !== SUBAGENT_SESSION_RECORD_VERSION || record.id !== id || !Array.isArray(record.messages))) {
|
|
1820
|
+
record = void 0;
|
|
1821
|
+
}
|
|
1822
|
+
live = sessions ? touchLiveSession(sessions, id) : void 0;
|
|
1823
|
+
if (live?.busy) throw new Error(`Subagent session "${id}" is busy with another task; wait for it to settle.`);
|
|
1824
|
+
if (live) {
|
|
1825
|
+
const liveTranscriptLength = live.agent?.state.messages.length ?? live.pendingMessages?.length ?? 0;
|
|
1826
|
+
const recordIsNewer = record !== void 0 && (record.revision !== void 0 ? record.revision > live.revision : record.runCount > live.runCount || record.runCount === live.runCount && record.lastUsedAt > live.lastUsedAt || record.runCount === live.runCount && record.lastUsedAt === live.lastUsedAt && record.messages.length > liveTranscriptLength);
|
|
1827
|
+
const forked = record?.revision !== void 0 && record.revision > (live.persistedRevision ?? 0) && live.revision > (live.persistedRevision ?? 0);
|
|
1828
|
+
if (forked && record?.revision !== void 0) {
|
|
1829
|
+
deps.debugLog?.("session", "live and durable sessions diverged after failed persistence", {
|
|
1830
|
+
id,
|
|
1831
|
+
liveRevision: live.revision,
|
|
1832
|
+
persistedRevision: record.revision,
|
|
1833
|
+
lastSharedRevision: live.persistedRevision
|
|
1834
|
+
});
|
|
1835
|
+
sessions?.delete(id);
|
|
1836
|
+
throw new Error(`Subagent session "${id}" diverged across processes: this process holds unsaved work (revision ${live.revision}, last durable sync ${live.persistedRevision ?? 0}) while another process advanced the stored record (revision ${record.revision}). The diverged in-memory branch was discarded; recall again to continue from the last durable transcript, or spawn a fresh task.`);
|
|
1837
|
+
}
|
|
1838
|
+
if (recordIsNewer && record) {
|
|
1839
|
+
deps.debugLog?.("session", "live session behind the durable record; adopting the persisted record", {
|
|
1840
|
+
id,
|
|
1841
|
+
liveMessages: liveTranscriptLength,
|
|
1842
|
+
persistedMessages: record.messages.length,
|
|
1843
|
+
liveRevision: live.revision,
|
|
1844
|
+
persistedRevision: record.revision,
|
|
1845
|
+
liveRunCount: live.runCount,
|
|
1846
|
+
persistedRunCount: record.runCount,
|
|
1847
|
+
liveLastUsedAt: live.lastUsedAt,
|
|
1848
|
+
persistedLastUsedAt: record.lastUsedAt
|
|
1849
|
+
});
|
|
1850
|
+
const shell2 = shellFromRecord(record);
|
|
1851
|
+
if (sessions) {
|
|
1852
|
+
retainLiveSession(sessions, shell2, maxLiveSessions);
|
|
1853
|
+
}
|
|
1854
|
+
return { session: shell2, restored: true };
|
|
1855
|
+
}
|
|
1856
|
+
live.busy = true;
|
|
1857
|
+
return { session: live, restored: false };
|
|
1858
|
+
}
|
|
1859
|
+
if (!record) return void 0;
|
|
1860
|
+
const shell = shellFromRecord(record);
|
|
1861
|
+
if (sessions) {
|
|
1862
|
+
retainLiveSession(sessions, shell, maxLiveSessions);
|
|
1863
|
+
}
|
|
1864
|
+
return { session: shell, restored: true };
|
|
1865
|
+
}
|
|
1866
|
+
async function persistSubagentSession(deps, session, task) {
|
|
1867
|
+
const messages = session.agent?.state.messages ?? session.pendingMessages ?? [];
|
|
1868
|
+
const transcriptChars = estimateTranscriptChars(messages);
|
|
1869
|
+
if (task.status === "completed" && task.result && transcriptChars > SESSION_TRANSCRIPT_WARN_CHARS) {
|
|
1870
|
+
task.result += `
|
|
1871
|
+
|
|
1872
|
+
[Session growth warning: session ${session.id} now carries ~${Math.round(transcriptChars / 4)} tokens of transcript; every recall pays for the whole history. For the next round, consider a fresh subagent with a compact brief instead of recalling.]`;
|
|
1873
|
+
}
|
|
1874
|
+
if (!deps.sessionStore) return;
|
|
1875
|
+
if (transcriptChars > SESSION_RECORD_MAX_CHARS) {
|
|
1876
|
+
deps.debugLog?.("session", "skipping session persist: transcript over the size cap", { id: session.id, transcriptChars });
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
try {
|
|
1880
|
+
const saved = await deps.sessionStore.save(toSessionRecord(session, messages));
|
|
1881
|
+
task.sessionPersisted = saved === true;
|
|
1882
|
+
if (saved === true) session.persistedRevision = session.revision;
|
|
1883
|
+
} catch (error) {
|
|
1884
|
+
deps.debugLog?.("session", "session persist failed", { id: session.id, error: error instanceof Error ? error.message : String(error) });
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
async function runSubagentWithProgress(deps, task, reportProgress, signal, sessions) {
|
|
1696
1888
|
const startTime = Date.now();
|
|
1697
1889
|
task.status = "running";
|
|
1698
1890
|
let timedOut = false;
|
|
@@ -1706,6 +1898,11 @@ async function runSubagentWithProgress(deps, task, reportProgress, signal) {
|
|
|
1706
1898
|
const heartbeatIntervalMs = deps.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
1707
1899
|
deps.debugLog?.("run", "runSubagentWithProgress START", { taskName: task.name, yoloMode: deps.yoloMode });
|
|
1708
1900
|
let heartbeat;
|
|
1901
|
+
let session;
|
|
1902
|
+
let runStarted = false;
|
|
1903
|
+
let pendingRun;
|
|
1904
|
+
let agentSettled = true;
|
|
1905
|
+
let releaseLease;
|
|
1709
1906
|
if (heartbeatIntervalMs > 0) {
|
|
1710
1907
|
heartbeat = setInterval(() => {
|
|
1711
1908
|
try {
|
|
@@ -1718,46 +1915,134 @@ async function runSubagentWithProgress(deps, task, reportProgress, signal) {
|
|
|
1718
1915
|
}
|
|
1719
1916
|
}, Math.max(50, Math.min(1e3, heartbeatIntervalMs / 2)));
|
|
1720
1917
|
}
|
|
1918
|
+
if (task.sessionId) task.wasRecall = true;
|
|
1721
1919
|
try {
|
|
1722
1920
|
report({ message: `[${task.name}] Starting...`, increment: 5 });
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1921
|
+
let subagent;
|
|
1922
|
+
let baseline = 0;
|
|
1923
|
+
let plan;
|
|
1924
|
+
let runTimeoutSeconds = task.timeoutSeconds;
|
|
1925
|
+
if (task.sessionId) {
|
|
1926
|
+
if (sessions?.get(task.sessionId)?.busy) {
|
|
1927
|
+
throw new Error(`Subagent session "${task.sessionId}" is busy with another task; wait for it to settle.`);
|
|
1928
|
+
}
|
|
1929
|
+
releaseLease = await deps.sessionStore?.acquire?.(task.sessionId);
|
|
1930
|
+
const resolved = await resolveRecallSession(deps, task.sessionId, sessions, Math.max(1, deps.maxLiveSessions ?? DEFAULT_MAX_LIVE_SESSIONS));
|
|
1931
|
+
if (!resolved) {
|
|
1932
|
+
throw new Error(`Unknown or expired subagent session "${task.sessionId}": it is neither live in this process nor in the session store (evicted, swept, or never persisted). Spawn a fresh task instead of recalling.`);
|
|
1933
|
+
}
|
|
1934
|
+
const owned = resolved.session;
|
|
1935
|
+
session = owned;
|
|
1936
|
+
if (task.requestedTimeoutSeconds !== void 0) {
|
|
1937
|
+
session.timeoutSeconds = task.requestedTimeoutSeconds;
|
|
1938
|
+
}
|
|
1939
|
+
const nextMode = task.requestedMode ?? session.mode;
|
|
1940
|
+
if (nextMode === "edit" && (task.batchSize ?? 1) > 1) {
|
|
1941
|
+
throw new Error(`Recalling session "${session.id}" resolves to edit mode, and an edit delegation must contain exactly one task (the workspace is shared). Retry it alone.`);
|
|
1942
|
+
}
|
|
1943
|
+
const nextModelOverride = task.modelOverride ?? session.modelOverride;
|
|
1944
|
+
const nextThinking = task.thinkingLevel ?? session.thinkingLevel;
|
|
1945
|
+
const mustRebuild = session.agent === null || task.requestedMode !== void 0 || task.modelOverride !== void 0 || task.thinkingLevel !== void 0;
|
|
1946
|
+
if (!mustRebuild) {
|
|
1947
|
+
subagent = session.agent;
|
|
1948
|
+
baseline = subagent.state.messages.length;
|
|
1949
|
+
} else {
|
|
1950
|
+
let built = buildSubagentPlan(deps, session.originalTask, session.name, session.priority, session.timeoutSeconds, nextModelOverride, nextThinking, nextMode);
|
|
1951
|
+
let effectiveOverride = nextModelOverride;
|
|
1952
|
+
if (task.modelOverride && !built.plan.modelOverrideApplied && session.modelOverride && session.modelOverride !== task.modelOverride) {
|
|
1953
|
+
const missReason = built.plan.modelFallbackReason ?? `model override "${task.modelOverride}" could not be resolved`;
|
|
1954
|
+
built = buildSubagentPlan(deps, session.originalTask, session.name, session.priority, session.timeoutSeconds, session.modelOverride, nextThinking, nextMode);
|
|
1955
|
+
built.plan.modelFallbackReason = built.plan.modelFallbackReason ? `${missReason}; ${built.plan.modelFallbackReason}` : missReason;
|
|
1956
|
+
effectiveOverride = session.modelOverride;
|
|
1957
|
+
}
|
|
1958
|
+
plan = built.plan;
|
|
1959
|
+
task.plan = plan;
|
|
1960
|
+
for (const excluded of plan.toolsExcluded) {
|
|
1961
|
+
if (excluded.reason === "unclassified_tool") {
|
|
1962
|
+
task.confidenceSignals.push({ type: "unclassified_tool", message: `${excluded.name} inherited without risk metadata` });
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
if (plan.modelFallbackReason) {
|
|
1966
|
+
task.confidenceSignals.push({ type: "model_fallback", message: plan.modelFallbackReason });
|
|
1967
|
+
}
|
|
1968
|
+
if (!built.apiKey) {
|
|
1969
|
+
throw new Error("No API key available for subagent execution.");
|
|
1970
|
+
}
|
|
1971
|
+
if (!built.model) {
|
|
1972
|
+
throw new Error("No model configured for subagent execution.");
|
|
1973
|
+
}
|
|
1974
|
+
const priorMessages = session.agent ? session.agent.state.messages : session.pendingMessages ?? [];
|
|
1975
|
+
const rebuilt = createAgentInstance(deps, buildSubagentSystemPrompt(nextMode, deps.skills), built.tools, built.model, built.apiKey, plan.thinkingLevel);
|
|
1976
|
+
transplantMessages(rebuilt, priorMessages);
|
|
1977
|
+
baseline = priorMessages.length;
|
|
1978
|
+
subagent = rebuilt;
|
|
1979
|
+
session.agent = subagent;
|
|
1980
|
+
session.pendingMessages = void 0;
|
|
1981
|
+
session.mode = nextMode;
|
|
1982
|
+
session.modelOverride = effectiveOverride;
|
|
1983
|
+
session.thinkingLevel = plan.thinkingLevel;
|
|
1984
|
+
}
|
|
1985
|
+
if (plan) {
|
|
1986
|
+
runTimeoutSeconds = plan.timeoutSeconds;
|
|
1987
|
+
} else {
|
|
1988
|
+
runTimeoutSeconds = session.timeoutSeconds;
|
|
1729
1989
|
}
|
|
1990
|
+
task.mode = session.mode;
|
|
1991
|
+
task.needsParentWrite = task.mode === "read" && needsParentWrite(task.task);
|
|
1992
|
+
deps.log(`[Subagent ${task.name}] Recalling session ${session.id} (run ${session.runCount + 1}, mode=${session.mode}${resolved.restored ? ", restored from disk" : ""}${mustRebuild ? ", reconfigured" : ""})`);
|
|
1993
|
+
} else {
|
|
1994
|
+
const built = buildSubagentPlan(deps, task.task, task.name, task.priority, task.timeoutSeconds, task.modelOverride, task.thinkingLevel, task.mode);
|
|
1995
|
+
plan = built.plan;
|
|
1996
|
+
task.plan = plan;
|
|
1997
|
+
const { tools, apiKey, model } = built;
|
|
1998
|
+
task.needsParentWrite = task.mode === "read" && needsParentWrite(task.task);
|
|
1999
|
+
for (const excluded of plan.toolsExcluded) {
|
|
2000
|
+
if (excluded.reason === "unclassified_tool") {
|
|
2001
|
+
task.confidenceSignals.push({ type: "unclassified_tool", message: `${excluded.name} inherited without risk metadata` });
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
if (plan.modelFallbackReason) {
|
|
2005
|
+
task.confidenceSignals.push({ type: "model_fallback", message: plan.modelFallbackReason });
|
|
2006
|
+
}
|
|
2007
|
+
if (!apiKey) {
|
|
2008
|
+
throw new Error("No API key available for subagent execution.");
|
|
2009
|
+
}
|
|
2010
|
+
if (!model) {
|
|
2011
|
+
throw new Error("No model configured for subagent execution.");
|
|
2012
|
+
}
|
|
2013
|
+
if (signal?.aborted) {
|
|
2014
|
+
throw new Error("Subagent aborted by parent");
|
|
2015
|
+
}
|
|
2016
|
+
deps.log(`[Subagent ${task.name}] Plan: ${plan.complexity}/${plan.modelClass}${plan.modelOverride ? `, model=${plan.modelOverrideApplied ? plan.modelOverride : "override failed"}` : ""}, ${tools.length} tools`);
|
|
2017
|
+
deps.debugLog?.("plan", "SubagentPlan", plan);
|
|
2018
|
+
subagent = createAgentInstance(deps, buildSubagentSystemPrompt(task.mode, deps.skills), tools, model, apiKey, plan.thinkingLevel);
|
|
2019
|
+
session = {
|
|
2020
|
+
id: task.id,
|
|
2021
|
+
name: task.name,
|
|
2022
|
+
mode: task.mode,
|
|
2023
|
+
priority: task.priority,
|
|
2024
|
+
originalTask: task.task,
|
|
2025
|
+
modelOverride: task.modelOverride,
|
|
2026
|
+
thinkingLevel: plan.thinkingLevel,
|
|
2027
|
+
timeoutSeconds: task.timeoutSeconds,
|
|
2028
|
+
agent: subagent,
|
|
2029
|
+
createdAt: Date.now(),
|
|
2030
|
+
lastUsedAt: Date.now(),
|
|
2031
|
+
runCount: 0,
|
|
2032
|
+
revision: 0,
|
|
2033
|
+
busy: true
|
|
2034
|
+
};
|
|
2035
|
+
task.sessionId = session.id;
|
|
1730
2036
|
}
|
|
1731
|
-
if (
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
throw new Error("No model configured for subagent execution.");
|
|
1739
|
-
}
|
|
1740
|
-
if (signal?.aborted) {
|
|
1741
|
-
throw new Error("Subagent aborted by parent");
|
|
2037
|
+
if (!task.wasRecall) {
|
|
2038
|
+
try {
|
|
2039
|
+
releaseLease = await deps.sessionStore?.acquire?.(session.id);
|
|
2040
|
+
} catch (error) {
|
|
2041
|
+
session = void 0;
|
|
2042
|
+
throw error;
|
|
2043
|
+
}
|
|
1742
2044
|
}
|
|
1743
|
-
|
|
1744
|
-
deps.debugLog?.("plan", "SubagentPlan", plan);
|
|
1745
|
-
const systemPrompt = `You are a specialized subagent focused on one bounded task.
|
|
1746
|
-
|
|
1747
|
-
Task boundary:
|
|
1748
|
-
- Work only on the assigned task.
|
|
1749
|
-
- Use available tools to gather evidence before making claims.
|
|
1750
|
-
${task.mode === "edit" ? "- You may edit files and run tests using the inherited parent tools and permissions. This is a shared workspace, not an isolated sandbox. Preserve existing user changes. Do not commit or automatically roll back changes. Report changed files, tests actually run, and remaining work for parent review." : "- Do not edit files; if writing is needed, say what the parent agent should change."}
|
|
1751
|
-
- Return a concise answer with evidence and uncertainty.
|
|
1752
|
-
|
|
1753
|
-
Output:
|
|
1754
|
-
- Answer
|
|
1755
|
-
- Evidence
|
|
1756
|
-
- Uncertainty: low, medium, or high
|
|
1757
|
-
- Parent follow-up needed, if any
|
|
1758
|
-
|
|
1759
|
-
${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
1760
|
-
const subagent = createAgentInstance(deps, systemPrompt, tools, model, apiKey, plan.thinkingLevel);
|
|
2045
|
+
if (sessions) retainLiveSession(sessions, session, Math.max(1, deps.maxLiveSessions ?? DEFAULT_MAX_LIVE_SESSIONS));
|
|
1761
2046
|
let assistantTurns = 0;
|
|
1762
2047
|
let toolCalls = 0;
|
|
1763
2048
|
let rejectTimeout = () => void 0;
|
|
@@ -1769,10 +2054,10 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
|
1769
2054
|
});
|
|
1770
2055
|
timeout = setTimeout(() => {
|
|
1771
2056
|
timedOut = true;
|
|
1772
|
-
const timeoutMessage = `Subagent timed out after ${
|
|
2057
|
+
const timeoutMessage = `Subagent timed out after ${runTimeoutSeconds} seconds`;
|
|
1773
2058
|
subagent.abort?.();
|
|
1774
2059
|
rejectTimeout(new Error(timeoutMessage));
|
|
1775
|
-
},
|
|
2060
|
+
}, runTimeoutSeconds * 1e3);
|
|
1776
2061
|
const abortPromise = new Promise((_, reject) => {
|
|
1777
2062
|
if (!signal) return;
|
|
1778
2063
|
const abortSubagent = () => {
|
|
@@ -1809,17 +2094,33 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
|
1809
2094
|
}
|
|
1810
2095
|
});
|
|
1811
2096
|
let prompt = task.task;
|
|
2097
|
+
if (task.wasRecall && session) {
|
|
2098
|
+
prompt = `This task CONTINUES subagent session "${session.name}" (${session.id}); your earlier transcript is retained. Build on it instead of redoing completed investigation, and re-verify anything the workspace may have changed since your last run.
|
|
2099
|
+
|
|
2100
|
+
New instruction:
|
|
2101
|
+
${task.task}`;
|
|
2102
|
+
}
|
|
1812
2103
|
if (task.mode === "read") {
|
|
1813
2104
|
prompt += "\n\nThis delegation is read mode. Do not write files. If changes are needed, describe them for the parent.";
|
|
1814
2105
|
}
|
|
1815
2106
|
prompt += "\n\nReturn evidence for your conclusion and state uncertainty as low, medium, or high.";
|
|
1816
|
-
|
|
2107
|
+
const runLabel = plan ? `plan ${plan.complexity}/${plan.modelClass}` : `session ${session?.id ?? ""} (run ${(session?.runCount ?? 0) + 1})`;
|
|
2108
|
+
report({ message: `[${task.name}] Running ${runLabel}...`, increment: 20 });
|
|
2109
|
+
runStarted = true;
|
|
2110
|
+
agentSettled = false;
|
|
2111
|
+
const runPromise = (async () => {
|
|
2112
|
+
try {
|
|
2113
|
+
await subagent.prompt(prompt);
|
|
2114
|
+
await subagent.waitForIdle?.();
|
|
2115
|
+
} finally {
|
|
2116
|
+
agentSettled = true;
|
|
2117
|
+
}
|
|
2118
|
+
})();
|
|
2119
|
+
const settledRun = runPromise.catch(() => void 0);
|
|
2120
|
+
pendingRun = settledRun;
|
|
1817
2121
|
try {
|
|
1818
2122
|
await Promise.race([
|
|
1819
|
-
|
|
1820
|
-
await subagent.prompt(prompt);
|
|
1821
|
-
await subagent.waitForIdle?.();
|
|
1822
|
-
})(),
|
|
2123
|
+
runPromise,
|
|
1823
2124
|
timeoutPromise,
|
|
1824
2125
|
abortPromise
|
|
1825
2126
|
]);
|
|
@@ -1829,9 +2130,19 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
|
1829
2130
|
}
|
|
1830
2131
|
removeAbortListener?.();
|
|
1831
2132
|
unsubscribe?.();
|
|
2133
|
+
if (!agentSettled) {
|
|
2134
|
+
const graceMs = deps.sessionSettleGraceMs ?? 2e3;
|
|
2135
|
+
if (graceMs > 0) {
|
|
2136
|
+
await Promise.race([
|
|
2137
|
+
settledRun,
|
|
2138
|
+
new Promise((resolve3) => setTimeout(resolve3, graceMs))
|
|
2139
|
+
]);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
1832
2142
|
}
|
|
1833
|
-
const
|
|
1834
|
-
|
|
2143
|
+
const allMessages = subagent.state.messages;
|
|
2144
|
+
const messages = allMessages.slice(baseline);
|
|
2145
|
+
deps.log(`[Subagent ${task.name}] Total messages: ${allMessages.length} (${messages.length} new)`);
|
|
1835
2146
|
const lastAssistant = [...messages].reverse().find((m) => m?.role === "assistant");
|
|
1836
2147
|
const terminalStopReason = lastAssistant?.stopReason;
|
|
1837
2148
|
const terminalError = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
|
|
@@ -1891,6 +2202,29 @@ ${formatSkillsForPrompt(deps.skills ?? [])}`;
|
|
|
1891
2202
|
if (heartbeat) {
|
|
1892
2203
|
clearInterval(heartbeat);
|
|
1893
2204
|
}
|
|
2205
|
+
const finalize = async () => {
|
|
2206
|
+
try {
|
|
2207
|
+
if (session) {
|
|
2208
|
+
if (runStarted) session.runCount++;
|
|
2209
|
+
session.lastUsedAt = Date.now();
|
|
2210
|
+
session.revision++;
|
|
2211
|
+
task.sessionRunCount = session.runCount;
|
|
2212
|
+
await persistSubagentSession(deps, session, task);
|
|
2213
|
+
}
|
|
2214
|
+
} finally {
|
|
2215
|
+
await releaseLease?.();
|
|
2216
|
+
if (session) {
|
|
2217
|
+
session.busy = false;
|
|
2218
|
+
if (sessions) retainLiveSession(sessions, session, Math.max(1, deps.maxLiveSessions ?? DEFAULT_MAX_LIVE_SESSIONS));
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
};
|
|
2222
|
+
if (!agentSettled && pendingRun) {
|
|
2223
|
+
task.sessionBusy = true;
|
|
2224
|
+
void pendingRun.then(finalize).catch((error) => deps.debugLog?.("session", "late session cleanup failed", { error: String(error) }));
|
|
2225
|
+
} else {
|
|
2226
|
+
await finalize();
|
|
2227
|
+
}
|
|
1894
2228
|
}
|
|
1895
2229
|
}
|
|
1896
2230
|
function buildParallelResult(tasks, totalDuration) {
|
|
@@ -1927,6 +2261,12 @@ function buildParallelResult(tasks, totalDuration) {
|
|
|
1927
2261
|
resultText += `Evidence: ${task.evidence.slice(0, 5).map((entry) => `${entry.type}:${entry.value}`).join("; ")}
|
|
1928
2262
|
`;
|
|
1929
2263
|
}
|
|
2264
|
+
if (task.sessionId) {
|
|
2265
|
+
resultText += task.sessionBusy ? `Session: ${task.sessionId} (busy \u2014 previous run is still settling; recall will fail until it settles` : `Session: ${task.sessionId} (resumable \u2014 pass as "sessionId" in a later spawn to continue this subagent with its accumulated context`;
|
|
2266
|
+
if (task.sessionRunCount) resultText += `; run ${task.sessionRunCount}`;
|
|
2267
|
+
if (task.sessionPersisted) resultText += "; persisted to disk, survives restarts";
|
|
2268
|
+
resultText += ")\n";
|
|
2269
|
+
}
|
|
1930
2270
|
if (task.mode === "edit") {
|
|
1931
2271
|
resultText += "Parent follow-up: review changes and validate reported tests. Partial edits are preserved on failure; no automatic rollback or task retry.\n";
|
|
1932
2272
|
} else if (task.needsParentWrite) {
|
|
@@ -1951,7 +2291,8 @@ function normalizeTaskDefs(args) {
|
|
|
1951
2291
|
return { error: "Invalid spawn_subagents_parallel arguments: tasks must be a non-empty array." };
|
|
1952
2292
|
}
|
|
1953
2293
|
const normalized = [];
|
|
1954
|
-
const allowedFields = [...TASK_FIELD_ALIASES, "name", "priority", "timeout", "model", "thinkingLevel", "mode"];
|
|
2294
|
+
const allowedFields = [...TASK_FIELD_ALIASES, "name", "priority", "timeout", "model", "thinkingLevel", "mode", "sessionId"];
|
|
2295
|
+
const seenSessionIds = /* @__PURE__ */ new Set();
|
|
1955
2296
|
for (const [index, def] of taskDefs.entries()) {
|
|
1956
2297
|
if (!def || typeof def !== "object") {
|
|
1957
2298
|
return { error: `Invalid task #${index + 1}: expected an object with the objective in "task" (aliases "description"/"prompt"/"objective" accepted).` };
|
|
@@ -1984,6 +2325,16 @@ function normalizeTaskDefs(args) {
|
|
|
1984
2325
|
if (def.thinkingLevel !== void 0 && (typeof def.thinkingLevel !== "string" || !SUBAGENT_THINKING_LEVELS.includes(def.thinkingLevel))) {
|
|
1985
2326
|
return { error: `Invalid task #${index + 1}: "thinkingLevel" must be one of ${SUBAGENT_THINKING_LEVELS.map((level) => `"${level}"`).join(", ")}.` };
|
|
1986
2327
|
}
|
|
2328
|
+
const sessionId = typeof def.sessionId === "string" && def.sessionId.trim() ? def.sessionId.trim() : void 0;
|
|
2329
|
+
if (def.sessionId !== void 0 && !sessionId) {
|
|
2330
|
+
return { error: `Invalid task #${index + 1}: "sessionId" must be a non-empty session id from an earlier result's Session line.` };
|
|
2331
|
+
}
|
|
2332
|
+
if (sessionId) {
|
|
2333
|
+
if (seenSessionIds.has(sessionId)) {
|
|
2334
|
+
return { error: `Invalid task #${index + 1}: duplicate "sessionId" "${sessionId}" \u2014 recalls to the same session must run in separate calls.` };
|
|
2335
|
+
}
|
|
2336
|
+
seenSessionIds.add(sessionId);
|
|
2337
|
+
}
|
|
1987
2338
|
normalized.push({
|
|
1988
2339
|
name: typeof def.name === "string" && def.name.trim() ? def.name.trim() : void 0,
|
|
1989
2340
|
task,
|
|
@@ -1991,7 +2342,10 @@ function normalizeTaskDefs(args) {
|
|
|
1991
2342
|
mode: def.mode ?? "read",
|
|
1992
2343
|
timeoutSeconds,
|
|
1993
2344
|
modelOverride,
|
|
1994
|
-
thinkingLevel: def.thinkingLevel
|
|
2345
|
+
thinkingLevel: def.thinkingLevel,
|
|
2346
|
+
sessionId,
|
|
2347
|
+
requestedMode: def.mode,
|
|
2348
|
+
requestedTimeoutSeconds: "timeout" in def ? timeoutSeconds : void 0
|
|
1995
2349
|
});
|
|
1996
2350
|
}
|
|
1997
2351
|
if (normalized.length !== 1 && normalized.some((task) => task.mode === "edit")) {
|
|
@@ -2000,6 +2354,7 @@ function normalizeTaskDefs(args) {
|
|
|
2000
2354
|
return normalized;
|
|
2001
2355
|
}
|
|
2002
2356
|
var DEFAULT_MAX_CONCURRENT_SUBAGENTS = 4;
|
|
2357
|
+
var taskIdCounter = 0;
|
|
2003
2358
|
async function runWithConcurrencyLimit(jobs, limit) {
|
|
2004
2359
|
let next = 0;
|
|
2005
2360
|
const workers = Array.from({ length: Math.max(1, Math.min(limit, jobs.length)) }, async () => {
|
|
@@ -2011,6 +2366,13 @@ async function runWithConcurrencyLimit(jobs, limit) {
|
|
|
2011
2366
|
await Promise.all(workers);
|
|
2012
2367
|
}
|
|
2013
2368
|
function createSubagentCapability(deps) {
|
|
2369
|
+
const liveSessions = /* @__PURE__ */ new Map();
|
|
2370
|
+
if (deps.sessionStore?.sweep) {
|
|
2371
|
+
try {
|
|
2372
|
+
void Promise.resolve(deps.sessionStore.sweep({ maxAgeMs: DEFAULT_SESSION_STORE_MAX_AGE_MS, maxRecords: DEFAULT_SESSION_STORE_MAX_RECORDS })).catch(() => void 0);
|
|
2373
|
+
} catch {
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2014
2376
|
const spawnParallelTool = {
|
|
2015
2377
|
name: "spawn_subagents_parallel",
|
|
2016
2378
|
label: "Spawn Subagents",
|
|
@@ -2023,7 +2385,9 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
2023
2385
|
|
|
2024
2386
|
"model" overrides the automatic model-class selection for that task when the user explicitly asks for a specific model (e.g. "anthropic/claude-..."). Prefer "provider/model-id"; a bare model id works only when it is unambiguous across providers. When the override cannot be resolved, the task falls back to automatic selection and reports why.
|
|
2025
2387
|
|
|
2026
|
-
"thinkingLevel" defaults to "medium"; set it only when the user asks (e.g. "xhigh" for deep review) or for cheap lookup tasks ("off"). Non-reasoning models ignore it
|
|
2388
|
+
"thinkingLevel" defaults to "medium"; set it only when the user asks (e.g. "xhigh" for deep review) or for cheap lookup tasks ("off"). Non-reasoning models ignore it.
|
|
2389
|
+
|
|
2390
|
+
Sessions: every task that starts execution returns a Session id (persisted to disk when the host supports it). Pass "sessionId" in a later call to continue that subagent with its accumulated context \u2014 cheaper than re-briefing a fresh agent for iterative work on the same area. A recall may override "mode" (including upgrading read\u2192edit), "model", and "thinkingLevel": the session is reconfigured with freshly resolved tools and credentials. Recalls never replay on failure, and a session runs one task at a time. Prefer a FRESH task for independent reviews \u2014 accumulated context anchors judgment, and each recall carries the whole transcript.`,
|
|
2027
2391
|
parameters: Type.Object({
|
|
2028
2392
|
tasks: Type.Array(
|
|
2029
2393
|
Type.Object(
|
|
@@ -2037,9 +2401,10 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
2037
2401
|
priority: Type.Optional(Type.Union([Type.Literal("fast"), Type.Literal("thorough")], { description: "fast uses lower-cost defaults; thorough raises model class when needed." })),
|
|
2038
2402
|
model: Type.Optional(Type.String({ description: 'Explicit model override for this task, e.g. "provider/model-id". Only set when the user explicitly requests a specific model; otherwise Pie picks the model class automatically.' })),
|
|
2039
2403
|
thinkingLevel: Type.Optional(Type.Union([Type.Literal("off"), Type.Literal("minimal"), Type.Literal("low"), Type.Literal("medium"), Type.Literal("high"), Type.Literal("xhigh")], { description: `Reasoning level for this task. Default: "medium". Only override when the user asks (e.g. "xhigh" for deep review tasks, "off" for cheap lookups). Ignored by non-reasoning models.` })),
|
|
2040
|
-
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds. Default: 1200s (20 minutes). For longer tasks, explicitly set a higher value, e.g. 3600." }))
|
|
2404
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds. Default: 1200s (20 minutes). For longer tasks, explicitly set a higher value, e.g. 3600." })),
|
|
2405
|
+
sessionId: Type.Optional(Type.String({ description: "Resume a previous subagent session by id (from an earlier result's Session line). The session's accumulated transcript is reused \u2014 cheaper for iterative work on the same area. Optional mode/model/thinkingLevel on a recall RECONFIGURE the session with freshly resolved tools and credentials (read sessions may be upgraded to edit). Recalls never replay on failure and must target an idle session." }))
|
|
2041
2406
|
},
|
|
2042
|
-
{ additionalProperties: false, description: "Objective goes in 'task' (or an accepted alias); plus optional name/priority/model/thinkingLevel/timeout." }
|
|
2407
|
+
{ additionalProperties: false, description: "Objective goes in 'task' (or an accepted alias); plus optional name/priority/model/thinkingLevel/timeout/sessionId." }
|
|
2043
2408
|
),
|
|
2044
2409
|
{ description: "Array of tasks to run in parallel; at least one of task/description/prompt/objective must be a non-empty string per item.", minItems: 1, maxItems: 10 }
|
|
2045
2410
|
)
|
|
@@ -2066,7 +2431,9 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
2066
2431
|
};
|
|
2067
2432
|
}
|
|
2068
2433
|
const tasks = normalized.map((def, index) => ({
|
|
2069
|
-
|
|
2434
|
+
// Date.now()+index alone collides when two spawn calls land in the
|
|
2435
|
+
// same millisecond (ids double as session ids and store filenames).
|
|
2436
|
+
id: `subagent-${Date.now().toString(36)}-${(++taskIdCounter).toString(36)}${Math.random().toString(36).slice(2, 6)}-${index}`,
|
|
2070
2437
|
name: def.name || `Task-${index + 1}`,
|
|
2071
2438
|
status: "pending",
|
|
2072
2439
|
task: def.task,
|
|
@@ -2075,6 +2442,10 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
2075
2442
|
timeoutSeconds: def.timeoutSeconds,
|
|
2076
2443
|
modelOverride: def.modelOverride,
|
|
2077
2444
|
thinkingLevel: def.thinkingLevel,
|
|
2445
|
+
sessionId: def.sessionId,
|
|
2446
|
+
requestedMode: def.requestedMode,
|
|
2447
|
+
requestedTimeoutSeconds: def.requestedTimeoutSeconds,
|
|
2448
|
+
batchSize: normalized.length,
|
|
2078
2449
|
evidence: [],
|
|
2079
2450
|
confidenceSignals: [],
|
|
2080
2451
|
uncertainty: "medium",
|
|
@@ -2088,8 +2459,8 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
2088
2459
|
toolContext.reportProgress?.({ message: `Launching ${tasks.length} parallel subagents...`, increment: 5 });
|
|
2089
2460
|
const startTime = Date.now();
|
|
2090
2461
|
const runTask = async (task) => {
|
|
2091
|
-
await runSubagentWithProgress(deps, task, toolContext.reportProgress, signal);
|
|
2092
|
-
if (task.mode !== "read" || task.status !== "failed" || !task.error) return;
|
|
2462
|
+
await runSubagentWithProgress(deps, task, toolContext.reportProgress, signal, liveSessions);
|
|
2463
|
+
if (task.wasRecall || task.mode !== "read" || task.status !== "failed" || !task.error) return;
|
|
2093
2464
|
if (signal?.aborted) return;
|
|
2094
2465
|
if (!isRetryableSubagentFailure(task.error)) return;
|
|
2095
2466
|
const settleMs = Math.max(0, (deps.transientRetryBackoffMs ?? 2e3) * 10);
|
|
@@ -2104,7 +2475,10 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
2104
2475
|
task.result = void 0;
|
|
2105
2476
|
task.details = void 0;
|
|
2106
2477
|
task.parentHandoffRequired = false;
|
|
2107
|
-
|
|
2478
|
+
task.sessionId = void 0;
|
|
2479
|
+
task.requestedMode = void 0;
|
|
2480
|
+
task.wasRecall = void 0;
|
|
2481
|
+
await runSubagentWithProgress(deps, task, toolContext.reportProgress, signal, liveSessions);
|
|
2108
2482
|
};
|
|
2109
2483
|
await runWithConcurrencyLimit(
|
|
2110
2484
|
tasks.map((task) => () => runTask(task)),
|
|
@@ -2122,6 +2496,9 @@ Arguments shape (each tasks[] item puts the objective in "task" \u2014 "descript
|
|
|
2122
2496
|
name: task.name,
|
|
2123
2497
|
status: task.status,
|
|
2124
2498
|
mode: task.mode,
|
|
2499
|
+
sessionId: task.sessionId,
|
|
2500
|
+
sessionRunCount: task.sessionRunCount,
|
|
2501
|
+
sessionPersisted: task.sessionPersisted,
|
|
2125
2502
|
task: task.task,
|
|
2126
2503
|
timeoutSeconds: task.timeoutSeconds,
|
|
2127
2504
|
plan: task.plan,
|
|
@@ -5967,6 +6344,7 @@ var AgentSessionRetryController = class {
|
|
|
5967
6344
|
/** Total attempts across kinds in this recovery episode, for reporting. */
|
|
5968
6345
|
totalRetryAttempts = 0;
|
|
5969
6346
|
retryTimer;
|
|
6347
|
+
retryEpoch = 0;
|
|
5970
6348
|
pendingRetry;
|
|
5971
6349
|
resolvePendingRetry;
|
|
5972
6350
|
get policy() {
|
|
@@ -6022,18 +6400,26 @@ var AgentSessionRetryController = class {
|
|
|
6022
6400
|
const rawDelayMs = (this.retry.baseDelayMs ?? 2e3) * 2 ** (attempt - 1);
|
|
6023
6401
|
const delayMs = this.retry.maxDelayMs !== void 0 ? Math.min(rawDelayMs, this.retry.maxDelayMs) : rawDelayMs;
|
|
6024
6402
|
this.ensurePending();
|
|
6403
|
+
const epoch = this.retryEpoch;
|
|
6404
|
+
const pending = this.pendingRetry;
|
|
6025
6405
|
this.setRecoveryState("retry_pending");
|
|
6026
6406
|
this.emit({ type: "turn_recovery_pending", recovery: "retry", failure, attempt, maxRetries, delayMs });
|
|
6407
|
+
if (epoch !== this.retryEpoch) return;
|
|
6027
6408
|
this.emit({ type: "retry_scheduled", errorMessage: failure.message, attempt, maxRetries, delayMs });
|
|
6409
|
+
if (epoch !== this.retryEpoch) return;
|
|
6028
6410
|
this.cancelTimerOnly();
|
|
6029
6411
|
const setTimer = this.retry.setTimeout ?? defaultSetTimeout4;
|
|
6030
6412
|
this.retryTimer = setTimer(() => {
|
|
6413
|
+
if (epoch !== this.retryEpoch) return;
|
|
6031
6414
|
this.retryTimer = void 0;
|
|
6032
6415
|
this.setRecoveryState("retrying");
|
|
6033
6416
|
this.emit({ type: "retry_start", attempt, maxRetries });
|
|
6417
|
+
if (epoch !== this.retryEpoch) return;
|
|
6034
6418
|
this.emit({ type: "turn_retry_started", attempt, maxRetries, failure });
|
|
6419
|
+
if (epoch !== this.retryEpoch) return;
|
|
6035
6420
|
this.removeTrailingAssistantError();
|
|
6036
6421
|
void this.continueTurn().catch((error) => {
|
|
6422
|
+
if (epoch !== this.retryEpoch || pending !== this.pendingRetry) return;
|
|
6037
6423
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6038
6424
|
const terminalFailure = asTerminalFailure(failure, errorMessage);
|
|
6039
6425
|
this.emit({
|
|
@@ -6045,7 +6431,7 @@ var AgentSessionRetryController = class {
|
|
|
6045
6431
|
this.emit({ type: "turn_settled" });
|
|
6046
6432
|
this.resetAttempts();
|
|
6047
6433
|
this.setRecoveryState("terminal");
|
|
6048
|
-
this.resolvePending();
|
|
6434
|
+
this.resolvePending(pending);
|
|
6049
6435
|
});
|
|
6050
6436
|
}, delayMs);
|
|
6051
6437
|
}
|
|
@@ -6077,6 +6463,7 @@ var AgentSessionRetryController = class {
|
|
|
6077
6463
|
this.resolvePending();
|
|
6078
6464
|
}
|
|
6079
6465
|
cancel() {
|
|
6466
|
+
this.retryEpoch++;
|
|
6080
6467
|
this.cancelTimerOnly();
|
|
6081
6468
|
this.resetAttempts();
|
|
6082
6469
|
this.resolvePending();
|
|
@@ -6101,7 +6488,8 @@ var AgentSessionRetryController = class {
|
|
|
6101
6488
|
this.resolvePendingRetry = resolve3;
|
|
6102
6489
|
});
|
|
6103
6490
|
}
|
|
6104
|
-
resolvePending() {
|
|
6491
|
+
resolvePending(expected = this.pendingRetry) {
|
|
6492
|
+
if (expected !== this.pendingRetry) return;
|
|
6105
6493
|
const resolve3 = this.resolvePendingRetry;
|
|
6106
6494
|
this.pendingRetry = void 0;
|
|
6107
6495
|
this.resolvePendingRetry = void 0;
|
|
@@ -6435,6 +6823,8 @@ async function handleAgentSessionTurnEndRuntimePolicies(params) {
|
|
|
6435
6823
|
if (params.disposed || params.runtimeGuardTriggered) {
|
|
6436
6824
|
return;
|
|
6437
6825
|
}
|
|
6826
|
+
const startedGeneration = params.getCurrentGeneration?.();
|
|
6827
|
+
const isStale = () => params.getCurrentGeneration !== void 0 && params.getCurrentGeneration() !== startedGeneration;
|
|
6438
6828
|
const failure = classifyControllerTurnFailure({
|
|
6439
6829
|
message: params.message,
|
|
6440
6830
|
retry: params.retry,
|
|
@@ -6454,6 +6844,7 @@ async function handleAgentSessionTurnEndRuntimePolicies(params) {
|
|
|
6454
6844
|
message: params.message,
|
|
6455
6845
|
messages: params.agent.state.messages.slice()
|
|
6456
6846
|
});
|
|
6847
|
+
if (isStale()) return;
|
|
6457
6848
|
if (failure?.compactable) {
|
|
6458
6849
|
params.scheduleAutoCompact(compactDecision ?? { reason: "overflow", willRetry: true }, failure);
|
|
6459
6850
|
return;
|
|
@@ -6472,14 +6863,18 @@ async function handleAgentSessionTurnEndRuntimePolicies(params) {
|
|
|
6472
6863
|
return;
|
|
6473
6864
|
}
|
|
6474
6865
|
await params.handleRetryTurnEnd(params.message, failure);
|
|
6866
|
+
if (isStale()) return;
|
|
6475
6867
|
params.setRecoveryState("terminal");
|
|
6476
6868
|
params.emit({ type: "turn_failed_terminal", failure: asTerminalFailure(failure), message: params.message });
|
|
6869
|
+
if (isStale()) return;
|
|
6477
6870
|
params.emit({ type: "turn_settled" });
|
|
6478
6871
|
return;
|
|
6479
6872
|
}
|
|
6480
6873
|
await params.handleRetryTurnEnd(params.message);
|
|
6874
|
+
if (isStale()) return;
|
|
6481
6875
|
if (isAssistantTerminalCompletion(params.message)) {
|
|
6482
6876
|
params.emit({ type: "turn_completed", message: params.message, toolResults: params.toolResults });
|
|
6877
|
+
if (isStale()) return;
|
|
6483
6878
|
const postTurnCompactDecision = compactDecision ?? params.getAutoCompactDecision?.({
|
|
6484
6879
|
agent: params.agent,
|
|
6485
6880
|
sessionManager: params.sessionManager,
|
|
@@ -6487,6 +6882,7 @@ async function handleAgentSessionTurnEndRuntimePolicies(params) {
|
|
|
6487
6882
|
message: params.message,
|
|
6488
6883
|
messages: params.agent.state.messages.slice()
|
|
6489
6884
|
});
|
|
6885
|
+
if (isStale()) return;
|
|
6490
6886
|
if (postTurnCompactDecision?.reason === "threshold") {
|
|
6491
6887
|
params.scheduleAutoCompact(postTurnCompactDecision);
|
|
6492
6888
|
return;
|
|
@@ -6734,6 +7130,8 @@ var AgentSessionController = class {
|
|
|
6734
7130
|
try {
|
|
6735
7131
|
this.generation++;
|
|
6736
7132
|
this.cancelScheduledWork();
|
|
7133
|
+
this.recoveryState = "none";
|
|
7134
|
+
this.pendingCompactionFailure = void 0;
|
|
6737
7135
|
this.pendingEndOperations.clear();
|
|
6738
7136
|
const activeSession = this.sessionManager.getActiveSession();
|
|
6739
7137
|
this.agent.sessionId = activeSession?.id;
|
|
@@ -6964,16 +7362,20 @@ var AgentSessionController = class {
|
|
|
6964
7362
|
this.emit({ type: "turn_policy_error", message: String(error) });
|
|
6965
7363
|
}));
|
|
6966
7364
|
this.recordLifecycle({ type: "turn_end_success" });
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
this.
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
7365
|
+
const generation = this.generation;
|
|
7366
|
+
this.pendingEndOperations.track(() => this.handleTurnEndRuntimePolicies(event.message, event.toolResults).catch((error) => {
|
|
7367
|
+
if (generation !== this.generation || this.disposed) return;
|
|
7368
|
+
handleTurnEndPolicyThrow((event2) => this.emit(event2), error, {
|
|
7369
|
+
recoveryWasActive: this.isRecoveryActive(),
|
|
7370
|
+
cancelPendingRecovery: () => {
|
|
7371
|
+
this.retryController.cancel();
|
|
7372
|
+
this.autoCompact.cancel();
|
|
7373
|
+
},
|
|
7374
|
+
markRecoveryTerminal: () => {
|
|
7375
|
+
this.recoveryState = "terminal";
|
|
7376
|
+
}
|
|
7377
|
+
});
|
|
7378
|
+
}));
|
|
6977
7379
|
}
|
|
6978
7380
|
if (event.type === "agent_end") {
|
|
6979
7381
|
this.recordLifecycle({ type: "agent_end" });
|
|
@@ -7027,6 +7429,7 @@ var AgentSessionController = class {
|
|
|
7027
7429
|
getAutoCompactDecision: this.getAutoCompactDecision,
|
|
7028
7430
|
classifyFailure: this.classifyFailure,
|
|
7029
7431
|
retry: this.retryController.policy,
|
|
7432
|
+
getCurrentGeneration: () => this.generation,
|
|
7030
7433
|
emit: (event) => this.emit(event),
|
|
7031
7434
|
setRecoveryState: (state) => {
|
|
7032
7435
|
this.recoveryState = state;
|
|
@@ -10352,6 +10755,9 @@ function leadingWhitespace(line) {
|
|
|
10352
10755
|
const match2 = line.match(/^[ \t]*/);
|
|
10353
10756
|
return match2 ? match2[0] : "";
|
|
10354
10757
|
}
|
|
10758
|
+
function stripLeadingWhitespace(line) {
|
|
10759
|
+
return line.replace(/^[ \t]*/, "");
|
|
10760
|
+
}
|
|
10355
10761
|
var RelaxedWindow;
|
|
10356
10762
|
((RelaxedWindow2) => {
|
|
10357
10763
|
RelaxedWindow2.EMPTY_MAP = /* @__PURE__ */ new Map();
|
|
@@ -10495,7 +10901,7 @@ function reindentNewText(newText, qLines, fileLines, window) {
|
|
|
10495
10901
|
}
|
|
10496
10902
|
const nPos = nPosOfLine.get(i) ?? 0;
|
|
10497
10903
|
const qPos = counterpart.get(nPos) ?? 0;
|
|
10498
|
-
result.push(qIndent(qIdx[qPos] ?? qIdx[0]) + newLines[i]
|
|
10904
|
+
result.push(qIndent(qIdx[qPos] ?? qIdx[0]) + stripLeadingWhitespace(newLines[i]));
|
|
10499
10905
|
}
|
|
10500
10906
|
return result.join("\n");
|
|
10501
10907
|
}
|
|
@@ -17347,6 +17753,7 @@ export {
|
|
|
17347
17753
|
modalityForReadFileUnderstandingKind,
|
|
17348
17754
|
createReadFileUnderstandingHandler,
|
|
17349
17755
|
formatSkillSummariesForPrompt,
|
|
17756
|
+
SUBAGENT_SESSION_RECORD_VERSION,
|
|
17350
17757
|
classifyShellCommand,
|
|
17351
17758
|
interpretShellExit,
|
|
17352
17759
|
createSubagentCapability,
|