@securecoms/agent-bridge 0.7.0 → 0.9.0
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 +2 -0
- package/connectors/hermes/__init__.py +60 -0
- package/connectors/hermes/adapter.py +470 -0
- package/connectors/hermes/plugin.yaml +25 -0
- package/connectors/hermes/pytest.ini +3 -0
- package/connectors/hermes/securecoms_gate.py +70 -0
- package/connectors/hermes/securecoms_transport.py +257 -0
- package/connectors/hermes/tests/conftest.py +23 -0
- package/connectors/hermes/tests/test_adapter_wiring.py +541 -0
- package/connectors/hermes/tests/test_securecoms_gate.py +113 -0
- package/connectors/hermes/tests/test_securecoms_transport.py +608 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +467 -67
- package/dist/bridge-agent.d.ts +4 -0
- package/dist/config.d.ts +4 -0
- package/dist/http-server.d.ts +4 -4
- package/dist/index.js +160 -18
- package/dist/memory/openclaw-provider.d.ts +10 -1
- package/dist/memory/openclaw-workspace-facts.d.ts +38 -0
- package/dist/memory/provider.d.ts +2 -1
- package/dist/service/harness-wire.d.ts +3 -2
- package/dist/service/hermes-config.d.ts +51 -0
- package/dist/service/wire.d.ts +97 -40
- package/package.json +1 -1
package/dist/bridge-agent.d.ts
CHANGED
|
@@ -842,6 +842,10 @@ export declare class BridgeAgent {
|
|
|
842
842
|
hermes_write_failed?: number;
|
|
843
843
|
openclaw_no_session?: number;
|
|
844
844
|
openclaw_read_failed?: number;
|
|
845
|
+
openclaw_facts_missing?: number;
|
|
846
|
+
openclaw_facts_read_failed?: number;
|
|
847
|
+
openclaw_facts_truncated?: number;
|
|
848
|
+
openclaw_facts_chars?: number;
|
|
845
849
|
};
|
|
846
850
|
/**
|
|
847
851
|
* Loopback observability snapshot served at GET /v1/metrics.
|
package/dist/config.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export interface BridgeConfig {
|
|
|
23
23
|
memoryBaseDir?: string;
|
|
24
24
|
/** For memoryProvider="openclaw": the OpenClaw agent dir slug, e.g. "cortina" (→ <base_dir>/agents/<agent>/). */
|
|
25
25
|
memoryAgent?: string;
|
|
26
|
+
/** For memoryProvider="openclaw": explicit workspace dir override; wins over openclaw.json (spec D4 rung 1). */
|
|
27
|
+
memoryWorkspaceDir?: string;
|
|
28
|
+
/** For memoryProvider="openclaw": total facts cap in UTF-16 code units. 0 = facts kill switch (preserved via ?? downstream, never ||). Absent/junk → provider default 8000. */
|
|
29
|
+
memoryFactsCharBudget?: number;
|
|
26
30
|
}
|
|
27
31
|
export declare const REQUIRED: readonly ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
|
|
28
32
|
export declare function loadConfig(opts: {
|
package/dist/http-server.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export declare const memoryRecordSchema: z.ZodEffects<z.ZodObject<{
|
|
|
34
34
|
message_id: z.ZodOptional<z.ZodString>;
|
|
35
35
|
idempotency_key: z.ZodOptional<z.ZodString>;
|
|
36
36
|
}, "strip", z.ZodTypeAny, {
|
|
37
|
-
role: "
|
|
37
|
+
role: "agent" | "peer";
|
|
38
38
|
text: string;
|
|
39
39
|
conversation_id: string;
|
|
40
40
|
modality: "text" | "voice";
|
|
@@ -42,7 +42,7 @@ export declare const memoryRecordSchema: z.ZodEffects<z.ZodObject<{
|
|
|
42
42
|
idempotency_key?: string | undefined;
|
|
43
43
|
peer_id?: string | undefined;
|
|
44
44
|
}, {
|
|
45
|
-
role: "
|
|
45
|
+
role: "agent" | "peer";
|
|
46
46
|
text: string;
|
|
47
47
|
conversation_id: string;
|
|
48
48
|
modality: "text" | "voice";
|
|
@@ -50,7 +50,7 @@ export declare const memoryRecordSchema: z.ZodEffects<z.ZodObject<{
|
|
|
50
50
|
idempotency_key?: string | undefined;
|
|
51
51
|
peer_id?: string | undefined;
|
|
52
52
|
}>, {
|
|
53
|
-
role: "
|
|
53
|
+
role: "agent" | "peer";
|
|
54
54
|
text: string;
|
|
55
55
|
conversation_id: string;
|
|
56
56
|
modality: "text" | "voice";
|
|
@@ -58,7 +58,7 @@ export declare const memoryRecordSchema: z.ZodEffects<z.ZodObject<{
|
|
|
58
58
|
idempotency_key?: string | undefined;
|
|
59
59
|
peer_id?: string | undefined;
|
|
60
60
|
}, {
|
|
61
|
-
role: "
|
|
61
|
+
role: "agent" | "peer";
|
|
62
62
|
text: string;
|
|
63
63
|
conversation_id: string;
|
|
64
64
|
modality: "text" | "voice";
|
package/dist/index.js
CHANGED
|
@@ -3811,7 +3811,7 @@ function loadPackageMeta(importMetaUrl) {
|
|
|
3811
3811
|
|
|
3812
3812
|
// src/bridge-agent.ts
|
|
3813
3813
|
import { promises as fsp } from "node:fs";
|
|
3814
|
-
import { join as
|
|
3814
|
+
import { join as join6, dirname } from "node:path";
|
|
3815
3815
|
|
|
3816
3816
|
// src/latency.ts
|
|
3817
3817
|
var DEFAULT_CAPACITY = 1024;
|
|
@@ -4324,29 +4324,153 @@ function readRecentTurns(baseDir, agent, sessionId, k) {
|
|
|
4324
4324
|
return k > 0 ? turns.slice(-k) : [];
|
|
4325
4325
|
}
|
|
4326
4326
|
|
|
4327
|
+
// src/memory/openclaw-workspace-facts.ts
|
|
4328
|
+
import { readdirSync, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
4329
|
+
import { join as join5 } from "node:path";
|
|
4330
|
+
var DEFAULT_FACTS_BUDGET = 8e3;
|
|
4331
|
+
var MEMORY_MD_CAP = 6e3;
|
|
4332
|
+
var MIN_FRAGMENT = 200;
|
|
4333
|
+
var MAX_SOURCE_BYTES = 1024 * 1024;
|
|
4334
|
+
var DAILY_RE = /^\d{4}-\d{2}-\d{2}\.md$/;
|
|
4335
|
+
function resolveWorkspaceDir(baseDir, agent, override) {
|
|
4336
|
+
if (override) return expandHome(override);
|
|
4337
|
+
const base = expandHome(baseDir);
|
|
4338
|
+
const isMain = agent === "" || agent === "main";
|
|
4339
|
+
try {
|
|
4340
|
+
const cfg = JSON.parse(readFileSync4(join5(base, "openclaw.json"), "utf8"));
|
|
4341
|
+
const agents = cfg?.agents;
|
|
4342
|
+
if (agents && typeof agents === "object") {
|
|
4343
|
+
const list = Array.isArray(agents.list) ? agents.list : [];
|
|
4344
|
+
const wanted = isMain ? "main" : agent;
|
|
4345
|
+
const entry = list.find((e) => e && typeof e === "object" && e.id === wanted);
|
|
4346
|
+
const ws = entry?.workspace;
|
|
4347
|
+
if (typeof ws === "string" && ws) return expandHome(ws);
|
|
4348
|
+
const def = agents.defaults?.workspace;
|
|
4349
|
+
if (typeof def === "string" && def) return expandHome(def);
|
|
4350
|
+
}
|
|
4351
|
+
} catch {
|
|
4352
|
+
}
|
|
4353
|
+
return isMain ? join5(base, "workspace") : join5(base, `workspace-${agent}`);
|
|
4354
|
+
}
|
|
4355
|
+
var TRUNC = "\u2026[truncated]";
|
|
4356
|
+
function headSlice(s, n) {
|
|
4357
|
+
if (s.length <= n) return { text: s, clipped: false };
|
|
4358
|
+
let out = s.slice(0, n);
|
|
4359
|
+
const last = out.charCodeAt(out.length - 1);
|
|
4360
|
+
if (last >= 55296 && last <= 56319) out = out.slice(0, -1);
|
|
4361
|
+
return { text: out, clipped: true };
|
|
4362
|
+
}
|
|
4363
|
+
function tailSlice(s, n) {
|
|
4364
|
+
if (s.length <= n) return { text: s, clipped: false };
|
|
4365
|
+
let out = s.slice(s.length - n);
|
|
4366
|
+
const first = out.charCodeAt(0);
|
|
4367
|
+
if (first >= 56320 && first <= 57343) out = out.slice(1);
|
|
4368
|
+
return { text: out, clipped: true };
|
|
4369
|
+
}
|
|
4370
|
+
function readBounded(path) {
|
|
4371
|
+
if (statSync(path).size > MAX_SOURCE_BYTES) return { text: null, oversize: true };
|
|
4372
|
+
return { text: readFileSync4(path, "utf8"), oversize: false };
|
|
4373
|
+
}
|
|
4374
|
+
function readWorkspaceFacts(baseDir, agent, opts) {
|
|
4375
|
+
const raw = opts?.charBudget;
|
|
4376
|
+
const budget = typeof raw === "number" && Number.isInteger(raw) && raw >= 0 ? raw : DEFAULT_FACTS_BUDGET;
|
|
4377
|
+
if (budget === 0) return { facts: [], truncated: false, missing: false };
|
|
4378
|
+
const dir = resolveWorkspaceDir(baseDir, agent, opts?.workspaceDir);
|
|
4379
|
+
const facts = [];
|
|
4380
|
+
let truncated = false;
|
|
4381
|
+
let remaining = budget;
|
|
4382
|
+
try {
|
|
4383
|
+
const r = readBounded(join5(dir, "MEMORY.md"));
|
|
4384
|
+
if (r.oversize) truncated = true;
|
|
4385
|
+
else if (r.text && r.text.trim()) {
|
|
4386
|
+
const { text, clipped } = headSlice(r.text, Math.min(MEMORY_MD_CAP, budget));
|
|
4387
|
+
truncated ||= clipped;
|
|
4388
|
+
facts.push(`workspace MEMORY.md:
|
|
4389
|
+
${text}${clipped ? `
|
|
4390
|
+
${TRUNC}` : ""}`);
|
|
4391
|
+
remaining -= text.length;
|
|
4392
|
+
}
|
|
4393
|
+
} catch {
|
|
4394
|
+
}
|
|
4395
|
+
try {
|
|
4396
|
+
const dailies = readdirSync(join5(dir, "memory")).filter((f) => DAILY_RE.test(f)).sort().reverse().slice(0, 2);
|
|
4397
|
+
for (const f of dailies) {
|
|
4398
|
+
if (remaining < MIN_FRAGMENT) {
|
|
4399
|
+
truncated = true;
|
|
4400
|
+
break;
|
|
4401
|
+
}
|
|
4402
|
+
let r;
|
|
4403
|
+
try {
|
|
4404
|
+
r = readBounded(join5(dir, "memory", f));
|
|
4405
|
+
} catch {
|
|
4406
|
+
continue;
|
|
4407
|
+
}
|
|
4408
|
+
if (r.oversize) {
|
|
4409
|
+
truncated = true;
|
|
4410
|
+
continue;
|
|
4411
|
+
}
|
|
4412
|
+
if (!r.text || !r.text.trim()) continue;
|
|
4413
|
+
const { text, clipped } = tailSlice(r.text, remaining);
|
|
4414
|
+
truncated ||= clipped;
|
|
4415
|
+
facts.push(`workspace memory/${f} (daily):
|
|
4416
|
+
${clipped ? `${TRUNC}
|
|
4417
|
+
` : ""}${text}`);
|
|
4418
|
+
remaining -= text.length;
|
|
4419
|
+
}
|
|
4420
|
+
} catch {
|
|
4421
|
+
}
|
|
4422
|
+
return { facts, truncated, missing: facts.length === 0 };
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4327
4425
|
// src/memory/openclaw-provider.ts
|
|
4328
4426
|
var DEFAULT_K2 = 200;
|
|
4329
4427
|
var OpenClawMemoryProvider = class {
|
|
4330
|
-
|
|
4428
|
+
// gauge: last successful returned total, including prefixes/markers
|
|
4429
|
+
constructor(store, baseDir, agent, factsOpts) {
|
|
4331
4430
|
this.store = store;
|
|
4332
4431
|
this.baseDir = baseDir;
|
|
4333
4432
|
this.agent = agent;
|
|
4433
|
+
this.factsOpts = factsOpts;
|
|
4334
4434
|
}
|
|
4335
4435
|
noSession = 0;
|
|
4336
4436
|
readFailed = 0;
|
|
4437
|
+
factsMissing = 0;
|
|
4438
|
+
// absence-shaped: resolved path missing or zero sources (per-call)
|
|
4439
|
+
factsReadFailed = 0;
|
|
4440
|
+
// anything THROWN out of the facts computation
|
|
4441
|
+
factsTruncated = 0;
|
|
4442
|
+
// budget clipped/dropped a source (incl. the 1MB skip)
|
|
4443
|
+
factsChars = 0;
|
|
4337
4444
|
async record(input) {
|
|
4338
4445
|
if (input.modality !== "voice") return { seq: 0, deduped: true };
|
|
4339
4446
|
return this.store.recordMemory({ ...input, text: `[voice] ${input.text}` });
|
|
4340
4447
|
}
|
|
4341
4448
|
async context(conversationId, limit) {
|
|
4342
4449
|
const k = limit ?? DEFAULT_K2;
|
|
4450
|
+
let wsFacts = [];
|
|
4451
|
+
try {
|
|
4452
|
+
const r = readWorkspaceFacts(this.baseDir, this.agent, {
|
|
4453
|
+
workspaceDir: this.factsOpts?.workspaceDir,
|
|
4454
|
+
charBudget: this.factsOpts?.factsCharBudget
|
|
4455
|
+
});
|
|
4456
|
+
wsFacts = r.facts;
|
|
4457
|
+
if (r.missing) this.factsMissing++;
|
|
4458
|
+
if (r.truncated) this.factsTruncated++;
|
|
4459
|
+
this.factsChars = r.facts.reduce((n, f) => n + f.length, 0);
|
|
4460
|
+
} catch {
|
|
4461
|
+
this.factsReadFailed++;
|
|
4462
|
+
}
|
|
4463
|
+
const withFacts = (res) => ({
|
|
4464
|
+
recent: res.recent,
|
|
4465
|
+
facts: [...wsFacts, ...res.facts]
|
|
4466
|
+
});
|
|
4343
4467
|
const voice = await this.store.memoryContext(conversationId, k);
|
|
4344
4468
|
let textTurns = [];
|
|
4345
4469
|
try {
|
|
4346
4470
|
const sid = resolveOwnerDmSession(this.baseDir, this.agent);
|
|
4347
4471
|
if (!sid) {
|
|
4348
4472
|
this.noSession++;
|
|
4349
|
-
return voice;
|
|
4473
|
+
return withFacts(voice);
|
|
4350
4474
|
}
|
|
4351
4475
|
textTurns = readRecentTurns(this.baseDir, this.agent, sid, k).map((t) => ({
|
|
4352
4476
|
seq: 0,
|
|
@@ -4356,7 +4480,7 @@ var OpenClawMemoryProvider = class {
|
|
|
4356
4480
|
}));
|
|
4357
4481
|
} catch {
|
|
4358
4482
|
this.readFailed++;
|
|
4359
|
-
return voice;
|
|
4483
|
+
return withFacts(voice);
|
|
4360
4484
|
}
|
|
4361
4485
|
const tagged = [
|
|
4362
4486
|
...textTurns.map((t, i) => ({ ...t, _src: 0, _i: i })),
|
|
@@ -4375,10 +4499,17 @@ var OpenClawMemoryProvider = class {
|
|
|
4375
4499
|
text: t.text,
|
|
4376
4500
|
ts: t.ts
|
|
4377
4501
|
}));
|
|
4378
|
-
return { recent, facts: voice.facts };
|
|
4502
|
+
return withFacts({ recent, facts: voice.facts });
|
|
4379
4503
|
}
|
|
4380
4504
|
stats() {
|
|
4381
|
-
return {
|
|
4505
|
+
return {
|
|
4506
|
+
openclaw_no_session: this.noSession,
|
|
4507
|
+
openclaw_read_failed: this.readFailed,
|
|
4508
|
+
openclaw_facts_missing: this.factsMissing,
|
|
4509
|
+
openclaw_facts_read_failed: this.factsReadFailed,
|
|
4510
|
+
openclaw_facts_truncated: this.factsTruncated,
|
|
4511
|
+
openclaw_facts_chars: this.factsChars
|
|
4512
|
+
};
|
|
4382
4513
|
}
|
|
4383
4514
|
};
|
|
4384
4515
|
|
|
@@ -4402,10 +4533,10 @@ var NullMemory = class {
|
|
|
4402
4533
|
return { recent: [], facts: [] };
|
|
4403
4534
|
}
|
|
4404
4535
|
};
|
|
4405
|
-
function createMemoryProvider(provider, store, baseDir, agent) {
|
|
4536
|
+
function createMemoryProvider(provider, store, baseDir, agent, openclawFacts) {
|
|
4406
4537
|
if (provider === "none") return new NullMemory();
|
|
4407
4538
|
if (provider === "hermes") return new HermesMemoryProvider(baseDir ?? "~/.hermes");
|
|
4408
|
-
if (provider === "openclaw") return new OpenClawMemoryProvider(store, baseDir ?? "~/.openclaw", agent ?? "");
|
|
4539
|
+
if (provider === "openclaw") return new OpenClawMemoryProvider(store, baseDir ?? "~/.openclaw", agent ?? "", openclawFacts);
|
|
4409
4540
|
return new SecureComsLocalMemory(store);
|
|
4410
4541
|
}
|
|
4411
4542
|
|
|
@@ -4513,7 +4644,10 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
4513
4644
|
identityId: cfg.identityId,
|
|
4514
4645
|
cipherFor: () => new KeyedInboxCipher(deriveMlsStoreKey(seed))
|
|
4515
4646
|
});
|
|
4516
|
-
this.memory = createMemoryProvider(this.cfg.memoryProvider, this.store, this.cfg.memoryBaseDir, this.cfg.memoryAgent
|
|
4647
|
+
this.memory = createMemoryProvider(this.cfg.memoryProvider, this.store, this.cfg.memoryBaseDir, this.cfg.memoryAgent, {
|
|
4648
|
+
workspaceDir: this.cfg.memoryWorkspaceDir,
|
|
4649
|
+
factsCharBudget: this.cfg.memoryFactsCharBudget
|
|
4650
|
+
});
|
|
4517
4651
|
this.provider = new MlsGroupKeyProvider({
|
|
4518
4652
|
store: this.store,
|
|
4519
4653
|
backend: this.backend,
|
|
@@ -4593,7 +4727,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
4593
4727
|
const j = await res.json();
|
|
4594
4728
|
return String(j.attachment_id);
|
|
4595
4729
|
};
|
|
4596
|
-
this.attachmentsDirImpl = deps.attachmentsDir ??
|
|
4730
|
+
this.attachmentsDirImpl = deps.attachmentsDir ?? join6(dirname(cfg.storePath), "attachments");
|
|
4597
4731
|
this.action = {
|
|
4598
4732
|
begin: (channelId) => this.actionBegin(channelId),
|
|
4599
4733
|
start: (opts) => this.actionStart(opts),
|
|
@@ -6120,13 +6254,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
6120
6254
|
};
|
|
6121
6255
|
|
|
6122
6256
|
// src/config.ts
|
|
6123
|
-
import { readFileSync as
|
|
6124
|
-
import { dirname as dirname2, join as
|
|
6257
|
+
import { readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
|
|
6258
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
6125
6259
|
var REQUIRED = ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
|
|
6126
6260
|
function loadConfig(opts) {
|
|
6127
|
-
const raw =
|
|
6261
|
+
const raw = readFileSync5(opts.credentialPath, "utf8");
|
|
6128
6262
|
try {
|
|
6129
|
-
const mode =
|
|
6263
|
+
const mode = statSync2(opts.credentialPath).mode & 511;
|
|
6130
6264
|
if (mode & 63) process.stderr.write(`[bridge] WARNING: credential file ${opts.credentialPath} permission ${mode.toString(8)} is not 0600 \u2014 tighten it (chmod 600).
|
|
6131
6265
|
`);
|
|
6132
6266
|
} catch {
|
|
@@ -6138,7 +6272,7 @@ function loadConfig(opts) {
|
|
|
6138
6272
|
throw new Error("credential file is not valid JSON");
|
|
6139
6273
|
}
|
|
6140
6274
|
for (const k of REQUIRED) if (typeof json2[k] !== "string" || !json2[k]) throw new Error(`credential file missing required field: ${k}`);
|
|
6141
|
-
const storePath = opts.storePath ??
|
|
6275
|
+
const storePath = opts.storePath ?? join7(dirname2(opts.credentialPath), "bridge-state.db");
|
|
6142
6276
|
const agentName = typeof json2.agent_name === "string" && json2.agent_name ? json2.agent_name : typeof json2.display_name === "string" && json2.display_name ? json2.display_name : void 0;
|
|
6143
6277
|
const rateLimitCapacity = typeof json2.rate_limit_capacity === "number" ? json2.rate_limit_capacity : 20;
|
|
6144
6278
|
const rateLimitRefillPerMinute = typeof json2.rate_limit_refill_per_minute === "number" ? json2.rate_limit_refill_per_minute : 20;
|
|
@@ -6146,6 +6280,8 @@ function loadConfig(opts) {
|
|
|
6146
6280
|
let memoryProvider;
|
|
6147
6281
|
let memoryBaseDir;
|
|
6148
6282
|
let memoryAgent;
|
|
6283
|
+
let memoryWorkspaceDir;
|
|
6284
|
+
let memoryFactsCharBudget;
|
|
6149
6285
|
if (memProvRaw === "none") {
|
|
6150
6286
|
memoryProvider = "none";
|
|
6151
6287
|
} else if (memProvRaw === "hermes") {
|
|
@@ -6159,6 +6295,10 @@ function loadConfig(opts) {
|
|
|
6159
6295
|
memoryBaseDir = typeof bd === "string" && bd ? bd : "~/.openclaw";
|
|
6160
6296
|
const ag = oc?.agent;
|
|
6161
6297
|
memoryAgent = typeof ag === "string" && ag ? ag : void 0;
|
|
6298
|
+
const wd = oc?.workspace_dir;
|
|
6299
|
+
memoryWorkspaceDir = typeof wd === "string" && wd ? wd : void 0;
|
|
6300
|
+
const fb = oc?.facts_char_budget;
|
|
6301
|
+
memoryFactsCharBudget = typeof fb === "number" && Number.isInteger(fb) && fb >= 0 ? fb : void 0;
|
|
6162
6302
|
} else {
|
|
6163
6303
|
memoryProvider = "securecoms";
|
|
6164
6304
|
}
|
|
@@ -6175,7 +6315,9 @@ function loadConfig(opts) {
|
|
|
6175
6315
|
rateLimitRefillPerMinute,
|
|
6176
6316
|
memoryProvider,
|
|
6177
6317
|
memoryBaseDir,
|
|
6178
|
-
memoryAgent
|
|
6318
|
+
memoryAgent,
|
|
6319
|
+
memoryWorkspaceDir,
|
|
6320
|
+
memoryFactsCharBudget
|
|
6179
6321
|
};
|
|
6180
6322
|
}
|
|
6181
6323
|
function arg(argv, flag) {
|
|
@@ -6219,11 +6361,11 @@ import { z } from "zod";
|
|
|
6219
6361
|
|
|
6220
6362
|
// src/auth.ts
|
|
6221
6363
|
import { randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
|
|
6222
|
-
import { existsSync, readFileSync as
|
|
6364
|
+
import { existsSync, readFileSync as readFileSync6, writeFileSync } from "node:fs";
|
|
6223
6365
|
function resolveToken(opts) {
|
|
6224
6366
|
if (opts.token) return opts.token;
|
|
6225
6367
|
const f = opts.tokenFile;
|
|
6226
|
-
if (f && existsSync(f)) return
|
|
6368
|
+
if (f && existsSync(f)) return readFileSync6(f, "utf8").trim();
|
|
6227
6369
|
const tok = randomBytes3(32).toString("base64url");
|
|
6228
6370
|
if (f) writeFileSync(f, tok, { mode: 384 });
|
|
6229
6371
|
return tok;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { MemoryProvider } from "./provider.js";
|
|
2
2
|
import type { SqliteMlsStateStore, MemoryRecordInput, MemoryContextResult } from "@agentvault/connect-mls-store";
|
|
3
|
+
export interface OpenClawFactsOptions {
|
|
4
|
+
workspaceDir?: string;
|
|
5
|
+
factsCharBudget?: number;
|
|
6
|
+
}
|
|
3
7
|
/**
|
|
4
8
|
* MemoryProvider for an OpenClaw-hosted agent (Cortina). context() merges
|
|
5
9
|
* OpenClaw's own text history (read-only, plain session .jsonl) with the
|
|
@@ -12,9 +16,14 @@ export declare class OpenClawMemoryProvider implements MemoryProvider {
|
|
|
12
16
|
private readonly store;
|
|
13
17
|
private readonly baseDir;
|
|
14
18
|
private readonly agent;
|
|
19
|
+
private readonly factsOpts?;
|
|
15
20
|
private noSession;
|
|
16
21
|
private readFailed;
|
|
17
|
-
|
|
22
|
+
private factsMissing;
|
|
23
|
+
private factsReadFailed;
|
|
24
|
+
private factsTruncated;
|
|
25
|
+
private factsChars;
|
|
26
|
+
constructor(store: SqliteMlsStateStore, baseDir: string, agent: string, factsOpts?: OpenClawFactsOptions | undefined);
|
|
18
27
|
record(input: MemoryRecordInput): Promise<{
|
|
19
28
|
seq: number;
|
|
20
29
|
deduped: boolean;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Spec D2 constants — pinned so tests are deterministic. "Chars" = UTF-16 code units. */
|
|
2
|
+
export declare const DEFAULT_FACTS_BUDGET = 8000;
|
|
3
|
+
export declare const MEMORY_MD_CAP = 6000;
|
|
4
|
+
export declare const MIN_FRAGMENT = 200;
|
|
5
|
+
export declare const MAX_SOURCE_BYTES: number;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the agent's OpenClaw workspace dir (spec D4). Ladder, first hit wins:
|
|
8
|
+
* 1. explicit override (credential workspace_dir) — operator beats everything;
|
|
9
|
+
* 2. openclaw.json, resolved the way OpenClaw itself would:
|
|
10
|
+
* 2a. agents.list entry with id===agent and a NON-EMPTY string workspace (verbatim,
|
|
11
|
+
* never case-transformed — the live config declares "workspace-cortina" for an
|
|
12
|
+
* on-disk "workspace-Cortina" and works only because APFS is case-insensitive);
|
|
13
|
+
* 2b. else agents.defaults.workspace when it is a non-empty string (covers main,
|
|
14
|
+
* whose live entry has the workspace key ABSENT, and named agents w/o one);
|
|
15
|
+
* 3. derivation, LAST resort (openclaw.json missing/malformed/silent):
|
|
16
|
+
* main/"" → <base>/workspace, else <base>/workspace-<agent> verbatim.
|
|
17
|
+
* Empty agent is treated as "main" throughout (the factory passes agent ?? "").
|
|
18
|
+
* Total: always returns a path; existence is checked at read time, fail-safe.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveWorkspaceDir(baseDir: string, agent: string, override?: string): string;
|
|
21
|
+
export interface WorkspaceFactsResult {
|
|
22
|
+
/** 0..3 provenance-prefixed facts: MEMORY.md first, then dailies newest-first. */
|
|
23
|
+
facts: string[];
|
|
24
|
+
/** True when the budget clipped or dropped any source (includes the >1MB skip). */
|
|
25
|
+
truncated: boolean;
|
|
26
|
+
/** True when the resolved workspace yielded zero usable sources. False on budget-0 kill switch. */
|
|
27
|
+
missing: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Read the workspace facts (spec D1-D3). READ-ONLY, total: never throws for any
|
|
31
|
+
* filesystem state — absence degrades to fewer/zero facts. Only MEMORY.md and
|
|
32
|
+
* memory/\d{4}-\d{2}-\d{2}\.md under the resolved dir are ever opened.
|
|
33
|
+
* Budget accounting is content-only; prefixes and markers are exempt (audit M2).
|
|
34
|
+
*/
|
|
35
|
+
export declare function readWorkspaceFacts(baseDir: string, agent: string, opts?: {
|
|
36
|
+
workspaceDir?: string;
|
|
37
|
+
charBudget?: number;
|
|
38
|
+
}): WorkspaceFactsResult;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SqliteMlsStateStore, MemoryRecordInput, MemoryContextResult } from "@agentvault/connect-mls-store";
|
|
2
|
+
import type { OpenClawFactsOptions } from "./openclaw-provider.js";
|
|
2
3
|
export interface MemoryProvider {
|
|
3
4
|
record(input: MemoryRecordInput): Promise<{
|
|
4
5
|
seq: number;
|
|
@@ -26,4 +27,4 @@ export declare class NullMemory implements MemoryProvider {
|
|
|
26
27
|
}>;
|
|
27
28
|
context(): Promise<MemoryContextResult>;
|
|
28
29
|
}
|
|
29
|
-
export declare function createMemoryProvider(provider: "securecoms" | "none" | "hermes" | "openclaw", store: SqliteMlsStateStore, baseDir?: string, agent?: string): MemoryProvider;
|
|
30
|
+
export declare function createMemoryProvider(provider: "securecoms" | "none" | "hermes" | "openclaw", store: SqliteMlsStateStore, baseDir?: string, agent?: string, openclawFacts?: OpenClawFactsOptions): MemoryProvider;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type HermesConfigIoDeps, type Harness } from "./wire.js";
|
|
1
2
|
export interface AutoWireInput {
|
|
2
3
|
harness: string | undefined;
|
|
3
4
|
identityId: string;
|
|
@@ -7,10 +8,10 @@ export interface AutoWireInput {
|
|
|
7
8
|
tokenFilePath: string;
|
|
8
9
|
connectorsSrcDir: string;
|
|
9
10
|
}
|
|
10
|
-
export interface AutoWireDeps {
|
|
11
|
+
export interface AutoWireDeps extends HermesConfigIoDeps {
|
|
11
12
|
statDir: (p: string) => boolean;
|
|
12
13
|
readBinding: (dstDir: string) => string | null;
|
|
13
|
-
copyConnector: (srcDir: string, dstDir: string) => void;
|
|
14
|
+
copyConnector: (srcDir: string, dstDir: string, harness: Harness) => void;
|
|
14
15
|
writeBinding: (dstDir: string, identityId: string) => void;
|
|
15
16
|
log: (s: string) => void;
|
|
16
17
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict, line-based editor for `~/.hermes/config.yaml`'s `plugins.enabled`
|
|
3
|
+
* block sequence. Hermes only loads user plugins listed there (plugins.py's
|
|
4
|
+
* loader — see spec §4), so `wire --harness hermes` must add our plugin's
|
|
5
|
+
* entry or the copied connector never runs.
|
|
6
|
+
*
|
|
7
|
+
* This module is PURE: it takes the file's text and returns either a noop
|
|
8
|
+
* (already correct / refused) or the edited text. File I/O and the
|
|
9
|
+
* `config.yaml.bak-wire-<ts>-<pid>` backup live with the caller (wire.ts /
|
|
10
|
+
* harness-wire.ts, Task 6).
|
|
11
|
+
*
|
|
12
|
+
* Hardened rules (spec §4, audited against the real production file at
|
|
13
|
+
* `config.yaml:569-573`):
|
|
14
|
+
* 1. The inserted item copies the EXISTING first item's exact column —
|
|
15
|
+
* never `key-indent + 2`. Production shape has items at the SAME
|
|
16
|
+
* indent as the `enabled:` key itself (a legal, if unusual, YAML block
|
|
17
|
+
* sequence style); some configs indent items by +2. Both must work,
|
|
18
|
+
* which is why the column is READ from the first item, never computed.
|
|
19
|
+
* 2. Insertion lands after the LAST consecutive same-column `- ` item —
|
|
20
|
+
* stop scanning at the first line that isn't one (the boundary before
|
|
21
|
+
* a sibling key such as `disabled: []`).
|
|
22
|
+
* 2b. Stopping the scan is not enough on its own: the line the scan
|
|
23
|
+
* stopped at must be a LEGITIMATE list terminator before the insertion
|
|
24
|
+
* is allowed to happen there. Legitimate means one of: (a) a sibling
|
|
25
|
+
* key at the same column as `enabled:` itself (e.g. `disabled:`), (b)
|
|
26
|
+
* end-of-block via blank-line(s) then a genuine dedent below the
|
|
27
|
+
* items' column, or (c) end of file. Anything else that stops the
|
|
28
|
+
* scan — a bare `-`, a mapping-style item (`- ` followed by an
|
|
29
|
+
* indented `key: value` on the next line), a blank line followed by
|
|
30
|
+
* MORE same-column items (ambiguous: did the writer mean to end the
|
|
31
|
+
* list or just add a spacer?), or any other unrecognized content —
|
|
32
|
+
* refuses rather than insert at a spot that was never verified to be
|
|
33
|
+
* the list's actual end.
|
|
34
|
+
* 3. `plugins.disabled` is checked too: an entry listed there wins over
|
|
35
|
+
* `plugins.enabled` in the loader, so refuse rather than add a second,
|
|
36
|
+
* shadowed entry.
|
|
37
|
+
* 4. Any structural surprise — no `plugins:` block, an inline
|
|
38
|
+
* `enabled: [...]` form, an empty enabled list, mixed-indent items, a
|
|
39
|
+
* comment line interleaved with items (ambiguous: it could be a
|
|
40
|
+
* trailing comment on the item above or a leading comment on the item
|
|
41
|
+
* below), or a non-boundary list terminator (see 2b) — refuses with no
|
|
42
|
+
* write and an actionable message. Never reformats, never touches any
|
|
43
|
+
* other key.
|
|
44
|
+
*/
|
|
45
|
+
export declare const HERMES_PLUGIN_NAME = "securecoms-platform";
|
|
46
|
+
export interface EnsureHermesPluginEnabledResult {
|
|
47
|
+
action: "noop" | "edited";
|
|
48
|
+
text?: string;
|
|
49
|
+
reason?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function ensureHermesPluginEnabled(configText: string): EnsureHermesPluginEnabledResult;
|