@memorax/memorax-code 0.1.7 → 0.1.9
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 +36 -5
- package/bin/memorax-code-setup.mjs +0 -3
- package/docs/configuration.md +1 -3
- package/docs/troubleshooting.md +9 -8
- package/lib/memorax-code-backend/dist/app/backend-server.js +2 -29
- package/lib/memorax-code-backend/dist/app/memory-observability.js +1 -4
- package/lib/memorax-code-backend/dist/app/state.js +1 -12
- package/lib/memorax-code-backend/dist/clients/claude/transcript-turn.js +0 -182
- package/lib/memorax-code-backend/dist/clients/codex/rollout-turn.js +52 -2
- package/lib/memorax-code-backend/dist/clients/opencode/message-turn.js +68 -2
- package/lib/memorax-code-backend/dist/lifecycle/backend/service.js +0 -5
- package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +1 -14
- package/lib/memorax-code-backend/dist/memory/automatic-writeback.js +8 -5
- package/lib/memorax-code-backend/dist/memory/project.js +4 -26
- package/lib/memorax-code-backend/dist/memory/quota-notice.js +79 -19
- package/lib/memorax-code-backend/dist/provider/memorax/adapter.js +1 -4
- package/lib/memorax-code-backend/dist/provider/memorax/http.js +0 -28
- package/lib/memorax-code-backend/dist/transport/http/request.js +1 -39
- package/lib/memorax-code-backend/package.json +2 -3
- package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +0 -6
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +0 -6
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +1 -1
- package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
- package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-codex-adapter/package.json +1 -1
- package/lib/memorax-code-codex-adapter/skills/memorax-code/SKILL.md +0 -6
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +1 -1
- package/lib/memorax-code-dsh-adapter/package.json +1 -1
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/SKILL.md +0 -6
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-search.md +1 -1
- package/lib/memorax-code-opencode-adapter/package.json +1 -1
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/SKILL.md +0 -6
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-search.md +1 -1
- package/lib/memorax-code-opencode-adapter/src/plugin.mjs +121 -16
- package/lib/memorax-code-opencode-adapter/src/repo-memory-server-runner.mjs +2 -0
- package/package.json +1 -1
- package/lib/memorax-code-backend/dist/clients/codex/effective-prompt.js +0 -54
- package/lib/memorax-code-backend/dist/memory/writeback-reconciler.js +0 -244
- package/lib/memorax-code-backend/dist/memory/writeback-task-projection.js +0 -377
- package/lib/memorax-code-backend/dist/repository/readiness.js +0 -188
- package/lib/memorax-code-backend/dist/shared/incremental-jsonl-projection.js +0 -271
- package/lib/memorax-code-backend/dist/viewer/history/claude-transcript.js +0 -648
- package/lib/memorax-code-backend/dist/viewer/history/session-title.js +0 -93
- package/lib/memorax-code-backend/dist/viewer/http/public-routes.js +0 -146
- package/lib/memorax-code-backend/dist/viewer/model.js +0 -1
- package/lib/memorax-code-backend/dist/viewer/projection/activity.js +0 -113
- package/lib/memorax-code-backend/dist/viewer/projection/cache.js +0 -69
- package/lib/memorax-code-backend/dist/viewer/projection/event-identity.js +0 -3
- package/lib/memorax-code-backend/dist/viewer/projection/history.js +0 -79
- package/lib/memorax-code-backend/dist/viewer/projection/observability.js +0 -8
- package/lib/memorax-code-backend/dist/viewer/projection/redaction.js +0 -52
- package/lib/memorax-code-backend/dist/viewer/projection/turn-reference.js +0 -12
- package/lib/memorax-code-backend/dist/viewer/projection/user.js +0 -168
- package/lib/memorax-code-backend/dist/viewer/projection/writeback-status.js +0 -97
- package/lib/memorax-code-backend/dist/viewer/store.js +0 -991
- package/lib/memorax-code-backend/dist/viewer/ui/icon.js +0 -1
- package/lib/memorax-code-backend/dist/viewer/ui/logo.js +0 -1
- package/lib/memorax-code-backend/dist/viewer/ui/user-html.js +0 -117
|
@@ -82,10 +82,4 @@ Apply instructions in this order: system and developer instructions, `AGENTS.md`
|
|
|
82
82
|
|
|
83
83
|
For MemoraX Code coding memory, use `memorax-cli` exactly as described by the selected reference. Invoke this skill as `$memorax-code` in Codex or `/memorax-code` in Claude Code. In OpenCode, ask the agent to use the `memorax-code` skill by name. These invocation forms are not shell commands. `memorax-code` is the lifecycle CLI and must not be used for memory search or add. Do not call MemoraX HTTP endpoints directly.
|
|
84
84
|
|
|
85
|
-
When explaining Memory Viewer, describe only `/memory-viewer`. It is a
|
|
86
|
-
content-free local summary and must not expose conversation or memory text,
|
|
87
|
-
session or turn identifiers, paths, or trace details. The page never queries
|
|
88
|
-
MemoraX directly; it projects client-qualified local activity and keeps each
|
|
89
|
-
supported harness isolated.
|
|
90
|
-
|
|
91
85
|
Never store secrets, credentials, `.env` content, sensitive personal data, raw transcripts, hidden tests, exact patches, temporary target commits, or unsafe destructive commands. Do not announce internal routing or reference loading.
|
|
@@ -83,6 +83,6 @@ Do not add speculation, assistant-only praise, or a current instruction merely b
|
|
|
83
83
|
|
|
84
84
|
## Output
|
|
85
85
|
|
|
86
|
-
If a successful Add returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or
|
|
86
|
+
If a successful Add returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or anonymous-account claim details. A guest reminder may already contain the local Mark ID; present it once as part of the reminder, but do not repeat or separately quote it. Never run `memorax-code account --show-mark-id` for the user or ask for its output. Treat the reminder as user-facing operational output, not coding memory content, and continue the current task.
|
|
87
87
|
|
|
88
88
|
After an add request is accepted, confirm briefly that it was submitted for processing and identify the reusable coding lesson at a high level. If add is disabled or fails, report the issue and continue without bypassing the CLI.
|
|
@@ -88,6 +88,6 @@ Do not apply this retry to `memorax-cli add`, authentication or configuration fa
|
|
|
88
88
|
|
|
89
89
|
## Output
|
|
90
90
|
|
|
91
|
-
If a successful Search returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or
|
|
91
|
+
If a successful Search returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or anonymous-account claim details. A guest reminder may already contain the local Mark ID; present it once as part of the reminder, but do not repeat or separately quote it. Never run `memorax-code account --show-mark-id` for the user or ask for its output. Treat the reminder as user-facing operational output, not recalled memory, and continue the current task.
|
|
92
92
|
|
|
93
93
|
Mention only an invariant, pitfall, convention, or validation idea that materially affects the answer. Ground claims about current implementation behavior in live code and checks.
|
|
@@ -82,10 +82,4 @@ Apply instructions in this order: system and developer instructions, `AGENTS.md`
|
|
|
82
82
|
|
|
83
83
|
For MemoraX Code coding memory, use `memorax-cli` exactly as described by the selected reference. Invoke this skill as `$memorax-code` in Codex or `/memorax-code` in Claude Code. In OpenCode, ask the agent to use the `memorax-code` skill by name. These invocation forms are not shell commands. `memorax-code` is the lifecycle CLI and must not be used for memory search or add. Do not call MemoraX HTTP endpoints directly.
|
|
84
84
|
|
|
85
|
-
When explaining Memory Viewer, describe only `/memory-viewer`. It is a
|
|
86
|
-
content-free local summary and must not expose conversation or memory text,
|
|
87
|
-
session or turn identifiers, paths, or trace details. The page never queries
|
|
88
|
-
MemoraX directly; it projects client-qualified local activity and keeps each
|
|
89
|
-
supported harness isolated.
|
|
90
|
-
|
|
91
85
|
Never store secrets, credentials, `.env` content, sensitive personal data, raw transcripts, hidden tests, exact patches, temporary target commits, or unsafe destructive commands. Do not announce internal routing or reference loading.
|
|
@@ -83,6 +83,6 @@ Do not add speculation, assistant-only praise, or a current instruction merely b
|
|
|
83
83
|
|
|
84
84
|
## Output
|
|
85
85
|
|
|
86
|
-
If a successful Add returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or
|
|
86
|
+
If a successful Add returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or anonymous-account claim details. A guest reminder may already contain the local Mark ID; present it once as part of the reminder, but do not repeat or separately quote it. Never run `memorax-code account --show-mark-id` for the user or ask for its output. Treat the reminder as user-facing operational output, not coding memory content, and continue the current task.
|
|
87
87
|
|
|
88
88
|
After an add request is accepted, confirm briefly that it was submitted for processing and identify the reusable coding lesson at a high level. If add is disabled or fails, report the issue and continue without bypassing the CLI.
|
|
@@ -88,6 +88,6 @@ Do not apply this retry to `memorax-cli add`, authentication or configuration fa
|
|
|
88
88
|
|
|
89
89
|
## Output
|
|
90
90
|
|
|
91
|
-
If a successful Search returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or
|
|
91
|
+
If a successful Search returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or anonymous-account claim details. A guest reminder may already contain the local Mark ID; present it once as part of the reminder, but do not repeat or separately quote it. Never run `memorax-code account --show-mark-id` for the user or ask for its output. Treat the reminder as user-facing operational output, not recalled memory, and continue the current task.
|
|
92
92
|
|
|
93
93
|
Mention only an invariant, pitfall, convention, or validation idea that materially affects the answer. Ground claims about current implementation behavior in live code and checks.
|
|
@@ -103,13 +103,14 @@ export function createMemoraxOpenCodePlugin(options = {}) {
|
|
|
103
103
|
&& message.info.id === turn.userMessageId
|
|
104
104
|
&& message.info.sessionID === sessionId
|
|
105
105
|
));
|
|
106
|
-
const
|
|
106
|
+
const terminal = terminalTurnFor(
|
|
107
107
|
messages,
|
|
108
108
|
sessionId,
|
|
109
109
|
turn.userMessageId,
|
|
110
110
|
target?.assistantMessageId,
|
|
111
111
|
);
|
|
112
|
-
if (!user || !
|
|
112
|
+
if (!user || !terminal) continue;
|
|
113
|
+
const { assistant, evidence } = terminal;
|
|
113
114
|
let result;
|
|
114
115
|
try {
|
|
115
116
|
result = await postBackend(options, "/memory/writeback", {
|
|
@@ -118,7 +119,7 @@ export function createMemoraxOpenCodePlugin(options = {}) {
|
|
|
118
119
|
sessionId,
|
|
119
120
|
userMessageId: turn.userMessageId,
|
|
120
121
|
assistantMessageId: assistant.info.id,
|
|
121
|
-
messages:
|
|
122
|
+
messages: evidence,
|
|
122
123
|
cwd: workspaceRoot,
|
|
123
124
|
workspaceKind,
|
|
124
125
|
}, WRITEBACK_TIMEOUT_MS);
|
|
@@ -281,10 +282,13 @@ export function createMemoraxOpenCodePlugin(options = {}) {
|
|
|
281
282
|
return;
|
|
282
283
|
}
|
|
283
284
|
if (event?.type === "message.updated") {
|
|
284
|
-
const interrupted =
|
|
285
|
+
const interrupted = terminalErrorAssistantFromEvent(event, pendingTurns);
|
|
285
286
|
if (interrupted) {
|
|
286
287
|
track(
|
|
287
|
-
queueSessionFlush(
|
|
288
|
+
queueSessionFlush(
|
|
289
|
+
interrupted.sessionId,
|
|
290
|
+
interrupted.userMessageId ? interrupted : undefined,
|
|
291
|
+
),
|
|
288
292
|
"opencode interrupted turn finalization failed",
|
|
289
293
|
);
|
|
290
294
|
}
|
|
@@ -310,18 +314,16 @@ export function createMemoraxOpenCodePlugin(options = {}) {
|
|
|
310
314
|
export const MemoraxOpenCodePlugin = createMemoraxOpenCodePlugin();
|
|
311
315
|
export default MemoraxOpenCodePlugin;
|
|
312
316
|
|
|
313
|
-
function
|
|
314
|
-
|
|
317
|
+
function terminalTurnFor(messages, sessionId, userMessageId, assistantMessageId) {
|
|
318
|
+
const lineage = turnLineage(messages, sessionId, userMessageId);
|
|
319
|
+
if (!lineage || lineage.awaitingContinuation) return undefined;
|
|
320
|
+
const assistant = lineage.messages
|
|
315
321
|
.filter((message) => (
|
|
316
322
|
message?.info?.role === "assistant"
|
|
317
323
|
&& message.info.sessionID === sessionId
|
|
318
|
-
&& message.info.parentID ===
|
|
324
|
+
&& message.info.parentID === lineage.terminalUserMessageId
|
|
319
325
|
&& (!assistantMessageId || message.info.id === assistantMessageId)
|
|
320
326
|
&& Number.isFinite(message.info.time?.completed)
|
|
321
|
-
&& (
|
|
322
|
-
message.info.error === undefined
|
|
323
|
-
|| message.info.error?.name === "MessageAbortedError"
|
|
324
|
-
)
|
|
325
327
|
&& message.info.summary !== true
|
|
326
328
|
&& !message.parts?.some((part) => part?.type === "compaction")
|
|
327
329
|
))
|
|
@@ -330,24 +332,127 @@ function terminalAssistantFor(messages, sessionId, userMessageId, assistantMessa
|
|
|
330
332
|
|| String(left.info.id).localeCompare(String(right.info.id))
|
|
331
333
|
))
|
|
332
334
|
.at(-1);
|
|
335
|
+
if (!assistant) return undefined;
|
|
336
|
+
return {
|
|
337
|
+
assistant,
|
|
338
|
+
evidence: uniqueMessages([...lineage.evidence, assistant]),
|
|
339
|
+
};
|
|
333
340
|
}
|
|
334
341
|
|
|
335
|
-
function
|
|
342
|
+
function turnLineage(messages, sessionId, userMessageId) {
|
|
343
|
+
const sessionMessages = messages.filter((message) => (
|
|
344
|
+
message?.info?.sessionID === sessionId
|
|
345
|
+
&& stringValue(message.info.id)
|
|
346
|
+
&& Array.isArray(message.parts)
|
|
347
|
+
));
|
|
348
|
+
const startIndex = sessionMessages.findIndex((message) => (
|
|
349
|
+
message.info.role === "user" && message.info.id === userMessageId
|
|
350
|
+
));
|
|
351
|
+
if (startIndex < 0) return undefined;
|
|
352
|
+
const original = sessionMessages[startIndex];
|
|
353
|
+
const lineageAssistants = new Map();
|
|
354
|
+
const evidence = [original];
|
|
355
|
+
let terminalUserMessageId = userMessageId;
|
|
356
|
+
let awaitingContinuation = false;
|
|
357
|
+
|
|
358
|
+
for (const message of sessionMessages.slice(startIndex + 1)) {
|
|
359
|
+
if (message.info.role === "assistant") {
|
|
360
|
+
if (message.info.parentID === terminalUserMessageId) {
|
|
361
|
+
lineageAssistants.set(message.info.id, message);
|
|
362
|
+
}
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (message.info.role !== "user") continue;
|
|
366
|
+
if (hasCompactionPart(message.parts)) {
|
|
367
|
+
const compactionTailId = compactionTailStartId(message, sessionId);
|
|
368
|
+
if (!compactionTailId || awaitingContinuation) return undefined;
|
|
369
|
+
const tail = lineageAssistants.get(compactionTailId);
|
|
370
|
+
if (!tail) return undefined;
|
|
371
|
+
evidence.push(minimalAssistantEvidence(tail), message);
|
|
372
|
+
awaitingContinuation = true;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (isCompactionContinuation(message, sessionId)) {
|
|
376
|
+
if (!awaitingContinuation) return undefined;
|
|
377
|
+
terminalUserMessageId = message.info.id;
|
|
378
|
+
evidence.push(message);
|
|
379
|
+
awaitingContinuation = false;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
messages: sessionMessages.slice(startIndex + 1),
|
|
386
|
+
terminalUserMessageId,
|
|
387
|
+
awaitingContinuation,
|
|
388
|
+
evidence,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function hasCompactionPart(parts) {
|
|
393
|
+
return parts.some((part) => part?.type === "compaction");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function compactionTailStartId(message, sessionId) {
|
|
397
|
+
const messageId = stringValue(message?.info?.id);
|
|
398
|
+
const parts = message?.parts?.filter((part) => (
|
|
399
|
+
part?.type === "compaction"
|
|
400
|
+
&& part.sessionID === sessionId
|
|
401
|
+
&& part.messageID === messageId
|
|
402
|
+
)) ?? [];
|
|
403
|
+
if (parts.length !== 1) return undefined;
|
|
404
|
+
return stringValue(parts[0].tail_start_id);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function minimalAssistantEvidence(message) {
|
|
408
|
+
return {
|
|
409
|
+
info: {
|
|
410
|
+
id: message.info.id,
|
|
411
|
+
sessionID: message.info.sessionID,
|
|
412
|
+
role: message.info.role,
|
|
413
|
+
parentID: message.info.parentID,
|
|
414
|
+
},
|
|
415
|
+
parts: [],
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function isCompactionContinuation(message, sessionId) {
|
|
420
|
+
const messageId = stringValue(message?.info?.id);
|
|
421
|
+
return message?.parts?.some((part) => (
|
|
422
|
+
part?.type === "text"
|
|
423
|
+
&& part.synthetic === true
|
|
424
|
+
&& part.metadata?.compaction_continue === true
|
|
425
|
+
&& part.sessionID === sessionId
|
|
426
|
+
&& part.messageID === messageId
|
|
427
|
+
)) === true;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function uniqueMessages(messages) {
|
|
431
|
+
const unique = new Map();
|
|
432
|
+
for (const message of messages) unique.set(message.info.id, message);
|
|
433
|
+
return [...unique.values()];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function terminalErrorAssistantFromEvent(event, pendingTurns) {
|
|
336
437
|
const info = event?.properties?.info;
|
|
337
438
|
const sessionId = stringValue(info?.sessionID);
|
|
338
439
|
const userMessageId = stringValue(info?.parentID);
|
|
339
440
|
const assistantMessageId = stringValue(info?.id);
|
|
340
441
|
if (
|
|
341
442
|
info?.role !== "assistant"
|
|
342
|
-
|| info?.error?.name
|
|
443
|
+
|| !stringValue(info?.error?.name)
|
|
343
444
|
|| !Number.isFinite(info?.time?.completed)
|
|
344
445
|
|| info?.summary === true
|
|
345
446
|
|| !sessionId
|
|
346
447
|
|| !userMessageId
|
|
347
448
|
|| !assistantMessageId
|
|
348
|
-
|| !pendingTurns.has(turnKey({ sessionId, userMessageId }))
|
|
349
449
|
) return undefined;
|
|
350
|
-
|
|
450
|
+
if (pendingTurns.has(turnKey({ sessionId, userMessageId }))) {
|
|
451
|
+
return { sessionId, userMessageId, assistantMessageId };
|
|
452
|
+
}
|
|
453
|
+
return [...pendingTurns.values()].some((turn) => turn.sessionId === sessionId)
|
|
454
|
+
? { sessionId }
|
|
455
|
+
: undefined;
|
|
351
456
|
}
|
|
352
457
|
|
|
353
458
|
function textParts(parts) {
|
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
export const OPENCODE_REPO_MEMORY_AGENT = "memorax-code-repo-memory";
|
|
8
8
|
|
|
9
9
|
const OWNED_SERVER_USERNAME = "memorax-code";
|
|
10
|
+
const OWNED_SERVER_DATABASE = ":memory:";
|
|
10
11
|
const DEFAULT_SERVER_START_TIMEOUT_MS = 20_000;
|
|
11
12
|
const DEFAULT_SERVER_STOP_TIMEOUT_MS = 5_000;
|
|
12
13
|
const MAX_SERVER_START_OUTPUT = 8_192;
|
|
@@ -155,6 +156,7 @@ async function startOwnedOpenCodeServer(input) {
|
|
|
155
156
|
cwd: input.directory,
|
|
156
157
|
env: {
|
|
157
158
|
...input.env,
|
|
159
|
+
OPENCODE_DB: OWNED_SERVER_DATABASE,
|
|
158
160
|
OPENCODE_SERVER_USERNAME: username,
|
|
159
161
|
OPENCODE_SERVER_PASSWORD: password,
|
|
160
162
|
},
|
package/package.json
CHANGED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
export function stripCodexPreambleSegments(text) {
|
|
2
|
-
let remaining = text;
|
|
3
|
-
for (;;) {
|
|
4
|
-
const next = stripOneLeadingCodexPreambleSegment(remaining);
|
|
5
|
-
if (next === remaining) {
|
|
6
|
-
return stripCodexSkillSegments(stripCodexAmbientUiSegments(remaining)).trim();
|
|
7
|
-
}
|
|
8
|
-
remaining = next.trimStart();
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
function stripOneLeadingCodexPreambleSegment(text) {
|
|
12
|
-
return stripLeadingEnvironmentContext(text)
|
|
13
|
-
?? stripLeadingInstructionsBlock(text)
|
|
14
|
-
?? stripLeadingSessionPreamble(text)
|
|
15
|
-
?? text;
|
|
16
|
-
}
|
|
17
|
-
function stripCodexSkillSegments(text) {
|
|
18
|
-
return text.replace(/(^|\n)[^\S\n]*<skill\b[\s\S]*?<\/skill>[^\S\n]*(?=\n|$)/gi, (match, prefix) => {
|
|
19
|
-
const block = match.slice(prefix.length).trim();
|
|
20
|
-
return isCodexSkillSegment(block) ? prefix : match;
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
function stripCodexAmbientUiSegments(text) {
|
|
24
|
-
let removedAmbientContext = false;
|
|
25
|
-
const withoutAmbientContext = text.replace(/(^|\n)[^\S\n]*<in-app-browser-context\b[\s\S]*?<\/in-app-browser-context>[^\S\n]*(?=\n|$)/gi, (_match, prefix) => {
|
|
26
|
-
removedAmbientContext = true;
|
|
27
|
-
return prefix;
|
|
28
|
-
});
|
|
29
|
-
if (!removedAmbientContext)
|
|
30
|
-
return text;
|
|
31
|
-
return withoutAmbientContext
|
|
32
|
-
.replace(/(^|\n)[^\S\n]*##\s+My request for Codex:[^\S\n]*(?=\n|$)/gi, "$1")
|
|
33
|
-
.replace(/\n[^\S\n]*(?:\n[^\S\n]*){2,}/g, "\n\n");
|
|
34
|
-
}
|
|
35
|
-
function isCodexSkillSegment(text) {
|
|
36
|
-
return /^<skill\b/i.test(text)
|
|
37
|
-
&& /<name>[^<\n]+<\/name>/i.test(text)
|
|
38
|
-
&& /<path>[\s\S]*?SKILL\.md[\s\S]*?<\/path>/i.test(text);
|
|
39
|
-
}
|
|
40
|
-
function stripLeadingEnvironmentContext(text) {
|
|
41
|
-
const match = text.match(/^\s*<environment_context\b[\s\S]*?<\/environment_context>\s*/);
|
|
42
|
-
return match ? text.slice(match[0].length) : undefined;
|
|
43
|
-
}
|
|
44
|
-
function stripLeadingInstructionsBlock(text) {
|
|
45
|
-
const match = text.match(/^\s*#\s+(?:AGENTS|CLAUDE)\.md[^\n]*[\s\S]*?<\/INSTRUCTIONS>\s*/i);
|
|
46
|
-
return match ? text.slice(match[0].length) : undefined;
|
|
47
|
-
}
|
|
48
|
-
function stripLeadingSessionPreamble(text) {
|
|
49
|
-
const trimmed = text.trimStart();
|
|
50
|
-
if (!trimmed.startsWith("You are Claude Code"))
|
|
51
|
-
return undefined;
|
|
52
|
-
const boundary = trimmed.indexOf("\n\n");
|
|
53
|
-
return boundary >= 0 ? trimmed.slice(boundary + 2) : "";
|
|
54
|
-
}
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { backendDebug } from "../shared/debug-log.js";
|
|
3
|
-
import { callMemoAddStatus, memoraxConfigFromEnv } from "../provider/memorax/adapter.js";
|
|
4
|
-
import { MEMORAX_PROVIDER_ID, memoraxWritebackEnabled } from "../provider/memorax/config.js";
|
|
5
|
-
import { completeMemoryWritebackTask, createMemoryWritebackTaskProjection, } from "./writeback-task-projection.js";
|
|
6
|
-
import { clientTraceConfigFromEnv } from "../trace/config.js";
|
|
7
|
-
import { recordTraceEvent } from "../trace/store.js";
|
|
8
|
-
const DEFAULT_INTERVAL_MS = 60_000;
|
|
9
|
-
const DEFAULT_MAX_TASKS_PER_RUN = 20;
|
|
10
|
-
const DEFAULT_CONCURRENCY = 4;
|
|
11
|
-
const DEFAULT_MAX_BACKOFF_MS = 60 * 60_000;
|
|
12
|
-
export async function reconcileMemoryWritebackStatuses(options) {
|
|
13
|
-
return reconcileMemoryWritebackStatusesWithPolicy(options);
|
|
14
|
-
}
|
|
15
|
-
export function startMemoryWritebackReconciler(options) {
|
|
16
|
-
let closed = false;
|
|
17
|
-
let active;
|
|
18
|
-
let candidateOffset = 0;
|
|
19
|
-
const intervalMs = positiveInteger(options.intervalMs, DEFAULT_INTERVAL_MS);
|
|
20
|
-
const maxTasks = positiveInteger(options.maxTasks, DEFAULT_MAX_TASKS_PER_RUN);
|
|
21
|
-
const taskProjection = resolveTaskProjection(options);
|
|
22
|
-
const policy = createReconcileBackoffPolicy({
|
|
23
|
-
now: options.now ?? Date.now,
|
|
24
|
-
baseDelayMs: intervalMs,
|
|
25
|
-
maxDelayMs: Math.max(intervalMs, positiveInteger(options.maxBackoffMs, DEFAULT_MAX_BACKOFF_MS)),
|
|
26
|
-
});
|
|
27
|
-
const runNow = () => {
|
|
28
|
-
if (closed)
|
|
29
|
-
return Promise.resolve(emptyReport());
|
|
30
|
-
if (active)
|
|
31
|
-
return active;
|
|
32
|
-
active = reconcileMemoryWritebackStatusesWithPolicy({
|
|
33
|
-
...options,
|
|
34
|
-
maxTasks,
|
|
35
|
-
candidateOffset,
|
|
36
|
-
taskProjection,
|
|
37
|
-
}, policy).finally(() => {
|
|
38
|
-
candidateOffset += maxTasks;
|
|
39
|
-
active = undefined;
|
|
40
|
-
});
|
|
41
|
-
return active;
|
|
42
|
-
};
|
|
43
|
-
const scheduleRun = () => {
|
|
44
|
-
void runNow().catch((error) => {
|
|
45
|
-
backendDebug("memory_writeback_reconciler.run_failed", {
|
|
46
|
-
error: error instanceof Error ? error.message : String(error),
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
};
|
|
50
|
-
scheduleRun();
|
|
51
|
-
const timer = setInterval(scheduleRun, intervalMs);
|
|
52
|
-
timer.unref?.();
|
|
53
|
-
return {
|
|
54
|
-
runNow,
|
|
55
|
-
async close() {
|
|
56
|
-
closed = true;
|
|
57
|
-
clearInterval(timer);
|
|
58
|
-
await active?.catch(() => undefined);
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
async function reconcileMemoryWritebackStatusesWithPolicy(options, policy) {
|
|
63
|
-
const env = { ...(options.env ?? process.env), MEMORAX_CODE_HOME: options.memoraxCodeHome };
|
|
64
|
-
const config = memoraxConfigFromEnv(env);
|
|
65
|
-
const taskProjection = resolveTaskProjection(options);
|
|
66
|
-
if (!clientTraceConfigFromEnv(taskProjection.client, env).enabled
|
|
67
|
-
|| !config.ok
|
|
68
|
-
|| !memoraxWritebackEnabled(env)) {
|
|
69
|
-
return emptyReport();
|
|
70
|
-
}
|
|
71
|
-
const maxTasks = positiveInteger(options.maxTasks, DEFAULT_MAX_TASKS_PER_RUN);
|
|
72
|
-
const concurrency = positiveInteger(options.concurrency, DEFAULT_CONCURRENCY);
|
|
73
|
-
const pendingWritebacks = await taskProjection.listPending();
|
|
74
|
-
policy?.retainPending(pendingWritebacks);
|
|
75
|
-
const dueWritebacks = pendingWritebacks.filter((task) => policy?.isDue(task) ?? true);
|
|
76
|
-
const candidates = rotatedBatch(dueWritebacks, maxTasks, options.candidateOffset ?? 0);
|
|
77
|
-
const queue = [...candidates];
|
|
78
|
-
const report = { inspected: 0, persisted: 0, pending: 0, failed: 0 };
|
|
79
|
-
const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
|
|
80
|
-
for (;;) {
|
|
81
|
-
const task = queue.shift();
|
|
82
|
-
if (!task)
|
|
83
|
-
return;
|
|
84
|
-
report.inspected += 1;
|
|
85
|
-
try {
|
|
86
|
-
const raw = await callMemoAddStatus(config.config, task.taskId, options.fetchImpl);
|
|
87
|
-
const status = normalizeAddStatus(raw);
|
|
88
|
-
if (isPendingWritebackStatus(status.status)) {
|
|
89
|
-
report.pending += 1;
|
|
90
|
-
policy?.recordResult(task, "pending");
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
await persistTerminalWritebackStatus(options.memoraxCodeHome, env, task, status);
|
|
94
|
-
report.persisted += 1;
|
|
95
|
-
policy?.recordResult(task, "persisted");
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
report.failed += 1;
|
|
99
|
-
policy?.recordResult(task, "failed");
|
|
100
|
-
backendDebug("memory_writeback_reconciler.status_failed", {
|
|
101
|
-
taskId: task.taskId,
|
|
102
|
-
error: error instanceof Error ? error.message : String(error),
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
await Promise.all(workers);
|
|
108
|
-
return report;
|
|
109
|
-
}
|
|
110
|
-
function resolveTaskProjection(options) {
|
|
111
|
-
return options.taskProjection ?? createMemoryWritebackTaskProjection({
|
|
112
|
-
memoraxCodeHome: options.memoraxCodeHome,
|
|
113
|
-
client: options.client ?? "codex",
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
async function persistTerminalWritebackStatus(memoraxCodeHome, env, task, status) {
|
|
117
|
-
const completed = completeMemoryWritebackTask({
|
|
118
|
-
status: status.status,
|
|
119
|
-
...(status.memoryKnown ? { memory: status.memory, memoryKnown: true } : {}),
|
|
120
|
-
error: status.error,
|
|
121
|
-
});
|
|
122
|
-
const capturedAt = new Date().toISOString();
|
|
123
|
-
const recorded = await recordTraceEvent({
|
|
124
|
-
memoraxCodeHome,
|
|
125
|
-
env,
|
|
126
|
-
eventId: terminalEventId(task, completed.status),
|
|
127
|
-
traceContext: {
|
|
128
|
-
schemaVersion: "1",
|
|
129
|
-
client: task.client,
|
|
130
|
-
sessionId: task.sessionId,
|
|
131
|
-
...(task.turnId ? { turnId: task.turnId } : {}),
|
|
132
|
-
...(task.memoryProject ? { memoryProject: task.memoryProject } : {}),
|
|
133
|
-
contextOrigin: "manual",
|
|
134
|
-
capturedAt,
|
|
135
|
-
},
|
|
136
|
-
type: "memory_writeback_status",
|
|
137
|
-
source: "writeback_reconciler",
|
|
138
|
-
operation: "writeback",
|
|
139
|
-
ok: completed.ok,
|
|
140
|
-
request: {
|
|
141
|
-
task_id: task.taskId,
|
|
142
|
-
original_event_id: task.eventId,
|
|
143
|
-
provider: MEMORAX_PROVIDER_ID,
|
|
144
|
-
},
|
|
145
|
-
response: {
|
|
146
|
-
taskId: task.taskId,
|
|
147
|
-
status: completed.status,
|
|
148
|
-
outcome: completed.outcome,
|
|
149
|
-
...(completed.savedMemoryCount === undefined ? {} : { savedMemoryCount: completed.savedMemoryCount }),
|
|
150
|
-
...(completed.savedMemoryIds?.length ? { savedMemoryIds: completed.savedMemoryIds } : {}),
|
|
151
|
-
...(completed.savedMemories?.length ? { savedMemories: completed.savedMemories } : {}),
|
|
152
|
-
},
|
|
153
|
-
error: completed.error,
|
|
154
|
-
});
|
|
155
|
-
if (!recorded.written && recorded.reason !== "duplicate_event") {
|
|
156
|
-
throw new Error(`writeback terminal status was not persisted: ${recorded.reason}`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
function createReconcileBackoffPolicy(options) {
|
|
160
|
-
const retries = new Map();
|
|
161
|
-
return {
|
|
162
|
-
retainPending(tasks) {
|
|
163
|
-
const pendingKeys = new Set(tasks.map(reconcileCandidateKey));
|
|
164
|
-
for (const key of retries.keys()) {
|
|
165
|
-
if (!pendingKeys.has(key))
|
|
166
|
-
retries.delete(key);
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
isDue(task) {
|
|
170
|
-
const key = reconcileCandidateKey(task);
|
|
171
|
-
return (retries.get(key)?.nextAttemptAt ?? 0) <= options.now();
|
|
172
|
-
},
|
|
173
|
-
recordResult(task, result) {
|
|
174
|
-
const key = reconcileCandidateKey(task);
|
|
175
|
-
if (result === "persisted") {
|
|
176
|
-
retries.delete(key);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
const attempts = (retries.get(key)?.attempts ?? 0) + 1;
|
|
180
|
-
const multiplier = 2 ** Math.min(attempts - 1, 30);
|
|
181
|
-
const delayMs = Math.min(options.maxDelayMs, options.baseDelayMs * multiplier);
|
|
182
|
-
retries.set(key, {
|
|
183
|
-
attempts,
|
|
184
|
-
nextAttemptAt: options.now() + delayMs,
|
|
185
|
-
});
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
function reconcileCandidateKey(task) {
|
|
190
|
-
return JSON.stringify([
|
|
191
|
-
task.client,
|
|
192
|
-
task.sessionId,
|
|
193
|
-
task.eventId,
|
|
194
|
-
task.taskId,
|
|
195
|
-
]);
|
|
196
|
-
}
|
|
197
|
-
function normalizeAddStatus(raw) {
|
|
198
|
-
const envelope = record(raw);
|
|
199
|
-
const data = record(envelope?.data) ?? envelope;
|
|
200
|
-
const status = typeof data?.status === "string" ? data.status.trim().toLowerCase() : "unknown";
|
|
201
|
-
const error = typeof data?.error === "string" && data.error.trim() ? data.error.trim() : null;
|
|
202
|
-
const hasMemoryField = Boolean(data)
|
|
203
|
-
&& (Object.prototype.hasOwnProperty.call(data, "memory")
|
|
204
|
-
|| Object.prototype.hasOwnProperty.call(data, "memories"));
|
|
205
|
-
const memory = data?.memory ?? data?.memories;
|
|
206
|
-
const memoryKnown = hasMemoryField && memory !== null && memory !== undefined;
|
|
207
|
-
return {
|
|
208
|
-
status,
|
|
209
|
-
...(memoryKnown ? { memory } : {}),
|
|
210
|
-
memoryKnown,
|
|
211
|
-
error,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
function isPendingWritebackStatus(status) {
|
|
215
|
-
return status === "accepted"
|
|
216
|
-
|| status === "processing"
|
|
217
|
-
|| status === "queued"
|
|
218
|
-
|| status === "pending"
|
|
219
|
-
|| status === "unknown";
|
|
220
|
-
}
|
|
221
|
-
function terminalEventId(task, status) {
|
|
222
|
-
const digest = createHash("sha256")
|
|
223
|
-
.update(`${task.sessionId}\u0000${task.eventId}\u0000${task.taskId}\u0000${status}`)
|
|
224
|
-
.digest("hex")
|
|
225
|
-
.slice(0, 32);
|
|
226
|
-
return `memory-writeback-status-${digest}`;
|
|
227
|
-
}
|
|
228
|
-
function positiveInteger(value, fallback) {
|
|
229
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : fallback;
|
|
230
|
-
}
|
|
231
|
-
function rotatedBatch(items, limit, offset) {
|
|
232
|
-
if (items.length <= limit)
|
|
233
|
-
return [...items];
|
|
234
|
-
const start = Number.isSafeInteger(offset) && offset > 0 ? offset % items.length : 0;
|
|
235
|
-
return Array.from({ length: limit }, (_, index) => items[(start + index) % items.length]);
|
|
236
|
-
}
|
|
237
|
-
function emptyReport() {
|
|
238
|
-
return { inspected: 0, persisted: 0, pending: 0, failed: 0 };
|
|
239
|
-
}
|
|
240
|
-
function record(value) {
|
|
241
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
242
|
-
? value
|
|
243
|
-
: undefined;
|
|
244
|
-
}
|