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.
Files changed (120) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/harness/glm-quota-countdown.ts +9 -1
  3. package/harness/honcho-init.ts +745 -0
  4. package/harness/honcho-memory.ts +290 -97
  5. package/harness/service-commands.ts +28 -3
  6. package/index.ts +191 -63
  7. package/package.json +1 -1
  8. package/packages/a2a-adapter/package.json +1 -1
  9. package/packages/agent-policy/dist/extension.d.ts.map +1 -1
  10. package/packages/agent-policy/dist/extension.js +16 -9
  11. package/packages/agent-policy/dist/extension.js.map +1 -1
  12. package/packages/agent-policy/dist/policy-loader.d.ts.map +1 -1
  13. package/packages/agent-policy/dist/policy-loader.js +5 -16
  14. package/packages/agent-policy/dist/policy-loader.js.map +1 -1
  15. package/packages/agent-policy/package.json +1 -1
  16. package/packages/agent-policy/src/extension.ts +16 -9
  17. package/packages/agent-policy/src/policy-loader.ts +5 -22
  18. package/packages/agent-policy/test/interlock.test.ts +101 -0
  19. package/packages/architecture-generator/package.json +1 -1
  20. package/packages/auth/package.json +1 -1
  21. package/packages/autonomous-refactor/package.json +1 -1
  22. package/packages/autonomous-runtime/package.json +1 -1
  23. package/packages/cache-strategy/package.json +1 -1
  24. package/packages/capability-registry/package.json +1 -1
  25. package/packages/checkpoint/package.json +1 -1
  26. package/packages/cli-plugin-sdk/package.json +1 -1
  27. package/packages/clipboard/package.json +1 -1
  28. package/packages/clipboard-plugin/package.json +1 -1
  29. package/packages/code-generation/package.json +1 -1
  30. package/packages/code-review/package.json +1 -1
  31. package/packages/codex-adapter/package.json +1 -1
  32. package/packages/codex-watcher/package.json +1 -1
  33. package/packages/config-capture/package.json +1 -1
  34. package/packages/context-compiler/package.json +1 -1
  35. package/packages/context-discovery/package.json +1 -1
  36. package/packages/context-manager/package.json +1 -1
  37. package/packages/cookie-sanitizer/package.json +1 -1
  38. package/packages/cost-optimizer/package.json +1 -1
  39. package/packages/dependency-analyzer/package.json +1 -1
  40. package/packages/django-plugin/package.json +1 -1
  41. package/packages/doc-generator/package.json +1 -1
  42. package/packages/e2e-tester/package.json +1 -1
  43. package/packages/evaluation-engine/package.json +1 -1
  44. package/packages/evaluation-runner/package.json +1 -1
  45. package/packages/event-bus/package.json +1 -1
  46. package/packages/event-store/package.json +1 -1
  47. package/packages/experience-replay/package.json +1 -1
  48. package/packages/feedback-collector/package.json +1 -1
  49. package/packages/file-copy-helper/package.json +1 -1
  50. package/packages/framework-detector/package.json +1 -1
  51. package/packages/framework-plugin-sdk/package.json +1 -1
  52. package/packages/frappe-plugin/package.json +1 -1
  53. package/packages/generic-web-plugin/package.json +1 -1
  54. package/packages/health-monitor/package.json +1 -1
  55. package/packages/intent-analyzer/package.json +1 -1
  56. package/packages/jev-judge/package.json +1 -1
  57. package/packages/knowledge-graph/package.json +1 -1
  58. package/packages/knowledge-retrieval/package.json +1 -1
  59. package/packages/laravel-plugin/package.json +1 -1
  60. package/packages/learning-engine/package.json +1 -1
  61. package/packages/mcp-adapter/package.json +1 -1
  62. package/packages/memory-engine/package.json +1 -1
  63. package/packages/milestone-manager/package.json +1 -1
  64. package/packages/model-registry/package.json +1 -1
  65. package/packages/nextjs-plugin/package.json +1 -1
  66. package/packages/notification/dist/adapters/telegram-adapter.d.ts +1 -1
  67. package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
  68. package/packages/notification/dist/adapters/telegram-adapter.js +34 -21
  69. package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
  70. package/packages/notification/package.json +1 -1
  71. package/packages/notification/src/adapters/telegram-adapter.ts +37 -21
  72. package/packages/observability/package.json +1 -1
  73. package/packages/okf-indexer/package.json +1 -1
  74. package/packages/paperclip-integration/package.json +1 -1
  75. package/packages/performance-optimizer/package.json +1 -1
  76. package/packages/privilege-broker/package.json +1 -1
  77. package/packages/project-analyzer/package.json +1 -1
  78. package/packages/project-bootstrap/package.json +1 -1
  79. package/packages/projection-engine/package.json +1 -1
  80. package/packages/prompt-compiler/package.json +1 -1
  81. package/packages/prompt-versioning/package.json +1 -1
  82. package/packages/provider-adapter-sdk/package.json +1 -1
  83. package/packages/provider-router/package.json +1 -1
  84. package/packages/provider-selector/package.json +1 -1
  85. package/packages/providers/package.json +1 -1
  86. package/packages/qdrant-skills/package.json +1 -1
  87. package/packages/quota-manager/package.json +1 -1
  88. package/packages/rate-limiter/package.json +1 -1
  89. package/packages/react-vite-plugin/package.json +1 -1
  90. package/packages/release-manager/package.json +1 -1
  91. package/packages/requirement-compiler/package.json +1 -1
  92. package/packages/runtime/package.json +1 -1
  93. package/packages/scheduler/package.json +1 -1
  94. package/packages/scheduler-adapter/package.json +1 -1
  95. package/packages/session/package.json +1 -1
  96. package/packages/session-api/package.json +1 -1
  97. package/packages/session-export/package.json +1 -1
  98. package/packages/shared-context/package.json +1 -1
  99. package/packages/skill-mcp-client/package.json +1 -1
  100. package/packages/skill-registry/package.json +1 -1
  101. package/packages/skills/package.json +1 -1
  102. package/packages/sprint-planner/package.json +1 -1
  103. package/packages/subscription-engine/package.json +1 -1
  104. package/packages/task-compiler/package.json +1 -1
  105. package/packages/telegram/package.json +1 -1
  106. package/packages/tencentdb-memory/package.json +1 -1
  107. package/packages/tencentdb-sync/package.json +1 -1
  108. package/packages/test-data-generator/package.json +1 -1
  109. package/packages/test-generator/package.json +1 -1
  110. package/packages/todo-bd-sync/package.json +1 -1
  111. package/packages/token-estimation/package.json +1 -1
  112. package/packages/token-optimizer/package.json +1 -1
  113. package/packages/trajectory/package.json +1 -1
  114. package/packages/tui/package.json +1 -1
  115. package/packages/types/package.json +1 -1
  116. package/packages/webmcp-tester/package.json +1 -1
  117. package/packages/workflow-events/package.json +1 -1
  118. package/packages/workspace-scanner/package.json +1 -1
  119. package/packages/worktree/package.json +1 -1
  120. package/packages/write-review/package.json +1 -1
@@ -0,0 +1,745 @@
1
+ /**
2
+ * Honcho MCP Integration - Initialization and Registration
3
+ *
4
+ * Implements the full 6-step Honcho MCP integration:
5
+ * 1. Register MCP server through pi-mcp-adapter
6
+ * 2. Discover tools and establish workspace/peer mapping
7
+ * 3. Create/retrieve session for Pi session
8
+ * 4. Ingest messages on agent turn complete
9
+ * 5. Retrieve context before agent execution
10
+ * 6. Truthful status and proper shutdown
11
+ */
12
+
13
+ import { logServiceEvent, honchoStatus } from "./service-diagnostics.js";
14
+ import {
15
+ HonchoMemory,
16
+ loadHonchoApiKey,
17
+ ensureMcpConfigDirectory,
18
+ writeHonchoMcpConfig,
19
+ } from "./honcho-memory.js";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Types
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /**
26
+ * MCP Runtime Registration Request (per pi-mcp-adapter contract)
27
+ */
28
+ interface McpRuntimeRegistrationRequest {
29
+ version: 1;
30
+ name: string;
31
+ definition: {
32
+ url: string;
33
+ headers?: Record<string, string>;
34
+ };
35
+ result?:
36
+ | { ok: true; registration: { dispose(): Promise<void> } }
37
+ | { ok: false; error: Error };
38
+ }
39
+
40
+ /**
41
+ * MCP Tool Call Request (per pi-mcp-adapter contract)
42
+ */
43
+ interface McpToolCallRequest {
44
+ version: 1;
45
+ server: string;
46
+ tool: string;
47
+ args: Record<string, unknown>;
48
+ result?: McpToolCallResult;
49
+ }
50
+
51
+ type McpToolCallResult =
52
+ | { ok: true; result?: unknown }
53
+ | { ok: false; error?: Error };
54
+
55
+ /**
56
+ * Honcho session identity
57
+ */
58
+ interface HonchoIdentity {
59
+ workspaceId: string;
60
+ userPeerId: string;
61
+ assistantPeerId: string;
62
+ sessionId: string;
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Constants
67
+ // ---------------------------------------------------------------------------
68
+
69
+ const MCP_RUNTIME_REGISTER_EVENT = "pi-mcp-adapter:runtime-register:v1";
70
+ const MCP_RUNTIME_TOOL_CALL_EVENT = "pi-mcp-adapter:runtime-tool-call:v1";
71
+
72
+ /** Honcho MCP server name in the adapter */
73
+ const HONCHO_SERVER_NAME = "honcho";
74
+
75
+ /** Default workspace name for Pi harness */
76
+ const DEFAULT_WORKSPACE_NAME = "pi-harness";
77
+
78
+ /** Default assistant name */
79
+ const DEFAULT_ASSISTANT_NAME = "pi-agent";
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // State
83
+ // ---------------------------------------------------------------------------
84
+
85
+ /** Global Honcho memory instance */
86
+ let _honchoMemory: HonchoMemory | null = null;
87
+
88
+ /** Owned registration handle for disposal */
89
+ let _honchoRegistration: { dispose(): Promise<void> } | null = null;
90
+
91
+ /** Whether initHoncho has been called (attempted) */
92
+ let _initialized = false;
93
+
94
+ /** Whether initHoncho succeeded (can retry if false) */
95
+ let _initSucceeded = false;
96
+
97
+ /** Stored pi.events for use in tool calls */
98
+ let _piEvents: PiEvents | null = null;
99
+
100
+ /** Current Honcho identity (workspace, peers, session) */
101
+ let _identity: HonchoIdentity | null = null;
102
+
103
+ /** Disposed flag to prevent post-shutdown operations */
104
+ let _disposed = false;
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // MCP Tool Calls
108
+ // ---------------------------------------------------------------------------
109
+
110
+ /**
111
+ * Call a tool on the Honcho MCP server through the adapter.
112
+ * Returns actual result from the MCP server.
113
+ */
114
+ async function callHonchoTool(
115
+ toolName: string,
116
+ args: Record<string, unknown>
117
+ ): Promise<{ success: boolean; result?: unknown; error?: string }> {
118
+ if (_disposed) {
119
+ return { success: false, error: "Honcho integration disposed" };
120
+ }
121
+
122
+ // Use stored pi.events from initHoncho
123
+ if (!_piEvents) {
124
+ return { success: false, error: "pi.events not available" };
125
+ }
126
+ const piEvents = _piEvents;
127
+
128
+ // Create tool call request
129
+ const request: McpToolCallRequest = {
130
+ version: 1,
131
+ server: HONCHO_SERVER_NAME,
132
+ tool: toolName,
133
+ args,
134
+ };
135
+
136
+ // Emit tool call
137
+ piEvents.emit(MCP_RUNTIME_TOOL_CALL_EVENT, request);
138
+
139
+ // Wait for result - adapter assigns a Promise to request.result
140
+ if (request.result === undefined) {
141
+ return { success: false, error: "No response from MCP adapter" };
142
+ }
143
+
144
+ // request.result is a Promise from the adapter
145
+ const result = await request.result;
146
+
147
+ if (!result.ok) {
148
+ return { success: false, error: result.error?.message ?? "Unknown error" };
149
+ }
150
+
151
+ return { success: true, result: result.result };
152
+ }
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // Honcho API Operations
156
+ // ---------------------------------------------------------------------------
157
+
158
+ /**
159
+ * Create or get workspace for the harness
160
+ */
161
+ async function ensureWorkspace(workspaceName: string): Promise<string | null> {
162
+ const result = await callHonchoTool("create_workspace", {
163
+ name: workspaceName,
164
+ });
165
+
166
+ if (result.success && result.result) {
167
+ const response = result.result as { id?: string; workspace?: { id?: string } };
168
+ return response.id ?? response.workspace?.id ?? null;
169
+ }
170
+
171
+ // Try get_workspace as fallback
172
+ const getResult = await callHonchoTool("get_workspace", {
173
+ name: workspaceName,
174
+ });
175
+
176
+ if (getResult.success && getResult.result) {
177
+ const response = getResult.result as { id?: string };
178
+ return response.id ?? null;
179
+ }
180
+
181
+ logServiceEvent({
182
+ ts: new Date().toISOString(),
183
+ level: "error",
184
+ service: "honcho",
185
+ operation: "workspace_failed",
186
+ phase: "failed",
187
+ error: result.error ?? "Unknown error",
188
+ });
189
+
190
+ return null;
191
+ }
192
+
193
+ /**
194
+ * Create or get user peer
195
+ */
196
+ async function ensureUserPeer(
197
+ workspaceId: string,
198
+ userName: string
199
+ ): Promise<string | null> {
200
+ const result = await callHonchoTool("create_user", {
201
+ workspace_id: workspaceId,
202
+ name: userName,
203
+ });
204
+
205
+ if (result.success && result.result) {
206
+ const response = result.result as { id?: string; user?: { id?: string } };
207
+ return response.id ?? response.user?.id ?? null;
208
+ }
209
+
210
+ // Try get_or_create_user as fallback
211
+ const getResult = await callHonchoTool("get_or_create_user", {
212
+ workspace_id: workspaceId,
213
+ name: userName,
214
+ });
215
+
216
+ if (getResult.success && getResult.result) {
217
+ const response = getResult.result as { id?: string };
218
+ return response.id ?? null;
219
+ }
220
+
221
+ logServiceEvent({
222
+ ts: new Date().toISOString(),
223
+ level: "error",
224
+ service: "honcho",
225
+ operation: "user_peer_failed",
226
+ phase: "failed",
227
+ error: result.error ?? "Unknown error",
228
+ });
229
+
230
+ return null;
231
+ }
232
+
233
+ /**
234
+ * Create or get assistant peer
235
+ */
236
+ async function ensureAssistantPeer(
237
+ workspaceId: string,
238
+ assistantName: string
239
+ ): Promise<string | null> {
240
+ const result = await callHonchoTool("create_assistant", {
241
+ workspace_id: workspaceId,
242
+ name: assistantName,
243
+ });
244
+
245
+ if (result.success && result.result) {
246
+ const response = result.result as { id?: string; assistant?: { id?: string } };
247
+ return response.id ?? response.assistant?.id ?? null;
248
+ }
249
+
250
+ logServiceEvent({
251
+ ts: new Date().toISOString(),
252
+ level: "error",
253
+ service: "honcho",
254
+ operation: "assistant_peer_failed",
255
+ phase: "failed",
256
+ error: result.error ?? "Unknown error",
257
+ });
258
+
259
+ return null;
260
+ }
261
+
262
+ /**
263
+ * Create or get session for the current Pi session
264
+ */
265
+ async function ensureSession(
266
+ workspaceId: string,
267
+ userPeerId: string,
268
+ assistantPeerId: string,
269
+ sessionId: string
270
+ ): Promise<string | null> {
271
+ const result = await callHonchoTool("create_session", {
272
+ workspace_id: workspaceId,
273
+ user_id: userPeerId,
274
+ assistant_id: assistantPeerId,
275
+ id: sessionId,
276
+ });
277
+
278
+ if (result.success && result.result) {
279
+ const response = result.result as { id?: string; session?: { id?: string } };
280
+ return response.id ?? response.session?.id ?? null;
281
+ }
282
+
283
+ // Try get_or_create_session as fallback
284
+ const getResult = await callHonchoTool("get_or_create_session", {
285
+ workspace_id: workspaceId,
286
+ user_id: userPeerId,
287
+ assistant_id: assistantPeerId,
288
+ id: sessionId,
289
+ });
290
+
291
+ if (getResult.success && getResult.result) {
292
+ const response = getResult.result as { id?: string };
293
+ return response.id ?? null;
294
+ }
295
+
296
+ logServiceEvent({
297
+ ts: new Date().toISOString(),
298
+ level: "error",
299
+ service: "honcho",
300
+ operation: "session_failed",
301
+ phase: "failed",
302
+ error: result.error ?? "Unknown error",
303
+ });
304
+
305
+ return null;
306
+ }
307
+
308
+ /**
309
+ * Add a message to the session
310
+ */
311
+ async function addMessage(
312
+ sessionId: string,
313
+ peerId: string,
314
+ role: "user" | "assistant",
315
+ content: string,
316
+ messageId?: string
317
+ ): Promise<boolean> {
318
+ const result = await callHonchoTool("add_message", {
319
+ session_id: sessionId,
320
+ sender_id: peerId,
321
+ role,
322
+ content,
323
+ message_id: messageId,
324
+ });
325
+
326
+ return result.success;
327
+ }
328
+
329
+ /**
330
+ * Get context for a query
331
+ */
332
+ async function getContext(
333
+ workspaceId: string,
334
+ sessionId: string,
335
+ query: string,
336
+ limit: number = 5
337
+ ): Promise<string | null> {
338
+ const result = await callHonchoTool("get_context", {
339
+ workspace_id: workspaceId,
340
+ session_id: sessionId,
341
+ query,
342
+ limit,
343
+ });
344
+
345
+ if (result.success && result.result) {
346
+ const response = result.result as { content?: string; context?: string; text?: string };
347
+ return response.content ?? response.context ?? response.text ?? null;
348
+ }
349
+
350
+ return null;
351
+ }
352
+
353
+ // ---------------------------------------------------------------------------
354
+ // Initialization
355
+ // ---------------------------------------------------------------------------
356
+
357
+ /**
358
+ * Initialize Honcho MCP integration.
359
+ *
360
+ * Must be called during session_start or later when the adapter listener is installed.
361
+ * Reads request.result synchronously after emit per the adapter contract.
362
+ *
363
+ * @param options Initialization options
364
+ * @returns Promise resolving to the memory instance (or null if registration failed)
365
+ */
366
+ /**
367
+ * Pi events interface for lifecycle hooks
368
+ */
369
+ interface PiEvents {
370
+ on: (event: string, handler: (...args: unknown[]) => void | Promise<void>) => void;
371
+ emit: (event: string, ...args: unknown[]) => void;
372
+ }
373
+
374
+ export async function initHoncho(options?: {
375
+ workspaceName?: string;
376
+ assistantName?: string;
377
+ userName?: string;
378
+ sessionId?: string;
379
+ apiKey?: string;
380
+ piEvents?: PiEvents;
381
+ }): Promise<HonchoMemory | null> {
382
+ if (_disposed) {
383
+ console.error("[honcho] Integration has been disposed, cannot reinitialize");
384
+ return null;
385
+ }
386
+
387
+ // Allow retry if previous init failed
388
+ if (_initialized && _initSucceeded) {
389
+ console.debug("[honcho] Already initialized, returning existing instance");
390
+ return _honchoMemory;
391
+ }
392
+
393
+ // Reset state if retrying
394
+ if (_initialized && !_initSucceeded) {
395
+ console.info("[honcho] Retrying initialization...");
396
+ }
397
+ _initialized = true;
398
+ _initSucceeded = false;
399
+
400
+ console.info("[honcho] Initializing Honcho MCP integration...");
401
+
402
+ // Load API key
403
+ const key = options?.apiKey ?? loadHonchoApiKey();
404
+ if (!key) {
405
+ console.error("[honcho] No API key found. Run: echo \"YOUR_KEY\" > ~/.pi-harness-runtime/keys/honcho-api-key.txt");
406
+ honchoStatus.setConfigured(false);
407
+ honchoStatus.recordFailure("init", "no_key", { error: "No API key" });
408
+ return null;
409
+ }
410
+
411
+ honchoStatus.setConfigured(true);
412
+
413
+ // Store pi.events for use in tool calls
414
+ if (options?.piEvents) {
415
+ _piEvents = options.piEvents;
416
+ }
417
+
418
+ // Ensure MCP config directory exists (non-destructive)
419
+ ensureMcpConfigDirectory();
420
+
421
+ // Write MCP config for the adapter to pick up
422
+ try {
423
+ writeHonchoMcpConfig(key);
424
+ console.info("[honcho] MCP config written");
425
+ } catch (err) {
426
+ console.warn("[honcho] Failed to write MCP config:", err);
427
+ // Continue anyway - adapter may use its own config
428
+ }
429
+
430
+ // Get pi.events from options (passed from startup) or fall back to global
431
+ const piEvents = options?.piEvents ?? (globalThis as { pi?: { events?: PiEvents } }).pi?.events;
432
+
433
+ if (!piEvents) {
434
+ console.error("[honcho] pi.events not available - MCP adapter may not be installed");
435
+ honchoStatus.recordFailure("init", "adapter_missing", { error: "pi.events not available" });
436
+ return null;
437
+ }
438
+
439
+ // Create registration request with bearer token authentication
440
+ const request: McpRuntimeRegistrationRequest = {
441
+ version: 1,
442
+ name: HONCHO_SERVER_NAME,
443
+ definition: {
444
+ url: "https://mcp.honcho.dev",
445
+ headers: {
446
+ Authorization: `Bearer ${key}`,
447
+ },
448
+ },
449
+ };
450
+
451
+ // Emit registration event - adapter writes request.result synchronously
452
+ piEvents.emit(MCP_RUNTIME_REGISTER_EVENT, request);
453
+
454
+ // Read result immediately after emit
455
+ if (request.result === undefined) {
456
+ console.error("[honcho] MCP adapter not responding to registration");
457
+ console.error("[honcho] Install: pi install npm:pi-mcp-adapter");
458
+ honchoStatus.recordFailure("init", "no_listener", { error: "No adapter responded" });
459
+ return null;
460
+ }
461
+
462
+ if (!request.result.ok) {
463
+ const errorMsg = request.result.error?.message ?? "Unknown registration error";
464
+
465
+ // Check for duplicate name - an existing server may already be registered
466
+ if (errorMsg.includes("already") || errorMsg.includes("duplicate")) {
467
+ console.warn(`[honcho] Server already registered (duplicate name), continuing...`);
468
+ // Continue with memory initialization - the existing server should work
469
+ } else {
470
+ console.error(`[honcho] Registration failed: ${errorMsg}`);
471
+ honchoStatus.recordFailure("init", "registration_rejected", { error: errorMsg });
472
+ return null;
473
+ }
474
+ } else {
475
+ // Registration accepted - store handle for disposal
476
+ _honchoRegistration = request.result.registration;
477
+ console.info("[honcho] MCP server registered");
478
+ }
479
+
480
+ // Create memory instance
481
+ _honchoMemory = new HonchoMemory({
482
+ apiKey: key,
483
+ });
484
+
485
+ // Discover tools and establish identity
486
+ const workspaceName = options?.workspaceName ?? DEFAULT_WORKSPACE_NAME;
487
+ const assistantName = options?.assistantName ?? DEFAULT_ASSISTANT_NAME;
488
+ const userName = options?.userName ?? "pi-user";
489
+ const sessionId = options?.sessionId ?? `pi-session-${Date.now()}`;
490
+
491
+ console.info("[honcho] Establishing workspace/peer identity...");
492
+
493
+ // Step 1: Create/get workspace
494
+ const workspaceId = await ensureWorkspace(workspaceName);
495
+ if (!workspaceId) {
496
+ console.error("[honcho] Failed to create workspace");
497
+ honchoStatus.recordFailure("init", "workspace_failed", {});
498
+ return _honchoMemory; // Return instance but mark as not ready
499
+ }
500
+ console.info(`[honcho] Workspace: ${workspaceId}`);
501
+
502
+ // Step 2: Create/get user peer
503
+ const userPeerId = await ensureUserPeer(workspaceId, userName);
504
+ if (!userPeerId) {
505
+ console.error("[honcho] Failed to create user peer");
506
+ honchoStatus.recordFailure("init", "user_peer_failed", {});
507
+ return _honchoMemory;
508
+ }
509
+ console.info(`[honcho] User peer: ${userPeerId}`);
510
+
511
+ // Step 3: Create/get assistant peer
512
+ const assistantPeerId = await ensureAssistantPeer(workspaceId, assistantName);
513
+ if (!assistantPeerId) {
514
+ console.error("[honcho] Failed to create assistant peer");
515
+ honchoStatus.recordFailure("init", "assistant_peer_failed", {});
516
+ return _honchoMemory;
517
+ }
518
+ console.info(`[honcho] Assistant peer: ${assistantPeerId}`);
519
+
520
+ // Step 4: Create/get session
521
+ const honchoSessionId = await ensureSession(workspaceId, userPeerId, assistantPeerId, sessionId);
522
+ if (!honchoSessionId) {
523
+ console.error("[honcho] Failed to create session");
524
+ honchoStatus.recordFailure("init", "session_failed", {});
525
+ return _honchoMemory;
526
+ }
527
+ console.info(`[honcho] Session: ${honchoSessionId}`);
528
+
529
+ // Store identity
530
+ _identity = {
531
+ workspaceId,
532
+ userPeerId,
533
+ assistantPeerId,
534
+ sessionId: honchoSessionId,
535
+ };
536
+
537
+ // Update memory state with all identities
538
+ _honchoMemory.setConnected(true);
539
+ _honchoMemory.setToolsDiscovered(true);
540
+ _honchoMemory.setWorkspace(workspaceId);
541
+ _honchoMemory.setSession(honchoSessionId);
542
+
543
+ console.info("[honcho] Memory ready");
544
+ honchoStatus.recordSuccess("init", undefined, {
545
+ workspaceId,
546
+ userPeerId,
547
+ assistantPeerId,
548
+ sessionId: honchoSessionId,
549
+ });
550
+
551
+ // Mark initialization as successful
552
+ _initSucceeded = true;
553
+
554
+ return _honchoMemory;
555
+ }
556
+
557
+ /**
558
+ * Get the current Honcho memory instance
559
+ */
560
+ export function getHonchoMemory(): HonchoMemory | null {
561
+ return _honchoMemory;
562
+ }
563
+
564
+ /**
565
+ * Get the current Honcho identity
566
+ */
567
+ export function getHonchoIdentity(): HonchoIdentity | null {
568
+ return _identity;
569
+ }
570
+
571
+ /**
572
+ * Check if Honcho is initialized and ready
573
+ */
574
+ export function isHonchoInitialized(): boolean {
575
+ return _initialized && _honchoMemory?.isReady() === true;
576
+ }
577
+
578
+ /**
579
+ * Ingest a user message
580
+ */
581
+ export async function ingestUserMessage(
582
+ content: string,
583
+ messageId?: string
584
+ ): Promise<{ success: boolean; deduped: boolean; error?: string }> {
585
+ if (!_identity || !_honchoMemory) {
586
+ return { success: false, deduped: false, error: "Honcho not initialized" };
587
+ }
588
+
589
+ return await addMessage(
590
+ _identity.sessionId,
591
+ _identity.userPeerId,
592
+ "user",
593
+ content,
594
+ messageId
595
+ ) ? { success: true, deduped: false } : { success: false, deduped: false, error: "Failed to add message" };
596
+ }
597
+
598
+ /**
599
+ * Ingest an assistant message
600
+ */
601
+ export async function ingestAssistantMessage(
602
+ content: string,
603
+ messageId?: string
604
+ ): Promise<{ success: boolean; deduped: boolean; error?: string }> {
605
+ if (!_identity || !_honchoMemory) {
606
+ return { success: false, deduped: false, error: "Honcho not initialized" };
607
+ }
608
+
609
+ return await addMessage(
610
+ _identity.sessionId,
611
+ _identity.assistantPeerId,
612
+ "assistant",
613
+ content,
614
+ messageId
615
+ ) ? { success: true, deduped: false } : { success: false, deduped: false, error: "Failed to add message" };
616
+ }
617
+
618
+ /**
619
+ * Retrieve context for a query
620
+ */
621
+ export async function retrieveHonchoContext(
622
+ query: string,
623
+ limit: number = 5
624
+ ): Promise<{ success: boolean; context: string; error?: string }> {
625
+ if (!_identity) {
626
+ return { success: false, context: "", error: "Honcho not initialized" };
627
+ }
628
+
629
+ const context = await getContext(
630
+ _identity.workspaceId,
631
+ _identity.sessionId,
632
+ query,
633
+ limit
634
+ );
635
+
636
+ if (context !== null) {
637
+ return { success: true, context };
638
+ }
639
+
640
+ return { success: false, context: "", error: "Failed to retrieve context" };
641
+ }
642
+
643
+ /**
644
+ * Shutdown Honcho integration.
645
+ * Disposes owned registration, stops timers, clears state.
646
+ */
647
+ export async function shutdownHoncho(): Promise<void> {
648
+ if (_disposed) {
649
+ return;
650
+ }
651
+
652
+ _disposed = true;
653
+ console.info("[honcho] Shutting down...");
654
+
655
+ // Dispose memory instance
656
+ if (_honchoMemory) {
657
+ _honchoMemory.dispose();
658
+ _honchoMemory = null;
659
+ }
660
+
661
+ // Clear identity
662
+ _identity = null;
663
+
664
+ // Dispose owned registration
665
+ if (_honchoRegistration) {
666
+ try {
667
+ await _honchoRegistration.dispose();
668
+ console.info("[honcho] Registration disposed");
669
+ } catch (err) {
670
+ console.warn("[honcho] Failed to dispose registration:", err);
671
+ }
672
+ _honchoRegistration = null;
673
+ }
674
+
675
+ _initialized = false;
676
+ console.info("[honcho] Shutdown complete");
677
+ }
678
+
679
+ /**
680
+ * Register Honcho with session lifecycle hooks.
681
+ * Call this during harness startup to wire Honcho into Pi events.
682
+ *
683
+ * @param getPiEvents Function that returns pi.events when available
684
+ */
685
+ /**
686
+ * Call a Honcho MCP tool (exported for honcho-memory.ts)
687
+ */
688
+ export async function honchoToolCall(
689
+ toolName: string,
690
+ args: Record<string, unknown>
691
+ ): Promise<{ success: boolean; result?: unknown; error?: string }> {
692
+ return await callHonchoTool(toolName, args);
693
+ }
694
+
695
+ /**
696
+ * Register Honcho lifecycle hooks using pi.on.
697
+ * Must be called before Pi emits session_start.
698
+ * @param pi ExtensionAPI instance from startup
699
+ */
700
+ export function registerHonchoLifecycle(
701
+ pi: {
702
+ on: (event: string, handler: (...args: unknown[]) => void | Promise<void>) => void;
703
+ }
704
+ ): void {
705
+ // Initialize at session start
706
+ pi.on("session_start", async () => {
707
+ console.info("[honcho] Session started, initializing...");
708
+ await initHoncho();
709
+ });
710
+
711
+ // Shutdown at session shutdown
712
+ pi.on("session_shutdown", async () => {
713
+ await shutdownHoncho();
714
+ });
715
+
716
+ // Ingest assistant messages at message_end
717
+ pi.on("message_end", async (event: unknown) => {
718
+ const msgEvent = event as { message?: { role?: string; content?: string | Array<{ type?: string; text?: string }> } } | undefined;
719
+ if (!msgEvent?.message || msgEvent.message.role !== "assistant") return;
720
+
721
+ const memory = getHonchoMemory();
722
+ if (!memory?.isReady()) return;
723
+
724
+ const msg = msgEvent.message;
725
+
726
+ // Extract text content
727
+ let content = "";
728
+ if (typeof msg.content === "string") {
729
+ content = msg.content;
730
+ } else if (Array.isArray(msg.content)) {
731
+ content = msg.content
732
+ .filter((block): block is { type: "text"; text: string } =>
733
+ block.type === "text" && typeof block.text === "string"
734
+ )
735
+ .map(block => block.text)
736
+ .join("\n");
737
+ }
738
+
739
+ if (content) {
740
+ await memory.ingestMessage("assistant", content);
741
+ }
742
+ });
743
+
744
+ console.info("[honcho] Lifecycle hooks registered");
745
+ }