flowviant 0.76.0 → 0.77.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/work.mjs +129 -0
- package/package.json +2 -2
package/bin/lib/work.mjs
CHANGED
|
@@ -3712,6 +3712,31 @@ export function createWorkManager({
|
|
|
3712
3712
|
await postAgentTurn({ turnId, outcome: 'nothing' });
|
|
3713
3713
|
return;
|
|
3714
3714
|
}
|
|
3715
|
+
/**
|
|
3716
|
+
* A TASK TURN WITH NO CARD IS REFUSED, NOT IMPROVISED.
|
|
3717
|
+
*
|
|
3718
|
+
* The kickoff below branches on `job.kind === 'task' && job.task` and falls
|
|
3719
|
+
* through to the HUMAN kickoff otherwise — and a task turn's body is empty
|
|
3720
|
+
* by design, because the daemon composes the prompt from the card's own
|
|
3721
|
+
* spec. So a card deleted while its agent held it spawned a real CLI on a
|
|
3722
|
+
* prompt whose entire content was "a member of this project said: (nothing).
|
|
3723
|
+
* Carry on, and end with the JSON object." Nothing on either side refused
|
|
3724
|
+
* it, and an ordinary gesture reached it: the board's Delete.
|
|
3725
|
+
*
|
|
3726
|
+
* The server now declines to hand out such a job at all. This is the
|
|
3727
|
+
* backstop, and it is worth having on its own: it costs one comparison and
|
|
3728
|
+
* it is the layer that cannot be skipped by a server on an older deploy.
|
|
3729
|
+
* `nothing` is the honest outcome — the agent goes to Stuck saying the turn
|
|
3730
|
+
* produced nothing, which is exactly what happened.
|
|
3731
|
+
*/
|
|
3732
|
+
if (job.kind === 'task' && !job.task) {
|
|
3733
|
+
await postAgentTurn({
|
|
3734
|
+
turnId,
|
|
3735
|
+
outcome: 'nothing',
|
|
3736
|
+
answer: 'the card this turn was for no longer exists',
|
|
3737
|
+
});
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3715
3740
|
|
|
3716
3741
|
await inPlace(place, false, async () => {
|
|
3717
3742
|
const dir = placeWtFor(place);
|
|
@@ -4163,6 +4188,110 @@ export function createWorkManager({
|
|
|
4163
4188
|
await postAgentMerge({ agentId, ok: true, sha: tip || undefined });
|
|
4164
4189
|
return;
|
|
4165
4190
|
}
|
|
4191
|
+
/**
|
|
4192
|
+
* THIS PROJECT MERGES THROUGH A PULL REQUEST.
|
|
4193
|
+
*
|
|
4194
|
+
* PR mode existed for sessions and was simply not honoured for agents:
|
|
4195
|
+
* nothing read the project's merge mode on this path and the direct
|
|
4196
|
+
* merge below ran unconditionally. On the repos PR mode exists FOR —
|
|
4197
|
+
* branch protection refuses a direct push of a merge commit — every
|
|
4198
|
+
* agent approval failed at the push and came back to review carrying
|
|
4199
|
+
* git's refusal, forever.
|
|
4200
|
+
*
|
|
4201
|
+
* PUSH, CREATE, MERGE, in that order, and each step's reasoning is the
|
|
4202
|
+
* session PR job's: push first because GitHub merges the REMOTE tip;
|
|
4203
|
+
* `--fill` titles from the branch's own commits so nothing is invented;
|
|
4204
|
+
* `--merge` and never squash, because the cards' receipts are commit
|
|
4205
|
+
* shas and a squash rewrites them off base, orphaning every receipt and
|
|
4206
|
+
* blinding the landed observer's trailer read. Adopt only an OPEN PR:
|
|
4207
|
+
* gh's branch finder falls back to the most recent merged one, and
|
|
4208
|
+
* adopting a dead PR would report success over work that never moves.
|
|
4209
|
+
*/
|
|
4210
|
+
if (job.prMode) {
|
|
4211
|
+
try {
|
|
4212
|
+
execFileSync('gh', ['auth', 'status'], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
4213
|
+
} catch (e) {
|
|
4214
|
+
await postAgentMerge({
|
|
4215
|
+
agentId,
|
|
4216
|
+
ok: false,
|
|
4217
|
+
detail:
|
|
4218
|
+
e?.code === 'ENOENT'
|
|
4219
|
+
? 'this project merges through pull requests, and the GitHub CLI (gh) is not installed on this machine'
|
|
4220
|
+
: `this project merges through pull requests, and gh is not signed in here: ${ghFirstLine(e)}`,
|
|
4221
|
+
});
|
|
4222
|
+
return;
|
|
4223
|
+
}
|
|
4224
|
+
const prBase = baseBranchName(baseRef());
|
|
4225
|
+
if (branch === prBase) {
|
|
4226
|
+
await postAgentMerge({
|
|
4227
|
+
agentId,
|
|
4228
|
+
ok: false,
|
|
4229
|
+
detail: `this agent is on the base branch (${prBase}) — there is nothing to open a pull request from`,
|
|
4230
|
+
});
|
|
4231
|
+
return;
|
|
4232
|
+
}
|
|
4233
|
+
try {
|
|
4234
|
+
git(['push', '-u', 'origin', branch], wt);
|
|
4235
|
+
} catch (e) {
|
|
4236
|
+
await postAgentMerge({ agentId, ok: false, detail: ghFirstLine(e) });
|
|
4237
|
+
return;
|
|
4238
|
+
}
|
|
4239
|
+
let prUrl = null;
|
|
4240
|
+
try {
|
|
4241
|
+
const j = JSON.parse(
|
|
4242
|
+
execFileSync('gh', ['pr', 'view', branch, '--json', 'url,state'], {
|
|
4243
|
+
cwd: repoRoot,
|
|
4244
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4245
|
+
}).toString()
|
|
4246
|
+
);
|
|
4247
|
+
if (j?.state === 'OPEN' && typeof j?.url === 'string') prUrl = j.url.trim();
|
|
4248
|
+
} catch {
|
|
4249
|
+
/* no PR for this branch at all — created below */
|
|
4250
|
+
}
|
|
4251
|
+
if (!prUrl) {
|
|
4252
|
+
try {
|
|
4253
|
+
const out = execFileSync(
|
|
4254
|
+
'gh',
|
|
4255
|
+
// baseBranchName, not baseRef: gh 422s on a remote-tracking name.
|
|
4256
|
+
['pr', 'create', '--head', branch, '--base', prBase, '--fill'],
|
|
4257
|
+
{ cwd: repoRoot, stdio: ['ignore', 'pipe', 'pipe'] }
|
|
4258
|
+
)
|
|
4259
|
+
.toString()
|
|
4260
|
+
.trim();
|
|
4261
|
+
prUrl = out.split('\n').filter(Boolean).pop() ?? null;
|
|
4262
|
+
} catch (e) {
|
|
4263
|
+
await postAgentMerge({ agentId, ok: false, detail: ghFirstLine(e) });
|
|
4264
|
+
return;
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
try {
|
|
4268
|
+
execFileSync('gh', ['pr', 'merge', branch, '--merge'], {
|
|
4269
|
+
cwd: repoRoot,
|
|
4270
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4271
|
+
});
|
|
4272
|
+
} catch (e) {
|
|
4273
|
+
const line = ghFirstLine(e);
|
|
4274
|
+
// Already merged is a SUCCESS: a re-offered job, or somebody merged
|
|
4275
|
+
// it in the browser. The observer closes the cards either way.
|
|
4276
|
+
if (!/already merged/i.test(line)) {
|
|
4277
|
+
await postAgentMerge({
|
|
4278
|
+
agentId,
|
|
4279
|
+
ok: false,
|
|
4280
|
+
detail:
|
|
4281
|
+
prUrl && PR_URL_RE.test(prUrl) ? `${line} — the pull request is at ${prUrl}` : line,
|
|
4282
|
+
});
|
|
4283
|
+
return;
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
// THE TIP, not a merge commit: the merge commit was made on GitHub and
|
|
4287
|
+
// this machine has not fetched it. It identifies the state we asked to
|
|
4288
|
+
// be merged, which is what the receipt is for — the cards themselves
|
|
4289
|
+
// close when the landed observer sees the commits arrive on base.
|
|
4290
|
+
await postAgentMerge({ agentId, ok: true, sha: tip });
|
|
4291
|
+
onRepoChanged();
|
|
4292
|
+
landed.observe();
|
|
4293
|
+
return;
|
|
4294
|
+
}
|
|
4166
4295
|
try {
|
|
4167
4296
|
shipMergeOutward({
|
|
4168
4297
|
tip,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.77.0",
|
|
4
4
|
"description": "Run your own coding CLIs as build agents for Flowviant \u2014 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": {
|
|
@@ -40,4 +40,4 @@
|
|
|
40
40
|
"bugs": {
|
|
41
41
|
"url": "https://github.com/flowviant/cli/issues"
|
|
42
42
|
}
|
|
43
|
-
}
|
|
43
|
+
}
|