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.
- package/harness/langchain/graph.ts +9 -9
- package/harness/langchain/{jev-logger.ts → planner-logger.ts} +25 -24
- package/harness/service-diagnostics.ts +217 -0
- package/index.ts +113 -9
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/dist/index.d.ts +14 -3
- package/packages/jev-judge/dist/index.d.ts.map +1 -1
- package/packages/jev-judge/dist/index.js +54 -12
- package/packages/jev-judge/dist/index.js.map +1 -1
- package/packages/jev-judge/dist/typesafe-provider.d.ts +66 -0
- package/packages/jev-judge/dist/typesafe-provider.d.ts.map +1 -0
- package/packages/jev-judge/dist/typesafe-provider.js +220 -0
- package/packages/jev-judge/dist/typesafe-provider.js.map +1 -0
- package/packages/jev-judge/package.json +1 -1
- package/packages/jev-judge/src/index.ts +58 -12
- package/packages/jev-judge/src/typesafe-provider.ts +335 -0
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/dist/client.d.ts.map +1 -1
- package/packages/qdrant-skills/dist/client.js +4 -3
- package/packages/qdrant-skills/dist/client.js.map +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/qdrant-skills/src/client.ts +14 -17
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/dist/loader.d.ts +9 -2
- package/packages/skills/dist/loader.d.ts.map +1 -1
- package/packages/skills/dist/loader.js +24 -7
- package/packages/skills/dist/loader.js.map +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/skills/src/loader.ts +27 -7
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- 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
|
|
460
|
+
// Import and use planner logger (async - logs to file + Telegram)
|
|
461
461
|
try {
|
|
462
|
-
const {
|
|
463
|
-
const
|
|
464
|
-
setJevProvider(
|
|
465
|
-
await
|
|
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:
|
|
469
|
-
reason:
|
|
470
|
-
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("[
|
|
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
|
-
*
|
|
2
|
+
* Planner Logger - Logs LangChain planner decisions
|
|
3
3
|
*
|
|
4
|
-
* Logs
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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, "
|
|
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:
|
|
58
|
+
function formatFileLog(entry: PlannerLogEntry): string {
|
|
58
59
|
const lines = [
|
|
59
60
|
`═══════════════════════════════════════════════════════════════`,
|
|
60
|
-
`[
|
|
61
|
+
`[PLANNER] ${entry.timestamp}`,
|
|
61
62
|
`═══════════════════════════════════════════════════════════════`,
|
|
62
63
|
``,
|
|
63
64
|
`## Request`,
|
|
64
65
|
`${entry.request}`,
|
|
65
66
|
``,
|
|
66
|
-
`##
|
|
67
|
+
`## Provider Decision`,
|
|
67
68
|
`Provider: ${entry.provider.toUpperCase()}`,
|
|
68
69
|
`Reason: ${entry.reason}`,
|
|
69
70
|
``,
|
|
70
|
-
`##
|
|
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:
|
|
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} *
|
|
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("[
|
|
129
|
+
console.log("[PLANNER] Telegram not configured - skipping");
|
|
129
130
|
return;
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
console.log(`[
|
|
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("[
|
|
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(`[
|
|
160
|
+
console.error(`[PLANNER] Telegram send failed: ${response.status}`);
|
|
160
161
|
} else {
|
|
161
|
-
console.log("[
|
|
162
|
+
console.log("[PLANNER] Telegram sent successfully");
|
|
162
163
|
}
|
|
163
164
|
} catch (err) {
|
|
164
|
-
console.error(`[
|
|
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
|
|
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("[
|
|
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
|
|
193
|
+
* Parse planner's decision from output
|
|
193
194
|
* Looks for [JEVO] and [USER_INPUT] markers
|
|
194
195
|
*/
|
|
195
|
-
export function
|
|
196
|
+
export function parsePlannerDecision(planOutput: string): {
|
|
196
197
|
provider: "minimax" | "gpt";
|
|
197
198
|
reason: string;
|
|
198
|
-
userInput:
|
|
199
|
+
userInput: PlannerDecision["userInput"];
|
|
199
200
|
} {
|
|
200
201
|
const result: {
|
|
201
202
|
provider: "minimax" | "gpt";
|
|
202
203
|
reason: string;
|
|
203
|
-
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
|
-
|
|
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:
|
|
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
|
-
|
|
164
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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/capability-registry",
|
|
3
|
-
"version": "1.1.
|
|
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",
|