@mp-lb/berth 0.1.2 → 0.1.3

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 +164 -44
  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.3";
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,28 +23490,33 @@ 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
 
@@ -24064,6 +24102,22 @@ var runPromptCommand = async (args2) => {
24064
24102
  );
24065
24103
  };
24066
24104
 
24105
+ // ../../packages/core/src/work/data/getSessionRecoveryProblem.ts
24106
+ var getSessionRecoveryProblem = (session, task) => {
24107
+ if (!task || session.taskId !== task.uuid || task.status !== "working" || task.archivedAt)
24108
+ return "Task is not in the working column.";
24109
+ if (task.workerRun?.completedAt)
24110
+ return "This worker has already completed its work.";
24111
+ const owner = task.ownership?.worker;
24112
+ if (owner && (!("sessionId" in owner) || owner.sessionId !== session.id || owner.machineId !== session.machineId))
24113
+ return "Task is assigned to another worker.";
24114
+ if (task.workerId && task.workerId !== session.workerSessionId)
24115
+ return "Task is assigned to another worker.";
24116
+ if (session.endedAt || !session.startedAt)
24117
+ return "Session is not an unexpectedly exited session.";
24118
+ return null;
24119
+ };
24120
+
24067
24121
  // ../../packages/core/src/work/data/renderAgentPrompt.ts
24068
24122
  var import_handlebars = __toESM(require_lib(), 1);
24069
24123
 
@@ -24083,7 +24137,7 @@ var renderAgentPrompt = (prompts, key, variables) => import_handlebars.default.c
24083
24137
  })(variables);
24084
24138
 
24085
24139
  // src/runSessionCommand.ts
24086
- import { setTimeout as delay2 } from "node:timers/promises";
24140
+ import { setTimeout as delay3 } from "node:timers/promises";
24087
24141
  import { realpathSync } from "node:fs";
24088
24142
  import { homedir as homedir6 } from "node:os";
24089
24143
  import { join as join6 } from "node:path";
@@ -24141,41 +24195,8 @@ var findWorkerType = (types, id) => {
24141
24195
  return type;
24142
24196
  };
24143
24197
 
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
- };
24198
+ // src/recoverTerminalSession.ts
24199
+ import { setTimeout as delay2 } from "node:timers/promises";
24179
24200
 
24180
24201
  // src/tmuxSession.ts
24181
24202
  import { spawnSync } from "node:child_process";
@@ -24258,6 +24279,79 @@ var attachTmuxSession = (name) => {
24258
24279
  throw new Error("Could not attach to the tmux session.");
24259
24280
  };
24260
24281
 
24282
+ // src/recoverTerminalSession.ts
24283
+ var recoverTerminalSession = async (session, worker) => {
24284
+ if (session.endedAt || !session.startedAt)
24285
+ throw new Error("Session is not an unexpectedly exited session.");
24286
+ if (tmuxSessionExists(session.terminalSessionId))
24287
+ throw new Error("Session is already running.");
24288
+ if (session.workerTypeId !== "shell" && (!worker?.resumeCommand || !session.workerSessionId))
24289
+ throw new Error(
24290
+ "Worker has no configured resume command or conversation ID."
24291
+ );
24292
+ if (worker && !/{{\s*prompt\s*}}/.test(worker.resumeCommand))
24293
+ throw new Error("Resume command must include {{prompt}} to continue work.");
24294
+ if (worker && !/{{\s*workerSessionId\s*}}/.test(worker.resumeCommand))
24295
+ throw new Error(
24296
+ "Resume command must include {{workerSessionId}} to resume the original conversation."
24297
+ );
24298
+ const command = worker ? renderWorkerCommand(worker.resumeCommand, {
24299
+ prompt: `Your terminal was interrupted. Review task ${session.taskId ?? ""} and your prior progress, then continue working. Respect any existing blockers and do not repeat completed actions.`,
24300
+ sessionId: session.id,
24301
+ workerSessionId: session.workerSessionId ?? "",
24302
+ taskId: session.taskId ?? "",
24303
+ directory: session.directory,
24304
+ terminalSessionId: session.terminalSessionId
24305
+ }) : null;
24306
+ startTmuxSession(
24307
+ session.terminalSessionId,
24308
+ session.directory,
24309
+ command,
24310
+ session.id,
24311
+ session.machineId
24312
+ );
24313
+ await delay2(250);
24314
+ if (!tmuxSessionExists(session.terminalSessionId))
24315
+ throw new Error("Worker exited immediately after recovery.");
24316
+ return { id: session.id, recovered: true };
24317
+ };
24318
+
24319
+ // ../../packages/core/src/work/data/resolveDefaultWorkerTypeId.ts
24320
+ var resolveDefaultWorkerTypeId = (types, current) => types.some((type) => type.id === current) ? current : types[0]?.id ?? null;
24321
+
24322
+ // src/prepareTaskWorkerInstance.ts
24323
+ var prepareTaskWorkerInstance = async (taskId, instanceSlug) => {
24324
+ external_exports.uuid().parse(taskId);
24325
+ const config2 = await createWorkClient().config.read.query();
24326
+ const workerTypeId = resolveDefaultWorkerTypeId(
24327
+ config2.workerTypes ?? [],
24328
+ config2.defaultWorkerTypeId
24329
+ );
24330
+ if (!workerTypeId) throw new Error("Configure a default worker type first.");
24331
+ findWorkerType(config2.workerTypes ?? [], workerTypeId);
24332
+ const context = await readTaskLaunchContext(taskId);
24333
+ if (!["draft", "queue"].includes(context.task.status))
24334
+ throw new Error(
24335
+ "Only an unstarted Draft or Queue task can start a worker."
24336
+ );
24337
+ if (context.task.blocked || context.task.ownership?.worker || context.task.workerId)
24338
+ throw new Error("Task is blocked or already assigned to a worker.");
24339
+ if (context.state === "missing_instance") {
24340
+ if (!context.project) throw new Error("Task has no project to prepare.");
24341
+ await runInstanceCommand([
24342
+ "prepare",
24343
+ "--project",
24344
+ context.project.id,
24345
+ "--task",
24346
+ context.task.slug,
24347
+ ...instanceSlug ? ["--slug", instanceSlug] : []
24348
+ ]);
24349
+ } else if (context.state !== "ready") {
24350
+ throw new Error(`Task instance is ${context.state}; cannot start worker.`);
24351
+ }
24352
+ return workerTypeId;
24353
+ };
24354
+
24261
24355
  // src/runSessionCommand.ts
24262
24356
  var runSessionCommand = async (args2) => {
24263
24357
  const [action, value, agentValue, instanceSlug, ...extra] = args2;
@@ -24283,6 +24377,27 @@ var runSessionCommand = async (args2) => {
24283
24377
  live: session.machineId === machine.id ? tmuxSessionExists(session.terminalSessionId) : null
24284
24378
  };
24285
24379
  }
24380
+ if (action === "recover" && value && !agentValue) {
24381
+ const session = await client.terminalSession.read.query(
24382
+ external_exports.uuid().parse(value)
24383
+ );
24384
+ const config2 = await client.config.read.query();
24385
+ const machine = resolveSessionMachine(config2);
24386
+ if (session.machineId !== machine.id)
24387
+ throw new Error("Session belongs to another work machine.");
24388
+ if (!session.taskId)
24389
+ throw new Error("Only task sessions can be recovered.");
24390
+ const tasks = await client.task.list.query({ status: "working" });
24391
+ const problem = getSessionRecoveryProblem(
24392
+ session,
24393
+ tasks.find((task) => task.uuid === session.taskId)
24394
+ );
24395
+ if (problem) throw new Error(problem);
24396
+ return recoverTerminalSession(
24397
+ session,
24398
+ session.workerTypeId === "shell" ? null : findWorkerType(config2.workerTypes ?? [], session.workerTypeId)
24399
+ );
24400
+ }
24286
24401
  if (action === "attach" && value && !agentValue) {
24287
24402
  const session = await client.terminalSession.read.query(
24288
24403
  external_exports.uuid().parse(value)
@@ -24354,7 +24469,7 @@ var runSessionCommand = async (args2) => {
24354
24469
  session.id,
24355
24470
  machine.id
24356
24471
  );
24357
- await delay2(250);
24472
+ await delay3(250);
24358
24473
  if (!tmuxSessionExists(session.terminalSessionId))
24359
24474
  throw new Error("Agent process exited immediately after launch.");
24360
24475
  return client.terminalSession.update.mutate({
@@ -24460,7 +24575,7 @@ var runSessionCommand = async (args2) => {
24460
24575
  session.id,
24461
24576
  context.machine.id
24462
24577
  );
24463
- await delay2(250);
24578
+ await delay3(250);
24464
24579
  if (!tmuxSessionExists(session.terminalSessionId))
24465
24580
  throw new Error("Agent process exited immediately after launch.");
24466
24581
  } catch (error51) {
@@ -24479,7 +24594,7 @@ var runSessionCommand = async (args2) => {
24479
24594
  });
24480
24595
  }
24481
24596
  throw new Error(
24482
- "Use berth session list|read|status|create|start|launch|attach|kill|delete|register|schema. Run berth --help."
24597
+ "Use berth session list|read|status|create|start|launch|recover|attach|kill|delete|register|schema. Run berth --help."
24483
24598
  );
24484
24599
  };
24485
24600
 
@@ -24573,6 +24688,9 @@ var runRoleCommand = async (args2) => {
24573
24688
  // ../../packages/core/src/work/schemas/signOffRequestSchema.ts
24574
24689
  var signOffRequestSchema = external_exports.strictObject({
24575
24690
  slug: taskSlugSchema,
24691
+ sender: external_exports.string().trim().min(1).optional().describe(
24692
+ "Requesting role within the project; defaults to delivery-owner."
24693
+ ),
24576
24694
  deliveryAttemptId: external_exports.uuid().optional().describe(
24577
24695
  "Latest recorded delivery attempt; omitted selects latest. Legacy issued requests remain valid without an attempt."
24578
24696
  ),
@@ -25223,6 +25341,7 @@ berth session status <session-uuid>
25223
25341
  berth session create [shell|worker-type-id] [directory]
25224
25342
  berth session start <task-uuid> [instance-slug]
25225
25343
  berth session launch <task-uuid> <worker-type-id> [instance-slug]
25344
+ berth session recover <session-uuid>
25226
25345
  berth session attach <session-uuid>
25227
25346
  berth session kill <session-uuid>
25228
25347
  berth session delete <session-uuid>
@@ -25249,7 +25368,8 @@ berth message create '{"recipient":{"name":"delivery-owner","projectId":"bond"},
25249
25368
  berth message update '{"id":"<message-id>","recipient":{"name":"delivery-owner","projectId":"bond"}}'
25250
25369
  berth message read <message-id>
25251
25370
  berth message list '{"recipient":{"name":"delivery-owner","projectId":"bond"}}'
25252
- berth message schema [create|update|list]
25371
+ berth message append '{"id":"<message-id>","expectedLength":1,"actor":"delivery-owner","action":"closed"}'
25372
+ berth message schema [create|update|append|list]
25253
25373
  Messages are stored records. Recipient, type, priority and status are independent.
25254
25374
  List includes stored messages; deleting one never deletes its task or blocker.
25255
25375
 
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.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {