@sema-agent/core 2.13.0 → 4.0.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/CHANGELOG.md +2345 -0
- package/dist/agents/cascade.d.ts +6 -1
- package/dist/agents/cascade.js +76 -32
- package/dist/agents/cumulative-stats.d.ts +29 -0
- package/dist/agents/cumulative-stats.js +29 -0
- package/dist/agents/observer.d.ts +4 -0
- package/dist/agents/observer.js +11 -10
- package/dist/agents/repair-loop.d.ts +2 -0
- package/dist/agents/repair-loop.js +110 -3
- package/dist/agents/retain-ledger.d.ts +20 -2
- package/dist/agents/retain-ledger.js +38 -8
- package/dist/agents/roster-store.d.ts +4 -0
- package/dist/agents/roster-store.js +20 -1
- package/dist/agents/subagent.js +66 -64
- package/dist/agents/teacher.d.ts +1 -9
- package/dist/agents/teacher.js +23 -24
- package/dist/agents/team.js +7 -4
- package/dist/agents/verify.d.ts +15 -2
- package/dist/agents/verify.js +39 -3
- package/dist/bin/sema-tb.js +2 -2
- package/dist/brain/anthropic.js +24 -21
- package/dist/brain/circuit-breaker.d.ts +1 -0
- package/dist/brain/circuit-breaker.js +26 -9
- package/dist/brain/degrading.js +13 -1
- package/dist/brain/failover.js +2 -0
- package/dist/brain/openai.js +24 -21
- package/dist/brain/repetition.js +10 -1
- package/dist/brain/routing.js +5 -1
- package/dist/brain/status-sink.js +5 -1
- package/dist/brain/stream-engine.js +5 -2
- package/dist/brain/stream-shared.d.ts +1 -0
- package/dist/brain/stream-shared.js +14 -0
- package/dist/brain/terminal-cause.d.ts +4 -0
- package/dist/brain/terminal-cause.js +12 -0
- package/dist/brain/tool-call-id.d.ts +1 -0
- package/dist/brain/tool-call-id.js +3 -0
- package/dist/brain/tool-call-repair.js +34 -15
- package/dist/core/auto-compaction.js +33 -32
- package/dist/core/background-agent-store.d.ts +22 -22
- package/dist/core/background-agent-store.js +19 -23
- package/dist/core/checkpoint-store.d.ts +7 -13
- package/dist/core/checkpoint-store.js +39 -15
- package/dist/core/consolidate-scope.js +4 -3
- package/dist/core/context-edit.js +3 -0
- package/dist/core/context-guard.js +3 -0
- package/dist/core/file-snapshot-store.d.ts +8 -0
- package/dist/core/file-snapshot-store.js +4 -3
- package/dist/core/git-worktree-env.d.ts +6 -1
- package/dist/core/git-worktree-env.js +12 -1
- package/dist/core/hooks.d.ts +4 -1
- package/dist/core/hooks.js +35 -7
- package/dist/core/lsp-diagnostics.js +13 -7
- package/dist/core/lsp-protocol.d.ts +1 -1
- package/dist/core/lsp-protocol.js +20 -8
- package/dist/core/lsp-session.d.ts +12 -2
- package/dist/core/lsp-session.js +114 -49
- package/dist/core/lsp.d.ts +13 -1
- package/dist/core/lsp.js +65 -9
- package/dist/core/mailbox-store.d.ts +7 -10
- package/dist/core/mcp.d.ts +7 -10
- package/dist/core/mcp.js +7 -3
- package/dist/core/memory-engine/data-plane.js +4 -0
- package/dist/core/memory-engine/engine.d.ts +2 -2
- package/dist/core/memory-engine/engine.js +96 -31
- package/dist/core/memory-engine/file-backend.d.ts +2 -2
- package/dist/core/memory-engine/file-backend.js +38 -39
- package/dist/core/memory-engine/layout.d.ts +8 -0
- package/dist/core/memory-engine/layout.js +71 -4
- package/dist/core/memory-engine/scope-contract.js +9 -3
- package/dist/core/memory-engine/types.d.ts +1 -1
- package/dist/core/memory.js +3 -0
- package/dist/core/permission-rules.d.ts +2 -1
- package/dist/core/permission-rules.js +24 -3
- package/dist/core/runner/active-skill-scope.d.ts +0 -1
- package/dist/core/runner/active-skill-scope.js +1 -15
- package/dist/core/runner/assemble-result.d.ts +3 -8
- package/dist/core/runner/assemble-result.js +3 -5
- package/dist/core/runner/compaction-call-options.d.ts +7 -0
- package/dist/core/runner/compaction-call-options.js +30 -0
- package/dist/core/runner/prepare-task.d.ts +6 -23
- package/dist/core/runner/prepare-task.js +31 -53
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +86 -178
- package/dist/core/runner/session-rule-policy.js +4 -1
- package/dist/core/runner/teardown-bounded.d.ts +7 -0
- package/dist/core/runner/teardown-bounded.js +36 -0
- package/dist/core/runner/tool-disclosure.d.ts +2 -5
- package/dist/core/runner/turn-attachments.d.ts +5 -15
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runner/usage-accounting.d.ts +6 -0
- package/dist/core/runner/usage-accounting.js +9 -8
- package/dist/core/safe-notify.d.ts +17 -0
- package/dist/core/safe-notify.js +59 -0
- package/dist/core/secret-env.js +5 -1
- package/dist/core/sensitive-path-policy.js +11 -4
- package/dist/core/store-contracts/checkpoint-store-contract.js +1 -1
- package/dist/core/store-contracts/session-repo-contract.js +40 -0
- package/dist/core/stub-env.d.ts +2 -6
- package/dist/core/surrogate-safe-slice.d.ts +4 -0
- package/dist/core/surrogate-safe-slice.js +18 -0
- package/dist/core/task-registry-monitor.js +15 -14
- package/dist/core/task-registry-shared.d.ts +22 -0
- package/dist/core/task-registry-shared.js +16 -0
- package/dist/core/task-registry.d.ts +1 -0
- package/dist/core/task-registry.js +40 -57
- package/dist/core/tool-errors.js +5 -4
- package/dist/core/tool-name-aliases.js +2 -1
- package/dist/core/tool-policy.js +72 -19
- package/dist/core/tool-result-store.d.ts +1 -0
- package/dist/core/tool-result-store.js +17 -1
- package/dist/core/trace.d.ts +3 -9
- package/dist/core/types.d.ts +16 -12
- package/dist/core/untrusted-egress.js +7 -5
- package/dist/core/workflow-journal-store.d.ts +9 -20
- package/dist/core/workflow-run-store.d.ts +11 -10
- package/dist/core/workflow-run-store.js +22 -16
- package/dist/engine/compaction/compaction.d.ts +1 -0
- package/dist/engine/compaction/compaction.js +14 -5
- package/dist/engine/compaction/utils.js +4 -2
- package/dist/engine/execution-env/node-execution-env.d.ts +2 -24
- package/dist/engine/harness/agent-harness.d.ts +1 -0
- package/dist/engine/harness/agent-harness.js +13 -33
- package/dist/engine/harness/types.d.ts +29 -67
- package/dist/engine/llm/types.d.ts +1 -1
- package/dist/engine/loop/agent-loop.js +11 -4
- package/dist/engine/loop/types.d.ts +11 -9
- package/dist/engine/lsp/frame-decoder.js +6 -3
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -0
- package/dist/engine/lsp/node-lsp-manager.js +103 -26
- package/dist/engine/lsp/stdio-lsp-transport.js +16 -7
- package/dist/engine/session/memory-repo.d.ts +2 -6
- package/dist/engine/session/session.d.ts +5 -30
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/internal/harness-types.d.ts +3 -1
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/orchestration/run-spec.js +7 -4
- package/dist/orchestration/run-workflow-tool.js +3 -35
- package/dist/orchestration/workflow-governance.js +43 -9
- package/dist/orchestration/workflow-sandbox-conformance.js +85 -51
- package/dist/orchestration/workflow-script-store.js +34 -4
- package/dist/orchestration/workflow.d.ts +3 -11
- package/dist/orchestration/workflow.js +164 -204
- package/dist/prompt-assembly/artifact-store.d.ts +2 -0
- package/dist/prompt-assembly/artifact-store.js +39 -24
- package/dist/prompt-assembly/assemble.js +38 -7
- package/dist/prompt-assembly/epoch.js +19 -2
- package/dist/prompt-assembly/event-registry.js +3 -3
- package/dist/prompt-assembly/explain.js +1 -1
- package/dist/prompt-assembly/tool-catalog.d.ts +10 -9
- package/dist/prompt-assembly/tool-catalog.js +16 -1
- package/dist/prompt-assembly/turn-snapshot.d.ts +2 -5
- package/dist/prompt-assembly/turn-snapshot.js +4 -1
- package/dist/stores/file/background-agent-store.d.ts +3 -12
- package/dist/stores/file/background-agent-store.js +3 -41
- package/dist/stores/file/checkpoint-store.js +6 -6
- package/dist/stores/file/fs-atomic.d.ts +3 -0
- package/dist/stores/file/fs-atomic.js +53 -8
- package/dist/stores/file/index.d.ts +1 -0
- package/dist/stores/file/index.js +7 -0
- package/dist/stores/file/mailbox-store.d.ts +2 -6
- package/dist/stores/file/session-policy-store.js +15 -1
- package/dist/stores/file/session-store.d.ts +4 -6
- package/dist/stores/file/session-store.js +28 -1
- package/dist/stores/file/workflow-journal-store.d.ts +3 -11
- package/dist/stores/file/workflow-run-store.d.ts +3 -7
- package/dist/stores/file/workflow-run-store.js +4 -17
- package/dist/tools/fs/bash-readonly-classifier.d.ts +2 -0
- package/dist/tools/fs/bash-readonly-classifier.js +73 -21
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +407 -91
- package/dist/tools/fs/fs-pdf.d.ts +2 -12
- package/dist/tools/fs/fs-shared.js +2 -2
- package/dist/tools/fs/notebook.d.ts +2 -5
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +43 -14
- package/dist/tools/fs/search.js +5 -5
- package/dist/tools/loop-tick.js +1 -1
- package/dist/tools/monitor.d.ts +1 -0
- package/dist/tools/monitor.js +57 -9
- package/dist/tools/worktree.d.ts +4 -7
- package/dist/tools/worktree.js +235 -143
- package/package.json +9 -2
package/dist/agents/cascade.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface CascadeConfig {
|
|
|
14
14
|
index: number;
|
|
15
15
|
model: ModelRef;
|
|
16
16
|
}) => GateVerdict | Promise<GateVerdict>;
|
|
17
|
+
acceptPartial?: boolean;
|
|
17
18
|
maxEscalations?: number;
|
|
18
19
|
costCeilingMicroUsd?: number;
|
|
19
20
|
totalTimeoutMs?: number;
|
|
@@ -28,14 +29,18 @@ export interface CascadeAttempt {
|
|
|
28
29
|
index: number;
|
|
29
30
|
model: ModelRef;
|
|
30
31
|
passed: boolean;
|
|
31
|
-
costMicroUsd
|
|
32
|
+
costMicroUsd?: number;
|
|
32
33
|
status: TaskResult["status"];
|
|
33
34
|
errorCode?: string;
|
|
34
35
|
gateError?: string;
|
|
36
|
+
timedOut?: true;
|
|
37
|
+
cancelled?: true;
|
|
38
|
+
statusRejected?: true;
|
|
35
39
|
diagnostics?: string;
|
|
36
40
|
}
|
|
37
41
|
export interface CascadeRunResult extends TaskResult {
|
|
38
42
|
cascadeOutcome: "passed" | "exhausted";
|
|
43
|
+
costCeilingUnenforceable?: true;
|
|
39
44
|
escalated: boolean;
|
|
40
45
|
finalRung: number;
|
|
41
46
|
attempts: CascadeAttempt[];
|
package/dist/agents/cascade.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { releaseSession } from "./session-util.js";
|
|
2
2
|
import { mapNestedSuspend, isDurablePause } from "./suspend-guard.js";
|
|
3
|
+
import { buildCumulativeStats } from "./cumulative-stats.js";
|
|
4
|
+
import { createSafeNotifier } from "../core/safe-notify.js";
|
|
5
|
+
const CASCADE_ON_RUNG_SITE = "cascade.onRung";
|
|
3
6
|
function createDefaultGate(spec) {
|
|
4
7
|
const requiresStructured = spec.outputSchema != null;
|
|
5
8
|
return (result) => result.status === "completed" && (!requiresStructured || result.structuredOutput !== undefined);
|
|
@@ -13,61 +16,86 @@ export async function runCascade(runner, spec, config) {
|
|
|
13
16
|
const maxRungs = Math.min(ladder.length, Math.max(0, maxEscalations) + 1);
|
|
14
17
|
const gate = config.gate ?? createDefaultGate(spec);
|
|
15
18
|
const startedAt = Date.now();
|
|
19
|
+
const deadlineAt = config.totalTimeoutMs != null ? startedAt + config.totalTimeoutMs : undefined;
|
|
20
|
+
const notifier = createSafeNotifier();
|
|
16
21
|
const attempts = [];
|
|
17
22
|
let totalCost = 0;
|
|
23
|
+
let ceilingCostKnown = true;
|
|
18
24
|
let ownTokens = 0, ownTurns = 0, ownPrompt = 0, ownCached = 0, ownOutput = 0, ownCostMicro = 0;
|
|
25
|
+
let ownTotalInput = 0;
|
|
19
26
|
let ownCacheWrite = 0, ownCacheWriteLong = 0;
|
|
20
27
|
let nestedTokens = 0, nestedTurns = 0, nestedTasks = 0, nestedCostMicro = 0;
|
|
21
28
|
let ownLlmRootMicro = 0, ownCompactionMicro = 0;
|
|
29
|
+
let anyRungUnpriced = false;
|
|
22
30
|
let hrCount = 0, hrWaitMs = 0;
|
|
23
31
|
const hrGates = [];
|
|
24
|
-
const cumulativeStats = (baseStats) => ({
|
|
25
|
-
...baseStats,
|
|
32
|
+
const cumulativeStats = (baseStats) => buildCumulativeStats(baseStats, {
|
|
26
33
|
tokens: ownTokens,
|
|
27
34
|
turns: ownTurns,
|
|
28
|
-
promptTokens: ownPrompt
|
|
29
|
-
totalInputTokens:
|
|
30
|
-
cachedTokens: ownCached
|
|
31
|
-
outputTokens: ownOutput
|
|
32
|
-
cacheWriteTokens: ownCacheWrite
|
|
33
|
-
cacheWriteTokensLong: ownCacheWriteLong
|
|
34
|
-
cacheHitRate: ownPrompt > 0 ? ownCached / ownPrompt : undefined,
|
|
35
|
+
promptTokens: ownPrompt,
|
|
36
|
+
totalInputTokens: ownTotalInput,
|
|
37
|
+
cachedTokens: ownCached,
|
|
38
|
+
outputTokens: ownOutput,
|
|
39
|
+
cacheWriteTokens: ownCacheWrite,
|
|
40
|
+
cacheWriteTokensLong: ownCacheWriteLong,
|
|
35
41
|
costMicroUsd: ownCostMicro,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
nestedSubagentMicroUsd: nestedCostMicro,
|
|
42
|
-
memoryConsolidationMicroUsd: 0,
|
|
43
|
-
compactionMicroUsd: ownCompactionMicro,
|
|
44
|
-
},
|
|
45
|
-
humanReview: hrCount > 0 ? { count: hrCount, totalWaitMs: hrWaitMs, gates: hrGates } : undefined,
|
|
42
|
+
anyLegUnpriced: anyRungUnpriced,
|
|
43
|
+
llmRootMicroUsd: ownLlmRootMicro,
|
|
44
|
+
compactionMicroUsd: ownCompactionMicro,
|
|
45
|
+
nested: { tokens: nestedTokens, turns: nestedTurns, tasks: nestedTasks, costMicroUsd: nestedCostMicro },
|
|
46
|
+
humanReview: { count: hrCount, totalWaitMs: hrWaitMs, gates: hrGates },
|
|
46
47
|
});
|
|
47
48
|
let lastResult;
|
|
48
49
|
let passedRung = -1;
|
|
49
50
|
const { sessionId: _drop, ...specBase } = spec;
|
|
50
51
|
for (let i = 0; i < maxRungs; i++) {
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
break;
|
|
52
|
+
if (deadlineAt !== undefined && Date.now() >= deadlineAt)
|
|
53
|
+
break;
|
|
54
|
+
if (i > 0 && config.costCeilingMicroUsd != null && (totalCost >= config.costCeilingMicroUsd || !ceilingCostKnown)) {
|
|
55
|
+
break;
|
|
56
56
|
}
|
|
57
57
|
const rung = ladder[i];
|
|
58
|
-
|
|
58
|
+
let deadlineFired = false;
|
|
59
|
+
const rungAbort = new AbortController();
|
|
60
|
+
let deadlineTimer;
|
|
61
|
+
if (deadlineAt !== undefined) {
|
|
62
|
+
deadlineTimer = setTimeout(() => {
|
|
63
|
+
deadlineFired = true;
|
|
64
|
+
rungAbort.abort(new Error(`runCascade: totalTimeoutMs (${config.totalTimeoutMs}ms) reached`));
|
|
65
|
+
}, Math.max(0, deadlineAt - Date.now()));
|
|
66
|
+
deadlineTimer.unref?.();
|
|
67
|
+
}
|
|
68
|
+
const rungSignal = deadlineAt === undefined
|
|
69
|
+
? specBase.signal
|
|
70
|
+
: specBase.signal
|
|
71
|
+
? AbortSignal.any([specBase.signal, rungAbort.signal])
|
|
72
|
+
: rungAbort.signal;
|
|
73
|
+
let result;
|
|
74
|
+
try {
|
|
75
|
+
result = await runner.runTask({ ...specBase, model: rung.model, ...(rung.overrides ?? {}), ...(rungSignal ? { signal: rungSignal } : {}) });
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
if (deadlineTimer !== undefined)
|
|
79
|
+
clearTimeout(deadlineTimer);
|
|
80
|
+
}
|
|
59
81
|
lastResult = result;
|
|
60
82
|
const s = result.stats;
|
|
61
|
-
const
|
|
62
|
-
|
|
83
|
+
const rungCostKnown = s.costMicroUsd !== undefined && (s.nested === undefined || s.nested.costMicroUsd !== undefined);
|
|
84
|
+
const rungCost = rungCostKnown ? (s.costMicroUsd ?? 0) + (s.nested?.costMicroUsd ?? 0) : undefined;
|
|
85
|
+
totalCost += rungCost ?? 0;
|
|
86
|
+
if (!rungCostKnown)
|
|
87
|
+
ceilingCostKnown = false;
|
|
63
88
|
ownTokens += s.tokens;
|
|
64
89
|
ownTurns += s.turns;
|
|
65
90
|
ownPrompt += s.promptTokens ?? 0;
|
|
91
|
+
ownTotalInput += s.totalInputTokens ?? 0;
|
|
66
92
|
ownCached += s.cachedTokens ?? 0;
|
|
67
93
|
ownOutput += s.outputTokens ?? 0;
|
|
68
94
|
ownCacheWrite += s.cacheWriteTokens ?? 0;
|
|
69
95
|
ownCacheWriteLong += s.cacheWriteTokensLong ?? 0;
|
|
70
96
|
ownCostMicro += s.costMicroUsd ?? 0;
|
|
97
|
+
if (s.costMicroUsd === undefined)
|
|
98
|
+
anyRungUnpriced = true;
|
|
71
99
|
if (s.costBreakdown) {
|
|
72
100
|
ownLlmRootMicro += s.costBreakdown.llmRootMicroUsd;
|
|
73
101
|
ownCompactionMicro += s.costBreakdown.compactionMicroUsd;
|
|
@@ -90,11 +118,26 @@ export async function runCascade(runner, spec, config) {
|
|
|
90
118
|
...mapped,
|
|
91
119
|
stats: cumulativeStats(result.stats),
|
|
92
120
|
cascadeOutcome: "exhausted",
|
|
121
|
+
...(config.costCeilingMicroUsd != null && !ceilingCostKnown ? { costCeilingUnenforceable: true } : {}),
|
|
93
122
|
escalated: i > 0,
|
|
94
123
|
finalRung: i,
|
|
95
124
|
attempts,
|
|
96
125
|
};
|
|
97
126
|
}
|
|
127
|
+
if (deadlineFired || specBase.signal?.aborted === true) {
|
|
128
|
+
attempts.push({
|
|
129
|
+
index: i,
|
|
130
|
+
model: rung.model,
|
|
131
|
+
passed: false,
|
|
132
|
+
costMicroUsd: rungCost,
|
|
133
|
+
status: result.status,
|
|
134
|
+
errorCode: result.errorCode,
|
|
135
|
+
...(deadlineFired ? { timedOut: true } : { cancelled: true }),
|
|
136
|
+
});
|
|
137
|
+
notifier.notify(() => config.onRung?.({ index: i, model: rung.model, passed: false, result }), CASCADE_ON_RUNG_SITE);
|
|
138
|
+
await releaseSession(runner, result.sessionId);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
98
141
|
let passed = false;
|
|
99
142
|
let gateError;
|
|
100
143
|
let diagnostics;
|
|
@@ -112,6 +155,9 @@ export async function runCascade(runner, spec, config) {
|
|
|
112
155
|
passed = false;
|
|
113
156
|
gateError = e instanceof Error ? e.message : String(e);
|
|
114
157
|
}
|
|
158
|
+
const statusRejected = passed && config.acceptPartial !== true && result.status !== "completed";
|
|
159
|
+
if (statusRejected)
|
|
160
|
+
passed = false;
|
|
115
161
|
attempts.push({
|
|
116
162
|
index: i,
|
|
117
163
|
model: rung.model,
|
|
@@ -121,12 +167,9 @@ export async function runCascade(runner, spec, config) {
|
|
|
121
167
|
errorCode: result.errorCode,
|
|
122
168
|
gateError,
|
|
123
169
|
diagnostics,
|
|
170
|
+
...(statusRejected ? { statusRejected: true } : {}),
|
|
124
171
|
});
|
|
125
|
-
|
|
126
|
-
config.onRung?.({ index: i, model: rung.model, passed, result });
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
}
|
|
172
|
+
notifier.notify(() => config.onRung?.({ index: i, model: rung.model, passed, result }), CASCADE_ON_RUNG_SITE);
|
|
130
173
|
}
|
|
131
174
|
finally {
|
|
132
175
|
await releaseSession(runner, result.sessionId);
|
|
@@ -142,6 +185,7 @@ export async function runCascade(runner, spec, config) {
|
|
|
142
185
|
...base,
|
|
143
186
|
stats: cumulativeStats(base.stats),
|
|
144
187
|
cascadeOutcome: passedRung >= 0 ? "passed" : "exhausted",
|
|
188
|
+
...(config.costCeilingMicroUsd != null && !ceilingCostKnown ? { costCeilingUnenforceable: true } : {}),
|
|
145
189
|
escalated: finalRung > 0,
|
|
146
190
|
finalRung,
|
|
147
191
|
attempts,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TaskResult } from "../core/types.js";
|
|
2
|
+
export interface CumulativeNestedTotals {
|
|
3
|
+
tokens: number;
|
|
4
|
+
turns: number;
|
|
5
|
+
tasks: number;
|
|
6
|
+
costMicroUsd: number;
|
|
7
|
+
}
|
|
8
|
+
export interface CumulativeHumanReviewTotals {
|
|
9
|
+
count: number;
|
|
10
|
+
totalWaitMs: number;
|
|
11
|
+
gates: NonNullable<TaskResult["stats"]["humanReview"]>["gates"];
|
|
12
|
+
}
|
|
13
|
+
export interface CumulativeStatsAccumulator {
|
|
14
|
+
tokens: number;
|
|
15
|
+
turns: number;
|
|
16
|
+
promptTokens: number;
|
|
17
|
+
totalInputTokens: number;
|
|
18
|
+
cachedTokens: number;
|
|
19
|
+
outputTokens: number;
|
|
20
|
+
cacheWriteTokens: number;
|
|
21
|
+
cacheWriteTokensLong: number;
|
|
22
|
+
costMicroUsd: number | undefined;
|
|
23
|
+
anyLegUnpriced: boolean;
|
|
24
|
+
llmRootMicroUsd: number;
|
|
25
|
+
compactionMicroUsd: number;
|
|
26
|
+
nested: CumulativeNestedTotals;
|
|
27
|
+
humanReview: CumulativeHumanReviewTotals;
|
|
28
|
+
}
|
|
29
|
+
export declare function buildCumulativeStats(base: TaskResult["stats"], acc: CumulativeStatsAccumulator): TaskResult["stats"];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function buildCumulativeStats(base, acc) {
|
|
2
|
+
return {
|
|
3
|
+
...base,
|
|
4
|
+
tokens: acc.tokens,
|
|
5
|
+
turns: acc.turns,
|
|
6
|
+
promptTokens: acc.promptTokens || undefined,
|
|
7
|
+
totalInputTokens: acc.totalInputTokens || undefined,
|
|
8
|
+
cachedTokens: acc.cachedTokens || undefined,
|
|
9
|
+
outputTokens: acc.outputTokens || undefined,
|
|
10
|
+
cacheWriteTokens: acc.cacheWriteTokens || undefined,
|
|
11
|
+
cacheWriteTokensLong: acc.cacheWriteTokensLong || undefined,
|
|
12
|
+
cacheHitRate: acc.totalInputTokens > 0 ? Math.min(1, acc.cachedTokens / acc.totalInputTokens) : undefined,
|
|
13
|
+
costMicroUsd: acc.anyLegUnpriced ? undefined : acc.costMicroUsd,
|
|
14
|
+
nested: acc.nested.tokens > 0 || acc.nested.tasks > 0
|
|
15
|
+
? { tokens: acc.nested.tokens, turns: acc.nested.turns, tasks: acc.nested.tasks, costMicroUsd: acc.nested.costMicroUsd || undefined }
|
|
16
|
+
: undefined,
|
|
17
|
+
costBreakdown: acc.anyLegUnpriced
|
|
18
|
+
? undefined
|
|
19
|
+
: {
|
|
20
|
+
llmRootMicroUsd: acc.llmRootMicroUsd,
|
|
21
|
+
nestedSubagentMicroUsd: acc.nested.costMicroUsd,
|
|
22
|
+
memoryConsolidationMicroUsd: 0,
|
|
23
|
+
compactionMicroUsd: acc.compactionMicroUsd,
|
|
24
|
+
},
|
|
25
|
+
humanReview: acc.humanReview.count > 0
|
|
26
|
+
? { count: acc.humanReview.count, totalWaitMs: acc.humanReview.totalWaitMs, gates: acc.humanReview.gates }
|
|
27
|
+
: undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -63,7 +63,9 @@ export declare class ObserverDigestTap {
|
|
|
63
63
|
constructor(flush: (activity: ObserverDigestEvent[]) => void);
|
|
64
64
|
record(e: TaskEvent): void;
|
|
65
65
|
private flushText;
|
|
66
|
+
private readonly notifier;
|
|
66
67
|
private safeFlush;
|
|
68
|
+
get flushFailures(): number;
|
|
67
69
|
flushSegment(): void;
|
|
68
70
|
finish(reason: string): void;
|
|
69
71
|
}
|
|
@@ -112,7 +114,9 @@ export declare class ObserverPairing {
|
|
|
112
114
|
});
|
|
113
115
|
enqueueSegment(activity: ObserverDigestEvent[], trigger?: string): void;
|
|
114
116
|
private pumping?;
|
|
117
|
+
private readonly notifier;
|
|
115
118
|
private safeOnError;
|
|
119
|
+
get onErrorFailures(): number;
|
|
116
120
|
drain(): Promise<void>;
|
|
117
121
|
private pump;
|
|
118
122
|
private deliverBatch;
|
package/dist/agents/observer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { defineTool } from "../core/tools.js";
|
|
3
|
+
import { createSafeNotifier } from "../core/safe-notify.js";
|
|
3
4
|
export const OBSERVER_EVENT_TAGS = ["tool-call", "user-message", "tool-result", "turn-ended"];
|
|
4
5
|
const TAG_ESCAPE_RE = new RegExp(`<(?=/?(?:${OBSERVER_EVENT_TAGS.join("|")})(?:[>\\s/]|$))`, "gi");
|
|
5
6
|
export function escapeObserverTags(text) {
|
|
@@ -181,12 +182,12 @@ export class ObserverDigestTap {
|
|
|
181
182
|
this.pending.push({ type: "assistant_text", text: this.textBuf });
|
|
182
183
|
this.textBuf = "";
|
|
183
184
|
}
|
|
185
|
+
notifier = createSafeNotifier();
|
|
184
186
|
safeFlush(slice) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
187
|
+
this.notifier.notify(() => this.flush(slice), "observer.digestTap.flush");
|
|
188
|
+
}
|
|
189
|
+
get flushFailures() {
|
|
190
|
+
return this.notifier.failuresAt("observer.digestTap.flush");
|
|
190
191
|
}
|
|
191
192
|
flushSegment() {
|
|
192
193
|
if (this.finished)
|
|
@@ -270,12 +271,12 @@ export class ObserverPairing {
|
|
|
270
271
|
}).catch(() => undefined);
|
|
271
272
|
}
|
|
272
273
|
pumping;
|
|
274
|
+
notifier = createSafeNotifier();
|
|
273
275
|
safeOnError(err) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
276
|
+
this.notifier.notify(() => this.onError?.(err), "observer.pairing.onError");
|
|
277
|
+
}
|
|
278
|
+
get onErrorFailures() {
|
|
279
|
+
return this.notifier.failuresAt("observer.pairing.onError");
|
|
279
280
|
}
|
|
280
281
|
async drain() {
|
|
281
282
|
while (this.pumping) {
|
|
@@ -12,6 +12,7 @@ export interface OracleResult {
|
|
|
12
12
|
trace?: string;
|
|
13
13
|
flaky: boolean;
|
|
14
14
|
retries: number;
|
|
15
|
+
costMicroUsd?: number;
|
|
15
16
|
}
|
|
16
17
|
export type RepairOracle = (graderEnv: ExecutionEnv, evidence: string | undefined) => Promise<OracleResult>;
|
|
17
18
|
export interface RepairBundle {
|
|
@@ -41,6 +42,7 @@ export interface RepairResult extends VerificationResult {
|
|
|
41
42
|
terminal: RepairTerminal;
|
|
42
43
|
bundle: RepairBundle;
|
|
43
44
|
isolationClass?: OracleIsolationVerdict["isolationClass"];
|
|
45
|
+
oracleCostMicroUsd: number;
|
|
44
46
|
}
|
|
45
47
|
export declare function isolationPermitsAutoAccept(verdict: OracleIsolationVerdict): boolean;
|
|
46
48
|
export declare function repairBundleFromCheckpoint(cp: Checkpoint): RepairBundle | undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isDurablePause } from "./suspend-guard.js";
|
|
2
2
|
import { delimitUntrusted, sanitizeUntrustedText } from "../core/untrusted-text.js";
|
|
3
3
|
import { assertOracleIsolation } from "../core/oracle-isolation.js";
|
|
4
|
+
import { buildCumulativeStats } from "./cumulative-stats.js";
|
|
4
5
|
export function isolationPermitsAutoAccept(verdict) {
|
|
5
6
|
return verdict.protected && verdict.isolationClass === "out_of_process";
|
|
6
7
|
}
|
|
@@ -55,6 +56,7 @@ export async function runRepairLoop(runner, implSpec, config) {
|
|
|
55
56
|
terminal: "oracle.unprotected",
|
|
56
57
|
...(isolationClass ? { isolationClass } : {}),
|
|
57
58
|
bundle: { ...freshBundle(), attemptCount: config.resumeBundle?.attemptCount ?? 0 },
|
|
59
|
+
oracleCostMicroUsd: 0,
|
|
58
60
|
verification: outcomeFor("oracle.unprotected", oracle, 0),
|
|
59
61
|
};
|
|
60
62
|
};
|
|
@@ -77,7 +79,10 @@ export async function runRepairLoop(runner, implSpec, config) {
|
|
|
77
79
|
else if (config.workerEnv !== undefined && config.workerEnv === config.graderEnv) {
|
|
78
80
|
return unprotectedResult("graderEnv must not be the worker env");
|
|
79
81
|
}
|
|
80
|
-
const maxAttempts = Math.max(1, Math.floor(config.maxAttempts));
|
|
82
|
+
const maxAttempts = Number.isFinite(config.maxAttempts) ? Math.max(1, Math.floor(config.maxAttempts)) : 1;
|
|
83
|
+
if (!Number.isFinite(config.maxAttempts)) {
|
|
84
|
+
config.onWarn?.(new Error(`repair-loop: maxAttempts must be a finite number (got ${config.maxAttempts}) — falling back to 1 attempt`));
|
|
85
|
+
}
|
|
81
86
|
const bundle = config.resumeBundle
|
|
82
87
|
? { ...config.resumeBundle, diagnostics: [...config.resumeBundle.diagnostics], rejectedHypotheses: [...config.resumeBundle.rejectedHypotheses] }
|
|
83
88
|
: freshBundle();
|
|
@@ -85,34 +90,126 @@ export async function runRepairLoop(runner, implSpec, config) {
|
|
|
85
90
|
let spend = 0;
|
|
86
91
|
let lastResult;
|
|
87
92
|
let lastOracle;
|
|
93
|
+
let ownTokens = 0, ownTurns = 0, ownPrompt = 0, ownTotalInput = 0, ownCached = 0, ownOutput = 0;
|
|
94
|
+
let ownCacheWrite = 0, ownCacheWriteLong = 0, ownCostMicro = 0;
|
|
95
|
+
let ownLlmRootMicro = 0, ownCompactionMicro = 0;
|
|
96
|
+
let anyAttemptUnpriced = false;
|
|
97
|
+
let nestedTokens = 0, nestedTurns = 0, nestedTasks = 0, nestedCostMicro = 0;
|
|
98
|
+
let hrCount = 0, hrWaitMs = 0;
|
|
99
|
+
const hrGates = [];
|
|
100
|
+
let oracleCostMicroUsd = 0;
|
|
101
|
+
const accumulate = (s) => {
|
|
102
|
+
ownTokens += s.tokens;
|
|
103
|
+
ownTurns += s.turns;
|
|
104
|
+
ownPrompt += s.promptTokens ?? 0;
|
|
105
|
+
ownTotalInput += s.totalInputTokens ?? 0;
|
|
106
|
+
ownCached += s.cachedTokens ?? 0;
|
|
107
|
+
ownOutput += s.outputTokens ?? 0;
|
|
108
|
+
ownCacheWrite += s.cacheWriteTokens ?? 0;
|
|
109
|
+
ownCacheWriteLong += s.cacheWriteTokensLong ?? 0;
|
|
110
|
+
ownCostMicro += s.costMicroUsd ?? 0;
|
|
111
|
+
if (s.costMicroUsd === undefined)
|
|
112
|
+
anyAttemptUnpriced = true;
|
|
113
|
+
if (s.costBreakdown) {
|
|
114
|
+
ownLlmRootMicro += s.costBreakdown.llmRootMicroUsd;
|
|
115
|
+
ownCompactionMicro += s.costBreakdown.compactionMicroUsd;
|
|
116
|
+
}
|
|
117
|
+
if (s.humanReview) {
|
|
118
|
+
hrCount += s.humanReview.count;
|
|
119
|
+
hrWaitMs += s.humanReview.totalWaitMs;
|
|
120
|
+
hrGates.push(...s.humanReview.gates);
|
|
121
|
+
}
|
|
122
|
+
if (s.nested) {
|
|
123
|
+
nestedTokens += s.nested.tokens;
|
|
124
|
+
nestedTurns += s.nested.turns;
|
|
125
|
+
nestedTasks += s.nested.tasks;
|
|
126
|
+
nestedCostMicro += s.nested.costMicroUsd ?? 0;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const cumulativeStats = (base) => buildCumulativeStats(base, {
|
|
130
|
+
tokens: ownTokens,
|
|
131
|
+
turns: ownTurns,
|
|
132
|
+
promptTokens: ownPrompt,
|
|
133
|
+
totalInputTokens: ownTotalInput,
|
|
134
|
+
cachedTokens: ownCached,
|
|
135
|
+
outputTokens: ownOutput,
|
|
136
|
+
cacheWriteTokens: ownCacheWrite,
|
|
137
|
+
cacheWriteTokensLong: ownCacheWriteLong,
|
|
138
|
+
costMicroUsd: ownCostMicro,
|
|
139
|
+
anyLegUnpriced: anyAttemptUnpriced,
|
|
140
|
+
llmRootMicroUsd: ownLlmRootMicro,
|
|
141
|
+
compactionMicroUsd: ownCompactionMicro,
|
|
142
|
+
nested: { tokens: nestedTokens, turns: nestedTurns, tasks: nestedTasks, costMicroUsd: nestedCostMicro },
|
|
143
|
+
humanReview: { count: hrCount, totalWaitMs: hrWaitMs, gates: hrGates },
|
|
144
|
+
});
|
|
88
145
|
let restartedOnce = false;
|
|
89
146
|
let restartUsed = false;
|
|
90
147
|
const cleanRestartSpec = () => {
|
|
91
148
|
const { sessionId: _drop, ...rest } = implSpec;
|
|
92
149
|
return { ...rest, objective: `${implSpec.objective}\n\n${repairObjective(bundle)}` };
|
|
93
150
|
};
|
|
151
|
+
const exhaustedBeforeDispatch = () => ({
|
|
152
|
+
taskId: implSpec.taskId ?? "",
|
|
153
|
+
status: "failed",
|
|
154
|
+
result: `repair loop: attempt budget already spent (attemptCount ${bundle.attemptCount} >= maxAttempts ${maxAttempts}) — no attempt dispatched`,
|
|
155
|
+
errorCode: "repair.attempts_exhausted",
|
|
156
|
+
sessionId: implSpec.sessionId ?? "",
|
|
157
|
+
stats: { tokens: 0, turns: 0, costMicroUsd: 0 },
|
|
158
|
+
terminal: "gave_up",
|
|
159
|
+
bundle,
|
|
160
|
+
...(isolationClass ? { isolationClass } : {}),
|
|
161
|
+
oracleCostMicroUsd,
|
|
162
|
+
verification: outcomeFor("gave_up", undefined, bundle.attemptCount),
|
|
163
|
+
});
|
|
94
164
|
while (true) {
|
|
165
|
+
if (!restartedOnce && bundle.attemptCount >= maxAttempts)
|
|
166
|
+
return exhaustedBeforeDispatch();
|
|
95
167
|
const isRepair = bundle.attemptCount > 0 && lastResult !== undefined && !restartedOnce;
|
|
168
|
+
const isSeededResume = !restartedOnce &&
|
|
169
|
+
lastResult === undefined &&
|
|
170
|
+
config.resumeBundle !== undefined &&
|
|
171
|
+
(bundle.failureTrace.trim() !== "" || bundle.diagnostics.length > 0);
|
|
96
172
|
const spec = restartedOnce
|
|
97
173
|
? cleanRestartSpec()
|
|
98
174
|
: isRepair
|
|
99
175
|
? { ...implSpec, sessionId: lastResult.sessionId, objective: repairObjective(bundle) }
|
|
100
|
-
:
|
|
176
|
+
: isSeededResume
|
|
177
|
+
? {
|
|
178
|
+
...implSpec,
|
|
179
|
+
objective: implSpec.sessionId ? repairObjective(bundle) : `${implSpec.objective}\n\n${repairObjective(bundle)}`,
|
|
180
|
+
}
|
|
181
|
+
: implSpec;
|
|
101
182
|
restartedOnce = false;
|
|
102
183
|
const result = await runner.runTask(spec, { repairBundle: bundle });
|
|
103
184
|
spend += (result.stats.costMicroUsd ?? 0) + (result.stats.nested?.costMicroUsd ?? 0);
|
|
185
|
+
accumulate(result.stats);
|
|
104
186
|
lastResult = result;
|
|
105
187
|
bundle.attemptCount += 1;
|
|
106
188
|
if (isDurablePause(result.status)) {
|
|
107
189
|
return {
|
|
108
190
|
...result,
|
|
191
|
+
stats: cumulativeStats(result.stats),
|
|
109
192
|
terminal: "gave_up",
|
|
110
193
|
bundle,
|
|
111
194
|
...(isolationClass ? { isolationClass } : {}),
|
|
195
|
+
oracleCostMicroUsd,
|
|
112
196
|
verification: { verdict: "unverified", unverifiedReason: result.status === "needs_review" ? "needs_review" : "suspended", rounds: bundle.attemptCount, findings: [] },
|
|
113
197
|
};
|
|
114
198
|
}
|
|
199
|
+
if (result.status !== "completed") {
|
|
200
|
+
return {
|
|
201
|
+
...result,
|
|
202
|
+
stats: cumulativeStats(result.stats),
|
|
203
|
+
terminal: "gave_up",
|
|
204
|
+
bundle,
|
|
205
|
+
...(isolationClass ? { isolationClass } : {}),
|
|
206
|
+
oracleCostMicroUsd,
|
|
207
|
+
verification: { verdict: "unverified", unverifiedReason: "impl_incomplete", rounds: bundle.attemptCount, findings: [] },
|
|
208
|
+
};
|
|
209
|
+
}
|
|
115
210
|
const oracle = await config.oracle(config.graderEnv, result.result);
|
|
211
|
+
oracleCostMicroUsd += oracle.costMicroUsd ?? 0;
|
|
212
|
+
spend += oracle.costMicroUsd ?? 0;
|
|
116
213
|
lastOracle = oracle;
|
|
117
214
|
bundle.oracleTier = oracle.tier;
|
|
118
215
|
const projected = terminalForTier(oracle);
|
|
@@ -120,9 +217,11 @@ export async function runRepairLoop(runner, implSpec, config) {
|
|
|
120
217
|
if (projected !== undefined) {
|
|
121
218
|
return {
|
|
122
219
|
...result,
|
|
220
|
+
stats: cumulativeStats(result.stats),
|
|
123
221
|
terminal: projected,
|
|
124
222
|
bundle,
|
|
125
223
|
...(isolationClass ? { isolationClass } : {}),
|
|
224
|
+
oracleCostMicroUsd,
|
|
126
225
|
verification: outcomeFor(projected, oracle, bundle.attemptCount),
|
|
127
226
|
};
|
|
128
227
|
}
|
|
@@ -140,7 +239,15 @@ export async function runRepairLoop(runner, implSpec, config) {
|
|
|
140
239
|
continue;
|
|
141
240
|
}
|
|
142
241
|
const terminal = "gave_up";
|
|
143
|
-
return {
|
|
242
|
+
return {
|
|
243
|
+
...lastResult,
|
|
244
|
+
stats: cumulativeStats(lastResult.stats),
|
|
245
|
+
terminal,
|
|
246
|
+
bundle,
|
|
247
|
+
...(isolationClass ? { isolationClass } : {}),
|
|
248
|
+
oracleCostMicroUsd,
|
|
249
|
+
verification: outcomeFor(terminal, lastOracle, bundle.attemptCount),
|
|
250
|
+
};
|
|
144
251
|
}
|
|
145
252
|
}
|
|
146
253
|
}
|
|
@@ -22,6 +22,14 @@ export interface SubagentRetainEntry {
|
|
|
22
22
|
activeAbort?: AbortController;
|
|
23
23
|
release: () => Promise<void>;
|
|
24
24
|
}
|
|
25
|
+
export type RetainEvictionTrigger = "ttl_sweep" | "ttl_timer" | "lru_overflow" | "session_ledger_lru" | "session_reap";
|
|
26
|
+
export interface RetainLedgerHooks {
|
|
27
|
+
onEvictionError?: (info: {
|
|
28
|
+
parentToolCallId?: string;
|
|
29
|
+
trigger: RetainEvictionTrigger;
|
|
30
|
+
error: Error;
|
|
31
|
+
}) => void;
|
|
32
|
+
}
|
|
25
33
|
export declare class SubagentRetainLedger {
|
|
26
34
|
readonly ttlMs: number;
|
|
27
35
|
readonly max: number;
|
|
@@ -29,10 +37,20 @@ export declare class SubagentRetainLedger {
|
|
|
29
37
|
private evictedIds;
|
|
30
38
|
private ttlTimers;
|
|
31
39
|
private isDisposed;
|
|
40
|
+
private readonly hooks;
|
|
41
|
+
private failedEvictions;
|
|
42
|
+
private lastFailure;
|
|
43
|
+
private readonly notifier;
|
|
32
44
|
constructor(config: true | {
|
|
33
45
|
ttlMs?: number;
|
|
34
46
|
max?: number;
|
|
35
|
-
});
|
|
47
|
+
}, hooks?: RetainLedgerHooks);
|
|
48
|
+
get evictionFailureCount(): number;
|
|
49
|
+
get lastEvictionError(): Error | undefined;
|
|
50
|
+
get evictionSinkFailureCount(): number;
|
|
51
|
+
noteDetachedFailure(trigger: RetainEvictionTrigger, error: unknown, parentToolCallId?: string): void;
|
|
52
|
+
private evictDetached;
|
|
53
|
+
disposeAllDetached(trigger: RetainEvictionTrigger): void;
|
|
36
54
|
private static readonly TOMBSTONES_MAX;
|
|
37
55
|
private tombstone;
|
|
38
56
|
private effectiveTtlMs;
|
|
@@ -54,7 +72,7 @@ export declare function getSessionRetainLedger(sessionId: string): SubagentRetai
|
|
|
54
72
|
export declare function getOrCreateSessionRetainLedger(sessionId: string, config: {
|
|
55
73
|
ttlMs: number;
|
|
56
74
|
max: number;
|
|
57
|
-
}): SubagentRetainLedger;
|
|
75
|
+
}, hooks?: RetainLedgerHooks): SubagentRetainLedger;
|
|
58
76
|
export declare function releaseSessionRetainLedger(sessionId: string): Promise<void>;
|
|
59
77
|
export declare function ensureSessionReapHook(registry: import("../core/task-registry.js").TaskRegistry): void;
|
|
60
78
|
export declare function createResumePrompt(marker: string, content: string): string;
|