@totalreclaw/totalreclaw 3.3.12-rc.9 → 3.3.12
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/SKILL.md +33 -27
- package/api-client.ts +17 -9
- package/batch-gate.ts +42 -0
- package/billing-cache.ts +72 -23
- package/claims-helper.ts +2 -1
- package/config.ts +95 -13
- package/consolidation.ts +6 -13
- package/contradiction-sync.ts +19 -14
- package/credential-provider.ts +184 -0
- package/crypto.ts +27 -160
- package/dist/api-client.js +17 -9
- package/dist/batch-gate.js +40 -0
- package/dist/billing-cache.js +54 -24
- package/dist/claims-helper.js +2 -1
- package/dist/config.js +91 -13
- package/dist/consolidation.js +6 -15
- package/dist/contradiction-sync.js +15 -15
- package/dist/credential-provider.js +145 -0
- package/dist/crypto.js +17 -137
- package/dist/download-ux.js +11 -7
- package/dist/embedder-loader.js +266 -0
- package/dist/embedding.js +36 -3
- package/dist/entry.js +123 -0
- package/dist/fs-helpers.js +75 -379
- package/dist/import-adapters/gemini-adapter.js +29 -159
- package/dist/index.js +864 -2631
- package/dist/memory-runtime.js +459 -0
- package/dist/native-memory.js +123 -0
- package/dist/onboarding-cli.js +4 -8
- package/dist/pair-cli-relay.js +1 -8
- package/dist/pair-cli.js +1 -1
- package/dist/pair-crypto.js +16 -358
- package/dist/pair-http.js +147 -4
- package/dist/relay.js +140 -0
- package/dist/reranker.js +13 -8
- package/dist/semantic-dedup.js +5 -7
- package/dist/skill-register.js +97 -0
- package/dist/subgraph-search.js +3 -1
- package/dist/subgraph-store.js +348 -290
- package/dist/tool-gating.js +39 -26
- package/dist/tools.js +367 -0
- package/dist/tr-cli-export-helper.js +3 -3
- package/dist/tr-cli.js +65 -156
- package/dist/trajectory-poller.js +155 -9
- package/dist/vault-crypto.js +551 -0
- package/download-ux.ts +12 -6
- package/embedder-loader.ts +293 -1
- package/embedding.ts +43 -3
- package/entry.ts +132 -0
- package/fs-helpers.ts +93 -458
- package/import-adapters/gemini-adapter.ts +38 -183
- package/index.ts +912 -2917
- package/memory-runtime.ts +723 -0
- package/native-memory.ts +196 -0
- package/onboarding-cli.ts +3 -9
- package/openclaw.plugin.json +5 -17
- package/package.json +6 -3
- package/pair-cli-relay.ts +0 -9
- package/pair-cli.ts +1 -1
- package/pair-crypto.ts +41 -483
- package/pair-http.ts +194 -5
- package/postinstall.mjs +138 -0
- package/relay.ts +172 -0
- package/reranker.ts +13 -8
- package/semantic-dedup.ts +5 -6
- package/skill-register.ts +146 -0
- package/skill.json +1 -1
- package/subgraph-search.ts +3 -1
- package/subgraph-store.ts +367 -307
- package/tool-gating.ts +39 -26
- package/tools.ts +499 -0
- package/tr-cli-export-helper.ts +3 -3
- package/tr-cli.ts +69 -182
- package/trajectory-poller.ts +162 -10
- package/vault-crypto.ts +705 -0
- package/auto-pair-on-load.ts +0 -308
- package/dist/auto-pair-on-load.js +0 -197
- package/dist/pair-pending-injection.js +0 -125
- package/pair-pending-injection.ts +0 -205
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory-runtime — adapter that bridges OpenClaw's FILE-ORIENTED memory
|
|
3
|
+
* result shapes to TR's ENCRYPTED-FACT + ON-CHAIN vault.
|
|
4
|
+
*
|
|
5
|
+
* Phase 2 (Task 2.1) of the OpenClaw native integration plan
|
|
6
|
+
* (docs/plans/2026-06-21-openclaw-native-integration-plan.md, 2026-06-21).
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS FILE EXISTS — the load-bearing discovery:
|
|
9
|
+
* OpenClaw 2026.6.8's memory subsystem calls
|
|
10
|
+
* `runtime.getMemorySearchManager(...)` to get a MemorySearchManager,
|
|
11
|
+
* then `.search(query)` / a file-read method on it. Its result shapes
|
|
12
|
+
* are FILE-ORIENTED:
|
|
13
|
+
* search() -> MemorySearchResult[] where each hit =
|
|
14
|
+
* { path, startLine, endLine, score, snippet, source, citation? }
|
|
15
|
+
* read-by-rel -> { text, path, truncated?, from?, lines?, nextFrom? }
|
|
16
|
+
*
|
|
17
|
+
* TR's vault is ENCRYPTED-FACT + ON-CHAIN: facts have an id, encrypted
|
|
18
|
+
* blob, blind index, plaintext (after decrypt), scope, pinned flag.
|
|
19
|
+
* So this adapter SYNTHESIZES file-shaped results from decrypted facts:
|
|
20
|
+
* path = FACT_PATH_PREFIX + factId (a synthetic URI)
|
|
21
|
+
* startLine = 1, endLine = line-count of plaintext (synthetic)
|
|
22
|
+
* snippet = decrypted plaintext (truncated to 500 chars)
|
|
23
|
+
* source = 'memory', citation = factId
|
|
24
|
+
* The read-by-rel path reverses relPath -> id -> decrypt.
|
|
25
|
+
*
|
|
26
|
+
* This is THE thing that makes TR's on-chain vault look like a memory
|
|
27
|
+
* corpus to OpenClaw's `active-memory` sub-agent and the
|
|
28
|
+
* `memory_search` / `memory_get` tools.
|
|
29
|
+
*
|
|
30
|
+
* SCANNER-CLEAN HARD CONTRACT (env=N net=N):
|
|
31
|
+
* This file is pure orchestration. It touches NO environment state and
|
|
32
|
+
* performs NO outbound network I/O. All subgraph + decrypt work lives
|
|
33
|
+
* in the injected `recall` / `getById` closures (wired to the real
|
|
34
|
+
* pipeline in Task 2.3: subgraph-search + vault-crypto.decrypt +
|
|
35
|
+
* reranker). Keeping all I/O in those closures is what keeps this file
|
|
36
|
+
* clean under OpenClaw's per-file scanner rules — neither the
|
|
37
|
+
* env-harvesting pair nor the disk-exfil pair can ever co-occur here.
|
|
38
|
+
* `npm run check-scanner` must remain 0 flags; this docstring itself
|
|
39
|
+
* avoids the literal trigger tokens for that reason.
|
|
40
|
+
*
|
|
41
|
+
* PHASE 2 STATUS:
|
|
42
|
+
* - Task 2.1: `createTrMemorySearchManager` (shipped).
|
|
43
|
+
* - Task 2.3: `createTrMemoryPluginRuntime` (shipped) — the
|
|
44
|
+
* MemoryPluginRuntime wrapper that owns the wiring surface OpenClaw's
|
|
45
|
+
* memory subsystem calls. The actual binding of recall/getById to the
|
|
46
|
+
* real subgraph-search + vault-crypto.decrypt + reranker pipeline
|
|
47
|
+
* happens in Task 2.7's `buildRecallDeps` inside register().
|
|
48
|
+
* - Task 2.4: `buildPromptSection` (shipped) — recall guidance +
|
|
49
|
+
* quota warning + pinned facts. Mirrors memory-core's branching on
|
|
50
|
+
* memory_search/memory_get availability, adapted to TR's encrypted
|
|
51
|
+
* vault, plus the Hermes-grade extras (quota + pinned). The real
|
|
52
|
+
* quota/pinned binding happens in Task 2.7's `buildRecallDeps`.
|
|
53
|
+
* - Task 2.5: `buildFlushPlan` (shipped) — the `flushPlanResolver` that
|
|
54
|
+
* returns the memory flush PLAN (thresholds + extraction prompt) so
|
|
55
|
+
* OpenClaw's host can decide WHEN/HOW to flush the trajectory to TR's
|
|
56
|
+
* extract→encrypt→on-chain pipeline. Does NOT perform capture itself;
|
|
57
|
+
* the actual encrypt→on-chain path is Task 4.2 / H2 QA, and RC1 keeps
|
|
58
|
+
* the trajectory poller as the capture fallback so capture works
|
|
59
|
+
* regardless of flush cadence.
|
|
60
|
+
*/
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Imports — kept scoped: only the canonical extraction system prompt is
|
|
63
|
+
// pulled from extractor.ts. memory-runtime.ts otherwise stays self-contained
|
|
64
|
+
// (no OpenClaw type import) so the plugin compiles without depending on
|
|
65
|
+
// OpenClaw's type package.
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
import { EXTRACTION_SYSTEM_PROMPT } from './extractor.js';
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Constants
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
/**
|
|
72
|
+
* Synthetic URI prefix encoding a fact id as a memory path. Reversible by
|
|
73
|
+
* readFile() so the active-memory sub-agent can dereference any hit.
|
|
74
|
+
*/
|
|
75
|
+
export const FACT_PATH_PREFIX = 'totalreclaw://facts/';
|
|
76
|
+
/** Maximum snippet length surfaced in search() hits. Keeps tool payloads small. */
|
|
77
|
+
const SNIPPET_MAX = 500;
|
|
78
|
+
/** Default search cap when the caller doesn't pass maxResults. */
|
|
79
|
+
const DEFAULT_MAX_RESULTS = 8;
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Helpers
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
function toLineCount(s) {
|
|
84
|
+
// '' -> 1 (a single empty line), 'a\nb' -> 2. Used purely for synthetic
|
|
85
|
+
// startLine/endLine; OpenClaw treats these as display hints, not offsets.
|
|
86
|
+
return s.split('\n').length;
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// createTrMemorySearchManager — the adapter factory
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
export function createTrMemorySearchManager(deps) {
|
|
92
|
+
/**
|
|
93
|
+
* search(): run recall, sort defensively by score, filter by minScore if
|
|
94
|
+
* requested, and synthesize file-shaped MemorySearchResult hits. We sort
|
|
95
|
+
* defensively here rather than relying on recall()'s ordering — the
|
|
96
|
+
* `score` field exists for exactly this, and n <= maxResults so it's
|
|
97
|
+
* effectively free. signal + sessionKey are forwarded so an aborted
|
|
98
|
+
* active-memory search actually cancels the in-flight recall.
|
|
99
|
+
*/
|
|
100
|
+
async function search(query, opts) {
|
|
101
|
+
const max = opts?.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
102
|
+
const minScore = opts?.minScore;
|
|
103
|
+
const facts = await deps.recall(query, {
|
|
104
|
+
maxResults: max,
|
|
105
|
+
signal: opts?.signal,
|
|
106
|
+
sessionKey: opts?.sessionKey,
|
|
107
|
+
});
|
|
108
|
+
facts.sort((a, b) => b.score - a.score);
|
|
109
|
+
const filtered = minScore === undefined ? facts : facts.filter((f) => f.score >= minScore);
|
|
110
|
+
return filtered.slice(0, max).map((f) => ({
|
|
111
|
+
path: `${FACT_PATH_PREFIX}${f.id}`,
|
|
112
|
+
startLine: 1,
|
|
113
|
+
endLine: toLineCount(f.plaintext),
|
|
114
|
+
score: f.score,
|
|
115
|
+
snippet: f.plaintext.slice(0, SNIPPET_MAX),
|
|
116
|
+
source: 'memory',
|
|
117
|
+
citation: f.id,
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* readFile(): reverse relPath -> id -> decrypt. Supports optional
|
|
122
|
+
* `from` / `lines` pagination for large facts (1-indexed line ranges,
|
|
123
|
+
* matching OpenClaw's convention). Returns nextFrom when more lines
|
|
124
|
+
* remain so the caller can page.
|
|
125
|
+
*/
|
|
126
|
+
async function readFile(params) {
|
|
127
|
+
const id = params.relPath.startsWith(FACT_PATH_PREFIX)
|
|
128
|
+
? params.relPath.slice(FACT_PATH_PREFIX.length)
|
|
129
|
+
: params.relPath;
|
|
130
|
+
const f = await deps.getById(id);
|
|
131
|
+
if (!f)
|
|
132
|
+
throw new Error(`fact not found: ${id}`);
|
|
133
|
+
const from = params.from && params.from > 0 ? params.from : 1;
|
|
134
|
+
const want = params.lines && params.lines > 0 ? params.lines : undefined;
|
|
135
|
+
const allLines = f.plaintext.split('\n');
|
|
136
|
+
const totalLines = allLines.length;
|
|
137
|
+
const sliceEnd = want === undefined ? totalLines : Math.min(from + want - 1, totalLines);
|
|
138
|
+
const text = allLines.slice(from - 1, sliceEnd).join('\n');
|
|
139
|
+
const truncated = want !== undefined && from + want - 1 < totalLines;
|
|
140
|
+
const nextFrom = truncated ? from + want : undefined;
|
|
141
|
+
return {
|
|
142
|
+
text,
|
|
143
|
+
path: `${FACT_PATH_PREFIX}${id}`,
|
|
144
|
+
truncated,
|
|
145
|
+
from,
|
|
146
|
+
// Clamp to non-negative: when `from` exceeds totalLines (e.g. reading
|
|
147
|
+
// past the end of a 1-line fact), sliceEnd - from + 1 goes negative.
|
|
148
|
+
// A bridge must never surface a negative line count.
|
|
149
|
+
lines: Math.max(0, sliceEnd - from + 1),
|
|
150
|
+
nextFrom,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function status() {
|
|
154
|
+
// `backend: 'builtin'` mirrors OpenClaw's non-qmd providers. The
|
|
155
|
+
// provider string is what the active-memory sub-agent logs against.
|
|
156
|
+
return { backend: 'builtin', provider: 'totalreclaw' };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* probeEmbeddingAvailability / probeVectorAvailability: optimistic OK
|
|
160
|
+
* here. The real availability depends on the injected pipeline (Task
|
|
161
|
+
* 2.3 wires the embedder + vector store); this adapter doesn't own
|
|
162
|
+
* that state, so the probes report ok until 2.3 gives them real hooks.
|
|
163
|
+
*/
|
|
164
|
+
async function probeEmbeddingAvailability() {
|
|
165
|
+
// TODO(task 2.3): replace with real embedder probe.
|
|
166
|
+
return { ok: true };
|
|
167
|
+
}
|
|
168
|
+
async function probeVectorAvailability() {
|
|
169
|
+
// TODO(task 2.3): replace with real vector-store probe.
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
async function close() { }
|
|
173
|
+
return {
|
|
174
|
+
search,
|
|
175
|
+
readFile,
|
|
176
|
+
status,
|
|
177
|
+
probeEmbeddingAvailability,
|
|
178
|
+
probeVectorAvailability,
|
|
179
|
+
close,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// createTrMemoryPluginRuntime — the MemoryPluginRuntime wrapper (Task 2.3)
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
//
|
|
186
|
+
// WHY THIS WRAPPER EXISTS:
|
|
187
|
+
// OpenClaw 2026.6.8's memory subsystem does NOT call search/get directly.
|
|
188
|
+
// It calls `runtime.getMemorySearchManager(...)` to obtain a
|
|
189
|
+
// MemorySearchManager, then invokes `.search()` / readFile on it. It also
|
|
190
|
+
// calls `resolveMemoryBackendConfig` to decide between the built-in
|
|
191
|
+
// provider and an external `qmd` process, and `close*` on shutdown.
|
|
192
|
+
//
|
|
193
|
+
// So this wrapper is the seam OpenClaw actually talks to. It returns a
|
|
194
|
+
// fresh TrMemorySearchManager bound to the injected recall/getById
|
|
195
|
+
// pipeline on each getMemorySearchManager call. TR is its own backend
|
|
196
|
+
// (not qmd), so resolveMemoryBackendConfig reports `builtin`.
|
|
197
|
+
//
|
|
198
|
+
// The real pipeline binding — recall/getById wired to subgraph-search +
|
|
199
|
+
// vault-crypto.decrypt + reranker, parameterized by the paired account —
|
|
200
|
+
// is Task 2.7's `buildRecallDeps` in register(). This wrapper just carries
|
|
201
|
+
// whatever deps it's given.
|
|
202
|
+
//
|
|
203
|
+
// SCANNER-CLEAN HARD CONTRACT (env=N net=N):
|
|
204
|
+
// This function is pure orchestration. It touches NO environment state and
|
|
205
|
+
// performs NO outbound network I/O. The injected deps own all I/O. The
|
|
206
|
+
// `cfg` parameter is held as opaque (`unknown`) on purpose — the plugin
|
|
207
|
+
// does not import OpenClaw's config type, and getMemorySearchManager does
|
|
208
|
+
// not read anything off cfg (the paired-account context arrives via deps
|
|
209
|
+
// bound in 2.7, not via cfg here).
|
|
210
|
+
//
|
|
211
|
+
// ERROR CONTRACT:
|
|
212
|
+
// getMemorySearchManager MUST NEVER throw out of its async boundary — a
|
|
213
|
+
// failure to construct the adapter surfaces as `{ manager: null, error }`.
|
|
214
|
+
// Today construction is a closure capture and cannot realistically fail,
|
|
215
|
+
// but the try/catch is the durable guarantee for the day 2.7 adds
|
|
216
|
+
// paired-account resolution at construction time.
|
|
217
|
+
export function createTrMemoryPluginRuntime(deps) {
|
|
218
|
+
return {
|
|
219
|
+
async getMemorySearchManager(_params) {
|
|
220
|
+
try {
|
|
221
|
+
return {
|
|
222
|
+
manager: createTrMemorySearchManager(deps),
|
|
223
|
+
error: undefined,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
catch (e) {
|
|
227
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
228
|
+
return { manager: null, error: msg };
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
resolveMemoryBackendConfig(_params) {
|
|
232
|
+
// TR is its own backend — never the external qmd process path.
|
|
233
|
+
return { backend: 'builtin' };
|
|
234
|
+
},
|
|
235
|
+
async closeMemorySearchManager(_params) {
|
|
236
|
+
// No per-manager resources to release today: the adapter holds only the
|
|
237
|
+
// injected closures; the closures' lifetimes are owned by register().
|
|
238
|
+
// Task 2.7 may add connection-pool / embedder teardown here.
|
|
239
|
+
},
|
|
240
|
+
async closeAllMemorySearchManagers() {
|
|
241
|
+
// See closeMemorySearchManager — no-op until 2.7 binds pool resources.
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// buildPromptSection — recall guidance + quota warning + pinned facts
|
|
247
|
+
// (Task 2.4)
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
//
|
|
250
|
+
// WHY THIS EXISTS:
|
|
251
|
+
// OpenClaw's memory subsystem calls the registered `promptBuilder` to
|
|
252
|
+
// inject memory guidance into the agent's system prompt. The bundled
|
|
253
|
+
// memory-core's reference branches on which memory tools are available:
|
|
254
|
+
// search + get -> search first, then pull only the needed lines.
|
|
255
|
+
// search only -> search and answer from the matching results.
|
|
256
|
+
// get only -> pull only the needed lines.
|
|
257
|
+
// neither -> no guidance.
|
|
258
|
+
// TR's promptBuilder mirrors that branching BUT adapts the wording to
|
|
259
|
+
// TR's encrypted-vault model (the agent doesn't see files; it calls
|
|
260
|
+
// memory_search/memory_get which decrypt on the fly), AND adds two
|
|
261
|
+
// Hermes-grade extras via injected deps:
|
|
262
|
+
//
|
|
263
|
+
// 1. QUOTA WARNING — when the vault is near quota (>80% used) OR the
|
|
264
|
+
// last capture hit a 403, prepend a one-line warning so the agent
|
|
265
|
+
// can tell the user new memories may not be saved. Mirrors Hermes
|
|
266
|
+
// `on_session_start`'s billing-cache >0.8 + 403 path.
|
|
267
|
+
//
|
|
268
|
+
// 2. PINNED FACTS — always surface pinned facts as a `Pinned memories:`
|
|
269
|
+
// block, regardless of the query. These are always-relevant (user
|
|
270
|
+
// preferences, core commitments) and mirror Hermes surfacing
|
|
271
|
+
// pinned facts at session start.
|
|
272
|
+
//
|
|
273
|
+
// The quota + pinned data arrive via the injected `deps` object so this
|
|
274
|
+
// function stays environment/network clean — the caller in Task 2.7's
|
|
275
|
+
// `buildRecallDeps` binds real quota/pinned from the paired account.
|
|
276
|
+
// `citationsMode` is accepted for shape compatibility with the
|
|
277
|
+
// MemoryPluginCapability contract; it does not alter the guidance today
|
|
278
|
+
// (memory-core itself does not branch on it either, as of 2026.6.8).
|
|
279
|
+
//
|
|
280
|
+
// SCANNER-CLEAN HARD CONTRACT (env=N net=N):
|
|
281
|
+
// This function is pure string rendering. It touches NO host environment
|
|
282
|
+
// state and performs NO outbound network I/O. All quota + pinned data
|
|
283
|
+
// arrives via the deps parameter; the host environment and network
|
|
284
|
+
// primitives are never referenced. Neither the env-harvesting pair nor
|
|
285
|
+
// the disk-exfil pair can ever co-occur here — this docstring itself
|
|
286
|
+
// avoids the literal trigger tokens for that reason.
|
|
287
|
+
/** Quota threshold above which the warning fires. Matches Hermes >0.8. */
|
|
288
|
+
const QUOTA_WARN_THRESHOLD_PCT = 80;
|
|
289
|
+
/**
|
|
290
|
+
* Build the memory-prompt guidance section. Returns an array of lines
|
|
291
|
+
* (OpenClaw concatenates them into the system prompt).
|
|
292
|
+
*
|
|
293
|
+
* Ordering when all three are present:
|
|
294
|
+
* 1. (optional) quota warning — prepended so it's the first thing the
|
|
295
|
+
* agent sees; a near-full vault affects every capture decision.
|
|
296
|
+
* 2. recall guidance — the memory-core branching block.
|
|
297
|
+
* 3. (optional) pinned block — appended so always-relevant facts sit
|
|
298
|
+
* after the recall instructions the agent must follow.
|
|
299
|
+
*/
|
|
300
|
+
export function buildPromptSection(params, deps = {}) {
|
|
301
|
+
const out = [];
|
|
302
|
+
// (1) Quota warning — prepend when >80% used OR on 403/denied.
|
|
303
|
+
if (deps.quota !== undefined) {
|
|
304
|
+
const q = deps.quota;
|
|
305
|
+
const isOver = 'denied' in q ? q.denied === true : typeof q.usedPct === 'number' && q.usedPct > QUOTA_WARN_THRESHOLD_PCT;
|
|
306
|
+
if (isOver) {
|
|
307
|
+
// Wording mirrors Hermes: tells the agent new memories may not be
|
|
308
|
+
// saved so it can surface the state to the user when relevant.
|
|
309
|
+
out.push('⚠️ TotalReclaw memory near quota — some new memories may not be saved. ' +
|
|
310
|
+
'Let the user know if they ask why something was not remembered.');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// (2) Recall guidance — branch on memory tool availability, same shape
|
|
314
|
+
// as memory-core but adapted to TR's encrypted-vault model. The agent
|
|
315
|
+
// never sees files; memory_search / memory_get decrypt on demand.
|
|
316
|
+
const hasSearch = params.availableTools.has('memory_search');
|
|
317
|
+
const hasGet = params.availableTools.has('memory_get');
|
|
318
|
+
if (hasSearch && hasGet) {
|
|
319
|
+
out.push('Before answering anything about prior work, decisions, dates, people, ' +
|
|
320
|
+
'preferences, or todos: run memory_search against the user’s encrypted ' +
|
|
321
|
+
'TotalReclaw memory vault, then use memory_get to pull only the needed ' +
|
|
322
|
+
'facts in full. If your confidence is low after searching, say you ' +
|
|
323
|
+
'checked memory and could not find it.');
|
|
324
|
+
}
|
|
325
|
+
else if (hasSearch) {
|
|
326
|
+
out.push('Before answering anything about prior work, decisions, dates, people, ' +
|
|
327
|
+
'preferences, or todos: run memory_search against the user’s encrypted ' +
|
|
328
|
+
'TotalReclaw memory vault and answer from the matching results. If your ' +
|
|
329
|
+
'confidence is low after searching, say you checked memory and could ' +
|
|
330
|
+
'not find it.');
|
|
331
|
+
}
|
|
332
|
+
else if (hasGet) {
|
|
333
|
+
out.push('When you need a specific prior fact, use memory_get to pull it in full ' +
|
|
334
|
+
'from the user’s encrypted TotalReclaw memory vault. Do not speculate ' +
|
|
335
|
+
'about prior work, decisions, dates, people, preferences, or todos ' +
|
|
336
|
+
'without checking memory first.');
|
|
337
|
+
}
|
|
338
|
+
// If neither tool is available, no recall guidance is emitted (matches
|
|
339
|
+
// memory-core's no-guidance path). Pinned facts below still surface.
|
|
340
|
+
// (3) Pinned facts — always surface, even with no memory tools, because
|
|
341
|
+
// pinned facts are always-relevant context the agent should know
|
|
342
|
+
// regardless of whether it can also search/get.
|
|
343
|
+
const pinned = deps.pinned;
|
|
344
|
+
if (pinned !== undefined && pinned.length > 0) {
|
|
345
|
+
out.push('Pinned memories (always relevant):');
|
|
346
|
+
for (const p of pinned) {
|
|
347
|
+
// Each pinned fact on its own line, prefixed so the agent can tell
|
|
348
|
+
// the block apart from the recall guidance above. The plaintext is
|
|
349
|
+
// already decrypted by the caller (Task 2.7 binds this to the real
|
|
350
|
+
// pinned-fact lookup + decrypt pipeline).
|
|
351
|
+
out.push(`- ${p.plaintext}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return out;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Default flush thresholds. Cribbed from memory-core's
|
|
358
|
+
* `buildMemoryFlushPlan` defaults (OpenClaw 2026.6.8):
|
|
359
|
+
* - softThresholdTokens = 4000 (flush as context nears 4k tokens)
|
|
360
|
+
* - forceFlushTranscriptBytes = 2 MiB (hard flush on raw transcript size)
|
|
361
|
+
* - reserveTokensFloor = 20000 (headroom kept after a flush)
|
|
362
|
+
* These are documented defaults to be tuned at the H2 QA gate; Task 2.7
|
|
363
|
+
* may override them from cfg.agents.defaults.compaction.memoryFlush.
|
|
364
|
+
*/
|
|
365
|
+
const DEFAULT_SOFT_THRESHOLD_TOKENS = 4000;
|
|
366
|
+
const DEFAULT_FORCE_FLUSH_TRANSCRIPT_BYTES = 2 * 1024 * 1024; // 2 MiB
|
|
367
|
+
const DEFAULT_RESERVE_TOKENS_FLOOR = 20000;
|
|
368
|
+
/**
|
|
369
|
+
* TR-canonical user-prompt TEMPLATE for flush-driven extraction. Mirrors
|
|
370
|
+
* the turn-extraction user prompt built inline in extractor.ts's
|
|
371
|
+
* `extractFacts()` (see `extractor.ts:1596`): the host appends the
|
|
372
|
+
* trajectory slice (and optionally the dedup context) after this prefix.
|
|
373
|
+
* Kept here rather than in extractor.ts because extractor.ts builds the
|
|
374
|
+
* user prompt dynamically per-call (it concatenates conversationText +
|
|
375
|
+
* existing-memory dedup context), so there's no single constant to
|
|
376
|
+
* import. This template captures the TR wording so the host's flush-
|
|
377
|
+
* driven extraction produces facts in the same shape as turn extraction.
|
|
378
|
+
*/
|
|
379
|
+
const EXTRACTION_USER_PROMPT = 'Extract important facts from these recent conversation turns:\n\n';
|
|
380
|
+
/**
|
|
381
|
+
* Scratch path prefix for TR flush output. The host writes the extraction
|
|
382
|
+
* result here before handing it to the plugin for encrypt→on-chain.
|
|
383
|
+
* Namespaced so it cannot collide with memory-core's `memory/*.md` paths.
|
|
384
|
+
*/
|
|
385
|
+
const TR_FLUSH_DIR = '.totalreclaw/flush';
|
|
386
|
+
/**
|
|
387
|
+
* Format a UTC date stamp (YYYY-MM-DD) from a epoch-ms value. Pure
|
|
388
|
+
* function of the input — no host TZ dependence, no Intl nuance.
|
|
389
|
+
*/
|
|
390
|
+
function formatUtcDateStamp(nowMs) {
|
|
391
|
+
// ISO 8601 UTC: slice the YYYY-MM-DD prefix off the date portion. This
|
|
392
|
+
// mirrors memory-core's date-stamp derivation (which uses the host's
|
|
393
|
+
// configured TZ); TR deliberately uses UTC so the path is invariant
|
|
394
|
+
// across hosts and timezones — the path is a function of nowMs alone.
|
|
395
|
+
return new Date(nowMs).toISOString().slice(0, 10);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Build the memory flush plan returned to OpenClaw's host.
|
|
399
|
+
*
|
|
400
|
+
* @param params.cfg OpenClaw config (loose; today only the compaction
|
|
401
|
+
* overrides are consulted, and only `enabled:false`
|
|
402
|
+
* forces null. Task 2.7 may wire more overrides.)
|
|
403
|
+
* @param params.nowMs epoch-ms used to derive the date-stamped relativePath.
|
|
404
|
+
* Defaults to Date.now() — a pure clock read.
|
|
405
|
+
* @returns the MemoryFlushPlan, or null only if capture is explicitly
|
|
406
|
+
* disabled via cfg. Today capture is always on, so this never
|
|
407
|
+
* returns null in practice.
|
|
408
|
+
*/
|
|
409
|
+
export function buildFlushPlan(params = {}) {
|
|
410
|
+
const cfg = params.cfg;
|
|
411
|
+
const flushCfg = cfg?.agents?.defaults?.compaction?.memoryFlush;
|
|
412
|
+
// Capture is opt-OUT: only an explicit `enabled: false` returns null.
|
|
413
|
+
// This mirrors memory-core's contract. TR has no reason to disable
|
|
414
|
+
// capture (the poller is always-on), so default is to ship the plan.
|
|
415
|
+
if (flushCfg?.enabled === false)
|
|
416
|
+
return null;
|
|
417
|
+
// Thresholds: cribbed defaults, optionally overridden by cfg. The byte
|
|
418
|
+
// size accepts a number or a human string (e.g. "2MiB") — we only honor
|
|
419
|
+
// numeric overrides today; string parsing is left to Task 2.7.
|
|
420
|
+
const softThresholdTokens = typeof flushCfg?.softThresholdTokens === 'number' && flushCfg.softThresholdTokens >= 0
|
|
421
|
+
? flushCfg.softThresholdTokens
|
|
422
|
+
: DEFAULT_SOFT_THRESHOLD_TOKENS;
|
|
423
|
+
const forceFlushTranscriptBytes = typeof flushCfg?.forceFlushTranscriptBytes === 'number' && flushCfg.forceFlushTranscriptBytes >= 0
|
|
424
|
+
? flushCfg.forceFlushTranscriptBytes
|
|
425
|
+
: DEFAULT_FORCE_FLUSH_TRANSCRIPT_BYTES;
|
|
426
|
+
const reserveTokensFloor = typeof cfg?.agents?.defaults?.compaction?.reserveTokensFloor === 'number' &&
|
|
427
|
+
cfg.agents.defaults.compaction.reserveTokensFloor >= 0
|
|
428
|
+
? cfg.agents.defaults.compaction.reserveTokensFloor
|
|
429
|
+
: DEFAULT_RESERVE_TOKENS_FLOOR;
|
|
430
|
+
// Extraction prompt: TR's canonical v1 taxonomy prompt. Imported from
|
|
431
|
+
// extractor.ts at module load. The host hands this to the LLM at flush
|
|
432
|
+
// time; the resulting facts are then encrypt→on-chain captured by the
|
|
433
|
+
// poller (today) or the flush-driven capture path (Task 4.2).
|
|
434
|
+
//
|
|
435
|
+
// cfg overrides are honored if provided (string, trimmed) — same shape
|
|
436
|
+
// as memory-core.
|
|
437
|
+
const prompt = typeof flushCfg?.prompt === 'string' && flushCfg.prompt.trim().length > 0
|
|
438
|
+
? flushCfg.prompt.trim()
|
|
439
|
+
: EXTRACTION_USER_PROMPT;
|
|
440
|
+
const systemPrompt = typeof flushCfg?.systemPrompt === 'string' && flushCfg.systemPrompt.trim().length > 0
|
|
441
|
+
? flushCfg.systemPrompt.trim()
|
|
442
|
+
: EXTRACTION_SYSTEM_PROMPT;
|
|
443
|
+
const model = typeof flushCfg?.model === 'string' && flushCfg.model.trim().length > 0
|
|
444
|
+
? flushCfg.model.trim()
|
|
445
|
+
: undefined;
|
|
446
|
+
// relativePath: TR-namespaced scratch path, date-stamped from nowMs.
|
|
447
|
+
const nowMs = typeof params.nowMs === 'number' ? params.nowMs : Date.now();
|
|
448
|
+
const dateStamp = formatUtcDateStamp(nowMs);
|
|
449
|
+
const relativePath = `${TR_FLUSH_DIR}/${dateStamp}.jsonl`;
|
|
450
|
+
return {
|
|
451
|
+
softThresholdTokens,
|
|
452
|
+
forceFlushTranscriptBytes,
|
|
453
|
+
reserveTokensFloor,
|
|
454
|
+
model,
|
|
455
|
+
prompt,
|
|
456
|
+
systemPrompt,
|
|
457
|
+
relativePath,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* native-memory — the OpenClaw-native MemoryPluginCapability wiring helper
|
|
3
|
+
* for the TR plugin (Task 2.7 of the OpenClaw native integration plan,
|
|
4
|
+
* docs/plans/2026-06-21-openclaw-native-integration-plan.md, 2026-06-21).
|
|
5
|
+
*
|
|
6
|
+
* WHY THIS FILE EXISTS:
|
|
7
|
+
* This is THE integration point. OpenClaw 2026.6.8 exposes a
|
|
8
|
+
* `api.registerMemoryCapability({ promptBuilder, flushPlanResolver, runtime })`
|
|
9
|
+
* host surface plus the conventional `api.registerTool(...)` calls for the
|
|
10
|
+
* `memory_search` / `memory_get` tools the active-memory sub-agent already
|
|
11
|
+
* knows how to drive. For TR to BE the memory backend (not just a tool
|
|
12
|
+
* plugin), it must register all four against TR's own pipeline.
|
|
13
|
+
*
|
|
14
|
+
* This helper takes a single `deps` object (built by `buildRecallDeps` in
|
|
15
|
+
* index.ts) carrying:
|
|
16
|
+
* - recall / getById (the real subgraph-search + decrypt + reranker
|
|
17
|
+
* pipeline, parameterized by the paired account — wired in index.ts
|
|
18
|
+
* because that is where the unexported pipeline helpers + the
|
|
19
|
+
* module-level auth/encryption/owner state live)
|
|
20
|
+
* - quota / pinned (prompt-builder inputs; default to "no warning" /
|
|
21
|
+
* "no pinned" when the caller does not supply them — see TODO markers
|
|
22
|
+
* in index.ts's buildRecallDeps for the H1 QA gate)
|
|
23
|
+
*
|
|
24
|
+
* and performs the canonical four-call registration in the order memory-core
|
|
25
|
+
* itself registers them (verified at
|
|
26
|
+
* /tmp/tr-openclaw-probe/node_modules/openclaw/dist/extensions/memory-core/
|
|
27
|
+
* index.js, 2026.6.8):
|
|
28
|
+
* 1. api.registerMemoryCapability({ promptBuilder, flushPlanResolver, runtime })
|
|
29
|
+
* 2. api.registerTool(() => createMemorySearchTool(runtime), { names: ['memory_search'] })
|
|
30
|
+
* 3. api.registerTool(() => createMemoryGetTool(runtime), { names: ['memory_get'] })
|
|
31
|
+
*
|
|
32
|
+
* The SAME `runtime` instance is handed to the capability AND both tool
|
|
33
|
+
* factories. This is load-bearing: the host calls
|
|
34
|
+
* `runtime.getMemorySearchManager(...)` to obtain a manager, and the
|
|
35
|
+
* tools obtain a manager via the same surface. A distinct runtime per
|
|
36
|
+
* registration would still work today (each construction is a pure
|
|
37
|
+
* closure capture) but would violate the memory-core invariant and
|
|
38
|
+
* break the day runtime owns real per-manager resources (embedder pool,
|
|
39
|
+
* connection cache). register-native.test.ts asserts the identity.
|
|
40
|
+
*
|
|
41
|
+
* DESIGN: WHY THE DEPS OBJECT IS PRE-BUILT, NOT BUILT HERE.
|
|
42
|
+
* `buildRecallDeps` lives in index.ts (not here) on purpose:
|
|
43
|
+
* 1. The real recall/getById closures capture unexported index.ts helpers
|
|
44
|
+
* (ensureInitialized, generateBlindIndices, generateEmbedding,
|
|
45
|
+
* getLSHHasher, computeCandidatePool, isDigestBlob, readClaimFromBlob,
|
|
46
|
+
* searchSubgraph, fetchFactById) AND module-level state
|
|
47
|
+
* (authKeyHex / encryptionKey / userId / subgraphOwner). Moving them
|
|
48
|
+
* here would force either (a) exporting all of those from index.ts
|
|
49
|
+
* (high blast-radius refactor with scanner-trap risk) or (b)
|
|
50
|
+
* re-plumbing them through this file's signature. Neither is in scope
|
|
51
|
+
* for Task 2.7.
|
|
52
|
+
* 2. The paired-account context is resolved LAZILY by `ensureInitialized()`
|
|
53
|
+
* on the first tool/hook call, NOT synchronously at register() time.
|
|
54
|
+
* So the closures must call `ensureInitialized(logger)` internally —
|
|
55
|
+
* they cannot be resolved at register() time even if we wanted to.
|
|
56
|
+
* 3. Keeping this file scanner-trivial: it touches NO host environment
|
|
57
|
+
* state and performs NO outbound network I/O. The closures live in
|
|
58
|
+
* index.ts alongside the rest of the plugin's network surface.
|
|
59
|
+
*
|
|
60
|
+
* SCANNER-CLEAN HARD CONTRACT (env=N net=N):
|
|
61
|
+
* This file is pure orchestration. It contains no environment-variable
|
|
62
|
+
* read token and no outbound network primitive, so neither the
|
|
63
|
+
* env-harvesting pair nor the disk-exfiltration pair can ever co-occur
|
|
64
|
+
* here. It also contains no dynamic-code-evaluation primitive (no
|
|
65
|
+
* runtime `eval` call, no `new Function` constructor). `npm run
|
|
66
|
+
* check-scanner` MUST remain 0 flags; this docstring itself avoids the
|
|
67
|
+
* literal trigger tokens for that reason.
|
|
68
|
+
*
|
|
69
|
+
* `npm run build` uses `--noCheck`, so the loose typing (the api object
|
|
70
|
+
* is typed against a minimal local interface) is safe — the plugin does
|
|
71
|
+
* not import OpenClaw's type package.
|
|
72
|
+
*/
|
|
73
|
+
import { createTrMemoryPluginRuntime, buildPromptSection, buildFlushPlan, } from './memory-runtime.js';
|
|
74
|
+
import { createMemorySearchTool, createMemoryGetTool } from './tools.js';
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// registerNativeMemory — the canonical four-call wiring
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
/**
|
|
79
|
+
* Wire TR's native MemoryPluginCapability + the two memory tools into the
|
|
80
|
+
* OpenClaw host. Performs the canonical registration in the order memory-core
|
|
81
|
+
* itself registers them (capability first, then both tools).
|
|
82
|
+
*
|
|
83
|
+
* The SAME `runtime` instance is passed to:
|
|
84
|
+
* - api.registerMemoryCapability({ ..., runtime }) (host surface)
|
|
85
|
+
* - createMemorySearchTool(runtime) (memory_search factory)
|
|
86
|
+
* - createMemoryGetTool(runtime) (memory_get factory)
|
|
87
|
+
*
|
|
88
|
+
* Identity is load-bearing — see file header. register-native.test.ts
|
|
89
|
+
* asserts the same `runtime` reaches all three.
|
|
90
|
+
*
|
|
91
|
+
* @param api the OpenClaw plugin api (registerMemoryCapability + registerTool)
|
|
92
|
+
* @param deps the combined deps object from buildRecallDeps in index.ts
|
|
93
|
+
* @returns the runtime that was registered (for callers that want to
|
|
94
|
+
* hold a reference, e.g. for close on plugin stop)
|
|
95
|
+
*/
|
|
96
|
+
export function registerNativeMemory(api, deps) {
|
|
97
|
+
// Build the runtime ONCE. This is the single object that flows into the
|
|
98
|
+
// capability AND both tool factories below — identity is asserted by
|
|
99
|
+
// register-native.test.ts.
|
|
100
|
+
const runtime = createTrMemoryPluginRuntime({
|
|
101
|
+
recall: deps.recall,
|
|
102
|
+
getById: deps.getById,
|
|
103
|
+
});
|
|
104
|
+
// (1) Register the capability. The prompt builder closes over deps.quota
|
|
105
|
+
// and deps.pinned directly (it needs no runtime state — it's pure string
|
|
106
|
+
// rendering, see memory-runtime.ts buildPromptSection). flushPlanResolver
|
|
107
|
+
// is stateless (it returns TR's canonical extraction plan; capture is not
|
|
108
|
+
// required, we hand the function reference verbatim).
|
|
109
|
+
api.registerMemoryCapability({
|
|
110
|
+
promptBuilder: (params) => buildPromptSection(params, { quota: deps.quota, pinned: deps.pinned }),
|
|
111
|
+
flushPlanResolver: buildFlushPlan,
|
|
112
|
+
runtime,
|
|
113
|
+
});
|
|
114
|
+
// (2) + (3) Register the two tools. The factories capture the SAME runtime
|
|
115
|
+
// (the captured-runtime design — see tools.ts header). The conventional
|
|
116
|
+
// tool names survive the tool-policy strip in OC 2026.5.x (issue #223):
|
|
117
|
+
// they are passed via the `names` opts so the SDK's name bookkeeping sees
|
|
118
|
+
// them even though the tool object's own `name` field is the visible
|
|
119
|
+
// registration name.
|
|
120
|
+
api.registerTool(() => createMemorySearchTool(runtime), { names: ['memory_search'] });
|
|
121
|
+
api.registerTool(() => createMemoryGetTool(runtime), { names: ['memory_get'] });
|
|
122
|
+
return runtime;
|
|
123
|
+
}
|
package/dist/onboarding-cli.js
CHANGED
|
@@ -43,7 +43,7 @@ import path from 'node:path';
|
|
|
43
43
|
import readline from 'node:readline/promises';
|
|
44
44
|
import { generateMnemonic, validateMnemonic } from '@scure/bip39';
|
|
45
45
|
import { wordlist } from '@scure/bip39/wordlists/english.js';
|
|
46
|
-
import {
|
|
46
|
+
import { writeCredentialsJson, loadCredentialsJson, writeOnboardingState, loadOnboardingState, } from './fs-helpers.js';
|
|
47
47
|
// ---------------------------------------------------------------------------
|
|
48
48
|
// User-facing strings (centralised so tests can assert on them + localisation
|
|
49
49
|
// has one place to grow into later).
|
|
@@ -55,7 +55,8 @@ import { defaultPairPendingPath, deletePairPendingFile, writeCredentialsJson, lo
|
|
|
55
55
|
*
|
|
56
56
|
* The phrase-print branch will be REMOVED in the next RC after rc.18.
|
|
57
57
|
* Users running on a TTY can still complete the flow in rc.18; agents
|
|
58
|
-
* MUST use `--pair-only` or
|
|
58
|
+
* MUST use `--pair-only` (or `tr pair --url-pin`) — the legacy
|
|
59
|
+
* `totalreclaw_pair` agent tool was retired in Phase 3.2.
|
|
59
60
|
*/
|
|
60
61
|
export const PHRASE_PRINT_DEPRECATION_WARNING = '\nDEPRECATION (issue #95): the interactive `openclaw totalreclaw onboard` flow\n' +
|
|
61
62
|
' prints your recovery phrase to this terminal. This is being removed in the\n' +
|
|
@@ -111,7 +112,7 @@ export const COPY = {
|
|
|
111
112
|
postSuccessImport: '\nDone. Your phrase is saved at ~/.totalreclaw/credentials.json (mode 0600).\n' +
|
|
112
113
|
'Memory tools are now active.\n\n' +
|
|
113
114
|
' Next: run `openclaw chat` to start. Existing memories tied to this\n' +
|
|
114
|
-
' phrase
|
|
115
|
+
' phrase are recalled automatically by the agent via memory_search.\n',
|
|
115
116
|
skipped: '\nSkipped. Run `openclaw totalreclaw onboard` anytime to resume.\n' +
|
|
116
117
|
'Memory tools remain disabled until you do.\n',
|
|
117
118
|
ackFailed: '\nWord mismatch. Please write the phrase down carefully and run this\n' +
|
|
@@ -271,11 +272,6 @@ function writeCredsAndState(credentialsPath, statePath, mnemonic, createdBy) {
|
|
|
271
272
|
if (!writeOnboardingState(statePath, state)) {
|
|
272
273
|
throw new Error(`Could not write state.json at ${statePath}. Check that the parent directory is writable.`);
|
|
273
274
|
}
|
|
274
|
-
// 3.3.13 — sentinel cleanup. The CLI wizard finalizes setup independently
|
|
275
|
-
// of the relay flow, but the user may have triggered auto-pair-on-load
|
|
276
|
-
// earlier in the same dir; the .pair-pending.json sentinel must not
|
|
277
|
-
// outlive credentials.json or the agent will keep surfacing a stale URL.
|
|
278
|
-
deletePairPendingFile(defaultPairPendingPath(credentialsPath));
|
|
279
275
|
return state;
|
|
280
276
|
}
|
|
281
277
|
// ---------------------------------------------------------------------------
|
package/dist/pair-cli-relay.js
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
*/
|
|
46
46
|
import { validateMnemonic } from '@scure/bip39';
|
|
47
47
|
import { wordlist } from '@scure/bip39/wordlists/english.js';
|
|
48
|
-
import {
|
|
48
|
+
import { loadCredentialsJson, writeCredentialsJson, writeOnboardingState, } from './fs-helpers.js';
|
|
49
49
|
import { awaitPhraseUpload, openRemotePairSession, } from './pair-remote-client.js';
|
|
50
50
|
import { setRecoveryPhraseOverride } from './config.js';
|
|
51
51
|
import { encodePng, encodeUnicode } from './pair-qr.js';
|
|
@@ -280,13 +280,6 @@ export async function runRelayPairCli(mode, opts) {
|
|
|
280
280
|
credentialsCreatedAt: new Date().toISOString(),
|
|
281
281
|
version: opts.pluginVersion,
|
|
282
282
|
});
|
|
283
|
-
// 3.3.13 — sentinel cleanup. The CLI flow shares the
|
|
284
|
-
// .pair-pending.json sentinel with the auto-pair-on-load flow
|
|
285
|
-
// (a user who runs `tr pair --json` after the plugin already
|
|
286
|
-
// auto-opened a session ends up with both surfaces pointing at
|
|
287
|
-
// the same credentials.json target). Delete the sentinel here
|
|
288
|
-
// so subsequent agent turns don't keep surfacing a stale URL.
|
|
289
|
-
deletePairPendingFile(defaultPairPendingPath(opts.credentialsPath));
|
|
290
283
|
opts.logger.info(`pair-cli (relay): session ${session.token.slice(0, 8)}… completed; credentials written` +
|
|
291
284
|
(registeredUserId ? ` (userId=${registeredUserId.slice(0, 8)}…)` : '') +
|
|
292
285
|
(scopeAddress ? ` (scope_address=${scopeAddress})` : ''));
|
package/dist/pair-cli.js
CHANGED
|
@@ -305,7 +305,7 @@ export function registerPairCli(program, deps) {
|
|
|
305
305
|
'works through NAT and inside Docker). Use --local to fall back to ' +
|
|
306
306
|
'gateway-loopback URLs for air-gapped setups.')
|
|
307
307
|
.option('--json', 'Emit a single JSON payload (url/pin/qr_ascii) instead of the human-readable banner. Enables agent-driven pairing.')
|
|
308
|
-
.option('--url-pin-only', 'Emit ONLY {v,url,pin,expires_at_ms} — no QR ASCII, no SID, no mode echo. Headless fallback for container-based agents
|
|
308
|
+
.option('--url-pin-only', 'Emit ONLY {v,url,pin,expires_at_ms} — no QR ASCII, no SID, no mode echo. Headless fallback for container-based agents (issue #87; the legacy totalreclaw_pair agent tool was retired in Phase 3.2, so this is now the canonical agent-driven pair surface). Zero phrase exposure on stdout.')
|
|
309
309
|
.option('--local', '(3.3.4-rc.1) Use the loopback / LAN URL flow instead of the relay. URLs point at this gateway\'s bound interface (e.g. http://localhost:18789/…) and require the user\'s browser to be on a reachable network. Default since rc.6 was relay; this flag preserves the air-gapped path.')
|
|
310
310
|
.option('--timeout <sec>', 'Session TTL in seconds (default: 900 = 15 min, matches pair-session-store default)')
|
|
311
311
|
.action(async (...args) => {
|