@chrok/pi-braid 0.3.6 → 0.4.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/README.md +73 -15
- package/dist/drain.js +165 -0
- package/dist/index.js +26 -5
- package/dist/jobs.js +17 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,29 +5,34 @@
|
|
|
5
5
|
This optional Pi extension runs Braid agent graphs as background jobs by default, with
|
|
6
6
|
bounded loops, live updates, pause/resume, and a live flow panel.
|
|
7
7
|
|
|
8
|
-
**0.
|
|
8
|
+
**0.4 API:** The npm badge shows the published version; see
|
|
9
9
|
[GitHub releases](https://github.com/Epsirom/braid/releases) for release notes.
|
|
10
|
-
Review the [0.
|
|
11
|
-
before upgrading:
|
|
12
|
-
|
|
13
|
-
Users coming from 0.
|
|
14
|
-
[0.
|
|
10
|
+
Review the [0.3 → 0.4 migration guide](https://github.com/Epsirom/braid/blob/v0.4.0/docs/compatibility.md#migrating-from-03-to-04)
|
|
11
|
+
before upgrading: print and JSON runs wait for background jobs, Pi SDK sessions
|
|
12
|
+
in the default print mode drain jobs before `session.prompt()` returns, and
|
|
13
|
+
blocking mode is deprecated. Users coming from 0.2 also need the
|
|
14
|
+
[0.2 → 0.3 guide](https://github.com/Epsirom/braid/blob/v0.4.0/docs/compatibility.md#migrating-from-02-to-03),
|
|
15
|
+
and users coming from 0.1 the
|
|
16
|
+
[0.1 → 0.2 guide](https://github.com/Epsirom/braid/blob/v0.4.0/docs/compatibility.md#migrating-from-01-to-02).
|
|
15
17
|
For the previous release, use the
|
|
16
|
-
[0.
|
|
18
|
+
[0.3.6 guide](https://github.com/Epsirom/braid/blob/v0.3.6/integrations/pi/README.md).
|
|
17
19
|
|
|
18
20
|
It registers:
|
|
19
21
|
|
|
20
22
|
- `braid` — submit a graph with optional bounded loops; receive a `jobId`
|
|
21
|
-
immediately in background mode
|
|
23
|
+
immediately in background mode (print/JSON runs wait for it before exiting), or
|
|
24
|
+
final results in deprecated blocking mode.
|
|
22
25
|
- `braid_status` — retrieve progress and results with `{ "jobId": "..." }`, or
|
|
23
26
|
add `"executionId": "..."` for an exact invocation's full output/error.
|
|
24
27
|
`"nodeId": "..."` selects that definition's latest invocation. Omit all IDs
|
|
25
28
|
to list jobs in the current session.
|
|
26
29
|
- `braid_update` / `braid_resume` — edit live definitions or release paused executions.
|
|
27
30
|
- `braid_cancel` — cancel a job with `{ "jobId": "..." }`.
|
|
31
|
+
- `braid_finish` — print/JSON runs only: record the run outcome with
|
|
32
|
+
`{ "status": "success" | "failure", "summary": "..." }`.
|
|
28
33
|
- `/braid [jobId]` — open a live flow panel in interactive Pi.
|
|
29
|
-
- `/braid:blocking [on|off]` —
|
|
30
|
-
omit the argument to show the current mode.
|
|
34
|
+
- `/braid:blocking [on|off]` — deprecated, removed in 0.5.0: enable or disable
|
|
35
|
+
blocking for new `braid` tool calls; omit the argument to show the current mode.
|
|
31
36
|
- `/braid:review [target or focus]` — run a bounded parallel review/fix loop.
|
|
32
37
|
- `/braid:clear` — clear the progress widget above the editor; run it again while
|
|
33
38
|
the widget is empty to restore what was cleared.
|
|
@@ -53,12 +58,65 @@ work, or end its turn if it needs the result, and not to sleep, use a timer, or
|
|
|
53
58
|
poll in a loop to wait for the job.
|
|
54
59
|
Stopping the foreground response does not stop background jobs.
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
> command, and behavior may change or be replaced by an async-preserving CI mode
|
|
58
|
-
> that keeps reminders, `pauseAfter`, and live updates
|
|
59
|
-
> ([#80](https://github.com/Epsirom/braid/issues/80)).
|
|
61
|
+
### Single-shot and CI runs
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
In print mode (`pi -p`) and `--mode json`, background execution works unchanged:
|
|
64
|
+
the run stays open until every Braid job reaches a terminal status and its
|
|
65
|
+
reminder has been delivered, then exits after the parent's final response.
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
pi -p "Use Braid to review the changes and summarize the results."
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The parent submits, ends its turn, and is resumed by the reminder as in
|
|
72
|
+
interactive Pi, so `pauseAfter`, `notifyOnCompletion`, `braid_update`, and
|
|
73
|
+
`braid_resume` keep working. Paused executions are never resumed automatically.
|
|
74
|
+
If the parent ends its turn while only paused executions remain, it receives one
|
|
75
|
+
warning naming them; ending the next turn without resuming them cancels their
|
|
76
|
+
jobs, gives the parent a final turn on the cancellation reminder, and exits 1.
|
|
77
|
+
|
|
78
|
+
`--braid-ci-timeout <duration>` bounds the whole run, starting at its first
|
|
79
|
+
prompt. It accepts milliseconds or an `s`, `m`, or `h` suffix (for example
|
|
80
|
+
`--braid-ci-timeout 30m`). On expiry, outstanding jobs are cancelled, the parent
|
|
81
|
+
gets a final turn on the cancellation reminders, and the run exits 1. Either
|
|
82
|
+
forced cancellation prints a `Braid:` line on stderr.
|
|
83
|
+
|
|
84
|
+
A failed graph does not by itself fail the run. Instead, the parent is told to
|
|
85
|
+
call `braid_finish` with `status` `success` or `failure` and a short `summary`
|
|
86
|
+
once the task is done. The tool only records the outcome; the run still ends
|
|
87
|
+
when the parent ends its turn with no Braid job outstanding, and a later call
|
|
88
|
+
replaces the earlier outcome. A final `failure` exits 1 and prints
|
|
89
|
+
`Braid: run reported failure: <summary>` on stderr. `success` never overrides a
|
|
90
|
+
non-zero exit from a forced cancellation or from Pi itself. If the parent never
|
|
91
|
+
calls it, the exit status is Pi's own apart from forced cancellations.
|
|
92
|
+
|
|
93
|
+
In `--mode json`, the final outcome is also written as a session entry, after the
|
|
94
|
+
`tool_execution_end` event of the call itself:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{"type":"entry_appended","entry":{"type":"custom","customType":"braid-finish","data":{"status":"failure","summary":"Review found 2 blocking issues.","forcedFailures":[]},...}}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`forcedFailures` lists any forced cancellations, which fail the run whatever the
|
|
101
|
+
reported status. The entry is not part of model context, and text mode still
|
|
102
|
+
prints only the parent's final response. `braid_finish` is not offered in
|
|
103
|
+
interactive or RPC sessions.
|
|
104
|
+
|
|
105
|
+
Pi SDK sessions default to print mode too, so `session.prompt()` returns only
|
|
106
|
+
after outstanding Braid jobs drain. Hosts that stay running and want reminders
|
|
107
|
+
to start new runs after settling should bind with another mode, for example
|
|
108
|
+
`session.bindExtensions({ mode: "rpc" })`.
|
|
109
|
+
|
|
110
|
+
### Blocking mode (deprecated)
|
|
111
|
+
|
|
112
|
+
> **Deprecated:** `--braid-blocking` and `/braid:blocking` will be removed in
|
|
113
|
+
> 0.5.0. Print and JSON runs now wait for background jobs (see above), which keeps
|
|
114
|
+
> `pauseAfter`, reminders, and live updates, and `braid_finish` sets the exit
|
|
115
|
+
> status. To migrate, drop `--braid-blocking` (or `/braid:blocking on`) from the
|
|
116
|
+
> command line ([#80](https://github.com/Epsirom/braid/issues/80)). Using either
|
|
117
|
+
> prints a one-time warning: on stderr in print/JSON mode, or as a UI notice.
|
|
118
|
+
|
|
119
|
+
Until then, to make each `braid` call wait in the foreground, enable blocking when starting Pi:
|
|
62
120
|
|
|
63
121
|
```sh
|
|
64
122
|
pi --braid-blocking -p "Use Braid to review the changes and summarize the results."
|
package/dist/drain.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
2
|
+
import { defineTool, } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
/** Pi exits once print and JSON runs settle, so Braid drains jobs before that. */
|
|
4
|
+
export const isSingleShot = (ctx) => ctx.mode === "print" || ctx.mode === "json";
|
|
5
|
+
export const BRAID_DRAIN_GUIDANCE = "This is a single-shot run. It stays open until every Braid job finishes and its reminder is delivered, so submit, end your turn, and continue from the reminder as usual. " +
|
|
6
|
+
"Paused executions are not resumed automatically: resume or cancel them before ending your turn. If only paused executions remain when you end a turn, you get one warning; ending the next turn without resuming them cancels their jobs and the run exits with a failure status.";
|
|
7
|
+
export const BRAID_FINISH_GUIDANCE = "When the task is done and no Braid job is outstanding, call braid_finish once with status success or failure and a short summary of the outcome, then end your turn with your final response. " +
|
|
8
|
+
"A failure status makes the run exit 1. braid_finish only records the outcome: the run still ends when your turn ends and every Braid job has finished. Call it again to replace the outcome if more work follows.";
|
|
9
|
+
/** The run's wall-clock budget is checked by a timer, so the drain also wakes periodically. */
|
|
10
|
+
const RECHECK_MS = 1_000;
|
|
11
|
+
export function parseDuration(value) {
|
|
12
|
+
const match = /^\s*(\d+(?:\.\d+)?)\s*(ms|s|m|h)?\s*$/.exec(value);
|
|
13
|
+
const scale = { ms: 1, s: 1_000, m: 60_000, h: 3_600_000 };
|
|
14
|
+
const ms = match ? Number(match[1]) * scale[(match[2] ?? "ms")] : NaN;
|
|
15
|
+
if (!(ms > 0) || ms > 2_147_483_647)
|
|
16
|
+
throw new Error(`Invalid --braid-ci-timeout ${JSON.stringify(value)}: use a positive duration such as 90000, 90s, 30m, or 2h`);
|
|
17
|
+
return ms;
|
|
18
|
+
}
|
|
19
|
+
const stalledReminder = (jobs) => `[system-reminder] This single-shot run cannot end while Braid jobs are outstanding, and nothing can progress: ` +
|
|
20
|
+
jobs.map(job => `${job.handle} is waiting on paused executions ${JSON.stringify(job.pausedExecutionIds)} at revision ${job.revision}`).join("; ") +
|
|
21
|
+
`. Resume them with braid_resume or braid_update with resume, or cancel the job with braid_cancel, then continue the original task. ` +
|
|
22
|
+
`If you end your turn again without doing so, Braid cancels these jobs and the run exits with a failure status. [/system-reminder]`;
|
|
23
|
+
/**
|
|
24
|
+
* Keeps print and JSON runs alive until every Braid job reaches a terminal
|
|
25
|
+
* status. Reminders are steered into the run as in interactive mode, so the
|
|
26
|
+
* parent keeps the async model: it ends its turn and the reminder resumes it.
|
|
27
|
+
*/
|
|
28
|
+
export function registerDrain(pi, jobs, hasPendingReminders) {
|
|
29
|
+
pi.registerFlag("braid-ci-timeout", {
|
|
30
|
+
description: "Wall-clock budget for Braid jobs in print/JSON mode (e.g. 90000, 90s, 30m, 2h); on expiry, outstanding jobs are cancelled and the run exits 1",
|
|
31
|
+
type: "string",
|
|
32
|
+
});
|
|
33
|
+
let closed = false;
|
|
34
|
+
let timer;
|
|
35
|
+
let timedOut = false;
|
|
36
|
+
let warnedStall;
|
|
37
|
+
const failures = new Set();
|
|
38
|
+
let outcome;
|
|
39
|
+
let reported;
|
|
40
|
+
const fail = (reason) => {
|
|
41
|
+
if (failures.has(reason))
|
|
42
|
+
return;
|
|
43
|
+
failures.add(reason);
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
console.error(`Braid: ${reason}`);
|
|
46
|
+
};
|
|
47
|
+
const cancelOutstanding = (reason) => {
|
|
48
|
+
const outstanding = jobs.outstanding();
|
|
49
|
+
if (!outstanding.length)
|
|
50
|
+
return;
|
|
51
|
+
fail(reason);
|
|
52
|
+
for (const job of outstanding)
|
|
53
|
+
jobs.cancel(job.jobId);
|
|
54
|
+
};
|
|
55
|
+
const expire = () => {
|
|
56
|
+
timedOut = true;
|
|
57
|
+
cancelOutstanding("--braid-ci-timeout expired; cancelled outstanding Braid jobs");
|
|
58
|
+
};
|
|
59
|
+
const nextChange = () => new Promise(resolve => {
|
|
60
|
+
const done = () => {
|
|
61
|
+
clearTimeout(tick);
|
|
62
|
+
unsubscribe();
|
|
63
|
+
resolve();
|
|
64
|
+
};
|
|
65
|
+
const tick = setTimeout(done, RECHECK_MS);
|
|
66
|
+
const unsubscribe = jobs.subscribe(done);
|
|
67
|
+
});
|
|
68
|
+
const finishParameters = Type.Object({
|
|
69
|
+
status: StringEnum(["success", "failure"], { description: "Whether the run achieved its task. failure makes the process exit 1." }),
|
|
70
|
+
summary: Type.String({ minLength: 1, description: "Short summary of the outcome for CI logs and JSON output." }),
|
|
71
|
+
}, { additionalProperties: false });
|
|
72
|
+
pi.registerTool(defineTool({
|
|
73
|
+
name: "braid_finish",
|
|
74
|
+
label: "Finish Braid run",
|
|
75
|
+
description: "Single-shot (print/JSON) runs only: record the run outcome for CI. status failure makes the process exit 1. " +
|
|
76
|
+
"It does not end the run; call it once the task is done and no Braid job is outstanding, then end your turn. A later call replaces the outcome.",
|
|
77
|
+
parameters: finishParameters,
|
|
78
|
+
// Activated in single-shot sessions only, so interactive runs never see it.
|
|
79
|
+
defaultActive: false,
|
|
80
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
81
|
+
if (!isSingleShot(ctx))
|
|
82
|
+
throw new Error("braid_finish is only available in print and JSON runs");
|
|
83
|
+
outcome = { status: params.status, summary: params.summary };
|
|
84
|
+
const outstanding = jobs.outstanding().length;
|
|
85
|
+
return {
|
|
86
|
+
content: [{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: `Recorded ${params.status}.` + (outstanding
|
|
89
|
+
? ` ${outstanding} Braid job(s) are still outstanding; the run stays open until they finish, so continue from their reminders and call braid_finish again if the outcome changes.`
|
|
90
|
+
: " End your turn with your final response."),
|
|
91
|
+
}],
|
|
92
|
+
details: outcome,
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
}));
|
|
96
|
+
/** Applies the reported outcome once the run is about to settle with nothing outstanding. */
|
|
97
|
+
const settle = (event) => {
|
|
98
|
+
if (!outcome || outcome === reported)
|
|
99
|
+
return;
|
|
100
|
+
reported = outcome;
|
|
101
|
+
// Never reset the exit code: success must not mask a forced cancellation or a Pi error.
|
|
102
|
+
if (outcome.status === "failure") {
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
console.error(`Braid: run reported failure: ${outcome.summary}`);
|
|
105
|
+
}
|
|
106
|
+
// A custom entry is not model context and leaves the final assistant message last,
|
|
107
|
+
// so text output is unchanged; JSON mode streams it as an entry_appended event.
|
|
108
|
+
return {
|
|
109
|
+
entries: [...event.entries, {
|
|
110
|
+
type: "custom",
|
|
111
|
+
customType: "braid-finish",
|
|
112
|
+
data: { ...outcome, forcedFailures: [...failures] },
|
|
113
|
+
}],
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
pi.on("session_start", (_event, ctx) => {
|
|
117
|
+
if (isSingleShot(ctx) && !pi.getActiveTools().includes("braid_finish"))
|
|
118
|
+
pi.setActiveTools([...pi.getActiveTools(), "braid_finish"]);
|
|
119
|
+
});
|
|
120
|
+
pi.on("before_agent_start", (_event, ctx) => {
|
|
121
|
+
const budget = pi.getFlag("braid-ci-timeout");
|
|
122
|
+
if (timer || timedOut || !isSingleShot(ctx) || typeof budget !== "string")
|
|
123
|
+
return;
|
|
124
|
+
// The budget covers the whole run, starting at its first prompt.
|
|
125
|
+
timer = setTimeout(expire, parseDuration(budget));
|
|
126
|
+
timer.unref();
|
|
127
|
+
});
|
|
128
|
+
pi.on("agent_before_settle", async (event, ctx) => {
|
|
129
|
+
if (!isSingleShot(ctx))
|
|
130
|
+
return;
|
|
131
|
+
while (!closed) {
|
|
132
|
+
// A queued reminder continues this run; Pi settles only when its queues are empty.
|
|
133
|
+
if (hasPendingReminders())
|
|
134
|
+
return;
|
|
135
|
+
if (timedOut)
|
|
136
|
+
cancelOutstanding("--braid-ci-timeout expired; cancelled outstanding Braid jobs");
|
|
137
|
+
const outstanding = jobs.outstanding();
|
|
138
|
+
if (!outstanding.length)
|
|
139
|
+
return settle(event);
|
|
140
|
+
if (outstanding.every(job => job.stalled)) {
|
|
141
|
+
const key = JSON.stringify(outstanding.map(job => [job.jobId, job.revision, job.pausedExecutionIds]));
|
|
142
|
+
if (key !== warnedStall) {
|
|
143
|
+
warnedStall = key;
|
|
144
|
+
return {
|
|
145
|
+
entries: [...event.entries, {
|
|
146
|
+
type: "custom_message",
|
|
147
|
+
customType: "braid-drain-stalled",
|
|
148
|
+
display: true,
|
|
149
|
+
content: stalledReminder(outstanding),
|
|
150
|
+
details: { jobs: outstanding },
|
|
151
|
+
}],
|
|
152
|
+
continue: true,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// Cancellation reminders give the parent a final turn to report the outcome.
|
|
156
|
+
cancelOutstanding(`paused executions were never resumed; cancelled ${outstanding.map(job => job.handle).join(", ")}`);
|
|
157
|
+
}
|
|
158
|
+
await nextChange();
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
pi.on("session_shutdown", () => {
|
|
162
|
+
closed = true;
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
});
|
|
165
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Text } from "@earendil-works/pi-tui";
|
|
|
7
7
|
import { BraidJobs } from "./jobs.js";
|
|
8
8
|
import { registerBraidCommand, registerBraidWidget } from "./command.js";
|
|
9
9
|
import { registerReviewCommand } from "./review.js";
|
|
10
|
+
import { BRAID_DRAIN_GUIDANCE, BRAID_FINISH_GUIDANCE, isSingleShot, registerDrain } from "./drain.js";
|
|
10
11
|
import { renderGraphCall, renderGraphResult, renderNodeResult } from "./display.js";
|
|
11
12
|
import { formatJobList, formatJobStatus, formatNodeStatus, formatReceipt, formatResume, formatUpdate } from "./status-text.js";
|
|
12
13
|
const text = (description) => Type.String({ minLength: 1, pattern: "\\S", ...(description ? { description } : {}) });
|
|
@@ -97,6 +98,8 @@ export const BRAID_ASYNC_GUIDANCE = "By default, Braid is asynchronous: braid re
|
|
|
97
98
|
"Do not call sleep, run a shell sleep, use a timer, or enter a sleep/poll loop solely to wait for a Braid job; delays required by the user's actual task remain allowed. " +
|
|
98
99
|
"On the reminder, retrieve terminal outputs with braid_status before relying on them and continue the original task. Between reminders, use braid_status only for deliberate progress checks or diagnosis. " +
|
|
99
100
|
"Set notifyOnCompletion=true on selected nodes to receive intermediate success/failure reminders. Use braid_status({jobId, executionId}) to retrieve the exact execution; nodeId selects the latest instance. Use pauseAfter=true to hold outgoing scheduling. Definitions can always be changed with braid_update using expectedRevision; existing executions retain their captured inputs. Use resume in the same update to apply changes and release held executions atomically, or braid_resume for no graph changes.";
|
|
101
|
+
export const BRAID_BLOCKING_DEPRECATION = "Blocking mode (--braid-blocking, /braid:blocking) is deprecated and will be removed in 0.5.0. " +
|
|
102
|
+
"Print and JSON runs (pi -p) now wait for background Braid jobs; call braid_finish to report the outcome.";
|
|
100
103
|
/** Short reminder repeated in every submission receipt. */
|
|
101
104
|
export const BRAID_WAIT_REMINDER = "Braid runs asynchronously and notifies you when the job finishes. Continue independent work, or end your turn if you need the result before proceeding; the completion reminder will resume you. " +
|
|
102
105
|
"Do not sleep or poll in a loop to wait for this job. Retrieve its results with braid_status after the reminder.";
|
|
@@ -371,20 +374,33 @@ export function createBraidTools(jobs, blocking = () => false) {
|
|
|
371
374
|
}
|
|
372
375
|
export default function braidExtension(pi) {
|
|
373
376
|
pi.registerFlag("braid-blocking", {
|
|
374
|
-
description: "
|
|
377
|
+
description: "Deprecated, removed in 0.5.0: wait for braid tool calls to finish in the foreground. pi -p now waits for background jobs",
|
|
375
378
|
type: "boolean",
|
|
376
379
|
default: false,
|
|
377
380
|
});
|
|
378
381
|
let sessionBlocking;
|
|
379
382
|
const blocking = () => sessionBlocking ?? (pi.getFlag("braid-blocking") === true);
|
|
383
|
+
let warnedBlocking = false;
|
|
384
|
+
const warnBlocking = (ctx) => {
|
|
385
|
+
if (warnedBlocking)
|
|
386
|
+
return;
|
|
387
|
+
warnedBlocking = true;
|
|
388
|
+
// Print and JSON runs have no UI; stderr keeps JSON stdout parseable.
|
|
389
|
+
if (ctx.hasUI)
|
|
390
|
+
ctx.ui.notify(BRAID_BLOCKING_DEPRECATION, "warning");
|
|
391
|
+
else
|
|
392
|
+
console.error(`Braid: ${BRAID_BLOCKING_DEPRECATION}`);
|
|
393
|
+
};
|
|
380
394
|
pi.registerCommand("braid:blocking", {
|
|
381
|
-
description: "
|
|
395
|
+
description: "Deprecated, removed in 0.5.0: set blocking mode for new braid tool calls: /braid:blocking [on|off]; omit to show the current mode",
|
|
382
396
|
handler: async (args, ctx) => {
|
|
383
397
|
const setting = args.trim().toLowerCase();
|
|
384
398
|
if (setting === "on" || setting === "off")
|
|
385
399
|
sessionBlocking = setting === "on";
|
|
386
400
|
else if (setting)
|
|
387
401
|
throw new Error("Usage: /braid:blocking [on|off]");
|
|
402
|
+
if (setting === "on")
|
|
403
|
+
warnBlocking(ctx);
|
|
388
404
|
ctx.ui.notify(`Braid blocking mode: ${blocking() ? "on" : "off"}. Applies to new braid tool calls.`, "info");
|
|
389
405
|
},
|
|
390
406
|
});
|
|
@@ -441,6 +457,7 @@ export default function braidExtension(pi) {
|
|
|
441
457
|
}
|
|
442
458
|
});
|
|
443
459
|
const { braidTool, statusTool, cancelTool, updateTool, resumeTool } = createBraidTools(jobs, blocking);
|
|
460
|
+
registerDrain(pi, jobs, () => pending.size > 0);
|
|
444
461
|
registerBraidCommand(pi, jobs);
|
|
445
462
|
registerReviewCommand(pi, jobs);
|
|
446
463
|
const disposeWidget = registerBraidWidget(pi, jobs);
|
|
@@ -454,7 +471,11 @@ export default function braidExtension(pi) {
|
|
|
454
471
|
jobs.dispose();
|
|
455
472
|
disposeWidget();
|
|
456
473
|
});
|
|
457
|
-
pi.on("before_agent_start", (event) =>
|
|
458
|
-
|
|
459
|
-
|
|
474
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
475
|
+
if (pi.getFlag("braid-blocking") === true)
|
|
476
|
+
warnBlocking(ctx);
|
|
477
|
+
return {
|
|
478
|
+
systemPrompt: `${event.systemPrompt}\n\n## Braid execution policy\n${BRAID_USAGE_GUIDANCE}\n${blocking() ? "Blocking mode is enabled for this session. " + BRAID_BLOCKING_GUIDANCE : BRAID_ASYNC_GUIDANCE}${isSingleShot(ctx) ? `\n${BRAID_DRAIN_GUIDANCE}\n${BRAID_FINISH_GUIDANCE}` : ""}\n\nFor the current user request, make this delegation choice before using read, grep, find, edit, write, or bash. Completion reminders refer to existing jobs: retrieve their results instead of submitting the same graph again.`,
|
|
479
|
+
};
|
|
480
|
+
});
|
|
460
481
|
}
|
package/dist/jobs.js
CHANGED
|
@@ -313,6 +313,23 @@ export class BraidJobs {
|
|
|
313
313
|
}))
|
|
314
314
|
.reverse();
|
|
315
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Jobs that have not reached a terminal status. A job is stalled when only
|
|
318
|
+
* paused executions remain, so it cannot progress without resume or cancel.
|
|
319
|
+
*/
|
|
320
|
+
outstanding() {
|
|
321
|
+
return [...this.jobs.values()].filter(job => job.status === "running").map(job => {
|
|
322
|
+
// Skip the snapshot copy while executions are visibly running.
|
|
323
|
+
const state = job.runningExecutions.size ? undefined : job.run?.snapshot();
|
|
324
|
+
return {
|
|
325
|
+
jobId: job.jobId,
|
|
326
|
+
handle: job.handle,
|
|
327
|
+
stalled: state?.status === "waiting",
|
|
328
|
+
...(state ? { revision: state.revision } : {}),
|
|
329
|
+
pausedExecutionIds: state?.pausedExecutionIds ?? [],
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
}
|
|
316
333
|
/** Editor progress without copying outputs, transcripts, or execution snapshots. */
|
|
317
334
|
progress(jobId) {
|
|
318
335
|
const job = this.lookup(jobId);
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrok/pi-braid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Pi extension for Braid: background agent graphs, bounded loops, live updates, and a flow panel",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"grok-mermaid": "^0.2.2",
|
|
9
|
-
"@chrok/braid": "0.
|
|
9
|
+
"@chrok/braid": "0.4.0"
|
|
10
10
|
},
|
|
11
11
|
"peerDependencies": {
|
|
12
12
|
"@earendil-works/pi-ai": "*",
|