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.
Files changed (50) hide show
  1. package/AGENTS.md +6 -4
  2. package/CHANGELOG.md +68 -0
  3. package/README.md +10 -2
  4. package/harness/honcho-init.ts +907 -192
  5. package/harness/honcho-memory.ts +31 -69
  6. package/harness/key-file-audit.ts +79 -1
  7. package/harness/service-commands.ts +48 -16
  8. package/index.ts +244 -48
  9. package/package.json +3 -8
  10. package/packages/agent-policy/package.json +1 -1
  11. package/packages/auth/package.json +1 -1
  12. package/packages/autonomous-runtime/package.json +1 -1
  13. package/packages/checkpoint/package.json +1 -1
  14. package/packages/clipboard/package.json +1 -1
  15. package/packages/codex-watcher/package.json +1 -1
  16. package/packages/config-capture/package.json +1 -1
  17. package/packages/contracts/package.json +1 -1
  18. package/packages/cookie-sanitizer/package.json +1 -1
  19. package/packages/evaluation-engine/package.json +1 -1
  20. package/packages/event-bus/package.json +1 -1
  21. package/packages/event-store/package.json +1 -1
  22. package/packages/experience-replay/package.json +1 -1
  23. package/packages/file-copy-helper/package.json +1 -1
  24. package/packages/intent-analyzer/package.json +1 -1
  25. package/packages/jev-judge/package.json +1 -1
  26. package/packages/knowledge-retrieval/package.json +1 -1
  27. package/packages/learning-engine/package.json +1 -1
  28. package/packages/logging/package.json +1 -1
  29. package/packages/memory-engine/package.json +1 -1
  30. package/packages/notification/package.json +1 -1
  31. package/packages/paperclip-integration/package.json +1 -1
  32. package/packages/performance-optimizer/package.json +1 -1
  33. package/packages/privilege-broker/package.json +1 -1
  34. package/packages/prompt-compiler/package.json +1 -1
  35. package/packages/providers/package.json +1 -1
  36. package/packages/qdrant-skills/package.json +1 -1
  37. package/packages/quota-manager/package.json +1 -1
  38. package/packages/relay/package.json +1 -1
  39. package/packages/skill-mcp-client/package.json +1 -1
  40. package/packages/skills/package.json +1 -1
  41. package/packages/todo-bd-sync/package.json +1 -1
  42. package/packages/token-estimation/package.json +1 -1
  43. package/packages/tooling-preference/package.json +1 -1
  44. package/packages/trajectory/package.json +1 -1
  45. package/packages/tui/package.json +1 -1
  46. package/packages/two-phase-review/package.json +1 -1
  47. package/packages/types/package.json +1 -1
  48. package/packages/webmcp-tester/package.json +1 -1
  49. package/packages/wiki/package.json +1 -1
  50. package/packages/write-review/package.json +1 -1
@@ -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, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "node:fs";
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 MCP Configuration (non-destructive)
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
- * Ensure MCP config directory exists (non-destructive).
82
- * Does NOT write config at import time.
83
- */
84
- export function ensureMcpConfigDirectory(): void {
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
- /** Honcho API key */
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 adapter.
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 { existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
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 through the MCP adapter.
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 registration status from the status tracker
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
- // Check if MCP adapter is available via globalThis
122
- // SAFETY: We're accessing pi.events which may not exist in all contexts
123
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
- const globalAny = globalThis as any;
125
- const piEvents = globalAny.pi?.events;
126
-
127
- if (!piEvents) {
128
- ctx.ui.notify("[services] Honcho test skipped: MCP adapter not available (pi.events)", "warning");
129
- ctx.ui.notify("[services] Note: Install pi-mcp-adapter to enable Honcho MCP", "info");
130
- return;
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 MCP server is connected", "info");
137
- ctx.ui.notify("[services] Honcho test completed successfully", "info");
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) {