pi-harness-runtime 1.1.226 → 1.1.228

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 (102) hide show
  1. package/harness-startup-status.ts +27 -0
  2. package/index.ts +105 -99
  3. package/package.json +1 -1
  4. package/packages/a2a-adapter/package.json +1 -1
  5. package/packages/agent-policy/package.json +1 -1
  6. package/packages/architecture-generator/package.json +1 -1
  7. package/packages/auth/package.json +1 -1
  8. package/packages/autonomous-refactor/package.json +1 -1
  9. package/packages/autonomous-runtime/package.json +1 -1
  10. package/packages/cache-strategy/package.json +1 -1
  11. package/packages/capability-registry/package.json +1 -1
  12. package/packages/checkpoint/package.json +1 -1
  13. package/packages/cli-plugin-sdk/package.json +1 -1
  14. package/packages/clipboard/package.json +1 -1
  15. package/packages/clipboard-plugin/package.json +1 -1
  16. package/packages/code-generation/package.json +1 -1
  17. package/packages/code-review/package.json +1 -1
  18. package/packages/codex-adapter/package.json +1 -1
  19. package/packages/codex-watcher/package.json +1 -1
  20. package/packages/config-capture/package.json +1 -1
  21. package/packages/context-compiler/package.json +1 -1
  22. package/packages/context-discovery/package.json +1 -1
  23. package/packages/context-manager/package.json +1 -1
  24. package/packages/cookie-sanitizer/package.json +1 -1
  25. package/packages/cost-optimizer/package.json +1 -1
  26. package/packages/dependency-analyzer/package.json +1 -1
  27. package/packages/django-plugin/package.json +1 -1
  28. package/packages/doc-generator/package.json +1 -1
  29. package/packages/e2e-tester/package.json +1 -1
  30. package/packages/evaluation-engine/package.json +1 -1
  31. package/packages/evaluation-runner/package.json +1 -1
  32. package/packages/event-bus/package.json +1 -1
  33. package/packages/event-store/package.json +1 -1
  34. package/packages/experience-replay/package.json +1 -1
  35. package/packages/feedback-collector/package.json +1 -1
  36. package/packages/file-copy-helper/package.json +1 -1
  37. package/packages/framework-detector/package.json +1 -1
  38. package/packages/framework-plugin-sdk/package.json +1 -1
  39. package/packages/frappe-plugin/package.json +1 -1
  40. package/packages/generic-web-plugin/package.json +1 -1
  41. package/packages/health-monitor/package.json +1 -1
  42. package/packages/intent-analyzer/package.json +1 -1
  43. package/packages/jev-judge/package.json +1 -1
  44. package/packages/knowledge-graph/package.json +1 -1
  45. package/packages/knowledge-retrieval/package.json +1 -1
  46. package/packages/laravel-plugin/package.json +1 -1
  47. package/packages/learning-engine/package.json +1 -1
  48. package/packages/mcp-adapter/package.json +1 -1
  49. package/packages/memory-engine/package.json +1 -1
  50. package/packages/milestone-manager/package.json +1 -1
  51. package/packages/model-registry/package.json +1 -1
  52. package/packages/nextjs-plugin/package.json +1 -1
  53. package/packages/notification/package.json +1 -1
  54. package/packages/observability/package.json +1 -1
  55. package/packages/okf-indexer/package.json +1 -1
  56. package/packages/paperclip-integration/package.json +1 -1
  57. package/packages/performance-optimizer/package.json +1 -1
  58. package/packages/privilege-broker/package.json +1 -1
  59. package/packages/project-analyzer/package.json +1 -1
  60. package/packages/project-bootstrap/package.json +1 -1
  61. package/packages/projection-engine/package.json +1 -1
  62. package/packages/prompt-compiler/package.json +1 -1
  63. package/packages/prompt-versioning/package.json +1 -1
  64. package/packages/provider-adapter-sdk/package.json +1 -1
  65. package/packages/provider-router/package.json +1 -1
  66. package/packages/provider-selector/package.json +1 -1
  67. package/packages/providers/package.json +1 -1
  68. package/packages/qdrant-skills/package.json +1 -1
  69. package/packages/quota-manager/package.json +1 -1
  70. package/packages/rate-limiter/package.json +1 -1
  71. package/packages/react-vite-plugin/package.json +1 -1
  72. package/packages/release-manager/package.json +1 -1
  73. package/packages/requirement-compiler/package.json +1 -1
  74. package/packages/runtime/package.json +1 -1
  75. package/packages/scheduler/package.json +1 -1
  76. package/packages/scheduler-adapter/package.json +1 -1
  77. package/packages/session/package.json +1 -1
  78. package/packages/session-api/package.json +1 -1
  79. package/packages/session-export/package.json +1 -1
  80. package/packages/shared-context/package.json +1 -1
  81. package/packages/skill-mcp-client/package.json +1 -1
  82. package/packages/skill-registry/package.json +1 -1
  83. package/packages/skills/package.json +1 -1
  84. package/packages/sprint-planner/package.json +1 -1
  85. package/packages/subscription-engine/package.json +1 -1
  86. package/packages/task-compiler/package.json +1 -1
  87. package/packages/telegram/package.json +1 -1
  88. package/packages/tencentdb-memory/package.json +1 -1
  89. package/packages/tencentdb-sync/package.json +1 -1
  90. package/packages/test-data-generator/package.json +1 -1
  91. package/packages/test-generator/package.json +1 -1
  92. package/packages/todo-bd-sync/package.json +1 -1
  93. package/packages/token-estimation/package.json +1 -1
  94. package/packages/token-optimizer/package.json +1 -1
  95. package/packages/trajectory/package.json +1 -1
  96. package/packages/tui/package.json +1 -1
  97. package/packages/types/package.json +1 -1
  98. package/packages/webmcp-tester/package.json +1 -1
  99. package/packages/workflow-events/package.json +1 -1
  100. package/packages/workspace-scanner/package.json +1 -1
  101. package/packages/worktree/package.json +1 -1
  102. package/packages/write-review/package.json +1 -1
@@ -0,0 +1,27 @@
1
+ export type HarnessStatus = "wait" | "ok" | "warn" | "err";
2
+
3
+ export interface HarnessMessage {
4
+ id: string;
5
+ label: string;
6
+ text: string;
7
+ status: HarnessStatus;
8
+ details?: string[];
9
+ }
10
+
11
+ /** One store for initialization before, during, and after the widget mounts. */
12
+ export class HarnessStartupStatus {
13
+ readonly messages: HarnessMessage[] = [];
14
+ onChange?: () => void;
15
+
16
+ update(label: string, text: string, status: HarnessStatus = "wait", details?: string[]): void {
17
+ const next = { id: label, label, text, status, details };
18
+ const existing = this.messages.findIndex((message) => message.id === label);
19
+ if (existing === -1) this.messages.push(next);
20
+ else this.messages[existing] = next;
21
+ this.onChange?.();
22
+ }
23
+ }
24
+
25
+ export function harnessStatusMarker(status: HarnessStatus): string {
26
+ return { wait: "[WAIT]", ok: "[OK]", warn: "[WARN]", err: "[ERR]" }[status];
27
+ }
package/index.ts CHANGED
@@ -19,6 +19,7 @@ import type {
19
19
  ExtensionCommandContext,
20
20
  ExtensionContext,
21
21
  } from "@earendil-works/pi-coding-agent";
22
+ import { HarnessStartupStatus, harnessStatusMarker } from "./harness-startup-status.ts";
22
23
  import { UsageTracker } from "./tracker.ts";
23
24
  import { MirrorStore, type MirrorRecord } from "./mirror.ts";
24
25
  import { MiniMaxQuotaScraper } from "./harness/e2e/minimax-quota-scraper.js";
@@ -122,30 +123,12 @@ async function getTaskContext(): Promise<{
122
123
  }
123
124
 
124
125
  // --- Harness Startup Widget Messages (used by init functions) -----------------
125
- interface HarnessMessage {
126
- id: string;
127
- label: string;
128
- text: string;
129
- status: "wait" | "ok" | "err";
130
- details?: string[]; // Multi-line fix instructions for errors
131
- }
132
- const harnessMessages: HarnessMessage[] = [];
133
- const preSessionErrors: HarnessMessage[] = []; // Errors from init functions (before session_start)
134
-
135
- function addHarnessMessage(label: string, text: string, status: "wait" | "ok" | "err" = "wait", details?: string[]) {
136
- const id = `${label}-${Date.now()}`;
137
- const msg: HarnessMessage = { id, label, text, status, details };
138
-
139
- // If session hasn't started yet, store in preSessionErrors
140
- // The session_start handler will merge these into harnessMessages
141
- if (harnessMessages.length === 0) {
142
- preSessionErrors.push(msg);
143
- } else {
144
- harnessMessages.push(msg);
145
- }
146
- }
126
+ const startupStatus = new HarnessStartupStatus();
127
+ const harnessMessages = startupStatus.messages;
128
+ const addHarnessMessage = startupStatus.update.bind(startupStatus);
147
129
 
148
130
  async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
131
+ addHarnessMessage("Jev", "initializing", "wait");
149
132
  try {
150
133
  // Check if Jev API key is available (env var or keys file)
151
134
  const homedir = process.env.HOME || process.env.USERPROFILE || "/home/frappe";
@@ -158,19 +141,21 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
158
141
  }
159
142
 
160
143
  const hasEnvKey =
161
- process.env.TYPESAFE_API_KEY || process.env.OPENROUTER_API_KEY;
144
+ process.env.TYPESAFE_API_KEY?.trim() || process.env.OPENROUTER_API_KEY?.trim();
162
145
  const hasFileKey =
163
146
  existsSync(`${keysDir}/jev-api-key.txt`) &&
164
- readFileSync(`${keysDir}/jev-api-key.txt`, "utf8").trim().length > 10;
147
+ readFileSync(`${keysDir}/jev-api-key.txt`, "utf8").trim().length > 0;
165
148
 
166
149
  if (!hasEnvKey && !hasFileKey) {
150
+ addHarnessMessage("Jev", "API key missing or empty; auto-continue unavailable", "warn", [
151
+ "Set TYPESAFE_API_KEY or ~/.pi-harness-runtime/keys/jev-api-key.txt, then reload Pi.",
152
+ ]);
167
153
  logToFile("[auto-continue]", "No Jev API key found. Set TYPESAFE_API_KEY run: echo \"{api_key}\" > ~/.pi-harness-runtime/keys/jev-api-key.txt");
168
154
  return;
169
155
  }
170
156
 
171
157
  // Resolve the actual API key: env takes priority, file is fallback
172
- const resolvedApiKey = process.env.TYPESAFE_API_KEY ||
173
- process.env.OPENROUTER_API_KEY ||
158
+ const resolvedApiKey = hasEnvKey ||
174
159
  readFileSync(`${keysDir}/jev-api-key.txt`, "utf8").trim();
175
160
 
176
161
  const mod = await import("./packages/jev-judge/src/auto-continue.js");
@@ -450,13 +435,33 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
450
435
  } catch {
451
436
  _inferenceInProgress = false;
452
437
  updateAutoContinueStatus("ERROR");
438
+ addHarnessMessage("Jev", "auto-continue evaluation failed", "err");
453
439
  }
454
440
  };
455
441
 
456
442
  // Start periodic check
457
443
  autoContinueTimer = setInterval(checkAndAutoContinue, AUTO_CONTINUE_INTERVAL_MS);
458
- } catch {
459
- // auto-continue not available
444
+ addHarnessMessage("Jev", "auto-continue configured (verifying API...)", "ok");
445
+
446
+ // Verify API key works with a lightweight health check
447
+ try {
448
+ const { JevJudge } = await import("./packages/jev-judge/dist/index.js");
449
+ const judge = new JevJudge({ apiKey: resolvedApiKey });
450
+ // Lightweight verification - ask a simple question
451
+ await judge.evaluate("health check", {
452
+ test: { type: "noul" as const, instructions: "Is this a test?" }
453
+ });
454
+ addHarnessMessage("Jev", "auto-continue ready (API verified)", "ok");
455
+ } catch (verifyErr) {
456
+ addHarnessMessage("Jev", "auto-continue ready (API verification failed)", "warn", [
457
+ `Error: ${verifyErr instanceof Error ? verifyErr.message : String(verifyErr)}`,
458
+ "Check API key and network access, then reload Pi.",
459
+ ]);
460
+ }
461
+ } catch (_err) {
462
+ addHarnessMessage("Jev", "auto-continue initialization failed", "err", [
463
+ "Check Jev package installation and key-file permissions, then reload Pi.",
464
+ ]);
460
465
  }
461
466
  }
462
467
 
@@ -485,6 +490,7 @@ async function initConfigCapture(pi: ExtensionAPI): Promise<void> {
485
490
  // --- qdrant-vector-search: Semantic skill search via Qdrant -------------------
486
491
  // Check config and report status — right after Jev for discoverability
487
492
  async function initQdrant(): Promise<void> {
493
+ addHarnessMessage("Qdrant", "checking configuration", "wait");
488
494
  try {
489
495
  const { getQdrantConfig } = await import("./packages/skills/src/loader.js");
490
496
  const cfg = getQdrantConfig();
@@ -494,23 +500,48 @@ async function initQdrant(): Promise<void> {
494
500
  const embeddingKey = getOpenAIApiKey();
495
501
  if (embeddingKey) {
496
502
  logStartup("[pi-harness] Qdrant vector search ready");
503
+ addHarnessMessage("Qdrant", "verifying connection...", "ok");
504
+
505
+ // Verify Qdrant connection with health check
506
+ try {
507
+ const healthUrl = `${cfg.url}/health`;
508
+ const healthResponse = await fetch(healthUrl, {
509
+ headers: cfg.apiKey ? { "api-key": cfg.apiKey } : {},
510
+ });
511
+ if (healthResponse.ok) {
512
+ addHarnessMessage("Qdrant", "ready (connection verified)", "ok");
513
+ } else {
514
+ addHarnessMessage("Qdrant", "ready (connection failed)", "warn", [
515
+ `Health check returned: ${healthResponse.status} ${healthResponse.statusText}`,
516
+ "Check cluster URL and network access, then reload Pi.",
517
+ ]);
518
+ }
519
+ } catch (healthErr) {
520
+ addHarnessMessage("Qdrant", "ready (connection failed)", "warn", [
521
+ `Error: ${healthErr instanceof Error ? healthErr.message : String(healthErr)}`,
522
+ "Check cluster URL and network access, then reload Pi.",
523
+ ]);
524
+ }
497
525
  } else {
498
526
  logStartup("[pi-harness] Qdrant ready (no embedding key — skills won't be vector-indexed)");
527
+ addHarnessMessage("Qdrant", "embedding key missing; vector indexing unavailable", "warn");
499
528
  logStartup('[pi-harness] For vector search: echo "{openai-api-key}" > ~/.pi-harness-runtime/keys/openai-api-key.txt');
500
529
  }
501
530
  } else {
502
531
  logStartup("[pi-harness] Qdrant not configured. Set keys to enable vector skill search:");
532
+ addHarnessMessage("Qdrant", "not configured", "warn");
503
533
  logStartup(` echo "{cluster-url}" > ~/.pi-harness-runtime/keys/qdrant-cluster-url.txt`);
504
534
  logStartup(` echo "{api-key}" > ~/.pi-harness-runtime/keys/qdrant-api-key.txt`);
505
535
  }
506
536
  } catch {
507
- // qdrant-skills not available
537
+ addHarnessMessage("Qdrant", "initialization failed", "err");
508
538
  }
509
539
  }
510
540
 
511
541
  // --- telegram-notifications: Telegram bot for harness event notifications ------------
512
542
  // Sends Telegram messages when jobs complete, tasks finish, or human input is needed.
513
543
  async function initTelegram(pi: ExtensionAPI): Promise<void> {
544
+ addHarnessMessage("Telegram", "initializing", "wait");
514
545
  try {
515
546
  const {
516
547
  NotificationCenter,
@@ -530,10 +561,10 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
530
561
  logStartup("[pi-harness] 3. echo \"{token}\" > ~/.pi-harness-runtime/keys/telegram-bot-token.txt");
531
562
  logStartup("[pi-harness] 4. echo \"{chat-id}\" > ~/.pi-harness-runtime/keys/telegram-chat-id.txt");
532
563
  logStartup("[pi-harness] 5. Reload pi — then DM @PiHarnessRuntimeBot to verify");
533
- addHarnessMessage("Telegram", "not configured", "err", [
564
+ addHarnessMessage("Telegram", "credentials missing; notifications unavailable", "warn", [
534
565
  "echo \"<TOKEN>\" > keys/telegram-bot-token.txt",
535
566
  "echo \"<CHAT_ID>\" > keys/telegram-chat-id.txt",
536
- "Then: pi restart",
567
+ "Then reload Pi.",
537
568
  ]);
538
569
  return;
539
570
  }
@@ -543,6 +574,9 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
543
574
 
544
575
  if (!botToken || !chatId) {
545
576
  logStartup("[pi-harness] Telegram: bot token or chat ID is empty — skipping");
577
+ addHarnessMessage("Telegram", "bot token or chat ID is empty; notifications unavailable", "warn", [
578
+ "Fill telegram-bot-token.txt and telegram-chat-id.txt in ~/.pi-harness-runtime/keys/, then reload Pi.",
579
+ ]);
546
580
  return;
547
581
  }
548
582
 
@@ -559,8 +593,11 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
559
593
  });
560
594
 
561
595
  await center.initialize();
562
- if (!center.hasChannels()) {
596
+ if (!center.hasHealthyChannels()) {
563
597
  logStartup("[pi-harness] Telegram: initialization failed — skipping");
598
+ addHarnessMessage("Telegram", "bot verification failed; notifications unavailable", "err", [
599
+ "Check the bot token and network access, then reload Pi.",
600
+ ]);
564
601
  return;
565
602
  }
566
603
 
@@ -609,13 +646,17 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
609
646
 
610
647
  // Log the actual bot username (fetched from Telegram API via getMe)
611
648
  const botUsername = center.getTelegramBotUsername();
649
+ addHarnessMessage("Telegram", "bot verified (chat delivery not tested)", "ok");
612
650
  if (botUsername) {
613
651
  logStartup(`[pi-harness] Telegram (@${botUsername}) ready — DM the bot to receive alerts`);
614
652
  } else {
615
653
  logStartup(`[pi-harness] Telegram notifications ready`);
616
654
  }
617
- } catch (err) {
618
- logStartup("[pi-harness] Telegram: init failed:", err instanceof Error ? err.message : String(err));
655
+ } catch (_err) {
656
+ logStartup("[pi-harness] Telegram: init failed (check installation, credentials, and network)");
657
+ addHarnessMessage("Telegram", "initialization failed; notifications unavailable", "err", [
658
+ "Check package installation, key-file permissions, and network access, then reload Pi.",
659
+ ]);
619
660
  }
620
661
  }
621
662
 
@@ -627,6 +668,8 @@ async function initTelegram(pi: ExtensionAPI): Promise<void> {
627
668
  // - Bounded context injection with provenance
628
669
 
629
670
  async function initHoncho(pi: ExtensionAPI): Promise<void> {
671
+ addHarnessMessage("Honcho", "initializing", "wait");
672
+ try {
630
673
  // Import new Honcho integration module
631
674
  const { initHoncho: honchoInit, registerHonchoLifecycle } = await import("./harness/honcho-init.js");
632
675
 
@@ -639,10 +682,10 @@ async function initHoncho(pi: ExtensionAPI): Promise<void> {
639
682
  logStartup("[pi-harness] Honcho not configured:");
640
683
  logStartup("[pi-harness] Get key: https://app.honcho.dev/api-keys");
641
684
  logStartup(`[pi-harness] Then: echo "{api-key}" > ~/.pi-harness-runtime/keys/honcho-api-key.txt`);
642
- addHarnessMessage("Honcho", "API key missing", "err", [
685
+ addHarnessMessage("Honcho", "API key missing", "warn", [
643
686
  "Get key: https://app.honcho.dev/api-keys",
644
687
  "echo \"<KEY>\" > keys/honcho-api-key.txt",
645
- "Then: pi restart",
688
+ "Then reload Pi.",
646
689
  ]);
647
690
  return;
648
691
  }
@@ -650,14 +693,13 @@ async function initHoncho(pi: ExtensionAPI): Promise<void> {
650
693
  const apiKey = readFileSync(keyPath, "utf8").trim();
651
694
  if (!apiKey) {
652
695
  logStartup("[pi-harness] Honcho: API key is empty — skipping");
653
- addHarnessMessage("Honcho", "API key empty", "err", [
696
+ addHarnessMessage("Honcho", "API key empty", "warn", [
654
697
  "echo \"<KEY>\" > keys/honcho-api-key.txt",
655
- "Then: pi restart",
698
+ "Then reload Pi.",
656
699
  ]);
657
700
  return;
658
701
  }
659
702
 
660
- try {
661
703
  // Register lifecycle hooks using the new integration
662
704
  // SAFETY: pi.on accepts these event names at runtime
663
705
  registerHonchoLifecycle(pi as unknown as { on: (event: string, handler: (...args: unknown[]) => void | Promise<void>) => void });
@@ -668,8 +710,10 @@ async function initHoncho(pi: ExtensionAPI): Promise<void> {
668
710
 
669
711
  if (result) {
670
712
  logStartup("[pi-harness] Honcho integration ready");
713
+ addHarnessMessage("Honcho", "initialized", "ok");
671
714
  } else {
672
715
  logStartup("[pi-harness] Honcho: initialization returned null");
716
+ addHarnessMessage("Honcho", "initialization returned no client", "err");
673
717
  }
674
718
  } catch (err) {
675
719
  logStartup("[pi-harness] Honcho: init failed:", err instanceof Error ? err.message : String(err));
@@ -1100,6 +1144,7 @@ async function initLoopCompletions(pi: ExtensionAPI): Promise<void> {
1100
1144
  * - Scan skills from ~/.pi-harness-runtime/skills/
1101
1145
  */
1102
1146
  async function initHermesSkills(pi: ExtensionAPI): Promise<void> {
1147
+ addHarnessMessage("Skills", "initializing", "wait");
1103
1148
  try {
1104
1149
  // Dynamically import to avoid circular deps
1105
1150
  const { initSkills } =
@@ -1108,6 +1153,7 @@ async function initHermesSkills(pi: ExtensionAPI): Promise<void> {
1108
1153
  // Scan skills from default locations
1109
1154
  const result = initSkills();
1110
1155
  logStartup(`[pi-harness] Scanned ${result.skills.length} skills`);
1156
+ addHarnessMessage("Skills", `${result.skills.length} discovered`, "ok");
1111
1157
 
1112
1158
  // Register /skill commands
1113
1159
  try {
@@ -1147,6 +1193,7 @@ async function initHermesSkills(pi: ExtensionAPI): Promise<void> {
1147
1193
 
1148
1194
  } catch (err) {
1149
1195
  logStartup(`[pi-harness] Failed to initialize skills:`, err instanceof Error ? err.message : String(err));
1196
+ addHarnessMessage("Skills", "initialization failed", "err");
1150
1197
  }
1151
1198
  }
1152
1199
 
@@ -1604,7 +1651,7 @@ export default function (pi: ExtensionAPI) {
1604
1651
  void initSshDetachInterceptor(pi);
1605
1652
 
1606
1653
  // --- jev-auto-continue: Agent autonomous decision when user unavailable -----
1607
- void initAutoContinue(pi);
1654
+ asyncInitPromises.push(initAutoContinue(pi));
1608
1655
 
1609
1656
  // Register todo-driven continuation controller
1610
1657
  createTodoContinuation(pi);
@@ -3080,24 +3127,12 @@ Run \`bd ready\` to see current bd issues.
3080
3127
  // - Tree characters like Todos (├─ └─)
3081
3128
  // - Trailing spacer line for breathing room
3082
3129
  const HARNESS_WIDGET_KEY = "pi-harness-startup";
3083
- interface HarnessMessage {
3084
- id: string;
3085
- label: string;
3086
- text: string;
3087
- status: "wait" | "ok" | "err";
3088
- details?: string[]; // Multi-line fix instructions for errors
3089
- }
3090
- const harnessMessages: HarnessMessage[] = [];
3091
3130
  let startupVisible = true;
3092
3131
  let harnessTUI: { requestRender: (forced?: boolean) => void } | null = null;
3093
3132
  let harnessWidgetRegistered = false;
3094
3133
  let sessionGeneration = 0;
3095
3134
 
3096
- function addHarnessMessage(label: string, text: string, status: "wait" | "ok" | "err" = "wait", details?: string[]) {
3097
- const id = `${label}-${Date.now()}`;
3098
- harnessMessages.push({ id, label, text, status, details });
3099
- harnessTUI?.requestRender();
3100
- }
3135
+ startupStatus.onChange = () => harnessTUI?.requestRender();
3101
3136
 
3102
3137
  function hideHarnessBlock() {
3103
3138
  startupVisible = false;
@@ -3131,7 +3166,7 @@ Run \`bd ready\` to see current bd issues.
3131
3166
 
3132
3167
  for (let i = 0; i < harnessMessages.length; i++) {
3133
3168
  const msg = harnessMessages[i];
3134
- const marker = msg.status === "ok" ? "[OK]" : msg.status === "err" ? "[ERR]" : "[WAIT]";
3169
+ const marker = harnessStatusMarker(msg.status);
3135
3170
  const isLast = i === harnessMessages.length - 1;
3136
3171
  const treeChar = isLast ? "└─" : "├─";
3137
3172
  let text = `${treeChar} ${marker} ${msg.label}: ${msg.text}`;
@@ -3139,7 +3174,7 @@ Run \`bd ready\` to see current bd issues.
3139
3174
  lines.push(text);
3140
3175
 
3141
3176
  // Add fix instructions for errors (indented)
3142
- if (msg.status === "err" && msg.details && msg.details.length > 0) {
3177
+ if ((msg.status === "err" || msg.status === "warn") && msg.details && msg.details.length > 0) {
3143
3178
  for (const detail of msg.details) {
3144
3179
  const detailLine = `${isLast ? " " : "│ "} Fix: ${detail}`;
3145
3180
  lines.push(truncateToWidth(detailLine, width));
@@ -3170,7 +3205,7 @@ Run \`bd ready\` to see current bd issues.
3170
3205
  sessionGeneration++;
3171
3206
  const mySession = sessionGeneration;
3172
3207
  startupVisible = true;
3173
- harnessMessages.length = 0;
3208
+ // Service states survive session changes; initialization runs once per extension load.
3174
3209
  harnessWidgetRegistered = false; // Reset for new session
3175
3210
 
3176
3211
  // --- Install harness widget (like Todos with placement: "aboveEditor") -------
@@ -3189,13 +3224,7 @@ Run \`bd ready\` to see current bd issues.
3189
3224
  );
3190
3225
  harnessWidgetRegistered = true;
3191
3226
 
3192
- // Merge pre-session errors into harnessMessages
3193
- for (const err of preSessionErrors) {
3194
- harnessMessages.push(err);
3195
- }
3196
- preSessionErrors.length = 0; // Clear pre-session errors
3197
-
3198
- harnessTUI?.requestRender(); // Show widget with error messages
3227
+ harnessTUI?.requestRender();
3199
3228
  } else {
3200
3229
  harnessTUI?.requestRender();
3201
3230
  }
@@ -3203,42 +3232,19 @@ Run \`bd ready\` to see current bd issues.
3203
3232
  // Add initial harness message
3204
3233
  addHarnessMessage("Harness Runtime", `v${HARNESS_VERSION} initializing`, "wait");
3205
3234
 
3206
- // Wait for async inits to complete, then update status
3207
- const startupTimeout = new Promise<void>((resolve) => {
3208
- setTimeout(resolve, 5000);
3209
- });
3210
- Promise.race([
3211
- Promise.allSettled(asyncInitPromises),
3212
- startupTimeout,
3213
- ]).then(() => {
3214
- // Fence: ignore if session changed or already hidden
3215
- if (mySession !== sessionGeneration || !startupVisible) return;
3216
-
3217
- // Update Runtime status
3218
- const runtimeMsg = harnessMessages.find((m) => m.label === "Harness Runtime");
3219
- if (runtimeMsg) {
3220
- runtimeMsg.status = "ok";
3221
- runtimeMsg.text = `v${HARNESS_VERSION} initialized`;
3222
- }
3223
-
3224
- // Add service messages from startup
3225
- for (const msg of startupMessages) {
3226
- if (msg.includes("Qdrant") && msg.includes("ready")) {
3227
- addHarnessMessage("Qdrant", "connected", "ok");
3228
- }
3229
- if (msg.includes("Telegram") && msg.includes("ready")) {
3230
- addHarnessMessage("Telegram", "verified", "ok");
3231
- }
3232
- if (msg.includes("Honcho")) {
3233
- addHarnessMessage("Honcho", "registered", "ok");
3234
- }
3235
- if (msg.includes("Scanned") && msg.includes("skills")) {
3236
- const match = msg.match(/Scanned (\d+)/);
3237
- if (match) {
3238
- addHarnessMessage("Skills", `${match[1]} discovered`, "ok");
3239
- }
3240
- }
3241
- }
3235
+ // A timeout is not success. Keep accepting late service updates.
3236
+ const startupTimeout = setTimeout(() => {
3237
+ if (mySession !== sessionGeneration) return;
3238
+ addHarnessMessage("Harness Runtime", `v${HARNESS_VERSION} waiting for service initialization`, "warn");
3239
+ }, 5000);
3240
+ Promise.allSettled(asyncInitPromises).then((results) => {
3241
+ clearTimeout(startupTimeout);
3242
+ if (mySession !== sessionGeneration) return;
3243
+ const degraded = results.some((result) => result.status === "rejected") ||
3244
+ harnessMessages.some((msg) => msg.label !== "Harness Runtime" && msg.status !== "ok");
3245
+ addHarnessMessage("Harness Runtime",
3246
+ `v${HARNESS_VERSION} ${degraded ? "initialized with unavailable services" : "initialized"}`,
3247
+ degraded ? "warn" : "ok");
3242
3248
  });
3243
3249
  });
3244
3250
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-harness-runtime",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "[BETA] Codex-style /usage status + autonomous coding harness for pi. Not production ready — expect breaking changes.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/a2a-adapter",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "type": "module",
5
5
  "description": "Google Agent-to-Agent (A2A) protocol adapter for pi-harness",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/agent-policy",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "Agent policy runtime for pi-harness - enforces mandatory rule delivery and mutation gating",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/architecture-generator",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "Generate ADR and Mermaid diagrams from project analysis",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/auth",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "pi-harness-runtime — auth",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/autonomous-refactor",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "type": "module",
5
5
  "description": "Autonomous refactoring engine",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/autonomous-runtime",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "Autonomous Operations Runtime — task inbox, lease management, and worker lifecycle (RFC-0101)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/cache-strategy",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "type": "module",
5
5
  "description": "Smart caching with TTL and invalidation strategies (RFC-0022)",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/capability-registry",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "Capability Registry for pi-harness-runtime — tracks model capabilities and enables capability-based routing",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/checkpoint",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "Enhanced checkpoint system with incremental snapshots, diff-based storage, and recovery strategies",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/cli-plugin-sdk",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "type": "module",
5
5
  "description": "CLI Plugin SDK for pi-harness — install, list, remove, update, invoke plugins",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/clipboard",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/clipboard-plugin",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/code-generation",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "Code generation pipeline for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/code-review",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "Automated code review engine for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/codex-adapter",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "type": "module",
5
5
  "description": "OpenAI Codex provider adapter for pi-harness",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/codex-watcher",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "Watches Codex CLI sessions and emits visible messages for PING-PONG monitoring",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moocoding/config-capture",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "Auto-capture API-fetched configuration values to AGENTS.md",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/context-compiler",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "Bounded context selection engine for pi-harness-runtime",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/context-discovery",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/context-manager",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "Context Window Manager — prevents model context overflow via compaction and resume prompts (RFC-0010)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/cookie-sanitizer",
3
- "version": "1.1.226",
3
+ "version": "1.1.228",
4
4
  "description": "pi-harness-runtime — cookie sanitizer (provider-quota cookie file normaliser)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",