agentbox-app 0.1.7 → 0.1.12
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 +220 -58
- package/bin/agentbox.mjs +18 -3
- package/briefs/message-rules.md +24 -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 +11 -11
- 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 +116 -0
- package/main/agents.mjs +52 -46
- package/main/analytics.mjs +27 -0
- package/main/approval-prompt-server.mjs +2 -2
- package/main/approvals.mjs +14 -5
- package/main/artifact-path.mjs +11 -0
- package/main/claude-bin.mjs +32 -5
- package/main/claude-input.mjs +45 -3
- package/main/claude-plan.mjs +25 -13
- package/main/code-change.mjs +3 -3
- package/main/codex-account.mjs +1 -1
- package/main/codex-bin.mjs +18 -0
- package/main/codex-launch-env.mjs +31 -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 +82 -17
- package/main/demo.mjs +17 -14
- package/main/dev-window.mjs +9 -9
- package/main/doc-scheme.mjs +2 -0
- package/main/engine-setup.mjs +204 -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 +7 -4
- package/main/git-change-offthread.mjs +70 -0
- package/main/git-change-thread.mjs +11 -0
- package/main/git-change.mjs +9 -3
- package/main/img-scheme.mjs +70 -1
- package/main/instruction-settings.mjs +134 -60
- package/main/ipc.mjs +335 -42
- package/main/leftovers.mjs +518 -0
- package/main/live-replies.mjs +43 -5
- package/main/machine.mjs +48 -1
- package/main/main.mjs +138 -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/notify.mjs +1 -1
- package/main/rail-note.mjs +2 -2
- package/main/refresh-agent-models.mjs +21 -0
- package/main/repeats.mjs +4 -4
- package/main/row-label.mjs +115 -51
- package/main/runs-on.mjs +93 -0
- package/main/serve.mjs +77 -5
- package/main/settings.mjs +245 -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 +84 -7
- package/main/store/project.mjs +15 -19
- package/main/store/work-items.mjs +150 -6
- package/main/store.mjs +245 -13
- package/main/supervisor.mjs +1634 -417
- package/main/task-commands.mjs +8 -10
- package/main/task-folders.mjs +94 -16
- package/main/task-remote-control.mjs +3 -3
- package/main/task-terminals.mjs +14 -2
- 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 +10 -12
- package/mcp/core/work.mjs +123 -26
- package/mcp/tools.mjs +13 -6
- package/package.json +21 -9
- package/renderer/dist/assets/agentbox-icon-F1oN9nj_.svg +15 -0
- package/renderer/dist/assets/index-ChKsQkRw.js +1169 -0
- package/renderer/dist/assets/index-ClBXnq3B.css +32 -0
- package/renderer/dist/index.html +2 -2
- package/scripts/after-install.mjs +14 -0
- package/scripts/approval-server.sh +7 -2
- package/scripts/memory-gate-hook.sh +59 -0
- package/shared/agent-import.mjs +39 -16
- 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 +38 -1
- package/shared/claude-commands.generated.mjs +2 -2
- package/shared/claude-models.generated.mjs +6 -6
- 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-modes.mjs +1 -1
- package/shared/codex-usage.mjs +7 -8
- package/shared/demo-world.mjs +6 -6
- 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 +34 -4
- package/shared/instruction-defaults.json +2 -2
- package/shared/product-name.mjs +4 -2
- package/shared/project-folder-check.mjs +9 -3
- package/shared/rank.d.mts +1 -0
- package/shared/rank.mjs +22 -0
- package/shared/referenced-files.mjs +1 -1
- package/shared/runs-on.d.mts +20 -0
- package/shared/runs-on.mjs +79 -0
- 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 +40 -19
- 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 +258 -28
- 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-B7hhfHrT.js +0 -684
- package/renderer/dist/assets/index-CwiDv5bL.css +0 -32
- 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/task-commands.mjs
CHANGED
|
@@ -26,10 +26,10 @@ const exec = promisify(execFile);
|
|
|
26
26
|
* a chat of its own, that takes over and nothing forks twice.
|
|
27
27
|
*/
|
|
28
28
|
/**
|
|
29
|
-
* A row name out of the sentence
|
|
29
|
+
* A row name out of the sentence typed after `/fork`: its first sentence,
|
|
30
30
|
* or the first sixty characters of it, so the inbox line says which fork this
|
|
31
31
|
* is rather than which row it came from. Whitespace flattened because a title
|
|
32
|
-
* is one line and
|
|
32
|
+
* is one line and a paragraph can be pasted.
|
|
33
33
|
*/
|
|
34
34
|
export function forkTitle(words, cap = 60) {
|
|
35
35
|
const flat = String(words ?? '').replace(/\s+/g, ' ').trim();
|
|
@@ -45,18 +45,16 @@ function forkTask(sup, productSlug, id, item, engine, op, publish) {
|
|
|
45
45
|
}
|
|
46
46
|
// NAMING THE KEY, because the menu closes at the space and plain return then
|
|
47
47
|
// writes a newline, the way it does on every other reply here. Without that
|
|
48
|
-
// sentence
|
|
49
|
-
// does nothing
|
|
50
|
-
// hit return").
|
|
48
|
+
// sentence the user types the words, presses return, and is back to a key
|
|
49
|
+
// that does nothing, so a bare /fork looks as if it did nothing.
|
|
51
50
|
if (!op.args) return publish('failed', 'Say what the fork should try, as in /fork try it without the modal, then press command and return.', op.name);
|
|
52
51
|
const chat = sup.rowSessionFor(item, sup.store.listProducts().find((p) => p.slug === productSlug));
|
|
53
52
|
if (!chat?.sessionId) return publish('failed', 'This task has no saved conversation to fork yet. Send a message first.', op.name);
|
|
54
53
|
const made = sup.store.composeItem(productSlug, {
|
|
55
|
-
// THE FORK IS NAMED AFTER WHAT
|
|
56
|
-
// came from. Naming it `<parent title> · fork` put two rows in
|
|
57
|
-
// whose names differ by one word at the end,
|
|
58
|
-
//
|
|
59
|
-
// (2026-09-23). The instruction is the only thing that distinguishes them,
|
|
54
|
+
// THE FORK IS NAMED AFTER WHAT IT WAS ASKED TO TRY, not after the row it
|
|
55
|
+
// came from. Naming it `<parent title> · fork` put two rows in the inbox
|
|
56
|
+
// whose names differ by one word at the end, so there was no telling which
|
|
57
|
+
// agent was which. The instruction is the only thing that distinguishes them,
|
|
60
58
|
// so it is what the row is called; the parent is still on the row as the
|
|
61
59
|
// `fork:` label, which is where the machinery reads it anyway.
|
|
62
60
|
title: forkTitle(op.args),
|
package/main/task-folders.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// ONE FOLDER PER TASK, OWNED BY THE APP RATHER THAN BY THE AGENT IN IT.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// The two engines are not equal here. Claude Code has isolation built in
|
|
3
|
+
// Between letting Claude Code make its own worktree and doing it ourselves, the
|
|
4
|
+
// app makes the folder for both agents. The two engines are not equal here. Claude Code has isolation built in
|
|
6
5
|
// (`--worktree`), makes the folder under `.claude/worktrees/`, locks it, reuses
|
|
7
6
|
// it by name and deletes it when it leaves nothing behind. Codex has none of
|
|
8
7
|
// that, only `--cd` for choosing where to work. So the rules have to live on
|
|
@@ -11,7 +10,7 @@
|
|
|
11
10
|
// exactly where Claude Code would, so a person who opens the repository in a
|
|
12
11
|
// terminal sees the layout they already know.
|
|
13
12
|
//
|
|
14
|
-
// WHAT WAS MEASURED,
|
|
13
|
+
// WHAT WAS MEASURED, 2026-09-22. This repository had 25 leftover
|
|
15
14
|
// agent copies holding 26 GB: twelve with edits committed nowhere, twelve on
|
|
16
15
|
// branches never merged, one safe to delete. Isolation was a rule in the worker
|
|
17
16
|
// brief; the rule's first half (make a copy) was kept and its second half (fold
|
|
@@ -51,6 +50,9 @@ const BRANCH_PREFIX = `${nameSlug}/`;
|
|
|
51
50
|
/** Ids are `w-` plus hex today. This is the guard for the day they are not. */
|
|
52
51
|
const SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
53
52
|
|
|
53
|
+
/** What an unfinished folder is called. A task name may never start with a dot. */
|
|
54
|
+
const BUILDING = '.building-';
|
|
55
|
+
|
|
54
56
|
const git = (cwd, args) => execFileSync('git', args, {
|
|
55
57
|
cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
56
58
|
}).trim();
|
|
@@ -81,6 +83,32 @@ export function taskFolderPath(repoPath, id) {
|
|
|
81
83
|
return path.join(real(repoPath), ...WORKTREES, safeTaskName(id));
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
/**
|
|
87
|
+
* WHERE A FOLDER IS BUILT, WHICH IS NEVER WHERE IT IS RUN IN.
|
|
88
|
+
*
|
|
89
|
+
* Making one is a worktree plus a block clone of the checkout. It used to be
|
|
90
|
+
* built AT the path a session runs in, so for the whole of that a directory sat
|
|
91
|
+
* there, registered with git and holding almost none of the code. MEASURED on
|
|
92
|
+
* astral-video, the repository this happened in, 2026-10-01: the build takes 115
|
|
93
|
+
* seconds now that the checkout is 14 GB, and the path a session runs in
|
|
94
|
+
* appeared 2.7 seconds in. That is 112 seconds of a folder that looked ready and
|
|
95
|
+
* was not, against a supervisor tick of fifteen seconds, so seven ticks could
|
|
96
|
+
* read it. Everything that asks whether a row's folder is ready asks the disk,
|
|
97
|
+
* which means for those 112 seconds every one of them said yes:
|
|
98
|
+
* the supervisor started a session in a half written folder, and when the build
|
|
99
|
+
* finished it started a second session on the same row (2026-09-30,
|
|
100
|
+
* w-4d722ecf92, where the second one's claim came back refused and the first
|
|
101
|
+
* one's folder was deleted under it a minute in).
|
|
102
|
+
*
|
|
103
|
+
* Built here and MOVED into place once it is finished and checked, so a
|
|
104
|
+
* directory at the real path means a folder that is complete. That makes every
|
|
105
|
+
* existing question correct without any of them learning a new one, and it
|
|
106
|
+
* leaves the rollback below nothing to delete but its own unfinished copy.
|
|
107
|
+
*/
|
|
108
|
+
export function buildingFolderPath(repoPath, id) {
|
|
109
|
+
return path.join(real(repoPath), ...WORKTREES, `${BUILDING}${safeTaskName(id)}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
84
112
|
export function taskBranch(id) { return `${BRANCH_PREFIX}${safeTaskName(id)}`; }
|
|
85
113
|
|
|
86
114
|
/** The repository root, or null when this is not a checkout at all. */
|
|
@@ -156,20 +184,52 @@ export function ensureTaskFolder(dir, id, { pid = process.pid, dependencies = tr
|
|
|
156
184
|
|
|
157
185
|
hideFromTheCheckout(root);
|
|
158
186
|
|
|
187
|
+
// EVERY STEP BELOW HAPPENS SOMEWHERE ELSE, and the folder appears at its own
|
|
188
|
+
// path in one move at the end. `buildingFolderPath` is why. A build the app
|
|
189
|
+
// died in the middle of leaves an unfinished copy there, which nobody wants
|
|
190
|
+
// and which would refuse the next attempt, so it goes first.
|
|
191
|
+
const staging = buildingFolderPath(root, name);
|
|
192
|
+
clearUnfinished(root, staging);
|
|
193
|
+
|
|
159
194
|
const branchExists = tryGit(root, ['rev-parse', '--verify', '-q', `refs/heads/${branch}`]).ok;
|
|
160
195
|
const from = branchExists ? branch : baseRef(root);
|
|
161
|
-
if (!cloneCheckout(root,
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
196
|
+
if (!cloneCheckout(root, staging, branch, from, branchExists)) {
|
|
197
|
+
// The fast path may have created the branch and then failed the copy.
|
|
198
|
+
// The branch stays: this file never deletes one. The slow path uses it
|
|
199
|
+
// when it is already there, which is what `worktree add -b` would refuse.
|
|
200
|
+
const existsNow = tryGit(root, ['rev-parse', '--verify', '-q', `refs/heads/${branch}`]).ok;
|
|
201
|
+
const add = existsNow
|
|
202
|
+
? tryGit(root, ['worktree', 'add', staging, branch])
|
|
203
|
+
: tryGit(root, ['worktree', 'add', '-b', branch, staging, baseRef(root)]);
|
|
165
204
|
if (!add.ok) throw Error(`Could not make a folder for ${name}: ${add.out}`);
|
|
166
|
-
if (dependencies) cloneDependencies(root,
|
|
205
|
+
if (dependencies) cloneDependencies(root, staging);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// `worktree move` rather than a rename, so git's own record of where this
|
|
209
|
+
// worktree lives moves with it. It refuses if anything is already at the
|
|
210
|
+
// destination, which is the last guard against walking over a live folder.
|
|
211
|
+
const moved = tryGit(root, ['worktree', 'move', staging, folder]);
|
|
212
|
+
if (!moved.ok) {
|
|
213
|
+
clearUnfinished(root, staging);
|
|
214
|
+
throw Error(`Could not make a folder for ${name}: ${moved.out}`);
|
|
167
215
|
}
|
|
168
216
|
|
|
169
217
|
lock(root, folder, pid);
|
|
170
218
|
return { path: folder, branch, created: true };
|
|
171
219
|
}
|
|
172
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Throw away an unfinished copy. Nothing is ever lost here: a staging folder
|
|
223
|
+
* has never been handed to anybody, so the most it can hold is a checkout that
|
|
224
|
+
* did not finish. The branch is left alone, as everywhere else in this file.
|
|
225
|
+
*/
|
|
226
|
+
function clearUnfinished(root, staging) {
|
|
227
|
+
if (!fs.existsSync(staging) && !record(root, staging)) return;
|
|
228
|
+
tryGit(root, ['worktree', 'remove', '--force', staging]);
|
|
229
|
+
try { fs.rmSync(staging, { recursive: true, force: true }); } catch { /* already gone */ }
|
|
230
|
+
tryGit(root, ['worktree', 'prune']);
|
|
231
|
+
}
|
|
232
|
+
|
|
173
233
|
/**
|
|
174
234
|
* MAKING THE FOLDER WITHOUT PAYING FOR IT TWICE.
|
|
175
235
|
*
|
|
@@ -188,7 +248,20 @@ export function ensureTaskFolder(dir, id, { pid = process.pid, dependencies = tr
|
|
|
188
248
|
* commit, down to an empty `git status`, the folder is thrown away and the
|
|
189
249
|
* ordinary checkout runs. A fast folder that is subtly not the code would be
|
|
190
250
|
* far worse than a slow one.
|
|
251
|
+
*
|
|
252
|
+
* THE COPY IS THE PLATFORM'S OWN CLONE. macOS `cp -c` is APFS clonefile.
|
|
253
|
+
* GNU `cp -a --reflink=auto` shares blocks where the filesystem can and
|
|
254
|
+
* copies where it cannot. `-c` is not an option there, and a copy that
|
|
255
|
+
* failed used to leave the branch the fast path had just created, so the
|
|
256
|
+
* ordinary checkout then died with "a branch named … already exists".
|
|
191
257
|
*/
|
|
258
|
+
function copyTree(from, to) {
|
|
259
|
+
const args = process.platform === 'darwin'
|
|
260
|
+
? ['-c', '-R', from, to]
|
|
261
|
+
: ['-a', '--reflink=auto', from, to];
|
|
262
|
+
execFileSync('cp', args, { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
263
|
+
}
|
|
264
|
+
|
|
192
265
|
function cloneCheckout(root, folder, branch, from, branchExists) {
|
|
193
266
|
// TRACKED CONTENT ONLY, on both sides of this. Her checkout permanently
|
|
194
267
|
// carries a couple of untracked files (a scratch `:memory:.ses`, a folder of
|
|
@@ -210,17 +283,19 @@ function cloneCheckout(root, folder, branch, from, branchExists) {
|
|
|
210
283
|
try {
|
|
211
284
|
for (const entry of fs.readdirSync(root)) {
|
|
212
285
|
if (entry === '.git' || entry === WORKTREES[0]) continue;
|
|
213
|
-
|
|
286
|
+
copyTree(path.join(root, entry), folder);
|
|
214
287
|
}
|
|
215
288
|
if (!tryGit(folder, ['reset', '-q', 'HEAD']).ok) throw Error('the index would not fill in');
|
|
216
289
|
const after = tryGit(folder, ['status', '--porcelain', '--untracked-files=no']);
|
|
217
290
|
if (!after.ok || after.out.length > 0) throw Error('the clone did not match the commit');
|
|
218
291
|
return true;
|
|
219
292
|
} catch {
|
|
220
|
-
// Back to nothing, so the ordinary path starts from a clean sheet.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
293
|
+
// Back to nothing, so the ordinary path starts from a clean sheet. What is
|
|
294
|
+
// thrown away here is the UNFINISHED COPY and never a folder anybody is in:
|
|
295
|
+
// `folder` is the staging path, which no session has ever been given. It
|
|
296
|
+
// used to be the real one, and a `remove --force` of that is how a running
|
|
297
|
+
// agent lost the ground under it.
|
|
298
|
+
clearUnfinished(root, folder);
|
|
224
299
|
return false;
|
|
225
300
|
}
|
|
226
301
|
}
|
|
@@ -257,7 +332,7 @@ function cloneDependencies(root, folder) {
|
|
|
257
332
|
const to = path.join(folder, 'node_modules');
|
|
258
333
|
if (!fs.existsSync(from) || fs.existsSync(to)) return false;
|
|
259
334
|
try {
|
|
260
|
-
|
|
335
|
+
copyTree(from, to);
|
|
261
336
|
return true;
|
|
262
337
|
} catch {
|
|
263
338
|
try { fs.rmSync(to, { recursive: true, force: true }); } catch { /* nothing to undo */ }
|
|
@@ -390,7 +465,10 @@ export function listTaskFolders(dir) {
|
|
|
390
465
|
if (!root) return [];
|
|
391
466
|
const home = path.join(root, ...WORKTREES);
|
|
392
467
|
return registered(root)
|
|
393
|
-
|
|
468
|
+
// A folder still being built is not one of hers: it has no row standing in
|
|
469
|
+
// it and nothing in it is wanted, so it may never be listed, shown or swept.
|
|
470
|
+
.filter((w) => path.dirname(w.path) === home && !w.prunable
|
|
471
|
+
&& !path.basename(w.path).startsWith(BUILDING))
|
|
394
472
|
.map((w) => {
|
|
395
473
|
const { dirty, merged } = folderState(root, w.path);
|
|
396
474
|
let touchedAt = null;
|
|
@@ -18,9 +18,9 @@ export async function taskRemoteControl(sup,product,id,action='status') {
|
|
|
18
18
|
if(sup._engineFor(item)==='codex')throw Error('Remote Control is currently available for Claude Code conversations only.');
|
|
19
19
|
if(action==='dismiss')return current()?.state==='failed'&&!current()?.mayBeActive?publish('off'):current();
|
|
20
20
|
if(current()?.state==='connecting'||current()?.state==='disconnecting')return current();
|
|
21
|
-
// Typing the command IS the decision, so it never stops at a card asking again
|
|
22
|
-
//
|
|
23
|
-
//
|
|
21
|
+
// Typing the command IS the decision, so it never stops at a card asking again
|
|
22
|
+
// (w-79bffd7ec3): it turns remote control on or off depending on its current
|
|
23
|
+
// state, with no further confirmation.
|
|
24
24
|
if(action==='toggle')action=['connected','ready','reconnecting'].includes(current()?.state)||current()?.mayBeActive?'disable':'enable';
|
|
25
25
|
if(action==='enable'&&['connected','ready','reconnecting'].includes(current()?.state))return current();
|
|
26
26
|
let session=sup.sessions.get(id);
|
package/main/task-terminals.mjs
CHANGED
|
@@ -32,6 +32,17 @@ export function terminalPlace({store,supervisor,product,id,agent}){
|
|
|
32
32
|
return {cwd,notice};
|
|
33
33
|
}
|
|
34
34
|
export function terminalFolder(args){return terminalPlace(args).cwd;}
|
|
35
|
+
// The shell a person actually has. `$SHELL` when that file exists, then
|
|
36
|
+
// zsh, bash, and sh. A machine with no zsh used to spawn `/bin/zsh` and
|
|
37
|
+
// the terminal never came up.
|
|
38
|
+
export function loginShell(env=process.env,exists=fs.existsSync){
|
|
39
|
+
const preferred=env.SHELL;
|
|
40
|
+
if(preferred&&exists(preferred))return preferred;
|
|
41
|
+
for(const candidate of ['/bin/zsh','/bin/bash','/bin/sh']){
|
|
42
|
+
if(exists(candidate))return candidate;
|
|
43
|
+
}
|
|
44
|
+
return preferred||'/bin/sh';
|
|
45
|
+
}
|
|
35
46
|
// Enumerate only descendants of our own shell, including background process
|
|
36
47
|
// groups. Kill children before shell; closing a pane never calls this.
|
|
37
48
|
export function disposeTerminalProcess(pty){
|
|
@@ -69,7 +80,7 @@ const IDLE_MS=600000;
|
|
|
69
80
|
const SWEEP_MS=60000;
|
|
70
81
|
const MAX_TERMINALS=20;
|
|
71
82
|
export class TaskTerminals {
|
|
72
|
-
constructor({spawn,resolve,disposeProcess=disposeTerminalProcess,env=terminalEnv(),shell=
|
|
83
|
+
constructor({spawn,resolve,disposeProcess=disposeTerminalProcess,env=terminalEnv(),shell=loginShell(),idleMs=IDLE_MS,sweepMs=SWEEP_MS,now=()=>Date.now()}={}){
|
|
73
84
|
this.spawn=spawn??((...args)=>require('node-pty').spawn(...args));this.resolve=resolve;this.disposeProcess=disposeProcess;this.env=env;this.shell=shell;this.sessions=new Map();
|
|
74
85
|
this.idleMs=idleMs;this.now=now;
|
|
75
86
|
// Keys whose shell this class ended on its own, and WHY. The next open on
|
|
@@ -133,7 +144,8 @@ export class TaskTerminals {
|
|
|
133
144
|
}
|
|
134
145
|
const place=this.resolve(key);
|
|
135
146
|
const cwd=typeof place==='string'?place:place.cwd;
|
|
136
|
-
const
|
|
147
|
+
const command=place?.command;
|
|
148
|
+
const pty=this.spawn(command?.file??this.shell,command?.args??['-l'],{cwd,env:{...this.env,...command?.env},cols:80,rows:24,name:'xterm-256color'});
|
|
137
149
|
let resolveExit;const exitPromise=new Promise(resolve=>{resolveExit=resolve;});
|
|
138
150
|
const session={token:randomUUID(),pty,cwd,buffer:'',offset:0,exitCode:null,exited:false,exitPromise,lastReadAt:this.now()};this.sessions.set(key,session);
|
|
139
151
|
/*
|