@epoch-agent/core 0.20.1 → 0.22.0

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/index.js CHANGED
@@ -2,7 +2,7 @@ import { existsSync, statSync, readdirSync, readFileSync, mkdirSync, writeFileSy
2
2
  import { homedir, platform, tmpdir, hostname } from 'os';
3
3
  import { join, dirname, resolve, relative, isAbsolute, sep, extname, basename } from 'path';
4
4
  import { execFile, execSync, execFileSync, spawn } from 'child_process';
5
- import { createLogger, dbPath, resolveHomeDir, DEFAULT_MAX_TURNS, PROJECT_DIR_NAME, guardSpawn, t, suggestKey, misspelledKeyIssues, openDatabase, migrate, releaseDatabase, parseLenient, issueDetails, splitWords, unknownKeyIssues, checkpointsDir, pluginsDir, projectHooksPath, projectClaudeSettingsPath, projectClaudeLocalSettingsPath, projectSettingsPath, projectLocalSettingsPath, projectPoliciesDir, trustedImportsPath, canProbeGit, budgetStatePath, artifactsDir, UNLIMITED_TURNS, turnLimitOf, configPath, managedSettingsPath, getSecretValues, listStoredSecretNames, maskApiKey, setSectionField, countCodePoints, headCodePoints, attachmentsDir, isInWorkspace, workspacesPath, samePath, lastSessionsPath, automationLogsDir, projectComplianceDir, projectSkillsDir, trustPath, projectCommandsDir, detectBackend, EXEC_PATH_AS_NODE_ENV, isolate, createStreamDecoder, parseShellCommand, writeArtifact, envPath, checkDangerousCommand, checkObfuscation, keepHeadAndTail, normalizeForMatch, isReadOnlyCommand, CODE_EXEC_ROOTS, tailCodePoints } from '@epoch-agent/infra';
5
+ import { createLogger, dbPath, resolveHomeDir, DEFAULT_MAX_TURNS, PROJECT_DIR_NAME, guardSpawn, t, suggestKey, misspelledKeyIssues, openDatabase, migrate, releaseDatabase, parseLenient, issueDetails, splitWords, unknownKeyIssues, checkpointsDir, pluginsDir, projectHooksPath, projectClaudeSettingsPath, projectClaudeLocalSettingsPath, projectSettingsPath, projectLocalSettingsPath, projectPoliciesDir, trustedImportsPath, canProbeGit, budgetStatePath, artifactsDir, UNLIMITED_TURNS, turnLimitOf, configPath, managedSettingsPath, getSecretValues, listStoredSecretNames, maskApiKey, setSectionField, countCodePoints, headCodePoints, attachmentsDir, isInWorkspace, workspacesPath, samePath, lastSessionsPath, appearancePath, automationLogsDir, projectComplianceDir, projectSkillsDir, trustPath, projectCommandsDir, detectBackend, EXEC_PATH_AS_NODE_ENV, isolate, createStreamDecoder, parseShellCommand, writeArtifact, envPath, checkDangerousCommand, checkObfuscation, keepHeadAndTail, normalizeForMatch, isReadOnlyCommand, CODE_EXEC_ROOTS, tailCodePoints } from '@epoch-agent/infra';
6
6
  export { IS_WINDOWS, WINDOWS_HIDE_FLAGS, buildBwrapArgs, buildProfile, checkDangerousCommand, checkObfuscation, createLogger, detectBackend, getDefaultShell, getPythonCommand, isInWorkspace, isReadOnlyCommand, isolate, maskApiKey, probeBubblewrap, probeSeatbelt, complianceDir as resolveComplianceDir, policiesDir as resolvePoliciesDir, setLogLevel } from '@epoch-agent/infra';
7
7
  import { PROVIDER_TYPES, OPERATION_TYPES as OPERATION_TYPES$1, COMPLIANCE_CATEGORIES, COMPLIANCE_ACTIONS, SEARCH_PROVIDER_TYPES, CURRENCIES, LANGS, isSettingsPaneId, SETTINGS_PANE_IDS, PERMISSION_LEVELS, SHELL_KINDS, ToolExposure, MAX_OPTIONS, MIN_OPTIONS, MAX_HEADER_CHARS, MAX_QUESTIONS, SCHEDULE_INTERVAL_MINUTES, isPermissionLevel, SCHEDULE_DRAFT_TOOL, isValidAgentRoleName, DEFAULT_AGENT_ROLE, isValidCommandName, RESERVED_COMMAND_NAMES, parseModelRef, isWirePluginArchiveName, systemNote, formatMoney, totalUsageTokens, NOOP_TELEMETRY, METRIC, contentToText, GEN_AI, isPromptOversize, promptTokens, apiKeyEnvVar, PROVIDER_INFOS, isProviderType, API_KEY_ENV_VARS, KEY_ACTIONS, tableOf, perAction, isKeyAction, parseSequence, RESERVED_CHORDS, chordId, sequenceId, ACTION_CONTEXTS, SEQUENCE_CONTEXTS, assertNeverPart, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_REFS, utf8ByteLength, truncateToBudget, attachSessionSurface, isContextNearlyFull, isOperationType, normalizeApproval, imagePartOmittedSummary, MAX_ATTACH_BYTES, SIMULTANEOUS_CONTEXTS, SESSION_REFERENCE_ERROR_CODES, filePartSummary, sessionPartSummary } from '@epoch-agent/protocol';
8
8
  export { API_KEY_ENV_VARS, COMPLIANCE_ACTIONS, COMPLIANCE_CATEGORIES, MAX_ATTACH_BYTES, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_ATTACH_BYTES, MAX_SESSION_REFS, OPERATION_TYPES, PERMISSION_LEVELS, PLAN_OUTCOMES, PROVIDER_INFOS, PROVIDER_TYPES, ToolExposure, apiKeyEnvVar, extractAllMentions, extractMentions, extractSessionMentions, getProviderInfo, isOperationType, isPermissionLevel, isPlanOutcome, isProviderType, normalizeApproval } from '@epoch-agent/protocol';
@@ -274,7 +274,7 @@ function createLaunchdBackend(opts = {}) {
274
274
  mkdirSync(input.logDir, { recursive: true });
275
275
  writeFileSync(path, renderPlist(label, input), "utf-8");
276
276
  } catch (err) {
277
- return { ok: false, detail: `plist-write-failed: ${describe8(err)}` };
277
+ return { ok: false, detail: `plist-write-failed: ${describe9(err)}` };
278
278
  }
279
279
  await run("launchctl", ["bootout", `${guiDomain()}/${label}`]);
280
280
  const toggle = await backend.setEnabled(label, input.enabled);
@@ -290,7 +290,7 @@ function createLaunchdBackend(opts = {}) {
290
290
  try {
291
291
  rmSync(plistPath(label), { force: true });
292
292
  } catch (err) {
293
- return { ok: false, detail: `plist-remove-failed: ${describe8(err)}` };
293
+ return { ok: false, detail: `plist-remove-failed: ${describe9(err)}` };
294
294
  }
295
295
  return { ok: true };
296
296
  },
@@ -447,7 +447,7 @@ function detailOf2(what, res) {
447
447
  const msg = (res.stderr || res.stdout).trim();
448
448
  return `${what} exit ${res.code}${msg ? `: ${msg}` : ""}`;
449
449
  }
450
- function describe8(err) {
450
+ function describe9(err) {
451
451
  return err instanceof Error ? err.message : String(err);
452
452
  }
453
453
  var LAUNCHD_LABEL_PREFIX;
@@ -650,7 +650,7 @@ var ToolRegistry = class {
650
650
  this.addTool(tool, source);
651
651
  }
652
652
  registerTools(tools, source = "builtin") {
653
- for (const t80 of tools) this.addTool(t80, source);
653
+ for (const t81 of tools) this.addTool(t81, source);
654
654
  }
655
655
  addTool(tool, source) {
656
656
  const existing = this.tools.get(tool.name);
@@ -699,14 +699,14 @@ var ToolRegistry = class {
699
699
  return isCodeModeDeferred(tool) ? ToolExposure.Deferred : declared;
700
700
  }
701
701
  exposed() {
702
- return this.list().filter((t80) => this.exposureOf(t80) === ToolExposure.Direct);
702
+ return this.list().filter((t81) => this.exposureOf(t81) === ToolExposure.Direct);
703
703
  }
704
704
  byExposure(exposure) {
705
- return this.list().filter((t80) => this.exposureOf(t80) === exposure);
705
+ return this.list().filter((t81) => this.exposureOf(t81) === exposure);
706
706
  }
707
707
  codeModeDeclared() {
708
708
  if (!this.codeMode) return [];
709
- return this.list().filter((t80) => isCodeModeDeferred(t80));
709
+ return this.list().filter((t81) => isCodeModeDeferred(t81));
710
710
  }
711
711
  sourceOf(name) {
712
712
  return this.tools.get(name)?.source;
@@ -755,7 +755,7 @@ var ToolRegistry = class {
755
755
  function staticToolProvider(tools) {
756
756
  return {
757
757
  list: () => tools,
758
- get: (name) => tools.find((t80) => t80.name === name)
758
+ get: (name) => tools.find((t81) => t81.name === name)
759
759
  };
760
760
  }
761
761
  var log = createLogger("tokenizer");
@@ -861,6 +861,21 @@ function parseOption(raw) {
861
861
  const description = typeof raw["description"] === "string" ? raw["description"].trim() : "";
862
862
  return { label, description };
863
863
  }
864
+ function parsePage(raw) {
865
+ if (!isRecord(raw)) return void 0;
866
+ const rawUrl = typeof raw["url"] === "string" ? raw["url"].trim() : "";
867
+ if (!rawUrl) return void 0;
868
+ let url;
869
+ try {
870
+ url = new URL(rawUrl);
871
+ } catch {
872
+ return void 0;
873
+ }
874
+ if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
875
+ if (url.hostname !== "127.0.0.1" && url.hostname !== "localhost") return void 0;
876
+ const label = typeof raw["label"] === "string" ? raw["label"].trim() : "";
877
+ return label ? { url: rawUrl, label } : { url: rawUrl };
878
+ }
864
879
  function parseQuestion(raw, at) {
865
880
  if (!isRecord(raw)) return `questions[${at}] \u4E0D\u662F\u5BF9\u8C61`;
866
881
  const question = typeof raw["question"] === "string" ? raw["question"].trim() : "";
@@ -878,11 +893,13 @@ function parseQuestion(raw, at) {
878
893
  if (options.length < MIN_OPTIONS || options.length > MAX_OPTIONS) {
879
894
  return `questions[${at}] \u6709 ${options.length} \u4E2A\u9009\u9879\uFF0C\u8981 ${MIN_OPTIONS}\u2013${MAX_OPTIONS} \u4E2A\u3002**\u4E0D\u8981**\u81EA\u5DF1\u52A0\u300C\u5176\u4ED6\u300D\u9009\u9879 \u2014\u2014 \u81EA\u7531\u8F93\u5165\u7531\u754C\u9762\u81EA\u52A8\u63D0\u4F9B`;
880
895
  }
896
+ const page = parsePage(raw["page"]);
881
897
  return {
882
898
  question,
883
899
  header,
884
900
  options,
885
- multiSelect: raw["multiSelect"] === true
901
+ multiSelect: raw["multiSelect"] === true,
902
+ ...page ? { page } : {}
886
903
  };
887
904
  }
888
905
  function parseQuestions(raw) {
@@ -957,7 +974,19 @@ var PARAMETERS = {
957
974
  required: ["label", "description"]
958
975
  }
959
976
  },
960
- multiSelect: { type: "boolean", description: "\u80FD\u4E0D\u80FD\u591A\u9009\u3002\u9ED8\u8BA4 false" }
977
+ multiSelect: { type: "boolean", description: "\u80FD\u4E0D\u80FD\u591A\u9009\u3002\u9ED8\u8BA4 false" },
978
+ page: {
979
+ type: "object",
980
+ description: "\u8FD9\u4E00\u95EE\u8FD8\u53EF\u4EE5\u5D4C\u4E00\u5F20\u63D2\u4EF6\u9875\u9762\uFF08\u53EA\u5728\u5E26\u754C\u9762\u7684\u5BBF\u4E3B\u91CC\u6709 \u2014\u2014 \u7EC8\u7AEF\u91CC\u4F1A\u88AB\u8BF4\u6210\u4E00\u53E5\u8BDD\uFF0C\u7528\u6237\u81EA\u5DF1\u5F00\uFF09\u3002\u26A0\uFE0F url \u53EA\u6536\u672C\u673A\u56DE\u73AF\uFF08127.0.0.1 / localhost\uFF09\uFF0C\u522B\u7684\u4F1A\u88AB\u4E22\u6389",
981
+ properties: {
982
+ url: {
983
+ type: "string",
984
+ description: "\u90A3\u4E00\u9875\u7684\u5730\u5740\uFF0C\u5F62\u5982 http://127.0.0.1:52341/confirm?t=\u2026"
985
+ },
986
+ label: { type: "string", description: "\u9875\u9762\u4E0A\u90A3\u51E0\u4E2A\u5B57\u3002\u4E0D\u7ED9\u5C31\u7528\u4E00\u53E5\u901A\u7528\u6587\u6848" }
987
+ },
988
+ required: ["url"]
989
+ }
961
990
  },
962
991
  required: ["question", "header", "options", "multiSelect"]
963
992
  }
@@ -972,7 +1001,10 @@ var DESCRIPTION = [
972
1001
  "**\u6709\u660E\u663E\u9ED8\u8BA4\u7B54\u6848\u65F6\u4E0D\u8981\u95EE**\uFF0C\u76F4\u63A5\u505A\u5E76\u8BF4\u660E \u2014\u2014 \u4ED3\u5E93\u91CC\u5DF2\u7ECF\u6709 vitest.config \u7684\u65F6\u5019",
973
1002
  "\u300C\u7528\u54EA\u4E2A\u6D4B\u8BD5\u6846\u67B6\u300D\u4E0D\u662F\u4E00\u4E2A\u95EE\u9898\u3002",
974
1003
  "\u4E0D\u8981\u7528\u5B83\u95EE\u300C\u6211\u7684\u65B9\u6848\u884C\u4E0D\u884C\u300D\uFF08\u90A3\u662F exit_plan_mode\uFF09\uFF0C\u4E5F\u4E0D\u8981\u7528\u5B83\u6C47\u62A5\u8FDB\u5EA6\u3002",
975
- "\u9009\u9879\u91CC**\u4E0D\u8981**\u81EA\u5DF1\u653E\u300C\u5176\u4ED6\u300D\uFF1A\u754C\u9762\u4F1A\u81EA\u52A8\u63D0\u4F9B\u81EA\u7531\u8F93\u5165\u3002"
1004
+ "\u9009\u9879\u91CC**\u4E0D\u8981**\u81EA\u5DF1\u653E\u300C\u5176\u4ED6\u300D\uFF1A\u754C\u9762\u4F1A\u81EA\u52A8\u63D0\u4F9B\u81EA\u7531\u8F93\u5165\u3002",
1005
+ "\u26A0\uFE0F \u63D2\u4EF6\u7684\u5DE5\u5177\u7ED9\u4E86\u4F60\u4E00\u4E2A**\u672C\u673A\u5730\u5740**\u3001\u800C\u90A3\u4E2A\u9009\u62E9\u7528\u51E0\u4E2A\u9009\u9879\u8BF4\u4E0D\u6E05\u65F6\uFF08\u51E0\u5341\u5F20\u56FE\u91CC\u6311\u4E00\u5F20\u3001",
1006
+ "\u4E00\u5F20\u8868\u91CC\u5708\u51E0\u884C\uFF09\uFF0C\u628A\u5730\u5740\u653E\u8FDB `questions[].page` \u2014\u2014 \u7528\u6237\u4F1A\u5728\u5F39\u7A97\u91CC\u76F4\u63A5\u770B\u5230\u90A3\u4E00\u9875\u3002",
1007
+ "\u5B83**\u9644\u52A0\u5728\u9009\u9879\u4E4B\u5916**\uFF0C\u9009\u9879\u7167\u65E7\u8981\u7ED9\uFF1A\u7EC8\u7AEF\u91CC\u753B\u4E0D\u51FA\u9875\u9762\uFF0C\u90A3\u65F6\u7528\u6237\u770B\u5230\u7684\u53EA\u6709\u4F60\u5199\u7684\u9009\u9879\u3002"
976
1008
  ].join("");
977
1009
  function createAskQuestionTool(deps) {
978
1010
  return {
@@ -1388,7 +1420,7 @@ var DelegateManager = class {
1388
1420
  }
1389
1421
  normalizeTasks(args) {
1390
1422
  if (args.tasks && args.tasks.length > 0) {
1391
- return args.role ? args.tasks.map((t80) => ({ ...t80, role: t80.role ?? args.role })) : args.tasks;
1423
+ return args.role ? args.tasks.map((t81) => ({ ...t81, role: t81.role ?? args.role })) : args.tasks;
1392
1424
  }
1393
1425
  if (args.goal) return [{ goal: args.goal, ...args.role ? { role: args.role } : {} }];
1394
1426
  return [];
@@ -1398,7 +1430,7 @@ var DelegateManager = class {
1398
1430
  for (let i = 0; i < tasks.length; i += maxConcurrent) {
1399
1431
  const batch = tasks.slice(i, i + maxConcurrent);
1400
1432
  const batchResults = await Promise.allSettled(
1401
- batch.map((t80) => this.runSingleTask(t80, parentSessionId, signal))
1433
+ batch.map((t81) => this.runSingleTask(t81, parentSessionId, signal))
1402
1434
  );
1403
1435
  for (const r of batchResults) {
1404
1436
  if (r.status === "fulfilled") {
@@ -1556,21 +1588,21 @@ function createDelegateTool(runner, config, roles) {
1556
1588
  operation: "command",
1557
1589
  describeTarget: (args) => {
1558
1590
  const tasks = Array.isArray(args["tasks"]) ? args["tasks"] : [];
1559
- const goals = tasks.map((t80) => String(t80["goal"] ?? ""));
1591
+ const goals = tasks.map((t81) => String(t81["goal"] ?? ""));
1560
1592
  const single = typeof args["goal"] === "string" ? args["goal"] : "";
1561
1593
  return [...goals, single].filter(Boolean).join(" | ");
1562
1594
  },
1563
1595
  async execute(args, ctx) {
1564
1596
  const start = Date.now();
1565
1597
  const rawTasks = Array.isArray(args["tasks"]) ? args["tasks"] : void 0;
1566
- const tasks = rawTasks?.map((t80) => {
1567
- const o = t80;
1598
+ const tasks = rawTasks?.map((t81) => {
1599
+ const o = t81;
1568
1600
  return {
1569
1601
  goal: String(o["goal"] ?? "").trim(),
1570
1602
  context: String(o["context"] ?? ""),
1571
1603
  ...typeof o["role"] === "string" && o["role"].trim() ? { role: o["role"].trim() } : {}
1572
1604
  };
1573
- }).filter((t80) => t80.goal.length > 0);
1605
+ }).filter((t81) => t81.goal.length > 0);
1574
1606
  const goal = typeof args["goal"] === "string" ? args["goal"].trim() : "";
1575
1607
  const topRole = typeof args["role"] === "string" ? args["role"].trim() : "";
1576
1608
  if ((!tasks || tasks.length === 0) && !goal) {
@@ -1586,7 +1618,7 @@ function createDelegateTool(runner, config, roles) {
1586
1618
  };
1587
1619
  }
1588
1620
  const known = listRoles();
1589
- const requested = [...(tasks ?? []).map((t80) => t80.role), topRole].filter(
1621
+ const requested = [...(tasks ?? []).map((t81) => t81.role), topRole].filter(
1590
1622
  (r) => Boolean(r)
1591
1623
  );
1592
1624
  const unknown = [...new Set(requested)].filter((r) => !known.some((k) => k.name === r));
@@ -1760,8 +1792,8 @@ function createGoalTool(deps) {
1760
1792
  }
1761
1793
  var VALID_TARGETS = ["memory", "user"];
1762
1794
  var VALID_ACTIONS = ["add", "replace", "remove", "search", "read", "budget"];
1763
- function isValidTarget(t80) {
1764
- return typeof t80 === "string" && VALID_TARGETS.includes(t80);
1795
+ function isValidTarget(t81) {
1796
+ return typeof t81 === "string" && VALID_TARGETS.includes(t81);
1765
1797
  }
1766
1798
  function isValidAction(a) {
1767
1799
  return VALID_ACTIONS.includes(a);
@@ -4915,7 +4947,7 @@ function available(tools) {
4915
4947
  if (tools.length === 0) {
4916
4948
  return "No deferred tools in this session \u2014 every tool is already in your tool list.";
4917
4949
  }
4918
- const names = tools.map((t80) => t80.name).sort();
4950
+ const names = tools.map((t81) => t81.name).sort();
4919
4951
  const shown = names.slice(0, MAX_LISTED);
4920
4952
  const rest = names.length - shown.length;
4921
4953
  return `Searchable tools: ${shown.join(", ")}${rest > 0 ? ` (\u2026and ${rest} more)` : ""}.`;
@@ -5035,15 +5067,15 @@ var TrackerDB = class {
5035
5067
  );
5036
5068
  this.db.transaction(() => {
5037
5069
  this.db.prepare("DELETE FROM todos WHERE session_id = ?").run(sessionId);
5038
- for (const t80 of todos) {
5070
+ for (const t81 of todos) {
5039
5071
  insertStmt.run({
5040
- id: t80.id,
5072
+ id: t81.id,
5041
5073
  session_id: sessionId,
5042
- content: t80.content,
5043
- status: t80.status,
5044
- reason: t80.reason || null,
5045
- created_at: t80.created_at,
5046
- updated_at: t80.updated_at
5074
+ content: t81.content,
5075
+ status: t81.status,
5076
+ reason: t81.reason || null,
5077
+ created_at: t81.created_at,
5078
+ updated_at: t81.updated_at
5047
5079
  });
5048
5080
  }
5049
5081
  })();
@@ -5065,9 +5097,9 @@ function formatTodoList(todos, pendingCount) {
5065
5097
  cancelled: "\u5DF2\u53D6\u6D88"
5066
5098
  };
5067
5099
  lines.push(`[todo] \u72B6\u6001\u6C47\u603B: ${pendingCount} \u4E2A\u5F85\u5B8C\u6210`);
5068
- for (const t80 of todos) {
5069
- const label = statusLabel[t80.status] || t80.status;
5070
- lines.push(`- [${label}] ${t80.content}${t80.reason ? ` \u2014\u2014 ${t80.reason}` : ""}`);
5100
+ for (const t81 of todos) {
5101
+ const label = statusLabel[t81.status] || t81.status;
5102
+ lines.push(`- [${label}] ${t81.content}${t81.reason ? ` \u2014\u2014 ${t81.reason}` : ""}`);
5071
5103
  }
5072
5104
  return lines.join("\n");
5073
5105
  }
@@ -5141,14 +5173,14 @@ function createTodoTool(db) {
5141
5173
  };
5142
5174
  }
5143
5175
  const now = (/* @__PURE__ */ new Date()).toISOString();
5144
- const existing = new Map(db.listBySession(ctx.sessionId).map((t80) => [t80.id, t80]));
5145
- const todos = rawTodos.map((t80) => {
5146
- const prev = t80.id ? existing.get(t80.id) : void 0;
5147
- const status = isValidStatus(t80.status) ? t80.status : "pending";
5148
- const reason9 = normalizeReason(status, t80.reason);
5176
+ const existing = new Map(db.listBySession(ctx.sessionId).map((t81) => [t81.id, t81]));
5177
+ const todos = rawTodos.map((t81) => {
5178
+ const prev = t81.id ? existing.get(t81.id) : void 0;
5179
+ const status = isValidStatus(t81.status) ? t81.status : "pending";
5180
+ const reason9 = normalizeReason(status, t81.reason);
5149
5181
  return {
5150
- id: t80.id || generateShortId(),
5151
- content: String(t80.content || "").slice(0, 500),
5182
+ id: t81.id || generateShortId(),
5183
+ content: String(t81.content || "").slice(0, 500),
5152
5184
  status,
5153
5185
  ...reason9 ? { reason: reason9 } : {},
5154
5186
  created_at: prev?.created_at || now,
@@ -5156,7 +5188,7 @@ function createTodoTool(db) {
5156
5188
  };
5157
5189
  });
5158
5190
  db.replaceAll(ctx.sessionId, todos);
5159
- const pendingCount = todos.filter((t80) => isOpenTodo(t80.status)).length;
5191
+ const pendingCount = todos.filter((t81) => isOpenTodo(t81.status)).length;
5160
5192
  return {
5161
5193
  success: true,
5162
5194
  output: formatTodoList(todos, pendingCount),
@@ -5491,7 +5523,7 @@ function intersectNames(base, incoming) {
5491
5523
  if (!base) return incoming ? [...incoming] : void 0;
5492
5524
  if (!incoming) return [...base];
5493
5525
  const baseSet = new Set(base);
5494
- return incoming.filter((t80) => baseSet.has(t80));
5526
+ return incoming.filter((t81) => baseSet.has(t81));
5495
5527
  }
5496
5528
  function findRole(roles, name) {
5497
5529
  return roles.find((r) => r.name === name);
@@ -5635,7 +5667,7 @@ function roleScopedProvider(base, role, blocked) {
5635
5667
  return allow ? allow.has(name) : true;
5636
5668
  };
5637
5669
  return {
5638
- list: () => base.list().filter((t80) => permitted(t80.name)),
5670
+ list: () => base.list().filter((t81) => permitted(t81.name)),
5639
5671
  get: (name) => permitted(name) ? base.get(name) : void 0
5640
5672
  };
5641
5673
  }
@@ -5662,7 +5694,7 @@ function createRoleScope(base, sessionRole, blocked) {
5662
5694
  }
5663
5695
  function unknownRoleTools(base, role, blocked) {
5664
5696
  if (!role?.tools) return [];
5665
- const available2 = new Set(base.list().map((t80) => t80.name));
5697
+ const available2 = new Set(base.list().map((t81) => t81.name));
5666
5698
  const blockedSet = new Set(blocked);
5667
5699
  return role.tools.filter((name) => !available2.has(name) && !blockedSet.has(name));
5668
5700
  }
@@ -5998,7 +6030,7 @@ function createToolScope(base) {
5998
6030
  const permitted = (name) => allow ? allow.has(name) : true;
5999
6031
  return {
6000
6032
  provider: {
6001
- list: () => base.list().filter((t80) => permitted(t80.name)),
6033
+ list: () => base.list().filter((t81) => permitted(t81.name)),
6002
6034
  get: (name) => permitted(name) ? base.get(name) : void 0
6003
6035
  },
6004
6036
  enter: (next) => {
@@ -6168,7 +6200,7 @@ function scanHooks(dir, issues) {
6168
6200
  return [...parsed.entries].map(([type, list]) => ({
6169
6201
  type,
6170
6202
  count: list.reduce((sum, entry) => sum + entry.hooks.length, 0)
6171
- })).filter((t80) => t80.count > 0).sort((a, b) => a.type.localeCompare(b.type));
6203
+ })).filter((t81) => t81.count > 0).sort((a, b) => a.type.localeCompare(b.type));
6172
6204
  }
6173
6205
  function scanMcp(dir, plugin, issues) {
6174
6206
  const path = join(dir, PLUGIN_LAYOUT.mcp);
@@ -6264,6 +6296,7 @@ var ManifestSchema = z.object({
6264
6296
  epochVersion: z.string().min(1).optional(),
6265
6297
  homepage: z.string().min(1).optional(),
6266
6298
  keywords: z.array(z.string().min(1)).optional(),
6299
+ dependencies: z.array(z.string().min(1)).optional().catch(void 0),
6267
6300
  displayName: z.string().min(1).optional(),
6268
6301
  category: z.string().min(1).optional(),
6269
6302
  icon: z.string().min(1).optional(),
@@ -6306,6 +6339,23 @@ function readPluginManifest(dir) {
6306
6339
  })
6307
6340
  });
6308
6341
  }
6342
+ const rawDependencies = parsed?.dependencies;
6343
+ if (rawDependencies !== void 0 && !Array.isArray(rawDependencies)) {
6344
+ issues.push({
6345
+ path: `${path}.dependencies`,
6346
+ message: t("plugin_manifest.bad_dependencies", { raw: JSON.stringify(rawDependencies) })
6347
+ });
6348
+ }
6349
+ if (Array.isArray(rawDependencies)) {
6350
+ for (const item of rawDependencies) {
6351
+ if (typeof item !== "string" || !isValidPluginName(item)) {
6352
+ issues.push({
6353
+ path: `${path}.dependencies`,
6354
+ message: t("plugin_manifest.bad_dependency_name", { name: String(item) })
6355
+ });
6356
+ }
6357
+ }
6358
+ }
6309
6359
  const checked = parseLenient(ManifestSchema.optional(), parsed, void 0);
6310
6360
  if (!checked.value) {
6311
6361
  issues.push(
@@ -6607,6 +6657,7 @@ function loadPlugins(opts) {
6607
6657
  const state = readPluginRecords(statePath);
6608
6658
  const out = noPlugins();
6609
6659
  out.issues.push(...state.issues);
6660
+ const declared = /* @__PURE__ */ new Map();
6610
6661
  const epochVersion = opts.epochVersion ?? currentEpochVersion();
6611
6662
  for (const record of state.records) {
6612
6663
  const outcome = admit(record, epochVersion);
@@ -6625,6 +6676,21 @@ function loadPlugins(opts) {
6625
6676
  continue;
6626
6677
  }
6627
6678
  collect(record, out);
6679
+ if (outcome.manifest?.dependencies?.length) {
6680
+ declared.set(record.name, outcome.manifest.dependencies);
6681
+ }
6682
+ }
6683
+ const present = new Set(out.loaded.map((plugin) => plugin.name));
6684
+ for (const [name, dependencies] of declared) {
6685
+ if (!present.has(name)) continue;
6686
+ for (const dependency of dependencies) {
6687
+ if (present.has(dependency)) continue;
6688
+ diags?.warn(
6689
+ MODULE,
6690
+ t("plugin_load.dependency_missing", { name, dependency }),
6691
+ "plugin-dependency-missing"
6692
+ );
6693
+ }
6628
6694
  }
6629
6695
  for (const detail of issueDetails(out.issues)) {
6630
6696
  diags?.warn(MODULE, detail, "plugin-invalid");
@@ -6651,7 +6717,7 @@ function admit(record, epochVersion) {
6651
6717
  if (!version.ok) {
6652
6718
  return { reason: version.reason, code: "plugin-version-mismatch" };
6653
6719
  }
6654
- return {};
6720
+ return { manifest: manifest.manifest };
6655
6721
  }
6656
6722
  function collect(record, out) {
6657
6723
  const { name, path: dir } = record;
@@ -6997,99 +7063,426 @@ function firstLine3(err) {
6997
7063
  const message = err instanceof Error ? err.message : String(err);
6998
7064
  return message.split("\n")[0] ?? message;
6999
7065
  }
7000
- var NPM_SPEC = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@[a-zA-Z0-9.^~*+-]+)?$/;
7001
- var REMOTE_URL = /^https?:\/\/[^\s'"\\^|<>]+$/;
7002
- function resolveSource(raw) {
7003
- const input = raw.trim();
7004
- if (!input) return void 0;
7005
- if (input.startsWith("github:")) {
7006
- const [slug = "", fragment] = input.slice("github:".length).split("#", 2);
7007
- if (!/^[\w.-]+\/[\w.-]+$/.test(slug)) return void 0;
7008
- const sub = subdirOf(fragment);
7009
- if (!sub.ok) return void 0;
7066
+ var MARKETPLACE_FILE = "epoch-marketplace.json";
7067
+ var NAME_PATTERN = PLUGIN_NAME_PATTERN;
7068
+ var EntryCategoryInput = z.union([z.string().min(1), z.array(z.string().min(1))]).optional().catch(void 0);
7069
+ var EntryShape = z.object({
7070
+ name: z.string().regex(NAME_PATTERN),
7071
+ description: z.string().min(1).optional(),
7072
+ source: z.string().min(1),
7073
+ category: EntryCategoryInput,
7074
+ keywords: z.array(z.string().min(1)).optional(),
7075
+ author: PluginAuthorSchema.optional(),
7076
+ downloads: z.number().int().nonnegative().optional(),
7077
+ likes: z.number().int().nonnegative().optional(),
7078
+ displayName: z.string().min(1).optional(),
7079
+ icon: z.string().min(1).optional(),
7080
+ version: z.string().min(1).optional(),
7081
+ changelog: z.string().min(1).optional(),
7082
+ size: z.number().int().positive().optional(),
7083
+ homepage: z.string().min(1).optional(),
7084
+ visibility: z.enum(PLUGIN_VISIBILITY).optional().catch(void 0),
7085
+ status: z.enum(["listed", "reviewing", "rejected", "offline"]).optional().catch(void 0)
7086
+ });
7087
+ var EntrySchema = EntryShape.transform(({ category, ...rest }) => {
7088
+ const picked = category === void 0 ? [] : typeof category === "string" ? [category] : category;
7089
+ return { ...rest, ...picked.length === 0 ? {} : { category: [...picked] } };
7090
+ });
7091
+ var MARKETPLACE_DISPLAY_FIELDS = [
7092
+ "status",
7093
+ "downloads",
7094
+ "likes",
7095
+ "author",
7096
+ "category",
7097
+ "visibility",
7098
+ "icon",
7099
+ "homepage",
7100
+ "size"
7101
+ ];
7102
+ var DisplaySchema = z.object({
7103
+ fields: z.array(z.string().min(1)).optional()
7104
+ });
7105
+ var CategoryNodeSchema = z.lazy(
7106
+ () => z.object({
7107
+ id: z.string().min(1),
7108
+ label: z.string().min(1).optional(),
7109
+ children: z.array(CategoryNodeSchema).optional()
7110
+ })
7111
+ );
7112
+ var CategoriesSchema = z.array(CategoryNodeSchema).refine(hasUniqueCategoryIds, { message: "category ids must be unique across the whole tree" }).optional().catch(void 0);
7113
+ function hasUniqueCategoryIds(nodes) {
7114
+ const seen = /* @__PURE__ */ new Set();
7115
+ const walk3 = (list) => list.every((node) => {
7116
+ if (seen.has(node.id)) return false;
7117
+ seen.add(node.id);
7118
+ return node.children === void 0 || walk3(node.children);
7119
+ });
7120
+ return walk3(nodes);
7121
+ }
7122
+ var CatalogSchema = z.object({
7123
+ name: z.string().regex(NAME_PATTERN),
7124
+ description: z.string().min(1).optional(),
7125
+ owner: z.object({ name: z.string().min(1), url: z.string().min(1).optional() }).optional(),
7126
+ display: DisplaySchema.optional(),
7127
+ categories: CategoriesSchema,
7128
+ plugins: z.array(EntrySchema)
7129
+ });
7130
+ var RecordSchema2 = z.object({
7131
+ name: z.string().regex(NAME_PATTERN),
7132
+ source: z.string().min(1),
7133
+ addedAt: z.number(),
7134
+ catalog: CatalogSchema
7135
+ });
7136
+ var StateSchema2 = z.object({
7137
+ version: z.literal(1).optional(),
7138
+ marketplaces: z.array(RecordSchema2)
7139
+ });
7140
+ function projectEntryForDisplay(entry, display) {
7141
+ const allowed = display?.fields;
7142
+ if (allowed === void 0) return entry;
7143
+ const out = { ...entry };
7144
+ for (const field of MARKETPLACE_DISPLAY_FIELDS) {
7145
+ if (!displayAllows(display, field)) delete out[field];
7146
+ }
7147
+ return out;
7148
+ }
7149
+ function displayAllows(display, field) {
7150
+ const allowed = display?.fields;
7151
+ if (allowed === void 0) return true;
7152
+ return allowed.includes(field);
7153
+ }
7154
+ function readMarketplaces(statePath) {
7155
+ if (!existsSync(statePath)) return { records: [], issues: [] };
7156
+ let raw;
7157
+ try {
7158
+ raw = JSON.parse(readFileSync(statePath, "utf-8"));
7159
+ } catch (err) {
7010
7160
  return {
7011
- type: "git",
7012
- raw: input,
7013
- target: `https://github.com/${slug}.git`,
7014
- ...sub.subdir ? { subdir: sub.subdir } : {}
7161
+ records: [],
7162
+ issues: [
7163
+ { path: statePath, message: t("plugin_marketplace.bad_json", { reason: first(err) }) }
7164
+ ]
7015
7165
  };
7016
7166
  }
7017
- if (input.startsWith("git+")) {
7018
- const [url = "", fragment] = input.slice("git+".length).split("#", 2);
7019
- if (!REMOTE_URL.test(url)) return void 0;
7020
- const sub = subdirOf(fragment);
7021
- if (!sub.ok) return void 0;
7022
- return { type: "git", raw: input, target: url, ...sub.subdir ? { subdir: sub.subdir } : {} };
7023
- }
7024
- if (input.startsWith("npm:")) {
7025
- const pkg = input.slice("npm:".length);
7026
- if (!NPM_SPEC.test(pkg)) return void 0;
7027
- return { type: "npm", raw: input, target: pkg };
7028
- }
7029
- if (input.startsWith("host:")) {
7030
- const opaque = input.slice("host:".length).trim();
7031
- if (!opaque) return void 0;
7032
- return { type: "host", raw: input, target: opaque };
7033
- }
7034
- if (input.startsWith("https://") || input.startsWith("http://")) {
7035
- const [url = "", fragment] = input.split("#", 2);
7036
- if (!REMOTE_URL.test(url)) return void 0;
7037
- const sha = fragment?.startsWith("sha256=") ? fragment.slice("sha256=".length) : void 0;
7038
- if (isArchiveUrl(url)) {
7039
- if (sha !== void 0 && !/^[a-f0-9]{64}$/i.test(sha)) return void 0;
7040
- return {
7041
- type: "archive",
7042
- raw: input,
7043
- target: url,
7044
- ...sha ? { sha256: sha.toLowerCase() } : {}
7045
- };
7046
- }
7047
- return { type: "git", raw: input, target: url };
7048
- }
7049
- const abs = isAbsolute(input) ? input : resolve(input);
7050
- if (isArchiveUrl(abs)) {
7051
- return { type: "archive", raw: input, target: abs, localFile: true };
7167
+ const checked = parseLenient(StateSchema2.optional(), raw, void 0);
7168
+ if (!checked.value) {
7169
+ return {
7170
+ records: [],
7171
+ issues: checked.issues.map((i) => ({
7172
+ path: statePath,
7173
+ message: t("plugin_marketplace.issue", { path: i.path, message: i.message })
7174
+ }))
7175
+ };
7052
7176
  }
7053
- return { type: "local", raw: input, target: abs };
7177
+ return { records: checked.value.marketplaces, issues: [] };
7054
7178
  }
7055
- function isArchiveUrl(url) {
7056
- return isWirePluginArchiveName(url);
7179
+ async function mutate(statePath, fn) {
7180
+ const release = await acquireStateLock(statePath);
7181
+ try {
7182
+ const next = fn(readMarketplaces(statePath).records);
7183
+ if (!next) return false;
7184
+ writeStateFile(statePath, JSON.stringify({ version: 1, marketplaces: next }, null, 2));
7185
+ return true;
7186
+ } finally {
7187
+ release();
7188
+ }
7057
7189
  }
7058
- function subdirOf(fragment) {
7059
- if (fragment === void 0 || fragment === "") return { ok: true };
7060
- if (!fragment.startsWith("subdir=")) return { ok: false };
7061
- const rel = fragment.slice("subdir=".length).replace(/\/+$/, "");
7062
- if (!/^[\w.-]+(?:\/[\w.-]+)*$/.test(rel)) return { ok: false };
7063
- if (rel.split("/").includes("..")) return { ok: false };
7064
- return { ok: true, subdir: rel };
7190
+ async function addMarketplace(source, opts) {
7191
+ const fetched = await fetchCatalog(source);
7192
+ if (!fetched.ok) return fetched;
7193
+ const record = {
7194
+ name: fetched.catalog.name,
7195
+ source: source.trim(),
7196
+ addedAt: Date.now(),
7197
+ catalog: fetched.catalog
7198
+ };
7199
+ const written = await mutate(
7200
+ opts.statePath,
7201
+ (records) => records.some((r) => r.name === record.name) ? void 0 : [...records, record].sort((a, b) => a.name.localeCompare(b.name))
7202
+ );
7203
+ return written ? { ok: true, record } : {
7204
+ ok: false,
7205
+ reason: t("plugin_marketplace.already_added", { name: record.name })
7206
+ };
7065
7207
  }
7066
- function pluginRoot(source, staging) {
7067
- return source.subdir ? join(staging, source.subdir) : staging;
7208
+ async function removeMarketplace(name, opts) {
7209
+ const written = await mutate(
7210
+ opts.statePath,
7211
+ (records) => records.some((r) => r.name === name) ? records.filter((r) => r.name !== name) : void 0
7212
+ );
7213
+ return written ? { ok: true } : { ok: false, reason: t("plugin_marketplace.not_added", { name }) };
7068
7214
  }
7069
- // src/plugin/install.ts
7070
- async function installPlugin(source, opts) {
7071
- const resolved = resolveSource(source);
7072
- if (!resolved) {
7215
+ async function updateMarketplace(name, opts) {
7216
+ const existing = readMarketplaces(opts.statePath).records.find((r) => r.name === name);
7217
+ if (!existing) return { ok: false, reason: t("plugin_marketplace.not_added", { name }) };
7218
+ const fetched = await fetchCatalog(existing.source);
7219
+ if (!fetched.ok) return fetched;
7220
+ if (fetched.catalog.name !== name) {
7073
7221
  return {
7074
7222
  ok: false,
7075
- reason: t("plugin_install.bad_source", { source })
7223
+ reason: t("plugin_marketplace.renamed", {
7224
+ source: existing.source,
7225
+ name: fetched.catalog.name
7226
+ })
7076
7227
  };
7077
7228
  }
7078
- mkdirSync(opts.pluginsDir, { recursive: true });
7079
- const staging = join(opts.pluginsDir, `.staging-${randomUUID().slice(0, 8)}`);
7080
- try {
7081
- return await runInstall(resolved, staging, opts);
7082
- } finally {
7083
- rmSync(staging, { recursive: true, force: true });
7084
- }
7229
+ const record = { ...existing, catalog: fetched.catalog };
7230
+ await mutate(opts.statePath, (records) => records.map((r) => r.name === name ? record : r));
7231
+ return { ok: true, record };
7085
7232
  }
7086
- async function runInstall(resolved, staging, opts) {
7087
- const fetched = await fetchInto(resolved, staging, opts);
7088
- if (!fetched.ok) return { ok: false, reason: fetched.reason };
7089
- const readDir = resolved.type === "local" ? resolved.target : pluginRoot(resolved, staging);
7090
- const read = readPluginManifest(readDir);
7091
- const manifest = read.manifest;
7092
- if (!manifest) {
7233
+ function searchMarketplaces(keyword, statePath) {
7234
+ const needle = keyword.trim().toLowerCase();
7235
+ const hits = [];
7236
+ for (const record of readMarketplaces(statePath).records) {
7237
+ for (const entry of record.catalog.plugins) {
7238
+ const haystack = [
7239
+ entry.name,
7240
+ entry.description ?? "",
7241
+ ...entry.category ?? [],
7242
+ entry.author?.name ?? "",
7243
+ ...entry.keywords ?? []
7244
+ ].join(" ").toLowerCase();
7245
+ if (needle && !haystack.includes(needle)) continue;
7246
+ hits.push({
7247
+ marketplace: record.name,
7248
+ entry: projectEntryForDisplay(entry, record.catalog.display),
7249
+ ref: `${record.name}/${entry.name}`
7250
+ });
7251
+ }
7252
+ }
7253
+ return hits;
7254
+ }
7255
+ function resolveMarketplaceRef(input, statePath) {
7256
+ const parts = input.trim().split("/");
7257
+ if (parts.length !== 2) return void 0;
7258
+ const [market = "", plugin = ""] = parts;
7259
+ if (!NAME_PATTERN.test(market) || !NAME_PATTERN.test(plugin)) return void 0;
7260
+ const record = readMarketplaces(statePath).records.find((r) => r.name === market);
7261
+ if (!record) {
7262
+ return {
7263
+ ok: false,
7264
+ reason: t("plugin_marketplace.market_not_added", { market, input })
7265
+ };
7266
+ }
7267
+ const entry = record.catalog.plugins.find((p) => p.name === plugin);
7268
+ if (!entry) {
7269
+ return {
7270
+ ok: false,
7271
+ reason: t("plugin_marketplace.plugin_not_in_market", { market, plugin })
7272
+ };
7273
+ }
7274
+ const source = resolveEntrySource(entry.source, record.source);
7275
+ if (!source) {
7276
+ return {
7277
+ ok: false,
7278
+ reason: t("plugin_marketplace.entry_unresolvable", {
7279
+ market,
7280
+ plugin,
7281
+ source: entry.source
7282
+ })
7283
+ };
7284
+ }
7285
+ return { ok: true, ref: { marketplace: market, source } };
7286
+ }
7287
+ function resolveEntrySource(entry, marketSource) {
7288
+ const spec = entry.trim();
7289
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return spec;
7290
+ const rel = spec.replace(/^\.\//, "").replace(/^\//, "");
7291
+ if (rel === "" || rel.split("/").includes("..")) return void 0;
7292
+ const market = marketSource.trim();
7293
+ if (market.startsWith("github:") || market.startsWith("git+")) {
7294
+ const [base = "", fragment] = market.split("#", 2);
7295
+ const prefix = fragment?.startsWith("subdir=") ? `${fragment.slice("subdir=".length)}/` : "";
7296
+ return `${base}#subdir=${prefix}${rel}`;
7297
+ }
7298
+ if (market.startsWith("https://") || market.startsWith("http://")) {
7299
+ return `${market.replace(/\/[^/]*$/, "")}/${rel}`;
7300
+ }
7301
+ return join(marketDir(market), rel);
7302
+ }
7303
+ function findMarketplaceEntryByName(name, statePath) {
7304
+ for (const record of readMarketplaces(statePath).records) {
7305
+ const entry = record.catalog.plugins.find((plugin) => plugin.name === name);
7306
+ if (!entry) continue;
7307
+ const source = resolveEntrySource(entry.source, record.source);
7308
+ if (!source) continue;
7309
+ return { marketplace: record.name, source };
7310
+ }
7311
+ return void 0;
7312
+ }
7313
+ async function fetchCatalog(source) {
7314
+ const input = source.trim();
7315
+ if (!input) return { ok: false, reason: t("plugin_marketplace.source_empty") };
7316
+ const local = localCatalogPath(input);
7317
+ if (local) {
7318
+ if (!existsSync(local)) {
7319
+ return { ok: false, reason: t("plugin_marketplace.local_missing", { path: local }) };
7320
+ }
7321
+ try {
7322
+ return parseCatalog(readFileSync(local, "utf-8"), local);
7323
+ } catch (err) {
7324
+ return { ok: false, reason: t("plugin_marketplace.read_failed", { reason: first(err) }) };
7325
+ }
7326
+ }
7327
+ const url = catalogUrl(input);
7328
+ if (!url) {
7329
+ return {
7330
+ ok: false,
7331
+ reason: t("plugin_marketplace.bad_url", { input, file: MARKETPLACE_FILE })
7332
+ };
7333
+ }
7334
+ try {
7335
+ const response = await fetch(url, { redirect: "follow" });
7336
+ if (!response.ok) {
7337
+ const vars = { url, status: response.status };
7338
+ return {
7339
+ ok: false,
7340
+ reason: looksPrivate(response.status) ? t("marketplace.fetch_failed_private", vars) : t("marketplace.fetch_failed", vars)
7341
+ };
7342
+ }
7343
+ return parseCatalog(await response.text(), url);
7344
+ } catch (err) {
7345
+ return { ok: false, reason: t("marketplace.fetch_error", { url, reason: first(err) }) };
7346
+ }
7347
+ }
7348
+ function looksPrivate(status) {
7349
+ return status === 401 || status === 403 || status === 404;
7350
+ }
7351
+ function catalogUrl(input) {
7352
+ if (input.startsWith("github:")) {
7353
+ const slug = input.slice("github:".length);
7354
+ if (!/^[\w.-]+\/[\w.-]+$/.test(slug)) return void 0;
7355
+ return `https://raw.githubusercontent.com/${slug}/HEAD/${MARKETPLACE_FILE}`;
7356
+ }
7357
+ if (!input.startsWith("https://") && !input.startsWith("http://")) return void 0;
7358
+ return input.endsWith(".json") ? input : `${input.replace(/\/+$/, "")}/${MARKETPLACE_FILE}`;
7359
+ }
7360
+ function localCatalogPath(input) {
7361
+ if (/^[a-z][a-z0-9+.-]+:/i.test(input) || input.startsWith("//")) return void 0;
7362
+ const abs = isAbsolute(input) ? input : resolve(input);
7363
+ if (existsSync(abs) && statSync(abs).isDirectory()) return join(abs, MARKETPLACE_FILE);
7364
+ return abs;
7365
+ }
7366
+ function marketDir(market) {
7367
+ const abs = isAbsolute(market) ? market : resolve(market);
7368
+ if (existsSync(abs) && statSync(abs).isDirectory()) return abs;
7369
+ return abs.replace(/[/\\][^/\\]*$/, "");
7370
+ }
7371
+ function parseCatalog(body2, where) {
7372
+ let raw;
7373
+ try {
7374
+ raw = JSON.parse(body2);
7375
+ } catch (err) {
7376
+ return {
7377
+ ok: false,
7378
+ reason: t("plugin_marketplace.catalog_bad_json", { where, reason: first(err) })
7379
+ };
7380
+ }
7381
+ const checked = parseLenient(CatalogSchema.optional(), raw, void 0);
7382
+ if (!checked.value) {
7383
+ const detail = checked.issues.map((i) => t("plugin_marketplace.issue", { path: i.path, message: i.message })).join(t("plugin_marketplace.catalog_issue_sep"));
7384
+ return { ok: false, reason: t("plugin_marketplace.catalog_invalid", { where, detail }) };
7385
+ }
7386
+ return { ok: true, catalog: checked.value };
7387
+ }
7388
+ function first(err) {
7389
+ const message = err instanceof Error ? err.message : String(err);
7390
+ return message.split("\n")[0] ?? t("plugin_marketplace.unknown_error");
7391
+ }
7392
+ var NPM_SPEC = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@[a-zA-Z0-9.^~*+-]+)?$/;
7393
+ var REMOTE_URL = /^https?:\/\/[^\s'"\\^|<>]+$/;
7394
+ function resolveSource(raw) {
7395
+ const input = raw.trim();
7396
+ if (!input) return void 0;
7397
+ if (input.startsWith("github:")) {
7398
+ const [slug = "", fragment] = input.slice("github:".length).split("#", 2);
7399
+ if (!/^[\w.-]+\/[\w.-]+$/.test(slug)) return void 0;
7400
+ const sub = subdirOf(fragment);
7401
+ if (!sub.ok) return void 0;
7402
+ return {
7403
+ type: "git",
7404
+ raw: input,
7405
+ target: `https://github.com/${slug}.git`,
7406
+ ...sub.subdir ? { subdir: sub.subdir } : {}
7407
+ };
7408
+ }
7409
+ if (input.startsWith("git+")) {
7410
+ const [url = "", fragment] = input.slice("git+".length).split("#", 2);
7411
+ if (!REMOTE_URL.test(url)) return void 0;
7412
+ const sub = subdirOf(fragment);
7413
+ if (!sub.ok) return void 0;
7414
+ return { type: "git", raw: input, target: url, ...sub.subdir ? { subdir: sub.subdir } : {} };
7415
+ }
7416
+ if (input.startsWith("npm:")) {
7417
+ const pkg = input.slice("npm:".length);
7418
+ if (!NPM_SPEC.test(pkg)) return void 0;
7419
+ return { type: "npm", raw: input, target: pkg };
7420
+ }
7421
+ if (input.startsWith("host:")) {
7422
+ const opaque = input.slice("host:".length).trim();
7423
+ if (!opaque) return void 0;
7424
+ return { type: "host", raw: input, target: opaque };
7425
+ }
7426
+ if (input.startsWith("https://") || input.startsWith("http://")) {
7427
+ const [url = "", fragment] = input.split("#", 2);
7428
+ if (!REMOTE_URL.test(url)) return void 0;
7429
+ const sha = fragment?.startsWith("sha256=") ? fragment.slice("sha256=".length) : void 0;
7430
+ if (isArchiveUrl(url)) {
7431
+ if (sha !== void 0 && !/^[a-f0-9]{64}$/i.test(sha)) return void 0;
7432
+ return {
7433
+ type: "archive",
7434
+ raw: input,
7435
+ target: url,
7436
+ ...sha ? { sha256: sha.toLowerCase() } : {}
7437
+ };
7438
+ }
7439
+ return { type: "git", raw: input, target: url };
7440
+ }
7441
+ const abs = isAbsolute(input) ? input : resolve(input);
7442
+ if (isArchiveUrl(abs)) {
7443
+ return { type: "archive", raw: input, target: abs, localFile: true };
7444
+ }
7445
+ return { type: "local", raw: input, target: abs };
7446
+ }
7447
+ function isArchiveUrl(url) {
7448
+ return isWirePluginArchiveName(url);
7449
+ }
7450
+ function subdirOf(fragment) {
7451
+ if (fragment === void 0 || fragment === "") return { ok: true };
7452
+ if (!fragment.startsWith("subdir=")) return { ok: false };
7453
+ const rel = fragment.slice("subdir=".length).replace(/\/+$/, "");
7454
+ if (!/^[\w.-]+(?:\/[\w.-]+)*$/.test(rel)) return { ok: false };
7455
+ if (rel.split("/").includes("..")) return { ok: false };
7456
+ return { ok: true, subdir: rel };
7457
+ }
7458
+ function pluginRoot(source, staging) {
7459
+ return source.subdir ? join(staging, source.subdir) : staging;
7460
+ }
7461
+ // src/plugin/install.ts
7462
+ async function installPlugin(source, opts) {
7463
+ const resolved = resolveSource(source);
7464
+ if (!resolved) {
7465
+ return {
7466
+ ok: false,
7467
+ reason: t("plugin_install.bad_source", { source })
7468
+ };
7469
+ }
7470
+ mkdirSync(opts.pluginsDir, { recursive: true });
7471
+ const staging = join(opts.pluginsDir, `.staging-${randomUUID().slice(0, 8)}`);
7472
+ try {
7473
+ return await runInstall(resolved, staging, opts);
7474
+ } finally {
7475
+ rmSync(staging, { recursive: true, force: true });
7476
+ }
7477
+ }
7478
+ async function runInstall(resolved, staging, opts) {
7479
+ const rootStaging = join(staging, "root");
7480
+ const fetched = await fetchInto(resolved, rootStaging, opts);
7481
+ if (!fetched.ok) return { ok: false, reason: fetched.reason };
7482
+ const readDir = resolved.type === "local" ? resolved.target : pluginRoot(resolved, rootStaging);
7483
+ const read = readPluginManifest(readDir);
7484
+ const manifest = read.manifest;
7485
+ if (!manifest) {
7093
7486
  const detail = read.issues.map((i) => t("plugin_install.issue_line", { path: i.path, message: i.message })).join("\n");
7094
7487
  return { ok: false, reason: t("plugin_install.manifest_unusable", { detail: `
7095
7488
  ${detail}` }) };
@@ -7099,12 +7492,20 @@ ${detail}` }) };
7099
7492
  const current = opts.epochVersion ?? currentEpochVersion();
7100
7493
  const version = checkEpochVersion(manifest.epochVersion, current);
7101
7494
  const versionUnmet = !version.ok && manifest.epochVersion ? { required: manifest.epochVersion, current, reason: version.reason } : void 0;
7495
+ const closure = await resolveClosure({
7496
+ root: manifest.name,
7497
+ declared: manifest.dependencies ?? [],
7498
+ installed: new Set(new PluginStore(opts.statePath).read().records.map((r) => r.name)),
7499
+ staging,
7500
+ opts
7501
+ });
7102
7502
  const preview3 = {
7103
7503
  manifest,
7104
7504
  source: resolved,
7105
7505
  inventory: scanPluginDir(readDir, manifest.name),
7106
7506
  ...conflict ? { conflict } : {},
7107
- ...versionUnmet ? { versionUnmet } : {}
7507
+ ...versionUnmet ? { versionUnmet } : {},
7508
+ ...closure.report.length > 0 ? { dependencies: closure.report } : {}
7108
7509
  };
7109
7510
  if (opts.replace !== void 0 && manifest.name !== opts.replace) {
7110
7511
  return {
@@ -7127,52 +7528,199 @@ ${detail}` }) };
7127
7528
  if (opts.confirm && !await opts.confirm(preview3)) {
7128
7529
  return { ok: false, reason: t("plugin.install_cancelled"), cancelled: true, preview: preview3 };
7129
7530
  }
7130
- const record = await commit(resolved, staging, preview3, opts);
7531
+ const records = await commitAll(
7532
+ [
7533
+ {
7534
+ name: manifest.name,
7535
+ version: manifest.version,
7536
+ source: resolved,
7537
+ stagingDir: rootStaging,
7538
+ ...opts.marketplace ? { marketplace: opts.marketplace } : {}
7539
+ },
7540
+ ...closure.plans.map((plan) => ({
7541
+ name: plan.name,
7542
+ version: plan.manifest.version,
7543
+ source: plan.source,
7544
+ stagingDir: plan.stagingDir,
7545
+ ...plan.marketplace ? { marketplace: plan.marketplace } : {}
7546
+ }))
7547
+ ],
7548
+ opts
7549
+ );
7550
+ const record = records?.find((r) => r.name === manifest.name);
7131
7551
  return record ? { ok: true, record, preview: preview3 } : {
7132
7552
  ok: false,
7133
7553
  preview: preview3,
7134
7554
  reason: t("plugin_install.raced", { name: manifest.name })
7135
7555
  };
7136
7556
  }
7137
- async function commit(source, staging, preview3, opts) {
7138
- const name = preview3.manifest.name;
7139
- const dest = join(opts.pluginsDir, name);
7140
- removeInstallDest(dest);
7141
- const linked = source.type === "local";
7142
- if (linked) {
7143
- try {
7144
- symlinkSync(source.target, dest, "junction");
7145
- } catch {
7146
- opts.onProgress?.(t("plugin_install.symlink_failed"));
7147
- cpSync(source.target, dest, { recursive: true });
7148
- return write(false);
7149
- }
7150
- } else {
7151
- renameSync(pluginRoot(source, staging), dest);
7152
- }
7153
- return write(linked);
7154
- function write(isLink) {
7155
- const record = {
7156
- name,
7157
- version: preview3.manifest.version,
7158
- source: source.raw,
7159
- sourceType: source.type,
7557
+ var DEPENDENCY_LIMIT = 32;
7558
+ async function commitAll(entries2, opts) {
7559
+ const records = [];
7560
+ for (const entry of entries2) {
7561
+ const dest = join(opts.pluginsDir, entry.name);
7562
+ removeInstallDest(dest);
7563
+ let linked = entry.source.type === "local";
7564
+ if (linked) {
7565
+ try {
7566
+ symlinkSync(entry.source.target, dest, "junction");
7567
+ } catch {
7568
+ opts.onProgress?.(t("plugin_install.symlink_failed"));
7569
+ cpSync(entry.source.target, dest, { recursive: true });
7570
+ linked = false;
7571
+ }
7572
+ } else {
7573
+ renameSync(pluginRoot(entry.source, entry.stagingDir), dest);
7574
+ }
7575
+ records.push({
7576
+ name: entry.name,
7577
+ version: entry.version,
7578
+ source: entry.source.raw,
7579
+ sourceType: entry.source.type,
7160
7580
  path: dest,
7161
- linked: isLink,
7581
+ linked,
7162
7582
  enabled: true,
7163
7583
  installedAt: Date.now(),
7164
- ...opts.marketplace ? { marketplace: opts.marketplace } : {},
7165
- ...source.sha256 ? { checksum: `sha256:${source.sha256}` } : {}
7166
- };
7167
- return new PluginStore(opts.statePath).mutate((records) => {
7168
- const taken = records.some((r) => r.name === name);
7169
- if (taken && opts.replace !== name) return void 0;
7170
- return {
7171
- records: taken ? records.map((r) => r.name === name ? record : r) : [...records, record],
7172
- result: record
7173
- };
7584
+ ...entry.marketplace ? { marketplace: entry.marketplace } : {},
7585
+ ...entry.source.sha256 ? { checksum: `sha256:${entry.source.sha256}` } : {}
7174
7586
  });
7175
7587
  }
7588
+ return new PluginStore(opts.statePath).mutate((existing) => {
7589
+ for (const record of records) {
7590
+ const taken = existing.some((r) => r.name === record.name);
7591
+ if (taken && opts.replace !== record.name) return void 0;
7592
+ }
7593
+ let next = existing;
7594
+ for (const record of records) {
7595
+ const taken = next.some((r) => r.name === record.name);
7596
+ next = taken ? next.map((r) => r.name === record.name ? record : r) : [...next, record];
7597
+ }
7598
+ return { records: next, result: records };
7599
+ });
7600
+ }
7601
+ async function resolveClosure(input) {
7602
+ const { opts, staging } = input;
7603
+ const seen = /* @__PURE__ */ new Set([input.root]);
7604
+ const queue = input.declared.map((name) => ({
7605
+ name,
7606
+ requiredBy: input.root
7607
+ }));
7608
+ const report3 = [];
7609
+ const plans = [];
7610
+ while (queue.length > 0) {
7611
+ const next = queue.shift();
7612
+ if (!next) break;
7613
+ const { name, requiredBy } = next;
7614
+ if (seen.has(name)) continue;
7615
+ seen.add(name);
7616
+ if (input.installed.has(name)) {
7617
+ report3.push({ name, status: "installed", requiredBy });
7618
+ continue;
7619
+ }
7620
+ if (plans.length >= DEPENDENCY_LIMIT) {
7621
+ report3.push({
7622
+ name,
7623
+ status: "unresolved",
7624
+ requiredBy,
7625
+ reason: t("plugin_install.dep_too_many", { limit: DEPENDENCY_LIMIT, root: input.root })
7626
+ });
7627
+ continue;
7628
+ }
7629
+ const located = await locateDependency(name, opts);
7630
+ if (!located) {
7631
+ report3.push({
7632
+ name,
7633
+ status: "unresolved",
7634
+ requiredBy,
7635
+ reason: t("plugin_install.dep_no_source", { name })
7636
+ });
7637
+ continue;
7638
+ }
7639
+ const source = resolveSource(located.source);
7640
+ if (!source) {
7641
+ report3.push({
7642
+ name,
7643
+ status: "unresolved",
7644
+ requiredBy,
7645
+ source: located.source,
7646
+ reason: t("plugin_install.dep_bad_source", { name, source: located.source })
7647
+ });
7648
+ continue;
7649
+ }
7650
+ opts.onProgress?.(t("plugin_install.dep_fetching", { name, source: located.source }));
7651
+ const stagingDir = join(staging, `dep-${plans.length}`);
7652
+ const fetched = await fetchInto(source, stagingDir, opts);
7653
+ if (!fetched.ok) {
7654
+ report3.push({
7655
+ name,
7656
+ status: "unresolved",
7657
+ requiredBy,
7658
+ source: located.source,
7659
+ reason: fetched.reason
7660
+ });
7661
+ continue;
7662
+ }
7663
+ const readDir = source.type === "local" ? source.target : pluginRoot(source, stagingDir);
7664
+ const read = readPluginManifest(readDir);
7665
+ const manifest = read.manifest;
7666
+ if (!manifest) {
7667
+ report3.push({
7668
+ name,
7669
+ status: "unresolved",
7670
+ requiredBy,
7671
+ source: located.source,
7672
+ reason: t("plugin_install.dep_bad_manifest", {
7673
+ name,
7674
+ detail: read.issues.map((issue) => issue.message).join("; ")
7675
+ })
7676
+ });
7677
+ continue;
7678
+ }
7679
+ if (manifest.name !== name) {
7680
+ report3.push({
7681
+ name,
7682
+ status: "unresolved",
7683
+ requiredBy,
7684
+ source: located.source,
7685
+ reason: t("plugin_install.dep_name_mismatch", { name, found: manifest.name })
7686
+ });
7687
+ continue;
7688
+ }
7689
+ const current = opts.epochVersion ?? currentEpochVersion();
7690
+ const version = checkEpochVersion(manifest.epochVersion, current);
7691
+ const versionUnmet = !version.ok && manifest.epochVersion ? { required: manifest.epochVersion, current, reason: version.reason } : void 0;
7692
+ report3.push({
7693
+ name,
7694
+ status: "plan",
7695
+ requiredBy,
7696
+ source: located.source,
7697
+ manifest,
7698
+ inventory: scanPluginDir(readDir, manifest.name),
7699
+ ...versionUnmet ? { versionUnmet } : {}
7700
+ });
7701
+ plans.push({
7702
+ name: manifest.name,
7703
+ source,
7704
+ stagingDir,
7705
+ manifest,
7706
+ ...located.marketplace ? { marketplace: located.marketplace } : {}
7707
+ });
7708
+ for (const nested of manifest.dependencies ?? []) {
7709
+ queue.push({ name: nested, requiredBy: manifest.name });
7710
+ }
7711
+ }
7712
+ return { report: report3, plans };
7713
+ }
7714
+ async function locateDependency(name, opts) {
7715
+ const local = opts.marketplacesStatePath ? findMarketplaceEntryByName(name, opts.marketplacesStatePath) : void 0;
7716
+ if (local) return { source: local.source, marketplace: local.marketplace };
7717
+ if (!opts.resolveDependency) return void 0;
7718
+ try {
7719
+ const answer = await opts.resolveDependency(name);
7720
+ return answer ? { source: answer } : void 0;
7721
+ } catch {
7722
+ return void 0;
7723
+ }
7176
7724
  }
7177
7725
  function removeInstallDest(dest) {
7178
7726
  let link = false;
@@ -7236,469 +7784,153 @@ async function fetchFromHost(source, staging, opts) {
7236
7784
  reason: t("plugin_install.host_fetch_no_file", { path: answer.archivePath })
7237
7785
  };
7238
7786
  }
7239
- return extractArchive(answer.archivePath, staging);
7240
- });
7241
- }
7242
- function fetchLocal(source) {
7243
- if (!existsSync(source.target)) {
7244
- return { ok: false, reason: t("plugin_install.local_missing", { path: source.target }) };
7245
- }
7246
- return { ok: true };
7247
- }
7248
- async function fetchGit(source, staging, opts) {
7249
- opts.onProgress?.(t("plugin_install.cloning", { target: source.target }));
7250
- const result = await runCommand("git", ["clone", "--depth", "1", source.target, staging]);
7251
- if (!result.ok) {
7252
- return { ok: false, reason: t("plugin_install.clone_failed", { detail: result.detail }) };
7253
- }
7254
- rmSync(join(staging, ".git"), { recursive: true, force: true });
7255
- return { ok: true };
7256
- }
7257
- async function fetchNpm(source, staging, opts) {
7258
- opts.onProgress?.(t("plugin_install.npm_fetching", { target: source.target }));
7259
- mkdirSync(staging, { recursive: true });
7260
- return withDownloadDir(async (dl) => {
7261
- const packed = await runCommand(
7262
- "npm",
7263
- ["pack", source.target, "--pack-destination", dl, "--silent"],
7264
- { cwd: dl, windowsShell: true }
7265
- );
7266
- if (!packed.ok) {
7267
- return { ok: false, reason: t("plugin_install.npm_pack_failed", { detail: packed.detail }) };
7268
- }
7269
- const tarball = packed.stdout.trim().split("\n").pop()?.trim();
7270
- if (!tarball) return { ok: false, reason: t("plugin_install.npm_no_tarball") };
7271
- return extractArchive(isAbsolute(tarball) ? tarball : join(dl, tarball), staging);
7272
- });
7273
- }
7274
- async function fetchArchive(source, staging, opts) {
7275
- opts.onProgress?.(t("plugin_install.downloading", { target: source.target }));
7276
- mkdirSync(staging, { recursive: true });
7277
- let bytes;
7278
- try {
7279
- const response = await fetch(source.target, { redirect: "follow" });
7280
- if (!response.ok) {
7281
- return {
7282
- ok: false,
7283
- reason: t("plugin_install.download_http", {
7284
- status: response.status,
7285
- statusText: response.statusText
7286
- })
7287
- };
7288
- }
7289
- bytes = Buffer.from(await response.arrayBuffer());
7290
- } catch (err) {
7291
- return {
7292
- ok: false,
7293
- reason: t("plugin_install.download_failed", {
7294
- message: err instanceof Error ? err.message : String(err)
7295
- })
7296
- };
7297
- }
7298
- if (source.sha256) {
7299
- const actual = createHash("sha256").update(bytes).digest("hex");
7300
- if (actual !== source.sha256) {
7301
- return {
7302
- ok: false,
7303
- reason: [
7304
- t("plugin_install.sha_mismatch"),
7305
- t("plugin_install.sha_expected", { expected: source.sha256 }),
7306
- t("plugin_install.sha_actual", { actual })
7307
- ].join("\n")
7308
- };
7309
- }
7310
- }
7311
- return withDownloadDir(async (dl) => {
7312
- const archive = join(dl, `download${archiveExt(source.target)}`);
7313
- writeFileSync(archive, bytes);
7314
- return extractArchive(archive, staging);
7315
- });
7316
- }
7317
- function archiveExt(url) {
7318
- const path = (url.split("?")[0] ?? "").toLowerCase();
7319
- if (path.endsWith(".tgz")) return ".tgz";
7320
- if (path.endsWith(".tar.gz")) return ".tar.gz";
7321
- return ".zip";
7322
- }
7323
- async function withDownloadDir(fn) {
7324
- const dir = mkdtempSync(join(tmpdir(), "epoch-plugin-dl-"));
7325
- try {
7326
- return await fn(dir);
7327
- } finally {
7328
- rmSync(dir, { recursive: true, force: true });
7329
- }
7330
- }
7331
- // src/plugin/root-token.ts
7332
- var PLUGIN_ROOT_TOKEN = "${EPOCH_PLUGIN_ROOT}";
7333
- var PLUGIN_ROOT_ENV = "EPOCH_PLUGIN_ROOT";
7334
- function mentionsPluginRoot(text) {
7335
- return text.includes(PLUGIN_ROOT_TOKEN);
7336
- }
7337
- function substitutePluginRoot(text, root) {
7338
- return text.split(PLUGIN_ROOT_TOKEN).join(root);
7339
- }
7340
- async function uninstallPlugin(name, opts) {
7341
- const removed = await new PluginStore(opts.statePath).mutate((records) => {
7342
- const hit = records.find((r) => r.name === name);
7343
- if (!hit) return void 0;
7344
- return { records: records.filter((r) => r.name !== name), result: hit };
7345
- });
7346
- if (!removed) return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
7347
- if (removed.linked) {
7348
- rmSync(removed.path, { force: true });
7349
- return { ok: true, message: t("plugin_lifecycle.uninstalled_link", { name }) };
7350
- }
7351
- rmSync(removed.path, { recursive: true, force: true });
7352
- return { ok: true, message: t("plugin_lifecycle.uninstalled", { name }) };
7353
- }
7354
- async function setPluginEnabled(name, enabled, opts) {
7355
- const changed = await new PluginStore(opts.statePath).mutate((records) => {
7356
- const hit = records.find((r) => r.name === name);
7357
- if (!hit) return void 0;
7358
- return {
7359
- records: records.map((r) => r.name === name ? { ...r, enabled } : r),
7360
- result: hit.enabled !== enabled
7361
- };
7362
- });
7363
- if (changed === void 0) {
7364
- return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
7365
- }
7366
- const verb = enabled ? t("plugin_lifecycle.verb_enable") : t("plugin_lifecycle.verb_disable");
7367
- return {
7368
- ok: true,
7369
- message: changed ? t("plugin_lifecycle.toggled", { verb, name }) : t("plugin_lifecycle.already", { name, verb })
7370
- };
7371
- }
7372
- async function updatePlugin(name, opts) {
7373
- const existing = new PluginStore(opts.statePath).read().records.find((r) => r.name === name);
7374
- if (!existing) return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
7375
- if (existing.linked) {
7376
- return {
7377
- ok: true,
7378
- message: t("plugin_lifecycle.linked_no_update", { name, source: existing.source })
7379
- };
7380
- }
7381
- return installPlugin(existing.source, {
7382
- ...opts,
7383
- replace: name,
7384
- ...existing.marketplace ? { marketplace: existing.marketplace } : {}
7385
- });
7386
- }
7387
- var MARKETPLACE_FILE = "epoch-marketplace.json";
7388
- var NAME_PATTERN = PLUGIN_NAME_PATTERN;
7389
- var EntryCategoryInput = z.union([z.string().min(1), z.array(z.string().min(1))]).optional().catch(void 0);
7390
- var EntryShape = z.object({
7391
- name: z.string().regex(NAME_PATTERN),
7392
- description: z.string().min(1).optional(),
7393
- source: z.string().min(1),
7394
- category: EntryCategoryInput,
7395
- keywords: z.array(z.string().min(1)).optional(),
7396
- author: PluginAuthorSchema.optional(),
7397
- downloads: z.number().int().nonnegative().optional(),
7398
- likes: z.number().int().nonnegative().optional(),
7399
- displayName: z.string().min(1).optional(),
7400
- icon: z.string().min(1).optional(),
7401
- version: z.string().min(1).optional(),
7402
- changelog: z.string().min(1).optional(),
7403
- size: z.number().int().positive().optional(),
7404
- homepage: z.string().min(1).optional(),
7405
- visibility: z.enum(PLUGIN_VISIBILITY).optional().catch(void 0),
7406
- status: z.enum(["listed", "reviewing", "rejected", "offline"]).optional().catch(void 0)
7407
- });
7408
- var EntrySchema = EntryShape.transform(({ category, ...rest }) => {
7409
- const picked = category === void 0 ? [] : typeof category === "string" ? [category] : category;
7410
- return { ...rest, ...picked.length === 0 ? {} : { category: [...picked] } };
7411
- });
7412
- var MARKETPLACE_DISPLAY_FIELDS = [
7413
- "status",
7414
- "downloads",
7415
- "likes",
7416
- "author",
7417
- "category",
7418
- "visibility",
7419
- "icon",
7420
- "homepage",
7421
- "size"
7422
- ];
7423
- var DisplaySchema = z.object({
7424
- fields: z.array(z.string().min(1)).optional()
7425
- });
7426
- var CategoryNodeSchema = z.lazy(
7427
- () => z.object({
7428
- id: z.string().min(1),
7429
- label: z.string().min(1).optional(),
7430
- children: z.array(CategoryNodeSchema).optional()
7431
- })
7432
- );
7433
- var CategoriesSchema = z.array(CategoryNodeSchema).refine(hasUniqueCategoryIds, { message: "category ids must be unique across the whole tree" }).optional().catch(void 0);
7434
- function hasUniqueCategoryIds(nodes) {
7435
- const seen = /* @__PURE__ */ new Set();
7436
- const walk3 = (list) => list.every((node) => {
7437
- if (seen.has(node.id)) return false;
7438
- seen.add(node.id);
7439
- return node.children === void 0 || walk3(node.children);
7440
- });
7441
- return walk3(nodes);
7442
- }
7443
- var CatalogSchema = z.object({
7444
- name: z.string().regex(NAME_PATTERN),
7445
- description: z.string().min(1).optional(),
7446
- owner: z.object({ name: z.string().min(1), url: z.string().min(1).optional() }).optional(),
7447
- display: DisplaySchema.optional(),
7448
- categories: CategoriesSchema,
7449
- plugins: z.array(EntrySchema)
7450
- });
7451
- var RecordSchema2 = z.object({
7452
- name: z.string().regex(NAME_PATTERN),
7453
- source: z.string().min(1),
7454
- addedAt: z.number(),
7455
- catalog: CatalogSchema
7456
- });
7457
- var StateSchema2 = z.object({
7458
- version: z.literal(1).optional(),
7459
- marketplaces: z.array(RecordSchema2)
7460
- });
7461
- function projectEntryForDisplay(entry, display) {
7462
- const allowed = display?.fields;
7463
- if (allowed === void 0) return entry;
7464
- const out = { ...entry };
7465
- for (const field of MARKETPLACE_DISPLAY_FIELDS) {
7466
- if (!displayAllows(display, field)) delete out[field];
7467
- }
7468
- return out;
7469
- }
7470
- function displayAllows(display, field) {
7471
- const allowed = display?.fields;
7472
- if (allowed === void 0) return true;
7473
- return allowed.includes(field);
7474
- }
7475
- function readMarketplaces(statePath) {
7476
- if (!existsSync(statePath)) return { records: [], issues: [] };
7477
- let raw;
7478
- try {
7479
- raw = JSON.parse(readFileSync(statePath, "utf-8"));
7480
- } catch (err) {
7481
- return {
7482
- records: [],
7483
- issues: [
7484
- { path: statePath, message: t("plugin_marketplace.bad_json", { reason: first(err) }) }
7485
- ]
7486
- };
7487
- }
7488
- const checked = parseLenient(StateSchema2.optional(), raw, void 0);
7489
- if (!checked.value) {
7490
- return {
7491
- records: [],
7492
- issues: checked.issues.map((i) => ({
7493
- path: statePath,
7494
- message: t("plugin_marketplace.issue", { path: i.path, message: i.message })
7495
- }))
7496
- };
7497
- }
7498
- return { records: checked.value.marketplaces, issues: [] };
7499
- }
7500
- async function mutate(statePath, fn) {
7501
- const release = await acquireStateLock(statePath);
7502
- try {
7503
- const next = fn(readMarketplaces(statePath).records);
7504
- if (!next) return false;
7505
- writeStateFile(statePath, JSON.stringify({ version: 1, marketplaces: next }, null, 2));
7506
- return true;
7507
- } finally {
7508
- release();
7509
- }
7510
- }
7511
- async function addMarketplace(source, opts) {
7512
- const fetched = await fetchCatalog(source);
7513
- if (!fetched.ok) return fetched;
7514
- const record = {
7515
- name: fetched.catalog.name,
7516
- source: source.trim(),
7517
- addedAt: Date.now(),
7518
- catalog: fetched.catalog
7519
- };
7520
- const written = await mutate(
7521
- opts.statePath,
7522
- (records) => records.some((r) => r.name === record.name) ? void 0 : [...records, record].sort((a, b) => a.name.localeCompare(b.name))
7523
- );
7524
- return written ? { ok: true, record } : {
7525
- ok: false,
7526
- reason: t("plugin_marketplace.already_added", { name: record.name })
7527
- };
7528
- }
7529
- async function removeMarketplace(name, opts) {
7530
- const written = await mutate(
7531
- opts.statePath,
7532
- (records) => records.some((r) => r.name === name) ? records.filter((r) => r.name !== name) : void 0
7533
- );
7534
- return written ? { ok: true } : { ok: false, reason: t("plugin_marketplace.not_added", { name }) };
7535
- }
7536
- async function updateMarketplace(name, opts) {
7537
- const existing = readMarketplaces(opts.statePath).records.find((r) => r.name === name);
7538
- if (!existing) return { ok: false, reason: t("plugin_marketplace.not_added", { name }) };
7539
- const fetched = await fetchCatalog(existing.source);
7540
- if (!fetched.ok) return fetched;
7541
- if (fetched.catalog.name !== name) {
7542
- return {
7543
- ok: false,
7544
- reason: t("plugin_marketplace.renamed", {
7545
- source: existing.source,
7546
- name: fetched.catalog.name
7547
- })
7548
- };
7549
- }
7550
- const record = { ...existing, catalog: fetched.catalog };
7551
- await mutate(opts.statePath, (records) => records.map((r) => r.name === name ? record : r));
7552
- return { ok: true, record };
7553
- }
7554
- function searchMarketplaces(keyword, statePath) {
7555
- const needle = keyword.trim().toLowerCase();
7556
- const hits = [];
7557
- for (const record of readMarketplaces(statePath).records) {
7558
- for (const entry of record.catalog.plugins) {
7559
- const haystack = [
7560
- entry.name,
7561
- entry.description ?? "",
7562
- ...entry.category ?? [],
7563
- entry.author?.name ?? "",
7564
- ...entry.keywords ?? []
7565
- ].join(" ").toLowerCase();
7566
- if (needle && !haystack.includes(needle)) continue;
7567
- hits.push({
7568
- marketplace: record.name,
7569
- entry: projectEntryForDisplay(entry, record.catalog.display),
7570
- ref: `${record.name}/${entry.name}`
7571
- });
7572
- }
7573
- }
7574
- return hits;
7575
- }
7576
- function resolveMarketplaceRef(input, statePath) {
7577
- const parts = input.trim().split("/");
7578
- if (parts.length !== 2) return void 0;
7579
- const [market = "", plugin = ""] = parts;
7580
- if (!NAME_PATTERN.test(market) || !NAME_PATTERN.test(plugin)) return void 0;
7581
- const record = readMarketplaces(statePath).records.find((r) => r.name === market);
7582
- if (!record) {
7583
- return {
7584
- ok: false,
7585
- reason: t("plugin_marketplace.market_not_added", { market, input })
7586
- };
7587
- }
7588
- const entry = record.catalog.plugins.find((p) => p.name === plugin);
7589
- if (!entry) {
7590
- return {
7591
- ok: false,
7592
- reason: t("plugin_marketplace.plugin_not_in_market", { market, plugin })
7593
- };
7594
- }
7595
- const source = resolveEntrySource(entry.source, record.source);
7596
- if (!source) {
7597
- return {
7598
- ok: false,
7599
- reason: t("plugin_marketplace.entry_unresolvable", {
7600
- market,
7601
- plugin,
7602
- source: entry.source
7603
- })
7604
- };
7605
- }
7606
- return { ok: true, ref: { marketplace: market, source } };
7607
- }
7608
- function resolveEntrySource(entry, marketSource) {
7609
- const spec = entry.trim();
7610
- if (!spec.startsWith(".") && !spec.startsWith("/")) return spec;
7611
- const rel = spec.replace(/^\.\//, "").replace(/^\//, "");
7612
- if (rel === "" || rel.split("/").includes("..")) return void 0;
7613
- const market = marketSource.trim();
7614
- if (market.startsWith("github:") || market.startsWith("git+")) {
7615
- const [base = "", fragment] = market.split("#", 2);
7616
- const prefix = fragment?.startsWith("subdir=") ? `${fragment.slice("subdir=".length)}/` : "";
7617
- return `${base}#subdir=${prefix}${rel}`;
7787
+ return extractArchive(answer.archivePath, staging);
7788
+ });
7789
+ }
7790
+ function fetchLocal(source) {
7791
+ if (!existsSync(source.target)) {
7792
+ return { ok: false, reason: t("plugin_install.local_missing", { path: source.target }) };
7618
7793
  }
7619
- if (market.startsWith("https://") || market.startsWith("http://")) {
7620
- return `${market.replace(/\/[^/]*$/, "")}/${rel}`;
7794
+ return { ok: true };
7795
+ }
7796
+ async function fetchGit(source, staging, opts) {
7797
+ opts.onProgress?.(t("plugin_install.cloning", { target: source.target }));
7798
+ const result = await runCommand("git", ["clone", "--depth", "1", source.target, staging]);
7799
+ if (!result.ok) {
7800
+ return { ok: false, reason: t("plugin_install.clone_failed", { detail: result.detail }) };
7621
7801
  }
7622
- return join(marketDir(market), rel);
7802
+ rmSync(join(staging, ".git"), { recursive: true, force: true });
7803
+ return { ok: true };
7623
7804
  }
7624
- async function fetchCatalog(source) {
7625
- const input = source.trim();
7626
- if (!input) return { ok: false, reason: t("plugin_marketplace.source_empty") };
7627
- const local = localCatalogPath(input);
7628
- if (local) {
7629
- if (!existsSync(local)) {
7630
- return { ok: false, reason: t("plugin_marketplace.local_missing", { path: local }) };
7805
+ async function fetchNpm(source, staging, opts) {
7806
+ opts.onProgress?.(t("plugin_install.npm_fetching", { target: source.target }));
7807
+ mkdirSync(staging, { recursive: true });
7808
+ return withDownloadDir(async (dl) => {
7809
+ const packed = await runCommand(
7810
+ "npm",
7811
+ ["pack", source.target, "--pack-destination", dl, "--silent"],
7812
+ { cwd: dl, windowsShell: true }
7813
+ );
7814
+ if (!packed.ok) {
7815
+ return { ok: false, reason: t("plugin_install.npm_pack_failed", { detail: packed.detail }) };
7631
7816
  }
7632
- try {
7633
- return parseCatalog(readFileSync(local, "utf-8"), local);
7634
- } catch (err) {
7635
- return { ok: false, reason: t("plugin_marketplace.read_failed", { reason: first(err) }) };
7817
+ const tarball = packed.stdout.trim().split("\n").pop()?.trim();
7818
+ if (!tarball) return { ok: false, reason: t("plugin_install.npm_no_tarball") };
7819
+ return extractArchive(isAbsolute(tarball) ? tarball : join(dl, tarball), staging);
7820
+ });
7821
+ }
7822
+ async function fetchArchive(source, staging, opts) {
7823
+ opts.onProgress?.(t("plugin_install.downloading", { target: source.target }));
7824
+ mkdirSync(staging, { recursive: true });
7825
+ let bytes;
7826
+ try {
7827
+ const response = await fetch(source.target, { redirect: "follow" });
7828
+ if (!response.ok) {
7829
+ return {
7830
+ ok: false,
7831
+ reason: t("plugin_install.download_http", {
7832
+ status: response.status,
7833
+ statusText: response.statusText
7834
+ })
7835
+ };
7636
7836
  }
7637
- }
7638
- const url = catalogUrl(input);
7639
- if (!url) {
7837
+ bytes = Buffer.from(await response.arrayBuffer());
7838
+ } catch (err) {
7640
7839
  return {
7641
7840
  ok: false,
7642
- reason: t("plugin_marketplace.bad_url", { input, file: MARKETPLACE_FILE })
7841
+ reason: t("plugin_install.download_failed", {
7842
+ message: err instanceof Error ? err.message : String(err)
7843
+ })
7643
7844
  };
7644
7845
  }
7645
- try {
7646
- const response = await fetch(url, { redirect: "follow" });
7647
- if (!response.ok) {
7648
- const vars = { url, status: response.status };
7846
+ if (source.sha256) {
7847
+ const actual = createHash("sha256").update(bytes).digest("hex");
7848
+ if (actual !== source.sha256) {
7649
7849
  return {
7650
7850
  ok: false,
7651
- reason: looksPrivate(response.status) ? t("marketplace.fetch_failed_private", vars) : t("marketplace.fetch_failed", vars)
7851
+ reason: [
7852
+ t("plugin_install.sha_mismatch"),
7853
+ t("plugin_install.sha_expected", { expected: source.sha256 }),
7854
+ t("plugin_install.sha_actual", { actual })
7855
+ ].join("\n")
7652
7856
  };
7653
7857
  }
7654
- return parseCatalog(await response.text(), url);
7655
- } catch (err) {
7656
- return { ok: false, reason: t("marketplace.fetch_error", { url, reason: first(err) }) };
7657
7858
  }
7859
+ return withDownloadDir(async (dl) => {
7860
+ const archive = join(dl, `download${archiveExt(source.target)}`);
7861
+ writeFileSync(archive, bytes);
7862
+ return extractArchive(archive, staging);
7863
+ });
7658
7864
  }
7659
- function looksPrivate(status) {
7660
- return status === 401 || status === 403 || status === 404;
7865
+ function archiveExt(url) {
7866
+ const path = (url.split("?")[0] ?? "").toLowerCase();
7867
+ if (path.endsWith(".tgz")) return ".tgz";
7868
+ if (path.endsWith(".tar.gz")) return ".tar.gz";
7869
+ return ".zip";
7661
7870
  }
7662
- function catalogUrl(input) {
7663
- if (input.startsWith("github:")) {
7664
- const slug = input.slice("github:".length);
7665
- if (!/^[\w.-]+\/[\w.-]+$/.test(slug)) return void 0;
7666
- return `https://raw.githubusercontent.com/${slug}/HEAD/${MARKETPLACE_FILE}`;
7871
+ async function withDownloadDir(fn) {
7872
+ const dir = mkdtempSync(join(tmpdir(), "epoch-plugin-dl-"));
7873
+ try {
7874
+ return await fn(dir);
7875
+ } finally {
7876
+ rmSync(dir, { recursive: true, force: true });
7667
7877
  }
7668
- if (!input.startsWith("https://") && !input.startsWith("http://")) return void 0;
7669
- return input.endsWith(".json") ? input : `${input.replace(/\/+$/, "")}/${MARKETPLACE_FILE}`;
7670
7878
  }
7671
- function localCatalogPath(input) {
7672
- if (/^[a-z][a-z0-9+.-]+:/i.test(input) || input.startsWith("//")) return void 0;
7673
- const abs = isAbsolute(input) ? input : resolve(input);
7674
- if (existsSync(abs) && statSync(abs).isDirectory()) return join(abs, MARKETPLACE_FILE);
7675
- return abs;
7879
+ // src/plugin/root-token.ts
7880
+ var PLUGIN_ROOT_TOKEN = "${EPOCH_PLUGIN_ROOT}";
7881
+ var PLUGIN_ROOT_ENV = "EPOCH_PLUGIN_ROOT";
7882
+ function mentionsPluginRoot(text) {
7883
+ return text.includes(PLUGIN_ROOT_TOKEN);
7676
7884
  }
7677
- function marketDir(market) {
7678
- const abs = isAbsolute(market) ? market : resolve(market);
7679
- if (existsSync(abs) && statSync(abs).isDirectory()) return abs;
7680
- return abs.replace(/[/\\][^/\\]*$/, "");
7885
+ function substitutePluginRoot(text, root) {
7886
+ return text.split(PLUGIN_ROOT_TOKEN).join(root);
7681
7887
  }
7682
- function parseCatalog(body2, where) {
7683
- let raw;
7684
- try {
7685
- raw = JSON.parse(body2);
7686
- } catch (err) {
7888
+ async function uninstallPlugin(name, opts) {
7889
+ const removed = await new PluginStore(opts.statePath).mutate((records) => {
7890
+ const hit = records.find((r) => r.name === name);
7891
+ if (!hit) return void 0;
7892
+ return { records: records.filter((r) => r.name !== name), result: hit };
7893
+ });
7894
+ if (!removed) return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
7895
+ if (removed.linked) {
7896
+ rmSync(removed.path, { force: true });
7897
+ return { ok: true, message: t("plugin_lifecycle.uninstalled_link", { name }) };
7898
+ }
7899
+ rmSync(removed.path, { recursive: true, force: true });
7900
+ return { ok: true, message: t("plugin_lifecycle.uninstalled", { name }) };
7901
+ }
7902
+ async function setPluginEnabled(name, enabled, opts) {
7903
+ const changed = await new PluginStore(opts.statePath).mutate((records) => {
7904
+ const hit = records.find((r) => r.name === name);
7905
+ if (!hit) return void 0;
7687
7906
  return {
7688
- ok: false,
7689
- reason: t("plugin_marketplace.catalog_bad_json", { where, reason: first(err) })
7907
+ records: records.map((r) => r.name === name ? { ...r, enabled } : r),
7908
+ result: hit.enabled !== enabled
7690
7909
  };
7910
+ });
7911
+ if (changed === void 0) {
7912
+ return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
7691
7913
  }
7692
- const checked = parseLenient(CatalogSchema.optional(), raw, void 0);
7693
- if (!checked.value) {
7694
- const detail = checked.issues.map((i) => t("plugin_marketplace.issue", { path: i.path, message: i.message })).join(t("plugin_marketplace.catalog_issue_sep"));
7695
- return { ok: false, reason: t("plugin_marketplace.catalog_invalid", { where, detail }) };
7696
- }
7697
- return { ok: true, catalog: checked.value };
7914
+ const verb = enabled ? t("plugin_lifecycle.verb_enable") : t("plugin_lifecycle.verb_disable");
7915
+ return {
7916
+ ok: true,
7917
+ message: changed ? t("plugin_lifecycle.toggled", { verb, name }) : t("plugin_lifecycle.already", { name, verb })
7918
+ };
7698
7919
  }
7699
- function first(err) {
7700
- const message = err instanceof Error ? err.message : String(err);
7701
- return message.split("\n")[0] ?? t("plugin_marketplace.unknown_error");
7920
+ async function updatePlugin(name, opts) {
7921
+ const existing = new PluginStore(opts.statePath).read().records.find((r) => r.name === name);
7922
+ if (!existing) return { ok: false, reason: t("plugin_lifecycle.not_installed", { name }) };
7923
+ if (existing.linked) {
7924
+ return {
7925
+ ok: true,
7926
+ message: t("plugin_lifecycle.linked_no_update", { name, source: existing.source })
7927
+ };
7928
+ }
7929
+ return installPlugin(existing.source, {
7930
+ ...opts,
7931
+ replace: name,
7932
+ ...existing.marketplace ? { marketplace: existing.marketplace } : {}
7933
+ });
7702
7934
  }
7703
7935
  var TMP_SUFFIX = ".epoch-rewind";
7704
7936
  function planFor(entry, current) {
@@ -7746,7 +7978,7 @@ async function applyRewind(store, sessionId, manifest, apply, skippedConflicts)
7746
7978
  } catch (err) {
7747
7979
  return { ...empty, failed: t("rewind.stage_failed", { message: messageOf(err) }) };
7748
7980
  }
7749
- return commitAll(staged, skippedConflicts);
7981
+ return commitAll2(staged, skippedConflicts);
7750
7982
  }
7751
7983
  async function stageAll(store, sessionId, manifest, todo, byPath) {
7752
7984
  const staged = [];
@@ -7774,7 +8006,7 @@ async function stageAll(store, sessionId, manifest, todo, byPath) {
7774
8006
  }
7775
8007
  return staged;
7776
8008
  }
7777
- async function commitAll(staged, skippedConflicts) {
8009
+ async function commitAll2(staged, skippedConflicts) {
7778
8010
  const outcome = {
7779
8011
  restored: [],
7780
8012
  deleted: [],
@@ -7931,14 +8163,14 @@ var CheckpointStore = class {
7931
8163
  }
7932
8164
  async enforceTotalSize(protectSession, protectTurnIndex) {
7933
8165
  const turns = await this.collectTurns();
7934
- let total = turns.reduce((sum, t80) => sum + t80.bytes, 0);
8166
+ let total = turns.reduce((sum, t81) => sum + t81.bytes, 0);
7935
8167
  if (total <= this.maxTotalBytes) return;
7936
8168
  turns.sort((a, b) => a.createdAt - b.createdAt);
7937
- for (const t80 of turns) {
8169
+ for (const t81 of turns) {
7938
8170
  if (total <= this.maxTotalBytes) return;
7939
- if (t80.sessionId === protectSession && t80.turnIndex === protectTurnIndex) continue;
7940
- await this.removeTurn(t80.sessionId, t80.turnIndex);
7941
- total -= t80.bytes;
8171
+ if (t81.sessionId === protectSession && t81.turnIndex === protectTurnIndex) continue;
8172
+ await this.removeTurn(t81.sessionId, t81.turnIndex);
8173
+ total -= t81.bytes;
7942
8174
  }
7943
8175
  }
7944
8176
  async collectTurns() {
@@ -10739,11 +10971,11 @@ var PromptBuilder = class {
10739
10971
  memoryInitialized = false;
10740
10972
  projectBlock(tools) {
10741
10973
  const permLevel = this.deps.permissionManager?.getLevel() || "default";
10742
- const hasPlanTool = tools.some((t80) => t80.name === "enter_plan_mode");
10743
- const hasLspTool = tools.some((t80) => t80.name === "lsp_diagnostics");
10744
- const hasAskTool = tools.some((t80) => t80.name === "ask_user_question");
10745
- const hasTodoTool = tools.some((t80) => t80.name === "todo");
10746
- const hasTerminal = tools.some((t80) => t80.name === "terminal");
10974
+ const hasPlanTool = tools.some((t81) => t81.name === "enter_plan_mode");
10975
+ const hasLspTool = tools.some((t81) => t81.name === "lsp_diagnostics");
10976
+ const hasAskTool = tools.some((t81) => t81.name === "ask_user_question");
10977
+ const hasTodoTool = tools.some((t81) => t81.name === "todo");
10978
+ const hasTerminal = tools.some((t81) => t81.name === "terminal");
10747
10979
  const needsCommandApproval = hasTerminal && permLevel === "default";
10748
10980
  const plan = this.deps.approvedPlan?.()?.trim();
10749
10981
  const codeSdk = renderSdkDeclaration(this.deps.codeModeTools?.() ?? []);
@@ -11433,8 +11665,8 @@ function imageParts(content) {
11433
11665
  return typeof content === "string" ? [] : content.filter((p) => p.type === "image");
11434
11666
  }
11435
11667
  function toolSignatureOf(config, tools) {
11436
- const direct = tools.map((t80) => t80.name).join("\0");
11437
- const declared = (config.codeModeTools?.() ?? []).map((t80) => t80.name).join("\0");
11668
+ const direct = tools.map((t81) => t81.name).join("\0");
11669
+ const declared = (config.codeModeTools?.() ?? []).map((t81) => t81.name).join("\0");
11438
11670
  return declared ? `${direct}|${declared}` : direct;
11439
11671
  }
11440
11672
  function modelSignatureOf(info) {
@@ -12982,8 +13214,10 @@ function _loadImpl(overrideHomeDir) {
12982
13214
  return cached.config;
12983
13215
  }
12984
13216
  const config = parseRawConfig(read.raw, issues, track);
12985
- const pinned = applyProviderOverride(config, issues, track);
13217
+ const override = readProviderOverride(issues);
13218
+ const pinned = applyProviderOverride(config, track, override);
12986
13219
  const secretSource = applySecretsFromEnv(config, homeDir, track);
13220
+ if (pinned) applyProviderOverride(config, track, override);
12987
13221
  applyEnvOverrides(config, issues, track);
12988
13222
  if (secretSource === "config-file" && !pinned) detectProviderFromKey(config);
12989
13223
  if (overrideHomeDir) {
@@ -13054,19 +13288,30 @@ function applySecretsFromEnv(config, homeDir, track) {
13054
13288
  }
13055
13289
  return "config-file";
13056
13290
  }
13057
- function applyProviderOverride(config, issues, track) {
13058
- const raw = process.env.EPOCH_BASE_URL;
13059
- if (raw) {
13060
- config.provider.baseUrl = raw;
13061
- track.env.add("provider.baseUrl");
13291
+ function readProviderOverride(issues) {
13292
+ const override = {};
13293
+ const baseUrl = process.env.EPOCH_BASE_URL;
13294
+ if (baseUrl) {
13295
+ override.baseUrl = baseUrl;
13296
+ override.type = "openai-compatible";
13062
13297
  }
13063
13298
  const type = process.env.EPOCH_PROVIDER;
13064
- if (!type) return false;
13065
- if (!isProviderType(type)) {
13066
- issues.push(envIssue("EPOCH_PROVIDER", type, PROVIDER_TYPES.join(" / ")));
13067
- return false;
13299
+ if (type) {
13300
+ if (isProviderType(type)) {
13301
+ override.type ??= type;
13302
+ } else {
13303
+ issues.push(envIssue("EPOCH_PROVIDER", type, PROVIDER_TYPES.join(" / ")));
13304
+ }
13305
+ }
13306
+ return override;
13307
+ }
13308
+ function applyProviderOverride(config, track, override) {
13309
+ if (override.baseUrl) {
13310
+ config.provider.baseUrl = override.baseUrl;
13311
+ track.env.add("provider.baseUrl");
13068
13312
  }
13069
- config.provider.type = type;
13313
+ if (!override.type) return false;
13314
+ config.provider.type = override.type;
13070
13315
  track.env.add("provider.type");
13071
13316
  return true;
13072
13317
  }
@@ -13326,6 +13571,10 @@ var DEFAULT_MODELS = {
13326
13571
  groq: "llama-3.3-70b-versatile",
13327
13572
  mistral: "ministral-8b-latest",
13328
13573
  moonshotai: "kimi-latest",
13574
+ zhipu: "glm-4.5-flash",
13575
+ dashscope: "qwen-flash",
13576
+ bytedance: "doubao-seed-2.0-mini",
13577
+ qianfan: "ernie-speed-128k",
13329
13578
  cohere: "command-r7b-12-2024",
13330
13579
  xai: "grok-vision-beta",
13331
13580
  perplexity: "sonar",
@@ -13738,6 +13987,10 @@ var PACKAGES = {
13738
13987
  groq: "@ai-sdk/groq",
13739
13988
  mistral: "@ai-sdk/mistral",
13740
13989
  moonshotai: "@ai-sdk/moonshotai",
13990
+ zhipu: "@ai-sdk/openai-compatible",
13991
+ dashscope: "@ai-sdk/alibaba",
13992
+ bytedance: "@ai-sdk/openai-compatible",
13993
+ qianfan: "@ai-sdk/openai-compatible",
13741
13994
  cohere: "@ai-sdk/cohere",
13742
13995
  xai: "@ai-sdk/xai",
13743
13996
  perplexity: "@ai-sdk/perplexity",
@@ -13747,6 +14000,17 @@ var PACKAGES = {
13747
14000
  ollama: "ollama-ai-provider-v2",
13748
14001
  "openai-compatible": "@ai-sdk/openai-compatible"
13749
14002
  };
14003
+ function openaiCompatibleFactory(name, defaultBaseUrl) {
14004
+ return async ({ apiKey, baseUrl }) => {
14005
+ const { createOpenAICompatible } = await import('@ai-sdk/openai-compatible');
14006
+ return createOpenAICompatible({
14007
+ apiKey,
14008
+ baseURL: baseUrl || defaultBaseUrl,
14009
+ name,
14010
+ includeUsage: true
14011
+ });
14012
+ };
14013
+ }
13750
14014
  var LOADERS = {
13751
14015
  openai: async ({ apiKey, baseUrl }) => {
13752
14016
  const { createOpenAI } = await import('@ai-sdk/openai');
@@ -13778,6 +14042,17 @@ var LOADERS = {
13778
14042
  const { createMoonshotAI } = await import('@ai-sdk/moonshotai');
13779
14043
  return createMoonshotAI({ apiKey });
13780
14044
  },
14045
+ zhipu: openaiCompatibleFactory("zhipu", "https://open.bigmodel.cn/api/paas/v4"),
14046
+ dashscope: async ({ apiKey, baseUrl }) => {
14047
+ const { createAlibaba } = await import('@ai-sdk/alibaba');
14048
+ const provider = createAlibaba({
14049
+ apiKey,
14050
+ baseURL: baseUrl || "https://dashscope.aliyuncs.com/compatible-mode/v1"
14051
+ });
14052
+ return (model) => provider.languageModel(model);
14053
+ },
14054
+ bytedance: openaiCompatibleFactory("bytedance", "https://ark.cn-beijing.volces.com/api/v3"),
14055
+ qianfan: openaiCompatibleFactory("qianfan", "https://qianfan.baidubce.com/v2"),
13781
14056
  cohere: async ({ apiKey }) => {
13782
14057
  const { createCohere } = await import('@ai-sdk/cohere');
13783
14058
  return createCohere({ apiKey });
@@ -13806,15 +14081,7 @@ var LOADERS = {
13806
14081
  const { createOllama } = await import('ollama-ai-provider-v2');
13807
14082
  return createOllama();
13808
14083
  },
13809
- "openai-compatible": async ({ apiKey, baseUrl }) => {
13810
- const { createOpenAICompatible } = await import('@ai-sdk/openai-compatible');
13811
- return createOpenAICompatible({
13812
- apiKey,
13813
- baseURL: baseUrl || "http://localhost:8080/v1",
13814
- name: "custom",
13815
- includeUsage: true
13816
- });
13817
- }
14084
+ "openai-compatible": openaiCompatibleFactory("custom", "http://localhost:8080/v1")
13818
14085
  };
13819
14086
  var ProviderSdkMissingError = class extends Error {
13820
14087
  constructor(providerType, packageName, cause) {
@@ -14446,6 +14713,10 @@ var STATIC_MODEL_CATALOG = {
14446
14713
  ],
14447
14714
  mistral: ["mistral-large-latest", "mistral-small-latest", "codestral-latest"],
14448
14715
  moonshotai: ["kimi-latest"],
14716
+ zhipu: ["glm-4.6", "glm-4.5-flash", "glm-4.5-air", "glm-4.5"],
14717
+ dashscope: ["qwen3-max", "qwen-plus", "qwen-flash", "qwen-turbo", "qwen-max-latest"],
14718
+ bytedance: ["doubao-seed-2.1-pro", "doubao-seed-2.0-mini", "doubao-seed-1.6-flash"],
14719
+ qianfan: ["ernie-5.0", "ernie-speed-128k", "ernie-4.0-turbo-8k"],
14449
14720
  cohere: ["command-r-plus-04-2024", "command-r7b-12-2024", "command-a-03-2025"],
14450
14721
  xai: ["grok-vision-beta"],
14451
14722
  perplexity: ["sonar", "sonar-pro", "sonar-reasoning"],
@@ -14461,6 +14732,10 @@ var PROVIDER_DISCOVERY = {
14461
14732
  groq: { baseUrl: "https://api.groq.com/openai/v1", auth: "bearer" },
14462
14733
  mistral: { baseUrl: "https://api.mistral.ai/v1", auth: "bearer" },
14463
14734
  moonshotai: { baseUrl: "https://api.moonshot.ai/v1", auth: "bearer" },
14735
+ zhipu: { baseUrl: "https://open.bigmodel.cn/api/paas/v4", auth: "bearer" },
14736
+ dashscope: { baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", auth: "bearer" },
14737
+ bytedance: { baseUrl: "https://ark.cn-beijing.volces.com/api/v3", auth: "bearer" },
14738
+ qianfan: { baseUrl: "https://qianfan.baidubce.com/v2", auth: "bearer" },
14464
14739
  xai: { baseUrl: "https://api.x.ai/v1", auth: "bearer" },
14465
14740
  perplexity: { baseUrl: "https://api.perplexity.ai", auth: "bearer" },
14466
14741
  togetherai: { baseUrl: "https://api.together.xyz/v1", auth: "bearer" }
@@ -14711,7 +14986,7 @@ function writeUserYaml(path, key, value) {
14711
14986
  const after = asMapping(parseYaml(next));
14712
14987
  const intact = after !== null && (dot < 0 ? onlyChanged(before, after, key, value) : onlyChangedNested(before, after, key.slice(0, dot), key.slice(dot + 1), value));
14713
14988
  if (!intact) return { ok: false, reason: "unwritable-file" };
14714
- return commit2(path, next, 384, value);
14989
+ return commit(path, next, 384, value);
14715
14990
  }
14716
14991
  function writeSettingsJson(path, key, value) {
14717
14992
  let before = {};
@@ -14731,7 +15006,7 @@ function writeSettingsJson(path, key, value) {
14731
15006
  }
14732
15007
  const body2 = `${JSON.stringify({ ...before, [key]: value }, null, 2)}
14733
15008
  `;
14734
- return commit2(path, body2, 420, value);
15009
+ return commit(path, body2, 420, value);
14735
15010
  }
14736
15011
  function scalarText(value) {
14737
15012
  return dump(value, { lineWidth: -1 }).trimEnd();
@@ -14788,7 +15063,7 @@ function sameKeysAndValues(before, after, except) {
14788
15063
  function sameData(a, b) {
14789
15064
  return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
14790
15065
  }
14791
- function commit2(path, body2, createMode, value) {
15066
+ function commit(path, body2, createMode, value) {
14792
15067
  const tmp = `${path}.${process.pid}.tmp`;
14793
15068
  try {
14794
15069
  mkdirSync(dirname(path), { recursive: true });
@@ -15079,11 +15354,11 @@ function buildSearchQuery(query) {
15079
15354
  }
15080
15355
  const latinPart = cleaned.replace(CJK_RUN, " ");
15081
15356
  for (const w of latinPart.split(/\s+/)) {
15082
- const t80 = w.trim();
15083
- if (t80.length >= 2) terms.add(t80);
15357
+ const t81 = w.trim();
15358
+ if (t81.length >= 2) terms.add(t81);
15084
15359
  }
15085
15360
  if (terms.size === 0) return null;
15086
- return [...terms].map((t80) => `"${t80.replace(/"/g, "")}"`).join(" OR ");
15361
+ return [...terms].map((t81) => `"${t81.replace(/"/g, "")}"`).join(" OR ");
15087
15362
  }
15088
15363
  // src/memory/db.ts
15089
15364
  var NS2 = "memory";
@@ -17102,7 +17377,7 @@ function checkFtsHealth(db) {
17102
17377
  messages,
17103
17378
  tables,
17104
17379
  missingTriggers,
17105
- healthy: missingTriggers.length === 0 && tables.every((t80) => t80.error === null && t80.drift === null && t80.rows === messages),
17380
+ healthy: missingTriggers.length === 0 && tables.every((t81) => t81.error === null && t81.drift === null && t81.rows === messages),
17106
17381
  indexBytes: indexBytesOf(db),
17107
17382
  space: spaceOf(db)
17108
17383
  };
@@ -18196,6 +18471,72 @@ function isLastOpenSession(value) {
18196
18471
  const v = value;
18197
18472
  return typeof v["root"] === "string" && v["root"].length > 0 && typeof v["sessionId"] === "string" && v["sessionId"].length > 0 && typeof v["at"] === "number";
18198
18473
  }
18474
+ var FILE_VERSION4 = 1;
18475
+ function describe8(err) {
18476
+ return err instanceof Error ? err.message : String(err);
18477
+ }
18478
+ var AppearanceStore = class {
18479
+ constructor(filePath = appearancePath()) {
18480
+ this.filePath = filePath;
18481
+ this.load();
18482
+ }
18483
+ filePath;
18484
+ prefs = {};
18485
+ loadErrors = [];
18486
+ writeErrors = [];
18487
+ get() {
18488
+ return { ...this.prefs };
18489
+ }
18490
+ set(patch) {
18491
+ const next = { ...this.prefs };
18492
+ for (const key of ["theme", "lang"]) {
18493
+ const value = patch[key];
18494
+ if (typeof value === "string" && value.length > 0) next[key] = value;
18495
+ }
18496
+ if (next.theme === this.prefs.theme && next.lang === this.prefs.lang) return;
18497
+ this.prefs = next;
18498
+ this.save();
18499
+ }
18500
+ load() {
18501
+ this.prefs = {};
18502
+ if (!existsSync(this.filePath)) return;
18503
+ let parsed;
18504
+ try {
18505
+ parsed = JSON.parse(readFileSync(this.filePath, "utf-8"));
18506
+ } catch (err) {
18507
+ this.loadErrors.push(
18508
+ (lang) => t("appearance.unreadable", { path: this.filePath, reason: describe8(err) }, lang)
18509
+ );
18510
+ return;
18511
+ }
18512
+ this.adopt(parsed);
18513
+ }
18514
+ adopt(parsed) {
18515
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
18516
+ const file = parsed;
18517
+ if (file.version !== FILE_VERSION4) return;
18518
+ if (typeof file.theme === "string" && file.theme.length > 0) this.prefs.theme = file.theme;
18519
+ if (typeof file.lang === "string" && file.lang.length > 0) this.prefs.lang = file.lang;
18520
+ }
18521
+ save() {
18522
+ const body2 = JSON.stringify(
18523
+ { version: FILE_VERSION4, ...this.prefs },
18524
+ null,
18525
+ 2
18526
+ );
18527
+ const tmp = `${this.filePath}.${process.pid}.tmp`;
18528
+ try {
18529
+ mkdirSync(dirname(this.filePath), { recursive: true });
18530
+ writeFileSync(tmp, body2, { encoding: "utf-8", mode: 384 });
18531
+ renameSync(tmp, this.filePath);
18532
+ } catch (err) {
18533
+ rmSync(tmp, { force: true });
18534
+ this.writeErrors.push(
18535
+ (lang) => t("appearance.write_failed", { path: this.filePath, reason: describe8(err) }, lang)
18536
+ );
18537
+ }
18538
+ }
18539
+ };
18199
18540
  function ruleAskActive(level) {
18200
18541
  return level !== "bypass";
18201
18542
  }
@@ -18446,11 +18787,11 @@ var ApprovalCache = class {
18446
18787
  return !rel.startsWith("..") && !isAbsolute(rel);
18447
18788
  }
18448
18789
  normalizeTarget(target) {
18449
- const t80 = target.trim();
18450
- if (!t80) return "";
18451
- const unix = t80.replace(/\\/g, "/");
18790
+ const t81 = target.trim();
18791
+ if (!t81) return "";
18792
+ const unix = t81.replace(/\\/g, "/");
18452
18793
  if (unix.startsWith("/") || /^[A-Za-z]:\//.test(unix)) return resolve(unix);
18453
- return t80;
18794
+ return t81;
18454
18795
  }
18455
18796
  load() {
18456
18797
  try {
@@ -18553,9 +18894,9 @@ function createSharedPermissionState(cache) {
18553
18894
  };
18554
18895
  }
18555
18896
  function cacheKeyFor(raw) {
18556
- const t80 = raw.trim();
18557
- if (!t80) return null;
18558
- return t80.slice(0, 500);
18897
+ const t81 = raw.trim();
18898
+ if (!t81) return null;
18899
+ return t81.slice(0, 500);
18559
18900
  }
18560
18901
  // src/permission/manager.ts
18561
18902
  var log3 = createLogger("permission");
@@ -19854,7 +20195,7 @@ function parseSimpleToml(content, file) {
19854
20195
  const at = `${file}:${i + 1}`;
19855
20196
  if (!trimmed || trimmed.startsWith("#")) continue;
19856
20197
  if (trimmed === "[[rule]]") {
19857
- if (current) commit3(current, file, rules, issues);
20198
+ if (current) commit2(current, file, rules, issues);
19858
20199
  current = { raw: {}, line: i + 1 };
19859
20200
  continue;
19860
20201
  }
@@ -19877,7 +20218,7 @@ function parseSimpleToml(content, file) {
19877
20218
  current.raw[kv.key] = kv.value;
19878
20219
  }
19879
20220
  }
19880
- if (current) commit3(current, file, rules, issues);
20221
+ if (current) commit2(current, file, rules, issues);
19881
20222
  return { rules, issues };
19882
20223
  }
19883
20224
  function readKeyValue(line) {
@@ -19891,7 +20232,7 @@ function readKeyValue(line) {
19891
20232
  const sq = rawVal.match(/^'(.*)'$/);
19892
20233
  return { key, value: sq && sq[1] ? sq[1] : rawVal };
19893
20234
  }
19894
- function commit3(draft, file, rules, issues) {
20235
+ function commit2(draft, file, rules, issues) {
19895
20236
  const at = `${file}:${draft.line}`;
19896
20237
  issues.push(...unknownKeyIssues(POLICY_RULE_KEYS, draft.raw, at));
19897
20238
  const raw = { ...draft.raw };
@@ -20985,7 +21326,7 @@ var HookManager = class {
20985
21326
  return { blocked: true, reason: result.rejectReason || t("hook_load.rejected") };
20986
21327
  }
20987
21328
  } catch (err) {
20988
- this.log("warn", `[HookManager] ${type} \u7684 command hook \u6267\u884C\u5F02\u5E38: ${describe9(err)}`, {
21329
+ this.log("warn", `[HookManager] ${type} \u7684 command hook \u6267\u884C\u5F02\u5E38: ${describe10(err)}`, {
20989
21330
  hookType: type,
20990
21331
  command: config.command
20991
21332
  });
@@ -21003,7 +21344,7 @@ var HookManager = class {
21003
21344
  (_, reject2) => setTimeout(() => reject2(new Error("Hook timeout")), HOOK_TIMEOUT_MS)
21004
21345
  )
21005
21346
  ]).catch((err) => {
21006
- this.log("warn", `[HookManager] ${type} (${reg.plugin}) \u5931\u8D25: ${describe9(err)}`, {
21347
+ this.log("warn", `[HookManager] ${type} (${reg.plugin}) \u5931\u8D25: ${describe10(err)}`, {
21007
21348
  hookType: type,
21008
21349
  plugin: reg.plugin
21009
21350
  });
@@ -21015,7 +21356,7 @@ var HookManager = class {
21015
21356
  if (!matchesHook(entry.matcher, event)) continue;
21016
21357
  for (const config of entry.configs) {
21017
21358
  runCommandHook(config, event, ctx.workDir).then((result) => reportIfNeverRan(this.log, type, config, result)).catch((err) => {
21018
- this.log("warn", `[HookManager] ${type} \u7684 command hook \u5F02\u6B65\u5F02\u5E38: ${describe9(err)}`, {
21359
+ this.log("warn", `[HookManager] ${type} \u7684 command hook \u5F02\u6B65\u5F02\u5E38: ${describe10(err)}`, {
21019
21360
  hookType: type,
21020
21361
  command: config.command
21021
21362
  });
@@ -21036,7 +21377,7 @@ var HookManager = class {
21036
21377
  ]);
21037
21378
  if (result !== null && result !== void 0) return result;
21038
21379
  } catch (err) {
21039
- this.log("warn", `[HookManager] ${type} (${reg.plugin}) \u5931\u8D25: ${describe9(err)}`, {
21380
+ this.log("warn", `[HookManager] ${type} (${reg.plugin}) \u5931\u8D25: ${describe10(err)}`, {
21040
21381
  hookType: type,
21041
21382
  plugin: reg.plugin
21042
21383
  });
@@ -21057,7 +21398,7 @@ var HookManager = class {
21057
21398
  ]);
21058
21399
  if (result) results.push(result);
21059
21400
  } catch (err) {
21060
- this.log("warn", `[HookManager] ${type} (${reg.plugin}) \u5931\u8D25: ${describe9(err)}`, {
21401
+ this.log("warn", `[HookManager] ${type} (${reg.plugin}) \u5931\u8D25: ${describe10(err)}`, {
21061
21402
  hookType: type,
21062
21403
  plugin: reg.plugin
21063
21404
  });
@@ -21074,7 +21415,7 @@ var HookManager = class {
21074
21415
  const verdict = verdictOf2(result);
21075
21416
  if (verdict) results.push(verdict);
21076
21417
  } catch (err) {
21077
- this.log("warn", `[HookManager] ${type} \u7684 command hook \u6267\u884C\u5F02\u5E38: ${describe9(err)}`, {
21418
+ this.log("warn", `[HookManager] ${type} \u7684 command hook \u6267\u884C\u5F02\u5E38: ${describe10(err)}`, {
21078
21419
  hookType: type,
21079
21420
  command: config.command
21080
21421
  });
@@ -21090,7 +21431,7 @@ var HookManager = class {
21090
21431
  return [...list].sort((a, b) => a.priority - b.priority);
21091
21432
  }
21092
21433
  };
21093
- function describe9(err) {
21434
+ function describe10(err) {
21094
21435
  return err instanceof Error ? err.message : String(err);
21095
21436
  }
21096
21437
  function verdictOf2(result) {
@@ -22379,7 +22720,7 @@ async function importSkills(source, opts) {
22379
22720
  detail: t("skill.import_stale", void 0, opts.lang)
22380
22721
  };
22381
22722
  }
22382
- return { ok: true, ...commit4(preview3, staged, opts.skillsDir, opts.lang) };
22723
+ return { ok: true, ...commit3(preview3, staged, opts.skillsDir, opts.lang) };
22383
22724
  });
22384
22725
  }
22385
22726
  async function withStaged(source, opts, fn) {
@@ -22413,7 +22754,7 @@ async function withStaged(source, opts, fn) {
22413
22754
  shape = existsSync(join(unpacked, SKILL_FILE)) ? "skill-dir" : "tree";
22414
22755
  }
22415
22756
  const staged = stageImport(root, staging, shape, opts.lang);
22416
- const preview3 = describe10(source, form, staged.skills, staged.issues, opts.existing, opts.lang);
22757
+ const preview3 = describe11(source, form, staged.skills, staged.issues, opts.existing, opts.lang);
22417
22758
  if (preview3.skills.length === 0) {
22418
22759
  const detail = t("skill.import_empty", { path: source }, opts.lang);
22419
22760
  return { ok: false, reason: "empty", detail, issues: preview3.issues };
@@ -22424,7 +22765,7 @@ async function withStaged(source, opts, fn) {
22424
22765
  rmSync(unpacked, { recursive: true, force: true });
22425
22766
  }
22426
22767
  }
22427
- function describe10(source, form, staged, issues, existing, lang) {
22768
+ function describe11(source, form, staged, issues, existing, lang) {
22428
22769
  const byName = new Map(existing.map((s) => [s.name, s.scope]));
22429
22770
  const entries2 = [];
22430
22771
  const taken = /* @__PURE__ */ new Set();
@@ -22498,7 +22839,7 @@ function digest(entries2, staged) {
22498
22839
  }
22499
22840
  return hash.digest("hex").slice(0, 32);
22500
22841
  }
22501
- function commit4(preview3, staged, skillsDir, lang) {
22842
+ function commit3(preview3, staged, skillsDir, lang) {
22502
22843
  const byName = new Map(staged.map((s) => [s.name, s.dir]));
22503
22844
  const imported = [];
22504
22845
  const skipped = [];
@@ -23273,10 +23614,10 @@ ${extra}` : withGoal;
23273
23614
  return messages.reduce((sum, m) => sum + estimateTokens(m.content), 0);
23274
23615
  }
23275
23616
  };
23276
- var FILE_VERSION4 = 1;
23617
+ var FILE_VERSION5 = 1;
23277
23618
  var LEVELS = ["trusted", "untrusted"];
23278
23619
  var SCOPES = ["directory", "directory-tree"];
23279
- function describe11(err) {
23620
+ function describe12(err) {
23280
23621
  return err instanceof Error ? err.message : String(err);
23281
23622
  }
23282
23623
  function covers(base, target, scope) {
@@ -23350,7 +23691,7 @@ var TrustManager = class {
23350
23691
  raw = readFileSync(this.filePath, "utf-8");
23351
23692
  } catch (err) {
23352
23693
  this.loadErrors.push(
23353
- t("trust_gate.record_read_failed", { path: this.filePath, reason: describe11(err) })
23694
+ t("trust_gate.record_read_failed", { path: this.filePath, reason: describe12(err) })
23354
23695
  );
23355
23696
  return;
23356
23697
  }
@@ -23359,7 +23700,7 @@ var TrustManager = class {
23359
23700
  parsed = JSON.parse(raw);
23360
23701
  } catch (err) {
23361
23702
  this.loadErrors.push(
23362
- t("trust_gate.record_bad_json", { path: this.filePath, reason: describe11(err) })
23703
+ t("trust_gate.record_bad_json", { path: this.filePath, reason: describe12(err) })
23363
23704
  );
23364
23705
  return;
23365
23706
  }
@@ -23371,12 +23712,12 @@ var TrustManager = class {
23371
23712
  return;
23372
23713
  }
23373
23714
  const file = parsed;
23374
- if (file.version !== FILE_VERSION4) {
23715
+ if (file.version !== FILE_VERSION5) {
23375
23716
  this.loadErrors.push(
23376
23717
  t("trust_gate.record_bad_version", {
23377
23718
  path: this.filePath,
23378
23719
  found: String(file.version),
23379
- expected: FILE_VERSION4
23720
+ expected: FILE_VERSION5
23380
23721
  })
23381
23722
  );
23382
23723
  return;
@@ -23397,7 +23738,7 @@ var TrustManager = class {
23397
23738
  save() {
23398
23739
  mkdirSync(dirname(this.filePath), { recursive: true });
23399
23740
  const body2 = JSON.stringify(
23400
- { version: FILE_VERSION4, records: this.records },
23741
+ { version: FILE_VERSION5, records: this.records },
23401
23742
  null,
23402
23743
  2
23403
23744
  );
@@ -23408,7 +23749,7 @@ var TrustManager = class {
23408
23749
  } catch (err) {
23409
23750
  rmSync(tmp, { force: true });
23410
23751
  throw new Error(
23411
- t("trust_gate.record_write_failed", { path: this.filePath, reason: describe11(err) })
23752
+ t("trust_gate.record_write_failed", { path: this.filePath, reason: describe12(err) })
23412
23753
  );
23413
23754
  }
23414
23755
  }
@@ -23797,4 +24138,4 @@ function trunc(stdout, stderr) {
23797
24138
  * `--tree` 的命令帮助里,让用户自己知道 `--tree` 意味着什么。
23798
24139
  */
23799
24140
 
23800
- export { AGENT_ROLE_DIAG_MODULE, ARTIFACT_MAX_TOTAL_BYTES, ARTIFACT_RETENTION_DEFAULTS, ATTACHMENT_MAX_TOTAL_BYTES, ATTACHMENT_RETENTION_DEFAULTS, AgentLoop, ApprovalCache, BLOCKED_TOOLS, BudgetGuard, BudgetStore, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, CheckpointManager, CheckpointStore, CodeSandbox, ComplianceEngine, ContextCompressor, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_MAX_HOLD_CHARS, DelegateManager, EMPTY_RULES, EPOCH_DISK_LEDGER, EpochConfigSchema, FTS_TABLES, FTS_TRIGGERS, GOAL_BLOCK_CODES, GoalService, GoalStore, HEADLESS_AUDIT_CODES, HookManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, ImageLoadError, ImportTrustStore, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, LastOpenSessions, MARKETPLACE_DISPLAY_FIELDS, MARKETPLACE_FILE, MAX_AUDIT_ENTRIES, MAX_COMMAND_DEPTH, MAX_GOAL_MAX_ROUNDS, MAX_IMPORT_DEPTH, MAX_LAST_OPEN_SESSIONS, MAX_OBJECTIVE_CHARS, MAX_REASON_CHARS, MAX_RECENT_WORKSPACES, MODEL_GIVE_UP_THRESHOLD, MULTIMODAL_DEFAULTS, ManagedPolicyError, ManagedSettingsSchema, MemoryManager, ModelFailureTracker, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, PACK_EXCLUDES, PACK_MAX_BYTES, PACK_MAX_FILES, PERMISSION_AUDIT_CODES, PLUGIN_LAYOUT, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_KEYS, PLUGIN_NAME_PATTERN, PLUGIN_ROOT_ENV, PLUGIN_ROOT_TOKEN, PLUGIN_VISIBILITY, POLICY_DECISIONS, PROBE_VERSION, PROJECT_AGENTS_SUBDIR, PROVIDER_NO_CREDENTIAL, PermissionManager, PlanModeState, PluginAuthorSchema, PluginStore, PolicyRuleSchema, ProviderError, ProviderRouter, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, RecentWorkspaces, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCALAR_KEYS, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_FORM_BUDGET_USD, SCHEDULE_ISSUE_CODES, SCHTASKS_FOLDER, SESSION_MESSAGES_READ_TOOL, SESSION_MESSAGES_SEARCH_TOOL, SESSION_QUERY_TOOLS, SESSION_SEARCH_TOOL, SESSION_TRACE_TOOL, SETTINGS_LAYER_ORDER, SETTING_WRITE_KEYS, SETTING_WRITE_LAYERS, SKILL_CREATE_TOOL, SKILL_EDIT_TOOL, SKILL_PATCH_TOOL, SKILL_VIEW_TOOL, SKILL_WRITE_FILE_TOOL, SLASH_COMMANDS_DIAG_MODULE, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, SessionManager, SessionQueries, SessionReferences, SettingsFileError, SettingsFileSchema, SkillLearner, SkillSystem, TOOL_SEARCH_TOOL, TaskNoticeTracker, ToolRegistry, TrackerDB, TrustManager, Workspace, acquireScheduleLock, addMarketplace, addUsage, admitArtifact, admitArtifacts, affectedPaths, artifactLaunchArgv, artifactOpenRefusal, assertLevelAllowed, blockedRoleTools, buildContextBreakdown, buildFireArgv, buildFtsMatchQuery, buildSchemaExports, buildSystemPrompt, buildTrigramMatchQuery, builtinRoles, checkEpochVersion, checkFtsHealth, checkShadowing, classifyPersistFailure, classifyProviderError, clearModelCache, coalesceRecording, codeModeWorkerPath, commandNameFromRelPath, compileRuleLists, compressionOptions, computeCost, computeCostMicros, computeExecHash, countRules, createAllowAllGate, createAllowAllImportGate, createAskQuestionTool, createCodeExecTool, createDelegateTool, createDenyAllGate, createDenyAllImportGate, createGoalTool, createImportGate, createMemoryTool, createPlanModeTools, createRoleFile, createRoleScope, createRunCodeTool, createScheduleDraftTool, createSessionMessagesReadTool, createSessionMessagesSearchTool, createSessionSearchTool, createSessionSearchTools, createSessionTraceTool, createSkillViewTool, createSkillWriteTools, createTodoTool, createToolRevealState, createToolScope, createToolSearchTool, createTrustGate, currentEpochVersion, decideImport, decideTrust, defaultConfig, defaultModelOf, describeArtifactRefusal, describeBackgroundTask, describeHeadlessPolicy, describeIsolation, describeJsonError, diagnoseSchedules, discoverMarkdown, discoverModels, discoverProject, displayAllows, displayCurrency, draftCommandLine, draftWidening, escapeRuleContent, estimateImageTokens, estimateRunTokens, estimateTokens, evaluateSelection, expandCommand, expandImports, explainSetting, extractArchive, findInstructionFiles, findProjectInstructions, findRole, formatBytes, formatSkillIndex, formatUsd, gateForTool, getCapability, getConfigIssues, getConfigOrigins, getImportGate, getTrustGate, hasDedicatedExecContent, hasExecTierContent, hasPromptCaching, hasProviderCredentials, hasReadTierContent, imageFromBase64, imageFromPath, imagePartTokens, importParts, importSkills, installPlugin, instructionDirs, instructionNotices, intervalSlots, inventoryProjectExtensions, inventoryTotal, isBlockCode, isCodeModeDeferred, isEmptyRules, isGoalPhase, isNonInteractive, isReadOnlySubCall, isSettingWriteLayer, isTransientPersistFailure, isValidPluginName, isWithinWindow, isWritableSettingKey, labelSlug, layerRank, listRunningSchedules, listSessionCandidates, listWorkspaceFiles, loadCommandDefinitions, loadCommandFile, loadCommands, loadConfig, loadHookSources, loadKeybindings, loadLexiconDir, loadLexiconFiles, loadPlugins, loadPolicyFiles, loadPolicyRules, loadRoleDefinitions, loadRoleDir, loadedImports, localDateString, managedSwitchNotes, mapClaudeToolName, mapEpochToolName, mapOperationType, matchPreauthorization, matchRuleLists, matchesHook, mentionsPluginRoot, mergeHeadlessPolicy, mergeRoles, mergeSchedulePatch, microsToUsd, modelListCandidates, needsAllowlist, nextFallbackModel, nextRunAt, noConfigOrigins, noManagedPolicy, noPlugins, openArtifact, originOf, packFileName, packPlugin, parseClaudeSettingsHooks, parseClock, parseDate, parseFrontmatter, parseHooksConfig, parseHooksConfigVerbose, parseImportLine, parseModelList, parseRoleDefinition, parseScheduleDraft, parseSkillFrontmatter, parseTrigger, pendingImageTokens, persistFailureCode, persistParts, persistToolResults, persistedUsage, planPack, previewSkillImport, pricingFor, projectEntryForDisplay, pruneArtifacts, pruneAttachments, pruneRecordings, pruneSchedules, readImageSize, readMarketplaces, readPluginManifest, readPluginRecords, readProviderKeyEnv, readRecording, readSessionSurface, reclaimFtsSpace, redactLine, refreshOpenRouterMetadata, removeMarketplace, removeRecordings, removeRoleFile, removeSessionArtifacts, removeSessionAttachments, renderImportTree, renderProjectContext, renderSdkDeclaration, renderSelfIndex, repairSchedules, resolveArtifactRetention, resolveCodeModeWorker, resolveComplianceDirs, resolveEntrySource, resolveInWorkspace, resolveMarketplaceRef, resolveMentions, resolveMultimodalPolicy, resolvePolicyDirs, resolvePricingAt, resolveProjectRoot, resolveScheduleBackend, resolveSessionReference, resolveSessionVisibility, resolveSettings, resolveSkillDirs, resolveSource, resolveTar, resolveWorkspace, restoreParts, revealScopedProvider, roleScopedProvider, roleSourceLabel, ruleCovers, ruleFromString, ruleToString, runCode, runCommand2 as runCommand, sanitizeArgNames, sanitizeCommand, sanitizeFtsQuery, sanitizePath, sanitizeToolOutput, scanInstructions, scanPluginDir, scanSkillDirs, searchMarketplaces, setDisplayCurrency, setImportGate, setPluginEnabled, setTrustGate, settingValueChoices, settingValueKind, settingWriteApply, settingWriteLayers, settingWriteTargets, skillIndexDescription, skillIndexResidency, skillIndexStats, sniffMediaType, splitFrontmatter, staticToolProvider, stripArtifactData, substitutePluginRoot, suggestRuleFromApproval, systemOpenArgv, systemPromptSegments, toPosix, toolDefinitionTokens, toolMatches, toolTableTokens, totalTokens, traceSession, triggerPeriodMs, unescapeRuleContent, uninstallPlugin, unknownRoleSkills, unknownRoleTools, unloadedInstructionFiles, updateMarketplace, updatePlugin, usdToMicros, validateSchedule, validateTrigger, validateWindow, withGoalCompression, withGoalPrompt, workspaceDisplayPath, writeSettingValue };
24141
+ export { AGENT_ROLE_DIAG_MODULE, ARTIFACT_MAX_TOTAL_BYTES, ARTIFACT_RETENTION_DEFAULTS, ATTACHMENT_MAX_TOTAL_BYTES, ATTACHMENT_RETENTION_DEFAULTS, AgentLoop, AppearanceStore, ApprovalCache, BLOCKED_TOOLS, BudgetGuard, BudgetStore, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, CheckpointManager, CheckpointStore, CodeSandbox, ComplianceEngine, ContextCompressor, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_MAX_HOLD_CHARS, DelegateManager, EMPTY_RULES, EPOCH_DISK_LEDGER, EpochConfigSchema, FTS_TABLES, FTS_TRIGGERS, GOAL_BLOCK_CODES, GoalService, GoalStore, HEADLESS_AUDIT_CODES, HookManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, ImageLoadError, ImportTrustStore, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, LastOpenSessions, MARKETPLACE_DISPLAY_FIELDS, MARKETPLACE_FILE, MAX_AUDIT_ENTRIES, MAX_COMMAND_DEPTH, MAX_GOAL_MAX_ROUNDS, MAX_IMPORT_DEPTH, MAX_LAST_OPEN_SESSIONS, MAX_OBJECTIVE_CHARS, MAX_REASON_CHARS, MAX_RECENT_WORKSPACES, MODEL_GIVE_UP_THRESHOLD, MULTIMODAL_DEFAULTS, ManagedPolicyError, ManagedSettingsSchema, MemoryManager, ModelFailureTracker, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, PACK_EXCLUDES, PACK_MAX_BYTES, PACK_MAX_FILES, PERMISSION_AUDIT_CODES, PLUGIN_LAYOUT, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_KEYS, PLUGIN_NAME_PATTERN, PLUGIN_ROOT_ENV, PLUGIN_ROOT_TOKEN, PLUGIN_VISIBILITY, POLICY_DECISIONS, PROBE_VERSION, PROJECT_AGENTS_SUBDIR, PROVIDER_NO_CREDENTIAL, PermissionManager, PlanModeState, PluginAuthorSchema, PluginStore, PolicyRuleSchema, ProviderError, ProviderRouter, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, RecentWorkspaces, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCALAR_KEYS, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_FORM_BUDGET_USD, SCHEDULE_ISSUE_CODES, SCHTASKS_FOLDER, SESSION_MESSAGES_READ_TOOL, SESSION_MESSAGES_SEARCH_TOOL, SESSION_QUERY_TOOLS, SESSION_SEARCH_TOOL, SESSION_TRACE_TOOL, SETTINGS_LAYER_ORDER, SETTING_WRITE_KEYS, SETTING_WRITE_LAYERS, SKILL_CREATE_TOOL, SKILL_EDIT_TOOL, SKILL_PATCH_TOOL, SKILL_VIEW_TOOL, SKILL_WRITE_FILE_TOOL, SLASH_COMMANDS_DIAG_MODULE, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, SessionManager, SessionQueries, SessionReferences, SettingsFileError, SettingsFileSchema, SkillLearner, SkillSystem, TOOL_SEARCH_TOOL, TaskNoticeTracker, ToolRegistry, TrackerDB, TrustManager, Workspace, acquireScheduleLock, addMarketplace, addUsage, admitArtifact, admitArtifacts, affectedPaths, artifactLaunchArgv, artifactOpenRefusal, assertLevelAllowed, blockedRoleTools, buildContextBreakdown, buildFireArgv, buildFtsMatchQuery, buildSchemaExports, buildSystemPrompt, buildTrigramMatchQuery, builtinRoles, checkEpochVersion, checkFtsHealth, checkShadowing, classifyPersistFailure, classifyProviderError, clearModelCache, coalesceRecording, codeModeWorkerPath, commandNameFromRelPath, compileRuleLists, compressionOptions, computeCost, computeCostMicros, computeExecHash, countRules, createAllowAllGate, createAllowAllImportGate, createAskQuestionTool, createCodeExecTool, createDelegateTool, createDenyAllGate, createDenyAllImportGate, createGoalTool, createImportGate, createMemoryTool, createPlanModeTools, createRoleFile, createRoleScope, createRunCodeTool, createScheduleDraftTool, createSessionMessagesReadTool, createSessionMessagesSearchTool, createSessionSearchTool, createSessionSearchTools, createSessionTraceTool, createSkillViewTool, createSkillWriteTools, createTodoTool, createToolRevealState, createToolScope, createToolSearchTool, createTrustGate, currentEpochVersion, decideImport, decideTrust, defaultConfig, defaultModelOf, describeArtifactRefusal, describeBackgroundTask, describeHeadlessPolicy, describeIsolation, describeJsonError, diagnoseSchedules, discoverMarkdown, discoverModels, discoverProject, displayAllows, displayCurrency, draftCommandLine, draftWidening, escapeRuleContent, estimateImageTokens, estimateRunTokens, estimateTokens, evaluateSelection, expandCommand, expandImports, explainSetting, extractArchive, findInstructionFiles, findProjectInstructions, findRole, formatBytes, formatSkillIndex, formatUsd, gateForTool, getCapability, getConfigIssues, getConfigOrigins, getImportGate, getTrustGate, hasDedicatedExecContent, hasExecTierContent, hasPromptCaching, hasProviderCredentials, hasReadTierContent, imageFromBase64, imageFromPath, imagePartTokens, importParts, importSkills, installPlugin, instructionDirs, instructionNotices, intervalSlots, inventoryProjectExtensions, inventoryTotal, isBlockCode, isCodeModeDeferred, isEmptyRules, isGoalPhase, isNonInteractive, isReadOnlySubCall, isSettingWriteLayer, isTransientPersistFailure, isValidPluginName, isWithinWindow, isWritableSettingKey, labelSlug, layerRank, listRunningSchedules, listSessionCandidates, listWorkspaceFiles, loadCommandDefinitions, loadCommandFile, loadCommands, loadConfig, loadHookSources, loadKeybindings, loadLexiconDir, loadLexiconFiles, loadPlugins, loadPolicyFiles, loadPolicyRules, loadRoleDefinitions, loadRoleDir, loadedImports, localDateString, managedSwitchNotes, mapClaudeToolName, mapEpochToolName, mapOperationType, matchPreauthorization, matchRuleLists, matchesHook, mentionsPluginRoot, mergeHeadlessPolicy, mergeRoles, mergeSchedulePatch, microsToUsd, modelListCandidates, needsAllowlist, nextFallbackModel, nextRunAt, noConfigOrigins, noManagedPolicy, noPlugins, openArtifact, originOf, packFileName, packPlugin, parseClaudeSettingsHooks, parseClock, parseDate, parseFrontmatter, parseHooksConfig, parseHooksConfigVerbose, parseImportLine, parseModelList, parseRoleDefinition, parseScheduleDraft, parseSkillFrontmatter, parseTrigger, pendingImageTokens, persistFailureCode, persistParts, persistToolResults, persistedUsage, planPack, previewSkillImport, pricingFor, projectEntryForDisplay, pruneArtifacts, pruneAttachments, pruneRecordings, pruneSchedules, readImageSize, readMarketplaces, readPluginManifest, readPluginRecords, readProviderKeyEnv, readRecording, readSessionSurface, reclaimFtsSpace, redactLine, refreshOpenRouterMetadata, removeMarketplace, removeRecordings, removeRoleFile, removeSessionArtifacts, removeSessionAttachments, renderImportTree, renderProjectContext, renderSdkDeclaration, renderSelfIndex, repairSchedules, resolveArtifactRetention, resolveCodeModeWorker, resolveComplianceDirs, resolveEntrySource, resolveInWorkspace, resolveMarketplaceRef, resolveMentions, resolveMultimodalPolicy, resolvePolicyDirs, resolvePricingAt, resolveProjectRoot, resolveScheduleBackend, resolveSessionReference, resolveSessionVisibility, resolveSettings, resolveSkillDirs, resolveSource, resolveTar, resolveWorkspace, restoreParts, revealScopedProvider, roleScopedProvider, roleSourceLabel, ruleCovers, ruleFromString, ruleToString, runCode, runCommand2 as runCommand, sanitizeArgNames, sanitizeCommand, sanitizeFtsQuery, sanitizePath, sanitizeToolOutput, scanInstructions, scanPluginDir, scanSkillDirs, searchMarketplaces, setDisplayCurrency, setImportGate, setPluginEnabled, setTrustGate, settingValueChoices, settingValueKind, settingWriteApply, settingWriteLayers, settingWriteTargets, skillIndexDescription, skillIndexResidency, skillIndexStats, sniffMediaType, splitFrontmatter, staticToolProvider, stripArtifactData, substitutePluginRoot, suggestRuleFromApproval, systemOpenArgv, systemPromptSegments, toPosix, toolDefinitionTokens, toolMatches, toolTableTokens, totalTokens, traceSession, triggerPeriodMs, unescapeRuleContent, uninstallPlugin, unknownRoleSkills, unknownRoleTools, unloadedInstructionFiles, updateMarketplace, updatePlugin, usdToMicros, validateSchedule, validateTrigger, validateWindow, withGoalCompression, withGoalPrompt, workspaceDisplayPath, writeSettingValue };