odai-dsh-agent 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +3 -3
  2. package/build/bin/odai-dsh-agent.d.mts +3 -0
  3. package/build/bin/odai-dsh-agent.d.mts.map +1 -0
  4. package/build/bin/odai-dsh-agent.mjs +114 -0
  5. package/build/bin/odai-dsh-agent.mjs.map +1 -0
  6. package/build/src/dsh-version.d.mts +16 -0
  7. package/build/src/dsh-version.d.mts.map +1 -0
  8. package/build/src/dsh-version.mjs +20 -0
  9. package/build/src/dsh-version.mjs.map +1 -0
  10. package/build/src/installer.d.mts +46 -0
  11. package/build/src/installer.d.mts.map +1 -0
  12. package/build/src/installer.mjs +398 -0
  13. package/build/src/installer.mjs.map +1 -0
  14. package/package.json +13 -12
  15. package/preset/odai/runtime/compaction-config.d.mts +66 -0
  16. package/preset/odai/runtime/compaction-config.d.mts.map +1 -0
  17. package/preset/odai/runtime/compaction-config.mjs +320 -342
  18. package/preset/odai/runtime/compaction-config.mjs.map +1 -0
  19. package/preset/odai/runtime/compaction-runtime.d.mts +68 -0
  20. package/preset/odai/runtime/compaction-runtime.d.mts.map +1 -0
  21. package/preset/odai/runtime/compaction-runtime.mjs +157 -0
  22. package/preset/odai/runtime/compaction-runtime.mjs.map +1 -0
  23. package/preset/odai/runtime/context-activation.d.mts +31 -0
  24. package/preset/odai/runtime/context-activation.d.mts.map +1 -0
  25. package/preset/odai/runtime/context-activation.mjs +55 -49
  26. package/preset/odai/runtime/context-activation.mjs.map +1 -0
  27. package/preset/odai/runtime/context-capability.d.mts +16 -0
  28. package/preset/odai/runtime/context-capability.d.mts.map +1 -0
  29. package/preset/odai/runtime/context-capability.mjs +79 -74
  30. package/preset/odai/runtime/context-capability.mjs.map +1 -0
  31. package/preset/odai/runtime/governance.d.mts +30 -0
  32. package/preset/odai/runtime/governance.d.mts.map +1 -0
  33. package/preset/odai/runtime/governance.mjs +126 -113
  34. package/preset/odai/runtime/governance.mjs.map +1 -0
  35. package/preset/odai/runtime/human-care.d.mts +13 -0
  36. package/preset/odai/runtime/human-care.d.mts.map +1 -0
  37. package/preset/odai/runtime/human-care.mjs +45 -42
  38. package/preset/odai/runtime/human-care.mjs.map +1 -0
  39. package/preset/odai/runtime/human-safety-continuity-store.d.mts +43 -0
  40. package/preset/odai/runtime/human-safety-continuity-store.d.mts.map +1 -0
  41. package/preset/odai/runtime/human-safety-continuity-store.mjs +193 -215
  42. package/preset/odai/runtime/human-safety-continuity-store.mjs.map +1 -0
  43. package/preset/odai/runtime/human-safety-continuity.d.mts +27 -0
  44. package/preset/odai/runtime/human-safety-continuity.d.mts.map +1 -0
  45. package/preset/odai/runtime/human-safety-continuity.mjs +205 -216
  46. package/preset/odai/runtime/human-safety-continuity.mjs.map +1 -0
  47. package/preset/odai/runtime/human-safety.d.mts +14 -0
  48. package/preset/odai/runtime/human-safety.d.mts.map +1 -0
  49. package/preset/odai/runtime/human-safety.mjs +50 -47
  50. package/preset/odai/runtime/human-safety.mjs.map +1 -0
  51. package/preset/odai/runtime/index.d.mts +8 -0
  52. package/preset/odai/runtime/index.d.mts.map +1 -0
  53. package/preset/odai/runtime/index.mjs +190 -2473
  54. package/preset/odai/runtime/index.mjs.map +1 -0
  55. package/preset/odai/runtime/lifecycle-runtime.d.mts +83 -0
  56. package/preset/odai/runtime/lifecycle-runtime.d.mts.map +1 -0
  57. package/preset/odai/runtime/lifecycle-runtime.mjs +1140 -0
  58. package/preset/odai/runtime/lifecycle-runtime.mjs.map +1 -0
  59. package/preset/odai/runtime/model-route.d.mts +22 -0
  60. package/preset/odai/runtime/model-route.d.mts.map +1 -0
  61. package/preset/odai/runtime/model-route.mjs +68 -60
  62. package/preset/odai/runtime/model-route.mjs.map +1 -0
  63. package/preset/odai/runtime/output-config.d.mts +54 -0
  64. package/preset/odai/runtime/output-config.d.mts.map +1 -0
  65. package/preset/odai/runtime/output-config.mjs +289 -315
  66. package/preset/odai/runtime/output-config.mjs.map +1 -0
  67. package/preset/odai/runtime/output-policy-state.d.mts +3 -0
  68. package/preset/odai/runtime/output-policy-state.d.mts.map +1 -0
  69. package/preset/odai/runtime/output-policy-state.mjs +41 -29
  70. package/preset/odai/runtime/output-policy-state.mjs.map +1 -0
  71. package/preset/odai/runtime/prompt-runtime.d.mts +55 -0
  72. package/preset/odai/runtime/prompt-runtime.d.mts.map +1 -0
  73. package/preset/odai/runtime/prompt-runtime.mjs +303 -0
  74. package/preset/odai/runtime/prompt-runtime.mjs.map +1 -0
  75. package/preset/odai/runtime/research-packet.d.mts +30 -0
  76. package/preset/odai/runtime/research-packet.d.mts.map +1 -0
  77. package/preset/odai/runtime/research-packet.mjs +116 -117
  78. package/preset/odai/runtime/research-packet.mjs.map +1 -0
  79. package/preset/odai/runtime/responsibility-gap.d.mts +26 -0
  80. package/preset/odai/runtime/responsibility-gap.d.mts.map +1 -0
  81. package/preset/odai/runtime/responsibility-gap.mjs +93 -85
  82. package/preset/odai/runtime/responsibility-gap.mjs.map +1 -0
  83. package/preset/odai/runtime/responsibility-scope.d.mts +69 -0
  84. package/preset/odai/runtime/responsibility-scope.d.mts.map +1 -0
  85. package/preset/odai/runtime/responsibility-scope.mjs +199 -171
  86. package/preset/odai/runtime/responsibility-scope.mjs.map +1 -0
  87. package/preset/odai/runtime/role-overlays.d.mts +5 -0
  88. package/preset/odai/runtime/role-overlays.d.mts.map +1 -0
  89. package/preset/odai/runtime/role-overlays.mjs +15 -14
  90. package/preset/odai/runtime/role-overlays.mjs.map +1 -0
  91. package/preset/odai/runtime/route-card.d.mts +42 -0
  92. package/preset/odai/runtime/route-card.d.mts.map +1 -0
  93. package/preset/odai/runtime/route-card.mjs +158 -146
  94. package/preset/odai/runtime/route-card.mjs.map +1 -0
  95. package/preset/odai/runtime/router.d.mts +80 -0
  96. package/preset/odai/runtime/router.d.mts.map +1 -0
  97. package/preset/odai/runtime/router.mjs +509 -559
  98. package/preset/odai/runtime/router.mjs.map +1 -0
  99. package/preset/odai/runtime/routing-config.d.mts +76 -0
  100. package/preset/odai/runtime/routing-config.d.mts.map +1 -0
  101. package/preset/odai/runtime/routing-config.mjs +373 -438
  102. package/preset/odai/runtime/routing-config.mjs.map +1 -0
  103. package/preset/odai/runtime/routing-context.d.mts +64 -0
  104. package/preset/odai/runtime/routing-context.d.mts.map +1 -0
  105. package/preset/odai/runtime/routing-context.mjs +398 -277
  106. package/preset/odai/runtime/routing-context.mjs.map +1 -0
  107. package/preset/odai/runtime/runtime-config.d.mts +24 -0
  108. package/preset/odai/runtime/runtime-config.d.mts.map +1 -0
  109. package/preset/odai/runtime/runtime-config.mjs +181 -0
  110. package/preset/odai/runtime/runtime-config.mjs.map +1 -0
  111. package/preset/odai/runtime/runtime-support.d.mts +91 -0
  112. package/preset/odai/runtime/runtime-support.d.mts.map +1 -0
  113. package/preset/odai/runtime/runtime-support.mjs +371 -0
  114. package/preset/odai/runtime/runtime-support.mjs.map +1 -0
  115. package/preset/odai/runtime/runtime-types.d.mts +237 -0
  116. package/preset/odai/runtime/runtime-types.d.mts.map +1 -0
  117. package/preset/odai/runtime/runtime-types.mjs +4 -0
  118. package/preset/odai/runtime/runtime-types.mjs.map +1 -0
  119. package/preset/odai/runtime/semantic-memory-store.d.mts +110 -0
  120. package/preset/odai/runtime/semantic-memory-store.d.mts.map +1 -0
  121. package/preset/odai/runtime/semantic-memory-store.mjs +271 -385
  122. package/preset/odai/runtime/semantic-memory-store.mjs.map +1 -0
  123. package/preset/odai/runtime/semantic-memory.d.mts +88 -0
  124. package/preset/odai/runtime/semantic-memory.d.mts.map +1 -0
  125. package/preset/odai/runtime/semantic-memory.mjs +790 -750
  126. package/preset/odai/runtime/semantic-memory.mjs.map +1 -0
  127. package/preset/odai/runtime/session-compat.d.mts +31 -0
  128. package/preset/odai/runtime/session-compat.d.mts.map +1 -0
  129. package/preset/odai/runtime/session-compat.mjs +301 -328
  130. package/preset/odai/runtime/session-compat.mjs.map +1 -0
  131. package/preset/odai/runtime/session-evidence.d.mts +25 -0
  132. package/preset/odai/runtime/session-evidence.d.mts.map +1 -0
  133. package/preset/odai/runtime/session-evidence.mjs +309 -287
  134. package/preset/odai/runtime/session-evidence.mjs.map +1 -0
  135. package/preset/odai/runtime/skill-bundle.d.mts +53 -0
  136. package/preset/odai/runtime/skill-bundle.d.mts.map +1 -0
  137. package/preset/odai/runtime/skill-bundle.mjs +286 -276
  138. package/preset/odai/runtime/skill-bundle.mjs.map +1 -0
  139. package/preset/odai/runtime/skill-evolution.d.mts +112 -0
  140. package/preset/odai/runtime/skill-evolution.d.mts.map +1 -0
  141. package/preset/odai/runtime/skill-evolution.mjs +1287 -1261
  142. package/preset/odai/runtime/skill-evolution.mjs.map +1 -0
  143. package/preset/odai/runtime/skill-selection-state.d.mts +5 -0
  144. package/preset/odai/runtime/skill-selection-state.d.mts.map +1 -0
  145. package/preset/odai/runtime/skill-selection-state.mjs +59 -43
  146. package/preset/odai/runtime/skill-selection-state.mjs.map +1 -0
  147. package/preset/odai/runtime/skill-selector.d.mts +28 -0
  148. package/preset/odai/runtime/skill-selector.d.mts.map +1 -0
  149. package/preset/odai/runtime/skill-selector.mjs +100 -109
  150. package/preset/odai/runtime/skill-selector.mjs.map +1 -0
  151. package/preset/odai/runtime/skill-source-config.d.mts +32 -0
  152. package/preset/odai/runtime/skill-source-config.d.mts.map +1 -0
  153. package/preset/odai/runtime/skill-source-config.mjs +200 -210
  154. package/preset/odai/runtime/skill-source-config.mjs.map +1 -0
  155. package/preset/odai/runtime/store-lock.d.mts +2 -0
  156. package/preset/odai/runtime/store-lock.d.mts.map +1 -0
  157. package/preset/odai/runtime/store-lock.mjs +92 -87
  158. package/preset/odai/runtime/store-lock.mjs.map +1 -0
  159. package/preset/odai/runtime/tool-runtime.d.mts +43 -0
  160. package/preset/odai/runtime/tool-runtime.d.mts.map +1 -0
  161. package/preset/odai/runtime/tool-runtime.mjs +221 -0
  162. package/preset/odai/runtime/tool-runtime.mjs.map +1 -0
  163. package/bin/odai-dsh-agent.mjs +0 -108
  164. package/scripts/verify-agent-load.mjs +0 -260
  165. package/src/dsh-version.mjs +0 -24
  166. package/src/installer.mjs +0 -385
@@ -1,321 +1,442 @@
1
1
  import { createHash } from "node:crypto";
2
-
2
+ import { classifyResponsibilityInterruptionText } from "./router.mjs";
3
+ import { isUnknownRecord } from "./runtime-types.mjs";
3
4
  const DEFAULT_MAX_CHARS = 12_000;
4
5
  const DEFAULT_MAX_EVENTS = 80;
6
+ const DEFAULT_MAX_ENTRY_CHARS = 4_000;
5
7
  const REQUIREMENT_PATTERNS = [/requirement/iu, /acceptance/iu, /需求/u, /要求/u, /目标/u, /验收/u];
6
- const ACCEPTANCE_PATTERNS = [/\bA\d+\b/u, /accept(?:ance)?/iu, /验收/u, /通过条件/u, /完成条件/u];
7
8
  const DIFF_OUTPUT_PATTERNS = [/diff --git/iu, /^---\s+a\/[^\n]+\n\+\+\+\s+b\//imu];
8
9
  const DIFF_COMMAND_PATTERNS = [/^\s*git(?:\.exe)?\s+diff(?:\s|$)/iu];
9
10
  const TEST_COMMAND_PATTERNS = [
10
- /^\s*(?:&\s*)?(?:"[^"\r\n]*[\\/]node(?:\.exe)?"|[A-Za-z]:\\[^\s"\r\n]*[\\/]node(?:\.exe)?|\/[^\s"\r\n]*\/node|node(?:\.exe)?)\s+--test(?:\s|$)/iu,
11
- /^\s*(?:npm|pnpm|yarn)(?:\.cmd)?(?:\s+--(?:prefix|dir)\s+(?:"[^"]+"|\S+))?\s+(?:run\s+)?test(?:\s|$)/iu,
12
- /^\s*(?:pytest|vitest|jest|mocha)(?:\.cmd)?(?:\s|$)/iu,
13
- /^\s*(?:go|cargo|dotnet)\s+test(?:\s|$)/iu,
11
+ /^\s*(?:&\s*)?(?:"[^"\r\n]*[\\/]node(?:\.exe)?"|[A-Za-z]:\\[^\s"\r\n]*[\\/]node(?:\.exe)?|\/[^\s"\r\n]*\/node|node(?:\.exe)?)\s+--test(?:\s|$)/iu,
12
+ /^\s*(?:npm|pnpm|yarn)(?:\.cmd)?(?:\s+--(?:prefix|dir)\s+(?:"[^"]+"|\S+))?\s+(?:run\s+)?test(?:[:.][\w.-]+)?(?:\s|$)/iu,
13
+ /^\s*(?:(?:npx|pnpm\s+exec|yarn\s+exec)(?:\.cmd)?|npm(?:\.cmd)?\s+exec(?:\s+--)?)\s+(?:--yes\s+)?(?:pytest|vitest|jest|mocha)(?:\.cmd)?(?:\s|$)/iu,
14
+ /^\s*(?:pytest|vitest|jest|mocha)(?:\.cmd)?(?:\s|$)/iu,
15
+ /^\s*(?:go|cargo|dotnet)\s+test(?:\s|$)/iu,
16
+ /^\s*(?:(?:\.\/)?(?:[\w.-]+\/)*mvnw(?:\.cmd)?|mvn(?:\.cmd)?)\s+(?:[^\r\n]*\s)?(?:test|verify)(?:\s|$)/iu,
17
+ /^\s*(?:\.\/)?gradlew(?:\.bat)?\s+(?:[^\r\n]*\s)?test(?:\s|$)/iu,
14
18
  ];
15
19
  const TEST_SUCCESS_PATTERNS = [
16
- /exit code:\s*0/iu,
17
- /(?:^|\n)[^\n]*(?:pass|passed)\s*[:=]?\s*[1-9]\d*/iu,
18
- /(?:^|\n)[^\n]*[1-9]\d*\s+passed\b/iu,
19
- /(?:^|\n)[^\n]*fail(?:ed)?\s*[:=]?\s*0\b/iu,
20
- /\btests?\s+\d+[^\n]*(?:pass|passed)\s+[1-9]\d*[^\n]*fail(?:ed)?\s+0\b/iu,
20
+ /exit code:\s*0/iu,
21
+ /(?:^|\n)[^\n]*(?:pass|passed)\s*[:=]?\s*[1-9]\d*/iu,
22
+ /(?:^|\n)[^\n]*[1-9]\d*\s+passed\b/iu,
23
+ /(?:^|\n)[^\n]*fail(?:ed)?\s*[:=]?\s*0\b/iu,
24
+ /\btests?\s+\d+[^\n]*(?:pass|passed)\s+[1-9]\d*[^\n]*fail(?:ed)?\s+0\b/iu,
21
25
  ];
22
26
  const TEST_FAILURE_PATTERNS = [
23
- /exit code:\s*[1-9]\d*/iu,
24
- /(?:^|\n)[^\n]*fail(?:ed)?\s*[:=]?\s*[1-9]\d*/iu,
25
- /\b(?:tests?|test suite) failed\b/iu,
27
+ /exit code:\s*[1-9]\d*/iu,
28
+ /(?:^|\n)[^\n]*fail(?:ed)?\s*[:=]?\s*[1-9]\d*/iu,
29
+ /\b(?:tests?|test suite) failed\b/iu,
26
30
  ];
27
31
  const WRITE_TOOL_NAMES = new Set(["edit", "write", "apply_patch"]);
28
32
  const SHELL_TOOL_NAMES = new Set(["bash", "pwsh", "shell", "powershell"]);
29
33
  const SHELL_CONTROL_PATTERN = /[;&|<>]/u;
30
34
  const READ_ONLY_SHELL_COMMAND_PATTERNS = [
31
- /^\s*git(?:\.exe)?\s+(?:status|log|show|rev-parse|ls-files|branch)(?:\s|$)/iu,
32
- /^\s*(?:Get-[A-Za-z]+|Select-String|Test-Path)(?:\s|$)/iu,
33
- /^\s*(?:rg|grep|find|ls|pwd|cat|head|tail|wc|where|which)(?:\.exe)?(?:\s|$)/iu,
34
- /^\s*node(?:\.exe)?\s+--check(?:\s|$)/iu,
35
+ /^\s*git(?:\.exe)?\s+(?:status|log|show|rev-parse|ls-files|branch)(?:\s|$)/iu,
36
+ /^\s*(?:Get-[A-Za-z]+|Select-String|Test-Path)(?:\s|$)/iu,
37
+ /^\s*(?:rg|grep|find|ls|pwd|cat|head|tail|wc|where|which|lsof|ps|netstat|ss)(?:\.exe)?(?:\s|$)/iu,
38
+ /^\s*(?:npx(?:\.cmd)?|pnpm(?:\.cmd)?\s+exec|yarn(?:\.cmd)?\s+exec|npm(?:\.cmd)?\s+exec(?:\s+--)?)?\s*prettier(?:\.cmd)?\s+--check(?:\s|$)/iu,
39
+ /^\s*node(?:\.exe)?\s+--check(?:\s|$)/iu,
35
40
  ];
36
41
  const WRITE_COMMAND_PATTERNS = [
37
- /(?:^|\s)(?:git\s+apply|apply_patch|sed\s+-i|perl\s+-pi)(?:\s|$)/iu,
38
- /(?:^|\s)(?:Set-Content|Add-Content|Out-File|Remove-Item|Move-Item|Copy-Item|New-Item)(?:\s|$)/iu,
39
- /(?:^|\s)(?:rm|mv|cp|mkdir|touch)(?:\s|$)/u,
42
+ /(?:^|\s)(?:git\s+apply|apply_patch|sed\s+-i|perl\s+-pi)(?:\s|$)/iu,
43
+ /(?:^|\s)(?:Set-Content|Add-Content|Out-File|Remove-Item|Move-Item|Copy-Item|New-Item)(?:\s|$)/iu,
44
+ /(?:^|\s)(?:rm|mv|cp|mkdir|touch)(?:\s|$)/u,
40
45
  ];
41
-
42
46
  function textBlocks(value, depth = 0) {
43
- if (depth > 5 || value === null || value === undefined) return [];
44
- if (typeof value === "string") return [value];
45
- if (Array.isArray(value)) return value.flatMap((item) => textBlocks(item, depth + 1));
46
- if (typeof value !== "object") return [];
47
- if (value.type === "text" && typeof value.text === "string") return [value.text];
48
- const keys = ["content", "message", "output", "result", "data", "text", "value", "command", "args", "input"];
49
- return keys.flatMap((key) => key in value ? textBlocks(value[key], depth + 1) : []);
47
+ if (depth > 5 || value === null || value === undefined)
48
+ return [];
49
+ if (typeof value === "string")
50
+ return [value];
51
+ if (Array.isArray(value))
52
+ return value.flatMap((item) => textBlocks(item, depth + 1));
53
+ if (!isUnknownRecord(value))
54
+ return [];
55
+ if (value.type === "text" && typeof value.text === "string")
56
+ return [value.text];
57
+ const keys = ["content", "message", "output", "result", "data", "text", "value", "command", "args", "input"];
58
+ return keys.flatMap((key) => key in value ? textBlocks(value[key], depth + 1) : []);
50
59
  }
51
-
52
60
  function matchesAny(text, patterns) {
53
- return patterns.some((pattern) => pattern.test(text));
61
+ return patterns.some((pattern) => pattern.test(text));
62
+ }
63
+ function directUserMessage(event) {
64
+ const message = isUnknownRecord(event.data?.message) ? event.data.message : event.data;
65
+ const source = isUnknownRecord(message?.source) ? message.source : undefined;
66
+ if (typeof message?.role === "string" && message.role !== "user")
67
+ return undefined;
68
+ return source?.kind === "user" ? message : undefined;
69
+ }
70
+ function isDirectUserEvent(event) {
71
+ return directUserMessage(event) !== undefined;
54
72
  }
55
-
56
73
  function toolResultBlocks(value, depth = 0) {
57
- if (depth > 5 || value === null || value === undefined) return [];
58
- if (Array.isArray(value)) return value.flatMap((item) => toolResultBlocks(item, depth + 1));
59
- if (typeof value !== "object") return [];
60
- const own = value.type === "tool-result" ? [value] : [];
61
- return own.concat(Object.values(value).flatMap((item) => toolResultBlocks(item, depth + 1)));
74
+ if (depth > 5 || value === null || value === undefined)
75
+ return [];
76
+ if (Array.isArray(value))
77
+ return value.flatMap((item) => toolResultBlocks(item, depth + 1));
78
+ if (!isUnknownRecord(value))
79
+ return [];
80
+ const own = value.type === "tool-result" ? [value] : [];
81
+ return own.concat(Object.values(value).flatMap((item) => toolResultBlocks(item, depth + 1)));
62
82
  }
63
-
64
83
  function nativeToolResult(data) {
65
- const blocks = toolResultBlocks(data);
66
- if (blocks.length !== 1) return undefined;
67
- const block = blocks[0];
68
- const source = data?.message?.source;
69
- if (source?.kind !== "tool"
70
- || typeof source.callId !== "string"
71
- || !source.callId
72
- || block.toolCallId !== source.callId
73
- || typeof block.isError !== "boolean"
74
- || !Array.isArray(block.content)) {
75
- return undefined;
76
- }
77
- return Object.freeze({
78
- callId: source.callId,
79
- successful: block.isError === false && !data?.error,
80
- });
84
+ const blocks = toolResultBlocks(data);
85
+ if (blocks.length !== 1)
86
+ return undefined;
87
+ const block = blocks[0];
88
+ const message = isUnknownRecord(data.message) ? data.message : undefined;
89
+ const source = isUnknownRecord(message?.source) ? message.source : undefined;
90
+ if (source?.kind !== "tool"
91
+ || typeof source.callId !== "string"
92
+ || !source.callId
93
+ || block?.toolCallId !== source.callId
94
+ || typeof block.isError !== "boolean"
95
+ || !Array.isArray(block.content))
96
+ return undefined;
97
+ return Object.freeze({
98
+ callId: source.callId,
99
+ successful: block.isError === false && !data.error,
100
+ });
101
+ }
102
+ function commandFromArguments(value) {
103
+ let arguments_ = value;
104
+ if (typeof arguments_ === "string") {
105
+ const trimmed = arguments_.trim();
106
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
107
+ return Object.freeze({ command: arguments_, parsed: true });
108
+ }
109
+ try {
110
+ arguments_ = JSON.parse(trimmed);
111
+ }
112
+ catch {
113
+ return Object.freeze({ command: "", parsed: false });
114
+ }
115
+ }
116
+ if (!isUnknownRecord(arguments_))
117
+ return Object.freeze({ command: "", parsed: false });
118
+ for (const field of ["command", "cmd", "script"]) {
119
+ if (typeof arguments_[field] === "string")
120
+ return Object.freeze({ command: arguments_[field], parsed: true });
121
+ }
122
+ return Object.freeze({ command: "", parsed: true });
81
123
  }
82
-
83
- function commandFromCall(call) {
84
- const args = call?.arguments;
85
- if (typeof args === "string") return args;
86
- if (!args || typeof args !== "object" || Array.isArray(args)) return "";
87
- for (const field of ["command", "cmd", "script"]) {
88
- if (typeof args[field] === "string") return args[field];
89
- }
90
- return "";
124
+ function normalizedToolName(name) {
125
+ const segments = name.split(/[.:/]/u).filter(Boolean);
126
+ return segments.at(-1) ?? name;
91
127
  }
92
-
93
128
  function resultReferencesCall(event, call) {
94
- return Number.isSafeInteger(call?.seq)
95
- && Array.isArray(event?.sourceEventSeqs)
96
- && event.sourceEventSeqs.length === 1
97
- && event.sourceEventSeqs[0] === call.seq;
129
+ return Number.isSafeInteger(call?.seq)
130
+ && Array.isArray(event?.sourceEventSeqs)
131
+ && event.sourceEventSeqs.length === 1
132
+ && event.sourceEventSeqs[0] === call.seq;
98
133
  }
99
-
100
- function nativeToolCalls(events, startIndex) {
101
- const calls = new Map();
102
- const duplicates = new Set();
103
- for (let index = startIndex; index < events.length; index += 1) {
104
- const event = events[index];
105
- if (event?.type !== "tool/call") continue;
106
- const callId = event.data?.callId;
107
- const name = event.data?.name;
108
- if (typeof callId !== "string" || !callId || typeof name !== "string" || !name || !Number.isSafeInteger(event.seq)) continue;
109
- if (calls.has(callId)) {
110
- calls.delete(callId);
111
- duplicates.add(callId);
112
- continue;
134
+ function nativeToolCalls(events) {
135
+ const calls = new Map();
136
+ const duplicates = new Set();
137
+ for (let index = 0; index < events.length; index += 1) {
138
+ const event = events[index];
139
+ if (event?.type !== "tool/call")
140
+ continue;
141
+ const callId = event.data?.callId;
142
+ const name = event.data?.name;
143
+ if (typeof callId !== "string" || !callId || typeof name !== "string" || !name || !Number.isSafeInteger(event.seq))
144
+ continue;
145
+ if (calls.has(callId)) {
146
+ calls.delete(callId);
147
+ duplicates.add(callId);
148
+ continue;
149
+ }
150
+ if (duplicates.has(callId))
151
+ continue;
152
+ calls.set(callId, Object.freeze({ index, seq: event.seq, name, arguments: event.data?.arguments }));
113
153
  }
114
- if (duplicates.has(callId)) continue;
115
- calls.set(callId, Object.freeze({
116
- index,
117
- seq: event.seq,
118
- name,
119
- arguments: event.data?.arguments,
120
- }));
121
- }
122
- return calls;
154
+ return calls;
123
155
  }
124
-
125
- function eventEvidence(event, index, calls) {
126
- if (event?.type === "user/message") {
127
- const text = textBlocks(event.data).join("\n").trim();
128
- return text ? { index, source: "user", kinds: ["requirement"], label: "earlier user requirement", text } : undefined;
129
- }
130
- if (["assistant/message", "agent/message", "message/assistant"].includes(event?.type)) {
131
- const text = textBlocks(event.data).join("\n").trim();
132
- if (!text) return undefined;
133
- const kinds = ["assistant-claim"];
134
- if (matchesAny(text, REQUIREMENT_PATTERNS)) kinds.push("requirement");
135
- if (matchesAny(text, ACCEPTANCE_PATTERNS)) kinds.push("acceptance");
136
- return { index, source: "assistant", kinds, label: "controller claim", text };
137
- }
138
- if (event?.type === "odai/route-card-frozen" && event.data?.card) {
139
- return {
140
- index,
141
- source: "route-card",
142
- kinds: ["acceptance"],
143
- label: `frozen route card ${event.data.card.id ?? "(unknown)"}`,
144
- text: JSON.stringify(event.data.card),
145
- };
146
- }
147
- if (event?.type === "tool/result") {
148
- const result = nativeToolResult(event.data);
149
- const call = result ? calls.get(result.callId) : undefined;
150
- if (!call || call.index >= index || !resultReferencesCall(event, call)) return undefined;
151
- const callId = result.callId;
152
- const successful = result.successful;
153
- const command = commandFromCall(call);
154
- const simpleCommand = !SHELL_CONTROL_PATTERN.test(command);
155
- const diffCommand = simpleCommand && matchesAny(command, DIFF_COMMAND_PATTERNS);
156
- const testCommand = simpleCommand && matchesAny(command, TEST_COMMAND_PATTERNS);
157
- const explicitWrite = WRITE_TOOL_NAMES.has(call.name) || matchesAny(command, WRITE_COMMAND_PATTERNS);
158
- const unknownShellMutation = SHELL_TOOL_NAMES.has(call.name)
159
- && !diffCommand
160
- && !testCommand
161
- && (SHELL_CONTROL_PATTERN.test(command) || !matchesAny(command, READ_ONLY_SHELL_COMMAND_PATTERNS));
162
- const writeCommand = explicitWrite || unknownShellMutation;
163
- if (!successful && !testCommand && !writeCommand) return undefined;
164
- const output = textBlocks(event.data).join("\n").trim();
165
- const kinds = successful ? ["tool"] : [];
166
- if (successful && diffCommand && matchesAny(output, DIFF_OUTPUT_PATTERNS)) kinds.push("diff");
167
- if (writeCommand) kinds.push("write");
168
- if (testCommand) {
169
- const testPassed = successful
170
- && matchesAny(output, TEST_SUCCESS_PATTERNS)
171
- && !matchesAny(output, TEST_FAILURE_PATTERNS);
172
- kinds.push(testPassed ? "test" : "test-failed");
156
+ function eventEvidence(event, index, calls, directUserMessageIds, diagnostics) {
157
+ if (event?.type === "user/message") {
158
+ if (!isDirectUserEvent(event))
159
+ return undefined;
160
+ const text = textBlocks(event.data).join("\n").trim();
161
+ if (!text)
162
+ return undefined;
163
+ if (classifyResponsibilityInterruptionText(text) === "continue") {
164
+ return { index, source: "user", kinds: ["continuation"], label: "direct-user continuation", text };
165
+ }
166
+ return { index, source: "user", kinds: ["requirement", "acceptance"], label: "user-owned requirement and acceptance", text };
173
167
  }
174
- const identity = `tool-call:${callId}`;
175
- const text = [command ? `command: ${command}` : "", output || "(no textual result)"].filter(Boolean).join("\n\n");
176
- return {
177
- index,
178
- source: "tool",
179
- kinds,
180
- label: `tool ${call.name} (${identity})`,
181
- identity,
182
- text,
183
- };
184
- }
185
- if (event?.type === "odai/tool-observed" && event.data?.isError === false) {
186
- const identity = typeof event.data.callId === "string" ? `tool-call:${event.data.callId}` : undefined;
187
- const tool = event.data.tool ?? "unknown";
188
- return {
189
- index,
190
- source: "tool",
191
- kinds: ["tool", ...(WRITE_TOOL_NAMES.has(tool) ? ["write"] : [])],
192
- label: `observed tool ${tool}${identity ? ` (${identity})` : ""}`,
193
- ...(identity ? { identity } : {}),
194
- text: JSON.stringify(event.data),
195
- };
196
- }
197
- return undefined;
168
+ if (["assistant/message", "agent/message", "message/assistant"].includes(event?.type)) {
169
+ const text = textBlocks(event.data).join("\n").trim();
170
+ if (!text)
171
+ return undefined;
172
+ const kinds = ["assistant-claim"];
173
+ if (matchesAny(text, REQUIREMENT_PATTERNS))
174
+ kinds.push("requirement");
175
+ return { index, source: "assistant", kinds, label: "controller claim", text };
176
+ }
177
+ if (event?.type === "odai/route-card-frozen" && isUnknownRecord(event.data?.card)) {
178
+ const authorization = isUnknownRecord(event.data.card.authorization) ? event.data.card.authorization : undefined;
179
+ const userMessageId = typeof authorization?.userMessageId === "string" ? authorization.userMessageId : undefined;
180
+ if (authorization?.status !== "authorized"
181
+ || !userMessageId
182
+ || !directUserMessageIds.has(userMessageId)
183
+ || !Array.isArray(event.data.card.accept)
184
+ || event.data.card.accept.length === 0)
185
+ return undefined;
186
+ return {
187
+ index, source: "route-card", kinds: ["acceptance"],
188
+ label: `authorized frozen route card ${typeof event.data.card.id === "string" ? event.data.card.id : "(unknown)"}`,
189
+ text: JSON.stringify(event.data.card),
190
+ };
191
+ }
192
+ if (event?.type === "tool/result") {
193
+ const result = nativeToolResult(event.data);
194
+ if (!result) {
195
+ diagnostics.malformedToolResultCount += 1;
196
+ return undefined;
197
+ }
198
+ const call = calls.get(result.callId);
199
+ if (!call || call.index >= index || !resultReferencesCall(event, call)) {
200
+ diagnostics.unlinkedToolResultCount += 1;
201
+ return undefined;
202
+ }
203
+ diagnostics.linkedToolResultCount += 1;
204
+ const decoded = commandFromArguments(call.arguments);
205
+ if (!decoded.parsed)
206
+ diagnostics.unparsedToolArgumentsCount += 1;
207
+ const command = decoded.command;
208
+ const toolName = normalizedToolName(call.name);
209
+ const simpleCommand = command !== "" && !SHELL_CONTROL_PATTERN.test(command);
210
+ const diffCommand = simpleCommand && matchesAny(command, DIFF_COMMAND_PATTERNS);
211
+ const testCommand = simpleCommand && matchesAny(command, TEST_COMMAND_PATTERNS);
212
+ const explicitWrite = WRITE_TOOL_NAMES.has(toolName) || matchesAny(command, WRITE_COMMAND_PATTERNS);
213
+ const unknownShellMutation = SHELL_TOOL_NAMES.has(toolName)
214
+ && !diffCommand && !testCommand
215
+ && (SHELL_CONTROL_PATTERN.test(command) || !matchesAny(command, READ_ONLY_SHELL_COMMAND_PATTERNS));
216
+ if (SHELL_TOOL_NAMES.has(toolName) && !diffCommand && !testCommand && !explicitWrite
217
+ && !matchesAny(command, READ_ONLY_SHELL_COMMAND_PATTERNS))
218
+ diagnostics.unclassifiedShellResultCount += 1;
219
+ const writeCommand = explicitWrite || unknownShellMutation;
220
+ if (!result.successful && !testCommand && !writeCommand)
221
+ return undefined;
222
+ const output = textBlocks(event.data).join("\n").trim();
223
+ const kinds = result.successful ? ["tool"] : [];
224
+ if (result.successful && diffCommand) {
225
+ if (matchesAny(output, DIFF_OUTPUT_PATTERNS))
226
+ kinds.push("diff");
227
+ else
228
+ diagnostics.diffWithoutPatchCount += 1;
229
+ }
230
+ if (writeCommand)
231
+ kinds.push("write");
232
+ if (testCommand) {
233
+ const testPassed = result.successful && matchesAny(output, TEST_SUCCESS_PATTERNS) && !matchesAny(output, TEST_FAILURE_PATTERNS);
234
+ if (result.successful && !matchesAny(output, TEST_SUCCESS_PATTERNS) && !matchesAny(output, TEST_FAILURE_PATTERNS)) {
235
+ diagnostics.testWithoutVerdictCount += 1;
236
+ }
237
+ kinds.push(testPassed ? "test" : "test-failed");
238
+ }
239
+ const identity = `tool-call:${result.callId}`;
240
+ const text = [command ? `command: ${command}` : "", output || "(no textual result)"].filter(Boolean).join("\n\n");
241
+ return { index, source: "tool", kinds, label: `tool ${call.name} (${identity})`, identity, text };
242
+ }
243
+ if (event?.type === "odai/tool-observed" && event.data?.isError === false) {
244
+ const identity = typeof event.data.callId === "string" ? `tool-call:${event.data.callId}` : undefined;
245
+ const tool = typeof event.data.tool === "string" ? event.data.tool : "unknown";
246
+ const normalized = normalizedToolName(tool);
247
+ return {
248
+ index, source: "tool", kinds: ["tool", ...(WRITE_TOOL_NAMES.has(normalized) ? ["write"] : [])],
249
+ label: `observed tool ${tool}${identity ? ` (${identity})` : ""}`,
250
+ ...(identity ? { identity } : {}), text: JSON.stringify(event.data),
251
+ };
252
+ }
253
+ return undefined;
198
254
  }
199
-
200
255
  function truncateText(text, limit) {
201
- if (text.length <= limit) return { text, truncated: false };
202
- return { text: `${text.slice(0, Math.max(0, limit - 24))}\n...[packet truncated]`, truncated: true };
256
+ if (text.length <= limit)
257
+ return { text, truncated: false };
258
+ return { text: `${text.slice(0, Math.max(0, limit - 24))}\n...[packet truncated]`, truncated: true };
203
259
  }
204
-
205
260
  function digestPacket(value) {
206
- return createHash("sha256").update(JSON.stringify(value)).digest("hex");
261
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
207
262
  }
208
-
209
263
  function coverageFor(currentTask, entries) {
210
- const matching = (kind) => entries.filter((entry) => entry.kinds.includes(kind));
211
- const latestIndex = (kind) => matching(kind).reduce((latest, entry) => Math.max(latest, entry.index), -1);
212
- const acceptanceCount = matching("acceptance").length;
213
- const diffEntries = matching("diff");
214
- const testEntries = matching("test");
215
- const failedTestEntries = matching("test-failed");
216
- const latestWriteIndex = latestIndex("write");
217
- const latestDiffIndex = latestIndex("diff");
218
- const latestTestIndex = latestIndex("test");
219
- const latestFailedTestIndex = latestIndex("test-failed");
220
- const currentEvidence = acceptanceCount > 0
221
- && latestDiffIndex >= 0
222
- && latestTestIndex >= 0
223
- && latestDiffIndex > latestWriteIndex
224
- && latestTestIndex > latestWriteIndex
225
- && latestTestIndex > latestFailedTestIndex
226
- && diffEntries.some((diff) => testEntries.some((testResult) => diff.identity !== testResult.identity));
227
- return Object.freeze({
228
- requirements: Boolean(currentTask) || matching("requirement").length > 0,
229
- acceptanceCount,
230
- diffCount: diffEntries.length,
231
- testCount: testEntries.length,
232
- failedTestCount: failedTestEntries.length,
233
- writeCount: matching("write").length,
234
- toolEvidenceCount: matching("tool").length,
235
- latestWriteIndex,
236
- latestDiffIndex,
237
- latestTestIndex,
238
- latestFailedTestIndex,
239
- currentEvidence,
240
- });
264
+ const matching = (kind) => entries.filter((entry) => entry.kinds.includes(kind));
265
+ const latestIndex = (kind) => matching(kind).reduce((latest, entry) => Math.max(latest, entry.index), -1);
266
+ const acceptanceCount = matching("acceptance").length;
267
+ const diffEntries = matching("diff");
268
+ const testEntries = matching("test");
269
+ const failedTestEntries = matching("test-failed");
270
+ const latestWriteIndex = latestIndex("write");
271
+ const latestDiffIndex = latestIndex("diff");
272
+ const latestTestIndex = latestIndex("test");
273
+ const latestFailedTestIndex = latestIndex("test-failed");
274
+ const currentEvidence = acceptanceCount > 0
275
+ && latestDiffIndex >= 0 && latestTestIndex >= 0
276
+ && latestDiffIndex > latestWriteIndex && latestTestIndex > latestDiffIndex
277
+ && latestTestIndex > latestFailedTestIndex
278
+ && diffEntries.some((diff) => testEntries.some((testResult) => diff.identity !== testResult.identity));
279
+ return Object.freeze({
280
+ requirements: Boolean(currentTask) || matching("requirement").length > 0,
281
+ acceptanceCount, diffCount: diffEntries.length, testCount: testEntries.length,
282
+ failedTestCount: failedTestEntries.length, writeCount: matching("write").length,
283
+ toolEvidenceCount: matching("tool").length, latestWriteIndex, latestDiffIndex,
284
+ latestTestIndex, latestFailedTestIndex, currentEvidence,
285
+ });
241
286
  }
242
-
243
287
  export function buildRoleContextPacket(agent, role, taskText, options = {}) {
244
- const maxChars = Number.isSafeInteger(options.maxChars) && options.maxChars > 0
245
- ? options.maxChars
246
- : DEFAULT_MAX_CHARS;
247
- const maxEvents = Number.isSafeInteger(options.maxEvents) && options.maxEvents > 0
248
- ? options.maxEvents
249
- : DEFAULT_MAX_EVENTS;
250
- const taskBudget = Math.max(128, Math.floor(maxChars / 2));
251
- const task = truncateText(String(taskText ?? "").trim(), taskBudget);
252
- const events = Array.isArray(agent?.session?.events) ? agent.session.events : [];
253
- const startIndex = Math.max(0, events.length - maxEvents);
254
- const calls = nativeToolCalls(events, startIndex);
255
- const selected = [];
256
- let evidenceChars = 0;
257
- let truncated = task.truncated || events.length > maxEvents;
258
-
259
- for (let index = events.length - 1; index >= startIndex; index -= 1) {
260
- const entry = eventEvidence(events[index], index, calls);
261
- if (!entry) continue;
262
- const remaining = maxChars - task.text.length - evidenceChars;
263
- if (remaining <= 0) { truncated = true; break; }
264
- const bounded = truncateText(entry.text, remaining);
265
- selected.push(Object.freeze({ ...entry, kinds: Object.freeze([...new Set(entry.kinds)]), text: bounded.text }));
266
- evidenceChars += bounded.text.length;
267
- if (bounded.truncated) { truncated = true; break; }
268
- }
269
- selected.reverse();
270
-
271
- const entries = Object.freeze(selected);
272
- const coverage = coverageFor(task.text, entries);
273
- const packetBody = Object.freeze({
274
- schemaVersion: 1,
275
- role,
276
- currentTask: task.text,
277
- entries,
278
- coverage,
279
- truncated,
280
- });
281
- const digest = digestPacket(packetBody);
282
- const reviewerSufficient = !truncated
283
- && coverage.requirements
284
- && coverage.acceptanceCount > 0
285
- && coverage.diffCount > 0
286
- && coverage.testCount > 0
287
- && coverage.toolEvidenceCount > 0
288
- && coverage.currentEvidence;
289
- return Object.freeze({
290
- ...packetBody,
291
- digest,
292
- evidenceCount: entries.length,
293
- toolEvidenceCount: coverage.toolEvidenceCount,
294
- sufficient: role === "reviewer" ? reviewerSufficient : Boolean(task.text),
295
- });
288
+ const maxChars = Number.isSafeInteger(options.maxChars) && (options.maxChars ?? 0) > 0 ? options.maxChars : DEFAULT_MAX_CHARS;
289
+ const maxEvents = Number.isSafeInteger(options.maxEvents) && (options.maxEvents ?? 0) > 0 ? options.maxEvents : DEFAULT_MAX_EVENTS;
290
+ const taskBudget = Math.max(32, Math.floor(maxChars / 3));
291
+ const task = truncateText(String(taskText ?? "").trim(), taskBudget);
292
+ const events = Array.isArray(agent?.session?.events) ? agent.session.events : [];
293
+ const calls = nativeToolCalls(events);
294
+ const directUserMessageIds = new Set(events.flatMap((event) => {
295
+ if (event?.type !== "user/message")
296
+ return [];
297
+ const message = directUserMessage(event);
298
+ return message && typeof message.id === "string" ? [message.id] : [];
299
+ }));
300
+ const mutableDiagnostics = {
301
+ rawEventCount: events.length,
302
+ evidenceEventCount: 0,
303
+ selectedEvidenceCount: 0,
304
+ omittedEvidenceCount: 0,
305
+ nativeToolCallCount: calls.size,
306
+ linkedToolResultCount: 0,
307
+ malformedToolResultCount: 0,
308
+ unlinkedToolResultCount: 0,
309
+ unparsedToolArgumentsCount: 0,
310
+ unclassifiedShellResultCount: 0,
311
+ diffWithoutPatchCount: 0,
312
+ testWithoutVerdictCount: 0,
313
+ };
314
+ const allEvidence = [];
315
+ const preTruncatedIndices = new Set();
316
+ for (let index = 0; index < events.length; index += 1) {
317
+ const event = events[index];
318
+ if (!event)
319
+ continue;
320
+ const entry = eventEvidence(event, index, calls, directUserMessageIds, mutableDiagnostics);
321
+ if (!entry)
322
+ continue;
323
+ const bounded = truncateText(entry.text, DEFAULT_MAX_ENTRY_CHARS);
324
+ if (bounded.truncated)
325
+ preTruncatedIndices.add(entry.index);
326
+ allEvidence.push(Object.freeze({ ...entry, kinds: Object.freeze([...new Set(entry.kinds)]), text: bounded.text }));
327
+ }
328
+ mutableDiagnostics.evidenceEventCount = allEvidence.length;
329
+ const recent = allEvidence.slice(-maxEvents);
330
+ const anchors = ["requirement", "acceptance"].flatMap((kind) => {
331
+ const entry = allEvidence.findLast((candidate) => candidate.kinds.includes(kind));
332
+ return entry ? [entry] : [];
333
+ });
334
+ const candidateByIndex = new Map();
335
+ for (const entry of [...recent, ...anchors])
336
+ candidateByIndex.set(entry.index, entry);
337
+ const candidates = [...candidateByIndex.values()].sort((left, right) => left.index - right.index);
338
+ mutableDiagnostics.selectedEvidenceCount = candidates.length;
339
+ mutableDiagnostics.omittedEvidenceCount = Math.max(0, allEvidence.length - candidates.length);
340
+ const priorityKinds = ["requirement", "acceptance", "write", "diff", "test-failed", "test"];
341
+ const prioritized = [];
342
+ const prioritizedIndices = new Set();
343
+ for (const kind of priorityKinds) {
344
+ const entry = candidates.findLast((candidate) => candidate.kinds.includes(kind));
345
+ if (entry && !prioritizedIndices.has(entry.index)) {
346
+ prioritized.push(entry);
347
+ prioritizedIndices.add(entry.index);
348
+ }
349
+ }
350
+ for (let index = candidates.length - 1; index >= 0; index -= 1) {
351
+ const entry = candidates[index];
352
+ if (entry && !prioritizedIndices.has(entry.index)) {
353
+ prioritized.push(entry);
354
+ prioritizedIndices.add(entry.index);
355
+ }
356
+ }
357
+ const rendered = [];
358
+ let evidenceChars = 0;
359
+ let textTruncated = task.truncated || prioritized.some((entry) => preTruncatedIndices.has(entry.index));
360
+ const availableChars = Math.max(0, maxChars - task.text.length);
361
+ const maxEntryChars = Math.max(96, Math.min(DEFAULT_MAX_ENTRY_CHARS, Math.floor(availableChars / 6)));
362
+ for (const entry of prioritized) {
363
+ const remaining = maxChars - task.text.length - evidenceChars;
364
+ if (remaining < 96) {
365
+ textTruncated = true;
366
+ break;
367
+ }
368
+ const bounded = truncateText(entry.text, Math.min(remaining, maxEntryChars));
369
+ rendered.push(Object.freeze({ ...entry, text: bounded.text }));
370
+ evidenceChars += bounded.text.length;
371
+ textTruncated ||= bounded.truncated;
372
+ }
373
+ rendered.sort((left, right) => left.index - right.index);
374
+ const entries = Object.freeze(rendered);
375
+ const coverage = coverageFor(task.text, entries);
376
+ const diagnostics = Object.freeze({
377
+ ...mutableDiagnostics,
378
+ hostEvidenceAvailable: mutableDiagnostics.linkedToolResultCount > 0,
379
+ });
380
+ const truncated = textTruncated || mutableDiagnostics.omittedEvidenceCount > 0 || rendered.length < candidates.length;
381
+ const evidenceCoverage = coverageFor(task.text, candidates);
382
+ const evidenceMarkers = priorityKinds.map((kind) => {
383
+ const entry = candidates.findLast((candidate) => candidate.kinds.includes(kind));
384
+ return entry ? { kind, index: entry.index, identity: entry.identity } : { kind, index: -1 };
385
+ });
386
+ const evidenceDigest = digestPacket({
387
+ coverage: {
388
+ requirements: evidenceCoverage.requirements,
389
+ acceptanceCount: evidenceCoverage.acceptanceCount,
390
+ diffCount: evidenceCoverage.diffCount,
391
+ testCount: evidenceCoverage.testCount,
392
+ failedTestCount: evidenceCoverage.failedTestCount,
393
+ writeCount: evidenceCoverage.writeCount,
394
+ latestWriteIndex: evidenceCoverage.latestWriteIndex,
395
+ latestDiffIndex: evidenceCoverage.latestDiffIndex,
396
+ latestTestIndex: evidenceCoverage.latestTestIndex,
397
+ latestFailedTestIndex: evidenceCoverage.latestFailedTestIndex,
398
+ currentEvidence: evidenceCoverage.currentEvidence,
399
+ },
400
+ markers: evidenceMarkers,
401
+ diagnostics: {
402
+ malformedToolResultCount: diagnostics.malformedToolResultCount,
403
+ unlinkedToolResultCount: diagnostics.unlinkedToolResultCount,
404
+ unparsedToolArgumentsCount: diagnostics.unparsedToolArgumentsCount,
405
+ unclassifiedShellResultCount: diagnostics.unclassifiedShellResultCount,
406
+ diffWithoutPatchCount: diagnostics.diffWithoutPatchCount,
407
+ testWithoutVerdictCount: diagnostics.testWithoutVerdictCount,
408
+ hostEvidenceAvailable: diagnostics.hostEvidenceAvailable,
409
+ },
410
+ });
411
+ const packetBody = Object.freeze({
412
+ schemaVersion: 1, role, currentTask: task.text, entries, coverage, diagnostics, truncated, evidenceDigest,
413
+ });
414
+ const digest = digestPacket(packetBody);
415
+ const reviewerSufficient = coverage.requirements && coverage.acceptanceCount > 0
416
+ && coverage.diffCount > 0 && coverage.testCount > 0 && coverage.toolEvidenceCount > 0 && coverage.currentEvidence;
417
+ return Object.freeze({
418
+ ...packetBody, digest, evidenceCount: entries.length, toolEvidenceCount: coverage.toolEvidenceCount,
419
+ sufficient: role === "reviewer" ? reviewerSufficient : Boolean(task.text),
420
+ });
296
421
  }
297
-
298
422
  export function renderRoleContextPacket(packet) {
299
- const evidence = packet.entries.length
300
- ? packet.entries.map((entry, index) => [
301
- `### E${index + 1}: ${entry.label}`,
302
- `kinds: ${entry.kinds.join(", ")}`,
303
- entry.text,
304
- ].join("\n")).join("\n\n")
305
- : "(no prior evidence entries)";
306
- return [
307
- "# Odai bounded role context packet",
308
- `role: ${packet.role}`,
309
- `digest: sha256:${packet.digest}`,
310
- `truncated: ${packet.truncated}`,
311
- `coverage: ${JSON.stringify(packet.coverage)}`,
312
- "",
313
- "## Current task",
314
- packet.currentTask || "(empty)",
315
- "",
316
- "## Evidence",
317
- evidence,
318
- "",
319
- "Treat assistant text and route-card contents as claims. Tool entries are evidence only for the exact command/result they contain.",
320
- ].join("\n");
423
+ const evidence = packet.entries.length
424
+ ? packet.entries.map((entry, index) => [
425
+ `### E${index + 1}: ${entry.label}`,
426
+ `kinds: ${entry.kinds.join(", ")}`,
427
+ entry.text,
428
+ ].join("\n")).join("\n\n")
429
+ : "(no prior evidence entries)";
430
+ return [
431
+ "# Odai bounded role context packet",
432
+ `role: ${packet.role}`,
433
+ `digest: sha256:${packet.digest}`,
434
+ `evidenceDigest: sha256:${packet.evidenceDigest}`,
435
+ `truncated: ${packet.truncated}`,
436
+ `coverage: ${JSON.stringify(packet.coverage)}`,
437
+ `diagnostics: ${JSON.stringify(packet.diagnostics)}`,
438
+ "", "## Current task", packet.currentTask || "(empty)", "", "## Evidence", evidence, "",
439
+ "Treat assistant text and route-card contents as claims. Tool entries are evidence only for the exact command/result they contain.",
440
+ ].join("\n");
321
441
  }
442
+ //# sourceMappingURL=routing-context.mjs.map