agentbox-app 0.1.7 → 0.1.11
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/README.md +209 -55
- package/bin/agentbox.mjs +6 -0
- package/briefs/message-rules.md +13 -3
- package/briefs/worker.md +63 -49
- package/briefs/writing-rules.md +90 -131
- package/main/account-discovery.mjs +21 -20
- package/main/account-tooling.mjs +9 -10
- package/main/agent-activity.mjs +45 -5
- package/main/agent-schedule.mjs +14 -14
- package/main/agent-sessions.mjs +94 -10
- package/main/agent-updates.mjs +114 -0
- package/main/agents.mjs +52 -46
- package/main/analytics.mjs +27 -0
- package/main/approval-prompt-server.mjs +2 -2
- package/main/artifact-path.mjs +11 -0
- package/main/claude-bin.mjs +31 -5
- package/main/claude-input.mjs +45 -3
- package/main/claude-plan.mjs +11 -11
- package/main/code-change.mjs +3 -3
- package/main/codex-account.mjs +1 -1
- package/main/codex-bin.mjs +17 -0
- package/main/codex-memory-gate.mjs +260 -0
- package/main/codex-models.mjs +12 -7
- package/main/codex-threads.mjs +46 -23
- package/main/codex-usage-file.mjs +7 -7
- package/main/codex-usage.mjs +4 -4
- package/main/codex-watch.mjs +12 -12
- package/main/config.mjs +68 -8
- package/main/dev-window.mjs +9 -9
- package/main/doc-scheme.mjs +2 -0
- package/main/engine-setup.mjs +193 -0
- package/main/feedback.mjs +55 -0
- package/main/folder-pictures.mjs +48 -0
- package/main/frame-scrollbars.mjs +90 -0
- package/main/fresh-user.mjs +6 -3
- package/main/git-change-offthread.mjs +70 -0
- package/main/git-change-thread.mjs +11 -0
- package/main/git-change.mjs +1 -1
- package/main/img-scheme.mjs +70 -1
- package/main/instruction-settings.mjs +92 -54
- package/main/ipc.mjs +316 -42
- package/main/leftovers.mjs +518 -0
- package/main/live-replies.mjs +43 -5
- package/main/machine.mjs +47 -0
- package/main/main.mjs +119 -15
- package/main/memory-gate-history.mjs +254 -0
- package/main/memory-gate-server.mjs +461 -0
- package/main/memory-gate.mjs +206 -0
- package/main/message-priority.mjs +87 -0
- package/main/rail-note.mjs +2 -2
- package/main/refresh-agent-models.mjs +20 -0
- package/main/repeats.mjs +4 -4
- package/main/row-label.mjs +114 -51
- package/main/serve.mjs +67 -5
- package/main/settings.mjs +223 -26
- package/main/ship-queue.mjs +279 -0
- package/main/sign-in-files.mjs +39 -0
- package/main/source-updater.mjs +298 -0
- package/main/store/home.mjs +47 -2
- package/main/store/work-items.mjs +150 -6
- package/main/store.mjs +235 -9
- package/main/supervisor.mjs +1444 -406
- package/main/task-commands.mjs +8 -10
- package/main/task-folders.mjs +74 -13
- package/main/task-remote-control.mjs +3 -3
- package/main/task-terminals.mjs +2 -1
- package/main/team/index.mjs +440 -0
- package/main/team/memory-cloud.mjs +247 -0
- package/main/team/projects.mjs +162 -0
- package/main/team/session.mjs +225 -0
- package/main/team/sign-in.mjs +106 -0
- package/main/team/supabase-backend.mjs +231 -0
- package/main/team/sync.mjs +163 -0
- package/main/updater.mjs +15 -5
- package/main/write-before-reload.mjs +50 -0
- package/mcp/core/page.mjs +9 -11
- package/mcp/core/work.mjs +91 -25
- package/mcp/tools.mjs +12 -6
- package/package.json +21 -9
- package/renderer/dist/assets/agentbox-icon-F1oN9nj_.svg +15 -0
- package/renderer/dist/assets/index-B-2Az952.css +32 -0
- package/renderer/dist/assets/index-D6qncgai.js +1168 -0
- package/renderer/dist/index.html +2 -2
- package/scripts/memory-gate-hook.sh +59 -0
- package/shared/agent-import.mjs +38 -15
- package/shared/agents.d.mts +26 -8
- package/shared/agents.mjs +100 -108
- package/shared/analytics-events.mjs +19 -2
- package/shared/answers.mjs +12 -0
- package/shared/bridge-map.d.mts +12 -0
- package/shared/bridge-map.mjs +37 -1
- package/shared/claude-commands.generated.mjs +2 -2
- package/shared/claude-models.generated.mjs +4 -4
- package/shared/claude-usage.mjs +6 -6
- package/shared/codex-import.mjs +7 -7
- package/shared/codex-modes.d.mts +50 -0
- package/shared/codex-usage.mjs +7 -8
- package/shared/demo-world.mjs +5 -5
- package/shared/engines.mjs +27 -10
- package/shared/feedback.d.mts +14 -0
- package/shared/feedback.mjs +44 -0
- package/shared/first-run-examples.mjs +4 -4
- package/shared/first-run-practice.d.mts +16 -0
- package/shared/first-run-practice.mjs +176 -70
- package/shared/fresh-user-home.mjs +22 -3
- package/shared/instruction-defaults.json +2 -2
- package/shared/product-name.mjs +4 -2
- package/shared/project-folder-check.mjs +6 -0
- package/shared/rank.d.mts +1 -0
- package/shared/rank.mjs +22 -0
- package/shared/referenced-files.mjs +1 -1
- package/shared/schemes.d.mts +11 -0
- package/shared/schemes.mjs +1 -1
- package/shared/shortcuts.d.mts +41 -0
- package/shared/shortcuts.mjs +236 -0
- package/shared/spawn-trouble.d.mts +2 -0
- package/shared/spawn-trouble.mjs +39 -18
- package/shared/team-rules.d.mts +17 -0
- package/shared/team-rules.mjs +230 -0
- package/shared/team-status.d.mts +11 -0
- package/shared/team-status.mjs +90 -0
- package/shared/terminal-links.d.mts +2 -0
- package/shared/terminal-links.mjs +55 -0
- package/shared/terminal-state.d.mts +1 -0
- package/shared/terminal-state.mjs +16 -0
- package/shared/thread-cards.d.mts +19 -0
- package/shared/thread-cards.mjs +254 -0
- package/shared/usage.d.mts +1 -1
- package/shared/usage.mjs +6 -7
- package/shared/work-items.mjs +255 -27
- package/shared/work-lines.mjs +393 -16
- package/worker-permissions.json +1 -1
- package/renderer/dist/assets/cel-dusk-CS5I7kJS.webp +0 -0
- package/renderer/dist/assets/cel-dusk-DUslqS6B.webp +0 -0
- package/renderer/dist/assets/cel-dusk-Jt_xVGxp.webp +0 -0
- package/renderer/dist/assets/cel-dusk-sm-BBkV-tWT.webp +0 -0
- package/renderer/dist/assets/frost-haze-CxPSOBEg.webp +0 -0
- package/renderer/dist/assets/frost-haze-sm-C_w21qgd.webp +0 -0
- package/renderer/dist/assets/ice-BC8luINc.webp +0 -0
- package/renderer/dist/assets/ice-CP9PrKHo.webp +0 -0
- package/renderer/dist/assets/ice-river-D6ziOhLY.webp +0 -0
- package/renderer/dist/assets/ice-river-UVM2-JY_.webp +0 -0
- package/renderer/dist/assets/ice-river-bErJwYWW.webp +0 -0
- package/renderer/dist/assets/ice-river-sm-Bg1U-SIj.webp +0 -0
- package/renderer/dist/assets/ice-sm-CbVcg3_D.webp +0 -0
- package/renderer/dist/assets/index-B7hhfHrT.js +0 -684
- package/renderer/dist/assets/index-CwiDv5bL.css +0 -32
- package/renderer/dist/assets/lake-C0WaW-6E.webp +0 -0
- package/renderer/dist/assets/lake-CSKjLC5K.webp +0 -0
- package/renderer/dist/assets/lake-ETGcOb25.webp +0 -0
- package/renderer/dist/assets/lake-harbour-BCQRbrHb.webp +0 -0
- package/renderer/dist/assets/lake-harbour-B_wfXTmE.webp +0 -0
- package/renderer/dist/assets/lake-harbour-Te1pEvBh.webp +0 -0
- package/renderer/dist/assets/lake-harbour-sm-Cme_ShEg.webp +0 -0
- package/renderer/dist/assets/lake-sm-CbG72Hbv.webp +0 -0
- package/renderer/dist/assets/lino-coast-CUZHcucI.webp +0 -0
- package/renderer/dist/assets/lino-coast-DP4J7cgd.webp +0 -0
- package/renderer/dist/assets/lino-coast-kOIAffr_.webp +0 -0
- package/renderer/dist/assets/lino-coast-sm-C1Mdbgit.webp +0 -0
- package/renderer/dist/assets/moon-pines-BskgnnXR.webp +0 -0
- package/renderer/dist/assets/moon-pines-CFSIIwt0.webp +0 -0
- package/renderer/dist/assets/moon-pines-PNhdOYww.webp +0 -0
- package/renderer/dist/assets/moon-pines-sm-C6K1NomK.webp +0 -0
- package/renderer/dist/assets/orbit-rings-2XV36Ch3.webp +0 -0
- package/renderer/dist/assets/orbit-rings-C4NrcYGQ.webp +0 -0
- package/renderer/dist/assets/orbit-rings-DGLk07dT.webp +0 -0
- package/renderer/dist/assets/orbit-rings-sm-Dky3YdvL.webp +0 -0
- package/renderer/dist/assets/peach-haze-2-C6f7xnWw.webp +0 -0
- package/renderer/dist/assets/peach-haze-2-sm-Dj_Ez_Sb.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-DQjqMROg.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-Dw71H_bc.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-kOZfm-GM.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-sm-Dturjuky.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-C296JVMO.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-C4oMRDSx.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-CqVmQ4eh.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-sm-CHagC_nO.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-C6LYu6uE.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-CIUCEESs.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-sm-BnE0F_w-.webp +0 -0
- package/renderer/dist/assets/riso-hills-7heKVtry.webp +0 -0
- package/renderer/dist/assets/riso-hills-CIjAL3J6.webp +0 -0
- package/renderer/dist/assets/riso-hills-sm-DHsnbCfP.webp +0 -0
- package/renderer/dist/assets/slate-haze-BPWdkNsw.webp +0 -0
- package/renderer/dist/assets/slate-haze-sm-B0Cb5a2W.webp +0 -0
- package/renderer/dist/assets/valley-haze-BDHPqoJy.webp +0 -0
- package/renderer/dist/assets/valley-haze-sm-CTyjmPAG.webp +0 -0
- package/renderer/dist/assets/vector-dunes-DzzWLdKo.webp +0 -0
- package/renderer/dist/assets/vector-dunes-Yr9NBQUy.webp +0 -0
- package/renderer/dist/assets/vector-dunes-sm-D8MYY6ms.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-BCGKN4u4.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-BF-jk5MC.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-sm-Ds1xzTw9.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-B1um67Z9.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-DKQdYs32.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-sm-BJzNq8xh.webp +0 -0
- package/renderer/dist/emulator/audio-rules.mjs +0 -74
- package/renderer/dist/emulator/data/compression/README.md +0 -11
- package/renderer/dist/emulator/data/compression/extract7z.js +0 -1
- package/renderer/dist/emulator/data/compression/extractzip.js +0 -1
- package/renderer/dist/emulator/data/compression/libunrar.js +0 -519
- package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
- package/renderer/dist/emulator/data/cores/README.md +0 -22
- package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/package.json +0 -65
- package/renderer/dist/emulator/data/cores/reports/mgba.json +0 -1
- package/renderer/dist/emulator/data/emulator.css +0 -1614
- package/renderer/dist/emulator/data/loader.js +0 -170
- package/renderer/dist/emulator/data/localization/README.md +0 -63
- package/renderer/dist/emulator/data/localization/af-FR.json +0 -339
- package/renderer/dist/emulator/data/localization/ar-AR.json +0 -301
- package/renderer/dist/emulator/data/localization/ben-BEN.json +0 -301
- package/renderer/dist/emulator/data/localization/de-GER.json +0 -301
- package/renderer/dist/emulator/data/localization/el-GR.json +0 -301
- package/renderer/dist/emulator/data/localization/en-US.json +0 -339
- package/renderer/dist/emulator/data/localization/es-ES.json +0 -310
- package/renderer/dist/emulator/data/localization/fa-AF.json +0 -310
- package/renderer/dist/emulator/data/localization/hi-HI.json +0 -301
- package/renderer/dist/emulator/data/localization/it-IT.json +0 -302
- package/renderer/dist/emulator/data/localization/ja-JA.json +0 -301
- package/renderer/dist/emulator/data/localization/jv-JV.json +0 -301
- package/renderer/dist/emulator/data/localization/ko-KO.json +0 -336
- package/renderer/dist/emulator/data/localization/pt-BR.json +0 -361
- package/renderer/dist/emulator/data/localization/retroarch.json +0 -617
- package/renderer/dist/emulator/data/localization/ro-RO.json +0 -310
- package/renderer/dist/emulator/data/localization/ru-RU.json +0 -301
- package/renderer/dist/emulator/data/localization/tr-TR.json +0 -310
- package/renderer/dist/emulator/data/localization/vi-VN.json +0 -302
- package/renderer/dist/emulator/data/localization/zh-CN.json +0 -351
- package/renderer/dist/emulator/data/src/GameManager.js +0 -464
- package/renderer/dist/emulator/data/src/compression.js +0 -142
- package/renderer/dist/emulator/data/src/emulator.js +0 -6158
- package/renderer/dist/emulator/data/src/gamepad.js +0 -167
- package/renderer/dist/emulator/data/src/nipplejs.js +0 -1
- package/renderer/dist/emulator/data/src/shaders.js +0 -236
- package/renderer/dist/emulator/data/src/socket.io.min.js +0 -7
- package/renderer/dist/emulator/data/src/storage.js +0 -125
- package/renderer/dist/emulator/data/version.json +0 -5
- package/renderer/dist/emulator/host.js +0 -299
- package/renderer/dist/emulator/index.html +0 -28
- package/renderer/dist/emulator/provenance.json +0 -6
package/shared/work-lines.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// PURE. WHAT A TOOL CALL SAYS ON
|
|
1
|
+
// PURE. WHAT A TOOL CALL SAYS ON SCREEN.
|
|
2
2
|
//
|
|
3
|
-
// WHAT MESSY ACTUALLY WAS, measured over
|
|
4
|
-
//
|
|
3
|
+
// WHAT MESSY ACTUALLY WAS, measured over seven live imported sessions, off the
|
|
4
|
+
// drawn screen and not off the source:
|
|
5
5
|
//
|
|
6
6
|
// 535 work lines in 162 runs 328 of them (61%) ran past the end of their box
|
|
7
|
-
// and were cut by the ellipsis, so
|
|
7
|
+
// and were cut by the ellipsis, so they could not be read at all 368 of them
|
|
8
8
|
// (69%) were over sixty characters the six worst showed 2% to 4% of
|
|
9
9
|
// themselves; every one of those was `cd /Users/you/Desktop/dev/... &&
|
|
10
10
|
// python3 - <<'PY' ...` 18 distinct raw tool names were printed as verbs, the
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// ONE. THE INTERESTING PART OF A COMMAND IS NEVER AT THE FRONT. Every one of
|
|
18
18
|
// the six worst lines was a real command hiding behind a `cd` into an absolute
|
|
19
19
|
// path, or an `export PATH=` prelude, or both. Truncating from the right, which
|
|
20
|
-
// is what an ellipsis does, therefore threw away the only part
|
|
20
|
+
// is what an ellipsis does, therefore threw away the only part that mattered and
|
|
21
21
|
// kept the part every line shares. `plainCommand` drops the prelude instead, so
|
|
22
22
|
// the line STARTS at the thing that ran.
|
|
23
23
|
//
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
// three segments behind a leading ellipsis. Nothing is invented and nothing is
|
|
29
29
|
// silently dropped: the whole string is one press away on the line itself.
|
|
30
30
|
//
|
|
31
|
-
// THREE. A RUN AGGREGATES
|
|
32
|
-
//
|
|
33
|
-
//
|
|
31
|
+
// THREE. A RUN AGGREGATES. Consecutive tool calls fold into one line that says
|
|
32
|
+
// how many and what kind. The threshold is three and it is measured, not
|
|
33
|
+
// chosen: the run-length histogram is
|
|
34
34
|
// {1:38, 2:42, 3:36, 4:18, 5:6, 6:6, 7:8, 8:2, 11:2, 13:1, 20:3}. Folding at
|
|
35
35
|
// two takes the screen down 70% and folding at three takes it down 62%, so the
|
|
36
|
-
// last eight points cost
|
|
36
|
+
// last eight points cost the 42 pairs, and a pair read as two short clean
|
|
37
37
|
// lines says more than "2 things it ran" does. Three is where a wall starts.
|
|
38
38
|
//
|
|
39
39
|
// NOTHING HERE HIDES A FAILURE. A folded run says on its own line that
|
|
@@ -42,15 +42,20 @@
|
|
|
42
42
|
// wall it replaced.
|
|
43
43
|
|
|
44
44
|
/* ------------------------------ WHAT IT DID ------------------------------- */
|
|
45
|
-
//
|
|
46
|
-
// so "Bash" and "Glob" are ours, not
|
|
45
|
+
// PLAIN WORDS AND NOT OUR TOOL NAMES. The design law forbids jargon in UI copy,
|
|
46
|
+
// so "Bash" and "Glob" are ours, not the reader's.
|
|
47
|
+
//
|
|
48
|
+
// `changed` RATHER THAN `edited` (2026-10-01). A tester who is not a programmer
|
|
49
|
+
// reads "edited" as a document being revised; the thing that happened is that a
|
|
50
|
+
// file in the code is now different, and the line next to it already offers the
|
|
51
|
+
// file as a chip into the change. One word for one event.
|
|
47
52
|
const VERBS = {
|
|
48
53
|
Bash: 'ran',
|
|
49
54
|
Read: 'read',
|
|
50
55
|
Write: 'wrote',
|
|
51
|
-
Edit: '
|
|
52
|
-
MultiEdit: '
|
|
53
|
-
NotebookEdit: '
|
|
56
|
+
Edit: 'changed',
|
|
57
|
+
MultiEdit: 'changed',
|
|
58
|
+
NotebookEdit: 'changed',
|
|
54
59
|
Glob: 'looked for',
|
|
55
60
|
Grep: 'searched for',
|
|
56
61
|
WebFetch: 'fetched',
|
|
@@ -219,6 +224,376 @@ export function plainCommand(raw) {
|
|
|
219
224
|
return s || whole.replace(/\s+/g, ' ').trim();
|
|
220
225
|
}
|
|
221
226
|
|
|
227
|
+
/* --------------------- THE STORE'S OWN BOOKKEEPING ------------------------ */
|
|
228
|
+
// NOT WORK, AND NOT SHOWN AT ALL.
|
|
229
|
+
//
|
|
230
|
+
// Testers who are not programmers read the top of every thread as noise, and
|
|
231
|
+
// the top of every thread is this: a tool lookup, a claim, and later an update
|
|
232
|
+
// to the item. MEASURED over 3,344 real session traces from 2026-09-20 to
|
|
233
|
+
// 2026-10-01: 55,500 tool lines, of which 2,462 are the store's own records
|
|
234
|
+
// (claim 1,212, update 999, release 138, the lists 113) and 498 are a tool
|
|
235
|
+
// lookup. Three per cent of the lines and the first two lines of nearly every
|
|
236
|
+
// thread.
|
|
237
|
+
//
|
|
238
|
+
// NOTHING OF THE WORK IS HIDDEN BY THIS. Reading a document, writing one,
|
|
239
|
+
// filing a question and looking at a page are what the session was asked to do;
|
|
240
|
+
// taking the item and writing its status back are how it tells the app it is
|
|
241
|
+
// running. The second kind is what comes off the screen.
|
|
242
|
+
//
|
|
243
|
+
// The server is named after the product (`mcp__agentbox__`, `mcp__daydream__`),
|
|
244
|
+
// so the prefix is never one fixed string and the tool is what follows the last
|
|
245
|
+
// double underscore, the same reading `workVerb` makes.
|
|
246
|
+
const BOOKKEEPING = new Set([
|
|
247
|
+
'claim_work_item', 'release_work_item', 'update_work_item',
|
|
248
|
+
'list_work_items', 'list_documents', 'list_products', 'list_skills',
|
|
249
|
+
'list_chats', 'get_product',
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
// The name arrives three ways and all three are the same tool: the full
|
|
253
|
+
// `mcp__<server>__<tool>` a Claude transcript writes, the bare tool Codex
|
|
254
|
+
// reports beside its server, and `ToolSearch`, which is the harness loading a
|
|
255
|
+
// schema and is nobody's work.
|
|
256
|
+
export function isBookkeeping(name) {
|
|
257
|
+
const n = String(name ?? '').trim();
|
|
258
|
+
if (!n) return false;
|
|
259
|
+
if (n === 'ToolSearch') return true;
|
|
260
|
+
return BOOKKEEPING.has(n.startsWith('mcp__') ? n.slice(n.lastIndexOf('__') + 2) : n);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* ------------------------- A COMMAND, IN PLAIN WORDS ---------------------- */
|
|
264
|
+
// "Running cat README.md; echo ---; cat package.json" is what a tester was
|
|
265
|
+
// shown. What happened was that it read two files.
|
|
266
|
+
//
|
|
267
|
+
// MEASURED over the same 3,344 traces: 32,349 Bash lines, 361 distinct leading
|
|
268
|
+
// programs, and the eleven commonest cover 80% of them (grep 5,325, git 3,998,
|
|
269
|
+
// sed 3,894, npx 2,471, python3 2,435, cat 2,074, ls 1,814, a shell -c 1,464,
|
|
270
|
+
// arch 947, a for loop 894, node 689). 55% of the lines are compound (`a && b;
|
|
271
|
+
// c`) and 48% hold a pipe, so the thing on the screen is a shell one-liner and
|
|
272
|
+
// not a sentence.
|
|
273
|
+
//
|
|
274
|
+
// THE RULE IS THAT A GUESS IS WORSE THAN A COMMAND. Every segment has to be
|
|
275
|
+
// recognised and they all have to agree on one verb; one unknown part, or two
|
|
276
|
+
// different verbs, and this returns null and the line stays the command it
|
|
277
|
+
// always was. A wrong verb on her screen cannot be checked against anything,
|
|
278
|
+
// and the raw command at least is true. The exact string is one press away
|
|
279
|
+
// either way (`full` on the work line).
|
|
280
|
+
//
|
|
281
|
+
// `doing` IS THE SAME EVENT WHILE IT IS STILL HAPPENING, because the live line
|
|
282
|
+
// under a running row reads "Reading README.md" and the thread above it reads
|
|
283
|
+
// "Read README.md". One table, so the two can never drift.
|
|
284
|
+
|
|
285
|
+
// Scaffolding between the real steps of a line. A banner, a `cd`, a wait.
|
|
286
|
+
const NOISE = new Set(['echo', 'cd', 'pwd', 'true', 'false', ':', 'sleep', 'clear', 'date', 'printf', 'set']);
|
|
287
|
+
|
|
288
|
+
// `2>&1` and `2>/dev/null` say nothing about what ran and are on a third of
|
|
289
|
+
// these lines. Any OTHER redirect writes or reads a file we have not accounted
|
|
290
|
+
// for, so it gives up rather than guess.
|
|
291
|
+
const QUIET_REDIRECT = /^[0-9]*>>?&?(?:[0-9]+|\/dev\/null)$/;
|
|
292
|
+
|
|
293
|
+
const FAMILIES = [
|
|
294
|
+
{ verb: 'read', doing: 'Reading', heads: ['cat', 'head', 'tail', 'less', 'more', 'nl', 'bat'], take: 'files', skip: ['-n', '-c', '-m'] },
|
|
295
|
+
{ verb: 'counted the lines in', doing: 'Counting the lines in', heads: ['wc'], take: 'files' },
|
|
296
|
+
{ verb: 'searched for', doing: 'Searching for', heads: ['grep', 'rg', 'ag', 'ack'], take: 'first', many: 'things', skip: ['-e', '-m', '-A', '-B', '-C'] },
|
|
297
|
+
{ verb: 'listed', doing: 'Listing', heads: ['ls'], take: 'files', empty: './', many: 'folders' },
|
|
298
|
+
{ verb: 'looked for', doing: 'Looking for', heads: ['find', 'fd'], take: 'named', many: 'things' },
|
|
299
|
+
{ verb: 'copied', doing: 'Copying', heads: ['cp'], take: 'files' },
|
|
300
|
+
{ verb: 'moved', doing: 'Moving', heads: ['mv'], take: 'files' },
|
|
301
|
+
{ verb: 'deleted', doing: 'Deleting', heads: ['rm'], take: 'files' },
|
|
302
|
+
{ verb: 'made a folder', doing: 'Making a folder', heads: ['mkdir'], take: 'files', many: 'folders', plural: { verb: 'made folders', doing: 'Making folders' } },
|
|
303
|
+
{ verb: 'made a file', doing: 'Making a file', heads: ['touch'], take: 'files', plural: { verb: 'made files', doing: 'Making files' } },
|
|
304
|
+
{ verb: 'fetched', doing: 'Fetching', heads: ['curl', 'wget'], take: 'url', many: 'pages', skip: ['-o', '-w', '-H', '-X', '-d', '-u', '-A', '-e', '-F', '--output', '--header', '--data'] },
|
|
305
|
+
];
|
|
306
|
+
const BY_HEAD = new Map();
|
|
307
|
+
for (const family of FAMILIES) for (const head of family.heads) BY_HEAD.set(head, family);
|
|
308
|
+
|
|
309
|
+
// A `git` subcommand is the verb; the ones not listed keep their command,
|
|
310
|
+
// because "checked the code" over `git merge-base` says less than the line did.
|
|
311
|
+
const GIT = {
|
|
312
|
+
status: { verb: 'checked what changed', doing: 'Checking what changed' },
|
|
313
|
+
diff: { verb: 'checked what changed', doing: 'Checking what changed' },
|
|
314
|
+
log: { verb: 'read the code history', doing: 'Reading the code history' },
|
|
315
|
+
show: { verb: 'read the code history', doing: 'Reading the code history' },
|
|
316
|
+
blame: { verb: 'read the code history', doing: 'Reading the code history' },
|
|
317
|
+
add: { verb: 'saved the changes', doing: 'Saving the changes' },
|
|
318
|
+
commit: { verb: 'saved the changes', doing: 'Saving the changes' },
|
|
319
|
+
branch: { verb: 'looked at the branches', doing: 'Looking at the branches' },
|
|
320
|
+
push: { verb: 'pushed the branch', doing: 'Pushing the branch' },
|
|
321
|
+
fetch: { verb: 'fetched the latest code', doing: 'Fetching the latest code' },
|
|
322
|
+
pull: { verb: 'fetched the latest code', doing: 'Fetching the latest code' },
|
|
323
|
+
merge: { verb: 'merged the branch', doing: 'Merging the branch' },
|
|
324
|
+
checkout: { verb: 'switched branch', doing: 'Switching branch' },
|
|
325
|
+
switch: { verb: 'switched branch', doing: 'Switching branch' },
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const TESTS = { verb: 'ran the tests', doing: 'Running the tests', many: 'test files' };
|
|
329
|
+
const SCRIPT = { verb: 'ran a script', doing: 'Running a script', many: 'scripts' };
|
|
330
|
+
const RUNNERS = new Set(['npx', 'npm', 'pnpm', 'yarn', 'bun']);
|
|
331
|
+
const SHELLS = new Set(['sh', 'bash', 'zsh', '/bin/sh', '/bin/bash', '/bin/zsh']);
|
|
332
|
+
const SCRIPTS = new Set(['node', 'python', 'python3', 'ruby', 'osascript', 'deno', 'tsx']);
|
|
333
|
+
const TEST_RUNNERS = new Set(['vitest', 'jest', 'pytest', 'mocha', 'ava']);
|
|
334
|
+
const TEST_FILE = /\.(?:test|spec)\.[a-z]+$/;
|
|
335
|
+
|
|
336
|
+
const lastSegment = (file) => String(file).split('/').filter(Boolean).pop() ?? '';
|
|
337
|
+
|
|
338
|
+
function unquote(token) {
|
|
339
|
+
const t = String(token);
|
|
340
|
+
if (t.length > 1 && (t[0] === '"' || t[0] === "'") && t[t.length - 1] === t[0]) return t.slice(1, -1);
|
|
341
|
+
return t;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Split on separators that are OUTSIDE quotes. A `;` inside a sed script or a
|
|
345
|
+
// grep pattern is not a new command, and splitting on it is how a reader of
|
|
346
|
+
// shell lines invents steps that never ran.
|
|
347
|
+
function splitOutside(text, separators) {
|
|
348
|
+
const out = [];
|
|
349
|
+
let buffer = '';
|
|
350
|
+
let quote = '';
|
|
351
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
352
|
+
const ch = text[i];
|
|
353
|
+
if (quote) {
|
|
354
|
+
buffer += ch;
|
|
355
|
+
if (ch === quote) quote = '';
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (ch === '"' || ch === "'") { quote = ch; buffer += ch; continue; }
|
|
359
|
+
const pair = text.slice(i, i + 2);
|
|
360
|
+
if (separators.includes(pair)) { out.push(buffer); buffer = ''; i += 1; continue; }
|
|
361
|
+
if (separators.includes(ch)) { out.push(buffer); buffer = ''; continue; }
|
|
362
|
+
buffer += ch;
|
|
363
|
+
}
|
|
364
|
+
out.push(buffer);
|
|
365
|
+
return out.map((part) => part.trim()).filter(Boolean);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const tokens = (segment) => splitOutside(segment, [' ', '\t']);
|
|
369
|
+
const isFlag = (token) => token.startsWith('-') && token !== '-';
|
|
370
|
+
const looksLikeAFile = (token) => /[./]/.test(token) && !token.startsWith('-');
|
|
371
|
+
|
|
372
|
+
// The arguments of one stage, with the quiet redirects and the flag values
|
|
373
|
+
// dropped. Null when something is there that this file cannot account for.
|
|
374
|
+
function argumentsOf(words, skip = []) {
|
|
375
|
+
const out = [];
|
|
376
|
+
for (let i = 0; i < words.length; i += 1) {
|
|
377
|
+
const raw = words[i];
|
|
378
|
+
const quoted = raw[0] === '"' || raw[0] === "'";
|
|
379
|
+
const token = unquote(raw);
|
|
380
|
+
if (!quoted) {
|
|
381
|
+
if (QUIET_REDIRECT.test(token)) continue;
|
|
382
|
+
// Anything else involving a file handle, a subshell or a glob of
|
|
383
|
+
// commands is a shape nobody taught this.
|
|
384
|
+
if (/[<>`]/.test(token) || token.includes('$(')) return null;
|
|
385
|
+
}
|
|
386
|
+
if (isFlag(token)) {
|
|
387
|
+
if (skip.includes(token)) i += 1;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
out.push(token);
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// One stage of one segment, as a verb and the things it names, or 'noise', or
|
|
396
|
+
// null for "this file does not know".
|
|
397
|
+
function stageWords(stage, cwd, home) {
|
|
398
|
+
const words = tokens(stage);
|
|
399
|
+
if (!words.length) return 'noise';
|
|
400
|
+
const head = unquote(words[0]);
|
|
401
|
+
const rest = words.slice(1);
|
|
402
|
+
if (NOISE.has(head)) {
|
|
403
|
+
// `echo something > a file` writes a file, and this is not the reader of it.
|
|
404
|
+
const args = argumentsOf(rest);
|
|
405
|
+
return args === null ? null : 'noise';
|
|
406
|
+
}
|
|
407
|
+
// `timeout 300 npx vitest …` and `arch -arm64 node …` are the real command
|
|
408
|
+
// with a wrapper in front of it.
|
|
409
|
+
if (head === 'timeout' || head === 'nice' || head === 'arch' || head === 'env') {
|
|
410
|
+
const inner = rest.filter((w) => !isFlag(unquote(w)) && !/^[0-9]+$/.test(unquote(w)));
|
|
411
|
+
return inner.length ? stageWords(inner.join(' '), cwd, home) : null;
|
|
412
|
+
}
|
|
413
|
+
// A shell asked to run a string: the string is the command.
|
|
414
|
+
if (SHELLS.has(head)) {
|
|
415
|
+
const flag = rest.findIndex((w) => /^-[a-z]*c$/.test(unquote(w)));
|
|
416
|
+
if (flag < 0 || !rest[flag + 1]) return null;
|
|
417
|
+
const words2 = commandWork(unquote(rest[flag + 1]), cwd, home);
|
|
418
|
+
return words2 ? { ...words2, names: words2.subject ? [words2.subject] : [], whole: true } : null;
|
|
419
|
+
}
|
|
420
|
+
if (head === 'git') return gitWords(rest, cwd, home);
|
|
421
|
+
if (head === 'sed' || head === 'perl') return sedWords(rest, cwd, home);
|
|
422
|
+
if (RUNNERS.has(head)) return runnerWords(rest, cwd, home);
|
|
423
|
+
if (SCRIPTS.has(head) || /\.(?:sh|mjs|js|py)$/.test(head)) {
|
|
424
|
+
const args = argumentsOf(rest);
|
|
425
|
+
if (args === null) return null;
|
|
426
|
+
// `python3 - <<'PY'` is a script with no name, and `plainCommand` already
|
|
427
|
+
// puts its first line after the ellipsis. That line is what a developer
|
|
428
|
+
// reads to tell one of 2,793 of these from another, so it is kept.
|
|
429
|
+
if (stage.includes('…')) {
|
|
430
|
+
const first = stage.slice(stage.lastIndexOf('…') + 1).trim();
|
|
431
|
+
return { ...SCRIPT, names: first ? [first] : [] };
|
|
432
|
+
}
|
|
433
|
+
const file = args.find(looksLikeAFile);
|
|
434
|
+
return { ...SCRIPT, names: file ? [lastSegment(file)] : [] };
|
|
435
|
+
}
|
|
436
|
+
if (TEST_RUNNERS.has(head)) return testWords(rest);
|
|
437
|
+
if (head === 'tsc') return { verb: 'checked the types', doing: 'Checking the types', names: [] };
|
|
438
|
+
if (head === 'eslint') return { verb: 'checked the code style', doing: 'Checking the code style', names: [] };
|
|
439
|
+
const family = BY_HEAD.get(head);
|
|
440
|
+
if (!family) return null;
|
|
441
|
+
const args = argumentsOf(rest, family.skip);
|
|
442
|
+
if (args === null) return null;
|
|
443
|
+
return { ...family, names: namesFor(family, args, cwd, home) };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const URL_LIKE = /^(?:https?:\/\/|www\.|localhost|127\.0\.0\.1)/;
|
|
447
|
+
|
|
448
|
+
function namesFor(family, args, cwd, home) {
|
|
449
|
+
if (family.take === 'first') return args.slice(0, 1);
|
|
450
|
+
// A fetch names the page, which is not always the first argument: `curl -s -o
|
|
451
|
+
// /dev/null -w '%{http_code}' <url>` had three before it.
|
|
452
|
+
if (family.take === 'url') {
|
|
453
|
+
const url = args.find((a) => URL_LIKE.test(a));
|
|
454
|
+
return url ? [url] : args.slice(0, 1);
|
|
455
|
+
}
|
|
456
|
+
if (family.take === 'named') {
|
|
457
|
+
const named = args.find((a) => /[*?]/.test(a));
|
|
458
|
+
return named ? [named] : args.slice(0, 1).map((a) => shortPath(a, cwd, home));
|
|
459
|
+
}
|
|
460
|
+
const files = args.filter(looksLikeAFile).map((a) => shortPath(a, cwd, home));
|
|
461
|
+
if (!files.length) return family.empty ? [family.empty] : [];
|
|
462
|
+
return files;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// WHAT THE SUBCOMMAND NAMED, because a developer reads these lines for the
|
|
466
|
+
// file, the branch or the message, and a verb on its own tells them less than
|
|
467
|
+
// the command did. Measured over the same traces: `git add`, `git commit`,
|
|
468
|
+
// `git push` and `git merge` are 563 lines that said nothing but their verb.
|
|
469
|
+
function gitWords(rest, cwd, home) {
|
|
470
|
+
const words = [...rest];
|
|
471
|
+
// `git -C <path> <subcommand>`: the folder is not the verb.
|
|
472
|
+
while (words.length && isFlag(unquote(words[0]))) {
|
|
473
|
+
const flag = unquote(words.shift());
|
|
474
|
+
if (flag === '-C' || flag === '--git-dir' || flag === '--work-tree') words.shift();
|
|
475
|
+
}
|
|
476
|
+
const sub = unquote(words[0] ?? '');
|
|
477
|
+
const known = GIT[sub];
|
|
478
|
+
if (!known) return null;
|
|
479
|
+
return { ...known, names: gitNames(sub, words.slice(1), cwd, home) };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Not a ref: the remote it went to, and the `--` that ends the flags.
|
|
483
|
+
const NOT_A_REF = new Set(['origin', 'upstream', '.', '--', 'HEAD']);
|
|
484
|
+
|
|
485
|
+
function gitNames(sub, rest, cwd, home) {
|
|
486
|
+
if (sub === 'commit') {
|
|
487
|
+
// The message, which is the one thing a commit line is read for. It is
|
|
488
|
+
// absent when the message came in on stdin (`git commit -F -`).
|
|
489
|
+
const at = rest.findIndex((w) => unquote(w) === '-m' || unquote(w) === '--message');
|
|
490
|
+
const message = at >= 0 ? unquote(rest[at + 1] ?? '') : '';
|
|
491
|
+
return message ? [message] : [];
|
|
492
|
+
}
|
|
493
|
+
const args = argumentsOf(rest, ['-m', '--message', '-C', '-S']);
|
|
494
|
+
if (args === null) return [];
|
|
495
|
+
if (sub === 'push' || sub === 'merge' || sub === 'checkout' || sub === 'switch' || sub === 'fetch' || sub === 'pull') {
|
|
496
|
+
const refs = args.filter((a) => a && !NOT_A_REF.has(a));
|
|
497
|
+
return refs.length ? [refs[refs.length - 1]] : [];
|
|
498
|
+
}
|
|
499
|
+
const files = args.filter(looksLikeAFile).map((a) => shortPath(a, cwd, home));
|
|
500
|
+
// A HISTORY COMMAND OFTEN NAMES SOMETHING THAT IS NOT A PATH, and it is the
|
|
501
|
+
// point of the line: `git log --grep w-4ac1af8c99` is a search for one
|
|
502
|
+
// thread. The value a flag carried survives when no file does.
|
|
503
|
+
if (files.length) return files;
|
|
504
|
+
return args.length ? [args[0]] : [];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// The leading quote survives on a line the trace cut mid-string.
|
|
508
|
+
const SED_SCRIPT = /^['"]?[0-9,$]*[a-zA-Z]?[/#|]/;
|
|
509
|
+
|
|
510
|
+
function sedWords(rest, cwd, home) {
|
|
511
|
+
const args = argumentsOf(rest);
|
|
512
|
+
if (args === null) return null;
|
|
513
|
+
const flags = rest.map(unquote).filter(isFlag);
|
|
514
|
+
const inPlace = flags.some((f) => f.startsWith('-i'));
|
|
515
|
+
// THE FILE IS THE LAST ARGUMENT, AND A SED SCRIPT IS NOT IT. A trace line is
|
|
516
|
+
// cut at 200 characters, so the file is sometimes not on it at all and the
|
|
517
|
+
// script (`s#a#b#`) is the last thing that looks like a path.
|
|
518
|
+
const file = [...args].reverse().find((a) => looksLikeAFile(a) && !SED_SCRIPT.test(a));
|
|
519
|
+
if (inPlace) {
|
|
520
|
+
return file ? { verb: 'changed', doing: 'Changing', names: [shortPath(file, cwd, home)] } : null;
|
|
521
|
+
}
|
|
522
|
+
// `sed -n 1,200p <file>` is how an agent reads part of a file; 3,529 of the
|
|
523
|
+
// 3,894 sed lines measured were exactly that, against 325 that edited.
|
|
524
|
+
if (!flags.some((f) => f === '-n' || f === '-e' || f === '-E')) return null;
|
|
525
|
+
return file ? { verb: 'read', doing: 'Reading', names: [shortPath(file, cwd, home)] } : null;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function runnerWords(rest, cwd, home) {
|
|
529
|
+
const words = rest.map(unquote).filter((w) => w !== '--yes' && w !== '-y' && w !== '-s');
|
|
530
|
+
let tool = words[0] ?? '';
|
|
531
|
+
if (tool === 'run' || tool === 'exec') tool = words[1] ?? '';
|
|
532
|
+
// `npx vitest@4` and `npx hyperframes@0.8.77` name a version, not a tool.
|
|
533
|
+
tool = tool.replace(/@[^@/]*$/, '');
|
|
534
|
+
if (tool === 'test' || TEST_RUNNERS.has(tool)) return testWords(words.slice(1));
|
|
535
|
+
if (tool === 'tsc' || tool === 'typecheck') return { verb: 'checked the types', doing: 'Checking the types', names: [] };
|
|
536
|
+
if (tool === 'eslint' || tool === 'lint') return { verb: 'checked the code style', doing: 'Checking the code style', names: [] };
|
|
537
|
+
if (tool === 'build' || (tool === 'vite' && words.includes('build'))) {
|
|
538
|
+
return { verb: 'built the app', doing: 'Building the app', names: [] };
|
|
539
|
+
}
|
|
540
|
+
if (tool === 'install' || tool === 'ci' || tool === 'i') {
|
|
541
|
+
return { verb: 'installed the packages', doing: 'Installing the packages', names: [] };
|
|
542
|
+
}
|
|
543
|
+
if (SCRIPTS.has(tool)) return stageWords(words.join(' '), cwd, home);
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// A trace line is cut at 200 characters, so the last test file on a long
|
|
548
|
+
// `vitest run a b c` often arrives without its extension. Anything that looks
|
|
549
|
+
// like a path counts, which is how 586 lines that named nothing got their name.
|
|
550
|
+
function testWords(rest) {
|
|
551
|
+
const args = rest.map(unquote).filter((w) => !isFlag(w) && w !== 'run');
|
|
552
|
+
const files = args.filter((w) => TEST_FILE.test(w) || w.includes('/'));
|
|
553
|
+
return { ...TESTS, names: files.map(lastSegment) };
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Up to three names, then the count. Her standing objection to a cap is one
|
|
557
|
+
// that rounds off in silence, so the count says what it counted.
|
|
558
|
+
function joinNames(names, many = 'files') {
|
|
559
|
+
const list = [...new Set(names.filter(Boolean))];
|
|
560
|
+
if (!list.length) return '';
|
|
561
|
+
if (list.length > 3) return `${list.length} ${many}`;
|
|
562
|
+
if (list.length === 1) return list[0];
|
|
563
|
+
return `${list.slice(0, -1).join(', ')} and ${list[list.length - 1]}`;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* What a shell command did, in words, or null to leave it as the command.
|
|
568
|
+
*
|
|
569
|
+
* `cwd` and `home` are the folder the session ran in and the Mac's home, the
|
|
570
|
+
* same two arguments `shortPath` takes and for the same reason: a path is only
|
|
571
|
+
* shortenable against the folder it sits under, and nothing is guessed when
|
|
572
|
+
* nobody says.
|
|
573
|
+
*/
|
|
574
|
+
export function commandWork(raw, cwd = '', home = '') {
|
|
575
|
+
const command = plainCommand(raw);
|
|
576
|
+
if (!command) return null;
|
|
577
|
+
const steps = [];
|
|
578
|
+
for (const segment of splitOutside(command, ['&&', '||', ';', '\n'])) {
|
|
579
|
+
// A pipeline's first stage is what ran; the rest is plumbing that shapes
|
|
580
|
+
// its output (`| head`, `| sort | uniq -c`).
|
|
581
|
+
const stage = splitOutside(segment, ['|'])[0] ?? '';
|
|
582
|
+
const read = stageWords(stage, cwd, home);
|
|
583
|
+
if (read === null) return null;
|
|
584
|
+
if (read !== 'noise') steps.push(read);
|
|
585
|
+
}
|
|
586
|
+
if (!steps.length) return null;
|
|
587
|
+
const first = steps[0];
|
|
588
|
+
if (steps.some((step) => step.verb !== first.verb)) return null;
|
|
589
|
+
if (first.whole) return { verb: first.verb, doing: first.doing, subject: first.names[0] ?? '' };
|
|
590
|
+
const names = [...new Set(steps.flatMap((step) => step.names).filter(Boolean))];
|
|
591
|
+
// "Made a folder, 4 folders" is the verb and the count disagreeing about how
|
|
592
|
+
// many there were. A family that counts its own subject says both ways.
|
|
593
|
+
const said = names.length > 1 && first.plural ? first.plural : first;
|
|
594
|
+
return { verb: said.verb, doing: said.doing, subject: joinNames(names, first.many) };
|
|
595
|
+
}
|
|
596
|
+
|
|
222
597
|
/* ------------------------------- THE SUBJECT ------------------------------ */
|
|
223
598
|
// WHICH ONE THIS WAS, in as few characters as the truth allows. `cwd` is the
|
|
224
599
|
// folder the session is running in, which is what makes a path relative; it is
|
|
@@ -260,7 +635,9 @@ export function groupWork(events, { min = RUN_MIN } = {}) {
|
|
|
260
635
|
run = [];
|
|
261
636
|
};
|
|
262
637
|
for (const e of events ?? []) {
|
|
263
|
-
|
|
638
|
+
// SOMETHING SHE DID IS NOT THE AGENT'S WORK and never folds into it: three
|
|
639
|
+
// snoozes are three lines, not one "You put it off" (w-49b4e45403).
|
|
640
|
+
if (e && e.kind === 'work' && !e.yours) { run.push(e); continue; }
|
|
264
641
|
flush();
|
|
265
642
|
out.push(e);
|
|
266
643
|
}
|
|
@@ -302,7 +679,7 @@ export function runSummary(items) {
|
|
|
302
679
|
return `${list.length} things it did · ${shown.join(' · ')}`;
|
|
303
680
|
}
|
|
304
681
|
|
|
305
|
-
// THE BAD NEWS IS NEVER BEHIND THE FOLD. Said in
|
|
682
|
+
// THE BAD NEWS IS NEVER BEHIND THE FOLD. Said in plain words on the closed line,
|
|
306
683
|
// in the one colour this surface uses.
|
|
307
684
|
export function runFailures(items) {
|
|
308
685
|
const bad = (items ?? []).filter((e) => e && e.failed).length;
|
package/worker-permissions.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"//": "Worker settings, passed per-spawn via --settings. It carries NO permission rules of our own, on purpose: workers run on Claude Code's own defaults for whatever mode they are in, the way Codex workers run on the Codex app's own posture (main/codex-session.mjs).
|
|
2
|
+
"//": "Worker settings, passed per-spawn via --settings. It carries NO permission rules of our own, on purpose: workers run on Claude Code's own defaults for whatever mode they are in, the way Codex workers run on the Codex app's own posture (main/codex-session.mjs). The app matches Claude Code's and Codex's default settings rather than keeping its own (w-6ade63e1aa). This used to ship allow Bash plus a 28-entry ask list. In auto mode Claude Code drops a blanket Bash allow and still forces a prompt on every ask rule, so the list did nothing but add cards: on w-60f57bec36, 10 of 10 cards in nine minutes were `vercel` commands it named. additionalDirectories is EMPTY ON PURPOSE and is filled in at spawn time by Supervisor.writeWorkerSettings with this person's own store root: it used to name one developer's home folder, which shipped inside the download and granted every stranger's agents a directory that does not exist on their Mac (w-24fa146714, 2026-08-23).",
|
|
3
3
|
"permissions": {
|
|
4
4
|
"additionalDirectories": []
|
|
5
5
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|