pi-harness-runtime 1.1.152 → 1.1.154

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 (120) hide show
  1. package/harness/langchain/graph.ts +9 -9
  2. package/harness/langchain/{jev-logger.ts → planner-logger.ts} +25 -24
  3. package/harness/service-diagnostics.ts +217 -0
  4. package/index.ts +113 -9
  5. package/package.json +1 -1
  6. package/packages/a2a-adapter/package.json +1 -1
  7. package/packages/agent-policy/package.json +1 -1
  8. package/packages/architecture-generator/package.json +1 -1
  9. package/packages/auth/package.json +1 -1
  10. package/packages/autonomous-refactor/package.json +1 -1
  11. package/packages/autonomous-runtime/package.json +1 -1
  12. package/packages/cache-strategy/package.json +1 -1
  13. package/packages/capability-registry/package.json +1 -1
  14. package/packages/checkpoint/package.json +1 -1
  15. package/packages/cli-plugin-sdk/package.json +1 -1
  16. package/packages/clipboard/package.json +1 -1
  17. package/packages/clipboard-plugin/package.json +1 -1
  18. package/packages/code-generation/package.json +1 -1
  19. package/packages/code-review/package.json +1 -1
  20. package/packages/codex-adapter/package.json +1 -1
  21. package/packages/codex-watcher/package.json +1 -1
  22. package/packages/config-capture/package.json +1 -1
  23. package/packages/context-compiler/package.json +1 -1
  24. package/packages/context-discovery/package.json +1 -1
  25. package/packages/context-manager/package.json +1 -1
  26. package/packages/cookie-sanitizer/package.json +1 -1
  27. package/packages/cost-optimizer/package.json +1 -1
  28. package/packages/dependency-analyzer/package.json +1 -1
  29. package/packages/django-plugin/package.json +1 -1
  30. package/packages/doc-generator/package.json +1 -1
  31. package/packages/evaluation-engine/package.json +1 -1
  32. package/packages/evaluation-runner/package.json +1 -1
  33. package/packages/event-bus/package.json +1 -1
  34. package/packages/event-store/package.json +1 -1
  35. package/packages/experience-replay/package.json +1 -1
  36. package/packages/feedback-collector/package.json +1 -1
  37. package/packages/file-copy-helper/package.json +1 -1
  38. package/packages/framework-detector/package.json +1 -1
  39. package/packages/framework-plugin-sdk/package.json +1 -1
  40. package/packages/frappe-plugin/package.json +1 -1
  41. package/packages/generic-web-plugin/package.json +1 -1
  42. package/packages/health-monitor/package.json +1 -1
  43. package/packages/intent-analyzer/package.json +1 -1
  44. package/packages/jev-judge/dist/index.d.ts +14 -3
  45. package/packages/jev-judge/dist/index.d.ts.map +1 -1
  46. package/packages/jev-judge/dist/index.js +54 -12
  47. package/packages/jev-judge/dist/index.js.map +1 -1
  48. package/packages/jev-judge/dist/typesafe-provider.d.ts +66 -0
  49. package/packages/jev-judge/dist/typesafe-provider.d.ts.map +1 -0
  50. package/packages/jev-judge/dist/typesafe-provider.js +220 -0
  51. package/packages/jev-judge/dist/typesafe-provider.js.map +1 -0
  52. package/packages/jev-judge/package.json +1 -1
  53. package/packages/jev-judge/src/index.ts +58 -12
  54. package/packages/jev-judge/src/typesafe-provider.ts +335 -0
  55. package/packages/knowledge-graph/package.json +1 -1
  56. package/packages/knowledge-retrieval/package.json +1 -1
  57. package/packages/laravel-plugin/package.json +1 -1
  58. package/packages/learning-engine/package.json +1 -1
  59. package/packages/mcp-adapter/package.json +1 -1
  60. package/packages/memory-engine/package.json +1 -1
  61. package/packages/milestone-manager/package.json +1 -1
  62. package/packages/model-registry/package.json +1 -1
  63. package/packages/nextjs-plugin/package.json +1 -1
  64. package/packages/notification/package.json +1 -1
  65. package/packages/observability/package.json +1 -1
  66. package/packages/okf-indexer/package.json +1 -1
  67. package/packages/paperclip-integration/package.json +1 -1
  68. package/packages/performance-optimizer/package.json +1 -1
  69. package/packages/privilege-broker/package.json +1 -1
  70. package/packages/project-analyzer/package.json +1 -1
  71. package/packages/project-bootstrap/package.json +1 -1
  72. package/packages/projection-engine/package.json +1 -1
  73. package/packages/prompt-compiler/package.json +1 -1
  74. package/packages/prompt-versioning/package.json +1 -1
  75. package/packages/provider-adapter-sdk/package.json +1 -1
  76. package/packages/provider-router/package.json +1 -1
  77. package/packages/provider-selector/package.json +1 -1
  78. package/packages/providers/package.json +1 -1
  79. package/packages/qdrant-skills/dist/client.d.ts.map +1 -1
  80. package/packages/qdrant-skills/dist/client.js +4 -3
  81. package/packages/qdrant-skills/dist/client.js.map +1 -1
  82. package/packages/qdrant-skills/package.json +1 -1
  83. package/packages/qdrant-skills/src/client.ts +14 -17
  84. package/packages/quota-manager/package.json +1 -1
  85. package/packages/rate-limiter/package.json +1 -1
  86. package/packages/react-vite-plugin/package.json +1 -1
  87. package/packages/release-manager/package.json +1 -1
  88. package/packages/requirement-compiler/package.json +1 -1
  89. package/packages/runtime/package.json +1 -1
  90. package/packages/scheduler/package.json +1 -1
  91. package/packages/scheduler-adapter/package.json +1 -1
  92. package/packages/session/package.json +1 -1
  93. package/packages/session-api/package.json +1 -1
  94. package/packages/session-export/package.json +1 -1
  95. package/packages/shared-context/package.json +1 -1
  96. package/packages/skill-mcp-client/package.json +1 -1
  97. package/packages/skill-registry/package.json +1 -1
  98. package/packages/skills/dist/loader.d.ts +9 -2
  99. package/packages/skills/dist/loader.d.ts.map +1 -1
  100. package/packages/skills/dist/loader.js +24 -7
  101. package/packages/skills/dist/loader.js.map +1 -1
  102. package/packages/skills/package.json +1 -1
  103. package/packages/skills/src/loader.ts +27 -7
  104. package/packages/sprint-planner/package.json +1 -1
  105. package/packages/subscription-engine/package.json +1 -1
  106. package/packages/task-compiler/package.json +1 -1
  107. package/packages/tencentdb-memory/package.json +1 -1
  108. package/packages/tencentdb-sync/package.json +1 -1
  109. package/packages/test-data-generator/package.json +1 -1
  110. package/packages/test-generator/package.json +1 -1
  111. package/packages/todo-bd-sync/package.json +1 -1
  112. package/packages/token-estimation/package.json +1 -1
  113. package/packages/token-optimizer/package.json +1 -1
  114. package/packages/trajectory/package.json +1 -1
  115. package/packages/tui/package.json +1 -1
  116. package/packages/types/package.json +1 -1
  117. package/packages/workflow-events/package.json +1 -1
  118. package/packages/workspace-scanner/package.json +1 -1
  119. package/packages/worktree/package.json +1 -1
  120. package/packages/write-review/package.json +1 -1
@@ -457,22 +457,22 @@ export async function buildRealLoopDeps(
457
457
  });
458
458
  const planOutput = lastMessage(plannerResponse) ?? "";
459
459
 
460
- // Import and use Jev logger (async - logs to file + Telegram)
460
+ // Import and use planner logger (async - logs to file + Telegram)
461
461
  try {
462
- const { logJevDecision, parseJevDecision } = await import("./jev-logger.js");
463
- const jevDecision = parseJevDecision(planOutput);
464
- setJevProvider(jevDecision.provider);
465
- await logJevDecision({
462
+ const { logPlannerDecision, parsePlannerDecision } = await import("./planner-logger.js");
463
+ const plannerDecision = parsePlannerDecision(planOutput);
464
+ setJevProvider(plannerDecision.provider);
465
+ await logPlannerDecision({
466
466
  timestamp: new Date().toISOString(),
467
467
  request,
468
- provider: jevDecision.provider,
469
- reason: jevDecision.reason,
470
- userInput: jevDecision.userInput,
468
+ provider: plannerDecision.provider,
469
+ reason: plannerDecision.reason,
470
+ userInput: plannerDecision.userInput,
471
471
  escalate: false,
472
472
  raw: planOutput,
473
473
  });
474
474
  } catch (logErr) {
475
- console.error("[JEV] Logger error:", (logErr as Error).message);
475
+ console.error("[PLANNER] Logger error:", (logErr as Error).message);
476
476
  }
477
477
 
478
478
  return planOutput;
@@ -1,20 +1,21 @@
1
1
  /**
2
- * Jev Logger - Exclusive logging for Jev's decisions
2
+ * Planner Logger - Logs LangChain planner decisions
3
3
  *
4
- * Logs all Jev decisions:
4
+ * Logs planner output:
5
5
  * - Provider selection (minimax vs gpt)
6
6
  * - User input decisions
7
7
  * - Escalation triggers
8
8
  * - Reasoning
9
9
  *
10
- * Sends to both file and Telegram for real-time monitoring.
10
+ * NOTE: This logs the LangChain planner's output, not TypeSafe Jev service calls.
11
+ * The TypeSafe Jev service is tracked separately in the auto-continue system.
11
12
  */
12
13
 
13
14
  import { mkdirSync, appendFileSync, existsSync } from "node:fs";
14
15
  import { join } from "node:path";
15
16
  import { homedir } from "node:os";
16
17
 
17
- export interface JevDecision {
18
+ export interface PlannerDecision {
18
19
  timestamp: string;
19
20
  request: string;
20
21
  provider: "minimax" | "gpt";
@@ -27,12 +28,12 @@ export interface JevDecision {
27
28
  escalateReason?: string;
28
29
  }
29
30
 
30
- export interface JevLogEntry extends JevDecision {
31
+ export interface PlannerLogEntry extends PlannerDecision {
31
32
  raw?: string; // Raw planner output for debugging
32
33
  }
33
34
 
34
35
  const LOG_DIR = join(homedir(), ".pi-harness-runtime", "logs");
35
- const LOG_FILE = join(LOG_DIR, "jev.log");
36
+ const LOG_FILE = join(LOG_DIR, "planner.log");
36
37
 
37
38
  // Telegram config (loaded from environment)
38
39
  let TELEGRAM_BOT_TOKEN: string | undefined;
@@ -54,20 +55,20 @@ function ensureLogDir(): void {
54
55
  /**
55
56
  * Format decision for file log (detailed, multi-line)
56
57
  */
57
- function formatFileLog(entry: JevLogEntry): string {
58
+ function formatFileLog(entry: PlannerLogEntry): string {
58
59
  const lines = [
59
60
  `═══════════════════════════════════════════════════════════════`,
60
- `[JEV] ${entry.timestamp}`,
61
+ `[PLANNER] ${entry.timestamp}`,
61
62
  `═══════════════════════════════════════════════════════════════`,
62
63
  ``,
63
64
  `## Request`,
64
65
  `${entry.request}`,
65
66
  ``,
66
- `## Jev Provider Decision`,
67
+ `## Provider Decision`,
67
68
  `Provider: ${entry.provider.toUpperCase()}`,
68
69
  `Reason: ${entry.reason}`,
69
70
  ``,
70
- `## Jev User Input Decision`,
71
+ `## User Input Decision`,
71
72
  `Type: ${entry.userInput?.type ?? "none (proceed automatically)"}`,
72
73
  entry.userInput?.question ? `Question: ${entry.userInput.question}` : ``,
73
74
  ``,
@@ -92,12 +93,12 @@ function formatFileLog(entry: JevLogEntry): string {
92
93
  /**
93
94
  * Format decision for Telegram (compact, single message)
94
95
  */
95
- function formatTelegramMessage(entry: JevLogEntry): string {
96
+ function formatTelegramMessage(entry: PlannerLogEntry): string {
96
97
  const providerIcon = entry.provider === "gpt" ? "🤖" : "⚡";
97
98
  const escalateIcon = entry.escalate ? " ↑ ESCALATE" : "";
98
99
 
99
100
  const parts = [
100
- `${providerIcon} *JEV DECISION*`,
101
+ `${providerIcon} *PLANNER DECISION*`,
101
102
  `\n*Provider:* ${entry.provider.toUpperCase()}${escalateIcon}`,
102
103
  `\n*Reason:* ${entry.reason}`,
103
104
  ];
@@ -125,16 +126,16 @@ async function sendToTelegram(message: string): Promise<void> {
125
126
  TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID ?? TELEGRAM_CHAT_ID;
126
127
 
127
128
  if (!TELEGRAM_BOT_TOKEN || !TELEGRAM_CHAT_ID) {
128
- console.log("[JEV] Telegram not configured - skipping");
129
+ console.log("[PLANNER] Telegram not configured - skipping");
129
130
  return;
130
131
  }
131
132
 
132
- console.log(`[JEV] Telegram sending: ${TELEGRAM_BOT_TOKEN?.slice(0, 10)}... to ${TELEGRAM_CHAT_ID}`);
133
+ console.log(`[PLANNER] Telegram sending: ${TELEGRAM_BOT_TOKEN?.slice(0, 10)}... to ${TELEGRAM_CHAT_ID}`);
133
134
  telegramQueue.push(message);
134
135
 
135
136
  // Process queue sequentially
136
137
  if (telegramSending) {
137
- console.log("[JEV] Telegram already sending, queued");
138
+ console.log("[PLANNER] Telegram already sending, queued");
138
139
  return;
139
140
  }
140
141
  telegramSending = true;
@@ -156,12 +157,12 @@ async function sendToTelegram(message: string): Promise<void> {
156
157
  );
157
158
 
158
159
  if (!response.ok) {
159
- console.error(`[JEV] Telegram send failed: ${response.status}`);
160
+ console.error(`[PLANNER] Telegram send failed: ${response.status}`);
160
161
  } else {
161
- console.log("[JEV] Telegram sent successfully");
162
+ console.log("[PLANNER] Telegram sent successfully");
162
163
  }
163
164
  } catch (err) {
164
- console.error(`[JEV] Telegram error:`, err);
165
+ console.error(`[PLANNER] Telegram error:`, err);
165
166
  }
166
167
  // Rate limit: 1 message per second
167
168
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -170,7 +171,7 @@ async function sendToTelegram(message: string): Promise<void> {
170
171
  telegramSending = false;
171
172
  }
172
173
 
173
- export async function logJevDecision(decision: JevLogEntry): Promise<void> {
174
+ export async function logPlannerDecision(decision: PlannerLogEntry): Promise<void> {
174
175
  ensureLogDir();
175
176
 
176
177
  // Always log to file (sync)
@@ -182,25 +183,25 @@ export async function logJevDecision(decision: JevLogEntry): Promise<void> {
182
183
  const telegramMsg = formatTelegramMessage(decision);
183
184
  // Fire-and-forget: don't await, let it run in background
184
185
  sendToTelegram(telegramMsg).catch((err) => {
185
- console.error("[JEV] Failed to send Telegram notification:", err);
186
+ console.error("[PLANNER] Failed to send Telegram notification:", err);
186
187
  });
187
188
  // Also await briefly for testing, then let process exit handle it
188
189
  await new Promise((resolve) => setTimeout(resolve, 100));
189
190
  }
190
191
 
191
192
  /**
192
- * Parse Jev's decision from planner output
193
+ * Parse planner's decision from output
193
194
  * Looks for [JEVO] and [USER_INPUT] markers
194
195
  */
195
- export function parseJevDecision(planOutput: string): {
196
+ export function parsePlannerDecision(planOutput: string): {
196
197
  provider: "minimax" | "gpt";
197
198
  reason: string;
198
- userInput: JevDecision["userInput"];
199
+ userInput: PlannerDecision["userInput"];
199
200
  } {
200
201
  const result: {
201
202
  provider: "minimax" | "gpt";
202
203
  reason: string;
203
- userInput: JevDecision["userInput"];
204
+ userInput: PlannerDecision["userInput"];
204
205
  } = {
205
206
  provider: "minimax",
206
207
  reason: "default (no explicit decision)",
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Service Diagnostics - Structured logging for harness services
3
+ *
4
+ * Logs service events to ~/.pi-harness-runtime/logs/services.jsonl
5
+ * with structured data for observability.
6
+ */
7
+
8
+ import { appendFileSync, existsSync, mkdirSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { homedir } from "node:os";
11
+
12
+ export type ServiceLevel = "info" | "warn" | "error" | "debug";
13
+ export type ServiceName = "jev" | "honcho" | "qdrant";
14
+
15
+ export interface ServiceEvent {
16
+ ts: string;
17
+ level: ServiceLevel;
18
+ service: ServiceName;
19
+ operation: string;
20
+ phase: "start" | "completed" | "failed";
21
+ sessionId?: string;
22
+ requestId?: string;
23
+ durationMs?: number;
24
+ [key: string]: unknown;
25
+ }
26
+
27
+ const LOG_DIR = join(homedir(), ".pi-harness-runtime", "logs");
28
+ const LOG_FILE = join(LOG_DIR, "services.jsonl");
29
+ let _initialized = false;
30
+
31
+ function ensureLogDir(): void {
32
+ if (_initialized) return;
33
+ if (!existsSync(LOG_DIR)) {
34
+ mkdirSync(LOG_DIR, { recursive: true });
35
+ }
36
+ _initialized = true;
37
+ }
38
+
39
+ /**
40
+ * Log a structured service event.
41
+ * Sensitive fields (keys, tokens, full content) are never logged.
42
+ */
43
+ export function logServiceEvent(event: ServiceEvent): void {
44
+ ensureLogDir();
45
+
46
+ // Redact sensitive fields
47
+ const safeEvent = redactSensitive(event);
48
+
49
+ try {
50
+ appendFileSync(LOG_FILE, JSON.stringify(safeEvent) + "\n");
51
+ } catch {
52
+ // Silently fail if logging fails - don't crash the service
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Redact sensitive fields from event data.
58
+ * Never log: apiKey, token, secret, chatId, content, vectors, embeddings
59
+ */
60
+ function redactSensitive(event: ServiceEvent): Record<string, unknown> {
61
+ const sensitiveKeys = [
62
+ "apiKey",
63
+ "token",
64
+ "secret",
65
+ "bearerToken",
66
+ "chatId",
67
+ "content",
68
+ "vectors",
69
+ "embeddings",
70
+ "prompt",
71
+ "messages",
72
+ "raw",
73
+ ];
74
+
75
+ const result: Record<string, unknown> = { ...event };
76
+
77
+ for (const key of Object.keys(result)) {
78
+ if (sensitiveKeys.some((sk) => key.toLowerCase().includes(sk.toLowerCase()))) {
79
+ result[key] = "[REDACTED]";
80
+ }
81
+ }
82
+
83
+ return result;
84
+ }
85
+
86
+ /**
87
+ * Create a service status tracker.
88
+ */
89
+ export class ServiceStatus {
90
+ private _configured = false;
91
+ private _initialized = false;
92
+ private _connected = false;
93
+ private _lastSuccessAt: string | null = null;
94
+ private _lastFailureAt: string | null = null;
95
+ private _lastErrorCode: string | null = null;
96
+ private _requests = 0;
97
+ private _successes = 0;
98
+ private _failures = 0;
99
+ private _featureEnabled = false;
100
+ private _service: ServiceName;
101
+
102
+ constructor(service: ServiceName) {
103
+ this._service = service;
104
+ }
105
+
106
+ get configured() { return this._configured; }
107
+ get initialized() { return this._initialized; }
108
+ get connected() { return this._connected; }
109
+ get lastSuccessAt() { return this._lastSuccessAt; }
110
+ get lastFailureAt() { return this._lastFailureAt; }
111
+ get lastErrorCode() { return this._lastErrorCode; }
112
+ get requests() { return this._requests; }
113
+ get successes() { return this._successes; }
114
+ get failures() { return this._failures; }
115
+ get featureEnabled() { return this._featureEnabled; }
116
+
117
+ setConfigured(v: boolean): this {
118
+ this._configured = v;
119
+ logServiceEvent({
120
+ ts: new Date().toISOString(),
121
+ level: "info",
122
+ service: this._service,
123
+ operation: "config_checked",
124
+ phase: "completed",
125
+ configured: v,
126
+ });
127
+ return this;
128
+ }
129
+
130
+ setInitialized(v: boolean): this {
131
+ this._initialized = v;
132
+ if (v) {
133
+ logServiceEvent({
134
+ ts: new Date().toISOString(),
135
+ level: "info",
136
+ service: this._service,
137
+ operation: "initialized",
138
+ phase: "completed",
139
+ });
140
+ }
141
+ return this;
142
+ }
143
+
144
+ setConnected(v: boolean): this {
145
+ this._connected = v;
146
+ logServiceEvent({
147
+ ts: new Date().toISOString(),
148
+ level: "info",
149
+ service: this._service,
150
+ operation: "connected",
151
+ phase: v ? "completed" : "failed",
152
+ connected: v,
153
+ });
154
+ return this;
155
+ }
156
+
157
+ setFeatureEnabled(v: boolean): this {
158
+ this._featureEnabled = v;
159
+ return this;
160
+ }
161
+
162
+ recordSuccess(operation: string, durationMs?: number, extra?: Record<string, unknown>): void {
163
+ this._requests++;
164
+ this._successes++;
165
+ this._lastSuccessAt = new Date().toISOString();
166
+ logServiceEvent({
167
+ ts: new Date().toISOString(),
168
+ level: "info",
169
+ service: this._service,
170
+ operation,
171
+ phase: "completed",
172
+ durationMs,
173
+ requests: this._requests,
174
+ successes: this._successes,
175
+ ...extra,
176
+ });
177
+ }
178
+
179
+ recordFailure(operation: string, errorCode: string, extra?: Record<string, unknown>): void {
180
+ this._requests++;
181
+ this._failures++;
182
+ this._lastFailureAt = new Date().toISOString();
183
+ this._lastErrorCode = errorCode;
184
+ logServiceEvent({
185
+ ts: new Date().toISOString(),
186
+ level: "error",
187
+ service: this._service,
188
+ operation,
189
+ phase: "failed",
190
+ errorCode,
191
+ requests: this._requests,
192
+ failures: this._failures,
193
+ ...extra,
194
+ });
195
+ }
196
+
197
+ toJSON(): Record<string, unknown> {
198
+ return {
199
+ service: this._service,
200
+ configured: this._configured,
201
+ initialized: this._initialized,
202
+ connected: this._connected,
203
+ lastSuccessAt: this._lastSuccessAt,
204
+ lastFailureAt: this._lastFailureAt,
205
+ lastErrorCode: this._lastErrorCode,
206
+ requests: this._requests,
207
+ successes: this._successes,
208
+ failures: this._failures,
209
+ featureEnabled: this._featureEnabled,
210
+ };
211
+ }
212
+ }
213
+
214
+ // Export service status instances
215
+ export const jevStatus = new ServiceStatus("jev");
216
+ export const honchoStatus = new ServiceStatus("honcho");
217
+ export const qdrantStatus = new ServiceStatus("qdrant");
package/index.ts CHANGED
@@ -78,6 +78,48 @@ import {
78
78
 
79
79
  // --- Jev Auto-Continue: Agent autonomous decision-making --------------------
80
80
  // Lazy import - only loads when packages/jev-judge exists and API key available
81
+
82
+ /**
83
+ * Get tasks from bd (beads) for Jev decision context.
84
+ * Returns { completed, remaining } arrays of task descriptions.
85
+ */
86
+ async function getTaskContext(): Promise<{
87
+ completed: string[];
88
+ remaining: string[];
89
+ total: number;
90
+ }> {
91
+ try {
92
+ const { execSync } = await import("node:child_process");
93
+ // Get open (pending + in_progress) tasks
94
+ const output = execSync("bd list --json 2>/dev/null", {
95
+ encoding: "utf8",
96
+ timeout: 5000,
97
+ });
98
+ const tasks = JSON.parse(output) as Array<{ title?: string; status?: string }>;
99
+
100
+ const completed: string[] = [];
101
+ const remaining: string[] = [];
102
+
103
+ for (const task of tasks ?? []) {
104
+ if (!task.title) continue;
105
+ if (task.status === "closed" || task.status === "done") {
106
+ completed.push(task.title);
107
+ } else {
108
+ remaining.push(task.title);
109
+ }
110
+ }
111
+
112
+ return {
113
+ completed,
114
+ remaining,
115
+ total: completed.length + remaining.length,
116
+ };
117
+ } catch {
118
+ // bd not available or error - return empty context
119
+ return { completed: [], remaining: [], total: 0 };
120
+ }
121
+ }
122
+
81
123
  async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
82
124
  try {
83
125
  // Check if Jev API key is available (env var or keys file)
@@ -103,18 +145,38 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
103
145
  return;
104
146
  }
105
147
 
148
+ // Resolve the actual API key: env takes priority, file is fallback
149
+ const resolvedApiKey = process.env.TYPESAFE_API_KEY ||
150
+ process.env.OPENROUTER_API_KEY ||
151
+ readFileSync(`${keysDir}/jev-api-key.txt`, "utf8").trim();
152
+
106
153
  const mod = await import("./packages/jev-judge/src/auto-continue.js");
107
154
 
108
155
  // Track waiting state
109
156
  let waitingForUserSince: Date | null = null;
110
157
  let lastUserMessage: Date | null = null;
158
+ let autoContinueTimer: NodeJS.Timeout | null = null;
159
+ let _inferenceInProgress = false; // Track for overlap prevention
160
+ let _sessionId = Date.now().toString(36); // Track session for cancellation
111
161
 
112
162
  // Detect when agent asks for continuation
113
163
  pi.on("message_end", async (event) => {
114
- const msg = event.message as { role?: string; content?: string } | undefined;
164
+ const msg = event.message as { role?: string; content?: string | Array<{ type?: string; text?: string }> } | undefined;
115
165
  if (!msg || msg.role !== "assistant") return;
116
166
 
117
- const content = typeof msg.content === "string" ? msg.content : "";
167
+ // Extract text from array content (TextContent blocks) or legacy string
168
+ let content = "";
169
+ if (typeof msg.content === "string") {
170
+ content = msg.content;
171
+ } else if (Array.isArray(msg.content)) {
172
+ // Extract text from TextContent blocks only; ignore thinking/tool calls
173
+ content = msg.content
174
+ .filter((block): block is { type: "text"; text: string } =>
175
+ block.type === "text" && typeof block.text === "string"
176
+ )
177
+ .map(block => block.text)
178
+ .join("\n");
179
+ }
118
180
  const hasContinuation = /continue|proceed|next phase|next step/i.test(content);
119
181
 
120
182
  if (hasContinuation && !waitingForUserSince) {
@@ -145,29 +207,64 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
145
207
  const MIN_WAIT_MINUTES = 5; // Minimum wait before auto-continue
146
208
 
147
209
  const checkAndAutoContinue = async () => {
210
+ // Prevent overlap if inference is already in progress
211
+ if (_inferenceInProgress) {
212
+ console.log("[auto-continue] Inference in progress, skipping this check");
213
+ return;
214
+ }
215
+
148
216
  if (!waitingForUserSince) return;
149
217
 
218
+ // Capture session ID at check time for post-inference validation
219
+ const checkSessionId = _sessionId;
220
+
150
221
  const waitMinutes = (Date.now() - waitingForUserSince.getTime()) / 60000;
151
222
  if (waitMinutes < MIN_WAIT_MINUTES) return;
152
223
 
153
224
  console.log(`[auto-continue] Checking after ${waitMinutes.toFixed(0)} minutes wait...`);
154
225
 
155
226
  try {
227
+ // Mark inference as in progress to prevent overlap
228
+ _inferenceInProgress = true;
229
+
156
230
  const judge = new mod.AutoContinueJudge({
157
- apiKey: process.env.TYPESAFE_API_KEY || process.env.OPENROUTER_API_KEY!,
231
+ apiKey: resolvedApiKey,
158
232
  proceedThreshold: 0.7,
159
233
  maxWaitMinutes: 30,
160
234
  });
161
235
 
236
+ // Get real task context from bd
237
+ const taskContext = await getTaskContext();
238
+
239
+ // Guard against empty task list
240
+ if (taskContext.total === 0) {
241
+ console.log("[auto-continue] No tasks to decide on — skipping");
242
+ _inferenceInProgress = false;
243
+ return;
244
+ }
245
+
162
246
  const taskState = mod.createTaskState(
163
- [], // completed tasks
164
- [], // remaining tasks
247
+ taskContext.completed,
248
+ taskContext.remaining,
165
249
  lastUserMessage || undefined,
166
250
  new Date(Date.now() - waitMinutes * 60000),
167
251
  );
168
252
 
169
253
  const decision = await judge.decide(taskState);
170
254
 
255
+ // Check session validity and user response before applying
256
+ if (checkSessionId !== _sessionId) {
257
+ console.log("[auto-continue] Session changed during inference — cancelling");
258
+ _inferenceInProgress = false;
259
+ return;
260
+ }
261
+
262
+ if (!waitingForUserSince) {
263
+ console.log("[auto-continue] User responded during inference — cancelling");
264
+ _inferenceInProgress = false;
265
+ return;
266
+ }
267
+
171
268
  console.log(
172
269
  `[auto-continue] Decision: ${decision.action} (${(decision.probability * 100).toFixed(0)}% confidence)`,
173
270
  );
@@ -197,7 +294,7 @@ async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
197
294
  };
198
295
 
199
296
  // Start periodic check
200
- setInterval(checkAndAutoContinue, AUTO_CONTINUE_INTERVAL_MS);
297
+ autoContinueTimer = setInterval(checkAndAutoContinue, AUTO_CONTINUE_INTERVAL_MS);
201
298
 
202
299
  console.error("[auto-continue] Jev Auto-Continue initialized");
203
300
  } catch {
@@ -336,7 +433,7 @@ async function initHoncho(pi: { events: { emit(name: string, data: unknown): voi
336
433
  const keyPath = `${home}/.pi-harness-runtime/keys/honcho-api-key.txt`;
337
434
 
338
435
  if (!existsSync(keyPath)) {
339
- logStartup("[pi-harness] Honcho memory not configured:");
436
+ logStartup("[pi-harness] Honcho not configured:");
340
437
  logStartup("[pi-harness] Get key: https://app.honcho.dev/api-keys");
341
438
  logStartup(`[pi-harness] Then: echo "{api-key}" > ~/.pi-harness-runtime/keys/honcho-api-key.txt`);
342
439
  return;
@@ -356,12 +453,19 @@ async function initHoncho(pi: { events: { emit(name: string, data: unknown): voi
356
453
  name: "honcho",
357
454
  definition: {
358
455
  url: "https://mcp.honcho.dev",
359
- auth: { header: `Bearer ${apiKey}` },
456
+ auth: "bearer" as const,
457
+ bearerToken: apiKey,
360
458
  },
361
459
  };
460
+
461
+ // Emit registration request — pi-mcp-adapter will respond with
462
+ // pi-mcp-adapter:runtime-register-result event on completion
362
463
  pi.events.emit("pi-mcp-adapter:runtime-register:v1", request);
464
+ console.log("[honcho] MCP registration emitted (awaiting adapter ack)");
363
465
  });
364
- logStartup("[pi-harness] Honcho memory ready (peer/user memory via mcp.honcho.dev)");
466
+
467
+ // Report pending state — actual memory active requires explicit lifecycle
468
+ logStartup("[pi-harness] Honcho registered (awaiting adapter connection)");
365
469
  } catch (err) {
366
470
  logStartup("[pi-harness] Honcho: init failed:", err instanceof Error ? err.message : String(err));
367
471
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-harness-runtime",
3
- "version": "1.1.152",
3
+ "version": "1.1.154",
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.152",
3
+ "version": "1.1.154",
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.152",
3
+ "version": "1.1.154",
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.152",
3
+ "version": "1.1.154",
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.152",
3
+ "version": "1.1.154",
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.152",
3
+ "version": "1.1.154",
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.152",
3
+ "version": "1.1.154",
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.152",
3
+ "version": "1.1.154",
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.152",
3
+ "version": "1.1.154",
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",