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.
- package/harness/honcho-init.ts +256 -66
- package/harness/honcho-memory.ts +61 -17
- package/package.json +2 -2
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/dist/approvals.d.ts +70 -183
- package/packages/contracts/dist/events.d.ts +83 -57
- package/packages/contracts/dist/jobs.d.ts +77 -156
- package/packages/contracts/dist/notifications.d.ts +68 -230
- package/packages/contracts/dist/projects.d.ts +31 -109
- package/packages/contracts/dist/sessions.d.ts +66 -150
- package/packages/contracts/package.json +2 -2
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/logging/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/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/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
package/harness/honcho-init.ts
CHANGED
|
@@ -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
|
-
//
|
|
188
|
-
|
|
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
|
-
//
|
|
194
|
-
const result = await
|
|
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 (
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
408
|
+
peer_id: userName,
|
|
291
409
|
});
|
|
292
410
|
|
|
293
|
-
if (result.success
|
|
294
|
-
const
|
|
295
|
-
|
|
411
|
+
if (result.success) {
|
|
412
|
+
const peerId = peerIdFrom(honchoPayload(result.result).data);
|
|
413
|
+
if (peerId) {
|
|
414
|
+
return peerId;
|
|
415
|
+
}
|
|
296
416
|
}
|
|
297
417
|
|
|
298
|
-
//
|
|
299
|
-
const
|
|
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
|
-
|
|
421
|
+
size: 100,
|
|
302
422
|
});
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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("
|
|
451
|
+
const result = await callHonchoTool("create_peer", {
|
|
329
452
|
workspace_id: workspaceId,
|
|
330
|
-
|
|
453
|
+
peer_id: assistantName,
|
|
331
454
|
});
|
|
332
455
|
|
|
333
|
-
if (result.success
|
|
334
|
-
const
|
|
335
|
-
|
|
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
|
-
|
|
362
|
-
assistant_id: assistantPeerId,
|
|
363
|
-
id: sessionId,
|
|
501
|
+
session_id: sessionId,
|
|
364
502
|
});
|
|
365
503
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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 (
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
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
|
|
434
|
-
|
|
435
|
-
return response.content ?? response.context ?? response.text ?? null;
|
|
611
|
+
if (!result.success) {
|
|
612
|
+
return null;
|
|
436
613
|
}
|
|
437
614
|
|
|
438
|
-
|
|
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
|
-
|
|
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 (
|
|
551
|
-
const errorMsg =
|
|
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 =
|
|
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",
|
package/harness/honcho-memory.ts
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
//
|
|
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("
|
|
644
|
+
const result = await this.callHonchoTool("search", {
|
|
645
|
+
workspace_id: this.workspaceId,
|
|
609
646
|
session_id: targetSessionId,
|
|
610
647
|
query,
|
|
611
|
-
|
|
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
|
|
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 (
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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.
|
|
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.
|
|
119
|
+
"zod": "^4.6.7"
|
|
120
120
|
},
|
|
121
121
|
"peerDependenciesMeta": {
|
|
122
122
|
"pi-mcp-adapter": {
|