@lumi.ai/runner 0.5.6 → 0.5.7

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/cli.js +67 -43
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  collection as collection5,
10
10
  deleteField as deleteField2,
11
11
  doc as doc7,
12
- getDoc as getDoc5,
12
+ getDoc as getDoc6,
13
13
  getDocs as getDocs4,
14
14
  onSnapshot as onSnapshot2,
15
15
  orderBy as orderBy4,
@@ -242,6 +242,9 @@ function clampStepText(text, max) {
242
242
  function firstLine(text) {
243
243
  return text.split("\n").find((l) => l.trim()) ?? "";
244
244
  }
245
+ function step(kind, label, detail) {
246
+ return detail ? { kind, label, detail } : { kind, label };
247
+ }
245
248
  function str(input, key) {
246
249
  if (!input || typeof input !== "object")
247
250
  return void 0;
@@ -354,36 +357,28 @@ function builtinDetail(tool, input) {
354
357
  function describeToolUse(name, input, opts) {
355
358
  if (name.startsWith(WORKSPACE_PREFIX)) {
356
359
  const tool = name.slice(WORKSPACE_PREFIX.length);
357
- return {
358
- kind: "tool",
359
- label: WORKSPACE_LABELS[tool] ?? "Working",
360
- detail: workspaceDetail(tool, input)
361
- };
360
+ return step("tool", WORKSPACE_LABELS[tool] ?? "Working", workspaceDetail(tool, input));
362
361
  }
363
362
  if (name.startsWith(MCP_PREFIX)) {
364
363
  const rest = name.slice(MCP_PREFIX.length);
365
364
  const split = rest.indexOf("__");
366
365
  const key = split < 0 ? rest : rest.slice(0, split);
367
366
  const tool = split < 0 ? void 0 : rest.slice(split + 2);
368
- return {
369
- kind: "tool",
370
- label: `Using ${opts?.mcpNames?.[key] ?? key}`,
371
- detail: tool || void 0
372
- };
367
+ return step("tool", `Using ${opts?.mcpNames?.[key] ?? key}`, tool);
373
368
  }
374
369
  const label = BUILTIN_LABELS[name];
375
370
  if (label)
376
- return { kind: "tool", label, detail: builtinDetail(name, input) };
377
- return { kind: "tool", label: "Working" };
371
+ return step("tool", label, builtinDetail(name, input));
372
+ return step("tool", "Working");
378
373
  }
379
374
  function describeAssistantText(text) {
380
- return { kind: "saying", label: "Working it out", detail: firstLine(text) || void 0 };
375
+ return step("saying", "Working it out", firstLine(text));
381
376
  }
382
- function pushJobStep(progress, step, max = MAX_JOB_STEPS) {
383
- const steps = [...progress?.steps ?? [], step];
377
+ function pushJobStep(progress, step2, max = MAX_JOB_STEPS) {
378
+ const steps = [...progress?.steps ?? [], step2];
384
379
  return {
385
380
  seq: (progress?.seq ?? 0) + 1,
386
- updatedAt: step.at,
381
+ updatedAt: step2.at,
387
382
  // Keep the NEWEST `max`, oldest first — a reader renders top to bottom.
388
383
  steps: steps.slice(Math.max(0, steps.length - max))
389
384
  };
@@ -739,7 +734,7 @@ function mcpUrl(config2) {
739
734
  }
740
735
 
741
736
  // src/version.ts
742
- var RUNNER_VERSION = true ? "0.5.6" : "0.0.0-dev";
737
+ var RUNNER_VERSION = true ? "0.5.7" : "0.0.0-dev";
743
738
 
744
739
  // src/auth.ts
745
740
  import { signInWithCustomToken } from "firebase/auth";
@@ -1777,7 +1772,7 @@ async function runSession(input, bin, dirs) {
1777
1772
  if (event.type === "result") resultEvent = event;
1778
1773
  if (event.type === "assistant") input.log("claude: assistant turn");
1779
1774
  if (input.onStep) {
1780
- for (const step of stepsFromClaudeEvent(event, { mcpNames })) input.onStep(step);
1775
+ for (const step2 of stepsFromClaudeEvent(event, { mcpNames })) input.onStep(step2);
1781
1776
  }
1782
1777
  } catch {
1783
1778
  }
@@ -2054,6 +2049,7 @@ import {
2054
2049
  collection as collection4,
2055
2050
  deleteField,
2056
2051
  doc as doc6,
2052
+ getDoc as getDoc5,
2057
2053
  getDocs as getDocs3,
2058
2054
  limit as fsLimit,
2059
2055
  orderBy as orderBy3,
@@ -2190,16 +2186,27 @@ ${error.slice(0, 800)}
2190
2186
  }
2191
2187
  async function markTaskStopped(db, shipId, job, stoppedBy) {
2192
2188
  const taskRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.tasks, job.taskId);
2189
+ const who = await actorName(db, shipId, stoppedBy);
2193
2190
  await addDoc(collection4(taskRef, COLLECTIONS.activity), {
2194
2191
  author: { type: "agent", id: job.agentId },
2195
2192
  createdAt: Date.now(),
2196
2193
  kind: "comment",
2197
- content: `This run was stopped by ${stoppedBy} before it finished. The task is unchanged.`
2194
+ content: `This run was stopped by ${who} before it finished. The task is unchanged.`
2198
2195
  });
2199
2196
  }
2197
+ async function actorName(db, shipId, actorId) {
2198
+ try {
2199
+ const snap = await getDoc5(doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.members, actorId));
2200
+ const m = snap.data();
2201
+ return m?.displayName || m?.email || actorId;
2202
+ } catch {
2203
+ return actorId;
2204
+ }
2205
+ }
2200
2206
  async function markChatStopped(db, shipId, job, stoppedBy) {
2201
2207
  const chatRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.chats, job.chatId);
2202
- const content = `Stopped by ${stoppedBy} before I finished. Write again to start a fresh run.`;
2208
+ const who = await actorName(db, shipId, stoppedBy);
2209
+ const content = `Stopped by ${who} before I finished. Write again to start a fresh run.`;
2203
2210
  await addDoc(collection4(chatRef, COLLECTIONS.chatMessages), {
2204
2211
  author: { type: "agent", id: job.agentId },
2205
2212
  content,
@@ -2264,16 +2271,24 @@ async function ensureChatReply(db, shipId, job, resultText) {
2264
2271
  // src/jobs/progress.ts
2265
2272
  var PROGRESS_FLUSH_MS = 3e3;
2266
2273
  var PROGRESS_DENIALS_BEFORE_PROBE = 2;
2267
- function redactStep(step, secrets) {
2268
- const label = clampStepText(redactTranscript(step.label, secrets), MAX_STEP_LABEL);
2269
- if (step.detail === void 0) return { ...step, label };
2270
- const detail = clampStepText(redactTranscript(step.detail, secrets), MAX_STEP_DETAIL);
2271
- return detail ? { ...step, label, detail } : { at: step.at, kind: step.kind, label };
2274
+ function redactStep(step2, secrets) {
2275
+ const label = clampStepText(redactTranscript(step2.label, secrets), MAX_STEP_LABEL);
2276
+ const base = { at: step2.at, kind: step2.kind, label };
2277
+ if (step2.detail === void 0) return base;
2278
+ const detail = clampStepText(redactTranscript(step2.detail, secrets), MAX_STEP_DETAIL);
2279
+ return detail ? { ...base, detail } : base;
2280
+ }
2281
+ function errorCode(error) {
2282
+ return error?.code ?? "";
2272
2283
  }
2273
2284
  function isDenied(error) {
2274
- const code = error?.code ?? "";
2285
+ const code = errorCode(error);
2275
2286
  return code === "permission-denied" || code.endsWith("/permission-denied");
2276
2287
  }
2288
+ function isUnwritable(error) {
2289
+ const code = errorCode(error);
2290
+ return code === "invalid-argument" || code.endsWith("/invalid-argument");
2291
+ }
2277
2292
  function createProgressWriter(deps) {
2278
2293
  const now = deps.now ?? Date.now;
2279
2294
  const flushMs = deps.flushMs ?? PROGRESS_FLUSH_MS;
@@ -2292,12 +2307,18 @@ function createProgressWriter(deps) {
2292
2307
  }
2293
2308
  if (closed || inFlight || pending.length === 0) return;
2294
2309
  const secrets = deps.secrets();
2295
- for (const step of pending) progress = pushJobStep(progress, redactStep(step, secrets));
2310
+ for (const step2 of pending) progress = pushJobStep(progress, redactStep(step2, secrets));
2296
2311
  pending = [];
2297
2312
  lastWriteAt = now();
2298
2313
  inFlight = true;
2299
2314
  const snapshot = progress;
2300
- void deps.write(snapshot).then(() => {
2315
+ let written;
2316
+ try {
2317
+ written = deps.write(snapshot);
2318
+ } catch (e) {
2319
+ written = Promise.reject(e);
2320
+ }
2321
+ void written.then(() => {
2301
2322
  denials = 0;
2302
2323
  }).catch((e) => {
2303
2324
  if (isDenied(e)) {
@@ -2309,9 +2330,12 @@ function createProgressWriter(deps) {
2309
2330
  return;
2310
2331
  }
2311
2332
  denials = 0;
2333
+ if (isUnwritable(e)) closed = true;
2312
2334
  if (!loggedFailure) {
2313
2335
  loggedFailure = true;
2314
- deps.log(`progress write failed (further ones silent): ${e instanceof Error ? e.message : e}`);
2336
+ deps.log(
2337
+ `progress write failed${closed ? " \u2014 live steps are off for this job" : " (further ones silent)"}: ${e instanceof Error ? e.message : e}`
2338
+ );
2315
2339
  }
2316
2340
  }).finally(() => {
2317
2341
  inFlight = false;
@@ -2329,9 +2353,9 @@ function createProgressWriter(deps) {
2329
2353
  timer.unref?.();
2330
2354
  }
2331
2355
  return {
2332
- push(step) {
2356
+ push(step2) {
2333
2357
  if (closed) return;
2334
- pending.push({ ...step, at: now() });
2358
+ pending.push({ ...step2, at: now() });
2335
2359
  schedule();
2336
2360
  },
2337
2361
  stop() {
@@ -2415,7 +2439,7 @@ async function startDaemon() {
2415
2439
  try {
2416
2440
  for (const shipId of [...serving]) {
2417
2441
  try {
2418
- const snap = await getDoc5(shipRunnerRef(shipId));
2442
+ const snap = await getDoc6(shipRunnerRef(shipId));
2419
2443
  if (!snap.exists()) {
2420
2444
  forgetShipLocally(shipId, "a captain removed this machine on the Daemons page");
2421
2445
  continue;
@@ -2741,7 +2765,7 @@ async function startDaemon() {
2741
2765
  void (async () => {
2742
2766
  if (slot.stop) return;
2743
2767
  try {
2744
- const snap = await getDoc5(
2768
+ const snap = await getDoc6(
2745
2769
  doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id)
2746
2770
  );
2747
2771
  const fresh = snap.data();
@@ -2796,7 +2820,7 @@ async function startDaemon() {
2796
2820
  onDenied: () => {
2797
2821
  void (async () => {
2798
2822
  try {
2799
- const snap = await getDoc5(
2823
+ const snap = await getDoc6(
2800
2824
  doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id)
2801
2825
  );
2802
2826
  const fresh = snap.data();
@@ -2889,7 +2913,7 @@ async function startDaemon() {
2889
2913
  // §15.36. The driver emits Crew's step vocabulary; the writer coalesces, redacts and
2890
2914
  // persists. A throw here must never reach the driver's stdout loop, which is why `push`
2891
2915
  // swallows everything.
2892
- onStep: (step) => progress.push(step)
2916
+ onStep: (step2) => progress.push(step2)
2893
2917
  });
2894
2918
  transcript = redactTranscript(session.transcript, knownSecrets);
2895
2919
  usage = session.usage;
@@ -3335,7 +3359,7 @@ function setParallel(config2, value, ship2) {
3335
3359
 
3336
3360
  // src/cli/commands/doctor.ts
3337
3361
  import { spawnSync as spawnSync2 } from "node:child_process";
3338
- import { collection as collection6, doc as doc8, getDoc as getDoc6, getDocs as getDocs5 } from "firebase/firestore";
3362
+ import { collection as collection6, doc as doc8, getDoc as getDoc7, getDocs as getDocs5 } from "firebase/firestore";
3339
3363
 
3340
3364
  // src/service.ts
3341
3365
  import { spawnSync } from "node:child_process";
@@ -3754,7 +3778,7 @@ async function checkShips(config2) {
3754
3778
  continue;
3755
3779
  }
3756
3780
  try {
3757
- const snap = await getDoc6(
3781
+ const snap = await getDoc7(
3758
3782
  doc8(fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.runners, config2.runnerId)
3759
3783
  );
3760
3784
  if (!snap.exists()) {
@@ -4179,7 +4203,7 @@ async function runUninstall(options) {
4179
4203
  }
4180
4204
 
4181
4205
  // src/cli/commands/ship.ts
4182
- import { doc as doc9, getDoc as getDoc7 } from "firebase/firestore";
4206
+ import { doc as doc9, getDoc as getDoc8 } from "firebase/firestore";
4183
4207
  async function listMyShips() {
4184
4208
  const config2 = requireConfig();
4185
4209
  const ids = Object.keys(config2.shipKeys ?? {});
@@ -4187,7 +4211,7 @@ async function listMyShips() {
4187
4211
  ids.map(async (id) => {
4188
4212
  try {
4189
4213
  const { fb } = await openShipSession(id);
4190
- const snap = await getDoc7(doc9(fb.db, COLLECTIONS.ships, id));
4214
+ const snap = await getDoc8(doc9(fb.db, COLLECTIONS.ships, id));
4191
4215
  return { id, ...snap.data() };
4192
4216
  } catch {
4193
4217
  return null;
@@ -4314,7 +4338,7 @@ import {
4314
4338
  collection as collection7,
4315
4339
  doc as doc10,
4316
4340
  getCountFromServer as getCountFromServer2,
4317
- getDoc as getDoc8,
4341
+ getDoc as getDoc9,
4318
4342
  getDocs as getDocs6,
4319
4343
  query as query5,
4320
4344
  where as where5
@@ -4352,7 +4376,7 @@ async function runStatus() {
4352
4376
  continue;
4353
4377
  }
4354
4378
  const shipRef = doc10(fb.db, COLLECTIONS.ships, shipId);
4355
- const mirrorSnap = await getDoc8(doc10(shipRef, COLLECTIONS.runners, config2.runnerId));
4379
+ const mirrorSnap = await getDoc9(doc10(shipRef, COLLECTIONS.runners, config2.runnerId));
4356
4380
  const mirror = mirrorSnap.data();
4357
4381
  let queued = 0;
4358
4382
  try {
@@ -4363,7 +4387,7 @@ async function runStatus() {
4363
4387
  } catch {
4364
4388
  queued = 0;
4365
4389
  }
4366
- const usageSnap = await getDoc8(doc10(shipRef, COLLECTIONS.usageDaily, utcDay(Date.now())));
4390
+ const usageSnap = await getDoc9(doc10(shipRef, COLLECTIONS.usageDaily, utcDay(Date.now())));
4367
4391
  const today = { ...EMPTY_USAGE_TOTALS, ...usageSnap.data()?.totals ?? {} };
4368
4392
  const now = Date.now();
4369
4393
  const limitsSnap = await getDocs6(collection7(shipRef, COLLECTIONS.engineLimits));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumi.ai/runner",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "type": "module",
5
5
  "description": "Lumi Crew runner daemon — claims jobs from your Ships and executes them as headless Claude sessions on your own machine.",
6
6
  "//name": "The ONLY package in this monorepo published to the public registry, so it is the one that does not follow the internal @lumi/crew-* convention: `@lumi` is not a scope we own, `@lumi.ai` is (the npm org). The workspace DIRECTORY stays packages/crew/runner — renaming the package is not renaming the folder.",