pi-harness-runtime 1.1.420 → 1.1.422

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 (119) hide show
  1. package/harness/honcho-init.ts +256 -66
  2. package/harness/honcho-memory.ts +61 -17
  3. package/package.json +2 -2
  4. package/packages/a2a-adapter/package.json +1 -1
  5. package/packages/agent-policy/package.json +1 -1
  6. package/packages/architecture-generator/package.json +1 -1
  7. package/packages/auth/package.json +1 -1
  8. package/packages/autonomous-refactor/package.json +1 -1
  9. package/packages/autonomous-runtime/package.json +1 -1
  10. package/packages/cache-strategy/package.json +1 -1
  11. package/packages/capability-registry/package.json +1 -1
  12. package/packages/checkpoint/package.json +1 -1
  13. package/packages/cli-plugin-sdk/package.json +1 -1
  14. package/packages/clipboard/package.json +1 -1
  15. package/packages/clipboard-plugin/package.json +1 -1
  16. package/packages/code-generation/package.json +1 -1
  17. package/packages/code-review/package.json +1 -1
  18. package/packages/codex-adapter/package.json +1 -1
  19. package/packages/codex-watcher/package.json +1 -1
  20. package/packages/config-capture/package.json +1 -1
  21. package/packages/connection-runtime/package.json +1 -1
  22. package/packages/context-compiler/package.json +1 -1
  23. package/packages/context-discovery/package.json +1 -1
  24. package/packages/context-manager/package.json +1 -1
  25. package/packages/contracts/dist/approvals.d.ts +70 -183
  26. package/packages/contracts/dist/events.d.ts +83 -57
  27. package/packages/contracts/dist/jobs.d.ts +77 -156
  28. package/packages/contracts/dist/notifications.d.ts +68 -230
  29. package/packages/contracts/dist/projects.d.ts +31 -109
  30. package/packages/contracts/dist/sessions.d.ts +66 -150
  31. package/packages/contracts/package.json +2 -2
  32. package/packages/cookie-sanitizer/package.json +1 -1
  33. package/packages/cost-optimizer/package.json +1 -1
  34. package/packages/dependency-analyzer/package.json +1 -1
  35. package/packages/django-plugin/package.json +1 -1
  36. package/packages/doc-generator/package.json +1 -1
  37. package/packages/durable-review/package.json +1 -1
  38. package/packages/e2e-tester/package.json +1 -1
  39. package/packages/evaluation-engine/package.json +1 -1
  40. package/packages/evaluation-runner/package.json +1 -1
  41. package/packages/event-bus/package.json +1 -1
  42. package/packages/event-sink/package.json +1 -1
  43. package/packages/event-store/package.json +1 -1
  44. package/packages/experience-replay/package.json +1 -1
  45. package/packages/feedback-collector/package.json +1 -1
  46. package/packages/file-copy-helper/package.json +1 -1
  47. package/packages/framework-detector/package.json +1 -1
  48. package/packages/framework-plugin-sdk/package.json +1 -1
  49. package/packages/frappe-plugin/package.json +1 -1
  50. package/packages/generic-web-plugin/package.json +1 -1
  51. package/packages/health-monitor/package.json +1 -1
  52. package/packages/intent-analyzer/package.json +1 -1
  53. package/packages/jev-judge/package.json +1 -1
  54. package/packages/knowledge-graph/package.json +1 -1
  55. package/packages/knowledge-retrieval/package.json +1 -1
  56. package/packages/laravel-plugin/package.json +1 -1
  57. package/packages/learning-engine/package.json +1 -1
  58. package/packages/logging/package.json +1 -1
  59. package/packages/mcp-adapter/package.json +1 -1
  60. package/packages/memory-engine/package.json +1 -1
  61. package/packages/milestone-manager/package.json +1 -1
  62. package/packages/model-registry/package.json +1 -1
  63. package/packages/nextjs-plugin/package.json +1 -1
  64. package/packages/notification/package.json +1 -1
  65. package/packages/observability/package.json +1 -1
  66. package/packages/okf-indexer/package.json +1 -1
  67. package/packages/paperclip-integration/package.json +1 -1
  68. package/packages/performance-optimizer/package.json +1 -1
  69. package/packages/permission-mode/package.json +1 -1
  70. package/packages/privilege-broker/package.json +1 -1
  71. package/packages/project-analyzer/package.json +1 -1
  72. package/packages/project-bootstrap/package.json +1 -1
  73. package/packages/projection-engine/package.json +1 -1
  74. package/packages/prompt-compiler/package.json +1 -1
  75. package/packages/prompt-versioning/package.json +1 -1
  76. package/packages/provider-adapter-sdk/package.json +1 -1
  77. package/packages/provider-router/package.json +1 -1
  78. package/packages/provider-selector/package.json +1 -1
  79. package/packages/providers/package.json +1 -1
  80. package/packages/qdrant-skills/package.json +1 -1
  81. package/packages/quota-manager/package.json +1 -1
  82. package/packages/rate-limiter/package.json +1 -1
  83. package/packages/react-vite-plugin/package.json +1 -1
  84. package/packages/relay/package.json +1 -1
  85. package/packages/release-manager/package.json +1 -1
  86. package/packages/requirement-compiler/package.json +1 -1
  87. package/packages/runtime/package.json +1 -1
  88. package/packages/scheduler/package.json +1 -1
  89. package/packages/scheduler-adapter/package.json +1 -1
  90. package/packages/session/package.json +1 -1
  91. package/packages/session-api/package.json +1 -1
  92. package/packages/session-export/package.json +1 -1
  93. package/packages/shared-context/package.json +1 -1
  94. package/packages/skill-mcp-client/package.json +1 -1
  95. package/packages/skill-registry/package.json +1 -1
  96. package/packages/skills/package.json +1 -1
  97. package/packages/sprint-planner/package.json +1 -1
  98. package/packages/subscription-engine/package.json +1 -1
  99. package/packages/task-compiler/package.json +1 -1
  100. package/packages/telegram/package.json +1 -1
  101. package/packages/tencentdb-memory/package.json +1 -1
  102. package/packages/tencentdb-sync/package.json +1 -1
  103. package/packages/test-data-generator/package.json +1 -1
  104. package/packages/test-generator/package.json +1 -1
  105. package/packages/todo-bd-sync/package.json +1 -1
  106. package/packages/token-estimation/package.json +1 -1
  107. package/packages/token-optimizer/package.json +1 -1
  108. package/packages/tooling-preference/package.json +1 -1
  109. package/packages/trajectory/package.json +1 -1
  110. package/packages/tui/package.json +1 -1
  111. package/packages/two-phase-review/package.json +1 -1
  112. package/packages/types/package.json +1 -1
  113. package/packages/voice-api/package.json +1 -1
  114. package/packages/webmcp-tester/package.json +1 -1
  115. package/packages/wiki/package.json +1 -1
  116. package/packages/workflow-events/package.json +1 -1
  117. package/packages/workspace-scanner/package.json +1 -1
  118. package/packages/worktree/package.json +1 -1
  119. package/packages/write-review/package.json +1 -1
@@ -55,6 +55,30 @@ type McpToolCallResult =
55
55
  | { ok: true; result?: unknown }
56
56
  | { ok: false; error?: Error };
57
57
 
58
+ /**
59
+ * Did the adapter refuse the call?
60
+ *
61
+ * The check is a type guard and not a plain `!result.ok`: the root project
62
+ * runs without strictNullChecks, and there the discriminant of a union is
63
+ * not narrowed - the guard narrows in every setting.
64
+ */
65
+ function isToolCallFailure(
66
+ result: McpToolCallResult
67
+ ): result is { ok: false; error?: Error } {
68
+ return result.ok === false;
69
+ }
70
+
71
+ /**
72
+ * Did the adapter refuse the registration?
73
+ *
74
+ * The same reason as isToolCallFailure: the guard narrows in every setting.
75
+ */
76
+ function isRegistrationFailure(
77
+ result: NonNullable<McpRuntimeRegistrationRequest["result"]>
78
+ ): result is { ok: false; error: Error } {
79
+ return result.ok === false;
80
+ }
81
+
58
82
  /**
59
83
  * Honcho session identity
60
84
  */
@@ -153,6 +177,79 @@ let _disposed = false;
153
177
  // MCP Tool Calls
154
178
  // ---------------------------------------------------------------------------
155
179
 
180
+ /**
181
+ * The payload of a Honcho tool call.
182
+ *
183
+ * The MCP server answers with the MCP shape: the payload is the TEXT of the
184
+ * first content block, and for the tools that answer with data that text is
185
+ * JSON. The tools that only confirm something ("Peers added to session
186
+ * successfully") answer with plain text.
187
+ *
188
+ * The harness therefore has to unwrap the content block before it can read a
189
+ * peer id - reading `result.id` on the raw MCP result finds nothing, because
190
+ * the id lives in the JSON text of the first block.
191
+ */
192
+ export function honchoPayload(result: unknown): {
193
+ data: Record<string, unknown> | null;
194
+ parsed: unknown;
195
+ text: string;
196
+ } {
197
+ const content = (result as { content?: unknown })?.content;
198
+ const first = Array.isArray(content) ? content[0] : undefined;
199
+ const text = typeof (first as { text?: unknown })?.text === "string" ? (first as { text: string }).text : "";
200
+ if (text === "") {
201
+ return { data: null, parsed: null, text: "" };
202
+ }
203
+ try {
204
+ const parsed = JSON.parse(text);
205
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
206
+ return { data: parsed as Record<string, unknown>, parsed, text };
207
+ }
208
+ if (parsed !== null && typeof parsed === "object") {
209
+ return { data: null, parsed, text };
210
+ }
211
+ } catch {
212
+ // Not JSON: the tool confirmed with plain text.
213
+ }
214
+ return { data: null, parsed: null, text };
215
+ }
216
+
217
+ /**
218
+ * The entries of a list tool ("peers", "sessions").
219
+ *
220
+ * The server answers with the list under its own key and the paging beside
221
+ * it. A bare array is accepted as well.
222
+ */
223
+ function listEntriesFrom(parsed: unknown, key: string): Record<string, unknown>[] {
224
+ const list = (parsed as Record<string, unknown> | null)?.[key];
225
+ if (Array.isArray(list)) {
226
+ return list.filter((entry): entry is Record<string, unknown> => typeof entry === "object" && entry !== null);
227
+ }
228
+ if (Array.isArray(parsed)) {
229
+ return parsed.filter((entry): entry is Record<string, unknown> => typeof entry === "object" && entry !== null);
230
+ }
231
+ return [];
232
+ }
233
+
234
+ /**
235
+ * The peer id in the answer of a peer tool.
236
+ *
237
+ * The server answers with `peer_id`; the answers that carry a nested peer
238
+ * use `id`. Both are accepted so a server change cannot silently empty the
239
+ * identity.
240
+ */
241
+ function peerIdFrom(data: Record<string, unknown> | null): string | null {
242
+ if (!data) {
243
+ return null;
244
+ }
245
+ const direct = data.peer_id ?? data.id;
246
+ if (typeof direct === "string" && direct !== "") {
247
+ return direct;
248
+ }
249
+ const nested = (data.peer as { id?: unknown } | undefined)?.id;
250
+ return typeof nested === "string" && nested !== "" ? nested : null;
251
+ }
252
+
156
253
  /**
157
254
  * Call a tool on the Honcho MCP server through the adapter.
158
255
  * Returns actual result from the MCP server.
@@ -184,21 +281,29 @@ async function callHonchoTool(
184
281
  // Emit tool call
185
282
  piEvents.emit(MCP_RUNTIME_TOOL_CALL_EVENT, request);
186
283
 
187
- // Wait for result - adapter assigns a Promise to request.result
188
- if (request.result === undefined) {
284
+ // Read result immediately after emit. The value goes into a local: reading
285
+ // the property again after the emit would lose the narrowing, because a
286
+ // call may change the property.
287
+ const pending = request.result;
288
+ if (pending === undefined) {
189
289
  logServiceEvent({ ts: new Date().toISOString(), level: "error", service: "honcho", operation: "tool_call", phase: "failed", tool: toolName, reason: "no response from adapter" });
190
290
  return { success: false, error: "No response from MCP adapter" };
191
291
  }
192
292
 
193
- // request.result is a Promise from the adapter
194
- const result = await request.result;
195
-
196
- logServiceEvent({ ts: new Date().toISOString(), level: "debug", service: "honcho", operation: "tool_call", phase: "completed", tool: toolName, result: JSON.stringify(result).slice(0, 200) });
293
+ // The adapter sets the promise during the emit - await it.
294
+ const result = await pending;
197
295
 
198
- if (!result.ok) {
199
- return { success: false, error: result.error?.message ?? "Unknown error" };
296
+ if (isToolCallFailure(result)) {
297
+ // The adapter hands back an Error instance: JSON.stringify would show an
298
+ // empty object, so the message goes into the log on its own. Without it
299
+ // a failing call reads like "Unknown error" and names nothing.
300
+ const message = result.error?.message ?? "Unknown error";
301
+ logServiceEvent({ ts: new Date().toISOString(), level: "error", service: "honcho", operation: "tool_call", phase: "failed", tool: toolName, error: message });
302
+ return { success: false, error: message };
200
303
  }
201
304
 
305
+ logServiceEvent({ ts: new Date().toISOString(), level: "debug", service: "honcho", operation: "tool_call", phase: "completed", tool: toolName, result: JSON.stringify(result.result).slice(0, 200) });
306
+
202
307
  return { success: true, result: result.result };
203
308
  }
204
309
 
@@ -252,7 +357,19 @@ async function ensureWorkspace(workspaceName: string): Promise<string | null> {
252
357
  });
253
358
 
254
359
  if (inspectResult.success && inspectResult.result) {
255
- const response = inspectResult.result as { workspace?: { id?: string }; id?: string; metadata?: Record<string, unknown> };
360
+ // The server answers with the MCP envelope: the JSON sits in the text of
361
+ // the first content block. Reading the envelope directly would find
362
+ // nothing and silently fall back to the workspace name.
363
+ const parsed = honchoPayload(inspectResult.result).parsed as {
364
+ workspace?: { id?: string };
365
+ id?: string;
366
+ metadata?: Record<string, unknown>;
367
+ } | null;
368
+ const response: {
369
+ workspace?: { id?: string };
370
+ id?: string;
371
+ metadata?: Record<string, unknown>;
372
+ } = parsed ?? {};
256
373
  const workspaceId = response.workspace?.id ?? response.id ?? workspaceName;
257
374
  savePersistedWorkspace(workspaceId, workspaceName);
258
375
 
@@ -285,25 +402,31 @@ async function ensureUserPeer(
285
402
  workspaceId: string,
286
403
  userName: string
287
404
  ): Promise<string | null> {
288
- const result = await callHonchoTool("create_user", {
405
+ // The server's peer API: create_peer is a get-or-create.
406
+ const result = await callHonchoTool("create_peer", {
289
407
  workspace_id: workspaceId,
290
- name: userName,
408
+ peer_id: userName,
291
409
  });
292
410
 
293
- if (result.success && result.result) {
294
- const response = result.result as { id?: string; user?: { id?: string } };
295
- return response.id ?? response.user?.id ?? null;
411
+ if (result.success) {
412
+ const peerId = peerIdFrom(honchoPayload(result.result).data);
413
+ if (peerId) {
414
+ return peerId;
415
+ }
296
416
  }
297
417
 
298
- // Try get_or_create_user as fallback
299
- const getResult = await callHonchoTool("get_or_create_user", {
418
+ // The peer may exist already: list the peers and look for it.
419
+ const listResult = await callHonchoTool("list_peers", {
300
420
  workspace_id: workspaceId,
301
- name: userName,
421
+ size: 100,
302
422
  });
303
-
304
- if (getResult.success && getResult.result) {
305
- const response = getResult.result as { id?: string };
306
- return response.id ?? null;
423
+ if (listResult.success) {
424
+ for (const peer of listEntriesFrom(honchoPayload(listResult.result).parsed, "peers")) {
425
+ const peerId = peerIdFrom(peer);
426
+ if (peerId === userName) {
427
+ return peerId;
428
+ }
429
+ }
307
430
  }
308
431
 
309
432
  logServiceEvent({
@@ -325,14 +448,29 @@ async function ensureAssistantPeer(
325
448
  workspaceId: string,
326
449
  assistantName: string
327
450
  ): Promise<string | null> {
328
- const result = await callHonchoTool("create_assistant", {
451
+ const result = await callHonchoTool("create_peer", {
329
452
  workspace_id: workspaceId,
330
- name: assistantName,
453
+ peer_id: assistantName,
331
454
  });
332
455
 
333
- if (result.success && result.result) {
334
- const response = result.result as { id?: string; assistant?: { id?: string } };
335
- return response.id ?? response.assistant?.id ?? null;
456
+ if (result.success) {
457
+ const peerId = peerIdFrom(honchoPayload(result.result).data);
458
+ if (peerId) {
459
+ return peerId;
460
+ }
461
+ }
462
+
463
+ const listResult = await callHonchoTool("list_peers", {
464
+ workspace_id: workspaceId,
465
+ size: 100,
466
+ });
467
+ if (listResult.success) {
468
+ for (const peer of listEntriesFrom(honchoPayload(listResult.result).parsed, "peers")) {
469
+ const peerId = peerIdFrom(peer);
470
+ if (peerId === assistantName) {
471
+ return peerId;
472
+ }
473
+ }
336
474
  }
337
475
 
338
476
  logServiceEvent({
@@ -356,59 +494,97 @@ async function ensureSession(
356
494
  assistantPeerId: string,
357
495
  sessionId: string
358
496
  ): Promise<string | null> {
497
+ // The server's session API: create_session is a get-or-create, and the
498
+ // peers join the session with their own call.
359
499
  const result = await callHonchoTool("create_session", {
360
500
  workspace_id: workspaceId,
361
- user_id: userPeerId,
362
- assistant_id: assistantPeerId,
363
- id: sessionId,
501
+ session_id: sessionId,
364
502
  });
365
503
 
366
- if (result.success && result.result) {
367
- const response = result.result as { id?: string; session?: { id?: string } };
368
- return response.id ?? response.session?.id ?? null;
504
+ let createdSessionId: string | null = null;
505
+ if (result.success) {
506
+ const data = honchoPayload(result.result).data;
507
+ const id = data?.session_id ?? data?.id;
508
+ createdSessionId = typeof id === "string" && id !== "" ? id : null;
369
509
  }
370
510
 
371
- // Try get_or_create_session as fallback
372
- const getResult = await callHonchoTool("get_or_create_session", {
373
- workspace_id: workspaceId,
374
- user_id: userPeerId,
375
- assistant_id: assistantPeerId,
376
- id: sessionId,
377
- });
511
+ if (!createdSessionId) {
512
+ // The session may exist already: list the sessions and look for it.
513
+ const listResult = await callHonchoTool("list_sessions", {
514
+ workspace_id: workspaceId,
515
+ size: 100,
516
+ });
517
+ if (listResult.success) {
518
+ for (const session of listEntriesFrom(honchoPayload(listResult.result).parsed, "sessions")) {
519
+ const id = session.session_id ?? session.id;
520
+ if (typeof id === "string" && id === sessionId) {
521
+ createdSessionId = id;
522
+ break;
523
+ }
524
+ }
525
+ }
526
+ }
378
527
 
379
- if (getResult.success && getResult.result) {
380
- const response = getResult.result as { id?: string };
381
- return response.id ?? null;
528
+ if (!createdSessionId) {
529
+ logServiceEvent({
530
+ ts: new Date().toISOString(),
531
+ level: "error",
532
+ service: "honcho",
533
+ operation: "session_failed",
534
+ phase: "failed",
535
+ error: result.error ?? "Unknown error",
536
+ });
537
+
538
+ return null;
382
539
  }
383
540
 
384
- logServiceEvent({
385
- ts: new Date().toISOString(),
386
- level: "error",
387
- service: "honcho",
388
- operation: "session_failed",
389
- phase: "failed",
390
- error: result.error ?? "Unknown error",
541
+ // The peers have to join the session explicitly.
542
+ const peersResult = await callHonchoTool("add_peers_to_session", {
543
+ workspace_id: workspaceId,
544
+ session_id: createdSessionId,
545
+ peers: [userPeerId, assistantPeerId],
391
546
  });
547
+ if (!peersResult.success) {
548
+ logServiceEvent({
549
+ ts: new Date().toISOString(),
550
+ level: "warn",
551
+ service: "honcho",
552
+ operation: "session_peers_failed",
553
+ phase: "failed",
554
+ error: peersResult.error ?? "Unknown error",
555
+ sessionId: createdSessionId,
556
+ });
557
+ }
392
558
 
393
- return null;
559
+ return createdSessionId;
394
560
  }
395
561
 
396
562
  /**
397
563
  * Add a message to the session
398
564
  */
399
565
  async function addMessage(
566
+ workspaceId: string,
400
567
  sessionId: string,
401
568
  peerId: string,
402
569
  role: "user" | "assistant",
403
570
  content: string,
404
571
  messageId?: string
405
572
  ): Promise<boolean> {
406
- const result = await callHonchoTool("add_message", {
573
+ // The server takes the messages of a session in one call, and the author
574
+ // of a message is the peer - the role lives in the metadata.
575
+ const result = await callHonchoTool("add_messages_to_session", {
576
+ workspace_id: workspaceId,
407
577
  session_id: sessionId,
408
- sender_id: peerId,
409
- role,
410
- content,
411
- message_id: messageId,
578
+ messages: [
579
+ {
580
+ peer_id: peerId,
581
+ content,
582
+ metadata: {
583
+ role,
584
+ ...(messageId ? { message_id: messageId } : {}),
585
+ },
586
+ },
587
+ ],
412
588
  });
413
589
 
414
590
  return result.success;
@@ -423,19 +599,25 @@ async function getContext(
423
599
  query: string,
424
600
  limit: number = 5
425
601
  ): Promise<string | null> {
426
- const result = await callHonchoTool("get_context", {
602
+ // The server has no query-scoped "get_context": the recall is the search,
603
+ // and the messages it finds are the context.
604
+ const result = await callHonchoTool("search", {
427
605
  workspace_id: workspaceId,
428
606
  session_id: sessionId,
429
607
  query,
430
- limit,
608
+ message_limit: Math.max(1, Math.min(100, limit)),
431
609
  });
432
610
 
433
- if (result.success && result.result) {
434
- const response = result.result as { content?: string; context?: string; text?: string };
435
- return response.content ?? response.context ?? response.text ?? null;
611
+ if (!result.success) {
612
+ return null;
436
613
  }
437
614
 
438
- return null;
615
+ const messages = listEntriesFrom(honchoPayload(result.result).parsed, "messages");
616
+ const texts = messages
617
+ .map((message) => (typeof message.content === "string" ? message.content : ""))
618
+ .filter((text) => text !== "");
619
+
620
+ return texts.length > 0 ? texts.join("\n") : null;
439
621
  }
440
622
 
441
623
  // ---------------------------------------------------------------------------
@@ -539,16 +721,19 @@ export async function initHoncho(options?: {
539
721
  // Emit registration event - adapter writes request.result synchronously
540
722
  piEvents.emit(MCP_RUNTIME_REGISTER_EVENT, request);
541
723
 
542
- // Read result immediately after emit
543
- if (request.result === undefined) {
724
+ // Read result immediately after emit. The value goes into a local: reading
725
+ // the property again after the emit would lose the narrowing, because a
726
+ // call may change the property.
727
+ const registration = request.result;
728
+ if (registration === undefined) {
544
729
  logServiceEvent({ ts: new Date().toISOString(), level: "error", service: "honcho", operation: "mcp_register", phase: "failed", reason: "MCP adapter not responding" });
545
730
  logServiceEvent({ ts: new Date().toISOString(), level: "info", service: "honcho", operation: "mcp_register", phase: "failed", helpUrl: "pi install npm:pi-mcp-adapter" });
546
731
  honchoStatus.recordFailure("init", "no_listener", { error: "No adapter responded" });
547
732
  return null;
548
733
  }
549
734
 
550
- if (!request.result.ok) {
551
- const errorMsg = request.result.error?.message ?? "Unknown registration error";
735
+ if (isRegistrationFailure(registration)) {
736
+ const errorMsg = registration.error?.message ?? "Unknown registration error";
552
737
 
553
738
  // Check for duplicate name - an existing server may already be registered
554
739
  if (errorMsg.includes("already") || errorMsg.includes("duplicate")) {
@@ -561,7 +746,7 @@ export async function initHoncho(options?: {
561
746
  }
562
747
  } else {
563
748
  // Registration accepted - store handle for disposal
564
- _honchoRegistration = request.result.registration;
749
+ _honchoRegistration = registration.registration;
565
750
  logServiceEvent({ ts: new Date().toISOString(), level: "info", service: "honcho", operation: "mcp_register", phase: "completed" });
566
751
  }
567
752
 
@@ -643,6 +828,9 @@ export async function ensureHonchoIdentity(): Promise<boolean> {
643
828
  _honchoMemory.setToolsDiscovered(true);
644
829
  _honchoMemory.setWorkspace(workspaceId);
645
830
  _honchoMemory.setSession(honchoSessionId);
831
+ // The peers are the authors of the messages: without them the ingestion
832
+ // cannot say who said what.
833
+ _honchoMemory.setPeers(userPeerId, assistantPeerId);
646
834
 
647
835
  logServiceEvent({ ts: new Date().toISOString(), level: "info", service: "honcho", operation: "memory_init", phase: "completed" });
648
836
  honchoStatus.recordSuccess("init", undefined, {
@@ -691,6 +879,7 @@ export async function ingestUserMessage(
691
879
  }
692
880
 
693
881
  return await addMessage(
882
+ _identity.workspaceId,
694
883
  _identity.sessionId,
695
884
  _identity.userPeerId,
696
885
  "user",
@@ -711,6 +900,7 @@ export async function ingestAssistantMessage(
711
900
  }
712
901
 
713
902
  return await addMessage(
903
+ _identity.workspaceId,
714
904
  _identity.sessionId,
715
905
  _identity.assistantPeerId,
716
906
  "assistant",
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  import { logServiceEvent, honchoStatus } from "./service-diagnostics.js";
15
- import { honchoToolCall } from "./honcho-init.js";
15
+ import { honchoPayload, honchoToolCall } from "./honcho-init.js";
16
16
  import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "node:fs";
17
17
  import { join } from "node:path";
18
18
  import { homedir } from "node:os";
@@ -308,6 +308,8 @@ export class HonchoMemory {
308
308
  private toolsDiscovered = false;
309
309
  private workspaceId: string | null = null;
310
310
  private sessionId: string | null = null;
311
+ private userPeerId: string | null = null;
312
+ private assistantPeerId: string | null = null;
311
313
  private _disposed = false;
312
314
 
313
315
  constructor(config: HonchoMemoryConfig) {
@@ -383,6 +385,26 @@ export class HonchoMemory {
383
385
  });
384
386
  }
385
387
 
388
+ /**
389
+ * Set the peers that author the messages.
390
+ *
391
+ * The server takes the author of a message as the peer, so the ingestion
392
+ * cannot say who said what without them.
393
+ */
394
+ setPeers(userPeerId: string, assistantPeerId: string): void {
395
+ this.userPeerId = userPeerId;
396
+ this.assistantPeerId = assistantPeerId;
397
+ logServiceEvent({
398
+ ts: new Date().toISOString(),
399
+ level: "info",
400
+ service: "honcho",
401
+ operation: "peers_set",
402
+ phase: "completed",
403
+ userPeerId,
404
+ assistantPeerId,
405
+ });
406
+ }
407
+
386
408
  /**
387
409
  * Check if memory is ready
388
410
  */
@@ -514,15 +536,28 @@ export class HonchoMemory {
514
536
  });
515
537
 
516
538
  try {
517
- // Call Honcho MCP tool through the adapter
539
+ // The server takes the messages of a session in one call, and the
540
+ // author of a message is the peer.
518
541
  // Use sessionId if available, fall back to workspaceId
519
542
  const targetSessionId = this.sessionId ?? this.workspaceId;
520
- const result = await this.callHonchoTool("add_message", {
543
+ const peerId = role === "assistant" ? this.assistantPeerId : this.userPeerId;
544
+ if (!peerId) {
545
+ throw new Error("The Honcho peers are not known yet");
546
+ }
547
+ const result = await this.callHonchoTool("add_messages_to_session", {
548
+ workspace_id: this.workspaceId,
521
549
  session_id: targetSessionId,
522
- role,
523
- content: sanitized,
524
- message_id: messageId,
525
- timestamp: timestamp.toISOString(),
550
+ messages: [
551
+ {
552
+ peer_id: peerId,
553
+ content: sanitized,
554
+ metadata: {
555
+ role,
556
+ message_id: messageId,
557
+ timestamp: timestamp.toISOString(),
558
+ },
559
+ },
560
+ ],
526
561
  });
527
562
 
528
563
  if (!result.success) {
@@ -602,27 +637,36 @@ export class HonchoMemory {
602
637
  });
603
638
 
604
639
  try {
605
- // Call Honcho get_session_context tool through the adapter
640
+ // The recall of the server is the search: it answers with the messages
641
+ // that match the query, and those messages are the context.
606
642
  // Use sessionId if available, fall back to workspaceId
607
643
  const targetSessionId = this.sessionId ?? this.workspaceId;
608
- const result = await this.callHonchoTool("get_context", {
644
+ const result = await this.callHonchoTool("search", {
645
+ workspace_id: this.workspaceId,
609
646
  session_id: targetSessionId,
610
647
  query,
611
- limit: 10,
648
+ message_limit: 10,
612
649
  });
613
650
 
614
651
  if (!result.success) {
615
652
  throw new Error(result.error || "Tool call failed");
616
653
  }
617
654
 
618
- // Parse actual response content
655
+ // Parse the messages the search found.
656
+ const payload = honchoPayload(result.result);
657
+ const messages = (payload.parsed as { messages?: unknown } | null)?.messages;
619
658
  let context = "";
620
- if (result.result && typeof result.result === "object") {
621
- const response = result.result as Record<string, unknown>;
622
- // Extract context from response based on actual Honcho API shape
623
- context = (response.content as string) ||
624
- (response.messages as string[])?.join("\n") ||
625
- JSON.stringify(response);
659
+ if (Array.isArray(messages)) {
660
+ context = messages
661
+ .map((message) => {
662
+ const text = (message as { content?: unknown })?.content;
663
+ return typeof text === "string" ? text : "";
664
+ })
665
+ .filter((text) => text !== "")
666
+ .join("\n");
667
+ }
668
+ if (context === "" && payload.text !== "") {
669
+ context = payload.text;
626
670
  }
627
671
 
628
672
  // Apply max context chars limit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-harness-runtime",
3
- "version": "1.1.420",
3
+ "version": "1.1.422",
4
4
  "description": "[BETA] Codex-style /usage status + autonomous coding harness for pi. Not production ready — expect breaking changes.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -116,7 +116,7 @@
116
116
  "deepagents": "^1.10.5",
117
117
  "langchain": "^1.5.10",
118
118
  "ws": "^8.22.0",
119
- "zod": "^4.5.4"
119
+ "zod": "^4.6.7"
120
120
  },
121
121
  "peerDependenciesMeta": {
122
122
  "pi-mcp-adapter": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/a2a-adapter",
3
- "version": "1.1.420",
3
+ "version": "1.1.422",
4
4
  "type": "module",
5
5
  "description": "Google Agent-to-Agent (A2A) protocol adapter for pi-harness",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/agent-policy",
3
- "version": "1.1.420",
3
+ "version": "1.1.422",
4
4
  "description": "Agent policy runtime for pi-harness - enforces mandatory rule delivery and mutation gating",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/architecture-generator",
3
- "version": "1.1.420",
3
+ "version": "1.1.422",
4
4
  "description": "Generate ADR and Mermaid diagrams from project analysis",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/auth",
3
- "version": "1.1.420",
3
+ "version": "1.1.422",
4
4
  "description": "pi-harness-runtime — auth",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/autonomous-refactor",
3
- "version": "1.1.420",
3
+ "version": "1.1.422",
4
4
  "type": "module",
5
5
  "description": "Autonomous refactoring engine",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/autonomous-runtime",
3
- "version": "1.1.420",
3
+ "version": "1.1.422",
4
4
  "description": "Autonomous Operations Runtime — task inbox, lease management, and worker lifecycle (RFC-0101)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi/cache-strategy",
3
- "version": "1.1.420",
3
+ "version": "1.1.422",
4
4
  "type": "module",
5
5
  "description": "Smart caching with TTL and invalidation strategies (RFC-0022)",
6
6
  "main": "dist/index.js",