@lumi.ai/runner 0.5.3 → 0.5.4

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 +55 -1
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -474,6 +474,11 @@ var DEFAULT_SHIP_SETTINGS = {
474
474
  };
475
475
  var SHIP_INVITE_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
476
476
 
477
+ // ../shared/dist/taskRelation.js
478
+ function taskBlockedBy(task) {
479
+ return Array.isArray(task?.blockedBy) ? task.blockedBy : [];
480
+ }
481
+
477
482
  // ../shared/dist/usage.js
478
483
  var EMPTY_USAGE_TOTALS = {
479
484
  inputTokens: 0,
@@ -575,7 +580,7 @@ function mcpUrl(config2) {
575
580
  }
576
581
 
577
582
  // src/version.ts
578
- var RUNNER_VERSION = true ? "0.5.3" : "0.0.0-dev";
583
+ var RUNNER_VERSION = true ? "0.5.4" : "0.0.0-dev";
579
584
 
580
585
  // src/auth.ts
581
586
  import { signInWithCustomToken } from "firebase/auth";
@@ -958,6 +963,38 @@ async function withFirestoreRetry(read, sleep2 = (ms) => new Promise((r) => setT
958
963
  // src/jobs/contextPack.ts
959
964
  var MAX_ACTIVITY_IN_PROMPT = 40;
960
965
  var MAX_PREVIOUS_JOBS_READ = 5;
966
+ var MAX_BLOCKERS_IN_PROMPT = 20;
967
+ async function loadSettledBlockers(shipRef, task) {
968
+ const ids = taskBlockedBy(task).slice(0, MAX_BLOCKERS_IN_PROMPT);
969
+ if (ids.length === 0) return [];
970
+ try {
971
+ return await Promise.all(
972
+ ids.map(async (id) => {
973
+ const ref = doc(shipRef, COLLECTIONS.tasks, id);
974
+ const [snap, resultSnap] = await Promise.all([
975
+ getDoc(ref),
976
+ getDocs(
977
+ query(
978
+ collection(ref, COLLECTIONS.activity),
979
+ where("kind", "==", "result"),
980
+ orderBy("createdAt", "desc"),
981
+ limit(1)
982
+ )
983
+ ).catch(() => null)
984
+ ]);
985
+ const data = snap.data();
986
+ return {
987
+ id,
988
+ title: data?.title ?? id,
989
+ status: data?.status ?? "unknown",
990
+ result: resultSnap?.docs[0]?.data()?.content ?? null
991
+ };
992
+ })
993
+ );
994
+ } catch {
995
+ return [];
996
+ }
997
+ }
961
998
  async function loadJobContext(db, shipId, job) {
962
999
  const shipRef = doc(db, COLLECTIONS.ships, shipId);
963
1000
  const taskRef = doc(shipRef, COLLECTIONS.tasks, job.taskId);
@@ -1025,11 +1062,13 @@ async function loadJobContext(db, shipId, job) {
1025
1062
  }
1026
1063
  const activityDocs = activitySnap.docs.slice(0, MAX_ACTIVITY_IN_PROMPT);
1027
1064
  const agent = { id: agentSnap.id, ...agentSnap.data() };
1065
+ const settledBlockers = job.reason === "unblocked" ? await loadSettledBlockers(shipRef, task) : [];
1028
1066
  return {
1029
1067
  ship: { id: shipSnap.id, ...shipSnap.data() },
1030
1068
  agent,
1031
1069
  task,
1032
1070
  parentTask,
1071
+ settledBlockers,
1033
1072
  workflow,
1034
1073
  memory: readMemory(agent.memory),
1035
1074
  knowledgeIndex: indexSnap?.exists() ? indexSnap.data() : null,
@@ -1113,6 +1152,10 @@ function buildPrompt(ctx, reason, mcpServers = []) {
1113
1152
  parts.push(
1114
1153
  "# Why this session started\n\nYou asked a captain for permission and stopped. They have now answered \u2014 their decision is the newest entry in the task activity below. Read it first.\n\n**If they approved it, do that thing now** \u2014 the permission is granted for this task and may be single-use, so do not ask again for the same thing. **If they refused, do not retry and do not look for a way around it**: say what you will do instead, or hand the task back with task_assign."
1115
1154
  );
1155
+ } else if (reason === "unblocked") {
1156
+ parts.push(
1157
+ '# Why this session started\n\nThis task was blocked and is not any more: everything it was waiting on is now done. Nobody has just assigned it to you \u2014 you have had it all along, and the work has become startable.\n\n**Read "What you were waiting on" below before you do anything else.** It carries what those tasks produced, which is the input this work was held up for; starting without it means redoing or contradicting somebody else' + (playbook ? ", then follow your playbook below." : "'s work.")
1158
+ );
1116
1159
  } else if (reason === "schedule") {
1117
1160
  parts.push(
1118
1161
  "# Why this session started\n\nThis is a scheduled run: one of your own playbooks created this task on its cron and assigned it to you. It is routine work, not a request from a person, so nobody is waiting on a reply" + (playbook ? " \u2014 the playbook below is the work, and the task's description is only a record of why it exists." : ".")
@@ -1158,6 +1201,17 @@ ${t.description || "(no description)"}
1158
1201
 
1159
1202
  Labels: ${t.labels.join(", ") || "none"} \xB7 Status: ${statusLabel} (\`${t.status}\`)` + dates
1160
1203
  );
1204
+ if (ctx.settledBlockers.length > 0) {
1205
+ const blockers = ctx.settledBlockers.map((b) => {
1206
+ const label = statusById(statuses, b.status)?.label ?? b.status;
1207
+ return `## ${b.title} (id: ${b.id}) \u2014 ${label}
1208
+
1209
+ ` + (b.result ? b.result : "It left no result note. Open it with task_get if you need to know what it did.");
1210
+ }).join("\n\n");
1211
+ parts.push(`# What you were waiting on
1212
+
1213
+ ${blockers}`);
1214
+ }
1161
1215
  if (ctx.parentTask) {
1162
1216
  const p = ctx.parentTask;
1163
1217
  const parentStatus = statusById(statuses, p.status)?.label ?? p.status;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumi.ai/runner",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
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.",