omk-agent-core 0.90.7 → 0.90.9

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.
Files changed (62) hide show
  1. package/README.md +97 -1
  2. package/dist/agent-loop.d.ts +25 -2
  3. package/dist/agent-loop.d.ts.map +1 -1
  4. package/dist/agent-loop.js +524 -189
  5. package/dist/agent-loop.js.map +1 -1
  6. package/dist/agent.d.ts +29 -7
  7. package/dist/agent.d.ts.map +1 -1
  8. package/dist/agent.js +81 -46
  9. package/dist/agent.js.map +1 -1
  10. package/dist/builtin-tool-resource-claims.d.ts +19 -0
  11. package/dist/builtin-tool-resource-claims.d.ts.map +1 -0
  12. package/dist/builtin-tool-resource-claims.js +200 -0
  13. package/dist/builtin-tool-resource-claims.js.map +1 -0
  14. package/dist/index.d.ts +5 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +8 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/node-resource-resolver.d.ts +42 -0
  19. package/dist/node-resource-resolver.d.ts.map +1 -0
  20. package/dist/node-resource-resolver.js +149 -0
  21. package/dist/node-resource-resolver.js.map +1 -0
  22. package/dist/node.d.ts +1 -0
  23. package/dist/node.d.ts.map +1 -1
  24. package/dist/node.js +2 -0
  25. package/dist/node.js.map +1 -1
  26. package/dist/parallel-tool-batch.d.ts +12 -1
  27. package/dist/parallel-tool-batch.d.ts.map +1 -1
  28. package/dist/parallel-tool-batch.js +71 -49
  29. package/dist/parallel-tool-batch.js.map +1 -1
  30. package/dist/path-segments.d.ts +21 -1
  31. package/dist/path-segments.d.ts.map +1 -1
  32. package/dist/path-segments.js +91 -9
  33. package/dist/path-segments.js.map +1 -1
  34. package/dist/plain-data.d.ts +7 -0
  35. package/dist/plain-data.d.ts.map +1 -0
  36. package/dist/plain-data.js +70 -0
  37. package/dist/plain-data.js.map +1 -0
  38. package/dist/tool-dag-scheduler.d.ts +86 -0
  39. package/dist/tool-dag-scheduler.d.ts.map +1 -0
  40. package/dist/tool-dag-scheduler.js +171 -0
  41. package/dist/tool-dag-scheduler.js.map +1 -0
  42. package/dist/tool-execution-boundary.d.ts +52 -0
  43. package/dist/tool-execution-boundary.d.ts.map +1 -0
  44. package/dist/tool-execution-boundary.js +185 -0
  45. package/dist/tool-execution-boundary.js.map +1 -0
  46. package/dist/tool-resource-claims.d.ts +31 -0
  47. package/dist/tool-resource-claims.d.ts.map +1 -0
  48. package/dist/tool-resource-claims.js +128 -0
  49. package/dist/tool-resource-claims.js.map +1 -0
  50. package/dist/tool-timeout.d.ts +96 -0
  51. package/dist/tool-timeout.d.ts.map +1 -0
  52. package/dist/tool-timeout.js +173 -0
  53. package/dist/tool-timeout.js.map +1 -0
  54. package/dist/tool-transcript-integrity.d.ts +65 -0
  55. package/dist/tool-transcript-integrity.d.ts.map +1 -0
  56. package/dist/tool-transcript-integrity.js +223 -0
  57. package/dist/tool-transcript-integrity.js.map +1 -0
  58. package/dist/types.d.ts +219 -10
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/types.js +50 -1
  61. package/dist/types.js.map +1 -1
  62. package/package.json +2 -2
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Pure, browser-safe transcript integrity inspector and repair for the tool-call
3
+ * transcripts produced by the agent loop.
4
+ *
5
+ * The inspector detects five classes of structural corruption:
6
+ * - `missing_result`: a tool call with no matching tool result
7
+ * - `duplicate_result`: two or more tool results for the same call id
8
+ * - `orphan_result`: a tool result whose call id was never emitted
9
+ * - `duplicate_call_id`: two or more tool calls sharing an id
10
+ * - `interleaved_non_result`: a non-result message breaks the contiguous run of
11
+ * results that must follow an assistant message's tool calls (including a
12
+ * result that arrives before its call, after a user/custom boundary, or at the
13
+ * start of the transcript)
14
+ *
15
+ * IDs are accounted globally, so an orphan or duplicate is caught regardless of
16
+ * where it appears. Repair is intentionally conservative: it only appends
17
+ * synthetic results for unambiguous missing tail calls. Any duplicate, orphan,
18
+ * interleaving, or mid-transcript gap fails closed.
19
+ *
20
+ * This module uses no platform APIs (no `process`, fs, or timers) so it is safe
21
+ * to run in a browser.
22
+ */
23
+ import { createImmutableSnapshot } from "./plain-data.js";
24
+ import { createToolResultEnvelope } from "./types.js";
25
+ /** Thrown by {@link repairTranscriptIntegrity} when a transcript cannot be safely repaired. */
26
+ export class TranscriptIntegrityError extends Error {
27
+ report;
28
+ constructor(message, report) {
29
+ super(message);
30
+ this.name = "TranscriptIntegrityError";
31
+ this.report = report;
32
+ }
33
+ }
34
+ function isAssistantMessage(message) {
35
+ return message.role === "assistant";
36
+ }
37
+ function isToolResultMessage(message) {
38
+ return message.role === "toolResult";
39
+ }
40
+ function isToolCallBlock(block) {
41
+ return block?.type === "toolCall";
42
+ }
43
+ /**
44
+ * Inspect a transcript and report every detected integrity issue. Pure: does not
45
+ * mutate the input and uses no platform APIs.
46
+ *
47
+ * IDs are accounted globally first (catching duplicates, orphans, and missing
48
+ * results anywhere in the transcript), then a single left-to-right pass checks
49
+ * the ordering invariant that an assistant message's tool calls must be followed
50
+ * by a contiguous run of their results with no intervening non-result message.
51
+ */
52
+ export function inspectTranscriptIntegrity(messages) {
53
+ const issues = [];
54
+ const callIdCount = new Map();
55
+ const resultIdCount = new Map();
56
+ const callToolName = new Map();
57
+ for (const message of messages) {
58
+ if (isAssistantMessage(message)) {
59
+ for (const block of message.content) {
60
+ if (isToolCallBlock(block)) {
61
+ callIdCount.set(block.id, (callIdCount.get(block.id) ?? 0) + 1);
62
+ if (!callToolName.has(block.id)) {
63
+ callToolName.set(block.id, block.name);
64
+ }
65
+ }
66
+ }
67
+ }
68
+ else if (isToolResultMessage(message)) {
69
+ resultIdCount.set(message.toolCallId, (resultIdCount.get(message.toolCallId) ?? 0) + 1);
70
+ }
71
+ }
72
+ for (const [id, count] of callIdCount) {
73
+ if (count > 1) {
74
+ issues.push({ kind: "duplicate_call_id", toolCallId: id, toolName: callToolName.get(id) });
75
+ }
76
+ }
77
+ for (const [id, count] of resultIdCount) {
78
+ if (count > 1) {
79
+ issues.push({ kind: "duplicate_result", toolCallId: id });
80
+ }
81
+ }
82
+ for (const [id] of resultIdCount) {
83
+ if (!callIdCount.has(id)) {
84
+ issues.push({ kind: "orphan_result", toolCallId: id });
85
+ }
86
+ }
87
+ for (const [id] of callIdCount) {
88
+ if (!resultIdCount.has(id)) {
89
+ issues.push({ kind: "missing_result", toolCallId: id, toolName: callToolName.get(id) });
90
+ }
91
+ }
92
+ const pending = new Map();
93
+ let resultsRegion = false;
94
+ for (const message of messages) {
95
+ if (isAssistantMessage(message)) {
96
+ const calls = message.content.filter(isToolCallBlock);
97
+ if (calls.length > 0) {
98
+ // A new assistant message with tool calls while previous calls are
99
+ // still unresolved means a non-result interrupted the prior region.
100
+ if (pending.size > 0) {
101
+ for (const [id, name] of pending) {
102
+ issues.push({ kind: "interleaved_non_result", toolCallId: id, toolName: name });
103
+ }
104
+ break;
105
+ }
106
+ for (const call of calls) {
107
+ pending.set(call.id, call.name);
108
+ }
109
+ resultsRegion = true;
110
+ }
111
+ else if (resultsRegion && pending.size > 0) {
112
+ for (const [id, name] of pending) {
113
+ issues.push({ kind: "interleaved_non_result", toolCallId: id, toolName: name });
114
+ }
115
+ break;
116
+ }
117
+ }
118
+ else if (isToolResultMessage(message)) {
119
+ if (resultsRegion && pending.has(message.toolCallId)) {
120
+ pending.delete(message.toolCallId);
121
+ if (pending.size === 0) {
122
+ resultsRegion = false;
123
+ }
124
+ }
125
+ else if (callIdCount.has(message.toolCallId)) {
126
+ // Known call id arriving outside its contiguous results region:
127
+ // orphan-at-start, after-user, or assistant -> non-result -> result.
128
+ issues.push({ kind: "interleaved_non_result", toolCallId: message.toolCallId });
129
+ break;
130
+ }
131
+ // Unknown-id orphans are already reported above; do not double report.
132
+ }
133
+ else {
134
+ // user or custom non-result message: breaks an open results region.
135
+ if (resultsRegion && pending.size > 0) {
136
+ for (const [id, name] of pending) {
137
+ issues.push({ kind: "interleaved_non_result", toolCallId: id, toolName: name });
138
+ }
139
+ break;
140
+ }
141
+ }
142
+ }
143
+ const frozenIssues = Object.freeze(issues.map((issue) => Object.freeze({ ...issue })));
144
+ return Object.freeze({ ok: frozenIssues.length === 0, issues: frozenIssues });
145
+ }
146
+ /**
147
+ * Create a synthetic terminal tool result. Shared by transcript repair and the
148
+ * agent-loop abort closure so the disposition of an unresolved call is encoded
149
+ * identically everywhere. The `details.omk` envelope marks the artifact as
150
+ * synthetic (`executionStarted: false`): it closes the provider transcript and
151
+ * never claims the tool actually ran.
152
+ */
153
+ export function createSyntheticToolResult(toolCallId, toolName, reason, timestamp = Date.now(), disposition = "aborted") {
154
+ const envelope = createToolResultEnvelope({
155
+ synthetic: true,
156
+ disposition,
157
+ reason,
158
+ executionStarted: false,
159
+ });
160
+ return createImmutableSnapshot({
161
+ role: "toolResult",
162
+ toolCallId,
163
+ toolName,
164
+ content: [{ type: "text", text: reason }],
165
+ details: { omk: envelope },
166
+ isError: true,
167
+ timestamp,
168
+ });
169
+ }
170
+ /**
171
+ * Repair a transcript by appending synthetic results for unambiguous missing
172
+ * tail calls. Fails closed (throws {@link TranscriptIntegrityError}) for any
173
+ * duplicate, orphan, interleaving, or mid-transcript gap. Idempotent: a second
174
+ * call on already-repaired input returns an equal copy with no new messages.
175
+ */
176
+ export function repairTranscriptIntegrity(messages, reason) {
177
+ const report = inspectTranscriptIntegrity(messages);
178
+ for (const issue of report.issues) {
179
+ if (issue.kind !== "missing_result") {
180
+ throw new TranscriptIntegrityError(`Cannot repair transcript: detected ${issue.kind} for tool call ${issue.toolCallId}`, report);
181
+ }
182
+ }
183
+ const missingIds = new Set(report.issues.map((issue) => issue.toolCallId));
184
+ if (missingIds.size === 0) {
185
+ return messages.slice();
186
+ }
187
+ let lastAssistantIndex = -1;
188
+ for (let i = messages.length - 1; i >= 0; i--) {
189
+ if (isAssistantMessage(messages[i])) {
190
+ lastAssistantIndex = i;
191
+ break;
192
+ }
193
+ }
194
+ if (lastAssistantIndex === -1) {
195
+ throw new TranscriptIntegrityError("Cannot repair transcript: missing results without an assistant message", report);
196
+ }
197
+ const lastAssistant = messages[lastAssistantIndex];
198
+ if (!isAssistantMessage(lastAssistant)) {
199
+ throw new TranscriptIntegrityError("Cannot repair transcript: trailing message is not assistant", report);
200
+ }
201
+ const tailCalls = lastAssistant.content.filter(isToolCallBlock);
202
+ const tailCallIds = new Set(tailCalls.map((call) => call.id));
203
+ for (const id of missingIds) {
204
+ if (!tailCallIds.has(id)) {
205
+ throw new TranscriptIntegrityError(`Cannot repair transcript: missing result for ${id} is not in the trailing assistant message`, report);
206
+ }
207
+ }
208
+ for (let i = lastAssistantIndex + 1; i < messages.length; i++) {
209
+ const message = messages[i];
210
+ if (!isToolResultMessage(message) || !tailCallIds.has(message.toolCallId)) {
211
+ throw new TranscriptIntegrityError("Cannot repair transcript: non-result message after the trailing assistant message", report);
212
+ }
213
+ }
214
+ const text = reason ?? "Tool result missing; synthesized by transcript repair";
215
+ const repaired = messages.slice();
216
+ for (const call of tailCalls) {
217
+ if (missingIds.has(call.id)) {
218
+ repaired.push(createSyntheticToolResult(call.id, call.name, text));
219
+ }
220
+ }
221
+ return repaired;
222
+ }
223
+ //# sourceMappingURL=tool-transcript-integrity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-transcript-integrity.js","sourceRoot":"","sources":["../src/tool-transcript-integrity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure, browser-safe transcript integrity inspector and repair for the tool-call\n * transcripts produced by the agent loop.\n *\n * The inspector detects five classes of structural corruption:\n * - `missing_result`: a tool call with no matching tool result\n * - `duplicate_result`: two or more tool results for the same call id\n * - `orphan_result`: a tool result whose call id was never emitted\n * - `duplicate_call_id`: two or more tool calls sharing an id\n * - `interleaved_non_result`: a non-result message breaks the contiguous run of\n * results that must follow an assistant message's tool calls (including a\n * result that arrives before its call, after a user/custom boundary, or at the\n * start of the transcript)\n *\n * IDs are accounted globally, so an orphan or duplicate is caught regardless of\n * where it appears. Repair is intentionally conservative: it only appends\n * synthetic results for unambiguous missing tail calls. Any duplicate, orphan,\n * interleaving, or mid-transcript gap fails closed.\n *\n * This module uses no platform APIs (no `process`, fs, or timers) so it is safe\n * to run in a browser.\n */\n\nimport type { AssistantMessage, ToolCall, ToolResultMessage } from \"omk-ai\";\nimport { createImmutableSnapshot } from \"./plain-data.ts\";\nimport { type AgentMessage, createToolResultEnvelope } from \"./types.ts\";\n\nexport type TranscriptIntegrityIssueKind =\n\t| \"missing_result\"\n\t| \"duplicate_result\"\n\t| \"orphan_result\"\n\t| \"duplicate_call_id\"\n\t| \"interleaved_non_result\";\n\nexport interface TranscriptIntegrityIssue {\n\treadonly kind: TranscriptIntegrityIssueKind;\n\treadonly toolCallId: string;\n\treadonly toolName?: string;\n}\n\nexport interface TranscriptIntegrityReport {\n\treadonly ok: boolean;\n\treadonly issues: readonly TranscriptIntegrityIssue[];\n}\n\n/** Thrown by {@link repairTranscriptIntegrity} when a transcript cannot be safely repaired. */\nexport class TranscriptIntegrityError extends Error {\n\treadonly report: TranscriptIntegrityReport;\n\tconstructor(message: string, report: TranscriptIntegrityReport) {\n\t\tsuper(message);\n\t\tthis.name = \"TranscriptIntegrityError\";\n\t\tthis.report = report;\n\t}\n}\n\nfunction isAssistantMessage(message: AgentMessage): message is AssistantMessage {\n\treturn message.role === \"assistant\";\n}\n\nfunction isToolResultMessage(message: AgentMessage): message is ToolResultMessage {\n\treturn message.role === \"toolResult\";\n}\n\nfunction isToolCallBlock(block: unknown): block is ToolCall {\n\treturn (block as { type?: string } | null)?.type === \"toolCall\";\n}\n\n/**\n * Inspect a transcript and report every detected integrity issue. Pure: does not\n * mutate the input and uses no platform APIs.\n *\n * IDs are accounted globally first (catching duplicates, orphans, and missing\n * results anywhere in the transcript), then a single left-to-right pass checks\n * the ordering invariant that an assistant message's tool calls must be followed\n * by a contiguous run of their results with no intervening non-result message.\n */\nexport function inspectTranscriptIntegrity(messages: readonly AgentMessage[]): TranscriptIntegrityReport {\n\tconst issues: TranscriptIntegrityIssue[] = [];\n\n\tconst callIdCount = new Map<string, number>();\n\tconst resultIdCount = new Map<string, number>();\n\tconst callToolName = new Map<string, string>();\n\n\tfor (const message of messages) {\n\t\tif (isAssistantMessage(message)) {\n\t\t\tfor (const block of message.content) {\n\t\t\t\tif (isToolCallBlock(block)) {\n\t\t\t\t\tcallIdCount.set(block.id, (callIdCount.get(block.id) ?? 0) + 1);\n\t\t\t\t\tif (!callToolName.has(block.id)) {\n\t\t\t\t\t\tcallToolName.set(block.id, block.name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (isToolResultMessage(message)) {\n\t\t\tresultIdCount.set(message.toolCallId, (resultIdCount.get(message.toolCallId) ?? 0) + 1);\n\t\t}\n\t}\n\n\tfor (const [id, count] of callIdCount) {\n\t\tif (count > 1) {\n\t\t\tissues.push({ kind: \"duplicate_call_id\", toolCallId: id, toolName: callToolName.get(id) });\n\t\t}\n\t}\n\tfor (const [id, count] of resultIdCount) {\n\t\tif (count > 1) {\n\t\t\tissues.push({ kind: \"duplicate_result\", toolCallId: id });\n\t\t}\n\t}\n\tfor (const [id] of resultIdCount) {\n\t\tif (!callIdCount.has(id)) {\n\t\t\tissues.push({ kind: \"orphan_result\", toolCallId: id });\n\t\t}\n\t}\n\tfor (const [id] of callIdCount) {\n\t\tif (!resultIdCount.has(id)) {\n\t\t\tissues.push({ kind: \"missing_result\", toolCallId: id, toolName: callToolName.get(id) });\n\t\t}\n\t}\n\n\tconst pending = new Map<string, string>();\n\tlet resultsRegion = false;\n\n\tfor (const message of messages) {\n\t\tif (isAssistantMessage(message)) {\n\t\t\tconst calls = message.content.filter(isToolCallBlock);\n\t\t\tif (calls.length > 0) {\n\t\t\t\t// A new assistant message with tool calls while previous calls are\n\t\t\t\t// still unresolved means a non-result interrupted the prior region.\n\t\t\t\tif (pending.size > 0) {\n\t\t\t\t\tfor (const [id, name] of pending) {\n\t\t\t\t\t\tissues.push({ kind: \"interleaved_non_result\", toolCallId: id, toolName: name });\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tfor (const call of calls) {\n\t\t\t\t\tpending.set(call.id, call.name);\n\t\t\t\t}\n\t\t\t\tresultsRegion = true;\n\t\t\t} else if (resultsRegion && pending.size > 0) {\n\t\t\t\tfor (const [id, name] of pending) {\n\t\t\t\t\tissues.push({ kind: \"interleaved_non_result\", toolCallId: id, toolName: name });\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else if (isToolResultMessage(message)) {\n\t\t\tif (resultsRegion && pending.has(message.toolCallId)) {\n\t\t\t\tpending.delete(message.toolCallId);\n\t\t\t\tif (pending.size === 0) {\n\t\t\t\t\tresultsRegion = false;\n\t\t\t\t}\n\t\t\t} else if (callIdCount.has(message.toolCallId)) {\n\t\t\t\t// Known call id arriving outside its contiguous results region:\n\t\t\t\t// orphan-at-start, after-user, or assistant -> non-result -> result.\n\t\t\t\tissues.push({ kind: \"interleaved_non_result\", toolCallId: message.toolCallId });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// Unknown-id orphans are already reported above; do not double report.\n\t\t} else {\n\t\t\t// user or custom non-result message: breaks an open results region.\n\t\t\tif (resultsRegion && pending.size > 0) {\n\t\t\t\tfor (const [id, name] of pending) {\n\t\t\t\t\tissues.push({ kind: \"interleaved_non_result\", toolCallId: id, toolName: name });\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst frozenIssues = Object.freeze(issues.map((issue) => Object.freeze({ ...issue })));\n\treturn Object.freeze({ ok: frozenIssues.length === 0, issues: frozenIssues });\n}\n\n/**\n * Create a synthetic terminal tool result. Shared by transcript repair and the\n * agent-loop abort closure so the disposition of an unresolved call is encoded\n * identically everywhere. The `details.omk` envelope marks the artifact as\n * synthetic (`executionStarted: false`): it closes the provider transcript and\n * never claims the tool actually ran.\n */\nexport function createSyntheticToolResult(\n\ttoolCallId: string,\n\ttoolName: string,\n\treason: string,\n\ttimestamp: number = Date.now(),\n\tdisposition: \"aborted\" | \"skipped\" = \"aborted\",\n): ToolResultMessage {\n\tconst envelope = createToolResultEnvelope({\n\t\tsynthetic: true,\n\t\tdisposition,\n\t\treason,\n\t\texecutionStarted: false,\n\t});\n\treturn createImmutableSnapshot({\n\t\trole: \"toolResult\",\n\t\ttoolCallId,\n\t\ttoolName,\n\t\tcontent: [{ type: \"text\", text: reason }],\n\t\tdetails: { omk: envelope },\n\t\tisError: true,\n\t\ttimestamp,\n\t});\n}\n\n/**\n * Repair a transcript by appending synthetic results for unambiguous missing\n * tail calls. Fails closed (throws {@link TranscriptIntegrityError}) for any\n * duplicate, orphan, interleaving, or mid-transcript gap. Idempotent: a second\n * call on already-repaired input returns an equal copy with no new messages.\n */\nexport function repairTranscriptIntegrity(messages: readonly AgentMessage[], reason?: string): AgentMessage[] {\n\tconst report = inspectTranscriptIntegrity(messages);\n\n\tfor (const issue of report.issues) {\n\t\tif (issue.kind !== \"missing_result\") {\n\t\t\tthrow new TranscriptIntegrityError(\n\t\t\t\t`Cannot repair transcript: detected ${issue.kind} for tool call ${issue.toolCallId}`,\n\t\t\t\treport,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst missingIds = new Set(report.issues.map((issue) => issue.toolCallId));\n\tif (missingIds.size === 0) {\n\t\treturn messages.slice();\n\t}\n\n\tlet lastAssistantIndex = -1;\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tif (isAssistantMessage(messages[i])) {\n\t\t\tlastAssistantIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (lastAssistantIndex === -1) {\n\t\tthrow new TranscriptIntegrityError(\n\t\t\t\"Cannot repair transcript: missing results without an assistant message\",\n\t\t\treport,\n\t\t);\n\t}\n\n\tconst lastAssistant = messages[lastAssistantIndex];\n\tif (!isAssistantMessage(lastAssistant)) {\n\t\tthrow new TranscriptIntegrityError(\"Cannot repair transcript: trailing message is not assistant\", report);\n\t}\n\tconst tailCalls = lastAssistant.content.filter(isToolCallBlock);\n\tconst tailCallIds = new Set(tailCalls.map((call) => call.id));\n\tfor (const id of missingIds) {\n\t\tif (!tailCallIds.has(id)) {\n\t\t\tthrow new TranscriptIntegrityError(\n\t\t\t\t`Cannot repair transcript: missing result for ${id} is not in the trailing assistant message`,\n\t\t\t\treport,\n\t\t\t);\n\t\t}\n\t}\n\n\tfor (let i = lastAssistantIndex + 1; i < messages.length; i++) {\n\t\tconst message = messages[i];\n\t\tif (!isToolResultMessage(message) || !tailCallIds.has(message.toolCallId)) {\n\t\t\tthrow new TranscriptIntegrityError(\n\t\t\t\t\"Cannot repair transcript: non-result message after the trailing assistant message\",\n\t\t\t\treport,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst text = reason ?? \"Tool result missing; synthesized by transcript repair\";\n\tconst repaired = messages.slice();\n\tfor (const call of tailCalls) {\n\t\tif (missingIds.has(call.id)) {\n\t\t\trepaired.push(createSyntheticToolResult(call.id, call.name, text));\n\t\t}\n\t}\n\treturn repaired;\n}\n"]}
package/dist/types.d.ts CHANGED
@@ -19,6 +19,20 @@ export type StreamFn = (...args: Parameters<typeof streamSimple>) => ReturnType<
19
19
  * while tool-result message artifacts are emitted later in assistant source order.
20
20
  */
21
21
  export type ToolExecutionMode = "sequential" | "parallel";
22
+ /**
23
+ * Tool-call scheduler selection for a single assistant turn's tool batch.
24
+ *
25
+ * - `"waves-v1"` (default): the original contiguous-wave scheduler
26
+ * (`partitionToolBatchWaves`). Established behavior and the rollback target.
27
+ * - `"dag-v2"`: the deterministic resource-claim DAG scheduler
28
+ * (`scheduleDagLevels`). Active only when explicitly selected. It resolves
29
+ * per-call resource claims and groups conflict-free calls into source-index
30
+ * DAG levels so a conflicting call no longer head-of-line-blocks independent
31
+ * later calls (e.g. `write x, write x, write y` schedules as `[[0, 2], [1]]`).
32
+ * Final tool results are buffered globally and emitted in original source
33
+ * order.
34
+ */
35
+ export type ToolSchedulerKind = "waves-v1" | "dag-v2";
22
36
  /**
23
37
  * Controls how many queued user messages are injected when the agent loop reaches a queue drain point.
24
38
  *
@@ -220,17 +234,69 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
220
234
  * Default: "parallel"
221
235
  */
222
236
  toolExecution?: ToolExecutionMode;
237
+ /**
238
+ * Default per-tool execution timeout in milliseconds applied to every tool
239
+ * call that does not resolve a more specific timeout.
240
+ *
241
+ * This is deliberately named to avoid colliding with the inherited
242
+ * {@link SimpleStreamOptions.timeoutMs} (the provider request timeout).
243
+ * Absent, non-finite, or non-positive values disable the tool timeout and
244
+ * preserve the current unbounded execution behavior.
245
+ */
246
+ toolTimeoutMs?: number;
247
+ /**
248
+ * Per-tool-name execution timeouts in milliseconds. An entry here overrides
249
+ * {@link toolTimeoutMs} for that tool name, and is itself overridden by a
250
+ * per-tool {@link AgentTool.timeoutMs}. Absent, non-finite, or non-positive
251
+ * entries disable the timeout for that name.
252
+ */
253
+ toolTimeouts?: Record<string, number>;
254
+ /**
255
+ * Tool-call scheduler. Defaults to `"waves-v1"` (the established
256
+ * contiguous-wave scheduler). Set to `"dag-v2"` to opt into the
257
+ * deterministic resource-claim DAG scheduler. The v1 path and its exports
258
+ * are unchanged when this is unset or `"waves-v1"`.
259
+ */
260
+ toolScheduler?: ToolSchedulerKind;
261
+ /**
262
+ * dag-v2 only. Optional positive width cap. When set, each DAG level is
263
+ * split into deterministic contiguous chunks of at most this many calls
264
+ * (preserving source order) so a wide conflict-free level does not fan out
265
+ * unbounded. Absent, non-finite, or non-positive values leave each level
266
+ * whole. No effect on the default `"waves-v1"` scheduler.
267
+ */
268
+ maxToolConcurrency?: number;
269
+ /**
270
+ * dag-v2 only. When `false` (default), an extension/custom tool with
271
+ * `executionMode: "parallel"` and no resource claims is treated as freely
272
+ * parallel (compatibility). When `true`, such tools are treated as
273
+ * exclusive (run alone). Unknown tools and bash are always exclusive
274
+ * regardless of this flag. No effect on the default `"waves-v1"` scheduler.
275
+ */
276
+ strictExtensionClaims?: boolean;
223
277
  /** Working directory for path-scoped parallel tool batch checks (read/write/edit). */
224
278
  cwd?: string;
279
+ /**
280
+ * dag-v2 only. Optional platform identity resolver used to detect
281
+ * symlink/hardlink/drive/UNC path aliases when scheduling resource claims.
282
+ * Absent, the scheduler uses lexical canonicalization only (browser-safe).
283
+ * Resolver failure isolates the call as exclusive (fail closed).
284
+ */
285
+ resourceKeyResolver?: ResourceKeyResolver;
286
+ /**
287
+ * Execution policy defaults for every tool call in this run. Only
288
+ * `lateSettlement` is consumed today; it defaults to `"audit"`.
289
+ */
290
+ toolExecutionPolicy?: Partial<ToolExecutionPolicy>;
225
291
  /**
226
292
  * Called before a tool is executed, after arguments have been validated.
227
293
  *
228
294
  * Return `{ block: true }` to prevent execution. The loop emits an error tool result instead.
229
- * The hook receives the agent abort signal and is responsible for honoring it.
295
+ * The runtime bounds the hook by the parent abort signal and ignores a late settlement.
230
296
  */
231
297
  beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;
232
298
  /**
233
- * Called after a tool finishes executing, before `tool_execution_end` and tool-result message events are emitted.
299
+ * Called after execution but skipped for an immutable committed timeout/abort terminal.
234
300
  *
235
301
  * Return an `AfterToolCallResult` to override parts of the executed tool result:
236
302
  * - `content` replaces the full content array
@@ -239,7 +305,7 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
239
305
  * - `terminate` replaces the early-termination hint
240
306
  *
241
307
  * Any omitted fields keep their original values. No deep merge is performed.
242
- * The hook receives the agent abort signal and is responsible for honoring it.
308
+ * The runtime bounds the hook by the parent abort signal and ignores a late settlement.
243
309
  */
244
310
  afterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;
245
311
  }
@@ -317,6 +383,45 @@ export interface AgentToolResult<T> {
317
383
  }
318
384
  /** Callback used by tools to stream partial execution updates. */
319
385
  export type AgentToolUpdateCallback<T = any> = (partialResult: AgentToolResult<T>) => void;
386
+ /** Access mode used by a tool resource claim. */
387
+ export type ResourceAccess = "read" | "write" | "exclusive";
388
+ /** Access modes valid for path claims and built-in read/write resources. */
389
+ export type ToolResourceAccess = Extract<ResourceAccess, "read" | "write">;
390
+ /**
391
+ * A resource touched by one tool call.
392
+ *
393
+ * Path claims conflict on lexical parent/child overlap. Other claim kinds
394
+ * conflict when their keys are equal. `exclusive` access conflicts with every
395
+ * call, regardless of resource kind or key.
396
+ */
397
+ export type ToolResourceClaim = {
398
+ kind: "path";
399
+ key: string;
400
+ access: ToolResourceAccess;
401
+ /** Canonical real-path identity (symlink-resolved), when a resolver is injected. */
402
+ realKey?: string;
403
+ /** `dev:ino` identity for an existing file, when a resolver is injected. */
404
+ inodeKey?: string;
405
+ } | {
406
+ kind: "session" | "terminal" | "network" | "global";
407
+ key: string;
408
+ access: ResourceAccess;
409
+ };
410
+ /**
411
+ * Resource contract returned by {@link AgentTool.resourceClaims}.
412
+ *
413
+ * Return `"exclusive"` when the call must run alone, or a non-empty claim
414
+ * list. The dag-v2 scheduler fails closed to exclusive when a resolver throws,
415
+ * rejects, or returns an empty or malformed claim list.
416
+ */
417
+ export type ToolResourceClaims = readonly ToolResourceClaim[] | "exclusive";
418
+ /** Context passed to {@link AgentTool.resourceClaims}. */
419
+ export interface ToolResourceClaimsContext {
420
+ /** Working directory used by the scheduler for this call batch. */
421
+ cwd: string;
422
+ /** Provider-supplied id of the tool call being scheduled. */
423
+ toolCallId: string;
424
+ }
320
425
  /** Tool definition used by the agent runtime. */
321
426
  export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {
322
427
  /** Human-readable label for UI display. */
@@ -336,7 +441,105 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
336
441
  * If omitted, the default execution mode applies.
337
442
  */
338
443
  executionMode?: ToolExecutionMode;
444
+ /**
445
+ * dag-v2 resource contract for this tool call.
446
+ *
447
+ * The resolver receives the raw call arguments and scheduler context before
448
+ * execution starts. It may return synchronously or asynchronously. Throwing,
449
+ * rejecting, returning malformed data, or returning no usable claim fails
450
+ * closed to exclusive scheduling. This field has no effect on waves-v1.
451
+ */
452
+ resourceClaims?: (args: unknown, context: ToolResourceClaimsContext) => ToolResourceClaims | Promise<ToolResourceClaims>;
453
+ /**
454
+ * Optional per-call execution timeout in milliseconds.
455
+ *
456
+ * When positive, the tool's `execute` promise is raced against this timeout at
457
+ * the shared execution chokepoint. If the timeout elapses first, the loop
458
+ * commits an immediate terminal timeout result and aborts the tool's child
459
+ * `AbortSignal`, so a tool that ignores the signal cannot stall the run.
460
+ *
461
+ * Precedence: this value overrides `AgentLoopConfig.toolTimeouts[name]`, which
462
+ * overrides `AgentLoopConfig.toolTimeoutMs`. Absent, non-finite, or
463
+ * non-positive values disable the timeout and preserve current behavior.
464
+ */
465
+ timeoutMs?: number;
466
+ }
467
+ /**
468
+ * Terminal disposition of one tool call in a transcript (six-state model).
469
+ *
470
+ * - `completed`: the tool ran and returned a result.
471
+ * - `failed`: the tool ran and threw/rejected, or could not be prepared.
472
+ * - `blocked`: a policy hook prevented execution.
473
+ * - `aborted`: the run's abort terminated the call (started or unstarted).
474
+ * - `timeout`: the per-call timeout terminated the call.
475
+ * - `skipped`: the scheduler closed the call without ever starting it.
476
+ */
477
+ export type ToolCallDisposition = "completed" | "failed" | "blocked" | "aborted" | "timeout" | "skipped";
478
+ /** Schema tag for the {@link ToolResultEnvelope} attached to terminal tool results. */
479
+ export declare const TOOL_RESULT_ENVELOPE_SCHEMA: "tool-result/v2";
480
+ /**
481
+ * Model-invisible disposition envelope stamped at `details.omk` on every
482
+ * terminal tool result. `synthetic` means the result artifact was fabricated
483
+ * by the runtime and does not represent the tool's returned details;
484
+ * `executionStarted` records whether `AgentTool.execute` actually began.
485
+ */
486
+ export interface ToolResultEnvelope {
487
+ schema: typeof TOOL_RESULT_ENVELOPE_SCHEMA;
488
+ synthetic: boolean;
489
+ disposition: ToolCallDisposition;
490
+ reason?: string;
491
+ timeoutMs?: number;
492
+ executionStarted: boolean;
493
+ }
494
+ /** Input accepted by the executor-owned {@link createToolResultEnvelope} boundary. */
495
+ export type ToolResultEnvelopeInput = Omit<ToolResultEnvelope, "schema">;
496
+ /** Compatibility wrapper used when original tool details are not a plain object. */
497
+ export interface WrappedToolResultDetails {
498
+ readonly originalDetails: unknown;
499
+ readonly omk: ToolResultEnvelope;
339
500
  }
501
+ /** Validate both the v2 shape and disposition-specific executor invariants. */
502
+ export declare function isToolResultEnvelope(value: unknown): value is ToolResultEnvelope;
503
+ /**
504
+ * Construct and freeze a validated executor envelope. Invalid combinations
505
+ * throw at the executor boundary instead of being persisted into a transcript.
506
+ */
507
+ export declare function createToolResultEnvelope(input: ToolResultEnvelopeInput): ToolResultEnvelope;
508
+ /**
509
+ * Per-call execution policy (ALG-004 §6.2). `lateSettlement` controls whether
510
+ * a real tool promise settling after its terminal cause is surfaced as an
511
+ * audit-only event (`"audit"`, default) or silently dropped (`"ignore"`).
512
+ * Terminal results are immutable either way.
513
+ */
514
+ export interface ToolExecutionPolicy {
515
+ timeoutMs?: number;
516
+ cancelSiblingsOnFatal?: boolean;
517
+ lateSettlement: "audit" | "ignore";
518
+ }
519
+ /** Committed disposition for a tool call terminated by timeout or parent abort. */
520
+ export type ToolTimeoutDisposition = "timeout" | "aborted";
521
+ /** Identity keys resolved for one raw path by a {@link ResourceKeyResolver}. */
522
+ export interface ResolvedResourceKeys {
523
+ /** Canonical lexical key (slash-normalized, dot-collapsed, drive/UNC normalized). */
524
+ lexicalKey: string;
525
+ /** Canonical real path: nearest existing ancestor realpath + non-existing suffix. */
526
+ realKey?: string;
527
+ /** `dev:ino` identity for an existing file (symlink target/hardlink aware). */
528
+ inodeKey?: string;
529
+ }
530
+ /**
531
+ * Optional platform identity resolver injected into the dag-v2 scheduler
532
+ * (§5.5 stage 2). The shared agent package stays browser-safe; a Node
533
+ * implementation lives in `omk-agent-core/node`. The raw path reaches the
534
+ * resolver before cwd-relative lexical canonicalization so Node-only tilde and
535
+ * platform aliases remain visible. Returning `null`, throwing, rejecting, or
536
+ * returning malformed keys isolates the call as exclusive.
537
+ */
538
+ export interface ResourceKeyResolver {
539
+ resolvePath(rawPath: string, cwd: string): Promise<ResolvedResourceKeys | null> | ResolvedResourceKeys | null;
540
+ }
541
+ /** How a tool's real promise eventually settled after its terminal cause won. */
542
+ export type ToolLateSettlementOutcome = "resolved" | "rejected";
340
543
  /** Context snapshot passed into the low-level agent loop. */
341
544
  export interface AgentContext {
342
545
  /** System prompt included with the request. */
@@ -349,9 +552,9 @@ export interface AgentContext {
349
552
  /**
350
553
  * Events emitted by the Agent for UI updates.
351
554
  *
352
- * `agent_end` is the last event emitted for a run, but awaited `Agent.subscribe()`
353
- * listeners for that event are still part of run settlement. The agent becomes
354
- * idle only after those listeners finish.
555
+ * `agent_end` is the last event emitted for a run, and its subscribers remain
556
+ * part of run settlement. `tool_execution_update` delivery is observation-only:
557
+ * subscriber promises are detached and cannot delay timeout/abort terminality.
355
558
  */
356
559
  export type AgentEvent = {
357
560
  type: "agent_start";
@@ -378,18 +581,24 @@ export type AgentEvent = {
378
581
  type: "tool_execution_start";
379
582
  toolCallId: string;
380
583
  toolName: string;
381
- args: any;
584
+ args: unknown;
382
585
  } | {
383
586
  type: "tool_execution_update";
384
587
  toolCallId: string;
385
588
  toolName: string;
386
- args: any;
387
- partialResult: any;
589
+ args: unknown;
590
+ partialResult: unknown;
388
591
  } | {
389
592
  type: "tool_execution_end";
390
593
  toolCallId: string;
391
594
  toolName: string;
392
- result: any;
595
+ result: unknown;
393
596
  isError: boolean;
597
+ } | {
598
+ type: "tool_execution_late_settlement";
599
+ toolCallId: string;
600
+ toolName: string;
601
+ disposition: ToolTimeoutDisposition;
602
+ outcome: ToolLateSettlementOutcome;
394
603
  };
395
604
  //# sourceMappingURL=types.d.ts.map