@lumi.ai/runner 0.15.17 → 0.15.19
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 +103 -89
- 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,
|
|
@@ -63,6 +63,9 @@ var ENGINES = {
|
|
|
63
63
|
claude: CLAUDE_ENGINE
|
|
64
64
|
};
|
|
65
65
|
var DEFAULT_ENGINE_ID = "claude";
|
|
66
|
+
function listEngines() {
|
|
67
|
+
return Object.values(ENGINES);
|
|
68
|
+
}
|
|
66
69
|
function isKnownEngine(id) {
|
|
67
70
|
return !!id && Object.hasOwn(ENGINES, id);
|
|
68
71
|
}
|
|
@@ -82,6 +85,8 @@ function estimateCostUsd(engineId, modelId, tokens) {
|
|
|
82
85
|
const fresh = Math.max(0, tokens.inputTokens - tokens.cachedInputTokens);
|
|
83
86
|
return (fresh * model.rates.input + tokens.cachedInputTokens * model.rates.cachedInput + tokens.outputTokens * model.rates.output) / 1e6;
|
|
84
87
|
}
|
|
88
|
+
var AGENT_ENGINE_IDS = Object.freeze(listEngines().map((e) => e.id));
|
|
89
|
+
var AGENT_MODEL_IDS = Object.freeze(listEngines().flatMap((e) => e.models.map((m) => m.id)));
|
|
85
90
|
|
|
86
91
|
// ../shared/dist/agent.js
|
|
87
92
|
var DEFAULT_AGENT_TOOLS = {
|
|
@@ -144,6 +149,41 @@ var ASSISTANT_PROFILE = Object.freeze({
|
|
|
144
149
|
})
|
|
145
150
|
});
|
|
146
151
|
|
|
152
|
+
// ../shared/dist/assistantAuthority.js
|
|
153
|
+
var ASSISTANT_TOOL_AUTHORITY = Object.freeze({
|
|
154
|
+
// Reads. Everything the asker could open in the app.
|
|
155
|
+
ship_info: "member",
|
|
156
|
+
agent_list: "member",
|
|
157
|
+
task_list: "member",
|
|
158
|
+
task_get: "member",
|
|
159
|
+
knowledge_get: "member",
|
|
160
|
+
chat_get: "member",
|
|
161
|
+
// Writes a member can already make by clicking.
|
|
162
|
+
task_create: "member",
|
|
163
|
+
task_comment: "member",
|
|
164
|
+
task_update_status: "member",
|
|
165
|
+
task_relate: "member",
|
|
166
|
+
task_assign: "member",
|
|
167
|
+
knowledge_write: "member",
|
|
168
|
+
chat_send: "member",
|
|
169
|
+
// Captain-only, because the rules say so for a direct write — and `agent_write` also bills.
|
|
170
|
+
agent_write: "captain",
|
|
171
|
+
workflow_write: "captain",
|
|
172
|
+
knowledge_folder: "captain"
|
|
173
|
+
});
|
|
174
|
+
var ASSISTANT_NEVER_REGISTER = Object.freeze([
|
|
175
|
+
"approval_decide",
|
|
176
|
+
"approval_request",
|
|
177
|
+
"memory_write",
|
|
178
|
+
"media_attach"
|
|
179
|
+
]);
|
|
180
|
+
var ASSISTANT_FROZEN_ARGS = Object.freeze({
|
|
181
|
+
agent_write: Object.freeze(["tools"])
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// ../shared/dist/toolStats.js
|
|
185
|
+
var TOOL_STATS_HALF_LIFE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
186
|
+
|
|
147
187
|
// ../shared/dist/browser.js
|
|
148
188
|
var BROWSER_ONLINE_WINDOW_MS = 9e4;
|
|
149
189
|
var DEFAULT_BROWSER_CONSENT_MS = 60 * 60 * 1e3;
|
|
@@ -332,6 +372,8 @@ var COLLECTIONS = {
|
|
|
332
372
|
engineLimits: "engine_limits",
|
|
333
373
|
/** `ships/{shipId}/events/{id}` — append-only Ship-level audit log. */
|
|
334
374
|
events: "events",
|
|
375
|
+
/** §15.70: per-actor tool usage, so the assistant declares what it actually reaches for. */
|
|
376
|
+
toolStats: "toolStats",
|
|
335
377
|
/** `ships/{shipId}/secrets/{docId}` — runner creds + MCP token hash (see secrets.ts). */
|
|
336
378
|
secrets: "secrets",
|
|
337
379
|
/**
|
|
@@ -844,21 +886,6 @@ function firstStatusIn(statuses, category) {
|
|
|
844
886
|
var RUNNER_OFFLINE_AFTER_MS = 2 * 60 * 1e3;
|
|
845
887
|
|
|
846
888
|
// ../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
889
|
function missingSecretsFor(engineId, secrets) {
|
|
863
890
|
const bag = secrets;
|
|
864
891
|
return getEngine(engineId).requiredSecrets.filter((req) => !bag?.[req.key]).map((req) => req.label);
|
|
@@ -1038,7 +1065,7 @@ function mcpUrl(config2) {
|
|
|
1038
1065
|
}
|
|
1039
1066
|
|
|
1040
1067
|
// src/version.ts
|
|
1041
|
-
var RUNNER_VERSION = true ? "0.15.
|
|
1068
|
+
var RUNNER_VERSION = true ? "0.15.19" : "0.0.0-dev";
|
|
1042
1069
|
|
|
1043
1070
|
// src/auth.ts
|
|
1044
1071
|
import { signInWithCustomToken } from "firebase/auth";
|
|
@@ -2420,13 +2447,12 @@ async function resolveGithubToken(input) {
|
|
|
2420
2447
|
throw e;
|
|
2421
2448
|
}
|
|
2422
2449
|
}
|
|
2423
|
-
if (integration
|
|
2450
|
+
if (integration) {
|
|
2424
2451
|
return {
|
|
2425
2452
|
source: "none",
|
|
2426
2453
|
unavailable: `The Ship's GitHub connection is ${integration.status}. A captain can fix it in Ship Settings \u203A GitHub.`
|
|
2427
2454
|
};
|
|
2428
2455
|
}
|
|
2429
|
-
if (input.githubPat) return { token: input.githubPat, source: "pat" };
|
|
2430
2456
|
return null;
|
|
2431
2457
|
}
|
|
2432
2458
|
|
|
@@ -2738,17 +2764,8 @@ function serversNeedingProbe(servers) {
|
|
|
2738
2764
|
);
|
|
2739
2765
|
}
|
|
2740
2766
|
|
|
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
2767
|
// src/jobs/credentials.ts
|
|
2751
|
-
import { collection as collection4, doc as
|
|
2768
|
+
import { collection as collection4, doc as doc4, getDoc as getDoc4, getDocs as getDocs4 } from "firebase/firestore";
|
|
2752
2769
|
async function loadShipCredentials(db, shipId) {
|
|
2753
2770
|
const snap = await withFirestoreRetry(
|
|
2754
2771
|
() => getDocs4(collection4(db, COLLECTIONS.ships, shipId, COLLECTIONS.credentials))
|
|
@@ -2757,35 +2774,41 @@ async function loadShipCredentials(db, shipId) {
|
|
|
2757
2774
|
}
|
|
2758
2775
|
async function loadCredentialToken(db, shipId, credentialId) {
|
|
2759
2776
|
const snap = await withFirestoreRetry(
|
|
2760
|
-
() =>
|
|
2777
|
+
() => getDoc4(doc4(db, COLLECTIONS.ships, shipId, COLLECTIONS.secrets, credentialId))
|
|
2761
2778
|
);
|
|
2762
2779
|
if (!snap.exists()) return null;
|
|
2763
2780
|
const token = snap.data().token;
|
|
2764
2781
|
return typeof token === "string" && token.trim() ? token : null;
|
|
2765
2782
|
}
|
|
2766
2783
|
async function resolveJobSecrets(input) {
|
|
2767
|
-
const { agent, credentials
|
|
2784
|
+
const { agent, credentials } = input;
|
|
2768
2785
|
const engineId = agentEngine(agent);
|
|
2769
2786
|
const resolved = resolveAgentCredential(agent, credentials, DEFAULT_ENGINE_ID);
|
|
2770
|
-
if (resolved === void 0)
|
|
2787
|
+
if (resolved === void 0) {
|
|
2788
|
+
return {
|
|
2789
|
+
secrets: null,
|
|
2790
|
+
problem: `This workspace has no AI credential for ${engineId}. Add one in Settings \u203A AI credentials and the crew starts taking work again.`
|
|
2791
|
+
};
|
|
2792
|
+
}
|
|
2771
2793
|
if (resolved === null) {
|
|
2772
2794
|
return {
|
|
2773
|
-
secrets:
|
|
2795
|
+
secrets: null,
|
|
2774
2796
|
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
2797
|
};
|
|
2776
2798
|
}
|
|
2777
2799
|
const token = await loadCredentialToken(input.db, input.shipId, resolved.id);
|
|
2778
2800
|
if (!token) {
|
|
2779
2801
|
return {
|
|
2780
|
-
secrets:
|
|
2802
|
+
secrets: null,
|
|
2781
2803
|
problem: `The AI credential "${resolved.label}" has no token stored. Erase it in Settings \u203A AI credentials and add it again.`
|
|
2782
2804
|
};
|
|
2783
2805
|
}
|
|
2784
2806
|
const key = getEngine(engineId).requiredSecrets[0]?.key;
|
|
2785
|
-
if (!key) return { secrets:
|
|
2807
|
+
if (!key) return { secrets: null };
|
|
2786
2808
|
return {
|
|
2787
2809
|
secrets: {
|
|
2788
|
-
|
|
2810
|
+
updatedAt: resolved.updatedAt,
|
|
2811
|
+
updatedBy: resolved.createdBy,
|
|
2789
2812
|
[key]: token,
|
|
2790
2813
|
[`${key}Tail`]: resolved.tail
|
|
2791
2814
|
}
|
|
@@ -2796,7 +2819,7 @@ async function resolveJobSecrets(input) {
|
|
|
2796
2819
|
import {
|
|
2797
2820
|
collection as collection5,
|
|
2798
2821
|
deleteDoc,
|
|
2799
|
-
doc as
|
|
2822
|
+
doc as doc5,
|
|
2800
2823
|
onSnapshot,
|
|
2801
2824
|
setDoc
|
|
2802
2825
|
} from "firebase/firestore";
|
|
@@ -2825,7 +2848,7 @@ function nextResetAt(limits, now) {
|
|
|
2825
2848
|
return soonest;
|
|
2826
2849
|
}
|
|
2827
2850
|
function limitRef(db, shipId, engineId) {
|
|
2828
|
-
return
|
|
2851
|
+
return doc5(db, COLLECTIONS.ships, shipId, COLLECTIONS.engineLimits, engineId);
|
|
2829
2852
|
}
|
|
2830
2853
|
async function noteEngineLimit(db, shipId, engineId, limit3, runnerId) {
|
|
2831
2854
|
await setDoc(limitRef(db, shipId, engineId), {
|
|
@@ -2924,8 +2947,8 @@ import {
|
|
|
2924
2947
|
addDoc,
|
|
2925
2948
|
collection as collection6,
|
|
2926
2949
|
deleteField,
|
|
2927
|
-
doc as
|
|
2928
|
-
getDoc as
|
|
2950
|
+
doc as doc6,
|
|
2951
|
+
getDoc as getDoc5,
|
|
2929
2952
|
getDocs as getDocs5,
|
|
2930
2953
|
limit as fsLimit,
|
|
2931
2954
|
orderBy as orderBy3,
|
|
@@ -2971,10 +2994,10 @@ function backstopReportContent(resultText2) {
|
|
|
2971
2994
|
return { report: report4, summary };
|
|
2972
2995
|
}
|
|
2973
2996
|
async function finalizeJob(db, shipId, job, input) {
|
|
2974
|
-
const shipRef =
|
|
2975
|
-
const jobRef =
|
|
2997
|
+
const shipRef = doc6(db, COLLECTIONS.ships, shipId);
|
|
2998
|
+
const jobRef = doc6(shipRef, COLLECTIONS.jobs, job.id);
|
|
2976
2999
|
const now = Date.now();
|
|
2977
|
-
const usageRef =
|
|
3000
|
+
const usageRef = doc6(shipRef, COLLECTIONS.usageDaily, utcDay(now));
|
|
2978
3001
|
await runTransaction(db, async (tx) => {
|
|
2979
3002
|
const usageSnap = await tx.get(usageRef);
|
|
2980
3003
|
const jobSnap = await tx.get(jobRef);
|
|
@@ -3036,7 +3059,7 @@ async function finalizeJob(db, shipId, job, input) {
|
|
|
3036
3059
|
}
|
|
3037
3060
|
async function requeueForRetry(db, shipId, job, error) {
|
|
3038
3061
|
await runTransaction(db, async (tx) => {
|
|
3039
|
-
tx.update(
|
|
3062
|
+
tx.update(doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
|
|
3040
3063
|
status: "queued",
|
|
3041
3064
|
attempt: job.attempt + 1,
|
|
3042
3065
|
error: error.slice(0, 1500),
|
|
@@ -3046,7 +3069,7 @@ async function requeueForRetry(db, shipId, job, error) {
|
|
|
3046
3069
|
});
|
|
3047
3070
|
}
|
|
3048
3071
|
async function releaseJob(db, shipId, job, reason) {
|
|
3049
|
-
await updateDoc(
|
|
3072
|
+
await updateDoc(doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
|
|
3050
3073
|
status: "queued",
|
|
3051
3074
|
runnerId: "",
|
|
3052
3075
|
startedAt: 0,
|
|
@@ -3062,13 +3085,13 @@ async function releaseJob(db, shipId, job, reason) {
|
|
|
3062
3085
|
});
|
|
3063
3086
|
}
|
|
3064
3087
|
async function markTaskFailed(db, shipId, job, error, statuses) {
|
|
3065
|
-
const shipRef =
|
|
3066
|
-
const taskRef =
|
|
3088
|
+
const shipRef = doc6(db, COLLECTIONS.ships, shipId);
|
|
3089
|
+
const taskRef = doc6(shipRef, COLLECTIONS.tasks, job.taskId);
|
|
3067
3090
|
const now = Date.now();
|
|
3068
3091
|
const failedStatus = firstStatusIn(statuses, "failed")?.id ?? "failed";
|
|
3069
3092
|
await runTransaction(db, async (tx) => {
|
|
3070
3093
|
tx.update(taskRef, { status: failedStatus, updatedAt: now });
|
|
3071
|
-
tx.set(
|
|
3094
|
+
tx.set(doc6(collection6(taskRef, COLLECTIONS.activity)), {
|
|
3072
3095
|
author: { type: "agent", id: job.agentId },
|
|
3073
3096
|
createdAt: now,
|
|
3074
3097
|
kind: "comment",
|
|
@@ -3082,7 +3105,7 @@ ${error.slice(0, 800)}
|
|
|
3082
3105
|
});
|
|
3083
3106
|
}
|
|
3084
3107
|
async function markTaskStopped(db, shipId, job, stoppedBy) {
|
|
3085
|
-
const taskRef =
|
|
3108
|
+
const taskRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.tasks, job.taskId);
|
|
3086
3109
|
const who = await actorName(db, shipId, stoppedBy);
|
|
3087
3110
|
await addDoc(collection6(taskRef, COLLECTIONS.activity), {
|
|
3088
3111
|
author: { type: "agent", id: job.agentId },
|
|
@@ -3093,7 +3116,7 @@ async function markTaskStopped(db, shipId, job, stoppedBy) {
|
|
|
3093
3116
|
}
|
|
3094
3117
|
async function actorName(db, shipId, actorId) {
|
|
3095
3118
|
try {
|
|
3096
|
-
const snap = await
|
|
3119
|
+
const snap = await getDoc5(doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.members, actorId));
|
|
3097
3120
|
const m = snap.data();
|
|
3098
3121
|
return m?.displayName || m?.email || actorId;
|
|
3099
3122
|
} catch {
|
|
@@ -3101,7 +3124,7 @@ async function actorName(db, shipId, actorId) {
|
|
|
3101
3124
|
}
|
|
3102
3125
|
}
|
|
3103
3126
|
async function markChatStopped(db, shipId, job, stoppedBy) {
|
|
3104
|
-
const chatRef =
|
|
3127
|
+
const chatRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.chats, job.chatId);
|
|
3105
3128
|
const who = await actorName(db, shipId, stoppedBy);
|
|
3106
3129
|
const content = `Stopped by ${who} before I finished. Write again to start a fresh run.`;
|
|
3107
3130
|
await addDoc(collection6(chatRef, COLLECTIONS.chatMessages), {
|
|
@@ -3116,8 +3139,8 @@ async function markChatStopped(db, shipId, job, stoppedBy) {
|
|
|
3116
3139
|
});
|
|
3117
3140
|
}
|
|
3118
3141
|
async function markChatFailed(db, shipId, job, error) {
|
|
3119
|
-
const shipRef =
|
|
3120
|
-
const chatRef =
|
|
3142
|
+
const shipRef = doc6(db, COLLECTIONS.ships, shipId);
|
|
3143
|
+
const chatRef = doc6(shipRef, COLLECTIONS.chats, job.chatId);
|
|
3121
3144
|
const now = Date.now();
|
|
3122
3145
|
const content = `I could not finish replying \u2014 the run failed after ${job.attempt} attempt(s).
|
|
3123
3146
|
|
|
@@ -3125,7 +3148,7 @@ async function markChatFailed(db, shipId, job, error) {
|
|
|
3125
3148
|
${error.slice(0, 500)}
|
|
3126
3149
|
\`\`\`
|
|
3127
3150
|
|
|
3128
|
-
|
|
3151
|
+
Use Try again above to run it once more.`;
|
|
3129
3152
|
await addDoc(collection6(chatRef, COLLECTIONS.chatMessages), {
|
|
3130
3153
|
author: { type: "agent", id: job.agentId },
|
|
3131
3154
|
content,
|
|
@@ -3145,7 +3168,7 @@ function backstopReplyContent(resultText2) {
|
|
|
3145
3168
|
return `${text.slice(0, MAX_CHAT_MESSAGE_CHARS - marker.length)}${marker}`;
|
|
3146
3169
|
}
|
|
3147
3170
|
async function ensureChatReply(db, shipId, job, resultText2) {
|
|
3148
|
-
const chatRef =
|
|
3171
|
+
const chatRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.chats, job.chatId);
|
|
3149
3172
|
const messagesCol = collection6(chatRef, COLLECTIONS.chatMessages);
|
|
3150
3173
|
const snap = await getDocs5(
|
|
3151
3174
|
query3(
|
|
@@ -3971,7 +3994,7 @@ async function startDaemon() {
|
|
|
3971
3994
|
const warnedUnapproved = /* @__PURE__ */ new Set();
|
|
3972
3995
|
const shipRunnerRef = (shipId) => {
|
|
3973
3996
|
const session = sess(shipId);
|
|
3974
|
-
return
|
|
3997
|
+
return doc7(session.fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.runners, session.runnerId);
|
|
3975
3998
|
};
|
|
3976
3999
|
const needsRefill = /* @__PURE__ */ new Set();
|
|
3977
4000
|
let beating = false;
|
|
@@ -3982,7 +4005,7 @@ async function startDaemon() {
|
|
|
3982
4005
|
try {
|
|
3983
4006
|
for (const shipId of [...serving]) {
|
|
3984
4007
|
try {
|
|
3985
|
-
const snap = await
|
|
4008
|
+
const snap = await getDoc6(shipRunnerRef(shipId));
|
|
3986
4009
|
if (!snap.exists()) {
|
|
3987
4010
|
forgetShipLocally(shipId, "a captain removed this machine on the Daemons page");
|
|
3988
4011
|
continue;
|
|
@@ -4327,7 +4350,7 @@ async function startDaemon() {
|
|
|
4327
4350
|
pending.set(`${shipId}/${job.id}`, { shipId, job });
|
|
4328
4351
|
return null;
|
|
4329
4352
|
}
|
|
4330
|
-
const jobRef =
|
|
4353
|
+
const jobRef = doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id);
|
|
4331
4354
|
try {
|
|
4332
4355
|
let claimed = null;
|
|
4333
4356
|
await runTransaction2(sess(shipId).fb.db, async (tx) => {
|
|
@@ -4350,14 +4373,11 @@ async function startDaemon() {
|
|
|
4350
4373
|
}
|
|
4351
4374
|
async function setAgentStatus(shipId, agentId, status) {
|
|
4352
4375
|
try {
|
|
4353
|
-
await updateDoc2(
|
|
4376
|
+
await updateDoc2(doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.agents, agentId), { status });
|
|
4354
4377
|
} catch (e) {
|
|
4355
4378
|
log2(`agent status update failed: ${e instanceof Error ? e.message : e}`);
|
|
4356
4379
|
}
|
|
4357
4380
|
}
|
|
4358
|
-
async function loadSecrets(shipId) {
|
|
4359
|
-
return loadRunnerSecrets(sess(shipId).fb.db, shipId);
|
|
4360
|
-
}
|
|
4361
4381
|
async function processJob(shipId, queuedJob, slot) {
|
|
4362
4382
|
const job = await claim(shipId, queuedJob);
|
|
4363
4383
|
if (!job) return;
|
|
@@ -4397,8 +4417,8 @@ async function startDaemon() {
|
|
|
4397
4417
|
void (async () => {
|
|
4398
4418
|
if (slot.stop) return;
|
|
4399
4419
|
try {
|
|
4400
|
-
const snap = await
|
|
4401
|
-
|
|
4420
|
+
const snap = await getDoc6(
|
|
4421
|
+
doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id)
|
|
4402
4422
|
);
|
|
4403
4423
|
const fresh = snap.data();
|
|
4404
4424
|
if (!fresh?.stopRequestedAt || slot.stop) return;
|
|
@@ -4438,7 +4458,7 @@ async function startDaemon() {
|
|
|
4438
4458
|
let statuses = DEFAULT_TASK_STATUSES;
|
|
4439
4459
|
let knownSecrets = [];
|
|
4440
4460
|
const progress = createProgressWriter({
|
|
4441
|
-
write: (p) => updateDoc2(
|
|
4461
|
+
write: (p) => updateDoc2(doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id), {
|
|
4442
4462
|
progress: p
|
|
4443
4463
|
}),
|
|
4444
4464
|
secrets: () => knownSecrets,
|
|
@@ -4452,8 +4472,8 @@ async function startDaemon() {
|
|
|
4452
4472
|
onDenied: () => {
|
|
4453
4473
|
void (async () => {
|
|
4454
4474
|
try {
|
|
4455
|
-
const snap = await
|
|
4456
|
-
|
|
4475
|
+
const snap = await getDoc6(
|
|
4476
|
+
doc7(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs, job.id)
|
|
4457
4477
|
);
|
|
4458
4478
|
const fresh = snap.data();
|
|
4459
4479
|
if (!fresh || fresh.status !== "running" || fresh.runnerId !== sess(shipId).runnerId) {
|
|
@@ -4468,7 +4488,7 @@ async function startDaemon() {
|
|
|
4468
4488
|
});
|
|
4469
4489
|
progress.push({ kind: "start", label: "Starting up" });
|
|
4470
4490
|
try {
|
|
4471
|
-
let secrets =
|
|
4491
|
+
let secrets = null;
|
|
4472
4492
|
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
4493
|
if (packed.kind === "task" && !isWorkflowSentinel(job.workflowId) && !packed.ctx.workflow) {
|
|
4474
4494
|
log2(
|
|
@@ -4483,7 +4503,6 @@ async function startDaemon() {
|
|
|
4483
4503
|
db: sess(shipId).fb.db,
|
|
4484
4504
|
shipId,
|
|
4485
4505
|
agent,
|
|
4486
|
-
runnerSecrets: secrets,
|
|
4487
4506
|
credentials: await loadShipCredentials(sess(shipId).fb.db, shipId).catch(() => [])
|
|
4488
4507
|
});
|
|
4489
4508
|
if (resolvedSecrets.problem) {
|
|
@@ -4521,8 +4540,7 @@ async function startDaemon() {
|
|
|
4521
4540
|
idToken,
|
|
4522
4541
|
shipId,
|
|
4523
4542
|
jobId: job.id,
|
|
4524
|
-
agent
|
|
4525
|
-
githubPat: secrets?.githubPat
|
|
4543
|
+
agent
|
|
4526
4544
|
});
|
|
4527
4545
|
githubToken = gh?.token;
|
|
4528
4546
|
githubRepos = gh?.repos;
|
|
@@ -4562,7 +4580,6 @@ async function startDaemon() {
|
|
|
4562
4580
|
idToken,
|
|
4563
4581
|
secrets?.claudeToken,
|
|
4564
4582
|
githubToken,
|
|
4565
|
-
secrets?.githubPat,
|
|
4566
4583
|
// §15.31. BOTH the composed header value and the bare credential — see
|
|
4567
4584
|
// `mcpSecretsToRedact` for why one of them is not enough.
|
|
4568
4585
|
...mcpSecretsToRedact(extraMcpServers)
|
|
@@ -4747,7 +4764,6 @@ async function startDaemon() {
|
|
|
4747
4764
|
if (serving.has(shipId)) await setAgentStatus(shipId, job.agentId, "idle");
|
|
4748
4765
|
if (!shuttingDown) void heartbeat();
|
|
4749
4766
|
}
|
|
4750
|
-
await Promise.all([...serving].map((shipId) => loadSecrets(shipId).catch(() => null)));
|
|
4751
4767
|
await heartbeat();
|
|
4752
4768
|
const heartbeatTimer = setInterval(() => {
|
|
4753
4769
|
void heartbeat();
|
|
@@ -5288,7 +5304,7 @@ function setParallel(config2, value, ship2) {
|
|
|
5288
5304
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
5289
5305
|
import fs7 from "node:fs";
|
|
5290
5306
|
import path8 from "node:path";
|
|
5291
|
-
import { collection as collection8, doc as
|
|
5307
|
+
import { collection as collection8, doc as doc8, getDoc as getDoc7, getDocs as getDocs7 } from "firebase/firestore";
|
|
5292
5308
|
|
|
5293
5309
|
// src/cli/session.ts
|
|
5294
5310
|
async function openShipSession(shipId) {
|
|
@@ -5457,8 +5473,8 @@ async function checkShips(config2) {
|
|
|
5457
5473
|
continue;
|
|
5458
5474
|
}
|
|
5459
5475
|
try {
|
|
5460
|
-
const snap = await
|
|
5461
|
-
|
|
5476
|
+
const snap = await getDoc7(
|
|
5477
|
+
doc8(session.fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.runners, session.runnerId)
|
|
5462
5478
|
);
|
|
5463
5479
|
if (!snap.exists()) {
|
|
5464
5480
|
checks.push(
|
|
@@ -5508,14 +5524,12 @@ async function checkShips(config2) {
|
|
|
5508
5524
|
for (const id of shipEngines) engines.add(id);
|
|
5509
5525
|
if (agents.some((agent) => effectiveAgentTools(agent).github.enabled)) needsGithub = true;
|
|
5510
5526
|
try {
|
|
5511
|
-
const secrets = await loadRunnerSecrets(session.fb.db, shipId);
|
|
5512
5527
|
const credentials = await loadShipCredentials(session.fb.db, shipId).catch(() => []);
|
|
5513
5528
|
const missing = [
|
|
5514
5529
|
...new Set(
|
|
5515
|
-
[...shipEngines].flatMap(
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
})
|
|
5530
|
+
[...shipEngines].flatMap(
|
|
5531
|
+
(id) => credentials.some((c) => c.engine === id) ? [] : missingSecretsFor(id, null)
|
|
5532
|
+
)
|
|
5519
5533
|
)
|
|
5520
5534
|
];
|
|
5521
5535
|
const orphaned = agents.filter(
|
|
@@ -5999,7 +6013,7 @@ async function runUninstall(options) {
|
|
|
5999
6013
|
}
|
|
6000
6014
|
|
|
6001
6015
|
// src/cli/commands/ship.ts
|
|
6002
|
-
import { doc as
|
|
6016
|
+
import { doc as doc9, getDoc as getDoc8 } from "firebase/firestore";
|
|
6003
6017
|
async function listMyShips() {
|
|
6004
6018
|
const config2 = requireConfig();
|
|
6005
6019
|
const ids = Object.keys(config2.shipKeys ?? {});
|
|
@@ -6007,7 +6021,7 @@ async function listMyShips() {
|
|
|
6007
6021
|
ids.map(async (id) => {
|
|
6008
6022
|
try {
|
|
6009
6023
|
const { fb } = await openShipSession(id);
|
|
6010
|
-
const snap = await
|
|
6024
|
+
const snap = await getDoc8(doc9(fb.db, COLLECTIONS.ships, id));
|
|
6011
6025
|
return { id, ...snap.data() };
|
|
6012
6026
|
} catch {
|
|
6013
6027
|
return null;
|
|
@@ -6168,9 +6182,9 @@ async function runSetup(options) {
|
|
|
6168
6182
|
// src/cli/commands/status.ts
|
|
6169
6183
|
import {
|
|
6170
6184
|
collection as collection9,
|
|
6171
|
-
doc as
|
|
6185
|
+
doc as doc10,
|
|
6172
6186
|
getCountFromServer as getCountFromServer2,
|
|
6173
|
-
getDoc as
|
|
6187
|
+
getDoc as getDoc9,
|
|
6174
6188
|
getDocs as getDocs8,
|
|
6175
6189
|
query as query5,
|
|
6176
6190
|
where as where5
|
|
@@ -6208,8 +6222,8 @@ async function runStatus() {
|
|
|
6208
6222
|
});
|
|
6209
6223
|
continue;
|
|
6210
6224
|
}
|
|
6211
|
-
const shipRef =
|
|
6212
|
-
const mirrorSnap = await
|
|
6225
|
+
const shipRef = doc10(session.fb.db, COLLECTIONS.ships, shipId);
|
|
6226
|
+
const mirrorSnap = await getDoc9(doc10(shipRef, COLLECTIONS.runners, session.runnerId));
|
|
6213
6227
|
const mirror = mirrorSnap.data();
|
|
6214
6228
|
let queued = 0;
|
|
6215
6229
|
try {
|
|
@@ -6220,7 +6234,7 @@ async function runStatus() {
|
|
|
6220
6234
|
} catch {
|
|
6221
6235
|
queued = 0;
|
|
6222
6236
|
}
|
|
6223
|
-
const usageSnap = await
|
|
6237
|
+
const usageSnap = await getDoc9(doc10(shipRef, COLLECTIONS.usageDaily, utcDay(Date.now())));
|
|
6224
6238
|
const today = { ...EMPTY_USAGE_TOTALS, ...usageSnap.data()?.totals ?? {} };
|
|
6225
6239
|
const now = Date.now();
|
|
6226
6240
|
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.19",
|
|
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.",
|