@graphorin/agent 0.6.0 → 0.7.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 +96 -0
- package/README.md +31 -11
- package/dist/errors/index.d.ts +17 -4
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +19 -3
- package/dist/errors/index.js.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +153 -1930
- package/dist/factory.js.map +1 -1
- package/dist/fanout/index.d.ts +13 -1
- package/dist/fanout/index.d.ts.map +1 -1
- package/dist/fanout/index.js +13 -4
- package/dist/fanout/index.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -9
- package/dist/index.js.map +1 -1
- package/dist/lateral-leak/index.js +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/run-state/index.d.ts +32 -6
- package/dist/run-state/index.d.ts.map +1 -1
- package/dist/run-state/index.js +46 -22
- package/dist/run-state/index.js.map +1 -1
- package/dist/runtime/agent-surface.js +122 -0
- package/dist/runtime/agent-surface.js.map +1 -0
- package/dist/runtime/agent-to-tool.d.ts +51 -0
- package/dist/runtime/agent-to-tool.d.ts.map +1 -0
- package/dist/runtime/agent-to-tool.js +145 -0
- package/dist/runtime/agent-to-tool.js.map +1 -0
- package/dist/runtime/approvals.js +0 -0
- package/dist/runtime/approvals.js.map +1 -0
- package/dist/runtime/dispatch.js +108 -0
- package/dist/runtime/dispatch.js.map +1 -0
- package/dist/runtime/executor-wiring.js +128 -0
- package/dist/runtime/executor-wiring.js.map +1 -0
- package/dist/runtime/fallback-chain.js +139 -0
- package/dist/runtime/fallback-chain.js.map +1 -0
- package/dist/runtime/handoff.js +307 -0
- package/dist/runtime/handoff.js.map +1 -0
- package/dist/runtime/messages.d.ts +22 -0
- package/dist/runtime/messages.d.ts.map +1 -0
- package/dist/runtime/messages.js +204 -0
- package/dist/runtime/messages.js.map +1 -0
- package/dist/runtime/provider-events.js +117 -0
- package/dist/runtime/provider-events.js.map +1 -0
- package/dist/runtime/run-compaction.js +210 -0
- package/dist/runtime/run-compaction.js.map +1 -0
- package/dist/runtime/run-finish.js +48 -0
- package/dist/runtime/run-finish.js.map +1 -0
- package/dist/runtime/run-gates.js +336 -0
- package/dist/runtime/run-gates.js.map +1 -0
- package/dist/runtime/run-init.js +81 -0
- package/dist/runtime/run-init.js.map +1 -0
- package/dist/runtime/run-input.js +46 -0
- package/dist/runtime/run-input.js.map +1 -0
- package/dist/runtime/step-catalogue.js +173 -0
- package/dist/runtime/step-catalogue.js.map +1 -0
- package/dist/runtime/tool-call-walk.js +189 -0
- package/dist/runtime/tool-call-walk.js.map +1 -0
- package/dist/runtime/tool-wiring.js +159 -0
- package/dist/runtime/tool-wiring.js.map +1 -0
- package/dist/tooling/adapters.js +1 -1
- package/dist/tooling/dataflow.js +1 -1
- package/dist/tooling/policy.js +2 -0
- package/dist/tooling/policy.js.map +1 -1
- package/dist/types.d.ts +63 -13
- package/dist/types.d.ts.map +1 -1
- package/package.json +20 -20
- package/src/errors/index.ts +320 -0
- package/src/evaluator-optimizer/index.ts +212 -0
- package/src/factory.ts +957 -0
- package/src/fallback/index.ts +108 -0
- package/src/fanout/index.ts +523 -0
- package/src/filters/index.ts +347 -0
- package/src/index.ts +180 -0
- package/src/internal/ids.ts +46 -0
- package/src/internal/usage-accumulator.ts +90 -0
- package/src/lateral-leak/causality-monitor.ts +221 -0
- package/src/lateral-leak/index.ts +35 -0
- package/src/lateral-leak/merge-guard.ts +151 -0
- package/src/lateral-leak/protocol-guard.ts +222 -0
- package/src/preferred-model/index.ts +210 -0
- package/src/progress/index.ts +238 -0
- package/src/run-state/index.ts +607 -0
- package/src/runtime/agent-surface.ts +218 -0
- package/src/runtime/agent-to-tool.ts +323 -0
- package/src/runtime/approvals.ts +0 -0
- package/src/runtime/dispatch.ts +183 -0
- package/src/runtime/executor-wiring.ts +331 -0
- package/src/runtime/fallback-chain.ts +250 -0
- package/src/runtime/handoff.ts +428 -0
- package/src/runtime/messages.ts +309 -0
- package/src/runtime/provider-events.ts +175 -0
- package/src/runtime/run-compaction.ts +288 -0
- package/src/runtime/run-finish.ts +93 -0
- package/src/runtime/run-gates.ts +419 -0
- package/src/runtime/run-init.ts +169 -0
- package/src/runtime/run-input.ts +102 -0
- package/src/runtime/step-catalogue.ts +338 -0
- package/src/runtime/tool-call-walk.ts +301 -0
- package/src/runtime/tool-wiring.ts +218 -0
- package/src/testing/replay-provider.ts +121 -0
- package/src/tooling/adapters.ts +403 -0
- package/src/tooling/catalogue.ts +36 -0
- package/src/tooling/dataflow.ts +171 -0
- package/src/tooling/plan.ts +123 -0
- package/src/tooling/policy.ts +67 -0
- package/src/tooling/registry-build.ts +191 -0
- package/src/types.ts +696 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//#region src/runtime/provider-events.ts
|
|
2
|
+
/**
|
|
3
|
+
* AG-21: classify a **thrown** provider error into a {@link ProviderErrorKind}
|
|
4
|
+
* so the fallback chain can act on it, instead of flattening every exception to
|
|
5
|
+
* `'unknown'` (which is always fallback-ineligible). Structural - reads the
|
|
6
|
+
* `kind` carried by `@graphorin/provider`'s `GraphorinProviderError` subclasses
|
|
7
|
+
* without importing them, keeping the agent decoupled from the provider package.
|
|
8
|
+
*/
|
|
9
|
+
/** Canonical `ProviderErrorKind` values honoured off a thrown error's `errorKind`. */
|
|
10
|
+
const CANONICAL_PROVIDER_ERROR_KINDS = new Set([
|
|
11
|
+
"rate-limit",
|
|
12
|
+
"capacity",
|
|
13
|
+
"context-length",
|
|
14
|
+
"transient",
|
|
15
|
+
"invalid-request",
|
|
16
|
+
"unauthorized",
|
|
17
|
+
"content-filter"
|
|
18
|
+
]);
|
|
19
|
+
function classifyThrownProviderErrorKind(cause) {
|
|
20
|
+
if (typeof cause === "object" && cause !== null) {
|
|
21
|
+
const errorKind = cause.errorKind;
|
|
22
|
+
if (typeof errorKind === "string" && CANONICAL_PROVIDER_ERROR_KINDS.has(errorKind)) return errorKind;
|
|
23
|
+
switch (cause.kind) {
|
|
24
|
+
case "rate-limit-exceeded": return "rate-limit";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return "unknown";
|
|
28
|
+
}
|
|
29
|
+
function specToProvider(spec) {
|
|
30
|
+
if ("provider" in spec) return spec.provider;
|
|
31
|
+
return spec;
|
|
32
|
+
}
|
|
33
|
+
function handleProviderEvent(ev, state) {
|
|
34
|
+
switch (ev.type) {
|
|
35
|
+
case "stream-start": return {};
|
|
36
|
+
case "reasoning-delta":
|
|
37
|
+
state.reasoningBuffer += ev.delta;
|
|
38
|
+
return { emit: {
|
|
39
|
+
type: "reasoning.delta",
|
|
40
|
+
delta: ev.delta
|
|
41
|
+
} };
|
|
42
|
+
case "reasoning-end": {
|
|
43
|
+
const text = state.reasoningBuffer;
|
|
44
|
+
state.reasoningBuffer = "";
|
|
45
|
+
state.reasoningParts.push({
|
|
46
|
+
type: "reasoning",
|
|
47
|
+
text,
|
|
48
|
+
...ev.meta !== void 0 ? { meta: ev.meta } : {}
|
|
49
|
+
});
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
case "text-delta":
|
|
53
|
+
state.textBuffer += ev.delta;
|
|
54
|
+
return { emit: {
|
|
55
|
+
type: "text.delta",
|
|
56
|
+
delta: ev.delta
|
|
57
|
+
} };
|
|
58
|
+
case "tool-call-start":
|
|
59
|
+
state.calls.set(ev.toolCallId, {
|
|
60
|
+
toolCallId: ev.toolCallId,
|
|
61
|
+
toolName: ev.toolName,
|
|
62
|
+
argsBuffer: ""
|
|
63
|
+
});
|
|
64
|
+
return { emit: {
|
|
65
|
+
type: "tool.call.start",
|
|
66
|
+
toolCallId: ev.toolCallId,
|
|
67
|
+
toolName: ev.toolName,
|
|
68
|
+
args: void 0
|
|
69
|
+
} };
|
|
70
|
+
case "tool-call-input-delta": {
|
|
71
|
+
const acc = state.calls.get(ev.toolCallId);
|
|
72
|
+
if (acc !== void 0) acc.argsBuffer += ev.argsDelta;
|
|
73
|
+
return { emit: {
|
|
74
|
+
type: "tool.call.delta",
|
|
75
|
+
toolCallId: ev.toolCallId,
|
|
76
|
+
argsDelta: ev.argsDelta
|
|
77
|
+
} };
|
|
78
|
+
}
|
|
79
|
+
case "tool-call-end": {
|
|
80
|
+
const acc = state.calls.get(ev.toolCallId);
|
|
81
|
+
if (acc === void 0) {
|
|
82
|
+
process.stderr.write(`[graphorin/agent] dropped tool-call-end '${ev.toolCallId}' with no matching tool-call-start.\n`);
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
state.finalCalls.push({
|
|
86
|
+
toolCallId: ev.toolCallId,
|
|
87
|
+
toolName: acc.toolName,
|
|
88
|
+
args: ev.finalArgs
|
|
89
|
+
});
|
|
90
|
+
return { emit: {
|
|
91
|
+
type: "tool.call.end",
|
|
92
|
+
toolCallId: ev.toolCallId,
|
|
93
|
+
finalArgs: ev.finalArgs
|
|
94
|
+
} };
|
|
95
|
+
}
|
|
96
|
+
case "file": return { emit: {
|
|
97
|
+
type: "file.generated",
|
|
98
|
+
mimeType: ev.mimeType,
|
|
99
|
+
data: ev.data
|
|
100
|
+
} };
|
|
101
|
+
case "source": return { emit: {
|
|
102
|
+
type: "source.cited",
|
|
103
|
+
uri: ev.uri,
|
|
104
|
+
...ev.title !== void 0 ? { title: ev.title } : {}
|
|
105
|
+
} };
|
|
106
|
+
case "finish": return {
|
|
107
|
+
usage: ev.usage,
|
|
108
|
+
finished: true
|
|
109
|
+
};
|
|
110
|
+
case "error": return { providerError: ev.error };
|
|
111
|
+
default: return {};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
export { classifyThrownProviderErrorKind, handleProviderEvent, specToProvider };
|
|
117
|
+
//# sourceMappingURL=provider-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-events.js","names":["CANONICAL_PROVIDER_ERROR_KINDS: ReadonlySet<string>"],"sources":["../../src/runtime/provider-events.ts"],"sourcesContent":["/**\n * Provider stream bridging for the agent runtime: the per-step\n * accumulation of `ProviderEvent`s into agent-visible events / final\n * tool calls, thrown-provider-error classification for the fallback\n * chain, and the `ModelSpec` -> `Provider` projection. Extracted\n * verbatim from `factory.ts` (issue #23).\n *\n * @packageDocumentation\n */\n\nimport type {\n AgentEvent,\n ModelSpec,\n Provider,\n ProviderError,\n ProviderErrorKind,\n ProviderEvent,\n ReasoningContent,\n ToolCall,\n Usage,\n} from '@graphorin/core';\n\n/**\n * AG-21: classify a **thrown** provider error into a {@link ProviderErrorKind}\n * so the fallback chain can act on it, instead of flattening every exception to\n * `'unknown'` (which is always fallback-ineligible). Structural - reads the\n * `kind` carried by `@graphorin/provider`'s `GraphorinProviderError` subclasses\n * without importing them, keeping the agent decoupled from the provider package.\n */\n/** Canonical `ProviderErrorKind` values honoured off a thrown error's `errorKind`. */\nconst CANONICAL_PROVIDER_ERROR_KINDS: ReadonlySet<string> = new Set([\n 'rate-limit',\n 'capacity',\n 'context-length',\n 'transient',\n 'invalid-request',\n 'unauthorized',\n 'content-filter',\n]);\n\nexport function classifyThrownProviderErrorKind(cause: unknown): ProviderErrorKind {\n if (typeof cause === 'object' && cause !== null) {\n // B1: `ProviderHttpError` carries the canonical mapped kind on\n // `errorKind` (its `kind` stays the stable 'provider-http'\n // discriminant) - honour it so a thrown 429 / context overflow is\n // classified like the structured-event equivalent.\n const errorKind = (cause as { readonly errorKind?: unknown }).errorKind;\n if (typeof errorKind === 'string' && CANONICAL_PROVIDER_ERROR_KINDS.has(errorKind)) {\n return errorKind as ProviderErrorKind;\n }\n switch ((cause as { readonly kind?: unknown }).kind) {\n case 'rate-limit-exceeded':\n return 'rate-limit';\n }\n }\n return 'unknown';\n}\n\nexport function specToProvider(spec: ModelSpec): Provider {\n if ('provider' in spec) return spec.provider as Provider;\n return spec as Provider;\n}\n\nexport interface ToolCallAccumulator {\n readonly toolCallId: string;\n toolName: string;\n argsBuffer: string;\n}\n\nexport interface ProviderEventOutcome {\n readonly emit?: AgentEvent;\n readonly providerError?: ProviderError;\n readonly usage?: Usage;\n readonly finished?: boolean;\n}\n\nexport interface ProviderEventCollector {\n textBuffer: string;\n reasoningBuffer: string;\n reasoningParts: ReasoningContent[];\n calls: Map<string, ToolCallAccumulator>;\n finalCalls: ToolCall[];\n}\n\nexport function handleProviderEvent(\n ev: ProviderEvent,\n state: ProviderEventCollector,\n): ProviderEventOutcome {\n switch (ev.type) {\n case 'stream-start':\n return {};\n case 'reasoning-delta':\n state.reasoningBuffer += ev.delta;\n return { emit: { type: 'reasoning.delta', delta: ev.delta } };\n case 'reasoning-end': {\n // W-024: close the current reasoning block - flush the buffered\n // deltas into a per-block ReasoningContent carrying the adapter's\n // opaque round-trip meta (Anthropic thinking signature / redacted\n // data). A redacted block has no deltas: its part is meta-only\n // with empty text, which the retention pipeline still round-trips.\n const text = state.reasoningBuffer;\n state.reasoningBuffer = '';\n state.reasoningParts.push({\n type: 'reasoning',\n text,\n ...(ev.meta !== undefined ? { meta: ev.meta } : {}),\n });\n return {};\n }\n case 'text-delta':\n state.textBuffer += ev.delta;\n return { emit: { type: 'text.delta', delta: ev.delta } };\n case 'tool-call-start':\n state.calls.set(ev.toolCallId, {\n toolCallId: ev.toolCallId,\n toolName: ev.toolName,\n argsBuffer: '',\n });\n return {\n emit: {\n type: 'tool.call.start',\n toolCallId: ev.toolCallId,\n toolName: ev.toolName,\n args: undefined,\n },\n };\n case 'tool-call-input-delta': {\n const acc = state.calls.get(ev.toolCallId);\n if (acc !== undefined) acc.argsBuffer += ev.argsDelta;\n return {\n emit: { type: 'tool.call.delta', toolCallId: ev.toolCallId, argsDelta: ev.argsDelta },\n };\n }\n case 'tool-call-end': {\n const acc = state.calls.get(ev.toolCallId);\n if (acc === undefined) {\n // AG-26: an end without a matching start has no tool name - the\n // old path dispatched it as the unknown tool ''. Drop it loudly.\n process.stderr.write(\n `[graphorin/agent] dropped tool-call-end '${ev.toolCallId}' with no matching tool-call-start.\\n`,\n );\n return {};\n }\n state.finalCalls.push({\n toolCallId: ev.toolCallId,\n toolName: acc.toolName,\n args: ev.finalArgs,\n });\n return {\n emit: { type: 'tool.call.end', toolCallId: ev.toolCallId, finalArgs: ev.finalArgs },\n };\n }\n // AG-26: provider-generated files / citations are consumer-observable\n // events instead of silently vanishing.\n case 'file':\n return { emit: { type: 'file.generated', mimeType: ev.mimeType, data: ev.data } };\n case 'source':\n return {\n emit: {\n type: 'source.cited',\n uri: ev.uri,\n ...(ev.title !== undefined ? { title: ev.title } : {}),\n },\n };\n case 'finish':\n return { usage: ev.usage, finished: true };\n case 'error':\n return { providerError: ev.error };\n default: {\n const _exhaustive: never = ev;\n void _exhaustive;\n return {};\n }\n }\n}\n"],"mappings":";;;;;;;;;AA8BA,MAAMA,iCAAsD,IAAI,IAAI;CAClE;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAgB,gCAAgC,OAAmC;AACjF,KAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EAK/C,MAAM,YAAa,MAA2C;AAC9D,MAAI,OAAO,cAAc,YAAY,+BAA+B,IAAI,UAAU,CAChF,QAAO;AAET,UAAS,MAAsC,MAA/C;GACE,KAAK,sBACH,QAAO;;;AAGb,QAAO;;AAGT,SAAgB,eAAe,MAA2B;AACxD,KAAI,cAAc,KAAM,QAAO,KAAK;AACpC,QAAO;;AAwBT,SAAgB,oBACd,IACA,OACsB;AACtB,SAAQ,GAAG,MAAX;EACE,KAAK,eACH,QAAO,EAAE;EACX,KAAK;AACH,SAAM,mBAAmB,GAAG;AAC5B,UAAO,EAAE,MAAM;IAAE,MAAM;IAAmB,OAAO,GAAG;IAAO,EAAE;EAC/D,KAAK,iBAAiB;GAMpB,MAAM,OAAO,MAAM;AACnB,SAAM,kBAAkB;AACxB,SAAM,eAAe,KAAK;IACxB,MAAM;IACN;IACA,GAAI,GAAG,SAAS,SAAY,EAAE,MAAM,GAAG,MAAM,GAAG,EAAE;IACnD,CAAC;AACF,UAAO,EAAE;;EAEX,KAAK;AACH,SAAM,cAAc,GAAG;AACvB,UAAO,EAAE,MAAM;IAAE,MAAM;IAAc,OAAO,GAAG;IAAO,EAAE;EAC1D,KAAK;AACH,SAAM,MAAM,IAAI,GAAG,YAAY;IAC7B,YAAY,GAAG;IACf,UAAU,GAAG;IACb,YAAY;IACb,CAAC;AACF,UAAO,EACL,MAAM;IACJ,MAAM;IACN,YAAY,GAAG;IACf,UAAU,GAAG;IACb,MAAM;IACP,EACF;EACH,KAAK,yBAAyB;GAC5B,MAAM,MAAM,MAAM,MAAM,IAAI,GAAG,WAAW;AAC1C,OAAI,QAAQ,OAAW,KAAI,cAAc,GAAG;AAC5C,UAAO,EACL,MAAM;IAAE,MAAM;IAAmB,YAAY,GAAG;IAAY,WAAW,GAAG;IAAW,EACtF;;EAEH,KAAK,iBAAiB;GACpB,MAAM,MAAM,MAAM,MAAM,IAAI,GAAG,WAAW;AAC1C,OAAI,QAAQ,QAAW;AAGrB,YAAQ,OAAO,MACb,4CAA4C,GAAG,WAAW,uCAC3D;AACD,WAAO,EAAE;;AAEX,SAAM,WAAW,KAAK;IACpB,YAAY,GAAG;IACf,UAAU,IAAI;IACd,MAAM,GAAG;IACV,CAAC;AACF,UAAO,EACL,MAAM;IAAE,MAAM;IAAiB,YAAY,GAAG;IAAY,WAAW,GAAG;IAAW,EACpF;;EAIH,KAAK,OACH,QAAO,EAAE,MAAM;GAAE,MAAM;GAAkB,UAAU,GAAG;GAAU,MAAM,GAAG;GAAM,EAAE;EACnF,KAAK,SACH,QAAO,EACL,MAAM;GACJ,MAAM;GACN,KAAK,GAAG;GACR,GAAI,GAAG,UAAU,SAAY,EAAE,OAAO,GAAG,OAAO,GAAG,EAAE;GACtD,EACF;EACH,KAAK,SACH,QAAO;GAAE,OAAO,GAAG;GAAO,UAAU;GAAM;EAC5C,KAAK,QACH,QAAO,EAAE,eAAe,GAAG,OAAO;EACpC,QAGE,QAAO,EAAE"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
//#region src/runtime/run-compaction.ts
|
|
2
|
+
const noopCompactionResult = (skippedReason) => ({
|
|
3
|
+
beforeTokens: 0,
|
|
4
|
+
afterTokens: 0,
|
|
5
|
+
summaryTokens: 0,
|
|
6
|
+
durationMs: 0,
|
|
7
|
+
hooksFiredCount: 0,
|
|
8
|
+
summary: "",
|
|
9
|
+
applied: false,
|
|
10
|
+
skippedReason
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Auto-compaction trigger (WI-09 / P1-1). Before assembling each
|
|
14
|
+
* provider request, ask the memory {@link ContextEngine} whether the
|
|
15
|
+
* in-flight buffer has crossed its per-provider threshold
|
|
16
|
+
* (`shouldCompact`); when it has, summarise the older turns
|
|
17
|
+
* (`compactNow`, `source: 'auto-trigger'`), splice the result back in
|
|
18
|
+
* - preserving the byte-stable system prefix and the most-recent
|
|
19
|
+
* turns verbatim - and emit `context.compacted`. The compaction is
|
|
20
|
+
* configured on the memory facade (`createMemory({ contextEngine })`,
|
|
21
|
+
* RB-46); there is no parallel agent-level knob.
|
|
22
|
+
*
|
|
23
|
+
* No-op when no memory is wired, when compaction is disabled or below
|
|
24
|
+
* threshold (the engine returns `false`), or for `secret`-tier runs
|
|
25
|
+
* (secret history is never shipped to the summarizer - a less-trusted
|
|
26
|
+
* external sink; per-result handle references land in WI-10). Best
|
|
27
|
+
* effort: a misconfigured engine (e.g. no summarizer) is swallowed and
|
|
28
|
+
* the run proceeds uncompacted rather than aborting mid-flight.
|
|
29
|
+
*
|
|
30
|
+
* Operator-requested compactions (`agent.compact()`, CE-3/AG-13) are
|
|
31
|
+
* serviced here too, FIRST - the queue carries the `compact()` promise
|
|
32
|
+
* resolvers, and manual requests bypass the trigger evaluation.
|
|
33
|
+
*/
|
|
34
|
+
async function* maybeAutoCompact(env) {
|
|
35
|
+
const { config, memory, state, messages, sessionId, agentId, systemPrefixLength } = env;
|
|
36
|
+
const { pendingManualCompacts } = env;
|
|
37
|
+
while (pendingManualCompacts.length > 0) {
|
|
38
|
+
const pending = pendingManualCompacts.shift();
|
|
39
|
+
if (pending !== void 0) yield* serviceManualCompact(env, pending);
|
|
40
|
+
}
|
|
41
|
+
const mem = memory;
|
|
42
|
+
if (mem === void 0) return;
|
|
43
|
+
if (config.sensitivity === "secret") return;
|
|
44
|
+
const engine = mem.contextEngine;
|
|
45
|
+
if (!await engine.shouldCompact(messages, { compactableFromIndex: systemPrefixLength }).catch(() => false)) return;
|
|
46
|
+
const startedAt = Date.now();
|
|
47
|
+
const envelope = await engine.compactNow({
|
|
48
|
+
scope: {
|
|
49
|
+
userId: state.userId ?? agentId,
|
|
50
|
+
sessionId,
|
|
51
|
+
agentId
|
|
52
|
+
},
|
|
53
|
+
runId: state.id,
|
|
54
|
+
sessionId,
|
|
55
|
+
agentId,
|
|
56
|
+
source: "auto-trigger",
|
|
57
|
+
messages: messages.slice(systemPrefixLength),
|
|
58
|
+
prefixMessages: messages.slice(0, systemPrefixLength),
|
|
59
|
+
memory: mem
|
|
60
|
+
}).catch(() => void 0);
|
|
61
|
+
if (envelope === void 0) return;
|
|
62
|
+
if (envelope.result.droppedMessageIndices.length === 0) return;
|
|
63
|
+
spliceCompacted(env, envelope);
|
|
64
|
+
yield {
|
|
65
|
+
type: "context.compacted",
|
|
66
|
+
runId: state.id,
|
|
67
|
+
sessionId,
|
|
68
|
+
agentId,
|
|
69
|
+
beforeTokens: envelope.result.beforeTokens,
|
|
70
|
+
afterTokens: envelope.result.afterTokens,
|
|
71
|
+
summaryTokens: envelope.result.summaryTokens,
|
|
72
|
+
durationMs: Date.now() - startedAt,
|
|
73
|
+
source: "auto-trigger",
|
|
74
|
+
hooksFiredCount: envelope.result.hooksFiredCount
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* context-engine-06: last-resort tier at hard context overflow. When
|
|
79
|
+
* a provider rejects the request as over-window, force ONE aggressive
|
|
80
|
+
* compaction (`preserveRecentTurns: 2`, trigger evaluation bypassed)
|
|
81
|
+
* and let the caller retry the same provider - the fallback chain's
|
|
82
|
+
* members usually share the same window, so without this the run just
|
|
83
|
+
* dies. Returns `true` when the buffer actually shrank (retry is
|
|
84
|
+
* worthwhile); `false` when memory is not wired, the run is
|
|
85
|
+
* secret-tier, compaction trimmed nothing, or the engine threw.
|
|
86
|
+
*/
|
|
87
|
+
async function* tryEmergencyCompact(env) {
|
|
88
|
+
const { config, memory, state, messages, sessionId, agentId, systemPrefixLength } = env;
|
|
89
|
+
const mem = memory;
|
|
90
|
+
if (mem === void 0 || config.sensitivity === "secret") return false;
|
|
91
|
+
const startedAt = Date.now();
|
|
92
|
+
const envelope = await mem.contextEngine.compactNow({
|
|
93
|
+
scope: {
|
|
94
|
+
userId: state.userId ?? agentId,
|
|
95
|
+
sessionId,
|
|
96
|
+
agentId
|
|
97
|
+
},
|
|
98
|
+
runId: state.id,
|
|
99
|
+
sessionId,
|
|
100
|
+
agentId,
|
|
101
|
+
source: "auto-trigger",
|
|
102
|
+
messages: messages.slice(systemPrefixLength),
|
|
103
|
+
prefixMessages: messages.slice(0, systemPrefixLength),
|
|
104
|
+
memory: mem,
|
|
105
|
+
preserveRecentTurns: 2
|
|
106
|
+
}).catch(() => void 0);
|
|
107
|
+
if (envelope === void 0 || envelope.result.droppedMessageIndices.length === 0) return false;
|
|
108
|
+
spliceCompacted(env, envelope);
|
|
109
|
+
yield {
|
|
110
|
+
type: "context.compacted",
|
|
111
|
+
runId: state.id,
|
|
112
|
+
sessionId,
|
|
113
|
+
agentId,
|
|
114
|
+
beforeTokens: envelope.result.beforeTokens,
|
|
115
|
+
afterTokens: envelope.result.afterTokens,
|
|
116
|
+
summaryTokens: envelope.result.summaryTokens,
|
|
117
|
+
durationMs: Date.now() - startedAt,
|
|
118
|
+
source: "auto-trigger",
|
|
119
|
+
hooksFiredCount: envelope.result.hooksFiredCount
|
|
120
|
+
};
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Prefix-pinned splice shared by the auto + manual compaction paths
|
|
125
|
+
* (CE-3): stable system prefix + [summary, ...recent turns], with the
|
|
126
|
+
* post-compaction hooks' text Context Essentials re-anchored as a
|
|
127
|
+
* trailing system message so they survive the trim (RB-46). Mutates
|
|
128
|
+
* BOTH the live loop buffer and `state.messages`.
|
|
129
|
+
*/
|
|
130
|
+
function spliceCompacted(env, envelope) {
|
|
131
|
+
const { state, messages, systemPrefixLength } = env;
|
|
132
|
+
const rebuilt = [...messages.slice(0, systemPrefixLength), ...envelope.result.trimmedMessages];
|
|
133
|
+
const essentials = envelope.extraContent.map((part) => typeof part === "object" && part !== null && "text" in part ? String(part.text) : "").filter((text) => text.length > 0).join("\n\n");
|
|
134
|
+
if (essentials.length > 0) rebuilt.push({
|
|
135
|
+
role: "system",
|
|
136
|
+
content: essentials
|
|
137
|
+
});
|
|
138
|
+
messages.splice(0, messages.length, ...rebuilt);
|
|
139
|
+
state.messages.splice(0, state.messages.length, ...rebuilt);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Service one `agent.compact()` request inside the loop (CE-3/AG-13):
|
|
143
|
+
* same prefix-pinned splice as the auto path, `source: 'manual'` (or
|
|
144
|
+
* the caller's `'pre-step'`), `preserveRecentTurns` forwarded as a
|
|
145
|
+
* per-call strategy override. An engine failure rejects the caller's
|
|
146
|
+
* promise but never aborts the live run; a summarize that trims
|
|
147
|
+
* nothing resolves `applied: false` without an event.
|
|
148
|
+
*/
|
|
149
|
+
async function* serviceManualCompact(env, pending) {
|
|
150
|
+
const { memory, state, messages, sessionId, agentId, systemPrefixLength } = env;
|
|
151
|
+
const mem = memory;
|
|
152
|
+
if (mem === void 0) {
|
|
153
|
+
pending.resolve(noopCompactionResult("no-memory"));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const source = pending.options?.source ?? "manual";
|
|
157
|
+
const startedAt = Date.now();
|
|
158
|
+
let envelope;
|
|
159
|
+
try {
|
|
160
|
+
envelope = await mem.contextEngine.compactNow({
|
|
161
|
+
scope: {
|
|
162
|
+
userId: state.userId ?? agentId,
|
|
163
|
+
sessionId,
|
|
164
|
+
agentId
|
|
165
|
+
},
|
|
166
|
+
runId: state.id,
|
|
167
|
+
sessionId,
|
|
168
|
+
agentId,
|
|
169
|
+
source,
|
|
170
|
+
messages: messages.slice(systemPrefixLength),
|
|
171
|
+
prefixMessages: messages.slice(0, systemPrefixLength),
|
|
172
|
+
memory: mem,
|
|
173
|
+
...pending.options?.preserveRecentTurns !== void 0 ? { preserveRecentTurns: pending.options.preserveRecentTurns } : {}
|
|
174
|
+
});
|
|
175
|
+
} catch (cause) {
|
|
176
|
+
pending.reject(cause);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const { result } = envelope;
|
|
180
|
+
const applied = result.droppedMessageIndices.length > 0;
|
|
181
|
+
if (applied) {
|
|
182
|
+
spliceCompacted(env, envelope);
|
|
183
|
+
yield {
|
|
184
|
+
type: "context.compacted",
|
|
185
|
+
runId: state.id,
|
|
186
|
+
sessionId,
|
|
187
|
+
agentId,
|
|
188
|
+
beforeTokens: result.beforeTokens,
|
|
189
|
+
afterTokens: result.afterTokens,
|
|
190
|
+
summaryTokens: result.summaryTokens,
|
|
191
|
+
durationMs: Date.now() - startedAt,
|
|
192
|
+
source,
|
|
193
|
+
hooksFiredCount: result.hooksFiredCount
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
pending.resolve({
|
|
197
|
+
beforeTokens: result.beforeTokens,
|
|
198
|
+
afterTokens: result.afterTokens,
|
|
199
|
+
summaryTokens: result.summaryTokens,
|
|
200
|
+
durationMs: Date.now() - startedAt,
|
|
201
|
+
hooksFiredCount: result.hooksFiredCount,
|
|
202
|
+
summary: result.summary ?? "",
|
|
203
|
+
applied,
|
|
204
|
+
...applied ? {} : { skippedReason: "nothing-to-trim" }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
//#endregion
|
|
209
|
+
export { maybeAutoCompact, noopCompactionResult, tryEmergencyCompact };
|
|
210
|
+
//# sourceMappingURL=run-compaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-compaction.js","names":["rebuilt: Message[]","envelope: CompactionEnvelope"],"sources":["../../src/runtime/run-compaction.ts"],"sourcesContent":["/**\n * Compaction lifecycle for the agent run loop (WI-09 / P1-1, CE-3 /\n * AG-13, context-engine-06): the auto-compaction trigger evaluated\n * before each provider request, the last-resort emergency compaction at\n * hard context overflow, the servicing of `agent.compact()` manual\n * requests, and the prefix-pinned splice shared by all three paths.\n * Extracted verbatim from `factory.ts` (issue #23); the former run-loop\n * closures now take an explicit {@link CompactionRunEnv}.\n *\n * @packageDocumentation\n */\n\nimport type { AgentEvent, Message, RunState, Sensitivity } from '@graphorin/core';\nimport type { Memory } from '@graphorin/memory';\nimport type { CompactionApiResult, CompactOptions } from '../types.js';\nimport type { MutableRunState } from './run-input.js';\n\n/** Return envelope of `ContextEngine.compactNow` (shared splice paths, CE-3). */\nexport type CompactionEnvelope = Awaited<ReturnType<Memory['contextEngine']['compactNow']>>;\n\n/**\n * Manual-compaction requests enqueued by `agent.compact()` (CE-3 /\n * AG-13). The run loop owns the live message buffer, so the splice\n * must happen inside the loop: `maybeAutoCompact` services the queue\n * at the next step boundary, and `finishRun` settles leftovers as\n * explicit no-ops.\n */\nexport interface PendingManualCompact {\n readonly options: CompactOptions | undefined;\n readonly resolve: (result: CompactionApiResult) => void;\n readonly reject: (cause: unknown) => void;\n}\n\nexport const noopCompactionResult = (\n skippedReason: NonNullable<CompactionApiResult['skippedReason']>,\n): CompactionApiResult => ({\n beforeTokens: 0,\n afterTokens: 0,\n summaryTokens: 0,\n durationMs: 0,\n hooksFiredCount: 0,\n summary: '',\n applied: false,\n skippedReason,\n});\n\n/**\n * The run-scoped context the compaction paths operate on. Field names\n * mirror the run-loop locals the former closures captured; the live\n * message buffer and the pending-manual queue are shared references.\n */\nexport interface CompactionRunEnv {\n readonly config: { readonly sensitivity?: Sensitivity };\n readonly memory: Memory | undefined;\n readonly state: MutableRunState & RunState;\n readonly messages: Message[];\n readonly sessionId: string;\n readonly agentId: string;\n readonly systemPrefixLength: number;\n readonly pendingManualCompacts: PendingManualCompact[];\n}\n\n/**\n * Auto-compaction trigger (WI-09 / P1-1). Before assembling each\n * provider request, ask the memory {@link ContextEngine} whether the\n * in-flight buffer has crossed its per-provider threshold\n * (`shouldCompact`); when it has, summarise the older turns\n * (`compactNow`, `source: 'auto-trigger'`), splice the result back in\n * - preserving the byte-stable system prefix and the most-recent\n * turns verbatim - and emit `context.compacted`. The compaction is\n * configured on the memory facade (`createMemory({ contextEngine })`,\n * RB-46); there is no parallel agent-level knob.\n *\n * No-op when no memory is wired, when compaction is disabled or below\n * threshold (the engine returns `false`), or for `secret`-tier runs\n * (secret history is never shipped to the summarizer - a less-trusted\n * external sink; per-result handle references land in WI-10). Best\n * effort: a misconfigured engine (e.g. no summarizer) is swallowed and\n * the run proceeds uncompacted rather than aborting mid-flight.\n *\n * Operator-requested compactions (`agent.compact()`, CE-3/AG-13) are\n * serviced here too, FIRST - the queue carries the `compact()` promise\n * resolvers, and manual requests bypass the trigger evaluation.\n */\nexport async function* maybeAutoCompact<TOutput>(\n env: CompactionRunEnv,\n): AsyncGenerator<AgentEvent<TOutput>, void, void> {\n const { config, memory, state, messages, sessionId, agentId, systemPrefixLength } = env;\n const { pendingManualCompacts } = env;\n while (pendingManualCompacts.length > 0) {\n const pending = pendingManualCompacts.shift();\n if (pending !== undefined) yield* serviceManualCompact<TOutput>(env, pending);\n }\n const mem = memory;\n if (mem === undefined) return;\n // Sensitivity gate (WI-09 step 2): drop, never re-route, secret-tier\n // content. Auto-compaction is an LLM summarizer call, so a secret\n // run is left un-compacted here.\n if (config.sensitivity === 'secret') return;\n const engine = mem.contextEngine;\n // context-engine-04: trigger, reclaim floor, and anti-thrash guard\n // share one basis - the engine sees the full buffer for the trigger\n // total, learns where the pinned (uncompactable) prefix ends, and\n // receives the prefix messages so the guard arms against the FULL\n // post-splice context instead of the sliced body.\n const triggered = await engine\n .shouldCompact(messages, { compactableFromIndex: systemPrefixLength })\n .catch(() => false);\n if (!triggered) return;\n\n const startedAt = Date.now();\n const envelope = await engine\n .compactNow({\n scope: { userId: state.userId ?? agentId, sessionId, agentId },\n runId: state.id,\n sessionId,\n agentId,\n source: 'auto-trigger',\n messages: messages.slice(systemPrefixLength),\n prefixMessages: messages.slice(0, systemPrefixLength),\n memory: mem,\n })\n // No summarizer configured (or the strategy threw) - proceed with\n // the un-compacted buffer rather than failing a live run.\n .catch(() => undefined);\n if (envelope === undefined) return;\n // Nothing was old enough to trim (body ≤ preserve-recent) - skip the\n // splice + event so `context.compacted` only fires on real work.\n if (envelope.result.droppedMessageIndices.length === 0) return;\n\n spliceCompacted(env, envelope);\n yield {\n type: 'context.compacted',\n runId: state.id,\n sessionId,\n agentId,\n beforeTokens: envelope.result.beforeTokens,\n afterTokens: envelope.result.afterTokens,\n summaryTokens: envelope.result.summaryTokens,\n durationMs: Date.now() - startedAt,\n source: 'auto-trigger',\n hooksFiredCount: envelope.result.hooksFiredCount,\n };\n}\n\n/**\n * context-engine-06: last-resort tier at hard context overflow. When\n * a provider rejects the request as over-window, force ONE aggressive\n * compaction (`preserveRecentTurns: 2`, trigger evaluation bypassed)\n * and let the caller retry the same provider - the fallback chain's\n * members usually share the same window, so without this the run just\n * dies. Returns `true` when the buffer actually shrank (retry is\n * worthwhile); `false` when memory is not wired, the run is\n * secret-tier, compaction trimmed nothing, or the engine threw.\n */\nexport async function* tryEmergencyCompact<TOutput>(\n env: CompactionRunEnv,\n): AsyncGenerator<AgentEvent<TOutput>, boolean, void> {\n const { config, memory, state, messages, sessionId, agentId, systemPrefixLength } = env;\n const mem = memory;\n if (mem === undefined || config.sensitivity === 'secret') return false;\n const startedAt = Date.now();\n const envelope = await mem.contextEngine\n .compactNow({\n scope: { userId: state.userId ?? agentId, sessionId, agentId },\n runId: state.id,\n sessionId,\n agentId,\n source: 'auto-trigger',\n messages: messages.slice(systemPrefixLength),\n prefixMessages: messages.slice(0, systemPrefixLength),\n memory: mem,\n preserveRecentTurns: 2,\n })\n .catch(() => undefined);\n if (envelope === undefined || envelope.result.droppedMessageIndices.length === 0) {\n return false;\n }\n spliceCompacted(env, envelope);\n yield {\n type: 'context.compacted',\n runId: state.id,\n sessionId,\n agentId,\n beforeTokens: envelope.result.beforeTokens,\n afterTokens: envelope.result.afterTokens,\n summaryTokens: envelope.result.summaryTokens,\n durationMs: Date.now() - startedAt,\n source: 'auto-trigger',\n hooksFiredCount: envelope.result.hooksFiredCount,\n };\n return true;\n}\n\n/**\n * Prefix-pinned splice shared by the auto + manual compaction paths\n * (CE-3): stable system prefix + [summary, ...recent turns], with the\n * post-compaction hooks' text Context Essentials re-anchored as a\n * trailing system message so they survive the trim (RB-46). Mutates\n * BOTH the live loop buffer and `state.messages`.\n */\nfunction spliceCompacted(env: CompactionRunEnv, envelope: CompactionEnvelope): void {\n const { state, messages, systemPrefixLength } = env;\n const prefix = messages.slice(0, systemPrefixLength);\n const rebuilt: Message[] = [...prefix, ...envelope.result.trimmedMessages];\n const essentials = envelope.extraContent\n .map((part) =>\n typeof part === 'object' && part !== null && 'text' in part\n ? String((part as { readonly text: unknown }).text)\n : '',\n )\n .filter((text) => text.length > 0)\n .join('\\n\\n');\n if (essentials.length > 0) {\n rebuilt.push({ role: 'system', content: essentials });\n }\n messages.splice(0, messages.length, ...rebuilt);\n state.messages.splice(0, state.messages.length, ...rebuilt);\n}\n\n/**\n * Service one `agent.compact()` request inside the loop (CE-3/AG-13):\n * same prefix-pinned splice as the auto path, `source: 'manual'` (or\n * the caller's `'pre-step'`), `preserveRecentTurns` forwarded as a\n * per-call strategy override. An engine failure rejects the caller's\n * promise but never aborts the live run; a summarize that trims\n * nothing resolves `applied: false` without an event.\n */\nasync function* serviceManualCompact<TOutput>(\n env: CompactionRunEnv,\n pending: PendingManualCompact,\n): AsyncGenerator<AgentEvent<TOutput>, void, void> {\n const { memory, state, messages, sessionId, agentId, systemPrefixLength } = env;\n const mem = memory;\n if (mem === undefined) {\n pending.resolve(noopCompactionResult('no-memory'));\n return;\n }\n const source = pending.options?.source ?? 'manual';\n const startedAt = Date.now();\n let envelope: CompactionEnvelope;\n try {\n envelope = await mem.contextEngine.compactNow({\n scope: { userId: state.userId ?? agentId, sessionId, agentId },\n runId: state.id,\n sessionId,\n agentId,\n source,\n messages: messages.slice(systemPrefixLength),\n // context-engine-04: same accounting basis as the auto path.\n prefixMessages: messages.slice(0, systemPrefixLength),\n memory: mem,\n ...(pending.options?.preserveRecentTurns !== undefined\n ? { preserveRecentTurns: pending.options.preserveRecentTurns }\n : {}),\n });\n } catch (cause) {\n pending.reject(cause);\n return;\n }\n const { result } = envelope;\n const applied = result.droppedMessageIndices.length > 0;\n if (applied) {\n spliceCompacted(env, envelope);\n yield {\n type: 'context.compacted',\n runId: state.id,\n sessionId,\n agentId,\n beforeTokens: result.beforeTokens,\n afterTokens: result.afterTokens,\n summaryTokens: result.summaryTokens,\n durationMs: Date.now() - startedAt,\n source,\n hooksFiredCount: result.hooksFiredCount,\n };\n }\n pending.resolve({\n beforeTokens: result.beforeTokens,\n afterTokens: result.afterTokens,\n summaryTokens: result.summaryTokens,\n durationMs: Date.now() - startedAt,\n hooksFiredCount: result.hooksFiredCount,\n summary: result.summary ?? '',\n applied,\n ...(applied ? {} : { skippedReason: 'nothing-to-trim' as const }),\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { noopCompactionResult } from "./run-compaction.js";
|
|
2
|
+
|
|
3
|
+
//#region src/runtime/run-finish.ts
|
|
4
|
+
/**
|
|
5
|
+
* Build the factory's `finishRunBase` over the shared manual-compact
|
|
6
|
+
* queue and spill writer.
|
|
7
|
+
*/
|
|
8
|
+
function createRunFinisher(deps) {
|
|
9
|
+
const { pendingManualCompacts, spillWriter, checkpointStore, checkpointPolicy } = deps;
|
|
10
|
+
/**
|
|
11
|
+
* Terminal wrapper around {@link finalize}: every exit path of the run
|
|
12
|
+
* loop - completed, failed, aborted, suspended - ends the stream with
|
|
13
|
+
* an `agent.end` event carrying the final {@link AgentResult} (AG-20).
|
|
14
|
+
*
|
|
15
|
+
* The parameter is `MutableRunState & RunState`: the single caller
|
|
16
|
+
* (factory `finishRun`) is typed exactly so, and the intersection lets
|
|
17
|
+
* {@link finalize} assemble `AgentResult.state` without a cast (W-047).
|
|
18
|
+
*/
|
|
19
|
+
async function* finishRunBase(state, snapshot) {
|
|
20
|
+
while (pendingManualCompacts.length > 0) pendingManualCompacts.shift()?.resolve(noopCompactionResult("no-active-run"));
|
|
21
|
+
const result = finalize(state, snapshot);
|
|
22
|
+
if (result.status === "completed" || result.status === "failed") {
|
|
23
|
+
await spillWriter.clear?.(result.state.id).catch(() => {});
|
|
24
|
+
if (checkpointPolicy === "delete-on-terminal" && checkpointStore !== void 0) await checkpointStore.deleteThread(result.state.id).catch(() => {});
|
|
25
|
+
}
|
|
26
|
+
yield {
|
|
27
|
+
type: "agent.end",
|
|
28
|
+
runId: result.state.id,
|
|
29
|
+
result
|
|
30
|
+
};
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
function finalize(state, snapshot) {
|
|
34
|
+
state.finishedAt = state.finishedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
35
|
+
return {
|
|
36
|
+
output: snapshot.output,
|
|
37
|
+
usage: state.usage,
|
|
38
|
+
status: state.status,
|
|
39
|
+
...state.error !== void 0 ? { error: state.error } : {},
|
|
40
|
+
state
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return finishRunBase;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { createRunFinisher };
|
|
48
|
+
//# sourceMappingURL=run-finish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-finish.js","names":[],"sources":["../../src/runtime/run-finish.ts"],"sourcesContent":["/**\n * The terminal path of every agent run: settle leftover manual-compact\n * requests (CE-3 / AG-13), assemble the final {@link AgentResult}\n * (AG-9), clear terminal-run spill artifacts (TL-10), and end the\n * stream with `agent.end` (AG-20). Extracted verbatim from\n * `factory.ts` (issue #23); the former factory closures now hang off\n * {@link createRunFinisher}'s deps.\n *\n * @packageDocumentation\n */\n\nimport type { AgentEvent, AgentResult, CheckpointStore, RunState } from '@graphorin/core';\nimport type { createDefaultSpillWriter } from '@graphorin/tools/result';\nimport { noopCompactionResult, type PendingManualCompact } from './run-compaction.js';\nimport type { InternalRunSnapshot, MutableRunState } from './run-input.js';\n\n/** What the terminal path needs from the agent factory scope. */\nexport interface RunFinisherDeps {\n readonly pendingManualCompacts: PendingManualCompact[];\n readonly spillWriter: ReturnType<typeof createDefaultSpillWriter>;\n /** W-005: thread erasure on terminal runs (`'delete-on-terminal'`). */\n readonly checkpointStore?: CheckpointStore | undefined;\n readonly checkpointPolicy?: 'keep' | 'delete-on-terminal' | undefined;\n}\n\n/**\n * Build the factory's `finishRunBase` over the shared manual-compact\n * queue and spill writer.\n */\nexport function createRunFinisher<TOutput>(\n deps: RunFinisherDeps,\n): (\n state: MutableRunState & RunState,\n snapshot: InternalRunSnapshot<TOutput>,\n) => AsyncGenerator<AgentEvent<TOutput>, AgentResult<TOutput>, void> {\n const { pendingManualCompacts, spillWriter, checkpointStore, checkpointPolicy } = deps;\n\n /**\n * Terminal wrapper around {@link finalize}: every exit path of the run\n * loop - completed, failed, aborted, suspended - ends the stream with\n * an `agent.end` event carrying the final {@link AgentResult} (AG-20).\n *\n * The parameter is `MutableRunState & RunState`: the single caller\n * (factory `finishRun`) is typed exactly so, and the intersection lets\n * {@link finalize} assemble `AgentResult.state` without a cast (W-047).\n */\n async function* finishRunBase(\n state: MutableRunState & RunState,\n snapshot: InternalRunSnapshot<TOutput>,\n ): AsyncGenerator<AgentEvent<TOutput>, AgentResult<TOutput>, void> {\n // CE-3/AG-13: settle manual-compact requests the loop never serviced -\n // the run is over, there is no live buffer left to splice.\n while (pendingManualCompacts.length > 0) {\n pendingManualCompacts.shift()?.resolve(noopCompactionResult('no-active-run'));\n }\n const result = finalize(state, snapshot);\n // TL-10: spill artifacts are run-scoped scratch - drop them once the\n // run is terminal. `awaiting_approval` and `aborted` runs keep\n // theirs (handles must survive resume); orphans fall to the writer's\n // startup TTL sweep.\n if (result.status === 'completed' || result.status === 'failed') {\n await spillWriter.clear?.(result.state.id).catch(() => {});\n // W-005: opt-in checkpoint hygiene mirrors the spill lifecycle -\n // same statuses, best-effort, and NEVER for awaiting_approval /\n // aborted runs (their thread is the resume state). Default 'keep'\n // preserves post-hoc debugging and process-restart resume.\n if (checkpointPolicy === 'delete-on-terminal' && checkpointStore !== undefined) {\n await checkpointStore.deleteThread(result.state.id).catch(() => {});\n }\n }\n yield { type: 'agent.end', runId: result.state.id, result };\n return result;\n }\n\n function finalize(\n state: MutableRunState & RunState,\n snapshot: InternalRunSnapshot<TOutput>,\n ): AgentResult<TOutput> {\n state.finishedAt = state.finishedAt ?? new Date().toISOString();\n // AG-9: the result carries the terminal status, the failure (when\n // any), and the final RunState - a suspended run is resumable from\n // the result alone, no checkpointStore required.\n return {\n output: snapshot.output,\n usage: state.usage,\n status: state.status,\n ...(state.error !== undefined ? { error: state.error } : {}),\n state,\n };\n }\n\n return finishRunBase;\n}\n"],"mappings":";;;;;;;AA6BA,SAAgB,kBACd,MAImE;CACnE,MAAM,EAAE,uBAAuB,aAAa,iBAAiB,qBAAqB;;;;;;;;;;CAWlF,gBAAgB,cACd,OACA,UACiE;AAGjE,SAAO,sBAAsB,SAAS,EACpC,uBAAsB,OAAO,EAAE,QAAQ,qBAAqB,gBAAgB,CAAC;EAE/E,MAAM,SAAS,SAAS,OAAO,SAAS;AAKxC,MAAI,OAAO,WAAW,eAAe,OAAO,WAAW,UAAU;AAC/D,SAAM,YAAY,QAAQ,OAAO,MAAM,GAAG,CAAC,YAAY,GAAG;AAK1D,OAAI,qBAAqB,wBAAwB,oBAAoB,OACnE,OAAM,gBAAgB,aAAa,OAAO,MAAM,GAAG,CAAC,YAAY,GAAG;;AAGvE,QAAM;GAAE,MAAM;GAAa,OAAO,OAAO,MAAM;GAAI;GAAQ;AAC3D,SAAO;;CAGT,SAAS,SACP,OACA,UACsB;AACtB,QAAM,aAAa,MAAM,+BAAc,IAAI,MAAM,EAAC,aAAa;AAI/D,SAAO;GACL,QAAQ,SAAS;GACjB,OAAO,MAAM;GACb,QAAQ,MAAM;GACd,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,OAAO,GAAG,EAAE;GAC3D;GACD;;AAGH,QAAO"}
|