toolnet-memory 0.2.13 → 0.2.14
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/README.md +119 -26
- package/bundle/auto-integrate.js +600 -127
- package/bundle/opencode.js +637 -161
- package/bundle/setup.js +607 -134
- package/package.json +1 -1
package/bundle/setup.js
CHANGED
|
@@ -1,25 +1,169 @@
|
|
|
1
|
-
import
|
|
2
|
-
`,{encoding:"utf8",mode:384}),t}import{existsSync as oe,mkdirSync as
|
|
3
|
-
## ToolNet Memory
|
|
1
|
+
import S from"node:fs";import Oe from"node:os";import G from"node:path";import Ae from"node:readline/promises";import{stdin as g,stdout as E}from"node:process";import{existsSync as A}from"node:fs";import{homedir as h}from"node:os";import{join as R}from"node:path";import{spawnSync as Se}from"node:child_process";import{existsSync as X,mkdirSync as W,readFileSync as V,writeFileSync as Z}from"node:fs";import{homedir as Q}from"node:os";import{dirname as z,join as ee}from"node:path";function te(e){return`'${e.replace(/'/g,"'\\''")}'`}function F(e={}){let t=e.hooksFile??ee(Q(),".gemini","config","hooks.json");W(z(t),{recursive:!0});let o={};if(X(t))try{o=JSON.parse(V(t,"utf8"))}catch(i){throw new Error(`Invalid existing Agy hooks.json: ${i instanceof Error?i.message:String(i)}`)}let r=e.binary??"toolnet-memory",n=`${te(r)} session:agy-hook`;return o["toolnet-memory"]={enabled:!0,PreInvocation:[{type:"command",command:`${n} pre`,timeout:15}],PostInvocation:[{type:"command",command:`${n} post`,timeout:15}],Stop:[{type:"command",command:`${n} stop`,timeout:30}]},Z(t,JSON.stringify(o,null,2)+`
|
|
2
|
+
`,{encoding:"utf8",mode:384}),t}import{existsSync as oe,mkdirSync as D,readFileSync as ne,writeFileSync as $}from"node:fs";import{homedir as T}from"node:os";import{join as _}from"node:path";var Y="<!-- TOOLNET_MEMORY_BOOTSTRAP_START -->",C="<!-- TOOLNET_MEMORY_BOOTSTRAP_END -->";function re(){let e=_(T(),".config","opencode","AGENTS.md");D(_(T(),".config","opencode"),{recursive:!0});let t=`${Y}
|
|
3
|
+
## ToolNet Memory
|
|
4
|
+
|
|
5
|
+
ToolNet Memory is available for projects that already contain a valid \`.toolnet/project.json\`.
|
|
6
|
+
|
|
7
|
+
Rules:
|
|
8
|
+
|
|
9
|
+
1. Do not create a ToolNet project automatically.
|
|
10
|
+
2. If no valid \`.toolnet/project.json\` exists in the current project or an ancestor, ignore ToolNet Memory.
|
|
11
|
+
3. Normal startup context must stay small and selective.
|
|
12
|
+
4. Do not automatically run deep recovery commands.
|
|
13
|
+
5. Use ToolNet MCP or selective retrieval when older project knowledge is actually needed.
|
|
14
|
+
6. Raw transcripts must not be injected into prompts.
|
|
15
|
+
7. Current repository evidence has priority over stale memory when they conflict.
|
|
16
|
+
|
|
17
|
+
${C}`,o=oe(e)?ne(e,"utf8"):"",r=o.indexOf(Y),n=o.indexOf(C);return r>=0&&n>=r?o=o.slice(0,r)+t+o.slice(n+C.length):(o=o.trimEnd(),o&&(o+=`
|
|
18
|
+
|
|
19
|
+
`),o+=t),$(e,o.trimEnd()+`
|
|
20
|
+
`,{encoding:"utf8",mode:384}),e}function v(e={}){let t=e.directory??_(T(),".config","opencode","plugins");D(t,{recursive:!0}),re();let o=_(t,"toolnet-memory.js"),r=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",n=`
|
|
21
|
+
// Generated by ToolNet Memory.
|
|
22
|
+
// OpenCode integration:
|
|
23
|
+
// - project gated
|
|
24
|
+
// - selective context injection
|
|
25
|
+
// - incremental session checkpoints
|
|
26
|
+
// - periodic durability sync
|
|
27
|
+
// - best-effort shutdown flush
|
|
28
|
+
|
|
29
|
+
import fs from "node:fs"
|
|
30
|
+
import path from "node:path"
|
|
31
|
+
import os from "node:os"
|
|
32
|
+
|
|
33
|
+
const TOOLNET_BINARY =
|
|
34
|
+
${JSON.stringify(r)}
|
|
35
|
+
|
|
36
|
+
const CONTEXT_MAX_TOKENS = 700
|
|
37
|
+
|
|
38
|
+
const CONTEXT_CACHE_MS = 5000
|
|
39
|
+
|
|
40
|
+
const PERIODIC_SYNC_MS = 30000
|
|
41
|
+
|
|
42
|
+
const SYNC_TIMEOUT_MS = 15000
|
|
43
|
+
|
|
44
|
+
const STATUS_FILE = path.join(
|
|
45
|
+
os.homedir(),
|
|
46
|
+
".config",
|
|
47
|
+
"toolnet-memory",
|
|
48
|
+
"opencode-sync-status.json"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
function writeStatus(data) {
|
|
52
|
+
try {
|
|
53
|
+
fs.mkdirSync(
|
|
54
|
+
path.dirname(
|
|
55
|
+
STATUS_FILE
|
|
56
|
+
),
|
|
57
|
+
{
|
|
58
|
+
recursive: true,
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(
|
|
63
|
+
STATUS_FILE,
|
|
64
|
+
JSON.stringify(
|
|
65
|
+
{
|
|
66
|
+
timestamp:
|
|
67
|
+
new Date()
|
|
68
|
+
.toISOString(),
|
|
69
|
+
...data,
|
|
70
|
+
},
|
|
71
|
+
null,
|
|
72
|
+
2
|
|
73
|
+
) + "\\n"
|
|
74
|
+
)
|
|
75
|
+
} catch {
|
|
76
|
+
// Status reporting must
|
|
77
|
+
// never break OpenCode.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function validProjectManifest(
|
|
82
|
+
file
|
|
83
|
+
) {
|
|
84
|
+
try {
|
|
85
|
+
const parsed =
|
|
86
|
+
JSON.parse(
|
|
87
|
+
fs.readFileSync(
|
|
88
|
+
file,
|
|
89
|
+
"utf8"
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return Boolean(
|
|
94
|
+
parsed &&
|
|
95
|
+
typeof parsed ===
|
|
96
|
+
"object" &&
|
|
97
|
+
typeof parsed.id ===
|
|
98
|
+
"string" &&
|
|
99
|
+
parsed.id.length > 0
|
|
100
|
+
)
|
|
101
|
+
} catch {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function findProjectRoot(
|
|
107
|
+
inputDirectory
|
|
108
|
+
) {
|
|
109
|
+
let current =
|
|
110
|
+
path.resolve(
|
|
111
|
+
inputDirectory
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
const filesystemRoot =
|
|
115
|
+
path.parse(
|
|
116
|
+
current
|
|
117
|
+
).root
|
|
118
|
+
|
|
119
|
+
while (true) {
|
|
120
|
+
const manifest =
|
|
121
|
+
path.join(
|
|
122
|
+
current,
|
|
123
|
+
".toolnet",
|
|
124
|
+
"project.json"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if (
|
|
128
|
+
fs.existsSync(
|
|
129
|
+
manifest
|
|
130
|
+
) &&
|
|
131
|
+
validProjectManifest(
|
|
132
|
+
manifest
|
|
133
|
+
)
|
|
134
|
+
) {
|
|
135
|
+
return current
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (
|
|
139
|
+
current ===
|
|
140
|
+
filesystemRoot
|
|
141
|
+
) {
|
|
142
|
+
break
|
|
143
|
+
}
|
|
4
144
|
|
|
5
|
-
|
|
145
|
+
const parent =
|
|
146
|
+
path.dirname(
|
|
147
|
+
current
|
|
148
|
+
)
|
|
6
149
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
6. Never invent missing rationale. If ToolNet says a reason was not explicitly recorded, inspect the implementation or ask rather than guessing.
|
|
150
|
+
if (
|
|
151
|
+
parent === current
|
|
152
|
+
) {
|
|
153
|
+
break
|
|
154
|
+
}
|
|
13
155
|
|
|
14
|
-
|
|
15
|
-
|
|
156
|
+
current = parent
|
|
157
|
+
}
|
|
16
158
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// OpenCode session capture + continuity bootstrap.
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
20
161
|
|
|
21
|
-
function getSessionId(
|
|
22
|
-
|
|
162
|
+
function getSessionId(
|
|
163
|
+
event
|
|
164
|
+
) {
|
|
165
|
+
const p =
|
|
166
|
+
event?.properties ?? {}
|
|
23
167
|
|
|
24
168
|
const candidates = [
|
|
25
169
|
p.sessionID,
|
|
@@ -28,158 +172,487 @@ function getSessionId(event) {
|
|
|
28
172
|
p.session?.id,
|
|
29
173
|
]
|
|
30
174
|
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
175
|
+
return (
|
|
176
|
+
candidates.find(
|
|
177
|
+
value =>
|
|
178
|
+
typeof value ===
|
|
179
|
+
"string" &&
|
|
180
|
+
value.length > 0
|
|
181
|
+
) ?? null
|
|
182
|
+
)
|
|
34
183
|
}
|
|
35
184
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
185
|
+
async function runWithTimeout(
|
|
186
|
+
args,
|
|
187
|
+
{
|
|
188
|
+
stdout = "ignore",
|
|
189
|
+
timeout =
|
|
190
|
+
SYNC_TIMEOUT_MS,
|
|
191
|
+
} = {}
|
|
192
|
+
) {
|
|
193
|
+
const child =
|
|
194
|
+
Bun.spawn(
|
|
195
|
+
args,
|
|
196
|
+
{
|
|
197
|
+
stdin:
|
|
198
|
+
"ignore",
|
|
199
|
+
stdout,
|
|
200
|
+
stderr:
|
|
201
|
+
"ignore",
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
let timer
|
|
206
|
+
|
|
207
|
+
const timeoutPromise =
|
|
208
|
+
new Promise(
|
|
209
|
+
(_, reject) => {
|
|
210
|
+
timer =
|
|
211
|
+
setTimeout(
|
|
212
|
+
() => {
|
|
213
|
+
try {
|
|
214
|
+
child.kill()
|
|
215
|
+
} catch {}
|
|
216
|
+
|
|
217
|
+
reject(
|
|
218
|
+
new Error(
|
|
219
|
+
"ToolNet command timeout"
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
},
|
|
223
|
+
timeout
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const exitCode =
|
|
230
|
+
await Promise.race([
|
|
231
|
+
child.exited,
|
|
232
|
+
timeoutPromise,
|
|
233
|
+
])
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
typeof exitCode ===
|
|
237
|
+
"number" &&
|
|
238
|
+
exitCode !== 0
|
|
239
|
+
) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
\`ToolNet exited with code \${exitCode}\`
|
|
53
242
|
)
|
|
243
|
+
}
|
|
54
244
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
245
|
+
return child
|
|
246
|
+
} finally {
|
|
247
|
+
if (timer) {
|
|
248
|
+
clearTimeout(
|
|
249
|
+
timer
|
|
250
|
+
)
|
|
58
251
|
}
|
|
59
252
|
}
|
|
253
|
+
}
|
|
60
254
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
stderr: "ignore",
|
|
76
|
-
}
|
|
255
|
+
export const ToolNetMemoryPlugin =
|
|
256
|
+
async ({
|
|
257
|
+
directory,
|
|
258
|
+
}) => {
|
|
259
|
+
/*
|
|
260
|
+
* CRITICAL:
|
|
261
|
+
* Never auto-create a ToolNet
|
|
262
|
+
* project just because OpenCode
|
|
263
|
+
* happens to start in /root,
|
|
264
|
+
* /tmp, $HOME, etc.
|
|
265
|
+
*/
|
|
266
|
+
const projectRoot =
|
|
267
|
+
findProjectRoot(
|
|
268
|
+
directory
|
|
77
269
|
)
|
|
78
270
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
271
|
+
if (!projectRoot) {
|
|
272
|
+
writeStatus({
|
|
273
|
+
active: false,
|
|
274
|
+
reason:
|
|
275
|
+
"no-toolnet-project",
|
|
276
|
+
directory,
|
|
277
|
+
})
|
|
85
278
|
|
|
86
|
-
return
|
|
87
|
-
} catch {
|
|
88
|
-
return ""
|
|
279
|
+
return {}
|
|
89
280
|
}
|
|
90
|
-
}
|
|
91
281
|
|
|
92
|
-
|
|
93
|
-
|
|
282
|
+
writeStatus({
|
|
283
|
+
active: true,
|
|
284
|
+
projectRoot,
|
|
285
|
+
state:
|
|
286
|
+
"plugin-loaded",
|
|
287
|
+
})
|
|
94
288
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
binary,
|
|
98
|
-
"session:opencode-sync",
|
|
99
|
-
sessionId,
|
|
100
|
-
"--project",
|
|
101
|
-
directory,
|
|
102
|
-
]
|
|
289
|
+
let lastSessionId =
|
|
290
|
+
null
|
|
103
291
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
292
|
+
let syncChain =
|
|
293
|
+
Promise.resolve()
|
|
107
294
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
295
|
+
let contextCache = {
|
|
296
|
+
value: "",
|
|
297
|
+
expiresAt: 0,
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function readContext() {
|
|
301
|
+
const now =
|
|
302
|
+
Date.now()
|
|
113
303
|
|
|
114
304
|
if (
|
|
115
|
-
|
|
305
|
+
contextCache.value &&
|
|
306
|
+
now <
|
|
307
|
+
contextCache.expiresAt
|
|
116
308
|
) {
|
|
117
|
-
|
|
309
|
+
return (
|
|
310
|
+
contextCache.value
|
|
311
|
+
)
|
|
118
312
|
}
|
|
119
|
-
} catch {
|
|
120
|
-
// Memory capture must never break OpenCode.
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
313
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
314
|
+
try {
|
|
315
|
+
const child =
|
|
316
|
+
Bun.spawn(
|
|
317
|
+
[
|
|
318
|
+
TOOLNET_BINARY,
|
|
319
|
+
"context:print",
|
|
320
|
+
"--project",
|
|
321
|
+
projectRoot,
|
|
322
|
+
"--tokens",
|
|
323
|
+
String(
|
|
324
|
+
CONTEXT_MAX_TOKENS
|
|
325
|
+
),
|
|
326
|
+
],
|
|
327
|
+
{
|
|
328
|
+
stdin:
|
|
329
|
+
"ignore",
|
|
330
|
+
stdout:
|
|
331
|
+
"pipe",
|
|
332
|
+
stderr:
|
|
333
|
+
"ignore",
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
const textPromise =
|
|
338
|
+
new Response(
|
|
339
|
+
child.stdout
|
|
340
|
+
).text()
|
|
341
|
+
|
|
342
|
+
const exitCode =
|
|
343
|
+
await child.exited
|
|
344
|
+
|
|
345
|
+
if (
|
|
346
|
+
exitCode !== 0
|
|
347
|
+
) {
|
|
348
|
+
return ""
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const text =
|
|
352
|
+
(
|
|
353
|
+
await textPromise
|
|
354
|
+
).trim()
|
|
129
355
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
356
|
+
contextCache = {
|
|
357
|
+
value: text,
|
|
358
|
+
expiresAt:
|
|
359
|
+
now +
|
|
360
|
+
CONTEXT_CACHE_MS,
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return text
|
|
364
|
+
} catch {
|
|
365
|
+
return ""
|
|
366
|
+
}
|
|
367
|
+
}
|
|
134
368
|
|
|
369
|
+
async function syncNow(
|
|
370
|
+
sessionId,
|
|
371
|
+
flag,
|
|
372
|
+
reason
|
|
373
|
+
) {
|
|
135
374
|
if (!sessionId) {
|
|
136
375
|
return
|
|
137
376
|
}
|
|
138
377
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
378
|
+
const args = [
|
|
379
|
+
TOOLNET_BINARY,
|
|
380
|
+
"session:opencode-sync",
|
|
381
|
+
sessionId,
|
|
382
|
+
"--project",
|
|
383
|
+
projectRoot,
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
if (flag) {
|
|
387
|
+
args.push(
|
|
388
|
+
flag
|
|
389
|
+
)
|
|
142
390
|
}
|
|
143
391
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
392
|
+
try {
|
|
393
|
+
await runWithTimeout(
|
|
394
|
+
args
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
writeStatus({
|
|
398
|
+
active: true,
|
|
399
|
+
projectRoot,
|
|
400
|
+
sessionId,
|
|
401
|
+
reason,
|
|
402
|
+
state:
|
|
403
|
+
"sync-success",
|
|
404
|
+
})
|
|
405
|
+
} catch (error) {
|
|
406
|
+
writeStatus({
|
|
407
|
+
active: true,
|
|
408
|
+
projectRoot,
|
|
409
|
+
sessionId,
|
|
410
|
+
reason,
|
|
411
|
+
state:
|
|
412
|
+
"sync-failed",
|
|
413
|
+
error:
|
|
414
|
+
error instanceof
|
|
415
|
+
Error
|
|
416
|
+
? error.message
|
|
417
|
+
: String(
|
|
418
|
+
error
|
|
419
|
+
),
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
throw error
|
|
147
423
|
}
|
|
424
|
+
}
|
|
148
425
|
|
|
149
|
-
|
|
150
|
-
|
|
426
|
+
function queueSync(
|
|
427
|
+
sessionId,
|
|
428
|
+
flag,
|
|
429
|
+
reason
|
|
430
|
+
) {
|
|
431
|
+
if (!sessionId) {
|
|
432
|
+
return syncChain
|
|
151
433
|
}
|
|
152
|
-
|
|
434
|
+
|
|
435
|
+
lastSessionId =
|
|
436
|
+
sessionId
|
|
437
|
+
|
|
438
|
+
syncChain =
|
|
439
|
+
syncChain
|
|
440
|
+
.catch(
|
|
441
|
+
() =>
|
|
442
|
+
undefined
|
|
443
|
+
)
|
|
444
|
+
.then(
|
|
445
|
+
() =>
|
|
446
|
+
syncNow(
|
|
447
|
+
sessionId,
|
|
448
|
+
flag,
|
|
449
|
+
reason
|
|
450
|
+
)
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
return syncChain
|
|
454
|
+
}
|
|
153
455
|
|
|
154
456
|
/*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
457
|
+
* Periodic durability checkpoint.
|
|
458
|
+
*
|
|
459
|
+
* This is intentionally independent
|
|
460
|
+
* from session.idle/dispose because
|
|
461
|
+
* abrupt SSH/process termination may
|
|
462
|
+
* skip shutdown hooks.
|
|
463
|
+
*
|
|
464
|
+
* unref() prevents the timer itself
|
|
465
|
+
* from keeping OpenCode alive.
|
|
157
466
|
*/
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
467
|
+
const periodic =
|
|
468
|
+
setInterval(
|
|
469
|
+
() => {
|
|
470
|
+
if (
|
|
471
|
+
lastSessionId
|
|
472
|
+
) {
|
|
473
|
+
void queueSync(
|
|
474
|
+
lastSessionId,
|
|
475
|
+
null,
|
|
476
|
+
"periodic"
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
PERIODIC_SYNC_MS
|
|
481
|
+
)
|
|
161
482
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
483
|
+
if (
|
|
484
|
+
typeof periodic.unref ===
|
|
485
|
+
"function"
|
|
486
|
+
) {
|
|
487
|
+
periodic.unref()
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
event: async ({
|
|
492
|
+
event,
|
|
493
|
+
}) => {
|
|
494
|
+
const sessionId =
|
|
495
|
+
getSessionId(
|
|
496
|
+
event
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
if (sessionId) {
|
|
500
|
+
lastSessionId =
|
|
501
|
+
sessionId
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (
|
|
505
|
+
event.type ===
|
|
506
|
+
"session.idle"
|
|
507
|
+
) {
|
|
508
|
+
await queueSync(
|
|
509
|
+
sessionId,
|
|
510
|
+
"--idle",
|
|
511
|
+
"session.idle"
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
return
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (
|
|
518
|
+
event.type ===
|
|
519
|
+
"session.compacted"
|
|
520
|
+
) {
|
|
521
|
+
await queueSync(
|
|
522
|
+
sessionId,
|
|
523
|
+
"--compacted",
|
|
524
|
+
"session.compacted"
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
contextCache = {
|
|
528
|
+
value: "",
|
|
529
|
+
expiresAt: 0,
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (
|
|
536
|
+
event.type ===
|
|
537
|
+
"session.error"
|
|
538
|
+
) {
|
|
539
|
+
await queueSync(
|
|
540
|
+
sessionId,
|
|
541
|
+
"--error",
|
|
542
|
+
"session.error"
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
return
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
/*
|
|
550
|
+
* VERIFIED on OpenCode 1.18.14:
|
|
551
|
+
* this hook fires on real model turns.
|
|
552
|
+
*
|
|
553
|
+
* It remains experimental, therefore
|
|
554
|
+
* AGENTS.md + MCP stay as fallbacks.
|
|
555
|
+
*/
|
|
556
|
+
"experimental.chat.system.transform":
|
|
557
|
+
async (
|
|
558
|
+
_input,
|
|
559
|
+
output
|
|
560
|
+
) => {
|
|
561
|
+
const context =
|
|
562
|
+
await readContext()
|
|
563
|
+
|
|
564
|
+
if (!context) {
|
|
565
|
+
return
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (
|
|
569
|
+
Array.isArray(
|
|
570
|
+
output?.system
|
|
571
|
+
)
|
|
572
|
+
) {
|
|
573
|
+
output.system.push(
|
|
574
|
+
context
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
return
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (
|
|
581
|
+
typeof output?.system ===
|
|
582
|
+
"string"
|
|
583
|
+
) {
|
|
584
|
+
output.system =
|
|
585
|
+
output.system
|
|
586
|
+
? \`\${output.system}\\n\\n\${context}\`
|
|
587
|
+
: context
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
|
|
591
|
+
/*
|
|
592
|
+
* Optional compaction survival hook.
|
|
593
|
+
* We keep it as a supplementary path,
|
|
594
|
+
* never as the only continuity path.
|
|
595
|
+
*/
|
|
596
|
+
"experimental.session.compacting":
|
|
597
|
+
async (
|
|
598
|
+
_input,
|
|
599
|
+
output
|
|
600
|
+
) => {
|
|
601
|
+
const context =
|
|
602
|
+
await readContext()
|
|
603
|
+
|
|
604
|
+
if (
|
|
605
|
+
context &&
|
|
606
|
+
Array.isArray(
|
|
607
|
+
output?.context
|
|
608
|
+
)
|
|
609
|
+
) {
|
|
610
|
+
output.context.push(
|
|
611
|
+
context
|
|
612
|
+
)
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
/*
|
|
617
|
+
* VERIFIED for clean OpenCode exits.
|
|
618
|
+
* Best effort only: kill -9 / crash /
|
|
619
|
+
* machine loss cannot guarantee this.
|
|
620
|
+
*/
|
|
621
|
+
dispose: async () => {
|
|
622
|
+
clearInterval(
|
|
623
|
+
periodic
|
|
170
624
|
)
|
|
171
|
-
|
|
172
|
-
|
|
625
|
+
|
|
626
|
+
if (
|
|
627
|
+
lastSessionId
|
|
628
|
+
) {
|
|
629
|
+
try {
|
|
630
|
+
await queueSync(
|
|
631
|
+
lastSessionId,
|
|
632
|
+
"--idle",
|
|
633
|
+
"dispose"
|
|
634
|
+
)
|
|
635
|
+
} catch {
|
|
636
|
+
// Status already recorded.
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
try {
|
|
641
|
+
await syncChain
|
|
642
|
+
} catch {
|
|
643
|
+
// Do not block OpenCode shutdown.
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
}
|
|
173
647
|
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
`),a=
|
|
177
|
-
|
|
178
|
-
`,{encoding:"utf8",mode:384}),r=i.join(`
|
|
648
|
+
`;return $(o,n.trimStart(),{encoding:"utf8",mode:384}),o}import{existsSync as se,mkdirSync as K,readFileSync as ie,writeFileSync as P}from"node:fs";import{homedir as k}from"node:os";import{dirname as H,join as O}from"node:path";function ce(e){let t=[],o=/"((?:\\.|[^"\\])*)"|'([^']*)'/g,r;for(;r=o.exec(e);){let n=r[1]??r[2]??"";try{t.push(r[1]!==void 0?JSON.parse(`"${n}"`):n)}catch{t.push(n)}}return t}function b(e={}){let t=e.configFile??O(process.env.CODEX_HOME??O(k(),".codex"),"config.toml"),o=e.previousFile??O(k(),".config","toolnet-memory","codex-notify-previous.json");K(H(t),{recursive:!0}),K(H(o),{recursive:!0});let r=se(t)?ie(t,"utf8"):"",n=e.binary??"toolnet-memory",i=`notify = [${JSON.stringify(n)}, "session:codex-notify"]`,s=r.split(`
|
|
649
|
+
`),a=s.findIndex(l=>/^\s*\[/.test(l));a<0&&(a=s.length);let f=-1,c=-1;for(let l=0;l<a;l+=1)if(/^\s*notify\s*=/.test(s[l])){if(f=l,c=l,s[l].includes("[")&&!s[l].includes("]"))for(;c+1<a&&(c+=1,!s[c].includes("]")););break}let u=[];if(f>=0){let l=s.slice(f,c+1).join(`
|
|
650
|
+
`);u=ce(l),s.splice(f,c-f+1,i)}else a=s.findIndex(l=>/^\s*\[/.test(l)),a<0&&(a=s.length),s.splice(a,0,i);let m=u.length>=2&&u[u.length-1]==="session:codex-notify";return u.length>0&&!m&&P(o,JSON.stringify(u,null,2)+`
|
|
651
|
+
`,{encoding:"utf8",mode:384}),r=s.join(`
|
|
179
652
|
`),r.endsWith(`
|
|
180
653
|
`)||(r+=`
|
|
181
|
-
`),
|
|
182
|
-
`,{encoding:"utf8",mode:384}),t}function
|
|
654
|
+
`),P(t,r,{encoding:"utf8",mode:384}),{configFile:t,previousFile:o,preservedPrevious:u.length>0&&!m}}import{existsSync as ae,mkdirSync as le,readFileSync as ue,writeFileSync as ge}from"node:fs";import{homedir as de}from"node:os";import{dirname as fe,join as j}from"node:path";function me(e){return`'${e.replace(/'/g,"'\\''")}'`}function L(e={}){let t=e.hooksFile??j(process.env.CODEX_HOME??j(de(),".codex"),"hooks.json");le(fe(t),{recursive:!0});let o={};if(ae(t))try{o=JSON.parse(ue(t,"utf8"))}catch(a){throw new Error(`Invalid existing Codex hooks.json: ${a instanceof Error?a.message:String(a)}`)}let r=o.hooks&&typeof o.hooks=="object"&&!Array.isArray(o.hooks)?o.hooks:{};o.hooks=r;let i=(Array.isArray(r.SessionStart)?r.SessionStart:[]).filter(a=>{try{return!JSON.stringify(a).includes("session:codex-context")}catch{return!0}}),s=e.binary??"toolnet-memory";return i.push({matcher:"startup|resume|clear|compact",hooks:[{type:"command",command:`${me(s)} session:codex-context`,timeout:15,additionalContextLimit:1e3,statusMessage:"Loading ToolNet project continuity"}]}),r.SessionStart=i,ge(t,JSON.stringify(o,null,2)+`
|
|
655
|
+
`,{encoding:"utf8",mode:384}),t}function I(e){return Se("sh",["-lc",`command -v ${JSON.stringify(e)} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}function pe(){return I("agy")||A(R(h(),".gemini"))}function Ee(){return I("opencode")||A(R(h(),".config","opencode"))}function _e(){return I("codex")||A(process.env.CODEX_HOME??R(h(),".codex"))}function N(e={}){let t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=[];if(!(e.force===!0||pe()))o.push({agent:"agy",detected:!1,installed:!1,targets:[]});else try{let n=F({binary:t});o.push({agent:"agy",detected:!0,installed:!0,targets:[n]})}catch(n){o.push({agent:"agy",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}if(!(e.force===!0||Ee()))o.push({agent:"opencode",detected:!1,installed:!1,targets:[]});else try{let n=v({binary:t});o.push({agent:"opencode",detected:!0,installed:!0,targets:[n]})}catch(n){o.push({agent:"opencode",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}if(!(e.force===!0||_e()))o.push({agent:"codex",detected:!1,installed:!1,targets:[]});else try{let n=b({binary:t}),i=L({binary:t}),s=[n.configFile,i];n.preservedPrevious&&s.push(n.previousFile),o.push({agent:"codex",detected:!0,installed:!0,targets:s})}catch(n){o.push({agent:"codex",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}return o}function ye(e){console.log(""),console.log("ToolNet Memory AI Integrations"),console.log("=============================="),console.log("");for(let t of e){let o=t.agent==="agy"?"Agy / Antigravity":t.agent==="opencode"?"OpenCode":"Codex";if(!t.detected){console.log(`- ${o}: not detected`);continue}if(t.installed){console.log(`\u2713 ${o}: automatic memory enabled`);continue}console.log(`\u2717 ${o}: integration failed`),t.error&&console.log(` ${t.error}`)}console.log("")}async function Ce(){let e=process.argv.slice(2),t=e.includes("--all"),o=e.includes("--json"),r=N({force:t});if(o){console.log(JSON.stringify(r,null,2));return}ye(r)}var Te=process.argv[1]&&(process.argv[1].endsWith("auto-integrate.js")||process.argv[1].endsWith("auto-integrate.ts"));Te&&Ce().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exitCode=1});var x=G.join(Oe.homedir(),".config","toolnet-memory"),p=G.join(x,".env");function he(e){let t=new Map;for(let o of e.split(/\r?\n/)){let r=o.trim();if(!r||r.startsWith("#"))continue;let n=r.indexOf("=");n!==-1&&t.set(r.slice(0,n).trim(),r.slice(n+1).trim())}return t}function J(e){let t=e.get("MEMORY_STORAGE_PROVIDER")?.trim();return t==="r2"||t==="s3"||t==="local"||t==="huggingface"?t:e.get("R2_ACCOUNT_ID")&&e.get("R2_BUCKET")?"r2":e.get("S3_BUCKET")?"s3":e.get("HF_NAMESPACE")&&e.get("HF_BUCKET")?"huggingface":"r2"}function U(e){return e==="r2"?["R2_ACCOUNT_ID","R2_BUCKET","R2_ACCESS_KEY_ID","R2_SECRET_ACCESS_KEY"]:e==="s3"?["S3_BUCKET","S3_ACCESS_KEY_ID","S3_SECRET_ACCESS_KEY"]:e==="huggingface"?["HF_NAMESPACE","HF_BUCKET","HF_S3_ACCESS_KEY_ID","HF_S3_SECRET_ACCESS_KEY"]:[]}function y(e){return e==="r2"?"Cloudflare R2":e==="s3"?"S3-compatible storage":e==="huggingface"?"Hugging Face S3 (legacy)":"Local storage"}function B(e){S.mkdirSync(x,{recursive:!0,mode:448});let o=`# ==========================================================
|
|
183
656
|
# TOOLNET MEMORY
|
|
184
657
|
# ==========================================================
|
|
185
658
|
|
|
@@ -231,7 +704,7 @@ TOOLNET_SESSION_LEARNING=${e.get("TOOLNET_SESSION_LEARNING")??"1"}
|
|
|
231
704
|
TOOLNET_WORK_CONTINUITY=${e.get("TOOLNET_WORK_CONTINUITY")??"1"}
|
|
232
705
|
TOOLNET_SEMANTIC_CONTINUITY=${e.get("TOOLNET_SEMANTIC_CONTINUITY")??"1"}
|
|
233
706
|
TOOLNET_SMART_HANDOFF=${e.get("TOOLNET_SMART_HANDOFF")??"1"}
|
|
234
|
-
`;
|
|
235
|
-
`),t(o)},n=
|
|
236
|
-
`){r();return}if(
|
|
237
|
-
`),process.exit(130)),
|
|
707
|
+
`;S.writeFileSync(p,o,{encoding:"utf8",mode:384}),S.chmodSync(x,448),S.chmodSync(p,384)}function q(e,t=!0){let o=e.trim().toLowerCase();return o?o==="y"||o==="yes":t}async function Re(e){return g.isTTY?(E.write(e),new Promise(t=>{let o="",r=()=>{g.off("data",n),g.setRawMode?.(!1),g.pause(),E.write(`
|
|
708
|
+
`),t(o)},n=i=>{for(let s of i.toString("utf8")){if(s==="\r"||s===`
|
|
709
|
+
`){r();return}if(s===""&&(g.setRawMode?.(!1),E.write(`
|
|
710
|
+
`),process.exit(130)),s==="\x7F"){o=o.slice(0,-1);continue}o+=s}};g.resume(),g.setRawMode?.(!0),g.on("data",n)})):""}function M(){try{let t=N().filter(o=>o.installed);if(t.length>0){console.log(""),console.log("Automatic AI memory:");for(let o of t){let r=o.agent==="agy"?"Agy / Antigravity":o.agent==="opencode"?"OpenCode":"Codex";console.log(` \u2713 ${r}`)}}}catch{}}function d(e,t,o,r){o.trim()?e.set(t,o.trim()):!e.get(t)&&r!==void 0&&e.set(t,r)}async function Ie(e,t){console.log("Storage provider:"),console.log(" 1. Cloudflare R2 (recommended)"),console.log(" 2. S3 / S3-compatible"),console.log(" 3. Local"),console.log(" 4. Hugging Face S3 (legacy)"),console.log("");let o=t==="s3"?"2":t==="local"?"3":t==="huggingface"?"4":"1",n=(await e.question(`Choose [${o}]: `)).trim()||o;return n==="2"?"s3":n==="3"?"local":n==="4"?"huggingface":"r2"}async function Ne(){let e=S.existsSync(p),t=e?he(S.readFileSync(p,"utf8")):new Map,o=J(t),r=U(o),n=r.every(c=>!!t.get(c)?.trim());if(console.log(""),console.log("TOOLNET MEMORY SETUP"),console.log("===================="),console.log(""),console.log(`Config: ${p}`),console.log(""),!g.isTTY||!E.isTTY){e||(t.set("MEMORY_STORAGE_PROVIDER",o),B(t)),n?console.log(`\u2713 ${y(o)} already configured`):(console.log("Configuration pending."),console.log("Run:"),console.log(" toolnet-memory setup")),M();return}let i=Ae.createInterface({input:g,output:E});if(n){console.log(`\u2713 ${y(o)} already configured`),console.log("");let c=await i.question("Use existing storage configuration? (Y/n) [Y]: ");if(q(c)){i.close(),console.log(""),console.log(`\u2713 Existing ${y(o)} configuration kept`),console.log(""),M(),console.log("Next:"),console.log(" toolnet-memory doctor");return}console.log("")}o=await Ie(i,o),t.set("MEMORY_STORAGE_PROVIDER",o),console.log("");let s="",a="";if(o==="r2"){let c=await i.question(t.get("R2_ACCOUNT_ID")?`Cloudflare Account ID [${t.get("R2_ACCOUNT_ID")}]: `:"Cloudflare Account ID: "),u=await i.question(`R2 Bucket [${t.get("R2_BUCKET")||"toolnet-memory"}]: `),m=await i.question(t.get("R2_ACCESS_KEY_ID")?"R2 Access Key ID [configured]: ":"R2 Access Key ID: ");d(t,"R2_ACCOUNT_ID",c),d(t,"R2_BUCKET",u,"toolnet-memory"),d(t,"R2_ACCESS_KEY_ID",m),s="R2_SECRET_ACCESS_KEY",a=t.get(s)?"R2 Secret Access Key [configured]: ":"R2 Secret Access Key: "}else if(o==="s3"){let c=await i.question(t.get("S3_ENDPOINT")?`S3 Endpoint [${t.get("S3_ENDPOINT")}]: `:"S3 Endpoint [blank = AWS S3]: "),u=await i.question(`S3 Region [${t.get("S3_REGION")||"us-east-1"}]: `),m=await i.question(`S3 Bucket [${t.get("S3_BUCKET")||"toolnet-memory"}]: `),l=await i.question(t.get("S3_ACCESS_KEY_ID")?"S3 Access Key ID [configured]: ":"S3 Access Key ID: "),w=await i.question(`Force path-style? (y/N) [${t.get("S3_FORCE_PATH_STYLE")==="true"?"Y":"N"}]: `);c.trim()&&t.set("S3_ENDPOINT",c.trim()),d(t,"S3_REGION",u,"us-east-1"),d(t,"S3_BUCKET",m,"toolnet-memory"),d(t,"S3_ACCESS_KEY_ID",l),w.trim()?t.set("S3_FORCE_PATH_STYLE",q(w,!1)?"true":"false"):t.get("S3_FORCE_PATH_STYLE")||t.set("S3_FORCE_PATH_STYLE","false"),s="S3_SECRET_ACCESS_KEY",a=t.get(s)?"S3 Secret Access Key [configured]: ":"S3 Secret Access Key: "}else if(o==="huggingface"){let c=await i.question(t.get("HF_NAMESPACE")?`Hugging Face namespace [${t.get("HF_NAMESPACE")}]: `:"Hugging Face namespace: "),u=await i.question(`Bucket [${t.get("HF_BUCKET")||"toolnet-memory"}]: `),m=await i.question(t.get("HF_S3_ACCESS_KEY_ID")?"S3 Access Key ID [configured]: ":"S3 Access Key ID: ");d(t,"HF_NAMESPACE",c),d(t,"HF_BUCKET",u,"toolnet-memory"),d(t,"HF_S3_ACCESS_KEY_ID",m),s="HF_S3_SECRET_ACCESS_KEY",a=t.get(s)?"S3 Secret Access Key [configured]: ":"S3 Secret Access Key: "}if(i.close(),s){let c=await Re(a);c.trim()&&t.set(s,c.trim())}B(t),r=U(o);let f=r.filter(c=>!t.get(c)?.trim());if(console.log(""),console.log("\u2713 Configuration saved"),console.log(` ${p}`),console.log(""),f.length){console.log("Missing configuration:");for(let c of f)console.log(` - ${c}`);console.log(""),console.log("Run setup again:"),console.log(" toolnet-memory setup");return}console.log(`\u2713 ${y(o)} configuration complete`),console.log(""),M(),console.log("Next:"),console.log(" toolnet-memory doctor")}Ne().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exit(1)});
|