jorgex-stack 1.0.14 → 1.0.15
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/dist/cli.js +1 -0
- package/package.json +1 -1
- package/stack/plugins/opencode/engram.ts +54 -27
package/dist/cli.js
CHANGED
|
@@ -1323,6 +1323,7 @@ function planPlugins(adapter, ctx) {
|
|
|
1323
1323
|
);
|
|
1324
1324
|
content = content.replace(/"\{\{ENGRAM_PROTOCOL\}\}"/g, JSON.stringify(protocol));
|
|
1325
1325
|
}
|
|
1326
|
+
content = content.replace(/(from\s+["'])(\.{1,2}\/[^"']+?)\.js(["'])/g, "$1$2.ts$3");
|
|
1326
1327
|
if (content === raw) return { kind: "copy", source: sourceFile, target };
|
|
1327
1328
|
return { kind: "write", target, content };
|
|
1328
1329
|
});
|
package/package.json
CHANGED
|
@@ -16,12 +16,30 @@
|
|
|
16
16
|
|
|
17
17
|
import type { Plugin } from "@opencode-ai/plugin"
|
|
18
18
|
|
|
19
|
+
declare const Bun: {
|
|
20
|
+
which?: (bin: string) => string | null
|
|
21
|
+
spawnSync: (args: string[]) => { exitCode: number; stdout?: { toString(): string } | string }
|
|
22
|
+
spawn: (args: string[], options?: Record<string, unknown>) => unknown
|
|
23
|
+
file: (path: string) => { exists: () => Promise<boolean> }
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
// ─── Configuration ───────────────────────────────────────────────────────────
|
|
20
27
|
|
|
21
28
|
const ENGRAM_PORT = parseInt(process.env.ENGRAM_PORT ?? "7437")
|
|
22
29
|
const ENGRAM_URL = `http://127.0.0.1:${ENGRAM_PORT}`
|
|
23
30
|
// "{{ENGRAM_BIN}}" lo resuelve el instalador con el binario detectado (D7).
|
|
24
|
-
const ENGRAM_BIN =
|
|
31
|
+
const ENGRAM_BIN = "{{ENGRAM_BIN}}"
|
|
32
|
+
|
|
33
|
+
export function resolveEngramBin(installerBin = ENGRAM_BIN): string {
|
|
34
|
+
const envBin = process.env.ENGRAM_BIN
|
|
35
|
+
if (envBin) return envBin
|
|
36
|
+
|
|
37
|
+
const bun = globalThis as typeof globalThis & { Bun?: { which?: (bin: string) => string | null } }
|
|
38
|
+
const bunBin = bun.Bun?.which?.("engram")
|
|
39
|
+
if (bunBin) return bunBin
|
|
40
|
+
|
|
41
|
+
return installerBin !== "{{ENGRAM_BIN}}" ? installerBin : "engram"
|
|
42
|
+
}
|
|
25
43
|
|
|
26
44
|
// Engram's own MCP tools — don't count these as "tool calls" for session stats
|
|
27
45
|
const ENGRAM_TOOLS = new Set([
|
|
@@ -60,6 +78,7 @@ async function engramFetch(
|
|
|
60
78
|
headers: opts.body ? { "Content-Type": "application/json" } : undefined,
|
|
61
79
|
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
|
62
80
|
})
|
|
81
|
+
if (!res.ok) return null
|
|
63
82
|
return await res.json()
|
|
64
83
|
} catch {
|
|
65
84
|
// Engram server not running — silently fail
|
|
@@ -126,6 +145,7 @@ function stripPrivateTags(str: string): string {
|
|
|
126
145
|
export const Engram: Plugin = async (ctx) => {
|
|
127
146
|
const oldProject = ctx.directory.split(/[\\/]/).pop() ?? "unknown"
|
|
128
147
|
const project = extractProjectName(ctx.directory)
|
|
148
|
+
const engramBin = resolveEngramBin()
|
|
129
149
|
|
|
130
150
|
// Track tool counts per session (in-memory only, not critical)
|
|
131
151
|
const toolCounts = new Map<string, number>()
|
|
@@ -145,12 +165,12 @@ export const Engram: Plugin = async (ctx) => {
|
|
|
145
165
|
*
|
|
146
166
|
* Silently skips sub-agent sessions (tracked in `subAgentSessions`).
|
|
147
167
|
*/
|
|
148
|
-
async function ensureSession(sessionId: string): Promise<
|
|
149
|
-
if (!sessionId
|
|
168
|
+
async function ensureSession(sessionId: string): Promise<boolean> {
|
|
169
|
+
if (!sessionId) return false
|
|
170
|
+
if (knownSessions.has(sessionId)) return true
|
|
150
171
|
// Do not register sub-agent sessions in Engram (issue #116).
|
|
151
|
-
if (subAgentSessions.has(sessionId)) return
|
|
152
|
-
|
|
153
|
-
await engramFetch("/sessions", {
|
|
172
|
+
if (subAgentSessions.has(sessionId)) return false
|
|
173
|
+
const session = await engramFetch("/sessions", {
|
|
154
174
|
method: "POST",
|
|
155
175
|
body: {
|
|
156
176
|
id: sessionId,
|
|
@@ -158,13 +178,18 @@ export const Engram: Plugin = async (ctx) => {
|
|
|
158
178
|
directory: ctx.directory,
|
|
159
179
|
},
|
|
160
180
|
})
|
|
181
|
+
|
|
182
|
+
if (session === null) return false
|
|
183
|
+
|
|
184
|
+
knownSessions.add(sessionId)
|
|
185
|
+
return true
|
|
161
186
|
}
|
|
162
187
|
|
|
163
188
|
// Try to start engram server if not running
|
|
164
189
|
const running = await isEngramRunning()
|
|
165
190
|
if (!running) {
|
|
166
191
|
try {
|
|
167
|
-
Bun.spawn([
|
|
192
|
+
Bun.spawn([engramBin, "serve"], {
|
|
168
193
|
stdout: "ignore",
|
|
169
194
|
stderr: "ignore",
|
|
170
195
|
stdin: "ignore",
|
|
@@ -192,7 +217,7 @@ export const Engram: Plugin = async (ctx) => {
|
|
|
192
217
|
const manifestFile = `${ctx.directory}/.engram/manifest.json`
|
|
193
218
|
const file = Bun.file(manifestFile)
|
|
194
219
|
if (await file.exists()) {
|
|
195
|
-
Bun.spawn([
|
|
220
|
+
Bun.spawn([engramBin, "sync", "--import"], {
|
|
196
221
|
cwd: ctx.directory,
|
|
197
222
|
stdout: "ignore",
|
|
198
223
|
stderr: "ignore",
|
|
@@ -206,7 +231,7 @@ export const Engram: Plugin = async (ctx) => {
|
|
|
206
231
|
return {
|
|
207
232
|
// ─── Event Listeners ───────────────────────────────────────────
|
|
208
233
|
|
|
209
|
-
event: async ({ event }) => {
|
|
234
|
+
event: async ({ event }: { event: { type: string; properties?: unknown } }) => {
|
|
210
235
|
// --- Session Created ---
|
|
211
236
|
if (event.type === "session.created") {
|
|
212
237
|
// Bug fix (#116): session data is nested under event.properties.info,
|
|
@@ -254,7 +279,7 @@ export const Engram: Plugin = async (ctx) => {
|
|
|
254
279
|
// output.message is typed as UserMessage (role:"user" already guaranteed).
|
|
255
280
|
// output.parts contains TextPart[] with the actual message text.
|
|
256
281
|
|
|
257
|
-
"chat.message": async (input, output) => {
|
|
282
|
+
"chat.message": async (input: any, output: any) => {
|
|
258
283
|
// Skip sub-agent sessions — they inflate session counts (issue #116)
|
|
259
284
|
if (subAgentSessions.has(input.sessionID)) return
|
|
260
285
|
|
|
@@ -262,8 +287,8 @@ export const Engram: Plugin = async (ctx) => {
|
|
|
262
287
|
|
|
263
288
|
// Extract text from parts (type:"text")
|
|
264
289
|
const content = output.parts
|
|
265
|
-
.filter((p) => p.type === "text")
|
|
266
|
-
.map((p) =>
|
|
290
|
+
.filter((p: any) => p.type === "text")
|
|
291
|
+
.map((p: any) => p.text ?? "")
|
|
267
292
|
.join("\n")
|
|
268
293
|
.trim()
|
|
269
294
|
|
|
@@ -276,15 +301,17 @@ export const Engram: Plugin = async (ctx) => {
|
|
|
276
301
|
|
|
277
302
|
// Only capture non-trivial prompts (>10 chars)
|
|
278
303
|
if (finalContent.length > 10) {
|
|
279
|
-
await ensureSession(sessionId)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
304
|
+
const sessionReady = await ensureSession(sessionId)
|
|
305
|
+
if (sessionReady) {
|
|
306
|
+
await engramFetch("/prompts", {
|
|
307
|
+
method: "POST",
|
|
308
|
+
body: {
|
|
309
|
+
session_id: sessionId,
|
|
310
|
+
content: stripPrivateTags(truncate(finalContent, 2000)),
|
|
311
|
+
project,
|
|
312
|
+
},
|
|
313
|
+
})
|
|
314
|
+
}
|
|
288
315
|
}
|
|
289
316
|
},
|
|
290
317
|
|
|
@@ -294,13 +321,13 @@ export const Engram: Plugin = async (ctx) => {
|
|
|
294
321
|
// Passive capture: when a Task tool completes, POST its output to
|
|
295
322
|
// the passive capture endpoint so the server extracts learnings.
|
|
296
323
|
|
|
297
|
-
"tool.execute.after": async (input, output) => {
|
|
324
|
+
"tool.execute.after": async (input: any, output: any) => {
|
|
298
325
|
if (ENGRAM_TOOLS.has(input.tool.toLowerCase())) return
|
|
299
326
|
|
|
300
327
|
// input.sessionID comes from OpenCode — always available
|
|
301
328
|
const sessionId = input.sessionID
|
|
302
|
-
|
|
303
|
-
|
|
329
|
+
const sessionReady = sessionId ? await ensureSession(sessionId) : false
|
|
330
|
+
if (sessionReady && sessionId) {
|
|
304
331
|
toolCounts.set(sessionId, (toolCounts.get(sessionId) ?? 0) + 1)
|
|
305
332
|
}
|
|
306
333
|
|
|
@@ -308,7 +335,7 @@ export const Engram: Plugin = async (ctx) => {
|
|
|
308
335
|
// (OpenCode reports the tool name in lowercase: "task")
|
|
309
336
|
if (input.tool.toLowerCase() === "task" && output && sessionId) {
|
|
310
337
|
const text = typeof output === "string" ? output : JSON.stringify(output)
|
|
311
|
-
if (text.length > 50) {
|
|
338
|
+
if (text.length > 50 && sessionReady) {
|
|
312
339
|
await engramFetch("/observations/passive", {
|
|
313
340
|
method: "POST",
|
|
314
341
|
body: {
|
|
@@ -332,7 +359,7 @@ export const Engram: Plugin = async (ctx) => {
|
|
|
332
359
|
// block at the beginning. By concatenating, we avoid adding extra system
|
|
333
360
|
// messages that would break these models. See: GitHub issue #23.
|
|
334
361
|
|
|
335
|
-
"experimental.chat.system.transform": async (_input, output) => {
|
|
362
|
+
"experimental.chat.system.transform": async (_input: any, output: any) => {
|
|
336
363
|
if (output.system.length > 0) {
|
|
337
364
|
output.system[output.system.length - 1] += "\n\n" + MEMORY_INSTRUCTIONS
|
|
338
365
|
} else {
|
|
@@ -348,7 +375,7 @@ export const Engram: Plugin = async (ctx) => {
|
|
|
348
375
|
// 2. Inject context from previous sessions into the compaction prompt
|
|
349
376
|
// 3. Tell the compressor to remind the new agent to save memories
|
|
350
377
|
|
|
351
|
-
"experimental.session.compacting": async (input, output) => {
|
|
378
|
+
"experimental.session.compacting": async (input: any, output: any) => {
|
|
352
379
|
if (input.sessionID) {
|
|
353
380
|
await ensureSession(input.sessionID)
|
|
354
381
|
}
|