@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 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, import_node_url2.fileURLToPath)(import_meta2.url));
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, import_node_url2, import_meta2, BRAILLE_FRAMES, COMPACT_BANNER, UI, _ui;
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
- import_node_url2 = require("url");
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: () => pidAlive2,
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 v1.9.7");
547
+ ui.smallHeader("Runtime CLI v${VERSION}");
548
548
  return;
549
549
  }
550
- ui.banner("Runtime CLI v1.9.7", { full: true });
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, import_node_path4.join)(paths.root, "daemon.pid");
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 pidAlive2(pid) {
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, import_node_path4, DEFAULT_CLOUD_BASE_URL, ANSI;
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
- import_node_path4 = require("path");
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 import_commander19 = require("commander");
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 import_node_os4 = require("os");
633
- var import_node_path5 = require("path");
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 = getHermesProfileName(profile);
1367
- const portOverride = config.port !== 8642 ? config.port : portForProfile(`${profileName}:${config.apiKey}`, 8642);
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, profile.workspaceId);
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: instructions || void 0,
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, workspaceId) {
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 req = (0, import_node_module.createRequire)(import_meta.url);
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 here = (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
1843
- const candidate = (0, import_node_path.join)(here, "../../mcp/dist/index.js");
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 = (0, import_node_path.join)(here, "../../../mcp/dist/index.js");
1779
+ const candidate2 = join14(here, "../../../mcp/dist/index.js");
1846
1780
  if ((0, import_node_fs.existsSync)(candidate2)) return candidate2;
1847
- const candidate3 = (0, import_node_path.join)(here, "../../../../mcp/dist/index.js");
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(path9) {
2017
- if (!(0, import_node_fs.existsSync)(path9)) return {};
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)(path9, "utf8")) ?? {};
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 ${path9}: ${err.message}`);
1955
+ throw new Error(`Failed to parse Hermes config at ${path7}: ${err.message}`);
2022
1956
  }
2023
1957
  }
2024
- function writeEnvValue(path9, key, value) {
2025
- (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path9), { recursive: true });
2026
- const lines = (0, import_node_fs.existsSync)(path9) ? (0, import_node_fs.readFileSync)(path9, "utf8").split(/\r?\n/) : [];
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)(path9, `${next.join("\n")}
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, resolveOpenClawSkillsDir(profile, config));
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, skillsDir) {
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 import_node_os3 = require("os");
2388
- var import_node_path3 = require("path");
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, import_node_path3.join)((0, import_node_os3.homedir)(), ".prismer");
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, import_node_path3.join)(root, "config.toml"),
2422
- localDb: (0, import_node_path3.join)(root, "local.db"),
2423
- cacheDir: (0, import_node_path3.join)(root, "cache"),
2424
- logsDir: (0, import_node_path3.join)(root, "logs"),
2425
- runsDir: (0, import_node_path3.join)(root, "runs")
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, import_node_path3.dirname)(paths.configFile))) {
2456
- (0, import_node_fs2.mkdirSync)((0, import_node_path3.dirname)(paths.configFile), { recursive: true });
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, import_node_path5.join)((0, import_node_os4.homedir)(), ".claude", "hooks.json"), kind: "json-file" }
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, import_node_path5.join)((0, import_node_os4.homedir)(), ".openclaw", "hooks"), kind: "directory" }
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, import_node_path5.join)((0, import_node_os4.homedir)(), ".codex", "hooks.json"), kind: "json-file" }
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, import_node_path5.join)((0, import_node_os4.homedir)(), ".hermes", "hooks.json"), kind: "json-file" }
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, version, dryRun) {
2596
- const { argv0, args } = installCommand(spec, version);
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, import_node_path5.join)(planned.path, "prismer.json");
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, import_node_path5.dirname)(planned.path), { recursive: true });
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, version) {
2741
- const ref = version === "latest" ? spec.package : `${spec.package}@${version}`;
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 = version === "latest" ? spec.package : `${spec.package}==${version}`;
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 = version === "latest" ? spec.package : `${spec.package}==${version}`;
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, import_node_path5.join)((0, import_node_os4.homedir)(), ".hermes", ".env");
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, import_node_path5.join)((0, import_node_os4.homedir)(), ".openclaw", "openclaw.json");
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, import_node_path5.join)((0, import_node_os4.homedir)(), ".openclaw", "hooks.json");
2829
- const markerPath = (0, import_node_path5.join)(target.path, "prismer.json");
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, import_node_path5.join)(target.path, "prismer.json");
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(path9, spec) {
2726
+ function mergeHookJson(path7, spec) {
2888
2727
  const marker = prismerHookMarker(spec);
2889
- if (!(0, import_node_fs4.existsSync)(path9)) {
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)(path9, "utf8"));
2735
+ parsed = JSON.parse((0, import_node_fs4.readFileSync)(path7, "utf8"));
2897
2736
  } catch (err) {
2898
- throw new Error(`Cannot parse ${path9} as JSON: ${err.message}`);
2737
+ throw new Error(`Cannot parse ${path7} as JSON: ${err.message}`);
2899
2738
  }
2900
2739
  if (!isRecord(parsed)) {
2901
- throw new Error(`Cannot merge ${path9}: expected a JSON object`);
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(path9) {
2920
- if (!(0, import_node_fs4.existsSync)(path9)) return null;
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 = `${path9}.bak.${suffix}`;
2923
- const stat = (0, import_node_fs4.statSync)(path9);
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)(path9, backup);
2764
+ (0, import_node_fs4.copyFileSync)(path7, backup);
2926
2765
  return backup;
2927
2766
  }
2928
- function fileContainsPrismerMarker(path9) {
2767
+ function fileContainsPrismerMarker(path7) {
2929
2768
  try {
2930
- return (0, import_node_fs4.readFileSync)(path9, "utf8").includes("prismer-daemon-runtime");
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, version) {
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, path9, init) {
3004
- const url = this.urlFor(path9);
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(path9, opts) {
3064
- const res = await this.request("GET", path9, opts);
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(path9) {
2912
+ urlFor(path7) {
3074
2913
  const trimmedBase = this.opts.baseUrl.replace(/\/$/, "");
3075
- const trimmedPath = path9.startsWith("/") ? path9 : `/${path9}`;
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(path9, init) {
3080
- const url = this.urlFor(path9);
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 import_node_path6 = require("path");
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(path9) {
3253
- if (path9 !== ":memory:" && !(0, import_node_fs5.existsSync)((0, import_node_path6.dirname)(path9))) {
3254
- (0, import_node_fs5.mkdirSync)((0, import_node_path6.dirname)(path9), { recursive: true });
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(path9);
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 || !pidAlive2(pid)) return { ok: false, error: "daemon process is not running", agents: [] };
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 import_node_path7 = require("path");
3667
- function rowToCached(row) {
3668
- return {
3669
- contentHash: row.content_hash,
3670
- sizeBytes: row.size_bytes,
3671
- mime: row.mime,
3672
- localPath: row.local_path,
3673
- fetchedAt: row.fetched_at,
3674
- lastUsedAt: row.last_used_at,
3675
- pin: row.pin === 1
3676
- };
3677
- }
3678
- var AssetCache = class {
3679
- db;
3680
- cloud;
3681
- cacheDir;
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
- /** Local file path for a content hash. Files split by first 2 chars for fs scaling. */
3693
- pathFor(hash) {
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
- this.touch(hash);
3704
- return rowToCached({ ...row, last_used_at: Date.now() });
3705
- }
3706
- /** Bump `last_used_at` (call when an asset is consulted). */
3707
- touch(hash) {
3708
- this.db.prepare("UPDATE cached_assets SET last_used_at = ? WHERE content_hash = ?").run(Date.now(), hash);
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
- const res = await this.cloud.fetchRaw(`/api/im/assets/${encodeURIComponent(assetId)}`, {
3752
- signal: opts?.signal
3753
- });
3754
- if (!res.ok) {
3755
- throw new Error(`Asset bytes fetch failed for ${hash} (id=${assetId}): HTTP ${res.status}`);
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
- const buf = Buffer.from(await res.arrayBuffer());
3758
- const actualHash = sha256(buf);
3759
- if (actualHash !== hash) {
3760
- throw new Error(`Asset hash mismatch for ${hash}: downloaded ${actualHash}`);
3543
+ if (opts.json) {
3544
+ printJson(body ?? null);
3545
+ return;
3761
3546
  }
3762
- const localPath = this.pathFor(hash);
3763
- if (!(0, import_node_fs6.existsSync)((0, import_node_path7.dirname)(localPath))) {
3764
- (0, import_node_fs6.mkdirSync)((0, import_node_path7.dirname)(localPath), { recursive: true });
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
- const tmpPath = `${localPath}.${process.pid}.${Date.now()}.tmp`;
3767
- (0, import_node_fs6.writeFileSync)(tmpPath, buf);
3768
- (0, import_node_fs6.renameSync)(tmpPath, localPath);
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
- const size = (0, import_node_fs6.statSync)(localPath).size;
3794
- const now = Date.now();
3795
- this.db.prepare(
3796
- `INSERT OR REPLACE INTO cached_assets
3797
- (content_hash, size_bytes, mime, local_path, fetched_at, last_used_at, pin)
3798
- VALUES (?, ?, ?, ?, ?, ?, 1)`
3799
- ).run(hash, size, mime ?? null, localPath, now, now);
3800
- return { contentHash: hash, sizeBytes: size, mime: mime ?? null, localPath, fetchedAt: now, lastUsedAt: now, pin: true };
3801
- }
3802
- /** Total bytes across all rows (incl. pinned). */
3803
- totalBytes() {
3804
- const row = this.db.prepare("SELECT COALESCE(SUM(size_bytes),0) AS total FROM cached_assets").get();
3805
- return row.total;
3806
- }
3807
- /** Run LRU eviction until under cap. Pinned rows are never evicted. */
3808
- evictIfOver() {
3809
- let total = this.totalBytes();
3810
- if (total <= this.maxBytes) return { removed: 0, freed: 0 };
3811
- const candidates = this.db.prepare(
3812
- `SELECT content_hash, size_bytes, local_path FROM cached_assets
3813
- WHERE pin = 0
3814
- ORDER BY last_used_at ASC`
3815
- ).all();
3816
- let freed = 0;
3817
- let removed = 0;
3818
- for (const row of candidates) {
3819
- if (total <= this.maxBytes) break;
3820
- try {
3821
- if ((0, import_node_fs6.existsSync)(row.local_path)) (0, import_node_fs6.unlinkSync)(row.local_path);
3822
- } catch {
3823
- }
3824
- this.db.prepare("DELETE FROM cached_assets WHERE content_hash = ?").run(row.content_hash);
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, import_node_fs9.writeFileSync)(outPath, bytes);
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", parsePositiveInt2).option("--before <id>", "Return messages before this message id").option("--json", "Print raw JSON response").action(async (groupId, opts) => {
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", parsePositiveInt2).option("--before <id>", "Return messages before this message id").option("--json", "Print raw JSON response").action(async (conversationId, opts) => {
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, path9, body) {
3805
+ async function requestOrExit(method, path7, body) {
4587
3806
  let res;
4588
3807
  try {
4589
- res = await mkCloud2().request(method, path9, body === void 0 ? void 0 : { body });
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} ${path9} failed (${res.status}): ${sanitizeError(res.error?.message ?? "unknown")}`);
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 parsePositiveInt2(value) {
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(path9, params) {
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 ? `${path9}?${suffix}` : path9;
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", parsePositiveInt3, 6e4).option("--strict", "Treat skipped optional checks as failure").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
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, path9, body, client = cloud) {
4809
- return requestCheck(client, suite, name, method, path9, body);
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, path9, body) {
4102
+ async function requestCheck(cloud, suite, name, method, path7, body) {
4884
4103
  let res;
4885
4104
  try {
4886
- res = await cloud.request(method, path9, body === void 0 ? void 0 : { body });
4105
+ res = await cloud.request(method, path7, body === void 0 ? void 0 : { body });
4887
4106
  } catch (err) {
4888
- return { suite, name, status: "fail", endpoint: path9, detail: sanitize(err.message) };
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: path9,
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: path9, detail: sanitize(apiErrorMessage(res.data)), data: res.data };
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: path9, detail: summarizeData(data), data };
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 parsePositiveInt3(value) {
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 import_node_fs18 = require("fs");
5022
- var import_promises2 = require("timers/promises");
5023
- var import_node_path14 = require("path");
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 import_node_fs17 = require("fs");
5028
- var import_promises = require("fs/promises");
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
- size() {
5061
- return this.adapters.size;
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
- * Find adapters that satisfy a capability tag.
5065
- *
5066
- * Match rules (deterministic, sorted by name):
5067
- * - exact: adapter declared the tag verbatim
5068
- * - wildcard: adapter declared `code.*` → matches `code.write`
5069
- */
5070
- findByCapability(capability) {
5071
- const matches2 = [];
5072
- for (const adapter of this.adapters.values()) {
5073
- if (adapter.capabilities.includes(capability)) {
5074
- matches2.push(adapter);
5075
- continue;
5076
- }
5077
- if (adapter.capabilities.some(
5078
- (tag) => tag.endsWith(".*") && capability.startsWith(tag.slice(0, -1))
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
- matches2.sort((a, b) => a.name.localeCompare(b.name));
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, path9, signal) {
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, path9);
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(path9)}`,
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, path9, remote.assetId, remote.contentHash, remote.version, Date.now());
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 import_node_fs11 = require("fs");
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 import_node_fs11.promises.mkdir(outboxDir, { recursive: true });
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, import_node_fs11.readFileSync)(cached.localPath);
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 import_node_crypto3 = require("crypto");
6180
- var import_node_fs12 = require("fs");
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 import_node_fs12.promises.stat(root);
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, import_node_crypto3.randomUUID)();
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 import_node_fs12.promises.readdir(current);
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 import_node_fs12.promises.stat(full);
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 import_node_fs12.promises.readFile(full);
6487
- const sha2564 = (0, import_node_crypto3.createHash)("sha256").update(buf).digest("hex");
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: sha2564,
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 import_node_crypto4 = require("crypto");
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, version) {
5901
+ loadContent(pageId, version2) {
6658
5902
  const db = this.requireDb();
6659
- const targetVersion = version ?? db.prepare("SELECT version FROM memory_pages WHERE workspaceId = ? AND id = ?").get(this.opts.workspaceId, pageId)?.version;
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 = sha2563(input.content);
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, import_node_crypto4.randomUUID)().replace(/-/g, "").slice(0, 22)}`;
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 sha2563(s) {
6892
- return (0, import_node_crypto4.createHash)("sha256").update(s, "utf8").digest("hex");
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 import_node_crypto5 = require("crypto");
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, import_node_crypto5.randomUUID)()}`;
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, import_node_crypto5.randomUUID)()}`;
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, import_node_crypto5.randomUUID)()}`;
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 import_node_crypto6 = require("crypto");
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 path9 of paths) {
7751
- const page = slot.store.loadByPath(path9);
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
- const paths = [];
8010
- for (const p of b.paths) {
8011
- if (typeof p === "string" && p.length > 0) paths.push(p);
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
- const snippetMaxBytes = typeof b.snippetMaxBytes === "number" && b.snippetMaxBytes > 0 ? b.snippetMaxBytes : void 0;
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 await handleSearch2(opts, body, res);
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
- return false;
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
- async function handleSearch2(opts, body, res) {
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
- const index = opts.resolveIndex(b.workspaceId);
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
- const parsed = JSON.parse(row.payloadJson);
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 import_node_fs15 = require("fs");
8758
- var path8 = __toESM(require("path"), 1);
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 import_node_fs15.promises.readdir(dir);
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 = path8.join(dir, name);
7681
+ const full = path6.join(dir, name);
8936
7682
  let st;
8937
7683
  try {
8938
- st = await import_node_fs15.promises.stat(full);
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 import_node_fs15.promises.readFile(filePath);
8964
- const fileName = path8.basename(filePath);
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 import_node_fs16 = require("fs");
9060
- var import_node_path12 = require("path");
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, import_node_fs16.existsSync)(cwd)) return fail2(payload.taskId, "shell_cwd_missing", `cwd does not exist: ${cwd}`);
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, import_node_path12.resolve)(fallback);
9199
- return (0, import_node_path12.resolve)(raw);
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.syncAssetState(this.workspaceId).catch(
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, import_node_fs17.existsSync)(rawFile)) {
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, import_node_fs17.readFileSync)(rawFile, "utf8");
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, import_node_os6.platform)() === "win32" ? "win32" : (0, import_node_os6.platform)() === "linux" ? "linux" : "darwin",
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, version]) => ({ id, version }))
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.syncAssetState(this.workspaceId).catch(
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
- for (const id of payload.profilesToDelete ?? []) {
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
- const reply = await handleDispatch(payload, requestId, {
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
- let profile;
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 this.syncAssetState(payload.workspaceId);
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 sync failed workspace=${payload.workspaceId}:`, err.message);
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 path9;
8826
+ let path7;
10301
8827
  switch (op) {
10302
8828
  case "workspace.create":
10303
8829
  method = "POST";
10304
- path9 = "/api/im/workspaces";
8830
+ path7 = "/api/im/workspaces";
10305
8831
  break;
10306
8832
  case "workspace.update":
10307
8833
  method = "PATCH";
10308
- path9 = `/api/im/workspaces/${id}`;
8834
+ path7 = `/api/im/workspaces/${id}`;
10309
8835
  break;
10310
8836
  case "workspace.delete":
10311
8837
  method = "DELETE";
10312
- path9 = `/api/im/workspaces/${id}`;
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
- path9 = "/api/im/register";
8843
+ path7 = "/api/im/register";
10318
8844
  break;
10319
8845
  case "agent.update":
10320
8846
  method = "PATCH";
10321
- path9 = `/api/im/agents/${id}`;
8847
+ path7 = `/api/im/agents/${id}`;
10322
8848
  break;
10323
8849
  case "agent.delete":
10324
8850
  method = "DELETE";
10325
- path9 = `/api/im/agents/${id}`;
8851
+ path7 = `/api/im/agents/${id}`;
10326
8852
  break;
10327
8853
  case "agent_profile.create":
10328
8854
  method = "POST";
10329
- path9 = "/api/im/agent_profiles";
8855
+ path7 = "/api/im/agent_profiles";
10330
8856
  break;
10331
8857
  case "agent_profile.update":
10332
8858
  method = "PATCH";
10333
- path9 = `/api/im/agent_profiles/${id}`;
8859
+ path7 = `/api/im/agent_profiles/${id}`;
10334
8860
  break;
10335
8861
  case "agent_profile.delete":
10336
8862
  method = "DELETE";
10337
- path9 = `/api/im/agent_profiles/${id}`;
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, path9, { body });
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 && pidAlive2(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, import_promises2.setTimeout)(5e3);
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 && pidAlive2(existingPid)) {
9024
+ if (existingPid && pidAlive(existingPid)) {
10590
9025
  exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
10591
9026
  }
10592
- if (!(0, import_node_fs18.existsSync)(paths.logsDir)) (0, import_node_fs18.mkdirSync)(paths.logsDir, { recursive: true });
10593
- const logFile = (0, import_node_path14.join)(paths.logsDir, "daemon.log");
10594
- const fd = (0, import_node_fs18.openSync)(logFile, "a");
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, import_promises2.setTimeout)(200);
9041
+ await (0, import_promises.setTimeout)(200);
10607
9042
  const pid = readPidFile(paths);
10608
- if (pid && pidAlive2(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 (!pidAlive2(pid)) {
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 (!pidAlive2(pid)) {
9070
+ if (!pidAlive(pid)) {
10636
9071
  clearPidFile(paths);
10637
9072
  getUI().ok("Daemon stopped", `pid ${pid}`);
10638
9073
  return;
10639
9074
  }
10640
- await (0, import_promises2.setTimeout)(200);
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 ? pidAlive2(pid) : false;
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, import_node_path14.join)(paths.logsDir, "daemon.log");
10678
- if (!(0, import_node_fs18.existsSync)(logFile)) {
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(path9, lines) {
9123
+ async function tailFromEnd(path7, lines) {
10689
9124
  const { open } = await import("fs/promises");
10690
- const handle = await open(path9, "r");
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(path9) {
10719
- let offset = (0, import_node_fs18.statSync)(path9).size;
9153
+ async function followFile(path7) {
9154
+ let offset = (0, import_node_fs14.statSync)(path7).size;
10720
9155
  for (; ; ) {
10721
- await (0, import_promises2.setTimeout)(1e3);
10722
- const size = (0, import_node_fs18.statSync)(path9).size;
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, import_node_fs18.createReadStream)(path9, { start: offset, end: size - 1, encoding: "utf8" });
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 import_node_fs19 = require("fs");
10734
- var import_node_os7 = require("os");
10735
- var import_node_path15 = require("path");
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, import_node_fs19.existsSync)(file)) {
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, import_node_fs19.existsSync)(file)) {
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", parsePositiveInt4, 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)");
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, import_node_path15.join)(process.env.PRISMER_HOME ?? (0, import_node_path15.join)((0, import_node_os7.homedir)(), ".prismer"), "para", "events.jsonl");
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, import_node_fs19.createReadStream)(file, { encoding: "utf8" }),
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 parsePositiveInt4(v) {
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 import_better_sqlite35 = __toESM(require("better-sqlite3"), 1);
9296
+ var import_better_sqlite34 = __toESM(require("better-sqlite3"), 1);
10862
9297
  var import_commander10 = require("commander");
10863
- var import_node_fs20 = require("fs");
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", parsePositiveInt5, 20).option("--json", "Output JSON (default)").action(async (opts) => {
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", parsePositiveInt5, 20).option("--json", "Output JSON (default)").action(async (argQuery, opts) => {
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 path9 of paths) {
9387
+ for (const path7 of paths) {
10953
9388
  try {
10954
- const res = await fetch(`${LOCAL_BASE}${path9}`, { method, signal: AbortSignal.timeout(1500) });
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} ${path9} returned HTTP ${res.status}`
9397
+ message: messageFromBody(body) ?? `Daemon endpoint ${method} ${path7} returned HTTP ${res.status}`
10963
9398
  },
10964
- checks: { daemon: { url: LOCAL_BASE, endpoint: path9, reachable: true } }
9399
+ checks: { daemon: { url: LOCAL_BASE, endpoint: path7, reachable: true } }
10965
9400
  };
10966
9401
  }
10967
- return normalizeDaemonBody(body, { method, path: path9 });
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, import_node_fs20.existsSync)(paths.localDb),
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 import_better_sqlite35.default(paths.localDb, { readonly: true, fileMustExist: true });
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 parsePositiveInt5(v) {
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 import_node_crypto9 = require("crypto");
11138
- var import_node_os9 = require("os");
11139
- var import_promises3 = require("timers/promises");
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 import_node_crypto8 = require("crypto");
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, import_node_os8.hostname)().replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unknown";
11148
- return `${PREFIX}${host}-${(0, import_node_crypto8.randomBytes)(6).toString("hex")}`;
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, import_node_crypto9.generateKeyPairSync)("ed25519");
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, import_node_os9.hostname)() }
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, import_promises3.setTimeout)(pollIntervalMs);
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 import_node_fs21 = require("fs");
11301
- var import_node_os10 = require("os");
11302
- var import_node_path16 = require("path");
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, import_node_path16.join)((0, import_node_os10.tmpdir)(), `prismer-profile-${profileId}.json`);
11427
- (0, import_node_fs21.writeFileSync)(tmpFile, JSON.stringify(profile.config, null, 2), "utf8");
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, import_node_fs21.readFileSync)(tmpFile, "utf8"));
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 path9 = arg.slice(1);
11453
- if (!(0, import_node_fs21.existsSync)(path9)) throw new Error(`File not found: ${path9}`);
11454
- return JSON.parse((0, import_node_fs21.readFileSync)(path9, "utf8"));
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 import_commander14 = require("commander");
9900
+ var import_commander13 = require("commander");
11654
9901
  init_util();
11655
9902
  init_ui();
11656
9903
  function buildSandboxCommand() {
11657
- const cmd = new import_commander14.Command("sandbox").description("Inspect and smoke-test sandbox lifecycle");
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", parsePositiveInt6, 50).option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
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", parsePositiveInt6, 6e4).option("--json", "Output machine-readable JSON").allowUnknownOption(true).action(runAction(async (id, command, opts) => {
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, path9, body) {
10005
+ async function cloudRequest(method, path7, body) {
11759
10006
  const cloud = mkCloud5();
11760
- const res = await cloud.request(method, path9, body === void 0 ? void 0 : { body });
11761
- if (!res.ok) exitWithError(`${method} ${path9} failed (${res.status}): ${res.error?.message ?? "request failed"}`);
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} ${path9} failed (${res.status}): ${errorMessage2(responseBody)}`);
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 parsePositiveInt6(value) {
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 import_commander15 = require("commander");
11857
- var import_node_os12 = require("os");
11858
- var import_node_child_process10 = require("child_process");
11859
- var import_node_crypto10 = require("crypto");
11860
- var import_node_fs23 = require("fs");
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 import_commander15.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(
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 ?? (opts.force ? void 0 : process.env.PRISMER_API_KEY);
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, import_node_os12.hostname)(),
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, import_node_os12.hostname)()
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, import_node_os12.hostname)(),
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: daemonId
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, import_node_crypto10.randomBytes)(18).toString("base64url");
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, import_node_child_process10.spawn)(command, args, { stdio: "ignore", detached: true });
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, import_node_child_process10.spawn)(process.execPath, [process.argv[1], "daemon", "start"], {
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, import_node_fs23.existsSync)(localDbPath)) return;
10380
+ if (!(0, import_node_fs18.existsSync)(localDbPath)) return;
12112
10381
  const archived = `${localDbPath}.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
12113
- (0, import_node_fs23.renameSync)(localDbPath, archived);
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 import_commander16 = require("commander");
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 import_commander16.Command("status").description("Show daemon + config + cloud status").option("--json", "Output machine-readable JSON").action(async (opts) => {
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: pidAlive3 } = await Promise.resolve().then(() => (init_util(), util_exports));
12230
- if (pidAlive3(pid)) return { running: true, pid };
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", `${daemonName} pid=${report.daemon.pid} ws=${ws}`);
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
- ui.line(` User: ${me.user.displayName ?? me.user.username ?? "?"}`);
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 import_commander17 = require("commander");
12310
- var import_promises5 = require("timers/promises");
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 import_commander17.Command("task").description("Manage tasks");
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, import_promises5.setTimeout)(1e3);
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 import_commander18 = require("commander");
10710
+ var import_commander17 = require("commander");
12431
10711
  init_util();
12432
10712
  init_ui();
12433
10713
  function buildWorkspaceCommand() {
12434
- const cmd = new import_commander18.Command("workspace").description("Manage workspaces, runtime snapshots, and workspace files");
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
- var VERSION = "1.9.7";
10948
+
10949
+ // package.json
10950
+ var version = "1.9.22";
10951
+
10952
+ // src/cli/index.ts
12669
10953
  function buildProgram() {
12670
- const program = new import_commander19.Command("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(VERSION);
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());