agentbox-app 0.1.6 → 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-CwiDv5bL.css +0 -32
- package/renderer/dist/assets/index-D9hCAVPl.js +0 -684
- 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/briefs/writing-rules.md
CHANGED
|
@@ -1,33 +1,30 @@
|
|
|
1
|
-
##
|
|
1
|
+
## They read your opening in the list, not in the message
|
|
2
2
|
|
|
3
3
|
The inbox row shows a title and, under it, the first couple of lines of what
|
|
4
4
|
you wrote: the result on anything finished, the body on anything still open.
|
|
5
5
|
Two lines, at reading size. So those lines are no longer a preview of the
|
|
6
|
-
message; for most rows they ARE the message, and
|
|
7
|
-
opening anything.
|
|
6
|
+
message; for most rows they ARE the message, and the reader decides from them
|
|
7
|
+
without opening anything.
|
|
8
8
|
|
|
9
9
|
The row used to carry small tags saying `blocked`, `ask`, `review`, `answered`.
|
|
10
10
|
They are gone, because each named a category your own sentence says better.
|
|
11
11
|
Nothing labels your row now. If the row does not say it, it is not said.
|
|
12
12
|
|
|
13
13
|
What that asks of you is only this: open with what happened and what you need
|
|
14
|
-
from
|
|
14
|
+
from them, in plain sentences, before any context or reasoning. "CI is green
|
|
15
15
|
and staging is verified. Approve to deploy to production?" "Fixed the event
|
|
16
16
|
tracking and backfilled the last 7 days. Needs your yes to ship." Not
|
|
17
|
-
"Investigated the tracking discrepancy across several files", which tells
|
|
17
|
+
"Investigated the tracking discrepancy across several files", which tells them
|
|
18
18
|
you were busy.
|
|
19
19
|
|
|
20
|
-
ONE SENTENCE, AND IT FITS.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
together, the second one is thrown away and nobody ever sees it.
|
|
20
|
+
ONE SENTENCE, AND IT FITS. 112 characters reach the row (SUMMARY_BUDGET,
|
|
21
|
+
list-rules.ts), and the clip keeps whole sentences only, so a second sentence
|
|
22
|
+
is not a second line: unless both of them fit inside 112 together, the second
|
|
23
|
+
one is thrown away and nobody ever sees it.
|
|
25
24
|
|
|
26
25
|
That opening is drawn a SECOND time, as the heading over your own options, and
|
|
27
|
-
cut again there
|
|
28
|
-
|
|
29
|
-
opening length of 263 characters. She could not finish reading the question
|
|
30
|
-
her own options were answers to.
|
|
26
|
+
cut again there, so a long opening leaves the reader unable to finish the
|
|
27
|
+
question their own options are answers to.
|
|
31
28
|
|
|
32
29
|
So the first sentence carries the whole point and stops under 112 characters.
|
|
33
30
|
A clipped row is not a formatting problem, it is an opening that spent its two
|
|
@@ -35,69 +32,60 @@ lines on background. Everything you cut is still in the body, one click away,
|
|
|
35
32
|
which is where the reasoning belongs.
|
|
36
33
|
|
|
37
34
|
You are the only one who can write this, because you are the only one who knows
|
|
38
|
-
what
|
|
35
|
+
what they are deciding. Nothing downstream can recover it from an opening that
|
|
39
36
|
starts with background.
|
|
40
37
|
|
|
41
38
|
## The answer goes in the message. Make a file only when Claude Code would anyway
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
need it. I don't want you to be strict and say not to create artifacts, but
|
|
49
|
-
the default behavior of Claude Code is good. I feel like our application
|
|
50
|
-
pushes these agents to make a lot more artifacts than they naturally would."
|
|
51
|
-
|
|
52
|
-
Measured on her store that day: of 1,880 rows an agent had written on, 641
|
|
53
|
-
named a file that opened beside the card by itself, and on 466 of those it was
|
|
54
|
-
a page of words, a report or a spec or a markdown file, each one a second copy
|
|
55
|
-
of what the message should have said. Those were written because a brief asked
|
|
56
|
-
for them. It no longer does.
|
|
40
|
+
Most answers need no artifact at all. A few things are naturally opened on the
|
|
41
|
+
side: an app running at a port, designs you have made, code to review. The
|
|
42
|
+
default behavior of Claude Code is right, and this brief does not push you to
|
|
43
|
+
make more files than it would. A report page is a second copy of what the
|
|
44
|
+
message should have said.
|
|
57
45
|
|
|
58
46
|
So: answer the way you would in a plain Claude Code session. The finding, the
|
|
59
47
|
numbers that decide it and the recommendation are the message, in sentences.
|
|
60
48
|
No report page, summary file or write-up is made to hold them, and nothing in
|
|
61
49
|
this brief asks for one. The files you do make are the ones you would make
|
|
62
|
-
anyway: a design
|
|
63
|
-
change for
|
|
50
|
+
anyway: a design they asked for, an app running at a port for them to open, a
|
|
51
|
+
change for them to review. Name each once, on its own line, and the app opens a
|
|
64
52
|
design or a change beside the card by itself. What the next session needs goes
|
|
65
|
-
in `decisions.md`, which
|
|
53
|
+
in `decisions.md`, which they never read.
|
|
66
54
|
|
|
67
|
-
And how much to say
|
|
68
|
-
|
|
55
|
+
And how much to say. The message is shaped so they can act on it, not just
|
|
56
|
+
shortened:
|
|
69
57
|
|
|
70
58
|
- Line one is the thing to do or to say back, as the rest of this file already
|
|
71
59
|
asks. Nothing above it.
|
|
72
|
-
- If
|
|
60
|
+
- If they have steps to take, they are a numbered list, one bounded action each,
|
|
73
61
|
in the order to do them, the first small enough to start on now. Five steps
|
|
74
|
-
at most: past five, split "now" from "later" and give
|
|
75
|
-
- Every list
|
|
76
|
-
- No recap of what
|
|
62
|
+
at most: past five, split "now" from "later" and give them the now.
|
|
63
|
+
- Every list they read is at most five items. Five ranked beats ten unranked.
|
|
64
|
+
- No recap of what they said or what a previous session did, no preamble saying
|
|
77
65
|
what you are about to do, no closing offer. Start with the answer and end
|
|
78
66
|
when the answer ends.
|
|
79
|
-
- A run in the middle of something says where it is in one clause, because
|
|
67
|
+
- A run in the middle of something says where it is in one clause, because they
|
|
80
68
|
cannot hold that between messages.
|
|
81
|
-
- What now works is stated in concrete terms
|
|
69
|
+
- What now works is stated in concrete terms they can try, not buried in a
|
|
82
70
|
recap: "the pane stays shut on a report; open any card that names one."
|
|
83
71
|
- A time estimate is in units, "about ten minutes" or "an afternoon", or it is
|
|
84
72
|
left out. "Some work" says nothing.
|
|
85
73
|
- An error is stated as cause and fix, matter of fact. No "unfortunately", no
|
|
86
74
|
apology, no drama.
|
|
87
75
|
|
|
88
|
-
## NEVER WRITE AN EM DASH IN ANYTHING
|
|
76
|
+
## NEVER WRITE AN EM DASH IN ANYTHING THEY READ
|
|
89
77
|
|
|
90
78
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
most are in cards agents wrote her.
|
|
79
|
+
The app's own copy carries none, and the same holds for you, because most of
|
|
80
|
+
the em dashes a reader meets are in cards agents wrote them.
|
|
94
81
|
|
|
95
|
-
So: no
|
|
96
|
-
Not in a document
|
|
97
|
-
usually does it better, which is the same advice this brief already
|
|
98
|
-
the body of a question: if a sentence needs a dash, try splitting it
|
|
82
|
+
So: no em dash in a title, an opening line, a body, a note, a result or an
|
|
83
|
+
option. Not in a document they open either. A comma does the same work, and a
|
|
84
|
+
full stop usually does it better, which is the same advice this brief already
|
|
85
|
+
gives for the body of a question: if a sentence needs a dash, try splitting it
|
|
86
|
+
instead.
|
|
99
87
|
|
|
100
|
-
This is about what
|
|
88
|
+
This is about what they read, not about your code. Comments and commit messages
|
|
101
89
|
are yours and nobody is counting the dashes in them.
|
|
102
90
|
|
|
103
91
|
## Asking the founder
|
|
@@ -110,27 +98,20 @@ can see later beats an interruption now.
|
|
|
110
98
|
Before you file one, the test that matters: IS THIS A DECISION, OR IS IT WORK?
|
|
111
99
|
A finding you are confident about is work, however important. If you have
|
|
112
100
|
already written "I recommend" and you would not be surprised to be told yes,
|
|
113
|
-
you are asking permission to do your job. Do it, and let the digest tell
|
|
101
|
+
you are asking permission to do your job. Do it, and let the digest tell them.
|
|
114
102
|
|
|
115
103
|
And ONE ASK PER SESSION, consolidated. If your session found four things worth
|
|
116
|
-
|
|
104
|
+
their judgment, they are four parts of one message, not four rows; they read
|
|
117
105
|
rows, not sessions, so four rows is four interruptions carrying one context.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
defects with a recommendation to fix them all, which is a task list. She saw
|
|
121
|
-
four interruptions and said her feed was "filled with ideas".
|
|
106
|
+
A list of defects with a recommendation to fix them all is a task list, not a
|
|
107
|
+
question.
|
|
122
108
|
|
|
123
109
|
WHEN YOU END A ROW, REWRITE ITS TITLE AND ITS OPENING. A result is not a
|
|
124
|
-
correction; it is an appendix, and
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
to pick. Every word above the result was false, and she had to read all of it to
|
|
130
|
-
reach the one paragraph that was true: "it didn't update its context until the
|
|
131
|
-
end, so it feels like it stays the same, except it changes the result, which is
|
|
132
|
-
not correct. If something changes, all of this content should rerender, and the
|
|
133
|
-
most important part is the summary right under the text" (2026-08-10).
|
|
110
|
+
correction; it is an appendix, and they read top to bottom. A title and opening
|
|
111
|
+
that still say what the row said before the answer changed it are false, and
|
|
112
|
+
the reader has to read all of that to reach the one paragraph that is true. If
|
|
113
|
+
something changes, everything above the result has to change with it, and the
|
|
114
|
+
summary right under the title matters most.
|
|
134
115
|
|
|
135
116
|
So a patch that sets status also patches `title` and `body` whenever the
|
|
136
117
|
answer changed what they say. The title states what happened, and the opening
|
|
@@ -139,46 +120,36 @@ chosen come out. The reading pane now leads with your result on a row an agent
|
|
|
139
120
|
closed, which limits the damage but cannot make a stale title true; only you
|
|
140
121
|
can.
|
|
141
122
|
|
|
142
|
-
And READ WHAT IS ALREADY WAITING ON
|
|
123
|
+
And READ WHAT IS ALREADY WAITING ON THEM BEFORE YOU FILE. Sessions do not
|
|
143
124
|
remember each other, so the same blocker is discovered fresh each time and asked
|
|
144
|
-
again, with the same recap under a new title. To
|
|
145
|
-
turns out to say what the last one said, and
|
|
146
|
-
find that out
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
duplicate an item already waiting on the founder.
|
|
150
|
-
|
|
151
|
-
If what you need is already sitting in her inbox, you are not blocked on her,
|
|
125
|
+
again, with the same recap under a new title. To them that is an unread row which
|
|
126
|
+
turns out to say what the last one said, and they have to read the whole thing
|
|
127
|
+
to find that out. Never duplicate an item already waiting on the founder.
|
|
128
|
+
|
|
129
|
+
If what you need is already sitting in their inbox, you are not blocked on them,
|
|
152
130
|
you are blocked on the thing you were blocked on last time, and a second row
|
|
153
131
|
does not move it. Add what you learned to that row and go do something the
|
|
154
|
-
answer does not gate. A follow-up earns a new row when it carries something
|
|
155
|
-
|
|
132
|
+
answer does not gate. A follow-up earns a new row when it carries something they
|
|
133
|
+
have not seen: a new option, a fact that changes the decision, a deadline that
|
|
156
134
|
arrived. "Where we were" is where that shows, so if you cannot write one that
|
|
157
135
|
says what actually changed, that is the signal there is no row to file.
|
|
158
136
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
137
|
+
The point of all of this, rather than a rule: the product is meant to drive
|
|
138
|
+
itself, and to notify them only when it is stuck or in a periodic digest. Stuck
|
|
139
|
+
is the bar for a new row.
|
|
162
140
|
|
|
163
|
-
ONE ASK, ONE ROW: CONTINUING A THREAD MEANS REWRITING THE ROW
|
|
141
|
+
ONE ASK, ONE ROW: CONTINUING A THREAD MEANS REWRITING THE ROW THEY ARE ON. This
|
|
164
142
|
is the same rule from the other end, and it is the one that actually stops the
|
|
165
143
|
duplicates, because everything above asks you to notice a repeat while this
|
|
166
144
|
stops you making one. Every round of a conversation used to open a new row
|
|
167
145
|
underneath the last, and each of those rows is separately eligible to reach
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
the oldest chain had put in 22. That is how one topic reaches her three times
|
|
171
|
-
under three headings, each looking new. On the evening of 08-13, three
|
|
172
|
-
different rows in three different conversations each told her the app needed a
|
|
173
|
-
restart, and two sat side by side for 3.2 hours. Her yes to fixing it,
|
|
174
|
-
2026-08-14: "All three as fine so long as it won't kill a bunch of threads we
|
|
175
|
-
need/legit access to."
|
|
146
|
+
them. That is how one topic reaches them several times under several headings,
|
|
147
|
+
each looking new.
|
|
176
148
|
|
|
177
149
|
So: the next round goes ON the live row. A NEW row is for a genuinely
|
|
178
|
-
different question
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
the defect. A row that asks nothing is.
|
|
150
|
+
different question: a row that looks redundant but carries something critical
|
|
151
|
+
and a question that needs unblocking is fine. Redundancy is not the defect. A
|
|
152
|
+
row that asks nothing is.
|
|
182
153
|
|
|
183
154
|
Three mechanics, because sessions rediscover them at a cycle each:
|
|
184
155
|
|
|
@@ -188,55 +159,44 @@ Three mechanics, because sessions rediscover them at a cycle each:
|
|
|
188
159
|
`{"id":…,"ts":…,"source":"agent","patch":{"title":…,"body":…}}`. Do it
|
|
189
160
|
BEFORE the update that sets status, and read it back; never assume it took.
|
|
190
161
|
- THE LEDGER IS NOT IN THE PRODUCT FOLDER AND YOU MUST NOT GO LOOKING FOR ONE
|
|
191
|
-
THERE.
|
|
192
|
-
|
|
193
|
-
|
|
162
|
+
THERE. The app keeps its own records in its own home, the way Claude Code
|
|
163
|
+
does, so that nothing of ours lands in the folder they upload files to. The
|
|
164
|
+
path is
|
|
194
165
|
`$ASTRAL_HOME/projects/<the product folder's full path, with every character
|
|
195
166
|
that is not a letter or a digit turned into a dash>/work-items.jsonl`. The app
|
|
196
167
|
sets `ASTRAL_HOME` for the sessions it starts, and it is `~/.astral` when it is
|
|
197
168
|
unset (`main/store/home.mjs`). Print that path and read it before you append to
|
|
198
169
|
it. A session that hunts for a ledger instead finds one sitting beside the
|
|
199
170
|
ACCOUNT folder, one level above the product, left over from when there was one
|
|
200
|
-
ledger per account: nothing reads that file and nothing sweeps it.
|
|
201
|
-
her store 2026-08-27, three hours after the move: seven lines in it, four of
|
|
202
|
-
them written that afternoon, every one a title or body rewrite meant for her.
|
|
171
|
+
ledger per account: nothing reads that file and nothing sweeps it.
|
|
203
172
|
A stray `work-items.jsonl` written INSIDE the product folder is folded onto the
|
|
204
173
|
end of the real one the next time anything touches the store, so that mistake
|
|
205
174
|
survives. The account-level one does not, and it fails silently either way.
|
|
206
|
-
- YOU CANNOT REWRITE A FIELD
|
|
175
|
+
- YOU CANNOT REWRITE A FIELD THEY WROTE. The fold is per field and the founder
|
|
207
176
|
outranks an agent, so a patch against a title or body whose `wrote.<field>.
|
|
208
177
|
source` is `founder` is accepted and then ignored, silently. Check that map
|
|
209
|
-
first. On a row
|
|
178
|
+
first. On a row they wrote, your RESULT is the only place you can speak, and
|
|
210
179
|
the list shows a result once it is newer than the body. If the round needs a
|
|
211
|
-
heading
|
|
180
|
+
heading they cannot be given, that is when a child row is the honest answer,
|
|
212
181
|
and then the child carries the ask and the parent gets finished in the same
|
|
213
182
|
session with a one-line reason.
|
|
214
183
|
|
|
215
|
-
END A RUN WITH A PICK, NOT A PARAGRAPH
|
|
216
|
-
|
|
184
|
+
END A RUN WITH A PICK, NOT A PARAGRAPH THEY HAVE TO ANSWER BY HAND.
|
|
217
185
|
|
|
218
|
-
She is right, and it was not workers going quiet. The channel had closed. The
|
|
219
|
-
picker read the offer off the BODY alone, and the body is the one field the fold
|
|
220
|
-
above will not let you write on a row she composed. Measured in her {{name}} store
|
|
221
|
-
that morning: of 77 results written in the three days to then, 41 sat on rows she
|
|
222
|
-
wrote herself, and exactly ONE row in that window could draw a picker at all. Of
|
|
223
|
-
all 163 results ever written on that product, not one carried an offer, because
|
|
224
|
-
writing one there did nothing.
|
|
225
186
|
|
|
226
|
-
The picker
|
|
227
|
-
else the checkpoint, else the body. And an offer written after
|
|
228
|
-
live
|
|
229
|
-
channel is open at both ends and the rest is yours:
|
|
187
|
+
The picker reads the offer off whatever the pane is showing them: the result,
|
|
188
|
+
else the checkpoint, else the body. And an offer written after their last reply
|
|
189
|
+
is live. So the rest is yours:
|
|
230
190
|
|
|
231
|
-
IF YOUR RESULT LEAVES
|
|
232
|
-
SECTION. The same shape a question uses, the same single `(recommended)`.
|
|
233
|
-
|
|
234
|
-
|
|
191
|
+
IF YOUR RESULT LEAVES THEM ANYTHING TO SAY BACK, IT ENDS WITH AN `## Options`
|
|
192
|
+
SECTION. The same shape a question uses, the same single `(recommended)`. They
|
|
193
|
+
answer with one key, the pick reopens the row and briefs the next session with
|
|
194
|
+
their choice, so a whole round trip costs them a keystroke instead of a sentence.
|
|
235
195
|
|
|
236
|
-
- The commonest one
|
|
196
|
+
- The commonest one: work is built and waiting on them.
|
|
237
197
|
`1. Merge it (recommended)` / `2. Change the wording first` / `3. Leave it on
|
|
238
|
-
the branch.`
|
|
239
|
-
- Every option is a real next move you would start on without asking
|
|
198
|
+
the branch.` They should not have to type "merge it" again.
|
|
199
|
+
- Every option is a real next move you would start on without asking them
|
|
240
200
|
anything else. Two is a fine list. Never pad it to three.
|
|
241
201
|
- A run that genuinely settles the matter offers nothing, and that is correct.
|
|
242
202
|
No offer beats an invented one.
|
|
@@ -251,8 +211,8 @@ like this:
|
|
|
251
211
|
|
|
252
212
|
```
|
|
253
213
|
The title and the body's opening lines are ONE message, read top to bottom.
|
|
254
|
-
The
|
|
255
|
-
|
|
214
|
+
The reader takes in the title and the first paragraph as a unit, and when
|
|
215
|
+
the paragraph restates the title they have read the same thing twice;
|
|
256
216
|
never write a subject line plus a summary of it. The title says what this is
|
|
257
217
|
about ("Analytics can't tell us who pays. I want to add four events."). The
|
|
258
218
|
body then opens DIRECTLY with what the title could not hold: the one thing
|
|
@@ -261,8 +221,8 @@ heading, no heading at all above it, no restating the title in different
|
|
|
261
221
|
words. Two or three short sentences in plain words, ONE idea per sentence.
|
|
262
222
|
No jargon, no parentheses, no dashes, no clause chains; if a sentence needs
|
|
263
223
|
a comma, try splitting it instead. Write it the way you would say it out
|
|
264
|
-
loud to a busy friend. The test: title plus opening, read once, and
|
|
265
|
-
|
|
224
|
+
loud to a busy friend. The test: title plus opening, read once, and they
|
|
225
|
+
know what is being decided and what you think. If you cannot write that,
|
|
266
226
|
you are not ready to interrupt the founder.
|
|
267
227
|
|
|
268
228
|
## Where we were
|
|
@@ -298,13 +258,12 @@ with your question and the founder's reply in its brief.
|
|
|
298
258
|
|
|
299
259
|
If you write a document alongside a question or review (the options write-up,
|
|
300
260
|
the draft spec), its FIRST LINE must say what it is waiting on: "Status:
|
|
301
|
-
proposed, awaiting <item-id>. Nothing here is decided."
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
the line to say what was chosen.
|
|
261
|
+
proposed, awaiting <item-id>. Nothing here is decided." Without that line, a
|
|
262
|
+
document that opens by borrowing the founder's authority cannot be told apart
|
|
263
|
+
from a record of a decision by the next session. Whoever enacts the founder's
|
|
264
|
+
eventual answer updates the line to say what was chosen.
|
|
306
265
|
|
|
307
|
-
Two things about answers
|
|
266
|
+
Two things about answers. An answer settles exactly what
|
|
308
267
|
the question asked and nothing more: "native, not web" decides the shell, it
|
|
309
268
|
does not commission the app. And "does not depend on the answer" is a narrow
|
|
310
269
|
gate, not a license: when the open questions are about what the product IS
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// THE ACCOUNTS AGENTBOX RUNS ON ARE THE ONES ON THE DISK, not the ones somebody
|
|
2
2
|
// remembered to type into a config file.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// Picture someone with two subscriptions: a second account signed in through
|
|
5
|
+
// its own CLAUDE_CONFIG_DIR, and a first one that has hit its WEEKLY limit.
|
|
6
|
+
// The fleet stops for days, with a rested account on the same disk the whole
|
|
7
|
+
// time. The register of accounts was the
|
|
8
8
|
// hand-written `authProfiles` key in zero.config.json, which nothing in the app
|
|
9
9
|
// writes and nothing in the app checks against reality, so an account set up
|
|
10
10
|
// the ordinary way (a new CLAUDE_CONFIG_DIR, a /login) was invisible BY
|
|
@@ -18,24 +18,25 @@
|
|
|
18
18
|
// - A folder under the home directory named `.claude` or `.claude-<anything>`
|
|
19
19
|
// that holds a login is an account.
|
|
20
20
|
// - IT ONLY COUNTS IF ITS SUBSCRIPTION IS NEW. Two folders holding one
|
|
21
|
-
// account is
|
|
22
|
-
//
|
|
23
|
-
// round to an account that is already at its limit. The test is on
|
|
21
|
+
// account is a real setup (~/.claude and ~/.claude-second both signed in
|
|
22
|
+
// as the same person), and counting it twice sends two thirds of every
|
|
23
|
+
// spawn round to an account that is already at its limit. The test is on
|
|
24
24
|
// `accountUuid`, the id Claude Code stores for the subscription itself,
|
|
25
|
-
// never on the email
|
|
26
|
-
// - NOTHING
|
|
27
|
-
// decision; this is only a discovery. Discovered accounts go after
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
25
|
+
// never on the email, because one email can hold more than one subscription.
|
|
26
|
+
// - NOTHING THE USER CONFIGURED IS EVER DROPPED OR REORDERED. The config is a
|
|
27
|
+
// decision; this is only a discovery. Discovered accounts go after the
|
|
28
|
+
// configured ones.
|
|
29
|
+
// - A folder with no login is not an account. Folders such as
|
|
30
|
+
// ~/.claude-swap-backup or ~/.claude-worktrees are exactly that, and
|
|
31
|
+
// neither is a place to send work.
|
|
31
32
|
//
|
|
32
|
-
// WHY THERE IS NO REFRESH BUTTON
|
|
33
|
+
// WHY THERE IS NO REFRESH BUTTON. It is not costly.
|
|
33
34
|
// The directory listing is cached on the home folder's own mtime, so the
|
|
34
35
|
// readdir happens once and then only when a folder appears or disappears; the
|
|
35
36
|
// login inside each candidate is read through `accountIdentity`, which is
|
|
36
|
-
// already cached on that file's mtime. Measured on
|
|
37
|
-
//
|
|
38
|
-
//
|
|
37
|
+
// already cached on that file's mtime. Measured on a home folder of 200
|
|
38
|
+
// entries, 9 .claude-shaped, 5 of them directories, 3 holding a login, a
|
|
39
|
+
// warm call costs 0.029 ms, and it is asked on the supervisor's
|
|
39
40
|
// 15-second tick. A button would only be a thing to remember to press.
|
|
40
41
|
//
|
|
41
42
|
// THE DELIBERATE LIMIT: one level, under the home directory only. A profile
|
|
@@ -103,9 +104,9 @@ export function discoverProfiles({ home = os.homedir() } = {}) {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
/**
|
|
106
|
-
* THE PROFILES THE FLEET ACTUALLY RUNS ON:
|
|
107
|
-
*
|
|
108
|
-
* running on.
|
|
107
|
+
* THE PROFILES THE FLEET ACTUALLY RUNS ON: the configured ones first, exactly
|
|
108
|
+
* as written, then any subscription found on the disk that none of the
|
|
109
|
+
* configured ones is already running on.
|
|
109
110
|
*
|
|
110
111
|
* Profiles are the strings the rest of the app has always used: the literal
|
|
111
112
|
* 'default' for ~/.claude (which is what spawning with no CLAUDE_CONFIG_DIR
|
package/main/account-tooling.mjs
CHANGED
|
@@ -182,16 +182,15 @@ function list(names) {
|
|
|
182
182
|
//
|
|
183
183
|
// The Accounts page named its two rows `default` and `.claude-second`, which
|
|
184
184
|
// are folders, not people. So there was no way to tell from the screen which
|
|
185
|
-
// subscription either row was running on, and
|
|
186
|
-
// second
|
|
187
|
-
// what it
|
|
188
|
-
// browser
|
|
189
|
-
// that one back.
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
// reported: "Idk which is logged in nothing changed in settings".
|
|
185
|
+
// subscription either row was running on, and that can cost a person their
|
|
186
|
+
// second account. Say the page reports the second account signed out. They do
|
|
187
|
+
// exactly what it tells them to do, run the command and type /login, and the
|
|
188
|
+
// browser is already signed in as the account the first row uses, so the login
|
|
189
|
+
// hands that one back. The other account that had been in that folder is gone,
|
|
190
|
+
// replaced by the same account the first row was already using; the config
|
|
191
|
+
// backups the CLI itself writes show the swap. Nothing on the page changes
|
|
192
|
+
// when it happens, because nothing on the page had ever said whose account it
|
|
193
|
+
// was: "Idk which is logged in nothing changed in settings".
|
|
195
194
|
//
|
|
196
195
|
// One line of the answer, read from the file Claude Code keeps its own account
|
|
197
196
|
// in. Null when the folder holds no login, which reads on the page as the one
|
package/main/agent-activity.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Ephemeral, local status from matched tool start/end events. A persisted trace
|
|
2
2
|
// is history and cannot prove a command is still running. Nothing here executes
|
|
3
3
|
// commands, records reasoning, or changes worker permissions.
|
|
4
|
-
import { plainCommand, shortPath } from '../shared/work-lines.mjs';
|
|
4
|
+
import { commandWork, isBookkeeping, plainCommand, shortPath } from '../shared/work-lines.mjs';
|
|
5
5
|
const clean = value => typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
|
|
6
6
|
function add(session, id, label, detail) {
|
|
7
7
|
if (!id || !label) return false;
|
|
@@ -11,12 +11,43 @@ function add(session, id, label, detail) {
|
|
|
11
11
|
return true;
|
|
12
12
|
}
|
|
13
13
|
function finish(session, id) { return session.activeTools?.delete(id) ?? false; }
|
|
14
|
-
|
|
14
|
+
// A turn ending clears what it was doing, but not a background job: that is
|
|
15
|
+
// still running, and the session is kept open waiting for it
|
|
16
|
+
// (main/claude-input.mjs), so the row keeps saying what it waits on.
|
|
17
|
+
function clear(session) {
|
|
18
|
+
let changed = false;
|
|
19
|
+
for (const [id, entry] of session.activeTools ?? []) {
|
|
20
|
+
if (entry.background) continue;
|
|
21
|
+
session.activeTools.delete(id);
|
|
22
|
+
changed = true;
|
|
23
|
+
}
|
|
24
|
+
return changed;
|
|
25
|
+
}
|
|
26
|
+
function backgroundJobs(session, tasks) {
|
|
27
|
+
const pending = session.activeTools ??= new Map();
|
|
28
|
+
const live = new Set(tasks.map(t => `bg:${t?.task_id}`));
|
|
29
|
+
let changed = false;
|
|
30
|
+
for (const [id, entry] of pending) if (entry.background && !live.has(id)) { pending.delete(id); changed = true; }
|
|
31
|
+
for (const task of tasks) {
|
|
32
|
+
const description = clean(task?.description);
|
|
33
|
+
const plain = commandWork(description);
|
|
34
|
+
const label = plain ? `${plain.doing} ${plain.subject}`.trim() : `Waiting on: ${description || 'a background job'}`;
|
|
35
|
+
if (add(session, `bg:${task?.task_id}`, label, description)) { pending.get(`bg:${task?.task_id}`).background = true; changed = true; }
|
|
36
|
+
}
|
|
37
|
+
return changed;
|
|
38
|
+
}
|
|
15
39
|
export function currentActivity(session) { return [...(session.activeTools?.values() ?? [])]; }
|
|
16
40
|
function toolLabel(name, input) {
|
|
17
41
|
const command = clean(input.command);
|
|
18
42
|
const file = clean(input.file_path ?? input.path);
|
|
19
|
-
if (name === 'Bash')
|
|
43
|
+
if (name === 'Bash') {
|
|
44
|
+
// WHAT IT IS DOING, IN WORDS, when the whole command is recognised. The
|
|
45
|
+
// line under a running row is the same event as the line in the thread
|
|
46
|
+
// above it, read in the present tense (`doing`, shared/work-lines.mjs).
|
|
47
|
+
const plain = commandWork(command);
|
|
48
|
+
if (plain) return [`${plain.doing} ${plain.subject}`.trim(), input.command ?? ''];
|
|
49
|
+
return ['Running ' + (plainCommand(command) || 'command'), input.command ?? ''];
|
|
50
|
+
}
|
|
20
51
|
if (['Read','Write','Edit','MultiEdit'].includes(name)) return [`${name === 'Read' ? 'Reading' : name === 'Write' ? 'Writing' : 'Editing'} ${shortPath(file) || 'file'}`, file];
|
|
21
52
|
if (['Grep','Glob','WebSearch'].includes(name)) return [`Searching ${clean(input.pattern ?? input.query) || 'files'}`, input.pattern ?? input.query ?? ''];
|
|
22
53
|
if (name === 'WebFetch') return ['Reading web page', input.url ?? ''];
|
|
@@ -28,9 +59,13 @@ export function claudeActivity(session, line) {
|
|
|
28
59
|
const obj = JSON.parse(line);
|
|
29
60
|
if (obj.parent_tool_use_id) return false;
|
|
30
61
|
if (obj.type === 'result') return clear(session);
|
|
62
|
+
if (obj.type === 'system' && obj.subtype === 'background_tasks_changed' && Array.isArray(obj.tasks)) return backgroundJobs(session, obj.tasks);
|
|
31
63
|
let changed = false;
|
|
32
64
|
for (const part of Array.isArray(obj.message?.content) ? obj.message.content : []) {
|
|
33
65
|
if (obj.type === 'assistant' && part.type === 'tool_use') {
|
|
66
|
+
// The store's own records never become the word on the row: taking the
|
|
67
|
+
// item is not what the session is doing (w-0bd0d8b2ef).
|
|
68
|
+
if (isBookkeeping(part.name)) continue;
|
|
34
69
|
const [label, detail] = toolLabel(part.name, part.input ?? {});
|
|
35
70
|
changed = add(session, part.id, label, detail) || changed;
|
|
36
71
|
}
|
|
@@ -47,9 +82,14 @@ export function codexActivity(session, method, params) {
|
|
|
47
82
|
if (method !== 'item/started') return false;
|
|
48
83
|
if (item.status && !['inProgress', 'in_progress', 'running'].includes(item.status)) return false;
|
|
49
84
|
switch (item.type) {
|
|
50
|
-
case 'commandExecution':
|
|
85
|
+
case 'commandExecution': {
|
|
86
|
+
const plain = commandWork(clean(item.command));
|
|
87
|
+
const label = plain ? `${plain.doing} ${plain.subject}`.trim() : `Running ${plainCommand(clean(item.command)) || 'command'}`;
|
|
88
|
+
return add(session, item.id, label, item.command);
|
|
89
|
+
}
|
|
51
90
|
case 'fileChange': return add(session, item.id, 'Editing files', (Array.isArray(item.changes) ? item.changes : []).map(c => c?.path ?? '').join('\n'));
|
|
52
|
-
case 'mcpToolCall': return
|
|
91
|
+
case 'mcpToolCall': return isBookkeeping(clean(item.tool)) ? false
|
|
92
|
+
: add(session, item.id, `Using ${clean(item.tool).replace(/_/g, ' ') || 'tool'}`, `${item.server ?? ''} · ${item.tool ?? ''}`);
|
|
53
93
|
case 'webSearch': return add(session, item.id, 'Searching the web', item.query ?? '');
|
|
54
94
|
default: return false;
|
|
55
95
|
}
|
package/main/agent-schedule.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// WHEN
|
|
1
|
+
// WHEN THE USER ASKED TO SEE AN AGENT AGAIN, AND WHEN THEY ASKED NOT TO. One small
|
|
2
2
|
// durable map, and the only state the app keeps about a session it did not start.
|
|
3
3
|
//
|
|
4
4
|
// Three facts per agent and they are different facts. `runAt` is a moment she
|
|
@@ -41,9 +41,9 @@ import { agentKey } from '../shared/agents.mjs';
|
|
|
41
41
|
|
|
42
42
|
const num = (v) => (Number.isFinite(Number(v)) ? Math.max(0, Math.trunc(Number(v))) : 0);
|
|
43
43
|
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
44
|
+
// THE USER'S SIDE OF A THREAD IS SMALL, and this file is read on every draw. A
|
|
45
|
+
// real session has a handful of human turns, typically a sentence or two each.
|
|
46
|
+
// The card shows three. Eight, at 600
|
|
47
47
|
// characters, is far more than anything draws and small enough that a session
|
|
48
48
|
// left running for a month cannot grow the file without bound.
|
|
49
49
|
const KEEP_SPOKE = 8;
|
|
@@ -116,8 +116,8 @@ export class AgentSchedule {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// An entry that says none of "later", "seen", "I spoke to it" or "here is what
|
|
119
|
-
//
|
|
120
|
-
// same list as it was:
|
|
119
|
+
// the user said" is not an entry. The last of those four is why this is not the
|
|
120
|
+
// same list as it was: the user's words outlive the reply that carried them, so an
|
|
121
121
|
// entry holding only `spoke` is still the record of a conversation.
|
|
122
122
|
_keep(key, entry, now) {
|
|
123
123
|
if (entry.runAt > now || entry.doneThrough > 0 || entry.repliedAt > 0 || entry.spoke?.length) this.byKey[key] = entry;
|
|
@@ -155,8 +155,8 @@ export class AgentSchedule {
|
|
|
155
155
|
// undid the close, saying a session was working on something she had
|
|
156
156
|
// already finished with.
|
|
157
157
|
//
|
|
158
|
-
// BUT IT DOES NOT UNSAY WHAT
|
|
159
|
-
// and `spoke` is the ONLY record anywhere that
|
|
158
|
+
// BUT IT DOES NOT UNSAY WHAT THE USER SAID. This used to empty `spoke` as well,
|
|
159
|
+
// and `spoke` is the ONLY record anywhere that the user ever spoke to a session:
|
|
160
160
|
// Claude Code stamps an injected message `isMeta` and the transcript reader
|
|
161
161
|
// drops it, correctly, so nothing else on the machine knows. Measured on
|
|
162
162
|
// session-46: four of her messages reached that session and the file holds
|
|
@@ -200,12 +200,12 @@ export class AgentSchedule {
|
|
|
200
200
|
*
|
|
201
201
|
* It is here because it is the only place that knows. Claude Code stamps a
|
|
202
202
|
* message injected over its socket `isMeta: true` and wraps it in "Another
|
|
203
|
-
* Claude session sent a message…
|
|
204
|
-
*
|
|
205
|
-
* what
|
|
203
|
+
* Claude session sent a message…", so the transcript reader drops it, and
|
|
204
|
+
* without this record messages sent from Agentbox left the row saying only
|
|
205
|
+
* what the session was started with days earlier.
|
|
206
206
|
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
207
|
+
* Another of the user's sessions pinging this one is a real peer, and drawing
|
|
208
|
+
* it as the user's words would be a lie on the one card built to be trusted. So the record
|
|
209
209
|
* is written at the moment of delivery, by the only process that knows it
|
|
210
210
|
* delivered it.
|
|
211
211
|
*/
|
|
@@ -236,7 +236,7 @@ export class AgentSchedule {
|
|
|
236
236
|
if (entry.runAt > now) { out.runAt = entry.runAt; out.runAtSetAt = entry.setAt; }
|
|
237
237
|
if (entry.doneThrough > 0) out.doneThrough = entry.doneThrough;
|
|
238
238
|
if (entry.repliedAt > 0) out.repliedAt = entry.repliedAt;
|
|
239
|
-
// the card quotes what
|
|
239
|
+
// the card quotes what the user said last, and a session that has since
|
|
240
240
|
// stopped is exactly when she most needs to see it.
|
|
241
241
|
if (entry.spoke?.length) out.spoke = entry.spoke;
|
|
242
242
|
return out;
|