flowviant 0.40.1 → 0.41.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/fleet.mjs +30 -20
- package/bin/lib/live.mjs +10 -4
- package/bin/lib/resources.mjs +2 -2
- package/package.json +1 -1
package/bin/lib/fleet.mjs
CHANGED
|
@@ -191,7 +191,7 @@ function sampleDiffstat(cwd, baseRef, intentId, agentId) {
|
|
|
191
191
|
signal: AbortSignal.timeout(15_000),
|
|
192
192
|
// The lane, not just the task: the server matches the run on both, so a
|
|
193
193
|
// sample can only ever overwrite the diffstat of THIS lane's own run.
|
|
194
|
-
body: JSON.stringify({ intentId, agentId, diffstat: stat }),
|
|
194
|
+
body: JSON.stringify({ taskId: intentId, agentId, diffstat: stat }),
|
|
195
195
|
});
|
|
196
196
|
// Only a sample the server ACCEPTED counts as sent. Marking it delivered
|
|
197
197
|
// before the round-trip meant a dropped request suppressed every retry
|
|
@@ -575,7 +575,7 @@ export async function runFleetDaemon() {
|
|
|
575
575
|
// roster re-serves the job every poll, and each pass reverts the
|
|
576
576
|
// revert — the change flapping in and out of the owner's tree forever.
|
|
577
577
|
await reportMergeOutcome(PATCH_REVERT_DONE_URL, {
|
|
578
|
-
|
|
578
|
+
taskId: job.id,
|
|
579
579
|
ok: res.ok,
|
|
580
580
|
error: res.ok ? undefined : String(res.error ?? 'revert failed'),
|
|
581
581
|
});
|
|
@@ -693,9 +693,12 @@ export async function runFleetDaemon() {
|
|
|
693
693
|
const checkingPlans = new Set();
|
|
694
694
|
const processPlanCheckJobs = (jobs) => {
|
|
695
695
|
for (const job of jobs ?? []) {
|
|
696
|
-
|
|
696
|
+
// New name first; the roster mirrors `intents` off `tasks` for exactly
|
|
697
|
+
// this fallback window.
|
|
698
|
+
const planTasks = Array.isArray(job?.tasks) ? job.tasks : job?.intents;
|
|
699
|
+
if (!job || typeof job.id !== 'string' || !Array.isArray(planTasks)) continue;
|
|
697
700
|
if (checkingPlans.has(job.id)) continue;
|
|
698
|
-
if (
|
|
701
|
+
if (planTasks.length === 0) continue;
|
|
699
702
|
checkingPlans.add(job.id);
|
|
700
703
|
(async () => {
|
|
701
704
|
try {
|
|
@@ -711,7 +714,7 @@ export async function runFleetDaemon() {
|
|
|
711
714
|
const out = await withWikiLock(async () => {
|
|
712
715
|
ensureWikiWorktree();
|
|
713
716
|
return runTurn({
|
|
714
|
-
prompt: PLAN_CHECK_KICKOFF({ title: job.title, intents:
|
|
717
|
+
prompt: PLAN_CHECK_KICKOFF({ title: job.title, intents: planTasks }),
|
|
715
718
|
resume: false,
|
|
716
719
|
system: SYSTEM_PLAN_CHECK,
|
|
717
720
|
cwd: wikiWt,
|
|
@@ -721,12 +724,12 @@ export async function runFleetDaemon() {
|
|
|
721
724
|
label: c.cyan('[plan]'),
|
|
722
725
|
});
|
|
723
726
|
});
|
|
724
|
-
const checks = parsePlanChecks(out,
|
|
727
|
+
const checks = parsePlanChecks(out, planTasks);
|
|
725
728
|
if (checks === null) {
|
|
726
729
|
warn(`plan check for "${job.title}": no usable JSON — leaving the plan as drafted`);
|
|
727
730
|
}
|
|
728
731
|
await reportMergeOutcome(PLAN_CHECK_DONE_URL, {
|
|
729
|
-
|
|
732
|
+
taskId: job.id,
|
|
730
733
|
checks: checks ?? [],
|
|
731
734
|
});
|
|
732
735
|
if (checks?.length) {
|
|
@@ -737,7 +740,7 @@ export async function runFleetDaemon() {
|
|
|
737
740
|
} catch (e) {
|
|
738
741
|
warn(`plan check failed for "${job.title}": ${e?.message ?? e}`);
|
|
739
742
|
// Clear the flag anyway — a stuck job would re-run every poll forever.
|
|
740
|
-
await reportMergeOutcome(PLAN_CHECK_DONE_URL, {
|
|
743
|
+
await reportMergeOutcome(PLAN_CHECK_DONE_URL, { taskId: job.id, checks: [] });
|
|
741
744
|
} finally {
|
|
742
745
|
checkingPlans.delete(job.id);
|
|
743
746
|
}
|
|
@@ -797,7 +800,7 @@ export async function runFleetDaemon() {
|
|
|
797
800
|
joinChain = joinChain.then(async () => {
|
|
798
801
|
let settled = false;
|
|
799
802
|
try {
|
|
800
|
-
const target = worktreeBuilding(job.intentId);
|
|
803
|
+
const target = worktreeBuilding(job.taskId ?? job.intentId);
|
|
801
804
|
if (!target) {
|
|
802
805
|
// The run ended (or moved) between the human pressing ⚡ and this
|
|
803
806
|
// poll. Settle rather than retry: there is no worktree to join, and
|
|
@@ -818,11 +821,11 @@ export async function runFleetDaemon() {
|
|
|
818
821
|
const claim = await postForData(JOIN_TAKE_URL, { joinId: job.id });
|
|
819
822
|
if (!claim?.taken) return;
|
|
820
823
|
note(
|
|
821
|
-
`${c.cyan('quick')} ${c.dim(`— ${job.askedByName || 'someone'} on "${job.intentTitle || 'a task'}"`)}`
|
|
824
|
+
`${c.cyan('quick')} ${c.dim(`— ${job.askedByName || 'someone'} on "${job.taskTitle || job.intentTitle || 'a task'}"`)}`
|
|
822
825
|
);
|
|
823
826
|
const out = await runTurn({
|
|
824
827
|
prompt: QUICK_EDIT_KICKOFF({
|
|
825
|
-
intentTitle: job.intentTitle,
|
|
828
|
+
intentTitle: job.taskTitle ?? job.intentTitle,
|
|
826
829
|
instruction: job.instruction,
|
|
827
830
|
askedByName: job.askedByName,
|
|
828
831
|
}),
|
|
@@ -946,7 +949,7 @@ export async function runFleetDaemon() {
|
|
|
946
949
|
if (!isValidPrUrl(job.prUrl, originSlug(repoRoot))) {
|
|
947
950
|
mergeAttempts.delete(job.id);
|
|
948
951
|
await reportMergeOutcome(MERGE_FAILED_URL, {
|
|
949
|
-
|
|
952
|
+
taskId: job.id,
|
|
950
953
|
message: 'refused: PR URL is not a pull request in this repository',
|
|
951
954
|
});
|
|
952
955
|
warn(`merge REFUSED for "${job.title}": untrusted PR URL ${String(job.prUrl)}`);
|
|
@@ -973,7 +976,7 @@ export async function runFleetDaemon() {
|
|
|
973
976
|
if (!/no changes|already/i.test(err)) {
|
|
974
977
|
mergeAttempts.delete(job.id);
|
|
975
978
|
await reportMergeOutcome(MERGE_FAILED_URL, {
|
|
976
|
-
|
|
979
|
+
taskId: job.id,
|
|
977
980
|
message: `could not retarget the stacked PR onto ${baseBranchName(baseRef)} — merging it now would land in the branch below it, not ${baseBranchName(baseRef)}`,
|
|
978
981
|
});
|
|
979
982
|
warn(`merge held for "${job.title}": retarget failed — ${err.split('\n')[0]}`);
|
|
@@ -1010,7 +1013,7 @@ export async function runFleetDaemon() {
|
|
|
1010
1013
|
}
|
|
1011
1014
|
if (merged) {
|
|
1012
1015
|
mergeAttempts.delete(job.id);
|
|
1013
|
-
await reportMergeOutcome(MERGE_DONE_URL, {
|
|
1016
|
+
await reportMergeOutcome(MERGE_DONE_URL, { taskId: job.id });
|
|
1014
1017
|
ok(`${c.cyan('merged')} ${c.dim(`— ${job.title} → ${baseRef}`)}`);
|
|
1015
1018
|
// The code just landed — re-ground the living wiki for what shipped
|
|
1016
1019
|
// (touched nodes re-read + a persistent feature-history node).
|
|
@@ -1023,7 +1026,7 @@ export async function runFleetDaemon() {
|
|
|
1023
1026
|
// button + notifies) — the job disappears from the roster.
|
|
1024
1027
|
mergeAttempts.delete(job.id);
|
|
1025
1028
|
await reportMergeOutcome(MERGE_FAILED_URL, {
|
|
1026
|
-
|
|
1029
|
+
taskId: job.id,
|
|
1027
1030
|
message: failedReason,
|
|
1028
1031
|
});
|
|
1029
1032
|
warn(`merge failed for "${job.title}": ${failedReason} — reported to the thread`);
|
|
@@ -1083,7 +1086,7 @@ export async function runFleetDaemon() {
|
|
|
1083
1086
|
} else if (job.prUrl || job.branch) {
|
|
1084
1087
|
warn(`cleanup REFUSED for "${job.title}": untrusted PR/branch value`);
|
|
1085
1088
|
}
|
|
1086
|
-
await reportMergeOutcome(CLEANUP_DONE_URL, {
|
|
1089
|
+
await reportMergeOutcome(CLEANUP_DONE_URL, { taskId: job.id });
|
|
1087
1090
|
ok(`${c.cyan('cleaned')} ${c.dim(`— ${job.title}`)}`);
|
|
1088
1091
|
} finally {
|
|
1089
1092
|
cleaning.delete(job.id);
|
|
@@ -1397,7 +1400,7 @@ export async function runFleetDaemon() {
|
|
|
1397
1400
|
// sweep), so a failing re-ground can't loop-burn quota. Only a
|
|
1398
1401
|
// crash BEFORE this line leaves the job listed for a retry.
|
|
1399
1402
|
regroundAttempts.delete(task.intentId);
|
|
1400
|
-
await reportMergeOutcome(REGROUND_DONE_URL, {
|
|
1403
|
+
await reportMergeOutcome(REGROUND_DONE_URL, { taskId: task.intentId });
|
|
1401
1404
|
}
|
|
1402
1405
|
} catch (e) {
|
|
1403
1406
|
warn(`wiki ${task.type} failed: ${e.message}`);
|
|
@@ -1553,7 +1556,13 @@ export async function runFleetDaemon() {
|
|
|
1553
1556
|
mintedAt.set(a.agentId, Date.now());
|
|
1554
1557
|
}
|
|
1555
1558
|
hasWorkByAgent.set(a.agentId, !!a.hasWork);
|
|
1556
|
-
|
|
1559
|
+
// The hint's task id, new name first. Normalized ONTO `intentId` here so
|
|
1560
|
+
// every downstream read (poll worker, kickoff, diffstat attribution)
|
|
1561
|
+
// keeps its one spelling — intent is still the daemon's internal word,
|
|
1562
|
+
// taskId is the wire's.
|
|
1563
|
+
const nextId = a.next && (a.next.taskId ?? a.next.intentId);
|
|
1564
|
+
if (a.next && typeof nextId === 'string')
|
|
1565
|
+
nextByAgent.set(a.agentId, { ...a.next, intentId: nextId });
|
|
1557
1566
|
else nextByAgent.delete(a.agentId);
|
|
1558
1567
|
if (!workers.has(a.agentId)) {
|
|
1559
1568
|
// Local ceiling, enforced and not merely requested. The roster can carry
|
|
@@ -1647,8 +1656,9 @@ export async function runFleetDaemon() {
|
|
|
1647
1656
|
// whose earlier mint failed.
|
|
1648
1657
|
enqueueSweep(roster.codeMapJob);
|
|
1649
1658
|
for (const j of roster.regroundJobs ?? []) {
|
|
1650
|
-
|
|
1651
|
-
|
|
1659
|
+
const rid = j && (j.taskId ?? j.intentId); // new name first, old as fallback
|
|
1660
|
+
if (!j || typeof rid !== 'string') continue; // a null element would throw + wedge the loop
|
|
1661
|
+
enqueueReground(rid, j.prUrl, j.title, j.dirtiesPages);
|
|
1652
1662
|
}
|
|
1653
1663
|
void drainWiki();
|
|
1654
1664
|
|
package/bin/lib/live.mjs
CHANGED
|
@@ -88,7 +88,7 @@ async function registerLiveTarget(intentId, kind, url) {
|
|
|
88
88
|
'Content-Type': 'application/json',
|
|
89
89
|
},
|
|
90
90
|
signal: AbortSignal.timeout(30_000),
|
|
91
|
-
body: JSON.stringify({ intentId, kind, url, ttlMinutes: PREVIEW_TTL_MINUTES }),
|
|
91
|
+
body: JSON.stringify({ taskId: intentId, kind, url, ttlMinutes: PREVIEW_TTL_MINUTES }),
|
|
92
92
|
});
|
|
93
93
|
} catch {
|
|
94
94
|
/* best-effort — the tunnel still works; it just isn't linked in the app */
|
|
@@ -108,7 +108,7 @@ function clearLiveTarget(intentId, kind) {
|
|
|
108
108
|
'Content-Type': 'application/json',
|
|
109
109
|
},
|
|
110
110
|
signal: AbortSignal.timeout(5_000),
|
|
111
|
-
body: JSON.stringify({ intentId, kind }),
|
|
111
|
+
body: JSON.stringify({ taskId: intentId, kind }),
|
|
112
112
|
}).catch(() => {});
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -127,7 +127,7 @@ function postPreviewNote(intentId, text) {
|
|
|
127
127
|
signal: AbortSignal.timeout(10_000),
|
|
128
128
|
// Scrub: preview failure reasons can quote dev-server output, which can
|
|
129
129
|
// echo env values.
|
|
130
|
-
body: JSON.stringify({ intentId, text: envScrub(text) }),
|
|
130
|
+
body: JSON.stringify({ taskId: intentId, text: envScrub(text) }),
|
|
131
131
|
}).catch(() => {});
|
|
132
132
|
}
|
|
133
133
|
|
|
@@ -1274,7 +1274,13 @@ export async function runLiveTask({
|
|
|
1274
1274
|
runtimes: DRIVABLE_HERE,
|
|
1275
1275
|
}).catch(() => null);
|
|
1276
1276
|
if (!claim || claim.claimed !== true) return { outcome: 'nothing' };
|
|
1277
|
-
const
|
|
1277
|
+
const runId = claim.runId;
|
|
1278
|
+
// New name first: the server returns `taskId` natively and mirrors
|
|
1279
|
+
// `intentId` beside it for exactly this read. Reading taskId is what lets
|
|
1280
|
+
// that mirror (and the fleet routes' intentId compat) retire once
|
|
1281
|
+
// daemon:min passes this release. The variable keeps the old spelling —
|
|
1282
|
+
// it is the daemon's internal word, not a wire field.
|
|
1283
|
+
const intentId = claim.taskId ?? claim.intentId;
|
|
1278
1284
|
const brief = claim.brief ?? {};
|
|
1279
1285
|
const title = brief.title ?? 'a task';
|
|
1280
1286
|
|
package/bin/lib/resources.mjs
CHANGED
|
@@ -121,7 +121,7 @@ export function machineSnapshot({ worktreeDir, tasks = [] } = {}) {
|
|
|
121
121
|
diskTotal: disk?.total ?? null,
|
|
122
122
|
// Per-task, so "the box is full" can be traced to the task that filled it.
|
|
123
123
|
tasks: tasks
|
|
124
|
-
.map((t) => ({
|
|
125
|
-
.filter((t) => t.
|
|
124
|
+
.map((t) => ({ taskId: t.intentId, rss: processTreeRssBytes(t.pid) }))
|
|
125
|
+
.filter((t) => t.taskId && t.rss),
|
|
126
126
|
};
|
|
127
127
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "Run your own coding CLIs as headless build agents for Flowviant — Claude Code or Codex, on your own credentials. Claims dispatched work, opens PRs, captures review evidence, and routes questions back to you.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|