pi-harness-runtime 1.1.183 → 1.1.185
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/CHANGELOG.md +7 -0
- package/harness/glm-quota-countdown.ts +9 -1
- package/harness/honcho-init.ts +745 -0
- package/harness/honcho-memory.ts +290 -97
- package/harness/service-commands.ts +28 -3
- package/index.ts +191 -63
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/dist/extension.d.ts.map +1 -1
- package/packages/agent-policy/dist/extension.js +16 -9
- package/packages/agent-policy/dist/extension.js.map +1 -1
- package/packages/agent-policy/dist/policy-loader.d.ts.map +1 -1
- package/packages/agent-policy/dist/policy-loader.js +5 -16
- package/packages/agent-policy/dist/policy-loader.js.map +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/agent-policy/src/extension.ts +16 -9
- package/packages/agent-policy/src/policy-loader.ts +5 -22
- package/packages/agent-policy/test/interlock.test.ts +101 -0
- 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/e2e-tester/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/package.json +1 -1
- 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/dist/adapters/telegram-adapter.d.ts +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.js +34 -21
- package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/notification/src/adapters/telegram-adapter.ts +37 -21
- 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/package.json +1 -1
- 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/package.json +1 -1
- 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/telegram/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/webmcp-tester/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
package/harness/honcho-memory.ts
CHANGED
|
@@ -12,79 +12,151 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { logServiceEvent, honchoStatus } from "./service-diagnostics.js";
|
|
15
|
-
import {
|
|
15
|
+
import { honchoToolCall } from "./honcho-init.js";
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "node:fs";
|
|
16
17
|
import { join } from "node:path";
|
|
17
18
|
import { homedir } from "node:os";
|
|
18
19
|
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
|
-
//
|
|
21
|
-
//
|
|
21
|
+
// Honcho MCP Configuration (non-destructive)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
22
23
|
|
|
23
24
|
const MCP_CONFIG_DIR = join(homedir(), ".config", "mcp");
|
|
24
25
|
const MCP_CONFIG_FILE = join(MCP_CONFIG_DIR, "mcp.json");
|
|
25
26
|
const HONCHO_KEY_FILE = join(homedir(), ".pi-harness-runtime", "keys", "honcho-api-key.txt");
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Secrets to redact from messages before transmission.
|
|
30
|
+
* These patterns are checked case-insensitively.
|
|
31
|
+
* Matches: key=VALUE, key: VALUE, key: Bearer VALUE, etc.
|
|
32
|
+
* For compound keys like api_key, match the full key name.
|
|
33
|
+
*/
|
|
34
|
+
const SECRET_PATTERNS: (string | RegExp)[] = [
|
|
35
|
+
// Compound patterns (check first, more specific)
|
|
36
|
+
"api[_-]?key",
|
|
37
|
+
"honcho[_-]?api[_-]?key",
|
|
38
|
+
"minimax[_-]?api[_-]?key",
|
|
39
|
+
"openai[_-]?api[_-]?key",
|
|
40
|
+
// Simple patterns
|
|
41
|
+
"auth",
|
|
42
|
+
"authorization",
|
|
43
|
+
"bearer",
|
|
44
|
+
"token",
|
|
45
|
+
"secret",
|
|
46
|
+
"password",
|
|
47
|
+
"credential",
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Redact secrets from text content.
|
|
52
|
+
* Replaces secret VALUES with [REDACTED].
|
|
53
|
+
* Handles: password=VALUE, password: VALUE, auth: Bearer VALUE, etc.
|
|
54
|
+
*/
|
|
55
|
+
function redactSecrets(text: string): string {
|
|
56
|
+
let result = text;
|
|
57
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
58
|
+
if (typeof pattern === "string") {
|
|
59
|
+
// Escape special regex chars in pattern string
|
|
60
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
61
|
+
// Match key=VALUE, key: VALUE, key: Bearer VALUE patterns
|
|
62
|
+
// Allow optional space after colon and multi-word values
|
|
63
|
+
// The separator can be =, :, or _ (for compound keys like api_key_value)
|
|
64
|
+
const regex = new RegExp(
|
|
65
|
+
`(${escaped})(?:[_=:]\\s*)(\\S+(?:\\s+\\S+)*)`,
|
|
66
|
+
"gi"
|
|
67
|
+
);
|
|
68
|
+
result = result.replace(regex, "$1=[REDACTED]");
|
|
69
|
+
} else {
|
|
70
|
+
// For regex patterns, redact the entire match
|
|
71
|
+
// Add 'g' flag if not present
|
|
72
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
|
|
73
|
+
const globalPattern = new RegExp(pattern.source, flags);
|
|
74
|
+
result = result.replace(globalPattern, "[REDACTED]");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Ensure MCP config directory exists (non-destructive).
|
|
82
|
+
* Does NOT write config at import time.
|
|
83
|
+
*/
|
|
84
|
+
export function ensureMcpConfigDirectory(): void {
|
|
29
85
|
if (!existsSync(MCP_CONFIG_DIR)) {
|
|
30
86
|
mkdirSync(MCP_CONFIG_DIR, { recursive: true, mode: 0o755 });
|
|
31
87
|
}
|
|
88
|
+
}
|
|
32
89
|
|
|
33
|
-
|
|
34
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Load Honcho API key from keys directory.
|
|
92
|
+
* Does NOT write config files.
|
|
93
|
+
*/
|
|
94
|
+
export function loadHonchoApiKey(): string | undefined {
|
|
95
|
+
if (existsSync(HONCHO_KEY_FILE)) {
|
|
35
96
|
try {
|
|
36
|
-
|
|
37
|
-
const config = JSON.parse(content);
|
|
38
|
-
if (config.mcpServers?.honcho?.bearerToken &&
|
|
39
|
-
config.mcpServers.honcho.bearerToken !== "YOUR_HONCHO_API_KEY") {
|
|
40
|
-
return; // Already configured with real key
|
|
41
|
-
}
|
|
97
|
+
return readFileSync(HONCHO_KEY_FILE, "utf8").trim() || undefined;
|
|
42
98
|
} catch {
|
|
43
|
-
|
|
99
|
+
return undefined;
|
|
44
100
|
}
|
|
45
101
|
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
46
104
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
105
|
+
/**
|
|
106
|
+
* Read existing MCP config (non-destructive).
|
|
107
|
+
* Returns null if config doesn't exist or is invalid.
|
|
108
|
+
*/
|
|
109
|
+
export function readMcpConfig(): Record<string, unknown> | null {
|
|
110
|
+
if (!existsSync(MCP_CONFIG_FILE)) {
|
|
111
|
+
return null;
|
|
55
112
|
}
|
|
113
|
+
try {
|
|
114
|
+
const content = readFileSync(MCP_CONFIG_FILE, "utf8");
|
|
115
|
+
return JSON.parse(content);
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
56
120
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Write MCP config atomically (non-destructive).
|
|
123
|
+
* Only updates the Honcho server entry, preserves other entries.
|
|
124
|
+
*/
|
|
125
|
+
export function writeHonchoMcpConfig(apiKey: string): void {
|
|
126
|
+
ensureMcpConfigDirectory();
|
|
127
|
+
|
|
128
|
+
// Read existing config or create new one
|
|
129
|
+
let config: Record<string, unknown> = readMcpConfig() || { mcpServers: {} };
|
|
130
|
+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
|
131
|
+
config = { mcpServers: {} };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Update Honcho entry
|
|
135
|
+
const mcpServers = config.mcpServers as Record<string, unknown>;
|
|
136
|
+
mcpServers.honcho = {
|
|
137
|
+
url: "https://mcp.honcho.dev",
|
|
138
|
+
headers: {
|
|
139
|
+
Authorization: `Bearer ${apiKey}`,
|
|
66
140
|
},
|
|
67
141
|
};
|
|
68
142
|
|
|
143
|
+
// Atomic write with temp file
|
|
144
|
+
const tempFile = `${MCP_CONFIG_FILE}.tmp`;
|
|
145
|
+
writeFileSync(tempFile, JSON.stringify(config, null, 2), { mode: 0o644 });
|
|
69
146
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
} catch (err) {
|
|
78
|
-
console.warn("[honcho] Failed to create MCP config:", err);
|
|
147
|
+
// Verify the temp file is valid JSON before renaming
|
|
148
|
+
JSON.parse(readFileSync(tempFile, "utf8"));
|
|
149
|
+
renameSync(tempFile, MCP_CONFIG_FILE);
|
|
150
|
+
} catch {
|
|
151
|
+
// Clean up invalid temp file
|
|
152
|
+
try { unlinkSync(tempFile); } catch { /* ignore */ }
|
|
153
|
+
throw new Error("Failed to write valid MCP config");
|
|
79
154
|
}
|
|
80
155
|
}
|
|
81
156
|
|
|
82
|
-
// Auto-create MCP config on module load
|
|
83
|
-
ensureMcpConfig();
|
|
84
|
-
|
|
85
157
|
// ---------------------------------------------------------------------------
|
|
86
158
|
// Honcho Health Check - Detect suspended accounts
|
|
87
|
-
//
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
88
160
|
|
|
89
161
|
interface HonchoHealthResponse {
|
|
90
162
|
status: string;
|
|
@@ -96,13 +168,8 @@ let _honchoHealthInterval: ReturnType<typeof setInterval> | null = null;
|
|
|
96
168
|
const HONCHO_HEALTH_CHECK_INTERVAL = 5 * 60 * 1000; // Check every 5 minutes
|
|
97
169
|
const HONCHO_HEALTH_ENDPOINT = "https://api.honcho.dev/v1/health";
|
|
98
170
|
|
|
99
|
-
// Start only after the timer state and health-check constants are initialized.
|
|
100
|
-
startHonchoHealthCheck();
|
|
101
|
-
|
|
102
171
|
async function checkHonchoHealth(): Promise<void> {
|
|
103
|
-
const apiKey =
|
|
104
|
-
? readFileSync(HONCHO_KEY_FILE, "utf8").trim()
|
|
105
|
-
: null;
|
|
172
|
+
const apiKey = loadHonchoApiKey();
|
|
106
173
|
|
|
107
174
|
if (!apiKey) {
|
|
108
175
|
return; // No key, skip health check
|
|
@@ -166,6 +233,10 @@ export function stopHonchoHealthCheck(): void {
|
|
|
166
233
|
}
|
|
167
234
|
}
|
|
168
235
|
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Honcho Memory Implementation
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
|
|
169
240
|
/**
|
|
170
241
|
* Honcho memory configuration
|
|
171
242
|
*/
|
|
@@ -178,6 +249,8 @@ export interface HonchoMemoryConfig {
|
|
|
178
249
|
userPeerId?: string;
|
|
179
250
|
/** Assistant peer ID */
|
|
180
251
|
assistantPeerId?: string;
|
|
252
|
+
/** Session ID for memory storage */
|
|
253
|
+
sessionId?: string;
|
|
181
254
|
/** Max context size to inject (chars) */
|
|
182
255
|
maxContextChars?: number;
|
|
183
256
|
/** Enable auto-ingestion */
|
|
@@ -201,6 +274,8 @@ export class HonchoMemory {
|
|
|
201
274
|
private isConnected = false;
|
|
202
275
|
private toolsDiscovered = false;
|
|
203
276
|
private workspaceId: string | null = null;
|
|
277
|
+
private sessionId: string | null = null;
|
|
278
|
+
private _disposed = false;
|
|
204
279
|
|
|
205
280
|
constructor(config: HonchoMemoryConfig) {
|
|
206
281
|
this.config = {
|
|
@@ -208,6 +283,8 @@ export class HonchoMemory {
|
|
|
208
283
|
autoIngest: true,
|
|
209
284
|
...config,
|
|
210
285
|
};
|
|
286
|
+
// Store session ID from config
|
|
287
|
+
this.sessionId = config.sessionId ?? null;
|
|
211
288
|
}
|
|
212
289
|
|
|
213
290
|
/**
|
|
@@ -258,6 +335,21 @@ export class HonchoMemory {
|
|
|
258
335
|
});
|
|
259
336
|
}
|
|
260
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Set session ID for memory storage
|
|
340
|
+
*/
|
|
341
|
+
setSession(sessionId: string): void {
|
|
342
|
+
this.sessionId = sessionId;
|
|
343
|
+
logServiceEvent({
|
|
344
|
+
ts: new Date().toISOString(),
|
|
345
|
+
level: "info",
|
|
346
|
+
service: "honcho",
|
|
347
|
+
operation: "session_created",
|
|
348
|
+
phase: "completed",
|
|
349
|
+
sessionId,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
261
353
|
/**
|
|
262
354
|
* Check if memory is ready
|
|
263
355
|
*/
|
|
@@ -266,15 +358,18 @@ export class HonchoMemory {
|
|
|
266
358
|
}
|
|
267
359
|
|
|
268
360
|
/**
|
|
269
|
-
* Generate deduplication ID for a message
|
|
361
|
+
* Generate stable deduplication ID for a message.
|
|
362
|
+
* Uses content hash + event identity (not current time) for retry stability.
|
|
270
363
|
*/
|
|
271
|
-
|
|
364
|
+
generateMessageId(
|
|
365
|
+
eventId: string,
|
|
272
366
|
role: string,
|
|
273
367
|
content: string,
|
|
274
368
|
timestamp: Date
|
|
275
369
|
): string {
|
|
276
|
-
//
|
|
277
|
-
|
|
370
|
+
// Stable hash: eventId ensures retry stability
|
|
371
|
+
// timestamp is from the original event, not Date.now()
|
|
372
|
+
const data = `${eventId}:${role}:${content}:${timestamp.toISOString()}`;
|
|
278
373
|
let hash = 0;
|
|
279
374
|
for (let i = 0; i < data.length; i++) {
|
|
280
375
|
const char = data.charCodeAt(i);
|
|
@@ -285,39 +380,70 @@ export class HonchoMemory {
|
|
|
285
380
|
}
|
|
286
381
|
|
|
287
382
|
/**
|
|
288
|
-
* Sanitize message content for storage
|
|
289
|
-
*
|
|
383
|
+
* Sanitize message content for storage.
|
|
384
|
+
* Redacts secrets and enforces total payload bound.
|
|
290
385
|
*/
|
|
291
|
-
|
|
386
|
+
sanitizeContent(
|
|
292
387
|
content: string | Array<{ type?: string; text?: string }>
|
|
293
|
-
): string {
|
|
294
|
-
|
|
295
|
-
// Truncate very long messages
|
|
296
|
-
return content.slice(0, 2000);
|
|
297
|
-
}
|
|
388
|
+
): { text: string; originalLength: number } {
|
|
389
|
+
let text: string;
|
|
298
390
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
391
|
+
if (typeof content === "string") {
|
|
392
|
+
text = content;
|
|
393
|
+
} else if (Array.isArray(content)) {
|
|
394
|
+
text = content
|
|
302
395
|
.filter((block) => block.type === "text")
|
|
303
|
-
.map((block) => {
|
|
304
|
-
const text = (block as { text?: string }).text ?? "";
|
|
305
|
-
return text.slice(0, 2000);
|
|
306
|
-
})
|
|
396
|
+
.map((block) => (block as { text?: string }).text ?? "")
|
|
307
397
|
.join("\n");
|
|
398
|
+
} else {
|
|
399
|
+
text = String(content);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const originalLength = text.length;
|
|
403
|
+
|
|
404
|
+
// Step 1: Redact secrets before truncation
|
|
405
|
+
text = redactSecrets(text);
|
|
406
|
+
|
|
407
|
+
// Step 2: Truncate to per-block limit
|
|
408
|
+
const BLOCK_LIMIT = 2000;
|
|
409
|
+
if (text.length > BLOCK_LIMIT) {
|
|
410
|
+
text = text.slice(0, BLOCK_LIMIT) + "...[truncated]";
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Step 3: Enforce total payload bound (separate limit for ingestion vs retrieval)
|
|
414
|
+
const TOTAL_LIMIT = 10000;
|
|
415
|
+
if (text.length > TOTAL_LIMIT) {
|
|
416
|
+
text = text.slice(0, TOTAL_LIMIT) + "...[payload truncated]";
|
|
308
417
|
}
|
|
309
418
|
|
|
310
|
-
return
|
|
419
|
+
return { text, originalLength };
|
|
311
420
|
}
|
|
312
421
|
|
|
313
422
|
/**
|
|
314
|
-
*
|
|
423
|
+
* Call a Honcho MCP tool through the adapter.
|
|
424
|
+
* Uses the honchoToolCall from honcho-init.ts.
|
|
425
|
+
*/
|
|
426
|
+
private async callHonchoTool(
|
|
427
|
+
toolName: string,
|
|
428
|
+
args: Record<string, unknown>
|
|
429
|
+
): Promise<{ success: boolean; result?: unknown; error?: string }> {
|
|
430
|
+
return await honchoToolCall(toolName, args);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Ingest a completed turn message through Honcho MCP.
|
|
435
|
+
* Persists only after verified successful response.
|
|
315
436
|
*/
|
|
316
437
|
async ingestMessage(
|
|
317
438
|
role: "user" | "assistant",
|
|
318
439
|
content: string | Array<{ type?: string; text?: string }>,
|
|
319
|
-
timestamp: Date = new Date()
|
|
440
|
+
timestamp: Date = new Date(),
|
|
441
|
+
eventId: string = `msg-${timestamp.getTime()}`
|
|
320
442
|
): Promise<{ success: boolean; deduped: boolean; error?: string }> {
|
|
443
|
+
if (this._disposed) {
|
|
444
|
+
return { success: false, deduped: false, error: "memory disposed" };
|
|
445
|
+
}
|
|
446
|
+
|
|
321
447
|
if (!this.isReady()) {
|
|
322
448
|
return { success: false, deduped: false, error: "memory not ready" };
|
|
323
449
|
}
|
|
@@ -326,9 +452,10 @@ export class HonchoMemory {
|
|
|
326
452
|
return { success: false, deduped: false, error: "auto-ingest disabled" };
|
|
327
453
|
}
|
|
328
454
|
|
|
329
|
-
const
|
|
455
|
+
const { text: sanitized, originalLength } = this.sanitizeContent(content);
|
|
456
|
+
const messageId = this.generateMessageId(eventId, role, sanitized, timestamp);
|
|
330
457
|
|
|
331
|
-
// Check deduplication
|
|
458
|
+
// Check deduplication BEFORE any remote call
|
|
332
459
|
if (this.ingestedMessages.has(messageId)) {
|
|
333
460
|
logServiceEvent({
|
|
334
461
|
ts: new Date().toISOString(),
|
|
@@ -342,22 +469,34 @@ export class HonchoMemory {
|
|
|
342
469
|
return { success: true, deduped: true };
|
|
343
470
|
}
|
|
344
471
|
|
|
345
|
-
|
|
472
|
+
logServiceEvent({
|
|
473
|
+
ts: new Date().toISOString(),
|
|
474
|
+
level: "info",
|
|
475
|
+
service: "honcho",
|
|
476
|
+
operation: "ingest_started",
|
|
477
|
+
phase: "start",
|
|
478
|
+
role,
|
|
479
|
+
messageLength: sanitized.length,
|
|
480
|
+
originalLength,
|
|
481
|
+
});
|
|
346
482
|
|
|
347
483
|
try {
|
|
348
|
-
// Call Honcho
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
service: "honcho",
|
|
354
|
-
operation: "ingest_started",
|
|
355
|
-
phase: "start",
|
|
484
|
+
// Call Honcho MCP tool through the adapter
|
|
485
|
+
// Use sessionId if available, fall back to workspaceId
|
|
486
|
+
const targetSessionId = this.sessionId ?? this.workspaceId;
|
|
487
|
+
const result = await this.callHonchoTool("add_message", {
|
|
488
|
+
session_id: targetSessionId,
|
|
356
489
|
role,
|
|
357
|
-
|
|
490
|
+
content: sanitized,
|
|
491
|
+
message_id: messageId,
|
|
492
|
+
timestamp: timestamp.toISOString(),
|
|
358
493
|
});
|
|
359
494
|
|
|
360
|
-
|
|
495
|
+
if (!result.success) {
|
|
496
|
+
throw new Error(result.error || "Tool call failed");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Record success ONLY after verified response
|
|
361
500
|
this.ingestedMessages.set(messageId, {
|
|
362
501
|
id: messageId,
|
|
363
502
|
ingestedAt: timestamp,
|
|
@@ -403,7 +542,8 @@ export class HonchoMemory {
|
|
|
403
542
|
}
|
|
404
543
|
|
|
405
544
|
/**
|
|
406
|
-
* Retrieve relevant context
|
|
545
|
+
* Retrieve relevant context from Honcho MCP.
|
|
546
|
+
* Returns real retrieved content, never fabricates.
|
|
407
547
|
*/
|
|
408
548
|
async retrieveContext(query: string): Promise<{
|
|
409
549
|
success: boolean;
|
|
@@ -411,24 +551,55 @@ export class HonchoMemory {
|
|
|
411
551
|
empty: boolean;
|
|
412
552
|
error?: string;
|
|
413
553
|
}> {
|
|
554
|
+
if (this._disposed) {
|
|
555
|
+
return { success: false, context: "", empty: true, error: "memory disposed" };
|
|
556
|
+
}
|
|
557
|
+
|
|
414
558
|
if (!this.isReady()) {
|
|
415
559
|
return { success: false, context: "", empty: true, error: "memory not ready" };
|
|
416
560
|
}
|
|
417
561
|
|
|
562
|
+
logServiceEvent({
|
|
563
|
+
ts: new Date().toISOString(),
|
|
564
|
+
level: "info",
|
|
565
|
+
service: "honcho",
|
|
566
|
+
operation: "retrieve_started",
|
|
567
|
+
phase: "start",
|
|
568
|
+
queryLength: query.length,
|
|
569
|
+
});
|
|
570
|
+
|
|
418
571
|
try {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
572
|
+
// Call Honcho get_session_context tool through the adapter
|
|
573
|
+
// Use sessionId if available, fall back to workspaceId
|
|
574
|
+
const targetSessionId = this.sessionId ?? this.workspaceId;
|
|
575
|
+
const result = await this.callHonchoTool("get_context", {
|
|
576
|
+
session_id: targetSessionId,
|
|
577
|
+
query,
|
|
578
|
+
limit: 10,
|
|
426
579
|
});
|
|
427
580
|
|
|
428
|
-
|
|
429
|
-
|
|
581
|
+
if (!result.success) {
|
|
582
|
+
throw new Error(result.error || "Tool call failed");
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Parse actual response content
|
|
586
|
+
let context = "";
|
|
587
|
+
if (result.result && typeof result.result === "object") {
|
|
588
|
+
const response = result.result as Record<string, unknown>;
|
|
589
|
+
// Extract context from response based on actual Honcho API shape
|
|
590
|
+
context = (response.content as string) ||
|
|
591
|
+
(response.messages as string[])?.join("\n") ||
|
|
592
|
+
JSON.stringify(response);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Apply max context chars limit
|
|
596
|
+
const maxChars = this.config.maxContextChars ?? 4000;
|
|
597
|
+
if (context.length > maxChars) {
|
|
598
|
+
context = context.slice(0, maxChars) + "...[context truncated]";
|
|
599
|
+
}
|
|
430
600
|
|
|
431
|
-
|
|
601
|
+
// Empty retrieval is a valid result, not a fabricated context
|
|
602
|
+
const isEmpty = context.trim().length === 0;
|
|
432
603
|
|
|
433
604
|
logServiceEvent({
|
|
434
605
|
ts: new Date().toISOString(),
|
|
@@ -437,6 +608,7 @@ export class HonchoMemory {
|
|
|
437
608
|
operation: "retrieve_succeeded",
|
|
438
609
|
phase: "completed",
|
|
439
610
|
contextLength: context.length,
|
|
611
|
+
empty: isEmpty,
|
|
440
612
|
});
|
|
441
613
|
|
|
442
614
|
honchoStatus.recordSuccess("retrieve", undefined, { contextLength: context.length });
|
|
@@ -444,7 +616,7 @@ export class HonchoMemory {
|
|
|
444
616
|
return {
|
|
445
617
|
success: true,
|
|
446
618
|
context,
|
|
447
|
-
empty:
|
|
619
|
+
empty: isEmpty,
|
|
448
620
|
};
|
|
449
621
|
} catch (err) {
|
|
450
622
|
const error = err instanceof Error ? err.message : String(err);
|
|
@@ -459,6 +631,7 @@ export class HonchoMemory {
|
|
|
459
631
|
|
|
460
632
|
honchoStatus.recordFailure("retrieve", "unknown", { error });
|
|
461
633
|
|
|
634
|
+
// On failure, return empty context (don't fabricate)
|
|
462
635
|
return { success: false, context: "", empty: true, error };
|
|
463
636
|
}
|
|
464
637
|
}
|
|
@@ -481,4 +654,24 @@ export class HonchoMemory {
|
|
|
481
654
|
ingestedCount: this.ingestedMessages.size,
|
|
482
655
|
};
|
|
483
656
|
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Dispose of the memory instance.
|
|
660
|
+
* Stops timers, clears state, prevents further operations.
|
|
661
|
+
*/
|
|
662
|
+
dispose(): void {
|
|
663
|
+
this._disposed = true;
|
|
664
|
+
this.ingestedMessages.clear();
|
|
665
|
+
honchoStatus.setConnected(false);
|
|
666
|
+
logServiceEvent({
|
|
667
|
+
ts: new Date().toISOString(),
|
|
668
|
+
level: "info",
|
|
669
|
+
service: "honcho",
|
|
670
|
+
operation: "disposed",
|
|
671
|
+
phase: "completed",
|
|
672
|
+
});
|
|
673
|
+
}
|
|
484
674
|
}
|
|
675
|
+
|
|
676
|
+
// Export redactSecrets for testing
|
|
677
|
+
export { redactSecrets };
|
|
@@ -116,6 +116,8 @@ async function handleTestJev(ctx: ExtensionContext, flags: Record<string, string
|
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
118
|
* Handle /harness-services test honcho --read-only
|
|
119
|
+
* Checks Honcho integration status through the MCP adapter.
|
|
120
|
+
* Does NOT create sessions/peers, write messages, or claim success when only flags were inspected.
|
|
119
121
|
*/
|
|
120
122
|
async function handleTestHoncho(ctx: ExtensionContext, flags: Record<string, string | boolean>): Promise<void> {
|
|
121
123
|
if (flags.read_only !== true && flags.read_only !== "true") {
|
|
@@ -126,9 +128,32 @@ async function handleTestHoncho(ctx: ExtensionContext, flags: Record<string, str
|
|
|
126
128
|
ctx.ui.notify("[services] Testing Honcho MCP connection...", "info");
|
|
127
129
|
|
|
128
130
|
try {
|
|
129
|
-
|
|
130
|
-
ctx.ui.notify(
|
|
131
|
-
ctx.ui.notify(
|
|
131
|
+
// Report actual registration status from the status tracker
|
|
132
|
+
ctx.ui.notify(`[services] Honcho configured: ${honchoStatus.configured}`, "info");
|
|
133
|
+
ctx.ui.notify(`[services] Honcho connected: ${honchoStatus.connected}`, "info");
|
|
134
|
+
ctx.ui.notify(`[services] Honcho feature enabled: ${honchoStatus.featureEnabled}`, "info");
|
|
135
|
+
|
|
136
|
+
// Check if MCP adapter is available via globalThis
|
|
137
|
+
const piEvents = (globalThis as { pi?: { events?: { emit: (event: string, req: unknown) } } }).pi?.events;
|
|
138
|
+
|
|
139
|
+
if (!piEvents) {
|
|
140
|
+
ctx.ui.notify("[services] Honcho test skipped: MCP adapter not available (pi.events)", "warning");
|
|
141
|
+
ctx.ui.notify("[services] Note: Install pi-mcp-adapter to enable Honcho MCP", "info");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// For read-only test, report current status from our tracker
|
|
146
|
+
// A full MCP tool discovery would require a non-read-only request
|
|
147
|
+
if (honchoStatus.connected) {
|
|
148
|
+
ctx.ui.notify("[services] Honcho MCP server is connected", "info");
|
|
149
|
+
ctx.ui.notify("[services] Honcho test completed successfully", "info");
|
|
150
|
+
} else if (honchoStatus.configured) {
|
|
151
|
+
ctx.ui.notify("[services] Honcho MCP server not yet connected", "warning");
|
|
152
|
+
ctx.ui.notify("[services] Connection will be established on first use", "info");
|
|
153
|
+
} else {
|
|
154
|
+
ctx.ui.notify("[services] Honcho MCP not configured", "warning");
|
|
155
|
+
ctx.ui.notify("[services] Add API key to enable Honcho memory", "info");
|
|
156
|
+
}
|
|
132
157
|
} catch (err) {
|
|
133
158
|
const error = err instanceof Error ? err.message : String(err);
|
|
134
159
|
ctx.ui.notify(`[services] Honcho test failed: ${error}`, "error");
|