@bridge4dev/runner 0.36.0 → 0.37.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/dist/index.js +5 -0
- package/dist/protocol.d.ts +74 -2
- package/dist/protocol.js +26 -12
- package/dist/supervisor.js +50 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -389,6 +389,11 @@ function runnerCapabilities(apiUrlOverride) {
|
|
|
389
389
|
'agent_prompt_state',
|
|
390
390
|
'propose_commit_message',
|
|
391
391
|
'recall_message',
|
|
392
|
+
// The answer to a parked question, as a command that ANSWERS rather than
|
|
393
|
+
// a frame written into a socket and hoped for. Announced here because
|
|
394
|
+
// this list is what `runCommand` actually dispatches on, and the API
|
|
395
|
+
// falls back to the old frame for runners that do not name it.
|
|
396
|
+
'answer_question',
|
|
392
397
|
'compact_context',
|
|
393
398
|
...(checkpointsEnabled
|
|
394
399
|
? [
|
package/dist/protocol.d.ts
CHANGED
|
@@ -272,6 +272,78 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
|
|
|
272
272
|
workMode?: unknown;
|
|
273
273
|
}>;
|
|
274
274
|
export type SessionDescriptor = z.infer<typeof SessionDescriptorSchema>;
|
|
275
|
+
/**
|
|
276
|
+
* One human answer to a parked agent question, minus the session it belongs to.
|
|
277
|
+
*
|
|
278
|
+
* Written once and used twice on purpose: as the body of the `question_answer`
|
|
279
|
+
* frame every runner since 0.14 understands, and as the arguments of the
|
|
280
|
+
* `answer_question` COMMAND that replaced it (session 18). The two must not
|
|
281
|
+
* drift — an answer that the frame accepts and the command rejects would fail
|
|
282
|
+
* exactly on the machines whose connection is bad enough to need the command.
|
|
283
|
+
*/
|
|
284
|
+
export declare const QuestionAnswerShape: {
|
|
285
|
+
readonly askId: z.ZodString;
|
|
286
|
+
readonly action: z.ZodEnum<["answer", "discuss"]>;
|
|
287
|
+
readonly answers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
288
|
+
questionId: z.ZodString;
|
|
289
|
+
values: z.ZodArray<z.ZodString, "many">;
|
|
290
|
+
custom: z.ZodOptional<z.ZodString>;
|
|
291
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
292
|
+
}, "strip", z.ZodTypeAny, {
|
|
293
|
+
values: string[];
|
|
294
|
+
questionId: string;
|
|
295
|
+
custom?: string | undefined;
|
|
296
|
+
notes?: string | undefined;
|
|
297
|
+
}, {
|
|
298
|
+
values: string[];
|
|
299
|
+
questionId: string;
|
|
300
|
+
custom?: string | undefined;
|
|
301
|
+
notes?: string | undefined;
|
|
302
|
+
}>, "many">>;
|
|
303
|
+
readonly text: z.ZodOptional<z.ZodString>;
|
|
304
|
+
};
|
|
305
|
+
/** The `answer_question` command's arguments — the shape above on its own. */
|
|
306
|
+
export declare const QuestionAnswerArgsSchema: z.ZodObject<{
|
|
307
|
+
readonly askId: z.ZodString;
|
|
308
|
+
readonly action: z.ZodEnum<["answer", "discuss"]>;
|
|
309
|
+
readonly answers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
310
|
+
questionId: z.ZodString;
|
|
311
|
+
values: z.ZodArray<z.ZodString, "many">;
|
|
312
|
+
custom: z.ZodOptional<z.ZodString>;
|
|
313
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
314
|
+
}, "strip", z.ZodTypeAny, {
|
|
315
|
+
values: string[];
|
|
316
|
+
questionId: string;
|
|
317
|
+
custom?: string | undefined;
|
|
318
|
+
notes?: string | undefined;
|
|
319
|
+
}, {
|
|
320
|
+
values: string[];
|
|
321
|
+
questionId: string;
|
|
322
|
+
custom?: string | undefined;
|
|
323
|
+
notes?: string | undefined;
|
|
324
|
+
}>, "many">>;
|
|
325
|
+
readonly text: z.ZodOptional<z.ZodString>;
|
|
326
|
+
}, "strip", z.ZodTypeAny, {
|
|
327
|
+
askId: string;
|
|
328
|
+
action: "answer" | "discuss";
|
|
329
|
+
text?: string | undefined;
|
|
330
|
+
answers?: {
|
|
331
|
+
values: string[];
|
|
332
|
+
questionId: string;
|
|
333
|
+
custom?: string | undefined;
|
|
334
|
+
notes?: string | undefined;
|
|
335
|
+
}[] | undefined;
|
|
336
|
+
}, {
|
|
337
|
+
askId: string;
|
|
338
|
+
action: "answer" | "discuss";
|
|
339
|
+
text?: string | undefined;
|
|
340
|
+
answers?: {
|
|
341
|
+
values: string[];
|
|
342
|
+
questionId: string;
|
|
343
|
+
custom?: string | undefined;
|
|
344
|
+
notes?: string | undefined;
|
|
345
|
+
}[] | undefined;
|
|
346
|
+
}>;
|
|
275
347
|
export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
276
348
|
type: z.ZodLiteral<"hello_ack">;
|
|
277
349
|
serverId: z.ZodString;
|
|
@@ -1104,8 +1176,6 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
|
|
|
1104
1176
|
requestId: string;
|
|
1105
1177
|
note?: string | undefined;
|
|
1106
1178
|
}>, z.ZodObject<{
|
|
1107
|
-
type: z.ZodLiteral<"question_answer">;
|
|
1108
|
-
sessionId: z.ZodString;
|
|
1109
1179
|
askId: z.ZodString;
|
|
1110
1180
|
action: z.ZodEnum<["answer", "discuss"]>;
|
|
1111
1181
|
answers: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -1125,6 +1195,8 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
|
|
|
1125
1195
|
notes?: string | undefined;
|
|
1126
1196
|
}>, "many">>;
|
|
1127
1197
|
text: z.ZodOptional<z.ZodString>;
|
|
1198
|
+
type: z.ZodLiteral<"question_answer">;
|
|
1199
|
+
sessionId: z.ZodString;
|
|
1128
1200
|
}, "strip", z.ZodTypeAny, {
|
|
1129
1201
|
sessionId: string;
|
|
1130
1202
|
type: "question_answer";
|
package/dist/protocol.js
CHANGED
|
@@ -183,6 +183,31 @@ export const SessionDescriptorSchema = z.object({
|
|
|
183
183
|
// project). Preferred over the [mcp] section of config.toml when present.
|
|
184
184
|
mcp: z.object({ url: z.string().url(), token: z.string().min(1) }).optional(),
|
|
185
185
|
});
|
|
186
|
+
/**
|
|
187
|
+
* One human answer to a parked agent question, minus the session it belongs to.
|
|
188
|
+
*
|
|
189
|
+
* Written once and used twice on purpose: as the body of the `question_answer`
|
|
190
|
+
* frame every runner since 0.14 understands, and as the arguments of the
|
|
191
|
+
* `answer_question` COMMAND that replaced it (session 18). The two must not
|
|
192
|
+
* drift — an answer that the frame accepts and the command rejects would fail
|
|
193
|
+
* exactly on the machines whose connection is bad enough to need the command.
|
|
194
|
+
*/
|
|
195
|
+
export const QuestionAnswerShape = {
|
|
196
|
+
askId: z.string().min(1).max(64),
|
|
197
|
+
action: z.enum(['answer', 'discuss']),
|
|
198
|
+
answers: z
|
|
199
|
+
.array(z.object({
|
|
200
|
+
questionId: z.string().min(1).max(120),
|
|
201
|
+
values: z.array(z.string().max(2_000)).max(16),
|
|
202
|
+
custom: z.string().max(10_000).optional(),
|
|
203
|
+
notes: z.string().max(2_000).optional(),
|
|
204
|
+
}))
|
|
205
|
+
.max(4)
|
|
206
|
+
.optional(),
|
|
207
|
+
text: z.string().max(20_000).optional(),
|
|
208
|
+
};
|
|
209
|
+
/** The `answer_question` command's arguments — the shape above on its own. */
|
|
210
|
+
export const QuestionAnswerArgsSchema = z.object(QuestionAnswerShape);
|
|
186
211
|
export const GatewayFrameSchema = z.discriminatedUnion('type', [
|
|
187
212
|
z.object({
|
|
188
213
|
type: z.literal('hello_ack'),
|
|
@@ -238,18 +263,7 @@ export const GatewayFrameSchema = z.discriminatedUnion('type', [
|
|
|
238
263
|
z.object({
|
|
239
264
|
type: z.literal('question_answer'),
|
|
240
265
|
sessionId: z.string().uuid(),
|
|
241
|
-
|
|
242
|
-
action: z.enum(['answer', 'discuss']),
|
|
243
|
-
answers: z
|
|
244
|
-
.array(z.object({
|
|
245
|
-
questionId: z.string().min(1).max(120),
|
|
246
|
-
values: z.array(z.string().max(2_000)).max(16),
|
|
247
|
-
custom: z.string().max(10_000).optional(),
|
|
248
|
-
notes: z.string().max(2_000).optional(),
|
|
249
|
-
}))
|
|
250
|
-
.max(4)
|
|
251
|
-
.optional(),
|
|
252
|
-
text: z.string().max(20_000).optional(),
|
|
266
|
+
...QuestionAnswerShape,
|
|
253
267
|
}),
|
|
254
268
|
z.object({ type: z.literal('session_stop'), sessionId: z.string().uuid() }),
|
|
255
269
|
z.object({ type: z.literal('session_interrupt'), sessionId: z.string().uuid() }),
|
package/dist/supervisor.js
CHANGED
|
@@ -18,6 +18,7 @@ import { selfUpdate } from './self-update.js';
|
|
|
18
18
|
import { rememberWorkspacePath } from './environment.js';
|
|
19
19
|
import { composeMessageWithAttachments, saveAttachments, } from './attachments.js';
|
|
20
20
|
import { applyRewind, createCheckpoint, dropCheckpoints, listCheckpoints, previewRewind, pruneCheckpoints, } from './checkpoints.js';
|
|
21
|
+
import { QuestionAnswerArgsSchema } from './protocol.js';
|
|
21
22
|
import { availableModes, MODE_REFUSED_TEXT } from './adapters/types.js';
|
|
22
23
|
/** Refusals shared by every checkpoint command (ticket #126). */
|
|
23
24
|
const CHECKPOINTS_OFF = 'Restore points are switched off on this server ([checkpoints] enabled = false)';
|
|
@@ -309,6 +310,7 @@ export class Supervisor {
|
|
|
309
310
|
extraBudgetMinutes: descriptor.extraBudgetMinutes,
|
|
310
311
|
epoch: descriptor.epoch,
|
|
311
312
|
openQuestions: new Set(),
|
|
313
|
+
answeredAsks: new Set(),
|
|
312
314
|
// Ticket #196: a pause is part of what a session IS, so it is read off
|
|
313
315
|
// the descriptor rather than waiting for a frame. Without this a runner
|
|
314
316
|
// that restarted mid-pause would come back knowing nothing and pick the
|
|
@@ -1471,8 +1473,13 @@ export class Supervisor {
|
|
|
1471
1473
|
if (!running) {
|
|
1472
1474
|
log.warn('supervisor: question answer for unknown session', { sessionId: frame.sessionId });
|
|
1473
1475
|
this.ws.send({ type: 'session_unknown', sessionId: frame.sessionId });
|
|
1474
|
-
return;
|
|
1476
|
+
return 'unknown_session';
|
|
1475
1477
|
}
|
|
1478
|
+
// A redelivery of an answer this runner already took. Silence here is the
|
|
1479
|
+
// point: the first copy did everything, and the second must not add a note,
|
|
1480
|
+
// a message or a status report — see `answeredAsks`.
|
|
1481
|
+
if (running.answeredAsks.has(frame.askId))
|
|
1482
|
+
return 'duplicate';
|
|
1476
1483
|
const typed = frame.text?.trim();
|
|
1477
1484
|
// Asked first, echoed second: the adapter resolves synchronously but its
|
|
1478
1485
|
// own events reach the feed a microtask later, so the user's words still
|
|
@@ -1485,12 +1492,17 @@ export class Supervisor {
|
|
|
1485
1492
|
...(frame.text !== undefined ? { text: frame.text } : {}),
|
|
1486
1493
|
});
|
|
1487
1494
|
if (accepted) {
|
|
1495
|
+
running.answeredAsks.add(frame.askId);
|
|
1488
1496
|
// What the user typed belongs in the conversation, whichever exit they took.
|
|
1489
1497
|
if (typed)
|
|
1490
1498
|
this.sendEvent(running, 'message', { role: 'user', text: typed });
|
|
1491
|
-
return;
|
|
1499
|
+
return 'answered';
|
|
1492
1500
|
}
|
|
1493
1501
|
running.openQuestions.delete(frame.askId);
|
|
1502
|
+
// Remembered even though it was a miss: whatever happened to the ask, the
|
|
1503
|
+
// words below have now been published once, and a redelivery must not
|
|
1504
|
+
// publish them again.
|
|
1505
|
+
running.answeredAsks.add(frame.askId);
|
|
1494
1506
|
this.sendEvent(running, 'system_note', {
|
|
1495
1507
|
text: typed
|
|
1496
1508
|
? 'That question is no longer open — sending your reply as an ordinary message instead.'
|
|
@@ -1505,11 +1517,12 @@ export class Supervisor {
|
|
|
1505
1517
|
sessionId: frame.sessionId,
|
|
1506
1518
|
error: String(error),
|
|
1507
1519
|
}));
|
|
1508
|
-
return;
|
|
1520
|
+
return 'not_open';
|
|
1509
1521
|
}
|
|
1510
1522
|
// Nothing to deliver — but the API optimistically flipped the session to
|
|
1511
1523
|
// RUNNING when it relayed the answer, so put the real status back.
|
|
1512
1524
|
this.reportStatus(frame.sessionId, statusForReport(running), {});
|
|
1525
|
+
return 'not_open';
|
|
1513
1526
|
}
|
|
1514
1527
|
async onUserMessage(sessionId, text, attachments) {
|
|
1515
1528
|
const running = this.sessions.get(sessionId);
|
|
@@ -2258,6 +2271,7 @@ export class Supervisor {
|
|
|
2258
2271
|
extraBudgetMinutes: descriptor.extraBudgetMinutes,
|
|
2259
2272
|
epoch: descriptor.epoch,
|
|
2260
2273
|
openQuestions: new Set(),
|
|
2274
|
+
answeredAsks: new Set(),
|
|
2261
2275
|
...pausedUntilOf(descriptor),
|
|
2262
2276
|
mode: descriptor.mode,
|
|
2263
2277
|
...(descriptor.model ? { model: descriptor.model } : {}),
|
|
@@ -2457,6 +2471,39 @@ export class Supervisor {
|
|
|
2457
2471
|
ok: false,
|
|
2458
2472
|
error: 'reset_workspace is not supported by this runner version',
|
|
2459
2473
|
});
|
|
2474
|
+
case 'answer_question': {
|
|
2475
|
+
// The same work as the `question_answer` frame, with one difference
|
|
2476
|
+
// that is the whole point: this one ANSWERS. The frame was
|
|
2477
|
+
// fire-and-forget, so the API called a write into a socket a delivery
|
|
2478
|
+
// — and a socket reports OPEN for up to three missed pongs after the
|
|
2479
|
+
// machine behind it has gone. An answer lost in that window took the
|
|
2480
|
+
// card with it for a day (2026-08-11).
|
|
2481
|
+
//
|
|
2482
|
+
// No `await` before the reply, for the same reason as `recall_message`
|
|
2483
|
+
// below: `onQuestionAnswer` is synchronous, and a yield here would let
|
|
2484
|
+
// a concurrent frame close the ask between the check and the answer.
|
|
2485
|
+
const sessionId = frame.sessionId;
|
|
2486
|
+
if (!sessionId)
|
|
2487
|
+
return void reply({ ok: false, error: 'sessionId is required' });
|
|
2488
|
+
const parsed = QuestionAnswerArgsSchema.safeParse(frame.args ?? {});
|
|
2489
|
+
if (!parsed.success) {
|
|
2490
|
+
return void reply({
|
|
2491
|
+
ok: false,
|
|
2492
|
+
error: parsed.error.issues[0]?.message ?? 'malformed answer',
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
const outcome = this.onQuestionAnswer({
|
|
2496
|
+
type: 'question_answer',
|
|
2497
|
+
sessionId,
|
|
2498
|
+
...parsed.data,
|
|
2499
|
+
});
|
|
2500
|
+
// `ok` is about the RELAY, not about the agent's luck: an answer for a
|
|
2501
|
+
// session this runner no longer holds is the one case the API can fix
|
|
2502
|
+
// by trying again later, so it is the one case reported as a failure.
|
|
2503
|
+
return void reply(outcome === 'unknown_session'
|
|
2504
|
+
? { ok: false, error: 'Unknown session', result: { outcome } }
|
|
2505
|
+
: { ok: true, result: { outcome } });
|
|
2506
|
+
}
|
|
2460
2507
|
case 'recall_message': {
|
|
2461
2508
|
// Ticket #125: take a queued message back before any agent sees it.
|
|
2462
2509
|
const sessionId = frame.sessionId;
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const RUNNER_VERSION = "0.
|
|
1
|
+
export declare const RUNNER_VERSION = "0.37.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED