@mp-lb/berth 0.1.2 → 0.1.4

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.
Files changed (2) hide show
  1. package/dist/berth.js +155 -45
  2. package/package.json +1 -1
package/dist/berth.js CHANGED
@@ -5995,7 +5995,7 @@ var require_lib = __commonJS({
5995
5995
  });
5996
5996
 
5997
5997
  // package.json
5998
- var version = "0.1.2";
5998
+ var version = "0.1.4";
5999
5999
 
6000
6000
  // src/runAuthCommand.ts
6001
6001
  import { spawn } from "node:child_process";
@@ -23251,6 +23251,36 @@ var runInstanceCommand = async (args2) => {
23251
23251
  });
23252
23252
  };
23253
23253
 
23254
+ // ../../packages/core/src/work/schemas/messageEventSchema.ts
23255
+ var messageEventSchema = external_exports.strictObject({
23256
+ at: external_exports.iso.datetime(),
23257
+ actor: external_exports.string().trim().min(1).nullable(),
23258
+ action: external_exports.enum([
23259
+ "assigned",
23260
+ "commented",
23261
+ "closed",
23262
+ "reopened",
23263
+ "requested",
23264
+ "approved",
23265
+ "rejected",
23266
+ "deferred",
23267
+ "restored"
23268
+ ]),
23269
+ assignee: external_exports.string().trim().min(1).nullable().optional(),
23270
+ request: external_exports.strictObject({
23271
+ summary: external_exports.string(),
23272
+ evidence: external_exports.string(),
23273
+ deliveryAttemptId: external_exports.string().nullable()
23274
+ }).optional(),
23275
+ note: external_exports.string().trim().max(8e3).optional()
23276
+ });
23277
+
23278
+ // ../../packages/core/src/work/schemas/messageAppendSchema.ts
23279
+ var messageAppendSchema = messageEventSchema.omit({ at: true, request: true }).extend({
23280
+ id: external_exports.string().min(1),
23281
+ expectedLength: external_exports.number().int().nonnegative()
23282
+ });
23283
+
23254
23284
  // ../../packages/core/src/work/schemas/messageTypeSchema.ts
23255
23285
  var messageTypeSchema = external_exports.string().trim().min(1).max(100).regex(/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/);
23256
23286
 
@@ -23275,6 +23305,7 @@ var messageListSchema = external_exports.strictObject({
23275
23305
  // ../../packages/core/src/work/schemas/messageCreateSchema.ts
23276
23306
  var messageCreateSchema = external_exports.strictObject({
23277
23307
  recipient: messageRecipientSchema,
23308
+ sender: external_exports.string().trim().min(1).nullable().default(null),
23278
23309
  type: messageTypeSchema.default("message"),
23279
23310
  priority: external_exports.number().int().default(0),
23280
23311
  projectId: slugSchema.nullable(),
@@ -23424,6 +23455,8 @@ var blockerSchema = external_exports.discriminatedUnion("type", [
23424
23455
  // ../../packages/core/src/work/schemas/messageSchema.ts
23425
23456
  var messageSchema = external_exports.strictObject({
23426
23457
  id: external_exports.string(),
23458
+ sender: external_exports.string().min(1).nullable().optional(),
23459
+ timeline: external_exports.array(messageEventSchema).optional(),
23427
23460
  type: messageTypeSchema,
23428
23461
  recipient: messageRecipientSchema,
23429
23462
  priority: external_exports.number().int(),
@@ -23457,33 +23490,39 @@ var runMessageCommand = async (args2) => {
23457
23490
  if (extra.length) throw new Error("Quote the JSON payload as one argument.");
23458
23491
  if (action === "schema") {
23459
23492
  const schemas = {
23493
+ append: messageAppendSchema,
23460
23494
  create: messageCreateSchema,
23461
23495
  update: messageUpdateSchema,
23462
23496
  list: messageListSchema
23463
23497
  };
23464
23498
  const key = input ?? "create";
23465
- if (key !== "create" && key !== "update" && key !== "list")
23466
- throw new Error("Choose create, update or list schema.");
23499
+ if (key !== "append" && key !== "create" && key !== "update" && key !== "list")
23500
+ throw new Error("Choose create, update, append or list schema.");
23467
23501
  return external_exports.toJSONSchema(schemas[key], { io: "input" });
23468
23502
  }
23469
23503
  const api = createWorkClient().message;
23470
23504
  if (action === "types" && input === void 0) return api.types.query();
23505
+ if (action === "read" && typeof input === "string")
23506
+ return api.read.query(input);
23471
23507
  const payload = input === void 0 ? void 0 : JSON.parse(input);
23472
23508
  if (action === "list")
23473
23509
  return api.list.query(messageListSchema.optional().parse(payload));
23474
23510
  if (action === "read" && typeof input === "string")
23475
23511
  return api.read.query(input);
23512
+ if (action === "append")
23513
+ return api.append.mutate(messageAppendSchema.parse(payload));
23476
23514
  if (action === "create")
23477
23515
  return api.create.mutate(messageCreateSchema.parse(payload));
23478
23516
  if (action === "update")
23479
23517
  return api.update.mutate(messageUpdateSchema.parse(payload));
23480
23518
  throw new Error(
23481
- "Messages support types, create, update, read, list and schema."
23519
+ "Messages support types, create, update, append, read, list and schema."
23482
23520
  );
23483
23521
  };
23484
23522
 
23485
23523
  // ../../packages/core/src/work/schemas/agentPromptsSchema.ts
23486
23524
  var agentPromptsSchema = external_exports.object({
23525
+ workerRecovery: external_exports.string().min(1).optional(),
23487
23526
  workerLaunch: external_exports.string().min(1),
23488
23527
  roleMessage: external_exports.string().min(1),
23489
23528
  reassignedRoleMessage: external_exports.string().min(1)
@@ -24064,6 +24103,13 @@ var runPromptCommand = async (args2) => {
24064
24103
  );
24065
24104
  };
24066
24105
 
24106
+ // ../../packages/core/src/work/data/getSessionRecoveryProblem.ts
24107
+ var getSessionRecoveryProblem = (session) => {
24108
+ if (session.endedAt) return "Worker was intentionally ended.";
24109
+ if (!session.startedAt) return "Worker startup was never confirmed.";
24110
+ return null;
24111
+ };
24112
+
24067
24113
  // ../../packages/core/src/work/data/renderAgentPrompt.ts
24068
24114
  var import_handlebars = __toESM(require_lib(), 1);
24069
24115
 
@@ -24071,7 +24117,8 @@ var import_handlebars = __toESM(require_lib(), 1);
24071
24117
  var agentPromptDefaults_default = {
24072
24118
  workerLaunch: "Run fm read 'worker.md' --store 'mp-lb/roles' and follow its instructions for task {{taskId}}.",
24073
24119
  roleMessage: "You have a new Berth message ({{messageId}}). Read it with berth message read {{messageId}}.",
24074
- reassignedRoleMessage: "Berth message {{messageId}} is ready for you. Read it with berth message read {{messageId}}."
24120
+ reassignedRoleMessage: "Berth message {{messageId}} is ready for you. Read it with berth message read {{messageId}}.",
24121
+ workerRecovery: "Your terminal was interrupted. Review your conversation and the current state of task {{taskId}} if one is linked. Continue only unfinished work still assigned to you, respect blockers, and do not repeat completed actions. If work is complete or assigned elsewhere, wait for instructions."
24075
24122
  };
24076
24123
 
24077
24124
  // ../../packages/core/src/work/data/defaultAgentPrompts.ts
@@ -24083,7 +24130,7 @@ var renderAgentPrompt = (prompts, key, variables) => import_handlebars.default.c
24083
24130
  })(variables);
24084
24131
 
24085
24132
  // src/runSessionCommand.ts
24086
- import { setTimeout as delay2 } from "node:timers/promises";
24133
+ import { setTimeout as delay3 } from "node:timers/promises";
24087
24134
  import { realpathSync } from "node:fs";
24088
24135
  import { homedir as homedir6 } from "node:os";
24089
24136
  import { join as join6 } from "node:path";
@@ -24141,41 +24188,8 @@ var findWorkerType = (types, id) => {
24141
24188
  return type;
24142
24189
  };
24143
24190
 
24144
- // ../../packages/core/src/work/data/resolveDefaultWorkerTypeId.ts
24145
- var resolveDefaultWorkerTypeId = (types, current) => types.some((type) => type.id === current) ? current : types[0]?.id ?? null;
24146
-
24147
- // src/prepareTaskWorkerInstance.ts
24148
- var prepareTaskWorkerInstance = async (taskId, instanceSlug) => {
24149
- external_exports.uuid().parse(taskId);
24150
- const config2 = await createWorkClient().config.read.query();
24151
- const workerTypeId = resolveDefaultWorkerTypeId(
24152
- config2.workerTypes ?? [],
24153
- config2.defaultWorkerTypeId
24154
- );
24155
- if (!workerTypeId) throw new Error("Configure a default worker type first.");
24156
- findWorkerType(config2.workerTypes ?? [], workerTypeId);
24157
- const context = await readTaskLaunchContext(taskId);
24158
- if (!["draft", "queue"].includes(context.task.status))
24159
- throw new Error(
24160
- "Only an unstarted Draft or Queue task can start a worker."
24161
- );
24162
- if (context.task.blocked || context.task.ownership?.worker || context.task.workerId)
24163
- throw new Error("Task is blocked or already assigned to a worker.");
24164
- if (context.state === "missing_instance") {
24165
- if (!context.project) throw new Error("Task has no project to prepare.");
24166
- await runInstanceCommand([
24167
- "prepare",
24168
- "--project",
24169
- context.project.id,
24170
- "--task",
24171
- context.task.slug,
24172
- ...instanceSlug ? ["--slug", instanceSlug] : []
24173
- ]);
24174
- } else if (context.state !== "ready") {
24175
- throw new Error(`Task instance is ${context.state}; cannot start worker.`);
24176
- }
24177
- return workerTypeId;
24178
- };
24191
+ // src/recoverTerminalSession.ts
24192
+ import { setTimeout as delay2 } from "node:timers/promises";
24179
24193
 
24180
24194
  // src/tmuxSession.ts
24181
24195
  import { spawnSync } from "node:child_process";
@@ -24258,6 +24272,81 @@ var attachTmuxSession = (name) => {
24258
24272
  throw new Error("Could not attach to the tmux session.");
24259
24273
  };
24260
24274
 
24275
+ // src/recoverTerminalSession.ts
24276
+ var recoverTerminalSession = async (session, worker, prompts) => {
24277
+ if (session.endedAt || !session.startedAt)
24278
+ throw new Error("Session is not an unexpectedly exited session.");
24279
+ if (tmuxSessionExists(session.terminalSessionId))
24280
+ throw new Error("Session is already running.");
24281
+ if (session.workerTypeId !== "shell" && (!worker?.resumeCommand || !session.workerSessionId))
24282
+ throw new Error(
24283
+ "Worker has no configured resume command or conversation ID."
24284
+ );
24285
+ if (worker && !/{{\s*prompt\s*}}/.test(worker.resumeCommand))
24286
+ throw new Error("Resume command must include {{prompt}} to continue work.");
24287
+ if (worker && !/{{\s*workerSessionId\s*}}/.test(worker.resumeCommand))
24288
+ throw new Error(
24289
+ "Resume command must include {{workerSessionId}} to resume the original conversation."
24290
+ );
24291
+ const command = worker ? renderWorkerCommand(worker.resumeCommand, {
24292
+ prompt: renderAgentPrompt(prompts, "workerRecovery", {
24293
+ taskId: session.taskId ?? ""
24294
+ }),
24295
+ sessionId: session.id,
24296
+ workerSessionId: session.workerSessionId ?? "",
24297
+ taskId: session.taskId ?? "",
24298
+ directory: session.directory,
24299
+ terminalSessionId: session.terminalSessionId
24300
+ }) : null;
24301
+ startTmuxSession(
24302
+ session.terminalSessionId,
24303
+ session.directory,
24304
+ command,
24305
+ session.id,
24306
+ session.machineId
24307
+ );
24308
+ await delay2(250);
24309
+ if (!tmuxSessionExists(session.terminalSessionId))
24310
+ throw new Error("Worker exited immediately after recovery.");
24311
+ return { id: session.id, recovered: true };
24312
+ };
24313
+
24314
+ // ../../packages/core/src/work/data/resolveDefaultWorkerTypeId.ts
24315
+ var resolveDefaultWorkerTypeId = (types, current) => types.some((type) => type.id === current) ? current : types[0]?.id ?? null;
24316
+
24317
+ // src/prepareTaskWorkerInstance.ts
24318
+ var prepareTaskWorkerInstance = async (taskId, instanceSlug) => {
24319
+ external_exports.uuid().parse(taskId);
24320
+ const config2 = await createWorkClient().config.read.query();
24321
+ const workerTypeId = resolveDefaultWorkerTypeId(
24322
+ config2.workerTypes ?? [],
24323
+ config2.defaultWorkerTypeId
24324
+ );
24325
+ if (!workerTypeId) throw new Error("Configure a default worker type first.");
24326
+ findWorkerType(config2.workerTypes ?? [], workerTypeId);
24327
+ const context = await readTaskLaunchContext(taskId);
24328
+ if (!["draft", "queue"].includes(context.task.status))
24329
+ throw new Error(
24330
+ "Only an unstarted Draft or Queue task can start a worker."
24331
+ );
24332
+ if (context.task.blocked || context.task.ownership?.worker || context.task.workerId)
24333
+ throw new Error("Task is blocked or already assigned to a worker.");
24334
+ if (context.state === "missing_instance") {
24335
+ if (!context.project) throw new Error("Task has no project to prepare.");
24336
+ await runInstanceCommand([
24337
+ "prepare",
24338
+ "--project",
24339
+ context.project.id,
24340
+ "--task",
24341
+ context.task.slug,
24342
+ ...instanceSlug ? ["--slug", instanceSlug] : []
24343
+ ]);
24344
+ } else if (context.state !== "ready") {
24345
+ throw new Error(`Task instance is ${context.state}; cannot start worker.`);
24346
+ }
24347
+ return workerTypeId;
24348
+ };
24349
+
24261
24350
  // src/runSessionCommand.ts
24262
24351
  var runSessionCommand = async (args2) => {
24263
24352
  const [action, value, agentValue, instanceSlug, ...extra] = args2;
@@ -24283,6 +24372,22 @@ var runSessionCommand = async (args2) => {
24283
24372
  live: session.machineId === machine.id ? tmuxSessionExists(session.terminalSessionId) : null
24284
24373
  };
24285
24374
  }
24375
+ if (action === "recover" && value && !agentValue) {
24376
+ const session = await client.terminalSession.read.query(
24377
+ external_exports.uuid().parse(value)
24378
+ );
24379
+ const config2 = await client.config.read.query();
24380
+ const machine = resolveSessionMachine(config2);
24381
+ if (session.machineId !== machine.id)
24382
+ throw new Error("Session belongs to another work machine.");
24383
+ const problem = getSessionRecoveryProblem(session);
24384
+ if (problem) throw new Error(problem);
24385
+ return recoverTerminalSession(
24386
+ session,
24387
+ session.workerTypeId === "shell" ? null : findWorkerType(config2.workerTypes ?? [], session.workerTypeId),
24388
+ config2.agentPrompts
24389
+ );
24390
+ }
24286
24391
  if (action === "attach" && value && !agentValue) {
24287
24392
  const session = await client.terminalSession.read.query(
24288
24393
  external_exports.uuid().parse(value)
@@ -24354,7 +24459,7 @@ var runSessionCommand = async (args2) => {
24354
24459
  session.id,
24355
24460
  machine.id
24356
24461
  );
24357
- await delay2(250);
24462
+ await delay3(250);
24358
24463
  if (!tmuxSessionExists(session.terminalSessionId))
24359
24464
  throw new Error("Agent process exited immediately after launch.");
24360
24465
  return client.terminalSession.update.mutate({
@@ -24460,7 +24565,7 @@ var runSessionCommand = async (args2) => {
24460
24565
  session.id,
24461
24566
  context.machine.id
24462
24567
  );
24463
- await delay2(250);
24568
+ await delay3(250);
24464
24569
  if (!tmuxSessionExists(session.terminalSessionId))
24465
24570
  throw new Error("Agent process exited immediately after launch.");
24466
24571
  } catch (error51) {
@@ -24479,7 +24584,7 @@ var runSessionCommand = async (args2) => {
24479
24584
  });
24480
24585
  }
24481
24586
  throw new Error(
24482
- "Use berth session list|read|status|create|start|launch|attach|kill|delete|register|schema. Run berth --help."
24587
+ "Use berth session list|read|status|create|start|launch|recover|attach|kill|delete|register|schema. Run berth --help."
24483
24588
  );
24484
24589
  };
24485
24590
 
@@ -24573,6 +24678,9 @@ var runRoleCommand = async (args2) => {
24573
24678
  // ../../packages/core/src/work/schemas/signOffRequestSchema.ts
24574
24679
  var signOffRequestSchema = external_exports.strictObject({
24575
24680
  slug: taskSlugSchema,
24681
+ sender: external_exports.string().trim().min(1).optional().describe(
24682
+ "Requesting role within the project; defaults to delivery-owner."
24683
+ ),
24576
24684
  deliveryAttemptId: external_exports.uuid().optional().describe(
24577
24685
  "Latest recorded delivery attempt; omitted selects latest. Legacy issued requests remain valid without an attempt."
24578
24686
  ),
@@ -25223,6 +25331,7 @@ berth session status <session-uuid>
25223
25331
  berth session create [shell|worker-type-id] [directory]
25224
25332
  berth session start <task-uuid> [instance-slug]
25225
25333
  berth session launch <task-uuid> <worker-type-id> [instance-slug]
25334
+ berth session recover <session-uuid>
25226
25335
  berth session attach <session-uuid>
25227
25336
  berth session kill <session-uuid>
25228
25337
  berth session delete <session-uuid>
@@ -25249,7 +25358,8 @@ berth message create '{"recipient":{"name":"delivery-owner","projectId":"bond"},
25249
25358
  berth message update '{"id":"<message-id>","recipient":{"name":"delivery-owner","projectId":"bond"}}'
25250
25359
  berth message read <message-id>
25251
25360
  berth message list '{"recipient":{"name":"delivery-owner","projectId":"bond"}}'
25252
- berth message schema [create|update|list]
25361
+ berth message append '{"id":"<message-id>","expectedLength":1,"actor":"delivery-owner","action":"closed"}'
25362
+ berth message schema [create|update|append|list]
25253
25363
  Messages are stored records. Recipient, type, priority and status are independent.
25254
25364
  List includes stored messages; deleting one never deletes its task or blocker.
25255
25365
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mp-lb/berth",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {