@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.
- package/dist/cli.js +98 -92
- 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,
|
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
-
() =>
|
|
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
|
|
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)
|
|
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:
|
|
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:
|
|
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:
|
|
2765
|
+
if (!key) return { secrets: null };
|
|
2758
2766
|
return {
|
|
2759
2767
|
secrets: {
|
|
2760
|
-
|
|
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
|
|
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
|
|
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
|
|
2900
|
-
getDoc as
|
|
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 =
|
|
2947
|
-
const jobRef =
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
3038
|
-
const taskRef =
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
3088
|
-
const chatRef =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
4367
|
-
|
|
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(
|
|
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
|
|
4422
|
-
|
|
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 =
|
|
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
|
|
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
|
|
5427
|
-
|
|
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(
|
|
5482
|
-
|
|
5483
|
-
|
|
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
|
|
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
|
|
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
|
|
6143
|
+
doc as doc10,
|
|
6138
6144
|
getCountFromServer as getCountFromServer2,
|
|
6139
|
-
getDoc as
|
|
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 =
|
|
6178
|
-
const mirrorSnap = await
|
|
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
|
|
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.
|
|
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.",
|