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
package/main/engine-setup.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { harnessFor } from './harnesses.mjs';
|
|
1
2
|
// SETTING UP SOMEBODY'S PLAN FOR THEM (w-9f6975906c, picked 2026-10-05).
|
|
2
3
|
//
|
|
3
4
|
// A person with a Claude or ChatGPT plan and no Claude Code or Codex used to
|
|
@@ -25,28 +26,16 @@ import { spawn as nodeSpawn } from 'node:child_process';
|
|
|
25
26
|
import { terminalEnv } from './task-terminals.mjs';
|
|
26
27
|
import { codexLaunchEnv } from './codex-launch-env.mjs';
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
claude: { shell: '/bin/bash', url: 'https://claude.ai/install.sh', pipe: 'bash' },
|
|
30
|
-
codex: { shell: '/bin/sh', url: 'https://chatgpt.com/codex/install.sh', pipe: 'sh' },
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export function installCommand(engine) {
|
|
34
|
-
const i = INSTALLERS[engine];
|
|
35
|
-
return { file: i.shell, args: ['-c', `curl -fsSL ${i.url} | ${i.pipe}`] };
|
|
36
|
-
}
|
|
29
|
+
export function installCommand(engine) { return harnessFor(engine).install(); }
|
|
37
30
|
|
|
38
31
|
/** The plan, never the API console: `--claudeai` is Claude Code's own name for it. */
|
|
39
32
|
export function signInCommand(engine, bin) {
|
|
40
|
-
return engine
|
|
41
|
-
? { file: bin, args: ['auth', 'login', '--claudeai'] }
|
|
42
|
-
: { file: bin, args: ['login'] };
|
|
33
|
+
return harnessFor(engine).signIn(bin);
|
|
43
34
|
}
|
|
44
35
|
|
|
45
36
|
/** Exit 0 is signed in and 1 is not, for both (measured 2026-10-05). */
|
|
46
37
|
export function statusCommand(engine, bin) {
|
|
47
|
-
return engine
|
|
48
|
-
? { file: bin, args: ['auth', 'status', '--json'] }
|
|
49
|
-
: { file: bin, args: ['login', 'status'] };
|
|
38
|
+
return harnessFor(engine).signInStatus(bin);
|
|
50
39
|
}
|
|
51
40
|
|
|
52
41
|
const LOG_LIMIT = 64 * 1024;
|
|
@@ -77,10 +66,10 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
77
66
|
/**
|
|
78
67
|
* One setup per tool, held for the life of the app.
|
|
79
68
|
*
|
|
80
|
-
* `find(engine)` is the finder, looking again from scratch
|
|
81
|
-
*
|
|
82
|
-
* the app runs on what was just installed). `spawn` and
|
|
83
|
-
* the tests; nothing in the app passes them.
|
|
69
|
+
* `find(engine)` is the finder, looking again from scratch, and may answer
|
|
70
|
+
* with a promise (the caller passes `lookAgainFor`, which also moves the
|
|
71
|
+
* config so the rest of the app runs on what was just installed). `spawn` and
|
|
72
|
+
* `wait` are seams for the tests; nothing in the app passes them.
|
|
84
73
|
*/
|
|
85
74
|
export function createEngineSetup({ find, spawn = realSpawn, wait = sleep, env = process.env, poll = POLL, onSignIn = () => {} } = {}) {
|
|
86
75
|
const runEnv = terminalEnv(env);
|
|
@@ -120,7 +109,11 @@ export function createEngineSetup({ find, spawn = realSpawn, wait = sleep, env =
|
|
|
120
109
|
|
|
121
110
|
async function go(j, run) {
|
|
122
111
|
const live = () => j.run === run;
|
|
123
|
-
let hit = find(j.engine);
|
|
112
|
+
let hit = await find(j.engine);
|
|
113
|
+
if (!live()) return;
|
|
114
|
+
// The card's first row says Found or Install off this, because it ticks
|
|
115
|
+
// that row either way.
|
|
116
|
+
j.already = !!hit?.found;
|
|
124
117
|
if (!hit?.found) {
|
|
125
118
|
j.phase = 'installing';
|
|
126
119
|
const { file, args } = installCommand(j.engine);
|
|
@@ -130,7 +123,7 @@ export function createEngineSetup({ find, spawn = realSpawn, wait = sleep, env =
|
|
|
130
123
|
j.running = null;
|
|
131
124
|
if (!live()) return;
|
|
132
125
|
if (code !== 0) return fail(j, `The installer stopped: ${lastLine(j.log) || `exit ${code}`}`);
|
|
133
|
-
hit = find(j.engine);
|
|
126
|
+
hit = await find(j.engine);
|
|
134
127
|
if (!hit?.found) return fail(j, 'The installer finished, but the app still cannot find it.');
|
|
135
128
|
}
|
|
136
129
|
j.bin = hit.path;
|
|
@@ -151,7 +144,15 @@ export function createEngineSetup({ find, spawn = realSpawn, wait = sleep, env =
|
|
|
151
144
|
ended = false;
|
|
152
145
|
watchLogin(login);
|
|
153
146
|
}
|
|
154
|
-
|
|
147
|
+
// ASK ONCE MORE BEFORE CALLING IT CLOSED. A sign-in that finishes while
|
|
148
|
+
// the check above is running exits before that check answers, and the
|
|
149
|
+
// check, asked a moment too early, says no. Without this, somebody who
|
|
150
|
+
// had just approved it was told it was closed.
|
|
151
|
+
if (ended) {
|
|
152
|
+
if (await signedIn(j, hit.path)) { if (live()) ready(j); return; }
|
|
153
|
+
if (!live()) return;
|
|
154
|
+
return fail(j, `The sign-in was closed before it finished.${lastLine(j.log) ? ` ${lastLine(j.log)}` : ''}`);
|
|
155
|
+
}
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
|
|
@@ -183,14 +184,14 @@ export function createEngineSetup({ find, spawn = realSpawn, wait = sleep, env =
|
|
|
183
184
|
},
|
|
184
185
|
status(engine) {
|
|
185
186
|
const j = job(engine);
|
|
186
|
-
return { engine, phase: j.phase, error: j.error, log: j.log };
|
|
187
|
+
return { engine, phase: j.phase, error: j.error, log: j.log, bin: j.bin ?? null, already: !!j.already };
|
|
187
188
|
},
|
|
188
189
|
/**
|
|
189
190
|
* WHETHER THIS TOOL CAN RUN AN AGENT RIGHT NOW: found, and signed in by its
|
|
190
191
|
* own account. The walk asks before it asks anything, so a Mac that is
|
|
191
192
|
* already set up never sees the plan question. */
|
|
192
193
|
async readiness(engine) {
|
|
193
|
-
const hit = find(engine);
|
|
194
|
+
const hit = await find(engine);
|
|
194
195
|
if (!hit?.found) return { engine, found: false, signedIn: false };
|
|
195
196
|
try { return { engine, found: true, signedIn: await signedIn({ engine, log: '' }, hit.path) }; }
|
|
196
197
|
catch (error) { return { engine, found: true, signedIn: false, error: error.message }; }
|
package/main/feedback.mjs
CHANGED
|
@@ -5,22 +5,28 @@
|
|
|
5
5
|
// to as a secret (`FEEDBACK_TO`, cloud/supabase/functions/feedback), so the
|
|
6
6
|
// address is in no file of this public repository and no install can show it.
|
|
7
7
|
//
|
|
8
|
-
// WHERE IT GOES
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
8
|
+
// WHERE IT GOES SHIPS IN THE CODE (w-58e758c81e, 2026-10-09). It used to be
|
|
9
|
+
// baked only into the packaged Mac app, so the npm package and a git clone,
|
|
10
|
+
// the two installs the README offers, told everyone who pressed Send that
|
|
11
|
+
// feedback was not set up, and lost what they wrote. FEEDBACK_ADDRESS is the
|
|
12
|
+
// server function, not an inbox. A `feedbackUrl` in zero.config.json, the
|
|
13
|
+
// FEEDBACK_URL variable, or a `bakedFeedbackUrl` in a packaged package.json
|
|
14
|
+
// still outrank it.
|
|
12
15
|
//
|
|
13
16
|
// It is not behind the diagnostics switch. That switch is about what Agentbox
|
|
14
17
|
// sends on its own; this is something a person typed and pressed Send on.
|
|
15
18
|
import { createRequire } from 'node:module';
|
|
16
|
-
import {
|
|
17
|
-
|
|
19
|
+
import { readEnv } from '../shared/product-name.mjs';
|
|
20
|
+
|
|
21
|
+
export { sendFeedback } from '../shared/feedback.mjs';
|
|
22
|
+
|
|
23
|
+
export const FEEDBACK_ADDRESS = 'https://lywvthwkhkfzhqivvond.supabase.co/functions/v1/feedback';
|
|
18
24
|
|
|
19
25
|
function bakedUrl() {
|
|
20
26
|
try {
|
|
21
|
-
return createRequire(import.meta.url)('../package.json')?.bakedFeedbackUrl ??
|
|
27
|
+
return createRequire(import.meta.url)('../package.json')?.bakedFeedbackUrl ?? FEEDBACK_ADDRESS;
|
|
22
28
|
} catch {
|
|
23
|
-
return
|
|
29
|
+
return FEEDBACK_ADDRESS;
|
|
24
30
|
}
|
|
25
31
|
}
|
|
26
32
|
|
|
@@ -30,26 +36,3 @@ const clean = (v) => (typeof v === 'string' && v.trim() ? v.trim() : null);
|
|
|
30
36
|
export function feedbackUrl(config, baked = BAKED, env = process.env) {
|
|
31
37
|
return clean(config?.feedbackUrl) ?? clean(readEnv('FEEDBACK_URL', env)) ?? clean(baked);
|
|
32
38
|
}
|
|
33
|
-
|
|
34
|
-
// Answers { ok: true } or { ok: false, error } with the error as a sentence.
|
|
35
|
-
// It never throws, so the card always has something to say.
|
|
36
|
-
export async function sendFeedback({ url, payload, fetchImpl = globalThis.fetch, timeoutMs = 60_000 }) {
|
|
37
|
-
if (!url) return { ok: false, error: `Feedback is not set up in this copy of ${Name}.` };
|
|
38
|
-
// Checked again here, off the bytes that would actually travel, since the
|
|
39
|
-
// renderer is not the last word on what leaves this machine.
|
|
40
|
-
const sized = (payload?.files ?? []).map((f) => ({ size: Math.floor((String(f?.data ?? '').length * 3) / 4) }));
|
|
41
|
-
const check = checkFeedback({ text: payload?.text, files: sized });
|
|
42
|
-
if (!check.ok) return { ok: false, error: check.reason };
|
|
43
|
-
try {
|
|
44
|
-
const res = await fetchImpl(url, {
|
|
45
|
-
method: 'POST',
|
|
46
|
-
headers: { 'content-type': 'application/json' },
|
|
47
|
-
body: JSON.stringify(payload),
|
|
48
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
49
|
-
});
|
|
50
|
-
if (res?.ok) return { ok: true };
|
|
51
|
-
return { ok: false, error: 'It did not send. Try again in a moment.' };
|
|
52
|
-
} catch {
|
|
53
|
-
return { ok: false, error: 'It did not send. Check your connection and try again.' };
|
|
54
|
-
}
|
|
55
|
-
}
|
package/main/folder-pictures.mjs
CHANGED
|
@@ -17,10 +17,10 @@ import { servable } from './img-scheme.mjs';
|
|
|
17
17
|
|
|
18
18
|
const PICTURE = /\.(png|jpe?g|gif|webp|avif)$/i;
|
|
19
19
|
|
|
20
|
-
export function folderPictures({ src, product, products = [], accountRoot, storeRoot, limit = 60, fs = fsDefault }) {
|
|
20
|
+
export async function folderPictures({ src, product, products = [], accountRoot, storeRoot, limit = 60, fs = fsDefault }) {
|
|
21
21
|
if (typeof src !== 'string' || !src.trim()) return { ok: false, error: 'No folder was named.' };
|
|
22
22
|
const named = src.replace(/\/+$/, '');
|
|
23
|
-
const found = resolveArtifact({ src: named, product, products, accountRoot, storeRoot, fs });
|
|
23
|
+
const found = await resolveArtifact({ src: named, product, products, accountRoot, storeRoot, fs });
|
|
24
24
|
if (!found.ok) return { ok: false, error: found.error };
|
|
25
25
|
const own = products.find((p) => p.slug === product) ?? null;
|
|
26
26
|
const roots = [accountRoot, own?.dir, own?.repoPath].filter(Boolean);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// THE OTHER END OF main/freeze-recorder.mjs: the watcher, on its own thread.
|
|
2
|
+
//
|
|
3
|
+
// The profile is rotated every `rotateMs` so it never grows, and at the end of
|
|
4
|
+
// each freeze so the freeze is in the one it hands back. The one before is
|
|
5
|
+
// kept too, because a freeze can start before a rotation and end after it.
|
|
6
|
+
import { Session } from 'node:inspector';
|
|
7
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
8
|
+
import { freezeFrom, writeFreeze } from './freeze-recorder.mjs';
|
|
9
|
+
|
|
10
|
+
const { beat, dir, appDir, thresholdMs, rotateMs, intervalUs } = workerData;
|
|
11
|
+
const stamp = new BigInt64Array(beat);
|
|
12
|
+
const nowUs = () => Number(process.hrtime.bigint() / 1000n);
|
|
13
|
+
// The main thread asleep rather than stuck (App Nap timers, say) has an idle
|
|
14
|
+
// profile. Below this share of busy samples it is not a freeze.
|
|
15
|
+
const BUSY_ENOUGH = 0.5;
|
|
16
|
+
|
|
17
|
+
const session = new Session();
|
|
18
|
+
const post = (method, params = {}) => new Promise((resolve, reject) => {
|
|
19
|
+
session.post(method, params, (err, out) => (err ? reject(err) : resolve(out)));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
let running = null; // { anchorUs } of the profile being taken
|
|
23
|
+
let previous = null; // { profile, offsetUs } of the last one stopped
|
|
24
|
+
let frozeAt = null; // the last stamp before the freeze in progress, in µs
|
|
25
|
+
const seen = []; // freezes ended and not yet cut out of a profile
|
|
26
|
+
let busy = false;
|
|
27
|
+
|
|
28
|
+
async function start() {
|
|
29
|
+
const before = nowUs();
|
|
30
|
+
await post('Profiler.start');
|
|
31
|
+
running = { anchorUs: (before + nowUs()) / 2 };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function rotate() {
|
|
35
|
+
const { profile } = await post('Profiler.stop');
|
|
36
|
+
const stopped = { profile, offsetUs: profile.startTime - running.anchorUs };
|
|
37
|
+
await start();
|
|
38
|
+
const profiles = [previous, stopped].filter(Boolean);
|
|
39
|
+
previous = stopped;
|
|
40
|
+
for (const f of seen.splice(0)) {
|
|
41
|
+
const read = freezeFrom(profiles, { fromUs: f.fromUs, toUs: f.toUs, appDir });
|
|
42
|
+
if (read.busy < BUSY_ENOUGH) continue;
|
|
43
|
+
const line = { at: new Date(Date.now() - (nowUs() - f.fromUs) / 1000).toISOString(), ms: Math.round((f.toUs - f.fromUs) / 1000), ...read };
|
|
44
|
+
writeFreeze(dir, line);
|
|
45
|
+
parentPort.postMessage({ freeze: { at: line.at, ms: line.ms, where: line.where } });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A freeze that ends while a rotation is under way goes round again.
|
|
50
|
+
async function turn() {
|
|
51
|
+
if (busy || !running) return;
|
|
52
|
+
busy = true;
|
|
53
|
+
try {
|
|
54
|
+
do { await rotate(); } while (seen.length);
|
|
55
|
+
} catch (err) { quit(err); } finally { busy = false; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function watch() {
|
|
59
|
+
const last = Number(Atomics.load(stamp, 0) / 1000n);
|
|
60
|
+
if (nowUs() - last > thresholdMs * 1000) { frozeAt ??= last; return; }
|
|
61
|
+
if (frozeAt === null) return;
|
|
62
|
+
seen.push({ fromUs: frozeAt, toUs: last });
|
|
63
|
+
frozeAt = null;
|
|
64
|
+
void turn();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let watching = null;
|
|
68
|
+
let rotating = null;
|
|
69
|
+
function quit(err) {
|
|
70
|
+
clearInterval(watching);
|
|
71
|
+
clearInterval(rotating);
|
|
72
|
+
try { session.disconnect(); } catch { /* already gone */ }
|
|
73
|
+
parentPort.postMessage({ gaveUp: String(err?.message ?? err) });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The timers go first: an inspector reply does not keep this thread alive.
|
|
77
|
+
watching = setInterval(watch, 50);
|
|
78
|
+
rotating = setInterval(() => { if (frozeAt === null) void turn(); }, rotateMs);
|
|
79
|
+
try {
|
|
80
|
+
session.connectToMainThread();
|
|
81
|
+
await post('Profiler.enable');
|
|
82
|
+
await post('Profiler.setSamplingInterval', { interval: intervalUs });
|
|
83
|
+
await start();
|
|
84
|
+
} catch (err) {
|
|
85
|
+
quit(err);
|
|
86
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// WHAT THE MAIN THREAD WAS DOING WHEN THE APP FROZE.
|
|
2
|
+
//
|
|
3
|
+
// The spinning wheel is this thread not answering, and it comes when nobody is
|
|
4
|
+
// profiling. So a thread of its own watches all the time: the main thread
|
|
5
|
+
// stamps a clock every 100 ms, the
|
|
6
|
+
// watcher keeps a low-rate CPU profile of it running through the inspector,
|
|
7
|
+
// and when the stamps stop for longer than `thresholdMs` it cuts that stretch
|
|
8
|
+
// out of the profile and writes one line saying which of our functions held
|
|
9
|
+
// it, to `freeze-<day>.jsonl` beside the memory log, kept a week.
|
|
10
|
+
//
|
|
11
|
+
// It stays on this computer. Nothing here is sent anywhere.
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { Worker } from 'node:worker_threads';
|
|
16
|
+
|
|
17
|
+
export const THRESHOLD_MS = 500;
|
|
18
|
+
export const KEEP_DAYS = 7;
|
|
19
|
+
const BEAT_MS = 100;
|
|
20
|
+
const FILE = /^freeze-(\d{4}-\d{2}-\d{2})\.jsonl$/;
|
|
21
|
+
const IDLE = new Set(['(idle)', '(program)']);
|
|
22
|
+
const GC = '(garbage collector)';
|
|
23
|
+
const FRAMES_KEPT = 12;
|
|
24
|
+
|
|
25
|
+
export const dayOf = (ms) => {
|
|
26
|
+
const d = new Date(ms);
|
|
27
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function fileOf(url, appDirs) {
|
|
31
|
+
if (!url) return '';
|
|
32
|
+
let file = url;
|
|
33
|
+
if (url.startsWith('file://')) { try { file = fileURLToPath(url); } catch { /* keep the url */ } }
|
|
34
|
+
const appDir = appDirs.find((d) => file.startsWith(d + path.sep));
|
|
35
|
+
if (appDir) return file.slice(appDir.length + 1);
|
|
36
|
+
if (file.startsWith('node:') || !path.isAbsolute(file)) return file;
|
|
37
|
+
// Never a home path in the log: a file outside the app is named by its last parts.
|
|
38
|
+
return `…/${file.split(path.sep).slice(-2).join('/')}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The profile names a file by its real path, which on macOS is /private/var for /var.
|
|
42
|
+
const realPath = (dir) => { try { return dir ? fs.realpathSync(dir) : ''; } catch { return ''; } };
|
|
43
|
+
|
|
44
|
+
const ours = (frame) => /\.(m?js|cjs|ts)$/.test(frame.file) && !/^(node:|…\/)/.test(frame.file) && !frame.file.includes('node_modules');
|
|
45
|
+
const named = (f) => (f.file ? `${f.fn} ${f.file}:${f.line}` : f.fn);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* What held the thread between `fromUs` and `toUs`, read off one or more CPU
|
|
49
|
+
* profiles. Times are on the watcher's clock; `offsetUs` is each profile's
|
|
50
|
+
* clock minus that one. `appDir` is one path or several. Pure.
|
|
51
|
+
*/
|
|
52
|
+
export function freezeFrom(profiles, { fromUs, toUs, appDir = '' }) {
|
|
53
|
+
const appDirs = [appDir].flat().filter(Boolean);
|
|
54
|
+
const groups = new Map();
|
|
55
|
+
let total = 0;
|
|
56
|
+
let idle = 0;
|
|
57
|
+
let gc = 0;
|
|
58
|
+
for (const { profile, offsetUs = 0 } of profiles) {
|
|
59
|
+
if (!profile?.nodes?.length || !profile.samples?.length) continue;
|
|
60
|
+
const byId = new Map(profile.nodes.map((n) => [n.id, n]));
|
|
61
|
+
const parent = new Map();
|
|
62
|
+
for (const n of profile.nodes) for (const c of n.children ?? []) parent.set(c, n.id);
|
|
63
|
+
const stacks = new Map();
|
|
64
|
+
const stackOf = (id) => {
|
|
65
|
+
if (stacks.has(id)) return stacks.get(id);
|
|
66
|
+
const frames = [];
|
|
67
|
+
for (let at = id; at != null; at = parent.get(at)) {
|
|
68
|
+
const cf = byId.get(at)?.callFrame;
|
|
69
|
+
if (!cf || cf.functionName === '(root)') continue;
|
|
70
|
+
frames.push({ fn: cf.functionName || '(anonymous)', file: fileOf(cf.url, appDirs), line: cf.url ? cf.lineNumber + 1 : 0 });
|
|
71
|
+
}
|
|
72
|
+
const leaf = frames[0]?.fn ?? '(program)';
|
|
73
|
+
const kind = frames.length <= 1 && IDLE.has(leaf) ? 'idle' : leaf === GC ? 'gc' : 'busy';
|
|
74
|
+
const first = frames.findIndex(ours);
|
|
75
|
+
const mine = first < 0 ? [] : frames.slice(first);
|
|
76
|
+
const s = { kind, frames, where: mine[0] ? named(mine[0]) : leaf, key: `${leaf}|${mine.slice(0, 10).map(named).join('<')}` };
|
|
77
|
+
stacks.set(id, s);
|
|
78
|
+
return s;
|
|
79
|
+
};
|
|
80
|
+
let t = profile.startTime - offsetUs;
|
|
81
|
+
for (let i = 0; i < profile.samples.length; i += 1) {
|
|
82
|
+
t += profile.timeDeltas?.[i] ?? 0;
|
|
83
|
+
if (t < fromUs || t > toUs) continue;
|
|
84
|
+
total += 1;
|
|
85
|
+
const s = stackOf(profile.samples[i]);
|
|
86
|
+
if (s.kind === 'idle') { idle += 1; continue; }
|
|
87
|
+
if (s.kind === 'gc') gc += 1;
|
|
88
|
+
const g = groups.get(s.key) ?? { samples: 0, s };
|
|
89
|
+
g.samples += 1;
|
|
90
|
+
groups.set(s.key, g);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const busy = total - idle;
|
|
94
|
+
const top = [...groups.values()].sort((a, b) => b.samples - a.samples).slice(0, 3);
|
|
95
|
+
return {
|
|
96
|
+
samples: total,
|
|
97
|
+
busy: total ? Math.round((busy / total) * 100) / 100 : 0,
|
|
98
|
+
gc: busy ? Math.round((gc / busy) * 100) / 100 : 0,
|
|
99
|
+
where: top[0]?.s.where ?? null,
|
|
100
|
+
stacks: top.map((g) => ({ share: Math.round((g.samples / busy) * 100) / 100, frames: g.s.frames.slice(0, FRAMES_KEPT).map(named) })),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** One freeze's line, appended to today's file; older files than a week go. */
|
|
105
|
+
export function writeFreeze(dir, line, { keepDays = KEEP_DAYS } = {}) {
|
|
106
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
107
|
+
const at = Date.parse(line.at) || Date.now();
|
|
108
|
+
fs.appendFileSync(path.join(dir, `freeze-${dayOf(at)}.jsonl`), `${JSON.stringify(line)}\n`);
|
|
109
|
+
const oldest = dayOf(new Date(at).setDate(new Date(at).getDate() - (keepDays - 1)));
|
|
110
|
+
for (const f of fs.readdirSync(dir)) {
|
|
111
|
+
const m = f.match(FILE);
|
|
112
|
+
if (m && m[1] < oldest) fs.rmSync(path.join(dir, f), { force: true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Starts the watcher for this thread. `onFreeze` hears each line as it is
|
|
118
|
+
* written. Never throws: a recorder that breaks the app is worse than a gap.
|
|
119
|
+
*/
|
|
120
|
+
export function startFreezeRecorder({ dir, appDir = '', thresholdMs = THRESHOLD_MS, rotateMs = 60_000, intervalUs = 10_000, onFreeze = () => {} } = {}) {
|
|
121
|
+
const beat = new SharedArrayBuffer(8);
|
|
122
|
+
const stamp = new BigInt64Array(beat);
|
|
123
|
+
const tick = () => Atomics.store(stamp, 0, process.hrtime.bigint());
|
|
124
|
+
tick();
|
|
125
|
+
const timer = setInterval(tick, BEAT_MS);
|
|
126
|
+
timer.unref?.();
|
|
127
|
+
let worker = null;
|
|
128
|
+
try {
|
|
129
|
+
worker = new Worker(new URL('./freeze-recorder-thread.mjs', import.meta.url), {
|
|
130
|
+
workerData: { beat, dir, appDir: [appDir, realPath(appDir)], thresholdMs, rotateMs, intervalUs },
|
|
131
|
+
});
|
|
132
|
+
} catch (err) {
|
|
133
|
+
clearInterval(timer);
|
|
134
|
+
console.warn(`zero: freezes will not be recorded: ${err.message}`);
|
|
135
|
+
return { stop() {} };
|
|
136
|
+
}
|
|
137
|
+
worker.unref();
|
|
138
|
+
worker.on('message', (m) => {
|
|
139
|
+
if (m?.freeze) { try { onFreeze(m.freeze); } catch { /* the line is already on disk */ } }
|
|
140
|
+
if (m?.gaveUp) console.warn(`zero: freezes will not be recorded: ${m.gaveUp}`);
|
|
141
|
+
});
|
|
142
|
+
worker.on('error', (err) => { clearInterval(timer); console.warn(`zero: the freeze recorder stopped: ${err.message}`); });
|
|
143
|
+
return {
|
|
144
|
+
stop() { clearInterval(timer); worker?.terminate().catch(() => {}); },
|
|
145
|
+
};
|
|
146
|
+
}
|
package/main/fresh-user.mjs
CHANGED
|
@@ -44,6 +44,22 @@ export function relaunchCommand({ execPath = process.execPath, argv = process.ar
|
|
|
44
44
|
return { bin: execPath, args: appPath ? [appPath] : [] };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* THE NOTES ARE THE HONEST PART. Each is something a fresh copy can be missing
|
|
49
|
+
* without looking broken, and a copy that quietly cannot run an agent is one
|
|
50
|
+
* that lies, so they are said rather than assumed.
|
|
51
|
+
*
|
|
52
|
+
* Pure, and reads nothing, because what it says about a missing Claude Code
|
|
53
|
+
* otherwise depends on whether the computer running the test has one.
|
|
54
|
+
*/
|
|
55
|
+
export function freshUserNotes({ claudeBin, keychain, agents } = {}, { withTools = true, withAgents = true } = {}) {
|
|
56
|
+
const notes = [];
|
|
57
|
+
if (withTools && !claudeBin) notes.push('That copy has no Claude Code of its own, so its setup offers to set one up or to use ChatGPT.');
|
|
58
|
+
if (!keychain) notes.push('There is no ~/Library/Keychains here, so Claude Code will say it is not logged in.');
|
|
59
|
+
if (withAgents && !agents) notes.push('There is no ~/.claude on this computer, so it will have no agents to offer.');
|
|
60
|
+
return notes;
|
|
61
|
+
}
|
|
62
|
+
|
|
47
63
|
/**
|
|
48
64
|
* Open one. Returns what happened, in the words the app will show her: this
|
|
49
65
|
* is reported to the window rather than thrown, because a row in ⌘K that
|
|
@@ -90,15 +106,10 @@ export function openFreshUser({
|
|
|
90
106
|
return { ok: false, error: `Could not start a second ${NAME}: ${err?.message ?? err}`, home: prepared.home };
|
|
91
107
|
}
|
|
92
108
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (withTools && !prepared.claudeBin) notes.push('Claude Code is not at ~/.local/bin/claude, so agents will not run in it.');
|
|
98
|
-
if (!prepared.keychain) notes.push('There is no ~/Library/Keychains here, so Claude Code will say it is not logged in.');
|
|
99
|
-
if (withAgents && !prepared.agents) notes.push('There is no ~/.claude on this computer, so it will have no agents to offer.');
|
|
100
|
-
|
|
101
|
-
return { ok: true, home: prepared.home, agents: prepared.agents, notes, pid: child?.pid ?? null };
|
|
109
|
+
return {
|
|
110
|
+
ok: true, home: prepared.home, agents: prepared.agents, pid: child?.pid ?? null,
|
|
111
|
+
notes: freshUserNotes(prepared, { withTools, withAgents }),
|
|
112
|
+
};
|
|
102
113
|
}
|
|
103
114
|
|
|
104
115
|
/**
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
// runs whole inside a worker thread, and if the thread cannot start the job
|
|
10
10
|
// runs here, the old way.
|
|
11
11
|
import { Worker } from 'node:worker_threads';
|
|
12
|
-
import * as
|
|
12
|
+
import * as git from './git-change.mjs';
|
|
13
|
+
import { saveRunChange } from './run-change.mjs';
|
|
13
14
|
|
|
14
|
-
const
|
|
15
|
+
const change = { ...git, saveRunChange };
|
|
16
|
+
const JOBS = new Set(['snapshotRepo', 'saveRunChange']);
|
|
15
17
|
|
|
16
18
|
let worker = null;
|
|
17
19
|
let broken = false;
|
|
@@ -55,6 +57,11 @@ function thread() {
|
|
|
55
57
|
return worker;
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
/** Settles once no job is waiting on the thread. */
|
|
61
|
+
export async function __gitJobsIdle() {
|
|
62
|
+
while (waiting.size) await new Promise((resolve) => { setTimeout(resolve, 10); });
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
/** Run one of git-change.mjs's exports without blocking this thread. */
|
|
59
66
|
export function gitJob(name, ...args) {
|
|
60
67
|
if (!JOBS.has(name)) return Promise.reject(new Error(`not a git job: ${name}`));
|
|
@@ -65,6 +72,13 @@ export function gitJob(name, ...args) {
|
|
|
65
72
|
const id = ++seq;
|
|
66
73
|
waiting.set(id, job);
|
|
67
74
|
w.ref();
|
|
68
|
-
|
|
75
|
+
try {
|
|
76
|
+
w.postMessage({ id, name, args });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
// Arguments a thread cannot be handed (a function, say) run here.
|
|
79
|
+
waiting.delete(id);
|
|
80
|
+
if (!waiting.size) w.unref();
|
|
81
|
+
runHere(job);
|
|
82
|
+
}
|
|
69
83
|
});
|
|
70
84
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// THE OTHER END OF main/git-change-offthread.mjs. One job at a time, in order.
|
|
2
2
|
import { parentPort } from 'node:worker_threads';
|
|
3
|
-
import * as
|
|
3
|
+
import * as git from './git-change.mjs';
|
|
4
|
+
import { saveRunChange } from './run-change.mjs';
|
|
5
|
+
|
|
6
|
+
const jobs = { ...git, saveRunChange };
|
|
4
7
|
|
|
5
8
|
parentPort.on('message', ({ id, name, args }) => {
|
|
6
9
|
try {
|
|
7
|
-
parentPort.postMessage({ id, ok: true, value:
|
|
10
|
+
parentPort.postMessage({ id, ok: true, value: jobs[name](...args) });
|
|
8
11
|
} catch (error) {
|
|
9
12
|
parentPort.postMessage({ id, ok: false, error: String(error?.message ?? error) });
|
|
10
13
|
}
|