@memorax/memorax-code 0.1.5 → 0.1.7
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 +7 -18
- package/bin/memorax-code-npm-preinstall.mjs +28 -39
- package/bin/memorax-code-plugin-postinstall.mjs +23 -1537
- package/bin/memorax-code-setup.mjs +1615 -0
- package/bin/memorax-code.mjs +289 -5
- package/docs/configuration.md +66 -17
- package/docs/troubleshooting.md +123 -7
- package/lib/memorax-code-adapter-common/src/config-utils.d.mts +5 -1
- package/lib/memorax-code-adapter-common/src/config-utils.mjs +36 -3
- package/lib/memorax-code-adapter-common/src/credentials/linux-secret-service.mjs +110 -0
- package/lib/memorax-code-adapter-common/src/credentials/macos-keychain.mjs +224 -0
- package/lib/memorax-code-adapter-common/src/credentials/secure-command.mjs +340 -0
- package/lib/memorax-code-adapter-common/src/credentials/trial-credential-record.d.mts +82 -0
- package/lib/memorax-code-adapter-common/src/credentials/trial-credential-record.mjs +183 -0
- package/lib/memorax-code-adapter-common/src/credentials/trial-credential-store.d.mts +82 -0
- package/lib/memorax-code-adapter-common/src/credentials/trial-credential-store.mjs +271 -0
- package/lib/memorax-code-adapter-common/src/credentials/windows-dpapi.mjs +440 -0
- package/lib/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +7 -4
- package/lib/memorax-code-adapter-common/src/setup-completion.d.mts +81 -0
- package/lib/memorax-code-adapter-common/src/setup-completion.mjs +154 -0
- package/lib/memorax-code-backend/dist/clients/claude/memory-hook-runtime.js +21 -2
- package/lib/memorax-code-backend/dist/clients/codex/memory-hook-runtime.js +23 -3
- package/lib/memorax-code-backend/dist/clients/opencode/memory-hook-runtime.js +19 -1
- package/lib/memorax-code-backend/dist/entrypoints/backend-cli.js +17 -16
- package/lib/memorax-code-backend/dist/lifecycle/backend/service.js +1 -0
- package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +81 -22
- package/lib/memorax-code-backend/dist/memorax-cli.js +3 -0
- package/lib/memorax-code-backend/dist/memory/automatic-retrieval.js +8 -2
- package/lib/memorax-code-backend/dist/memory/automatic-writeback.js +13 -1
- package/lib/memorax-code-backend/dist/memory/cli.js +10 -0
- package/lib/memorax-code-backend/dist/memory/quota-notice.js +208 -0
- package/lib/memorax-code-backend/dist/memory/service.js +11 -0
- package/lib/memorax-code-backend/dist/provider/memorax/adapter.js +4 -2
- package/lib/memorax-code-backend/dist/provider/memorax/http.js +10 -3
- package/lib/memorax-code-backend/dist/provider/memorax/quota.js +26 -0
- package/lib/memorax-code-backend/package.json +1 -1
- 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/runtime-hooks/memory-turn.mjs +5 -3
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +2 -0
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +4 -2
- 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/memorax-code-adapter-common/src/config-utils.d.mts +5 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.mjs +36 -3
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/linux-secret-service.mjs +110 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/macos-keychain.mjs +224 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/secure-command.mjs +340 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-record.d.mts +82 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-record.mjs +183 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-store.d.mts +82 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-store.mjs +271 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/windows-dpapi.mjs +440 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +7 -4
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/setup-completion.d.mts +81 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/setup-completion.mjs +154 -0
- 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/runtime-hooks/memory-turn.mjs +5 -3
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +2 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +4 -2
- 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/runtime-hooks/memory-skill-reminder.mjs +2 -0
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +2 -0
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +4 -2
- package/lib/memorax-code-dsh-adapter/hooks/repo-memory-job.mjs +5 -2
- package/lib/memorax-code-dsh-adapter/package.json +1 -1
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-add.md +2 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-search.md +4 -2
- package/lib/memorax-code-dsh-adapter/src/profile-lifecycle.mjs +158 -7
- package/lib/memorax-code-dsh-adapter/src/runtime-state.mjs +21 -0
- package/lib/memorax-code-opencode-adapter/package.json +1 -1
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-add.md +2 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-search.md +4 -2
- package/lib/memorax-code-opencode-adapter/src/plugin.mjs +20 -0
- package/lib/package-transition.mjs +312 -0
- package/lib/resolve-codex-command.mjs +32 -6
- package/lib/setup-memory-preferences.mjs +113 -0
- package/lib/setup-reconcile.mjs +128 -0
- package/lib/trial-plugin-mark.mjs +171 -0
- package/lib/trial-provision-client.mjs +307 -0
- package/lib/trial-provision-flow.mjs +215 -0
- package/lib/trial-setup.mjs +53 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { BackendConnectionAuthorityError } from "../../../memorax-code-adapter-common/src/backend-connection.mjs";
|
|
7
7
|
import { RuntimeRecordError } from "../../../memorax-code-adapter-common/src/runtime-record.mjs";
|
|
8
|
+
import { withSetupCompletionLock } from "../../../memorax-code-adapter-common/src/setup-completion.mjs";
|
|
8
9
|
import { runBackendStatus } from "./backend/status.js";
|
|
9
10
|
import { readBackendServiceState, preflightBackendServiceStart, startBackendService, stopBackendService, } from "./backend/service.js";
|
|
10
11
|
import { backendServiceHome, BackendLifecycleLockError, withBackendLifecycleLock, } from "./lock.js";
|
|
@@ -17,6 +18,7 @@ import { claudeAdapterLifecycle } from "../clients/claude/lifecycle.js";
|
|
|
17
18
|
import { collectDshAdapterLifecycleStatus, withDshAdapterLifecycleLock, } from "../clients/dsh/lifecycle.js";
|
|
18
19
|
import { openCodeAdapterLifecycle } from "../clients/opencode/lifecycle.js";
|
|
19
20
|
const PACKAGE_REPLACEMENT_ENV = "MEMORAX_CODE_PACKAGE_REPLACEMENT";
|
|
21
|
+
const DSH_OPTIONAL_ENV = "MEMORAX_CODE_DSH_ADAPTER_OPTIONAL";
|
|
20
22
|
const DSH_RECOVERY_ENV = "MEMORAX_CODE_DSH_ADAPTER_RECOVERY";
|
|
21
23
|
const DSH_RECOVERY_REVISION_ENV = "MEMORAX_CODE_DSH_ADAPTER_EXPECTED_REVISION";
|
|
22
24
|
export async function collectMemoraxCodeStatus(backendUrl, backendToken, serviceOptions, argv) {
|
|
@@ -50,9 +52,9 @@ export async function collectMemoraxCodeStatus(backendUrl, backendToken, service
|
|
|
50
52
|
const claudeAdapter = clients.claude
|
|
51
53
|
? await claudeAdapterLifecycle.status({ argv, serviceOptions, backendUrl })
|
|
52
54
|
: undefined;
|
|
53
|
-
const dshAdapter = clients.dsh
|
|
55
|
+
const dshAdapter = markOptionalDshAdapter(clients.dsh
|
|
54
56
|
? await collectDshAdapterLifecycleStatus({ argv, serviceOptions, backendUrl })
|
|
55
|
-
: undefined;
|
|
57
|
+
: undefined, clients.dsh && isOptionalDshSelection(argv));
|
|
56
58
|
const opencodeAdapter = clients.opencode
|
|
57
59
|
? await openCodeAdapterLifecycle.status({ argv, serviceOptions, backendUrl })
|
|
58
60
|
: undefined;
|
|
@@ -60,13 +62,15 @@ export async function collectMemoraxCodeStatus(backendUrl, backendToken, service
|
|
|
60
62
|
const claudeReady = claudeAdapter ? isAdapterReady(claudeAdapter) : true;
|
|
61
63
|
const dshReady = dshAdapter ? isAdapterReady(dshAdapter) : true;
|
|
62
64
|
const opencodeReady = opencodeAdapter ? isAdapterReady(opencodeAdapter) : true;
|
|
65
|
+
const optionalDshUnavailable = isOptionalUnavailableDshAdapter(dshAdapter);
|
|
63
66
|
return {
|
|
64
67
|
ok: backend.ok
|
|
65
68
|
&& codexReady
|
|
66
69
|
&& opencodeReady
|
|
67
70
|
&& (isOptionalUnconfiguredClaudeAdapter(claudeAdapter, codexAdapter) || claudeReady)
|
|
68
|
-
&& (
|
|
71
|
+
&& (optionalDshUnavailable || dshReady),
|
|
69
72
|
action: "status",
|
|
73
|
+
...(optionalDshUnavailable ? { degraded: true } : {}),
|
|
70
74
|
backend,
|
|
71
75
|
...(codexAdapter ? { codexAdapter } : {}),
|
|
72
76
|
...(claudeAdapter ? { claudeAdapter } : {}),
|
|
@@ -104,16 +108,22 @@ async function startMemoraxCodeServiceLocked(serviceOptions, argv) {
|
|
|
104
108
|
if (serviceStateFailure) {
|
|
105
109
|
return { ok: false, action: "start", backend: serviceStateFailure };
|
|
106
110
|
}
|
|
107
|
-
const
|
|
108
|
-
|
|
111
|
+
const packageReplacement = isPackageReplacement();
|
|
112
|
+
// Active client intent must not bypass strict lifecycle config validation.
|
|
113
|
+
if (packageReplacement)
|
|
114
|
+
loadManagedClientsConfig(memoraxCodeHome);
|
|
115
|
+
const requestedClients = managedClientsFor(argv, serviceOptions, {
|
|
116
|
+
preferActive: packageReplacement,
|
|
117
|
+
});
|
|
109
118
|
const clients = isDshAdapterRecovery()
|
|
110
119
|
? { ...requestedClients, dsh: true }
|
|
111
120
|
: requestedClients;
|
|
112
121
|
const previousClients = readActiveManagedClients(memoraxCodeHome);
|
|
122
|
+
const optionalDsh = clients.dsh && isOptionalDshSelection(argv);
|
|
113
123
|
const context = { argv, serviceOptions };
|
|
114
124
|
if (clients.dsh || previousClients?.dsh || isDshAdapterRecovery()) {
|
|
115
125
|
try {
|
|
116
|
-
return await withDshAdapterLifecycleLock(context, (dshLifecycle) => (executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memoraxCodeHome, clients, previousClients, dshLifecycle)));
|
|
126
|
+
return await withDshAdapterLifecycleLock(context, (dshLifecycle) => (executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memoraxCodeHome, clients, previousClients, dshLifecycle, optionalDsh)));
|
|
117
127
|
}
|
|
118
128
|
catch (error) {
|
|
119
129
|
return dshLifecycleFailure("start", error);
|
|
@@ -121,7 +131,7 @@ async function startMemoraxCodeServiceLocked(serviceOptions, argv) {
|
|
|
121
131
|
}
|
|
122
132
|
return executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memoraxCodeHome, clients, previousClients);
|
|
123
133
|
}
|
|
124
|
-
async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memoraxCodeHome, clients, previousClients, dshLifecycle) {
|
|
134
|
+
async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memoraxCodeHome, clients, previousClients, dshLifecycle, optionalDsh = false) {
|
|
125
135
|
const lifecycleContext = { argv, serviceOptions };
|
|
126
136
|
const lifecycleBackendContext = { ...lifecycleContext, backendUrl };
|
|
127
137
|
if (isDshAdapterRecovery() && dshLifecycle) {
|
|
@@ -148,7 +158,7 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
|
|
|
148
158
|
const quiescedDsh = dshLifecycle && (clients.dsh || previousClients?.dsh)
|
|
149
159
|
? await dshLifecycle.quiesce?.(lifecycleContext)
|
|
150
160
|
: undefined;
|
|
151
|
-
if (quiescedDsh?.ok === false) {
|
|
161
|
+
if (quiescedDsh?.ok === false && !optionalDsh) {
|
|
152
162
|
return {
|
|
153
163
|
ok: false,
|
|
154
164
|
action: "start",
|
|
@@ -249,10 +259,12 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
|
|
|
249
259
|
opencodeAdapter,
|
|
250
260
|
};
|
|
251
261
|
}
|
|
252
|
-
const preparedDshAdapter =
|
|
253
|
-
?
|
|
254
|
-
:
|
|
255
|
-
|
|
262
|
+
const preparedDshAdapter = markOptionalDshAdapter(quiescedDsh?.ok === false
|
|
263
|
+
? quiescedDsh
|
|
264
|
+
: clients.dsh && dshLifecycle
|
|
265
|
+
? await dshLifecycle.prepareEnable(lifecycleBackendContext)
|
|
266
|
+
: undefined, optionalDsh);
|
|
267
|
+
if (preparedDshAdapter?.ok === false && !optionalDsh) {
|
|
256
268
|
const recovery = await recoverPreparationFailure("dsh_adapter_enable_failed");
|
|
257
269
|
return {
|
|
258
270
|
ok: false,
|
|
@@ -286,12 +298,15 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
|
|
|
286
298
|
...(disabledOpenCode ? { opencodeAdapter: disabledOpenCode } : {}),
|
|
287
299
|
};
|
|
288
300
|
}
|
|
289
|
-
const dshAdapter = preparedDshAdapter?.installed === true
|
|
301
|
+
const dshAdapter = markOptionalDshAdapter(preparedDshAdapter?.installed === true
|
|
290
302
|
? await dshLifecycle?.activate?.(lifecycleBackendContext)
|
|
291
|
-
: preparedDshAdapter;
|
|
303
|
+
: preparedDshAdapter, optionalDsh);
|
|
304
|
+
const optionalDshUnavailable = isOptionalUnavailableDshAdapter(dshAdapter);
|
|
292
305
|
return {
|
|
293
|
-
ok: claudeAdapter?.ok !== false
|
|
306
|
+
ok: claudeAdapter?.ok !== false
|
|
307
|
+
&& (!dshAdapter || isAdapterReady(dshAdapter) || optionalDshUnavailable),
|
|
294
308
|
action: "start",
|
|
309
|
+
...(optionalDshUnavailable ? { degraded: true } : {}),
|
|
295
310
|
backend,
|
|
296
311
|
...(codexAdapter ? { codexAdapter } : {}),
|
|
297
312
|
...(claudeAdapter ? { claudeAdapter } : {}),
|
|
@@ -426,7 +441,9 @@ async function executeMemoraxCodeStop(serviceOptions, argv, context, dshLifecycl
|
|
|
426
441
|
...(dshAdapter ? { dshAdapter } : {}),
|
|
427
442
|
...(opencodeAdapter ? { opencodeAdapter } : {}),
|
|
428
443
|
},
|
|
429
|
-
remainingClients:
|
|
444
|
+
remainingClients: packageReplacement
|
|
445
|
+
? activeClients
|
|
446
|
+
: remaining && hasRemainingClients ? remaining : undefined,
|
|
430
447
|
};
|
|
431
448
|
}
|
|
432
449
|
export async function restartMemoraxCodeService(serviceOptions, argv, commitReadyState) {
|
|
@@ -457,7 +474,39 @@ async function restartMemoraxCodeServiceLocked(serviceOptions, argv) {
|
|
|
457
474
|
return { ...started, action: "restart" };
|
|
458
475
|
}
|
|
459
476
|
export async function uninstallMemoraxCodeService(serviceOptions, argv) {
|
|
460
|
-
|
|
477
|
+
const memoraxCodeHome = memoraxCodeHomeForService(serviceOptions);
|
|
478
|
+
let report;
|
|
479
|
+
let lifecycleEntered = false;
|
|
480
|
+
try {
|
|
481
|
+
return await withSetupCompletionLock(memoraxCodeHome, async (_completion, mutation) => {
|
|
482
|
+
lifecycleEntered = true;
|
|
483
|
+
report = await withMemoraxCodeLifecycleLock("uninstall", serviceOptions, () => uninstallMemoraxCodeServiceLocked(serviceOptions, argv));
|
|
484
|
+
if (isCompleteSuccessfulUninstall(report))
|
|
485
|
+
mutation.clear();
|
|
486
|
+
return report;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
if (lifecycleEntered && !report)
|
|
491
|
+
throw error;
|
|
492
|
+
if (!report) {
|
|
493
|
+
return {
|
|
494
|
+
ok: false,
|
|
495
|
+
action: "uninstall",
|
|
496
|
+
reason: "setup_completion_lock_failed",
|
|
497
|
+
message: error instanceof Error ? error.message : String(error),
|
|
498
|
+
npmPackageRemoval: skippedNpmPackageRemoval("setup_completion_lock_failed"),
|
|
499
|
+
removesPlugin: false,
|
|
500
|
+
removesUserState: false,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
...report,
|
|
505
|
+
ok: false,
|
|
506
|
+
reason: "setup_completion_clear_failed",
|
|
507
|
+
message: error instanceof Error ? error.message : String(error),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
461
510
|
}
|
|
462
511
|
async function uninstallMemoraxCodeServiceLocked(serviceOptions, argv) {
|
|
463
512
|
const context = memoraxCodeStopContext(serviceOptions, argv);
|
|
@@ -537,6 +586,11 @@ async function executeMemoraxCodeUninstall(serviceOptions, argv, context, dshLif
|
|
|
537
586
|
function skippedNpmPackageRemoval(reason) {
|
|
538
587
|
return { ok: true, action: "npm-package-remove", skipped: true, reason };
|
|
539
588
|
}
|
|
589
|
+
function isCompleteSuccessfulUninstall(report) {
|
|
590
|
+
return report.ok
|
|
591
|
+
&& report.npmPackageRemoval !== undefined
|
|
592
|
+
&& report.npmPackageRemoval.reason !== "partial_client_uninstall";
|
|
593
|
+
}
|
|
540
594
|
async function removeNpmPackageIfInstalled(argv) {
|
|
541
595
|
if (argv.includes("--no-npm-uninstall")) {
|
|
542
596
|
return { ok: true, action: "npm-package-remove", skipped: true, reason: "disabled_by_flag" };
|
|
@@ -638,6 +692,10 @@ function commitActiveManagedClients(memoraxCodeHome, clients) {
|
|
|
638
692
|
function hasExplicitClientSelection(argv) {
|
|
639
693
|
return argv.includes("--clients");
|
|
640
694
|
}
|
|
695
|
+
function isOptionalDshSelection(argv) {
|
|
696
|
+
return !isDshAdapterRecovery()
|
|
697
|
+
&& (!hasExplicitClientSelection(argv) || truthyEnv(process.env[DSH_OPTIONAL_ENV]));
|
|
698
|
+
}
|
|
641
699
|
function argValue(argv, name) {
|
|
642
700
|
const index = argv.indexOf(name);
|
|
643
701
|
return index >= 0 ? argv[index + 1] : undefined;
|
|
@@ -777,11 +835,12 @@ export function isAdapterReady(report) {
|
|
|
777
835
|
&& report.opencodeSkills?.ok !== false;
|
|
778
836
|
}
|
|
779
837
|
export function isOptionalUnavailableDshAdapter(report) {
|
|
780
|
-
return Boolean(report
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
838
|
+
return Boolean(report?.optional === true && !isAdapterReady(report));
|
|
839
|
+
}
|
|
840
|
+
function markOptionalDshAdapter(report, optional) {
|
|
841
|
+
return report && optional && !isAdapterReady(report)
|
|
842
|
+
? { ...report, optional: true }
|
|
843
|
+
: report;
|
|
785
844
|
}
|
|
786
845
|
export function isOptionalUnconfiguredClaudeAdapter(report, codexAdapter) {
|
|
787
846
|
return Boolean(report
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { runMemoryCli } from "./memory/cli.js";
|
|
3
3
|
runMemoryCli(process.argv.slice(2)).then((result) => {
|
|
4
|
+
if (!process.argv.includes("--json") && result.ok && result.quotaNotice) {
|
|
5
|
+
console.warn(result.quotaNotice);
|
|
6
|
+
}
|
|
4
7
|
if (!process.argv.includes("--json") && result.ok && result.action === "memory.search") {
|
|
5
8
|
if (result.userNotice)
|
|
6
9
|
console.warn(`Warning: ${result.userNotice}`);
|
|
@@ -48,6 +48,7 @@ export async function retrieveAutomaticMemoryContext(options) {
|
|
|
48
48
|
}, {
|
|
49
49
|
config,
|
|
50
50
|
diagnosticLogger: options.diagnosticLogger,
|
|
51
|
+
env,
|
|
51
52
|
fetchImpl: options.fetchImpl,
|
|
52
53
|
observability: options.memoryObservability,
|
|
53
54
|
observabilitySource: options.memoryObservabilitySource ?? "automatic_retrieval",
|
|
@@ -57,6 +58,9 @@ export async function retrieveAutomaticMemoryContext(options) {
|
|
|
57
58
|
if (!response.ok)
|
|
58
59
|
return finish(skipped("retrieve_failed", response.error));
|
|
59
60
|
const result = response.result ?? {};
|
|
61
|
+
const userNotice = result.quota && options.claimQuotaNotice
|
|
62
|
+
? await options.claimQuotaNotice(config, result.quota, { diagnosticLogger: options.diagnosticLogger, env })
|
|
63
|
+
: undefined;
|
|
60
64
|
const payload = isRecord(result.tool_result_payload) ? result.tool_result_payload : {};
|
|
61
65
|
const memoryItems = Array.isArray(payload.items) ? payload.items : [];
|
|
62
66
|
const contextBlocks = Array.isArray(payload.contextBlocks) ? payload.contextBlocks : [];
|
|
@@ -69,7 +73,7 @@ export async function retrieveAutomaticMemoryContext(options) {
|
|
|
69
73
|
itemCount: memoryItems.length,
|
|
70
74
|
contextBlockCount: contextBlocks.length,
|
|
71
75
|
blockChars,
|
|
72
|
-
}));
|
|
76
|
+
}, userNotice));
|
|
73
77
|
}
|
|
74
78
|
return finish({
|
|
75
79
|
context,
|
|
@@ -77,6 +81,7 @@ export async function retrieveAutomaticMemoryContext(options) {
|
|
|
77
81
|
itemCount: memoryItems.length,
|
|
78
82
|
contextBlockCount: contextBlocks.length,
|
|
79
83
|
blockChars,
|
|
84
|
+
...(userNotice ? { userNotice } : {}),
|
|
80
85
|
});
|
|
81
86
|
}
|
|
82
87
|
function isAutomaticMemoryControlCommand(text) {
|
|
@@ -85,7 +90,7 @@ function isAutomaticMemoryControlCommand(text) {
|
|
|
85
90
|
return true;
|
|
86
91
|
return /^(?:memorax-code|memorax-cli)(?:\s|$)/i.test(trimmed);
|
|
87
92
|
}
|
|
88
|
-
function skipped(skipReason, error, fields = {}) {
|
|
93
|
+
function skipped(skipReason, error, fields = {}, userNotice) {
|
|
89
94
|
return {
|
|
90
95
|
retrieved: false,
|
|
91
96
|
skipReason,
|
|
@@ -93,6 +98,7 @@ function skipped(skipReason, error, fields = {}) {
|
|
|
93
98
|
contextBlockCount: fields.contextBlockCount ?? 0,
|
|
94
99
|
blockChars: fields.blockChars ?? 0,
|
|
95
100
|
...(error ? { error } : {}),
|
|
101
|
+
...(userNotice ? { userNotice } : {}),
|
|
96
102
|
};
|
|
97
103
|
}
|
|
98
104
|
function automaticMemoryContextText(result) {
|
|
@@ -12,6 +12,7 @@ const AUTOMATIC_MEMORY_WRITEBACK_MAX_RETRY_DELAY_MS = 5_000;
|
|
|
12
12
|
export function createAutomaticMemoryWritebackRuntime(options = {}) {
|
|
13
13
|
const state = {
|
|
14
14
|
diagnosticLogger: options.diagnosticLogger ?? (() => { }),
|
|
15
|
+
queueQuotaNotice: options.queueQuotaNotice,
|
|
15
16
|
pendingWritebacks: new Map(),
|
|
16
17
|
writebackBuffer: createMemoryWritebackBufferRuntime(),
|
|
17
18
|
accepting: true,
|
|
@@ -243,8 +244,11 @@ async function enqueueAutomaticMemoryWritebackAsync(state, decision, options) {
|
|
|
243
244
|
errorKind: response.ok ? undefined : response.errorKind,
|
|
244
245
|
httpStatus: response.ok ? undefined : response.httpStatus,
|
|
245
246
|
});
|
|
246
|
-
if (response.ok)
|
|
247
|
+
if (response.ok) {
|
|
248
|
+
if (response.result.quota)
|
|
249
|
+
queueQuotaNoticeBestEffort(state, response.result.quota);
|
|
247
250
|
break;
|
|
251
|
+
}
|
|
248
252
|
if (!retrying) {
|
|
249
253
|
releasePendingWritebacks(state, [decision.idempotencyKey, ...(decision.dedupeKeys ?? [])]);
|
|
250
254
|
return;
|
|
@@ -283,6 +287,14 @@ function trackAutomaticMemoryWriteback(state, writeback) {
|
|
|
283
287
|
state.inFlight.add(writeback);
|
|
284
288
|
void writeback.then(() => state.inFlight.delete(writeback), () => state.inFlight.delete(writeback));
|
|
285
289
|
}
|
|
290
|
+
function queueQuotaNoticeBestEffort(state, quota) {
|
|
291
|
+
try {
|
|
292
|
+
state.queueQuotaNotice?.(quota);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
state.diagnosticLogger("memory.automatic_writeback.quota_notice_queue_failed", {});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
286
298
|
function hasPendingWriteback(state, idempotencyKey) {
|
|
287
299
|
prunePendingWritebacks(state);
|
|
288
300
|
return state.pendingWritebacks.has(idempotencyKey);
|
|
@@ -7,6 +7,7 @@ import { resolveConfiguredRepositoryMemory, } from "./repository-session.js";
|
|
|
7
7
|
import { repositoryMemoryScopeContainsWorkspace, repositoryMemoryScopeKind, repositoryMemoryScopesMatch, } from "../repository/scope.js";
|
|
8
8
|
import { isTraceClient } from "../trace/context.js";
|
|
9
9
|
import { readCurrentTraceTurn, recordTraceEvent } from "../trace/store.js";
|
|
10
|
+
import { claimQuotaNotice, } from "./quota-notice.js";
|
|
10
11
|
export async function runMemoryCli(args, options = {}) {
|
|
11
12
|
const command = args[0] || "status";
|
|
12
13
|
const env = options.env ?? process.env;
|
|
@@ -88,6 +89,7 @@ async function memorySearch(args, options) {
|
|
|
88
89
|
}, {
|
|
89
90
|
config: repositoryMemory.memory.config,
|
|
90
91
|
diagnosticLogger: backendDebug,
|
|
92
|
+
env: options.env,
|
|
91
93
|
fetchImpl: options.fetchImpl,
|
|
92
94
|
observability: observability.hook,
|
|
93
95
|
observabilitySource: "memory_cli",
|
|
@@ -96,6 +98,9 @@ async function memorySearch(args, options) {
|
|
|
96
98
|
await observability.flush();
|
|
97
99
|
if (!response.ok)
|
|
98
100
|
return { ok: false, action: "memory.search", query, error: response.error };
|
|
101
|
+
const quotaNotice = response.result.quota
|
|
102
|
+
? await (options.claimQuotaNotice ?? claimQuotaNotice)(repositoryMemory.memory.config, response.result.quota, { diagnosticLogger: backendDebug, env: options.env })
|
|
103
|
+
: undefined;
|
|
99
104
|
const payload = isRecord(response.result.tool_result_payload) ? response.result.tool_result_payload : {};
|
|
100
105
|
return {
|
|
101
106
|
ok: true,
|
|
@@ -106,6 +111,7 @@ async function memorySearch(args, options) {
|
|
|
106
111
|
answer: typeof payload.answer === "string" ? payload.answer : "",
|
|
107
112
|
items: Array.isArray(payload.items) ? payload.items : [],
|
|
108
113
|
receipt: response.result.dispatch_receipt ?? null,
|
|
114
|
+
...(quotaNotice ? { quotaNotice } : {}),
|
|
109
115
|
};
|
|
110
116
|
}
|
|
111
117
|
async function memoryAdd(args, options) {
|
|
@@ -167,12 +173,16 @@ async function memoryAdd(args, options) {
|
|
|
167
173
|
await observability.flush();
|
|
168
174
|
if (!response.ok)
|
|
169
175
|
return { ok: false, action: "memory.add", error: response.error };
|
|
176
|
+
const quotaNotice = response.result.quota
|
|
177
|
+
? await (options.claimQuotaNotice ?? claimQuotaNotice)(repositoryMemory.memory.config, response.result.quota, { diagnosticLogger: backendDebug, env })
|
|
178
|
+
: undefined;
|
|
170
179
|
return {
|
|
171
180
|
ok: true,
|
|
172
181
|
action: "memory.add",
|
|
173
182
|
provider: "memory.memorax",
|
|
174
183
|
...memoryCliIdentityFields(repositoryMemory.memory),
|
|
175
184
|
receipt: response.result.dispatch_receipt ?? null,
|
|
185
|
+
...(quotaNotice ? { quotaNotice } : {}),
|
|
176
186
|
};
|
|
177
187
|
}
|
|
178
188
|
async function resolveMemoryCliRepositoryMemory(options) {
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { withJsonFileLockAsync, } from "../../../memorax-code-adapter-common/src/config-utils.mjs";
|
|
4
|
+
import { readJsonRuntimeRecord, writePrivateJsonRecord, } from "../../../memorax-code-adapter-common/src/runtime-record.mjs";
|
|
5
|
+
import { MEMORAX_ACCOUNT_URL, } from "../../../memorax-code-adapter-common/src/memorax-defaults.mjs";
|
|
6
|
+
import { defaultMemoraxCodeHome } from "../config/memorax-code.js";
|
|
7
|
+
const QUOTA_NOTICE_STATE_VERSION = 1;
|
|
8
|
+
const DEFAULT_NOTICE_TIMEOUT_MS = 6_000;
|
|
9
|
+
const LOCK_TIMEOUT_MS = 500;
|
|
10
|
+
const ANONYMOUS_QUOTA_LIMIT = 100;
|
|
11
|
+
const NOTICE_LEVELS = [10, 0];
|
|
12
|
+
const QUOTA_NOTICE_STATE_KEYS = new Set([
|
|
13
|
+
"version",
|
|
14
|
+
"connection_fingerprint",
|
|
15
|
+
"last_notified_write_level",
|
|
16
|
+
"last_notified_search_level",
|
|
17
|
+
]);
|
|
18
|
+
const SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
19
|
+
export const claimQuotaNotice = async (config, quota, options = {}) => {
|
|
20
|
+
const env = options.env ?? process.env;
|
|
21
|
+
const transitionState = options.transitionState ?? transitionQuotaNoticeState;
|
|
22
|
+
const timeoutMs = positiveTimeout(options.timeoutMs);
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
const level = quotaNoticeLevel(quota);
|
|
25
|
+
const initial = initialQuotaNoticeState(config);
|
|
26
|
+
const noticeField = quota.featureCode === "memory_write"
|
|
27
|
+
? "last_notified_write_level"
|
|
28
|
+
: "last_notified_search_level";
|
|
29
|
+
let timeout;
|
|
30
|
+
let claimed = false;
|
|
31
|
+
try {
|
|
32
|
+
await Promise.race([
|
|
33
|
+
transitionState(initial, (current) => {
|
|
34
|
+
const lastNotifiedLevel = current[noticeField];
|
|
35
|
+
if (level === undefined) {
|
|
36
|
+
return lastNotifiedLevel === null
|
|
37
|
+
? undefined
|
|
38
|
+
: { ...current, [noticeField]: null };
|
|
39
|
+
}
|
|
40
|
+
if (lastNotifiedLevel !== null && level >= lastNotifiedLevel) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
claimed = true;
|
|
44
|
+
return { ...current, [noticeField]: level };
|
|
45
|
+
}, {
|
|
46
|
+
memoraxCodeHome: defaultMemoraxCodeHome(env),
|
|
47
|
+
signal: controller.signal,
|
|
48
|
+
timeoutMs: LOCK_TIMEOUT_MS,
|
|
49
|
+
}),
|
|
50
|
+
new Promise((_resolve, reject) => {
|
|
51
|
+
timeout = setTimeout(() => {
|
|
52
|
+
controller.abort();
|
|
53
|
+
reject(new Error("Quota notice state update timed out"));
|
|
54
|
+
}, timeoutMs);
|
|
55
|
+
timeout.unref?.();
|
|
56
|
+
}),
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
options.diagnosticLogger?.("memorax_quota_notice.update_failed", {});
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
if (timeout)
|
|
65
|
+
clearTimeout(timeout);
|
|
66
|
+
}
|
|
67
|
+
return claimed && level !== undefined
|
|
68
|
+
? quotaNotice(quota, level, config.memoryOutputLanguage)
|
|
69
|
+
: undefined;
|
|
70
|
+
};
|
|
71
|
+
export function createPendingQuotaNoticeRuntime(options = {}) {
|
|
72
|
+
const { claimQuotaNotice: claim = claimQuotaNotice, ...noticeOptions } = options;
|
|
73
|
+
let pendingQuota;
|
|
74
|
+
let closed = false;
|
|
75
|
+
return {
|
|
76
|
+
queue(quota) {
|
|
77
|
+
if (closed || quota.featureCode !== "memory_write")
|
|
78
|
+
return;
|
|
79
|
+
if (!pendingQuota || quotaRemainingRatio(quota) < quotaRemainingRatio(pendingQuota)) {
|
|
80
|
+
pendingQuota = { ...quota };
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
async claim(config) {
|
|
84
|
+
if (closed || !pendingQuota)
|
|
85
|
+
return undefined;
|
|
86
|
+
const quota = pendingQuota;
|
|
87
|
+
pendingQuota = undefined;
|
|
88
|
+
try {
|
|
89
|
+
return await claim(config, quota, noticeOptions);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
options.diagnosticLogger?.("memorax_quota_notice.claim_failed", {});
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
close() {
|
|
97
|
+
closed = true;
|
|
98
|
+
pendingQuota = undefined;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async function transitionQuotaNoticeState(initial, operation, options) {
|
|
103
|
+
const path = join(options.memoraxCodeHome, "runtime", "memory", "quota-notices.json");
|
|
104
|
+
return await withJsonFileLockAsync(path, async () => {
|
|
105
|
+
const stored = readQuotaNoticeState(path);
|
|
106
|
+
const current = stored?.connection_fingerprint === initial.connection_fingerprint
|
|
107
|
+
? stored
|
|
108
|
+
: initial;
|
|
109
|
+
const next = operation(current);
|
|
110
|
+
if (!next)
|
|
111
|
+
return current;
|
|
112
|
+
writePrivateJsonRecord(path, next, { durableBoundary: options.memoraxCodeHome });
|
|
113
|
+
return next;
|
|
114
|
+
}, {
|
|
115
|
+
signal: options.signal,
|
|
116
|
+
timeoutMs: options.timeoutMs,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function readQuotaNoticeState(path) {
|
|
120
|
+
const state = readJsonRuntimeRecord(path);
|
|
121
|
+
if (state.status === "absent")
|
|
122
|
+
return undefined;
|
|
123
|
+
if (state.status !== "present")
|
|
124
|
+
throw new Error("Quota notice state is unreadable");
|
|
125
|
+
const value = state.value;
|
|
126
|
+
if (value.version !== QUOTA_NOTICE_STATE_VERSION
|
|
127
|
+
|| Object.keys(value).some((key) => !QUOTA_NOTICE_STATE_KEYS.has(key))
|
|
128
|
+
|| Object.keys(value).length !== QUOTA_NOTICE_STATE_KEYS.size
|
|
129
|
+
|| typeof value.connection_fingerprint !== "string"
|
|
130
|
+
|| !SHA256_PATTERN.test(value.connection_fingerprint)
|
|
131
|
+
|| !validNoticeLevel(value.last_notified_write_level)
|
|
132
|
+
|| !validNoticeLevel(value.last_notified_search_level)) {
|
|
133
|
+
throw new Error("Quota notice state is invalid");
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
version: QUOTA_NOTICE_STATE_VERSION,
|
|
137
|
+
connection_fingerprint: value.connection_fingerprint,
|
|
138
|
+
last_notified_write_level: value.last_notified_write_level,
|
|
139
|
+
last_notified_search_level: value.last_notified_search_level,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function initialQuotaNoticeState(config) {
|
|
143
|
+
return {
|
|
144
|
+
version: QUOTA_NOTICE_STATE_VERSION,
|
|
145
|
+
connection_fingerprint: createHash("sha256")
|
|
146
|
+
.update(config.baseUrl)
|
|
147
|
+
.update("\0")
|
|
148
|
+
.update(config.apiKey)
|
|
149
|
+
.digest("hex"),
|
|
150
|
+
last_notified_write_level: null,
|
|
151
|
+
last_notified_search_level: null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function positiveTimeout(value) {
|
|
155
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0
|
|
156
|
+
? value
|
|
157
|
+
: DEFAULT_NOTICE_TIMEOUT_MS;
|
|
158
|
+
}
|
|
159
|
+
function quotaNoticeLevel(quota) {
|
|
160
|
+
if (quota.remaining === 0)
|
|
161
|
+
return 0;
|
|
162
|
+
if (quota.limit <= 0)
|
|
163
|
+
return undefined;
|
|
164
|
+
const percentage = quotaRemainingRatio(quota) * 100;
|
|
165
|
+
if (percentage <= 10)
|
|
166
|
+
return 10;
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
function quotaRemainingRatio(quota) {
|
|
170
|
+
return quota.limit === 0 ? 0 : quota.remaining / quota.limit;
|
|
171
|
+
}
|
|
172
|
+
function validNoticeLevel(value) {
|
|
173
|
+
return value === null || NOTICE_LEVELS.some((level) => value === level);
|
|
174
|
+
}
|
|
175
|
+
function quotaNotice(quota, level, language) {
|
|
176
|
+
if (language === "zh") {
|
|
177
|
+
const quotaName = quota.featureCode === "memory_write" ? "记忆写入" : "记忆搜索";
|
|
178
|
+
const quotaStatus = level === 0 ? "已用完" : `剩余不超过 ${level}%`;
|
|
179
|
+
if (quota.limit !== ANONYMOUS_QUOTA_LIMIT) {
|
|
180
|
+
return [
|
|
181
|
+
`额度提醒:您的 MemoraX Code ${quotaName}额度${quotaStatus}。`,
|
|
182
|
+
`请访问 ${MEMORAX_ACCOUNT_URL} 查看额度或管理账户。`,
|
|
183
|
+
].join(" ");
|
|
184
|
+
}
|
|
185
|
+
return [
|
|
186
|
+
`额度提醒:您的 MemoraX Code ${quotaName}额度${quotaStatus}。`,
|
|
187
|
+
`请访问 ${MEMORAX_ACCOUNT_URL} 查看额度、注册或管理账户。`,
|
|
188
|
+
"若本机使用的是尚未注册的匿名身份,可以在本机终端运行 `memorax-code account --show-mark-id` 获取用于认领当前身份的 Mark ID。",
|
|
189
|
+
"请妥善保管,不要在聊天中分享。",
|
|
190
|
+
].join(" ");
|
|
191
|
+
}
|
|
192
|
+
const quotaName = quota.featureCode === "memory_write" ? "memory write" : "memory search";
|
|
193
|
+
const quotaStatus = level === 0
|
|
194
|
+
? "has been used up"
|
|
195
|
+
: `has ${level}% or less remaining`;
|
|
196
|
+
if (quota.limit !== ANONYMOUS_QUOTA_LIMIT) {
|
|
197
|
+
return [
|
|
198
|
+
`Quota reminder: Your MemoraX Code ${quotaName} quota ${quotaStatus}.`,
|
|
199
|
+
`Visit ${MEMORAX_ACCOUNT_URL} to view your quota or manage your account.`,
|
|
200
|
+
].join(" ");
|
|
201
|
+
}
|
|
202
|
+
return [
|
|
203
|
+
`Quota reminder: Your MemoraX Code ${quotaName} quota ${quotaStatus}.`,
|
|
204
|
+
`Visit ${MEMORAX_ACCOUNT_URL} to view your quota, register, or manage your account.`,
|
|
205
|
+
"If this device uses an unregistered anonymous identity, run `memorax-code account --show-mark-id` in your local terminal to retrieve the Mark ID used to claim it.",
|
|
206
|
+
"Keep it private and do not share it in chat.",
|
|
207
|
+
].join(" ");
|
|
208
|
+
}
|
|
@@ -5,9 +5,16 @@ import { createDshMemoryHookRuntime, } from "../clients/dsh/memory-hook-runtime.
|
|
|
5
5
|
import { createOpenCodeMemoryHookRuntime, } from "../clients/opencode/memory-hook-runtime.js";
|
|
6
6
|
import { createMemoryTurnCoordinator } from "./turn-coordinator.js";
|
|
7
7
|
import { createRepositoryMemorySessionRuntime, } from "./repository-session.js";
|
|
8
|
+
import { createPendingQuotaNoticeRuntime } from "./quota-notice.js";
|
|
8
9
|
export function createMemoryService(options = {}) {
|
|
10
|
+
const pendingQuotaNotice = createPendingQuotaNoticeRuntime({
|
|
11
|
+
claimQuotaNotice: options.claimQuotaNotice,
|
|
12
|
+
diagnosticLogger: options.diagnosticLogger,
|
|
13
|
+
env: options.env,
|
|
14
|
+
});
|
|
9
15
|
const automaticWriteback = createAutomaticMemoryWritebackRuntime({
|
|
10
16
|
diagnosticLogger: options.diagnosticLogger,
|
|
17
|
+
queueQuotaNotice: pendingQuotaNotice.queue,
|
|
11
18
|
});
|
|
12
19
|
const repositoryMemorySession = createRepositoryMemorySessionRuntime({
|
|
13
20
|
onScopeUpgrade: automaticWriteback.discardForScopeUpgrade,
|
|
@@ -21,16 +28,19 @@ export function createMemoryService(options = {}) {
|
|
|
21
28
|
});
|
|
22
29
|
const codexHook = createCodexMemoryHookRuntime({
|
|
23
30
|
...options,
|
|
31
|
+
pendingQuotaNotice,
|
|
24
32
|
repositoryMemorySession,
|
|
25
33
|
turnCoordinator,
|
|
26
34
|
});
|
|
27
35
|
const claudeHook = createClaudeMemoryHookRuntime({
|
|
28
36
|
...options,
|
|
37
|
+
pendingQuotaNotice,
|
|
29
38
|
repositoryMemorySession,
|
|
30
39
|
turnCoordinator,
|
|
31
40
|
});
|
|
32
41
|
const openCodeHook = createOpenCodeMemoryHookRuntime({
|
|
33
42
|
...options,
|
|
43
|
+
pendingQuotaNotice,
|
|
34
44
|
repositoryMemorySession,
|
|
35
45
|
turnCoordinator,
|
|
36
46
|
});
|
|
@@ -81,6 +91,7 @@ export function createMemoryService(options = {}) {
|
|
|
81
91
|
turnCoordinator.close();
|
|
82
92
|
repositoryMemorySession.close();
|
|
83
93
|
automaticWriteback.close();
|
|
94
|
+
pendingQuotaNotice.close();
|
|
84
95
|
},
|
|
85
96
|
};
|
|
86
97
|
}
|
|
@@ -40,7 +40,7 @@ export async function invokeMemoraxMemoryProvider(run, request, options = {}) {
|
|
|
40
40
|
return repositoryScope;
|
|
41
41
|
const payload = buildMemoraxSearchPayload(config, query, context, repositoryScope.scope);
|
|
42
42
|
try {
|
|
43
|
-
const raw = await callMemoSearch(config, payload, options.fetchImpl);
|
|
43
|
+
const { body: raw, quota } = await callMemoSearch(config, payload, options.fetchImpl);
|
|
44
44
|
const items = extractMemoraxSearchItems(raw);
|
|
45
45
|
const contextBlocks = renderMemoraxContextBlocks(items, config);
|
|
46
46
|
const promptFragments = contextBlocks.map((block) => ({
|
|
@@ -72,6 +72,7 @@ export async function invokeMemoraxMemoryProvider(run, request, options = {}) {
|
|
|
72
72
|
slot: request.slot || "state_context",
|
|
73
73
|
operation,
|
|
74
74
|
prompt_fragments: promptFragments,
|
|
75
|
+
...(quota ? { quota } : {}),
|
|
75
76
|
tool_result_payload: {
|
|
76
77
|
answer: contextBlocks.map((block) => block.content).join("\n\n"),
|
|
77
78
|
items,
|
|
@@ -195,7 +196,7 @@ async function invokeMemoraxWriteback(run, request, config, repositoryScope, opt
|
|
|
195
196
|
return { ok: false, error: addOptions.error };
|
|
196
197
|
const payload = buildMemoraxAddPayload(config, run, messages, context, idempotencyKey, repositoryScope, addOptions.options);
|
|
197
198
|
try {
|
|
198
|
-
const raw = await callMemoAdd(config, payload, options.fetchImpl);
|
|
199
|
+
const { body: raw, quota } = await callMemoAdd(config, payload, options.fetchImpl);
|
|
199
200
|
recordMemoryObservabilityEvent(options, {
|
|
200
201
|
operation: "writeback",
|
|
201
202
|
ok: true,
|
|
@@ -217,6 +218,7 @@ async function invokeMemoraxWriteback(run, request, config, repositoryScope, opt
|
|
|
217
218
|
slot: request.slot || "state_context",
|
|
218
219
|
operation: "writeback",
|
|
219
220
|
prompt_fragments: [],
|
|
221
|
+
...(quota ? { quota } : {}),
|
|
220
222
|
tool_result_payload: {
|
|
221
223
|
accepted: true,
|
|
222
224
|
receiptId: memoraxReceiptId(raw),
|