flowviant 0.116.0 → 0.124.0
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 +66 -12
- package/bin/cli.mjs +50 -17
- package/bin/lib/admission.mjs +7 -14
- package/bin/lib/agentAsk.mjs +123 -0
- package/bin/lib/agentBrief.mjs +36 -0
- package/bin/lib/agentFootprint.mjs +76 -0
- package/bin/lib/agentInternalWait.mjs +83 -0
- package/bin/lib/agentPlan.mjs +134 -31
- package/bin/lib/agentPrerequisiteContract.mjs +43 -0
- package/bin/lib/agentPublish.mjs +12 -13
- package/bin/lib/agentTaskKinds.mjs +5 -8
- package/bin/lib/agySessions.mjs +3 -3
- package/bin/lib/artifactContracts.mjs +91 -20
- package/bin/lib/artifactIteration.mjs +182 -0
- package/bin/lib/artifactPreview.mjs +92 -18
- package/bin/lib/artifactRecovery.mjs +36 -0
- package/bin/lib/artifactReporter.mjs +194 -22
- package/bin/lib/artifacts.mjs +70 -63
- package/bin/lib/authproxy.mjs +2 -1
- package/bin/lib/childEnv.mjs +9 -4
- package/bin/lib/claude.mjs +10 -20
- package/bin/lib/claudeAuth.mjs +15 -5
- package/bin/lib/claudePersonal.mjs +71 -6
- package/bin/lib/claudePosture.mjs +227 -54
- package/bin/lib/claudeSessions.mjs +7 -7
- package/bin/lib/claudeStream.mjs +27 -13
- package/bin/lib/cliLimit.mjs +2 -2
- package/bin/lib/codexHome.mjs +17 -0
- package/bin/lib/codexPersonal.mjs +44 -3
- package/bin/lib/codexProjectRead.mjs +63 -0
- package/bin/lib/codexRelay.mjs +21 -5
- package/bin/lib/codexResearch.mjs +193 -0
- package/bin/lib/commandSpecs.mjs +6 -0
- package/bin/lib/config.mjs +37 -24
- package/bin/lib/conversationIds.mjs +51 -0
- package/bin/lib/credentials.mjs +2 -7
- package/bin/lib/daemonLocks.mjs +90 -0
- package/bin/lib/daemonState.mjs +5 -5
- package/bin/lib/deploy.mjs +6 -9
- package/bin/lib/deployCheckout.mjs +26 -22
- package/bin/lib/deployRunner.mjs +54 -18
- package/bin/lib/deployWire.mjs +32 -23
- package/bin/lib/desktopContract.mjs +6 -5
- package/bin/lib/discoveryContract.mjs +19 -0
- package/bin/lib/env.mjs +104 -35
- package/bin/lib/fleet.mjs +53 -13
- package/bin/lib/fleetPost.mjs +172 -25
- package/bin/lib/fleetReports.mjs +172 -171
- package/bin/lib/fleetStartup.mjs +6 -6
- package/bin/lib/fleetWire.mjs +42 -0
- package/bin/lib/git.mjs +111 -61
- package/bin/lib/gitMergeIdentity.mjs +2 -8
- package/bin/lib/grant.mjs +23 -7
- package/bin/lib/holder.mjs +108 -81
- package/bin/lib/hooks/mcpAllow.mjs +60 -0
- package/bin/lib/hooks/readGuard.mjs +54 -11
- package/bin/lib/imageArtifact.mjs +74 -0
- package/bin/lib/install.mjs +80 -33
- package/bin/lib/instance.mjs +36 -83
- package/bin/lib/intakeDraft.mjs +4 -1
- package/bin/lib/knowledgeFileSync.mjs +31 -0
- package/bin/lib/knowledgeFiles.mjs +28 -83
- package/bin/lib/landed.mjs +22 -29
- package/bin/lib/launchCommand.mjs +91 -0
- package/bin/lib/listeners.mjs +0 -5
- package/bin/lib/localSessions.mjs +52 -39
- package/bin/lib/login.mjs +22 -12
- package/bin/lib/loginPath.mjs +22 -17
- package/bin/lib/machineListing.mjs +6 -3
- package/bin/lib/maintenance.mjs +47 -0
- package/bin/lib/maintenanceInventory.mjs +39 -0
- package/bin/lib/mcp-cli.mjs +9 -10
- package/bin/lib/mcpInstall.mjs +163 -0
- package/bin/lib/mcpInventory.mjs +119 -0
- package/bin/lib/noWindowEnv.mjs +23 -3
- package/bin/lib/ownedDirs.mjs +99 -0
- package/bin/lib/pinnedCapture.mjs +383 -0
- package/bin/lib/planDependencies.mjs +70 -0
- package/bin/lib/postureTools.mjs +85 -0
- package/bin/lib/prWorkflow.mjs +94 -17
- package/bin/lib/preflight.mjs +8 -25
- package/bin/lib/preview.mjs +19 -25
- package/bin/lib/previewGatePolicy.mjs +34 -9
- package/bin/lib/previewRegistry.mjs +4 -11
- package/bin/lib/printable.mjs +70 -7
- package/bin/lib/procRegistry.mjs +33 -13
- package/bin/lib/procScan.mjs +2 -3
- package/bin/lib/procStat.mjs +76 -0
- package/bin/lib/processGroup.mjs +48 -0
- package/bin/lib/processes.mjs +10 -24
- package/bin/lib/projectToolRuntimes.mjs +328 -19
- package/bin/lib/projectTools.mjs +109 -34
- package/bin/lib/prompts.mjs +205 -60
- package/bin/lib/repoPath.mjs +19 -0
- package/bin/lib/repoState.mjs +17 -19
- package/bin/lib/repositoryBaseline.mjs +68 -0
- package/bin/lib/runTurn.mjs +286 -209
- package/bin/lib/runtimeAntigravity.mjs +16 -5
- package/bin/lib/runtimeCapabilities.mjs +215 -21
- package/bin/lib/runtimeClaude.mjs +78 -10
- package/bin/lib/runtimeCodex.mjs +217 -209
- package/bin/lib/runtimeDetection.mjs +8 -2
- package/bin/lib/runtimeEvents.mjs +28 -11
- package/bin/lib/runtimeLimits.mjs +5 -6
- package/bin/lib/runtimeOfferings.mjs +99 -14
- package/bin/lib/runtimes.mjs +10 -0
- package/bin/lib/shipSweep.mjs +5 -3
- package/bin/lib/shot.mjs +91 -17
- package/bin/lib/sleep.mjs +11 -0
- package/bin/lib/startProject.mjs +5 -5
- package/bin/lib/statePaths.mjs +40 -0
- package/bin/lib/tty.mjs +25 -15
- package/bin/lib/turnDiagnostics.mjs +93 -0
- package/bin/lib/turnLock.mjs +7 -14
- package/bin/lib/turnProfile.mjs +16 -4
- package/bin/lib/ui.mjs +0 -3
- package/bin/lib/uninstall.mjs +30 -26
- package/bin/lib/update.mjs +79 -27
- package/bin/lib/uplinkScrub.mjs +296 -31
- package/bin/lib/vault.mjs +78 -10
- package/bin/lib/vaultDiff.mjs +5 -2
- package/bin/lib/views.mjs +37 -12
- package/bin/lib/wikiRunner.mjs +40 -61
- package/bin/lib/work.mjs +44 -13
- package/bin/lib/workAdoptCarry.mjs +2 -4
- package/bin/lib/workAgentBrief.mjs +92 -0
- package/bin/lib/workAgentBriefRunner.mjs +59 -0
- package/bin/lib/workAgentCheck.mjs +104 -57
- package/bin/lib/workAgentMerges.mjs +62 -57
- package/bin/lib/workAgentPlans.mjs +82 -199
- package/bin/lib/workAgentPrecheck.mjs +76 -138
- package/bin/lib/workAgentPublish.mjs +6 -8
- package/bin/lib/workAgentTurnBegun.mjs +15 -11
- package/bin/lib/workAgentTurnExecution.mjs +131 -67
- package/bin/lib/workAgentTurnOutcome.mjs +11 -2
- package/bin/lib/workAgentTurnReports.mjs +9 -37
- package/bin/lib/workAgentTurns.mjs +64 -7
- package/bin/lib/workArtifactReconcile.mjs +79 -0
- package/bin/lib/workAttachments.mjs +66 -11
- package/bin/lib/workBrain.mjs +9 -4
- package/bin/lib/workCappedTurn.mjs +119 -0
- package/bin/lib/workChildren.mjs +5 -5
- package/bin/lib/workCommandAudit.mjs +18 -2
- package/bin/lib/workDiffs.mjs +52 -6
- package/bin/lib/workIntake.mjs +35 -60
- package/bin/lib/workPlaceReports.mjs +7 -4
- package/bin/lib/workPlaces.mjs +113 -31
- package/bin/lib/workPreviews.mjs +47 -112
- package/bin/lib/workProcesses.mjs +8 -35
- package/bin/lib/workPullRequests.mjs +37 -56
- package/bin/lib/workReportQueue.mjs +100 -148
- package/bin/lib/workRetire.mjs +2 -1
- package/bin/lib/workSessionRuntime.mjs +38 -37
- package/bin/lib/workSessionTurns.mjs +256 -199
- package/bin/lib/workShip.mjs +43 -23
- package/bin/lib/workToolLog.mjs +70 -3
- package/bin/lib/workTurnShape.mjs +165 -0
- package/npm-shrinkwrap.json +69 -0
- package/package.json +9 -4
- package/bin/lib/fleetJobs.mjs +0 -120
- package/bin/lib/patchRevert.mjs +0 -60
package/bin/lib/views.mjs
CHANGED
|
@@ -7,32 +7,37 @@
|
|
|
7
7
|
* status line for a server that did not answer is left out, never guessed.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
11
|
import { closeSync, existsSync, openSync, readSync, statSync } from 'node:fs';
|
|
12
|
-
import {
|
|
12
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
13
|
+
import { APP_URL, FLEET_URL, USER_AGENT, VERSION } from './config.mjs';
|
|
13
14
|
import { detectRepoRoot, listStoredProjects, projectLabel, resolveStoredCredential } from './credentials.mjs';
|
|
14
15
|
import { desktopStatusRemote, installChannel } from './desktopContract.mjs';
|
|
15
16
|
import { daemonLogPath } from './daemonState.mjs';
|
|
17
|
+
import { git as runGit } from './git.mjs';
|
|
16
18
|
import { daemonRunningFor } from './instance.mjs';
|
|
17
19
|
import { agoFrom } from './machineListing.mjs';
|
|
20
|
+
import { printable, printableText } from './printable.mjs';
|
|
18
21
|
import { detectRuntimes } from './runtimeDetection.mjs';
|
|
19
22
|
import { c } from './ui.mjs';
|
|
20
23
|
import { versionBelow } from './versionOrder.mjs';
|
|
21
24
|
|
|
22
|
-
const APP_URL = process.env.FLOWVIANT_APP_URL || 'https://app.flowviant.com';
|
|
23
|
-
|
|
24
25
|
// "Behind" in these views is versionBelow (versionOrder.mjs): an unreadable
|
|
25
26
|
// version is never marked older — a read-only view states nothing it cannot
|
|
26
27
|
// measure.
|
|
27
28
|
|
|
28
29
|
function agentLine(agent, now) {
|
|
29
|
-
|
|
30
|
+
// A member names an agent; this prints it to a terminal. Scrubbed like every
|
|
31
|
+
// other server string this daemon prints (2026-10-10, the audit's F151;
|
|
32
|
+
// see printable.mjs).
|
|
33
|
+
const name = printable(agent.name);
|
|
34
|
+
if (agent.asks) return `${name} · ${c.yellow('asks you a question')}`;
|
|
30
35
|
const since = agent.status === 'working' && !agent.parked && agent.since ? Date.parse(agent.since) : NaN;
|
|
31
36
|
const minutes = Number.isFinite(since) ? Math.max(0, Math.floor((now - since) / 60_000)) : null;
|
|
32
37
|
const took = minutes == null ? '' : ` ${minutes < 60 ? `${minutes}m` : `${Math.floor(minutes / 60)}h ${minutes % 60}m`}`;
|
|
33
38
|
const word = agent.parked ? 'parked' : agent.status;
|
|
34
39
|
const cards = `${agent.delivered} of ${agent.total} ${agent.total === 1 ? 'card' : 'cards'}${agent.delivered ? ' delivered' : ''}`;
|
|
35
|
-
return `${
|
|
40
|
+
return `${name} · ${word === 'working' ? c.blue(`working${took}`) : word} · ${cards}`;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
/** `flowviant status`, rendered from the same document `status --json --remote` prints. */
|
|
@@ -51,7 +56,7 @@ export function renderStatus(status, { now = Date.now() } = {}) {
|
|
|
51
56
|
lines.push(label('here', `not running — run ${c.cyan('flowviant')} in that repo`));
|
|
52
57
|
} else if (project.running === true) {
|
|
53
58
|
const serves = role === 'serving' ? c.green('serving')
|
|
54
|
-
: role === 'inactive' ? `inactive${remote?.servingBoxName ? ` — ${remote.servingBoxName} serves this project` : ''}`
|
|
59
|
+
: role === 'inactive' ? `inactive${remote?.servingBoxName ? ` — ${printable(remote.servingBoxName)} serves this project` : ''}`
|
|
55
60
|
: project.holder === 'serving' ? c.green('serving') : null;
|
|
56
61
|
lines.push(label('here', serves ? `running · ${serves}` : 'running'));
|
|
57
62
|
}
|
|
@@ -61,7 +66,9 @@ export function renderStatus(status, { now = Date.now() } = {}) {
|
|
|
61
66
|
if (agents && agents.length > 0) {
|
|
62
67
|
agents.forEach((agent, i) => lines.push(label(i === 0 ? 'agents' : '', agentLine(agent, now))));
|
|
63
68
|
}
|
|
64
|
-
|
|
69
|
+
// The server scrubs these two (machineBoxes.ts, machinePressure.ts); the
|
|
70
|
+
// CLI keeps its belt, as printable.mjs explains.
|
|
71
|
+
if (remote?.pressure?.reason) lines.push(label('waiting', `Waiting for the machine — ${printable(remote.pressure.reason)}.`));
|
|
65
72
|
for (const rt of project.runtimes ?? []) {
|
|
66
73
|
if (rt.parkedByLimit && rt.message) lines.push(label('limit', `${rt.id} hit its limit: “${rt.message}”`));
|
|
67
74
|
}
|
|
@@ -94,19 +101,36 @@ export function resolveProjectForView(argv = process.argv, cwd = process.cwd())
|
|
|
94
101
|
};
|
|
95
102
|
}
|
|
96
103
|
|
|
97
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* The last `count` lines of a file, read from its end — PRINTABLE (2026-10-10,
|
|
106
|
+
* the audit's F133). A daemon before this release logged member-written card
|
|
107
|
+
* titles raw, so a replay could carry an OSC 52 into whichever terminal runs
|
|
108
|
+
* `flowviant logs`. `printableText` keeps the lines' shape; a log written on a
|
|
109
|
+
* TTY loses its colours with everything else.
|
|
110
|
+
*/
|
|
98
111
|
export function tailLines(path, count) {
|
|
99
112
|
const size = statSync(path).size;
|
|
100
113
|
const want = Math.min(size, 256 * 1024);
|
|
101
114
|
const buf = Buffer.alloc(want);
|
|
102
115
|
const fd = openSync(path, 'r');
|
|
103
116
|
try { readSync(fd, buf, 0, want, size - want); } finally { closeSync(fd); }
|
|
104
|
-
const lines = buf.toString('utf8').split('\n');
|
|
117
|
+
const lines = printableText(buf.toString('utf8')).split('\n');
|
|
105
118
|
if (lines.at(-1) === '') lines.pop();
|
|
106
119
|
if (want < size) lines.shift(); // the first line is cut mid-way
|
|
107
120
|
return { text: lines.slice(-count).join('\n'), size };
|
|
108
121
|
}
|
|
109
122
|
|
|
123
|
+
/**
|
|
124
|
+
* What `logs -f` prints of each new chunk: decoded as a STREAM, because a read
|
|
125
|
+
* can end inside a multi-byte character, and scrubbed like the tail. A sequence
|
|
126
|
+
* split across two reads loses its ESC in the first and its terminator in the
|
|
127
|
+
* second, so what is left of it is plain text.
|
|
128
|
+
*/
|
|
129
|
+
export function createLogFollower() {
|
|
130
|
+
const decoder = new StringDecoder('utf8');
|
|
131
|
+
return (buf) => printableText(decoder.write(buf));
|
|
132
|
+
}
|
|
133
|
+
|
|
110
134
|
export async function runLogs(argv = process.argv) {
|
|
111
135
|
const which = resolveProjectForView(argv);
|
|
112
136
|
if (!which.entry) { console.error(which.message); process.exit(1); }
|
|
@@ -125,6 +149,7 @@ export async function runLogs(argv = process.argv) {
|
|
|
125
149
|
// Poll rather than fs.watch: the daemon rotates the file (a rename), and a
|
|
126
150
|
// watch on the old inode would go quiet exactly then.
|
|
127
151
|
let offset = existsSync(path) ? statSync(path).size : 0;
|
|
152
|
+
const followed = createLogFollower();
|
|
128
153
|
for (;;) {
|
|
129
154
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
130
155
|
if (!existsSync(path)) continue;
|
|
@@ -134,7 +159,7 @@ export async function runLogs(argv = process.argv) {
|
|
|
134
159
|
const buf = Buffer.alloc(size - offset);
|
|
135
160
|
const fd = openSync(path, 'r');
|
|
136
161
|
try { readSync(fd, buf, 0, buf.length, offset); } finally { closeSync(fd); }
|
|
137
|
-
process.stdout.write(buf);
|
|
162
|
+
process.stdout.write(followed(buf));
|
|
138
163
|
offset = size;
|
|
139
164
|
}
|
|
140
165
|
}
|
|
@@ -173,7 +198,7 @@ export async function runDoctor({ cwd = process.cwd(), fetchImpl = fetch } = {})
|
|
|
173
198
|
const add = (state, text, fix) => { if (state === false) failed++; lines.push(check(state, text, fix)); };
|
|
174
199
|
|
|
175
200
|
let git = null;
|
|
176
|
-
try { git =
|
|
201
|
+
try { git = runGit(['--version'], undefined, { timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] }); } catch { /* absent */ }
|
|
177
202
|
add(Boolean(git), git ?? 'git is not installed', 'Install git; agents work in git worktrees.');
|
|
178
203
|
|
|
179
204
|
const installed = detectRuntimes().filter((rt) => rt.installed);
|
package/bin/lib/wikiRunner.mjs
CHANGED
|
@@ -21,24 +21,23 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { existsSync } from 'node:fs';
|
|
24
|
-
import { execFileSync } from 'node:child_process';
|
|
25
24
|
import { homedir } from 'node:os';
|
|
26
25
|
import { join } from 'node:path';
|
|
27
26
|
import { FLEET_URL, FLEET_TOKEN, USER_AGENT } from './config.mjs';
|
|
28
|
-
import { git, resetWorktree, originSlug, isSafePathSegment } from './git.mjs';
|
|
27
|
+
import { git, gitOrNull, gitRaw, resetWorktree, originSlug, isSafePathSegment, SHA1_ABBREV_RE } from './git.mjs';
|
|
29
28
|
import { c, note, ok, warn } from './ui.mjs';
|
|
30
29
|
import { sawSentinel } from './claude.mjs';
|
|
31
30
|
import { runTurn as defaultRunTurn } from './runTurn.mjs';
|
|
32
31
|
import { SYSTEM_WIKI, WIKI_KICKOFF, SYSTEM_REGROUND, REGROUND_KICKOFF } from './prompts.mjs';
|
|
33
32
|
import { ensureVault, syncVault } from './vault.mjs';
|
|
34
33
|
import { scrub as envScrub } from './uplinkScrub.mjs';
|
|
35
|
-
import {
|
|
34
|
+
import { runtimeLabel } from './runtimes.mjs';
|
|
36
35
|
import { pickRuntimeFor as defaultPickRuntimeFor } from './runtimeDetection.mjs';
|
|
37
|
-
import { recordMcpServers, recordSkills } from './runtimeCapabilities.mjs';
|
|
38
36
|
import { THINK_MARKER } from './runtimeEvents.mjs';
|
|
39
37
|
import { fleetEndpoint } from './fleetWire.mjs';
|
|
40
|
-
import { postToFleet } from './fleetPost.mjs';
|
|
38
|
+
import { fleetHeaders, postToFleet } from './fleetPost.mjs';
|
|
41
39
|
import { addUsage } from './turnUsage.mjs';
|
|
40
|
+
import { printable } from './printable.mjs';
|
|
42
41
|
|
|
43
42
|
export function createWikiRunner({
|
|
44
43
|
repoRoot,
|
|
@@ -126,11 +125,7 @@ export function createWikiRunner({
|
|
|
126
125
|
try {
|
|
127
126
|
await fetch(WIKI_PROGRESS_URL, {
|
|
128
127
|
method: 'POST',
|
|
129
|
-
headers:
|
|
130
|
-
Authorization: `Bearer ${FLEET_TOKEN}`,
|
|
131
|
-
'User-Agent': USER_AGENT,
|
|
132
|
-
'Content-Type': 'application/json',
|
|
133
|
-
},
|
|
128
|
+
headers: fleetHeaders(),
|
|
134
129
|
signal: AbortSignal.timeout(15_000),
|
|
135
130
|
body: JSON.stringify(safe),
|
|
136
131
|
});
|
|
@@ -148,22 +143,10 @@ export function createWikiRunner({
|
|
|
148
143
|
* sweep queued, for the rest of the day. Best-effort: an older server 404s
|
|
149
144
|
* once and the TTL is still the backstop it always was.
|
|
150
145
|
*/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
headers: {
|
|
156
|
-
Authorization: `Bearer ${FLEET_TOKEN}`,
|
|
157
|
-
'User-Agent': USER_AGENT,
|
|
158
|
-
'Content-Type': 'application/json',
|
|
159
|
-
},
|
|
160
|
-
signal: AbortSignal.timeout(15_000),
|
|
161
|
-
body: '{}',
|
|
162
|
-
});
|
|
163
|
-
} catch {
|
|
164
|
-
/* best-effort — the request's own TTL still expires it */
|
|
165
|
-
}
|
|
166
|
-
};
|
|
146
|
+
// No longer a near copy of `postToFleet` (decisions.md's 2026-09-26 open
|
|
147
|
+
// item, closed audit 2026-10-10 F136): the same post, on its own 15 s
|
|
148
|
+
// ceiling. Never throws — the request's own TTL still expires it.
|
|
149
|
+
const postWikiAbandoned = () => postToFleet(WIKI_ABANDONED_URL, {}, { timeoutMs: 15_000 });
|
|
167
150
|
|
|
168
151
|
const enqueueSweep = (job) => {
|
|
169
152
|
if (!job || job.requestedAt === lastSweepAt) return;
|
|
@@ -187,7 +170,19 @@ export function createWikiRunner({
|
|
|
187
170
|
type: 'reground',
|
|
188
171
|
intentId,
|
|
189
172
|
prUrl,
|
|
190
|
-
|
|
173
|
+
// A CARD'S TITLE, which any member writes (and intake can seed from a
|
|
174
|
+
// stranger's ticket), printed by the drain's narration six times and put
|
|
175
|
+
// in the prompt. SANITIZED AT THE INTAKE like the pages below (2026-10-10,
|
|
176
|
+
// the audit's F097): an OSC 52 in a title rewrote the operator's
|
|
177
|
+
// clipboard the moment the card shipped. printable() takes the whole
|
|
178
|
+
// display-control class, newlines included, so a title is one line.
|
|
179
|
+
title:
|
|
180
|
+
(typeof title === 'string'
|
|
181
|
+
? printable(title)
|
|
182
|
+
.replace(/\s+/g, ' ')
|
|
183
|
+
.trim()
|
|
184
|
+
.slice(0, 300)
|
|
185
|
+
: '') || 'a delivered task',
|
|
191
186
|
// What the PLAN thought this would invalidate. A hint, not the truth —
|
|
192
187
|
// the turn still reads the real changed files; this catches pages whose
|
|
193
188
|
// frontmatter file list has drifted, or that document a concept rather
|
|
@@ -239,13 +234,11 @@ export function createWikiRunner({
|
|
|
239
234
|
if (!Array.isArray(shas) || shas.length === 0) return null;
|
|
240
235
|
const files = new Set();
|
|
241
236
|
for (const sha of shas.slice(0, 50)) {
|
|
242
|
-
|
|
237
|
+
// git.mjs's SHA-1 shape, either case — the shape this lookup has
|
|
238
|
+
// always taken (audit 2026-10-10, F749).
|
|
239
|
+
if (!SHA1_ABBREV_RE.test(String(sha).toLowerCase())) continue;
|
|
243
240
|
try {
|
|
244
|
-
const out =
|
|
245
|
-
'git',
|
|
246
|
-
['show', '--name-only', '--pretty=format:', String(sha)],
|
|
247
|
-
{ cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }
|
|
248
|
-
);
|
|
241
|
+
const out = gitRaw(['show', '--name-only', '--pretty=format:', String(sha)], repoRoot);
|
|
249
242
|
for (const line of out.split('\n')) {
|
|
250
243
|
const f = line.trim();
|
|
251
244
|
if (f) files.add(f);
|
|
@@ -399,12 +392,8 @@ export function createWikiRunner({
|
|
|
399
392
|
}
|
|
400
393
|
}
|
|
401
394
|
resetWorktree(wikiWt, getBaseRef());
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
sha = git(['rev-parse', 'HEAD'], wikiWt);
|
|
405
|
-
} catch {
|
|
406
|
-
/* detached/no HEAD — still writes the map, just ungrounded */
|
|
407
|
-
}
|
|
395
|
+
// No HEAD → '' — still writes the map, just ungrounded.
|
|
396
|
+
const sha = gitOrNull(['rev-parse', 'HEAD'], wikiWt) ?? '';
|
|
408
397
|
// Sync the vault after the turn regardless of the sentinel: a died
|
|
409
398
|
// sweep's partial pages still persist (merge, no prune) — only a
|
|
410
399
|
// COMPLETED sweep finalizes, so an interrupted one can't erase pages.
|
|
@@ -441,7 +430,7 @@ export function createWikiRunner({
|
|
|
441
430
|
warn('wiki generation skipped — no installed CLI can run a vault-scoped turn');
|
|
442
431
|
return;
|
|
443
432
|
}
|
|
444
|
-
const wikiLabel =
|
|
433
|
+
const wikiLabel = runtimeLabel(wikiRt);
|
|
445
434
|
if (task.type === 'sweep') {
|
|
446
435
|
sweepAttempts++;
|
|
447
436
|
note(`${c.cyan('wiki')} ${c.dim(`— regenerating: your ${wikiLabel} is reading the repo…`)}`);
|
|
@@ -454,19 +443,11 @@ export function createWikiRunner({
|
|
|
454
443
|
vaultDir,
|
|
455
444
|
runtime: wikiRt,
|
|
456
445
|
label: c.cyan('[wiki]'),
|
|
457
|
-
|
|
446
|
+
// No skills or connectors are harvested off a wiki turn: it is
|
|
447
|
+
// fenced (no settings source, strict MCP), so its init names less
|
|
448
|
+
// than the machine has (runTurn.mjs, `initHarvestFor`).
|
|
458
449
|
onActivity,
|
|
459
450
|
onUsage: onUsage(wikiRt),
|
|
460
|
-
// FREE SKILLS, off a turn that was running anyway. This stream is
|
|
461
|
-
// already parsed and its init event already carries the CLI's own
|
|
462
|
-
// resolved skill set — the same fact a tab turn teaches — so the
|
|
463
|
-
// only thing missing was the handler. The wiki turn runs in a
|
|
464
|
-
// detached worktree of THIS repo, so its `.claude/skills` and the
|
|
465
|
-
// machine's personal ones resolve identically to a tab's.
|
|
466
|
-
onInit: (i) => {
|
|
467
|
-
recordSkills(i.skills);
|
|
468
|
-
recordMcpServers(i.mcpServers);
|
|
469
|
-
},
|
|
470
451
|
onSpawn: (ch) => {
|
|
471
452
|
wikiChild = ch;
|
|
472
453
|
},
|
|
@@ -512,14 +493,8 @@ export function createWikiRunner({
|
|
|
512
493
|
vaultDir,
|
|
513
494
|
runtime: wikiRt,
|
|
514
495
|
label: c.cyan('[wiki]'),
|
|
515
|
-
streamJson: true,
|
|
516
496
|
onActivity,
|
|
517
497
|
onUsage: onUsage(wikiRt),
|
|
518
|
-
// Same free harvest as the sweep above.
|
|
519
|
-
onInit: (i) => {
|
|
520
|
-
recordSkills(i.skills);
|
|
521
|
-
recordMcpServers(i.mcpServers);
|
|
522
|
-
},
|
|
523
498
|
onSpawn: (ch) => {
|
|
524
499
|
wikiChild = ch;
|
|
525
500
|
},
|
|
@@ -579,10 +554,14 @@ export function createWikiRunner({
|
|
|
579
554
|
// Terminal frame so the app cover clears promptly (don't wait for the
|
|
580
555
|
// freshness window to lapse). force-sent past the throttle.
|
|
581
556
|
await postWikiProgress(frame({ done: true, ...(usage ? { usage } : {}) }), true);
|
|
582
|
-
// Safety net: the wiki turn is read-only on the repo by CONTRACT
|
|
583
|
-
//
|
|
584
|
-
//
|
|
585
|
-
//
|
|
557
|
+
// Safety net: the wiki turn is read-only on the repo by CONTRACT.
|
|
558
|
+
// Claude's list writes only the vault (claudePosture.mjs
|
|
559
|
+
// `wikiPermFor`, 2026-10-10) and Codex's kernel profile likewise,
|
|
560
|
+
// but Antigravity's flags cannot say "only the vault" — so discard
|
|
561
|
+
// anything a turn wrote to the worktree before it can leak into a
|
|
562
|
+
// later turn or a push. It reaches the worktree ONLY: a write
|
|
563
|
+
// anywhere else is the posture's to refuse, never this reset's to
|
|
564
|
+
// undo.
|
|
586
565
|
try {
|
|
587
566
|
resetWorktree(wikiWt, getBaseRef());
|
|
588
567
|
} catch {
|
package/bin/lib/work.mjs
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* workChildren.mjs the live CLI children, teardown, the admission
|
|
34
34
|
* workAgentPublish.mjs pushing and fetching an agent's branch
|
|
35
35
|
* workPlaceReports.mjs the worktree readout: settle, burst, sweep
|
|
36
|
+
* workArtifactReconcile.mjs an idle agent's stored delivery against its worktree
|
|
36
37
|
* workReportQueue.mjs every settle, held and retried until delivered
|
|
37
38
|
* workSessionTokens.mjs the per-session work credential
|
|
38
39
|
* workNarration.mjs the tab's live line and the deferral sentence
|
|
@@ -40,17 +41,22 @@
|
|
|
40
41
|
* workSessionRuntime.mjs which CLI a tab speaks, which conversation resumes
|
|
41
42
|
* workAdoptCarry.mjs adoption's dirty carry
|
|
42
43
|
* workSessionTurns.mjs a tab's turn, start to settle
|
|
44
|
+
* workTurnShape.mjs what that turn is — plain, plan, capture or build —
|
|
45
|
+
* and its prompt pair (pure, 2026-10-10)
|
|
43
46
|
* workRetire.mjs held sessions, retirement, manual-place freshening
|
|
44
47
|
* workToolLog.mjs one turn's tool cards, folded and capped (pure)
|
|
45
48
|
* workCommandAudit.mjs one turn's `$ …` audit, batched to the server
|
|
46
49
|
* workBrain.mjs the model and effort a turn may put on argv (pure)
|
|
47
50
|
* workIntake.mjs one incoming ticket drafted into a card, read-only (2026-09-28)
|
|
51
|
+
* workCappedTurn.mjs one capped read-only consult turn: its child, its slot,
|
|
52
|
+
* its cap and its transcript — the planner, the pre-review
|
|
53
|
+
* and intake all run one (2026-10-10)
|
|
48
54
|
* turnLock.mjs whether a live CLI of ours holds a directory's turn
|
|
49
55
|
* (and, split earlier: workDiffs, workPreviews, workPullRequests, workShip,
|
|
50
56
|
* workAgentPlans, workAgentCheck, workAgentPrecheck, workAgentReview,
|
|
51
57
|
* workAgentTurns, workAgentMerges, workProcesses; and out of workAgentTurns,
|
|
52
|
-
* SOLID F036: workAgentTurnReports, workAgentTurnExecution,
|
|
53
|
-
*
|
|
58
|
+
* SOLID F036: workAgentTurnReports, workAgentTurnExecution, workAgentTurnBegun,
|
|
59
|
+
* workAgentTurnOutcome; the live brief: workAgentBrief, workAgentBriefRunner)
|
|
54
60
|
*/
|
|
55
61
|
|
|
56
62
|
import { createWorkProcesses } from './workProcesses.mjs';
|
|
@@ -71,6 +77,7 @@ import { createWorkChildren } from './workChildren.mjs';
|
|
|
71
77
|
import { createWorkAgentPublish } from './workAgentPublish.mjs';
|
|
72
78
|
import { createWorkPlaceReports } from './workPlaceReports.mjs';
|
|
73
79
|
import { createWorkReportQueue } from './workReportQueue.mjs';
|
|
80
|
+
import { postBestEffort } from './fleetPost.mjs';
|
|
74
81
|
import { createWorkSessionTokens } from './workSessionTokens.mjs';
|
|
75
82
|
import { createWorkNarration } from './workNarration.mjs';
|
|
76
83
|
import { createWorkAttachments } from './workAttachments.mjs';
|
|
@@ -86,6 +93,7 @@ import { warn } from './ui.mjs';
|
|
|
86
93
|
import { FLOWVIANT_OWN_PATHS } from './knowledgeLibrary.mjs';
|
|
87
94
|
import { buildArtifactUpload, changedSince, snapshotArtifacts } from './artifacts.mjs';
|
|
88
95
|
import { createArtifactReporter } from './artifactReporter.mjs';
|
|
96
|
+
import { createWorkArtifactReconcile } from './workArtifactReconcile.mjs';
|
|
89
97
|
import { renderDesignPreview } from './artifactPreview.mjs';
|
|
90
98
|
import { scrub as envScrub, secretIn as envSecretIn } from './uplinkScrub.mjs';
|
|
91
99
|
|
|
@@ -150,6 +158,13 @@ export function createWorkManager({
|
|
|
150
158
|
listChanged: changedSince,
|
|
151
159
|
buildUpload: buildArtifactUpload,
|
|
152
160
|
renderPreview: renderDesignPreview,
|
|
161
|
+
// What each place held, per owner, beside its worktree's git dir: it dies
|
|
162
|
+
// with the worktree, so a recreated one proves no discard (artifactIteration.mjs).
|
|
163
|
+
// `sessionMetaPath` and `worktreeBornAt` are declared below; both are called only once the manager is built.
|
|
164
|
+
seenPath: (dir, owner) =>
|
|
165
|
+
/^[A-Za-z0-9_-]{1,64}$/.test(String(owner)) ? sessionMetaPath(dir, 'flowviant-artifacts-seen', owner) : null,
|
|
166
|
+
// …and when that worktree was cut, so rows stored before it are never its own.
|
|
167
|
+
placeBornAt: (dir) => worktreeBornAt(dir),
|
|
153
168
|
});
|
|
154
169
|
/**
|
|
155
170
|
* Snapshot a place's artifact directory before a CLI spawns in it — and make
|
|
@@ -186,7 +201,7 @@ export function createWorkManager({
|
|
|
186
201
|
const gitNet = (args, ms) => gitNetIn(args, repoRoot, ms);
|
|
187
202
|
|
|
188
203
|
// ── THE SESSION'S OWN HALF — built first, because the lanes below are handed it.
|
|
189
|
-
const {
|
|
204
|
+
const { placeOf, notePlace, learnPlaces, placeDir, branchAt, placeWtFor, sessionMetaPath, worktreeBornAt } =
|
|
190
205
|
createWorkPlaces({ repoRoot, baseDir, baseRef, onRepoChanged });
|
|
191
206
|
const { sessionGroups, noteSessionGroup, pruneSessionGroups, sessionProcesses } =
|
|
192
207
|
createWorkSessionGroups({ repoRoot });
|
|
@@ -194,6 +209,15 @@ export function createWorkManager({
|
|
|
194
209
|
createWorkChildren({ extraLiveTurns });
|
|
195
210
|
const { agentPublished, agentRemoteAt, publishAgentBranch, fetchPublishedBranch } =
|
|
196
211
|
createWorkAgentPublish({ repoRoot, gitNet });
|
|
212
|
+
const { reconcileIdleArtifacts } = createWorkArtifactReconcile({
|
|
213
|
+
artifacts,
|
|
214
|
+
placeLocks,
|
|
215
|
+
inPlace,
|
|
216
|
+
placeDir,
|
|
217
|
+
beforeArtifacts,
|
|
218
|
+
getArtifactsAccepted,
|
|
219
|
+
log: (line) => warn(line),
|
|
220
|
+
});
|
|
197
221
|
const { reportSessionWorktree, reportPlaceWorktrees, burstListeners, reportWorktrees } =
|
|
198
222
|
createWorkPlaceReports({
|
|
199
223
|
repoRoot,
|
|
@@ -211,7 +235,6 @@ export function createWorkManager({
|
|
|
211
235
|
const {
|
|
212
236
|
pendingWorkReports,
|
|
213
237
|
pendingShipReports,
|
|
214
|
-
postBestEffort,
|
|
215
238
|
REJECT_RETRY_MS,
|
|
216
239
|
settleWorkTurn,
|
|
217
240
|
settleShip,
|
|
@@ -232,8 +255,9 @@ export function createWorkManager({
|
|
|
232
255
|
const { fetchAttachments, fetchAgentFiles } = createWorkAttachments();
|
|
233
256
|
const { sessionRuntime, agyRegistryLookup } = createWorkSessionRuntime({ sessionMetaPath });
|
|
234
257
|
const { carryDirtyState } = createAdoptCarry({ sessionMetaPath });
|
|
235
|
-
const { processWorkTurns, workAnswering } = createWorkSessionTurns({
|
|
258
|
+
const { processWorkTurns, interruptWorkTurns, workAnswering } = createWorkSessionTurns({
|
|
236
259
|
repoRoot,
|
|
260
|
+
baseRef,
|
|
237
261
|
baseDir,
|
|
238
262
|
getMcpUrl,
|
|
239
263
|
getArtifactsAccepted,
|
|
@@ -241,7 +265,7 @@ export function createWorkManager({
|
|
|
241
265
|
inPlace,
|
|
242
266
|
workChildren,
|
|
243
267
|
workAttempts,
|
|
244
|
-
|
|
268
|
+
notePlace,
|
|
245
269
|
placeOf,
|
|
246
270
|
placeWtFor,
|
|
247
271
|
sessionMetaPath,
|
|
@@ -282,8 +306,9 @@ export function createWorkManager({
|
|
|
282
306
|
|
|
283
307
|
const { processPrJobs } = createWorkPullRequests({
|
|
284
308
|
placeOf,
|
|
309
|
+
placeDir,
|
|
310
|
+
branchAt,
|
|
285
311
|
repoRoot,
|
|
286
|
-
baseDir,
|
|
287
312
|
baseRef,
|
|
288
313
|
gitNet,
|
|
289
314
|
landed,
|
|
@@ -298,6 +323,8 @@ export function createWorkManager({
|
|
|
298
323
|
baseDir,
|
|
299
324
|
gitNet,
|
|
300
325
|
baseRef,
|
|
326
|
+
placeDir,
|
|
327
|
+
branchAt,
|
|
301
328
|
placeWtFor,
|
|
302
329
|
sessionMetaPath,
|
|
303
330
|
shipping,
|
|
@@ -341,7 +368,7 @@ export function createWorkManager({
|
|
|
341
368
|
});
|
|
342
369
|
const { runReviewEntry } = createWorkAgentReview({ runCheck, runPrecheck });
|
|
343
370
|
|
|
344
|
-
const { processAgentTurnJobs, settleAgentTurns, agentTurns, agentChildren, agentReported } = createWorkAgentTurns({
|
|
371
|
+
const { processAgentTurnJobs, interruptAgentTurns, settleAgentTurns, agentTurns, agentChildren, agentReported } = createWorkAgentTurns({
|
|
345
372
|
REJECT_RETRY_MS,
|
|
346
373
|
baseRef,
|
|
347
374
|
inPlace,
|
|
@@ -433,7 +460,7 @@ export function createWorkManager({
|
|
|
433
460
|
return {
|
|
434
461
|
flushWorkReports,
|
|
435
462
|
learnPlaces,
|
|
436
|
-
processWorkTurns,
|
|
463
|
+
processWorkTurns, interruptWorkTurns,
|
|
437
464
|
processShipJobs,
|
|
438
465
|
processDiffJobs,
|
|
439
466
|
processKillJobs,
|
|
@@ -445,8 +472,10 @@ export function createWorkManager({
|
|
|
445
472
|
shutdownPreviews,
|
|
446
473
|
retireWorkSessions,
|
|
447
474
|
reportWorktrees,
|
|
475
|
+
reconcileIdleArtifacts,
|
|
448
476
|
shutdownWork,
|
|
449
477
|
workBusy,
|
|
478
|
+
runningAgentCount: () => agentTurns.size,
|
|
450
479
|
// The machine's own admission answer, and what it is counting. Handed to
|
|
451
480
|
// the loop so the lanes it owns — the wiki cartographer (wikiRunner.mjs) — ask the
|
|
452
481
|
// same question, and so the machine snapshot can charge memory to the work
|
|
@@ -456,10 +485,12 @@ export function createWorkManager({
|
|
|
456
485
|
liveTurnCount,
|
|
457
486
|
processAgentPlanJobs,
|
|
458
487
|
processIntakeJobs,
|
|
459
|
-
processAgentTurnJobs,
|
|
460
|
-
// Only the
|
|
461
|
-
//
|
|
462
|
-
//
|
|
488
|
+
processAgentTurnJobs, interruptAgentTurns,
|
|
489
|
+
// Only the roster's stand-down calls this — a displacement, a removal or a
|
|
490
|
+
// commanded stop (holder.mjs, `standDownDisplaced`); see its comment.
|
|
491
|
+
// Exported rather than hooked into `shutdownWork` because the signal
|
|
492
|
+
// handlers cannot await, and a settle that is not awaited is a settle that
|
|
493
|
+
// did not happen.
|
|
463
494
|
settleAgentTurns,
|
|
464
495
|
processAgentMergeJobs,
|
|
465
496
|
freshenManualPlaces,
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* promise of its own (the source is never written, a problem becomes one line
|
|
7
7
|
* for the agent to say), reached once, from the session lane's adopt path.
|
|
8
8
|
*/
|
|
9
|
-
import { execFileSync } from 'node:child_process';
|
|
10
9
|
import { cpSync, lstatSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
10
|
import { dirname, join } from 'node:path';
|
|
12
11
|
import { git, gitRaw, splitNul } from './git.mjs';
|
|
@@ -31,9 +30,8 @@ export function createAdoptCarry({ sessionMetaPath }) {
|
|
|
31
30
|
// text file) must round-trip byte-exact or the apply corrupts what it
|
|
32
31
|
// carries. 64MB of headroom — a dirtier tree than that fails the read
|
|
33
32
|
// here and is SAID, below, rather than half-applied.
|
|
34
|
-
const patch =
|
|
35
|
-
|
|
36
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
33
|
+
const patch = gitRaw(['diff', 'HEAD', '--binary'], srcCwd, {
|
|
34
|
+
encoding: 'buffer',
|
|
37
35
|
maxBuffer: 64 * 1024 * 1024,
|
|
38
36
|
});
|
|
39
37
|
if (patch.length) {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** A readout alongside ONE running CLI, never another worker on its branch.
|
|
2
|
+
* No events since the last snapshot means no model call. A stopped child,
|
|
3
|
+
* pause, park or lost holder prevents a call or a late result from publishing. */
|
|
4
|
+
import { AGENT_BRIEF_SYSTEM, parseAgentBrief, briefModelFor } from './agentBrief.mjs';
|
|
5
|
+
|
|
6
|
+
export const AGENT_BRIEF_INTERVAL_MS = 3 * 60_000;
|
|
7
|
+
export const AGENT_BRIEF_FIRST_MS = 20_000;
|
|
8
|
+
export const AGENT_BRIEF_INPUT_MAX = 24_000;
|
|
9
|
+
export function makeAgentBrief({ runtime, model, context, summarize, post, scrub = (s) => s, models = () => [], now = Date.now,
|
|
10
|
+
setTimer = setTimeout, clearTimer = clearTimeout }) {
|
|
11
|
+
let running = false;
|
|
12
|
+
let stopped = false;
|
|
13
|
+
let timer = null;
|
|
14
|
+
let sequence = 0;
|
|
15
|
+
let consumed = 0;
|
|
16
|
+
let entries = [];
|
|
17
|
+
let flight = null;
|
|
18
|
+
let controller = null;
|
|
19
|
+
let held = null;
|
|
20
|
+
const append = (kind, data) => {
|
|
21
|
+
if (stopped || data == null) return;
|
|
22
|
+
// Scrub before trimming so a cut never turns a secret into an unrecognized prefix.
|
|
23
|
+
let text;
|
|
24
|
+
try {
|
|
25
|
+
text = (typeof data === 'string' ? scrub(data) : JSON.stringify(data, (_key, value) => {
|
|
26
|
+
// Redact decoded strings BEFORE JSON escapes quotes and newlines;
|
|
27
|
+
// otherwise a multiline secret no longer matches its learned value.
|
|
28
|
+
if (typeof value === 'string') return scrub(value);
|
|
29
|
+
if (value && typeof value === 'object' && !Array.isArray(value))
|
|
30
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [scrub(key), item]));
|
|
31
|
+
return value;
|
|
32
|
+
})).slice(0, 4_000);
|
|
33
|
+
} catch { return; } // an optional readout must never fail the working CLI
|
|
34
|
+
if (!text.trim()) return;
|
|
35
|
+
entries.push({ seq: ++sequence, kind, text });
|
|
36
|
+
while (entries.length > 100 || entries.reduce((sum, e) => sum + e.text.length, 0) > AGENT_BRIEF_INPUT_MAX) entries.shift();
|
|
37
|
+
};
|
|
38
|
+
const schedule = (ms = AGENT_BRIEF_INTERVAL_MS) => {
|
|
39
|
+
if (timer || stopped || !running) return;
|
|
40
|
+
timer = setTimer(() => { timer = null; void tick(); }, ms);
|
|
41
|
+
timer?.unref?.();
|
|
42
|
+
};
|
|
43
|
+
const refresh = async () => {
|
|
44
|
+
if (!running || stopped || (!held && sequence === consumed)) return;
|
|
45
|
+
// Retry a paid-for reading without paying for another. The API rechecks
|
|
46
|
+
// holder and turn in the write, and duplicate/older snapshots cannot win.
|
|
47
|
+
if (held) { if (await post(held)) held = null; return; }
|
|
48
|
+
const brain = briefModelFor(runtime, typeof model === 'function' ? model() : model, models());
|
|
49
|
+
if (!brain) return;
|
|
50
|
+
controller = new AbortController();
|
|
51
|
+
const state = await context(controller.signal);
|
|
52
|
+
if (state?.unsupported) { stopped = true; if (timer) clearTimer(timer); timer = null; return; }
|
|
53
|
+
if (stopped || !running || !state?.active || controller.signal.aborted) return;
|
|
54
|
+
// Card changes and daemon restarts do not reset the agent's three-minute
|
|
55
|
+
// cadence. Both dates came from the server, not this machine's clock.
|
|
56
|
+
if (state.brief && Date.parse(state.sourceAt) - Date.parse(state.brief.updatedAt) < AGENT_BRIEF_INTERVAL_MS) return;
|
|
57
|
+
const through = sequence;
|
|
58
|
+
const events = entries.filter((e) => e.seq > consumed);
|
|
59
|
+
if (!events.length) return;
|
|
60
|
+
const prompt = JSON.stringify({ previous: state.brief ?? null, taskId: state.taskId ?? null,
|
|
61
|
+
omittedEvents: Math.max(0, events[0].seq - consumed - 1), events: events.map(({ kind, text }) => ({ kind, text })) });
|
|
62
|
+
const out = await summarize({ runtime, ...brain, system: AGENT_BRIEF_SYSTEM, prompt, signal: controller.signal });
|
|
63
|
+
if (stopped || !running || controller.signal.aborted) return;
|
|
64
|
+
const content = parseAgentBrief(typeof out === 'string' ? out : out?.out, scrub);
|
|
65
|
+
if (!content) return;
|
|
66
|
+
consumed = through;
|
|
67
|
+
entries = entries.filter((e) => e.seq > through);
|
|
68
|
+
held = { ...content, sourceAt: state.sourceAt ?? new Date(now()).toISOString(), ...(out?.usage ? { usage: out.usage } : {}) };
|
|
69
|
+
if (await post(held)) held = null;
|
|
70
|
+
};
|
|
71
|
+
const tick = () => {
|
|
72
|
+
if (flight) return flight;
|
|
73
|
+
flight = refresh().catch(() => {}).finally(() => { flight = null; controller = null; schedule(); });
|
|
74
|
+
return flight;
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
append, tick,
|
|
78
|
+
running(active) {
|
|
79
|
+
running = active;
|
|
80
|
+
if (active) schedule(AGENT_BRIEF_FIRST_MS);
|
|
81
|
+
else { if (timer) clearTimer(timer); timer = null; controller?.abort(); }
|
|
82
|
+
},
|
|
83
|
+
async stop() {
|
|
84
|
+
stopped = true;
|
|
85
|
+
running = false;
|
|
86
|
+
if (timer) clearTimer(timer);
|
|
87
|
+
timer = null;
|
|
88
|
+
controller?.abort();
|
|
89
|
+
await flight;
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|