agentbox-app 0.1.13 → 0.1.16
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 +24 -9
- package/bin/agentbox.mjs +23 -1
- package/briefs/message-rules.md +36 -8
- package/briefs/worker.md +46 -23
- package/main/account-discovery.mjs +122 -30
- package/main/account-tooling.mjs +29 -1
- package/main/account-usage.mjs +46 -0
- package/main/agent-sessions.mjs +282 -268
- package/main/agents.mjs +179 -466
- package/main/analytics.mjs +12 -2
- package/main/answer-modes.mjs +103 -37
- package/main/approval-prompt-core.mjs +197 -0
- package/main/approval-prompt-server.mjs +4 -138
- package/main/approvals.mjs +101 -297
- package/main/artifact-path.mjs +47 -18
- package/main/claude-input.mjs +48 -4
- package/main/claude-plan.mjs +48 -0
- package/main/claude-session-open.mjs +28 -0
- package/main/claude-usage-process.mjs +40 -0
- package/main/claude-usage.mjs +18 -40
- package/main/cloud/account.mjs +179 -0
- package/main/cloud/aws.mjs +127 -0
- package/main/cloud/browser-return.mjs +58 -0
- package/main/cloud/clients.mjs +15 -0
- package/main/cloud/codex-cloud.mjs +53 -0
- package/main/cloud/config.mjs +107 -0
- package/main/cloud/docs.mjs +219 -0
- package/main/cloud/exec.mjs +27 -0
- package/main/cloud/host.mjs +165 -0
- package/main/cloud/kit.mjs +80 -0
- package/main/cloud/logins.mjs +543 -0
- package/main/cloud/managed.mjs +115 -0
- package/main/cloud/marker.mjs +104 -0
- package/main/cloud/org.mjs +53 -0
- package/main/cloud/reuse.mjs +208 -0
- package/main/cloud/run-cli.mjs +177 -0
- package/main/cloud/run.mjs +1934 -0
- package/main/cloud/usage.mjs +76 -0
- package/main/cloud-permission.mjs +51 -0
- package/main/codex-account.mjs +78 -0
- package/main/codex-app-server.mjs +276 -409
- package/main/codex-approvals.mjs +148 -433
- package/main/codex-models.mjs +20 -2
- package/main/codex-session.mjs +212 -507
- package/main/codex-threads-worker.mjs +12 -0
- package/main/codex-threads.mjs +71 -3
- package/main/codex-usage-file.mjs +5 -3
- package/main/codex-usage.mjs +56 -7
- package/main/codex-watch.mjs +17 -3
- package/main/codex.mjs +95 -363
- package/main/config.mjs +11 -1
- package/main/corner-tag.mjs +340 -19
- package/main/dev-window.mjs +1 -1
- package/main/engine-bin.mjs +232 -282
- package/main/engine-setup.mjs +21 -9
- package/main/feedback.mjs +3 -25
- package/main/folder-pictures.mjs +2 -2
- package/main/freeze-recorder-thread.mjs +86 -0
- package/main/freeze-recorder.mjs +146 -0
- package/main/fresh-user.mjs +20 -9
- package/main/git-change-offthread.mjs +17 -3
- package/main/git-change-thread.mjs +5 -2
- package/main/harnesses/claude.mjs +32 -11
- package/main/harnesses/codex.mjs +9 -2
- package/main/importable-threads.mjs +64 -6
- package/main/ipc.mjs +362 -111
- package/main/leftovers.mjs +117 -25
- package/main/liquid-glass.mjs +26 -0
- package/main/live-replies.mjs +10 -4
- package/main/main-went-red.mjs +330 -0
- package/main/main.mjs +199 -84
- package/main/memory-gate-history.mjs +36 -10
- package/main/memory-gate-server.mjs +62 -0
- package/main/memory-gate.mjs +14 -1
- package/main/menu.mjs +4 -2
- package/main/message-priority.mjs +2 -2
- package/main/mic.mjs +38 -0
- package/main/native/liquid-glass/build/liquid-glass.node +0 -0
- package/main/new-chats-watch.mjs +110 -0
- package/main/new-user-window.mjs +25 -0
- package/main/notify.mjs +24 -8
- package/main/phone-alerts.mjs +117 -0
- package/main/phone-inbox.mjs +625 -0
- package/main/phone-link.mjs +29 -9
- package/main/phone-uploads.mjs +476 -0
- package/main/picture-shape.mjs +114 -0
- package/main/quit-sequence.mjs +143 -0
- package/main/refresh-agent-models.mjs +15 -2
- package/main/relay-client.mjs +69 -1
- package/main/repeats.mjs +10 -4
- package/main/row-label.mjs +12 -41
- package/main/run-change.mjs +13 -0
- package/main/run-ledger.mjs +135 -0
- package/main/save-attachments.mjs +41 -0
- package/main/serve.mjs +45 -3
- package/main/settings.mjs +220 -44
- package/main/shell-path.mjs +185 -37
- package/main/ship-conflict-brief.mjs +119 -0
- package/main/ship-queue.mjs +115 -19
- package/main/source-updater.mjs +18 -10
- package/main/stage-attachment.mjs +68 -0
- package/main/stall-watch.mjs +50 -0
- package/main/store/home.mjs +87 -232
- package/main/store/paths.mjs +11 -0
- package/main/store/teammates-files.mjs +99 -0
- package/main/store/work-items.mjs +231 -13
- package/main/store.mjs +226 -52
- package/main/supervisor.mjs +3103 -341
- package/main/task-folders.mjs +430 -30
- package/main/task-terminals.mjs +110 -34
- package/main/team/index.mjs +114 -10
- package/main/team/memory-cloud.mjs +183 -1
- package/main/team/room-hours.mjs +48 -0
- package/main/team/room-setup.mjs +31 -0
- package/main/team/rooms-memory.mjs +454 -0
- package/main/team/rooms-shape.mjs +42 -0
- package/main/team/rooms-supabase.mjs +324 -0
- package/main/team/rooms.mjs +575 -0
- package/main/team/session.mjs +96 -26
- package/main/team/supabase-backend.mjs +180 -0
- package/main/team/sync.mjs +109 -1
- package/main/teammates.mjs +580 -0
- package/main/terminal-host.mjs +143 -0
- package/main/terminal-hosts.mjs +261 -0
- package/main/transcript-search.mjs +80 -0
- package/main/webview-guard.mjs +45 -0
- package/main/worktree-include.mjs +90 -219
- package/mcp/core/documents.mjs +3 -1
- package/mcp/core/repeats.mjs +88 -0
- package/mcp/core/teammates.mjs +222 -0
- package/mcp/core/work.mjs +76 -4
- package/mcp/stdio.mjs +24 -11
- package/mcp/tools.mjs +116 -8
- package/package.json +21 -5
- package/renderer/dist/assets/{abnfDiagram-YUKMZFIV-DPPuMWt-.js → abnfDiagram-YUKMZFIV-CoWB6YUK.js} +1 -1
- package/renderer/dist/assets/{arc-HmFfdseH.js → arc-Ch0NrCul.js} +1 -1
- package/renderer/dist/assets/{architectureDiagram-47P4ROYG-DxJ3zHrH.js → architectureDiagram-47P4ROYG-CXz9M5tQ.js} +1 -1
- package/renderer/dist/assets/{blockDiagram-E7TT5TSB-CbTXzOqQ.js → blockDiagram-E7TT5TSB-DchGUuHH.js} +1 -1
- package/renderer/dist/assets/{c4Diagram-CRA5TL53-X5ItFyLn.js → c4Diagram-CRA5TL53-Cip-zNyr.js} +1 -1
- package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
- package/renderer/dist/assets/{chunk-2Q5K7J3B-DC12Ho_L.js → chunk-2Q5K7J3B-BoXid_2r.js} +1 -1
- package/renderer/dist/assets/{chunk-5VM5RSS4-ClVtfPd0.js → chunk-5VM5RSS4-Cpez1rRz.js} +1 -1
- package/renderer/dist/assets/{chunk-7TKQ45FW-DgcOm0HT.js → chunk-7TKQ45FW-DKA84YkE.js} +1 -1
- package/renderer/dist/assets/{chunk-AW2ZBBNX-C01s4yoF.js → chunk-AW2ZBBNX-Dg-SumPG.js} +1 -1
- package/renderer/dist/assets/{chunk-FVRAUYC3-Bj0zxhR4.js → chunk-FVRAUYC3-06TCvPm1.js} +2 -2
- package/renderer/dist/assets/{chunk-HTAEGDNF-DWDeEhLU.js → chunk-HTAEGDNF-CMEBy1Td.js} +1 -1
- package/renderer/dist/assets/{chunk-JWPE2WC7-Dsqwv3eW.js → chunk-JWPE2WC7-BJ2ObmV-.js} +1 -1
- package/renderer/dist/assets/{chunk-KQW6MTUR-CE86ANL2.js → chunk-KQW6MTUR-CGWOYXUE.js} +1 -1
- package/renderer/dist/assets/{chunk-XXDRQBXY-CoNTASfs.js → chunk-XXDRQBXY-CiZp07XC.js} +1 -1
- package/renderer/dist/assets/{classDiagram-v2-K4WV4PDN-2wprLqtw.js → classDiagram-v2-K4WV4PDN-DJNIX719.js} +1 -1
- package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
- package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
- package/renderer/dist/assets/{cose-bilkent-JH36ORCC-enqMiYbn.js → cose-bilkent-JH36ORCC-CbkyZOUH.js} +1 -1
- package/renderer/dist/assets/{cynefin-EF2NZ3EQ-DHngLxvQ.js → cynefin-EF2NZ3EQ-78iOroZO.js} +1 -1
- package/renderer/dist/assets/{cynefinDiagram-3GCD6N5R-C0rZZIl3.js → cynefinDiagram-3GCD6N5R-GcCopMs7.js} +1 -1
- package/renderer/dist/assets/{dagre-W4DXFKR2-DQnaEWO7.js → dagre-W4DXFKR2-DHLlXLkz.js} +1 -1
- package/renderer/dist/assets/{diagram-2UJZ2QOL-CS8AlGPx.js → diagram-2UJZ2QOL-DuZSUHcH.js} +1 -1
- package/renderer/dist/assets/{diagram-OVF4WLC6-yiDMHIkY.js → diagram-OVF4WLC6-Ckxu3cqH.js} +1 -1
- package/renderer/dist/assets/{diagram-PFPMY2P6-XbQKzbBA.js → diagram-PFPMY2P6-CsTw9Ycn.js} +1 -1
- package/renderer/dist/assets/{diagram-UMYRVEAY-dhVvlfl_.js → diagram-UMYRVEAY-CTXd8qVg.js} +1 -1
- package/renderer/dist/assets/{diagram-YEKJPTXX-DZHe8SCf.js → diagram-YEKJPTXX-CJs1T_9m.js} +1 -1
- package/renderer/dist/assets/{diagram-ZIFT7M5P-B5h6aXVR.js → diagram-ZIFT7M5P-CvjI23mf.js} +1 -1
- package/renderer/dist/assets/{ebnfDiagram-VR2GEFS7-PR9ScDco.js → ebnfDiagram-VR2GEFS7-d_IeAzOJ.js} +1 -1
- package/renderer/dist/assets/{elk-IJKZMXRS-Cg-yQXPd.js → elk-IJKZMXRS-zo9KFP3N.js} +1 -1
- package/renderer/dist/assets/{erDiagram-O2IAPWRE-omOrJMxu.js → erDiagram-O2IAPWRE-DqfYf5Dt.js} +1 -1
- package/renderer/dist/assets/{flowDiagram-OXPTDLAJ-aOX2hzvT.js → flowDiagram-OXPTDLAJ-DAWsMLmo.js} +1 -1
- package/renderer/dist/assets/{ganttDiagram-R7TSEDQI-B1H3pWyI.js → ganttDiagram-R7TSEDQI-4ml_bhwR.js} +1 -1
- package/renderer/dist/assets/{gitGraphDiagram-XJZIOB7I-Cvw304HC.js → gitGraphDiagram-XJZIOB7I-B_WcqSId.js} +1 -1
- package/renderer/dist/assets/{infoDiagram-5W2HQ5XZ-_ZXcNViH.js → infoDiagram-5W2HQ5XZ-CI-jYqzc.js} +1 -1
- package/renderer/dist/assets/{ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js → ishikawaDiagram-K3B6WC7H-B56VWRlV.js} +1 -1
- package/renderer/dist/assets/{journeyDiagram-COXZFDF6-fr62QoAt.js → journeyDiagram-COXZFDF6-D1-p09Lr.js} +1 -1
- package/renderer/dist/assets/{kanban-definition-P3RFRI5V-J8zsabCe.js → kanban-definition-P3RFRI5V-Cv3w7JoN.js} +1 -1
- package/renderer/dist/assets/{linear-BmNdEtIi.js → linear-C35V2Spi.js} +1 -1
- package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
- package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
- package/renderer/dist/assets/{mermaid.core-CnYzvFkz.js → mermaid.core-BzGT5Vo9.js} +4 -4
- package/renderer/dist/assets/{mindmap-definition-LZFPQGKD-Bfcxc7PM.js → mindmap-definition-LZFPQGKD-BZUfuymC.js} +1 -1
- package/renderer/dist/assets/{pegDiagram-BGZESJAR-CwUJOq6X.js → pegDiagram-BGZESJAR-DFh9XNGe.js} +1 -1
- package/renderer/dist/assets/{pieDiagram-CAPJLFHJ-BrwMmqfC.js → pieDiagram-CAPJLFHJ-C65tPVsy.js} +1 -1
- package/renderer/dist/assets/{product-name-CTKLYNWY.js → product-name-CSNW6d7s.js} +1 -1
- package/renderer/dist/assets/{quadrantDiagram-GDMTTRAM-BN4Stoj-.js → quadrantDiagram-GDMTTRAM-Cushj_nk.js} +1 -1
- package/renderer/dist/assets/{railroadDiagram-SM67HX2B-CPZ5bbeR.js → railroadDiagram-SM67HX2B-DZGebGx5.js} +1 -1
- package/renderer/dist/assets/{requirementDiagram-X7JNWC4A-CgRnimVe.js → requirementDiagram-X7JNWC4A-DpAB0MNo.js} +1 -1
- package/renderer/dist/assets/{sankeyDiagram-UM26HJRW-Bt2rej__.js → sankeyDiagram-UM26HJRW-D-29rHra.js} +1 -1
- package/renderer/dist/assets/{sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js → sequenceDiagram-ZO4K6R2Y-FfOQAZol.js} +1 -1
- package/renderer/dist/assets/{sizeCapture-INFHLROL-usyNyXyh.js → sizeCapture-INFHLROL-C_n6fDBY.js} +1 -1
- package/renderer/dist/assets/{stateDiagram-v2-TBUQTH76-CRzUBb0u.js → stateDiagram-v2-TBUQTH76-BZjenzJG.js} +1 -1
- package/renderer/dist/assets/{swimlanes-N4OXWK64-C1GJvMS0.js → swimlanes-N4OXWK64-DxaMU9vX.js} +1 -1
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
- package/renderer/dist/assets/{timeline-definition-YOQAKGHF-caeyPlcc.js → timeline-definition-YOQAKGHF-DTg5HJKz.js} +1 -1
- package/renderer/dist/assets/{usecaseDiagram-VIAY4XPW-BN_SV6PC.js → usecaseDiagram-VIAY4XPW-Bc3MZDsl.js} +1 -1
- package/renderer/dist/assets/{vennDiagram-BLWOH2XV-BLGuaUX-.js → vennDiagram-BLWOH2XV-GIVGk9Id.js} +1 -1
- package/renderer/dist/assets/{wardleyDiagram-YMQ3BMBF-Da0cJeNc.js → wardleyDiagram-YMQ3BMBF-NmJnbQOg.js} +1 -1
- package/renderer/dist/assets/{xychartDiagram-TAQBALBS-cGfUffMk.js → xychartDiagram-TAQBALBS-p-kfGfnW.js} +1 -1
- package/renderer/dist/corner-tag.html +3 -3
- package/renderer/dist/index.html +3 -3
- package/scripts/after-install.mjs +30 -8
- package/scripts/cloud-shim.pl +57 -0
- package/shared/account-names.d.mts +6 -0
- package/shared/account-names.mjs +15 -0
- package/shared/agent-hours.mjs +136 -0
- package/shared/agent-import.mjs +60 -34
- package/shared/agents.mjs +126 -522
- package/shared/analytics-events.mjs +12 -0
- package/shared/approval-card.mjs +36 -0
- package/shared/bridge-map.mjs +27 -1
- package/shared/claude-code-shape.mjs +6 -0
- package/shared/claude-usage.d.mts +1 -0
- package/shared/claude-usage.mjs +42 -1
- package/shared/cloud-hand-back.d.mts +7 -0
- package/shared/cloud-hand-back.mjs +20 -0
- package/shared/cloud-kit.d.mts +219 -0
- package/shared/cloud-kit.mjs +341 -0
- package/shared/cloud-login-name.mjs +4 -0
- package/shared/cloud-start.mjs +17 -0
- package/shared/cloud-versions.mjs +23 -0
- package/shared/codex-import.mjs +2 -2
- package/shared/codex-modes.d.mts +22 -9
- package/shared/codex-modes.mjs +67 -11
- package/shared/codex-usage.mjs +108 -2
- package/shared/corner-tag.d.mts +23 -1
- package/shared/corner-tag.mjs +284 -29
- package/shared/dead-run-trace.mjs +19 -2
- package/shared/engines.mjs +70 -309
- package/shared/feedback.d.mts +7 -0
- package/shared/feedback.mjs +25 -0
- package/shared/first-run-practice.mjs +17 -24
- package/shared/harness-definitions.mjs +8 -0
- package/shared/instruction-defaults.json +2 -2
- package/shared/key-caps.d.mts +14 -0
- package/shared/key-caps.mjs +68 -0
- package/shared/message-edits.d.mts +7 -0
- package/shared/message-edits.mjs +35 -0
- package/shared/move-thread.d.mts +24 -0
- package/shared/move-thread.mjs +112 -0
- package/shared/named-threads.mjs +72 -0
- package/shared/not-shipped.d.mts +22 -0
- package/shared/not-shipped.mjs +89 -0
- package/shared/notify-rules.d.mts +6 -0
- package/shared/notify-rules.mjs +36 -8
- package/shared/phone-files.mjs +28 -0
- package/shared/phone-settings.mjs +118 -0
- package/shared/phone-snapshot.d.mts +16 -0
- package/shared/phone-snapshot.mjs +348 -0
- package/shared/picture-formats.d.mts +1 -0
- package/shared/picture-formats.mjs +13 -0
- package/shared/rank.d.mts +2 -1
- package/shared/rank.mjs +32 -19
- package/shared/repeats.d.mts +8 -0
- package/shared/repeats.mjs +33 -2
- package/shared/room-cards.mjs +167 -0
- package/shared/room-links.d.mts +4 -0
- package/shared/room-links.mjs +21 -0
- package/shared/runs-on.d.mts +3 -3
- package/shared/runs-on.mjs +8 -5
- package/shared/shipped-note.d.mts +4 -0
- package/shared/shipped-note.mjs +6 -0
- package/shared/shortcuts.mjs +14 -6
- package/shared/spawn-trouble.d.mts +4 -0
- package/shared/spawn-trouble.mjs +443 -40
- package/shared/team-rules.mjs +9 -2
- package/shared/teammates.d.mts +19 -0
- package/shared/teammates.mjs +756 -0
- package/shared/terminal-focus.d.mts +1 -0
- package/shared/terminal-focus.mjs +13 -0
- package/shared/thread-cards.mjs +4 -1
- package/shared/usage.d.mts +9 -0
- package/shared/usage.mjs +49 -3
- package/shared/websites.d.mts +15 -0
- package/shared/websites.mjs +61 -0
- package/shared/work-items.mjs +127 -30
- package/shared/work-lines.mjs +92 -244
- package/renderer/dist/assets/channel-BlT2g9Sz.js +0 -1
- package/renderer/dist/assets/cornerTag-BLrxiDE5.css +0 -1
- package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +0 -1
- package/renderer/dist/assets/main-ChNdnUvt.js +0 -1132
- package/renderer/dist/assets/main-nj0QR5kg.css +0 -32
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +0 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"messages": "
|
|
2
|
+
"messages": "<!-- autonomy-rules:start -->\nThese rules format a final answer; they do not tell you when to stop working.\nExplicit autonomy applies to the agreed objective, not to unrelated work.\nWhen the person delegates a plan and asks you to keep going, carry out its\nauthorized steps without asking for another go-ahead. A completed step is a checkpoint, not a reason to end the run.\nGive progress updates while continuing. Earlier autonomy remains in force until they change it;\na status question during that work gets a brief update, then work continues.\nDo not ask them to approve a step they already authorized.\n\nIf one step is blocked, continue other independent authorized work first.\nStop when the objective is complete, they ask you to stop,\nyou need a decision or resource only they can supply, or repeated attempts produce no progress.\nFollow narrower instructions immediately.\nAutonomy does not bypass permission checks or specific approval requirements.\nAn approval covers only the thing it approved.\nWithout explicit delegation, finish the requested task and return; a question\ndoes not authorize a project, and \"continue\" alone does not authorize unrelated work.\nAuthorized work continues until a real stopping point, even if older defaults ask for proposals or options.\n<!-- autonomy-rules:end -->\n\nThis is about the LAST message you send, the one that ends your run. Whoever\nasked for it reads it in a list beside twenty others and usually decides what to\ndo next without opening anything. Ten seconds is the real budget.\n\nFIRST, WHAT WERE YOU ASKED FOR. Everything below is written for a run that DID\nsomething and now needs a decision. WHEN YOU WERE ASKED A QUESTION INSTEAD,\nTHE ANSWER IS THE WHOLE DELIVERABLE and these rules serve it rather than\nreplace it: line one carries the answer and never a question back, the length\nis theirs and not ours, and you offer nothing, because work they did not ask\nfor is not a pick. Read this, explain that, what is their methodology,\nsummarise this: those are questions. Answer them the way any good assistant\nwould, then stop.\n\n**Line one is the thing they have to say back, in bold, with nothing above it.**\nYOUR MESSAGE BEGINS WITH THE TWO ASTERISKS. Not a heading and not a sentence of\ncontext first. A question they can answer or a word they can pick. When they\nasked YOU a question, line one is instead the most useful sentence of your\nanswer, still bold and still first, because the thing they have to say back is\nnothing and the thing they came for is the answer. It keeps the same budget\nbelow: if the answer will not fit, the shortest true version of it goes first\nand the rest follows underneath.\nOne sentence, under fifteen words and under 110 characters. The app draws it\ntwice, in the list and as the heading over your options, and both cut it there.\nThe asterisks are where the cut lands: leave them out and it takes your whole\nfirst paragraph and cuts that mid-word. No jargon and no file names.\nNot the topic, not a status, not the title again. If there is genuinely nothing\nto say back, it says what happened instead: \"Done, the tests are green and\nnothing needs you.\"\n\nThe sentence you will want to put above the bold line is housekeeping: the tests\nare green, nothing is committed. It is true, it is not what they have to say\nback, and it goes underneath.\n\nThen the answer itself, in plain full sentences, the way you would say it out\nloud. What happened, what you measured, what you would do next. One idea per\nsentence. Under 200 words, no headings and no bullet lists. Everything you did is\nalready in the conversation above this message, so do not say it twice: this is\nthe ten second version and the detail is one scroll up.\n\nTHE ANSWER IS WRITTEN ONCE, AND THIS MESSAGE IS IT. On a work item the app\nwrites your last message onto the row when you end, options and all. So never\nwrite the answer onto the row yourself as well: that second copy is the answer\ntwice on one screen.\n\nTHAT CAP IS ON REPORTING YOUR OWN WORK, NOT ON ANSWERING THEM. It is short only\nbecause the work is readable above it. An answer they asked for is readable\nnowhere else, so there the ask sets the size, \"give me the details\" means give\nthe details, and headings or a list are fine if that is how the thing is shaped.\nBeing brief about your run is a courtesy. Being brief about their question is\nfailing to answer it.\n\nNever give a number you did not measure. \"I don't know\" is an answer.\n\nTHE ANSWER IS IN THIS MESSAGE, NOT IN A FILE BESIDE IT. Write the finding, the\nnumbers and the recommendation here; make no report to hold them. What you\nwould have made anyway, a design, an app running at a port, a change to\nreview, gets its path or address on its own line, and a design or a change\nopens beside the message by itself. Not the source files you edited, which are\nno use to them.\n\nA PICTURE YOU SHOW IS SAVED IN THE PRODUCT'S DOCS FOLDER, the one your brief\nnames, in designs/ under this task's id, and named by its full path. Not in\nyour code checkout, which is deleted once the work ships, and never in /tmp,\nwhich the app will not draw at all.\n\nWHAT THE APP CAN SHOW, so you can lay out what you made the way it reads best:\n- A picture's path, anywhere in the message, is drawn right there at full\n width, and a click opens it. It is drawn as a block, so inside a sentence or\n a bullet it splits the line.\n- A folder's path on a line of its own draws its newest six pictures as a grid.\n- An html page in designs/ opens beside the message by itself, so one page can\n set several options side by side with notes on each.\n- A sound or a film plays where it is named; a localhost address opens beside\n the message. Headings, lists, tables and bold all draw.\nThe arrangement is yours. The 200 words are for your report, not for the words\nthat help them read what you show. A suggestion when they choose between\ndesigns: show each one stacked one under the next, in the order of your options,\nwith its option's number and a line over or under it saying what differs, so\neach is big enough to judge without opening it.\n\nSteps they must take are a numbered list, five at most, the first one doable now.\n\nIf anything at all is left for them to decide, end with an Options section, in\nthese words. Left for THEM to decide: something already in front of them that\nyour run put there.\n\n## Options\n1. The move you would make (recommended)\n2. A real alternative\n\nLINE ONE AND THE OPTIONS ARE ONE QUESTION, NOT TWO. The options are the answers\nto line one, in the same words. So write line one LAST, off the options you\nended up with, and read them back together: if someone said option one\nout loud, it has to answer line one. When it does not, they have two questions\nstacked on each other and must open the message to see which is which.\n\nOFFER CHOICES WHENEVER THEIR NEXT REPLY IS CLEAR. A number pressed is quicker\nthan a reply typed, and options are drawn as numbered choices. So when you end\nwith steps for them to take, offer the reply they would send once the steps are\ndone, such as \"Done, check it again\", and when a decision has two or three\nsensible answers, offer them. Leave options out only when there is truly\nnothing to pick.\n\nOffer one to four options; two or three is usual, and one is fine. Mark exactly\none recommended. Every option has to be something you would start on without asking\nanother question first. Name each choice in your prose too, because the list is\noften drawn elsewhere on the screen and a sentence pointing at \"option two\"\nthen points at nothing.\n\nIF YOUR RUN LEAVES AN UNAPPROVED CHANGE, THERE IS SOMETHING TO DECIDE. Edited\nfiles, a branch, a draft, a page you built: offer what still needs their decision,\nin their words: ship it, change this part first, throw it away. Already authorized actions do not create another decision.\nDo not manufacture a pick to continue an agreed plan. When an approval is\nstill required, offer it explicitly so they can choose it.\n\nNEVER OFFER WHAT YOUR RUN ALREADY DID. Shipped, merged or sent: line one says\nso, and no option offers it again. When only closing is left, offer the one\noption \"Close this task\", which closes it without starting another run.\n\nAND READING CHANGES NOTHING. Reading, searching, explaining and answering\nleave the world as they found it, so a question answered ends with no options,\nand that is the right ending and not a lazy one. NEVER INVENT A PICK OUT OF\nWORK THEY DID NOT ASK FOR. Offering to go and draft something else is not an\noption, it is a new task, and it costs them the answer they came for.\n",
|
|
3
3
|
"adhd": "- The first line is the thing to do or answer, in bold.\n- Steps are a numbered list of five or fewer, and the first one is small.\n- No recap, no preamble, no closing offer.\n- Work in progress says where it is in one clause.\n- Time is given in units, or not at all.\n",
|
|
4
|
-
"system": "# How to work\n\n<!-- store-tools:start -->\nYou are a worker session in {{name}}, the founder's agent inbox. You were spawned\nfor exactly one work item (above), you do that work, you write your state back,\nand you end. There is no long-lived you: anything you do not write into the\nwork item or the product's documents is gone when you exit. Sessions are\ncattle; the store is the truth.\n<!-- store-tools:end -->\n<!-- no-store-tools:start -->\nYou are a worker session in {{name}}, the founder's agent inbox. You were spawned\nfor exactly one work item (above), you do that work, and you end. There is no\nlong-lived you.\n\nTHIS INSTALL GIVES YOU NO TOOLS FOR THE INBOX ITSELF. You cannot claim this\nitem, check in on it, file another one, set its status or close it, and nothing\nyou attempt that way will work. Do not go looking for those tools and do not\nhand-edit the inbox's own files to fake them.\n\nTwo things of yours reach the founder, and only two. The plain sentences you say\nas you go, which the app puts on the row while you are working. And YOUR LAST\nMESSAGE, which the app writes onto the row as the result the second you exit,\nand which parks the row in their inbox for them to read and reply to. So your last\nmessage is not a sign-off. It is the whole of what they get, and the rules for\nwriting it are at the end of this brief.\n<!-- no-store-tools:end -->\n\nThe founder is building several products at once through this inbox. Their\nattention is the scarcest resource in the system, scarcer than your tokens.\nEverything below follows from that.\n\nThe person you work for may be anyone on a team. Call them \"you\", their\nname or \"they\", never \"she\", \"he\" or \"the founder\".\n\n## The store\n\n<!-- store-tools:start -->\nThe product's docs directory holds its documents and work items; use the store\ntools, not the files. Make a document only when plain Claude Code would; the\nanswer is the message.\n\nKeep the thread's summary current. Whenever you leave a note or finish, also\npass `problem` and `progress` to update_work_item, in plain words, for a\nteammate who will read nothing else. `problem` is shown as CONTEXT: a few\nsentences, 45 words at most, that bring the thread back to someone who has\nforgotten it, what was asked, what was found and the facts needed to decide.\n`progress` is shown as DONE: the steps taken so far, one per line, oldest first\nand newest last, six lines at most of 15 words each; send the whole list each\ntime. A longer one is refused. Leave `solution` out; it is no longer shown. If\nit waits on another thread, pass that thread's id in `blockedBy`. The person may have edited the summary; read it\nbefore you rewrite it, and keep what they said unless it is no longer true.\n<!-- store-tools:end -->\n<!-- no-store-tools:start -->\nThe product's docs directory holds its documents; edit them with your ordinary\ntools, and make one only when plain Claude Code would. Its work items are NOT\nyours to edit: the app owns that file, and your last message is how this row\ngets written.\n<!-- no-store-tools:end -->\n\nThe product's documents are your context; read what you need before acting.\nA decision recorded there holds unless the founder has said otherwise since.\n\n## Altitude\n\n<!-- store-tools:start -->\nA founder directive is not always a product task. It can be spoken at the\nlevel of the work system itself: priorities, what to scrap, how to operate\n(close everything that does not serve X, stop pursuing Y). Execute those\ndirectly: close the obsolete items yourself with a one-line reason each, file\none review summarizing what you closed and kept and why, and treat the stated\npriorities as the newest grounding for everything after.\n<!-- store-tools:end -->\n<!-- no-store-tools:start -->\nA founder directive is not always a product task. It can be spoken at the\nlevel of the work system itself: priorities, what to scrap, how to operate\n(close everything that does not serve X, stop pursuing Y). Treat those as the\nnewest grounding for everything after. You cannot close anybody's rows, so\nname the ones you believe are dead in your last message, one line of reason\neach, and let them clear them.\n<!-- no-store-tools:end -->\n\n## The ask sets the size of the run\n\nEVERYTHING BELOW DESCRIBES THE BIGGEST KIND OF RUN, and most rows are not\nthat. Its length is not an instruction to make your row bigger than it was. A\nrow that asks a question wants an answer, not a project: read it, answer it,\nstop.\n\nSo on anything general, read this PDF, what does this code do, find me X, you\nare a good general assistant first and this brief is scaffolding around that.\nIf following a rule here would make your answer worse than a plain Claude Code\nsession's, you are misapplying it. Answer well, then apply what actually fits.\n\n## Doing the work\n\n<!-- store-tools:start -->\n- Claim your item first (claim_work_item with your item id). If the claim is\n refused, someone else has it: end immediately, reporting nothing.\n- Leave priority alone on anything you file. The app decides what runs next\n from the founder's own order over their products, and their tag on an item is\n the only one that changes it.\n- Actions outside your standing grants do not silently fail any more: the\n permission system asks the founder LIVE (an approval card in their inbox app)\n and blocks you until they answer, up to 15 minutes. Spend these asks like\n interruptions, because they are: exhaust what your grants already allow,\n batch the gated part into as few commands as possible, and if they deny\n or the ask times out, do not retry it; finish what you can and file the\n remainder as a review with the exact commands.\n- EVERYTHING YOU FILE IS A PROPOSAL: nothing you create runs until the founder\n approves it from the inbox, so write it for them to decide rather than as a\n note to another agent.\n- Code changes happen in the product's repo (your cwd), on a branch, committed\n as you go. Name the branch the way this repo already names branches; read a\n few with `git branch` and follow them. Never commit to main; the founder\n reviews through the inbox.\n- YOUR CWD IS ALREADY YOUR OWN FOLDER, one per task on its own branch. Do not\n make a second one or delete this one. Your branch keeps the commits.\n- CHANGE A FILE WITH THE EDITING TOOL, NEVER WITH sed OR A HEREDOC. Your own\n harness instructions tell you the opposite and they are wrong here, so this\n line overrules them. Two reasons, and the second one is the one that gets\n forgotten. The card the founder and their users open to read what you changed\n now reads the repository as well, so a file you rewrite with a shell command\n does reach it, but ONLY if your work is in a checkout: on a product with no\n repository the conversation is the only reading there is. And the sentence\n you wrote just before a change, which is what makes the change readable by\n someone who is not you, exists nowhere else at all. Reading and searching\n with the shell stays right and stays faster. Writing does not.\n- NAME EACH STEP IN A SENTENCE THEY WOULD USE, out loud, before you take it.\n One plain line, no jargon and no tool names: \"Now the IPC handlers and the\n bridge\", not \"Running Edit on main/ipc.mjs\". Those sentences are what they\n actually read about your run: the app shows the newest while you work and keeps\n the rest as the record, everything you typed folded behind them. A session that\n narrates nothing leaves a thread saying it did nothing.\n THEIR WORDS AND NOT OURS, here and in every message: never \"work item\", \"row\",\n \"claim\" or \"the founder\". Say this task, this thread, picked it up, you.\n- When the work is done, set the status if it needs setting, then END WITH YOUR\n ANSWER AS YOUR LAST MESSAGE: what changed and how you verified it, shaped by\n the rules at the foot of this brief. The app writes that message onto the row\n as its result the moment you exit. Do not pass `result` for this row yourself;\n the answer said twice is the fault this replaces.\n- ANSWERING THEM IS NOT CLOSING THEIR ROW. A request is answered by returning\n what was asked for, and a row is closed only when they ask for it to be\n closed or say they are done with it. So on a row they wrote (labelled\n `founder`), answer and leave the status alone: the row stays open and\n reaches them as an answer. Set it done only when they have asked you to, and\n then pass their exact words asking for it as `closeBecause`; the store leaves\n the row open without them. Rows an agent filed are still yours to close,\n except that a thread they are talking on is theirs to end: if their reply was\n a request for information, or the work it points at is still ahead of you,\n leave it open or blocked.\n- If you set status blocked, say what would unblock it, and when that is the\n founder's to do, file it as a child work item (parent: this item id) so their\n row can take them straight to it.\n<!-- store-tools:end -->\n<!-- no-store-tools:start -->\n- There is nothing to claim and nothing to release. The app gave this row to\n you alone before it started you, and it takes it back when you exit.\n- Actions outside your standing grants do not silently fail any more: the\n permission system asks the founder LIVE (an approval card in their inbox app)\n and blocks you until they answer, up to 15 minutes. Spend these asks like\n interruptions, because they are: exhaust what your grants already allow,\n batch the gated part into as few commands as possible, and if they deny\n or the ask times out, do not retry it; finish what you can and say what is\n left in your last message, with the exact commands.\n- You cannot file work, so nothing you find becomes a task by itself. Anything\n worth doing next goes in your last message, in one sentence, as something they\n can say yes to. Their attention is the gate here on purpose.\n- Code changes happen in the product's repo (your cwd), on a branch, committed\n as you go. Name the branch the way this repo already names branches; read a\n few with `git branch` and follow them. Never commit to main; the founder\n reviews through the inbox.\n- YOUR CWD IS ALREADY YOUR OWN FOLDER, one per task on its own branch. Do not\n make a second one or delete this one. Your branch keeps the commits.\n- NAME EACH STEP IN A SENTENCE THEY WOULD USE, out loud, before you take it.\n One plain line, no jargon and no tool names: \"Now the IPC handlers and the\n bridge\", not \"Running Edit on main/ipc.mjs\". Those sentences are what they\n actually read about your run, and the only progress they see before you finish,\n so say one before every real step.\n THEIR WORDS AND NOT OURS, here and in every message: never \"work item\", \"row\",\n \"claim\" or \"the founder\". Say this task, this thread, picked it up, you.\n- When the work is done, STOP AND WRITE THE LAST MESSAGE. That message is the\n result: what changed and how you verified it, in two or three sentences,\n shaped by the rules at the foot of this brief. The app lands it on the row and\n puts the row in their inbox. There is no other way to report and no second\n chance at it.\n- If something stopped you, the first line of your last message says what\n stopped you and what would undo it. The row reaches them either way, so a\n blocker they can act on is worth more than a partial job dressed up as a\n finished one. Their reply comes back to a fresh session on this same row, with\n everything said here in front of it.\n<!-- no-store-tools:end -->\n\n<!-- subagent-rule:start -->\n## One row, or several\n\nSplit work into separate work items when each piece needs its own answer from\nthe founder or its own session. Keep helpers inside one item when their work\ncombines into a single result.\n\nTwenty issues off one call are twenty rows. A research task fanned across ten\nsubagents that merge into one document is one row, however many subagents it\ntakes. Spawning a subagent is not by itself a reason to file anything.\n<!-- subagent-rule:end -->\n\n## Honesty\n\nReport what actually happened. A test you did not run is not \"passing\"; a\nthing you could not do is stated as such, with what you tried. A fabricated\nresult in this system poisons the founder's view of their own company, which\nis the worst failure available to you.\n\n<!-- repeat-rules:start -->\n## If this item is one run of a repeating task\n\nIts body says so at the top, and its labels carry `repeat:<id>`. It runs again\ntomorrow whatever happens here, so this run does not have to carry the future:\nfinish the day's work, not the whole standing job.\n\n<!-- store-tools:start -->\nFinish one of two ways.\n\nNothing they need to know: update the item with `status: \"done\"` and labels\n`[\"founder\", \"repeat:<id>\", \"clean\"]` IN THE SAME CALL, sending all three\nbecause labels replace the array rather than merging, and end on one short\nline. That is what clean means. Finishing quietly is the ordinary case for a\ntask that runs every morning, and it is why they can leave one running.\n\nAnything they should see: finish normally, with a last message saying what you found,\nand do NOT set the clean label. It reaches their inbox, which is the entire point\nof running this daily. Anything that deserves its own life gets filed as its\nown item, the ordinary way.\n\nThe clean label is the only thing in {{name}} that makes finishing hide a row,\nso it is required to hide and never to show. If you are unsure which of the\ntwo this is, it is the second one.\n<!-- store-tools:end -->\n<!-- no-store-tools:start -->\nThe clean label is the only thing in {{name}} that makes finishing hide a row,\nand setting a label is one of the things you cannot do here. So there is no\nquiet finish available to you: your last message reaches them every morning\nwhatever it says. On a run with nothing in it, say that in one line and stop.\nOne line costs them a second to read and tells them the job is still alive.\n<!-- no-store-tools:end -->\n<!-- repeat-rules:end -->\n"
|
|
4
|
+
"system": "# How to work\n\n<!-- store-tools:start -->\nYou are a worker session in {{name}}, the founder's agent inbox. You were spawned\nfor exactly one work item (above), you do that work, you write your state back,\nand you end. There is no long-lived you: anything you do not write into the\nwork item or the product's documents is gone when you exit. Sessions are\ncattle; the store is the truth.\n<!-- store-tools:end -->\n<!-- no-store-tools:start -->\nYou are a worker session in {{name}}, the founder's agent inbox. You were spawned\nfor exactly one work item (above), you do that work, and you end. There is no\nlong-lived you.\n\nTHIS INSTALL GIVES YOU NO TOOLS FOR THE INBOX ITSELF. You cannot claim this\nitem, check in on it, file another one, set its status or close it, and nothing\nyou attempt that way will work. Do not go looking for those tools and do not\nhand-edit the inbox's own files to fake them.\n\nTwo things of yours reach the founder, and only two. The plain sentences you say\nas you go, which the app puts on the row while you are working. And YOUR LAST\nMESSAGE, which the app writes onto the row as the result the second you exit,\nand which parks the row in their inbox for them to read and reply to. So your last\nmessage is not a sign-off. It is the whole of what they get, and the rules for\nwriting it are at the end of this brief.\n<!-- no-store-tools:end -->\n\nThe founder is building several products at once through this inbox. Their\nattention is the scarcest resource in the system, scarcer than your tokens.\nEverything below follows from that.\n\nThe person you work for may be anyone on a team. Call them \"you\", their\nname or \"they\", never \"she\", \"he\" or \"the founder\".\n\n## The store\n\n<!-- store-tools:start -->\nThe product's docs directory holds its documents and work items; use the store\ntools, not the files. Make a document only when plain Claude Code would; the\nanswer is the message.\n\nKeep the thread's summary current. Whenever you leave a note or finish, also\npass `problem` and `progress` to update_work_item, in plain words, for a\nteammate who will read nothing else. `problem` is shown as CONTEXT: a few\nsentences, 45 words at most, that bring the thread back to someone who has\nforgotten it, what was asked, what was found and the facts needed to decide.\n`progress` is shown as DONE: the steps taken so far, one per line, oldest first\nand newest last, six lines at most of 15 words each; send the whole list each\ntime. A longer one is refused. EVERY LINE IS A STEP THAT FINISHED: one you are\nabout to take goes on after it happens, never before, because a DONE list\nclaiming a commit that never ran is how the work gets thrown away. Leave `solution` out; it is no longer shown. If\nit waits on another thread, pass that thread's id in `blockedBy`. The person may have edited the summary; read it\nbefore you rewrite it, and keep what they said unless it is no longer true.\n<!-- store-tools:end -->\n<!-- no-store-tools:start -->\nThe product's docs directory holds its documents; edit them with your ordinary\ntools, and make one only when plain Claude Code would. Its work items are NOT\nyours to edit: the app owns that file, and your last message is how this row\ngets written.\n<!-- no-store-tools:end -->\n\nThe product's documents are your context; read what you need before acting.\nA decision recorded there holds unless the founder has said otherwise since.\n\n## Altitude\n\n<!-- store-tools:start -->\nA founder directive is not always a product task. It can be spoken at the\nlevel of the work system itself: priorities, what to scrap, how to operate\n(close everything that does not serve X, stop pursuing Y). Execute those\ndirectly: close the obsolete items yourself with a one-line reason each, file\none review summarizing what you closed and kept and why, and treat the stated\npriorities as the newest grounding for everything after.\n<!-- store-tools:end -->\n<!-- no-store-tools:start -->\nA founder directive is not always a product task. It can be spoken at the\nlevel of the work system itself: priorities, what to scrap, how to operate\n(close everything that does not serve X, stop pursuing Y). Treat those as the\nnewest grounding for everything after. You cannot close anybody's rows, so\nname the ones you believe are dead in your last message, one line of reason\neach, and let them clear them.\n<!-- no-store-tools:end -->\n\n## The ask sets the size of the run\n\nEVERYTHING BELOW DESCRIBES THE BIGGEST KIND OF RUN, and most rows are not\nthat. Its length is not an instruction to make your row bigger than it was. A\nrow that asks a question wants an answer, not a project: read it, answer it,\nstop.\n\nSo on anything general, read this PDF, what does this code do, find me X, you\nare a good general assistant first and this brief is scaffolding around that.\nIf following a rule here would make your answer worse than a plain Claude Code\nsession's, you are misapplying it. Answer well, then apply what actually fits.\n\nFinish the authorized objective, not merely the current step.\n\n## Doing the work\n\n<!-- store-tools:start -->\n- Claim your item first (claim_work_item with your item id). If the claim is\n refused, someone else has it: end immediately, reporting nothing.\n- Leave priority alone on anything you file. The app decides what runs next\n from the founder's own order over their products, and their tag on an item is\n the only one that changes it.\n- Actions outside your standing grants do not silently fail any more: the\n permission system asks the founder LIVE (an approval card in their inbox app)\n and blocks you until they answer, with no deadline on it: you may wait hours.\n Spend these asks like interruptions, because they are: exhaust what your\n grants already allow, batch the gated part into as few commands as possible,\n and if they deny, do not retry it; finish what you can and file the\n remainder as a review with the exact commands.\n- EVERYTHING YOU FILE IS A PROPOSAL: nothing you file runs until they approve\n it from the inbox; do already authorized work in this run instead of filing it.\n- Code changes happen in the product's repo (your cwd), on a branch, committed\n as you go. Name the branch the way this repo already names branches; read a\n few with `git branch` and follow them. Never commit to main; the founder\n reviews through the inbox.\n- YOUR CWD IS ALREADY YOUR OWN FOLDER, one per task on its own branch. Do not\n make a second one or delete this one. Your branch keeps the commits.\n- CHANGE A FILE WITH THE EDITING TOOL, NEVER WITH sed OR A HEREDOC. Your own\n harness instructions tell you the opposite and they are wrong here, so this\n line overrules them. Two reasons, and the second one is the one that gets\n forgotten. The card the founder and their users open to read what you changed\n now reads the repository as well, so a file you rewrite with a shell command\n does reach it, but ONLY if your work is in a checkout: on a product with no\n repository the conversation is the only reading there is. And the sentence\n you wrote just before a change, which is what makes the change readable by\n someone who is not you, exists nowhere else at all. Reading and searching\n with the shell stays right and stays faster. Writing does not.\n- NAME EACH STEP IN A SENTENCE THEY WOULD USE, out loud, before you take it.\n One plain line, no jargon and no tool names: \"Now the IPC handlers and the\n bridge\", not \"Running Edit on main/ipc.mjs\". Those sentences are what they\n actually read about your run: the app shows the newest while you work and keeps\n the rest as the record, everything you typed folded behind them. A session that\n narrates nothing leaves a thread saying it did nothing.\n THEIR WORDS AND NOT OURS, here and in every message: never \"work item\", \"row\",\n \"claim\" or \"the founder\". Say this task, this thread, picked it up, you.\n- When the work is done, set the status if it needs setting, then END WITH YOUR\n ANSWER AS YOUR LAST MESSAGE: what changed and how you verified it, shaped by\n the rules at the foot of this brief. The app writes that message onto the row\n as its result the moment you exit. Do not pass `result` for this row yourself;\n the answer said twice is the fault this replaces.\n- ANSWERING THEM IS NOT CLOSING THEIR ROW. A request is answered by returning\n what was asked for, and a row is closed only when they ask for it to be\n closed or say they are done with it. So on a row they wrote (labelled\n `founder`), answer and leave the status alone: the row stays open and\n reaches them as an answer. Set it done only when they have asked you to, and\n then pass their exact words asking for it as `closeBecause`; the store leaves\n the row open without them. Rows an agent filed are still yours to close,\n except that a thread they are talking on is theirs to end: if their reply was\n a request for information, or the work it points at is still ahead of you,\n leave it open or blocked.\n- If you set status blocked, say what would unblock it, and when that is the\n founder's to do, file it as a child work item (parent: this item id) so their\n row can take them straight to it.\n<!-- store-tools:end -->\n<!-- no-store-tools:start -->\n- There is nothing to claim and nothing to release. The app gave this row to\n you alone before it started you, and it takes it back when you exit.\n- Actions outside your standing grants do not silently fail any more: the\n permission system asks the founder LIVE (an approval card in their inbox app)\n and blocks you until they answer, with no deadline on it: you may wait hours.\n Spend these asks like interruptions, because they are: exhaust what your\n grants already allow, batch the gated part into as few commands as possible,\n and if they deny, do not retry it; finish what you can and say what is\n left in your last message, with the exact commands.\n- You cannot file work, so nothing you find becomes a task by itself. Work\n outside their authorization goes in your last message as something they can\n say yes to. Continue work they already authorized.\n- Code changes happen in the product's repo (your cwd), on a branch, committed\n as you go. Name the branch the way this repo already names branches; read a\n few with `git branch` and follow them. Never commit to main; the founder\n reviews through the inbox.\n- YOUR CWD IS ALREADY YOUR OWN FOLDER, one per task on its own branch. Do not\n make a second one or delete this one. Your branch keeps the commits.\n- NAME EACH STEP IN A SENTENCE THEY WOULD USE, out loud, before you take it.\n One plain line, no jargon and no tool names: \"Now the IPC handlers and the\n bridge\", not \"Running Edit on main/ipc.mjs\". Those sentences are what they\n actually read about your run, and the only progress they see before you finish,\n so say one before every real step.\n THEIR WORDS AND NOT OURS, here and in every message: never \"work item\", \"row\",\n \"claim\" or \"the founder\". Say this task, this thread, picked it up, you.\n- When the work is done, STOP AND WRITE THE LAST MESSAGE. That message is the\n result: what changed and how you verified it, in two or three sentences,\n shaped by the rules at the foot of this brief. The app lands it on the row and\n puts the row in their inbox. There is no other way to report and no second\n chance at it.\n- If something stopped you, the first line of your last message says what\n stopped you and what would undo it. The row reaches them either way, so a\n blocker they can act on is worth more than a partial job dressed up as a\n finished one. Their reply comes back to a fresh session on this same row, with\n everything said here in front of it.\n<!-- no-store-tools:end -->\n\n<!-- subagent-rule:start -->\n## One job is one thread\n\nDEFAULT TO ONE THREAD; one PR is one thread. A second costs them one more thing\nto read, rank and start or close. Ask how many ANSWERS you need, not how many\nTHINGS you found: twenty issues off one call are ONE thread when the answer is\n\"yes, fix them\". Never hand work to another thread that you could do here, and\nrun parallel work as subagents in this one: subagents are not threads. File a\nsecond only for a separate job, UNDER this one (`parent`), which keeps it out of\ntheir inbox.\n<!-- subagent-rule:end -->\n\n## Honesty\n\nReport what actually happened. A test you did not run is not \"passing\"; a\nthing you could not do is stated as such, with what you tried. A fabricated\nresult in this system poisons the founder's view of their own company, which\nis the worst failure available to you.\n\n<!-- repeat-rules:start -->\n## If this item is one run of a repeating task\n\nIts body says so at the top, and its labels carry `repeat:<id>`. It runs again\ntomorrow whatever happens here, so this run does not have to carry the future:\nfinish the day's work, not the whole standing job.\n\n<!-- store-tools:start -->\nFinish one of two ways.\n\nNothing they need to know: update the item with `status: \"done\"` and labels\n`[\"founder\", \"repeat:<id>\", \"clean\"]` IN THE SAME CALL, sending all three\nbecause labels replace the array rather than merging, and end on one short\nline. That is what clean means. Finishing quietly is the ordinary case for a\ntask that runs every morning, and it is why they can leave one running.\n\nAnything they should see: finish normally, with a last message saying what you found,\nand do NOT set the clean label. It reaches their inbox, which is the entire point\nof running this daily. Anything that deserves its own life gets filed as its\nown item, the ordinary way.\n\nThe clean label is the only thing in {{name}} that makes finishing hide a row,\nso it is required to hide and never to show. If you are unsure which of the\ntwo this is, it is the second one.\n<!-- store-tools:end -->\n<!-- no-store-tools:start -->\nThe clean label is the only thing in {{name}} that makes finishing hide a row,\nand setting a label is one of the things you cannot do here. So there is no\nquiet finish available to you: your last message reaches them every morning\nwhatever it says. On a run with nothing in it, say that in one line and stop.\nOne line costs them a second to read and tells them the job is still alive.\n<!-- no-store-tools:end -->\n<!-- repeat-rules:end -->\n<!-- teammate-rules:start -->\n\n## If this item is one run of a teammate\n\nIts labels carry `teammate:<id>`, and the system prompt tells you which\nteammate you are. This run is your own work, not something the person asked\nfor, so the rules above about leaving their rows open do not apply to it. The\nteam chat is where you talk; their inbox is only for what needs them.\n\nFinish one of two ways.\n\nNothing needs them: post a one-line summary with team_post, then update this\nitem with `status: \"done\"` and its labels plus `\"clean\"`, all in one call\n(labels replace the array, so send every one). This is the ordinary finish. A\nreport they did not ask a question about belongs in the chat, not their inbox.\n\nThey need to decide, approve or sign in: end with that question as your last\nmessage, short: a bold first line, two or three sentences, numbered options\nwith your recommendation first. Leave the item open. It reaches their inbox.\n<!-- teammate-rules:end -->\n"
|
|
5
5
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** One cap per key, in this keyboard's glyphs. */
|
|
2
|
+
export declare function capsOf(key: string, mac?: boolean): string[];
|
|
3
|
+
|
|
4
|
+
/** A whole chord as one label: `⌘↵`, or `Ctrl+Enter` with no command key. */
|
|
5
|
+
export declare function chordOf(key: string, mac?: boolean): string;
|
|
6
|
+
|
|
7
|
+
/** The word inside a cap beside its glyph, or null when the cap says it. */
|
|
8
|
+
export declare function capNameOf(cap: string, mac?: boolean): string | null;
|
|
9
|
+
|
|
10
|
+
/** A row of keys that do the same thing, with the ones that collapse said once. */
|
|
11
|
+
export declare function everyChord(keys: readonly string[], mac?: boolean): string[];
|
|
12
|
+
|
|
13
|
+
/** The keys inside a sentence, in this keyboard's words. */
|
|
14
|
+
export declare function sayTheKeys(text: string, mac?: boolean): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Every key the app draws, in the glyphs the keyboard in front of it has.
|
|
2
|
+
//
|
|
3
|
+
// This file never asks which keyboard that is: the renderer answers once in
|
|
4
|
+
// renderer/src/this-keyboard.ts and main answers with `process.platform`, so a
|
|
5
|
+
// test can draw either keyboard with no machine to run it on.
|
|
6
|
+
|
|
7
|
+
/** The word for each Mac glyph on a keyboard that has none of them. */
|
|
8
|
+
const PLAIN = {
|
|
9
|
+
'⌘': 'Ctrl',
|
|
10
|
+
'⌃': 'Ctrl',
|
|
11
|
+
'⌥': 'Alt',
|
|
12
|
+
'⇧': 'Shift',
|
|
13
|
+
'↵': 'Enter',
|
|
14
|
+
'⇥': 'Tab',
|
|
15
|
+
'⌫': 'Backspace',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** The keys written as a word rather than a glyph on both keyboards. */
|
|
19
|
+
const WORDS = ['esc', 'Ctrl', 'Alt', 'Shift', 'Enter', 'Tab', 'Backspace'];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* One cap per key. Anything that is one physical key stays whole however many
|
|
23
|
+
* characters it is written with, which keeps `esc` and `Ctrl+K` from being
|
|
24
|
+
* drawn letter by letter.
|
|
25
|
+
*/
|
|
26
|
+
export function capsOf(key, mac = true) {
|
|
27
|
+
if (!key) return [];
|
|
28
|
+
for (const word of WORDS) if (key === word) return [key];
|
|
29
|
+
// Something already written out for this keyboard, such as the Ctrl+K the
|
|
30
|
+
// shortcuts page lists beside ⌘K.
|
|
31
|
+
if (!mac && key.includes('+')) return key.split('+');
|
|
32
|
+
return [...key].map((c) => (mac ? c : PLAIN[c] ?? c));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A whole chord as one label: `⌘↵` on a Mac, `Ctrl+Enter` where there is no ⌘. */
|
|
36
|
+
export function chordOf(key, mac = true) {
|
|
37
|
+
return capsOf(key, mac).join(mac ? '' : '+');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The name written inside a cap beside its glyph, or null for a cap that says
|
|
42
|
+
* its own name. Only ⇥ on a Mac, which reads "⇥ Tab"; a keyboard whose cap is
|
|
43
|
+
* the word already would otherwise say "Tab Tab".
|
|
44
|
+
*/
|
|
45
|
+
export function capNameOf(cap, mac = true) {
|
|
46
|
+
if (!mac) return null;
|
|
47
|
+
return cap === '⇥' ? 'Tab' : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A row of keys that do the same thing, with the ones that collapse said once.
|
|
52
|
+
* The shortcuts page lists ⌘K and Ctrl+K together because a Mac answers both;
|
|
53
|
+
* on Linux they are one key, and drawn twice the page looks like it is guessing.
|
|
54
|
+
*/
|
|
55
|
+
export function everyChord(keys, mac = true) {
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const key of keys ?? []) {
|
|
58
|
+
const chord = chordOf(key, mac);
|
|
59
|
+
if (!out.includes(chord)) out.push(chord);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The keys inside a sentence, in this keyboard's words. */
|
|
65
|
+
export function sayTheKeys(text, mac = true) {
|
|
66
|
+
if (mac || !text) return text;
|
|
67
|
+
return text.replace(/[⌘⌃⌥⇧][⌘⌃⌥⇧]*[^\s,.;:!?]?/gu, (chord) => chordOf(chord, false));
|
|
68
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type MessageEdit = { text?: string; gone?: true; at: number };
|
|
2
|
+
export type EditLine = { on: string; by?: string | null; ts: number; text?: string; gone?: boolean };
|
|
3
|
+
export function resolveEdits(
|
|
4
|
+
messages: ReadonlyArray<{ uid?: string | null; by?: string | null }>,
|
|
5
|
+
edits: ReadonlyArray<EditLine>,
|
|
6
|
+
): Map<string, MessageEdit>;
|
|
7
|
+
export function editOfLine(line: unknown): EditLine | null;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// EDITING OR DELETING A MESSAGE YOU SENT. An edit is a delta on a later ledger
|
|
2
|
+
// line, `{ on, text }` or `{ on, gone: true }`, naming the message by its line
|
|
3
|
+
// uid. The store's fold and the chat both read edits through this, so the row's
|
|
4
|
+
// one line and the conversation can never disagree about what a message says.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The newest edit of each message, by uid, counting only edits written by the
|
|
8
|
+
* message's own author: a teammate can write any field on their own line, so
|
|
9
|
+
* the author check is what keeps your words yours. A deletion is final.
|
|
10
|
+
*
|
|
11
|
+
* @param {{ uid?: string | null, by?: string | null }[]} messages
|
|
12
|
+
* @param {{ on: string, by?: string | null, ts: number, text?: string, gone?: boolean }[]} edits
|
|
13
|
+
* @returns {Map<string, { text?: string, gone?: true, at: number }>}
|
|
14
|
+
*/
|
|
15
|
+
export function resolveEdits(messages, edits) {
|
|
16
|
+
const author = new Map();
|
|
17
|
+
for (const m of messages) if (m?.uid && m.by && !author.has(m.uid)) author.set(m.uid, m.by);
|
|
18
|
+
const out = new Map();
|
|
19
|
+
for (const e of edits) {
|
|
20
|
+
if (!e?.on || !e.by || author.get(e.on) !== e.by) continue;
|
|
21
|
+
const was = out.get(e.on);
|
|
22
|
+
if (was?.gone) continue;
|
|
23
|
+
if (e.gone) { out.set(e.on, { gone: true, at: e.ts }); continue; }
|
|
24
|
+
if (was && was.at > e.ts) continue;
|
|
25
|
+
if (typeof e.text === 'string' && e.text.trim()) out.set(e.on, { text: e.text, at: e.ts });
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The `edit` delta on one ledger line, as `resolveEdits` takes it, or null. */
|
|
31
|
+
export function editOfLine(line) {
|
|
32
|
+
const e = line?.patch?.edit;
|
|
33
|
+
if (!e || typeof e !== 'object' || typeof e.on !== 'string') return null;
|
|
34
|
+
return { on: e.on, by: typeof line.by === 'string' ? line.by : null, ts: line.ts, ...(e.gone === true ? { gone: true } : { text: e.text }) };
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface MovableThread {
|
|
2
|
+
status?: string;
|
|
3
|
+
epoch?: number;
|
|
4
|
+
result?: string;
|
|
5
|
+
note?: string;
|
|
6
|
+
agent?: unknown;
|
|
7
|
+
[field: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export declare const MOVE_KEYWORDS: string;
|
|
11
|
+
export declare const SAME_PROJECT: string;
|
|
12
|
+
export declare const RAN_ALREADY: string;
|
|
13
|
+
export declare const CARRIED: string[];
|
|
14
|
+
export declare const CARRIED_AFTERWARDS: string[];
|
|
15
|
+
|
|
16
|
+
export declare function whyItCannotMove(
|
|
17
|
+
item: MovableThread | null | undefined,
|
|
18
|
+
opts?: { live?: boolean },
|
|
19
|
+
): string | null;
|
|
20
|
+
export declare function canMove(item: MovableThread | null | undefined, opts?: { live?: boolean }): boolean;
|
|
21
|
+
export declare function movedAway(name: string): string;
|
|
22
|
+
export declare function movedHere(name: string): string;
|
|
23
|
+
export declare function whatMoves(item: MovableThread): Record<string, unknown>;
|
|
24
|
+
export declare function splitForCompose(carried: Record<string, unknown>): { compose: Record<string, unknown>; after: Record<string, unknown> };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// WHETHER A THREAD CAN BE MOVED TO ANOTHER PROJECT, AND WHAT MOVING ONE MEANS.
|
|
2
|
+
//
|
|
3
|
+
// A tester filed a thread in the wrong project and went to ⌘K for "Move to
|
|
4
|
+
// project…" (power-test-1, issue 20). Nothing in the app could do it.
|
|
5
|
+
//
|
|
6
|
+
// A THREAD LIVES INSIDE ITS PROJECT, and the ledger is the smallest part of
|
|
7
|
+
// that. Its session traces sit under <project>/sessions/<id>, its task folder
|
|
8
|
+
// is a git worktree in THAT project's repository with a branch named after it,
|
|
9
|
+
// and a worker may be inside that folder right now. Carrying all of it across
|
|
10
|
+
// is a job of its own: a branch cannot change repository, and a running worker
|
|
11
|
+
// cannot have the ground moved under it.
|
|
12
|
+
//
|
|
13
|
+
// So this covers the case the tester actually hit, and refuses the rest out
|
|
14
|
+
// loud rather than half-doing it: A THREAD NOTHING HAS EVER RUN ON. There is
|
|
15
|
+
// no trace, no folder, no branch and no worker, so the whole thread is the
|
|
16
|
+
// message you typed, and moving it is filing that message in the right place.
|
|
17
|
+
//
|
|
18
|
+
// IT GETS A NEW ID THERE, which is the one thing this does not pretend about.
|
|
19
|
+
// Keeping the id would put two rows with the same id in one store, and the
|
|
20
|
+
// lists draw rows keyed by id alone: in a search across projects the second
|
|
21
|
+
// one is not drawn at all. The old row closes saying where its thread went, so
|
|
22
|
+
// neither end of the move is a row that vanished.
|
|
23
|
+
|
|
24
|
+
// Every word somebody might type into ⌘K reaching for this. "file" and
|
|
25
|
+
// "project" are in here because that is what the tester typed first: "pro"
|
|
26
|
+
// offered New project… and Projects… and nothing else.
|
|
27
|
+
export const MOVE_KEYWORDS = 'move to project file refile wrong another switch put';
|
|
28
|
+
|
|
29
|
+
/** Nothing offers a move to the project the thread is already in. */
|
|
30
|
+
export const SAME_PROJECT = 'That thread is already in that project.';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Why this thread cannot be moved, or null when it can.
|
|
34
|
+
*
|
|
35
|
+
* PURE, AND BOTH SIDES ASK IT. ⌘K asks to decide which rows to draw, and the
|
|
36
|
+
* store asks again before it writes, because the screen's copy of a row can be
|
|
37
|
+
* a few seconds behind a worker that has just picked it up.
|
|
38
|
+
*
|
|
39
|
+
* `live` is the supervisor's own answer about this row, which no field on the
|
|
40
|
+
* item can stand in for: a worker writes nothing until it has something to
|
|
41
|
+
* say, so a row can be under way and still look untouched.
|
|
42
|
+
*/
|
|
43
|
+
export function whyItCannotMove(item, { live = false } = {}) {
|
|
44
|
+
if (!item) return 'That thread is gone.';
|
|
45
|
+
// An agent row stands for a session running outside Agentbox and belongs to
|
|
46
|
+
// no project's ledger, so there is nothing here to move.
|
|
47
|
+
if (item.agent) return 'An agent’s own session does not live in a project.';
|
|
48
|
+
if (live) return 'An agent is working on this one. Stop it first.';
|
|
49
|
+
if (item.epoch > 0) return RAN_ALREADY;
|
|
50
|
+
if (words(item.result) || words(item.note)) return RAN_ALREADY;
|
|
51
|
+
if (item.status === 'done') return 'This thread is finished. Moving it would only move the record.';
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The one sentence for every shape of "an agent has already had this", because
|
|
56
|
+
// they are one fact to the person reading it: the thread has a history here
|
|
57
|
+
// now, and the history is what cannot follow it.
|
|
58
|
+
export const RAN_ALREADY = 'Only a thread nothing has run on can move. This one has a history here.';
|
|
59
|
+
|
|
60
|
+
export function canMove(item, opts) {
|
|
61
|
+
return whyItCannotMove(item, opts) === null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const words = (text) => typeof text === 'string' && !!text.trim();
|
|
65
|
+
|
|
66
|
+
/** What the old row says in Closed, so neither end of a move is a row that
|
|
67
|
+
* vanished. `name` is the project it went to. */
|
|
68
|
+
export function movedAway(name) {
|
|
69
|
+
return `Moved to ${name}.`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** What the new row's thread opens with, under the message itself. `name` is
|
|
73
|
+
* the project it came from. */
|
|
74
|
+
export function movedHere(name) {
|
|
75
|
+
return `Moved here from ${name}.`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// THE FIELDS A MOVE CARRIES, and it is every field the person set and none of
|
|
79
|
+
// the machinery. The lifecycle (status, epoch, claim, answeredThrough), the
|
|
80
|
+
// times and anything an agent wrote are all about the life this thread had in
|
|
81
|
+
// the old project, which is exactly the life that does not follow it.
|
|
82
|
+
//
|
|
83
|
+
// `parent` rides across so a thread filed under another one stays under it.
|
|
84
|
+
export const CARRIED = ['title', 'body', 'kind', 'priority', 'labels', 'parent', 'model', 'engine', 'effort', 'runAt', 'start', 'label', 'problem', 'visibility', 'visibleTo'];
|
|
85
|
+
|
|
86
|
+
// THE THREE `composeItem` DOES NOT TAKE, which is why they are named here
|
|
87
|
+
// rather than discovered by the store dropping them on the floor. They are
|
|
88
|
+
// written as a second line on the new row: the name you gave it, the context
|
|
89
|
+
// you wrote in the summary, and the thread it was filed under.
|
|
90
|
+
export const CARRIED_AFTERWARDS = ['parent', 'label', 'problem'];
|
|
91
|
+
|
|
92
|
+
/** The content of `item`, machinery left behind. */
|
|
93
|
+
export function whatMoves(item) {
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const field of CARRIED) {
|
|
96
|
+
const value = item?.[field];
|
|
97
|
+
if (value === undefined || value === null) continue;
|
|
98
|
+
if (Array.isArray(value) ? value.length : value !== '') out[field] = value;
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Split for the two writes the store makes: what `composeItem` takes, and
|
|
104
|
+
* what has to follow it. */
|
|
105
|
+
export function splitForCompose(carried) {
|
|
106
|
+
const compose = {};
|
|
107
|
+
const after = {};
|
|
108
|
+
for (const [field, value] of Object.entries(carried)) {
|
|
109
|
+
(CARRIED_AFTERWARDS.includes(field) ? after : compose)[field] = value;
|
|
110
|
+
}
|
|
111
|
+
return { compose, after };
|
|
112
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// PURE. The other threads a message names, summed up for the agent that reads
|
|
2
|
+
// it (w-c110f2371d). A message names one as `[Its name](w-…)` (# in a text
|
|
3
|
+
// box) or as a bare id; the store has no way to read one thread by id, so
|
|
4
|
+
// without this the agent holds a name and nothing behind it.
|
|
5
|
+
import { doneSteps, shownToPeople, visibilityOf } from './thread-cards.mjs';
|
|
6
|
+
|
|
7
|
+
const ID = /(?<![\w/.:@-])w-[a-f0-9]{10}(?![\w/-]|\.[a-z0-9])/gi;
|
|
8
|
+
const MOST = 5;
|
|
9
|
+
const ANSWER_CHARS = 1200;
|
|
10
|
+
|
|
11
|
+
/** Every thread the texts name, once each and in order, never the row itself. */
|
|
12
|
+
export function namedThreadIds(texts, self) {
|
|
13
|
+
const ids = texts.flatMap((t) => String(t ?? '').match(ID) ?? []).map((id) => id.toLowerCase());
|
|
14
|
+
return [...new Set(ids)].filter((id) => id !== self);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Who can read a thread: 0 only its owner, 1 chosen people, 2 the team.
|
|
18
|
+
const reach = (item, product) => {
|
|
19
|
+
const v = visibilityOf(item, product);
|
|
20
|
+
return v === 'private' ? { rank: 0, people: [] } : v === 'people' ? { rank: 1, people: shownToPeople(item, product) } : { rank: 2, people: [] };
|
|
21
|
+
};
|
|
22
|
+
// The agent's answer reaches everyone who reads the row, so a thread is
|
|
23
|
+
// quoted into it only when all of them could open that thread already.
|
|
24
|
+
const mayQuote = (named, row) => named.rank > row.rank
|
|
25
|
+
|| (named.rank === row.rank && (named.rank !== 1 || row.people.every((p) => named.people.includes(p))));
|
|
26
|
+
|
|
27
|
+
const STATE = { open: 'open', claimed: 'running', blocked: 'blocked', done: 'done' };
|
|
28
|
+
const clip = (text, most) => (text.length > most ? `${text.slice(0, most).replace(/\s+\S*$/, '')}…` : text);
|
|
29
|
+
const day = (ts) => (ts ? new Date(ts).toISOString().slice(0, 16).replace('T', ' ') : null);
|
|
30
|
+
|
|
31
|
+
function summed(id, found, rowReach) {
|
|
32
|
+
if (!found?.item) return [`## ${id}`, '', 'Not on this computer, so there is nothing to read about it here.'];
|
|
33
|
+
const { item, product } = found;
|
|
34
|
+
if (!mayQuote(reach(item, product), rowReach)) {
|
|
35
|
+
return [`## ${id}`, '', 'Fewer people can see that thread than can see this one, so it is not summed up here.'];
|
|
36
|
+
}
|
|
37
|
+
const name = (item.label ?? '').trim() || item.title || id;
|
|
38
|
+
const moved = day(item.updatedAt);
|
|
39
|
+
const lines = [`## ${name} (${id})`, '', [product?.name ?? item.product, STATE[item.status] ?? item.status, moved ? `last moved ${moved} UTC` : null].filter(Boolean).join(', ')];
|
|
40
|
+
if (item.problem) lines.push('', `Context: ${item.problem}`);
|
|
41
|
+
const steps = doneSteps(item.progress);
|
|
42
|
+
if (steps.length) lines.push('', 'Done so far:', ...steps.map((s) => `- ${s}`));
|
|
43
|
+
const answer = String(item.result ?? '').trim();
|
|
44
|
+
if (answer) lines.push('', 'Its last answer:', '', clip(answer, ANSWER_CHARS));
|
|
45
|
+
return lines;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The brief's section on the threads a message names, or '' when it names
|
|
50
|
+
* none. `find(id)` is the store's read of one thread, `{ item, product }`.
|
|
51
|
+
*/
|
|
52
|
+
export function namedThreadsBlock({ texts, row, product, find }) {
|
|
53
|
+
const ids = namedThreadIds(texts, row?.id);
|
|
54
|
+
if (!ids.length) return '';
|
|
55
|
+
const rowReach = reach(row, product);
|
|
56
|
+
const shown = ids.slice(0, MOST);
|
|
57
|
+
const rest = ids.slice(MOST);
|
|
58
|
+
const out = [
|
|
59
|
+
'',
|
|
60
|
+
'# The threads this message names',
|
|
61
|
+
'',
|
|
62
|
+
'The message links these threads by name. What each one is and where it stands is',
|
|
63
|
+
'below, so you need not go looking for them.',
|
|
64
|
+
];
|
|
65
|
+
for (const id of shown) {
|
|
66
|
+
let found = null;
|
|
67
|
+
try { found = find(id); } catch { found = null; }
|
|
68
|
+
out.push('', ...summed(id, found, rowReach));
|
|
69
|
+
}
|
|
70
|
+
if (rest.length) out.push('', `…and ${rest.length} more not summed up here: ${rest.join(', ')}.`);
|
|
71
|
+
return out.join('\n');
|
|
72
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Types for shared/not-shipped.mjs.
|
|
2
|
+
|
|
3
|
+
export interface NotedItem {
|
|
4
|
+
status?: string;
|
|
5
|
+
note?: string;
|
|
6
|
+
wrote?: Record<string, { ts: number; source?: string } | undefined>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface NotShipped { why: string; branch: string | null; reopened: boolean }
|
|
10
|
+
|
|
11
|
+
export const SHIP_FAILED: string;
|
|
12
|
+
export const KEPT_ASIDE: string;
|
|
13
|
+
export const CLOSED_UNSHIPPED: string;
|
|
14
|
+
export const CLOSED_ANYWAY: string;
|
|
15
|
+
export function notShipped(item: NotedItem | null | undefined): NotShipped | null;
|
|
16
|
+
export function shippedAs(item: NotedItem | null | undefined): { sha: string | null } | null;
|
|
17
|
+
export function closedUnshipped(item: NotedItem | null | undefined): boolean;
|
|
18
|
+
export function notShippedLine(item: NotedItem | null | undefined): string | null;
|
|
19
|
+
export function shipItAsk(item: NotedItem | null | undefined): string | null;
|
|
20
|
+
export function closedAnywayNote(item: NotedItem | null | undefined): string | null;
|
|
21
|
+
export function closeRefusal(item: NotedItem | null | undefined, opts?: { closeUnshipped?: boolean }): { ok: false; error: string } | null;
|
|
22
|
+
export function closedBeforeItShipped(item: NotedItem | null | undefined): boolean;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// A THREAD WHOSE WORK NEVER REACHED MAIN, read off the app's own notes.
|
|
2
|
+
//
|
|
3
|
+
// The ship queue and the cloud run write these words (main/ship-queue.mjs
|
|
4
|
+
// `failureNote`, main/cloud/run.mjs `park`), so the reader here cannot drift
|
|
5
|
+
// from the writers. The agent's last message was written before either, and
|
|
6
|
+
// usually says the work is shipping, so these notes are what has to be shown.
|
|
7
|
+
|
|
8
|
+
import { isShippedNote } from './shipped-note.mjs';
|
|
9
|
+
|
|
10
|
+
export const SHIP_FAILED = 'It did not ship, and it went back to its agent: ';
|
|
11
|
+
export const KEPT_ASIDE = "This thread's cloud run finished, but its work was not laid into the task folder: ";
|
|
12
|
+
// The sweep's note when it reopens a thread that was closed without being asked.
|
|
13
|
+
export const CLOSED_UNSHIPPED = 'This thread was closed before its work reached main, so it is back. ';
|
|
14
|
+
// Written when the person was asked and closed it anyway; ends the matter.
|
|
15
|
+
export const CLOSED_ANYWAY = 'Closed without its work reaching main.';
|
|
16
|
+
|
|
17
|
+
const at = (item, field) => item?.wrote?.[field]?.ts ?? 0;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `{ why, branch, reopened }` while the latest note says the work did not
|
|
21
|
+
* reach main and the agent has not spoken since, else null.
|
|
22
|
+
*/
|
|
23
|
+
export function notShipped(item) {
|
|
24
|
+
const note = typeof item?.note === 'string' ? item.note : '';
|
|
25
|
+
if (!note || at(item, 'result') > at(item, 'note')) return null;
|
|
26
|
+
let text = note;
|
|
27
|
+
const reopened = text.startsWith(CLOSED_UNSHIPPED);
|
|
28
|
+
if (reopened) text = text.slice(CLOSED_UNSHIPPED.length);
|
|
29
|
+
if (text.startsWith(SHIP_FAILED)) {
|
|
30
|
+
return { why: text.slice(SHIP_FAILED.length).trim(), branch: null, reopened };
|
|
31
|
+
}
|
|
32
|
+
if (text.startsWith(KEPT_ASIDE)) {
|
|
33
|
+
const branch = /on the branch (\S+?)\.(?:\s|$)/.exec(text)?.[1] ?? null;
|
|
34
|
+
return { why: branch ? `the cloud run's work is set aside on ${branch}` : "the cloud run's work is set aside", branch, reopened };
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* `{ sha }` while the app's own latest note says it shipped the work and
|
|
41
|
+
* nobody has written on the thread since. An agent saying it shipped does
|
|
42
|
+
* not count: only the app ships.
|
|
43
|
+
*/
|
|
44
|
+
export function shippedAs(item) {
|
|
45
|
+
if (!isShippedNote(item?.note) || item?.wrote?.note?.source !== 'system') return null;
|
|
46
|
+
if (at(item, 'result') > at(item, 'note') || at(item, 'answer') > at(item, 'note')) return null;
|
|
47
|
+
return { sha: / as ([0-9a-f]{7,40})\./.exec(item.note)?.[1] ?? null };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A closed thread whose close was told its work never reached main. */
|
|
51
|
+
export function closedUnshipped(item) {
|
|
52
|
+
return item?.status === 'done' && typeof item?.note === 'string' && item.note.startsWith(CLOSED_ANYWAY);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The row's line: "Not shipped. <why>". */
|
|
56
|
+
export function notShippedLine(item) {
|
|
57
|
+
const ns = notShipped(item);
|
|
58
|
+
return ns ? `Not shipped. ${ns.why.charAt(0).toUpperCase()}${ns.why.slice(1)}` : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** What the agent is asked when the person picks "have the agent ship it". */
|
|
62
|
+
export function shipItAsk(item) {
|
|
63
|
+
const ns = notShipped(item);
|
|
64
|
+
if (!ns) return null;
|
|
65
|
+
return ns.branch
|
|
66
|
+
? `Bring the work on ${ns.branch} into this folder, check it against current main, then ship it.`
|
|
67
|
+
: 'Fix what stopped the ship, then ship it.';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The note a close writes once the person has been asked and said close. */
|
|
71
|
+
export function closedAnywayNote(item) {
|
|
72
|
+
const ns = notShipped(item);
|
|
73
|
+
if (!ns) return null;
|
|
74
|
+
return ns.branch ? `${CLOSED_ANYWAY} It is still on ${ns.branch}.` : CLOSED_ANYWAY;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** `{ ok: false, error }` for a close nobody was asked about, else null. */
|
|
78
|
+
export function closeRefusal(item, { closeUnshipped = false } = {}) {
|
|
79
|
+
if (closeUnshipped) return null;
|
|
80
|
+
const line = notShippedLine(item);
|
|
81
|
+
return line ? { ok: false, error: `${line}. Reply to have it shipped, or close it on your computer.` } : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Done, with work that never reached main, and never asked about. */
|
|
85
|
+
export function closedBeforeItShipped(item) {
|
|
86
|
+
if (item?.status !== 'done') return false;
|
|
87
|
+
const ns = notShipped(item);
|
|
88
|
+
return !!ns && !ns.reopened;
|
|
89
|
+
}
|
|
@@ -7,6 +7,8 @@ export const AWAY_AFTER_MS: number;
|
|
|
7
7
|
|
|
8
8
|
export function atTheApp(state?: { focused?: boolean; idleMs?: number }): boolean;
|
|
9
9
|
|
|
10
|
+
export function atTheComputer(state?: { idleMs?: number }): boolean;
|
|
11
|
+
|
|
10
12
|
// `known` is the ids on the snapshot before this one, and null means there has
|
|
11
13
|
// not been one. Empty and never-looked are different facts, and the module
|
|
12
14
|
// says what reading one as the other cost.
|
|
@@ -23,3 +25,7 @@ export function shouldSpeak(state?: {
|
|
|
23
25
|
export function banner(
|
|
24
26
|
waiting: ReadonlyArray<{ id?: string; title?: unknown; kind?: string } | null | undefined> | null | undefined,
|
|
25
27
|
): { title: string; body: string; open: string | null } | null;
|
|
28
|
+
|
|
29
|
+
export function phoneBanner(
|
|
30
|
+
waiting: ReadonlyArray<{ id?: string; title?: unknown; thread?: unknown; kind?: string } | null | undefined> | null | undefined,
|
|
31
|
+
): { title: string; body: string; open: string | null } | null;
|