@lumi.ai/runner 0.15.16 → 0.15.18

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 +98 -92
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -8,8 +8,8 @@ import os4 from "node:os";
8
8
  import {
9
9
  collection as collection7,
10
10
  deleteField as deleteField2,
11
- doc as doc8,
12
- getDoc as getDoc7,
11
+ doc as doc7,
12
+ getDoc as getDoc6,
13
13
  getDocs as getDocs6,
14
14
  onSnapshot as onSnapshot2,
15
15
  orderBy as orderBy4,
@@ -125,6 +125,25 @@ function effectiveAgentTools(agent) {
125
125
  };
126
126
  }
127
127
 
128
+ // ../shared/dist/assistant.js
129
+ var ASSISTANT_ACTOR_ID = "assistant";
130
+ var ASSISTANT_ACTOR = Object.freeze({
131
+ type: "agent",
132
+ id: ASSISTANT_ACTOR_ID
133
+ });
134
+ var ASSISTANT_PROFILE = Object.freeze({
135
+ autonomy: 2,
136
+ riskyActions: [],
137
+ tools: Object.freeze({
138
+ workspaceMcp: true,
139
+ github: Object.freeze({ enabled: false, repos: [] }),
140
+ bash: false,
141
+ webSearch: false,
142
+ extraMcps: [],
143
+ browser: false
144
+ })
145
+ });
146
+
128
147
  // ../shared/dist/browser.js
129
148
  var BROWSER_ONLINE_WINDOW_MS = 9e4;
130
149
  var DEFAULT_BROWSER_CONSENT_MS = 60 * 60 * 1e3;
@@ -825,21 +844,6 @@ function firstStatusIn(statuses, category) {
825
844
  var RUNNER_OFFLINE_AFTER_MS = 2 * 60 * 1e3;
826
845
 
827
846
  // ../shared/dist/secrets.js
828
- var SECRET_DOCS = {
829
- /** Captain-provided runner credentials (this file's shape). */
830
- runner: "runner",
831
- /** `{ tokenHash }` for the Workspace MCP bearer — backend-only, never client-readable. */
832
- mcp: "mcp",
833
- /**
834
- * `{ runnerId, keyHash }` for THIS Ship's daemon credential (PRD §8.1) — backend-only.
835
- *
836
- * One document, not one per machine, because a Ship runs one daemon: issuing a key is
837
- * therefore also how the previous one is revoked, exactly like rotating the MCP bearer.
838
- * Only `docId == 'runner'` is released by packages/crew/firestore.rules, so this falls
839
- * through to deny for every client without needing a rule of its own.
840
- */
841
- runnerKey: "runnerKey"
842
- };
843
847
  function missingSecretsFor(engineId, secrets) {
844
848
  const bag = secrets;
845
849
  return getEngine(engineId).requiredSecrets.filter((req) => !bag?.[req.key]).map((req) => req.label);
@@ -894,7 +898,7 @@ var WEBHOOK_ACK_REASONS = {
894
898
  };
895
899
 
896
900
  // ../shared/dist/workflow.js
897
- var WORKFLOW_SENTINELS = ["generic", "assigned", "activity", "chat"];
901
+ var WORKFLOW_SENTINELS = ["generic", "assigned", "activity", "chat", "assistant"];
898
902
  function isWorkflowSentinel(id) {
899
903
  return WORKFLOW_SENTINELS.includes(id);
900
904
  }
@@ -1019,7 +1023,7 @@ function mcpUrl(config2) {
1019
1023
  }
1020
1024
 
1021
1025
  // src/version.ts
1022
- var RUNNER_VERSION = true ? "0.15.16" : "0.0.0-dev";
1026
+ var RUNNER_VERSION = true ? "0.15.18" : "0.0.0-dev";
1023
1027
 
1024
1028
  // src/auth.ts
1025
1029
  import { signInWithCustomToken } from "firebase/auth";
@@ -2044,6 +2048,15 @@ function normalizeClaudeUsage(resultEvent, model, fallbackDurationS) {
2044
2048
  const costReported = typeof result.total_cost_usd === "number";
2045
2049
  const usage = {
2046
2050
  engine: CLAUDE_DRIVER_ID,
2051
+ // The model the session was LAUNCHED with (§15.69), which is the parameter this function
2052
+ // already receives and used to drop on the floor. It is `engine`'s sibling: the pair is what
2053
+ // identifies a rate, and it is the only place the workspace assistant's model can come from,
2054
+ // since the assistant has no `agents/{id}` document to read one off.
2055
+ //
2056
+ // OUR REGISTRY'S ALIAS, not a resolved API id. The driver reads only the CLI's final `result`
2057
+ // event, and this file's header is explicit that the CLI is not a contract — so a reader
2058
+ // renders the registry LABEL and never invents a full version string.
2059
+ model,
2047
2060
  inputTokens,
2048
2061
  cachedInputTokens: cacheRead,
2049
2062
  outputTokens,
@@ -2392,13 +2405,12 @@ async function resolveGithubToken(input) {
2392
2405
  throw e;
2393
2406
  }
2394
2407
  }
2395
- if (integration && !input.githubPat) {
2408
+ if (integration) {
2396
2409
  return {
2397
2410
  source: "none",
2398
2411
  unavailable: `The Ship's GitHub connection is ${integration.status}. A captain can fix it in Ship Settings \u203A GitHub.`
2399
2412
  };
2400
2413
  }
2401
- if (input.githubPat) return { token: input.githubPat, source: "pat" };
2402
2414
  return null;
2403
2415
  }
2404
2416
 
@@ -2710,17 +2722,8 @@ function serversNeedingProbe(servers) {
2710
2722
  );
2711
2723
  }
2712
2724
 
2713
- // src/jobs/secrets.ts
2714
- import { doc as doc4, getDoc as getDoc4 } from "firebase/firestore";
2715
- async function loadRunnerSecrets(db, shipId) {
2716
- const snap = await withFirestoreRetry(
2717
- () => getDoc4(doc4(db, COLLECTIONS.ships, shipId, COLLECTIONS.secrets, SECRET_DOCS.runner))
2718
- );
2719
- return snap.exists() ? snap.data() : null;
2720
- }
2721
-
2722
2725
  // src/jobs/credentials.ts
2723
- import { collection as collection4, doc as doc5, getDoc as getDoc5, getDocs as getDocs4 } from "firebase/firestore";
2726
+ import { collection as collection4, doc as doc4, getDoc as getDoc4, getDocs as getDocs4 } from "firebase/firestore";
2724
2727
  async function loadShipCredentials(db, shipId) {
2725
2728
  const snap = await withFirestoreRetry(
2726
2729
  () => getDocs4(collection4(db, COLLECTIONS.ships, shipId, COLLECTIONS.credentials))
@@ -2729,35 +2732,41 @@ async function loadShipCredentials(db, shipId) {
2729
2732
  }
2730
2733
  async function loadCredentialToken(db, shipId, credentialId) {
2731
2734
  const snap = await withFirestoreRetry(
2732
- () => getDoc5(doc5(db, COLLECTIONS.ships, shipId, COLLECTIONS.secrets, credentialId))
2735
+ () => getDoc4(doc4(db, COLLECTIONS.ships, shipId, COLLECTIONS.secrets, credentialId))
2733
2736
  );
2734
2737
  if (!snap.exists()) return null;
2735
2738
  const token = snap.data().token;
2736
2739
  return typeof token === "string" && token.trim() ? token : null;
2737
2740
  }
2738
2741
  async function resolveJobSecrets(input) {
2739
- const { agent, credentials, runnerSecrets } = input;
2742
+ const { agent, credentials } = input;
2740
2743
  const engineId = agentEngine(agent);
2741
2744
  const resolved = resolveAgentCredential(agent, credentials, DEFAULT_ENGINE_ID);
2742
- if (resolved === void 0) return { secrets: runnerSecrets };
2745
+ if (resolved === void 0) {
2746
+ return {
2747
+ secrets: null,
2748
+ problem: `This workspace has no AI credential for ${engineId}. Add one in Settings \u203A AI credentials and the crew starts taking work again.`
2749
+ };
2750
+ }
2743
2751
  if (resolved === null) {
2744
2752
  return {
2745
- secrets: runnerSecrets,
2753
+ secrets: null,
2746
2754
  problem: `This agent is set to an AI credential that no longer exists on the Ship. Point it at another one in Settings \u203A AI credentials, or add the credential back.`
2747
2755
  };
2748
2756
  }
2749
2757
  const token = await loadCredentialToken(input.db, input.shipId, resolved.id);
2750
2758
  if (!token) {
2751
2759
  return {
2752
- secrets: runnerSecrets,
2760
+ secrets: null,
2753
2761
  problem: `The AI credential "${resolved.label}" has no token stored. Erase it in Settings \u203A AI credentials and add it again.`
2754
2762
  };
2755
2763
  }
2756
2764
  const key = getEngine(engineId).requiredSecrets[0]?.key;
2757
- if (!key) return { secrets: runnerSecrets };
2765
+ if (!key) return { secrets: null };
2758
2766
  return {
2759
2767
  secrets: {
2760
- ...runnerSecrets ?? { updatedAt: resolved.updatedAt, updatedBy: resolved.createdBy },
2768
+ updatedAt: resolved.updatedAt,
2769
+ updatedBy: resolved.createdBy,
2761
2770
  [key]: token,
2762
2771
  [`${key}Tail`]: resolved.tail
2763
2772
  }
@@ -2768,7 +2777,7 @@ async function resolveJobSecrets(input) {
2768
2777
  import {
2769
2778
  collection as collection5,
2770
2779
  deleteDoc,
2771
- doc as doc6,
2780
+ doc as doc5,
2772
2781
  onSnapshot,
2773
2782
  setDoc
2774
2783
  } from "firebase/firestore";
@@ -2797,7 +2806,7 @@ function nextResetAt(limits, now) {
2797
2806
  return soonest;
2798
2807
  }
2799
2808
  function limitRef(db, shipId, engineId) {
2800
- return doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.engineLimits, engineId);
2809
+ return doc5(db, COLLECTIONS.ships, shipId, COLLECTIONS.engineLimits, engineId);
2801
2810
  }
2802
2811
  async function noteEngineLimit(db, shipId, engineId, limit3, runnerId) {
2803
2812
  await setDoc(limitRef(db, shipId, engineId), {
@@ -2896,8 +2905,8 @@ import {
2896
2905
  addDoc,
2897
2906
  collection as collection6,
2898
2907
  deleteField,
2899
- doc as doc7,
2900
- getDoc as getDoc6,
2908
+ doc as doc6,
2909
+ getDoc as getDoc5,
2901
2910
  getDocs as getDocs5,
2902
2911
  limit as fsLimit,
2903
2912
  orderBy as orderBy3,
@@ -2943,10 +2952,10 @@ function backstopReportContent(resultText2) {
2943
2952
  return { report: report4, summary };
2944
2953
  }
2945
2954
  async function finalizeJob(db, shipId, job, input) {
2946
- const shipRef = doc7(db, COLLECTIONS.ships, shipId);
2947
- const jobRef = doc7(shipRef, COLLECTIONS.jobs, job.id);
2955
+ const shipRef = doc6(db, COLLECTIONS.ships, shipId);
2956
+ const jobRef = doc6(shipRef, COLLECTIONS.jobs, job.id);
2948
2957
  const now = Date.now();
2949
- const usageRef = doc7(shipRef, COLLECTIONS.usageDaily, utcDay(now));
2958
+ const usageRef = doc6(shipRef, COLLECTIONS.usageDaily, utcDay(now));
2950
2959
  await runTransaction(db, async (tx) => {
2951
2960
  const usageSnap = await tx.get(usageRef);
2952
2961
  const jobSnap = await tx.get(jobRef);
@@ -3008,7 +3017,7 @@ async function finalizeJob(db, shipId, job, input) {
3008
3017
  }
3009
3018
  async function requeueForRetry(db, shipId, job, error) {
3010
3019
  await runTransaction(db, async (tx) => {
3011
- tx.update(doc7(db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
3020
+ tx.update(doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
3012
3021
  status: "queued",
3013
3022
  attempt: job.attempt + 1,
3014
3023
  error: error.slice(0, 1500),
@@ -3018,7 +3027,7 @@ async function requeueForRetry(db, shipId, job, error) {
3018
3027
  });
3019
3028
  }
3020
3029
  async function releaseJob(db, shipId, job, reason) {
3021
- await updateDoc(doc7(db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
3030
+ await updateDoc(doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
3022
3031
  status: "queued",
3023
3032
  runnerId: "",
3024
3033
  startedAt: 0,
@@ -3034,13 +3043,13 @@ async function releaseJob(db, shipId, job, reason) {
3034
3043
  });
3035
3044
  }
3036
3045
  async function markTaskFailed(db, shipId, job, error, statuses) {
3037
- const shipRef = doc7(db, COLLECTIONS.ships, shipId);
3038
- const taskRef = doc7(shipRef, COLLECTIONS.tasks, job.taskId);
3046
+ const shipRef = doc6(db, COLLECTIONS.ships, shipId);
3047
+ const taskRef = doc6(shipRef, COLLECTIONS.tasks, job.taskId);
3039
3048
  const now = Date.now();
3040
3049
  const failedStatus = firstStatusIn(statuses, "failed")?.id ?? "failed";
3041
3050
  await runTransaction(db, async (tx) => {
3042
3051
  tx.update(taskRef, { status: failedStatus, updatedAt: now });
3043
- tx.set(doc7(collection6(taskRef, COLLECTIONS.activity)), {
3052
+ tx.set(doc6(collection6(taskRef, COLLECTIONS.activity)), {
3044
3053
  author: { type: "agent", id: job.agentId },
3045
3054
  createdAt: now,
3046
3055
  kind: "comment",
@@ -3054,7 +3063,7 @@ ${error.slice(0, 800)}
3054
3063
  });
3055
3064
  }
3056
3065
  async function markTaskStopped(db, shipId, job, stoppedBy) {
3057
- const taskRef = doc7(db, COLLECTIONS.ships, shipId, COLLECTIONS.tasks, job.taskId);
3066
+ const taskRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.tasks, job.taskId);
3058
3067
  const who = await actorName(db, shipId, stoppedBy);
3059
3068
  await addDoc(collection6(taskRef, COLLECTIONS.activity), {
3060
3069
  author: { type: "agent", id: job.agentId },
@@ -3065,7 +3074,7 @@ async function markTaskStopped(db, shipId, job, stoppedBy) {
3065
3074
  }
3066
3075
  async function actorName(db, shipId, actorId) {
3067
3076
  try {
3068
- const snap = await getDoc6(doc7(db, COLLECTIONS.ships, shipId, COLLECTIONS.members, actorId));
3077
+ const snap = await getDoc5(doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.members, actorId));
3069
3078
  const m = snap.data();
3070
3079
  return m?.displayName || m?.email || actorId;
3071
3080
  } catch {
@@ -3073,19 +3082,23 @@ async function actorName(db, shipId, actorId) {
3073
3082
  }
3074
3083
  }
3075
3084
  async function markChatStopped(db, shipId, job, stoppedBy) {
3076
- const chatRef = doc7(db, COLLECTIONS.ships, shipId, COLLECTIONS.chats, job.chatId);
3085
+ const chatRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.chats, job.chatId);
3077
3086
  const who = await actorName(db, shipId, stoppedBy);
3078
3087
  const content = `Stopped by ${who} before I finished. Write again to start a fresh run.`;
3079
3088
  await addDoc(collection6(chatRef, COLLECTIONS.chatMessages), {
3080
3089
  author: { type: "agent", id: job.agentId },
3081
3090
  content,
3082
3091
  chars: content.length,
3083
- createdAt: Date.now()
3092
+ createdAt: Date.now(),
3093
+ // The run that produced this message (§15.69) — the same join key `chat_send` stamps. These
3094
+ // three runner-written messages are exactly the ones a person most wants the run meta on,
3095
+ // because they are the ones explaining why a reply is missing or short.
3096
+ jobId: job.id
3084
3097
  });
3085
3098
  }
3086
3099
  async function markChatFailed(db, shipId, job, error) {
3087
- const shipRef = doc7(db, COLLECTIONS.ships, shipId);
3088
- const chatRef = doc7(shipRef, COLLECTIONS.chats, job.chatId);
3100
+ const shipRef = doc6(db, COLLECTIONS.ships, shipId);
3101
+ const chatRef = doc6(shipRef, COLLECTIONS.chats, job.chatId);
3089
3102
  const now = Date.now();
3090
3103
  const content = `I could not finish replying \u2014 the run failed after ${job.attempt} attempt(s).
3091
3104
 
@@ -3098,7 +3111,8 @@ Write again to start a fresh run.`;
3098
3111
  author: { type: "agent", id: job.agentId },
3099
3112
  content,
3100
3113
  chars: content.length,
3101
- createdAt: now
3114
+ createdAt: now,
3115
+ jobId: job.id
3102
3116
  });
3103
3117
  }
3104
3118
  var REPLY_SCAN_LIMIT = 20;
@@ -3112,7 +3126,7 @@ function backstopReplyContent(resultText2) {
3112
3126
  return `${text.slice(0, MAX_CHAT_MESSAGE_CHARS - marker.length)}${marker}`;
3113
3127
  }
3114
3128
  async function ensureChatReply(db, shipId, job, resultText2) {
3115
- const chatRef = doc7(db, COLLECTIONS.ships, shipId, COLLECTIONS.chats, job.chatId);
3129
+ const chatRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.chats, job.chatId);
3116
3130
  const messagesCol = collection6(chatRef, COLLECTIONS.chatMessages);
3117
3131
  const snap = await getDocs5(
3118
3132
  query3(
@@ -3132,7 +3146,8 @@ async function ensureChatReply(db, shipId, job, resultText2) {
3132
3146
  author: { type: "agent", id: job.agentId },
3133
3147
  content,
3134
3148
  chars: content.length,
3135
- createdAt: Date.now()
3149
+ createdAt: Date.now(),
3150
+ jobId: job.id
3136
3151
  });
3137
3152
  return resultText2.trim() ? "posted-final-text" : "posted-silence-note";
3138
3153
  }
@@ -3937,7 +3952,7 @@ async function startDaemon() {
3937
3952
  const warnedUnapproved = /* @__PURE__ */ new Set();
3938
3953
  const shipRunnerRef = (shipId) => {
3939
3954
  const session = sess(shipId);
3940
- return doc8(session.fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.runners, session.runnerId);
3955
+ return doc7(session.fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.runners, session.runnerId);
3941
3956
  };
3942
3957
  const needsRefill = /* @__PURE__ */ new Set();
3943
3958
  let beating = false;
@@ -3948,7 +3963,7 @@ async function startDaemon() {
3948
3963
  try {
3949
3964
  for (const shipId of [...serving]) {
3950
3965
  try {
3951
- const snap = await getDoc7(shipRunnerRef(shipId));
3966
+ const snap = await getDoc6(shipRunnerRef(shipId));
3952
3967
  if (!snap.exists()) {
3953
3968
  forgetShipLocally(shipId, "a captain removed this machine on the Daemons page");
3954
3969
  continue;
@@ -4293,7 +4308,7 @@ async function startDaemon() {
4293
4308
  pending.set(`${shipId}/${job.id}`, { shipId, job });
4294
4309
  return null;
4295
4310
  }
4296
- const jobRef = doc8(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id);
4311
+ const jobRef = doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id);
4297
4312
  try {
4298
4313
  let claimed = null;
4299
4314
  await runTransaction2(sess(shipId).fb.db, async (tx) => {
@@ -4316,14 +4331,11 @@ async function startDaemon() {
4316
4331
  }
4317
4332
  async function setAgentStatus(shipId, agentId, status) {
4318
4333
  try {
4319
- await updateDoc2(doc8(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.agents, agentId), { status });
4334
+ await updateDoc2(doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.agents, agentId), { status });
4320
4335
  } catch (e) {
4321
4336
  log2(`agent status update failed: ${e instanceof Error ? e.message : e}`);
4322
4337
  }
4323
4338
  }
4324
- async function loadSecrets(shipId) {
4325
- return loadRunnerSecrets(sess(shipId).fb.db, shipId);
4326
- }
4327
4339
  async function processJob(shipId, queuedJob, slot) {
4328
4340
  const job = await claim(shipId, queuedJob);
4329
4341
  if (!job) return;
@@ -4363,8 +4375,8 @@ async function startDaemon() {
4363
4375
  void (async () => {
4364
4376
  if (slot.stop) return;
4365
4377
  try {
4366
- const snap = await getDoc7(
4367
- doc8(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id)
4378
+ const snap = await getDoc6(
4379
+ doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id)
4368
4380
  );
4369
4381
  const fresh = snap.data();
4370
4382
  if (!fresh?.stopRequestedAt || slot.stop) return;
@@ -4404,7 +4416,7 @@ async function startDaemon() {
4404
4416
  let statuses = DEFAULT_TASK_STATUSES;
4405
4417
  let knownSecrets = [];
4406
4418
  const progress = createProgressWriter({
4407
- write: (p) => updateDoc2(doc8(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
4419
+ write: (p) => updateDoc2(doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
4408
4420
  progress: p
4409
4421
  }),
4410
4422
  secrets: () => knownSecrets,
@@ -4418,8 +4430,8 @@ async function startDaemon() {
4418
4430
  onDenied: () => {
4419
4431
  void (async () => {
4420
4432
  try {
4421
- const snap = await getDoc7(
4422
- doc8(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id)
4433
+ const snap = await getDoc6(
4434
+ doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id)
4423
4435
  );
4424
4436
  const fresh = snap.data();
4425
4437
  if (!fresh || fresh.status !== "running" || fresh.runnerId !== sess(shipId).runnerId) {
@@ -4434,7 +4446,7 @@ async function startDaemon() {
4434
4446
  });
4435
4447
  progress.push({ kind: "start", label: "Starting up" });
4436
4448
  try {
4437
- let secrets = await loadSecrets(shipId);
4449
+ let secrets = null;
4438
4450
  const packed = target.kind === "chat" ? { kind: "chat", ctx: await loadChatContext(sess(shipId).fb.db, shipId, { ...job, chatId: target.chatId }) } : { kind: "task", ctx: await loadJobContext(sess(shipId).fb.db, shipId, { ...job, taskId: target.taskId }) };
4439
4451
  if (packed.kind === "task" && !isWorkflowSentinel(job.workflowId) && !packed.ctx.workflow) {
4440
4452
  log2(
@@ -4449,7 +4461,6 @@ async function startDaemon() {
4449
4461
  db: sess(shipId).fb.db,
4450
4462
  shipId,
4451
4463
  agent,
4452
- runnerSecrets: secrets,
4453
4464
  credentials: await loadShipCredentials(sess(shipId).fb.db, shipId).catch(() => [])
4454
4465
  });
4455
4466
  if (resolvedSecrets.problem) {
@@ -4487,8 +4498,7 @@ async function startDaemon() {
4487
4498
  idToken,
4488
4499
  shipId,
4489
4500
  jobId: job.id,
4490
- agent,
4491
- githubPat: secrets?.githubPat
4501
+ agent
4492
4502
  });
4493
4503
  githubToken = gh?.token;
4494
4504
  githubRepos = gh?.repos;
@@ -4528,7 +4538,6 @@ async function startDaemon() {
4528
4538
  idToken,
4529
4539
  secrets?.claudeToken,
4530
4540
  githubToken,
4531
- secrets?.githubPat,
4532
4541
  // §15.31. BOTH the composed header value and the bare credential — see
4533
4542
  // `mcpSecretsToRedact` for why one of them is not enough.
4534
4543
  ...mcpSecretsToRedact(extraMcpServers)
@@ -4713,7 +4722,6 @@ async function startDaemon() {
4713
4722
  if (serving.has(shipId)) await setAgentStatus(shipId, job.agentId, "idle");
4714
4723
  if (!shuttingDown) void heartbeat();
4715
4724
  }
4716
- await Promise.all([...serving].map((shipId) => loadSecrets(shipId).catch(() => null)));
4717
4725
  await heartbeat();
4718
4726
  const heartbeatTimer = setInterval(() => {
4719
4727
  void heartbeat();
@@ -5254,7 +5262,7 @@ function setParallel(config2, value, ship2) {
5254
5262
  import { spawnSync as spawnSync3 } from "node:child_process";
5255
5263
  import fs7 from "node:fs";
5256
5264
  import path8 from "node:path";
5257
- import { collection as collection8, doc as doc9, getDoc as getDoc8, getDocs as getDocs7 } from "firebase/firestore";
5265
+ import { collection as collection8, doc as doc8, getDoc as getDoc7, getDocs as getDocs7 } from "firebase/firestore";
5258
5266
 
5259
5267
  // src/cli/session.ts
5260
5268
  async function openShipSession(shipId) {
@@ -5423,8 +5431,8 @@ async function checkShips(config2) {
5423
5431
  continue;
5424
5432
  }
5425
5433
  try {
5426
- const snap = await getDoc8(
5427
- doc9(session.fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.runners, session.runnerId)
5434
+ const snap = await getDoc7(
5435
+ doc8(session.fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.runners, session.runnerId)
5428
5436
  );
5429
5437
  if (!snap.exists()) {
5430
5438
  checks.push(
@@ -5474,14 +5482,12 @@ async function checkShips(config2) {
5474
5482
  for (const id of shipEngines) engines.add(id);
5475
5483
  if (agents.some((agent) => effectiveAgentTools(agent).github.enabled)) needsGithub = true;
5476
5484
  try {
5477
- const secrets = await loadRunnerSecrets(session.fb.db, shipId);
5478
5485
  const credentials = await loadShipCredentials(session.fb.db, shipId).catch(() => []);
5479
5486
  const missing = [
5480
5487
  ...new Set(
5481
- [...shipEngines].flatMap((id) => {
5482
- if (credentials.some((c) => c.engine === id)) return [];
5483
- return missingSecretsFor(id, secrets);
5484
- })
5488
+ [...shipEngines].flatMap(
5489
+ (id) => credentials.some((c) => c.engine === id) ? [] : missingSecretsFor(id, null)
5490
+ )
5485
5491
  )
5486
5492
  ];
5487
5493
  const orphaned = agents.filter(
@@ -5965,7 +5971,7 @@ async function runUninstall(options) {
5965
5971
  }
5966
5972
 
5967
5973
  // src/cli/commands/ship.ts
5968
- import { doc as doc10, getDoc as getDoc9 } from "firebase/firestore";
5974
+ import { doc as doc9, getDoc as getDoc8 } from "firebase/firestore";
5969
5975
  async function listMyShips() {
5970
5976
  const config2 = requireConfig();
5971
5977
  const ids = Object.keys(config2.shipKeys ?? {});
@@ -5973,7 +5979,7 @@ async function listMyShips() {
5973
5979
  ids.map(async (id) => {
5974
5980
  try {
5975
5981
  const { fb } = await openShipSession(id);
5976
- const snap = await getDoc9(doc10(fb.db, COLLECTIONS.ships, id));
5982
+ const snap = await getDoc8(doc9(fb.db, COLLECTIONS.ships, id));
5977
5983
  return { id, ...snap.data() };
5978
5984
  } catch {
5979
5985
  return null;
@@ -6134,9 +6140,9 @@ async function runSetup(options) {
6134
6140
  // src/cli/commands/status.ts
6135
6141
  import {
6136
6142
  collection as collection9,
6137
- doc as doc11,
6143
+ doc as doc10,
6138
6144
  getCountFromServer as getCountFromServer2,
6139
- getDoc as getDoc10,
6145
+ getDoc as getDoc9,
6140
6146
  getDocs as getDocs8,
6141
6147
  query as query5,
6142
6148
  where as where5
@@ -6174,8 +6180,8 @@ async function runStatus() {
6174
6180
  });
6175
6181
  continue;
6176
6182
  }
6177
- const shipRef = doc11(session.fb.db, COLLECTIONS.ships, shipId);
6178
- const mirrorSnap = await getDoc10(doc11(shipRef, COLLECTIONS.runners, session.runnerId));
6183
+ const shipRef = doc10(session.fb.db, COLLECTIONS.ships, shipId);
6184
+ const mirrorSnap = await getDoc9(doc10(shipRef, COLLECTIONS.runners, session.runnerId));
6179
6185
  const mirror = mirrorSnap.data();
6180
6186
  let queued = 0;
6181
6187
  try {
@@ -6186,7 +6192,7 @@ async function runStatus() {
6186
6192
  } catch {
6187
6193
  queued = 0;
6188
6194
  }
6189
- const usageSnap = await getDoc10(doc11(shipRef, COLLECTIONS.usageDaily, utcDay(Date.now())));
6195
+ const usageSnap = await getDoc9(doc10(shipRef, COLLECTIONS.usageDaily, utcDay(Date.now())));
6190
6196
  const today = { ...EMPTY_USAGE_TOTALS, ...usageSnap.data()?.totals ?? {} };
6191
6197
  const now = Date.now();
6192
6198
  const limitsSnap = await getDocs8(collection9(shipRef, COLLECTIONS.engineLimits));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumi.ai/runner",
3
- "version": "0.15.16",
3
+ "version": "0.15.18",
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.",