flowviant 0.97.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 +136 -26
- 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 +166 -71
- 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 +130 -13
- package/bin/lib/fleet.mjs +99 -28
- package/bin/lib/git.mjs +131 -3
- package/bin/lib/hooks/readGuard.mjs +24 -0
- package/bin/lib/instance.mjs +115 -14
- package/bin/lib/knowledge.mjs +180 -60
- 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 +16 -11
- package/bin/lib/resources.mjs +46 -4
- package/bin/lib/runtimeOfferings.mjs +75 -0
- package/bin/lib/runtimes.mjs +71 -9
- 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 +1192 -4584
- 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
|
|
@@ -555,7 +589,7 @@ export const RUNTIMES = {
|
|
|
555
589
|
* strongest form of it available anywhere: `--append-system-prompt` sits
|
|
556
590
|
* above the conversation rather than inside it.
|
|
557
591
|
*/
|
|
558
|
-
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 }) {
|
|
559
593
|
const a = [];
|
|
560
594
|
// THREE ANSWERS TO ONE QUESTION — "what conversation is this?" — and they
|
|
561
595
|
// are mutually exclusive, never combined.
|
|
@@ -582,6 +616,17 @@ export const RUNTIMES = {
|
|
|
582
616
|
else if (resumeThreadId) a.push('--resume', resumeThreadId);
|
|
583
617
|
else if (resume) a.push('--continue');
|
|
584
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');
|
|
585
630
|
a.push(...mcp, ...resultSchemaArgs);
|
|
586
631
|
a.push('--model', model || MODEL);
|
|
587
632
|
if (effort) a.push('--effort', effort);
|
|
@@ -634,6 +679,11 @@ export const RUNTIMES = {
|
|
|
634
679
|
* user's repository, which is not a risk worth taking for a slightly
|
|
635
680
|
* stickier instruction.
|
|
636
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
|
+
*
|
|
637
687
|
* `--skip-git-repo-check` is deliberately NOT passed: a task always builds
|
|
638
688
|
* in a git worktree, and if it somehow is not one, failing loudly beats
|
|
639
689
|
* silently editing files nobody can diff.
|
|
@@ -644,7 +694,7 @@ export const RUNTIMES = {
|
|
|
644
694
|
* placed before it. Appending them after the positional is the kind of argv
|
|
645
695
|
* that parses today and stops parsing on some future clap upgrade.
|
|
646
696
|
*/
|
|
647
|
-
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 }) {
|
|
648
698
|
// Adoption resumes a conversation in ITS OWN CLI's store (claude forks,
|
|
649
699
|
// agy moves) — codex has no adoptable store wired yet. Reaching here
|
|
650
700
|
// with an adopt id is a wiring mistake upstream, and it fails loudly on
|
|
@@ -661,6 +711,13 @@ export const RUNTIMES = {
|
|
|
661
711
|
if (resumeThreadId) a.push('resume', resumeThreadId);
|
|
662
712
|
else if (resume) a.push('resume', '--last');
|
|
663
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
|
+
}
|
|
664
721
|
if (model) a.push('--model', model);
|
|
665
722
|
// Effort is a config value on Codex rather than a flag.
|
|
666
723
|
if (effort) a.push('-c', `model_reasoning_effort="${effort}"`);
|
|
@@ -1462,9 +1519,14 @@ export function probeSkillsOnce(cwd) {
|
|
|
1462
1519
|
timer.unref?.();
|
|
1463
1520
|
child.on('error', () => finish(null));
|
|
1464
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');
|
|
1465
1527
|
child.stdout.on('data', (d) => {
|
|
1466
1528
|
if (settled) return;
|
|
1467
|
-
buf += d
|
|
1529
|
+
buf += d;
|
|
1468
1530
|
let nl;
|
|
1469
1531
|
while (!settled && (nl = buf.indexOf('\n')) >= 0) {
|
|
1470
1532
|
const line = buf.slice(0, nl);
|
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
|