@chrok/pi-braid 0.3.5 → 0.3.6
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 +42 -5
- package/dist/index.js +72 -27
- package/dist/jobs.js +8 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@chrok/pi-braid)
|
|
4
4
|
|
|
5
|
-
This optional Pi extension runs Braid agent graphs as background jobs with
|
|
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
8
|
**0.3 API:** The npm badge shows the published version; see
|
|
@@ -17,7 +17,8 @@ For the previous release, use the
|
|
|
17
17
|
|
|
18
18
|
It registers:
|
|
19
19
|
|
|
20
|
-
- `braid` — submit a graph with optional bounded loops
|
|
20
|
+
- `braid` — submit a graph with optional bounded loops; receive a `jobId`
|
|
21
|
+
immediately in background mode, or final results in experimental blocking mode.
|
|
21
22
|
- `braid_status` — retrieve progress and results with `{ "jobId": "..." }`, or
|
|
22
23
|
add `"executionId": "..."` for an exact invocation's full output/error.
|
|
23
24
|
`"nodeId": "..."` selects that definition's latest invocation. Omit all IDs
|
|
@@ -25,6 +26,8 @@ It registers:
|
|
|
25
26
|
- `braid_update` / `braid_resume` — edit live definitions or release paused executions.
|
|
26
27
|
- `braid_cancel` — cancel a job with `{ "jobId": "..." }`.
|
|
27
28
|
- `/braid [jobId]` — open a live flow panel in interactive Pi.
|
|
29
|
+
- `/braid:blocking [on|off]` — experimental: enable or disable blocking for new `braid` tool calls;
|
|
30
|
+
omit the argument to show the current mode.
|
|
28
31
|
- `/braid:review [target or focus]` — run a bounded parallel review/fix loop.
|
|
29
32
|
- `/braid:clear` — clear the progress widget above the editor; run it again while
|
|
30
33
|
the widget is empty to restore what was cleared.
|
|
@@ -50,6 +53,38 @@ work, or end its turn if it needs the result, and not to sleep, use a timer, or
|
|
|
50
53
|
poll in a loop to wait for the job.
|
|
51
54
|
Stopping the foreground response does not stop background jobs.
|
|
52
55
|
|
|
56
|
+
> **Experimental:** blocking mode is a stopgap for single-shot runs. Its flag,
|
|
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)).
|
|
60
|
+
|
|
61
|
+
For CI or other single-shot runs, enable blocking manually when starting Pi:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
pi --braid-blocking -p "Use Braid to review the changes and summarize the results."
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
With `@chrok/pi-braid` installed, `--braid-blocking` makes every `braid` tool call
|
|
68
|
+
wait for the entire graph and workspace cleanup, then return final status, outputs,
|
|
69
|
+
and usage directly. The parent can summarize those results before `pi -p` exits.
|
|
70
|
+
It also works with `--mode json` for structured tool results. The CLI flag sets the
|
|
71
|
+
initial mode; within a session, use `/braid:blocking on` to enable it,
|
|
72
|
+
`/braid:blocking off` to disable it, or `/braid:blocking` to check the current mode.
|
|
73
|
+
Switching affects only new `braid` tool calls; running jobs keep their original
|
|
74
|
+
mode. The session override is not persisted. These are user controls, not model
|
|
75
|
+
tool parameters; the default remains background execution.
|
|
76
|
+
For CI pass/fail decisions, inspect the returned job status: a failed graph does
|
|
77
|
+
not by itself set Pi's process exit code.
|
|
78
|
+
Blocking calls emit an initial progress update with the job handle while waiting.
|
|
79
|
+
They suppress node/job completion reminders except for cancellation: stopping the
|
|
80
|
+
foreground response cancels their work, waits for cleanup, and sends a reminder
|
|
81
|
+
so the next turn can retrieve partial outputs and recovery checkpoints.
|
|
82
|
+
Wait/result retrieval errors include the existing job handle and any terminal
|
|
83
|
+
usage; retrieve that job with `braid_status` instead of submitting the graph again.
|
|
84
|
+
`pauseAfter: true` is rejected before starting a blocking job because it requires
|
|
85
|
+
a later resume call. The interactive
|
|
86
|
+
`/braid:review` command continues to use its background review workflow.
|
|
87
|
+
|
|
53
88
|
Jobs live in memory for the current Pi session. Quitting, reloading extensions,
|
|
54
89
|
or switching/forking sessions aborts outstanding work and suppresses its
|
|
55
90
|
reminders. Job IDs cannot be retrieved after that lifecycle ends. They are not
|
|
@@ -602,9 +637,11 @@ fails that node cleanly and does not invoke another provider.
|
|
|
602
637
|
|
|
603
638
|
The Pi extension has no node or graph time limit by default. Use `braid_cancel`
|
|
604
639
|
or press `c` in the flow panel to abort running nodes and mark queued nodes
|
|
605
|
-
`cancelled`.
|
|
606
|
-
|
|
607
|
-
|
|
640
|
+
`cancelled`. In the default background mode, Escape stops the foreground response
|
|
641
|
+
or closes the panel without cancelling jobs. In blocking mode, stopping the
|
|
642
|
+
foreground response also cancels its Braid job. To set a node or graph timeout,
|
|
643
|
+
supply milliseconds in the submission tool input under `options`; each omitted
|
|
644
|
+
timeout remains unlimited:
|
|
608
645
|
|
|
609
646
|
```json
|
|
610
647
|
{
|
package/dist/index.js
CHANGED
|
@@ -34,8 +34,8 @@ const toolBudget = (unit) => Type.Optional(Type.Integer({
|
|
|
34
34
|
const commonNodeParameters = {
|
|
35
35
|
id: text("Unique node definition ID; edges refer to this exact ID. This is not an executionId."),
|
|
36
36
|
model: Type.Optional(text("Model override as provider/model-id; omit to use the parent model.")),
|
|
37
|
-
notifyOnCompletion: Type.Optional(Type.Boolean({ description: "
|
|
38
|
-
pauseAfter: Type.Optional(Type.Boolean({ description: "
|
|
37
|
+
notifyOnCompletion: Type.Optional(Type.Boolean({ description: "In background mode, send an execution completion reminder; default false. Does not pause scheduling." })),
|
|
38
|
+
pauseAfter: Type.Optional(Type.Boolean({ description: "Background mode only: hold this execution's outgoing dependencies until braid_resume or an atomic braid_update with resume. Sends a pause reminder." })),
|
|
39
39
|
requireSuccess: Type.Optional(Type.Boolean({ description: "If true, failure aborts the entire job and cancels running siblings. Default false: unconditional successors can recover." })),
|
|
40
40
|
};
|
|
41
41
|
const workspace = Type.Optional(StringEnum(["read-only", "worktree"], {
|
|
@@ -91,22 +91,24 @@ const BRAID_FILESYSTEM_GUIDANCE = "Every execute/decision activation gets a fres
|
|
|
91
91
|
"Do not set workspace on merge/integrate nodes. Checkpoints remain recoverable after cleanup. Optional failed predecessors pass errors and partial work along unconditional edges; requireSuccess=true makes failure abort the job. " +
|
|
92
92
|
"Nodes have local read/ls and Git inspection; writable nodes have write/edit and Pi shell tools for dependencies, builds, and tests, while merge/integrate also have local Git integration tools. Search tools require local rg/fd. Outside Git all filesystem access is read-only; read-only nodes have no shell. Worktrees are not an OS sandbox: prompts constrain shell writes and shared resources. Checkpoints omit ignored new files. Parent extension/MCP tools and recursive Braid calls are not provided. Nodes should verify their changes; the parent reviews results and performs any remaining validation after integration.";
|
|
93
93
|
/** Lifecycle guidance shared by the system prompt and tool descriptions. */
|
|
94
|
-
export const BRAID_ASYNC_GUIDANCE = "Braid is asynchronous: braid returns a jobId immediately and the job keeps running independently of the foreground response; ending your turn does not cancel it. " +
|
|
94
|
+
export const BRAID_ASYNC_GUIDANCE = "By default, Braid is asynchronous: braid returns a jobId immediately and the job keeps running independently of the foreground response; ending your turn does not cancel it. " +
|
|
95
95
|
"Completion, failure, or cancellation sends a reminder: an idle agent is resumed automatically, and a running agent receives it at the next model step. " +
|
|
96
96
|
"After submitting, continue useful independent work. If nothing else can proceed until the result arrives, end your turn and let the reminder resume the task. " +
|
|
97
97
|
"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
|
-
"On the reminder, retrieve
|
|
98
|
+
"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
|
+
"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.";
|
|
99
100
|
/** Short reminder repeated in every submission receipt. */
|
|
100
101
|
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. " +
|
|
101
102
|
"Do not sleep or poll in a loop to wait for this job. Retrieve its results with braid_status after the reminder.";
|
|
103
|
+
const BRAID_BLOCKING_GUIDANCE = "In blocking mode (enabled by --braid-blocking or /braid:blocking on), braid waits for the entire graph and workspace cleanup, then returns final status, outputs, and usage directly. " +
|
|
104
|
+
"Foreground cancellation cancels the submitted job and waits for cleanup. Completion reminders are suppressed unless the job is cancelled; cancellation reminders preserve its handle and access to partial results. " +
|
|
105
|
+
"Do not set pauseAfter=true in blocking mode: it requires a later resume call and is rejected. Use braid_status for focused reads, diagnosis, or retrieving an existing job after cancellation or a wait/result error.";
|
|
102
106
|
const BRAID_USAGE_GUIDANCE = [
|
|
103
107
|
"Braid is a proactive execution primitive, not only a user-requested command.",
|
|
104
108
|
"Selection rule: for a code review, bug investigation, design comparison, test-planning request, or change spanning multiple files, call braid FIRST when two or more concerns can be handled independently. Nodes can analyze the project and implement changes in isolated Git worktrees. Do this without waiting for the user to say Braid; do not read everything in the parent and then decide whether to delegate.",
|
|
105
109
|
BRAID_FILESYSTEM_GUIDANCE,
|
|
106
110
|
"For repeated instructions, define promptTemplates once and use prompt={template: name, variables: {name: value}} on nodes. Values are strings inserted literally into {{name}} placeholders; plain-string prompts remain supported.",
|
|
107
|
-
"When Braid fits, submit a graph: use parallel execute nodes for independent analysis or implementation, execute nodes to synthesize findings, merge nodes to combine code snapshots, and integrate nodes to apply changes to the working branch.
|
|
108
|
-
BRAID_ASYNC_GUIDANCE,
|
|
109
|
-
"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.",
|
|
111
|
+
"When Braid fits, submit a graph: use parallel execute nodes for independent analysis or implementation, execute nodes to synthesize findings, merge nodes to combine code snapshots, and integrate nodes to apply changes to the working branch. Read terminal outputs before relying on them.",
|
|
110
112
|
"Make the delegation choice once per user request. Reminders and definition errors are not new tasks. If the same definition problem recurs, stop resubmitting and report the concrete mismatch; do not launch repeated probe/replacement jobs. In braid_update, add new nodes together with their dependencies and loops: a node added without incoming edges can start immediately, even while another execution is paused.",
|
|
111
113
|
"Do not use braid for a simple one-step answer, a trivial direct edit, a single shell command, or when decomposition adds no value. Writable nodes can implement and test their work. The parent reviews results and performs any remaining validation after Braid completes.",
|
|
112
114
|
].join("\n");
|
|
@@ -131,12 +133,12 @@ async function boundedText(text, name) {
|
|
|
131
133
|
await writeFile(path, text, { mode: 0o600 });
|
|
132
134
|
return { text: `${preview.content}\n[Truncated. Full text: ${path}]`, path };
|
|
133
135
|
}
|
|
134
|
-
export function createBraidTools(jobs) {
|
|
136
|
+
export function createBraidTools(jobs, blocking = () => false) {
|
|
135
137
|
const braidTool = defineTool({
|
|
136
138
|
name: "braid",
|
|
137
139
|
label: "Braid",
|
|
138
140
|
description: "Use this tool FIRST for nontrivial engineering work: code reviews, bug investigations, design comparisons, test planning, and changes spanning multiple files. " +
|
|
139
|
-
"
|
|
141
|
+
"Run a mutable graph of isolated LLM invocations with parallel branches and joins; the user does not need to mention Braid. " +
|
|
140
142
|
"Use execute, decision, merge, or integrate nodes. Execute/decision nodes require prompt. Only decision nodes declare choices and call decide; matching choice edges activate together. Merge/integrate nodes accept multiple predecessors and an optional prompt, with no workspace or choices field. " +
|
|
141
143
|
"Structured loops declare id, entry, maxIterations, and one back edge from a decision to entry with both choice and feedback=loopId. The decision also needs an exit choice. External edges enter only at entry and leave only through that decision; the body must be acyclic and loops cannot overlap or nest. Each round uses new executions and worktrees. " +
|
|
142
144
|
'Example retry loop: nodes work (execute with prompt) and check (decision with prompt and choices ["retry","done"]); edges [{"from":"work","to":"check"},{"from":"check","to":"work","choice":"retry","feedback":"retryLoop"}]; loops [{"id":"retryLoop","entry":"work","maxIterations":3}]. An optional done edge leaves check to a downstream node. ' +
|
|
@@ -147,8 +149,7 @@ export function createBraidTools(jobs) {
|
|
|
147
149
|
BRAID_FILESYSTEM_GUIDANCE + " " +
|
|
148
150
|
"Do not use it for a simple one-step answer or trivial direct edit. " +
|
|
149
151
|
"Use braid_cancel(jobId) to stop it. Humans can open /braid for the live flow panel. " +
|
|
150
|
-
|
|
151
|
-
"Set notifyOnCompletion=true on selected nodes for intermediate success/failure reminders; retrieve their output/error with braid_status({jobId, nodeId}). Notifications do not pause scheduling; use pauseAfter for a hold, and braid_update to edit definitions. " +
|
|
152
|
+
"Follow the current session's Braid execution policy in the system prompt for waiting, result retrieval, and pause/notification controls. " +
|
|
152
153
|
"Read result.status: failed graphs can still return successful terminal outputs.",
|
|
153
154
|
promptSnippet: "Use FIRST for nontrivial code review/debug/design/implementation work; set workspace=read-only for analysis/synthesis, use worktrees for edits, merge to combine snapshots, and integrate to apply changes",
|
|
154
155
|
promptGuidelines: [
|
|
@@ -173,30 +174,56 @@ export function createBraidTools(jobs) {
|
|
|
173
174
|
const pending = options.isPartial && !ctx.isError;
|
|
174
175
|
return new Text(theme.fg(pending ? "warning" : "error", pending ? fallback || "Braid submission pending…" : `✗ ${fallback || "Braid submission failed without error details"}`), 0, 0);
|
|
175
176
|
}
|
|
176
|
-
|
|
177
|
+
if (job.status !== "running")
|
|
178
|
+
return statusTool.renderResult(result, options, theme, { ...ctx, args: { jobId: job.jobId } });
|
|
179
|
+
return new Text(theme.fg("accent", `Braid ${options.isPartial ? "job" : "background job"} ${job.handle ?? job.jobId} · ${job.status} · /braid to view${job.error ? `\n${job.error}` : ""}`), 0, 0);
|
|
177
180
|
},
|
|
178
|
-
async execute(_toolCallId, params, signal,
|
|
181
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
179
182
|
signal?.throwIfAborted();
|
|
183
|
+
const waitForCompletion = blocking();
|
|
184
|
+
let job;
|
|
180
185
|
try {
|
|
181
|
-
|
|
186
|
+
job = jobs.start({
|
|
182
187
|
goal: params.goal,
|
|
183
188
|
nodes: params.nodes,
|
|
184
189
|
edges: params.edges,
|
|
185
190
|
...(params.loops !== undefined ? { loops: params.loops } : {}),
|
|
186
191
|
...(params.promptTemplates !== undefined ? { promptTemplates: params.promptTemplates } : {}),
|
|
187
|
-
}, params.options ?? {}, ctx);
|
|
192
|
+
}, params.options ?? {}, ctx, waitForCompletion);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
throw new Error(`Braid submission failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
196
|
+
}
|
|
197
|
+
if (!waitForCompletion)
|
|
188
198
|
return {
|
|
189
|
-
content: [
|
|
190
|
-
{
|
|
191
|
-
type: "text",
|
|
192
|
-
text: formatReceipt(job, `${BRAID_WAIT_REMINDER} Humans can follow it live in /braid.`),
|
|
193
|
-
},
|
|
194
|
-
],
|
|
199
|
+
content: [{ type: "text", text: formatReceipt(job, `${BRAID_WAIT_REMINDER} Humans can follow it live in /braid.`) }],
|
|
195
200
|
details: job,
|
|
196
201
|
};
|
|
202
|
+
const cancel = () => { jobs.cancel(job.jobId); };
|
|
203
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
204
|
+
try {
|
|
205
|
+
try {
|
|
206
|
+
onUpdate?.({
|
|
207
|
+
content: [{ type: "text", text: formatReceipt(job, "Waiting for the graph and workspace cleanup. Humans can follow it live in /braid.") }],
|
|
208
|
+
details: job,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
catch { /* Progress observers must not stop the foreground wait. */ }
|
|
212
|
+
await jobs.wait(job.jobId);
|
|
213
|
+
return await statusTool.execute(_toolCallId, { jobId: job.jobId }, signal, undefined, ctx);
|
|
197
214
|
}
|
|
198
215
|
catch (error) {
|
|
199
|
-
|
|
216
|
+
const current = jobs.get(job.jobId);
|
|
217
|
+
const usage = current.status === "running" || signal?.aborted ? undefined : jobs.claimUsage(job.jobId);
|
|
218
|
+
return {
|
|
219
|
+
content: [{ type: "text", text: `Braid job ${job.handle} was started and is ${current.status}. Could not wait for or retrieve its results: ${error instanceof Error ? error.message : String(error)}. Do not resubmit this graph: it may already have changed the checkout. Retrieve the existing job with braid_status(${JSON.stringify({ jobId: job.handle })}).` }],
|
|
220
|
+
details: current,
|
|
221
|
+
isError: true,
|
|
222
|
+
...(usage ? { usage } : {}),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
signal?.removeEventListener("abort", cancel);
|
|
200
227
|
}
|
|
201
228
|
},
|
|
202
229
|
});
|
|
@@ -209,7 +236,7 @@ export function createBraidTools(jobs) {
|
|
|
209
236
|
const statusTool = defineTool({
|
|
210
237
|
name: "braid_status",
|
|
211
238
|
label: "Braid status",
|
|
212
|
-
description: "Retrieve a
|
|
239
|
+
description: "Retrieve a Braid job's status, node progress, and final results by exact session handle (e.g. job-1) or UUID in jobId. Add executionId for an exact execution, or nodeId for the latest instance, including intermediate results while the job runs. Focused reads include live activity: phase, time in phase and since the last observed signal, model request/stream state, running tools, and recent history. Lack of observed activity is a diagnostic signal, not proof of a stall. Every job read starts with the current graph revision and paused execution IDs for live control. Results are text; long ones are truncated and the full text is saved to a file whose path is shown. Usage is Pi's provider accounting, including rounds from failed nodes. Prefer the short handle from submission/reminders. Omit all IDs to list jobs in this session. Follow the current session's Braid execution policy for result retrieval; do not sleep, use a timer, or poll in a loop to wait for a job.",
|
|
213
240
|
parameters: statusParameters,
|
|
214
241
|
renderResult(result, options, theme, ctx) {
|
|
215
242
|
const job = result.details;
|
|
@@ -242,7 +269,7 @@ export function createBraidTools(jobs) {
|
|
|
242
269
|
...(job.fullOutputPath ? { fullOutputPath: job.fullOutputPath } : {}),
|
|
243
270
|
}, options.expanded, options.isPartial, theme, fallback);
|
|
244
271
|
},
|
|
245
|
-
async execute(_id, params) {
|
|
272
|
+
async execute(_id, params, signal) {
|
|
246
273
|
if ((params.nodeId !== undefined || params.executionId !== undefined) && !params.jobId)
|
|
247
274
|
throw new Error("nodeId/executionId requires jobId");
|
|
248
275
|
if (params.activityBefore !== undefined && params.nodeId === undefined && params.executionId === undefined)
|
|
@@ -270,7 +297,7 @@ export function createBraidTools(jobs) {
|
|
|
270
297
|
const details = result.path ? { ...job, fullOutputPath: result.path } : job;
|
|
271
298
|
// Saving a running snapshot can race job completion; claim usage only
|
|
272
299
|
// when this response actually contains a terminal snapshot.
|
|
273
|
-
const usage = job.status === "running" ? undefined : jobs.claimUsage(job.jobId);
|
|
300
|
+
const usage = job.status === "running" || signal?.aborted ? undefined : jobs.claimUsage(job.jobId);
|
|
274
301
|
return {
|
|
275
302
|
content: [{ type: "text", text: result.text }],
|
|
276
303
|
details,
|
|
@@ -343,6 +370,24 @@ export function createBraidTools(jobs) {
|
|
|
343
370
|
return { braidTool, statusTool, cancelTool, updateTool, resumeTool };
|
|
344
371
|
}
|
|
345
372
|
export default function braidExtension(pi) {
|
|
373
|
+
pi.registerFlag("braid-blocking", {
|
|
374
|
+
description: "Experimental: wait for braid tool calls to finish in the foreground (for CI/print mode)",
|
|
375
|
+
type: "boolean",
|
|
376
|
+
default: false,
|
|
377
|
+
});
|
|
378
|
+
let sessionBlocking;
|
|
379
|
+
const blocking = () => sessionBlocking ?? (pi.getFlag("braid-blocking") === true);
|
|
380
|
+
pi.registerCommand("braid:blocking", {
|
|
381
|
+
description: "Experimental: set blocking mode for new braid tool calls: /braid:blocking [on|off]; omit to show the current mode",
|
|
382
|
+
handler: async (args, ctx) => {
|
|
383
|
+
const setting = args.trim().toLowerCase();
|
|
384
|
+
if (setting === "on" || setting === "off")
|
|
385
|
+
sessionBlocking = setting === "on";
|
|
386
|
+
else if (setting)
|
|
387
|
+
throw new Error("Usage: /braid:blocking [on|off]");
|
|
388
|
+
ctx.ui.notify(`Braid blocking mode: ${blocking() ? "on" : "off"}. Applies to new braid tool calls.`, "info");
|
|
389
|
+
},
|
|
390
|
+
});
|
|
346
391
|
const pending = new Map();
|
|
347
392
|
const remind = (message) => {
|
|
348
393
|
try {
|
|
@@ -395,7 +440,7 @@ export default function braidExtension(pi) {
|
|
|
395
440
|
remind(message);
|
|
396
441
|
}
|
|
397
442
|
});
|
|
398
|
-
const { braidTool, statusTool, cancelTool, updateTool, resumeTool } = createBraidTools(jobs);
|
|
443
|
+
const { braidTool, statusTool, cancelTool, updateTool, resumeTool } = createBraidTools(jobs, blocking);
|
|
399
444
|
registerBraidCommand(pi, jobs);
|
|
400
445
|
registerReviewCommand(pi, jobs);
|
|
401
446
|
const disposeWidget = registerBraidWidget(pi, jobs);
|
|
@@ -410,6 +455,6 @@ export default function braidExtension(pi) {
|
|
|
410
455
|
disposeWidget();
|
|
411
456
|
});
|
|
412
457
|
pi.on("before_agent_start", (event) => ({
|
|
413
|
-
systemPrompt: `${event.systemPrompt}\n\n## Braid execution policy\n${BRAID_USAGE_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.`,
|
|
458
|
+
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}\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.`,
|
|
414
459
|
}));
|
|
415
460
|
}
|
package/dist/jobs.js
CHANGED
|
@@ -36,10 +36,12 @@ export class BraidJobs {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
start(input, options, ctx) {
|
|
39
|
+
start(input, options, ctx, blocking = false) {
|
|
40
40
|
if (this.disposed)
|
|
41
41
|
throw new Error("Braid session has closed");
|
|
42
42
|
validateGraph(input);
|
|
43
|
+
if (blocking && input.nodes.some(node => node.pauseAfter))
|
|
44
|
+
throw new Error("Blocking Braid jobs cannot use pauseAfter; remove it or disable blocking with /braid:blocking off.");
|
|
43
45
|
const snapshot = structuredClone(input);
|
|
44
46
|
const settings = { ...options };
|
|
45
47
|
const model = ctx.model
|
|
@@ -49,6 +51,7 @@ export class BraidJobs {
|
|
|
49
51
|
const cwd = ctx.cwd;
|
|
50
52
|
this.cwd = cwd;
|
|
51
53
|
const job = {
|
|
54
|
+
blocking,
|
|
52
55
|
handle: `job-${this.nextHandle++}`,
|
|
53
56
|
jobId: crypto.randomUUID(),
|
|
54
57
|
goal: snapshot.goal,
|
|
@@ -73,7 +76,7 @@ export class BraidJobs {
|
|
|
73
76
|
async run(job, input, options, registry, cwd, model) {
|
|
74
77
|
const reports = [];
|
|
75
78
|
try {
|
|
76
|
-
//
|
|
79
|
+
// Defer provider work until the job handle has been published.
|
|
77
80
|
job.run = startBraid(input, {
|
|
78
81
|
...options,
|
|
79
82
|
cwd,
|
|
@@ -150,7 +153,7 @@ export class BraidJobs {
|
|
|
150
153
|
const shouldNotify = event.type === "execution_paused" ||
|
|
151
154
|
((event.type === "node_completed" || event.type === "node_failed") && instance?.node.notifyOnCompletion &&
|
|
152
155
|
(!instance.node.pauseAfter || state?.error));
|
|
153
|
-
if (!this.disposed && shouldNotify && "nodeId" in event && instance) {
|
|
156
|
+
if (!this.disposed && !job.blocking && shouldNotify && "nodeId" in event && instance) {
|
|
154
157
|
try {
|
|
155
158
|
void Promise.resolve(this.onNodeFinished({
|
|
156
159
|
jobId: job.jobId,
|
|
@@ -190,7 +193,7 @@ export class BraidJobs {
|
|
|
190
193
|
job.usage = sumPiUsage(reports);
|
|
191
194
|
}
|
|
192
195
|
this.changed();
|
|
193
|
-
if (!this.disposed) {
|
|
196
|
+
if (!this.disposed && (!job.blocking || job.status === "cancelled")) {
|
|
194
197
|
try {
|
|
195
198
|
this.onFinished(this.get(job.jobId));
|
|
196
199
|
}
|
|
@@ -210,7 +213,7 @@ export class BraidJobs {
|
|
|
210
213
|
const job = this.lookup(jobId);
|
|
211
214
|
if (!job)
|
|
212
215
|
return undefined;
|
|
213
|
-
const { run: _run, controller: _controller, done: _done, usageClaimed: _claimed, nodeResults: _nodeResults, runningExecutions: _runningExecutions, activity: _activity, transcripts: _transcripts, humanInputs: _humanInputs, ...snapshot } = job;
|
|
216
|
+
const { blocking: _blocking, run: _run, controller: _controller, done: _done, usageClaimed: _claimed, nodeResults: _nodeResults, runningExecutions: _runningExecutions, activity: _activity, transcripts: _transcripts, humanInputs: _humanInputs, ...snapshot } = job;
|
|
214
217
|
const copy = structuredClone(snapshot);
|
|
215
218
|
if (job.run)
|
|
216
219
|
copy.execution = job.run.snapshot();
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrok/pi-braid",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
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.3.
|
|
9
|
+
"@chrok/braid": "0.3.6"
|
|
10
10
|
},
|
|
11
11
|
"peerDependencies": {
|
|
12
12
|
"@earendil-works/pi-ai": "*",
|