@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.
- package/dist/berth.js +164 -44
- 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.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
|
|
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
|
-
//
|
|
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
|
-
};
|
|
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
|
|
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
|
|
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
|
|
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
|
|