@lumi.ai/runner 0.15.17 → 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.
- package/dist/cli.js +60 -88
- 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
|
|
12
|
-
getDoc as
|
|
11
|
+
doc as doc7,
|
|
12
|
+
getDoc as getDoc6,
|
|
13
13
|
getDocs as getDocs6,
|
|
14
14
|
onSnapshot as onSnapshot2,
|
|
15
15
|
orderBy as orderBy4,
|
|
@@ -844,21 +844,6 @@ function firstStatusIn(statuses, category) {
|
|
|
844
844
|
var RUNNER_OFFLINE_AFTER_MS = 2 * 60 * 1e3;
|
|
845
845
|
|
|
846
846
|
// ../shared/dist/secrets.js
|
|
847
|
-
var SECRET_DOCS = {
|
|
848
|
-
/** Captain-provided runner credentials (this file's shape). */
|
|
849
|
-
runner: "runner",
|
|
850
|
-
/** `{ tokenHash }` for the Workspace MCP bearer — backend-only, never client-readable. */
|
|
851
|
-
mcp: "mcp",
|
|
852
|
-
/**
|
|
853
|
-
* `{ runnerId, keyHash }` for THIS Ship's daemon credential (PRD §8.1) — backend-only.
|
|
854
|
-
*
|
|
855
|
-
* One document, not one per machine, because a Ship runs one daemon: issuing a key is
|
|
856
|
-
* therefore also how the previous one is revoked, exactly like rotating the MCP bearer.
|
|
857
|
-
* Only `docId == 'runner'` is released by packages/crew/firestore.rules, so this falls
|
|
858
|
-
* through to deny for every client without needing a rule of its own.
|
|
859
|
-
*/
|
|
860
|
-
runnerKey: "runnerKey"
|
|
861
|
-
};
|
|
862
847
|
function missingSecretsFor(engineId, secrets) {
|
|
863
848
|
const bag = secrets;
|
|
864
849
|
return getEngine(engineId).requiredSecrets.filter((req) => !bag?.[req.key]).map((req) => req.label);
|
|
@@ -1038,7 +1023,7 @@ function mcpUrl(config2) {
|
|
|
1038
1023
|
}
|
|
1039
1024
|
|
|
1040
1025
|
// src/version.ts
|
|
1041
|
-
var RUNNER_VERSION = true ? "0.15.
|
|
1026
|
+
var RUNNER_VERSION = true ? "0.15.18" : "0.0.0-dev";
|
|
1042
1027
|
|
|
1043
1028
|
// src/auth.ts
|
|
1044
1029
|
import { signInWithCustomToken } from "firebase/auth";
|
|
@@ -2420,13 +2405,12 @@ async function resolveGithubToken(input) {
|
|
|
2420
2405
|
throw e;
|
|
2421
2406
|
}
|
|
2422
2407
|
}
|
|
2423
|
-
if (integration
|
|
2408
|
+
if (integration) {
|
|
2424
2409
|
return {
|
|
2425
2410
|
source: "none",
|
|
2426
2411
|
unavailable: `The Ship's GitHub connection is ${integration.status}. A captain can fix it in Ship Settings \u203A GitHub.`
|
|
2427
2412
|
};
|
|
2428
2413
|
}
|
|
2429
|
-
if (input.githubPat) return { token: input.githubPat, source: "pat" };
|
|
2430
2414
|
return null;
|
|
2431
2415
|
}
|
|
2432
2416
|
|
|
@@ -2738,17 +2722,8 @@ function serversNeedingProbe(servers) {
|
|
|
2738
2722
|
);
|
|
2739
2723
|
}
|
|
2740
2724
|
|
|
2741
|
-
// src/jobs/secrets.ts
|
|
2742
|
-
import { doc as doc4, getDoc as getDoc4 } from "firebase/firestore";
|
|
2743
|
-
async function loadRunnerSecrets(db, shipId) {
|
|
2744
|
-
const snap = await withFirestoreRetry(
|
|
2745
|
-
() => getDoc4(doc4(db, COLLECTIONS.ships, shipId, COLLECTIONS.secrets, SECRET_DOCS.runner))
|
|
2746
|
-
);
|
|
2747
|
-
return snap.exists() ? snap.data() : null;
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
2725
|
// src/jobs/credentials.ts
|
|
2751
|
-
import { collection as collection4, doc as
|
|
2726
|
+
import { collection as collection4, doc as doc4, getDoc as getDoc4, getDocs as getDocs4 } from "firebase/firestore";
|
|
2752
2727
|
async function loadShipCredentials(db, shipId) {
|
|
2753
2728
|
const snap = await withFirestoreRetry(
|
|
2754
2729
|
() => getDocs4(collection4(db, COLLECTIONS.ships, shipId, COLLECTIONS.credentials))
|
|
@@ -2757,35 +2732,41 @@ async function loadShipCredentials(db, shipId) {
|
|
|
2757
2732
|
}
|
|
2758
2733
|
async function loadCredentialToken(db, shipId, credentialId) {
|
|
2759
2734
|
const snap = await withFirestoreRetry(
|
|
2760
|
-
() =>
|
|
2735
|
+
() => getDoc4(doc4(db, COLLECTIONS.ships, shipId, COLLECTIONS.secrets, credentialId))
|
|
2761
2736
|
);
|
|
2762
2737
|
if (!snap.exists()) return null;
|
|
2763
2738
|
const token = snap.data().token;
|
|
2764
2739
|
return typeof token === "string" && token.trim() ? token : null;
|
|
2765
2740
|
}
|
|
2766
2741
|
async function resolveJobSecrets(input) {
|
|
2767
|
-
const { agent, credentials
|
|
2742
|
+
const { agent, credentials } = input;
|
|
2768
2743
|
const engineId = agentEngine(agent);
|
|
2769
2744
|
const resolved = resolveAgentCredential(agent, credentials, DEFAULT_ENGINE_ID);
|
|
2770
|
-
if (resolved === void 0)
|
|
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
|
+
}
|
|
2771
2751
|
if (resolved === null) {
|
|
2772
2752
|
return {
|
|
2773
|
-
secrets:
|
|
2753
|
+
secrets: null,
|
|
2774
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.`
|
|
2775
2755
|
};
|
|
2776
2756
|
}
|
|
2777
2757
|
const token = await loadCredentialToken(input.db, input.shipId, resolved.id);
|
|
2778
2758
|
if (!token) {
|
|
2779
2759
|
return {
|
|
2780
|
-
secrets:
|
|
2760
|
+
secrets: null,
|
|
2781
2761
|
problem: `The AI credential "${resolved.label}" has no token stored. Erase it in Settings \u203A AI credentials and add it again.`
|
|
2782
2762
|
};
|
|
2783
2763
|
}
|
|
2784
2764
|
const key = getEngine(engineId).requiredSecrets[0]?.key;
|
|
2785
|
-
if (!key) return { secrets:
|
|
2765
|
+
if (!key) return { secrets: null };
|
|
2786
2766
|
return {
|
|
2787
2767
|
secrets: {
|
|
2788
|
-
|
|
2768
|
+
updatedAt: resolved.updatedAt,
|
|
2769
|
+
updatedBy: resolved.createdBy,
|
|
2789
2770
|
[key]: token,
|
|
2790
2771
|
[`${key}Tail`]: resolved.tail
|
|
2791
2772
|
}
|
|
@@ -2796,7 +2777,7 @@ async function resolveJobSecrets(input) {
|
|
|
2796
2777
|
import {
|
|
2797
2778
|
collection as collection5,
|
|
2798
2779
|
deleteDoc,
|
|
2799
|
-
doc as
|
|
2780
|
+
doc as doc5,
|
|
2800
2781
|
onSnapshot,
|
|
2801
2782
|
setDoc
|
|
2802
2783
|
} from "firebase/firestore";
|
|
@@ -2825,7 +2806,7 @@ function nextResetAt(limits, now) {
|
|
|
2825
2806
|
return soonest;
|
|
2826
2807
|
}
|
|
2827
2808
|
function limitRef(db, shipId, engineId) {
|
|
2828
|
-
return
|
|
2809
|
+
return doc5(db, COLLECTIONS.ships, shipId, COLLECTIONS.engineLimits, engineId);
|
|
2829
2810
|
}
|
|
2830
2811
|
async function noteEngineLimit(db, shipId, engineId, limit3, runnerId) {
|
|
2831
2812
|
await setDoc(limitRef(db, shipId, engineId), {
|
|
@@ -2924,8 +2905,8 @@ import {
|
|
|
2924
2905
|
addDoc,
|
|
2925
2906
|
collection as collection6,
|
|
2926
2907
|
deleteField,
|
|
2927
|
-
doc as
|
|
2928
|
-
getDoc as
|
|
2908
|
+
doc as doc6,
|
|
2909
|
+
getDoc as getDoc5,
|
|
2929
2910
|
getDocs as getDocs5,
|
|
2930
2911
|
limit as fsLimit,
|
|
2931
2912
|
orderBy as orderBy3,
|
|
@@ -2971,10 +2952,10 @@ function backstopReportContent(resultText2) {
|
|
|
2971
2952
|
return { report: report4, summary };
|
|
2972
2953
|
}
|
|
2973
2954
|
async function finalizeJob(db, shipId, job, input) {
|
|
2974
|
-
const shipRef =
|
|
2975
|
-
const jobRef =
|
|
2955
|
+
const shipRef = doc6(db, COLLECTIONS.ships, shipId);
|
|
2956
|
+
const jobRef = doc6(shipRef, COLLECTIONS.jobs, job.id);
|
|
2976
2957
|
const now = Date.now();
|
|
2977
|
-
const usageRef =
|
|
2958
|
+
const usageRef = doc6(shipRef, COLLECTIONS.usageDaily, utcDay(now));
|
|
2978
2959
|
await runTransaction(db, async (tx) => {
|
|
2979
2960
|
const usageSnap = await tx.get(usageRef);
|
|
2980
2961
|
const jobSnap = await tx.get(jobRef);
|
|
@@ -3036,7 +3017,7 @@ async function finalizeJob(db, shipId, job, input) {
|
|
|
3036
3017
|
}
|
|
3037
3018
|
async function requeueForRetry(db, shipId, job, error) {
|
|
3038
3019
|
await runTransaction(db, async (tx) => {
|
|
3039
|
-
tx.update(
|
|
3020
|
+
tx.update(doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
|
|
3040
3021
|
status: "queued",
|
|
3041
3022
|
attempt: job.attempt + 1,
|
|
3042
3023
|
error: error.slice(0, 1500),
|
|
@@ -3046,7 +3027,7 @@ async function requeueForRetry(db, shipId, job, error) {
|
|
|
3046
3027
|
});
|
|
3047
3028
|
}
|
|
3048
3029
|
async function releaseJob(db, shipId, job, reason) {
|
|
3049
|
-
await updateDoc(
|
|
3030
|
+
await updateDoc(doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
|
|
3050
3031
|
status: "queued",
|
|
3051
3032
|
runnerId: "",
|
|
3052
3033
|
startedAt: 0,
|
|
@@ -3062,13 +3043,13 @@ async function releaseJob(db, shipId, job, reason) {
|
|
|
3062
3043
|
});
|
|
3063
3044
|
}
|
|
3064
3045
|
async function markTaskFailed(db, shipId, job, error, statuses) {
|
|
3065
|
-
const shipRef =
|
|
3066
|
-
const taskRef =
|
|
3046
|
+
const shipRef = doc6(db, COLLECTIONS.ships, shipId);
|
|
3047
|
+
const taskRef = doc6(shipRef, COLLECTIONS.tasks, job.taskId);
|
|
3067
3048
|
const now = Date.now();
|
|
3068
3049
|
const failedStatus = firstStatusIn(statuses, "failed")?.id ?? "failed";
|
|
3069
3050
|
await runTransaction(db, async (tx) => {
|
|
3070
3051
|
tx.update(taskRef, { status: failedStatus, updatedAt: now });
|
|
3071
|
-
tx.set(
|
|
3052
|
+
tx.set(doc6(collection6(taskRef, COLLECTIONS.activity)), {
|
|
3072
3053
|
author: { type: "agent", id: job.agentId },
|
|
3073
3054
|
createdAt: now,
|
|
3074
3055
|
kind: "comment",
|
|
@@ -3082,7 +3063,7 @@ ${error.slice(0, 800)}
|
|
|
3082
3063
|
});
|
|
3083
3064
|
}
|
|
3084
3065
|
async function markTaskStopped(db, shipId, job, stoppedBy) {
|
|
3085
|
-
const taskRef =
|
|
3066
|
+
const taskRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.tasks, job.taskId);
|
|
3086
3067
|
const who = await actorName(db, shipId, stoppedBy);
|
|
3087
3068
|
await addDoc(collection6(taskRef, COLLECTIONS.activity), {
|
|
3088
3069
|
author: { type: "agent", id: job.agentId },
|
|
@@ -3093,7 +3074,7 @@ async function markTaskStopped(db, shipId, job, stoppedBy) {
|
|
|
3093
3074
|
}
|
|
3094
3075
|
async function actorName(db, shipId, actorId) {
|
|
3095
3076
|
try {
|
|
3096
|
-
const snap = await
|
|
3077
|
+
const snap = await getDoc5(doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.members, actorId));
|
|
3097
3078
|
const m = snap.data();
|
|
3098
3079
|
return m?.displayName || m?.email || actorId;
|
|
3099
3080
|
} catch {
|
|
@@ -3101,7 +3082,7 @@ async function actorName(db, shipId, actorId) {
|
|
|
3101
3082
|
}
|
|
3102
3083
|
}
|
|
3103
3084
|
async function markChatStopped(db, shipId, job, stoppedBy) {
|
|
3104
|
-
const chatRef =
|
|
3085
|
+
const chatRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.chats, job.chatId);
|
|
3105
3086
|
const who = await actorName(db, shipId, stoppedBy);
|
|
3106
3087
|
const content = `Stopped by ${who} before I finished. Write again to start a fresh run.`;
|
|
3107
3088
|
await addDoc(collection6(chatRef, COLLECTIONS.chatMessages), {
|
|
@@ -3116,8 +3097,8 @@ async function markChatStopped(db, shipId, job, stoppedBy) {
|
|
|
3116
3097
|
});
|
|
3117
3098
|
}
|
|
3118
3099
|
async function markChatFailed(db, shipId, job, error) {
|
|
3119
|
-
const shipRef =
|
|
3120
|
-
const chatRef =
|
|
3100
|
+
const shipRef = doc6(db, COLLECTIONS.ships, shipId);
|
|
3101
|
+
const chatRef = doc6(shipRef, COLLECTIONS.chats, job.chatId);
|
|
3121
3102
|
const now = Date.now();
|
|
3122
3103
|
const content = `I could not finish replying \u2014 the run failed after ${job.attempt} attempt(s).
|
|
3123
3104
|
|
|
@@ -3145,7 +3126,7 @@ function backstopReplyContent(resultText2) {
|
|
|
3145
3126
|
return `${text.slice(0, MAX_CHAT_MESSAGE_CHARS - marker.length)}${marker}`;
|
|
3146
3127
|
}
|
|
3147
3128
|
async function ensureChatReply(db, shipId, job, resultText2) {
|
|
3148
|
-
const chatRef =
|
|
3129
|
+
const chatRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.chats, job.chatId);
|
|
3149
3130
|
const messagesCol = collection6(chatRef, COLLECTIONS.chatMessages);
|
|
3150
3131
|
const snap = await getDocs5(
|
|
3151
3132
|
query3(
|
|
@@ -3971,7 +3952,7 @@ async function startDaemon() {
|
|
|
3971
3952
|
const warnedUnapproved = /* @__PURE__ */ new Set();
|
|
3972
3953
|
const shipRunnerRef = (shipId) => {
|
|
3973
3954
|
const session = sess(shipId);
|
|
3974
|
-
return
|
|
3955
|
+
return doc7(session.fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.runners, session.runnerId);
|
|
3975
3956
|
};
|
|
3976
3957
|
const needsRefill = /* @__PURE__ */ new Set();
|
|
3977
3958
|
let beating = false;
|
|
@@ -3982,7 +3963,7 @@ async function startDaemon() {
|
|
|
3982
3963
|
try {
|
|
3983
3964
|
for (const shipId of [...serving]) {
|
|
3984
3965
|
try {
|
|
3985
|
-
const snap = await
|
|
3966
|
+
const snap = await getDoc6(shipRunnerRef(shipId));
|
|
3986
3967
|
if (!snap.exists()) {
|
|
3987
3968
|
forgetShipLocally(shipId, "a captain removed this machine on the Daemons page");
|
|
3988
3969
|
continue;
|
|
@@ -4327,7 +4308,7 @@ async function startDaemon() {
|
|
|
4327
4308
|
pending.set(`${shipId}/${job.id}`, { shipId, job });
|
|
4328
4309
|
return null;
|
|
4329
4310
|
}
|
|
4330
|
-
const jobRef =
|
|
4311
|
+
const jobRef = doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id);
|
|
4331
4312
|
try {
|
|
4332
4313
|
let claimed = null;
|
|
4333
4314
|
await runTransaction2(sess(shipId).fb.db, async (tx) => {
|
|
@@ -4350,14 +4331,11 @@ async function startDaemon() {
|
|
|
4350
4331
|
}
|
|
4351
4332
|
async function setAgentStatus(shipId, agentId, status) {
|
|
4352
4333
|
try {
|
|
4353
|
-
await updateDoc2(
|
|
4334
|
+
await updateDoc2(doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.agents, agentId), { status });
|
|
4354
4335
|
} catch (e) {
|
|
4355
4336
|
log2(`agent status update failed: ${e instanceof Error ? e.message : e}`);
|
|
4356
4337
|
}
|
|
4357
4338
|
}
|
|
4358
|
-
async function loadSecrets(shipId) {
|
|
4359
|
-
return loadRunnerSecrets(sess(shipId).fb.db, shipId);
|
|
4360
|
-
}
|
|
4361
4339
|
async function processJob(shipId, queuedJob, slot) {
|
|
4362
4340
|
const job = await claim(shipId, queuedJob);
|
|
4363
4341
|
if (!job) return;
|
|
@@ -4397,8 +4375,8 @@ async function startDaemon() {
|
|
|
4397
4375
|
void (async () => {
|
|
4398
4376
|
if (slot.stop) return;
|
|
4399
4377
|
try {
|
|
4400
|
-
const snap = await
|
|
4401
|
-
|
|
4378
|
+
const snap = await getDoc6(
|
|
4379
|
+
doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id)
|
|
4402
4380
|
);
|
|
4403
4381
|
const fresh = snap.data();
|
|
4404
4382
|
if (!fresh?.stopRequestedAt || slot.stop) return;
|
|
@@ -4438,7 +4416,7 @@ async function startDaemon() {
|
|
|
4438
4416
|
let statuses = DEFAULT_TASK_STATUSES;
|
|
4439
4417
|
let knownSecrets = [];
|
|
4440
4418
|
const progress = createProgressWriter({
|
|
4441
|
-
write: (p) => updateDoc2(
|
|
4419
|
+
write: (p) => updateDoc2(doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
|
|
4442
4420
|
progress: p
|
|
4443
4421
|
}),
|
|
4444
4422
|
secrets: () => knownSecrets,
|
|
@@ -4452,8 +4430,8 @@ async function startDaemon() {
|
|
|
4452
4430
|
onDenied: () => {
|
|
4453
4431
|
void (async () => {
|
|
4454
4432
|
try {
|
|
4455
|
-
const snap = await
|
|
4456
|
-
|
|
4433
|
+
const snap = await getDoc6(
|
|
4434
|
+
doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id)
|
|
4457
4435
|
);
|
|
4458
4436
|
const fresh = snap.data();
|
|
4459
4437
|
if (!fresh || fresh.status !== "running" || fresh.runnerId !== sess(shipId).runnerId) {
|
|
@@ -4468,7 +4446,7 @@ async function startDaemon() {
|
|
|
4468
4446
|
});
|
|
4469
4447
|
progress.push({ kind: "start", label: "Starting up" });
|
|
4470
4448
|
try {
|
|
4471
|
-
let secrets =
|
|
4449
|
+
let secrets = null;
|
|
4472
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 }) };
|
|
4473
4451
|
if (packed.kind === "task" && !isWorkflowSentinel(job.workflowId) && !packed.ctx.workflow) {
|
|
4474
4452
|
log2(
|
|
@@ -4483,7 +4461,6 @@ async function startDaemon() {
|
|
|
4483
4461
|
db: sess(shipId).fb.db,
|
|
4484
4462
|
shipId,
|
|
4485
4463
|
agent,
|
|
4486
|
-
runnerSecrets: secrets,
|
|
4487
4464
|
credentials: await loadShipCredentials(sess(shipId).fb.db, shipId).catch(() => [])
|
|
4488
4465
|
});
|
|
4489
4466
|
if (resolvedSecrets.problem) {
|
|
@@ -4521,8 +4498,7 @@ async function startDaemon() {
|
|
|
4521
4498
|
idToken,
|
|
4522
4499
|
shipId,
|
|
4523
4500
|
jobId: job.id,
|
|
4524
|
-
agent
|
|
4525
|
-
githubPat: secrets?.githubPat
|
|
4501
|
+
agent
|
|
4526
4502
|
});
|
|
4527
4503
|
githubToken = gh?.token;
|
|
4528
4504
|
githubRepos = gh?.repos;
|
|
@@ -4562,7 +4538,6 @@ async function startDaemon() {
|
|
|
4562
4538
|
idToken,
|
|
4563
4539
|
secrets?.claudeToken,
|
|
4564
4540
|
githubToken,
|
|
4565
|
-
secrets?.githubPat,
|
|
4566
4541
|
// §15.31. BOTH the composed header value and the bare credential — see
|
|
4567
4542
|
// `mcpSecretsToRedact` for why one of them is not enough.
|
|
4568
4543
|
...mcpSecretsToRedact(extraMcpServers)
|
|
@@ -4747,7 +4722,6 @@ async function startDaemon() {
|
|
|
4747
4722
|
if (serving.has(shipId)) await setAgentStatus(shipId, job.agentId, "idle");
|
|
4748
4723
|
if (!shuttingDown) void heartbeat();
|
|
4749
4724
|
}
|
|
4750
|
-
await Promise.all([...serving].map((shipId) => loadSecrets(shipId).catch(() => null)));
|
|
4751
4725
|
await heartbeat();
|
|
4752
4726
|
const heartbeatTimer = setInterval(() => {
|
|
4753
4727
|
void heartbeat();
|
|
@@ -5288,7 +5262,7 @@ function setParallel(config2, value, ship2) {
|
|
|
5288
5262
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
5289
5263
|
import fs7 from "node:fs";
|
|
5290
5264
|
import path8 from "node:path";
|
|
5291
|
-
import { collection as collection8, doc as
|
|
5265
|
+
import { collection as collection8, doc as doc8, getDoc as getDoc7, getDocs as getDocs7 } from "firebase/firestore";
|
|
5292
5266
|
|
|
5293
5267
|
// src/cli/session.ts
|
|
5294
5268
|
async function openShipSession(shipId) {
|
|
@@ -5457,8 +5431,8 @@ async function checkShips(config2) {
|
|
|
5457
5431
|
continue;
|
|
5458
5432
|
}
|
|
5459
5433
|
try {
|
|
5460
|
-
const snap = await
|
|
5461
|
-
|
|
5434
|
+
const snap = await getDoc7(
|
|
5435
|
+
doc8(session.fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.runners, session.runnerId)
|
|
5462
5436
|
);
|
|
5463
5437
|
if (!snap.exists()) {
|
|
5464
5438
|
checks.push(
|
|
@@ -5508,14 +5482,12 @@ async function checkShips(config2) {
|
|
|
5508
5482
|
for (const id of shipEngines) engines.add(id);
|
|
5509
5483
|
if (agents.some((agent) => effectiveAgentTools(agent).github.enabled)) needsGithub = true;
|
|
5510
5484
|
try {
|
|
5511
|
-
const secrets = await loadRunnerSecrets(session.fb.db, shipId);
|
|
5512
5485
|
const credentials = await loadShipCredentials(session.fb.db, shipId).catch(() => []);
|
|
5513
5486
|
const missing = [
|
|
5514
5487
|
...new Set(
|
|
5515
|
-
[...shipEngines].flatMap(
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
})
|
|
5488
|
+
[...shipEngines].flatMap(
|
|
5489
|
+
(id) => credentials.some((c) => c.engine === id) ? [] : missingSecretsFor(id, null)
|
|
5490
|
+
)
|
|
5519
5491
|
)
|
|
5520
5492
|
];
|
|
5521
5493
|
const orphaned = agents.filter(
|
|
@@ -5999,7 +5971,7 @@ async function runUninstall(options) {
|
|
|
5999
5971
|
}
|
|
6000
5972
|
|
|
6001
5973
|
// src/cli/commands/ship.ts
|
|
6002
|
-
import { doc as
|
|
5974
|
+
import { doc as doc9, getDoc as getDoc8 } from "firebase/firestore";
|
|
6003
5975
|
async function listMyShips() {
|
|
6004
5976
|
const config2 = requireConfig();
|
|
6005
5977
|
const ids = Object.keys(config2.shipKeys ?? {});
|
|
@@ -6007,7 +5979,7 @@ async function listMyShips() {
|
|
|
6007
5979
|
ids.map(async (id) => {
|
|
6008
5980
|
try {
|
|
6009
5981
|
const { fb } = await openShipSession(id);
|
|
6010
|
-
const snap = await
|
|
5982
|
+
const snap = await getDoc8(doc9(fb.db, COLLECTIONS.ships, id));
|
|
6011
5983
|
return { id, ...snap.data() };
|
|
6012
5984
|
} catch {
|
|
6013
5985
|
return null;
|
|
@@ -6168,9 +6140,9 @@ async function runSetup(options) {
|
|
|
6168
6140
|
// src/cli/commands/status.ts
|
|
6169
6141
|
import {
|
|
6170
6142
|
collection as collection9,
|
|
6171
|
-
doc as
|
|
6143
|
+
doc as doc10,
|
|
6172
6144
|
getCountFromServer as getCountFromServer2,
|
|
6173
|
-
getDoc as
|
|
6145
|
+
getDoc as getDoc9,
|
|
6174
6146
|
getDocs as getDocs8,
|
|
6175
6147
|
query as query5,
|
|
6176
6148
|
where as where5
|
|
@@ -6208,8 +6180,8 @@ async function runStatus() {
|
|
|
6208
6180
|
});
|
|
6209
6181
|
continue;
|
|
6210
6182
|
}
|
|
6211
|
-
const shipRef =
|
|
6212
|
-
const mirrorSnap = await
|
|
6183
|
+
const shipRef = doc10(session.fb.db, COLLECTIONS.ships, shipId);
|
|
6184
|
+
const mirrorSnap = await getDoc9(doc10(shipRef, COLLECTIONS.runners, session.runnerId));
|
|
6213
6185
|
const mirror = mirrorSnap.data();
|
|
6214
6186
|
let queued = 0;
|
|
6215
6187
|
try {
|
|
@@ -6220,7 +6192,7 @@ async function runStatus() {
|
|
|
6220
6192
|
} catch {
|
|
6221
6193
|
queued = 0;
|
|
6222
6194
|
}
|
|
6223
|
-
const usageSnap = await
|
|
6195
|
+
const usageSnap = await getDoc9(doc10(shipRef, COLLECTIONS.usageDaily, utcDay(Date.now())));
|
|
6224
6196
|
const today = { ...EMPTY_USAGE_TOTALS, ...usageSnap.data()?.totals ?? {} };
|
|
6225
6197
|
const now = Date.now();
|
|
6226
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.
|
|
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.",
|