start-command 0.41.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/package.json +3 -3
- package/src/lib/formal-ai-client.js +249 -22
- package/src/lib/formal-ai-files.js +797 -0
- package/src/lib/formal-ai-policy.js +10 -3
- package/src/lib/formal-ai.js +328 -12
- package/src/lib/log-sanitizer.js +4 -0
- package/src/lib/usage.js +3 -1
- package/test/fixtures/formal-ai/receipts-258.json +449 -0
- package/test/fixtures/formal-ai/receipts-261.json +589 -0
- package/test/fixtures/formal-ai/replay.cjs +60 -7
- package/test/regression-255-cli.js +3 -1
- package/test/regression-255-e2e.js +80 -3
- package/test/regression-255-loop.js +6 -1
- package/test/regression-255-policy.js +1 -1
- package/test/regression-257.js +265 -0
- package/test/regression-258-files.js +452 -0
- package/test/regression-258.js +456 -0
- package/test/regression-261.js +856 -0
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "captured from Formal AI 0.355.32 (08ca27d1ee0560fb56450ad798f3a79c684404ed) js agent mode by experiments/issue-261/capture-receipts.cjs",
|
|
3
|
+
"capturedAt": "2026-11-04",
|
|
4
|
+
"receipts": [
|
|
5
|
+
{
|
|
6
|
+
"name": "issue reproduction",
|
|
7
|
+
"request": "In draft.txt, replace «draft» with «verified».",
|
|
8
|
+
"effects": "workspace-write",
|
|
9
|
+
"files": {
|
|
10
|
+
"draft.txt": "draft\n",
|
|
11
|
+
"verified-source.txt": "verified\n"
|
|
12
|
+
},
|
|
13
|
+
"links": {},
|
|
14
|
+
"offered": ["bash", "read", "edit", "write"],
|
|
15
|
+
"responses": [
|
|
16
|
+
{
|
|
17
|
+
"finish_reason": "tool_calls",
|
|
18
|
+
"message": {
|
|
19
|
+
"role": "assistant",
|
|
20
|
+
"content": "Let me open draft.txt and read what it says.",
|
|
21
|
+
"tool_calls": [
|
|
22
|
+
{
|
|
23
|
+
"id": "call_2c33961d84b9a31d",
|
|
24
|
+
"type": "function",
|
|
25
|
+
"function": {
|
|
26
|
+
"name": "read",
|
|
27
|
+
"arguments": "{\"path\":\"draft.txt\"}"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"finish_reason": "tool_calls",
|
|
35
|
+
"message": {
|
|
36
|
+
"role": "assistant",
|
|
37
|
+
"content": "Let me update draft.txt for you.",
|
|
38
|
+
"tool_calls": [
|
|
39
|
+
{
|
|
40
|
+
"id": "call_7469658d98e59005",
|
|
41
|
+
"type": "function",
|
|
42
|
+
"function": {
|
|
43
|
+
"name": "edit",
|
|
44
|
+
"arguments": "{\"new_string\":\"verified\",\"old_string\":\"draft\",\"path\":\"draft.txt\"}"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"finish_reason": "tool_calls",
|
|
52
|
+
"message": {
|
|
53
|
+
"role": "assistant",
|
|
54
|
+
"content": "Let me run the requested command to get that for you.",
|
|
55
|
+
"tool_calls": [
|
|
56
|
+
{
|
|
57
|
+
"id": "call_b3ddcc9ecf22728b",
|
|
58
|
+
"type": "function",
|
|
59
|
+
"function": {
|
|
60
|
+
"name": "bash",
|
|
61
|
+
"arguments": "{\"command\":\"sha256sum -- draft.txt\"}"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"finish_reason": "stop",
|
|
69
|
+
"message": {
|
|
70
|
+
"role": "assistant",
|
|
71
|
+
"content": "Replaced `draft` with `verified` in `draft.txt` and observed the result."
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"tools": [
|
|
76
|
+
{
|
|
77
|
+
"tool": "read",
|
|
78
|
+
"command": "read draft.txt",
|
|
79
|
+
"decision": "allowed",
|
|
80
|
+
"exitCode": 0
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"tool": "edit",
|
|
84
|
+
"command": "edit draft.txt",
|
|
85
|
+
"decision": "allowed",
|
|
86
|
+
"exitCode": 0
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"tool": "bash",
|
|
90
|
+
"command": "sha256sum -- draft.txt",
|
|
91
|
+
"decision": "allowed",
|
|
92
|
+
"exitCode": 0
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"outcome": {
|
|
96
|
+
"status": "completed",
|
|
97
|
+
"exitCode": 0,
|
|
98
|
+
"message": "finished after 3 tool call(s); changed draft.txt",
|
|
99
|
+
"remainingObligations": []
|
|
100
|
+
},
|
|
101
|
+
"after": {
|
|
102
|
+
"draft.txt": "verified\n",
|
|
103
|
+
"verified-source.txt": "verified\n"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "issue reproduction, read-only",
|
|
108
|
+
"request": "In draft.txt, replace «draft» with «verified».",
|
|
109
|
+
"effects": "read-only",
|
|
110
|
+
"files": {
|
|
111
|
+
"draft.txt": "draft\n",
|
|
112
|
+
"verified-source.txt": "verified\n"
|
|
113
|
+
},
|
|
114
|
+
"links": {},
|
|
115
|
+
"offered": ["bash", "read"],
|
|
116
|
+
"responses": [
|
|
117
|
+
{
|
|
118
|
+
"finish_reason": "tool_calls",
|
|
119
|
+
"message": {
|
|
120
|
+
"role": "assistant",
|
|
121
|
+
"content": "Let me open draft.txt and read what it says.",
|
|
122
|
+
"tool_calls": [
|
|
123
|
+
{
|
|
124
|
+
"id": "call_2c33961d84b9a31d",
|
|
125
|
+
"type": "function",
|
|
126
|
+
"function": {
|
|
127
|
+
"name": "read",
|
|
128
|
+
"arguments": "{\"path\":\"draft.txt\"}"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"finish_reason": "stop",
|
|
136
|
+
"message": {
|
|
137
|
+
"role": "assistant",
|
|
138
|
+
"content": "{\"complete\":false,\"missingCapabilities\":[\"edit\"],\"reason\":\"EditToolUnavailable\",\"schema\":\"workspace-edit-capability-gap/v1\",\"target\":\"draft.txt\",\"verified\":false}"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
"tools": [
|
|
143
|
+
{
|
|
144
|
+
"tool": "read",
|
|
145
|
+
"command": "read draft.txt",
|
|
146
|
+
"decision": "allowed",
|
|
147
|
+
"exitCode": 0
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
"outcome": {
|
|
151
|
+
"status": "capability-gap",
|
|
152
|
+
"exitCode": 65,
|
|
153
|
+
"message": "Formal AI reported a capability gap for draft.txt (EditToolUnavailable; missing edit); nothing was verified as done",
|
|
154
|
+
"remainingObligations": [
|
|
155
|
+
"the requested change to draft.txt was not made or not verified",
|
|
156
|
+
"set START_FORMAL_AI_EFFECTS=workspace-write to offer the edit and write tools"
|
|
157
|
+
],
|
|
158
|
+
"gap": {
|
|
159
|
+
"schema": "workspace-edit-capability-gap/v1",
|
|
160
|
+
"target": "draft.txt",
|
|
161
|
+
"reason": "EditToolUnavailable",
|
|
162
|
+
"missingCapabilities": ["edit"],
|
|
163
|
+
"complete": false,
|
|
164
|
+
"verified": false
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"after": {
|
|
168
|
+
"draft.txt": "draft\n",
|
|
169
|
+
"verified-source.txt": "verified\n"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "mismatched OLD",
|
|
174
|
+
"request": "In mismatch.txt, replace «drafted» with «verified».",
|
|
175
|
+
"effects": "workspace-write",
|
|
176
|
+
"files": {
|
|
177
|
+
"mismatch.txt": "draft\n",
|
|
178
|
+
"verified-source.txt": "verified\n"
|
|
179
|
+
},
|
|
180
|
+
"links": {},
|
|
181
|
+
"offered": ["bash", "read", "edit", "write"],
|
|
182
|
+
"responses": [
|
|
183
|
+
{
|
|
184
|
+
"finish_reason": "tool_calls",
|
|
185
|
+
"message": {
|
|
186
|
+
"role": "assistant",
|
|
187
|
+
"content": "Let me open mismatch.txt and read what it says.",
|
|
188
|
+
"tool_calls": [
|
|
189
|
+
{
|
|
190
|
+
"id": "call_fd4368a9e9ad0e50",
|
|
191
|
+
"type": "function",
|
|
192
|
+
"function": {
|
|
193
|
+
"name": "read",
|
|
194
|
+
"arguments": "{\"path\":\"mismatch.txt\"}"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"finish_reason": "stop",
|
|
202
|
+
"message": {
|
|
203
|
+
"role": "assistant",
|
|
204
|
+
"content": "`drafted` does not occur in `mismatch.txt`, so nothing was changed."
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
],
|
|
208
|
+
"tools": [
|
|
209
|
+
{
|
|
210
|
+
"tool": "read",
|
|
211
|
+
"command": "read mismatch.txt",
|
|
212
|
+
"decision": "allowed",
|
|
213
|
+
"exitCode": 0
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
"outcome": {
|
|
217
|
+
"status": "completed",
|
|
218
|
+
"exitCode": 0,
|
|
219
|
+
"message": "finished after 1 tool call(s); no file changed",
|
|
220
|
+
"remainingObligations": []
|
|
221
|
+
},
|
|
222
|
+
"after": {
|
|
223
|
+
"mismatch.txt": "draft\n",
|
|
224
|
+
"verified-source.txt": "verified\n"
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"name": "repeated occurrences",
|
|
229
|
+
"request": "In twice.txt, replace «draft» with «verified».",
|
|
230
|
+
"effects": "workspace-write",
|
|
231
|
+
"files": {
|
|
232
|
+
"twice.txt": "draft one\ndraft two\n"
|
|
233
|
+
},
|
|
234
|
+
"links": {},
|
|
235
|
+
"offered": ["bash", "read", "edit", "write"],
|
|
236
|
+
"responses": [
|
|
237
|
+
{
|
|
238
|
+
"finish_reason": "tool_calls",
|
|
239
|
+
"message": {
|
|
240
|
+
"role": "assistant",
|
|
241
|
+
"content": "Let me open twice.txt and read what it says.",
|
|
242
|
+
"tool_calls": [
|
|
243
|
+
{
|
|
244
|
+
"id": "call_08ec81c4f930c1cd",
|
|
245
|
+
"type": "function",
|
|
246
|
+
"function": {
|
|
247
|
+
"name": "read",
|
|
248
|
+
"arguments": "{\"path\":\"twice.txt\"}"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"finish_reason": "tool_calls",
|
|
256
|
+
"message": {
|
|
257
|
+
"role": "assistant",
|
|
258
|
+
"content": "Let me run the requested command to get that for you.",
|
|
259
|
+
"tool_calls": [
|
|
260
|
+
{
|
|
261
|
+
"id": "call_c93ac09bdb199918",
|
|
262
|
+
"type": "function",
|
|
263
|
+
"function": {
|
|
264
|
+
"name": "bash",
|
|
265
|
+
"arguments": "{\"command\":\"perl -pi -e 's/draft/verified/g' -- twice.txt\"}"
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
"tools": [
|
|
273
|
+
{
|
|
274
|
+
"tool": "read",
|
|
275
|
+
"command": "read twice.txt",
|
|
276
|
+
"decision": "allowed",
|
|
277
|
+
"exitCode": 0
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"tool": "bash",
|
|
281
|
+
"command": "perl -pi -e 's/draft/verified/g' -- twice.txt",
|
|
282
|
+
"decision": "refused",
|
|
283
|
+
"exitCode": null
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
"outcome": {
|
|
287
|
+
"status": "refused",
|
|
288
|
+
"exitCode": 77,
|
|
289
|
+
"message": "refused `perl -pi -e 's/draft/verified/g' -- twice.txt`: program \"perl\" is not in the supported tool set",
|
|
290
|
+
"remainingObligations": [
|
|
291
|
+
"the refused call did not run; nothing after it ran either"
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
"after": {
|
|
295
|
+
"twice.txt": "draft one\ndraft two\n"
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"name": "Unicode and CRLF",
|
|
300
|
+
"request": "In unicode.txt, replace «café» with «naïve».",
|
|
301
|
+
"effects": "workspace-write",
|
|
302
|
+
"files": {
|
|
303
|
+
"unicode.txt": "Ünïcödé café ☕\r\nsecond line 🙂\r\nno final newline"
|
|
304
|
+
},
|
|
305
|
+
"links": {},
|
|
306
|
+
"offered": ["bash", "read", "edit", "write"],
|
|
307
|
+
"responses": [
|
|
308
|
+
{
|
|
309
|
+
"finish_reason": "tool_calls",
|
|
310
|
+
"message": {
|
|
311
|
+
"role": "assistant",
|
|
312
|
+
"content": "Let me open unicode.txt and read what it says.",
|
|
313
|
+
"tool_calls": [
|
|
314
|
+
{
|
|
315
|
+
"id": "call_751c11cbd746b338",
|
|
316
|
+
"type": "function",
|
|
317
|
+
"function": {
|
|
318
|
+
"name": "read",
|
|
319
|
+
"arguments": "{\"path\":\"unicode.txt\"}"
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
"finish_reason": "tool_calls",
|
|
327
|
+
"message": {
|
|
328
|
+
"role": "assistant",
|
|
329
|
+
"content": "Let me update unicode.txt for you.",
|
|
330
|
+
"tool_calls": [
|
|
331
|
+
{
|
|
332
|
+
"id": "call_68431c4da851607b",
|
|
333
|
+
"type": "function",
|
|
334
|
+
"function": {
|
|
335
|
+
"name": "edit",
|
|
336
|
+
"arguments": "{\"new_string\":\"naïve\",\"old_string\":\"café\",\"path\":\"unicode.txt\"}"
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"finish_reason": "tool_calls",
|
|
344
|
+
"message": {
|
|
345
|
+
"role": "assistant",
|
|
346
|
+
"content": "Let me run the requested command to get that for you.",
|
|
347
|
+
"tool_calls": [
|
|
348
|
+
{
|
|
349
|
+
"id": "call_b1f11e74e8e7bd46",
|
|
350
|
+
"type": "function",
|
|
351
|
+
"function": {
|
|
352
|
+
"name": "bash",
|
|
353
|
+
"arguments": "{\"command\":\"sha256sum -- unicode.txt\"}"
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
]
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
"finish_reason": "stop",
|
|
361
|
+
"message": {
|
|
362
|
+
"role": "assistant",
|
|
363
|
+
"content": "Replaced `café` with `naïve` in `unicode.txt` and observed the result."
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
],
|
|
367
|
+
"tools": [
|
|
368
|
+
{
|
|
369
|
+
"tool": "read",
|
|
370
|
+
"command": "read unicode.txt",
|
|
371
|
+
"decision": "allowed",
|
|
372
|
+
"exitCode": 0
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
"tool": "edit",
|
|
376
|
+
"command": "edit unicode.txt",
|
|
377
|
+
"decision": "allowed",
|
|
378
|
+
"exitCode": 0
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
"tool": "bash",
|
|
382
|
+
"command": "sha256sum -- unicode.txt",
|
|
383
|
+
"decision": "allowed",
|
|
384
|
+
"exitCode": 0
|
|
385
|
+
}
|
|
386
|
+
],
|
|
387
|
+
"outcome": {
|
|
388
|
+
"status": "completed",
|
|
389
|
+
"exitCode": 0,
|
|
390
|
+
"message": "finished after 3 tool call(s); changed unicode.txt",
|
|
391
|
+
"remainingObligations": []
|
|
392
|
+
},
|
|
393
|
+
"after": {
|
|
394
|
+
"unicode.txt": "Ünïcödé naïve ☕\r\nsecond line 🙂\r\nno final newline"
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"name": "symlinked path",
|
|
399
|
+
"request": "In link.txt, replace «draft» with «verified».",
|
|
400
|
+
"effects": "workspace-write",
|
|
401
|
+
"files": {
|
|
402
|
+
"real.txt": "draft\n"
|
|
403
|
+
},
|
|
404
|
+
"links": {
|
|
405
|
+
"link.txt": "real.txt"
|
|
406
|
+
},
|
|
407
|
+
"offered": ["bash", "read", "edit", "write"],
|
|
408
|
+
"responses": [
|
|
409
|
+
{
|
|
410
|
+
"finish_reason": "tool_calls",
|
|
411
|
+
"message": {
|
|
412
|
+
"role": "assistant",
|
|
413
|
+
"content": "Let me open link.txt and read what it says.",
|
|
414
|
+
"tool_calls": [
|
|
415
|
+
{
|
|
416
|
+
"id": "call_f1b2a529ea88b28d",
|
|
417
|
+
"type": "function",
|
|
418
|
+
"function": {
|
|
419
|
+
"name": "read",
|
|
420
|
+
"arguments": "{\"path\":\"link.txt\"}"
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
]
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
"finish_reason": "tool_calls",
|
|
428
|
+
"message": {
|
|
429
|
+
"role": "assistant",
|
|
430
|
+
"content": "Let me update link.txt for you.",
|
|
431
|
+
"tool_calls": [
|
|
432
|
+
{
|
|
433
|
+
"id": "call_f4e6366385e51cc7",
|
|
434
|
+
"type": "function",
|
|
435
|
+
"function": {
|
|
436
|
+
"name": "edit",
|
|
437
|
+
"arguments": "{\"new_string\":\"verified\",\"old_string\":\"draft\",\"path\":\"link.txt\"}"
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
]
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
"finish_reason": "tool_calls",
|
|
445
|
+
"message": {
|
|
446
|
+
"role": "assistant",
|
|
447
|
+
"content": "Let me run the requested command to get that for you.",
|
|
448
|
+
"tool_calls": [
|
|
449
|
+
{
|
|
450
|
+
"id": "call_0e57dc5d4eebde9b",
|
|
451
|
+
"type": "function",
|
|
452
|
+
"function": {
|
|
453
|
+
"name": "bash",
|
|
454
|
+
"arguments": "{\"command\":\"sha256sum -- link.txt\"}"
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
]
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
"finish_reason": "stop",
|
|
462
|
+
"message": {
|
|
463
|
+
"role": "assistant",
|
|
464
|
+
"content": "Replaced `draft` with `verified` in `link.txt` and observed the result."
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
],
|
|
468
|
+
"tools": [
|
|
469
|
+
{
|
|
470
|
+
"tool": "read",
|
|
471
|
+
"command": "read link.txt",
|
|
472
|
+
"decision": "allowed",
|
|
473
|
+
"exitCode": 0
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
"tool": "edit",
|
|
477
|
+
"command": "edit link.txt",
|
|
478
|
+
"decision": "allowed",
|
|
479
|
+
"exitCode": 0
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"tool": "bash",
|
|
483
|
+
"command": "sha256sum -- link.txt",
|
|
484
|
+
"decision": "allowed",
|
|
485
|
+
"exitCode": 0
|
|
486
|
+
}
|
|
487
|
+
],
|
|
488
|
+
"outcome": {
|
|
489
|
+
"status": "completed",
|
|
490
|
+
"exitCode": 0,
|
|
491
|
+
"message": "finished after 3 tool call(s); changed real.txt",
|
|
492
|
+
"remainingObligations": []
|
|
493
|
+
},
|
|
494
|
+
"after": {
|
|
495
|
+
"real.txt": "verified\n"
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
"name": "symlink leaving the workspace",
|
|
500
|
+
"request": "In escape.txt, replace «draft» with «verified».",
|
|
501
|
+
"effects": "workspace-write",
|
|
502
|
+
"files": {
|
|
503
|
+
"../outside.txt": "draft\n"
|
|
504
|
+
},
|
|
505
|
+
"links": {
|
|
506
|
+
"escape.txt": "../outside.txt"
|
|
507
|
+
},
|
|
508
|
+
"offered": ["bash", "read", "edit", "write"],
|
|
509
|
+
"responses": [
|
|
510
|
+
{
|
|
511
|
+
"finish_reason": "tool_calls",
|
|
512
|
+
"message": {
|
|
513
|
+
"role": "assistant",
|
|
514
|
+
"content": "Let me open escape.txt and read what it says.",
|
|
515
|
+
"tool_calls": [
|
|
516
|
+
{
|
|
517
|
+
"id": "call_f63acc2df7065c9f",
|
|
518
|
+
"type": "function",
|
|
519
|
+
"function": {
|
|
520
|
+
"name": "read",
|
|
521
|
+
"arguments": "{\"path\":\"escape.txt\"}"
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
]
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
],
|
|
528
|
+
"tools": [
|
|
529
|
+
{
|
|
530
|
+
"tool": "read",
|
|
531
|
+
"command": "read escape.txt",
|
|
532
|
+
"decision": "refused",
|
|
533
|
+
"exitCode": null
|
|
534
|
+
}
|
|
535
|
+
],
|
|
536
|
+
"outcome": {
|
|
537
|
+
"status": "refused",
|
|
538
|
+
"exitCode": 77,
|
|
539
|
+
"message": "refused `read escape.txt`: \"escape.txt\" is outside the working directory",
|
|
540
|
+
"remainingObligations": [
|
|
541
|
+
"the refused call did not run; nothing after it ran either"
|
|
542
|
+
]
|
|
543
|
+
},
|
|
544
|
+
"after": {
|
|
545
|
+
"../outside.txt": "draft\n"
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
"name": "foreign path",
|
|
550
|
+
"request": "In ../outside.txt, replace «draft» with «verified».",
|
|
551
|
+
"effects": "workspace-write",
|
|
552
|
+
"files": {
|
|
553
|
+
"draft.txt": "draft\n",
|
|
554
|
+
"verified-source.txt": "verified\n",
|
|
555
|
+
"../outside.txt": "draft\n"
|
|
556
|
+
},
|
|
557
|
+
"links": {},
|
|
558
|
+
"offered": ["bash", "read", "edit", "write"],
|
|
559
|
+
"responses": [
|
|
560
|
+
{
|
|
561
|
+
"finish_reason": "stop",
|
|
562
|
+
"message": {
|
|
563
|
+
"role": "assistant",
|
|
564
|
+
"content": "obligation_7d79209116af07c0\n record_type \"obligation_gap\"\n clause \"In .\"\n span_start \"0\"\n span_end \"4\"\n reason \"no_artifact_in_clause\""
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
],
|
|
568
|
+
"tools": [],
|
|
569
|
+
"outcome": {
|
|
570
|
+
"status": "incomplete",
|
|
571
|
+
"exitCode": 65,
|
|
572
|
+
"message": "Formal AI reported obligation_gap (no_artifact_in_clause) for the clause \"In .\"; nothing was verified as done by Formal AI; no file changed",
|
|
573
|
+
"remainingObligations": [
|
|
574
|
+
"Formal AI did not confirm the requested change"
|
|
575
|
+
],
|
|
576
|
+
"gapRecord": {
|
|
577
|
+
"schema": "obligation_gap",
|
|
578
|
+
"reason": "no_artifact_in_clause",
|
|
579
|
+
"clause": "In ."
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
"after": {
|
|
583
|
+
"draft.txt": "draft\n",
|
|
584
|
+
"verified-source.txt": "verified\n",
|
|
585
|
+
"../outside.txt": "draft\n"
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
]
|
|
589
|
+
}
|
|
@@ -6,16 +6,31 @@
|
|
|
6
6
|
* is the number of assistant turns already in the conversation. Unknown
|
|
7
7
|
* requests get the runtime's "did not understand" reply. SYNTHETIC scenarios
|
|
8
8
|
* (requests starting with `synthetic:`) cover states a real runtime cannot be
|
|
9
|
-
* made to produce on demand. A number reply is an HTTP error status
|
|
9
|
+
* made to produce on demand. A number reply is an HTTP error status, and a
|
|
10
|
+
* `{status, text}` reply an HTTP error with that exact body.
|
|
11
|
+
*
|
|
12
|
+
* Receipts of issue #258 (receipts-258.json) also record the tool names start
|
|
13
|
+
* offered; they answer only a request offering exactly those tools, so the
|
|
14
|
+
* released Bash-only contract and the structured file tools replay apart.
|
|
15
|
+
* The held-out edits of issue #261 (receipts-261.json) replay the same way.
|
|
10
16
|
*/
|
|
11
17
|
|
|
12
18
|
const fs = require('fs');
|
|
13
19
|
const http = require('http');
|
|
14
20
|
const path = require('path');
|
|
15
21
|
|
|
16
|
-
const
|
|
17
|
-
fs.readFileSync(path.join(__dirname,
|
|
18
|
-
);
|
|
22
|
+
const load = (name) =>
|
|
23
|
+
JSON.parse(fs.readFileSync(path.join(__dirname, name), 'utf8'));
|
|
24
|
+
const { receipts } = load('receipts.json');
|
|
25
|
+
const edits = load('receipts-258.json');
|
|
26
|
+
const heldOut = load('receipts-261.json');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The HTTP 500 body a released start discarded (issue #257), captured from a
|
|
30
|
+
* Formal AI 0.355.28 runtime whose local policy forbids its Rust grammar WASM.
|
|
31
|
+
*/
|
|
32
|
+
const CAPTURED_500 =
|
|
33
|
+
'{"error":{"message":"Aborted(Error: LocalProductWasmForbidden). Build with -sASSERTIONS for more info.","type":"formal_ai_error"}}';
|
|
19
34
|
|
|
20
35
|
function toolCall(command, id = `call_${Math.random().toString(16).slice(2)}`) {
|
|
21
36
|
return {
|
|
@@ -34,7 +49,8 @@ function toolCall(command, id = `call_${Math.random().toString(16).slice(2)}`) {
|
|
|
34
49
|
const answer = (content) => ({ role: 'assistant', content });
|
|
35
50
|
|
|
36
51
|
/**
|
|
37
|
-
* SYNTHETIC scenarios: request -> (assistantTurns, messages) ->
|
|
52
|
+
* SYNTHETIC scenarios: request -> (assistantTurns, messages, body) ->
|
|
53
|
+
* message | status | {status, text}.
|
|
38
54
|
* A constant list matched by string equality: request text only selects among
|
|
39
55
|
* these entries and is never used as a property key, so `constructor`,
|
|
40
56
|
* `toString` and similar names reach the "did not understand" reply.
|
|
@@ -66,6 +82,21 @@ const SYNTHETIC = Object.freeze(
|
|
|
66
82
|
'synthetic: write outside': () => toolCall('touch ../escape.txt'),
|
|
67
83
|
// A server error on every request.
|
|
68
84
|
'synthetic: server error': () => 500,
|
|
85
|
+
// The captured runtime failure of issue #257 on every request.
|
|
86
|
+
'synthetic: server error (captured #257)': () => ({
|
|
87
|
+
status: 500,
|
|
88
|
+
text: CAPTURED_500,
|
|
89
|
+
}),
|
|
90
|
+
// A 503 whose body echoes credentials and control characters.
|
|
91
|
+
'synthetic: server error with secrets': () => ({
|
|
92
|
+
status: 503,
|
|
93
|
+
text: JSON.stringify({
|
|
94
|
+
error: {
|
|
95
|
+
type: 'upstream_error',
|
|
96
|
+
message: `proxy refused Authorization: Bearer sk-test-0123456789\u0007 api_key=sk-live-abcdef`,
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
}),
|
|
69
100
|
// A write that claims success but leaves no trace (fake `mkdir` on PATH).
|
|
70
101
|
'synthetic: unverified mkdir': (turns) =>
|
|
71
102
|
turns === 0 ? toolCall('test ! -e out') : toolCall('mkdir out'),
|
|
@@ -79,7 +110,16 @@ function reply(body) {
|
|
|
79
110
|
const scenario = SYNTHETIC.find(([name]) => name === request);
|
|
80
111
|
if (scenario) {
|
|
81
112
|
const [, respond] = scenario;
|
|
82
|
-
return respond(turns, messages);
|
|
113
|
+
return respond(turns, messages, body);
|
|
114
|
+
}
|
|
115
|
+
const offered = (Array.isArray(body.tools) ? body.tools : [])
|
|
116
|
+
.map((tool) => tool && tool.function && tool.function.name)
|
|
117
|
+
.join(',');
|
|
118
|
+
const edit = [...edits.receipts, ...heldOut.receipts].find(
|
|
119
|
+
(r) => r.request === request && r.offered.join(',') === offered
|
|
120
|
+
);
|
|
121
|
+
if (edit && edit.responses[turns]) {
|
|
122
|
+
return edit.responses[turns].message;
|
|
83
123
|
}
|
|
84
124
|
const receipt = receipts.find((r) => r.request === request);
|
|
85
125
|
if (receipt && receipt.responses[turns]) {
|
|
@@ -125,6 +165,11 @@ function createFakeServer(log) {
|
|
|
125
165
|
send(message, { error: 'synthetic failure' });
|
|
126
166
|
return;
|
|
127
167
|
}
|
|
168
|
+
if (typeof message.status === 'number') {
|
|
169
|
+
res.writeHead(message.status, { 'content-type': 'application/json' });
|
|
170
|
+
res.end(message.text);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
128
173
|
send(200, {
|
|
129
174
|
id: 'chatcmpl_fake',
|
|
130
175
|
object: 'chat.completion',
|
|
@@ -141,4 +186,12 @@ function createFakeServer(log) {
|
|
|
141
186
|
});
|
|
142
187
|
}
|
|
143
188
|
|
|
144
|
-
module.exports = {
|
|
189
|
+
module.exports = {
|
|
190
|
+
CAPTURED_500,
|
|
191
|
+
SYNTHETIC,
|
|
192
|
+
createFakeServer,
|
|
193
|
+
edits,
|
|
194
|
+
heldOut,
|
|
195
|
+
receipts,
|
|
196
|
+
reply,
|
|
197
|
+
};
|