@lumi.ai/runner 0.5.4 → 0.5.5
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 +91 -24
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,14 +10,14 @@ import {
|
|
|
10
10
|
deleteField,
|
|
11
11
|
doc as doc7,
|
|
12
12
|
getDoc as getDoc5,
|
|
13
|
-
getDocs as
|
|
13
|
+
getDocs as getDocs4,
|
|
14
14
|
onSnapshot as onSnapshot2,
|
|
15
|
-
orderBy as
|
|
16
|
-
query as
|
|
15
|
+
orderBy as orderBy4,
|
|
16
|
+
query as query4,
|
|
17
17
|
runTransaction as runTransaction2,
|
|
18
18
|
setDoc as setDoc2,
|
|
19
19
|
updateDoc as updateDoc2,
|
|
20
|
-
where as
|
|
20
|
+
where as where4
|
|
21
21
|
} from "firebase/firestore";
|
|
22
22
|
|
|
23
23
|
// ../shared/dist/engines/claude.js
|
|
@@ -117,6 +117,7 @@ function effectiveAgentTools(agent) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
// ../shared/dist/chat.js
|
|
120
|
+
var MAX_CHAT_MESSAGE_CHARS = 8e3;
|
|
120
121
|
var MAX_CHAT_MESSAGES_IN_PROMPT = 40;
|
|
121
122
|
|
|
122
123
|
// ../shared/dist/collections.js
|
|
@@ -580,7 +581,7 @@ function mcpUrl(config2) {
|
|
|
580
581
|
}
|
|
581
582
|
|
|
582
583
|
// src/version.ts
|
|
583
|
-
var RUNNER_VERSION = true ? "0.5.
|
|
584
|
+
var RUNNER_VERSION = true ? "0.5.5" : "0.0.0-dev";
|
|
584
585
|
|
|
585
586
|
// src/auth.ts
|
|
586
587
|
import { signInWithCustomToken } from "firebase/auth";
|
|
@@ -1864,8 +1865,13 @@ import {
|
|
|
1864
1865
|
addDoc,
|
|
1865
1866
|
collection as collection4,
|
|
1866
1867
|
doc as doc6,
|
|
1868
|
+
getDocs as getDocs3,
|
|
1869
|
+
limit as fsLimit,
|
|
1870
|
+
orderBy as orderBy3,
|
|
1871
|
+
query as query3,
|
|
1867
1872
|
runTransaction,
|
|
1868
|
-
updateDoc
|
|
1873
|
+
updateDoc,
|
|
1874
|
+
where as where3
|
|
1869
1875
|
} from "firebase/firestore";
|
|
1870
1876
|
import { ref as storageRef, uploadBytes } from "firebase/storage";
|
|
1871
1877
|
function redactTranscript(transcript, knownSecrets) {
|
|
@@ -1991,6 +1997,41 @@ Write again to start a fresh run.`;
|
|
|
1991
1997
|
createdAt: now
|
|
1992
1998
|
});
|
|
1993
1999
|
}
|
|
2000
|
+
var REPLY_SCAN_LIMIT = 20;
|
|
2001
|
+
function backstopReplyContent(resultText) {
|
|
2002
|
+
const text = resultText.trim();
|
|
2003
|
+
if (!text) {
|
|
2004
|
+
return "I finished that run without writing a reply. Write again to start a fresh one.";
|
|
2005
|
+
}
|
|
2006
|
+
if (text.length <= MAX_CHAT_MESSAGE_CHARS) return text;
|
|
2007
|
+
const marker = "\n\n\u2026(truncated)";
|
|
2008
|
+
return `${text.slice(0, MAX_CHAT_MESSAGE_CHARS - marker.length)}${marker}`;
|
|
2009
|
+
}
|
|
2010
|
+
async function ensureChatReply(db, shipId, job, resultText) {
|
|
2011
|
+
const chatRef = doc6(db, COLLECTIONS.ships, shipId, COLLECTIONS.chats, job.chatId);
|
|
2012
|
+
const messagesCol = collection4(chatRef, COLLECTIONS.chatMessages);
|
|
2013
|
+
const snap = await getDocs3(
|
|
2014
|
+
query3(
|
|
2015
|
+
messagesCol,
|
|
2016
|
+
where3("createdAt", ">=", job.startedAt || 0),
|
|
2017
|
+
orderBy3("createdAt", "desc"),
|
|
2018
|
+
fsLimit(REPLY_SCAN_LIMIT)
|
|
2019
|
+
)
|
|
2020
|
+
);
|
|
2021
|
+
const replied = snap.docs.some((d) => {
|
|
2022
|
+
const author = d.data().author;
|
|
2023
|
+
return author?.type === "agent" && author.id === job.agentId;
|
|
2024
|
+
});
|
|
2025
|
+
if (replied) return "agent-replied";
|
|
2026
|
+
const content = backstopReplyContent(resultText);
|
|
2027
|
+
await addDoc(messagesCol, {
|
|
2028
|
+
author: { type: "agent", id: job.agentId },
|
|
2029
|
+
content,
|
|
2030
|
+
chars: content.length,
|
|
2031
|
+
createdAt: Date.now()
|
|
2032
|
+
});
|
|
2033
|
+
return resultText.trim() ? "posted-final-text" : "posted-silence-note";
|
|
2034
|
+
}
|
|
1994
2035
|
|
|
1995
2036
|
// src/daemon.ts
|
|
1996
2037
|
var HEARTBEAT_MS = 3e4;
|
|
@@ -2110,11 +2151,11 @@ async function startDaemon() {
|
|
|
2110
2151
|
needsRefill.delete(shipId);
|
|
2111
2152
|
if (!serving.has(shipId) || approved.get(shipId) !== true) continue;
|
|
2112
2153
|
try {
|
|
2113
|
-
const snap = await
|
|
2114
|
-
|
|
2154
|
+
const snap = await getDocs4(
|
|
2155
|
+
query4(
|
|
2115
2156
|
collection5(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs),
|
|
2116
|
-
|
|
2117
|
-
|
|
2157
|
+
where4("status", "==", "queued"),
|
|
2158
|
+
orderBy4("createdAt", "asc")
|
|
2118
2159
|
)
|
|
2119
2160
|
);
|
|
2120
2161
|
for (const d of snap.docs) {
|
|
@@ -2142,10 +2183,10 @@ async function startDaemon() {
|
|
|
2142
2183
|
console.error(`${what} listener error (${shipId}):`, e.message);
|
|
2143
2184
|
};
|
|
2144
2185
|
for (const shipId of serving) {
|
|
2145
|
-
const q =
|
|
2186
|
+
const q = query4(
|
|
2146
2187
|
collection5(sess(shipId).fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.jobs),
|
|
2147
|
-
|
|
2148
|
-
|
|
2188
|
+
where4("status", "==", "queued"),
|
|
2189
|
+
orderBy4("createdAt", "asc")
|
|
2149
2190
|
);
|
|
2150
2191
|
unsubsByShip.set(shipId, [
|
|
2151
2192
|
onSnapshot2(
|
|
@@ -2396,6 +2437,7 @@ async function startDaemon() {
|
|
|
2396
2437
|
let sessionLimit = null;
|
|
2397
2438
|
let engineId = DEFAULT_ENGINE_ID;
|
|
2398
2439
|
let transcript = "";
|
|
2440
|
+
let resultText = "";
|
|
2399
2441
|
let usage = {
|
|
2400
2442
|
engine: DEFAULT_ENGINE_ID,
|
|
2401
2443
|
inputTokens: 0,
|
|
@@ -2496,6 +2538,7 @@ async function startDaemon() {
|
|
|
2496
2538
|
});
|
|
2497
2539
|
armLimitTimer();
|
|
2498
2540
|
}
|
|
2541
|
+
resultText = session.resultText;
|
|
2499
2542
|
if (!session.ok) failure = session.resultText || "Session failed.";
|
|
2500
2543
|
} catch (e) {
|
|
2501
2544
|
failure = e instanceof Error ? e.message : String(e);
|
|
@@ -2541,9 +2584,33 @@ async function startDaemon() {
|
|
|
2541
2584
|
mcpServers: extraMcpServers.map((s) => s.key)
|
|
2542
2585
|
});
|
|
2543
2586
|
log2(`Job ${job.id} done (${usage.inputTokens}in/${usage.outputTokens}out tokens).`);
|
|
2587
|
+
let delivery = "agent-replied";
|
|
2588
|
+
if (target.kind === "chat") {
|
|
2589
|
+
try {
|
|
2590
|
+
delivery = await ensureChatReply(
|
|
2591
|
+
sess(shipId).fb.db,
|
|
2592
|
+
shipId,
|
|
2593
|
+
{ ...job, chatId: target.chatId },
|
|
2594
|
+
resultText
|
|
2595
|
+
);
|
|
2596
|
+
if (delivery === "posted-final-text") {
|
|
2597
|
+
log2(
|
|
2598
|
+
`Job ${job.id}: the agent never called chat_send \u2014 posted its final text to chat ${target.chatId} instead.`
|
|
2599
|
+
);
|
|
2600
|
+
} else if (delivery === "posted-silence-note") {
|
|
2601
|
+
log2(
|
|
2602
|
+
`Job ${job.id}: the agent neither called chat_send nor produced any text \u2014 posted a note to chat ${target.chatId} so the thread is not silent.`
|
|
2603
|
+
);
|
|
2604
|
+
}
|
|
2605
|
+
} catch (e) {
|
|
2606
|
+
log2(
|
|
2607
|
+
`Job ${job.id}: could not check whether the agent replied in chat ${target.chatId} \u2014 ${e instanceof Error ? e.message : e}`
|
|
2608
|
+
);
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2544
2611
|
notify(
|
|
2545
2612
|
"Crew job finished",
|
|
2546
|
-
target.kind === "task" ? `Task ${target.taskId} is done.` : "Agent replied in a chat."
|
|
2613
|
+
target.kind === "task" ? `Task ${target.taskId} is done.` : delivery === "agent-replied" ? "Agent replied in a chat." : "Agent finished a chat run without replying \u2014 its answer was posted for it."
|
|
2547
2614
|
);
|
|
2548
2615
|
} else if (transient) {
|
|
2549
2616
|
await releaseJob(
|
|
@@ -2884,7 +2951,7 @@ function setParallel(config2, value, ship2) {
|
|
|
2884
2951
|
|
|
2885
2952
|
// src/cli/commands/doctor.ts
|
|
2886
2953
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2887
|
-
import { collection as collection6, doc as doc8, getDoc as getDoc6, getDocs as
|
|
2954
|
+
import { collection as collection6, doc as doc8, getDoc as getDoc6, getDocs as getDocs5 } from "firebase/firestore";
|
|
2888
2955
|
|
|
2889
2956
|
// src/service.ts
|
|
2890
2957
|
import { spawnSync } from "node:child_process";
|
|
@@ -3099,10 +3166,10 @@ function serviceStatus() {
|
|
|
3099
3166
|
};
|
|
3100
3167
|
}
|
|
3101
3168
|
if (process.platform === "win32") {
|
|
3102
|
-
const
|
|
3103
|
-
if (!
|
|
3169
|
+
const query6 = run("schtasks", ["/Query", "/TN", WINDOWS_TASK]);
|
|
3170
|
+
if (!query6.ok) return { state: "not-installed", detail: "No scheduled task installed." };
|
|
3104
3171
|
return {
|
|
3105
|
-
state: /\bRunning\b/i.test(
|
|
3172
|
+
state: /\bRunning\b/i.test(query6.out) ? "running" : "installed",
|
|
3106
3173
|
detail: "Scheduled task installed (runs at logon).",
|
|
3107
3174
|
unitPath: WINDOWS_TASK
|
|
3108
3175
|
};
|
|
@@ -3339,7 +3406,7 @@ async function checkShips(config2) {
|
|
|
3339
3406
|
}
|
|
3340
3407
|
let agents = [];
|
|
3341
3408
|
try {
|
|
3342
|
-
const snap = await
|
|
3409
|
+
const snap = await getDocs5(collection6(fb.db, COLLECTIONS.ships, shipId, COLLECTIONS.agents));
|
|
3343
3410
|
agents = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
|
|
3344
3411
|
} catch {
|
|
3345
3412
|
}
|
|
@@ -3864,9 +3931,9 @@ import {
|
|
|
3864
3931
|
doc as doc10,
|
|
3865
3932
|
getCountFromServer as getCountFromServer2,
|
|
3866
3933
|
getDoc as getDoc8,
|
|
3867
|
-
getDocs as
|
|
3868
|
-
query as
|
|
3869
|
-
where as
|
|
3934
|
+
getDocs as getDocs6,
|
|
3935
|
+
query as query5,
|
|
3936
|
+
where as where5
|
|
3870
3937
|
} from "firebase/firestore";
|
|
3871
3938
|
async function runStatus() {
|
|
3872
3939
|
const config2 = loadConfig();
|
|
@@ -3906,7 +3973,7 @@ async function runStatus() {
|
|
|
3906
3973
|
let queued = 0;
|
|
3907
3974
|
try {
|
|
3908
3975
|
const counted = await getCountFromServer2(
|
|
3909
|
-
|
|
3976
|
+
query5(collection7(shipRef, COLLECTIONS.jobs), where5("status", "==", "queued"))
|
|
3910
3977
|
);
|
|
3911
3978
|
queued = counted.data().count;
|
|
3912
3979
|
} catch {
|
|
@@ -3915,7 +3982,7 @@ async function runStatus() {
|
|
|
3915
3982
|
const usageSnap = await getDoc8(doc10(shipRef, COLLECTIONS.usageDaily, utcDay(Date.now())));
|
|
3916
3983
|
const today = { ...EMPTY_USAGE_TOTALS, ...usageSnap.data()?.totals ?? {} };
|
|
3917
3984
|
const now = Date.now();
|
|
3918
|
-
const limitsSnap = await
|
|
3985
|
+
const limitsSnap = await getDocs6(collection7(shipRef, COLLECTIONS.engineLimits));
|
|
3919
3986
|
const engineLimits = limitsSnap.docs.map((d) => ({ id: d.id, ...d.data() })).filter((l) => isEngineLimited(l, now));
|
|
3920
3987
|
ships.push({
|
|
3921
3988
|
shipId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumi.ai/runner",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
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.",
|