@bridge4dev/runner 0.69.1 → 0.70.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 CHANGED
@@ -397,6 +397,17 @@ function runnerCapabilities(apiUrlOverride) {
397
397
  * machine in the fleet is up to date.
398
398
  */
399
399
  agentOutsideFolder: true,
400
+ /**
401
+ * «Tell the agent who is writing»: the name the API attaches to a message
402
+ * (`author` on `deliver_message` and `session_message`, `promptAuthor` on
403
+ * the descriptor) goes on top of the agent's prompt, never into the feed.
404
+ *
405
+ * Announced for the settings card only, and as a warning rather than a
406
+ * refusal, like `agentOutsideFolder` above it: an older runner drops the
407
+ * field unread and the message still arrives — just unsigned — so a
408
+ * manager can record the decision before the last machine is current.
409
+ */
410
+ messageAuthors: true,
400
411
  /**
401
412
  * #137: this runner can read a repository that has no commits yet.
402
413
  *
package/dist/journal.d.ts CHANGED
@@ -30,6 +30,13 @@ export interface PendingMessage {
30
30
  * those cannot be recalled, which is correct — they predate the feature.
31
31
  */
32
32
  seq?: number;
33
+ /**
34
+ * Who typed it, as DevBridge shows them — present only when the project has
35
+ * «Tell the agent who is writing» switched on. Held with the words so a
36
+ * message that waited out a restart is still signed for the agent when it
37
+ * finally goes (`message-author.ts`).
38
+ */
39
+ author?: string;
33
40
  }
34
41
  export interface PendingAttachment {
35
42
  id: string;
@@ -140,7 +147,7 @@ export declare class SessionJournal {
140
147
  * it is queued in memory, so the ordering is "on disk, then held" — a crash
141
148
  * between the two costs a duplicate delivery at worst, never a lost message.
142
149
  */
143
- appendPending(text: string, attachments?: PendingAttachment[], seq?: number): PendingMessage;
150
+ appendPending(text: string, attachments?: PendingAttachment[], seq?: number, author?: string): PendingMessage;
144
151
  /** The message reached an agent — stop replaying it after a restart. */
145
152
  resolvePending(id: string): void;
146
153
  /**
package/dist/journal.js CHANGED
@@ -97,6 +97,7 @@ export class SessionJournal {
97
97
  text: parsed.text,
98
98
  ...(parsed.attachments?.length ? { attachments: parsed.attachments } : {}),
99
99
  ...(typeof parsed.seq === 'number' ? { seq: parsed.seq } : {}),
100
+ ...(typeof parsed.author === 'string' ? { author: parsed.author } : {}),
100
101
  });
101
102
  // Ids are `p<n>`; keep the counter above whatever the file holds so a
102
103
  // restarted runner cannot mint an id that is already in flight.
@@ -157,6 +158,7 @@ export class SessionJournal {
157
158
  text: record.text,
158
159
  ...(record.attachments?.length ? { attachments: record.attachments } : {}),
159
160
  ...(record.seq === undefined ? {} : { seq: record.seq }),
161
+ ...(record.author === undefined ? {} : { author: record.author }),
160
162
  ts: new Date().toISOString(),
161
163
  });
162
164
  }
@@ -294,13 +296,14 @@ export class SessionJournal {
294
296
  * it is queued in memory, so the ordering is "on disk, then held" — a crash
295
297
  * between the two costs a duplicate delivery at worst, never a lost message.
296
298
  */
297
- appendPending(text, attachments, seq) {
299
+ appendPending(text, attachments, seq, author) {
298
300
  const id = `p${this.pendingCounter++}`;
299
301
  const record = {
300
302
  id,
301
303
  text,
302
304
  ...(attachments?.length ? { attachments } : {}),
303
305
  ...(seq === undefined ? {} : { seq }),
306
+ ...(author === undefined ? {} : { author }),
304
307
  };
305
308
  this.write({
306
309
  kind: 'pending',
@@ -308,6 +311,7 @@ export class SessionJournal {
308
311
  text,
309
312
  ...(attachments?.length ? { attachments } : {}),
310
313
  ...(seq === undefined ? {} : { seq }),
314
+ ...(author === undefined ? {} : { author }),
311
315
  ts: new Date().toISOString(),
312
316
  });
313
317
  this.pendingById.set(id, record);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Who typed this — for the agent, not for the feed.
3
+ *
4
+ * ## Why the agent needs it
5
+ *
6
+ * Several people write into one session: the developer, a colleague forwarding
7
+ * a client's email, the platform owner stepping in to help. The runner echoes
8
+ * every message into the feed as `role: 'user'` and the API stamps the author
9
+ * onto that echo for people to see — but the AGENT got the bare words, and
10
+ * could not tell one person from another. A project prompt that says «if Andy
11
+ * writes, do not start development — say you are waiting for Dima» had nothing
12
+ * to go on.
13
+ *
14
+ * ## What it is, and what it is not
15
+ *
16
+ * A line the platform puts on top of the composed prompt, named after the
17
+ * person as DevBridge shows them. The feed never sees it: `acceptUserMessage`
18
+ * echoes the text the person typed, and this runs on the way to the agent, the
19
+ * same place the attachment paths are added. It is a hint to the model, not an
20
+ * access control — the project's real permissions are its roles, and the line
21
+ * carries no authority of its own. The API decides per message whether to send
22
+ * a name at all (the project setting «Tell the agent who is writing»); the
23
+ * runner adds the line whenever one arrives and never invents one.
24
+ *
25
+ * The name comes off the wire and is treated as such: folded onto one line,
26
+ * trimmed, and an empty one means no line at all.
27
+ */
28
+ export declare function signMessage(text: string, author: string | undefined): string;
29
+ //# sourceMappingURL=message-author.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Who typed this — for the agent, not for the feed.
3
+ *
4
+ * ## Why the agent needs it
5
+ *
6
+ * Several people write into one session: the developer, a colleague forwarding
7
+ * a client's email, the platform owner stepping in to help. The runner echoes
8
+ * every message into the feed as `role: 'user'` and the API stamps the author
9
+ * onto that echo for people to see — but the AGENT got the bare words, and
10
+ * could not tell one person from another. A project prompt that says «if Andy
11
+ * writes, do not start development — say you are waiting for Dima» had nothing
12
+ * to go on.
13
+ *
14
+ * ## What it is, and what it is not
15
+ *
16
+ * A line the platform puts on top of the composed prompt, named after the
17
+ * person as DevBridge shows them. The feed never sees it: `acceptUserMessage`
18
+ * echoes the text the person typed, and this runs on the way to the agent, the
19
+ * same place the attachment paths are added. It is a hint to the model, not an
20
+ * access control — the project's real permissions are its roles, and the line
21
+ * carries no authority of its own. The API decides per message whether to send
22
+ * a name at all (the project setting «Tell the agent who is writing»); the
23
+ * runner adds the line whenever one arrives and never invents one.
24
+ *
25
+ * The name comes off the wire and is treated as such: folded onto one line,
26
+ * trimmed, and an empty one means no line at all.
27
+ */
28
+ export function signMessage(text, author) {
29
+ const name = (author ?? '').replace(/\s+/g, ' ').trim();
30
+ if (name === '')
31
+ return text;
32
+ return `[Message from ${name}]\n\n${text}`;
33
+ }
34
+ //# sourceMappingURL=message-author.js.map
@@ -1,6 +1,12 @@
1
1
  import { z } from 'zod';
2
2
  import type { HostLoadFrame } from './host-load.js';
3
3
  import type { SessionLimitsFrame } from './session-limits.js';
4
+ /**
5
+ * The longest name the wire accepts for «who typed this». A display name from a
6
+ * profile, not a document — anything longer is not a name, and the field is a
7
+ * nicety: over the limit it is dropped and the message goes on without it.
8
+ */
9
+ export declare const MESSAGE_AUTHOR_MAX = 200;
4
10
  export declare const SessionDescriptorSchema: z.ZodObject<{
5
11
  id: z.ZodString;
6
12
  kind: z.ZodEnum<["TICKET", "CHAT"]>;
@@ -71,6 +77,18 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
71
77
  * arrives as `undefined`, which is the same as «auto-update is off».
72
78
  */
73
79
  agentLatestVersion: z.ZodCatch<z.ZodOptional<z.ZodString>>;
80
+ /**
81
+ * Who handed over the first message — the person who created the session, as
82
+ * DevBridge shows them. Sent only when the project has «Tell the agent who is
83
+ * writing» switched on and the prompt is not empty; `composeInitialPrompt`
84
+ * puts the name on top of the whole first message (`message-author.ts`).
85
+ *
86
+ * Optional, and absent means «no name»: an older API never sends it, and a
87
+ * project with the setting off gets exactly the first message it always had.
88
+ * `.catch(undefined)` like its neighbours — a bad value costs the signature,
89
+ * never the descriptor, never the `hello_ack` it rides in (QA-100 MAJOR-1).
90
+ */
91
+ promptAuthor: z.ZodCatch<z.ZodOptional<z.ZodString>>;
74
92
  workspace: z.ZodObject<{
75
93
  id: z.ZodString;
76
94
  path: z.ZodString;
@@ -277,6 +295,7 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
277
295
  pausedUntil?: string | null | undefined;
278
296
  backgroundTasks?: number | undefined;
279
297
  agentLatestVersion?: string | undefined;
298
+ promptAuthor?: string | undefined;
280
299
  skipAgentPrompt?: boolean | undefined;
281
300
  branchHint?: string | undefined;
282
301
  branchPlan?: {
@@ -328,6 +347,7 @@ export declare const SessionDescriptorSchema: z.ZodObject<{
328
347
  pausedUntil?: unknown;
329
348
  backgroundTasks?: unknown;
330
349
  agentLatestVersion?: unknown;
350
+ promptAuthor?: unknown;
331
351
  skipAgentPrompt?: unknown;
332
352
  branchHint?: unknown;
333
353
  branchPlan?: unknown;
@@ -362,6 +382,14 @@ export declare const QuestionAnswerShape: {
362
382
  notes?: string | undefined;
363
383
  }>, "many">>;
364
384
  readonly text: z.ZodOptional<z.ZodString>;
385
+ /**
386
+ * Who typed `text` — sent only when the project has «Tell the agent who is
387
+ * writing» on. Signs the free text of a «discuss» reply (words a person typed
388
+ * into an open card are a message like any other) and the words rescued when
389
+ * the card turns out to be dead; a pick stays a pick. `.catch(undefined)`
390
+ * like the message author: a bad name costs the signature, not the answer.
391
+ */
392
+ readonly author: z.ZodCatch<z.ZodOptional<z.ZodString>>;
365
393
  };
366
394
  /** The `answer_question` command's arguments — the shape above on its own. */
367
395
  export declare const QuestionAnswerArgsSchema: z.ZodObject<{
@@ -384,10 +412,19 @@ export declare const QuestionAnswerArgsSchema: z.ZodObject<{
384
412
  notes?: string | undefined;
385
413
  }>, "many">>;
386
414
  readonly text: z.ZodOptional<z.ZodString>;
415
+ /**
416
+ * Who typed `text` — sent only when the project has «Tell the agent who is
417
+ * writing» on. Signs the free text of a «discuss» reply (words a person typed
418
+ * into an open card are a message like any other) and the words rescued when
419
+ * the card turns out to be dead; a pick stays a pick. `.catch(undefined)`
420
+ * like the message author: a bad name costs the signature, not the answer.
421
+ */
422
+ readonly author: z.ZodCatch<z.ZodOptional<z.ZodString>>;
387
423
  }, "strip", z.ZodTypeAny, {
388
424
  action: "answer" | "discuss";
389
425
  askId: string;
390
426
  text?: string | undefined;
427
+ author?: string | undefined;
391
428
  answers?: {
392
429
  values: string[];
393
430
  questionId: string;
@@ -398,6 +435,7 @@ export declare const QuestionAnswerArgsSchema: z.ZodObject<{
398
435
  action: "answer" | "discuss";
399
436
  askId: string;
400
437
  text?: string | undefined;
438
+ author?: unknown;
401
439
  answers?: {
402
440
  values: string[];
403
441
  questionId: string;
@@ -432,9 +470,18 @@ export declare const DeliverMessageArgsSchema: z.ZodObject<{
432
470
  mimeType: string;
433
471
  fileSize: number;
434
472
  }>, "many">>>;
473
+ /**
474
+ * Who typed it, as DevBridge shows them — present only when the project has
475
+ * «Tell the agent who is writing» switched on. Goes on top of the composed
476
+ * prompt (`message-author.ts`), never into the feed echo. `.catch(undefined)`
477
+ * for the same reason attachments have it: a malformed name must cost the
478
+ * signature, never the sentence the person typed.
479
+ */
480
+ author: z.ZodCatch<z.ZodOptional<z.ZodString>>;
435
481
  }, "strip", z.ZodTypeAny, {
436
482
  text: string;
437
483
  messageId: string;
484
+ author?: string | undefined;
438
485
  attachments?: {
439
486
  id: string;
440
487
  fileName: string;
@@ -444,6 +491,7 @@ export declare const DeliverMessageArgsSchema: z.ZodObject<{
444
491
  }, {
445
492
  text: string;
446
493
  messageId: string;
494
+ author?: unknown;
447
495
  attachments?: unknown;
448
496
  }>;
449
497
  export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
@@ -521,6 +569,18 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
521
569
  * arrives as `undefined`, which is the same as «auto-update is off».
522
570
  */
523
571
  agentLatestVersion: z.ZodCatch<z.ZodOptional<z.ZodString>>;
572
+ /**
573
+ * Who handed over the first message — the person who created the session, as
574
+ * DevBridge shows them. Sent only when the project has «Tell the agent who is
575
+ * writing» switched on and the prompt is not empty; `composeInitialPrompt`
576
+ * puts the name on top of the whole first message (`message-author.ts`).
577
+ *
578
+ * Optional, and absent means «no name»: an older API never sends it, and a
579
+ * project with the setting off gets exactly the first message it always had.
580
+ * `.catch(undefined)` like its neighbours — a bad value costs the signature,
581
+ * never the descriptor, never the `hello_ack` it rides in (QA-100 MAJOR-1).
582
+ */
583
+ promptAuthor: z.ZodCatch<z.ZodOptional<z.ZodString>>;
524
584
  workspace: z.ZodObject<{
525
585
  id: z.ZodString;
526
586
  path: z.ZodString;
@@ -727,6 +787,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
727
787
  pausedUntil?: string | null | undefined;
728
788
  backgroundTasks?: number | undefined;
729
789
  agentLatestVersion?: string | undefined;
790
+ promptAuthor?: string | undefined;
730
791
  skipAgentPrompt?: boolean | undefined;
731
792
  branchHint?: string | undefined;
732
793
  branchPlan?: {
@@ -778,6 +839,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
778
839
  pausedUntil?: unknown;
779
840
  backgroundTasks?: unknown;
780
841
  agentLatestVersion?: unknown;
842
+ promptAuthor?: unknown;
781
843
  skipAgentPrompt?: unknown;
782
844
  branchHint?: unknown;
783
845
  branchPlan?: unknown;
@@ -827,6 +889,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
827
889
  pausedUntil?: string | null | undefined;
828
890
  backgroundTasks?: number | undefined;
829
891
  agentLatestVersion?: string | undefined;
892
+ promptAuthor?: string | undefined;
830
893
  skipAgentPrompt?: boolean | undefined;
831
894
  branchHint?: string | undefined;
832
895
  branchPlan?: {
@@ -884,6 +947,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
884
947
  pausedUntil?: unknown;
885
948
  backgroundTasks?: unknown;
886
949
  agentLatestVersion?: unknown;
950
+ promptAuthor?: unknown;
887
951
  skipAgentPrompt?: unknown;
888
952
  branchHint?: unknown;
889
953
  branchPlan?: unknown;
@@ -990,6 +1054,18 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
990
1054
  * arrives as `undefined`, which is the same as «auto-update is off».
991
1055
  */
992
1056
  agentLatestVersion: z.ZodCatch<z.ZodOptional<z.ZodString>>;
1057
+ /**
1058
+ * Who handed over the first message — the person who created the session, as
1059
+ * DevBridge shows them. Sent only when the project has «Tell the agent who is
1060
+ * writing» switched on and the prompt is not empty; `composeInitialPrompt`
1061
+ * puts the name on top of the whole first message (`message-author.ts`).
1062
+ *
1063
+ * Optional, and absent means «no name»: an older API never sends it, and a
1064
+ * project with the setting off gets exactly the first message it always had.
1065
+ * `.catch(undefined)` like its neighbours — a bad value costs the signature,
1066
+ * never the descriptor, never the `hello_ack` it rides in (QA-100 MAJOR-1).
1067
+ */
1068
+ promptAuthor: z.ZodCatch<z.ZodOptional<z.ZodString>>;
993
1069
  workspace: z.ZodObject<{
994
1070
  id: z.ZodString;
995
1071
  path: z.ZodString;
@@ -1196,6 +1272,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1196
1272
  pausedUntil?: string | null | undefined;
1197
1273
  backgroundTasks?: number | undefined;
1198
1274
  agentLatestVersion?: string | undefined;
1275
+ promptAuthor?: string | undefined;
1199
1276
  skipAgentPrompt?: boolean | undefined;
1200
1277
  branchHint?: string | undefined;
1201
1278
  branchPlan?: {
@@ -1247,6 +1324,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1247
1324
  pausedUntil?: unknown;
1248
1325
  backgroundTasks?: unknown;
1249
1326
  agentLatestVersion?: unknown;
1327
+ promptAuthor?: unknown;
1250
1328
  skipAgentPrompt?: unknown;
1251
1329
  branchHint?: unknown;
1252
1330
  branchPlan?: unknown;
@@ -1295,6 +1373,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1295
1373
  pausedUntil?: string | null | undefined;
1296
1374
  backgroundTasks?: number | undefined;
1297
1375
  agentLatestVersion?: string | undefined;
1376
+ promptAuthor?: string | undefined;
1298
1377
  skipAgentPrompt?: boolean | undefined;
1299
1378
  branchHint?: string | undefined;
1300
1379
  branchPlan?: {
@@ -1349,6 +1428,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1349
1428
  pausedUntil?: unknown;
1350
1429
  backgroundTasks?: unknown;
1351
1430
  agentLatestVersion?: unknown;
1431
+ promptAuthor?: unknown;
1352
1432
  skipAgentPrompt?: unknown;
1353
1433
  branchHint?: unknown;
1354
1434
  branchPlan?: unknown;
@@ -1380,10 +1460,12 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1380
1460
  mimeType: string;
1381
1461
  fileSize: number;
1382
1462
  }>, "many">>>;
1463
+ author: z.ZodCatch<z.ZodOptional<z.ZodString>>;
1383
1464
  }, "strip", z.ZodTypeAny, {
1384
1465
  text: string;
1385
1466
  sessionId: string;
1386
1467
  type: "session_message";
1468
+ author?: string | undefined;
1387
1469
  attachments?: {
1388
1470
  id: string;
1389
1471
  fileName: string;
@@ -1395,6 +1477,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1395
1477
  text: string;
1396
1478
  sessionId: string;
1397
1479
  type: "session_message";
1480
+ author?: unknown;
1398
1481
  attachments?: unknown;
1399
1482
  messageId?: unknown;
1400
1483
  }>, z.ZodObject<{
@@ -1435,6 +1518,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1435
1518
  notes?: string | undefined;
1436
1519
  }>, "many">>;
1437
1520
  text: z.ZodOptional<z.ZodString>;
1521
+ author: z.ZodCatch<z.ZodOptional<z.ZodString>>;
1438
1522
  type: z.ZodLiteral<"question_answer">;
1439
1523
  sessionId: z.ZodString;
1440
1524
  }, "strip", z.ZodTypeAny, {
@@ -1443,6 +1527,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1443
1527
  action: "answer" | "discuss";
1444
1528
  askId: string;
1445
1529
  text?: string | undefined;
1530
+ author?: string | undefined;
1446
1531
  answers?: {
1447
1532
  values: string[];
1448
1533
  questionId: string;
@@ -1455,6 +1540,7 @@ export declare const GatewayFrameSchema: z.ZodDiscriminatedUnion<"type", [z.ZodO
1455
1540
  action: "answer" | "discuss";
1456
1541
  askId: string;
1457
1542
  text?: string | undefined;
1543
+ author?: unknown;
1458
1544
  answers?: {
1459
1545
  values: string[];
1460
1546
  questionId: string;
package/dist/protocol.js CHANGED
@@ -3,6 +3,12 @@ import { AGENT_DB_VALUES, AGENT_VERSION_PATTERN } from './agent-registry.js';
3
3
  // Wire protocol with the DevBridge API (mirror of
4
4
  // packages/api/src/modules/dev-orchestration/dev-orchestration.schema.ts —
5
5
  // the API side is the source of truth).
6
+ /**
7
+ * The longest name the wire accepts for «who typed this». A display name from a
8
+ * profile, not a document — anything longer is not a name, and the field is a
9
+ * nicety: over the limit it is dropped and the message goes on without it.
10
+ */
11
+ export const MESSAGE_AUTHOR_MAX = 200;
6
12
  export const SessionDescriptorSchema = z.object({
7
13
  id: z.string().uuid(),
8
14
  kind: z.enum(['TICKET', 'CHAT']),
@@ -102,6 +108,18 @@ export const SessionDescriptorSchema = z.object({
102
108
  .regex(new RegExp(AGENT_VERSION_PATTERN))
103
109
  .optional()
104
110
  .catch(undefined),
111
+ /**
112
+ * Who handed over the first message — the person who created the session, as
113
+ * DevBridge shows them. Sent only when the project has «Tell the agent who is
114
+ * writing» switched on and the prompt is not empty; `composeInitialPrompt`
115
+ * puts the name on top of the whole first message (`message-author.ts`).
116
+ *
117
+ * Optional, and absent means «no name»: an older API never sends it, and a
118
+ * project with the setting off gets exactly the first message it always had.
119
+ * `.catch(undefined)` like its neighbours — a bad value costs the signature,
120
+ * never the descriptor, never the `hello_ack` it rides in (QA-100 MAJOR-1).
121
+ */
122
+ promptAuthor: z.string().max(MESSAGE_AUTHOR_MAX).optional().catch(undefined),
105
123
  workspace: z.object({
106
124
  id: z.string().uuid(),
107
125
  path: z.string(),
@@ -263,6 +281,14 @@ export const QuestionAnswerShape = {
263
281
  .max(4)
264
282
  .optional(),
265
283
  text: z.string().max(20_000).optional(),
284
+ /**
285
+ * Who typed `text` — sent only when the project has «Tell the agent who is
286
+ * writing» on. Signs the free text of a «discuss» reply (words a person typed
287
+ * into an open card are a message like any other) and the words rescued when
288
+ * the card turns out to be dead; a pick stays a pick. `.catch(undefined)`
289
+ * like the message author: a bad name costs the signature, not the answer.
290
+ */
291
+ author: z.string().max(MESSAGE_AUTHOR_MAX).optional().catch(undefined),
266
292
  };
267
293
  /** The `answer_question` command's arguments — the shape above on its own. */
268
294
  export const QuestionAnswerArgsSchema = z.object(QuestionAnswerShape);
@@ -295,6 +321,14 @@ export const DeliverMessageArgsSchema = z.object({
295
321
  messageId: z.string().min(1).max(120),
296
322
  text: z.string(),
297
323
  attachments: MessageAttachmentsSchema,
324
+ /**
325
+ * Who typed it, as DevBridge shows them — present only when the project has
326
+ * «Tell the agent who is writing» switched on. Goes on top of the composed
327
+ * prompt (`message-author.ts`), never into the feed echo. `.catch(undefined)`
328
+ * for the same reason attachments have it: a malformed name must cost the
329
+ * signature, never the sentence the person typed.
330
+ */
331
+ author: z.string().max(MESSAGE_AUTHOR_MAX).optional().catch(undefined),
298
332
  });
299
333
  export const GatewayFrameSchema = z.discriminatedUnion('type', [
300
334
  z.object({
@@ -334,6 +368,10 @@ export const GatewayFrameSchema = z.discriminatedUnion('type', [
334
368
  // fetched with the runner's own token. `.catch` so a malformed entry costs
335
369
  // the attachments, never the message the user typed.
336
370
  attachments: MessageAttachmentsSchema,
371
+ // Who typed it — the same field `deliver_message` carries, on the legacy
372
+ // door too, so a project with the setting on is not silently unsigned on
373
+ // an API that still sends the frame.
374
+ author: z.string().max(MESSAGE_AUTHOR_MAX).optional().catch(undefined),
337
375
  }),
338
376
  z.object({
339
377
  type: z.literal('permission_answer'),
@@ -35,6 +35,7 @@ import { countRunningCommands, freshStallState, pickKillCandidate, readScopeProc
35
35
  import { machineReserveBytes } from './service-unit.js';
36
36
  import { sessionLimitsChangedEnough, sessionLimitsHeartbeatDue, SESSION_LIMITS_HEARTBEAT_MS, } from './session-limits.js';
37
37
  import { composeMessageWithAttachments, saveAttachments, } from './attachments.js';
38
+ import { signMessage } from './message-author.js';
38
39
  import { applyRewind, createCheckpoint, dropCheckpoints, listCheckpoints, MAX_BUSY_SESSIONS, previewRewind, pruneCheckpoints, } from './checkpoints.js';
39
40
  import { DeliverMessageArgsSchema, QuestionAnswerArgsSchema } from './protocol.js';
40
41
  import { availableModes, MODE_REFUSED_TEXT } from './adapters/types.js';
@@ -1828,7 +1829,7 @@ export class Supervisor {
1828
1829
  // command yet. Its outcome goes nowhere because nobody is listening —
1829
1830
  // which is the whole defect ticket #299 is about, and why the command
1830
1831
  // below exists.
1831
- this.acceptUserMessage(frame.sessionId, frame.text, frame.attachments, frame.messageId ?? undefined);
1832
+ this.acceptUserMessage(frame.sessionId, frame.text, frame.attachments, frame.messageId ?? undefined, frame.author);
1832
1833
  break;
1833
1834
  case 'permission_answer': {
1834
1835
  const running = this.sessions.get(frame.sessionId);
@@ -2021,7 +2022,7 @@ export class Supervisor {
2021
2022
  if (orphaned) {
2022
2023
  this.orphanMessages.delete(descriptor.id);
2023
2024
  for (const message of orphaned) {
2024
- this.acceptUserMessage(descriptor.id, message.text, message.attachments, message.messageId);
2025
+ this.acceptUserMessage(descriptor.id, message.text, message.attachments, message.messageId, message.author);
2025
2026
  }
2026
2027
  }
2027
2028
  try {
@@ -4293,11 +4294,18 @@ export class Supervisor {
4293
4294
  // own events reach the feed a microtask later, so the user's words still
4294
4295
  // land before the agent's reaction — and on a miss we can hand the text to
4295
4296
  // the ordinary delivery path, which does the echo itself.
4297
+ //
4298
+ // The free text of a «discuss» reply is a person's message like any other,
4299
+ // so it is signed for the agent the way `acceptUserMessage` signs one
4300
+ // (`message-author.ts`) — and only for the agent: the echo below stays the
4301
+ // words as typed. A pick (`answer`) is structured and gets no line.
4296
4302
  const accepted = running.session?.answerQuestion({
4297
4303
  askId: frame.askId,
4298
4304
  action: frame.action,
4299
4305
  ...(frame.answers ? { answers: frame.answers } : {}),
4300
- ...(frame.text !== undefined ? { text: frame.text } : {}),
4306
+ ...(frame.text !== undefined
4307
+ ? { text: frame.action === 'discuss' ? signMessage(frame.text, frame.author) : frame.text }
4308
+ : {}),
4301
4309
  });
4302
4310
  if (accepted) {
4303
4311
  running.answeredAsks.add(frame.askId);
@@ -4372,7 +4380,7 @@ export class Supervisor {
4372
4380
  this.sendEvent(running, 'system_note', {
4373
4381
  text: 'That question is no longer open — sending your reply as an ordinary message instead.',
4374
4382
  });
4375
- this.acceptUserMessage(frame.sessionId, rescued);
4383
+ this.acceptUserMessage(frame.sessionId, rescued, undefined, undefined, frame.author);
4376
4384
  return 'not_open';
4377
4385
  }
4378
4386
  // Nothing to deliver — but the API optimistically flipped the session to
@@ -4395,7 +4403,14 @@ export class Supervisor {
4395
4403
  * thrown away, because «I wrote it into a socket» was never an answer to
4396
4404
  * «did the runner get it».
4397
4405
  */
4398
- acceptUserMessage(sessionId, text, attachments, messageId) {
4406
+ acceptUserMessage(sessionId, text, attachments, messageId,
4407
+ /**
4408
+ * Who typed it, as DevBridge shows them — sent only when the project has
4409
+ * «Tell the agent who is writing» on. Goes on top of the COMPOSED prompt
4410
+ * (`message-author.ts`), the way attachment paths do; the feed echo below
4411
+ * stays the bare text, exactly as the person wrote it.
4412
+ */
4413
+ author) {
4399
4414
  const running = this.sessions.get(sessionId);
4400
4415
  if (!running) {
4401
4416
  // The API believes this session lives here but the runner lost track of
@@ -4417,6 +4432,7 @@ export class Supervisor {
4417
4432
  text,
4418
4433
  ...(attachments?.length ? { attachments } : {}),
4419
4434
  ...(messageId ? { messageId } : {}),
4435
+ ...(author ? { author } : {}),
4420
4436
  });
4421
4437
  this.orphanMessages.set(sessionId, queued.slice(-Supervisor.ORPHAN_MESSAGE_CAP));
4422
4438
  this.ws.send({ type: 'session_unknown', sessionId });
@@ -4483,17 +4499,17 @@ export class Supervisor {
4483
4499
  // check — the outbox flushes on reconnect, and the git service posts its
4484
4500
  // own conflict tasks. Held here rather than delivered, so «на паузе»
4485
4501
  // means the same thing whichever door the words came through.
4486
- this.queueMessage(running, running.journal.appendPending(text, attachments, originSeq));
4502
+ this.queueMessage(running, running.journal.appendPending(text, attachments, originSeq, author));
4487
4503
  return 'accepted';
4488
4504
  }
4489
4505
  this.enqueueDelivery(running, async () => {
4490
- const composed = await this.materializeAttachments(running, text, attachments);
4506
+ const composed = signMessage(await this.materializeAttachments(running, text, attachments), author);
4491
4507
  if (this.isStale(running)) {
4492
4508
  // The session ended while the files downloaded. Park the message on
4493
4509
  // disk rather than dropping it — «Продолжить» carries it to the agent.
4494
4510
  // Announced like every other queueing decision: a bubble that is
4495
4511
  // waiting must say so, whichever door it came through.
4496
- const parked = running.journal.appendPending(text, attachments, originSeq);
4512
+ const parked = running.journal.appendPending(text, attachments, originSeq, author);
4497
4513
  if (parked.seq !== undefined) {
4498
4514
  this.sendEvent(running, 'message_queued', { targetSeq: parked.seq });
4499
4515
  }
@@ -5077,7 +5093,10 @@ export class Supervisor {
5077
5093
  const parts = [];
5078
5094
  try {
5079
5095
  for (const record of pending) {
5080
- parts.push(await this.materializeAttachments(running, record.text, record.attachments));
5096
+ // Each part under its own name: two people who wrote while the
5097
+ // session was held go to the agent together, and the agent must
5098
+ // still be able to tell which words are whose.
5099
+ parts.push(signMessage(await this.materializeAttachments(running, record.text, record.attachments), record.author));
5081
5100
  }
5082
5101
  }
5083
5102
  catch (error) {
@@ -6507,7 +6526,7 @@ export class Supervisor {
6507
6526
  error: parsed.error.issues[0]?.message ?? 'malformed message',
6508
6527
  });
6509
6528
  }
6510
- const outcome = this.acceptUserMessage(sessionId, parsed.data.text, parsed.data.attachments, parsed.data.messageId);
6529
+ const outcome = this.acceptUserMessage(sessionId, parsed.data.text, parsed.data.attachments, parsed.data.messageId, parsed.data.author);
6511
6530
  // `ok` is about the RELAY, like the answer above: `unknown_session` is
6512
6531
  // the one case the API can fix by asking again after it has re-sent
6513
6532
  // the descriptor, so it is the one case reported as a failure.
@@ -8219,6 +8238,15 @@ export function carryOnMessage(refusedWhole) {
8219
8238
  * prompt beside `CLAUDE.md` — which is read last and therefore overrides ours.
8220
8239
  */
8221
8240
  export function composeInitialPrompt(descriptor) {
8241
+ const body = composeInitialBody(descriptor);
8242
+ // Signed like every later message (`message-author.ts`): the API names the
8243
+ // creator only when the project has «Tell the agent who is writing» on. The
8244
+ // name goes above the assignment too — handing a ticket over is that
8245
+ // person's act. An empty first message stays empty: there is nothing to sign
8246
+ // and the agent is meant to boot and wait.
8247
+ return body === '' ? body : signMessage(body, descriptor.promptAuthor);
8248
+ }
8249
+ function composeInitialBody(descriptor) {
8222
8250
  // A free chat may carry tickets as CONTEXT. Assigning them would put an agent
8223
8251
  // nobody asked onto somebody else's ticket (session 13).
8224
8252
  if (descriptor.kind === 'CHAT' || descriptor.tickets.length === 0)
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const RUNNER_VERSION = "0.69.1";
1
+ export declare const RUNNER_VERSION = "0.70.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Kept in sync with package.json by the release script (manual for now).
2
- export const RUNNER_VERSION = '0.69.1';
2
+ export const RUNNER_VERSION = '0.70.0';
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge4dev/runner",
3
- "version": "0.69.1",
3
+ "version": "0.70.0",
4
4
  "description": "DevBridge dev runner — connects a dev server to DevBridge and runs agent sessions (Claude Code / Codex)",
5
5
  "homepage": "https://bridge4.dev",
6
6
  "license": "MIT",