flowviant 0.73.0 → 0.74.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/lib/claude.mjs +7 -3
- package/bin/lib/runtimes.mjs +76 -0
- package/bin/lib/work.mjs +142 -4
- package/bin/lib/worktreeDiff.mjs +11 -0
- package/package.json +1 -1
package/bin/lib/claude.mjs
CHANGED
|
@@ -197,7 +197,7 @@ const oneLine = (s, n = 160) => String(s).replace(/\s+/g, ' ').trim().slice(0, n
|
|
|
197
197
|
// every intermediate text block still NARRATES, but only the final `result`
|
|
198
198
|
// event contributes text — otherwise the same sentences arrive twice, once as
|
|
199
199
|
// they stream and once in the result, and the tab posts the duplicate.
|
|
200
|
-
function handleStreamLine(line, { cwd, emit, onActivity, appendText, answerFromResult, onInit }) {
|
|
200
|
+
function handleStreamLine(line, { cwd, emit, onActivity, onToolEvent, appendText, answerFromResult, onInit }) {
|
|
201
201
|
let ev;
|
|
202
202
|
try {
|
|
203
203
|
ev = JSON.parse(line);
|
|
@@ -222,6 +222,10 @@ function handleStreamLine(line, { cwd, emit, onActivity, appendText, answerFromR
|
|
|
222
222
|
push({ kind: 'say', label: oneLine(b.text) });
|
|
223
223
|
} else if (b.type === 'tool_use') {
|
|
224
224
|
push(humanizeToolUse(b.name, b.input || {}, cwd));
|
|
225
|
+
// The STRUCTURED form of the same event, for the transcript's tool
|
|
226
|
+
// cards — raw name + input, so the collector can keep what the
|
|
227
|
+
// one-line humanizer drops (an Edit's counts, the plan's items).
|
|
228
|
+
onToolEvent?.(b.name, b.input || {});
|
|
225
229
|
}
|
|
226
230
|
}
|
|
227
231
|
} else if (ev.type === 'system' && ev.subtype === 'init') {
|
|
@@ -270,7 +274,7 @@ function handleStreamLine(line, { cwd, emit, onActivity, appendText, answerFromR
|
|
|
270
274
|
// returned string for sentinel detection, and each activity is handed to
|
|
271
275
|
// `onActivity` so the caller can forward progress. Build-agent turns leave it
|
|
272
276
|
// off and keep the raw text passthrough + line sentinels.
|
|
273
|
-
export function runTurn({ prompt, resume, system, cwd, mcpConfig, mcpArgs, mcpEnv, runtime = 'claude', label, onSpawn, streamJson, answerFromResult, onActivity, onInit, onThreadId, wikiPerm, readOnly, planPerm, vaultDir, resultSchemaArgs, model, effort, adoptResumeId, resumeThreadId, resumeConversationId }) {
|
|
277
|
+
export function runTurn({ prompt, resume, system, cwd, mcpConfig, mcpArgs, mcpEnv, runtime = 'claude', label, onSpawn, streamJson, answerFromResult, onActivity, onToolEvent, onInit, onThreadId, wikiPerm, readOnly, planPerm, vaultDir, resultSchemaArgs, model, effort, adoptResumeId, resumeThreadId, resumeConversationId }) {
|
|
274
278
|
return new Promise((resolve) => {
|
|
275
279
|
const rt = runtimeById(runtime);
|
|
276
280
|
if (!rt.args) {
|
|
@@ -401,7 +405,7 @@ export function runTurn({ prompt, resume, system, cwd, mcpConfig, mcpArgs, mcpEn
|
|
|
401
405
|
/** One line of the child's stdout, in whichever dialect it speaks. */
|
|
402
406
|
const onLine = (line) => {
|
|
403
407
|
if (!rt.parse)
|
|
404
|
-
return handleStreamLine(line, { cwd, emit, onActivity, appendText, answerFromResult, onInit });
|
|
408
|
+
return handleStreamLine(line, { cwd, emit, onActivity, onToolEvent, appendText, answerFromResult, onInit });
|
|
405
409
|
const ev = rt.parse(line, cwd);
|
|
406
410
|
if (!ev) return;
|
|
407
411
|
// The conversation id, when the runtime announces one (codex's
|
package/bin/lib/runtimes.mjs
CHANGED
|
@@ -89,11 +89,87 @@ export function humanizeClaudeTool(name, input = {}, cwd = '') {
|
|
|
89
89
|
command: String(input.command ?? '').slice(0, 2000),
|
|
90
90
|
label: `$ ${oneLine(input.command, 60)}`,
|
|
91
91
|
};
|
|
92
|
+
case 'TodoWrite': {
|
|
93
|
+
// The CLI's own todo list — the plan the transcript's PLAN card renders.
|
|
94
|
+
// The label narrates the change ("plan: 2 of 5 — cut over /api/session");
|
|
95
|
+
// the structured items ride through toolEventOf below.
|
|
96
|
+
const todos = Array.isArray(input.todos) ? input.todos : [];
|
|
97
|
+
if (todos.length === 0) return null;
|
|
98
|
+
const done = todos.filter((t) => t?.status === 'completed').length;
|
|
99
|
+
const active = todos.find((t) => t?.status === 'in_progress');
|
|
100
|
+
return {
|
|
101
|
+
kind: 'plan',
|
|
102
|
+
label: `plan: ${done} of ${todos.length}${active?.content ? ` — ${oneLine(active.content, 60)}` : ''}`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
92
105
|
default:
|
|
93
106
|
return null; // other tools: silent
|
|
94
107
|
}
|
|
95
108
|
}
|
|
96
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Tool-call → ONE STRUCTURED EVENT for the transcript's tool cards — the
|
|
112
|
+
* relay's durable form, where `humanizeClaudeTool` above is its one-line live
|
|
113
|
+
* form. Same source (the CLI's own tool_use input), zero inference: every
|
|
114
|
+
* field is something the CLI emitted, and a tool this doesn't know renders as
|
|
115
|
+
* nothing rather than as a guess.
|
|
116
|
+
*
|
|
117
|
+
* Wire vocabulary (compact keys — this rides a 1.5s-throttled POST):
|
|
118
|
+
* t: read|edit|write|grep|glob|bash|task|plan
|
|
119
|
+
* p: path (worktree-relative) q: pattern/description c: command
|
|
120
|
+
* a/d: line counts added/deleted (from the input's own strings)
|
|
121
|
+
* dl: a few "-/+" prefixed preview lines of an Edit
|
|
122
|
+
* items: the plan's todos, x = text, s = done|active|open
|
|
123
|
+
*
|
|
124
|
+
* The CALLER scrubs (work.mjs envScrub) — this stays a pure shape function so
|
|
125
|
+
* it is testable without a vault.
|
|
126
|
+
*/
|
|
127
|
+
export function toolEventOf(name, input = {}, cwd = '') {
|
|
128
|
+
const rel = (p) => shortPath(p, cwd).slice(0, 300);
|
|
129
|
+
const lines = (s) => (s ? String(s).split('\n').length : 0);
|
|
130
|
+
switch (name) {
|
|
131
|
+
case 'Read':
|
|
132
|
+
return { t: 'read', p: rel(input.file_path) };
|
|
133
|
+
case 'Write':
|
|
134
|
+
return { t: 'write', p: rel(input.file_path), a: lines(input.content) };
|
|
135
|
+
case 'Edit': {
|
|
136
|
+
const oldS = String(input.old_string ?? '');
|
|
137
|
+
const newS = String(input.new_string ?? '');
|
|
138
|
+
// A MINI-DIFF, not the diff: the first lines of each side, enough to
|
|
139
|
+
// recognise the change at a glance. The real diff lives in git.
|
|
140
|
+
const dl = [
|
|
141
|
+
...oldS.split('\n').slice(0, 2).map((l) => `- ${l}`),
|
|
142
|
+
...newS.split('\n').slice(0, 3).map((l) => `+ ${l}`),
|
|
143
|
+
].map((l) => l.slice(0, 160));
|
|
144
|
+
return { t: 'edit', p: rel(input.file_path), a: lines(newS), d: lines(oldS), dl };
|
|
145
|
+
}
|
|
146
|
+
case 'Grep':
|
|
147
|
+
return {
|
|
148
|
+
t: 'grep',
|
|
149
|
+
q: String(input.pattern ?? '').slice(0, 200),
|
|
150
|
+
...(input.path ? { p: rel(input.path) } : {}),
|
|
151
|
+
};
|
|
152
|
+
case 'Glob':
|
|
153
|
+
return { t: 'glob', q: String(input.pattern ?? '').slice(0, 200) };
|
|
154
|
+
case 'Bash':
|
|
155
|
+
return { t: 'bash', c: String(input.command ?? '').slice(0, 200) };
|
|
156
|
+
case 'Task':
|
|
157
|
+
return { t: 'task', q: String(input.description ?? '').slice(0, 200) };
|
|
158
|
+
case 'TodoWrite': {
|
|
159
|
+
const todos = Array.isArray(input.todos) ? input.todos.slice(0, 20) : [];
|
|
160
|
+
const items = todos
|
|
161
|
+
.map((td) => ({
|
|
162
|
+
x: String(td?.content ?? '').slice(0, 120),
|
|
163
|
+
s: td?.status === 'completed' ? 'done' : td?.status === 'in_progress' ? 'active' : 'open',
|
|
164
|
+
}))
|
|
165
|
+
.filter((i) => i.x);
|
|
166
|
+
return items.length ? { t: 'plan', items } : null;
|
|
167
|
+
}
|
|
168
|
+
default:
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
97
173
|
// ── Codex ──────────────────────────────────────────────────────────────────
|
|
98
174
|
|
|
99
175
|
/**
|
package/bin/lib/work.mjs
CHANGED
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
WORK_TURN_KICKOFF_PLAIN,
|
|
56
56
|
} from './prompts.mjs';
|
|
57
57
|
import { materializeInto, hasMaterialized, excludeInWorktree, scrub as envScrub } from './env.mjs';
|
|
58
|
-
import { detectRuntimes, canRun, recordSkills, RUNTIMES } from './runtimes.mjs';
|
|
58
|
+
import { detectRuntimes, canRun, recordSkills, toolEventOf, RUNTIMES } from './runtimes.mjs';
|
|
59
59
|
|
|
60
60
|
/** The place id meaning "the checkout", not a worktree. Must match the
|
|
61
61
|
* server's REPO_PLACE — it is a wire value, not a local convention. */
|
|
@@ -336,6 +336,7 @@ export function createWorkManager({
|
|
|
336
336
|
// an action that changes what the machine would measure must cause a new
|
|
337
337
|
// measurement, and the 60s sweep is not that.
|
|
338
338
|
void reportPlaceWorktrees(sessionId).catch(() => {});
|
|
339
|
+
burstListeners(sessionId);
|
|
339
340
|
// …and the REPO picture changed too: the session branch is gone and base
|
|
340
341
|
// moved. Without this the Repository block keeps counting a branch the
|
|
341
342
|
// ship just deleted.
|
|
@@ -364,7 +365,7 @@ export function createWorkManager({
|
|
|
364
365
|
* line over the finished reply — the server drops narration for a turn
|
|
365
366
|
* that is no longer pending. (A session-level pending count can't tell the
|
|
366
367
|
* settled turn's stale line from the queued NEXT turn's fresh one.) */
|
|
367
|
-
const makeNarrator = (sessionId, turnId) => {
|
|
368
|
+
const makeNarrator = (sessionId, turnId, getTools) => {
|
|
368
369
|
const recent = [];
|
|
369
370
|
let lastSent = 0;
|
|
370
371
|
let dirty = false;
|
|
@@ -386,7 +387,15 @@ export function createWorkManager({
|
|
|
386
387
|
'Content-Type': 'application/json',
|
|
387
388
|
},
|
|
388
389
|
signal: AbortSignal.timeout(10_000),
|
|
389
|
-
body: JSON.stringify({
|
|
390
|
+
body: JSON.stringify({
|
|
391
|
+
sessionId,
|
|
392
|
+
turnId,
|
|
393
|
+
lines,
|
|
394
|
+
// The structured tool log so far, riding the same throttled beat.
|
|
395
|
+
// Same lifecycle as the lines: overwritten as the turn moves,
|
|
396
|
+
// cleared server-side at settle. Absent until something ran.
|
|
397
|
+
...(getTools ? { tools: getTools() } : {}),
|
|
398
|
+
}),
|
|
390
399
|
});
|
|
391
400
|
} catch {
|
|
392
401
|
/* narration is decoration — a dropped line is not an incident */
|
|
@@ -649,6 +658,69 @@ export function createWorkManager({
|
|
|
649
658
|
const reports = ids.map(sessionWorktreeReport).filter(Boolean);
|
|
650
659
|
if (reports.length) await postWorktrees(reports);
|
|
651
660
|
};
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* THE FIRST MINUTE AFTER A SETTLE — when "run the dev server" actually binds.
|
|
664
|
+
*
|
|
665
|
+
* The settle-time report fires the moment the reply lands, but a dev server
|
|
666
|
+
* the agent just started usually takes a few more seconds to open its socket
|
|
667
|
+
* (vite boots, next compiles). It therefore missed the settle measurement and
|
|
668
|
+
* waited the full 60s sweep — up to a minute of "nothing is running here"
|
|
669
|
+
* over a server that was already up, which is the slowest link in the whole
|
|
670
|
+
* "ask for dev → see the preview" chain. Asked directly: "how do we make it
|
|
671
|
+
* more responsive when the user prompts claude to run dev to waiting for it
|
|
672
|
+
* to appear on the preview?"
|
|
673
|
+
*
|
|
674
|
+
* A DECAYING BURST, and it re-CHECKS before it re-REPORTS: each beat walks
|
|
675
|
+
* /proc for the place's listeners (purely local, no git, no network) and only
|
|
676
|
+
* when the PORT SET actually changed does the full place report run and post.
|
|
677
|
+
* A settle where nothing ever binds costs five /proc walks and zero posts; a
|
|
678
|
+
* dev server that binds at +7s is on the wire at +9 instead of +60. The burst
|
|
679
|
+
* for a place restarts on its next settle, so overlapping turns cannot stack
|
|
680
|
+
* timers, and every timer is unref'd — a readout must never hold the process
|
|
681
|
+
* open.
|
|
682
|
+
*
|
|
683
|
+
* This also serves the OPPOSITE transition for free: a stopped dev server
|
|
684
|
+
* (the panel's Stop, a ctrl-C in a terminal) vanishes from the port set the
|
|
685
|
+
* same way it appeared, so the preview's "origin gone" story starts in
|
|
686
|
+
* seconds too.
|
|
687
|
+
*/
|
|
688
|
+
const LISTEN_BURST_DELAYS_MS = [4_000, 9_000, 16_000, 30_000, 55_000];
|
|
689
|
+
const listenBursts = new Map(); // place -> timers[]
|
|
690
|
+
const listenSignature = (wt) => {
|
|
691
|
+
try {
|
|
692
|
+
const l = measureListeners(wt);
|
|
693
|
+
return l.rows.map((r) => r.port).sort((a, b) => a - b).join(',');
|
|
694
|
+
} catch {
|
|
695
|
+
return '';
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
const burstListeners = (sessionId) => {
|
|
699
|
+
try {
|
|
700
|
+
const place = placeOf(sessionId);
|
|
701
|
+
for (const t of listenBursts.get(place) ?? []) clearTimeout(t);
|
|
702
|
+
const wt = placeDir(sessionId);
|
|
703
|
+
// Captured alongside the settle report, so only a CHANGE after this
|
|
704
|
+
// moment triggers a post — the settle report already said the rest.
|
|
705
|
+
let last = listenSignature(wt);
|
|
706
|
+
const timers = LISTEN_BURST_DELAYS_MS.map((d) =>
|
|
707
|
+
setTimeout(() => {
|
|
708
|
+
try {
|
|
709
|
+
const sig = listenSignature(wt);
|
|
710
|
+
if (sig === last) return;
|
|
711
|
+
last = sig;
|
|
712
|
+
void reportPlaceWorktrees(sessionId).catch(() => {});
|
|
713
|
+
} catch {
|
|
714
|
+
/* a readout — the sweep still carries it */
|
|
715
|
+
}
|
|
716
|
+
}, d)
|
|
717
|
+
);
|
|
718
|
+
for (const t of timers) t.unref?.();
|
|
719
|
+
listenBursts.set(place, timers);
|
|
720
|
+
} catch {
|
|
721
|
+
/* never let the burst break a settle */
|
|
722
|
+
}
|
|
723
|
+
};
|
|
652
724
|
/** Every live session, throttled — called from the reconcile loop. */
|
|
653
725
|
/**
|
|
654
726
|
* A SESSION NOBODY HAS MEASURED YET JUMPS THE SWEEP (2026-08-26).
|
|
@@ -2318,7 +2390,66 @@ export function createWorkManager({
|
|
|
2318
2390
|
let seenThreadId = null; // codex's conversation id, off thread.started
|
|
2319
2391
|
let seenClaudeSession = null; // claude's own conversation id, off system.init
|
|
2320
2392
|
const spawned = []; // this turn's children, for the teardown registry
|
|
2321
|
-
|
|
2393
|
+
/**
|
|
2394
|
+
* THE TURN'S TOOL LOG — the structured relay behind the transcript's
|
|
2395
|
+
* tool cards. Same source as the narrator (the CLI's own tool_use
|
|
2396
|
+
* events), zero inference; scrubbed AT COLLECTION so every copy that
|
|
2397
|
+
* leaves the machine — live beat and settle alike — is already clean.
|
|
2398
|
+
*
|
|
2399
|
+
* Shape rules, applied here because the collector is the one writer:
|
|
2400
|
+
* · consecutive identical read/grep/glob/bash/task events collapse
|
|
2401
|
+
* into one row with a count (n);
|
|
2402
|
+
* · consecutive edits of ONE file merge, summing counts, keeping
|
|
2403
|
+
* the newest preview;
|
|
2404
|
+
* · the PLAN is a single event — a new TodoWrite replaces the old
|
|
2405
|
+
* plan at the current position, so the log shows the latest plan
|
|
2406
|
+
* where it last changed rather than five stale copies;
|
|
2407
|
+
* · capped at the newest 60, with the shed counted (`dropped`) —
|
|
2408
|
+
* scrollback semantics, the same trade the transcript itself
|
|
2409
|
+
* makes.
|
|
2410
|
+
*/
|
|
2411
|
+
const toolLog = { ev: [], dropped: 0 };
|
|
2412
|
+
const scrubEv = (e) => {
|
|
2413
|
+
for (const k of ['p', 'q', 'c']) if (typeof e[k] === 'string') e[k] = envScrub(e[k]);
|
|
2414
|
+
if (Array.isArray(e.dl)) e.dl = e.dl.map((l) => envScrub(l));
|
|
2415
|
+
if (Array.isArray(e.items)) for (const i of e.items) i.x = envScrub(i.x);
|
|
2416
|
+
return e;
|
|
2417
|
+
};
|
|
2418
|
+
const pushToolEvent = (name, input) => {
|
|
2419
|
+
const e = toolEventOf(name, input, dir.wt);
|
|
2420
|
+
if (!e) return;
|
|
2421
|
+
scrubEv(e);
|
|
2422
|
+
if (e.t === 'plan') {
|
|
2423
|
+
const i = toolLog.ev.findIndex((x) => x.t === 'plan');
|
|
2424
|
+
if (i >= 0) toolLog.ev.splice(i, 1);
|
|
2425
|
+
toolLog.ev.push(e);
|
|
2426
|
+
} else {
|
|
2427
|
+
const last = toolLog.ev[toolLog.ev.length - 1];
|
|
2428
|
+
const sameKey =
|
|
2429
|
+
last &&
|
|
2430
|
+
last.t === e.t &&
|
|
2431
|
+
last.p === e.p &&
|
|
2432
|
+
last.q === e.q &&
|
|
2433
|
+
last.c === e.c;
|
|
2434
|
+
if (sameKey && (e.t === 'edit' || e.t === 'write')) {
|
|
2435
|
+
last.n = (last.n ?? 1) + 1;
|
|
2436
|
+
last.a = (last.a ?? 0) + (e.a ?? 0);
|
|
2437
|
+
last.d = (last.d ?? 0) + (e.d ?? 0);
|
|
2438
|
+
if (e.dl) last.dl = e.dl;
|
|
2439
|
+
} else if (sameKey) {
|
|
2440
|
+
last.n = (last.n ?? 1) + 1;
|
|
2441
|
+
} else {
|
|
2442
|
+
toolLog.ev.push(e);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
while (toolLog.ev.length > 60) {
|
|
2446
|
+
toolLog.ev.shift();
|
|
2447
|
+
toolLog.dropped++;
|
|
2448
|
+
}
|
|
2449
|
+
};
|
|
2450
|
+
const narrator = makeNarrator(job.sessionId, job.id, () =>
|
|
2451
|
+
toolLog.ev.length > 0 ? toolLog : undefined
|
|
2452
|
+
);
|
|
2322
2453
|
|
|
2323
2454
|
// THE COMMAND AUDIT — every `$ …` the CLI's stream reports, batched
|
|
2324
2455
|
// to the server verbatim so an admin can read what actually ran on
|
|
@@ -2412,6 +2543,8 @@ export function createWorkManager({
|
|
|
2412
2543
|
narrator.line(a?.label);
|
|
2413
2544
|
auditCommand(a);
|
|
2414
2545
|
},
|
|
2546
|
+
// The structured twin of the line above — see toolLog.
|
|
2547
|
+
onToolEvent: pushToolEvent,
|
|
2415
2548
|
// What this CLI says it can be asked for by name. Harvested off
|
|
2416
2549
|
// the init event the stream already carries — no probe, no scan,
|
|
2417
2550
|
// no extra spawn — and reported on the next roster poll so the
|
|
@@ -2572,6 +2705,10 @@ export function createWorkManager({
|
|
|
2572
2705
|
// here. Recording the path unconditionally is how a crashed first
|
|
2573
2706
|
// turn used to brick resume for the session's whole life.
|
|
2574
2707
|
...(answer.length > 0 ? { sessionRef: dir.wt } : {}),
|
|
2708
|
+
// The turn's tool log, in final form — the durable copy that lands
|
|
2709
|
+
// on the settled message (the live copy on the record is cleared
|
|
2710
|
+
// at settle). Already scrubbed at collection.
|
|
2711
|
+
...(toolLog.ev.length > 0 ? { tools: toolLog } : {}),
|
|
2575
2712
|
});
|
|
2576
2713
|
if (answer.length > 0) ok(`${c.cyan('tab')} ${c.dim('— replied in the session')}`);
|
|
2577
2714
|
else warn('session turn produced no output — settled as failed');
|
|
@@ -2592,6 +2729,7 @@ export function createWorkManager({
|
|
|
2592
2729
|
// awaited: this runs inside the session's chain, and a slow POST
|
|
2593
2730
|
// would delay the next turn of that tab behind a readout.
|
|
2594
2731
|
void reportPlaceWorktrees(job.sessionId).catch(() => {});
|
|
2732
|
+
burstListeners(job.sessionId);
|
|
2595
2733
|
}
|
|
2596
2734
|
});
|
|
2597
2735
|
}
|
package/bin/lib/worktreeDiff.mjs
CHANGED
|
@@ -169,6 +169,16 @@ export function worktreeDiff(wt, baseRef) {
|
|
|
169
169
|
} catch {
|
|
170
170
|
return null; // not a worktree (or not readable) — report nothing, not zeros
|
|
171
171
|
}
|
|
172
|
+
// The commit HEAD names, for the strip's branch chip. Best-effort: an
|
|
173
|
+
// unborn branch (fresh repo, no commit yet) has a name and no sha, and the
|
|
174
|
+
// report simply omits the key — absent must never become an empty string,
|
|
175
|
+
// which would render as a blank chip.
|
|
176
|
+
let headSha = '';
|
|
177
|
+
try {
|
|
178
|
+
headSha = git(['rev-parse', 'HEAD'], wt);
|
|
179
|
+
} catch {
|
|
180
|
+
/* unborn HEAD — no sha to report */
|
|
181
|
+
}
|
|
172
182
|
let base = '';
|
|
173
183
|
try {
|
|
174
184
|
base = git(['merge-base', 'HEAD', baseRef], wt);
|
|
@@ -289,6 +299,7 @@ export function worktreeDiff(wt, baseRef) {
|
|
|
289
299
|
// pathological commit subject — would 400 every session's readout at once.
|
|
290
300
|
return {
|
|
291
301
|
branch: branch.slice(0, 200),
|
|
302
|
+
...(headSha ? { headSha: headSha.slice(0, 64) } : {}),
|
|
292
303
|
path: wt,
|
|
293
304
|
ahead,
|
|
294
305
|
behind,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.74.0",
|
|
4
4
|
"description": "Run your own coding CLIs as build agents for Flowviant — Claude Code, Codex or Antigravity, on your own credentials. Holds your sessions, keeps a worktree per tab, and ships branches on your word.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|