@mem9/mem9 0.4.11 → 0.4.13
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 +3 -3
- package/dist/backend.js +1 -1
- package/dist/hooks.js +19 -0
- package/dist/index.js +10 -21
- package/dist/quota-error.js +215 -0
- package/dist/server-backend.js +20 -9
- package/openclaw.plugin.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Memory plugin for [OpenClaw](https://github.com/openclaw) — replaces the built-in memory slot with cloud-persistent shared memory. Runs in server mode only, connecting to `mnemo-server` via `apiUrl` + `apiKey` (preferred) or legacy `tenantID`. Optional `provisionToken` and `provisionQueryParams` are used only during first-time create-new setup before an explicit `apiKey` is configured.
|
|
4
4
|
|
|
5
|
-
When `apiKey` is absent during create-new onboarding, the plugin does not auto-provision on startup. Instead, the first post-restart
|
|
5
|
+
When `apiKey` is absent during create-new onboarding, the plugin does not auto-provision on startup. Instead, the first post-restart OpenClaw agent turn that runs `before_prompt_build` triggers exactly one create-new provision. Setup/control chats such as TUI local embedded sessions may not run that plugin hook themselves, so onboarding can trigger one minimal OpenClaw agent turn to complete provisioning. The plugin coordinates that call across concurrent OpenClaw plugin registrations on the same machine and reuses the generated key locally for future restarts tied to the same `provisionToken`.
|
|
6
6
|
|
|
7
7
|
## 🚀 Quick Start (Server Mode)
|
|
8
8
|
|
|
@@ -182,7 +182,7 @@ Defined in `openclaw.plugin.json`:
|
|
|
182
182
|
|---|---|---|
|
|
183
183
|
| `apiUrl` | string | mnemo-server URL |
|
|
184
184
|
| `apiKey` | string | Preferred key. Uses `/v1alpha2/mem9s/...` with `X-API-Key` header |
|
|
185
|
-
| `provisionToken` | string | Optional one-time create-new token used locally to ensure
|
|
185
|
+
| `provisionToken` | string | Optional one-time create-new token used locally to ensure create-new provisioning runs only once from an OpenClaw agent turn and is reused on this machine until an explicit `apiKey` is configured |
|
|
186
186
|
| `provisionQueryParams` | object | Optional `utm_*` map forwarded only to the initial `POST /v1alpha1/mem9s` request made during create-new when `apiKey` is absent |
|
|
187
187
|
| `defaultTimeoutMs` | number | Default timeout for non-search mem9 API requests in milliseconds. Default: `8000` |
|
|
188
188
|
| `searchTimeoutMs` | number | Timeout for `memory_search` and automatic recall search in milliseconds. Default: `15000` |
|
|
@@ -247,7 +247,7 @@ openclaw-plugin/
|
|
|
247
247
|
| `No mode configured` | Missing config | Add `apiUrl` and `apiKey` (or legacy `tenantID`) to plugin config |
|
|
248
248
|
| `Server mode requires...` | Missing key | Add `apiKey` (or legacy `tenantID`) to config |
|
|
249
249
|
| `config reload skipped (invalid config): plugins.entries.mem9: Unrecognized key: "apiKey"` | Setup wrote `plugins.entries.mem9.apiKey` instead of `plugins.entries.mem9.config.apiKey` | Remove the invalid top-level key and keep the secret only under `config.apiKey` |
|
|
250
|
-
| Multiple auto-provisioned keys appear during create-new | Setup retriggered create-new provisioning before the first result was reused, or an older plugin still auto-provisions on startup | Upgrade to `@mem9/mem9@0.4.7+`; newer builds provision only from
|
|
250
|
+
| Multiple auto-provisioned keys appear during create-new | Setup retriggered create-new provisioning before the first result was reused, or an older plugin still auto-provisions on startup | Upgrade to `@mem9/mem9@0.4.7+`; newer builds provision only from an OpenClaw agent turn that runs `before_prompt_build` and reuse one local result across duplicate setup retries |
|
|
251
251
|
| Search requests time out | Hybrid/vector search exceeds plugin timeout | Increase `searchTimeoutMs` in plugin config |
|
|
252
252
|
| Conversations are not uploaded on OpenClaw 4.23+ | `agent_end` does not include conversation messages without explicit hook permission | Set `plugins.entries.mem9.hooks.allowConversationAccess` to `true` and restart OpenClaw |
|
|
253
253
|
| Plugin not loading | Not in memory slot | Set `"slots": {"memory": "mem9"}` in openclaw.json |
|
package/dist/backend.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export class PendingProvisionError extends Error {
|
|
2
|
-
constructor(message = "mem9 create-new setup is waiting for
|
|
2
|
+
constructor(message = "mem9 create-new setup is waiting for an OpenClaw agent turn that runs before_prompt_build to finish provisioning") {
|
|
3
3
|
super(message);
|
|
4
4
|
this.name = "PendingProvisionError";
|
|
5
5
|
}
|
package/dist/hooks.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* Reference: OpenClaw's built-in memory-lancedb extension uses the same pattern.
|
|
12
12
|
*/
|
|
13
13
|
import { isPendingProvisionError } from "./backend.js";
|
|
14
|
+
import { formatRuntimeQuotaNotice } from "./quota-error.js";
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
// Constants
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
@@ -21,6 +22,7 @@ const MAX_CONTENT_LEN = 500; // truncate individual memory content in prompt
|
|
|
21
22
|
// Ingest defaults — configurable via maxIngestBytes in plugin config
|
|
22
23
|
const DEFAULT_MAX_INGEST_BYTES = 200_000; // ~200KB safe for most LLM context windows
|
|
23
24
|
const MAX_INGEST_MESSAGES = 20; // absolute cap even if small messages
|
|
25
|
+
const BACKGROUND_QUOTA_PAUSED_LOG = "[mem9] memory saving paused by runtime quota";
|
|
24
26
|
function previewText(text, maxLen = 160) {
|
|
25
27
|
const normalized = text.replace(/\s+/g, " ").trim();
|
|
26
28
|
if (normalized.length <= maxLen) {
|
|
@@ -166,6 +168,13 @@ export function registerHooks(api, backend, logger, options) {
|
|
|
166
168
|
if (isPendingProvisionError(err)) {
|
|
167
169
|
return;
|
|
168
170
|
}
|
|
171
|
+
const quotaNotice = formatRuntimeQuotaNotice(err, "recall paused");
|
|
172
|
+
if (quotaNotice) {
|
|
173
|
+
logger.info(quotaNotice);
|
|
174
|
+
return {
|
|
175
|
+
prependContext: quotaNotice,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
169
178
|
// Graceful degradation — never block the LLM call
|
|
170
179
|
logger.error(`[mem9] before_prompt_build failed: ${String(err)}`);
|
|
171
180
|
}
|
|
@@ -215,6 +224,11 @@ export function registerHooks(api, backend, logger, options) {
|
|
|
215
224
|
if (isPendingProvisionError(err)) {
|
|
216
225
|
return;
|
|
217
226
|
}
|
|
227
|
+
const quotaNotice = formatRuntimeQuotaNotice(err, "before_reset save paused");
|
|
228
|
+
if (quotaNotice) {
|
|
229
|
+
logger.info(BACKGROUND_QUOTA_PAUSED_LOG);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
218
232
|
// Best-effort — never block /reset
|
|
219
233
|
logger.error(`[mem9] before_reset save failed: ${String(err)}`);
|
|
220
234
|
}
|
|
@@ -310,6 +324,11 @@ export function registerHooks(api, backend, logger, options) {
|
|
|
310
324
|
if (isPendingProvisionError(err)) {
|
|
311
325
|
return;
|
|
312
326
|
}
|
|
327
|
+
const quotaNotice = formatRuntimeQuotaNotice(err, "agent_end ingest paused");
|
|
328
|
+
if (quotaNotice) {
|
|
329
|
+
logger.info(BACKGROUND_QUOTA_PAUSED_LOG);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
313
332
|
// Best-effort — never fail the agent end phase
|
|
314
333
|
}
|
|
315
334
|
});
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { PendingProvisionError, isPendingProvisionError } from "./backend.js";
|
|
6
6
|
import { DEFAULT_SEARCH_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, ServerBackend, } from "./server-backend.js";
|
|
7
7
|
import { registerHooks } from "./hooks.js";
|
|
8
|
+
import { toolErrorPayload } from "./quota-error.js";
|
|
8
9
|
const DEFAULT_API_URL = "https://api.mem9.ai";
|
|
9
10
|
const TIMEOUT_FIELDS = ["defaultTimeoutMs", "searchTimeoutMs"];
|
|
10
11
|
const SHARED_PROVISION_DIR = path.join(os.homedir(), ".openclaw", "mem9", "provision");
|
|
@@ -47,6 +48,9 @@ function jsonResult(data) {
|
|
|
47
48
|
function errorMessage(err) {
|
|
48
49
|
return err instanceof Error ? err.message : String(err);
|
|
49
50
|
}
|
|
51
|
+
function jsonToolError(err) {
|
|
52
|
+
return jsonResult(toolErrorPayload(err));
|
|
53
|
+
}
|
|
50
54
|
function sleep(ms) {
|
|
51
55
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
52
56
|
}
|
|
@@ -248,10 +252,7 @@ function buildTools(backend) {
|
|
|
248
252
|
return jsonResult({ ok: true, data: result });
|
|
249
253
|
}
|
|
250
254
|
catch (err) {
|
|
251
|
-
return
|
|
252
|
-
ok: false,
|
|
253
|
-
error: err instanceof Error ? err.message : String(err),
|
|
254
|
-
});
|
|
255
|
+
return jsonToolError(err);
|
|
255
256
|
}
|
|
256
257
|
},
|
|
257
258
|
},
|
|
@@ -287,10 +288,7 @@ function buildTools(backend) {
|
|
|
287
288
|
return jsonResult({ ok: true, ...result });
|
|
288
289
|
}
|
|
289
290
|
catch (err) {
|
|
290
|
-
return
|
|
291
|
-
ok: false,
|
|
292
|
-
error: err instanceof Error ? err.message : String(err),
|
|
293
|
-
});
|
|
291
|
+
return jsonToolError(err);
|
|
294
292
|
}
|
|
295
293
|
},
|
|
296
294
|
},
|
|
@@ -314,10 +312,7 @@ function buildTools(backend) {
|
|
|
314
312
|
return jsonResult({ ok: true, data: result });
|
|
315
313
|
}
|
|
316
314
|
catch (err) {
|
|
317
|
-
return
|
|
318
|
-
ok: false,
|
|
319
|
-
error: err instanceof Error ? err.message : String(err),
|
|
320
|
-
});
|
|
315
|
+
return jsonToolError(err);
|
|
321
316
|
}
|
|
322
317
|
},
|
|
323
318
|
},
|
|
@@ -349,10 +344,7 @@ function buildTools(backend) {
|
|
|
349
344
|
return jsonResult({ ok: true, data: result });
|
|
350
345
|
}
|
|
351
346
|
catch (err) {
|
|
352
|
-
return
|
|
353
|
-
ok: false,
|
|
354
|
-
error: err instanceof Error ? err.message : String(err),
|
|
355
|
-
});
|
|
347
|
+
return jsonToolError(err);
|
|
356
348
|
}
|
|
357
349
|
},
|
|
358
350
|
},
|
|
@@ -376,10 +368,7 @@ function buildTools(backend) {
|
|
|
376
368
|
return jsonResult({ ok: true });
|
|
377
369
|
}
|
|
378
370
|
catch (err) {
|
|
379
|
-
return
|
|
380
|
-
ok: false,
|
|
381
|
-
error: err instanceof Error ? err.message : String(err),
|
|
382
|
-
});
|
|
371
|
+
return jsonToolError(err);
|
|
383
372
|
}
|
|
384
373
|
},
|
|
385
374
|
},
|
|
@@ -471,7 +460,7 @@ const mnemoPlugin = {
|
|
|
471
460
|
api.logger.info("[mem9] Server mode (v1alpha2)");
|
|
472
461
|
if (!configuredApiKey) {
|
|
473
462
|
if (configuredProvisionToken) {
|
|
474
|
-
api.logger.info("[mem9] apiKey not configured; waiting for
|
|
463
|
+
api.logger.info("[mem9] apiKey not configured; waiting for an OpenClaw agent turn that runs before_prompt_build to finish create-new provision");
|
|
475
464
|
}
|
|
476
465
|
else {
|
|
477
466
|
api.logger.info("[mem9] apiKey not configured; mem9 will stay idle until apiKey is configured");
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
export class Mem9HttpError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
body;
|
|
4
|
+
data;
|
|
5
|
+
constructor(message, status, body, data) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.body = body;
|
|
9
|
+
this.data = data;
|
|
10
|
+
this.name = "Mem9HttpError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function isRecord(value) {
|
|
14
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
function normalizeString(value) {
|
|
17
|
+
return typeof value === "string" ? value.trim() : "";
|
|
18
|
+
}
|
|
19
|
+
function normalizePositiveInteger(value) {
|
|
20
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : null;
|
|
21
|
+
}
|
|
22
|
+
export function parseJsonOrUndefined(text) {
|
|
23
|
+
if (!text.trim()) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(text);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function messageFromErrorBody(status, body, data) {
|
|
34
|
+
if (isRecord(data)) {
|
|
35
|
+
const message = normalizeString(data.message);
|
|
36
|
+
if (message) {
|
|
37
|
+
return message;
|
|
38
|
+
}
|
|
39
|
+
const error = normalizeString(data.error);
|
|
40
|
+
if (error) {
|
|
41
|
+
return error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const text = body.trim();
|
|
45
|
+
return text || `HTTP ${status}`;
|
|
46
|
+
}
|
|
47
|
+
function normalizeRecommendedAction(runtimeQuota) {
|
|
48
|
+
const nested = isRecord(runtimeQuota.recommendedAction) ? runtimeQuota.recommendedAction : {};
|
|
49
|
+
const providerActionCode = normalizeString(nested.providerActionCode);
|
|
50
|
+
const severity = normalizeString(nested.severity);
|
|
51
|
+
const type = normalizeString(nested.type);
|
|
52
|
+
const url = normalizeString(nested.url);
|
|
53
|
+
if (!providerActionCode && !severity && !type && !url) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
...(providerActionCode ? { providerActionCode } : {}),
|
|
58
|
+
...(severity ? { severity } : {}),
|
|
59
|
+
...(type ? { type } : {}),
|
|
60
|
+
...(url ? { url } : {}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function quotaGateReason(runtimeQuota) {
|
|
64
|
+
const quotaGateResult = isRecord(runtimeQuota.quotaGateResult) ? runtimeQuota.quotaGateResult : {};
|
|
65
|
+
return normalizeString(quotaGateResult.reason);
|
|
66
|
+
}
|
|
67
|
+
function retryAfterSeconds(runtimeQuota) {
|
|
68
|
+
const direct = normalizePositiveInteger(runtimeQuota.retryAfterSeconds);
|
|
69
|
+
if (direct !== null) {
|
|
70
|
+
return direct;
|
|
71
|
+
}
|
|
72
|
+
const quotaGateResult = isRecord(runtimeQuota.quotaGateResult) ? runtimeQuota.quotaGateResult : {};
|
|
73
|
+
const postQuotaRateLimit = isRecord(quotaGateResult.postQuotaRateLimit) ? quotaGateResult.postQuotaRateLimit : {};
|
|
74
|
+
return normalizePositiveInteger(postQuotaRateLimit.retryAfterSeconds);
|
|
75
|
+
}
|
|
76
|
+
function isPostQuotaRateLimited(denied) {
|
|
77
|
+
return denied.status === 429 ||
|
|
78
|
+
denied.quotaGateReason === "postQuotaRateLimitExceeded";
|
|
79
|
+
}
|
|
80
|
+
function quotaReason(denied) {
|
|
81
|
+
if (isPostQuotaRateLimited(denied)) {
|
|
82
|
+
return "this API key has reached the temporary request limit for this memory feature";
|
|
83
|
+
}
|
|
84
|
+
const providerActionCode = normalizeString(denied.recommendedAction?.providerActionCode);
|
|
85
|
+
if (providerActionCode === "claimApiKey") {
|
|
86
|
+
return "the included usage quota for this API key has been used up";
|
|
87
|
+
}
|
|
88
|
+
if (providerActionCode === "increaseSpendingLimit") {
|
|
89
|
+
return "the configured spending limit would be exceeded";
|
|
90
|
+
}
|
|
91
|
+
if (providerActionCode === "enableOnDemand") {
|
|
92
|
+
return "the included usage quota has been used up and on-demand usage is not enabled";
|
|
93
|
+
}
|
|
94
|
+
if (providerActionCode === "upgradePlan") {
|
|
95
|
+
return "the included usage quota for this mem9 account has been used up";
|
|
96
|
+
}
|
|
97
|
+
if (providerActionCode === "resolveAccountState") {
|
|
98
|
+
return "the current account or billing state blocks runtime memory access";
|
|
99
|
+
}
|
|
100
|
+
return "the runtime quota check blocked this request";
|
|
101
|
+
}
|
|
102
|
+
function quotaNoticeSubject(denied, operation) {
|
|
103
|
+
const meter = normalizeString(denied.meter);
|
|
104
|
+
if (meter === "memory_write_requests") {
|
|
105
|
+
return {
|
|
106
|
+
headline: "Mem9 memory saving is temporarily unavailable",
|
|
107
|
+
userState: "mem9 cannot save new memories right now",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (meter === "memory_recall_requests") {
|
|
111
|
+
return {
|
|
112
|
+
headline: "Mem9 recall is temporarily unavailable",
|
|
113
|
+
userState: "mem9 cannot recall memories right now",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const operationText = normalizeString(operation).toLowerCase();
|
|
117
|
+
if (/\b(ingest|save|store|write)\b/.test(operationText)) {
|
|
118
|
+
return {
|
|
119
|
+
headline: "Mem9 memory saving is temporarily unavailable",
|
|
120
|
+
userState: "mem9 cannot save new memories right now",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (/\b(recall|search)\b/.test(operationText)) {
|
|
124
|
+
return {
|
|
125
|
+
headline: "Mem9 recall is temporarily unavailable",
|
|
126
|
+
userState: "mem9 cannot recall memories right now",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
headline: "Mem9 memory is temporarily unavailable",
|
|
131
|
+
userState: "mem9 cannot complete the memory request right now",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function actionUrlForDenied(denied) {
|
|
135
|
+
return normalizeString(denied.recommendedAction?.url);
|
|
136
|
+
}
|
|
137
|
+
function actionInstruction(denied) {
|
|
138
|
+
const action = denied.recommendedAction;
|
|
139
|
+
const providerActionCode = normalizeString(action?.providerActionCode);
|
|
140
|
+
const actionUrl = actionUrlForDenied(denied);
|
|
141
|
+
if (!actionUrl) {
|
|
142
|
+
if (isPostQuotaRateLimited(denied)) {
|
|
143
|
+
return "Tell them that the quota/rate-limit check blocked this request and to retry later or open the mem9 console to review account and billing settings.";
|
|
144
|
+
}
|
|
145
|
+
return "Ask them to open the mem9 console to resolve the account or billing state.";
|
|
146
|
+
}
|
|
147
|
+
switch (providerActionCode) {
|
|
148
|
+
case "claimApiKey":
|
|
149
|
+
return `Ask them to open this link to sign in or create a mem9 account and claim this API key: ${actionUrl}. After claiming the key, they can upgrade their plan or set up billing to get more usage. Include the link exactly as written.`;
|
|
150
|
+
case "upgradePlan":
|
|
151
|
+
return `Ask them to open this link to upgrade their mem9 plan and get more included usage: ${actionUrl}. Include the link exactly as written.`;
|
|
152
|
+
case "increaseSpendingLimit":
|
|
153
|
+
return `Ask them to open this link to increase the mem9 spending limit or adjust billing settings: ${actionUrl}. Include the link exactly as written.`;
|
|
154
|
+
case "enableOnDemand":
|
|
155
|
+
return `Ask them to open this link to enable billing or on-demand usage for mem9: ${actionUrl}. Include the link exactly as written.`;
|
|
156
|
+
case "resolveAccountState":
|
|
157
|
+
return `Ask them to open this mem9 link to resolve the account or billing state: ${actionUrl}. Include the link exactly as written.`;
|
|
158
|
+
default:
|
|
159
|
+
return `Ask them to open this mem9 link to resolve the account or billing state: ${actionUrl}. Include the link exactly as written.`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export function parseRuntimeQuotaDenied(value) {
|
|
163
|
+
const payload = value instanceof Mem9HttpError ? value.data : value;
|
|
164
|
+
if (!isRecord(payload)) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const details = isRecord(payload.details) ? payload.details : {};
|
|
168
|
+
if (normalizeString(details.errorCategory) !== "runtime_quota_denied") {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const runtimeQuota = isRecord(details.runtimeQuota) ? details.runtimeQuota : {};
|
|
172
|
+
const recommendedAction = normalizeRecommendedAction(runtimeQuota);
|
|
173
|
+
const retryAfter = retryAfterSeconds(runtimeQuota);
|
|
174
|
+
const reason = quotaGateReason(runtimeQuota);
|
|
175
|
+
return {
|
|
176
|
+
status: value instanceof Mem9HttpError ? value.status : null,
|
|
177
|
+
code: "runtime_quota_denied",
|
|
178
|
+
message: normalizeString(payload.error) || "Runtime usage quota denied.",
|
|
179
|
+
...(normalizeString(runtimeQuota.meter) ? { meter: normalizeString(runtimeQuota.meter) } : {}),
|
|
180
|
+
...(reason ? { quotaGateReason: reason } : {}),
|
|
181
|
+
...(retryAfter !== null ? { retryAfterSeconds: retryAfter } : {}),
|
|
182
|
+
...(recommendedAction ? { recommendedAction } : {}),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
export function formatRuntimeQuotaNotice(value, operation) {
|
|
186
|
+
const denied = parseRuntimeQuotaDenied(value);
|
|
187
|
+
if (!denied) {
|
|
188
|
+
return "";
|
|
189
|
+
}
|
|
190
|
+
const subject = quotaNoticeSubject(denied, operation);
|
|
191
|
+
return `${subject.headline} because ${quotaReason(denied)}. In your reply, briefly tell the user that ${subject.userState}. ${actionInstruction(denied)}`;
|
|
192
|
+
}
|
|
193
|
+
export function toolErrorPayload(error) {
|
|
194
|
+
const denied = parseRuntimeQuotaDenied(error);
|
|
195
|
+
if (denied) {
|
|
196
|
+
const actionUrl = actionUrlForDenied(denied);
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
error: denied.message,
|
|
200
|
+
status_code: denied.status,
|
|
201
|
+
code: denied.code,
|
|
202
|
+
quota: {
|
|
203
|
+
code: denied.code,
|
|
204
|
+
message: denied.message,
|
|
205
|
+
...(denied.retryAfterSeconds !== undefined ? { retryAfterSeconds: denied.retryAfterSeconds } : {}),
|
|
206
|
+
...(denied.recommendedAction ? { recommendedAction: denied.recommendedAction } : {}),
|
|
207
|
+
},
|
|
208
|
+
...(actionUrl ? { action_url: actionUrl } : {}),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
error: error instanceof Error ? error.message : String(error),
|
|
214
|
+
};
|
|
215
|
+
}
|
package/dist/server-backend.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Mem9HttpError, messageFromErrorBody, parseJsonOrUndefined, } from "./quota-error.js";
|
|
1
2
|
export const DEFAULT_TIMEOUT_MS = 8_000;
|
|
2
3
|
export const DEFAULT_SEARCH_TIMEOUT_MS = 15_000;
|
|
3
4
|
export class ServerBackend {
|
|
@@ -76,16 +77,22 @@ export class ServerBackend {
|
|
|
76
77
|
try {
|
|
77
78
|
return await this.request("GET", this.memoryPath(`/memories/${id}`));
|
|
78
79
|
}
|
|
79
|
-
catch {
|
|
80
|
-
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (err instanceof Mem9HttpError && err.status === 404) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
throw err;
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
async update(id, input) {
|
|
84
88
|
try {
|
|
85
89
|
return await this.request("PUT", this.memoryPath(`/memories/${id}`), input);
|
|
86
90
|
}
|
|
87
|
-
catch {
|
|
88
|
-
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err instanceof Mem9HttpError && err.status === 404) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
throw err;
|
|
89
96
|
}
|
|
90
97
|
}
|
|
91
98
|
async remove(id) {
|
|
@@ -93,8 +100,11 @@ export class ServerBackend {
|
|
|
93
100
|
await this.request("DELETE", this.memoryPath(`/memories/${id}`));
|
|
94
101
|
return true;
|
|
95
102
|
}
|
|
96
|
-
catch {
|
|
97
|
-
|
|
103
|
+
catch (err) {
|
|
104
|
+
if (err instanceof Mem9HttpError && err.status === 404) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
98
108
|
}
|
|
99
109
|
}
|
|
100
110
|
async ingest(input) {
|
|
@@ -119,10 +129,11 @@ export class ServerBackend {
|
|
|
119
129
|
if (resp.status === 204) {
|
|
120
130
|
return undefined;
|
|
121
131
|
}
|
|
122
|
-
const
|
|
132
|
+
const text = await resp.text();
|
|
123
133
|
if (!resp.ok) {
|
|
124
|
-
|
|
134
|
+
const data = parseJsonOrUndefined(text);
|
|
135
|
+
throw new Mem9HttpError(messageFromErrorBody(resp.status, text, data), resp.status, text, data);
|
|
125
136
|
}
|
|
126
|
-
return
|
|
137
|
+
return JSON.parse(text);
|
|
127
138
|
}
|
|
128
139
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
},
|
|
17
17
|
"provisionToken": {
|
|
18
18
|
"type": "string",
|
|
19
|
-
"description": "One-time create-new token used locally to ensure
|
|
19
|
+
"description": "One-time create-new token used locally to ensure API-key provisioning runs only once from an OpenClaw agent turn and can be reused on this machine before apiKey is configured explicitly"
|
|
20
20
|
},
|
|
21
21
|
"provisionQueryParams": {
|
|
22
22
|
"type": "object",
|
|
23
|
-
"description": "Optional utm_* params forwarded only by the first create-new provision request triggered after the initial restart",
|
|
23
|
+
"description": "Optional utm_* params forwarded only by the first create-new provision request triggered by an OpenClaw agent turn after the initial restart",
|
|
24
24
|
"additionalProperties": {
|
|
25
25
|
"type": "string"
|
|
26
26
|
}
|
package/package.json
CHANGED