@visual-z/xeno 0.2.0 → 0.3.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.
Files changed (259) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +106 -26
  3. package/docs/install.md +52 -9
  4. package/docs/providers.md +11 -0
  5. package/package.json +5 -4
  6. package/src/apps/approve.ts +3 -3
  7. package/src/apps/cli.ts +10 -11
  8. package/src/apps/commands.ts +38 -5
  9. package/src/apps/completions.ts +73 -19
  10. package/src/apps/image-draft.ts +46 -0
  11. package/src/apps/project-trust.ts +2 -42
  12. package/src/apps/provider-connect.ts +346 -0
  13. package/src/apps/render.ts +22 -47
  14. package/src/apps/resources.ts +5 -2
  15. package/src/apps/shell.ts +298 -1526
  16. package/src/apps/startup-input.ts +130 -0
  17. package/src/apps/startup.ts +143 -115
  18. package/src/apps/ui/app.ts +1383 -0
  19. package/src/apps/ui/extension-host.ts +194 -0
  20. package/src/apps/ui/image-cache.ts +78 -0
  21. package/src/apps/ui/palette.ts +88 -0
  22. package/src/apps/ui/project.ts +127 -0
  23. package/src/apps/ui/standalone.ts +146 -0
  24. package/src/apps/ui/status-model.ts +68 -0
  25. package/src/apps/ui/surface.ts +84 -0
  26. package/src/apps/ui/theme.ts +86 -0
  27. package/src/core/acceptance.ts +107 -0
  28. package/src/core/action-defaults.ts +47 -295
  29. package/src/core/benchmark.ts +2 -2
  30. package/src/core/branch-receipt.ts +24 -0
  31. package/src/core/checkpoints.ts +64 -0
  32. package/src/core/churn.ts +2 -0
  33. package/src/core/command-artifacts.ts +94 -0
  34. package/src/core/command-contract.ts +35 -0
  35. package/src/core/completion-review.ts +21 -5
  36. package/src/core/context-detail.ts +37 -0
  37. package/src/core/context-policy.ts +74 -1
  38. package/src/core/continuation.ts +17 -2
  39. package/src/core/derive.ts +29 -3
  40. package/src/core/draft-images.ts +56 -0
  41. package/src/core/environment.ts +145 -0
  42. package/src/core/evaluation-coverage.ts +74 -0
  43. package/src/core/event-validation.ts +80 -5
  44. package/src/core/events.ts +97 -3
  45. package/src/core/evidence-links.ts +7 -0
  46. package/src/core/evidence.ts +13 -3
  47. package/src/core/execution.ts +8 -2
  48. package/src/core/experiment.ts +133 -0
  49. package/src/core/export.ts +9 -0
  50. package/src/core/failure-recovery.ts +173 -0
  51. package/src/core/file-precondition.ts +72 -0
  52. package/src/core/handoff.ts +65 -4
  53. package/src/core/history-query.ts +38 -2
  54. package/src/core/history.ts +38 -1
  55. package/src/core/integration-health.ts +34 -0
  56. package/src/core/keymap.ts +8 -20
  57. package/src/core/model-picker.ts +64 -0
  58. package/src/core/native-eval.ts +25 -0
  59. package/src/core/note-guidance.ts +34 -0
  60. package/src/core/observation-view.ts +49 -8
  61. package/src/core/permissions.ts +24 -2
  62. package/src/core/process-receipt.ts +105 -0
  63. package/src/core/process-state.ts +37 -0
  64. package/src/core/project.ts +45 -18
  65. package/src/core/prompt-policy.ts +37 -0
  66. package/src/core/query-eval-contract.ts +14 -0
  67. package/src/core/read-observation.ts +82 -0
  68. package/src/core/recovery-corpus.ts +17 -0
  69. package/src/core/recovery-evaluation.ts +60 -0
  70. package/src/core/recovery-sources.ts +74 -0
  71. package/src/core/runtime-target.ts +94 -0
  72. package/src/core/search-observation.ts +97 -0
  73. package/src/core/session-tree.ts +147 -0
  74. package/src/core/settings.ts +26 -10
  75. package/src/core/skills.ts +44 -0
  76. package/src/core/sql-query.ts +211 -0
  77. package/src/core/theme.ts +36 -58
  78. package/src/core/tool-contract.ts +51 -2
  79. package/src/core/tool-discovery.ts +87 -0
  80. package/src/core/tool-failure.ts +60 -0
  81. package/src/core/tool-mode-protocol.ts +25 -0
  82. package/src/core/transcript.ts +19 -4
  83. package/src/core/trial-lifecycle.ts +14 -0
  84. package/src/core/tui-mode.ts +29 -0
  85. package/src/core/update-check.ts +97 -0
  86. package/src/core/version.ts +55 -0
  87. package/src/core/work-state.ts +49 -3
  88. package/src/core/working-set.ts +91 -0
  89. package/src/runtime/auth.ts +2 -2
  90. package/src/runtime/branch-jobs.ts +36 -0
  91. package/src/runtime/clipboard.ts +15 -4
  92. package/src/runtime/command-artifacts.ts +64 -0
  93. package/src/runtime/environment-tools.ts +60 -0
  94. package/src/runtime/evidence-notes.ts +4 -2
  95. package/src/runtime/exec-sessions.ts +206 -61
  96. package/src/runtime/execution-control.ts +1 -0
  97. package/src/runtime/extension-ui.ts +22 -30
  98. package/src/runtime/extensions.ts +95 -16
  99. package/src/runtime/harness-comparison.ts +27 -7
  100. package/src/runtime/harness-eval.ts +460 -0
  101. package/src/runtime/history-cache.ts +57 -0
  102. package/src/runtime/history-tools.ts +62 -12
  103. package/src/runtime/integrations.ts +210 -0
  104. package/src/runtime/loop.ts +1021 -268
  105. package/src/runtime/model-discovery.ts +208 -0
  106. package/src/runtime/native-eval-fixtures.ts +33 -0
  107. package/src/runtime/native-eval.ts +143 -0
  108. package/src/runtime/oauth.ts +1 -1
  109. package/src/runtime/provider-connect.ts +109 -0
  110. package/src/runtime/provider-presets.ts +87 -0
  111. package/src/runtime/query-eval-verifier.ts +28 -0
  112. package/src/runtime/recovery-coverage.ts +71 -0
  113. package/src/runtime/recovery-evidence.ts +76 -0
  114. package/src/runtime/recovery-fixtures.ts +116 -0
  115. package/src/runtime/runtime-target.ts +149 -0
  116. package/src/runtime/tool-broker.ts +137 -0
  117. package/src/runtime/tool-discovery.ts +57 -0
  118. package/src/runtime/tool-mode-eval.ts +146 -0
  119. package/src/runtime/tool-queue.ts +77 -0
  120. package/src/runtime/tools.ts +628 -183
  121. package/src/runtime/trial-control.ts +45 -0
  122. package/src/runtime/update-check.ts +47 -0
  123. package/src/runtime/window-trial.ts +2 -1
  124. package/src/runtime/working-notes.ts +33 -3
  125. package/src/runtime/workspace-lifecycle.ts +97 -0
  126. package/src/store/acceptance.ts +15 -0
  127. package/src/store/checkpoint-history.ts +43 -0
  128. package/src/store/command-artifacts.ts +96 -0
  129. package/src/store/command.ts +65 -37
  130. package/src/store/file-checkpoints.ts +104 -0
  131. package/src/store/file-index.ts +13 -1
  132. package/src/store/git-checkpoints.ts +67 -0
  133. package/src/store/observations.ts +61 -20
  134. package/src/store/process-receipts.ts +34 -0
  135. package/src/store/query-source.ts +13 -8
  136. package/src/store/recovery.ts +2 -3
  137. package/src/store/replay.ts +16 -4
  138. package/src/store/runtime-target.ts +64 -0
  139. package/src/store/search.ts +171 -132
  140. package/src/store/session-archive.ts +4 -0
  141. package/src/store/source-cancellation.ts +6 -0
  142. package/src/store/update-cache.ts +36 -0
  143. package/src/store/workspace-observation.ts +113 -0
  144. package/src/tui/ansi-spans.ts +126 -0
  145. package/src/tui/style.ts +19 -16
  146. package/src/tui/ui/atomic-draft.ts +121 -0
  147. package/src/tui/ui/canvas.ts +96 -0
  148. package/src/tui/ui/colors.ts +70 -0
  149. package/src/tui/ui/composer.ts +199 -0
  150. package/src/tui/ui/dialog.ts +379 -0
  151. package/src/tui/ui/draft.ts +23 -0
  152. package/src/tui/ui/edge.ts +186 -0
  153. package/src/tui/ui/image-size.ts +40 -0
  154. package/src/tui/ui/ink.ts +210 -0
  155. package/src/tui/ui/items.ts +40 -0
  156. package/src/tui/ui/keys.ts +40 -0
  157. package/src/tui/ui/layout.ts +74 -0
  158. package/src/tui/ui/list-model.ts +86 -0
  159. package/src/tui/ui/list-view.ts +172 -0
  160. package/src/tui/ui/mascot.ts +67 -0
  161. package/src/tui/ui/messages.ts +518 -0
  162. package/src/tui/ui/motion.ts +28 -0
  163. package/src/tui/ui/paths.ts +10 -0
  164. package/src/tui/ui/pictures.ts +78 -0
  165. package/src/tui/ui/screen.ts +421 -0
  166. package/src/tui/ui/status.ts +271 -0
  167. package/src/tui/ui/styled.ts +85 -0
  168. package/src/tui/ui/testing.ts +5 -0
  169. package/src/tui/ui/tool-card.ts +150 -0
  170. package/src/apps/extension-ui.ts +0 -330
  171. package/src/apps/resource-browser.ts +0 -75
  172. package/src/apps/session-browser.ts +0 -113
  173. package/src/apps/session-picker.ts +0 -55
  174. package/src/tui/ansi.ts +0 -67
  175. package/src/tui/autocomplete.ts +0 -29
  176. package/src/tui/chat-viewport.ts +0 -47
  177. package/src/tui/complete.ts +0 -87
  178. package/src/tui/editor.ts +0 -940
  179. package/src/tui/host.ts +0 -264
  180. package/src/tui/input.ts +0 -75
  181. package/src/tui/keys.ts +0 -378
  182. package/src/tui/markdown.ts +0 -17
  183. package/src/tui/render.ts +0 -153
  184. package/src/tui/select.ts +0 -258
  185. package/src/tui/settings.ts +0 -44
  186. package/src/tui/statusline.ts +0 -162
  187. package/src/tui/system-theme.ts +0 -700
  188. package/src/tui/terminal.ts +0 -30
  189. package/src/tui/text.ts +0 -52
  190. package/src/tui/toolview.ts +0 -386
  191. package/src/tui/transcript.ts +0 -123
  192. package/src/tui/tree.ts +0 -471
  193. package/src/vendor/pi-tui/LICENSE.upstream +0 -21
  194. package/src/vendor/pi-tui/UPSTREAM.json +0 -50
  195. package/src/vendor/pi-tui/VENDOR.md +0 -25
  196. package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
  197. package/src/vendor/pi-tui/autocomplete.ts +0 -860
  198. package/src/vendor/pi-tui/colors.ts +0 -367
  199. package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
  200. package/src/vendor/pi-tui/components/box.ts +0 -166
  201. package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
  202. package/src/vendor/pi-tui/components/editor.ts +0 -2472
  203. package/src/vendor/pi-tui/components/h-stack.ts +0 -44
  204. package/src/vendor/pi-tui/components/image.ts +0 -127
  205. package/src/vendor/pi-tui/components/input.ts +0 -494
  206. package/src/vendor/pi-tui/components/loader.ts +0 -101
  207. package/src/vendor/pi-tui/components/markdown.ts +0 -1021
  208. package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
  209. package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
  210. package/src/vendor/pi-tui/components/select-list.ts +0 -273
  211. package/src/vendor/pi-tui/components/settings-list.ts +0 -328
  212. package/src/vendor/pi-tui/components/spacer.ts +0 -28
  213. package/src/vendor/pi-tui/components/stack.ts +0 -154
  214. package/src/vendor/pi-tui/components/text.ts +0 -107
  215. package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
  216. package/src/vendor/pi-tui/components/v-stack.ts +0 -33
  217. package/src/vendor/pi-tui/editor-component.ts +0 -74
  218. package/src/vendor/pi-tui/fuzzy.ts +0 -138
  219. package/src/vendor/pi-tui/index.ts +0 -186
  220. package/src/vendor/pi-tui/keybindings.ts +0 -320
  221. package/src/vendor/pi-tui/keys.ts +0 -1401
  222. package/src/vendor/pi-tui/kill-ring.ts +0 -46
  223. package/src/vendor/pi-tui/latex.ts +0 -1506
  224. package/src/vendor/pi-tui/layout-node.ts +0 -51
  225. package/src/vendor/pi-tui/layout.ts +0 -449
  226. package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
  227. package/src/vendor/pi-tui/native/clipboard.h +0 -208
  228. package/src/vendor/pi-tui/native/darwin/README.md +0 -22
  229. package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
  230. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  231. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  232. package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
  233. package/src/vendor/pi-tui/native/linux/README.md +0 -23
  234. package/src/vendor/pi-tui/native/linux/build.sh +0 -65
  235. package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  236. package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  237. package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
  238. package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
  239. package/src/vendor/pi-tui/native/napi.h +0 -91
  240. package/src/vendor/pi-tui/native/win32/README.md +0 -31
  241. package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
  242. package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  243. package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  244. package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
  245. package/src/vendor/pi-tui/native-modifiers.ts +0 -13
  246. package/src/vendor/pi-tui/native-module-path.ts +0 -31
  247. package/src/vendor/pi-tui/native-platform.ts +0 -65
  248. package/src/vendor/pi-tui/oklab.ts +0 -233
  249. package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
  250. package/src/vendor/pi-tui/terminal-colors.ts +0 -91
  251. package/src/vendor/pi-tui/terminal-image.ts +0 -731
  252. package/src/vendor/pi-tui/terminal.ts +0 -554
  253. package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
  254. package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
  255. package/src/vendor/pi-tui/tui.ts +0 -1493
  256. package/src/vendor/pi-tui/undo-stack.ts +0 -28
  257. package/src/vendor/pi-tui/utils.ts +0 -1385
  258. package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
  259. package/src/vendor/pi-tui/word-navigation.ts +0 -117
@@ -1,306 +1,58 @@
1
- /** Adapted from Pi keybindings at 02eed88 (MIT). Platform-neutral defaults; overrides share scoped ownership. */
1
+ /**
2
+ * xeno's key bindings (D20). One flat namespace:
3
+ * app.* global, handled before the focused component
4
+ * editor.* the composer (submit / newline / history / completion)
5
+ * list.* every list dialog (palette, model, login, resume, tree, …)
6
+ * Shortcuts that only exist inside one dialog are shown in that dialog's
7
+ * hint row instead of being configurable globals; the palette (Ctrl+P)
8
+ * reaches every command, so rarely used actions need no chord at all.
9
+ */
2
10
  export const ACTION_DEFAULTS = {
3
- "editor.cursorUp": [
4
- "up"
5
- ],
6
- "editor.cursorDown": [
7
- "down"
8
- ],
9
- "tui.editor.historyPrevious": [],
10
- "tui.editor.historyNext": [],
11
- "editor.cursorLeft": [
12
- "left",
13
- "ctrl+b"
14
- ],
15
- "editor.cursorRight": [
16
- "right",
17
- "ctrl+f"
18
- ],
19
- "editor.cursorWordLeft": [
20
- "alt+left",
21
- "ctrl+left",
22
- "alt+b"
23
- ],
24
- "editor.cursorWordRight": [
25
- "alt+right",
26
- "ctrl+right",
27
- "alt+f"
28
- ],
29
- "editor.cursorLineStart": [
30
- "home",
31
- "ctrl+home",
32
- "ctrl+a"
33
- ],
34
- "editor.cursorLineEnd": [
35
- "end",
36
- "ctrl+end",
37
- "ctrl+e"
38
- ],
39
- "tui.editor.jumpForward": [
40
- "ctrl+]"
41
- ],
42
- "tui.editor.jumpBackward": [
43
- "ctrl+alt+]"
44
- ],
45
- "tui.editor.pageUp": [
46
- "pageUp",
47
- "ctrl+pageUp"
48
- ],
49
- "tui.editor.pageDown": [
50
- "pageDown",
51
- "ctrl+pageDown"
52
- ],
53
- "editor.deleteCharBackward": [
54
- "backspace"
55
- ],
56
- "editor.deleteCharForward": [
57
- "delete",
58
- "ctrl+d"
59
- ],
60
- "editor.deleteWordBackward": [
61
- "ctrl+w",
62
- "alt+backspace"
63
- ],
64
- "editor.deleteWordForward": [
65
- "alt+d",
66
- "alt+delete"
67
- ],
68
- "editor.deleteToLineStart": [
69
- "ctrl+u"
70
- ],
71
- "editor.deleteToLineEnd": [
72
- "ctrl+k"
73
- ],
74
- "editor.yank": [
75
- "ctrl+y"
76
- ],
77
- "tui.editor.yankPop": [
78
- "alt+y"
79
- ],
80
- "editor.undo": [
81
- "ctrl+-",
82
- "ctrl+_"
83
- ],
84
- "editor.newLine": [
85
- "shift+enter",
86
- "ctrl+j"
87
- ],
88
- "editor.submit": [
89
- "enter"
90
- ],
91
- "editor.complete": [
92
- "tab"
93
- ],
94
- "tui.input.copy": [
95
- "ctrl+c"
96
- ],
97
- "tui.select.up": [
98
- "up"
99
- ],
100
- "tui.select.down": [
101
- "down"
102
- ],
103
- "tui.select.pageUp": [
104
- "pageUp"
105
- ],
106
- "tui.select.pageDown": [
107
- "pageDown"
108
- ],
109
- "tui.select.confirm": [
110
- "enter"
111
- ],
112
- "tui.select.cancel": [
113
- "escape",
114
- "ctrl+c"
115
- ],
116
- "tui.altScreen.pageUp": [
117
- "pageUp"
118
- ],
119
- "tui.altScreen.pageDown": [
120
- "pageDown"
121
- ],
122
- "tui.altScreen.halfPageUp": [],
123
- "tui.altScreen.halfPageDown": [],
124
- "tui.altScreen.lineUp": [],
125
- "tui.altScreen.lineDown": [],
126
- "tui.altScreen.previousPrompt": [
127
- "ctrl+shift+up",
128
- "ctrl+up"
129
- ],
130
- "tui.altScreen.nextPrompt": [
131
- "ctrl+shift+down",
132
- "ctrl+down"
133
- ],
134
- "tui.altScreen.search": [
135
- "ctrl+shift+f"
136
- ],
137
- "tui.altScreen.searchNext": [
138
- "enter",
139
- "ctrl+g"
140
- ],
141
- "tui.altScreen.searchPrevious": [
142
- "shift+enter",
143
- "ctrl+shift+g"
144
- ],
145
- "tui.altScreen.searchClose": [
146
- "escape"
147
- ],
148
- "tui.altScreen.top": [
149
- "home"
150
- ],
151
- "tui.altScreen.bottom": [
152
- "end"
153
- ],
154
- "app.interrupt": [
155
- "escape"
156
- ],
157
- "app.clear": [
158
- "ctrl+c"
159
- ],
160
- "app.exit": [
161
- "ctrl+d"
162
- ],
163
- "app.suspend": [
164
- "ctrl+z"
165
- ],
166
- "app.cycleThinking": [
167
- "shift+tab"
168
- ],
169
- "app.saveDefault": [
170
- "ctrl+s"
171
- ],
172
- "app.cycleModel": [
173
- "ctrl+p"
174
- ],
175
- "app.cycleModelReverse": [
176
- "shift+ctrl+p"
177
- ],
178
- "app.model": [
179
- "ctrl+l"
180
- ],
181
- "app.toggleTools": [
182
- "ctrl+o"
183
- ],
184
- "app.toggleThinking": [
185
- "ctrl+t"
186
- ],
187
- "app.session.toggleNamedFilter": [
188
- "ctrl+n"
189
- ],
190
- "app.externalEditor": [
191
- "ctrl+g"
192
- ],
193
- "app.message.copy": [
194
- "ctrl+x"
195
- ],
196
- "app.message.followUp": [
197
- "alt+enter"
198
- ],
199
- "app.message.dequeue": [
200
- "alt+up"
201
- ],
202
- "app.clipboard.pasteImage": [
203
- "ctrl+v"
204
- ],
11
+ "app.palette": ["ctrl+p"],
12
+ "app.model": ["ctrl+l"],
13
+ "app.cycleModel": ["alt+m"],
14
+ "app.cycleModelReverse": ["alt+shift+m"],
15
+ "app.cycleThinking": ["shift+tab"],
16
+ "app.toggleTools": ["ctrl+o"],
17
+ "app.toggleThinking": ["ctrl+t"],
18
+ "app.toggleSidebar": ["ctrl+b"],
19
+ "app.interrupt": ["escape"],
20
+ "app.clear": ["ctrl+c"],
21
+ "app.exit": ["ctrl+d"],
22
+ "app.suspend": ["ctrl+z"],
23
+ "app.externalEditor": ["ctrl+g"],
24
+ "app.message.copy": ["ctrl+x"],
25
+ "app.message.followUp": ["alt+enter"],
26
+ "app.message.dequeue": ["alt+up"],
27
+ "app.clipboard.pasteImage": ["ctrl+v"],
28
+ "app.scroll.pageUp": ["pageup"],
29
+ "app.scroll.pageDown": ["pagedown"],
30
+ "app.scroll.top": ["ctrl+home"],
31
+ "app.scroll.bottom": ["ctrl+end"],
205
32
  "app.session.new": [],
206
33
  "app.session.tree": [],
207
34
  "app.session.fork": [],
208
35
  "app.session.resume": [],
209
- "app.tree.foldOrUp": [
210
- "alt+left",
211
- "ctrl+left"
212
- ],
213
- "app.tree.unfoldOrDown": [
214
- "alt+right",
215
- "ctrl+right"
216
- ],
217
- "app.tree.editLabel": [
218
- "shift+l"
219
- ],
220
- "app.tree.toggleLabelTimestamp": [
221
- "shift+t"
222
- ],
223
- "app.session.togglePath": [
224
- "ctrl+p"
225
- ],
226
- "app.session.toggleSort": [
227
- "ctrl+s"
228
- ],
229
- "app.session.rename": [
230
- "ctrl+r"
231
- ],
232
- "app.session.delete": [
233
- "ctrl+d"
234
- ],
235
- "app.session.deleteNoninvasive": [
236
- "ctrl+backspace"
237
- ],
238
- "app.models.save": [
239
- "ctrl+s"
240
- ],
241
- "app.models.enableAll": [
242
- "ctrl+a"
243
- ],
244
- "app.models.clearAll": [
245
- "ctrl+x"
246
- ],
247
- "app.models.toggleProvider": [
248
- "ctrl+p"
249
- ],
250
- "app.models.reorderUp": [
251
- "alt+up"
252
- ],
253
- "app.models.reorderDown": [
254
- "alt+down"
255
- ],
256
- "app.tree.filter.default": [
257
- "ctrl+d"
258
- ],
259
- "app.tree.filter.noTools": [
260
- "ctrl+t"
261
- ],
262
- "app.tree.filter.userOnly": [
263
- "ctrl+u"
264
- ],
265
- "app.tree.filter.labeledOnly": [
266
- "ctrl+l"
267
- ],
268
- "app.tree.filter.all": [
269
- "ctrl+a"
270
- ],
271
- "app.tree.filter.cycleForward": [
272
- "ctrl+o"
273
- ],
274
- "app.tree.filter.cycleBackward": [
275
- "shift+ctrl+o"
276
- ]
277
- } as const;
278
- export const ACTION_ALIASES: Readonly<Record<string,string>> = {
279
- "tui.input.submit": "editor.submit",
280
- "tui.input.newLine": "editor.newLine",
281
- "tui.input.tab": "editor.complete",
36
+ "app.saveDefault": ["ctrl+s"],
37
+ "editor.submit": ["enter"],
38
+ "editor.newLine": ["shift+enter", "ctrl+j"],
39
+ "editor.complete": ["tab"],
40
+ "editor.historyPrevious": ["up"],
41
+ "editor.historyNext": ["down"],
42
+ "list.up": ["up", "ctrl+k"],
43
+ "list.down": ["down", "ctrl+j"],
44
+ "list.pageUp": ["pageup"],
45
+ "list.pageDown": ["pagedown"],
46
+ "list.confirm": ["enter"],
47
+ "list.cancel": ["escape"],
48
+ "list.toggle": ["space"],
49
+ } as const satisfies Record<string, readonly string[]>;
50
+
51
+ /** Renamed ids still read from a user's keybindings file. */
52
+ export const ACTION_ALIASES: Readonly<Record<string, string>> = {
282
53
  "app.model.select": "app.model",
283
- "app.model.cycleForward": "app.cycleModel",
284
- "app.model.cycleBackward": "app.cycleModelReverse",
285
54
  "app.thinking.cycle": "app.cycleThinking",
286
55
  "app.thinking.toggle": "app.toggleThinking",
287
56
  "app.tools.expand": "app.toggleTools",
288
57
  "app.editor.external": "app.externalEditor",
289
- "app.thinking.save": "app.saveDefault",
290
- "tui.editor.cursorUp": "editor.cursorUp",
291
- "tui.editor.cursorDown": "editor.cursorDown",
292
- "tui.editor.cursorLeft": "editor.cursorLeft",
293
- "tui.editor.cursorRight": "editor.cursorRight",
294
- "tui.editor.cursorWordLeft": "editor.cursorWordLeft",
295
- "tui.editor.cursorWordRight": "editor.cursorWordRight",
296
- "tui.editor.cursorLineStart": "editor.cursorLineStart",
297
- "tui.editor.cursorLineEnd": "editor.cursorLineEnd",
298
- "tui.editor.deleteCharBackward": "editor.deleteCharBackward",
299
- "tui.editor.deleteCharForward": "editor.deleteCharForward",
300
- "tui.editor.deleteWordBackward": "editor.deleteWordBackward",
301
- "tui.editor.deleteWordForward": "editor.deleteWordForward",
302
- "tui.editor.deleteToLineStart": "editor.deleteToLineStart",
303
- "tui.editor.deleteToLineEnd": "editor.deleteToLineEnd",
304
- "tui.editor.yank": "editor.yank",
305
- "tui.editor.undo": "editor.undo"
306
58
  };
@@ -126,7 +126,7 @@ export function benchmarkPrompt(item: BenchmarkItem, chunks: number): string {
126
126
  'Format your final response as follows: "The correct answer is (insert answer here)".';
127
127
  }
128
128
 
129
- export interface BenchmarkPrice { input: number; output: number; cacheRead: number }
129
+ export interface BenchmarkPrice { input: number; output: number; cacheRead: number; cacheWrite?: number }
130
130
  export interface BenchmarkUsage { input_tokens: number; output_tokens: number; cache_read_input_tokens: number; cache_creation_input_tokens: number }
131
131
 
132
132
  /** A conservative allowance, not a tokenizer: wire bytes plus framing, at uncached peak rates. */
@@ -139,7 +139,7 @@ export function measuredCharge(usage: BenchmarkUsage, price: BenchmarkPrice): nu
139
139
  const counts = [usage.input_tokens, usage.output_tokens, usage.cache_read_input_tokens, usage.cache_creation_input_tokens];
140
140
  if (counts.some((value) => !Number.isSafeInteger(value) || value < 0) ||
141
141
  usage.input_tokens + usage.cache_read_input_tokens + usage.cache_creation_input_tokens === 0 || usage.output_tokens === 0) return null;
142
- return ((usage.input_tokens + usage.cache_creation_input_tokens) * price.input +
142
+ return (usage.input_tokens * price.input + usage.cache_creation_input_tokens * (price.cacheWrite ?? price.input) +
143
143
  usage.cache_read_input_tokens * price.cacheRead + usage.output_tokens * price.output) / 1_000_000;
144
144
  }
145
145
 
@@ -0,0 +1,24 @@
1
+ /** O.9: references to facts, never a child's self-assessed completion. */
2
+ import type { LogEvent, NodeRef } from "./events.ts";
3
+ import type { Handoff } from "./handoff.ts";
4
+ import type { ExecutionOutcome } from "./outcome.ts";
5
+
6
+ export function deriveBranchReceipt(input: {
7
+ source: NodeRef; outcome: ExecutionOutcome; handoff: Handoff;
8
+ nodes: readonly { node: number; events: readonly LogEvent[] }[];
9
+ }) {
10
+ const entries = input.nodes.flatMap(part => part.events.map(event => ({ node: part.node, event })));
11
+ const commands = entries.flatMap(({ node, event }) => event.t === "tool_result" && event.effect?.kind === "command"
12
+ ? [{ node, event: event.id, exit: event.effect.exit }] : []);
13
+ const changes = entries.flatMap(({ node, event }) => event.t === "tool_result" && event.effect?.kind === "edit"
14
+ ? [{ node, event: event.id, path: event.effect.path }] : []);
15
+ const observations = entries.flatMap(({ node, event }) => event.t === "tool_result" ? [{ node, event: event.id }] : []);
16
+ const unfinished = (input.handoff.state.work_items ?? []).filter(item => item.status === "active" || item.status === "blocked")
17
+ .map(item => ({ work_id: item.id, status: item.status, next_action: item.next_action ?? null }));
18
+ return { version: 1, source: input.source, execution: input.outcome,
19
+ verification: commands.length === 0 ? "not_recorded" : commands.some(command => command.exit === 0) ? "command_succeeded" : "commands_failed",
20
+ task_acceptance: "not_assessed", commands, changes, observations, unfinished,
21
+ ...(input.handoff.processes?.length ? { processes: input.handoff.processes } : {}),
22
+ unresolved_branches: input.handoff.branches.filter(b => b.resolution === null).map(b => b.node),
23
+ };
24
+ }
@@ -0,0 +1,64 @@
1
+ /** P.3: recovery describes observed file transitions, never a complete disk image. */
2
+ export type FileVersion = { sha256: string; mode: number } | null;
3
+ export type FileChange = { before: FileVersion; after: FileVersion };
4
+ export type FileChanges = Record<string, FileChange>;
5
+ export interface CheckpointEvent {
6
+ t: "checkpoint";
7
+ id: string;
8
+ at: string;
9
+ operation: string;
10
+ source: "edit" | "git" | "restore";
11
+ phase: "prepared" | "applied" | "unavailable";
12
+ changes: FileChanges;
13
+ tree?: string;
14
+ reason?: string;
15
+ }
16
+ export interface WorkspaceObservationEvent {
17
+ t: "workspace_observation";
18
+ id: string;
19
+ at: string;
20
+ /** P.3: this scope excludes ignored/build output and is not a backup. */
21
+ fingerprint?: string;
22
+ scope: "source-v1";
23
+ changed: boolean;
24
+ reason?: string;
25
+ }
26
+
27
+ export function sameVersion(a: FileVersion, b: FileVersion): boolean {
28
+ return a === null || b === null ? a === b : a.sha256 === b.sha256 && a.mode === b.mode;
29
+ }
30
+ export function changedPaths(changes: FileChanges): string[] {
31
+ return Object.keys(changes).filter(path => !sameVersion(changes[path]!.before, changes[path]!.after)).sort();
32
+ }
33
+
34
+ /** Undo the abandoned suffix then replay the selected suffix, touching only their paths. */
35
+ export function planFileTransition(from: readonly CheckpointEvent[], to: readonly CheckpointEvent[]): FileChanges {
36
+ const left = from.filter(event => event.phase === "applied");
37
+ const right = to.filter(event => event.phase === "applied");
38
+ let common = 0;
39
+ while (common < left.length && common < right.length && left[common]!.id === right[common]!.id) common++;
40
+ const changes: FileChanges = Object.create(null) as FileChanges;
41
+ const apply = (path: string, before: FileVersion, after: FileVersion) => {
42
+ const existing = changes[path];
43
+ if (existing && !sameVersion(existing.after, before)) throw new Error(`discontinuous file history: ${path}; inspect intervening external edits`);
44
+ changes[path] = { before: existing ? existing.before : before, after };
45
+ };
46
+ for (const event of left.slice(common).reverse()) for (const path of changedPaths(event.changes)) {
47
+ const change = event.changes[path]!;
48
+ apply(path, change.after, change.before);
49
+ }
50
+ for (const event of right.slice(common)) for (const path of changedPaths(event.changes)) {
51
+ const change = event.changes[path]!;
52
+ apply(path, change.before, change.after);
53
+ }
54
+ return Object.fromEntries(changedPaths(changes).map(path => [path, changes[path]!])) as FileChanges;
55
+ }
56
+
57
+ export function validFileChanges(value: unknown): value is FileChanges {
58
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
59
+ const version = (v: unknown): boolean => v === null || typeof v === "object" && !Array.isArray(v) &&
60
+ typeof (v as Record<string, unknown>)["sha256"] === "string" && /^[a-f0-9]{64}$/.test(String((v as Record<string, unknown>)["sha256"])) &&
61
+ Number.isInteger((v as Record<string, unknown>)["mode"]) && Number((v as Record<string, unknown>)["mode"]) >= 0 && Number((v as Record<string, unknown>)["mode"]) <= 0o777;
62
+ return Object.entries(value).every(([path, change]: [string, unknown]) => path.length > 0 && typeof change === "object" && change !== null &&
63
+ version((change as Record<string, unknown>)["before"]) && version((change as Record<string, unknown>)["after"]));
64
+ }
package/src/core/churn.ts CHANGED
@@ -34,6 +34,8 @@ export function churnReview(events: readonly LogEvent[]): Pick<GuidanceEvent, "r
34
34
  if (event.t !== "tool_result" || event.effect === undefined) continue;
35
35
  const effect = event.effect;
36
36
  if (effect.kind === "command") {
37
+ // R5 handles structured failures with version/change premises; keep this path for legacy logs.
38
+ if (event.diagnostic?.execution !== undefined) continue;
37
39
  const key = effect.cmd.trim();
38
40
  if (effect.exit === 0) { failures.delete(key); continue; }
39
41
  const seen = { count: (failures.get(key)?.count ?? 0) + 1, last: event.id };
@@ -0,0 +1,94 @@
1
+ /** R8 / D8 / D11: an explicit observation scope is not a claim of build causality. */
2
+ import type { ArtifactRef } from "./events.ts";
3
+ import { isObservationArtifact } from "./observation-view.ts";
4
+
5
+ // Assumed observation budgets keep a model-supplied plan bounded before execution.
6
+ export const MAX_COMMAND_ARTIFACTS = 16;
7
+ export const MAX_ARTIFACT_PLAN_BYTES = 16 * 1024;
8
+ export const MAX_COMMAND_ARTIFACT_BYTES = 512 * 1024 * 1024;
9
+ export const COMMAND_ARTIFACT_SCOPE = "source-v1-minus-artifacts";
10
+ export type CommandArtifactPlan = { paths: string[]; output_roots: string[] };
11
+ export type ArtifactFileIdentity = { device: string; inode: string; mtime_ns: string; ctime_ns: string; mode: number };
12
+ export type ArtifactFile = { state: "observed"; sha256: string; bytes: number; identity: ArtifactFileIdentity } |
13
+ { state: "missing" | "unsafe" | "changed" | "unknown"; reason: string };
14
+ export type ArtifactChange = "created" | "modified" | "unchanged" | "unknown";
15
+ export type CommandArtifactOutput = { path: string; before: ArtifactFile; after: ArtifactFile; change: ArtifactChange };
16
+ export type CommandArtifactReceipt = {
17
+ kind: "command_artifacts"; version: 1;
18
+ state: "pending" | "observed" | "changed" | "unknown";
19
+ plan: CommandArtifactPlan;
20
+ source: { host: string; workspace: string; cwd: string; runtime: string };
21
+ command: string; exit: number | null;
22
+ started_at: string; observed_at: string | null;
23
+ inputs: { scope: typeof COMMAND_ARTIFACT_SCOPE; exclusions: string[]; before: string | null; after: string | null };
24
+ outputs: CommandArtifactOutput[];
25
+ reasons: string[];
26
+ manifest?: ArtifactRef;
27
+ };
28
+
29
+ const object = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value);
30
+ const digest = (value: unknown): value is string => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
31
+ const covers = (root: string, path: string) => root === path || path.startsWith(root + "/");
32
+ export function validArtifactPath(value: unknown): value is string {
33
+ return typeof value === "string" && value.length > 0 && new TextEncoder().encode(value).length <= 4096 &&
34
+ !/[\x00-\x1f\x7f\\:]/.test(value) && value.split("/").every(part => part !== "" && part !== "." && part !== ".." && part !== ".git" && part !== ".xeno");
35
+ }
36
+ export function parseCommandArtifactPlan(value: unknown): CommandArtifactPlan {
37
+ if (!object(value) || Object.keys(value).some(key => !["paths", "output_roots"].includes(key))) throw new Error("artifacts needs paths and optional output_roots only");
38
+ const paths = value["paths"], roots = value["output_roots"] ?? paths;
39
+ for (const list of [paths, roots]) {
40
+ if (!Array.isArray(list) || list.length < 1 || list.length > MAX_COMMAND_ARTIFACTS || !list.every(validArtifactPath) || new Set(list).size !== list.length)
41
+ throw new Error("artifacts needs 1..16 unique workspace-relative file paths; no traversal or protected paths");
42
+ }
43
+ const plan = { paths: [...paths as string[]].sort(), output_roots: [...roots as string[]].sort() };
44
+ if (new TextEncoder().encode(JSON.stringify(plan)).length > MAX_ARTIFACT_PLAN_BYTES) throw new Error("artifact plan exceeds 16 KiB");
45
+ if (plan.paths.some((path, i) => plan.paths.some((other, j) => i !== j && covers(path, other)))) throw new Error("artifact file paths cannot contain one another");
46
+ if (plan.output_roots.some((root, i) => !plan.paths.some(path => covers(root, path)) || plan.output_roots.some((other, j) => i !== j && covers(root, other))) ||
47
+ plan.paths.some(path => !plan.output_roots.some(root => covers(root, path)))) throw new Error("output_roots must be nonoverlapping and cover exactly the requested artifact paths");
48
+ return plan;
49
+ }
50
+ export function artifactChange(before: ArtifactFile, after: ArtifactFile): ArtifactChange {
51
+ if (after.state !== "observed") return "unknown";
52
+ if (before.state === "missing") return "created";
53
+ if (before.state !== "observed") return "unknown";
54
+ return before.sha256 === after.sha256 && before.bytes === after.bytes && sameArtifactIdentity(before.identity, after.identity) ? "unchanged" : "modified";
55
+ }
56
+ export function sameArtifactIdentity(a: ArtifactFileIdentity, b: ArtifactFileIdentity): boolean {
57
+ return a.device === b.device && a.inode === b.inode && a.mtime_ns === b.mtime_ns && a.ctime_ns === b.ctime_ns && a.mode === b.mode;
58
+ }
59
+ export function commandArtifactState(receipt: Pick<CommandArtifactReceipt, "exit" | "inputs" | "outputs" | "reasons">): CommandArtifactReceipt["state"] {
60
+ if (receipt.exit === null) return "pending";
61
+ if (receipt.inputs.before !== null && receipt.inputs.after !== null && receipt.inputs.before !== receipt.inputs.after ||
62
+ receipt.outputs.some(output => output.after.state === "changed")) return "changed";
63
+ if (receipt.exit !== 0 || receipt.reasons.length > 0 || receipt.inputs.before === null || receipt.inputs.after === null ||
64
+ receipt.outputs.length === 0 || receipt.outputs.some(output => output.after.state !== "observed")) return "unknown";
65
+ return "observed";
66
+ }
67
+ export function isArtifactFile(value: unknown): value is ArtifactFile {
68
+ if (!object(value)) return false;
69
+ if (value["state"] !== "observed") return ["missing", "unsafe", "changed", "unknown"].includes(String(value["state"])) && typeof value["reason"] === "string" && value["reason"].length > 0;
70
+ const id = value["identity"];
71
+ return digest(value["sha256"]) && Number.isSafeInteger(value["bytes"]) && Number(value["bytes"]) >= 0 && Number(value["bytes"]) <= MAX_COMMAND_ARTIFACT_BYTES &&
72
+ object(id) && ["device", "inode", "mtime_ns", "ctime_ns"].every(key => typeof id[key] === "string" && /^-?\d+$/.test(id[key])) &&
73
+ Number.isInteger(id["mode"]) && Number(id["mode"]) >= 0 && Number(id["mode"]) <= 0o777;
74
+ }
75
+ export function isCommandArtifactReceipt(value: unknown): value is CommandArtifactReceipt {
76
+ if (!object(value) || value["kind"] !== "command_artifacts" || value["version"] !== 1 || typeof value["command"] !== "string" || !value["command"].trim() ||
77
+ typeof value["started_at"] !== "string" || (value["exit"] !== null && !Number.isInteger(value["exit"]))) return false;
78
+ let plan: CommandArtifactPlan;
79
+ try { plan = parseCommandArtifactPlan(value["plan"]); } catch { return false; }
80
+ if (!object(value["plan"]) || JSON.stringify(value["plan"]["paths"]) !== JSON.stringify(plan.paths) ||
81
+ JSON.stringify(value["plan"]["output_roots"]) !== JSON.stringify(plan.output_roots)) return false;
82
+ const source = value["source"], inputs = value["inputs"], outputs = value["outputs"], reasons = value["reasons"];
83
+ if (!object(source) || !["host", "workspace", "cwd", "runtime"].every(key => typeof source[key] === "string" && source[key] !== "") ||
84
+ !object(inputs) || inputs["scope"] !== COMMAND_ARTIFACT_SCOPE || !Array.isArray(inputs["exclusions"]) ||
85
+ !inputs["exclusions"].every(path => typeof path === "string" && path.length > 0) || new Set(inputs["exclusions"]).size !== inputs["exclusions"].length ||
86
+ !plan.output_roots.every(root => (inputs["exclusions"] as unknown[]).includes(root)) ||
87
+ !["before", "after"].every(key => inputs[key] === null || digest(inputs[key])) || !Array.isArray(outputs) ||
88
+ !Array.isArray(reasons) || !reasons.every(reason => typeof reason === "string" && reason.length > 0)) return false;
89
+ if (value["exit"] === null) return value["state"] === "pending" && value["observed_at"] === null && inputs["after"] === null && outputs.length === 0 && value["manifest"] === undefined;
90
+ if (typeof value["observed_at"] !== "string" || !value["observed_at"] || outputs.length !== plan.paths.length || !outputs.every((item, index) =>
91
+ object(item) && item["path"] === plan.paths[index] && isArtifactFile(item["before"]) && isArtifactFile(item["after"]) && item["change"] === artifactChange(item["before"], item["after"]))) return false;
92
+ if (value["manifest"] !== undefined && !isObservationArtifact(value["manifest"])) return false;
93
+ return value["state"] === commandArtifactState(value as CommandArtifactReceipt) && (value["state"] !== "observed" || isObservationArtifact(value["manifest"]));
94
+ }
@@ -0,0 +1,35 @@
1
+ /** R1 / D11: all command entrances agree on invocation and observed termination. */
2
+ import { executionOutcome } from "./outcome.ts";
3
+ import type { ToolDiagnostic } from "./tool-contract.ts";
4
+
5
+ /** Operational grace, not a measured optimum: ignored SIGTERM cannot defeat cancellation. */
6
+ export const COMMAND_TERMINATION_GRACE_MS = 200;
7
+
8
+ /** A fresh login Bash per command; interactive stdin does not change shell success semantics. */
9
+ export function commandArgv(command: string): string[] {
10
+ return ["bash", "-o", "pipefail", "-lc", command];
11
+ }
12
+
13
+ /** Preserve inherited values except documented noninteractive defaults; never mutate the host. */
14
+ export function commandEnvironment(base: Readonly<Record<string, string | undefined>>): Record<string, string> {
15
+ const env: Record<string, string> = {};
16
+ for (const [key, value] of Object.entries(base)) if (value !== undefined) env[key] = value;
17
+ Object.assign(env, {
18
+ TERM: "dumb", PAGER: "cat", GIT_PAGER: "cat", NO_COLOR: "1",
19
+ GIT_TERMINAL_PROMPT: "0", DEBIAN_FRONTEND: "noninteractive",
20
+ });
21
+ if (env["LANG"] === undefined || env["LANG"] === "") env["LANG"] = "C.UTF-8";
22
+ return env;
23
+ }
24
+
25
+ /** Model seconds and host milliseconds meet once; a yield is never a command deadline. */
26
+ export function commandTimeout(requestedSeconds: number | undefined, defaultMs: number, maximumMs = defaultMs): number {
27
+ return Math.min(requestedSeconds === undefined ? defaultMs : requestedSeconds * 1000, maximumMs);
28
+ }
29
+
30
+ /** A trap can exit zero after cancellation: successful exit alone does not erase the stop. */
31
+ export function commandCompletion(exit: number, stopped?: "cancelled" | "timeout", cleanup?: ToolDiagnostic) {
32
+ return { ok: stopped === undefined && exit === 0 && cleanup === undefined,
33
+ outcome: executionOutcome(stopped ?? (exit === 0 && cleanup === undefined ? "tool_completed" : "tool_failed")),
34
+ ...(cleanup ? { warnings: [cleanup] } : {}) };
35
+ }
@@ -11,6 +11,9 @@
11
11
  * turn into a loop.
12
12
  */
13
13
  import type { GuidanceEvent, LogEvent } from "./events.ts";
14
+ import type { AcceptanceState } from "./acceptance.ts";
15
+ import { changedPaths } from "./checkpoints.ts";
16
+ import { resolveEvidence } from "./evidence.ts";
14
17
 
15
18
  /**
16
19
  * Wording that hands the task back instead of finishing it. Ported from
@@ -35,6 +38,7 @@ export function looksLikeHandBack(text: string): boolean {
35
38
  export function completionReview(
36
39
  events: readonly LogEvent[],
37
40
  remainingRequests: number,
41
+ options?: { strictVerification?: boolean; acceptance?: AcceptanceState; taskAware?: boolean },
38
42
  ): Pick<GuidanceEvent, "reason" | "text" | "sources"> | null {
39
43
  if (remainingRequests < 1) return null;
40
44
  const start = events.findLastIndex((event) => event.t === "user");
@@ -46,21 +50,33 @@ export function completionReview(
46
50
 
47
51
  const text = final.blocks.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n").trim();
48
52
  const usedTools = current.some((event) => event.t === "tool_result");
49
- const lastEdit = current.findLastIndex((event) => event.t === "tool_result" && event.effect?.kind === "edit");
53
+ const lastEdit = current.findLastIndex((event) => event.t === "tool_result" && event.effect?.kind === "edit" ||
54
+ event.t === "checkpoint" && event.phase === "applied" && changedPaths(event.changes).length > 0);
50
55
  const verifiedAfter = lastEdit >= 0 && current.slice(lastEdit + 1)
51
- .some((event) => event.t === "tool_result" && event.effect?.kind === "command" && event.effect.exit === 0);
56
+ .some((event) => event.t === "tool_result" && event.effect?.kind === "command" && event.effect.exit === 0 &&
57
+ resolveEvidence(current, { command: event.effect.cmd }).ok &&
58
+ (!options?.strictVerification || event.effect.gate !== undefined));
52
59
 
53
60
  const gaps: string[] = [];
54
61
  if (usedTools && text.length === 0) gaps.push("the final reply is empty");
55
- if (lastEdit >= 0 && !verifiedAfter) gaps.push("files were changed but no command has succeeded since the last edit");
56
- if (text.length > 0 && looksLikeHandBack(text)) gaps.push("the reply handed the task back instead of finishing it; no one will answer");
62
+ if (lastEdit >= 0 && !verifiedAfter && !options?.acceptance) gaps.push("files were changed but no command has succeeded since the last edit");
63
+ const acceptance = options?.acceptance;
64
+ if (acceptance?.status === "needs_work") gaps.push(`declared acceptance criteria need evidence: ${[
65
+ ...acceptance.criteria.filter(item => item.kind === "command" && item.status !== "pass").map(item => `${item.id}=${item.status}`),
66
+ ...(acceptance.targets ?? []).filter(target => target.status !== "ready").map(target => `target ${target.id}=${target.status}`),
67
+ ].join(", ")}`);
68
+ if (text.length > 0 && looksLikeHandBack(text) && acceptance?.status !== "awaiting_human") gaps.push(options?.taskAware
69
+ ? "the reply deferred the request; check whether authorized work remains or a specific blocker requires the user"
70
+ : "the reply handed the task back instead of finishing it; no one will answer");
57
71
  if (gaps.length === 0) return null;
58
72
  return {
59
73
  reason: "completion_review",
60
74
  sources: [current[0]!.id, final.id],
61
75
  text: `Harness completion check: ${gaps.join("; ")}. ` +
62
76
  `Re-read the original request (event ${current[0]!.id}) and check each requirement against evidence in this session. ` +
63
- "Run the command that verifies the result, or finish with the specific blocker that prevents verification. " +
77
+ (options?.taskAware && lastEdit < 0
78
+ ? "Use original sources for research and full inputs for calculations; do not invent command evidence. Required human acceptance remains pending. Finish with the result or the specific blocker. "
79
+ : "Run the command that verifies the result, or finish with the specific blocker that prevents verification. ") +
64
80
  "This check runs once per request.",
65
81
  };
66
82
  }