toolnet-memory 0.3.3 → 0.3.5

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.
@@ -1,6 +1,6 @@
1
- import{existsSync as Ae}from"node:fs";import{homedir as Te}from"node:os";import{join as Me}from"node:path";import{spawnSync as Fe}from"node:child_process";import{homedir as je}from"node:os";import{join as p}from"node:path";function P(e={}){return p(e.home??je(),".gemini")}function k(e={}){return p(P(e),"config")}function v(e={}){return p(k(e),"mcp_config.json")}function C(e={}){return p(k(e),"hooks.json")}function _(e={}){return p(P(e),"antigravity-cli")}function $(e="toolnet-memory",n={}){return p(_(n),"plugins",e)}function J(e={}){return[_(e),k(e)]}import{homedir as Ee}from"node:os";import{join as y}from"node:path";function g(e={}){let n=e.xdgConfigHome??process.env.XDG_CONFIG_HOME?.trim();return n?y(n,"opencode"):y(e.home??Ee(),".config","opencode")}function D(e={}){return y(g(e),"opencode.json")}function H(e={}){return y(g(e),"plugins")}function L(e={}){return y(g(e),"AGENTS.md")}import{homedir as B}from"node:os";import{join as N}from"node:path";function I(e={}){return N(e.home??B(),".claude")}function Y(e={}){return N(I(e),"settings.json")}function U(e={}){return N(e.home??B(),".claude.json")}function Re(e){return Fe("sh",["-lc",`command -v ${JSON.stringify(e)} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}function S(e){let n=e.commandExists(e.command),t=e.configPaths.filter(s=>Ae(s)),o=t.length>0,i=[];n&&i.push(`command:${e.command}`);for(let s of t)i.push(`config:${s}`);return{agent:e.agent,detected:n||o,commandDetected:n,configDetected:o,evidence:i}}function q(e={}){let n=e.home??Te(),t=e.commandExists??Re,o=e.codexHome??process.env.CODEX_HOME??Me(n,".codex");return[S({agent:"agy",command:"agy",commandExists:t,configPaths:J({home:n})}),S({agent:"opencode",command:"opencode",commandExists:t,configPaths:[g({home:n,xdgConfigHome:e.xdgConfigHome})]}),S({agent:"claude",command:"claude",commandExists:t,configPaths:[I({home:n})]}),S({agent:"codex",command:"codex",commandExists:t,configPaths:[o]})]}import{existsSync as w,mkdirSync as z,readFileSync as Q,renameSync as Ke,writeFileSync as ze}from"node:fs";import{dirname as Qe,join as b}from"node:path";import{existsSync as Pe,mkdirSync as _e,readFileSync as $e,renameSync as Je,rmSync as De,writeFileSync as He}from"node:fs";import{dirname as Le}from"node:path";function Be(e){return`'${e.replace(/'/g,"'\\''")}'`}function G(e={}){let n=e.hooksFile??C();_e(Le(n),{recursive:!0,mode:448});let t={};if(Pe(n)){let r;try{r=JSON.parse($e(n,"utf8"))}catch(c){throw new Error(`Invalid existing Agy hooks.json: ${c instanceof Error?c.message:String(c)}`)}if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("Invalid existing Agy hooks.json: root must be a JSON object.");t=r}let o=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",i=`${Be(o)} session:agy-hook`;t["toolnet-memory"]={enabled:!0,PreToolUse:[{matcher:"view_file|list_dir|find_by_name|grep_search|run_command",hooks:[{type:"command",command:`${i} pre-tool`,timeout:5}]}],PreInvocation:[{type:"command",command:`${i} pre`,timeout:15}],PostInvocation:[{type:"command",command:`${i} post`,timeout:15}],Stop:[{type:"command",command:`${i} stop`,timeout:30}]};let s=`${n}.tmp-${process.pid}-${Date.now()}`;try{He(s,JSON.stringify(t,null,2)+`
2
- `,{encoding:"utf8",mode:384}),Je(s,n)}finally{De(s,{force:!0})}return n}import{existsSync as Ye,mkdirSync as Ue,readFileSync as qe,renameSync as Ge,writeFileSync as We}from"node:fs";import{dirname as Xe}from"node:path";function h(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Ve(e,n){Ue(Xe(e),{recursive:!0,mode:448});let t=`${e}.tmp-${process.pid}-${Date.now()}`;We(t,`${JSON.stringify(n,null,2)}
3
- `,{encoding:"utf8",mode:384}),Ge(t,e)}function W(e){if(!Ye(e))return{};let n=qe(e,"utf8").trim();if(!n)return{};let t;try{t=JSON.parse(n)}catch(o){throw new Error(`Invalid existing Agy MCP config: ${o instanceof Error?o.message:String(o)}`)}if(!h(t))throw new Error("Invalid existing Agy MCP config: root must be a JSON object.");return t}function X(e,n){return h(e)?e.command===n&&Array.isArray(e.args)&&e.args.length===1&&e.args[0]==="mcp":!1}function V(e={}){let n=e.configFile??v(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",i=W(n),s=i.mcpServers;if(s!==void 0&&!h(s))throw new Error("Invalid existing Agy MCP config: mcpServers must be an object.");let r=h(s)?{...s}:{},c=r[o];if(X(c,t))return{installed:!0,changed:!1,configFile:n,serverName:o,command:t,args:["mcp"]};r[o]={command:t,args:["mcp"]};let l={...i,mcpServers:r};Ve(n,l);let a=W(n).mcpServers;if(!h(a)||!X(a[o],t))throw new Error("Agy MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:n,serverName:o,command:t,args:["mcp"]}}var Ze=`# ToolNet Memory Continuity
1
+ import{existsSync as je}from"node:fs";import{homedir as Ae}from"node:os";import{join as Me}from"node:path";import{spawnSync as Fe}from"node:child_process";import{homedir as Te}from"node:os";import{join as p}from"node:path";function _(e={}){return p(e.home??Te(),".gemini")}function I(e={}){return p(_(e),"config")}function x(e={}){return p(I(e),"mcp_config.json")}function C(e={}){return p(I(e),"hooks.json")}function P(e={}){return p(_(e),"antigravity-cli")}function J(e="toolnet-memory",n={}){return p(P(n),"plugins",e)}function D(e={}){return[P(e),I(e)]}import{homedir as Ee}from"node:os";import{join as y}from"node:path";function m(e={}){let n=e.xdgConfigHome??process.env.XDG_CONFIG_HOME?.trim();return n?y(n,"opencode"):y(e.home??Ee(),".config","opencode")}function $(e={}){return y(m(e),"opencode.json")}function L(e={}){return y(m(e),"plugins")}function H(e={}){return y(m(e),"AGENTS.md")}import{homedir as B}from"node:os";import{join as k}from"node:path";function N(e={}){return k(e.home??B(),".claude")}function U(e={}){return k(N(e),"settings.json")}function Y(e={}){return k(e.home??B(),".claude.json")}function Re(e){return Fe("sh",["-lc",`command -v ${JSON.stringify(e)} >/dev/null 2>&1`],{stdio:"ignore"}).status===0}function S(e){let n=e.commandExists(e.command),t=e.configPaths.filter(s=>je(s)),o=t.length>0,i=[];n&&i.push(`command:${e.command}`);for(let s of t)i.push(`config:${s}`);return{agent:e.agent,detected:n||o,commandDetected:n,configDetected:o,evidence:i}}function q(e={}){let n=e.home??Ae(),t=e.commandExists??Re,o=e.codexHome??process.env.CODEX_HOME??Me(n,".codex");return[S({agent:"agy",command:"agy",commandExists:t,configPaths:D({home:n})}),S({agent:"opencode",command:"opencode",commandExists:t,configPaths:[m({home:n,xdgConfigHome:e.xdgConfigHome})]}),S({agent:"claude",command:"claude",commandExists:t,configPaths:[N({home:n})]}),S({agent:"codex",command:"codex",commandExists:t,configPaths:[o]})]}import{existsSync as w,mkdirSync as z,readFileSync as Q,renameSync as Ve,writeFileSync as ze}from"node:fs";import{dirname as Qe,join as b}from"node:path";import{existsSync as _e,mkdirSync as Pe,readFileSync as Je,renameSync as De,rmSync as $e,writeFileSync as Le}from"node:fs";import{dirname as He}from"node:path";function Be(e){return`'${e.replace(/'/g,"'\\''")}'`}function W(e={}){let n=e.hooksFile??C();Pe(He(n),{recursive:!0,mode:448});let t={};if(_e(n)){let r;try{r=JSON.parse(Je(n,"utf8"))}catch(c){throw new Error(`Invalid existing Agy hooks.json: ${c instanceof Error?c.message:String(c)}`)}if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("Invalid existing Agy hooks.json: root must be a JSON object.");t=r}let o=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",i=`${Be(o)} session:agy-hook`;t["toolnet-memory"]={enabled:!0,PreToolUse:[{matcher:"view_file|list_dir|find_by_name|grep_search|run_command",hooks:[{type:"command",command:`${i} pre-tool`,timeout:5}]}],PreInvocation:[{type:"command",command:`${i} pre`,timeout:15}],PostInvocation:[{type:"command",command:`${i} post`,timeout:15}],Stop:[{type:"command",command:`${i} stop`,timeout:30}]};let s=`${n}.tmp-${process.pid}-${Date.now()}`;try{Le(s,JSON.stringify(t,null,2)+`
2
+ `,{encoding:"utf8",mode:384}),De(s,n)}finally{$e(s,{force:!0})}return n}import{existsSync as Ue,mkdirSync as Ye,readFileSync as qe,renameSync as We,writeFileSync as Ge}from"node:fs";import{dirname as Xe}from"node:path";function h(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Ke(e,n){Ye(Xe(e),{recursive:!0,mode:448});let t=`${e}.tmp-${process.pid}-${Date.now()}`;Ge(t,`${JSON.stringify(n,null,2)}
3
+ `,{encoding:"utf8",mode:384}),We(t,e)}function G(e){if(!Ue(e))return{};let n=qe(e,"utf8").trim();if(!n)return{};let t;try{t=JSON.parse(n)}catch(o){throw new Error(`Invalid existing Agy MCP config: ${o instanceof Error?o.message:String(o)}`)}if(!h(t))throw new Error("Invalid existing Agy MCP config: root must be a JSON object.");return t}function X(e,n){return h(e)?e.command===n&&Array.isArray(e.args)&&e.args.length===1&&e.args[0]==="mcp":!1}function K(e={}){let n=e.configFile??x(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",i=G(n),s=i.mcpServers;if(s!==void 0&&!h(s))throw new Error("Invalid existing Agy MCP config: mcpServers must be an object.");let r=h(s)?{...s}:{},c=r[o];if(X(c,t))return{installed:!0,changed:!1,configFile:n,serverName:o,command:t,args:["mcp"]};r[o]={command:t,args:["mcp"]};let l={...i,mcpServers:r};Ke(n,l);let a=G(n).mcpServers;if(!h(a)||!X(a[o],t))throw new Error("Agy MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:n,serverName:o,command:t,args:["mcp"]}}var Ze=`# ToolNet Memory Continuity
4
4
 
5
5
  ToolNet Memory is the authoritative continuity layer for previous project work.
6
6
 
@@ -41,11 +41,11 @@ Use \`mode="ai"\` only when continuity is ambiguous or requires synthesis.
41
41
  Current repository evidence overrides stale memory after ToolNet has restored the working context.
42
42
 
43
43
  Do not ask the user to repeat context already available through ToolNet Memory.
44
- `;function Z(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function j(e,n){z(Qe(e),{recursive:!0});let t=`${e}.tmp-${process.pid}-${Date.now()}`;ze(t,n,{encoding:"utf8",mode:384}),Ke(t,e)}function K(e,n){w(e)&&Q(e,"utf8")===n||j(e,n)}function ee(e){if(!w(e))return{};let n=Q(e,"utf8").trim();if(!n)return{};let t;try{t=JSON.parse(n)}catch(o){throw new Error(`Invalid legacy Antigravity config ${e}: ${o instanceof Error?o.message:String(o)}`)}if(!Z(t))throw new Error(`Invalid legacy Antigravity config ${e}: root must be object`);return t}function et(e,n){if(!w(e))return!1;let t=ee(e);if(!Z(t.mcpServers)||!Object.prototype.hasOwnProperty.call(t.mcpServers,n))return!1;let o={...t.mcpServers};return delete o[n],j(e,`${JSON.stringify({...t,mcpServers:o},null,2)}
45
- `),!0}function tt(e){if(!w(e))return!1;let n=ee(e);if(!Object.prototype.hasOwnProperty.call(n,"toolnet-memory"))return!1;let t={...n};return delete t["toolnet-memory"],j(e,`${JSON.stringify(t,null,2)}
46
- `),!0}function te(e={}){let n=e.pluginName??"toolnet-memory",t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.pluginRoot??$(n),i=b(o,"plugin.json"),s=b(o,"mcp_config.json"),r=b(o,"hooks.json"),c=b(o,"rules","toolnet-memory-continuity.md");z(o,{recursive:!0,mode:448}),K(i,`${JSON.stringify({$schema:"https://antigravity.google/schemas/v1/plugin.json",name:n,description:"Persistent project continuity and memory for Antigravity coding sessions."},null,2)}
47
- `),V({configFile:s,binary:t,serverName:"toolnet-memory"}),G({hooksFile:r,binary:t}),K(c,`${Ze.trim()}
48
- `);let l=e.legacyMcpFile??v(),u=e.legacyHooksFile??C(),a=[];return l!==s&&et(l,"toolnet-memory")&&a.push(l),u!==r&&tt(u)&&a.push(u),{installed:!0,pluginRoot:o,files:[i,s,r,c],migratedLegacy:a}}import{existsSync as ot,mkdirSync as re,readFileSync as rt,writeFileSync as se}from"node:fs";import{join as st}from"node:path";var nt="memory_agent_ask";function ne(){return`
44
+ `;function Z(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function T(e,n){z(Qe(e),{recursive:!0});let t=`${e}.tmp-${process.pid}-${Date.now()}`;ze(t,n,{encoding:"utf8",mode:384}),Ve(t,e)}function V(e,n){w(e)&&Q(e,"utf8")===n||T(e,n)}function ee(e){if(!w(e))return{};let n=Q(e,"utf8").trim();if(!n)return{};let t;try{t=JSON.parse(n)}catch(o){throw new Error(`Invalid legacy Antigravity config ${e}: ${o instanceof Error?o.message:String(o)}`)}if(!Z(t))throw new Error(`Invalid legacy Antigravity config ${e}: root must be object`);return t}function et(e,n){if(!w(e))return!1;let t=ee(e);if(!Z(t.mcpServers)||!Object.prototype.hasOwnProperty.call(t.mcpServers,n))return!1;let o={...t.mcpServers};return delete o[n],T(e,`${JSON.stringify({...t,mcpServers:o},null,2)}
45
+ `),!0}function tt(e){if(!w(e))return!1;let n=ee(e);if(!Object.prototype.hasOwnProperty.call(n,"toolnet-memory"))return!1;let t={...n};return delete t["toolnet-memory"],T(e,`${JSON.stringify(t,null,2)}
46
+ `),!0}function te(e={}){let n=e.pluginName??"toolnet-memory",t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.pluginRoot??J(n),i=b(o,"plugin.json"),s=b(o,"mcp_config.json"),r=b(o,"hooks.json"),c=b(o,"rules","toolnet-memory-continuity.md");z(o,{recursive:!0,mode:448}),V(i,`${JSON.stringify({$schema:"https://antigravity.google/schemas/v1/plugin.json",name:n,description:"Persistent project continuity and memory for Antigravity coding sessions."},null,2)}
47
+ `),K({configFile:s,binary:t,serverName:"toolnet-memory"}),W({hooksFile:r,binary:t}),V(c,`${Ze.trim()}
48
+ `);let l=e.legacyMcpFile??x(),u=e.legacyHooksFile??C(),a=[];return l!==s&&et(l,"toolnet-memory")&&a.push(l),u!==r&&tt(u)&&a.push(u),{installed:!0,pluginRoot:o,files:[i,s,r,c],migratedLegacy:a}}import{existsSync as ot,mkdirSync as re,readFileSync as rt,writeFileSync as se}from"node:fs";import{join as st}from"node:path";var nt="memory_agent_ask";function ne(){return`
49
49
  [TOOLNET MEMORY AGENT]
50
50
 
51
51
  Tool available:
@@ -96,7 +96,7 @@ Rules:
96
96
  - After receiving the ToolNet answer, continue the task
97
97
  instead of asking the user to repeat known context.
98
98
  - If ToolNet says information is not recorded, say so.
99
- `.trim()}var oe="<!-- TOOLNET_MEMORY_BOOTSTRAP_START -->",E="<!-- TOOLNET_MEMORY_BOOTSTRAP_END -->";function it(){let e=L();re(g(),{recursive:!0});let n=`${oe}
99
+ `.trim()}var oe="<!-- TOOLNET_MEMORY_BOOTSTRAP_START -->",E="<!-- TOOLNET_MEMORY_BOOTSTRAP_END -->";function it(){let e=H();re(m(),{recursive:!0});let n=`${oe}
100
100
  ## ToolNet Memory
101
101
 
102
102
  ToolNet Memory is available for projects that already contain a valid \`.toolnet/project.json\`.
@@ -117,7 +117,7 @@ ${ne()}
117
117
  ${E}`,t=ot(e)?rt(e,"utf8"):"",o=t.indexOf(oe),i=t.indexOf(E);return o>=0&&i>=o?t=t.slice(0,o)+n+t.slice(i+E.length):(t=t.trimEnd(),t&&(t+=`
118
118
 
119
119
  `),t+=n),se(e,t.trimEnd()+`
120
- `,{encoding:"utf8",mode:384}),e}function ie(e={}){let n=e.directory??H();re(n,{recursive:!0}),it();let t=st(n,"toolnet-memory.js"),o=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",i=`
120
+ `,{encoding:"utf8",mode:384}),e}function ie(e={}){let n=e.directory??L();re(n,{recursive:!0}),it();let t=st(n,"toolnet-memory.js"),o=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",i=`
121
121
  // Generated by ToolNet Memory.
122
122
  // OpenCode integration:
123
123
  // - project gated
@@ -137,22 +137,52 @@ const CONTEXT_MAX_TOKENS = 700
137
137
 
138
138
  const CONTEXT_CACHE_MS = 5000
139
139
 
140
- const PERIODIC_SYNC_MS = 30000
140
+ const LOCAL_CAPTURE_MS = 15000
141
141
 
142
- const SYNC_TIMEOUT_MS = 15000
142
+ const REMOTE_SYNC_MS = 60000
143
143
 
144
- const STATUS_FILE = path.join(
144
+ const EVENT_CAPTURE_DEBOUNCE_MS = 1200
145
+
146
+ const CAPTURE_TIMEOUT_MS = 20000
147
+
148
+ const REMOTE_TIMEOUT_MS = 120000
149
+
150
+ const GLOBAL_STATUS_FILE = path.join(
145
151
  os.homedir(),
146
152
  ".config",
147
153
  "toolnet-memory",
148
154
  "opencode-sync-status.json"
149
155
  )
150
156
 
157
+ function projectStatusFile(
158
+ data
159
+ ) {
160
+ if (
161
+ typeof data?.projectRoot ===
162
+ "string" &&
163
+ data.projectRoot.length > 0
164
+ ) {
165
+ return path.join(
166
+ data.projectRoot,
167
+ ".toolnet",
168
+ "runtime",
169
+ "opencode-status.json"
170
+ )
171
+ }
172
+
173
+ return GLOBAL_STATUS_FILE
174
+ }
175
+
151
176
  function writeStatus(data) {
152
177
  try {
178
+ const statusFile =
179
+ projectStatusFile(
180
+ data
181
+ )
182
+
153
183
  fs.mkdirSync(
154
184
  path.dirname(
155
- STATUS_FILE
185
+ statusFile
156
186
  ),
157
187
  {
158
188
  recursive: true,
@@ -160,7 +190,7 @@ function writeStatus(data) {
160
190
  )
161
191
 
162
192
  fs.writeFileSync(
163
- STATUS_FILE,
193
+ statusFile,
164
194
  JSON.stringify(
165
195
  {
166
196
  timestamp:
@@ -287,7 +317,7 @@ async function runWithTimeout(
287
317
  {
288
318
  stdout = "ignore",
289
319
  timeout =
290
- SYNC_TIMEOUT_MS,
320
+ REMOTE_TIMEOUT_MS,
291
321
  } = {}
292
322
  ) {
293
323
  const child =
@@ -389,14 +419,67 @@ export const ToolNetMemoryPlugin =
389
419
  let lastSessionId =
390
420
  null
391
421
 
392
- let syncChain =
422
+ /*
423
+ * Two independent lanes:
424
+ *
425
+ * captureChain:
426
+ * OpenCode DB -> local fsync WAL -> current work
427
+ *
428
+ * remoteInFlight:
429
+ * WAL -> Hugging Face / S3
430
+ *
431
+ * A slow remote backend must never block local capture.
432
+ */
433
+ let captureChain =
393
434
  Promise.resolve()
394
435
 
436
+ let remoteInFlight =
437
+ null
438
+
439
+ let debounceTimer =
440
+ null
441
+
395
442
  let contextCache = {
396
443
  value: "",
397
444
  expiresAt: 0,
398
445
  }
399
446
 
447
+ /*
448
+ * Compact startup context is injected once
449
+ * per native OpenCode session.
450
+ *
451
+ * Deep history is retrieved through
452
+ * memory_agent_ask / MCP on demand.
453
+ */
454
+ const injectedSessions =
455
+ new Set()
456
+
457
+ function contextSessionKey(
458
+ input
459
+ ) {
460
+ const candidates = [
461
+ input?.sessionID,
462
+ input?.sessionId,
463
+ input?.session?.id,
464
+ input?.info?.id,
465
+ lastSessionId,
466
+ ]
467
+
468
+ const sessionId =
469
+ candidates.find(
470
+ value =>
471
+ typeof value ===
472
+ "string" &&
473
+ value.length > 0
474
+ )
475
+
476
+ return sessionId
477
+ ? "session:" +
478
+ sessionId
479
+ : "project:" +
480
+ projectRoot
481
+ }
482
+
400
483
  async function readContext() {
401
484
  const now =
402
485
  Date.now()
@@ -468,8 +551,13 @@ export const ToolNetMemoryPlugin =
468
551
 
469
552
  async function syncNow(
470
553
  sessionId,
471
- flag,
472
- reason
554
+ {
555
+ flag = null,
556
+ reason = "unknown",
557
+ localOnly = false,
558
+ timeout =
559
+ REMOTE_TIMEOUT_MS,
560
+ } = {}
473
561
  ) {
474
562
  if (!sessionId) {
475
563
  return
@@ -489,9 +577,18 @@ export const ToolNetMemoryPlugin =
489
577
  )
490
578
  }
491
579
 
580
+ if (localOnly) {
581
+ args.push(
582
+ "--local-only"
583
+ )
584
+ }
585
+
492
586
  try {
493
587
  await runWithTimeout(
494
- args
588
+ args,
589
+ {
590
+ timeout,
591
+ }
495
592
  )
496
593
 
497
594
  writeStatus({
@@ -499,8 +596,14 @@ export const ToolNetMemoryPlugin =
499
596
  projectRoot,
500
597
  sessionId,
501
598
  reason,
599
+ mode:
600
+ localOnly
601
+ ? "local"
602
+ : "remote",
502
603
  state:
503
- "sync-success",
604
+ localOnly
605
+ ? "capture-success"
606
+ : "sync-success",
504
607
  })
505
608
  } catch (error) {
506
609
  writeStatus({
@@ -508,8 +611,14 @@ export const ToolNetMemoryPlugin =
508
611
  projectRoot,
509
612
  sessionId,
510
613
  reason,
614
+ mode:
615
+ localOnly
616
+ ? "local"
617
+ : "remote",
511
618
  state:
512
- "sync-failed",
619
+ localOnly
620
+ ? "capture-failed"
621
+ : "sync-failed",
513
622
  error:
514
623
  error instanceof
515
624
  Error
@@ -523,20 +632,24 @@ export const ToolNetMemoryPlugin =
523
632
  }
524
633
  }
525
634
 
526
- function queueSync(
635
+ function queueCapture(
527
636
  sessionId,
528
637
  flag,
529
638
  reason
530
639
  ) {
531
640
  if (!sessionId) {
532
- return syncChain
641
+ return captureChain
533
642
  }
534
643
 
535
644
  lastSessionId =
536
645
  sessionId
537
646
 
538
- syncChain =
539
- syncChain
647
+ /*
648
+ * Local captures are serialized with each other,
649
+ * but completely independent from remote sync.
650
+ */
651
+ captureChain =
652
+ captureChain
540
653
  .catch(
541
654
  () =>
542
655
  undefined
@@ -545,46 +658,162 @@ export const ToolNetMemoryPlugin =
545
658
  () =>
546
659
  syncNow(
547
660
  sessionId,
548
- flag,
549
- reason
661
+ {
662
+ flag,
663
+ reason,
664
+ localOnly: true,
665
+ timeout:
666
+ CAPTURE_TIMEOUT_MS,
667
+ }
550
668
  )
551
669
  )
670
+ .catch(
671
+ () =>
672
+ undefined
673
+ )
674
+
675
+ return captureChain
676
+ }
677
+
678
+ function queueRemote(
679
+ sessionId,
680
+ flag,
681
+ reason
682
+ ) {
683
+ if (!sessionId) {
684
+ return Promise.resolve()
685
+ }
686
+
687
+ lastSessionId =
688
+ sessionId
689
+
690
+ /*
691
+ * Never build an endless remote backlog.
692
+ * One remote flush is enough because WAL keeps
693
+ * every pending local event until acknowledged.
694
+ */
695
+ if (remoteInFlight) {
696
+ return remoteInFlight
697
+ }
698
+
699
+ remoteInFlight =
700
+ syncNow(
701
+ sessionId,
702
+ {
703
+ flag,
704
+ reason,
705
+ localOnly: false,
706
+ timeout:
707
+ REMOTE_TIMEOUT_MS,
708
+ }
709
+ )
710
+ .catch(
711
+ () =>
712
+ undefined
713
+ )
714
+ .finally(
715
+ () => {
716
+ remoteInFlight =
717
+ null
718
+ }
719
+ )
720
+
721
+ return remoteInFlight
722
+ }
723
+
724
+ function scheduleCapture(
725
+ sessionId,
726
+ reason
727
+ ) {
728
+ if (!sessionId) {
729
+ return
730
+ }
731
+
732
+ if (debounceTimer) {
733
+ clearTimeout(
734
+ debounceTimer
735
+ )
736
+ }
737
+
738
+ debounceTimer =
739
+ setTimeout(
740
+ () => {
741
+ debounceTimer =
742
+ null
743
+
744
+ void queueCapture(
745
+ sessionId,
746
+ null,
747
+ reason
748
+ )
749
+ },
750
+ EVENT_CAPTURE_DEBOUNCE_MS
751
+ )
552
752
 
553
- return syncChain
753
+ if (
754
+ typeof debounceTimer.unref ===
755
+ "function"
756
+ ) {
757
+ debounceTimer.unref()
758
+ }
554
759
  }
555
760
 
556
761
  /*
557
- * Periodic durability checkpoint.
762
+ * Crash-safe LOCAL checkpoint.
558
763
  *
559
- * This is intentionally independent
560
- * from session.idle/dispose because
561
- * abrupt SSH/process termination may
562
- * skip shutdown hooks.
764
+ * Fast lane:
765
+ * OpenCode DB -> fsync WAL -> current work.
563
766
  *
564
- * unref() prevents the timer itself
565
- * from keeping OpenCode alive.
767
+ * No network dependency.
566
768
  */
567
- const periodic =
769
+ const localPeriodic =
568
770
  setInterval(
569
771
  () => {
570
772
  if (
571
773
  lastSessionId
572
774
  ) {
573
- void queueSync(
775
+ void queueCapture(
574
776
  lastSessionId,
575
777
  null,
576
- "periodic"
778
+ "periodic-local"
577
779
  )
578
780
  }
579
781
  },
580
- PERIODIC_SYNC_MS
782
+ LOCAL_CAPTURE_MS
581
783
  )
582
784
 
583
- if (
584
- typeof periodic.unref ===
585
- "function"
785
+ /*
786
+ * Remote durability is deliberately slower
787
+ * and independent from the local lane.
788
+ */
789
+ const remotePeriodic =
790
+ setInterval(
791
+ () => {
792
+ if (
793
+ lastSessionId
794
+ ) {
795
+ void queueRemote(
796
+ lastSessionId,
797
+ null,
798
+ "periodic-remote"
799
+ )
800
+ }
801
+ },
802
+ REMOTE_SYNC_MS
803
+ )
804
+
805
+ for (
806
+ const timer of [
807
+ localPeriodic,
808
+ remotePeriodic,
809
+ ]
586
810
  ) {
587
- periodic.unref()
811
+ if (
812
+ typeof timer.unref ===
813
+ "function"
814
+ ) {
815
+ timer.unref()
816
+ }
588
817
  }
589
818
 
590
819
  return {
@@ -601,14 +830,39 @@ export const ToolNetMemoryPlugin =
601
830
  sessionId
602
831
  }
603
832
 
833
+ const terminalEvent =
834
+ event.type ===
835
+ "session.idle" ||
836
+ event.type ===
837
+ "session.compacted" ||
838
+ event.type ===
839
+ "session.error"
840
+
841
+ if (
842
+ sessionId &&
843
+ !terminalEvent
844
+ ) {
845
+ scheduleCapture(
846
+ sessionId,
847
+ "event:" +
848
+ event.type
849
+ )
850
+ }
851
+
604
852
  if (
605
853
  event.type ===
606
854
  "session.idle"
607
855
  ) {
608
- await queueSync(
856
+ await queueCapture(
609
857
  sessionId,
610
858
  "--idle",
611
- "session.idle"
859
+ "session.idle:capture"
860
+ )
861
+
862
+ void queueRemote(
863
+ sessionId,
864
+ null,
865
+ "session.idle:remote"
612
866
  )
613
867
 
614
868
  return
@@ -618,10 +872,16 @@ export const ToolNetMemoryPlugin =
618
872
  event.type ===
619
873
  "session.compacted"
620
874
  ) {
621
- await queueSync(
875
+ await queueCapture(
622
876
  sessionId,
623
877
  "--compacted",
624
- "session.compacted"
878
+ "session.compacted:capture"
879
+ )
880
+
881
+ void queueRemote(
882
+ sessionId,
883
+ null,
884
+ "session.compacted:remote"
625
885
  )
626
886
 
627
887
  contextCache = {
@@ -636,10 +896,16 @@ export const ToolNetMemoryPlugin =
636
896
  event.type ===
637
897
  "session.error"
638
898
  ) {
639
- await queueSync(
899
+ await queueCapture(
640
900
  sessionId,
641
901
  "--error",
642
- "session.error"
902
+ "session.error:capture"
903
+ )
904
+
905
+ void queueRemote(
906
+ sessionId,
907
+ null,
908
+ "session.error:remote"
643
909
  )
644
910
 
645
911
  return
@@ -655,9 +921,26 @@ export const ToolNetMemoryPlugin =
655
921
  */
656
922
  "experimental.chat.system.transform":
657
923
  async (
658
- _input,
924
+ input,
659
925
  output
660
926
  ) => {
927
+ const injectionKey =
928
+ contextSessionKey(
929
+ input
930
+ )
931
+
932
+ /*
933
+ * Never inject ToolNet context on
934
+ * every model turn.
935
+ */
936
+ if (
937
+ injectedSessions.has(
938
+ injectionKey
939
+ )
940
+ ) {
941
+ return
942
+ }
943
+
661
944
  const context =
662
945
  await readContext()
663
946
 
@@ -674,6 +957,10 @@ export const ToolNetMemoryPlugin =
674
957
  context
675
958
  )
676
959
 
960
+ injectedSessions.add(
961
+ injectionKey
962
+ )
963
+
677
964
  return
678
965
  }
679
966
 
@@ -683,8 +970,14 @@ export const ToolNetMemoryPlugin =
683
970
  ) {
684
971
  output.system =
685
972
  output.system
686
- ? \`\${output.system}\\n\\n\${context}\`
973
+ ? output.system +
974
+ "\\n\\n" +
975
+ context
687
976
  : context
977
+
978
+ injectedSessions.add(
979
+ injectionKey
980
+ )
688
981
  }
689
982
  },
690
983
 
@@ -720,39 +1013,52 @@ export const ToolNetMemoryPlugin =
720
1013
  */
721
1014
  dispose: async () => {
722
1015
  clearInterval(
723
- periodic
1016
+ localPeriodic
1017
+ )
1018
+
1019
+ clearInterval(
1020
+ remotePeriodic
724
1021
  )
725
1022
 
1023
+ if (debounceTimer) {
1024
+ clearTimeout(
1025
+ debounceTimer
1026
+ )
1027
+ }
1028
+
1029
+ /*
1030
+ * On a clean exit we only REQUIRE the
1031
+ * fast local fsync checkpoint.
1032
+ *
1033
+ * Never hold OpenCode shutdown hostage
1034
+ * to a slow remote backend.
1035
+ */
726
1036
  if (
727
1037
  lastSessionId
728
1038
  ) {
729
- try {
730
- await queueSync(
731
- lastSessionId,
732
- "--idle",
733
- "dispose"
734
- )
735
- } catch {
736
- // Status already recorded.
737
- }
1039
+ await queueCapture(
1040
+ lastSessionId,
1041
+ "--idle",
1042
+ "dispose:capture"
1043
+ )
738
1044
  }
739
1045
 
740
1046
  try {
741
- await syncChain
1047
+ await captureChain
742
1048
  } catch {
743
- // Do not block OpenCode shutdown.
1049
+ // Fail open.
744
1050
  }
745
1051
  },
746
1052
  }
747
1053
  }
748
- `;return se(t,i.trimStart(),{encoding:"utf8",mode:384}),t}import{existsSync as le,mkdirSync as ct,readFileSync as at,renameSync as lt,writeFileSync as ut}from"node:fs";import{dirname as ue,join as dt}from"node:path";function f(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function gt(e,n){ct(ue(e),{recursive:!0});let t=`${e}.tmp-${process.pid}-${Date.now()}`;ut(t,`${JSON.stringify(n,null,2)}
749
- `,{encoding:"utf8",mode:384}),lt(t,e)}function ce(e){if(!le(e))return{};let n=at(e,"utf8").trim();if(!n)return{};let t;try{t=JSON.parse(n)}catch(o){throw new Error(`Invalid existing OpenCode opencode.json: ${o instanceof Error?o.message:String(o)}`)}if(!f(t))throw new Error("Invalid existing OpenCode opencode.json: root must be a JSON object.");return t}function ae(e,n){if(!f(e))return!1;let t=e.command;return e.type==="local"&&e.enabled!==!1&&Array.isArray(t)&&t.length===2&&t[0]===n&&t[1]==="mcp"}function mt(e,n){let t=e.mcpServers;if(!f(t)||!Object.prototype.hasOwnProperty.call(t,n))return{root:e,changed:!1};let o={...t};return delete o[n],{root:{...e,mcpServers:o},changed:!0}}function de(e={}){let n=e.configFile??D(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",i=dt(ue(n),"opencode.jsonc"),s=le(i)?i:void 0,r=ce(n),c=mt(r,o),l=c.root,u=l.mcp;if(u!==void 0&&!f(u))throw new Error("Invalid existing OpenCode config: mcp must be an object.");let a=f(u)?{...u}:{},m=a[o];if(ae(m,t)&&!c.changed)return{installed:!0,changed:!1,configFile:n,serverName:o,command:[t,"mcp"],preservedJsonc:s};a[o]={type:"local",command:[t,"mcp"],enabled:!0};let d={...l,mcp:a};gt(n,d);let R=ce(n);if(!f(R.mcp)||!ae(R.mcp[o],t))throw new Error("OpenCode MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:n,serverName:o,command:[t,"mcp"],preservedJsonc:s}}import{existsSync as pt,mkdirSync as ge,readFileSync as ft,writeFileSync as me}from"node:fs";import{homedir as pe}from"node:os";import{dirname as fe,join as A}from"node:path";function yt(e){let n=[],t=/"((?:\\.|[^"\\])*)"|'([^']*)'/g,o;for(;o=t.exec(e);){let i=o[1]??o[2]??"";try{n.push(o[1]!==void 0?JSON.parse(`"${i}"`):i)}catch{n.push(i)}}return n}function ye(e={}){let n=e.configFile??A(process.env.CODEX_HOME??A(pe(),".codex"),"config.toml"),t=e.previousFile??A(pe(),".config","toolnet-memory","codex-notify-previous.json");ge(fe(n),{recursive:!0}),ge(fe(t),{recursive:!0});let o=pt(n)?ft(n,"utf8"):"",i=e.binary??"toolnet-memory",s=`notify = [${JSON.stringify(i)}, "session:codex-notify"]`,r=o.split(`
1054
+ `;return se(t,i.trimStart(),{encoding:"utf8",mode:384}),t}import{existsSync as le,mkdirSync as ct,readFileSync as at,renameSync as lt,writeFileSync as ut}from"node:fs";import{dirname as ue,join as dt}from"node:path";function f(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function mt(e,n){ct(ue(e),{recursive:!0});let t=`${e}.tmp-${process.pid}-${Date.now()}`;ut(t,`${JSON.stringify(n,null,2)}
1055
+ `,{encoding:"utf8",mode:384}),lt(t,e)}function ce(e){if(!le(e))return{};let n=at(e,"utf8").trim();if(!n)return{};let t;try{t=JSON.parse(n)}catch(o){throw new Error(`Invalid existing OpenCode opencode.json: ${o instanceof Error?o.message:String(o)}`)}if(!f(t))throw new Error("Invalid existing OpenCode opencode.json: root must be a JSON object.");return t}function ae(e,n){if(!f(e))return!1;let t=e.command;return e.type==="local"&&e.enabled!==!1&&Array.isArray(t)&&t.length===2&&t[0]===n&&t[1]==="mcp"}function gt(e,n){let t=e.mcpServers;if(!f(t)||!Object.prototype.hasOwnProperty.call(t,n))return{root:e,changed:!1};let o={...t};return delete o[n],{root:{...e,mcpServers:o},changed:!0}}function de(e={}){let n=e.configFile??$(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",i=dt(ue(n),"opencode.jsonc"),s=le(i)?i:void 0,r=ce(n),c=gt(r,o),l=c.root,u=l.mcp;if(u!==void 0&&!f(u))throw new Error("Invalid existing OpenCode config: mcp must be an object.");let a=f(u)?{...u}:{},g=a[o];if(ae(g,t)&&!c.changed)return{installed:!0,changed:!1,configFile:n,serverName:o,command:[t,"mcp"],preservedJsonc:s};a[o]={type:"local",command:[t,"mcp"],enabled:!0};let d={...l,mcp:a};mt(n,d);let R=ce(n);if(!f(R.mcp)||!ae(R.mcp[o],t))throw new Error("OpenCode MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:n,serverName:o,command:[t,"mcp"],preservedJsonc:s}}import{existsSync as pt,mkdirSync as me,readFileSync as ft,writeFileSync as ge}from"node:fs";import{homedir as pe}from"node:os";import{dirname as fe,join as j}from"node:path";function yt(e){let n=[],t=/"((?:\\.|[^"\\])*)"|'([^']*)'/g,o;for(;o=t.exec(e);){let i=o[1]??o[2]??"";try{n.push(o[1]!==void 0?JSON.parse(`"${i}"`):i)}catch{n.push(i)}}return n}function ye(e={}){let n=e.configFile??j(process.env.CODEX_HOME??j(pe(),".codex"),"config.toml"),t=e.previousFile??j(pe(),".config","toolnet-memory","codex-notify-previous.json");me(fe(n),{recursive:!0}),me(fe(t),{recursive:!0});let o=pt(n)?ft(n,"utf8"):"",i=e.binary??"toolnet-memory",s=`notify = [${JSON.stringify(i)}, "session:codex-notify"]`,r=o.split(`
750
1056
  `),c=r.findIndex(d=>/^\s*\[/.test(d));c<0&&(c=r.length);let l=-1,u=-1;for(let d=0;d<c;d+=1)if(/^\s*notify\s*=/.test(r[d])){if(l=d,u=d,r[d].includes("[")&&!r[d].includes("]"))for(;u+1<c&&(u+=1,!r[u].includes("]")););break}let a=[];if(l>=0){let d=r.slice(l,u+1).join(`
751
- `);a=yt(d),r.splice(l,u-l+1,s)}else c=r.findIndex(d=>/^\s*\[/.test(d)),c<0&&(c=r.length),r.splice(c,0,s);let m=a.length>=2&&a[a.length-1]==="session:codex-notify";return a.length>0&&!m&&me(t,JSON.stringify(a,null,2)+`
1057
+ `);a=yt(d),r.splice(l,u-l+1,s)}else c=r.findIndex(d=>/^\s*\[/.test(d)),c<0&&(c=r.length),r.splice(c,0,s);let g=a.length>=2&&a[a.length-1]==="session:codex-notify";return a.length>0&&!g&&ge(t,JSON.stringify(a,null,2)+`
752
1058
  `,{encoding:"utf8",mode:384}),o=r.join(`
753
1059
  `),o.endsWith(`
754
1060
  `)||(o+=`
755
- `),me(n,o,{encoding:"utf8",mode:384}),{configFile:n,previousFile:t,preservedPrevious:a.length>0&&!m}}import{existsSync as ht,mkdirSync as Ot,readFileSync as xt,writeFileSync as vt}from"node:fs";import{homedir as Ct}from"node:os";import{dirname as St,join as he}from"node:path";function bt(e){return`'${e.replace(/'/g,"'\\''")}'`}function Oe(e={}){let n=e.hooksFile??he(process.env.CODEX_HOME??he(Ct(),".codex"),"hooks.json");Ot(St(n),{recursive:!0});let t={};if(ht(n))try{t=JSON.parse(xt(n,"utf8"))}catch(c){throw new Error(`Invalid existing Codex hooks.json: ${c instanceof Error?c.message:String(c)}`)}let o=t.hooks&&typeof t.hooks=="object"&&!Array.isArray(t.hooks)?t.hooks:{};t.hooks=o;let s=(Array.isArray(o.SessionStart)?o.SessionStart:[]).filter(c=>{try{return!JSON.stringify(c).includes("session:codex-context")}catch{return!0}}),r=e.binary??"toolnet-memory";return s.push({matcher:"startup|resume|clear|compact",hooks:[{type:"command",command:`${bt(r)} session:codex-context`,timeout:15,additionalContextLimit:1e3,statusMessage:"Loading ToolNet project continuity"}]}),o.SessionStart=s,vt(n,JSON.stringify(t,null,2)+`
756
- `,{encoding:"utf8",mode:384}),n}import{spawnSync as wt}from"node:child_process";function T(e,n){return wt(e,n,{encoding:"utf8",stdio:["ignore","pipe","pipe"]})}function xe(e,n){let t=T(e,["mcp","get",n,"--json"]);if(t.status!==0||!t.stdout)return null;try{return JSON.parse(t.stdout)}catch{return null}}function ve(e,n){return e.enabled!==!1&&e.transport?.type==="stdio"&&e.transport?.command===n&&Array.isArray(e.transport?.args)&&e.transport?.args.length===1&&e.transport.args[0]==="mcp"}function Ce(e={}){let n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",t=e.codexBinary??"codex",o=e.serverName??"toolnet-memory",i=xe(t,o);if(i&&ve(i,n))return{installed:!0,changed:!1,serverName:o,command:n,args:["mcp"]};if(i){let c=T(t,["mcp","remove",o]);if(c.status!==0)return{installed:!1,changed:!1,serverName:o,command:n,args:["mcp"],error:(c.stderr||c.stdout||"Unable to remove old ToolNet MCP configuration.").trim()}}let s=T(t,["mcp","add",o,"--",n,"mcp"]);if(s.status!==0)return{installed:!1,changed:!1,serverName:o,command:n,args:["mcp"],error:(s.stderr||s.stdout||"Unable to register ToolNet MCP.").trim()};let r=xe(t,o);return!r||!ve(r,n)?{installed:!1,changed:!0,serverName:o,command:n,args:["mcp"],error:"Codex accepted MCP registration but verification did not match expected ToolNet command."}:{installed:!0,changed:!0,serverName:o,command:n,args:["mcp"]}}import{existsSync as kt,mkdirSync as Nt,readFileSync as It,renameSync as jt,rmSync as Et,writeFileSync as At}from"node:fs";import{dirname as Tt}from"node:path";function O(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Mt(e){return/^[A-Za-z0-9_./:-]+$/u.test(e)?e:`'${e.replace(/'/gu,"'\\''")}'`}function Ft(e){if(!kt(e))return{};let n;try{n=JSON.parse(It(e,"utf8"))}catch(t){throw new Error(`Invalid existing Claude settings.json: ${t instanceof Error?t.message:String(t)}`)}if(!O(n))throw new Error("Invalid existing Claude settings.json: root must be a JSON object.");return n}function M(e){if(e===void 0)return[];if(!Array.isArray(e))throw new Error("Invalid existing Claude settings.json: hook event must be an array.");let n=[];for(let t of e){if(!O(t)){n.push(t);continue}let o=t.hooks;if(!Array.isArray(o)){n.push(t);continue}let i=o.filter(s=>{if(!O(s))return!0;let r=s.command;return!(typeof r=="string"&&r.includes("session:claude-hook"))});i.length!==0&&n.push({...t,hooks:i})}return n}function F(e){return{type:"command",command:e,timeout:10}}function Rt(e,n){Nt(Tt(e),{recursive:!0,mode:448});let t=`${e}.toolnet-${process.pid}-${Date.now()}.tmp`;try{At(t,JSON.stringify(n,null,2)+`
757
- `,{encoding:"utf8",mode:384}),jt(t,e)}finally{Et(t,{force:!0})}}function Se(e={}){let n=e.settingsFile??Y(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=Ft(n),i=o.hooks;if(i!==void 0&&!O(i))throw new Error("Invalid existing Claude settings.json: hooks must be an object.");let s=O(i)?{...i}:{},r=`${Mt(t)} session:claude-hook`,c=M(s.SessionStart);c.push({matcher:"startup|resume|clear|compact",hooks:[F(r)]}),s.SessionStart=c;let l=M(s.PostToolUse);l.push({matcher:"Edit|Write",hooks:[F(r)]}),s.PostToolUse=l;let u=M(s.Stop);u.push({hooks:[F(r)]}),s.Stop=u;let a={...o,hooks:s},m=JSON.stringify(o),d=JSON.stringify(a);return m===d?{settingsFile:n,changed:!1}:(Rt(n,a),{settingsFile:n,changed:!0})}import{existsSync as Pt,mkdirSync as _t,readFileSync as $t,renameSync as Jt,rmSync as Dt,writeFileSync as Ht}from"node:fs";import{dirname as Lt}from"node:path";function x(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function be(e){if(!Pt(e))return{};let n;try{n=JSON.parse($t(e,"utf8"))}catch(t){throw new Error(`Invalid existing Claude Code config: ${t instanceof Error?t.message:String(t)}`)}if(!x(n))throw new Error("Invalid existing Claude Code config: root must be a JSON object.");return n}function we(e,n){if(!x(e))return!1;let t=e.args;return e.type==="stdio"&&e.command===n&&Array.isArray(t)&&t.length===1&&t[0]==="mcp"}function Bt(e,n){_t(Lt(e),{recursive:!0});let t=`${e}.toolnet-${process.pid}-${Date.now()}.tmp`;try{Ht(t,JSON.stringify(n,null,2)+`
758
- `,{encoding:"utf8",mode:384}),Jt(t,e)}finally{Dt(t,{force:!0})}}function ke(e={}){let n=e.stateFile??U(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",i=be(n),s=i.mcpServers;if(s!==void 0&&!x(s))throw new Error("Invalid existing Claude Code config: mcpServers must be an object.");let r=x(s)?{...s}:{},c=r[o];if(we(c,t))return{installed:!0,changed:!1,configFile:n,serverName:o,command:[t,"mcp"],repaired:!1};let l=c!==void 0;r[o]={type:"stdio",command:t,args:["mcp"]},Bt(n,{...i,mcpServers:r});let a=be(n).mcpServers;if(!x(a)||!we(a[o],t))throw new Error("Claude Code MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:n,serverName:o,command:[t,"mcp"],repaired:l}}function Ne(e={}){let n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",t=Se({binary:n,settingsFile:e.settingsFile}),o=ke({binary:n,stateFile:e.stateFile});return{hooks:t,mcp:o,files:[t.settingsFile,o.configFile]}}function Ie(){return q()}function Yt(e={}){let n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",t=[],o=Ie(),i=new Map(o.map(s=>[s.agent,s.detected]));if(!(e.force===!0||i.get("agy")===!0))t.push({agent:"agy",detected:!1,installed:!1,targets:[]});else try{let r=te({binary:n});t.push({agent:"agy",detected:!0,installed:!0,targets:r.files})}catch(r){t.push({agent:"agy",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||i.get("opencode")===!0))t.push({agent:"opencode",detected:!1,installed:!1,targets:[]});else try{let r=ie({binary:n}),c=de({binary:n});t.push({agent:"opencode",detected:!0,installed:!0,targets:[r,c.configFile,`mcp:${c.serverName}`]})}catch(r){t.push({agent:"opencode",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||i.get("claude")===!0))t.push({agent:"claude",detected:!1,installed:!1,targets:[]});else try{let r=Ne({binary:n});t.push({agent:"claude",detected:!0,installed:!0,targets:[r.hooks.settingsFile,r.mcp.configFile,`mcp:${r.mcp.serverName}`]})}catch(r){t.push({agent:"claude",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||i.get("codex")===!0))t.push({agent:"codex",detected:!1,installed:!1,targets:[]});else try{let r=ye({binary:n}),c=Oe({binary:n}),l=Ce({binary:n});if(!l.installed)throw new Error(l.error??"Codex MCP registration failed");let u=[r.configFile,c,`mcp:${l.serverName}`];r.preservedPrevious&&u.push(r.previousFile),t.push({agent:"codex",detected:!0,installed:!0,targets:u})}catch(r){t.push({agent:"codex",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}return t}function Ut(e){console.log(""),console.log("ToolNet Memory Integration Detection"),console.log("===================================="),console.log("");for(let n of e){let t=n.agent==="agy"?"Agy / Antigravity":n.agent==="opencode"?"OpenCode":n.agent==="claude"?"Claude Code":"Codex";if(!n.detected){console.log(`\u25CB ${t}: not detected`);continue}console.log(`\u2713 ${t}: detected`);for(let o of n.evidence)console.log(` ${o}`)}console.log("")}function qt(e){console.log(""),console.log("ToolNet Memory AI Integrations"),console.log("=============================="),console.log("");for(let n of e){let t=n.agent==="agy"?"Agy / Antigravity":n.agent==="opencode"?"OpenCode":"Codex";if(!n.detected){console.log(`- ${t}: not detected`);continue}if(n.installed){console.log(`\u2713 ${t}: automatic memory enabled`);continue}console.log(`\u2717 ${t}: integration failed`),n.error&&console.log(` ${n.error}`)}console.log("")}async function Gt(){let e=process.argv.slice(2),n=e.includes("--all"),t=e.includes("--json");if(e.includes("--detect-only")){let s=Ie();if(t){console.log(JSON.stringify(s,null,2));return}Ut(s);return}let i=Yt({force:n});if(t){console.log(JSON.stringify(i,null,2));return}qt(i)}var Wt=process.argv[1]&&(process.argv[1].endsWith("auto-integrate.js")||process.argv[1].endsWith("auto-integrate.ts"));Wt&&Gt().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exitCode=1});export{Ie as detectAutoIntegrations,Yt as installAutoIntegrations};
1061
+ `),ge(n,o,{encoding:"utf8",mode:384}),{configFile:n,previousFile:t,preservedPrevious:a.length>0&&!g}}import{existsSync as ht,mkdirSync as Ot,readFileSync as vt,writeFileSync as xt}from"node:fs";import{homedir as Ct}from"node:os";import{dirname as St,join as he}from"node:path";function bt(e){return`'${e.replace(/'/g,"'\\''")}'`}function Oe(e={}){let n=e.hooksFile??he(process.env.CODEX_HOME??he(Ct(),".codex"),"hooks.json");Ot(St(n),{recursive:!0});let t={};if(ht(n))try{t=JSON.parse(vt(n,"utf8"))}catch(c){throw new Error(`Invalid existing Codex hooks.json: ${c instanceof Error?c.message:String(c)}`)}let o=t.hooks&&typeof t.hooks=="object"&&!Array.isArray(t.hooks)?t.hooks:{};t.hooks=o;let s=(Array.isArray(o.SessionStart)?o.SessionStart:[]).filter(c=>{try{return!JSON.stringify(c).includes("session:codex-context")}catch{return!0}}),r=e.binary??"toolnet-memory";return s.push({matcher:"startup|resume|clear|compact",hooks:[{type:"command",command:`${bt(r)} session:codex-context`,timeout:15,additionalContextLimit:1e3,statusMessage:"Loading ToolNet project continuity"}]}),o.SessionStart=s,xt(n,JSON.stringify(t,null,2)+`
1062
+ `,{encoding:"utf8",mode:384}),n}import{spawnSync as wt}from"node:child_process";function A(e,n){return wt(e,n,{encoding:"utf8",stdio:["ignore","pipe","pipe"]})}function ve(e,n){let t=A(e,["mcp","get",n,"--json"]);if(t.status!==0||!t.stdout)return null;try{return JSON.parse(t.stdout)}catch{return null}}function xe(e,n){return e.enabled!==!1&&e.transport?.type==="stdio"&&e.transport?.command===n&&Array.isArray(e.transport?.args)&&e.transport?.args.length===1&&e.transport.args[0]==="mcp"}function Ce(e={}){let n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",t=e.codexBinary??"codex",o=e.serverName??"toolnet-memory",i=ve(t,o);if(i&&xe(i,n))return{installed:!0,changed:!1,serverName:o,command:n,args:["mcp"]};if(i){let c=A(t,["mcp","remove",o]);if(c.status!==0)return{installed:!1,changed:!1,serverName:o,command:n,args:["mcp"],error:(c.stderr||c.stdout||"Unable to remove old ToolNet MCP configuration.").trim()}}let s=A(t,["mcp","add",o,"--",n,"mcp"]);if(s.status!==0)return{installed:!1,changed:!1,serverName:o,command:n,args:["mcp"],error:(s.stderr||s.stdout||"Unable to register ToolNet MCP.").trim()};let r=ve(t,o);return!r||!xe(r,n)?{installed:!1,changed:!0,serverName:o,command:n,args:["mcp"],error:"Codex accepted MCP registration but verification did not match expected ToolNet command."}:{installed:!0,changed:!0,serverName:o,command:n,args:["mcp"]}}import{existsSync as It,mkdirSync as kt,readFileSync as Nt,renameSync as Tt,rmSync as Et,writeFileSync as jt}from"node:fs";import{dirname as At}from"node:path";function O(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function Mt(e){return/^[A-Za-z0-9_./:-]+$/u.test(e)?e:`'${e.replace(/'/gu,"'\\''")}'`}function Ft(e){if(!It(e))return{};let n;try{n=JSON.parse(Nt(e,"utf8"))}catch(t){throw new Error(`Invalid existing Claude settings.json: ${t instanceof Error?t.message:String(t)}`)}if(!O(n))throw new Error("Invalid existing Claude settings.json: root must be a JSON object.");return n}function M(e){if(e===void 0)return[];if(!Array.isArray(e))throw new Error("Invalid existing Claude settings.json: hook event must be an array.");let n=[];for(let t of e){if(!O(t)){n.push(t);continue}let o=t.hooks;if(!Array.isArray(o)){n.push(t);continue}let i=o.filter(s=>{if(!O(s))return!0;let r=s.command;return!(typeof r=="string"&&r.includes("session:claude-hook"))});i.length!==0&&n.push({...t,hooks:i})}return n}function F(e){return{type:"command",command:e,timeout:10}}function Rt(e,n){kt(At(e),{recursive:!0,mode:448});let t=`${e}.toolnet-${process.pid}-${Date.now()}.tmp`;try{jt(t,JSON.stringify(n,null,2)+`
1063
+ `,{encoding:"utf8",mode:384}),Tt(t,e)}finally{Et(t,{force:!0})}}function Se(e={}){let n=e.settingsFile??U(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=Ft(n),i=o.hooks;if(i!==void 0&&!O(i))throw new Error("Invalid existing Claude settings.json: hooks must be an object.");let s=O(i)?{...i}:{},r=`${Mt(t)} session:claude-hook`,c=M(s.SessionStart);c.push({matcher:"startup|resume|clear|compact",hooks:[F(r)]}),s.SessionStart=c;let l=M(s.PostToolUse);l.push({matcher:"Edit|Write",hooks:[F(r)]}),s.PostToolUse=l;let u=M(s.Stop);u.push({hooks:[F(r)]}),s.Stop=u;let a={...o,hooks:s},g=JSON.stringify(o),d=JSON.stringify(a);return g===d?{settingsFile:n,changed:!1}:(Rt(n,a),{settingsFile:n,changed:!0})}import{existsSync as _t,mkdirSync as Pt,readFileSync as Jt,renameSync as Dt,rmSync as $t,writeFileSync as Lt}from"node:fs";import{dirname as Ht}from"node:path";function v(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function be(e){if(!_t(e))return{};let n;try{n=JSON.parse(Jt(e,"utf8"))}catch(t){throw new Error(`Invalid existing Claude Code config: ${t instanceof Error?t.message:String(t)}`)}if(!v(n))throw new Error("Invalid existing Claude Code config: root must be a JSON object.");return n}function we(e,n){if(!v(e))return!1;let t=e.args;return e.type==="stdio"&&e.command===n&&Array.isArray(t)&&t.length===1&&t[0]==="mcp"}function Bt(e,n){Pt(Ht(e),{recursive:!0});let t=`${e}.toolnet-${process.pid}-${Date.now()}.tmp`;try{Lt(t,JSON.stringify(n,null,2)+`
1064
+ `,{encoding:"utf8",mode:384}),Dt(t,e)}finally{$t(t,{force:!0})}}function Ie(e={}){let n=e.stateFile??Y(),t=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",o=e.serverName??"toolnet-memory",i=be(n),s=i.mcpServers;if(s!==void 0&&!v(s))throw new Error("Invalid existing Claude Code config: mcpServers must be an object.");let r=v(s)?{...s}:{},c=r[o];if(we(c,t))return{installed:!0,changed:!1,configFile:n,serverName:o,command:[t,"mcp"],repaired:!1};let l=c!==void 0;r[o]={type:"stdio",command:t,args:["mcp"]},Bt(n,{...i,mcpServers:r});let a=be(n).mcpServers;if(!v(a)||!we(a[o],t))throw new Error("Claude Code MCP configuration was written but verification failed.");return{installed:!0,changed:!0,configFile:n,serverName:o,command:[t,"mcp"],repaired:l}}function ke(e={}){let n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",t=Se({binary:n,settingsFile:e.settingsFile}),o=Ie({binary:n,stateFile:e.stateFile});return{hooks:t,mcp:o,files:[t.settingsFile,o.configFile]}}function Ne(){return q()}function Ut(e={}){let n=e.binary??process.env.TOOLNET_MEMORY_BIN??"toolnet-memory",t=[],o=Ne(),i=new Map(o.map(s=>[s.agent,s.detected]));if(!(e.force===!0||i.get("agy")===!0))t.push({agent:"agy",detected:!1,installed:!1,targets:[]});else try{let r=te({binary:n});t.push({agent:"agy",detected:!0,installed:!0,targets:r.files})}catch(r){t.push({agent:"agy",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||i.get("opencode")===!0))t.push({agent:"opencode",detected:!1,installed:!1,targets:[]});else try{let r=ie({binary:n}),c=de({binary:n});t.push({agent:"opencode",detected:!0,installed:!0,targets:[r,c.configFile,`mcp:${c.serverName}`]})}catch(r){t.push({agent:"opencode",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||i.get("claude")===!0))t.push({agent:"claude",detected:!1,installed:!1,targets:[]});else try{let r=ke({binary:n});t.push({agent:"claude",detected:!0,installed:!0,targets:[r.hooks.settingsFile,r.mcp.configFile,`mcp:${r.mcp.serverName}`]})}catch(r){t.push({agent:"claude",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}if(!(e.force===!0||i.get("codex")===!0))t.push({agent:"codex",detected:!1,installed:!1,targets:[]});else try{let r=ye({binary:n}),c=Oe({binary:n}),l=Ce({binary:n});if(!l.installed)throw new Error(l.error??"Codex MCP registration failed");let u=[r.configFile,c,`mcp:${l.serverName}`];r.preservedPrevious&&u.push(r.previousFile),t.push({agent:"codex",detected:!0,installed:!0,targets:u})}catch(r){t.push({agent:"codex",detected:!0,installed:!1,targets:[],error:r instanceof Error?r.message:String(r)})}return t}function Yt(e){console.log(""),console.log("ToolNet Memory Integration Detection"),console.log("===================================="),console.log("");for(let n of e){let t=n.agent==="agy"?"Agy / Antigravity":n.agent==="opencode"?"OpenCode":n.agent==="claude"?"Claude Code":"Codex";if(!n.detected){console.log(`\u25CB ${t}: not detected`);continue}console.log(`\u2713 ${t}: detected`);for(let o of n.evidence)console.log(` ${o}`)}console.log("")}function qt(e){console.log(""),console.log("ToolNet Memory AI Integrations"),console.log("=============================="),console.log("");for(let n of e){let t=n.agent==="agy"?"Agy / Antigravity":n.agent==="opencode"?"OpenCode":"Codex";if(!n.detected){console.log(`- ${t}: not detected`);continue}if(n.installed){console.log(`\u2713 ${t}: automatic memory enabled`);continue}console.log(`\u2717 ${t}: integration failed`),n.error&&console.log(` ${n.error}`)}console.log("")}async function Wt(){let e=process.argv.slice(2),n=e.includes("--all"),t=e.includes("--json");if(e.includes("--detect-only")){let s=Ne();if(t){console.log(JSON.stringify(s,null,2));return}Yt(s);return}let i=Ut({force:n});if(t){console.log(JSON.stringify(i,null,2));return}qt(i)}var Gt=process.argv[1]&&(process.argv[1].endsWith("auto-integrate.js")||process.argv[1].endsWith("auto-integrate.ts"));Gt&&Wt().catch(e=>{console.error(e instanceof Error?e.message:String(e)),process.exitCode=1});export{Ne as detectAutoIntegrations,Ut as installAutoIntegrations};