@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/cli.cjs
CHANGED
|
@@ -33,7 +33,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
// src/cli/ui.ts
|
|
34
34
|
function thisDirname() {
|
|
35
35
|
try {
|
|
36
|
-
return path.dirname((0,
|
|
36
|
+
return path.dirname((0, import_node_url.fileURLToPath)(import_meta2.url));
|
|
37
37
|
} catch {
|
|
38
38
|
return process.cwd();
|
|
39
39
|
}
|
|
@@ -95,13 +95,13 @@ function applyCommonFlags(argv) {
|
|
|
95
95
|
}
|
|
96
96
|
return { mode, color: color2, restArgv: rest };
|
|
97
97
|
}
|
|
98
|
-
var fs, path,
|
|
98
|
+
var fs, path, 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
102
|
fs = __toESM(require("fs"), 1);
|
|
103
103
|
path = __toESM(require("path"), 1);
|
|
104
|
-
|
|
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"];
|
|
@@ -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,7 +573,7 @@ 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_path3.join)(paths.root, "daemon.pid");
|
|
577
577
|
}
|
|
578
578
|
function writePidFile(paths, pid) {
|
|
579
579
|
(0, import_node_fs3.writeFileSync)(pidFilePath(paths), `${pid}
|
|
@@ -595,7 +595,7 @@ function clearPidFile(paths) {
|
|
|
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 import_node_fs3,
|
|
606
|
+
var import_node_fs3, import_node_path3, DEFAULT_CLOUD_BASE_URL, ANSI;
|
|
607
607
|
var init_util = __esm({
|
|
608
608
|
"src/cli/util.ts"() {
|
|
609
609
|
"use strict";
|
|
610
610
|
import_node_fs3 = require("fs");
|
|
611
|
-
|
|
611
|
+
import_node_path3 = require("path");
|
|
612
612
|
init_ui();
|
|
613
613
|
DEFAULT_CLOUD_BASE_URL = "https://prismer.cloud";
|
|
614
614
|
ANSI = {
|
|
@@ -624,13 +624,13 @@ var init_util = __esm({
|
|
|
624
624
|
});
|
|
625
625
|
|
|
626
626
|
// src/cli/index.ts
|
|
627
|
-
var
|
|
627
|
+
var import_commander18 = require("commander");
|
|
628
628
|
|
|
629
629
|
// src/cli/commands/adapter.ts
|
|
630
630
|
var import_node_child_process4 = require("child_process");
|
|
631
631
|
var import_node_fs4 = require("fs");
|
|
632
|
-
var
|
|
633
|
-
var
|
|
632
|
+
var import_node_os3 = require("os");
|
|
633
|
+
var import_node_path4 = require("path");
|
|
634
634
|
var import_commander = require("commander");
|
|
635
635
|
|
|
636
636
|
// src/adapters/claude-code/index.ts
|
|
@@ -1150,35 +1150,14 @@ var codexAdapter = {
|
|
|
1150
1150
|
// src/adapters/hermes/index.ts
|
|
1151
1151
|
var import_node_child_process3 = require("child_process");
|
|
1152
1152
|
var import_node_fs = require("fs");
|
|
1153
|
-
var import_node_module = require("module");
|
|
1154
1153
|
var import_node_os = require("os");
|
|
1155
1154
|
var import_node_path = require("path");
|
|
1156
|
-
var import_node_url = require("url");
|
|
1157
1155
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
1158
1156
|
var YAML = __toESM(require("yaml"), 1);
|
|
1159
1157
|
var import_zod3 = require("zod");
|
|
1160
1158
|
var import_meta = {};
|
|
1161
1159
|
var PRISMER_IM_SKILL_NAME = "prismer-im-collab";
|
|
1162
|
-
var PRISMER_IM_SKILL_CONTENT =
|
|
1163
|
-
name: prismer-im-collab
|
|
1164
|
-
description: Coordinate reliably in Prismer conversations, use workspace assets through bounded MCP tools, and keep task work on the board.
|
|
1165
|
-
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
|
|
1166
|
-
metadata:
|
|
1167
|
-
prismer:
|
|
1168
|
-
version: "1.0.0"
|
|
1169
|
-
mcp_tools:
|
|
1170
|
-
- prismer.agent.send
|
|
1171
|
-
- prismer.conversation.listAgents
|
|
1172
|
-
- prismer.task.create
|
|
1173
|
-
- prismer.task.update
|
|
1174
|
-
- prismer.task.complete
|
|
1175
|
-
- prismer.approval.request_human_approval
|
|
1176
|
-
- prismer.asset.search
|
|
1177
|
-
- prismer.asset.describe
|
|
1178
|
-
- prismer.asset.read
|
|
1179
|
-
---
|
|
1180
|
-
|
|
1181
|
-
# Prismer IM Collaboration
|
|
1160
|
+
var PRISMER_IM_SKILL_CONTENT = `# Prismer IM Collaboration
|
|
1182
1161
|
|
|
1183
1162
|
You are an agent in a Prismer multi-agent workspace. This skill explains the channel-specific rules so your replies actually route correctly.
|
|
1184
1163
|
|
|
@@ -1208,19 +1187,6 @@ You will receive a \`[Channel context]\` block at the top of each prompt indicat
|
|
|
1208
1187
|
| Address another agent so the platform dispatches your message to them | Call \`prismer.agent.send\` (from the \`prismer-tasks\` MCP server). |
|
|
1209
1188
|
| Look up which agents you can talk to | Call \`prismer.conversation.listAgents\` before guessing usernames. |
|
|
1210
1189
|
| Create / update tasks on the workspace board | \`prismer.task.create\` / \`prismer.task.update\` from \`prismer-tasks\` MCP. |
|
|
1211
|
-
| Ask for structured human sign-off | \`prismer.approval.request_human_approval\`. |
|
|
1212
|
-
| Find uploaded workspace files | \`prismer.asset.search\`. |
|
|
1213
|
-
| Inspect file metadata before reading bytes | \`prismer.asset.describe\`. |
|
|
1214
|
-
| Read a bounded byte range from a file | \`prismer.asset.read\`. |
|
|
1215
|
-
|
|
1216
|
-
## Workspace asset rules
|
|
1217
|
-
|
|
1218
|
-
- Web-uploaded workspace files are available as asset metadata first; bytes are fetched lazily by the local daemon.
|
|
1219
|
-
- When a task needs file content, call \`prismer.asset.describe\` first, then read only the needed range with \`prismer.asset.read\`.
|
|
1220
|
-
- For many files, call \`prismer.asset.search\` to find candidates. Do not ask the user to paste file contents already present in workspace assets.
|
|
1221
|
-
- Do not claim you read a file unless an asset tool call succeeded.
|
|
1222
|
-
- For large tables/documents, read headers, samples, or targeted byte ranges. Never try to load the whole file into a reply.
|
|
1223
|
-
- Cite the asset id/URI and locator returned by the tool when using file evidence.
|
|
1224
1190
|
|
|
1225
1191
|
### When to use \`prismer.agent.send\` vs inline \`@\`
|
|
1226
1192
|
|
|
@@ -1234,7 +1200,6 @@ You will receive a \`[Channel context]\` block at the top of each prompt indicat
|
|
|
1234
1200
|
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 @).
|
|
1235
1201
|
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.
|
|
1236
1202
|
4. **Check tool return values.** \`prismer.agent.send\` may return \`{ ok: false, error: 'agent_not_found' }\`; surface the error rather than pretending it worked.
|
|
1237
|
-
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.
|
|
1238
1203
|
|
|
1239
1204
|
## Example: group discussion handoff
|
|
1240
1205
|
|
|
@@ -1322,23 +1287,7 @@ var HermesProfileConfigSchema = import_zod3.z.object({
|
|
|
1322
1287
|
hermesSourceDir: import_zod3.z.string().optional(),
|
|
1323
1288
|
nativeMirrorTimeoutMs: import_zod3.z.number().int().positive().default(2e3),
|
|
1324
1289
|
/** Task authority level: executor (default) or orchestrator. */
|
|
1325
|
-
taskAuthority: import_zod3.z.enum(["executor", "orchestrator"]).optional().default("executor")
|
|
1326
|
-
/** Human approval escalation policy. Enforcement is currently prompt/cloud-side. */
|
|
1327
|
-
approvalPolicy: import_zod3.z.enum(["strict", "auto-low-risk", "autonomous"]).optional().default("auto-low-risk"),
|
|
1328
|
-
/** Agent-level MCP tool allowlist. Empty/null means all tools. Supports exact names and trailing * wildcards. */
|
|
1329
|
-
mcpAllowlist: import_zod3.z.array(import_zod3.z.string()).nullable().optional(),
|
|
1330
|
-
/** Agent-level operating principles injected through /v1/runs instructions. */
|
|
1331
|
-
operatingPrinciples: import_zod3.z.union([import_zod3.z.string(), import_zod3.z.record(import_zod3.z.string())]).optional(),
|
|
1332
|
-
/**
|
|
1333
|
-
* Role-template snapshot carried in AgentProfile.config by cloud ACP.
|
|
1334
|
-
* Expected fields used here:
|
|
1335
|
-
* - roleTemplate.mcpServers[].toolsAllowlist
|
|
1336
|
-
* - roleTemplate.operatingPrinciples
|
|
1337
|
-
*/
|
|
1338
|
-
roleTemplate: import_zod3.z.object({
|
|
1339
|
-
mcpServers: import_zod3.z.array(import_zod3.z.object({ toolsAllowlist: import_zod3.z.array(import_zod3.z.string()).optional() }).passthrough()).optional(),
|
|
1340
|
-
operatingPrinciples: import_zod3.z.union([import_zod3.z.string(), import_zod3.z.record(import_zod3.z.string())]).optional()
|
|
1341
|
-
}).passthrough().optional()
|
|
1290
|
+
taskAuthority: import_zod3.z.enum(["executor", "orchestrator"]).optional().default("executor")
|
|
1342
1291
|
});
|
|
1343
1292
|
var hermesAdapter = {
|
|
1344
1293
|
name: "hermes",
|
|
@@ -1353,22 +1302,14 @@ var hermesAdapter = {
|
|
|
1353
1302
|
errors: r.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
|
|
1354
1303
|
};
|
|
1355
1304
|
},
|
|
1356
|
-
async prepareProfile(profile) {
|
|
1357
|
-
const config = HermesProfileConfigSchema.parse(profile.config);
|
|
1358
|
-
if (config.configurePrismerProvider) {
|
|
1359
|
-
const profileName = getHermesProfileName(profile);
|
|
1360
|
-
const agentUsername = profile.agentUsername || profileName;
|
|
1361
|
-
configurePrismerProvider(profileName, config, agentUsername, profile.workspaceId);
|
|
1362
|
-
}
|
|
1363
|
-
},
|
|
1364
1305
|
async ensureService(profile) {
|
|
1365
1306
|
const config = HermesProfileConfigSchema.parse(profile.config);
|
|
1366
|
-
const profileName =
|
|
1367
|
-
const portOverride = config.port !== 8642 ? config.port : portForProfile(
|
|
1307
|
+
const profileName = config.hermesProfileName && config.hermesProfileName !== "default" ? config.hermesProfileName : profile.agentUsername || profile.id.slice(0, 8);
|
|
1308
|
+
const portOverride = config.port !== 8642 ? config.port : portForProfile(profileName, 8642);
|
|
1368
1309
|
const baseUrl = `http://127.0.0.1:${portOverride}`;
|
|
1369
1310
|
const agentUsername = profile.agentUsername || profileName;
|
|
1370
1311
|
if (config.configurePrismerProvider) {
|
|
1371
|
-
configurePrismerProvider(profileName, config, agentUsername
|
|
1312
|
+
configurePrismerProvider(profileName, config, agentUsername);
|
|
1372
1313
|
}
|
|
1373
1314
|
if (!await checkHealth(baseUrl, config.apiKey)) {
|
|
1374
1315
|
if (!config.autoStart) {
|
|
@@ -1378,7 +1319,6 @@ var hermesAdapter = {
|
|
|
1378
1319
|
(set autoStart: true on the AgentProfile to spawn it automatically)`
|
|
1379
1320
|
);
|
|
1380
1321
|
}
|
|
1381
|
-
await stopStaleHermesGateways(profileName, portOverride, config.startupTimeoutMs);
|
|
1382
1322
|
(0, import_node_child_process3.spawn)("hermes", ["-p", profileName, "gateway", "run"], {
|
|
1383
1323
|
detached: false,
|
|
1384
1324
|
stdio: "ignore",
|
|
@@ -1438,8 +1378,6 @@ var HermesService = class {
|
|
|
1438
1378
|
const startedAt = Date.now();
|
|
1439
1379
|
let runId;
|
|
1440
1380
|
const sysPrompt = typeof task.metadata?.systemPrompt === "string" ? task.metadata.systemPrompt : void 0;
|
|
1441
|
-
const operatingPrinciples = typeof task.metadata?.operatingPrinciples === "string" ? task.metadata.operatingPrinciples : void 0;
|
|
1442
|
-
const instructions = [sysPrompt, operatingPrinciples].filter(Boolean).join("\n\n");
|
|
1443
1381
|
if (sysPrompt) {
|
|
1444
1382
|
try {
|
|
1445
1383
|
const profileDir = getHermesProfileDir(this.profileName);
|
|
@@ -1476,7 +1414,7 @@ var HermesService = class {
|
|
|
1476
1414
|
// sent `messages: [...]` which Hermes 0.12 rejects with 400.
|
|
1477
1415
|
body: JSON.stringify({
|
|
1478
1416
|
input: task.prompt,
|
|
1479
|
-
instructions:
|
|
1417
|
+
instructions: sysPrompt,
|
|
1480
1418
|
session_id: typeof task.metadata?.conversationId === "string" ? task.metadata.conversationId : void 0,
|
|
1481
1419
|
stream: true
|
|
1482
1420
|
}),
|
|
@@ -1761,7 +1699,7 @@ function formatHermesGoalText(goals) {
|
|
|
1761
1699
|
return `${index + 1}. ${priority}${goal.title}${description}`;
|
|
1762
1700
|
}).join("\n");
|
|
1763
1701
|
}
|
|
1764
|
-
function configurePrismerProvider(profileName, config, agentUsername
|
|
1702
|
+
function configurePrismerProvider(profileName, config, agentUsername) {
|
|
1765
1703
|
const baseUrl = resolvePrismerProviderBaseUrl(config);
|
|
1766
1704
|
const apiKey = resolvePrismerApiKey(config);
|
|
1767
1705
|
if (!baseUrl || !apiKey) {
|
|
@@ -1804,16 +1742,9 @@ function configurePrismerProvider(profileName, config, agentUsername, workspaceI
|
|
|
1804
1742
|
PRISMER_API_KEY: apiKey,
|
|
1805
1743
|
PRISMER_BASE_URL: baseUrl.replace(/\/api\/v1\/?$/, "")
|
|
1806
1744
|
};
|
|
1807
|
-
if (workspaceId) {
|
|
1808
|
-
mcpEnv.PRISMER_WORKSPACE_ID = workspaceId;
|
|
1809
|
-
}
|
|
1810
1745
|
if (agentUsername) {
|
|
1811
1746
|
mcpEnv.PRISMER_AGENT_USERNAME = agentUsername;
|
|
1812
1747
|
}
|
|
1813
|
-
const allowlist = resolveMcpAllowlist(config);
|
|
1814
|
-
if (allowlist) {
|
|
1815
|
-
mcpEnv.PRISMER_MCP_ALLOWLIST = allowlist.join(",");
|
|
1816
|
-
}
|
|
1817
1748
|
mcpServers["prismer-tasks"] = {
|
|
1818
1749
|
command: "node",
|
|
1819
1750
|
args: [serverPath],
|
|
@@ -1834,17 +1765,20 @@ function resolvePrismerMcpServerPath(config) {
|
|
|
1834
1765
|
if (config.prismerMcpServerPath) return config.prismerMcpServerPath;
|
|
1835
1766
|
if (process.env.PRISMER_MCP_SERVER) return process.env.PRISMER_MCP_SERVER;
|
|
1836
1767
|
try {
|
|
1837
|
-
const
|
|
1768
|
+
const { createRequire } = require("module");
|
|
1769
|
+
const req = createRequire(import_meta.url);
|
|
1838
1770
|
return req.resolve("@prismer/mcp-server");
|
|
1839
1771
|
} catch {
|
|
1840
1772
|
}
|
|
1841
1773
|
try {
|
|
1842
|
-
const
|
|
1843
|
-
const
|
|
1774
|
+
const { fileURLToPath: fileURLToPath2 } = require("url");
|
|
1775
|
+
const { join: join14, dirname: dirname8 } = require("path");
|
|
1776
|
+
const here = dirname8(fileURLToPath2(import_meta.url));
|
|
1777
|
+
const candidate = join14(here, "../../mcp/dist/index.js");
|
|
1844
1778
|
if ((0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
1845
|
-
const candidate2 = (
|
|
1779
|
+
const candidate2 = join14(here, "../../../mcp/dist/index.js");
|
|
1846
1780
|
if ((0, import_node_fs.existsSync)(candidate2)) return candidate2;
|
|
1847
|
-
const candidate3 = (
|
|
1781
|
+
const candidate3 = join14(here, "../../../../mcp/dist/index.js");
|
|
1848
1782
|
if ((0, import_node_fs.existsSync)(candidate3)) return candidate3;
|
|
1849
1783
|
} catch {
|
|
1850
1784
|
}
|
|
@@ -2013,29 +1947,25 @@ function runPythonJson(args, env, timeoutMs) {
|
|
|
2013
1947
|
});
|
|
2014
1948
|
});
|
|
2015
1949
|
}
|
|
2016
|
-
function readHermesConfig(
|
|
2017
|
-
if (!(0, import_node_fs.existsSync)(
|
|
1950
|
+
function readHermesConfig(path7) {
|
|
1951
|
+
if (!(0, import_node_fs.existsSync)(path7)) return {};
|
|
2018
1952
|
try {
|
|
2019
|
-
return YAML.parse((0, import_node_fs.readFileSync)(
|
|
1953
|
+
return YAML.parse((0, import_node_fs.readFileSync)(path7, "utf8")) ?? {};
|
|
2020
1954
|
} catch (err) {
|
|
2021
|
-
throw new Error(`Failed to parse Hermes config at ${
|
|
1955
|
+
throw new Error(`Failed to parse Hermes config at ${path7}: ${err.message}`);
|
|
2022
1956
|
}
|
|
2023
1957
|
}
|
|
2024
|
-
function writeEnvValue(
|
|
2025
|
-
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(
|
|
2026
|
-
const lines = (0, import_node_fs.existsSync)(
|
|
1958
|
+
function writeEnvValue(path7, key, value) {
|
|
1959
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path7), { recursive: true });
|
|
1960
|
+
const lines = (0, import_node_fs.existsSync)(path7) ? (0, import_node_fs.readFileSync)(path7, "utf8").split(/\r?\n/) : [];
|
|
2027
1961
|
const next = lines.filter((line) => !line.startsWith(`${key}=`) && line.trim() !== "");
|
|
2028
1962
|
next.push(`${key}=${quoteEnv(value)}`);
|
|
2029
|
-
(0, import_node_fs.writeFileSync)(
|
|
1963
|
+
(0, import_node_fs.writeFileSync)(path7, `${next.join("\n")}
|
|
2030
1964
|
`, "utf8");
|
|
2031
1965
|
}
|
|
2032
1966
|
function quoteEnv(value) {
|
|
2033
1967
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
2034
1968
|
}
|
|
2035
|
-
function getHermesProfileName(profile) {
|
|
2036
|
-
const configured = profile.config && typeof profile.config.hermesProfileName === "string" ? profile.config.hermesProfileName : void 0;
|
|
2037
|
-
return configured && configured !== "default" ? configured : profile.agentUsername || profile.id.slice(0, 8);
|
|
2038
|
-
}
|
|
2039
1969
|
function getHermesProfileDir(profileName) {
|
|
2040
1970
|
const root = process.env.HERMES_HOME || (0, import_node_path.join)((0, import_node_os.homedir)(), ".hermes");
|
|
2041
1971
|
if (!profileName || profileName === "default") return root;
|
|
@@ -2057,18 +1987,6 @@ function resolvePrismerProviderBaseUrl(config) {
|
|
|
2057
1987
|
function resolvePrismerApiKey(config) {
|
|
2058
1988
|
return process.env[config.prismerApiKeyEnv] || process.env.PRISMER_API_KEY || "";
|
|
2059
1989
|
}
|
|
2060
|
-
function resolveMcpAllowlist(config) {
|
|
2061
|
-
if (Array.isArray(config.mcpAllowlist)) return normalizeAllowlist(config.mcpAllowlist);
|
|
2062
|
-
const prismerServer = config.roleTemplate?.mcpServers?.find((server) => {
|
|
2063
|
-
const record = server;
|
|
2064
|
-
return record.package === "@prismer/mcp-server" || record.name === "prismer-tasks";
|
|
2065
|
-
});
|
|
2066
|
-
if (!prismerServer) return null;
|
|
2067
|
-
return Array.isArray(prismerServer.toolsAllowlist) ? normalizeAllowlist(prismerServer.toolsAllowlist) : null;
|
|
2068
|
-
}
|
|
2069
|
-
function normalizeAllowlist(values) {
|
|
2070
|
-
return values.map((value) => value.trim()).filter(Boolean);
|
|
2071
|
-
}
|
|
2072
1990
|
function normalizeProviderName(value) {
|
|
2073
1991
|
return value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
2074
1992
|
}
|
|
@@ -2174,71 +2092,9 @@ async function waitForHealthy(baseUrl, apiKey, timeoutMs) {
|
|
|
2174
2092
|
}
|
|
2175
2093
|
throw new Error(`Hermes did not become healthy at ${baseUrl} within ${timeoutMs}ms`);
|
|
2176
2094
|
}
|
|
2177
|
-
async function stopStaleHermesGateways(profileName, port, timeoutMs) {
|
|
2178
|
-
const pids = findHermesGatewayPids(profileName, port);
|
|
2179
|
-
for (const pid of pids) {
|
|
2180
|
-
try {
|
|
2181
|
-
process.kill(pid, "SIGTERM");
|
|
2182
|
-
} catch {
|
|
2183
|
-
continue;
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
const deadline = Date.now() + Math.min(timeoutMs, 5e3);
|
|
2187
|
-
while (Date.now() < deadline) {
|
|
2188
|
-
const alive = pids.filter(pidAlive);
|
|
2189
|
-
if (alive.length === 0) return;
|
|
2190
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
2191
|
-
}
|
|
2192
|
-
for (const pid of pids) {
|
|
2193
|
-
if (!pidAlive(pid)) continue;
|
|
2194
|
-
try {
|
|
2195
|
-
process.kill(pid, "SIGKILL");
|
|
2196
|
-
} catch {
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
function findHermesGatewayPids(profileName, port) {
|
|
2201
|
-
try {
|
|
2202
|
-
const out = (0, import_node_child_process3.execFileSync)("ps", ["-axo", "pid=,command="], { encoding: "utf8" });
|
|
2203
|
-
const current = process.pid;
|
|
2204
|
-
const escapedProfile = escapeRegExp(profileName);
|
|
2205
|
-
const profilePattern = new RegExp(`(?:^|\\s)-p\\s+${escapedProfile}(?:\\s|$)`);
|
|
2206
|
-
const portPattern = new RegExp(`(?:^|\\s)API_SERVER_PORT=${port}(?:\\s|$)`);
|
|
2207
|
-
return out.split(/\r?\n/).map((line) => {
|
|
2208
|
-
const trimmed = line.trim();
|
|
2209
|
-
const match = /^(\d+)\s+(.+)$/.exec(trimmed);
|
|
2210
|
-
if (!match) return null;
|
|
2211
|
-
const pid = Number.parseInt(match[1], 10);
|
|
2212
|
-
const command = match[2];
|
|
2213
|
-
if (!Number.isFinite(pid) || pid === current) return null;
|
|
2214
|
-
if (!isHermesGatewayCommand(command)) return null;
|
|
2215
|
-
if (!profilePattern.test(command) && !portPattern.test(command)) return null;
|
|
2216
|
-
return pid;
|
|
2217
|
-
}).filter((pid) => pid !== null);
|
|
2218
|
-
} catch {
|
|
2219
|
-
return [];
|
|
2220
|
-
}
|
|
2221
|
-
}
|
|
2222
|
-
function isHermesGatewayCommand(command) {
|
|
2223
|
-
if (!/\bgateway\b/.test(command) || !/\brun\b/.test(command)) return false;
|
|
2224
|
-
return /(^|\s)(?:\S*\/)?hermes(?:\s|$)/.test(command);
|
|
2225
|
-
}
|
|
2226
|
-
function pidAlive(pid) {
|
|
2227
|
-
try {
|
|
2228
|
-
process.kill(pid, 0);
|
|
2229
|
-
return true;
|
|
2230
|
-
} catch {
|
|
2231
|
-
return false;
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
function escapeRegExp(value) {
|
|
2235
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2236
|
-
}
|
|
2237
2095
|
|
|
2238
2096
|
// src/adapters/openclaw/index.ts
|
|
2239
2097
|
var import_zod4 = require("zod");
|
|
2240
|
-
var import_node_os2 = require("os");
|
|
2241
|
-
var import_node_path2 = require("path");
|
|
2242
2098
|
var OpenClawProfileConfigSchema = import_zod4.z.object({
|
|
2243
2099
|
/** OpenClaw chat-completions HTTP port. */
|
|
2244
2100
|
port: import_zod4.z.number().int().min(1).max(65535).default(18789),
|
|
@@ -2252,13 +2108,7 @@ var OpenClawProfileConfigSchema = import_zod4.z.object({
|
|
|
2252
2108
|
* by `agents.defaults.model.primary` in openclaw.json (e.g.
|
|
2253
2109
|
* `prismer/us-kimi-k2.5`, set by the bootstrap).
|
|
2254
2110
|
*/
|
|
2255
|
-
model: import_zod4.z.string().min(1).default("openclaw")
|
|
2256
|
-
mcpAllowlist: import_zod4.z.array(import_zod4.z.string()).nullable().optional(),
|
|
2257
|
-
approvalPolicy: import_zod4.z.enum(["strict", "auto-low-risk", "autonomous"]).optional().default("auto-low-risk"),
|
|
2258
|
-
roleTemplate: import_zod4.z.object({
|
|
2259
|
-
mcpServers: import_zod4.z.array(import_zod4.z.object({ toolsAllowlist: import_zod4.z.array(import_zod4.z.string()).optional() }).passthrough()).optional()
|
|
2260
|
-
}).passthrough().optional(),
|
|
2261
|
-
skillsDir: import_zod4.z.string().optional()
|
|
2111
|
+
model: import_zod4.z.string().min(1).default("openclaw")
|
|
2262
2112
|
});
|
|
2263
2113
|
var openclawAdapter = {
|
|
2264
2114
|
name: "openclaw",
|
|
@@ -2282,24 +2132,22 @@ var openclawAdapter = {
|
|
|
2282
2132
|
openclaw gateway --port ${config.port}`
|
|
2283
2133
|
);
|
|
2284
2134
|
}
|
|
2285
|
-
return new OpenClawService(baseUrl, config.apiKey, config.model
|
|
2135
|
+
return new OpenClawService(baseUrl, config.apiKey, config.model);
|
|
2286
2136
|
},
|
|
2287
2137
|
async health() {
|
|
2288
2138
|
return { available: true };
|
|
2289
2139
|
}
|
|
2290
2140
|
};
|
|
2291
2141
|
var OpenClawService = class {
|
|
2292
|
-
constructor(baseUrl, apiKey, model
|
|
2142
|
+
constructor(baseUrl, apiKey, model) {
|
|
2293
2143
|
this.baseUrl = baseUrl;
|
|
2294
2144
|
this.apiKey = apiKey;
|
|
2295
2145
|
this.model = model;
|
|
2296
|
-
this.skillsDir = skillsDir;
|
|
2297
2146
|
this.id = baseUrl;
|
|
2298
2147
|
}
|
|
2299
2148
|
baseUrl;
|
|
2300
2149
|
apiKey;
|
|
2301
2150
|
model;
|
|
2302
|
-
skillsDir;
|
|
2303
2151
|
id;
|
|
2304
2152
|
async healthy() {
|
|
2305
2153
|
return checkHealth2(this.baseUrl, this.apiKey);
|
|
@@ -2360,15 +2208,6 @@ var OpenClawService = class {
|
|
|
2360
2208
|
}
|
|
2361
2209
|
}
|
|
2362
2210
|
};
|
|
2363
|
-
function getOpenClawSkillsDir(profile) {
|
|
2364
|
-
const config = OpenClawProfileConfigSchema.parse(profile.config);
|
|
2365
|
-
return resolveOpenClawSkillsDir(profile, config);
|
|
2366
|
-
}
|
|
2367
|
-
function resolveOpenClawSkillsDir(profile, config) {
|
|
2368
|
-
if (config.skillsDir) return config.skillsDir;
|
|
2369
|
-
const name = profile.agentUsername || profile.id.slice(0, 8);
|
|
2370
|
-
return (0, import_node_path2.join)(process.env.OPENCLAW_HOME || (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".openclaw"), "profiles", name, "skills");
|
|
2371
|
-
}
|
|
2372
2211
|
async function checkHealth2(baseUrl, apiKey) {
|
|
2373
2212
|
try {
|
|
2374
2213
|
const res = await fetch(`${baseUrl}/health`, {
|
|
@@ -2384,8 +2223,8 @@ async function checkHealth2(baseUrl, apiKey) {
|
|
|
2384
2223
|
// src/config.ts
|
|
2385
2224
|
var TOML = __toESM(require("@iarna/toml"), 1);
|
|
2386
2225
|
var import_node_fs2 = require("fs");
|
|
2387
|
-
var
|
|
2388
|
-
var
|
|
2226
|
+
var import_node_os2 = require("os");
|
|
2227
|
+
var import_node_path2 = require("path");
|
|
2389
2228
|
var import_zod5 = require("zod");
|
|
2390
2229
|
var ConfigSchema = import_zod5.z.object({
|
|
2391
2230
|
/** API key from `prismer setup`, or env override. */
|
|
@@ -2415,14 +2254,14 @@ var ConfigSchema = import_zod5.z.object({
|
|
|
2415
2254
|
}).optional()
|
|
2416
2255
|
});
|
|
2417
2256
|
function resolvePaths(home) {
|
|
2418
|
-
const root = home ?? process.env.PRISMER_HOME ?? (0,
|
|
2257
|
+
const root = home ?? process.env.PRISMER_HOME ?? (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".prismer");
|
|
2419
2258
|
return {
|
|
2420
2259
|
root,
|
|
2421
|
-
configFile: (0,
|
|
2422
|
-
localDb: (0,
|
|
2423
|
-
cacheDir: (0,
|
|
2424
|
-
logsDir: (0,
|
|
2425
|
-
runsDir: (0,
|
|
2260
|
+
configFile: (0, import_node_path2.join)(root, "config.toml"),
|
|
2261
|
+
localDb: (0, import_node_path2.join)(root, "local.db"),
|
|
2262
|
+
cacheDir: (0, import_node_path2.join)(root, "cache"),
|
|
2263
|
+
logsDir: (0, import_node_path2.join)(root, "logs"),
|
|
2264
|
+
runsDir: (0, import_node_path2.join)(root, "runs")
|
|
2426
2265
|
};
|
|
2427
2266
|
}
|
|
2428
2267
|
function configExists(paths = resolvePaths()) {
|
|
@@ -2452,8 +2291,8 @@ function saveConfig(config, paths = resolvePaths()) {
|
|
|
2452
2291
|
if (!(0, import_node_fs2.existsSync)(paths.root)) {
|
|
2453
2292
|
(0, import_node_fs2.mkdirSync)(paths.root, { recursive: true });
|
|
2454
2293
|
}
|
|
2455
|
-
if (!(0, import_node_fs2.existsSync)((0,
|
|
2456
|
-
(0, import_node_fs2.mkdirSync)((0,
|
|
2294
|
+
if (!(0, import_node_fs2.existsSync)((0, import_node_path2.dirname)(paths.configFile))) {
|
|
2295
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(paths.configFile), { recursive: true });
|
|
2457
2296
|
}
|
|
2458
2297
|
ConfigSchema.parse(config);
|
|
2459
2298
|
(0, import_node_fs2.writeFileSync)(paths.configFile, TOML.stringify(config), "utf8");
|
|
@@ -2477,7 +2316,7 @@ var INSTALL_SPECS = {
|
|
|
2477
2316
|
binary: "claude",
|
|
2478
2317
|
hint: "Set ANTHROPIC_API_KEY in your shell profile, or rely on Claude Code OAuth login. Run `claude login` to sign in.",
|
|
2479
2318
|
authHints: ["ANTHROPIC_API_KEY or Claude Code OAuth login (`claude login`)"],
|
|
2480
|
-
hookTarget: { path: (0,
|
|
2319
|
+
hookTarget: { path: (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".claude", "hooks.json"), kind: "json-file" }
|
|
2481
2320
|
},
|
|
2482
2321
|
openclaw: {
|
|
2483
2322
|
name: "openclaw",
|
|
@@ -2486,7 +2325,7 @@ var INSTALL_SPECS = {
|
|
|
2486
2325
|
binary: "openclaw",
|
|
2487
2326
|
hint: "OpenClaw runs as a gateway. Configure ~/.openclaw/openclaw.json and start it with `openclaw gateway` before tasks dispatch.",
|
|
2488
2327
|
authHints: ["~/.openclaw/openclaw.json gateway.auth.bearerTokens", "Prismer daemon api_key"],
|
|
2489
|
-
hookTarget: { path: (0,
|
|
2328
|
+
hookTarget: { path: (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".openclaw", "hooks"), kind: "directory" }
|
|
2490
2329
|
},
|
|
2491
2330
|
codex: {
|
|
2492
2331
|
name: "codex",
|
|
@@ -2495,7 +2334,7 @@ var INSTALL_SPECS = {
|
|
|
2495
2334
|
binary: "codex",
|
|
2496
2335
|
hint: "Set OPENAI_API_KEY in your shell profile. Verify with `codex --help`.",
|
|
2497
2336
|
authHints: ["OPENAI_API_KEY or Codex CLI account login"],
|
|
2498
|
-
hookTarget: { path: (0,
|
|
2337
|
+
hookTarget: { path: (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".codex", "hooks.json"), kind: "json-file" }
|
|
2499
2338
|
},
|
|
2500
2339
|
hermes: {
|
|
2501
2340
|
name: "hermes",
|
|
@@ -2504,7 +2343,7 @@ var INSTALL_SPECS = {
|
|
|
2504
2343
|
binary: "hermes",
|
|
2505
2344
|
hint: "Hermes runs as a long-lived HTTP gateway in your own Python venv. Start with `hermes -p <profile> gateway` after configuring ~/.hermes/.env.",
|
|
2506
2345
|
authHints: ["~/.hermes/.env API_SERVER_KEY", "Hermes profile provider credentials"],
|
|
2507
|
-
hookTarget: { path: (0,
|
|
2346
|
+
hookTarget: { path: (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".hermes", "hooks.json"), kind: "json-file" }
|
|
2508
2347
|
}
|
|
2509
2348
|
};
|
|
2510
2349
|
function buildAdapterCommand() {
|
|
@@ -2592,8 +2431,8 @@ function buildAdapterCommand() {
|
|
|
2592
2431
|
});
|
|
2593
2432
|
return cmd;
|
|
2594
2433
|
}
|
|
2595
|
-
function runInstall(spec,
|
|
2596
|
-
const { argv0, args } = installCommand(spec,
|
|
2434
|
+
function runInstall(spec, version2, dryRun) {
|
|
2435
|
+
const { argv0, args } = installCommand(spec, version2);
|
|
2597
2436
|
getUI().line(`> ${argv0} ${args.join(" ")}`);
|
|
2598
2437
|
if (dryRun) {
|
|
2599
2438
|
getUI().line("(dry-run \u2014 not executing)");
|
|
@@ -2720,11 +2559,11 @@ function runHooks(spec, opts) {
|
|
|
2720
2559
|
let backup;
|
|
2721
2560
|
if (planned.kind === "directory") {
|
|
2722
2561
|
(0, import_node_fs4.mkdirSync)(planned.path, { recursive: true });
|
|
2723
|
-
const markerPath = (0,
|
|
2562
|
+
const markerPath = (0, import_node_path4.join)(planned.path, "prismer.json");
|
|
2724
2563
|
backup = backupIfExists(markerPath);
|
|
2725
2564
|
(0, import_node_fs4.writeFileSync)(markerPath, JSON.stringify(prismerHookMarker(spec), null, 2) + "\n", "utf8");
|
|
2726
2565
|
} else {
|
|
2727
|
-
(0, import_node_fs4.mkdirSync)((0,
|
|
2566
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path4.dirname)(planned.path), { recursive: true });
|
|
2728
2567
|
backup = backupIfExists(planned.path);
|
|
2729
2568
|
const merged = mergeHookJson(planned.path, spec);
|
|
2730
2569
|
(0, import_node_fs4.writeFileSync)(planned.path, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
@@ -2737,16 +2576,16 @@ function runHooks(spec, opts) {
|
|
|
2737
2576
|
hook: inspectHook(spec)
|
|
2738
2577
|
};
|
|
2739
2578
|
}
|
|
2740
|
-
function installCommand(spec,
|
|
2741
|
-
const ref =
|
|
2579
|
+
function installCommand(spec, version2) {
|
|
2580
|
+
const ref = version2 === "latest" ? spec.package : `${spec.package}@${version2}`;
|
|
2742
2581
|
switch (spec.manager) {
|
|
2743
2582
|
case "npm":
|
|
2744
2583
|
return { argv0: "npm", args: ["install", "-g", ref] };
|
|
2745
2584
|
case "pipx":
|
|
2746
|
-
const pyRef =
|
|
2585
|
+
const pyRef = version2 === "latest" ? spec.package : `${spec.package}==${version2}`;
|
|
2747
2586
|
return { argv0: "pipx", args: ["install", pyRef] };
|
|
2748
2587
|
case "pip":
|
|
2749
|
-
const pipRef =
|
|
2588
|
+
const pipRef = version2 === "latest" ? spec.package : `${spec.package}==${version2}`;
|
|
2750
2589
|
return { argv0: "pip", args: ["install", "--user", pipRef] };
|
|
2751
2590
|
}
|
|
2752
2591
|
}
|
|
@@ -2794,7 +2633,7 @@ function inspectAuthEnv(spec) {
|
|
|
2794
2633
|
hints: spec.authHints ?? []
|
|
2795
2634
|
};
|
|
2796
2635
|
case "hermes": {
|
|
2797
|
-
const envFile = (0,
|
|
2636
|
+
const envFile = (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".hermes", ".env");
|
|
2798
2637
|
return {
|
|
2799
2638
|
ok: (0, import_node_fs4.existsSync)(envFile) || Boolean(process.env.HERMES_API_KEY || process.env.API_SERVER_KEY),
|
|
2800
2639
|
present: [
|
|
@@ -2805,7 +2644,7 @@ function inspectAuthEnv(spec) {
|
|
|
2805
2644
|
};
|
|
2806
2645
|
}
|
|
2807
2646
|
case "openclaw": {
|
|
2808
|
-
const cfgFile = (0,
|
|
2647
|
+
const cfgFile = (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".openclaw", "openclaw.json");
|
|
2809
2648
|
return {
|
|
2810
2649
|
ok: (0, import_node_fs4.existsSync)(cfgFile) || Boolean(process.env.OPENCLAW_API_KEY),
|
|
2811
2650
|
present: [
|
|
@@ -2825,8 +2664,8 @@ function inspectHook(spec) {
|
|
|
2825
2664
|
return { supported: false, markerPresent: false };
|
|
2826
2665
|
}
|
|
2827
2666
|
if (spec.name === "openclaw") {
|
|
2828
|
-
const jsonPath = (0,
|
|
2829
|
-
const markerPath = (0,
|
|
2667
|
+
const jsonPath = (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".openclaw", "hooks.json");
|
|
2668
|
+
const markerPath = (0, import_node_path4.join)(target.path, "prismer.json");
|
|
2830
2669
|
const jsonMarkerPresent = (0, import_node_fs4.existsSync)(jsonPath) && fileContainsPrismerMarker(jsonPath);
|
|
2831
2670
|
const dirMarkerPresent = (0, import_node_fs4.existsSync)(markerPath) && fileContainsPrismerMarker(markerPath);
|
|
2832
2671
|
return {
|
|
@@ -2840,7 +2679,7 @@ function inspectHook(spec) {
|
|
|
2840
2679
|
};
|
|
2841
2680
|
}
|
|
2842
2681
|
if (target.kind === "directory") {
|
|
2843
|
-
const markerPath = (0,
|
|
2682
|
+
const markerPath = (0, import_node_path4.join)(target.path, "prismer.json");
|
|
2844
2683
|
return {
|
|
2845
2684
|
supported: true,
|
|
2846
2685
|
kind: target.kind,
|
|
@@ -2884,21 +2723,21 @@ function prismerHookMarker(spec) {
|
|
|
2884
2723
|
installed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2885
2724
|
};
|
|
2886
2725
|
}
|
|
2887
|
-
function mergeHookJson(
|
|
2726
|
+
function mergeHookJson(path7, spec) {
|
|
2888
2727
|
const marker = prismerHookMarker(spec);
|
|
2889
|
-
if (!(0, import_node_fs4.existsSync)(
|
|
2728
|
+
if (!(0, import_node_fs4.existsSync)(path7)) {
|
|
2890
2729
|
return {
|
|
2891
2730
|
prismer: marker
|
|
2892
2731
|
};
|
|
2893
2732
|
}
|
|
2894
2733
|
let parsed;
|
|
2895
2734
|
try {
|
|
2896
|
-
parsed = JSON.parse((0, import_node_fs4.readFileSync)(
|
|
2735
|
+
parsed = JSON.parse((0, import_node_fs4.readFileSync)(path7, "utf8"));
|
|
2897
2736
|
} catch (err) {
|
|
2898
|
-
throw new Error(`Cannot parse ${
|
|
2737
|
+
throw new Error(`Cannot parse ${path7} as JSON: ${err.message}`);
|
|
2899
2738
|
}
|
|
2900
2739
|
if (!isRecord(parsed)) {
|
|
2901
|
-
throw new Error(`Cannot merge ${
|
|
2740
|
+
throw new Error(`Cannot merge ${path7}: expected a JSON object`);
|
|
2902
2741
|
}
|
|
2903
2742
|
const next = { ...parsed };
|
|
2904
2743
|
next.prismer = marker;
|
|
@@ -2916,18 +2755,18 @@ function mergeHookJson(path9, spec) {
|
|
|
2916
2755
|
}
|
|
2917
2756
|
return next;
|
|
2918
2757
|
}
|
|
2919
|
-
function backupIfExists(
|
|
2920
|
-
if (!(0, import_node_fs4.existsSync)(
|
|
2758
|
+
function backupIfExists(path7) {
|
|
2759
|
+
if (!(0, import_node_fs4.existsSync)(path7)) return null;
|
|
2921
2760
|
const suffix = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2922
|
-
const backup = `${
|
|
2923
|
-
const stat = (0, import_node_fs4.statSync)(
|
|
2761
|
+
const backup = `${path7}.bak.${suffix}`;
|
|
2762
|
+
const stat = (0, import_node_fs4.statSync)(path7);
|
|
2924
2763
|
if (stat.isDirectory()) return null;
|
|
2925
|
-
(0, import_node_fs4.copyFileSync)(
|
|
2764
|
+
(0, import_node_fs4.copyFileSync)(path7, backup);
|
|
2926
2765
|
return backup;
|
|
2927
2766
|
}
|
|
2928
|
-
function fileContainsPrismerMarker(
|
|
2767
|
+
function fileContainsPrismerMarker(path7) {
|
|
2929
2768
|
try {
|
|
2930
|
-
return (0, import_node_fs4.readFileSync)(
|
|
2769
|
+
return (0, import_node_fs4.readFileSync)(path7, "utf8").includes("prismer-daemon-runtime");
|
|
2931
2770
|
} catch {
|
|
2932
2771
|
return false;
|
|
2933
2772
|
}
|
|
@@ -2945,7 +2784,7 @@ function readAdapterConfig() {
|
|
|
2945
2784
|
return {};
|
|
2946
2785
|
}
|
|
2947
2786
|
}
|
|
2948
|
-
function recordInstallInConfig(spec, binPath,
|
|
2787
|
+
function recordInstallInConfig(spec, binPath, version2) {
|
|
2949
2788
|
const paths = resolvePaths();
|
|
2950
2789
|
if (!configExists(paths)) {
|
|
2951
2790
|
process.stderr.write(
|
|
@@ -2967,7 +2806,7 @@ function recordInstallInConfig(spec, binPath, version) {
|
|
|
2967
2806
|
package_manager: spec.manager,
|
|
2968
2807
|
package_name: spec.package,
|
|
2969
2808
|
binary: binPath,
|
|
2970
|
-
version,
|
|
2809
|
+
version: version2,
|
|
2971
2810
|
installed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2972
2811
|
};
|
|
2973
2812
|
saveConfig({ ...cfg, adapters }, paths);
|
|
@@ -3000,8 +2839,8 @@ var CloudClient = class {
|
|
|
3000
2839
|
opts;
|
|
3001
2840
|
fetchImpl;
|
|
3002
2841
|
/** Low-level request. Caller decides on retry; sync-worker handles backoff. */
|
|
3003
|
-
async request(method,
|
|
3004
|
-
const url = this.urlFor(
|
|
2842
|
+
async request(method, path7, init) {
|
|
2843
|
+
const url = this.urlFor(path7);
|
|
3005
2844
|
const headers = {
|
|
3006
2845
|
"Content-Type": "application/json",
|
|
3007
2846
|
Accept: "application/json",
|
|
@@ -3060,8 +2899,8 @@ var CloudClient = class {
|
|
|
3060
2899
|
return { ok: true, status: res.status, data: body ?? void 0 };
|
|
3061
2900
|
}
|
|
3062
2901
|
/** Convenience: GET expecting `{ ok, data }` envelope. Returns `data` or throws. */
|
|
3063
|
-
async get(
|
|
3064
|
-
const res = await this.request("GET",
|
|
2902
|
+
async get(path7, opts) {
|
|
2903
|
+
const res = await this.request("GET", path7, opts);
|
|
3065
2904
|
if (!res.ok) throw new CloudError(res.status, res.error?.code ?? "unknown", res.error?.message ?? "request failed");
|
|
3066
2905
|
if (res.data && typeof res.data === "object" && "ok" in res.data) {
|
|
3067
2906
|
const env = res.data;
|
|
@@ -3070,14 +2909,14 @@ var CloudClient = class {
|
|
|
3070
2909
|
}
|
|
3071
2910
|
return res.data;
|
|
3072
2911
|
}
|
|
3073
|
-
urlFor(
|
|
2912
|
+
urlFor(path7) {
|
|
3074
2913
|
const trimmedBase = this.opts.baseUrl.replace(/\/$/, "");
|
|
3075
|
-
const trimmedPath =
|
|
2914
|
+
const trimmedPath = path7.startsWith("/") ? path7 : `/${path7}`;
|
|
3076
2915
|
return `${trimmedBase}${trimmedPath}`;
|
|
3077
2916
|
}
|
|
3078
2917
|
/** Raw byte download (for asset cache). Returns response so caller can stream body. */
|
|
3079
|
-
async fetchRaw(
|
|
3080
|
-
const url = this.urlFor(
|
|
2918
|
+
async fetchRaw(path7, init) {
|
|
2919
|
+
const url = this.urlFor(path7);
|
|
3081
2920
|
const headers = {
|
|
3082
2921
|
Authorization: `Bearer ${this.opts.apiKey}`,
|
|
3083
2922
|
...init?.headers ?? {}
|
|
@@ -3118,7 +2957,7 @@ function mapStatusToCode(status) {
|
|
|
3118
2957
|
// src/sync/store.ts
|
|
3119
2958
|
var import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
|
|
3120
2959
|
var import_node_fs5 = require("fs");
|
|
3121
|
-
var
|
|
2960
|
+
var import_node_path5 = require("path");
|
|
3122
2961
|
var MIGRATIONS = [
|
|
3123
2962
|
{
|
|
3124
2963
|
version: 1,
|
|
@@ -3249,11 +3088,11 @@ var MIGRATIONS = [
|
|
|
3249
3088
|
function runSql(db, sql) {
|
|
3250
3089
|
db["exec"](sql);
|
|
3251
3090
|
}
|
|
3252
|
-
function openLocalDb(
|
|
3253
|
-
if (
|
|
3254
|
-
(0, import_node_fs5.mkdirSync)((0,
|
|
3091
|
+
function openLocalDb(path7) {
|
|
3092
|
+
if (path7 !== ":memory:" && !(0, import_node_fs5.existsSync)((0, import_node_path5.dirname)(path7))) {
|
|
3093
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path5.dirname)(path7), { recursive: true });
|
|
3255
3094
|
}
|
|
3256
|
-
const db = new import_better_sqlite32.default(
|
|
3095
|
+
const db = new import_better_sqlite32.default(path7);
|
|
3257
3096
|
db.pragma("journal_mode = WAL");
|
|
3258
3097
|
db.pragma("foreign_keys = ON");
|
|
3259
3098
|
runMigrations(db);
|
|
@@ -3633,7 +3472,7 @@ async function fetchCloudProfiles(cloud, imUserId) {
|
|
|
3633
3472
|
async function fetchLocalServer() {
|
|
3634
3473
|
const paths = resolvePaths();
|
|
3635
3474
|
const pid = readPidFile(paths);
|
|
3636
|
-
if (!pid || !
|
|
3475
|
+
if (!pid || !pidAlive(pid)) return { ok: false, error: "daemon process is not running", agents: [] };
|
|
3637
3476
|
try {
|
|
3638
3477
|
const [healthRes, agentsRes] = await Promise.all([
|
|
3639
3478
|
fetch(`${LOCAL_SERVER}/healthz`, { signal: AbortSignal.timeout(1e3) }),
|
|
@@ -3657,685 +3496,103 @@ function whichBinary2(bin) {
|
|
|
3657
3496
|
|
|
3658
3497
|
// src/cli/commands/asset.ts
|
|
3659
3498
|
var import_commander3 = require("commander");
|
|
3660
|
-
var import_node_fs9 = require("fs");
|
|
3661
|
-
var import_node_path10 = require("path");
|
|
3662
|
-
|
|
3663
|
-
// src/asset-cache.ts
|
|
3664
|
-
var import_node_crypto = require("crypto");
|
|
3665
3499
|
var import_node_fs6 = require("fs");
|
|
3666
|
-
var
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
maxBytes;
|
|
3683
|
-
constructor(opts) {
|
|
3684
|
-
this.db = opts.db;
|
|
3685
|
-
this.cloud = opts.cloud;
|
|
3686
|
-
this.cacheDir = opts.cacheDir;
|
|
3687
|
-
this.maxBytes = opts.maxBytes ?? 5 * 1024 * 1024 * 1024;
|
|
3688
|
-
if (!(0, import_node_fs6.existsSync)(this.cacheDir)) {
|
|
3689
|
-
(0, import_node_fs6.mkdirSync)(this.cacheDir, { recursive: true });
|
|
3500
|
+
var import_node_path6 = require("path");
|
|
3501
|
+
init_util();
|
|
3502
|
+
init_ui();
|
|
3503
|
+
function buildAssetCommand() {
|
|
3504
|
+
const cmd = new import_commander3.Command("asset").description("Inspect IM assets");
|
|
3505
|
+
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) => {
|
|
3506
|
+
const cloud = mkCloud();
|
|
3507
|
+
const query = new URLSearchParams();
|
|
3508
|
+
if (opts.workspaceId) query.set("workspaceId", opts.workspaceId);
|
|
3509
|
+
if (opts.taskId) query.set("taskId", opts.taskId);
|
|
3510
|
+
const path7 = `/api/im/assets${query.toString() ? `?${query.toString()}` : ""}`;
|
|
3511
|
+
const res = await cloud.request("GET", path7);
|
|
3512
|
+
if (!res.ok) exitWithError(`asset list failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_list_failed" });
|
|
3513
|
+
const body = res.data;
|
|
3514
|
+
if (isErrorEnvelope(body)) {
|
|
3515
|
+
exitWithError(`asset list failed (${res.status}): ${errorMessage(body)}`, { code: "asset_list_failed" });
|
|
3690
3516
|
}
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
return (0, import_node_path7.join)(this.cacheDir, hash.slice(0, 2), hash);
|
|
3695
|
-
}
|
|
3696
|
-
get(hash) {
|
|
3697
|
-
const row = this.db.prepare("SELECT * FROM cached_assets WHERE content_hash = ?").get(hash);
|
|
3698
|
-
if (!row) return void 0;
|
|
3699
|
-
if (!(0, import_node_fs6.existsSync)(row.local_path)) {
|
|
3700
|
-
this.db.prepare("DELETE FROM cached_assets WHERE content_hash = ?").run(hash);
|
|
3701
|
-
return void 0;
|
|
3517
|
+
if (opts.json) {
|
|
3518
|
+
printJson(body ?? null);
|
|
3519
|
+
return;
|
|
3702
3520
|
}
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
pin(hash) {
|
|
3711
|
-
this.db.prepare("UPDATE cached_assets SET pin = 1 WHERE content_hash = ?").run(hash);
|
|
3712
|
-
}
|
|
3713
|
-
unpin(hash) {
|
|
3714
|
-
this.db.prepare("UPDATE cached_assets SET pin = 0 WHERE content_hash = ?").run(hash);
|
|
3715
|
-
}
|
|
3716
|
-
/**
|
|
3717
|
-
* Get or fetch by hash. On miss, downloads the raw bytes and caches them.
|
|
3718
|
-
*
|
|
3719
|
-
* Why two endpoints: `GET /api/im/assets/by-hash/:hash` returns the JSON
|
|
3720
|
-
* `AssetDTO` (id, contentHash, storageUri, …) — *metadata only*. The raw
|
|
3721
|
-
* bytes live at `GET /api/im/assets/:id`. If `opts.assetId` is supplied
|
|
3722
|
-
* the caller already has the id from an `AssetRef`, so we go straight to
|
|
3723
|
-
* the byte endpoint; otherwise we resolve `hash → id` first (one extra
|
|
3724
|
-
* round-trip on the metadata route) before downloading the body.
|
|
3725
|
-
*
|
|
3726
|
-
* Earlier this method called `by-hash` and treated the JSON body as the
|
|
3727
|
-
* asset content — the daemon stored the 500-byte metadata blob on disk
|
|
3728
|
-
* and `resolveAssetRefs` inlined that JSON into the prompt. The agent
|
|
3729
|
-
* saw `{"id":"…","storageUri":"…"}` instead of the user's file and
|
|
3730
|
-
* concluded the upload had failed.
|
|
3731
|
-
*/
|
|
3732
|
-
async getOrFetch(hash, opts) {
|
|
3733
|
-
const existing = this.get(hash);
|
|
3734
|
-
if (existing) return existing;
|
|
3735
|
-
const wsParam = opts?.workspaceIdHint ? `?wsId=${encodeURIComponent(opts.workspaceIdHint)}` : "";
|
|
3736
|
-
let assetId = opts?.assetId;
|
|
3737
|
-
if (!assetId) {
|
|
3738
|
-
const metaRes = await this.cloud.fetchRaw(
|
|
3739
|
-
`/api/im/assets/by-hash/${encodeURIComponent(hash)}${wsParam}`,
|
|
3740
|
-
{ signal: opts?.signal }
|
|
3741
|
-
);
|
|
3742
|
-
if (!metaRes.ok) {
|
|
3743
|
-
throw new Error(`Asset metadata fetch failed for ${hash}: HTTP ${metaRes.status}`);
|
|
3744
|
-
}
|
|
3745
|
-
const metaJson = await metaRes.json();
|
|
3746
|
-
if (!metaJson?.ok || typeof metaJson.data?.id !== "string") {
|
|
3747
|
-
throw new Error(`Asset metadata payload missing id for ${hash}`);
|
|
3748
|
-
}
|
|
3749
|
-
assetId = metaJson.data.id;
|
|
3521
|
+
printAssetList(body);
|
|
3522
|
+
}, { code: "asset_list_failed" }));
|
|
3523
|
+
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) => {
|
|
3524
|
+
const body = await uploadAsset(file, opts);
|
|
3525
|
+
if (opts.json) {
|
|
3526
|
+
printJson(body ?? null);
|
|
3527
|
+
return;
|
|
3750
3528
|
}
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3529
|
+
printAssetDetail("Uploaded asset", file, body);
|
|
3530
|
+
}, { code: "asset_upload_failed" }));
|
|
3531
|
+
cmd.command("download <assetId>").description("Download asset bytes").requiredOption("--out <path>", "Destination file path").action(runAction(async (assetId, opts) => {
|
|
3532
|
+
await downloadAsset(assetId, opts.out);
|
|
3533
|
+
getUI().line(`Downloaded ${assetId} -> ${opts.out}`);
|
|
3534
|
+
}, { code: "asset_download_failed" }));
|
|
3535
|
+
cmd.command("get <assetId>").description("Fetch asset metadata by id").option("--json", "Output machine-readable JSON").action(runAction(async (assetId, opts) => {
|
|
3536
|
+
const cloud = mkCloud();
|
|
3537
|
+
const res = await cloud.request("GET", `/api/im/assets/${encodeURIComponent(assetId)}/detail`);
|
|
3538
|
+
if (!res.ok) exitWithError(`asset get failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_get_failed" });
|
|
3539
|
+
const body = res.data;
|
|
3540
|
+
if (isErrorEnvelope(body)) {
|
|
3541
|
+
exitWithError(`asset get failed (${res.status}): ${errorMessage(body)}`, { code: "asset_get_failed" });
|
|
3756
3542
|
}
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
throw new Error(`Asset hash mismatch for ${hash}: downloaded ${actualHash}`);
|
|
3543
|
+
if (opts.json) {
|
|
3544
|
+
printJson(body ?? null);
|
|
3545
|
+
return;
|
|
3761
3546
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3547
|
+
printAssetDetail("Asset", assetId, body);
|
|
3548
|
+
}, { code: "asset_get_failed" }));
|
|
3549
|
+
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) => {
|
|
3550
|
+
const cloud = mkCloud();
|
|
3551
|
+
const query = new URLSearchParams();
|
|
3552
|
+
if (opts.workspaceId) query.set("wsId", opts.workspaceId);
|
|
3553
|
+
const path7 = `/api/im/assets/by-hash/${encodeURIComponent(sha2562)}${query.toString() ? `?${query.toString()}` : ""}`;
|
|
3554
|
+
const res = await cloud.request("GET", path7);
|
|
3555
|
+
if (!res.ok) exitWithError(`asset by-hash failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_by_hash_failed" });
|
|
3556
|
+
const body = res.data;
|
|
3557
|
+
if (isErrorEnvelope(body)) {
|
|
3558
|
+
exitWithError(`asset by-hash failed (${res.status}): ${errorMessage(body)}`, { code: "asset_by_hash_failed" });
|
|
3765
3559
|
}
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
const mime = res.headers.get("content-type");
|
|
3770
|
-
const now = Date.now();
|
|
3771
|
-
this.db.prepare(
|
|
3772
|
-
`INSERT OR REPLACE INTO cached_assets
|
|
3773
|
-
(content_hash, size_bytes, mime, local_path, fetched_at, last_used_at, pin)
|
|
3774
|
-
VALUES (?, ?, ?, ?, ?, ?, 0)`
|
|
3775
|
-
).run(hash, buf.length, mime, localPath, now, now);
|
|
3776
|
-
this.evictIfOver();
|
|
3777
|
-
return {
|
|
3778
|
-
contentHash: hash,
|
|
3779
|
-
sizeBytes: buf.length,
|
|
3780
|
-
mime,
|
|
3781
|
-
localPath,
|
|
3782
|
-
fetchedAt: now,
|
|
3783
|
-
lastUsedAt: now,
|
|
3784
|
-
pin: false
|
|
3785
|
-
};
|
|
3786
|
-
}
|
|
3787
|
-
/** Insert an already-on-disk asset into the cache (e.g., asset just produced by adapter). */
|
|
3788
|
-
registerLocal(hash, localPath, mime) {
|
|
3789
|
-
const actualHash = sha256(readFileBuffer(localPath));
|
|
3790
|
-
if (actualHash !== hash) {
|
|
3791
|
-
throw new Error(`Asset hash mismatch for ${hash}: local file is ${actualHash}`);
|
|
3560
|
+
if (opts.json) {
|
|
3561
|
+
printJson(body ?? null);
|
|
3562
|
+
return;
|
|
3792
3563
|
}
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
freed += row.size_bytes;
|
|
3826
|
-
total -= row.size_bytes;
|
|
3827
|
-
removed += 1;
|
|
3828
|
-
}
|
|
3829
|
-
return { removed, freed };
|
|
3830
|
-
}
|
|
3831
|
-
};
|
|
3832
|
-
function sha256(buf) {
|
|
3833
|
-
return (0, import_node_crypto.createHash)("sha256").update(buf).digest("hex");
|
|
3834
|
-
}
|
|
3835
|
-
function readFileBuffer(path9) {
|
|
3836
|
-
return (0, import_node_fs6.readFileSync)(path9);
|
|
3837
|
-
}
|
|
3838
|
-
|
|
3839
|
-
// src/daemon/asset/metadata-index.ts
|
|
3840
|
-
var import_node_fs7 = require("fs");
|
|
3841
|
-
var import_node_path8 = require("path");
|
|
3842
|
-
var DEFAULT_LIMIT = 8;
|
|
3843
|
-
var PULL_PAGE_SIZE = 500;
|
|
3844
|
-
var THROTTLE_MS = 3e4;
|
|
3845
|
-
function rowToMetadata(row) {
|
|
3846
|
-
return {
|
|
3847
|
-
assetId: row.asset_id,
|
|
3848
|
-
contentHash: row.content_hash,
|
|
3849
|
-
filename: row.filename,
|
|
3850
|
-
folderPath: row.folder_path,
|
|
3851
|
-
mime: row.mime,
|
|
3852
|
-
kind: row.kind,
|
|
3853
|
-
sizeBytes: row.size_bytes,
|
|
3854
|
-
description: row.description,
|
|
3855
|
-
assetIndexSeq: row.asset_index_seq
|
|
3856
|
-
};
|
|
3857
|
-
}
|
|
3858
|
-
var AssetMetadataIndex = class {
|
|
3859
|
-
db;
|
|
3860
|
-
cloud;
|
|
3861
|
-
/** Workspace ID — exposed for prismer:// URI construction. */
|
|
3862
|
-
workspaceId;
|
|
3863
|
-
cursorPath;
|
|
3864
|
-
_lastSyncMs = 0;
|
|
3865
|
-
constructor(opts) {
|
|
3866
|
-
this.db = opts.db;
|
|
3867
|
-
this.cloud = opts.cloud;
|
|
3868
|
-
this.workspaceId = opts.workspaceId;
|
|
3869
|
-
if (!(0, import_node_fs7.existsSync)(opts.workspaceStateDir)) {
|
|
3870
|
-
(0, import_node_fs7.mkdirSync)(opts.workspaceStateDir, { recursive: true });
|
|
3871
|
-
}
|
|
3872
|
-
this.cursorPath = (0, import_node_path8.join)(opts.workspaceStateDir, "asset-metadata-cursor.json");
|
|
3873
|
-
}
|
|
3874
|
-
/** Persisted cursor for this workspace, or 0 on first run / corrupted file. */
|
|
3875
|
-
readCursor() {
|
|
3876
|
-
if (!(0, import_node_fs7.existsSync)(this.cursorPath)) return 0;
|
|
3877
|
-
try {
|
|
3878
|
-
const parsed = JSON.parse((0, import_node_fs7.readFileSync)(this.cursorPath, "utf8"));
|
|
3879
|
-
if (parsed.workspaceId !== this.workspaceId) return 0;
|
|
3880
|
-
return parsed.cursor;
|
|
3881
|
-
} catch {
|
|
3882
|
-
return 0;
|
|
3883
|
-
}
|
|
3884
|
-
}
|
|
3885
|
-
writeCursor(cursor) {
|
|
3886
|
-
const payload = {
|
|
3887
|
-
workspaceId: this.workspaceId,
|
|
3888
|
-
cursor,
|
|
3889
|
-
writtenAt: Date.now()
|
|
3890
|
-
};
|
|
3891
|
-
(0, import_node_fs7.writeFileSync)(this.cursorPath, JSON.stringify(payload, null, 2));
|
|
3892
|
-
}
|
|
3893
|
-
/**
|
|
3894
|
-
* Pull incremental asset metadata changes since the persisted cursor and
|
|
3895
|
-
* upsert into the local index. Newer rows overwrite older ones by
|
|
3896
|
-
* (workspace_id, asset_id) primary key.
|
|
3897
|
-
*
|
|
3898
|
-
* Returns the count of items applied + the new cursor. Throttled: if called
|
|
3899
|
-
* within 30s of the last successful pull, returns immediately.
|
|
3900
|
-
*/
|
|
3901
|
-
async pullDelta(opts) {
|
|
3902
|
-
const now = Date.now();
|
|
3903
|
-
if (now - this._lastSyncMs < THROTTLE_MS) {
|
|
3904
|
-
return { applied: 0, cursor: this.readCursor() };
|
|
3905
|
-
}
|
|
3906
|
-
const since = this.readCursor();
|
|
3907
|
-
const sinceParam = since > 0 ? `&since=${since}` : "";
|
|
3908
|
-
const envelope2 = await this.cloud.get(
|
|
3909
|
-
`/api/im/assets/index?workspaceId=${encodeURIComponent(this.workspaceId)}&limit=${PULL_PAGE_SIZE}${sinceParam}`,
|
|
3910
|
-
{ signal: opts?.signal }
|
|
3911
|
-
);
|
|
3912
|
-
const upsert = this.db.prepare(`
|
|
3913
|
-
INSERT INTO asset_metadata_index
|
|
3914
|
-
(workspace_id, asset_id, content_hash, filename, folder_path, mime, kind, size_bytes, description, asset_index_seq, updated_at)
|
|
3915
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
3916
|
-
ON CONFLICT(workspace_id, asset_id) DO UPDATE SET
|
|
3917
|
-
content_hash = excluded.content_hash,
|
|
3918
|
-
filename = excluded.filename,
|
|
3919
|
-
folder_path = excluded.folder_path,
|
|
3920
|
-
mime = excluded.mime,
|
|
3921
|
-
kind = excluded.kind,
|
|
3922
|
-
size_bytes = excluded.size_bytes,
|
|
3923
|
-
description = excluded.description,
|
|
3924
|
-
asset_index_seq = excluded.asset_index_seq,
|
|
3925
|
-
updated_at = excluded.updated_at
|
|
3926
|
-
`);
|
|
3927
|
-
const nowTs = Date.now();
|
|
3928
|
-
let applied = 0;
|
|
3929
|
-
const tx = this.db.transaction((items) => {
|
|
3930
|
-
for (const item of items) {
|
|
3931
|
-
const assetId = item.assetId ?? item.id;
|
|
3932
|
-
if (!assetId) {
|
|
3933
|
-
throw new Error("Asset metadata index item missing assetId/id");
|
|
3934
|
-
}
|
|
3935
|
-
upsert.run(
|
|
3936
|
-
this.workspaceId,
|
|
3937
|
-
assetId,
|
|
3938
|
-
item.contentHash,
|
|
3939
|
-
item.filename ?? null,
|
|
3940
|
-
item.folderPath ?? null,
|
|
3941
|
-
item.mime,
|
|
3942
|
-
item.kind,
|
|
3943
|
-
item.sizeBytes,
|
|
3944
|
-
item.description ?? null,
|
|
3945
|
-
item.assetIndexSeq,
|
|
3946
|
-
nowTs
|
|
3947
|
-
);
|
|
3948
|
-
applied += 1;
|
|
3949
|
-
}
|
|
3950
|
-
});
|
|
3951
|
-
try {
|
|
3952
|
-
tx(envelope2.items);
|
|
3953
|
-
this.writeCursor(envelope2.cursor);
|
|
3954
|
-
} catch (err) {
|
|
3955
|
-
throw err;
|
|
3956
|
-
}
|
|
3957
|
-
this._lastSyncMs = now;
|
|
3958
|
-
return { applied, cursor: envelope2.cursor };
|
|
3959
|
-
}
|
|
3960
|
-
/**
|
|
3961
|
-
* Search local index by filename or description substring.
|
|
3962
|
-
* Escapes LIKE wildcards (% and _). Default limit 8.
|
|
3963
|
-
*/
|
|
3964
|
-
search(query, limit) {
|
|
3965
|
-
const escaped = query.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
3966
|
-
const pattern = `%${escaped}%`;
|
|
3967
|
-
const limitVal = Math.min(Math.max(limit ?? DEFAULT_LIMIT, 1), 200);
|
|
3968
|
-
const rows = this.db.prepare(
|
|
3969
|
-
`SELECT * FROM asset_metadata_index
|
|
3970
|
-
WHERE workspace_id = ?
|
|
3971
|
-
AND (filename LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\')
|
|
3972
|
-
ORDER BY asset_index_seq DESC
|
|
3973
|
-
LIMIT ?`
|
|
3974
|
-
).all(this.workspaceId, pattern, pattern, limitVal);
|
|
3975
|
-
return rows.map(rowToMetadata);
|
|
3976
|
-
}
|
|
3977
|
-
/** Exact match on filename column. Returns undefined if not indexed. */
|
|
3978
|
-
resolveByFilename(filename) {
|
|
3979
|
-
const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND filename = ?").get(this.workspaceId, filename);
|
|
3980
|
-
return row ? rowToMetadata(row) : void 0;
|
|
3981
|
-
}
|
|
3982
|
-
resolveByAssetId(assetId) {
|
|
3983
|
-
const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND asset_id = ?").get(this.workspaceId, assetId);
|
|
3984
|
-
return row ? rowToMetadata(row) : void 0;
|
|
3985
|
-
}
|
|
3986
|
-
resolveByContentHash(contentHash) {
|
|
3987
|
-
const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND content_hash = ?").get(this.workspaceId, contentHash);
|
|
3988
|
-
return row ? rowToMetadata(row) : void 0;
|
|
3989
|
-
}
|
|
3990
|
-
/** Batch exact match — returns Map for O(1) access. */
|
|
3991
|
-
resolveByFilenames(filenames) {
|
|
3992
|
-
if (filenames.length === 0) return /* @__PURE__ */ new Map();
|
|
3993
|
-
const placeholders = filenames.map(() => "?").join(",");
|
|
3994
|
-
const params = [this.workspaceId, ...filenames];
|
|
3995
|
-
const rows = this.db.prepare(
|
|
3996
|
-
`SELECT * FROM asset_metadata_index
|
|
3997
|
-
WHERE workspace_id = ? AND filename IN (${placeholders})`
|
|
3998
|
-
).all(...params);
|
|
3999
|
-
const map = /* @__PURE__ */ new Map();
|
|
4000
|
-
for (const row of rows) {
|
|
4001
|
-
if (row.filename) map.set(row.filename, rowToMetadata(row));
|
|
4002
|
-
}
|
|
4003
|
-
return map;
|
|
4004
|
-
}
|
|
4005
|
-
};
|
|
4006
|
-
|
|
4007
|
-
// src/daemon/asset/mirror.ts
|
|
4008
|
-
var import_node_fs8 = require("fs");
|
|
4009
|
-
var import_node_path9 = require("path");
|
|
4010
|
-
function rowToBinding(row) {
|
|
4011
|
-
return {
|
|
4012
|
-
workspaceId: row.workspace_id,
|
|
4013
|
-
path: row.path,
|
|
4014
|
-
assetId: row.asset_id,
|
|
4015
|
-
contentHash: row.content_hash,
|
|
4016
|
-
version: row.version,
|
|
4017
|
-
syncedAt: row.synced_at,
|
|
4018
|
-
dirty: row.dirty === 1
|
|
4019
|
-
};
|
|
4020
|
-
}
|
|
4021
|
-
var WorkspaceMirror = class {
|
|
4022
|
-
db;
|
|
4023
|
-
cloud;
|
|
4024
|
-
workspaceId;
|
|
4025
|
-
cursorPath;
|
|
4026
|
-
constructor(opts) {
|
|
4027
|
-
this.db = opts.db;
|
|
4028
|
-
this.cloud = opts.cloud;
|
|
4029
|
-
this.workspaceId = opts.workspaceId;
|
|
4030
|
-
if (!(0, import_node_fs8.existsSync)(opts.workspaceStateDir)) {
|
|
4031
|
-
(0, import_node_fs8.mkdirSync)(opts.workspaceStateDir, { recursive: true });
|
|
4032
|
-
}
|
|
4033
|
-
this.cursorPath = (0, import_node_path9.join)(opts.workspaceStateDir, "asset-cursor.json");
|
|
4034
|
-
}
|
|
4035
|
-
/** Persisted cursor for this workspace, or null on first run / corrupted file. */
|
|
4036
|
-
readCursor() {
|
|
4037
|
-
if (!(0, import_node_fs8.existsSync)(this.cursorPath)) return null;
|
|
4038
|
-
try {
|
|
4039
|
-
const parsed = JSON.parse((0, import_node_fs8.readFileSync)(this.cursorPath, "utf8"));
|
|
4040
|
-
if (parsed.workspaceId !== this.workspaceId) return null;
|
|
4041
|
-
return parsed.cursor;
|
|
4042
|
-
} catch {
|
|
4043
|
-
return null;
|
|
4044
|
-
}
|
|
4045
|
-
}
|
|
4046
|
-
writeCursor(cursor) {
|
|
4047
|
-
const payload = {
|
|
4048
|
-
workspaceId: this.workspaceId,
|
|
4049
|
-
cursor,
|
|
4050
|
-
writtenAt: Date.now()
|
|
4051
|
-
};
|
|
4052
|
-
(0, import_node_fs8.writeFileSync)(this.cursorPath, JSON.stringify(payload, null, 2));
|
|
4053
|
-
}
|
|
4054
|
-
/**
|
|
4055
|
-
* Pull workspace_file changes since the persisted cursor and upsert into
|
|
4056
|
-
* the local mirror. Soft-deleted rows are removed from the mirror; active
|
|
4057
|
-
* rows are upserted with synced_at = now and dirty = 0.
|
|
4058
|
-
*
|
|
4059
|
-
* Returns the count of items applied + the new cursor (may be unchanged
|
|
4060
|
-
* when the cloud has nothing new). Idempotent — safe to retry.
|
|
4061
|
-
*/
|
|
4062
|
-
async pullDelta(opts) {
|
|
4063
|
-
const since = this.readCursor();
|
|
4064
|
-
const sinceParam = since ? `?since=${encodeURIComponent(since)}` : "";
|
|
4065
|
-
const envelope2 = await this.cloud.get(
|
|
4066
|
-
`/api/im/workspaces/${encodeURIComponent(this.workspaceId)}/files/sync${sinceParam}`,
|
|
4067
|
-
{ signal: opts?.signal }
|
|
4068
|
-
);
|
|
4069
|
-
const upsert = this.db.prepare(`
|
|
4070
|
-
INSERT INTO workspace_files_mirror (workspace_id, path, asset_id, content_hash, version, synced_at, dirty)
|
|
4071
|
-
VALUES (?, ?, ?, ?, ?, ?, 0)
|
|
4072
|
-
ON CONFLICT(workspace_id, path) DO UPDATE SET
|
|
4073
|
-
asset_id = excluded.asset_id,
|
|
4074
|
-
content_hash = excluded.content_hash,
|
|
4075
|
-
version = excluded.version,
|
|
4076
|
-
synced_at = excluded.synced_at,
|
|
4077
|
-
dirty = 0
|
|
4078
|
-
`);
|
|
4079
|
-
const remove = this.db.prepare(
|
|
4080
|
-
"DELETE FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?"
|
|
4081
|
-
);
|
|
4082
|
-
const now = Date.now();
|
|
4083
|
-
let applied = 0;
|
|
4084
|
-
const tx = this.db.transaction((items) => {
|
|
4085
|
-
for (const item of items) {
|
|
4086
|
-
if (item.workspaceId !== this.workspaceId) continue;
|
|
4087
|
-
if (item.deletedAt) {
|
|
4088
|
-
remove.run(this.workspaceId, item.path);
|
|
4089
|
-
} else if (item.contentHash) {
|
|
4090
|
-
upsert.run(this.workspaceId, item.path, item.assetId, item.contentHash, item.version, now);
|
|
4091
|
-
} else {
|
|
4092
|
-
remove.run(this.workspaceId, item.path);
|
|
4093
|
-
}
|
|
4094
|
-
applied += 1;
|
|
4095
|
-
}
|
|
4096
|
-
});
|
|
4097
|
-
tx(envelope2.items);
|
|
4098
|
-
this.writeCursor(envelope2.cursor);
|
|
4099
|
-
return { applied, cursor: envelope2.cursor };
|
|
4100
|
-
}
|
|
4101
|
-
/** Lookup the active binding for a workspace path. Returns undefined if not mirrored. */
|
|
4102
|
-
lookupByPath(path9) {
|
|
4103
|
-
const row = this.db.prepare("SELECT * FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?").get(this.workspaceId, path9);
|
|
4104
|
-
return row ? rowToBinding(row) : void 0;
|
|
4105
|
-
}
|
|
4106
|
-
/** Find an asset's contentHash via any path binding (asset may map to many paths). */
|
|
4107
|
-
hashForAssetId(assetId) {
|
|
4108
|
-
const row = this.db.prepare(
|
|
4109
|
-
"SELECT content_hash FROM workspace_files_mirror WHERE workspace_id = ? AND asset_id = ? LIMIT 1"
|
|
4110
|
-
).get(this.workspaceId, assetId);
|
|
4111
|
-
return row?.content_hash;
|
|
4112
|
-
}
|
|
4113
|
-
/** All active bindings for this workspace — used by status / debug surfaces. */
|
|
4114
|
-
list() {
|
|
4115
|
-
const rows = this.db.prepare("SELECT * FROM workspace_files_mirror WHERE workspace_id = ? ORDER BY path ASC").all(this.workspaceId);
|
|
4116
|
-
return rows.map(rowToBinding);
|
|
4117
|
-
}
|
|
4118
|
-
/** Wipe the mirror for this workspace (e.g., re-pair). Cursor file is left intact. */
|
|
4119
|
-
clear() {
|
|
4120
|
-
this.db.prepare("DELETE FROM workspace_files_mirror WHERE workspace_id = ?").run(this.workspaceId);
|
|
4121
|
-
}
|
|
4122
|
-
};
|
|
4123
|
-
|
|
4124
|
-
// src/cli/commands/asset.ts
|
|
4125
|
-
init_util();
|
|
4126
|
-
init_ui();
|
|
4127
|
-
function buildAssetCommand() {
|
|
4128
|
-
const cmd = new import_commander3.Command("asset").description("Inspect IM assets");
|
|
4129
|
-
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) => {
|
|
4130
|
-
const result = await syncAssets(opts);
|
|
4131
|
-
if (opts.json) {
|
|
4132
|
-
printJson({ ok: true, data: result });
|
|
4133
|
-
return;
|
|
4134
|
-
}
|
|
4135
|
-
getUI().line(`Synced asset metadata: ${result.metadata.applied} applied (cursor=${result.metadata.cursor})`);
|
|
4136
|
-
getUI().line(`Synced workspace files: ${result.workspaceFiles.applied} applied (cursor=${result.workspaceFiles.cursor ?? "null"})`);
|
|
4137
|
-
if (opts.bytes) {
|
|
4138
|
-
getUI().line(
|
|
4139
|
-
`Prefetched bytes: ${result.prefetch.fetched}/${result.prefetch.considered} assets, ${result.prefetch.bytes} bytes`
|
|
4140
|
-
);
|
|
4141
|
-
if (result.prefetch.failed.length > 0) {
|
|
4142
|
-
getUI().line(`Prefetch failures: ${result.prefetch.failed.length}`);
|
|
4143
|
-
}
|
|
4144
|
-
} else {
|
|
4145
|
-
getUI().line("Asset bytes were not prefetched; they are downloaded lazily on first use. Run with --bytes to fill ~/.prismer/cache.");
|
|
4146
|
-
}
|
|
4147
|
-
}, { code: "asset_sync_failed" }));
|
|
4148
|
-
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) => {
|
|
4149
|
-
const cloud = mkCloud();
|
|
4150
|
-
const query = new URLSearchParams();
|
|
4151
|
-
if (opts.workspaceId) query.set("workspaceId", opts.workspaceId);
|
|
4152
|
-
if (opts.taskId) query.set("taskId", opts.taskId);
|
|
4153
|
-
const path9 = `/api/im/assets${query.toString() ? `?${query.toString()}` : ""}`;
|
|
4154
|
-
const res = await cloud.request("GET", path9);
|
|
4155
|
-
if (!res.ok) exitWithError(`asset list failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_list_failed" });
|
|
4156
|
-
const body = res.data;
|
|
4157
|
-
if (isErrorEnvelope(body)) {
|
|
4158
|
-
exitWithError(`asset list failed (${res.status}): ${errorMessage(body)}`, { code: "asset_list_failed" });
|
|
4159
|
-
}
|
|
4160
|
-
if (opts.json) {
|
|
4161
|
-
printJson(body ?? null);
|
|
4162
|
-
return;
|
|
4163
|
-
}
|
|
4164
|
-
printAssetList(body);
|
|
4165
|
-
}, { code: "asset_list_failed" }));
|
|
4166
|
-
cmd.command("upload <path>").description("Upload a local file or directory as IM assets").requiredOption("--workspace-id <id>", "Workspace id").option("--kind <kind>", "Asset kind", "file").option("--folder-path <path>", "Cloud folderPath prefix for the uploaded file(s)").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 (inputPath, opts) => {
|
|
4167
|
-
const body = await uploadAssetPath(inputPath, opts);
|
|
4168
|
-
if (opts.json) {
|
|
4169
|
-
printJson(body ?? null);
|
|
4170
|
-
return;
|
|
4171
|
-
}
|
|
4172
|
-
if (isUploadManyResult(body)) {
|
|
4173
|
-
getUI().line(`Uploaded ${body.data.count} assets`);
|
|
4174
|
-
for (const item of body.data.items) {
|
|
4175
|
-
printAssetDetail("Uploaded asset", item.file, item.response);
|
|
4176
|
-
}
|
|
4177
|
-
} else {
|
|
4178
|
-
printAssetDetail("Uploaded asset", inputPath, body);
|
|
4179
|
-
}
|
|
4180
|
-
}, { code: "asset_upload_failed" }));
|
|
4181
|
-
cmd.command("download <assetId>").description("Download asset bytes").requiredOption("--out <path>", "Destination file path").action(runAction(async (assetId, opts) => {
|
|
4182
|
-
await downloadAsset(assetId, opts.out);
|
|
4183
|
-
getUI().line(`Downloaded ${assetId} -> ${opts.out}`);
|
|
4184
|
-
}, { code: "asset_download_failed" }));
|
|
4185
|
-
cmd.command("get <assetId>").description("Fetch asset metadata by id").option("--json", "Output machine-readable JSON").action(runAction(async (assetId, opts) => {
|
|
4186
|
-
const cloud = mkCloud();
|
|
4187
|
-
const res = await cloud.request("GET", `/api/im/assets/${encodeURIComponent(assetId)}/detail`);
|
|
4188
|
-
if (!res.ok) exitWithError(`asset get failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_get_failed" });
|
|
4189
|
-
const body = res.data;
|
|
4190
|
-
if (isErrorEnvelope(body)) {
|
|
4191
|
-
exitWithError(`asset get failed (${res.status}): ${errorMessage(body)}`, { code: "asset_get_failed" });
|
|
4192
|
-
}
|
|
4193
|
-
if (opts.json) {
|
|
4194
|
-
printJson(body ?? null);
|
|
4195
|
-
return;
|
|
4196
|
-
}
|
|
4197
|
-
printAssetDetail("Asset", assetId, body);
|
|
4198
|
-
}, { code: "asset_get_failed" }));
|
|
4199
|
-
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 (sha2564, opts) => {
|
|
4200
|
-
const cloud = mkCloud();
|
|
4201
|
-
const query = new URLSearchParams();
|
|
4202
|
-
if (opts.workspaceId) query.set("wsId", opts.workspaceId);
|
|
4203
|
-
const path9 = `/api/im/assets/by-hash/${encodeURIComponent(sha2564)}${query.toString() ? `?${query.toString()}` : ""}`;
|
|
4204
|
-
const res = await cloud.request("GET", path9);
|
|
4205
|
-
if (!res.ok) exitWithError(`asset by-hash failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_by_hash_failed" });
|
|
4206
|
-
const body = res.data;
|
|
4207
|
-
if (isErrorEnvelope(body)) {
|
|
4208
|
-
exitWithError(`asset by-hash failed (${res.status}): ${errorMessage(body)}`, { code: "asset_by_hash_failed" });
|
|
4209
|
-
}
|
|
4210
|
-
if (opts.json) {
|
|
4211
|
-
printJson(body ?? null);
|
|
4212
|
-
return;
|
|
4213
|
-
}
|
|
4214
|
-
printAssetDetail("Asset hash", sha2564, body);
|
|
4215
|
-
}, { code: "asset_by_hash_failed" }));
|
|
4216
|
-
return cmd;
|
|
4217
|
-
}
|
|
4218
|
-
function mkCloud() {
|
|
4219
|
-
const cfg = loadConfig(resolvePaths());
|
|
4220
|
-
return new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
|
|
4221
|
-
}
|
|
4222
|
-
async function syncAssets(opts) {
|
|
4223
|
-
const paths = resolvePaths();
|
|
4224
|
-
const cfg = loadConfig(paths);
|
|
4225
|
-
const cloud = new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
|
|
4226
|
-
const db = openLocalDb(paths.localDb);
|
|
4227
|
-
try {
|
|
4228
|
-
const stateDir = `${paths.root}/${opts.workspaceId}`;
|
|
4229
|
-
const metadataIndex = new AssetMetadataIndex({
|
|
4230
|
-
db,
|
|
4231
|
-
cloud,
|
|
4232
|
-
workspaceId: opts.workspaceId,
|
|
4233
|
-
workspaceStateDir: stateDir
|
|
4234
|
-
});
|
|
4235
|
-
const mirror = new WorkspaceMirror({
|
|
4236
|
-
db,
|
|
4237
|
-
cloud,
|
|
4238
|
-
workspaceId: opts.workspaceId,
|
|
4239
|
-
workspaceStateDir: stateDir
|
|
4240
|
-
});
|
|
4241
|
-
const [metadata, workspaceFiles] = await Promise.all([
|
|
4242
|
-
metadataIndex.pullDelta(),
|
|
4243
|
-
mirror.pullDelta()
|
|
4244
|
-
]);
|
|
4245
|
-
const prefetch = opts.bytes ? await prefetchAssetBytes({
|
|
4246
|
-
db,
|
|
4247
|
-
cloud,
|
|
4248
|
-
workspaceId: opts.workspaceId,
|
|
4249
|
-
cacheDir: paths.cacheDir,
|
|
4250
|
-
maxBytes: cfg.cache?.max_bytes,
|
|
4251
|
-
limit: opts.limit
|
|
4252
|
-
}) : { considered: 0, fetched: 0, bytes: 0, failed: [] };
|
|
4253
|
-
return { workspaceId: opts.workspaceId, metadata, workspaceFiles, prefetch };
|
|
4254
|
-
} finally {
|
|
4255
|
-
db.close();
|
|
4256
|
-
}
|
|
4257
|
-
}
|
|
4258
|
-
async function prefetchAssetBytes(opts) {
|
|
4259
|
-
const limitClause = opts.limit ? " LIMIT ?" : "";
|
|
4260
|
-
const params = [opts.workspaceId];
|
|
4261
|
-
if (opts.limit) params.push(opts.limit);
|
|
4262
|
-
const rows = opts.db.prepare(
|
|
4263
|
-
`SELECT asset_id, content_hash, mime, size_bytes
|
|
4264
|
-
FROM asset_metadata_index
|
|
4265
|
-
WHERE workspace_id = ?
|
|
4266
|
-
ORDER BY asset_index_seq DESC${limitClause}`
|
|
4267
|
-
).all(...params);
|
|
4268
|
-
const cache = new AssetCache({
|
|
4269
|
-
db: opts.db,
|
|
4270
|
-
cloud: opts.cloud,
|
|
4271
|
-
cacheDir: opts.cacheDir,
|
|
4272
|
-
maxBytes: opts.maxBytes
|
|
4273
|
-
});
|
|
4274
|
-
let fetched = 0;
|
|
4275
|
-
let bytes = 0;
|
|
4276
|
-
const failed = [];
|
|
4277
|
-
for (const row of rows) {
|
|
4278
|
-
try {
|
|
4279
|
-
const cached = await cache.getOrFetch(row.content_hash, {
|
|
4280
|
-
workspaceIdHint: opts.workspaceId,
|
|
4281
|
-
assetId: row.asset_id
|
|
4282
|
-
});
|
|
4283
|
-
fetched += 1;
|
|
4284
|
-
bytes += cached.sizeBytes;
|
|
4285
|
-
} catch (err) {
|
|
4286
|
-
failed.push({
|
|
4287
|
-
assetId: row.asset_id,
|
|
4288
|
-
contentHash: row.content_hash,
|
|
4289
|
-
error: err.message
|
|
4290
|
-
});
|
|
4291
|
-
}
|
|
4292
|
-
}
|
|
4293
|
-
return { considered: rows.length, fetched, bytes, failed };
|
|
4294
|
-
}
|
|
4295
|
-
async function uploadAssetPath(inputPath, opts) {
|
|
4296
|
-
if (!(0, import_node_fs9.existsSync)(inputPath)) exitWithError(`path not found: ${inputPath}`);
|
|
4297
|
-
const stat = (0, import_node_fs9.lstatSync)(inputPath);
|
|
4298
|
-
if (stat.isSymbolicLink()) exitWithError(`refusing to upload symlink: ${inputPath}`);
|
|
4299
|
-
if (stat.isFile()) {
|
|
4300
|
-
return uploadAsset(inputPath, { ...opts, folderPath: normalizeFolderPath(opts.folderPath) });
|
|
4301
|
-
}
|
|
4302
|
-
if (!stat.isDirectory()) exitWithError(`path is not a file or directory: ${inputPath}`);
|
|
4303
|
-
const files = listFilesRecursive(inputPath);
|
|
4304
|
-
if (files.length === 0) exitWithError(`directory has no uploadable files: ${inputPath}`);
|
|
4305
|
-
const items = [];
|
|
4306
|
-
for (const file of files) {
|
|
4307
|
-
const relDir = (0, import_node_path10.dirname)((0, import_node_path10.relative)(inputPath, file));
|
|
4308
|
-
const folderPath = combineFolderPath(opts.folderPath, relDir === "." ? "" : relDir);
|
|
4309
|
-
const response = await uploadAsset(file, { ...opts, folderPath });
|
|
4310
|
-
items.push({ file, folderPath: folderPath ?? null, response });
|
|
4311
|
-
}
|
|
4312
|
-
return { ok: true, data: { count: items.length, items } };
|
|
4313
|
-
}
|
|
4314
|
-
async function uploadAsset(file, opts) {
|
|
4315
|
-
if (!(0, import_node_fs9.existsSync)(file)) exitWithError(`file not found: ${file}`);
|
|
4316
|
-
const cfg = loadConfig(resolvePaths());
|
|
4317
|
-
const bytes = (0, import_node_fs9.readFileSync)(file);
|
|
4318
|
-
const metadata = parseMetadata(opts.metadata);
|
|
4319
|
-
if (opts.taskId && metadata.taskId === void 0) metadata.taskId = opts.taskId;
|
|
4320
|
-
if (opts.containerId && metadata.containerId === void 0) metadata.containerId = opts.containerId;
|
|
4321
|
-
const form = new FormData();
|
|
4322
|
-
form.set("workspaceId", opts.workspaceId);
|
|
4323
|
-
form.set("kind", opts.kind);
|
|
4324
|
-
if (opts.agentId) form.set("sourceAgentImUserId", opts.agentId);
|
|
4325
|
-
if (opts.taskId) form.set("sourceTaskId", opts.taskId);
|
|
4326
|
-
if (opts.folderPath !== void 0) form.set("folderPath", opts.folderPath);
|
|
4327
|
-
if (Object.keys(metadata).length > 0) form.set("metadata", JSON.stringify(metadata));
|
|
4328
|
-
form.set("file", new Blob([bytes], { type: opts.mime ?? "application/octet-stream" }), (0, import_node_path10.basename)(file));
|
|
4329
|
-
const url = `${cfg.cloud_api_base.replace(/\/$/, "")}/api/im/assets`;
|
|
4330
|
-
let res;
|
|
4331
|
-
try {
|
|
4332
|
-
res = await fetch(url, {
|
|
4333
|
-
method: "POST",
|
|
4334
|
-
headers: { Authorization: `Bearer ${cfg.api_key}` },
|
|
4335
|
-
body: form
|
|
4336
|
-
});
|
|
4337
|
-
} catch (err) {
|
|
4338
|
-
exitWithError(`asset upload failed: ${err.message}`);
|
|
3564
|
+
printAssetDetail("Asset hash", sha2562, body);
|
|
3565
|
+
}, { code: "asset_by_hash_failed" }));
|
|
3566
|
+
return cmd;
|
|
3567
|
+
}
|
|
3568
|
+
function mkCloud() {
|
|
3569
|
+
const cfg = loadConfig(resolvePaths());
|
|
3570
|
+
return new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
|
|
3571
|
+
}
|
|
3572
|
+
async function uploadAsset(file, opts) {
|
|
3573
|
+
if (!(0, import_node_fs6.existsSync)(file)) exitWithError(`file not found: ${file}`);
|
|
3574
|
+
const cfg = loadConfig(resolvePaths());
|
|
3575
|
+
const bytes = (0, import_node_fs6.readFileSync)(file);
|
|
3576
|
+
const metadata = parseMetadata(opts.metadata);
|
|
3577
|
+
if (opts.taskId && metadata.taskId === void 0) metadata.taskId = opts.taskId;
|
|
3578
|
+
if (opts.containerId && metadata.containerId === void 0) metadata.containerId = opts.containerId;
|
|
3579
|
+
const form = new FormData();
|
|
3580
|
+
form.set("workspaceId", opts.workspaceId);
|
|
3581
|
+
form.set("kind", opts.kind);
|
|
3582
|
+
if (opts.agentId) form.set("sourceAgentImUserId", opts.agentId);
|
|
3583
|
+
if (opts.taskId) form.set("sourceTaskId", opts.taskId);
|
|
3584
|
+
if (Object.keys(metadata).length > 0) form.set("metadata", JSON.stringify(metadata));
|
|
3585
|
+
form.set("file", new Blob([bytes], { type: opts.mime ?? "application/octet-stream" }), (0, import_node_path6.basename)(file));
|
|
3586
|
+
const url = `${cfg.cloud_api_base.replace(/\/$/, "")}/api/im/assets`;
|
|
3587
|
+
let res;
|
|
3588
|
+
try {
|
|
3589
|
+
res = await fetch(url, {
|
|
3590
|
+
method: "POST",
|
|
3591
|
+
headers: { Authorization: `Bearer ${cfg.api_key}` },
|
|
3592
|
+
body: form
|
|
3593
|
+
});
|
|
3594
|
+
} catch (err) {
|
|
3595
|
+
exitWithError(`asset upload failed: ${err.message}`);
|
|
4339
3596
|
}
|
|
4340
3597
|
const body = await parseResponseBody(res);
|
|
4341
3598
|
if (!res.ok || isErrorEnvelope(body)) {
|
|
@@ -4343,37 +3600,6 @@ async function uploadAsset(file, opts) {
|
|
|
4343
3600
|
}
|
|
4344
3601
|
return body;
|
|
4345
3602
|
}
|
|
4346
|
-
function listFilesRecursive(root) {
|
|
4347
|
-
const out = [];
|
|
4348
|
-
for (const name of (0, import_node_fs9.readdirSync)(root)) {
|
|
4349
|
-
if (name.startsWith(".")) continue;
|
|
4350
|
-
const full = (0, import_node_path10.join)(root, name);
|
|
4351
|
-
const stat = (0, import_node_fs9.lstatSync)(full);
|
|
4352
|
-
if (stat.isSymbolicLink()) continue;
|
|
4353
|
-
if (stat.isDirectory()) {
|
|
4354
|
-
out.push(...listFilesRecursive(full));
|
|
4355
|
-
} else if (stat.isFile()) {
|
|
4356
|
-
out.push(full);
|
|
4357
|
-
}
|
|
4358
|
-
}
|
|
4359
|
-
return out.sort();
|
|
4360
|
-
}
|
|
4361
|
-
function combineFolderPath(prefix, relDir) {
|
|
4362
|
-
const normalizedPrefix = normalizeFolderPath(prefix);
|
|
4363
|
-
const normalizedRel = normalizeFolderPath(relDir);
|
|
4364
|
-
if (normalizedPrefix && normalizedRel) return `${normalizedPrefix}/${normalizedRel}`;
|
|
4365
|
-
return normalizedPrefix ?? normalizedRel;
|
|
4366
|
-
}
|
|
4367
|
-
function normalizeFolderPath(value) {
|
|
4368
|
-
if (value === void 0) return void 0;
|
|
4369
|
-
const normalized = value.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
|
|
4370
|
-
return normalized.length > 0 ? normalized : "";
|
|
4371
|
-
}
|
|
4372
|
-
function isUploadManyResult(raw) {
|
|
4373
|
-
return Boolean(
|
|
4374
|
-
raw && typeof raw === "object" && raw.ok === true && raw.data && Array.isArray(raw.data.items)
|
|
4375
|
-
);
|
|
4376
|
-
}
|
|
4377
3603
|
async function downloadAsset(assetId, outPath) {
|
|
4378
3604
|
const cloud = mkCloud();
|
|
4379
3605
|
const res = await cloud.fetchRaw(`/api/im/assets/${encodeURIComponent(assetId)}`);
|
|
@@ -4382,7 +3608,7 @@ async function downloadAsset(assetId, outPath) {
|
|
|
4382
3608
|
exitWithError(`asset download failed (${res.status}): ${errorMessage(body)}`);
|
|
4383
3609
|
}
|
|
4384
3610
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
4385
|
-
(0,
|
|
3611
|
+
(0, import_node_fs6.writeFileSync)(outPath, bytes);
|
|
4386
3612
|
}
|
|
4387
3613
|
function parseMetadata(raw) {
|
|
4388
3614
|
if (!raw) return {};
|
|
@@ -4467,13 +3693,6 @@ function stringField(obj, key) {
|
|
|
4467
3693
|
const value = obj?.[key];
|
|
4468
3694
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
4469
3695
|
}
|
|
4470
|
-
function parsePositiveInt(raw) {
|
|
4471
|
-
const n = Number(raw);
|
|
4472
|
-
if (!Number.isInteger(n) || n <= 0) {
|
|
4473
|
-
throw new Error("--limit must be a positive integer");
|
|
4474
|
-
}
|
|
4475
|
-
return n;
|
|
4476
|
-
}
|
|
4477
3696
|
|
|
4478
3697
|
// src/cli/commands/banner.ts
|
|
4479
3698
|
var import_commander4 = require("commander");
|
|
@@ -4529,7 +3748,7 @@ ${name} (${role})`);
|
|
|
4529
3748
|
getUI().line(`Group created ${data.groupId ?? "(unknown)"}
|
|
4530
3749
|
${data.title ?? opts.name} (${memberCount} members)`);
|
|
4531
3750
|
});
|
|
4532
|
-
group.command("messages <groupId>").description("List group messages").option("--limit <n>", "Max messages",
|
|
3751
|
+
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) => {
|
|
4533
3752
|
const data = await requestOrExit(
|
|
4534
3753
|
"GET",
|
|
4535
3754
|
withQuery(`/api/im/groups/${encodeURIComponent(groupId)}/messages`, {
|
|
@@ -4567,7 +3786,7 @@ ${data.title ?? opts.name} (${memberCount} members)`);
|
|
|
4567
3786
|
getUI().line(`Removed ${memberId} from group ${groupId}`);
|
|
4568
3787
|
});
|
|
4569
3788
|
cmd.addCommand(group);
|
|
4570
|
-
cmd.command("messages <conversationId>").description("List conversation messages").option("--limit <n>", "Max messages",
|
|
3789
|
+
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) => {
|
|
4571
3790
|
const data = await requestOrExit(
|
|
4572
3791
|
"GET",
|
|
4573
3792
|
withQuery(`/api/im/messages/${encodeURIComponent(conversationId)}`, {
|
|
@@ -4583,15 +3802,15 @@ ${data.title ?? opts.name} (${memberCount} members)`);
|
|
|
4583
3802
|
});
|
|
4584
3803
|
return cmd;
|
|
4585
3804
|
}
|
|
4586
|
-
async function requestOrExit(method,
|
|
3805
|
+
async function requestOrExit(method, path7, body) {
|
|
4587
3806
|
let res;
|
|
4588
3807
|
try {
|
|
4589
|
-
res = await mkCloud2().request(method,
|
|
3808
|
+
res = await mkCloud2().request(method, path7, body === void 0 ? void 0 : { body });
|
|
4590
3809
|
} catch (err) {
|
|
4591
3810
|
exitWithError(sanitizeError(err.message));
|
|
4592
3811
|
}
|
|
4593
3812
|
if (!res.ok) {
|
|
4594
|
-
exitWithError(`${method} ${
|
|
3813
|
+
exitWithError(`${method} ${path7} failed (${res.status}): ${sanitizeError(res.error?.message ?? "unknown")}`);
|
|
4595
3814
|
}
|
|
4596
3815
|
return unwrapEnvelope(res.data);
|
|
4597
3816
|
}
|
|
@@ -4623,20 +3842,20 @@ function unwrapEnvelope(raw) {
|
|
|
4623
3842
|
function parseCsv(value) {
|
|
4624
3843
|
return value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
4625
3844
|
}
|
|
4626
|
-
function
|
|
3845
|
+
function parsePositiveInt(value) {
|
|
4627
3846
|
const parsed = Number.parseInt(value, 10);
|
|
4628
3847
|
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
4629
3848
|
throw new Error("expected a positive integer");
|
|
4630
3849
|
}
|
|
4631
3850
|
return parsed;
|
|
4632
3851
|
}
|
|
4633
|
-
function withQuery(
|
|
3852
|
+
function withQuery(path7, params) {
|
|
4634
3853
|
const query = new URLSearchParams();
|
|
4635
3854
|
for (const [key, value] of Object.entries(params)) {
|
|
4636
3855
|
if (value !== void 0) query.set(key, String(value));
|
|
4637
3856
|
}
|
|
4638
3857
|
const suffix = query.toString();
|
|
4639
|
-
return suffix ? `${
|
|
3858
|
+
return suffix ? `${path7}?${suffix}` : path7;
|
|
4640
3859
|
}
|
|
4641
3860
|
function printMessageSummary(label, data) {
|
|
4642
3861
|
const msg = data.message ?? {};
|
|
@@ -4763,7 +3982,7 @@ init_util();
|
|
|
4763
3982
|
init_ui();
|
|
4764
3983
|
function buildCookbookCommand() {
|
|
4765
3984
|
const cmd = new import_commander7.Command("cookbook").description("Run CLI-only 54release MVP regression suites");
|
|
4766
|
-
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",
|
|
3985
|
+
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) => {
|
|
4767
3986
|
const suites = parseSuites(opts.suite);
|
|
4768
3987
|
const cloud = mkCloud3(opts.timeoutMs);
|
|
4769
3988
|
const checks = [];
|
|
@@ -4805,8 +4024,8 @@ async function runStatusSuite(cloud) {
|
|
|
4805
4024
|
await check("status", "cloud health", "GET", "/api/health"),
|
|
4806
4025
|
await check("status", "current IM identity", "GET", "/api/im/me", void 0, cloud)
|
|
4807
4026
|
];
|
|
4808
|
-
async function check(suite, name, method,
|
|
4809
|
-
return requestCheck(client, suite, name, method,
|
|
4027
|
+
async function check(suite, name, method, path7, body, client = cloud) {
|
|
4028
|
+
return requestCheck(client, suite, name, method, path7, body);
|
|
4810
4029
|
}
|
|
4811
4030
|
}
|
|
4812
4031
|
async function runIMSuite(cloud) {
|
|
@@ -4880,27 +4099,27 @@ async function runSandboxSuite(cloud, opts) {
|
|
|
4880
4099
|
}
|
|
4881
4100
|
return checks;
|
|
4882
4101
|
}
|
|
4883
|
-
async function requestCheck(cloud, suite, name, method,
|
|
4102
|
+
async function requestCheck(cloud, suite, name, method, path7, body) {
|
|
4884
4103
|
let res;
|
|
4885
4104
|
try {
|
|
4886
|
-
res = await cloud.request(method,
|
|
4105
|
+
res = await cloud.request(method, path7, body === void 0 ? void 0 : { body });
|
|
4887
4106
|
} catch (err) {
|
|
4888
|
-
return { suite, name, status: "fail", endpoint:
|
|
4107
|
+
return { suite, name, status: "fail", endpoint: path7, detail: sanitize(err.message) };
|
|
4889
4108
|
}
|
|
4890
4109
|
if (!res.ok) {
|
|
4891
4110
|
return {
|
|
4892
4111
|
suite,
|
|
4893
4112
|
name,
|
|
4894
4113
|
status: "fail",
|
|
4895
|
-
endpoint:
|
|
4114
|
+
endpoint: path7,
|
|
4896
4115
|
detail: sanitize(`${res.status}: ${res.error?.message ?? "request failed"}`)
|
|
4897
4116
|
};
|
|
4898
4117
|
}
|
|
4899
4118
|
const data = unwrap(res.data);
|
|
4900
4119
|
if (isApiError(res.data)) {
|
|
4901
|
-
return { suite, name, status: "fail", endpoint:
|
|
4120
|
+
return { suite, name, status: "fail", endpoint: path7, detail: sanitize(apiErrorMessage(res.data)), data: res.data };
|
|
4902
4121
|
}
|
|
4903
|
-
return { suite, name, status: "pass", endpoint:
|
|
4122
|
+
return { suite, name, status: "pass", endpoint: path7, detail: summarizeData(data), data };
|
|
4904
4123
|
}
|
|
4905
4124
|
async function pollTask(cloud, taskId, timeoutMs) {
|
|
4906
4125
|
const started = Date.now();
|
|
@@ -5007,7 +4226,7 @@ function taskFrom(data) {
|
|
|
5007
4226
|
function sanitize(message) {
|
|
5008
4227
|
return message.replace(/sk-prismer-[A-Za-z0-9_-]+/g, "sk-prismer-***");
|
|
5009
4228
|
}
|
|
5010
|
-
function
|
|
4229
|
+
function parsePositiveInt2(value) {
|
|
5011
4230
|
const parsed = Number.parseInt(value, 10);
|
|
5012
4231
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
5013
4232
|
throw new Error("expected a positive integer");
|
|
@@ -5018,16 +4237,14 @@ function parsePositiveInt3(value) {
|
|
|
5018
4237
|
// src/cli/commands/daemon.ts
|
|
5019
4238
|
var import_commander8 = require("commander");
|
|
5020
4239
|
var import_node_child_process7 = require("child_process");
|
|
5021
|
-
var
|
|
5022
|
-
var
|
|
5023
|
-
var
|
|
4240
|
+
var import_node_fs14 = require("fs");
|
|
4241
|
+
var import_promises = require("timers/promises");
|
|
4242
|
+
var import_node_path10 = require("path");
|
|
5024
4243
|
|
|
5025
4244
|
// src/daemon/runner.ts
|
|
5026
4245
|
var import_node_events3 = require("events");
|
|
5027
|
-
var
|
|
5028
|
-
var
|
|
5029
|
-
var import_node_os6 = require("os");
|
|
5030
|
-
var import_node_path13 = require("path");
|
|
4246
|
+
var import_node_fs13 = require("fs");
|
|
4247
|
+
var import_node_os4 = require("os");
|
|
5031
4248
|
|
|
5032
4249
|
// src/adapters/registry.ts
|
|
5033
4250
|
var AdapterRegistry = class {
|
|
@@ -5054,34 +4271,193 @@ var AdapterRegistry = class {
|
|
|
5054
4271
|
get(name) {
|
|
5055
4272
|
return this.adapters.get(name);
|
|
5056
4273
|
}
|
|
5057
|
-
list() {
|
|
5058
|
-
return Array.from(this.adapters.values());
|
|
4274
|
+
list() {
|
|
4275
|
+
return Array.from(this.adapters.values());
|
|
4276
|
+
}
|
|
4277
|
+
size() {
|
|
4278
|
+
return this.adapters.size;
|
|
4279
|
+
}
|
|
4280
|
+
/**
|
|
4281
|
+
* Find adapters that satisfy a capability tag.
|
|
4282
|
+
*
|
|
4283
|
+
* Match rules (deterministic, sorted by name):
|
|
4284
|
+
* - exact: adapter declared the tag verbatim
|
|
4285
|
+
* - wildcard: adapter declared `code.*` → matches `code.write`
|
|
4286
|
+
*/
|
|
4287
|
+
findByCapability(capability) {
|
|
4288
|
+
const matches2 = [];
|
|
4289
|
+
for (const adapter of this.adapters.values()) {
|
|
4290
|
+
if (adapter.capabilities.includes(capability)) {
|
|
4291
|
+
matches2.push(adapter);
|
|
4292
|
+
continue;
|
|
4293
|
+
}
|
|
4294
|
+
if (adapter.capabilities.some(
|
|
4295
|
+
(tag) => tag.endsWith(".*") && capability.startsWith(tag.slice(0, -1))
|
|
4296
|
+
)) {
|
|
4297
|
+
matches2.push(adapter);
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
matches2.sort((a, b) => a.name.localeCompare(b.name));
|
|
4301
|
+
return matches2;
|
|
4302
|
+
}
|
|
4303
|
+
};
|
|
4304
|
+
|
|
4305
|
+
// src/asset-cache.ts
|
|
4306
|
+
var import_node_fs7 = require("fs");
|
|
4307
|
+
var import_node_path7 = require("path");
|
|
4308
|
+
function rowToCached(row) {
|
|
4309
|
+
return {
|
|
4310
|
+
contentHash: row.content_hash,
|
|
4311
|
+
sizeBytes: row.size_bytes,
|
|
4312
|
+
mime: row.mime,
|
|
4313
|
+
localPath: row.local_path,
|
|
4314
|
+
fetchedAt: row.fetched_at,
|
|
4315
|
+
lastUsedAt: row.last_used_at,
|
|
4316
|
+
pin: row.pin === 1
|
|
4317
|
+
};
|
|
4318
|
+
}
|
|
4319
|
+
var AssetCache = class {
|
|
4320
|
+
db;
|
|
4321
|
+
cloud;
|
|
4322
|
+
cacheDir;
|
|
4323
|
+
maxBytes;
|
|
4324
|
+
constructor(opts) {
|
|
4325
|
+
this.db = opts.db;
|
|
4326
|
+
this.cloud = opts.cloud;
|
|
4327
|
+
this.cacheDir = opts.cacheDir;
|
|
4328
|
+
this.maxBytes = opts.maxBytes ?? 5 * 1024 * 1024 * 1024;
|
|
4329
|
+
if (!(0, import_node_fs7.existsSync)(this.cacheDir)) {
|
|
4330
|
+
(0, import_node_fs7.mkdirSync)(this.cacheDir, { recursive: true });
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
/** Local file path for a content hash. Files split by first 2 chars for fs scaling. */
|
|
4334
|
+
pathFor(hash) {
|
|
4335
|
+
return (0, import_node_path7.join)(this.cacheDir, hash.slice(0, 2), hash);
|
|
4336
|
+
}
|
|
4337
|
+
get(hash) {
|
|
4338
|
+
const row = this.db.prepare("SELECT * FROM cached_assets WHERE content_hash = ?").get(hash);
|
|
4339
|
+
if (!row) return void 0;
|
|
4340
|
+
if (!(0, import_node_fs7.existsSync)(row.local_path)) {
|
|
4341
|
+
this.db.prepare("DELETE FROM cached_assets WHERE content_hash = ?").run(hash);
|
|
4342
|
+
return void 0;
|
|
4343
|
+
}
|
|
4344
|
+
this.touch(hash);
|
|
4345
|
+
return rowToCached({ ...row, last_used_at: Date.now() });
|
|
4346
|
+
}
|
|
4347
|
+
/** Bump `last_used_at` (call when an asset is consulted). */
|
|
4348
|
+
touch(hash) {
|
|
4349
|
+
this.db.prepare("UPDATE cached_assets SET last_used_at = ? WHERE content_hash = ?").run(Date.now(), hash);
|
|
4350
|
+
}
|
|
4351
|
+
pin(hash) {
|
|
4352
|
+
this.db.prepare("UPDATE cached_assets SET pin = 1 WHERE content_hash = ?").run(hash);
|
|
4353
|
+
}
|
|
4354
|
+
unpin(hash) {
|
|
4355
|
+
this.db.prepare("UPDATE cached_assets SET pin = 0 WHERE content_hash = ?").run(hash);
|
|
4356
|
+
}
|
|
4357
|
+
/**
|
|
4358
|
+
* Get or fetch by hash. On miss, downloads the raw bytes and caches them.
|
|
4359
|
+
*
|
|
4360
|
+
* Why two endpoints: `GET /api/im/assets/by-hash/:hash` returns the JSON
|
|
4361
|
+
* `AssetDTO` (id, contentHash, storageUri, …) — *metadata only*. The raw
|
|
4362
|
+
* bytes live at `GET /api/im/assets/:id`. If `opts.assetId` is supplied
|
|
4363
|
+
* the caller already has the id from an `AssetRef`, so we go straight to
|
|
4364
|
+
* the byte endpoint; otherwise we resolve `hash → id` first (one extra
|
|
4365
|
+
* round-trip on the metadata route) before downloading the body.
|
|
4366
|
+
*
|
|
4367
|
+
* Earlier this method called `by-hash` and treated the JSON body as the
|
|
4368
|
+
* asset content — the daemon stored the 500-byte metadata blob on disk
|
|
4369
|
+
* and `resolveAssetRefs` inlined that JSON into the prompt. The agent
|
|
4370
|
+
* saw `{"id":"…","storageUri":"…"}` instead of the user's file and
|
|
4371
|
+
* concluded the upload had failed.
|
|
4372
|
+
*/
|
|
4373
|
+
async getOrFetch(hash, opts) {
|
|
4374
|
+
const existing = this.get(hash);
|
|
4375
|
+
if (existing) return existing;
|
|
4376
|
+
const wsParam = opts?.workspaceIdHint ? `?wsId=${encodeURIComponent(opts.workspaceIdHint)}` : "";
|
|
4377
|
+
let assetId = opts?.assetId;
|
|
4378
|
+
if (!assetId) {
|
|
4379
|
+
const metaRes = await this.cloud.fetchRaw(
|
|
4380
|
+
`/api/im/assets/by-hash/${encodeURIComponent(hash)}${wsParam}`,
|
|
4381
|
+
{ signal: opts?.signal }
|
|
4382
|
+
);
|
|
4383
|
+
if (!metaRes.ok) {
|
|
4384
|
+
throw new Error(`Asset metadata fetch failed for ${hash}: HTTP ${metaRes.status}`);
|
|
4385
|
+
}
|
|
4386
|
+
const metaJson = await metaRes.json();
|
|
4387
|
+
if (!metaJson?.ok || typeof metaJson.data?.id !== "string") {
|
|
4388
|
+
throw new Error(`Asset metadata payload missing id for ${hash}`);
|
|
4389
|
+
}
|
|
4390
|
+
assetId = metaJson.data.id;
|
|
4391
|
+
}
|
|
4392
|
+
const res = await this.cloud.fetchRaw(`/api/im/assets/${encodeURIComponent(assetId)}`, {
|
|
4393
|
+
signal: opts?.signal
|
|
4394
|
+
});
|
|
4395
|
+
if (!res.ok) {
|
|
4396
|
+
throw new Error(`Asset bytes fetch failed for ${hash} (id=${assetId}): HTTP ${res.status}`);
|
|
4397
|
+
}
|
|
4398
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
4399
|
+
const localPath = this.pathFor(hash);
|
|
4400
|
+
if (!(0, import_node_fs7.existsSync)((0, import_node_path7.dirname)(localPath))) {
|
|
4401
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path7.dirname)(localPath), { recursive: true });
|
|
4402
|
+
}
|
|
4403
|
+
(0, import_node_fs7.writeFileSync)(localPath, buf);
|
|
4404
|
+
const mime = res.headers.get("content-type");
|
|
4405
|
+
const now = Date.now();
|
|
4406
|
+
this.db.prepare(
|
|
4407
|
+
`INSERT OR REPLACE INTO cached_assets
|
|
4408
|
+
(content_hash, size_bytes, mime, local_path, fetched_at, last_used_at, pin)
|
|
4409
|
+
VALUES (?, ?, ?, ?, ?, ?, 0)`
|
|
4410
|
+
).run(hash, buf.length, mime, localPath, now, now);
|
|
4411
|
+
this.evictIfOver();
|
|
4412
|
+
return {
|
|
4413
|
+
contentHash: hash,
|
|
4414
|
+
sizeBytes: buf.length,
|
|
4415
|
+
mime,
|
|
4416
|
+
localPath,
|
|
4417
|
+
fetchedAt: now,
|
|
4418
|
+
lastUsedAt: now,
|
|
4419
|
+
pin: false
|
|
4420
|
+
};
|
|
4421
|
+
}
|
|
4422
|
+
/** Insert an already-on-disk asset into the cache (e.g., asset just produced by adapter). */
|
|
4423
|
+
registerLocal(hash, localPath, mime) {
|
|
4424
|
+
const size = (0, import_node_fs7.statSync)(localPath).size;
|
|
4425
|
+
const now = Date.now();
|
|
4426
|
+
this.db.prepare(
|
|
4427
|
+
`INSERT OR REPLACE INTO cached_assets
|
|
4428
|
+
(content_hash, size_bytes, mime, local_path, fetched_at, last_used_at, pin)
|
|
4429
|
+
VALUES (?, ?, ?, ?, ?, ?, 1)`
|
|
4430
|
+
).run(hash, size, mime ?? null, localPath, now, now);
|
|
4431
|
+
return { contentHash: hash, sizeBytes: size, mime: mime ?? null, localPath, fetchedAt: now, lastUsedAt: now, pin: true };
|
|
5059
4432
|
}
|
|
5060
|
-
|
|
5061
|
-
|
|
4433
|
+
/** Total bytes across all rows (incl. pinned). */
|
|
4434
|
+
totalBytes() {
|
|
4435
|
+
const row = this.db.prepare("SELECT COALESCE(SUM(size_bytes),0) AS total FROM cached_assets").get();
|
|
4436
|
+
return row.total;
|
|
5062
4437
|
}
|
|
5063
|
-
/**
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
)) {
|
|
5080
|
-
matches2.push(adapter);
|
|
4438
|
+
/** Run LRU eviction until under cap. Pinned rows are never evicted. */
|
|
4439
|
+
evictIfOver() {
|
|
4440
|
+
let total = this.totalBytes();
|
|
4441
|
+
if (total <= this.maxBytes) return { removed: 0, freed: 0 };
|
|
4442
|
+
const candidates = this.db.prepare(
|
|
4443
|
+
`SELECT content_hash, size_bytes, local_path FROM cached_assets
|
|
4444
|
+
WHERE pin = 0
|
|
4445
|
+
ORDER BY last_used_at ASC`
|
|
4446
|
+
).all();
|
|
4447
|
+
let freed = 0;
|
|
4448
|
+
let removed = 0;
|
|
4449
|
+
for (const row of candidates) {
|
|
4450
|
+
if (total <= this.maxBytes) break;
|
|
4451
|
+
try {
|
|
4452
|
+
if ((0, import_node_fs7.existsSync)(row.local_path)) (0, import_node_fs7.unlinkSync)(row.local_path);
|
|
4453
|
+
} catch {
|
|
5081
4454
|
}
|
|
4455
|
+
this.db.prepare("DELETE FROM cached_assets WHERE content_hash = ?").run(row.content_hash);
|
|
4456
|
+
freed += row.size_bytes;
|
|
4457
|
+
total -= row.size_bytes;
|
|
4458
|
+
removed += 1;
|
|
5082
4459
|
}
|
|
5083
|
-
|
|
5084
|
-
return matches2;
|
|
4460
|
+
return { removed, freed };
|
|
5085
4461
|
}
|
|
5086
4462
|
};
|
|
5087
4463
|
|
|
@@ -5338,20 +4714,20 @@ var UriResolver = class {
|
|
|
5338
4714
|
}
|
|
5339
4715
|
return { texts: all, resolvedHashes: dedupe(allHashes) };
|
|
5340
4716
|
}
|
|
5341
|
-
async lookupFile(workspaceId,
|
|
4717
|
+
async lookupFile(workspaceId, path7, signal) {
|
|
5342
4718
|
const local = this.db.prepare(
|
|
5343
4719
|
"SELECT asset_id, content_hash, version FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?"
|
|
5344
|
-
).get(workspaceId,
|
|
4720
|
+
).get(workspaceId, path7);
|
|
5345
4721
|
if (local) return local;
|
|
5346
4722
|
const remote = await this.cloud.get(
|
|
5347
|
-
`/api/im/workspaces/${encodeURIComponent(workspaceId)}/files?path=${encodeURIComponent(
|
|
4723
|
+
`/api/im/workspaces/${encodeURIComponent(workspaceId)}/files?path=${encodeURIComponent(path7)}`,
|
|
5348
4724
|
{ signal }
|
|
5349
4725
|
);
|
|
5350
4726
|
this.db.prepare(
|
|
5351
4727
|
`INSERT OR REPLACE INTO workspace_files_mirror
|
|
5352
4728
|
(workspace_id, path, asset_id, content_hash, version, synced_at, dirty)
|
|
5353
4729
|
VALUES (?, ?, ?, ?, ?, ?, 0)`
|
|
5354
|
-
).run(workspaceId,
|
|
4730
|
+
).run(workspaceId, path7, remote.assetId, remote.contentHash, remote.version, Date.now());
|
|
5355
4731
|
return { asset_id: remote.assetId, content_hash: remote.contentHash, version: remote.version };
|
|
5356
4732
|
}
|
|
5357
4733
|
};
|
|
@@ -5360,96 +4736,11 @@ function dedupe(xs) {
|
|
|
5360
4736
|
}
|
|
5361
4737
|
|
|
5362
4738
|
// src/daemon/dispatch.ts
|
|
5363
|
-
var
|
|
4739
|
+
var import_node_fs8 = require("fs");
|
|
5364
4740
|
var path2 = __toESM(require("path"), 1);
|
|
5365
|
-
|
|
5366
|
-
// src/daemon/skill-sync.ts
|
|
5367
|
-
var import_node_fs10 = require("fs");
|
|
5368
|
-
var import_node_path11 = require("path");
|
|
5369
|
-
var import_node_crypto2 = require("crypto");
|
|
5370
|
-
async function syncInstalledSkillsForDispatch(profile, agentImUserId, cloud, signal) {
|
|
5371
|
-
if (!agentImUserId) return { synced: 0, skipped: 0, unchanged: 0 };
|
|
5372
|
-
const data = await cloud.get(
|
|
5373
|
-
`/api/im/skills/installed?agentId=${encodeURIComponent(agentImUserId)}`,
|
|
5374
|
-
{ signal }
|
|
5375
|
-
);
|
|
5376
|
-
const entries = normalizeInstalledSkills(data);
|
|
5377
|
-
const skillsRoot = resolveSkillsRoot(profile);
|
|
5378
|
-
if (!skillsRoot) return { synced: 0, skipped: 0, unchanged: 0 };
|
|
5379
|
-
let synced = 0;
|
|
5380
|
-
let skipped = 0;
|
|
5381
|
-
let unchanged = 0;
|
|
5382
|
-
for (const entry of entries) {
|
|
5383
|
-
const slug = sanitizeSlug(trimmedStringFrom(entry.skill?.slug) ?? trimmedStringFrom(entry.slug));
|
|
5384
|
-
const content = contentStringFrom(entry.skill?.content) ?? contentStringFrom(entry.content);
|
|
5385
|
-
if (!slug || !content) {
|
|
5386
|
-
skipped++;
|
|
5387
|
-
if (slug && !content) {
|
|
5388
|
-
process.stderr.write(`[daemon] skill sync skipped ${slug}: missing content
|
|
5389
|
-
`);
|
|
5390
|
-
}
|
|
5391
|
-
continue;
|
|
5392
|
-
}
|
|
5393
|
-
const skillDir = (0, import_node_path11.join)(skillsRoot, slug);
|
|
5394
|
-
await import_node_fs10.promises.mkdir(skillDir, { recursive: true });
|
|
5395
|
-
const target = (0, import_node_path11.join)(skillDir, "SKILL.md");
|
|
5396
|
-
const existing = await import_node_fs10.promises.readFile(target, "utf8").catch(() => null);
|
|
5397
|
-
if (existing != null && sha2562(existing) === sha2562(content)) {
|
|
5398
|
-
unchanged++;
|
|
5399
|
-
continue;
|
|
5400
|
-
}
|
|
5401
|
-
await import_node_fs10.promises.writeFile(target, content, "utf8");
|
|
5402
|
-
synced++;
|
|
5403
|
-
}
|
|
5404
|
-
return { synced, skipped, unchanged };
|
|
5405
|
-
}
|
|
5406
|
-
function resolveSkillsRoot(profile) {
|
|
5407
|
-
if (profile.adapterName === "hermes") return (0, import_node_path11.join)(getHermesProfileDir(getHermesProfileName(profile)), "skills");
|
|
5408
|
-
if (profile.adapterName === "openclaw") return getOpenClawSkillsDir(profile);
|
|
5409
|
-
return null;
|
|
5410
|
-
}
|
|
5411
|
-
function sha2562(value) {
|
|
5412
|
-
return (0, import_node_crypto2.createHash)("sha256").update(value).digest("hex");
|
|
5413
|
-
}
|
|
5414
|
-
function normalizeInstalledSkills(data) {
|
|
5415
|
-
if (Array.isArray(data)) return data.filter(isInstalledSkillEntry);
|
|
5416
|
-
if (!data || typeof data !== "object" || Array.isArray(data)) return [];
|
|
5417
|
-
const record = data;
|
|
5418
|
-
const nested = record.data;
|
|
5419
|
-
if (Array.isArray(nested)) return nested.filter(isInstalledSkillEntry);
|
|
5420
|
-
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
5421
|
-
const skills2 = nested.skills;
|
|
5422
|
-
if (Array.isArray(skills2)) return skills2.filter(isInstalledSkillEntry);
|
|
5423
|
-
}
|
|
5424
|
-
const skills = record.skills;
|
|
5425
|
-
if (Array.isArray(skills)) return skills.filter(isInstalledSkillEntry);
|
|
5426
|
-
return [];
|
|
5427
|
-
}
|
|
5428
|
-
function isInstalledSkillEntry(value) {
|
|
5429
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
5430
|
-
}
|
|
5431
|
-
function trimmedStringFrom(value) {
|
|
5432
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
5433
|
-
}
|
|
5434
|
-
function contentStringFrom(value) {
|
|
5435
|
-
return typeof value === "string" && value.trim() ? value : void 0;
|
|
5436
|
-
}
|
|
5437
|
-
function sanitizeSlug(value) {
|
|
5438
|
-
if (!value) return void 0;
|
|
5439
|
-
const safe = value.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
5440
|
-
return safe || void 0;
|
|
5441
|
-
}
|
|
5442
|
-
|
|
5443
|
-
// src/daemon/dispatch.ts
|
|
5444
4741
|
var DEFAULT_CONTEXT_MAX = 8e3;
|
|
5445
4742
|
var GOAL_CONTEXT_MAX = 4;
|
|
5446
4743
|
var MEMORY_DIGEST_MAX_BYTES = 4e3;
|
|
5447
|
-
var DEFAULT_OPERATING_PRINCIPLES = [
|
|
5448
|
-
"- Decide and act; only request human approval for destructive operations or irreversible external effects.",
|
|
5449
|
-
"- Assignable work should become explicit tasks; do not leave concrete work as chat discussion only.",
|
|
5450
|
-
"- Use workspace asset tools for uploaded files: search/describe first, then read bounded ranges; do not claim file contents unless a tool call succeeded.",
|
|
5451
|
-
"- Keep users informed with concise status and concrete next steps."
|
|
5452
|
-
].join("\n");
|
|
5453
4744
|
var ASSET_INLINE_FULL_MAX_BYTES = 64 * 1024;
|
|
5454
4745
|
var ASSET_INLINE_TRUNCATE_BYTES = 16 * 1024;
|
|
5455
4746
|
async function handleDispatch(payload, requestId, deps) {
|
|
@@ -5460,7 +4751,7 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
5460
4751
|
const outboxDir = outboxBase ? path2.join(outboxBase, "_outbox") : null;
|
|
5461
4752
|
if (outboxDir) {
|
|
5462
4753
|
try {
|
|
5463
|
-
await
|
|
4754
|
+
await import_node_fs8.promises.mkdir(outboxDir, { recursive: true });
|
|
5464
4755
|
} catch (err) {
|
|
5465
4756
|
process.stderr.write(
|
|
5466
4757
|
`[daemon] outbox provision failed task=${taskId} dir=${outboxDir}: ${err.message}
|
|
@@ -5487,18 +4778,6 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
5487
4778
|
sendReply(deps.ws, reply, requestId);
|
|
5488
4779
|
return reply;
|
|
5489
4780
|
}
|
|
5490
|
-
try {
|
|
5491
|
-
const skillSync = await syncInstalledSkillsForDispatch(profile, agentImUserId, deps.cloud, deps.signal);
|
|
5492
|
-
if (skillSync.synced > 0 || skillSync.skipped > 0) {
|
|
5493
|
-
process.stderr.write(
|
|
5494
|
-
`[daemon] skill sync profile=${profile.id} adapter=${profile.adapterName} synced=${skillSync.synced} skipped=${skillSync.skipped}
|
|
5495
|
-
`
|
|
5496
|
-
);
|
|
5497
|
-
}
|
|
5498
|
-
} catch (err) {
|
|
5499
|
-
process.stderr.write(`[daemon] skill sync skipped profile=${profile.id}: ${err.message}
|
|
5500
|
-
`);
|
|
5501
|
-
}
|
|
5502
4781
|
let hashRefResult = { text: payload.prompt, resolutions: [] };
|
|
5503
4782
|
if (deps.assetMetadataIndexes && profile.workspaceId) {
|
|
5504
4783
|
const assetIndex = deps.assetMetadataIndexes.get(profile.workspaceId);
|
|
@@ -5548,7 +4827,6 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
5548
4827
|
`
|
|
5549
4828
|
);
|
|
5550
4829
|
}
|
|
5551
|
-
const operatingPrinciples = resolveOperatingPrinciples(profile.config);
|
|
5552
4830
|
const taskInput = {
|
|
5553
4831
|
taskId,
|
|
5554
4832
|
prompt: adapterPrompt,
|
|
@@ -5557,7 +4835,6 @@ async function handleDispatch(payload, requestId, deps) {
|
|
|
5557
4835
|
conversationId: payload.conversationId,
|
|
5558
4836
|
prismerGoals: goalContext.map(toGoalMirrorPayload),
|
|
5559
4837
|
...profileSystemPrompt ? { systemPrompt: profileSystemPrompt } : {},
|
|
5560
|
-
...operatingPrinciples ? { operatingPrinciples } : {},
|
|
5561
4838
|
// Wave-9: spawn-style adapters (claude-code, codex, openclaw) read
|
|
5562
4839
|
// this and inject `PRISMER_OUTBOX_DIR=<abs>` into the child env so
|
|
5563
4840
|
// the LLM tool can resolve the path even if it doesn't read the
|
|
@@ -5828,7 +5105,7 @@ async function resolveAssetRefs(refs, cache) {
|
|
|
5828
5105
|
const inlineCandidate = isTextLikeMime(effectiveMime);
|
|
5829
5106
|
if (inlineCandidate) {
|
|
5830
5107
|
try {
|
|
5831
|
-
const buf = (0,
|
|
5108
|
+
const buf = (0, import_node_fs8.readFileSync)(cached.localPath);
|
|
5832
5109
|
let strategy;
|
|
5833
5110
|
let body;
|
|
5834
5111
|
if (buf.byteLength <= ASSET_INLINE_FULL_MAX_BYTES) {
|
|
@@ -5902,36 +5179,6 @@ ${memory.digest.trim()}
|
|
|
5902
5179
|
|
|
5903
5180
|
${prompt}`;
|
|
5904
5181
|
}
|
|
5905
|
-
function resolveOperatingPrinciples(config) {
|
|
5906
|
-
const cfg = readRecord(config);
|
|
5907
|
-
const roleTemplate = readRecord(cfg.roleTemplate);
|
|
5908
|
-
const principles = stringifyPrinciples(cfg.operatingPrinciples) ?? stringifyPrinciples(roleTemplate.operatingPrinciples) ?? DEFAULT_OPERATING_PRINCIPLES;
|
|
5909
|
-
const policy = typeof cfg.approvalPolicy === "string" ? cfg.approvalPolicy : roleTemplate.approvalPolicy;
|
|
5910
|
-
if (policy === "strict") {
|
|
5911
|
-
return `${principles}
|
|
5912
|
-
- Approval policy: strict. Request human approval before destructive, external, spend, access, or publish operations.`;
|
|
5913
|
-
}
|
|
5914
|
-
if (policy === "autonomous") {
|
|
5915
|
-
return `${principles}
|
|
5916
|
-
- Approval policy: autonomous. Do not request human approval unless a platform policy explicitly requires it.`;
|
|
5917
|
-
}
|
|
5918
|
-
return `${principles}
|
|
5919
|
-
- Approval policy: auto-low-risk. Proceed on reversible low-risk work; request human approval for destructive or irreversible operations.`;
|
|
5920
|
-
}
|
|
5921
|
-
function stringifyPrinciples(value) {
|
|
5922
|
-
if (typeof value === "string" && value.trim()) return value.trim();
|
|
5923
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
5924
|
-
const record = value;
|
|
5925
|
-
for (const key of ["en", "zh"]) {
|
|
5926
|
-
const text = record[key];
|
|
5927
|
-
if (typeof text === "string" && text.trim()) return text.trim();
|
|
5928
|
-
}
|
|
5929
|
-
for (const text of Object.values(record)) {
|
|
5930
|
-
if (typeof text === "string" && text.trim()) return text.trim();
|
|
5931
|
-
}
|
|
5932
|
-
}
|
|
5933
|
-
return void 0;
|
|
5934
|
-
}
|
|
5935
5182
|
function appendOutboxInstruction(prompt, outboxDir, profileConfig) {
|
|
5936
5183
|
if (!outboxDir) return prompt;
|
|
5937
5184
|
if (profileConfig?.disableOutboxHints === true) return prompt;
|
|
@@ -5950,9 +5197,6 @@ function appendChannelContext(prompt, payload, profile) {
|
|
|
5950
5197
|
const type = payload.conversationType === "direct" || payload.conversationType === "group" ? payload.conversationType : "unknown";
|
|
5951
5198
|
const youAre = profile.agentUsername || payload.agentImUserId || "this agent";
|
|
5952
5199
|
const lines = ["[Channel context]", `Conversation type: ${type}`, `You are: ${youAre}`];
|
|
5953
|
-
if (payload.conversationId) {
|
|
5954
|
-
lines.push(`Conversation ID: ${payload.conversationId}`);
|
|
5955
|
-
}
|
|
5956
5200
|
const participants = Array.isArray(payload.participants) ? payload.participants : [];
|
|
5957
5201
|
if (participants.length > 0) {
|
|
5958
5202
|
if (type === "direct") {
|
|
@@ -6176,8 +5420,8 @@ function updatedTime(task) {
|
|
|
6176
5420
|
|
|
6177
5421
|
// src/daemon/local-server.ts
|
|
6178
5422
|
var import_node_http = require("http");
|
|
6179
|
-
var
|
|
6180
|
-
var
|
|
5423
|
+
var import_node_crypto = require("crypto");
|
|
5424
|
+
var import_node_fs9 = require("fs");
|
|
6181
5425
|
var path3 = __toESM(require("path"), 1);
|
|
6182
5426
|
var LocalServer = class {
|
|
6183
5427
|
constructor(opts) {
|
|
@@ -6330,7 +5574,7 @@ var LocalServer = class {
|
|
|
6330
5574
|
async handleSnapshot(req, res) {
|
|
6331
5575
|
const root = this.opts.snapshotRoot ?? "/workspace";
|
|
6332
5576
|
try {
|
|
6333
|
-
const st = await
|
|
5577
|
+
const st = await import_node_fs9.promises.stat(root);
|
|
6334
5578
|
if (!st.isDirectory()) {
|
|
6335
5579
|
respond(res, 400, { error: "snapshot_root_not_directory", rootPath: root });
|
|
6336
5580
|
return;
|
|
@@ -6406,7 +5650,7 @@ var LocalServer = class {
|
|
|
6406
5650
|
respond(res, 400, { error: "missing_taskId" });
|
|
6407
5651
|
return;
|
|
6408
5652
|
}
|
|
6409
|
-
const runId = (0,
|
|
5653
|
+
const runId = (0, import_node_crypto.randomUUID)();
|
|
6410
5654
|
try {
|
|
6411
5655
|
this.opts.onDispatch?.(payload, runId);
|
|
6412
5656
|
} catch (err) {
|
|
@@ -6461,7 +5705,7 @@ async function walkAndDigest(root, current) {
|
|
|
6461
5705
|
const out = [];
|
|
6462
5706
|
let entries;
|
|
6463
5707
|
try {
|
|
6464
|
-
entries = await
|
|
5708
|
+
entries = await import_node_fs9.promises.readdir(current);
|
|
6465
5709
|
} catch (err) {
|
|
6466
5710
|
if (err.code === "ENOENT") return out;
|
|
6467
5711
|
throw err;
|
|
@@ -6472,7 +5716,7 @@ async function walkAndDigest(root, current) {
|
|
|
6472
5716
|
const rel = path3.relative(root, full);
|
|
6473
5717
|
let st;
|
|
6474
5718
|
try {
|
|
6475
|
-
st = await
|
|
5719
|
+
st = await import_node_fs9.promises.stat(full);
|
|
6476
5720
|
} catch {
|
|
6477
5721
|
continue;
|
|
6478
5722
|
}
|
|
@@ -6483,11 +5727,11 @@ async function walkAndDigest(root, current) {
|
|
|
6483
5727
|
continue;
|
|
6484
5728
|
}
|
|
6485
5729
|
if (!st.isFile()) continue;
|
|
6486
|
-
const buf = await
|
|
6487
|
-
const
|
|
5730
|
+
const buf = await import_node_fs9.promises.readFile(full);
|
|
5731
|
+
const sha2562 = (0, import_node_crypto.createHash)("sha256").update(buf).digest("hex");
|
|
6488
5732
|
out.push({
|
|
6489
5733
|
path: rel,
|
|
6490
|
-
sha256:
|
|
5734
|
+
sha256: sha2562,
|
|
6491
5735
|
sizeBytes: st.size,
|
|
6492
5736
|
mtime: Math.floor(st.mtimeMs)
|
|
6493
5737
|
});
|
|
@@ -6503,7 +5747,7 @@ var path5 = __toESM(require("path"), 1);
|
|
|
6503
5747
|
var import_better_sqlite33 = __toESM(require("better-sqlite3"), 1);
|
|
6504
5748
|
var fs3 = __toESM(require("fs"), 1);
|
|
6505
5749
|
var path4 = __toESM(require("path"), 1);
|
|
6506
|
-
var
|
|
5750
|
+
var import_node_crypto2 = require("crypto");
|
|
6507
5751
|
|
|
6508
5752
|
// src/daemon/memory/crypto.ts
|
|
6509
5753
|
function sealPlaintext(content) {
|
|
@@ -6654,9 +5898,9 @@ var MemoryStore = class {
|
|
|
6654
5898
|
const row = db.prepare("SELECT * FROM memory_pages WHERE workspaceId = ? AND id = ?").get(this.opts.workspaceId, pageId);
|
|
6655
5899
|
return row ? this.rowToPage(row) : null;
|
|
6656
5900
|
}
|
|
6657
|
-
loadContent(pageId,
|
|
5901
|
+
loadContent(pageId, version2) {
|
|
6658
5902
|
const db = this.requireDb();
|
|
6659
|
-
const targetVersion =
|
|
5903
|
+
const targetVersion = version2 ?? db.prepare("SELECT version FROM memory_pages WHERE workspaceId = ? AND id = ?").get(this.opts.workspaceId, pageId)?.version;
|
|
6660
5904
|
if (targetVersion === void 0) return null;
|
|
6661
5905
|
const row = db.prepare(
|
|
6662
5906
|
`SELECT c.pageId, c.version, c.payloadKind, c.payloadValue
|
|
@@ -6694,7 +5938,7 @@ var MemoryStore = class {
|
|
|
6694
5938
|
}
|
|
6695
5939
|
const db = this.requireDb();
|
|
6696
5940
|
const now = Date.now();
|
|
6697
|
-
const contentHash =
|
|
5941
|
+
const contentHash = sha256(input.content);
|
|
6698
5942
|
const payload = sealPlaintext(input.content);
|
|
6699
5943
|
if (payload.kind !== "inline") {
|
|
6700
5944
|
throw new Error("MemoryStore.write: non-inline payload not yet supported in phase-0");
|
|
@@ -6705,7 +5949,7 @@ var MemoryStore = class {
|
|
|
6705
5949
|
const existing = db.prepare(
|
|
6706
5950
|
"SELECT id, version, createdAt FROM memory_pages WHERE workspaceId = ? AND path = ?"
|
|
6707
5951
|
).get(this.opts.workspaceId, input.path);
|
|
6708
|
-
const pageId = existing?.id ?? `page_${(0,
|
|
5952
|
+
const pageId = existing?.id ?? `page_${(0, import_node_crypto2.randomUUID)().replace(/-/g, "").slice(0, 22)}`;
|
|
6709
5953
|
const newVersion = (existing?.version ?? 0) + 1;
|
|
6710
5954
|
const createdAt = existing?.createdAt ?? now;
|
|
6711
5955
|
const staleFlag = input.stale ? 1 : 0;
|
|
@@ -6888,8 +6132,8 @@ var MemoryStore = class {
|
|
|
6888
6132
|
};
|
|
6889
6133
|
}
|
|
6890
6134
|
};
|
|
6891
|
-
function
|
|
6892
|
-
return (0,
|
|
6135
|
+
function sha256(s) {
|
|
6136
|
+
return (0, import_node_crypto2.createHash)("sha256").update(s, "utf8").digest("hex");
|
|
6893
6137
|
}
|
|
6894
6138
|
|
|
6895
6139
|
// src/daemon/memory/search.ts
|
|
@@ -6995,7 +6239,7 @@ function clamp(n, lo, hi) {
|
|
|
6995
6239
|
}
|
|
6996
6240
|
|
|
6997
6241
|
// src/daemon/memory/outbox.ts
|
|
6998
|
-
var
|
|
6242
|
+
var import_node_crypto3 = require("crypto");
|
|
6999
6243
|
|
|
7000
6244
|
// src/daemon/memory/envelope.ts
|
|
7001
6245
|
var import_zod6 = require("zod");
|
|
@@ -7128,7 +6372,7 @@ var MemoryOutbox = class {
|
|
|
7128
6372
|
const now = Date.now();
|
|
7129
6373
|
const parsed = MemoryOutboxEnvelope.safeParse(event);
|
|
7130
6374
|
if (!parsed.success) {
|
|
7131
|
-
const dlId = `dl_${(0,
|
|
6375
|
+
const dlId = `dl_${(0, import_node_crypto3.randomUUID)()}`;
|
|
7132
6376
|
db.prepare(
|
|
7133
6377
|
`INSERT INTO memory_outbox_dead_letter (id, eventType, rawJson, errorJson, createdAt)
|
|
7134
6378
|
VALUES (?, ?, ?, ?, ?)`
|
|
@@ -7145,7 +6389,7 @@ var MemoryOutbox = class {
|
|
|
7145
6389
|
if (validated.eventType === "memory.feedback") {
|
|
7146
6390
|
const check = validateFeedbackTarget(validated);
|
|
7147
6391
|
if (!check.ok) {
|
|
7148
|
-
const dlId = `dl_${(0,
|
|
6392
|
+
const dlId = `dl_${(0, import_node_crypto3.randomUUID)()}`;
|
|
7149
6393
|
db.prepare(
|
|
7150
6394
|
`INSERT INTO memory_outbox_dead_letter (id, eventType, rawJson, errorJson, createdAt)
|
|
7151
6395
|
VALUES (?, ?, ?, ?, ?)`
|
|
@@ -7159,7 +6403,7 @@ var MemoryOutbox = class {
|
|
|
7159
6403
|
return { id: dlId, deadLetter: true };
|
|
7160
6404
|
}
|
|
7161
6405
|
}
|
|
7162
|
-
const rowId = `out_${(0,
|
|
6406
|
+
const rowId = `out_${(0, import_node_crypto3.randomUUID)()}`;
|
|
7163
6407
|
try {
|
|
7164
6408
|
db.prepare(
|
|
7165
6409
|
`INSERT INTO memory_outbox (id, eventType, envelopeJson, idempotencyKey, status, createdAt)
|
|
@@ -7739,7 +6983,7 @@ function clamp2(n, lo, hi) {
|
|
|
7739
6983
|
}
|
|
7740
6984
|
|
|
7741
6985
|
// src/daemon/memory/fork/tracing.ts
|
|
7742
|
-
var
|
|
6986
|
+
var import_node_crypto4 = require("crypto");
|
|
7743
6987
|
|
|
7744
6988
|
// src/daemon/memory/fork/runner.ts
|
|
7745
6989
|
var DEFAULT_SNIPPET_MAX_BYTES = 500;
|
|
@@ -7747,8 +6991,8 @@ function finalizeSelected(runtime, workspaceId, paths, snippetMaxBytes = DEFAULT
|
|
|
7747
6991
|
const slot = runtime.peek(workspaceId);
|
|
7748
6992
|
if (!slot) return [];
|
|
7749
6993
|
const out = [];
|
|
7750
|
-
for (const
|
|
7751
|
-
const page = slot.store.loadByPath(
|
|
6994
|
+
for (const path7 of paths) {
|
|
6995
|
+
const page = slot.store.loadByPath(path7);
|
|
7752
6996
|
if (!page) continue;
|
|
7753
6997
|
const content = slot.store.loadContent(page.id)?.content ?? "";
|
|
7754
6998
|
out.push({
|
|
@@ -8003,36 +7247,189 @@ function handleRecallFinalize(runtime, body, res) {
|
|
|
8003
7247
|
if (typeof b.workspaceId !== "string" || !b.workspaceId) {
|
|
8004
7248
|
return respond400(res, "workspaceId is required");
|
|
8005
7249
|
}
|
|
8006
|
-
if (!Array.isArray(b.paths)) {
|
|
8007
|
-
return respond400(res, "paths[] is required");
|
|
7250
|
+
if (!Array.isArray(b.paths)) {
|
|
7251
|
+
return respond400(res, "paths[] is required");
|
|
7252
|
+
}
|
|
7253
|
+
const paths = [];
|
|
7254
|
+
for (const p of b.paths) {
|
|
7255
|
+
if (typeof p === "string" && p.length > 0) paths.push(p);
|
|
7256
|
+
}
|
|
7257
|
+
const snippetMaxBytes = typeof b.snippetMaxBytes === "number" && b.snippetMaxBytes > 0 ? b.snippetMaxBytes : void 0;
|
|
7258
|
+
const results = finalizeSelected(runtime, b.workspaceId, paths, snippetMaxBytes);
|
|
7259
|
+
respond2(res, 200, { workspaceId: b.workspaceId, results });
|
|
7260
|
+
return true;
|
|
7261
|
+
}
|
|
7262
|
+
function parsePrismerUri(uri) {
|
|
7263
|
+
const PREFIX2 = "prismer://workspace/";
|
|
7264
|
+
if (!uri.startsWith(PREFIX2)) return null;
|
|
7265
|
+
const rest = uri.slice(PREFIX2.length);
|
|
7266
|
+
const segMemory = "/memory/";
|
|
7267
|
+
const memoryIdx = rest.indexOf(segMemory);
|
|
7268
|
+
if (memoryIdx <= 0) return null;
|
|
7269
|
+
const workspaceId = rest.slice(0, memoryIdx);
|
|
7270
|
+
const path7 = rest.slice(memoryIdx + segMemory.length);
|
|
7271
|
+
if (!workspaceId || !path7) return null;
|
|
7272
|
+
return { workspaceId, path: path7 };
|
|
7273
|
+
}
|
|
7274
|
+
|
|
7275
|
+
// src/daemon/asset/metadata-index.ts
|
|
7276
|
+
var import_node_fs10 = require("fs");
|
|
7277
|
+
var import_node_path8 = require("path");
|
|
7278
|
+
var DEFAULT_LIMIT = 8;
|
|
7279
|
+
var PULL_PAGE_SIZE = 500;
|
|
7280
|
+
var THROTTLE_MS = 3e4;
|
|
7281
|
+
function rowToMetadata(row) {
|
|
7282
|
+
return {
|
|
7283
|
+
assetId: row.asset_id,
|
|
7284
|
+
contentHash: row.content_hash,
|
|
7285
|
+
filename: row.filename,
|
|
7286
|
+
folderPath: row.folder_path,
|
|
7287
|
+
mime: row.mime,
|
|
7288
|
+
kind: row.kind,
|
|
7289
|
+
sizeBytes: row.size_bytes,
|
|
7290
|
+
description: row.description,
|
|
7291
|
+
assetIndexSeq: row.asset_index_seq
|
|
7292
|
+
};
|
|
7293
|
+
}
|
|
7294
|
+
var AssetMetadataIndex = class {
|
|
7295
|
+
db;
|
|
7296
|
+
cloud;
|
|
7297
|
+
/** Workspace ID — exposed for prismer:// URI construction. */
|
|
7298
|
+
workspaceId;
|
|
7299
|
+
cursorPath;
|
|
7300
|
+
_lastSyncMs = 0;
|
|
7301
|
+
constructor(opts) {
|
|
7302
|
+
this.db = opts.db;
|
|
7303
|
+
this.cloud = opts.cloud;
|
|
7304
|
+
this.workspaceId = opts.workspaceId;
|
|
7305
|
+
if (!(0, import_node_fs10.existsSync)(opts.workspaceStateDir)) {
|
|
7306
|
+
(0, import_node_fs10.mkdirSync)(opts.workspaceStateDir, { recursive: true });
|
|
7307
|
+
}
|
|
7308
|
+
this.cursorPath = (0, import_node_path8.join)(opts.workspaceStateDir, "asset-metadata-cursor.json");
|
|
7309
|
+
}
|
|
7310
|
+
/** Persisted cursor for this workspace, or 0 on first run / corrupted file. */
|
|
7311
|
+
readCursor() {
|
|
7312
|
+
if (!(0, import_node_fs10.existsSync)(this.cursorPath)) return 0;
|
|
7313
|
+
try {
|
|
7314
|
+
const parsed = JSON.parse((0, import_node_fs10.readFileSync)(this.cursorPath, "utf8"));
|
|
7315
|
+
if (parsed.workspaceId !== this.workspaceId) return 0;
|
|
7316
|
+
return parsed.cursor;
|
|
7317
|
+
} catch {
|
|
7318
|
+
return 0;
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
writeCursor(cursor) {
|
|
7322
|
+
const payload = {
|
|
7323
|
+
workspaceId: this.workspaceId,
|
|
7324
|
+
cursor,
|
|
7325
|
+
writtenAt: Date.now()
|
|
7326
|
+
};
|
|
7327
|
+
(0, import_node_fs10.writeFileSync)(this.cursorPath, JSON.stringify(payload, null, 2));
|
|
7328
|
+
}
|
|
7329
|
+
/**
|
|
7330
|
+
* Pull incremental asset metadata changes since the persisted cursor and
|
|
7331
|
+
* upsert into the local index. Newer rows overwrite older ones by
|
|
7332
|
+
* (workspace_id, asset_id) primary key.
|
|
7333
|
+
*
|
|
7334
|
+
* Returns the count of items applied + the new cursor. Throttled: if called
|
|
7335
|
+
* within 30s of the last successful pull, returns immediately.
|
|
7336
|
+
*/
|
|
7337
|
+
async pullDelta(opts) {
|
|
7338
|
+
const now = Date.now();
|
|
7339
|
+
if (now - this._lastSyncMs < THROTTLE_MS) {
|
|
7340
|
+
return { applied: 0, cursor: this.readCursor() };
|
|
7341
|
+
}
|
|
7342
|
+
const since = this.readCursor();
|
|
7343
|
+
const sinceParam = since > 0 ? `&since=${since}` : "";
|
|
7344
|
+
const envelope2 = await this.cloud.get(
|
|
7345
|
+
`/api/im/assets/index?workspaceId=${encodeURIComponent(this.workspaceId)}&limit=${PULL_PAGE_SIZE}${sinceParam}`,
|
|
7346
|
+
{ signal: opts?.signal }
|
|
7347
|
+
);
|
|
7348
|
+
const upsert = this.db.prepare(`
|
|
7349
|
+
INSERT INTO asset_metadata_index
|
|
7350
|
+
(workspace_id, asset_id, content_hash, filename, folder_path, mime, kind, size_bytes, description, asset_index_seq, updated_at)
|
|
7351
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7352
|
+
ON CONFLICT(workspace_id, asset_id) DO UPDATE SET
|
|
7353
|
+
content_hash = excluded.content_hash,
|
|
7354
|
+
filename = excluded.filename,
|
|
7355
|
+
folder_path = excluded.folder_path,
|
|
7356
|
+
mime = excluded.mime,
|
|
7357
|
+
kind = excluded.kind,
|
|
7358
|
+
size_bytes = excluded.size_bytes,
|
|
7359
|
+
description = excluded.description,
|
|
7360
|
+
asset_index_seq = excluded.asset_index_seq,
|
|
7361
|
+
updated_at = excluded.updated_at
|
|
7362
|
+
`);
|
|
7363
|
+
const nowTs = Date.now();
|
|
7364
|
+
let applied = 0;
|
|
7365
|
+
const tx = this.db.transaction((items) => {
|
|
7366
|
+
for (const item of items) {
|
|
7367
|
+
upsert.run(
|
|
7368
|
+
this.workspaceId,
|
|
7369
|
+
item.assetId,
|
|
7370
|
+
item.contentHash,
|
|
7371
|
+
item.filename ?? null,
|
|
7372
|
+
item.folderPath ?? null,
|
|
7373
|
+
item.mime,
|
|
7374
|
+
item.kind,
|
|
7375
|
+
item.sizeBytes,
|
|
7376
|
+
item.description ?? null,
|
|
7377
|
+
item.assetIndexSeq,
|
|
7378
|
+
nowTs
|
|
7379
|
+
);
|
|
7380
|
+
applied += 1;
|
|
7381
|
+
}
|
|
7382
|
+
});
|
|
7383
|
+
try {
|
|
7384
|
+
tx(envelope2.items);
|
|
7385
|
+
this.writeCursor(envelope2.cursor);
|
|
7386
|
+
} catch (err) {
|
|
7387
|
+
throw err;
|
|
7388
|
+
}
|
|
7389
|
+
this._lastSyncMs = now;
|
|
7390
|
+
return { applied, cursor: envelope2.cursor };
|
|
7391
|
+
}
|
|
7392
|
+
/**
|
|
7393
|
+
* Search local index by filename or description substring.
|
|
7394
|
+
* Escapes LIKE wildcards (% and _). Default limit 8.
|
|
7395
|
+
*/
|
|
7396
|
+
search(query, limit) {
|
|
7397
|
+
const escaped = query.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
7398
|
+
const pattern = `%${escaped}%`;
|
|
7399
|
+
const limitVal = Math.min(Math.max(limit ?? DEFAULT_LIMIT, 1), 200);
|
|
7400
|
+
const rows = this.db.prepare(
|
|
7401
|
+
`SELECT * FROM asset_metadata_index
|
|
7402
|
+
WHERE workspace_id = ?
|
|
7403
|
+
AND (filename LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\')
|
|
7404
|
+
ORDER BY asset_index_seq DESC
|
|
7405
|
+
LIMIT ?`
|
|
7406
|
+
).all(this.workspaceId, pattern, pattern, limitVal);
|
|
7407
|
+
return rows.map(rowToMetadata);
|
|
7408
|
+
}
|
|
7409
|
+
/** Exact match on filename column. Returns undefined if not indexed. */
|
|
7410
|
+
resolveByFilename(filename) {
|
|
7411
|
+
const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND filename = ?").get(this.workspaceId, filename);
|
|
7412
|
+
return row ? rowToMetadata(row) : void 0;
|
|
8008
7413
|
}
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
if (
|
|
7414
|
+
/** Batch exact match — returns Map for O(1) access. */
|
|
7415
|
+
resolveByFilenames(filenames) {
|
|
7416
|
+
if (filenames.length === 0) return /* @__PURE__ */ new Map();
|
|
7417
|
+
const placeholders = filenames.map(() => "?").join(",");
|
|
7418
|
+
const params = [this.workspaceId, ...filenames];
|
|
7419
|
+
const rows = this.db.prepare(
|
|
7420
|
+
`SELECT * FROM asset_metadata_index
|
|
7421
|
+
WHERE workspace_id = ? AND filename IN (${placeholders})`
|
|
7422
|
+
).all(...params);
|
|
7423
|
+
const map = /* @__PURE__ */ new Map();
|
|
7424
|
+
for (const row of rows) {
|
|
7425
|
+
if (row.filename) map.set(row.filename, rowToMetadata(row));
|
|
7426
|
+
}
|
|
7427
|
+
return map;
|
|
8012
7428
|
}
|
|
8013
|
-
|
|
8014
|
-
const results = finalizeSelected(runtime, b.workspaceId, paths, snippetMaxBytes);
|
|
8015
|
-
respond2(res, 200, { workspaceId: b.workspaceId, results });
|
|
8016
|
-
return true;
|
|
8017
|
-
}
|
|
8018
|
-
function parsePrismerUri(uri) {
|
|
8019
|
-
const PREFIX2 = "prismer://workspace/";
|
|
8020
|
-
if (!uri.startsWith(PREFIX2)) return null;
|
|
8021
|
-
const rest = uri.slice(PREFIX2.length);
|
|
8022
|
-
const segMemory = "/memory/";
|
|
8023
|
-
const memoryIdx = rest.indexOf(segMemory);
|
|
8024
|
-
if (memoryIdx <= 0) return null;
|
|
8025
|
-
const workspaceId = rest.slice(0, memoryIdx);
|
|
8026
|
-
const path9 = rest.slice(memoryIdx + segMemory.length);
|
|
8027
|
-
if (!workspaceId || !path9) return null;
|
|
8028
|
-
return { workspaceId, path: path9 };
|
|
8029
|
-
}
|
|
7429
|
+
};
|
|
8030
7430
|
|
|
8031
7431
|
// src/daemon/asset/rpc.ts
|
|
8032
|
-
var import_node_fs13 = require("fs");
|
|
8033
7432
|
var ASSET_PATH_PREFIX = "/local/asset/";
|
|
8034
|
-
var MAX_READ_BYTES = 256 * 1024;
|
|
8035
|
-
var MAX_SEARCH_BYTES = 1024 * 1024;
|
|
8036
7433
|
function attachAssetRpc(opts) {
|
|
8037
7434
|
return async (req, res) => {
|
|
8038
7435
|
const url = req.url ?? "/";
|
|
@@ -8043,17 +7440,10 @@ function attachAssetRpc(opts) {
|
|
|
8043
7440
|
try {
|
|
8044
7441
|
if (method === "POST" && subpath === "search") {
|
|
8045
7442
|
const body = await readJson3(req);
|
|
8046
|
-
return
|
|
8047
|
-
}
|
|
8048
|
-
if (method === "POST" && subpath === "describe") {
|
|
8049
|
-
const body = await readJson3(req);
|
|
8050
|
-
return handleDescribe(opts, body, res);
|
|
8051
|
-
}
|
|
8052
|
-
if (method === "POST" && subpath === "read") {
|
|
8053
|
-
const body = await readJson3(req);
|
|
8054
|
-
return await handleRead(opts, body, res);
|
|
7443
|
+
return handleSearch2(opts.resolveIndex, body, res);
|
|
8055
7444
|
}
|
|
8056
|
-
|
|
7445
|
+
respond3(res, 404, { error: "asset_route_not_found", path: url });
|
|
7446
|
+
return true;
|
|
8057
7447
|
} catch (err) {
|
|
8058
7448
|
respond3(res, 500, {
|
|
8059
7449
|
error: "asset_rpc_failed",
|
|
@@ -8063,21 +7453,18 @@ function attachAssetRpc(opts) {
|
|
|
8063
7453
|
}
|
|
8064
7454
|
};
|
|
8065
7455
|
}
|
|
8066
|
-
|
|
7456
|
+
function handleSearch2(resolveIndex, body, res) {
|
|
8067
7457
|
if (!body || typeof body !== "object") {
|
|
8068
7458
|
return respond4002(res, "request body must be a JSON object");
|
|
8069
7459
|
}
|
|
8070
7460
|
const b = body;
|
|
8071
|
-
if (typeof b.query !== "string" || !b.query.trim()) {
|
|
8072
|
-
return respond4002(res, "query is required (non-empty string)");
|
|
8073
|
-
}
|
|
8074
|
-
if (typeof b.uri === "string" && b.uri.trim()) {
|
|
8075
|
-
return searchAssetContent(opts, b.uri.trim(), b.query.trim(), res);
|
|
8076
|
-
}
|
|
8077
7461
|
if (typeof b.workspaceId !== "string" || !b.workspaceId) {
|
|
8078
7462
|
return respond4002(res, "workspaceId is required (string)");
|
|
8079
7463
|
}
|
|
8080
|
-
|
|
7464
|
+
if (typeof b.query !== "string" || !b.query.trim()) {
|
|
7465
|
+
return respond4002(res, "query is required (non-empty string)");
|
|
7466
|
+
}
|
|
7467
|
+
const index = resolveIndex(b.workspaceId);
|
|
8081
7468
|
if (!index) {
|
|
8082
7469
|
respond3(res, 404, {
|
|
8083
7470
|
error: "workspace_index_not_found",
|
|
@@ -8091,671 +7478,30 @@ async function handleSearch2(opts, body, res) {
|
|
|
8091
7478
|
respond3(res, 200, { items });
|
|
8092
7479
|
return true;
|
|
8093
7480
|
}
|
|
8094
|
-
function handleDescribe(opts, body, res) {
|
|
8095
|
-
const resolved = resolveAssetRef(opts, body);
|
|
8096
|
-
if ("error" in resolved) return respond4002(res, resolved.error);
|
|
8097
|
-
const cached = opts.assetCache?.get(resolved.row.contentHash);
|
|
8098
|
-
respond3(res, 200, {
|
|
8099
|
-
asset: resolved.row,
|
|
8100
|
-
uri: `prismer://workspace/${resolved.workspaceId}/asset/${resolved.row.contentHash}`,
|
|
8101
|
-
cache: cached ? { status: "hit", localPath: cached.localPath, sizeBytes: cached.sizeBytes, lastUsedAt: cached.lastUsedAt } : { status: "miss" },
|
|
8102
|
-
tools: ["asset.describe", "asset.read", "asset.search"]
|
|
8103
|
-
});
|
|
8104
|
-
return true;
|
|
8105
|
-
}
|
|
8106
|
-
async function handleRead(opts, body, res) {
|
|
8107
|
-
const resolved = resolveAssetRef(opts, body);
|
|
8108
|
-
if ("error" in resolved) return respond4002(res, resolved.error);
|
|
8109
|
-
if (!opts.assetCache) {
|
|
8110
|
-
respond3(res, 503, { error: "asset_cache_unavailable" });
|
|
8111
|
-
return true;
|
|
8112
|
-
}
|
|
8113
|
-
const b = body;
|
|
8114
|
-
const offset = typeof b.offset === "number" && b.offset > 0 ? Math.floor(b.offset) : 0;
|
|
8115
|
-
const requested = typeof b.length === "number" && b.length > 0 ? Math.floor(b.length) : MAX_READ_BYTES;
|
|
8116
|
-
const length = Math.min(requested, MAX_READ_BYTES);
|
|
8117
|
-
const cached = await opts.assetCache.getOrFetch(resolved.row.contentHash, {
|
|
8118
|
-
workspaceIdHint: resolved.workspaceId,
|
|
8119
|
-
assetId: resolved.row.assetId
|
|
8120
|
-
});
|
|
8121
|
-
const chunk = readFileRange(cached.localPath, offset, length);
|
|
8122
|
-
const textLike = isTextLike(resolved.row);
|
|
8123
|
-
respond3(res, 200, {
|
|
8124
|
-
asset: resolved.row,
|
|
8125
|
-
locator: { byteOffset: offset, byteLength: chunk.bytesRead },
|
|
8126
|
-
truncated: requested > MAX_READ_BYTES || offset + chunk.bytesRead < chunk.sizeBytes,
|
|
8127
|
-
encoding: textLike ? "utf8" : "base64",
|
|
8128
|
-
content: textLike ? chunk.buffer.toString("utf8") : chunk.buffer.toString("base64")
|
|
8129
|
-
});
|
|
8130
|
-
return true;
|
|
8131
|
-
}
|
|
8132
|
-
async function searchAssetContent(opts, uri, query, res) {
|
|
8133
|
-
const resolved = resolveAssetRef(opts, { uri });
|
|
8134
|
-
if ("error" in resolved) return respond4002(res, resolved.error);
|
|
8135
|
-
if (!opts.assetCache) {
|
|
8136
|
-
respond3(res, 503, { error: "asset_cache_unavailable" });
|
|
8137
|
-
return true;
|
|
8138
|
-
}
|
|
8139
|
-
if (!isTextLike(resolved.row)) {
|
|
8140
|
-
respond3(res, 200, {
|
|
8141
|
-
items: [],
|
|
8142
|
-
unsupported: true,
|
|
8143
|
-
reason: "content search currently supports text-like assets only",
|
|
8144
|
-
asset: resolved.row
|
|
8145
|
-
});
|
|
8146
|
-
return true;
|
|
8147
|
-
}
|
|
8148
|
-
const cached = await opts.assetCache.getOrFetch(resolved.row.contentHash, {
|
|
8149
|
-
workspaceIdHint: resolved.workspaceId,
|
|
8150
|
-
assetId: resolved.row.assetId
|
|
8151
|
-
});
|
|
8152
|
-
const chunk = readFileRange(cached.localPath, 0, MAX_SEARCH_BYTES);
|
|
8153
|
-
const text = chunk.buffer.toString("utf8");
|
|
8154
|
-
const needle = query.toLowerCase();
|
|
8155
|
-
const lines = text.split(/\r?\n/);
|
|
8156
|
-
const items = [];
|
|
8157
|
-
let byteOffset = 0;
|
|
8158
|
-
for (let i = 0; i < lines.length && items.length < 20; i += 1) {
|
|
8159
|
-
const line = lines[i] ?? "";
|
|
8160
|
-
if (line.toLowerCase().includes(needle)) {
|
|
8161
|
-
items.push({ line: i + 1, byteOffset, preview: line.slice(0, 500) });
|
|
8162
|
-
}
|
|
8163
|
-
byteOffset += Buffer.byteLength(line) + 1;
|
|
8164
|
-
}
|
|
8165
|
-
respond3(res, 200, {
|
|
8166
|
-
items,
|
|
8167
|
-
asset: resolved.row,
|
|
8168
|
-
searched: { byteOffset: 0, byteLength: chunk.bytesRead, truncated: chunk.bytesRead < chunk.sizeBytes }
|
|
8169
|
-
});
|
|
8170
|
-
return true;
|
|
8171
|
-
}
|
|
8172
|
-
function resolveAssetRef(opts, body) {
|
|
8173
|
-
if (!body || typeof body !== "object") return { error: "request body must be a JSON object" };
|
|
8174
|
-
const b = body;
|
|
8175
|
-
let workspaceId = typeof b.workspaceId === "string" ? b.workspaceId : "";
|
|
8176
|
-
let assetId = typeof b.assetId === "string" ? b.assetId : "";
|
|
8177
|
-
let contentHash = typeof b.contentHash === "string" ? b.contentHash : "";
|
|
8178
|
-
if (typeof b.uri === "string" && b.uri.trim()) {
|
|
8179
|
-
const [uri] = parseUris(b.uri.trim());
|
|
8180
|
-
if (!uri) return { error: "uri must be prismer://workspace/<workspaceId>/asset/<hash>" };
|
|
8181
|
-
if (uri.type !== "asset") return { error: "file uri lookup is not available in this RPC yet; pass asset uri or assetId" };
|
|
8182
|
-
workspaceId = uri.workspaceId ?? workspaceId;
|
|
8183
|
-
contentHash = uri.rest;
|
|
8184
|
-
}
|
|
8185
|
-
if (!workspaceId) return { error: "workspaceId is required (string)" };
|
|
8186
|
-
const index = opts.resolveIndex(workspaceId);
|
|
8187
|
-
if (!index) return { error: "workspace asset index is not synced yet" };
|
|
8188
|
-
const row = assetId ? index.resolveByAssetId(assetId) : contentHash ? index.resolveByContentHash(contentHash) : void 0;
|
|
8189
|
-
if (!row) return { error: "asset not found in local metadata index" };
|
|
8190
|
-
return { workspaceId, row };
|
|
8191
|
-
}
|
|
8192
|
-
function isTextLike(row) {
|
|
8193
|
-
const mime = row.mime ?? "";
|
|
8194
|
-
const name = row.filename ?? "";
|
|
8195
|
-
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(
|
|
8196
|
-
name
|
|
8197
|
-
);
|
|
8198
|
-
}
|
|
8199
|
-
function readFileRange(path9, offset, length) {
|
|
8200
|
-
const sizeBytes = (0, import_node_fs13.statSync)(path9).size;
|
|
8201
|
-
if (offset >= sizeBytes) return { buffer: Buffer.alloc(0), bytesRead: 0, sizeBytes };
|
|
8202
|
-
const fd = (0, import_node_fs13.openSync)(path9, "r");
|
|
8203
|
-
try {
|
|
8204
|
-
const target = Math.min(length, sizeBytes - offset);
|
|
8205
|
-
const buffer = Buffer.alloc(target);
|
|
8206
|
-
const bytesRead = (0, import_node_fs13.readSync)(fd, buffer, 0, target, offset);
|
|
8207
|
-
return { buffer: buffer.subarray(0, bytesRead), bytesRead, sizeBytes };
|
|
8208
|
-
} finally {
|
|
8209
|
-
(0, import_node_fs13.closeSync)(fd);
|
|
8210
|
-
}
|
|
8211
|
-
}
|
|
8212
7481
|
function respond3(res, status, body) {
|
|
8213
7482
|
res.statusCode = status;
|
|
8214
7483
|
res.setHeader("Content-Type", "application/json");
|
|
8215
7484
|
res.end(JSON.stringify(body));
|
|
8216
|
-
}
|
|
8217
|
-
function respond4002(res, message) {
|
|
8218
|
-
respond3(res, 400, { error: "invalid_request", message });
|
|
8219
|
-
return true;
|
|
8220
|
-
}
|
|
8221
|
-
async function readJson3(req) {
|
|
8222
|
-
let raw = "";
|
|
8223
|
-
req.setEncoding("utf8");
|
|
8224
|
-
for await (const chunk of req) raw += chunk;
|
|
8225
|
-
if (!raw) return {};
|
|
8226
|
-
try {
|
|
8227
|
-
return JSON.parse(raw);
|
|
8228
|
-
} catch {
|
|
8229
|
-
throw new Error("invalid_json");
|
|
8230
|
-
}
|
|
8231
|
-
}
|
|
8232
|
-
|
|
8233
|
-
// src/daemon/asset/origin/drop-folder.ts
|
|
8234
|
-
var import_node_fs14 = require("fs");
|
|
8235
|
-
var import_node_os5 = require("os");
|
|
8236
|
-
var path6 = __toESM(require("path"), 1);
|
|
8237
|
-
var DropFolderAdapter = class {
|
|
8238
|
-
kind = "drop-folder";
|
|
8239
|
-
workspaceDir;
|
|
8240
|
-
rootDir;
|
|
8241
|
-
constructor(opts = {}) {
|
|
8242
|
-
this.workspaceDir = opts.workspaceDir;
|
|
8243
|
-
this.rootDir = opts.rootDir ?? path6.join(opts.homeDir ?? (0, import_node_os5.homedir)(), ".prismer");
|
|
8244
|
-
}
|
|
8245
|
-
/**
|
|
8246
|
-
* Long-lived async iterable wrapping `scanOnce` on a 1s polling interval.
|
|
8247
|
-
* Stops when the iterator is broken (e.g. consumer returns / throws).
|
|
8248
|
-
*/
|
|
8249
|
-
async *observe(workspaceId) {
|
|
8250
|
-
while (true) {
|
|
8251
|
-
const batch = await this.scanOnce(workspaceId);
|
|
8252
|
-
for (const obs of batch) yield obs;
|
|
8253
|
-
await sleep(1e3);
|
|
8254
|
-
}
|
|
8255
|
-
}
|
|
8256
|
-
/**
|
|
8257
|
-
* Single-tick scan exposed for tests and one-shot CLI usage. Reads every
|
|
8258
|
-
* file currently under the workspace's drop directory, returning one
|
|
8259
|
-
* observation per file. The caller is expected to feed each into the
|
|
8260
|
-
* outbox; idempotency on the outbox side collapses re-observations of
|
|
8261
|
-
* unchanged files.
|
|
8262
|
-
*/
|
|
8263
|
-
async scanOnce(workspaceId) {
|
|
8264
|
-
const drop = this.dropDir(workspaceId);
|
|
8265
|
-
let entries = [];
|
|
8266
|
-
try {
|
|
8267
|
-
entries = await import_node_fs14.promises.readdir(drop);
|
|
8268
|
-
} catch (err) {
|
|
8269
|
-
if (err.code === "ENOENT") return [];
|
|
8270
|
-
throw err;
|
|
8271
|
-
}
|
|
8272
|
-
const out = [];
|
|
8273
|
-
for (const name of entries) {
|
|
8274
|
-
if (name.startsWith(".")) continue;
|
|
8275
|
-
const fullPath = path6.join(drop, name);
|
|
8276
|
-
const stat = await import_node_fs14.promises.lstat(fullPath).catch(() => null);
|
|
8277
|
-
if (!stat) continue;
|
|
8278
|
-
if (stat.isSymbolicLink()) continue;
|
|
8279
|
-
if (stat.isDirectory()) {
|
|
8280
|
-
for (const nested of await walk(fullPath)) {
|
|
8281
|
-
const nstat = await import_node_fs14.promises.lstat(nested).catch(() => null);
|
|
8282
|
-
if (!nstat || !nstat.isFile()) continue;
|
|
8283
|
-
out.push(makeObservation(workspaceId, nested, drop, nstat.size, Math.floor(nstat.mtimeMs)));
|
|
8284
|
-
}
|
|
8285
|
-
continue;
|
|
8286
|
-
}
|
|
8287
|
-
if (!stat.isFile()) continue;
|
|
8288
|
-
out.push(makeObservation(workspaceId, fullPath, drop, stat.size, Math.floor(stat.mtimeMs)));
|
|
8289
|
-
}
|
|
8290
|
-
return out;
|
|
8291
|
-
}
|
|
8292
|
-
async identifySource(obs) {
|
|
8293
|
-
const detail = obs.detail;
|
|
8294
|
-
const rel = path6.relative(detail.watchDir, detail.path);
|
|
8295
|
-
const folderDir = path6.dirname(rel);
|
|
8296
|
-
return {
|
|
8297
|
-
sourceRef: `folder://${obs.workspaceId}/${rel.split(path6.sep).join("/")}`,
|
|
8298
|
-
hints: {
|
|
8299
|
-
filename: path6.basename(detail.path),
|
|
8300
|
-
folderPath: folderDir === "." ? "" : folderDir.split(path6.sep).join("/")
|
|
8301
|
-
}
|
|
8302
|
-
};
|
|
8303
|
-
}
|
|
8304
|
-
async fetch(obs) {
|
|
8305
|
-
const detail = obs.detail;
|
|
8306
|
-
const bytes = await import_node_fs14.promises.readFile(detail.path);
|
|
8307
|
-
return {
|
|
8308
|
-
bytes,
|
|
8309
|
-
mime: mimeFromExtension(detail.path),
|
|
8310
|
-
size: bytes.length
|
|
8311
|
-
};
|
|
8312
|
-
}
|
|
8313
|
-
/** Hook called by UploadRunner after a successful upload of one observation. */
|
|
8314
|
-
async onUploadSuccess(obs) {
|
|
8315
|
-
await this.moveTo(obs, this.uploadedDir(obs.workspaceId));
|
|
8316
|
-
}
|
|
8317
|
-
/** Hook called by UploadRunner after attempts hit maxAttempts. */
|
|
8318
|
-
async onUploadFailure(obs) {
|
|
8319
|
-
await this.moveTo(obs, this.failedDir(obs.workspaceId));
|
|
8320
|
-
}
|
|
8321
|
-
async moveTo(obs, destDir) {
|
|
8322
|
-
const detail = obs.detail;
|
|
8323
|
-
if (!detail || typeof detail.path !== "string") return;
|
|
8324
|
-
try {
|
|
8325
|
-
const rel = typeof detail.watchDir === "string" ? path6.relative(detail.watchDir, detail.path) : path6.basename(detail.path);
|
|
8326
|
-
const safeRel = rel && !rel.startsWith("..") && !path6.isAbsolute(rel) ? rel : path6.basename(detail.path);
|
|
8327
|
-
const destPath = path6.join(destDir, safeRel);
|
|
8328
|
-
await import_node_fs14.promises.mkdir(path6.dirname(destPath), { recursive: true });
|
|
8329
|
-
await import_node_fs14.promises.rename(detail.path, destPath);
|
|
8330
|
-
} catch (err) {
|
|
8331
|
-
if (err.code === "ENOENT") return;
|
|
8332
|
-
throw err;
|
|
8333
|
-
}
|
|
8334
|
-
}
|
|
8335
|
-
dropDir(workspaceId) {
|
|
8336
|
-
if (this.workspaceDir) return path6.join(this.workspaceDir, "drop");
|
|
8337
|
-
return path6.join(this.rootDir, "workspaces", workspaceId, "drop");
|
|
8338
|
-
}
|
|
8339
|
-
uploadedDir(workspaceId) {
|
|
8340
|
-
if (this.workspaceDir) return path6.join(this.workspaceDir, "uploaded");
|
|
8341
|
-
return path6.join(this.rootDir, "workspaces", workspaceId, "uploaded");
|
|
8342
|
-
}
|
|
8343
|
-
failedDir(workspaceId) {
|
|
8344
|
-
if (this.workspaceDir) return path6.join(this.workspaceDir, "upload-failed");
|
|
8345
|
-
return path6.join(this.rootDir, "workspaces", workspaceId, "upload-failed");
|
|
8346
|
-
}
|
|
8347
|
-
};
|
|
8348
|
-
function makeObservation(workspaceId, filePath, watchDir, size, mtime) {
|
|
8349
|
-
return {
|
|
8350
|
-
workspaceId,
|
|
8351
|
-
detail: { path: filePath, watchDir, size, mtime },
|
|
8352
|
-
observedAt: mtime
|
|
8353
|
-
};
|
|
8354
|
-
}
|
|
8355
|
-
async function walk(root) {
|
|
8356
|
-
const out = [];
|
|
8357
|
-
const entries = await import_node_fs14.promises.readdir(root).catch(() => []);
|
|
8358
|
-
for (const name of entries) {
|
|
8359
|
-
if (name.startsWith(".")) continue;
|
|
8360
|
-
const full = path6.join(root, name);
|
|
8361
|
-
const stat = await import_node_fs14.promises.lstat(full).catch(() => null);
|
|
8362
|
-
if (!stat) continue;
|
|
8363
|
-
if (stat.isSymbolicLink()) continue;
|
|
8364
|
-
if (stat.isDirectory()) {
|
|
8365
|
-
out.push(...await walk(full));
|
|
8366
|
-
} else if (stat.isFile()) {
|
|
8367
|
-
out.push(full);
|
|
8368
|
-
}
|
|
8369
|
-
}
|
|
8370
|
-
return out;
|
|
8371
|
-
}
|
|
8372
|
-
var MIME_BY_EXT = {
|
|
8373
|
-
".txt": "text/plain",
|
|
8374
|
-
".md": "text/markdown",
|
|
8375
|
-
".csv": "text/csv",
|
|
8376
|
-
".json": "application/json",
|
|
8377
|
-
".yaml": "application/yaml",
|
|
8378
|
-
".yml": "application/yaml",
|
|
8379
|
-
".pdf": "application/pdf",
|
|
8380
|
-
".png": "image/png",
|
|
8381
|
-
".jpg": "image/jpeg",
|
|
8382
|
-
".jpeg": "image/jpeg",
|
|
8383
|
-
".gif": "image/gif",
|
|
8384
|
-
".webp": "image/webp",
|
|
8385
|
-
".svg": "image/svg+xml",
|
|
8386
|
-
".html": "text/html",
|
|
8387
|
-
".htm": "text/html",
|
|
8388
|
-
".xhtml": "application/xhtml+xml",
|
|
8389
|
-
".xml": "application/xml",
|
|
8390
|
-
".zip": "application/zip",
|
|
8391
|
-
".tar": "application/x-tar",
|
|
8392
|
-
".gz": "application/gzip",
|
|
8393
|
-
".mp3": "audio/mpeg",
|
|
8394
|
-
".mp4": "video/mp4",
|
|
8395
|
-
".mov": "video/quicktime",
|
|
8396
|
-
".wav": "audio/wav"
|
|
8397
|
-
};
|
|
8398
|
-
function mimeFromExtension(p) {
|
|
8399
|
-
const ext = path6.extname(p).toLowerCase();
|
|
8400
|
-
return MIME_BY_EXT[ext] ?? null;
|
|
8401
|
-
}
|
|
8402
|
-
function sleep(ms) {
|
|
8403
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
8404
|
-
}
|
|
8405
|
-
|
|
8406
|
-
// src/daemon/asset/origin/outbox.ts
|
|
8407
|
-
var import_better_sqlite34 = __toESM(require("better-sqlite3"), 1);
|
|
8408
|
-
var import_node_crypto7 = require("crypto");
|
|
8409
|
-
var import_zod7 = require("zod");
|
|
8410
|
-
var OriginObservationRecordSchema = import_zod7.z.object({
|
|
8411
|
-
workspaceId: import_zod7.z.string().min(1),
|
|
8412
|
-
originKind: import_zod7.z.enum(["upload", "drop-folder", "agent-gen"]),
|
|
8413
|
-
sourceRef: import_zod7.z.string().min(1),
|
|
8414
|
-
payloadJson: import_zod7.z.string().min(2),
|
|
8415
|
-
hintsJson: import_zod7.z.string().min(2),
|
|
8416
|
-
observedAt: import_zod7.z.number().int().nonnegative()
|
|
8417
|
-
});
|
|
8418
|
-
var DDL_STATEMENTS = [
|
|
8419
|
-
`CREATE TABLE IF NOT EXISTS asset_origin_outbox (
|
|
8420
|
-
id TEXT PRIMARY KEY,
|
|
8421
|
-
workspaceId TEXT NOT NULL,
|
|
8422
|
-
originKind TEXT NOT NULL,
|
|
8423
|
-
sourceRef TEXT NOT NULL,
|
|
8424
|
-
payloadJson TEXT NOT NULL,
|
|
8425
|
-
hintsJson TEXT NOT NULL,
|
|
8426
|
-
observedAt INTEGER NOT NULL,
|
|
8427
|
-
idempotencyKey TEXT NOT NULL UNIQUE,
|
|
8428
|
-
status TEXT NOT NULL CHECK (status IN ('pending', 'claimed', 'uploaded')),
|
|
8429
|
-
attempts INTEGER NOT NULL DEFAULT 0,
|
|
8430
|
-
lastError TEXT,
|
|
8431
|
-
assetId TEXT,
|
|
8432
|
-
contentHash TEXT,
|
|
8433
|
-
createdAt INTEGER NOT NULL,
|
|
8434
|
-
updatedAt INTEGER NOT NULL
|
|
8435
|
-
)`,
|
|
8436
|
-
`CREATE INDEX IF NOT EXISTS idx_asset_origin_outbox_status
|
|
8437
|
-
ON asset_origin_outbox(status, createdAt)`,
|
|
8438
|
-
`CREATE TABLE IF NOT EXISTS asset_origin_outbox_dead_letter (
|
|
8439
|
-
id TEXT PRIMARY KEY,
|
|
8440
|
-
workspaceId TEXT NOT NULL,
|
|
8441
|
-
originKind TEXT NOT NULL,
|
|
8442
|
-
sourceRef TEXT NOT NULL,
|
|
8443
|
-
payloadJson TEXT NOT NULL,
|
|
8444
|
-
hintsJson TEXT NOT NULL,
|
|
8445
|
-
observedAt INTEGER NOT NULL,
|
|
8446
|
-
errorJson TEXT NOT NULL,
|
|
8447
|
-
attempts INTEGER NOT NULL,
|
|
8448
|
-
createdAt INTEGER NOT NULL
|
|
8449
|
-
)`
|
|
8450
|
-
];
|
|
8451
|
-
var OriginOutbox = class {
|
|
8452
|
-
db;
|
|
8453
|
-
constructor(opts) {
|
|
8454
|
-
this.db = new import_better_sqlite34.default(opts.dbPath);
|
|
8455
|
-
this.db.pragma("journal_mode = WAL");
|
|
8456
|
-
for (const ddl of DDL_STATEMENTS) this.db.prepare(ddl).run();
|
|
8457
|
-
}
|
|
8458
|
-
/**
|
|
8459
|
-
* Validate + persist an observation. Schema-invalid input lands in
|
|
8460
|
-
* `asset_origin_outbox_dead_letter` (never silently dropped). Re-enqueue
|
|
8461
|
-
* with the same {originKind, workspaceId, sourceRef, observedAt} returns
|
|
8462
|
-
* the existing row id without inserting again.
|
|
8463
|
-
*/
|
|
8464
|
-
enqueue(record) {
|
|
8465
|
-
const now = Date.now();
|
|
8466
|
-
const parsed = OriginObservationRecordSchema.safeParse(record);
|
|
8467
|
-
if (!parsed.success) {
|
|
8468
|
-
const dlId = `dl_${(0, import_node_crypto7.randomUUID)()}`;
|
|
8469
|
-
const raw = record && typeof record === "object" ? record : {};
|
|
8470
|
-
this.db.prepare(
|
|
8471
|
-
`INSERT INTO asset_origin_outbox_dead_letter
|
|
8472
|
-
(id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt, errorJson, attempts, createdAt)
|
|
8473
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
8474
|
-
).run(
|
|
8475
|
-
dlId,
|
|
8476
|
-
coerceToString(raw.workspaceId, ""),
|
|
8477
|
-
coerceToString(raw.originKind, ""),
|
|
8478
|
-
coerceToString(raw.sourceRef, ""),
|
|
8479
|
-
coerceToJson(raw.payloadJson),
|
|
8480
|
-
coerceToJson(raw.hintsJson),
|
|
8481
|
-
typeof raw.observedAt === "number" ? raw.observedAt : 0,
|
|
8482
|
-
JSON.stringify(parsed.error.errors),
|
|
8483
|
-
0,
|
|
8484
|
-
now
|
|
8485
|
-
);
|
|
8486
|
-
return { id: dlId, deadLetter: true };
|
|
8487
|
-
}
|
|
8488
|
-
const validated = parsed.data;
|
|
8489
|
-
const idempotencyKey = computeIdempotencyKey(validated);
|
|
8490
|
-
const rowId = `out_${(0, import_node_crypto7.randomUUID)()}`;
|
|
8491
|
-
try {
|
|
8492
|
-
this.db.prepare(
|
|
8493
|
-
`INSERT INTO asset_origin_outbox
|
|
8494
|
-
(id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt,
|
|
8495
|
-
idempotencyKey, status, attempts, createdAt, updatedAt)
|
|
8496
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, ?)`
|
|
8497
|
-
).run(
|
|
8498
|
-
rowId,
|
|
8499
|
-
validated.workspaceId,
|
|
8500
|
-
validated.originKind,
|
|
8501
|
-
validated.sourceRef,
|
|
8502
|
-
validated.payloadJson,
|
|
8503
|
-
validated.hintsJson,
|
|
8504
|
-
validated.observedAt,
|
|
8505
|
-
idempotencyKey,
|
|
8506
|
-
now,
|
|
8507
|
-
now
|
|
8508
|
-
);
|
|
8509
|
-
return { id: rowId, deadLetter: false };
|
|
8510
|
-
} catch (err) {
|
|
8511
|
-
const code = err.code;
|
|
8512
|
-
if (code === "SQLITE_CONSTRAINT_UNIQUE") {
|
|
8513
|
-
const existing = this.db.prepare("SELECT id FROM asset_origin_outbox WHERE idempotencyKey = ?").get(idempotencyKey);
|
|
8514
|
-
if (existing) return { id: existing.id, deadLetter: false };
|
|
8515
|
-
}
|
|
8516
|
-
throw err;
|
|
8517
|
-
}
|
|
8518
|
-
}
|
|
8519
|
-
/**
|
|
8520
|
-
* Atomically claim the oldest pending row, transition status to 'claimed',
|
|
8521
|
-
* and return it. Returns null when the queue is empty. The caller MUST
|
|
8522
|
-
* follow up with `markUploaded` (success) or `recordFailure` (error) so
|
|
8523
|
-
* the row doesn't sit in 'claimed' forever — Phase-0 is a single worker
|
|
8524
|
-
* so we don't need a claim-expiry sweeper yet.
|
|
8525
|
-
*/
|
|
8526
|
-
claimNext() {
|
|
8527
|
-
const pickAndClaim = this.db.transaction(() => {
|
|
8528
|
-
const row = this.db.prepare(
|
|
8529
|
-
`SELECT id, workspaceId, originKind, sourceRef, payloadJson, hintsJson,
|
|
8530
|
-
observedAt, attempts, lastError, createdAt
|
|
8531
|
-
FROM asset_origin_outbox
|
|
8532
|
-
WHERE status = 'pending'
|
|
8533
|
-
ORDER BY createdAt ASC
|
|
8534
|
-
LIMIT 1`
|
|
8535
|
-
).get();
|
|
8536
|
-
if (!row) return null;
|
|
8537
|
-
this.db.prepare("UPDATE asset_origin_outbox SET status = 'claimed', updatedAt = ? WHERE id = ?").run(Date.now(), row.id);
|
|
8538
|
-
return row;
|
|
8539
|
-
});
|
|
8540
|
-
const claimed = pickAndClaim();
|
|
8541
|
-
if (!claimed) return null;
|
|
8542
|
-
return {
|
|
8543
|
-
id: claimed.id,
|
|
8544
|
-
row: rawToRow(claimed)
|
|
8545
|
-
};
|
|
8546
|
-
}
|
|
8547
|
-
markUploaded(id, meta) {
|
|
8548
|
-
this.db.prepare(
|
|
8549
|
-
`UPDATE asset_origin_outbox
|
|
8550
|
-
SET status = 'uploaded', assetId = ?, contentHash = ?, updatedAt = ?
|
|
8551
|
-
WHERE id = ?`
|
|
8552
|
-
).run(meta.assetId, meta.contentHash, Date.now(), id);
|
|
8553
|
-
}
|
|
8554
|
-
/**
|
|
8555
|
-
* Record one failed upload attempt. Increments `attempts`, stores the
|
|
8556
|
-
* latest error, and either re-queues for retry (status -> 'pending') or
|
|
8557
|
-
* spills to dead-letter once attempts hit maxAttempts.
|
|
8558
|
-
*/
|
|
8559
|
-
recordFailure(id, error, opts) {
|
|
8560
|
-
const now = Date.now();
|
|
8561
|
-
const updated = this.db.transaction(() => {
|
|
8562
|
-
const row = this.db.prepare(
|
|
8563
|
-
`SELECT id, workspaceId, originKind, sourceRef, payloadJson, hintsJson,
|
|
8564
|
-
observedAt, attempts, createdAt
|
|
8565
|
-
FROM asset_origin_outbox
|
|
8566
|
-
WHERE id = ?`
|
|
8567
|
-
).get(id);
|
|
8568
|
-
if (!row) return;
|
|
8569
|
-
const nextAttempts = row.attempts + 1;
|
|
8570
|
-
if (nextAttempts >= opts.maxAttempts) {
|
|
8571
|
-
this.db.prepare(
|
|
8572
|
-
`INSERT INTO asset_origin_outbox_dead_letter
|
|
8573
|
-
(id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt, errorJson, attempts, createdAt)
|
|
8574
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
8575
|
-
).run(
|
|
8576
|
-
`dl_${(0, import_node_crypto7.randomUUID)()}`,
|
|
8577
|
-
row.workspaceId,
|
|
8578
|
-
row.originKind,
|
|
8579
|
-
row.sourceRef,
|
|
8580
|
-
row.payloadJson,
|
|
8581
|
-
row.hintsJson,
|
|
8582
|
-
row.observedAt,
|
|
8583
|
-
JSON.stringify({ message: error, attempts: nextAttempts }),
|
|
8584
|
-
nextAttempts,
|
|
8585
|
-
now
|
|
8586
|
-
);
|
|
8587
|
-
this.db.prepare("DELETE FROM asset_origin_outbox WHERE id = ?").run(id);
|
|
8588
|
-
} else {
|
|
8589
|
-
this.db.prepare(
|
|
8590
|
-
`UPDATE asset_origin_outbox
|
|
8591
|
-
SET status = 'pending', attempts = ?, lastError = ?, updatedAt = ?
|
|
8592
|
-
WHERE id = ?`
|
|
8593
|
-
).run(nextAttempts, error, now, id);
|
|
8594
|
-
}
|
|
8595
|
-
});
|
|
8596
|
-
updated();
|
|
8597
|
-
}
|
|
8598
|
-
/**
|
|
8599
|
-
* Re-promote any rows orphaned in `'claimed'` state back to `'pending'`.
|
|
8600
|
-
*
|
|
8601
|
-
* Why this exists: claimNext() transitions a row to 'claimed' under a
|
|
8602
|
-
* transaction, but the upload that follows runs OUTSIDE the transaction
|
|
8603
|
-
* (network call). If the daemon dies between the claim and the
|
|
8604
|
-
* markUploaded/recordFailure call, the row is stuck — claimNext() only
|
|
8605
|
-
* selects status='pending', so it would never be picked up again.
|
|
8606
|
-
*
|
|
8607
|
-
* Phase-0 single-worker safety: there is at most one in-flight claim at
|
|
8608
|
-
* any time, so on next process startup ANY 'claimed' row is by definition
|
|
8609
|
-
* orphaned. We promote it back to 'pending' (preserving attempts /
|
|
8610
|
-
* lastError) so the next drainOnce can retry it.
|
|
8611
|
-
*
|
|
8612
|
-
* If we ever go multi-worker, this naive sweep becomes unsafe and must be
|
|
8613
|
-
* replaced with a lease/heartbeat scheme.
|
|
8614
|
-
*
|
|
8615
|
-
* Returns the number of rows recovered (for logging / observability).
|
|
8616
|
-
*/
|
|
8617
|
-
resetClaimed() {
|
|
8618
|
-
const result = this.db.prepare(
|
|
8619
|
-
"UPDATE asset_origin_outbox SET status = 'pending', updatedAt = ? WHERE status = 'claimed'"
|
|
8620
|
-
).run(Date.now());
|
|
8621
|
-
return Number(result.changes);
|
|
8622
|
-
}
|
|
8623
|
-
pendingCount() {
|
|
8624
|
-
return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox WHERE status = 'pending'").get().n;
|
|
8625
|
-
}
|
|
8626
|
-
uploadedCount() {
|
|
8627
|
-
return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox WHERE status = 'uploaded'").get().n;
|
|
8628
|
-
}
|
|
8629
|
-
deadLetterCount() {
|
|
8630
|
-
return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox_dead_letter").get().n;
|
|
8631
|
-
}
|
|
8632
|
-
close() {
|
|
8633
|
-
this.db.close();
|
|
8634
|
-
}
|
|
8635
|
-
};
|
|
8636
|
-
function computeIdempotencyKey(record) {
|
|
8637
|
-
return (0, import_node_crypto7.createHash)("sha256").update(record.originKind).update("|").update(record.workspaceId).update("|").update(record.sourceRef).update("|").update(String(record.observedAt)).digest("hex");
|
|
8638
|
-
}
|
|
8639
|
-
function coerceToString(value, fallback) {
|
|
8640
|
-
if (typeof value === "string") return value;
|
|
8641
|
-
if (value == null) return fallback;
|
|
8642
|
-
return String(value);
|
|
8643
|
-
}
|
|
8644
|
-
function coerceToJson(value) {
|
|
8645
|
-
if (typeof value === "string") return value;
|
|
8646
|
-
if (value == null) return "{}";
|
|
8647
|
-
try {
|
|
8648
|
-
return JSON.stringify(value);
|
|
8649
|
-
} catch {
|
|
8650
|
-
return "{}";
|
|
8651
|
-
}
|
|
8652
|
-
}
|
|
8653
|
-
function rawToRow(raw) {
|
|
8654
|
-
return {
|
|
8655
|
-
id: raw.id,
|
|
8656
|
-
workspaceId: raw.workspaceId,
|
|
8657
|
-
originKind: raw.originKind,
|
|
8658
|
-
sourceRef: raw.sourceRef,
|
|
8659
|
-
payloadJson: raw.payloadJson,
|
|
8660
|
-
hintsJson: raw.hintsJson,
|
|
8661
|
-
observedAt: raw.observedAt,
|
|
8662
|
-
attempts: raw.attempts,
|
|
8663
|
-
lastError: raw.lastError,
|
|
8664
|
-
createdAt: raw.createdAt
|
|
8665
|
-
};
|
|
8666
|
-
}
|
|
8667
|
-
|
|
8668
|
-
// src/daemon/asset/origin/upload-runner.ts
|
|
8669
|
-
var UploadRunner = class {
|
|
8670
|
-
constructor(opts) {
|
|
8671
|
-
this.opts = opts;
|
|
8672
|
-
opts.outbox.resetClaimed();
|
|
8673
|
-
}
|
|
8674
|
-
opts;
|
|
8675
|
-
/**
|
|
8676
|
-
* Process every currently-pending outbox row exactly once. Failed rows
|
|
8677
|
-
* stay 'pending' (with attempts+1) for the next drainOnce call; rows
|
|
8678
|
-
* that hit maxAttempts spill to dead-letter and trigger onUploadFailure.
|
|
8679
|
-
*
|
|
8680
|
-
* Returns the count of rows processed (success + failure).
|
|
8681
|
-
*/
|
|
8682
|
-
async drainOnce() {
|
|
8683
|
-
let count = 0;
|
|
8684
|
-
while (true) {
|
|
8685
|
-
const claim = this.opts.outbox.claimNext();
|
|
8686
|
-
if (!claim) break;
|
|
8687
|
-
count += 1;
|
|
8688
|
-
const row = claim.row;
|
|
8689
|
-
const adapter = this.opts.adapters[row.originKind];
|
|
8690
|
-
if (!adapter) {
|
|
8691
|
-
this.opts.outbox.recordFailure(claim.id, `no adapter registered for kind=${row.originKind}`, {
|
|
8692
|
-
maxAttempts: 1
|
|
8693
|
-
// unrecoverable — fast-fail to dead-letter
|
|
8694
|
-
});
|
|
8695
|
-
continue;
|
|
8696
|
-
}
|
|
8697
|
-
try {
|
|
8698
|
-
const obs = rowToObservation(row);
|
|
8699
|
-
const bytes = await adapter.fetch(obs);
|
|
8700
|
-
const id = await adapter.identifySource(obs);
|
|
8701
|
-
const filename = id.hints?.filename ?? path7.basename(row.sourceRef);
|
|
8702
|
-
const metadata = {
|
|
8703
|
-
sourceRef: row.sourceRef,
|
|
8704
|
-
sourceKind: row.originKind
|
|
8705
|
-
};
|
|
8706
|
-
if (id.hints?.description) metadata.description = id.hints.description;
|
|
8707
|
-
if (id.hints?.sourceAgentImUserId) {
|
|
8708
|
-
metadata.sourceAgentImUserId = id.hints.sourceAgentImUserId;
|
|
8709
|
-
}
|
|
8710
|
-
const folderPath = id.hints?.folderPath || void 0;
|
|
8711
|
-
const result = await this.opts.cloud.uploadAsset({
|
|
8712
|
-
workspaceId: row.workspaceId,
|
|
8713
|
-
filename,
|
|
8714
|
-
bytes: bytes.bytes,
|
|
8715
|
-
mime: bytes.mime,
|
|
8716
|
-
size: bytes.size,
|
|
8717
|
-
metadata,
|
|
8718
|
-
folderPath
|
|
8719
|
-
});
|
|
8720
|
-
this.opts.outbox.markUploaded(claim.id, result);
|
|
8721
|
-
await adapter.onUploadSuccess?.(obs);
|
|
8722
|
-
} catch (err) {
|
|
8723
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
8724
|
-
const before = this.opts.outbox.deadLetterCount();
|
|
8725
|
-
this.opts.outbox.recordFailure(claim.id, message, { maxAttempts: this.opts.maxAttempts });
|
|
8726
|
-
const after = this.opts.outbox.deadLetterCount();
|
|
8727
|
-
if (after > before) {
|
|
8728
|
-
const obs = rowToObservation(row);
|
|
8729
|
-
await adapter.onUploadFailure?.(obs, err);
|
|
8730
|
-
}
|
|
8731
|
-
}
|
|
8732
|
-
}
|
|
8733
|
-
return count;
|
|
8734
|
-
}
|
|
8735
|
-
};
|
|
8736
|
-
function rowToObservation(row) {
|
|
8737
|
-
let detail = {};
|
|
7485
|
+
}
|
|
7486
|
+
function respond4002(res, message) {
|
|
7487
|
+
respond3(res, 400, { error: "invalid_request", message });
|
|
7488
|
+
return true;
|
|
7489
|
+
}
|
|
7490
|
+
async function readJson3(req) {
|
|
7491
|
+
let raw = "";
|
|
7492
|
+
req.setEncoding("utf8");
|
|
7493
|
+
for await (const chunk of req) raw += chunk;
|
|
7494
|
+
if (!raw) return {};
|
|
8738
7495
|
try {
|
|
8739
|
-
|
|
8740
|
-
if (parsed && typeof parsed === "object") detail = parsed;
|
|
7496
|
+
return JSON.parse(raw);
|
|
8741
7497
|
} catch {
|
|
7498
|
+
throw new Error("invalid_json");
|
|
8742
7499
|
}
|
|
8743
|
-
return {
|
|
8744
|
-
workspaceId: row.workspaceId,
|
|
8745
|
-
detail,
|
|
8746
|
-
observedAt: row.observedAt
|
|
8747
|
-
};
|
|
8748
7500
|
}
|
|
8749
|
-
var path7 = {
|
|
8750
|
-
basename(p) {
|
|
8751
|
-
const idx = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
|
|
8752
|
-
return idx === -1 ? p : p.slice(idx + 1);
|
|
8753
|
-
}
|
|
8754
|
-
};
|
|
8755
7501
|
|
|
8756
7502
|
// src/daemon/outbox-watcher.ts
|
|
8757
|
-
var
|
|
8758
|
-
var
|
|
7503
|
+
var import_node_fs11 = require("fs");
|
|
7504
|
+
var path6 = __toESM(require("path"), 1);
|
|
8759
7505
|
var DEFAULT_INTERVAL_MS = 2e3;
|
|
8760
7506
|
var RESERVED_SUBDIR = "_uploaded";
|
|
8761
7507
|
var OutboxWatcher = class {
|
|
@@ -8922,7 +7668,7 @@ var OutboxWatcher = class {
|
|
|
8922
7668
|
async scanDir(dir, kind, task) {
|
|
8923
7669
|
let entries = [];
|
|
8924
7670
|
try {
|
|
8925
|
-
entries = await
|
|
7671
|
+
entries = await import_node_fs11.promises.readdir(dir);
|
|
8926
7672
|
} catch (err) {
|
|
8927
7673
|
const code = err.code;
|
|
8928
7674
|
if (code === "ENOENT") return;
|
|
@@ -8932,10 +7678,10 @@ var OutboxWatcher = class {
|
|
|
8932
7678
|
for (const name of entries) {
|
|
8933
7679
|
if (name.startsWith(".")) continue;
|
|
8934
7680
|
if (name === RESERVED_SUBDIR) continue;
|
|
8935
|
-
const full =
|
|
7681
|
+
const full = path6.join(dir, name);
|
|
8936
7682
|
let st;
|
|
8937
7683
|
try {
|
|
8938
|
-
st = await
|
|
7684
|
+
st = await import_node_fs11.promises.stat(full);
|
|
8939
7685
|
} catch {
|
|
8940
7686
|
continue;
|
|
8941
7687
|
}
|
|
@@ -8960,8 +7706,8 @@ var OutboxWatcher = class {
|
|
|
8960
7706
|
this.log("warn", `skip ${filePath}: no active taskId (no dispatch/handoff received yet)`);
|
|
8961
7707
|
return;
|
|
8962
7708
|
}
|
|
8963
|
-
const bytes = await
|
|
8964
|
-
const fileName =
|
|
7709
|
+
const bytes = await import_node_fs11.promises.readFile(filePath);
|
|
7710
|
+
const fileName = path6.basename(filePath);
|
|
8965
7711
|
const blob = new Blob([new Uint8Array(bytes)]);
|
|
8966
7712
|
const form = new FormData();
|
|
8967
7713
|
form.append("workspaceId", wsId);
|
|
@@ -9056,8 +7802,8 @@ var ServicePool = class {
|
|
|
9056
7802
|
|
|
9057
7803
|
// src/daemon/shell-executor.ts
|
|
9058
7804
|
var import_node_child_process6 = require("child_process");
|
|
9059
|
-
var
|
|
9060
|
-
var
|
|
7805
|
+
var import_node_fs12 = require("fs");
|
|
7806
|
+
var import_node_path9 = require("path");
|
|
9061
7807
|
var DEFAULT_OUTPUT_LIMIT = 256 * 1024;
|
|
9062
7808
|
var DEFAULT_TIMEOUT = 6e4;
|
|
9063
7809
|
var HARD_MAX_TIMEOUT = 30 * 6e4;
|
|
@@ -9093,7 +7839,7 @@ async function executeShellDispatch(payload, deps) {
|
|
|
9093
7839
|
const command = readCommand(payload, execution);
|
|
9094
7840
|
if (!command.trim()) return fail2(payload.taskId, "shell_command_required", "Shell command is required");
|
|
9095
7841
|
const cwd = resolveCwd(execution.cwd, deps.config.defaultCwd);
|
|
9096
|
-
if (!(0,
|
|
7842
|
+
if (!(0, import_node_fs12.existsSync)(cwd)) return fail2(payload.taskId, "shell_cwd_missing", `cwd does not exist: ${cwd}`);
|
|
9097
7843
|
const timeoutMs = Math.min(
|
|
9098
7844
|
typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : deps.config.maxTimeoutMs,
|
|
9099
7845
|
deps.config.maxTimeoutMs
|
|
@@ -9195,8 +7941,8 @@ function readCommand(payload, execution) {
|
|
|
9195
7941
|
return payload.prompt;
|
|
9196
7942
|
}
|
|
9197
7943
|
function resolveCwd(raw, fallback) {
|
|
9198
|
-
if (typeof raw !== "string" || raw.trim() === "") return (0,
|
|
9199
|
-
return (0,
|
|
7944
|
+
if (typeof raw !== "string" || raw.trim() === "") return (0, import_node_path9.resolve)(fallback);
|
|
7945
|
+
return (0, import_node_path9.resolve)(raw);
|
|
9200
7946
|
}
|
|
9201
7947
|
function clampNumber(value, fallback, min, max) {
|
|
9202
7948
|
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
@@ -9349,15 +8095,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9349
8095
|
outboxWatcher;
|
|
9350
8096
|
memoryWiring;
|
|
9351
8097
|
assetMetadataIndexes = /* @__PURE__ */ new Map();
|
|
9352
|
-
workspaceMirrors = /* @__PURE__ */ new Map();
|
|
9353
|
-
assetOriginOutbox;
|
|
9354
|
-
dropFolderAdapter;
|
|
9355
|
-
dropFolderUploadRunner;
|
|
9356
|
-
dropFolderTimer;
|
|
9357
|
-
dropFolderWorkspaceId = "";
|
|
9358
|
-
dropFolderWorkspaceDir = "";
|
|
9359
|
-
dropFolderTickInFlight = false;
|
|
9360
|
-
dropFolderStable = /* @__PURE__ */ new Map();
|
|
9361
8098
|
state = "idle";
|
|
9362
8099
|
startedAt = 0;
|
|
9363
8100
|
workspaceId = "";
|
|
@@ -9399,7 +8136,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9399
8136
|
this.servicePool = new ServicePool();
|
|
9400
8137
|
this.installStaticHostedAgentFromEnv();
|
|
9401
8138
|
this.loadAgentsFromDb();
|
|
9402
|
-
await this.prepareLocalProfiles();
|
|
9403
8139
|
this.syncQueue = new SyncQueue(this.db);
|
|
9404
8140
|
this.syncWorker = new SyncWorker({ queue: this.syncQueue, flush: (row) => this.flushSyncRow(row) });
|
|
9405
8141
|
this.syncWorker.start();
|
|
@@ -9421,10 +8157,9 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9421
8157
|
);
|
|
9422
8158
|
}
|
|
9423
8159
|
if (this.workspaceId) {
|
|
9424
|
-
this.
|
|
9425
|
-
(err) => console.error("[Daemon] Initial asset sync failed:", err.message)
|
|
8160
|
+
this.syncAssetMetadata(this.workspaceId).catch(
|
|
8161
|
+
(err) => console.error("[Daemon] Initial asset metadata sync failed:", err.message)
|
|
9426
8162
|
);
|
|
9427
|
-
await this.ensureDropFolderRuntime(this.workspaceId);
|
|
9428
8163
|
}
|
|
9429
8164
|
const containerId = process.env.PRISMER_CONTAINER_ID;
|
|
9430
8165
|
const isContainer = !!containerId || process.env.PRISMER_RUNTIME_MODE === "container";
|
|
@@ -9475,8 +8210,7 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9475
8210
|
// an actual agent process.
|
|
9476
8211
|
attachMemory: this.memoryWiring ? attachMemoryRpc({ runtime: this.memoryWiring.runtime }) : void 0,
|
|
9477
8212
|
attachAsset: attachAssetRpc({
|
|
9478
|
-
resolveIndex: (workspaceId) => this.assetMetadataIndexes.get(workspaceId)
|
|
9479
|
-
assetCache: this.assetCache
|
|
8213
|
+
resolveIndex: (workspaceId) => this.assetMetadataIndexes.get(workspaceId)
|
|
9480
8214
|
})
|
|
9481
8215
|
});
|
|
9482
8216
|
await this.localServer.start();
|
|
@@ -9525,7 +8259,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9525
8259
|
this.memoryWiring = void 0;
|
|
9526
8260
|
this.ws?.close();
|
|
9527
8261
|
this.outboxWatcher?.stop();
|
|
9528
|
-
this.stopDropFolderRuntime();
|
|
9529
8262
|
await this.servicePool?.shutdown();
|
|
9530
8263
|
await this.localServer?.stop();
|
|
9531
8264
|
try {
|
|
@@ -9582,7 +8315,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9582
8315
|
runningTaskIds: Array.from(this.runningTasks.keys()),
|
|
9583
8316
|
observability: {
|
|
9584
8317
|
adapters: this.snapshotAdapterObservability(),
|
|
9585
|
-
assetSync: this.snapshotAssetSyncObservability(),
|
|
9586
8318
|
...this.lastTaskError ? { lastTaskError: this.lastTaskError } : {}
|
|
9587
8319
|
}
|
|
9588
8320
|
};
|
|
@@ -9618,35 +8350,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9618
8350
|
});
|
|
9619
8351
|
}
|
|
9620
8352
|
}
|
|
9621
|
-
async prepareLocalProfiles() {
|
|
9622
|
-
const rows = this.db.prepare(
|
|
9623
|
-
`SELECT id, workspace_id, agent_im_user_id, adapter_name, name, config, version, synced_at
|
|
9624
|
-
FROM agent_profiles
|
|
9625
|
-
WHERE deleted_at IS NULL`
|
|
9626
|
-
).all();
|
|
9627
|
-
for (const row of rows) {
|
|
9628
|
-
const adapter = this.registry.get(row.adapter_name);
|
|
9629
|
-
if (!adapter?.prepareProfile) continue;
|
|
9630
|
-
try {
|
|
9631
|
-
await adapter.prepareProfile({
|
|
9632
|
-
id: row.id,
|
|
9633
|
-
workspaceId: row.workspace_id,
|
|
9634
|
-
agentImUserId: row.agent_im_user_id,
|
|
9635
|
-
adapterName: row.adapter_name,
|
|
9636
|
-
name: row.name,
|
|
9637
|
-
config: parseProfileConfig(row.config),
|
|
9638
|
-
version: row.version,
|
|
9639
|
-
createdAt: new Date(row.synced_at ?? Date.now()),
|
|
9640
|
-
updatedAt: new Date(row.synced_at ?? Date.now())
|
|
9641
|
-
});
|
|
9642
|
-
} catch (err) {
|
|
9643
|
-
process.stderr.write(
|
|
9644
|
-
`[daemon] local profile preflight skipped agent=${row.agent_im_user_id} profile=${row.id}: ${err.message}
|
|
9645
|
-
`
|
|
9646
|
-
);
|
|
9647
|
-
}
|
|
9648
|
-
}
|
|
9649
|
-
}
|
|
9650
8353
|
/**
|
|
9651
8354
|
* Register an in-process AgentProfile snapshot (called by agent CLI / sync layer).
|
|
9652
8355
|
* Used to populate the `agents` list in agent.host.declare.
|
|
@@ -9704,10 +8407,10 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9704
8407
|
const rawJson = process.env.PRISMER_HOSTED_AGENT_JSON;
|
|
9705
8408
|
let raw;
|
|
9706
8409
|
if (rawFile) {
|
|
9707
|
-
if (!(0,
|
|
8410
|
+
if (!(0, import_node_fs13.existsSync)(rawFile)) {
|
|
9708
8411
|
throw new Error(`PRISMER_HOSTED_AGENT_FILE not found: ${rawFile}`);
|
|
9709
8412
|
}
|
|
9710
|
-
raw = (0,
|
|
8413
|
+
raw = (0, import_node_fs13.readFileSync)(rawFile, "utf8");
|
|
9711
8414
|
} else if (rawJson) {
|
|
9712
8415
|
raw = rawJson;
|
|
9713
8416
|
}
|
|
@@ -9803,13 +8506,13 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9803
8506
|
const payload = {
|
|
9804
8507
|
daemonId: this.config.daemon_id,
|
|
9805
8508
|
daemonVersion: this.opts.daemonVersion ?? "0.0.0",
|
|
9806
|
-
platform: (0,
|
|
8509
|
+
platform: (0, import_node_os4.platform)() === "win32" ? "win32" : (0, import_node_os4.platform)() === "linux" ? "linux" : "darwin",
|
|
9807
8510
|
agents: Array.from(this.hostedAgents.values()).map((a) => ({
|
|
9808
8511
|
imUserId: a.imUserId,
|
|
9809
8512
|
name: a.name,
|
|
9810
8513
|
adapterName: a.adapterName,
|
|
9811
8514
|
capabilities: a.capabilities,
|
|
9812
|
-
profiles: Array.from(a.profiles.entries()).map(([id,
|
|
8515
|
+
profiles: Array.from(a.profiles.entries()).map(([id, version2]) => ({ id, version: version2 }))
|
|
9813
8516
|
}))
|
|
9814
8517
|
};
|
|
9815
8518
|
this.ws.send(envelope("agent.host.declare", payload, this.config.daemon_id));
|
|
@@ -9851,29 +8554,17 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9851
8554
|
(err) => console.error("[Daemon] Initial memory sync failed:", err.message)
|
|
9852
8555
|
);
|
|
9853
8556
|
}
|
|
9854
|
-
this.
|
|
9855
|
-
(err) => console.error("[Daemon] Asset sync failed:", err.message)
|
|
8557
|
+
this.syncAssetMetadata(this.workspaceId).catch(
|
|
8558
|
+
(err) => console.error("[Daemon] Asset metadata sync failed:", err.message)
|
|
9856
8559
|
);
|
|
9857
|
-
await this.ensureDropFolderRuntime(this.workspaceId);
|
|
9858
8560
|
for (const id of payload.profilesToSync) {
|
|
9859
8561
|
try {
|
|
9860
|
-
await this.servicePool.drop(id);
|
|
9861
8562
|
await this.syncProfileFromCloud(id);
|
|
9862
8563
|
} catch (err) {
|
|
9863
8564
|
this.emit("sync-error", err);
|
|
9864
8565
|
}
|
|
9865
8566
|
}
|
|
9866
|
-
|
|
9867
|
-
const row = this.db.prepare("SELECT agent_im_user_id FROM agent_profiles WHERE id = ?").get(id);
|
|
9868
|
-
this.db.prepare("DELETE FROM agent_profiles WHERE id = ?").run(id);
|
|
9869
|
-
if (row) {
|
|
9870
|
-
this.db.prepare("DELETE FROM agents WHERE im_user_id = ?").run(row.agent_im_user_id);
|
|
9871
|
-
}
|
|
9872
|
-
process.stderr.write(`[daemon] tombstone profile=${id}
|
|
9873
|
-
`);
|
|
9874
|
-
}
|
|
9875
|
-
if (payload.profilesToDelete?.length) this.loadAgentsFromDb();
|
|
9876
|
-
if ((payload.profilesToSync.length > 0 || (payload.profilesToDelete?.length ?? 0) > 0) && this.wsConnected) this.sendDeclare();
|
|
8567
|
+
if (payload.profilesToSync.length > 0 && this.wsConnected) this.sendDeclare();
|
|
9877
8568
|
this.emit("host-acked", payload);
|
|
9878
8569
|
}
|
|
9879
8570
|
async onTaskDispatch(payload, requestId) {
|
|
@@ -9914,14 +8605,8 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9914
8605
|
}
|
|
9915
8606
|
});
|
|
9916
8607
|
this.ws.send(envelope("task.dispatch.reply", reply, requestId));
|
|
9917
|
-
if (!reply.ok) {
|
|
9918
|
-
const code = reply.error?.code ?? "unknown";
|
|
9919
|
-
const message = reply.error?.message ?? "unknown error";
|
|
9920
|
-
process.stderr.write(`[daemon] dispatch failed task=${payload.taskId} code=${code} message=${message}
|
|
9921
|
-
`);
|
|
9922
|
-
}
|
|
9923
8608
|
} else {
|
|
9924
|
-
|
|
8609
|
+
await handleDispatch(payload, requestId, {
|
|
9925
8610
|
registry: this.registry,
|
|
9926
8611
|
cloud: this.cloud,
|
|
9927
8612
|
uriResolver: this.uriResolver,
|
|
@@ -9937,12 +8622,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9937
8622
|
if (running) running.lastProgressAt = Date.now();
|
|
9938
8623
|
}
|
|
9939
8624
|
});
|
|
9940
|
-
if (!reply.ok) {
|
|
9941
|
-
const code = reply.error?.code ?? "unknown";
|
|
9942
|
-
const message = reply.error?.message ?? "unknown error";
|
|
9943
|
-
process.stderr.write(`[daemon] dispatch failed task=${payload.taskId} code=${code} message=${message}
|
|
9944
|
-
`);
|
|
9945
|
-
}
|
|
9946
8625
|
}
|
|
9947
8626
|
process.stdout.write(`[daemon] dispatch done task=${payload.taskId}
|
|
9948
8627
|
`);
|
|
@@ -9970,7 +8649,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9970
8649
|
}
|
|
9971
8650
|
async onAgentProfileChanged(payload) {
|
|
9972
8651
|
try {
|
|
9973
|
-
await this.servicePool.drop(payload.profileId);
|
|
9974
8652
|
await this.syncProfileFromCloud(payload.profileId);
|
|
9975
8653
|
if (this.wsConnected) this.sendDeclare();
|
|
9976
8654
|
} catch (err) {
|
|
@@ -9978,21 +8656,7 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
9978
8656
|
}
|
|
9979
8657
|
}
|
|
9980
8658
|
async syncProfileFromCloud(profileId) {
|
|
9981
|
-
|
|
9982
|
-
try {
|
|
9983
|
-
profile = await this.cloud.get(`/api/im/agent_profiles/${encodeURIComponent(profileId)}`);
|
|
9984
|
-
} catch (err) {
|
|
9985
|
-
if (err instanceof CloudError && err.status === 404) {
|
|
9986
|
-
const row = this.db.prepare("SELECT agent_im_user_id FROM agent_profiles WHERE id = ?").get(profileId);
|
|
9987
|
-
this.db.prepare("DELETE FROM agent_profiles WHERE id = ?").run(profileId);
|
|
9988
|
-
if (row) {
|
|
9989
|
-
this.db.prepare("DELETE FROM agents WHERE im_user_id = ?").run(row.agent_im_user_id);
|
|
9990
|
-
}
|
|
9991
|
-
this.loadAgentsFromDb();
|
|
9992
|
-
return;
|
|
9993
|
-
}
|
|
9994
|
-
throw err;
|
|
9995
|
-
}
|
|
8659
|
+
const profile = await this.cloud.get(`/api/im/agent_profiles/${encodeURIComponent(profileId)}`);
|
|
9996
8660
|
const agent = await this.resolveOwnedAgent(profile.agentImUserId);
|
|
9997
8661
|
const adapter = this.registry.get(profile.adapterName);
|
|
9998
8662
|
const capabilities = agent?.card?.capabilities?.length ? agent.card.capabilities : adapter?.capabilities ?? [];
|
|
@@ -10027,14 +8691,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
10027
8691
|
);
|
|
10028
8692
|
});
|
|
10029
8693
|
tx();
|
|
10030
|
-
try {
|
|
10031
|
-
await adapter?.prepareProfile?.(profile);
|
|
10032
|
-
} catch (err) {
|
|
10033
|
-
process.stderr.write(
|
|
10034
|
-
`[daemon] profile preflight skipped agent=${profile.agentImUserId} profile=${profile.id}: ${err.message}
|
|
10035
|
-
`
|
|
10036
|
-
);
|
|
10037
|
-
}
|
|
10038
8694
|
this.loadAgentsFromDb();
|
|
10039
8695
|
process.stdout.write(
|
|
10040
8696
|
`[daemon] profile synced agent=${profile.agentImUserId} profile=${profile.id} adapter=${profile.adapterName}
|
|
@@ -10063,10 +8719,15 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
10063
8719
|
}
|
|
10064
8720
|
async onAssetChanged(payload) {
|
|
10065
8721
|
if (!payload.workspaceId) return;
|
|
8722
|
+
const index = this.assetMetadataIndexes.get(payload.workspaceId);
|
|
8723
|
+
if (!index) return;
|
|
10066
8724
|
try {
|
|
10067
|
-
await
|
|
8725
|
+
const result = await index.pullDelta();
|
|
8726
|
+
if (result.applied > 0) {
|
|
8727
|
+
console.log(`[Daemon] asset.changed workspace=${payload.workspaceId} applied=${result.applied}`);
|
|
8728
|
+
}
|
|
10068
8729
|
} catch (err) {
|
|
10069
|
-
console.error(`[Daemon] asset.changed
|
|
8730
|
+
console.error(`[Daemon] asset.changed pullDelta failed workspace=${payload.workspaceId}:`, err.message);
|
|
10070
8731
|
}
|
|
10071
8732
|
}
|
|
10072
8733
|
onWorkspaceFileChanged(payload) {
|
|
@@ -10104,15 +8765,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
10104
8765
|
}
|
|
10105
8766
|
};
|
|
10106
8767
|
}
|
|
10107
|
-
snapshotAssetSyncObservability() {
|
|
10108
|
-
return {
|
|
10109
|
-
workspaceId: this.dropFolderWorkspaceId || null,
|
|
10110
|
-
rootDir: this.dropFolderWorkspaceDir || null,
|
|
10111
|
-
dropDir: this.dropFolderWorkspaceDir ? (0, import_node_path13.join)(this.dropFolderWorkspaceDir, "drop") : null,
|
|
10112
|
-
uploadedDir: this.dropFolderWorkspaceDir ? (0, import_node_path13.join)(this.dropFolderWorkspaceDir, "uploaded") : null,
|
|
10113
|
-
failedDir: this.dropFolderWorkspaceDir ? (0, import_node_path13.join)(this.dropFolderWorkspaceDir, "upload-failed") : null
|
|
10114
|
-
};
|
|
10115
|
-
}
|
|
10116
8768
|
/**
|
|
10117
8769
|
* Ensure an AssetMetadataIndex exists for the given workspace and pull
|
|
10118
8770
|
* delta from cloud. Idempotent — creates the index on first call, reuses
|
|
@@ -10135,132 +8787,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
10135
8787
|
console.log(`[AssetMeta] workspace=${workspaceId}: ${result.applied} applied, cursor=${result.cursor}`);
|
|
10136
8788
|
}
|
|
10137
8789
|
}
|
|
10138
|
-
/**
|
|
10139
|
-
* Ensure a WorkspaceMirror exists for the workspace and pull path→asset
|
|
10140
|
-
* bindings from cloud. Cold-start daemons previously only had this table
|
|
10141
|
-
* populated by WS push or per-path URI fallback, so local asset state looked
|
|
10142
|
-
* empty even though cloud files existed.
|
|
10143
|
-
*/
|
|
10144
|
-
async syncWorkspaceFiles(workspaceId) {
|
|
10145
|
-
let mirror = this.workspaceMirrors.get(workspaceId);
|
|
10146
|
-
if (!mirror) {
|
|
10147
|
-
const stateDir = `${this.paths.root}/${workspaceId}`;
|
|
10148
|
-
mirror = new WorkspaceMirror({
|
|
10149
|
-
db: this.db,
|
|
10150
|
-
cloud: this.cloud,
|
|
10151
|
-
workspaceId,
|
|
10152
|
-
workspaceStateDir: stateDir
|
|
10153
|
-
});
|
|
10154
|
-
this.workspaceMirrors.set(workspaceId, mirror);
|
|
10155
|
-
}
|
|
10156
|
-
const result = await mirror.pullDelta();
|
|
10157
|
-
if (result.applied > 0) {
|
|
10158
|
-
console.log(`[WorkspaceFiles] workspace=${workspaceId}: ${result.applied} applied, cursor=${result.cursor ?? "null"}`);
|
|
10159
|
-
}
|
|
10160
|
-
}
|
|
10161
|
-
async syncAssetState(workspaceId) {
|
|
10162
|
-
if (!this.paths?.root) return;
|
|
10163
|
-
await Promise.all([
|
|
10164
|
-
this.syncAssetMetadata(workspaceId),
|
|
10165
|
-
this.syncWorkspaceFiles(workspaceId)
|
|
10166
|
-
]);
|
|
10167
|
-
}
|
|
10168
|
-
/**
|
|
10169
|
-
* Desktop asset ingress: watch a user-visible local asset folder
|
|
10170
|
-
* and upload new files as IMAssets, preserving nested relative dirs in
|
|
10171
|
-
* IMAsset.folderPath. The lower-level adapter/outbox/uploader already
|
|
10172
|
-
* existed; this method wires them into the real daemon lifecycle.
|
|
10173
|
-
*/
|
|
10174
|
-
async ensureDropFolderRuntime(workspaceId) {
|
|
10175
|
-
if (!workspaceId || !this.paths?.root) return;
|
|
10176
|
-
if (this.dropFolderWorkspaceId === workspaceId && this.dropFolderTimer) return;
|
|
10177
|
-
this.stopDropFolderRuntime();
|
|
10178
|
-
this.dropFolderWorkspaceId = workspaceId;
|
|
10179
|
-
const workspaceRoot = resolveUserAssetWorkspaceDir(workspaceId);
|
|
10180
|
-
this.dropFolderWorkspaceDir = workspaceRoot;
|
|
10181
|
-
await (0, import_promises.mkdir)((0, import_node_path13.join)(workspaceRoot, "drop"), { recursive: true });
|
|
10182
|
-
await (0, import_promises.mkdir)((0, import_node_path13.join)(workspaceRoot, "uploaded"), { recursive: true });
|
|
10183
|
-
await (0, import_promises.mkdir)((0, import_node_path13.join)(workspaceRoot, "upload-failed"), { recursive: true });
|
|
10184
|
-
this.assetOriginOutbox = new OriginOutbox({ dbPath: (0, import_node_path13.join)(this.paths.root, "asset-origin.db") });
|
|
10185
|
-
this.dropFolderAdapter = new DropFolderAdapter({ workspaceDir: workspaceRoot });
|
|
10186
|
-
this.dropFolderUploadRunner = new UploadRunner({
|
|
10187
|
-
outbox: this.assetOriginOutbox,
|
|
10188
|
-
cloud: new DaemonAssetUploadClient(this.config),
|
|
10189
|
-
adapters: { "drop-folder": this.dropFolderAdapter },
|
|
10190
|
-
maxAttempts: 3
|
|
10191
|
-
});
|
|
10192
|
-
const tick = () => {
|
|
10193
|
-
this.runDropFolderTick().catch((err) => {
|
|
10194
|
-
process.stderr.write(`[daemon] drop-folder sync failed: ${err.message}
|
|
10195
|
-
`);
|
|
10196
|
-
});
|
|
10197
|
-
};
|
|
10198
|
-
this.dropFolderTimer = setInterval(tick, 1e3);
|
|
10199
|
-
tick();
|
|
10200
|
-
process.stdout.write(`[daemon] drop-folder asset sync watching ${(0, import_node_path13.join)(workspaceRoot, "drop")}
|
|
10201
|
-
`);
|
|
10202
|
-
}
|
|
10203
|
-
stopDropFolderRuntime() {
|
|
10204
|
-
if (this.dropFolderTimer) {
|
|
10205
|
-
clearInterval(this.dropFolderTimer);
|
|
10206
|
-
this.dropFolderTimer = void 0;
|
|
10207
|
-
}
|
|
10208
|
-
try {
|
|
10209
|
-
this.assetOriginOutbox?.close();
|
|
10210
|
-
} catch {
|
|
10211
|
-
}
|
|
10212
|
-
this.assetOriginOutbox = void 0;
|
|
10213
|
-
this.dropFolderAdapter = void 0;
|
|
10214
|
-
this.dropFolderUploadRunner = void 0;
|
|
10215
|
-
this.dropFolderWorkspaceId = "";
|
|
10216
|
-
this.dropFolderWorkspaceDir = "";
|
|
10217
|
-
this.dropFolderTickInFlight = false;
|
|
10218
|
-
this.dropFolderStable.clear();
|
|
10219
|
-
}
|
|
10220
|
-
async runDropFolderTick() {
|
|
10221
|
-
if (this.dropFolderTickInFlight) return;
|
|
10222
|
-
const workspaceId = this.dropFolderWorkspaceId;
|
|
10223
|
-
const adapter = this.dropFolderAdapter;
|
|
10224
|
-
const outbox = this.assetOriginOutbox;
|
|
10225
|
-
const runner = this.dropFolderUploadRunner;
|
|
10226
|
-
if (!workspaceId || !adapter || !outbox || !runner) return;
|
|
10227
|
-
this.dropFolderTickInFlight = true;
|
|
10228
|
-
try {
|
|
10229
|
-
const observations = await adapter.scanOnce(workspaceId);
|
|
10230
|
-
for (const obs of observations) {
|
|
10231
|
-
const id = await adapter.identifySource(obs);
|
|
10232
|
-
if (!this.isDropFolderObservationStable(id.sourceRef, obs.detail)) continue;
|
|
10233
|
-
outbox.enqueue({
|
|
10234
|
-
workspaceId: obs.workspaceId,
|
|
10235
|
-
originKind: "drop-folder",
|
|
10236
|
-
sourceRef: id.sourceRef,
|
|
10237
|
-
payloadJson: JSON.stringify(obs.detail),
|
|
10238
|
-
hintsJson: JSON.stringify(id.hints ?? {}),
|
|
10239
|
-
observedAt: obs.observedAt
|
|
10240
|
-
});
|
|
10241
|
-
}
|
|
10242
|
-
const processed = await runner.drainOnce();
|
|
10243
|
-
if (processed > 0) {
|
|
10244
|
-
this.dropFolderStable.clear();
|
|
10245
|
-
process.stdout.write(`[daemon] drop-folder uploaded/processed ${processed} asset(s)
|
|
10246
|
-
`);
|
|
10247
|
-
await this.syncAssetState(workspaceId).catch(
|
|
10248
|
-
(err) => process.stderr.write(`[daemon] drop-folder post-sync failed: ${err.message}
|
|
10249
|
-
`)
|
|
10250
|
-
);
|
|
10251
|
-
}
|
|
10252
|
-
} finally {
|
|
10253
|
-
this.dropFolderTickInFlight = false;
|
|
10254
|
-
}
|
|
10255
|
-
}
|
|
10256
|
-
isDropFolderObservationStable(sourceRef, detail) {
|
|
10257
|
-
const raw = detail && typeof detail === "object" ? detail : {};
|
|
10258
|
-
const size = typeof raw.size === "number" ? raw.size : -1;
|
|
10259
|
-
const mtime = typeof raw.mtime === "number" ? raw.mtime : -1;
|
|
10260
|
-
const previous = this.dropFolderStable.get(sourceRef);
|
|
10261
|
-
this.dropFolderStable.set(sourceRef, { size, mtime });
|
|
10262
|
-
return Boolean(previous && previous.size === size && previous.mtime === mtime);
|
|
10263
|
-
}
|
|
10264
8790
|
/**
|
|
10265
8791
|
* SyncWorker FlushFn — pushes local writes to cloud.
|
|
10266
8792
|
*
|
|
@@ -10297,44 +8823,44 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
10297
8823
|
const id = encodeURIComponent(row.resource_id);
|
|
10298
8824
|
const body = row.operation === "delete" ? void 0 : safeJsonParse(row.payload);
|
|
10299
8825
|
let method;
|
|
10300
|
-
let
|
|
8826
|
+
let path7;
|
|
10301
8827
|
switch (op) {
|
|
10302
8828
|
case "workspace.create":
|
|
10303
8829
|
method = "POST";
|
|
10304
|
-
|
|
8830
|
+
path7 = "/api/im/workspaces";
|
|
10305
8831
|
break;
|
|
10306
8832
|
case "workspace.update":
|
|
10307
8833
|
method = "PATCH";
|
|
10308
|
-
|
|
8834
|
+
path7 = `/api/im/workspaces/${id}`;
|
|
10309
8835
|
break;
|
|
10310
8836
|
case "workspace.delete":
|
|
10311
8837
|
method = "DELETE";
|
|
10312
|
-
|
|
8838
|
+
path7 = `/api/im/workspaces/${id}`;
|
|
10313
8839
|
break;
|
|
10314
8840
|
// agent.create must use /register — POST /api/im/agents is not exposed.
|
|
10315
8841
|
case "agent.create":
|
|
10316
8842
|
method = "POST";
|
|
10317
|
-
|
|
8843
|
+
path7 = "/api/im/register";
|
|
10318
8844
|
break;
|
|
10319
8845
|
case "agent.update":
|
|
10320
8846
|
method = "PATCH";
|
|
10321
|
-
|
|
8847
|
+
path7 = `/api/im/agents/${id}`;
|
|
10322
8848
|
break;
|
|
10323
8849
|
case "agent.delete":
|
|
10324
8850
|
method = "DELETE";
|
|
10325
|
-
|
|
8851
|
+
path7 = `/api/im/agents/${id}`;
|
|
10326
8852
|
break;
|
|
10327
8853
|
case "agent_profile.create":
|
|
10328
8854
|
method = "POST";
|
|
10329
|
-
|
|
8855
|
+
path7 = "/api/im/agent_profiles";
|
|
10330
8856
|
break;
|
|
10331
8857
|
case "agent_profile.update":
|
|
10332
8858
|
method = "PATCH";
|
|
10333
|
-
|
|
8859
|
+
path7 = `/api/im/agent_profiles/${id}`;
|
|
10334
8860
|
break;
|
|
10335
8861
|
case "agent_profile.delete":
|
|
10336
8862
|
method = "DELETE";
|
|
10337
|
-
|
|
8863
|
+
path7 = `/api/im/agent_profiles/${id}`;
|
|
10338
8864
|
break;
|
|
10339
8865
|
default:
|
|
10340
8866
|
process.stderr.write(`[daemon] sync flush op=${op} id=${row.id} result=drop (unknown op)
|
|
@@ -10343,7 +8869,7 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
10343
8869
|
}
|
|
10344
8870
|
let res;
|
|
10345
8871
|
try {
|
|
10346
|
-
res = await this.cloud.request(method,
|
|
8872
|
+
res = await this.cloud.request(method, path7, { body });
|
|
10347
8873
|
} catch (err) {
|
|
10348
8874
|
process.stderr.write(
|
|
10349
8875
|
`[daemon] sync flush op=${op} id=${row.id} result=retry (threw: ${err.message})
|
|
@@ -10366,89 +8892,6 @@ var Runner = class extends import_node_events3.EventEmitter {
|
|
|
10366
8892
|
return { ok: res.ok, status: res.status, message: res.error?.message };
|
|
10367
8893
|
}
|
|
10368
8894
|
};
|
|
10369
|
-
var DaemonAssetUploadClient = class {
|
|
10370
|
-
constructor(config) {
|
|
10371
|
-
this.config = config;
|
|
10372
|
-
}
|
|
10373
|
-
config;
|
|
10374
|
-
async uploadAsset(req) {
|
|
10375
|
-
const form = new FormData();
|
|
10376
|
-
form.set("workspaceId", req.workspaceId);
|
|
10377
|
-
form.set("kind", "file");
|
|
10378
|
-
form.set("metadata", JSON.stringify(req.metadata));
|
|
10379
|
-
if (req.folderPath !== void 0) form.set("folderPath", req.folderPath);
|
|
10380
|
-
form.set("file", new Blob([req.bytes], { type: req.mime ?? "application/octet-stream" }), req.filename);
|
|
10381
|
-
const url = `${this.config.cloud_api_base.replace(/\/$/, "")}/api/im/assets`;
|
|
10382
|
-
const controller = new AbortController();
|
|
10383
|
-
const timer = setTimeout(() => controller.abort(), 6e4);
|
|
10384
|
-
let res;
|
|
10385
|
-
try {
|
|
10386
|
-
res = await fetch(url, {
|
|
10387
|
-
method: "POST",
|
|
10388
|
-
headers: { Authorization: `Bearer ${this.config.api_key}` },
|
|
10389
|
-
body: form,
|
|
10390
|
-
signal: controller.signal
|
|
10391
|
-
});
|
|
10392
|
-
} catch (err) {
|
|
10393
|
-
if (err.name === "AbortError") {
|
|
10394
|
-
throw new Error("asset upload timed out after 60000ms");
|
|
10395
|
-
}
|
|
10396
|
-
throw err;
|
|
10397
|
-
} finally {
|
|
10398
|
-
clearTimeout(timer);
|
|
10399
|
-
}
|
|
10400
|
-
const text = await res.text();
|
|
10401
|
-
let body;
|
|
10402
|
-
try {
|
|
10403
|
-
body = text ? JSON.parse(text) : void 0;
|
|
10404
|
-
} catch {
|
|
10405
|
-
body = text;
|
|
10406
|
-
}
|
|
10407
|
-
if (!res.ok || isCloudErrorEnvelope(body)) {
|
|
10408
|
-
throw new Error(`asset upload failed (${res.status}): ${cloudUploadErrorMessage(body)}`);
|
|
10409
|
-
}
|
|
10410
|
-
const data = unwrapCloudData(body);
|
|
10411
|
-
const assetId = stringValue(data, "id");
|
|
10412
|
-
const contentHash = stringValue(data, "contentHash");
|
|
10413
|
-
if (!assetId || !contentHash) {
|
|
10414
|
-
throw new Error("asset upload response missing id/contentHash");
|
|
10415
|
-
}
|
|
10416
|
-
return { assetId, contentHash };
|
|
10417
|
-
}
|
|
10418
|
-
};
|
|
10419
|
-
function isCloudErrorEnvelope(raw) {
|
|
10420
|
-
return Boolean(raw && typeof raw === "object" && "ok" in raw && raw.ok === false);
|
|
10421
|
-
}
|
|
10422
|
-
function cloudUploadErrorMessage(raw) {
|
|
10423
|
-
if (raw && typeof raw === "object") {
|
|
10424
|
-
const error = raw.error;
|
|
10425
|
-
if (typeof error === "string") return error;
|
|
10426
|
-
if (error && typeof error === "object") {
|
|
10427
|
-
const message = error.message;
|
|
10428
|
-
if (typeof message === "string") return message;
|
|
10429
|
-
}
|
|
10430
|
-
}
|
|
10431
|
-
return typeof raw === "string" ? raw : "request failed";
|
|
10432
|
-
}
|
|
10433
|
-
function unwrapCloudData(raw) {
|
|
10434
|
-
if (raw && typeof raw === "object") {
|
|
10435
|
-
const data = raw.data;
|
|
10436
|
-
if (data && typeof data === "object" && !Array.isArray(data)) return data;
|
|
10437
|
-
}
|
|
10438
|
-
return {};
|
|
10439
|
-
}
|
|
10440
|
-
function stringValue(obj, key) {
|
|
10441
|
-
const value = obj[key];
|
|
10442
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
10443
|
-
}
|
|
10444
|
-
function resolveUserAssetWorkspaceDir(workspaceId) {
|
|
10445
|
-
const override = process.env.PRISMER_ASSET_SYNC_ROOT;
|
|
10446
|
-
if (override) return (0, import_node_path13.join)(override, workspaceId);
|
|
10447
|
-
const home = (0, import_node_os6.homedir)();
|
|
10448
|
-
const desktop = (0, import_node_path13.join)(home, "Desktop");
|
|
10449
|
-
const base = (0, import_node_fs17.existsSync)(desktop) ? desktop : home;
|
|
10450
|
-
return (0, import_node_path13.join)(base, "Prismer Assets", workspaceId);
|
|
10451
|
-
}
|
|
10452
8895
|
function readTargetDaemonId(payload) {
|
|
10453
8896
|
if (typeof payload.targetDaemonId === "string" && payload.targetDaemonId.length > 0) {
|
|
10454
8897
|
return payload.targetDaemonId;
|
|
@@ -10499,14 +8942,6 @@ function validateStaticHostedAgent(raw) {
|
|
|
10499
8942
|
}
|
|
10500
8943
|
};
|
|
10501
8944
|
}
|
|
10502
|
-
function parseProfileConfig(raw) {
|
|
10503
|
-
try {
|
|
10504
|
-
const parsed = JSON.parse(raw);
|
|
10505
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
10506
|
-
} catch {
|
|
10507
|
-
return {};
|
|
10508
|
-
}
|
|
10509
|
-
}
|
|
10510
8945
|
function safeJsonParse(raw) {
|
|
10511
8946
|
try {
|
|
10512
8947
|
return JSON.parse(raw);
|
|
@@ -10533,7 +8968,7 @@ function buildDaemonCommand() {
|
|
|
10533
8968
|
async function runForeground(opts) {
|
|
10534
8969
|
const paths = resolvePaths();
|
|
10535
8970
|
const existingPid = readPidFile(paths);
|
|
10536
|
-
if (existingPid &&
|
|
8971
|
+
if (existingPid && pidAlive(existingPid)) {
|
|
10537
8972
|
exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
|
|
10538
8973
|
}
|
|
10539
8974
|
writePidFile(paths, process.pid);
|
|
@@ -10557,7 +8992,7 @@ function buildDaemonCommand() {
|
|
|
10557
8992
|
while (!configExists(paths)) {
|
|
10558
8993
|
if (stopRequested) return;
|
|
10559
8994
|
getUI().info(`[daemon] waiting for ${paths.configFile} \u2014 run \`prismer setup\` to create it`);
|
|
10560
|
-
await (0,
|
|
8995
|
+
await (0, import_promises.setTimeout)(5e3);
|
|
10561
8996
|
}
|
|
10562
8997
|
runner = new Runner({
|
|
10563
8998
|
startLocalServer: opts.localServer !== false,
|
|
@@ -10586,12 +9021,12 @@ function buildDaemonCommand() {
|
|
|
10586
9021
|
async function startBackground(opts) {
|
|
10587
9022
|
const paths = resolvePaths();
|
|
10588
9023
|
const existingPid = readPidFile(paths);
|
|
10589
|
-
if (existingPid &&
|
|
9024
|
+
if (existingPid && pidAlive(existingPid)) {
|
|
10590
9025
|
exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
|
|
10591
9026
|
}
|
|
10592
|
-
if (!(0,
|
|
10593
|
-
const logFile = (0,
|
|
10594
|
-
const fd = (0,
|
|
9027
|
+
if (!(0, import_node_fs14.existsSync)(paths.logsDir)) (0, import_node_fs14.mkdirSync)(paths.logsDir, { recursive: true });
|
|
9028
|
+
const logFile = (0, import_node_path10.join)(paths.logsDir, "daemon.log");
|
|
9029
|
+
const fd = (0, import_node_fs14.openSync)(logFile, "a");
|
|
10595
9030
|
const args = [process.argv[1], "daemon", "run"];
|
|
10596
9031
|
if (opts.port) args.push("--port", String(opts.port));
|
|
10597
9032
|
if (opts.localServer === false) args.push("--no-local-server");
|
|
@@ -10603,9 +9038,9 @@ function buildDaemonCommand() {
|
|
|
10603
9038
|
child.unref();
|
|
10604
9039
|
const deadline = Date.now() + 5e3;
|
|
10605
9040
|
while (Date.now() < deadline) {
|
|
10606
|
-
await (0,
|
|
9041
|
+
await (0, import_promises.setTimeout)(200);
|
|
10607
9042
|
const pid = readPidFile(paths);
|
|
10608
|
-
if (pid &&
|
|
9043
|
+
if (pid && pidAlive(pid)) {
|
|
10609
9044
|
if (opts.json) printJson({ ok: true, pid, logFile });
|
|
10610
9045
|
else getUI().ok("Daemon started", `pid ${pid} \xB7 logs ${logFile}`);
|
|
10611
9046
|
return;
|
|
@@ -10620,7 +9055,7 @@ function buildDaemonCommand() {
|
|
|
10620
9055
|
const paths = resolvePaths();
|
|
10621
9056
|
const pid = readPidFile(paths);
|
|
10622
9057
|
if (!pid) exitWithError("No daemon.pid found \u2014 daemon does not appear to be running.");
|
|
10623
|
-
if (!
|
|
9058
|
+
if (!pidAlive(pid)) {
|
|
10624
9059
|
clearPidFile(paths);
|
|
10625
9060
|
getUI().warn("Daemon was not alive", `pid ${pid}; cleared stale pid file`);
|
|
10626
9061
|
return;
|
|
@@ -10632,12 +9067,12 @@ function buildDaemonCommand() {
|
|
|
10632
9067
|
}
|
|
10633
9068
|
const deadline = Date.now() + (opts.timeout ?? 5e3);
|
|
10634
9069
|
while (Date.now() < deadline) {
|
|
10635
|
-
if (!
|
|
9070
|
+
if (!pidAlive(pid)) {
|
|
10636
9071
|
clearPidFile(paths);
|
|
10637
9072
|
getUI().ok("Daemon stopped", `pid ${pid}`);
|
|
10638
9073
|
return;
|
|
10639
9074
|
}
|
|
10640
|
-
await (0,
|
|
9075
|
+
await (0, import_promises.setTimeout)(200);
|
|
10641
9076
|
}
|
|
10642
9077
|
process.stderr.write(`Daemon did not exit within timeout; pid ${pid} may still be alive.
|
|
10643
9078
|
`);
|
|
@@ -10657,7 +9092,7 @@ function buildDaemonCommand() {
|
|
|
10657
9092
|
cmd.command("status").description("Show daemon process + WS state").option("--json", "Output JSON (default)").action(async () => {
|
|
10658
9093
|
const paths = resolvePaths();
|
|
10659
9094
|
const pid = readPidFile(paths);
|
|
10660
|
-
const alive = pid ?
|
|
9095
|
+
const alive = pid ? pidAlive(pid) : false;
|
|
10661
9096
|
if (!pid || !alive) {
|
|
10662
9097
|
printJson({ running: false, pid: pid ?? null, paths: { config: paths.configFile } });
|
|
10663
9098
|
return;
|
|
@@ -10674,8 +9109,8 @@ function buildDaemonCommand() {
|
|
|
10674
9109
|
});
|
|
10675
9110
|
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) => {
|
|
10676
9111
|
const paths = resolvePaths();
|
|
10677
|
-
const logFile = (0,
|
|
10678
|
-
if (!(0,
|
|
9112
|
+
const logFile = (0, import_node_path10.join)(paths.logsDir, "daemon.log");
|
|
9113
|
+
if (!(0, import_node_fs14.existsSync)(logFile)) {
|
|
10679
9114
|
exitWithError(`No daemon log found at ${logFile}. Start the daemon with \`prismer daemon start\`.`);
|
|
10680
9115
|
}
|
|
10681
9116
|
const lines = Math.max(1, opts.tail);
|
|
@@ -10685,9 +9120,9 @@ function buildDaemonCommand() {
|
|
|
10685
9120
|
});
|
|
10686
9121
|
return cmd;
|
|
10687
9122
|
}
|
|
10688
|
-
async function tailFromEnd(
|
|
9123
|
+
async function tailFromEnd(path7, lines) {
|
|
10689
9124
|
const { open } = await import("fs/promises");
|
|
10690
|
-
const handle = await open(
|
|
9125
|
+
const handle = await open(path7, "r");
|
|
10691
9126
|
try {
|
|
10692
9127
|
const stat = await handle.stat();
|
|
10693
9128
|
let size = stat.size;
|
|
@@ -10715,14 +9150,14 @@ async function tailFromEnd(path9, lines) {
|
|
|
10715
9150
|
await handle.close();
|
|
10716
9151
|
}
|
|
10717
9152
|
}
|
|
10718
|
-
async function followFile(
|
|
10719
|
-
let offset = (0,
|
|
9153
|
+
async function followFile(path7) {
|
|
9154
|
+
let offset = (0, import_node_fs14.statSync)(path7).size;
|
|
10720
9155
|
for (; ; ) {
|
|
10721
|
-
await (0,
|
|
10722
|
-
const size = (0,
|
|
9156
|
+
await (0, import_promises.setTimeout)(1e3);
|
|
9157
|
+
const size = (0, import_node_fs14.statSync)(path7).size;
|
|
10723
9158
|
if (size < offset) offset = 0;
|
|
10724
9159
|
if (size === offset) continue;
|
|
10725
|
-
const stream = (0,
|
|
9160
|
+
const stream = (0, import_node_fs14.createReadStream)(path7, { start: offset, end: size - 1, encoding: "utf8" });
|
|
10726
9161
|
for await (const chunk of stream) process.stdout.write(chunk);
|
|
10727
9162
|
offset = size;
|
|
10728
9163
|
}
|
|
@@ -10730,16 +9165,16 @@ async function followFile(path9) {
|
|
|
10730
9165
|
|
|
10731
9166
|
// src/cli/commands/events.ts
|
|
10732
9167
|
var import_commander9 = require("commander");
|
|
10733
|
-
var
|
|
10734
|
-
var
|
|
10735
|
-
var
|
|
9168
|
+
var import_node_fs15 = require("fs");
|
|
9169
|
+
var import_node_os5 = require("os");
|
|
9170
|
+
var import_node_path11 = require("path");
|
|
10736
9171
|
var import_node_readline = require("readline");
|
|
10737
9172
|
init_util();
|
|
10738
9173
|
var DEFAULT_LIMIT2 = 50;
|
|
10739
9174
|
function buildEventsCommand() {
|
|
10740
9175
|
return addEventOptions(new import_commander9.Command("events").description("Read local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
|
|
10741
9176
|
const file = eventsPath();
|
|
10742
|
-
if (!(0,
|
|
9177
|
+
if (!(0, import_node_fs15.existsSync)(file)) {
|
|
10743
9178
|
printJson(unavailable(file));
|
|
10744
9179
|
process.exitCode = 1;
|
|
10745
9180
|
return;
|
|
@@ -10756,7 +9191,7 @@ function buildEventsCommand() {
|
|
|
10756
9191
|
function buildEventsStatsCommand() {
|
|
10757
9192
|
return addEventOptions(new import_commander9.Command("events:stats").description("Summarize local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
|
|
10758
9193
|
const file = eventsPath();
|
|
10759
|
-
if (!(0,
|
|
9194
|
+
if (!(0, import_node_fs15.existsSync)(file)) {
|
|
10760
9195
|
printJson(unavailable(file));
|
|
10761
9196
|
process.exitCode = 1;
|
|
10762
9197
|
return;
|
|
@@ -10771,10 +9206,10 @@ function buildEventsStatsCommand() {
|
|
|
10771
9206
|
});
|
|
10772
9207
|
}
|
|
10773
9208
|
function addEventOptions(cmd) {
|
|
10774
|
-
return cmd.option("--limit <n>", "Max events to return/read",
|
|
9209
|
+
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)");
|
|
10775
9210
|
}
|
|
10776
9211
|
function eventsPath() {
|
|
10777
|
-
return (0,
|
|
9212
|
+
return (0, import_node_path11.join)(process.env.PRISMER_HOME ?? (0, import_node_path11.join)((0, import_node_os5.homedir)(), ".prismer"), "para", "events.jsonl");
|
|
10778
9213
|
}
|
|
10779
9214
|
function unavailable(file) {
|
|
10780
9215
|
return {
|
|
@@ -10790,7 +9225,7 @@ function unavailable(file) {
|
|
|
10790
9225
|
async function readEvents(file, filters) {
|
|
10791
9226
|
const out = [];
|
|
10792
9227
|
const rl = (0, import_node_readline.createInterface)({
|
|
10793
|
-
input: (0,
|
|
9228
|
+
input: (0, import_node_fs15.createReadStream)(file, { encoding: "utf8" }),
|
|
10794
9229
|
crlfDelay: Infinity
|
|
10795
9230
|
});
|
|
10796
9231
|
for await (const line of rl) {
|
|
@@ -10852,15 +9287,15 @@ function normalizeFilters(opts) {
|
|
|
10852
9287
|
function cleanFilters(filters) {
|
|
10853
9288
|
return Object.fromEntries(Object.entries(filters).filter(([, value]) => value !== void 0));
|
|
10854
9289
|
}
|
|
10855
|
-
function
|
|
9290
|
+
function parsePositiveInt3(v) {
|
|
10856
9291
|
const n = Number.parseInt(v, 10);
|
|
10857
9292
|
return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMIT2;
|
|
10858
9293
|
}
|
|
10859
9294
|
|
|
10860
9295
|
// src/cli/commands/memory.ts
|
|
10861
|
-
var
|
|
9296
|
+
var import_better_sqlite34 = __toESM(require("better-sqlite3"), 1);
|
|
10862
9297
|
var import_commander10 = require("commander");
|
|
10863
|
-
var
|
|
9298
|
+
var import_node_fs16 = require("fs");
|
|
10864
9299
|
init_util();
|
|
10865
9300
|
var LOCAL_BASE = process.env.PRISMER_DAEMON_URL ?? "http://127.0.0.1:3210";
|
|
10866
9301
|
function buildMemoryCommand() {
|
|
@@ -10886,7 +9321,7 @@ function buildMemoryCommand() {
|
|
|
10886
9321
|
}));
|
|
10887
9322
|
process.exitCode = 1;
|
|
10888
9323
|
});
|
|
10889
|
-
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",
|
|
9324
|
+
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) => {
|
|
10890
9325
|
const limit = clampLimit(opts.limit);
|
|
10891
9326
|
const daemonPaths = [];
|
|
10892
9327
|
if (opts.workspaceId) {
|
|
@@ -10905,7 +9340,7 @@ function buildMemoryCommand() {
|
|
|
10905
9340
|
printJson(unavailable2("list", snapshot, { items: snapshot.items, limit }));
|
|
10906
9341
|
process.exitCode = 1;
|
|
10907
9342
|
});
|
|
10908
|
-
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",
|
|
9343
|
+
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) => {
|
|
10909
9344
|
const query = opts.query ?? argQuery ?? "";
|
|
10910
9345
|
const limit = clampLimit(opts.limit);
|
|
10911
9346
|
const encoded = encodeURIComponent(query);
|
|
@@ -10949,9 +9384,9 @@ function buildMemoryCommand() {
|
|
|
10949
9384
|
}
|
|
10950
9385
|
async function tryDaemon(methods, paths) {
|
|
10951
9386
|
for (const method of methods) {
|
|
10952
|
-
for (const
|
|
9387
|
+
for (const path7 of paths) {
|
|
10953
9388
|
try {
|
|
10954
|
-
const res = await fetch(`${LOCAL_BASE}${
|
|
9389
|
+
const res = await fetch(`${LOCAL_BASE}${path7}`, { method, signal: AbortSignal.timeout(1500) });
|
|
10955
9390
|
if (res.status === 404) continue;
|
|
10956
9391
|
const body = await readJson4(res);
|
|
10957
9392
|
if (!res.ok) {
|
|
@@ -10959,12 +9394,12 @@ async function tryDaemon(methods, paths) {
|
|
|
10959
9394
|
ok: false,
|
|
10960
9395
|
error: {
|
|
10961
9396
|
code: `daemon_http_${res.status}`,
|
|
10962
|
-
message: messageFromBody(body) ?? `Daemon endpoint ${method} ${
|
|
9397
|
+
message: messageFromBody(body) ?? `Daemon endpoint ${method} ${path7} returned HTTP ${res.status}`
|
|
10963
9398
|
},
|
|
10964
|
-
checks: { daemon: { url: LOCAL_BASE, endpoint:
|
|
9399
|
+
checks: { daemon: { url: LOCAL_BASE, endpoint: path7, reachable: true } }
|
|
10965
9400
|
};
|
|
10966
9401
|
}
|
|
10967
|
-
return normalizeDaemonBody(body, { method, path:
|
|
9402
|
+
return normalizeDaemonBody(body, { method, path: path7 });
|
|
10968
9403
|
} catch {
|
|
10969
9404
|
continue;
|
|
10970
9405
|
}
|
|
@@ -11012,7 +9447,7 @@ function readCacheSnapshot(limit) {
|
|
|
11012
9447
|
const paths = resolvePaths();
|
|
11013
9448
|
const empty = {
|
|
11014
9449
|
dbPath: paths.localDb,
|
|
11015
|
-
dbExists: (0,
|
|
9450
|
+
dbExists: (0, import_node_fs16.existsSync)(paths.localDb),
|
|
11016
9451
|
tables: {
|
|
11017
9452
|
cached_assets: { exists: false, count: 0, sizeBytes: 0 },
|
|
11018
9453
|
workspace_files_mirror: { exists: false, count: 0 }
|
|
@@ -11022,7 +9457,7 @@ function readCacheSnapshot(limit) {
|
|
|
11022
9457
|
if (!empty.dbExists) return empty;
|
|
11023
9458
|
let db;
|
|
11024
9459
|
try {
|
|
11025
|
-
db = new
|
|
9460
|
+
db = new import_better_sqlite34.default(paths.localDb, { readonly: true, fileMustExist: true });
|
|
11026
9461
|
const hasAssets = tableExists(db, "cached_assets");
|
|
11027
9462
|
const hasFiles = tableExists(db, "workspace_files_mirror");
|
|
11028
9463
|
const assets = hasAssets ? db.prepare("SELECT COUNT(*) AS count, COALESCE(SUM(size_bytes), 0) AS sizeBytes FROM cached_assets").get() : { count: 0, sizeBytes: 0 };
|
|
@@ -11111,7 +9546,7 @@ function searchableText(item) {
|
|
|
11111
9546
|
item.localPath
|
|
11112
9547
|
].filter(Boolean).join(" ");
|
|
11113
9548
|
}
|
|
11114
|
-
function
|
|
9549
|
+
function parsePositiveInt4(v) {
|
|
11115
9550
|
const n = Number.parseInt(v, 10);
|
|
11116
9551
|
return Number.isFinite(n) && n > 0 ? n : 20;
|
|
11117
9552
|
}
|
|
@@ -11134,18 +9569,17 @@ function messageFromBody(body) {
|
|
|
11134
9569
|
var import_commander11 = require("commander");
|
|
11135
9570
|
|
|
11136
9571
|
// src/pair.ts
|
|
11137
|
-
var
|
|
11138
|
-
var
|
|
11139
|
-
var
|
|
9572
|
+
var import_node_crypto5 = require("crypto");
|
|
9573
|
+
var import_node_os7 = require("os");
|
|
9574
|
+
var import_promises2 = require("timers/promises");
|
|
11140
9575
|
var import_qrcode = __toESM(require("qrcode"), 1);
|
|
11141
9576
|
|
|
11142
9577
|
// src/daemon-id.ts
|
|
11143
|
-
var
|
|
11144
|
-
var import_node_os8 = require("os");
|
|
9578
|
+
var import_node_os6 = require("os");
|
|
11145
9579
|
var PREFIX = "daemon-";
|
|
11146
9580
|
function newDaemonId() {
|
|
11147
|
-
const host = (0,
|
|
11148
|
-
return `${PREFIX}${host}
|
|
9581
|
+
const host = (0, import_node_os6.hostname)().replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unknown";
|
|
9582
|
+
return `${PREFIX}${host}`;
|
|
11149
9583
|
}
|
|
11150
9584
|
|
|
11151
9585
|
// src/pair.ts
|
|
@@ -11163,7 +9597,7 @@ async function pair(opts) {
|
|
|
11163
9597
|
"pair: --as-user requires LOCAL_ONLY=1. Without that gate, this would skip mobile approval and silently mint a key for the named user."
|
|
11164
9598
|
);
|
|
11165
9599
|
}
|
|
11166
|
-
const { publicKey } = (0,
|
|
9600
|
+
const { publicKey } = (0, import_node_crypto5.generateKeyPairSync)("ed25519");
|
|
11167
9601
|
const devicePub = publicKey.export({ format: "der", type: "spki" }).toString("base64");
|
|
11168
9602
|
const cloud = new CloudClient({
|
|
11169
9603
|
baseUrl: opts.cloudBaseUrl,
|
|
@@ -11176,7 +9610,7 @@ async function pair(opts) {
|
|
|
11176
9610
|
"/api/im/pair/offer",
|
|
11177
9611
|
{
|
|
11178
9612
|
auth: false,
|
|
11179
|
-
body: { devicePub, deviceName: opts.deviceName ?? (0,
|
|
9613
|
+
body: { devicePub, deviceName: opts.deviceName ?? (0, import_node_os7.hostname)() }
|
|
11180
9614
|
}
|
|
11181
9615
|
);
|
|
11182
9616
|
if (!offerRes.ok) {
|
|
@@ -11216,7 +9650,7 @@ Scan with Lumin to approve, or open: ${offer.qrUrl}
|
|
|
11216
9650
|
const pollIntervalMs = opts.pollIntervalMs ?? 5e3;
|
|
11217
9651
|
for (let i = 0; i < maxAttempts; i += 1) {
|
|
11218
9652
|
if (i > 0 || !localOnlyMode) {
|
|
11219
|
-
await (0,
|
|
9653
|
+
await (0, import_promises2.setTimeout)(pollIntervalMs);
|
|
11220
9654
|
}
|
|
11221
9655
|
const res = await cloud.request(
|
|
11222
9656
|
"GET",
|
|
@@ -11297,9 +9731,9 @@ function buildPairCommand() {
|
|
|
11297
9731
|
|
|
11298
9732
|
// src/cli/commands/profile.ts
|
|
11299
9733
|
var import_commander12 = require("commander");
|
|
11300
|
-
var
|
|
11301
|
-
var
|
|
11302
|
-
var
|
|
9734
|
+
var import_node_fs17 = require("fs");
|
|
9735
|
+
var import_node_os8 = require("os");
|
|
9736
|
+
var import_node_path12 = require("path");
|
|
11303
9737
|
var import_node_child_process8 = require("child_process");
|
|
11304
9738
|
|
|
11305
9739
|
// src/templates/roles/product-manager.json
|
|
@@ -11423,12 +9857,12 @@ function buildProfileCommand() {
|
|
|
11423
9857
|
const profile = await cloud.get(
|
|
11424
9858
|
`/api/im/agent_profiles/${encodeURIComponent(profileId)}`
|
|
11425
9859
|
);
|
|
11426
|
-
const tmpFile = (0,
|
|
11427
|
-
(0,
|
|
9860
|
+
const tmpFile = (0, import_node_path12.join)((0, import_node_os8.tmpdir)(), `prismer-profile-${profileId}.json`);
|
|
9861
|
+
(0, import_node_fs17.writeFileSync)(tmpFile, JSON.stringify(profile.config, null, 2), "utf8");
|
|
11428
9862
|
const editor = process.env.EDITOR || "vi";
|
|
11429
9863
|
const ed = (0, import_node_child_process8.spawnSync)(editor, [tmpFile], { stdio: "inherit" });
|
|
11430
9864
|
if (ed.status !== 0) exitWithError(`editor exited ${ed.status}`, { code: "editor_failed" });
|
|
11431
|
-
const newConfig = JSON.parse((0,
|
|
9865
|
+
const newConfig = JSON.parse((0, import_node_fs17.readFileSync)(tmpFile, "utf8"));
|
|
11432
9866
|
const res = await cloud.request("PATCH", `/api/im/agent_profiles/${encodeURIComponent(profileId)}`, {
|
|
11433
9867
|
body: { config: newConfig, version: profile.version }
|
|
11434
9868
|
});
|
|
@@ -11449,9 +9883,9 @@ function mkCloud4() {
|
|
|
11449
9883
|
}
|
|
11450
9884
|
function readJsonArg(arg) {
|
|
11451
9885
|
if (arg.startsWith("@")) {
|
|
11452
|
-
const
|
|
11453
|
-
if (!(0,
|
|
11454
|
-
return JSON.parse((0,
|
|
9886
|
+
const path7 = arg.slice(1);
|
|
9887
|
+
if (!(0, import_node_fs17.existsSync)(path7)) throw new Error(`File not found: ${path7}`);
|
|
9888
|
+
return JSON.parse((0, import_node_fs17.readFileSync)(path7, "utf8"));
|
|
11455
9889
|
}
|
|
11456
9890
|
return JSON.parse(arg);
|
|
11457
9891
|
}
|
|
@@ -11462,200 +9896,13 @@ async function resolveDefaultWorkspaceId(cloud) {
|
|
|
11462
9896
|
return def.id;
|
|
11463
9897
|
}
|
|
11464
9898
|
|
|
11465
|
-
// src/cli/commands/reset.ts
|
|
11466
|
-
var import_commander13 = require("commander");
|
|
11467
|
-
var import_node_child_process9 = require("child_process");
|
|
11468
|
-
var import_node_fs22 = require("fs");
|
|
11469
|
-
var import_node_os11 = require("os");
|
|
11470
|
-
var import_node_path17 = require("path");
|
|
11471
|
-
var import_promises4 = require("timers/promises");
|
|
11472
|
-
init_util();
|
|
11473
|
-
init_ui();
|
|
11474
|
-
function buildResetCommand() {
|
|
11475
|
-
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) => {
|
|
11476
|
-
const paths = resolvePaths();
|
|
11477
|
-
const pid = readPidFile(paths);
|
|
11478
|
-
const runningPid = pid && pidAlive2(pid) ? pid : null;
|
|
11479
|
-
const archivePath = opts.wipe ? null : `${paths.root}.reset.${timestamp()}`;
|
|
11480
|
-
const assetSyncRoot = resolveAssetSyncRoot();
|
|
11481
|
-
const assetSyncArchivePath = opts.wipe ? null : `${assetSyncRoot}.reset.${timestamp()}`;
|
|
11482
|
-
const hermesHome = resolveHermesHome();
|
|
11483
|
-
const hermesArchivePath = opts.wipe ? null : `${hermesHome}.reset.${timestamp()}`;
|
|
11484
|
-
const hermesGatewayPids = findHermesGatewayPids2();
|
|
11485
|
-
const plan = {
|
|
11486
|
-
root: paths.root,
|
|
11487
|
-
exists: (0, import_node_fs22.existsSync)(paths.root),
|
|
11488
|
-
runningPid,
|
|
11489
|
-
mode: opts.wipe ? "wipe" : "archive",
|
|
11490
|
-
archivePath,
|
|
11491
|
-
assetSyncRoot,
|
|
11492
|
-
assetSyncExists: (0, import_node_fs22.existsSync)(assetSyncRoot),
|
|
11493
|
-
assetSyncArchivePath,
|
|
11494
|
-
hermesHome,
|
|
11495
|
-
hermesExists: (0, import_node_fs22.existsSync)(hermesHome),
|
|
11496
|
-
hermesArchivePath,
|
|
11497
|
-
hermesGatewayPids,
|
|
11498
|
-
willStopDaemon: runningPid !== null,
|
|
11499
|
-
nextStep: "prismer setup"
|
|
11500
|
-
};
|
|
11501
|
-
if (opts.check) {
|
|
11502
|
-
printPlan(plan, opts.json);
|
|
11503
|
-
return;
|
|
11504
|
-
}
|
|
11505
|
-
if (!opts.yes) {
|
|
11506
|
-
exitWithError("Refusing to reset without --yes. Run `prismer reset --check` to preview.", {
|
|
11507
|
-
code: "confirmation_required"
|
|
11508
|
-
});
|
|
11509
|
-
}
|
|
11510
|
-
if (runningPid !== null) {
|
|
11511
|
-
await stopDaemon(runningPid, opts.timeout ?? 5e3);
|
|
11512
|
-
clearPidFile(paths);
|
|
11513
|
-
} else if (pid) {
|
|
11514
|
-
clearPidFile(paths);
|
|
11515
|
-
}
|
|
11516
|
-
for (const gatewayPid of hermesGatewayPids) {
|
|
11517
|
-
await stopProcess(gatewayPid, opts.timeout ?? 5e3, "Hermes gateway");
|
|
11518
|
-
}
|
|
11519
|
-
if ((0, import_node_fs22.existsSync)(paths.root)) {
|
|
11520
|
-
if (opts.wipe) {
|
|
11521
|
-
(0, import_node_fs22.rmSync)(paths.root, { recursive: true, force: true });
|
|
11522
|
-
} else {
|
|
11523
|
-
if (!archivePath) throw new Error("internal: archivePath missing");
|
|
11524
|
-
if (!(0, import_node_fs22.existsSync)((0, import_node_path17.dirname)(archivePath))) {
|
|
11525
|
-
(0, import_node_fs22.mkdirSync)((0, import_node_path17.dirname)(archivePath), { recursive: true });
|
|
11526
|
-
}
|
|
11527
|
-
(0, import_node_fs22.renameSync)(paths.root, archivePath);
|
|
11528
|
-
}
|
|
11529
|
-
}
|
|
11530
|
-
if ((0, import_node_fs22.existsSync)(assetSyncRoot)) {
|
|
11531
|
-
if (opts.wipe) {
|
|
11532
|
-
(0, import_node_fs22.rmSync)(assetSyncRoot, { recursive: true, force: true });
|
|
11533
|
-
} else {
|
|
11534
|
-
if (!assetSyncArchivePath) throw new Error("internal: assetSyncArchivePath missing");
|
|
11535
|
-
if (!(0, import_node_fs22.existsSync)((0, import_node_path17.dirname)(assetSyncArchivePath))) {
|
|
11536
|
-
(0, import_node_fs22.mkdirSync)((0, import_node_path17.dirname)(assetSyncArchivePath), { recursive: true });
|
|
11537
|
-
}
|
|
11538
|
-
(0, import_node_fs22.renameSync)(assetSyncRoot, assetSyncArchivePath);
|
|
11539
|
-
}
|
|
11540
|
-
}
|
|
11541
|
-
if ((0, import_node_fs22.existsSync)(hermesHome)) {
|
|
11542
|
-
if (opts.wipe) {
|
|
11543
|
-
(0, import_node_fs22.rmSync)(hermesHome, { recursive: true, force: true });
|
|
11544
|
-
} else {
|
|
11545
|
-
if (!hermesArchivePath) throw new Error("internal: hermesArchivePath missing");
|
|
11546
|
-
if (!(0, import_node_fs22.existsSync)((0, import_node_path17.dirname)(hermesArchivePath))) {
|
|
11547
|
-
(0, import_node_fs22.mkdirSync)((0, import_node_path17.dirname)(hermesArchivePath), { recursive: true });
|
|
11548
|
-
}
|
|
11549
|
-
(0, import_node_fs22.renameSync)(hermesHome, hermesArchivePath);
|
|
11550
|
-
}
|
|
11551
|
-
}
|
|
11552
|
-
(0, import_node_fs22.mkdirSync)(paths.root, { recursive: true });
|
|
11553
|
-
if (opts.json) {
|
|
11554
|
-
printJson({ ok: true, reset: true, plan });
|
|
11555
|
-
return;
|
|
11556
|
-
}
|
|
11557
|
-
const ui = getUI();
|
|
11558
|
-
ui.ok("Runtime reset complete", paths.root);
|
|
11559
|
-
if (archivePath) ui.line(` archived: ${archivePath}`);
|
|
11560
|
-
if (assetSyncArchivePath && plan.assetSyncExists) ui.line(` asset sync archived: ${assetSyncArchivePath}`);
|
|
11561
|
-
if (hermesArchivePath && plan.hermesExists) ui.line(` Hermes archived: ${hermesArchivePath}`);
|
|
11562
|
-
if (hermesGatewayPids.length > 0) ui.line(` stopped Hermes gateway pid(s): ${hermesGatewayPids.join(", ")}`);
|
|
11563
|
-
ui.line(" next: prismer setup");
|
|
11564
|
-
}, { code: "reset_failed" }));
|
|
11565
|
-
}
|
|
11566
|
-
function printPlan(plan, json) {
|
|
11567
|
-
if (json) {
|
|
11568
|
-
printJson({ ok: true, check: true, plan });
|
|
11569
|
-
return;
|
|
11570
|
-
}
|
|
11571
|
-
const ui = getUI();
|
|
11572
|
-
ui.header("Runtime reset plan");
|
|
11573
|
-
ui.blank();
|
|
11574
|
-
ui.line(` root: ${plan.root}`);
|
|
11575
|
-
ui.line(` exists: ${plan.exists ? "yes" : "no"}`);
|
|
11576
|
-
ui.line(` daemon: ${plan.runningPid ? `running pid ${plan.runningPid}` : "not running"}`);
|
|
11577
|
-
ui.line(` mode: ${plan.mode}`);
|
|
11578
|
-
if (plan.archivePath) ui.line(` archive: ${plan.archivePath}`);
|
|
11579
|
-
ui.line(` asset sync: ${plan.assetSyncExists ? plan.assetSyncRoot : `${plan.assetSyncRoot} (not found)`}`);
|
|
11580
|
-
if (plan.assetSyncArchivePath) ui.line(` asset archive: ${plan.assetSyncArchivePath}`);
|
|
11581
|
-
ui.line(` Hermes: ${plan.hermesExists ? plan.hermesHome : `${plan.hermesHome} (not found)`}`);
|
|
11582
|
-
if (plan.hermesArchivePath) ui.line(` Hermes archive: ${plan.hermesArchivePath}`);
|
|
11583
|
-
ui.line(
|
|
11584
|
-
` Hermes gateway: ${plan.hermesGatewayPids.length > 0 ? `running pid(s) ${plan.hermesGatewayPids.join(", ")}` : "not running"}`
|
|
11585
|
-
);
|
|
11586
|
-
ui.blank();
|
|
11587
|
-
ui.line("Run `prismer reset --yes` to apply, then `prismer setup` to bind a new account.");
|
|
11588
|
-
}
|
|
11589
|
-
async function stopDaemon(pid, timeoutMs) {
|
|
11590
|
-
await stopProcess(pid, timeoutMs, "daemon");
|
|
11591
|
-
}
|
|
11592
|
-
async function stopProcess(pid, timeoutMs, label) {
|
|
11593
|
-
try {
|
|
11594
|
-
process.kill(pid, "SIGTERM");
|
|
11595
|
-
} catch (err) {
|
|
11596
|
-
throw new Error(`SIGTERM failed for ${label} pid ${pid}: ${err.message}`);
|
|
11597
|
-
}
|
|
11598
|
-
const deadline = Date.now() + timeoutMs;
|
|
11599
|
-
while (Date.now() < deadline) {
|
|
11600
|
-
if (!pidAlive2(pid)) return;
|
|
11601
|
-
await (0, import_promises4.setTimeout)(100);
|
|
11602
|
-
}
|
|
11603
|
-
try {
|
|
11604
|
-
process.kill(pid, "SIGKILL");
|
|
11605
|
-
} catch (err) {
|
|
11606
|
-
throw new Error(`SIGKILL failed for ${label} pid ${pid}: ${err.message}`);
|
|
11607
|
-
}
|
|
11608
|
-
const killDeadline = Date.now() + 1e3;
|
|
11609
|
-
while (Date.now() < killDeadline) {
|
|
11610
|
-
if (!pidAlive2(pid)) return;
|
|
11611
|
-
await (0, import_promises4.setTimeout)(100);
|
|
11612
|
-
}
|
|
11613
|
-
throw new Error(`${label} pid ${pid} did not exit within ${timeoutMs + 1e3}ms`);
|
|
11614
|
-
}
|
|
11615
|
-
function timestamp() {
|
|
11616
|
-
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
11617
|
-
}
|
|
11618
|
-
function resolveAssetSyncRoot() {
|
|
11619
|
-
const override = process.env.PRISMER_ASSET_SYNC_ROOT;
|
|
11620
|
-
if (override) return override;
|
|
11621
|
-
const home = (0, import_node_os11.homedir)();
|
|
11622
|
-
const desktop = (0, import_node_path17.join)(home, "Desktop");
|
|
11623
|
-
const base = (0, import_node_fs22.existsSync)(desktop) ? desktop : home;
|
|
11624
|
-
return (0, import_node_path17.join)(base, "Prismer Assets");
|
|
11625
|
-
}
|
|
11626
|
-
function resolveHermesHome() {
|
|
11627
|
-
return process.env.HERMES_HOME || (0, import_node_path17.join)((0, import_node_os11.homedir)(), ".hermes");
|
|
11628
|
-
}
|
|
11629
|
-
function findHermesGatewayPids2() {
|
|
11630
|
-
try {
|
|
11631
|
-
const out = (0, import_node_child_process9.execFileSync)("ps", ["-axo", "pid=,command="], { encoding: "utf8" });
|
|
11632
|
-
const current = process.pid;
|
|
11633
|
-
return out.split(/\r?\n/).map((line) => {
|
|
11634
|
-
const trimmed = line.trim();
|
|
11635
|
-
const match = /^(\d+)\s+(.+)$/.exec(trimmed);
|
|
11636
|
-
if (!match) return null;
|
|
11637
|
-
const pid = Number.parseInt(match[1], 10);
|
|
11638
|
-
const command = match[2];
|
|
11639
|
-
if (!Number.isFinite(pid) || pid === current) return null;
|
|
11640
|
-
if (!isHermesGatewayCommand2(command)) return null;
|
|
11641
|
-
return pid;
|
|
11642
|
-
}).filter((pid) => pid !== null);
|
|
11643
|
-
} catch {
|
|
11644
|
-
return [];
|
|
11645
|
-
}
|
|
11646
|
-
}
|
|
11647
|
-
function isHermesGatewayCommand2(command) {
|
|
11648
|
-
if (!/\bgateway\b/.test(command) || !/\brun\b/.test(command)) return false;
|
|
11649
|
-
return /(^|\s)(?:\S*\/)?hermes(?:\s|$)/.test(command);
|
|
11650
|
-
}
|
|
11651
|
-
|
|
11652
9899
|
// src/cli/commands/sandbox.ts
|
|
11653
|
-
var
|
|
9900
|
+
var import_commander13 = require("commander");
|
|
11654
9901
|
init_util();
|
|
11655
9902
|
init_ui();
|
|
11656
9903
|
function buildSandboxCommand() {
|
|
11657
|
-
const cmd = new
|
|
11658
|
-
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",
|
|
9904
|
+
const cmd = new import_commander13.Command("sandbox").description("Inspect and smoke-test sandbox lifecycle");
|
|
9905
|
+
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) => {
|
|
11659
9906
|
const query = new URLSearchParams({ workspaceId: opts.workspaceId, limit: String(opts.limit) });
|
|
11660
9907
|
if (opts.status) query.set("status", opts.status);
|
|
11661
9908
|
const body = await cloudRequest("GET", `/api/sandboxes?${query.toString()}`);
|
|
@@ -11714,7 +9961,7 @@ function buildSandboxCommand() {
|
|
|
11714
9961
|
}
|
|
11715
9962
|
printSnapshotResult(id, body);
|
|
11716
9963
|
}, { code: "sandbox_snapshot_failed" }));
|
|
11717
|
-
cmd.command("runCmd <id> <command...>").description("Run a command in a sandbox container").option("--timeout-ms <ms>", "Command timeout",
|
|
9964
|
+
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) => {
|
|
11718
9965
|
if (!Array.isArray(command) || command.length === 0) exitWithError("runCmd requires a command after --", { code: "invalid_argument" });
|
|
11719
9966
|
const body = await cloudRequest("POST", `/api/sandboxes/${encodeURIComponent(id)}/runCmd`, {
|
|
11720
9967
|
command,
|
|
@@ -11755,12 +10002,12 @@ function errorMessage2(raw) {
|
|
|
11755
10002
|
if (typeof raw.error === "string") return raw.error;
|
|
11756
10003
|
return raw.error?.message ?? "request failed";
|
|
11757
10004
|
}
|
|
11758
|
-
async function cloudRequest(method,
|
|
10005
|
+
async function cloudRequest(method, path7, body) {
|
|
11759
10006
|
const cloud = mkCloud5();
|
|
11760
|
-
const res = await cloud.request(method,
|
|
11761
|
-
if (!res.ok) exitWithError(`${method} ${
|
|
10007
|
+
const res = await cloud.request(method, path7, body === void 0 ? void 0 : { body });
|
|
10008
|
+
if (!res.ok) exitWithError(`${method} ${path7} failed (${res.status}): ${res.error?.message ?? "request failed"}`);
|
|
11762
10009
|
const responseBody = res.data;
|
|
11763
|
-
if (isErrorEnvelope2(responseBody)) exitWithError(`${method} ${
|
|
10010
|
+
if (isErrorEnvelope2(responseBody)) exitWithError(`${method} ${path7} failed (${res.status}): ${errorMessage2(responseBody)}`);
|
|
11764
10011
|
return responseBody;
|
|
11765
10012
|
}
|
|
11766
10013
|
function printSandboxList(raw) {
|
|
@@ -11835,7 +10082,7 @@ function writeLine2(label, value) {
|
|
|
11835
10082
|
if (value === void 0 || value === null || value === "") return;
|
|
11836
10083
|
getUI().line(` ${label}: ${String(value)}`);
|
|
11837
10084
|
}
|
|
11838
|
-
function
|
|
10085
|
+
function parsePositiveInt5(value) {
|
|
11839
10086
|
const parsed = Number.parseInt(value, 10);
|
|
11840
10087
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
11841
10088
|
throw new Error("expected a positive integer");
|
|
@@ -11853,16 +10100,16 @@ async function safeParseResponse(res) {
|
|
|
11853
10100
|
}
|
|
11854
10101
|
|
|
11855
10102
|
// src/cli/commands/setup.ts
|
|
11856
|
-
var
|
|
11857
|
-
var
|
|
11858
|
-
var
|
|
11859
|
-
var
|
|
11860
|
-
var
|
|
10103
|
+
var import_commander14 = require("commander");
|
|
10104
|
+
var import_node_os9 = require("os");
|
|
10105
|
+
var import_node_child_process9 = require("child_process");
|
|
10106
|
+
var import_node_crypto6 = require("crypto");
|
|
10107
|
+
var import_node_fs18 = require("fs");
|
|
11861
10108
|
var import_node_http2 = require("http");
|
|
11862
10109
|
init_util();
|
|
11863
10110
|
init_ui();
|
|
11864
10111
|
function buildSetupCommand() {
|
|
11865
|
-
return new
|
|
10112
|
+
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(
|
|
11866
10113
|
"--cloud <url>",
|
|
11867
10114
|
`Cloud base URL (default: ${DEFAULT_CLOUD_BASE_URL}; bare host:port is auto-prefixed http://)`,
|
|
11868
10115
|
(v) => v
|
|
@@ -11875,7 +10122,7 @@ function buildSetupCommand() {
|
|
|
11875
10122
|
} catch (err) {
|
|
11876
10123
|
exitWithError(err.message, { code: "invalid_cloud_url" });
|
|
11877
10124
|
}
|
|
11878
|
-
let apiKey = apiKeyArg ??
|
|
10125
|
+
let apiKey = apiKeyArg ?? process.env.PRISMER_API_KEY;
|
|
11879
10126
|
const authToken = opts.token ?? process.env.PRISMER_AUTH_TOKEN;
|
|
11880
10127
|
if (!opts.json) {
|
|
11881
10128
|
printBanner({ compact: true });
|
|
@@ -11883,6 +10130,7 @@ function buildSetupCommand() {
|
|
|
11883
10130
|
getUI().blank();
|
|
11884
10131
|
}
|
|
11885
10132
|
const shouldStart = opts.start !== false;
|
|
10133
|
+
stopRunningDaemon(paths);
|
|
11886
10134
|
if (opts.pair || opts.asUser) {
|
|
11887
10135
|
if (!opts.json) warn("Legacy pair setup path", "plain `prismer setup --start` is the canonical runtime binding flow");
|
|
11888
10136
|
if (opts.asUser && process.env.LOCAL_ONLY !== "1") {
|
|
@@ -11899,7 +10147,7 @@ function buildSetupCommand() {
|
|
|
11899
10147
|
}
|
|
11900
10148
|
const result = await pair({
|
|
11901
10149
|
cloudBaseUrl,
|
|
11902
|
-
deviceName: opts.deviceName ?? (0,
|
|
10150
|
+
deviceName: opts.deviceName ?? (0, import_node_os9.hostname)(),
|
|
11903
10151
|
force: opts.force,
|
|
11904
10152
|
paths,
|
|
11905
10153
|
asUserEmail: opts.asUser
|
|
@@ -11916,13 +10164,13 @@ function buildSetupCommand() {
|
|
|
11916
10164
|
apiKey = await mintDaemonApiKey({
|
|
11917
10165
|
cloudBaseUrl,
|
|
11918
10166
|
token: authToken,
|
|
11919
|
-
deviceName: opts.deviceName ?? (0,
|
|
10167
|
+
deviceName: opts.deviceName ?? (0, import_node_os9.hostname)()
|
|
11920
10168
|
});
|
|
11921
10169
|
}
|
|
11922
10170
|
if (!apiKey && !authToken && !opts.check && opts.browser !== false) {
|
|
11923
10171
|
apiKey = await runBrowserSetup({
|
|
11924
10172
|
cloudBaseUrl,
|
|
11925
|
-
deviceName: opts.deviceName ?? (0,
|
|
10173
|
+
deviceName: opts.deviceName ?? (0, import_node_os9.hostname)(),
|
|
11926
10174
|
json: Boolean(opts.json)
|
|
11927
10175
|
});
|
|
11928
10176
|
}
|
|
@@ -11973,11 +10221,10 @@ function buildSetupCommand() {
|
|
|
11973
10221
|
return;
|
|
11974
10222
|
}
|
|
11975
10223
|
const previousConfig = configExists(paths) ? loadConfig(paths) : null;
|
|
11976
|
-
const daemonId = previousConfig && previousConfig.api_key === apiKey && previousConfig.cloud_api_base === cloudBaseUrl ? previousConfig.daemon_id : newDaemonId();
|
|
11977
10224
|
const config = {
|
|
11978
10225
|
api_key: apiKey,
|
|
11979
10226
|
cloud_api_base: cloudBaseUrl,
|
|
11980
|
-
daemon_id:
|
|
10227
|
+
daemon_id: newDaemonId()
|
|
11981
10228
|
};
|
|
11982
10229
|
if (previousConfig && shouldArchiveLocalDb(previousConfig, config)) {
|
|
11983
10230
|
archiveLocalDb(paths.localDb);
|
|
@@ -12007,7 +10254,7 @@ function buildSetupCommand() {
|
|
|
12007
10254
|
}, { code: "setup_failed" }));
|
|
12008
10255
|
}
|
|
12009
10256
|
async function runBrowserSetup(input) {
|
|
12010
|
-
const state = (0,
|
|
10257
|
+
const state = (0, import_node_crypto6.randomBytes)(18).toString("base64url");
|
|
12011
10258
|
const server = (0, import_node_http2.createServer)();
|
|
12012
10259
|
const apiKeyPromise = waitForSetupCallback(server, state);
|
|
12013
10260
|
await new Promise((resolve3, reject) => {
|
|
@@ -12070,7 +10317,7 @@ function waitForSetupCallback(server, expectedState) {
|
|
|
12070
10317
|
function openBrowser(url) {
|
|
12071
10318
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
12072
10319
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
12073
|
-
const child = (0,
|
|
10320
|
+
const child = (0, import_node_child_process9.spawn)(command, args, { stdio: "ignore", detached: true });
|
|
12074
10321
|
child.on("error", () => {
|
|
12075
10322
|
});
|
|
12076
10323
|
child.unref();
|
|
@@ -12097,20 +10344,42 @@ function withTimeout(promise, ms, message) {
|
|
|
12097
10344
|
});
|
|
12098
10345
|
}
|
|
12099
10346
|
function startDaemonDetached(home) {
|
|
12100
|
-
const child = (0,
|
|
10347
|
+
const child = (0, import_node_child_process9.spawn)(process.execPath, [process.argv[1], "daemon", "start"], {
|
|
12101
10348
|
env: { ...process.env, PRISMER_HOME: home },
|
|
12102
10349
|
stdio: "ignore",
|
|
12103
10350
|
detached: true
|
|
12104
10351
|
});
|
|
12105
10352
|
child.unref();
|
|
12106
10353
|
}
|
|
10354
|
+
function stopRunningDaemon(paths) {
|
|
10355
|
+
const pid = readPidFile(paths);
|
|
10356
|
+
if (!pid || !pidAlive(pid)) {
|
|
10357
|
+
if (pid) clearPidFile(paths);
|
|
10358
|
+
return;
|
|
10359
|
+
}
|
|
10360
|
+
try {
|
|
10361
|
+
process.kill(pid, "SIGTERM");
|
|
10362
|
+
} catch {
|
|
10363
|
+
return;
|
|
10364
|
+
}
|
|
10365
|
+
const deadline = Date.now() + 5e3;
|
|
10366
|
+
while (Date.now() < deadline) {
|
|
10367
|
+
if (!pidAlive(pid)) {
|
|
10368
|
+
clearPidFile(paths);
|
|
10369
|
+
return;
|
|
10370
|
+
}
|
|
10371
|
+
const start = Date.now();
|
|
10372
|
+
while (Date.now() - start < 200) {
|
|
10373
|
+
}
|
|
10374
|
+
}
|
|
10375
|
+
}
|
|
12107
10376
|
function shouldArchiveLocalDb(previous, next) {
|
|
12108
10377
|
return previous.api_key !== next.api_key || previous.cloud_api_base !== next.cloud_api_base || previous.daemon_id !== next.daemon_id;
|
|
12109
10378
|
}
|
|
12110
10379
|
function archiveLocalDb(localDbPath) {
|
|
12111
|
-
if (!(0,
|
|
10380
|
+
if (!(0, import_node_fs18.existsSync)(localDbPath)) return;
|
|
12112
10381
|
const archived = `${localDbPath}.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
12113
|
-
(0,
|
|
10382
|
+
(0, import_node_fs18.renameSync)(localDbPath, archived);
|
|
12114
10383
|
}
|
|
12115
10384
|
async function mintDaemonApiKey(input) {
|
|
12116
10385
|
const label = `Daemon: ${input.deviceName} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
@@ -12130,12 +10399,11 @@ async function mintDaemonApiKey(input) {
|
|
|
12130
10399
|
}
|
|
12131
10400
|
|
|
12132
10401
|
// src/cli/commands/status.ts
|
|
12133
|
-
var
|
|
12134
|
-
var import_node_os13 = require("os");
|
|
10402
|
+
var import_commander15 = require("commander");
|
|
12135
10403
|
init_util();
|
|
12136
10404
|
init_ui();
|
|
12137
10405
|
function buildStatusCommand() {
|
|
12138
|
-
return new
|
|
10406
|
+
return new import_commander15.Command("status").description("Show daemon + config + cloud status").option("--json", "Output machine-readable JSON").action(async (opts) => {
|
|
12139
10407
|
const paths = resolvePaths();
|
|
12140
10408
|
if (!configExists(paths)) {
|
|
12141
10409
|
const report2 = { configured: false, paired: false, paths: { config: paths.configFile } };
|
|
@@ -12203,7 +10471,11 @@ function buildStatusCommand() {
|
|
|
12203
10471
|
info: daemonStatus.info ?? {}
|
|
12204
10472
|
},
|
|
12205
10473
|
cloud: { base: cfg.cloud_api_base, reachable: cloudOk, me, devices, agents },
|
|
12206
|
-
local
|
|
10474
|
+
local,
|
|
10475
|
+
binding: {
|
|
10476
|
+
daemonId: cfg.daemon_id,
|
|
10477
|
+
apiKey: cfg.api_key
|
|
10478
|
+
}
|
|
12207
10479
|
};
|
|
12208
10480
|
if (opts.json) {
|
|
12209
10481
|
printJson(report);
|
|
@@ -12226,8 +10498,8 @@ async function readDaemonStatus() {
|
|
|
12226
10498
|
const paths = resolvePaths();
|
|
12227
10499
|
const pid = readPidFile(paths);
|
|
12228
10500
|
if (pid) {
|
|
12229
|
-
const { pidAlive:
|
|
12230
|
-
if (
|
|
10501
|
+
const { pidAlive: pidAlive2 } = await Promise.resolve().then(() => (init_util(), util_exports));
|
|
10502
|
+
if (pidAlive2(pid)) return { running: true, pid };
|
|
12231
10503
|
}
|
|
12232
10504
|
return { running: false };
|
|
12233
10505
|
}
|
|
@@ -12255,9 +10527,8 @@ function printPretty(report) {
|
|
|
12255
10527
|
ui.blank();
|
|
12256
10528
|
ok("Config", report.paths.config);
|
|
12257
10529
|
if (report.daemon.running) {
|
|
12258
|
-
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)();
|
|
12259
10530
|
const ws = report.daemon.wsConnected ? "connected" : "pending";
|
|
12260
|
-
ok("Daemon",
|
|
10531
|
+
ok("Daemon", `pid=${report.daemon.pid} ws=${ws}`);
|
|
12261
10532
|
if (report.daemon.info) {
|
|
12262
10533
|
const info2 = report.daemon.info;
|
|
12263
10534
|
if (info2.version) ui.line(` Version: ${info2.version}`);
|
|
@@ -12272,7 +10543,8 @@ function printPretty(report) {
|
|
|
12272
10543
|
ok("Cloud", report.cloud.base);
|
|
12273
10544
|
const me = report.cloud.me;
|
|
12274
10545
|
if (me?.user) {
|
|
12275
|
-
|
|
10546
|
+
const roleTag = me.user.role ? ` role=${me.user.role}` : "";
|
|
10547
|
+
ui.line(` Account: ${me.user.displayName ?? me.user.username ?? "?"}${roleTag}`);
|
|
12276
10548
|
}
|
|
12277
10549
|
if (me?.credits) {
|
|
12278
10550
|
ui.line(` Credits: ${typeof me.credits.balance === "number" ? me.credits.balance.toLocaleString() : "?"}`);
|
|
@@ -12281,6 +10553,12 @@ function printPretty(report) {
|
|
|
12281
10553
|
fail("Cloud", `${report.cloud.base} unreachable or unauthorized`);
|
|
12282
10554
|
tip("prismer setup --force");
|
|
12283
10555
|
}
|
|
10556
|
+
if (report.binding) {
|
|
10557
|
+
ui.blank();
|
|
10558
|
+
ui.line(` Daemon ID: ${report.binding.daemonId}`);
|
|
10559
|
+
const masked = report.binding.apiKey.slice(0, 14) + "\u2022\u2022\u2022\u2022" + report.binding.apiKey.slice(-4);
|
|
10560
|
+
ui.line(` API Key: ${masked}`);
|
|
10561
|
+
}
|
|
12284
10562
|
if (Array.isArray(report.cloud.devices) && report.cloud.devices.length > 0) {
|
|
12285
10563
|
const devs = report.cloud.devices;
|
|
12286
10564
|
ui.blank();
|
|
@@ -12291,6 +10569,8 @@ function printPretty(report) {
|
|
|
12291
10569
|
const declared = d.hostedAgentSummary?.declared ?? 0;
|
|
12292
10570
|
ui.line(` ${statusIcon} ${d.podName?.replace(/^daemon:/, "")?.slice(0, 28) ?? "?"} kind=${kind} agents=${declared}`);
|
|
12293
10571
|
}
|
|
10572
|
+
} else {
|
|
10573
|
+
ui.line(` Workspace Devices: none`);
|
|
12294
10574
|
}
|
|
12295
10575
|
if (Array.isArray(report.cloud.agents) && report.cloud.agents.length > 0) {
|
|
12296
10576
|
ui.line(` Hosted agents: ${report.cloud.agents.length}`);
|
|
@@ -12306,14 +10586,14 @@ function printPretty(report) {
|
|
|
12306
10586
|
}
|
|
12307
10587
|
|
|
12308
10588
|
// src/cli/commands/task.ts
|
|
12309
|
-
var
|
|
12310
|
-
var
|
|
10589
|
+
var import_commander16 = require("commander");
|
|
10590
|
+
var import_promises3 = require("timers/promises");
|
|
12311
10591
|
init_util();
|
|
12312
10592
|
function describeStatus(status) {
|
|
12313
10593
|
return status === 0 ? "network error" : `HTTP ${status}`;
|
|
12314
10594
|
}
|
|
12315
10595
|
function buildTaskCommand() {
|
|
12316
|
-
const cmd = new
|
|
10596
|
+
const cmd = new import_commander16.Command("task").description("Manage tasks");
|
|
12317
10597
|
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) => {
|
|
12318
10598
|
const cloud = mkCloud6();
|
|
12319
10599
|
const body = {
|
|
@@ -12390,7 +10670,7 @@ function unwrap2(raw) {
|
|
|
12390
10670
|
async function pollUntilTerminal(cloud, taskId, timeoutMs) {
|
|
12391
10671
|
const deadline = Date.now() + (timeoutMs ?? 5 * 6e4);
|
|
12392
10672
|
while (Date.now() < deadline) {
|
|
12393
|
-
await (0,
|
|
10673
|
+
await (0, import_promises3.setTimeout)(1e3);
|
|
12394
10674
|
const res = await cloud.request(
|
|
12395
10675
|
"GET",
|
|
12396
10676
|
`/api/im/tasks/${encodeURIComponent(taskId)}`
|
|
@@ -12427,11 +10707,11 @@ function taskFrom2(raw) {
|
|
|
12427
10707
|
}
|
|
12428
10708
|
|
|
12429
10709
|
// src/cli/commands/workspace.ts
|
|
12430
|
-
var
|
|
10710
|
+
var import_commander17 = require("commander");
|
|
12431
10711
|
init_util();
|
|
12432
10712
|
init_ui();
|
|
12433
10713
|
function buildWorkspaceCommand() {
|
|
12434
|
-
const cmd = new
|
|
10714
|
+
const cmd = new import_commander17.Command("workspace").description("Manage workspaces, runtime snapshots, and workspace files");
|
|
12435
10715
|
cmd.command("list").description("List workspaces").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
|
|
12436
10716
|
const cloud = mkCloud7();
|
|
12437
10717
|
const data = await cloud.get("/api/im/workspaces");
|
|
@@ -12665,15 +10945,18 @@ async function readResponseError(res) {
|
|
|
12665
10945
|
|
|
12666
10946
|
// src/cli/index.ts
|
|
12667
10947
|
init_ui();
|
|
12668
|
-
|
|
10948
|
+
|
|
10949
|
+
// package.json
|
|
10950
|
+
var version = "1.9.22";
|
|
10951
|
+
|
|
10952
|
+
// src/cli/index.ts
|
|
12669
10953
|
function buildProgram() {
|
|
12670
|
-
const program = new
|
|
10954
|
+
const program = new import_commander18.Command("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(version);
|
|
12671
10955
|
program.addCommand(buildBannerCommand());
|
|
12672
10956
|
program.addCommand(buildSetupCommand());
|
|
12673
10957
|
program.addCommand(buildConfigCommand());
|
|
12674
10958
|
program.addCommand(buildDaemonCommand());
|
|
12675
10959
|
program.addCommand(buildPairCommand());
|
|
12676
|
-
program.addCommand(buildResetCommand());
|
|
12677
10960
|
program.addCommand(buildStatusCommand());
|
|
12678
10961
|
program.addCommand(buildAdapterCommand());
|
|
12679
10962
|
program.addCommand(buildAgentCommand());
|