agentbox-app 0.1.6 → 0.1.11
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 +209 -55
- package/bin/agentbox.mjs +6 -0
- package/briefs/message-rules.md +13 -3
- package/briefs/worker.md +63 -49
- package/briefs/writing-rules.md +90 -131
- package/main/account-discovery.mjs +21 -20
- package/main/account-tooling.mjs +9 -10
- package/main/agent-activity.mjs +45 -5
- package/main/agent-schedule.mjs +14 -14
- package/main/agent-sessions.mjs +94 -10
- package/main/agent-updates.mjs +114 -0
- package/main/agents.mjs +52 -46
- package/main/analytics.mjs +27 -0
- package/main/approval-prompt-server.mjs +2 -2
- package/main/artifact-path.mjs +11 -0
- package/main/claude-bin.mjs +31 -5
- package/main/claude-input.mjs +45 -3
- package/main/claude-plan.mjs +11 -11
- package/main/code-change.mjs +3 -3
- package/main/codex-account.mjs +1 -1
- package/main/codex-bin.mjs +17 -0
- package/main/codex-memory-gate.mjs +260 -0
- package/main/codex-models.mjs +12 -7
- package/main/codex-threads.mjs +46 -23
- package/main/codex-usage-file.mjs +7 -7
- package/main/codex-usage.mjs +4 -4
- package/main/codex-watch.mjs +12 -12
- package/main/config.mjs +68 -8
- package/main/dev-window.mjs +9 -9
- package/main/doc-scheme.mjs +2 -0
- package/main/engine-setup.mjs +193 -0
- package/main/feedback.mjs +55 -0
- package/main/folder-pictures.mjs +48 -0
- package/main/frame-scrollbars.mjs +90 -0
- package/main/fresh-user.mjs +6 -3
- package/main/git-change-offthread.mjs +70 -0
- package/main/git-change-thread.mjs +11 -0
- package/main/git-change.mjs +1 -1
- package/main/img-scheme.mjs +70 -1
- package/main/instruction-settings.mjs +92 -54
- package/main/ipc.mjs +316 -42
- package/main/leftovers.mjs +518 -0
- package/main/live-replies.mjs +43 -5
- package/main/machine.mjs +47 -0
- package/main/main.mjs +119 -15
- package/main/memory-gate-history.mjs +254 -0
- package/main/memory-gate-server.mjs +461 -0
- package/main/memory-gate.mjs +206 -0
- package/main/message-priority.mjs +87 -0
- package/main/rail-note.mjs +2 -2
- package/main/refresh-agent-models.mjs +20 -0
- package/main/repeats.mjs +4 -4
- package/main/row-label.mjs +114 -51
- package/main/serve.mjs +67 -5
- package/main/settings.mjs +223 -26
- package/main/ship-queue.mjs +279 -0
- package/main/sign-in-files.mjs +39 -0
- package/main/source-updater.mjs +298 -0
- package/main/store/home.mjs +47 -2
- package/main/store/work-items.mjs +150 -6
- package/main/store.mjs +235 -9
- package/main/supervisor.mjs +1444 -406
- package/main/task-commands.mjs +8 -10
- package/main/task-folders.mjs +74 -13
- package/main/task-remote-control.mjs +3 -3
- package/main/task-terminals.mjs +2 -1
- package/main/team/index.mjs +440 -0
- package/main/team/memory-cloud.mjs +247 -0
- package/main/team/projects.mjs +162 -0
- package/main/team/session.mjs +225 -0
- package/main/team/sign-in.mjs +106 -0
- package/main/team/supabase-backend.mjs +231 -0
- package/main/team/sync.mjs +163 -0
- package/main/updater.mjs +15 -5
- package/main/write-before-reload.mjs +50 -0
- package/mcp/core/page.mjs +9 -11
- package/mcp/core/work.mjs +91 -25
- package/mcp/tools.mjs +12 -6
- package/package.json +21 -9
- package/renderer/dist/assets/agentbox-icon-F1oN9nj_.svg +15 -0
- package/renderer/dist/assets/index-B-2Az952.css +32 -0
- package/renderer/dist/assets/index-D6qncgai.js +1168 -0
- package/renderer/dist/index.html +2 -2
- package/scripts/memory-gate-hook.sh +59 -0
- package/shared/agent-import.mjs +38 -15
- package/shared/agents.d.mts +26 -8
- package/shared/agents.mjs +100 -108
- package/shared/analytics-events.mjs +19 -2
- package/shared/answers.mjs +12 -0
- package/shared/bridge-map.d.mts +12 -0
- package/shared/bridge-map.mjs +37 -1
- package/shared/claude-commands.generated.mjs +2 -2
- package/shared/claude-models.generated.mjs +4 -4
- package/shared/claude-usage.mjs +6 -6
- package/shared/codex-import.mjs +7 -7
- package/shared/codex-modes.d.mts +50 -0
- package/shared/codex-usage.mjs +7 -8
- package/shared/demo-world.mjs +5 -5
- package/shared/engines.mjs +27 -10
- package/shared/feedback.d.mts +14 -0
- package/shared/feedback.mjs +44 -0
- package/shared/first-run-examples.mjs +4 -4
- package/shared/first-run-practice.d.mts +16 -0
- package/shared/first-run-practice.mjs +176 -70
- package/shared/fresh-user-home.mjs +22 -3
- package/shared/instruction-defaults.json +2 -2
- package/shared/product-name.mjs +4 -2
- package/shared/project-folder-check.mjs +6 -0
- package/shared/rank.d.mts +1 -0
- package/shared/rank.mjs +22 -0
- package/shared/referenced-files.mjs +1 -1
- package/shared/schemes.d.mts +11 -0
- package/shared/schemes.mjs +1 -1
- package/shared/shortcuts.d.mts +41 -0
- package/shared/shortcuts.mjs +236 -0
- package/shared/spawn-trouble.d.mts +2 -0
- package/shared/spawn-trouble.mjs +39 -18
- package/shared/team-rules.d.mts +17 -0
- package/shared/team-rules.mjs +230 -0
- package/shared/team-status.d.mts +11 -0
- package/shared/team-status.mjs +90 -0
- package/shared/terminal-links.d.mts +2 -0
- package/shared/terminal-links.mjs +55 -0
- package/shared/terminal-state.d.mts +1 -0
- package/shared/terminal-state.mjs +16 -0
- package/shared/thread-cards.d.mts +19 -0
- package/shared/thread-cards.mjs +254 -0
- package/shared/usage.d.mts +1 -1
- package/shared/usage.mjs +6 -7
- package/shared/work-items.mjs +255 -27
- package/shared/work-lines.mjs +393 -16
- package/worker-permissions.json +1 -1
- package/renderer/dist/assets/cel-dusk-CS5I7kJS.webp +0 -0
- package/renderer/dist/assets/cel-dusk-DUslqS6B.webp +0 -0
- package/renderer/dist/assets/cel-dusk-Jt_xVGxp.webp +0 -0
- package/renderer/dist/assets/cel-dusk-sm-BBkV-tWT.webp +0 -0
- package/renderer/dist/assets/frost-haze-CxPSOBEg.webp +0 -0
- package/renderer/dist/assets/frost-haze-sm-C_w21qgd.webp +0 -0
- package/renderer/dist/assets/ice-BC8luINc.webp +0 -0
- package/renderer/dist/assets/ice-CP9PrKHo.webp +0 -0
- package/renderer/dist/assets/ice-river-D6ziOhLY.webp +0 -0
- package/renderer/dist/assets/ice-river-UVM2-JY_.webp +0 -0
- package/renderer/dist/assets/ice-river-bErJwYWW.webp +0 -0
- package/renderer/dist/assets/ice-river-sm-Bg1U-SIj.webp +0 -0
- package/renderer/dist/assets/ice-sm-CbVcg3_D.webp +0 -0
- package/renderer/dist/assets/index-CwiDv5bL.css +0 -32
- package/renderer/dist/assets/index-D9hCAVPl.js +0 -684
- package/renderer/dist/assets/lake-C0WaW-6E.webp +0 -0
- package/renderer/dist/assets/lake-CSKjLC5K.webp +0 -0
- package/renderer/dist/assets/lake-ETGcOb25.webp +0 -0
- package/renderer/dist/assets/lake-harbour-BCQRbrHb.webp +0 -0
- package/renderer/dist/assets/lake-harbour-B_wfXTmE.webp +0 -0
- package/renderer/dist/assets/lake-harbour-Te1pEvBh.webp +0 -0
- package/renderer/dist/assets/lake-harbour-sm-Cme_ShEg.webp +0 -0
- package/renderer/dist/assets/lake-sm-CbG72Hbv.webp +0 -0
- package/renderer/dist/assets/lino-coast-CUZHcucI.webp +0 -0
- package/renderer/dist/assets/lino-coast-DP4J7cgd.webp +0 -0
- package/renderer/dist/assets/lino-coast-kOIAffr_.webp +0 -0
- package/renderer/dist/assets/lino-coast-sm-C1Mdbgit.webp +0 -0
- package/renderer/dist/assets/moon-pines-BskgnnXR.webp +0 -0
- package/renderer/dist/assets/moon-pines-CFSIIwt0.webp +0 -0
- package/renderer/dist/assets/moon-pines-PNhdOYww.webp +0 -0
- package/renderer/dist/assets/moon-pines-sm-C6K1NomK.webp +0 -0
- package/renderer/dist/assets/orbit-rings-2XV36Ch3.webp +0 -0
- package/renderer/dist/assets/orbit-rings-C4NrcYGQ.webp +0 -0
- package/renderer/dist/assets/orbit-rings-DGLk07dT.webp +0 -0
- package/renderer/dist/assets/orbit-rings-sm-Dky3YdvL.webp +0 -0
- package/renderer/dist/assets/peach-haze-2-C6f7xnWw.webp +0 -0
- package/renderer/dist/assets/peach-haze-2-sm-Dj_Ez_Sb.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-DQjqMROg.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-Dw71H_bc.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-kOZfm-GM.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-sm-Dturjuky.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-C296JVMO.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-C4oMRDSx.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-CqVmQ4eh.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-sm-CHagC_nO.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-C6LYu6uE.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-CIUCEESs.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-sm-BnE0F_w-.webp +0 -0
- package/renderer/dist/assets/riso-hills-7heKVtry.webp +0 -0
- package/renderer/dist/assets/riso-hills-CIjAL3J6.webp +0 -0
- package/renderer/dist/assets/riso-hills-sm-DHsnbCfP.webp +0 -0
- package/renderer/dist/assets/slate-haze-BPWdkNsw.webp +0 -0
- package/renderer/dist/assets/slate-haze-sm-B0Cb5a2W.webp +0 -0
- package/renderer/dist/assets/valley-haze-BDHPqoJy.webp +0 -0
- package/renderer/dist/assets/valley-haze-sm-CTyjmPAG.webp +0 -0
- package/renderer/dist/assets/vector-dunes-DzzWLdKo.webp +0 -0
- package/renderer/dist/assets/vector-dunes-Yr9NBQUy.webp +0 -0
- package/renderer/dist/assets/vector-dunes-sm-D8MYY6ms.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-BCGKN4u4.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-BF-jk5MC.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-sm-Ds1xzTw9.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-B1um67Z9.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-DKQdYs32.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-sm-BJzNq8xh.webp +0 -0
- package/renderer/dist/emulator/audio-rules.mjs +0 -74
- package/renderer/dist/emulator/data/compression/README.md +0 -11
- package/renderer/dist/emulator/data/compression/extract7z.js +0 -1
- package/renderer/dist/emulator/data/compression/extractzip.js +0 -1
- package/renderer/dist/emulator/data/compression/libunrar.js +0 -519
- package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
- package/renderer/dist/emulator/data/cores/README.md +0 -22
- 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 +0 -65
- package/renderer/dist/emulator/data/cores/reports/mgba.json +0 -1
- package/renderer/dist/emulator/data/emulator.css +0 -1614
- package/renderer/dist/emulator/data/loader.js +0 -170
- package/renderer/dist/emulator/data/localization/README.md +0 -63
- package/renderer/dist/emulator/data/localization/af-FR.json +0 -339
- package/renderer/dist/emulator/data/localization/ar-AR.json +0 -301
- package/renderer/dist/emulator/data/localization/ben-BEN.json +0 -301
- package/renderer/dist/emulator/data/localization/de-GER.json +0 -301
- package/renderer/dist/emulator/data/localization/el-GR.json +0 -301
- package/renderer/dist/emulator/data/localization/en-US.json +0 -339
- package/renderer/dist/emulator/data/localization/es-ES.json +0 -310
- package/renderer/dist/emulator/data/localization/fa-AF.json +0 -310
- package/renderer/dist/emulator/data/localization/hi-HI.json +0 -301
- package/renderer/dist/emulator/data/localization/it-IT.json +0 -302
- package/renderer/dist/emulator/data/localization/ja-JA.json +0 -301
- package/renderer/dist/emulator/data/localization/jv-JV.json +0 -301
- package/renderer/dist/emulator/data/localization/ko-KO.json +0 -336
- package/renderer/dist/emulator/data/localization/pt-BR.json +0 -361
- package/renderer/dist/emulator/data/localization/retroarch.json +0 -617
- package/renderer/dist/emulator/data/localization/ro-RO.json +0 -310
- package/renderer/dist/emulator/data/localization/ru-RU.json +0 -301
- package/renderer/dist/emulator/data/localization/tr-TR.json +0 -310
- package/renderer/dist/emulator/data/localization/vi-VN.json +0 -302
- package/renderer/dist/emulator/data/localization/zh-CN.json +0 -351
- package/renderer/dist/emulator/data/src/GameManager.js +0 -464
- package/renderer/dist/emulator/data/src/compression.js +0 -142
- package/renderer/dist/emulator/data/src/emulator.js +0 -6158
- package/renderer/dist/emulator/data/src/gamepad.js +0 -167
- package/renderer/dist/emulator/data/src/nipplejs.js +0 -1
- package/renderer/dist/emulator/data/src/shaders.js +0 -236
- package/renderer/dist/emulator/data/src/socket.io.min.js +0 -7
- package/renderer/dist/emulator/data/src/storage.js +0 -125
- package/renderer/dist/emulator/data/version.json +0 -5
- package/renderer/dist/emulator/host.js +0 -299
- package/renderer/dist/emulator/index.html +0 -28
- package/renderer/dist/emulator/provenance.json +0 -6
package/main/ipc.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { openSourceFile } from './open-source-file.mjs';
|
|
2
2
|
import {submitReply} from './live-replies.mjs';
|
|
3
|
+
import { createEngineSetup } from './engine-setup.mjs';
|
|
3
4
|
// IPC: a thin, typed-by-convention surface. The renderer asks; the main
|
|
4
5
|
// process derives from files and answers. Pushes go one way: "state changed,
|
|
5
6
|
// refetch." No state is cached on either side of the bridge.
|
|
@@ -21,14 +22,19 @@ import {submitReply} from './live-replies.mjs';
|
|
|
21
22
|
import {terminalSenderAllowed} from './terminal-access.mjs';
|
|
22
23
|
import {TaskTerminals,terminalPlace} from './task-terminals.mjs';
|
|
23
24
|
import {SETTINGS_TERMINAL} from '../shared/settings-terminal.mjs';
|
|
25
|
+
import {AgentUpdates,installedAgent} from './agent-updates.mjs';
|
|
26
|
+
import {refreshAgentModels} from './refresh-agent-models.mjs';
|
|
24
27
|
import {readInstruction,writeInstruction,listVersions,readVersion} from './instruction-settings.mjs';
|
|
25
28
|
import path from 'node:path';
|
|
26
29
|
import os from 'node:os';
|
|
27
30
|
import fsWatch from 'node:fs';
|
|
28
31
|
import { listFolders } from './folders.mjs';
|
|
32
|
+
import { feedbackUrl, sendFeedback } from './feedback.mjs';
|
|
33
|
+
import { feedbackPayload } from '../shared/feedback.mjs';
|
|
29
34
|
import * as approvals from './approvals.mjs';
|
|
30
35
|
import { restartNeeded } from './staleness.mjs';
|
|
31
36
|
import { artifactRoots, resolveArtifact } from './artifact-path.mjs';
|
|
37
|
+
import { folderPictures } from './folder-pictures.mjs';
|
|
32
38
|
import { docUrl } from './doc-scheme.mjs';
|
|
33
39
|
import { readDoc, writeDoc } from './doc-file.mjs';
|
|
34
40
|
import { readChange } from './code-change.mjs';
|
|
@@ -43,6 +49,7 @@ import * as agents from './agents.mjs';
|
|
|
43
49
|
import { findAgentFolders, readAgentFiles, readFolderAgents } from './agent-files.mjs';
|
|
44
50
|
import { readSessionThreads } from './agent-sessions.mjs';
|
|
45
51
|
import { readCodexThreads } from './codex-threads.mjs';
|
|
52
|
+
import { markImported } from '../shared/agent-import.mjs';
|
|
46
53
|
import { codexIdOf, importChoice, isCodexImportRow, isNotImportedRow } from '../shared/codex-import.mjs';
|
|
47
54
|
import { checkProjectFolder } from '../shared/project-folder-check.mjs';
|
|
48
55
|
import { openFreshUser } from './fresh-user.mjs';
|
|
@@ -52,6 +59,7 @@ import { agentKey } from '../shared/agents.mjs';
|
|
|
52
59
|
import { machineryPath } from './store/home.mjs';
|
|
53
60
|
import { EVENT_NAMES } from '../shared/analytics-events.mjs';
|
|
54
61
|
import { Name } from '../shared/product-name.mjs';
|
|
62
|
+
import { handedOnByReply } from '../shared/team-rules.mjs';
|
|
55
63
|
|
|
56
64
|
// WHAT A WORKER IS TOLD WHEN HER DECISION COULD NOT BE RECORDED.
|
|
57
65
|
const SPOOL_REFUSED = `${Name} could not record the founder's decision, so nothing was allowed. Finish what your grants cover and file the rest as a review.`;
|
|
@@ -85,7 +93,7 @@ const NO_UPDATER = {
|
|
|
85
93
|
install: () => false,
|
|
86
94
|
};
|
|
87
95
|
|
|
88
|
-
export function registerIpc({ store, supervisor, config, window, analytics = NO_ANALYTICS, docGrants = null, updater = NO_UPDATER, host }) {
|
|
96
|
+
export function registerIpc({ store, supervisor, config, window, analytics = NO_ANALYTICS, docGrants = null, updater = NO_UPDATER, host, team = null }) {
|
|
89
97
|
// The three Electron things, or a plain Node stand-in for them. There is no
|
|
90
98
|
// default: a caller that forgets says so here rather than throwing eighty
|
|
91
99
|
// lines further down on `ipcMain.handle` of undefined.
|
|
@@ -123,7 +131,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
123
131
|
// Allow/Deny Pending Approval in the Agents menu. All three go through here
|
|
124
132
|
// so all three tell the renderer what was just decided: without that, the
|
|
125
133
|
// card only ever "disappeared" on the next store push and nothing on screen
|
|
126
|
-
// acknowledged the press
|
|
134
|
+
// acknowledged the press. The card's exit is drawn
|
|
127
135
|
// from this.
|
|
128
136
|
//
|
|
129
137
|
// THE MENU WAS NOT ONE OF THE THREE UNTIL NOW, AND THIS PARAGRAPH SAID IT
|
|
@@ -196,12 +204,19 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
196
204
|
// and attaches the properties itself. This is the reason there is no
|
|
197
205
|
// analytics SDK in the renderer at all (a test fails the build if one
|
|
198
206
|
// appears): a browser SDK autocaptures the text of what was clicked, and in
|
|
199
|
-
// Agentbox that text is task titles.
|
|
200
|
-
|
|
207
|
+
// Agentbox that text is task titles. A NUMBER MAY RIDE WITH THE NAME, and only
|
|
208
|
+
// a number (2026-10-06): which setup step was reached.
|
|
209
|
+
ipcMain.handle('zero:track', (_e, { name, count } = {}) => {
|
|
201
210
|
if (!EVENT_NAMES.includes(name)) return false;
|
|
202
|
-
return analytics.track(name);
|
|
211
|
+
return analytics.track(name, Number.isFinite(count) ? { count } : undefined);
|
|
203
212
|
});
|
|
204
213
|
|
|
214
|
+
// WHETHER A COUNT CAME FROM SETUP'S PRACTICE PROJECT, read off the project's
|
|
215
|
+
// own flag rather than its name, so the walkthrough and real use part.
|
|
216
|
+
const isPractice = (slug) => {
|
|
217
|
+
try { return store.listProducts().some((p) => p.slug === slug && p.practice === true); } catch { return false; }
|
|
218
|
+
};
|
|
219
|
+
|
|
205
220
|
ipcMain.handle('zero:approve', (_e, { id, allow, note }) => {
|
|
206
221
|
return { ok: answerApproval(id, allow, note) };
|
|
207
222
|
});
|
|
@@ -304,17 +319,32 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
304
319
|
const agent=id.startsWith('agent:')?liveAgents().find(a=>'agent:'+a.pid===id && (a.product??'')===product):null;
|
|
305
320
|
return terminalPlace({store,supervisor,product,id,agent});
|
|
306
321
|
}});
|
|
322
|
+
const agentUpdates=new AgentUpdates({
|
|
323
|
+
installation:async engine=>{if(engine==='claude')recheckClaude(config);else recheckCodex(config);return installedAgent(engine,config);},
|
|
324
|
+
refresh:async engine=>{if(engine==='claude')recheckClaude(config);else recheckCodex(config);await refreshAgentModels(engine,config,supervisor._codexHome());if(engine==='codex')for(const profile of supervisor._codexProfiles())supervisor._releaseIdleCodex(profile);},
|
|
325
|
+
});
|
|
326
|
+
ipcMain.handle('zero:agent-update',async(event,{engine,action}={})=>{
|
|
327
|
+
if(event.senderFrame?.parent||(event.sender&&event.sender!==window.webContents))throw Error('Updates are only available in the main app window.');
|
|
328
|
+
if(!['claude','codex'].includes(engine))throw Error('Unknown agent.');
|
|
329
|
+
if(action==='check')return agentUpdates.check(engine);
|
|
330
|
+
if(action==='recheck')return agentUpdates.check(engine,true);
|
|
331
|
+
if(action==='start')return agentUpdates.start(engine);
|
|
332
|
+
if(action==='status')return agentUpdates.status(engine);
|
|
333
|
+
if(action==='refresh'){await refreshAgentModels(engine,config,supervisor._codexHome());return true;}
|
|
334
|
+
throw Error('Unknown update action.');
|
|
335
|
+
});
|
|
307
336
|
let terminalQuitPending=false,terminalQuitReady=false;
|
|
308
337
|
app.on('before-quit',event=>{
|
|
309
|
-
if(terminalQuitReady||(!terminalQuitPending&&!terminals.sessions.size))return;
|
|
338
|
+
if(terminalQuitReady||(!terminalQuitPending&&!terminals.sessions.size&&!agentUpdates.terminals.sessions.size))return;
|
|
310
339
|
event.preventDefault();
|
|
311
|
-
if(!terminalQuitPending){terminalQuitPending=true;void terminals.shutdown().finally(()=>{terminalQuitReady=true;app.quit();});}
|
|
340
|
+
if(!terminalQuitPending){terminalQuitPending=true;void Promise.all([terminals.shutdown(),agentUpdates.shutdown()]).finally(()=>{terminalQuitReady=true;app.quit();});}
|
|
312
341
|
});
|
|
313
342
|
ipcMain.handle('zero:terminal',(_event,payload={})=>{
|
|
314
343
|
if(_event.senderFrame?.parent || (_event.sender && _event.sender!==window.webContents)) throw Error('Terminal is only available in the main app window.');
|
|
315
344
|
const {product,id,action,data,cols,rows,offset}=payload;
|
|
316
345
|
if(typeof product!=='string'||typeof id!=='string'||!id||id.length>300)throw Error('Invalid task.');
|
|
317
346
|
const key=JSON.stringify({product,id});
|
|
347
|
+
if(product==='@agent-update')return agentUpdates.terminal(id,payload);
|
|
318
348
|
switch(action){
|
|
319
349
|
case 'open':return terminals.open(key);
|
|
320
350
|
case 'read':return terminals.read(key,offset);
|
|
@@ -325,6 +355,11 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
325
355
|
}
|
|
326
356
|
});
|
|
327
357
|
|
|
358
|
+
// The Done and All tabs' next page of finished threads the snapshot's 8 MB
|
|
359
|
+
// read left out, asked for as she scrolls to their foot (main/store.mjs).
|
|
360
|
+
ipcMain.handle('zero:older-items', (_e, { offset = 0, limit = 50 } = {}) =>
|
|
361
|
+
store.listOlderItems({ offset: Number(offset) || 0, limit: Math.min(Number(limit) || 50, 200) }));
|
|
362
|
+
|
|
328
363
|
ipcMain.handle('zero:snapshot', () => {
|
|
329
364
|
const now = Date.now();
|
|
330
365
|
const items = store.listItems(now);
|
|
@@ -345,6 +380,10 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
345
380
|
return {
|
|
346
381
|
products: store.listProducts(),
|
|
347
382
|
items,
|
|
383
|
+
// THE TEAM: who is signed in, their team and its people, and the
|
|
384
|
+
// title-free activity of their private work. Null on a build with no
|
|
385
|
+
// team cloud, which is the single-person app.
|
|
386
|
+
team: team ? team.state() : null,
|
|
348
387
|
agents: liveAgents(),
|
|
349
388
|
approvals: approvals.listPending(config.storeRoot),
|
|
350
389
|
supervisor: running,
|
|
@@ -414,23 +453,22 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
414
453
|
};
|
|
415
454
|
});
|
|
416
455
|
|
|
417
|
-
//
|
|
456
|
+
// THE USER'S WORDS, INTO ONE OF THEIR LIVE AGENTS. The only thing in the app that writes
|
|
418
457
|
// anywhere outside its own store, and it writes into a socket rather than to
|
|
419
458
|
// disk. The reader refuses a send to an agent frozen on a permission box, and
|
|
420
459
|
// reports back whether the agent actually moved rather than assuming it did.
|
|
421
460
|
ipcMain.handle('zero:agent-reply', async (_e, { pid, text }) => {
|
|
422
461
|
try {
|
|
423
462
|
const out = await agents.reply(Number(pid), text);
|
|
424
|
-
// AND THE ROW REMEMBERS THAT
|
|
463
|
+
// AND THE ROW REMEMBERS THAT THE USER SPOKE. Only on a delivered message: a
|
|
425
464
|
// mark written for a send that failed would move the row into In progress
|
|
426
465
|
// over words that never arrived, which is the failure this codebase cares
|
|
427
|
-
// about most.It does now, until the session stops. AND WHAT
|
|
428
|
-
// not only that
|
|
429
|
-
// socket as machine chatter (`isMeta`), so the transcript reader drops
|
|
430
|
-
// and
|
|
431
|
-
//
|
|
432
|
-
//
|
|
433
|
-
// she said it, so this is where it is written.
|
|
466
|
+
// about most. It does now, until the session stops. AND WHAT THE USER
|
|
467
|
+
// TYPED, not only that they typed. Claude Code marks a message arriving over
|
|
468
|
+
// its socket as machine chatter (`isMeta`), so the transcript reader drops
|
|
469
|
+
// it and the user's own sentence never reached the card: the row went on
|
|
470
|
+
// saying only what the session was started with days before. This is the
|
|
471
|
+
// only process that knows the user said it, so this is where it is written.
|
|
434
472
|
if (out?.ok && out.key) agentSchedule.replied(out.key, out.at ?? Date.now(), Date.now(), String(text ?? ''));
|
|
435
473
|
push();
|
|
436
474
|
return out;
|
|
@@ -491,8 +529,17 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
491
529
|
ipcMain.handle('zero:compact', (_e, { product, id }) => supervisor.compactItem(product, id));
|
|
492
530
|
ipcMain.handle('zero:compaction-status', (_e, { product, id }) => supervisor.compactionStatus(product, id));
|
|
493
531
|
|
|
494
|
-
|
|
532
|
+
// Send now: cut the running agent's current step so her waiting message is
|
|
533
|
+
// answered at once (supervisor.sendNow).
|
|
534
|
+
ipcMain.handle('zero:send-now', (_e, { product, id }) => supervisor.sendNow(product, id));
|
|
535
|
+
|
|
536
|
+
ipcMain.handle('zero:answer', async (_e, { product, id, answer, status, priority, permissionMode, model, effort, now, inReplyTo }) => {
|
|
495
537
|
if (isAgentRow(id)) return { ok: false };
|
|
538
|
+
// A REPLY IN A THREAD (w-920461cbe6) only means something in a conversation
|
|
539
|
+
// between people, which is the only place a thread is drawn and the only
|
|
540
|
+
// record the mark crosses to a teammate in (shared/team-rules.mjs). Anywhere
|
|
541
|
+
// else it is dropped and the words go in as an ordinary reply.
|
|
542
|
+
const thread = typeof inReplyTo === 'string' && inReplyTo && productOf(product)?.team?.direct ? inReplyTo : undefined;
|
|
496
543
|
// A CODEX CONVERSATION'S YES OR NO NEVER REACHES A WORKER. The row asking
|
|
497
544
|
// whether to import one is answered by the app itself: yes makes it a row
|
|
498
545
|
// she can read, no parks it in Closed under Not imported, and Import on a
|
|
@@ -524,11 +571,15 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
524
571
|
// handing them over in one call and writing the model afterwards would be a
|
|
525
572
|
// race the run usually wins, so she would pick a model and watch the old one
|
|
526
573
|
// take the task. `answerItem` writes both, and it writes the model first.
|
|
527
|
-
const out = await submitReply(supervisor,{product,id,answer,status,permissionMode},()=>store.answerItem(product, id, { answer, status, priority, permissionMode, model, effort }));
|
|
528
|
-
//
|
|
574
|
+
const out = await submitReply(supervisor,{product,id,answer,status,permissionMode,now:!!now},()=>store.answerItem(product, id, { answer, status, priority, permissionMode, model, effort, inReplyTo: thread }));
|
|
575
|
+
// A REPLY ON A ROW A TEAMMATE GAVE YOU HANDS IT BACK TO THEM, so the
|
|
576
|
+
// conversation moves to their inbox instead of sitting in both
|
|
577
|
+
// (shared/team-rules.mjs handedOnByReply). Archiving hands nothing on.
|
|
578
|
+
if (team && status !== 'done' && typeof answer === 'string' && answer.trim()) handOnByReply(product, id);
|
|
579
|
+
// THAT the user replied and THAT something finished. Never a word of either:
|
|
529
580
|
// in Agentbox the answer IS the prompt (privacy page, section 4).
|
|
530
|
-
if (typeof answer === 'string' && answer.trim()) analytics.track('reply_sent');
|
|
531
|
-
if (status === 'done') analytics.track('task_finished');
|
|
581
|
+
if (typeof answer === 'string' && answer.trim()) analytics.track('reply_sent', { practice: isPractice(product) });
|
|
582
|
+
if (status === 'done') analytics.track('task_finished', { practice: isPractice(product) });
|
|
532
583
|
// HER REPLY IS THE OTHER HALF OF THE SAME WAIT, and it is the one she pays
|
|
533
584
|
// on every round trip rather than once: measured 2026-08-24, median 9.1
|
|
534
585
|
// seconds between pressing the key and the continuation being spawned.
|
|
@@ -555,13 +606,141 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
555
606
|
return supervisor.status();
|
|
556
607
|
});
|
|
557
608
|
|
|
558
|
-
|
|
609
|
+
// WHO IS SIGNED IN, and the project a row is in, for the team's rules.
|
|
610
|
+
const teamMe = () => team?.state()?.me?.id ?? null;
|
|
611
|
+
const productOf = (slug) => store.listProducts().find((p) => p.slug === slug) ?? null;
|
|
612
|
+
|
|
613
|
+
function handOnByReply(product, id) {
|
|
614
|
+
try {
|
|
615
|
+
const row = store.readItem(product, id);
|
|
616
|
+
const next = row && handedOnByReply(row, productOf(product), teamMe());
|
|
617
|
+
if (next) store.teamPatch(product, id, { assignee: next });
|
|
618
|
+
} catch (err) {
|
|
619
|
+
console.warn(`team: could not hand ${id} on: ${err.message}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// FEEDBACK, from the card the sidebar's Feedback row opens (w-1b574413db).
|
|
624
|
+
// main/feedback.mjs decides where it goes and whether it may; the version
|
|
625
|
+
// and the macOS release ride along so a report can be matched to a build.
|
|
626
|
+
ipcMain.handle('zero:send-feedback', async (_e, { text, files } = {}) => sendFeedback({
|
|
627
|
+
url: feedbackUrl(config),
|
|
628
|
+
payload: feedbackPayload({ text, files, app: { version: app.getVersion(), os: `${process.platform} ${os.release()}` } }),
|
|
629
|
+
}));
|
|
630
|
+
|
|
631
|
+
// THE TEAM'S DOORS. Each answers { ok, team } or { ok: false, error } with
|
|
632
|
+
// the error in words, so the window can say what went wrong where it
|
|
633
|
+
// happened instead of a row silently not appearing.
|
|
634
|
+
const teamCall = (fn) => async (_e, arg) => {
|
|
635
|
+
if (!team) return { ok: false, error: 'This build has no team cloud set up.' };
|
|
636
|
+
try {
|
|
637
|
+
await fn(arg ?? {});
|
|
638
|
+
push();
|
|
639
|
+
return { ok: true, team: team.state() };
|
|
640
|
+
} catch (err) {
|
|
641
|
+
return { ok: false, error: String(err?.message ?? err), team: team.state() };
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
// Signing in finishes in the browser, so the app comes back to the front
|
|
645
|
+
// when it lands rather than leaving the person to find it.
|
|
646
|
+
ipcMain.handle('zero:team-sign-in', teamCall(async () => {
|
|
647
|
+
await team.signIn();
|
|
648
|
+
try { if (window?.isMinimized?.()) window.restore(); window?.show?.(); app?.focus?.({ steal: true }); } catch { /* no window to raise */ }
|
|
649
|
+
}));
|
|
650
|
+
ipcMain.handle('zero:team-sign-in-cancel', teamCall(() => team.cancelSignIn()));
|
|
651
|
+
ipcMain.handle('zero:team-sign-in-reopen', teamCall(() => team.reopenSignIn()));
|
|
652
|
+
ipcMain.handle('zero:team-sign-out', teamCall(() => team.signOut()));
|
|
653
|
+
ipcMain.handle('zero:team-sign-in-email', teamCall(({ email, password }) => team.signInWithEmail(email, password)));
|
|
654
|
+
// Its own door, because it has one more thing to say: whether the account
|
|
655
|
+
// waits on the link in its confirmation email.
|
|
656
|
+
ipcMain.handle('zero:team-sign-up', async (_e, { email, password, name } = {}) => {
|
|
657
|
+
if (!team) return { ok: false, error: 'This build has no team cloud set up.' };
|
|
658
|
+
try {
|
|
659
|
+
const out = await team.signUp(email, password, name);
|
|
660
|
+
push();
|
|
661
|
+
return { ok: true, team: team.state(), confirm: !!out?.confirm };
|
|
662
|
+
} catch (err) {
|
|
663
|
+
return { ok: false, error: String(err?.message ?? err), team: team.state() };
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
ipcMain.handle('zero:team-create', teamCall(({ name }) => team.createTeam(name)));
|
|
667
|
+
// An invite answered yes. Nothing else joins a team.
|
|
668
|
+
ipcMain.handle('zero:team-accept-invite', teamCall(({ teamId }) => team.acceptInvite(teamId)));
|
|
669
|
+
ipcMain.handle('zero:team-invite', teamCall(({ email }) => team.invite(email)));
|
|
670
|
+
ipcMain.handle('zero:team-rename', teamCall(({ name }) => team.renameTeam(name)));
|
|
671
|
+
ipcMain.handle('zero:team-remove-member', teamCall(({ personId }) => team.removeMember(personId)));
|
|
672
|
+
ipcMain.handle('zero:team-leave', teamCall(() => team.leaveTeam()));
|
|
673
|
+
ipcMain.handle('zero:team-cancel-invite', teamCall(({ email }) => team.cancelInvite(email)));
|
|
674
|
+
// A line you write about yourself, so a day in meetings is not read off
|
|
675
|
+
// the Team page as idleness.
|
|
676
|
+
ipcMain.handle('zero:team-status', teamCall(({ text, hold, until }) => team.setStatus({ text, hold, until })));
|
|
677
|
+
ipcMain.handle('zero:team-share', teamCall(({ product, visibility, people }) => team.share(product, { visibility, people })));
|
|
678
|
+
ipcMain.handle('zero:team-sync', teamCall(() => team.syncNow()));
|
|
679
|
+
// A MESSAGE TO A PERSON (approved 2026-10-01: people get messages, never
|
|
680
|
+
// tasks). main/team/index.mjs keeps it between the two of them.
|
|
681
|
+
ipcMain.handle('zero:team-message', teamCall(({ to, body, priority }) => team.message(to, body, priority)));
|
|
682
|
+
// AN EDIT TO A THREAD, MADE IN PLACE from its summary: the summary's lines,
|
|
683
|
+
// who sees it, its priority and what it is linked to. Works with or without a
|
|
684
|
+
// team, since every thread has a summary.
|
|
685
|
+
ipcMain.handle('zero:thread-edit', (_e, { product, id, patch }) => {
|
|
686
|
+
try {
|
|
687
|
+
store.threadEdit(product, id, patch);
|
|
688
|
+
push();
|
|
689
|
+
return { ok: true };
|
|
690
|
+
} catch (err) {
|
|
691
|
+
return { ok: false, error: String(err?.message ?? err) };
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
// A TASK A TEAMMATE GAVE YOU, ROUTED WITH ONE KEY. To an agent: it runs on
|
|
695
|
+
// your Mac (you become its runner). Keep it: it stays yours and the routing
|
|
696
|
+
// choices go away. Hand it back: it returns to whoever gave it to you.
|
|
697
|
+
ipcMain.handle('zero:team-route', teamCall(({ product, id, route }) => {
|
|
698
|
+
const me = teamMe();
|
|
699
|
+
if (!me) throw new Error('sign in first');
|
|
700
|
+
const row = store.readItem(product, id);
|
|
701
|
+
if (!row) throw new Error('that task is gone');
|
|
702
|
+
if (row.assignee !== me) throw new Error('this task is not yours to route');
|
|
703
|
+
if (route === 'agent') store.teamPatch(product, id, { assignee: 'agent', runner: me });
|
|
704
|
+
else if (route === 'me') store.teamPatch(product, id, { assignee: me });
|
|
705
|
+
else if (route === 'back') {
|
|
706
|
+
const to = (row.people ?? []).find((p) => p !== me) || row.createdBy;
|
|
707
|
+
if (!to || to === me) throw new Error('there is nobody to hand it back to');
|
|
708
|
+
store.teamPatch(product, id, { assignee: to });
|
|
709
|
+
} else throw new Error(`unknown route ${route}`);
|
|
710
|
+
supervisor.wake();
|
|
711
|
+
}));
|
|
712
|
+
|
|
713
|
+
// PUT A REACTION ON ONE MESSAGE, OR TAKE YOURS BACK (w-560647d4db). Only in a
|
|
714
|
+
// conversation between people, because a chip is only ever drawn in one: the
|
|
715
|
+
// sync carries a reaction across in a message record and nowhere else
|
|
716
|
+
// (shared/team-rules.mjs), so one written anywhere else would sit on this Mac
|
|
717
|
+
// alone and look to its author like it had reached somebody.
|
|
718
|
+
ipcMain.handle('zero:team-react', teamCall(async ({ product, id, on, emoji, off }) => {
|
|
719
|
+
const me = teamMe();
|
|
720
|
+
if (!me) throw new Error('sign in first');
|
|
721
|
+
const row = store.readItem(product, id);
|
|
722
|
+
if (!row) throw new Error('that conversation is gone');
|
|
723
|
+
const here = store.listProducts().find((p) => p.slug === product);
|
|
724
|
+
if (!here?.team?.direct) throw new Error('reactions are for a conversation with a teammate');
|
|
725
|
+
store.react(product, id, { on, emoji, off });
|
|
726
|
+
await team.syncNow();
|
|
727
|
+
}));
|
|
728
|
+
|
|
729
|
+
ipcMain.handle('zero:compose', (_e, { product, title, body, kind, priority, runAt, start, labels, engine, model, effort, assignee, due, visibility, visibleTo }) => {
|
|
559
730
|
const out = store.composeItem(product, {
|
|
560
731
|
// HOW HARD IT THINKS rides through unjudged: the store keeps any word
|
|
561
732
|
// shaped like a level, and the spawn is the gate that knows each
|
|
562
733
|
// engine's real list (`Supervisor#spawnPlan` for Claude Code,
|
|
563
734
|
// `codexEffortRefusal` for Codex).
|
|
564
|
-
title, body, kind, priority, runAt, labels, effort,
|
|
735
|
+
title, body, kind, priority, runAt, start, labels, effort,
|
|
736
|
+
// WHO DOES IT, when it is a person (the team version): the teammate it
|
|
737
|
+
// goes to and the day it is due, and the two of you as the conversation,
|
|
738
|
+
// so a reply can hand it back. An agent's row carries none of these.
|
|
739
|
+
assignee, due, visibility,
|
|
740
|
+
// AND WHO MAY SEE IT, when she chose people rather than the team
|
|
741
|
+
// (w-41ff964775). The store keeps the list only beside 'people'.
|
|
742
|
+
visibleTo,
|
|
743
|
+
people: assignee && teamMe() ? [teamMe(), assignee] : undefined,
|
|
565
744
|
// WHICH CODING AGENT SHE CHOSE, AND ONLY IF SHE COULD HAVE. `engineOffered`
|
|
566
745
|
// is the supervisor's own test and the door makes no judgement of its own:
|
|
567
746
|
// it answers null on any Mac where the picker is not drawn, so a renderer
|
|
@@ -576,12 +755,17 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
576
755
|
// fields cannot be corrected separately.
|
|
577
756
|
model: supervisor.modelOffered(engine, model),
|
|
578
757
|
});
|
|
758
|
+
// A real thread ends the walk's hold on agents (`Supervisor#sentByThem`).
|
|
759
|
+
supervisor.sentByThem(labels);
|
|
760
|
+
// THAT a task was written, and whether in practice. Never its words.
|
|
761
|
+
analytics.track('task_written', { practice: isPractice(product) });
|
|
579
762
|
// AND LOOK AT IT NOW. Composing used to write the line and stop, so the
|
|
580
763
|
// task sat in the store until the next fifteen second tick: measured
|
|
581
764
|
// 2026-08-24, median 7.0 seconds and up to 15.0 before the spawn was even
|
|
582
765
|
// attempted. A row scheduled for later is not woken for, because it is not
|
|
583
766
|
// due; the timer is what serves those.
|
|
584
|
-
|
|
767
|
+
// Nothing to wake for a thread added to Later: it starts when she says.
|
|
768
|
+
if (start !== 'later' && (!runAt || runAt <= Date.now())) supervisor.wake();
|
|
585
769
|
return out;
|
|
586
770
|
});
|
|
587
771
|
|
|
@@ -591,8 +775,8 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
591
775
|
// The whole of the work is reading the readme in the folder chosen two
|
|
592
776
|
// screens earlier, so this returns in milliseconds. The two second pause
|
|
593
777
|
// before it is called is in the renderer, where the row is being watched.
|
|
594
|
-
ipcMain.handle('zero:first-run-answer', (_e, { product, id }) => {
|
|
595
|
-
return store.finishFirstRunTask(product, id);
|
|
778
|
+
ipcMain.handle('zero:first-run-answer', (_e, { product, id, round = 0 }) => {
|
|
779
|
+
return store.finishFirstRunTask(product, id, round);
|
|
596
780
|
});
|
|
597
781
|
|
|
598
782
|
// THE PRACTICE PROJECT.Made when the hand-off screen is pressed, filled with
|
|
@@ -606,7 +790,12 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
606
790
|
// somebody mid-walk. They land in an empty practice inbox and the beat that
|
|
607
791
|
// clears them is skipped, exactly as beat thirteen already behaves.
|
|
608
792
|
try { rows = store.stagePracticeRows(made.slug); } catch { rows = { ids: [] }; }
|
|
609
|
-
|
|
793
|
+
// AND THE REST OF THE PRACTICE TEAM'S WORK, so the tour and the board have
|
|
794
|
+
// something in every column (w-58c8f466e7). Its own try: a store that will
|
|
795
|
+
// not take these still leaves the walk its four rows.
|
|
796
|
+
let backdrop = { ids: [] };
|
|
797
|
+
try { backdrop = store.stagePracticeBackdrop(made.slug); } catch { backdrop = { ids: [] }; }
|
|
798
|
+
return { ...made, examples: rows.ids, backdrop: backdrop.ids };
|
|
610
799
|
});
|
|
611
800
|
|
|
612
801
|
// AND TAKING IT BACK OFF THE SCREEN. `sweepPractice` rather than the one
|
|
@@ -640,6 +829,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
640
829
|
ipcMain.handle('zero:open-fresh-user', (_e, payload) => {
|
|
641
830
|
return openFreshUser({
|
|
642
831
|
withAgents: payload?.withAgents !== false,
|
|
832
|
+
withTools: payload?.withTools !== false,
|
|
643
833
|
packaged: app.isPackaged,
|
|
644
834
|
});
|
|
645
835
|
});
|
|
@@ -869,10 +1059,10 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
869
1059
|
// feel instant. The card read them a moment before pressing, so the last read
|
|
870
1060
|
// is the right answer and it is ours rather than the renderer's.
|
|
871
1061
|
let lastThreads = [];
|
|
872
|
-
//
|
|
1062
|
+
// THE USER'S CODEX CONVERSATIONS ARE ON THE SAME CARD. They carry `source:
|
|
873
1063
|
// 'codex'`, land under the project whose folder they ran in exactly as the
|
|
874
|
-
// Claude Code ones do, and the ones
|
|
875
|
-
// is
|
|
1064
|
+
// Claude Code ones do, and the ones the user said not now to are here too, so
|
|
1065
|
+
// there is a way back to them.
|
|
876
1066
|
const readAllThreads = () => {
|
|
877
1067
|
let claude = [];
|
|
878
1068
|
let codex = [];
|
|
@@ -883,7 +1073,11 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
883
1073
|
ipcMain.handle('zero:agent-threads', () => {
|
|
884
1074
|
const threads = readAllThreads();
|
|
885
1075
|
lastThreads = threads;
|
|
886
|
-
|
|
1076
|
+
// Each one already in the inbox says so, and the card leaves it out
|
|
1077
|
+
// (w-db6f5e331e). The walk's folder suggestions still read all of them.
|
|
1078
|
+
let items = [];
|
|
1079
|
+
try { items = store.listItems(); } catch { items = []; }
|
|
1080
|
+
return { threads: markImported(threads, items) };
|
|
887
1081
|
});
|
|
888
1082
|
|
|
889
1083
|
// So this is `zero:import-agents` with the agent taken out of it. No ticks are
|
|
@@ -924,7 +1118,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
924
1118
|
// `projectAgents` and stopped. Three occurrences of that key in the whole
|
|
925
1119
|
// shipped app, all of them this file and settings.mjs, and none in the
|
|
926
1120
|
// renderer: the list was read back only so the card could remember its own
|
|
927
|
-
// ticks the next time somebody walked the setup. Measured on the build
|
|
1121
|
+
// ticks the next time somebody walked the setup. Measured on the build a tester
|
|
928
1122
|
// used, the store was byte for byte identical before and after pressing it.
|
|
929
1123
|
//
|
|
930
1124
|
// The names are still saved, because the card still opens on what was taken
|
|
@@ -976,6 +1170,12 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
976
1170
|
return store.answerItem(product, id, { status: 'blocked' });
|
|
977
1171
|
});
|
|
978
1172
|
|
|
1173
|
+
// Run now, from the three-dot menu on a waiting task (supervisor.runNow).
|
|
1174
|
+
ipcMain.handle('zero:run-now', (_e, { product, id }) => {
|
|
1175
|
+
if (isAgentRow(id)) return { ok: false, reason: 'missing' };
|
|
1176
|
+
return supervisor.runNow(product, id);
|
|
1177
|
+
});
|
|
1178
|
+
|
|
979
1179
|
// Send a stopped/blocked item back to the queue; the fresh worker's brief
|
|
980
1180
|
// carries whatever the founder wrote on the item meanwhile.
|
|
981
1181
|
ipcMain.handle('zero:reopen', (_e, { product, id }) => {
|
|
@@ -1096,6 +1296,45 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1096
1296
|
}
|
|
1097
1297
|
});
|
|
1098
1298
|
|
|
1299
|
+
// SETTING UP THEIR PLAN FOR THEM (w-9f6975906c). The walk asks which plan
|
|
1300
|
+
// they pay for and this installs the tool and starts its own sign-in. The
|
|
1301
|
+
// finder is the recheck, so what gets installed lands on the config and the
|
|
1302
|
+
// rest of the app runs on it without a restart.
|
|
1303
|
+
const engineSetup = createEngineSetup({
|
|
1304
|
+
find: (engine) => {
|
|
1305
|
+
if (engine === 'claude') { const s = recheckClaude(config); return { found: s.claudeFound, path: s.claudeBin }; }
|
|
1306
|
+
const s = recheckCodex(config);
|
|
1307
|
+
return { found: s.found, path: s.bin };
|
|
1308
|
+
},
|
|
1309
|
+
// WHICH ONE IS SIGNED IN, kept on the config for the run that reads it
|
|
1310
|
+
// (`Supervisor#_enginesFound`) and never saved: it changes outside the app.
|
|
1311
|
+
onSignIn: (engine, signedIn) => {
|
|
1312
|
+
config[`${engine}SignedIn`] = signedIn;
|
|
1313
|
+
},
|
|
1314
|
+
});
|
|
1315
|
+
// ASKED ONCE AT LAUNCH, and only on a Mac with both, which is the only Mac
|
|
1316
|
+
// where the answer moves a task (w-d5d632e503): a tester who pays for
|
|
1317
|
+
// ChatGPT had the Claude app's copy of Claude Code, never signed in, so
|
|
1318
|
+
// every task went to Claude Code and Codex was never offered.
|
|
1319
|
+
if (config.claudeFound && config.codexBin) {
|
|
1320
|
+
void engineSetup.signedInNow('claude', config.claudeBin);
|
|
1321
|
+
void engineSetup.signedInNow('codex', config.codexBin);
|
|
1322
|
+
}
|
|
1323
|
+
// A sign-in left waiting on the browser is not left running after the app.
|
|
1324
|
+
app.on('will-quit', () => { engineSetup.cancel('claude'); engineSetup.cancel('codex'); });
|
|
1325
|
+
ipcMain.handle('zero:engine-setup', async (_e, { action, engine } = {}) => {
|
|
1326
|
+
if (engine !== 'claude' && engine !== 'codex') return { ok: false, error: 'Unknown coding agent.' };
|
|
1327
|
+
try {
|
|
1328
|
+
if (action === 'ready') return { ok: true, ...(await engineSetup.readiness(engine)) };
|
|
1329
|
+
if (action === 'start') return { ok: true, ...engineSetup.start(engine) };
|
|
1330
|
+
if (action === 'again') return { ok: true, ...engineSetup.signInAgain(engine) };
|
|
1331
|
+
if (action === 'cancel') return { ok: true, ...engineSetup.cancel(engine) };
|
|
1332
|
+
return { ok: true, ...engineSetup.status(engine) };
|
|
1333
|
+
} catch (err) {
|
|
1334
|
+
return { ok: false, error: String(err.message) };
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1099
1338
|
/* * ADD A CODEX ACCOUNT: make the folder, register the login, hand back the one line the
|
|
1100
1339
|
terminal has to run.
|
|
1101
1340
|
*/
|
|
@@ -1166,7 +1405,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1166
1405
|
const dir = productDirOf(product);
|
|
1167
1406
|
if (!dir) return { ok: false, error: 'That project could not be found.' };
|
|
1168
1407
|
const saved = setProjectName(dir, name);
|
|
1169
|
-
// PUSH, because the name is on far more than the screen
|
|
1408
|
+
// PUSH, because the name is on far more than the screen it was typed on:
|
|
1170
1409
|
// the rail, every row's byline, the composer's picker and ⌘K all print it,
|
|
1171
1410
|
// and a rename that only redrew Settings would leave the rest of the app
|
|
1172
1411
|
// calling it the old thing until something else happened to refresh.
|
|
@@ -1177,6 +1416,32 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1177
1416
|
}
|
|
1178
1417
|
});
|
|
1179
1418
|
|
|
1419
|
+
// ARCHIVE A PROJECT, OR BRING ONE BACK (main/store.mjs setProductArchived).
|
|
1420
|
+
// Its files stay on disk; it leaves every list the app draws. Pushed for the
|
|
1421
|
+
// same reason a rename is: the rail, the inbox and the picker all list it.
|
|
1422
|
+
ipcMain.handle('zero:project-archive', (_e, { product, archived } = {}) => {
|
|
1423
|
+
try {
|
|
1424
|
+
store.setProductArchived(product, archived === true);
|
|
1425
|
+
push();
|
|
1426
|
+
return { ok: true, ...readSettings({ config, supervisor, store }) };
|
|
1427
|
+
} catch (err) {
|
|
1428
|
+
return { ok: false, error: String(err.message) };
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
// WHO SEES A PROJECT'S THREADS (main/store.mjs setProductSeenBy,
|
|
1433
|
+
// w-b989839656). Pushed, because the inbox's locks and the New thread
|
|
1434
|
+
// card's starting chip both read it off the snapshot's projects.
|
|
1435
|
+
ipcMain.handle('zero:project-seen-by', (_e, { product, who, people } = {}) => {
|
|
1436
|
+
try {
|
|
1437
|
+
store.setProductSeenBy(product, { who, people });
|
|
1438
|
+
push();
|
|
1439
|
+
return { ok: true };
|
|
1440
|
+
} catch (err) {
|
|
1441
|
+
return { ok: false, error: String(err.message) };
|
|
1442
|
+
}
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1180
1445
|
// Opens the picker AND does the copy, in one round trip. Two handlers would
|
|
1181
1446
|
// mean the renderer briefly holds a path into her Downloads folder and hands
|
|
1182
1447
|
// it back to be trusted; this way the only path that crosses the bridge is one
|
|
@@ -1248,10 +1513,10 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1248
1513
|
for (const file of files ?? []) {
|
|
1249
1514
|
const safe = String(file.name ?? 'file').replace(/[^\w.-]+/g, '-').slice(0, 80);
|
|
1250
1515
|
// A RANDOM SUFFIX, THE SAME ONE THE STAGING PATH ALREADY USES. The name
|
|
1251
|
-
// was the millisecond plus
|
|
1516
|
+
// was the millisecond plus the filename, so two files saved in the same
|
|
1252
1517
|
// millisecond with the same name overwrote each other and the message
|
|
1253
|
-
// ended up with two links to one picture.
|
|
1254
|
-
// which is exactly the shape that collides. It has never been observed
|
|
1518
|
+
// ended up with two links to one picture. Screenshots are often sent in
|
|
1519
|
+
// pairs, which is exactly the shape that collides. It has never been observed
|
|
1255
1520
|
// happening; it is one line to make impossible.
|
|
1256
1521
|
const rel = `attachments/${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}-${safe}`;
|
|
1257
1522
|
const abs = path.join(dir, rel);
|
|
@@ -1265,13 +1530,12 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1265
1530
|
|
|
1266
1531
|
// A PASTED SCREENSHOT GOES TO DISK THE MOMENT IT IS PASTED.
|
|
1267
1532
|
//
|
|
1268
|
-
//
|
|
1269
|
-
//
|
|
1270
|
-
//
|
|
1271
|
-
//
|
|
1272
|
-
// is 6.08 MB.
|
|
1533
|
+
// The constraint was ours. Pasted bytes used to ride in the draft as base64,
|
|
1534
|
+
// and a draft lives in localStorage, whose quota MEASURED on this machine is
|
|
1535
|
+
// 5,241,856 characters for the WHOLE app. Ordinary full-screen screenshots
|
|
1536
|
+
// are regularly over that; 6 MB is not unusual.
|
|
1273
1537
|
//
|
|
1274
|
-
// Raising the number cannot reach
|
|
1538
|
+
// Raising the number cannot reach that bar: even the whole quota is under 4 MB
|
|
1275
1539
|
// of image, and Claude's own limit is 10 MB base64 per image (about 7.5 MB of
|
|
1276
1540
|
// file) at up to 8000x8000. localStorage is simply the wrong place for image
|
|
1277
1541
|
// bytes. So they go here instead, and the draft keeps a path — which the draft
|
|
@@ -1408,6 +1672,16 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1408
1672
|
return err ? { ok: false, error: err } : { ok: true, opened: found.path };
|
|
1409
1673
|
});
|
|
1410
1674
|
|
|
1675
|
+
// THE PICTURES IN A FOLDER A MESSAGE NAMES, so the folder line draws them
|
|
1676
|
+
// (FolderPreview in Focus.tsx). Only pictures, only inside this product.
|
|
1677
|
+
ipcMain.handle('zero:folder-pictures', (_e, { product, src } = {}) => folderPictures({
|
|
1678
|
+
src,
|
|
1679
|
+
product,
|
|
1680
|
+
products: store.listProducts(),
|
|
1681
|
+
accountRoot: config.accountRoot,
|
|
1682
|
+
storeRoot: config.storeRoot,
|
|
1683
|
+
}));
|
|
1684
|
+
|
|
1411
1685
|
// THE DOCUMENT PANE READS AND WRITES THE FILE ITSELF. A path goes through
|
|
1412
1686
|
// the very same finder the links and the chips use, so the pane opens
|
|
1413
1687
|
// exactly the file a click would have opened, and then main hands back the
|