@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.
- package/dist/berth.js +155 -45
- 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.
|
|
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
|
|
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
|
-
//
|
|
24145
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|