@epoch-agent/core 0.14.0 → 0.16.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,9 +2,9 @@ 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, budgetStatePath, artifactsDir, UNLIMITED_TURNS, turnLimitOf, configPath, managedSettingsPath, getSecretValues, listStoredSecretNames, maskApiKey, setSectionField, countCodePoints, headCodePoints, isInWorkspace, workspacesPath, samePath, lastSessionsPath, automationLogsDir, projectComplianceDir, projectSkillsDir, attachmentsDir, 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, 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
- import { PROVIDER_TYPES, OPERATION_TYPES as OPERATION_TYPES$1, COMPLIANCE_CATEGORIES, COMPLIANCE_ACTIONS, SEARCH_PROVIDER_TYPES, CURRENCIES, LANGS, 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';
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';
9
9
  import { createRequire } from 'module';
10
10
  import { randomUUID, createHash, randomBytes } from 'crypto';
@@ -34,7 +34,8 @@ __export(schtasks_exports, {
34
34
  quoteTaskRun: () => quoteTaskRun,
35
35
  scheduleArgs: () => scheduleArgs,
36
36
  specFromTrigger: () => specFromTrigger,
37
- specFromXml: () => specFromXml
37
+ specFromXml: () => specFromXml,
38
+ taskRunArgv: () => taskRunArgv
38
39
  });
39
40
  function createSchtasksBackend(opts = {}) {
40
41
  const run = opts.run ?? runCommand2;
@@ -99,9 +100,11 @@ function createSchtasksBackend(opts = {}) {
99
100
  async inspect(osTaskId) {
100
101
  const res = await run("schtasks", ["/Query", "/TN", osTaskId, "/XML"]);
101
102
  if (res.code !== 0) return null;
103
+ const commandLine = commandLineFromXml(res.stdout);
102
104
  return {
103
105
  osTaskId,
104
- commandLine: commandLineFromXml(res.stdout),
106
+ commandLine,
107
+ argv: taskRunArgv(commandLine),
105
108
  spec: specFromXml(res.stdout),
106
109
  enabled: !/<Enabled>\s*false\s*<\/Enabled>/i.test(res.stdout)
107
110
  };
@@ -118,6 +121,13 @@ function createSchtasksBackend(opts = {}) {
118
121
  function quoteTaskRun(argv) {
119
122
  return argv.map((part) => `"${part.replace(/"/g, "")}"`).join(" ");
120
123
  }
124
+ function taskRunArgv(commandLine) {
125
+ const out = [];
126
+ const re = /"([^"]*)"|(\S+)/g;
127
+ let m;
128
+ while ((m = re.exec(commandLine)) !== null) out.push(m[1] ?? m[2] ?? "");
129
+ return out;
130
+ }
121
131
  function scheduleArgs(trigger) {
122
132
  if (trigger.kind === "interval") {
123
133
  const slots = intervalSlots(trigger.everyMinutes);
@@ -331,9 +341,11 @@ function createLaunchdBackend(opts = {}) {
331
341
  return null;
332
342
  }
333
343
  const disabled = await isDisabled(run, label);
344
+ const argv = extractProgramArguments(xml2);
334
345
  return {
335
346
  osTaskId: label,
336
- commandLine: extractProgramArguments(xml2).join(" "),
347
+ commandLine: argv.join(" "),
348
+ argv,
337
349
  spec: extractCalendarSpec(xml2),
338
350
  enabled: !disabled
339
351
  };
@@ -542,6 +554,7 @@ var INTERNAL_TOOLS = /* @__PURE__ */ new Set([
542
554
  "session_messages_search",
543
555
  "session_messages_read",
544
556
  "session_trace",
557
+ "session_list",
545
558
  "goal",
546
559
  "skill_view",
547
560
  "tool_search",
@@ -1117,6 +1130,7 @@ var BLOCKED_TOOLS = Object.freeze([
1117
1130
  "session_messages_search",
1118
1131
  "session_messages_read",
1119
1132
  "session_trace",
1133
+ "session_list",
1120
1134
  "goal"
1121
1135
  ]);
1122
1136
  var DelegateManager = class {
@@ -3438,11 +3452,13 @@ var SESSION_SEARCH_TOOL = "session_search";
3438
3452
  var SESSION_MESSAGES_SEARCH_TOOL = "session_messages_search";
3439
3453
  var SESSION_MESSAGES_READ_TOOL = "session_messages_read";
3440
3454
  var SESSION_TRACE_TOOL = "session_trace";
3455
+ var SESSION_LIST_TOOL = "session_list";
3441
3456
  var SESSION_QUERY_TOOLS = Object.freeze([
3442
3457
  SESSION_SEARCH_TOOL,
3443
3458
  SESSION_MESSAGES_SEARCH_TOOL,
3444
3459
  SESSION_MESSAGES_READ_TOOL,
3445
- SESSION_TRACE_TOOL
3460
+ SESSION_TRACE_TOOL,
3461
+ SESSION_LIST_TOOL
3446
3462
  ]);
3447
3463
  var MAX_SNIPPET = 160;
3448
3464
  var CANDIDATE_FACTOR = 4;
@@ -3536,6 +3552,36 @@ var SessionQueries = class {
3536
3552
  trace(input) {
3537
3553
  return traceSession(this.db, input);
3538
3554
  }
3555
+ listSessions(input) {
3556
+ const vis = resolveSessionVisibility({ ...input, db: this.db });
3557
+ const archivedClause = input.archived === "all" ? "" : input.archived === "only" ? "AND s.archived = 1" : "AND s.archived = 0";
3558
+ const where = `${vis.clause} ${archivedClause}`;
3559
+ const total = this.db.prepare(`SELECT count(*) AS n FROM sessions s WHERE ${where}`).get(vis.params).n;
3560
+ const rows = this.db.prepare(
3561
+ `SELECT s.id,
3562
+ COALESCE(NULLIF(s.display_name, ''), s.title) AS title,
3563
+ s.cwd,
3564
+ COALESCE(s.updated_at, s.started_at) AS updated_at,
3565
+ s.message_count,
3566
+ s.archived
3567
+ FROM sessions s
3568
+ WHERE ${where}
3569
+ ORDER BY COALESCE(s.updated_at, s.started_at) DESC
3570
+ LIMIT @limit`
3571
+ ).all({ ...vis.params, limit: input.limit });
3572
+ return {
3573
+ total,
3574
+ sessions: rows.map((row) => ({
3575
+ sessionId: row.id,
3576
+ title: row.title,
3577
+ cwd: row.cwd ?? void 0,
3578
+ updatedAt: row.updated_at,
3579
+ messageCount: row.message_count,
3580
+ archived: row.archived === 1,
3581
+ isCurrent: row.id === input.currentSessionId
3582
+ }))
3583
+ };
3584
+ }
3539
3585
  contentRows(safe, limit, visClause, visParams) {
3540
3586
  const sql = (table) => `
3541
3587
  SELECT m.id, m.session_id, m.role, m.content, m.timestamp, m.active,
@@ -3601,7 +3647,123 @@ var SessionQueries = class {
3601
3647
  return this.db.prepare(sql("messages_fts_trigram")).all({ query: buildTrigramMatchQuery(safe), sid: sessionId, limit });
3602
3648
  }
3603
3649
  };
3604
- // src/tools/builtin/session-read.ts
3650
+ // src/tools/builtin/session-list.ts
3651
+ var DEFAULT_LIMIT = 10;
3652
+ var MAX_LIMIT = 30;
3653
+ var MAX_TITLE = 80;
3654
+ function fail3(message, start, code, suggestion) {
3655
+ return {
3656
+ success: false,
3657
+ output: "",
3658
+ error: { code, message, ...{} },
3659
+ duration: Date.now() - start
3660
+ };
3661
+ }
3662
+ function clampLimit(raw) {
3663
+ const n = Number(raw);
3664
+ if (!Number.isFinite(n) || n <= 0) return DEFAULT_LIMIT;
3665
+ return Math.min(Math.floor(n), MAX_LIMIT);
3666
+ }
3667
+ function stamp(ms) {
3668
+ return new Date(ms).toISOString().slice(0, 16).replace("T", " ");
3669
+ }
3670
+ function archivedScopeOf(raw) {
3671
+ return raw === "only" || raw === "all" ? raw : "exclude";
3672
+ }
3673
+ function scopeNote(allWorkspaces, archived) {
3674
+ const where = allWorkspaces ? "all workspaces" : "current workspace + this session lineage";
3675
+ const which = archived === "only" ? "archived only" : archived === "all" ? "archived included" : "archived excluded";
3676
+ return `${where}, ${which}`;
3677
+ }
3678
+ function renderEntry(entry, index) {
3679
+ const self = entry.isCurrent ? " (this session)" : "";
3680
+ const title = entry.title.length > MAX_TITLE ? `${entry.title.slice(0, MAX_TITLE)}\u2026` : entry.title;
3681
+ const where = entry.cwd ? ` \xB7 ${entry.cwd}` : "";
3682
+ const archived = entry.archived ? " \xB7 archived" : "";
3683
+ return [
3684
+ `${index + 1}. ${entry.sessionId}${self} \u2014 ${title || "(untitled)"}`,
3685
+ ` ${stamp(entry.updatedAt)} \xB7 ${entry.messageCount} messages${where}${archived}`
3686
+ ].join("\n");
3687
+ }
3688
+ function render(out, note) {
3689
+ const shown = out.sessions.length;
3690
+ const head = shown < out.total ? `${out.total} session(s) (${note}), showing the ${shown} most recently active:` : `${out.total} session(s) (${note}):`;
3691
+ return [
3692
+ head,
3693
+ "",
3694
+ ...out.sessions.map(renderEntry),
3695
+ "",
3696
+ "Next: session_messages_search({ sessionId, query }) to look inside one, or session_messages_read({ sessionId, messageId }) once you have a hit."
3697
+ ].join("\n");
3698
+ }
3699
+ function createSessionListTool(deps) {
3700
+ return {
3701
+ name: SESSION_LIST_TOOL,
3702
+ description: "\u5217\u51FA\u4F60\u80FD\u8BFB\u5230\u7684\u5386\u53F2\u4F1A\u8BDD \u2014\u2014 \u68C0\u7D22\u90A3\u51E0\u4E2A\u90FD\u8981\u5148\u60F3\u51FA\u4E00\u4E2A\u8BCD\uFF0C\u8FD9\u4E2A\u4E0D\u8981\u3002\n\u7528\u5B83\u56DE\u7B54\u300C\u6211\u90FD\u6709\u54EA\u4E9B\u4F1A\u8BDD\u300D\u300C\u4E0A\u6B21\u90A3\u6BB5\u804A\u7684\u662F\u4EC0\u4E48\u300D\uFF0C\u4EE5\u53CA\u7528\u6237\u63D0\u8D77\u67D0\u6BB5\u8FC7\u53BB\u7684\u4F1A\u8BDD\u5374\u6CA1\u7ED9 id \u7684\u65F6\u5019\u3002\n\u6309\u6700\u540E\u6D3B\u52A8\u5012\u5E8F\uFF0C\u6BCF\u6BB5\u7ED9 sessionId + \u6807\u9898 + \u65F6\u95F4 + \u6D88\u606F\u6570\uFF1B\u62FF\u5230 id \u518D\u7528 session_messages_search \u8FDB\u53BB\u770B\u3002\n\u9ED8\u8BA4\u4E0D\u5217\u5F52\u6863\u7684\u4F1A\u8BDD\uFF08\u5F52\u6863\u662F\u300C\u7528\u6237\u628A\u5B83\u6536\u8D77\u6765\u4E86\u300D\uFF09\u2014\u2014 \u8981\u627E\u5F52\u6863\u7684\u5C31\u4F20 archived\u3002\n\u9ED8\u8BA4\u53EA\u5217\u5F53\u524D\u5DE5\u4F5C\u533A\u7684\u4F1A\u8BDD\uFF0C\u5916\u52A0\u672C\u4F1A\u8BDD fork \u51FA\u6765\u7684\u90A3\u6761\u8840\u7F18\u94FE\u3002",
3703
+ get parameters() {
3704
+ return {
3705
+ type: "object",
3706
+ properties: {
3707
+ limit: {
3708
+ type: "number",
3709
+ description: `\u56DE\u51E0\u6BB5\uFF0C\u9ED8\u8BA4 ${DEFAULT_LIMIT}\uFF0C\u4E0A\u9650 ${MAX_LIMIT}\uFF08\u4F20\u5927\u4E86\u81EA\u52A8\u5939\u7D27\uFF0C\u4E0D\u62A5\u9519\uFF09`
3710
+ },
3711
+ archived: {
3712
+ type: "string",
3713
+ enum: ["exclude", "only", "all"],
3714
+ description: "exclude \u4E0D\u5217\u5F52\u6863\u7684\uFF08\u9ED8\u8BA4\uFF09\uFF1Bonly \u53EA\u5217\u5F52\u6863\u7684\uFF1Ball \u4E24\u8FB9\u90FD\u5217\u3002\u7528\u6237\u63D0\u8D77\u4E00\u6BB5\u300C\u4EE5\u524D\u5F52\u6863\u6389\u7684\u4F1A\u8BDD\u300D\u65F6\u7528 only"
3715
+ },
3716
+ ...deps.allowAllWorkspaces ? {
3717
+ allWorkspaces: {
3718
+ type: "boolean",
3719
+ description: "\u5217\u5168\u90E8\u5DE5\u4F5C\u533A\u7684\u4F1A\u8BDD\uFF08\u9ED8\u8BA4 false\uFF0C\u53EA\u5217\u5F53\u524D\u5DE5\u4F5C\u533A\uFF09"
3720
+ }
3721
+ } : {}
3722
+ },
3723
+ required: []
3724
+ };
3725
+ },
3726
+ exposure: ToolExposure.Direct,
3727
+ supportsParallel: true,
3728
+ annotations: {
3729
+ readOnlyHint: true,
3730
+ destructiveHint: false,
3731
+ idempotentHint: true,
3732
+ openWorldHint: true
3733
+ },
3734
+ operation: "file_read",
3735
+ describeTarget: (args) => `${SESSION_LIST_TOOL}:${archivedScopeOf(args["archived"])}`,
3736
+ execute(args, ctx) {
3737
+ const start = Date.now();
3738
+ const allWorkspaces = deps.allowAllWorkspaces === true && args["allWorkspaces"] === true;
3739
+ const archived = archivedScopeOf(args["archived"]);
3740
+ try {
3741
+ const out = deps.queries.listSessions({
3742
+ limit: clampLimit(args["limit"]),
3743
+ archived,
3744
+ currentSessionId: ctx.sessionId,
3745
+ workspaceCwd: deps.workspaceCwd,
3746
+ allWorkspaces
3747
+ });
3748
+ if (out.sessions.length === 0) {
3749
+ const hint = archived === "exclude" ? ' If the user is asking about a session they archived, retry with archived: "only".' : "";
3750
+ return Promise.resolve({
3751
+ success: true,
3752
+ output: `No session in readable scope (${scopeNote(allWorkspaces, archived)}).${hint}`,
3753
+ duration: Date.now() - start
3754
+ });
3755
+ }
3756
+ return Promise.resolve({
3757
+ success: true,
3758
+ output: render(out, scopeNote(allWorkspaces, archived)),
3759
+ duration: Date.now() - start
3760
+ });
3761
+ } catch (err) {
3762
+ return Promise.resolve(fail3(err instanceof Error ? err.message : String(err), start, 8014));
3763
+ }
3764
+ }
3765
+ };
3766
+ }
3605
3767
  var DEFAULT_WINDOW = 2;
3606
3768
  var MAX_WINDOW = 10;
3607
3769
  var DEFAULT_TRACE_LIMIT = 20;
@@ -3609,7 +3771,7 @@ var MAX_TRACE_LIMIT = 50;
3609
3771
  var BODY_BUDGET = 1500;
3610
3772
  var MIN_BODY = 60;
3611
3773
  var ANCHOR_SHARE = 4;
3612
- function fail3(message, start, code, suggestion) {
3774
+ function fail4(message, start, code, suggestion) {
3613
3775
  return {
3614
3776
  success: false,
3615
3777
  output: "",
@@ -3623,7 +3785,7 @@ function clampWindow(raw, fallback2, max) {
3623
3785
  if (!Number.isFinite(n) || n < 0) return fallback2;
3624
3786
  return Math.min(Math.floor(n), max);
3625
3787
  }
3626
- function stamp(ms) {
3788
+ function stamp2(ms) {
3627
3789
  return new Date(ms).toISOString().slice(0, 16).replace("T", " ");
3628
3790
  }
3629
3791
  function budgetOf(total) {
@@ -3645,7 +3807,7 @@ function surfaceOf(record, isCurrent) {
3645
3807
  function renderRecord(record, mark, budget, isCurrent) {
3646
3808
  const tool = record.toolName ? ` \xB7 ${record.toolName}` : "";
3647
3809
  return [
3648
- `${mark} #${record.messageId} [${record.role}${tool} \xB7 ${stamp(record.timestamp)} \xB7 ${surfaceOf(record, isCurrent)}]`,
3810
+ `${mark} #${record.messageId} [${record.role}${tool} \xB7 ${stamp2(record.timestamp)} \xB7 ${surfaceOf(record, isCurrent)}]`,
3649
3811
  ` ${body(record.content, budget).split("\n").join("\n ")}`
3650
3812
  ].join("\n");
3651
3813
  }
@@ -3711,11 +3873,11 @@ before / after \u5404\u9ED8\u8BA4 ${DEFAULT_WINDOW}\u3001\u4E0A\u9650 ${MAX_WIND
3711
3873
  execute(args, ctx) {
3712
3874
  const start = Date.now();
3713
3875
  const sessionId = String(args["sessionId"] ?? "").trim();
3714
- if (!sessionId) return Promise.resolve(fail3("sessionId must not be empty", start, 8007));
3876
+ if (!sessionId) return Promise.resolve(fail4("sessionId must not be empty", start, 8007));
3715
3877
  const messageId = Number(args["messageId"]);
3716
3878
  if (!Number.isInteger(messageId) || messageId <= 0) {
3717
3879
  return Promise.resolve(
3718
- fail3(
3880
+ fail4(
3719
3881
  "messageId must be a positive integer",
3720
3882
  start,
3721
3883
  8008,
@@ -3736,7 +3898,7 @@ before / after \u5404\u9ED8\u8BA4 ${DEFAULT_WINDOW}\u3001\u4E0A\u9650 ${MAX_WIND
3736
3898
  });
3737
3899
  if (!out.authorized) {
3738
3900
  return Promise.resolve(
3739
- fail3(
3901
+ fail4(
3740
3902
  `session ${sessionId} is out of readable scope`,
3741
3903
  start,
3742
3904
  8009,
@@ -3746,7 +3908,7 @@ before / after \u5404\u9ED8\u8BA4 ${DEFAULT_WINDOW}\u3001\u4E0A\u9650 ${MAX_WIND
3746
3908
  }
3747
3909
  if (!out.anchor) {
3748
3910
  return Promise.resolve(
3749
- fail3(
3911
+ fail4(
3750
3912
  `session ${sessionId} has no message #${messageId} (it has ${out.total})`,
3751
3913
  start,
3752
3914
  8010,
@@ -3760,7 +3922,7 @@ before / after \u5404\u9ED8\u8BA4 ${DEFAULT_WINDOW}\u3001\u4E0A\u9650 ${MAX_WIND
3760
3922
  duration: Date.now() - start
3761
3923
  });
3762
3924
  } catch (err) {
3763
- return Promise.resolve(fail3(err instanceof Error ? err.message : String(err), start, 8011));
3925
+ return Promise.resolve(fail4(err instanceof Error ? err.message : String(err), start, 8011));
3764
3926
  }
3765
3927
  }
3766
3928
  };
@@ -3774,7 +3936,7 @@ function renderNode(node, isCurrent) {
3774
3936
  const where = node.cwd ? ` \xB7 ${node.cwd}` : "";
3775
3937
  const archived = node.archived ? " \xB7 archived" : "";
3776
3938
  return `${indent}${node.sessionId}${self} \u2014 ${node.title || "(untitled)"}
3777
- ${indent} ${stamp(node.startedAt ?? 0)} \xB7 ${node.messageCount ?? 0} messages${where}${archived}`;
3939
+ ${indent} ${stamp2(node.startedAt ?? 0)} \xB7 ${node.messageCount ?? 0} messages${where}${archived}`;
3778
3940
  }
3779
3941
  function renderTrace(trace, currentSessionId) {
3780
3942
  const anchor = trace.anchor;
@@ -3853,7 +4015,7 @@ function createSessionTraceTool(deps) {
3853
4015
  });
3854
4016
  if (!trace.authorized) {
3855
4017
  return Promise.resolve(
3856
- fail3(
4018
+ fail4(
3857
4019
  `session ${sessionId} is out of readable scope`,
3858
4020
  start,
3859
4021
  8012,
@@ -3867,14 +4029,14 @@ function createSessionTraceTool(deps) {
3867
4029
  duration: Date.now() - start
3868
4030
  });
3869
4031
  } catch (err) {
3870
- return Promise.resolve(fail3(err instanceof Error ? err.message : String(err), start, 8013));
4032
+ return Promise.resolve(fail4(err instanceof Error ? err.message : String(err), start, 8013));
3871
4033
  }
3872
4034
  }
3873
4035
  };
3874
4036
  }
3875
- var DEFAULT_LIMIT = 5;
3876
- var MAX_LIMIT = 20;
3877
- function fail4(message, start, code, suggestion) {
4037
+ var DEFAULT_LIMIT2 = 5;
4038
+ var MAX_LIMIT2 = 20;
4039
+ function fail5(message, start, code, suggestion) {
3878
4040
  return {
3879
4041
  success: false,
3880
4042
  output: "",
@@ -3885,12 +4047,12 @@ function fail4(message, start, code, suggestion) {
3885
4047
  function emptyHint(query) {
3886
4048
  return belowTrigramFloor(sanitizeFtsQuery(query)) ? ` Note: every word here is shorter than ${TRIGRAM_MIN_CHARS} characters, which the substring index cannot match at all \u2014 this is "cannot search", not "not found". Retry with a longer phrase.` : " Try fewer or different words.";
3887
4049
  }
3888
- function clampLimit(raw) {
4050
+ function clampLimit2(raw) {
3889
4051
  const n = Number(raw);
3890
- if (!Number.isFinite(n) || n <= 0) return DEFAULT_LIMIT;
3891
- return Math.min(Math.floor(n), MAX_LIMIT);
4052
+ if (!Number.isFinite(n) || n <= 0) return DEFAULT_LIMIT2;
4053
+ return Math.min(Math.floor(n), MAX_LIMIT2);
3892
4054
  }
3893
- function stamp2(ms) {
4055
+ function stamp3(ms) {
3894
4056
  return new Date(ms).toISOString().slice(0, 16).replace("T", " ");
3895
4057
  }
3896
4058
  function surfaceNote(hit, isCurrent) {
@@ -3904,14 +4066,14 @@ function renderSessionHit(hit, index) {
3904
4066
  const self = hit.isCurrent ? " (this session)" : "";
3905
4067
  return [
3906
4068
  `${index + 1}. ${hit.sessionId}${self} \u2014 ${hit.title || "(untitled)"}`,
3907
- ` ${stamp2(hit.startedAt)} \xB7 ${hit.messageCount} messages${where}`,
4069
+ ` ${stamp3(hit.startedAt)} \xB7 ${hit.messageCount} messages${where}`,
3908
4070
  ` best match [${hit.best.role} #${hit.best.messageId}, ${surfaceNote(hit.best, hit.isCurrent)}]:`,
3909
4071
  ` ${hit.best.snippet}`
3910
4072
  ].join("\n");
3911
4073
  }
3912
4074
  function renderMessageHit(hit, isCurrent) {
3913
4075
  return [
3914
- `#${hit.messageId} [${hit.role} \xB7 ${stamp2(hit.timestamp)} \xB7 ${surfaceNote(hit, isCurrent)}]`,
4076
+ `#${hit.messageId} [${hit.role} \xB7 ${stamp3(hit.timestamp)} \xB7 ${surfaceNote(hit, isCurrent)}]`,
3915
4077
  ` ${hit.snippet}`
3916
4078
  ].join("\n");
3917
4079
  }
@@ -3929,7 +4091,7 @@ function createSessionSearchTool(deps) {
3929
4091
  },
3930
4092
  limit: {
3931
4093
  type: "number",
3932
- description: `\u8FD4\u56DE\u51E0\u6BB5\u4F1A\u8BDD\uFF0C\u9ED8\u8BA4 ${DEFAULT_LIMIT}\uFF0C\u4E0A\u9650 ${MAX_LIMIT}`
4094
+ description: `\u8FD4\u56DE\u51E0\u6BB5\u4F1A\u8BDD\uFF0C\u9ED8\u8BA4 ${DEFAULT_LIMIT2}\uFF0C\u4E0A\u9650 ${MAX_LIMIT2}`
3933
4095
  },
3934
4096
  scope: {
3935
4097
  type: "string",
@@ -3959,13 +4121,13 @@ function createSessionSearchTool(deps) {
3959
4121
  execute(args, ctx) {
3960
4122
  const start = Date.now();
3961
4123
  const query = String(args["query"] ?? "").trim();
3962
- if (!query) return Promise.resolve(fail4("query must not be empty", start, 8001));
4124
+ if (!query) return Promise.resolve(fail5("query must not be empty", start, 8001));
3963
4125
  const allWorkspaces = deps.allowAllWorkspaces === true && args["allWorkspaces"] === true;
3964
4126
  const scope = args["scope"] === "title" ? "title" : "all";
3965
4127
  try {
3966
4128
  const hits = deps.queries.searchSessions({
3967
4129
  query,
3968
- limit: clampLimit(args["limit"]),
4130
+ limit: clampLimit2(args["limit"]),
3969
4131
  scope,
3970
4132
  currentSessionId: ctx.sessionId,
3971
4133
  workspaceCwd: deps.workspaceCwd,
@@ -3992,7 +4154,7 @@ function createSessionSearchTool(deps) {
3992
4154
  });
3993
4155
  } catch (err) {
3994
4156
  return Promise.resolve(
3995
- fail4(
4157
+ fail5(
3996
4158
  err instanceof Error ? err.message : String(err),
3997
4159
  start,
3998
4160
  8002,
@@ -4021,7 +4183,7 @@ function createSessionMessagesSearchTool(deps) {
4021
4183
  },
4022
4184
  limit: {
4023
4185
  type: "number",
4024
- description: `\u8FD4\u56DE\u51E0\u6761\uFF0C\u9ED8\u8BA4 ${DEFAULT_LIMIT}\uFF0C\u4E0A\u9650 ${MAX_LIMIT}`
4186
+ description: `\u8FD4\u56DE\u51E0\u6761\uFF0C\u9ED8\u8BA4 ${DEFAULT_LIMIT2}\uFF0C\u4E0A\u9650 ${MAX_LIMIT2}`
4025
4187
  },
4026
4188
  ...deps.allowAllWorkspaces ? {
4027
4189
  allWorkspaces: {
@@ -4047,21 +4209,21 @@ function createSessionMessagesSearchTool(deps) {
4047
4209
  const start = Date.now();
4048
4210
  const sessionId = String(args["sessionId"] ?? "").trim();
4049
4211
  const query = String(args["query"] ?? "").trim();
4050
- if (!sessionId) return Promise.resolve(fail4("sessionId must not be empty", start, 8003));
4051
- if (!query) return Promise.resolve(fail4("query must not be empty", start, 8004));
4212
+ if (!sessionId) return Promise.resolve(fail5("sessionId must not be empty", start, 8003));
4213
+ if (!query) return Promise.resolve(fail5("query must not be empty", start, 8004));
4052
4214
  const allWorkspaces = deps.allowAllWorkspaces === true && args["allWorkspaces"] === true;
4053
4215
  try {
4054
4216
  const out = deps.queries.searchMessages({
4055
4217
  sessionId,
4056
4218
  query,
4057
- limit: clampLimit(args["limit"]),
4219
+ limit: clampLimit2(args["limit"]),
4058
4220
  currentSessionId: ctx.sessionId,
4059
4221
  workspaceCwd: deps.workspaceCwd,
4060
4222
  allWorkspaces
4061
4223
  });
4062
4224
  if (!out.authorized) {
4063
4225
  return Promise.resolve(
4064
- fail4(
4226
+ fail5(
4065
4227
  `session ${sessionId} is out of searchable scope`,
4066
4228
  start,
4067
4229
  8005,
@@ -4088,7 +4250,7 @@ function createSessionMessagesSearchTool(deps) {
4088
4250
  });
4089
4251
  } catch (err) {
4090
4252
  return Promise.resolve(
4091
- fail4(
4253
+ fail5(
4092
4254
  err instanceof Error ? err.message : String(err),
4093
4255
  start,
4094
4256
  8006,
@@ -4104,12 +4266,13 @@ function createSessionSearchTools(deps) {
4104
4266
  createSessionSearchTool(deps),
4105
4267
  createSessionMessagesSearchTool(deps),
4106
4268
  createSessionMessagesReadTool(deps),
4107
- createSessionTraceTool(deps)
4269
+ createSessionTraceTool(deps),
4270
+ createSessionListTool(deps)
4108
4271
  ];
4109
4272
  }
4110
4273
  var SKILL_VIEW_TOOL = "skill_view";
4111
4274
  var MAX_SUGGESTED = 20;
4112
- function fail5(message, start, code, suggestion) {
4275
+ function fail6(message, start, code, suggestion) {
4113
4276
  return {
4114
4277
  success: false,
4115
4278
  output: "",
@@ -4154,18 +4317,18 @@ function createSkillViewTool(deps) {
4154
4317
  const name = String(args["name"] ?? "").trim();
4155
4318
  if (!name) {
4156
4319
  return Promise.resolve(
4157
- fail5("name must not be empty", start, 3001, describeAvailableSkills(deps.skills))
4320
+ fail6("name must not be empty", start, 3001, describeAvailableSkills(deps.skills))
4158
4321
  );
4159
4322
  }
4160
4323
  const body2 = deps.skills.view(name);
4161
4324
  if (body2 === null) {
4162
4325
  return Promise.resolve(
4163
- fail5(`skill "${name}" not found`, start, 3002, describeAvailableSkills(deps.skills))
4326
+ fail6(`skill "${name}" not found`, start, 3002, describeAvailableSkills(deps.skills))
4164
4327
  );
4165
4328
  }
4166
4329
  if (body2.trim() === "") {
4167
4330
  return Promise.resolve(
4168
- fail5(
4331
+ fail6(
4169
4332
  `skill "${name}" has an empty body \u2014 its SKILL.md is missing or empty`,
4170
4333
  start,
4171
4334
  3003,
@@ -4292,7 +4455,7 @@ var SKILL_EDIT_TOOL = "skill_edit";
4292
4455
  var SKILL_PATCH_TOOL = "skill_patch";
4293
4456
  var SKILL_WRITE_FILE_TOOL = "skill_write_file";
4294
4457
  var WRITABLE_SKILL_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
4295
- function fail6(message, start, code, suggestion) {
4458
+ function fail7(message, start, code, suggestion) {
4296
4459
  return {
4297
4460
  success: false,
4298
4461
  output: "",
@@ -4304,7 +4467,7 @@ function done(output, start) {
4304
4467
  return { success: true, output, duration: Date.now() - start };
4305
4468
  }
4306
4469
  function fromThrow(err, start, suggestion) {
4307
- return fail6(err instanceof Error ? err.message : String(err), start, 3102, suggestion);
4470
+ return fail7(err instanceof Error ? err.message : String(err), start, 3102, suggestion);
4308
4471
  }
4309
4472
  function str2(args, key) {
4310
4473
  const raw = args[key];
@@ -4360,13 +4523,13 @@ function createTool(deps) {
4360
4523
  const content = str2(args, "content");
4361
4524
  const category = str2(args, "category").trim() || "general";
4362
4525
  if (!WRITABLE_SKILL_NAME.test(name)) {
4363
- return fail6(`invalid skill name: ${JSON.stringify(name)}`, start, 3101, WRITABLE_HINT);
4526
+ return fail7(`invalid skill name: ${JSON.stringify(name)}`, start, 3101, WRITABLE_HINT);
4364
4527
  }
4365
4528
  if (!WRITABLE_SKILL_NAME.test(category)) {
4366
- return fail6(`invalid category: ${JSON.stringify(category)}`, start, 3101, WRITABLE_HINT);
4529
+ return fail7(`invalid category: ${JSON.stringify(category)}`, start, 3101, WRITABLE_HINT);
4367
4530
  }
4368
4531
  const bad2 = checkFrontmatter(name, content);
4369
- if (bad2 !== null) return fail6(bad2, start, 3103);
4532
+ if (bad2 !== null) return fail7(bad2, start, 3103);
4370
4533
  try {
4371
4534
  const path = await deps.skills.create(name, content, category);
4372
4535
  return done(`created skill "${name}" at ${path}`, start);
@@ -4398,10 +4561,10 @@ function editTool(deps) {
4398
4561
  const name = str2(args, "name").trim();
4399
4562
  const content = str2(args, "content");
4400
4563
  if (!name) {
4401
- return fail6("name must not be empty", start, 3101, describeAvailableSkills(deps.skills));
4564
+ return fail7("name must not be empty", start, 3101, describeAvailableSkills(deps.skills));
4402
4565
  }
4403
4566
  const bad2 = checkFrontmatter(name, content);
4404
- if (bad2 !== null) return fail6(bad2, start, 3103);
4567
+ if (bad2 !== null) return fail7(bad2, start, 3103);
4405
4568
  try {
4406
4569
  await deps.skills.edit(name, content);
4407
4570
  return done(`rewrote SKILL.md of skill "${name}"`, start);
@@ -4439,10 +4602,10 @@ function patchTool(deps) {
4439
4602
  const oldString = str2(args, "old_string");
4440
4603
  const filePath = str2(args, "file_path").trim();
4441
4604
  if (!name) {
4442
- return fail6("name must not be empty", start, 3101, describeAvailableSkills(deps.skills));
4605
+ return fail7("name must not be empty", start, 3101, describeAvailableSkills(deps.skills));
4443
4606
  }
4444
4607
  if (oldString === "") {
4445
- return fail6("old_string must not be empty", start, 3101);
4608
+ return fail7("old_string must not be empty", start, 3101);
4446
4609
  }
4447
4610
  try {
4448
4611
  await deps.skills.patch(
@@ -4484,9 +4647,9 @@ function writeFileTool(deps) {
4484
4647
  const name = str2(args, "name").trim();
4485
4648
  const filePath = str2(args, "file_path").trim();
4486
4649
  if (!name) {
4487
- return fail6("name must not be empty", start, 3101, describeAvailableSkills(deps.skills));
4650
+ return fail7("name must not be empty", start, 3101, describeAvailableSkills(deps.skills));
4488
4651
  }
4489
- if (!filePath) return fail6("file_path must not be empty", start, 3101);
4652
+ if (!filePath) return fail7("file_path must not be empty", start, 3101);
4490
4653
  try {
4491
4654
  await deps.skills.writeFile(name, filePath, str2(args, "content"));
4492
4655
  return done(`wrote ${filePath} into skill "${name}"`, start);
@@ -4502,7 +4665,7 @@ function createSkillWriteTools(deps) {
4502
4665
  var TOOL_SEARCH_TOOL = "tool_search";
4503
4666
  var MAX_LISTED = 40;
4504
4667
  var MAX_REVEAL_PER_CALL = 10;
4505
- function fail7(message, start, code, suggestion) {
4668
+ function fail8(message, start, code, suggestion) {
4506
4669
  return {
4507
4670
  success: false,
4508
4671
  output: "",
@@ -4552,12 +4715,12 @@ function createToolSearchTool(deps) {
4552
4715
  const pool = deps.deferred();
4553
4716
  const raw = String(args["query"] ?? "").trim();
4554
4717
  if (!raw) {
4555
- return Promise.resolve(fail7("query must not be empty", start, 3101, available(pool)));
4718
+ return Promise.resolve(fail8("query must not be empty", start, 3101, available(pool)));
4556
4719
  }
4557
4720
  const terms = raw.toLowerCase().split(/\s+/).filter(Boolean);
4558
4721
  const hits = pool.filter((tool) => matches(tool, terms));
4559
4722
  if (hits.length === 0) {
4560
- return Promise.resolve(fail7(`No tool matches "${raw}"`, start, 3102, available(pool)));
4723
+ return Promise.resolve(fail8(`No tool matches "${raw}"`, start, 3102, available(pool)));
4561
4724
  }
4562
4725
  const picked = hits.slice(0, MAX_REVEAL_PER_CALL);
4563
4726
  for (const tool of picked) deps.reveal.reveal(tool.name);
@@ -5113,7 +5276,7 @@ function createRoleFile(input, opts) {
5113
5276
  const detail = checked.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
5114
5277
  return { ok: false, reason: "invalid-field", detail };
5115
5278
  }
5116
- const text = render(fm, input.prompt ?? "");
5279
+ const text = render2(fm, input.prompt ?? "");
5117
5280
  const file = `${name}.md`;
5118
5281
  const back = parseRoleDefinition(text, file, opts.source ?? "user");
5119
5282
  if (!back.role) {
@@ -5145,7 +5308,7 @@ function frontmatterOf(name, input) {
5145
5308
  ...input.maxTurns === void 0 ? {} : { maxTurns: input.maxTurns }
5146
5309
  };
5147
5310
  }
5148
- function render(fm, prompt) {
5311
+ function render2(fm, prompt) {
5149
5312
  const head = dump(fm, { lineWidth: -1, noRefs: true });
5150
5313
  const body2 = prompt.trim();
5151
5314
  return `---
@@ -7017,11 +7180,29 @@ var MARKETPLACE_DISPLAY_FIELDS = [
7017
7180
  var DisplaySchema = z.object({
7018
7181
  fields: z.array(z.string().min(1)).optional()
7019
7182
  });
7183
+ var CategoryNodeSchema = z.lazy(
7184
+ () => z.object({
7185
+ id: z.string().min(1),
7186
+ label: z.string().min(1).optional(),
7187
+ children: z.array(CategoryNodeSchema).optional()
7188
+ })
7189
+ );
7190
+ var CategoriesSchema = z.array(CategoryNodeSchema).refine(hasUniqueCategoryIds, { message: "category ids must be unique across the whole tree" }).optional().catch(void 0);
7191
+ function hasUniqueCategoryIds(nodes) {
7192
+ const seen = /* @__PURE__ */ new Set();
7193
+ const walk3 = (list) => list.every((node) => {
7194
+ if (seen.has(node.id)) return false;
7195
+ seen.add(node.id);
7196
+ return node.children === void 0 || walk3(node.children);
7197
+ });
7198
+ return walk3(nodes);
7199
+ }
7020
7200
  var CatalogSchema = z.object({
7021
7201
  name: z.string().regex(NAME_PATTERN),
7022
7202
  description: z.string().min(1).optional(),
7023
7203
  owner: z.object({ name: z.string().min(1), url: z.string().min(1).optional() }).optional(),
7024
7204
  display: DisplaySchema.optional(),
7205
+ categories: CategoriesSchema,
7025
7206
  plugins: z.array(EntrySchema)
7026
7207
  });
7027
7208
  var RecordSchema2 = z.object({
@@ -7037,13 +7218,17 @@ var StateSchema2 = z.object({
7037
7218
  function projectEntryForDisplay(entry, display) {
7038
7219
  const allowed = display?.fields;
7039
7220
  if (allowed === void 0) return entry;
7040
- const keep = new Set(allowed);
7041
7221
  const out = { ...entry };
7042
7222
  for (const field of MARKETPLACE_DISPLAY_FIELDS) {
7043
- if (!keep.has(field)) delete out[field];
7223
+ if (!displayAllows(display, field)) delete out[field];
7044
7224
  }
7045
7225
  return out;
7046
7226
  }
7227
+ function displayAllows(display, field) {
7228
+ const allowed = display?.fields;
7229
+ if (allowed === void 0) return true;
7230
+ return allowed.includes(field);
7231
+ }
7047
7232
  function readMarketplaces(statePath) {
7048
7233
  if (!existsSync(statePath)) return { records: [], issues: [] };
7049
7234
  let raw;
@@ -9264,7 +9449,18 @@ function gitProbeEnv() {
9264
9449
  delete env.GIT_WORK_TREE;
9265
9450
  return env;
9266
9451
  }
9452
+ function insideGitRepo(rootDir) {
9453
+ let dir = rootDir;
9454
+ for (; ; ) {
9455
+ if (existsSync(join(dir, ".git"))) return true;
9456
+ const parent = dirname(dir);
9457
+ if (parent === dir) return false;
9458
+ dir = parent;
9459
+ }
9460
+ }
9267
9461
  function getGitStatus(rootDir) {
9462
+ if (!insideGitRepo(rootDir)) return void 0;
9463
+ if (!canProbeGit()) return void 0;
9268
9464
  const quiet = ["ignore", "pipe", "ignore"];
9269
9465
  const env = gitProbeEnv();
9270
9466
  try {
@@ -12237,8 +12433,48 @@ var EpochConfigSchema = z.object({
12237
12433
  message: "blankCards: id must be unique"
12238
12434
  }).optional(),
12239
12435
  blankGreeting: BlankGreetingSchema.optional(),
12240
- user: HostUserSchema.optional()
12436
+ user: HostUserSchema.optional(),
12437
+ settingsHidden: z.array(z.string().min(1)).min(1).optional(),
12438
+ settingsPanes: z.array(
12439
+ z.object({
12440
+ id: z.string().min(1).refine((id) => !isSettingsPaneId(id), {
12441
+ message: `id must not collide with a built-in pane (${SETTINGS_PANE_IDS.join(", ")})`
12442
+ }),
12443
+ label: z.string().min(1),
12444
+ icon: z.string().min(1).optional(),
12445
+ url: z.string().min(1).refine(
12446
+ (url) => {
12447
+ try {
12448
+ const scheme = new URL(url).protocol;
12449
+ return scheme === "http:" || scheme === "https:";
12450
+ } catch {
12451
+ return false;
12452
+ }
12453
+ },
12454
+ { message: "url must be an absolute http(s) URL" }
12455
+ ),
12456
+ before: z.string().min(1).optional()
12457
+ })
12458
+ ).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
12459
+ message: "settingsPanes: id must be unique"
12460
+ }).optional()
12241
12461
  });
12462
+ function settingsHiddenIssues(config) {
12463
+ const hidden = config.settingsHidden;
12464
+ if (hidden === void 0) return [];
12465
+ return hidden.filter((id) => !isSettingsPaneId(id)).map((id) => ({
12466
+ path: "settingsHidden",
12467
+ message: `unknown settings pane id: ${id} (known: ${SETTINGS_PANE_IDS.join(", ")})`
12468
+ }));
12469
+ }
12470
+ function settingsPaneBeforeIssues(config) {
12471
+ const panes = config.settingsPanes;
12472
+ if (panes === void 0) return [];
12473
+ return panes.filter((pane) => pane.before !== void 0 && !isSettingsPaneId(pane.before)).map((pane) => ({
12474
+ path: `settingsPanes.${pane.id}.before`,
12475
+ message: `unknown settings pane id: ${String(pane.before)} (known: ${SETTINGS_PANE_IDS.join(", ")})`
12476
+ }));
12477
+ }
12242
12478
  function defaultConfig() {
12243
12479
  return EpochConfigSchema.parse({});
12244
12480
  }
@@ -12709,6 +12945,8 @@ function parseRawConfig(raw, issues, track) {
12709
12945
  EpochConfigSchema.parse({})
12710
12946
  );
12711
12947
  issues.push(...outcome.issues);
12948
+ issues.push(...settingsHiddenIssues(outcome.value));
12949
+ issues.push(...settingsPaneBeforeIssues(outcome.value));
12712
12950
  if (outcome.issues.length === 0) collectKeyPaths(migrated.raw, track.file);
12713
12951
  return outcome.value;
12714
12952
  }
@@ -14280,6 +14518,7 @@ var SETTING_WRITE_KEYS = [
14280
14518
  "sessions.vacuumAfterPrune",
14281
14519
  "automation.keepRuns",
14282
14520
  "automation.maxRecordingMb",
14521
+ "sessionSearch.allWorkspaces",
14283
14522
  "permission"
14284
14523
  ];
14285
14524
  function isWritableSettingKey(value) {
@@ -14349,6 +14588,7 @@ var VALUE_SCHEMA = {
14349
14588
  "sessions.vacuumAfterPrune": EpochConfigSchema.shape.sessions.unwrap().shape.vacuumAfterPrune.unwrap(),
14350
14589
  "automation.keepRuns": EpochConfigSchema.shape.automation.unwrap().shape.keepRuns.unwrap(),
14351
14590
  "automation.maxRecordingMb": EpochConfigSchema.shape.automation.unwrap().shape.maxRecordingMb.unwrap(),
14591
+ "sessionSearch.allWorkspaces": EpochConfigSchema.shape.sessionSearch.unwrap().shape.allWorkspaces.unwrap(),
14352
14592
  permission: EpochConfigSchema.shape.permission.unwrap()
14353
14593
  };
14354
14594
  function settingValueKind(key) {
@@ -15527,6 +15767,17 @@ function textSimilarity(a, b) {
15527
15767
  const union = /* @__PURE__ */ new Set([...wordsA, ...wordsB]);
15528
15768
  return intersection / union.size;
15529
15769
  }
15770
+ var ATTACHMENT_RETENTION_DEFAULTS = {
15771
+ maxAgeDays: 7,
15772
+ maxTotalBytes: ATTACHMENT_MAX_TOTAL_BYTES
15773
+ };
15774
+ function pruneAttachments(root, keepSession, retention = ATTACHMENT_RETENTION_DEFAULTS, now = Date.now()) {
15775
+ const shared = retention;
15776
+ return pruneArtifacts(root, keepSession, shared, now);
15777
+ }
15778
+ async function removeSessionAttachments(sessionId, homeDir) {
15779
+ await rm(attachmentsDir(sessionId, homeDir), { recursive: true, force: true });
15780
+ }
15530
15781
  // src/goal/store.ts
15531
15782
  function toGoal(row) {
15532
15783
  const phase = isGoalPhase(row.phase) ? row.phase : "blocked";
@@ -15680,6 +15931,52 @@ function restoreParts(parts, opts) {
15680
15931
  }
15681
15932
  });
15682
15933
  }
15934
+ function importParts(parts) {
15935
+ const out = [];
15936
+ for (const part of parts) {
15937
+ if (part.type === "text") {
15938
+ out.push({ type: "text", text: part.text });
15939
+ continue;
15940
+ }
15941
+ if (part.type === "file") {
15942
+ out.push({
15943
+ type: "file",
15944
+ path: part.path,
15945
+ ...part.omitted ? { omitted: part.omitted } : {},
15946
+ ...part.bytes === void 0 ? {} : { bytes: part.bytes }
15947
+ });
15948
+ continue;
15949
+ }
15950
+ if (part.type === "session") {
15951
+ out.push({
15952
+ type: "session",
15953
+ ref: part.ref,
15954
+ ...part.sessionId ? { sessionId: part.sessionId } : {},
15955
+ ...part.title ? { title: part.title } : {},
15956
+ ...part.omitted ? { omitted: part.omitted } : {},
15957
+ ...part.messageCount === void 0 ? {} : { messageCount: part.messageCount },
15958
+ ...part.totalMessages === void 0 ? {} : { totalMessages: part.totalMessages }
15959
+ });
15960
+ continue;
15961
+ }
15962
+ if (part.type !== "image") {
15963
+ assertNeverPart(part);
15964
+ continue;
15965
+ }
15966
+ const { image, mediaType } = part;
15967
+ const media = mediaType ? { mediaType } : {};
15968
+ if (/^https?:/i.test(image)) {
15969
+ out.push({ type: "image", url: image, ...media });
15970
+ continue;
15971
+ }
15972
+ if (isAbsolute(image)) {
15973
+ out.push({ type: "image", path: image, ...media });
15974
+ continue;
15975
+ }
15976
+ return { ok: false, detail: DATA_URL.test(image) ? "image:data-url" : "image:base64" };
15977
+ }
15978
+ return { ok: true, parts: out };
15979
+ }
15683
15980
  function stripArtifactData(artifacts) {
15684
15981
  return artifacts.map(({ data: _data, ...rest }) => rest);
15685
15982
  }
@@ -16178,13 +16475,11 @@ var FTS_EXTERNAL_MIGRATIONS = [
16178
16475
  var SessionDB = class {
16179
16476
  db;
16180
16477
  path;
16181
- msgId = 0;
16182
16478
  constructor(dbPath$1) {
16183
16479
  this.path = dbPath$1 ?? dbPath();
16184
16480
  this.db = openDatabase(this.path);
16185
16481
  migrate(this.db, NS3, SESSION_MIGRATIONS);
16186
16482
  this.migrateFtsIndex();
16187
- this.refreshMsgId();
16188
16483
  }
16189
16484
  migrateFtsIndex() {
16190
16485
  try {
@@ -16196,9 +16491,8 @@ var SessionDB = class {
16196
16491
  );
16197
16492
  }
16198
16493
  }
16199
- refreshMsgId() {
16200
- const row = this.db.prepare("SELECT MAX(id) as maxId FROM messages").get();
16201
- this.msgId = row?.maxId ?? 0;
16494
+ transaction(fn) {
16495
+ return this.db.transaction(fn)();
16202
16496
  }
16203
16497
  insertSession(meta) {
16204
16498
  this.db.prepare(
@@ -16252,7 +16546,7 @@ var SessionDB = class {
16252
16546
  const dir = input.direction ?? "desc";
16253
16547
  let where = "1=1";
16254
16548
  const params = {};
16255
- if (input.archived !== void 0) {
16549
+ if (input.archived === "any") ; else if (input.archived !== void 0) {
16256
16550
  where += " AND archived = @archived";
16257
16551
  params["archived"] = input.archived ? 1 : 0;
16258
16552
  } else {
@@ -16265,6 +16559,10 @@ var SessionDB = class {
16265
16559
  where += " AND cwd = @cwd";
16266
16560
  params["cwd"] = input.cwd;
16267
16561
  }
16562
+ if (input.updatedSince !== void 0) {
16563
+ where += " AND COALESCE(updated_at, started_at) > @updatedSince";
16564
+ params["updatedSince"] = input.updatedSince;
16565
+ }
16268
16566
  if (input.cursor) {
16269
16567
  where += ` AND ${cursorCol} < @cursor`;
16270
16568
  params["cursor"] = Number(input.cursor);
@@ -16308,11 +16606,20 @@ var SessionDB = class {
16308
16606
  unarchiveSession(id) {
16309
16607
  this.db.prepare("UPDATE sessions SET archived = 0 WHERE id = ?").run(id);
16310
16608
  }
16609
+ importSession(meta, messages) {
16610
+ const run = this.db.transaction(
16611
+ (m, list) => {
16612
+ this.insertSession(m);
16613
+ return list.map((msg) => this.insertMessage({ ...msg, sessionId: m.id }));
16614
+ }
16615
+ );
16616
+ return run(meta, messages);
16617
+ }
16311
16618
  insertMessage(msg) {
16312
- this.db.prepare(
16619
+ const info = this.db.prepare(
16313
16620
  `
16314
- INSERT INTO messages (id, session_id, role, content, tool_call_id, tool_calls, tool_results, parts, tool_name, timestamp, token_count, finish_reason, reasoning, turn_facts, active, compacted, observed)
16315
- VALUES (@id, @sessionId, @role, @content, @toolCallId, @toolCalls, @toolResults, @parts, @toolName, @timestamp, @tokenCount, @finishReason, @reasoning, @turnFacts, 1, 0, 0)
16621
+ INSERT INTO messages (session_id, role, content, tool_call_id, tool_calls, tool_results, parts, tool_name, timestamp, token_count, finish_reason, reasoning, turn_facts, active, compacted, observed)
16622
+ VALUES (@sessionId, @role, @content, @toolCallId, @toolCalls, @toolResults, @parts, @toolName, @timestamp, @tokenCount, @finishReason, @reasoning, @turnFacts, 1, 0, 0)
16316
16623
  `
16317
16624
  ).run({
16318
16625
  ...msg,
@@ -16330,7 +16637,7 @@ var SessionDB = class {
16330
16637
  this.db.prepare(
16331
16638
  `UPDATE sessions SET message_count = message_count + 1, tool_call_count = tool_call_count + @toolCalls, updated_at = MAX(COALESCE(updated_at, 0), @timestamp) WHERE id = @id`
16332
16639
  ).run({ id: msg.sessionId, toolCalls, timestamp: msg.timestamp });
16333
- if (msg.id > this.msgId) this.msgId = msg.id;
16640
+ return Number(info.lastInsertRowid);
16334
16641
  }
16335
16642
  recordUsage(sessionId, usage) {
16336
16643
  const priced = usage.costUsd !== void 0;
@@ -16392,9 +16699,6 @@ var SessionDB = class {
16392
16699
  }
16393
16700
  return info.changes;
16394
16701
  }
16395
- nextMessageId() {
16396
- return ++this.msgId;
16397
- }
16398
16702
  search(input) {
16399
16703
  const limit = input.limit ?? 5;
16400
16704
  const scope = input.scope ?? "all";
@@ -16559,10 +16863,8 @@ var SessionDB = class {
16559
16863
  if (middle.length === 0) return null;
16560
16864
  const mark = this.db.prepare("UPDATE messages SET active = 0, compacted = 1 WHERE id = ?");
16561
16865
  for (const row of middle) mark.run(row.id);
16562
- const summaryId = this.nextMessageId();
16563
- this.insertMessage({
16866
+ const summaryId = this.insertMessage({
16564
16867
  sessionId,
16565
- id: summaryId,
16566
16868
  role: "user",
16567
16869
  content: input.summary,
16568
16870
  timestamp: middle[0].timestamp
@@ -16918,6 +17220,61 @@ var SessionReferences = class {
16918
17220
  };
16919
17221
  // src/session/manager.ts
16920
17222
  var AUTO_VACUUM_MIN_FREELIST_RATIO = 0.25;
17223
+ var IMPORT_ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant", "tool"]);
17224
+ function validateImport(input) {
17225
+ const { meta, messages } = input;
17226
+ if (typeof meta.id !== "string" || meta.id.length === 0) return "meta.id";
17227
+ if (!Number.isFinite(meta.startedAt)) return "meta.startedAt";
17228
+ if (!Number.isFinite(meta.updatedAt)) return "meta.updatedAt";
17229
+ if (meta.endedAt !== void 0 && !Number.isFinite(meta.endedAt)) return "meta.endedAt";
17230
+ const seen = /* @__PURE__ */ new Set();
17231
+ for (const [i, msg] of messages.entries()) {
17232
+ if (!IMPORT_ROLES.has(msg.role)) return `messages[${i}].role`;
17233
+ if (typeof msg.content !== "string") return `messages[${i}].content`;
17234
+ if (!Number.isFinite(msg.timestamp)) return `messages[${i}].timestamp`;
17235
+ if (!Number.isFinite(msg.id) || seen.has(msg.id)) return `messages[${i}].id`;
17236
+ seen.add(msg.id);
17237
+ }
17238
+ return void 0;
17239
+ }
17240
+ function buildImportedMeta(meta, keepParent) {
17241
+ return {
17242
+ id: meta.id,
17243
+ title: meta.title,
17244
+ displayName: meta.displayName,
17245
+ source: meta.source ?? "import",
17246
+ cwd: meta.cwd,
17247
+ workspaceState: meta.workspaceState,
17248
+ workspaceRoot: meta.workspaceRoot,
17249
+ gitBranch: meta.gitBranch,
17250
+ gitRepoRoot: meta.gitRepoRoot,
17251
+ model: meta.model,
17252
+ tags: [],
17253
+ preview: "",
17254
+ pinned: meta.pinned ?? false,
17255
+ archived: meta.archived ?? false,
17256
+ rewindCount: 0,
17257
+ parentId: keepParent ? meta.parentId : void 0,
17258
+ messageCount: 0,
17259
+ toolCallCount: 0,
17260
+ apiCallCount: 0,
17261
+ inputTokens: 0,
17262
+ outputTokens: 0,
17263
+ cacheReadTokens: 0,
17264
+ cacheWriteTokens: 0,
17265
+ reasoningTokens: 0,
17266
+ estimatedCostUsd: void 0,
17267
+ actualCostUsd: void 0,
17268
+ compressionIneffectiveCount: 0,
17269
+ expiryFinalized: 0,
17270
+ summary: null,
17271
+ plan: null,
17272
+ startedAt: new Date(meta.startedAt),
17273
+ updatedAt: new Date(meta.updatedAt),
17274
+ endedAt: meta.endedAt === void 0 ? void 0 : new Date(meta.endedAt),
17275
+ endReason: meta.endReason
17276
+ };
17277
+ }
16921
17278
  var SessionManager = class {
16922
17279
  db;
16923
17280
  touched = /* @__PURE__ */ new Set();
@@ -17008,6 +17365,56 @@ var SessionManager = class {
17008
17365
  list(input) {
17009
17366
  return this.db.listSessions(input ?? {});
17010
17367
  }
17368
+ importSession(input) {
17369
+ const { meta, messages } = input;
17370
+ const id = meta.id;
17371
+ const bad2 = validateImport(input);
17372
+ if (bad2) return { ok: false, id, reason: "invalid", detail: bad2 };
17373
+ if (this.db.hasSession(id)) return { ok: false, id, reason: "exists" };
17374
+ const converted = [];
17375
+ for (const [i, msg] of messages.entries()) {
17376
+ const { id: _sourceId, parts, ...rest } = msg;
17377
+ if (!parts) {
17378
+ converted.push(rest);
17379
+ continue;
17380
+ }
17381
+ const result = importParts(parts);
17382
+ if (!result.ok) {
17383
+ return {
17384
+ ok: false,
17385
+ id,
17386
+ reason: "invalid",
17387
+ detail: `messages[${i}].parts.${result.detail}`
17388
+ };
17389
+ }
17390
+ converted.push({ ...rest, parts: result.parts });
17391
+ }
17392
+ const droppedParentId = meta.parentId !== void 0 && !this.db.hasSession(meta.parentId) ? meta.parentId : void 0;
17393
+ try {
17394
+ const newIds = this.db.importSession(
17395
+ buildImportedMeta(meta, droppedParentId === void 0),
17396
+ converted
17397
+ );
17398
+ const idMap = {};
17399
+ messages.forEach((msg, i) => {
17400
+ idMap[msg.id] = newIds[i];
17401
+ });
17402
+ return {
17403
+ ok: true,
17404
+ id,
17405
+ messageCount: newIds.length,
17406
+ idMap,
17407
+ ...droppedParentId === void 0 ? {} : { droppedParentId }
17408
+ };
17409
+ } catch (err) {
17410
+ return {
17411
+ ok: false,
17412
+ id,
17413
+ reason: "failed",
17414
+ ...err instanceof Error ? { detail: err.name } : {}
17415
+ };
17416
+ }
17417
+ }
17011
17418
  updateMeta(id, patch) {
17012
17419
  this.db.updateSessionMeta(id, patch);
17013
17420
  }
@@ -17062,6 +17469,16 @@ var SessionManager = class {
17062
17469
  return { ...result, error: cause instanceof Error ? cause.message : String(cause) };
17063
17470
  }
17064
17471
  }
17472
+ maybePruneAttachments(root, keepSession, opts = {}) {
17473
+ const now = opts.now?.() ?? Date.now();
17474
+ const minInterval = opts.minIntervalMs ?? 24 * 60 * 60 * 1e3;
17475
+ try {
17476
+ if (!this.db.claimMaintenanceSlot("last_attachment_prune", minInterval, now)) return 0;
17477
+ return pruneAttachments(root, keepSession, ATTACHMENT_RETENTION_DEFAULTS, now);
17478
+ } catch {
17479
+ return 0;
17480
+ }
17481
+ }
17065
17482
  delete(id) {
17066
17483
  return this.db.deleteSession(id);
17067
17484
  }
@@ -17081,15 +17498,16 @@ var SessionManager = class {
17081
17498
  }
17082
17499
  appendMessage(sessionId, msg) {
17083
17500
  this.touched.add(sessionId);
17084
- const id = this.db.nextMessageId();
17085
- this.db.insertMessage({ ...msg, sessionId, id, timestamp: Date.now() });
17086
- const meta = this.db.getSessionMeta(sessionId);
17087
- if (!meta) return id;
17088
- if (!meta.title && msg.role === "user") {
17089
- const title = msg.content.slice(0, 50).replace(/\n/g, " ");
17090
- this.db.updateSessionMeta(sessionId, { title });
17091
- }
17092
- return id;
17501
+ return this.db.transaction(() => {
17502
+ const id = this.db.insertMessage({ ...msg, sessionId, timestamp: Date.now() });
17503
+ const meta = this.db.getSessionMeta(sessionId);
17504
+ if (!meta) return id;
17505
+ if (!meta.title && msg.role === "user") {
17506
+ const title = msg.content.slice(0, 50).replace(/\n/g, " ");
17507
+ this.db.updateSessionMeta(sessionId, { title });
17508
+ }
17509
+ return id;
17510
+ });
17093
17511
  }
17094
17512
  attachTurnFacts(sessionId, messageId, input) {
17095
17513
  this.db.attachTurnFacts(sessionId, messageId, input);
@@ -17268,6 +17686,30 @@ function firstSentence(text, maxLen) {
17268
17686
  const lastSpace = cut.lastIndexOf(" ");
17269
17687
  return (lastSpace > maxLen * 0.6 ? cut.slice(0, lastSpace) : cut) + "\u2026";
17270
17688
  }
17689
+ // src/session/persist-failure.ts
17690
+ function codeOf(cause) {
17691
+ if (typeof cause !== "object" || cause === null) return "";
17692
+ const code = cause.code;
17693
+ return typeof code === "string" ? code : "";
17694
+ }
17695
+ function classifyPersistFailure(cause) {
17696
+ const code = codeOf(cause);
17697
+ if (!code) return "unknown";
17698
+ if (code.startsWith("SQLITE_FULL") || code === "ENOSPC") return "disk-full";
17699
+ if (code.startsWith("SQLITE_BUSY") || code.startsWith("SQLITE_LOCKED")) return "locked";
17700
+ if (code.startsWith("SQLITE_READONLY") || code.startsWith("SQLITE_PERM") || code.startsWith("SQLITE_CANTOPEN") || code === "EACCES" || code === "EPERM" || code === "EROFS") {
17701
+ return "read-only";
17702
+ }
17703
+ if (code.startsWith("SQLITE_CORRUPT") || code === "SQLITE_NOTADB") return "corrupt";
17704
+ if (code.startsWith("SQLITE_IOERR") || code === "EIO") return "io-error";
17705
+ return "unknown";
17706
+ }
17707
+ function isTransientPersistFailure(kind) {
17708
+ return kind === "locked";
17709
+ }
17710
+ function persistFailureCode(cause) {
17711
+ return codeOf(cause);
17712
+ }
17271
17713
  function isSettled(phase) {
17272
17714
  return phase === "complete";
17273
17715
  }
@@ -19136,6 +19578,9 @@ var ScheduleRegistrar = class {
19136
19578
  this.opts = opts;
19137
19579
  }
19138
19580
  opts;
19581
+ get homeDir() {
19582
+ return this.opts.homeDir;
19583
+ }
19139
19584
  osTaskIdFor(taskId) {
19140
19585
  const backend = this.opts.backend;
19141
19586
  if (!backend) return "";
@@ -19206,7 +19651,6 @@ var ScheduleRegistrar = class {
19206
19651
  if (this.opts.backend && def.osTaskId) await this.opts.backend.retire(def.osTaskId);
19207
19652
  }
19208
19653
  };
19209
- // src/schedule/doctor.ts
19210
19654
  async function diagnoseSchedules(opts) {
19211
19655
  const { store, backend, registrar } = opts;
19212
19656
  if (!backend) return { checked: 0, drifts: [], unsupported: true };
@@ -19243,8 +19687,18 @@ async function diagnoseSchedules(opts) {
19243
19687
  });
19244
19688
  }
19245
19689
  }
19690
+ let foreign = 0;
19246
19691
  for (const osTaskId of osTaskIds) {
19247
19692
  if (seen.has(osTaskId)) continue;
19693
+ const snapshot = await backend.inspect(osTaskId);
19694
+ if (!snapshot) {
19695
+ foreign++;
19696
+ continue;
19697
+ }
19698
+ if (!ownedByThisHome(snapshot.argv, registrar.homeDir)) {
19699
+ foreign++;
19700
+ continue;
19701
+ }
19248
19702
  const taskId = backend.taskIdFrom(osTaskId);
19249
19703
  drifts.push({
19250
19704
  kind: "orphan-in-os",
@@ -19252,7 +19706,17 @@ async function diagnoseSchedules(opts) {
19252
19706
  ...taskId ? { scheduleId: taskId } : {}
19253
19707
  });
19254
19708
  }
19255
- return { checked: defs.length + osTaskIds.length - seen.size, drifts, unsupported: false };
19709
+ return {
19710
+ checked: defs.length + osTaskIds.length - seen.size - foreign,
19711
+ drifts,
19712
+ unsupported: false
19713
+ };
19714
+ }
19715
+ function ownedByThisHome(argv, homeDir) {
19716
+ const at = argv.indexOf("--home");
19717
+ const home = at >= 0 ? argv[at + 1] : void 0;
19718
+ if (home === void 0 || home.length === 0) return true;
19719
+ return samePath(home, homeDir);
19256
19720
  }
19257
19721
  function compare2(def, snapshot, backend, registrar) {
19258
19722
  const expected = backend.expectedSnapshot(registrar.toRegisterInput(def, def.osCommand));
@@ -20083,9 +20547,9 @@ function buildAutomaton(patterns) {
20083
20547
  while (queue.length > 0) {
20084
20548
  const cur = queue.shift();
20085
20549
  for (const [ch, child] of nodes[cur].next) {
20086
- let fail8 = nodes[cur].fail;
20087
- while (fail8 !== 0 && !nodes[fail8].next.has(ch)) fail8 = nodes[fail8].fail;
20088
- const target = nodes[fail8].next.get(ch);
20550
+ let fail9 = nodes[cur].fail;
20551
+ while (fail9 !== 0 && !nodes[fail9].next.has(ch)) fail9 = nodes[fail9].fail;
20552
+ const target = nodes[fail9].next.get(ch);
20089
20553
  nodes[child].fail = target !== void 0 && target !== child ? target : 0;
20090
20554
  nodes[child].out.push(...nodes[nodes[child].fail].out);
20091
20555
  queue.push(child);
@@ -22150,6 +22614,7 @@ function listWorkspaceFiles(root) {
22150
22614
  }
22151
22615
  function listFromGit(root) {
22152
22616
  if (!existsSync(join(root, ".git"))) return null;
22617
+ if (!canProbeGit()) return null;
22153
22618
  try {
22154
22619
  const out = execFileSync("git", ["ls-files", "-co", "--exclude-standard", "-z"], {
22155
22620
  cwd: root,
@@ -22358,17 +22823,6 @@ function readSession(ref, opts, usedBytes) {
22358
22823
  })
22359
22824
  };
22360
22825
  }
22361
- var ATTACHMENT_RETENTION_DEFAULTS = {
22362
- maxAgeDays: 7,
22363
- maxTotalBytes: ATTACHMENT_MAX_TOTAL_BYTES
22364
- };
22365
- function pruneAttachments(root, keepSession, retention = ATTACHMENT_RETENTION_DEFAULTS, now = Date.now()) {
22366
- const shared = retention;
22367
- return pruneArtifacts(root, keepSession, shared, now);
22368
- }
22369
- async function removeSessionAttachments(sessionId, homeDir) {
22370
- await rm(attachmentsDir(sessionId, homeDir), { recursive: true, force: true });
22371
- }
22372
22826
  var OPENABLE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
22373
22827
  function artifactOpenRefusal(path, homeDir) {
22374
22828
  const root = homeDir === void 0 ? artifactsDir() : artifactsDir(void 0, homeDir);
@@ -22403,12 +22857,12 @@ async function openArtifact(path, homeDir) {
22403
22857
  if (refusal2) throw new Error(describeArtifactRefusal(refusal2));
22404
22858
  const launch = artifactLaunchArgv(path, process.platform, homeDir);
22405
22859
  if (!launch) throw new Error(describeArtifactRefusal("launch-failed"));
22406
- await new Promise((done2, fail8) => {
22860
+ await new Promise((done2, fail9) => {
22407
22861
  try {
22408
22862
  const child = spawn(launch.command, launch.args, { stdio: "ignore", detached: true });
22409
22863
  child.on(
22410
22864
  "error",
22411
- (err) => fail8(
22865
+ (err) => fail9(
22412
22866
  new Error(
22413
22867
  t("artifact.open_launch_failed_detail", {
22414
22868
  reason: describeArtifactRefusal("launch-failed"),
@@ -22422,7 +22876,7 @@ async function openArtifact(path, homeDir) {
22422
22876
  done2();
22423
22877
  });
22424
22878
  } catch (err) {
22425
- fail8(
22879
+ fail9(
22426
22880
  new Error(
22427
22881
  t("artifact.open_launch_failed_detail", {
22428
22882
  reason: describeArtifactRefusal("launch-failed"),
@@ -23279,4 +23733,4 @@ function trunc(stdout, stderr) {
23279
23733
  * `--tree` 的命令帮助里,让用户自己知道 `--tree` 意味着什么。
23280
23734
  */
23281
23735
 
23282
- 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, MemoryReviewer, 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, 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, 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, importSkills, installPlugin, instructionDirs, instructionNotices, intervalSlots, inventoryProjectExtensions, inventoryTotal, isBlockCode, isCodeModeDeferred, isEmptyRules, isGoalPhase, isNonInteractive, isReadOnlySubCall, isSettingWriteLayer, 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, 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 };
23736
+ 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, MemoryReviewer, 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 };