@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.js CHANGED
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
4
10
  var __esm = (fn, res) => function __init() {
5
11
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
12
  };
@@ -12,10 +18,10 @@ var __export = (target, all) => {
12
18
  // src/cli/ui.ts
13
19
  import * as fs from "fs";
14
20
  import * as path from "path";
15
- import { fileURLToPath as fileURLToPath2 } from "url";
21
+ import { fileURLToPath } from "url";
16
22
  function thisDirname() {
17
23
  try {
18
- return path.dirname(fileURLToPath2(import.meta.url));
24
+ return path.dirname(fileURLToPath(import.meta.url));
19
25
  } catch {
20
26
  return process.cwd();
21
27
  }
@@ -447,7 +453,7 @@ __export(util_exports, {
447
453
  info: () => info,
448
454
  normalizeCloudUrl: () => normalizeCloudUrl,
449
455
  ok: () => ok,
450
- pidAlive: () => pidAlive2,
456
+ pidAlive: () => pidAlive,
451
457
  pidFilePath: () => pidFilePath,
452
458
  printBanner: () => printBanner,
453
459
  printJson: () => printJson,
@@ -459,7 +465,7 @@ __export(util_exports, {
459
465
  writePidFile: () => writePidFile
460
466
  });
461
467
  import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
462
- import { join as join4 } from "path";
468
+ import { join as join3 } from "path";
463
469
  function color(kind, text) {
464
470
  if (process.env.NO_COLOR === "1" || process.env.NO_COLOR === "true") return text;
465
471
  return `${ANSI[kind]}${text}${ANSI.reset}`;
@@ -524,10 +530,10 @@ function runAction(fn, opts = {}) {
524
530
  function printBanner(opts = {}) {
525
531
  const ui = getUI();
526
532
  if (opts.compact) {
527
- ui.smallHeader("Runtime CLI v1.9.7");
533
+ ui.smallHeader("Runtime CLI v${VERSION}");
528
534
  return;
529
535
  }
530
- ui.banner("Runtime CLI v1.9.7", { full: true });
536
+ ui.banner("Runtime CLI v${VERSION}", { full: true });
531
537
  }
532
538
  function ok(label, detail) {
533
539
  getUI().ok(label, detail);
@@ -553,7 +559,7 @@ function table(rows, columns) {
553
559
  getUI().table(rows, { columns });
554
560
  }
555
561
  function pidFilePath(paths) {
556
- return join4(paths.root, "daemon.pid");
562
+ return join3(paths.root, "daemon.pid");
557
563
  }
558
564
  function writePidFile(paths, pid) {
559
565
  writeFileSync3(pidFilePath(paths), `${pid}
@@ -575,7 +581,7 @@ function clearPidFile(paths) {
575
581
  }
576
582
  }
577
583
  }
578
- function pidAlive2(pid) {
584
+ function pidAlive(pid) {
579
585
  try {
580
586
  process.kill(pid, 0);
581
587
  return true;
@@ -602,7 +608,7 @@ var init_util = __esm({
602
608
  });
603
609
 
604
610
  // src/cli/index.ts
605
- import { Command as Command19 } from "commander";
611
+ import { Command as Command18 } from "commander";
606
612
 
607
613
  // src/cli/commands/adapter.ts
608
614
  import { spawnSync as spawnSync2 } from "child_process";
@@ -614,8 +620,8 @@ import {
614
620
  statSync,
615
621
  writeFileSync as writeFileSync4
616
622
  } from "fs";
617
- import { homedir as homedir4 } from "os";
618
- import { dirname as dirname4, join as join5 } from "path";
623
+ import { homedir as homedir3 } from "os";
624
+ import { dirname as dirname4, join as join4 } from "path";
619
625
  import { Command } from "commander";
620
626
 
621
627
  // src/adapters/claude-code/index.ts
@@ -1133,36 +1139,15 @@ var codexAdapter = {
1133
1139
  };
1134
1140
 
1135
1141
  // src/adapters/hermes/index.ts
1136
- import { execFileSync, spawn as spawn3 } from "child_process";
1142
+ import { spawn as spawn3 } from "child_process";
1137
1143
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
1138
- import { createRequire } from "module";
1139
1144
  import { homedir } from "os";
1140
1145
  import { dirname, join } from "path";
1141
- import { fileURLToPath } from "url";
1142
1146
  import Database from "better-sqlite3";
1143
1147
  import * as YAML from "yaml";
1144
1148
  import { z as z3 } from "zod";
1145
1149
  var PRISMER_IM_SKILL_NAME = "prismer-im-collab";
1146
- var PRISMER_IM_SKILL_CONTENT = `---
1147
- name: prismer-im-collab
1148
- description: Coordinate reliably in Prismer conversations, use workspace assets through bounded MCP tools, and keep task work on the board.
1149
- 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
1150
- metadata:
1151
- prismer:
1152
- version: "1.0.0"
1153
- mcp_tools:
1154
- - prismer.agent.send
1155
- - prismer.conversation.listAgents
1156
- - prismer.task.create
1157
- - prismer.task.update
1158
- - prismer.task.complete
1159
- - prismer.approval.request_human_approval
1160
- - prismer.asset.search
1161
- - prismer.asset.describe
1162
- - prismer.asset.read
1163
- ---
1164
-
1165
- # Prismer IM Collaboration
1150
+ var PRISMER_IM_SKILL_CONTENT = `# Prismer IM Collaboration
1166
1151
 
1167
1152
  You are an agent in a Prismer multi-agent workspace. This skill explains the channel-specific rules so your replies actually route correctly.
1168
1153
 
@@ -1192,19 +1177,6 @@ You will receive a \`[Channel context]\` block at the top of each prompt indicat
1192
1177
  | Address another agent so the platform dispatches your message to them | Call \`prismer.agent.send\` (from the \`prismer-tasks\` MCP server). |
1193
1178
  | Look up which agents you can talk to | Call \`prismer.conversation.listAgents\` before guessing usernames. |
1194
1179
  | Create / update tasks on the workspace board | \`prismer.task.create\` / \`prismer.task.update\` from \`prismer-tasks\` MCP. |
1195
- | Ask for structured human sign-off | \`prismer.approval.request_human_approval\`. |
1196
- | Find uploaded workspace files | \`prismer.asset.search\`. |
1197
- | Inspect file metadata before reading bytes | \`prismer.asset.describe\`. |
1198
- | Read a bounded byte range from a file | \`prismer.asset.read\`. |
1199
-
1200
- ## Workspace asset rules
1201
-
1202
- - Web-uploaded workspace files are available as asset metadata first; bytes are fetched lazily by the local daemon.
1203
- - When a task needs file content, call \`prismer.asset.describe\` first, then read only the needed range with \`prismer.asset.read\`.
1204
- - For many files, call \`prismer.asset.search\` to find candidates. Do not ask the user to paste file contents already present in workspace assets.
1205
- - Do not claim you read a file unless an asset tool call succeeded.
1206
- - For large tables/documents, read headers, samples, or targeted byte ranges. Never try to load the whole file into a reply.
1207
- - Cite the asset id/URI and locator returned by the tool when using file evidence.
1208
1180
 
1209
1181
  ### When to use \`prismer.agent.send\` vs inline \`@\`
1210
1182
 
@@ -1218,7 +1190,6 @@ You will receive a \`[Channel context]\` block at the top of each prompt indicat
1218
1190
  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 @).
1219
1191
  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.
1220
1192
  4. **Check tool return values.** \`prismer.agent.send\` may return \`{ ok: false, error: 'agent_not_found' }\`; surface the error rather than pretending it worked.
1221
- 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.
1222
1193
 
1223
1194
  ## Example: group discussion handoff
1224
1195
 
@@ -1306,23 +1277,7 @@ var HermesProfileConfigSchema = z3.object({
1306
1277
  hermesSourceDir: z3.string().optional(),
1307
1278
  nativeMirrorTimeoutMs: z3.number().int().positive().default(2e3),
1308
1279
  /** Task authority level: executor (default) or orchestrator. */
1309
- taskAuthority: z3.enum(["executor", "orchestrator"]).optional().default("executor"),
1310
- /** Human approval escalation policy. Enforcement is currently prompt/cloud-side. */
1311
- approvalPolicy: z3.enum(["strict", "auto-low-risk", "autonomous"]).optional().default("auto-low-risk"),
1312
- /** Agent-level MCP tool allowlist. Empty/null means all tools. Supports exact names and trailing * wildcards. */
1313
- mcpAllowlist: z3.array(z3.string()).nullable().optional(),
1314
- /** Agent-level operating principles injected through /v1/runs instructions. */
1315
- operatingPrinciples: z3.union([z3.string(), z3.record(z3.string())]).optional(),
1316
- /**
1317
- * Role-template snapshot carried in AgentProfile.config by cloud ACP.
1318
- * Expected fields used here:
1319
- * - roleTemplate.mcpServers[].toolsAllowlist
1320
- * - roleTemplate.operatingPrinciples
1321
- */
1322
- roleTemplate: z3.object({
1323
- mcpServers: z3.array(z3.object({ toolsAllowlist: z3.array(z3.string()).optional() }).passthrough()).optional(),
1324
- operatingPrinciples: z3.union([z3.string(), z3.record(z3.string())]).optional()
1325
- }).passthrough().optional()
1280
+ taskAuthority: z3.enum(["executor", "orchestrator"]).optional().default("executor")
1326
1281
  });
1327
1282
  var hermesAdapter = {
1328
1283
  name: "hermes",
@@ -1337,22 +1292,14 @@ var hermesAdapter = {
1337
1292
  errors: r.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
1338
1293
  };
1339
1294
  },
1340
- async prepareProfile(profile) {
1341
- const config = HermesProfileConfigSchema.parse(profile.config);
1342
- if (config.configurePrismerProvider) {
1343
- const profileName = getHermesProfileName(profile);
1344
- const agentUsername = profile.agentUsername || profileName;
1345
- configurePrismerProvider(profileName, config, agentUsername, profile.workspaceId);
1346
- }
1347
- },
1348
1295
  async ensureService(profile) {
1349
1296
  const config = HermesProfileConfigSchema.parse(profile.config);
1350
- const profileName = getHermesProfileName(profile);
1351
- const portOverride = config.port !== 8642 ? config.port : portForProfile(`${profileName}:${config.apiKey}`, 8642);
1297
+ const profileName = config.hermesProfileName && config.hermesProfileName !== "default" ? config.hermesProfileName : profile.agentUsername || profile.id.slice(0, 8);
1298
+ const portOverride = config.port !== 8642 ? config.port : portForProfile(profileName, 8642);
1352
1299
  const baseUrl = `http://127.0.0.1:${portOverride}`;
1353
1300
  const agentUsername = profile.agentUsername || profileName;
1354
1301
  if (config.configurePrismerProvider) {
1355
- configurePrismerProvider(profileName, config, agentUsername, profile.workspaceId);
1302
+ configurePrismerProvider(profileName, config, agentUsername);
1356
1303
  }
1357
1304
  if (!await checkHealth(baseUrl, config.apiKey)) {
1358
1305
  if (!config.autoStart) {
@@ -1362,7 +1309,6 @@ var hermesAdapter = {
1362
1309
  (set autoStart: true on the AgentProfile to spawn it automatically)`
1363
1310
  );
1364
1311
  }
1365
- await stopStaleHermesGateways(profileName, portOverride, config.startupTimeoutMs);
1366
1312
  spawn3("hermes", ["-p", profileName, "gateway", "run"], {
1367
1313
  detached: false,
1368
1314
  stdio: "ignore",
@@ -1422,8 +1368,6 @@ var HermesService = class {
1422
1368
  const startedAt = Date.now();
1423
1369
  let runId;
1424
1370
  const sysPrompt = typeof task.metadata?.systemPrompt === "string" ? task.metadata.systemPrompt : void 0;
1425
- const operatingPrinciples = typeof task.metadata?.operatingPrinciples === "string" ? task.metadata.operatingPrinciples : void 0;
1426
- const instructions = [sysPrompt, operatingPrinciples].filter(Boolean).join("\n\n");
1427
1371
  if (sysPrompt) {
1428
1372
  try {
1429
1373
  const profileDir = getHermesProfileDir(this.profileName);
@@ -1460,7 +1404,7 @@ var HermesService = class {
1460
1404
  // sent `messages: [...]` which Hermes 0.12 rejects with 400.
1461
1405
  body: JSON.stringify({
1462
1406
  input: task.prompt,
1463
- instructions: instructions || void 0,
1407
+ instructions: sysPrompt,
1464
1408
  session_id: typeof task.metadata?.conversationId === "string" ? task.metadata.conversationId : void 0,
1465
1409
  stream: true
1466
1410
  }),
@@ -1745,7 +1689,7 @@ function formatHermesGoalText(goals) {
1745
1689
  return `${index + 1}. ${priority}${goal.title}${description}`;
1746
1690
  }).join("\n");
1747
1691
  }
1748
- function configurePrismerProvider(profileName, config, agentUsername, workspaceId) {
1692
+ function configurePrismerProvider(profileName, config, agentUsername) {
1749
1693
  const baseUrl = resolvePrismerProviderBaseUrl(config);
1750
1694
  const apiKey = resolvePrismerApiKey(config);
1751
1695
  if (!baseUrl || !apiKey) {
@@ -1788,16 +1732,9 @@ function configurePrismerProvider(profileName, config, agentUsername, workspaceI
1788
1732
  PRISMER_API_KEY: apiKey,
1789
1733
  PRISMER_BASE_URL: baseUrl.replace(/\/api\/v1\/?$/, "")
1790
1734
  };
1791
- if (workspaceId) {
1792
- mcpEnv.PRISMER_WORKSPACE_ID = workspaceId;
1793
- }
1794
1735
  if (agentUsername) {
1795
1736
  mcpEnv.PRISMER_AGENT_USERNAME = agentUsername;
1796
1737
  }
1797
- const allowlist = resolveMcpAllowlist(config);
1798
- if (allowlist) {
1799
- mcpEnv.PRISMER_MCP_ALLOWLIST = allowlist.join(",");
1800
- }
1801
1738
  mcpServers["prismer-tasks"] = {
1802
1739
  command: "node",
1803
1740
  args: [serverPath],
@@ -1818,17 +1755,20 @@ function resolvePrismerMcpServerPath(config) {
1818
1755
  if (config.prismerMcpServerPath) return config.prismerMcpServerPath;
1819
1756
  if (process.env.PRISMER_MCP_SERVER) return process.env.PRISMER_MCP_SERVER;
1820
1757
  try {
1758
+ const { createRequire } = __require("module");
1821
1759
  const req = createRequire(import.meta.url);
1822
1760
  return req.resolve("@prismer/mcp-server");
1823
1761
  } catch {
1824
1762
  }
1825
1763
  try {
1826
- const here = dirname(fileURLToPath(import.meta.url));
1827
- const candidate = join(here, "../../mcp/dist/index.js");
1764
+ const { fileURLToPath: fileURLToPath2 } = __require("url");
1765
+ const { join: join14, dirname: dirname8 } = __require("path");
1766
+ const here = dirname8(fileURLToPath2(import.meta.url));
1767
+ const candidate = join14(here, "../../mcp/dist/index.js");
1828
1768
  if (existsSync(candidate)) return candidate;
1829
- const candidate2 = join(here, "../../../mcp/dist/index.js");
1769
+ const candidate2 = join14(here, "../../../mcp/dist/index.js");
1830
1770
  if (existsSync(candidate2)) return candidate2;
1831
- const candidate3 = join(here, "../../../../mcp/dist/index.js");
1771
+ const candidate3 = join14(here, "../../../../mcp/dist/index.js");
1832
1772
  if (existsSync(candidate3)) return candidate3;
1833
1773
  } catch {
1834
1774
  }
@@ -1997,29 +1937,25 @@ function runPythonJson(args, env, timeoutMs) {
1997
1937
  });
1998
1938
  });
1999
1939
  }
2000
- function readHermesConfig(path9) {
2001
- if (!existsSync(path9)) return {};
1940
+ function readHermesConfig(path7) {
1941
+ if (!existsSync(path7)) return {};
2002
1942
  try {
2003
- return YAML.parse(readFileSync(path9, "utf8")) ?? {};
1943
+ return YAML.parse(readFileSync(path7, "utf8")) ?? {};
2004
1944
  } catch (err) {
2005
- throw new Error(`Failed to parse Hermes config at ${path9}: ${err.message}`);
1945
+ throw new Error(`Failed to parse Hermes config at ${path7}: ${err.message}`);
2006
1946
  }
2007
1947
  }
2008
- function writeEnvValue(path9, key, value) {
2009
- mkdirSync(dirname(path9), { recursive: true });
2010
- const lines = existsSync(path9) ? readFileSync(path9, "utf8").split(/\r?\n/) : [];
1948
+ function writeEnvValue(path7, key, value) {
1949
+ mkdirSync(dirname(path7), { recursive: true });
1950
+ const lines = existsSync(path7) ? readFileSync(path7, "utf8").split(/\r?\n/) : [];
2011
1951
  const next = lines.filter((line) => !line.startsWith(`${key}=`) && line.trim() !== "");
2012
1952
  next.push(`${key}=${quoteEnv(value)}`);
2013
- writeFileSync(path9, `${next.join("\n")}
1953
+ writeFileSync(path7, `${next.join("\n")}
2014
1954
  `, "utf8");
2015
1955
  }
2016
1956
  function quoteEnv(value) {
2017
1957
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
2018
1958
  }
2019
- function getHermesProfileName(profile) {
2020
- const configured = profile.config && typeof profile.config.hermesProfileName === "string" ? profile.config.hermesProfileName : void 0;
2021
- return configured && configured !== "default" ? configured : profile.agentUsername || profile.id.slice(0, 8);
2022
- }
2023
1959
  function getHermesProfileDir(profileName) {
2024
1960
  const root = process.env.HERMES_HOME || join(homedir(), ".hermes");
2025
1961
  if (!profileName || profileName === "default") return root;
@@ -2041,18 +1977,6 @@ function resolvePrismerProviderBaseUrl(config) {
2041
1977
  function resolvePrismerApiKey(config) {
2042
1978
  return process.env[config.prismerApiKeyEnv] || process.env.PRISMER_API_KEY || "";
2043
1979
  }
2044
- function resolveMcpAllowlist(config) {
2045
- if (Array.isArray(config.mcpAllowlist)) return normalizeAllowlist(config.mcpAllowlist);
2046
- const prismerServer = config.roleTemplate?.mcpServers?.find((server) => {
2047
- const record = server;
2048
- return record.package === "@prismer/mcp-server" || record.name === "prismer-tasks";
2049
- });
2050
- if (!prismerServer) return null;
2051
- return Array.isArray(prismerServer.toolsAllowlist) ? normalizeAllowlist(prismerServer.toolsAllowlist) : null;
2052
- }
2053
- function normalizeAllowlist(values) {
2054
- return values.map((value) => value.trim()).filter(Boolean);
2055
- }
2056
1980
  function normalizeProviderName(value) {
2057
1981
  return value.trim().toLowerCase().replace(/\s+/g, "-");
2058
1982
  }
@@ -2158,71 +2082,9 @@ async function waitForHealthy(baseUrl, apiKey, timeoutMs) {
2158
2082
  }
2159
2083
  throw new Error(`Hermes did not become healthy at ${baseUrl} within ${timeoutMs}ms`);
2160
2084
  }
2161
- async function stopStaleHermesGateways(profileName, port, timeoutMs) {
2162
- const pids = findHermesGatewayPids(profileName, port);
2163
- for (const pid of pids) {
2164
- try {
2165
- process.kill(pid, "SIGTERM");
2166
- } catch {
2167
- continue;
2168
- }
2169
- }
2170
- const deadline = Date.now() + Math.min(timeoutMs, 5e3);
2171
- while (Date.now() < deadline) {
2172
- const alive = pids.filter(pidAlive);
2173
- if (alive.length === 0) return;
2174
- await new Promise((r) => setTimeout(r, 100));
2175
- }
2176
- for (const pid of pids) {
2177
- if (!pidAlive(pid)) continue;
2178
- try {
2179
- process.kill(pid, "SIGKILL");
2180
- } catch {
2181
- }
2182
- }
2183
- }
2184
- function findHermesGatewayPids(profileName, port) {
2185
- try {
2186
- const out = execFileSync("ps", ["-axo", "pid=,command="], { encoding: "utf8" });
2187
- const current = process.pid;
2188
- const escapedProfile = escapeRegExp(profileName);
2189
- const profilePattern = new RegExp(`(?:^|\\s)-p\\s+${escapedProfile}(?:\\s|$)`);
2190
- const portPattern = new RegExp(`(?:^|\\s)API_SERVER_PORT=${port}(?:\\s|$)`);
2191
- return out.split(/\r?\n/).map((line) => {
2192
- const trimmed = line.trim();
2193
- const match = /^(\d+)\s+(.+)$/.exec(trimmed);
2194
- if (!match) return null;
2195
- const pid = Number.parseInt(match[1], 10);
2196
- const command = match[2];
2197
- if (!Number.isFinite(pid) || pid === current) return null;
2198
- if (!isHermesGatewayCommand(command)) return null;
2199
- if (!profilePattern.test(command) && !portPattern.test(command)) return null;
2200
- return pid;
2201
- }).filter((pid) => pid !== null);
2202
- } catch {
2203
- return [];
2204
- }
2205
- }
2206
- function isHermesGatewayCommand(command) {
2207
- if (!/\bgateway\b/.test(command) || !/\brun\b/.test(command)) return false;
2208
- return /(^|\s)(?:\S*\/)?hermes(?:\s|$)/.test(command);
2209
- }
2210
- function pidAlive(pid) {
2211
- try {
2212
- process.kill(pid, 0);
2213
- return true;
2214
- } catch {
2215
- return false;
2216
- }
2217
- }
2218
- function escapeRegExp(value) {
2219
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2220
- }
2221
2085
 
2222
2086
  // src/adapters/openclaw/index.ts
2223
2087
  import { z as z4 } from "zod";
2224
- import { homedir as homedir2 } from "os";
2225
- import { join as join2 } from "path";
2226
2088
  var OpenClawProfileConfigSchema = z4.object({
2227
2089
  /** OpenClaw chat-completions HTTP port. */
2228
2090
  port: z4.number().int().min(1).max(65535).default(18789),
@@ -2236,13 +2098,7 @@ var OpenClawProfileConfigSchema = z4.object({
2236
2098
  * by `agents.defaults.model.primary` in openclaw.json (e.g.
2237
2099
  * `prismer/us-kimi-k2.5`, set by the bootstrap).
2238
2100
  */
2239
- model: z4.string().min(1).default("openclaw"),
2240
- mcpAllowlist: z4.array(z4.string()).nullable().optional(),
2241
- approvalPolicy: z4.enum(["strict", "auto-low-risk", "autonomous"]).optional().default("auto-low-risk"),
2242
- roleTemplate: z4.object({
2243
- mcpServers: z4.array(z4.object({ toolsAllowlist: z4.array(z4.string()).optional() }).passthrough()).optional()
2244
- }).passthrough().optional(),
2245
- skillsDir: z4.string().optional()
2101
+ model: z4.string().min(1).default("openclaw")
2246
2102
  });
2247
2103
  var openclawAdapter = {
2248
2104
  name: "openclaw",
@@ -2266,24 +2122,22 @@ var openclawAdapter = {
2266
2122
  openclaw gateway --port ${config.port}`
2267
2123
  );
2268
2124
  }
2269
- return new OpenClawService(baseUrl, config.apiKey, config.model, resolveOpenClawSkillsDir(profile, config));
2125
+ return new OpenClawService(baseUrl, config.apiKey, config.model);
2270
2126
  },
2271
2127
  async health() {
2272
2128
  return { available: true };
2273
2129
  }
2274
2130
  };
2275
2131
  var OpenClawService = class {
2276
- constructor(baseUrl, apiKey, model, skillsDir) {
2132
+ constructor(baseUrl, apiKey, model) {
2277
2133
  this.baseUrl = baseUrl;
2278
2134
  this.apiKey = apiKey;
2279
2135
  this.model = model;
2280
- this.skillsDir = skillsDir;
2281
2136
  this.id = baseUrl;
2282
2137
  }
2283
2138
  baseUrl;
2284
2139
  apiKey;
2285
2140
  model;
2286
- skillsDir;
2287
2141
  id;
2288
2142
  async healthy() {
2289
2143
  return checkHealth2(this.baseUrl, this.apiKey);
@@ -2344,15 +2198,6 @@ var OpenClawService = class {
2344
2198
  }
2345
2199
  }
2346
2200
  };
2347
- function getOpenClawSkillsDir(profile) {
2348
- const config = OpenClawProfileConfigSchema.parse(profile.config);
2349
- return resolveOpenClawSkillsDir(profile, config);
2350
- }
2351
- function resolveOpenClawSkillsDir(profile, config) {
2352
- if (config.skillsDir) return config.skillsDir;
2353
- const name = profile.agentUsername || profile.id.slice(0, 8);
2354
- return join2(process.env.OPENCLAW_HOME || join2(homedir2(), ".openclaw"), "profiles", name, "skills");
2355
- }
2356
2201
  async function checkHealth2(baseUrl, apiKey) {
2357
2202
  try {
2358
2203
  const res = await fetch(`${baseUrl}/health`, {
@@ -2368,8 +2213,8 @@ async function checkHealth2(baseUrl, apiKey) {
2368
2213
  // src/config.ts
2369
2214
  import * as TOML from "@iarna/toml";
2370
2215
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2371
- import { homedir as homedir3 } from "os";
2372
- import { dirname as dirname2, join as join3 } from "path";
2216
+ import { homedir as homedir2 } from "os";
2217
+ import { dirname as dirname2, join as join2 } from "path";
2373
2218
  import { z as z5 } from "zod";
2374
2219
  var ConfigSchema = z5.object({
2375
2220
  /** API key from `prismer setup`, or env override. */
@@ -2399,14 +2244,14 @@ var ConfigSchema = z5.object({
2399
2244
  }).optional()
2400
2245
  });
2401
2246
  function resolvePaths(home) {
2402
- const root = home ?? process.env.PRISMER_HOME ?? join3(homedir3(), ".prismer");
2247
+ const root = home ?? process.env.PRISMER_HOME ?? join2(homedir2(), ".prismer");
2403
2248
  return {
2404
2249
  root,
2405
- configFile: join3(root, "config.toml"),
2406
- localDb: join3(root, "local.db"),
2407
- cacheDir: join3(root, "cache"),
2408
- logsDir: join3(root, "logs"),
2409
- runsDir: join3(root, "runs")
2250
+ configFile: join2(root, "config.toml"),
2251
+ localDb: join2(root, "local.db"),
2252
+ cacheDir: join2(root, "cache"),
2253
+ logsDir: join2(root, "logs"),
2254
+ runsDir: join2(root, "runs")
2410
2255
  };
2411
2256
  }
2412
2257
  function configExists(paths = resolvePaths()) {
@@ -2461,7 +2306,7 @@ var INSTALL_SPECS = {
2461
2306
  binary: "claude",
2462
2307
  hint: "Set ANTHROPIC_API_KEY in your shell profile, or rely on Claude Code OAuth login. Run `claude login` to sign in.",
2463
2308
  authHints: ["ANTHROPIC_API_KEY or Claude Code OAuth login (`claude login`)"],
2464
- hookTarget: { path: join5(homedir4(), ".claude", "hooks.json"), kind: "json-file" }
2309
+ hookTarget: { path: join4(homedir3(), ".claude", "hooks.json"), kind: "json-file" }
2465
2310
  },
2466
2311
  openclaw: {
2467
2312
  name: "openclaw",
@@ -2470,7 +2315,7 @@ var INSTALL_SPECS = {
2470
2315
  binary: "openclaw",
2471
2316
  hint: "OpenClaw runs as a gateway. Configure ~/.openclaw/openclaw.json and start it with `openclaw gateway` before tasks dispatch.",
2472
2317
  authHints: ["~/.openclaw/openclaw.json gateway.auth.bearerTokens", "Prismer daemon api_key"],
2473
- hookTarget: { path: join5(homedir4(), ".openclaw", "hooks"), kind: "directory" }
2318
+ hookTarget: { path: join4(homedir3(), ".openclaw", "hooks"), kind: "directory" }
2474
2319
  },
2475
2320
  codex: {
2476
2321
  name: "codex",
@@ -2479,7 +2324,7 @@ var INSTALL_SPECS = {
2479
2324
  binary: "codex",
2480
2325
  hint: "Set OPENAI_API_KEY in your shell profile. Verify with `codex --help`.",
2481
2326
  authHints: ["OPENAI_API_KEY or Codex CLI account login"],
2482
- hookTarget: { path: join5(homedir4(), ".codex", "hooks.json"), kind: "json-file" }
2327
+ hookTarget: { path: join4(homedir3(), ".codex", "hooks.json"), kind: "json-file" }
2483
2328
  },
2484
2329
  hermes: {
2485
2330
  name: "hermes",
@@ -2488,7 +2333,7 @@ var INSTALL_SPECS = {
2488
2333
  binary: "hermes",
2489
2334
  hint: "Hermes runs as a long-lived HTTP gateway in your own Python venv. Start with `hermes -p <profile> gateway` after configuring ~/.hermes/.env.",
2490
2335
  authHints: ["~/.hermes/.env API_SERVER_KEY", "Hermes profile provider credentials"],
2491
- hookTarget: { path: join5(homedir4(), ".hermes", "hooks.json"), kind: "json-file" }
2336
+ hookTarget: { path: join4(homedir3(), ".hermes", "hooks.json"), kind: "json-file" }
2492
2337
  }
2493
2338
  };
2494
2339
  function buildAdapterCommand() {
@@ -2576,8 +2421,8 @@ function buildAdapterCommand() {
2576
2421
  });
2577
2422
  return cmd;
2578
2423
  }
2579
- function runInstall(spec, version, dryRun) {
2580
- const { argv0, args } = installCommand(spec, version);
2424
+ function runInstall(spec, version2, dryRun) {
2425
+ const { argv0, args } = installCommand(spec, version2);
2581
2426
  getUI().line(`> ${argv0} ${args.join(" ")}`);
2582
2427
  if (dryRun) {
2583
2428
  getUI().line("(dry-run \u2014 not executing)");
@@ -2704,7 +2549,7 @@ function runHooks(spec, opts) {
2704
2549
  let backup;
2705
2550
  if (planned.kind === "directory") {
2706
2551
  mkdirSync3(planned.path, { recursive: true });
2707
- const markerPath = join5(planned.path, "prismer.json");
2552
+ const markerPath = join4(planned.path, "prismer.json");
2708
2553
  backup = backupIfExists(markerPath);
2709
2554
  writeFileSync4(markerPath, JSON.stringify(prismerHookMarker(spec), null, 2) + "\n", "utf8");
2710
2555
  } else {
@@ -2721,16 +2566,16 @@ function runHooks(spec, opts) {
2721
2566
  hook: inspectHook(spec)
2722
2567
  };
2723
2568
  }
2724
- function installCommand(spec, version) {
2725
- const ref = version === "latest" ? spec.package : `${spec.package}@${version}`;
2569
+ function installCommand(spec, version2) {
2570
+ const ref = version2 === "latest" ? spec.package : `${spec.package}@${version2}`;
2726
2571
  switch (spec.manager) {
2727
2572
  case "npm":
2728
2573
  return { argv0: "npm", args: ["install", "-g", ref] };
2729
2574
  case "pipx":
2730
- const pyRef = version === "latest" ? spec.package : `${spec.package}==${version}`;
2575
+ const pyRef = version2 === "latest" ? spec.package : `${spec.package}==${version2}`;
2731
2576
  return { argv0: "pipx", args: ["install", pyRef] };
2732
2577
  case "pip":
2733
- const pipRef = version === "latest" ? spec.package : `${spec.package}==${version}`;
2578
+ const pipRef = version2 === "latest" ? spec.package : `${spec.package}==${version2}`;
2734
2579
  return { argv0: "pip", args: ["install", "--user", pipRef] };
2735
2580
  }
2736
2581
  }
@@ -2778,7 +2623,7 @@ function inspectAuthEnv(spec) {
2778
2623
  hints: spec.authHints ?? []
2779
2624
  };
2780
2625
  case "hermes": {
2781
- const envFile = join5(homedir4(), ".hermes", ".env");
2626
+ const envFile = join4(homedir3(), ".hermes", ".env");
2782
2627
  return {
2783
2628
  ok: existsSync5(envFile) || Boolean(process.env.HERMES_API_KEY || process.env.API_SERVER_KEY),
2784
2629
  present: [
@@ -2789,7 +2634,7 @@ function inspectAuthEnv(spec) {
2789
2634
  };
2790
2635
  }
2791
2636
  case "openclaw": {
2792
- const cfgFile = join5(homedir4(), ".openclaw", "openclaw.json");
2637
+ const cfgFile = join4(homedir3(), ".openclaw", "openclaw.json");
2793
2638
  return {
2794
2639
  ok: existsSync5(cfgFile) || Boolean(process.env.OPENCLAW_API_KEY),
2795
2640
  present: [
@@ -2809,8 +2654,8 @@ function inspectHook(spec) {
2809
2654
  return { supported: false, markerPresent: false };
2810
2655
  }
2811
2656
  if (spec.name === "openclaw") {
2812
- const jsonPath = join5(homedir4(), ".openclaw", "hooks.json");
2813
- const markerPath = join5(target.path, "prismer.json");
2657
+ const jsonPath = join4(homedir3(), ".openclaw", "hooks.json");
2658
+ const markerPath = join4(target.path, "prismer.json");
2814
2659
  const jsonMarkerPresent = existsSync5(jsonPath) && fileContainsPrismerMarker(jsonPath);
2815
2660
  const dirMarkerPresent = existsSync5(markerPath) && fileContainsPrismerMarker(markerPath);
2816
2661
  return {
@@ -2824,7 +2669,7 @@ function inspectHook(spec) {
2824
2669
  };
2825
2670
  }
2826
2671
  if (target.kind === "directory") {
2827
- const markerPath = join5(target.path, "prismer.json");
2672
+ const markerPath = join4(target.path, "prismer.json");
2828
2673
  return {
2829
2674
  supported: true,
2830
2675
  kind: target.kind,
@@ -2868,21 +2713,21 @@ function prismerHookMarker(spec) {
2868
2713
  installed_at: (/* @__PURE__ */ new Date()).toISOString()
2869
2714
  };
2870
2715
  }
2871
- function mergeHookJson(path9, spec) {
2716
+ function mergeHookJson(path7, spec) {
2872
2717
  const marker = prismerHookMarker(spec);
2873
- if (!existsSync5(path9)) {
2718
+ if (!existsSync5(path7)) {
2874
2719
  return {
2875
2720
  prismer: marker
2876
2721
  };
2877
2722
  }
2878
2723
  let parsed;
2879
2724
  try {
2880
- parsed = JSON.parse(readFileSync5(path9, "utf8"));
2725
+ parsed = JSON.parse(readFileSync5(path7, "utf8"));
2881
2726
  } catch (err) {
2882
- throw new Error(`Cannot parse ${path9} as JSON: ${err.message}`);
2727
+ throw new Error(`Cannot parse ${path7} as JSON: ${err.message}`);
2883
2728
  }
2884
2729
  if (!isRecord(parsed)) {
2885
- throw new Error(`Cannot merge ${path9}: expected a JSON object`);
2730
+ throw new Error(`Cannot merge ${path7}: expected a JSON object`);
2886
2731
  }
2887
2732
  const next = { ...parsed };
2888
2733
  next.prismer = marker;
@@ -2900,18 +2745,18 @@ function mergeHookJson(path9, spec) {
2900
2745
  }
2901
2746
  return next;
2902
2747
  }
2903
- function backupIfExists(path9) {
2904
- if (!existsSync5(path9)) return null;
2748
+ function backupIfExists(path7) {
2749
+ if (!existsSync5(path7)) return null;
2905
2750
  const suffix = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2906
- const backup = `${path9}.bak.${suffix}`;
2907
- const stat = statSync(path9);
2751
+ const backup = `${path7}.bak.${suffix}`;
2752
+ const stat = statSync(path7);
2908
2753
  if (stat.isDirectory()) return null;
2909
- copyFileSync(path9, backup);
2754
+ copyFileSync(path7, backup);
2910
2755
  return backup;
2911
2756
  }
2912
- function fileContainsPrismerMarker(path9) {
2757
+ function fileContainsPrismerMarker(path7) {
2913
2758
  try {
2914
- return readFileSync5(path9, "utf8").includes("prismer-daemon-runtime");
2759
+ return readFileSync5(path7, "utf8").includes("prismer-daemon-runtime");
2915
2760
  } catch {
2916
2761
  return false;
2917
2762
  }
@@ -2929,7 +2774,7 @@ function readAdapterConfig() {
2929
2774
  return {};
2930
2775
  }
2931
2776
  }
2932
- function recordInstallInConfig(spec, binPath, version) {
2777
+ function recordInstallInConfig(spec, binPath, version2) {
2933
2778
  const paths = resolvePaths();
2934
2779
  if (!configExists(paths)) {
2935
2780
  process.stderr.write(
@@ -2951,7 +2796,7 @@ function recordInstallInConfig(spec, binPath, version) {
2951
2796
  package_manager: spec.manager,
2952
2797
  package_name: spec.package,
2953
2798
  binary: binPath,
2954
- version,
2799
+ version: version2,
2955
2800
  installed_at: (/* @__PURE__ */ new Date()).toISOString()
2956
2801
  };
2957
2802
  saveConfig({ ...cfg, adapters }, paths);
@@ -2984,8 +2829,8 @@ var CloudClient = class {
2984
2829
  opts;
2985
2830
  fetchImpl;
2986
2831
  /** Low-level request. Caller decides on retry; sync-worker handles backoff. */
2987
- async request(method, path9, init) {
2988
- const url = this.urlFor(path9);
2832
+ async request(method, path7, init) {
2833
+ const url = this.urlFor(path7);
2989
2834
  const headers = {
2990
2835
  "Content-Type": "application/json",
2991
2836
  Accept: "application/json",
@@ -3044,8 +2889,8 @@ var CloudClient = class {
3044
2889
  return { ok: true, status: res.status, data: body ?? void 0 };
3045
2890
  }
3046
2891
  /** Convenience: GET expecting `{ ok, data }` envelope. Returns `data` or throws. */
3047
- async get(path9, opts) {
3048
- const res = await this.request("GET", path9, opts);
2892
+ async get(path7, opts) {
2893
+ const res = await this.request("GET", path7, opts);
3049
2894
  if (!res.ok) throw new CloudError(res.status, res.error?.code ?? "unknown", res.error?.message ?? "request failed");
3050
2895
  if (res.data && typeof res.data === "object" && "ok" in res.data) {
3051
2896
  const env = res.data;
@@ -3054,14 +2899,14 @@ var CloudClient = class {
3054
2899
  }
3055
2900
  return res.data;
3056
2901
  }
3057
- urlFor(path9) {
2902
+ urlFor(path7) {
3058
2903
  const trimmedBase = this.opts.baseUrl.replace(/\/$/, "");
3059
- const trimmedPath = path9.startsWith("/") ? path9 : `/${path9}`;
2904
+ const trimmedPath = path7.startsWith("/") ? path7 : `/${path7}`;
3060
2905
  return `${trimmedBase}${trimmedPath}`;
3061
2906
  }
3062
2907
  /** Raw byte download (for asset cache). Returns response so caller can stream body. */
3063
- async fetchRaw(path9, init) {
3064
- const url = this.urlFor(path9);
2908
+ async fetchRaw(path7, init) {
2909
+ const url = this.urlFor(path7);
3065
2910
  const headers = {
3066
2911
  Authorization: `Bearer ${this.opts.apiKey}`,
3067
2912
  ...init?.headers ?? {}
@@ -3233,11 +3078,11 @@ var MIGRATIONS = [
3233
3078
  function runSql(db, sql) {
3234
3079
  db["exec"](sql);
3235
3080
  }
3236
- function openLocalDb(path9) {
3237
- if (path9 !== ":memory:" && !existsSync6(dirname5(path9))) {
3238
- mkdirSync4(dirname5(path9), { recursive: true });
3081
+ function openLocalDb(path7) {
3082
+ if (path7 !== ":memory:" && !existsSync6(dirname5(path7))) {
3083
+ mkdirSync4(dirname5(path7), { recursive: true });
3239
3084
  }
3240
- const db = new Database2(path9);
3085
+ const db = new Database2(path7);
3241
3086
  db.pragma("journal_mode = WAL");
3242
3087
  db.pragma("foreign_keys = ON");
3243
3088
  runMigrations(db);
@@ -3617,7 +3462,7 @@ async function fetchCloudProfiles(cloud, imUserId) {
3617
3462
  async function fetchLocalServer() {
3618
3463
  const paths = resolvePaths();
3619
3464
  const pid = readPidFile(paths);
3620
- if (!pid || !pidAlive2(pid)) return { ok: false, error: "daemon process is not running", agents: [] };
3465
+ if (!pid || !pidAlive(pid)) return { ok: false, error: "daemon process is not running", agents: [] };
3621
3466
  try {
3622
3467
  const [healthRes, agentsRes] = await Promise.all([
3623
3468
  fetch(`${LOCAL_SERVER}/healthz`, { signal: AbortSignal.timeout(1e3) }),
@@ -3641,664 +3486,83 @@ function whichBinary2(bin) {
3641
3486
 
3642
3487
  // src/cli/commands/asset.ts
3643
3488
  import { Command as Command3 } from "commander";
3644
- import { existsSync as existsSync10, lstatSync, readdirSync, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
3645
- import { basename, dirname as dirname7, join as join9, relative } from "path";
3646
-
3647
- // src/asset-cache.ts
3648
- import { createHash } from "crypto";
3649
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, renameSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
3650
- import { dirname as dirname6, join as join6 } from "path";
3651
- function rowToCached(row) {
3652
- return {
3653
- contentHash: row.content_hash,
3654
- sizeBytes: row.size_bytes,
3655
- mime: row.mime,
3656
- localPath: row.local_path,
3657
- fetchedAt: row.fetched_at,
3658
- lastUsedAt: row.last_used_at,
3659
- pin: row.pin === 1
3660
- };
3661
- }
3662
- var AssetCache = class {
3663
- db;
3664
- cloud;
3665
- cacheDir;
3666
- maxBytes;
3667
- constructor(opts) {
3668
- this.db = opts.db;
3669
- this.cloud = opts.cloud;
3670
- this.cacheDir = opts.cacheDir;
3671
- this.maxBytes = opts.maxBytes ?? 5 * 1024 * 1024 * 1024;
3672
- if (!existsSync7(this.cacheDir)) {
3673
- mkdirSync5(this.cacheDir, { recursive: true });
3674
- }
3675
- }
3676
- /** Local file path for a content hash. Files split by first 2 chars for fs scaling. */
3677
- pathFor(hash) {
3678
- return join6(this.cacheDir, hash.slice(0, 2), hash);
3679
- }
3680
- get(hash) {
3681
- const row = this.db.prepare("SELECT * FROM cached_assets WHERE content_hash = ?").get(hash);
3682
- if (!row) return void 0;
3683
- if (!existsSync7(row.local_path)) {
3684
- this.db.prepare("DELETE FROM cached_assets WHERE content_hash = ?").run(hash);
3685
- return void 0;
3489
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3490
+ import { basename } from "path";
3491
+ init_util();
3492
+ init_ui();
3493
+ function buildAssetCommand() {
3494
+ const cmd = new Command3("asset").description("Inspect IM assets");
3495
+ 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) => {
3496
+ const cloud = mkCloud();
3497
+ const query = new URLSearchParams();
3498
+ if (opts.workspaceId) query.set("workspaceId", opts.workspaceId);
3499
+ if (opts.taskId) query.set("taskId", opts.taskId);
3500
+ const path7 = `/api/im/assets${query.toString() ? `?${query.toString()}` : ""}`;
3501
+ const res = await cloud.request("GET", path7);
3502
+ if (!res.ok) exitWithError(`asset list failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_list_failed" });
3503
+ const body = res.data;
3504
+ if (isErrorEnvelope(body)) {
3505
+ exitWithError(`asset list failed (${res.status}): ${errorMessage(body)}`, { code: "asset_list_failed" });
3686
3506
  }
3687
- this.touch(hash);
3688
- return rowToCached({ ...row, last_used_at: Date.now() });
3689
- }
3690
- /** Bump `last_used_at` (call when an asset is consulted). */
3691
- touch(hash) {
3692
- this.db.prepare("UPDATE cached_assets SET last_used_at = ? WHERE content_hash = ?").run(Date.now(), hash);
3693
- }
3694
- pin(hash) {
3695
- this.db.prepare("UPDATE cached_assets SET pin = 1 WHERE content_hash = ?").run(hash);
3696
- }
3697
- unpin(hash) {
3698
- this.db.prepare("UPDATE cached_assets SET pin = 0 WHERE content_hash = ?").run(hash);
3699
- }
3700
- /**
3701
- * Get or fetch by hash. On miss, downloads the raw bytes and caches them.
3702
- *
3703
- * Why two endpoints: `GET /api/im/assets/by-hash/:hash` returns the JSON
3704
- * `AssetDTO` (id, contentHash, storageUri, …) — *metadata only*. The raw
3705
- * bytes live at `GET /api/im/assets/:id`. If `opts.assetId` is supplied
3706
- * the caller already has the id from an `AssetRef`, so we go straight to
3707
- * the byte endpoint; otherwise we resolve `hash → id` first (one extra
3708
- * round-trip on the metadata route) before downloading the body.
3709
- *
3710
- * Earlier this method called `by-hash` and treated the JSON body as the
3711
- * asset content — the daemon stored the 500-byte metadata blob on disk
3712
- * and `resolveAssetRefs` inlined that JSON into the prompt. The agent
3713
- * saw `{"id":"…","storageUri":"…"}` instead of the user's file and
3714
- * concluded the upload had failed.
3715
- */
3716
- async getOrFetch(hash, opts) {
3717
- const existing = this.get(hash);
3718
- if (existing) return existing;
3719
- const wsParam = opts?.workspaceIdHint ? `?wsId=${encodeURIComponent(opts.workspaceIdHint)}` : "";
3720
- let assetId = opts?.assetId;
3721
- if (!assetId) {
3722
- const metaRes = await this.cloud.fetchRaw(
3723
- `/api/im/assets/by-hash/${encodeURIComponent(hash)}${wsParam}`,
3724
- { signal: opts?.signal }
3725
- );
3726
- if (!metaRes.ok) {
3727
- throw new Error(`Asset metadata fetch failed for ${hash}: HTTP ${metaRes.status}`);
3728
- }
3729
- const metaJson = await metaRes.json();
3730
- if (!metaJson?.ok || typeof metaJson.data?.id !== "string") {
3731
- throw new Error(`Asset metadata payload missing id for ${hash}`);
3732
- }
3733
- assetId = metaJson.data.id;
3507
+ if (opts.json) {
3508
+ printJson(body ?? null);
3509
+ return;
3734
3510
  }
3735
- const res = await this.cloud.fetchRaw(`/api/im/assets/${encodeURIComponent(assetId)}`, {
3736
- signal: opts?.signal
3737
- });
3738
- if (!res.ok) {
3739
- throw new Error(`Asset bytes fetch failed for ${hash} (id=${assetId}): HTTP ${res.status}`);
3511
+ printAssetList(body);
3512
+ }, { code: "asset_list_failed" }));
3513
+ 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) => {
3514
+ const body = await uploadAsset(file, opts);
3515
+ if (opts.json) {
3516
+ printJson(body ?? null);
3517
+ return;
3740
3518
  }
3741
- const buf = Buffer.from(await res.arrayBuffer());
3742
- const actualHash = sha256(buf);
3743
- if (actualHash !== hash) {
3744
- throw new Error(`Asset hash mismatch for ${hash}: downloaded ${actualHash}`);
3519
+ printAssetDetail("Uploaded asset", file, body);
3520
+ }, { code: "asset_upload_failed" }));
3521
+ cmd.command("download <assetId>").description("Download asset bytes").requiredOption("--out <path>", "Destination file path").action(runAction(async (assetId, opts) => {
3522
+ await downloadAsset(assetId, opts.out);
3523
+ getUI().line(`Downloaded ${assetId} -> ${opts.out}`);
3524
+ }, { code: "asset_download_failed" }));
3525
+ cmd.command("get <assetId>").description("Fetch asset metadata by id").option("--json", "Output machine-readable JSON").action(runAction(async (assetId, opts) => {
3526
+ const cloud = mkCloud();
3527
+ const res = await cloud.request("GET", `/api/im/assets/${encodeURIComponent(assetId)}/detail`);
3528
+ if (!res.ok) exitWithError(`asset get failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_get_failed" });
3529
+ const body = res.data;
3530
+ if (isErrorEnvelope(body)) {
3531
+ exitWithError(`asset get failed (${res.status}): ${errorMessage(body)}`, { code: "asset_get_failed" });
3745
3532
  }
3746
- const localPath = this.pathFor(hash);
3747
- if (!existsSync7(dirname6(localPath))) {
3748
- mkdirSync5(dirname6(localPath), { recursive: true });
3533
+ if (opts.json) {
3534
+ printJson(body ?? null);
3535
+ return;
3749
3536
  }
3750
- const tmpPath = `${localPath}.${process.pid}.${Date.now()}.tmp`;
3751
- writeFileSync5(tmpPath, buf);
3752
- renameSync(tmpPath, localPath);
3753
- const mime = res.headers.get("content-type");
3754
- const now = Date.now();
3755
- this.db.prepare(
3756
- `INSERT OR REPLACE INTO cached_assets
3757
- (content_hash, size_bytes, mime, local_path, fetched_at, last_used_at, pin)
3758
- VALUES (?, ?, ?, ?, ?, ?, 0)`
3759
- ).run(hash, buf.length, mime, localPath, now, now);
3760
- this.evictIfOver();
3761
- return {
3762
- contentHash: hash,
3763
- sizeBytes: buf.length,
3764
- mime,
3765
- localPath,
3766
- fetchedAt: now,
3767
- lastUsedAt: now,
3768
- pin: false
3769
- };
3770
- }
3771
- /** Insert an already-on-disk asset into the cache (e.g., asset just produced by adapter). */
3772
- registerLocal(hash, localPath, mime) {
3773
- const actualHash = sha256(readFileBuffer(localPath));
3774
- if (actualHash !== hash) {
3775
- throw new Error(`Asset hash mismatch for ${hash}: local file is ${actualHash}`);
3537
+ printAssetDetail("Asset", assetId, body);
3538
+ }, { code: "asset_get_failed" }));
3539
+ 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) => {
3540
+ const cloud = mkCloud();
3541
+ const query = new URLSearchParams();
3542
+ if (opts.workspaceId) query.set("wsId", opts.workspaceId);
3543
+ const path7 = `/api/im/assets/by-hash/${encodeURIComponent(sha2562)}${query.toString() ? `?${query.toString()}` : ""}`;
3544
+ const res = await cloud.request("GET", path7);
3545
+ if (!res.ok) exitWithError(`asset by-hash failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_by_hash_failed" });
3546
+ const body = res.data;
3547
+ if (isErrorEnvelope(body)) {
3548
+ exitWithError(`asset by-hash failed (${res.status}): ${errorMessage(body)}`, { code: "asset_by_hash_failed" });
3776
3549
  }
3777
- const size = statSync2(localPath).size;
3778
- const now = Date.now();
3779
- this.db.prepare(
3780
- `INSERT OR REPLACE INTO cached_assets
3781
- (content_hash, size_bytes, mime, local_path, fetched_at, last_used_at, pin)
3782
- VALUES (?, ?, ?, ?, ?, ?, 1)`
3783
- ).run(hash, size, mime ?? null, localPath, now, now);
3784
- return { contentHash: hash, sizeBytes: size, mime: mime ?? null, localPath, fetchedAt: now, lastUsedAt: now, pin: true };
3785
- }
3786
- /** Total bytes across all rows (incl. pinned). */
3787
- totalBytes() {
3788
- const row = this.db.prepare("SELECT COALESCE(SUM(size_bytes),0) AS total FROM cached_assets").get();
3789
- return row.total;
3790
- }
3791
- /** Run LRU eviction until under cap. Pinned rows are never evicted. */
3792
- evictIfOver() {
3793
- let total = this.totalBytes();
3794
- if (total <= this.maxBytes) return { removed: 0, freed: 0 };
3795
- const candidates = this.db.prepare(
3796
- `SELECT content_hash, size_bytes, local_path FROM cached_assets
3797
- WHERE pin = 0
3798
- ORDER BY last_used_at ASC`
3799
- ).all();
3800
- let freed = 0;
3801
- let removed = 0;
3802
- for (const row of candidates) {
3803
- if (total <= this.maxBytes) break;
3804
- try {
3805
- if (existsSync7(row.local_path)) unlinkSync2(row.local_path);
3806
- } catch {
3807
- }
3808
- this.db.prepare("DELETE FROM cached_assets WHERE content_hash = ?").run(row.content_hash);
3809
- freed += row.size_bytes;
3810
- total -= row.size_bytes;
3811
- removed += 1;
3550
+ if (opts.json) {
3551
+ printJson(body ?? null);
3552
+ return;
3812
3553
  }
3813
- return { removed, freed };
3814
- }
3815
- };
3816
- function sha256(buf) {
3817
- return createHash("sha256").update(buf).digest("hex");
3554
+ printAssetDetail("Asset hash", sha2562, body);
3555
+ }, { code: "asset_by_hash_failed" }));
3556
+ return cmd;
3818
3557
  }
3819
- function readFileBuffer(path9) {
3820
- return readFileSync6(path9);
3821
- }
3822
-
3823
- // src/daemon/asset/metadata-index.ts
3824
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
3825
- import { join as join7 } from "path";
3826
- var DEFAULT_LIMIT = 8;
3827
- var PULL_PAGE_SIZE = 500;
3828
- var THROTTLE_MS = 3e4;
3829
- function rowToMetadata(row) {
3830
- return {
3831
- assetId: row.asset_id,
3832
- contentHash: row.content_hash,
3833
- filename: row.filename,
3834
- folderPath: row.folder_path,
3835
- mime: row.mime,
3836
- kind: row.kind,
3837
- sizeBytes: row.size_bytes,
3838
- description: row.description,
3839
- assetIndexSeq: row.asset_index_seq
3840
- };
3841
- }
3842
- var AssetMetadataIndex = class {
3843
- db;
3844
- cloud;
3845
- /** Workspace ID — exposed for prismer:// URI construction. */
3846
- workspaceId;
3847
- cursorPath;
3848
- _lastSyncMs = 0;
3849
- constructor(opts) {
3850
- this.db = opts.db;
3851
- this.cloud = opts.cloud;
3852
- this.workspaceId = opts.workspaceId;
3853
- if (!existsSync8(opts.workspaceStateDir)) {
3854
- mkdirSync6(opts.workspaceStateDir, { recursive: true });
3855
- }
3856
- this.cursorPath = join7(opts.workspaceStateDir, "asset-metadata-cursor.json");
3857
- }
3858
- /** Persisted cursor for this workspace, or 0 on first run / corrupted file. */
3859
- readCursor() {
3860
- if (!existsSync8(this.cursorPath)) return 0;
3861
- try {
3862
- const parsed = JSON.parse(readFileSync7(this.cursorPath, "utf8"));
3863
- if (parsed.workspaceId !== this.workspaceId) return 0;
3864
- return parsed.cursor;
3865
- } catch {
3866
- return 0;
3867
- }
3868
- }
3869
- writeCursor(cursor) {
3870
- const payload = {
3871
- workspaceId: this.workspaceId,
3872
- cursor,
3873
- writtenAt: Date.now()
3874
- };
3875
- writeFileSync6(this.cursorPath, JSON.stringify(payload, null, 2));
3876
- }
3877
- /**
3878
- * Pull incremental asset metadata changes since the persisted cursor and
3879
- * upsert into the local index. Newer rows overwrite older ones by
3880
- * (workspace_id, asset_id) primary key.
3881
- *
3882
- * Returns the count of items applied + the new cursor. Throttled: if called
3883
- * within 30s of the last successful pull, returns immediately.
3884
- */
3885
- async pullDelta(opts) {
3886
- const now = Date.now();
3887
- if (now - this._lastSyncMs < THROTTLE_MS) {
3888
- return { applied: 0, cursor: this.readCursor() };
3889
- }
3890
- const since = this.readCursor();
3891
- const sinceParam = since > 0 ? `&since=${since}` : "";
3892
- const envelope2 = await this.cloud.get(
3893
- `/api/im/assets/index?workspaceId=${encodeURIComponent(this.workspaceId)}&limit=${PULL_PAGE_SIZE}${sinceParam}`,
3894
- { signal: opts?.signal }
3895
- );
3896
- const upsert = this.db.prepare(`
3897
- INSERT INTO asset_metadata_index
3898
- (workspace_id, asset_id, content_hash, filename, folder_path, mime, kind, size_bytes, description, asset_index_seq, updated_at)
3899
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3900
- ON CONFLICT(workspace_id, asset_id) DO UPDATE SET
3901
- content_hash = excluded.content_hash,
3902
- filename = excluded.filename,
3903
- folder_path = excluded.folder_path,
3904
- mime = excluded.mime,
3905
- kind = excluded.kind,
3906
- size_bytes = excluded.size_bytes,
3907
- description = excluded.description,
3908
- asset_index_seq = excluded.asset_index_seq,
3909
- updated_at = excluded.updated_at
3910
- `);
3911
- const nowTs = Date.now();
3912
- let applied = 0;
3913
- const tx = this.db.transaction((items) => {
3914
- for (const item of items) {
3915
- const assetId = item.assetId ?? item.id;
3916
- if (!assetId) {
3917
- throw new Error("Asset metadata index item missing assetId/id");
3918
- }
3919
- upsert.run(
3920
- this.workspaceId,
3921
- assetId,
3922
- item.contentHash,
3923
- item.filename ?? null,
3924
- item.folderPath ?? null,
3925
- item.mime,
3926
- item.kind,
3927
- item.sizeBytes,
3928
- item.description ?? null,
3929
- item.assetIndexSeq,
3930
- nowTs
3931
- );
3932
- applied += 1;
3933
- }
3934
- });
3935
- try {
3936
- tx(envelope2.items);
3937
- this.writeCursor(envelope2.cursor);
3938
- } catch (err) {
3939
- throw err;
3940
- }
3941
- this._lastSyncMs = now;
3942
- return { applied, cursor: envelope2.cursor };
3943
- }
3944
- /**
3945
- * Search local index by filename or description substring.
3946
- * Escapes LIKE wildcards (% and _). Default limit 8.
3947
- */
3948
- search(query, limit) {
3949
- const escaped = query.replace(/%/g, "\\%").replace(/_/g, "\\_");
3950
- const pattern = `%${escaped}%`;
3951
- const limitVal = Math.min(Math.max(limit ?? DEFAULT_LIMIT, 1), 200);
3952
- const rows = this.db.prepare(
3953
- `SELECT * FROM asset_metadata_index
3954
- WHERE workspace_id = ?
3955
- AND (filename LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\')
3956
- ORDER BY asset_index_seq DESC
3957
- LIMIT ?`
3958
- ).all(this.workspaceId, pattern, pattern, limitVal);
3959
- return rows.map(rowToMetadata);
3960
- }
3961
- /** Exact match on filename column. Returns undefined if not indexed. */
3962
- resolveByFilename(filename) {
3963
- const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND filename = ?").get(this.workspaceId, filename);
3964
- return row ? rowToMetadata(row) : void 0;
3965
- }
3966
- resolveByAssetId(assetId) {
3967
- const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND asset_id = ?").get(this.workspaceId, assetId);
3968
- return row ? rowToMetadata(row) : void 0;
3969
- }
3970
- resolveByContentHash(contentHash) {
3971
- const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND content_hash = ?").get(this.workspaceId, contentHash);
3972
- return row ? rowToMetadata(row) : void 0;
3973
- }
3974
- /** Batch exact match — returns Map for O(1) access. */
3975
- resolveByFilenames(filenames) {
3976
- if (filenames.length === 0) return /* @__PURE__ */ new Map();
3977
- const placeholders = filenames.map(() => "?").join(",");
3978
- const params = [this.workspaceId, ...filenames];
3979
- const rows = this.db.prepare(
3980
- `SELECT * FROM asset_metadata_index
3981
- WHERE workspace_id = ? AND filename IN (${placeholders})`
3982
- ).all(...params);
3983
- const map = /* @__PURE__ */ new Map();
3984
- for (const row of rows) {
3985
- if (row.filename) map.set(row.filename, rowToMetadata(row));
3986
- }
3987
- return map;
3988
- }
3989
- };
3990
-
3991
- // src/daemon/asset/mirror.ts
3992
- import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
3993
- import { join as join8 } from "path";
3994
- function rowToBinding(row) {
3995
- return {
3996
- workspaceId: row.workspace_id,
3997
- path: row.path,
3998
- assetId: row.asset_id,
3999
- contentHash: row.content_hash,
4000
- version: row.version,
4001
- syncedAt: row.synced_at,
4002
- dirty: row.dirty === 1
4003
- };
4004
- }
4005
- var WorkspaceMirror = class {
4006
- db;
4007
- cloud;
4008
- workspaceId;
4009
- cursorPath;
4010
- constructor(opts) {
4011
- this.db = opts.db;
4012
- this.cloud = opts.cloud;
4013
- this.workspaceId = opts.workspaceId;
4014
- if (!existsSync9(opts.workspaceStateDir)) {
4015
- mkdirSync7(opts.workspaceStateDir, { recursive: true });
4016
- }
4017
- this.cursorPath = join8(opts.workspaceStateDir, "asset-cursor.json");
4018
- }
4019
- /** Persisted cursor for this workspace, or null on first run / corrupted file. */
4020
- readCursor() {
4021
- if (!existsSync9(this.cursorPath)) return null;
4022
- try {
4023
- const parsed = JSON.parse(readFileSync8(this.cursorPath, "utf8"));
4024
- if (parsed.workspaceId !== this.workspaceId) return null;
4025
- return parsed.cursor;
4026
- } catch {
4027
- return null;
4028
- }
4029
- }
4030
- writeCursor(cursor) {
4031
- const payload = {
4032
- workspaceId: this.workspaceId,
4033
- cursor,
4034
- writtenAt: Date.now()
4035
- };
4036
- writeFileSync7(this.cursorPath, JSON.stringify(payload, null, 2));
4037
- }
4038
- /**
4039
- * Pull workspace_file changes since the persisted cursor and upsert into
4040
- * the local mirror. Soft-deleted rows are removed from the mirror; active
4041
- * rows are upserted with synced_at = now and dirty = 0.
4042
- *
4043
- * Returns the count of items applied + the new cursor (may be unchanged
4044
- * when the cloud has nothing new). Idempotent — safe to retry.
4045
- */
4046
- async pullDelta(opts) {
4047
- const since = this.readCursor();
4048
- const sinceParam = since ? `?since=${encodeURIComponent(since)}` : "";
4049
- const envelope2 = await this.cloud.get(
4050
- `/api/im/workspaces/${encodeURIComponent(this.workspaceId)}/files/sync${sinceParam}`,
4051
- { signal: opts?.signal }
4052
- );
4053
- const upsert = this.db.prepare(`
4054
- INSERT INTO workspace_files_mirror (workspace_id, path, asset_id, content_hash, version, synced_at, dirty)
4055
- VALUES (?, ?, ?, ?, ?, ?, 0)
4056
- ON CONFLICT(workspace_id, path) DO UPDATE SET
4057
- asset_id = excluded.asset_id,
4058
- content_hash = excluded.content_hash,
4059
- version = excluded.version,
4060
- synced_at = excluded.synced_at,
4061
- dirty = 0
4062
- `);
4063
- const remove = this.db.prepare(
4064
- "DELETE FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?"
4065
- );
4066
- const now = Date.now();
4067
- let applied = 0;
4068
- const tx = this.db.transaction((items) => {
4069
- for (const item of items) {
4070
- if (item.workspaceId !== this.workspaceId) continue;
4071
- if (item.deletedAt) {
4072
- remove.run(this.workspaceId, item.path);
4073
- } else if (item.contentHash) {
4074
- upsert.run(this.workspaceId, item.path, item.assetId, item.contentHash, item.version, now);
4075
- } else {
4076
- remove.run(this.workspaceId, item.path);
4077
- }
4078
- applied += 1;
4079
- }
4080
- });
4081
- tx(envelope2.items);
4082
- this.writeCursor(envelope2.cursor);
4083
- return { applied, cursor: envelope2.cursor };
4084
- }
4085
- /** Lookup the active binding for a workspace path. Returns undefined if not mirrored. */
4086
- lookupByPath(path9) {
4087
- const row = this.db.prepare("SELECT * FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?").get(this.workspaceId, path9);
4088
- return row ? rowToBinding(row) : void 0;
4089
- }
4090
- /** Find an asset's contentHash via any path binding (asset may map to many paths). */
4091
- hashForAssetId(assetId) {
4092
- const row = this.db.prepare(
4093
- "SELECT content_hash FROM workspace_files_mirror WHERE workspace_id = ? AND asset_id = ? LIMIT 1"
4094
- ).get(this.workspaceId, assetId);
4095
- return row?.content_hash;
4096
- }
4097
- /** All active bindings for this workspace — used by status / debug surfaces. */
4098
- list() {
4099
- const rows = this.db.prepare("SELECT * FROM workspace_files_mirror WHERE workspace_id = ? ORDER BY path ASC").all(this.workspaceId);
4100
- return rows.map(rowToBinding);
4101
- }
4102
- /** Wipe the mirror for this workspace (e.g., re-pair). Cursor file is left intact. */
4103
- clear() {
4104
- this.db.prepare("DELETE FROM workspace_files_mirror WHERE workspace_id = ?").run(this.workspaceId);
4105
- }
4106
- };
4107
-
4108
- // src/cli/commands/asset.ts
4109
- init_util();
4110
- init_ui();
4111
- function buildAssetCommand() {
4112
- const cmd = new Command3("asset").description("Inspect IM assets");
4113
- 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) => {
4114
- const result = await syncAssets(opts);
4115
- if (opts.json) {
4116
- printJson({ ok: true, data: result });
4117
- return;
4118
- }
4119
- getUI().line(`Synced asset metadata: ${result.metadata.applied} applied (cursor=${result.metadata.cursor})`);
4120
- getUI().line(`Synced workspace files: ${result.workspaceFiles.applied} applied (cursor=${result.workspaceFiles.cursor ?? "null"})`);
4121
- if (opts.bytes) {
4122
- getUI().line(
4123
- `Prefetched bytes: ${result.prefetch.fetched}/${result.prefetch.considered} assets, ${result.prefetch.bytes} bytes`
4124
- );
4125
- if (result.prefetch.failed.length > 0) {
4126
- getUI().line(`Prefetch failures: ${result.prefetch.failed.length}`);
4127
- }
4128
- } else {
4129
- getUI().line("Asset bytes were not prefetched; they are downloaded lazily on first use. Run with --bytes to fill ~/.prismer/cache.");
4130
- }
4131
- }, { code: "asset_sync_failed" }));
4132
- 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) => {
4133
- const cloud = mkCloud();
4134
- const query = new URLSearchParams();
4135
- if (opts.workspaceId) query.set("workspaceId", opts.workspaceId);
4136
- if (opts.taskId) query.set("taskId", opts.taskId);
4137
- const path9 = `/api/im/assets${query.toString() ? `?${query.toString()}` : ""}`;
4138
- const res = await cloud.request("GET", path9);
4139
- if (!res.ok) exitWithError(`asset list failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_list_failed" });
4140
- const body = res.data;
4141
- if (isErrorEnvelope(body)) {
4142
- exitWithError(`asset list failed (${res.status}): ${errorMessage(body)}`, { code: "asset_list_failed" });
4143
- }
4144
- if (opts.json) {
4145
- printJson(body ?? null);
4146
- return;
4147
- }
4148
- printAssetList(body);
4149
- }, { code: "asset_list_failed" }));
4150
- 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) => {
4151
- const body = await uploadAssetPath(inputPath, opts);
4152
- if (opts.json) {
4153
- printJson(body ?? null);
4154
- return;
4155
- }
4156
- if (isUploadManyResult(body)) {
4157
- getUI().line(`Uploaded ${body.data.count} assets`);
4158
- for (const item of body.data.items) {
4159
- printAssetDetail("Uploaded asset", item.file, item.response);
4160
- }
4161
- } else {
4162
- printAssetDetail("Uploaded asset", inputPath, body);
4163
- }
4164
- }, { code: "asset_upload_failed" }));
4165
- cmd.command("download <assetId>").description("Download asset bytes").requiredOption("--out <path>", "Destination file path").action(runAction(async (assetId, opts) => {
4166
- await downloadAsset(assetId, opts.out);
4167
- getUI().line(`Downloaded ${assetId} -> ${opts.out}`);
4168
- }, { code: "asset_download_failed" }));
4169
- cmd.command("get <assetId>").description("Fetch asset metadata by id").option("--json", "Output machine-readable JSON").action(runAction(async (assetId, opts) => {
4170
- const cloud = mkCloud();
4171
- const res = await cloud.request("GET", `/api/im/assets/${encodeURIComponent(assetId)}/detail`);
4172
- if (!res.ok) exitWithError(`asset get failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_get_failed" });
4173
- const body = res.data;
4174
- if (isErrorEnvelope(body)) {
4175
- exitWithError(`asset get failed (${res.status}): ${errorMessage(body)}`, { code: "asset_get_failed" });
4176
- }
4177
- if (opts.json) {
4178
- printJson(body ?? null);
4179
- return;
4180
- }
4181
- printAssetDetail("Asset", assetId, body);
4182
- }, { code: "asset_get_failed" }));
4183
- 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) => {
4184
- const cloud = mkCloud();
4185
- const query = new URLSearchParams();
4186
- if (opts.workspaceId) query.set("wsId", opts.workspaceId);
4187
- const path9 = `/api/im/assets/by-hash/${encodeURIComponent(sha2564)}${query.toString() ? `?${query.toString()}` : ""}`;
4188
- const res = await cloud.request("GET", path9);
4189
- if (!res.ok) exitWithError(`asset by-hash failed (${res.status}): ${res.error?.message ?? "request failed"}`, { code: res.error?.code ?? "asset_by_hash_failed" });
4190
- const body = res.data;
4191
- if (isErrorEnvelope(body)) {
4192
- exitWithError(`asset by-hash failed (${res.status}): ${errorMessage(body)}`, { code: "asset_by_hash_failed" });
4193
- }
4194
- if (opts.json) {
4195
- printJson(body ?? null);
4196
- return;
4197
- }
4198
- printAssetDetail("Asset hash", sha2564, body);
4199
- }, { code: "asset_by_hash_failed" }));
4200
- return cmd;
4201
- }
4202
- function mkCloud() {
4203
- const cfg = loadConfig(resolvePaths());
4204
- return new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
4205
- }
4206
- async function syncAssets(opts) {
4207
- const paths = resolvePaths();
4208
- const cfg = loadConfig(paths);
4209
- const cloud = new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
4210
- const db = openLocalDb(paths.localDb);
4211
- try {
4212
- const stateDir = `${paths.root}/${opts.workspaceId}`;
4213
- const metadataIndex = new AssetMetadataIndex({
4214
- db,
4215
- cloud,
4216
- workspaceId: opts.workspaceId,
4217
- workspaceStateDir: stateDir
4218
- });
4219
- const mirror = new WorkspaceMirror({
4220
- db,
4221
- cloud,
4222
- workspaceId: opts.workspaceId,
4223
- workspaceStateDir: stateDir
4224
- });
4225
- const [metadata, workspaceFiles] = await Promise.all([
4226
- metadataIndex.pullDelta(),
4227
- mirror.pullDelta()
4228
- ]);
4229
- const prefetch = opts.bytes ? await prefetchAssetBytes({
4230
- db,
4231
- cloud,
4232
- workspaceId: opts.workspaceId,
4233
- cacheDir: paths.cacheDir,
4234
- maxBytes: cfg.cache?.max_bytes,
4235
- limit: opts.limit
4236
- }) : { considered: 0, fetched: 0, bytes: 0, failed: [] };
4237
- return { workspaceId: opts.workspaceId, metadata, workspaceFiles, prefetch };
4238
- } finally {
4239
- db.close();
4240
- }
4241
- }
4242
- async function prefetchAssetBytes(opts) {
4243
- const limitClause = opts.limit ? " LIMIT ?" : "";
4244
- const params = [opts.workspaceId];
4245
- if (opts.limit) params.push(opts.limit);
4246
- const rows = opts.db.prepare(
4247
- `SELECT asset_id, content_hash, mime, size_bytes
4248
- FROM asset_metadata_index
4249
- WHERE workspace_id = ?
4250
- ORDER BY asset_index_seq DESC${limitClause}`
4251
- ).all(...params);
4252
- const cache = new AssetCache({
4253
- db: opts.db,
4254
- cloud: opts.cloud,
4255
- cacheDir: opts.cacheDir,
4256
- maxBytes: opts.maxBytes
4257
- });
4258
- let fetched = 0;
4259
- let bytes = 0;
4260
- const failed = [];
4261
- for (const row of rows) {
4262
- try {
4263
- const cached = await cache.getOrFetch(row.content_hash, {
4264
- workspaceIdHint: opts.workspaceId,
4265
- assetId: row.asset_id
4266
- });
4267
- fetched += 1;
4268
- bytes += cached.sizeBytes;
4269
- } catch (err) {
4270
- failed.push({
4271
- assetId: row.asset_id,
4272
- contentHash: row.content_hash,
4273
- error: err.message
4274
- });
4275
- }
4276
- }
4277
- return { considered: rows.length, fetched, bytes, failed };
4278
- }
4279
- async function uploadAssetPath(inputPath, opts) {
4280
- if (!existsSync10(inputPath)) exitWithError(`path not found: ${inputPath}`);
4281
- const stat = lstatSync(inputPath);
4282
- if (stat.isSymbolicLink()) exitWithError(`refusing to upload symlink: ${inputPath}`);
4283
- if (stat.isFile()) {
4284
- return uploadAsset(inputPath, { ...opts, folderPath: normalizeFolderPath(opts.folderPath) });
4285
- }
4286
- if (!stat.isDirectory()) exitWithError(`path is not a file or directory: ${inputPath}`);
4287
- const files = listFilesRecursive(inputPath);
4288
- if (files.length === 0) exitWithError(`directory has no uploadable files: ${inputPath}`);
4289
- const items = [];
4290
- for (const file of files) {
4291
- const relDir = dirname7(relative(inputPath, file));
4292
- const folderPath = combineFolderPath(opts.folderPath, relDir === "." ? "" : relDir);
4293
- const response = await uploadAsset(file, { ...opts, folderPath });
4294
- items.push({ file, folderPath: folderPath ?? null, response });
4295
- }
4296
- return { ok: true, data: { count: items.length, items } };
3558
+ function mkCloud() {
3559
+ const cfg = loadConfig(resolvePaths());
3560
+ return new CloudClient({ baseUrl: cfg.cloud_api_base, apiKey: cfg.api_key });
4297
3561
  }
4298
3562
  async function uploadAsset(file, opts) {
4299
- if (!existsSync10(file)) exitWithError(`file not found: ${file}`);
3563
+ if (!existsSync7(file)) exitWithError(`file not found: ${file}`);
4300
3564
  const cfg = loadConfig(resolvePaths());
4301
- const bytes = readFileSync9(file);
3565
+ const bytes = readFileSync6(file);
4302
3566
  const metadata = parseMetadata(opts.metadata);
4303
3567
  if (opts.taskId && metadata.taskId === void 0) metadata.taskId = opts.taskId;
4304
3568
  if (opts.containerId && metadata.containerId === void 0) metadata.containerId = opts.containerId;
@@ -4307,7 +3571,6 @@ async function uploadAsset(file, opts) {
4307
3571
  form.set("kind", opts.kind);
4308
3572
  if (opts.agentId) form.set("sourceAgentImUserId", opts.agentId);
4309
3573
  if (opts.taskId) form.set("sourceTaskId", opts.taskId);
4310
- if (opts.folderPath !== void 0) form.set("folderPath", opts.folderPath);
4311
3574
  if (Object.keys(metadata).length > 0) form.set("metadata", JSON.stringify(metadata));
4312
3575
  form.set("file", new Blob([bytes], { type: opts.mime ?? "application/octet-stream" }), basename(file));
4313
3576
  const url = `${cfg.cloud_api_base.replace(/\/$/, "")}/api/im/assets`;
@@ -4327,37 +3590,6 @@ async function uploadAsset(file, opts) {
4327
3590
  }
4328
3591
  return body;
4329
3592
  }
4330
- function listFilesRecursive(root) {
4331
- const out = [];
4332
- for (const name of readdirSync(root)) {
4333
- if (name.startsWith(".")) continue;
4334
- const full = join9(root, name);
4335
- const stat = lstatSync(full);
4336
- if (stat.isSymbolicLink()) continue;
4337
- if (stat.isDirectory()) {
4338
- out.push(...listFilesRecursive(full));
4339
- } else if (stat.isFile()) {
4340
- out.push(full);
4341
- }
4342
- }
4343
- return out.sort();
4344
- }
4345
- function combineFolderPath(prefix, relDir) {
4346
- const normalizedPrefix = normalizeFolderPath(prefix);
4347
- const normalizedRel = normalizeFolderPath(relDir);
4348
- if (normalizedPrefix && normalizedRel) return `${normalizedPrefix}/${normalizedRel}`;
4349
- return normalizedPrefix ?? normalizedRel;
4350
- }
4351
- function normalizeFolderPath(value) {
4352
- if (value === void 0) return void 0;
4353
- const normalized = value.trim().replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
4354
- return normalized.length > 0 ? normalized : "";
4355
- }
4356
- function isUploadManyResult(raw) {
4357
- return Boolean(
4358
- raw && typeof raw === "object" && raw.ok === true && raw.data && Array.isArray(raw.data.items)
4359
- );
4360
- }
4361
3593
  async function downloadAsset(assetId, outPath) {
4362
3594
  const cloud = mkCloud();
4363
3595
  const res = await cloud.fetchRaw(`/api/im/assets/${encodeURIComponent(assetId)}`);
@@ -4366,7 +3598,7 @@ async function downloadAsset(assetId, outPath) {
4366
3598
  exitWithError(`asset download failed (${res.status}): ${errorMessage(body)}`);
4367
3599
  }
4368
3600
  const bytes = Buffer.from(await res.arrayBuffer());
4369
- writeFileSync8(outPath, bytes);
3601
+ writeFileSync5(outPath, bytes);
4370
3602
  }
4371
3603
  function parseMetadata(raw) {
4372
3604
  if (!raw) return {};
@@ -4451,13 +3683,6 @@ function stringField(obj, key) {
4451
3683
  const value = obj?.[key];
4452
3684
  return typeof value === "string" && value.length > 0 ? value : void 0;
4453
3685
  }
4454
- function parsePositiveInt(raw) {
4455
- const n = Number(raw);
4456
- if (!Number.isInteger(n) || n <= 0) {
4457
- throw new Error("--limit must be a positive integer");
4458
- }
4459
- return n;
4460
- }
4461
3686
 
4462
3687
  // src/cli/commands/banner.ts
4463
3688
  init_util();
@@ -4513,7 +3738,7 @@ ${name} (${role})`);
4513
3738
  getUI().line(`Group created ${data.groupId ?? "(unknown)"}
4514
3739
  ${data.title ?? opts.name} (${memberCount} members)`);
4515
3740
  });
4516
- 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) => {
3741
+ 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) => {
4517
3742
  const data = await requestOrExit(
4518
3743
  "GET",
4519
3744
  withQuery(`/api/im/groups/${encodeURIComponent(groupId)}/messages`, {
@@ -4551,7 +3776,7 @@ ${data.title ?? opts.name} (${memberCount} members)`);
4551
3776
  getUI().line(`Removed ${memberId} from group ${groupId}`);
4552
3777
  });
4553
3778
  cmd.addCommand(group);
4554
- 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) => {
3779
+ 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) => {
4555
3780
  const data = await requestOrExit(
4556
3781
  "GET",
4557
3782
  withQuery(`/api/im/messages/${encodeURIComponent(conversationId)}`, {
@@ -4567,15 +3792,15 @@ ${data.title ?? opts.name} (${memberCount} members)`);
4567
3792
  });
4568
3793
  return cmd;
4569
3794
  }
4570
- async function requestOrExit(method, path9, body) {
3795
+ async function requestOrExit(method, path7, body) {
4571
3796
  let res;
4572
3797
  try {
4573
- res = await mkCloud2().request(method, path9, body === void 0 ? void 0 : { body });
3798
+ res = await mkCloud2().request(method, path7, body === void 0 ? void 0 : { body });
4574
3799
  } catch (err) {
4575
3800
  exitWithError(sanitizeError(err.message));
4576
3801
  }
4577
3802
  if (!res.ok) {
4578
- exitWithError(`${method} ${path9} failed (${res.status}): ${sanitizeError(res.error?.message ?? "unknown")}`);
3803
+ exitWithError(`${method} ${path7} failed (${res.status}): ${sanitizeError(res.error?.message ?? "unknown")}`);
4579
3804
  }
4580
3805
  return unwrapEnvelope(res.data);
4581
3806
  }
@@ -4607,20 +3832,20 @@ function unwrapEnvelope(raw) {
4607
3832
  function parseCsv(value) {
4608
3833
  return value.split(",").map((v) => v.trim()).filter(Boolean);
4609
3834
  }
4610
- function parsePositiveInt2(value) {
3835
+ function parsePositiveInt(value) {
4611
3836
  const parsed = Number.parseInt(value, 10);
4612
3837
  if (!Number.isInteger(parsed) || parsed < 1) {
4613
3838
  throw new Error("expected a positive integer");
4614
3839
  }
4615
3840
  return parsed;
4616
3841
  }
4617
- function withQuery(path9, params) {
3842
+ function withQuery(path7, params) {
4618
3843
  const query = new URLSearchParams();
4619
3844
  for (const [key, value] of Object.entries(params)) {
4620
3845
  if (value !== void 0) query.set(key, String(value));
4621
3846
  }
4622
3847
  const suffix = query.toString();
4623
- return suffix ? `${path9}?${suffix}` : path9;
3848
+ return suffix ? `${path7}?${suffix}` : path7;
4624
3849
  }
4625
3850
  function printMessageSummary(label, data) {
4626
3851
  const msg = data.message ?? {};
@@ -4747,7 +3972,7 @@ init_util();
4747
3972
  init_ui();
4748
3973
  function buildCookbookCommand() {
4749
3974
  const cmd = new Command7("cookbook").description("Run CLI-only 54release MVP regression suites");
4750
- 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) => {
3975
+ 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) => {
4751
3976
  const suites = parseSuites(opts.suite);
4752
3977
  const cloud = mkCloud3(opts.timeoutMs);
4753
3978
  const checks = [];
@@ -4789,8 +4014,8 @@ async function runStatusSuite(cloud) {
4789
4014
  await check("status", "cloud health", "GET", "/api/health"),
4790
4015
  await check("status", "current IM identity", "GET", "/api/im/me", void 0, cloud)
4791
4016
  ];
4792
- async function check(suite, name, method, path9, body, client = cloud) {
4793
- return requestCheck(client, suite, name, method, path9, body);
4017
+ async function check(suite, name, method, path7, body, client = cloud) {
4018
+ return requestCheck(client, suite, name, method, path7, body);
4794
4019
  }
4795
4020
  }
4796
4021
  async function runIMSuite(cloud) {
@@ -4864,27 +4089,27 @@ async function runSandboxSuite(cloud, opts) {
4864
4089
  }
4865
4090
  return checks;
4866
4091
  }
4867
- async function requestCheck(cloud, suite, name, method, path9, body) {
4092
+ async function requestCheck(cloud, suite, name, method, path7, body) {
4868
4093
  let res;
4869
4094
  try {
4870
- res = await cloud.request(method, path9, body === void 0 ? void 0 : { body });
4095
+ res = await cloud.request(method, path7, body === void 0 ? void 0 : { body });
4871
4096
  } catch (err) {
4872
- return { suite, name, status: "fail", endpoint: path9, detail: sanitize(err.message) };
4097
+ return { suite, name, status: "fail", endpoint: path7, detail: sanitize(err.message) };
4873
4098
  }
4874
4099
  if (!res.ok) {
4875
4100
  return {
4876
4101
  suite,
4877
4102
  name,
4878
4103
  status: "fail",
4879
- endpoint: path9,
4104
+ endpoint: path7,
4880
4105
  detail: sanitize(`${res.status}: ${res.error?.message ?? "request failed"}`)
4881
4106
  };
4882
4107
  }
4883
4108
  const data = unwrap(res.data);
4884
4109
  if (isApiError(res.data)) {
4885
- return { suite, name, status: "fail", endpoint: path9, detail: sanitize(apiErrorMessage(res.data)), data: res.data };
4110
+ return { suite, name, status: "fail", endpoint: path7, detail: sanitize(apiErrorMessage(res.data)), data: res.data };
4886
4111
  }
4887
- return { suite, name, status: "pass", endpoint: path9, detail: summarizeData(data), data };
4112
+ return { suite, name, status: "pass", endpoint: path7, detail: summarizeData(data), data };
4888
4113
  }
4889
4114
  async function pollTask(cloud, taskId, timeoutMs) {
4890
4115
  const started = Date.now();
@@ -4991,7 +4216,7 @@ function taskFrom(data) {
4991
4216
  function sanitize(message) {
4992
4217
  return message.replace(/sk-prismer-[A-Za-z0-9_-]+/g, "sk-prismer-***");
4993
4218
  }
4994
- function parsePositiveInt3(value) {
4219
+ function parsePositiveInt2(value) {
4995
4220
  const parsed = Number.parseInt(value, 10);
4996
4221
  if (!Number.isFinite(parsed) || parsed <= 0) {
4997
4222
  throw new Error("expected a positive integer");
@@ -5002,16 +4227,14 @@ function parsePositiveInt3(value) {
5002
4227
  // src/cli/commands/daemon.ts
5003
4228
  import { Command as Command8 } from "commander";
5004
4229
  import { spawn as spawn5 } from "child_process";
5005
- import { createReadStream, existsSync as existsSync14, mkdirSync as mkdirSync9, openSync as openSync2, statSync as statSync4 } from "fs";
5006
- import { setTimeout as sleep2 } from "timers/promises";
5007
- import { join as join17 } from "path";
4230
+ import { createReadStream, existsSync as existsSync13, mkdirSync as mkdirSync8, openSync, statSync as statSync3 } from "fs";
4231
+ import { setTimeout as sleep } from "timers/promises";
4232
+ import { join as join11 } from "path";
5008
4233
 
5009
4234
  // src/daemon/runner.ts
5010
4235
  import { EventEmitter as EventEmitter3 } from "events";
5011
- import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
5012
- import { mkdir } from "fs/promises";
5013
- import { homedir as homedir7, platform } from "os";
5014
- import { join as join16 } from "path";
4236
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
4237
+ import { platform } from "os";
5015
4238
 
5016
4239
  // src/adapters/registry.ts
5017
4240
  var AdapterRegistry = class {
@@ -5038,34 +4261,193 @@ var AdapterRegistry = class {
5038
4261
  get(name) {
5039
4262
  return this.adapters.get(name);
5040
4263
  }
5041
- list() {
5042
- return Array.from(this.adapters.values());
4264
+ list() {
4265
+ return Array.from(this.adapters.values());
4266
+ }
4267
+ size() {
4268
+ return this.adapters.size;
4269
+ }
4270
+ /**
4271
+ * Find adapters that satisfy a capability tag.
4272
+ *
4273
+ * Match rules (deterministic, sorted by name):
4274
+ * - exact: adapter declared the tag verbatim
4275
+ * - wildcard: adapter declared `code.*` → matches `code.write`
4276
+ */
4277
+ findByCapability(capability) {
4278
+ const matches2 = [];
4279
+ for (const adapter of this.adapters.values()) {
4280
+ if (adapter.capabilities.includes(capability)) {
4281
+ matches2.push(adapter);
4282
+ continue;
4283
+ }
4284
+ if (adapter.capabilities.some(
4285
+ (tag) => tag.endsWith(".*") && capability.startsWith(tag.slice(0, -1))
4286
+ )) {
4287
+ matches2.push(adapter);
4288
+ }
4289
+ }
4290
+ matches2.sort((a, b) => a.name.localeCompare(b.name));
4291
+ return matches2;
4292
+ }
4293
+ };
4294
+
4295
+ // src/asset-cache.ts
4296
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
4297
+ import { dirname as dirname6, join as join5 } from "path";
4298
+ function rowToCached(row) {
4299
+ return {
4300
+ contentHash: row.content_hash,
4301
+ sizeBytes: row.size_bytes,
4302
+ mime: row.mime,
4303
+ localPath: row.local_path,
4304
+ fetchedAt: row.fetched_at,
4305
+ lastUsedAt: row.last_used_at,
4306
+ pin: row.pin === 1
4307
+ };
4308
+ }
4309
+ var AssetCache = class {
4310
+ db;
4311
+ cloud;
4312
+ cacheDir;
4313
+ maxBytes;
4314
+ constructor(opts) {
4315
+ this.db = opts.db;
4316
+ this.cloud = opts.cloud;
4317
+ this.cacheDir = opts.cacheDir;
4318
+ this.maxBytes = opts.maxBytes ?? 5 * 1024 * 1024 * 1024;
4319
+ if (!existsSync8(this.cacheDir)) {
4320
+ mkdirSync5(this.cacheDir, { recursive: true });
4321
+ }
4322
+ }
4323
+ /** Local file path for a content hash. Files split by first 2 chars for fs scaling. */
4324
+ pathFor(hash) {
4325
+ return join5(this.cacheDir, hash.slice(0, 2), hash);
4326
+ }
4327
+ get(hash) {
4328
+ const row = this.db.prepare("SELECT * FROM cached_assets WHERE content_hash = ?").get(hash);
4329
+ if (!row) return void 0;
4330
+ if (!existsSync8(row.local_path)) {
4331
+ this.db.prepare("DELETE FROM cached_assets WHERE content_hash = ?").run(hash);
4332
+ return void 0;
4333
+ }
4334
+ this.touch(hash);
4335
+ return rowToCached({ ...row, last_used_at: Date.now() });
4336
+ }
4337
+ /** Bump `last_used_at` (call when an asset is consulted). */
4338
+ touch(hash) {
4339
+ this.db.prepare("UPDATE cached_assets SET last_used_at = ? WHERE content_hash = ?").run(Date.now(), hash);
4340
+ }
4341
+ pin(hash) {
4342
+ this.db.prepare("UPDATE cached_assets SET pin = 1 WHERE content_hash = ?").run(hash);
4343
+ }
4344
+ unpin(hash) {
4345
+ this.db.prepare("UPDATE cached_assets SET pin = 0 WHERE content_hash = ?").run(hash);
4346
+ }
4347
+ /**
4348
+ * Get or fetch by hash. On miss, downloads the raw bytes and caches them.
4349
+ *
4350
+ * Why two endpoints: `GET /api/im/assets/by-hash/:hash` returns the JSON
4351
+ * `AssetDTO` (id, contentHash, storageUri, …) — *metadata only*. The raw
4352
+ * bytes live at `GET /api/im/assets/:id`. If `opts.assetId` is supplied
4353
+ * the caller already has the id from an `AssetRef`, so we go straight to
4354
+ * the byte endpoint; otherwise we resolve `hash → id` first (one extra
4355
+ * round-trip on the metadata route) before downloading the body.
4356
+ *
4357
+ * Earlier this method called `by-hash` and treated the JSON body as the
4358
+ * asset content — the daemon stored the 500-byte metadata blob on disk
4359
+ * and `resolveAssetRefs` inlined that JSON into the prompt. The agent
4360
+ * saw `{"id":"…","storageUri":"…"}` instead of the user's file and
4361
+ * concluded the upload had failed.
4362
+ */
4363
+ async getOrFetch(hash, opts) {
4364
+ const existing = this.get(hash);
4365
+ if (existing) return existing;
4366
+ const wsParam = opts?.workspaceIdHint ? `?wsId=${encodeURIComponent(opts.workspaceIdHint)}` : "";
4367
+ let assetId = opts?.assetId;
4368
+ if (!assetId) {
4369
+ const metaRes = await this.cloud.fetchRaw(
4370
+ `/api/im/assets/by-hash/${encodeURIComponent(hash)}${wsParam}`,
4371
+ { signal: opts?.signal }
4372
+ );
4373
+ if (!metaRes.ok) {
4374
+ throw new Error(`Asset metadata fetch failed for ${hash}: HTTP ${metaRes.status}`);
4375
+ }
4376
+ const metaJson = await metaRes.json();
4377
+ if (!metaJson?.ok || typeof metaJson.data?.id !== "string") {
4378
+ throw new Error(`Asset metadata payload missing id for ${hash}`);
4379
+ }
4380
+ assetId = metaJson.data.id;
4381
+ }
4382
+ const res = await this.cloud.fetchRaw(`/api/im/assets/${encodeURIComponent(assetId)}`, {
4383
+ signal: opts?.signal
4384
+ });
4385
+ if (!res.ok) {
4386
+ throw new Error(`Asset bytes fetch failed for ${hash} (id=${assetId}): HTTP ${res.status}`);
4387
+ }
4388
+ const buf = Buffer.from(await res.arrayBuffer());
4389
+ const localPath = this.pathFor(hash);
4390
+ if (!existsSync8(dirname6(localPath))) {
4391
+ mkdirSync5(dirname6(localPath), { recursive: true });
4392
+ }
4393
+ writeFileSync6(localPath, buf);
4394
+ const mime = res.headers.get("content-type");
4395
+ const now = Date.now();
4396
+ this.db.prepare(
4397
+ `INSERT OR REPLACE INTO cached_assets
4398
+ (content_hash, size_bytes, mime, local_path, fetched_at, last_used_at, pin)
4399
+ VALUES (?, ?, ?, ?, ?, ?, 0)`
4400
+ ).run(hash, buf.length, mime, localPath, now, now);
4401
+ this.evictIfOver();
4402
+ return {
4403
+ contentHash: hash,
4404
+ sizeBytes: buf.length,
4405
+ mime,
4406
+ localPath,
4407
+ fetchedAt: now,
4408
+ lastUsedAt: now,
4409
+ pin: false
4410
+ };
4411
+ }
4412
+ /** Insert an already-on-disk asset into the cache (e.g., asset just produced by adapter). */
4413
+ registerLocal(hash, localPath, mime) {
4414
+ const size = statSync2(localPath).size;
4415
+ const now = Date.now();
4416
+ this.db.prepare(
4417
+ `INSERT OR REPLACE INTO cached_assets
4418
+ (content_hash, size_bytes, mime, local_path, fetched_at, last_used_at, pin)
4419
+ VALUES (?, ?, ?, ?, ?, ?, 1)`
4420
+ ).run(hash, size, mime ?? null, localPath, now, now);
4421
+ return { contentHash: hash, sizeBytes: size, mime: mime ?? null, localPath, fetchedAt: now, lastUsedAt: now, pin: true };
5043
4422
  }
5044
- size() {
5045
- return this.adapters.size;
4423
+ /** Total bytes across all rows (incl. pinned). */
4424
+ totalBytes() {
4425
+ const row = this.db.prepare("SELECT COALESCE(SUM(size_bytes),0) AS total FROM cached_assets").get();
4426
+ return row.total;
5046
4427
  }
5047
- /**
5048
- * Find adapters that satisfy a capability tag.
5049
- *
5050
- * Match rules (deterministic, sorted by name):
5051
- * - exact: adapter declared the tag verbatim
5052
- * - wildcard: adapter declared `code.*` → matches `code.write`
5053
- */
5054
- findByCapability(capability) {
5055
- const matches2 = [];
5056
- for (const adapter of this.adapters.values()) {
5057
- if (adapter.capabilities.includes(capability)) {
5058
- matches2.push(adapter);
5059
- continue;
5060
- }
5061
- if (adapter.capabilities.some(
5062
- (tag) => tag.endsWith(".*") && capability.startsWith(tag.slice(0, -1))
5063
- )) {
5064
- matches2.push(adapter);
4428
+ /** Run LRU eviction until under cap. Pinned rows are never evicted. */
4429
+ evictIfOver() {
4430
+ let total = this.totalBytes();
4431
+ if (total <= this.maxBytes) return { removed: 0, freed: 0 };
4432
+ const candidates = this.db.prepare(
4433
+ `SELECT content_hash, size_bytes, local_path FROM cached_assets
4434
+ WHERE pin = 0
4435
+ ORDER BY last_used_at ASC`
4436
+ ).all();
4437
+ let freed = 0;
4438
+ let removed = 0;
4439
+ for (const row of candidates) {
4440
+ if (total <= this.maxBytes) break;
4441
+ try {
4442
+ if (existsSync8(row.local_path)) unlinkSync2(row.local_path);
4443
+ } catch {
5065
4444
  }
4445
+ this.db.prepare("DELETE FROM cached_assets WHERE content_hash = ?").run(row.content_hash);
4446
+ freed += row.size_bytes;
4447
+ total -= row.size_bytes;
4448
+ removed += 1;
5066
4449
  }
5067
- matches2.sort((a, b) => a.name.localeCompare(b.name));
5068
- return matches2;
4450
+ return { removed, freed };
5069
4451
  }
5070
4452
  };
5071
4453
 
@@ -5322,20 +4704,20 @@ var UriResolver = class {
5322
4704
  }
5323
4705
  return { texts: all, resolvedHashes: dedupe(allHashes) };
5324
4706
  }
5325
- async lookupFile(workspaceId, path9, signal) {
4707
+ async lookupFile(workspaceId, path7, signal) {
5326
4708
  const local = this.db.prepare(
5327
4709
  "SELECT asset_id, content_hash, version FROM workspace_files_mirror WHERE workspace_id = ? AND path = ?"
5328
- ).get(workspaceId, path9);
4710
+ ).get(workspaceId, path7);
5329
4711
  if (local) return local;
5330
4712
  const remote = await this.cloud.get(
5331
- `/api/im/workspaces/${encodeURIComponent(workspaceId)}/files?path=${encodeURIComponent(path9)}`,
4713
+ `/api/im/workspaces/${encodeURIComponent(workspaceId)}/files?path=${encodeURIComponent(path7)}`,
5332
4714
  { signal }
5333
4715
  );
5334
4716
  this.db.prepare(
5335
4717
  `INSERT OR REPLACE INTO workspace_files_mirror
5336
4718
  (workspace_id, path, asset_id, content_hash, version, synced_at, dirty)
5337
4719
  VALUES (?, ?, ?, ?, ?, ?, 0)`
5338
- ).run(workspaceId, path9, remote.assetId, remote.contentHash, remote.version, Date.now());
4720
+ ).run(workspaceId, path7, remote.assetId, remote.contentHash, remote.version, Date.now());
5339
4721
  return { asset_id: remote.assetId, content_hash: remote.contentHash, version: remote.version };
5340
4722
  }
5341
4723
  };
@@ -5344,96 +4726,11 @@ function dedupe(xs) {
5344
4726
  }
5345
4727
 
5346
4728
  // src/daemon/dispatch.ts
5347
- import { readFileSync as readFileSync10, promises as fsp2 } from "fs";
4729
+ import { readFileSync as readFileSync7, promises as fsp } from "fs";
5348
4730
  import * as path2 from "path";
5349
-
5350
- // src/daemon/skill-sync.ts
5351
- import { promises as fsp } from "fs";
5352
- import { join as join10 } from "path";
5353
- import { createHash as createHash2 } from "crypto";
5354
- async function syncInstalledSkillsForDispatch(profile, agentImUserId, cloud, signal) {
5355
- if (!agentImUserId) return { synced: 0, skipped: 0, unchanged: 0 };
5356
- const data = await cloud.get(
5357
- `/api/im/skills/installed?agentId=${encodeURIComponent(agentImUserId)}`,
5358
- { signal }
5359
- );
5360
- const entries = normalizeInstalledSkills(data);
5361
- const skillsRoot = resolveSkillsRoot(profile);
5362
- if (!skillsRoot) return { synced: 0, skipped: 0, unchanged: 0 };
5363
- let synced = 0;
5364
- let skipped = 0;
5365
- let unchanged = 0;
5366
- for (const entry of entries) {
5367
- const slug = sanitizeSlug(trimmedStringFrom(entry.skill?.slug) ?? trimmedStringFrom(entry.slug));
5368
- const content = contentStringFrom(entry.skill?.content) ?? contentStringFrom(entry.content);
5369
- if (!slug || !content) {
5370
- skipped++;
5371
- if (slug && !content) {
5372
- process.stderr.write(`[daemon] skill sync skipped ${slug}: missing content
5373
- `);
5374
- }
5375
- continue;
5376
- }
5377
- const skillDir = join10(skillsRoot, slug);
5378
- await fsp.mkdir(skillDir, { recursive: true });
5379
- const target = join10(skillDir, "SKILL.md");
5380
- const existing = await fsp.readFile(target, "utf8").catch(() => null);
5381
- if (existing != null && sha2562(existing) === sha2562(content)) {
5382
- unchanged++;
5383
- continue;
5384
- }
5385
- await fsp.writeFile(target, content, "utf8");
5386
- synced++;
5387
- }
5388
- return { synced, skipped, unchanged };
5389
- }
5390
- function resolveSkillsRoot(profile) {
5391
- if (profile.adapterName === "hermes") return join10(getHermesProfileDir(getHermesProfileName(profile)), "skills");
5392
- if (profile.adapterName === "openclaw") return getOpenClawSkillsDir(profile);
5393
- return null;
5394
- }
5395
- function sha2562(value) {
5396
- return createHash2("sha256").update(value).digest("hex");
5397
- }
5398
- function normalizeInstalledSkills(data) {
5399
- if (Array.isArray(data)) return data.filter(isInstalledSkillEntry);
5400
- if (!data || typeof data !== "object" || Array.isArray(data)) return [];
5401
- const record = data;
5402
- const nested = record.data;
5403
- if (Array.isArray(nested)) return nested.filter(isInstalledSkillEntry);
5404
- if (nested && typeof nested === "object" && !Array.isArray(nested)) {
5405
- const skills2 = nested.skills;
5406
- if (Array.isArray(skills2)) return skills2.filter(isInstalledSkillEntry);
5407
- }
5408
- const skills = record.skills;
5409
- if (Array.isArray(skills)) return skills.filter(isInstalledSkillEntry);
5410
- return [];
5411
- }
5412
- function isInstalledSkillEntry(value) {
5413
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
5414
- }
5415
- function trimmedStringFrom(value) {
5416
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
5417
- }
5418
- function contentStringFrom(value) {
5419
- return typeof value === "string" && value.trim() ? value : void 0;
5420
- }
5421
- function sanitizeSlug(value) {
5422
- if (!value) return void 0;
5423
- const safe = value.replace(/[^a-zA-Z0-9_-]/g, "");
5424
- return safe || void 0;
5425
- }
5426
-
5427
- // src/daemon/dispatch.ts
5428
4731
  var DEFAULT_CONTEXT_MAX = 8e3;
5429
4732
  var GOAL_CONTEXT_MAX = 4;
5430
4733
  var MEMORY_DIGEST_MAX_BYTES = 4e3;
5431
- var DEFAULT_OPERATING_PRINCIPLES = [
5432
- "- Decide and act; only request human approval for destructive operations or irreversible external effects.",
5433
- "- Assignable work should become explicit tasks; do not leave concrete work as chat discussion only.",
5434
- "- Use workspace asset tools for uploaded files: search/describe first, then read bounded ranges; do not claim file contents unless a tool call succeeded.",
5435
- "- Keep users informed with concise status and concrete next steps."
5436
- ].join("\n");
5437
4734
  var ASSET_INLINE_FULL_MAX_BYTES = 64 * 1024;
5438
4735
  var ASSET_INLINE_TRUNCATE_BYTES = 16 * 1024;
5439
4736
  async function handleDispatch(payload, requestId, deps) {
@@ -5444,7 +4741,7 @@ async function handleDispatch(payload, requestId, deps) {
5444
4741
  const outboxDir = outboxBase ? path2.join(outboxBase, "_outbox") : null;
5445
4742
  if (outboxDir) {
5446
4743
  try {
5447
- await fsp2.mkdir(outboxDir, { recursive: true });
4744
+ await fsp.mkdir(outboxDir, { recursive: true });
5448
4745
  } catch (err) {
5449
4746
  process.stderr.write(
5450
4747
  `[daemon] outbox provision failed task=${taskId} dir=${outboxDir}: ${err.message}
@@ -5471,18 +4768,6 @@ async function handleDispatch(payload, requestId, deps) {
5471
4768
  sendReply(deps.ws, reply, requestId);
5472
4769
  return reply;
5473
4770
  }
5474
- try {
5475
- const skillSync = await syncInstalledSkillsForDispatch(profile, agentImUserId, deps.cloud, deps.signal);
5476
- if (skillSync.synced > 0 || skillSync.skipped > 0) {
5477
- process.stderr.write(
5478
- `[daemon] skill sync profile=${profile.id} adapter=${profile.adapterName} synced=${skillSync.synced} skipped=${skillSync.skipped}
5479
- `
5480
- );
5481
- }
5482
- } catch (err) {
5483
- process.stderr.write(`[daemon] skill sync skipped profile=${profile.id}: ${err.message}
5484
- `);
5485
- }
5486
4771
  let hashRefResult = { text: payload.prompt, resolutions: [] };
5487
4772
  if (deps.assetMetadataIndexes && profile.workspaceId) {
5488
4773
  const assetIndex = deps.assetMetadataIndexes.get(profile.workspaceId);
@@ -5532,7 +4817,6 @@ async function handleDispatch(payload, requestId, deps) {
5532
4817
  `
5533
4818
  );
5534
4819
  }
5535
- const operatingPrinciples = resolveOperatingPrinciples(profile.config);
5536
4820
  const taskInput = {
5537
4821
  taskId,
5538
4822
  prompt: adapterPrompt,
@@ -5541,7 +4825,6 @@ async function handleDispatch(payload, requestId, deps) {
5541
4825
  conversationId: payload.conversationId,
5542
4826
  prismerGoals: goalContext.map(toGoalMirrorPayload),
5543
4827
  ...profileSystemPrompt ? { systemPrompt: profileSystemPrompt } : {},
5544
- ...operatingPrinciples ? { operatingPrinciples } : {},
5545
4828
  // Wave-9: spawn-style adapters (claude-code, codex, openclaw) read
5546
4829
  // this and inject `PRISMER_OUTBOX_DIR=<abs>` into the child env so
5547
4830
  // the LLM tool can resolve the path even if it doesn't read the
@@ -5812,7 +5095,7 @@ async function resolveAssetRefs(refs, cache) {
5812
5095
  const inlineCandidate = isTextLikeMime(effectiveMime);
5813
5096
  if (inlineCandidate) {
5814
5097
  try {
5815
- const buf = readFileSync10(cached.localPath);
5098
+ const buf = readFileSync7(cached.localPath);
5816
5099
  let strategy;
5817
5100
  let body;
5818
5101
  if (buf.byteLength <= ASSET_INLINE_FULL_MAX_BYTES) {
@@ -5886,36 +5169,6 @@ ${memory.digest.trim()}
5886
5169
 
5887
5170
  ${prompt}`;
5888
5171
  }
5889
- function resolveOperatingPrinciples(config) {
5890
- const cfg = readRecord(config);
5891
- const roleTemplate = readRecord(cfg.roleTemplate);
5892
- const principles = stringifyPrinciples(cfg.operatingPrinciples) ?? stringifyPrinciples(roleTemplate.operatingPrinciples) ?? DEFAULT_OPERATING_PRINCIPLES;
5893
- const policy = typeof cfg.approvalPolicy === "string" ? cfg.approvalPolicy : roleTemplate.approvalPolicy;
5894
- if (policy === "strict") {
5895
- return `${principles}
5896
- - Approval policy: strict. Request human approval before destructive, external, spend, access, or publish operations.`;
5897
- }
5898
- if (policy === "autonomous") {
5899
- return `${principles}
5900
- - Approval policy: autonomous. Do not request human approval unless a platform policy explicitly requires it.`;
5901
- }
5902
- return `${principles}
5903
- - Approval policy: auto-low-risk. Proceed on reversible low-risk work; request human approval for destructive or irreversible operations.`;
5904
- }
5905
- function stringifyPrinciples(value) {
5906
- if (typeof value === "string" && value.trim()) return value.trim();
5907
- if (value && typeof value === "object" && !Array.isArray(value)) {
5908
- const record = value;
5909
- for (const key of ["en", "zh"]) {
5910
- const text = record[key];
5911
- if (typeof text === "string" && text.trim()) return text.trim();
5912
- }
5913
- for (const text of Object.values(record)) {
5914
- if (typeof text === "string" && text.trim()) return text.trim();
5915
- }
5916
- }
5917
- return void 0;
5918
- }
5919
5172
  function appendOutboxInstruction(prompt, outboxDir, profileConfig) {
5920
5173
  if (!outboxDir) return prompt;
5921
5174
  if (profileConfig?.disableOutboxHints === true) return prompt;
@@ -5934,9 +5187,6 @@ function appendChannelContext(prompt, payload, profile) {
5934
5187
  const type = payload.conversationType === "direct" || payload.conversationType === "group" ? payload.conversationType : "unknown";
5935
5188
  const youAre = profile.agentUsername || payload.agentImUserId || "this agent";
5936
5189
  const lines = ["[Channel context]", `Conversation type: ${type}`, `You are: ${youAre}`];
5937
- if (payload.conversationId) {
5938
- lines.push(`Conversation ID: ${payload.conversationId}`);
5939
- }
5940
5190
  const participants = Array.isArray(payload.participants) ? payload.participants : [];
5941
5191
  if (participants.length > 0) {
5942
5192
  if (type === "direct") {
@@ -6160,7 +5410,7 @@ function updatedTime(task) {
6160
5410
 
6161
5411
  // src/daemon/local-server.ts
6162
5412
  import { createServer } from "http";
6163
- import { randomUUID, createHash as createHash3 } from "crypto";
5413
+ import { randomUUID, createHash } from "crypto";
6164
5414
  import { promises as fs2 } from "fs";
6165
5415
  import * as path3 from "path";
6166
5416
  var LocalServer = class {
@@ -6468,10 +5718,10 @@ async function walkAndDigest(root, current) {
6468
5718
  }
6469
5719
  if (!st.isFile()) continue;
6470
5720
  const buf = await fs2.readFile(full);
6471
- const sha2564 = createHash3("sha256").update(buf).digest("hex");
5721
+ const sha2562 = createHash("sha256").update(buf).digest("hex");
6472
5722
  out.push({
6473
5723
  path: rel,
6474
- sha256: sha2564,
5724
+ sha256: sha2562,
6475
5725
  sizeBytes: st.size,
6476
5726
  mtime: Math.floor(st.mtimeMs)
6477
5727
  });
@@ -6487,7 +5737,7 @@ import * as path5 from "path";
6487
5737
  import Database3 from "better-sqlite3";
6488
5738
  import * as fs3 from "fs";
6489
5739
  import * as path4 from "path";
6490
- import { randomUUID as randomUUID2, createHash as createHash4 } from "crypto";
5740
+ import { randomUUID as randomUUID2, createHash as createHash2 } from "crypto";
6491
5741
 
6492
5742
  // src/daemon/memory/crypto.ts
6493
5743
  function sealPlaintext(content) {
@@ -6638,9 +5888,9 @@ var MemoryStore = class {
6638
5888
  const row = db.prepare("SELECT * FROM memory_pages WHERE workspaceId = ? AND id = ?").get(this.opts.workspaceId, pageId);
6639
5889
  return row ? this.rowToPage(row) : null;
6640
5890
  }
6641
- loadContent(pageId, version) {
5891
+ loadContent(pageId, version2) {
6642
5892
  const db = this.requireDb();
6643
- const targetVersion = version ?? db.prepare("SELECT version FROM memory_pages WHERE workspaceId = ? AND id = ?").get(this.opts.workspaceId, pageId)?.version;
5893
+ const targetVersion = version2 ?? db.prepare("SELECT version FROM memory_pages WHERE workspaceId = ? AND id = ?").get(this.opts.workspaceId, pageId)?.version;
6644
5894
  if (targetVersion === void 0) return null;
6645
5895
  const row = db.prepare(
6646
5896
  `SELECT c.pageId, c.version, c.payloadKind, c.payloadValue
@@ -6678,7 +5928,7 @@ var MemoryStore = class {
6678
5928
  }
6679
5929
  const db = this.requireDb();
6680
5930
  const now = Date.now();
6681
- const contentHash = sha2563(input.content);
5931
+ const contentHash = sha256(input.content);
6682
5932
  const payload = sealPlaintext(input.content);
6683
5933
  if (payload.kind !== "inline") {
6684
5934
  throw new Error("MemoryStore.write: non-inline payload not yet supported in phase-0");
@@ -6872,8 +6122,8 @@ var MemoryStore = class {
6872
6122
  };
6873
6123
  }
6874
6124
  };
6875
- function sha2563(s) {
6876
- return createHash4("sha256").update(s, "utf8").digest("hex");
6125
+ function sha256(s) {
6126
+ return createHash2("sha256").update(s, "utf8").digest("hex");
6877
6127
  }
6878
6128
 
6879
6129
  // src/daemon/memory/search.ts
@@ -7731,8 +6981,8 @@ function finalizeSelected(runtime, workspaceId, paths, snippetMaxBytes = DEFAULT
7731
6981
  const slot = runtime.peek(workspaceId);
7732
6982
  if (!slot) return [];
7733
6983
  const out = [];
7734
- for (const path9 of paths) {
7735
- const page = slot.store.loadByPath(path9);
6984
+ for (const path7 of paths) {
6985
+ const page = slot.store.loadByPath(path7);
7736
6986
  if (!page) continue;
7737
6987
  const content = slot.store.loadContent(page.id)?.content ?? "";
7738
6988
  out.push({
@@ -7990,33 +7240,186 @@ function handleRecallFinalize(runtime, body, res) {
7990
7240
  if (!Array.isArray(b.paths)) {
7991
7241
  return respond400(res, "paths[] is required");
7992
7242
  }
7993
- const paths = [];
7994
- for (const p of b.paths) {
7995
- if (typeof p === "string" && p.length > 0) paths.push(p);
7243
+ const paths = [];
7244
+ for (const p of b.paths) {
7245
+ if (typeof p === "string" && p.length > 0) paths.push(p);
7246
+ }
7247
+ const snippetMaxBytes = typeof b.snippetMaxBytes === "number" && b.snippetMaxBytes > 0 ? b.snippetMaxBytes : void 0;
7248
+ const results = finalizeSelected(runtime, b.workspaceId, paths, snippetMaxBytes);
7249
+ respond2(res, 200, { workspaceId: b.workspaceId, results });
7250
+ return true;
7251
+ }
7252
+ function parsePrismerUri(uri) {
7253
+ const PREFIX2 = "prismer://workspace/";
7254
+ if (!uri.startsWith(PREFIX2)) return null;
7255
+ const rest = uri.slice(PREFIX2.length);
7256
+ const segMemory = "/memory/";
7257
+ const memoryIdx = rest.indexOf(segMemory);
7258
+ if (memoryIdx <= 0) return null;
7259
+ const workspaceId = rest.slice(0, memoryIdx);
7260
+ const path7 = rest.slice(memoryIdx + segMemory.length);
7261
+ if (!workspaceId || !path7) return null;
7262
+ return { workspaceId, path: path7 };
7263
+ }
7264
+
7265
+ // src/daemon/asset/metadata-index.ts
7266
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
7267
+ import { join as join9 } from "path";
7268
+ var DEFAULT_LIMIT = 8;
7269
+ var PULL_PAGE_SIZE = 500;
7270
+ var THROTTLE_MS = 3e4;
7271
+ function rowToMetadata(row) {
7272
+ return {
7273
+ assetId: row.asset_id,
7274
+ contentHash: row.content_hash,
7275
+ filename: row.filename,
7276
+ folderPath: row.folder_path,
7277
+ mime: row.mime,
7278
+ kind: row.kind,
7279
+ sizeBytes: row.size_bytes,
7280
+ description: row.description,
7281
+ assetIndexSeq: row.asset_index_seq
7282
+ };
7283
+ }
7284
+ var AssetMetadataIndex = class {
7285
+ db;
7286
+ cloud;
7287
+ /** Workspace ID — exposed for prismer:// URI construction. */
7288
+ workspaceId;
7289
+ cursorPath;
7290
+ _lastSyncMs = 0;
7291
+ constructor(opts) {
7292
+ this.db = opts.db;
7293
+ this.cloud = opts.cloud;
7294
+ this.workspaceId = opts.workspaceId;
7295
+ if (!existsSync10(opts.workspaceStateDir)) {
7296
+ mkdirSync7(opts.workspaceStateDir, { recursive: true });
7297
+ }
7298
+ this.cursorPath = join9(opts.workspaceStateDir, "asset-metadata-cursor.json");
7299
+ }
7300
+ /** Persisted cursor for this workspace, or 0 on first run / corrupted file. */
7301
+ readCursor() {
7302
+ if (!existsSync10(this.cursorPath)) return 0;
7303
+ try {
7304
+ const parsed = JSON.parse(readFileSync8(this.cursorPath, "utf8"));
7305
+ if (parsed.workspaceId !== this.workspaceId) return 0;
7306
+ return parsed.cursor;
7307
+ } catch {
7308
+ return 0;
7309
+ }
7310
+ }
7311
+ writeCursor(cursor) {
7312
+ const payload = {
7313
+ workspaceId: this.workspaceId,
7314
+ cursor,
7315
+ writtenAt: Date.now()
7316
+ };
7317
+ writeFileSync7(this.cursorPath, JSON.stringify(payload, null, 2));
7318
+ }
7319
+ /**
7320
+ * Pull incremental asset metadata changes since the persisted cursor and
7321
+ * upsert into the local index. Newer rows overwrite older ones by
7322
+ * (workspace_id, asset_id) primary key.
7323
+ *
7324
+ * Returns the count of items applied + the new cursor. Throttled: if called
7325
+ * within 30s of the last successful pull, returns immediately.
7326
+ */
7327
+ async pullDelta(opts) {
7328
+ const now = Date.now();
7329
+ if (now - this._lastSyncMs < THROTTLE_MS) {
7330
+ return { applied: 0, cursor: this.readCursor() };
7331
+ }
7332
+ const since = this.readCursor();
7333
+ const sinceParam = since > 0 ? `&since=${since}` : "";
7334
+ const envelope2 = await this.cloud.get(
7335
+ `/api/im/assets/index?workspaceId=${encodeURIComponent(this.workspaceId)}&limit=${PULL_PAGE_SIZE}${sinceParam}`,
7336
+ { signal: opts?.signal }
7337
+ );
7338
+ const upsert = this.db.prepare(`
7339
+ INSERT INTO asset_metadata_index
7340
+ (workspace_id, asset_id, content_hash, filename, folder_path, mime, kind, size_bytes, description, asset_index_seq, updated_at)
7341
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
7342
+ ON CONFLICT(workspace_id, asset_id) DO UPDATE SET
7343
+ content_hash = excluded.content_hash,
7344
+ filename = excluded.filename,
7345
+ folder_path = excluded.folder_path,
7346
+ mime = excluded.mime,
7347
+ kind = excluded.kind,
7348
+ size_bytes = excluded.size_bytes,
7349
+ description = excluded.description,
7350
+ asset_index_seq = excluded.asset_index_seq,
7351
+ updated_at = excluded.updated_at
7352
+ `);
7353
+ const nowTs = Date.now();
7354
+ let applied = 0;
7355
+ const tx = this.db.transaction((items) => {
7356
+ for (const item of items) {
7357
+ upsert.run(
7358
+ this.workspaceId,
7359
+ item.assetId,
7360
+ item.contentHash,
7361
+ item.filename ?? null,
7362
+ item.folderPath ?? null,
7363
+ item.mime,
7364
+ item.kind,
7365
+ item.sizeBytes,
7366
+ item.description ?? null,
7367
+ item.assetIndexSeq,
7368
+ nowTs
7369
+ );
7370
+ applied += 1;
7371
+ }
7372
+ });
7373
+ try {
7374
+ tx(envelope2.items);
7375
+ this.writeCursor(envelope2.cursor);
7376
+ } catch (err) {
7377
+ throw err;
7378
+ }
7379
+ this._lastSyncMs = now;
7380
+ return { applied, cursor: envelope2.cursor };
7381
+ }
7382
+ /**
7383
+ * Search local index by filename or description substring.
7384
+ * Escapes LIKE wildcards (% and _). Default limit 8.
7385
+ */
7386
+ search(query, limit) {
7387
+ const escaped = query.replace(/%/g, "\\%").replace(/_/g, "\\_");
7388
+ const pattern = `%${escaped}%`;
7389
+ const limitVal = Math.min(Math.max(limit ?? DEFAULT_LIMIT, 1), 200);
7390
+ const rows = this.db.prepare(
7391
+ `SELECT * FROM asset_metadata_index
7392
+ WHERE workspace_id = ?
7393
+ AND (filename LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\')
7394
+ ORDER BY asset_index_seq DESC
7395
+ LIMIT ?`
7396
+ ).all(this.workspaceId, pattern, pattern, limitVal);
7397
+ return rows.map(rowToMetadata);
7398
+ }
7399
+ /** Exact match on filename column. Returns undefined if not indexed. */
7400
+ resolveByFilename(filename) {
7401
+ const row = this.db.prepare("SELECT * FROM asset_metadata_index WHERE workspace_id = ? AND filename = ?").get(this.workspaceId, filename);
7402
+ return row ? rowToMetadata(row) : void 0;
7403
+ }
7404
+ /** Batch exact match — returns Map for O(1) access. */
7405
+ resolveByFilenames(filenames) {
7406
+ if (filenames.length === 0) return /* @__PURE__ */ new Map();
7407
+ const placeholders = filenames.map(() => "?").join(",");
7408
+ const params = [this.workspaceId, ...filenames];
7409
+ const rows = this.db.prepare(
7410
+ `SELECT * FROM asset_metadata_index
7411
+ WHERE workspace_id = ? AND filename IN (${placeholders})`
7412
+ ).all(...params);
7413
+ const map = /* @__PURE__ */ new Map();
7414
+ for (const row of rows) {
7415
+ if (row.filename) map.set(row.filename, rowToMetadata(row));
7416
+ }
7417
+ return map;
7996
7418
  }
7997
- const snippetMaxBytes = typeof b.snippetMaxBytes === "number" && b.snippetMaxBytes > 0 ? b.snippetMaxBytes : void 0;
7998
- const results = finalizeSelected(runtime, b.workspaceId, paths, snippetMaxBytes);
7999
- respond2(res, 200, { workspaceId: b.workspaceId, results });
8000
- return true;
8001
- }
8002
- function parsePrismerUri(uri) {
8003
- const PREFIX2 = "prismer://workspace/";
8004
- if (!uri.startsWith(PREFIX2)) return null;
8005
- const rest = uri.slice(PREFIX2.length);
8006
- const segMemory = "/memory/";
8007
- const memoryIdx = rest.indexOf(segMemory);
8008
- if (memoryIdx <= 0) return null;
8009
- const workspaceId = rest.slice(0, memoryIdx);
8010
- const path9 = rest.slice(memoryIdx + segMemory.length);
8011
- if (!workspaceId || !path9) return null;
8012
- return { workspaceId, path: path9 };
8013
- }
7419
+ };
8014
7420
 
8015
7421
  // src/daemon/asset/rpc.ts
8016
- import { closeSync, openSync, readSync, statSync as statSync3 } from "fs";
8017
7422
  var ASSET_PATH_PREFIX = "/local/asset/";
8018
- var MAX_READ_BYTES = 256 * 1024;
8019
- var MAX_SEARCH_BYTES = 1024 * 1024;
8020
7423
  function attachAssetRpc(opts) {
8021
7424
  return async (req, res) => {
8022
7425
  const url = req.url ?? "/";
@@ -8027,17 +7430,10 @@ function attachAssetRpc(opts) {
8027
7430
  try {
8028
7431
  if (method === "POST" && subpath === "search") {
8029
7432
  const body = await readJson3(req);
8030
- return await handleSearch2(opts, body, res);
8031
- }
8032
- if (method === "POST" && subpath === "describe") {
8033
- const body = await readJson3(req);
8034
- return handleDescribe(opts, body, res);
8035
- }
8036
- if (method === "POST" && subpath === "read") {
8037
- const body = await readJson3(req);
8038
- return await handleRead(opts, body, res);
7433
+ return handleSearch2(opts.resolveIndex, body, res);
8039
7434
  }
8040
- return false;
7435
+ respond3(res, 404, { error: "asset_route_not_found", path: url });
7436
+ return true;
8041
7437
  } catch (err) {
8042
7438
  respond3(res, 500, {
8043
7439
  error: "asset_rpc_failed",
@@ -8047,699 +7443,55 @@ function attachAssetRpc(opts) {
8047
7443
  }
8048
7444
  };
8049
7445
  }
8050
- async function handleSearch2(opts, body, res) {
7446
+ function handleSearch2(resolveIndex, body, res) {
8051
7447
  if (!body || typeof body !== "object") {
8052
7448
  return respond4002(res, "request body must be a JSON object");
8053
7449
  }
8054
7450
  const b = body;
8055
- if (typeof b.query !== "string" || !b.query.trim()) {
8056
- return respond4002(res, "query is required (non-empty string)");
8057
- }
8058
- if (typeof b.uri === "string" && b.uri.trim()) {
8059
- return searchAssetContent(opts, b.uri.trim(), b.query.trim(), res);
8060
- }
8061
7451
  if (typeof b.workspaceId !== "string" || !b.workspaceId) {
8062
7452
  return respond4002(res, "workspaceId is required (string)");
8063
7453
  }
8064
- const index = opts.resolveIndex(b.workspaceId);
8065
- if (!index) {
8066
- respond3(res, 404, {
8067
- error: "workspace_index_not_found",
8068
- workspaceId: b.workspaceId,
8069
- message: "No asset metadata index for this workspace. Ensure the daemon has synced asset metadata."
8070
- });
8071
- return true;
8072
- }
8073
- const limit = typeof b.limit === "number" && b.limit > 0 ? b.limit : void 0;
8074
- const items = index.search(b.query.trim(), limit);
8075
- respond3(res, 200, { items });
8076
- return true;
8077
- }
8078
- function handleDescribe(opts, body, res) {
8079
- const resolved = resolveAssetRef(opts, body);
8080
- if ("error" in resolved) return respond4002(res, resolved.error);
8081
- const cached = opts.assetCache?.get(resolved.row.contentHash);
8082
- respond3(res, 200, {
8083
- asset: resolved.row,
8084
- uri: `prismer://workspace/${resolved.workspaceId}/asset/${resolved.row.contentHash}`,
8085
- cache: cached ? { status: "hit", localPath: cached.localPath, sizeBytes: cached.sizeBytes, lastUsedAt: cached.lastUsedAt } : { status: "miss" },
8086
- tools: ["asset.describe", "asset.read", "asset.search"]
8087
- });
8088
- return true;
8089
- }
8090
- async function handleRead(opts, body, res) {
8091
- const resolved = resolveAssetRef(opts, body);
8092
- if ("error" in resolved) return respond4002(res, resolved.error);
8093
- if (!opts.assetCache) {
8094
- respond3(res, 503, { error: "asset_cache_unavailable" });
8095
- return true;
8096
- }
8097
- const b = body;
8098
- const offset = typeof b.offset === "number" && b.offset > 0 ? Math.floor(b.offset) : 0;
8099
- const requested = typeof b.length === "number" && b.length > 0 ? Math.floor(b.length) : MAX_READ_BYTES;
8100
- const length = Math.min(requested, MAX_READ_BYTES);
8101
- const cached = await opts.assetCache.getOrFetch(resolved.row.contentHash, {
8102
- workspaceIdHint: resolved.workspaceId,
8103
- assetId: resolved.row.assetId
8104
- });
8105
- const chunk = readFileRange(cached.localPath, offset, length);
8106
- const textLike = isTextLike(resolved.row);
8107
- respond3(res, 200, {
8108
- asset: resolved.row,
8109
- locator: { byteOffset: offset, byteLength: chunk.bytesRead },
8110
- truncated: requested > MAX_READ_BYTES || offset + chunk.bytesRead < chunk.sizeBytes,
8111
- encoding: textLike ? "utf8" : "base64",
8112
- content: textLike ? chunk.buffer.toString("utf8") : chunk.buffer.toString("base64")
8113
- });
8114
- return true;
8115
- }
8116
- async function searchAssetContent(opts, uri, query, res) {
8117
- const resolved = resolveAssetRef(opts, { uri });
8118
- if ("error" in resolved) return respond4002(res, resolved.error);
8119
- if (!opts.assetCache) {
8120
- respond3(res, 503, { error: "asset_cache_unavailable" });
8121
- return true;
8122
- }
8123
- if (!isTextLike(resolved.row)) {
8124
- respond3(res, 200, {
8125
- items: [],
8126
- unsupported: true,
8127
- reason: "content search currently supports text-like assets only",
8128
- asset: resolved.row
8129
- });
8130
- return true;
8131
- }
8132
- const cached = await opts.assetCache.getOrFetch(resolved.row.contentHash, {
8133
- workspaceIdHint: resolved.workspaceId,
8134
- assetId: resolved.row.assetId
8135
- });
8136
- const chunk = readFileRange(cached.localPath, 0, MAX_SEARCH_BYTES);
8137
- const text = chunk.buffer.toString("utf8");
8138
- const needle = query.toLowerCase();
8139
- const lines = text.split(/\r?\n/);
8140
- const items = [];
8141
- let byteOffset = 0;
8142
- for (let i = 0; i < lines.length && items.length < 20; i += 1) {
8143
- const line = lines[i] ?? "";
8144
- if (line.toLowerCase().includes(needle)) {
8145
- items.push({ line: i + 1, byteOffset, preview: line.slice(0, 500) });
8146
- }
8147
- byteOffset += Buffer.byteLength(line) + 1;
8148
- }
8149
- respond3(res, 200, {
8150
- items,
8151
- asset: resolved.row,
8152
- searched: { byteOffset: 0, byteLength: chunk.bytesRead, truncated: chunk.bytesRead < chunk.sizeBytes }
8153
- });
8154
- return true;
8155
- }
8156
- function resolveAssetRef(opts, body) {
8157
- if (!body || typeof body !== "object") return { error: "request body must be a JSON object" };
8158
- const b = body;
8159
- let workspaceId = typeof b.workspaceId === "string" ? b.workspaceId : "";
8160
- let assetId = typeof b.assetId === "string" ? b.assetId : "";
8161
- let contentHash = typeof b.contentHash === "string" ? b.contentHash : "";
8162
- if (typeof b.uri === "string" && b.uri.trim()) {
8163
- const [uri] = parseUris(b.uri.trim());
8164
- if (!uri) return { error: "uri must be prismer://workspace/<workspaceId>/asset/<hash>" };
8165
- if (uri.type !== "asset") return { error: "file uri lookup is not available in this RPC yet; pass asset uri or assetId" };
8166
- workspaceId = uri.workspaceId ?? workspaceId;
8167
- contentHash = uri.rest;
8168
- }
8169
- if (!workspaceId) return { error: "workspaceId is required (string)" };
8170
- const index = opts.resolveIndex(workspaceId);
8171
- if (!index) return { error: "workspace asset index is not synced yet" };
8172
- const row = assetId ? index.resolveByAssetId(assetId) : contentHash ? index.resolveByContentHash(contentHash) : void 0;
8173
- if (!row) return { error: "asset not found in local metadata index" };
8174
- return { workspaceId, row };
8175
- }
8176
- function isTextLike(row) {
8177
- const mime = row.mime ?? "";
8178
- const name = row.filename ?? "";
8179
- 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(
8180
- name
8181
- );
8182
- }
8183
- function readFileRange(path9, offset, length) {
8184
- const sizeBytes = statSync3(path9).size;
8185
- if (offset >= sizeBytes) return { buffer: Buffer.alloc(0), bytesRead: 0, sizeBytes };
8186
- const fd = openSync(path9, "r");
8187
- try {
8188
- const target = Math.min(length, sizeBytes - offset);
8189
- const buffer = Buffer.alloc(target);
8190
- const bytesRead = readSync(fd, buffer, 0, target, offset);
8191
- return { buffer: buffer.subarray(0, bytesRead), bytesRead, sizeBytes };
8192
- } finally {
8193
- closeSync(fd);
8194
- }
8195
- }
8196
- function respond3(res, status, body) {
8197
- res.statusCode = status;
8198
- res.setHeader("Content-Type", "application/json");
8199
- res.end(JSON.stringify(body));
8200
- }
8201
- function respond4002(res, message) {
8202
- respond3(res, 400, { error: "invalid_request", message });
8203
- return true;
8204
- }
8205
- async function readJson3(req) {
8206
- let raw = "";
8207
- req.setEncoding("utf8");
8208
- for await (const chunk of req) raw += chunk;
8209
- if (!raw) return {};
8210
- try {
8211
- return JSON.parse(raw);
8212
- } catch {
8213
- throw new Error("invalid_json");
8214
- }
8215
- }
8216
-
8217
- // src/daemon/asset/origin/drop-folder.ts
8218
- import { promises as fs4 } from "fs";
8219
- import { homedir as homedir6 } from "os";
8220
- import * as path6 from "path";
8221
- var DropFolderAdapter = class {
8222
- kind = "drop-folder";
8223
- workspaceDir;
8224
- rootDir;
8225
- constructor(opts = {}) {
8226
- this.workspaceDir = opts.workspaceDir;
8227
- this.rootDir = opts.rootDir ?? path6.join(opts.homeDir ?? homedir6(), ".prismer");
8228
- }
8229
- /**
8230
- * Long-lived async iterable wrapping `scanOnce` on a 1s polling interval.
8231
- * Stops when the iterator is broken (e.g. consumer returns / throws).
8232
- */
8233
- async *observe(workspaceId) {
8234
- while (true) {
8235
- const batch = await this.scanOnce(workspaceId);
8236
- for (const obs of batch) yield obs;
8237
- await sleep(1e3);
8238
- }
8239
- }
8240
- /**
8241
- * Single-tick scan exposed for tests and one-shot CLI usage. Reads every
8242
- * file currently under the workspace's drop directory, returning one
8243
- * observation per file. The caller is expected to feed each into the
8244
- * outbox; idempotency on the outbox side collapses re-observations of
8245
- * unchanged files.
8246
- */
8247
- async scanOnce(workspaceId) {
8248
- const drop = this.dropDir(workspaceId);
8249
- let entries = [];
8250
- try {
8251
- entries = await fs4.readdir(drop);
8252
- } catch (err) {
8253
- if (err.code === "ENOENT") return [];
8254
- throw err;
8255
- }
8256
- const out = [];
8257
- for (const name of entries) {
8258
- if (name.startsWith(".")) continue;
8259
- const fullPath = path6.join(drop, name);
8260
- const stat = await fs4.lstat(fullPath).catch(() => null);
8261
- if (!stat) continue;
8262
- if (stat.isSymbolicLink()) continue;
8263
- if (stat.isDirectory()) {
8264
- for (const nested of await walk(fullPath)) {
8265
- const nstat = await fs4.lstat(nested).catch(() => null);
8266
- if (!nstat || !nstat.isFile()) continue;
8267
- out.push(makeObservation(workspaceId, nested, drop, nstat.size, Math.floor(nstat.mtimeMs)));
8268
- }
8269
- continue;
8270
- }
8271
- if (!stat.isFile()) continue;
8272
- out.push(makeObservation(workspaceId, fullPath, drop, stat.size, Math.floor(stat.mtimeMs)));
8273
- }
8274
- return out;
8275
- }
8276
- async identifySource(obs) {
8277
- const detail = obs.detail;
8278
- const rel = path6.relative(detail.watchDir, detail.path);
8279
- const folderDir = path6.dirname(rel);
8280
- return {
8281
- sourceRef: `folder://${obs.workspaceId}/${rel.split(path6.sep).join("/")}`,
8282
- hints: {
8283
- filename: path6.basename(detail.path),
8284
- folderPath: folderDir === "." ? "" : folderDir.split(path6.sep).join("/")
8285
- }
8286
- };
8287
- }
8288
- async fetch(obs) {
8289
- const detail = obs.detail;
8290
- const bytes = await fs4.readFile(detail.path);
8291
- return {
8292
- bytes,
8293
- mime: mimeFromExtension(detail.path),
8294
- size: bytes.length
8295
- };
8296
- }
8297
- /** Hook called by UploadRunner after a successful upload of one observation. */
8298
- async onUploadSuccess(obs) {
8299
- await this.moveTo(obs, this.uploadedDir(obs.workspaceId));
8300
- }
8301
- /** Hook called by UploadRunner after attempts hit maxAttempts. */
8302
- async onUploadFailure(obs) {
8303
- await this.moveTo(obs, this.failedDir(obs.workspaceId));
8304
- }
8305
- async moveTo(obs, destDir) {
8306
- const detail = obs.detail;
8307
- if (!detail || typeof detail.path !== "string") return;
8308
- try {
8309
- const rel = typeof detail.watchDir === "string" ? path6.relative(detail.watchDir, detail.path) : path6.basename(detail.path);
8310
- const safeRel = rel && !rel.startsWith("..") && !path6.isAbsolute(rel) ? rel : path6.basename(detail.path);
8311
- const destPath = path6.join(destDir, safeRel);
8312
- await fs4.mkdir(path6.dirname(destPath), { recursive: true });
8313
- await fs4.rename(detail.path, destPath);
8314
- } catch (err) {
8315
- if (err.code === "ENOENT") return;
8316
- throw err;
8317
- }
8318
- }
8319
- dropDir(workspaceId) {
8320
- if (this.workspaceDir) return path6.join(this.workspaceDir, "drop");
8321
- return path6.join(this.rootDir, "workspaces", workspaceId, "drop");
8322
- }
8323
- uploadedDir(workspaceId) {
8324
- if (this.workspaceDir) return path6.join(this.workspaceDir, "uploaded");
8325
- return path6.join(this.rootDir, "workspaces", workspaceId, "uploaded");
8326
- }
8327
- failedDir(workspaceId) {
8328
- if (this.workspaceDir) return path6.join(this.workspaceDir, "upload-failed");
8329
- return path6.join(this.rootDir, "workspaces", workspaceId, "upload-failed");
8330
- }
8331
- };
8332
- function makeObservation(workspaceId, filePath, watchDir, size, mtime) {
8333
- return {
8334
- workspaceId,
8335
- detail: { path: filePath, watchDir, size, mtime },
8336
- observedAt: mtime
8337
- };
8338
- }
8339
- async function walk(root) {
8340
- const out = [];
8341
- const entries = await fs4.readdir(root).catch(() => []);
8342
- for (const name of entries) {
8343
- if (name.startsWith(".")) continue;
8344
- const full = path6.join(root, name);
8345
- const stat = await fs4.lstat(full).catch(() => null);
8346
- if (!stat) continue;
8347
- if (stat.isSymbolicLink()) continue;
8348
- if (stat.isDirectory()) {
8349
- out.push(...await walk(full));
8350
- } else if (stat.isFile()) {
8351
- out.push(full);
8352
- }
8353
- }
8354
- return out;
8355
- }
8356
- var MIME_BY_EXT = {
8357
- ".txt": "text/plain",
8358
- ".md": "text/markdown",
8359
- ".csv": "text/csv",
8360
- ".json": "application/json",
8361
- ".yaml": "application/yaml",
8362
- ".yml": "application/yaml",
8363
- ".pdf": "application/pdf",
8364
- ".png": "image/png",
8365
- ".jpg": "image/jpeg",
8366
- ".jpeg": "image/jpeg",
8367
- ".gif": "image/gif",
8368
- ".webp": "image/webp",
8369
- ".svg": "image/svg+xml",
8370
- ".html": "text/html",
8371
- ".htm": "text/html",
8372
- ".xhtml": "application/xhtml+xml",
8373
- ".xml": "application/xml",
8374
- ".zip": "application/zip",
8375
- ".tar": "application/x-tar",
8376
- ".gz": "application/gzip",
8377
- ".mp3": "audio/mpeg",
8378
- ".mp4": "video/mp4",
8379
- ".mov": "video/quicktime",
8380
- ".wav": "audio/wav"
8381
- };
8382
- function mimeFromExtension(p) {
8383
- const ext = path6.extname(p).toLowerCase();
8384
- return MIME_BY_EXT[ext] ?? null;
8385
- }
8386
- function sleep(ms) {
8387
- return new Promise((r) => setTimeout(r, ms));
8388
- }
8389
-
8390
- // src/daemon/asset/origin/outbox.ts
8391
- import Database4 from "better-sqlite3";
8392
- import { createHash as createHash5, randomUUID as randomUUID5 } from "crypto";
8393
- import { z as z7 } from "zod";
8394
- var OriginObservationRecordSchema = z7.object({
8395
- workspaceId: z7.string().min(1),
8396
- originKind: z7.enum(["upload", "drop-folder", "agent-gen"]),
8397
- sourceRef: z7.string().min(1),
8398
- payloadJson: z7.string().min(2),
8399
- hintsJson: z7.string().min(2),
8400
- observedAt: z7.number().int().nonnegative()
8401
- });
8402
- var DDL_STATEMENTS = [
8403
- `CREATE TABLE IF NOT EXISTS asset_origin_outbox (
8404
- id TEXT PRIMARY KEY,
8405
- workspaceId TEXT NOT NULL,
8406
- originKind TEXT NOT NULL,
8407
- sourceRef TEXT NOT NULL,
8408
- payloadJson TEXT NOT NULL,
8409
- hintsJson TEXT NOT NULL,
8410
- observedAt INTEGER NOT NULL,
8411
- idempotencyKey TEXT NOT NULL UNIQUE,
8412
- status TEXT NOT NULL CHECK (status IN ('pending', 'claimed', 'uploaded')),
8413
- attempts INTEGER NOT NULL DEFAULT 0,
8414
- lastError TEXT,
8415
- assetId TEXT,
8416
- contentHash TEXT,
8417
- createdAt INTEGER NOT NULL,
8418
- updatedAt INTEGER NOT NULL
8419
- )`,
8420
- `CREATE INDEX IF NOT EXISTS idx_asset_origin_outbox_status
8421
- ON asset_origin_outbox(status, createdAt)`,
8422
- `CREATE TABLE IF NOT EXISTS asset_origin_outbox_dead_letter (
8423
- id TEXT PRIMARY KEY,
8424
- workspaceId TEXT NOT NULL,
8425
- originKind TEXT NOT NULL,
8426
- sourceRef TEXT NOT NULL,
8427
- payloadJson TEXT NOT NULL,
8428
- hintsJson TEXT NOT NULL,
8429
- observedAt INTEGER NOT NULL,
8430
- errorJson TEXT NOT NULL,
8431
- attempts INTEGER NOT NULL,
8432
- createdAt INTEGER NOT NULL
8433
- )`
8434
- ];
8435
- var OriginOutbox = class {
8436
- db;
8437
- constructor(opts) {
8438
- this.db = new Database4(opts.dbPath);
8439
- this.db.pragma("journal_mode = WAL");
8440
- for (const ddl of DDL_STATEMENTS) this.db.prepare(ddl).run();
8441
- }
8442
- /**
8443
- * Validate + persist an observation. Schema-invalid input lands in
8444
- * `asset_origin_outbox_dead_letter` (never silently dropped). Re-enqueue
8445
- * with the same {originKind, workspaceId, sourceRef, observedAt} returns
8446
- * the existing row id without inserting again.
8447
- */
8448
- enqueue(record) {
8449
- const now = Date.now();
8450
- const parsed = OriginObservationRecordSchema.safeParse(record);
8451
- if (!parsed.success) {
8452
- const dlId = `dl_${randomUUID5()}`;
8453
- const raw = record && typeof record === "object" ? record : {};
8454
- this.db.prepare(
8455
- `INSERT INTO asset_origin_outbox_dead_letter
8456
- (id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt, errorJson, attempts, createdAt)
8457
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
8458
- ).run(
8459
- dlId,
8460
- coerceToString(raw.workspaceId, ""),
8461
- coerceToString(raw.originKind, ""),
8462
- coerceToString(raw.sourceRef, ""),
8463
- coerceToJson(raw.payloadJson),
8464
- coerceToJson(raw.hintsJson),
8465
- typeof raw.observedAt === "number" ? raw.observedAt : 0,
8466
- JSON.stringify(parsed.error.errors),
8467
- 0,
8468
- now
8469
- );
8470
- return { id: dlId, deadLetter: true };
8471
- }
8472
- const validated = parsed.data;
8473
- const idempotencyKey = computeIdempotencyKey(validated);
8474
- const rowId = `out_${randomUUID5()}`;
8475
- try {
8476
- this.db.prepare(
8477
- `INSERT INTO asset_origin_outbox
8478
- (id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt,
8479
- idempotencyKey, status, attempts, createdAt, updatedAt)
8480
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, ?)`
8481
- ).run(
8482
- rowId,
8483
- validated.workspaceId,
8484
- validated.originKind,
8485
- validated.sourceRef,
8486
- validated.payloadJson,
8487
- validated.hintsJson,
8488
- validated.observedAt,
8489
- idempotencyKey,
8490
- now,
8491
- now
8492
- );
8493
- return { id: rowId, deadLetter: false };
8494
- } catch (err) {
8495
- const code = err.code;
8496
- if (code === "SQLITE_CONSTRAINT_UNIQUE") {
8497
- const existing = this.db.prepare("SELECT id FROM asset_origin_outbox WHERE idempotencyKey = ?").get(idempotencyKey);
8498
- if (existing) return { id: existing.id, deadLetter: false };
8499
- }
8500
- throw err;
8501
- }
8502
- }
8503
- /**
8504
- * Atomically claim the oldest pending row, transition status to 'claimed',
8505
- * and return it. Returns null when the queue is empty. The caller MUST
8506
- * follow up with `markUploaded` (success) or `recordFailure` (error) so
8507
- * the row doesn't sit in 'claimed' forever — Phase-0 is a single worker
8508
- * so we don't need a claim-expiry sweeper yet.
8509
- */
8510
- claimNext() {
8511
- const pickAndClaim = this.db.transaction(() => {
8512
- const row = this.db.prepare(
8513
- `SELECT id, workspaceId, originKind, sourceRef, payloadJson, hintsJson,
8514
- observedAt, attempts, lastError, createdAt
8515
- FROM asset_origin_outbox
8516
- WHERE status = 'pending'
8517
- ORDER BY createdAt ASC
8518
- LIMIT 1`
8519
- ).get();
8520
- if (!row) return null;
8521
- this.db.prepare("UPDATE asset_origin_outbox SET status = 'claimed', updatedAt = ? WHERE id = ?").run(Date.now(), row.id);
8522
- return row;
8523
- });
8524
- const claimed = pickAndClaim();
8525
- if (!claimed) return null;
8526
- return {
8527
- id: claimed.id,
8528
- row: rawToRow(claimed)
8529
- };
8530
- }
8531
- markUploaded(id, meta) {
8532
- this.db.prepare(
8533
- `UPDATE asset_origin_outbox
8534
- SET status = 'uploaded', assetId = ?, contentHash = ?, updatedAt = ?
8535
- WHERE id = ?`
8536
- ).run(meta.assetId, meta.contentHash, Date.now(), id);
8537
- }
8538
- /**
8539
- * Record one failed upload attempt. Increments `attempts`, stores the
8540
- * latest error, and either re-queues for retry (status -> 'pending') or
8541
- * spills to dead-letter once attempts hit maxAttempts.
8542
- */
8543
- recordFailure(id, error, opts) {
8544
- const now = Date.now();
8545
- const updated = this.db.transaction(() => {
8546
- const row = this.db.prepare(
8547
- `SELECT id, workspaceId, originKind, sourceRef, payloadJson, hintsJson,
8548
- observedAt, attempts, createdAt
8549
- FROM asset_origin_outbox
8550
- WHERE id = ?`
8551
- ).get(id);
8552
- if (!row) return;
8553
- const nextAttempts = row.attempts + 1;
8554
- if (nextAttempts >= opts.maxAttempts) {
8555
- this.db.prepare(
8556
- `INSERT INTO asset_origin_outbox_dead_letter
8557
- (id, workspaceId, originKind, sourceRef, payloadJson, hintsJson, observedAt, errorJson, attempts, createdAt)
8558
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
8559
- ).run(
8560
- `dl_${randomUUID5()}`,
8561
- row.workspaceId,
8562
- row.originKind,
8563
- row.sourceRef,
8564
- row.payloadJson,
8565
- row.hintsJson,
8566
- row.observedAt,
8567
- JSON.stringify({ message: error, attempts: nextAttempts }),
8568
- nextAttempts,
8569
- now
8570
- );
8571
- this.db.prepare("DELETE FROM asset_origin_outbox WHERE id = ?").run(id);
8572
- } else {
8573
- this.db.prepare(
8574
- `UPDATE asset_origin_outbox
8575
- SET status = 'pending', attempts = ?, lastError = ?, updatedAt = ?
8576
- WHERE id = ?`
8577
- ).run(nextAttempts, error, now, id);
8578
- }
8579
- });
8580
- updated();
8581
- }
8582
- /**
8583
- * Re-promote any rows orphaned in `'claimed'` state back to `'pending'`.
8584
- *
8585
- * Why this exists: claimNext() transitions a row to 'claimed' under a
8586
- * transaction, but the upload that follows runs OUTSIDE the transaction
8587
- * (network call). If the daemon dies between the claim and the
8588
- * markUploaded/recordFailure call, the row is stuck — claimNext() only
8589
- * selects status='pending', so it would never be picked up again.
8590
- *
8591
- * Phase-0 single-worker safety: there is at most one in-flight claim at
8592
- * any time, so on next process startup ANY 'claimed' row is by definition
8593
- * orphaned. We promote it back to 'pending' (preserving attempts /
8594
- * lastError) so the next drainOnce can retry it.
8595
- *
8596
- * If we ever go multi-worker, this naive sweep becomes unsafe and must be
8597
- * replaced with a lease/heartbeat scheme.
8598
- *
8599
- * Returns the number of rows recovered (for logging / observability).
8600
- */
8601
- resetClaimed() {
8602
- const result = this.db.prepare(
8603
- "UPDATE asset_origin_outbox SET status = 'pending', updatedAt = ? WHERE status = 'claimed'"
8604
- ).run(Date.now());
8605
- return Number(result.changes);
8606
- }
8607
- pendingCount() {
8608
- return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox WHERE status = 'pending'").get().n;
8609
- }
8610
- uploadedCount() {
8611
- return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox WHERE status = 'uploaded'").get().n;
8612
- }
8613
- deadLetterCount() {
8614
- return this.db.prepare("SELECT COUNT(*) AS n FROM asset_origin_outbox_dead_letter").get().n;
8615
- }
8616
- close() {
8617
- this.db.close();
8618
- }
8619
- };
8620
- function computeIdempotencyKey(record) {
8621
- return createHash5("sha256").update(record.originKind).update("|").update(record.workspaceId).update("|").update(record.sourceRef).update("|").update(String(record.observedAt)).digest("hex");
8622
- }
8623
- function coerceToString(value, fallback) {
8624
- if (typeof value === "string") return value;
8625
- if (value == null) return fallback;
8626
- return String(value);
8627
- }
8628
- function coerceToJson(value) {
8629
- if (typeof value === "string") return value;
8630
- if (value == null) return "{}";
8631
- try {
8632
- return JSON.stringify(value);
8633
- } catch {
8634
- return "{}";
8635
- }
8636
- }
8637
- function rawToRow(raw) {
8638
- return {
8639
- id: raw.id,
8640
- workspaceId: raw.workspaceId,
8641
- originKind: raw.originKind,
8642
- sourceRef: raw.sourceRef,
8643
- payloadJson: raw.payloadJson,
8644
- hintsJson: raw.hintsJson,
8645
- observedAt: raw.observedAt,
8646
- attempts: raw.attempts,
8647
- lastError: raw.lastError,
8648
- createdAt: raw.createdAt
8649
- };
8650
- }
8651
-
8652
- // src/daemon/asset/origin/upload-runner.ts
8653
- var UploadRunner = class {
8654
- constructor(opts) {
8655
- this.opts = opts;
8656
- opts.outbox.resetClaimed();
8657
- }
8658
- opts;
8659
- /**
8660
- * Process every currently-pending outbox row exactly once. Failed rows
8661
- * stay 'pending' (with attempts+1) for the next drainOnce call; rows
8662
- * that hit maxAttempts spill to dead-letter and trigger onUploadFailure.
8663
- *
8664
- * Returns the count of rows processed (success + failure).
8665
- */
8666
- async drainOnce() {
8667
- let count = 0;
8668
- while (true) {
8669
- const claim = this.opts.outbox.claimNext();
8670
- if (!claim) break;
8671
- count += 1;
8672
- const row = claim.row;
8673
- const adapter = this.opts.adapters[row.originKind];
8674
- if (!adapter) {
8675
- this.opts.outbox.recordFailure(claim.id, `no adapter registered for kind=${row.originKind}`, {
8676
- maxAttempts: 1
8677
- // unrecoverable — fast-fail to dead-letter
8678
- });
8679
- continue;
8680
- }
8681
- try {
8682
- const obs = rowToObservation(row);
8683
- const bytes = await adapter.fetch(obs);
8684
- const id = await adapter.identifySource(obs);
8685
- const filename = id.hints?.filename ?? path7.basename(row.sourceRef);
8686
- const metadata = {
8687
- sourceRef: row.sourceRef,
8688
- sourceKind: row.originKind
8689
- };
8690
- if (id.hints?.description) metadata.description = id.hints.description;
8691
- if (id.hints?.sourceAgentImUserId) {
8692
- metadata.sourceAgentImUserId = id.hints.sourceAgentImUserId;
8693
- }
8694
- const folderPath = id.hints?.folderPath || void 0;
8695
- const result = await this.opts.cloud.uploadAsset({
8696
- workspaceId: row.workspaceId,
8697
- filename,
8698
- bytes: bytes.bytes,
8699
- mime: bytes.mime,
8700
- size: bytes.size,
8701
- metadata,
8702
- folderPath
8703
- });
8704
- this.opts.outbox.markUploaded(claim.id, result);
8705
- await adapter.onUploadSuccess?.(obs);
8706
- } catch (err) {
8707
- const message = err instanceof Error ? err.message : String(err);
8708
- const before = this.opts.outbox.deadLetterCount();
8709
- this.opts.outbox.recordFailure(claim.id, message, { maxAttempts: this.opts.maxAttempts });
8710
- const after = this.opts.outbox.deadLetterCount();
8711
- if (after > before) {
8712
- const obs = rowToObservation(row);
8713
- await adapter.onUploadFailure?.(obs, err);
8714
- }
8715
- }
8716
- }
8717
- return count;
8718
- }
8719
- };
8720
- function rowToObservation(row) {
8721
- let detail = {};
7454
+ if (typeof b.query !== "string" || !b.query.trim()) {
7455
+ return respond4002(res, "query is required (non-empty string)");
7456
+ }
7457
+ const index = resolveIndex(b.workspaceId);
7458
+ if (!index) {
7459
+ respond3(res, 404, {
7460
+ error: "workspace_index_not_found",
7461
+ workspaceId: b.workspaceId,
7462
+ message: "No asset metadata index for this workspace. Ensure the daemon has synced asset metadata."
7463
+ });
7464
+ return true;
7465
+ }
7466
+ const limit = typeof b.limit === "number" && b.limit > 0 ? b.limit : void 0;
7467
+ const items = index.search(b.query.trim(), limit);
7468
+ respond3(res, 200, { items });
7469
+ return true;
7470
+ }
7471
+ function respond3(res, status, body) {
7472
+ res.statusCode = status;
7473
+ res.setHeader("Content-Type", "application/json");
7474
+ res.end(JSON.stringify(body));
7475
+ }
7476
+ function respond4002(res, message) {
7477
+ respond3(res, 400, { error: "invalid_request", message });
7478
+ return true;
7479
+ }
7480
+ async function readJson3(req) {
7481
+ let raw = "";
7482
+ req.setEncoding("utf8");
7483
+ for await (const chunk of req) raw += chunk;
7484
+ if (!raw) return {};
8722
7485
  try {
8723
- const parsed = JSON.parse(row.payloadJson);
8724
- if (parsed && typeof parsed === "object") detail = parsed;
7486
+ return JSON.parse(raw);
8725
7487
  } catch {
7488
+ throw new Error("invalid_json");
8726
7489
  }
8727
- return {
8728
- workspaceId: row.workspaceId,
8729
- detail,
8730
- observedAt: row.observedAt
8731
- };
8732
7490
  }
8733
- var path7 = {
8734
- basename(p) {
8735
- const idx = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\"));
8736
- return idx === -1 ? p : p.slice(idx + 1);
8737
- }
8738
- };
8739
7491
 
8740
7492
  // src/daemon/outbox-watcher.ts
8741
- import { promises as fs5 } from "fs";
8742
- import * as path8 from "path";
7493
+ import { promises as fs4 } from "fs";
7494
+ import * as path6 from "path";
8743
7495
  var DEFAULT_INTERVAL_MS = 2e3;
8744
7496
  var RESERVED_SUBDIR = "_uploaded";
8745
7497
  var OutboxWatcher = class {
@@ -8906,7 +7658,7 @@ var OutboxWatcher = class {
8906
7658
  async scanDir(dir, kind, task) {
8907
7659
  let entries = [];
8908
7660
  try {
8909
- entries = await fs5.readdir(dir);
7661
+ entries = await fs4.readdir(dir);
8910
7662
  } catch (err) {
8911
7663
  const code = err.code;
8912
7664
  if (code === "ENOENT") return;
@@ -8916,10 +7668,10 @@ var OutboxWatcher = class {
8916
7668
  for (const name of entries) {
8917
7669
  if (name.startsWith(".")) continue;
8918
7670
  if (name === RESERVED_SUBDIR) continue;
8919
- const full = path8.join(dir, name);
7671
+ const full = path6.join(dir, name);
8920
7672
  let st;
8921
7673
  try {
8922
- st = await fs5.stat(full);
7674
+ st = await fs4.stat(full);
8923
7675
  } catch {
8924
7676
  continue;
8925
7677
  }
@@ -8944,8 +7696,8 @@ var OutboxWatcher = class {
8944
7696
  this.log("warn", `skip ${filePath}: no active taskId (no dispatch/handoff received yet)`);
8945
7697
  return;
8946
7698
  }
8947
- const bytes = await fs5.readFile(filePath);
8948
- const fileName = path8.basename(filePath);
7699
+ const bytes = await fs4.readFile(filePath);
7700
+ const fileName = path6.basename(filePath);
8949
7701
  const blob = new Blob([new Uint8Array(bytes)]);
8950
7702
  const form = new FormData();
8951
7703
  form.append("workspaceId", wsId);
@@ -9040,7 +7792,7 @@ var ServicePool = class {
9040
7792
 
9041
7793
  // src/daemon/shell-executor.ts
9042
7794
  import { spawn as spawn4 } from "child_process";
9043
- import { existsSync as existsSync12 } from "fs";
7795
+ import { existsSync as existsSync11 } from "fs";
9044
7796
  import { resolve as resolve2 } from "path";
9045
7797
  var DEFAULT_OUTPUT_LIMIT = 256 * 1024;
9046
7798
  var DEFAULT_TIMEOUT = 6e4;
@@ -9077,7 +7829,7 @@ async function executeShellDispatch(payload, deps) {
9077
7829
  const command = readCommand(payload, execution);
9078
7830
  if (!command.trim()) return fail2(payload.taskId, "shell_command_required", "Shell command is required");
9079
7831
  const cwd = resolveCwd(execution.cwd, deps.config.defaultCwd);
9080
- if (!existsSync12(cwd)) return fail2(payload.taskId, "shell_cwd_missing", `cwd does not exist: ${cwd}`);
7832
+ if (!existsSync11(cwd)) return fail2(payload.taskId, "shell_cwd_missing", `cwd does not exist: ${cwd}`);
9081
7833
  const timeoutMs = Math.min(
9082
7834
  typeof payload.timeoutMs === "number" && payload.timeoutMs > 0 ? payload.timeoutMs : deps.config.maxTimeoutMs,
9083
7835
  deps.config.maxTimeoutMs
@@ -9333,15 +8085,6 @@ var Runner = class extends EventEmitter3 {
9333
8085
  outboxWatcher;
9334
8086
  memoryWiring;
9335
8087
  assetMetadataIndexes = /* @__PURE__ */ new Map();
9336
- workspaceMirrors = /* @__PURE__ */ new Map();
9337
- assetOriginOutbox;
9338
- dropFolderAdapter;
9339
- dropFolderUploadRunner;
9340
- dropFolderTimer;
9341
- dropFolderWorkspaceId = "";
9342
- dropFolderWorkspaceDir = "";
9343
- dropFolderTickInFlight = false;
9344
- dropFolderStable = /* @__PURE__ */ new Map();
9345
8088
  state = "idle";
9346
8089
  startedAt = 0;
9347
8090
  workspaceId = "";
@@ -9383,7 +8126,6 @@ var Runner = class extends EventEmitter3 {
9383
8126
  this.servicePool = new ServicePool();
9384
8127
  this.installStaticHostedAgentFromEnv();
9385
8128
  this.loadAgentsFromDb();
9386
- await this.prepareLocalProfiles();
9387
8129
  this.syncQueue = new SyncQueue(this.db);
9388
8130
  this.syncWorker = new SyncWorker({ queue: this.syncQueue, flush: (row) => this.flushSyncRow(row) });
9389
8131
  this.syncWorker.start();
@@ -9405,10 +8147,9 @@ var Runner = class extends EventEmitter3 {
9405
8147
  );
9406
8148
  }
9407
8149
  if (this.workspaceId) {
9408
- this.syncAssetState(this.workspaceId).catch(
9409
- (err) => console.error("[Daemon] Initial asset sync failed:", err.message)
8150
+ this.syncAssetMetadata(this.workspaceId).catch(
8151
+ (err) => console.error("[Daemon] Initial asset metadata sync failed:", err.message)
9410
8152
  );
9411
- await this.ensureDropFolderRuntime(this.workspaceId);
9412
8153
  }
9413
8154
  const containerId = process.env.PRISMER_CONTAINER_ID;
9414
8155
  const isContainer = !!containerId || process.env.PRISMER_RUNTIME_MODE === "container";
@@ -9459,8 +8200,7 @@ var Runner = class extends EventEmitter3 {
9459
8200
  // an actual agent process.
9460
8201
  attachMemory: this.memoryWiring ? attachMemoryRpc({ runtime: this.memoryWiring.runtime }) : void 0,
9461
8202
  attachAsset: attachAssetRpc({
9462
- resolveIndex: (workspaceId) => this.assetMetadataIndexes.get(workspaceId),
9463
- assetCache: this.assetCache
8203
+ resolveIndex: (workspaceId) => this.assetMetadataIndexes.get(workspaceId)
9464
8204
  })
9465
8205
  });
9466
8206
  await this.localServer.start();
@@ -9509,7 +8249,6 @@ var Runner = class extends EventEmitter3 {
9509
8249
  this.memoryWiring = void 0;
9510
8250
  this.ws?.close();
9511
8251
  this.outboxWatcher?.stop();
9512
- this.stopDropFolderRuntime();
9513
8252
  await this.servicePool?.shutdown();
9514
8253
  await this.localServer?.stop();
9515
8254
  try {
@@ -9566,7 +8305,6 @@ var Runner = class extends EventEmitter3 {
9566
8305
  runningTaskIds: Array.from(this.runningTasks.keys()),
9567
8306
  observability: {
9568
8307
  adapters: this.snapshotAdapterObservability(),
9569
- assetSync: this.snapshotAssetSyncObservability(),
9570
8308
  ...this.lastTaskError ? { lastTaskError: this.lastTaskError } : {}
9571
8309
  }
9572
8310
  };
@@ -9602,35 +8340,6 @@ var Runner = class extends EventEmitter3 {
9602
8340
  });
9603
8341
  }
9604
8342
  }
9605
- async prepareLocalProfiles() {
9606
- const rows = this.db.prepare(
9607
- `SELECT id, workspace_id, agent_im_user_id, adapter_name, name, config, version, synced_at
9608
- FROM agent_profiles
9609
- WHERE deleted_at IS NULL`
9610
- ).all();
9611
- for (const row of rows) {
9612
- const adapter = this.registry.get(row.adapter_name);
9613
- if (!adapter?.prepareProfile) continue;
9614
- try {
9615
- await adapter.prepareProfile({
9616
- id: row.id,
9617
- workspaceId: row.workspace_id,
9618
- agentImUserId: row.agent_im_user_id,
9619
- adapterName: row.adapter_name,
9620
- name: row.name,
9621
- config: parseProfileConfig(row.config),
9622
- version: row.version,
9623
- createdAt: new Date(row.synced_at ?? Date.now()),
9624
- updatedAt: new Date(row.synced_at ?? Date.now())
9625
- });
9626
- } catch (err) {
9627
- process.stderr.write(
9628
- `[daemon] local profile preflight skipped agent=${row.agent_im_user_id} profile=${row.id}: ${err.message}
9629
- `
9630
- );
9631
- }
9632
- }
9633
- }
9634
8343
  /**
9635
8344
  * Register an in-process AgentProfile snapshot (called by agent CLI / sync layer).
9636
8345
  * Used to populate the `agents` list in agent.host.declare.
@@ -9688,10 +8397,10 @@ var Runner = class extends EventEmitter3 {
9688
8397
  const rawJson = process.env.PRISMER_HOSTED_AGENT_JSON;
9689
8398
  let raw;
9690
8399
  if (rawFile) {
9691
- if (!existsSync13(rawFile)) {
8400
+ if (!existsSync12(rawFile)) {
9692
8401
  throw new Error(`PRISMER_HOSTED_AGENT_FILE not found: ${rawFile}`);
9693
8402
  }
9694
- raw = readFileSync11(rawFile, "utf8");
8403
+ raw = readFileSync9(rawFile, "utf8");
9695
8404
  } else if (rawJson) {
9696
8405
  raw = rawJson;
9697
8406
  }
@@ -9793,7 +8502,7 @@ var Runner = class extends EventEmitter3 {
9793
8502
  name: a.name,
9794
8503
  adapterName: a.adapterName,
9795
8504
  capabilities: a.capabilities,
9796
- profiles: Array.from(a.profiles.entries()).map(([id, version]) => ({ id, version }))
8505
+ profiles: Array.from(a.profiles.entries()).map(([id, version2]) => ({ id, version: version2 }))
9797
8506
  }))
9798
8507
  };
9799
8508
  this.ws.send(envelope("agent.host.declare", payload, this.config.daemon_id));
@@ -9835,29 +8544,17 @@ var Runner = class extends EventEmitter3 {
9835
8544
  (err) => console.error("[Daemon] Initial memory sync failed:", err.message)
9836
8545
  );
9837
8546
  }
9838
- this.syncAssetState(this.workspaceId).catch(
9839
- (err) => console.error("[Daemon] Asset sync failed:", err.message)
8547
+ this.syncAssetMetadata(this.workspaceId).catch(
8548
+ (err) => console.error("[Daemon] Asset metadata sync failed:", err.message)
9840
8549
  );
9841
- await this.ensureDropFolderRuntime(this.workspaceId);
9842
8550
  for (const id of payload.profilesToSync) {
9843
8551
  try {
9844
- await this.servicePool.drop(id);
9845
8552
  await this.syncProfileFromCloud(id);
9846
8553
  } catch (err) {
9847
8554
  this.emit("sync-error", err);
9848
8555
  }
9849
8556
  }
9850
- for (const id of payload.profilesToDelete ?? []) {
9851
- const row = this.db.prepare("SELECT agent_im_user_id FROM agent_profiles WHERE id = ?").get(id);
9852
- this.db.prepare("DELETE FROM agent_profiles WHERE id = ?").run(id);
9853
- if (row) {
9854
- this.db.prepare("DELETE FROM agents WHERE im_user_id = ?").run(row.agent_im_user_id);
9855
- }
9856
- process.stderr.write(`[daemon] tombstone profile=${id}
9857
- `);
9858
- }
9859
- if (payload.profilesToDelete?.length) this.loadAgentsFromDb();
9860
- if ((payload.profilesToSync.length > 0 || (payload.profilesToDelete?.length ?? 0) > 0) && this.wsConnected) this.sendDeclare();
8557
+ if (payload.profilesToSync.length > 0 && this.wsConnected) this.sendDeclare();
9861
8558
  this.emit("host-acked", payload);
9862
8559
  }
9863
8560
  async onTaskDispatch(payload, requestId) {
@@ -9898,14 +8595,8 @@ var Runner = class extends EventEmitter3 {
9898
8595
  }
9899
8596
  });
9900
8597
  this.ws.send(envelope("task.dispatch.reply", reply, requestId));
9901
- if (!reply.ok) {
9902
- const code = reply.error?.code ?? "unknown";
9903
- const message = reply.error?.message ?? "unknown error";
9904
- process.stderr.write(`[daemon] dispatch failed task=${payload.taskId} code=${code} message=${message}
9905
- `);
9906
- }
9907
8598
  } else {
9908
- const reply = await handleDispatch(payload, requestId, {
8599
+ await handleDispatch(payload, requestId, {
9909
8600
  registry: this.registry,
9910
8601
  cloud: this.cloud,
9911
8602
  uriResolver: this.uriResolver,
@@ -9921,12 +8612,6 @@ var Runner = class extends EventEmitter3 {
9921
8612
  if (running) running.lastProgressAt = Date.now();
9922
8613
  }
9923
8614
  });
9924
- if (!reply.ok) {
9925
- const code = reply.error?.code ?? "unknown";
9926
- const message = reply.error?.message ?? "unknown error";
9927
- process.stderr.write(`[daemon] dispatch failed task=${payload.taskId} code=${code} message=${message}
9928
- `);
9929
- }
9930
8615
  }
9931
8616
  process.stdout.write(`[daemon] dispatch done task=${payload.taskId}
9932
8617
  `);
@@ -9954,7 +8639,6 @@ var Runner = class extends EventEmitter3 {
9954
8639
  }
9955
8640
  async onAgentProfileChanged(payload) {
9956
8641
  try {
9957
- await this.servicePool.drop(payload.profileId);
9958
8642
  await this.syncProfileFromCloud(payload.profileId);
9959
8643
  if (this.wsConnected) this.sendDeclare();
9960
8644
  } catch (err) {
@@ -9962,21 +8646,7 @@ var Runner = class extends EventEmitter3 {
9962
8646
  }
9963
8647
  }
9964
8648
  async syncProfileFromCloud(profileId) {
9965
- let profile;
9966
- try {
9967
- profile = await this.cloud.get(`/api/im/agent_profiles/${encodeURIComponent(profileId)}`);
9968
- } catch (err) {
9969
- if (err instanceof CloudError && err.status === 404) {
9970
- const row = this.db.prepare("SELECT agent_im_user_id FROM agent_profiles WHERE id = ?").get(profileId);
9971
- this.db.prepare("DELETE FROM agent_profiles WHERE id = ?").run(profileId);
9972
- if (row) {
9973
- this.db.prepare("DELETE FROM agents WHERE im_user_id = ?").run(row.agent_im_user_id);
9974
- }
9975
- this.loadAgentsFromDb();
9976
- return;
9977
- }
9978
- throw err;
9979
- }
8649
+ const profile = await this.cloud.get(`/api/im/agent_profiles/${encodeURIComponent(profileId)}`);
9980
8650
  const agent = await this.resolveOwnedAgent(profile.agentImUserId);
9981
8651
  const adapter = this.registry.get(profile.adapterName);
9982
8652
  const capabilities = agent?.card?.capabilities?.length ? agent.card.capabilities : adapter?.capabilities ?? [];
@@ -10011,14 +8681,6 @@ var Runner = class extends EventEmitter3 {
10011
8681
  );
10012
8682
  });
10013
8683
  tx();
10014
- try {
10015
- await adapter?.prepareProfile?.(profile);
10016
- } catch (err) {
10017
- process.stderr.write(
10018
- `[daemon] profile preflight skipped agent=${profile.agentImUserId} profile=${profile.id}: ${err.message}
10019
- `
10020
- );
10021
- }
10022
8684
  this.loadAgentsFromDb();
10023
8685
  process.stdout.write(
10024
8686
  `[daemon] profile synced agent=${profile.agentImUserId} profile=${profile.id} adapter=${profile.adapterName}
@@ -10047,10 +8709,15 @@ var Runner = class extends EventEmitter3 {
10047
8709
  }
10048
8710
  async onAssetChanged(payload) {
10049
8711
  if (!payload.workspaceId) return;
8712
+ const index = this.assetMetadataIndexes.get(payload.workspaceId);
8713
+ if (!index) return;
10050
8714
  try {
10051
- await this.syncAssetState(payload.workspaceId);
8715
+ const result = await index.pullDelta();
8716
+ if (result.applied > 0) {
8717
+ console.log(`[Daemon] asset.changed workspace=${payload.workspaceId} applied=${result.applied}`);
8718
+ }
10052
8719
  } catch (err) {
10053
- console.error(`[Daemon] asset.changed sync failed workspace=${payload.workspaceId}:`, err.message);
8720
+ console.error(`[Daemon] asset.changed pullDelta failed workspace=${payload.workspaceId}:`, err.message);
10054
8721
  }
10055
8722
  }
10056
8723
  onWorkspaceFileChanged(payload) {
@@ -10088,15 +8755,6 @@ var Runner = class extends EventEmitter3 {
10088
8755
  }
10089
8756
  };
10090
8757
  }
10091
- snapshotAssetSyncObservability() {
10092
- return {
10093
- workspaceId: this.dropFolderWorkspaceId || null,
10094
- rootDir: this.dropFolderWorkspaceDir || null,
10095
- dropDir: this.dropFolderWorkspaceDir ? join16(this.dropFolderWorkspaceDir, "drop") : null,
10096
- uploadedDir: this.dropFolderWorkspaceDir ? join16(this.dropFolderWorkspaceDir, "uploaded") : null,
10097
- failedDir: this.dropFolderWorkspaceDir ? join16(this.dropFolderWorkspaceDir, "upload-failed") : null
10098
- };
10099
- }
10100
8758
  /**
10101
8759
  * Ensure an AssetMetadataIndex exists for the given workspace and pull
10102
8760
  * delta from cloud. Idempotent — creates the index on first call, reuses
@@ -10119,132 +8777,6 @@ var Runner = class extends EventEmitter3 {
10119
8777
  console.log(`[AssetMeta] workspace=${workspaceId}: ${result.applied} applied, cursor=${result.cursor}`);
10120
8778
  }
10121
8779
  }
10122
- /**
10123
- * Ensure a WorkspaceMirror exists for the workspace and pull path→asset
10124
- * bindings from cloud. Cold-start daemons previously only had this table
10125
- * populated by WS push or per-path URI fallback, so local asset state looked
10126
- * empty even though cloud files existed.
10127
- */
10128
- async syncWorkspaceFiles(workspaceId) {
10129
- let mirror = this.workspaceMirrors.get(workspaceId);
10130
- if (!mirror) {
10131
- const stateDir = `${this.paths.root}/${workspaceId}`;
10132
- mirror = new WorkspaceMirror({
10133
- db: this.db,
10134
- cloud: this.cloud,
10135
- workspaceId,
10136
- workspaceStateDir: stateDir
10137
- });
10138
- this.workspaceMirrors.set(workspaceId, mirror);
10139
- }
10140
- const result = await mirror.pullDelta();
10141
- if (result.applied > 0) {
10142
- console.log(`[WorkspaceFiles] workspace=${workspaceId}: ${result.applied} applied, cursor=${result.cursor ?? "null"}`);
10143
- }
10144
- }
10145
- async syncAssetState(workspaceId) {
10146
- if (!this.paths?.root) return;
10147
- await Promise.all([
10148
- this.syncAssetMetadata(workspaceId),
10149
- this.syncWorkspaceFiles(workspaceId)
10150
- ]);
10151
- }
10152
- /**
10153
- * Desktop asset ingress: watch a user-visible local asset folder
10154
- * and upload new files as IMAssets, preserving nested relative dirs in
10155
- * IMAsset.folderPath. The lower-level adapter/outbox/uploader already
10156
- * existed; this method wires them into the real daemon lifecycle.
10157
- */
10158
- async ensureDropFolderRuntime(workspaceId) {
10159
- if (!workspaceId || !this.paths?.root) return;
10160
- if (this.dropFolderWorkspaceId === workspaceId && this.dropFolderTimer) return;
10161
- this.stopDropFolderRuntime();
10162
- this.dropFolderWorkspaceId = workspaceId;
10163
- const workspaceRoot = resolveUserAssetWorkspaceDir(workspaceId);
10164
- this.dropFolderWorkspaceDir = workspaceRoot;
10165
- await mkdir(join16(workspaceRoot, "drop"), { recursive: true });
10166
- await mkdir(join16(workspaceRoot, "uploaded"), { recursive: true });
10167
- await mkdir(join16(workspaceRoot, "upload-failed"), { recursive: true });
10168
- this.assetOriginOutbox = new OriginOutbox({ dbPath: join16(this.paths.root, "asset-origin.db") });
10169
- this.dropFolderAdapter = new DropFolderAdapter({ workspaceDir: workspaceRoot });
10170
- this.dropFolderUploadRunner = new UploadRunner({
10171
- outbox: this.assetOriginOutbox,
10172
- cloud: new DaemonAssetUploadClient(this.config),
10173
- adapters: { "drop-folder": this.dropFolderAdapter },
10174
- maxAttempts: 3
10175
- });
10176
- const tick = () => {
10177
- this.runDropFolderTick().catch((err) => {
10178
- process.stderr.write(`[daemon] drop-folder sync failed: ${err.message}
10179
- `);
10180
- });
10181
- };
10182
- this.dropFolderTimer = setInterval(tick, 1e3);
10183
- tick();
10184
- process.stdout.write(`[daemon] drop-folder asset sync watching ${join16(workspaceRoot, "drop")}
10185
- `);
10186
- }
10187
- stopDropFolderRuntime() {
10188
- if (this.dropFolderTimer) {
10189
- clearInterval(this.dropFolderTimer);
10190
- this.dropFolderTimer = void 0;
10191
- }
10192
- try {
10193
- this.assetOriginOutbox?.close();
10194
- } catch {
10195
- }
10196
- this.assetOriginOutbox = void 0;
10197
- this.dropFolderAdapter = void 0;
10198
- this.dropFolderUploadRunner = void 0;
10199
- this.dropFolderWorkspaceId = "";
10200
- this.dropFolderWorkspaceDir = "";
10201
- this.dropFolderTickInFlight = false;
10202
- this.dropFolderStable.clear();
10203
- }
10204
- async runDropFolderTick() {
10205
- if (this.dropFolderTickInFlight) return;
10206
- const workspaceId = this.dropFolderWorkspaceId;
10207
- const adapter = this.dropFolderAdapter;
10208
- const outbox = this.assetOriginOutbox;
10209
- const runner = this.dropFolderUploadRunner;
10210
- if (!workspaceId || !adapter || !outbox || !runner) return;
10211
- this.dropFolderTickInFlight = true;
10212
- try {
10213
- const observations = await adapter.scanOnce(workspaceId);
10214
- for (const obs of observations) {
10215
- const id = await adapter.identifySource(obs);
10216
- if (!this.isDropFolderObservationStable(id.sourceRef, obs.detail)) continue;
10217
- outbox.enqueue({
10218
- workspaceId: obs.workspaceId,
10219
- originKind: "drop-folder",
10220
- sourceRef: id.sourceRef,
10221
- payloadJson: JSON.stringify(obs.detail),
10222
- hintsJson: JSON.stringify(id.hints ?? {}),
10223
- observedAt: obs.observedAt
10224
- });
10225
- }
10226
- const processed = await runner.drainOnce();
10227
- if (processed > 0) {
10228
- this.dropFolderStable.clear();
10229
- process.stdout.write(`[daemon] drop-folder uploaded/processed ${processed} asset(s)
10230
- `);
10231
- await this.syncAssetState(workspaceId).catch(
10232
- (err) => process.stderr.write(`[daemon] drop-folder post-sync failed: ${err.message}
10233
- `)
10234
- );
10235
- }
10236
- } finally {
10237
- this.dropFolderTickInFlight = false;
10238
- }
10239
- }
10240
- isDropFolderObservationStable(sourceRef, detail) {
10241
- const raw = detail && typeof detail === "object" ? detail : {};
10242
- const size = typeof raw.size === "number" ? raw.size : -1;
10243
- const mtime = typeof raw.mtime === "number" ? raw.mtime : -1;
10244
- const previous = this.dropFolderStable.get(sourceRef);
10245
- this.dropFolderStable.set(sourceRef, { size, mtime });
10246
- return Boolean(previous && previous.size === size && previous.mtime === mtime);
10247
- }
10248
8780
  /**
10249
8781
  * SyncWorker FlushFn — pushes local writes to cloud.
10250
8782
  *
@@ -10281,44 +8813,44 @@ var Runner = class extends EventEmitter3 {
10281
8813
  const id = encodeURIComponent(row.resource_id);
10282
8814
  const body = row.operation === "delete" ? void 0 : safeJsonParse(row.payload);
10283
8815
  let method;
10284
- let path9;
8816
+ let path7;
10285
8817
  switch (op) {
10286
8818
  case "workspace.create":
10287
8819
  method = "POST";
10288
- path9 = "/api/im/workspaces";
8820
+ path7 = "/api/im/workspaces";
10289
8821
  break;
10290
8822
  case "workspace.update":
10291
8823
  method = "PATCH";
10292
- path9 = `/api/im/workspaces/${id}`;
8824
+ path7 = `/api/im/workspaces/${id}`;
10293
8825
  break;
10294
8826
  case "workspace.delete":
10295
8827
  method = "DELETE";
10296
- path9 = `/api/im/workspaces/${id}`;
8828
+ path7 = `/api/im/workspaces/${id}`;
10297
8829
  break;
10298
8830
  // agent.create must use /register — POST /api/im/agents is not exposed.
10299
8831
  case "agent.create":
10300
8832
  method = "POST";
10301
- path9 = "/api/im/register";
8833
+ path7 = "/api/im/register";
10302
8834
  break;
10303
8835
  case "agent.update":
10304
8836
  method = "PATCH";
10305
- path9 = `/api/im/agents/${id}`;
8837
+ path7 = `/api/im/agents/${id}`;
10306
8838
  break;
10307
8839
  case "agent.delete":
10308
8840
  method = "DELETE";
10309
- path9 = `/api/im/agents/${id}`;
8841
+ path7 = `/api/im/agents/${id}`;
10310
8842
  break;
10311
8843
  case "agent_profile.create":
10312
8844
  method = "POST";
10313
- path9 = "/api/im/agent_profiles";
8845
+ path7 = "/api/im/agent_profiles";
10314
8846
  break;
10315
8847
  case "agent_profile.update":
10316
8848
  method = "PATCH";
10317
- path9 = `/api/im/agent_profiles/${id}`;
8849
+ path7 = `/api/im/agent_profiles/${id}`;
10318
8850
  break;
10319
8851
  case "agent_profile.delete":
10320
8852
  method = "DELETE";
10321
- path9 = `/api/im/agent_profiles/${id}`;
8853
+ path7 = `/api/im/agent_profiles/${id}`;
10322
8854
  break;
10323
8855
  default:
10324
8856
  process.stderr.write(`[daemon] sync flush op=${op} id=${row.id} result=drop (unknown op)
@@ -10327,7 +8859,7 @@ var Runner = class extends EventEmitter3 {
10327
8859
  }
10328
8860
  let res;
10329
8861
  try {
10330
- res = await this.cloud.request(method, path9, { body });
8862
+ res = await this.cloud.request(method, path7, { body });
10331
8863
  } catch (err) {
10332
8864
  process.stderr.write(
10333
8865
  `[daemon] sync flush op=${op} id=${row.id} result=retry (threw: ${err.message})
@@ -10350,89 +8882,6 @@ var Runner = class extends EventEmitter3 {
10350
8882
  return { ok: res.ok, status: res.status, message: res.error?.message };
10351
8883
  }
10352
8884
  };
10353
- var DaemonAssetUploadClient = class {
10354
- constructor(config) {
10355
- this.config = config;
10356
- }
10357
- config;
10358
- async uploadAsset(req) {
10359
- const form = new FormData();
10360
- form.set("workspaceId", req.workspaceId);
10361
- form.set("kind", "file");
10362
- form.set("metadata", JSON.stringify(req.metadata));
10363
- if (req.folderPath !== void 0) form.set("folderPath", req.folderPath);
10364
- form.set("file", new Blob([req.bytes], { type: req.mime ?? "application/octet-stream" }), req.filename);
10365
- const url = `${this.config.cloud_api_base.replace(/\/$/, "")}/api/im/assets`;
10366
- const controller = new AbortController();
10367
- const timer = setTimeout(() => controller.abort(), 6e4);
10368
- let res;
10369
- try {
10370
- res = await fetch(url, {
10371
- method: "POST",
10372
- headers: { Authorization: `Bearer ${this.config.api_key}` },
10373
- body: form,
10374
- signal: controller.signal
10375
- });
10376
- } catch (err) {
10377
- if (err.name === "AbortError") {
10378
- throw new Error("asset upload timed out after 60000ms");
10379
- }
10380
- throw err;
10381
- } finally {
10382
- clearTimeout(timer);
10383
- }
10384
- const text = await res.text();
10385
- let body;
10386
- try {
10387
- body = text ? JSON.parse(text) : void 0;
10388
- } catch {
10389
- body = text;
10390
- }
10391
- if (!res.ok || isCloudErrorEnvelope(body)) {
10392
- throw new Error(`asset upload failed (${res.status}): ${cloudUploadErrorMessage(body)}`);
10393
- }
10394
- const data = unwrapCloudData(body);
10395
- const assetId = stringValue(data, "id");
10396
- const contentHash = stringValue(data, "contentHash");
10397
- if (!assetId || !contentHash) {
10398
- throw new Error("asset upload response missing id/contentHash");
10399
- }
10400
- return { assetId, contentHash };
10401
- }
10402
- };
10403
- function isCloudErrorEnvelope(raw) {
10404
- return Boolean(raw && typeof raw === "object" && "ok" in raw && raw.ok === false);
10405
- }
10406
- function cloudUploadErrorMessage(raw) {
10407
- if (raw && typeof raw === "object") {
10408
- const error = raw.error;
10409
- if (typeof error === "string") return error;
10410
- if (error && typeof error === "object") {
10411
- const message = error.message;
10412
- if (typeof message === "string") return message;
10413
- }
10414
- }
10415
- return typeof raw === "string" ? raw : "request failed";
10416
- }
10417
- function unwrapCloudData(raw) {
10418
- if (raw && typeof raw === "object") {
10419
- const data = raw.data;
10420
- if (data && typeof data === "object" && !Array.isArray(data)) return data;
10421
- }
10422
- return {};
10423
- }
10424
- function stringValue(obj, key) {
10425
- const value = obj[key];
10426
- return typeof value === "string" && value.length > 0 ? value : void 0;
10427
- }
10428
- function resolveUserAssetWorkspaceDir(workspaceId) {
10429
- const override = process.env.PRISMER_ASSET_SYNC_ROOT;
10430
- if (override) return join16(override, workspaceId);
10431
- const home = homedir7();
10432
- const desktop = join16(home, "Desktop");
10433
- const base = existsSync13(desktop) ? desktop : home;
10434
- return join16(base, "Prismer Assets", workspaceId);
10435
- }
10436
8885
  function readTargetDaemonId(payload) {
10437
8886
  if (typeof payload.targetDaemonId === "string" && payload.targetDaemonId.length > 0) {
10438
8887
  return payload.targetDaemonId;
@@ -10483,14 +8932,6 @@ function validateStaticHostedAgent(raw) {
10483
8932
  }
10484
8933
  };
10485
8934
  }
10486
- function parseProfileConfig(raw) {
10487
- try {
10488
- const parsed = JSON.parse(raw);
10489
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
10490
- } catch {
10491
- return {};
10492
- }
10493
- }
10494
8935
  function safeJsonParse(raw) {
10495
8936
  try {
10496
8937
  return JSON.parse(raw);
@@ -10517,7 +8958,7 @@ function buildDaemonCommand() {
10517
8958
  async function runForeground(opts) {
10518
8959
  const paths = resolvePaths();
10519
8960
  const existingPid = readPidFile(paths);
10520
- if (existingPid && pidAlive2(existingPid)) {
8961
+ if (existingPid && pidAlive(existingPid)) {
10521
8962
  exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
10522
8963
  }
10523
8964
  writePidFile(paths, process.pid);
@@ -10541,7 +8982,7 @@ function buildDaemonCommand() {
10541
8982
  while (!configExists(paths)) {
10542
8983
  if (stopRequested) return;
10543
8984
  getUI().info(`[daemon] waiting for ${paths.configFile} \u2014 run \`prismer setup\` to create it`);
10544
- await sleep2(5e3);
8985
+ await sleep(5e3);
10545
8986
  }
10546
8987
  runner = new Runner({
10547
8988
  startLocalServer: opts.localServer !== false,
@@ -10570,12 +9011,12 @@ function buildDaemonCommand() {
10570
9011
  async function startBackground(opts) {
10571
9012
  const paths = resolvePaths();
10572
9013
  const existingPid = readPidFile(paths);
10573
- if (existingPid && pidAlive2(existingPid)) {
9014
+ if (existingPid && pidAlive(existingPid)) {
10574
9015
  exitWithError(`Daemon already running (pid ${existingPid}). Use \`prismer daemon stop\` first.`);
10575
9016
  }
10576
- if (!existsSync14(paths.logsDir)) mkdirSync9(paths.logsDir, { recursive: true });
10577
- const logFile = join17(paths.logsDir, "daemon.log");
10578
- const fd = openSync2(logFile, "a");
9017
+ if (!existsSync13(paths.logsDir)) mkdirSync8(paths.logsDir, { recursive: true });
9018
+ const logFile = join11(paths.logsDir, "daemon.log");
9019
+ const fd = openSync(logFile, "a");
10579
9020
  const args = [process.argv[1], "daemon", "run"];
10580
9021
  if (opts.port) args.push("--port", String(opts.port));
10581
9022
  if (opts.localServer === false) args.push("--no-local-server");
@@ -10587,9 +9028,9 @@ function buildDaemonCommand() {
10587
9028
  child.unref();
10588
9029
  const deadline = Date.now() + 5e3;
10589
9030
  while (Date.now() < deadline) {
10590
- await sleep2(200);
9031
+ await sleep(200);
10591
9032
  const pid = readPidFile(paths);
10592
- if (pid && pidAlive2(pid)) {
9033
+ if (pid && pidAlive(pid)) {
10593
9034
  if (opts.json) printJson({ ok: true, pid, logFile });
10594
9035
  else getUI().ok("Daemon started", `pid ${pid} \xB7 logs ${logFile}`);
10595
9036
  return;
@@ -10604,7 +9045,7 @@ function buildDaemonCommand() {
10604
9045
  const paths = resolvePaths();
10605
9046
  const pid = readPidFile(paths);
10606
9047
  if (!pid) exitWithError("No daemon.pid found \u2014 daemon does not appear to be running.");
10607
- if (!pidAlive2(pid)) {
9048
+ if (!pidAlive(pid)) {
10608
9049
  clearPidFile(paths);
10609
9050
  getUI().warn("Daemon was not alive", `pid ${pid}; cleared stale pid file`);
10610
9051
  return;
@@ -10616,12 +9057,12 @@ function buildDaemonCommand() {
10616
9057
  }
10617
9058
  const deadline = Date.now() + (opts.timeout ?? 5e3);
10618
9059
  while (Date.now() < deadline) {
10619
- if (!pidAlive2(pid)) {
9060
+ if (!pidAlive(pid)) {
10620
9061
  clearPidFile(paths);
10621
9062
  getUI().ok("Daemon stopped", `pid ${pid}`);
10622
9063
  return;
10623
9064
  }
10624
- await sleep2(200);
9065
+ await sleep(200);
10625
9066
  }
10626
9067
  process.stderr.write(`Daemon did not exit within timeout; pid ${pid} may still be alive.
10627
9068
  `);
@@ -10641,7 +9082,7 @@ function buildDaemonCommand() {
10641
9082
  cmd.command("status").description("Show daemon process + WS state").option("--json", "Output JSON (default)").action(async () => {
10642
9083
  const paths = resolvePaths();
10643
9084
  const pid = readPidFile(paths);
10644
- const alive = pid ? pidAlive2(pid) : false;
9085
+ const alive = pid ? pidAlive(pid) : false;
10645
9086
  if (!pid || !alive) {
10646
9087
  printJson({ running: false, pid: pid ?? null, paths: { config: paths.configFile } });
10647
9088
  return;
@@ -10658,8 +9099,8 @@ function buildDaemonCommand() {
10658
9099
  });
10659
9100
  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) => {
10660
9101
  const paths = resolvePaths();
10661
- const logFile = join17(paths.logsDir, "daemon.log");
10662
- if (!existsSync14(logFile)) {
9102
+ const logFile = join11(paths.logsDir, "daemon.log");
9103
+ if (!existsSync13(logFile)) {
10663
9104
  exitWithError(`No daemon log found at ${logFile}. Start the daemon with \`prismer daemon start\`.`);
10664
9105
  }
10665
9106
  const lines = Math.max(1, opts.tail);
@@ -10669,9 +9110,9 @@ function buildDaemonCommand() {
10669
9110
  });
10670
9111
  return cmd;
10671
9112
  }
10672
- async function tailFromEnd(path9, lines) {
9113
+ async function tailFromEnd(path7, lines) {
10673
9114
  const { open } = await import("fs/promises");
10674
- const handle = await open(path9, "r");
9115
+ const handle = await open(path7, "r");
10675
9116
  try {
10676
9117
  const stat = await handle.stat();
10677
9118
  let size = stat.size;
@@ -10699,14 +9140,14 @@ async function tailFromEnd(path9, lines) {
10699
9140
  await handle.close();
10700
9141
  }
10701
9142
  }
10702
- async function followFile(path9) {
10703
- let offset = statSync4(path9).size;
9143
+ async function followFile(path7) {
9144
+ let offset = statSync3(path7).size;
10704
9145
  for (; ; ) {
10705
- await sleep2(1e3);
10706
- const size = statSync4(path9).size;
9146
+ await sleep(1e3);
9147
+ const size = statSync3(path7).size;
10707
9148
  if (size < offset) offset = 0;
10708
9149
  if (size === offset) continue;
10709
- const stream = createReadStream(path9, { start: offset, end: size - 1, encoding: "utf8" });
9150
+ const stream = createReadStream(path7, { start: offset, end: size - 1, encoding: "utf8" });
10710
9151
  for await (const chunk of stream) process.stdout.write(chunk);
10711
9152
  offset = size;
10712
9153
  }
@@ -10715,15 +9156,15 @@ async function followFile(path9) {
10715
9156
  // src/cli/commands/events.ts
10716
9157
  init_util();
10717
9158
  import { Command as Command9 } from "commander";
10718
- import { createReadStream as createReadStream2, existsSync as existsSync15 } from "fs";
10719
- import { homedir as homedir8 } from "os";
10720
- import { join as join18 } from "path";
9159
+ import { createReadStream as createReadStream2, existsSync as existsSync14 } from "fs";
9160
+ import { homedir as homedir5 } from "os";
9161
+ import { join as join12 } from "path";
10721
9162
  import { createInterface } from "readline";
10722
9163
  var DEFAULT_LIMIT2 = 50;
10723
9164
  function buildEventsCommand() {
10724
9165
  return addEventOptions(new Command9("events").description("Read local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
10725
9166
  const file = eventsPath();
10726
- if (!existsSync15(file)) {
9167
+ if (!existsSync14(file)) {
10727
9168
  printJson(unavailable(file));
10728
9169
  process.exitCode = 1;
10729
9170
  return;
@@ -10740,7 +9181,7 @@ function buildEventsCommand() {
10740
9181
  function buildEventsStatsCommand() {
10741
9182
  return addEventOptions(new Command9("events:stats").description("Summarize local PARA events from ~/.prismer/para/events.jsonl")).action(async (opts) => {
10742
9183
  const file = eventsPath();
10743
- if (!existsSync15(file)) {
9184
+ if (!existsSync14(file)) {
10744
9185
  printJson(unavailable(file));
10745
9186
  process.exitCode = 1;
10746
9187
  return;
@@ -10755,10 +9196,10 @@ function buildEventsStatsCommand() {
10755
9196
  });
10756
9197
  }
10757
9198
  function addEventOptions(cmd) {
10758
- 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)");
9199
+ 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)");
10759
9200
  }
10760
9201
  function eventsPath() {
10761
- return join18(process.env.PRISMER_HOME ?? join18(homedir8(), ".prismer"), "para", "events.jsonl");
9202
+ return join12(process.env.PRISMER_HOME ?? join12(homedir5(), ".prismer"), "para", "events.jsonl");
10762
9203
  }
10763
9204
  function unavailable(file) {
10764
9205
  return {
@@ -10836,15 +9277,15 @@ function normalizeFilters(opts) {
10836
9277
  function cleanFilters(filters) {
10837
9278
  return Object.fromEntries(Object.entries(filters).filter(([, value]) => value !== void 0));
10838
9279
  }
10839
- function parsePositiveInt4(v) {
9280
+ function parsePositiveInt3(v) {
10840
9281
  const n = Number.parseInt(v, 10);
10841
9282
  return Number.isFinite(n) && n > 0 ? n : DEFAULT_LIMIT2;
10842
9283
  }
10843
9284
 
10844
9285
  // src/cli/commands/memory.ts
10845
- import Database5 from "better-sqlite3";
9286
+ import Database4 from "better-sqlite3";
10846
9287
  import { Command as Command10 } from "commander";
10847
- import { existsSync as existsSync16 } from "fs";
9288
+ import { existsSync as existsSync15 } from "fs";
10848
9289
  init_util();
10849
9290
  var LOCAL_BASE = process.env.PRISMER_DAEMON_URL ?? "http://127.0.0.1:3210";
10850
9291
  function buildMemoryCommand() {
@@ -10870,7 +9311,7 @@ function buildMemoryCommand() {
10870
9311
  }));
10871
9312
  process.exitCode = 1;
10872
9313
  });
10873
- 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) => {
9314
+ 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) => {
10874
9315
  const limit = clampLimit(opts.limit);
10875
9316
  const daemonPaths = [];
10876
9317
  if (opts.workspaceId) {
@@ -10889,7 +9330,7 @@ function buildMemoryCommand() {
10889
9330
  printJson(unavailable2("list", snapshot, { items: snapshot.items, limit }));
10890
9331
  process.exitCode = 1;
10891
9332
  });
10892
- 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) => {
9333
+ 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) => {
10893
9334
  const query = opts.query ?? argQuery ?? "";
10894
9335
  const limit = clampLimit(opts.limit);
10895
9336
  const encoded = encodeURIComponent(query);
@@ -10933,9 +9374,9 @@ function buildMemoryCommand() {
10933
9374
  }
10934
9375
  async function tryDaemon(methods, paths) {
10935
9376
  for (const method of methods) {
10936
- for (const path9 of paths) {
9377
+ for (const path7 of paths) {
10937
9378
  try {
10938
- const res = await fetch(`${LOCAL_BASE}${path9}`, { method, signal: AbortSignal.timeout(1500) });
9379
+ const res = await fetch(`${LOCAL_BASE}${path7}`, { method, signal: AbortSignal.timeout(1500) });
10939
9380
  if (res.status === 404) continue;
10940
9381
  const body = await readJson4(res);
10941
9382
  if (!res.ok) {
@@ -10943,12 +9384,12 @@ async function tryDaemon(methods, paths) {
10943
9384
  ok: false,
10944
9385
  error: {
10945
9386
  code: `daemon_http_${res.status}`,
10946
- message: messageFromBody(body) ?? `Daemon endpoint ${method} ${path9} returned HTTP ${res.status}`
9387
+ message: messageFromBody(body) ?? `Daemon endpoint ${method} ${path7} returned HTTP ${res.status}`
10947
9388
  },
10948
- checks: { daemon: { url: LOCAL_BASE, endpoint: path9, reachable: true } }
9389
+ checks: { daemon: { url: LOCAL_BASE, endpoint: path7, reachable: true } }
10949
9390
  };
10950
9391
  }
10951
- return normalizeDaemonBody(body, { method, path: path9 });
9392
+ return normalizeDaemonBody(body, { method, path: path7 });
10952
9393
  } catch {
10953
9394
  continue;
10954
9395
  }
@@ -10996,7 +9437,7 @@ function readCacheSnapshot(limit) {
10996
9437
  const paths = resolvePaths();
10997
9438
  const empty = {
10998
9439
  dbPath: paths.localDb,
10999
- dbExists: existsSync16(paths.localDb),
9440
+ dbExists: existsSync15(paths.localDb),
11000
9441
  tables: {
11001
9442
  cached_assets: { exists: false, count: 0, sizeBytes: 0 },
11002
9443
  workspace_files_mirror: { exists: false, count: 0 }
@@ -11006,7 +9447,7 @@ function readCacheSnapshot(limit) {
11006
9447
  if (!empty.dbExists) return empty;
11007
9448
  let db;
11008
9449
  try {
11009
- db = new Database5(paths.localDb, { readonly: true, fileMustExist: true });
9450
+ db = new Database4(paths.localDb, { readonly: true, fileMustExist: true });
11010
9451
  const hasAssets = tableExists(db, "cached_assets");
11011
9452
  const hasFiles = tableExists(db, "workspace_files_mirror");
11012
9453
  const assets = hasAssets ? db.prepare("SELECT COUNT(*) AS count, COALESCE(SUM(size_bytes), 0) AS sizeBytes FROM cached_assets").get() : { count: 0, sizeBytes: 0 };
@@ -11095,7 +9536,7 @@ function searchableText(item) {
11095
9536
  item.localPath
11096
9537
  ].filter(Boolean).join(" ");
11097
9538
  }
11098
- function parsePositiveInt5(v) {
9539
+ function parsePositiveInt4(v) {
11099
9540
  const n = Number.parseInt(v, 10);
11100
9541
  return Number.isFinite(n) && n > 0 ? n : 20;
11101
9542
  }
@@ -11120,16 +9561,15 @@ import { Command as Command11 } from "commander";
11120
9561
  // src/pair.ts
11121
9562
  import { generateKeyPairSync } from "crypto";
11122
9563
  import { hostname as hostname2 } from "os";
11123
- import { setTimeout as sleep3 } from "timers/promises";
9564
+ import { setTimeout as sleep2 } from "timers/promises";
11124
9565
  import qrcode from "qrcode";
11125
9566
 
11126
9567
  // src/daemon-id.ts
11127
- import { randomBytes } from "crypto";
11128
9568
  import { hostname } from "os";
11129
9569
  var PREFIX = "daemon-";
11130
9570
  function newDaemonId() {
11131
9571
  const host = hostname().replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unknown";
11132
- return `${PREFIX}${host}-${randomBytes(6).toString("hex")}`;
9572
+ return `${PREFIX}${host}`;
11133
9573
  }
11134
9574
 
11135
9575
  // src/pair.ts
@@ -11200,7 +9640,7 @@ Scan with Lumin to approve, or open: ${offer.qrUrl}
11200
9640
  const pollIntervalMs = opts.pollIntervalMs ?? 5e3;
11201
9641
  for (let i = 0; i < maxAttempts; i += 1) {
11202
9642
  if (i > 0 || !localOnlyMode) {
11203
- await sleep3(pollIntervalMs);
9643
+ await sleep2(pollIntervalMs);
11204
9644
  }
11205
9645
  const res = await cloud.request(
11206
9646
  "GET",
@@ -11281,9 +9721,9 @@ function buildPairCommand() {
11281
9721
 
11282
9722
  // src/cli/commands/profile.ts
11283
9723
  import { Command as Command12 } from "commander";
11284
- import { existsSync as existsSync17, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "fs";
9724
+ import { existsSync as existsSync16, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
11285
9725
  import { tmpdir } from "os";
11286
- import { join as join19 } from "path";
9726
+ import { join as join13 } from "path";
11287
9727
  import { spawnSync as spawnSync4 } from "child_process";
11288
9728
 
11289
9729
  // src/templates/roles/product-manager.json
@@ -11407,12 +9847,12 @@ function buildProfileCommand() {
11407
9847
  const profile = await cloud.get(
11408
9848
  `/api/im/agent_profiles/${encodeURIComponent(profileId)}`
11409
9849
  );
11410
- const tmpFile = join19(tmpdir(), `prismer-profile-${profileId}.json`);
11411
- writeFileSync9(tmpFile, JSON.stringify(profile.config, null, 2), "utf8");
9850
+ const tmpFile = join13(tmpdir(), `prismer-profile-${profileId}.json`);
9851
+ writeFileSync8(tmpFile, JSON.stringify(profile.config, null, 2), "utf8");
11412
9852
  const editor = process.env.EDITOR || "vi";
11413
9853
  const ed = spawnSync4(editor, [tmpFile], { stdio: "inherit" });
11414
9854
  if (ed.status !== 0) exitWithError(`editor exited ${ed.status}`, { code: "editor_failed" });
11415
- const newConfig = JSON.parse(readFileSync12(tmpFile, "utf8"));
9855
+ const newConfig = JSON.parse(readFileSync10(tmpFile, "utf8"));
11416
9856
  const res = await cloud.request("PATCH", `/api/im/agent_profiles/${encodeURIComponent(profileId)}`, {
11417
9857
  body: { config: newConfig, version: profile.version }
11418
9858
  });
@@ -11433,9 +9873,9 @@ function mkCloud4() {
11433
9873
  }
11434
9874
  function readJsonArg(arg) {
11435
9875
  if (arg.startsWith("@")) {
11436
- const path9 = arg.slice(1);
11437
- if (!existsSync17(path9)) throw new Error(`File not found: ${path9}`);
11438
- return JSON.parse(readFileSync12(path9, "utf8"));
9876
+ const path7 = arg.slice(1);
9877
+ if (!existsSync16(path7)) throw new Error(`File not found: ${path7}`);
9878
+ return JSON.parse(readFileSync10(path7, "utf8"));
11439
9879
  }
11440
9880
  return JSON.parse(arg);
11441
9881
  }
@@ -11446,200 +9886,13 @@ async function resolveDefaultWorkspaceId(cloud) {
11446
9886
  return def.id;
11447
9887
  }
11448
9888
 
11449
- // src/cli/commands/reset.ts
11450
- import { Command as Command13 } from "commander";
11451
- import { execFileSync as execFileSync2 } from "child_process";
11452
- import { existsSync as existsSync18, mkdirSync as mkdirSync10, rmSync, renameSync as renameSync2 } from "fs";
11453
- import { homedir as homedir9 } from "os";
11454
- import { dirname as dirname10, join as join20 } from "path";
11455
- import { setTimeout as sleep4 } from "timers/promises";
11456
- init_util();
11457
- init_ui();
11458
- function buildResetCommand() {
11459
- return new Command13("reset").description("Stop daemon and reset local runtime state so setup can bind another account").option("--yes", "Actually perform the reset").option("--check", "Print the reset plan without changing files").option("--wipe", "Delete local state instead of archiving it").option("--timeout <ms>", "Time to wait for daemon shutdown (default 5000)", (v) => Number.parseInt(v, 10), 5e3).option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
11460
- const paths = resolvePaths();
11461
- const pid = readPidFile(paths);
11462
- const runningPid = pid && pidAlive2(pid) ? pid : null;
11463
- const archivePath = opts.wipe ? null : `${paths.root}.reset.${timestamp()}`;
11464
- const assetSyncRoot = resolveAssetSyncRoot();
11465
- const assetSyncArchivePath = opts.wipe ? null : `${assetSyncRoot}.reset.${timestamp()}`;
11466
- const hermesHome = resolveHermesHome();
11467
- const hermesArchivePath = opts.wipe ? null : `${hermesHome}.reset.${timestamp()}`;
11468
- const hermesGatewayPids = findHermesGatewayPids2();
11469
- const plan = {
11470
- root: paths.root,
11471
- exists: existsSync18(paths.root),
11472
- runningPid,
11473
- mode: opts.wipe ? "wipe" : "archive",
11474
- archivePath,
11475
- assetSyncRoot,
11476
- assetSyncExists: existsSync18(assetSyncRoot),
11477
- assetSyncArchivePath,
11478
- hermesHome,
11479
- hermesExists: existsSync18(hermesHome),
11480
- hermesArchivePath,
11481
- hermesGatewayPids,
11482
- willStopDaemon: runningPid !== null,
11483
- nextStep: "prismer setup"
11484
- };
11485
- if (opts.check) {
11486
- printPlan(plan, opts.json);
11487
- return;
11488
- }
11489
- if (!opts.yes) {
11490
- exitWithError("Refusing to reset without --yes. Run `prismer reset --check` to preview.", {
11491
- code: "confirmation_required"
11492
- });
11493
- }
11494
- if (runningPid !== null) {
11495
- await stopDaemon(runningPid, opts.timeout ?? 5e3);
11496
- clearPidFile(paths);
11497
- } else if (pid) {
11498
- clearPidFile(paths);
11499
- }
11500
- for (const gatewayPid of hermesGatewayPids) {
11501
- await stopProcess(gatewayPid, opts.timeout ?? 5e3, "Hermes gateway");
11502
- }
11503
- if (existsSync18(paths.root)) {
11504
- if (opts.wipe) {
11505
- rmSync(paths.root, { recursive: true, force: true });
11506
- } else {
11507
- if (!archivePath) throw new Error("internal: archivePath missing");
11508
- if (!existsSync18(dirname10(archivePath))) {
11509
- mkdirSync10(dirname10(archivePath), { recursive: true });
11510
- }
11511
- renameSync2(paths.root, archivePath);
11512
- }
11513
- }
11514
- if (existsSync18(assetSyncRoot)) {
11515
- if (opts.wipe) {
11516
- rmSync(assetSyncRoot, { recursive: true, force: true });
11517
- } else {
11518
- if (!assetSyncArchivePath) throw new Error("internal: assetSyncArchivePath missing");
11519
- if (!existsSync18(dirname10(assetSyncArchivePath))) {
11520
- mkdirSync10(dirname10(assetSyncArchivePath), { recursive: true });
11521
- }
11522
- renameSync2(assetSyncRoot, assetSyncArchivePath);
11523
- }
11524
- }
11525
- if (existsSync18(hermesHome)) {
11526
- if (opts.wipe) {
11527
- rmSync(hermesHome, { recursive: true, force: true });
11528
- } else {
11529
- if (!hermesArchivePath) throw new Error("internal: hermesArchivePath missing");
11530
- if (!existsSync18(dirname10(hermesArchivePath))) {
11531
- mkdirSync10(dirname10(hermesArchivePath), { recursive: true });
11532
- }
11533
- renameSync2(hermesHome, hermesArchivePath);
11534
- }
11535
- }
11536
- mkdirSync10(paths.root, { recursive: true });
11537
- if (opts.json) {
11538
- printJson({ ok: true, reset: true, plan });
11539
- return;
11540
- }
11541
- const ui = getUI();
11542
- ui.ok("Runtime reset complete", paths.root);
11543
- if (archivePath) ui.line(` archived: ${archivePath}`);
11544
- if (assetSyncArchivePath && plan.assetSyncExists) ui.line(` asset sync archived: ${assetSyncArchivePath}`);
11545
- if (hermesArchivePath && plan.hermesExists) ui.line(` Hermes archived: ${hermesArchivePath}`);
11546
- if (hermesGatewayPids.length > 0) ui.line(` stopped Hermes gateway pid(s): ${hermesGatewayPids.join(", ")}`);
11547
- ui.line(" next: prismer setup");
11548
- }, { code: "reset_failed" }));
11549
- }
11550
- function printPlan(plan, json) {
11551
- if (json) {
11552
- printJson({ ok: true, check: true, plan });
11553
- return;
11554
- }
11555
- const ui = getUI();
11556
- ui.header("Runtime reset plan");
11557
- ui.blank();
11558
- ui.line(` root: ${plan.root}`);
11559
- ui.line(` exists: ${plan.exists ? "yes" : "no"}`);
11560
- ui.line(` daemon: ${plan.runningPid ? `running pid ${plan.runningPid}` : "not running"}`);
11561
- ui.line(` mode: ${plan.mode}`);
11562
- if (plan.archivePath) ui.line(` archive: ${plan.archivePath}`);
11563
- ui.line(` asset sync: ${plan.assetSyncExists ? plan.assetSyncRoot : `${plan.assetSyncRoot} (not found)`}`);
11564
- if (plan.assetSyncArchivePath) ui.line(` asset archive: ${plan.assetSyncArchivePath}`);
11565
- ui.line(` Hermes: ${plan.hermesExists ? plan.hermesHome : `${plan.hermesHome} (not found)`}`);
11566
- if (plan.hermesArchivePath) ui.line(` Hermes archive: ${plan.hermesArchivePath}`);
11567
- ui.line(
11568
- ` Hermes gateway: ${plan.hermesGatewayPids.length > 0 ? `running pid(s) ${plan.hermesGatewayPids.join(", ")}` : "not running"}`
11569
- );
11570
- ui.blank();
11571
- ui.line("Run `prismer reset --yes` to apply, then `prismer setup` to bind a new account.");
11572
- }
11573
- async function stopDaemon(pid, timeoutMs) {
11574
- await stopProcess(pid, timeoutMs, "daemon");
11575
- }
11576
- async function stopProcess(pid, timeoutMs, label) {
11577
- try {
11578
- process.kill(pid, "SIGTERM");
11579
- } catch (err) {
11580
- throw new Error(`SIGTERM failed for ${label} pid ${pid}: ${err.message}`);
11581
- }
11582
- const deadline = Date.now() + timeoutMs;
11583
- while (Date.now() < deadline) {
11584
- if (!pidAlive2(pid)) return;
11585
- await sleep4(100);
11586
- }
11587
- try {
11588
- process.kill(pid, "SIGKILL");
11589
- } catch (err) {
11590
- throw new Error(`SIGKILL failed for ${label} pid ${pid}: ${err.message}`);
11591
- }
11592
- const killDeadline = Date.now() + 1e3;
11593
- while (Date.now() < killDeadline) {
11594
- if (!pidAlive2(pid)) return;
11595
- await sleep4(100);
11596
- }
11597
- throw new Error(`${label} pid ${pid} did not exit within ${timeoutMs + 1e3}ms`);
11598
- }
11599
- function timestamp() {
11600
- return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
11601
- }
11602
- function resolveAssetSyncRoot() {
11603
- const override = process.env.PRISMER_ASSET_SYNC_ROOT;
11604
- if (override) return override;
11605
- const home = homedir9();
11606
- const desktop = join20(home, "Desktop");
11607
- const base = existsSync18(desktop) ? desktop : home;
11608
- return join20(base, "Prismer Assets");
11609
- }
11610
- function resolveHermesHome() {
11611
- return process.env.HERMES_HOME || join20(homedir9(), ".hermes");
11612
- }
11613
- function findHermesGatewayPids2() {
11614
- try {
11615
- const out = execFileSync2("ps", ["-axo", "pid=,command="], { encoding: "utf8" });
11616
- const current = process.pid;
11617
- return out.split(/\r?\n/).map((line) => {
11618
- const trimmed = line.trim();
11619
- const match = /^(\d+)\s+(.+)$/.exec(trimmed);
11620
- if (!match) return null;
11621
- const pid = Number.parseInt(match[1], 10);
11622
- const command = match[2];
11623
- if (!Number.isFinite(pid) || pid === current) return null;
11624
- if (!isHermesGatewayCommand2(command)) return null;
11625
- return pid;
11626
- }).filter((pid) => pid !== null);
11627
- } catch {
11628
- return [];
11629
- }
11630
- }
11631
- function isHermesGatewayCommand2(command) {
11632
- if (!/\bgateway\b/.test(command) || !/\brun\b/.test(command)) return false;
11633
- return /(^|\s)(?:\S*\/)?hermes(?:\s|$)/.test(command);
11634
- }
11635
-
11636
9889
  // src/cli/commands/sandbox.ts
11637
- import { Command as Command14 } from "commander";
9890
+ import { Command as Command13 } from "commander";
11638
9891
  init_util();
11639
9892
  init_ui();
11640
9893
  function buildSandboxCommand() {
11641
- const cmd = new Command14("sandbox").description("Inspect and smoke-test sandbox lifecycle");
11642
- 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) => {
9894
+ const cmd = new Command13("sandbox").description("Inspect and smoke-test sandbox lifecycle");
9895
+ 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) => {
11643
9896
  const query = new URLSearchParams({ workspaceId: opts.workspaceId, limit: String(opts.limit) });
11644
9897
  if (opts.status) query.set("status", opts.status);
11645
9898
  const body = await cloudRequest("GET", `/api/sandboxes?${query.toString()}`);
@@ -11698,7 +9951,7 @@ function buildSandboxCommand() {
11698
9951
  }
11699
9952
  printSnapshotResult(id, body);
11700
9953
  }, { code: "sandbox_snapshot_failed" }));
11701
- 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) => {
9954
+ 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) => {
11702
9955
  if (!Array.isArray(command) || command.length === 0) exitWithError("runCmd requires a command after --", { code: "invalid_argument" });
11703
9956
  const body = await cloudRequest("POST", `/api/sandboxes/${encodeURIComponent(id)}/runCmd`, {
11704
9957
  command,
@@ -11739,12 +9992,12 @@ function errorMessage2(raw) {
11739
9992
  if (typeof raw.error === "string") return raw.error;
11740
9993
  return raw.error?.message ?? "request failed";
11741
9994
  }
11742
- async function cloudRequest(method, path9, body) {
9995
+ async function cloudRequest(method, path7, body) {
11743
9996
  const cloud = mkCloud5();
11744
- const res = await cloud.request(method, path9, body === void 0 ? void 0 : { body });
11745
- if (!res.ok) exitWithError(`${method} ${path9} failed (${res.status}): ${res.error?.message ?? "request failed"}`);
9997
+ const res = await cloud.request(method, path7, body === void 0 ? void 0 : { body });
9998
+ if (!res.ok) exitWithError(`${method} ${path7} failed (${res.status}): ${res.error?.message ?? "request failed"}`);
11746
9999
  const responseBody = res.data;
11747
- if (isErrorEnvelope2(responseBody)) exitWithError(`${method} ${path9} failed (${res.status}): ${errorMessage2(responseBody)}`);
10000
+ if (isErrorEnvelope2(responseBody)) exitWithError(`${method} ${path7} failed (${res.status}): ${errorMessage2(responseBody)}`);
11748
10001
  return responseBody;
11749
10002
  }
11750
10003
  function printSandboxList(raw) {
@@ -11819,7 +10072,7 @@ function writeLine2(label, value) {
11819
10072
  if (value === void 0 || value === null || value === "") return;
11820
10073
  getUI().line(` ${label}: ${String(value)}`);
11821
10074
  }
11822
- function parsePositiveInt6(value) {
10075
+ function parsePositiveInt5(value) {
11823
10076
  const parsed = Number.parseInt(value, 10);
11824
10077
  if (!Number.isFinite(parsed) || parsed <= 0) {
11825
10078
  throw new Error("expected a positive integer");
@@ -11837,16 +10090,16 @@ async function safeParseResponse(res) {
11837
10090
  }
11838
10091
 
11839
10092
  // src/cli/commands/setup.ts
11840
- import { Command as Command15 } from "commander";
10093
+ import { Command as Command14 } from "commander";
11841
10094
  import { hostname as hostname3 } from "os";
11842
10095
  import { spawn as spawn6 } from "child_process";
11843
- import { randomBytes as randomBytes2 } from "crypto";
11844
- import { existsSync as existsSync19, renameSync as renameSync3 } from "fs";
10096
+ import { randomBytes } from "crypto";
10097
+ import { existsSync as existsSync17, renameSync } from "fs";
11845
10098
  import { createServer as createServer2 } from "http";
11846
10099
  init_util();
11847
10100
  init_ui();
11848
10101
  function buildSetupCommand() {
11849
- return new Command15("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(
10102
+ return new Command14("setup").description("Set up this local runtime and bind it to Prismer Cloud").argument("[api-key]", "Prismer daemon API key; primarily for manual recovery and automation").option(
11850
10103
  "--cloud <url>",
11851
10104
  `Cloud base URL (default: ${DEFAULT_CLOUD_BASE_URL}; bare host:port is auto-prefixed http://)`,
11852
10105
  (v) => v
@@ -11859,7 +10112,7 @@ function buildSetupCommand() {
11859
10112
  } catch (err) {
11860
10113
  exitWithError(err.message, { code: "invalid_cloud_url" });
11861
10114
  }
11862
- let apiKey = apiKeyArg ?? (opts.force ? void 0 : process.env.PRISMER_API_KEY);
10115
+ let apiKey = apiKeyArg ?? process.env.PRISMER_API_KEY;
11863
10116
  const authToken = opts.token ?? process.env.PRISMER_AUTH_TOKEN;
11864
10117
  if (!opts.json) {
11865
10118
  printBanner({ compact: true });
@@ -11867,6 +10120,7 @@ function buildSetupCommand() {
11867
10120
  getUI().blank();
11868
10121
  }
11869
10122
  const shouldStart = opts.start !== false;
10123
+ stopRunningDaemon(paths);
11870
10124
  if (opts.pair || opts.asUser) {
11871
10125
  if (!opts.json) warn("Legacy pair setup path", "plain `prismer setup --start` is the canonical runtime binding flow");
11872
10126
  if (opts.asUser && process.env.LOCAL_ONLY !== "1") {
@@ -11957,11 +10211,10 @@ function buildSetupCommand() {
11957
10211
  return;
11958
10212
  }
11959
10213
  const previousConfig = configExists(paths) ? loadConfig(paths) : null;
11960
- const daemonId = previousConfig && previousConfig.api_key === apiKey && previousConfig.cloud_api_base === cloudBaseUrl ? previousConfig.daemon_id : newDaemonId();
11961
10214
  const config = {
11962
10215
  api_key: apiKey,
11963
10216
  cloud_api_base: cloudBaseUrl,
11964
- daemon_id: daemonId
10217
+ daemon_id: newDaemonId()
11965
10218
  };
11966
10219
  if (previousConfig && shouldArchiveLocalDb(previousConfig, config)) {
11967
10220
  archiveLocalDb(paths.localDb);
@@ -11991,7 +10244,7 @@ function buildSetupCommand() {
11991
10244
  }, { code: "setup_failed" }));
11992
10245
  }
11993
10246
  async function runBrowserSetup(input) {
11994
- const state = randomBytes2(18).toString("base64url");
10247
+ const state = randomBytes(18).toString("base64url");
11995
10248
  const server = createServer2();
11996
10249
  const apiKeyPromise = waitForSetupCallback(server, state);
11997
10250
  await new Promise((resolve3, reject) => {
@@ -12088,13 +10341,35 @@ function startDaemonDetached(home) {
12088
10341
  });
12089
10342
  child.unref();
12090
10343
  }
10344
+ function stopRunningDaemon(paths) {
10345
+ const pid = readPidFile(paths);
10346
+ if (!pid || !pidAlive(pid)) {
10347
+ if (pid) clearPidFile(paths);
10348
+ return;
10349
+ }
10350
+ try {
10351
+ process.kill(pid, "SIGTERM");
10352
+ } catch {
10353
+ return;
10354
+ }
10355
+ const deadline = Date.now() + 5e3;
10356
+ while (Date.now() < deadline) {
10357
+ if (!pidAlive(pid)) {
10358
+ clearPidFile(paths);
10359
+ return;
10360
+ }
10361
+ const start = Date.now();
10362
+ while (Date.now() - start < 200) {
10363
+ }
10364
+ }
10365
+ }
12091
10366
  function shouldArchiveLocalDb(previous, next) {
12092
10367
  return previous.api_key !== next.api_key || previous.cloud_api_base !== next.cloud_api_base || previous.daemon_id !== next.daemon_id;
12093
10368
  }
12094
10369
  function archiveLocalDb(localDbPath) {
12095
- if (!existsSync19(localDbPath)) return;
10370
+ if (!existsSync17(localDbPath)) return;
12096
10371
  const archived = `${localDbPath}.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
12097
- renameSync3(localDbPath, archived);
10372
+ renameSync(localDbPath, archived);
12098
10373
  }
12099
10374
  async function mintDaemonApiKey(input) {
12100
10375
  const label = `Daemon: ${input.deviceName} ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
@@ -12114,12 +10389,11 @@ async function mintDaemonApiKey(input) {
12114
10389
  }
12115
10390
 
12116
10391
  // src/cli/commands/status.ts
12117
- import { Command as Command16 } from "commander";
12118
- import { hostname as hostname4 } from "os";
10392
+ import { Command as Command15 } from "commander";
12119
10393
  init_util();
12120
10394
  init_ui();
12121
10395
  function buildStatusCommand() {
12122
- return new Command16("status").description("Show daemon + config + cloud status").option("--json", "Output machine-readable JSON").action(async (opts) => {
10396
+ return new Command15("status").description("Show daemon + config + cloud status").option("--json", "Output machine-readable JSON").action(async (opts) => {
12123
10397
  const paths = resolvePaths();
12124
10398
  if (!configExists(paths)) {
12125
10399
  const report2 = { configured: false, paired: false, paths: { config: paths.configFile } };
@@ -12187,7 +10461,11 @@ function buildStatusCommand() {
12187
10461
  info: daemonStatus.info ?? {}
12188
10462
  },
12189
10463
  cloud: { base: cfg.cloud_api_base, reachable: cloudOk, me, devices, agents },
12190
- local
10464
+ local,
10465
+ binding: {
10466
+ daemonId: cfg.daemon_id,
10467
+ apiKey: cfg.api_key
10468
+ }
12191
10469
  };
12192
10470
  if (opts.json) {
12193
10471
  printJson(report);
@@ -12210,8 +10488,8 @@ async function readDaemonStatus() {
12210
10488
  const paths = resolvePaths();
12211
10489
  const pid = readPidFile(paths);
12212
10490
  if (pid) {
12213
- const { pidAlive: pidAlive3 } = await Promise.resolve().then(() => (init_util(), util_exports));
12214
- if (pidAlive3(pid)) return { running: true, pid };
10491
+ const { pidAlive: pidAlive2 } = await Promise.resolve().then(() => (init_util(), util_exports));
10492
+ if (pidAlive2(pid)) return { running: true, pid };
12215
10493
  }
12216
10494
  return { running: false };
12217
10495
  }
@@ -12239,9 +10517,8 @@ function printPretty(report) {
12239
10517
  ui.blank();
12240
10518
  ok("Config", report.paths.config);
12241
10519
  if (report.daemon.running) {
12242
- const daemonName = report.cloud.me && typeof report.cloud.me === "object" ? report.cloud.me.user?.username ?? hostname4() : hostname4();
12243
10520
  const ws = report.daemon.wsConnected ? "connected" : "pending";
12244
- ok("Daemon", `${daemonName} pid=${report.daemon.pid} ws=${ws}`);
10521
+ ok("Daemon", `pid=${report.daemon.pid} ws=${ws}`);
12245
10522
  if (report.daemon.info) {
12246
10523
  const info2 = report.daemon.info;
12247
10524
  if (info2.version) ui.line(` Version: ${info2.version}`);
@@ -12256,7 +10533,8 @@ function printPretty(report) {
12256
10533
  ok("Cloud", report.cloud.base);
12257
10534
  const me = report.cloud.me;
12258
10535
  if (me?.user) {
12259
- ui.line(` User: ${me.user.displayName ?? me.user.username ?? "?"}`);
10536
+ const roleTag = me.user.role ? ` role=${me.user.role}` : "";
10537
+ ui.line(` Account: ${me.user.displayName ?? me.user.username ?? "?"}${roleTag}`);
12260
10538
  }
12261
10539
  if (me?.credits) {
12262
10540
  ui.line(` Credits: ${typeof me.credits.balance === "number" ? me.credits.balance.toLocaleString() : "?"}`);
@@ -12265,6 +10543,12 @@ function printPretty(report) {
12265
10543
  fail("Cloud", `${report.cloud.base} unreachable or unauthorized`);
12266
10544
  tip("prismer setup --force");
12267
10545
  }
10546
+ if (report.binding) {
10547
+ ui.blank();
10548
+ ui.line(` Daemon ID: ${report.binding.daemonId}`);
10549
+ const masked = report.binding.apiKey.slice(0, 14) + "\u2022\u2022\u2022\u2022" + report.binding.apiKey.slice(-4);
10550
+ ui.line(` API Key: ${masked}`);
10551
+ }
12268
10552
  if (Array.isArray(report.cloud.devices) && report.cloud.devices.length > 0) {
12269
10553
  const devs = report.cloud.devices;
12270
10554
  ui.blank();
@@ -12275,6 +10559,8 @@ function printPretty(report) {
12275
10559
  const declared = d.hostedAgentSummary?.declared ?? 0;
12276
10560
  ui.line(` ${statusIcon} ${d.podName?.replace(/^daemon:/, "")?.slice(0, 28) ?? "?"} kind=${kind} agents=${declared}`);
12277
10561
  }
10562
+ } else {
10563
+ ui.line(` Workspace Devices: none`);
12278
10564
  }
12279
10565
  if (Array.isArray(report.cloud.agents) && report.cloud.agents.length > 0) {
12280
10566
  ui.line(` Hosted agents: ${report.cloud.agents.length}`);
@@ -12290,14 +10576,14 @@ function printPretty(report) {
12290
10576
  }
12291
10577
 
12292
10578
  // src/cli/commands/task.ts
12293
- import { Command as Command17 } from "commander";
12294
- import { setTimeout as sleep5 } from "timers/promises";
10579
+ import { Command as Command16 } from "commander";
10580
+ import { setTimeout as sleep3 } from "timers/promises";
12295
10581
  init_util();
12296
10582
  function describeStatus(status) {
12297
10583
  return status === 0 ? "network error" : `HTTP ${status}`;
12298
10584
  }
12299
10585
  function buildTaskCommand() {
12300
- const cmd = new Command17("task").description("Manage tasks");
10586
+ const cmd = new Command16("task").description("Manage tasks");
12301
10587
  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) => {
12302
10588
  const cloud = mkCloud6();
12303
10589
  const body = {
@@ -12374,7 +10660,7 @@ function unwrap2(raw) {
12374
10660
  async function pollUntilTerminal(cloud, taskId, timeoutMs) {
12375
10661
  const deadline = Date.now() + (timeoutMs ?? 5 * 6e4);
12376
10662
  while (Date.now() < deadline) {
12377
- await sleep5(1e3);
10663
+ await sleep3(1e3);
12378
10664
  const res = await cloud.request(
12379
10665
  "GET",
12380
10666
  `/api/im/tasks/${encodeURIComponent(taskId)}`
@@ -12411,11 +10697,11 @@ function taskFrom2(raw) {
12411
10697
  }
12412
10698
 
12413
10699
  // src/cli/commands/workspace.ts
12414
- import { Command as Command18 } from "commander";
10700
+ import { Command as Command17 } from "commander";
12415
10701
  init_util();
12416
10702
  init_ui();
12417
10703
  function buildWorkspaceCommand() {
12418
- const cmd = new Command18("workspace").description("Manage workspaces, runtime snapshots, and workspace files");
10704
+ const cmd = new Command17("workspace").description("Manage workspaces, runtime snapshots, and workspace files");
12419
10705
  cmd.command("list").description("List workspaces").option("--json", "Output machine-readable JSON").action(runAction(async (opts) => {
12420
10706
  const cloud = mkCloud7();
12421
10707
  const data = await cloud.get("/api/im/workspaces");
@@ -12649,15 +10935,18 @@ async function readResponseError(res) {
12649
10935
 
12650
10936
  // src/cli/index.ts
12651
10937
  init_ui();
12652
- var VERSION = "1.9.7";
10938
+
10939
+ // package.json
10940
+ var version = "1.9.22";
10941
+
10942
+ // src/cli/index.ts
12653
10943
  function buildProgram() {
12654
- const program = new Command19("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(VERSION);
10944
+ const program = new Command18("prismer").description("Prismer Cloud daemon CLI (TS-only).").version(version);
12655
10945
  program.addCommand(buildBannerCommand());
12656
10946
  program.addCommand(buildSetupCommand());
12657
10947
  program.addCommand(buildConfigCommand());
12658
10948
  program.addCommand(buildDaemonCommand());
12659
10949
  program.addCommand(buildPairCommand());
12660
- program.addCommand(buildResetCommand());
12661
10950
  program.addCommand(buildStatusCommand());
12662
10951
  program.addCommand(buildAdapterCommand());
12663
10952
  program.addCommand(buildAgentCommand());