agentbox-app 0.1.12 → 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/acp.mjs +296 -0
- package/main/agent-homes.mjs +158 -0
- package/main/agent-sessions.mjs +311 -260
- package/main/agent-updates.mjs +9 -22
- package/main/agents.mjs +179 -466
- package/main/analytics.mjs +42 -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-bin.mjs +45 -311
- package/main/claude-input.mjs +57 -7
- package/main/claude-launch-env.mjs +54 -0
- package/main/claude-models.mjs +39 -7
- package/main/claude-plan.mjs +64 -16
- package/main/claude-session-open.mjs +28 -0
- package/main/claude-usage-process.mjs +40 -0
- package/main/claude-usage.mjs +94 -59
- 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-bin.mjs +51 -249
- package/main/codex-models.mjs +20 -2
- package/main/codex-session.mjs +246 -506
- package/main/codex-threads-worker.mjs +12 -0
- package/main/codex-threads.mjs +112 -11
- package/main/codex-usage-file.mjs +5 -3
- package/main/codex-usage.mjs +56 -7
- package/main/codex-watch.mjs +83 -5
- package/main/codex.mjs +95 -363
- package/main/config.mjs +37 -5
- package/main/corner-tag.mjs +810 -0
- package/main/crash-report.mjs +26 -3
- package/main/dev-window.mjs +1 -1
- package/main/engine-bin.mjs +545 -0
- package/main/engine-setup.mjs +25 -24
- package/main/feedback.mjs +14 -31
- 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-stream.mjs +208 -0
- package/main/harnesses/claude.mjs +105 -0
- package/main/harnesses/codex.mjs +72 -0
- package/main/harnesses/common.mjs +78 -0
- package/main/harnesses/instructions-file.mjs +116 -0
- package/main/harnesses/opencode.mjs +234 -0
- package/main/harnesses.mjs +65 -0
- package/main/importable-threads.mjs +96 -0
- package/main/ipc.mjs +520 -135
- package/main/leftovers.mjs +117 -25
- package/main/liquid-glass.mjs +26 -0
- package/main/live-replies.mjs +10 -4
- package/main/local-app-frames.mjs +82 -0
- package/main/main-went-red.mjs +330 -0
- package/main/main.mjs +248 -37
- package/main/memory-gate-history.mjs +85 -20
- package/main/memory-gate-server.mjs +129 -11
- package/main/memory-gate.mjs +62 -8
- package/main/memory-log.mjs +124 -0
- 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 +64 -5
- package/main/opencode-server.mjs +271 -0
- package/main/opencode.mjs +287 -0
- package/main/phone-alerts.mjs +117 -0
- package/main/phone-inbox.mjs +625 -0
- package/main/phone-link.mjs +571 -0
- 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-address.mjs +28 -0
- package/main/relay-client.mjs +314 -0
- package/main/repeats.mjs +10 -4
- package/main/row-label.mjs +18 -47
- package/main/run-change.mjs +13 -0
- package/main/run-ledger.mjs +135 -0
- package/main/runs-on.mjs +32 -35
- package/main/save-attachments.mjs +41 -0
- package/main/serve.mjs +155 -9
- package/main/settings.mjs +364 -50
- package/main/shell-path.mjs +234 -0
- package/main/ship-conflict-brief.mjs +119 -0
- package/main/ship-queue.mjs +134 -20
- package/main/sign-in-files.mjs +3 -7
- package/main/source-updater.mjs +45 -11
- package/main/stage-attachment.mjs +68 -0
- package/main/stall-watch.mjs +50 -0
- package/main/start-hold.mjs +73 -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 +227 -53
- package/main/supervisor.mjs +3865 -699
- package/main/task-commands.mjs +13 -17
- package/main/task-folders-offthread.mjs +6 -1
- package/main/task-folders.mjs +897 -105
- package/main/task-terminals.mjs +147 -41
- 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/updater.mjs +71 -9
- package/main/webview-guard.mjs +45 -0
- package/main/worktree-include.mjs +365 -0
- 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 +30 -5
- package/renderer/dist/assets/abnfDiagram-YUKMZFIV-CoWB6YUK.js +1 -0
- package/renderer/dist/assets/arc-Ch0NrCul.js +1 -0
- package/renderer/dist/assets/architectureDiagram-47P4ROYG-CXz9M5tQ.js +36 -0
- package/renderer/dist/assets/blockDiagram-E7TT5TSB-DchGUuHH.js +140 -0
- package/renderer/dist/assets/c4Diagram-CRA5TL53-Cip-zNyr.js +38 -0
- package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
- package/renderer/dist/assets/chunk-2Q5K7J3B-BoXid_2r.js +1 -0
- package/renderer/dist/assets/chunk-5VM5RSS4-Cpez1rRz.js +15 -0
- package/renderer/dist/assets/chunk-7TKQ45FW-DKA84YkE.js +1 -0
- package/renderer/dist/assets/chunk-AW2ZBBNX-Dg-SumPG.js +88 -0
- package/renderer/dist/assets/chunk-FVRAUYC3-06TCvPm1.js +2 -0
- package/renderer/dist/assets/chunk-HTAEGDNF-CMEBy1Td.js +1 -0
- package/renderer/dist/assets/chunk-JWPE2WC7-BJ2ObmV-.js +1 -0
- package/renderer/dist/assets/chunk-KQW6MTUR-CGWOYXUE.js +1 -0
- package/renderer/dist/assets/chunk-XXDRQBXY-CiZp07XC.js +1 -0
- package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-DJNIX719.js +217 -0
- 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-CbkyZOUH.js +1 -0
- package/renderer/dist/assets/cynefin-EF2NZ3EQ-78iOroZO.js +141 -0
- package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-GcCopMs7.js +62 -0
- package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
- package/renderer/dist/assets/dagre-W4DXFKR2-DHLlXLkz.js +4 -0
- package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/renderer/dist/assets/diagram-2UJZ2QOL-DuZSUHcH.js +200 -0
- package/renderer/dist/assets/diagram-OVF4WLC6-Ckxu3cqH.js +24 -0
- package/renderer/dist/assets/diagram-PFPMY2P6-CsTw9Ycn.js +30 -0
- package/renderer/dist/assets/diagram-UMYRVEAY-CTXd8qVg.js +24 -0
- package/renderer/dist/assets/diagram-YEKJPTXX-CJs1T_9m.js +41 -0
- package/renderer/dist/assets/diagram-ZIFT7M5P-CvjI23mf.js +3 -0
- package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-d_IeAzOJ.js +1 -0
- package/renderer/dist/assets/elk-IJKZMXRS-zo9KFP3N.js +27 -0
- package/renderer/dist/assets/erDiagram-O2IAPWRE-DqfYf5Dt.js +99 -0
- package/renderer/dist/assets/flowDiagram-OXPTDLAJ-DAWsMLmo.js +213 -0
- package/renderer/dist/assets/ganttDiagram-R7TSEDQI-4ml_bhwR.js +292 -0
- package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-B_WcqSId.js +106 -0
- package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
- package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-CI-jYqzc.js +2 -0
- package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
- package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-B56VWRlV.js +70 -0
- package/renderer/dist/assets/journeyDiagram-COXZFDF6-D1-p09Lr.js +139 -0
- package/renderer/dist/assets/kanban-definition-P3RFRI5V-Cv3w7JoN.js +89 -0
- package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
- package/renderer/dist/assets/linear-C35V2Spi.js +1 -0
- package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
- package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
- package/renderer/dist/assets/mermaid.core-BzGT5Vo9.js +314 -0
- package/renderer/dist/assets/mindmap-definition-LZFPQGKD-BZUfuymC.js +96 -0
- package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/renderer/dist/assets/pegDiagram-BGZESJAR-DFh9XNGe.js +1 -0
- package/renderer/dist/assets/pieDiagram-CAPJLFHJ-C65tPVsy.js +39 -0
- package/renderer/dist/assets/product-name-CSNW6d7s.js +40 -0
- package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-Cushj_nk.js +7 -0
- package/renderer/dist/assets/railroadDiagram-SM67HX2B-DZGebGx5.js +1 -0
- package/renderer/dist/assets/requirementDiagram-X7JNWC4A-DpAB0MNo.js +84 -0
- package/renderer/dist/assets/sankeyDiagram-UM26HJRW-D-29rHra.js +40 -0
- package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-FfOQAZol.js +169 -0
- package/renderer/dist/assets/sizeCapture-INFHLROL-C_n6fDBY.js +1 -0
- package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-BZjenzJG.js +291 -0
- package/renderer/dist/assets/swimlanes-N4OXWK64-DxaMU9vX.js +1 -0
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
- package/renderer/dist/assets/timeline-definition-YOQAKGHF-DTg5HJKz.js +120 -0
- package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-Bc3MZDsl.js +370 -0
- package/renderer/dist/assets/vennDiagram-BLWOH2XV-GIVGk9Id.js +34 -0
- package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-NmJnbQOg.js +78 -0
- package/renderer/dist/assets/xychartDiagram-TAQBALBS-p-kfGfnW.js +7 -0
- package/renderer/dist/corner-tag.html +13 -0
- package/renderer/dist/emulator/audio-rules.mjs +74 -0
- package/renderer/dist/emulator/data/compression/README.md +11 -0
- package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
- package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
- package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
- package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
- package/renderer/dist/emulator/data/cores/README.md +22 -0
- package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/package.json +65 -0
- package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
- package/renderer/dist/emulator/data/emulator.css +1614 -0
- package/renderer/dist/emulator/data/loader.js +170 -0
- package/renderer/dist/emulator/data/localization/README.md +63 -0
- package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
- package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
- package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
- package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
- package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
- package/renderer/dist/emulator/data/localization/en-US.json +339 -0
- package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
- package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
- package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
- package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
- package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
- package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
- package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
- package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
- package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
- package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
- package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
- package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
- package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
- package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
- package/renderer/dist/emulator/data/src/GameManager.js +464 -0
- package/renderer/dist/emulator/data/src/compression.js +142 -0
- package/renderer/dist/emulator/data/src/emulator.js +6158 -0
- package/renderer/dist/emulator/data/src/gamepad.js +167 -0
- package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
- package/renderer/dist/emulator/data/src/shaders.js +236 -0
- package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
- package/renderer/dist/emulator/data/src/storage.js +125 -0
- package/renderer/dist/emulator/data/version.json +5 -0
- package/renderer/dist/emulator/host.js +297 -0
- package/renderer/dist/emulator/index.html +28 -0
- package/renderer/dist/emulator/provenance.json +6 -0
- package/renderer/dist/index.html +16 -3
- package/scripts/after-install.mjs +30 -8
- package/scripts/approval-server.sh +24 -5
- package/scripts/before-publish.mjs +12 -10
- 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 +62 -34
- package/shared/agents.d.mts +1 -0
- 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 +45 -1
- package/shared/claude-code-shape.mjs +6 -0
- package/shared/claude-commands.generated.mjs +6 -4
- package/shared/claude-models.generated.mjs +6 -4
- 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 +36 -0
- package/shared/corner-tag.mjs +396 -0
- package/shared/dead-run-trace.mjs +19 -2
- package/shared/engines.mjs +76 -300
- 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 +111 -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 +31 -0
- package/shared/notify-rules.mjs +55 -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/provider-commands.mjs +10 -2
- package/shared/rank.d.mts +4 -1
- package/shared/rank.mjs +82 -19
- package/shared/relay-address.mjs +6 -0
- package/shared/relay-crypto.mjs +165 -0
- 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 -13
- package/shared/runs-on.mjs +31 -52
- package/shared/shipped-note.d.mts +4 -0
- package/shared/shipped-note.mjs +6 -0
- package/shared/shortcuts.mjs +24 -10
- package/shared/spawn-trouble.d.mts +4 -0
- package/shared/spawn-trouble.mjs +484 -30
- 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/terminal-input.d.mts +1 -0
- package/shared/terminal-input.mjs +30 -0
- package/shared/terminal-keys.d.mts +1 -0
- package/shared/terminal-keys.mjs +16 -0
- package/shared/terminal-size.d.mts +4 -0
- package/shared/terminal-size.mjs +29 -0
- package/shared/thread-cards.mjs +4 -1
- package/shared/usage.d.mts +12 -0
- package/shared/usage.mjs +56 -3
- package/shared/websites.d.mts +15 -0
- package/shared/websites.mjs +61 -0
- package/shared/where-it-runs-from.d.mts +43 -0
- package/shared/where-it-runs-from.mjs +155 -0
- package/shared/work-items.mjs +150 -29
- package/shared/work-lines.mjs +93 -245
- package/shared/xterm-invalid-range.d.mts +2 -0
- package/shared/xterm-invalid-range.mjs +58 -0
- package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
- package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// ONE THREAD, AS HER PHONE SEES IT WITH THE MAC SHUT (docs/cloud/PHONE-PLAN.md).
|
|
2
|
+
//
|
|
3
|
+
// Her Mac writes this for each thread of a project she put on her phone, and
|
|
4
|
+
// the phone reads it. It is what the phone's own screens already draw: the row
|
|
5
|
+
// as the Mac folded it (the phone never decides a thread's state from raw
|
|
6
|
+
// lines; Codex's review of the plan, finding 5), and the thread's history as
|
|
7
|
+
// the window reads it (store.readHistory), which the thread view turns into
|
|
8
|
+
// the conversation. Long text is cut, and only the latest lines go. Browser-
|
|
9
|
+
// safe: no node imports.
|
|
10
|
+
|
|
11
|
+
import { checkSetting } from './phone-settings.mjs';
|
|
12
|
+
|
|
13
|
+
// How much of one thread goes up. The latest of a thread is what she reads on
|
|
14
|
+
// a phone; the whole of it stays on the Mac. `bytes` is measured in UTF-8, as
|
|
15
|
+
// the database measures it (Codex's review of step 1, finding 9).
|
|
16
|
+
export const SNAPSHOT_LIMITS = { lines: 400, textChars: 12_000, items: 300, bytes: 900_000 };
|
|
17
|
+
|
|
18
|
+
// WHAT A COPY MAY CARRY, BY NAME (Codex's review of step 1, finding 2). A copy
|
|
19
|
+
// is built out of these and nothing else, never by copying a row and deleting
|
|
20
|
+
// the bad parts: a lease, an epoch, who may see a team thread, a field some
|
|
21
|
+
// later version adds, none of it leaves unless it is named here.
|
|
22
|
+
const ROW_FIELDS = [
|
|
23
|
+
'id', 'title', 'label', 'body', 'kind', 'labels', 'priority', 'parent', 'status',
|
|
24
|
+
'result', 'note', 'answer', 'runAt', 'snoozedUntil', 'answeredThrough',
|
|
25
|
+
'engine', 'model', 'start', 'due', 'problem', 'progress', 'solution',
|
|
26
|
+
'createdAt', 'updatedAt',
|
|
27
|
+
];
|
|
28
|
+
// The fields a line in a thread's history may carry in its patch: what the
|
|
29
|
+
// thread view draws as the conversation and its state changes.
|
|
30
|
+
const LINE_FIELDS = [
|
|
31
|
+
'title', 'label', 'body', 'kind', 'labels', 'priority', 'status', 'result',
|
|
32
|
+
'note', 'answer', 'runAt', 'snoozedUntil', 'start', 'due', 'problem',
|
|
33
|
+
'progress', 'solution', 'inReplyTo',
|
|
34
|
+
];
|
|
35
|
+
const SOURCES = new Set(['founder', 'agent', 'system']);
|
|
36
|
+
|
|
37
|
+
// KEYS AND TOKENS IN WHAT AN AGENT OR SHE WROTE, hidden. A conversation can
|
|
38
|
+
// quote one ("here is the key it used"), and a copy online is no place for it.
|
|
39
|
+
const SECRETS = [
|
|
40
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g,
|
|
41
|
+
/\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{20,}/g,
|
|
42
|
+
/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}\b/g,
|
|
43
|
+
/\bgithub_pat_[A-Za-z0-9_]{40,}\b/g,
|
|
44
|
+
/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
|
|
45
|
+
/\bxox[abposr]-[A-Za-z0-9-]{10,}/g,
|
|
46
|
+
/\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
47
|
+
/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
|
|
48
|
+
/\b(Bearer\s+)[A-Za-z0-9._~+/-]{20,}=*/gi,
|
|
49
|
+
/\b(Basic\s+)[A-Za-z0-9+/]{12,}=*/gi,
|
|
50
|
+
];
|
|
51
|
+
// A VALUE GIVEN A SECRET'S NAME, whatever it looks like (Codex's second and
|
|
52
|
+
// third reviews): `AWS_SECRET_ACCESS_KEY=...`, `password: hunter22`,
|
|
53
|
+
// `PASSWORD=12345678`, `password="correct horse battery staple"`. The name
|
|
54
|
+
// stays, so the sentence still reads; a quoted value goes whole.
|
|
55
|
+
const STRONG = '[A-Za-z0-9_.-]*(?:passw(?:or)?d|passphrase|secret|api[_-]?key|access[_-]?key|private[_-]?key|credentials?|authorization|(?:auth|access|refresh|session|bearer|id)[_-]?token)[A-Za-z0-9_.-]*';
|
|
56
|
+
// "token" alone is a word people use ("token: unexpected identifier"), so it
|
|
57
|
+
// counts only as an ENV_STYLE name, or before a long run that looks like a key.
|
|
58
|
+
const WEAK = '(?:[A-Z0-9_]*TOKEN[A-Z0-9_]*|token)';
|
|
59
|
+
// A quoted value goes whole, however long, escaped quotes and all; a bare one
|
|
60
|
+
// is everything up to a space, brackets included (Codex's fourth review).
|
|
61
|
+
const VALUE = '(?:"((?:[^"\\\\\\n]|\\\\.)*)"|\'((?:[^\'\\\\\\n]|\\\\.)*)\'|([^\\s"\',;]+))';
|
|
62
|
+
const NAMED_STRONG = new RegExp(`\\b(${STRONG})(["']?\\s*[:=]\\s*)${VALUE}`, 'gi');
|
|
63
|
+
const NAMED_WEAK = new RegExp(`\\b(${WEAK})(["']?\\s*[:=]\\s*)${VALUE}`, 'g');
|
|
64
|
+
const hidden = (name, sep, dq, sq, bare) => `${name}${sep}${dq !== undefined ? '"[hidden]"' : sq !== undefined ? "'[hidden]'" : '[hidden]'}`;
|
|
65
|
+
function hideNamed(text) {
|
|
66
|
+
let out = text.replace(NAMED_STRONG, (m, name, sep, dq, sq, bare) => (bare === '[hidden]' ? m : hidden(name, sep, dq, sq, bare)));
|
|
67
|
+
out = out.replace(NAMED_WEAK, (m, name, sep, dq, sq, bare) => {
|
|
68
|
+
const value = dq ?? sq ?? bare ?? '';
|
|
69
|
+
// MAX_TOKENS=128000 and TOKEN_LIMIT=8192 are settings: a bare number is
|
|
70
|
+
// never taken for a key under this weaker name.
|
|
71
|
+
const envStyle = name !== 'token' && name === name.toUpperCase() && !/^[0-9]+$/.test(value);
|
|
72
|
+
const keyLike = value.length >= 16 && /[0-9]/.test(value) && !/\s/.test(value);
|
|
73
|
+
return value !== '[hidden]' && (envStyle || keyLike) ? hidden(name, sep, dq, sq, bare) : m;
|
|
74
|
+
});
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
// AND A LONG RUN OF LETTERS AND DIGITS THAT LOOKS LIKE NOTHING BUT A KEY: 32 or
|
|
78
|
+
// more, upper and lower case and digits all present. A commit id or a uuid is
|
|
79
|
+
// one case of hex and is left alone.
|
|
80
|
+
const KEYLIKE = /\b(?=[A-Za-z0-9_-]{32,}\b)(?=[A-Za-z0-9_-]*[a-z])(?=[A-Za-z0-9_-]*[A-Z])(?=[A-Za-z0-9_-]*[0-9])[A-Za-z0-9_-]{32,}\b/g;
|
|
81
|
+
export function hideSecrets(text, { home = null } = {}) {
|
|
82
|
+
let out = String(text);
|
|
83
|
+
for (const re of SECRETS) out = out.replace(re, (m, bearer) => (typeof bearer === 'string' && /^(Bearer|Basic)/i.test(bearer) ? `${bearer}[hidden]` : '[hidden]'));
|
|
84
|
+
out = hideNamed(out);
|
|
85
|
+
out = out.replace(KEYLIKE, '[hidden]');
|
|
86
|
+
// Her home folder is ~ online, as it is in a crash report (shared/crash-scrub.mjs).
|
|
87
|
+
if (home && home.length > 1) out = out.split(home).join('~');
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const clip = (s, n, ctx) => {
|
|
92
|
+
const t = hideSecrets(s, ctx);
|
|
93
|
+
return t.length > n ? `${t.slice(0, n)}…` : t;
|
|
94
|
+
};
|
|
95
|
+
// A value of a named field: text cut and cleaned, numbers as they are, short
|
|
96
|
+
// lists of words, nothing nested.
|
|
97
|
+
function plain(v, n, ctx) {
|
|
98
|
+
if (typeof v === 'string') return clip(v, n, ctx);
|
|
99
|
+
if (typeof v === 'number' && Number.isFinite(v)) return v;
|
|
100
|
+
if (typeof v === 'boolean' || v === null) return v;
|
|
101
|
+
if (Array.isArray(v)) return v.filter((x) => typeof x === 'string').slice(0, 40).map((x) => clip(x, 200, ctx));
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
function pick(obj, fields, n, ctx) {
|
|
105
|
+
const out = {};
|
|
106
|
+
for (const f of fields) {
|
|
107
|
+
if (!obj || !(f in obj)) continue;
|
|
108
|
+
const v = plain(obj[f], n, ctx);
|
|
109
|
+
if (v !== undefined) out[f] = v;
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const bytesOf = (value) => new TextEncoder().encode(JSON.stringify(value)).length;
|
|
115
|
+
|
|
116
|
+
// `item` is the folded row; `history` is store.readHistory for it. `home` is
|
|
117
|
+
// her home folder, written ~ in the copy.
|
|
118
|
+
export function threadSnapshot(item, history = [], limits = SNAPSHOT_LIMITS, { home = null } = {}) {
|
|
119
|
+
const ctx = { home };
|
|
120
|
+
const row = pick(item, ROW_FIELDS, limits.textChars, ctx);
|
|
121
|
+
// Who wrote each field, which is how the screens tell her words from an
|
|
122
|
+
// agent's: the source and the moment, nothing else of it.
|
|
123
|
+
if (item?.wrote && typeof item.wrote === 'object') {
|
|
124
|
+
const wrote = {};
|
|
125
|
+
for (const f of ROW_FIELDS) {
|
|
126
|
+
const w = item.wrote[f];
|
|
127
|
+
if (w && SOURCES.has(w.source) && Number.isFinite(w.ts)) wrote[f] = { ts: w.ts, source: w.source };
|
|
128
|
+
}
|
|
129
|
+
row.wrote = wrote;
|
|
130
|
+
}
|
|
131
|
+
const lines = [];
|
|
132
|
+
for (const l of Array.isArray(history) ? history : []) {
|
|
133
|
+
if (!l || l.claim || l.release || l.heartbeat || !SOURCES.has(l.source) || !Number.isFinite(l.ts)) continue;
|
|
134
|
+
const patch = pick(l.patch, LINE_FIELDS, limits.textChars, ctx);
|
|
135
|
+
if (!Object.keys(patch).length) continue;
|
|
136
|
+
lines.push({ id: row.id, ts: l.ts, source: l.source, patch });
|
|
137
|
+
}
|
|
138
|
+
// Under the database's megabyte, with room: fewer lines until it fits, and
|
|
139
|
+
// then shorter text, so one long thread never stops the rest being sent.
|
|
140
|
+
for (let keep = limits.lines, chars = limits.textChars; ;) {
|
|
141
|
+
const kept = lines.slice(-keep);
|
|
142
|
+
const snap = {
|
|
143
|
+
v: 3,
|
|
144
|
+
item: chars === limits.textChars ? row : pick(row, ROW_FIELDS, chars, ctx),
|
|
145
|
+
history: chars === limits.textChars ? kept : kept.map((l) => ({ ...l, patch: pick(l.patch, LINE_FIELDS, chars, ctx) })),
|
|
146
|
+
olderLines: lines.length - kept.length,
|
|
147
|
+
};
|
|
148
|
+
if (row.wrote && snap.item !== row) snap.item.wrote = row.wrote;
|
|
149
|
+
if (bytesOf(snap) <= limits.bytes) return snap;
|
|
150
|
+
if (keep > 1) keep = Math.floor(keep / 2);
|
|
151
|
+
else if (chars > 200) chars = Math.floor(chars / 4);
|
|
152
|
+
else return { v: 3, item: { id: row.id, title: clip(row.title ?? '', 200, ctx), status: row.status, updatedAt: row.updatedAt }, history: [], olderLines: lines.length };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// WHICH PROJECTS ARE ON THE PHONE OF THE PERSON SIGNED IN.
|
|
157
|
+
//
|
|
158
|
+
// ALL OF THEM, UNLESS SHE SAYS OTHERWISE (2026-10-15: "95% of people just want
|
|
159
|
+
// all of their projects connected ... I just turn them all on"). One switch
|
|
160
|
+
// for the lot, on until she turns it off, and new projects go up with the
|
|
161
|
+
// rest; keeping a project on this computer only is the exception, named
|
|
162
|
+
// project by project (`except`).
|
|
163
|
+
//
|
|
164
|
+
// Her choice is kept with her id on it (`phoneProducts` in the config:
|
|
165
|
+
// { owner, all, except }), so somebody else signing in on this computer starts
|
|
166
|
+
// from the default rather than from her choices (Codex's review of step 1,
|
|
167
|
+
// finding 1). A choice saved in the first shape, a list of the projects she
|
|
168
|
+
// turned on ({ owner, slugs }), reads as all of them except the ones that list
|
|
169
|
+
// left out, so the projects she had on stay on.
|
|
170
|
+
// `every` is every project here, by slug.
|
|
171
|
+
// The projects that can go to a phone at all: not the walk's practice project.
|
|
172
|
+
export const phoneEligible = (products) => (Array.isArray(products) ? products : []).map((p) => p?.slug).filter((s) => typeof s === 'string' && s && s !== 'practice');
|
|
173
|
+
export function phoneChoiceOf(saved, personId, every = []) {
|
|
174
|
+
const mine = personId && saved && typeof saved === 'object' && saved.owner === personId ? saved : null;
|
|
175
|
+
if (!mine) return { all: true, except: [] };
|
|
176
|
+
if (mine.all === undefined && Array.isArray(mine.slugs)) {
|
|
177
|
+
const on = new Set(mine.slugs);
|
|
178
|
+
return { all: true, except: every.filter((s) => !on.has(s)) };
|
|
179
|
+
}
|
|
180
|
+
return { all: mine.all !== false, except: Array.isArray(mine.except) ? mine.except.filter((s) => typeof s === 'string' && s) : [] };
|
|
181
|
+
}
|
|
182
|
+
export function phoneProjectsOf(saved, personId, every = []) {
|
|
183
|
+
if (!personId) return [];
|
|
184
|
+
const { all, except } = phoneChoiceOf(saved, personId, every);
|
|
185
|
+
if (!all) return [];
|
|
186
|
+
const kept = new Set(except);
|
|
187
|
+
return every.filter((s) => typeof s === 'string' && s && !kept.has(s));
|
|
188
|
+
}
|
|
189
|
+
// One project on or off the phone; the rest as they were.
|
|
190
|
+
export function withPhoneProject(saved, personId, slug, on, every = []) {
|
|
191
|
+
if (!personId) throw new Error('sign in first');
|
|
192
|
+
const { all, except } = phoneChoiceOf(saved, personId, every);
|
|
193
|
+
const kept = new Set(except);
|
|
194
|
+
if (on) kept.delete(slug); else kept.add(slug);
|
|
195
|
+
return { owner: personId, all, except: [...kept] };
|
|
196
|
+
}
|
|
197
|
+
// The whole phone on or off; which projects stay local is remembered.
|
|
198
|
+
export function withPhoneAll(saved, personId, on, every = []) {
|
|
199
|
+
if (!personId) throw new Error('sign in first');
|
|
200
|
+
const { except } = phoneChoiceOf(saved, personId, every);
|
|
201
|
+
return { owner: personId, all: !!on, except };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// A NEW THREAD STARTED ON THE PHONE has a row there before the Mac makes it,
|
|
205
|
+
// under this id, and a reply to it before then is a reply to this id. The Mac
|
|
206
|
+
// remembers which real row it became (main/phone-inbox.mjs) (Codex's review of
|
|
207
|
+
// step 1, finding 8). Shaped like a row id, so every screen takes it.
|
|
208
|
+
export const madeHere = (commandId) => `w-${String(commandId).replace(/[^0-9a-f]/gi, '').slice(0, 10).toLowerCase()}`;
|
|
209
|
+
|
|
210
|
+
// The threads of a project worth carrying: everything not yet archived, and
|
|
211
|
+
// what was finished in the last fortnight, newest first, at most `items`.
|
|
212
|
+
export function threadsWorthCarrying(items, { now = Date.now(), limits = SNAPSHOT_LIMITS } = {}) {
|
|
213
|
+
const fortnight = 14 * 24 * 3600_000;
|
|
214
|
+
return (Array.isArray(items) ? items : [])
|
|
215
|
+
.filter((i) => i && !i.agent && (i.status !== 'done' || now - (i.updatedAt ?? 0) < fortnight))
|
|
216
|
+
.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0))
|
|
217
|
+
.slice(0, limits.items);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// A cheap, stable fingerprint, so a thread that did not change is not sent.
|
|
221
|
+
export function snapshotKey(snapshot) {
|
|
222
|
+
const text = JSON.stringify(snapshot);
|
|
223
|
+
let h = 2166136261;
|
|
224
|
+
for (let i = 0; i < text.length; i += 1) { h ^= text.charCodeAt(i); h = Math.imul(h, 16777619); }
|
|
225
|
+
return `${text.length}:${(h >>> 0).toString(36)}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// WHAT HER PHONE MAY ASK HER MAC TO DO, and nothing else. A command that is not
|
|
229
|
+
// one of these, or names a project she did not put on her phone, or a thread
|
|
230
|
+
// that is not in it, is refused and says why.
|
|
231
|
+
// A `setting` belongs to the computer, not a project (shared/phone-settings.mjs).
|
|
232
|
+
export const PHONE_COMMANDS = ['reply', 'new', 'snooze', 'close', 'stop', 'run-now', 'send-now', 'reopen', 'schedule', 'approve', 'setting'];
|
|
233
|
+
|
|
234
|
+
// A PERMISSION CARD AS HER PHONE DRAWS IT: what the agent asks to do, with the
|
|
235
|
+
// secrets in it hidden as a thread's words are, and the fingerprint of the card
|
|
236
|
+
// her Mac drew, which her answer must carry back.
|
|
237
|
+
export function phoneApproval(request, card, ctx = {}) {
|
|
238
|
+
// Her Allow signs the whole request by its digest, so the phone may carry the
|
|
239
|
+
// digest only when it drew the request whole; a card that hid, clipped or
|
|
240
|
+
// dropped anything carries none and is answered on the computer. A home path
|
|
241
|
+
// written ~ is for her eyes, not a value hidden from what she signs.
|
|
242
|
+
let lossy = false;
|
|
243
|
+
const text = (s, n) => {
|
|
244
|
+
if (hideSecrets(s) !== s) lossy = true;
|
|
245
|
+
const t = hideSecrets(s, ctx);
|
|
246
|
+
if (t.length > n) { lossy = true; return `${t.slice(0, n)}…`; }
|
|
247
|
+
return t;
|
|
248
|
+
};
|
|
249
|
+
const clean = (v, depth = 0) => {
|
|
250
|
+
if (typeof v === 'string') return text(v, 4000);
|
|
251
|
+
if (typeof v === 'number' || typeof v === 'boolean' || v === null) return v;
|
|
252
|
+
if (depth > 3) { lossy = true; return undefined; }
|
|
253
|
+
if (Array.isArray(v)) { if (v.length > 40) lossy = true; return v.slice(0, 40).map((x) => clean(x, depth + 1)); }
|
|
254
|
+
if (v && typeof v === 'object') {
|
|
255
|
+
const entries = Object.entries(v);
|
|
256
|
+
if (entries.length > 40) lossy = true;
|
|
257
|
+
return Object.fromEntries(entries.slice(0, 40).map(([k, x]) => [k, clean(x, depth + 1)]));
|
|
258
|
+
}
|
|
259
|
+
return undefined;
|
|
260
|
+
};
|
|
261
|
+
const shown = { id: String(request.id), at: Number(request.at) || 0, tool: text(String(request.tool ?? 'unknown'), 200), input: clean(request.input ?? {}) };
|
|
262
|
+
return lossy ? { ...shown, card: null, answerOnComputer: true } : { ...shown, card };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// WHAT SHE PICKED WITH A REPLY OR A NEW THREAD, as the window's own handlers
|
|
266
|
+
// take it (main/ipc.mjs zero:answer, zero:compose): only these fields, each
|
|
267
|
+
// only in its right shape. Null on a reply clears a choice, as in the window.
|
|
268
|
+
const WORD = /^[A-Za-z][\w.:-]{0,79}$/;
|
|
269
|
+
const word = (v, n = 80) => typeof v === 'string' && v.length <= n && WORD.test(v);
|
|
270
|
+
const orNull = (ok) => (v) => v === null || ok(v);
|
|
271
|
+
const CHOICES = {
|
|
272
|
+
reply: {
|
|
273
|
+
permissionMode: orNull((v) => word(v, 40)), model: orNull(word), effort: orNull((v) => word(v, 20)),
|
|
274
|
+
priority: (v) => Number.isInteger(v) && v >= 0 && v <= 10, now: (v) => v === true, unschedule: (v) => v === true,
|
|
275
|
+
},
|
|
276
|
+
new: {
|
|
277
|
+
runAt: (v) => Number.isFinite(v) && v > 0, start: (v) => v === 'later' || v === 'now',
|
|
278
|
+
engine: (v) => word(v, 40), model: word, effort: (v) => word(v, 20), priority: (v) => Number.isInteger(v) && v >= 0 && v <= 10,
|
|
279
|
+
assignee: (v) => typeof v === 'string' && v.length >= 1 && v.length <= 80, due: (v) => typeof v === 'string' && v.length <= 40,
|
|
280
|
+
visibility: (v) => ['team', 'people', 'private'].includes(v),
|
|
281
|
+
visibleTo: (v) => Array.isArray(v) && v.length <= 50 && v.every((x) => typeof x === 'string' && x.length <= 80),
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
export function phoneChoices(kind, p) {
|
|
285
|
+
const out = {};
|
|
286
|
+
for (const [k, ok] of Object.entries(CHOICES[kind] ?? {})) if (p && k in p && p[k] !== undefined && ok(p[k])) out[k] = p[k];
|
|
287
|
+
return out;
|
|
288
|
+
}
|
|
289
|
+
export function checkCommand(cmd, { projects, hasItem }) {
|
|
290
|
+
if (!cmd || !PHONE_COMMANDS.includes(cmd.kind)) return 'not something the phone may do';
|
|
291
|
+
if (cmd.kind === 'setting') return cmd.project != null || cmd.item != null ? 'a setting names no project or thread' : checkSetting(cmd.payload);
|
|
292
|
+
if (!projects.includes(cmd.project)) return 'that project is not on your phone';
|
|
293
|
+
const p = cmd.payload || {};
|
|
294
|
+
if (cmd.kind === 'new') {
|
|
295
|
+
const title = typeof p.title === 'string' ? p.title.trim() : '';
|
|
296
|
+
const body = typeof p.body === 'string' ? p.body.trim() : '';
|
|
297
|
+
if (!title && !body) return 'a new thread needs some words';
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
if (!cmd.item || !hasItem(cmd.project, cmd.item)) return 'that thread is not on your phone';
|
|
301
|
+
if (cmd.kind === 'reply' && !(typeof p.text === 'string' && p.text.trim())) return 'an empty reply';
|
|
302
|
+
if (cmd.kind === 'snooze' && !(Number.isFinite(p.until) && p.until >= 0)) return 'a snooze needs a time';
|
|
303
|
+
if (cmd.kind === 'schedule' && !(Number.isFinite(p.runAt) && p.runAt >= 0)) return 'a schedule needs a time';
|
|
304
|
+
if (cmd.kind === 'approve' && !(typeof p.approval === 'string' && /^[\w-]{1,80}$/.test(p.approval) && typeof p.allow === 'boolean'
|
|
305
|
+
&& typeof p.card === 'string' && /^[\w+/=-]{8,200}$/.test(p.card) && (p.note === undefined || (typeof p.note === 'string' && p.note.length <= 500)))) return 'an answer needs its card';
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// A TURN THE CLOUD TOOK WITH THE COMPUTER SHUT, written into the thread's copy
|
|
310
|
+
// by the cloud server (cloud/gateway/phone.mjs), as the computer would have
|
|
311
|
+
// written it: her reply, the agent's answer and the row waiting on her
|
|
312
|
+
// (main/store.mjs recordSessionResult). The computer's own copy replaces this
|
|
313
|
+
// one once it is back and has brought the run home.
|
|
314
|
+
export function withCloudTurn(snapshot, { reply, answer, repliedAt, answeredAt, status = 'blocked' }, limits = SNAPSHOT_LIMITS) {
|
|
315
|
+
if (!snapshot || typeof snapshot !== 'object' || !snapshot.item?.id) return null;
|
|
316
|
+
const ctx = { home: null };
|
|
317
|
+
const id = snapshot.item.id;
|
|
318
|
+
// No reply for a new thread's first turn: her ask is already its first line.
|
|
319
|
+
const replied = reply !== null && reply !== undefined;
|
|
320
|
+
const said = clip(String(reply ?? ''), limits.textChars, ctx);
|
|
321
|
+
const result = clip(String(answer ?? ''), limits.textChars, ctx);
|
|
322
|
+
const lines = [
|
|
323
|
+
...(replied ? [{ id, ts: repliedAt, source: 'founder', patch: { answer: said, status: 'open' } }] : []),
|
|
324
|
+
{ id, ts: answeredAt, source: 'agent', patch: { result } },
|
|
325
|
+
{ id, ts: answeredAt, source: 'system', patch: { status } },
|
|
326
|
+
];
|
|
327
|
+
const wrote = { ...(snapshot.item.wrote ?? {}), ...(replied ? { answer: { ts: repliedAt, source: 'founder' } } : {}), result: { ts: answeredAt, source: 'agent' }, status: { ts: answeredAt, source: 'system' } };
|
|
328
|
+
const item = { ...snapshot.item, ...(replied ? { answer: said } : {}), result, status, updatedAt: answeredAt, wrote };
|
|
329
|
+
let history = [...(Array.isArray(snapshot.history) ? snapshot.history : []), ...lines];
|
|
330
|
+
let older = Number(snapshot.olderLines) || 0;
|
|
331
|
+
while (history.length > limits.lines || (history.length > lines.length && bytesOf({ ...snapshot, item, history }) > limits.bytes)) {
|
|
332
|
+
history = history.slice(1);
|
|
333
|
+
older += 1;
|
|
334
|
+
}
|
|
335
|
+
return { ...snapshot, item, history, olderLines: older };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// THE PHONE'S COPY OF A THREAD THE CLOUD STARTED FROM THE PHONE, before its
|
|
339
|
+
// computer has one: the row her ask made, being worked on.
|
|
340
|
+
export function newThreadSnapshot({ itemId, title, body = '', at }, limits = SNAPSHOT_LIMITS) {
|
|
341
|
+
const words = { title: String(title ?? ''), ...(body ? { body: String(body) } : {}) };
|
|
342
|
+
const said = { ts: at, source: 'founder' };
|
|
343
|
+
return threadSnapshot(
|
|
344
|
+
{ id: itemId, ...words, kind: 'directive', labels: ['founder'], priority: 0, status: 'claimed', createdAt: at, updatedAt: at, wrote: { title: said, ...(body ? { body: said } : {}) } },
|
|
345
|
+
[{ ts: at, source: 'founder', patch: { ...words, status: 'open' } }],
|
|
346
|
+
limits,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function turnedName(name: string | null | undefined): string | null;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// PICTURES THE WINDOW CANNOT DRAW, and what each is turned into so it can.
|
|
2
|
+
// Chromium decodes no TIFF and no HEIC, so one pasted or dropped into a reply
|
|
3
|
+
// box is turned into a PNG or a JPEG as it comes in (main/stage-attachment.mjs).
|
|
4
|
+
// Both sides read this: the window to know a dropped file is worth staging,
|
|
5
|
+
// the main process to turn it.
|
|
6
|
+
const TURNED = [[/\.tiff?$/i, '.png'], [/\.hei[cf]$/i, '.jpg']];
|
|
7
|
+
|
|
8
|
+
/** The name the picture goes on under once turned, or null when it is not one of these. */
|
|
9
|
+
export function turnedName(name) {
|
|
10
|
+
const s = String(name ?? '');
|
|
11
|
+
const hit = TURNED.find(([re]) => re.test(s));
|
|
12
|
+
return hit ? s.replace(/\.[^.]+$/, hit[1]) : null;
|
|
13
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// A command is an operation, never prose asking a model to imitate a CLI.
|
|
2
2
|
import { CLAUDE_COMMANDS, commandWords } from './claude-commands.mjs';
|
|
3
|
+
import { harnessDefinition } from './harness-definitions.mjs';
|
|
3
4
|
const command = (name, description, argumentHint = null, aliases = []) => ({ name, description, menuDescription: null, whole: true, argumentHint, aliases });
|
|
4
5
|
export const CODEX_COMMANDS = [
|
|
5
6
|
command('model','Show models or change this task’s model','[model]'),
|
|
@@ -23,12 +24,18 @@ export const LOCAL_COMMANDS = CODEX_COMMANDS.filter(c=>['model','effort','diff',
|
|
|
23
24
|
// is refused on Codex for now, in a sentence that says why, because that call
|
|
24
25
|
// has never been made against a real server here.
|
|
25
26
|
const FORK = command('fork','Start a new task from this conversation','<what to try instead>');
|
|
27
|
+
const COMMAND_MENUS = {
|
|
28
|
+
codex: CODEX_COMMANDS,
|
|
29
|
+
claude: [command('remote-control','Continue this conversation in Claude on another device',null,['rc']),FORK,...CLAUDE_COMMANDS.map(c=>LOCAL_COMMANDS.find(l=>l.name===c.name)??c),...LOCAL_COMMANDS.filter(l=>!CLAUDE_COMMANDS.some(c=>c.name===l.name))],
|
|
30
|
+
local: LOCAL_COMMANDS,
|
|
31
|
+
};
|
|
26
32
|
export function providerCommands(engine) {
|
|
27
33
|
// NOT ON THE CODEX MENU, because this menu's rule is that it offers nothing
|
|
28
34
|
// that engine cannot run, and Codex forking is not connected yet. Typed there
|
|
29
35
|
// it gets the ordinary unavailable refusal, which points at the native Codex
|
|
30
36
|
// client, where `codex fork` genuinely works.
|
|
31
|
-
|
|
37
|
+
const definition = harnessDefinition(engine) ?? harnessDefinition();
|
|
38
|
+
return COMMAND_MENUS[definition.commandMenu] ?? [];
|
|
32
39
|
}
|
|
33
40
|
export function providerCommand(text, engine) {
|
|
34
41
|
if(typeof text!=='string')return null;
|
|
@@ -38,7 +45,8 @@ export function providerCommand(text, engine) {
|
|
|
38
45
|
const cmd=providerCommands(engine).find(c=>commandWords(c).includes(word));
|
|
39
46
|
if(!cmd) return {name:word,args,route:'unavailable'};
|
|
40
47
|
if(cmd.name==='fork')return {name:'fork',args,route:'fork'};
|
|
41
|
-
|
|
48
|
+
const route = (harnessDefinition(engine) ?? harnessDefinition()).commandRoute;
|
|
49
|
+
return {name:cmd.name,args,route:cmd.name==='remote-control'?'remote':route==='codex'?'codex':LOCAL_COMMANDS.some(c=>c.name===cmd.name)?'local':route};
|
|
42
50
|
}
|
|
43
51
|
export function reviewTarget(args='') {
|
|
44
52
|
args=args.trim();
|
package/shared/rank.d.mts
CHANGED
|
@@ -8,7 +8,8 @@ export function placeScore(order: string[], slug: string, direct?: ReadonlySet<s
|
|
|
8
8
|
|
|
9
9
|
export const DEFAULT_PRIORITY: number;
|
|
10
10
|
|
|
11
|
-
export function itemPriority(item: { priority?: number; wrote?: Record<string, { source?: string } | undefined> } | null | undefined): number;
|
|
11
|
+
export function itemPriority(item: { priority?: number | null; wrote?: Record<string, { source?: string } | undefined> } | null | undefined): number;
|
|
12
|
+
export function shownPriority(item: { priority?: number | null; wrote?: Record<string, { source?: string } | undefined> } | null | undefined, conversation?: boolean): number | null;
|
|
12
13
|
export const URGENT_PRIORITY: number;
|
|
13
14
|
export function isUrgent(item: { priority?: number; wrote?: Record<string, { source?: string } | undefined> } | null | undefined): boolean;
|
|
14
15
|
|
|
@@ -18,3 +19,5 @@ export function landingIndex(
|
|
|
18
19
|
x: number, y: number,
|
|
19
20
|
): number;
|
|
20
21
|
export function moveProduct(order: string[], slug: string, toIndex: number): string[];
|
|
22
|
+
export function placedScore(place: number | null | undefined, natural: number): number;
|
|
23
|
+
export function dropPlaces(rows: Array<{ id: string; score: number }>, from: number, to: number): Record<string, number> | null;
|
package/shared/rank.mjs
CHANGED
|
@@ -69,19 +69,15 @@ export function placeScore(order, slug, direct) {
|
|
|
69
69
|
/**
|
|
70
70
|
* The number an item carries into that score.
|
|
71
71
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* in store.composeItem is words-versus-lifecycle, not authority), and a repeat
|
|
82
|
-
* rule's number arrives the same way. Reading only 'founder' here would have
|
|
83
|
-
* thrown away the Urgent tag on the very row she complained about
|
|
84
|
-
* (tests/urgent-does-not-wait-behind-medium.test.mjs).
|
|
72
|
+
* A LEVEL COUNTS WHOEVER SET IT (w-5ac9c9006d, 2026-10-16). An agent's tag
|
|
73
|
+
* used to count for nothing, because agents once tagged 127 of 442 rows at 7
|
|
74
|
+
* or higher on an old brief rule. That made every level an agent wrote a lie
|
|
75
|
+
* on screen, and her answer was to stop discarding them: "If agent-determined
|
|
76
|
+
* priorities are ignored, then it seems like the whole prioritization system
|
|
77
|
+
* is ignored. If an agent sets something and I'm like, 'Oh that's the wrong
|
|
78
|
+
* priority,' I'll fix it. We should also always treat priorities as the
|
|
79
|
+
* source of truth, regardless of where they came from." Her fix is a new
|
|
80
|
+
* level, and the fold keeps hers over an agent's.
|
|
85
81
|
*
|
|
86
82
|
* Nought means untagged, not bottom. It is the seed the fold starts an item
|
|
87
83
|
* at and the default her compose sends when she picks no number, and her own
|
|
@@ -93,11 +89,29 @@ export const DEFAULT_PRIORITY = 5;
|
|
|
93
89
|
|
|
94
90
|
export function itemPriority(item) {
|
|
95
91
|
if (!item) return DEFAULT_PRIORITY;
|
|
96
|
-
if (item.wrote?.priority?.source === 'agent') return DEFAULT_PRIORITY;
|
|
97
92
|
const n = item.priority;
|
|
98
93
|
return Number.isFinite(n) && n > 0 ? n : DEFAULT_PRIORITY;
|
|
99
94
|
}
|
|
100
95
|
|
|
96
|
+
/**
|
|
97
|
+
* THE LEVEL A THREAD SHOWS IS THE LEVEL IT RUNS AT (w-5ac9c9006d, 2026-10-15).
|
|
98
|
+
*
|
|
99
|
+
* Every screen drew the raw stored number while the fleet ran `itemPriority`,
|
|
100
|
+
* so a thread an agent filed untagged read Low and ran as Medium, and an
|
|
101
|
+
* agent's own 8 read High and ran as Medium. In Progress then showed three
|
|
102
|
+
* "Low" threads running while a "High" and a "Medium" waited. An agent's level
|
|
103
|
+
* now counts (above); an untagged row reads Medium here as it runs.
|
|
104
|
+
*
|
|
105
|
+
* A CONVERSATION keeps the number it carries: its level is the message
|
|
106
|
+
* sorter's (main/message-priority.mjs), written as an agent on purpose, and a
|
|
107
|
+
* conversation never spawns, so there is no running order for it to disagree
|
|
108
|
+
* with. `conversation` is the caller's to say, since only it knows the product.
|
|
109
|
+
*/
|
|
110
|
+
export function shownPriority(item, conversation = false) {
|
|
111
|
+
if (conversation) return item?.priority ?? null;
|
|
112
|
+
return itemPriority(item);
|
|
113
|
+
}
|
|
114
|
+
|
|
101
115
|
/**
|
|
102
116
|
* The top of her own scale, and the only level that INTERRUPTS.
|
|
103
117
|
*
|
|
@@ -110,12 +124,11 @@ export function itemPriority(item) {
|
|
|
110
124
|
export const URGENT_PRIORITY = 9;
|
|
111
125
|
|
|
112
126
|
/**
|
|
113
|
-
* Is this
|
|
127
|
+
* Is this row Urgent?
|
|
114
128
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* takes a running session away from something else.
|
|
129
|
+
* Whoever set it, since 2026-10-16 (itemPriority). Everything downstream of
|
|
130
|
+
* this predicate takes a running session away from something else, so an
|
|
131
|
+
* agent's nine interrupts like hers; a wrong one is hers to lower.
|
|
119
132
|
*/
|
|
120
133
|
export function isUrgent(item) {
|
|
121
134
|
return itemPriority(item) >= URGENT_PRIORITY;
|
|
@@ -155,6 +168,56 @@ export function landingIndex(rects, x, y) {
|
|
|
155
168
|
return rects.length;
|
|
156
169
|
}
|
|
157
170
|
|
|
171
|
+
/**
|
|
172
|
+
* A THREAD'S OWN PLACE, SET BY DRAGGING IT IN THE LIST (w-6e5b532a95,
|
|
173
|
+
* 2026-10-07). Her words: "sometimes there are tasks that are higher priority
|
|
174
|
+
* than other projects and it's hard to articulate. I'd rather be able to drag
|
|
175
|
+
* things around here ... and have that priority remembered and that's how the
|
|
176
|
+
* app processes it." A project's place and a thread's level could never say
|
|
177
|
+
* that one Low thread matters more today than a whole project above it.
|
|
178
|
+
*
|
|
179
|
+
* So a dragged thread carries a PLACE: a number on the same scale as the score
|
|
180
|
+
* it would otherwise have, which replaces that score outright. It is a number
|
|
181
|
+
* and not a list of ids so that everything it was not dragged past keeps
|
|
182
|
+
* sorting the way it always did: a new Urgent thread still lands by its level,
|
|
183
|
+
* next to the placed ones rather than under all of them.
|
|
184
|
+
*/
|
|
185
|
+
export function placedScore(place, natural) {
|
|
186
|
+
return Number.isFinite(place) ? place : natural;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* The places a drop writes. `rows` is the list as drawn, top first, each with
|
|
191
|
+
* the score it sorts by (its place, or its project and level); `from` is the
|
|
192
|
+
* dragged row and `to` the row it was let go in front of, or the length for
|
|
193
|
+
* the end, both counted in `rows`. Null when it lands where it was.
|
|
194
|
+
*
|
|
195
|
+
* The dragged row goes between its new neighbours. When those two TIE (two
|
|
196
|
+
* Medium threads in one project, whose order is only which changed last) no
|
|
197
|
+
* number fits between them, so the tied rows above the drop are given places
|
|
198
|
+
* of their own first, spread in the order they were drawn. The rows below are
|
|
199
|
+
* never written.
|
|
200
|
+
*/
|
|
201
|
+
export function dropPlaces(rows, from, to) {
|
|
202
|
+
if (!Array.isArray(rows) || from < 0 || from >= rows.length) return null;
|
|
203
|
+
const rest = rows.filter((_, i) => i !== from);
|
|
204
|
+
const at = Math.max(0, Math.min(rest.length, to > from ? to - 1 : to));
|
|
205
|
+
if (at === from || !rest.length) return null;
|
|
206
|
+
const close = (a, b) => Math.abs(a - b) < 1e-6;
|
|
207
|
+
const below = rest[at]?.score;
|
|
208
|
+
const floor = below ?? rest[at - 1].score - 2;
|
|
209
|
+
// The run of rows just above the drop that tie with the floor.
|
|
210
|
+
let top = at;
|
|
211
|
+
while (top > 0 && close(rest[top - 1].score, floor)) top -= 1;
|
|
212
|
+
const ceiling = top > 0 ? rest[top - 1].score : floor + 2;
|
|
213
|
+
const run = rest.slice(top, at);
|
|
214
|
+
const step = (ceiling - floor) / (run.length + 2);
|
|
215
|
+
const out = {};
|
|
216
|
+
run.forEach((r, j) => { out[r.id] = floor + (run.length + 1 - j) * step; });
|
|
217
|
+
out[rows[from].id] = floor + step;
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
|
|
158
221
|
/**
|
|
159
222
|
* Move one product to a new index, returning a fresh order. Products that were
|
|
160
223
|
* never in the order join it on their first move, so dragging an unranked chip
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// THE RELAY EVERY COPY OF THE APP USES (relay/worker.mjs), deployed to the
|
|
2
|
+
// founder's Cloudflare account on 2026-10-12. With an address here every
|
|
3
|
+
// computer offers "Anywhere" in Settings, then Phone, and starts new people on
|
|
4
|
+
// it; AGENTBOX_RELAY_URL or relay/relay.local.json still take precedence
|
|
5
|
+
// (main/relay-address.mjs). Redeploy with `cd relay && npx wrangler deploy`.
|
|
6
|
+
export const RELAY_URL = 'https://agentbox-relay.agentbox-app.workers.dev';
|