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/store.mjs
CHANGED
|
@@ -12,12 +12,14 @@ import { answerFor, describeFor } from './first-run.mjs';
|
|
|
12
12
|
import { checkProjectFolder } from '../shared/project-folder-check.mjs';
|
|
13
13
|
import { EXAMPLES, stampsFor } from '../shared/first-run-examples.mjs';
|
|
14
14
|
import {
|
|
15
|
-
PRACTICE_ANSWER, PRACTICE_FLAG, PRACTICE_NAME, PRACTICE_NOTE, PRACTICE_ROWS, PRACTICE_SLUG,
|
|
16
|
-
PRACTICE_TASK_TRACE,
|
|
15
|
+
PRACTICE_ANSWER, PRACTICE_BACKDROP, PRACTICE_FLAG, PRACTICE_NAME, PRACTICE_NOTE, PRACTICE_ROWS, PRACTICE_SLUG,
|
|
16
|
+
PRACTICE_REPLY_ANSWER, PRACTICE_TASK_TRACE,
|
|
17
17
|
} from '../shared/first-run-practice.mjs';
|
|
18
18
|
import { NOTE_NAME } from './rail-note.mjs';
|
|
19
19
|
import { iconPathFor } from './project-identity.mjs';
|
|
20
|
+
import { patchProjectAt } from './store/project.mjs';
|
|
20
21
|
import { imgUrl } from './img-scheme.mjs';
|
|
22
|
+
import { teamOf, PERSONAL_FLAG } from './team/projects.mjs';
|
|
21
23
|
import {
|
|
22
24
|
agentImportRow, agentsNeedingRows, threadImportRow, threadsNeedingRows,
|
|
23
25
|
} from '../shared/agent-import.mjs';
|
|
@@ -66,7 +68,12 @@ export class Store {
|
|
|
66
68
|
/* ------------------------------- products ------------------------------ */
|
|
67
69
|
// A product is a project directory under the account root carrying a
|
|
68
70
|
// project.json. Archived products (or the reserved analytics id) don't ride.
|
|
69
|
-
|
|
71
|
+
// ARCHIVED PROJECTS ARE LEFT OUT unless a caller asks for them, which is
|
|
72
|
+
// what makes archiving one take it off the inbox, the sidebar, the fleet and
|
|
73
|
+
// the Projects page in one write. Two callers do ask: the Projects page, so
|
|
74
|
+
// an archived one can be brought back, and the team sync, which must still
|
|
75
|
+
// count a shared project you archived as here or it joins it a second time.
|
|
76
|
+
listProducts({ includeArchived = false } = {}) {
|
|
70
77
|
const root = this.config.accountRoot;
|
|
71
78
|
let entries = [];
|
|
72
79
|
try {
|
|
@@ -93,9 +100,10 @@ export class Store {
|
|
|
93
100
|
} catch {
|
|
94
101
|
continue;
|
|
95
102
|
}
|
|
96
|
-
if (project.archived) continue;
|
|
103
|
+
if (project.archived && !includeArchived) continue;
|
|
97
104
|
products.push({
|
|
98
105
|
slug: entry.name,
|
|
106
|
+
...(includeArchived ? { archived: project.archived === true } : {}),
|
|
99
107
|
dir,
|
|
100
108
|
name: project.name || entry.name,
|
|
101
109
|
oneLiner: project.oneLiner || '',
|
|
@@ -117,11 +125,56 @@ export class Store {
|
|
|
117
125
|
// that turn on this flag — never spawning a worker in it, drawing a
|
|
118
126
|
// band over it — must never happen to real work.
|
|
119
127
|
practice: project[PRACTICE_FLAG] === true,
|
|
128
|
+
// YOUR PERSONAL PROJECT, where a thread is Only you unless you say
|
|
129
|
+
// otherwise (main/team/projects.mjs, w-b989839656).
|
|
130
|
+
personal: project[PERSONAL_FLAG] === true,
|
|
131
|
+
// WHO SEES ITS THREADS on the Team page (w-b989839656), read as
|
|
132
|
+
// written; shared/thread-cards.mjs projectSeenBy is the one reading.
|
|
133
|
+
...(typeof project.seenBy === 'string' ? { seenBy: project.seenBy } : {}),
|
|
134
|
+
...(Array.isArray(project.seenByPeople) ? { seenByPeople: project.seenByPeople.filter((p) => typeof p === 'string') } : {}),
|
|
135
|
+
// SHARED OR PRIVATE. Null on a private project, which is every
|
|
136
|
+
// project of a person who never signs in; otherwise the cloud id that
|
|
137
|
+
// is the same on every teammate's Mac (main/team/projects.mjs).
|
|
138
|
+
team: teamOf(project),
|
|
120
139
|
});
|
|
121
140
|
}
|
|
122
141
|
return products.sort((a, b) => a.name.localeCompare(b.name));
|
|
123
142
|
}
|
|
124
143
|
|
|
144
|
+
/** ARCHIVE A PROJECT, OR BRING ONE BACK. One flag in its project.json and
|
|
145
|
+
* nothing else: the folder, its documents and its threads stay on disk, and
|
|
146
|
+
* `listProducts` is what stops showing it. Agents already running there
|
|
147
|
+
* finish; nothing new starts, because the fleet reads the same list.
|
|
148
|
+
*
|
|
149
|
+
* Resolved through the list itself, so the only thing this can ever write
|
|
150
|
+
* is a project folder directly inside this account, named exactly. */
|
|
151
|
+
setProductArchived(slug, archived) {
|
|
152
|
+
const product = slug ? this.listProducts({ includeArchived: true }).find((p) => p.slug === slug) : null;
|
|
153
|
+
if (!product) throw new Error('That project could not be found.');
|
|
154
|
+
if (product.archived !== (archived === true)) {
|
|
155
|
+
patchProjectAt(product.dir, { archived: archived === true });
|
|
156
|
+
// The watched folders are the listed ones, so one brought back is not
|
|
157
|
+
// watched until the list is read again.
|
|
158
|
+
if (this.watchers.length) this.watch();
|
|
159
|
+
}
|
|
160
|
+
return { slug, archived: archived === true };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** WHO SEES A PROJECT'S THREADS on the Team page (w-b989839656): 'private'
|
|
164
|
+
* (Just you), 'team', or 'people' with the people named. Written into the
|
|
165
|
+
* project's own file, because it is the project's, and nothing of the
|
|
166
|
+
* project itself leaves this Mac either way. My Workspace is refused: it is
|
|
167
|
+
* Just you by what it is. */
|
|
168
|
+
setProductSeenBy(slug, { who, people } = {}) {
|
|
169
|
+
const product = slug ? this.listProducts({ includeArchived: true }).find((p) => p.slug === slug) : null;
|
|
170
|
+
if (!product) throw new Error('That project could not be found.');
|
|
171
|
+
if (product.personal) throw new Error('My Workspace is always just you.');
|
|
172
|
+
if (who !== 'private' && who !== 'team' && who !== 'people') throw new Error(`unknown setting for who sees it: ${who}`);
|
|
173
|
+
const ids = [...new Set((Array.isArray(people) ? people : []).filter((p) => typeof p === 'string' && p))];
|
|
174
|
+
patchProjectAt(product.dir, { seenBy: who, seenByPeople: who === 'people' ? ids : undefined });
|
|
175
|
+
return { slug, who, people: who === 'people' ? ids : [] };
|
|
176
|
+
}
|
|
177
|
+
|
|
125
178
|
/* ------------------------------ work items ----------------------------- */
|
|
126
179
|
// The cross-product fold. Every item carries its product slug so the
|
|
127
180
|
// renderer can group, filter, and rail without a second lookup.
|
|
@@ -149,6 +202,23 @@ export class Store {
|
|
|
149
202
|
return all;
|
|
150
203
|
}
|
|
151
204
|
|
|
205
|
+
// THE NEXT PAGE OF FINISHED THREADS THE SNAPSHOT'S 8 MB READ LEFT OUT
|
|
206
|
+
// (`readOlderWorkItems`), newest finished first, for the Done and All tabs
|
|
207
|
+
// as she scrolls to their foot (w-fda2165ec6). Archived projects stay out,
|
|
208
|
+
// as they do of `listItems`.
|
|
209
|
+
listOlderItems({ offset = 0, limit = 50, now = Date.now() } = {}) {
|
|
210
|
+
const { workItemsDisk } = this.modules;
|
|
211
|
+
const all = [];
|
|
212
|
+
for (const product of this.listProducts()) {
|
|
213
|
+
let items = [];
|
|
214
|
+
try { items = workItemsDisk.readOlderWorkItems(product.dir, now); } catch { continue; }
|
|
215
|
+
for (const item of items) all.push({ ...item, product: product.slug, productName: product.name });
|
|
216
|
+
}
|
|
217
|
+
const finished = (i) => i.wrote?.status?.ts || i.updatedAt;
|
|
218
|
+
all.sort((a, b) => finished(b) - finished(a));
|
|
219
|
+
return { items: all.slice(offset, offset + limit), more: all.length > offset + limit };
|
|
220
|
+
}
|
|
221
|
+
|
|
152
222
|
// One item, folded fresh off disk. The supervisor holds a SPAWN-TIME snapshot
|
|
153
223
|
// for the whole life of a session, so it cannot ask what the session did to
|
|
154
224
|
// the row without reading it again; that question is the difference between a
|
|
@@ -254,7 +324,11 @@ export class Store {
|
|
|
254
324
|
//
|
|
255
325
|
// Undefined means she never opened the drawer and the row keeps what it had.
|
|
256
326
|
// Null means she cleared it, and the engine chooses again.
|
|
257
|
-
|
|
327
|
+
//
|
|
328
|
+
// `inReplyTo` makes the answer a reply in a thread (w-920461cbe6): the uid
|
|
329
|
+
// of the message it answers, on THE SAME LINE as the words, so the reply's
|
|
330
|
+
// own uid (what its reactions hang off) is the line that says where it goes.
|
|
331
|
+
answerItem(slug, id, { answer, status, priority, permissionMode, model, effort, inReplyTo }) {
|
|
258
332
|
const { workItemsDisk } = this.modules;
|
|
259
333
|
const dir = this.productDir(slug);
|
|
260
334
|
let item = null;
|
|
@@ -269,7 +343,7 @@ export class Store {
|
|
|
269
343
|
// special-cased in both engines' argument builders.
|
|
270
344
|
if (model !== undefined) item = workItemsDisk.updateWorkItem(dir, id, { model: model ?? '' }, { source: 'founder' });
|
|
271
345
|
if (effort !== undefined) item = workItemsDisk.updateWorkItem(dir, id, { effort: effort ?? '' }, { source: 'founder' });
|
|
272
|
-
if (answer) item = workItemsDisk.updateWorkItem(dir, id, { answer }, { source: 'founder' });
|
|
346
|
+
if (answer) item = workItemsDisk.updateWorkItem(dir, id, inReplyTo ? { answer, inReplyTo } : { answer }, { source: 'founder' });
|
|
273
347
|
// The pane may still show a running row after Stop or a provider result.
|
|
274
348
|
// Resolve omitted reply status against the ledger that was just written.
|
|
275
349
|
if (status == null && typeof answer === 'string' && answer.trim() && answer !== '(withdrawn)'
|
|
@@ -308,6 +382,28 @@ export class Store {
|
|
|
308
382
|
return workItemsDisk.updateWorkItem(dir, id, { status }, { source: 'system' });
|
|
309
383
|
}
|
|
310
384
|
|
|
385
|
+
// THE APP SHIPPED A TASK, OR COULD NOT (main/ship-queue.mjs). Both are the
|
|
386
|
+
// app's own words, so both go in as 'system', as a note: the reply field is
|
|
387
|
+
// hers. A failure reopens the row, and the queue hands the script's words
|
|
388
|
+
// straight to the agent that wrote the change.
|
|
389
|
+
shipped(slug, id, { sha, labels }) {
|
|
390
|
+
const { workItemsDisk } = this.modules;
|
|
391
|
+
const dir = this.productDir(slug);
|
|
392
|
+
const note = sha ? `Shipped to main as ${sha}.` : 'Shipped to main.';
|
|
393
|
+
return workItemsDisk.updateWorkItem(dir, id, { labels, note }, { source: 'system' });
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
shipFailed(slug, id, { note, labels }) {
|
|
397
|
+
const { workItemsDisk } = this.modules;
|
|
398
|
+
const dir = this.productDir(slug);
|
|
399
|
+
workItemsDisk.updateWorkItem(dir, id, { labels, note }, { source: 'system' });
|
|
400
|
+
return workItemsDisk.updateWorkItem(dir, id, { status: 'open' }, { source: 'system' });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
releaseRunClaim(slug, id, run) {
|
|
404
|
+
return this.modules.workItemsDisk.releaseRunClaim(this.productDir(slug), id, run);
|
|
405
|
+
}
|
|
406
|
+
|
|
311
407
|
// THE WRITTEN NAME THE LIST DRAWS. Written by the app rather than by a
|
|
312
408
|
// session, on agent authority, and never onto `title`: the title is what she
|
|
313
409
|
// typed and the fold keeps it hers (main/row-label.mjs says why at length).
|
|
@@ -320,6 +416,15 @@ export class Store {
|
|
|
320
416
|
return workItemsDisk.updateWorkItem(this.productDir(slug), id, { label: clean }, { source: 'agent' });
|
|
321
417
|
}
|
|
322
418
|
|
|
419
|
+
// How urgent a teammate's message is, as the sorter judged it
|
|
420
|
+
// (main/message-priority.mjs). On agent authority, so a level the person set
|
|
421
|
+
// by hand stays theirs.
|
|
422
|
+
prioritizeItem(slug, id, value) {
|
|
423
|
+
const { workItemsDisk } = this.modules;
|
|
424
|
+
if (!Number.isFinite(value)) return null;
|
|
425
|
+
return workItemsDisk.updateWorkItem(this.productDir(slug), id, { priority: value }, { source: 'agent' });
|
|
426
|
+
}
|
|
427
|
+
|
|
323
428
|
// A one-off permission mode has been spent. Called by the supervisor at the
|
|
324
429
|
// moment it launches the run that mode was set for, so a mode she chose for
|
|
325
430
|
// one message cannot silently govern a respawn three hours later.
|
|
@@ -366,10 +471,7 @@ export class Store {
|
|
|
366
471
|
// app restart, a reboot, and a store restored onto another machine, and so no
|
|
367
472
|
// agent line can quietly move a time she set. 0 clears it.
|
|
368
473
|
//
|
|
369
|
-
//
|
|
370
|
-
// out of the inbox and scheduling her own work to start at six. They were
|
|
371
|
-
// separate mechanisms before, and only the first existed: the old snooze was
|
|
372
|
-
// a renderer localStorage map that hid a row and deferred no work at all.
|
|
474
|
+
// Execution scheduling only. Inbox reminders use snoozeItem instead.
|
|
373
475
|
scheduleItem(slug, id, runAt) {
|
|
374
476
|
const { workItemsDisk } = this.modules;
|
|
375
477
|
return workItemsDisk.updateWorkItem(
|
|
@@ -377,7 +479,50 @@ export class Store {
|
|
|
377
479
|
);
|
|
378
480
|
}
|
|
379
481
|
|
|
380
|
-
|
|
482
|
+
// Put the existing thread back in the person's inbox later. This never
|
|
483
|
+
// changes its execution schedule, answer, status, claim or delivery marks.
|
|
484
|
+
snoozeItem(slug, id, snoozedUntil) {
|
|
485
|
+
if (!Number.isFinite(snoozedUntil)) throw new Error('Snooze needs a valid time');
|
|
486
|
+
return this.modules.workItemsDisk.updateWorkItem(
|
|
487
|
+
this.productDir(slug), id, { snoozedUntil: Math.max(0, Math.trunc(snoozedUntil)) }, { source: 'founder' },
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// WHO A SHARED ROW IS WITH, changed by a person: handed to an agent, kept,
|
|
492
|
+
// handed back, or passed on by a reply. Only the team fields, on the
|
|
493
|
+
// person's own authority, so an agent writing afterwards cannot undo it.
|
|
494
|
+
teamPatch(slug, id, patch) {
|
|
495
|
+
const allowed = Object.fromEntries(Object.entries(patch ?? {}).filter(([k]) => ['assignee', 'runner', 'due', 'people'].includes(k)));
|
|
496
|
+
if (!Object.keys(allowed).length) throw new Error('nothing to change');
|
|
497
|
+
return this.modules.workItemsDisk.updateWorkItem(this.productDir(slug), id, allowed, { source: 'founder' });
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// A REACTION ON ONE MESSAGE (w-560647d4db): the chips under it in a chat.
|
|
501
|
+
// `on` is the uid of the ledger line the message was written as, which is the
|
|
502
|
+
// one name for a message that is the same on every teammate's Mac. Written as
|
|
503
|
+
// the person's own word, and appended like anything else: the fold keeps a
|
|
504
|
+
// press per person rather than one value per row, so two people reacting at
|
|
505
|
+
// the same moment keep both chips (shared/work-items.mjs).
|
|
506
|
+
react(slug, id, { on, emoji, off = false }) {
|
|
507
|
+
return this.modules.workItemsDisk.updateWorkItem(
|
|
508
|
+
this.productDir(slug), id, { react: off ? { on, emoji, off: true } : { on, emoji } }, { source: 'founder' },
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// AN EDIT TO A THREAD FROM ITS SUMMARY (approved 2026-10-01): the summary's
|
|
513
|
+
// three lines and its links, who sees it, and its priority. Written as the
|
|
514
|
+
// person's own words; on the summary the later write wins either way
|
|
515
|
+
// (SHARED_FIELDS in shared/work-items.mjs).
|
|
516
|
+
threadEdit(slug, id, patch) {
|
|
517
|
+
// `start` is how a thread leaves Later: the summary and the thread page
|
|
518
|
+
// both write 'now' on it, which is what makes `isDue` true again.
|
|
519
|
+
const fields = ['problem', 'progress', 'solution', 'visibility', 'visibleTo', 'priority', 'blockedBy', 'blocks', 'start'];
|
|
520
|
+
const allowed = Object.fromEntries(Object.entries(patch ?? {}).filter(([k]) => fields.includes(k)));
|
|
521
|
+
if (!Object.keys(allowed).length) throw new Error('nothing to change');
|
|
522
|
+
return this.modules.workItemsDisk.updateWorkItem(this.productDir(slug), id, allowed, { source: 'founder' });
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
composeItem(slug, { title, body, kind = 'directive', priority = 0, labels, runAt, start, engine, model, effort, assignee, due, people, visibility, visibleTo }) {
|
|
381
526
|
const { workItemsDisk } = this.modules;
|
|
382
527
|
const dir = this.productDir(slug);
|
|
383
528
|
// The 'founder' label is the human-in-the-loop marker: only items the
|
|
@@ -396,6 +541,9 @@ export class Store {
|
|
|
396
541
|
title,
|
|
397
542
|
...(body ? { body } : {}),
|
|
398
543
|
...(runAt ? { runAt: Math.max(0, Math.trunc(runAt)) } : {}),
|
|
544
|
+
// "Add it to Later" on the card: the thread exists now and starts when
|
|
545
|
+
// she says, which is the one answer on that menu with no clock in it.
|
|
546
|
+
...(start === 'later' ? { start: 'later' } : {}),
|
|
399
547
|
// WHICH MODEL SHE CHOSE FOR THIS ONE, on founder authority like the rest
|
|
400
548
|
// of her content (2026-08-26).It is a Claude Code alias, and the spawn
|
|
401
549
|
// hands it to `--model`.
|
|
@@ -417,8 +565,27 @@ export class Store {
|
|
|
417
565
|
// whole gate exists for. Whether the word may be written at all is decided
|
|
418
566
|
// before it gets here, by `Supervisor#engineOffered`.
|
|
419
567
|
...(engine ? { engine: String(engine) } : {}),
|
|
568
|
+
// A TASK GIVEN TO A PERSON (the team version): who has to do it and
|
|
569
|
+
// when. No agent runs on it until that person hands it to one
|
|
570
|
+
// (shared/team-rules.mjs), and it reaches their inbox, not the sender's.
|
|
571
|
+
...(assignee ? { assignee: String(assignee) } : {}),
|
|
572
|
+
...(due ? { due: String(due) } : {}),
|
|
573
|
+
...(Array.isArray(people) && people.length ? { people } : {}),
|
|
574
|
+
// WHO SEES IT (approved 2026-10-01): the team by default, nobody but its
|
|
575
|
+
// owner, or the people she chose (w-41ff964775). No field at all reads
|
|
576
|
+
// as the team, except in your personal project, where it reads as Only
|
|
577
|
+
// you (w-b989839656). So Team is written when it was chosen, which is
|
|
578
|
+
// how a thread in My Workspace is shared on purpose.
|
|
579
|
+
...(visibility === 'private' || visibility === 'team' ? { visibility } : {}),
|
|
580
|
+
...(visibility === 'people' && Array.isArray(visibleTo) && visibleTo.length
|
|
581
|
+
? { visibility, visibleTo: [...new Set(visibleTo.map(String))] } : {}),
|
|
420
582
|
};
|
|
421
|
-
|
|
583
|
+
// WITH ITS PROJECT ON IT, the way `readItem` answers. The fold never carries
|
|
584
|
+
// one, and the renderer withdraws a just-made task with `made.product`, so
|
|
585
|
+
// without this Z after a send asked for "no such product: undefined" and
|
|
586
|
+
// reached past the task to whatever was under it (w-c78d1e1607).
|
|
587
|
+
const made = workItemsDisk.updateWorkItem(dir, created.id, contentPatch, { source: 'founder' });
|
|
588
|
+
return made ? { ...made, product: slug } : made;
|
|
422
589
|
}
|
|
423
590
|
|
|
424
591
|
/* ---------------------------- repeating tasks --------------------------- */
|
|
@@ -508,6 +675,9 @@ export class Store {
|
|
|
508
675
|
name,
|
|
509
676
|
createdAt: new Date().toISOString(),
|
|
510
677
|
...(repo ? { repoPath: repo } : {}),
|
|
678
|
+
// EVERY NEW PROJECT STARTS AS JUST YOU (w-b989839656): its threads
|
|
679
|
+
// reach the Team page only once you share the project or the thread.
|
|
680
|
+
seenBy: 'private',
|
|
511
681
|
}, null, 2));
|
|
512
682
|
// AND IT FILES NOTHING INTO HER INBOX.
|
|
513
683
|
//
|
|
@@ -761,6 +931,47 @@ export class Store {
|
|
|
761
931
|
return { ids };
|
|
762
932
|
}
|
|
763
933
|
|
|
934
|
+
/**
|
|
935
|
+
* THE REST OF THE PRACTICE TEAM'S WORK, so the tour and the board are not
|
|
936
|
+
* empty (w-58c8f466e7). The copy, and which column each row lands in, is
|
|
937
|
+
* `PRACTICE_BACKDROP` in shared/first-run-practice.mjs. The ids come back in
|
|
938
|
+
* its order, which is how `walkRows` knows which one is which.
|
|
939
|
+
*
|
|
940
|
+
* The column is the app's own reading of the row, so each is written in the
|
|
941
|
+
* shape that reads that way and nothing marks it: a finished one has a
|
|
942
|
+
* result, a working one is her ask with a run and no result yet, and the
|
|
943
|
+
* scheduled one is her ask with a time in the future, set as hers. */
|
|
944
|
+
stagePracticeBackdrop(slug, { now = Date.now(), label = 'first-run' } = {}) {
|
|
945
|
+
const { workItemsDisk } = this.modules;
|
|
946
|
+
const dir = this.productDir(slug);
|
|
947
|
+
const ids = [];
|
|
948
|
+
for (const row of PRACTICE_BACKDROP) {
|
|
949
|
+
const at = stampsFor(row, now);
|
|
950
|
+
const created = workItemsDisk.createWorkItem(
|
|
951
|
+
dir,
|
|
952
|
+
{ title: row.title, kind: row.kind, priority: 5, labels: ['founder', label] },
|
|
953
|
+
{ source: 'system', now: at.hers },
|
|
954
|
+
);
|
|
955
|
+
workItemsDisk.updateWorkItem(
|
|
956
|
+
dir, created.id,
|
|
957
|
+
{ title: row.title, body: row.body, ...(row.state === 'scheduled' ? { runAt: now + (row.inMs ?? 0) } : {}) },
|
|
958
|
+
{ source: 'founder', now: at.hers },
|
|
959
|
+
);
|
|
960
|
+
if (row.state !== 'scheduled') {
|
|
961
|
+
this.appendPracticeRun(slug, created.id, at.hers);
|
|
962
|
+
this.writePracticeTrace(slug, created.id, row.trace, at.hers + 1_000);
|
|
963
|
+
}
|
|
964
|
+
if (row.state === 'needs') {
|
|
965
|
+
workItemsDisk.updateWorkItem(
|
|
966
|
+
dir, created.id, { result: row.result },
|
|
967
|
+
{ source: 'agent', now: at.hers + 1_000 + row.trace.length * 9_000 + 1_000 },
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
ids.push(created.id);
|
|
971
|
+
}
|
|
972
|
+
return { ids };
|
|
973
|
+
}
|
|
974
|
+
|
|
764
975
|
/**
|
|
765
976
|
* THE CLAIM AND THE RELEASE, so the row reads as one an agent worked on.
|
|
766
977
|
*
|
|
@@ -840,9 +1051,23 @@ export class Store {
|
|
|
840
1051
|
// was for. Only when a line was really found: the sentence that says a folder
|
|
841
1052
|
// has no readme in it is a true answer and it is not a description, so it is
|
|
842
1053
|
// never written under the project's name.
|
|
843
|
-
finishFirstRunTask(slug, id) {
|
|
1054
|
+
finishFirstRunTask(slug, id, round = 0) {
|
|
844
1055
|
const { workItemsDisk } = this.modules;
|
|
845
1056
|
const product = this.listProducts().find((p) => p.slug === slug) ?? null;
|
|
1057
|
+
// A REPLY TO IT IS ANSWERED HERE TOO (2026-10-06). The first answer offers
|
|
1058
|
+
// to make the summary shorter; any reply after that gets the shorter one,
|
|
1059
|
+
// and no second trace, because the run it did is already on the thread.
|
|
1060
|
+
// AND THE REPLY IS SETTLED FIRST, the way the supervisor settles one when a
|
|
1061
|
+
// session exits: without it the row reads as an answer still waiting for an
|
|
1062
|
+
// agent and stays in In progress (`answerSettled`, shared/answers.mjs).
|
|
1063
|
+
if (product?.practice && round > 0) {
|
|
1064
|
+
const before = this.readItem(slug, id);
|
|
1065
|
+
this.settleAnswer(slug, id, before?.wrote?.answer?.ts ?? 0);
|
|
1066
|
+
const item = workItemsDisk.updateWorkItem(
|
|
1067
|
+
this.productDir(slug), id, { result: PRACTICE_REPLY_ANSWER }, { source: 'agent' },
|
|
1068
|
+
);
|
|
1069
|
+
return { item, line: PRACTICE_REPLY_ANSWER, oneLiner: null };
|
|
1070
|
+
}
|
|
846
1071
|
// AND SINCE 2026-08-23 THE TASK IS SENT IN THE PRACTICE PROJECT, which
|
|
847
1072
|
// points at no folder at all. Everything below reads one, so it cannot run
|
|
848
1073
|
// here: a project that is not theirs has nothing honest to say about their
|
|
@@ -1231,6 +1456,13 @@ export class Store {
|
|
|
1231
1456
|
if (file && !/work-items\.jsonl|dashboard\.jsonl|project\.json/.test(file)) return;
|
|
1232
1457
|
this._notify();
|
|
1233
1458
|
});
|
|
1459
|
+
// fs.watch may return successfully, then emit an error (for example
|
|
1460
|
+
// EMFILE). An unhandled error takes down the app, outside this catch.
|
|
1461
|
+
// Reads still work without a watcher; release only the failed one.
|
|
1462
|
+
watcher.on('error', () => {
|
|
1463
|
+
try { watcher.close(); } catch {}
|
|
1464
|
+
this.watchers = this.watchers.filter((w) => w !== watcher);
|
|
1465
|
+
});
|
|
1234
1466
|
this.watchers.push(watcher);
|
|
1235
1467
|
} catch {}
|
|
1236
1468
|
}
|