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.
- package/CHANGELOG.md +7 -0
- package/harness/glm-quota-countdown.ts +9 -1
- package/harness/honcho-init.ts +745 -0
- package/harness/honcho-memory.ts +290 -97
- package/harness/service-commands.ts +28 -3
- package/index.ts +191 -63
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/dist/extension.d.ts.map +1 -1
- package/packages/agent-policy/dist/extension.js +16 -9
- package/packages/agent-policy/dist/extension.js.map +1 -1
- package/packages/agent-policy/dist/policy-loader.d.ts.map +1 -1
- package/packages/agent-policy/dist/policy-loader.js +5 -16
- package/packages/agent-policy/dist/policy-loader.js.map +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/agent-policy/src/extension.ts +16 -9
- package/packages/agent-policy/src/policy-loader.ts +5 -22
- package/packages/agent-policy/test/interlock.test.ts +101 -0
- 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/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- 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/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-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/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/dist/adapters/telegram-adapter.d.ts +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.d.ts.map +1 -1
- package/packages/notification/dist/adapters/telegram-adapter.js +34 -21
- package/packages/notification/dist/adapters/telegram-adapter.js.map +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/notification/src/adapters/telegram-adapter.ts +37 -21
- 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/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/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/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/webmcp-tester/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
|
@@ -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
|
+
}
|