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
package/main/store.mjs
CHANGED
|
@@ -8,8 +8,10 @@ import os from 'node:os';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { loadStore } from './store-modules.mjs';
|
|
10
10
|
import { Repeats } from './repeats.mjs';
|
|
11
|
+
import { CLEAN_LABEL, isCleanRun } from '../shared/repeats.mjs';
|
|
11
12
|
import { answerFor, describeFor } from './first-run.mjs';
|
|
12
13
|
import { checkProjectFolder } from '../shared/project-folder-check.mjs';
|
|
14
|
+
import { SAME_PROJECT, movedAway, movedHere, splitForCompose, whatMoves, whyItCannotMove } from '../shared/move-thread.mjs';
|
|
13
15
|
import { EXAMPLES, stampsFor } from '../shared/first-run-examples.mjs';
|
|
14
16
|
import {
|
|
15
17
|
PRACTICE_ANSWER, PRACTICE_BACKDROP, PRACTICE_FLAG, PRACTICE_NAME, PRACTICE_NOTE, PRACTICE_ROWS, PRACTICE_SLUG,
|
|
@@ -29,12 +31,21 @@ import {
|
|
|
29
31
|
} from '../shared/codex-import.mjs';
|
|
30
32
|
import { buildClaimLine, buildReleaseLine } from '../shared/work-items.mjs';
|
|
31
33
|
import { isEffortWord } from '../shared/effort-levels.mjs';
|
|
34
|
+
import { shippedNote } from '../shared/shipped-note.mjs';
|
|
32
35
|
import { machineryPath, removeMachinery } from './store/home.mjs';
|
|
36
|
+
import { Teammates } from './teammates.mjs';
|
|
33
37
|
import {
|
|
34
38
|
answerMode as readAnswerMode, answerModes, clearAnswerMode as clearAnswerModeFile, setAnswerMode,
|
|
35
39
|
touchAnswerMode as touchAnswerModeFile,
|
|
36
40
|
} from './answer-modes.mjs';
|
|
37
41
|
|
|
42
|
+
// WHAT A ROW'S ID LOOKS LIKE, for the reads that take one from the window: a
|
|
43
|
+
// task, `w-` and hex, or a run of a repeating task, `r-<rule>-<YYYYMMDD>`
|
|
44
|
+
// (shared/repeats.mjs occurrenceId). Runs were refused until a repeating task
|
|
45
|
+
// began opening as its latest run (w-5e3f2e047f), and a run's page then had no
|
|
46
|
+
// conversation to draw.
|
|
47
|
+
const ROW_ID = /^(w-[0-9a-f]{6,}|r-[0-9a-f]{6,}-\d{8})$/;
|
|
48
|
+
|
|
38
49
|
export class Store {
|
|
39
50
|
constructor(config) {
|
|
40
51
|
this.config = config;
|
|
@@ -48,6 +59,7 @@ export class Store {
|
|
|
48
59
|
async init() {
|
|
49
60
|
this.modules = await loadStore();
|
|
50
61
|
this.repeats = new Repeats(this.modules);
|
|
62
|
+
this.teammates = this.config?.storeRoot && this.config?.accountId ? new Teammates(this) : null;
|
|
51
63
|
// AND A PRACTICE PROJECT LEFT OVER FROM LAST TIME GOES NOW.A walk that was
|
|
52
64
|
// quit, or a Mac that went to sleep in the middle of one, leaves the
|
|
53
65
|
// practice project sitting in the account root, and everything downstream
|
|
@@ -108,6 +120,9 @@ export class Store {
|
|
|
108
120
|
name: project.name || entry.name,
|
|
109
121
|
oneLiner: project.oneLiner || '',
|
|
110
122
|
repoPath: project.repoPath || null,
|
|
123
|
+
// What puts an unplaced project under the ones already there rather
|
|
124
|
+
// than alphabetically among them (renderer/src/project-priority.ts).
|
|
125
|
+
createdAt: Number.isFinite(Date.parse(project.createdAt)) ? Date.parse(project.createdAt) : null,
|
|
111
126
|
// THE MARK SHE CHOSE FOR THIS PROJECT, already a url the window can
|
|
112
127
|
// draw, or null on every project that has never been given one, which is
|
|
113
128
|
// what makes the rail draw its burst instead (main/project-identity.mjs).
|
|
@@ -244,19 +259,13 @@ export class Store {
|
|
|
244
259
|
// The lease lines go here rather than there because they are the bulk of the
|
|
245
260
|
// file and none of them are news: a worker holding a task for an hour writes
|
|
246
261
|
// a heartbeat a minute, and's 39 lines are 15 events.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
try { line = JSON.parse(raw); } catch { continue; }
|
|
255
|
-
if (!line || line.id !== id) continue;
|
|
256
|
-
if (line.heartbeat || line.release) continue;
|
|
257
|
-
lines.push(line);
|
|
258
|
-
}
|
|
259
|
-
return lines.slice(-max);
|
|
262
|
+
//
|
|
263
|
+
// Read once per change of the ledger, not once per call (main/store/
|
|
264
|
+
// work-items.mjs readItemHistory): the phone pass asks for 300 threads in a
|
|
265
|
+
// row, and reading the file for each blocked the app for 21 s (2026-10-14).
|
|
266
|
+
readHistory(slug, id, { max = 400, before } = {}) {
|
|
267
|
+
if (!ROW_ID.test(String(id ?? ''))) return [];
|
|
268
|
+
return this.modules.workItemsDisk.readItemHistory(this.productDir(slug), id, { max, before });
|
|
260
269
|
}
|
|
261
270
|
|
|
262
271
|
// THE CONVERSATION ON ONE ROW, oldest first, for the brief a spawning worker
|
|
@@ -269,7 +278,7 @@ export class Store {
|
|
|
269
278
|
// here would silently decide it twice.
|
|
270
279
|
readThread(slug, id) {
|
|
271
280
|
const { workItemsDisk, workItemsCore } = this.modules;
|
|
272
|
-
if (
|
|
281
|
+
if (!ROW_ID.test(String(id ?? ''))) return [];
|
|
273
282
|
const file = workItemsDisk._internals.ledgerPath(this.productDir(slug));
|
|
274
283
|
return workItemsCore.threadOf(workItemsDisk._internals.readLines(file), id);
|
|
275
284
|
}
|
|
@@ -328,7 +337,7 @@ export class Store {
|
|
|
328
337
|
// `inReplyTo` makes the answer a reply in a thread (w-920461cbe6): the uid
|
|
329
338
|
// of the message it answers, on THE SAME LINE as the words, so the reply's
|
|
330
339
|
// own uid (what its reactions hang off) is the line that says where it goes.
|
|
331
|
-
answerItem(slug, id, { answer, status, priority, permissionMode, model, effort, inReplyTo }) {
|
|
340
|
+
answerItem(slug, id, { answer, status, priority, permissionMode, engine, model, effort, inReplyTo, fromPhone }) {
|
|
332
341
|
const { workItemsDisk } = this.modules;
|
|
333
342
|
const dir = this.productDir(slug);
|
|
334
343
|
let item = null;
|
|
@@ -341,9 +350,25 @@ export class Store {
|
|
|
341
350
|
// is said everywhere else here: `modelForEngine` (shared/engines.mjs) reads
|
|
342
351
|
// the empty string as nothing chosen, where a JSON null would have to be
|
|
343
352
|
// special-cased in both engines' argument builders.
|
|
353
|
+
// AND WHICH ENGINE, BESIDE IT AND FOR THE SAME REASON (w-c9da359466). The
|
|
354
|
+
// reply box's drawer only ever offered the models of the engine the row
|
|
355
|
+
// was already on, so a thread begun on Claude Code could not be carried on
|
|
356
|
+
// in Codex by any path in the app: "that is what I'd reach for when Claude
|
|
357
|
+
// runs out" (power-test-2, issues 39 and 40). It is the field `composeItem`
|
|
358
|
+
// already writes and `Supervisor#_engineFor` already reads per run, so the
|
|
359
|
+
// next turn picks it up and nothing else has to know.
|
|
360
|
+
if (engine !== undefined) item = workItemsDisk.updateWorkItem(dir, id, { engine: engine ?? '' }, { source: 'founder' });
|
|
344
361
|
if (model !== undefined) item = workItemsDisk.updateWorkItem(dir, id, { model: model ?? '' }, { source: 'founder' });
|
|
345
362
|
if (effort !== undefined) item = workItemsDisk.updateWorkItem(dir, id, { effort: effort ?? '' }, { source: 'founder' });
|
|
346
|
-
if (answer) item = workItemsDisk.updateWorkItem(dir, id, inReplyTo ? {
|
|
363
|
+
if (answer) item = workItemsDisk.updateWorkItem(dir, id, { answer, ...(inReplyTo ? { inReplyTo } : {}), ...(fromPhone ? { fromPhone } : {}) }, { source: 'founder' });
|
|
364
|
+
// A REPLY ON A RUN THAT FINISHED QUIETLY MAKES IT NEWS AGAIN (w-5e3f2e047f).
|
|
365
|
+
// A quiet run is kept out of her inbox, and a repeating task now opens on
|
|
366
|
+
// its latest run, so without this the answer to what she just asked there
|
|
367
|
+
// could never reach her. Written as the app rather than as her, so the
|
|
368
|
+
// worker can still mark a later quiet finish of its own.
|
|
369
|
+
if (answer && answer !== '(withdrawn)' && isCleanRun(item)) {
|
|
370
|
+
item = workItemsDisk.updateWorkItem(dir, id, { labels: item.labels.filter((l) => l !== CLEAN_LABEL) }, { source: 'system' });
|
|
371
|
+
}
|
|
347
372
|
// The pane may still show a running row after Stop or a provider result.
|
|
348
373
|
// Resolve omitted reply status against the ledger that was just written.
|
|
349
374
|
if (status == null && typeof answer === 'string' && answer.trim() && answer !== '(withdrawn)'
|
|
@@ -389,15 +414,17 @@ export class Store {
|
|
|
389
414
|
shipped(slug, id, { sha, labels }) {
|
|
390
415
|
const { workItemsDisk } = this.modules;
|
|
391
416
|
const dir = this.productDir(slug);
|
|
392
|
-
|
|
393
|
-
return workItemsDisk.updateWorkItem(dir, id, { labels, note }, { source: 'system' });
|
|
417
|
+
return workItemsDisk.updateWorkItem(dir, id, { labels, note: shippedNote(sha) }, { source: 'system' });
|
|
394
418
|
}
|
|
395
419
|
|
|
396
420
|
shipFailed(slug, id, { note, labels }) {
|
|
397
421
|
const { workItemsDisk } = this.modules;
|
|
398
422
|
const dir = this.productDir(slug);
|
|
399
423
|
workItemsDisk.updateWorkItem(dir, id, { labels, note }, { source: 'system' });
|
|
400
|
-
|
|
424
|
+
// A row the person closed while it waited to ship is theirs, and only
|
|
425
|
+
// their own Reopen outranks that (see `closedBeforeItShipped` below).
|
|
426
|
+
const closed = this.readItem(slug, id)?.status === 'done';
|
|
427
|
+
return workItemsDisk.updateWorkItem(dir, id, { status: 'open' }, { source: closed ? 'founder' : 'system' });
|
|
401
428
|
}
|
|
402
429
|
|
|
403
430
|
releaseRunClaim(slug, id, run) {
|
|
@@ -509,6 +536,13 @@ export class Store {
|
|
|
509
536
|
);
|
|
510
537
|
}
|
|
511
538
|
|
|
539
|
+
// NEW WORDS FOR A MESSAGE YOU SENT, OR THE MESSAGE TAKEN BACK. The fold only
|
|
540
|
+
// believes it from the message's own author (shared/message-edits.mjs).
|
|
541
|
+
editMessage(slug, id, { on, text, gone = false }) {
|
|
542
|
+
const edit = gone ? { on, gone: true } : { on, text };
|
|
543
|
+
return this.modules.workItemsDisk.updateWorkItem(this.productDir(slug), id, { edit }, { source: 'founder' });
|
|
544
|
+
}
|
|
545
|
+
|
|
512
546
|
// AN EDIT TO A THREAD FROM ITS SUMMARY (approved 2026-10-01): the summary's
|
|
513
547
|
// three lines and its links, who sees it, and its priority. Written as the
|
|
514
548
|
// person's own words; on the summary the later write wins either way
|
|
@@ -516,24 +550,33 @@ export class Store {
|
|
|
516
550
|
threadEdit(slug, id, patch) {
|
|
517
551
|
// `start` is how a thread leaves Later: the summary and the thread page
|
|
518
552
|
// both write 'now' on it, which is what makes `isDue` true again.
|
|
519
|
-
|
|
553
|
+
// `label` is a rename (w-ab3d51dc7d): the name every row shows. Never
|
|
554
|
+
// `title`, which is the message the thread was started with and briefs
|
|
555
|
+
// every run. Written on her authority, it outranks the namer's.
|
|
556
|
+
const fields = ['problem', 'progress', 'solution', 'visibility', 'visibleTo', 'priority', 'blockedBy', 'blocks', 'start', 'label'];
|
|
520
557
|
const allowed = Object.fromEntries(Object.entries(patch ?? {}).filter(([k]) => fields.includes(k)));
|
|
558
|
+
if ('label' in allowed) {
|
|
559
|
+
allowed.label = String(allowed.label ?? '').replace(/\s+/g, ' ').trim();
|
|
560
|
+
if (!allowed.label) throw new Error('a thread needs a name');
|
|
561
|
+
}
|
|
521
562
|
if (!Object.keys(allowed).length) throw new Error('nothing to change');
|
|
522
563
|
return this.modules.workItemsDisk.updateWorkItem(this.productDir(slug), id, allowed, { source: 'founder' });
|
|
523
564
|
}
|
|
524
565
|
|
|
525
|
-
composeItem(slug, { title, body, kind = 'directive', priority = 0, labels, runAt, start, engine, model, effort, assignee, due, people, visibility, visibleTo }) {
|
|
566
|
+
composeItem(slug, { id = null, title, body, kind = 'directive', priority = 0, labels, runAt, start, engine, model, effort, assignee, due, people, visibility, visibleTo, fromPhone }) {
|
|
526
567
|
const { workItemsDisk } = this.modules;
|
|
527
568
|
const dir = this.productDir(slug);
|
|
528
569
|
// The 'founder' label is the human-in-the-loop marker: only items the
|
|
529
570
|
// founder composed (or explicitly approved) execute; agent-filed items
|
|
530
571
|
// are proposals until then.
|
|
531
572
|
const allLabels = [...new Set(['founder', ...(labels ?? [])])];
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
573
|
+
// The phone command that asked for it, on the very line that makes the
|
|
574
|
+
// row, so no row it made is ever without it (Codex's fourth review).
|
|
575
|
+
const first = { title, kind, priority, labels: allLabels, ...(fromPhone ? { fromPhone: String(fromPhone) } : {}) };
|
|
576
|
+
// A thread the cloud started from the phone keeps the id it ran under there.
|
|
577
|
+
const created = id
|
|
578
|
+
? workItemsDisk.createWorkItemIfAbsent(dir, id, first, { source: 'system' }).item
|
|
579
|
+
: workItemsDisk.createWorkItem(dir, first, { source: 'system' });
|
|
537
580
|
// The founder's words carry founder authority; the lifecycle does not.
|
|
538
581
|
// A runAt sent with the compose is "send later": the item exists now and
|
|
539
582
|
// starts then, which is why it rides in with her content rather than after.
|
|
@@ -588,6 +631,57 @@ export class Store {
|
|
|
588
631
|
return made ? { ...made, product: slug } : made;
|
|
589
632
|
}
|
|
590
633
|
|
|
634
|
+
/**
|
|
635
|
+
* MOVE A THREAD TO ANOTHER PROJECT (w-c9da359466, power-test-1 issue 20).
|
|
636
|
+
*
|
|
637
|
+
* Only a thread nothing has ever run on, and shared/move-thread.mjs carries
|
|
638
|
+
* the whole of why: the rest of a thread is its session traces, its task
|
|
639
|
+
* folder and the git branch named after it, and a branch cannot change
|
|
640
|
+
* repository.
|
|
641
|
+
*
|
|
642
|
+
* THE RULE IS ASKED AGAIN HERE. ⌘K asked it to decide which rows to draw,
|
|
643
|
+
* off a snapshot that can be seconds old, and a worker that picked the
|
|
644
|
+
* thread up in those seconds would otherwise have the ground moved under it.
|
|
645
|
+
*
|
|
646
|
+
* THE NEW ROW IS WRITTEN BEFORE THE OLD ONE IS CLOSED. A close that landed
|
|
647
|
+
* first and a create that then failed on a full disk would be a thread that
|
|
648
|
+
* is in neither project, which is the one outcome this must not have.
|
|
649
|
+
*/
|
|
650
|
+
moveItem(fromSlug, id, toSlug) {
|
|
651
|
+
if (fromSlug === toSlug) throw new Error(SAME_PROJECT);
|
|
652
|
+
const from = this.listProducts().find((p) => p.slug === fromSlug);
|
|
653
|
+
const to = this.listProducts().find((p) => p.slug === toSlug);
|
|
654
|
+
if (!to) throw new Error(`no such project: ${toSlug}`);
|
|
655
|
+
const item = this.readItem(fromSlug, id);
|
|
656
|
+
const why = whyItCannotMove(item, { live: false });
|
|
657
|
+
if (why) throw new Error(why);
|
|
658
|
+
|
|
659
|
+
const { compose, after } = splitForCompose(whatMoves(item));
|
|
660
|
+
// Where it came from, in the thread rather than only in a toast, because a
|
|
661
|
+
// thread that turns up in a project nobody filed it in is a thread nobody
|
|
662
|
+
// can account for a week later.
|
|
663
|
+
const said = movedHere(from?.name ?? fromSlug);
|
|
664
|
+
const body = compose.body ? `${compose.body}\n\n${said}` : said;
|
|
665
|
+
let made = this.composeItem(toSlug, { ...compose, body });
|
|
666
|
+
// The three `composeItem` has no argument for (shared/move-thread.mjs).
|
|
667
|
+
if (Object.keys(after).length) {
|
|
668
|
+
made = { ...this.modules.workItemsDisk.updateWorkItem(this.productDir(toSlug), made.id, after, { source: 'founder' }), product: toSlug };
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// AND THE OLD ROW CLOSES SAYING WHERE ITS THREAD WENT. It is written as
|
|
672
|
+
// the app rather than as the founder: they moved a thread, they did not
|
|
673
|
+
// finish one, and a founder `done` is terminal in a way this need not be.
|
|
674
|
+
const note = movedAway(to.name ?? toSlug);
|
|
675
|
+
this.modules.workItemsDisk.updateWorkItem(this.productDir(fromSlug), id, { status: 'done', note }, { source: 'system' });
|
|
676
|
+
return { item: made, from: from?.name ?? fromSlug, to: to.name ?? toSlug, note };
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/* ------------------------------- teammates ------------------------------ */
|
|
680
|
+
teammatesView(items, now = Date.now()) {
|
|
681
|
+
if (!this.teammates) return null;
|
|
682
|
+
return this.teammates.view(items ?? this.listItems(now), now);
|
|
683
|
+
}
|
|
684
|
+
|
|
591
685
|
/* ---------------------------- repeating tasks --------------------------- */
|
|
592
686
|
// Every product's rules, with their product on each, the way listItems does
|
|
593
687
|
// it, so the renderer has one list to draw. Ended rules are gone from her
|
|
@@ -632,13 +726,52 @@ export class Store {
|
|
|
632
726
|
// An ordinary agent-filed item: a PROPOSAL, which is what puts it in her
|
|
633
727
|
// inbox and keeps it there until she answers. Used for the miss alert, which
|
|
634
728
|
// is news about a repeating task rather than work she asked for.
|
|
635
|
-
|
|
729
|
+
// `problem` is the summary's Context, the few sentences a reader gets before
|
|
730
|
+
// they open anything. Without one the panel falls back to the first sentence
|
|
731
|
+
// of the body (summaryOf, shared/thread-cards.mjs), which on a row whose body
|
|
732
|
+
// is a list reads as that list with its line breaks taken out.
|
|
733
|
+
fileItem(slug, { title, body, problem, kind = 'review', priority = 5, labels = [] }) {
|
|
636
734
|
const { workItemsDisk } = this.modules;
|
|
637
735
|
return workItemsDisk.createWorkItem(
|
|
638
|
-
this.productDir(slug), { title, body, kind, priority, labels }, { source: 'agent' },
|
|
736
|
+
this.productDir(slug), { title, body, problem, kind, priority, labels }, { source: 'agent' },
|
|
639
737
|
);
|
|
640
738
|
}
|
|
641
739
|
|
|
740
|
+
// NEWS THE APP ITSELF HAS, ON A ROW THAT ALREADY EXISTS. The app's own words,
|
|
741
|
+
// so 'system' and a note: the answer is theirs and the result is the agent's.
|
|
742
|
+
// A note is a thread field, so a second one reads as the next thing said on
|
|
743
|
+
// the row rather than overwriting the first. Used by main/main-went-red.mjs
|
|
744
|
+
// to say main is green again on the row that reported the red.
|
|
745
|
+
noteItem(slug, id, note) {
|
|
746
|
+
const { workItemsDisk } = this.modules;
|
|
747
|
+
return workItemsDisk.updateWorkItem(this.productDir(slug), id, { note }, { source: 'system' });
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// A CLOUD RUN'S WORK KEPT ON A BRANCH OF ITS OWN, because laying it into the
|
|
751
|
+
// task folder would have undone work done there (main/cloud/run.mjs
|
|
752
|
+
// `park`). Said as a note; the row waits for her, and loses `ship`, so the
|
|
753
|
+
// folder never ships without the run's work.
|
|
754
|
+
cloudWorkKeptAside(slug, id, note) {
|
|
755
|
+
const { workItemsDisk } = this.modules;
|
|
756
|
+
const dir = this.productDir(slug);
|
|
757
|
+
const labels = this.readItem(slug, id)?.labels ?? [];
|
|
758
|
+
const kept = labels.filter((l) => l !== 'ship');
|
|
759
|
+
workItemsDisk.updateWorkItem(dir, id, { note, ...(kept.length !== labels.length ? { labels: kept } : {}) }, { source: 'system' });
|
|
760
|
+
return workItemsDisk.updateWorkItem(dir, id, { status: 'blocked' }, { source: 'system' });
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// A CLOSED ROW WHOSE WORK NEVER REACHED MAIN, put back in front of the
|
|
764
|
+
// person. Their close outranks the app's own writes, so it reopens the way
|
|
765
|
+
// their Reopen does (`authorityOf` records that as the app's), then waits on
|
|
766
|
+
// them so no run starts by itself.
|
|
767
|
+
closedBeforeItShipped(slug, id, note) {
|
|
768
|
+
const { workItemsDisk } = this.modules;
|
|
769
|
+
const dir = this.productDir(slug);
|
|
770
|
+
workItemsDisk.updateWorkItem(dir, id, { note }, { source: 'system' });
|
|
771
|
+
workItemsDisk.updateWorkItem(dir, id, { status: 'open' }, { source: 'founder' });
|
|
772
|
+
return workItemsDisk.updateWorkItem(dir, id, { status: 'blocked' }, { source: 'system' });
|
|
773
|
+
}
|
|
774
|
+
|
|
642
775
|
// New product: rare and deliberate. A directory, a project.json, and a first
|
|
643
776
|
// work item that seeds the drive toward launch.
|
|
644
777
|
//
|
|
@@ -938,9 +1071,11 @@ export class Store {
|
|
|
938
1071
|
* its order, which is how `walkRows` knows which one is which.
|
|
939
1072
|
*
|
|
940
1073
|
* The column is the app's own reading of the row, so each is written in the
|
|
941
|
-
* shape that reads that way and nothing marks it: a
|
|
942
|
-
*
|
|
943
|
-
*
|
|
1074
|
+
* shape that reads that way and nothing marks it: a working one is the ask
|
|
1075
|
+
* with a run and no result yet, and a scheduled one is the ask with a time
|
|
1076
|
+
* in the future, set as hers. None of them carries a result, because a
|
|
1077
|
+
* result is what puts a row back in the inbox and nothing here may land
|
|
1078
|
+
* there (shared/first-run-practice.mjs). */
|
|
944
1079
|
stagePracticeBackdrop(slug, { now = Date.now(), label = 'first-run' } = {}) {
|
|
945
1080
|
const { workItemsDisk } = this.modules;
|
|
946
1081
|
const dir = this.productDir(slug);
|
|
@@ -961,12 +1096,6 @@ export class Store {
|
|
|
961
1096
|
this.appendPracticeRun(slug, created.id, at.hers);
|
|
962
1097
|
this.writePracticeTrace(slug, created.id, row.trace, at.hers + 1_000);
|
|
963
1098
|
}
|
|
964
|
-
if (row.state === 'needs') {
|
|
965
|
-
workItemsDisk.updateWorkItem(
|
|
966
|
-
dir, created.id, { result: row.result },
|
|
967
|
-
{ source: 'agent', now: at.hers + 1_000 + row.trace.length * 9_000 + 1_000 },
|
|
968
|
-
);
|
|
969
|
-
}
|
|
970
1099
|
ids.push(created.id);
|
|
971
1100
|
}
|
|
972
1101
|
return { ids };
|
|
@@ -1191,29 +1320,35 @@ export class Store {
|
|
|
1191
1320
|
* Stamped a second apart, oldest last, so the newest thread on the card is
|
|
1192
1321
|
* the newest row in the inbox. `chosen` arrives newest first from
|
|
1193
1322
|
* `readSessionThreads`. */
|
|
1194
|
-
importThreadRows(slug, chosen, { now = Date.now() } = {}) {
|
|
1323
|
+
importThreadRows(slug, chosen, { now = Date.now(), land = 'inbox' } = {}) {
|
|
1195
1324
|
const { workItemsDisk } = this.modules;
|
|
1196
1325
|
const dir = this.productDir(slug);
|
|
1197
|
-
const product = this.listProducts().find((p) => p.slug === slug) ?? null;
|
|
1198
1326
|
let existing = [];
|
|
1199
1327
|
try { existing = workItemsDisk.readWorkItems(dir, now); } catch { /* a fresh project has none */ }
|
|
1200
1328
|
// Codex conversations on the same card are importCodexThreads's, below.
|
|
1201
1329
|
const claude = (chosen ?? []).filter((t) => t?.source !== 'codex');
|
|
1202
1330
|
const wanted = threadsNeedingRows(claude, existing);
|
|
1203
1331
|
const ids = [];
|
|
1332
|
+
let done = 0;
|
|
1204
1333
|
wanted.forEach((thread, i) => {
|
|
1205
|
-
const at = now - i * 1000;
|
|
1206
|
-
const row = threadImportRow(thread
|
|
1334
|
+
const { at, last } = chatTimes(thread, now - i * 1000);
|
|
1335
|
+
const row = threadImportRow(thread);
|
|
1207
1336
|
const created = workItemsDisk.createWorkItem(
|
|
1208
1337
|
dir,
|
|
1209
1338
|
{ title: row.title, kind: row.kind, priority: row.priority, labels: row.labels },
|
|
1210
1339
|
{ source: 'agent', now: at },
|
|
1211
1340
|
);
|
|
1212
|
-
|
|
1341
|
+
// Your words draw under You and the agent's under the agent.
|
|
1342
|
+
workItemsDisk.updateWorkItem(dir, created.id, { body: row.body }, { source: 'founder', now: at });
|
|
1343
|
+
if (row.result) workItemsDisk.updateWorkItem(dir, created.id, { result: row.result }, { source: 'agent', now: last + 1 });
|
|
1344
|
+
if (land === 'sorted' && !thread.waiting) {
|
|
1345
|
+
workItemsDisk.updateWorkItem(dir, created.id, { status: 'done' }, { source: 'founder', now: last + 2 });
|
|
1346
|
+
done += 1;
|
|
1347
|
+
}
|
|
1213
1348
|
ids.push(created.id);
|
|
1214
1349
|
});
|
|
1215
1350
|
const seen = new Set(claude.map((t) => String(t?.id ?? '').trim()).filter(Boolean));
|
|
1216
|
-
return { ids, added: ids.length, already: seen.size - ids.length };
|
|
1351
|
+
return { ids, added: ids.length, already: seen.size - ids.length, done };
|
|
1217
1352
|
}
|
|
1218
1353
|
|
|
1219
1354
|
/* ---------------------------- codex conversations ----------------------- */
|
|
@@ -1296,7 +1431,7 @@ export class Store {
|
|
|
1296
1431
|
* the body, Codex's last message as the result, open, in her inbox. No closes
|
|
1297
1432
|
* it under `not-imported`, which is where Closed finds it to offer it back.
|
|
1298
1433
|
* Yes on a row she once said no to is the same yes. */
|
|
1299
|
-
answerCodexImport(slug, id, choice, { thread = null, now = Date.now() } = {}) {
|
|
1434
|
+
answerCodexImport(slug, id, choice, { thread = null, now = Date.now(), bodyAt = now } = {}) {
|
|
1300
1435
|
const { workItemsDisk } = this.modules;
|
|
1301
1436
|
const dir = this.productDir(slug);
|
|
1302
1437
|
const item = workItemsDisk.readWorkItem(dir, id, now);
|
|
@@ -1309,15 +1444,18 @@ export class Store {
|
|
|
1309
1444
|
const t = { ...this.codexThreadFromRow(item), ...(thread ?? {}) };
|
|
1310
1445
|
const patch = codexMirrorPatch(t);
|
|
1311
1446
|
// it draws under You, not under an agent's name.
|
|
1312
|
-
workItemsDisk.updateWorkItem(dir, id, { body: patch.body }, { source: 'founder', now });
|
|
1313
|
-
workItemsDisk.updateWorkItem(dir, id, { kind: patch.kind, labels: patch.labels, result: patch.result }, { source: 'agent', now: now + 1 });
|
|
1447
|
+
workItemsDisk.updateWorkItem(dir, id, { body: patch.body }, { source: 'founder', now: bodyAt });
|
|
1448
|
+
const mirrored = workItemsDisk.updateWorkItem(dir, id, { kind: patch.kind, labels: patch.labels, result: patch.result }, { source: 'agent', now: now + 1 });
|
|
1449
|
+
// Only a row that was not open is reopened; on an open one the line reads
|
|
1450
|
+
// as "You sent it back" in the thread.
|
|
1451
|
+
if (item.status === 'open') return mirrored;
|
|
1314
1452
|
return workItemsDisk.updateWorkItem(dir, id, { status: 'open' }, { source: 'founder', now: now + 1 });
|
|
1315
1453
|
}
|
|
1316
1454
|
|
|
1317
1455
|
/**
|
|
1318
1456
|
* THE ⌘K CARD'S PRESS on Codex conversations: each becomes a readable row at
|
|
1319
1457
|
* once, whether it had an asking row, a declined one, or none. */
|
|
1320
|
-
importCodexThreads(slug, chosen, { now = Date.now() } = {}) {
|
|
1458
|
+
importCodexThreads(slug, chosen, { now = Date.now(), land = 'inbox' } = {}) {
|
|
1321
1459
|
const { workItemsDisk } = this.modules;
|
|
1322
1460
|
const dir = this.productDir(slug);
|
|
1323
1461
|
const product = this.listProducts().find((p) => p.slug === slug) ?? null;
|
|
@@ -1325,6 +1463,7 @@ export class Store {
|
|
|
1325
1463
|
try { existing = workItemsDisk.readWorkItems(dir, now); } catch { existing = []; }
|
|
1326
1464
|
const ids = [];
|
|
1327
1465
|
let already = 0;
|
|
1466
|
+
let done = 0;
|
|
1328
1467
|
for (const thread of chosen ?? []) {
|
|
1329
1468
|
if (thread?.source !== 'codex') continue; // a Claude Code thread is importThreadRows's
|
|
1330
1469
|
const id = String(thread?.id ?? '').trim();
|
|
@@ -1332,20 +1471,28 @@ export class Store {
|
|
|
1332
1471
|
const had = existing.find((i) => codexIdOf(i) === id);
|
|
1333
1472
|
if (had && isCodexMirrorRow(had) && had.status !== 'done') { already += 1; continue; }
|
|
1334
1473
|
let rowId = had?.id ?? null;
|
|
1335
|
-
|
|
1474
|
+
const fresh = !rowId;
|
|
1475
|
+
// A row it already had keeps the import's time: an older line would lose
|
|
1476
|
+
// to what that row already says.
|
|
1477
|
+
const { at, last } = fresh ? chatTimes(thread, now) : { at: now, last: now };
|
|
1478
|
+
if (fresh) {
|
|
1336
1479
|
const row = codexImportRow(thread, { projectName: product?.name ?? null, now });
|
|
1337
1480
|
const created = workItemsDisk.createWorkItem(
|
|
1338
1481
|
dir,
|
|
1339
1482
|
{ title: row.title, kind: row.kind, priority: row.priority, labels: row.labels },
|
|
1340
|
-
{ source: 'agent', now },
|
|
1483
|
+
{ source: 'agent', now: at },
|
|
1341
1484
|
);
|
|
1342
|
-
workItemsDisk.updateWorkItem(dir, created.id, { body: row.body }, { source: 'agent', now });
|
|
1485
|
+
workItemsDisk.updateWorkItem(dir, created.id, { body: row.body }, { source: 'agent', now: at });
|
|
1343
1486
|
rowId = created.id;
|
|
1344
1487
|
}
|
|
1345
|
-
this.answerCodexImport(slug, rowId, 'yes', { thread, now:
|
|
1488
|
+
this.answerCodexImport(slug, rowId, 'yes', { thread, now: last + 2, bodyAt: fresh ? at + 1 : last + 2 });
|
|
1489
|
+
if (land === 'sorted' && !thread.waiting) {
|
|
1490
|
+
workItemsDisk.updateWorkItem(dir, rowId, { status: 'done' }, { source: 'founder', now: last + 4 });
|
|
1491
|
+
done += 1;
|
|
1492
|
+
}
|
|
1346
1493
|
ids.push(rowId);
|
|
1347
1494
|
}
|
|
1348
|
-
return { ids, added: ids.length, already };
|
|
1495
|
+
return { ids, added: ids.length, already, done };
|
|
1349
1496
|
}
|
|
1350
1497
|
|
|
1351
1498
|
/**
|
|
@@ -1380,6 +1527,8 @@ export class Store {
|
|
|
1380
1527
|
let refreshed = 0;
|
|
1381
1528
|
for (const item of this.listAllWorkItems(now)) {
|
|
1382
1529
|
if (!isCodexMirrorRow(item) || item.status === 'done') continue;
|
|
1530
|
+
// Replied on here, so an agent here answers it from now on.
|
|
1531
|
+
if (item.answer) continue;
|
|
1383
1532
|
const t = byId.get(codexIdOf(item));
|
|
1384
1533
|
if (!t) continue;
|
|
1385
1534
|
const patch = codexMirrorPatch(t);
|
|
@@ -1450,6 +1599,20 @@ export class Store {
|
|
|
1450
1599
|
watch() {
|
|
1451
1600
|
this.unwatch();
|
|
1452
1601
|
const dirs = [this.config.accountRoot, ...this.listProducts().map((p) => p.dir)];
|
|
1602
|
+
// The team chat and the teammates, which agents write to through the store
|
|
1603
|
+
// tools: watched so their words reach the screen as they are said.
|
|
1604
|
+
if (this.teammates) {
|
|
1605
|
+
try {
|
|
1606
|
+
const dir = this.teammates.dir();
|
|
1607
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1608
|
+
const watcher = fs.watch(dir, () => this._notify());
|
|
1609
|
+
watcher.on('error', () => {
|
|
1610
|
+
try { watcher.close(); } catch {}
|
|
1611
|
+
this.watchers = this.watchers.filter((w) => w !== watcher);
|
|
1612
|
+
});
|
|
1613
|
+
this.watchers.push(watcher);
|
|
1614
|
+
} catch {}
|
|
1615
|
+
}
|
|
1453
1616
|
for (const dir of dirs) {
|
|
1454
1617
|
try {
|
|
1455
1618
|
const watcher = fs.watch(dir, (event, file) => {
|
|
@@ -1499,3 +1662,14 @@ export class Store {
|
|
|
1499
1662
|
function clockAt(ms) {
|
|
1500
1663
|
return new Date(ms).toISOString().slice(11, 19);
|
|
1501
1664
|
}
|
|
1665
|
+
|
|
1666
|
+
/**
|
|
1667
|
+
* When a chat brought in happened: `at` when it started, `last` when it last
|
|
1668
|
+
* moved. A chat with no time it can be trusted for, or one ahead of `fallback`
|
|
1669
|
+
* (the moment of import), is stamped with `fallback`. */
|
|
1670
|
+
function chatTimes(thread, fallback) {
|
|
1671
|
+
const last = Number(thread?.when);
|
|
1672
|
+
if (!(last > 0 && last <= fallback)) return { at: fallback, last: fallback };
|
|
1673
|
+
const first = Number(thread?.startedAt);
|
|
1674
|
+
return { at: first > 0 && first <= last ? first : last, last };
|
|
1675
|
+
}
|