@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.cjs
CHANGED
|
@@ -33,7 +33,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
33
33
|
// src/cli/ui.ts
|
|
34
34
|
function thisDirname() {
|
|
35
35
|
try {
|
|
36
|
-
return
|
|
36
|
+
return path6.dirname((0, import_node_url.fileURLToPath)(import_meta2.url));
|
|
37
37
|
} catch {
|
|
38
38
|
return process.cwd();
|
|
39
39
|
}
|
|
@@ -43,18 +43,18 @@ function findIconPath(size = "big") {
|
|
|
43
43
|
const here = thisDirname();
|
|
44
44
|
const candidates = [
|
|
45
45
|
// npm-installed: node_modules/@prismer/runtime/dist/cli.js → ../assets
|
|
46
|
-
|
|
46
|
+
path6.resolve(here, "../assets", name),
|
|
47
47
|
// alternate dist layout (sub-bundle): dist/bin/cli.js → ../../assets
|
|
48
|
-
|
|
48
|
+
path6.resolve(here, "../../assets", name),
|
|
49
49
|
// source/typecheck: src/cli/ui.ts → ../../assets
|
|
50
|
-
|
|
50
|
+
path6.resolve(here, "../../assets", name),
|
|
51
51
|
// dev mode: cwd happens to be runtime root
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
path6.resolve(process.cwd(), "assets", name),
|
|
53
|
+
path6.resolve(process.cwd(), "sdk/prismer-cloud/runtime/assets", name)
|
|
54
54
|
];
|
|
55
55
|
for (const candidate of candidates) {
|
|
56
56
|
try {
|
|
57
|
-
if (
|
|
57
|
+
if (fs4.existsSync(candidate)) return candidate;
|
|
58
58
|
} catch {
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -95,13 +95,13 @@ function applyCommonFlags(argv) {
|
|
|
95
95
|
}
|
|
96
96
|
return { mode, color: color2, restArgv: rest };
|
|
97
97
|
}
|
|
98
|
-
var
|
|
98
|
+
var fs4, path6, import_node_url, import_meta2, BRAILLE_FRAMES, COMPACT_BANNER, UI, _ui;
|
|
99
99
|
var init_ui = __esm({
|
|
100
100
|
"src/cli/ui.ts"() {
|
|
101
101
|
"use strict";
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
fs4 = __toESM(require("fs"), 1);
|
|
103
|
+
path6 = __toESM(require("path"), 1);
|
|
104
|
+
import_node_url = require("url");
|
|
105
105
|
import_meta2 = {};
|
|
106
106
|
BRAILLE_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
107
107
|
COMPACT_BANNER = ["\u25C7 PRISMER", " Runtime CLI"];
|
|
@@ -182,7 +182,7 @@ var init_ui = __esm({
|
|
|
182
182
|
const iconPath = findIconPath("small");
|
|
183
183
|
if (iconPath !== null) {
|
|
184
184
|
try {
|
|
185
|
-
const raw =
|
|
185
|
+
const raw = fs4.readFileSync(iconPath, "utf-8").replace(/\n+$/, "");
|
|
186
186
|
for (const line of raw.split("\n")) {
|
|
187
187
|
this.write(this.cyan(line) + "\n");
|
|
188
188
|
}
|
|
@@ -205,7 +205,7 @@ var init_ui = __esm({
|
|
|
205
205
|
const shouldUseFull = opts?.full === true || width >= 120;
|
|
206
206
|
if (shouldUseFull && iconPath !== null) {
|
|
207
207
|
try {
|
|
208
|
-
const raw =
|
|
208
|
+
const raw = fs4.readFileSync(iconPath, "utf-8");
|
|
209
209
|
const lines = raw.split("\n");
|
|
210
210
|
for (const line of lines) {
|
|
211
211
|
const brandedLine = line.replace("Prismer Cloud SDK", "Prismer Runtime CLI");
|
|
@@ -469,7 +469,7 @@ __export(util_exports, {
|
|
|
469
469
|
info: () => info,
|
|
470
470
|
normalizeCloudUrl: () => normalizeCloudUrl,
|
|
471
471
|
ok: () => ok,
|
|
472
|
-
pidAlive: () =>
|
|
472
|
+
pidAlive: () => pidAlive,
|
|
473
473
|
pidFilePath: () => pidFilePath,
|
|
474
474
|
printBanner: () => printBanner,
|
|
475
475
|
printJson: () => printJson,
|
|
@@ -544,10 +544,10 @@ function runAction(fn, opts = {}) {
|
|
|
544
544
|
function printBanner(opts = {}) {
|
|
545
545
|
const ui = getUI();
|
|
546
546
|
if (opts.compact) {
|
|
547
|
-
ui.smallHeader("Runtime CLI
|
|
547
|
+
ui.smallHeader("Runtime CLI v${VERSION}");
|
|
548
548
|
return;
|
|
549
549
|
}
|
|
550
|
-
ui.banner("Runtime CLI
|
|
550
|
+
ui.banner("Runtime CLI v${VERSION}", { full: true });
|
|
551
551
|
}
|
|
552
552
|
function ok(label, detail) {
|
|
553
553
|
getUI().ok(label, detail);
|
|
@@ -573,29 +573,29 @@ function table(rows, columns) {
|
|
|
573
573
|
getUI().table(rows, { columns });
|
|
574
574
|
}
|
|
575
575
|
function pidFilePath(paths) {
|
|
576
|
-
return (0,
|
|
576
|
+
return (0, import_node_path8.join)(paths.root, "daemon.pid");
|
|
577
577
|
}
|
|
578
578
|
function writePidFile(paths, pid) {
|
|
579
|
-
(0,
|
|
579
|
+
(0, import_node_fs12.writeFileSync)(pidFilePath(paths), `${pid}
|
|
580
580
|
`, "utf8");
|
|
581
581
|
}
|
|
582
582
|
function readPidFile(paths) {
|
|
583
583
|
const p = pidFilePath(paths);
|
|
584
|
-
if (!(0,
|
|
585
|
-
const raw = (0,
|
|
584
|
+
if (!(0, import_node_fs12.existsSync)(p)) return void 0;
|
|
585
|
+
const raw = (0, import_node_fs12.readFileSync)(p, "utf8").trim();
|
|
586
586
|
const pid = Number.parseInt(raw, 10);
|
|
587
587
|
return Number.isFinite(pid) ? pid : void 0;
|
|
588
588
|
}
|
|
589
589
|
function clearPidFile(paths) {
|
|
590
590
|
const p = pidFilePath(paths);
|
|
591
|
-
if ((0,
|
|
591
|
+
if ((0, import_node_fs12.existsSync)(p)) {
|
|
592
592
|
try {
|
|
593
|
-
(0,
|
|
593
|
+
(0, import_node_fs12.unlinkSync)(p);
|
|
594
594
|
} catch {
|
|
595
595
|
}
|
|
596
596
|
}
|
|
597
597
|
}
|
|
598
|
-
function
|
|
598
|
+
function pidAlive(pid) {
|
|
599
599
|
try {
|
|
600
600
|
process.kill(pid, 0);
|
|
601
601
|
return true;
|
|
@@ -603,12 +603,12 @@ function pidAlive2(pid) {
|
|
|
603
603
|
return false;
|
|
604
604
|
}
|
|
605
605
|
}
|
|
606
|
-
var
|
|
606
|
+
var import_node_fs12, import_node_path8, DEFAULT_CLOUD_BASE_URL, ANSI;
|
|
607
607
|
var init_util = __esm({
|
|
608
608
|
"src/cli/util.ts"() {
|
|
609
609
|
"use strict";
|
|
610
|
-
|
|
611
|
-
|
|
610
|
+
import_node_fs12 = require("fs");
|
|
611
|
+
import_node_path8 = require("path");
|
|
612
612
|
init_ui();
|
|
613
613
|
DEFAULT_CLOUD_BASE_URL = "https://prismer.cloud";
|
|
614
614
|
ANSI = {
|
|
@@ -729,35 +729,14 @@ var AdapterRegistry = class {
|
|
|
729
729
|
// src/adapters/hermes/index.ts
|
|
730
730
|
var import_node_child_process = require("child_process");
|
|
731
731
|
var import_node_fs = require("fs");
|
|
732
|
-
var import_node_module = require("module");
|
|
733
732
|
var import_node_os = require("os");
|
|
734
733
|
var import_node_path = require("path");
|
|
735
|
-
var import_node_url = require("url");
|
|
736
734
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
737
735
|
var YAML = __toESM(require("yaml"), 1);
|
|
738
736
|
var import_zod = require("zod");
|
|
739
737
|
var import_meta = {};
|
|
740
738
|
var PRISMER_IM_SKILL_NAME = "prismer-im-collab";
|
|
741
|
-
var PRISMER_IM_SKILL_CONTENT =
|
|
742
|
-
name: prismer-im-collab
|
|
743
|
-
description: Coordinate reliably in Prismer conversations, use workspace assets through bounded MCP tools, and keep task work on the board.
|
|
744
|
-
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
|
|
745
|
-
metadata:
|
|
746
|
-
prismer:
|
|
747
|
-
version: "1.0.0"
|
|
748
|
-
mcp_tools:
|
|
749
|
-
- prismer.agent.send
|
|
750
|
-
- prismer.conversation.listAgents
|
|
751
|
-
- prismer.task.create
|
|
752
|
-
- prismer.task.update
|
|
753
|
-
- prismer.task.complete
|
|
754
|
-
- prismer.approval.request_human_approval
|
|
755
|
-
- prismer.asset.search
|
|
756
|
-
- prismer.asset.describe
|
|
757
|
-
- prismer.asset.read
|
|
758
|
-
---
|
|
759
|
-
|
|
760
|
-
# Prismer IM Collaboration
|
|
739
|
+
var PRISMER_IM_SKILL_CONTENT = `# Prismer IM Collaboration
|
|
761
740
|
|
|
762
741
|
You are an agent in a Prismer multi-agent workspace. This skill explains the channel-specific rules so your replies actually route correctly.
|
|
763
742
|
|
|
@@ -787,19 +766,6 @@ You will receive a \`[Channel context]\` block at the top of each prompt indicat
|
|
|
787
766
|
| Address another agent so the platform dispatches your message to them | Call \`prismer.agent.send\` (from the \`prismer-tasks\` MCP server). |
|
|
788
767
|
| Look up which agents you can talk to | Call \`prismer.conversation.listAgents\` before guessing usernames. |
|
|
789
768
|
| Create / update tasks on the workspace board | \`prismer.task.create\` / \`prismer.task.update\` from \`prismer-tasks\` MCP. |
|
|
790
|
-
| Ask for structured human sign-off | \`prismer.approval.request_human_approval\`. |
|
|
791
|
-
| Find uploaded workspace files | \`prismer.asset.search\`. |
|
|
792
|
-
| Inspect file metadata before reading bytes | \`prismer.asset.describe\`. |
|
|
793
|
-
| Read a bounded byte range from a file | \`prismer.asset.read\`. |
|
|
794
|
-
|
|
795
|
-
## Workspace asset rules
|
|
796
|
-
|
|
797
|
-
- Web-uploaded workspace files are available as asset metadata first; bytes are fetched lazily by the local daemon.
|
|
798
|
-
- When a task needs file content, call \`prismer.asset.describe\` first, then read only the needed range with \`prismer.asset.read\`.
|
|
799
|
-
- For many files, call \`prismer.asset.search\` to find candidates. Do not ask the user to paste file contents already present in workspace assets.
|
|
800
|
-
- Do not claim you read a file unless an asset tool call succeeded.
|
|
801
|
-
- For large tables/documents, read headers, samples, or targeted byte ranges. Never try to load the whole file into a reply.
|
|
802
|
-
- Cite the asset id/URI and locator returned by the tool when using file evidence.
|
|
803
769
|
|
|
804
770
|
### When to use \`prismer.agent.send\` vs inline \`@\`
|
|
805
771
|
|
|
@@ -813,7 +779,6 @@ You will receive a \`[Channel context]\` block at the top of each prompt indicat
|
|
|
813
779
|
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 @).
|
|
814
780
|
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.
|
|
815
781
|
4. **Check tool return values.** \`prismer.agent.send\` may return \`{ ok: false, error: 'agent_not_found' }\`; surface the error rather than pretending it worked.
|
|
816
|
-
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.
|
|
817
782
|
|
|
818
783
|
## Example: group discussion handoff
|
|
819
784
|
|
|
@@ -901,23 +866,7 @@ var HermesProfileConfigSchema = import_zod.z.object({
|
|
|
901
866
|
hermesSourceDir: import_zod.z.string().optional(),
|
|
902
867
|
nativeMirrorTimeoutMs: import_zod.z.number().int().positive().default(2e3),
|
|
903
868
|
/** Task authority level: executor (default) or orchestrator. */
|
|
904
|
-
taskAuthority: import_zod.z.enum(["executor", "orchestrator"]).optional().default("executor")
|
|
905
|
-
/** Human approval escalation policy. Enforcement is currently prompt/cloud-side. */
|
|
906
|
-
approvalPolicy: import_zod.z.enum(["strict", "auto-low-risk", "autonomous"]).optional().default("auto-low-risk"),
|
|
907
|
-
/** Agent-level MCP tool allowlist. Empty/null means all tools. Supports exact names and trailing * wildcards. */
|
|
908
|
-
mcpAllowlist: import_zod.z.array(import_zod.z.string()).nullable().optional(),
|
|
909
|
-
/** Agent-level operating principles injected through /v1/runs instructions. */
|
|
910
|
-
operatingPrinciples: import_zod.z.union([import_zod.z.string(), import_zod.z.record(import_zod.z.string())]).optional(),
|
|
911
|
-
/**
|
|
912
|
-
* Role-template snapshot carried in AgentProfile.config by cloud ACP.
|
|
913
|
-
* Expected fields used here:
|
|
914
|
-
* - roleTemplate.mcpServers[].toolsAllowlist
|
|
915
|
-
* - roleTemplate.operatingPrinciples
|
|
916
|
-
*/
|
|
917
|
-
roleTemplate: import_zod.z.object({
|
|
918
|
-
mcpServers: import_zod.z.array(import_zod.z.object({ toolsAllowlist: import_zod.z.array(import_zod.z.string()).optional() }).passthrough()).optional(),
|
|
919
|
-
operatingPrinciples: import_zod.z.union([import_zod.z.string(), import_zod.z.record(import_zod.z.string())]).optional()
|
|
920
|
-
}).passthrough().optional()
|
|
869
|
+
taskAuthority: import_zod.z.enum(["executor", "orchestrator"]).optional().default("executor")
|
|
921
870
|
});
|
|
922
871
|
var hermesAdapter = {
|
|
923
872
|
name: "hermes",
|
|
@@ -932,22 +881,14 @@ var hermesAdapter = {
|
|
|
932
881
|
errors: r.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
933
882
|
};
|
|
934
883
|
},
|
|
935
|
-
async prepareProfile(profile) {
|
|
936
|
-
const config = HermesProfileConfigSchema.parse(profile.config);
|
|
937
|
-
if (config.configurePrismerProvider) {
|
|
938
|
-
const profileName = getHermesProfileName(profile);
|
|
939
|
-
const agentUsername = profile.agentUsername || profileName;
|
|
940
|
-
configurePrismerProvider(profileName, config, agentUsername, profile.workspaceId);
|
|
941
|
-
}
|
|
942
|
-
},
|
|
943
884
|
async ensureService(profile) {
|
|
944
885
|
const config = HermesProfileConfigSchema.parse(profile.config);
|
|
945
|
-
const profileName =
|
|
946
|
-
const portOverride = config.port !== 8642 ? config.port : portForProfile(
|
|
886
|
+
const profileName = config.hermesProfileName && config.hermesProfileName !== "default" ? config.hermesProfileName : profile.agentUsername || profile.id.slice(0, 8);
|
|
887
|
+
const portOverride = config.port !== 8642 ? config.port : portForProfile(profileName, 8642);
|
|
947
888
|
const baseUrl = `http://127.0.0.1:${portOverride}`;
|
|
948
889
|
const agentUsername = profile.agentUsername || profileName;
|
|
949
890
|
if (config.configurePrismerProvider) {
|
|
950
|
-
configurePrismerProvider(profileName, config, agentUsername
|
|
891
|
+
configurePrismerProvider(profileName, config, agentUsername);
|
|
951
892
|
}
|
|
952
893
|
if (!await checkHealth(baseUrl, config.apiKey)) {
|
|
953
894
|
if (!config.autoStart) {
|
|
@@ -957,7 +898,6 @@ var hermesAdapter = {
|
|
|
957
898
|
(set autoStart: true on the AgentProfile to spawn it automatically)`
|
|
958
899
|
);
|
|
959
900
|
}
|
|
960
|
-
await stopStaleHermesGateways(profileName, portOverride, config.startupTimeoutMs);
|
|
961
901
|
(0, import_node_child_process.spawn)("hermes", ["-p", profileName, "gateway", "run"], {
|
|
962
902
|
detached: false,
|
|
963
903
|
stdio: "ignore",
|
|
@@ -1017,8 +957,6 @@ var HermesService = class {
|
|
|
1017
957
|
const startedAt = Date.now();
|
|
1018
958
|
let runId;
|
|
1019
959
|
const sysPrompt = typeof task.metadata?.systemPrompt === "string" ? task.metadata.systemPrompt : void 0;
|
|
1020
|
-
const operatingPrinciples = typeof task.metadata?.operatingPrinciples === "string" ? task.metadata.operatingPrinciples : void 0;
|
|
1021
|
-
const instructions = [sysPrompt, operatingPrinciples].filter(Boolean).join("\n\n");
|
|
1022
960
|
if (sysPrompt) {
|
|
1023
961
|
try {
|
|
1024
962
|
const profileDir = getHermesProfileDir(this.profileName);
|
|
@@ -1055,7 +993,7 @@ var HermesService = class {
|
|
|
1055
993
|
// sent `messages: [...]` which Hermes 0.12 rejects with 400.
|
|
1056
994
|
body: JSON.stringify({
|
|
1057
995
|
input: task.prompt,
|
|
1058
|
-
instructions:
|
|
996
|
+
instructions: sysPrompt,
|
|
1059
997
|
session_id: typeof task.metadata?.conversationId === "string" ? task.metadata.conversationId : void 0,
|
|
1060
998
|
stream: true
|
|
1061
999
|
}),
|
|
@@ -1340,7 +1278,7 @@ function formatHermesGoalText(goals) {
|
|
|
1340
1278
|
return `${index + 1}. ${priority}${goal.title}${description}`;
|
|
1341
1279
|
}).join("\n");
|
|
1342
1280
|
}
|
|
1343
|
-
function configurePrismerProvider(profileName, config, agentUsername
|
|
1281
|
+
function configurePrismerProvider(profileName, config, agentUsername) {
|
|
1344
1282
|
const baseUrl = resolvePrismerProviderBaseUrl(config);
|
|
1345
1283
|
const apiKey = resolvePrismerApiKey(config);
|
|
1346
1284
|
if (!baseUrl || !apiKey) {
|
|
@@ -1383,16 +1321,9 @@ function configurePrismerProvider(profileName, config, agentUsername, workspaceI
|
|
|
1383
1321
|
PRISMER_API_KEY: apiKey,
|
|
1384
1322
|
PRISMER_BASE_URL: baseUrl.replace(/\/api\/v1\/?$/, "")
|
|
1385
1323
|
};
|
|
1386
|
-
if (workspaceId) {
|
|
1387
|
-
mcpEnv.PRISMER_WORKSPACE_ID = workspaceId;
|
|
1388
|
-
}
|
|
1389
1324
|
if (agentUsername) {
|
|
1390
1325
|
mcpEnv.PRISMER_AGENT_USERNAME = agentUsername;
|
|
1391
1326
|
}
|
|
1392
|
-
const allowlist = resolveMcpAllowlist(config);
|
|
1393
|
-
if (allowlist) {
|
|
1394
|
-
mcpEnv.PRISMER_MCP_ALLOWLIST = allowlist.join(",");
|
|
1395
|
-
}
|
|
1396
1327
|
mcpServers["prismer-tasks"] = {
|
|
1397
1328
|
command: "node",
|
|
1398
1329
|
args: [serverPath],
|
|
@@ -1413,17 +1344,20 @@ function resolvePrismerMcpServerPath(config) {
|
|
|
1413
1344
|
if (config.prismerMcpServerPath) return config.prismerMcpServerPath;
|
|
1414
1345
|
if (process.env.PRISMER_MCP_SERVER) return process.env.PRISMER_MCP_SERVER;
|
|
1415
1346
|
try {
|
|
1416
|
-
const
|
|
1347
|
+
const { createRequire } = require("module");
|
|
1348
|
+
const req = createRequire(import_meta.url);
|
|
1417
1349
|
return req.resolve("@prismer/mcp-server");
|
|
1418
1350
|
} catch {
|
|
1419
1351
|
}
|
|
1420
1352
|
try {
|
|
1421
|
-
const
|
|
1422
|
-
const
|
|
1353
|
+
const { fileURLToPath: fileURLToPath2 } = require("url");
|
|
1354
|
+
const { join: join15, dirname: dirname8 } = require("path");
|
|
1355
|
+
const here = dirname8(fileURLToPath2(import_meta.url));
|
|
1356
|
+
const candidate = join15(here, "../../mcp/dist/index.js");
|
|
1423
1357
|
if ((0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
1424
|
-
const candidate2 = (
|
|
1358
|
+
const candidate2 = join15(here, "../../../mcp/dist/index.js");
|
|
1425
1359
|
if ((0, import_node_fs.existsSync)(candidate2)) return candidate2;
|
|
1426
|
-
const candidate3 = (
|
|
1360
|
+
const candidate3 = join15(here, "../../../../mcp/dist/index.js");
|
|
1427
1361
|
if ((0, import_node_fs.existsSync)(candidate3)) return candidate3;
|
|
1428
1362
|
} catch {
|
|
1429
1363
|
}
|
|
@@ -1592,29 +1526,25 @@ function runPythonJson(args, env, timeoutMs) {
|
|
|
1592
1526
|
});
|
|
1593
1527
|
});
|
|
1594
1528
|
}
|
|
1595
|
-
function readHermesConfig(
|
|
1596
|
-
if (!(0, import_node_fs.existsSync)(
|
|
1529
|
+
function readHermesConfig(path7) {
|
|
1530
|
+
if (!(0, import_node_fs.existsSync)(path7)) return {};
|
|
1597
1531
|
try {
|
|
1598
|
-
return YAML.parse((0, import_node_fs.readFileSync)(
|
|
1532
|
+
return YAML.parse((0, import_node_fs.readFileSync)(path7, "utf8")) ?? {};
|
|
1599
1533
|
} catch (err) {
|
|
1600
|
-
throw new Error(`Failed to parse Hermes config at ${
|
|
1534
|
+
throw new Error(`Failed to parse Hermes config at ${path7}: ${err.message}`);
|
|
1601
1535
|
}
|
|
1602
1536
|
}
|
|
1603
|
-
function writeEnvValue(
|
|
1604
|
-
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(
|
|
1605
|
-
const lines = (0, import_node_fs.existsSync)(
|
|
1537
|
+
function writeEnvValue(path7, key, value) {
|
|
1538
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path7), { recursive: true });
|
|
1539
|
+
const lines = (0, import_node_fs.existsSync)(path7) ? (0, import_node_fs.readFileSync)(path7, "utf8").split(/\r?\n/) : [];
|
|
1606
1540
|
const next = lines.filter((line) => !line.startsWith(`${key}=`) && line.trim() !== "");
|
|
1607
1541
|
next.push(`${key}=${quoteEnv(value)}`);
|
|
1608
|
-
(0, import_node_fs.writeFileSync)(
|
|
1542
|
+
(0, import_node_fs.writeFileSync)(path7, `${next.join("\n")}
|
|
1609
1543
|
`, "utf8");
|
|
1610
1544
|
}
|
|
1611
1545
|
function quoteEnv(value) {
|
|
1612
1546
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1613
1547
|
}
|
|
1614
|
-
function getHermesProfileName(profile) {
|
|
1615
|
-
const configured = profile.config && typeof profile.config.hermesProfileName === "string" ? profile.config.hermesProfileName : void 0;
|
|
1616
|
-
return configured && configured !== "default" ? configured : profile.agentUsername || profile.id.slice(0, 8);
|
|
1617
|
-
}
|
|
1618
1548
|
function getHermesProfileDir(profileName) {
|
|
1619
1549
|
const root = process.env.HERMES_HOME || (0, import_node_path.join)((0, import_node_os.homedir)(), ".hermes");
|
|
1620
1550
|
if (!profileName || profileName === "default") return root;
|
|
@@ -1636,18 +1566,6 @@ function resolvePrismerProviderBaseUrl(config) {
|
|
|
1636
1566
|
function resolvePrismerApiKey(config) {
|
|
1637
1567
|
return process.env[config.prismerApiKeyEnv] || process.env.PRISMER_API_KEY || "";
|
|
1638
1568
|
}
|
|
1639
|
-
function resolveMcpAllowlist(config) {
|
|
1640
|
-
if (Array.isArray(config.mcpAllowlist)) return normalizeAllowlist(config.mcpAllowlist);
|
|
1641
|
-
const prismerServer = config.roleTemplate?.mcpServers?.find((server) => {
|
|
1642
|
-
const record = server;
|
|
1643
|
-
return record.package === "@prismer/mcp-server" || record.name === "prismer-tasks";
|
|
1644
|
-
});
|
|
1645
|
-
if (!prismerServer) return null;
|
|
1646
|
-
return Array.isArray(prismerServer.toolsAllowlist) ? normalizeAllowlist(prismerServer.toolsAllowlist) : null;
|
|
1647
|
-
}
|
|
1648
|
-
function normalizeAllowlist(values) {
|
|
1649
|
-
return values.map((value) => value.trim()).filter(Boolean);
|
|
1650
|
-
}
|
|
1651
1569
|
function normalizeProviderName(value) {
|
|
1652
1570
|
return value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
1653
1571
|
}
|
|
@@ -1753,66 +1671,6 @@ async function waitForHealthy(baseUrl, apiKey, timeoutMs) {
|
|
|
1753
1671
|
}
|
|
1754
1672
|
throw new Error(`Hermes did not become healthy at ${baseUrl} within ${timeoutMs}ms`);
|
|
1755
1673
|
}
|
|
1756
|
-
async function stopStaleHermesGateways(profileName, port, timeoutMs) {
|
|
1757
|
-
const pids = findHermesGatewayPids(profileName, port);
|
|
1758
|
-
for (const pid of pids) {
|
|
1759
|
-
try {
|
|
1760
|
-
process.kill(pid, "SIGTERM");
|
|
1761
|
-
} catch {
|
|
1762
|
-
continue;
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
const deadline = Date.now() + Math.min(timeoutMs, 5e3);
|
|
1766
|
-
while (Date.now() < deadline) {
|
|
1767
|
-
const alive = pids.filter(pidAlive);
|
|
1768
|
-
if (alive.length === 0) return;
|
|
1769
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
1770
|
-
}
|
|
1771
|
-
for (const pid of pids) {
|
|
1772
|
-
if (!pidAlive(pid)) continue;
|
|
1773
|
-
try {
|
|
1774
|
-
process.kill(pid, "SIGKILL");
|
|
1775
|
-
} catch {
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
function findHermesGatewayPids(profileName, port) {
|
|
1780
|
-
try {
|
|
1781
|
-
const out = (0, import_node_child_process.execFileSync)("ps", ["-axo", "pid=,command="], { encoding: "utf8" });
|
|
1782
|
-
const current = process.pid;
|
|
1783
|
-
const escapedProfile = escapeRegExp(profileName);
|
|
1784
|
-
const profilePattern = new RegExp(`(?:^|\\s)-p\\s+${escapedProfile}(?:\\s|$)`);
|
|
1785
|
-
const portPattern = new RegExp(`(?:^|\\s)API_SERVER_PORT=${port}(?:\\s|$)`);
|
|
1786
|
-
return out.split(/\r?\n/).map((line) => {
|
|
1787
|
-
const trimmed = line.trim();
|
|
1788
|
-
const match = /^(\d+)\s+(.+)$/.exec(trimmed);
|
|
1789
|
-
if (!match) return null;
|
|
1790
|
-
const pid = Number.parseInt(match[1], 10);
|
|
1791
|
-
const command = match[2];
|
|
1792
|
-
if (!Number.isFinite(pid) || pid === current) return null;
|
|
1793
|
-
if (!isHermesGatewayCommand(command)) return null;
|
|
1794
|
-
if (!profilePattern.test(command) && !portPattern.test(command)) return null;
|
|
1795
|
-
return pid;
|
|
1796
|
-
}).filter((pid) => pid !== null);
|
|
1797
|
-
} catch {
|
|
1798
|
-
return [];
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
function isHermesGatewayCommand(command) {
|
|
1802
|
-
if (!/\bgateway\b/.test(command) || !/\brun\b/.test(command)) return false;
|
|
1803
|
-
return /(^|\s)(?:\S*\/)?hermes(?:\s|$)/.test(command);
|
|
1804
|
-
}
|
|
1805
|
-
function pidAlive(pid) {
|
|
1806
|
-
try {
|
|
1807
|
-
process.kill(pid, 0);
|
|
1808
|
-
return true;
|
|
1809
|
-
} catch {
|
|
1810
|
-
return false;
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
function escapeRegExp(value) {
|
|
1814
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1815
|
-
}
|
|
1816
1674
|
|
|
1817
1675
|
// src/daemon/ws-client.ts
|
|
1818
1676
|
var import_node_events = require("events");
|
|
@@ -2052,11 +1910,11 @@ var MIGRATIONS = [
|
|
|
2052
1910
|
function runSql(db, sql) {
|
|
2053
1911
|
db["exec"](sql);
|
|
2054
1912
|
}
|
|
2055
|
-
function openLocalDb(
|
|
2056
|
-
if (
|
|
2057
|
-
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(
|
|
1913
|
+
function openLocalDb(path7) {
|
|
1914
|
+
if (path7 !== ":memory:" && !(0, import_node_fs2.existsSync)((0, import_node_path2.dirname)(path7))) {
|
|
1915
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(path7), { recursive: true });
|
|
2058
1916
|
}
|
|
2059
|
-
const db = new import_better_sqlite32.default(
|
|
1917
|
+
const db = new import_better_sqlite32.default(path7);
|
|
2060
1918
|
db.pragma("journal_mode = WAL");
|
|
2061
1919
|
db.pragma("foreign_keys = ON");
|
|
2062
1920
|
runMigrations(db);
|
|
@@ -2374,12 +2232,11 @@ function deriveWsUrl(httpBase) {
|
|
|
2374
2232
|
}
|
|
2375
2233
|
|
|
2376
2234
|
// src/daemon-id.ts
|
|
2377
|
-
var import_node_crypto = require("crypto");
|
|
2378
2235
|
var import_node_os3 = require("os");
|
|
2379
2236
|
var PREFIX = "daemon-";
|
|
2380
2237
|
function newDaemonId() {
|
|
2381
2238
|
const host = (0, import_node_os3.hostname)().replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unknown";
|
|
2382
|
-
return `${PREFIX}${host}
|
|
2239
|
+
return `${PREFIX}${host}`;
|
|
2383
2240
|
}
|
|
2384
2241
|
function isDaemonId(s) {
|
|
2385
2242
|
return s.startsWith(PREFIX) && s.length > PREFIX.length;
|
|
@@ -2394,8 +2251,8 @@ var CloudClient = class {
|
|
|
2394
2251
|
opts;
|
|
2395
2252
|
fetchImpl;
|
|
2396
2253
|
/** Low-level request. Caller decides on retry; sync-worker handles backoff. */
|
|
2397
|
-
async request(method,
|
|
2398
|
-
const url = this.urlFor(
|
|
2254
|
+
async request(method, path7, init) {
|
|
2255
|
+
const url = this.urlFor(path7);
|
|
2399
2256
|
const headers = {
|
|
2400
2257
|
"Content-Type": "application/json",
|
|
2401
2258
|
Accept: "application/json",
|
|
@@ -2454,8 +2311,8 @@ var CloudClient = class {
|
|
|
2454
2311
|
return { ok: true, status: res.status, data: body ?? void 0 };
|
|
2455
2312
|
}
|
|
2456
2313
|
/** Convenience: GET expecting `{ ok, data }` envelope. Returns `data` or throws. */
|
|
2457
|
-
async get(
|
|
2458
|
-
const res = await this.request("GET",
|
|
2314
|
+
async get(path7, opts) {
|
|
2315
|
+
const res = await this.request("GET", path7, opts);
|
|
2459
2316
|
if (!res.ok) throw new CloudError(res.status, res.error?.code ?? "unknown", res.error?.message ?? "request failed");
|
|
2460
2317
|
if (res.data && typeof res.data === "object" && "ok" in res.data) {
|
|
2461
2318
|
const env = res.data;
|
|
@@ -2464,14 +2321,14 @@ var CloudClient = class {
|
|
|
2464
2321
|
}
|
|
2465
2322
|
return res.data;
|
|
2466
2323
|
}
|
|
2467
|
-
urlFor(
|
|
2324
|
+
urlFor(path7) {
|
|
2468
2325
|
const trimmedBase = this.opts.baseUrl.replace(/\/$/, "");
|
|
2469
|
-
const trimmedPath =
|
|
2326
|
+
const trimmedPath = path7.startsWith("/") ? path7 : `/${path7}`;
|
|
2470
2327
|
return `${trimmedBase}${trimmedPath}`;
|
|
2471
2328
|
}
|
|
2472
2329
|
/** Raw byte download (for asset cache). Returns response so caller can stream body. */
|
|
2473
|
-
async fetchRaw(
|
|
2474
|
-
const url = this.urlFor(
|
|
2330
|
+
async fetchRaw(path7, init) {
|
|
2331
|
+
const url = this.urlFor(path7);
|
|
2475
2332
|
const headers = {
|
|
2476
2333
|
Authorization: `Bearer ${this.opts.apiKey}`,
|
|
2477
2334
|
...init?.headers ?? {}
|
|
@@ -2520,7 +2377,6 @@ function envelope(type, payload, requestId) {
|
|
|
2520
2377
|
}
|
|
2521
2378
|
|
|
2522
2379
|
// src/asset-cache.ts
|
|
2523
|
-
var import_node_crypto2 = require("crypto");
|
|
2524
2380
|
var import_node_fs4 = require("fs");
|
|
2525
2381
|
var import_node_path4 = require("path");
|
|
2526
2382
|
function rowToCached(row) {
|
|
@@ -2614,17 +2470,11 @@ var AssetCache = class {
|
|
|
2614
2470
|
throw new Error(`Asset bytes fetch failed for ${hash} (id=${assetId}): HTTP ${res.status}`);
|
|
2615
2471
|
}
|
|
2616
2472
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
2617
|
-
const actualHash = sha256(buf);
|
|
2618
|
-
if (actualHash !== hash) {
|
|
2619
|
-
throw new Error(`Asset hash mismatch for ${hash}: downloaded ${actualHash}`);
|
|
2620
|
-
}
|
|
2621
2473
|
const localPath = this.pathFor(hash);
|
|
2622
2474
|
if (!(0, import_node_fs4.existsSync)((0, import_node_path4.dirname)(localPath))) {
|
|
2623
2475
|
(0, import_node_fs4.mkdirSync)((0, import_node_path4.dirname)(localPath), { recursive: true });
|
|
2624
2476
|
}
|
|
2625
|
-
|
|
2626
|
-
(0, import_node_fs4.writeFileSync)(tmpPath, buf);
|
|
2627
|
-
(0, import_node_fs4.renameSync)(tmpPath, localPath);
|
|
2477
|
+
(0, import_node_fs4.writeFileSync)(localPath, buf);
|
|
2628
2478
|
const mime = res.headers.get("content-type");
|
|
2629
2479
|
const now = Date.now();
|
|
2630
2480
|
this.db.prepare(
|
|
@@ -2645,10 +2495,6 @@ var AssetCache = class {
|
|
|
2645
2495
|
}
|
|
2646
2496
|
/** Insert an already-on-disk asset into the cache (e.g., asset just produced by adapter). */
|
|
2647
2497
|
registerLocal(hash, localPath, mime) {
|
|
2648
|
-
const actualHash = sha256(readFileBuffer(localPath));
|
|
2649
|
-
if (actualHash !== hash) {
|
|
2650
|
-
throw new Error(`Asset hash mismatch for ${hash}: local file is ${actualHash}`);
|
|
2651
|
-
}
|
|
2652
2498
|
const size = (0, import_node_fs4.statSync)(localPath).size;
|
|
2653
2499
|
const now = Date.now();
|
|
2654
2500
|
this.db.prepare(
|
|
@@ -2688,12 +2534,6 @@ var AssetCache = class {
|
|
|
2688
2534
|
return { removed, freed };
|
|
2689
2535
|
}
|
|
2690
2536
|
};
|
|
2691
|
-
function sha256(buf) {
|
|
2692
|
-
return (0, import_node_crypto2.createHash)("sha256").update(buf).digest("hex");
|
|
2693
|
-
}
|
|
2694
|
-
function readFileBuffer(path9) {
|
|
2695
|
-
return (0, import_node_fs4.readFileSync)(path9);
|
|
2696
|
-
}
|
|
2697
2537
|
|
|
2698
2538
|
// src/daemon/asset/mirror.ts
|
|
2699
2539
|
var import_node_fs5 = require("fs");
|
|
@@ -2790,8 +2630,8 @@ var WorkspaceMirror = class {
|
|
|
2790
2630
|
return { applied, cursor: envelope2.cursor };
|
|
2791
2631
|
}
|
|
2792
2632
|
/** Lookup the active binding for a workspace path. Returns undefined if not mirrored. */
|
|
2793
|
-
lookupByPath(
|
|
2794
|
-
const row = this.db.prepare("SELECT * FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?").get(this.workspaceId,
|
|
2633
|
+
lookupByPath(path7) {
|
|
2634
|
+
const row = this.db.prepare("SELECT * FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?").get(this.workspaceId, path7);
|
|
2795
2635
|
return row ? rowToBinding(row) : void 0;
|
|
2796
2636
|
}
|
|
2797
2637
|
/** Find an asset's contentHash via any path binding (asset may map to many paths). */
|
|
@@ -3053,20 +2893,20 @@ var UriResolver = class {
|
|
|
3053
2893
|
}
|
|
3054
2894
|
return { texts: all, resolvedHashes: dedupe(allHashes) };
|
|
3055
2895
|
}
|
|
3056
|
-
async lookupFile(workspaceId,
|
|
2896
|
+
async lookupFile(workspaceId, path7, signal) {
|
|
3057
2897
|
const local = this.db.prepare(
|
|
3058
2898
|
"SELECT asset_id, content_hash, version FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?"
|
|
3059
|
-
).get(workspaceId,
|
|
2899
|
+
).get(workspaceId, path7);
|
|
3060
2900
|
if (local) return local;
|
|
3061
2901
|
const remote = await this.cloud.get(
|
|
3062
|
-
`/api/im/workspaces/${encodeURIComponent(workspaceId)}/files?path=${encodeURIComponent(
|
|
2902
|
+
`/api/im/workspaces/${encodeURIComponent(workspaceId)}/files?path=${encodeURIComponent(path7)}`,
|
|
3063
2903
|
{ signal }
|
|
3064
2904
|
);
|
|
3065
2905
|
this.db.prepare(
|
|
3066
2906
|
`INSERT OR REPLACE INTO workspace_files_mirror
|
|
3067
2907
|
(workspace_id, path, asset_id, content_hash, version, synced_at, dirty)
|
|
3068
2908
|
VALUES (?, ?, ?, ?, ?, ?, 0)`
|
|
3069
|
-
).run(workspaceId,
|
|
2909
|
+
).run(workspaceId, path7, remote.assetId, remote.contentHash, remote.version, Date.now());
|
|
3070
2910
|
return { asset_id: remote.assetId, content_hash: remote.contentHash, version: remote.version };
|
|
3071
2911
|
}
|
|
3072
2912
|
};
|
|
@@ -3075,244 +2915,11 @@ function dedupe(xs) {
|
|
|
3075
2915
|
}
|
|
3076
2916
|
|
|
3077
2917
|
// src/daemon/dispatch.ts
|
|
3078
|
-
var import_node_fs7 = require("fs");
|
|
3079
|
-
var path = __toESM(require("path"), 1);
|
|
3080
|
-
|
|
3081
|
-
// src/daemon/skill-sync.ts
|
|
3082
2918
|
var import_node_fs6 = require("fs");
|
|
3083
|
-
var
|
|
3084
|
-
var import_node_crypto3 = require("crypto");
|
|
3085
|
-
|
|
3086
|
-
// src/adapters/openclaw/index.ts
|
|
3087
|
-
var import_zod3 = require("zod");
|
|
3088
|
-
var import_node_os4 = require("os");
|
|
3089
|
-
var import_node_path6 = require("path");
|
|
3090
|
-
var OpenClawProfileConfigSchema = import_zod3.z.object({
|
|
3091
|
-
/** OpenClaw chat-completions HTTP port. */
|
|
3092
|
-
port: import_zod3.z.number().int().min(1).max(65535).default(18789),
|
|
3093
|
-
/** Bearer token expected by the gateway (from gateway.auth.bearerTokens). */
|
|
3094
|
-
apiKey: import_zod3.z.string().min(1),
|
|
3095
|
-
/**
|
|
3096
|
-
* Model field passed to OpenClaw's /v1/chat/completions. OpenClaw
|
|
3097
|
-
* intercepts this differently from a vanilla OpenAI server: the value
|
|
3098
|
-
* MUST be `openclaw` (or `openclaw/<agentId>`) — it picks which
|
|
3099
|
-
* OpenClaw agent answers, NOT the underlying LLM. The LLM is selected
|
|
3100
|
-
* by `agents.defaults.model.primary` in openclaw.json (e.g.
|
|
3101
|
-
* `prismer/us-kimi-k2.5`, set by the bootstrap).
|
|
3102
|
-
*/
|
|
3103
|
-
model: import_zod3.z.string().min(1).default("openclaw"),
|
|
3104
|
-
mcpAllowlist: import_zod3.z.array(import_zod3.z.string()).nullable().optional(),
|
|
3105
|
-
approvalPolicy: import_zod3.z.enum(["strict", "auto-low-risk", "autonomous"]).optional().default("auto-low-risk"),
|
|
3106
|
-
roleTemplate: import_zod3.z.object({
|
|
3107
|
-
mcpServers: import_zod3.z.array(import_zod3.z.object({ toolsAllowlist: import_zod3.z.array(import_zod3.z.string()).optional() }).passthrough()).optional()
|
|
3108
|
-
}).passthrough().optional(),
|
|
3109
|
-
skillsDir: import_zod3.z.string().optional()
|
|
3110
|
-
});
|
|
3111
|
-
var openclawAdapter = {
|
|
3112
|
-
name: "openclaw",
|
|
3113
|
-
kind: "long-running",
|
|
3114
|
-
capabilities: ["chat", "code", "multi-channel"],
|
|
3115
|
-
workspaceSchema: OpenClawProfileConfigSchema,
|
|
3116
|
-
validate(config) {
|
|
3117
|
-
const r = OpenClawProfileConfigSchema.safeParse(config);
|
|
3118
|
-
if (r.success) return { ok: true };
|
|
3119
|
-
return {
|
|
3120
|
-
ok: false,
|
|
3121
|
-
errors: r.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
3122
|
-
};
|
|
3123
|
-
},
|
|
3124
|
-
async ensureService(profile) {
|
|
3125
|
-
const config = OpenClawProfileConfigSchema.parse(profile.config);
|
|
3126
|
-
const baseUrl = `http://127.0.0.1:${config.port}`;
|
|
3127
|
-
if (!await checkHealth2(baseUrl, config.apiKey)) {
|
|
3128
|
-
throw new Error(
|
|
3129
|
-
`OpenClaw gateway not reachable at ${baseUrl}. Start it with:
|
|
3130
|
-
openclaw gateway --port ${config.port}`
|
|
3131
|
-
);
|
|
3132
|
-
}
|
|
3133
|
-
return new OpenClawService(baseUrl, config.apiKey, config.model, resolveOpenClawSkillsDir(profile, config));
|
|
3134
|
-
},
|
|
3135
|
-
async health() {
|
|
3136
|
-
return { available: true };
|
|
3137
|
-
}
|
|
3138
|
-
};
|
|
3139
|
-
var OpenClawService = class {
|
|
3140
|
-
constructor(baseUrl, apiKey, model, skillsDir) {
|
|
3141
|
-
this.baseUrl = baseUrl;
|
|
3142
|
-
this.apiKey = apiKey;
|
|
3143
|
-
this.model = model;
|
|
3144
|
-
this.skillsDir = skillsDir;
|
|
3145
|
-
this.id = baseUrl;
|
|
3146
|
-
}
|
|
3147
|
-
baseUrl;
|
|
3148
|
-
apiKey;
|
|
3149
|
-
model;
|
|
3150
|
-
skillsDir;
|
|
3151
|
-
id;
|
|
3152
|
-
async healthy() {
|
|
3153
|
-
return checkHealth2(this.baseUrl, this.apiKey);
|
|
3154
|
-
}
|
|
3155
|
-
async dispatch(task) {
|
|
3156
|
-
const startedAt = Date.now();
|
|
3157
|
-
const systemPrompt = typeof task.metadata?.systemPrompt === "string" ? task.metadata.systemPrompt : void 0;
|
|
3158
|
-
const messages = [];
|
|
3159
|
-
if (systemPrompt) messages.push({ role: "system", content: systemPrompt });
|
|
3160
|
-
messages.push({ role: "user", content: task.prompt });
|
|
3161
|
-
try {
|
|
3162
|
-
const res = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
3163
|
-
method: "POST",
|
|
3164
|
-
headers: {
|
|
3165
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
3166
|
-
"Content-Type": "application/json"
|
|
3167
|
-
},
|
|
3168
|
-
body: JSON.stringify({
|
|
3169
|
-
model: this.model,
|
|
3170
|
-
messages,
|
|
3171
|
-
stream: false
|
|
3172
|
-
}),
|
|
3173
|
-
signal: task.signal
|
|
3174
|
-
});
|
|
3175
|
-
if (!res.ok) {
|
|
3176
|
-
const body = await res.text().catch(() => "") ?? "";
|
|
3177
|
-
return {
|
|
3178
|
-
ok: false,
|
|
3179
|
-
error: {
|
|
3180
|
-
code: res.status === 401 || res.status === 403 ? "auth_invalid" : "adapter_dispatch_failed",
|
|
3181
|
-
message: `OpenClaw ${res.status}: ${body.slice(0, 400)}`
|
|
3182
|
-
}
|
|
3183
|
-
};
|
|
3184
|
-
}
|
|
3185
|
-
const json = await res.json();
|
|
3186
|
-
let output = json.choices?.[0]?.message?.content ?? "";
|
|
3187
|
-
const ERR_PREFIX_RE = /^Cannot read properties of undefined \(reading '[^']+'\)\s*\n+/;
|
|
3188
|
-
if (ERR_PREFIX_RE.test(output)) {
|
|
3189
|
-
process.stderr.write(
|
|
3190
|
-
`[openclaw-adapter] stripping known stderr-bleed prefix from chat output (OpenClaw 2026.4.x bug)
|
|
3191
|
-
`
|
|
3192
|
-
);
|
|
3193
|
-
output = output.replace(ERR_PREFIX_RE, "");
|
|
3194
|
-
}
|
|
3195
|
-
return {
|
|
3196
|
-
ok: true,
|
|
3197
|
-
output,
|
|
3198
|
-
metrics: { durationMs: Date.now() - startedAt }
|
|
3199
|
-
};
|
|
3200
|
-
} catch (err) {
|
|
3201
|
-
if (task.signal?.aborted || err?.name === "AbortError") {
|
|
3202
|
-
return { ok: false, error: { code: "task_cancelled", message: "Task cancelled by client" } };
|
|
3203
|
-
}
|
|
3204
|
-
return {
|
|
3205
|
-
ok: false,
|
|
3206
|
-
error: { code: "adapter_dispatch_failed", message: err.message }
|
|
3207
|
-
};
|
|
3208
|
-
}
|
|
3209
|
-
}
|
|
3210
|
-
};
|
|
3211
|
-
function getOpenClawSkillsDir(profile) {
|
|
3212
|
-
const config = OpenClawProfileConfigSchema.parse(profile.config);
|
|
3213
|
-
return resolveOpenClawSkillsDir(profile, config);
|
|
3214
|
-
}
|
|
3215
|
-
function resolveOpenClawSkillsDir(profile, config) {
|
|
3216
|
-
if (config.skillsDir) return config.skillsDir;
|
|
3217
|
-
const name = profile.agentUsername || profile.id.slice(0, 8);
|
|
3218
|
-
return (0, import_node_path6.join)(process.env.OPENCLAW_HOME || (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".openclaw"), "profiles", name, "skills");
|
|
3219
|
-
}
|
|
3220
|
-
async function checkHealth2(baseUrl, apiKey) {
|
|
3221
|
-
try {
|
|
3222
|
-
const res = await fetch(`${baseUrl}/health`, {
|
|
3223
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
3224
|
-
signal: AbortSignal.timeout(2e3)
|
|
3225
|
-
});
|
|
3226
|
-
return res.ok;
|
|
3227
|
-
} catch {
|
|
3228
|
-
return false;
|
|
3229
|
-
}
|
|
3230
|
-
}
|
|
3231
|
-
|
|
3232
|
-
// src/daemon/skill-sync.ts
|
|
3233
|
-
async function syncInstalledSkillsForDispatch(profile, agentImUserId, cloud, signal) {
|
|
3234
|
-
if (!agentImUserId) return { synced: 0, skipped: 0, unchanged: 0 };
|
|
3235
|
-
const data = await cloud.get(
|
|
3236
|
-
`/api/im/skills/installed?agentId=${encodeURIComponent(agentImUserId)}`,
|
|
3237
|
-
{ signal }
|
|
3238
|
-
);
|
|
3239
|
-
const entries = normalizeInstalledSkills(data);
|
|
3240
|
-
const skillsRoot = resolveSkillsRoot(profile);
|
|
3241
|
-
if (!skillsRoot) return { synced: 0, skipped: 0, unchanged: 0 };
|
|
3242
|
-
let synced = 0;
|
|
3243
|
-
let skipped = 0;
|
|
3244
|
-
let unchanged = 0;
|
|
3245
|
-
for (const entry of entries) {
|
|
3246
|
-
const slug = sanitizeSlug(trimmedStringFrom(entry.skill?.slug) ?? trimmedStringFrom(entry.slug));
|
|
3247
|
-
const content = contentStringFrom(entry.skill?.content) ?? contentStringFrom(entry.content);
|
|
3248
|
-
if (!slug || !content) {
|
|
3249
|
-
skipped++;
|
|
3250
|
-
if (slug && !content) {
|
|
3251
|
-
process.stderr.write(`[daemon] skill sync skipped ${slug}: missing content
|
|
3252
|
-
`);
|
|
3253
|
-
}
|
|
3254
|
-
continue;
|
|
3255
|
-
}
|
|
3256
|
-
const skillDir = (0, import_node_path7.join)(skillsRoot, slug);
|
|
3257
|
-
await import_node_fs6.promises.mkdir(skillDir, { recursive: true });
|
|
3258
|
-
const target = (0, import_node_path7.join)(skillDir, "SKILL.md");
|
|
3259
|
-
const existing = await import_node_fs6.promises.readFile(target, "utf8").catch(() => null);
|
|
3260
|
-
if (existing != null && sha2562(existing) === sha2562(content)) {
|
|
3261
|
-
unchanged++;
|
|
3262
|
-
continue;
|
|
3263
|
-
}
|
|
3264
|
-
await import_node_fs6.promises.writeFile(target, content, "utf8");
|
|
3265
|
-
synced++;
|
|
3266
|
-
}
|
|
3267
|
-
return { synced, skipped, unchanged };
|
|
3268
|
-
}
|
|
3269
|
-
function resolveSkillsRoot(profile) {
|
|
3270
|
-
if (profile.adapterName === "hermes") return (0, import_node_path7.join)(getHermesProfileDir(getHermesProfileName(profile)), "skills");
|
|
3271
|
-
if (profile.adapterName === "openclaw") return getOpenClawSkillsDir(profile);
|
|
3272
|
-
return null;
|
|
3273
|
-
}
|
|
3274
|
-
function sha2562(value) {
|
|
3275
|
-
return (0, import_node_crypto3.createHash)("sha256").update(value).digest("hex");
|
|
3276
|
-
}
|
|
3277
|
-
function normalizeInstalledSkills(data) {
|
|
3278
|
-
if (Array.isArray(data)) return data.filter(isInstalledSkillEntry);
|
|
3279
|
-
if (!data || typeof data !== "object" || Array.isArray(data)) return [];
|
|
3280
|
-
const record = data;
|
|
3281
|
-
const nested = record.data;
|
|
3282
|
-
if (Array.isArray(nested)) return nested.filter(isInstalledSkillEntry);
|
|
3283
|
-
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
3284
|
-
const skills2 = nested.skills;
|
|
3285
|
-
if (Array.isArray(skills2)) return skills2.filter(isInstalledSkillEntry);
|
|
3286
|
-
}
|
|
3287
|
-
const skills = record.skills;
|
|
3288
|
-
if (Array.isArray(skills)) return skills.filter(isInstalledSkillEntry);
|
|
3289
|
-
return [];
|
|
3290
|
-
}
|
|
3291
|
-
function isInstalledSkillEntry(value) {
|
|
3292
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3293
|
-
}
|
|
3294
|
-
function trimmedStringFrom(value) {
|
|
3295
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
3296
|
-
}
|
|
3297
|
-
function contentStringFrom(value) {
|
|
3298
|
-
return typeof value === "string" && value.trim() ? value : void 0;
|
|
3299
|
-
}
|
|
3300
|
-
function sanitizeSlug(value) {
|
|
3301
|
-
if (!value) return void 0;
|
|
3302
|
-
const safe = value.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
3303
|
-
return safe || void 0;
|
|
3304
|
-
}
|
|
3305
|
-
|
|
3306
|
-
// src/daemon/dispatch.ts
|
|
2919
|
+
var path = __toESM(require("path"), 1);
|
|
3307
2920
|
var DEFAULT_CONTEXT_MAX = 8e3;
|
|
3308
2921
|
var GOAL_CONTEXT_MAX = 4;
|
|
3309
2922
|
var MEMORY_DIGEST_MAX_BYTES = 4e3;
|
|
3310
|
-
var DEFAULT_OPERATING_PRINCIPLES = [
|
|
3311
|
-
"- Decide and act; only request human approval for destructive operations or irreversible external effects.",
|
|
3312
|
-
"- Assignable work should become explicit tasks; do not leave concrete work as chat discussion only.",
|
|
3313
|
-
"- Use workspace asset tools for uploaded files: search/describe first, then read bounded ranges; do not claim file contents unless a tool call succeeded.",
|
|
3314
|
-
"- Keep users informed with concise status and concrete next steps."
|
|
3315
|
-
].join("\n");
|
|
3316
2923
|
var ASSET_INLINE_FULL_MAX_BYTES = 64 * 1024;
|
|
3317
2924
|
var ASSET_INLINE_TRUNCATE_BYTES = 16 * 1024;
|
|
3318
2925
|
async function handleDispatch(payload, requestId, deps) {
|
|
@@ -3323,7 +2930,7 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
3323
2930
|
const outboxDir = outboxBase ? path.join(outboxBase, "_outbox") : null;
|
|
3324
2931
|
if (outboxDir) {
|
|
3325
2932
|
try {
|
|
3326
|
-
await
|
|
2933
|
+
await import_node_fs6.promises.mkdir(outboxDir, { recursive: true });
|
|
3327
2934
|
} catch (err) {
|
|
3328
2935
|
process.stderr.write(
|
|
3329
2936
|
`[daemon] outbox provision failed task=${taskId} dir=${outboxDir}: ${err.message}
|
|
@@ -3350,18 +2957,6 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
3350
2957
|
sendReply(deps.ws, reply, requestId);
|
|
3351
2958
|
return reply;
|
|
3352
2959
|
}
|
|
3353
|
-
try {
|
|
3354
|
-
const skillSync = await syncInstalledSkillsForDispatch(profile, agentImUserId, deps.cloud, deps.signal);
|
|
3355
|
-
if (skillSync.synced > 0 || skillSync.skipped > 0) {
|
|
3356
|
-
process.stderr.write(
|
|
3357
|
-
`[daemon] skill sync profile=${profile.id} adapter=${profile.adapterName} synced=${skillSync.synced} skipped=${skillSync.skipped}
|
|
3358
|
-
`
|
|
3359
|
-
);
|
|
3360
|
-
}
|
|
3361
|
-
} catch (err) {
|
|
3362
|
-
process.stderr.write(`[daemon] skill sync skipped profile=${profile.id}: ${err.message}
|
|
3363
|
-
`);
|
|
3364
|
-
}
|
|
3365
2960
|
let hashRefResult = { text: payload.prompt, resolutions: [] };
|
|
3366
2961
|
if (deps.assetMetadataIndexes && profile.workspaceId) {
|
|
3367
2962
|
const assetIndex = deps.assetMetadataIndexes.get(profile.workspaceId);
|
|
@@ -3411,7 +3006,6 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
3411
3006
|
`
|
|
3412
3007
|
);
|
|
3413
3008
|
}
|
|
3414
|
-
const operatingPrinciples = resolveOperatingPrinciples(profile.config);
|
|
3415
3009
|
const taskInput = {
|
|
3416
3010
|
taskId,
|
|
3417
3011
|
prompt: adapterPrompt,
|
|
@@ -3420,7 +3014,6 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
3420
3014
|
conversationId: payload.conversationId,
|
|
3421
3015
|
prismerGoals: goalContext.map(toGoalMirrorPayload),
|
|
3422
3016
|
...profileSystemPrompt ? { systemPrompt: profileSystemPrompt } : {},
|
|
3423
|
-
...operatingPrinciples ? { operatingPrinciples } : {},
|
|
3424
3017
|
// Wave-9: spawn-style adapters (claude-code, codex, openclaw) read
|
|
3425
3018
|
// this and inject `PRISMER_OUTBOX_DIR=<abs>` into the child env so
|
|
3426
3019
|
// the LLM tool can resolve the path even if it doesn't read the
|
|
@@ -3691,7 +3284,7 @@ async function resolveAssetRefs(refs, cache) {
|
|
|
3691
3284
|
const inlineCandidate = isTextLikeMime(effectiveMime);
|
|
3692
3285
|
if (inlineCandidate) {
|
|
3693
3286
|
try {
|
|
3694
|
-
const buf = (0,
|
|
3287
|
+
const buf = (0, import_node_fs6.readFileSync)(cached.localPath);
|
|
3695
3288
|
let strategy;
|
|
3696
3289
|
let body;
|
|
3697
3290
|
if (buf.byteLength <= ASSET_INLINE_FULL_MAX_BYTES) {
|
|
@@ -3765,36 +3358,6 @@ ${memory.digest.trim()}
|
|
|
3765
3358
|
|
|
3766
3359
|
${prompt}`;
|
|
3767
3360
|
}
|
|
3768
|
-
function resolveOperatingPrinciples(config) {
|
|
3769
|
-
const cfg = readRecord(config);
|
|
3770
|
-
const roleTemplate = readRecord(cfg.roleTemplate);
|
|
3771
|
-
const principles = stringifyPrinciples(cfg.operatingPrinciples) ?? stringifyPrinciples(roleTemplate.operatingPrinciples) ?? DEFAULT_OPERATING_PRINCIPLES;
|
|
3772
|
-
const policy = typeof cfg.approvalPolicy === "string" ? cfg.approvalPolicy : roleTemplate.approvalPolicy;
|
|
3773
|
-
if (policy === "strict") {
|
|
3774
|
-
return `${principles}
|
|
3775
|
-
- Approval policy: strict. Request human approval before destructive, external, spend, access, or publish operations.`;
|
|
3776
|
-
}
|
|
3777
|
-
if (policy === "autonomous") {
|
|
3778
|
-
return `${principles}
|
|
3779
|
-
- Approval policy: autonomous. Do not request human approval unless a platform policy explicitly requires it.`;
|
|
3780
|
-
}
|
|
3781
|
-
return `${principles}
|
|
3782
|
-
- Approval policy: auto-low-risk. Proceed on reversible low-risk work; request human approval for destructive or irreversible operations.`;
|
|
3783
|
-
}
|
|
3784
|
-
function stringifyPrinciples(value) {
|
|
3785
|
-
if (typeof value === "string" && value.trim()) return value.trim();
|
|
3786
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
3787
|
-
const record = value;
|
|
3788
|
-
for (const key of ["en", "zh"]) {
|
|
3789
|
-
const text = record[key];
|
|
3790
|
-
if (typeof text === "string" && text.trim()) return text.trim();
|
|
3791
|
-
}
|
|
3792
|
-
for (const text of Object.values(record)) {
|
|
3793
|
-
if (typeof text === "string" && text.trim()) return text.trim();
|
|
3794
|
-
}
|
|
3795
|
-
}
|
|
3796
|
-
return void 0;
|
|
3797
|
-
}
|
|
3798
3361
|
function appendOutboxInstruction(prompt, outboxDir, profileConfig) {
|
|
3799
3362
|
if (!outboxDir) return prompt;
|
|
3800
3363
|
if (profileConfig?.disableOutboxHints === true) return prompt;
|
|
@@ -3813,9 +3376,6 @@ function appendChannelContext(prompt, payload, profile) {
|
|
|
3813
3376
|
const type = payload.conversationType === "direct" || payload.conversationType === "group" ? payload.conversationType : "unknown";
|
|
3814
3377
|
const youAre = profile.agentUsername || payload.agentImUserId || "this agent";
|
|
3815
3378
|
const lines = ["[Channel context]", `Conversation type: ${type}`, `You are: ${youAre}`];
|
|
3816
|
-
if (payload.conversationId) {
|
|
3817
|
-
lines.push(`Conversation ID: ${payload.conversationId}`);
|
|
3818
|
-
}
|
|
3819
3379
|
const participants = Array.isArray(payload.participants) ? payload.participants : [];
|
|
3820
3380
|
if (participants.length > 0) {
|
|
3821
3381
|
if (type === "direct") {
|
|
@@ -4085,8 +3645,8 @@ var ServicePool = class {
|
|
|
4085
3645
|
|
|
4086
3646
|
// src/daemon/local-server.ts
|
|
4087
3647
|
var import_node_http = require("http");
|
|
4088
|
-
var
|
|
4089
|
-
var
|
|
3648
|
+
var import_node_crypto = require("crypto");
|
|
3649
|
+
var import_node_fs7 = require("fs");
|
|
4090
3650
|
var path2 = __toESM(require("path"), 1);
|
|
4091
3651
|
var LocalServer = class {
|
|
4092
3652
|
constructor(opts) {
|
|
@@ -4239,7 +3799,7 @@ var LocalServer = class {
|
|
|
4239
3799
|
async handleSnapshot(req, res) {
|
|
4240
3800
|
const root = this.opts.snapshotRoot ?? "/workspace";
|
|
4241
3801
|
try {
|
|
4242
|
-
const st = await
|
|
3802
|
+
const st = await import_node_fs7.promises.stat(root);
|
|
4243
3803
|
if (!st.isDirectory()) {
|
|
4244
3804
|
respond(res, 400, { error: "snapshot_root_not_directory", rootPath: root });
|
|
4245
3805
|
return;
|
|
@@ -4315,7 +3875,7 @@ var LocalServer = class {
|
|
|
4315
3875
|
respond(res, 400, { error: "missing_taskId" });
|
|
4316
3876
|
return;
|
|
4317
3877
|
}
|
|
4318
|
-
const runId = (0,
|
|
3878
|
+
const runId = (0, import_node_crypto.randomUUID)();
|
|
4319
3879
|
try {
|
|
4320
3880
|
this.opts.onDispatch?.(payload, runId);
|
|
4321
3881
|
} catch (err) {
|
|
@@ -4370,7 +3930,7 @@ async function walkAndDigest(root, current) {
|
|
|
4370
3930
|
const out = [];
|
|
4371
3931
|
let entries;
|
|
4372
3932
|
try {
|
|
4373
|
-
entries = await
|
|
3933
|
+
entries = await import_node_fs7.promises.readdir(current);
|
|
4374
3934
|
} catch (err) {
|
|
4375
3935
|
if (err.code === "ENOENT") return out;
|
|
4376
3936
|
throw err;
|
|
@@ -4381,7 +3941,7 @@ async function walkAndDigest(root, current) {
|
|
|
4381
3941
|
const rel = path2.relative(root, full);
|
|
4382
3942
|
let st;
|
|
4383
3943
|
try {
|
|
4384
|
-
st = await
|
|
3944
|
+
st = await import_node_fs7.promises.stat(full);
|
|
4385
3945
|
} catch {
|
|
4386
3946
|
continue;
|
|
4387
3947
|
}
|
|
@@ -4392,11 +3952,11 @@ async function walkAndDigest(root, current) {
|
|
|
4392
3952
|
continue;
|
|
4393
3953
|
}
|
|
4394
3954
|
if (!st.isFile()) continue;
|
|
4395
|
-
const buf = await
|
|
4396
|
-
const
|
|
3955
|
+
const buf = await import_node_fs7.promises.readFile(full);
|
|
3956
|
+
const sha2562 = (0, import_node_crypto.createHash)("sha256").update(buf).digest("hex");
|
|
4397
3957
|
out.push({
|
|
4398
3958
|
path: rel,
|
|
4399
|
-
sha256:
|
|
3959
|
+
sha256: sha2562,
|
|
4400
3960
|
sizeBytes: st.size,
|
|
4401
3961
|
mtime: Math.floor(st.mtimeMs)
|
|
4402
3962
|
});
|
|
@@ -4406,14 +3966,12 @@ async function walkAndDigest(root, current) {
|
|
|
4406
3966
|
|
|
4407
3967
|
// src/daemon/runner.ts
|
|
4408
3968
|
var import_node_events3 = require("events");
|
|
4409
|
-
var
|
|
4410
|
-
var
|
|
4411
|
-
var import_node_os6 = require("os");
|
|
4412
|
-
var import_node_path10 = require("path");
|
|
3969
|
+
var import_node_fs11 = require("fs");
|
|
3970
|
+
var import_node_os4 = require("os");
|
|
4413
3971
|
|
|
4414
3972
|
// src/adapters/claude-code/index.ts
|
|
4415
3973
|
var import_node_child_process2 = require("child_process");
|
|
4416
|
-
var
|
|
3974
|
+
var import_zod3 = require("zod");
|
|
4417
3975
|
|
|
4418
3976
|
// src/adapters/claude-code/output-parser.ts
|
|
4419
3977
|
var MAX_OUTPUT_CHARS = 64 * 1024;
|
|
@@ -4445,28 +4003,28 @@ function parseHeadlessOutput(stdout) {
|
|
|
4445
4003
|
}
|
|
4446
4004
|
|
|
4447
4005
|
// src/adapters/claude-code/index.ts
|
|
4448
|
-
var McpServerSchema =
|
|
4449
|
-
name:
|
|
4450
|
-
command:
|
|
4451
|
-
args:
|
|
4452
|
-
env:
|
|
4006
|
+
var McpServerSchema = import_zod3.z.object({
|
|
4007
|
+
name: import_zod3.z.string(),
|
|
4008
|
+
command: import_zod3.z.string(),
|
|
4009
|
+
args: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
4010
|
+
env: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.string()).optional()
|
|
4453
4011
|
});
|
|
4454
|
-
var CCConfigSchema =
|
|
4455
|
-
cwd:
|
|
4012
|
+
var CCConfigSchema = import_zod3.z.object({
|
|
4013
|
+
cwd: import_zod3.z.string().min(1),
|
|
4456
4014
|
// claude 2.x accepts aliases ('sonnet', 'haiku', 'opus') and full ids
|
|
4457
4015
|
// (e.g. 'claude-sonnet-4-6'). The 1.x default 'claude-3-5-sonnet' is no
|
|
4458
4016
|
// longer a valid model id under the current CLI.
|
|
4459
|
-
model:
|
|
4460
|
-
systemPrompt:
|
|
4461
|
-
envVars:
|
|
4462
|
-
mcpServers:
|
|
4463
|
-
allowedTools:
|
|
4464
|
-
maxTurns:
|
|
4017
|
+
model: import_zod3.z.string().default("sonnet"),
|
|
4018
|
+
systemPrompt: import_zod3.z.string().optional(),
|
|
4019
|
+
envVars: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.string()).optional(),
|
|
4020
|
+
mcpServers: import_zod3.z.array(McpServerSchema).optional(),
|
|
4021
|
+
allowedTools: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
4022
|
+
maxTurns: import_zod3.z.number().int().positive().default(20),
|
|
4465
4023
|
// External-model route (1.9.x extension — optional, no breaking change)
|
|
4466
|
-
baseURL:
|
|
4467
|
-
apiKeyRef:
|
|
4024
|
+
baseURL: import_zod3.z.string().url().optional(),
|
|
4025
|
+
apiKeyRef: import_zod3.z.string().regex(/^(env|keychain):[A-Za-z0-9_][A-Za-z0-9_.\-]*$/).optional(),
|
|
4468
4026
|
// informational tag — dispatch does not branch on this value yet
|
|
4469
|
-
route:
|
|
4027
|
+
route: import_zod3.z.enum(["default", "prismer", "omniroute"]).default("default")
|
|
4470
4028
|
});
|
|
4471
4029
|
var claudeCodeAdapter = {
|
|
4472
4030
|
name: "claude-code",
|
|
@@ -4620,7 +4178,7 @@ function resolveKeyRef(ref) {
|
|
|
4620
4178
|
|
|
4621
4179
|
// src/adapters/codex/index.ts
|
|
4622
4180
|
var import_node_child_process3 = require("child_process");
|
|
4623
|
-
var
|
|
4181
|
+
var import_zod4 = require("zod");
|
|
4624
4182
|
|
|
4625
4183
|
// src/skills/memory-curation.ts
|
|
4626
4184
|
var MEMORY_CURATION_SKILL_TEXT = `---
|
|
@@ -4753,31 +4311,31 @@ If the answer is no, do not save.
|
|
|
4753
4311
|
`;
|
|
4754
4312
|
|
|
4755
4313
|
// src/adapters/codex/index.ts
|
|
4756
|
-
var CodexConfigSchema =
|
|
4314
|
+
var CodexConfigSchema = import_zod4.z.object({
|
|
4757
4315
|
/** Working directory for the codex subprocess. */
|
|
4758
|
-
cwd:
|
|
4316
|
+
cwd: import_zod4.z.string().min(1),
|
|
4759
4317
|
/**
|
|
4760
4318
|
* Model identifier passed to --model (e.g. 'codex-mini-latest', 'o4-mini').
|
|
4761
4319
|
* Default matches the Codex CLI built-in default as of 2026-05.
|
|
4762
4320
|
*/
|
|
4763
|
-
model:
|
|
4321
|
+
model: import_zod4.z.string().default("codex-mini-latest"),
|
|
4764
4322
|
/**
|
|
4765
4323
|
* Sandbox level for the codex subprocess.
|
|
4766
4324
|
* read-only — analysis only; code-gen tasks will stall
|
|
4767
4325
|
* workspace-write — write inside cwd, read-only outside (recommended)
|
|
4768
4326
|
* danger-full-access — no restrictions; safe only inside isolated containers
|
|
4769
4327
|
*/
|
|
4770
|
-
sandbox:
|
|
4328
|
+
sandbox: import_zod4.z.enum(["read-only", "workspace-write", "danger-full-access"]).default("workspace-write"),
|
|
4771
4329
|
/** Optional system prompt prepended to the user prompt as a preamble (see U4). */
|
|
4772
|
-
systemPrompt:
|
|
4330
|
+
systemPrompt: import_zod4.z.string().optional(),
|
|
4773
4331
|
/** Extra env vars merged into the codex subprocess environment. */
|
|
4774
|
-
envVars:
|
|
4332
|
+
envVars: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.string()).optional(),
|
|
4775
4333
|
/**
|
|
4776
4334
|
* Name of the env var holding the OpenAI API key. Defaults to
|
|
4777
4335
|
* 'OPENAI_API_KEY' (Codex CLI default). Override if using a workspace-
|
|
4778
4336
|
* scoped key under a different name.
|
|
4779
4337
|
*/
|
|
4780
|
-
apiKeyEnv:
|
|
4338
|
+
apiKeyEnv: import_zod4.z.string().default("OPENAI_API_KEY")
|
|
4781
4339
|
});
|
|
4782
4340
|
function buildCodexPrompt(config, taskPrompt) {
|
|
4783
4341
|
const preambleParts = [MEMORY_CURATION_SKILL_TEXT.trim()];
|
|
@@ -4925,21 +4483,148 @@ var codexAdapter = {
|
|
|
4925
4483
|
}
|
|
4926
4484
|
};
|
|
4927
4485
|
|
|
4928
|
-
// src/
|
|
4929
|
-
var
|
|
4930
|
-
var
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4486
|
+
// src/adapters/openclaw/index.ts
|
|
4487
|
+
var import_zod5 = require("zod");
|
|
4488
|
+
var OpenClawProfileConfigSchema = import_zod5.z.object({
|
|
4489
|
+
/** OpenClaw chat-completions HTTP port. */
|
|
4490
|
+
port: import_zod5.z.number().int().min(1).max(65535).default(18789),
|
|
4491
|
+
/** Bearer token expected by the gateway (from gateway.auth.bearerTokens). */
|
|
4492
|
+
apiKey: import_zod5.z.string().min(1),
|
|
4493
|
+
/**
|
|
4494
|
+
* Model field passed to OpenClaw's /v1/chat/completions. OpenClaw
|
|
4495
|
+
* intercepts this differently from a vanilla OpenAI server: the value
|
|
4496
|
+
* MUST be `openclaw` (or `openclaw/<agentId>`) — it picks which
|
|
4497
|
+
* OpenClaw agent answers, NOT the underlying LLM. The LLM is selected
|
|
4498
|
+
* by `agents.defaults.model.primary` in openclaw.json (e.g.
|
|
4499
|
+
* `prismer/us-kimi-k2.5`, set by the bootstrap).
|
|
4500
|
+
*/
|
|
4501
|
+
model: import_zod5.z.string().min(1).default("openclaw")
|
|
4502
|
+
});
|
|
4503
|
+
var openclawAdapter = {
|
|
4504
|
+
name: "openclaw",
|
|
4505
|
+
kind: "long-running",
|
|
4506
|
+
capabilities: ["chat", "code", "multi-channel"],
|
|
4507
|
+
workspaceSchema: OpenClawProfileConfigSchema,
|
|
4508
|
+
validate(config) {
|
|
4509
|
+
const r = OpenClawProfileConfigSchema.safeParse(config);
|
|
4510
|
+
if (r.success) return { ok: true };
|
|
4511
|
+
return {
|
|
4512
|
+
ok: false,
|
|
4513
|
+
errors: r.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
4514
|
+
};
|
|
4515
|
+
},
|
|
4516
|
+
async ensureService(profile) {
|
|
4517
|
+
const config = OpenClawProfileConfigSchema.parse(profile.config);
|
|
4518
|
+
const baseUrl = `http://127.0.0.1:${config.port}`;
|
|
4519
|
+
if (!await checkHealth2(baseUrl, config.apiKey)) {
|
|
4520
|
+
throw new Error(
|
|
4521
|
+
`OpenClaw gateway not reachable at ${baseUrl}. Start it with:
|
|
4522
|
+
openclaw gateway --port ${config.port}`
|
|
4523
|
+
);
|
|
4524
|
+
}
|
|
4525
|
+
return new OpenClawService(baseUrl, config.apiKey, config.model);
|
|
4526
|
+
},
|
|
4527
|
+
async health() {
|
|
4528
|
+
return { available: true };
|
|
4529
|
+
}
|
|
4530
|
+
};
|
|
4531
|
+
var OpenClawService = class {
|
|
4532
|
+
constructor(baseUrl, apiKey, model) {
|
|
4533
|
+
this.baseUrl = baseUrl;
|
|
4534
|
+
this.apiKey = apiKey;
|
|
4535
|
+
this.model = model;
|
|
4536
|
+
this.id = baseUrl;
|
|
4537
|
+
}
|
|
4538
|
+
baseUrl;
|
|
4539
|
+
apiKey;
|
|
4540
|
+
model;
|
|
4541
|
+
id;
|
|
4542
|
+
async healthy() {
|
|
4543
|
+
return checkHealth2(this.baseUrl, this.apiKey);
|
|
4544
|
+
}
|
|
4545
|
+
async dispatch(task) {
|
|
4546
|
+
const startedAt = Date.now();
|
|
4547
|
+
const systemPrompt = typeof task.metadata?.systemPrompt === "string" ? task.metadata.systemPrompt : void 0;
|
|
4548
|
+
const messages = [];
|
|
4549
|
+
if (systemPrompt) messages.push({ role: "system", content: systemPrompt });
|
|
4550
|
+
messages.push({ role: "user", content: task.prompt });
|
|
4551
|
+
try {
|
|
4552
|
+
const res = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
4553
|
+
method: "POST",
|
|
4554
|
+
headers: {
|
|
4555
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
4556
|
+
"Content-Type": "application/json"
|
|
4557
|
+
},
|
|
4558
|
+
body: JSON.stringify({
|
|
4559
|
+
model: this.model,
|
|
4560
|
+
messages,
|
|
4561
|
+
stream: false
|
|
4562
|
+
}),
|
|
4563
|
+
signal: task.signal
|
|
4564
|
+
});
|
|
4565
|
+
if (!res.ok) {
|
|
4566
|
+
const body = await res.text().catch(() => "") ?? "";
|
|
4567
|
+
return {
|
|
4568
|
+
ok: false,
|
|
4569
|
+
error: {
|
|
4570
|
+
code: res.status === 401 || res.status === 403 ? "auth_invalid" : "adapter_dispatch_failed",
|
|
4571
|
+
message: `OpenClaw ${res.status}: ${body.slice(0, 400)}`
|
|
4572
|
+
}
|
|
4573
|
+
};
|
|
4574
|
+
}
|
|
4575
|
+
const json = await res.json();
|
|
4576
|
+
let output = json.choices?.[0]?.message?.content ?? "";
|
|
4577
|
+
const ERR_PREFIX_RE = /^Cannot read properties of undefined \(reading '[^']+'\)\s*\n+/;
|
|
4578
|
+
if (ERR_PREFIX_RE.test(output)) {
|
|
4579
|
+
process.stderr.write(
|
|
4580
|
+
`[openclaw-adapter] stripping known stderr-bleed prefix from chat output (OpenClaw 2026.4.x bug)
|
|
4581
|
+
`
|
|
4582
|
+
);
|
|
4583
|
+
output = output.replace(ERR_PREFIX_RE, "");
|
|
4584
|
+
}
|
|
4585
|
+
return {
|
|
4586
|
+
ok: true,
|
|
4587
|
+
output,
|
|
4588
|
+
metrics: { durationMs: Date.now() - startedAt }
|
|
4589
|
+
};
|
|
4590
|
+
} catch (err) {
|
|
4591
|
+
if (task.signal?.aborted || err?.name === "AbortError") {
|
|
4592
|
+
return { ok: false, error: { code: "task_cancelled", message: "Task cancelled by client" } };
|
|
4593
|
+
}
|
|
4594
|
+
return {
|
|
4595
|
+
ok: false,
|
|
4596
|
+
error: { code: "adapter_dispatch_failed", message: err.message }
|
|
4597
|
+
};
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
};
|
|
4601
|
+
async function checkHealth2(baseUrl, apiKey) {
|
|
4602
|
+
try {
|
|
4603
|
+
const res = await fetch(`${baseUrl}/health`, {
|
|
4604
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
4605
|
+
signal: AbortSignal.timeout(2e3)
|
|
4606
|
+
});
|
|
4607
|
+
return res.ok;
|
|
4608
|
+
} catch {
|
|
4609
|
+
return false;
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
|
|
4613
|
+
// src/daemon/memory/runtime.ts
|
|
4614
|
+
var os = __toESM(require("os"), 1);
|
|
4615
|
+
var path4 = __toESM(require("path"), 1);
|
|
4616
|
+
|
|
4617
|
+
// src/daemon/memory/store.ts
|
|
4618
|
+
var import_better_sqlite33 = __toESM(require("better-sqlite3"), 1);
|
|
4619
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
4620
|
+
var path3 = __toESM(require("path"), 1);
|
|
4621
|
+
var import_node_crypto2 = require("crypto");
|
|
4622
|
+
|
|
4623
|
+
// src/daemon/memory/crypto.ts
|
|
4624
|
+
function sealPlaintext(content) {
|
|
4625
|
+
return { kind: "inline", content };
|
|
4626
|
+
}
|
|
4627
|
+
|
|
4943
4628
|
// src/daemon/memory/store.ts
|
|
4944
4629
|
var SCHEMA_VERSION2 = 1;
|
|
4945
4630
|
var SCHEMA_V1_STATEMENTS = [
|
|
@@ -5084,9 +4769,9 @@ var MemoryStore = class {
|
|
|
5084
4769
|
const row = db.prepare("SELECT * FROM memory_pages WHERE workspaceId = ? AND id = ?").get(this.opts.workspaceId, pageId);
|
|
5085
4770
|
return row ? this.rowToPage(row) : null;
|
|
5086
4771
|
}
|
|
5087
|
-
loadContent(pageId,
|
|
4772
|
+
loadContent(pageId, version2) {
|
|
5088
4773
|
const db = this.requireDb();
|
|
5089
|
-
const targetVersion =
|
|
4774
|
+
const targetVersion = version2 ?? db.prepare("SELECT version FROM memory_pages WHERE workspaceId = ? AND id = ?").get(this.opts.workspaceId, pageId)?.version;
|
|
5090
4775
|
if (targetVersion === void 0) return null;
|
|
5091
4776
|
const row = db.prepare(
|
|
5092
4777
|
`SELECT c.pageId, c.version, c.payloadKind, c.payloadValue
|
|
@@ -5124,7 +4809,7 @@ var MemoryStore = class {
|
|
|
5124
4809
|
}
|
|
5125
4810
|
const db = this.requireDb();
|
|
5126
4811
|
const now = Date.now();
|
|
5127
|
-
const contentHash =
|
|
4812
|
+
const contentHash = sha256(input.content);
|
|
5128
4813
|
const payload = sealPlaintext(input.content);
|
|
5129
4814
|
if (payload.kind !== "inline") {
|
|
5130
4815
|
throw new Error("MemoryStore.write: non-inline payload not yet supported in phase-0");
|
|
@@ -5135,7 +4820,7 @@ var MemoryStore = class {
|
|
|
5135
4820
|
const existing = db.prepare(
|
|
5136
4821
|
"SELECT id, version, createdAt FROM memory_pages WHERE workspaceId = ? AND path = ?"
|
|
5137
4822
|
).get(this.opts.workspaceId, input.path);
|
|
5138
|
-
const pageId = existing?.id ?? `page_${(0,
|
|
4823
|
+
const pageId = existing?.id ?? `page_${(0, import_node_crypto2.randomUUID)().replace(/-/g, "").slice(0, 22)}`;
|
|
5139
4824
|
const newVersion = (existing?.version ?? 0) + 1;
|
|
5140
4825
|
const createdAt = existing?.createdAt ?? now;
|
|
5141
4826
|
const staleFlag = input.stale ? 1 : 0;
|
|
@@ -5318,8 +5003,8 @@ var MemoryStore = class {
|
|
|
5318
5003
|
};
|
|
5319
5004
|
}
|
|
5320
5005
|
};
|
|
5321
|
-
function
|
|
5322
|
-
return (0,
|
|
5006
|
+
function sha256(s) {
|
|
5007
|
+
return (0, import_node_crypto2.createHash)("sha256").update(s, "utf8").digest("hex");
|
|
5323
5008
|
}
|
|
5324
5009
|
|
|
5325
5010
|
// src/daemon/memory/search.ts
|
|
@@ -5425,7 +5110,7 @@ function clamp(n, lo, hi) {
|
|
|
5425
5110
|
}
|
|
5426
5111
|
|
|
5427
5112
|
// src/daemon/memory/outbox.ts
|
|
5428
|
-
var
|
|
5113
|
+
var import_node_crypto3 = require("crypto");
|
|
5429
5114
|
|
|
5430
5115
|
// src/daemon/memory/envelope.ts
|
|
5431
5116
|
var import_zod6 = require("zod");
|
|
@@ -5558,7 +5243,7 @@ var MemoryOutbox = class {
|
|
|
5558
5243
|
const now = Date.now();
|
|
5559
5244
|
const parsed = MemoryOutboxEnvelope.safeParse(event);
|
|
5560
5245
|
if (!parsed.success) {
|
|
5561
|
-
const dlId = `dl_${(0,
|
|
5246
|
+
const dlId = `dl_${(0, import_node_crypto3.randomUUID)()}`;
|
|
5562
5247
|
db.prepare(
|
|
5563
5248
|
`INSERT INTO memory_outbox_dead_letter (id, eventType, rawJson, errorJson, createdAt)
|
|
5564
5249
|
VALUES (?, ?, ?, ?, ?)`
|
|
@@ -5575,7 +5260,7 @@ var MemoryOutbox = class {
|
|
|
5575
5260
|
if (validated.eventType === "memory.feedback") {
|
|
5576
5261
|
const check = validateFeedbackTarget(validated);
|
|
5577
5262
|
if (!check.ok) {
|
|
5578
|
-
const dlId = `dl_${(0,
|
|
5263
|
+
const dlId = `dl_${(0, import_node_crypto3.randomUUID)()}`;
|
|
5579
5264
|
db.prepare(
|
|
5580
5265
|
`INSERT INTO memory_outbox_dead_letter (id, eventType, rawJson, errorJson, createdAt)
|
|
5581
5266
|
VALUES (?, ?, ?, ?, ?)`
|
|
@@ -5589,7 +5274,7 @@ var MemoryOutbox = class {
|
|
|
5589
5274
|
return { id: dlId, deadLetter: true };
|
|
5590
5275
|
}
|
|
5591
5276
|
}
|
|
5592
|
-
const rowId = `out_${(0,
|
|
5277
|
+
const rowId = `out_${(0, import_node_crypto3.randomUUID)()}`;
|
|
5593
5278
|
try {
|
|
5594
5279
|
db.prepare(
|
|
5595
5280
|
`INSERT INTO memory_outbox (id, eventType, envelopeJson, idempotencyKey, status, createdAt)
|
|
@@ -6169,7 +5854,7 @@ function clamp2(n, lo, hi) {
|
|
|
6169
5854
|
}
|
|
6170
5855
|
|
|
6171
5856
|
// src/daemon/memory/fork/tracing.ts
|
|
6172
|
-
var
|
|
5857
|
+
var import_node_crypto4 = require("crypto");
|
|
6173
5858
|
|
|
6174
5859
|
// src/daemon/memory/fork/runner.ts
|
|
6175
5860
|
var DEFAULT_SNIPPET_MAX_BYTES = 500;
|
|
@@ -6177,8 +5862,8 @@ function finalizeSelected(runtime, workspaceId, paths, snippetMaxBytes = DEFAULT
|
|
|
6177
5862
|
const slot = runtime.peek(workspaceId);
|
|
6178
5863
|
if (!slot) return [];
|
|
6179
5864
|
const out = [];
|
|
6180
|
-
for (const
|
|
6181
|
-
const page = slot.store.loadByPath(
|
|
5865
|
+
for (const path7 of paths) {
|
|
5866
|
+
const page = slot.store.loadByPath(path7);
|
|
6182
5867
|
if (!page) continue;
|
|
6183
5868
|
const content = slot.store.loadContent(page.id)?.content ?? "";
|
|
6184
5869
|
out.push({
|
|
@@ -6453,14 +6138,14 @@ function parsePrismerUri(uri) {
|
|
|
6453
6138
|
const memoryIdx = rest.indexOf(segMemory);
|
|
6454
6139
|
if (memoryIdx <= 0) return null;
|
|
6455
6140
|
const workspaceId = rest.slice(0, memoryIdx);
|
|
6456
|
-
const
|
|
6457
|
-
if (!workspaceId || !
|
|
6458
|
-
return { workspaceId, path:
|
|
6141
|
+
const path7 = rest.slice(memoryIdx + segMemory.length);
|
|
6142
|
+
if (!workspaceId || !path7) return null;
|
|
6143
|
+
return { workspaceId, path: path7 };
|
|
6459
6144
|
}
|
|
6460
6145
|
|
|
6461
6146
|
// src/daemon/asset/metadata-index.ts
|
|
6462
|
-
var
|
|
6463
|
-
var
|
|
6147
|
+
var import_node_fs8 = require("fs");
|
|
6148
|
+
var import_node_path6 = require("path");
|
|
6464
6149
|
var DEFAULT_LIMIT = 8;
|
|
6465
6150
|
var PULL_PAGE_SIZE = 500;
|
|
6466
6151
|
var THROTTLE_MS = 3e4;
|
|
@@ -6488,16 +6173,16 @@ var AssetMetadataIndex = class {
|
|
|
6488
6173
|
this.db = opts.db;
|
|
6489
6174
|
this.cloud = opts.cloud;
|
|
6490
6175
|
this.workspaceId = opts.workspaceId;
|
|
6491
|
-
if (!(0,
|
|
6492
|
-
(0,
|
|
6176
|
+
if (!(0, import_node_fs8.existsSync)(opts.workspaceStateDir)) {
|
|
6177
|
+
(0, import_node_fs8.mkdirSync)(opts.workspaceStateDir, { recursive: true });
|
|
6493
6178
|
}
|
|
6494
|
-
this.cursorPath = (0,
|
|
6179
|
+
this.cursorPath = (0, import_node_path6.join)(opts.workspaceStateDir, "asset-metadata-cursor.json");
|
|
6495
6180
|
}
|
|
6496
6181
|
/** Persisted cursor for this workspace, or 0 on first run / corrupted file. */
|
|
6497
6182
|
readCursor() {
|
|
6498
|
-
if (!(0,
|
|
6183
|
+
if (!(0, import_node_fs8.existsSync)(this.cursorPath)) return 0;
|
|
6499
6184
|
try {
|
|
6500
|
-
const parsed = JSON.parse((0,
|
|
6185
|
+
const parsed = JSON.parse((0, import_node_fs8.readFileSync)(this.cursorPath, "utf8"));
|
|
6501
6186
|
if (parsed.workspaceId !== this.workspaceId) return 0;
|
|
6502
6187
|
return parsed.cursor;
|
|
6503
6188
|
} catch {
|
|
@@ -6510,7 +6195,7 @@ var AssetMetadataIndex = class {
|
|
|
6510
6195
|
cursor,
|
|
6511
6196
|
writtenAt: Date.now()
|
|
6512
6197
|
};
|
|
6513
|
-
(0,
|
|
6198
|
+
(0, import_node_fs8.writeFileSync)(this.cursorPath, JSON.stringify(payload, null, 2));
|
|
6514
6199
|
}
|
|
6515
6200
|
/**
|
|
6516
6201
|
* Pull incremental asset metadata changes since the persisted cursor and
|
|
@@ -6550,13 +6235,9 @@ var AssetMetadataIndex = class {
|
|
|
6550
6235
|
let applied = 0;
|
|
6551
6236
|
const tx = this.db.transaction((items) => {
|
|
6552
6237
|
for (const item of items) {
|
|
6553
|
-
const assetId = item.assetId ?? item.id;
|
|
6554
|
-
if (!assetId) {
|
|
6555
|
-
throw new Error("Asset metadata index item missing assetId/id");
|
|
6556
|
-
}
|
|
6557
6238
|
upsert.run(
|
|
6558
6239
|
this.workspaceId,
|
|
6559
|
-
assetId,
|
|
6240
|
+
item.assetId,
|
|
6560
6241
|
item.contentHash,
|
|
6561
6242
|
item.filename ?? null,
|
|
6562
6243
|
item.folderPath ?? null,
|
|
@@ -6601,14 +6282,6 @@ var AssetMetadataIndex = class {
|
|
|
6601
6282
|
const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND filename = ?").get(this.workspaceId, filename);
|
|
6602
6283
|
return row ? rowToMetadata(row) : void 0;
|
|
6603
6284
|
}
|
|
6604
|
-
resolveByAssetId(assetId) {
|
|
6605
|
-
const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND asset_id = ?").get(this.workspaceId, assetId);
|
|
6606
|
-
return row ? rowToMetadata(row) : void 0;
|
|
6607
|
-
}
|
|
6608
|
-
resolveByContentHash(contentHash) {
|
|
6609
|
-
const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND content_hash = ?").get(this.workspaceId, contentHash);
|
|
6610
|
-
return row ? rowToMetadata(row) : void 0;
|
|
6611
|
-
}
|
|
6612
6285
|
/** Batch exact match — returns Map for O(1) access. */
|
|
6613
6286
|
resolveByFilenames(filenames) {
|
|
6614
6287
|
if (filenames.length === 0) return /* @__PURE__ */ new Map();
|
|
@@ -6627,10 +6300,7 @@ var AssetMetadataIndex = class {
|
|
|
6627
6300
|
};
|
|
6628
6301
|
|
|
6629
6302
|
// src/daemon/asset/rpc.ts
|
|
6630
|
-
var import_node_fs10 = require("fs");
|
|
6631
6303
|
var ASSET_PATH_PREFIX = "/local/asset/";
|
|
6632
|
-
var MAX_READ_BYTES = 256 * 1024;
|
|
6633
|
-
var MAX_SEARCH_BYTES = 1024 * 1024;
|
|
6634
6304
|
function attachAssetRpc(opts) {
|
|
6635
6305
|
return async (req, res) => {
|
|
6636
6306
|
const url = req.url ?? "/";
|
|
@@ -6641,17 +6311,10 @@ function attachAssetRpc(opts) {
|
|
|
6641
6311
|
try {
|
|
6642
6312
|
if (method === "POST" && subpath === "search") {
|
|
6643
6313
|
const body = await readJson3(req);
|
|
6644
|
-
return
|
|
6645
|
-
}
|
|
6646
|
-
if (method === "POST" && subpath === "describe") {
|
|
6647
|
-
const body = await readJson3(req);
|
|
6648
|
-
return handleDescribe(opts, body, res);
|
|
6314
|
+
return handleSearch2(opts.resolveIndex, body, res);
|
|
6649
6315
|
}
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
return await handleRead(opts, body, res);
|
|
6653
|
-
}
|
|
6654
|
-
return false;
|
|
6316
|
+
respond3(res, 404, { error: "asset_route_not_found", path: url });
|
|
6317
|
+
return true;
|
|
6655
6318
|
} catch (err) {
|
|
6656
6319
|
respond3(res, 500, {
|
|
6657
6320
|
error: "asset_rpc_failed",
|
|
@@ -6661,21 +6324,18 @@ function attachAssetRpc(opts) {
|
|
|
6661
6324
|
}
|
|
6662
6325
|
};
|
|
6663
6326
|
}
|
|
6664
|
-
|
|
6327
|
+
function handleSearch2(resolveIndex, body, res) {
|
|
6665
6328
|
if (!body || typeof body !== "object") {
|
|
6666
6329
|
return respond4002(res, "request body must be a JSON object");
|
|
6667
6330
|
}
|
|
6668
6331
|
const b = body;
|
|
6669
|
-
if (typeof b.query !== "string" || !b.query.trim()) {
|
|
6670
|
-
return respond4002(res, "query is required (non-empty string)");
|
|
6671
|
-
}
|
|
6672
|
-
if (typeof b.uri === "string" && b.uri.trim()) {
|
|
6673
|
-
return searchAssetContent(opts, b.uri.trim(), b.query.trim(), res);
|
|
6674
|
-
}
|
|
6675
6332
|
if (typeof b.workspaceId !== "string" || !b.workspaceId) {
|
|
6676
6333
|
return respond4002(res, "workspaceId is required (string)");
|
|
6677
6334
|
}
|
|
6678
|
-
|
|
6335
|
+
if (typeof b.query !== "string" || !b.query.trim()) {
|
|
6336
|
+
return respond4002(res, "query is required (non-empty string)");
|
|
6337
|
+
}
|
|
6338
|
+
const index = resolveIndex(b.workspaceId);
|
|
6679
6339
|
if (!index) {
|
|
6680
6340
|
respond3(res, 404, {
|
|
6681
6341
|
error: "workspace_index_not_found",
|
|
@@ -6689,124 +6349,6 @@ async function handleSearch2(opts, body, res) {
|
|
|
6689
6349
|
respond3(res, 200, { items });
|
|
6690
6350
|
return true;
|
|
6691
6351
|
}
|
|
6692
|
-
function handleDescribe(opts, body, res) {
|
|
6693
|
-
const resolved = resolveAssetRef(opts, body);
|
|
6694
|
-
if ("error" in resolved) return respond4002(res, resolved.error);
|
|
6695
|
-
const cached = opts.assetCache?.get(resolved.row.contentHash);
|
|
6696
|
-
respond3(res, 200, {
|
|
6697
|
-
asset: resolved.row,
|
|
6698
|
-
uri: `prismer://workspace/${resolved.workspaceId}/asset/${resolved.row.contentHash}`,
|
|
6699
|
-
cache: cached ? { status: "hit", localPath: cached.localPath, sizeBytes: cached.sizeBytes, lastUsedAt: cached.lastUsedAt } : { status: "miss" },
|
|
6700
|
-
tools: ["asset.describe", "asset.read", "asset.search"]
|
|
6701
|
-
});
|
|
6702
|
-
return true;
|
|
6703
|
-
}
|
|
6704
|
-
async function handleRead(opts, body, res) {
|
|
6705
|
-
const resolved = resolveAssetRef(opts, body);
|
|
6706
|
-
if ("error" in resolved) return respond4002(res, resolved.error);
|
|
6707
|
-
if (!opts.assetCache) {
|
|
6708
|
-
respond3(res, 503, { error: "asset_cache_unavailable" });
|
|
6709
|
-
return true;
|
|
6710
|
-
}
|
|
6711
|
-
const b = body;
|
|
6712
|
-
const offset = typeof b.offset === "number" && b.offset > 0 ? Math.floor(b.offset) : 0;
|
|
6713
|
-
const requested = typeof b.length === "number" && b.length > 0 ? Math.floor(b.length) : MAX_READ_BYTES;
|
|
6714
|
-
const length = Math.min(requested, MAX_READ_BYTES);
|
|
6715
|
-
const cached = await opts.assetCache.getOrFetch(resolved.row.contentHash, {
|
|
6716
|
-
workspaceIdHint: resolved.workspaceId,
|
|
6717
|
-
assetId: resolved.row.assetId
|
|
6718
|
-
});
|
|
6719
|
-
const chunk = readFileRange(cached.localPath, offset, length);
|
|
6720
|
-
const textLike = isTextLike(resolved.row);
|
|
6721
|
-
respond3(res, 200, {
|
|
6722
|
-
asset: resolved.row,
|
|
6723
|
-
locator: { byteOffset: offset, byteLength: chunk.bytesRead },
|
|
6724
|
-
truncated: requested > MAX_READ_BYTES || offset + chunk.bytesRead < chunk.sizeBytes,
|
|
6725
|
-
encoding: textLike ? "utf8" : "base64",
|
|
6726
|
-
content: textLike ? chunk.buffer.toString("utf8") : chunk.buffer.toString("base64")
|
|
6727
|
-
});
|
|
6728
|
-
return true;
|
|
6729
|
-
}
|
|
6730
|
-
async function searchAssetContent(opts, uri, query, res) {
|
|
6731
|
-
const resolved = resolveAssetRef(opts, { uri });
|
|
6732
|
-
if ("error" in resolved) return respond4002(res, resolved.error);
|
|
6733
|
-
if (!opts.assetCache) {
|
|
6734
|
-
respond3(res, 503, { error: "asset_cache_unavailable" });
|
|
6735
|
-
return true;
|
|
6736
|
-
}
|
|
6737
|
-
if (!isTextLike(resolved.row)) {
|
|
6738
|
-
respond3(res, 200, {
|
|
6739
|
-
items: [],
|
|
6740
|
-
unsupported: true,
|
|
6741
|
-
reason: "content search currently supports text-like assets only",
|
|
6742
|
-
asset: resolved.row
|
|
6743
|
-
});
|
|
6744
|
-
return true;
|
|
6745
|
-
}
|
|
6746
|
-
const cached = await opts.assetCache.getOrFetch(resolved.row.contentHash, {
|
|
6747
|
-
workspaceIdHint: resolved.workspaceId,
|
|
6748
|
-
assetId: resolved.row.assetId
|
|
6749
|
-
});
|
|
6750
|
-
const chunk = readFileRange(cached.localPath, 0, MAX_SEARCH_BYTES);
|
|
6751
|
-
const text = chunk.buffer.toString("utf8");
|
|
6752
|
-
const needle = query.toLowerCase();
|
|
6753
|
-
const lines = text.split(/\r?\n/);
|
|
6754
|
-
const items = [];
|
|
6755
|
-
let byteOffset = 0;
|
|
6756
|
-
for (let i = 0; i < lines.length && items.length < 20; i += 1) {
|
|
6757
|
-
const line = lines[i] ?? "";
|
|
6758
|
-
if (line.toLowerCase().includes(needle)) {
|
|
6759
|
-
items.push({ line: i + 1, byteOffset, preview: line.slice(0, 500) });
|
|
6760
|
-
}
|
|
6761
|
-
byteOffset += Buffer.byteLength(line) + 1;
|
|
6762
|
-
}
|
|
6763
|
-
respond3(res, 200, {
|
|
6764
|
-
items,
|
|
6765
|
-
asset: resolved.row,
|
|
6766
|
-
searched: { byteOffset: 0, byteLength: chunk.bytesRead, truncated: chunk.bytesRead < chunk.sizeBytes }
|
|
6767
|
-
});
|
|
6768
|
-
return true;
|
|
6769
|
-
}
|
|
6770
|
-
function resolveAssetRef(opts, body) {
|
|
6771
|
-
if (!body || typeof body !== "object") return { error: "request body must be a JSON object" };
|
|
6772
|
-
const b = body;
|
|
6773
|
-
let workspaceId = typeof b.workspaceId === "string" ? b.workspaceId : "";
|
|
6774
|
-
let assetId = typeof b.assetId === "string" ? b.assetId : "";
|
|
6775
|
-
let contentHash = typeof b.contentHash === "string" ? b.contentHash : "";
|
|
6776
|
-
if (typeof b.uri === "string" && b.uri.trim()) {
|
|
6777
|
-
const [uri] = parseUris(b.uri.trim());
|
|
6778
|
-
if (!uri) return { error: "uri must be prismer://workspace/<workspaceId>/asset/<hash>" };
|
|
6779
|
-
if (uri.type !== "asset") return { error: "file uri lookup is not available in this RPC yet; pass asset uri or assetId" };
|
|
6780
|
-
workspaceId = uri.workspaceId ?? workspaceId;
|
|
6781
|
-
contentHash = uri.rest;
|
|
6782
|
-
}
|
|
6783
|
-
if (!workspaceId) return { error: "workspaceId is required (string)" };
|
|
6784
|
-
const index = opts.resolveIndex(workspaceId);
|
|
6785
|
-
if (!index) return { error: "workspace asset index is not synced yet" };
|
|
6786
|
-
const row = assetId ? index.resolveByAssetId(assetId) : contentHash ? index.resolveByContentHash(contentHash) : void 0;
|
|
6787
|
-
if (!row) return { error: "asset not found in local metadata index" };
|
|
6788
|
-
return { workspaceId, row };
|
|
6789
|
-
}
|
|
6790
|
-
function isTextLike(row) {
|
|
6791
|
-
const mime = row.mime ?? "";
|
|
6792
|
-
const name = row.filename ?? "";
|
|
6793
|
-
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(
|
|
6794
|
-
name
|
|
6795
|
-
);
|
|
6796
|
-
}
|
|
6797
|
-
function readFileRange(path9, offset, length) {
|
|
6798
|
-
const sizeBytes = (0, import_node_fs10.statSync)(path9).size;
|
|
6799
|
-
if (offset >= sizeBytes) return { buffer: Buffer.alloc(0), bytesRead: 0, sizeBytes };
|
|
6800
|
-
const fd = (0, import_node_fs10.openSync)(path9, "r");
|
|
6801
|
-
try {
|
|
6802
|
-
const target = Math.min(length, sizeBytes - offset);
|
|
6803
|
-
const buffer = Buffer.alloc(target);
|
|
6804
|
-
const bytesRead = (0, import_node_fs10.readSync)(fd, buffer, 0, target, offset);
|
|
6805
|
-
return { buffer: buffer.subarray(0, bytesRead), bytesRead, sizeBytes };
|
|
6806
|
-
} finally {
|
|
6807
|
-
(0, import_node_fs10.closeSync)(fd);
|
|
6808
|
-
}
|
|
6809
|
-
}
|
|
6810
6352
|
function respond3(res, status, body) {
|
|
6811
6353
|
res.statusCode = status;
|
|
6812
6354
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -6828,532 +6370,9 @@ async function readJson3(req) {
|
|
|
6828
6370
|
}
|
|
6829
6371
|
}
|
|
6830
6372
|
|
|
6831
|
-
// src/daemon/asset/origin/drop-folder.ts
|
|
6832
|
-
var import_node_fs11 = require("fs");
|
|
6833
|
-
var import_node_os5 = require("os");
|
|
6834
|
-
var path5 = __toESM(require("path"), 1);
|
|
6835
|
-
var DropFolderAdapter = class {
|
|
6836
|
-
kind = "drop-folder";
|
|
6837
|
-
workspaceDir;
|
|
6838
|
-
rootDir;
|
|
6839
|
-
constructor(opts = {}) {
|
|
6840
|
-
this.workspaceDir = opts.workspaceDir;
|
|
6841
|
-
this.rootDir = opts.rootDir ?? path5.join(opts.homeDir ?? (0, import_node_os5.homedir)(), ".prismer");
|
|
6842
|
-
}
|
|
6843
|
-
/**
|
|
6844
|
-
* Long-lived async iterable wrapping `scanOnce` on a 1s polling interval.
|
|
6845
|
-
* Stops when the iterator is broken (e.g. consumer returns / throws).
|
|
6846
|
-
*/
|
|
6847
|
-
async *observe(workspaceId) {
|
|
6848
|
-
while (true) {
|
|
6849
|
-
const batch = await this.scanOnce(workspaceId);
|
|
6850
|
-
for (const obs of batch) yield obs;
|
|
6851
|
-
await sleep(1e3);
|
|
6852
|
-
}
|
|
6853
|
-
}
|
|
6854
|
-
/**
|
|
6855
|
-
* Single-tick scan exposed for tests and one-shot CLI usage. Reads every
|
|
6856
|
-
* file currently under the workspace's drop directory, returning one
|
|
6857
|
-
* observation per file. The caller is expected to feed each into the
|
|
6858
|
-
* outbox; idempotency on the outbox side collapses re-observations of
|
|
6859
|
-
* unchanged files.
|
|
6860
|
-
*/
|
|
6861
|
-
async scanOnce(workspaceId) {
|
|
6862
|
-
const drop = this.dropDir(workspaceId);
|
|
6863
|
-
let entries = [];
|
|
6864
|
-
try {
|
|
6865
|
-
entries = await import_node_fs11.promises.readdir(drop);
|
|
6866
|
-
} catch (err) {
|
|
6867
|
-
if (err.code === "ENOENT") return [];
|
|
6868
|
-
throw err;
|
|
6869
|
-
}
|
|
6870
|
-
const out = [];
|
|
6871
|
-
for (const name of entries) {
|
|
6872
|
-
if (name.startsWith(".")) continue;
|
|
6873
|
-
const fullPath = path5.join(drop, name);
|
|
6874
|
-
const stat = await import_node_fs11.promises.lstat(fullPath).catch(() => null);
|
|
6875
|
-
if (!stat) continue;
|
|
6876
|
-
if (stat.isSymbolicLink()) continue;
|
|
6877
|
-
if (stat.isDirectory()) {
|
|
6878
|
-
for (const nested of await walk(fullPath)) {
|
|
6879
|
-
const nstat = await import_node_fs11.promises.lstat(nested).catch(() => null);
|
|
6880
|
-
if (!nstat || !nstat.isFile()) continue;
|
|
6881
|
-
out.push(makeObservation(workspaceId, nested, drop, nstat.size, Math.floor(nstat.mtimeMs)));
|
|
6882
|
-
}
|
|
6883
|
-
continue;
|
|
6884
|
-
}
|
|
6885
|
-
if (!stat.isFile()) continue;
|
|
6886
|
-
out.push(makeObservation(workspaceId, fullPath, drop, stat.size, Math.floor(stat.mtimeMs)));
|
|
6887
|
-
}
|
|
6888
|
-
return out;
|
|
6889
|
-
}
|
|
6890
|
-
async identifySource(obs) {
|
|
6891
|
-
const detail = obs.detail;
|
|
6892
|
-
const rel = path5.relative(detail.watchDir, detail.path);
|
|
6893
|
-
const folderDir = path5.dirname(rel);
|
|
6894
|
-
return {
|
|
6895
|
-
sourceRef: `folder://${obs.workspaceId}/${rel.split(path5.sep).join("/")}`,
|
|
6896
|
-
hints: {
|
|
6897
|
-
filename: path5.basename(detail.path),
|
|
6898
|
-
folderPath: folderDir === "." ? "" : folderDir.split(path5.sep).join("/")
|
|
6899
|
-
}
|
|
6900
|
-
};
|
|
6901
|
-
}
|
|
6902
|
-
async fetch(obs) {
|
|
6903
|
-
const detail = obs.detail;
|
|
6904
|
-
const bytes = await import_node_fs11.promises.readFile(detail.path);
|
|
6905
|
-
return {
|
|
6906
|
-
bytes,
|
|
6907
|
-
mime: mimeFromExtension(detail.path),
|
|
6908
|
-
size: bytes.length
|
|
6909
|
-
};
|
|
6910
|
-
}
|
|
6911
|
-
/** Hook called by UploadRunner after a successful upload of one observation. */
|
|
6912
|
-
async onUploadSuccess(obs) {
|
|
6913
|
-
await this.moveTo(obs, this.uploadedDir(obs.workspaceId));
|
|
6914
|
-
}
|
|
6915
|
-
/** Hook called by UploadRunner after attempts hit maxAttempts. */
|
|
6916
|
-
async onUploadFailure(obs) {
|
|
6917
|
-
await this.moveTo(obs, this.failedDir(obs.workspaceId));
|
|
6918
|
-
}
|
|
6919
|
-
async moveTo(obs, destDir) {
|
|
6920
|
-
const detail = obs.detail;
|
|
6921
|
-
if (!detail || typeof detail.path !== "string") return;
|
|
6922
|
-
try {
|
|
6923
|
-
const rel = typeof detail.watchDir === "string" ? path5.relative(detail.watchDir, detail.path) : path5.basename(detail.path);
|
|
6924
|
-
const safeRel = rel && !rel.startsWith("..") && !path5.isAbsolute(rel) ? rel : path5.basename(detail.path);
|
|
6925
|
-
const destPath = path5.join(destDir, safeRel);
|
|
6926
|
-
await import_node_fs11.promises.mkdir(path5.dirname(destPath), { recursive: true });
|
|
6927
|
-
await import_node_fs11.promises.rename(detail.path, destPath);
|
|
6928
|
-
} catch (err) {
|
|
6929
|
-
if (err.code === "ENOENT") return;
|
|
6930
|
-
throw err;
|
|
6931
|
-
}
|
|
6932
|
-
}
|
|
6933
|
-
dropDir(workspaceId) {
|
|
6934
|
-
if (this.workspaceDir) return path5.join(this.workspaceDir, "drop");
|
|
6935
|
-
return path5.join(this.rootDir, "workspaces", workspaceId, "drop");
|
|
6936
|
-
}
|
|
6937
|
-
uploadedDir(workspaceId) {
|
|
6938
|
-
if (this.workspaceDir) return path5.join(this.workspaceDir, "uploaded");
|
|
6939
|
-
return path5.join(this.rootDir, "workspaces", workspaceId, "uploaded");
|
|
6940
|
-
}
|
|
6941
|
-
failedDir(workspaceId) {
|
|
6942
|
-
if (this.workspaceDir) return path5.join(this.workspaceDir, "upload-failed");
|
|
6943
|
-
return path5.join(this.rootDir, "workspaces", workspaceId, "upload-failed");
|
|
6944
|
-
}
|
|
6945
|
-
};
|
|
6946
|
-
function makeObservation(workspaceId, filePath, watchDir, size, mtime) {
|
|
6947
|
-
return {
|
|
6948
|
-
workspaceId,
|
|
6949
|
-
detail: { path: filePath, watchDir, size, mtime },
|
|
6950
|
-
observedAt: mtime
|
|
6951
|
-
};
|
|
6952
|
-
}
|
|
6953
|
-
async function walk(root) {
|
|
6954
|
-
const out = [];
|
|
6955
|
-
const entries = await import_node_fs11.promises.readdir(root).catch(() => []);
|
|
6956
|
-
for (const name of entries) {
|
|
6957
|
-
if (name.startsWith(".")) continue;
|
|
6958
|
-
const full = path5.join(root, name);
|
|
6959
|
-
const stat = await import_node_fs11.promises.lstat(full).catch(() => null);
|
|
6960
|
-
if (!stat) continue;
|
|
6961
|
-
if (stat.isSymbolicLink()) continue;
|
|
6962
|
-
if (stat.isDirectory()) {
|
|
6963
|
-
out.push(...await walk(full));
|
|
6964
|
-
} else if (stat.isFile()) {
|
|
6965
|
-
out.push(full);
|
|
6966
|
-
}
|
|
6967
|
-
}
|
|
6968
|
-
return out;
|
|
6969
|
-
}
|
|
6970
|
-
var MIME_BY_EXT = {
|
|
6971
|
-
".txt": "text/plain",
|
|
6972
|
-
".md": "text/markdown",
|
|
6973
|
-
".csv": "text/csv",
|
|
6974
|
-
".json": "application/json",
|
|
6975
|
-
".yaml": "application/yaml",
|
|
6976
|
-
".yml": "application/yaml",
|
|
6977
|
-
".pdf": "application/pdf",
|
|
6978
|
-
".png": "image/png",
|
|
6979
|
-
".jpg": "image/jpeg",
|
|
6980
|
-
".jpeg": "image/jpeg",
|
|
6981
|
-
".gif": "image/gif",
|
|
6982
|
-
".webp": "image/webp",
|
|
6983
|
-
".svg": "image/svg+xml",
|
|
6984
|
-
".html": "text/html",
|
|
6985
|
-
".htm": "text/html",
|
|
6986
|
-
".xhtml": "application/xhtml+xml",
|
|
6987
|
-
".xml": "application/xml",
|
|
6988
|
-
".zip": "application/zip",
|
|
6989
|
-
".tar": "application/x-tar",
|
|
6990
|
-
".gz": "application/gzip",
|
|
6991
|
-
".mp3": "audio/mpeg",
|
|
6992
|
-
".mp4": "video/mp4",
|
|
6993
|
-
".mov": "video/quicktime",
|
|
6994
|
-
".wav": "audio/wav"
|
|
6995
|
-
};
|
|
6996
|
-
function mimeFromExtension(p) {
|
|
6997
|
-
const ext = path5.extname(p).toLowerCase();
|
|
6998
|
-
return MIME_BY_EXT[ext] ?? null;
|
|
6999
|
-
}
|
|
7000
|
-
function sleep(ms) {
|
|
7001
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
7002
|
-
}
|
|
7003
|
-
|
|
7004
|
-
// src/daemon/asset/origin/outbox.ts
|
|
7005
|
-
var import_better_sqlite34 = __toESM(require("better-sqlite3"), 1);
|
|
7006
|
-
var import_node_crypto8 = require("crypto");
|
|
7007
|
-
var import_zod7 = require("zod");
|
|
7008
|
-
var OriginObservationRecordSchema = import_zod7.z.object({
|
|
7009
|
-
workspaceId: import_zod7.z.string().min(1),
|
|
7010
|
-
originKind: import_zod7.z.enum(["upload", "drop-folder", "agent-gen"]),
|
|
7011
|
-
sourceRef: import_zod7.z.string().min(1),
|
|
7012
|
-
payloadJson: import_zod7.z.string().min(2),
|
|
7013
|
-
hintsJson: import_zod7.z.string().min(2),
|
|
7014
|
-
observedAt: import_zod7.z.number().int().nonnegative()
|
|
7015
|
-
});
|
|
7016
|
-
var DDL_STATEMENTS = [
|
|
7017
|
-
`CREATE TABLE IF NOT EXISTS asset_origin_outbox (
|
|
7018
|
-
id TEXT PRIMARY KEY,
|
|
7019
|
-
workspaceId TEXT NOT NULL,
|
|
7020
|
-
originKind TEXT NOT NULL,
|
|
7021
|
-
sourceRef TEXT NOT NULL,
|
|
7022
|
-
payloadJson TEXT NOT NULL,
|
|
7023
|
-
hintsJson TEXT NOT NULL,
|
|
7024
|
-
observedAt INTEGER NOT NULL,
|
|
7025
|
-
idempotencyKey TEXT NOT NULL UNIQUE,
|
|
7026
|
-
status TEXT NOT NULL CHECK (status IN ('pending', 'claimed', 'uploaded')),
|
|
7027
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
7028
|
-
lastError TEXT,
|
|
7029
|
-
assetId TEXT,
|
|
7030
|
-
contentHash TEXT,
|
|
7031
|
-
createdAt INTEGER NOT NULL,
|
|
7032
|
-
updatedAt INTEGER NOT NULL
|
|
7033
|
-
)`,
|
|
7034
|
-
`CREATE INDEX IF NOT EXISTS idx_asset_origin_outbox_status
|
|
7035
|
-
ON asset_origin_outbox(status, createdAt)`,
|
|
7036
|
-
`CREATE TABLE IF NOT EXISTS asset_origin_outbox_dead_letter (
|
|
7037
|
-
id TEXT PRIMARY KEY,
|
|
7038
|
-
workspaceId TEXT NOT NULL,
|
|
7039
|
-
originKind TEXT NOT NULL,
|
|
7040
|
-
sourceRef TEXT NOT NULL,
|
|
7041
|
-
payloadJson TEXT NOT NULL,
|
|
7042
|
-
hintsJson TEXT NOT NULL,
|
|
7043
|
-
observedAt INTEGER NOT NULL,
|
|
7044
|
-
errorJson TEXT NOT NULL,
|
|
7045
|
-
attempts INTEGER NOT NULL,
|
|
7046
|
-
createdAt INTEGER NOT NULL
|
|
7047
|
-
)`
|
|
7048
|
-
];
|
|
7049
|
-
var OriginOutbox = class {
|
|
7050
|
-
db;
|
|
7051
|
-
constructor(opts) {
|
|
7052
|
-
this.db = new import_better_sqlite34.default(opts.dbPath);
|
|
7053
|
-
this.db.pragma("journal_mode = WAL");
|
|
7054
|
-
for (const ddl of DDL_STATEMENTS) this.db.prepare(ddl).run();
|
|
7055
|
-
}
|
|
7056
|
-
/**
|
|
7057
|
-
* Validate + persist an observation. Schema-invalid input lands in
|
|
7058
|
-
* `asset_origin_outbox_dead_letter` (never silently dropped). Re-enqueue
|
|
7059
|
-
* with the same {originKind, workspaceId, sourceRef, observedAt} returns
|
|
7060
|
-
* the existing row id without inserting again.
|
|
7061
|
-
*/
|
|
7062
|
-
enqueue(record) {
|
|
7063
|
-
const now = Date.now();
|
|
7064
|
-
const parsed = OriginObservationRecordSchema.safeParse(record);
|
|
7065
|
-
if (!parsed.success) {
|
|
7066
|
-
const dlId = `dl_${(0, import_node_crypto8.randomUUID)()}`;
|
|
7067
|
-
const raw = record && typeof record === "object" ? record : {};
|
|
7068
|
-
this.db.prepare(
|
|
7069
|
-
`INSERT INTO asset_origin_outbox_dead_letter
|
|
7070
|
-
(id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt, errorJson, attempts, createdAt)
|
|
7071
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
7072
|
-
).run(
|
|
7073
|
-
dlId,
|
|
7074
|
-
coerceToString(raw.workspaceId, ""),
|
|
7075
|
-
coerceToString(raw.originKind, ""),
|
|
7076
|
-
coerceToString(raw.sourceRef, ""),
|
|
7077
|
-
coerceToJson(raw.payloadJson),
|
|
7078
|
-
coerceToJson(raw.hintsJson),
|
|
7079
|
-
typeof raw.observedAt === "number" ? raw.observedAt : 0,
|
|
7080
|
-
JSON.stringify(parsed.error.errors),
|
|
7081
|
-
0,
|
|
7082
|
-
now
|
|
7083
|
-
);
|
|
7084
|
-
return { id: dlId, deadLetter: true };
|
|
7085
|
-
}
|
|
7086
|
-
const validated = parsed.data;
|
|
7087
|
-
const idempotencyKey = computeIdempotencyKey(validated);
|
|
7088
|
-
const rowId = `out_${(0, import_node_crypto8.randomUUID)()}`;
|
|
7089
|
-
try {
|
|
7090
|
-
this.db.prepare(
|
|
7091
|
-
`INSERT INTO asset_origin_outbox
|
|
7092
|
-
(id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt,
|
|
7093
|
-
idempotencyKey, status, attempts, createdAt, updatedAt)
|
|
7094
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, ?)`
|
|
7095
|
-
).run(
|
|
7096
|
-
rowId,
|
|
7097
|
-
validated.workspaceId,
|
|
7098
|
-
validated.originKind,
|
|
7099
|
-
validated.sourceRef,
|
|
7100
|
-
validated.payloadJson,
|
|
7101
|
-
validated.hintsJson,
|
|
7102
|
-
validated.observedAt,
|
|
7103
|
-
idempotencyKey,
|
|
7104
|
-
now,
|
|
7105
|
-
now
|
|
7106
|
-
);
|
|
7107
|
-
return { id: rowId, deadLetter: false };
|
|
7108
|
-
} catch (err) {
|
|
7109
|
-
const code = err.code;
|
|
7110
|
-
if (code === "SQLITE_CONSTRAINT_UNIQUE") {
|
|
7111
|
-
const existing = this.db.prepare("SELECT id FROM asset_origin_outbox WHERE idempotencyKey = ?").get(idempotencyKey);
|
|
7112
|
-
if (existing) return { id: existing.id, deadLetter: false };
|
|
7113
|
-
}
|
|
7114
|
-
throw err;
|
|
7115
|
-
}
|
|
7116
|
-
}
|
|
7117
|
-
/**
|
|
7118
|
-
* Atomically claim the oldest pending row, transition status to 'claimed',
|
|
7119
|
-
* and return it. Returns null when the queue is empty. The caller MUST
|
|
7120
|
-
* follow up with `markUploaded` (success) or `recordFailure` (error) so
|
|
7121
|
-
* the row doesn't sit in 'claimed' forever — Phase-0 is a single worker
|
|
7122
|
-
* so we don't need a claim-expiry sweeper yet.
|
|
7123
|
-
*/
|
|
7124
|
-
claimNext() {
|
|
7125
|
-
const pickAndClaim = this.db.transaction(() => {
|
|
7126
|
-
const row = this.db.prepare(
|
|
7127
|
-
`SELECT id, workspaceId, originKind, sourceRef, payloadJson, hintsJson,
|
|
7128
|
-
observedAt, attempts, lastError, createdAt
|
|
7129
|
-
FROM asset_origin_outbox
|
|
7130
|
-
WHERE status = 'pending'
|
|
7131
|
-
ORDER BY createdAt ASC
|
|
7132
|
-
LIMIT 1`
|
|
7133
|
-
).get();
|
|
7134
|
-
if (!row) return null;
|
|
7135
|
-
this.db.prepare("UPDATE asset_origin_outbox SET status = 'claimed', updatedAt = ? WHERE id = ?").run(Date.now(), row.id);
|
|
7136
|
-
return row;
|
|
7137
|
-
});
|
|
7138
|
-
const claimed = pickAndClaim();
|
|
7139
|
-
if (!claimed) return null;
|
|
7140
|
-
return {
|
|
7141
|
-
id: claimed.id,
|
|
7142
|
-
row: rawToRow(claimed)
|
|
7143
|
-
};
|
|
7144
|
-
}
|
|
7145
|
-
markUploaded(id, meta) {
|
|
7146
|
-
this.db.prepare(
|
|
7147
|
-
`UPDATE asset_origin_outbox
|
|
7148
|
-
SET status = 'uploaded', assetId = ?, contentHash = ?, updatedAt = ?
|
|
7149
|
-
WHERE id = ?`
|
|
7150
|
-
).run(meta.assetId, meta.contentHash, Date.now(), id);
|
|
7151
|
-
}
|
|
7152
|
-
/**
|
|
7153
|
-
* Record one failed upload attempt. Increments `attempts`, stores the
|
|
7154
|
-
* latest error, and either re-queues for retry (status -> 'pending') or
|
|
7155
|
-
* spills to dead-letter once attempts hit maxAttempts.
|
|
7156
|
-
*/
|
|
7157
|
-
recordFailure(id, error, opts) {
|
|
7158
|
-
const now = Date.now();
|
|
7159
|
-
const updated = this.db.transaction(() => {
|
|
7160
|
-
const row = this.db.prepare(
|
|
7161
|
-
`SELECT id, workspaceId, originKind, sourceRef, payloadJson, hintsJson,
|
|
7162
|
-
observedAt, attempts, createdAt
|
|
7163
|
-
FROM asset_origin_outbox
|
|
7164
|
-
WHERE id = ?`
|
|
7165
|
-
).get(id);
|
|
7166
|
-
if (!row) return;
|
|
7167
|
-
const nextAttempts = row.attempts + 1;
|
|
7168
|
-
if (nextAttempts >= opts.maxAttempts) {
|
|
7169
|
-
this.db.prepare(
|
|
7170
|
-
`INSERT INTO asset_origin_outbox_dead_letter
|
|
7171
|
-
(id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt, errorJson, attempts, createdAt)
|
|
7172
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
7173
|
-
).run(
|
|
7174
|
-
`dl_${(0, import_node_crypto8.randomUUID)()}`,
|
|
7175
|
-
row.workspaceId,
|
|
7176
|
-
row.originKind,
|
|
7177
|
-
row.sourceRef,
|
|
7178
|
-
row.payloadJson,
|
|
7179
|
-
row.hintsJson,
|
|
7180
|
-
row.observedAt,
|
|
7181
|
-
JSON.stringify({ message: error, attempts: nextAttempts }),
|
|
7182
|
-
nextAttempts,
|
|
7183
|
-
now
|
|
7184
|
-
);
|
|
7185
|
-
this.db.prepare("DELETE FROM asset_origin_outbox WHERE id = ?").run(id);
|
|
7186
|
-
} else {
|
|
7187
|
-
this.db.prepare(
|
|
7188
|
-
`UPDATE asset_origin_outbox
|
|
7189
|
-
SET status = 'pending', attempts = ?, lastError = ?, updatedAt = ?
|
|
7190
|
-
WHERE id = ?`
|
|
7191
|
-
).run(nextAttempts, error, now, id);
|
|
7192
|
-
}
|
|
7193
|
-
});
|
|
7194
|
-
updated();
|
|
7195
|
-
}
|
|
7196
|
-
/**
|
|
7197
|
-
* Re-promote any rows orphaned in `'claimed'` state back to `'pending'`.
|
|
7198
|
-
*
|
|
7199
|
-
* Why this exists: claimNext() transitions a row to 'claimed' under a
|
|
7200
|
-
* transaction, but the upload that follows runs OUTSIDE the transaction
|
|
7201
|
-
* (network call). If the daemon dies between the claim and the
|
|
7202
|
-
* markUploaded/recordFailure call, the row is stuck — claimNext() only
|
|
7203
|
-
* selects status='pending', so it would never be picked up again.
|
|
7204
|
-
*
|
|
7205
|
-
* Phase-0 single-worker safety: there is at most one in-flight claim at
|
|
7206
|
-
* any time, so on next process startup ANY 'claimed' row is by definition
|
|
7207
|
-
* orphaned. We promote it back to 'pending' (preserving attempts /
|
|
7208
|
-
* lastError) so the next drainOnce can retry it.
|
|
7209
|
-
*
|
|
7210
|
-
* If we ever go multi-worker, this naive sweep becomes unsafe and must be
|
|
7211
|
-
* replaced with a lease/heartbeat scheme.
|
|
7212
|
-
*
|
|
7213
|
-
* Returns the number of rows recovered (for logging / observability).
|
|
7214
|
-
*/
|
|
7215
|
-
resetClaimed() {
|
|
7216
|
-
const result = this.db.prepare(
|
|
7217
|
-
"UPDATE asset_origin_outbox SET status = 'pending', updatedAt = ? WHERE status = 'claimed'"
|
|
7218
|
-
).run(Date.now());
|
|
7219
|
-
return Number(result.changes);
|
|
7220
|
-
}
|
|
7221
|
-
pendingCount() {
|
|
7222
|
-
return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox WHERE status = 'pending'").get().n;
|
|
7223
|
-
}
|
|
7224
|
-
uploadedCount() {
|
|
7225
|
-
return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox WHERE status = 'uploaded'").get().n;
|
|
7226
|
-
}
|
|
7227
|
-
deadLetterCount() {
|
|
7228
|
-
return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox_dead_letter").get().n;
|
|
7229
|
-
}
|
|
7230
|
-
close() {
|
|
7231
|
-
this.db.close();
|
|
7232
|
-
}
|
|
7233
|
-
};
|
|
7234
|
-
function computeIdempotencyKey(record) {
|
|
7235
|
-
return (0, import_node_crypto8.createHash)("sha256").update(record.originKind).update("|").update(record.workspaceId).update("|").update(record.sourceRef).update("|").update(String(record.observedAt)).digest("hex");
|
|
7236
|
-
}
|
|
7237
|
-
function coerceToString(value, fallback) {
|
|
7238
|
-
if (typeof value === "string") return value;
|
|
7239
|
-
if (value == null) return fallback;
|
|
7240
|
-
return String(value);
|
|
7241
|
-
}
|
|
7242
|
-
function coerceToJson(value) {
|
|
7243
|
-
if (typeof value === "string") return value;
|
|
7244
|
-
if (value == null) return "{}";
|
|
7245
|
-
try {
|
|
7246
|
-
return JSON.stringify(value);
|
|
7247
|
-
} catch {
|
|
7248
|
-
return "{}";
|
|
7249
|
-
}
|
|
7250
|
-
}
|
|
7251
|
-
function rawToRow(raw) {
|
|
7252
|
-
return {
|
|
7253
|
-
id: raw.id,
|
|
7254
|
-
workspaceId: raw.workspaceId,
|
|
7255
|
-
originKind: raw.originKind,
|
|
7256
|
-
sourceRef: raw.sourceRef,
|
|
7257
|
-
payloadJson: raw.payloadJson,
|
|
7258
|
-
hintsJson: raw.hintsJson,
|
|
7259
|
-
observedAt: raw.observedAt,
|
|
7260
|
-
attempts: raw.attempts,
|
|
7261
|
-
lastError: raw.lastError,
|
|
7262
|
-
createdAt: raw.createdAt
|
|
7263
|
-
};
|
|
7264
|
-
}
|
|
7265
|
-
|
|
7266
|
-
// src/daemon/asset/origin/upload-runner.ts
|
|
7267
|
-
var UploadRunner = class {
|
|
7268
|
-
constructor(opts) {
|
|
7269
|
-
this.opts = opts;
|
|
7270
|
-
opts.outbox.resetClaimed();
|
|
7271
|
-
}
|
|
7272
|
-
opts;
|
|
7273
|
-
/**
|
|
7274
|
-
* Process every currently-pending outbox row exactly once. Failed rows
|
|
7275
|
-
* stay 'pending' (with attempts+1) for the next drainOnce call; rows
|
|
7276
|
-
* that hit maxAttempts spill to dead-letter and trigger onUploadFailure.
|
|
7277
|
-
*
|
|
7278
|
-
* Returns the count of rows processed (success + failure).
|
|
7279
|
-
*/
|
|
7280
|
-
async drainOnce() {
|
|
7281
|
-
let count = 0;
|
|
7282
|
-
while (true) {
|
|
7283
|
-
const claim = this.opts.outbox.claimNext();
|
|
7284
|
-
if (!claim) break;
|
|
7285
|
-
count += 1;
|
|
7286
|
-
const row = claim.row;
|
|
7287
|
-
const adapter = this.opts.adapters[row.originKind];
|
|
7288
|
-
if (!adapter) {
|
|
7289
|
-
this.opts.outbox.recordFailure(claim.id, `no adapter registered for kind=${row.originKind}`, {
|
|
7290
|
-
maxAttempts: 1
|
|
7291
|
-
// unrecoverable — fast-fail to dead-letter
|
|
7292
|
-
});
|
|
7293
|
-
continue;
|
|
7294
|
-
}
|
|
7295
|
-
try {
|
|
7296
|
-
const obs = rowToObservation(row);
|
|
7297
|
-
const bytes = await adapter.fetch(obs);
|
|
7298
|
-
const id = await adapter.identifySource(obs);
|
|
7299
|
-
const filename = id.hints?.filename ?? path6.basename(row.sourceRef);
|
|
7300
|
-
const metadata = {
|
|
7301
|
-
sourceRef: row.sourceRef,
|
|
7302
|
-
sourceKind: row.originKind
|
|
7303
|
-
};
|
|
7304
|
-
if (id.hints?.description) metadata.description = id.hints.description;
|
|
7305
|
-
if (id.hints?.sourceAgentImUserId) {
|
|
7306
|
-
metadata.sourceAgentImUserId = id.hints.sourceAgentImUserId;
|
|
7307
|
-
}
|
|
7308
|
-
const folderPath = id.hints?.folderPath || void 0;
|
|
7309
|
-
const result = await this.opts.cloud.uploadAsset({
|
|
7310
|
-
workspaceId: row.workspaceId,
|
|
7311
|
-
filename,
|
|
7312
|
-
bytes: bytes.bytes,
|
|
7313
|
-
mime: bytes.mime,
|
|
7314
|
-
size: bytes.size,
|
|
7315
|
-
metadata,
|
|
7316
|
-
folderPath
|
|
7317
|
-
});
|
|
7318
|
-
this.opts.outbox.markUploaded(claim.id, result);
|
|
7319
|
-
await adapter.onUploadSuccess?.(obs);
|
|
7320
|
-
} catch (err) {
|
|
7321
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
7322
|
-
const before = this.opts.outbox.deadLetterCount();
|
|
7323
|
-
this.opts.outbox.recordFailure(claim.id, message, { maxAttempts: this.opts.maxAttempts });
|
|
7324
|
-
const after = this.opts.outbox.deadLetterCount();
|
|
7325
|
-
if (after > before) {
|
|
7326
|
-
const obs = rowToObservation(row);
|
|
7327
|
-
await adapter.onUploadFailure?.(obs, err);
|
|
7328
|
-
}
|
|
7329
|
-
}
|
|
7330
|
-
}
|
|
7331
|
-
return count;
|
|
7332
|
-
}
|
|
7333
|
-
};
|
|
7334
|
-
function rowToObservation(row) {
|
|
7335
|
-
let detail = {};
|
|
7336
|
-
try {
|
|
7337
|
-
const parsed = JSON.parse(row.payloadJson);
|
|
7338
|
-
if (parsed && typeof parsed === "object") detail = parsed;
|
|
7339
|
-
} catch {
|
|
7340
|
-
}
|
|
7341
|
-
return {
|
|
7342
|
-
workspaceId: row.workspaceId,
|
|
7343
|
-
detail,
|
|
7344
|
-
observedAt: row.observedAt
|
|
7345
|
-
};
|
|
7346
|
-
}
|
|
7347
|
-
var path6 = {
|
|
7348
|
-
basename(p) {
|
|
7349
|
-
const idx = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
|
|
7350
|
-
return idx === -1 ? p : p.slice(idx + 1);
|
|
7351
|
-
}
|
|
7352
|
-
};
|
|
7353
|
-
|
|
7354
6373
|
// src/daemon/outbox-watcher.ts
|
|
7355
|
-
var
|
|
7356
|
-
var
|
|
6374
|
+
var import_node_fs9 = require("fs");
|
|
6375
|
+
var path5 = __toESM(require("path"), 1);
|
|
7357
6376
|
var DEFAULT_INTERVAL_MS = 2e3;
|
|
7358
6377
|
var RESERVED_SUBDIR = "_uploaded";
|
|
7359
6378
|
var OutboxWatcher = class {
|
|
@@ -7520,7 +6539,7 @@ var OutboxWatcher = class {
|
|
|
7520
6539
|
async scanDir(dir, kind, task) {
|
|
7521
6540
|
let entries = [];
|
|
7522
6541
|
try {
|
|
7523
|
-
entries = await
|
|
6542
|
+
entries = await import_node_fs9.promises.readdir(dir);
|
|
7524
6543
|
} catch (err) {
|
|
7525
6544
|
const code = err.code;
|
|
7526
6545
|
if (code === "ENOENT") return;
|
|
@@ -7530,10 +6549,10 @@ var OutboxWatcher = class {
|
|
|
7530
6549
|
for (const name of entries) {
|
|
7531
6550
|
if (name.startsWith(".")) continue;
|
|
7532
6551
|
if (name === RESERVED_SUBDIR) continue;
|
|
7533
|
-
const full =
|
|
6552
|
+
const full = path5.join(dir, name);
|
|
7534
6553
|
let st;
|
|
7535
6554
|
try {
|
|
7536
|
-
st = await
|
|
6555
|
+
st = await import_node_fs9.promises.stat(full);
|
|
7537
6556
|
} catch {
|
|
7538
6557
|
continue;
|
|
7539
6558
|
}
|
|
@@ -7558,8 +6577,8 @@ var OutboxWatcher = class {
|
|
|
7558
6577
|
this.log("warn", `skip ${filePath}: no active taskId (no dispatch/handoff received yet)`);
|
|
7559
6578
|
return;
|
|
7560
6579
|
}
|
|
7561
|
-
const bytes = await
|
|
7562
|
-
const fileName =
|
|
6580
|
+
const bytes = await import_node_fs9.promises.readFile(filePath);
|
|
6581
|
+
const fileName = path5.basename(filePath);
|
|
7563
6582
|
const blob = new Blob([new Uint8Array(bytes)]);
|
|
7564
6583
|
const form = new FormData();
|
|
7565
6584
|
form.append("workspaceId", wsId);
|
|
@@ -7608,8 +6627,8 @@ var OutboxWatcher = class {
|
|
|
7608
6627
|
|
|
7609
6628
|
// src/daemon/shell-executor.ts
|
|
7610
6629
|
var import_node_child_process4 = require("child_process");
|
|
7611
|
-
var
|
|
7612
|
-
var
|
|
6630
|
+
var import_node_fs10 = require("fs");
|
|
6631
|
+
var import_node_path7 = require("path");
|
|
7613
6632
|
var DEFAULT_OUTPUT_LIMIT = 256 * 1024;
|
|
7614
6633
|
var DEFAULT_TIMEOUT = 6e4;
|
|
7615
6634
|
var HARD_MAX_TIMEOUT = 30 * 6e4;
|
|
@@ -7645,7 +6664,7 @@ async function executeShellDispatch(payload, deps) {
|
|
|
7645
6664
|
const command = readCommand(payload, execution);
|
|
7646
6665
|
if (!command.trim()) return fail(payload.taskId, "shell_command_required", "Shell command is required");
|
|
7647
6666
|
const cwd = resolveCwd(execution.cwd, deps.config.defaultCwd);
|
|
7648
|
-
if (!(0,
|
|
6667
|
+
if (!(0, import_node_fs10.existsSync)(cwd)) return fail(payload.taskId, "shell_cwd_missing", `cwd does not exist: ${cwd}`);
|
|
7649
6668
|
const timeoutMs = Math.min(
|
|
7650
6669
|
typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : deps.config.maxTimeoutMs,
|
|
7651
6670
|
deps.config.maxTimeoutMs
|
|
@@ -7747,8 +6766,8 @@ function readCommand(payload, execution) {
|
|
|
7747
6766
|
return payload.prompt;
|
|
7748
6767
|
}
|
|
7749
6768
|
function resolveCwd(raw, fallback) {
|
|
7750
|
-
if (typeof raw !== "string" || raw.trim() === "") return (0,
|
|
7751
|
-
return (0,
|
|
6769
|
+
if (typeof raw !== "string" || raw.trim() === "") return (0, import_node_path7.resolve)(fallback);
|
|
6770
|
+
return (0, import_node_path7.resolve)(raw);
|
|
7752
6771
|
}
|
|
7753
6772
|
function clampNumber(value, fallback, min, max) {
|
|
7754
6773
|
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
@@ -7798,15 +6817,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
7798
6817
|
outboxWatcher;
|
|
7799
6818
|
memoryWiring;
|
|
7800
6819
|
assetMetadataIndexes = /* @__PURE__ */ new Map();
|
|
7801
|
-
workspaceMirrors = /* @__PURE__ */ new Map();
|
|
7802
|
-
assetOriginOutbox;
|
|
7803
|
-
dropFolderAdapter;
|
|
7804
|
-
dropFolderUploadRunner;
|
|
7805
|
-
dropFolderTimer;
|
|
7806
|
-
dropFolderWorkspaceId = "";
|
|
7807
|
-
dropFolderWorkspaceDir = "";
|
|
7808
|
-
dropFolderTickInFlight = false;
|
|
7809
|
-
dropFolderStable = /* @__PURE__ */ new Map();
|
|
7810
6820
|
state = "idle";
|
|
7811
6821
|
startedAt = 0;
|
|
7812
6822
|
workspaceId = "";
|
|
@@ -7848,7 +6858,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
7848
6858
|
this.servicePool = new ServicePool();
|
|
7849
6859
|
this.installStaticHostedAgentFromEnv();
|
|
7850
6860
|
this.loadAgentsFromDb();
|
|
7851
|
-
await this.prepareLocalProfiles();
|
|
7852
6861
|
this.syncQueue = new SyncQueue(this.db);
|
|
7853
6862
|
this.syncWorker = new SyncWorker({ queue: this.syncQueue, flush: (row) => this.flushSyncRow(row) });
|
|
7854
6863
|
this.syncWorker.start();
|
|
@@ -7870,10 +6879,9 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
7870
6879
|
);
|
|
7871
6880
|
}
|
|
7872
6881
|
if (this.workspaceId) {
|
|
7873
|
-
this.
|
|
7874
|
-
(err) => console.error("[Daemon] Initial asset sync failed:", err.message)
|
|
6882
|
+
this.syncAssetMetadata(this.workspaceId).catch(
|
|
6883
|
+
(err) => console.error("[Daemon] Initial asset metadata sync failed:", err.message)
|
|
7875
6884
|
);
|
|
7876
|
-
await this.ensureDropFolderRuntime(this.workspaceId);
|
|
7877
6885
|
}
|
|
7878
6886
|
const containerId = process.env.PRISMER_CONTAINER_ID;
|
|
7879
6887
|
const isContainer = !!containerId || process.env.PRISMER_RUNTIME_MODE === "container";
|
|
@@ -7924,8 +6932,7 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
7924
6932
|
// an actual agent process.
|
|
7925
6933
|
attachMemory: this.memoryWiring ? attachMemoryRpc({ runtime: this.memoryWiring.runtime }) : void 0,
|
|
7926
6934
|
attachAsset: attachAssetRpc({
|
|
7927
|
-
resolveIndex: (workspaceId) => this.assetMetadataIndexes.get(workspaceId)
|
|
7928
|
-
assetCache: this.assetCache
|
|
6935
|
+
resolveIndex: (workspaceId) => this.assetMetadataIndexes.get(workspaceId)
|
|
7929
6936
|
})
|
|
7930
6937
|
});
|
|
7931
6938
|
await this.localServer.start();
|
|
@@ -7974,7 +6981,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
7974
6981
|
this.memoryWiring = void 0;
|
|
7975
6982
|
this.ws?.close();
|
|
7976
6983
|
this.outboxWatcher?.stop();
|
|
7977
|
-
this.stopDropFolderRuntime();
|
|
7978
6984
|
await this.servicePool?.shutdown();
|
|
7979
6985
|
await this.localServer?.stop();
|
|
7980
6986
|
try {
|
|
@@ -8031,7 +7037,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8031
7037
|
runningTaskIds: Array.from(this.runningTasks.keys()),
|
|
8032
7038
|
observability: {
|
|
8033
7039
|
adapters: this.snapshotAdapterObservability(),
|
|
8034
|
-
assetSync: this.snapshotAssetSyncObservability(),
|
|
8035
7040
|
...this.lastTaskError ? { lastTaskError: this.lastTaskError } : {}
|
|
8036
7041
|
}
|
|
8037
7042
|
};
|
|
@@ -8067,35 +7072,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8067
7072
|
});
|
|
8068
7073
|
}
|
|
8069
7074
|
}
|
|
8070
|
-
async prepareLocalProfiles() {
|
|
8071
|
-
const rows = this.db.prepare(
|
|
8072
|
-
`SELECT id, workspace_id, agent_im_user_id, adapter_name, name, config, version, synced_at
|
|
8073
|
-
FROM agent_profiles
|
|
8074
|
-
WHERE deleted_at IS NULL`
|
|
8075
|
-
).all();
|
|
8076
|
-
for (const row of rows) {
|
|
8077
|
-
const adapter = this.registry.get(row.adapter_name);
|
|
8078
|
-
if (!adapter?.prepareProfile) continue;
|
|
8079
|
-
try {
|
|
8080
|
-
await adapter.prepareProfile({
|
|
8081
|
-
id: row.id,
|
|
8082
|
-
workspaceId: row.workspace_id,
|
|
8083
|
-
agentImUserId: row.agent_im_user_id,
|
|
8084
|
-
adapterName: row.adapter_name,
|
|
8085
|
-
name: row.name,
|
|
8086
|
-
config: parseProfileConfig(row.config),
|
|
8087
|
-
version: row.version,
|
|
8088
|
-
createdAt: new Date(row.synced_at ?? Date.now()),
|
|
8089
|
-
updatedAt: new Date(row.synced_at ?? Date.now())
|
|
8090
|
-
});
|
|
8091
|
-
} catch (err) {
|
|
8092
|
-
process.stderr.write(
|
|
8093
|
-
`[daemon] local profile preflight skipped agent=${row.agent_im_user_id} profile=${row.id}: ${err.message}
|
|
8094
|
-
`
|
|
8095
|
-
);
|
|
8096
|
-
}
|
|
8097
|
-
}
|
|
8098
|
-
}
|
|
8099
7075
|
/**
|
|
8100
7076
|
* Register an in-process AgentProfile snapshot (called by agent CLI / sync layer).
|
|
8101
7077
|
* Used to populate the `agents` list in agent.host.declare.
|
|
@@ -8153,10 +7129,10 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8153
7129
|
const rawJson = process.env.PRISMER_HOSTED_AGENT_JSON;
|
|
8154
7130
|
let raw;
|
|
8155
7131
|
if (rawFile) {
|
|
8156
|
-
if (!(0,
|
|
7132
|
+
if (!(0, import_node_fs11.existsSync)(rawFile)) {
|
|
8157
7133
|
throw new Error(`PRISMER_HOSTED_AGENT_FILE not found: ${rawFile}`);
|
|
8158
7134
|
}
|
|
8159
|
-
raw = (0,
|
|
7135
|
+
raw = (0, import_node_fs11.readFileSync)(rawFile, "utf8");
|
|
8160
7136
|
} else if (rawJson) {
|
|
8161
7137
|
raw = rawJson;
|
|
8162
7138
|
}
|
|
@@ -8252,13 +7228,13 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8252
7228
|
const payload = {
|
|
8253
7229
|
daemonId: this.config.daemon_id,
|
|
8254
7230
|
daemonVersion: this.opts.daemonVersion ?? "0.0.0",
|
|
8255
|
-
platform: (0,
|
|
7231
|
+
platform: (0, import_node_os4.platform)() === "win32" ? "win32" : (0, import_node_os4.platform)() === "linux" ? "linux" : "darwin",
|
|
8256
7232
|
agents: Array.from(this.hostedAgents.values()).map((a) => ({
|
|
8257
7233
|
imUserId: a.imUserId,
|
|
8258
7234
|
name: a.name,
|
|
8259
7235
|
adapterName: a.adapterName,
|
|
8260
7236
|
capabilities: a.capabilities,
|
|
8261
|
-
profiles: Array.from(a.profiles.entries()).map(([id,
|
|
7237
|
+
profiles: Array.from(a.profiles.entries()).map(([id, version2]) => ({ id, version: version2 }))
|
|
8262
7238
|
}))
|
|
8263
7239
|
};
|
|
8264
7240
|
this.ws.send(envelope("agent.host.declare", payload, this.config.daemon_id));
|
|
@@ -8300,29 +7276,17 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8300
7276
|
(err) => console.error("[Daemon] Initial memory sync failed:", err.message)
|
|
8301
7277
|
);
|
|
8302
7278
|
}
|
|
8303
|
-
this.
|
|
8304
|
-
(err) => console.error("[Daemon] Asset sync failed:", err.message)
|
|
7279
|
+
this.syncAssetMetadata(this.workspaceId).catch(
|
|
7280
|
+
(err) => console.error("[Daemon] Asset metadata sync failed:", err.message)
|
|
8305
7281
|
);
|
|
8306
|
-
await this.ensureDropFolderRuntime(this.workspaceId);
|
|
8307
7282
|
for (const id of payload.profilesToSync) {
|
|
8308
7283
|
try {
|
|
8309
|
-
await this.servicePool.drop(id);
|
|
8310
7284
|
await this.syncProfileFromCloud(id);
|
|
8311
7285
|
} catch (err) {
|
|
8312
7286
|
this.emit("sync-error", err);
|
|
8313
7287
|
}
|
|
8314
7288
|
}
|
|
8315
|
-
|
|
8316
|
-
const row = this.db.prepare("SELECT agent_im_user_id FROM agent_profiles WHERE id = ?").get(id);
|
|
8317
|
-
this.db.prepare("DELETE FROM agent_profiles WHERE id = ?").run(id);
|
|
8318
|
-
if (row) {
|
|
8319
|
-
this.db.prepare("DELETE FROM agents WHERE im_user_id = ?").run(row.agent_im_user_id);
|
|
8320
|
-
}
|
|
8321
|
-
process.stderr.write(`[daemon] tombstone profile=${id}
|
|
8322
|
-
`);
|
|
8323
|
-
}
|
|
8324
|
-
if (payload.profilesToDelete?.length) this.loadAgentsFromDb();
|
|
8325
|
-
if ((payload.profilesToSync.length > 0 || (payload.profilesToDelete?.length ?? 0) > 0) && this.wsConnected) this.sendDeclare();
|
|
7289
|
+
if (payload.profilesToSync.length > 0 && this.wsConnected) this.sendDeclare();
|
|
8326
7290
|
this.emit("host-acked", payload);
|
|
8327
7291
|
}
|
|
8328
7292
|
async onTaskDispatch(payload, requestId) {
|
|
@@ -8363,14 +7327,8 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8363
7327
|
}
|
|
8364
7328
|
});
|
|
8365
7329
|
this.ws.send(envelope("task.dispatch.reply", reply, requestId));
|
|
8366
|
-
if (!reply.ok) {
|
|
8367
|
-
const code = reply.error?.code ?? "unknown";
|
|
8368
|
-
const message = reply.error?.message ?? "unknown error";
|
|
8369
|
-
process.stderr.write(`[daemon] dispatch failed task=${payload.taskId} code=${code} message=${message}
|
|
8370
|
-
`);
|
|
8371
|
-
}
|
|
8372
7330
|
} else {
|
|
8373
|
-
|
|
7331
|
+
await handleDispatch(payload, requestId, {
|
|
8374
7332
|
registry: this.registry,
|
|
8375
7333
|
cloud: this.cloud,
|
|
8376
7334
|
uriResolver: this.uriResolver,
|
|
@@ -8386,12 +7344,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8386
7344
|
if (running) running.lastProgressAt = Date.now();
|
|
8387
7345
|
}
|
|
8388
7346
|
});
|
|
8389
|
-
if (!reply.ok) {
|
|
8390
|
-
const code = reply.error?.code ?? "unknown";
|
|
8391
|
-
const message = reply.error?.message ?? "unknown error";
|
|
8392
|
-
process.stderr.write(`[daemon] dispatch failed task=${payload.taskId} code=${code} message=${message}
|
|
8393
|
-
`);
|
|
8394
|
-
}
|
|
8395
7347
|
}
|
|
8396
7348
|
process.stdout.write(`[daemon] dispatch done task=${payload.taskId}
|
|
8397
7349
|
`);
|
|
@@ -8419,7 +7371,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8419
7371
|
}
|
|
8420
7372
|
async onAgentProfileChanged(payload) {
|
|
8421
7373
|
try {
|
|
8422
|
-
await this.servicePool.drop(payload.profileId);
|
|
8423
7374
|
await this.syncProfileFromCloud(payload.profileId);
|
|
8424
7375
|
if (this.wsConnected) this.sendDeclare();
|
|
8425
7376
|
} catch (err) {
|
|
@@ -8427,21 +7378,7 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8427
7378
|
}
|
|
8428
7379
|
}
|
|
8429
7380
|
async syncProfileFromCloud(profileId) {
|
|
8430
|
-
|
|
8431
|
-
try {
|
|
8432
|
-
profile = await this.cloud.get(`/api/im/agent_profiles/${encodeURIComponent(profileId)}`);
|
|
8433
|
-
} catch (err) {
|
|
8434
|
-
if (err instanceof CloudError && err.status === 404) {
|
|
8435
|
-
const row = this.db.prepare("SELECT agent_im_user_id FROM agent_profiles WHERE id = ?").get(profileId);
|
|
8436
|
-
this.db.prepare("DELETE FROM agent_profiles WHERE id = ?").run(profileId);
|
|
8437
|
-
if (row) {
|
|
8438
|
-
this.db.prepare("DELETE FROM agents WHERE im_user_id = ?").run(row.agent_im_user_id);
|
|
8439
|
-
}
|
|
8440
|
-
this.loadAgentsFromDb();
|
|
8441
|
-
return;
|
|
8442
|
-
}
|
|
8443
|
-
throw err;
|
|
8444
|
-
}
|
|
7381
|
+
const profile = await this.cloud.get(`/api/im/agent_profiles/${encodeURIComponent(profileId)}`);
|
|
8445
7382
|
const agent = await this.resolveOwnedAgent(profile.agentImUserId);
|
|
8446
7383
|
const adapter = this.registry.get(profile.adapterName);
|
|
8447
7384
|
const capabilities = agent?.card?.capabilities?.length ? agent.card.capabilities : adapter?.capabilities ?? [];
|
|
@@ -8476,14 +7413,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8476
7413
|
);
|
|
8477
7414
|
});
|
|
8478
7415
|
tx();
|
|
8479
|
-
try {
|
|
8480
|
-
await adapter?.prepareProfile?.(profile);
|
|
8481
|
-
} catch (err) {
|
|
8482
|
-
process.stderr.write(
|
|
8483
|
-
`[daemon] profile preflight skipped agent=${profile.agentImUserId} profile=${profile.id}: ${err.message}
|
|
8484
|
-
`
|
|
8485
|
-
);
|
|
8486
|
-
}
|
|
8487
7416
|
this.loadAgentsFromDb();
|
|
8488
7417
|
process.stdout.write(
|
|
8489
7418
|
`[daemon] profile synced agent=${profile.agentImUserId} profile=${profile.id} adapter=${profile.adapterName}
|
|
@@ -8512,10 +7441,15 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8512
7441
|
}
|
|
8513
7442
|
async onAssetChanged(payload) {
|
|
8514
7443
|
if (!payload.workspaceId) return;
|
|
7444
|
+
const index = this.assetMetadataIndexes.get(payload.workspaceId);
|
|
7445
|
+
if (!index) return;
|
|
8515
7446
|
try {
|
|
8516
|
-
await
|
|
7447
|
+
const result = await index.pullDelta();
|
|
7448
|
+
if (result.applied > 0) {
|
|
7449
|
+
console.log(`[Daemon] asset.changed workspace=${payload.workspaceId} applied=${result.applied}`);
|
|
7450
|
+
}
|
|
8517
7451
|
} catch (err) {
|
|
8518
|
-
console.error(`[Daemon] asset.changed
|
|
7452
|
+
console.error(`[Daemon] asset.changed pullDelta failed workspace=${payload.workspaceId}:`, err.message);
|
|
8519
7453
|
}
|
|
8520
7454
|
}
|
|
8521
7455
|
onWorkspaceFileChanged(payload) {
|
|
@@ -8553,163 +7487,28 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8553
7487
|
}
|
|
8554
7488
|
};
|
|
8555
7489
|
}
|
|
8556
|
-
snapshotAssetSyncObservability() {
|
|
8557
|
-
return {
|
|
8558
|
-
workspaceId: this.dropFolderWorkspaceId || null,
|
|
8559
|
-
rootDir: this.dropFolderWorkspaceDir || null,
|
|
8560
|
-
dropDir: this.dropFolderWorkspaceDir ? (0, import_node_path10.join)(this.dropFolderWorkspaceDir, "drop") : null,
|
|
8561
|
-
uploadedDir: this.dropFolderWorkspaceDir ? (0, import_node_path10.join)(this.dropFolderWorkspaceDir, "uploaded") : null,
|
|
8562
|
-
failedDir: this.dropFolderWorkspaceDir ? (0, import_node_path10.join)(this.dropFolderWorkspaceDir, "upload-failed") : null
|
|
8563
|
-
};
|
|
8564
|
-
}
|
|
8565
7490
|
/**
|
|
8566
7491
|
* Ensure an AssetMetadataIndex exists for the given workspace and pull
|
|
8567
7492
|
* delta from cloud. Idempotent — creates the index on first call, reuses
|
|
8568
|
-
* it on subsequent calls. Same cursor-catch-up semantics as WorkspaceMirror.
|
|
8569
|
-
*/
|
|
8570
|
-
async syncAssetMetadata(workspaceId) {
|
|
8571
|
-
let index = this.assetMetadataIndexes.get(workspaceId);
|
|
8572
|
-
if (!index) {
|
|
8573
|
-
const stateDir = `${this.paths.root}/${workspaceId}`;
|
|
8574
|
-
index = new AssetMetadataIndex({
|
|
8575
|
-
db: this.db,
|
|
8576
|
-
cloud: this.cloud,
|
|
8577
|
-
workspaceId,
|
|
8578
|
-
workspaceStateDir: stateDir
|
|
8579
|
-
});
|
|
8580
|
-
this.assetMetadataIndexes.set(workspaceId, index);
|
|
8581
|
-
}
|
|
8582
|
-
const result = await index.pullDelta();
|
|
8583
|
-
if (result.applied > 0) {
|
|
8584
|
-
console.log(`[AssetMeta] workspace=${workspaceId}: ${result.applied} applied, cursor=${result.cursor}`);
|
|
8585
|
-
}
|
|
8586
|
-
}
|
|
8587
|
-
/**
|
|
8588
|
-
* Ensure a WorkspaceMirror exists for the workspace and pull path→asset
|
|
8589
|
-
* bindings from cloud. Cold-start daemons previously only had this table
|
|
8590
|
-
* populated by WS push or per-path URI fallback, so local asset state looked
|
|
8591
|
-
* empty even though cloud files existed.
|
|
7493
|
+
* it on subsequent calls. Same cursor-catch-up semantics as WorkspaceMirror.
|
|
8592
7494
|
*/
|
|
8593
|
-
async
|
|
8594
|
-
let
|
|
8595
|
-
if (!
|
|
7495
|
+
async syncAssetMetadata(workspaceId) {
|
|
7496
|
+
let index = this.assetMetadataIndexes.get(workspaceId);
|
|
7497
|
+
if (!index) {
|
|
8596
7498
|
const stateDir = `${this.paths.root}/${workspaceId}`;
|
|
8597
|
-
|
|
7499
|
+
index = new AssetMetadataIndex({
|
|
8598
7500
|
db: this.db,
|
|
8599
7501
|
cloud: this.cloud,
|
|
8600
7502
|
workspaceId,
|
|
8601
7503
|
workspaceStateDir: stateDir
|
|
8602
7504
|
});
|
|
8603
|
-
this.
|
|
7505
|
+
this.assetMetadataIndexes.set(workspaceId, index);
|
|
8604
7506
|
}
|
|
8605
|
-
const result = await
|
|
7507
|
+
const result = await index.pullDelta();
|
|
8606
7508
|
if (result.applied > 0) {
|
|
8607
|
-
console.log(`[
|
|
8608
|
-
}
|
|
8609
|
-
}
|
|
8610
|
-
async syncAssetState(workspaceId) {
|
|
8611
|
-
if (!this.paths?.root) return;
|
|
8612
|
-
await Promise.all([
|
|
8613
|
-
this.syncAssetMetadata(workspaceId),
|
|
8614
|
-
this.syncWorkspaceFiles(workspaceId)
|
|
8615
|
-
]);
|
|
8616
|
-
}
|
|
8617
|
-
/**
|
|
8618
|
-
* Desktop asset ingress: watch a user-visible local asset folder
|
|
8619
|
-
* and upload new files as IMAssets, preserving nested relative dirs in
|
|
8620
|
-
* IMAsset.folderPath. The lower-level adapter/outbox/uploader already
|
|
8621
|
-
* existed; this method wires them into the real daemon lifecycle.
|
|
8622
|
-
*/
|
|
8623
|
-
async ensureDropFolderRuntime(workspaceId) {
|
|
8624
|
-
if (!workspaceId || !this.paths?.root) return;
|
|
8625
|
-
if (this.dropFolderWorkspaceId === workspaceId && this.dropFolderTimer) return;
|
|
8626
|
-
this.stopDropFolderRuntime();
|
|
8627
|
-
this.dropFolderWorkspaceId = workspaceId;
|
|
8628
|
-
const workspaceRoot = resolveUserAssetWorkspaceDir(workspaceId);
|
|
8629
|
-
this.dropFolderWorkspaceDir = workspaceRoot;
|
|
8630
|
-
await (0, import_promises.mkdir)((0, import_node_path10.join)(workspaceRoot, "drop"), { recursive: true });
|
|
8631
|
-
await (0, import_promises.mkdir)((0, import_node_path10.join)(workspaceRoot, "uploaded"), { recursive: true });
|
|
8632
|
-
await (0, import_promises.mkdir)((0, import_node_path10.join)(workspaceRoot, "upload-failed"), { recursive: true });
|
|
8633
|
-
this.assetOriginOutbox = new OriginOutbox({ dbPath: (0, import_node_path10.join)(this.paths.root, "asset-origin.db") });
|
|
8634
|
-
this.dropFolderAdapter = new DropFolderAdapter({ workspaceDir: workspaceRoot });
|
|
8635
|
-
this.dropFolderUploadRunner = new UploadRunner({
|
|
8636
|
-
outbox: this.assetOriginOutbox,
|
|
8637
|
-
cloud: new DaemonAssetUploadClient(this.config),
|
|
8638
|
-
adapters: { "drop-folder": this.dropFolderAdapter },
|
|
8639
|
-
maxAttempts: 3
|
|
8640
|
-
});
|
|
8641
|
-
const tick = () => {
|
|
8642
|
-
this.runDropFolderTick().catch((err) => {
|
|
8643
|
-
process.stderr.write(`[daemon] drop-folder sync failed: ${err.message}
|
|
8644
|
-
`);
|
|
8645
|
-
});
|
|
8646
|
-
};
|
|
8647
|
-
this.dropFolderTimer = setInterval(tick, 1e3);
|
|
8648
|
-
tick();
|
|
8649
|
-
process.stdout.write(`[daemon] drop-folder asset sync watching ${(0, import_node_path10.join)(workspaceRoot, "drop")}
|
|
8650
|
-
`);
|
|
8651
|
-
}
|
|
8652
|
-
stopDropFolderRuntime() {
|
|
8653
|
-
if (this.dropFolderTimer) {
|
|
8654
|
-
clearInterval(this.dropFolderTimer);
|
|
8655
|
-
this.dropFolderTimer = void 0;
|
|
8656
|
-
}
|
|
8657
|
-
try {
|
|
8658
|
-
this.assetOriginOutbox?.close();
|
|
8659
|
-
} catch {
|
|
8660
|
-
}
|
|
8661
|
-
this.assetOriginOutbox = void 0;
|
|
8662
|
-
this.dropFolderAdapter = void 0;
|
|
8663
|
-
this.dropFolderUploadRunner = void 0;
|
|
8664
|
-
this.dropFolderWorkspaceId = "";
|
|
8665
|
-
this.dropFolderWorkspaceDir = "";
|
|
8666
|
-
this.dropFolderTickInFlight = false;
|
|
8667
|
-
this.dropFolderStable.clear();
|
|
8668
|
-
}
|
|
8669
|
-
async runDropFolderTick() {
|
|
8670
|
-
if (this.dropFolderTickInFlight) return;
|
|
8671
|
-
const workspaceId = this.dropFolderWorkspaceId;
|
|
8672
|
-
const adapter = this.dropFolderAdapter;
|
|
8673
|
-
const outbox = this.assetOriginOutbox;
|
|
8674
|
-
const runner = this.dropFolderUploadRunner;
|
|
8675
|
-
if (!workspaceId || !adapter || !outbox || !runner) return;
|
|
8676
|
-
this.dropFolderTickInFlight = true;
|
|
8677
|
-
try {
|
|
8678
|
-
const observations = await adapter.scanOnce(workspaceId);
|
|
8679
|
-
for (const obs of observations) {
|
|
8680
|
-
const id = await adapter.identifySource(obs);
|
|
8681
|
-
if (!this.isDropFolderObservationStable(id.sourceRef, obs.detail)) continue;
|
|
8682
|
-
outbox.enqueue({
|
|
8683
|
-
workspaceId: obs.workspaceId,
|
|
8684
|
-
originKind: "drop-folder",
|
|
8685
|
-
sourceRef: id.sourceRef,
|
|
8686
|
-
payloadJson: JSON.stringify(obs.detail),
|
|
8687
|
-
hintsJson: JSON.stringify(id.hints ?? {}),
|
|
8688
|
-
observedAt: obs.observedAt
|
|
8689
|
-
});
|
|
8690
|
-
}
|
|
8691
|
-
const processed = await runner.drainOnce();
|
|
8692
|
-
if (processed > 0) {
|
|
8693
|
-
this.dropFolderStable.clear();
|
|
8694
|
-
process.stdout.write(`[daemon] drop-folder uploaded/processed ${processed} asset(s)
|
|
8695
|
-
`);
|
|
8696
|
-
await this.syncAssetState(workspaceId).catch(
|
|
8697
|
-
(err) => process.stderr.write(`[daemon] drop-folder post-sync failed: ${err.message}
|
|
8698
|
-
`)
|
|
8699
|
-
);
|
|
8700
|
-
}
|
|
8701
|
-
} finally {
|
|
8702
|
-
this.dropFolderTickInFlight = false;
|
|
7509
|
+
console.log(`[AssetMeta] workspace=${workspaceId}: ${result.applied} applied, cursor=${result.cursor}`);
|
|
8703
7510
|
}
|
|
8704
7511
|
}
|
|
8705
|
-
isDropFolderObservationStable(sourceRef, detail) {
|
|
8706
|
-
const raw = detail && typeof detail === "object" ? detail : {};
|
|
8707
|
-
const size = typeof raw.size === "number" ? raw.size : -1;
|
|
8708
|
-
const mtime = typeof raw.mtime === "number" ? raw.mtime : -1;
|
|
8709
|
-
const previous = this.dropFolderStable.get(sourceRef);
|
|
8710
|
-
this.dropFolderStable.set(sourceRef, { size, mtime });
|
|
8711
|
-
return Boolean(previous && previous.size === size && previous.mtime === mtime);
|
|
8712
|
-
}
|
|
8713
7512
|
/**
|
|
8714
7513
|
* SyncWorker FlushFn — pushes local writes to cloud.
|
|
8715
7514
|
*
|
|
@@ -8746,44 +7545,44 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8746
7545
|
const id = encodeURIComponent(row.resource_id);
|
|
8747
7546
|
const body = row.operation === "delete" ? void 0 : safeJsonParse(row.payload);
|
|
8748
7547
|
let method;
|
|
8749
|
-
let
|
|
7548
|
+
let path7;
|
|
8750
7549
|
switch (op) {
|
|
8751
7550
|
case "workspace.create":
|
|
8752
7551
|
method = "POST";
|
|
8753
|
-
|
|
7552
|
+
path7 = "/api/im/workspaces";
|
|
8754
7553
|
break;
|
|
8755
7554
|
case "workspace.update":
|
|
8756
7555
|
method = "PATCH";
|
|
8757
|
-
|
|
7556
|
+
path7 = `/api/im/workspaces/${id}`;
|
|
8758
7557
|
break;
|
|
8759
7558
|
case "workspace.delete":
|
|
8760
7559
|
method = "DELETE";
|
|
8761
|
-
|
|
7560
|
+
path7 = `/api/im/workspaces/${id}`;
|
|
8762
7561
|
break;
|
|
8763
7562
|
// agent.create must use /register — POST /api/im/agents is not exposed.
|
|
8764
7563
|
case "agent.create":
|
|
8765
7564
|
method = "POST";
|
|
8766
|
-
|
|
7565
|
+
path7 = "/api/im/register";
|
|
8767
7566
|
break;
|
|
8768
7567
|
case "agent.update":
|
|
8769
7568
|
method = "PATCH";
|
|
8770
|
-
|
|
7569
|
+
path7 = `/api/im/agents/${id}`;
|
|
8771
7570
|
break;
|
|
8772
7571
|
case "agent.delete":
|
|
8773
7572
|
method = "DELETE";
|
|
8774
|
-
|
|
7573
|
+
path7 = `/api/im/agents/${id}`;
|
|
8775
7574
|
break;
|
|
8776
7575
|
case "agent_profile.create":
|
|
8777
7576
|
method = "POST";
|
|
8778
|
-
|
|
7577
|
+
path7 = "/api/im/agent_profiles";
|
|
8779
7578
|
break;
|
|
8780
7579
|
case "agent_profile.update":
|
|
8781
7580
|
method = "PATCH";
|
|
8782
|
-
|
|
7581
|
+
path7 = `/api/im/agent_profiles/${id}`;
|
|
8783
7582
|
break;
|
|
8784
7583
|
case "agent_profile.delete":
|
|
8785
7584
|
method = "DELETE";
|
|
8786
|
-
|
|
7585
|
+
path7 = `/api/im/agent_profiles/${id}`;
|
|
8787
7586
|
break;
|
|
8788
7587
|
default:
|
|
8789
7588
|
process.stderr.write(`[daemon] sync flush op=${op} id=${row.id} result=drop (unknown op)
|
|
@@ -8792,7 +7591,7 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8792
7591
|
}
|
|
8793
7592
|
let res;
|
|
8794
7593
|
try {
|
|
8795
|
-
res = await this.cloud.request(method,
|
|
7594
|
+
res = await this.cloud.request(method, path7, { body });
|
|
8796
7595
|
} catch (err) {
|
|
8797
7596
|
process.stderr.write(
|
|
8798
7597
|
`[daemon] sync flush op=${op} id=${row.id} result=retry (threw: ${err.message})
|
|
@@ -8815,89 +7614,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
8815
7614
|
return { ok: res.ok, status: res.status, message: res.error?.message };
|
|
8816
7615
|
}
|
|
8817
7616
|
};
|
|
8818
|
-
var DaemonAssetUploadClient = class {
|
|
8819
|
-
constructor(config) {
|
|
8820
|
-
this.config = config;
|
|
8821
|
-
}
|
|
8822
|
-
config;
|
|
8823
|
-
async uploadAsset(req) {
|
|
8824
|
-
const form = new FormData();
|
|
8825
|
-
form.set("workspaceId", req.workspaceId);
|
|
8826
|
-
form.set("kind", "file");
|
|
8827
|
-
form.set("metadata", JSON.stringify(req.metadata));
|
|
8828
|
-
if (req.folderPath !== void 0) form.set("folderPath", req.folderPath);
|
|
8829
|
-
form.set("file", new Blob([req.bytes], { type: req.mime ?? "application/octet-stream" }), req.filename);
|
|
8830
|
-
const url = `${this.config.cloud_api_base.replace(/\/$/, "")}/api/im/assets`;
|
|
8831
|
-
const controller = new AbortController();
|
|
8832
|
-
const timer = setTimeout(() => controller.abort(), 6e4);
|
|
8833
|
-
let res;
|
|
8834
|
-
try {
|
|
8835
|
-
res = await fetch(url, {
|
|
8836
|
-
method: "POST",
|
|
8837
|
-
headers: { Authorization: `Bearer ${this.config.api_key}` },
|
|
8838
|
-
body: form,
|
|
8839
|
-
signal: controller.signal
|
|
8840
|
-
});
|
|
8841
|
-
} catch (err) {
|
|
8842
|
-
if (err.name === "AbortError") {
|
|
8843
|
-
throw new Error("asset upload timed out after 60000ms");
|
|
8844
|
-
}
|
|
8845
|
-
throw err;
|
|
8846
|
-
} finally {
|
|
8847
|
-
clearTimeout(timer);
|
|
8848
|
-
}
|
|
8849
|
-
const text = await res.text();
|
|
8850
|
-
let body;
|
|
8851
|
-
try {
|
|
8852
|
-
body = text ? JSON.parse(text) : void 0;
|
|
8853
|
-
} catch {
|
|
8854
|
-
body = text;
|
|
8855
|
-
}
|
|
8856
|
-
if (!res.ok || isCloudErrorEnvelope(body)) {
|
|
8857
|
-
throw new Error(`asset upload failed (${res.status}): ${cloudUploadErrorMessage(body)}`);
|
|
8858
|
-
}
|
|
8859
|
-
const data = unwrapCloudData(body);
|
|
8860
|
-
const assetId = stringValue(data, "id");
|
|
8861
|
-
const contentHash = stringValue(data, "contentHash");
|
|
8862
|
-
if (!assetId || !contentHash) {
|
|
8863
|
-
throw new Error("asset upload response missing id/contentHash");
|
|
8864
|
-
}
|
|
8865
|
-
return { assetId, contentHash };
|
|
8866
|
-
}
|
|
8867
|
-
};
|
|
8868
|
-
function isCloudErrorEnvelope(raw) {
|
|
8869
|
-
return Boolean(raw && typeof raw === "object" && "ok" in raw && raw.ok === false);
|
|
8870
|
-
}
|
|
8871
|
-
function cloudUploadErrorMessage(raw) {
|
|
8872
|
-
if (raw && typeof raw === "object") {
|
|
8873
|
-
const error = raw.error;
|
|
8874
|
-
if (typeof error === "string") return error;
|
|
8875
|
-
if (error && typeof error === "object") {
|
|
8876
|
-
const message = error.message;
|
|
8877
|
-
if (typeof message === "string") return message;
|
|
8878
|
-
}
|
|
8879
|
-
}
|
|
8880
|
-
return typeof raw === "string" ? raw : "request failed";
|
|
8881
|
-
}
|
|
8882
|
-
function unwrapCloudData(raw) {
|
|
8883
|
-
if (raw && typeof raw === "object") {
|
|
8884
|
-
const data = raw.data;
|
|
8885
|
-
if (data && typeof data === "object" && !Array.isArray(data)) return data;
|
|
8886
|
-
}
|
|
8887
|
-
return {};
|
|
8888
|
-
}
|
|
8889
|
-
function stringValue(obj, key) {
|
|
8890
|
-
const value = obj[key];
|
|
8891
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
8892
|
-
}
|
|
8893
|
-
function resolveUserAssetWorkspaceDir(workspaceId) {
|
|
8894
|
-
const override = process.env.PRISMER_ASSET_SYNC_ROOT;
|
|
8895
|
-
if (override) return (0, import_node_path10.join)(override, workspaceId);
|
|
8896
|
-
const home = (0, import_node_os6.homedir)();
|
|
8897
|
-
const desktop = (0, import_node_path10.join)(home, "Desktop");
|
|
8898
|
-
const base = (0, import_node_fs14.existsSync)(desktop) ? desktop : home;
|
|
8899
|
-
return (0, import_node_path10.join)(base, "Prismer Assets", workspaceId);
|
|
8900
|
-
}
|
|
8901
7617
|
function readTargetDaemonId(payload) {
|
|
8902
7618
|
if (typeof payload.targetDaemonId === "string" && payload.targetDaemonId.length > 0) {
|
|
8903
7619
|
return payload.targetDaemonId;
|
|
@@ -8948,14 +7664,6 @@ function validateStaticHostedAgent(raw) {
|
|
|
8948
7664
|
}
|
|
8949
7665
|
};
|
|
8950
7666
|
}
|
|
8951
|
-
function parseProfileConfig(raw) {
|
|
8952
|
-
try {
|
|
8953
|
-
const parsed = JSON.parse(raw);
|
|
8954
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
8955
|
-
} catch {
|
|
8956
|
-
return {};
|
|
8957
|
-
}
|
|
8958
|
-
}
|
|
8959
7667
|
function safeJsonParse(raw) {
|
|
8960
7668
|
try {
|
|
8961
7669
|
return JSON.parse(raw);
|
|
@@ -8965,9 +7673,9 @@ function safeJsonParse(raw) {
|
|
|
8965
7673
|
}
|
|
8966
7674
|
|
|
8967
7675
|
// src/pair.ts
|
|
8968
|
-
var
|
|
8969
|
-
var
|
|
8970
|
-
var
|
|
7676
|
+
var import_node_crypto5 = require("crypto");
|
|
7677
|
+
var import_node_os5 = require("os");
|
|
7678
|
+
var import_promises = require("timers/promises");
|
|
8971
7679
|
var import_qrcode = __toESM(require("qrcode"), 1);
|
|
8972
7680
|
async function pair(opts) {
|
|
8973
7681
|
const paths = opts.paths ?? resolvePaths();
|
|
@@ -8983,7 +7691,7 @@ async function pair(opts) {
|
|
|
8983
7691
|
"pair: --as-user requires LOCAL_ONLY=1. Without that gate, this would skip mobile approval and silently mint a key for the named user."
|
|
8984
7692
|
);
|
|
8985
7693
|
}
|
|
8986
|
-
const { publicKey } = (0,
|
|
7694
|
+
const { publicKey } = (0, import_node_crypto5.generateKeyPairSync)("ed25519");
|
|
8987
7695
|
const devicePub = publicKey.export({ format: "der", type: "spki" }).toString("base64");
|
|
8988
7696
|
const cloud = new CloudClient({
|
|
8989
7697
|
baseUrl: opts.cloudBaseUrl,
|
|
@@ -8996,7 +7704,7 @@ async function pair(opts) {
|
|
|
8996
7704
|
"/api/im/pair/offer",
|
|
8997
7705
|
{
|
|
8998
7706
|
auth: false,
|
|
8999
|
-
body: { devicePub, deviceName: opts.deviceName ?? (0,
|
|
7707
|
+
body: { devicePub, deviceName: opts.deviceName ?? (0, import_node_os5.hostname)() }
|
|
9000
7708
|
}
|
|
9001
7709
|
);
|
|
9002
7710
|
if (!offerRes.ok) {
|
|
@@ -9036,7 +7744,7 @@ Scan with Lumin to approve, or open: ${offer.qrUrl}
|
|
|
9036
7744
|
const pollIntervalMs = opts.pollIntervalMs ?? 5e3;
|
|
9037
7745
|
for (let i = 0; i < maxAttempts; i += 1) {
|
|
9038
7746
|
if (i > 0 || !localOnlyMode) {
|
|
9039
|
-
await (0,
|
|
7747
|
+
await (0, import_promises.setTimeout)(pollIntervalMs);
|
|
9040
7748
|
}
|
|
9041
7749
|
const res = await cloud.request(
|
|
9042
7750
|
"GET",
|
|
@@ -9073,13 +7781,13 @@ function unwrapEnvelope(raw) {
|
|
|
9073
7781
|
}
|
|
9074
7782
|
|
|
9075
7783
|
// src/cli/index.ts
|
|
9076
|
-
var
|
|
7784
|
+
var import_commander18 = require("commander");
|
|
9077
7785
|
|
|
9078
7786
|
// src/cli/commands/adapter.ts
|
|
9079
7787
|
var import_node_child_process5 = require("child_process");
|
|
9080
|
-
var
|
|
9081
|
-
var
|
|
9082
|
-
var
|
|
7788
|
+
var import_node_fs13 = require("fs");
|
|
7789
|
+
var import_node_os6 = require("os");
|
|
7790
|
+
var import_node_path9 = require("path");
|
|
9083
7791
|
var import_commander = require("commander");
|
|
9084
7792
|
init_util();
|
|
9085
7793
|
init_ui();
|
|
@@ -9092,7 +7800,7 @@ var INSTALL_SPECS = {
|
|
|
9092
7800
|
binary: "claude",
|
|
9093
7801
|
hint: "Set ANTHROPIC_API_KEY in your shell profile, or rely on Claude Code OAuth login. Run `claude login` to sign in.",
|
|
9094
7802
|
authHints: ["ANTHROPIC_API_KEY or Claude Code OAuth login (`claude login`)"],
|
|
9095
|
-
hookTarget: { path: (0,
|
|
7803
|
+
hookTarget: { path: (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".claude", "hooks.json"), kind: "json-file" }
|
|
9096
7804
|
},
|
|
9097
7805
|
openclaw: {
|
|
9098
7806
|
name: "openclaw",
|
|
@@ -9101,7 +7809,7 @@ var INSTALL_SPECS = {
|
|
|
9101
7809
|
binary: "openclaw",
|
|
9102
7810
|
hint: "OpenClaw runs as a gateway. Configure ~/.openclaw/openclaw.json and start it with `openclaw gateway` before tasks dispatch.",
|
|
9103
7811
|
authHints: ["~/.openclaw/openclaw.json gateway.auth.bearerTokens", "Prismer daemon api_key"],
|
|
9104
|
-
hookTarget: { path: (0,
|
|
7812
|
+
hookTarget: { path: (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".openclaw", "hooks"), kind: "directory" }
|
|
9105
7813
|
},
|
|
9106
7814
|
codex: {
|
|
9107
7815
|
name: "codex",
|
|
@@ -9110,7 +7818,7 @@ var INSTALL_SPECS = {
|
|
|
9110
7818
|
binary: "codex",
|
|
9111
7819
|
hint: "Set OPENAI_API_KEY in your shell profile. Verify with `codex --help`.",
|
|
9112
7820
|
authHints: ["OPENAI_API_KEY or Codex CLI account login"],
|
|
9113
|
-
hookTarget: { path: (0,
|
|
7821
|
+
hookTarget: { path: (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".codex", "hooks.json"), kind: "json-file" }
|
|
9114
7822
|
},
|
|
9115
7823
|
hermes: {
|
|
9116
7824
|
name: "hermes",
|
|
@@ -9119,7 +7827,7 @@ var INSTALL_SPECS = {
|
|
|
9119
7827
|
binary: "hermes",
|
|
9120
7828
|
hint: "Hermes runs as a long-lived HTTP gateway in your own Python venv. Start with `hermes -p <profile> gateway` after configuring ~/.hermes/.env.",
|
|
9121
7829
|
authHints: ["~/.hermes/.env API_SERVER_KEY", "Hermes profile provider credentials"],
|
|
9122
|
-
hookTarget: { path: (0,
|
|
7830
|
+
hookTarget: { path: (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".hermes", "hooks.json"), kind: "json-file" }
|
|
9123
7831
|
}
|
|
9124
7832
|
};
|
|
9125
7833
|
function buildAdapterCommand() {
|
|
@@ -9207,8 +7915,8 @@ function buildAdapterCommand() {
|
|
|
9207
7915
|
});
|
|
9208
7916
|
return cmd;
|
|
9209
7917
|
}
|
|
9210
|
-
function runInstall(spec,
|
|
9211
|
-
const { argv0, args } = installCommand(spec,
|
|
7918
|
+
function runInstall(spec, version2, dryRun) {
|
|
7919
|
+
const { argv0, args } = installCommand(spec, version2);
|
|
9212
7920
|
getUI().line(`> ${argv0} ${args.join(" ")}`);
|
|
9213
7921
|
if (dryRun) {
|
|
9214
7922
|
getUI().line("(dry-run \u2014 not executing)");
|
|
@@ -9334,15 +8042,15 @@ function runHooks(spec, opts) {
|
|
|
9334
8042
|
}
|
|
9335
8043
|
let backup;
|
|
9336
8044
|
if (planned.kind === "directory") {
|
|
9337
|
-
(0,
|
|
9338
|
-
const markerPath = (0,
|
|
8045
|
+
(0, import_node_fs13.mkdirSync)(planned.path, { recursive: true });
|
|
8046
|
+
const markerPath = (0, import_node_path9.join)(planned.path, "prismer.json");
|
|
9339
8047
|
backup = backupIfExists(markerPath);
|
|
9340
|
-
(0,
|
|
8048
|
+
(0, import_node_fs13.writeFileSync)(markerPath, JSON.stringify(prismerHookMarker(spec), null, 2) + "\n", "utf8");
|
|
9341
8049
|
} else {
|
|
9342
|
-
(0,
|
|
8050
|
+
(0, import_node_fs13.mkdirSync)((0, import_node_path9.dirname)(planned.path), { recursive: true });
|
|
9343
8051
|
backup = backupIfExists(planned.path);
|
|
9344
8052
|
const merged = mergeHookJson(planned.path, spec);
|
|
9345
|
-
(0,
|
|
8053
|
+
(0, import_node_fs13.writeFileSync)(planned.path, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
9346
8054
|
}
|
|
9347
8055
|
return {
|
|
9348
8056
|
ok: true,
|
|
@@ -9352,16 +8060,16 @@ function runHooks(spec, opts) {
|
|
|
9352
8060
|
hook: inspectHook(spec)
|
|
9353
8061
|
};
|
|
9354
8062
|
}
|
|
9355
|
-
function installCommand(spec,
|
|
9356
|
-
const ref =
|
|
8063
|
+
function installCommand(spec, version2) {
|
|
8064
|
+
const ref = version2 === "latest" ? spec.package : `${spec.package}@${version2}`;
|
|
9357
8065
|
switch (spec.manager) {
|
|
9358
8066
|
case "npm":
|
|
9359
8067
|
return { argv0: "npm", args: ["install", "-g", ref] };
|
|
9360
8068
|
case "pipx":
|
|
9361
|
-
const pyRef =
|
|
8069
|
+
const pyRef = version2 === "latest" ? spec.package : `${spec.package}==${version2}`;
|
|
9362
8070
|
return { argv0: "pipx", args: ["install", pyRef] };
|
|
9363
8071
|
case "pip":
|
|
9364
|
-
const pipRef =
|
|
8072
|
+
const pipRef = version2 === "latest" ? spec.package : `${spec.package}==${version2}`;
|
|
9365
8073
|
return { argv0: "pip", args: ["install", "--user", pipRef] };
|
|
9366
8074
|
}
|
|
9367
8075
|
}
|
|
@@ -9409,23 +8117,23 @@ function inspectAuthEnv(spec) {
|
|
|
9409
8117
|
hints: spec.authHints ?? []
|
|
9410
8118
|
};
|
|
9411
8119
|
case "hermes": {
|
|
9412
|
-
const envFile = (0,
|
|
8120
|
+
const envFile = (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".hermes", ".env");
|
|
9413
8121
|
return {
|
|
9414
|
-
ok: (0,
|
|
8122
|
+
ok: (0, import_node_fs13.existsSync)(envFile) || Boolean(process.env.HERMES_API_KEY || process.env.API_SERVER_KEY),
|
|
9415
8123
|
present: [
|
|
9416
8124
|
...["HERMES_API_KEY", "API_SERVER_KEY"].filter((k) => Boolean(process.env[k])),
|
|
9417
|
-
...(0,
|
|
8125
|
+
...(0, import_node_fs13.existsSync)(envFile) ? [envFile] : []
|
|
9418
8126
|
],
|
|
9419
8127
|
hints: spec.authHints ?? []
|
|
9420
8128
|
};
|
|
9421
8129
|
}
|
|
9422
8130
|
case "openclaw": {
|
|
9423
|
-
const cfgFile = (0,
|
|
8131
|
+
const cfgFile = (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".openclaw", "openclaw.json");
|
|
9424
8132
|
return {
|
|
9425
|
-
ok: (0,
|
|
8133
|
+
ok: (0, import_node_fs13.existsSync)(cfgFile) || Boolean(process.env.OPENCLAW_API_KEY),
|
|
9426
8134
|
present: [
|
|
9427
8135
|
...["OPENCLAW_API_KEY"].filter((k) => Boolean(process.env[k])),
|
|
9428
|
-
...(0,
|
|
8136
|
+
...(0, import_node_fs13.existsSync)(cfgFile) ? [cfgFile] : []
|
|
9429
8137
|
],
|
|
9430
8138
|
hints: spec.authHints ?? []
|
|
9431
8139
|
};
|
|
@@ -9440,32 +8148,32 @@ function inspectHook(spec) {
|
|
|
9440
8148
|
return { supported: false, markerPresent: false };
|
|
9441
8149
|
}
|
|
9442
8150
|
if (spec.name === "openclaw") {
|
|
9443
|
-
const jsonPath = (0,
|
|
9444
|
-
const markerPath = (0,
|
|
9445
|
-
const jsonMarkerPresent = (0,
|
|
9446
|
-
const dirMarkerPresent = (0,
|
|
8151
|
+
const jsonPath = (0, import_node_path9.join)((0, import_node_os6.homedir)(), ".openclaw", "hooks.json");
|
|
8152
|
+
const markerPath = (0, import_node_path9.join)(target.path, "prismer.json");
|
|
8153
|
+
const jsonMarkerPresent = (0, import_node_fs13.existsSync)(jsonPath) && fileContainsPrismerMarker(jsonPath);
|
|
8154
|
+
const dirMarkerPresent = (0, import_node_fs13.existsSync)(markerPath) && fileContainsPrismerMarker(markerPath);
|
|
9447
8155
|
return {
|
|
9448
8156
|
supported: true,
|
|
9449
8157
|
kind: "json-file-or-directory",
|
|
9450
8158
|
path: target.path,
|
|
9451
8159
|
jsonPath,
|
|
9452
|
-
exists: (0,
|
|
8160
|
+
exists: (0, import_node_fs13.existsSync)(target.path) || (0, import_node_fs13.existsSync)(jsonPath),
|
|
9453
8161
|
markerPath,
|
|
9454
8162
|
markerPresent: jsonMarkerPresent || dirMarkerPresent
|
|
9455
8163
|
};
|
|
9456
8164
|
}
|
|
9457
8165
|
if (target.kind === "directory") {
|
|
9458
|
-
const markerPath = (0,
|
|
8166
|
+
const markerPath = (0, import_node_path9.join)(target.path, "prismer.json");
|
|
9459
8167
|
return {
|
|
9460
8168
|
supported: true,
|
|
9461
8169
|
kind: target.kind,
|
|
9462
8170
|
path: target.path,
|
|
9463
|
-
exists: (0,
|
|
8171
|
+
exists: (0, import_node_fs13.existsSync)(target.path),
|
|
9464
8172
|
markerPath,
|
|
9465
|
-
markerPresent: (0,
|
|
8173
|
+
markerPresent: (0, import_node_fs13.existsSync)(markerPath) && fileContainsPrismerMarker(markerPath)
|
|
9466
8174
|
};
|
|
9467
8175
|
}
|
|
9468
|
-
const exists = (0,
|
|
8176
|
+
const exists = (0, import_node_fs13.existsSync)(target.path);
|
|
9469
8177
|
if (!exists) {
|
|
9470
8178
|
return {
|
|
9471
8179
|
supported: true,
|
|
@@ -9499,21 +8207,21 @@ function prismerHookMarker(spec) {
|
|
|
9499
8207
|
installed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9500
8208
|
};
|
|
9501
8209
|
}
|
|
9502
|
-
function mergeHookJson(
|
|
8210
|
+
function mergeHookJson(path7, spec) {
|
|
9503
8211
|
const marker = prismerHookMarker(spec);
|
|
9504
|
-
if (!(0,
|
|
8212
|
+
if (!(0, import_node_fs13.existsSync)(path7)) {
|
|
9505
8213
|
return {
|
|
9506
8214
|
prismer: marker
|
|
9507
8215
|
};
|
|
9508
8216
|
}
|
|
9509
8217
|
let parsed;
|
|
9510
8218
|
try {
|
|
9511
|
-
parsed = JSON.parse((0,
|
|
8219
|
+
parsed = JSON.parse((0, import_node_fs13.readFileSync)(path7, "utf8"));
|
|
9512
8220
|
} catch (err) {
|
|
9513
|
-
throw new Error(`Cannot parse ${
|
|
8221
|
+
throw new Error(`Cannot parse ${path7} as JSON: ${err.message}`);
|
|
9514
8222
|
}
|
|
9515
8223
|
if (!isRecord(parsed)) {
|
|
9516
|
-
throw new Error(`Cannot merge ${
|
|
8224
|
+
throw new Error(`Cannot merge ${path7}: expected a JSON object`);
|
|
9517
8225
|
}
|
|
9518
8226
|
const next = { ...parsed };
|
|
9519
8227
|
next.prismer = marker;
|
|
@@ -9531,18 +8239,18 @@ function mergeHookJson(path9, spec) {
|
|
|
9531
8239
|
}
|
|
9532
8240
|
return next;
|
|
9533
8241
|
}
|
|
9534
|
-
function backupIfExists(
|
|
9535
|
-
if (!(0,
|
|
8242
|
+
function backupIfExists(path7) {
|
|
8243
|
+
if (!(0, import_node_fs13.existsSync)(path7)) return null;
|
|
9536
8244
|
const suffix = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9537
|
-
const backup = `${
|
|
9538
|
-
const stat = (0,
|
|
8245
|
+
const backup = `${path7}.bak.${suffix}`;
|
|
8246
|
+
const stat = (0, import_node_fs13.statSync)(path7);
|
|
9539
8247
|
if (stat.isDirectory()) return null;
|
|
9540
|
-
(0,
|
|
8248
|
+
(0, import_node_fs13.copyFileSync)(path7, backup);
|
|
9541
8249
|
return backup;
|
|
9542
8250
|
}
|
|
9543
|
-
function fileContainsPrismerMarker(
|
|
8251
|
+
function fileContainsPrismerMarker(path7) {
|
|
9544
8252
|
try {
|
|
9545
|
-
return (0,
|
|
8253
|
+
return (0, import_node_fs13.readFileSync)(path7, "utf8").includes("prismer-daemon-runtime");
|
|
9546
8254
|
} catch {
|
|
9547
8255
|
return false;
|
|
9548
8256
|
}
|
|
@@ -9560,7 +8268,7 @@ function readAdapterConfig() {
|
|
|
9560
8268
|
return {};
|
|
9561
8269
|
}
|
|
9562
8270
|
}
|
|
9563
|
-
function recordInstallInConfig(spec, binPath,
|
|
8271
|
+
function recordInstallInConfig(spec, binPath, version2) {
|
|
9564
8272
|
const paths = resolvePaths();
|
|
9565
8273
|
if (!configExists(paths)) {
|
|
9566
8274
|
process.stderr.write(
|
|
@@ -9582,7 +8290,7 @@ function recordInstallInConfig(spec, binPath, version) {
|
|
|
9582
8290
|
package_manager: spec.manager,
|
|
9583
8291
|
package_name: spec.package,
|
|
9584
8292
|
binary: binPath,
|
|
9585
|
-
version,
|
|
8293
|
+
version: version2,
|
|
9586
8294
|
installed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9587
8295
|
};
|
|
9588
8296
|
saveConfig({ ...cfg, adapters }, paths);
|
|
@@ -9962,7 +8670,7 @@ async function fetchCloudProfiles(cloud, imUserId) {
|
|
|
9962
8670
|
async function fetchLocalServer() {
|
|
9963
8671
|
const paths = resolvePaths();
|
|
9964
8672
|
const pid = readPidFile(paths);
|
|
9965
|
-
if (!pid || !
|
|
8673
|
+
if (!pid || !pidAlive(pid)) return { ok: false, error: "daemon process is not running", agents: [] };
|
|
9966
8674
|
try {
|
|
9967
8675
|
const [healthRes, agentsRes] = await Promise.all([
|
|
9968
8676
|
fetch(`${LOCAL_SERVER}/healthz`, { signal: AbortSignal.timeout(1e3) }),
|
|
@@ -9986,38 +8694,19 @@ function whichBinary2(bin) {
|
|
|
9986
8694
|
|
|
9987
8695
|
// src/cli/commands/asset.ts
|
|
9988
8696
|
var import_commander3 = require("commander");
|
|
9989
|
-
var
|
|
9990
|
-
var
|
|
8697
|
+
var import_node_fs14 = require("fs");
|
|
8698
|
+
var import_node_path10 = require("path");
|
|
9991
8699
|
init_util();
|
|
9992
8700
|
init_ui();
|
|
9993
8701
|
function buildAssetCommand() {
|
|
9994
8702
|
const cmd = new import_commander3.Command("asset").description("Inspect IM assets");
|
|
9995
|
-
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) => {
|
|
9996
|
-
const result = await syncAssets(opts);
|
|
9997
|
-
if (opts.json) {
|
|
9998
|
-
printJson({ ok: true, data: result });
|
|
9999
|
-
return;
|
|
10000
|
-
}
|
|
10001
|
-
getUI().line(`Synced asset metadata: ${result.metadata.applied} applied (cursor=${result.metadata.cursor})`);
|
|
10002
|
-
getUI().line(`Synced workspace files: ${result.workspaceFiles.applied} applied (cursor=${result.workspaceFiles.cursor ?? "null"})`);
|
|
10003
|
-
if (opts.bytes) {
|
|
10004
|
-
getUI().line(
|
|
10005
|
-
`Prefetched bytes: ${result.prefetch.fetched}/${result.prefetch.considered} assets, ${result.prefetch.bytes} bytes`
|
|
10006
|
-
);
|
|
10007
|
-
if (result.prefetch.failed.length > 0) {
|
|
10008
|
-
getUI().line(`Prefetch failures: ${result.prefetch.failed.length}`);
|
|
10009
|
-
}
|
|
10010
|
-
} else {
|
|
10011
|
-
getUI().line("Asset bytes were not prefetched; they are downloaded lazily on first use. Run with --bytes to fill ~/.prismer/cache.");
|
|
10012
|
-
}
|
|
10013
|
-
}, { code: "asset_sync_failed" }));
|
|
10014
8703
|
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) => {
|
|
10015
8704
|
const cloud = mkCloud();
|
|
10016
8705
|
const query = new URLSearchParams();
|
|
10017
8706
|
if (opts.workspaceId) query.set("workspaceId", opts.workspaceId);
|
|
10018
8707
|
if (opts.taskId) query.set("taskId", opts.taskId);
|
|
10019
|
-
const
|
|
10020
|
-
const res = await cloud.request("GET",
|
|
8708
|
+
const path7 = `/api/im/assets${query.toString() ? `?${query.toString()}` : ""}`;
|
|
8709
|
+
const res = await cloud.request("GET", path7);
|
|
10021
8710
|
if (!res.ok) exitWithError(`asset list failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_list_failed" });
|
|
10022
8711
|
const body = res.data;
|
|
10023
8712
|
if (isErrorEnvelope(body)) {
|
|
@@ -10029,20 +8718,13 @@ function buildAssetCommand() {
|
|
|
10029
8718
|
}
|
|
10030
8719
|
printAssetList(body);
|
|
10031
8720
|
}, { code: "asset_list_failed" }));
|
|
10032
|
-
cmd.command("upload <
|
|
10033
|
-
const body = await
|
|
8721
|
+
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) => {
|
|
8722
|
+
const body = await uploadAsset(file, opts);
|
|
10034
8723
|
if (opts.json) {
|
|
10035
8724
|
printJson(body ?? null);
|
|
10036
8725
|
return;
|
|
10037
8726
|
}
|
|
10038
|
-
|
|
10039
|
-
getUI().line(`Uploaded ${body.data.count} assets`);
|
|
10040
|
-
for (const item of body.data.items) {
|
|
10041
|
-
printAssetDetail("Uploaded asset", item.file, item.response);
|
|
10042
|
-
}
|
|
10043
|
-
} else {
|
|
10044
|
-
printAssetDetail("Uploaded asset", inputPath, body);
|
|
10045
|
-
}
|
|
8727
|
+
printAssetDetail("Uploaded asset", file, body);
|
|
10046
8728
|
}, { code: "asset_upload_failed" }));
|
|
10047
8729
|
cmd.command("download <assetId>").description("Download asset bytes").requiredOption("--out <path>", "Destination file path").action(runAction(async (assetId, opts) => {
|
|
10048
8730
|
await downloadAsset(assetId, opts.out);
|
|
@@ -10062,12 +8744,12 @@ function buildAssetCommand() {
|
|
|
10062
8744
|
}
|
|
10063
8745
|
printAssetDetail("Asset", assetId, body);
|
|
10064
8746
|
}, { code: "asset_get_failed" }));
|
|
10065
|
-
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 (
|
|
8747
|
+
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) => {
|
|
10066
8748
|
const cloud = mkCloud();
|
|
10067
8749
|
const query = new URLSearchParams();
|
|
10068
8750
|
if (opts.workspaceId) query.set("wsId", opts.workspaceId);
|
|
10069
|
-
const
|
|
10070
|
-
const res = await cloud.request("GET",
|
|
8751
|
+
const path7 = `/api/im/assets/by-hash/${encodeURIComponent(sha2562)}${query.toString() ? `?${query.toString()}` : ""}`;
|
|
8752
|
+
const res = await cloud.request("GET", path7);
|
|
10071
8753
|
if (!res.ok) exitWithError(`asset by-hash failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_by_hash_failed" });
|
|
10072
8754
|
const body = res.data;
|
|
10073
8755
|
if (isErrorEnvelope(body)) {
|
|
@@ -10077,7 +8759,7 @@ function buildAssetCommand() {
|
|
|
10077
8759
|
printJson(body ?? null);
|
|
10078
8760
|
return;
|
|
10079
8761
|
}
|
|
10080
|
-
printAssetDetail("Asset hash",
|
|
8762
|
+
printAssetDetail("Asset hash", sha2562, body);
|
|
10081
8763
|
}, { code: "asset_by_hash_failed" }));
|
|
10082
8764
|
return cmd;
|
|
10083
8765
|
}
|
|
@@ -10085,102 +8767,10 @@ function mkCloud() {
|
|
|
10085
8767
|
const cfg = loadConfig(resolvePaths());
|
|
10086
8768
|
return new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
|
|
10087
8769
|
}
|
|
10088
|
-
async function syncAssets(opts) {
|
|
10089
|
-
const paths = resolvePaths();
|
|
10090
|
-
const cfg = loadConfig(paths);
|
|
10091
|
-
const cloud = new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
|
|
10092
|
-
const db = openLocalDb(paths.localDb);
|
|
10093
|
-
try {
|
|
10094
|
-
const stateDir = `${paths.root}/${opts.workspaceId}`;
|
|
10095
|
-
const metadataIndex = new AssetMetadataIndex({
|
|
10096
|
-
db,
|
|
10097
|
-
cloud,
|
|
10098
|
-
workspaceId: opts.workspaceId,
|
|
10099
|
-
workspaceStateDir: stateDir
|
|
10100
|
-
});
|
|
10101
|
-
const mirror = new WorkspaceMirror({
|
|
10102
|
-
db,
|
|
10103
|
-
cloud,
|
|
10104
|
-
workspaceId: opts.workspaceId,
|
|
10105
|
-
workspaceStateDir: stateDir
|
|
10106
|
-
});
|
|
10107
|
-
const [metadata, workspaceFiles] = await Promise.all([
|
|
10108
|
-
metadataIndex.pullDelta(),
|
|
10109
|
-
mirror.pullDelta()
|
|
10110
|
-
]);
|
|
10111
|
-
const prefetch = opts.bytes ? await prefetchAssetBytes({
|
|
10112
|
-
db,
|
|
10113
|
-
cloud,
|
|
10114
|
-
workspaceId: opts.workspaceId,
|
|
10115
|
-
cacheDir: paths.cacheDir,
|
|
10116
|
-
maxBytes: cfg.cache?.max_bytes,
|
|
10117
|
-
limit: opts.limit
|
|
10118
|
-
}) : { considered: 0, fetched: 0, bytes: 0, failed: [] };
|
|
10119
|
-
return { workspaceId: opts.workspaceId, metadata, workspaceFiles, prefetch };
|
|
10120
|
-
} finally {
|
|
10121
|
-
db.close();
|
|
10122
|
-
}
|
|
10123
|
-
}
|
|
10124
|
-
async function prefetchAssetBytes(opts) {
|
|
10125
|
-
const limitClause = opts.limit ? " LIMIT ?" : "";
|
|
10126
|
-
const params = [opts.workspaceId];
|
|
10127
|
-
if (opts.limit) params.push(opts.limit);
|
|
10128
|
-
const rows = opts.db.prepare(
|
|
10129
|
-
`SELECT asset_id, content_hash, mime, size_bytes
|
|
10130
|
-
FROM asset_metadata_index
|
|
10131
|
-
WHERE workspace_id = ?
|
|
10132
|
-
ORDER BY asset_index_seq DESC${limitClause}`
|
|
10133
|
-
).all(...params);
|
|
10134
|
-
const cache = new AssetCache({
|
|
10135
|
-
db: opts.db,
|
|
10136
|
-
cloud: opts.cloud,
|
|
10137
|
-
cacheDir: opts.cacheDir,
|
|
10138
|
-
maxBytes: opts.maxBytes
|
|
10139
|
-
});
|
|
10140
|
-
let fetched = 0;
|
|
10141
|
-
let bytes = 0;
|
|
10142
|
-
const failed = [];
|
|
10143
|
-
for (const row of rows) {
|
|
10144
|
-
try {
|
|
10145
|
-
const cached = await cache.getOrFetch(row.content_hash, {
|
|
10146
|
-
workspaceIdHint: opts.workspaceId,
|
|
10147
|
-
assetId: row.asset_id
|
|
10148
|
-
});
|
|
10149
|
-
fetched += 1;
|
|
10150
|
-
bytes += cached.sizeBytes;
|
|
10151
|
-
} catch (err) {
|
|
10152
|
-
failed.push({
|
|
10153
|
-
assetId: row.asset_id,
|
|
10154
|
-
contentHash: row.content_hash,
|
|
10155
|
-
error: err.message
|
|
10156
|
-
});
|
|
10157
|
-
}
|
|
10158
|
-
}
|
|
10159
|
-
return { considered: rows.length, fetched, bytes, failed };
|
|
10160
|
-
}
|
|
10161
|
-
async function uploadAssetPath(inputPath, opts) {
|
|
10162
|
-
if (!(0, import_node_fs17.existsSync)(inputPath)) exitWithError(`path not found: ${inputPath}`);
|
|
10163
|
-
const stat = (0, import_node_fs17.lstatSync)(inputPath);
|
|
10164
|
-
if (stat.isSymbolicLink()) exitWithError(`refusing to upload symlink: ${inputPath}`);
|
|
10165
|
-
if (stat.isFile()) {
|
|
10166
|
-
return uploadAsset(inputPath, { ...opts, folderPath: normalizeFolderPath(opts.folderPath) });
|
|
10167
|
-
}
|
|
10168
|
-
if (!stat.isDirectory()) exitWithError(`path is not a file or directory: ${inputPath}`);
|
|
10169
|
-
const files = listFilesRecursive(inputPath);
|
|
10170
|
-
if (files.length === 0) exitWithError(`directory has no uploadable files: ${inputPath}`);
|
|
10171
|
-
const items = [];
|
|
10172
|
-
for (const file of files) {
|
|
10173
|
-
const relDir = (0, import_node_path13.dirname)((0, import_node_path13.relative)(inputPath, file));
|
|
10174
|
-
const folderPath = combineFolderPath(opts.folderPath, relDir === "." ? "" : relDir);
|
|
10175
|
-
const response = await uploadAsset(file, { ...opts, folderPath });
|
|
10176
|
-
items.push({ file, folderPath: folderPath ?? null, response });
|
|
10177
|
-
}
|
|
10178
|
-
return { ok: true, data: { count: items.length, items } };
|
|
10179
|
-
}
|
|
10180
8770
|
async function uploadAsset(file, opts) {
|
|
10181
|
-
if (!(0,
|
|
8771
|
+
if (!(0, import_node_fs14.existsSync)(file)) exitWithError(`file not found: ${file}`);
|
|
10182
8772
|
const cfg = loadConfig(resolvePaths());
|
|
10183
|
-
const bytes = (0,
|
|
8773
|
+
const bytes = (0, import_node_fs14.readFileSync)(file);
|
|
10184
8774
|
const metadata = parseMetadata(opts.metadata);
|
|
10185
8775
|
if (opts.taskId && metadata.taskId === void 0) metadata.taskId = opts.taskId;
|
|
10186
8776
|
if (opts.containerId && metadata.containerId === void 0) metadata.containerId = opts.containerId;
|
|
@@ -10189,9 +8779,8 @@ async function uploadAsset(file, opts) {
|
|
|
10189
8779
|
form.set("kind", opts.kind);
|
|
10190
8780
|
if (opts.agentId) form.set("sourceAgentImUserId", opts.agentId);
|
|
10191
8781
|
if (opts.taskId) form.set("sourceTaskId", opts.taskId);
|
|
10192
|
-
if (opts.folderPath !== void 0) form.set("folderPath", opts.folderPath);
|
|
10193
8782
|
if (Object.keys(metadata).length > 0) form.set("metadata", JSON.stringify(metadata));
|
|
10194
|
-
form.set("file", new Blob([bytes], { type: opts.mime ?? "application/octet-stream" }), (0,
|
|
8783
|
+
form.set("file", new Blob([bytes], { type: opts.mime ?? "application/octet-stream" }), (0, import_node_path10.basename)(file));
|
|
10195
8784
|
const url = `${cfg.cloud_api_base.replace(/\/$/, "")}/api/im/assets`;
|
|
10196
8785
|
let res;
|
|
10197
8786
|
try {
|
|
@@ -10209,37 +8798,6 @@ async function uploadAsset(file, opts) {
|
|
|
10209
8798
|
}
|
|
10210
8799
|
return body;
|
|
10211
8800
|
}
|
|
10212
|
-
function listFilesRecursive(root) {
|
|
10213
|
-
const out = [];
|
|
10214
|
-
for (const name of (0, import_node_fs17.readdirSync)(root)) {
|
|
10215
|
-
if (name.startsWith(".")) continue;
|
|
10216
|
-
const full = (0, import_node_path13.join)(root, name);
|
|
10217
|
-
const stat = (0, import_node_fs17.lstatSync)(full);
|
|
10218
|
-
if (stat.isSymbolicLink()) continue;
|
|
10219
|
-
if (stat.isDirectory()) {
|
|
10220
|
-
out.push(...listFilesRecursive(full));
|
|
10221
|
-
} else if (stat.isFile()) {
|
|
10222
|
-
out.push(full);
|
|
10223
|
-
}
|
|
10224
|
-
}
|
|
10225
|
-
return out.sort();
|
|
10226
|
-
}
|
|
10227
|
-
function combineFolderPath(prefix, relDir) {
|
|
10228
|
-
const normalizedPrefix = normalizeFolderPath(prefix);
|
|
10229
|
-
const normalizedRel = normalizeFolderPath(relDir);
|
|
10230
|
-
if (normalizedPrefix && normalizedRel) return `${normalizedPrefix}/${normalizedRel}`;
|
|
10231
|
-
return normalizedPrefix ?? normalizedRel;
|
|
10232
|
-
}
|
|
10233
|
-
function normalizeFolderPath(value) {
|
|
10234
|
-
if (value === void 0) return void 0;
|
|
10235
|
-
const normalized = value.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
10236
|
-
return normalized.length > 0 ? normalized : "";
|
|
10237
|
-
}
|
|
10238
|
-
function isUploadManyResult(raw) {
|
|
10239
|
-
return Boolean(
|
|
10240
|
-
raw && typeof raw === "object" && raw.ok === true && raw.data && Array.isArray(raw.data.items)
|
|
10241
|
-
);
|
|
10242
|
-
}
|
|
10243
8801
|
async function downloadAsset(assetId, outPath) {
|
|
10244
8802
|
const cloud = mkCloud();
|
|
10245
8803
|
const res = await cloud.fetchRaw(`/api/im/assets/${encodeURIComponent(assetId)}`);
|
|
@@ -10248,7 +8806,7 @@ async function downloadAsset(assetId, outPath) {
|
|
|
10248
8806
|
exitWithError(`asset download failed (${res.status}): ${errorMessage(body)}`);
|
|
10249
8807
|
}
|
|
10250
8808
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
10251
|
-
(0,
|
|
8809
|
+
(0, import_node_fs14.writeFileSync)(outPath, bytes);
|
|
10252
8810
|
}
|
|
10253
8811
|
function parseMetadata(raw) {
|
|
10254
8812
|
if (!raw) return {};
|
|
@@ -10333,13 +8891,6 @@ function stringField(obj, key) {
|
|
|
10333
8891
|
const value = obj?.[key];
|
|
10334
8892
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
10335
8893
|
}
|
|
10336
|
-
function parsePositiveInt(raw) {
|
|
10337
|
-
const n = Number(raw);
|
|
10338
|
-
if (!Number.isInteger(n) || n <= 0) {
|
|
10339
|
-
throw new Error("--limit must be a positive integer");
|
|
10340
|
-
}
|
|
10341
|
-
return n;
|
|
10342
|
-
}
|
|
10343
8894
|
|
|
10344
8895
|
// src/cli/commands/banner.ts
|
|
10345
8896
|
var import_commander4 = require("commander");
|
|
@@ -10395,7 +8946,7 @@ ${name} (${role})`);
|
|
|
10395
8946
|
getUI().line(`Group created ${data.groupId ?? "(unknown)"}
|
|
10396
8947
|
${data.title ?? opts.name} (${memberCount} members)`);
|
|
10397
8948
|
});
|
|
10398
|
-
group.command("messages <groupId>").description("List group messages").option("--limit <n>", "Max messages",
|
|
8949
|
+
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) => {
|
|
10399
8950
|
const data = await requestOrExit(
|
|
10400
8951
|
"GET",
|
|
10401
8952
|
withQuery(`/api/im/groups/${encodeURIComponent(groupId)}/messages`, {
|
|
@@ -10433,7 +8984,7 @@ ${data.title ?? opts.name} (${memberCount} members)`);
|
|
|
10433
8984
|
getUI().line(`Removed ${memberId} from group ${groupId}`);
|
|
10434
8985
|
});
|
|
10435
8986
|
cmd.addCommand(group);
|
|
10436
|
-
cmd.command("messages <conversationId>").description("List conversation messages").option("--limit <n>", "Max messages",
|
|
8987
|
+
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) => {
|
|
10437
8988
|
const data = await requestOrExit(
|
|
10438
8989
|
"GET",
|
|
10439
8990
|
withQuery(`/api/im/messages/${encodeURIComponent(conversationId)}`, {
|
|
@@ -10449,15 +9000,15 @@ ${data.title ?? opts.name} (${memberCount} members)`);
|
|
|
10449
9000
|
});
|
|
10450
9001
|
return cmd;
|
|
10451
9002
|
}
|
|
10452
|
-
async function requestOrExit(method,
|
|
9003
|
+
async function requestOrExit(method, path7, body) {
|
|
10453
9004
|
let res;
|
|
10454
9005
|
try {
|
|
10455
|
-
res = await mkCloud2().request(method,
|
|
9006
|
+
res = await mkCloud2().request(method, path7, body === void 0 ? void 0 : { body });
|
|
10456
9007
|
} catch (err) {
|
|
10457
9008
|
exitWithError(sanitizeError(err.message));
|
|
10458
9009
|
}
|
|
10459
9010
|
if (!res.ok) {
|
|
10460
|
-
exitWithError(`${method} ${
|
|
9011
|
+
exitWithError(`${method} ${path7} failed (${res.status}): ${sanitizeError(res.error?.message ?? "unknown")}`);
|
|
10461
9012
|
}
|
|
10462
9013
|
return unwrapEnvelope2(res.data);
|
|
10463
9014
|
}
|
|
@@ -10489,20 +9040,20 @@ function unwrapEnvelope2(raw) {
|
|
|
10489
9040
|
function parseCsv(value) {
|
|
10490
9041
|
return value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
10491
9042
|
}
|
|
10492
|
-
function
|
|
9043
|
+
function parsePositiveInt(value) {
|
|
10493
9044
|
const parsed = Number.parseInt(value, 10);
|
|
10494
9045
|
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
10495
9046
|
throw new Error("expected a positive integer");
|
|
10496
9047
|
}
|
|
10497
9048
|
return parsed;
|
|
10498
9049
|
}
|
|
10499
|
-
function withQuery(
|
|
9050
|
+
function withQuery(path7, params) {
|
|
10500
9051
|
const query = new URLSearchParams();
|
|
10501
9052
|
for (const [key, value] of Object.entries(params)) {
|
|
10502
9053
|
if (value !== void 0) query.set(key, String(value));
|
|
10503
9054
|
}
|
|
10504
9055
|
const suffix = query.toString();
|
|
10505
|
-
return suffix ? `${
|
|
9056
|
+
return suffix ? `${path7}?${suffix}` : path7;
|
|
10506
9057
|
}
|
|
10507
9058
|
function printMessageSummary(label, data) {
|
|
10508
9059
|
const msg = data.message ?? {};
|
|
@@ -10629,7 +9180,7 @@ init_util();
|
|
|
10629
9180
|
init_ui();
|
|
10630
9181
|
function buildCookbookCommand() {
|
|
10631
9182
|
const cmd = new import_commander7.Command("cookbook").description("Run CLI-only 54release MVP regression suites");
|
|
10632
|
-
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",
|
|
9183
|
+
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) => {
|
|
10633
9184
|
const suites = parseSuites(opts.suite);
|
|
10634
9185
|
const cloud = mkCloud3(opts.timeoutMs);
|
|
10635
9186
|
const checks = [];
|
|
@@ -10671,8 +9222,8 @@ async function runStatusSuite(cloud) {
|
|
|
10671
9222
|
await check("status", "cloud health", "GET", "/api/health"),
|
|
10672
9223
|
await check("status", "current IM identity", "GET", "/api/im/me", void 0, cloud)
|
|
10673
9224
|
];
|
|
10674
|
-
async function check(suite, name, method,
|
|
10675
|
-
return requestCheck(client, suite, name, method,
|
|
9225
|
+
async function check(suite, name, method, path7, body, client = cloud) {
|
|
9226
|
+
return requestCheck(client, suite, name, method, path7, body);
|
|
10676
9227
|
}
|
|
10677
9228
|
}
|
|
10678
9229
|
async function runIMSuite(cloud) {
|
|
@@ -10746,27 +9297,27 @@ async function runSandboxSuite(cloud, opts) {
|
|
|
10746
9297
|
}
|
|
10747
9298
|
return checks;
|
|
10748
9299
|
}
|
|
10749
|
-
async function requestCheck(cloud, suite, name, method,
|
|
9300
|
+
async function requestCheck(cloud, suite, name, method, path7, body) {
|
|
10750
9301
|
let res;
|
|
10751
9302
|
try {
|
|
10752
|
-
res = await cloud.request(method,
|
|
9303
|
+
res = await cloud.request(method, path7, body === void 0 ? void 0 : { body });
|
|
10753
9304
|
} catch (err) {
|
|
10754
|
-
return { suite, name, status: "fail", endpoint:
|
|
9305
|
+
return { suite, name, status: "fail", endpoint: path7, detail: sanitize(err.message) };
|
|
10755
9306
|
}
|
|
10756
9307
|
if (!res.ok) {
|
|
10757
9308
|
return {
|
|
10758
9309
|
suite,
|
|
10759
9310
|
name,
|
|
10760
9311
|
status: "fail",
|
|
10761
|
-
endpoint:
|
|
9312
|
+
endpoint: path7,
|
|
10762
9313
|
detail: sanitize(`${res.status}: ${res.error?.message ?? "request failed"}`)
|
|
10763
9314
|
};
|
|
10764
9315
|
}
|
|
10765
9316
|
const data = unwrap(res.data);
|
|
10766
9317
|
if (isApiError(res.data)) {
|
|
10767
|
-
return { suite, name, status: "fail", endpoint:
|
|
9318
|
+
return { suite, name, status: "fail", endpoint: path7, detail: sanitize(apiErrorMessage(res.data)), data: res.data };
|
|
10768
9319
|
}
|
|
10769
|
-
return { suite, name, status: "pass", endpoint:
|
|
9320
|
+
return { suite, name, status: "pass", endpoint: path7, detail: summarizeData(data), data };
|
|
10770
9321
|
}
|
|
10771
9322
|
async function pollTask(cloud, taskId, timeoutMs) {
|
|
10772
9323
|
const started = Date.now();
|
|
@@ -10873,7 +9424,7 @@ function taskFrom(data) {
|
|
|
10873
9424
|
function sanitize(message) {
|
|
10874
9425
|
return message.replace(/sk-prismer-[A-Za-z0-9_-]+/g, "sk-prismer-***");
|
|
10875
9426
|
}
|
|
10876
|
-
function
|
|
9427
|
+
function parsePositiveInt2(value) {
|
|
10877
9428
|
const parsed = Number.parseInt(value, 10);
|
|
10878
9429
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
10879
9430
|
throw new Error("expected a positive integer");
|
|
@@ -10884,9 +9435,9 @@ function parsePositiveInt3(value) {
|
|
|
10884
9435
|
// src/cli/commands/daemon.ts
|
|
10885
9436
|
var import_commander8 = require("commander");
|
|
10886
9437
|
var import_node_child_process7 = require("child_process");
|
|
10887
|
-
var
|
|
10888
|
-
var
|
|
10889
|
-
var
|
|
9438
|
+
var import_node_fs15 = require("fs");
|
|
9439
|
+
var import_promises2 = require("timers/promises");
|
|
9440
|
+
var import_node_path11 = require("path");
|
|
10890
9441
|
init_util();
|
|
10891
9442
|
init_ui();
|
|
10892
9443
|
function buildDaemonCommand() {
|
|
@@ -10904,7 +9455,7 @@ function buildDaemonCommand() {
|
|
|
10904
9455
|
async function runForeground(opts) {
|
|
10905
9456
|
const paths = resolvePaths();
|
|
10906
9457
|
const existingPid = readPidFile(paths);
|
|
10907
|
-
if (existingPid &&
|
|
9458
|
+
if (existingPid && pidAlive(existingPid)) {
|
|
10908
9459
|
exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
|
|
10909
9460
|
}
|
|
10910
9461
|
writePidFile(paths, process.pid);
|
|
@@ -10928,7 +9479,7 @@ function buildDaemonCommand() {
|
|
|
10928
9479
|
while (!configExists(paths)) {
|
|
10929
9480
|
if (stopRequested) return;
|
|
10930
9481
|
getUI().info(`[daemon] waiting for ${paths.configFile} \u2014 run \`prismer setup\` to create it`);
|
|
10931
|
-
await (0,
|
|
9482
|
+
await (0, import_promises2.setTimeout)(5e3);
|
|
10932
9483
|
}
|
|
10933
9484
|
runner = new Runner({
|
|
10934
9485
|
startLocalServer: opts.localServer !== false,
|
|
@@ -10957,12 +9508,12 @@ function buildDaemonCommand() {
|
|
|
10957
9508
|
async function startBackground(opts) {
|
|
10958
9509
|
const paths = resolvePaths();
|
|
10959
9510
|
const existingPid = readPidFile(paths);
|
|
10960
|
-
if (existingPid &&
|
|
9511
|
+
if (existingPid && pidAlive(existingPid)) {
|
|
10961
9512
|
exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
|
|
10962
9513
|
}
|
|
10963
|
-
if (!(0,
|
|
10964
|
-
const logFile = (0,
|
|
10965
|
-
const fd = (0,
|
|
9514
|
+
if (!(0, import_node_fs15.existsSync)(paths.logsDir)) (0, import_node_fs15.mkdirSync)(paths.logsDir, { recursive: true });
|
|
9515
|
+
const logFile = (0, import_node_path11.join)(paths.logsDir, "daemon.log");
|
|
9516
|
+
const fd = (0, import_node_fs15.openSync)(logFile, "a");
|
|
10966
9517
|
const args = [process.argv[1], "daemon", "run"];
|
|
10967
9518
|
if (opts.port) args.push("--port", String(opts.port));
|
|
10968
9519
|
if (opts.localServer === false) args.push("--no-local-server");
|
|
@@ -10974,9 +9525,9 @@ function buildDaemonCommand() {
|
|
|
10974
9525
|
child.unref();
|
|
10975
9526
|
const deadline = Date.now() + 5e3;
|
|
10976
9527
|
while (Date.now() < deadline) {
|
|
10977
|
-
await (0,
|
|
9528
|
+
await (0, import_promises2.setTimeout)(200);
|
|
10978
9529
|
const pid = readPidFile(paths);
|
|
10979
|
-
if (pid &&
|
|
9530
|
+
if (pid && pidAlive(pid)) {
|
|
10980
9531
|
if (opts.json) printJson({ ok: true, pid, logFile });
|
|
10981
9532
|
else getUI().ok("Daemon started", `pid ${pid} \xB7 logs ${logFile}`);
|
|
10982
9533
|
return;
|
|
@@ -10991,7 +9542,7 @@ function buildDaemonCommand() {
|
|
|
10991
9542
|
const paths = resolvePaths();
|
|
10992
9543
|
const pid = readPidFile(paths);
|
|
10993
9544
|
if (!pid) exitWithError("No daemon.pid found \u2014 daemon does not appear to be running.");
|
|
10994
|
-
if (!
|
|
9545
|
+
if (!pidAlive(pid)) {
|
|
10995
9546
|
clearPidFile(paths);
|
|
10996
9547
|
getUI().warn("Daemon was not alive", `pid ${pid}; cleared stale pid file`);
|
|
10997
9548
|
return;
|
|
@@ -11003,12 +9554,12 @@ function buildDaemonCommand() {
|
|
|
11003
9554
|
}
|
|
11004
9555
|
const deadline = Date.now() + (opts.timeout ?? 5e3);
|
|
11005
9556
|
while (Date.now() < deadline) {
|
|
11006
|
-
if (!
|
|
9557
|
+
if (!pidAlive(pid)) {
|
|
11007
9558
|
clearPidFile(paths);
|
|
11008
9559
|
getUI().ok("Daemon stopped", `pid ${pid}`);
|
|
11009
9560
|
return;
|
|
11010
9561
|
}
|
|
11011
|
-
await (0,
|
|
9562
|
+
await (0, import_promises2.setTimeout)(200);
|
|
11012
9563
|
}
|
|
11013
9564
|
process.stderr.write(`Daemon did not exit within timeout; pid ${pid} may still be alive.
|
|
11014
9565
|
`);
|
|
@@ -11028,7 +9579,7 @@ function buildDaemonCommand() {
|
|
|
11028
9579
|
cmd.command("status").description("Show daemon process + WS state").option("--json", "Output JSON (default)").action(async () => {
|
|
11029
9580
|
const paths = resolvePaths();
|
|
11030
9581
|
const pid = readPidFile(paths);
|
|
11031
|
-
const alive = pid ?
|
|
9582
|
+
const alive = pid ? pidAlive(pid) : false;
|
|
11032
9583
|
if (!pid || !alive) {
|
|
11033
9584
|
printJson({ running: false, pid: pid ?? null, paths: { config: paths.configFile } });
|
|
11034
9585
|
return;
|
|
@@ -11045,8 +9596,8 @@ function buildDaemonCommand() {
|
|
|
11045
9596
|
});
|
|
11046
9597
|
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) => {
|
|
11047
9598
|
const paths = resolvePaths();
|
|
11048
|
-
const logFile = (0,
|
|
11049
|
-
if (!(0,
|
|
9599
|
+
const logFile = (0, import_node_path11.join)(paths.logsDir, "daemon.log");
|
|
9600
|
+
if (!(0, import_node_fs15.existsSync)(logFile)) {
|
|
11050
9601
|
exitWithError(`No daemon log found at ${logFile}. Start the daemon with \`prismer daemon start\`.`);
|
|
11051
9602
|
}
|
|
11052
9603
|
const lines = Math.max(1, opts.tail);
|
|
@@ -11056,9 +9607,9 @@ function buildDaemonCommand() {
|
|
|
11056
9607
|
});
|
|
11057
9608
|
return cmd;
|
|
11058
9609
|
}
|
|
11059
|
-
async function tailFromEnd(
|
|
9610
|
+
async function tailFromEnd(path7, lines) {
|
|
11060
9611
|
const { open } = await import("fs/promises");
|
|
11061
|
-
const handle = await open(
|
|
9612
|
+
const handle = await open(path7, "r");
|
|
11062
9613
|
try {
|
|
11063
9614
|
const stat = await handle.stat();
|
|
11064
9615
|
let size = stat.size;
|
|
@@ -11086,14 +9637,14 @@ async function tailFromEnd(path9, lines) {
|
|
|
11086
9637
|
await handle.close();
|
|
11087
9638
|
}
|
|
11088
9639
|
}
|
|
11089
|
-
async function followFile(
|
|
11090
|
-
let offset = (0,
|
|
9640
|
+
async function followFile(path7) {
|
|
9641
|
+
let offset = (0, import_node_fs15.statSync)(path7).size;
|
|
11091
9642
|
for (; ; ) {
|
|
11092
|
-
await (0,
|
|
11093
|
-
const size = (0,
|
|
9643
|
+
await (0, import_promises2.setTimeout)(1e3);
|
|
9644
|
+
const size = (0, import_node_fs15.statSync)(path7).size;
|
|
11094
9645
|
if (size < offset) offset = 0;
|
|
11095
9646
|
if (size === offset) continue;
|
|
11096
|
-
const stream = (0,
|
|
9647
|
+
const stream = (0, import_node_fs15.createReadStream)(path7, { start: offset, end: size - 1, encoding: "utf8" });
|
|
11097
9648
|
for await (const chunk of stream) process.stdout.write(chunk);
|
|
11098
9649
|
offset = size;
|
|
11099
9650
|
}
|
|
@@ -11101,16 +9652,16 @@ async function followFile(path9) {
|
|
|
11101
9652
|
|
|
11102
9653
|
// src/cli/commands/events.ts
|
|
11103
9654
|
var import_commander9 = require("commander");
|
|
11104
|
-
var
|
|
11105
|
-
var
|
|
11106
|
-
var
|
|
9655
|
+
var import_node_fs16 = require("fs");
|
|
9656
|
+
var import_node_os7 = require("os");
|
|
9657
|
+
var import_node_path12 = require("path");
|
|
11107
9658
|
var import_node_readline = require("readline");
|
|
11108
9659
|
init_util();
|
|
11109
9660
|
var DEFAULT_LIMIT2 = 50;
|
|
11110
9661
|
function buildEventsCommand() {
|
|
11111
9662
|
return addEventOptions(new import_commander9.Command("events").description("Read local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
|
|
11112
9663
|
const file = eventsPath();
|
|
11113
|
-
if (!(0,
|
|
9664
|
+
if (!(0, import_node_fs16.existsSync)(file)) {
|
|
11114
9665
|
printJson(unavailable(file));
|
|
11115
9666
|
process.exitCode = 1;
|
|
11116
9667
|
return;
|
|
@@ -11127,7 +9678,7 @@ function buildEventsCommand() {
|
|
|
11127
9678
|
function buildEventsStatsCommand() {
|
|
11128
9679
|
return addEventOptions(new import_commander9.Command("events:stats").description("Summarize local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
|
|
11129
9680
|
const file = eventsPath();
|
|
11130
|
-
if (!(0,
|
|
9681
|
+
if (!(0, import_node_fs16.existsSync)(file)) {
|
|
11131
9682
|
printJson(unavailable(file));
|
|
11132
9683
|
process.exitCode = 1;
|
|
11133
9684
|
return;
|
|
@@ -11142,10 +9693,10 @@ function buildEventsStatsCommand() {
|
|
|
11142
9693
|
});
|
|
11143
9694
|
}
|
|
11144
9695
|
function addEventOptions(cmd) {
|
|
11145
|
-
return cmd.option("--limit <n>", "Max events to return/read",
|
|
9696
|
+
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)");
|
|
11146
9697
|
}
|
|
11147
9698
|
function eventsPath() {
|
|
11148
|
-
return (0,
|
|
9699
|
+
return (0, import_node_path12.join)(process.env.PRISMER_HOME ?? (0, import_node_path12.join)((0, import_node_os7.homedir)(), ".prismer"), "para", "events.jsonl");
|
|
11149
9700
|
}
|
|
11150
9701
|
function unavailable(file) {
|
|
11151
9702
|
return {
|
|
@@ -11161,7 +9712,7 @@ function unavailable(file) {
|
|
|
11161
9712
|
async function readEvents(file, filters) {
|
|
11162
9713
|
const out = [];
|
|
11163
9714
|
const rl = (0, import_node_readline.createInterface)({
|
|
11164
|
-
input: (0,
|
|
9715
|
+
input: (0, import_node_fs16.createReadStream)(file, { encoding: "utf8" }),
|
|
11165
9716
|
crlfDelay: Infinity
|
|
11166
9717
|
});
|
|
11167
9718
|
for await (const line of rl) {
|
|
@@ -11223,15 +9774,15 @@ function normalizeFilters(opts) {
|
|
|
11223
9774
|
function cleanFilters(filters) {
|
|
11224
9775
|
return Object.fromEntries(Object.entries(filters).filter(([, value]) => value !== void 0));
|
|
11225
9776
|
}
|
|
11226
|
-
function
|
|
9777
|
+
function parsePositiveInt3(v) {
|
|
11227
9778
|
const n = Number.parseInt(v, 10);
|
|
11228
9779
|
return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMIT2;
|
|
11229
9780
|
}
|
|
11230
9781
|
|
|
11231
9782
|
// src/cli/commands/memory.ts
|
|
11232
|
-
var
|
|
9783
|
+
var import_better_sqlite34 = __toESM(require("better-sqlite3"), 1);
|
|
11233
9784
|
var import_commander10 = require("commander");
|
|
11234
|
-
var
|
|
9785
|
+
var import_node_fs17 = require("fs");
|
|
11235
9786
|
init_util();
|
|
11236
9787
|
var LOCAL_BASE = process.env.PRISMER_DAEMON_URL ?? "http://127.0.0.1:3210";
|
|
11237
9788
|
function buildMemoryCommand() {
|
|
@@ -11257,7 +9808,7 @@ function buildMemoryCommand() {
|
|
|
11257
9808
|
}));
|
|
11258
9809
|
process.exitCode = 1;
|
|
11259
9810
|
});
|
|
11260
|
-
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",
|
|
9811
|
+
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) => {
|
|
11261
9812
|
const limit = clampLimit(opts.limit);
|
|
11262
9813
|
const daemonPaths = [];
|
|
11263
9814
|
if (opts.workspaceId) {
|
|
@@ -11276,7 +9827,7 @@ function buildMemoryCommand() {
|
|
|
11276
9827
|
printJson(unavailable2("list", snapshot, { items: snapshot.items, limit }));
|
|
11277
9828
|
process.exitCode = 1;
|
|
11278
9829
|
});
|
|
11279
|
-
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",
|
|
9830
|
+
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) => {
|
|
11280
9831
|
const query = opts.query ?? argQuery ?? "";
|
|
11281
9832
|
const limit = clampLimit(opts.limit);
|
|
11282
9833
|
const encoded = encodeURIComponent(query);
|
|
@@ -11320,9 +9871,9 @@ function buildMemoryCommand() {
|
|
|
11320
9871
|
}
|
|
11321
9872
|
async function tryDaemon(methods, paths) {
|
|
11322
9873
|
for (const method of methods) {
|
|
11323
|
-
for (const
|
|
9874
|
+
for (const path7 of paths) {
|
|
11324
9875
|
try {
|
|
11325
|
-
const res = await fetch(`${LOCAL_BASE}${
|
|
9876
|
+
const res = await fetch(`${LOCAL_BASE}${path7}`, { method, signal: AbortSignal.timeout(1500) });
|
|
11326
9877
|
if (res.status === 404) continue;
|
|
11327
9878
|
const body = await readJson4(res);
|
|
11328
9879
|
if (!res.ok) {
|
|
@@ -11330,12 +9881,12 @@ async function tryDaemon(methods, paths) {
|
|
|
11330
9881
|
ok: false,
|
|
11331
9882
|
error: {
|
|
11332
9883
|
code: `daemon_http_${res.status}`,
|
|
11333
|
-
message: messageFromBody(body) ?? `Daemon endpoint ${method} ${
|
|
9884
|
+
message: messageFromBody(body) ?? `Daemon endpoint ${method} ${path7} returned HTTP ${res.status}`
|
|
11334
9885
|
},
|
|
11335
|
-
checks: { daemon: { url: LOCAL_BASE, endpoint:
|
|
9886
|
+
checks: { daemon: { url: LOCAL_BASE, endpoint: path7, reachable: true } }
|
|
11336
9887
|
};
|
|
11337
9888
|
}
|
|
11338
|
-
return normalizeDaemonBody(body, { method, path:
|
|
9889
|
+
return normalizeDaemonBody(body, { method, path: path7 });
|
|
11339
9890
|
} catch {
|
|
11340
9891
|
continue;
|
|
11341
9892
|
}
|
|
@@ -11383,7 +9934,7 @@ function readCacheSnapshot(limit) {
|
|
|
11383
9934
|
const paths = resolvePaths();
|
|
11384
9935
|
const empty = {
|
|
11385
9936
|
dbPath: paths.localDb,
|
|
11386
|
-
dbExists: (0,
|
|
9937
|
+
dbExists: (0, import_node_fs17.existsSync)(paths.localDb),
|
|
11387
9938
|
tables: {
|
|
11388
9939
|
cached_assets: { exists: false, count: 0, sizeBytes: 0 },
|
|
11389
9940
|
workspace_files_mirror: { exists: false, count: 0 }
|
|
@@ -11393,7 +9944,7 @@ function readCacheSnapshot(limit) {
|
|
|
11393
9944
|
if (!empty.dbExists) return empty;
|
|
11394
9945
|
let db;
|
|
11395
9946
|
try {
|
|
11396
|
-
db = new
|
|
9947
|
+
db = new import_better_sqlite34.default(paths.localDb, { readonly: true, fileMustExist: true });
|
|
11397
9948
|
const hasAssets = tableExists(db, "cached_assets");
|
|
11398
9949
|
const hasFiles = tableExists(db, "workspace_files_mirror");
|
|
11399
9950
|
const assets = hasAssets ? db.prepare("SELECT COUNT(*) AS count, COALESCE(SUM(size_bytes), 0) AS sizeBytes FROM cached_assets").get() : { count: 0, sizeBytes: 0 };
|
|
@@ -11482,7 +10033,7 @@ function searchableText(item) {
|
|
|
11482
10033
|
item.localPath
|
|
11483
10034
|
].filter(Boolean).join(" ");
|
|
11484
10035
|
}
|
|
11485
|
-
function
|
|
10036
|
+
function parsePositiveInt4(v) {
|
|
11486
10037
|
const n = Number.parseInt(v, 10);
|
|
11487
10038
|
return Number.isFinite(n) && n > 0 ? n : 20;
|
|
11488
10039
|
}
|
|
@@ -11547,9 +10098,9 @@ function buildPairCommand() {
|
|
|
11547
10098
|
|
|
11548
10099
|
// src/cli/commands/profile.ts
|
|
11549
10100
|
var import_commander12 = require("commander");
|
|
11550
|
-
var
|
|
11551
|
-
var
|
|
11552
|
-
var
|
|
10101
|
+
var import_node_fs18 = require("fs");
|
|
10102
|
+
var import_node_os8 = require("os");
|
|
10103
|
+
var import_node_path13 = require("path");
|
|
11553
10104
|
var import_node_child_process8 = require("child_process");
|
|
11554
10105
|
init_util();
|
|
11555
10106
|
function buildProfileCommand() {
|
|
@@ -11597,12 +10148,12 @@ function buildProfileCommand() {
|
|
|
11597
10148
|
const profile = await cloud.get(
|
|
11598
10149
|
`/api/im/agent_profiles/${encodeURIComponent(profileId)}`
|
|
11599
10150
|
);
|
|
11600
|
-
const tmpFile = (0,
|
|
11601
|
-
(0,
|
|
10151
|
+
const tmpFile = (0, import_node_path13.join)((0, import_node_os8.tmpdir)(), `prismer-profile-${profileId}.json`);
|
|
10152
|
+
(0, import_node_fs18.writeFileSync)(tmpFile, JSON.stringify(profile.config, null, 2), "utf8");
|
|
11602
10153
|
const editor = process.env.EDITOR || "vi";
|
|
11603
10154
|
const ed = (0, import_node_child_process8.spawnSync)(editor, [tmpFile], { stdio: "inherit" });
|
|
11604
10155
|
if (ed.status !== 0) exitWithError(`editor exited ${ed.status}`, { code: "editor_failed" });
|
|
11605
|
-
const newConfig = JSON.parse((0,
|
|
10156
|
+
const newConfig = JSON.parse((0, import_node_fs18.readFileSync)(tmpFile, "utf8"));
|
|
11606
10157
|
const res = await cloud.request("PATCH", `/api/im/agent_profiles/${encodeURIComponent(profileId)}`, {
|
|
11607
10158
|
body: { config: newConfig, version: profile.version }
|
|
11608
10159
|
});
|
|
@@ -11623,9 +10174,9 @@ function mkCloud4() {
|
|
|
11623
10174
|
}
|
|
11624
10175
|
function readJsonArg(arg) {
|
|
11625
10176
|
if (arg.startsWith("@")) {
|
|
11626
|
-
const
|
|
11627
|
-
if (!(0,
|
|
11628
|
-
return JSON.parse((0,
|
|
10177
|
+
const path7 = arg.slice(1);
|
|
10178
|
+
if (!(0, import_node_fs18.existsSync)(path7)) throw new Error(`File not found: ${path7}`);
|
|
10179
|
+
return JSON.parse((0, import_node_fs18.readFileSync)(path7, "utf8"));
|
|
11629
10180
|
}
|
|
11630
10181
|
return JSON.parse(arg);
|
|
11631
10182
|
}
|
|
@@ -11636,200 +10187,13 @@ async function resolveDefaultWorkspaceId(cloud) {
|
|
|
11636
10187
|
return def.id;
|
|
11637
10188
|
}
|
|
11638
10189
|
|
|
11639
|
-
// src/cli/commands/reset.ts
|
|
11640
|
-
var import_commander13 = require("commander");
|
|
11641
|
-
var import_node_child_process9 = require("child_process");
|
|
11642
|
-
var import_node_fs22 = require("fs");
|
|
11643
|
-
var import_node_os11 = require("os");
|
|
11644
|
-
var import_node_path17 = require("path");
|
|
11645
|
-
var import_promises4 = require("timers/promises");
|
|
11646
|
-
init_util();
|
|
11647
|
-
init_ui();
|
|
11648
|
-
function buildResetCommand() {
|
|
11649
|
-
return new import_commander13.Command("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) => {
|
|
11650
|
-
const paths = resolvePaths();
|
|
11651
|
-
const pid = readPidFile(paths);
|
|
11652
|
-
const runningPid = pid && pidAlive2(pid) ? pid : null;
|
|
11653
|
-
const archivePath = opts.wipe ? null : `${paths.root}.reset.${timestamp()}`;
|
|
11654
|
-
const assetSyncRoot = resolveAssetSyncRoot();
|
|
11655
|
-
const assetSyncArchivePath = opts.wipe ? null : `${assetSyncRoot}.reset.${timestamp()}`;
|
|
11656
|
-
const hermesHome = resolveHermesHome();
|
|
11657
|
-
const hermesArchivePath = opts.wipe ? null : `${hermesHome}.reset.${timestamp()}`;
|
|
11658
|
-
const hermesGatewayPids = findHermesGatewayPids2();
|
|
11659
|
-
const plan = {
|
|
11660
|
-
root: paths.root,
|
|
11661
|
-
exists: (0, import_node_fs22.existsSync)(paths.root),
|
|
11662
|
-
runningPid,
|
|
11663
|
-
mode: opts.wipe ? "wipe" : "archive",
|
|
11664
|
-
archivePath,
|
|
11665
|
-
assetSyncRoot,
|
|
11666
|
-
assetSyncExists: (0, import_node_fs22.existsSync)(assetSyncRoot),
|
|
11667
|
-
assetSyncArchivePath,
|
|
11668
|
-
hermesHome,
|
|
11669
|
-
hermesExists: (0, import_node_fs22.existsSync)(hermesHome),
|
|
11670
|
-
hermesArchivePath,
|
|
11671
|
-
hermesGatewayPids,
|
|
11672
|
-
willStopDaemon: runningPid !== null,
|
|
11673
|
-
nextStep: "prismer setup"
|
|
11674
|
-
};
|
|
11675
|
-
if (opts.check) {
|
|
11676
|
-
printPlan(plan, opts.json);
|
|
11677
|
-
return;
|
|
11678
|
-
}
|
|
11679
|
-
if (!opts.yes) {
|
|
11680
|
-
exitWithError("Refusing to reset without --yes. Run `prismer reset --check` to preview.", {
|
|
11681
|
-
code: "confirmation_required"
|
|
11682
|
-
});
|
|
11683
|
-
}
|
|
11684
|
-
if (runningPid !== null) {
|
|
11685
|
-
await stopDaemon(runningPid, opts.timeout ?? 5e3);
|
|
11686
|
-
clearPidFile(paths);
|
|
11687
|
-
} else if (pid) {
|
|
11688
|
-
clearPidFile(paths);
|
|
11689
|
-
}
|
|
11690
|
-
for (const gatewayPid of hermesGatewayPids) {
|
|
11691
|
-
await stopProcess(gatewayPid, opts.timeout ?? 5e3, "Hermes gateway");
|
|
11692
|
-
}
|
|
11693
|
-
if ((0, import_node_fs22.existsSync)(paths.root)) {
|
|
11694
|
-
if (opts.wipe) {
|
|
11695
|
-
(0, import_node_fs22.rmSync)(paths.root, { recursive: true, force: true });
|
|
11696
|
-
} else {
|
|
11697
|
-
if (!archivePath) throw new Error("internal: archivePath missing");
|
|
11698
|
-
if (!(0, import_node_fs22.existsSync)((0, import_node_path17.dirname)(archivePath))) {
|
|
11699
|
-
(0, import_node_fs22.mkdirSync)((0, import_node_path17.dirname)(archivePath), { recursive: true });
|
|
11700
|
-
}
|
|
11701
|
-
(0, import_node_fs22.renameSync)(paths.root, archivePath);
|
|
11702
|
-
}
|
|
11703
|
-
}
|
|
11704
|
-
if ((0, import_node_fs22.existsSync)(assetSyncRoot)) {
|
|
11705
|
-
if (opts.wipe) {
|
|
11706
|
-
(0, import_node_fs22.rmSync)(assetSyncRoot, { recursive: true, force: true });
|
|
11707
|
-
} else {
|
|
11708
|
-
if (!assetSyncArchivePath) throw new Error("internal: assetSyncArchivePath missing");
|
|
11709
|
-
if (!(0, import_node_fs22.existsSync)((0, import_node_path17.dirname)(assetSyncArchivePath))) {
|
|
11710
|
-
(0, import_node_fs22.mkdirSync)((0, import_node_path17.dirname)(assetSyncArchivePath), { recursive: true });
|
|
11711
|
-
}
|
|
11712
|
-
(0, import_node_fs22.renameSync)(assetSyncRoot, assetSyncArchivePath);
|
|
11713
|
-
}
|
|
11714
|
-
}
|
|
11715
|
-
if ((0, import_node_fs22.existsSync)(hermesHome)) {
|
|
11716
|
-
if (opts.wipe) {
|
|
11717
|
-
(0, import_node_fs22.rmSync)(hermesHome, { recursive: true, force: true });
|
|
11718
|
-
} else {
|
|
11719
|
-
if (!hermesArchivePath) throw new Error("internal: hermesArchivePath missing");
|
|
11720
|
-
if (!(0, import_node_fs22.existsSync)((0, import_node_path17.dirname)(hermesArchivePath))) {
|
|
11721
|
-
(0, import_node_fs22.mkdirSync)((0, import_node_path17.dirname)(hermesArchivePath), { recursive: true });
|
|
11722
|
-
}
|
|
11723
|
-
(0, import_node_fs22.renameSync)(hermesHome, hermesArchivePath);
|
|
11724
|
-
}
|
|
11725
|
-
}
|
|
11726
|
-
(0, import_node_fs22.mkdirSync)(paths.root, { recursive: true });
|
|
11727
|
-
if (opts.json) {
|
|
11728
|
-
printJson({ ok: true, reset: true, plan });
|
|
11729
|
-
return;
|
|
11730
|
-
}
|
|
11731
|
-
const ui = getUI();
|
|
11732
|
-
ui.ok("Runtime reset complete", paths.root);
|
|
11733
|
-
if (archivePath) ui.line(` archived: ${archivePath}`);
|
|
11734
|
-
if (assetSyncArchivePath && plan.assetSyncExists) ui.line(` asset sync archived: ${assetSyncArchivePath}`);
|
|
11735
|
-
if (hermesArchivePath && plan.hermesExists) ui.line(` Hermes archived: ${hermesArchivePath}`);
|
|
11736
|
-
if (hermesGatewayPids.length > 0) ui.line(` stopped Hermes gateway pid(s): ${hermesGatewayPids.join(", ")}`);
|
|
11737
|
-
ui.line(" next: prismer setup");
|
|
11738
|
-
}, { code: "reset_failed" }));
|
|
11739
|
-
}
|
|
11740
|
-
function printPlan(plan, json) {
|
|
11741
|
-
if (json) {
|
|
11742
|
-
printJson({ ok: true, check: true, plan });
|
|
11743
|
-
return;
|
|
11744
|
-
}
|
|
11745
|
-
const ui = getUI();
|
|
11746
|
-
ui.header("Runtime reset plan");
|
|
11747
|
-
ui.blank();
|
|
11748
|
-
ui.line(` root: ${plan.root}`);
|
|
11749
|
-
ui.line(` exists: ${plan.exists ? "yes" : "no"}`);
|
|
11750
|
-
ui.line(` daemon: ${plan.runningPid ? `running pid ${plan.runningPid}` : "not running"}`);
|
|
11751
|
-
ui.line(` mode: ${plan.mode}`);
|
|
11752
|
-
if (plan.archivePath) ui.line(` archive: ${plan.archivePath}`);
|
|
11753
|
-
ui.line(` asset sync: ${plan.assetSyncExists ? plan.assetSyncRoot : `${plan.assetSyncRoot} (not found)`}`);
|
|
11754
|
-
if (plan.assetSyncArchivePath) ui.line(` asset archive: ${plan.assetSyncArchivePath}`);
|
|
11755
|
-
ui.line(` Hermes: ${plan.hermesExists ? plan.hermesHome : `${plan.hermesHome} (not found)`}`);
|
|
11756
|
-
if (plan.hermesArchivePath) ui.line(` Hermes archive: ${plan.hermesArchivePath}`);
|
|
11757
|
-
ui.line(
|
|
11758
|
-
` Hermes gateway: ${plan.hermesGatewayPids.length > 0 ? `running pid(s) ${plan.hermesGatewayPids.join(", ")}` : "not running"}`
|
|
11759
|
-
);
|
|
11760
|
-
ui.blank();
|
|
11761
|
-
ui.line("Run `prismer reset --yes` to apply, then `prismer setup` to bind a new account.");
|
|
11762
|
-
}
|
|
11763
|
-
async function stopDaemon(pid, timeoutMs) {
|
|
11764
|
-
await stopProcess(pid, timeoutMs, "daemon");
|
|
11765
|
-
}
|
|
11766
|
-
async function stopProcess(pid, timeoutMs, label) {
|
|
11767
|
-
try {
|
|
11768
|
-
process.kill(pid, "SIGTERM");
|
|
11769
|
-
} catch (err) {
|
|
11770
|
-
throw new Error(`SIGTERM failed for ${label} pid ${pid}: ${err.message}`);
|
|
11771
|
-
}
|
|
11772
|
-
const deadline = Date.now() + timeoutMs;
|
|
11773
|
-
while (Date.now() < deadline) {
|
|
11774
|
-
if (!pidAlive2(pid)) return;
|
|
11775
|
-
await (0, import_promises4.setTimeout)(100);
|
|
11776
|
-
}
|
|
11777
|
-
try {
|
|
11778
|
-
process.kill(pid, "SIGKILL");
|
|
11779
|
-
} catch (err) {
|
|
11780
|
-
throw new Error(`SIGKILL failed for ${label} pid ${pid}: ${err.message}`);
|
|
11781
|
-
}
|
|
11782
|
-
const killDeadline = Date.now() + 1e3;
|
|
11783
|
-
while (Date.now() < killDeadline) {
|
|
11784
|
-
if (!pidAlive2(pid)) return;
|
|
11785
|
-
await (0, import_promises4.setTimeout)(100);
|
|
11786
|
-
}
|
|
11787
|
-
throw new Error(`${label} pid ${pid} did not exit within ${timeoutMs + 1e3}ms`);
|
|
11788
|
-
}
|
|
11789
|
-
function timestamp() {
|
|
11790
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
11791
|
-
}
|
|
11792
|
-
function resolveAssetSyncRoot() {
|
|
11793
|
-
const override = process.env.PRISMER_ASSET_SYNC_ROOT;
|
|
11794
|
-
if (override) return override;
|
|
11795
|
-
const home = (0, import_node_os11.homedir)();
|
|
11796
|
-
const desktop = (0, import_node_path17.join)(home, "Desktop");
|
|
11797
|
-
const base = (0, import_node_fs22.existsSync)(desktop) ? desktop : home;
|
|
11798
|
-
return (0, import_node_path17.join)(base, "Prismer Assets");
|
|
11799
|
-
}
|
|
11800
|
-
function resolveHermesHome() {
|
|
11801
|
-
return process.env.HERMES_HOME || (0, import_node_path17.join)((0, import_node_os11.homedir)(), ".hermes");
|
|
11802
|
-
}
|
|
11803
|
-
function findHermesGatewayPids2() {
|
|
11804
|
-
try {
|
|
11805
|
-
const out = (0, import_node_child_process9.execFileSync)("ps", ["-axo", "pid=,command="], { encoding: "utf8" });
|
|
11806
|
-
const current = process.pid;
|
|
11807
|
-
return out.split(/\r?\n/).map((line) => {
|
|
11808
|
-
const trimmed = line.trim();
|
|
11809
|
-
const match = /^(\d+)\s+(.+)$/.exec(trimmed);
|
|
11810
|
-
if (!match) return null;
|
|
11811
|
-
const pid = Number.parseInt(match[1], 10);
|
|
11812
|
-
const command = match[2];
|
|
11813
|
-
if (!Number.isFinite(pid) || pid === current) return null;
|
|
11814
|
-
if (!isHermesGatewayCommand2(command)) return null;
|
|
11815
|
-
return pid;
|
|
11816
|
-
}).filter((pid) => pid !== null);
|
|
11817
|
-
} catch {
|
|
11818
|
-
return [];
|
|
11819
|
-
}
|
|
11820
|
-
}
|
|
11821
|
-
function isHermesGatewayCommand2(command) {
|
|
11822
|
-
if (!/\bgateway\b/.test(command) || !/\brun\b/.test(command)) return false;
|
|
11823
|
-
return /(^|\s)(?:\S*\/)?hermes(?:\s|$)/.test(command);
|
|
11824
|
-
}
|
|
11825
|
-
|
|
11826
10190
|
// src/cli/commands/sandbox.ts
|
|
11827
|
-
var
|
|
10191
|
+
var import_commander13 = require("commander");
|
|
11828
10192
|
init_util();
|
|
11829
10193
|
init_ui();
|
|
11830
10194
|
function buildSandboxCommand() {
|
|
11831
|
-
const cmd = new
|
|
11832
|
-
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",
|
|
10195
|
+
const cmd = new import_commander13.Command("sandbox").description("Inspect and smoke-test sandbox lifecycle");
|
|
10196
|
+
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) => {
|
|
11833
10197
|
const query = new URLSearchParams({ workspaceId: opts.workspaceId, limit: String(opts.limit) });
|
|
11834
10198
|
if (opts.status) query.set("status", opts.status);
|
|
11835
10199
|
const body = await cloudRequest("GET", `/api/sandboxes?${query.toString()}`);
|
|
@@ -11888,7 +10252,7 @@ function buildSandboxCommand() {
|
|
|
11888
10252
|
}
|
|
11889
10253
|
printSnapshotResult(id, body);
|
|
11890
10254
|
}, { code: "sandbox_snapshot_failed" }));
|
|
11891
|
-
cmd.command("runCmd <id> <command...>").description("Run a command in a sandbox container").option("--timeout-ms <ms>", "Command timeout",
|
|
10255
|
+
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) => {
|
|
11892
10256
|
if (!Array.isArray(command) || command.length === 0) exitWithError("runCmd requires a command after --", { code: "invalid_argument" });
|
|
11893
10257
|
const body = await cloudRequest("POST", `/api/sandboxes/${encodeURIComponent(id)}/runCmd`, {
|
|
11894
10258
|
command,
|
|
@@ -11929,12 +10293,12 @@ function errorMessage2(raw) {
|
|
|
11929
10293
|
if (typeof raw.error === "string") return raw.error;
|
|
11930
10294
|
return raw.error?.message ?? "request failed";
|
|
11931
10295
|
}
|
|
11932
|
-
async function cloudRequest(method,
|
|
10296
|
+
async function cloudRequest(method, path7, body) {
|
|
11933
10297
|
const cloud = mkCloud5();
|
|
11934
|
-
const res = await cloud.request(method,
|
|
11935
|
-
if (!res.ok) exitWithError(`${method} ${
|
|
10298
|
+
const res = await cloud.request(method, path7, body === void 0 ? void 0 : { body });
|
|
10299
|
+
if (!res.ok) exitWithError(`${method} ${path7} failed (${res.status}): ${res.error?.message ?? "request failed"}`);
|
|
11936
10300
|
const responseBody = res.data;
|
|
11937
|
-
if (isErrorEnvelope2(responseBody)) exitWithError(`${method} ${
|
|
10301
|
+
if (isErrorEnvelope2(responseBody)) exitWithError(`${method} ${path7} failed (${res.status}): ${errorMessage2(responseBody)}`);
|
|
11938
10302
|
return responseBody;
|
|
11939
10303
|
}
|
|
11940
10304
|
function printSandboxList(raw) {
|
|
@@ -12009,7 +10373,7 @@ function writeLine2(label, value) {
|
|
|
12009
10373
|
if (value === void 0 || value === null || value === "") return;
|
|
12010
10374
|
getUI().line(` ${label}: ${String(value)}`);
|
|
12011
10375
|
}
|
|
12012
|
-
function
|
|
10376
|
+
function parsePositiveInt5(value) {
|
|
12013
10377
|
const parsed = Number.parseInt(value, 10);
|
|
12014
10378
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
12015
10379
|
throw new Error("expected a positive integer");
|
|
@@ -12027,16 +10391,16 @@ async function safeParseResponse(res) {
|
|
|
12027
10391
|
}
|
|
12028
10392
|
|
|
12029
10393
|
// src/cli/commands/setup.ts
|
|
12030
|
-
var
|
|
12031
|
-
var
|
|
12032
|
-
var
|
|
12033
|
-
var
|
|
12034
|
-
var
|
|
10394
|
+
var import_commander14 = require("commander");
|
|
10395
|
+
var import_node_os9 = require("os");
|
|
10396
|
+
var import_node_child_process9 = require("child_process");
|
|
10397
|
+
var import_node_crypto6 = require("crypto");
|
|
10398
|
+
var import_node_fs19 = require("fs");
|
|
12035
10399
|
var import_node_http2 = require("http");
|
|
12036
10400
|
init_util();
|
|
12037
10401
|
init_ui();
|
|
12038
10402
|
function buildSetupCommand() {
|
|
12039
|
-
return new
|
|
10403
|
+
return new import_commander14.Command("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(
|
|
12040
10404
|
"--cloud <url>",
|
|
12041
10405
|
`Cloud base URL (default: ${DEFAULT_CLOUD_BASE_URL}; bare host:port is auto-prefixed http://)`,
|
|
12042
10406
|
(v) => v
|
|
@@ -12049,7 +10413,7 @@ function buildSetupCommand() {
|
|
|
12049
10413
|
} catch (err) {
|
|
12050
10414
|
exitWithError(err.message, { code: "invalid_cloud_url" });
|
|
12051
10415
|
}
|
|
12052
|
-
let apiKey = apiKeyArg ??
|
|
10416
|
+
let apiKey = apiKeyArg ?? process.env.PRISMER_API_KEY;
|
|
12053
10417
|
const authToken = opts.token ?? process.env.PRISMER_AUTH_TOKEN;
|
|
12054
10418
|
if (!opts.json) {
|
|
12055
10419
|
printBanner({ compact: true });
|
|
@@ -12057,6 +10421,7 @@ function buildSetupCommand() {
|
|
|
12057
10421
|
getUI().blank();
|
|
12058
10422
|
}
|
|
12059
10423
|
const shouldStart = opts.start !== false;
|
|
10424
|
+
stopRunningDaemon(paths);
|
|
12060
10425
|
if (opts.pair || opts.asUser) {
|
|
12061
10426
|
if (!opts.json) warn("Legacy pair setup path", "plain `prismer setup --start` is the canonical runtime binding flow");
|
|
12062
10427
|
if (opts.asUser && process.env.LOCAL_ONLY !== "1") {
|
|
@@ -12073,7 +10438,7 @@ function buildSetupCommand() {
|
|
|
12073
10438
|
}
|
|
12074
10439
|
const result = await pair({
|
|
12075
10440
|
cloudBaseUrl,
|
|
12076
|
-
deviceName: opts.deviceName ?? (0,
|
|
10441
|
+
deviceName: opts.deviceName ?? (0, import_node_os9.hostname)(),
|
|
12077
10442
|
force: opts.force,
|
|
12078
10443
|
paths,
|
|
12079
10444
|
asUserEmail: opts.asUser
|
|
@@ -12090,13 +10455,13 @@ function buildSetupCommand() {
|
|
|
12090
10455
|
apiKey = await mintDaemonApiKey({
|
|
12091
10456
|
cloudBaseUrl,
|
|
12092
10457
|
token: authToken,
|
|
12093
|
-
deviceName: opts.deviceName ?? (0,
|
|
10458
|
+
deviceName: opts.deviceName ?? (0, import_node_os9.hostname)()
|
|
12094
10459
|
});
|
|
12095
10460
|
}
|
|
12096
10461
|
if (!apiKey && !authToken && !opts.check && opts.browser !== false) {
|
|
12097
10462
|
apiKey = await runBrowserSetup({
|
|
12098
10463
|
cloudBaseUrl,
|
|
12099
|
-
deviceName: opts.deviceName ?? (0,
|
|
10464
|
+
deviceName: opts.deviceName ?? (0, import_node_os9.hostname)(),
|
|
12100
10465
|
json: Boolean(opts.json)
|
|
12101
10466
|
});
|
|
12102
10467
|
}
|
|
@@ -12147,11 +10512,10 @@ function buildSetupCommand() {
|
|
|
12147
10512
|
return;
|
|
12148
10513
|
}
|
|
12149
10514
|
const previousConfig = configExists(paths) ? loadConfig(paths) : null;
|
|
12150
|
-
const daemonId = previousConfig && previousConfig.api_key === apiKey && previousConfig.cloud_api_base === cloudBaseUrl ? previousConfig.daemon_id : newDaemonId();
|
|
12151
10515
|
const config = {
|
|
12152
10516
|
api_key: apiKey,
|
|
12153
10517
|
cloud_api_base: cloudBaseUrl,
|
|
12154
|
-
daemon_id:
|
|
10518
|
+
daemon_id: newDaemonId()
|
|
12155
10519
|
};
|
|
12156
10520
|
if (previousConfig && shouldArchiveLocalDb(previousConfig, config)) {
|
|
12157
10521
|
archiveLocalDb(paths.localDb);
|
|
@@ -12181,7 +10545,7 @@ function buildSetupCommand() {
|
|
|
12181
10545
|
}, { code: "setup_failed" }));
|
|
12182
10546
|
}
|
|
12183
10547
|
async function runBrowserSetup(input) {
|
|
12184
|
-
const state = (0,
|
|
10548
|
+
const state = (0, import_node_crypto6.randomBytes)(18).toString("base64url");
|
|
12185
10549
|
const server = (0, import_node_http2.createServer)();
|
|
12186
10550
|
const apiKeyPromise = waitForSetupCallback(server, state);
|
|
12187
10551
|
await new Promise((resolve3, reject) => {
|
|
@@ -12244,7 +10608,7 @@ function waitForSetupCallback(server, expectedState) {
|
|
|
12244
10608
|
function openBrowser(url) {
|
|
12245
10609
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
12246
10610
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
12247
|
-
const child = (0,
|
|
10611
|
+
const child = (0, import_node_child_process9.spawn)(command, args, { stdio: "ignore", detached: true });
|
|
12248
10612
|
child.on("error", () => {
|
|
12249
10613
|
});
|
|
12250
10614
|
child.unref();
|
|
@@ -12271,20 +10635,42 @@ function withTimeout(promise, ms, message) {
|
|
|
12271
10635
|
});
|
|
12272
10636
|
}
|
|
12273
10637
|
function startDaemonDetached(home) {
|
|
12274
|
-
const child = (0,
|
|
10638
|
+
const child = (0, import_node_child_process9.spawn)(process.execPath, [process.argv[1], "daemon", "start"], {
|
|
12275
10639
|
env: { ...process.env, PRISMER_HOME: home },
|
|
12276
10640
|
stdio: "ignore",
|
|
12277
10641
|
detached: true
|
|
12278
10642
|
});
|
|
12279
10643
|
child.unref();
|
|
12280
10644
|
}
|
|
10645
|
+
function stopRunningDaemon(paths) {
|
|
10646
|
+
const pid = readPidFile(paths);
|
|
10647
|
+
if (!pid || !pidAlive(pid)) {
|
|
10648
|
+
if (pid) clearPidFile(paths);
|
|
10649
|
+
return;
|
|
10650
|
+
}
|
|
10651
|
+
try {
|
|
10652
|
+
process.kill(pid, "SIGTERM");
|
|
10653
|
+
} catch {
|
|
10654
|
+
return;
|
|
10655
|
+
}
|
|
10656
|
+
const deadline = Date.now() + 5e3;
|
|
10657
|
+
while (Date.now() < deadline) {
|
|
10658
|
+
if (!pidAlive(pid)) {
|
|
10659
|
+
clearPidFile(paths);
|
|
10660
|
+
return;
|
|
10661
|
+
}
|
|
10662
|
+
const start = Date.now();
|
|
10663
|
+
while (Date.now() - start < 200) {
|
|
10664
|
+
}
|
|
10665
|
+
}
|
|
10666
|
+
}
|
|
12281
10667
|
function shouldArchiveLocalDb(previous, next) {
|
|
12282
10668
|
return previous.api_key !== next.api_key || previous.cloud_api_base !== next.cloud_api_base || previous.daemon_id !== next.daemon_id;
|
|
12283
10669
|
}
|
|
12284
10670
|
function archiveLocalDb(localDbPath) {
|
|
12285
|
-
if (!(0,
|
|
10671
|
+
if (!(0, import_node_fs19.existsSync)(localDbPath)) return;
|
|
12286
10672
|
const archived = `${localDbPath}.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
12287
|
-
(0,
|
|
10673
|
+
(0, import_node_fs19.renameSync)(localDbPath, archived);
|
|
12288
10674
|
}
|
|
12289
10675
|
async function mintDaemonApiKey(input) {
|
|
12290
10676
|
const label = `Daemon: ${input.deviceName} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
@@ -12304,12 +10690,11 @@ async function mintDaemonApiKey(input) {
|
|
|
12304
10690
|
}
|
|
12305
10691
|
|
|
12306
10692
|
// src/cli/commands/status.ts
|
|
12307
|
-
var
|
|
12308
|
-
var import_node_os13 = require("os");
|
|
10693
|
+
var import_commander15 = require("commander");
|
|
12309
10694
|
init_util();
|
|
12310
10695
|
init_ui();
|
|
12311
10696
|
function buildStatusCommand() {
|
|
12312
|
-
return new
|
|
10697
|
+
return new import_commander15.Command("status").description("Show daemon + config + cloud status").option("--json", "Output machine-readable JSON").action(async (opts) => {
|
|
12313
10698
|
const paths = resolvePaths();
|
|
12314
10699
|
if (!configExists(paths)) {
|
|
12315
10700
|
const report2 = { configured: false, paired: false, paths: { config: paths.configFile } };
|
|
@@ -12377,7 +10762,11 @@ function buildStatusCommand() {
|
|
|
12377
10762
|
info: daemonStatus.info ?? {}
|
|
12378
10763
|
},
|
|
12379
10764
|
cloud: { base: cfg.cloud_api_base, reachable: cloudOk, me, devices, agents },
|
|
12380
|
-
local
|
|
10765
|
+
local,
|
|
10766
|
+
binding: {
|
|
10767
|
+
daemonId: cfg.daemon_id,
|
|
10768
|
+
apiKey: cfg.api_key
|
|
10769
|
+
}
|
|
12381
10770
|
};
|
|
12382
10771
|
if (opts.json) {
|
|
12383
10772
|
printJson(report);
|
|
@@ -12400,8 +10789,8 @@ async function readDaemonStatus() {
|
|
|
12400
10789
|
const paths = resolvePaths();
|
|
12401
10790
|
const pid = readPidFile(paths);
|
|
12402
10791
|
if (pid) {
|
|
12403
|
-
const { pidAlive:
|
|
12404
|
-
if (
|
|
10792
|
+
const { pidAlive: pidAlive2 } = await Promise.resolve().then(() => (init_util(), util_exports));
|
|
10793
|
+
if (pidAlive2(pid)) return { running: true, pid };
|
|
12405
10794
|
}
|
|
12406
10795
|
return { running: false };
|
|
12407
10796
|
}
|
|
@@ -12429,9 +10818,8 @@ function printPretty(report) {
|
|
|
12429
10818
|
ui.blank();
|
|
12430
10819
|
ok("Config", report.paths.config);
|
|
12431
10820
|
if (report.daemon.running) {
|
|
12432
|
-
const daemonName = report.cloud.me && typeof report.cloud.me === "object" ? report.cloud.me.user?.username ?? (0, import_node_os13.hostname)() : (0, import_node_os13.hostname)();
|
|
12433
10821
|
const ws = report.daemon.wsConnected ? "connected" : "pending";
|
|
12434
|
-
ok("Daemon",
|
|
10822
|
+
ok("Daemon", `pid=${report.daemon.pid} ws=${ws}`);
|
|
12435
10823
|
if (report.daemon.info) {
|
|
12436
10824
|
const info2 = report.daemon.info;
|
|
12437
10825
|
if (info2.version) ui.line(` Version: ${info2.version}`);
|
|
@@ -12446,7 +10834,8 @@ function printPretty(report) {
|
|
|
12446
10834
|
ok("Cloud", report.cloud.base);
|
|
12447
10835
|
const me = report.cloud.me;
|
|
12448
10836
|
if (me?.user) {
|
|
12449
|
-
|
|
10837
|
+
const roleTag = me.user.role ? ` role=${me.user.role}` : "";
|
|
10838
|
+
ui.line(` Account: ${me.user.displayName ?? me.user.username ?? "?"}${roleTag}`);
|
|
12450
10839
|
}
|
|
12451
10840
|
if (me?.credits) {
|
|
12452
10841
|
ui.line(` Credits: ${typeof me.credits.balance === "number" ? me.credits.balance.toLocaleString() : "?"}`);
|
|
@@ -12455,6 +10844,12 @@ function printPretty(report) {
|
|
|
12455
10844
|
fail2("Cloud", `${report.cloud.base} unreachable or unauthorized`);
|
|
12456
10845
|
tip("prismer setup --force");
|
|
12457
10846
|
}
|
|
10847
|
+
if (report.binding) {
|
|
10848
|
+
ui.blank();
|
|
10849
|
+
ui.line(` Daemon ID: ${report.binding.daemonId}`);
|
|
10850
|
+
const masked = report.binding.apiKey.slice(0, 14) + "\u2022\u2022\u2022\u2022" + report.binding.apiKey.slice(-4);
|
|
10851
|
+
ui.line(` API Key: ${masked}`);
|
|
10852
|
+
}
|
|
12458
10853
|
if (Array.isArray(report.cloud.devices) && report.cloud.devices.length > 0) {
|
|
12459
10854
|
const devs = report.cloud.devices;
|
|
12460
10855
|
ui.blank();
|
|
@@ -12465,6 +10860,8 @@ function printPretty(report) {
|
|
|
12465
10860
|
const declared = d.hostedAgentSummary?.declared ?? 0;
|
|
12466
10861
|
ui.line(` ${statusIcon} ${d.podName?.replace(/^daemon:/, "")?.slice(0, 28) ?? "?"} kind=${kind} agents=${declared}`);
|
|
12467
10862
|
}
|
|
10863
|
+
} else {
|
|
10864
|
+
ui.line(` Workspace Devices: none`);
|
|
12468
10865
|
}
|
|
12469
10866
|
if (Array.isArray(report.cloud.agents) && report.cloud.agents.length > 0) {
|
|
12470
10867
|
ui.line(` Hosted agents: ${report.cloud.agents.length}`);
|
|
@@ -12480,14 +10877,14 @@ function printPretty(report) {
|
|
|
12480
10877
|
}
|
|
12481
10878
|
|
|
12482
10879
|
// src/cli/commands/task.ts
|
|
12483
|
-
var
|
|
12484
|
-
var
|
|
10880
|
+
var import_commander16 = require("commander");
|
|
10881
|
+
var import_promises3 = require("timers/promises");
|
|
12485
10882
|
init_util();
|
|
12486
10883
|
function describeStatus(status) {
|
|
12487
10884
|
return status === 0 ? "network error" : `HTTP ${status}`;
|
|
12488
10885
|
}
|
|
12489
10886
|
function buildTaskCommand() {
|
|
12490
|
-
const cmd = new
|
|
10887
|
+
const cmd = new import_commander16.Command("task").description("Manage tasks");
|
|
12491
10888
|
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) => {
|
|
12492
10889
|
const cloud = mkCloud6();
|
|
12493
10890
|
const body = {
|
|
@@ -12564,7 +10961,7 @@ function unwrap2(raw) {
|
|
|
12564
10961
|
async function pollUntilTerminal(cloud, taskId, timeoutMs) {
|
|
12565
10962
|
const deadline = Date.now() + (timeoutMs ?? 5 * 6e4);
|
|
12566
10963
|
while (Date.now() < deadline) {
|
|
12567
|
-
await (0,
|
|
10964
|
+
await (0, import_promises3.setTimeout)(1e3);
|
|
12568
10965
|
const res = await cloud.request(
|
|
12569
10966
|
"GET",
|
|
12570
10967
|
`/api/im/tasks/${encodeURIComponent(taskId)}`
|
|
@@ -12601,11 +10998,11 @@ function taskFrom2(raw) {
|
|
|
12601
10998
|
}
|
|
12602
10999
|
|
|
12603
11000
|
// src/cli/commands/workspace.ts
|
|
12604
|
-
var
|
|
11001
|
+
var import_commander17 = require("commander");
|
|
12605
11002
|
init_util();
|
|
12606
11003
|
init_ui();
|
|
12607
11004
|
function buildWorkspaceCommand() {
|
|
12608
|
-
const cmd = new
|
|
11005
|
+
const cmd = new import_commander17.Command("workspace").description("Manage workspaces, runtime snapshots, and workspace files");
|
|
12609
11006
|
cmd.command("list").description("List workspaces").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
|
|
12610
11007
|
const cloud = mkCloud7();
|
|
12611
11008
|
const data = await cloud.get("/api/im/workspaces");
|
|
@@ -12839,15 +11236,18 @@ async function readResponseError(res) {
|
|
|
12839
11236
|
|
|
12840
11237
|
// src/cli/index.ts
|
|
12841
11238
|
init_ui();
|
|
12842
|
-
|
|
11239
|
+
|
|
11240
|
+
// package.json
|
|
11241
|
+
var version = "1.9.22";
|
|
11242
|
+
|
|
11243
|
+
// src/cli/index.ts
|
|
12843
11244
|
function buildProgram() {
|
|
12844
|
-
const program = new
|
|
11245
|
+
const program = new import_commander18.Command("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(version);
|
|
12845
11246
|
program.addCommand(buildBannerCommand());
|
|
12846
11247
|
program.addCommand(buildSetupCommand());
|
|
12847
11248
|
program.addCommand(buildConfigCommand());
|
|
12848
11249
|
program.addCommand(buildDaemonCommand());
|
|
12849
11250
|
program.addCommand(buildPairCommand());
|
|
12850
|
-
program.addCommand(buildResetCommand());
|
|
12851
11251
|
program.addCommand(buildStatusCommand());
|
|
12852
11252
|
program.addCommand(buildAdapterCommand());
|
|
12853
11253
|
program.addCommand(buildAgentCommand());
|