agentbox-app 0.1.11 → 0.1.13
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 -16
- package/bin/agentbox.mjs +18 -9
- package/briefs/message-rules.md +15 -4
- package/main/account-tooling.mjs +2 -1
- package/main/acp.mjs +296 -0
- package/main/agent-homes.mjs +158 -0
- package/main/agent-sessions.mjs +57 -20
- package/main/agent-updates.mjs +9 -20
- package/main/analytics.mjs +30 -0
- package/main/approvals.mjs +14 -5
- package/main/claude-bin.mjs +45 -310
- package/main/claude-input.mjs +9 -3
- package/main/claude-launch-env.mjs +54 -0
- package/main/claude-models.mjs +39 -7
- package/main/claude-plan.mjs +14 -2
- package/main/claude-usage.mjs +82 -25
- package/main/codex-bin.mjs +51 -248
- package/main/codex-launch-env.mjs +31 -0
- package/main/codex-session.mjs +38 -3
- package/main/codex-threads.mjs +41 -8
- package/main/codex-watch.mjs +67 -3
- package/main/config.mjs +40 -13
- package/main/corner-tag.mjs +489 -0
- package/main/crash-report.mjs +26 -3
- package/main/demo.mjs +17 -14
- package/main/engine-bin.mjs +595 -0
- package/main/engine-setup.mjs +27 -27
- package/main/feedback.mjs +11 -6
- package/main/fresh-user.mjs +1 -1
- package/main/git-change.mjs +8 -2
- package/main/harnesses/claude-stream.mjs +208 -0
- package/main/harnesses/claude.mjs +84 -0
- package/main/harnesses/codex.mjs +65 -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 +38 -0
- package/main/instruction-settings.mjs +47 -11
- package/main/ipc.mjs +175 -22
- package/main/local-app-frames.mjs +82 -0
- package/main/machine.mjs +1 -1
- package/main/main.mjs +120 -5
- package/main/memory-gate-history.mjs +51 -12
- package/main/memory-gate-server.mjs +67 -11
- package/main/memory-gate.mjs +49 -8
- package/main/memory-log.mjs +124 -0
- package/main/notify.mjs +48 -5
- package/main/opencode-server.mjs +271 -0
- package/main/opencode.mjs +287 -0
- package/main/phone-link.mjs +551 -0
- package/main/refresh-agent-models.mjs +2 -1
- package/main/relay-address.mjs +28 -0
- package/main/relay-client.mjs +246 -0
- package/main/row-label.mjs +8 -7
- package/main/runs-on.mjs +90 -0
- package/main/serve.mjs +124 -10
- package/main/settings.mjs +171 -11
- package/main/shell-path.mjs +86 -0
- package/main/ship-queue.mjs +25 -7
- package/main/sign-in-files.mjs +3 -7
- package/main/source-updater.mjs +27 -1
- package/main/start-hold.mjs +73 -0
- package/main/store/home.mjs +37 -5
- package/main/store/project.mjs +15 -19
- package/main/store.mjs +10 -4
- package/main/supervisor.mjs +1030 -447
- package/main/task-commands.mjs +13 -17
- package/main/task-folders-offthread.mjs +6 -1
- package/main/task-folders.mjs +491 -82
- package/main/task-terminals.mjs +49 -8
- package/main/team/index.mjs +2 -2
- package/main/team/projects.mjs +1 -1
- package/main/updater.mjs +71 -9
- package/main/worktree-include.mjs +494 -0
- package/mcp/core/page.mjs +1 -1
- package/mcp/core/work.mjs +34 -3
- package/mcp/tools.mjs +2 -1
- package/package.json +10 -1
- package/renderer/dist/assets/abnfDiagram-YUKMZFIV-DPPuMWt-.js +1 -0
- package/renderer/dist/assets/arc-HmFfdseH.js +1 -0
- package/renderer/dist/assets/architectureDiagram-47P4ROYG-DxJ3zHrH.js +36 -0
- package/renderer/dist/assets/blockDiagram-E7TT5TSB-CbTXzOqQ.js +140 -0
- package/renderer/dist/assets/c4Diagram-CRA5TL53-X5ItFyLn.js +38 -0
- package/renderer/dist/assets/channel-BlT2g9Sz.js +1 -0
- package/renderer/dist/assets/chunk-2Q5K7J3B-DC12Ho_L.js +1 -0
- package/renderer/dist/assets/chunk-5VM5RSS4-ClVtfPd0.js +15 -0
- package/renderer/dist/assets/chunk-7TKQ45FW-DgcOm0HT.js +1 -0
- package/renderer/dist/assets/chunk-AW2ZBBNX-C01s4yoF.js +88 -0
- package/renderer/dist/assets/chunk-FVRAUYC3-Bj0zxhR4.js +2 -0
- package/renderer/dist/assets/chunk-HTAEGDNF-DWDeEhLU.js +1 -0
- package/renderer/dist/assets/chunk-JWPE2WC7-Dsqwv3eW.js +1 -0
- package/renderer/dist/assets/chunk-KQW6MTUR-CE86ANL2.js +1 -0
- package/renderer/dist/assets/chunk-XXDRQBXY-CoNTASfs.js +1 -0
- package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-2wprLqtw.js +217 -0
- package/renderer/dist/assets/cornerTag-BLrxiDE5.css +1 -0
- package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +1 -0
- package/renderer/dist/assets/cose-bilkent-JH36ORCC-enqMiYbn.js +1 -0
- package/renderer/dist/assets/cynefin-EF2NZ3EQ-DHngLxvQ.js +141 -0
- package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-C0rZZIl3.js +62 -0
- package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
- package/renderer/dist/assets/dagre-W4DXFKR2-DQnaEWO7.js +4 -0
- package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/renderer/dist/assets/diagram-2UJZ2QOL-CS8AlGPx.js +200 -0
- package/renderer/dist/assets/diagram-OVF4WLC6-yiDMHIkY.js +24 -0
- package/renderer/dist/assets/diagram-PFPMY2P6-XbQKzbBA.js +30 -0
- package/renderer/dist/assets/diagram-UMYRVEAY-dhVvlfl_.js +24 -0
- package/renderer/dist/assets/diagram-YEKJPTXX-DZHe8SCf.js +41 -0
- package/renderer/dist/assets/diagram-ZIFT7M5P-B5h6aXVR.js +3 -0
- package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-PR9ScDco.js +1 -0
- package/renderer/dist/assets/elk-IJKZMXRS-Cg-yQXPd.js +27 -0
- package/renderer/dist/assets/erDiagram-O2IAPWRE-omOrJMxu.js +99 -0
- package/renderer/dist/assets/flowDiagram-OXPTDLAJ-aOX2hzvT.js +213 -0
- package/renderer/dist/assets/ganttDiagram-R7TSEDQI-B1H3pWyI.js +292 -0
- package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-Cvw304HC.js +106 -0
- package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
- package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-_ZXcNViH.js +2 -0
- package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
- package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js +70 -0
- package/renderer/dist/assets/journeyDiagram-COXZFDF6-fr62QoAt.js +139 -0
- package/renderer/dist/assets/kanban-definition-P3RFRI5V-J8zsabCe.js +89 -0
- package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
- package/renderer/dist/assets/linear-BmNdEtIi.js +1 -0
- package/renderer/dist/assets/main-ChNdnUvt.js +1132 -0
- package/renderer/dist/assets/main-nj0QR5kg.css +32 -0
- package/renderer/dist/assets/mermaid.core-CnYzvFkz.js +314 -0
- package/renderer/dist/assets/mindmap-definition-LZFPQGKD-Bfcxc7PM.js +96 -0
- package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/renderer/dist/assets/pegDiagram-BGZESJAR-CwUJOq6X.js +1 -0
- package/renderer/dist/assets/pieDiagram-CAPJLFHJ-BrwMmqfC.js +39 -0
- package/renderer/dist/assets/product-name-CTKLYNWY.js +40 -0
- package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-BN4Stoj-.js +7 -0
- package/renderer/dist/assets/railroadDiagram-SM67HX2B-CPZ5bbeR.js +1 -0
- package/renderer/dist/assets/requirementDiagram-X7JNWC4A-CgRnimVe.js +84 -0
- package/renderer/dist/assets/sankeyDiagram-UM26HJRW-Bt2rej__.js +40 -0
- package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js +169 -0
- package/renderer/dist/assets/sizeCapture-INFHLROL-usyNyXyh.js +1 -0
- package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-CRzUBb0u.js +291 -0
- package/renderer/dist/assets/swimlanes-N4OXWK64-C1GJvMS0.js +1 -0
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +8 -0
- package/renderer/dist/assets/timeline-definition-YOQAKGHF-caeyPlcc.js +120 -0
- package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-BN_SV6PC.js +370 -0
- package/renderer/dist/assets/vennDiagram-BLWOH2XV-BLGuaUX-.js +34 -0
- package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-Da0cJeNc.js +78 -0
- package/renderer/dist/assets/xychartDiagram-TAQBALBS-cGfUffMk.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 +14 -0
- package/scripts/approval-server.sh +31 -7
- package/scripts/before-publish.mjs +12 -10
- package/shared/agent-import.mjs +3 -1
- package/shared/agents.d.mts +1 -0
- package/shared/bridge-map.mjs +19 -0
- package/shared/claude-commands.generated.mjs +8 -6
- package/shared/claude-models.generated.mjs +10 -8
- package/shared/codex-modes.mjs +1 -1
- package/shared/corner-tag.d.mts +14 -0
- package/shared/corner-tag.mjs +141 -0
- package/shared/demo-world.mjs +1 -1
- package/shared/engines.mjs +20 -5
- package/shared/fresh-user-home.mjs +12 -1
- package/shared/harness-definitions.mjs +103 -0
- package/shared/instruction-defaults.json +1 -1
- package/shared/notify-rules.d.mts +25 -0
- package/shared/notify-rules.mjs +19 -0
- package/shared/project-folder-check.mjs +3 -3
- package/shared/provider-commands.mjs +10 -2
- package/shared/rank.d.mts +2 -0
- package/shared/rank.mjs +50 -0
- package/shared/relay-address.mjs +6 -0
- package/shared/relay-crypto.mjs +165 -0
- package/shared/runs-on.d.mts +10 -0
- package/shared/runs-on.mjs +55 -0
- package/shared/shortcuts.mjs +11 -5
- package/shared/spawn-trouble.mjs +58 -7
- 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/usage.d.mts +3 -0
- package/shared/usage.mjs +7 -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 +28 -2
- package/shared/work-lines.mjs +1 -1
- package/shared/xterm-invalid-range.d.mts +2 -0
- package/shared/xterm-invalid-range.mjs +58 -0
- package/renderer/dist/assets/index-B-2Az952.css +0 -32
- package/renderer/dist/assets/index-D6qncgai.js +0 -1168
package/main/agent-sessions.mjs
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
//
|
|
13
13
|
// WHERE A THREAD LIVES. Two places, because she named two:
|
|
14
14
|
//
|
|
15
|
-
// ~/.claude/projects/<folder>/<id>.jsonl every terminal session
|
|
15
|
+
// ~/.claude/projects/<folder>/<id>.jsonl every terminal session (and the
|
|
16
|
+
// same under every other Claude
|
|
17
|
+
// data folder: main/agent-homes.mjs)
|
|
16
18
|
// ~/Library/Application Support/Claude/
|
|
17
19
|
// claude-code-sessions/**/local_<id>.json the desktop app
|
|
18
20
|
//
|
|
@@ -49,6 +51,7 @@
|
|
|
49
51
|
import fs from 'node:fs';
|
|
50
52
|
import os from 'node:os';
|
|
51
53
|
import path from 'node:path';
|
|
54
|
+
import { claudeDataDirs } from './agent-homes.mjs';
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* "Recent = last few days", in days.
|
|
@@ -149,9 +152,22 @@ export function isScratchProjectDir(name, home = os.homedir()) {
|
|
|
149
152
|
*
|
|
150
153
|
* An entrypoint we have never seen is treated as not hers. The cost of being
|
|
151
154
|
* wrong that way is one missing row on a card; the other way is the hundreds
|
|
152
|
-
* she is complaining about.
|
|
155
|
+
* she is complaining about.
|
|
156
|
+
*
|
|
157
|
+
* `claude-vscode` IS A PERSON TOO (w-e46f2e9cea, 2026-10-11). It is what the
|
|
158
|
+
* Claude Code extension writes in VS Code, Cursor and Windsurf, and leaving it
|
|
159
|
+
* out threw away every session of everybody who runs Claude Code in an editor.
|
|
160
|
+
* Read out of the 2.1.293 binary, which maps it to `claude_code_vscode`. The
|
|
161
|
+
* `remote*` family stays out: those run in the cloud, not on this disk. */
|
|
162
|
+
const BY_HAND = new Set(['cli', 'claude-desktop', 'claude-vscode']);
|
|
153
163
|
export function startedByHand(entrypoint) {
|
|
154
|
-
return entrypoint
|
|
164
|
+
return BY_HAND.has(entrypoint);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Which of the three places a person starts Claude Code a session came from. */
|
|
168
|
+
export function sourceOf(entrypoint) {
|
|
169
|
+
if (entrypoint === 'claude-desktop') return 'desktop';
|
|
170
|
+
return entrypoint === 'claude-vscode' ? 'editor' : 'terminal';
|
|
155
171
|
}
|
|
156
172
|
|
|
157
173
|
/**
|
|
@@ -251,9 +267,11 @@ export function readTranscriptHead(file, bytes = HEAD_BYTES, most = FIRST_TURN_B
|
|
|
251
267
|
const n = fs.readSync(fd, buf, 0, bytes, 0);
|
|
252
268
|
const text = buf.slice(0, n).toString('utf8');
|
|
253
269
|
// The last line of a fixed read is usually cut in half. Dropping it costs
|
|
254
|
-
// nothing, because what is wanted is at the top.
|
|
270
|
+
// nothing, because what is wanted is at the top. UNLESS THE WHOLE FILE FIT:
|
|
271
|
+
// then the last line is complete, and a file whose one row has no newline
|
|
272
|
+
// after it lost that row (found by Codex's review, 2026-10-11).
|
|
255
273
|
const lines = text.split('\n');
|
|
256
|
-
const cut = lines.pop();
|
|
274
|
+
const cut = n < bytes ? null : lines.pop();
|
|
257
275
|
let cwd = null;
|
|
258
276
|
let entrypoint = null;
|
|
259
277
|
const said = [];
|
|
@@ -278,7 +296,7 @@ export function readTranscriptHead(file, bytes = HEAD_BYTES, most = FIRST_TURN_B
|
|
|
278
296
|
// only when nothing she said was found and the session is not already
|
|
279
297
|
// known to be the app's own, so the thousands of worker transcripts are
|
|
280
298
|
// never read past their head.
|
|
281
|
-
if (!said.length && n === bytes && cut && /"type"
|
|
299
|
+
if (!said.length && n === bytes && cut && /"type"\s*:\s*"user"/.test(cut.slice(0, 2048))
|
|
282
300
|
&& (!entrypoint || startedByHand(entrypoint))) {
|
|
283
301
|
const rest = restOfLine(fd, n, most - n);
|
|
284
302
|
if (rest) take(Buffer.concat([buf.subarray(buf.lastIndexOf(0x0a, n - 1) + 1, n), rest]).toString('utf8'));
|
|
@@ -331,25 +349,35 @@ function messageText(row) {
|
|
|
331
349
|
* `skipped` comes back with it because the counts are the argument for the
|
|
332
350
|
* filters, and a session that changes one of them should be able to see what
|
|
333
351
|
* it changed rather than take this comment's word for it. */
|
|
334
|
-
function readTerminalThreads({ home, cutoff, max, skipIds = new Set() }) {
|
|
335
|
-
const
|
|
336
|
-
const skipped = { old: 0, scratch: 0, started: 0, subagent: 0, empty: 0, unread: 0, desktopDupe: 0 };
|
|
352
|
+
function readTerminalThreads({ home, dirs, cutoff, max, skipIds = new Set() }) {
|
|
353
|
+
const skipped = { old: 0, scratch: 0, started: 0, subagent: 0, empty: 0, unread: 0, desktopDupe: 0, copy: 0 };
|
|
337
354
|
const out = [];
|
|
338
|
-
const all = [];
|
|
339
|
-
walkTranscripts(root, all);
|
|
340
355
|
// Stat everything, keep the recent, newest first, and only then start opening
|
|
341
356
|
// files. This is the order that makes `max` safe: whatever it cuts off is
|
|
342
357
|
// older than everything it kept.
|
|
358
|
+
//
|
|
359
|
+
// EVERY DATA FOLDER, NOT ONLY ~/.claude (main/agent-homes.mjs). They are
|
|
360
|
+
// pooled before the sort, so the cap and the time budget are spent on the
|
|
361
|
+
// newest sessions on the machine, whichever folder they are in.
|
|
343
362
|
const recent = [];
|
|
344
|
-
for (const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
363
|
+
for (const dir of dirs) {
|
|
364
|
+
const root = path.join(dir, 'projects');
|
|
365
|
+
const all = [];
|
|
366
|
+
walkTranscripts(root, all);
|
|
367
|
+
for (const file of all) {
|
|
368
|
+
let when = 0;
|
|
369
|
+
try { when = fs.statSync(file).mtimeMs; } catch { continue; }
|
|
370
|
+
if (when < cutoff) { skipped.old += 1; continue; }
|
|
371
|
+
if (isSubagentPath(file.slice(root.length))) { skipped.subagent += 1; continue; }
|
|
372
|
+
if (isScratchProjectDir(path.relative(root, file).split(path.sep)[0], home)) { skipped.scratch += 1; continue; }
|
|
373
|
+
recent.push({ file, when });
|
|
374
|
+
}
|
|
351
375
|
}
|
|
352
376
|
recent.sort((a, b) => b.when - a.when);
|
|
377
|
+
// ONE SESSION IS ONE ROW, even when two folders hold it: a backup folder with
|
|
378
|
+
// a copy of a conversation is ordinary. Newest first, so the copy kept is the
|
|
379
|
+
// one still being written to.
|
|
380
|
+
const seenIds = new Set();
|
|
353
381
|
// THE CAP COUNTS WHAT IS KEPT, NOT WHAT IS OPENED (w-ec62ab6b38, 2026-09-28).
|
|
354
382
|
// On a busy machine the last seven days held 9,073 transcripts, the newest
|
|
355
383
|
// 1,200 were opened, and every one of those was started by agentbox itself,
|
|
@@ -373,14 +401,18 @@ function readTerminalThreads({ home, cutoff, max, skipIds = new Set() }) {
|
|
|
373
401
|
// one desktop thread in her store. Its own record wins, because the app
|
|
374
402
|
// wrote a real title into it and this side would have to guess one.
|
|
375
403
|
if (skipIds.has(id)) { skipped.desktopDupe += 1; continue; }
|
|
404
|
+
if (seenIds.has(id)) { skipped.copy += 1; continue; }
|
|
376
405
|
const { cwd, entrypoint, said } = readTranscriptHead(file);
|
|
406
|
+
// A copy is only the copy once it could be read. A damaged newer one, cut
|
|
407
|
+
// off mid-write, would otherwise hide the good older one in another folder.
|
|
408
|
+
if (cwd || entrypoint) seenIds.add(id);
|
|
377
409
|
if (!startedByHand(entrypoint)) { skipped.started += 1; continue; }
|
|
378
410
|
if (isScratchFolder(cwd, home)) { skipped.scratch += 1; continue; }
|
|
379
411
|
const title = threadTitle(said);
|
|
380
412
|
if (!title) { skipped.empty += 1; continue; }
|
|
381
413
|
out.push({
|
|
382
414
|
id,
|
|
383
|
-
source: entrypoint
|
|
415
|
+
source: sourceOf(entrypoint),
|
|
384
416
|
folder: cwd,
|
|
385
417
|
folderName: path.basename(cwd),
|
|
386
418
|
short: shortFolder(cwd, home),
|
|
@@ -484,6 +516,11 @@ function walkDesktop(dir, into, max, depth = 3) {
|
|
|
484
516
|
* the card groups by it, the same way it already groups agent files. */
|
|
485
517
|
export function readSessionThreads({
|
|
486
518
|
home = os.homedir(),
|
|
519
|
+
// Every Claude Code data folder to read (main/agent-homes.mjs). The app hands
|
|
520
|
+
// in the list it made with its own environment and settings; left out, it is
|
|
521
|
+
// the folders under `home`, and nothing from whatever CLAUDE_CONFIG_DIR the
|
|
522
|
+
// caller's process happens to carry.
|
|
523
|
+
dirs = claudeDataDirs({ home }),
|
|
487
524
|
days = RECENT_DAYS,
|
|
488
525
|
now = Date.now(),
|
|
489
526
|
max = MAX_READS,
|
|
@@ -492,7 +529,7 @@ export function readSessionThreads({
|
|
|
492
529
|
// The desktop side goes first so its ids are known before the transcripts are
|
|
493
530
|
// walked, which is what stops one thread appearing twice.
|
|
494
531
|
const desk = readDesktopThreads({ home, cutoff, max });
|
|
495
|
-
const term = readTerminalThreads({ home, cutoff, max, skipIds: desk.owned });
|
|
532
|
+
const term = readTerminalThreads({ home, dirs, cutoff, max, skipIds: desk.owned });
|
|
496
533
|
const threads = [...term.threads, ...desk.threads].sort((a, b) => b.when - a.when);
|
|
497
534
|
return {
|
|
498
535
|
threads,
|
package/main/agent-updates.mjs
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// started only by the app's Update now action, never by a poll or terminal open.
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
|
-
import path from 'node:path';
|
|
6
5
|
import {execFile} from 'node:child_process';
|
|
7
6
|
import {promisify} from 'node:util';
|
|
8
7
|
import {TaskTerminals,terminalEnv} from './task-terminals.mjs';
|
|
8
|
+
import { harnessFor } from './harnesses.mjs';
|
|
9
|
+
import { harnessDefinition } from '../shared/harness-definitions.mjs';
|
|
9
10
|
const exec=promisify(execFile);
|
|
10
11
|
const valid=v=>typeof v==='string'&&/^\d+\.\d+\.\d+$/.test(v);
|
|
11
12
|
export function newerVersion(installed,latest){
|
|
@@ -14,27 +15,15 @@ export function newerVersion(installed,latest){
|
|
|
14
15
|
for(let i=0;i<3;i++){if(b[i]!==a[i])return b[i]>a[i];}return false;
|
|
15
16
|
}
|
|
16
17
|
export function updatePlan({engine,bin,real,home=os.homedir(),channel='latest'}){
|
|
17
|
-
if(!
|
|
18
|
-
|
|
19
|
-
const brew=real.match(/^(\/opt\/homebrew|\/usr\/local)\/Caskroom\/(claude-code(?:@latest)?|codex)\//);
|
|
20
|
-
if(brew&&((engine==='claude'&&brew[2].startsWith('claude-code'))||(engine==='codex'&&brew[2]==='codex')))
|
|
21
|
-
return {name,file:`${brew[1]}/bin/brew`,args:['upgrade','--cask',brew[2]],feed:`https://formulae.brew.sh/api/cask/${brew[2]}.json`,format:'brew'};
|
|
22
|
-
const pkg=engine==='claude'?'@anthropic-ai/claude-code':'@openai/codex';
|
|
23
|
-
const tail=`/lib/node_modules/${pkg}/`;
|
|
24
|
-
const at=real.indexOf(tail);
|
|
25
|
-
if(at>0){const prefix=real.slice(0,at);return {name,file:path.join(prefix,'bin/npm'),args:['install','--global','--prefix',prefix,`${pkg}@${channel}`],env:{PATH:`${prefix}/bin:${process.env.PATH||''}`},feed:`https://registry.npmjs.org/${pkg}/${channel}`,format:'npm'};}
|
|
26
|
-
if(engine==='claude'&&real.startsWith(path.join(home,'.local/share/claude/versions')+'/'))return {name,file:bin,args:['update'],feed:`https://downloads.claude.ai/claude-code-releases/${channel}`,format:'text'};
|
|
27
|
-
const standalone=real.match(/^(.*)\/packages\/standalone\/(?:releases|current)\//);
|
|
28
|
-
if(engine==='codex'&&standalone)return {name,file:'/bin/bash',args:['-o','pipefail','-c','/usr/bin/curl -fsSL --connect-timeout 10 --max-time 60 https://chatgpt.com/codex/install.sh | /bin/sh'],env:{CODEX_HOME:standalone[1],CODEX_INSTALL_DIR:path.dirname(bin)},feed:'https://releases.openai.com/codex/channels/latest',format:'codex'};
|
|
29
|
-
return null;
|
|
18
|
+
if(!harnessDefinition(engine))return null;
|
|
19
|
+
return harnessFor(engine).updatePlan({bin,real,home,channel});
|
|
30
20
|
}
|
|
31
|
-
const json=file=>{try{return JSON.parse(fs.readFileSync(file,'utf8'))}catch{return {}}};
|
|
32
21
|
export async function installedAgent(engine,config){
|
|
33
|
-
const bin=
|
|
22
|
+
const harness=harnessFor(engine), bin=harness.binary(config);
|
|
34
23
|
if(!bin)throw Error('This agent is not installed.');
|
|
35
24
|
const home=config.home||os.homedir();
|
|
36
|
-
const settings=
|
|
37
|
-
const env=terminalEnv();
|
|
25
|
+
const settings=harness.updateSettings({home});
|
|
26
|
+
const env=harness.launchEnv(bin,terminalEnv());
|
|
38
27
|
// Version inspection must not itself auto-update anything.
|
|
39
28
|
const {stdout}=await exec(bin,['--version'],{timeout:8000,maxBuffer:16384,env:{...env,DISABLE_AUTOUPDATER:'1'}});
|
|
40
29
|
const version=stdout.match(/\b\d+\.\d+\.\d+(?:-[\w.-]+)?\b/)?.[0];
|
|
@@ -57,7 +46,7 @@ export class AgentUpdates{
|
|
|
57
46
|
}
|
|
58
47
|
status(engine){return this.entries.get(engine)||{engine,state:'unknown'};}
|
|
59
48
|
check(engine,force=false){
|
|
60
|
-
if(!
|
|
49
|
+
if(!harnessDefinition(engine))return Promise.reject(Error('Unknown agent.'));
|
|
61
50
|
if(this.starts.has(engine)||this.status(engine).state==='running')return Promise.resolve(this.status(engine));
|
|
62
51
|
if(this.checks.has(engine))return this.checks.get(engine);
|
|
63
52
|
const saved=this.status(engine);if(!force&&saved.checkedAt&&this.now()-saved.checkedAt<3600000)return Promise.resolve(saved);
|
|
@@ -103,7 +92,7 @@ export class AgentUpdates{
|
|
|
103
92
|
if(!this.plans.has(engine))throw Error('Click Update now first.');
|
|
104
93
|
switch(p.action){
|
|
105
94
|
case 'open':return this.terminals.read(engine,0);
|
|
106
|
-
case 'read':return this.terminals.read(engine,p.offset);
|
|
95
|
+
case 'read':return this.terminals.read(engine,p.offset,p.wait);
|
|
107
96
|
case 'write':return this.terminals.write(engine,p.data);
|
|
108
97
|
case 'resize':return this.terminals.resize(engine,p.cols,p.rows);
|
|
109
98
|
case 'close':return this.terminals.close(engine);
|
package/main/analytics.mjs
CHANGED
|
@@ -178,6 +178,36 @@ export function createAnalytics({ config, version, dir, client, env = process.en
|
|
|
178
178
|
report_version: Number(payload.v ?? 1),
|
|
179
179
|
error: payload.error ?? null,
|
|
180
180
|
happened_at: String(payload.ts ?? ''),
|
|
181
|
+
// THE VERSION IT CRASHED ON, WHICH IS NOT ALWAYS THE ONE SENDING IT.
|
|
182
|
+
// A report written by a dying process is drained on the NEXT launch,
|
|
183
|
+
// and that launch may be a newer app: `base.app_version` then files
|
|
184
|
+
// 0.1.11's crash against 0.1.12. It did, for three of the six window
|
|
185
|
+
// errors the launch report attributes to 0.1.12 (PostHog, 2026-10-08),
|
|
186
|
+
// and a version asked to answer for the bugs of the one before it is
|
|
187
|
+
// how a release that fixed something reads as a release that broke it.
|
|
188
|
+
// The report has carried the right answer all along.
|
|
189
|
+
app_version: String(payload.app?.version ?? base.app_version),
|
|
190
|
+
// WHY THE WINDOW DIED, which is the only thing a `renderer-gone`
|
|
191
|
+
// report is for and the one thing it was not carrying.
|
|
192
|
+
// `installCrashReports` collects Electron's `details.reason` and
|
|
193
|
+
// `details.exitCode` and says in its own comment why they are safe:
|
|
194
|
+
// the reason is Electron's closed vocabulary (`crashed`, `oom`,
|
|
195
|
+
// `killed`, `launch-failed` and the rest) and carries nothing of
|
|
196
|
+
// anyone's. They were written to disk and then dropped here, because
|
|
197
|
+
// this list names its properties by hand. So the launch report's one
|
|
198
|
+
// dead window says `render process gone` and nothing else, and `oom`
|
|
199
|
+
// after three and a half hours (a leak) could not be told apart from a
|
|
200
|
+
// hard fault (not a leak). Measured 2026-10-08.
|
|
201
|
+
//
|
|
202
|
+
// Absent rather than empty when there is nothing to say: a null reason
|
|
203
|
+
// on the thirteen crashes that never had one reads as an answer in a
|
|
204
|
+
// chart. A reason longer than Electron's longest word is a bug
|
|
205
|
+
// upstream rather than a reason, and is left out too.
|
|
206
|
+
...(typeof payload.reason === 'string' && payload.reason && payload.reason.length <= 40
|
|
207
|
+
? { crash_reason: payload.reason } : {}),
|
|
208
|
+
...(Number.isFinite(payload.exitCode) ? { exit_code: Number(payload.exitCode) } : {}),
|
|
209
|
+
...(typeof payload.processType === 'string' && payload.processType && payload.processType.length <= 40
|
|
210
|
+
? { process_type: payload.processType } : {}),
|
|
181
211
|
});
|
|
182
212
|
},
|
|
183
213
|
|
package/main/approvals.mjs
CHANGED
|
@@ -387,12 +387,20 @@ const REQUEST_SUFFIX = '.request.json';
|
|
|
387
387
|
* FORWARD to now. A worker can therefore make its own request look younger than
|
|
388
388
|
* it is, which costs it the front of the stack, and can never make it look
|
|
389
389
|
* older. Nanoseconds because APFS keeps them and two files written back to back
|
|
390
|
-
* differ by about 170 microseconds
|
|
391
|
-
*
|
|
390
|
+
* differ by about 170 microseconds.
|
|
391
|
+
*
|
|
392
|
+
* A coarse clock stamps both files the same. tmpfs does this: two writes in
|
|
393
|
+
* one poll share a ctime, and `readdir` then comes back in name order, which
|
|
394
|
+
* is the forgery the paragraph above is about. The inode breaks that tie.
|
|
395
|
+
* The kernel assigns it, a later create in a fresh directory gets a higher
|
|
396
|
+
* one, and a worker cannot move it backwards. Equal inode and equal ctime
|
|
397
|
+
* keep the order we already had.
|
|
392
398
|
*/
|
|
393
399
|
function bornAt(dir, name) {
|
|
394
|
-
try {
|
|
395
|
-
|
|
400
|
+
try {
|
|
401
|
+
const st = fs.lstatSync(path.join(dir, name), { bigint: true });
|
|
402
|
+
return { ctime: st.ctimeNs, ino: st.ino };
|
|
403
|
+
} catch { return { ctime: 2n ** 63n, ino: 2n ** 63n }; } // gone or unreadable: never in front of something real
|
|
396
404
|
}
|
|
397
405
|
|
|
398
406
|
export function listPending(storeRoot, { now = Date.now } = {}) {
|
|
@@ -409,7 +417,8 @@ export function listPending(storeRoot, { now = Date.now } = {}) {
|
|
|
409
417
|
for (const { f } of names
|
|
410
418
|
.filter((f) => !seenAt.has(prefix + f.slice(0, -REQUEST_SUFFIX.length)))
|
|
411
419
|
.map((f) => ({ f, born: bornAt(dir, f) }))
|
|
412
|
-
.sort((a, b) => (a.born < b.born ? -1 : a.born > b.born ? 1
|
|
420
|
+
.sort((a, b) => (a.born.ctime < b.born.ctime ? -1 : a.born.ctime > b.born.ctime ? 1
|
|
421
|
+
: a.born.ino < b.born.ino ? -1 : a.born.ino > b.born.ino ? 1 : 0))) {
|
|
413
422
|
seenAt.set(prefix + f.slice(0, -REQUEST_SUFFIX.length), { at, seq: sightings += 1 });
|
|
414
423
|
}
|
|
415
424
|
const present = new Set();
|