@tekmidian/pai 0.39.0 → 0.40.0

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 (126) hide show
  1. package/README.md +41 -0
  2. package/dist/{auto-route-BlWOWT4z.mjs → auto-route-BwzmlW3W.mjs} +2 -2
  3. package/dist/{auto-route-BlWOWT4z.mjs.map → auto-route-BwzmlW3W.mjs.map} +1 -1
  4. package/dist/{chain-CTtHligI.mjs → chain-DhVHVnmT.mjs} +263 -791
  5. package/dist/chain-DhVHVnmT.mjs.map +1 -0
  6. package/dist/cli/index.mjs +10 -8
  7. package/dist/cli/index.mjs.map +1 -1
  8. package/dist/cli/program.mjs +10 -8
  9. package/dist/{clusters-wlK0w41E.mjs → clusters-DRHhfli9.mjs} +1 -1
  10. package/dist/{clusters-wlK0w41E.mjs.map → clusters-DRHhfli9.mjs.map} +1 -1
  11. package/dist/{config-B64vFg14.mjs → config-D1G9IFpn.mjs} +50 -3
  12. package/dist/config-D1G9IFpn.mjs.map +1 -0
  13. package/dist/{config-C_ErGddD.mjs → config-DD52BPB-.mjs} +1 -1
  14. package/dist/config-Dl8lT4Lu.mjs +527 -0
  15. package/dist/config-Dl8lT4Lu.mjs.map +1 -0
  16. package/dist/{context-handover-cache-BpUojjsi.mjs → context-handover-cache-C6_rs7JT.mjs} +112 -24
  17. package/dist/context-handover-cache-C6_rs7JT.mjs.map +1 -0
  18. package/dist/daemon/index.mjs +12 -10
  19. package/dist/daemon/index.mjs.map +1 -1
  20. package/dist/daemon-BSL9sXxm.mjs +22 -0
  21. package/dist/{daemon-CTkN_Vn8.mjs → daemon-CpdVUnl_.mjs} +26 -26
  22. package/dist/{daemon-CTkN_Vn8.mjs.map → daemon-CpdVUnl_.mjs.map} +1 -1
  23. package/dist/daemon-mcp/index.mjs +127 -6
  24. package/dist/daemon-mcp/index.mjs.map +1 -1
  25. package/dist/detector-BLVOCiJ_.mjs +5 -0
  26. package/dist/{detector-Bwk_4Pk4.mjs → detector-smpCm7ku.mjs} +1 -1
  27. package/dist/{detector-Bwk_4Pk4.mjs.map → detector-smpCm7ku.mjs.map} +1 -1
  28. package/dist/{factory-A9x-T2Kg.mjs → factory-Bc5EyN54.mjs} +7 -7
  29. package/dist/{factory-A9x-T2Kg.mjs.map → factory-Bc5EyN54.mjs.map} +1 -1
  30. package/dist/factory-C7ua_3ik.mjs +3 -0
  31. package/dist/{providers-FYkZjn_C.mjs → fallback-CdXv-Np0.mjs} +411 -51
  32. package/dist/fallback-CdXv-Np0.mjs.map +1 -0
  33. package/dist/hooks/context-compression-hook.mjs +136 -22
  34. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  35. package/dist/hooks/post-compact-inject.mjs +98 -7
  36. package/dist/hooks/post-compact-inject.mjs.map +4 -4
  37. package/dist/hooks/route-agents-to-worker.mjs +75 -6
  38. package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
  39. package/dist/hooks/worker-proxy.mjs +75 -6
  40. package/dist/hooks/worker-proxy.mjs.map +2 -2
  41. package/dist/hooks/worker-status-line.mjs +149 -90
  42. package/dist/hooks/worker-status-line.mjs.map +4 -4
  43. package/dist/{indexer-backend-TG64CCQC.mjs → indexer-backend-rohVVKrA.mjs} +1 -1
  44. package/dist/{indexer-backend-TG64CCQC.mjs.map → indexer-backend-rohVVKrA.mjs.map} +1 -1
  45. package/dist/{ipc-client-BmypMNYk.mjs → ipc-client-C7AVpsyv.mjs} +2 -2
  46. package/dist/{ipc-client-BmypMNYk.mjs.map → ipc-client-C7AVpsyv.mjs.map} +1 -1
  47. package/dist/{kg-entity-r8duqhi9.mjs → kg-entity-DbOMPdF9.mjs} +1 -1
  48. package/dist/{kg-entity-r8duqhi9.mjs.map → kg-entity-DbOMPdF9.mjs.map} +1 -1
  49. package/dist/{latent-ideas-B7wq75Pt.mjs → latent-ideas-iNp5s55R.mjs} +2 -2
  50. package/dist/{latent-ideas-B7wq75Pt.mjs.map → latent-ideas-iNp5s55R.mjs.map} +1 -1
  51. package/dist/{link-boost-HkG7JWZR.mjs → link-boost-NNzADHX_.mjs} +1 -1
  52. package/dist/{link-boost-HkG7JWZR.mjs.map → link-boost-NNzADHX_.mjs.map} +1 -1
  53. package/dist/{main-resolver-DlaLOFBA.mjs → main-resolver-CM1IHbuu.mjs} +78 -75
  54. package/dist/main-resolver-CM1IHbuu.mjs.map +1 -0
  55. package/dist/main-resolver-E5WlilhM.mjs +6 -0
  56. package/dist/{neighborhood-lThN-MaQ.mjs → neighborhood-AGL7oXQ2.mjs} +1 -1
  57. package/dist/{neighborhood-lThN-MaQ.mjs.map → neighborhood-AGL7oXQ2.mjs.map} +1 -1
  58. package/dist/{note-context-b6k0mAKi.mjs → note-context-BHwYpSFf.mjs} +1 -1
  59. package/dist/{note-context-b6k0mAKi.mjs.map → note-context-BHwYpSFf.mjs.map} +1 -1
  60. package/dist/{planner-BDI7bE9B.mjs → planner-Cm3g6fWH.mjs} +5 -3
  61. package/dist/{planner-BDI7bE9B.mjs.map → planner-Cm3g6fWH.mjs.map} +1 -1
  62. package/dist/{postgres-mW1n7Vi1.mjs → postgres-Dwxe290H.mjs} +1 -1
  63. package/dist/{postgres-mW1n7Vi1.mjs.map → postgres-Dwxe290H.mjs.map} +1 -1
  64. package/dist/{program-DXBwJV7h.mjs → program-AR0qlYRg.mjs} +334 -78
  65. package/dist/program-AR0qlYRg.mjs.map +1 -0
  66. package/dist/query-feedback-CGcEiGtY.mjs +3 -0
  67. package/dist/{query-feedback-BV4CcxqS.mjs → query-feedback-DZfjI34w.mjs} +1 -1
  68. package/dist/{query-feedback-BV4CcxqS.mjs.map → query-feedback-DZfjI34w.mjs.map} +1 -1
  69. package/dist/router-CAtyVQud.mjs +3 -0
  70. package/dist/{router-DcHKnEPa.mjs → router-DHracTUC.mjs} +1 -1
  71. package/dist/{router-DcHKnEPa.mjs.map → router-DHracTUC.mjs.map} +1 -1
  72. package/dist/server-BOuAOj9b.mjs +386 -0
  73. package/dist/server-BOuAOj9b.mjs.map +1 -0
  74. package/dist/skills/Worker/SKILL.md +19 -1
  75. package/dist/{sources-kLnQsNrW.mjs → sources-ju6AA7lL.mjs} +1 -1
  76. package/dist/{sources-kLnQsNrW.mjs.map → sources-ju6AA7lL.mjs.map} +1 -1
  77. package/dist/{sqlite-BenGr3UP.mjs → sqlite-kFZeWCJ2.mjs} +1 -1
  78. package/dist/{sqlite-BenGr3UP.mjs.map → sqlite-kFZeWCJ2.mjs.map} +1 -1
  79. package/dist/{state-Ca9F_sZc.mjs → state-DH_mp5Xe.mjs} +1 -1
  80. package/dist/{state-CAeyOdfq.mjs → state-ahFAbD8b.mjs} +1 -1
  81. package/dist/{state-CAeyOdfq.mjs.map → state-ahFAbD8b.mjs.map} +1 -1
  82. package/dist/{themes-BI4GMRP9.mjs → themes-DqhNUUQk.mjs} +1 -1
  83. package/dist/{themes-BI4GMRP9.mjs.map → themes-DqhNUUQk.mjs.map} +1 -1
  84. package/dist/{tools-DGcB3o_S.mjs → tools-CMlHa9lZ.mjs} +14 -14
  85. package/dist/{tools-DGcB3o_S.mjs.map → tools-CMlHa9lZ.mjs.map} +1 -1
  86. package/dist/{tools-Bp7hj6OW.mjs → tools-COna9vZR.mjs} +1 -1
  87. package/dist/{trace-bobARFEX.mjs → trace-DTP6k4B_.mjs} +1 -1
  88. package/dist/{trace-bobARFEX.mjs.map → trace-DTP6k4B_.mjs.map} +1 -1
  89. package/dist/{vault-indexer-C3OfXTNF.mjs → vault-indexer-YMH22mLU.mjs} +1 -1
  90. package/dist/{vault-indexer-C3OfXTNF.mjs.map → vault-indexer-YMH22mLU.mjs.map} +1 -1
  91. package/dist/{work-queue-worker-R7UGIag_.mjs → work-queue-worker-B3nl8yyn.mjs} +23 -26
  92. package/dist/work-queue-worker-B3nl8yyn.mjs.map +1 -0
  93. package/dist/work-queue-worker-BM1GcAAq.mjs +14 -0
  94. package/dist/{zettelkasten-m5QPtb-L.mjs → zettelkasten-DDvKRm2V.mjs} +3 -3
  95. package/dist/{zettelkasten-m5QPtb-L.mjs.map → zettelkasten-DDvKRm2V.mjs.map} +1 -1
  96. package/docs/commands/README.md +5 -2
  97. package/docs/commands/worker.md +66 -5
  98. package/docs/images/provider-abstraction-live.png +0 -0
  99. package/docs/images/workers.png +0 -0
  100. package/docs/launcher-independence.md +70 -0
  101. package/docs/provider-abstraction.md +87 -0
  102. package/docs/provider-independence-details.md +330 -0
  103. package/docs/provider-independence.md +75 -0
  104. package/docs/worker.md +72 -11
  105. package/package.json +1 -1
  106. package/scripts/build-hooks.mjs +6 -1
  107. package/scripts/build-skill-stubs.mjs +17 -4
  108. package/src/hooks/ts/lib/context-fill.test.ts +158 -4
  109. package/src/hooks/ts/lib/context-fill.ts +102 -35
  110. package/statusline-command.sh +120 -21
  111. package/dist/chain-CTtHligI.mjs.map +0 -1
  112. package/dist/config-B64vFg14.mjs.map +0 -1
  113. package/dist/context-handover-cache-BpUojjsi.mjs.map +0 -1
  114. package/dist/daemon-CmHHmKpg.mjs +0 -20
  115. package/dist/detector-DO730Zq0.mjs +0 -5
  116. package/dist/factory-BD-np0Vd.mjs +0 -3
  117. package/dist/main-resolver-D6IImXvF.mjs +0 -4
  118. package/dist/main-resolver-DlaLOFBA.mjs.map +0 -1
  119. package/dist/program-DXBwJV7h.mjs.map +0 -1
  120. package/dist/providers-FYkZjn_C.mjs.map +0 -1
  121. package/dist/query-feedback-DSVyHtrG.mjs +0 -3
  122. package/dist/router-Bk77E7hj.mjs +0 -3
  123. package/dist/runtime-paths-rni52zHX.mjs +0 -50
  124. package/dist/runtime-paths-rni52zHX.mjs.map +0 -1
  125. package/dist/work-queue-worker-DW8lz-Oo.mjs +0 -11
  126. package/dist/work-queue-worker-R7UGIag_.mjs.map +0 -1
@@ -131,6 +131,7 @@ var CONFIG_DIR = join(homedir(), ".config", "pai");
131
131
  var CONFIG_FILE = join(CONFIG_DIR, "config.json");
132
132
 
133
133
  // src/workers/config.ts
134
+ var MODEL_TIERS = ["haiku", "sonnet", "opus"];
134
135
  var PROVIDER_TAGS = [
135
136
  "code",
136
137
  "vision",
@@ -139,6 +140,10 @@ var PROVIDER_TAGS = [
139
140
  "fast",
140
141
  "reasoning"
141
142
  ];
143
+ var MODEL_CAPABILITIES = ["default", "fast", "image"];
144
+ function isModelCapability(v) {
145
+ return MODEL_CAPABILITIES.includes(v);
146
+ }
142
147
  var DEFAULT_LOG_DIR = "~/.claude/logs/workers";
143
148
  var DEFAULT_PANE = {
144
149
  enabled: true,
@@ -167,7 +172,8 @@ function defaultWorkersConfig() {
167
172
  pane: { ...DEFAULT_PANE },
168
173
  logDir: DEFAULT_LOG_DIR,
169
174
  routing: { ...DEFAULT_ROUTING2, order: [] },
170
- tree: { ...DEFAULT_TREE }
175
+ tree: { ...DEFAULT_TREE },
176
+ fallback: null
171
177
  };
172
178
  }
173
179
  var WorkersConfigError = class extends Error {
@@ -199,7 +205,35 @@ function parseProvider(name, raw) {
199
205
  const m = modelsRaw;
200
206
  const defaultModel = str(m.default);
201
207
  if (!defaultModel) bad(`.providers.${name}.models.default`, "is required");
202
- const fast = m.fast === void 0 ? void 0 : str(m.fast);
208
+ const models = { default: defaultModel };
209
+ for (const key of Object.keys(m)) {
210
+ if (key === "default") continue;
211
+ if (!isModelCapability(key)) {
212
+ bad(
213
+ `.providers.${name}.models.${key}`,
214
+ `"${key}" is not a model capability (from: ${MODEL_CAPABILITIES.join(", ")})`
215
+ );
216
+ }
217
+ const id = str(m[key]);
218
+ if (!id) bad(`.providers.${name}.models.${key}`, "must be a non-empty model id");
219
+ models[key] = id;
220
+ }
221
+ let modelTiers;
222
+ if (p.modelTiers !== void 0) {
223
+ if (typeof p.modelTiers !== "object" || p.modelTiers === null || Array.isArray(p.modelTiers)) {
224
+ bad(`.providers.${name}.modelTiers`, "must be an object of model id \u2192 haiku | sonnet | opus");
225
+ }
226
+ modelTiers = {};
227
+ for (const [id, tier] of Object.entries(p.modelTiers)) {
228
+ if (typeof tier !== "string" || !MODEL_TIERS.includes(tier)) {
229
+ bad(
230
+ `.providers.${name}.modelTiers.${id}`,
231
+ `must be "haiku", "sonnet" or "opus" (got ${JSON.stringify(tier)})`
232
+ );
233
+ }
234
+ modelTiers[id] = tier;
235
+ }
236
+ }
203
237
  const env = {};
204
238
  if (p.env !== void 0) {
205
239
  if (typeof p.env !== "object" || p.env === null || Array.isArray(p.env)) {
@@ -249,7 +283,8 @@ function parseProvider(name, raw) {
249
283
  protocol,
250
284
  baseUrl,
251
285
  keyFile,
252
- models: fast ? { default: defaultModel, fast } : { default: defaultModel },
286
+ models,
287
+ ...modelTiers ? { modelTiers } : {},
253
288
  env,
254
289
  ...str(p.note) ? { note: str(p.note) } : {},
255
290
  ...upstreamUrl ? { upstreamUrl } : {},
@@ -413,6 +448,39 @@ function parseWorkersConfig(raw) {
413
448
  const active = w.active === void 0 || w.active === null ? null : str(w.active);
414
449
  if (active !== null && active !== "auto" && !(active in providers)) {
415
450
  }
451
+ let fallback = null;
452
+ if (w.fallback !== void 0 && w.fallback !== null) {
453
+ if (typeof w.fallback !== "object" || Array.isArray(w.fallback)) {
454
+ bad(".fallback", "must be an object (written by `pai worker fallback on`)");
455
+ }
456
+ const f = w.fallback;
457
+ const provider = str(f.provider);
458
+ if (!provider) bad(".fallback.provider", "is required");
459
+ const savedRaw = f.saved;
460
+ if (typeof savedRaw !== "object" || savedRaw === null || Array.isArray(savedRaw)) {
461
+ bad(".fallback.saved", "must be an object");
462
+ }
463
+ const s = savedRaw;
464
+ if (typeof s.env !== "object" || s.env === null || Array.isArray(s.env)) {
465
+ bad(".fallback.saved.env", "must be an object of env key \u2192 previous value or null");
466
+ }
467
+ const env = {};
468
+ for (const [k, v] of Object.entries(s.env)) {
469
+ env[k] = v === null || typeof v === "string" ? v : null;
470
+ }
471
+ if (s.model !== null && s.model !== void 0 && typeof s.model !== "string") {
472
+ bad(".fallback.saved.model", "must be a string or null");
473
+ }
474
+ fallback = {
475
+ provider,
476
+ saved: {
477
+ env,
478
+ model: typeof s.model === "string" ? s.model : null,
479
+ envExisted: s.envExisted === true
480
+ },
481
+ on: str(f.on) || (/* @__PURE__ */ new Date(0)).toISOString()
482
+ };
483
+ }
416
484
  return {
417
485
  enabled: w.enabled === void 0 ? d.enabled : w.enabled === true,
418
486
  active,
@@ -422,11 +490,12 @@ function parseWorkersConfig(raw) {
422
490
  pane,
423
491
  logDir: str(w.logDir) || d.logDir,
424
492
  routing,
425
- tree
493
+ tree,
494
+ fallback
426
495
  };
427
496
  }
428
- function readWorkersSection() {
429
- const raw = readJsonStrict(CONFIG_FILE, "~/.config/pai/config.json");
497
+ function readWorkersSection(path = CONFIG_FILE) {
498
+ const raw = readJsonStrict(path, "~/.config/pai/config.json");
430
499
  return { raw, workers: parseWorkersConfig(raw.workers) };
431
500
  }
432
501
  function expandHome(p) {
@@ -439,13 +508,23 @@ function workersLogDir(config) {
439
508
  return expandHome(config.logDir);
440
509
  }
441
510
 
511
+ // src/workers/scope.ts
512
+ import { existsSync as existsSync3, readFileSync as readFileSync3, renameSync as renameSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "node:fs";
513
+ import { homedir as homedir3 } from "node:os";
514
+ import { join as join4 } from "node:path";
515
+
442
516
  // src/workers/status.ts
443
517
  import { existsSync as existsSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2, readdirSync } from "node:fs";
444
518
  import { join as join3 } from "node:path";
445
- function contextPercent(s) {
446
- if (!s.contextTokens || !s.contextWindow) return null;
447
- return Math.round(s.contextTokens / s.contextWindow * 100);
519
+
520
+ // src/workers/args.ts
521
+ function shortText(s, n) {
522
+ const t = String(s ?? "").split(/\s+/).filter(Boolean).join(" ");
523
+ return t.length <= n ? t : t.slice(0, n - 1) + "\u2026";
448
524
  }
525
+
526
+ // src/workers/status.ts
527
+ var UNLABELED = "unlabeled";
449
528
  function loadStatuses(logDir) {
450
529
  if (!existsSync2(logDir)) return [];
451
530
  const out = [];
@@ -475,8 +554,6 @@ function ageOf(ts, now = /* @__PURE__ */ new Date()) {
475
554
  }
476
555
 
477
556
  // src/workers/scope.ts
478
- import { homedir as homedir3 } from "node:os";
479
- import { join as join4 } from "node:path";
480
557
  var AIBROKER_REGISTRY = join4(homedir3(), ".aibroker", "session-names.json");
481
558
  function tabKey(term2) {
482
559
  if (!term2) return "";
@@ -497,41 +574,36 @@ function workerInScope(worker, term2) {
497
574
  if (worker.session?.id && uuid) return worker.session.id === uuid;
498
575
  return tabKey(worker.term) === tabKey(term2) && tabKey(term2) !== "";
499
576
  }
500
-
501
- // src/workers/handoff.ts
502
- import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3 } from "node:fs";
503
- import { dirname, join as join5 } from "node:path";
504
- function inboxPath(logDir, id) {
505
- return join5(logDir, `${id}.inbox.jsonl`);
577
+ var SPAWNER_SESSION_TTL_MS = 10 * 6e4;
578
+ var SESSION_MAP_PRUNE_MS = 60 * 6e4;
579
+ function sessionMapPath(logDir) {
580
+ return join4(logDir, "claude-session-map.json");
506
581
  }
507
- function readInbox(logDir, id) {
508
- const path = inboxPath(logDir, id);
509
- if (!existsSync3(path)) return [];
510
- const out = [];
511
- for (const line of readFileSync3(path, "utf8").split("\n")) {
512
- if (!line.trim()) continue;
513
- try {
514
- out.push(JSON.parse(line));
515
- } catch {
582
+ function recordSessionMapEntry(logDir, cwd2, session, now = Date.now()) {
583
+ if (!cwd2 || !session) return;
584
+ const path = sessionMapPath(logDir);
585
+ let map = {};
586
+ try {
587
+ if (existsSync3(path)) {
588
+ map = JSON.parse(readFileSync3(path, "utf8"));
516
589
  }
590
+ } catch {
591
+ map = {};
592
+ }
593
+ const prev = map[cwd2];
594
+ if (prev && prev.session === session && now - prev.ts < 6e4) return;
595
+ const pruned = {};
596
+ for (const [dir, e] of Object.entries(map)) {
597
+ if (now - e.ts < SESSION_MAP_PRUNE_MS) pruned[dir] = e;
598
+ }
599
+ pruned[cwd2] = { session, ts: now };
600
+ try {
601
+ mkdirSync2(logDir, { recursive: true });
602
+ const tmp = `${path}.tmp`;
603
+ writeFileSync3(tmp, JSON.stringify(pruned), "utf8");
604
+ renameSync3(tmp, path);
605
+ } catch {
517
606
  }
518
- return out;
519
- }
520
-
521
- // src/workers/tree.ts
522
- function workerDepth(statuses, id) {
523
- const byId = new Map(statuses.map((s) => [s.id, s]));
524
- let depth = 0;
525
- let cur = byId.get(id);
526
- const seen = /* @__PURE__ */ new Set([id]);
527
- while (cur?.parent && !seen.has(cur.parent)) {
528
- seen.add(cur.parent);
529
- const next = byId.get(cur.parent);
530
- if (!next) break;
531
- depth += 1;
532
- cur = next;
533
- }
534
- return depth;
535
607
  }
536
608
 
537
609
  // src/workers/report.ts
@@ -544,6 +616,8 @@ var WORKER_CONTRACT_PROMPT = [
544
616
  "changed: files you touched (path + one-line summary). commands: the commands that verify the work.",
545
617
  "checks: each with ok true/false and the evidence in detail. open: what you could not finish, if anything.",
546
618
  "notes: one line, the headline a reviewer reads first.",
619
+ "Never write shell heredocs \u2014 write the script to a file with the Write tool, then run it",
620
+ "(heredocs fail silently or get swallowed).",
547
621
  "",
548
622
  `Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,
549
623
  "run (the prompt of a resumed run is the operator's too). Answer it FIRST, in one or two plain lines \u2014",
@@ -560,76 +634,61 @@ var WORKER_CONTRACT_PROMPT = [
560
634
  ].join("\n");
561
635
 
562
636
  // src/workers/render.ts
563
- var CODES = {
564
- dim: "2",
565
- bold: "1",
566
- red: "31",
567
- green: "32",
568
- yellow: "33",
569
- blue: "34",
570
- mag: "35",
571
- cyan: "36"
572
- };
573
- function makeColor(enabled) {
574
- return (name, s) => enabled ? `\x1B[${CODES[name]}m${s}\x1B[0m` : s;
575
- }
576
- function fmtK(n) {
577
- return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
578
- }
579
- function contextMeter(c, s, minPct = 0) {
580
- const pct = contextPercent(s);
581
- if (pct === null || pct <= minPct) return null;
582
- const label = `ctx ${fmtK(s.contextTokens ?? 0)}/${fmtK(s.contextWindow ?? 0)} (${pct}%)`;
583
- if (pct > 85) return c("red", label);
584
- if (pct > 70) return c("yellow", label);
585
- return label;
637
+ function stepOf(last) {
638
+ const s = String(last ?? "").trim();
639
+ if (s.startsWith("Bash: ")) {
640
+ const cmd = s.slice(6).trim();
641
+ const verb = cmd.split(/\s+/)[0] ?? "";
642
+ return shortText(cmd.length > verb.length ? `${verb} \u2026` : verb, 24);
643
+ }
644
+ return shortText(s, 24);
586
645
  }
587
- function renderStatusLine(mine, now = /* @__PURE__ */ new Date(), c = makeColor(true), all = mine, inbox = {}) {
646
+ function renderStatusLine(mine, now = /* @__PURE__ */ new Date()) {
588
647
  if (!mine.length) return "";
589
- const running = mine.filter((s) => s.state === "running" && alive(s.pid)).sort((a, b) => workerDepth(all, a.id) - workerDepth(all, b.id));
648
+ const isChat = (s) => s.origin === "chat" || !s.origin && (s.label === UNLABELED || s.label === "(no prompt)") && s.turns === 0;
649
+ const chat = mine.find((s) => isChat(s) && s.state === "running" && alive(s.pid));
650
+ const running = mine.filter((s) => !isChat(s) && s.state === "running" && alive(s.pid)).sort((a, b) => a.started.localeCompare(b.started));
590
651
  const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
591
652
  const doneToday = mine.filter((s) => s.state !== "running" && s.started.startsWith(today));
592
653
  const ok = doneToday.filter((s) => s.state === "done").length;
593
654
  const bad2 = doneToday.length - ok;
594
- const providers = new Set(running.map((s) => s.provider));
595
- const providerTag = providers.size === 1 ? [...providers][0] : "workers";
596
- let head = `${providerTag} \u25B6${running.length}`;
597
- const parts = running.slice(0, 3).map((s) => {
598
- const meter = contextMeter(c, s, 60);
599
- const depth = workerDepth(all, s.id);
600
- const lead = depth > 0 ? " ".repeat(depth - 1) + "\u21B3 " : "";
601
- const box = inbox[s.id] ? ` \u25C6${inbox[s.id]}` : "";
602
- return `${lead}${s.id.slice(-4)} ${s.label.slice(0, 26)} ${ageOf(s.started, now)} \xB7 ${s.last.slice(0, 30)}${box}` + (meter ? ` ${meter}` : "");
655
+ const rows = running.slice(0, 3).map((s) => {
656
+ const twin = running.some((o) => o !== s && o.label === s.label);
657
+ const name = `${twin ? `#${s.id.slice(-4)} ` : ""}${s.label.slice(0, 26)}`;
658
+ return `${name} \xB7 ${ageOf(s.started, now)} \xB7 ${stepOf(s.last)}`;
603
659
  });
604
- if (parts.length) head += " " + parts.join(" | ");
660
+ let head;
661
+ if (chat) head = chat.provider;
662
+ else {
663
+ const providers = new Set(running.map((s) => s.provider));
664
+ head = providers.size === 1 ? [...providers][0] : "workers";
665
+ }
666
+ if (running.length) head += ` \u25B6${running.length}`;
667
+ if (rows.length) head += " | " + rows.join(" | ");
605
668
  if (doneToday.length) head += ` \u2713${ok} \u2717${bad2} today`;
606
669
  return head;
607
670
  }
608
671
 
609
672
  // src/workers/viewer.ts
610
- function inboxCounts(logDir, statuses) {
611
- const out = {};
612
- for (const s of statuses) {
613
- const n = readInbox(logDir, s.id).length;
614
- if (n) out[s.id] = n;
615
- }
616
- return out;
617
- }
618
- function statusLineOutput(logDir, term2, cwd2, now = /* @__PURE__ */ new Date()) {
673
+ function statusLineOutput(logDir, term2, cwd2, claudeSession2 = "", now = /* @__PURE__ */ new Date()) {
619
674
  const statuses = loadStatuses(logDir);
620
675
  const mine = statuses.filter((s) => {
621
676
  const sameScope = term2 && workerInScope(s, term2);
677
+ const spawnedHere = claudeSession2 && s.spawnerSession === claudeSession2;
622
678
  const sameDir = cwd2 && s.cwd.startsWith(cwd2);
623
- return sameScope || !term2 && sameDir;
679
+ return sameScope || spawnedHere || !term2 && sameDir;
624
680
  });
625
- return renderStatusLine(mine, now, makeColor(false), statuses, inboxCounts(logDir, mine));
681
+ return renderStatusLine(mine, now);
626
682
  }
627
683
 
628
684
  // src/workers/standalone/status-line.ts
629
685
  var term = process.argv[2] ?? process.env.ITERM_SESSION_ID ?? "";
630
686
  var cwd = process.argv[3] ?? process.cwd();
687
+ var claudeSession = process.argv[4] ?? "";
631
688
  try {
632
- const out = statusLineOutput(workersLogDir(readWorkersSection().workers), term, cwd);
689
+ const logDir = workersLogDir(readWorkersSection().workers);
690
+ if (claudeSession) recordSessionMapEntry(logDir, cwd, claudeSession);
691
+ const out = statusLineOutput(logDir, term, cwd, claudeSession);
633
692
  if (out) process.stdout.write(out + "\n");
634
693
  } catch {
635
694
  }