agentbox-app 0.1.12 → 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/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 -22
- package/main/analytics.mjs +30 -0
- package/main/claude-bin.mjs +45 -311
- 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 +16 -16
- package/main/claude-usage.mjs +82 -25
- package/main/codex-bin.mjs +51 -249
- 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 +26 -4
- package/main/corner-tag.mjs +489 -0
- package/main/crash-report.mjs +26 -3
- package/main/engine-bin.mjs +595 -0
- package/main/engine-setup.mjs +4 -15
- package/main/feedback.mjs +11 -6
- 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/ipc.mjs +165 -31
- package/main/local-app-frames.mjs +82 -0
- package/main/main.mjs +100 -4
- 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 +47 -4
- package/main/opencode-server.mjs +271 -0
- package/main/opencode.mjs +287 -0
- package/main/phone-link.mjs +551 -0
- package/main/relay-address.mjs +28 -0
- package/main/relay-client.mjs +246 -0
- package/main/row-label.mjs +6 -6
- package/main/runs-on.mjs +32 -35
- package/main/serve.mjs +113 -9
- package/main/settings.mjs +148 -10
- package/main/shell-path.mjs +86 -0
- package/main/ship-queue.mjs +24 -6
- 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.mjs +1 -1
- package/main/supervisor.mjs +832 -428
- package/main/task-commands.mjs +13 -17
- package/main/task-folders-offthread.mjs +6 -1
- package/main/task-folders.mjs +490 -98
- package/main/task-terminals.mjs +37 -7
- package/main/updater.mjs +71 -9
- package/main/worktree-include.mjs +494 -0
- 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/approval-server.sh +24 -5
- package/scripts/before-publish.mjs +12 -10
- package/shared/agent-import.mjs +2 -0
- package/shared/agents.d.mts +1 -0
- package/shared/bridge-map.mjs +18 -0
- package/shared/claude-commands.generated.mjs +6 -4
- package/shared/claude-models.generated.mjs +6 -4
- package/shared/corner-tag.d.mts +14 -0
- package/shared/corner-tag.mjs +141 -0
- package/shared/engines.mjs +20 -5
- package/shared/harness-definitions.mjs +103 -0
- package/shared/notify-rules.d.mts +25 -0
- package/shared/notify-rules.mjs +19 -0
- 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 +0 -10
- package/shared/runs-on.mjs +25 -49
- package/shared/shortcuts.mjs +10 -4
- package/shared/spawn-trouble.mjs +57 -6
- 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 +25 -1
- 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-ChKsQkRw.js +0 -1169
- package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
package/main/codex-session.mjs
CHANGED
|
@@ -519,9 +519,10 @@ export function createCodexWorker({
|
|
|
519
519
|
// read loop, so anything the server packs in BEHIND the response is caught,
|
|
520
520
|
// and a `ready` that somehow came in AHEAD of it would belong to no watched
|
|
521
521
|
// thread and be dropped -- costing a healthy worker its run at the deadline.
|
|
522
|
-
// The resume road
|
|
523
|
-
//
|
|
524
|
-
//
|
|
522
|
+
// The resume road watches before sending, but an already-loaded thread does
|
|
523
|
+
// not repeat startup notifications. On 2026-10-07, 11 resumes of one loaded
|
|
524
|
+
// thread died at this deadline. Check that thread's current MCP connection
|
|
525
|
+
// on resume as well as listening for startup changes.
|
|
525
526
|
let storeIsUp = !requireMcpServer;
|
|
526
527
|
let storeTrouble = '';
|
|
527
528
|
const waiting = [];
|
|
@@ -581,6 +582,39 @@ export function createCodexWorker({
|
|
|
581
582
|
const timer = setTimeout(() => done(false), mcpReadyMs);
|
|
582
583
|
timer?.unref?.();
|
|
583
584
|
waiting.push(done);
|
|
585
|
+
if (resumeThreadId) {
|
|
586
|
+
const checkExistingStore = async () => {
|
|
587
|
+
let cursor;
|
|
588
|
+
const seen = new Set();
|
|
589
|
+
do {
|
|
590
|
+
if (ended || settled || storeIsUp) return;
|
|
591
|
+
const inventory = await server.request('mcpServerStatus/list', {
|
|
592
|
+
threadId: worker.threadId,
|
|
593
|
+
serverName: requireMcpServer,
|
|
594
|
+
detail: 'toolsAndAuthOnly',
|
|
595
|
+
...(cursor ? { cursor } : {}),
|
|
596
|
+
});
|
|
597
|
+
if (ended || settled || storeIsUp) return;
|
|
598
|
+
const store = inventory?.data?.find(s => s.name === requireMcpServer);
|
|
599
|
+
// Newer Codex reports connection state. Older versions only offer
|
|
600
|
+
// discovered tools; a configured name alone is never proof.
|
|
601
|
+
if (store?.runtimeStatus === 'connected'
|
|
602
|
+
|| (store && store.runtimeStatus == null && Object.keys(store.tools ?? {}).length > 0)) {
|
|
603
|
+
readStoreStatus({ name: requireMcpServer, status: 'ready' });
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (store?.runtimeStatus) storeTrouble = `its connection is ${store.runtimeStatus}`;
|
|
607
|
+
cursor = inventory?.nextCursor;
|
|
608
|
+
if (cursor && seen.has(cursor)) throw new Error('store inventory repeated its pagination cursor');
|
|
609
|
+
if (cursor) seen.add(cursor);
|
|
610
|
+
} while (cursor);
|
|
611
|
+
};
|
|
612
|
+
// The existing deadline still owns the wait, even if this RPC hangs.
|
|
613
|
+
// A startup notification can also settle it while inventory is read.
|
|
614
|
+
checkExistingStore().catch(err => {
|
|
615
|
+
if (!ended && !settled && !storeIsUp) storeTrouble = err?.message ?? String(err);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
584
618
|
});
|
|
585
619
|
};
|
|
586
620
|
|
|
@@ -595,6 +629,7 @@ export function createCodexWorker({
|
|
|
595
629
|
const end = (code, signal = null, how = null) => {
|
|
596
630
|
if (ended) return;
|
|
597
631
|
ended = true;
|
|
632
|
+
for (const wake of waiting.splice(0)) wake(false);
|
|
598
633
|
if (worker.threadId) { try { server.unwatch(worker.threadId); } catch { /* already gone */ } }
|
|
599
634
|
worker.emit('exit', code, signal, how);
|
|
600
635
|
};
|
package/main/codex-threads.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import fs from 'node:fs';
|
|
|
27
27
|
import os from 'node:os';
|
|
28
28
|
import path from 'node:path';
|
|
29
29
|
import { RECENT_DAYS, isScratchFolder, shortFolder, threadTitle } from './agent-sessions.mjs';
|
|
30
|
+
import { codexDataDirs } from './agent-homes.mjs';
|
|
30
31
|
import { WAS_SLUGS, nameSlug } from '../shared/product-name.mjs';
|
|
31
32
|
|
|
32
33
|
export const CODEX_RECENT_DAYS = RECENT_DAYS;
|
|
@@ -252,12 +253,40 @@ export const isLive = (mtime, now = Date.now()) => Number(mtime) > 0 && now - Nu
|
|
|
252
253
|
/**
|
|
253
254
|
* THE USER'S CONVERSATIONS, newest first, in the shape the import card and the
|
|
254
255
|
* import row both read. One entry per thread a person typed into, in a real folder,
|
|
255
|
-
* touched in the last `days` days.
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
* touched in the last `days` days.
|
|
257
|
+
*
|
|
258
|
+
* EVERY CODEX FOLDER ON THE MACHINE, not only `~/.codex` (w-e46f2e9cea): a
|
|
259
|
+
* second login under `~/.codex-<name>` or a `CODEX_HOME` holds conversations
|
|
260
|
+
* the import never offered. `codexDirs` is the list main/agent-homes.mjs makes;
|
|
261
|
+
* `codexDir` still names exactly one folder for a caller that means one. The
|
|
262
|
+
* same conversation in two folders is one row.
|
|
263
|
+
*
|
|
264
|
+
* WHICH COPY, AND WHICH NAME, decided apart (Codex's review of this change,
|
|
265
|
+
* 2026-10-11). The copy kept is the transcript file written last, judged by
|
|
266
|
+
* the file alone: judging by `when`, which folds in the index's own date, let
|
|
267
|
+
* a stale copy whose folder's index was touched later beat the copy still
|
|
268
|
+
* being written. And the name Codex gave a conversation is read from every
|
|
269
|
+
* folder's index, newest entry winning, so the copy kept still carries a name
|
|
270
|
+
* that only another folder's index knew. */
|
|
271
|
+
export function readCodexThreads({ home = os.homedir(), codexDir = null, codexDirs = null, days = CODEX_RECENT_DAYS, now = Date.now() } = {}) {
|
|
272
|
+
const dirs = codexDirs ?? (codexDir ? [codexDir] : codexDataDirs({ home }));
|
|
258
273
|
const cutoff = now - Math.max(0, Number(days) || 0) * 86400 * 1000;
|
|
259
|
-
const
|
|
260
|
-
const
|
|
274
|
+
const index = new Map();
|
|
275
|
+
for (const dir of dirs) {
|
|
276
|
+
for (const [id, entry] of readCodexIndex(dir)) {
|
|
277
|
+
const had = index.get(id);
|
|
278
|
+
if (!had || (entry.name && (!had.name || entry.updatedAt > had.updatedAt))) index.set(id, entry);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const byId = new Map();
|
|
282
|
+
const skipped = { notHers: 0, scratch: 0, old: 0, empty: 0, unreadable: 0, copy: 0 };
|
|
283
|
+
for (const dir of dirs) readOneCodexHome(dir, { home, days, now, cutoff, index, byId, skipped });
|
|
284
|
+
const out = [...byId.values()].map(({ fileTime, ...t }) => t);
|
|
285
|
+
out.sort((a, b) => b.when - a.when);
|
|
286
|
+
return { threads: out, skipped };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function readOneCodexHome(codexDir, { home, days, now, cutoff, index, byId, skipped }) {
|
|
261
290
|
for (const file of listRolloutFiles(codexDir, { days, since: cutoff })) {
|
|
262
291
|
const r = readRollout(file);
|
|
263
292
|
if (!r) { skipped.unreadable += 1; continue; }
|
|
@@ -268,7 +297,13 @@ export function readCodexThreads({ home = os.homedir(), codexDir = codexHome(hom
|
|
|
268
297
|
if (when < cutoff) { skipped.old += 1; continue; }
|
|
269
298
|
const title = named?.name || threadTitle([r.prompt]);
|
|
270
299
|
if (!title) { skipped.empty += 1; continue; }
|
|
271
|
-
|
|
300
|
+
const had = byId.get(r.id);
|
|
301
|
+
if (had) {
|
|
302
|
+
skipped.copy += 1;
|
|
303
|
+
if (had.fileTime >= r.mtime) continue;
|
|
304
|
+
}
|
|
305
|
+
byId.set(r.id, {
|
|
306
|
+
fileTime: r.mtime,
|
|
272
307
|
id: r.id,
|
|
273
308
|
source: 'codex',
|
|
274
309
|
folder: r.cwd,
|
|
@@ -286,6 +321,4 @@ export function readCodexThreads({ home = os.homedir(), codexDir = codexHome(hom
|
|
|
286
321
|
live: isLive(r.mtime, now),
|
|
287
322
|
});
|
|
288
323
|
}
|
|
289
|
-
out.sort((a, b) => b.when - a.when);
|
|
290
|
-
return { threads: out, skipped };
|
|
291
324
|
}
|
package/main/codex-watch.mjs
CHANGED
|
@@ -10,21 +10,85 @@
|
|
|
10
10
|
// The founder, before launch: "after onboarding for both anthropic and openai
|
|
11
11
|
// harnesses, it should only import on command, not continuously". Importing is
|
|
12
12
|
// now the walk's last card and ⌘K import, for Claude Code and Codex alike.
|
|
13
|
+
//
|
|
14
|
+
// IT IS ALSO WHERE A CODEX AGENT IS NOTICED (w-1116fbb68a, 2026-10-08). It is
|
|
15
|
+
// the one thing in the app that already knows a person's Codex conversations,
|
|
16
|
+
// and it reads them on a timer rather than on the snapshot, which is what makes
|
|
17
|
+
// it the right place: see `noticeCodexAgents` below.
|
|
13
18
|
import { readCodexThreads } from './codex-threads.mjs';
|
|
14
19
|
|
|
15
20
|
export const CODEX_POLL_MS = 60_000;
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
/**
|
|
23
|
+
* HOW LONG AFTER ITS LAST WRITE A CODEX THREAD IS STILL AN AGENT THAT WAS SEEN.
|
|
24
|
+
*
|
|
25
|
+
* A Claude Code session is a process, so `agent_seen` catches one that has sat
|
|
26
|
+
* open since earlier in the sitting (main/ipc.mjs, liveAgents). A Codex thread
|
|
27
|
+
* leaves no process behind: `isLive` is two minutes of file mtime, so counting
|
|
28
|
+
* only live threads would notice a Codex user ONLY when this watch's minute
|
|
29
|
+
* happened to land while they were typing. An hour is one sitting, which is
|
|
30
|
+
* the span the Claude read already covers without being asked to.
|
|
31
|
+
*
|
|
32
|
+
* AND IT IS NOT A DAY. The reader hands over ten days of conversations, and
|
|
33
|
+
* counting those would say every install with Codex on it had an agent up on
|
|
34
|
+
* every day the app was opened, which is the opposite of the under-count this
|
|
35
|
+
* was added to fix.
|
|
36
|
+
*/
|
|
37
|
+
export const CODEX_SEEN_MS = 60 * 60_000;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* "AN AGENT WAS SEEN", FOR CODEX TOO (w-1116fbb68a, 2026-10-08).
|
|
41
|
+
*
|
|
42
|
+
* `agent_seen` listed Claude Code processes and nothing else, and about a
|
|
43
|
+
* third of the people on this app are on Codex, so whether they had an agent
|
|
44
|
+
* at all was read off proxies: a run this app started, or a reply. The threads
|
|
45
|
+
* are already in hand once a minute for the mirror refresh, so this costs
|
|
46
|
+
* nothing beyond the comparison.
|
|
47
|
+
*
|
|
48
|
+
* NOTHING ABOUT A THREAD IS SENT. Not its folder, not its title, not its id,
|
|
49
|
+
* which is only ever compared inside this process. `engine` is the whole of
|
|
50
|
+
* the payload and it is one of two fixed words.
|
|
51
|
+
*
|
|
52
|
+
* ONCE PER THREAD PER RUN OF THE APP, for the reason the Claude side keeps a
|
|
53
|
+
* set: this reads the same conversations every minute, so without it one
|
|
54
|
+
* conversation would send an event a minute all day.
|
|
55
|
+
*/
|
|
56
|
+
export function noticeCodexAgents(threads, { count = null, seen = null, now = Date.now() } = {}) {
|
|
57
|
+
if (typeof count !== 'function') return 0;
|
|
58
|
+
let sent = 0;
|
|
59
|
+
for (const t of threads ?? []) {
|
|
60
|
+
const id = String(t?.id ?? '');
|
|
61
|
+
if (!id) continue;
|
|
62
|
+
const at = Number(t.lastActive ?? 0);
|
|
63
|
+
if (!t.live && !(at > 0 && now - at < CODEX_SEEN_MS)) continue;
|
|
64
|
+
if (seen) {
|
|
65
|
+
if (seen.has(id)) continue;
|
|
66
|
+
seen.add(id);
|
|
67
|
+
}
|
|
68
|
+
// A count is never worth the watch: the mirror refresh below is what
|
|
69
|
+
// somebody actually sees, and it runs whatever the switch does.
|
|
70
|
+
try { count('agent_seen', { engine: 'codex' }); } catch { /* nothing */ }
|
|
71
|
+
sent += 1;
|
|
72
|
+
}
|
|
73
|
+
return sent;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function scanCodex({ store, readThreads = readCodexThreads, now = Date.now(), count = null, seen = null } = {}) {
|
|
18
77
|
let threads = [];
|
|
19
78
|
try { ({ threads } = readThreads({ now })); } catch { return { refreshed: 0, retold: 0, threads: 0 }; }
|
|
79
|
+
noticeCodexAgents(threads, { count, seen, now });
|
|
20
80
|
const refreshed = store.refreshCodexMirrors(threads, { now });
|
|
21
81
|
const retold = store.refreshCodexAsks(threads, { now });
|
|
22
82
|
return { refreshed, retold, threads: threads.length };
|
|
23
83
|
}
|
|
24
84
|
|
|
25
|
-
export function startCodexWatch({ store, readThreads = readCodexThreads, pollMs = CODEX_POLL_MS, firstDelayMs = 8_000 } = {}) {
|
|
85
|
+
export function startCodexWatch({ store, readThreads = readCodexThreads, pollMs = CODEX_POLL_MS, firstDelayMs = 8_000, count = null } = {}) {
|
|
86
|
+
// Conversations this run has already counted. In memory on purpose, exactly
|
|
87
|
+
// as the Claude side's set is: a fresh launch seeing the same conversation
|
|
88
|
+
// again is a fresh fact about the app being used.
|
|
89
|
+
const seen = new Set();
|
|
26
90
|
const run = () => {
|
|
27
|
-
try { scanCodex({ store, readThreads }); } catch (e) { console.warn('codex watch:', e?.message ?? e); }
|
|
91
|
+
try { scanCodex({ store, readThreads, count, seen }); } catch (e) { console.warn('codex watch:', e?.message ?? e); }
|
|
28
92
|
};
|
|
29
93
|
const first = setTimeout(run, firstDelayMs);
|
|
30
94
|
const timer = setInterval(run, pollMs);
|
package/main/config.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import path from 'node:path';
|
|
|
10
10
|
import os from 'node:os';
|
|
11
11
|
import crypto from 'node:crypto';
|
|
12
12
|
import { resolveClaudeBin, candidatePaths } from './claude-bin.mjs';
|
|
13
|
+
import { warmShellAnswers } from './engine-bin.mjs';
|
|
13
14
|
import { resolveCodexBin } from './codex-bin.mjs';
|
|
14
15
|
import { defaultStoreRoot } from './store/home.mjs';
|
|
15
16
|
import { readPlans, slotsForPlans } from './claude-plan.mjs';
|
|
@@ -63,6 +64,11 @@ const DEFAULTS = {
|
|
|
63
64
|
// many times "Something felt slow" has been pressed, and only ever subtracts.
|
|
64
65
|
agentsAuto: true,
|
|
65
66
|
agentsNudge: 0,
|
|
67
|
+
// THE NUMBER PICKED ON THE MENU, FOR THE WHOLE COMPUTER (w-ec27ff1a51). Null
|
|
68
|
+
// until somebody picks one. It replaced `maxConcurrentSessions` as the thing
|
|
69
|
+
// the menu writes, because that one was per login: two logins turned a 7 into
|
|
70
|
+
// 14 agents on a 16 GB Mac, which then ran out of memory.
|
|
71
|
+
agentsAtOnce: null,
|
|
66
72
|
// HOLD HEAVY WORK WHEN MEMORY IS SHORT (w-3958c3753d). Off until somebody
|
|
67
73
|
// turns it on from the Agents page. With it on, every shell command a worker
|
|
68
74
|
// runs asks main/memory-gate-server.mjs first, and heavy ones wait their turn
|
|
@@ -205,6 +211,14 @@ export function loadConfig(appDir, { home = os.homedir() } = {}) {
|
|
|
205
211
|
config.claudeBinConfigured = typeof overrides.claudeBin === 'string' && overrides.claudeBin
|
|
206
212
|
? overrides.claudeBin
|
|
207
213
|
: null;
|
|
214
|
+
// ONE LOGIN SHELL FOR BOTH ENGINES, ASKED BEFORE EITHER SEARCH RUNS. This is
|
|
215
|
+
// on the boot path -- main/main.mjs calls loadConfig inside createWindow and
|
|
216
|
+
// before the window exists -- and a login shell costs about 0.7 s to start
|
|
217
|
+
// whether it is asked about one program or six. Measured on this Mac
|
|
218
|
+
// 2026-10-11, the two searches together took 1.4 s cold and the shell was all
|
|
219
|
+
// of it. It seeds only what it positively found, so a shell that will not
|
|
220
|
+
// start leaves both searches exactly as they were (main/engine-bin.mjs).
|
|
221
|
+
warmShellAnswers();
|
|
208
222
|
const claude = resolveClaudeBin(config.claudeBinConfigured);
|
|
209
223
|
config.claudeBin = claude.path ?? candidatePaths()[0];
|
|
210
224
|
config.claudeFound = claude.found;
|
|
@@ -281,10 +295,18 @@ export function loadConfig(appDir, { home = os.homedir() } = {}) {
|
|
|
281
295
|
// same thing said out loud and kept up to date. An explicit `agentsAuto`
|
|
282
296
|
// outranks the guess, because turning Automatic back on after setting a number
|
|
283
297
|
// has to be possible.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
298
|
+
//
|
|
299
|
+
// ONLY A NUMBER FOR THE WHOLE COMPUTER IS A CHOICE (w-ec27ff1a51). A
|
|
300
|
+
// `maxConcurrentSessions` in the file was written per login, by the stepper
|
|
301
|
+
// that was here before 5 Oct, and reading it as "you set this" is what ran
|
|
302
|
+
// 14 agents on a Mac Automatic would have run 6 on, with nobody having picked
|
|
303
|
+
// 14. So that install goes back to Automatic, even with `agentsAuto: false`
|
|
304
|
+
// written beside it, and a number picked on today's menu (`agentsAtOnce`)
|
|
305
|
+
// is kept as picked unless Automatic was chosen after it.
|
|
306
|
+
config.agentsAuto = typeof overrides.agentsAtOnce !== 'number' || overrides.agentsAuto === true;
|
|
307
|
+
// And the plan is read whenever the number is Automatic's, old per-login
|
|
308
|
+
// number or not, so a plan that allows one at a time still says so.
|
|
309
|
+
if (typeof overrides.agentsAtOnce !== 'number') {
|
|
288
310
|
const plans = readPlans(effectiveProfiles(config.authProfiles, { home }), { home });
|
|
289
311
|
const slots = slotsForPlans(plans, config.machineSlots);
|
|
290
312
|
config.maxConcurrentSessions = slots;
|