pi-honcho-memory 0.2.0 → 0.3.0
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/.github/workflows/npm-publish.yml +33 -0
- package/extensions/client.ts +38 -2
- package/extensions/config.ts +1 -0
- package/extensions/tools.ts +71 -8
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
|
3
|
+
|
|
4
|
+
name: Node.js Package
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [created]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: 20
|
|
18
|
+
- run: npm ci
|
|
19
|
+
- run: npm test
|
|
20
|
+
|
|
21
|
+
publish-npm:
|
|
22
|
+
needs: build
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 20
|
|
29
|
+
registry-url: https://registry.npmjs.org/
|
|
30
|
+
- run: npm ci
|
|
31
|
+
- run: npm publish
|
|
32
|
+
env:
|
|
33
|
+
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
package/extensions/client.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { Honcho } from "@honcho-ai/sdk";
|
|
2
2
|
import type { Peer, Session } from "@honcho-ai/sdk";
|
|
3
3
|
import type { HonchoConfig } from "./config.js";
|
|
4
|
+
import { readConfigFile } from "./config.js";
|
|
4
5
|
import { deriveSessionKey } from "./session.js";
|
|
5
6
|
|
|
7
|
+
export interface LinkedHostHandle {
|
|
8
|
+
name: string;
|
|
9
|
+
honcho: Honcho;
|
|
10
|
+
userPeer: Peer;
|
|
11
|
+
aiPeer: Peer;
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
export interface HonchoHandles {
|
|
7
15
|
honcho: Honcho;
|
|
8
16
|
userPeer: Peer;
|
|
@@ -10,6 +18,7 @@ export interface HonchoHandles {
|
|
|
10
18
|
session: Session | null;
|
|
11
19
|
sessionKey: string;
|
|
12
20
|
config: HonchoConfig;
|
|
21
|
+
linked: LinkedHostHandle[];
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
let cachedHandles: HonchoHandles | null = null;
|
|
@@ -51,14 +60,41 @@ export const bootstrap = async (config: HonchoConfig, cwd: string): Promise<Honc
|
|
|
51
60
|
honcho.peer(config.aiPeer),
|
|
52
61
|
]);
|
|
53
62
|
|
|
63
|
+
// Bootstrap linked host clients (read-only cross-workspace access)
|
|
64
|
+
const linked: LinkedHostHandle[] = [];
|
|
65
|
+
if (config.linkedHosts.length > 0) {
|
|
66
|
+
const file = await readConfigFile();
|
|
67
|
+
const allHosts = file?.hosts ?? {};
|
|
68
|
+
const settled = await Promise.allSettled(
|
|
69
|
+
config.linkedHosts.map(async (hostName): Promise<LinkedHostHandle | null> => {
|
|
70
|
+
const hc = allHosts[hostName];
|
|
71
|
+
if (!hc?.workspace || !hc?.aiPeer) return null;
|
|
72
|
+
const h = new Honcho({
|
|
73
|
+
apiKey: config.apiKey,
|
|
74
|
+
baseURL: config.baseURL,
|
|
75
|
+
workspaceId: hc.workspace,
|
|
76
|
+
environment: config.environment,
|
|
77
|
+
});
|
|
78
|
+
const [up, ap] = await Promise.all([h.peer(config.peerName), h.peer(hc.aiPeer)]);
|
|
79
|
+
return { name: hostName, honcho: h, userPeer: up, aiPeer: ap };
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
for (const result of settled) {
|
|
83
|
+
if (result.status === "fulfilled" && result.value) linked.push(result.value);
|
|
84
|
+
else if (result.status === "rejected" && config.logging) {
|
|
85
|
+
console.error("[honcho-memory] linked host bootstrap failed:", result.reason);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
54
90
|
if (config.recallMode === "tools") {
|
|
55
91
|
// Lazy: defer session creation until first tool call
|
|
56
|
-
cachedHandles = { honcho, userPeer, aiPeer, session: null, sessionKey, config };
|
|
92
|
+
cachedHandles = { honcho, userPeer, aiPeer, session: null, sessionKey, config, linked };
|
|
57
93
|
return cachedHandles;
|
|
58
94
|
}
|
|
59
95
|
|
|
60
96
|
// Eager: full init
|
|
61
|
-
cachedHandles = { honcho, userPeer, aiPeer, session: null, sessionKey, config };
|
|
97
|
+
cachedHandles = { honcho, userPeer, aiPeer, session: null, sessionKey, config, linked };
|
|
62
98
|
await ensureSession(cachedHandles);
|
|
63
99
|
return cachedHandles;
|
|
64
100
|
};
|
package/extensions/config.ts
CHANGED
package/extensions/tools.ts
CHANGED
|
@@ -44,10 +44,10 @@ const ensureHandles = async () => {
|
|
|
44
44
|
return handles;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
const formatSearch = (results: Array<{ peerId
|
|
47
|
+
const formatSearch = (results: Array<{ sourceHost?: string; peerId?: string; content: string }>, preview: number): string => {
|
|
48
48
|
if (results.length === 0) return "No relevant memory found.";
|
|
49
49
|
return results
|
|
50
|
-
.map((entry, index) => `${index + 1}. [${entry.peerId}] ${entry.content.slice(0, preview)}`)
|
|
50
|
+
.map((entry, index) => `${index + 1}. [${entry.sourceHost || "pi"} | ${entry.peerId || "unknown"}] ${entry.content.slice(0, preview)}`)
|
|
51
51
|
.join("\n\n");
|
|
52
52
|
};
|
|
53
53
|
|
|
@@ -57,7 +57,7 @@ export const registerTools = (pi: ExtensionAPI): void => {
|
|
|
57
57
|
pi.registerTool({
|
|
58
58
|
name: "honcho_profile",
|
|
59
59
|
label: "Honcho Profile",
|
|
60
|
-
description: "Retrieve what Honcho currently knows about the user profile.",
|
|
60
|
+
description: "Retrieve what Honcho currently knows about the user profile from this and linked workspaces.",
|
|
61
61
|
parameters: Type.Object({}),
|
|
62
62
|
async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
|
|
63
63
|
const handles = await ensureHandles();
|
|
@@ -67,8 +67,23 @@ export const registerTools = (pi: ExtensionAPI): void => {
|
|
|
67
67
|
peerTarget: handles.userPeer,
|
|
68
68
|
tokens: handles.config.contextTokens,
|
|
69
69
|
});
|
|
70
|
+
let result = `=== [pi] ===\n${context.peerRepresentation?.trim() || "No profile memory available yet."}`;
|
|
71
|
+
|
|
72
|
+
for (const lh of handles.linked) {
|
|
73
|
+
try {
|
|
74
|
+
const rep = await lh.aiPeer.representation({ target: lh.userPeer });
|
|
75
|
+
if (rep?.trim()) {
|
|
76
|
+
result += `\n\n=== [${lh.name}] ===\n${rep.trim()}`;
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (handles.config.logging) {
|
|
80
|
+
console.error(`[honcho-memory] ${lh.name} profile read failed:`, err instanceof Error ? err.message : err);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
return {
|
|
71
|
-
content: [{ type: "text", text:
|
|
86
|
+
content: [{ type: "text", text: result }],
|
|
72
87
|
details: {},
|
|
73
88
|
};
|
|
74
89
|
},
|
|
@@ -77,12 +92,33 @@ export const registerTools = (pi: ExtensionAPI): void => {
|
|
|
77
92
|
pi.registerTool({
|
|
78
93
|
name: "honcho_search",
|
|
79
94
|
label: "Honcho Search",
|
|
80
|
-
description: "Search durable memory for prior conversations, facts, and decisions.",
|
|
95
|
+
description: "Search durable memory for prior conversations, facts, and decisions across all linked workspaces.",
|
|
81
96
|
parameters: Type.Object({ query: Type.String({ description: "Search query" }) }),
|
|
82
97
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
83
98
|
const handles = await ensureHandles();
|
|
84
99
|
const limit = Math.min(handles.config.searchLimit, SEARCH_MAX_TOKENS);
|
|
85
|
-
const results
|
|
100
|
+
const results: Array<{ sourceHost?: string; peerId?: string; content: string }> = [];
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const mainResults = await handles.session!.search(params.query, { limit });
|
|
104
|
+
results.push(...mainResults.map((r: any) => ({ ...r, sourceHost: "pi" })));
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (handles.config.logging) {
|
|
107
|
+
console.error("[honcho-memory] primary search failed:", err instanceof Error ? err.message : err);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const lh of handles.linked) {
|
|
112
|
+
try {
|
|
113
|
+
const hostResults = await lh.honcho.search(params.query, { limit });
|
|
114
|
+
results.push(...hostResults.map((r: any) => ({ ...r, sourceHost: lh.name })));
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (handles.config.logging) {
|
|
117
|
+
console.error(`[honcho-memory] ${lh.name} search failed:`, err instanceof Error ? err.message : err);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
86
122
|
return {
|
|
87
123
|
content: [{ type: "text", text: formatSearch(results, handles.config.toolPreviewLength) }],
|
|
88
124
|
details: { count: results.length },
|
|
@@ -93,7 +129,7 @@ export const registerTools = (pi: ExtensionAPI): void => {
|
|
|
93
129
|
pi.registerTool({
|
|
94
130
|
name: "honcho_context",
|
|
95
131
|
label: "Honcho Context",
|
|
96
|
-
description: "Ask Honcho to synthesize memory context for the current question.",
|
|
132
|
+
description: "Ask Honcho to synthesize memory context for the current question across all linked workspaces.",
|
|
97
133
|
parameters: Type.Object({
|
|
98
134
|
query: Type.String({ description: "Question to ask about long-term memory" }),
|
|
99
135
|
reasoningLevel: Type.Optional(StringEnum(["minimal", "low", "medium", "high", "max"] as const)),
|
|
@@ -103,6 +139,7 @@ export const registerTools = (pi: ExtensionAPI): void => {
|
|
|
103
139
|
const truncatedQuery = params.query.slice(0, handles.config.dialecticMaxInputChars);
|
|
104
140
|
const level = params.reasoningLevel
|
|
105
141
|
?? dynamicLevel(truncatedQuery, handles.config.reasoningLevel, handles.config.dialecticDynamic, handles.config.reasoningLevelCap);
|
|
142
|
+
|
|
106
143
|
let reply = await handles.aiPeer.chat(truncatedQuery, {
|
|
107
144
|
target: handles.userPeer,
|
|
108
145
|
session: handles.session!,
|
|
@@ -118,7 +155,33 @@ export const registerTools = (pi: ExtensionAPI): void => {
|
|
|
118
155
|
});
|
|
119
156
|
}
|
|
120
157
|
}
|
|
121
|
-
|
|
158
|
+
let result = `=== [pi] ===\n${reply?.slice(0, handles.config.dialecticMaxChars) ?? "No additional context available."}`;
|
|
159
|
+
|
|
160
|
+
for (const lh of handles.linked) {
|
|
161
|
+
try {
|
|
162
|
+
let hostReply = await lh.aiPeer.chat(truncatedQuery, {
|
|
163
|
+
target: lh.userPeer,
|
|
164
|
+
reasoningLevel: level,
|
|
165
|
+
});
|
|
166
|
+
if (!hostReply?.trim()) {
|
|
167
|
+
const bumped = nextLevel(level);
|
|
168
|
+
if (bumped) {
|
|
169
|
+
hostReply = await lh.aiPeer.chat(truncatedQuery, {
|
|
170
|
+
target: lh.userPeer,
|
|
171
|
+
reasoningLevel: bumped,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (hostReply?.trim()) {
|
|
176
|
+
result += `\n\n=== [${lh.name}] ===\n${hostReply.slice(0, handles.config.dialecticMaxChars)}`;
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (handles.config.logging) {
|
|
180
|
+
console.error(`[honcho-memory] ${lh.name} context failed:`, err instanceof Error ? err.message : err);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
122
185
|
return {
|
|
123
186
|
content: [{ type: "text", text: result }],
|
|
124
187
|
details: {},
|