toolnet-memory 0.2.12 → 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/auto-integrate.js
CHANGED
|
@@ -1,25 +1,169 @@
|
|
|
1
|
-
import{existsSync as
|
|
2
|
-
`,{encoding:"utf8",mode:384}),t}import{existsSync as
|
|
3
|
-
## ToolNet Memory
|
|
1
|
+
import{existsSync as g}from"node:fs";import{homedir as h}from"node:os";import{join as x}from"node:path";import{spawnSync as ee}from"node:child_process";import{existsSync as _,mkdirSync as M,readFileSync as F,writeFileSync as P}from"node:fs";import{homedir as $}from"node:os";import{dirname as D,join as H}from"node:path";function L(o){return`'${o.replace(/'/g,"'\\''")}'`}function O(o={}){let t=o.hooksFile??H($(),".gemini","config","hooks.json");M(D(t),{recursive:!0});let e={};if(_(t))try{e=JSON.parse(F(t,"utf8"))}catch(a){throw new Error(`Invalid existing Agy hooks.json: ${a instanceof Error?a.message:String(a)}`)}let r=o.binary??"toolnet-memory",n=`${L(r)} session:agy-hook`;return e["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}]},P(t,JSON.stringify(e,null,2)+`
|
|
2
|
+
`,{encoding:"utf8",mode:384}),t}import{existsSync as B,mkdirSync as I,readFileSync as J,writeFileSync as T}from"node:fs";import{homedir as m}from"node:os";import{join as p}from"node:path";var C="<!-- TOOLNET_MEMORY_BOOTSTRAP_START -->",f="<!-- TOOLNET_MEMORY_BOOTSTRAP_END -->";function Y(){let o=p(m(),".config","opencode","AGENTS.md");I(p(m(),".config","opencode"),{recursive:!0});let t=`${C}
|
|
3
|
+
## ToolNet Memory
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
ToolNet Memory is available for projects that already contain a valid \`.toolnet/project.json\`.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
2. Treat its PROJECT RULES, MISSION, CURRENT OBJECTIVE, rationale, current phase/task, Definition of Done, blockers and next actions as project continuity context.
|
|
9
|
-
3. Do not restart work already marked completed unless repository evidence proves it must be revisited.
|
|
10
|
-
4. Verify current repository state before continuing old work.
|
|
11
|
-
5. If \`.toolnet/context/startup.md\` is missing, run \`toolnet-memory context:sync --project <project-root>\`, then read it.
|
|
12
|
-
6. Never invent missing rationale. If ToolNet says a reason was not explicitly recorded, inspect the implementation or ask rather than guessing.
|
|
7
|
+
Rules:
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
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
16
|
|
|
17
|
-
|
|
18
|
-
`,{encoding:"utf8",mode:384}),o}function I(o={}){let t=o.directory??g(p(),".config","opencode","plugins");b(t,{recursive:!0}),W();let e=g(t,"toolnet-memory.js"),r=o.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",n=`// Generated by ToolNet Memory.
|
|
19
|
-
// OpenCode session capture + continuity bootstrap.
|
|
17
|
+
${f}`,e=B(o)?J(o,"utf8"):"",r=e.indexOf(C),n=e.indexOf(f);return r>=0&&n>=r?e=e.slice(0,r)+t+e.slice(n+f.length):(e=e.trimEnd(),e&&(e+=`
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
`),e+=t),T(o,e.trimEnd()+`
|
|
20
|
+
`,{encoding:"utf8",mode:384}),o}function N(o={}){let t=o.directory??p(m(),".config","opencode","plugins");I(t,{recursive:!0}),Y();let e=p(t,"toolnet-memory.js"),r=o.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
|
+
}
|
|
144
|
+
|
|
145
|
+
const parent =
|
|
146
|
+
path.dirname(
|
|
147
|
+
current
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
parent === current
|
|
152
|
+
) {
|
|
153
|
+
break
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
current = parent
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getSessionId(
|
|
163
|
+
event
|
|
164
|
+
) {
|
|
165
|
+
const p =
|
|
166
|
+
event?.properties ?? {}
|
|
23
167
|
|
|
24
168
|
const candidates = [
|
|
25
169
|
p.sessionID,
|
|
@@ -28,155 +172,484 @@ 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
|
-
|
|
271
|
+
if (!projectRoot) {
|
|
272
|
+
writeStatus({
|
|
273
|
+
active: false,
|
|
274
|
+
reason:
|
|
275
|
+
"no-toolnet-project",
|
|
276
|
+
directory,
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
return {}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
writeStatus({
|
|
283
|
+
active: true,
|
|
284
|
+
projectRoot,
|
|
285
|
+
state:
|
|
286
|
+
"plugin-loaded",
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
let lastSessionId =
|
|
290
|
+
null
|
|
83
291
|
|
|
84
|
-
|
|
292
|
+
let syncChain =
|
|
293
|
+
Promise.resolve()
|
|
85
294
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
295
|
+
let contextCache = {
|
|
296
|
+
value: "",
|
|
297
|
+
expiresAt: 0,
|
|
89
298
|
}
|
|
90
|
-
}
|
|
91
299
|
|
|
92
|
-
|
|
93
|
-
|
|
300
|
+
async function readContext() {
|
|
301
|
+
const now =
|
|
302
|
+
Date.now()
|
|
303
|
+
|
|
304
|
+
if (
|
|
305
|
+
contextCache.value &&
|
|
306
|
+
now <
|
|
307
|
+
contextCache.expiresAt
|
|
308
|
+
) {
|
|
309
|
+
return (
|
|
310
|
+
contextCache.value
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
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()
|
|
355
|
+
|
|
356
|
+
contextCache = {
|
|
357
|
+
value: text,
|
|
358
|
+
expiresAt:
|
|
359
|
+
now +
|
|
360
|
+
CONTEXT_CACHE_MS,
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return text
|
|
364
|
+
} catch {
|
|
365
|
+
return ""
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function syncNow(
|
|
370
|
+
sessionId,
|
|
371
|
+
flag,
|
|
372
|
+
reason
|
|
373
|
+
) {
|
|
374
|
+
if (!sessionId) {
|
|
375
|
+
return
|
|
376
|
+
}
|
|
94
377
|
|
|
95
|
-
try {
|
|
96
378
|
const args = [
|
|
97
|
-
|
|
379
|
+
TOOLNET_BINARY,
|
|
98
380
|
"session:opencode-sync",
|
|
99
381
|
sessionId,
|
|
100
382
|
"--project",
|
|
101
|
-
|
|
383
|
+
projectRoot,
|
|
102
384
|
]
|
|
103
385
|
|
|
104
386
|
if (flag) {
|
|
105
|
-
args.push(
|
|
387
|
+
args.push(
|
|
388
|
+
flag
|
|
389
|
+
)
|
|
106
390
|
}
|
|
107
391
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
})
|
|
392
|
+
try {
|
|
393
|
+
await runWithTimeout(
|
|
394
|
+
args
|
|
395
|
+
)
|
|
113
396
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
118
423
|
}
|
|
119
|
-
} catch {
|
|
120
|
-
// Memory capture must never break OpenCode.
|
|
121
424
|
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/*
|
|
125
|
-
* Pull the latest project brief from ToolNet remote storage
|
|
126
|
-
* before the new OpenCode session starts doing work.
|
|
127
|
-
*/
|
|
128
|
-
await refreshContext()
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
event: async ({ event }) => {
|
|
132
|
-
const sessionId =
|
|
133
|
-
getSessionId(event)
|
|
134
425
|
|
|
426
|
+
function queueSync(
|
|
427
|
+
sessionId,
|
|
428
|
+
flag,
|
|
429
|
+
reason
|
|
430
|
+
) {
|
|
135
431
|
if (!sessionId) {
|
|
136
|
-
return
|
|
432
|
+
return syncChain
|
|
137
433
|
}
|
|
138
434
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return
|
|
142
|
-
}
|
|
435
|
+
lastSessionId =
|
|
436
|
+
sessionId
|
|
143
437
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
438
|
+
syncChain =
|
|
439
|
+
syncChain
|
|
440
|
+
.catch(
|
|
441
|
+
() =>
|
|
442
|
+
undefined
|
|
443
|
+
)
|
|
444
|
+
.then(
|
|
445
|
+
() =>
|
|
446
|
+
syncNow(
|
|
447
|
+
sessionId,
|
|
448
|
+
flag,
|
|
449
|
+
reason
|
|
450
|
+
)
|
|
451
|
+
)
|
|
148
452
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
},
|
|
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
|
-
`;return C(e,n,{encoding:"utf8",mode:384}),e}import{existsSync as Y,mkdirSync as N,readFileSync as X,writeFileSync as T}from"node:fs";import{homedir as E}from"node:os";import{dirname as w,join as m}from"node:path";function q(o){let t=[],e=/"((?:\\.|[^"\\])*)"|'([^']*)'/g,r;for(;r=e.exec(o);){let n=r[1]??r[2]??"";try{t.push(r[1]!==void 0?JSON.parse(`"${n}"`):n)}catch{t.push(n)}}return t}function j(o={}){let t=o.configFile??m(process.env.CODEX_HOME??m(E(),".codex"),"config.toml"),e=o.previousFile??m(E(),".config","toolnet-memory","codex-notify-previous.json");N(w(t),{recursive:!0}),N(w(e),{recursive:!0});let r=Y(t)?X(t,"utf8"):"",n=o.binary??"toolnet-memory",a=`notify = [${JSON.stringify(n)}, "session:codex-notify"]`,s=r.split(`
|
|
648
|
+
`;return T(e,n.trimStart(),{encoding:"utf8",mode:384}),e}import{existsSync as X,mkdirSync as w,readFileSync as q,writeFileSync as k}from"node:fs";import{homedir as E}from"node:os";import{dirname as j,join as y}from"node:path";function U(o){let t=[],e=/"((?:\\.|[^"\\])*)"|'([^']*)'/g,r;for(;r=e.exec(o);){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(o={}){let t=o.configFile??y(process.env.CODEX_HOME??y(E(),".codex"),"config.toml"),e=o.previousFile??y(E(),".config","toolnet-memory","codex-notify-previous.json");w(j(t),{recursive:!0}),w(j(e),{recursive:!0});let r=X(t)?q(t,"utf8"):"",n=o.binary??"toolnet-memory",a=`notify = [${JSON.stringify(n)}, "session:codex-notify"]`,s=r.split(`
|
|
176
649
|
`),i=s.findIndex(c=>/^\s*\[/.test(c));i<0&&(i=s.length);let u=-1,d=-1;for(let c=0;c<i;c+=1)if(/^\s*notify\s*=/.test(s[c])){if(u=c,d=c,s[c].includes("[")&&!s[c].includes("]"))for(;d+1<i&&(d+=1,!s[d].includes("]")););break}let l=[];if(u>=0){let c=s.slice(u,d+1).join(`
|
|
177
|
-
`);l=
|
|
650
|
+
`);l=U(c),s.splice(u,d-u+1,a)}else i=s.findIndex(c=>/^\s*\[/.test(c)),i<0&&(i=s.length),s.splice(i,0,a);let v=l.length>=2&&l[l.length-1]==="session:codex-notify";return l.length>0&&!v&&k(e,JSON.stringify(l,null,2)+`
|
|
178
651
|
`,{encoding:"utf8",mode:384}),r=s.join(`
|
|
179
652
|
`),r.endsWith(`
|
|
180
653
|
`)||(r+=`
|
|
181
|
-
`),
|
|
182
|
-
`,{encoding:"utf8",mode:384}),t}function
|
|
654
|
+
`),k(t,r,{encoding:"utf8",mode:384}),{configFile:t,previousFile:e,preservedPrevious:l.length>0&&!v}}import{existsSync as W,mkdirSync as G,readFileSync as K,writeFileSync as V}from"node:fs";import{homedir as z}from"node:os";import{dirname as Q,join as A}from"node:path";function Z(o){return`'${o.replace(/'/g,"'\\''")}'`}function R(o={}){let t=o.hooksFile??A(process.env.CODEX_HOME??A(z(),".codex"),"hooks.json");G(Q(t),{recursive:!0});let e={};if(W(t))try{e=JSON.parse(K(t,"utf8"))}catch(i){throw new Error(`Invalid existing Codex hooks.json: ${i instanceof Error?i.message:String(i)}`)}let r=e.hooks&&typeof e.hooks=="object"&&!Array.isArray(e.hooks)?e.hooks:{};e.hooks=r;let a=(Array.isArray(r.SessionStart)?r.SessionStart:[]).filter(i=>{try{return!JSON.stringify(i).includes("session:codex-context")}catch{return!0}}),s=o.binary??"toolnet-memory";return a.push({matcher:"startup|resume|clear|compact",hooks:[{type:"command",command:`${Z(s)} session:codex-context`,timeout:15,additionalContextLimit:1e3,statusMessage:"Loading ToolNet project continuity"}]}),r.SessionStart=a,V(t,JSON.stringify(e,null,2)+`
|
|
655
|
+
`,{encoding:"utf8",mode:384}),t}function S(o){return ee("sh",["-lc",`command -v ${JSON.stringify(o)} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}function te(){return S("agy")||g(x(h(),".gemini"))}function oe(){return S("opencode")||g(x(h(),".config","opencode"))}function ne(){return S("codex")||g(process.env.CODEX_HOME??x(h(),".codex"))}function re(o={}){let t=o.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",e=[];if(!(o.force===!0||te()))e.push({agent:"agy",detected:!1,installed:!1,targets:[]});else try{let n=O({binary:t});e.push({agent:"agy",detected:!0,installed:!0,targets:[n]})}catch(n){e.push({agent:"agy",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}if(!(o.force===!0||oe()))e.push({agent:"opencode",detected:!1,installed:!1,targets:[]});else try{let n=N({binary:t});e.push({agent:"opencode",detected:!0,installed:!0,targets:[n]})}catch(n){e.push({agent:"opencode",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}if(!(o.force===!0||ne()))e.push({agent:"codex",detected:!1,installed:!1,targets:[]});else try{let n=b({binary:t}),a=R({binary:t}),s=[n.configFile,a];n.preservedPrevious&&s.push(n.previousFile),e.push({agent:"codex",detected:!0,installed:!0,targets:s})}catch(n){e.push({agent:"codex",detected:!0,installed:!1,targets:[],error:n instanceof Error?n.message:String(n)})}return e}function se(o){console.log(""),console.log("ToolNet Memory AI Integrations"),console.log("=============================="),console.log("");for(let t of o){let e=t.agent==="agy"?"Agy / Antigravity":t.agent==="opencode"?"OpenCode":"Codex";if(!t.detected){console.log(`- ${e}: not detected`);continue}if(t.installed){console.log(`\u2713 ${e}: automatic memory enabled`);continue}console.log(`\u2717 ${e}: integration failed`),t.error&&console.log(` ${t.error}`)}console.log("")}async function ie(){let o=process.argv.slice(2),t=o.includes("--all"),e=o.includes("--json"),r=re({force:t});if(e){console.log(JSON.stringify(r,null,2));return}se(r)}var ce=process.argv[1]&&(process.argv[1].endsWith("auto-integrate.js")||process.argv[1].endsWith("auto-integrate.ts"));ce&&ie().catch(o=>{console.error(o instanceof Error?o.message:String(o)),process.exitCode=1});export{re as installAutoIntegrations};
|