pi-harness-runtime 1.1.457 → 1.1.459
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/AGENTS.md +6 -4
- package/CHANGELOG.md +68 -0
- package/README.md +10 -2
- package/harness/honcho-init.ts +907 -192
- package/harness/honcho-memory.ts +31 -69
- package/harness/key-file-audit.ts +79 -1
- package/harness/service-commands.ts +48 -16
- package/index.ts +244 -48
- package/package.json +3 -8
- package/packages/agent-policy/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/evaluation-engine/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/file-copy-helper/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/notification/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/prompt-compiler/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/relay/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/tooling-preference/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/two-phase-review/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/write-review/package.json +1 -1
package/harness/honcho-memory.ts
CHANGED
|
@@ -13,16 +13,14 @@
|
|
|
13
13
|
|
|
14
14
|
import { logServiceEvent, honchoStatus } from "./service-diagnostics.js";
|
|
15
15
|
import { honchoPayload, honchoToolCall } from "./honcho-init.js";
|
|
16
|
-
import { existsSync,
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { homedir } from "node:os";
|
|
19
19
|
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
|
-
// Honcho
|
|
21
|
+
// Honcho credential
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
23
23
|
|
|
24
|
-
const MCP_CONFIG_DIR = join(homedir(), ".config", "mcp");
|
|
25
|
-
const MCP_CONFIG_FILE = join(MCP_CONFIG_DIR, "mcp.json");
|
|
26
24
|
const HONCHO_KEY_FILE = join(homedir(), ".pi-harness-runtime", "keys", "honcho-api-key.txt");
|
|
27
25
|
|
|
28
26
|
/**
|
|
@@ -78,20 +76,30 @@ function redactSecrets(text: string): string {
|
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (!existsSync(MCP_CONFIG_DIR)) {
|
|
86
|
-
mkdirSync(MCP_CONFIG_DIR, { recursive: true, mode: 0o755 });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Load Honcho API key from keys directory.
|
|
79
|
+
* Load the Honcho API key.
|
|
80
|
+
*
|
|
81
|
+
* The environment variable wins over the key file: it is the explicit
|
|
82
|
+
* override of a session. The key file is the per-user default.
|
|
92
83
|
* Does NOT write config files.
|
|
84
|
+
*
|
|
85
|
+
* This is deliberately not `key-loader.loadKey`: that one resolves the key
|
|
86
|
+
* through the Infisical provider, and the Honcho startup must stay a local
|
|
87
|
+
* read (no secret manager, no network in the startup path). The environment
|
|
88
|
+
* it reads is already filled by the key loader when another runtime filled
|
|
89
|
+
* it.
|
|
93
90
|
*/
|
|
94
91
|
export function loadHonchoApiKey(): string | undefined {
|
|
92
|
+
// `process` does not exist in every context (a sandboxed module), so
|
|
93
|
+
// the environment is read defensively.
|
|
94
|
+
const fromEnv =
|
|
95
|
+
typeof process !== "undefined" && process.env
|
|
96
|
+
? process.env.HONCHO_API_KEY
|
|
97
|
+
: undefined;
|
|
98
|
+
const envKey = typeof fromEnv === "string" ? fromEnv.trim() : "";
|
|
99
|
+
if (envKey !== "") {
|
|
100
|
+
return envKey;
|
|
101
|
+
}
|
|
102
|
+
|
|
95
103
|
if (existsSync(HONCHO_KEY_FILE)) {
|
|
96
104
|
try {
|
|
97
105
|
return readFileSync(HONCHO_KEY_FILE, "utf8").trim() || undefined;
|
|
@@ -102,58 +110,6 @@ export function loadHonchoApiKey(): string | undefined {
|
|
|
102
110
|
return undefined;
|
|
103
111
|
}
|
|
104
112
|
|
|
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;
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
const content = readFileSync(MCP_CONFIG_FILE, "utf8");
|
|
115
|
-
return JSON.parse(content);
|
|
116
|
-
} catch {
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
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}`,
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// Atomic write with temp file
|
|
144
|
-
const tempFile = `${MCP_CONFIG_FILE}.tmp`;
|
|
145
|
-
writeFileSync(tempFile, JSON.stringify(config, null, 2), { mode: 0o644 });
|
|
146
|
-
try {
|
|
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");
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
113
|
// ---------------------------------------------------------------------------
|
|
158
114
|
// Honcho Health Check - Detect suspended accounts
|
|
159
115
|
// ---------------------------------------------------------------------------
|
|
@@ -274,7 +230,13 @@ export function stopHonchoHealthCheck(): void {
|
|
|
274
230
|
* Honcho memory configuration
|
|
275
231
|
*/
|
|
276
232
|
export interface HonchoMemoryConfig {
|
|
277
|
-
/**
|
|
233
|
+
/**
|
|
234
|
+
* Honcho API key.
|
|
235
|
+
*
|
|
236
|
+
* Accepted, but the class does not read it: the calls go through the
|
|
237
|
+
* native MCP client of honcho-init, which owns the credential of the
|
|
238
|
+
* session. The field stays for the callers that still pass it.
|
|
239
|
+
*/
|
|
278
240
|
apiKey: string;
|
|
279
241
|
/** Workspace ID for memory storage */
|
|
280
242
|
workspaceId?: string;
|
|
@@ -475,7 +437,7 @@ export class HonchoMemory {
|
|
|
475
437
|
}
|
|
476
438
|
|
|
477
439
|
/**
|
|
478
|
-
* Call a Honcho MCP tool through the
|
|
440
|
+
* Call a Honcho MCP tool through the native MCP client.
|
|
479
441
|
* Uses the honchoToolCall from honcho-init.ts.
|
|
480
442
|
*/
|
|
481
443
|
private async callHonchoTool(
|
|
@@ -15,7 +15,13 @@
|
|
|
15
15
|
* state of a file (missing / empty).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
chmodSync,
|
|
20
|
+
existsSync,
|
|
21
|
+
lstatSync,
|
|
22
|
+
readFileSync,
|
|
23
|
+
readdirSync,
|
|
24
|
+
} from "node:fs";
|
|
19
25
|
import { homedir } from "node:os";
|
|
20
26
|
import { join } from "node:path";
|
|
21
27
|
|
|
@@ -390,6 +396,78 @@ export interface KeyFileAuditResult {
|
|
|
390
396
|
exposedDirectories: ExposedKeyDirectory[];
|
|
391
397
|
}
|
|
392
398
|
|
|
399
|
+
export interface SecureKeyTreeResult {
|
|
400
|
+
/** False on platforms where chmod does not express the required ACL. */
|
|
401
|
+
supported: boolean;
|
|
402
|
+
securedFiles: number;
|
|
403
|
+
securedDirectories: number;
|
|
404
|
+
failedPaths: string[];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Restrict the key tree to its owner on Unix-like systems.
|
|
409
|
+
*
|
|
410
|
+
* This runs only on the dedicated application key directory. It never
|
|
411
|
+
* follows symlinks, and records per-path failures so startup can retain a
|
|
412
|
+
* warning when the current user cannot repair an entry (for example, when
|
|
413
|
+
* it is owned by another account).
|
|
414
|
+
*/
|
|
415
|
+
export function secureKeyTree(
|
|
416
|
+
keysDir: string,
|
|
417
|
+
platform: NodeJS.Platform = process.platform,
|
|
418
|
+
): SecureKeyTreeResult {
|
|
419
|
+
const result: SecureKeyTreeResult = {
|
|
420
|
+
supported: platform !== "win32",
|
|
421
|
+
securedFiles: 0,
|
|
422
|
+
securedDirectories: 0,
|
|
423
|
+
failedPaths: [],
|
|
424
|
+
};
|
|
425
|
+
if (!result.supported) return result;
|
|
426
|
+
|
|
427
|
+
const root = defaultStat(keysDir);
|
|
428
|
+
if (!root?.isDirectory || root.isSymbolicLink) {
|
|
429
|
+
result.failedPaths.push(keysDir);
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const secureDirectory = (path: string): boolean => {
|
|
434
|
+
try {
|
|
435
|
+
chmodSync(path, 0o700);
|
|
436
|
+
result.securedDirectories++;
|
|
437
|
+
return true;
|
|
438
|
+
} catch {
|
|
439
|
+
result.failedPaths.push(path);
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const secureFile = (path: string): void => {
|
|
445
|
+
try {
|
|
446
|
+
chmodSync(path, 0o600);
|
|
447
|
+
result.securedFiles++;
|
|
448
|
+
} catch {
|
|
449
|
+
result.failedPaths.push(path);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const walk = (dir: string, depth: number): void => {
|
|
454
|
+
if (depth > MAX_KEY_TREE_DEPTH) return;
|
|
455
|
+
for (const name of defaultReaddir(dir).sort(compareByName)) {
|
|
456
|
+
const path = join(dir, name);
|
|
457
|
+
const info = defaultStat(path);
|
|
458
|
+
if (!info || info.isSymbolicLink) continue;
|
|
459
|
+
if (info.isDirectory) {
|
|
460
|
+
if (secureDirectory(path)) walk(path, depth + 1);
|
|
461
|
+
} else if (info.isFile) {
|
|
462
|
+
secureFile(path);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
if (secureDirectory(keysDir)) walk(keysDir, 1);
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
|
|
393
471
|
/**
|
|
394
472
|
* Compare two file names in a stable, locale independent order.
|
|
395
473
|
*/
|
|
@@ -101,7 +101,7 @@ async function handleTestJev(ctx: ExtensionContext, flags: Record<string, string
|
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
103
|
* Handle /harness-services test honcho --read-only
|
|
104
|
-
* Checks Honcho integration status
|
|
104
|
+
* Checks the Honcho integration status.
|
|
105
105
|
* Does NOT create sessions/peers, write messages, or claim success when only flags were inspected.
|
|
106
106
|
*/
|
|
107
107
|
async function handleTestHoncho(ctx: ExtensionContext, flags: Record<string, string | boolean>): Promise<void> {
|
|
@@ -113,33 +113,65 @@ async function handleTestHoncho(ctx: ExtensionContext, flags: Record<string, str
|
|
|
113
113
|
ctx.ui.notify("[services] Testing Honcho MCP connection...", "info");
|
|
114
114
|
|
|
115
115
|
try {
|
|
116
|
-
// Report actual
|
|
116
|
+
// Report actual status from the status tracker
|
|
117
117
|
ctx.ui.notify(`[services] Honcho configured: ${honchoStatus.configured}`, "info");
|
|
118
118
|
ctx.ui.notify(`[services] Honcho connected: ${honchoStatus.connected}`, "info");
|
|
119
119
|
ctx.ui.notify(`[services] Honcho feature enabled: ${honchoStatus.featureEnabled}`, "info");
|
|
120
120
|
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
121
|
+
// The cause of the last attempt, when the integration knows one.
|
|
122
|
+
// The reason is credential-free: it names the status and the cause.
|
|
123
|
+
let reason: string | undefined;
|
|
124
|
+
// The memory client and the native tools are two things: a connected
|
|
125
|
+
// client does not mean that the model sees the Honcho tools.
|
|
126
|
+
let nativeTools: boolean | undefined;
|
|
127
|
+
try {
|
|
128
|
+
const { getHonchoInitResult } = await import("./honcho-init.js");
|
|
129
|
+
const init = getHonchoInitResult();
|
|
130
|
+
if (init && init.status !== "ready") {
|
|
131
|
+
reason = `${init.status}: ${init.reason ?? "no reason reported"}`;
|
|
132
|
+
}
|
|
133
|
+
if (init && init.status === "ready") {
|
|
134
|
+
nativeTools = init.mcpRegistered === true;
|
|
135
|
+
if (!nativeTools && init.mcpRegistrationReason) {
|
|
136
|
+
reason = `native tools not registered: ${init.mcpRegistrationReason}`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// The module is optional for this command: the tracker still reports.
|
|
131
141
|
}
|
|
132
142
|
|
|
133
|
-
// For read-only test, report current status from our tracker
|
|
134
|
-
// A full MCP tool discovery would require a non-read-only request
|
|
135
143
|
if (honchoStatus.connected) {
|
|
136
|
-
ctx.ui.notify("[services] Honcho
|
|
137
|
-
|
|
144
|
+
ctx.ui.notify("[services] Honcho memory client is connected", "info");
|
|
145
|
+
if (nativeTools === true) {
|
|
146
|
+
ctx.ui.notify("[services] Honcho native MCP tools are registered (the model sees them)", "info");
|
|
147
|
+
ctx.ui.notify("[services] Honcho test completed successfully", "info");
|
|
148
|
+
} else if (nativeTools === false) {
|
|
149
|
+
// The memory works, but the model has no Honcho tools: the check
|
|
150
|
+
// must not report a success that hides that.
|
|
151
|
+
ctx.ui.notify("[services] Honcho native MCP tools are NOT registered (the model sees no Honcho tools)", "warning");
|
|
152
|
+
if (reason) {
|
|
153
|
+
ctx.ui.notify(`[services] ${reason}`, "warning");
|
|
154
|
+
}
|
|
155
|
+
ctx.ui.notify("[services] The memory connection succeeded, but the native tool registration failed", "warning");
|
|
156
|
+
} else {
|
|
157
|
+
// The state of the native tools is unknown: only the memory
|
|
158
|
+
// connection is reported.
|
|
159
|
+
ctx.ui.notify("[services] Honcho memory test completed successfully", "info");
|
|
160
|
+
}
|
|
138
161
|
} else if (honchoStatus.configured) {
|
|
139
162
|
ctx.ui.notify("[services] Honcho MCP server not yet connected", "warning");
|
|
163
|
+
if (reason) {
|
|
164
|
+
ctx.ui.notify(`[services] Honcho is unavailable: ${reason}`, "warning");
|
|
165
|
+
}
|
|
166
|
+
if (honchoStatus.lastErrorCode) {
|
|
167
|
+
ctx.ui.notify(`[services] Last error: ${honchoStatus.lastErrorCode}`, "warning");
|
|
168
|
+
}
|
|
140
169
|
ctx.ui.notify("[services] Connection will be established on first use", "info");
|
|
141
170
|
} else {
|
|
142
171
|
ctx.ui.notify("[services] Honcho MCP not configured", "warning");
|
|
172
|
+
if (reason) {
|
|
173
|
+
ctx.ui.notify(`[services] Honcho is unavailable: ${reason}`, "warning");
|
|
174
|
+
}
|
|
143
175
|
ctx.ui.notify("[services] Add API key to enable Honcho memory", "info");
|
|
144
176
|
}
|
|
145
177
|
} catch (err) {
|