flowviant 0.96.0 → 0.99.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/bin/cli.mjs +13 -3
- package/bin/lib/agentPlan.mjs +159 -37
- package/bin/lib/artifacts.mjs +174 -33
- package/bin/lib/authReject.mjs +32 -0
- package/bin/lib/authproxy.mjs +52 -16
- package/bin/lib/childEnv.mjs +33 -1
- package/bin/lib/claude.mjs +377 -45
- package/bin/lib/config.mjs +24 -1
- package/bin/lib/credentials.mjs +27 -4
- package/bin/lib/deploy.mjs +364 -30
- package/bin/lib/env.mjs +146 -12
- package/bin/lib/fleet.mjs +125 -30
- package/bin/lib/git.mjs +131 -3
- package/bin/lib/hooks/readGuard.mjs +162 -0
- package/bin/lib/instance.mjs +115 -14
- package/bin/lib/knowledge.mjs +460 -23
- package/bin/lib/landed.mjs +38 -30
- package/bin/lib/listeners.mjs +211 -0
- package/bin/lib/localSessions.mjs +29 -4
- package/bin/lib/login.mjs +2 -2
- package/bin/lib/machines.mjs +16 -7
- package/bin/lib/preview.mjs +95 -6
- package/bin/lib/printable.mjs +42 -0
- package/bin/lib/procRegistry.mjs +65 -0
- package/bin/lib/processes.mjs +20 -7
- package/bin/lib/projectTools.mjs +248 -0
- package/bin/lib/prompts.mjs +354 -32
- package/bin/lib/resources.mjs +46 -4
- package/bin/lib/runtimeOfferings.mjs +75 -0
- package/bin/lib/runtimes.mjs +152 -11
- package/bin/lib/shipMerge.mjs +8 -4
- package/bin/lib/trace.mjs +16 -6
- package/bin/lib/update.mjs +5 -0
- package/bin/lib/work.mjs +1261 -4476
- package/bin/lib/workAgentMerges.mjs +504 -0
- package/bin/lib/workAgentPlans.mjs +447 -0
- package/bin/lib/workAgentReview.mjs +702 -0
- package/bin/lib/workAgentTurns.mjs +1148 -0
- package/bin/lib/workDiffs.mjs +107 -0
- package/bin/lib/workPreviews.mjs +351 -0
- package/bin/lib/workProcesses.mjs +236 -0
- package/bin/lib/workPullRequests.mjs +313 -0
- package/bin/lib/workShip.mjs +385 -0
- package/bin/lib/worktreeDiff.mjs +99 -43
- package/package.json +2 -2
package/bin/lib/runtimes.mjs
CHANGED
|
@@ -342,11 +342,40 @@ function parseCodexLine(line, cwd) {
|
|
|
342
342
|
const activity = humanizeCodexItem(item, cwd);
|
|
343
343
|
// Only the agent's MESSAGES are sentinel-bearing text. Reasoning is not:
|
|
344
344
|
// a model that muses "I could output NOTHING here" must not end the turn.
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
345
|
+
const isMessage = (item.item_type ?? item.type) === 'agent_message';
|
|
346
|
+
const text = isMessage ? `${item.text ?? item.message ?? ''}\n` : '';
|
|
347
|
+
// `answer` is the SAME message, surfaced alone: `text` accumulates every
|
|
348
|
+
// message plus every error and stderr line, so a caller that needs "what
|
|
349
|
+
// the agent said LAST" (the agent lane's final JSON object) cannot recover
|
|
350
|
+
// it from `out`. Claude's stream has this for free under
|
|
351
|
+
// `answerFromResult`; codex needed it said.
|
|
352
|
+
return { activity, text, ...(isMessage ? { answer: String(item.text ?? item.message ?? '') } : {}) };
|
|
353
|
+
}
|
|
354
|
+
// THE TURN'S OWN TOKEN COUNT (2026-09-24). `codex exec --json` closes a
|
|
355
|
+
// turn with `turn.completed` carrying `usage` — the codex twin of Claude's
|
|
356
|
+
// `result.usage`, and until now dropped on the floor, so a codex agent
|
|
357
|
+
// reported no spend at all. Mapped onto the daemon's four counters:
|
|
358
|
+
// OpenAI's `input_tokens` INCLUDES the cached part, Claude's excludes it,
|
|
359
|
+
// so the cached share is subtracted out rather than counted twice. Codex
|
|
360
|
+
// has no cache-creation figure; zero is what it reported, not a guess.
|
|
361
|
+
// Output already includes reasoning tokens on OpenAI's side.
|
|
362
|
+
case 'turn.completed': {
|
|
363
|
+
const u = ev.usage;
|
|
364
|
+
if (!u || typeof u !== 'object') return null;
|
|
365
|
+
const n = (v) => (Number.isFinite(Number(v)) && Number(v) > 0 ? Math.floor(Number(v)) : 0);
|
|
366
|
+
const cached = n(u.cached_input_tokens);
|
|
367
|
+
return {
|
|
368
|
+
activity: null,
|
|
369
|
+
text: '',
|
|
370
|
+
// `usageFromResult`'s shape (claude.mjs), so every consumer of
|
|
371
|
+
// `onUsage` reads one vocabulary whichever CLI counted it.
|
|
372
|
+
usage: {
|
|
373
|
+
input: Math.max(0, n(u.input_tokens) - cached),
|
|
374
|
+
output: n(u.output_tokens),
|
|
375
|
+
cacheCreate: 0,
|
|
376
|
+
cacheRead: cached,
|
|
377
|
+
},
|
|
378
|
+
};
|
|
350
379
|
}
|
|
351
380
|
case 'turn.failed': {
|
|
352
381
|
// An error is prose too, and the sentence that explains a failed turn is
|
|
@@ -367,7 +396,12 @@ function parseCodexLine(line, cwd) {
|
|
|
367
396
|
label: oneLine(ev.error?.message ?? 'turn failed'),
|
|
368
397
|
...(msg.trim() ? { full: msg } : {}),
|
|
369
398
|
},
|
|
370
|
-
text
|
|
399
|
+
// THE FAILURE'S OWN WORDS REACH `text` (2026-09-24), the rule the bare
|
|
400
|
+
// `error` arm below already keeps. This returned '' — so a codex turn
|
|
401
|
+
// that failed on a usage limit handed the limit matcher nothing, the
|
|
402
|
+
// agent landed in Stuck as "produced no output", and every other codex
|
|
403
|
+
// agent kept spending into the same wall.
|
|
404
|
+
text: msg.trim() ? `${msg}\n` : '',
|
|
371
405
|
};
|
|
372
406
|
}
|
|
373
407
|
// A bare `error` event — the shape an auth failure arrives in ("401
|
|
@@ -543,14 +577,19 @@ export const RUNTIMES = {
|
|
|
543
577
|
* "consult" and "plan" currently mean. That is a statement about where the
|
|
544
578
|
* contract was written, not a claim that only Claude could ever satisfy it.
|
|
545
579
|
*/
|
|
546
|
-
|
|
580
|
+
// `design` and `research` (0.97.0) are Claude's alone: each is an
|
|
581
|
+
// `--allowedTools` list with a PATH-SCOPED write (claude.mjs), and neither
|
|
582
|
+
// codex's sandbox modes nor agy's flags were measured to express "write
|
|
583
|
+
// only this directory". A runtime that does not declare them is refused
|
|
584
|
+
// such a card before spawn, in words — never handed a build turn instead.
|
|
585
|
+
profiles: ['build', 'wiki', 'consult', 'plan', 'design', 'research'],
|
|
547
586
|
mcp: claudeMcp,
|
|
548
587
|
/**
|
|
549
588
|
* Claude takes the operating contract as a real system prompt, which is the
|
|
550
589
|
* strongest form of it available anywhere: `--append-system-prompt` sits
|
|
551
590
|
* above the conversation rather than inside it.
|
|
552
591
|
*/
|
|
553
|
-
args({ prompt, system, model, effort, resume, streamJson, perm, mcp = [], resultSchemaArgs = [], adoptResumeId, resumeThreadId, knowledgeDir }) {
|
|
592
|
+
args({ prompt, system, model, effort, resume, streamJson, perm, profile = 'build', mcp = [], resultSchemaArgs = [], adoptResumeId, resumeThreadId, knowledgeDir, agentTools }) {
|
|
554
593
|
const a = [];
|
|
555
594
|
// THREE ANSWERS TO ONE QUESTION — "what conversation is this?" — and they
|
|
556
595
|
// are mutually exclusive, never combined.
|
|
@@ -577,6 +616,17 @@ export const RUNTIMES = {
|
|
|
577
616
|
else if (resumeThreadId) a.push('--resume', resumeThreadId);
|
|
578
617
|
else if (resume) a.push('--continue');
|
|
579
618
|
a.push('-p', prompt, '--append-system-prompt', system);
|
|
619
|
+
if (agentTools) {
|
|
620
|
+
// Keep personal settings, but never load the agent-editable project's
|
|
621
|
+
// settings, hooks, skills or CLAUDE.md discovery. A resumed agent must
|
|
622
|
+
// render the CURRENT base snapshot, not the first turn's prompt cache.
|
|
623
|
+
a.push('--setting-sources', 'user', '--system-prompt-snapshot', 'off');
|
|
624
|
+
if (agentTools.pluginDir) a.push('--plugin-dir', agentTools.pluginDir);
|
|
625
|
+
}
|
|
626
|
+
// These fenced postures have no project MCP, even if Claude discovers a
|
|
627
|
+
// .mcp.json in the agent's worktree. Build gets its reviewed base config
|
|
628
|
+
// explicitly from the caller; the plan profile keeps its own principal.
|
|
629
|
+
if (['consult', 'wiki', 'design', 'research'].includes(profile)) a.push('--strict-mcp-config');
|
|
580
630
|
a.push(...mcp, ...resultSchemaArgs);
|
|
581
631
|
a.push('--model', model || MODEL);
|
|
582
632
|
if (effort) a.push('--effort', effort);
|
|
@@ -629,6 +679,11 @@ export const RUNTIMES = {
|
|
|
629
679
|
* user's repository, which is not a risk worth taking for a slightly
|
|
630
680
|
* stickier instruction.
|
|
631
681
|
*
|
|
682
|
+
* Agent turns additionally pass reviewed base instructions as a developer
|
|
683
|
+
* config override when the personal config has no developer instruction;
|
|
684
|
+
* the prompt always carries them too. Workbench tabs keep their normal
|
|
685
|
+
* checkout discovery.
|
|
686
|
+
*
|
|
632
687
|
* `--skip-git-repo-check` is deliberately NOT passed: a task always builds
|
|
633
688
|
* in a git worktree, and if it somehow is not one, failing loudly beats
|
|
634
689
|
* silently editing files nobody can diff.
|
|
@@ -639,7 +694,7 @@ export const RUNTIMES = {
|
|
|
639
694
|
* placed before it. Appending them after the positional is the kind of argv
|
|
640
695
|
* that parses today and stops parsing on some future clap upgrade.
|
|
641
696
|
*/
|
|
642
|
-
args({ prompt, system, model, effort, resume, resumeThreadId, profile = 'build', vaultDir, mcp = [], resultSchemaArgs = [], adoptResumeId }) {
|
|
697
|
+
args({ prompt, system, model, effort, resume, resumeThreadId, profile = 'build', vaultDir, mcp = [], resultSchemaArgs = [], adoptResumeId, agentTools }) {
|
|
643
698
|
// Adoption resumes a conversation in ITS OWN CLI's store (claude forks,
|
|
644
699
|
// agy moves) — codex has no adoptable store wired yet. Reaching here
|
|
645
700
|
// with an adopt id is a wiring mistake upstream, and it fails loudly on
|
|
@@ -656,6 +711,13 @@ export const RUNTIMES = {
|
|
|
656
711
|
if (resumeThreadId) a.push('resume', resumeThreadId);
|
|
657
712
|
else if (resume) a.push('resume', '--last');
|
|
658
713
|
a.push('--json');
|
|
714
|
+
if (agentTools) {
|
|
715
|
+
a.push('-c', 'project_doc_max_bytes=0');
|
|
716
|
+
// A person's existing developer instructions keep their authority.
|
|
717
|
+
// The base snapshot is also present in the per-turn prompt preamble.
|
|
718
|
+
if (!agentTools.personalDeveloperInstructions)
|
|
719
|
+
a.push('-c', `developer_instructions=${JSON.stringify(agentTools.instructions)}`);
|
|
720
|
+
}
|
|
659
721
|
if (model) a.push('--model', model);
|
|
660
722
|
// Effort is a config value on Codex rather than a flag.
|
|
661
723
|
if (effort) a.push('-c', `model_reasoning_effort="${effort}"`);
|
|
@@ -1040,7 +1102,16 @@ export const runtimeById = (id) => RUNTIMES[id] ?? RUNTIMES.claude;
|
|
|
1040
1102
|
* the single drivability test, so a machine-wide MCP config disqualified it from
|
|
1041
1103
|
* two jobs that never open an MCP connection.
|
|
1042
1104
|
*/
|
|
1043
|
-
const PROFILE_NEEDS_MCP = {
|
|
1105
|
+
const PROFILE_NEEDS_MCP = {
|
|
1106
|
+
build: true,
|
|
1107
|
+
wiki: false,
|
|
1108
|
+
consult: false,
|
|
1109
|
+
plan: true,
|
|
1110
|
+
// An agent turn has no MCP at all (SYSTEM_AGENT's header) — everything it
|
|
1111
|
+
// says rides the final JSON — so neither non-code posture needs one.
|
|
1112
|
+
design: false,
|
|
1113
|
+
research: false,
|
|
1114
|
+
};
|
|
1044
1115
|
|
|
1045
1116
|
/**
|
|
1046
1117
|
* A build needs the control plane, but NOT necessarily an MCP config of its own.
|
|
@@ -1209,6 +1280,68 @@ export function knownSkills() {
|
|
|
1209
1280
|
return skillsCache;
|
|
1210
1281
|
}
|
|
1211
1282
|
|
|
1283
|
+
/**
|
|
1284
|
+
* WHICH MCP SERVERS AND CLAUDE.AI CONNECTORS THE CLI MOUNTED, AND HOW EACH
|
|
1285
|
+
* STANDS (2026-09-23, 0.97.0) — learned off the same `system.init` event the
|
|
1286
|
+
* skills are, so it costs nothing and cannot drift from what a turn reaches.
|
|
1287
|
+
*
|
|
1288
|
+
* MEASURED on Claude Code 2.1.281 (`claude -p hi --model haiku --output-format
|
|
1289
|
+
* stream-json --verbose`): the init event carries `mcp_servers: [{ name,
|
|
1290
|
+
* status, source }]` — statuses seen `connected`, `failed`, `needs-auth` and
|
|
1291
|
+
* `pending`; a claude.ai connector arrives named `claude.ai <Name>` with
|
|
1292
|
+
* `source: "claudeai"`, a server a turn mounts with `--mcp-config` with
|
|
1293
|
+
* `source: "dynamic"`. A connector that needs a one-time sign-in AT THIS BOX
|
|
1294
|
+
* reads `needs-auth` here long before a turn fails on it, and the app's
|
|
1295
|
+
* Machines page names it.
|
|
1296
|
+
*
|
|
1297
|
+
* `flowviant` IS EXCLUDED: it is the server THIS daemon mounts on every tab
|
|
1298
|
+
* turn, always present, and not something a person can act on. A status off
|
|
1299
|
+
* the closed list is relayed as `other` rather than dropped — the CLI said
|
|
1300
|
+
* something about the server, and silence would be the daemon deciding it did
|
|
1301
|
+
* not. Bounded (40 entries, names cut at 60) and sorted, the `recordSkills`
|
|
1302
|
+
* discipline: a stable param, so an unchanged report writes nothing.
|
|
1303
|
+
*
|
|
1304
|
+
* ONLY WHAT IS NOT CONNECTED LEAVES THE BOX (2026-09-23). The first cut
|
|
1305
|
+
* relayed every server, connected ones included — so the operator's whole set
|
|
1306
|
+
* of signed-in services (their mail, their brokerage, their calendar) rode the
|
|
1307
|
+
* poll to the server and into every teammate's read of the Machines page,
|
|
1308
|
+
* which renders only the servers that are NOT connected anyway. A connected
|
|
1309
|
+
* connector is good news and good news is not a line; it is also nobody
|
|
1310
|
+
* else's business. So `connected` is dropped HERE, before the cap, and what is
|
|
1311
|
+
* sent is exactly what the page can say something about.
|
|
1312
|
+
*
|
|
1313
|
+
* THREE STATES, the skills split: null = no turn has taught us (the param is
|
|
1314
|
+
* not sent), [] = nothing of the person's own needs anything (every server is
|
|
1315
|
+
* connected, or none is mounted), a list = the ones that do. Last turn wins,
|
|
1316
|
+
* so a connector somebody signs into leaves the list on the next turn's init,
|
|
1317
|
+
* and an all-connected turn sends `[]`, which clears a stale list server-side.
|
|
1318
|
+
*/
|
|
1319
|
+
let mcpServersCache = null;
|
|
1320
|
+
|
|
1321
|
+
export const MCP_STATUSES = new Set(['connected', 'failed', 'needs-auth', 'pending']);
|
|
1322
|
+
export const MAX_MCP_SERVERS = 40;
|
|
1323
|
+
export const MCP_NAME_MAX = 60;
|
|
1324
|
+
|
|
1325
|
+
export function recordMcpServers(list) {
|
|
1326
|
+
if (!Array.isArray(list)) return;
|
|
1327
|
+
const seen = new Map();
|
|
1328
|
+
for (const e of list) {
|
|
1329
|
+
if (!e || typeof e !== 'object' || typeof e.name !== 'string') continue;
|
|
1330
|
+
const n = e.name.trim().slice(0, MCP_NAME_MAX).trim();
|
|
1331
|
+
if (!n || n === 'flowviant' || seen.has(n)) continue;
|
|
1332
|
+
if (e.status === 'connected') continue; // see the header: never relayed
|
|
1333
|
+
seen.set(n, { n, s: MCP_STATUSES.has(e.status) ? e.status : 'other' });
|
|
1334
|
+
}
|
|
1335
|
+
mcpServersCache = [...seen.values()]
|
|
1336
|
+
.sort((a, b) => (a.n < b.n ? -1 : a.n > b.n ? 1 : 0))
|
|
1337
|
+
.slice(0, MAX_MCP_SERVERS);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/** What to send on the roster poll as `mcp` — null until a turn has taught us. */
|
|
1341
|
+
export function knownMcpServers() {
|
|
1342
|
+
return mcpServersCache;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1212
1345
|
/**
|
|
1213
1346
|
* LEARN WHAT `/` CAN OFFER, ON A MACHINE NO TURN HAS TAUGHT.
|
|
1214
1347
|
*
|
|
@@ -1338,6 +1471,8 @@ export function parseInitLine(line) {
|
|
|
1338
1471
|
// record nothing. Conflating the two would keep the probe scanning a whole
|
|
1339
1472
|
// turn's output on a CLI that does not report them.
|
|
1340
1473
|
skills: Array.isArray(ev.skills) ? ev.skills : null,
|
|
1474
|
+
// The CLI's mounted MCP servers (0.97.0) — `recordMcpServers` normalises.
|
|
1475
|
+
mcpServers: Array.isArray(ev.mcp_servers) ? ev.mcp_servers : null,
|
|
1341
1476
|
sessionId: typeof ev.session_id === 'string' ? ev.session_id : null,
|
|
1342
1477
|
};
|
|
1343
1478
|
}
|
|
@@ -1384,9 +1519,14 @@ export function probeSkillsOnce(cwd) {
|
|
|
1384
1519
|
timer.unref?.();
|
|
1385
1520
|
child.on('error', () => finish(null));
|
|
1386
1521
|
child.on('exit', () => finish(null));
|
|
1522
|
+
// UTF-8 mode on the STREAM, not a per-chunk `.toString()` — a multi-byte
|
|
1523
|
+
// character straddling a chunk boundary decodes to mojibake (or a stray
|
|
1524
|
+
// replacement byte) when each chunk is decoded on its own; the stream's
|
|
1525
|
+
// own decoder holds the partial sequence over to the next chunk instead.
|
|
1526
|
+
child.stdout.setEncoding('utf8');
|
|
1387
1527
|
child.stdout.on('data', (d) => {
|
|
1388
1528
|
if (settled) return;
|
|
1389
|
-
buf += d
|
|
1529
|
+
buf += d;
|
|
1390
1530
|
let nl;
|
|
1391
1531
|
while (!settled && (nl = buf.indexOf('\n')) >= 0) {
|
|
1392
1532
|
const line = buf.slice(0, nl);
|
|
@@ -1399,6 +1539,7 @@ export function probeSkillsOnce(cwd) {
|
|
|
1399
1539
|
const init = parseInitLine(line);
|
|
1400
1540
|
if (!init) continue;
|
|
1401
1541
|
if (init.skills) recordSkills(init.skills);
|
|
1542
|
+
if (init.mcpServers) recordMcpServers(init.mcpServers);
|
|
1402
1543
|
finish(init.sessionId);
|
|
1403
1544
|
return;
|
|
1404
1545
|
}
|
package/bin/lib/shipMerge.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { baseBranchName } from './git.mjs';
|
|
1
|
+
import { baseBranchName, gitNet as gitNetDefault } from './git.mjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* CARRY A SHIPPED TIP OUT ONTO BASE AND PUSH IT.
|
|
@@ -28,6 +28,10 @@ export function mergeOutward({
|
|
|
28
28
|
baseRef,
|
|
29
29
|
workingTree,
|
|
30
30
|
warn,
|
|
31
|
+
// NETWORK calls only (push, fetch) — timed and non-interactive, so a
|
|
32
|
+
// stale credential or a half-open connection cannot freeze the ship
|
|
33
|
+
// path's writer lock. Injectable for tests; defaults to the real one.
|
|
34
|
+
gitNet = gitNetDefault,
|
|
31
35
|
}) {
|
|
32
36
|
const dropTmp = () => {
|
|
33
37
|
try {
|
|
@@ -43,7 +47,7 @@ export function mergeOutward({
|
|
|
43
47
|
['merge', '--no-ff', tip, '-m', `ship(${label}): ${count} commit${count === 1 ? '' : 's'}`],
|
|
44
48
|
tmpDir
|
|
45
49
|
);
|
|
46
|
-
|
|
50
|
+
gitNet(['push', 'origin', `HEAD:${baseBranchName(baseRef())}`], tmpDir, 120_000);
|
|
47
51
|
};
|
|
48
52
|
try {
|
|
49
53
|
try {
|
|
@@ -74,7 +78,7 @@ export function mergeOutward({
|
|
|
74
78
|
if (!isRaceRejection(e)) throw e;
|
|
75
79
|
warn?.('ship: base moved under us — refetching and merging again');
|
|
76
80
|
try {
|
|
77
|
-
|
|
81
|
+
gitNet(['fetch', 'origin', '--quiet'], repoRoot, 60_000);
|
|
78
82
|
} catch {
|
|
79
83
|
/* offline — the retry fails honestly on the same push */
|
|
80
84
|
}
|
|
@@ -102,7 +106,7 @@ export function mergeOutward({
|
|
|
102
106
|
*/
|
|
103
107
|
if (workingTree && branch && branch === baseBranchName(baseRef())) {
|
|
104
108
|
try {
|
|
105
|
-
|
|
109
|
+
gitNet(['fetch', 'origin', '--quiet'], repoRoot, 60_000);
|
|
106
110
|
git(['merge', '--ff-only', baseRef()], workingTree);
|
|
107
111
|
} catch {
|
|
108
112
|
/* a readout, not the ship — the merge already landed, and the next
|
package/bin/lib/trace.mjs
CHANGED
|
@@ -38,10 +38,15 @@
|
|
|
38
38
|
* second attempt then opened at `seq: 0` against a mark of three hundred, so
|
|
39
39
|
* every batch it sent was trimmed to nothing and the surface showed the
|
|
40
40
|
* ABANDONED attempt's steps with the new one's tail welded on, no seam, no
|
|
41
|
-
* `dropped` to say so. `run` is a nonce per relay:
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
41
|
+
* `dropped` to say so. `run` is a nonce per relay: EACH RUN KEEPS ITS OWN
|
|
42
|
+
* HIGH-WATER MARK IN ITS OWN SEQ SPACE (2026-09-24) — an unfamiliar one starts
|
|
43
|
+
* its own mark at the server's shared `hw` rather than being rebased onto it,
|
|
44
|
+
* so a second attempt appends AFTER the first instead of being deleted by it,
|
|
45
|
+
* and a retry inside one run still trims exactly as before. The first cut
|
|
46
|
+
* rebased an unfamiliar run onto the one shared mark, so a takeover's old box,
|
|
47
|
+
* still flushing its last seconds, spliced its late steps between the new
|
|
48
|
+
* run's — a superseded run's late steps are COUNTED against the shared mark
|
|
49
|
+
* now (the trace is not lost) and never SPLICED between the live run's.
|
|
45
50
|
*
|
|
46
51
|
* SCRUB EVERY STRING. This is the CLI's own stdout — a command echoing an env
|
|
47
52
|
* var, a read of a config file — riding the same uplink the answer does. Prose
|
|
@@ -163,8 +168,13 @@ export function makeTraceRelay({ agentId, turnId, post, scrub = (s) => s, run =
|
|
|
163
168
|
dirty = true;
|
|
164
169
|
return;
|
|
165
170
|
}
|
|
166
|
-
|
|
167
|
-
base
|
|
171
|
+
// Remove only what of the batch is STILL at the head: a push during the
|
|
172
|
+
// await may have shed some of it already (advancing `base`), and
|
|
173
|
+
// splicing the whole batch length would then drop entries that were
|
|
174
|
+
// never sent — a gap the buffer had room to avoid.
|
|
175
|
+
const drop = Math.max(0, seq + entries.length - base);
|
|
176
|
+
queue.splice(0, drop);
|
|
177
|
+
base += drop;
|
|
168
178
|
}
|
|
169
179
|
};
|
|
170
180
|
|
package/bin/lib/update.mjs
CHANGED
|
@@ -100,6 +100,11 @@ function reexec(teardown, { viaNpx = false, target = null } = {}) {
|
|
|
100
100
|
env: {
|
|
101
101
|
...process.env,
|
|
102
102
|
FLOWVIANT_REEXEC: '1',
|
|
103
|
+
// WHICH DAEMON THE SUCCESSOR IS REPLACING. Under npx the successor's
|
|
104
|
+
// parent is `npm exec`, not us, so the instance lock's ppid check read us
|
|
105
|
+
// as a rival and the new daemon SIGTERMed this proxy. instance.mjs adopts
|
|
106
|
+
// the lock from this pid when it is really an ancestor of the successor.
|
|
107
|
+
FLOWVIANT_REEXEC_FROM: String(process.pid),
|
|
103
108
|
// WHAT WE RESTARTED IN ORDER TO BECOME. The successor compares its own
|
|
104
109
|
// VERSION against this: if it came back still short, the update did not
|
|
105
110
|
// take (a registry serving a stale `latest`, an npx cache that refused to
|