@visual-z/xeno 0.2.1 → 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 (258) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +106 -26
  3. package/docs/install.md +51 -13
  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/commands.ts +38 -5
  8. package/src/apps/completions.ts +73 -19
  9. package/src/apps/image-draft.ts +46 -0
  10. package/src/apps/project-trust.ts +2 -42
  11. package/src/apps/provider-connect.ts +346 -0
  12. package/src/apps/render.ts +22 -47
  13. package/src/apps/resources.ts +5 -2
  14. package/src/apps/shell.ts +298 -1526
  15. package/src/apps/startup-input.ts +45 -9
  16. package/src/apps/startup.ts +128 -86
  17. package/src/apps/ui/app.ts +1383 -0
  18. package/src/apps/ui/extension-host.ts +194 -0
  19. package/src/apps/ui/image-cache.ts +78 -0
  20. package/src/apps/ui/palette.ts +88 -0
  21. package/src/apps/ui/project.ts +127 -0
  22. package/src/apps/ui/standalone.ts +146 -0
  23. package/src/apps/ui/status-model.ts +68 -0
  24. package/src/apps/ui/surface.ts +84 -0
  25. package/src/apps/ui/theme.ts +86 -0
  26. package/src/core/acceptance.ts +107 -0
  27. package/src/core/action-defaults.ts +47 -295
  28. package/src/core/benchmark.ts +2 -2
  29. package/src/core/branch-receipt.ts +24 -0
  30. package/src/core/checkpoints.ts +64 -0
  31. package/src/core/churn.ts +2 -0
  32. package/src/core/command-artifacts.ts +94 -0
  33. package/src/core/command-contract.ts +35 -0
  34. package/src/core/completion-review.ts +21 -5
  35. package/src/core/context-detail.ts +37 -0
  36. package/src/core/context-policy.ts +74 -1
  37. package/src/core/continuation.ts +17 -2
  38. package/src/core/derive.ts +29 -3
  39. package/src/core/draft-images.ts +56 -0
  40. package/src/core/environment.ts +145 -0
  41. package/src/core/evaluation-coverage.ts +74 -0
  42. package/src/core/event-validation.ts +80 -5
  43. package/src/core/events.ts +97 -3
  44. package/src/core/evidence-links.ts +7 -0
  45. package/src/core/evidence.ts +13 -3
  46. package/src/core/execution.ts +8 -2
  47. package/src/core/experiment.ts +133 -0
  48. package/src/core/export.ts +9 -0
  49. package/src/core/failure-recovery.ts +173 -0
  50. package/src/core/file-precondition.ts +72 -0
  51. package/src/core/handoff.ts +65 -4
  52. package/src/core/history-query.ts +38 -2
  53. package/src/core/history.ts +38 -1
  54. package/src/core/integration-health.ts +34 -0
  55. package/src/core/keymap.ts +8 -20
  56. package/src/core/model-picker.ts +64 -0
  57. package/src/core/native-eval.ts +25 -0
  58. package/src/core/note-guidance.ts +34 -0
  59. package/src/core/observation-view.ts +49 -8
  60. package/src/core/permissions.ts +24 -2
  61. package/src/core/process-receipt.ts +105 -0
  62. package/src/core/process-state.ts +37 -0
  63. package/src/core/project.ts +45 -18
  64. package/src/core/prompt-policy.ts +37 -0
  65. package/src/core/query-eval-contract.ts +14 -0
  66. package/src/core/read-observation.ts +82 -0
  67. package/src/core/recovery-corpus.ts +17 -0
  68. package/src/core/recovery-evaluation.ts +60 -0
  69. package/src/core/recovery-sources.ts +74 -0
  70. package/src/core/runtime-target.ts +94 -0
  71. package/src/core/search-observation.ts +97 -0
  72. package/src/core/session-tree.ts +147 -0
  73. package/src/core/settings.ts +26 -10
  74. package/src/core/skills.ts +44 -0
  75. package/src/core/sql-query.ts +211 -0
  76. package/src/core/theme.ts +36 -58
  77. package/src/core/tool-contract.ts +51 -2
  78. package/src/core/tool-discovery.ts +87 -0
  79. package/src/core/tool-failure.ts +60 -0
  80. package/src/core/tool-mode-protocol.ts +25 -0
  81. package/src/core/transcript.ts +19 -4
  82. package/src/core/trial-lifecycle.ts +14 -0
  83. package/src/core/tui-mode.ts +29 -0
  84. package/src/core/update-check.ts +97 -0
  85. package/src/core/version.ts +55 -0
  86. package/src/core/work-state.ts +49 -3
  87. package/src/core/working-set.ts +91 -0
  88. package/src/runtime/auth.ts +2 -2
  89. package/src/runtime/branch-jobs.ts +36 -0
  90. package/src/runtime/clipboard.ts +15 -4
  91. package/src/runtime/command-artifacts.ts +64 -0
  92. package/src/runtime/environment-tools.ts +60 -0
  93. package/src/runtime/evidence-notes.ts +4 -2
  94. package/src/runtime/exec-sessions.ts +206 -61
  95. package/src/runtime/execution-control.ts +1 -0
  96. package/src/runtime/extension-ui.ts +22 -30
  97. package/src/runtime/extensions.ts +95 -16
  98. package/src/runtime/harness-comparison.ts +27 -7
  99. package/src/runtime/harness-eval.ts +460 -0
  100. package/src/runtime/history-cache.ts +57 -0
  101. package/src/runtime/history-tools.ts +62 -12
  102. package/src/runtime/integrations.ts +210 -0
  103. package/src/runtime/loop.ts +1021 -268
  104. package/src/runtime/model-discovery.ts +208 -0
  105. package/src/runtime/native-eval-fixtures.ts +33 -0
  106. package/src/runtime/native-eval.ts +143 -0
  107. package/src/runtime/oauth.ts +1 -1
  108. package/src/runtime/provider-connect.ts +109 -0
  109. package/src/runtime/provider-presets.ts +87 -0
  110. package/src/runtime/query-eval-verifier.ts +28 -0
  111. package/src/runtime/recovery-coverage.ts +71 -0
  112. package/src/runtime/recovery-evidence.ts +76 -0
  113. package/src/runtime/recovery-fixtures.ts +116 -0
  114. package/src/runtime/runtime-target.ts +149 -0
  115. package/src/runtime/tool-broker.ts +137 -0
  116. package/src/runtime/tool-discovery.ts +57 -0
  117. package/src/runtime/tool-mode-eval.ts +146 -0
  118. package/src/runtime/tool-queue.ts +77 -0
  119. package/src/runtime/tools.ts +628 -183
  120. package/src/runtime/trial-control.ts +45 -0
  121. package/src/runtime/update-check.ts +47 -0
  122. package/src/runtime/window-trial.ts +2 -1
  123. package/src/runtime/working-notes.ts +33 -3
  124. package/src/runtime/workspace-lifecycle.ts +97 -0
  125. package/src/store/acceptance.ts +15 -0
  126. package/src/store/checkpoint-history.ts +43 -0
  127. package/src/store/command-artifacts.ts +96 -0
  128. package/src/store/command.ts +65 -37
  129. package/src/store/file-checkpoints.ts +104 -0
  130. package/src/store/file-index.ts +13 -1
  131. package/src/store/git-checkpoints.ts +67 -0
  132. package/src/store/observations.ts +61 -20
  133. package/src/store/process-receipts.ts +34 -0
  134. package/src/store/query-source.ts +13 -8
  135. package/src/store/recovery.ts +2 -3
  136. package/src/store/replay.ts +16 -4
  137. package/src/store/runtime-target.ts +64 -0
  138. package/src/store/search.ts +171 -132
  139. package/src/store/session-archive.ts +4 -0
  140. package/src/store/source-cancellation.ts +6 -0
  141. package/src/store/update-cache.ts +36 -0
  142. package/src/store/workspace-observation.ts +113 -0
  143. package/src/tui/ansi-spans.ts +126 -0
  144. package/src/tui/style.ts +19 -16
  145. package/src/tui/ui/atomic-draft.ts +121 -0
  146. package/src/tui/ui/canvas.ts +96 -0
  147. package/src/tui/ui/colors.ts +70 -0
  148. package/src/tui/ui/composer.ts +199 -0
  149. package/src/tui/ui/dialog.ts +379 -0
  150. package/src/tui/ui/draft.ts +23 -0
  151. package/src/tui/ui/edge.ts +186 -0
  152. package/src/tui/ui/image-size.ts +40 -0
  153. package/src/tui/ui/ink.ts +210 -0
  154. package/src/tui/ui/items.ts +40 -0
  155. package/src/tui/ui/keys.ts +40 -0
  156. package/src/tui/ui/layout.ts +74 -0
  157. package/src/tui/ui/list-model.ts +86 -0
  158. package/src/tui/ui/list-view.ts +172 -0
  159. package/src/tui/ui/mascot.ts +67 -0
  160. package/src/tui/ui/messages.ts +518 -0
  161. package/src/tui/ui/motion.ts +28 -0
  162. package/src/tui/ui/paths.ts +10 -0
  163. package/src/tui/ui/pictures.ts +78 -0
  164. package/src/tui/ui/screen.ts +421 -0
  165. package/src/tui/ui/status.ts +271 -0
  166. package/src/tui/ui/styled.ts +85 -0
  167. package/src/tui/ui/testing.ts +5 -0
  168. package/src/tui/ui/tool-card.ts +150 -0
  169. package/src/apps/extension-ui.ts +0 -330
  170. package/src/apps/resource-browser.ts +0 -75
  171. package/src/apps/session-browser.ts +0 -113
  172. package/src/apps/session-picker.ts +0 -55
  173. package/src/tui/ansi.ts +0 -67
  174. package/src/tui/autocomplete.ts +0 -29
  175. package/src/tui/chat-viewport.ts +0 -47
  176. package/src/tui/complete.ts +0 -87
  177. package/src/tui/editor.ts +0 -940
  178. package/src/tui/host.ts +0 -264
  179. package/src/tui/input.ts +0 -75
  180. package/src/tui/keys.ts +0 -378
  181. package/src/tui/markdown.ts +0 -17
  182. package/src/tui/render.ts +0 -153
  183. package/src/tui/select.ts +0 -258
  184. package/src/tui/settings.ts +0 -44
  185. package/src/tui/statusline.ts +0 -162
  186. package/src/tui/system-theme.ts +0 -700
  187. package/src/tui/terminal.ts +0 -30
  188. package/src/tui/text.ts +0 -52
  189. package/src/tui/toolview.ts +0 -386
  190. package/src/tui/transcript.ts +0 -123
  191. package/src/tui/tree.ts +0 -471
  192. package/src/vendor/pi-tui/LICENSE.upstream +0 -21
  193. package/src/vendor/pi-tui/UPSTREAM.json +0 -50
  194. package/src/vendor/pi-tui/VENDOR.md +0 -25
  195. package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
  196. package/src/vendor/pi-tui/autocomplete.ts +0 -860
  197. package/src/vendor/pi-tui/colors.ts +0 -367
  198. package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
  199. package/src/vendor/pi-tui/components/box.ts +0 -166
  200. package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
  201. package/src/vendor/pi-tui/components/editor.ts +0 -2472
  202. package/src/vendor/pi-tui/components/h-stack.ts +0 -44
  203. package/src/vendor/pi-tui/components/image.ts +0 -127
  204. package/src/vendor/pi-tui/components/input.ts +0 -494
  205. package/src/vendor/pi-tui/components/loader.ts +0 -101
  206. package/src/vendor/pi-tui/components/markdown.ts +0 -1021
  207. package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
  208. package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
  209. package/src/vendor/pi-tui/components/select-list.ts +0 -273
  210. package/src/vendor/pi-tui/components/settings-list.ts +0 -328
  211. package/src/vendor/pi-tui/components/spacer.ts +0 -28
  212. package/src/vendor/pi-tui/components/stack.ts +0 -154
  213. package/src/vendor/pi-tui/components/text.ts +0 -107
  214. package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
  215. package/src/vendor/pi-tui/components/v-stack.ts +0 -33
  216. package/src/vendor/pi-tui/editor-component.ts +0 -74
  217. package/src/vendor/pi-tui/fuzzy.ts +0 -138
  218. package/src/vendor/pi-tui/index.ts +0 -186
  219. package/src/vendor/pi-tui/keybindings.ts +0 -320
  220. package/src/vendor/pi-tui/keys.ts +0 -1401
  221. package/src/vendor/pi-tui/kill-ring.ts +0 -46
  222. package/src/vendor/pi-tui/latex.ts +0 -1506
  223. package/src/vendor/pi-tui/layout-node.ts +0 -51
  224. package/src/vendor/pi-tui/layout.ts +0 -449
  225. package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
  226. package/src/vendor/pi-tui/native/clipboard.h +0 -208
  227. package/src/vendor/pi-tui/native/darwin/README.md +0 -22
  228. package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
  229. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
  230. package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
  231. package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
  232. package/src/vendor/pi-tui/native/linux/README.md +0 -23
  233. package/src/vendor/pi-tui/native/linux/build.sh +0 -65
  234. package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
  235. package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
  236. package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
  237. package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
  238. package/src/vendor/pi-tui/native/napi.h +0 -91
  239. package/src/vendor/pi-tui/native/win32/README.md +0 -31
  240. package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
  241. package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
  242. package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
  243. package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
  244. package/src/vendor/pi-tui/native-modifiers.ts +0 -13
  245. package/src/vendor/pi-tui/native-module-path.ts +0 -31
  246. package/src/vendor/pi-tui/native-platform.ts +0 -65
  247. package/src/vendor/pi-tui/oklab.ts +0 -233
  248. package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
  249. package/src/vendor/pi-tui/terminal-colors.ts +0 -91
  250. package/src/vendor/pi-tui/terminal-image.ts +0 -731
  251. package/src/vendor/pi-tui/terminal.ts +0 -554
  252. package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
  253. package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
  254. package/src/vendor/pi-tui/tui.ts +0 -1493
  255. package/src/vendor/pi-tui/undo-stack.ts +0 -28
  256. package/src/vendor/pi-tui/utils.ts +0 -1385
  257. package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
  258. 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
  }