@prismer/runtime 1.9.9 → 1.9.22
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/dist/cli.cjs +800 -2517
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +761 -2472
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +555 -2155
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -112
- package/dist/index.d.ts +6 -112
- package/dist/index.js +504 -2098
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
+
}) : x)(function(x) {
|
|
6
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
+
});
|
|
3
9
|
var __esm = (fn, res) => function __init() {
|
|
4
10
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
11
|
};
|
|
@@ -9,12 +15,12 @@ var __export = (target, all) => {
|
|
|
9
15
|
};
|
|
10
16
|
|
|
11
17
|
// src/cli/ui.ts
|
|
12
|
-
import * as
|
|
13
|
-
import * as
|
|
14
|
-
import { fileURLToPath
|
|
18
|
+
import * as fs4 from "fs";
|
|
19
|
+
import * as path6 from "path";
|
|
20
|
+
import { fileURLToPath } from "url";
|
|
15
21
|
function thisDirname() {
|
|
16
22
|
try {
|
|
17
|
-
return
|
|
23
|
+
return path6.dirname(fileURLToPath(import.meta.url));
|
|
18
24
|
} catch {
|
|
19
25
|
return process.cwd();
|
|
20
26
|
}
|
|
@@ -24,18 +30,18 @@ function findIconPath(size = "big") {
|
|
|
24
30
|
const here = thisDirname();
|
|
25
31
|
const candidates = [
|
|
26
32
|
// npm-installed: node_modules/@prismer/runtime/dist/cli.js → ../assets
|
|
27
|
-
|
|
33
|
+
path6.resolve(here, "../assets", name),
|
|
28
34
|
// alternate dist layout (sub-bundle): dist/bin/cli.js → ../../assets
|
|
29
|
-
|
|
35
|
+
path6.resolve(here, "../../assets", name),
|
|
30
36
|
// source/typecheck: src/cli/ui.ts → ../../assets
|
|
31
|
-
|
|
37
|
+
path6.resolve(here, "../../assets", name),
|
|
32
38
|
// dev mode: cwd happens to be runtime root
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
path6.resolve(process.cwd(), "assets", name),
|
|
40
|
+
path6.resolve(process.cwd(), "sdk/prismer-cloud/runtime/assets", name)
|
|
35
41
|
];
|
|
36
42
|
for (const candidate of candidates) {
|
|
37
43
|
try {
|
|
38
|
-
if (
|
|
44
|
+
if (fs4.existsSync(candidate)) return candidate;
|
|
39
45
|
} catch {
|
|
40
46
|
}
|
|
41
47
|
}
|
|
@@ -159,7 +165,7 @@ var init_ui = __esm({
|
|
|
159
165
|
const iconPath = findIconPath("small");
|
|
160
166
|
if (iconPath !== null) {
|
|
161
167
|
try {
|
|
162
|
-
const raw =
|
|
168
|
+
const raw = fs4.readFileSync(iconPath, "utf-8").replace(/\n+$/, "");
|
|
163
169
|
for (const line of raw.split("\n")) {
|
|
164
170
|
this.write(this.cyan(line) + "\n");
|
|
165
171
|
}
|
|
@@ -182,7 +188,7 @@ var init_ui = __esm({
|
|
|
182
188
|
const shouldUseFull = opts?.full === true || width >= 120;
|
|
183
189
|
if (shouldUseFull && iconPath !== null) {
|
|
184
190
|
try {
|
|
185
|
-
const raw =
|
|
191
|
+
const raw = fs4.readFileSync(iconPath, "utf-8");
|
|
186
192
|
const lines = raw.split("\n");
|
|
187
193
|
for (const line of lines) {
|
|
188
194
|
const brandedLine = line.replace("Prismer Cloud SDK", "Prismer Runtime CLI");
|
|
@@ -446,7 +452,7 @@ __export(util_exports, {
|
|
|
446
452
|
info: () => info,
|
|
447
453
|
normalizeCloudUrl: () => normalizeCloudUrl,
|
|
448
454
|
ok: () => ok,
|
|
449
|
-
pidAlive: () =>
|
|
455
|
+
pidAlive: () => pidAlive,
|
|
450
456
|
pidFilePath: () => pidFilePath,
|
|
451
457
|
printBanner: () => printBanner,
|
|
452
458
|
printJson: () => printJson,
|
|
@@ -457,8 +463,8 @@ __export(util_exports, {
|
|
|
457
463
|
warn: () => warn,
|
|
458
464
|
writePidFile: () => writePidFile
|
|
459
465
|
});
|
|
460
|
-
import { existsSync as existsSync11, readFileSync as
|
|
461
|
-
import { join as
|
|
466
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
467
|
+
import { join as join10 } from "path";
|
|
462
468
|
function color(kind, text) {
|
|
463
469
|
if (process.env.NO_COLOR === "1" || process.env.NO_COLOR === "true") return text;
|
|
464
470
|
return `${ANSI[kind]}${text}${ANSI.reset}`;
|
|
@@ -523,10 +529,10 @@ function runAction(fn, opts = {}) {
|
|
|
523
529
|
function printBanner(opts = {}) {
|
|
524
530
|
const ui = getUI();
|
|
525
531
|
if (opts.compact) {
|
|
526
|
-
ui.smallHeader("Runtime CLI
|
|
532
|
+
ui.smallHeader("Runtime CLI v${VERSION}");
|
|
527
533
|
return;
|
|
528
534
|
}
|
|
529
|
-
ui.banner("Runtime CLI
|
|
535
|
+
ui.banner("Runtime CLI v${VERSION}", { full: true });
|
|
530
536
|
}
|
|
531
537
|
function ok(label, detail) {
|
|
532
538
|
getUI().ok(label, detail);
|
|
@@ -552,7 +558,7 @@ function table(rows, columns) {
|
|
|
552
558
|
getUI().table(rows, { columns });
|
|
553
559
|
}
|
|
554
560
|
function pidFilePath(paths) {
|
|
555
|
-
return
|
|
561
|
+
return join10(paths.root, "daemon.pid");
|
|
556
562
|
}
|
|
557
563
|
function writePidFile(paths, pid) {
|
|
558
564
|
writeFileSync6(pidFilePath(paths), `${pid}
|
|
@@ -561,7 +567,7 @@ function writePidFile(paths, pid) {
|
|
|
561
567
|
function readPidFile(paths) {
|
|
562
568
|
const p = pidFilePath(paths);
|
|
563
569
|
if (!existsSync11(p)) return void 0;
|
|
564
|
-
const raw =
|
|
570
|
+
const raw = readFileSync8(p, "utf8").trim();
|
|
565
571
|
const pid = Number.parseInt(raw, 10);
|
|
566
572
|
return Number.isFinite(pid) ? pid : void 0;
|
|
567
573
|
}
|
|
@@ -574,7 +580,7 @@ function clearPidFile(paths) {
|
|
|
574
580
|
}
|
|
575
581
|
}
|
|
576
582
|
}
|
|
577
|
-
function
|
|
583
|
+
function pidAlive(pid) {
|
|
578
584
|
try {
|
|
579
585
|
process.kill(pid, 0);
|
|
580
586
|
return true;
|
|
@@ -657,36 +663,15 @@ var AdapterRegistry = class {
|
|
|
657
663
|
};
|
|
658
664
|
|
|
659
665
|
// src/adapters/hermes/index.ts
|
|
660
|
-
import {
|
|
666
|
+
import { spawn } from "child_process";
|
|
661
667
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
662
|
-
import { createRequire } from "module";
|
|
663
668
|
import { homedir } from "os";
|
|
664
669
|
import { dirname, join } from "path";
|
|
665
|
-
import { fileURLToPath } from "url";
|
|
666
670
|
import Database from "better-sqlite3";
|
|
667
671
|
import * as YAML from "yaml";
|
|
668
672
|
import { z } from "zod";
|
|
669
673
|
var PRISMER_IM_SKILL_NAME = "prismer-im-collab";
|
|
670
|
-
var PRISMER_IM_SKILL_CONTENT =
|
|
671
|
-
name: prismer-im-collab
|
|
672
|
-
description: Coordinate reliably in Prismer conversations, use workspace assets through bounded MCP tools, and keep task work on the board.
|
|
673
|
-
allowed-tools: prismer.agent.send prismer.conversation.listAgents prismer.task.create prismer.task.update prismer.task.complete prismer.approval.request_human_approval prismer.asset.search prismer.asset.describe prismer.asset.read
|
|
674
|
-
metadata:
|
|
675
|
-
prismer:
|
|
676
|
-
version: "1.0.0"
|
|
677
|
-
mcp_tools:
|
|
678
|
-
- prismer.agent.send
|
|
679
|
-
- prismer.conversation.listAgents
|
|
680
|
-
- prismer.task.create
|
|
681
|
-
- prismer.task.update
|
|
682
|
-
- prismer.task.complete
|
|
683
|
-
- prismer.approval.request_human_approval
|
|
684
|
-
- prismer.asset.search
|
|
685
|
-
- prismer.asset.describe
|
|
686
|
-
- prismer.asset.read
|
|
687
|
-
---
|
|
688
|
-
|
|
689
|
-
# Prismer IM Collaboration
|
|
674
|
+
var PRISMER_IM_SKILL_CONTENT = `# Prismer IM Collaboration
|
|
690
675
|
|
|
691
676
|
You are an agent in a Prismer multi-agent workspace. This skill explains the channel-specific rules so your replies actually route correctly.
|
|
692
677
|
|
|
@@ -716,19 +701,6 @@ You will receive a \`[Channel context]\` block at the top of each prompt indicat
|
|
|
716
701
|
| Address another agent so the platform dispatches your message to them | Call \`prismer.agent.send\` (from the \`prismer-tasks\` MCP server). |
|
|
717
702
|
| Look up which agents you can talk to | Call \`prismer.conversation.listAgents\` before guessing usernames. |
|
|
718
703
|
| Create / update tasks on the workspace board | \`prismer.task.create\` / \`prismer.task.update\` from \`prismer-tasks\` MCP. |
|
|
719
|
-
| Ask for structured human sign-off | \`prismer.approval.request_human_approval\`. |
|
|
720
|
-
| Find uploaded workspace files | \`prismer.asset.search\`. |
|
|
721
|
-
| Inspect file metadata before reading bytes | \`prismer.asset.describe\`. |
|
|
722
|
-
| Read a bounded byte range from a file | \`prismer.asset.read\`. |
|
|
723
|
-
|
|
724
|
-
## Workspace asset rules
|
|
725
|
-
|
|
726
|
-
- Web-uploaded workspace files are available as asset metadata first; bytes are fetched lazily by the local daemon.
|
|
727
|
-
- When a task needs file content, call \`prismer.asset.describe\` first, then read only the needed range with \`prismer.asset.read\`.
|
|
728
|
-
- For many files, call \`prismer.asset.search\` to find candidates. Do not ask the user to paste file contents already present in workspace assets.
|
|
729
|
-
- Do not claim you read a file unless an asset tool call succeeded.
|
|
730
|
-
- For large tables/documents, read headers, samples, or targeted byte ranges. Never try to load the whole file into a reply.
|
|
731
|
-
- Cite the asset id/URI and locator returned by the tool when using file evidence.
|
|
732
704
|
|
|
733
705
|
### When to use \`prismer.agent.send\` vs inline \`@\`
|
|
734
706
|
|
|
@@ -742,7 +714,6 @@ You will receive a \`[Channel context]\` block at the top of each prompt indicat
|
|
|
742
714
|
2. **Don't fabricate usernames.** If \`prismer.conversation.listAgents\` doesn't return a name, that agent doesn't exist; use plain text and address them by displayName (no @).
|
|
743
715
|
3. **Don't pretend to call a tool.** If you write "I sent a message via the tool" without actually invoking the tool, the message doesn't exist; the human will see the inconsistency.
|
|
744
716
|
4. **Check tool return values.** \`prismer.agent.send\` may return \`{ ok: false, error: 'agent_not_found' }\`; surface the error rather than pretending it worked.
|
|
745
|
-
5. **Use structured approvals.** For destructive, irreversible, external, spend, access, or publish operations, call \`prismer.approval.request_human_approval\`; do not inline-mention a human asking for approval.
|
|
746
717
|
|
|
747
718
|
## Example: group discussion handoff
|
|
748
719
|
|
|
@@ -830,23 +801,7 @@ var HermesProfileConfigSchema = z.object({
|
|
|
830
801
|
hermesSourceDir: z.string().optional(),
|
|
831
802
|
nativeMirrorTimeoutMs: z.number().int().positive().default(2e3),
|
|
832
803
|
/** Task authority level: executor (default) or orchestrator. */
|
|
833
|
-
taskAuthority: z.enum(["executor", "orchestrator"]).optional().default("executor")
|
|
834
|
-
/** Human approval escalation policy. Enforcement is currently prompt/cloud-side. */
|
|
835
|
-
approvalPolicy: z.enum(["strict", "auto-low-risk", "autonomous"]).optional().default("auto-low-risk"),
|
|
836
|
-
/** Agent-level MCP tool allowlist. Empty/null means all tools. Supports exact names and trailing * wildcards. */
|
|
837
|
-
mcpAllowlist: z.array(z.string()).nullable().optional(),
|
|
838
|
-
/** Agent-level operating principles injected through /v1/runs instructions. */
|
|
839
|
-
operatingPrinciples: z.union([z.string(), z.record(z.string())]).optional(),
|
|
840
|
-
/**
|
|
841
|
-
* Role-template snapshot carried in AgentProfile.config by cloud ACP.
|
|
842
|
-
* Expected fields used here:
|
|
843
|
-
* - roleTemplate.mcpServers[].toolsAllowlist
|
|
844
|
-
* - roleTemplate.operatingPrinciples
|
|
845
|
-
*/
|
|
846
|
-
roleTemplate: z.object({
|
|
847
|
-
mcpServers: z.array(z.object({ toolsAllowlist: z.array(z.string()).optional() }).passthrough()).optional(),
|
|
848
|
-
operatingPrinciples: z.union([z.string(), z.record(z.string())]).optional()
|
|
849
|
-
}).passthrough().optional()
|
|
804
|
+
taskAuthority: z.enum(["executor", "orchestrator"]).optional().default("executor")
|
|
850
805
|
});
|
|
851
806
|
var hermesAdapter = {
|
|
852
807
|
name: "hermes",
|
|
@@ -861,22 +816,14 @@ var hermesAdapter = {
|
|
|
861
816
|
errors: r.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
862
817
|
};
|
|
863
818
|
},
|
|
864
|
-
async prepareProfile(profile) {
|
|
865
|
-
const config = HermesProfileConfigSchema.parse(profile.config);
|
|
866
|
-
if (config.configurePrismerProvider) {
|
|
867
|
-
const profileName = getHermesProfileName(profile);
|
|
868
|
-
const agentUsername = profile.agentUsername || profileName;
|
|
869
|
-
configurePrismerProvider(profileName, config, agentUsername, profile.workspaceId);
|
|
870
|
-
}
|
|
871
|
-
},
|
|
872
819
|
async ensureService(profile) {
|
|
873
820
|
const config = HermesProfileConfigSchema.parse(profile.config);
|
|
874
|
-
const profileName =
|
|
875
|
-
const portOverride = config.port !== 8642 ? config.port : portForProfile(
|
|
821
|
+
const profileName = config.hermesProfileName && config.hermesProfileName !== "default" ? config.hermesProfileName : profile.agentUsername || profile.id.slice(0, 8);
|
|
822
|
+
const portOverride = config.port !== 8642 ? config.port : portForProfile(profileName, 8642);
|
|
876
823
|
const baseUrl = `http://127.0.0.1:${portOverride}`;
|
|
877
824
|
const agentUsername = profile.agentUsername || profileName;
|
|
878
825
|
if (config.configurePrismerProvider) {
|
|
879
|
-
configurePrismerProvider(profileName, config, agentUsername
|
|
826
|
+
configurePrismerProvider(profileName, config, agentUsername);
|
|
880
827
|
}
|
|
881
828
|
if (!await checkHealth(baseUrl, config.apiKey)) {
|
|
882
829
|
if (!config.autoStart) {
|
|
@@ -886,7 +833,6 @@ var hermesAdapter = {
|
|
|
886
833
|
(set autoStart: true on the AgentProfile to spawn it automatically)`
|
|
887
834
|
);
|
|
888
835
|
}
|
|
889
|
-
await stopStaleHermesGateways(profileName, portOverride, config.startupTimeoutMs);
|
|
890
836
|
spawn("hermes", ["-p", profileName, "gateway", "run"], {
|
|
891
837
|
detached: false,
|
|
892
838
|
stdio: "ignore",
|
|
@@ -946,8 +892,6 @@ var HermesService = class {
|
|
|
946
892
|
const startedAt = Date.now();
|
|
947
893
|
let runId;
|
|
948
894
|
const sysPrompt = typeof task.metadata?.systemPrompt === "string" ? task.metadata.systemPrompt : void 0;
|
|
949
|
-
const operatingPrinciples = typeof task.metadata?.operatingPrinciples === "string" ? task.metadata.operatingPrinciples : void 0;
|
|
950
|
-
const instructions = [sysPrompt, operatingPrinciples].filter(Boolean).join("\n\n");
|
|
951
895
|
if (sysPrompt) {
|
|
952
896
|
try {
|
|
953
897
|
const profileDir = getHermesProfileDir(this.profileName);
|
|
@@ -984,7 +928,7 @@ var HermesService = class {
|
|
|
984
928
|
// sent `messages: [...]` which Hermes 0.12 rejects with 400.
|
|
985
929
|
body: JSON.stringify({
|
|
986
930
|
input: task.prompt,
|
|
987
|
-
instructions:
|
|
931
|
+
instructions: sysPrompt,
|
|
988
932
|
session_id: typeof task.metadata?.conversationId === "string" ? task.metadata.conversationId : void 0,
|
|
989
933
|
stream: true
|
|
990
934
|
}),
|
|
@@ -1269,7 +1213,7 @@ function formatHermesGoalText(goals) {
|
|
|
1269
1213
|
return `${index + 1}. ${priority}${goal.title}${description}`;
|
|
1270
1214
|
}).join("\n");
|
|
1271
1215
|
}
|
|
1272
|
-
function configurePrismerProvider(profileName, config, agentUsername
|
|
1216
|
+
function configurePrismerProvider(profileName, config, agentUsername) {
|
|
1273
1217
|
const baseUrl = resolvePrismerProviderBaseUrl(config);
|
|
1274
1218
|
const apiKey = resolvePrismerApiKey(config);
|
|
1275
1219
|
if (!baseUrl || !apiKey) {
|
|
@@ -1312,16 +1256,9 @@ function configurePrismerProvider(profileName, config, agentUsername, workspaceI
|
|
|
1312
1256
|
PRISMER_API_KEY: apiKey,
|
|
1313
1257
|
PRISMER_BASE_URL: baseUrl.replace(/\/api\/v1\/?$/, "")
|
|
1314
1258
|
};
|
|
1315
|
-
if (workspaceId) {
|
|
1316
|
-
mcpEnv.PRISMER_WORKSPACE_ID = workspaceId;
|
|
1317
|
-
}
|
|
1318
1259
|
if (agentUsername) {
|
|
1319
1260
|
mcpEnv.PRISMER_AGENT_USERNAME = agentUsername;
|
|
1320
1261
|
}
|
|
1321
|
-
const allowlist = resolveMcpAllowlist(config);
|
|
1322
|
-
if (allowlist) {
|
|
1323
|
-
mcpEnv.PRISMER_MCP_ALLOWLIST = allowlist.join(",");
|
|
1324
|
-
}
|
|
1325
1262
|
mcpServers["prismer-tasks"] = {
|
|
1326
1263
|
command: "node",
|
|
1327
1264
|
args: [serverPath],
|
|
@@ -1342,17 +1279,20 @@ function resolvePrismerMcpServerPath(config) {
|
|
|
1342
1279
|
if (config.prismerMcpServerPath) return config.prismerMcpServerPath;
|
|
1343
1280
|
if (process.env.PRISMER_MCP_SERVER) return process.env.PRISMER_MCP_SERVER;
|
|
1344
1281
|
try {
|
|
1282
|
+
const { createRequire } = __require("module");
|
|
1345
1283
|
const req = createRequire(import.meta.url);
|
|
1346
1284
|
return req.resolve("@prismer/mcp-server");
|
|
1347
1285
|
} catch {
|
|
1348
1286
|
}
|
|
1349
1287
|
try {
|
|
1350
|
-
const
|
|
1351
|
-
const
|
|
1288
|
+
const { fileURLToPath: fileURLToPath2 } = __require("url");
|
|
1289
|
+
const { join: join15, dirname: dirname8 } = __require("path");
|
|
1290
|
+
const here = dirname8(fileURLToPath2(import.meta.url));
|
|
1291
|
+
const candidate = join15(here, "../../mcp/dist/index.js");
|
|
1352
1292
|
if (existsSync(candidate)) return candidate;
|
|
1353
|
-
const candidate2 =
|
|
1293
|
+
const candidate2 = join15(here, "../../../mcp/dist/index.js");
|
|
1354
1294
|
if (existsSync(candidate2)) return candidate2;
|
|
1355
|
-
const candidate3 =
|
|
1295
|
+
const candidate3 = join15(here, "../../../../mcp/dist/index.js");
|
|
1356
1296
|
if (existsSync(candidate3)) return candidate3;
|
|
1357
1297
|
} catch {
|
|
1358
1298
|
}
|
|
@@ -1521,29 +1461,25 @@ function runPythonJson(args, env, timeoutMs) {
|
|
|
1521
1461
|
});
|
|
1522
1462
|
});
|
|
1523
1463
|
}
|
|
1524
|
-
function readHermesConfig(
|
|
1525
|
-
if (!existsSync(
|
|
1464
|
+
function readHermesConfig(path7) {
|
|
1465
|
+
if (!existsSync(path7)) return {};
|
|
1526
1466
|
try {
|
|
1527
|
-
return YAML.parse(readFileSync(
|
|
1467
|
+
return YAML.parse(readFileSync(path7, "utf8")) ?? {};
|
|
1528
1468
|
} catch (err) {
|
|
1529
|
-
throw new Error(`Failed to parse Hermes config at ${
|
|
1469
|
+
throw new Error(`Failed to parse Hermes config at ${path7}: ${err.message}`);
|
|
1530
1470
|
}
|
|
1531
1471
|
}
|
|
1532
|
-
function writeEnvValue(
|
|
1533
|
-
mkdirSync(dirname(
|
|
1534
|
-
const lines = existsSync(
|
|
1472
|
+
function writeEnvValue(path7, key, value) {
|
|
1473
|
+
mkdirSync(dirname(path7), { recursive: true });
|
|
1474
|
+
const lines = existsSync(path7) ? readFileSync(path7, "utf8").split(/\r?\n/) : [];
|
|
1535
1475
|
const next = lines.filter((line) => !line.startsWith(`${key}=`) && line.trim() !== "");
|
|
1536
1476
|
next.push(`${key}=${quoteEnv(value)}`);
|
|
1537
|
-
writeFileSync(
|
|
1477
|
+
writeFileSync(path7, `${next.join("\n")}
|
|
1538
1478
|
`, "utf8");
|
|
1539
1479
|
}
|
|
1540
1480
|
function quoteEnv(value) {
|
|
1541
1481
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1542
1482
|
}
|
|
1543
|
-
function getHermesProfileName(profile) {
|
|
1544
|
-
const configured = profile.config && typeof profile.config.hermesProfileName === "string" ? profile.config.hermesProfileName : void 0;
|
|
1545
|
-
return configured && configured !== "default" ? configured : profile.agentUsername || profile.id.slice(0, 8);
|
|
1546
|
-
}
|
|
1547
1483
|
function getHermesProfileDir(profileName) {
|
|
1548
1484
|
const root = process.env.HERMES_HOME || join(homedir(), ".hermes");
|
|
1549
1485
|
if (!profileName || profileName === "default") return root;
|
|
@@ -1565,18 +1501,6 @@ function resolvePrismerProviderBaseUrl(config) {
|
|
|
1565
1501
|
function resolvePrismerApiKey(config) {
|
|
1566
1502
|
return process.env[config.prismerApiKeyEnv] || process.env.PRISMER_API_KEY || "";
|
|
1567
1503
|
}
|
|
1568
|
-
function resolveMcpAllowlist(config) {
|
|
1569
|
-
if (Array.isArray(config.mcpAllowlist)) return normalizeAllowlist(config.mcpAllowlist);
|
|
1570
|
-
const prismerServer = config.roleTemplate?.mcpServers?.find((server) => {
|
|
1571
|
-
const record = server;
|
|
1572
|
-
return record.package === "@prismer/mcp-server" || record.name === "prismer-tasks";
|
|
1573
|
-
});
|
|
1574
|
-
if (!prismerServer) return null;
|
|
1575
|
-
return Array.isArray(prismerServer.toolsAllowlist) ? normalizeAllowlist(prismerServer.toolsAllowlist) : null;
|
|
1576
|
-
}
|
|
1577
|
-
function normalizeAllowlist(values) {
|
|
1578
|
-
return values.map((value) => value.trim()).filter(Boolean);
|
|
1579
|
-
}
|
|
1580
1504
|
function normalizeProviderName(value) {
|
|
1581
1505
|
return value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
1582
1506
|
}
|
|
@@ -1682,66 +1606,6 @@ async function waitForHealthy(baseUrl, apiKey, timeoutMs) {
|
|
|
1682
1606
|
}
|
|
1683
1607
|
throw new Error(`Hermes did not become healthy at ${baseUrl} within ${timeoutMs}ms`);
|
|
1684
1608
|
}
|
|
1685
|
-
async function stopStaleHermesGateways(profileName, port, timeoutMs) {
|
|
1686
|
-
const pids = findHermesGatewayPids(profileName, port);
|
|
1687
|
-
for (const pid of pids) {
|
|
1688
|
-
try {
|
|
1689
|
-
process.kill(pid, "SIGTERM");
|
|
1690
|
-
} catch {
|
|
1691
|
-
continue;
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
const deadline = Date.now() + Math.min(timeoutMs, 5e3);
|
|
1695
|
-
while (Date.now() < deadline) {
|
|
1696
|
-
const alive = pids.filter(pidAlive);
|
|
1697
|
-
if (alive.length === 0) return;
|
|
1698
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
1699
|
-
}
|
|
1700
|
-
for (const pid of pids) {
|
|
1701
|
-
if (!pidAlive(pid)) continue;
|
|
1702
|
-
try {
|
|
1703
|
-
process.kill(pid, "SIGKILL");
|
|
1704
|
-
} catch {
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
function findHermesGatewayPids(profileName, port) {
|
|
1709
|
-
try {
|
|
1710
|
-
const out = execFileSync("ps", ["-axo", "pid=,command="], { encoding: "utf8" });
|
|
1711
|
-
const current = process.pid;
|
|
1712
|
-
const escapedProfile = escapeRegExp(profileName);
|
|
1713
|
-
const profilePattern = new RegExp(`(?:^|\\s)-p\\s+${escapedProfile}(?:\\s|$)`);
|
|
1714
|
-
const portPattern = new RegExp(`(?:^|\\s)API_SERVER_PORT=${port}(?:\\s|$)`);
|
|
1715
|
-
return out.split(/\r?\n/).map((line) => {
|
|
1716
|
-
const trimmed = line.trim();
|
|
1717
|
-
const match = /^(\d+)\s+(.+)$/.exec(trimmed);
|
|
1718
|
-
if (!match) return null;
|
|
1719
|
-
const pid = Number.parseInt(match[1], 10);
|
|
1720
|
-
const command = match[2];
|
|
1721
|
-
if (!Number.isFinite(pid) || pid === current) return null;
|
|
1722
|
-
if (!isHermesGatewayCommand(command)) return null;
|
|
1723
|
-
if (!profilePattern.test(command) && !portPattern.test(command)) return null;
|
|
1724
|
-
return pid;
|
|
1725
|
-
}).filter((pid) => pid !== null);
|
|
1726
|
-
} catch {
|
|
1727
|
-
return [];
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
function isHermesGatewayCommand(command) {
|
|
1731
|
-
if (!/\bgateway\b/.test(command) || !/\brun\b/.test(command)) return false;
|
|
1732
|
-
return /(^|\s)(?:\S*\/)?hermes(?:\s|$)/.test(command);
|
|
1733
|
-
}
|
|
1734
|
-
function pidAlive(pid) {
|
|
1735
|
-
try {
|
|
1736
|
-
process.kill(pid, 0);
|
|
1737
|
-
return true;
|
|
1738
|
-
} catch {
|
|
1739
|
-
return false;
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
function escapeRegExp(value) {
|
|
1743
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1744
|
-
}
|
|
1745
1609
|
|
|
1746
1610
|
// src/daemon/ws-client.ts
|
|
1747
1611
|
import { EventEmitter } from "events";
|
|
@@ -1981,11 +1845,11 @@ var MIGRATIONS = [
|
|
|
1981
1845
|
function runSql(db, sql) {
|
|
1982
1846
|
db["exec"](sql);
|
|
1983
1847
|
}
|
|
1984
|
-
function openLocalDb(
|
|
1985
|
-
if (
|
|
1986
|
-
mkdirSync2(dirname2(
|
|
1848
|
+
function openLocalDb(path7) {
|
|
1849
|
+
if (path7 !== ":memory:" && !existsSync2(dirname2(path7))) {
|
|
1850
|
+
mkdirSync2(dirname2(path7), { recursive: true });
|
|
1987
1851
|
}
|
|
1988
|
-
const db = new Database2(
|
|
1852
|
+
const db = new Database2(path7);
|
|
1989
1853
|
db.pragma("journal_mode = WAL");
|
|
1990
1854
|
db.pragma("foreign_keys = ON");
|
|
1991
1855
|
runMigrations(db);
|
|
@@ -2303,12 +2167,11 @@ function deriveWsUrl(httpBase) {
|
|
|
2303
2167
|
}
|
|
2304
2168
|
|
|
2305
2169
|
// src/daemon-id.ts
|
|
2306
|
-
import { randomBytes } from "crypto";
|
|
2307
2170
|
import { hostname } from "os";
|
|
2308
2171
|
var PREFIX = "daemon-";
|
|
2309
2172
|
function newDaemonId() {
|
|
2310
2173
|
const host = hostname().replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unknown";
|
|
2311
|
-
return `${PREFIX}${host}
|
|
2174
|
+
return `${PREFIX}${host}`;
|
|
2312
2175
|
}
|
|
2313
2176
|
function isDaemonId(s) {
|
|
2314
2177
|
return s.startsWith(PREFIX) && s.length > PREFIX.length;
|
|
@@ -2323,8 +2186,8 @@ var CloudClient = class {
|
|
|
2323
2186
|
opts;
|
|
2324
2187
|
fetchImpl;
|
|
2325
2188
|
/** Low-level request. Caller decides on retry; sync-worker handles backoff. */
|
|
2326
|
-
async request(method,
|
|
2327
|
-
const url = this.urlFor(
|
|
2189
|
+
async request(method, path7, init) {
|
|
2190
|
+
const url = this.urlFor(path7);
|
|
2328
2191
|
const headers = {
|
|
2329
2192
|
"Content-Type": "application/json",
|
|
2330
2193
|
Accept: "application/json",
|
|
@@ -2383,8 +2246,8 @@ var CloudClient = class {
|
|
|
2383
2246
|
return { ok: true, status: res.status, data: body ?? void 0 };
|
|
2384
2247
|
}
|
|
2385
2248
|
/** Convenience: GET expecting `{ ok, data }` envelope. Returns `data` or throws. */
|
|
2386
|
-
async get(
|
|
2387
|
-
const res = await this.request("GET",
|
|
2249
|
+
async get(path7, opts) {
|
|
2250
|
+
const res = await this.request("GET", path7, opts);
|
|
2388
2251
|
if (!res.ok) throw new CloudError(res.status, res.error?.code ?? "unknown", res.error?.message ?? "request failed");
|
|
2389
2252
|
if (res.data && typeof res.data === "object" && "ok" in res.data) {
|
|
2390
2253
|
const env = res.data;
|
|
@@ -2393,14 +2256,14 @@ var CloudClient = class {
|
|
|
2393
2256
|
}
|
|
2394
2257
|
return res.data;
|
|
2395
2258
|
}
|
|
2396
|
-
urlFor(
|
|
2259
|
+
urlFor(path7) {
|
|
2397
2260
|
const trimmedBase = this.opts.baseUrl.replace(/\/$/, "");
|
|
2398
|
-
const trimmedPath =
|
|
2261
|
+
const trimmedPath = path7.startsWith("/") ? path7 : `/${path7}`;
|
|
2399
2262
|
return `${trimmedBase}${trimmedPath}`;
|
|
2400
2263
|
}
|
|
2401
2264
|
/** Raw byte download (for asset cache). Returns response so caller can stream body. */
|
|
2402
|
-
async fetchRaw(
|
|
2403
|
-
const url = this.urlFor(
|
|
2265
|
+
async fetchRaw(path7, init) {
|
|
2266
|
+
const url = this.urlFor(path7);
|
|
2404
2267
|
const headers = {
|
|
2405
2268
|
Authorization: `Bearer ${this.opts.apiKey}`,
|
|
2406
2269
|
...init?.headers ?? {}
|
|
@@ -2449,8 +2312,7 @@ function envelope(type, payload, requestId) {
|
|
|
2449
2312
|
}
|
|
2450
2313
|
|
|
2451
2314
|
// src/asset-cache.ts
|
|
2452
|
-
import {
|
|
2453
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync, statSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2315
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, statSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2454
2316
|
import { dirname as dirname4, join as join3 } from "path";
|
|
2455
2317
|
function rowToCached(row) {
|
|
2456
2318
|
return {
|
|
@@ -2543,17 +2405,11 @@ var AssetCache = class {
|
|
|
2543
2405
|
throw new Error(`Asset bytes fetch failed for ${hash} (id=${assetId}): HTTP ${res.status}`);
|
|
2544
2406
|
}
|
|
2545
2407
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
2546
|
-
const actualHash = sha256(buf);
|
|
2547
|
-
if (actualHash !== hash) {
|
|
2548
|
-
throw new Error(`Asset hash mismatch for ${hash}: downloaded ${actualHash}`);
|
|
2549
|
-
}
|
|
2550
2408
|
const localPath = this.pathFor(hash);
|
|
2551
2409
|
if (!existsSync4(dirname4(localPath))) {
|
|
2552
2410
|
mkdirSync4(dirname4(localPath), { recursive: true });
|
|
2553
2411
|
}
|
|
2554
|
-
|
|
2555
|
-
writeFileSync3(tmpPath, buf);
|
|
2556
|
-
renameSync(tmpPath, localPath);
|
|
2412
|
+
writeFileSync3(localPath, buf);
|
|
2557
2413
|
const mime = res.headers.get("content-type");
|
|
2558
2414
|
const now = Date.now();
|
|
2559
2415
|
this.db.prepare(
|
|
@@ -2574,10 +2430,6 @@ var AssetCache = class {
|
|
|
2574
2430
|
}
|
|
2575
2431
|
/** Insert an already-on-disk asset into the cache (e.g., asset just produced by adapter). */
|
|
2576
2432
|
registerLocal(hash, localPath, mime) {
|
|
2577
|
-
const actualHash = sha256(readFileBuffer(localPath));
|
|
2578
|
-
if (actualHash !== hash) {
|
|
2579
|
-
throw new Error(`Asset hash mismatch for ${hash}: local file is ${actualHash}`);
|
|
2580
|
-
}
|
|
2581
2433
|
const size = statSync(localPath).size;
|
|
2582
2434
|
const now = Date.now();
|
|
2583
2435
|
this.db.prepare(
|
|
@@ -2617,15 +2469,9 @@ var AssetCache = class {
|
|
|
2617
2469
|
return { removed, freed };
|
|
2618
2470
|
}
|
|
2619
2471
|
};
|
|
2620
|
-
function sha256(buf) {
|
|
2621
|
-
return createHash("sha256").update(buf).digest("hex");
|
|
2622
|
-
}
|
|
2623
|
-
function readFileBuffer(path9) {
|
|
2624
|
-
return readFileSync3(path9);
|
|
2625
|
-
}
|
|
2626
2472
|
|
|
2627
2473
|
// src/daemon/asset/mirror.ts
|
|
2628
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as
|
|
2474
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
2629
2475
|
import { join as join4 } from "path";
|
|
2630
2476
|
function rowToBinding(row) {
|
|
2631
2477
|
return {
|
|
@@ -2656,7 +2502,7 @@ var WorkspaceMirror = class {
|
|
|
2656
2502
|
readCursor() {
|
|
2657
2503
|
if (!existsSync5(this.cursorPath)) return null;
|
|
2658
2504
|
try {
|
|
2659
|
-
const parsed = JSON.parse(
|
|
2505
|
+
const parsed = JSON.parse(readFileSync3(this.cursorPath, "utf8"));
|
|
2660
2506
|
if (parsed.workspaceId !== this.workspaceId) return null;
|
|
2661
2507
|
return parsed.cursor;
|
|
2662
2508
|
} catch {
|
|
@@ -2719,8 +2565,8 @@ var WorkspaceMirror = class {
|
|
|
2719
2565
|
return { applied, cursor: envelope2.cursor };
|
|
2720
2566
|
}
|
|
2721
2567
|
/** Lookup the active binding for a workspace path. Returns undefined if not mirrored. */
|
|
2722
|
-
lookupByPath(
|
|
2723
|
-
const row = this.db.prepare("SELECT * FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?").get(this.workspaceId,
|
|
2568
|
+
lookupByPath(path7) {
|
|
2569
|
+
const row = this.db.prepare("SELECT * FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?").get(this.workspaceId, path7);
|
|
2724
2570
|
return row ? rowToBinding(row) : void 0;
|
|
2725
2571
|
}
|
|
2726
2572
|
/** Find an asset's contentHash via any path binding (asset may map to many paths). */
|
|
@@ -2982,20 +2828,20 @@ var UriResolver = class {
|
|
|
2982
2828
|
}
|
|
2983
2829
|
return { texts: all, resolvedHashes: dedupe(allHashes) };
|
|
2984
2830
|
}
|
|
2985
|
-
async lookupFile(workspaceId,
|
|
2831
|
+
async lookupFile(workspaceId, path7, signal) {
|
|
2986
2832
|
const local = this.db.prepare(
|
|
2987
2833
|
"SELECT asset_id, content_hash, version FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?"
|
|
2988
|
-
).get(workspaceId,
|
|
2834
|
+
).get(workspaceId, path7);
|
|
2989
2835
|
if (local) return local;
|
|
2990
2836
|
const remote = await this.cloud.get(
|
|
2991
|
-
`/api/im/workspaces/${encodeURIComponent(workspaceId)}/files?path=${encodeURIComponent(
|
|
2837
|
+
`/api/im/workspaces/${encodeURIComponent(workspaceId)}/files?path=${encodeURIComponent(path7)}`,
|
|
2992
2838
|
{ signal }
|
|
2993
2839
|
);
|
|
2994
2840
|
this.db.prepare(
|
|
2995
2841
|
`INSERT OR REPLACE INTO workspace_files_mirror
|
|
2996
2842
|
(workspace_id, path, asset_id, content_hash, version, synced_at, dirty)
|
|
2997
2843
|
VALUES (?, ?, ?, ?, ?, ?, 0)`
|
|
2998
|
-
).run(workspaceId,
|
|
2844
|
+
).run(workspaceId, path7, remote.assetId, remote.contentHash, remote.version, Date.now());
|
|
2999
2845
|
return { asset_id: remote.assetId, content_hash: remote.contentHash, version: remote.version };
|
|
3000
2846
|
}
|
|
3001
2847
|
};
|
|
@@ -3004,244 +2850,11 @@ function dedupe(xs) {
|
|
|
3004
2850
|
}
|
|
3005
2851
|
|
|
3006
2852
|
// src/daemon/dispatch.ts
|
|
3007
|
-
import { readFileSync as
|
|
2853
|
+
import { readFileSync as readFileSync4, promises as fsp } from "fs";
|
|
3008
2854
|
import * as path from "path";
|
|
3009
|
-
|
|
3010
|
-
// src/daemon/skill-sync.ts
|
|
3011
|
-
import { promises as fsp } from "fs";
|
|
3012
|
-
import { join as join6 } from "path";
|
|
3013
|
-
import { createHash as createHash2 } from "crypto";
|
|
3014
|
-
|
|
3015
|
-
// src/adapters/openclaw/index.ts
|
|
3016
|
-
import { z as z3 } from "zod";
|
|
3017
|
-
import { homedir as homedir3 } from "os";
|
|
3018
|
-
import { join as join5 } from "path";
|
|
3019
|
-
var OpenClawProfileConfigSchema = z3.object({
|
|
3020
|
-
/** OpenClaw chat-completions HTTP port. */
|
|
3021
|
-
port: z3.number().int().min(1).max(65535).default(18789),
|
|
3022
|
-
/** Bearer token expected by the gateway (from gateway.auth.bearerTokens). */
|
|
3023
|
-
apiKey: z3.string().min(1),
|
|
3024
|
-
/**
|
|
3025
|
-
* Model field passed to OpenClaw's /v1/chat/completions. OpenClaw
|
|
3026
|
-
* intercepts this differently from a vanilla OpenAI server: the value
|
|
3027
|
-
* MUST be `openclaw` (or `openclaw/<agentId>`) — it picks which
|
|
3028
|
-
* OpenClaw agent answers, NOT the underlying LLM. The LLM is selected
|
|
3029
|
-
* by `agents.defaults.model.primary` in openclaw.json (e.g.
|
|
3030
|
-
* `prismer/us-kimi-k2.5`, set by the bootstrap).
|
|
3031
|
-
*/
|
|
3032
|
-
model: z3.string().min(1).default("openclaw"),
|
|
3033
|
-
mcpAllowlist: z3.array(z3.string()).nullable().optional(),
|
|
3034
|
-
approvalPolicy: z3.enum(["strict", "auto-low-risk", "autonomous"]).optional().default("auto-low-risk"),
|
|
3035
|
-
roleTemplate: z3.object({
|
|
3036
|
-
mcpServers: z3.array(z3.object({ toolsAllowlist: z3.array(z3.string()).optional() }).passthrough()).optional()
|
|
3037
|
-
}).passthrough().optional(),
|
|
3038
|
-
skillsDir: z3.string().optional()
|
|
3039
|
-
});
|
|
3040
|
-
var openclawAdapter = {
|
|
3041
|
-
name: "openclaw",
|
|
3042
|
-
kind: "long-running",
|
|
3043
|
-
capabilities: ["chat", "code", "multi-channel"],
|
|
3044
|
-
workspaceSchema: OpenClawProfileConfigSchema,
|
|
3045
|
-
validate(config) {
|
|
3046
|
-
const r = OpenClawProfileConfigSchema.safeParse(config);
|
|
3047
|
-
if (r.success) return { ok: true };
|
|
3048
|
-
return {
|
|
3049
|
-
ok: false,
|
|
3050
|
-
errors: r.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
3051
|
-
};
|
|
3052
|
-
},
|
|
3053
|
-
async ensureService(profile) {
|
|
3054
|
-
const config = OpenClawProfileConfigSchema.parse(profile.config);
|
|
3055
|
-
const baseUrl = `http://127.0.0.1:${config.port}`;
|
|
3056
|
-
if (!await checkHealth2(baseUrl, config.apiKey)) {
|
|
3057
|
-
throw new Error(
|
|
3058
|
-
`OpenClaw gateway not reachable at ${baseUrl}. Start it with:
|
|
3059
|
-
openclaw gateway --port ${config.port}`
|
|
3060
|
-
);
|
|
3061
|
-
}
|
|
3062
|
-
return new OpenClawService(baseUrl, config.apiKey, config.model, resolveOpenClawSkillsDir(profile, config));
|
|
3063
|
-
},
|
|
3064
|
-
async health() {
|
|
3065
|
-
return { available: true };
|
|
3066
|
-
}
|
|
3067
|
-
};
|
|
3068
|
-
var OpenClawService = class {
|
|
3069
|
-
constructor(baseUrl, apiKey, model, skillsDir) {
|
|
3070
|
-
this.baseUrl = baseUrl;
|
|
3071
|
-
this.apiKey = apiKey;
|
|
3072
|
-
this.model = model;
|
|
3073
|
-
this.skillsDir = skillsDir;
|
|
3074
|
-
this.id = baseUrl;
|
|
3075
|
-
}
|
|
3076
|
-
baseUrl;
|
|
3077
|
-
apiKey;
|
|
3078
|
-
model;
|
|
3079
|
-
skillsDir;
|
|
3080
|
-
id;
|
|
3081
|
-
async healthy() {
|
|
3082
|
-
return checkHealth2(this.baseUrl, this.apiKey);
|
|
3083
|
-
}
|
|
3084
|
-
async dispatch(task) {
|
|
3085
|
-
const startedAt = Date.now();
|
|
3086
|
-
const systemPrompt = typeof task.metadata?.systemPrompt === "string" ? task.metadata.systemPrompt : void 0;
|
|
3087
|
-
const messages = [];
|
|
3088
|
-
if (systemPrompt) messages.push({ role: "system", content: systemPrompt });
|
|
3089
|
-
messages.push({ role: "user", content: task.prompt });
|
|
3090
|
-
try {
|
|
3091
|
-
const res = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
3092
|
-
method: "POST",
|
|
3093
|
-
headers: {
|
|
3094
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
3095
|
-
"Content-Type": "application/json"
|
|
3096
|
-
},
|
|
3097
|
-
body: JSON.stringify({
|
|
3098
|
-
model: this.model,
|
|
3099
|
-
messages,
|
|
3100
|
-
stream: false
|
|
3101
|
-
}),
|
|
3102
|
-
signal: task.signal
|
|
3103
|
-
});
|
|
3104
|
-
if (!res.ok) {
|
|
3105
|
-
const body = await res.text().catch(() => "") ?? "";
|
|
3106
|
-
return {
|
|
3107
|
-
ok: false,
|
|
3108
|
-
error: {
|
|
3109
|
-
code: res.status === 401 || res.status === 403 ? "auth_invalid" : "adapter_dispatch_failed",
|
|
3110
|
-
message: `OpenClaw ${res.status}: ${body.slice(0, 400)}`
|
|
3111
|
-
}
|
|
3112
|
-
};
|
|
3113
|
-
}
|
|
3114
|
-
const json = await res.json();
|
|
3115
|
-
let output = json.choices?.[0]?.message?.content ?? "";
|
|
3116
|
-
const ERR_PREFIX_RE = /^Cannot read properties of undefined \(reading '[^']+'\)\s*\n+/;
|
|
3117
|
-
if (ERR_PREFIX_RE.test(output)) {
|
|
3118
|
-
process.stderr.write(
|
|
3119
|
-
`[openclaw-adapter] stripping known stderr-bleed prefix from chat output (OpenClaw 2026.4.x bug)
|
|
3120
|
-
`
|
|
3121
|
-
);
|
|
3122
|
-
output = output.replace(ERR_PREFIX_RE, "");
|
|
3123
|
-
}
|
|
3124
|
-
return {
|
|
3125
|
-
ok: true,
|
|
3126
|
-
output,
|
|
3127
|
-
metrics: { durationMs: Date.now() - startedAt }
|
|
3128
|
-
};
|
|
3129
|
-
} catch (err) {
|
|
3130
|
-
if (task.signal?.aborted || err?.name === "AbortError") {
|
|
3131
|
-
return { ok: false, error: { code: "task_cancelled", message: "Task cancelled by client" } };
|
|
3132
|
-
}
|
|
3133
|
-
return {
|
|
3134
|
-
ok: false,
|
|
3135
|
-
error: { code: "adapter_dispatch_failed", message: err.message }
|
|
3136
|
-
};
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3139
|
-
};
|
|
3140
|
-
function getOpenClawSkillsDir(profile) {
|
|
3141
|
-
const config = OpenClawProfileConfigSchema.parse(profile.config);
|
|
3142
|
-
return resolveOpenClawSkillsDir(profile, config);
|
|
3143
|
-
}
|
|
3144
|
-
function resolveOpenClawSkillsDir(profile, config) {
|
|
3145
|
-
if (config.skillsDir) return config.skillsDir;
|
|
3146
|
-
const name = profile.agentUsername || profile.id.slice(0, 8);
|
|
3147
|
-
return join5(process.env.OPENCLAW_HOME || join5(homedir3(), ".openclaw"), "profiles", name, "skills");
|
|
3148
|
-
}
|
|
3149
|
-
async function checkHealth2(baseUrl, apiKey) {
|
|
3150
|
-
try {
|
|
3151
|
-
const res = await fetch(`${baseUrl}/health`, {
|
|
3152
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
3153
|
-
signal: AbortSignal.timeout(2e3)
|
|
3154
|
-
});
|
|
3155
|
-
return res.ok;
|
|
3156
|
-
} catch {
|
|
3157
|
-
return false;
|
|
3158
|
-
}
|
|
3159
|
-
}
|
|
3160
|
-
|
|
3161
|
-
// src/daemon/skill-sync.ts
|
|
3162
|
-
async function syncInstalledSkillsForDispatch(profile, agentImUserId, cloud, signal) {
|
|
3163
|
-
if (!agentImUserId) return { synced: 0, skipped: 0, unchanged: 0 };
|
|
3164
|
-
const data = await cloud.get(
|
|
3165
|
-
`/api/im/skills/installed?agentId=${encodeURIComponent(agentImUserId)}`,
|
|
3166
|
-
{ signal }
|
|
3167
|
-
);
|
|
3168
|
-
const entries = normalizeInstalledSkills(data);
|
|
3169
|
-
const skillsRoot = resolveSkillsRoot(profile);
|
|
3170
|
-
if (!skillsRoot) return { synced: 0, skipped: 0, unchanged: 0 };
|
|
3171
|
-
let synced = 0;
|
|
3172
|
-
let skipped = 0;
|
|
3173
|
-
let unchanged = 0;
|
|
3174
|
-
for (const entry of entries) {
|
|
3175
|
-
const slug = sanitizeSlug(trimmedStringFrom(entry.skill?.slug) ?? trimmedStringFrom(entry.slug));
|
|
3176
|
-
const content = contentStringFrom(entry.skill?.content) ?? contentStringFrom(entry.content);
|
|
3177
|
-
if (!slug || !content) {
|
|
3178
|
-
skipped++;
|
|
3179
|
-
if (slug && !content) {
|
|
3180
|
-
process.stderr.write(`[daemon] skill sync skipped ${slug}: missing content
|
|
3181
|
-
`);
|
|
3182
|
-
}
|
|
3183
|
-
continue;
|
|
3184
|
-
}
|
|
3185
|
-
const skillDir = join6(skillsRoot, slug);
|
|
3186
|
-
await fsp.mkdir(skillDir, { recursive: true });
|
|
3187
|
-
const target = join6(skillDir, "SKILL.md");
|
|
3188
|
-
const existing = await fsp.readFile(target, "utf8").catch(() => null);
|
|
3189
|
-
if (existing != null && sha2562(existing) === sha2562(content)) {
|
|
3190
|
-
unchanged++;
|
|
3191
|
-
continue;
|
|
3192
|
-
}
|
|
3193
|
-
await fsp.writeFile(target, content, "utf8");
|
|
3194
|
-
synced++;
|
|
3195
|
-
}
|
|
3196
|
-
return { synced, skipped, unchanged };
|
|
3197
|
-
}
|
|
3198
|
-
function resolveSkillsRoot(profile) {
|
|
3199
|
-
if (profile.adapterName === "hermes") return join6(getHermesProfileDir(getHermesProfileName(profile)), "skills");
|
|
3200
|
-
if (profile.adapterName === "openclaw") return getOpenClawSkillsDir(profile);
|
|
3201
|
-
return null;
|
|
3202
|
-
}
|
|
3203
|
-
function sha2562(value) {
|
|
3204
|
-
return createHash2("sha256").update(value).digest("hex");
|
|
3205
|
-
}
|
|
3206
|
-
function normalizeInstalledSkills(data) {
|
|
3207
|
-
if (Array.isArray(data)) return data.filter(isInstalledSkillEntry);
|
|
3208
|
-
if (!data || typeof data !== "object" || Array.isArray(data)) return [];
|
|
3209
|
-
const record = data;
|
|
3210
|
-
const nested = record.data;
|
|
3211
|
-
if (Array.isArray(nested)) return nested.filter(isInstalledSkillEntry);
|
|
3212
|
-
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
3213
|
-
const skills2 = nested.skills;
|
|
3214
|
-
if (Array.isArray(skills2)) return skills2.filter(isInstalledSkillEntry);
|
|
3215
|
-
}
|
|
3216
|
-
const skills = record.skills;
|
|
3217
|
-
if (Array.isArray(skills)) return skills.filter(isInstalledSkillEntry);
|
|
3218
|
-
return [];
|
|
3219
|
-
}
|
|
3220
|
-
function isInstalledSkillEntry(value) {
|
|
3221
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3222
|
-
}
|
|
3223
|
-
function trimmedStringFrom(value) {
|
|
3224
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
3225
|
-
}
|
|
3226
|
-
function contentStringFrom(value) {
|
|
3227
|
-
return typeof value === "string" && value.trim() ? value : void 0;
|
|
3228
|
-
}
|
|
3229
|
-
function sanitizeSlug(value) {
|
|
3230
|
-
if (!value) return void 0;
|
|
3231
|
-
const safe = value.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
3232
|
-
return safe || void 0;
|
|
3233
|
-
}
|
|
3234
|
-
|
|
3235
|
-
// src/daemon/dispatch.ts
|
|
3236
2855
|
var DEFAULT_CONTEXT_MAX = 8e3;
|
|
3237
2856
|
var GOAL_CONTEXT_MAX = 4;
|
|
3238
2857
|
var MEMORY_DIGEST_MAX_BYTES = 4e3;
|
|
3239
|
-
var DEFAULT_OPERATING_PRINCIPLES = [
|
|
3240
|
-
"- Decide and act; only request human approval for destructive operations or irreversible external effects.",
|
|
3241
|
-
"- Assignable work should become explicit tasks; do not leave concrete work as chat discussion only.",
|
|
3242
|
-
"- Use workspace asset tools for uploaded files: search/describe first, then read bounded ranges; do not claim file contents unless a tool call succeeded.",
|
|
3243
|
-
"- Keep users informed with concise status and concrete next steps."
|
|
3244
|
-
].join("\n");
|
|
3245
2858
|
var ASSET_INLINE_FULL_MAX_BYTES = 64 * 1024;
|
|
3246
2859
|
var ASSET_INLINE_TRUNCATE_BYTES = 16 * 1024;
|
|
3247
2860
|
async function handleDispatch(payload, requestId, deps) {
|
|
@@ -3252,7 +2865,7 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
3252
2865
|
const outboxDir = outboxBase ? path.join(outboxBase, "_outbox") : null;
|
|
3253
2866
|
if (outboxDir) {
|
|
3254
2867
|
try {
|
|
3255
|
-
await
|
|
2868
|
+
await fsp.mkdir(outboxDir, { recursive: true });
|
|
3256
2869
|
} catch (err) {
|
|
3257
2870
|
process.stderr.write(
|
|
3258
2871
|
`[daemon] outbox provision failed task=${taskId} dir=${outboxDir}: ${err.message}
|
|
@@ -3279,18 +2892,6 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
3279
2892
|
sendReply(deps.ws, reply, requestId);
|
|
3280
2893
|
return reply;
|
|
3281
2894
|
}
|
|
3282
|
-
try {
|
|
3283
|
-
const skillSync = await syncInstalledSkillsForDispatch(profile, agentImUserId, deps.cloud, deps.signal);
|
|
3284
|
-
if (skillSync.synced > 0 || skillSync.skipped > 0) {
|
|
3285
|
-
process.stderr.write(
|
|
3286
|
-
`[daemon] skill sync profile=${profile.id} adapter=${profile.adapterName} synced=${skillSync.synced} skipped=${skillSync.skipped}
|
|
3287
|
-
`
|
|
3288
|
-
);
|
|
3289
|
-
}
|
|
3290
|
-
} catch (err) {
|
|
3291
|
-
process.stderr.write(`[daemon] skill sync skipped profile=${profile.id}: ${err.message}
|
|
3292
|
-
`);
|
|
3293
|
-
}
|
|
3294
2895
|
let hashRefResult = { text: payload.prompt, resolutions: [] };
|
|
3295
2896
|
if (deps.assetMetadataIndexes && profile.workspaceId) {
|
|
3296
2897
|
const assetIndex = deps.assetMetadataIndexes.get(profile.workspaceId);
|
|
@@ -3340,7 +2941,6 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
3340
2941
|
`
|
|
3341
2942
|
);
|
|
3342
2943
|
}
|
|
3343
|
-
const operatingPrinciples = resolveOperatingPrinciples(profile.config);
|
|
3344
2944
|
const taskInput = {
|
|
3345
2945
|
taskId,
|
|
3346
2946
|
prompt: adapterPrompt,
|
|
@@ -3349,7 +2949,6 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
3349
2949
|
conversationId: payload.conversationId,
|
|
3350
2950
|
prismerGoals: goalContext.map(toGoalMirrorPayload),
|
|
3351
2951
|
...profileSystemPrompt ? { systemPrompt: profileSystemPrompt } : {},
|
|
3352
|
-
...operatingPrinciples ? { operatingPrinciples } : {},
|
|
3353
2952
|
// Wave-9: spawn-style adapters (claude-code, codex, openclaw) read
|
|
3354
2953
|
// this and inject `PRISMER_OUTBOX_DIR=<abs>` into the child env so
|
|
3355
2954
|
// the LLM tool can resolve the path even if it doesn't read the
|
|
@@ -3620,7 +3219,7 @@ async function resolveAssetRefs(refs, cache) {
|
|
|
3620
3219
|
const inlineCandidate = isTextLikeMime(effectiveMime);
|
|
3621
3220
|
if (inlineCandidate) {
|
|
3622
3221
|
try {
|
|
3623
|
-
const buf =
|
|
3222
|
+
const buf = readFileSync4(cached.localPath);
|
|
3624
3223
|
let strategy;
|
|
3625
3224
|
let body;
|
|
3626
3225
|
if (buf.byteLength <= ASSET_INLINE_FULL_MAX_BYTES) {
|
|
@@ -3694,36 +3293,6 @@ ${memory.digest.trim()}
|
|
|
3694
3293
|
|
|
3695
3294
|
${prompt}`;
|
|
3696
3295
|
}
|
|
3697
|
-
function resolveOperatingPrinciples(config) {
|
|
3698
|
-
const cfg = readRecord(config);
|
|
3699
|
-
const roleTemplate = readRecord(cfg.roleTemplate);
|
|
3700
|
-
const principles = stringifyPrinciples(cfg.operatingPrinciples) ?? stringifyPrinciples(roleTemplate.operatingPrinciples) ?? DEFAULT_OPERATING_PRINCIPLES;
|
|
3701
|
-
const policy = typeof cfg.approvalPolicy === "string" ? cfg.approvalPolicy : roleTemplate.approvalPolicy;
|
|
3702
|
-
if (policy === "strict") {
|
|
3703
|
-
return `${principles}
|
|
3704
|
-
- Approval policy: strict. Request human approval before destructive, external, spend, access, or publish operations.`;
|
|
3705
|
-
}
|
|
3706
|
-
if (policy === "autonomous") {
|
|
3707
|
-
return `${principles}
|
|
3708
|
-
- Approval policy: autonomous. Do not request human approval unless a platform policy explicitly requires it.`;
|
|
3709
|
-
}
|
|
3710
|
-
return `${principles}
|
|
3711
|
-
- Approval policy: auto-low-risk. Proceed on reversible low-risk work; request human approval for destructive or irreversible operations.`;
|
|
3712
|
-
}
|
|
3713
|
-
function stringifyPrinciples(value) {
|
|
3714
|
-
if (typeof value === "string" && value.trim()) return value.trim();
|
|
3715
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
3716
|
-
const record = value;
|
|
3717
|
-
for (const key of ["en", "zh"]) {
|
|
3718
|
-
const text = record[key];
|
|
3719
|
-
if (typeof text === "string" && text.trim()) return text.trim();
|
|
3720
|
-
}
|
|
3721
|
-
for (const text of Object.values(record)) {
|
|
3722
|
-
if (typeof text === "string" && text.trim()) return text.trim();
|
|
3723
|
-
}
|
|
3724
|
-
}
|
|
3725
|
-
return void 0;
|
|
3726
|
-
}
|
|
3727
3296
|
function appendOutboxInstruction(prompt, outboxDir, profileConfig) {
|
|
3728
3297
|
if (!outboxDir) return prompt;
|
|
3729
3298
|
if (profileConfig?.disableOutboxHints === true) return prompt;
|
|
@@ -3742,9 +3311,6 @@ function appendChannelContext(prompt, payload, profile) {
|
|
|
3742
3311
|
const type = payload.conversationType === "direct" || payload.conversationType === "group" ? payload.conversationType : "unknown";
|
|
3743
3312
|
const youAre = profile.agentUsername || payload.agentImUserId || "this agent";
|
|
3744
3313
|
const lines = ["[Channel context]", `Conversation type: ${type}`, `You are: ${youAre}`];
|
|
3745
|
-
if (payload.conversationId) {
|
|
3746
|
-
lines.push(`Conversation ID: ${payload.conversationId}`);
|
|
3747
|
-
}
|
|
3748
3314
|
const participants = Array.isArray(payload.participants) ? payload.participants : [];
|
|
3749
3315
|
if (participants.length > 0) {
|
|
3750
3316
|
if (type === "direct") {
|
|
@@ -4014,7 +3580,7 @@ var ServicePool = class {
|
|
|
4014
3580
|
|
|
4015
3581
|
// src/daemon/local-server.ts
|
|
4016
3582
|
import { createServer } from "http";
|
|
4017
|
-
import { randomUUID, createHash
|
|
3583
|
+
import { randomUUID, createHash } from "crypto";
|
|
4018
3584
|
import { promises as fs } from "fs";
|
|
4019
3585
|
import * as path2 from "path";
|
|
4020
3586
|
var LocalServer = class {
|
|
@@ -4322,10 +3888,10 @@ async function walkAndDigest(root, current) {
|
|
|
4322
3888
|
}
|
|
4323
3889
|
if (!st.isFile()) continue;
|
|
4324
3890
|
const buf = await fs.readFile(full);
|
|
4325
|
-
const
|
|
3891
|
+
const sha2562 = createHash("sha256").update(buf).digest("hex");
|
|
4326
3892
|
out.push({
|
|
4327
3893
|
path: rel,
|
|
4328
|
-
sha256:
|
|
3894
|
+
sha256: sha2562,
|
|
4329
3895
|
sizeBytes: st.size,
|
|
4330
3896
|
mtime: Math.floor(st.mtimeMs)
|
|
4331
3897
|
});
|
|
@@ -4335,14 +3901,12 @@ async function walkAndDigest(root, current) {
|
|
|
4335
3901
|
|
|
4336
3902
|
// src/daemon/runner.ts
|
|
4337
3903
|
import { EventEmitter as EventEmitter3 } from "events";
|
|
4338
|
-
import { existsSync as existsSync9, readFileSync as
|
|
4339
|
-
import {
|
|
4340
|
-
import { homedir as homedir6, platform } from "os";
|
|
4341
|
-
import { join as join13 } from "path";
|
|
3904
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
3905
|
+
import { platform } from "os";
|
|
4342
3906
|
|
|
4343
3907
|
// src/adapters/claude-code/index.ts
|
|
4344
3908
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
4345
|
-
import { z as
|
|
3909
|
+
import { z as z3 } from "zod";
|
|
4346
3910
|
|
|
4347
3911
|
// src/adapters/claude-code/output-parser.ts
|
|
4348
3912
|
var MAX_OUTPUT_CHARS = 64 * 1024;
|
|
@@ -4374,28 +3938,28 @@ function parseHeadlessOutput(stdout) {
|
|
|
4374
3938
|
}
|
|
4375
3939
|
|
|
4376
3940
|
// src/adapters/claude-code/index.ts
|
|
4377
|
-
var McpServerSchema =
|
|
4378
|
-
name:
|
|
4379
|
-
command:
|
|
4380
|
-
args:
|
|
4381
|
-
env:
|
|
3941
|
+
var McpServerSchema = z3.object({
|
|
3942
|
+
name: z3.string(),
|
|
3943
|
+
command: z3.string(),
|
|
3944
|
+
args: z3.array(z3.string()).optional(),
|
|
3945
|
+
env: z3.record(z3.string(), z3.string()).optional()
|
|
4382
3946
|
});
|
|
4383
|
-
var CCConfigSchema =
|
|
4384
|
-
cwd:
|
|
3947
|
+
var CCConfigSchema = z3.object({
|
|
3948
|
+
cwd: z3.string().min(1),
|
|
4385
3949
|
// claude 2.x accepts aliases ('sonnet', 'haiku', 'opus') and full ids
|
|
4386
3950
|
// (e.g. 'claude-sonnet-4-6'). The 1.x default 'claude-3-5-sonnet' is no
|
|
4387
3951
|
// longer a valid model id under the current CLI.
|
|
4388
|
-
model:
|
|
4389
|
-
systemPrompt:
|
|
4390
|
-
envVars:
|
|
4391
|
-
mcpServers:
|
|
4392
|
-
allowedTools:
|
|
4393
|
-
maxTurns:
|
|
3952
|
+
model: z3.string().default("sonnet"),
|
|
3953
|
+
systemPrompt: z3.string().optional(),
|
|
3954
|
+
envVars: z3.record(z3.string(), z3.string()).optional(),
|
|
3955
|
+
mcpServers: z3.array(McpServerSchema).optional(),
|
|
3956
|
+
allowedTools: z3.array(z3.string()).optional(),
|
|
3957
|
+
maxTurns: z3.number().int().positive().default(20),
|
|
4394
3958
|
// External-model route (1.9.x extension — optional, no breaking change)
|
|
4395
|
-
baseURL:
|
|
4396
|
-
apiKeyRef:
|
|
3959
|
+
baseURL: z3.string().url().optional(),
|
|
3960
|
+
apiKeyRef: z3.string().regex(/^(env|keychain):[A-Za-z0-9_][A-Za-z0-9_.\-]*$/).optional(),
|
|
4397
3961
|
// informational tag — dispatch does not branch on this value yet
|
|
4398
|
-
route:
|
|
3962
|
+
route: z3.enum(["default", "prismer", "omniroute"]).default("default")
|
|
4399
3963
|
});
|
|
4400
3964
|
var claudeCodeAdapter = {
|
|
4401
3965
|
name: "claude-code",
|
|
@@ -4549,7 +4113,7 @@ function resolveKeyRef(ref) {
|
|
|
4549
4113
|
|
|
4550
4114
|
// src/adapters/codex/index.ts
|
|
4551
4115
|
import { spawn as spawn3 } from "child_process";
|
|
4552
|
-
import { z as
|
|
4116
|
+
import { z as z4 } from "zod";
|
|
4553
4117
|
|
|
4554
4118
|
// src/skills/memory-curation.ts
|
|
4555
4119
|
var MEMORY_CURATION_SKILL_TEXT = `---
|
|
@@ -4682,31 +4246,31 @@ If the answer is no, do not save.
|
|
|
4682
4246
|
`;
|
|
4683
4247
|
|
|
4684
4248
|
// src/adapters/codex/index.ts
|
|
4685
|
-
var CodexConfigSchema =
|
|
4249
|
+
var CodexConfigSchema = z4.object({
|
|
4686
4250
|
/** Working directory for the codex subprocess. */
|
|
4687
|
-
cwd:
|
|
4251
|
+
cwd: z4.string().min(1),
|
|
4688
4252
|
/**
|
|
4689
4253
|
* Model identifier passed to --model (e.g. 'codex-mini-latest', 'o4-mini').
|
|
4690
4254
|
* Default matches the Codex CLI built-in default as of 2026-05.
|
|
4691
4255
|
*/
|
|
4692
|
-
model:
|
|
4256
|
+
model: z4.string().default("codex-mini-latest"),
|
|
4693
4257
|
/**
|
|
4694
4258
|
* Sandbox level for the codex subprocess.
|
|
4695
4259
|
* read-only — analysis only; code-gen tasks will stall
|
|
4696
4260
|
* workspace-write — write inside cwd, read-only outside (recommended)
|
|
4697
4261
|
* danger-full-access — no restrictions; safe only inside isolated containers
|
|
4698
4262
|
*/
|
|
4699
|
-
sandbox:
|
|
4263
|
+
sandbox: z4.enum(["read-only", "workspace-write", "danger-full-access"]).default("workspace-write"),
|
|
4700
4264
|
/** Optional system prompt prepended to the user prompt as a preamble (see U4). */
|
|
4701
|
-
systemPrompt:
|
|
4265
|
+
systemPrompt: z4.string().optional(),
|
|
4702
4266
|
/** Extra env vars merged into the codex subprocess environment. */
|
|
4703
|
-
envVars:
|
|
4267
|
+
envVars: z4.record(z4.string(), z4.string()).optional(),
|
|
4704
4268
|
/**
|
|
4705
4269
|
* Name of the env var holding the OpenAI API key. Defaults to
|
|
4706
4270
|
* 'OPENAI_API_KEY' (Codex CLI default). Override if using a workspace-
|
|
4707
4271
|
* scoped key under a different name.
|
|
4708
4272
|
*/
|
|
4709
|
-
apiKeyEnv:
|
|
4273
|
+
apiKeyEnv: z4.string().default("OPENAI_API_KEY")
|
|
4710
4274
|
});
|
|
4711
4275
|
function buildCodexPrompt(config, taskPrompt) {
|
|
4712
4276
|
const preambleParts = [MEMORY_CURATION_SKILL_TEXT.trim()];
|
|
@@ -4854,33 +4418,160 @@ var codexAdapter = {
|
|
|
4854
4418
|
}
|
|
4855
4419
|
};
|
|
4856
4420
|
|
|
4857
|
-
// src/
|
|
4858
|
-
import
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
var
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4421
|
+
// src/adapters/openclaw/index.ts
|
|
4422
|
+
import { z as z5 } from "zod";
|
|
4423
|
+
var OpenClawProfileConfigSchema = z5.object({
|
|
4424
|
+
/** OpenClaw chat-completions HTTP port. */
|
|
4425
|
+
port: z5.number().int().min(1).max(65535).default(18789),
|
|
4426
|
+
/** Bearer token expected by the gateway (from gateway.auth.bearerTokens). */
|
|
4427
|
+
apiKey: z5.string().min(1),
|
|
4428
|
+
/**
|
|
4429
|
+
* Model field passed to OpenClaw's /v1/chat/completions. OpenClaw
|
|
4430
|
+
* intercepts this differently from a vanilla OpenAI server: the value
|
|
4431
|
+
* MUST be `openclaw` (or `openclaw/<agentId>`) — it picks which
|
|
4432
|
+
* OpenClaw agent answers, NOT the underlying LLM. The LLM is selected
|
|
4433
|
+
* by `agents.defaults.model.primary` in openclaw.json (e.g.
|
|
4434
|
+
* `prismer/us-kimi-k2.5`, set by the bootstrap).
|
|
4435
|
+
*/
|
|
4436
|
+
model: z5.string().min(1).default("openclaw")
|
|
4437
|
+
});
|
|
4438
|
+
var openclawAdapter = {
|
|
4439
|
+
name: "openclaw",
|
|
4440
|
+
kind: "long-running",
|
|
4441
|
+
capabilities: ["chat", "code", "multi-channel"],
|
|
4442
|
+
workspaceSchema: OpenClawProfileConfigSchema,
|
|
4443
|
+
validate(config) {
|
|
4444
|
+
const r = OpenClawProfileConfigSchema.safeParse(config);
|
|
4445
|
+
if (r.success) return { ok: true };
|
|
4446
|
+
return {
|
|
4447
|
+
ok: false,
|
|
4448
|
+
errors: r.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
4449
|
+
};
|
|
4450
|
+
},
|
|
4451
|
+
async ensureService(profile) {
|
|
4452
|
+
const config = OpenClawProfileConfigSchema.parse(profile.config);
|
|
4453
|
+
const baseUrl = `http://127.0.0.1:${config.port}`;
|
|
4454
|
+
if (!await checkHealth2(baseUrl, config.apiKey)) {
|
|
4455
|
+
throw new Error(
|
|
4456
|
+
`OpenClaw gateway not reachable at ${baseUrl}. Start it with:
|
|
4457
|
+
openclaw gateway --port ${config.port}`
|
|
4458
|
+
);
|
|
4459
|
+
}
|
|
4460
|
+
return new OpenClawService(baseUrl, config.apiKey, config.model);
|
|
4461
|
+
},
|
|
4462
|
+
async health() {
|
|
4463
|
+
return { available: true };
|
|
4464
|
+
}
|
|
4465
|
+
};
|
|
4466
|
+
var OpenClawService = class {
|
|
4467
|
+
constructor(baseUrl, apiKey, model) {
|
|
4468
|
+
this.baseUrl = baseUrl;
|
|
4469
|
+
this.apiKey = apiKey;
|
|
4470
|
+
this.model = model;
|
|
4471
|
+
this.id = baseUrl;
|
|
4472
|
+
}
|
|
4473
|
+
baseUrl;
|
|
4474
|
+
apiKey;
|
|
4475
|
+
model;
|
|
4476
|
+
id;
|
|
4477
|
+
async healthy() {
|
|
4478
|
+
return checkHealth2(this.baseUrl, this.apiKey);
|
|
4479
|
+
}
|
|
4480
|
+
async dispatch(task) {
|
|
4481
|
+
const startedAt = Date.now();
|
|
4482
|
+
const systemPrompt = typeof task.metadata?.systemPrompt === "string" ? task.metadata.systemPrompt : void 0;
|
|
4483
|
+
const messages = [];
|
|
4484
|
+
if (systemPrompt) messages.push({ role: "system", content: systemPrompt });
|
|
4485
|
+
messages.push({ role: "user", content: task.prompt });
|
|
4486
|
+
try {
|
|
4487
|
+
const res = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
4488
|
+
method: "POST",
|
|
4489
|
+
headers: {
|
|
4490
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
4491
|
+
"Content-Type": "application/json"
|
|
4492
|
+
},
|
|
4493
|
+
body: JSON.stringify({
|
|
4494
|
+
model: this.model,
|
|
4495
|
+
messages,
|
|
4496
|
+
stream: false
|
|
4497
|
+
}),
|
|
4498
|
+
signal: task.signal
|
|
4499
|
+
});
|
|
4500
|
+
if (!res.ok) {
|
|
4501
|
+
const body = await res.text().catch(() => "") ?? "";
|
|
4502
|
+
return {
|
|
4503
|
+
ok: false,
|
|
4504
|
+
error: {
|
|
4505
|
+
code: res.status === 401 || res.status === 403 ? "auth_invalid" : "adapter_dispatch_failed",
|
|
4506
|
+
message: `OpenClaw ${res.status}: ${body.slice(0, 400)}`
|
|
4507
|
+
}
|
|
4508
|
+
};
|
|
4509
|
+
}
|
|
4510
|
+
const json = await res.json();
|
|
4511
|
+
let output = json.choices?.[0]?.message?.content ?? "";
|
|
4512
|
+
const ERR_PREFIX_RE = /^Cannot read properties of undefined \(reading '[^']+'\)\s*\n+/;
|
|
4513
|
+
if (ERR_PREFIX_RE.test(output)) {
|
|
4514
|
+
process.stderr.write(
|
|
4515
|
+
`[openclaw-adapter] stripping known stderr-bleed prefix from chat output (OpenClaw 2026.4.x bug)
|
|
4516
|
+
`
|
|
4517
|
+
);
|
|
4518
|
+
output = output.replace(ERR_PREFIX_RE, "");
|
|
4519
|
+
}
|
|
4520
|
+
return {
|
|
4521
|
+
ok: true,
|
|
4522
|
+
output,
|
|
4523
|
+
metrics: { durationMs: Date.now() - startedAt }
|
|
4524
|
+
};
|
|
4525
|
+
} catch (err) {
|
|
4526
|
+
if (task.signal?.aborted || err?.name === "AbortError") {
|
|
4527
|
+
return { ok: false, error: { code: "task_cancelled", message: "Task cancelled by client" } };
|
|
4528
|
+
}
|
|
4529
|
+
return {
|
|
4530
|
+
ok: false,
|
|
4531
|
+
error: { code: "adapter_dispatch_failed", message: err.message }
|
|
4532
|
+
};
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
};
|
|
4536
|
+
async function checkHealth2(baseUrl, apiKey) {
|
|
4537
|
+
try {
|
|
4538
|
+
const res = await fetch(`${baseUrl}/health`, {
|
|
4539
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
4540
|
+
signal: AbortSignal.timeout(2e3)
|
|
4541
|
+
});
|
|
4542
|
+
return res.ok;
|
|
4543
|
+
} catch {
|
|
4544
|
+
return false;
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
|
|
4548
|
+
// src/daemon/memory/runtime.ts
|
|
4549
|
+
import * as os from "os";
|
|
4550
|
+
import * as path4 from "path";
|
|
4551
|
+
|
|
4552
|
+
// src/daemon/memory/store.ts
|
|
4553
|
+
import Database3 from "better-sqlite3";
|
|
4554
|
+
import * as fs2 from "fs";
|
|
4555
|
+
import * as path3 from "path";
|
|
4556
|
+
import { randomUUID as randomUUID2, createHash as createHash2 } from "crypto";
|
|
4557
|
+
|
|
4558
|
+
// src/daemon/memory/crypto.ts
|
|
4559
|
+
function sealPlaintext(content) {
|
|
4560
|
+
return { kind: "inline", content };
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4563
|
+
// src/daemon/memory/store.ts
|
|
4564
|
+
var SCHEMA_VERSION2 = 1;
|
|
4565
|
+
var SCHEMA_V1_STATEMENTS = [
|
|
4566
|
+
`CREATE TABLE IF NOT EXISTS memory_schema_version (
|
|
4567
|
+
version INTEGER PRIMARY KEY
|
|
4568
|
+
)`,
|
|
4569
|
+
`CREATE TABLE IF NOT EXISTS memory_pages (
|
|
4570
|
+
id TEXT PRIMARY KEY,
|
|
4571
|
+
workspaceId TEXT NOT NULL,
|
|
4572
|
+
path TEXT NOT NULL,
|
|
4573
|
+
title TEXT,
|
|
4574
|
+
description TEXT,
|
|
4884
4575
|
contentHash TEXT NOT NULL,
|
|
4885
4576
|
version INTEGER NOT NULL DEFAULT 1,
|
|
4886
4577
|
pageType TEXT NOT NULL DEFAULT 'leaf',
|
|
@@ -5013,9 +4704,9 @@ var MemoryStore = class {
|
|
|
5013
4704
|
const row = db.prepare("SELECT * FROM memory_pages WHERE workspaceId = ? AND id = ?").get(this.opts.workspaceId, pageId);
|
|
5014
4705
|
return row ? this.rowToPage(row) : null;
|
|
5015
4706
|
}
|
|
5016
|
-
loadContent(pageId,
|
|
4707
|
+
loadContent(pageId, version2) {
|
|
5017
4708
|
const db = this.requireDb();
|
|
5018
|
-
const targetVersion =
|
|
4709
|
+
const targetVersion = version2 ?? db.prepare("SELECT version FROM memory_pages WHERE workspaceId = ? AND id = ?").get(this.opts.workspaceId, pageId)?.version;
|
|
5019
4710
|
if (targetVersion === void 0) return null;
|
|
5020
4711
|
const row = db.prepare(
|
|
5021
4712
|
`SELECT c.pageId, c.version, c.payloadKind, c.payloadValue
|
|
@@ -5053,7 +4744,7 @@ var MemoryStore = class {
|
|
|
5053
4744
|
}
|
|
5054
4745
|
const db = this.requireDb();
|
|
5055
4746
|
const now = Date.now();
|
|
5056
|
-
const contentHash =
|
|
4747
|
+
const contentHash = sha256(input.content);
|
|
5057
4748
|
const payload = sealPlaintext(input.content);
|
|
5058
4749
|
if (payload.kind !== "inline") {
|
|
5059
4750
|
throw new Error("MemoryStore.write: non-inline payload not yet supported in phase-0");
|
|
@@ -5247,8 +4938,8 @@ var MemoryStore = class {
|
|
|
5247
4938
|
};
|
|
5248
4939
|
}
|
|
5249
4940
|
};
|
|
5250
|
-
function
|
|
5251
|
-
return
|
|
4941
|
+
function sha256(s) {
|
|
4942
|
+
return createHash2("sha256").update(s, "utf8").digest("hex");
|
|
5252
4943
|
}
|
|
5253
4944
|
|
|
5254
4945
|
// src/daemon/memory/search.ts
|
|
@@ -6106,8 +5797,8 @@ function finalizeSelected(runtime, workspaceId, paths, snippetMaxBytes = DEFAULT
|
|
|
6106
5797
|
const slot = runtime.peek(workspaceId);
|
|
6107
5798
|
if (!slot) return [];
|
|
6108
5799
|
const out = [];
|
|
6109
|
-
for (const
|
|
6110
|
-
const page = slot.store.loadByPath(
|
|
5800
|
+
for (const path7 of paths) {
|
|
5801
|
+
const page = slot.store.loadByPath(path7);
|
|
6111
5802
|
if (!page) continue;
|
|
6112
5803
|
const content = slot.store.loadContent(page.id)?.content ?? "";
|
|
6113
5804
|
out.push({
|
|
@@ -6382,14 +6073,14 @@ function parsePrismerUri(uri) {
|
|
|
6382
6073
|
const memoryIdx = rest.indexOf(segMemory);
|
|
6383
6074
|
if (memoryIdx <= 0) return null;
|
|
6384
6075
|
const workspaceId = rest.slice(0, memoryIdx);
|
|
6385
|
-
const
|
|
6386
|
-
if (!workspaceId || !
|
|
6387
|
-
return { workspaceId, path:
|
|
6076
|
+
const path7 = rest.slice(memoryIdx + segMemory.length);
|
|
6077
|
+
if (!workspaceId || !path7) return null;
|
|
6078
|
+
return { workspaceId, path: path7 };
|
|
6388
6079
|
}
|
|
6389
6080
|
|
|
6390
6081
|
// src/daemon/asset/metadata-index.ts
|
|
6391
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as
|
|
6392
|
-
import { join as
|
|
6082
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
6083
|
+
import { join as join8 } from "path";
|
|
6393
6084
|
var DEFAULT_LIMIT = 8;
|
|
6394
6085
|
var PULL_PAGE_SIZE = 500;
|
|
6395
6086
|
var THROTTLE_MS = 3e4;
|
|
@@ -6420,13 +6111,13 @@ var AssetMetadataIndex = class {
|
|
|
6420
6111
|
if (!existsSync7(opts.workspaceStateDir)) {
|
|
6421
6112
|
mkdirSync7(opts.workspaceStateDir, { recursive: true });
|
|
6422
6113
|
}
|
|
6423
|
-
this.cursorPath =
|
|
6114
|
+
this.cursorPath = join8(opts.workspaceStateDir, "asset-metadata-cursor.json");
|
|
6424
6115
|
}
|
|
6425
6116
|
/** Persisted cursor for this workspace, or 0 on first run / corrupted file. */
|
|
6426
6117
|
readCursor() {
|
|
6427
6118
|
if (!existsSync7(this.cursorPath)) return 0;
|
|
6428
6119
|
try {
|
|
6429
|
-
const parsed = JSON.parse(
|
|
6120
|
+
const parsed = JSON.parse(readFileSync5(this.cursorPath, "utf8"));
|
|
6430
6121
|
if (parsed.workspaceId !== this.workspaceId) return 0;
|
|
6431
6122
|
return parsed.cursor;
|
|
6432
6123
|
} catch {
|
|
@@ -6479,13 +6170,9 @@ var AssetMetadataIndex = class {
|
|
|
6479
6170
|
let applied = 0;
|
|
6480
6171
|
const tx = this.db.transaction((items) => {
|
|
6481
6172
|
for (const item of items) {
|
|
6482
|
-
const assetId = item.assetId ?? item.id;
|
|
6483
|
-
if (!assetId) {
|
|
6484
|
-
throw new Error("Asset metadata index item missing assetId/id");
|
|
6485
|
-
}
|
|
6486
6173
|
upsert.run(
|
|
6487
6174
|
this.workspaceId,
|
|
6488
|
-
assetId,
|
|
6175
|
+
item.assetId,
|
|
6489
6176
|
item.contentHash,
|
|
6490
6177
|
item.filename ?? null,
|
|
6491
6178
|
item.folderPath ?? null,
|
|
@@ -6530,14 +6217,6 @@ var AssetMetadataIndex = class {
|
|
|
6530
6217
|
const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND filename = ?").get(this.workspaceId, filename);
|
|
6531
6218
|
return row ? rowToMetadata(row) : void 0;
|
|
6532
6219
|
}
|
|
6533
|
-
resolveByAssetId(assetId) {
|
|
6534
|
-
const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND asset_id = ?").get(this.workspaceId, assetId);
|
|
6535
|
-
return row ? rowToMetadata(row) : void 0;
|
|
6536
|
-
}
|
|
6537
|
-
resolveByContentHash(contentHash) {
|
|
6538
|
-
const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND content_hash = ?").get(this.workspaceId, contentHash);
|
|
6539
|
-
return row ? rowToMetadata(row) : void 0;
|
|
6540
|
-
}
|
|
6541
6220
|
/** Batch exact match — returns Map for O(1) access. */
|
|
6542
6221
|
resolveByFilenames(filenames) {
|
|
6543
6222
|
if (filenames.length === 0) return /* @__PURE__ */ new Map();
|
|
@@ -6556,10 +6235,7 @@ var AssetMetadataIndex = class {
|
|
|
6556
6235
|
};
|
|
6557
6236
|
|
|
6558
6237
|
// src/daemon/asset/rpc.ts
|
|
6559
|
-
import { closeSync, openSync, readSync, statSync as statSync2 } from "fs";
|
|
6560
6238
|
var ASSET_PATH_PREFIX = "/local/asset/";
|
|
6561
|
-
var MAX_READ_BYTES = 256 * 1024;
|
|
6562
|
-
var MAX_SEARCH_BYTES = 1024 * 1024;
|
|
6563
6239
|
function attachAssetRpc(opts) {
|
|
6564
6240
|
return async (req, res) => {
|
|
6565
6241
|
const url = req.url ?? "/";
|
|
@@ -6570,17 +6246,10 @@ function attachAssetRpc(opts) {
|
|
|
6570
6246
|
try {
|
|
6571
6247
|
if (method === "POST" && subpath === "search") {
|
|
6572
6248
|
const body = await readJson3(req);
|
|
6573
|
-
return
|
|
6249
|
+
return handleSearch2(opts.resolveIndex, body, res);
|
|
6574
6250
|
}
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
return handleDescribe(opts, body, res);
|
|
6578
|
-
}
|
|
6579
|
-
if (method === "POST" && subpath === "read") {
|
|
6580
|
-
const body = await readJson3(req);
|
|
6581
|
-
return await handleRead(opts, body, res);
|
|
6582
|
-
}
|
|
6583
|
-
return false;
|
|
6251
|
+
respond3(res, 404, { error: "asset_route_not_found", path: url });
|
|
6252
|
+
return true;
|
|
6584
6253
|
} catch (err) {
|
|
6585
6254
|
respond3(res, 500, {
|
|
6586
6255
|
error: "asset_rpc_failed",
|
|
@@ -6590,21 +6259,18 @@ function attachAssetRpc(opts) {
|
|
|
6590
6259
|
}
|
|
6591
6260
|
};
|
|
6592
6261
|
}
|
|
6593
|
-
|
|
6262
|
+
function handleSearch2(resolveIndex, body, res) {
|
|
6594
6263
|
if (!body || typeof body !== "object") {
|
|
6595
6264
|
return respond4002(res, "request body must be a JSON object");
|
|
6596
6265
|
}
|
|
6597
6266
|
const b = body;
|
|
6598
|
-
if (typeof b.query !== "string" || !b.query.trim()) {
|
|
6599
|
-
return respond4002(res, "query is required (non-empty string)");
|
|
6600
|
-
}
|
|
6601
|
-
if (typeof b.uri === "string" && b.uri.trim()) {
|
|
6602
|
-
return searchAssetContent(opts, b.uri.trim(), b.query.trim(), res);
|
|
6603
|
-
}
|
|
6604
6267
|
if (typeof b.workspaceId !== "string" || !b.workspaceId) {
|
|
6605
6268
|
return respond4002(res, "workspaceId is required (string)");
|
|
6606
6269
|
}
|
|
6607
|
-
|
|
6270
|
+
if (typeof b.query !== "string" || !b.query.trim()) {
|
|
6271
|
+
return respond4002(res, "query is required (non-empty string)");
|
|
6272
|
+
}
|
|
6273
|
+
const index = resolveIndex(b.workspaceId);
|
|
6608
6274
|
if (!index) {
|
|
6609
6275
|
respond3(res, 404, {
|
|
6610
6276
|
error: "workspace_index_not_found",
|
|
@@ -6618,124 +6284,6 @@ async function handleSearch2(opts, body, res) {
|
|
|
6618
6284
|
respond3(res, 200, { items });
|
|
6619
6285
|
return true;
|
|
6620
6286
|
}
|
|
6621
|
-
function handleDescribe(opts, body, res) {
|
|
6622
|
-
const resolved = resolveAssetRef(opts, body);
|
|
6623
|
-
if ("error" in resolved) return respond4002(res, resolved.error);
|
|
6624
|
-
const cached = opts.assetCache?.get(resolved.row.contentHash);
|
|
6625
|
-
respond3(res, 200, {
|
|
6626
|
-
asset: resolved.row,
|
|
6627
|
-
uri: `prismer://workspace/${resolved.workspaceId}/asset/${resolved.row.contentHash}`,
|
|
6628
|
-
cache: cached ? { status: "hit", localPath: cached.localPath, sizeBytes: cached.sizeBytes, lastUsedAt: cached.lastUsedAt } : { status: "miss" },
|
|
6629
|
-
tools: ["asset.describe", "asset.read", "asset.search"]
|
|
6630
|
-
});
|
|
6631
|
-
return true;
|
|
6632
|
-
}
|
|
6633
|
-
async function handleRead(opts, body, res) {
|
|
6634
|
-
const resolved = resolveAssetRef(opts, body);
|
|
6635
|
-
if ("error" in resolved) return respond4002(res, resolved.error);
|
|
6636
|
-
if (!opts.assetCache) {
|
|
6637
|
-
respond3(res, 503, { error: "asset_cache_unavailable" });
|
|
6638
|
-
return true;
|
|
6639
|
-
}
|
|
6640
|
-
const b = body;
|
|
6641
|
-
const offset = typeof b.offset === "number" && b.offset > 0 ? Math.floor(b.offset) : 0;
|
|
6642
|
-
const requested = typeof b.length === "number" && b.length > 0 ? Math.floor(b.length) : MAX_READ_BYTES;
|
|
6643
|
-
const length = Math.min(requested, MAX_READ_BYTES);
|
|
6644
|
-
const cached = await opts.assetCache.getOrFetch(resolved.row.contentHash, {
|
|
6645
|
-
workspaceIdHint: resolved.workspaceId,
|
|
6646
|
-
assetId: resolved.row.assetId
|
|
6647
|
-
});
|
|
6648
|
-
const chunk = readFileRange(cached.localPath, offset, length);
|
|
6649
|
-
const textLike = isTextLike(resolved.row);
|
|
6650
|
-
respond3(res, 200, {
|
|
6651
|
-
asset: resolved.row,
|
|
6652
|
-
locator: { byteOffset: offset, byteLength: chunk.bytesRead },
|
|
6653
|
-
truncated: requested > MAX_READ_BYTES || offset + chunk.bytesRead < chunk.sizeBytes,
|
|
6654
|
-
encoding: textLike ? "utf8" : "base64",
|
|
6655
|
-
content: textLike ? chunk.buffer.toString("utf8") : chunk.buffer.toString("base64")
|
|
6656
|
-
});
|
|
6657
|
-
return true;
|
|
6658
|
-
}
|
|
6659
|
-
async function searchAssetContent(opts, uri, query, res) {
|
|
6660
|
-
const resolved = resolveAssetRef(opts, { uri });
|
|
6661
|
-
if ("error" in resolved) return respond4002(res, resolved.error);
|
|
6662
|
-
if (!opts.assetCache) {
|
|
6663
|
-
respond3(res, 503, { error: "asset_cache_unavailable" });
|
|
6664
|
-
return true;
|
|
6665
|
-
}
|
|
6666
|
-
if (!isTextLike(resolved.row)) {
|
|
6667
|
-
respond3(res, 200, {
|
|
6668
|
-
items: [],
|
|
6669
|
-
unsupported: true,
|
|
6670
|
-
reason: "content search currently supports text-like assets only",
|
|
6671
|
-
asset: resolved.row
|
|
6672
|
-
});
|
|
6673
|
-
return true;
|
|
6674
|
-
}
|
|
6675
|
-
const cached = await opts.assetCache.getOrFetch(resolved.row.contentHash, {
|
|
6676
|
-
workspaceIdHint: resolved.workspaceId,
|
|
6677
|
-
assetId: resolved.row.assetId
|
|
6678
|
-
});
|
|
6679
|
-
const chunk = readFileRange(cached.localPath, 0, MAX_SEARCH_BYTES);
|
|
6680
|
-
const text = chunk.buffer.toString("utf8");
|
|
6681
|
-
const needle = query.toLowerCase();
|
|
6682
|
-
const lines = text.split(/\r?\n/);
|
|
6683
|
-
const items = [];
|
|
6684
|
-
let byteOffset = 0;
|
|
6685
|
-
for (let i = 0; i < lines.length && items.length < 20; i += 1) {
|
|
6686
|
-
const line = lines[i] ?? "";
|
|
6687
|
-
if (line.toLowerCase().includes(needle)) {
|
|
6688
|
-
items.push({ line: i + 1, byteOffset, preview: line.slice(0, 500) });
|
|
6689
|
-
}
|
|
6690
|
-
byteOffset += Buffer.byteLength(line) + 1;
|
|
6691
|
-
}
|
|
6692
|
-
respond3(res, 200, {
|
|
6693
|
-
items,
|
|
6694
|
-
asset: resolved.row,
|
|
6695
|
-
searched: { byteOffset: 0, byteLength: chunk.bytesRead, truncated: chunk.bytesRead < chunk.sizeBytes }
|
|
6696
|
-
});
|
|
6697
|
-
return true;
|
|
6698
|
-
}
|
|
6699
|
-
function resolveAssetRef(opts, body) {
|
|
6700
|
-
if (!body || typeof body !== "object") return { error: "request body must be a JSON object" };
|
|
6701
|
-
const b = body;
|
|
6702
|
-
let workspaceId = typeof b.workspaceId === "string" ? b.workspaceId : "";
|
|
6703
|
-
let assetId = typeof b.assetId === "string" ? b.assetId : "";
|
|
6704
|
-
let contentHash = typeof b.contentHash === "string" ? b.contentHash : "";
|
|
6705
|
-
if (typeof b.uri === "string" && b.uri.trim()) {
|
|
6706
|
-
const [uri] = parseUris(b.uri.trim());
|
|
6707
|
-
if (!uri) return { error: "uri must be prismer://workspace/<workspaceId>/asset/<hash>" };
|
|
6708
|
-
if (uri.type !== "asset") return { error: "file uri lookup is not available in this RPC yet; pass asset uri or assetId" };
|
|
6709
|
-
workspaceId = uri.workspaceId ?? workspaceId;
|
|
6710
|
-
contentHash = uri.rest;
|
|
6711
|
-
}
|
|
6712
|
-
if (!workspaceId) return { error: "workspaceId is required (string)" };
|
|
6713
|
-
const index = opts.resolveIndex(workspaceId);
|
|
6714
|
-
if (!index) return { error: "workspace asset index is not synced yet" };
|
|
6715
|
-
const row = assetId ? index.resolveByAssetId(assetId) : contentHash ? index.resolveByContentHash(contentHash) : void 0;
|
|
6716
|
-
if (!row) return { error: "asset not found in local metadata index" };
|
|
6717
|
-
return { workspaceId, row };
|
|
6718
|
-
}
|
|
6719
|
-
function isTextLike(row) {
|
|
6720
|
-
const mime = row.mime ?? "";
|
|
6721
|
-
const name = row.filename ?? "";
|
|
6722
|
-
return mime.startsWith("text/") || mime.includes("json") || mime.includes("xml") || mime.includes("markdown") || mime.includes("csv") || /\.(md|markdown|txt|json|csv|tsv|ndjson|js|jsx|ts|tsx|py|go|rs|java|kt|swift|rb|php|css|html|htm|xhtml|sql|yaml|yml|toml|log)$/i.test(
|
|
6723
|
-
name
|
|
6724
|
-
);
|
|
6725
|
-
}
|
|
6726
|
-
function readFileRange(path9, offset, length) {
|
|
6727
|
-
const sizeBytes = statSync2(path9).size;
|
|
6728
|
-
if (offset >= sizeBytes) return { buffer: Buffer.alloc(0), bytesRead: 0, sizeBytes };
|
|
6729
|
-
const fd = openSync(path9, "r");
|
|
6730
|
-
try {
|
|
6731
|
-
const target = Math.min(length, sizeBytes - offset);
|
|
6732
|
-
const buffer = Buffer.alloc(target);
|
|
6733
|
-
const bytesRead = readSync(fd, buffer, 0, target, offset);
|
|
6734
|
-
return { buffer: buffer.subarray(0, bytesRead), bytesRead, sizeBytes };
|
|
6735
|
-
} finally {
|
|
6736
|
-
closeSync(fd);
|
|
6737
|
-
}
|
|
6738
|
-
}
|
|
6739
6287
|
function respond3(res, status, body) {
|
|
6740
6288
|
res.statusCode = status;
|
|
6741
6289
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -6757,532 +6305,9 @@ async function readJson3(req) {
|
|
|
6757
6305
|
}
|
|
6758
6306
|
}
|
|
6759
6307
|
|
|
6760
|
-
// src/daemon/
|
|
6308
|
+
// src/daemon/outbox-watcher.ts
|
|
6761
6309
|
import { promises as fs3 } from "fs";
|
|
6762
|
-
import { homedir as homedir5 } from "os";
|
|
6763
6310
|
import * as path5 from "path";
|
|
6764
|
-
var DropFolderAdapter = class {
|
|
6765
|
-
kind = "drop-folder";
|
|
6766
|
-
workspaceDir;
|
|
6767
|
-
rootDir;
|
|
6768
|
-
constructor(opts = {}) {
|
|
6769
|
-
this.workspaceDir = opts.workspaceDir;
|
|
6770
|
-
this.rootDir = opts.rootDir ?? path5.join(opts.homeDir ?? homedir5(), ".prismer");
|
|
6771
|
-
}
|
|
6772
|
-
/**
|
|
6773
|
-
* Long-lived async iterable wrapping `scanOnce` on a 1s polling interval.
|
|
6774
|
-
* Stops when the iterator is broken (e.g. consumer returns / throws).
|
|
6775
|
-
*/
|
|
6776
|
-
async *observe(workspaceId) {
|
|
6777
|
-
while (true) {
|
|
6778
|
-
const batch = await this.scanOnce(workspaceId);
|
|
6779
|
-
for (const obs of batch) yield obs;
|
|
6780
|
-
await sleep(1e3);
|
|
6781
|
-
}
|
|
6782
|
-
}
|
|
6783
|
-
/**
|
|
6784
|
-
* Single-tick scan exposed for tests and one-shot CLI usage. Reads every
|
|
6785
|
-
* file currently under the workspace's drop directory, returning one
|
|
6786
|
-
* observation per file. The caller is expected to feed each into the
|
|
6787
|
-
* outbox; idempotency on the outbox side collapses re-observations of
|
|
6788
|
-
* unchanged files.
|
|
6789
|
-
*/
|
|
6790
|
-
async scanOnce(workspaceId) {
|
|
6791
|
-
const drop = this.dropDir(workspaceId);
|
|
6792
|
-
let entries = [];
|
|
6793
|
-
try {
|
|
6794
|
-
entries = await fs3.readdir(drop);
|
|
6795
|
-
} catch (err) {
|
|
6796
|
-
if (err.code === "ENOENT") return [];
|
|
6797
|
-
throw err;
|
|
6798
|
-
}
|
|
6799
|
-
const out = [];
|
|
6800
|
-
for (const name of entries) {
|
|
6801
|
-
if (name.startsWith(".")) continue;
|
|
6802
|
-
const fullPath = path5.join(drop, name);
|
|
6803
|
-
const stat = await fs3.lstat(fullPath).catch(() => null);
|
|
6804
|
-
if (!stat) continue;
|
|
6805
|
-
if (stat.isSymbolicLink()) continue;
|
|
6806
|
-
if (stat.isDirectory()) {
|
|
6807
|
-
for (const nested of await walk(fullPath)) {
|
|
6808
|
-
const nstat = await fs3.lstat(nested).catch(() => null);
|
|
6809
|
-
if (!nstat || !nstat.isFile()) continue;
|
|
6810
|
-
out.push(makeObservation(workspaceId, nested, drop, nstat.size, Math.floor(nstat.mtimeMs)));
|
|
6811
|
-
}
|
|
6812
|
-
continue;
|
|
6813
|
-
}
|
|
6814
|
-
if (!stat.isFile()) continue;
|
|
6815
|
-
out.push(makeObservation(workspaceId, fullPath, drop, stat.size, Math.floor(stat.mtimeMs)));
|
|
6816
|
-
}
|
|
6817
|
-
return out;
|
|
6818
|
-
}
|
|
6819
|
-
async identifySource(obs) {
|
|
6820
|
-
const detail = obs.detail;
|
|
6821
|
-
const rel = path5.relative(detail.watchDir, detail.path);
|
|
6822
|
-
const folderDir = path5.dirname(rel);
|
|
6823
|
-
return {
|
|
6824
|
-
sourceRef: `folder://${obs.workspaceId}/${rel.split(path5.sep).join("/")}`,
|
|
6825
|
-
hints: {
|
|
6826
|
-
filename: path5.basename(detail.path),
|
|
6827
|
-
folderPath: folderDir === "." ? "" : folderDir.split(path5.sep).join("/")
|
|
6828
|
-
}
|
|
6829
|
-
};
|
|
6830
|
-
}
|
|
6831
|
-
async fetch(obs) {
|
|
6832
|
-
const detail = obs.detail;
|
|
6833
|
-
const bytes = await fs3.readFile(detail.path);
|
|
6834
|
-
return {
|
|
6835
|
-
bytes,
|
|
6836
|
-
mime: mimeFromExtension(detail.path),
|
|
6837
|
-
size: bytes.length
|
|
6838
|
-
};
|
|
6839
|
-
}
|
|
6840
|
-
/** Hook called by UploadRunner after a successful upload of one observation. */
|
|
6841
|
-
async onUploadSuccess(obs) {
|
|
6842
|
-
await this.moveTo(obs, this.uploadedDir(obs.workspaceId));
|
|
6843
|
-
}
|
|
6844
|
-
/** Hook called by UploadRunner after attempts hit maxAttempts. */
|
|
6845
|
-
async onUploadFailure(obs) {
|
|
6846
|
-
await this.moveTo(obs, this.failedDir(obs.workspaceId));
|
|
6847
|
-
}
|
|
6848
|
-
async moveTo(obs, destDir) {
|
|
6849
|
-
const detail = obs.detail;
|
|
6850
|
-
if (!detail || typeof detail.path !== "string") return;
|
|
6851
|
-
try {
|
|
6852
|
-
const rel = typeof detail.watchDir === "string" ? path5.relative(detail.watchDir, detail.path) : path5.basename(detail.path);
|
|
6853
|
-
const safeRel = rel && !rel.startsWith("..") && !path5.isAbsolute(rel) ? rel : path5.basename(detail.path);
|
|
6854
|
-
const destPath = path5.join(destDir, safeRel);
|
|
6855
|
-
await fs3.mkdir(path5.dirname(destPath), { recursive: true });
|
|
6856
|
-
await fs3.rename(detail.path, destPath);
|
|
6857
|
-
} catch (err) {
|
|
6858
|
-
if (err.code === "ENOENT") return;
|
|
6859
|
-
throw err;
|
|
6860
|
-
}
|
|
6861
|
-
}
|
|
6862
|
-
dropDir(workspaceId) {
|
|
6863
|
-
if (this.workspaceDir) return path5.join(this.workspaceDir, "drop");
|
|
6864
|
-
return path5.join(this.rootDir, "workspaces", workspaceId, "drop");
|
|
6865
|
-
}
|
|
6866
|
-
uploadedDir(workspaceId) {
|
|
6867
|
-
if (this.workspaceDir) return path5.join(this.workspaceDir, "uploaded");
|
|
6868
|
-
return path5.join(this.rootDir, "workspaces", workspaceId, "uploaded");
|
|
6869
|
-
}
|
|
6870
|
-
failedDir(workspaceId) {
|
|
6871
|
-
if (this.workspaceDir) return path5.join(this.workspaceDir, "upload-failed");
|
|
6872
|
-
return path5.join(this.rootDir, "workspaces", workspaceId, "upload-failed");
|
|
6873
|
-
}
|
|
6874
|
-
};
|
|
6875
|
-
function makeObservation(workspaceId, filePath, watchDir, size, mtime) {
|
|
6876
|
-
return {
|
|
6877
|
-
workspaceId,
|
|
6878
|
-
detail: { path: filePath, watchDir, size, mtime },
|
|
6879
|
-
observedAt: mtime
|
|
6880
|
-
};
|
|
6881
|
-
}
|
|
6882
|
-
async function walk(root) {
|
|
6883
|
-
const out = [];
|
|
6884
|
-
const entries = await fs3.readdir(root).catch(() => []);
|
|
6885
|
-
for (const name of entries) {
|
|
6886
|
-
if (name.startsWith(".")) continue;
|
|
6887
|
-
const full = path5.join(root, name);
|
|
6888
|
-
const stat = await fs3.lstat(full).catch(() => null);
|
|
6889
|
-
if (!stat) continue;
|
|
6890
|
-
if (stat.isSymbolicLink()) continue;
|
|
6891
|
-
if (stat.isDirectory()) {
|
|
6892
|
-
out.push(...await walk(full));
|
|
6893
|
-
} else if (stat.isFile()) {
|
|
6894
|
-
out.push(full);
|
|
6895
|
-
}
|
|
6896
|
-
}
|
|
6897
|
-
return out;
|
|
6898
|
-
}
|
|
6899
|
-
var MIME_BY_EXT = {
|
|
6900
|
-
".txt": "text/plain",
|
|
6901
|
-
".md": "text/markdown",
|
|
6902
|
-
".csv": "text/csv",
|
|
6903
|
-
".json": "application/json",
|
|
6904
|
-
".yaml": "application/yaml",
|
|
6905
|
-
".yml": "application/yaml",
|
|
6906
|
-
".pdf": "application/pdf",
|
|
6907
|
-
".png": "image/png",
|
|
6908
|
-
".jpg": "image/jpeg",
|
|
6909
|
-
".jpeg": "image/jpeg",
|
|
6910
|
-
".gif": "image/gif",
|
|
6911
|
-
".webp": "image/webp",
|
|
6912
|
-
".svg": "image/svg+xml",
|
|
6913
|
-
".html": "text/html",
|
|
6914
|
-
".htm": "text/html",
|
|
6915
|
-
".xhtml": "application/xhtml+xml",
|
|
6916
|
-
".xml": "application/xml",
|
|
6917
|
-
".zip": "application/zip",
|
|
6918
|
-
".tar": "application/x-tar",
|
|
6919
|
-
".gz": "application/gzip",
|
|
6920
|
-
".mp3": "audio/mpeg",
|
|
6921
|
-
".mp4": "video/mp4",
|
|
6922
|
-
".mov": "video/quicktime",
|
|
6923
|
-
".wav": "audio/wav"
|
|
6924
|
-
};
|
|
6925
|
-
function mimeFromExtension(p) {
|
|
6926
|
-
const ext = path5.extname(p).toLowerCase();
|
|
6927
|
-
return MIME_BY_EXT[ext] ?? null;
|
|
6928
|
-
}
|
|
6929
|
-
function sleep(ms) {
|
|
6930
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
6931
|
-
}
|
|
6932
|
-
|
|
6933
|
-
// src/daemon/asset/origin/outbox.ts
|
|
6934
|
-
import Database4 from "better-sqlite3";
|
|
6935
|
-
import { createHash as createHash5, randomUUID as randomUUID5 } from "crypto";
|
|
6936
|
-
import { z as z7 } from "zod";
|
|
6937
|
-
var OriginObservationRecordSchema = z7.object({
|
|
6938
|
-
workspaceId: z7.string().min(1),
|
|
6939
|
-
originKind: z7.enum(["upload", "drop-folder", "agent-gen"]),
|
|
6940
|
-
sourceRef: z7.string().min(1),
|
|
6941
|
-
payloadJson: z7.string().min(2),
|
|
6942
|
-
hintsJson: z7.string().min(2),
|
|
6943
|
-
observedAt: z7.number().int().nonnegative()
|
|
6944
|
-
});
|
|
6945
|
-
var DDL_STATEMENTS = [
|
|
6946
|
-
`CREATE TABLE IF NOT EXISTS asset_origin_outbox (
|
|
6947
|
-
id TEXT PRIMARY KEY,
|
|
6948
|
-
workspaceId TEXT NOT NULL,
|
|
6949
|
-
originKind TEXT NOT NULL,
|
|
6950
|
-
sourceRef TEXT NOT NULL,
|
|
6951
|
-
payloadJson TEXT NOT NULL,
|
|
6952
|
-
hintsJson TEXT NOT NULL,
|
|
6953
|
-
observedAt INTEGER NOT NULL,
|
|
6954
|
-
idempotencyKey TEXT NOT NULL UNIQUE,
|
|
6955
|
-
status TEXT NOT NULL CHECK (status IN ('pending', 'claimed', 'uploaded')),
|
|
6956
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
6957
|
-
lastError TEXT,
|
|
6958
|
-
assetId TEXT,
|
|
6959
|
-
contentHash TEXT,
|
|
6960
|
-
createdAt INTEGER NOT NULL,
|
|
6961
|
-
updatedAt INTEGER NOT NULL
|
|
6962
|
-
)`,
|
|
6963
|
-
`CREATE INDEX IF NOT EXISTS idx_asset_origin_outbox_status
|
|
6964
|
-
ON asset_origin_outbox(status, createdAt)`,
|
|
6965
|
-
`CREATE TABLE IF NOT EXISTS asset_origin_outbox_dead_letter (
|
|
6966
|
-
id TEXT PRIMARY KEY,
|
|
6967
|
-
workspaceId TEXT NOT NULL,
|
|
6968
|
-
originKind TEXT NOT NULL,
|
|
6969
|
-
sourceRef TEXT NOT NULL,
|
|
6970
|
-
payloadJson TEXT NOT NULL,
|
|
6971
|
-
hintsJson TEXT NOT NULL,
|
|
6972
|
-
observedAt INTEGER NOT NULL,
|
|
6973
|
-
errorJson TEXT NOT NULL,
|
|
6974
|
-
attempts INTEGER NOT NULL,
|
|
6975
|
-
createdAt INTEGER NOT NULL
|
|
6976
|
-
)`
|
|
6977
|
-
];
|
|
6978
|
-
var OriginOutbox = class {
|
|
6979
|
-
db;
|
|
6980
|
-
constructor(opts) {
|
|
6981
|
-
this.db = new Database4(opts.dbPath);
|
|
6982
|
-
this.db.pragma("journal_mode = WAL");
|
|
6983
|
-
for (const ddl of DDL_STATEMENTS) this.db.prepare(ddl).run();
|
|
6984
|
-
}
|
|
6985
|
-
/**
|
|
6986
|
-
* Validate + persist an observation. Schema-invalid input lands in
|
|
6987
|
-
* `asset_origin_outbox_dead_letter` (never silently dropped). Re-enqueue
|
|
6988
|
-
* with the same {originKind, workspaceId, sourceRef, observedAt} returns
|
|
6989
|
-
* the existing row id without inserting again.
|
|
6990
|
-
*/
|
|
6991
|
-
enqueue(record) {
|
|
6992
|
-
const now = Date.now();
|
|
6993
|
-
const parsed = OriginObservationRecordSchema.safeParse(record);
|
|
6994
|
-
if (!parsed.success) {
|
|
6995
|
-
const dlId = `dl_${randomUUID5()}`;
|
|
6996
|
-
const raw = record && typeof record === "object" ? record : {};
|
|
6997
|
-
this.db.prepare(
|
|
6998
|
-
`INSERT INTO asset_origin_outbox_dead_letter
|
|
6999
|
-
(id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt, errorJson, attempts, createdAt)
|
|
7000
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
7001
|
-
).run(
|
|
7002
|
-
dlId,
|
|
7003
|
-
coerceToString(raw.workspaceId, ""),
|
|
7004
|
-
coerceToString(raw.originKind, ""),
|
|
7005
|
-
coerceToString(raw.sourceRef, ""),
|
|
7006
|
-
coerceToJson(raw.payloadJson),
|
|
7007
|
-
coerceToJson(raw.hintsJson),
|
|
7008
|
-
typeof raw.observedAt === "number" ? raw.observedAt : 0,
|
|
7009
|
-
JSON.stringify(parsed.error.errors),
|
|
7010
|
-
0,
|
|
7011
|
-
now
|
|
7012
|
-
);
|
|
7013
|
-
return { id: dlId, deadLetter: true };
|
|
7014
|
-
}
|
|
7015
|
-
const validated = parsed.data;
|
|
7016
|
-
const idempotencyKey = computeIdempotencyKey(validated);
|
|
7017
|
-
const rowId = `out_${randomUUID5()}`;
|
|
7018
|
-
try {
|
|
7019
|
-
this.db.prepare(
|
|
7020
|
-
`INSERT INTO asset_origin_outbox
|
|
7021
|
-
(id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt,
|
|
7022
|
-
idempotencyKey, status, attempts, createdAt, updatedAt)
|
|
7023
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, ?)`
|
|
7024
|
-
).run(
|
|
7025
|
-
rowId,
|
|
7026
|
-
validated.workspaceId,
|
|
7027
|
-
validated.originKind,
|
|
7028
|
-
validated.sourceRef,
|
|
7029
|
-
validated.payloadJson,
|
|
7030
|
-
validated.hintsJson,
|
|
7031
|
-
validated.observedAt,
|
|
7032
|
-
idempotencyKey,
|
|
7033
|
-
now,
|
|
7034
|
-
now
|
|
7035
|
-
);
|
|
7036
|
-
return { id: rowId, deadLetter: false };
|
|
7037
|
-
} catch (err) {
|
|
7038
|
-
const code = err.code;
|
|
7039
|
-
if (code === "SQLITE_CONSTRAINT_UNIQUE") {
|
|
7040
|
-
const existing = this.db.prepare("SELECT id FROM asset_origin_outbox WHERE idempotencyKey = ?").get(idempotencyKey);
|
|
7041
|
-
if (existing) return { id: existing.id, deadLetter: false };
|
|
7042
|
-
}
|
|
7043
|
-
throw err;
|
|
7044
|
-
}
|
|
7045
|
-
}
|
|
7046
|
-
/**
|
|
7047
|
-
* Atomically claim the oldest pending row, transition status to 'claimed',
|
|
7048
|
-
* and return it. Returns null when the queue is empty. The caller MUST
|
|
7049
|
-
* follow up with `markUploaded` (success) or `recordFailure` (error) so
|
|
7050
|
-
* the row doesn't sit in 'claimed' forever — Phase-0 is a single worker
|
|
7051
|
-
* so we don't need a claim-expiry sweeper yet.
|
|
7052
|
-
*/
|
|
7053
|
-
claimNext() {
|
|
7054
|
-
const pickAndClaim = this.db.transaction(() => {
|
|
7055
|
-
const row = this.db.prepare(
|
|
7056
|
-
`SELECT id, workspaceId, originKind, sourceRef, payloadJson, hintsJson,
|
|
7057
|
-
observedAt, attempts, lastError, createdAt
|
|
7058
|
-
FROM asset_origin_outbox
|
|
7059
|
-
WHERE status = 'pending'
|
|
7060
|
-
ORDER BY createdAt ASC
|
|
7061
|
-
LIMIT 1`
|
|
7062
|
-
).get();
|
|
7063
|
-
if (!row) return null;
|
|
7064
|
-
this.db.prepare("UPDATE asset_origin_outbox SET status = 'claimed', updatedAt = ? WHERE id = ?").run(Date.now(), row.id);
|
|
7065
|
-
return row;
|
|
7066
|
-
});
|
|
7067
|
-
const claimed = pickAndClaim();
|
|
7068
|
-
if (!claimed) return null;
|
|
7069
|
-
return {
|
|
7070
|
-
id: claimed.id,
|
|
7071
|
-
row: rawToRow(claimed)
|
|
7072
|
-
};
|
|
7073
|
-
}
|
|
7074
|
-
markUploaded(id, meta) {
|
|
7075
|
-
this.db.prepare(
|
|
7076
|
-
`UPDATE asset_origin_outbox
|
|
7077
|
-
SET status = 'uploaded', assetId = ?, contentHash = ?, updatedAt = ?
|
|
7078
|
-
WHERE id = ?`
|
|
7079
|
-
).run(meta.assetId, meta.contentHash, Date.now(), id);
|
|
7080
|
-
}
|
|
7081
|
-
/**
|
|
7082
|
-
* Record one failed upload attempt. Increments `attempts`, stores the
|
|
7083
|
-
* latest error, and either re-queues for retry (status -> 'pending') or
|
|
7084
|
-
* spills to dead-letter once attempts hit maxAttempts.
|
|
7085
|
-
*/
|
|
7086
|
-
recordFailure(id, error, opts) {
|
|
7087
|
-
const now = Date.now();
|
|
7088
|
-
const updated = this.db.transaction(() => {
|
|
7089
|
-
const row = this.db.prepare(
|
|
7090
|
-
`SELECT id, workspaceId, originKind, sourceRef, payloadJson, hintsJson,
|
|
7091
|
-
observedAt, attempts, createdAt
|
|
7092
|
-
FROM asset_origin_outbox
|
|
7093
|
-
WHERE id = ?`
|
|
7094
|
-
).get(id);
|
|
7095
|
-
if (!row) return;
|
|
7096
|
-
const nextAttempts = row.attempts + 1;
|
|
7097
|
-
if (nextAttempts >= opts.maxAttempts) {
|
|
7098
|
-
this.db.prepare(
|
|
7099
|
-
`INSERT INTO asset_origin_outbox_dead_letter
|
|
7100
|
-
(id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt, errorJson, attempts, createdAt)
|
|
7101
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
7102
|
-
).run(
|
|
7103
|
-
`dl_${randomUUID5()}`,
|
|
7104
|
-
row.workspaceId,
|
|
7105
|
-
row.originKind,
|
|
7106
|
-
row.sourceRef,
|
|
7107
|
-
row.payloadJson,
|
|
7108
|
-
row.hintsJson,
|
|
7109
|
-
row.observedAt,
|
|
7110
|
-
JSON.stringify({ message: error, attempts: nextAttempts }),
|
|
7111
|
-
nextAttempts,
|
|
7112
|
-
now
|
|
7113
|
-
);
|
|
7114
|
-
this.db.prepare("DELETE FROM asset_origin_outbox WHERE id = ?").run(id);
|
|
7115
|
-
} else {
|
|
7116
|
-
this.db.prepare(
|
|
7117
|
-
`UPDATE asset_origin_outbox
|
|
7118
|
-
SET status = 'pending', attempts = ?, lastError = ?, updatedAt = ?
|
|
7119
|
-
WHERE id = ?`
|
|
7120
|
-
).run(nextAttempts, error, now, id);
|
|
7121
|
-
}
|
|
7122
|
-
});
|
|
7123
|
-
updated();
|
|
7124
|
-
}
|
|
7125
|
-
/**
|
|
7126
|
-
* Re-promote any rows orphaned in `'claimed'` state back to `'pending'`.
|
|
7127
|
-
*
|
|
7128
|
-
* Why this exists: claimNext() transitions a row to 'claimed' under a
|
|
7129
|
-
* transaction, but the upload that follows runs OUTSIDE the transaction
|
|
7130
|
-
* (network call). If the daemon dies between the claim and the
|
|
7131
|
-
* markUploaded/recordFailure call, the row is stuck — claimNext() only
|
|
7132
|
-
* selects status='pending', so it would never be picked up again.
|
|
7133
|
-
*
|
|
7134
|
-
* Phase-0 single-worker safety: there is at most one in-flight claim at
|
|
7135
|
-
* any time, so on next process startup ANY 'claimed' row is by definition
|
|
7136
|
-
* orphaned. We promote it back to 'pending' (preserving attempts /
|
|
7137
|
-
* lastError) so the next drainOnce can retry it.
|
|
7138
|
-
*
|
|
7139
|
-
* If we ever go multi-worker, this naive sweep becomes unsafe and must be
|
|
7140
|
-
* replaced with a lease/heartbeat scheme.
|
|
7141
|
-
*
|
|
7142
|
-
* Returns the number of rows recovered (for logging / observability).
|
|
7143
|
-
*/
|
|
7144
|
-
resetClaimed() {
|
|
7145
|
-
const result = this.db.prepare(
|
|
7146
|
-
"UPDATE asset_origin_outbox SET status = 'pending', updatedAt = ? WHERE status = 'claimed'"
|
|
7147
|
-
).run(Date.now());
|
|
7148
|
-
return Number(result.changes);
|
|
7149
|
-
}
|
|
7150
|
-
pendingCount() {
|
|
7151
|
-
return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox WHERE status = 'pending'").get().n;
|
|
7152
|
-
}
|
|
7153
|
-
uploadedCount() {
|
|
7154
|
-
return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox WHERE status = 'uploaded'").get().n;
|
|
7155
|
-
}
|
|
7156
|
-
deadLetterCount() {
|
|
7157
|
-
return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox_dead_letter").get().n;
|
|
7158
|
-
}
|
|
7159
|
-
close() {
|
|
7160
|
-
this.db.close();
|
|
7161
|
-
}
|
|
7162
|
-
};
|
|
7163
|
-
function computeIdempotencyKey(record) {
|
|
7164
|
-
return createHash5("sha256").update(record.originKind).update("|").update(record.workspaceId).update("|").update(record.sourceRef).update("|").update(String(record.observedAt)).digest("hex");
|
|
7165
|
-
}
|
|
7166
|
-
function coerceToString(value, fallback) {
|
|
7167
|
-
if (typeof value === "string") return value;
|
|
7168
|
-
if (value == null) return fallback;
|
|
7169
|
-
return String(value);
|
|
7170
|
-
}
|
|
7171
|
-
function coerceToJson(value) {
|
|
7172
|
-
if (typeof value === "string") return value;
|
|
7173
|
-
if (value == null) return "{}";
|
|
7174
|
-
try {
|
|
7175
|
-
return JSON.stringify(value);
|
|
7176
|
-
} catch {
|
|
7177
|
-
return "{}";
|
|
7178
|
-
}
|
|
7179
|
-
}
|
|
7180
|
-
function rawToRow(raw) {
|
|
7181
|
-
return {
|
|
7182
|
-
id: raw.id,
|
|
7183
|
-
workspaceId: raw.workspaceId,
|
|
7184
|
-
originKind: raw.originKind,
|
|
7185
|
-
sourceRef: raw.sourceRef,
|
|
7186
|
-
payloadJson: raw.payloadJson,
|
|
7187
|
-
hintsJson: raw.hintsJson,
|
|
7188
|
-
observedAt: raw.observedAt,
|
|
7189
|
-
attempts: raw.attempts,
|
|
7190
|
-
lastError: raw.lastError,
|
|
7191
|
-
createdAt: raw.createdAt
|
|
7192
|
-
};
|
|
7193
|
-
}
|
|
7194
|
-
|
|
7195
|
-
// src/daemon/asset/origin/upload-runner.ts
|
|
7196
|
-
var UploadRunner = class {
|
|
7197
|
-
constructor(opts) {
|
|
7198
|
-
this.opts = opts;
|
|
7199
|
-
opts.outbox.resetClaimed();
|
|
7200
|
-
}
|
|
7201
|
-
opts;
|
|
7202
|
-
/**
|
|
7203
|
-
* Process every currently-pending outbox row exactly once. Failed rows
|
|
7204
|
-
* stay 'pending' (with attempts+1) for the next drainOnce call; rows
|
|
7205
|
-
* that hit maxAttempts spill to dead-letter and trigger onUploadFailure.
|
|
7206
|
-
*
|
|
7207
|
-
* Returns the count of rows processed (success + failure).
|
|
7208
|
-
*/
|
|
7209
|
-
async drainOnce() {
|
|
7210
|
-
let count = 0;
|
|
7211
|
-
while (true) {
|
|
7212
|
-
const claim = this.opts.outbox.claimNext();
|
|
7213
|
-
if (!claim) break;
|
|
7214
|
-
count += 1;
|
|
7215
|
-
const row = claim.row;
|
|
7216
|
-
const adapter = this.opts.adapters[row.originKind];
|
|
7217
|
-
if (!adapter) {
|
|
7218
|
-
this.opts.outbox.recordFailure(claim.id, `no adapter registered for kind=${row.originKind}`, {
|
|
7219
|
-
maxAttempts: 1
|
|
7220
|
-
// unrecoverable — fast-fail to dead-letter
|
|
7221
|
-
});
|
|
7222
|
-
continue;
|
|
7223
|
-
}
|
|
7224
|
-
try {
|
|
7225
|
-
const obs = rowToObservation(row);
|
|
7226
|
-
const bytes = await adapter.fetch(obs);
|
|
7227
|
-
const id = await adapter.identifySource(obs);
|
|
7228
|
-
const filename = id.hints?.filename ?? path6.basename(row.sourceRef);
|
|
7229
|
-
const metadata = {
|
|
7230
|
-
sourceRef: row.sourceRef,
|
|
7231
|
-
sourceKind: row.originKind
|
|
7232
|
-
};
|
|
7233
|
-
if (id.hints?.description) metadata.description = id.hints.description;
|
|
7234
|
-
if (id.hints?.sourceAgentImUserId) {
|
|
7235
|
-
metadata.sourceAgentImUserId = id.hints.sourceAgentImUserId;
|
|
7236
|
-
}
|
|
7237
|
-
const folderPath = id.hints?.folderPath || void 0;
|
|
7238
|
-
const result = await this.opts.cloud.uploadAsset({
|
|
7239
|
-
workspaceId: row.workspaceId,
|
|
7240
|
-
filename,
|
|
7241
|
-
bytes: bytes.bytes,
|
|
7242
|
-
mime: bytes.mime,
|
|
7243
|
-
size: bytes.size,
|
|
7244
|
-
metadata,
|
|
7245
|
-
folderPath
|
|
7246
|
-
});
|
|
7247
|
-
this.opts.outbox.markUploaded(claim.id, result);
|
|
7248
|
-
await adapter.onUploadSuccess?.(obs);
|
|
7249
|
-
} catch (err) {
|
|
7250
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
7251
|
-
const before = this.opts.outbox.deadLetterCount();
|
|
7252
|
-
this.opts.outbox.recordFailure(claim.id, message, { maxAttempts: this.opts.maxAttempts });
|
|
7253
|
-
const after = this.opts.outbox.deadLetterCount();
|
|
7254
|
-
if (after > before) {
|
|
7255
|
-
const obs = rowToObservation(row);
|
|
7256
|
-
await adapter.onUploadFailure?.(obs, err);
|
|
7257
|
-
}
|
|
7258
|
-
}
|
|
7259
|
-
}
|
|
7260
|
-
return count;
|
|
7261
|
-
}
|
|
7262
|
-
};
|
|
7263
|
-
function rowToObservation(row) {
|
|
7264
|
-
let detail = {};
|
|
7265
|
-
try {
|
|
7266
|
-
const parsed = JSON.parse(row.payloadJson);
|
|
7267
|
-
if (parsed && typeof parsed === "object") detail = parsed;
|
|
7268
|
-
} catch {
|
|
7269
|
-
}
|
|
7270
|
-
return {
|
|
7271
|
-
workspaceId: row.workspaceId,
|
|
7272
|
-
detail,
|
|
7273
|
-
observedAt: row.observedAt
|
|
7274
|
-
};
|
|
7275
|
-
}
|
|
7276
|
-
var path6 = {
|
|
7277
|
-
basename(p) {
|
|
7278
|
-
const idx = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
|
|
7279
|
-
return idx === -1 ? p : p.slice(idx + 1);
|
|
7280
|
-
}
|
|
7281
|
-
};
|
|
7282
|
-
|
|
7283
|
-
// src/daemon/outbox-watcher.ts
|
|
7284
|
-
import { promises as fs4 } from "fs";
|
|
7285
|
-
import * as path7 from "path";
|
|
7286
6311
|
var DEFAULT_INTERVAL_MS = 2e3;
|
|
7287
6312
|
var RESERVED_SUBDIR = "_uploaded";
|
|
7288
6313
|
var OutboxWatcher = class {
|
|
@@ -7449,7 +6474,7 @@ var OutboxWatcher = class {
|
|
|
7449
6474
|
async scanDir(dir, kind, task) {
|
|
7450
6475
|
let entries = [];
|
|
7451
6476
|
try {
|
|
7452
|
-
entries = await
|
|
6477
|
+
entries = await fs3.readdir(dir);
|
|
7453
6478
|
} catch (err) {
|
|
7454
6479
|
const code = err.code;
|
|
7455
6480
|
if (code === "ENOENT") return;
|
|
@@ -7459,10 +6484,10 @@ var OutboxWatcher = class {
|
|
|
7459
6484
|
for (const name of entries) {
|
|
7460
6485
|
if (name.startsWith(".")) continue;
|
|
7461
6486
|
if (name === RESERVED_SUBDIR) continue;
|
|
7462
|
-
const full =
|
|
6487
|
+
const full = path5.join(dir, name);
|
|
7463
6488
|
let st;
|
|
7464
6489
|
try {
|
|
7465
|
-
st = await
|
|
6490
|
+
st = await fs3.stat(full);
|
|
7466
6491
|
} catch {
|
|
7467
6492
|
continue;
|
|
7468
6493
|
}
|
|
@@ -7487,8 +6512,8 @@ var OutboxWatcher = class {
|
|
|
7487
6512
|
this.log("warn", `skip ${filePath}: no active taskId (no dispatch/handoff received yet)`);
|
|
7488
6513
|
return;
|
|
7489
6514
|
}
|
|
7490
|
-
const bytes = await
|
|
7491
|
-
const fileName =
|
|
6515
|
+
const bytes = await fs3.readFile(filePath);
|
|
6516
|
+
const fileName = path5.basename(filePath);
|
|
7492
6517
|
const blob = new Blob([new Uint8Array(bytes)]);
|
|
7493
6518
|
const form = new FormData();
|
|
7494
6519
|
form.append("workspaceId", wsId);
|
|
@@ -7727,15 +6752,6 @@ var Runner = class extends EventEmitter3 {
|
|
|
7727
6752
|
outboxWatcher;
|
|
7728
6753
|
memoryWiring;
|
|
7729
6754
|
assetMetadataIndexes = /* @__PURE__ */ new Map();
|
|
7730
|
-
workspaceMirrors = /* @__PURE__ */ new Map();
|
|
7731
|
-
assetOriginOutbox;
|
|
7732
|
-
dropFolderAdapter;
|
|
7733
|
-
dropFolderUploadRunner;
|
|
7734
|
-
dropFolderTimer;
|
|
7735
|
-
dropFolderWorkspaceId = "";
|
|
7736
|
-
dropFolderWorkspaceDir = "";
|
|
7737
|
-
dropFolderTickInFlight = false;
|
|
7738
|
-
dropFolderStable = /* @__PURE__ */ new Map();
|
|
7739
6755
|
state = "idle";
|
|
7740
6756
|
startedAt = 0;
|
|
7741
6757
|
workspaceId = "";
|
|
@@ -7777,7 +6793,6 @@ var Runner = class extends EventEmitter3 {
|
|
|
7777
6793
|
this.servicePool = new ServicePool();
|
|
7778
6794
|
this.installStaticHostedAgentFromEnv();
|
|
7779
6795
|
this.loadAgentsFromDb();
|
|
7780
|
-
await this.prepareLocalProfiles();
|
|
7781
6796
|
this.syncQueue = new SyncQueue(this.db);
|
|
7782
6797
|
this.syncWorker = new SyncWorker({ queue: this.syncQueue, flush: (row) => this.flushSyncRow(row) });
|
|
7783
6798
|
this.syncWorker.start();
|
|
@@ -7799,10 +6814,9 @@ var Runner = class extends EventEmitter3 {
|
|
|
7799
6814
|
);
|
|
7800
6815
|
}
|
|
7801
6816
|
if (this.workspaceId) {
|
|
7802
|
-
this.
|
|
7803
|
-
(err) => console.error("[Daemon] Initial asset sync failed:", err.message)
|
|
6817
|
+
this.syncAssetMetadata(this.workspaceId).catch(
|
|
6818
|
+
(err) => console.error("[Daemon] Initial asset metadata sync failed:", err.message)
|
|
7804
6819
|
);
|
|
7805
|
-
await this.ensureDropFolderRuntime(this.workspaceId);
|
|
7806
6820
|
}
|
|
7807
6821
|
const containerId = process.env.PRISMER_CONTAINER_ID;
|
|
7808
6822
|
const isContainer = !!containerId || process.env.PRISMER_RUNTIME_MODE === "container";
|
|
@@ -7853,8 +6867,7 @@ var Runner = class extends EventEmitter3 {
|
|
|
7853
6867
|
// an actual agent process.
|
|
7854
6868
|
attachMemory: this.memoryWiring ? attachMemoryRpc({ runtime: this.memoryWiring.runtime }) : void 0,
|
|
7855
6869
|
attachAsset: attachAssetRpc({
|
|
7856
|
-
resolveIndex: (workspaceId) => this.assetMetadataIndexes.get(workspaceId)
|
|
7857
|
-
assetCache: this.assetCache
|
|
6870
|
+
resolveIndex: (workspaceId) => this.assetMetadataIndexes.get(workspaceId)
|
|
7858
6871
|
})
|
|
7859
6872
|
});
|
|
7860
6873
|
await this.localServer.start();
|
|
@@ -7903,7 +6916,6 @@ var Runner = class extends EventEmitter3 {
|
|
|
7903
6916
|
this.memoryWiring = void 0;
|
|
7904
6917
|
this.ws?.close();
|
|
7905
6918
|
this.outboxWatcher?.stop();
|
|
7906
|
-
this.stopDropFolderRuntime();
|
|
7907
6919
|
await this.servicePool?.shutdown();
|
|
7908
6920
|
await this.localServer?.stop();
|
|
7909
6921
|
try {
|
|
@@ -7960,7 +6972,6 @@ var Runner = class extends EventEmitter3 {
|
|
|
7960
6972
|
runningTaskIds: Array.from(this.runningTasks.keys()),
|
|
7961
6973
|
observability: {
|
|
7962
6974
|
adapters: this.snapshotAdapterObservability(),
|
|
7963
|
-
assetSync: this.snapshotAssetSyncObservability(),
|
|
7964
6975
|
...this.lastTaskError ? { lastTaskError: this.lastTaskError } : {}
|
|
7965
6976
|
}
|
|
7966
6977
|
};
|
|
@@ -7996,35 +7007,6 @@ var Runner = class extends EventEmitter3 {
|
|
|
7996
7007
|
});
|
|
7997
7008
|
}
|
|
7998
7009
|
}
|
|
7999
|
-
async prepareLocalProfiles() {
|
|
8000
|
-
const rows = this.db.prepare(
|
|
8001
|
-
`SELECT id, workspace_id, agent_im_user_id, adapter_name, name, config, version, synced_at
|
|
8002
|
-
FROM agent_profiles
|
|
8003
|
-
WHERE deleted_at IS NULL`
|
|
8004
|
-
).all();
|
|
8005
|
-
for (const row of rows) {
|
|
8006
|
-
const adapter = this.registry.get(row.adapter_name);
|
|
8007
|
-
if (!adapter?.prepareProfile) continue;
|
|
8008
|
-
try {
|
|
8009
|
-
await adapter.prepareProfile({
|
|
8010
|
-
id: row.id,
|
|
8011
|
-
workspaceId: row.workspace_id,
|
|
8012
|
-
agentImUserId: row.agent_im_user_id,
|
|
8013
|
-
adapterName: row.adapter_name,
|
|
8014
|
-
name: row.name,
|
|
8015
|
-
config: parseProfileConfig(row.config),
|
|
8016
|
-
version: row.version,
|
|
8017
|
-
createdAt: new Date(row.synced_at ?? Date.now()),
|
|
8018
|
-
updatedAt: new Date(row.synced_at ?? Date.now())
|
|
8019
|
-
});
|
|
8020
|
-
} catch (err) {
|
|
8021
|
-
process.stderr.write(
|
|
8022
|
-
`[daemon] local profile preflight skipped agent=${row.agent_im_user_id} profile=${row.id}: ${err.message}
|
|
8023
|
-
`
|
|
8024
|
-
);
|
|
8025
|
-
}
|
|
8026
|
-
}
|
|
8027
|
-
}
|
|
8028
7010
|
/**
|
|
8029
7011
|
* Register an in-process AgentProfile snapshot (called by agent CLI / sync layer).
|
|
8030
7012
|
* Used to populate the `agents` list in agent.host.declare.
|
|
@@ -8085,7 +7067,7 @@ var Runner = class extends EventEmitter3 {
|
|
|
8085
7067
|
if (!existsSync9(rawFile)) {
|
|
8086
7068
|
throw new Error(`PRISMER_HOSTED_AGENT_FILE not found: ${rawFile}`);
|
|
8087
7069
|
}
|
|
8088
|
-
raw =
|
|
7070
|
+
raw = readFileSync6(rawFile, "utf8");
|
|
8089
7071
|
} else if (rawJson) {
|
|
8090
7072
|
raw = rawJson;
|
|
8091
7073
|
}
|
|
@@ -8187,7 +7169,7 @@ var Runner = class extends EventEmitter3 {
|
|
|
8187
7169
|
name: a.name,
|
|
8188
7170
|
adapterName: a.adapterName,
|
|
8189
7171
|
capabilities: a.capabilities,
|
|
8190
|
-
profiles: Array.from(a.profiles.entries()).map(([id,
|
|
7172
|
+
profiles: Array.from(a.profiles.entries()).map(([id, version2]) => ({ id, version: version2 }))
|
|
8191
7173
|
}))
|
|
8192
7174
|
};
|
|
8193
7175
|
this.ws.send(envelope("agent.host.declare", payload, this.config.daemon_id));
|
|
@@ -8229,29 +7211,17 @@ var Runner = class extends EventEmitter3 {
|
|
|
8229
7211
|
(err) => console.error("[Daemon] Initial memory sync failed:", err.message)
|
|
8230
7212
|
);
|
|
8231
7213
|
}
|
|
8232
|
-
this.
|
|
8233
|
-
(err) => console.error("[Daemon] Asset sync failed:", err.message)
|
|
7214
|
+
this.syncAssetMetadata(this.workspaceId).catch(
|
|
7215
|
+
(err) => console.error("[Daemon] Asset metadata sync failed:", err.message)
|
|
8234
7216
|
);
|
|
8235
|
-
await this.ensureDropFolderRuntime(this.workspaceId);
|
|
8236
7217
|
for (const id of payload.profilesToSync) {
|
|
8237
7218
|
try {
|
|
8238
|
-
await this.servicePool.drop(id);
|
|
8239
7219
|
await this.syncProfileFromCloud(id);
|
|
8240
7220
|
} catch (err) {
|
|
8241
7221
|
this.emit("sync-error", err);
|
|
8242
7222
|
}
|
|
8243
7223
|
}
|
|
8244
|
-
|
|
8245
|
-
const row = this.db.prepare("SELECT agent_im_user_id FROM agent_profiles WHERE id = ?").get(id);
|
|
8246
|
-
this.db.prepare("DELETE FROM agent_profiles WHERE id = ?").run(id);
|
|
8247
|
-
if (row) {
|
|
8248
|
-
this.db.prepare("DELETE FROM agents WHERE im_user_id = ?").run(row.agent_im_user_id);
|
|
8249
|
-
}
|
|
8250
|
-
process.stderr.write(`[daemon] tombstone profile=${id}
|
|
8251
|
-
`);
|
|
8252
|
-
}
|
|
8253
|
-
if (payload.profilesToDelete?.length) this.loadAgentsFromDb();
|
|
8254
|
-
if ((payload.profilesToSync.length > 0 || (payload.profilesToDelete?.length ?? 0) > 0) && this.wsConnected) this.sendDeclare();
|
|
7224
|
+
if (payload.profilesToSync.length > 0 && this.wsConnected) this.sendDeclare();
|
|
8255
7225
|
this.emit("host-acked", payload);
|
|
8256
7226
|
}
|
|
8257
7227
|
async onTaskDispatch(payload, requestId) {
|
|
@@ -8291,15 +7261,9 @@ var Runner = class extends EventEmitter3 {
|
|
|
8291
7261
|
this.ws.send(envelope("task.dispatch.progress", progressPayload));
|
|
8292
7262
|
}
|
|
8293
7263
|
});
|
|
8294
|
-
this.ws.send(envelope("task.dispatch.reply", reply, requestId));
|
|
8295
|
-
if (!reply.ok) {
|
|
8296
|
-
const code = reply.error?.code ?? "unknown";
|
|
8297
|
-
const message = reply.error?.message ?? "unknown error";
|
|
8298
|
-
process.stderr.write(`[daemon] dispatch failed task=${payload.taskId} code=${code} message=${message}
|
|
8299
|
-
`);
|
|
8300
|
-
}
|
|
7264
|
+
this.ws.send(envelope("task.dispatch.reply", reply, requestId));
|
|
8301
7265
|
} else {
|
|
8302
|
-
|
|
7266
|
+
await handleDispatch(payload, requestId, {
|
|
8303
7267
|
registry: this.registry,
|
|
8304
7268
|
cloud: this.cloud,
|
|
8305
7269
|
uriResolver: this.uriResolver,
|
|
@@ -8315,12 +7279,6 @@ var Runner = class extends EventEmitter3 {
|
|
|
8315
7279
|
if (running) running.lastProgressAt = Date.now();
|
|
8316
7280
|
}
|
|
8317
7281
|
});
|
|
8318
|
-
if (!reply.ok) {
|
|
8319
|
-
const code = reply.error?.code ?? "unknown";
|
|
8320
|
-
const message = reply.error?.message ?? "unknown error";
|
|
8321
|
-
process.stderr.write(`[daemon] dispatch failed task=${payload.taskId} code=${code} message=${message}
|
|
8322
|
-
`);
|
|
8323
|
-
}
|
|
8324
7282
|
}
|
|
8325
7283
|
process.stdout.write(`[daemon] dispatch done task=${payload.taskId}
|
|
8326
7284
|
`);
|
|
@@ -8348,7 +7306,6 @@ var Runner = class extends EventEmitter3 {
|
|
|
8348
7306
|
}
|
|
8349
7307
|
async onAgentProfileChanged(payload) {
|
|
8350
7308
|
try {
|
|
8351
|
-
await this.servicePool.drop(payload.profileId);
|
|
8352
7309
|
await this.syncProfileFromCloud(payload.profileId);
|
|
8353
7310
|
if (this.wsConnected) this.sendDeclare();
|
|
8354
7311
|
} catch (err) {
|
|
@@ -8356,21 +7313,7 @@ var Runner = class extends EventEmitter3 {
|
|
|
8356
7313
|
}
|
|
8357
7314
|
}
|
|
8358
7315
|
async syncProfileFromCloud(profileId) {
|
|
8359
|
-
|
|
8360
|
-
try {
|
|
8361
|
-
profile = await this.cloud.get(`/api/im/agent_profiles/${encodeURIComponent(profileId)}`);
|
|
8362
|
-
} catch (err) {
|
|
8363
|
-
if (err instanceof CloudError && err.status === 404) {
|
|
8364
|
-
const row = this.db.prepare("SELECT agent_im_user_id FROM agent_profiles WHERE id = ?").get(profileId);
|
|
8365
|
-
this.db.prepare("DELETE FROM agent_profiles WHERE id = ?").run(profileId);
|
|
8366
|
-
if (row) {
|
|
8367
|
-
this.db.prepare("DELETE FROM agents WHERE im_user_id = ?").run(row.agent_im_user_id);
|
|
8368
|
-
}
|
|
8369
|
-
this.loadAgentsFromDb();
|
|
8370
|
-
return;
|
|
8371
|
-
}
|
|
8372
|
-
throw err;
|
|
8373
|
-
}
|
|
7316
|
+
const profile = await this.cloud.get(`/api/im/agent_profiles/${encodeURIComponent(profileId)}`);
|
|
8374
7317
|
const agent = await this.resolveOwnedAgent(profile.agentImUserId);
|
|
8375
7318
|
const adapter = this.registry.get(profile.adapterName);
|
|
8376
7319
|
const capabilities = agent?.card?.capabilities?.length ? agent.card.capabilities : adapter?.capabilities ?? [];
|
|
@@ -8405,14 +7348,6 @@ var Runner = class extends EventEmitter3 {
|
|
|
8405
7348
|
);
|
|
8406
7349
|
});
|
|
8407
7350
|
tx();
|
|
8408
|
-
try {
|
|
8409
|
-
await adapter?.prepareProfile?.(profile);
|
|
8410
|
-
} catch (err) {
|
|
8411
|
-
process.stderr.write(
|
|
8412
|
-
`[daemon] profile preflight skipped agent=${profile.agentImUserId} profile=${profile.id}: ${err.message}
|
|
8413
|
-
`
|
|
8414
|
-
);
|
|
8415
|
-
}
|
|
8416
7351
|
this.loadAgentsFromDb();
|
|
8417
7352
|
process.stdout.write(
|
|
8418
7353
|
`[daemon] profile synced agent=${profile.agentImUserId} profile=${profile.id} adapter=${profile.adapterName}
|
|
@@ -8441,10 +7376,15 @@ var Runner = class extends EventEmitter3 {
|
|
|
8441
7376
|
}
|
|
8442
7377
|
async onAssetChanged(payload) {
|
|
8443
7378
|
if (!payload.workspaceId) return;
|
|
7379
|
+
const index = this.assetMetadataIndexes.get(payload.workspaceId);
|
|
7380
|
+
if (!index) return;
|
|
8444
7381
|
try {
|
|
8445
|
-
await
|
|
7382
|
+
const result = await index.pullDelta();
|
|
7383
|
+
if (result.applied > 0) {
|
|
7384
|
+
console.log(`[Daemon] asset.changed workspace=${payload.workspaceId} applied=${result.applied}`);
|
|
7385
|
+
}
|
|
8446
7386
|
} catch (err) {
|
|
8447
|
-
console.error(`[Daemon] asset.changed
|
|
7387
|
+
console.error(`[Daemon] asset.changed pullDelta failed workspace=${payload.workspaceId}:`, err.message);
|
|
8448
7388
|
}
|
|
8449
7389
|
}
|
|
8450
7390
|
onWorkspaceFileChanged(payload) {
|
|
@@ -8482,15 +7422,6 @@ var Runner = class extends EventEmitter3 {
|
|
|
8482
7422
|
}
|
|
8483
7423
|
};
|
|
8484
7424
|
}
|
|
8485
|
-
snapshotAssetSyncObservability() {
|
|
8486
|
-
return {
|
|
8487
|
-
workspaceId: this.dropFolderWorkspaceId || null,
|
|
8488
|
-
rootDir: this.dropFolderWorkspaceDir || null,
|
|
8489
|
-
dropDir: this.dropFolderWorkspaceDir ? join13(this.dropFolderWorkspaceDir, "drop") : null,
|
|
8490
|
-
uploadedDir: this.dropFolderWorkspaceDir ? join13(this.dropFolderWorkspaceDir, "uploaded") : null,
|
|
8491
|
-
failedDir: this.dropFolderWorkspaceDir ? join13(this.dropFolderWorkspaceDir, "upload-failed") : null
|
|
8492
|
-
};
|
|
8493
|
-
}
|
|
8494
7425
|
/**
|
|
8495
7426
|
* Ensure an AssetMetadataIndex exists for the given workspace and pull
|
|
8496
7427
|
* delta from cloud. Idempotent — creates the index on first call, reuses
|
|
@@ -8513,132 +7444,6 @@ var Runner = class extends EventEmitter3 {
|
|
|
8513
7444
|
console.log(`[AssetMeta] workspace=${workspaceId}: ${result.applied} applied, cursor=${result.cursor}`);
|
|
8514
7445
|
}
|
|
8515
7446
|
}
|
|
8516
|
-
/**
|
|
8517
|
-
* Ensure a WorkspaceMirror exists for the workspace and pull path→asset
|
|
8518
|
-
* bindings from cloud. Cold-start daemons previously only had this table
|
|
8519
|
-
* populated by WS push or per-path URI fallback, so local asset state looked
|
|
8520
|
-
* empty even though cloud files existed.
|
|
8521
|
-
*/
|
|
8522
|
-
async syncWorkspaceFiles(workspaceId) {
|
|
8523
|
-
let mirror = this.workspaceMirrors.get(workspaceId);
|
|
8524
|
-
if (!mirror) {
|
|
8525
|
-
const stateDir = `${this.paths.root}/${workspaceId}`;
|
|
8526
|
-
mirror = new WorkspaceMirror({
|
|
8527
|
-
db: this.db,
|
|
8528
|
-
cloud: this.cloud,
|
|
8529
|
-
workspaceId,
|
|
8530
|
-
workspaceStateDir: stateDir
|
|
8531
|
-
});
|
|
8532
|
-
this.workspaceMirrors.set(workspaceId, mirror);
|
|
8533
|
-
}
|
|
8534
|
-
const result = await mirror.pullDelta();
|
|
8535
|
-
if (result.applied > 0) {
|
|
8536
|
-
console.log(`[WorkspaceFiles] workspace=${workspaceId}: ${result.applied} applied, cursor=${result.cursor ?? "null"}`);
|
|
8537
|
-
}
|
|
8538
|
-
}
|
|
8539
|
-
async syncAssetState(workspaceId) {
|
|
8540
|
-
if (!this.paths?.root) return;
|
|
8541
|
-
await Promise.all([
|
|
8542
|
-
this.syncAssetMetadata(workspaceId),
|
|
8543
|
-
this.syncWorkspaceFiles(workspaceId)
|
|
8544
|
-
]);
|
|
8545
|
-
}
|
|
8546
|
-
/**
|
|
8547
|
-
* Desktop asset ingress: watch a user-visible local asset folder
|
|
8548
|
-
* and upload new files as IMAssets, preserving nested relative dirs in
|
|
8549
|
-
* IMAsset.folderPath. The lower-level adapter/outbox/uploader already
|
|
8550
|
-
* existed; this method wires them into the real daemon lifecycle.
|
|
8551
|
-
*/
|
|
8552
|
-
async ensureDropFolderRuntime(workspaceId) {
|
|
8553
|
-
if (!workspaceId || !this.paths?.root) return;
|
|
8554
|
-
if (this.dropFolderWorkspaceId === workspaceId && this.dropFolderTimer) return;
|
|
8555
|
-
this.stopDropFolderRuntime();
|
|
8556
|
-
this.dropFolderWorkspaceId = workspaceId;
|
|
8557
|
-
const workspaceRoot = resolveUserAssetWorkspaceDir(workspaceId);
|
|
8558
|
-
this.dropFolderWorkspaceDir = workspaceRoot;
|
|
8559
|
-
await mkdir(join13(workspaceRoot, "drop"), { recursive: true });
|
|
8560
|
-
await mkdir(join13(workspaceRoot, "uploaded"), { recursive: true });
|
|
8561
|
-
await mkdir(join13(workspaceRoot, "upload-failed"), { recursive: true });
|
|
8562
|
-
this.assetOriginOutbox = new OriginOutbox({ dbPath: join13(this.paths.root, "asset-origin.db") });
|
|
8563
|
-
this.dropFolderAdapter = new DropFolderAdapter({ workspaceDir: workspaceRoot });
|
|
8564
|
-
this.dropFolderUploadRunner = new UploadRunner({
|
|
8565
|
-
outbox: this.assetOriginOutbox,
|
|
8566
|
-
cloud: new DaemonAssetUploadClient(this.config),
|
|
8567
|
-
adapters: { "drop-folder": this.dropFolderAdapter },
|
|
8568
|
-
maxAttempts: 3
|
|
8569
|
-
});
|
|
8570
|
-
const tick = () => {
|
|
8571
|
-
this.runDropFolderTick().catch((err) => {
|
|
8572
|
-
process.stderr.write(`[daemon] drop-folder sync failed: ${err.message}
|
|
8573
|
-
`);
|
|
8574
|
-
});
|
|
8575
|
-
};
|
|
8576
|
-
this.dropFolderTimer = setInterval(tick, 1e3);
|
|
8577
|
-
tick();
|
|
8578
|
-
process.stdout.write(`[daemon] drop-folder asset sync watching ${join13(workspaceRoot, "drop")}
|
|
8579
|
-
`);
|
|
8580
|
-
}
|
|
8581
|
-
stopDropFolderRuntime() {
|
|
8582
|
-
if (this.dropFolderTimer) {
|
|
8583
|
-
clearInterval(this.dropFolderTimer);
|
|
8584
|
-
this.dropFolderTimer = void 0;
|
|
8585
|
-
}
|
|
8586
|
-
try {
|
|
8587
|
-
this.assetOriginOutbox?.close();
|
|
8588
|
-
} catch {
|
|
8589
|
-
}
|
|
8590
|
-
this.assetOriginOutbox = void 0;
|
|
8591
|
-
this.dropFolderAdapter = void 0;
|
|
8592
|
-
this.dropFolderUploadRunner = void 0;
|
|
8593
|
-
this.dropFolderWorkspaceId = "";
|
|
8594
|
-
this.dropFolderWorkspaceDir = "";
|
|
8595
|
-
this.dropFolderTickInFlight = false;
|
|
8596
|
-
this.dropFolderStable.clear();
|
|
8597
|
-
}
|
|
8598
|
-
async runDropFolderTick() {
|
|
8599
|
-
if (this.dropFolderTickInFlight) return;
|
|
8600
|
-
const workspaceId = this.dropFolderWorkspaceId;
|
|
8601
|
-
const adapter = this.dropFolderAdapter;
|
|
8602
|
-
const outbox = this.assetOriginOutbox;
|
|
8603
|
-
const runner = this.dropFolderUploadRunner;
|
|
8604
|
-
if (!workspaceId || !adapter || !outbox || !runner) return;
|
|
8605
|
-
this.dropFolderTickInFlight = true;
|
|
8606
|
-
try {
|
|
8607
|
-
const observations = await adapter.scanOnce(workspaceId);
|
|
8608
|
-
for (const obs of observations) {
|
|
8609
|
-
const id = await adapter.identifySource(obs);
|
|
8610
|
-
if (!this.isDropFolderObservationStable(id.sourceRef, obs.detail)) continue;
|
|
8611
|
-
outbox.enqueue({
|
|
8612
|
-
workspaceId: obs.workspaceId,
|
|
8613
|
-
originKind: "drop-folder",
|
|
8614
|
-
sourceRef: id.sourceRef,
|
|
8615
|
-
payloadJson: JSON.stringify(obs.detail),
|
|
8616
|
-
hintsJson: JSON.stringify(id.hints ?? {}),
|
|
8617
|
-
observedAt: obs.observedAt
|
|
8618
|
-
});
|
|
8619
|
-
}
|
|
8620
|
-
const processed = await runner.drainOnce();
|
|
8621
|
-
if (processed > 0) {
|
|
8622
|
-
this.dropFolderStable.clear();
|
|
8623
|
-
process.stdout.write(`[daemon] drop-folder uploaded/processed ${processed} asset(s)
|
|
8624
|
-
`);
|
|
8625
|
-
await this.syncAssetState(workspaceId).catch(
|
|
8626
|
-
(err) => process.stderr.write(`[daemon] drop-folder post-sync failed: ${err.message}
|
|
8627
|
-
`)
|
|
8628
|
-
);
|
|
8629
|
-
}
|
|
8630
|
-
} finally {
|
|
8631
|
-
this.dropFolderTickInFlight = false;
|
|
8632
|
-
}
|
|
8633
|
-
}
|
|
8634
|
-
isDropFolderObservationStable(sourceRef, detail) {
|
|
8635
|
-
const raw = detail && typeof detail === "object" ? detail : {};
|
|
8636
|
-
const size = typeof raw.size === "number" ? raw.size : -1;
|
|
8637
|
-
const mtime = typeof raw.mtime === "number" ? raw.mtime : -1;
|
|
8638
|
-
const previous = this.dropFolderStable.get(sourceRef);
|
|
8639
|
-
this.dropFolderStable.set(sourceRef, { size, mtime });
|
|
8640
|
-
return Boolean(previous && previous.size === size && previous.mtime === mtime);
|
|
8641
|
-
}
|
|
8642
7447
|
/**
|
|
8643
7448
|
* SyncWorker FlushFn — pushes local writes to cloud.
|
|
8644
7449
|
*
|
|
@@ -8675,44 +7480,44 @@ var Runner = class extends EventEmitter3 {
|
|
|
8675
7480
|
const id = encodeURIComponent(row.resource_id);
|
|
8676
7481
|
const body = row.operation === "delete" ? void 0 : safeJsonParse(row.payload);
|
|
8677
7482
|
let method;
|
|
8678
|
-
let
|
|
7483
|
+
let path7;
|
|
8679
7484
|
switch (op) {
|
|
8680
7485
|
case "workspace.create":
|
|
8681
7486
|
method = "POST";
|
|
8682
|
-
|
|
7487
|
+
path7 = "/api/im/workspaces";
|
|
8683
7488
|
break;
|
|
8684
7489
|
case "workspace.update":
|
|
8685
7490
|
method = "PATCH";
|
|
8686
|
-
|
|
7491
|
+
path7 = `/api/im/workspaces/${id}`;
|
|
8687
7492
|
break;
|
|
8688
7493
|
case "workspace.delete":
|
|
8689
7494
|
method = "DELETE";
|
|
8690
|
-
|
|
7495
|
+
path7 = `/api/im/workspaces/${id}`;
|
|
8691
7496
|
break;
|
|
8692
7497
|
// agent.create must use /register — POST /api/im/agents is not exposed.
|
|
8693
7498
|
case "agent.create":
|
|
8694
7499
|
method = "POST";
|
|
8695
|
-
|
|
7500
|
+
path7 = "/api/im/register";
|
|
8696
7501
|
break;
|
|
8697
7502
|
case "agent.update":
|
|
8698
7503
|
method = "PATCH";
|
|
8699
|
-
|
|
7504
|
+
path7 = `/api/im/agents/${id}`;
|
|
8700
7505
|
break;
|
|
8701
7506
|
case "agent.delete":
|
|
8702
7507
|
method = "DELETE";
|
|
8703
|
-
|
|
7508
|
+
path7 = `/api/im/agents/${id}`;
|
|
8704
7509
|
break;
|
|
8705
7510
|
case "agent_profile.create":
|
|
8706
7511
|
method = "POST";
|
|
8707
|
-
|
|
7512
|
+
path7 = "/api/im/agent_profiles";
|
|
8708
7513
|
break;
|
|
8709
7514
|
case "agent_profile.update":
|
|
8710
7515
|
method = "PATCH";
|
|
8711
|
-
|
|
7516
|
+
path7 = `/api/im/agent_profiles/${id}`;
|
|
8712
7517
|
break;
|
|
8713
7518
|
case "agent_profile.delete":
|
|
8714
7519
|
method = "DELETE";
|
|
8715
|
-
|
|
7520
|
+
path7 = `/api/im/agent_profiles/${id}`;
|
|
8716
7521
|
break;
|
|
8717
7522
|
default:
|
|
8718
7523
|
process.stderr.write(`[daemon] sync flush op=${op} id=${row.id} result=drop (unknown op)
|
|
@@ -8721,7 +7526,7 @@ var Runner = class extends EventEmitter3 {
|
|
|
8721
7526
|
}
|
|
8722
7527
|
let res;
|
|
8723
7528
|
try {
|
|
8724
|
-
res = await this.cloud.request(method,
|
|
7529
|
+
res = await this.cloud.request(method, path7, { body });
|
|
8725
7530
|
} catch (err) {
|
|
8726
7531
|
process.stderr.write(
|
|
8727
7532
|
`[daemon] sync flush op=${op} id=${row.id} result=retry (threw: ${err.message})
|
|
@@ -8744,89 +7549,6 @@ var Runner = class extends EventEmitter3 {
|
|
|
8744
7549
|
return { ok: res.ok, status: res.status, message: res.error?.message };
|
|
8745
7550
|
}
|
|
8746
7551
|
};
|
|
8747
|
-
var DaemonAssetUploadClient = class {
|
|
8748
|
-
constructor(config) {
|
|
8749
|
-
this.config = config;
|
|
8750
|
-
}
|
|
8751
|
-
config;
|
|
8752
|
-
async uploadAsset(req) {
|
|
8753
|
-
const form = new FormData();
|
|
8754
|
-
form.set("workspaceId", req.workspaceId);
|
|
8755
|
-
form.set("kind", "file");
|
|
8756
|
-
form.set("metadata", JSON.stringify(req.metadata));
|
|
8757
|
-
if (req.folderPath !== void 0) form.set("folderPath", req.folderPath);
|
|
8758
|
-
form.set("file", new Blob([req.bytes], { type: req.mime ?? "application/octet-stream" }), req.filename);
|
|
8759
|
-
const url = `${this.config.cloud_api_base.replace(/\/$/, "")}/api/im/assets`;
|
|
8760
|
-
const controller = new AbortController();
|
|
8761
|
-
const timer = setTimeout(() => controller.abort(), 6e4);
|
|
8762
|
-
let res;
|
|
8763
|
-
try {
|
|
8764
|
-
res = await fetch(url, {
|
|
8765
|
-
method: "POST",
|
|
8766
|
-
headers: { Authorization: `Bearer ${this.config.api_key}` },
|
|
8767
|
-
body: form,
|
|
8768
|
-
signal: controller.signal
|
|
8769
|
-
});
|
|
8770
|
-
} catch (err) {
|
|
8771
|
-
if (err.name === "AbortError") {
|
|
8772
|
-
throw new Error("asset upload timed out after 60000ms");
|
|
8773
|
-
}
|
|
8774
|
-
throw err;
|
|
8775
|
-
} finally {
|
|
8776
|
-
clearTimeout(timer);
|
|
8777
|
-
}
|
|
8778
|
-
const text = await res.text();
|
|
8779
|
-
let body;
|
|
8780
|
-
try {
|
|
8781
|
-
body = text ? JSON.parse(text) : void 0;
|
|
8782
|
-
} catch {
|
|
8783
|
-
body = text;
|
|
8784
|
-
}
|
|
8785
|
-
if (!res.ok || isCloudErrorEnvelope(body)) {
|
|
8786
|
-
throw new Error(`asset upload failed (${res.status}): ${cloudUploadErrorMessage(body)}`);
|
|
8787
|
-
}
|
|
8788
|
-
const data = unwrapCloudData(body);
|
|
8789
|
-
const assetId = stringValue(data, "id");
|
|
8790
|
-
const contentHash = stringValue(data, "contentHash");
|
|
8791
|
-
if (!assetId || !contentHash) {
|
|
8792
|
-
throw new Error("asset upload response missing id/contentHash");
|
|
8793
|
-
}
|
|
8794
|
-
return { assetId, contentHash };
|
|
8795
|
-
}
|
|
8796
|
-
};
|
|
8797
|
-
function isCloudErrorEnvelope(raw) {
|
|
8798
|
-
return Boolean(raw && typeof raw === "object" && "ok" in raw && raw.ok === false);
|
|
8799
|
-
}
|
|
8800
|
-
function cloudUploadErrorMessage(raw) {
|
|
8801
|
-
if (raw && typeof raw === "object") {
|
|
8802
|
-
const error = raw.error;
|
|
8803
|
-
if (typeof error === "string") return error;
|
|
8804
|
-
if (error && typeof error === "object") {
|
|
8805
|
-
const message = error.message;
|
|
8806
|
-
if (typeof message === "string") return message;
|
|
8807
|
-
}
|
|
8808
|
-
}
|
|
8809
|
-
return typeof raw === "string" ? raw : "request failed";
|
|
8810
|
-
}
|
|
8811
|
-
function unwrapCloudData(raw) {
|
|
8812
|
-
if (raw && typeof raw === "object") {
|
|
8813
|
-
const data = raw.data;
|
|
8814
|
-
if (data && typeof data === "object" && !Array.isArray(data)) return data;
|
|
8815
|
-
}
|
|
8816
|
-
return {};
|
|
8817
|
-
}
|
|
8818
|
-
function stringValue(obj, key) {
|
|
8819
|
-
const value = obj[key];
|
|
8820
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
8821
|
-
}
|
|
8822
|
-
function resolveUserAssetWorkspaceDir(workspaceId) {
|
|
8823
|
-
const override = process.env.PRISMER_ASSET_SYNC_ROOT;
|
|
8824
|
-
if (override) return join13(override, workspaceId);
|
|
8825
|
-
const home = homedir6();
|
|
8826
|
-
const desktop = join13(home, "Desktop");
|
|
8827
|
-
const base = existsSync9(desktop) ? desktop : home;
|
|
8828
|
-
return join13(base, "Prismer Assets", workspaceId);
|
|
8829
|
-
}
|
|
8830
7552
|
function readTargetDaemonId(payload) {
|
|
8831
7553
|
if (typeof payload.targetDaemonId === "string" && payload.targetDaemonId.length > 0) {
|
|
8832
7554
|
return payload.targetDaemonId;
|
|
@@ -8877,14 +7599,6 @@ function validateStaticHostedAgent(raw) {
|
|
|
8877
7599
|
}
|
|
8878
7600
|
};
|
|
8879
7601
|
}
|
|
8880
|
-
function parseProfileConfig(raw) {
|
|
8881
|
-
try {
|
|
8882
|
-
const parsed = JSON.parse(raw);
|
|
8883
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
8884
|
-
} catch {
|
|
8885
|
-
return {};
|
|
8886
|
-
}
|
|
8887
|
-
}
|
|
8888
7602
|
function safeJsonParse(raw) {
|
|
8889
7603
|
try {
|
|
8890
7604
|
return JSON.parse(raw);
|
|
@@ -8896,7 +7610,7 @@ function safeJsonParse(raw) {
|
|
|
8896
7610
|
// src/pair.ts
|
|
8897
7611
|
import { generateKeyPairSync } from "crypto";
|
|
8898
7612
|
import { hostname as hostname2 } from "os";
|
|
8899
|
-
import { setTimeout as
|
|
7613
|
+
import { setTimeout as sleep } from "timers/promises";
|
|
8900
7614
|
import qrcode from "qrcode";
|
|
8901
7615
|
async function pair(opts) {
|
|
8902
7616
|
const paths = opts.paths ?? resolvePaths();
|
|
@@ -8965,7 +7679,7 @@ Scan with Lumin to approve, or open: ${offer.qrUrl}
|
|
|
8965
7679
|
const pollIntervalMs = opts.pollIntervalMs ?? 5e3;
|
|
8966
7680
|
for (let i = 0; i < maxAttempts; i += 1) {
|
|
8967
7681
|
if (i > 0 || !localOnlyMode) {
|
|
8968
|
-
await
|
|
7682
|
+
await sleep(pollIntervalMs);
|
|
8969
7683
|
}
|
|
8970
7684
|
const res = await cloud.request(
|
|
8971
7685
|
"GET",
|
|
@@ -9002,7 +7716,7 @@ function unwrapEnvelope(raw) {
|
|
|
9002
7716
|
}
|
|
9003
7717
|
|
|
9004
7718
|
// src/cli/index.ts
|
|
9005
|
-
import { Command as
|
|
7719
|
+
import { Command as Command18 } from "commander";
|
|
9006
7720
|
|
|
9007
7721
|
// src/cli/commands/adapter.ts
|
|
9008
7722
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
@@ -9010,12 +7724,12 @@ import {
|
|
|
9010
7724
|
copyFileSync,
|
|
9011
7725
|
existsSync as existsSync12,
|
|
9012
7726
|
mkdirSync as mkdirSync8,
|
|
9013
|
-
readFileSync as
|
|
9014
|
-
statSync as
|
|
7727
|
+
readFileSync as readFileSync9,
|
|
7728
|
+
statSync as statSync2,
|
|
9015
7729
|
writeFileSync as writeFileSync7
|
|
9016
7730
|
} from "fs";
|
|
9017
|
-
import { homedir as
|
|
9018
|
-
import { dirname as
|
|
7731
|
+
import { homedir as homedir4 } from "os";
|
|
7732
|
+
import { dirname as dirname7, join as join11 } from "path";
|
|
9019
7733
|
import { Command } from "commander";
|
|
9020
7734
|
init_util();
|
|
9021
7735
|
init_ui();
|
|
@@ -9028,7 +7742,7 @@ var INSTALL_SPECS = {
|
|
|
9028
7742
|
binary: "claude",
|
|
9029
7743
|
hint: "Set ANTHROPIC_API_KEY in your shell profile, or rely on Claude Code OAuth login. Run `claude login` to sign in.",
|
|
9030
7744
|
authHints: ["ANTHROPIC_API_KEY or Claude Code OAuth login (`claude login`)"],
|
|
9031
|
-
hookTarget: { path:
|
|
7745
|
+
hookTarget: { path: join11(homedir4(), ".claude", "hooks.json"), kind: "json-file" }
|
|
9032
7746
|
},
|
|
9033
7747
|
openclaw: {
|
|
9034
7748
|
name: "openclaw",
|
|
@@ -9037,7 +7751,7 @@ var INSTALL_SPECS = {
|
|
|
9037
7751
|
binary: "openclaw",
|
|
9038
7752
|
hint: "OpenClaw runs as a gateway. Configure ~/.openclaw/openclaw.json and start it with `openclaw gateway` before tasks dispatch.",
|
|
9039
7753
|
authHints: ["~/.openclaw/openclaw.json gateway.auth.bearerTokens", "Prismer daemon api_key"],
|
|
9040
|
-
hookTarget: { path:
|
|
7754
|
+
hookTarget: { path: join11(homedir4(), ".openclaw", "hooks"), kind: "directory" }
|
|
9041
7755
|
},
|
|
9042
7756
|
codex: {
|
|
9043
7757
|
name: "codex",
|
|
@@ -9046,7 +7760,7 @@ var INSTALL_SPECS = {
|
|
|
9046
7760
|
binary: "codex",
|
|
9047
7761
|
hint: "Set OPENAI_API_KEY in your shell profile. Verify with `codex --help`.",
|
|
9048
7762
|
authHints: ["OPENAI_API_KEY or Codex CLI account login"],
|
|
9049
|
-
hookTarget: { path:
|
|
7763
|
+
hookTarget: { path: join11(homedir4(), ".codex", "hooks.json"), kind: "json-file" }
|
|
9050
7764
|
},
|
|
9051
7765
|
hermes: {
|
|
9052
7766
|
name: "hermes",
|
|
@@ -9055,7 +7769,7 @@ var INSTALL_SPECS = {
|
|
|
9055
7769
|
binary: "hermes",
|
|
9056
7770
|
hint: "Hermes runs as a long-lived HTTP gateway in your own Python venv. Start with `hermes -p <profile> gateway` after configuring ~/.hermes/.env.",
|
|
9057
7771
|
authHints: ["~/.hermes/.env API_SERVER_KEY", "Hermes profile provider credentials"],
|
|
9058
|
-
hookTarget: { path:
|
|
7772
|
+
hookTarget: { path: join11(homedir4(), ".hermes", "hooks.json"), kind: "json-file" }
|
|
9059
7773
|
}
|
|
9060
7774
|
};
|
|
9061
7775
|
function buildAdapterCommand() {
|
|
@@ -9143,8 +7857,8 @@ function buildAdapterCommand() {
|
|
|
9143
7857
|
});
|
|
9144
7858
|
return cmd;
|
|
9145
7859
|
}
|
|
9146
|
-
function runInstall(spec,
|
|
9147
|
-
const { argv0, args } = installCommand(spec,
|
|
7860
|
+
function runInstall(spec, version2, dryRun) {
|
|
7861
|
+
const { argv0, args } = installCommand(spec, version2);
|
|
9148
7862
|
getUI().line(`> ${argv0} ${args.join(" ")}`);
|
|
9149
7863
|
if (dryRun) {
|
|
9150
7864
|
getUI().line("(dry-run \u2014 not executing)");
|
|
@@ -9271,11 +7985,11 @@ function runHooks(spec, opts) {
|
|
|
9271
7985
|
let backup;
|
|
9272
7986
|
if (planned.kind === "directory") {
|
|
9273
7987
|
mkdirSync8(planned.path, { recursive: true });
|
|
9274
|
-
const markerPath =
|
|
7988
|
+
const markerPath = join11(planned.path, "prismer.json");
|
|
9275
7989
|
backup = backupIfExists(markerPath);
|
|
9276
7990
|
writeFileSync7(markerPath, JSON.stringify(prismerHookMarker(spec), null, 2) + "\n", "utf8");
|
|
9277
7991
|
} else {
|
|
9278
|
-
mkdirSync8(
|
|
7992
|
+
mkdirSync8(dirname7(planned.path), { recursive: true });
|
|
9279
7993
|
backup = backupIfExists(planned.path);
|
|
9280
7994
|
const merged = mergeHookJson(planned.path, spec);
|
|
9281
7995
|
writeFileSync7(planned.path, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
@@ -9288,16 +8002,16 @@ function runHooks(spec, opts) {
|
|
|
9288
8002
|
hook: inspectHook(spec)
|
|
9289
8003
|
};
|
|
9290
8004
|
}
|
|
9291
|
-
function installCommand(spec,
|
|
9292
|
-
const ref =
|
|
8005
|
+
function installCommand(spec, version2) {
|
|
8006
|
+
const ref = version2 === "latest" ? spec.package : `${spec.package}@${version2}`;
|
|
9293
8007
|
switch (spec.manager) {
|
|
9294
8008
|
case "npm":
|
|
9295
8009
|
return { argv0: "npm", args: ["install", "-g", ref] };
|
|
9296
8010
|
case "pipx":
|
|
9297
|
-
const pyRef =
|
|
8011
|
+
const pyRef = version2 === "latest" ? spec.package : `${spec.package}==${version2}`;
|
|
9298
8012
|
return { argv0: "pipx", args: ["install", pyRef] };
|
|
9299
8013
|
case "pip":
|
|
9300
|
-
const pipRef =
|
|
8014
|
+
const pipRef = version2 === "latest" ? spec.package : `${spec.package}==${version2}`;
|
|
9301
8015
|
return { argv0: "pip", args: ["install", "--user", pipRef] };
|
|
9302
8016
|
}
|
|
9303
8017
|
}
|
|
@@ -9345,7 +8059,7 @@ function inspectAuthEnv(spec) {
|
|
|
9345
8059
|
hints: spec.authHints ?? []
|
|
9346
8060
|
};
|
|
9347
8061
|
case "hermes": {
|
|
9348
|
-
const envFile =
|
|
8062
|
+
const envFile = join11(homedir4(), ".hermes", ".env");
|
|
9349
8063
|
return {
|
|
9350
8064
|
ok: existsSync12(envFile) || Boolean(process.env.HERMES_API_KEY || process.env.API_SERVER_KEY),
|
|
9351
8065
|
present: [
|
|
@@ -9356,7 +8070,7 @@ function inspectAuthEnv(spec) {
|
|
|
9356
8070
|
};
|
|
9357
8071
|
}
|
|
9358
8072
|
case "openclaw": {
|
|
9359
|
-
const cfgFile =
|
|
8073
|
+
const cfgFile = join11(homedir4(), ".openclaw", "openclaw.json");
|
|
9360
8074
|
return {
|
|
9361
8075
|
ok: existsSync12(cfgFile) || Boolean(process.env.OPENCLAW_API_KEY),
|
|
9362
8076
|
present: [
|
|
@@ -9376,8 +8090,8 @@ function inspectHook(spec) {
|
|
|
9376
8090
|
return { supported: false, markerPresent: false };
|
|
9377
8091
|
}
|
|
9378
8092
|
if (spec.name === "openclaw") {
|
|
9379
|
-
const jsonPath =
|
|
9380
|
-
const markerPath =
|
|
8093
|
+
const jsonPath = join11(homedir4(), ".openclaw", "hooks.json");
|
|
8094
|
+
const markerPath = join11(target.path, "prismer.json");
|
|
9381
8095
|
const jsonMarkerPresent = existsSync12(jsonPath) && fileContainsPrismerMarker(jsonPath);
|
|
9382
8096
|
const dirMarkerPresent = existsSync12(markerPath) && fileContainsPrismerMarker(markerPath);
|
|
9383
8097
|
return {
|
|
@@ -9391,7 +8105,7 @@ function inspectHook(spec) {
|
|
|
9391
8105
|
};
|
|
9392
8106
|
}
|
|
9393
8107
|
if (target.kind === "directory") {
|
|
9394
|
-
const markerPath =
|
|
8108
|
+
const markerPath = join11(target.path, "prismer.json");
|
|
9395
8109
|
return {
|
|
9396
8110
|
supported: true,
|
|
9397
8111
|
kind: target.kind,
|
|
@@ -9435,21 +8149,21 @@ function prismerHookMarker(spec) {
|
|
|
9435
8149
|
installed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9436
8150
|
};
|
|
9437
8151
|
}
|
|
9438
|
-
function mergeHookJson(
|
|
8152
|
+
function mergeHookJson(path7, spec) {
|
|
9439
8153
|
const marker = prismerHookMarker(spec);
|
|
9440
|
-
if (!existsSync12(
|
|
8154
|
+
if (!existsSync12(path7)) {
|
|
9441
8155
|
return {
|
|
9442
8156
|
prismer: marker
|
|
9443
8157
|
};
|
|
9444
8158
|
}
|
|
9445
8159
|
let parsed;
|
|
9446
8160
|
try {
|
|
9447
|
-
parsed = JSON.parse(
|
|
8161
|
+
parsed = JSON.parse(readFileSync9(path7, "utf8"));
|
|
9448
8162
|
} catch (err) {
|
|
9449
|
-
throw new Error(`Cannot parse ${
|
|
8163
|
+
throw new Error(`Cannot parse ${path7} as JSON: ${err.message}`);
|
|
9450
8164
|
}
|
|
9451
8165
|
if (!isRecord(parsed)) {
|
|
9452
|
-
throw new Error(`Cannot merge ${
|
|
8166
|
+
throw new Error(`Cannot merge ${path7}: expected a JSON object`);
|
|
9453
8167
|
}
|
|
9454
8168
|
const next = { ...parsed };
|
|
9455
8169
|
next.prismer = marker;
|
|
@@ -9467,18 +8181,18 @@ function mergeHookJson(path9, spec) {
|
|
|
9467
8181
|
}
|
|
9468
8182
|
return next;
|
|
9469
8183
|
}
|
|
9470
|
-
function backupIfExists(
|
|
9471
|
-
if (!existsSync12(
|
|
8184
|
+
function backupIfExists(path7) {
|
|
8185
|
+
if (!existsSync12(path7)) return null;
|
|
9472
8186
|
const suffix = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9473
|
-
const backup = `${
|
|
9474
|
-
const stat =
|
|
8187
|
+
const backup = `${path7}.bak.${suffix}`;
|
|
8188
|
+
const stat = statSync2(path7);
|
|
9475
8189
|
if (stat.isDirectory()) return null;
|
|
9476
|
-
copyFileSync(
|
|
8190
|
+
copyFileSync(path7, backup);
|
|
9477
8191
|
return backup;
|
|
9478
8192
|
}
|
|
9479
|
-
function fileContainsPrismerMarker(
|
|
8193
|
+
function fileContainsPrismerMarker(path7) {
|
|
9480
8194
|
try {
|
|
9481
|
-
return
|
|
8195
|
+
return readFileSync9(path7, "utf8").includes("prismer-daemon-runtime");
|
|
9482
8196
|
} catch {
|
|
9483
8197
|
return false;
|
|
9484
8198
|
}
|
|
@@ -9496,7 +8210,7 @@ function readAdapterConfig() {
|
|
|
9496
8210
|
return {};
|
|
9497
8211
|
}
|
|
9498
8212
|
}
|
|
9499
|
-
function recordInstallInConfig(spec, binPath,
|
|
8213
|
+
function recordInstallInConfig(spec, binPath, version2) {
|
|
9500
8214
|
const paths = resolvePaths();
|
|
9501
8215
|
if (!configExists(paths)) {
|
|
9502
8216
|
process.stderr.write(
|
|
@@ -9518,7 +8232,7 @@ function recordInstallInConfig(spec, binPath, version) {
|
|
|
9518
8232
|
package_manager: spec.manager,
|
|
9519
8233
|
package_name: spec.package,
|
|
9520
8234
|
binary: binPath,
|
|
9521
|
-
version,
|
|
8235
|
+
version: version2,
|
|
9522
8236
|
installed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9523
8237
|
};
|
|
9524
8238
|
saveConfig({ ...cfg, adapters }, paths);
|
|
@@ -9898,7 +8612,7 @@ async function fetchCloudProfiles(cloud, imUserId) {
|
|
|
9898
8612
|
async function fetchLocalServer() {
|
|
9899
8613
|
const paths = resolvePaths();
|
|
9900
8614
|
const pid = readPidFile(paths);
|
|
9901
|
-
if (!pid || !
|
|
8615
|
+
if (!pid || !pidAlive(pid)) return { ok: false, error: "daemon process is not running", agents: [] };
|
|
9902
8616
|
try {
|
|
9903
8617
|
const [healthRes, agentsRes] = await Promise.all([
|
|
9904
8618
|
fetch(`${LOCAL_SERVER}/healthz`, { signal: AbortSignal.timeout(1e3) }),
|
|
@@ -9922,38 +8636,19 @@ function whichBinary2(bin) {
|
|
|
9922
8636
|
|
|
9923
8637
|
// src/cli/commands/asset.ts
|
|
9924
8638
|
import { Command as Command3 } from "commander";
|
|
9925
|
-
import { existsSync as existsSync13,
|
|
9926
|
-
import { basename as
|
|
8639
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
8640
|
+
import { basename as basename2 } from "path";
|
|
9927
8641
|
init_util();
|
|
9928
8642
|
init_ui();
|
|
9929
8643
|
function buildAssetCommand() {
|
|
9930
8644
|
const cmd = new Command3("asset").description("Inspect IM assets");
|
|
9931
|
-
cmd.command("sync").description("Sync remote asset indexes into the local daemon DB; optionally prefetch bytes").requiredOption("--workspace-id <id>", "Workspace id").option("--bytes", "Also prefetch asset bytes into ~/.prismer/cache").option("--limit <n>", "Maximum number of assets to prefetch when --bytes is set", parsePositiveInt).option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
|
|
9932
|
-
const result = await syncAssets(opts);
|
|
9933
|
-
if (opts.json) {
|
|
9934
|
-
printJson({ ok: true, data: result });
|
|
9935
|
-
return;
|
|
9936
|
-
}
|
|
9937
|
-
getUI().line(`Synced asset metadata: ${result.metadata.applied} applied (cursor=${result.metadata.cursor})`);
|
|
9938
|
-
getUI().line(`Synced workspace files: ${result.workspaceFiles.applied} applied (cursor=${result.workspaceFiles.cursor ?? "null"})`);
|
|
9939
|
-
if (opts.bytes) {
|
|
9940
|
-
getUI().line(
|
|
9941
|
-
`Prefetched bytes: ${result.prefetch.fetched}/${result.prefetch.considered} assets, ${result.prefetch.bytes} bytes`
|
|
9942
|
-
);
|
|
9943
|
-
if (result.prefetch.failed.length > 0) {
|
|
9944
|
-
getUI().line(`Prefetch failures: ${result.prefetch.failed.length}`);
|
|
9945
|
-
}
|
|
9946
|
-
} else {
|
|
9947
|
-
getUI().line("Asset bytes were not prefetched; they are downloaded lazily on first use. Run with --bytes to fill ~/.prismer/cache.");
|
|
9948
|
-
}
|
|
9949
|
-
}, { code: "asset_sync_failed" }));
|
|
9950
8645
|
cmd.command("list").description("List assets for a workspace, optionally filtered by task").option("--workspace-id <id>", "Workspace id").option("--task-id <id>", "Task id filter").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
|
|
9951
8646
|
const cloud = mkCloud();
|
|
9952
8647
|
const query = new URLSearchParams();
|
|
9953
8648
|
if (opts.workspaceId) query.set("workspaceId", opts.workspaceId);
|
|
9954
8649
|
if (opts.taskId) query.set("taskId", opts.taskId);
|
|
9955
|
-
const
|
|
9956
|
-
const res = await cloud.request("GET",
|
|
8650
|
+
const path7 = `/api/im/assets${query.toString() ? `?${query.toString()}` : ""}`;
|
|
8651
|
+
const res = await cloud.request("GET", path7);
|
|
9957
8652
|
if (!res.ok) exitWithError(`asset list failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_list_failed" });
|
|
9958
8653
|
const body = res.data;
|
|
9959
8654
|
if (isErrorEnvelope(body)) {
|
|
@@ -9965,20 +8660,13 @@ function buildAssetCommand() {
|
|
|
9965
8660
|
}
|
|
9966
8661
|
printAssetList(body);
|
|
9967
8662
|
}, { code: "asset_list_failed" }));
|
|
9968
|
-
cmd.command("upload <
|
|
9969
|
-
const body = await
|
|
8663
|
+
cmd.command("upload <file>").description("Upload a local file as an IM asset").requiredOption("--workspace-id <id>", "Workspace id").option("--kind <kind>", "Asset kind", "file").option("--task-id <id>", "Source task id").option("--agent-id <id>", "Source agent IM user id").option("--container-id <id>", "Container id; also added to metadata for sandbox-output assets").option("--metadata <json>", "Additional JSON metadata").option("--mime <type>", "Override file MIME type").option("--json", "Output machine-readable JSON").action(runAction(async (file, opts) => {
|
|
8664
|
+
const body = await uploadAsset(file, opts);
|
|
9970
8665
|
if (opts.json) {
|
|
9971
8666
|
printJson(body ?? null);
|
|
9972
8667
|
return;
|
|
9973
8668
|
}
|
|
9974
|
-
|
|
9975
|
-
getUI().line(`Uploaded ${body.data.count} assets`);
|
|
9976
|
-
for (const item of body.data.items) {
|
|
9977
|
-
printAssetDetail("Uploaded asset", item.file, item.response);
|
|
9978
|
-
}
|
|
9979
|
-
} else {
|
|
9980
|
-
printAssetDetail("Uploaded asset", inputPath, body);
|
|
9981
|
-
}
|
|
8669
|
+
printAssetDetail("Uploaded asset", file, body);
|
|
9982
8670
|
}, { code: "asset_upload_failed" }));
|
|
9983
8671
|
cmd.command("download <assetId>").description("Download asset bytes").requiredOption("--out <path>", "Destination file path").action(runAction(async (assetId, opts) => {
|
|
9984
8672
|
await downloadAsset(assetId, opts.out);
|
|
@@ -9998,12 +8686,12 @@ function buildAssetCommand() {
|
|
|
9998
8686
|
}
|
|
9999
8687
|
printAssetDetail("Asset", assetId, body);
|
|
10000
8688
|
}, { code: "asset_get_failed" }));
|
|
10001
|
-
cmd.command("by-hash <sha256>").description("Fetch asset metadata by content hash").option("--workspace-id <id>", "Workspace id (required by the cloud API)").option("--json", "Output machine-readable JSON").action(runAction(async (
|
|
8689
|
+
cmd.command("by-hash <sha256>").description("Fetch asset metadata by content hash").option("--workspace-id <id>", "Workspace id (required by the cloud API)").option("--json", "Output machine-readable JSON").action(runAction(async (sha2562, opts) => {
|
|
10002
8690
|
const cloud = mkCloud();
|
|
10003
8691
|
const query = new URLSearchParams();
|
|
10004
8692
|
if (opts.workspaceId) query.set("wsId", opts.workspaceId);
|
|
10005
|
-
const
|
|
10006
|
-
const res = await cloud.request("GET",
|
|
8693
|
+
const path7 = `/api/im/assets/by-hash/${encodeURIComponent(sha2562)}${query.toString() ? `?${query.toString()}` : ""}`;
|
|
8694
|
+
const res = await cloud.request("GET", path7);
|
|
10007
8695
|
if (!res.ok) exitWithError(`asset by-hash failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_by_hash_failed" });
|
|
10008
8696
|
const body = res.data;
|
|
10009
8697
|
if (isErrorEnvelope(body)) {
|
|
@@ -10013,7 +8701,7 @@ function buildAssetCommand() {
|
|
|
10013
8701
|
printJson(body ?? null);
|
|
10014
8702
|
return;
|
|
10015
8703
|
}
|
|
10016
|
-
printAssetDetail("Asset hash",
|
|
8704
|
+
printAssetDetail("Asset hash", sha2562, body);
|
|
10017
8705
|
}, { code: "asset_by_hash_failed" }));
|
|
10018
8706
|
return cmd;
|
|
10019
8707
|
}
|
|
@@ -10021,102 +8709,10 @@ function mkCloud() {
|
|
|
10021
8709
|
const cfg = loadConfig(resolvePaths());
|
|
10022
8710
|
return new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
|
|
10023
8711
|
}
|
|
10024
|
-
async function syncAssets(opts) {
|
|
10025
|
-
const paths = resolvePaths();
|
|
10026
|
-
const cfg = loadConfig(paths);
|
|
10027
|
-
const cloud = new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
|
|
10028
|
-
const db = openLocalDb(paths.localDb);
|
|
10029
|
-
try {
|
|
10030
|
-
const stateDir = `${paths.root}/${opts.workspaceId}`;
|
|
10031
|
-
const metadataIndex = new AssetMetadataIndex({
|
|
10032
|
-
db,
|
|
10033
|
-
cloud,
|
|
10034
|
-
workspaceId: opts.workspaceId,
|
|
10035
|
-
workspaceStateDir: stateDir
|
|
10036
|
-
});
|
|
10037
|
-
const mirror = new WorkspaceMirror({
|
|
10038
|
-
db,
|
|
10039
|
-
cloud,
|
|
10040
|
-
workspaceId: opts.workspaceId,
|
|
10041
|
-
workspaceStateDir: stateDir
|
|
10042
|
-
});
|
|
10043
|
-
const [metadata, workspaceFiles] = await Promise.all([
|
|
10044
|
-
metadataIndex.pullDelta(),
|
|
10045
|
-
mirror.pullDelta()
|
|
10046
|
-
]);
|
|
10047
|
-
const prefetch = opts.bytes ? await prefetchAssetBytes({
|
|
10048
|
-
db,
|
|
10049
|
-
cloud,
|
|
10050
|
-
workspaceId: opts.workspaceId,
|
|
10051
|
-
cacheDir: paths.cacheDir,
|
|
10052
|
-
maxBytes: cfg.cache?.max_bytes,
|
|
10053
|
-
limit: opts.limit
|
|
10054
|
-
}) : { considered: 0, fetched: 0, bytes: 0, failed: [] };
|
|
10055
|
-
return { workspaceId: opts.workspaceId, metadata, workspaceFiles, prefetch };
|
|
10056
|
-
} finally {
|
|
10057
|
-
db.close();
|
|
10058
|
-
}
|
|
10059
|
-
}
|
|
10060
|
-
async function prefetchAssetBytes(opts) {
|
|
10061
|
-
const limitClause = opts.limit ? " LIMIT ?" : "";
|
|
10062
|
-
const params = [opts.workspaceId];
|
|
10063
|
-
if (opts.limit) params.push(opts.limit);
|
|
10064
|
-
const rows = opts.db.prepare(
|
|
10065
|
-
`SELECT asset_id, content_hash, mime, size_bytes
|
|
10066
|
-
FROM asset_metadata_index
|
|
10067
|
-
WHERE workspace_id = ?
|
|
10068
|
-
ORDER BY asset_index_seq DESC${limitClause}`
|
|
10069
|
-
).all(...params);
|
|
10070
|
-
const cache = new AssetCache({
|
|
10071
|
-
db: opts.db,
|
|
10072
|
-
cloud: opts.cloud,
|
|
10073
|
-
cacheDir: opts.cacheDir,
|
|
10074
|
-
maxBytes: opts.maxBytes
|
|
10075
|
-
});
|
|
10076
|
-
let fetched = 0;
|
|
10077
|
-
let bytes = 0;
|
|
10078
|
-
const failed = [];
|
|
10079
|
-
for (const row of rows) {
|
|
10080
|
-
try {
|
|
10081
|
-
const cached = await cache.getOrFetch(row.content_hash, {
|
|
10082
|
-
workspaceIdHint: opts.workspaceId,
|
|
10083
|
-
assetId: row.asset_id
|
|
10084
|
-
});
|
|
10085
|
-
fetched += 1;
|
|
10086
|
-
bytes += cached.sizeBytes;
|
|
10087
|
-
} catch (err) {
|
|
10088
|
-
failed.push({
|
|
10089
|
-
assetId: row.asset_id,
|
|
10090
|
-
contentHash: row.content_hash,
|
|
10091
|
-
error: err.message
|
|
10092
|
-
});
|
|
10093
|
-
}
|
|
10094
|
-
}
|
|
10095
|
-
return { considered: rows.length, fetched, bytes, failed };
|
|
10096
|
-
}
|
|
10097
|
-
async function uploadAssetPath(inputPath, opts) {
|
|
10098
|
-
if (!existsSync13(inputPath)) exitWithError(`path not found: ${inputPath}`);
|
|
10099
|
-
const stat = lstatSync(inputPath);
|
|
10100
|
-
if (stat.isSymbolicLink()) exitWithError(`refusing to upload symlink: ${inputPath}`);
|
|
10101
|
-
if (stat.isFile()) {
|
|
10102
|
-
return uploadAsset(inputPath, { ...opts, folderPath: normalizeFolderPath(opts.folderPath) });
|
|
10103
|
-
}
|
|
10104
|
-
if (!stat.isDirectory()) exitWithError(`path is not a file or directory: ${inputPath}`);
|
|
10105
|
-
const files = listFilesRecursive(inputPath);
|
|
10106
|
-
if (files.length === 0) exitWithError(`directory has no uploadable files: ${inputPath}`);
|
|
10107
|
-
const items = [];
|
|
10108
|
-
for (const file of files) {
|
|
10109
|
-
const relDir = dirname9(relative3(inputPath, file));
|
|
10110
|
-
const folderPath = combineFolderPath(opts.folderPath, relDir === "." ? "" : relDir);
|
|
10111
|
-
const response = await uploadAsset(file, { ...opts, folderPath });
|
|
10112
|
-
items.push({ file, folderPath: folderPath ?? null, response });
|
|
10113
|
-
}
|
|
10114
|
-
return { ok: true, data: { count: items.length, items } };
|
|
10115
|
-
}
|
|
10116
8712
|
async function uploadAsset(file, opts) {
|
|
10117
8713
|
if (!existsSync13(file)) exitWithError(`file not found: ${file}`);
|
|
10118
8714
|
const cfg = loadConfig(resolvePaths());
|
|
10119
|
-
const bytes =
|
|
8715
|
+
const bytes = readFileSync10(file);
|
|
10120
8716
|
const metadata = parseMetadata(opts.metadata);
|
|
10121
8717
|
if (opts.taskId && metadata.taskId === void 0) metadata.taskId = opts.taskId;
|
|
10122
8718
|
if (opts.containerId && metadata.containerId === void 0) metadata.containerId = opts.containerId;
|
|
@@ -10125,9 +8721,8 @@ async function uploadAsset(file, opts) {
|
|
|
10125
8721
|
form.set("kind", opts.kind);
|
|
10126
8722
|
if (opts.agentId) form.set("sourceAgentImUserId", opts.agentId);
|
|
10127
8723
|
if (opts.taskId) form.set("sourceTaskId", opts.taskId);
|
|
10128
|
-
if (opts.folderPath !== void 0) form.set("folderPath", opts.folderPath);
|
|
10129
8724
|
if (Object.keys(metadata).length > 0) form.set("metadata", JSON.stringify(metadata));
|
|
10130
|
-
form.set("file", new Blob([bytes], { type: opts.mime ?? "application/octet-stream" }),
|
|
8725
|
+
form.set("file", new Blob([bytes], { type: opts.mime ?? "application/octet-stream" }), basename2(file));
|
|
10131
8726
|
const url = `${cfg.cloud_api_base.replace(/\/$/, "")}/api/im/assets`;
|
|
10132
8727
|
let res;
|
|
10133
8728
|
try {
|
|
@@ -10145,37 +8740,6 @@ async function uploadAsset(file, opts) {
|
|
|
10145
8740
|
}
|
|
10146
8741
|
return body;
|
|
10147
8742
|
}
|
|
10148
|
-
function listFilesRecursive(root) {
|
|
10149
|
-
const out = [];
|
|
10150
|
-
for (const name of readdirSync(root)) {
|
|
10151
|
-
if (name.startsWith(".")) continue;
|
|
10152
|
-
const full = join16(root, name);
|
|
10153
|
-
const stat = lstatSync(full);
|
|
10154
|
-
if (stat.isSymbolicLink()) continue;
|
|
10155
|
-
if (stat.isDirectory()) {
|
|
10156
|
-
out.push(...listFilesRecursive(full));
|
|
10157
|
-
} else if (stat.isFile()) {
|
|
10158
|
-
out.push(full);
|
|
10159
|
-
}
|
|
10160
|
-
}
|
|
10161
|
-
return out.sort();
|
|
10162
|
-
}
|
|
10163
|
-
function combineFolderPath(prefix, relDir) {
|
|
10164
|
-
const normalizedPrefix = normalizeFolderPath(prefix);
|
|
10165
|
-
const normalizedRel = normalizeFolderPath(relDir);
|
|
10166
|
-
if (normalizedPrefix && normalizedRel) return `${normalizedPrefix}/${normalizedRel}`;
|
|
10167
|
-
return normalizedPrefix ?? normalizedRel;
|
|
10168
|
-
}
|
|
10169
|
-
function normalizeFolderPath(value) {
|
|
10170
|
-
if (value === void 0) return void 0;
|
|
10171
|
-
const normalized = value.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
10172
|
-
return normalized.length > 0 ? normalized : "";
|
|
10173
|
-
}
|
|
10174
|
-
function isUploadManyResult(raw) {
|
|
10175
|
-
return Boolean(
|
|
10176
|
-
raw && typeof raw === "object" && raw.ok === true && raw.data && Array.isArray(raw.data.items)
|
|
10177
|
-
);
|
|
10178
|
-
}
|
|
10179
8743
|
async function downloadAsset(assetId, outPath) {
|
|
10180
8744
|
const cloud = mkCloud();
|
|
10181
8745
|
const res = await cloud.fetchRaw(`/api/im/assets/${encodeURIComponent(assetId)}`);
|
|
@@ -10269,13 +8833,6 @@ function stringField(obj, key) {
|
|
|
10269
8833
|
const value = obj?.[key];
|
|
10270
8834
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
10271
8835
|
}
|
|
10272
|
-
function parsePositiveInt(raw) {
|
|
10273
|
-
const n = Number(raw);
|
|
10274
|
-
if (!Number.isInteger(n) || n <= 0) {
|
|
10275
|
-
throw new Error("--limit must be a positive integer");
|
|
10276
|
-
}
|
|
10277
|
-
return n;
|
|
10278
|
-
}
|
|
10279
8836
|
|
|
10280
8837
|
// src/cli/commands/banner.ts
|
|
10281
8838
|
init_util();
|
|
@@ -10331,7 +8888,7 @@ ${name} (${role})`);
|
|
|
10331
8888
|
getUI().line(`Group created ${data.groupId ?? "(unknown)"}
|
|
10332
8889
|
${data.title ?? opts.name} (${memberCount} members)`);
|
|
10333
8890
|
});
|
|
10334
|
-
group.command("messages <groupId>").description("List group messages").option("--limit <n>", "Max messages",
|
|
8891
|
+
group.command("messages <groupId>").description("List group messages").option("--limit <n>", "Max messages", parsePositiveInt).option("--before <id>", "Return messages before this message id").option("--json", "Print raw JSON response").action(async (groupId, opts) => {
|
|
10335
8892
|
const data = await requestOrExit(
|
|
10336
8893
|
"GET",
|
|
10337
8894
|
withQuery(`/api/im/groups/${encodeURIComponent(groupId)}/messages`, {
|
|
@@ -10369,7 +8926,7 @@ ${data.title ?? opts.name} (${memberCount} members)`);
|
|
|
10369
8926
|
getUI().line(`Removed ${memberId} from group ${groupId}`);
|
|
10370
8927
|
});
|
|
10371
8928
|
cmd.addCommand(group);
|
|
10372
|
-
cmd.command("messages <conversationId>").description("List conversation messages").option("--limit <n>", "Max messages",
|
|
8929
|
+
cmd.command("messages <conversationId>").description("List conversation messages").option("--limit <n>", "Max messages", parsePositiveInt).option("--before <id>", "Return messages before this message id").option("--json", "Print raw JSON response").action(async (conversationId, opts) => {
|
|
10373
8930
|
const data = await requestOrExit(
|
|
10374
8931
|
"GET",
|
|
10375
8932
|
withQuery(`/api/im/messages/${encodeURIComponent(conversationId)}`, {
|
|
@@ -10385,15 +8942,15 @@ ${data.title ?? opts.name} (${memberCount} members)`);
|
|
|
10385
8942
|
});
|
|
10386
8943
|
return cmd;
|
|
10387
8944
|
}
|
|
10388
|
-
async function requestOrExit(method,
|
|
8945
|
+
async function requestOrExit(method, path7, body) {
|
|
10389
8946
|
let res;
|
|
10390
8947
|
try {
|
|
10391
|
-
res = await mkCloud2().request(method,
|
|
8948
|
+
res = await mkCloud2().request(method, path7, body === void 0 ? void 0 : { body });
|
|
10392
8949
|
} catch (err) {
|
|
10393
8950
|
exitWithError(sanitizeError(err.message));
|
|
10394
8951
|
}
|
|
10395
8952
|
if (!res.ok) {
|
|
10396
|
-
exitWithError(`${method} ${
|
|
8953
|
+
exitWithError(`${method} ${path7} failed (${res.status}): ${sanitizeError(res.error?.message ?? "unknown")}`);
|
|
10397
8954
|
}
|
|
10398
8955
|
return unwrapEnvelope2(res.data);
|
|
10399
8956
|
}
|
|
@@ -10425,20 +8982,20 @@ function unwrapEnvelope2(raw) {
|
|
|
10425
8982
|
function parseCsv(value) {
|
|
10426
8983
|
return value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
10427
8984
|
}
|
|
10428
|
-
function
|
|
8985
|
+
function parsePositiveInt(value) {
|
|
10429
8986
|
const parsed = Number.parseInt(value, 10);
|
|
10430
8987
|
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
10431
8988
|
throw new Error("expected a positive integer");
|
|
10432
8989
|
}
|
|
10433
8990
|
return parsed;
|
|
10434
8991
|
}
|
|
10435
|
-
function withQuery(
|
|
8992
|
+
function withQuery(path7, params) {
|
|
10436
8993
|
const query = new URLSearchParams();
|
|
10437
8994
|
for (const [key, value] of Object.entries(params)) {
|
|
10438
8995
|
if (value !== void 0) query.set(key, String(value));
|
|
10439
8996
|
}
|
|
10440
8997
|
const suffix = query.toString();
|
|
10441
|
-
return suffix ? `${
|
|
8998
|
+
return suffix ? `${path7}?${suffix}` : path7;
|
|
10442
8999
|
}
|
|
10443
9000
|
function printMessageSummary(label, data) {
|
|
10444
9001
|
const msg = data.message ?? {};
|
|
@@ -10565,7 +9122,7 @@ init_util();
|
|
|
10565
9122
|
init_ui();
|
|
10566
9123
|
function buildCookbookCommand() {
|
|
10567
9124
|
const cmd = new Command7("cookbook").description("Run CLI-only 54release MVP regression suites");
|
|
10568
|
-
cmd.command("run").description("Run one or more cookbook smoke suites using the configured API key").option("--suite <name>", "status|im|task|group|asset|sandbox|all, comma-separated", "all").option("--workspace-id <id>", "Workspace id for workspace-scoped suites").option("--agent-id <id>", "Agent IM user id for task create smoke").option("--group-id <id>", "Group/conversation id for group message history smoke").option("--sandbox-id <id>", "Sandbox id for sandbox status smoke").option("--prompt <text>", "Prompt for optional task create smoke").option("--timeout-ms <ms>", "Request/task timeout",
|
|
9125
|
+
cmd.command("run").description("Run one or more cookbook smoke suites using the configured API key").option("--suite <name>", "status|im|task|group|asset|sandbox|all, comma-separated", "all").option("--workspace-id <id>", "Workspace id for workspace-scoped suites").option("--agent-id <id>", "Agent IM user id for task create smoke").option("--group-id <id>", "Group/conversation id for group message history smoke").option("--sandbox-id <id>", "Sandbox id for sandbox status smoke").option("--prompt <text>", "Prompt for optional task create smoke").option("--timeout-ms <ms>", "Request/task timeout", parsePositiveInt2, 6e4).option("--strict", "Treat skipped optional checks as failure").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
|
|
10569
9126
|
const suites = parseSuites(opts.suite);
|
|
10570
9127
|
const cloud = mkCloud3(opts.timeoutMs);
|
|
10571
9128
|
const checks = [];
|
|
@@ -10607,8 +9164,8 @@ async function runStatusSuite(cloud) {
|
|
|
10607
9164
|
await check("status", "cloud health", "GET", "/api/health"),
|
|
10608
9165
|
await check("status", "current IM identity", "GET", "/api/im/me", void 0, cloud)
|
|
10609
9166
|
];
|
|
10610
|
-
async function check(suite, name, method,
|
|
10611
|
-
return requestCheck(client, suite, name, method,
|
|
9167
|
+
async function check(suite, name, method, path7, body, client = cloud) {
|
|
9168
|
+
return requestCheck(client, suite, name, method, path7, body);
|
|
10612
9169
|
}
|
|
10613
9170
|
}
|
|
10614
9171
|
async function runIMSuite(cloud) {
|
|
@@ -10682,27 +9239,27 @@ async function runSandboxSuite(cloud, opts) {
|
|
|
10682
9239
|
}
|
|
10683
9240
|
return checks;
|
|
10684
9241
|
}
|
|
10685
|
-
async function requestCheck(cloud, suite, name, method,
|
|
9242
|
+
async function requestCheck(cloud, suite, name, method, path7, body) {
|
|
10686
9243
|
let res;
|
|
10687
9244
|
try {
|
|
10688
|
-
res = await cloud.request(method,
|
|
9245
|
+
res = await cloud.request(method, path7, body === void 0 ? void 0 : { body });
|
|
10689
9246
|
} catch (err) {
|
|
10690
|
-
return { suite, name, status: "fail", endpoint:
|
|
9247
|
+
return { suite, name, status: "fail", endpoint: path7, detail: sanitize(err.message) };
|
|
10691
9248
|
}
|
|
10692
9249
|
if (!res.ok) {
|
|
10693
9250
|
return {
|
|
10694
9251
|
suite,
|
|
10695
9252
|
name,
|
|
10696
9253
|
status: "fail",
|
|
10697
|
-
endpoint:
|
|
9254
|
+
endpoint: path7,
|
|
10698
9255
|
detail: sanitize(`${res.status}: ${res.error?.message ?? "request failed"}`)
|
|
10699
9256
|
};
|
|
10700
9257
|
}
|
|
10701
9258
|
const data = unwrap(res.data);
|
|
10702
9259
|
if (isApiError(res.data)) {
|
|
10703
|
-
return { suite, name, status: "fail", endpoint:
|
|
9260
|
+
return { suite, name, status: "fail", endpoint: path7, detail: sanitize(apiErrorMessage(res.data)), data: res.data };
|
|
10704
9261
|
}
|
|
10705
|
-
return { suite, name, status: "pass", endpoint:
|
|
9262
|
+
return { suite, name, status: "pass", endpoint: path7, detail: summarizeData(data), data };
|
|
10706
9263
|
}
|
|
10707
9264
|
async function pollTask(cloud, taskId, timeoutMs) {
|
|
10708
9265
|
const started = Date.now();
|
|
@@ -10809,7 +9366,7 @@ function taskFrom(data) {
|
|
|
10809
9366
|
function sanitize(message) {
|
|
10810
9367
|
return message.replace(/sk-prismer-[A-Za-z0-9_-]+/g, "sk-prismer-***");
|
|
10811
9368
|
}
|
|
10812
|
-
function
|
|
9369
|
+
function parsePositiveInt2(value) {
|
|
10813
9370
|
const parsed = Number.parseInt(value, 10);
|
|
10814
9371
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
10815
9372
|
throw new Error("expected a positive integer");
|
|
@@ -10820,9 +9377,9 @@ function parsePositiveInt3(value) {
|
|
|
10820
9377
|
// src/cli/commands/daemon.ts
|
|
10821
9378
|
import { Command as Command8 } from "commander";
|
|
10822
9379
|
import { spawn as spawn5 } from "child_process";
|
|
10823
|
-
import { createReadStream, existsSync as existsSync14, mkdirSync as mkdirSync9, openSync
|
|
10824
|
-
import { setTimeout as
|
|
10825
|
-
import { join as
|
|
9380
|
+
import { createReadStream, existsSync as existsSync14, mkdirSync as mkdirSync9, openSync, statSync as statSync3 } from "fs";
|
|
9381
|
+
import { setTimeout as sleep2 } from "timers/promises";
|
|
9382
|
+
import { join as join12 } from "path";
|
|
10826
9383
|
init_util();
|
|
10827
9384
|
init_ui();
|
|
10828
9385
|
function buildDaemonCommand() {
|
|
@@ -10840,7 +9397,7 @@ function buildDaemonCommand() {
|
|
|
10840
9397
|
async function runForeground(opts) {
|
|
10841
9398
|
const paths = resolvePaths();
|
|
10842
9399
|
const existingPid = readPidFile(paths);
|
|
10843
|
-
if (existingPid &&
|
|
9400
|
+
if (existingPid && pidAlive(existingPid)) {
|
|
10844
9401
|
exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
|
|
10845
9402
|
}
|
|
10846
9403
|
writePidFile(paths, process.pid);
|
|
@@ -10864,7 +9421,7 @@ function buildDaemonCommand() {
|
|
|
10864
9421
|
while (!configExists(paths)) {
|
|
10865
9422
|
if (stopRequested) return;
|
|
10866
9423
|
getUI().info(`[daemon] waiting for ${paths.configFile} \u2014 run \`prismer setup\` to create it`);
|
|
10867
|
-
await
|
|
9424
|
+
await sleep2(5e3);
|
|
10868
9425
|
}
|
|
10869
9426
|
runner = new Runner({
|
|
10870
9427
|
startLocalServer: opts.localServer !== false,
|
|
@@ -10893,12 +9450,12 @@ function buildDaemonCommand() {
|
|
|
10893
9450
|
async function startBackground(opts) {
|
|
10894
9451
|
const paths = resolvePaths();
|
|
10895
9452
|
const existingPid = readPidFile(paths);
|
|
10896
|
-
if (existingPid &&
|
|
9453
|
+
if (existingPid && pidAlive(existingPid)) {
|
|
10897
9454
|
exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
|
|
10898
9455
|
}
|
|
10899
9456
|
if (!existsSync14(paths.logsDir)) mkdirSync9(paths.logsDir, { recursive: true });
|
|
10900
|
-
const logFile =
|
|
10901
|
-
const fd =
|
|
9457
|
+
const logFile = join12(paths.logsDir, "daemon.log");
|
|
9458
|
+
const fd = openSync(logFile, "a");
|
|
10902
9459
|
const args = [process.argv[1], "daemon", "run"];
|
|
10903
9460
|
if (opts.port) args.push("--port", String(opts.port));
|
|
10904
9461
|
if (opts.localServer === false) args.push("--no-local-server");
|
|
@@ -10910,9 +9467,9 @@ function buildDaemonCommand() {
|
|
|
10910
9467
|
child.unref();
|
|
10911
9468
|
const deadline = Date.now() + 5e3;
|
|
10912
9469
|
while (Date.now() < deadline) {
|
|
10913
|
-
await
|
|
9470
|
+
await sleep2(200);
|
|
10914
9471
|
const pid = readPidFile(paths);
|
|
10915
|
-
if (pid &&
|
|
9472
|
+
if (pid && pidAlive(pid)) {
|
|
10916
9473
|
if (opts.json) printJson({ ok: true, pid, logFile });
|
|
10917
9474
|
else getUI().ok("Daemon started", `pid ${pid} \xB7 logs ${logFile}`);
|
|
10918
9475
|
return;
|
|
@@ -10927,7 +9484,7 @@ function buildDaemonCommand() {
|
|
|
10927
9484
|
const paths = resolvePaths();
|
|
10928
9485
|
const pid = readPidFile(paths);
|
|
10929
9486
|
if (!pid) exitWithError("No daemon.pid found \u2014 daemon does not appear to be running.");
|
|
10930
|
-
if (!
|
|
9487
|
+
if (!pidAlive(pid)) {
|
|
10931
9488
|
clearPidFile(paths);
|
|
10932
9489
|
getUI().warn("Daemon was not alive", `pid ${pid}; cleared stale pid file`);
|
|
10933
9490
|
return;
|
|
@@ -10939,12 +9496,12 @@ function buildDaemonCommand() {
|
|
|
10939
9496
|
}
|
|
10940
9497
|
const deadline = Date.now() + (opts.timeout ?? 5e3);
|
|
10941
9498
|
while (Date.now() < deadline) {
|
|
10942
|
-
if (!
|
|
9499
|
+
if (!pidAlive(pid)) {
|
|
10943
9500
|
clearPidFile(paths);
|
|
10944
9501
|
getUI().ok("Daemon stopped", `pid ${pid}`);
|
|
10945
9502
|
return;
|
|
10946
9503
|
}
|
|
10947
|
-
await
|
|
9504
|
+
await sleep2(200);
|
|
10948
9505
|
}
|
|
10949
9506
|
process.stderr.write(`Daemon did not exit within timeout; pid ${pid} may still be alive.
|
|
10950
9507
|
`);
|
|
@@ -10964,7 +9521,7 @@ function buildDaemonCommand() {
|
|
|
10964
9521
|
cmd.command("status").description("Show daemon process + WS state").option("--json", "Output JSON (default)").action(async () => {
|
|
10965
9522
|
const paths = resolvePaths();
|
|
10966
9523
|
const pid = readPidFile(paths);
|
|
10967
|
-
const alive = pid ?
|
|
9524
|
+
const alive = pid ? pidAlive(pid) : false;
|
|
10968
9525
|
if (!pid || !alive) {
|
|
10969
9526
|
printJson({ running: false, pid: pid ?? null, paths: { config: paths.configFile } });
|
|
10970
9527
|
return;
|
|
@@ -10981,7 +9538,7 @@ function buildDaemonCommand() {
|
|
|
10981
9538
|
});
|
|
10982
9539
|
cmd.command("logs").description("Show daemon logs").option("--tail <n>", "Number of lines to show", (v) => Number.parseInt(v, 10), 80).option("--follow", "Follow log output").option("--json", "Accept --json for global flag compatibility (raw log bytes are streamed)").action(async (opts) => {
|
|
10983
9540
|
const paths = resolvePaths();
|
|
10984
|
-
const logFile =
|
|
9541
|
+
const logFile = join12(paths.logsDir, "daemon.log");
|
|
10985
9542
|
if (!existsSync14(logFile)) {
|
|
10986
9543
|
exitWithError(`No daemon log found at ${logFile}. Start the daemon with \`prismer daemon start\`.`);
|
|
10987
9544
|
}
|
|
@@ -10992,9 +9549,9 @@ function buildDaemonCommand() {
|
|
|
10992
9549
|
});
|
|
10993
9550
|
return cmd;
|
|
10994
9551
|
}
|
|
10995
|
-
async function tailFromEnd(
|
|
9552
|
+
async function tailFromEnd(path7, lines) {
|
|
10996
9553
|
const { open } = await import("fs/promises");
|
|
10997
|
-
const handle = await open(
|
|
9554
|
+
const handle = await open(path7, "r");
|
|
10998
9555
|
try {
|
|
10999
9556
|
const stat = await handle.stat();
|
|
11000
9557
|
let size = stat.size;
|
|
@@ -11022,14 +9579,14 @@ async function tailFromEnd(path9, lines) {
|
|
|
11022
9579
|
await handle.close();
|
|
11023
9580
|
}
|
|
11024
9581
|
}
|
|
11025
|
-
async function followFile(
|
|
11026
|
-
let offset =
|
|
9582
|
+
async function followFile(path7) {
|
|
9583
|
+
let offset = statSync3(path7).size;
|
|
11027
9584
|
for (; ; ) {
|
|
11028
|
-
await
|
|
11029
|
-
const size =
|
|
9585
|
+
await sleep2(1e3);
|
|
9586
|
+
const size = statSync3(path7).size;
|
|
11030
9587
|
if (size < offset) offset = 0;
|
|
11031
9588
|
if (size === offset) continue;
|
|
11032
|
-
const stream = createReadStream(
|
|
9589
|
+
const stream = createReadStream(path7, { start: offset, end: size - 1, encoding: "utf8" });
|
|
11033
9590
|
for await (const chunk of stream) process.stdout.write(chunk);
|
|
11034
9591
|
offset = size;
|
|
11035
9592
|
}
|
|
@@ -11039,8 +9596,8 @@ async function followFile(path9) {
|
|
|
11039
9596
|
init_util();
|
|
11040
9597
|
import { Command as Command9 } from "commander";
|
|
11041
9598
|
import { createReadStream as createReadStream2, existsSync as existsSync15 } from "fs";
|
|
11042
|
-
import { homedir as
|
|
11043
|
-
import { join as
|
|
9599
|
+
import { homedir as homedir5 } from "os";
|
|
9600
|
+
import { join as join13 } from "path";
|
|
11044
9601
|
import { createInterface } from "readline";
|
|
11045
9602
|
var DEFAULT_LIMIT2 = 50;
|
|
11046
9603
|
function buildEventsCommand() {
|
|
@@ -11078,10 +9635,10 @@ function buildEventsStatsCommand() {
|
|
|
11078
9635
|
});
|
|
11079
9636
|
}
|
|
11080
9637
|
function addEventOptions(cmd) {
|
|
11081
|
-
return cmd.option("--limit <n>", "Max events to return/read",
|
|
9638
|
+
return cmd.option("--limit <n>", "Max events to return/read", parsePositiveInt3, DEFAULT_LIMIT2).option("--agent-id <id>", "Filter by agent id").option("--session-id <id>", "Filter by session id").option("--family <name>", "Filter by event family").option("--type <name>", "Filter by event type").option("--json", "Output JSON (default)");
|
|
11082
9639
|
}
|
|
11083
9640
|
function eventsPath() {
|
|
11084
|
-
return
|
|
9641
|
+
return join13(process.env.PRISMER_HOME ?? join13(homedir5(), ".prismer"), "para", "events.jsonl");
|
|
11085
9642
|
}
|
|
11086
9643
|
function unavailable(file) {
|
|
11087
9644
|
return {
|
|
@@ -11159,13 +9716,13 @@ function normalizeFilters(opts) {
|
|
|
11159
9716
|
function cleanFilters(filters) {
|
|
11160
9717
|
return Object.fromEntries(Object.entries(filters).filter(([, value]) => value !== void 0));
|
|
11161
9718
|
}
|
|
11162
|
-
function
|
|
9719
|
+
function parsePositiveInt3(v) {
|
|
11163
9720
|
const n = Number.parseInt(v, 10);
|
|
11164
9721
|
return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMIT2;
|
|
11165
9722
|
}
|
|
11166
9723
|
|
|
11167
9724
|
// src/cli/commands/memory.ts
|
|
11168
|
-
import
|
|
9725
|
+
import Database4 from "better-sqlite3";
|
|
11169
9726
|
import { Command as Command10 } from "commander";
|
|
11170
9727
|
import { existsSync as existsSync16 } from "fs";
|
|
11171
9728
|
init_util();
|
|
@@ -11193,7 +9750,7 @@ function buildMemoryCommand() {
|
|
|
11193
9750
|
}));
|
|
11194
9751
|
process.exitCode = 1;
|
|
11195
9752
|
});
|
|
11196
|
-
cmd.command("list").description("List memory records, or local cache records when gateway is unavailable").option("--workspace-id <id>", "Workspace to list (required for daemon RPC)").option("--page-type <type>", "Filter by pageType (hub/leaf/decision/glossary/archive)").option("--limit <n>", "Max items",
|
|
9753
|
+
cmd.command("list").description("List memory records, or local cache records when gateway is unavailable").option("--workspace-id <id>", "Workspace to list (required for daemon RPC)").option("--page-type <type>", "Filter by pageType (hub/leaf/decision/glossary/archive)").option("--limit <n>", "Max items", parsePositiveInt4, 20).option("--json", "Output JSON (default)").action(async (opts) => {
|
|
11197
9754
|
const limit = clampLimit(opts.limit);
|
|
11198
9755
|
const daemonPaths = [];
|
|
11199
9756
|
if (opts.workspaceId) {
|
|
@@ -11212,7 +9769,7 @@ function buildMemoryCommand() {
|
|
|
11212
9769
|
printJson(unavailable2("list", snapshot, { items: snapshot.items, limit }));
|
|
11213
9770
|
process.exitCode = 1;
|
|
11214
9771
|
});
|
|
11215
|
-
cmd.command("search").description("Search memory records, or local cache metadata when gateway is unavailable").argument("[query]", "Search text").option("--query <text>", "Search text").option("--workspace-id <id>", "Workspace to search (required for daemon RPC)").option("--limit <n>", "Max items",
|
|
9772
|
+
cmd.command("search").description("Search memory records, or local cache metadata when gateway is unavailable").argument("[query]", "Search text").option("--query <text>", "Search text").option("--workspace-id <id>", "Workspace to search (required for daemon RPC)").option("--limit <n>", "Max items", parsePositiveInt4, 20).option("--json", "Output JSON (default)").action(async (argQuery, opts) => {
|
|
11216
9773
|
const query = opts.query ?? argQuery ?? "";
|
|
11217
9774
|
const limit = clampLimit(opts.limit);
|
|
11218
9775
|
const encoded = encodeURIComponent(query);
|
|
@@ -11256,9 +9813,9 @@ function buildMemoryCommand() {
|
|
|
11256
9813
|
}
|
|
11257
9814
|
async function tryDaemon(methods, paths) {
|
|
11258
9815
|
for (const method of methods) {
|
|
11259
|
-
for (const
|
|
9816
|
+
for (const path7 of paths) {
|
|
11260
9817
|
try {
|
|
11261
|
-
const res = await fetch(`${LOCAL_BASE}${
|
|
9818
|
+
const res = await fetch(`${LOCAL_BASE}${path7}`, { method, signal: AbortSignal.timeout(1500) });
|
|
11262
9819
|
if (res.status === 404) continue;
|
|
11263
9820
|
const body = await readJson4(res);
|
|
11264
9821
|
if (!res.ok) {
|
|
@@ -11266,12 +9823,12 @@ async function tryDaemon(methods, paths) {
|
|
|
11266
9823
|
ok: false,
|
|
11267
9824
|
error: {
|
|
11268
9825
|
code: `daemon_http_${res.status}`,
|
|
11269
|
-
message: messageFromBody(body) ?? `Daemon endpoint ${method} ${
|
|
9826
|
+
message: messageFromBody(body) ?? `Daemon endpoint ${method} ${path7} returned HTTP ${res.status}`
|
|
11270
9827
|
},
|
|
11271
|
-
checks: { daemon: { url: LOCAL_BASE, endpoint:
|
|
9828
|
+
checks: { daemon: { url: LOCAL_BASE, endpoint: path7, reachable: true } }
|
|
11272
9829
|
};
|
|
11273
9830
|
}
|
|
11274
|
-
return normalizeDaemonBody(body, { method, path:
|
|
9831
|
+
return normalizeDaemonBody(body, { method, path: path7 });
|
|
11275
9832
|
} catch {
|
|
11276
9833
|
continue;
|
|
11277
9834
|
}
|
|
@@ -11329,7 +9886,7 @@ function readCacheSnapshot(limit) {
|
|
|
11329
9886
|
if (!empty.dbExists) return empty;
|
|
11330
9887
|
let db;
|
|
11331
9888
|
try {
|
|
11332
|
-
db = new
|
|
9889
|
+
db = new Database4(paths.localDb, { readonly: true, fileMustExist: true });
|
|
11333
9890
|
const hasAssets = tableExists(db, "cached_assets");
|
|
11334
9891
|
const hasFiles = tableExists(db, "workspace_files_mirror");
|
|
11335
9892
|
const assets = hasAssets ? db.prepare("SELECT COUNT(*) AS count, COALESCE(SUM(size_bytes), 0) AS sizeBytes FROM cached_assets").get() : { count: 0, sizeBytes: 0 };
|
|
@@ -11418,7 +9975,7 @@ function searchableText(item) {
|
|
|
11418
9975
|
item.localPath
|
|
11419
9976
|
].filter(Boolean).join(" ");
|
|
11420
9977
|
}
|
|
11421
|
-
function
|
|
9978
|
+
function parsePositiveInt4(v) {
|
|
11422
9979
|
const n = Number.parseInt(v, 10);
|
|
11423
9980
|
return Number.isFinite(n) && n > 0 ? n : 20;
|
|
11424
9981
|
}
|
|
@@ -11483,9 +10040,9 @@ function buildPairCommand() {
|
|
|
11483
10040
|
|
|
11484
10041
|
// src/cli/commands/profile.ts
|
|
11485
10042
|
import { Command as Command12 } from "commander";
|
|
11486
|
-
import { existsSync as existsSync17, readFileSync as
|
|
10043
|
+
import { existsSync as existsSync17, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
11487
10044
|
import { tmpdir } from "os";
|
|
11488
|
-
import { join as
|
|
10045
|
+
import { join as join14 } from "path";
|
|
11489
10046
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
11490
10047
|
init_util();
|
|
11491
10048
|
function buildProfileCommand() {
|
|
@@ -11533,12 +10090,12 @@ function buildProfileCommand() {
|
|
|
11533
10090
|
const profile = await cloud.get(
|
|
11534
10091
|
`/api/im/agent_profiles/${encodeURIComponent(profileId)}`
|
|
11535
10092
|
);
|
|
11536
|
-
const tmpFile =
|
|
10093
|
+
const tmpFile = join14(tmpdir(), `prismer-profile-${profileId}.json`);
|
|
11537
10094
|
writeFileSync9(tmpFile, JSON.stringify(profile.config, null, 2), "utf8");
|
|
11538
10095
|
const editor = process.env.EDITOR || "vi";
|
|
11539
10096
|
const ed = spawnSync4(editor, [tmpFile], { stdio: "inherit" });
|
|
11540
10097
|
if (ed.status !== 0) exitWithError(`editor exited ${ed.status}`, { code: "editor_failed" });
|
|
11541
|
-
const newConfig = JSON.parse(
|
|
10098
|
+
const newConfig = JSON.parse(readFileSync11(tmpFile, "utf8"));
|
|
11542
10099
|
const res = await cloud.request("PATCH", `/api/im/agent_profiles/${encodeURIComponent(profileId)}`, {
|
|
11543
10100
|
body: { config: newConfig, version: profile.version }
|
|
11544
10101
|
});
|
|
@@ -11559,9 +10116,9 @@ function mkCloud4() {
|
|
|
11559
10116
|
}
|
|
11560
10117
|
function readJsonArg(arg) {
|
|
11561
10118
|
if (arg.startsWith("@")) {
|
|
11562
|
-
const
|
|
11563
|
-
if (!existsSync17(
|
|
11564
|
-
return JSON.parse(
|
|
10119
|
+
const path7 = arg.slice(1);
|
|
10120
|
+
if (!existsSync17(path7)) throw new Error(`File not found: ${path7}`);
|
|
10121
|
+
return JSON.parse(readFileSync11(path7, "utf8"));
|
|
11565
10122
|
}
|
|
11566
10123
|
return JSON.parse(arg);
|
|
11567
10124
|
}
|
|
@@ -11572,200 +10129,13 @@ async function resolveDefaultWorkspaceId(cloud) {
|
|
|
11572
10129
|
return def.id;
|
|
11573
10130
|
}
|
|
11574
10131
|
|
|
11575
|
-
// src/cli/commands/reset.ts
|
|
11576
|
-
import { Command as Command13 } from "commander";
|
|
11577
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
11578
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync10, rmSync, renameSync as renameSync2 } from "fs";
|
|
11579
|
-
import { homedir as homedir9 } from "os";
|
|
11580
|
-
import { dirname as dirname10, join as join20 } from "path";
|
|
11581
|
-
import { setTimeout as sleep4 } from "timers/promises";
|
|
11582
|
-
init_util();
|
|
11583
|
-
init_ui();
|
|
11584
|
-
function buildResetCommand() {
|
|
11585
|
-
return new Command13("reset").description("Stop daemon and reset local runtime state so setup can bind another account").option("--yes", "Actually perform the reset").option("--check", "Print the reset plan without changing files").option("--wipe", "Delete local state instead of archiving it").option("--timeout <ms>", "Time to wait for daemon shutdown (default 5000)", (v) => Number.parseInt(v, 10), 5e3).option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
|
|
11586
|
-
const paths = resolvePaths();
|
|
11587
|
-
const pid = readPidFile(paths);
|
|
11588
|
-
const runningPid = pid && pidAlive2(pid) ? pid : null;
|
|
11589
|
-
const archivePath = opts.wipe ? null : `${paths.root}.reset.${timestamp()}`;
|
|
11590
|
-
const assetSyncRoot = resolveAssetSyncRoot();
|
|
11591
|
-
const assetSyncArchivePath = opts.wipe ? null : `${assetSyncRoot}.reset.${timestamp()}`;
|
|
11592
|
-
const hermesHome = resolveHermesHome();
|
|
11593
|
-
const hermesArchivePath = opts.wipe ? null : `${hermesHome}.reset.${timestamp()}`;
|
|
11594
|
-
const hermesGatewayPids = findHermesGatewayPids2();
|
|
11595
|
-
const plan = {
|
|
11596
|
-
root: paths.root,
|
|
11597
|
-
exists: existsSync18(paths.root),
|
|
11598
|
-
runningPid,
|
|
11599
|
-
mode: opts.wipe ? "wipe" : "archive",
|
|
11600
|
-
archivePath,
|
|
11601
|
-
assetSyncRoot,
|
|
11602
|
-
assetSyncExists: existsSync18(assetSyncRoot),
|
|
11603
|
-
assetSyncArchivePath,
|
|
11604
|
-
hermesHome,
|
|
11605
|
-
hermesExists: existsSync18(hermesHome),
|
|
11606
|
-
hermesArchivePath,
|
|
11607
|
-
hermesGatewayPids,
|
|
11608
|
-
willStopDaemon: runningPid !== null,
|
|
11609
|
-
nextStep: "prismer setup"
|
|
11610
|
-
};
|
|
11611
|
-
if (opts.check) {
|
|
11612
|
-
printPlan(plan, opts.json);
|
|
11613
|
-
return;
|
|
11614
|
-
}
|
|
11615
|
-
if (!opts.yes) {
|
|
11616
|
-
exitWithError("Refusing to reset without --yes. Run `prismer reset --check` to preview.", {
|
|
11617
|
-
code: "confirmation_required"
|
|
11618
|
-
});
|
|
11619
|
-
}
|
|
11620
|
-
if (runningPid !== null) {
|
|
11621
|
-
await stopDaemon(runningPid, opts.timeout ?? 5e3);
|
|
11622
|
-
clearPidFile(paths);
|
|
11623
|
-
} else if (pid) {
|
|
11624
|
-
clearPidFile(paths);
|
|
11625
|
-
}
|
|
11626
|
-
for (const gatewayPid of hermesGatewayPids) {
|
|
11627
|
-
await stopProcess(gatewayPid, opts.timeout ?? 5e3, "Hermes gateway");
|
|
11628
|
-
}
|
|
11629
|
-
if (existsSync18(paths.root)) {
|
|
11630
|
-
if (opts.wipe) {
|
|
11631
|
-
rmSync(paths.root, { recursive: true, force: true });
|
|
11632
|
-
} else {
|
|
11633
|
-
if (!archivePath) throw new Error("internal: archivePath missing");
|
|
11634
|
-
if (!existsSync18(dirname10(archivePath))) {
|
|
11635
|
-
mkdirSync10(dirname10(archivePath), { recursive: true });
|
|
11636
|
-
}
|
|
11637
|
-
renameSync2(paths.root, archivePath);
|
|
11638
|
-
}
|
|
11639
|
-
}
|
|
11640
|
-
if (existsSync18(assetSyncRoot)) {
|
|
11641
|
-
if (opts.wipe) {
|
|
11642
|
-
rmSync(assetSyncRoot, { recursive: true, force: true });
|
|
11643
|
-
} else {
|
|
11644
|
-
if (!assetSyncArchivePath) throw new Error("internal: assetSyncArchivePath missing");
|
|
11645
|
-
if (!existsSync18(dirname10(assetSyncArchivePath))) {
|
|
11646
|
-
mkdirSync10(dirname10(assetSyncArchivePath), { recursive: true });
|
|
11647
|
-
}
|
|
11648
|
-
renameSync2(assetSyncRoot, assetSyncArchivePath);
|
|
11649
|
-
}
|
|
11650
|
-
}
|
|
11651
|
-
if (existsSync18(hermesHome)) {
|
|
11652
|
-
if (opts.wipe) {
|
|
11653
|
-
rmSync(hermesHome, { recursive: true, force: true });
|
|
11654
|
-
} else {
|
|
11655
|
-
if (!hermesArchivePath) throw new Error("internal: hermesArchivePath missing");
|
|
11656
|
-
if (!existsSync18(dirname10(hermesArchivePath))) {
|
|
11657
|
-
mkdirSync10(dirname10(hermesArchivePath), { recursive: true });
|
|
11658
|
-
}
|
|
11659
|
-
renameSync2(hermesHome, hermesArchivePath);
|
|
11660
|
-
}
|
|
11661
|
-
}
|
|
11662
|
-
mkdirSync10(paths.root, { recursive: true });
|
|
11663
|
-
if (opts.json) {
|
|
11664
|
-
printJson({ ok: true, reset: true, plan });
|
|
11665
|
-
return;
|
|
11666
|
-
}
|
|
11667
|
-
const ui = getUI();
|
|
11668
|
-
ui.ok("Runtime reset complete", paths.root);
|
|
11669
|
-
if (archivePath) ui.line(` archived: ${archivePath}`);
|
|
11670
|
-
if (assetSyncArchivePath && plan.assetSyncExists) ui.line(` asset sync archived: ${assetSyncArchivePath}`);
|
|
11671
|
-
if (hermesArchivePath && plan.hermesExists) ui.line(` Hermes archived: ${hermesArchivePath}`);
|
|
11672
|
-
if (hermesGatewayPids.length > 0) ui.line(` stopped Hermes gateway pid(s): ${hermesGatewayPids.join(", ")}`);
|
|
11673
|
-
ui.line(" next: prismer setup");
|
|
11674
|
-
}, { code: "reset_failed" }));
|
|
11675
|
-
}
|
|
11676
|
-
function printPlan(plan, json) {
|
|
11677
|
-
if (json) {
|
|
11678
|
-
printJson({ ok: true, check: true, plan });
|
|
11679
|
-
return;
|
|
11680
|
-
}
|
|
11681
|
-
const ui = getUI();
|
|
11682
|
-
ui.header("Runtime reset plan");
|
|
11683
|
-
ui.blank();
|
|
11684
|
-
ui.line(` root: ${plan.root}`);
|
|
11685
|
-
ui.line(` exists: ${plan.exists ? "yes" : "no"}`);
|
|
11686
|
-
ui.line(` daemon: ${plan.runningPid ? `running pid ${plan.runningPid}` : "not running"}`);
|
|
11687
|
-
ui.line(` mode: ${plan.mode}`);
|
|
11688
|
-
if (plan.archivePath) ui.line(` archive: ${plan.archivePath}`);
|
|
11689
|
-
ui.line(` asset sync: ${plan.assetSyncExists ? plan.assetSyncRoot : `${plan.assetSyncRoot} (not found)`}`);
|
|
11690
|
-
if (plan.assetSyncArchivePath) ui.line(` asset archive: ${plan.assetSyncArchivePath}`);
|
|
11691
|
-
ui.line(` Hermes: ${plan.hermesExists ? plan.hermesHome : `${plan.hermesHome} (not found)`}`);
|
|
11692
|
-
if (plan.hermesArchivePath) ui.line(` Hermes archive: ${plan.hermesArchivePath}`);
|
|
11693
|
-
ui.line(
|
|
11694
|
-
` Hermes gateway: ${plan.hermesGatewayPids.length > 0 ? `running pid(s) ${plan.hermesGatewayPids.join(", ")}` : "not running"}`
|
|
11695
|
-
);
|
|
11696
|
-
ui.blank();
|
|
11697
|
-
ui.line("Run `prismer reset --yes` to apply, then `prismer setup` to bind a new account.");
|
|
11698
|
-
}
|
|
11699
|
-
async function stopDaemon(pid, timeoutMs) {
|
|
11700
|
-
await stopProcess(pid, timeoutMs, "daemon");
|
|
11701
|
-
}
|
|
11702
|
-
async function stopProcess(pid, timeoutMs, label) {
|
|
11703
|
-
try {
|
|
11704
|
-
process.kill(pid, "SIGTERM");
|
|
11705
|
-
} catch (err) {
|
|
11706
|
-
throw new Error(`SIGTERM failed for ${label} pid ${pid}: ${err.message}`);
|
|
11707
|
-
}
|
|
11708
|
-
const deadline = Date.now() + timeoutMs;
|
|
11709
|
-
while (Date.now() < deadline) {
|
|
11710
|
-
if (!pidAlive2(pid)) return;
|
|
11711
|
-
await sleep4(100);
|
|
11712
|
-
}
|
|
11713
|
-
try {
|
|
11714
|
-
process.kill(pid, "SIGKILL");
|
|
11715
|
-
} catch (err) {
|
|
11716
|
-
throw new Error(`SIGKILL failed for ${label} pid ${pid}: ${err.message}`);
|
|
11717
|
-
}
|
|
11718
|
-
const killDeadline = Date.now() + 1e3;
|
|
11719
|
-
while (Date.now() < killDeadline) {
|
|
11720
|
-
if (!pidAlive2(pid)) return;
|
|
11721
|
-
await sleep4(100);
|
|
11722
|
-
}
|
|
11723
|
-
throw new Error(`${label} pid ${pid} did not exit within ${timeoutMs + 1e3}ms`);
|
|
11724
|
-
}
|
|
11725
|
-
function timestamp() {
|
|
11726
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
11727
|
-
}
|
|
11728
|
-
function resolveAssetSyncRoot() {
|
|
11729
|
-
const override = process.env.PRISMER_ASSET_SYNC_ROOT;
|
|
11730
|
-
if (override) return override;
|
|
11731
|
-
const home = homedir9();
|
|
11732
|
-
const desktop = join20(home, "Desktop");
|
|
11733
|
-
const base = existsSync18(desktop) ? desktop : home;
|
|
11734
|
-
return join20(base, "Prismer Assets");
|
|
11735
|
-
}
|
|
11736
|
-
function resolveHermesHome() {
|
|
11737
|
-
return process.env.HERMES_HOME || join20(homedir9(), ".hermes");
|
|
11738
|
-
}
|
|
11739
|
-
function findHermesGatewayPids2() {
|
|
11740
|
-
try {
|
|
11741
|
-
const out = execFileSync2("ps", ["-axo", "pid=,command="], { encoding: "utf8" });
|
|
11742
|
-
const current = process.pid;
|
|
11743
|
-
return out.split(/\r?\n/).map((line) => {
|
|
11744
|
-
const trimmed = line.trim();
|
|
11745
|
-
const match = /^(\d+)\s+(.+)$/.exec(trimmed);
|
|
11746
|
-
if (!match) return null;
|
|
11747
|
-
const pid = Number.parseInt(match[1], 10);
|
|
11748
|
-
const command = match[2];
|
|
11749
|
-
if (!Number.isFinite(pid) || pid === current) return null;
|
|
11750
|
-
if (!isHermesGatewayCommand2(command)) return null;
|
|
11751
|
-
return pid;
|
|
11752
|
-
}).filter((pid) => pid !== null);
|
|
11753
|
-
} catch {
|
|
11754
|
-
return [];
|
|
11755
|
-
}
|
|
11756
|
-
}
|
|
11757
|
-
function isHermesGatewayCommand2(command) {
|
|
11758
|
-
if (!/\bgateway\b/.test(command) || !/\brun\b/.test(command)) return false;
|
|
11759
|
-
return /(^|\s)(?:\S*\/)?hermes(?:\s|$)/.test(command);
|
|
11760
|
-
}
|
|
11761
|
-
|
|
11762
10132
|
// src/cli/commands/sandbox.ts
|
|
11763
|
-
import { Command as
|
|
10133
|
+
import { Command as Command13 } from "commander";
|
|
11764
10134
|
init_util();
|
|
11765
10135
|
init_ui();
|
|
11766
10136
|
function buildSandboxCommand() {
|
|
11767
|
-
const cmd = new
|
|
11768
|
-
cmd.command("list").description("List sandbox containers in a workspace").requiredOption("--workspace-id <id>", "Workspace id").option("--status <status>", "Status filter").option("--limit <n>", "Max rows",
|
|
10137
|
+
const cmd = new Command13("sandbox").description("Inspect and smoke-test sandbox lifecycle");
|
|
10138
|
+
cmd.command("list").description("List sandbox containers in a workspace").requiredOption("--workspace-id <id>", "Workspace id").option("--status <status>", "Status filter").option("--limit <n>", "Max rows", parsePositiveInt5, 50).option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
|
|
11769
10139
|
const query = new URLSearchParams({ workspaceId: opts.workspaceId, limit: String(opts.limit) });
|
|
11770
10140
|
if (opts.status) query.set("status", opts.status);
|
|
11771
10141
|
const body = await cloudRequest("GET", `/api/sandboxes?${query.toString()}`);
|
|
@@ -11824,7 +10194,7 @@ function buildSandboxCommand() {
|
|
|
11824
10194
|
}
|
|
11825
10195
|
printSnapshotResult(id, body);
|
|
11826
10196
|
}, { code: "sandbox_snapshot_failed" }));
|
|
11827
|
-
cmd.command("runCmd <id> <command...>").description("Run a command in a sandbox container").option("--timeout-ms <ms>", "Command timeout",
|
|
10197
|
+
cmd.command("runCmd <id> <command...>").description("Run a command in a sandbox container").option("--timeout-ms <ms>", "Command timeout", parsePositiveInt5, 6e4).option("--json", "Output machine-readable JSON").allowUnknownOption(true).action(runAction(async (id, command, opts) => {
|
|
11828
10198
|
if (!Array.isArray(command) || command.length === 0) exitWithError("runCmd requires a command after --", { code: "invalid_argument" });
|
|
11829
10199
|
const body = await cloudRequest("POST", `/api/sandboxes/${encodeURIComponent(id)}/runCmd`, {
|
|
11830
10200
|
command,
|
|
@@ -11865,12 +10235,12 @@ function errorMessage2(raw) {
|
|
|
11865
10235
|
if (typeof raw.error === "string") return raw.error;
|
|
11866
10236
|
return raw.error?.message ?? "request failed";
|
|
11867
10237
|
}
|
|
11868
|
-
async function cloudRequest(method,
|
|
10238
|
+
async function cloudRequest(method, path7, body) {
|
|
11869
10239
|
const cloud = mkCloud5();
|
|
11870
|
-
const res = await cloud.request(method,
|
|
11871
|
-
if (!res.ok) exitWithError(`${method} ${
|
|
10240
|
+
const res = await cloud.request(method, path7, body === void 0 ? void 0 : { body });
|
|
10241
|
+
if (!res.ok) exitWithError(`${method} ${path7} failed (${res.status}): ${res.error?.message ?? "request failed"}`);
|
|
11872
10242
|
const responseBody = res.data;
|
|
11873
|
-
if (isErrorEnvelope2(responseBody)) exitWithError(`${method} ${
|
|
10243
|
+
if (isErrorEnvelope2(responseBody)) exitWithError(`${method} ${path7} failed (${res.status}): ${errorMessage2(responseBody)}`);
|
|
11874
10244
|
return responseBody;
|
|
11875
10245
|
}
|
|
11876
10246
|
function printSandboxList(raw) {
|
|
@@ -11945,7 +10315,7 @@ function writeLine2(label, value) {
|
|
|
11945
10315
|
if (value === void 0 || value === null || value === "") return;
|
|
11946
10316
|
getUI().line(` ${label}: ${String(value)}`);
|
|
11947
10317
|
}
|
|
11948
|
-
function
|
|
10318
|
+
function parsePositiveInt5(value) {
|
|
11949
10319
|
const parsed = Number.parseInt(value, 10);
|
|
11950
10320
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
11951
10321
|
throw new Error("expected a positive integer");
|
|
@@ -11963,16 +10333,16 @@ async function safeParseResponse(res) {
|
|
|
11963
10333
|
}
|
|
11964
10334
|
|
|
11965
10335
|
// src/cli/commands/setup.ts
|
|
11966
|
-
import { Command as
|
|
10336
|
+
import { Command as Command14 } from "commander";
|
|
11967
10337
|
import { hostname as hostname3 } from "os";
|
|
11968
10338
|
import { spawn as spawn6 } from "child_process";
|
|
11969
|
-
import { randomBytes
|
|
11970
|
-
import { existsSync as
|
|
10339
|
+
import { randomBytes } from "crypto";
|
|
10340
|
+
import { existsSync as existsSync18, renameSync } from "fs";
|
|
11971
10341
|
import { createServer as createServer2 } from "http";
|
|
11972
10342
|
init_util();
|
|
11973
10343
|
init_ui();
|
|
11974
10344
|
function buildSetupCommand() {
|
|
11975
|
-
return new
|
|
10345
|
+
return new Command14("setup").description("Set up this local runtime and bind it to Prismer Cloud").argument("[api-key]", "Prismer daemon API key; primarily for manual recovery and automation").option(
|
|
11976
10346
|
"--cloud <url>",
|
|
11977
10347
|
`Cloud base URL (default: ${DEFAULT_CLOUD_BASE_URL}; bare host:port is auto-prefixed http://)`,
|
|
11978
10348
|
(v) => v
|
|
@@ -11985,7 +10355,7 @@ function buildSetupCommand() {
|
|
|
11985
10355
|
} catch (err) {
|
|
11986
10356
|
exitWithError(err.message, { code: "invalid_cloud_url" });
|
|
11987
10357
|
}
|
|
11988
|
-
let apiKey = apiKeyArg ??
|
|
10358
|
+
let apiKey = apiKeyArg ?? process.env.PRISMER_API_KEY;
|
|
11989
10359
|
const authToken = opts.token ?? process.env.PRISMER_AUTH_TOKEN;
|
|
11990
10360
|
if (!opts.json) {
|
|
11991
10361
|
printBanner({ compact: true });
|
|
@@ -11993,6 +10363,7 @@ function buildSetupCommand() {
|
|
|
11993
10363
|
getUI().blank();
|
|
11994
10364
|
}
|
|
11995
10365
|
const shouldStart = opts.start !== false;
|
|
10366
|
+
stopRunningDaemon(paths);
|
|
11996
10367
|
if (opts.pair || opts.asUser) {
|
|
11997
10368
|
if (!opts.json) warn("Legacy pair setup path", "plain `prismer setup --start` is the canonical runtime binding flow");
|
|
11998
10369
|
if (opts.asUser && process.env.LOCAL_ONLY !== "1") {
|
|
@@ -12083,11 +10454,10 @@ function buildSetupCommand() {
|
|
|
12083
10454
|
return;
|
|
12084
10455
|
}
|
|
12085
10456
|
const previousConfig = configExists(paths) ? loadConfig(paths) : null;
|
|
12086
|
-
const daemonId = previousConfig && previousConfig.api_key === apiKey && previousConfig.cloud_api_base === cloudBaseUrl ? previousConfig.daemon_id : newDaemonId();
|
|
12087
10457
|
const config = {
|
|
12088
10458
|
api_key: apiKey,
|
|
12089
10459
|
cloud_api_base: cloudBaseUrl,
|
|
12090
|
-
daemon_id:
|
|
10460
|
+
daemon_id: newDaemonId()
|
|
12091
10461
|
};
|
|
12092
10462
|
if (previousConfig && shouldArchiveLocalDb(previousConfig, config)) {
|
|
12093
10463
|
archiveLocalDb(paths.localDb);
|
|
@@ -12117,7 +10487,7 @@ function buildSetupCommand() {
|
|
|
12117
10487
|
}, { code: "setup_failed" }));
|
|
12118
10488
|
}
|
|
12119
10489
|
async function runBrowserSetup(input) {
|
|
12120
|
-
const state =
|
|
10490
|
+
const state = randomBytes(18).toString("base64url");
|
|
12121
10491
|
const server = createServer2();
|
|
12122
10492
|
const apiKeyPromise = waitForSetupCallback(server, state);
|
|
12123
10493
|
await new Promise((resolve3, reject) => {
|
|
@@ -12214,13 +10584,35 @@ function startDaemonDetached(home) {
|
|
|
12214
10584
|
});
|
|
12215
10585
|
child.unref();
|
|
12216
10586
|
}
|
|
10587
|
+
function stopRunningDaemon(paths) {
|
|
10588
|
+
const pid = readPidFile(paths);
|
|
10589
|
+
if (!pid || !pidAlive(pid)) {
|
|
10590
|
+
if (pid) clearPidFile(paths);
|
|
10591
|
+
return;
|
|
10592
|
+
}
|
|
10593
|
+
try {
|
|
10594
|
+
process.kill(pid, "SIGTERM");
|
|
10595
|
+
} catch {
|
|
10596
|
+
return;
|
|
10597
|
+
}
|
|
10598
|
+
const deadline = Date.now() + 5e3;
|
|
10599
|
+
while (Date.now() < deadline) {
|
|
10600
|
+
if (!pidAlive(pid)) {
|
|
10601
|
+
clearPidFile(paths);
|
|
10602
|
+
return;
|
|
10603
|
+
}
|
|
10604
|
+
const start = Date.now();
|
|
10605
|
+
while (Date.now() - start < 200) {
|
|
10606
|
+
}
|
|
10607
|
+
}
|
|
10608
|
+
}
|
|
12217
10609
|
function shouldArchiveLocalDb(previous, next) {
|
|
12218
10610
|
return previous.api_key !== next.api_key || previous.cloud_api_base !== next.cloud_api_base || previous.daemon_id !== next.daemon_id;
|
|
12219
10611
|
}
|
|
12220
10612
|
function archiveLocalDb(localDbPath) {
|
|
12221
|
-
if (!
|
|
10613
|
+
if (!existsSync18(localDbPath)) return;
|
|
12222
10614
|
const archived = `${localDbPath}.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
12223
|
-
|
|
10615
|
+
renameSync(localDbPath, archived);
|
|
12224
10616
|
}
|
|
12225
10617
|
async function mintDaemonApiKey(input) {
|
|
12226
10618
|
const label = `Daemon: ${input.deviceName} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
@@ -12240,12 +10632,11 @@ async function mintDaemonApiKey(input) {
|
|
|
12240
10632
|
}
|
|
12241
10633
|
|
|
12242
10634
|
// src/cli/commands/status.ts
|
|
12243
|
-
import { Command as
|
|
12244
|
-
import { hostname as hostname4 } from "os";
|
|
10635
|
+
import { Command as Command15 } from "commander";
|
|
12245
10636
|
init_util();
|
|
12246
10637
|
init_ui();
|
|
12247
10638
|
function buildStatusCommand() {
|
|
12248
|
-
return new
|
|
10639
|
+
return new Command15("status").description("Show daemon + config + cloud status").option("--json", "Output machine-readable JSON").action(async (opts) => {
|
|
12249
10640
|
const paths = resolvePaths();
|
|
12250
10641
|
if (!configExists(paths)) {
|
|
12251
10642
|
const report2 = { configured: false, paired: false, paths: { config: paths.configFile } };
|
|
@@ -12313,7 +10704,11 @@ function buildStatusCommand() {
|
|
|
12313
10704
|
info: daemonStatus.info ?? {}
|
|
12314
10705
|
},
|
|
12315
10706
|
cloud: { base: cfg.cloud_api_base, reachable: cloudOk, me, devices, agents },
|
|
12316
|
-
local
|
|
10707
|
+
local,
|
|
10708
|
+
binding: {
|
|
10709
|
+
daemonId: cfg.daemon_id,
|
|
10710
|
+
apiKey: cfg.api_key
|
|
10711
|
+
}
|
|
12317
10712
|
};
|
|
12318
10713
|
if (opts.json) {
|
|
12319
10714
|
printJson(report);
|
|
@@ -12336,8 +10731,8 @@ async function readDaemonStatus() {
|
|
|
12336
10731
|
const paths = resolvePaths();
|
|
12337
10732
|
const pid = readPidFile(paths);
|
|
12338
10733
|
if (pid) {
|
|
12339
|
-
const { pidAlive:
|
|
12340
|
-
if (
|
|
10734
|
+
const { pidAlive: pidAlive2 } = await Promise.resolve().then(() => (init_util(), util_exports));
|
|
10735
|
+
if (pidAlive2(pid)) return { running: true, pid };
|
|
12341
10736
|
}
|
|
12342
10737
|
return { running: false };
|
|
12343
10738
|
}
|
|
@@ -12365,9 +10760,8 @@ function printPretty(report) {
|
|
|
12365
10760
|
ui.blank();
|
|
12366
10761
|
ok("Config", report.paths.config);
|
|
12367
10762
|
if (report.daemon.running) {
|
|
12368
|
-
const daemonName = report.cloud.me && typeof report.cloud.me === "object" ? report.cloud.me.user?.username ?? hostname4() : hostname4();
|
|
12369
10763
|
const ws = report.daemon.wsConnected ? "connected" : "pending";
|
|
12370
|
-
ok("Daemon",
|
|
10764
|
+
ok("Daemon", `pid=${report.daemon.pid} ws=${ws}`);
|
|
12371
10765
|
if (report.daemon.info) {
|
|
12372
10766
|
const info2 = report.daemon.info;
|
|
12373
10767
|
if (info2.version) ui.line(` Version: ${info2.version}`);
|
|
@@ -12382,7 +10776,8 @@ function printPretty(report) {
|
|
|
12382
10776
|
ok("Cloud", report.cloud.base);
|
|
12383
10777
|
const me = report.cloud.me;
|
|
12384
10778
|
if (me?.user) {
|
|
12385
|
-
|
|
10779
|
+
const roleTag = me.user.role ? ` role=${me.user.role}` : "";
|
|
10780
|
+
ui.line(` Account: ${me.user.displayName ?? me.user.username ?? "?"}${roleTag}`);
|
|
12386
10781
|
}
|
|
12387
10782
|
if (me?.credits) {
|
|
12388
10783
|
ui.line(` Credits: ${typeof me.credits.balance === "number" ? me.credits.balance.toLocaleString() : "?"}`);
|
|
@@ -12391,6 +10786,12 @@ function printPretty(report) {
|
|
|
12391
10786
|
fail2("Cloud", `${report.cloud.base} unreachable or unauthorized`);
|
|
12392
10787
|
tip("prismer setup --force");
|
|
12393
10788
|
}
|
|
10789
|
+
if (report.binding) {
|
|
10790
|
+
ui.blank();
|
|
10791
|
+
ui.line(` Daemon ID: ${report.binding.daemonId}`);
|
|
10792
|
+
const masked = report.binding.apiKey.slice(0, 14) + "\u2022\u2022\u2022\u2022" + report.binding.apiKey.slice(-4);
|
|
10793
|
+
ui.line(` API Key: ${masked}`);
|
|
10794
|
+
}
|
|
12394
10795
|
if (Array.isArray(report.cloud.devices) && report.cloud.devices.length > 0) {
|
|
12395
10796
|
const devs = report.cloud.devices;
|
|
12396
10797
|
ui.blank();
|
|
@@ -12401,6 +10802,8 @@ function printPretty(report) {
|
|
|
12401
10802
|
const declared = d.hostedAgentSummary?.declared ?? 0;
|
|
12402
10803
|
ui.line(` ${statusIcon} ${d.podName?.replace(/^daemon:/, "")?.slice(0, 28) ?? "?"} kind=${kind} agents=${declared}`);
|
|
12403
10804
|
}
|
|
10805
|
+
} else {
|
|
10806
|
+
ui.line(` Workspace Devices: none`);
|
|
12404
10807
|
}
|
|
12405
10808
|
if (Array.isArray(report.cloud.agents) && report.cloud.agents.length > 0) {
|
|
12406
10809
|
ui.line(` Hosted agents: ${report.cloud.agents.length}`);
|
|
@@ -12416,14 +10819,14 @@ function printPretty(report) {
|
|
|
12416
10819
|
}
|
|
12417
10820
|
|
|
12418
10821
|
// src/cli/commands/task.ts
|
|
12419
|
-
import { Command as
|
|
12420
|
-
import { setTimeout as
|
|
10822
|
+
import { Command as Command16 } from "commander";
|
|
10823
|
+
import { setTimeout as sleep3 } from "timers/promises";
|
|
12421
10824
|
init_util();
|
|
12422
10825
|
function describeStatus(status) {
|
|
12423
10826
|
return status === 0 ? "network error" : `HTTP ${status}`;
|
|
12424
10827
|
}
|
|
12425
10828
|
function buildTaskCommand() {
|
|
12426
|
-
const cmd = new
|
|
10829
|
+
const cmd = new Command16("task").description("Manage tasks");
|
|
12427
10830
|
cmd.command("create").description("Create + dispatch a task to the given agent and wait for completion").requiredOption("--agent <imUserId>", "Assignee agent IMUser.id").requiredOption("--prompt <text>", "Prompt to send to the adapter").option("--profile <profileId>", "AgentProfile id (cloud picks first if omitted)").option("--capability <name>", "Capability tag", "chat").option("--title <text>", "Task title (defaults to prompt prefix)").option("--timeout-ms <ms>", "Per-task timeout", (v) => Number.parseInt(v, 10)).option("--no-wait", "Just create + print task id; don't poll").option("--json", "Output JSON (default)").action(runAction(async (opts) => {
|
|
12428
10831
|
const cloud = mkCloud6();
|
|
12429
10832
|
const body = {
|
|
@@ -12500,7 +10903,7 @@ function unwrap2(raw) {
|
|
|
12500
10903
|
async function pollUntilTerminal(cloud, taskId, timeoutMs) {
|
|
12501
10904
|
const deadline = Date.now() + (timeoutMs ?? 5 * 6e4);
|
|
12502
10905
|
while (Date.now() < deadline) {
|
|
12503
|
-
await
|
|
10906
|
+
await sleep3(1e3);
|
|
12504
10907
|
const res = await cloud.request(
|
|
12505
10908
|
"GET",
|
|
12506
10909
|
`/api/im/tasks/${encodeURIComponent(taskId)}`
|
|
@@ -12537,11 +10940,11 @@ function taskFrom2(raw) {
|
|
|
12537
10940
|
}
|
|
12538
10941
|
|
|
12539
10942
|
// src/cli/commands/workspace.ts
|
|
12540
|
-
import { Command as
|
|
10943
|
+
import { Command as Command17 } from "commander";
|
|
12541
10944
|
init_util();
|
|
12542
10945
|
init_ui();
|
|
12543
10946
|
function buildWorkspaceCommand() {
|
|
12544
|
-
const cmd = new
|
|
10947
|
+
const cmd = new Command17("workspace").description("Manage workspaces, runtime snapshots, and workspace files");
|
|
12545
10948
|
cmd.command("list").description("List workspaces").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
|
|
12546
10949
|
const cloud = mkCloud7();
|
|
12547
10950
|
const data = await cloud.get("/api/im/workspaces");
|
|
@@ -12775,15 +11178,18 @@ async function readResponseError(res) {
|
|
|
12775
11178
|
|
|
12776
11179
|
// src/cli/index.ts
|
|
12777
11180
|
init_ui();
|
|
12778
|
-
|
|
11181
|
+
|
|
11182
|
+
// package.json
|
|
11183
|
+
var version = "1.9.22";
|
|
11184
|
+
|
|
11185
|
+
// src/cli/index.ts
|
|
12779
11186
|
function buildProgram() {
|
|
12780
|
-
const program = new
|
|
11187
|
+
const program = new Command18("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(version);
|
|
12781
11188
|
program.addCommand(buildBannerCommand());
|
|
12782
11189
|
program.addCommand(buildSetupCommand());
|
|
12783
11190
|
program.addCommand(buildConfigCommand());
|
|
12784
11191
|
program.addCommand(buildDaemonCommand());
|
|
12785
11192
|
program.addCommand(buildPairCommand());
|
|
12786
|
-
program.addCommand(buildResetCommand());
|
|
12787
11193
|
program.addCommand(buildStatusCommand());
|
|
12788
11194
|
program.addCommand(buildAdapterCommand());
|
|
12789
11195
|
program.addCommand(buildAgentCommand());
|