@chanlerdev/scorel 0.0.2 → 0.0.4

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.
Files changed (42) hide show
  1. package/README.md +55 -2
  2. package/dist/index.js +1162 -192
  3. package/dist/index.js.map +3 -3
  4. package/docs/CHANGELOG.md +106 -0
  5. package/docs/ROADMAP.md +34 -2
  6. package/docs/SHIP.md +1 -1
  7. package/docs/spec/channels.md +15 -5
  8. package/docs/spec/extensions.md +6 -5
  9. package/docs/spec/ship/S0060-relay-hosted-webui-e2e-validation.verification.md +1 -1
  10. package/docs/spec/ship/S0073-provider-model-profile-contract.md +6 -6
  11. package/docs/spec/ship/S0074-gui-model-provider-settings-split.md +1 -1
  12. package/docs/spec/ship/S0076-provider-modal-search-and-direct-key.md +1 -1
  13. package/docs/spec/ship/S0086-auto-compact-and-session-memory.md +1 -1
  14. package/docs/spec/ship/S0087-gui-ui-polish-sweep.md +153 -0
  15. package/docs/spec/ship/S0088-gui-streaming-thinking-contract.md +35 -0
  16. package/docs/spec/ship/S0089-memory-reliability-and-dream-trigger.md +84 -0
  17. package/docs/spec/ship/S0090-gui-provider-delete-and-dark-code-theme.md +77 -0
  18. package/docs/spec/ship/S0091-built-in-qq-and-wechat-im-extensions.md +125 -0
  19. package/docs/spec/ship/S0092-im-message-media-and-human-cadence.md +83 -0
  20. package/docs/spec/ship/S0093-gui-im-settings-platform-layout.md +66 -0
  21. package/docs/spec/ship/S0094-im-inbound-runtime.md +67 -0
  22. package/docs/spec/ship/S0095-gui-im-session-list-refresh.md +36 -0
  23. package/docs/spec/ship/S0096-glob-stable-order.md +32 -0
  24. package/docs/spec/ship/S0097-rtk-token-saving-settings.md +61 -0
  25. package/docs/spec/ship/S0098-local-daemon-singleton-unified-state.md +96 -0
  26. package/docs/spec/ship/S0099-gui-connection-device-settings.md +85 -0
  27. package/docs/spec/ship/S0100-gui-provider-danger-zone.md +57 -0
  28. package/docs/spec/ship/S0101-gui-device-settings-polish.md +66 -0
  29. package/docs/spec/ship/S0102-device-only-config.md +58 -0
  30. package/extensions/builtin/loopback/skills/loopback/SKILL.md +2 -0
  31. package/extensions/builtin/qq/adapter.d.ts +27 -0
  32. package/extensions/builtin/qq/adapter.js +384 -0
  33. package/extensions/builtin/qq/scorel.extension.json +7 -0
  34. package/extensions/builtin/qq/skills/qq/SKILL.md +9 -0
  35. package/extensions/builtin/telegram/adapter.d.ts +1 -1
  36. package/extensions/builtin/telegram/adapter.js +7 -0
  37. package/extensions/builtin/telegram/skills/telegram/SKILL.md +2 -0
  38. package/extensions/builtin/wechat/adapter.d.ts +24 -0
  39. package/extensions/builtin/wechat/adapter.js +226 -0
  40. package/extensions/builtin/wechat/scorel.extension.json +7 -0
  41. package/extensions/builtin/wechat/skills/wechat/SKILL.md +9 -0
  42. package/package.json +4 -2
package/dist/index.js CHANGED
@@ -257,14 +257,30 @@ var init_src2 = __esm({
257
257
  this.#assertDaemonConnected();
258
258
  return (await this.#request("fetch_provider_models", input)).models;
259
259
  }
260
- async getMemorySettings(input) {
260
+ async removeModelProvider(input) {
261
+ this.#assertDaemonConnected();
262
+ return this.#request("remove_model_provider", input);
263
+ }
264
+ async getMemorySettings(input = {}) {
261
265
  this.#assertDaemonConnected();
262
266
  return (await this.#request("get_memory_settings", input)).memory;
263
267
  }
268
+ async getMemoryStatus(input) {
269
+ this.#assertDaemonConnected();
270
+ return (await this.#request("get_memory_status", input)).status;
271
+ }
264
272
  async upsertMemorySettings(input) {
265
273
  this.#assertDaemonConnected();
266
274
  return (await this.#request("upsert_memory_settings", input)).memory;
267
275
  }
276
+ async getRuntimeSettings(input = {}) {
277
+ this.#assertDaemonConnected();
278
+ return (await this.#request("get_runtime_settings", input)).runtime;
279
+ }
280
+ async upsertRuntimeSettings(input) {
281
+ this.#assertDaemonConnected();
282
+ return (await this.#request("upsert_runtime_settings", input)).runtime;
283
+ }
268
284
  async getExtensionSettings(input) {
269
285
  this.#assertDaemonConnected();
270
286
  return (await this.#request("get_extension_settings", input)).extension;
@@ -799,7 +815,7 @@ var init_sessions = __esm({
799
815
  // packages/core/src/config/index.ts
800
816
  import { readFile as readFile3 } from "node:fs/promises";
801
817
  import { join as join4 } from "node:path";
802
- var SCOREL_CONFIG_SCHEMA, scorelUserRoot, scorelUserConfigPath, scorelSessionsDir, scorelProjectConfigPath, loadScorelConfig, loadScorelConfigProfile, listProviderConnections, listAvailableModels, listProviderModels, resolveModelSelection, renderModelProfileConfig, renderMemoryConfig, renderExtensionConfig, DEFAULT_MEMORY_CONFIG, loadMemory, loadExtensions, loadProviders, loadProviderProfiles, loadProviderModels, loadAvailableModels, loadRoles, readConfigText, parseToml, parseEditableConfig, renderRawConfig, emptyRawConfig, stripComment, requireString, normalizeProviderName, requireProviderCredential, resolveProviderApiKey, providerCredentialSummary, requireNumber, requireNonNegativeNumber, requireCompactThreshold, requireBoolean, requireCustomApi, requireProviderType, requireSection, ensureSection, setConfigValue, assertKnownKey, setValue, parseTomlValue, stripTrailingSlashes, requireIdentifier, tomlString, renderTomlValue, requireModelRole, modelRoles;
818
+ var SCOREL_CONFIG_SCHEMA, scorelUserRoot, scorelUserConfigPath, scorelSessionsDir, loadScorelConfig, loadScorelConfigProfile, listProviderConnections, listAvailableModels, listProviderModels, resolveModelSelection, renderModelProfileConfig, removeProvider, renderMemoryConfig, renderRuntimeConfig, renderExtensionConfig, DEFAULT_MEMORY_CONFIG, DEFAULT_RUNTIME_CONFIG, loadMemory, loadRuntime, loadExtensions, loadProviders, loadProviderProfiles, loadProviderModels, loadAvailableModels, loadRoles, readConfigText, configPathForDevice, parseToml, parseEditableConfig, renderRawConfig, emptyRawConfig, stripComment, requireString, normalizeProviderName, requireProviderCredential, resolveProviderApiKey, providerCredentialSummary, requireNumber, requireNonNegativeNumber, requireCompactThreshold, requireBoolean, requireCustomApi, requireProviderType, requireSection, ensureSection, setConfigValue, assertKnownKey, setValue, parseTomlValue, stripTrailingSlashes, requireIdentifier, tomlString, renderTomlValue, isNodeErrorCode, requireModelRole, modelRoles;
803
819
  var init_config = __esm({
804
820
  "packages/core/src/config/index.ts"() {
805
821
  "use strict";
@@ -807,8 +823,7 @@ var init_config = __esm({
807
823
  fixedPaths: {
808
824
  userRoot: "~/.scorel",
809
825
  userConfig: "~/.scorel/config.toml",
810
- sessionsDir: "~/.scorel/sessions",
811
- projectConfig: ".scorel/config.toml"
826
+ sessionsDir: "~/.scorel/sessions"
812
827
  },
813
828
  sections: {
814
829
  root: {
@@ -829,6 +844,9 @@ var init_config = __esm({
829
844
  memory: {
830
845
  keys: ["enabled", "daily", "sessionMemory", "autoDream", "promoteRoot", "dreamIdleMinutes", "autoCompactThreshold"]
831
846
  },
847
+ runtime: {
848
+ keys: ["tokenSavingRtk"]
849
+ },
832
850
  extension: {
833
851
  keys: ["enabled", "kind"]
834
852
  },
@@ -840,7 +858,6 @@ var init_config = __esm({
840
858
  scorelUserRoot = (homeDir) => join4(homeDir, ".scorel");
841
859
  scorelUserConfigPath = (homeDir) => join4(scorelUserRoot(homeDir), "config.toml");
842
860
  scorelSessionsDir = (homeDir) => join4(scorelUserRoot(homeDir), "sessions");
843
- scorelProjectConfigPath = (cwd) => join4(cwd, ".scorel", "config.toml");
844
861
  loadScorelConfig = async (options) => {
845
862
  const env = options.env ?? process.env;
846
863
  const raw = parseToml(await readConfigText(options));
@@ -854,6 +871,7 @@ var init_config = __esm({
854
871
  models,
855
872
  modelProfile: { roles },
856
873
  memory: loadMemory(raw),
874
+ runtime: loadRuntime(raw),
857
875
  extensions: loadExtensions(raw)
858
876
  };
859
877
  };
@@ -870,6 +888,7 @@ var init_config = __esm({
870
888
  models,
871
889
  modelProfile: { roles },
872
890
  memory: loadMemory(raw),
891
+ runtime: loadRuntime(raw),
873
892
  extensions: loadExtensions(raw)
874
893
  };
875
894
  };
@@ -974,6 +993,9 @@ var init_config = __esm({
974
993
  };
975
994
  renderModelProfileConfig = (input) => {
976
995
  const raw = parseEditableConfig(input.existingConfigText);
996
+ if (input.removeProviderId) {
997
+ removeProvider(raw, requireIdentifier(input.removeProviderId, "removeProviderId"));
998
+ }
977
999
  if (input.providerType || input.provider || input.apiKeyEnv || input.apiKey || input.api || input.baseUrl) {
978
1000
  const providerId = requireIdentifier(input.providerId, "providerId");
979
1001
  const providerType = requireProviderType(input.providerType, "providerType");
@@ -1081,6 +1103,34 @@ var init_config = __esm({
1081
1103
  }
1082
1104
  return renderRawConfig(raw);
1083
1105
  };
1106
+ removeProvider = (raw, providerId) => {
1107
+ delete raw.providers[providerId];
1108
+ const removedProviderModels = /* @__PURE__ */ new Set();
1109
+ for (const [providerModelId, providerModel] of Object.entries(raw.providerModels)) {
1110
+ if (providerModel.provider === providerId) {
1111
+ delete raw.providerModels[providerModelId];
1112
+ removedProviderModels.add(providerModelId);
1113
+ }
1114
+ }
1115
+ const removedAvailableModels = /* @__PURE__ */ new Set();
1116
+ for (const [availableModelId, availableModel] of Object.entries(raw.availableModels)) {
1117
+ if (availableModel.model && removedProviderModels.has(availableModel.model)) {
1118
+ delete raw.availableModels[availableModelId];
1119
+ removedAvailableModels.add(availableModelId);
1120
+ }
1121
+ }
1122
+ if (!raw.modelProfile?.roles) return;
1123
+ const fallbackModelId = Object.keys(raw.availableModels).sort()[0];
1124
+ if (!fallbackModelId) {
1125
+ delete raw.modelProfile;
1126
+ return;
1127
+ }
1128
+ for (const role of ["primary", "standard", "auxiliary"]) {
1129
+ if (!raw.modelProfile.roles[role] || removedAvailableModels.has(raw.modelProfile.roles[role])) {
1130
+ raw.modelProfile.roles[role] = fallbackModelId;
1131
+ }
1132
+ }
1133
+ };
1084
1134
  renderMemoryConfig = (input) => {
1085
1135
  const raw = parseEditableConfig(input.existingConfigText);
1086
1136
  raw.memory = {
@@ -1095,6 +1145,14 @@ var init_config = __esm({
1095
1145
  };
1096
1146
  return renderRawConfig(raw);
1097
1147
  };
1148
+ renderRuntimeConfig = (input) => {
1149
+ const raw = parseEditableConfig(input.existingConfigText);
1150
+ raw.runtime = {
1151
+ ...loadRuntime(raw),
1152
+ ...input.tokenSavingRtk !== void 0 ? { tokenSavingRtk: requireBoolean(input.tokenSavingRtk, "runtime.tokenSavingRtk") } : {}
1153
+ };
1154
+ return renderRawConfig(raw);
1155
+ };
1098
1156
  renderExtensionConfig = (input) => {
1099
1157
  const raw = parseEditableConfig(input.existingConfigText);
1100
1158
  const extensionId = requireIdentifier(input.extensionId, "extensionId");
@@ -1126,6 +1184,9 @@ var init_config = __esm({
1126
1184
  dreamIdleMinutes: 60,
1127
1185
  autoCompactThreshold: 0.8
1128
1186
  };
1187
+ DEFAULT_RUNTIME_CONFIG = {
1188
+ tokenSavingRtk: false
1189
+ };
1129
1190
  loadMemory = (raw) => ({
1130
1191
  enabled: raw.memory?.enabled ?? DEFAULT_MEMORY_CONFIG.enabled,
1131
1192
  daily: raw.memory?.daily ?? DEFAULT_MEMORY_CONFIG.daily,
@@ -1135,6 +1196,9 @@ var init_config = __esm({
1135
1196
  dreamIdleMinutes: requireNonNegativeNumber(raw.memory?.dreamIdleMinutes ?? DEFAULT_MEMORY_CONFIG.dreamIdleMinutes, "memory.dreamIdleMinutes"),
1136
1197
  autoCompactThreshold: requireCompactThreshold(raw.memory?.autoCompactThreshold ?? DEFAULT_MEMORY_CONFIG.autoCompactThreshold)
1137
1198
  });
1199
+ loadRuntime = (raw) => ({
1200
+ tokenSavingRtk: raw.runtime?.tokenSavingRtk ?? DEFAULT_RUNTIME_CONFIG.tokenSavingRtk
1201
+ });
1138
1202
  loadExtensions = (raw) => {
1139
1203
  const extensions = {};
1140
1204
  for (const [extensionId, extension] of Object.entries(raw.extensions)) {
@@ -1297,22 +1361,26 @@ var init_config = __esm({
1297
1361
  };
1298
1362
  };
1299
1363
  readConfigText = async (options) => {
1300
- const projectPath = scorelProjectConfigPath(options.cwd);
1364
+ const userPath = configPathForDevice(options);
1301
1365
  try {
1302
- return await readFile3(projectPath, "utf8");
1303
- } catch {
1304
- const home = options.homeDir ?? process.env.HOME;
1305
- if (!home) {
1306
- throw new Error(`Scorel config not found: ${projectPath}`);
1307
- }
1308
- const userPath = scorelUserConfigPath(home);
1309
- try {
1310
- return await readFile3(userPath, "utf8");
1311
- } catch {
1312
- throw new Error(`Scorel config not found: ${projectPath} or ${userPath}`);
1366
+ return await readFile3(userPath, "utf8");
1367
+ } catch (cause) {
1368
+ if (isNodeErrorCode(cause, "ENOENT")) {
1369
+ throw new Error(`Scorel config not found: ${userPath}`);
1313
1370
  }
1371
+ throw cause;
1314
1372
  }
1315
1373
  };
1374
+ configPathForDevice = (options) => {
1375
+ if (options.scorelHomeDir) {
1376
+ return join4(options.scorelHomeDir, "config.toml");
1377
+ }
1378
+ const home = options.homeDir ?? process.env.HOME;
1379
+ if (!home) {
1380
+ throw new Error("Scorel config not found: HOME is not set");
1381
+ }
1382
+ return scorelUserConfigPath(home);
1383
+ };
1316
1384
  parseToml = (text) => {
1317
1385
  const result = emptyRawConfig();
1318
1386
  let section2 = { kind: "root" };
@@ -1414,6 +1482,12 @@ var init_config = __esm({
1414
1482
  lines.push(`autoCompactThreshold = ${memory.autoCompactThreshold}`);
1415
1483
  lines.push("");
1416
1484
  }
1485
+ if (raw.runtime) {
1486
+ const runtime = loadRuntime(raw);
1487
+ lines.push("[runtime]");
1488
+ lines.push(`tokenSavingRtk = ${runtime.tokenSavingRtk}`);
1489
+ lines.push("");
1490
+ }
1417
1491
  for (const [extensionId, extension] of Object.entries(raw.extensions).sort(([left], [right]) => left.localeCompare(right))) {
1418
1492
  lines.push(`[extensions.${extensionId}]`);
1419
1493
  lines.push(`enabled = ${extension.enabled === true}`);
@@ -1545,6 +1619,9 @@ var init_config = __esm({
1545
1619
  if (section2 === "memory") {
1546
1620
  return { kind: "memory" };
1547
1621
  }
1622
+ if (section2 === "runtime") {
1623
+ return { kind: "runtime" };
1624
+ }
1548
1625
  const extensionConfigMatch = /^extensions\.([A-Za-z0-9_-]+)\.config$/.exec(section2);
1549
1626
  if (extensionConfigMatch?.[1]) {
1550
1627
  return { kind: "extensionConfig", id: extensionConfigMatch[1] };
@@ -1567,6 +1644,8 @@ var init_config = __esm({
1567
1644
  config.modelProfile.roles ??= {};
1568
1645
  } else if (section2.kind === "memory") {
1569
1646
  config.memory ??= {};
1647
+ } else if (section2.kind === "runtime") {
1648
+ config.runtime ??= {};
1570
1649
  } else if (section2.kind === "extension") {
1571
1650
  config.extensions[section2.id] ??= {};
1572
1651
  } else if (section2.kind === "extensionConfig") {
@@ -1592,6 +1671,9 @@ var init_config = __esm({
1592
1671
  } else if (section2.kind === "memory") {
1593
1672
  config.memory ??= {};
1594
1673
  setValue(config.memory, key, value);
1674
+ } else if (section2.kind === "runtime") {
1675
+ config.runtime ??= {};
1676
+ setValue(config.runtime, key, value);
1595
1677
  } else if (section2.kind === "extension") {
1596
1678
  config.extensions[section2.id] ??= {};
1597
1679
  setValue(config.extensions[section2.id], key, value);
@@ -1645,6 +1727,7 @@ var init_config = __esm({
1645
1727
  };
1646
1728
  tomlString = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
1647
1729
  renderTomlValue = (value) => typeof value === "string" ? tomlString(value) : String(value);
1730
+ isNodeErrorCode = (cause, code) => typeof cause === "object" && cause !== null && "code" in cause && cause.code === code;
1648
1731
  requireModelRole = (value, role, models) => {
1649
1732
  const modelId = requireString(value, `model_profile.roles.${role}`);
1650
1733
  if (!models[modelId]) {
@@ -1660,9 +1743,10 @@ var init_config = __esm({
1660
1743
  import { createHash, randomUUID as randomUUID2 } from "node:crypto";
1661
1744
  import { execFile } from "node:child_process";
1662
1745
  import { mkdir as mkdir2, readFile as readFile4, rename as rename2, rm, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
1663
- import { dirname as dirname3, extname, isAbsolute, relative, resolve } from "node:path";
1746
+ import { userInfo } from "node:os";
1747
+ import { basename as basename2, dirname as dirname3, extname, isAbsolute, relative, resolve } from "node:path";
1664
1748
  import { promisify } from "node:util";
1665
- var execFileAsync, DEFAULT_SEARCH_LIMIT, DEFAULT_GREP_LIMIT, DEFAULT_READ_LIMIT, DEFAULT_CONTEXT_WINDOW, READ_TOKEN_BUDGET_RATIO, FULL_READ_TOKEN_BUDGET_RATIO, createCodingTools, parseReadArgs, parseWriteArgs, parseEditArgs, parseBashArgs, parseGlobArgs, parseGrepArgs, parseTodoWriteArgs, parseTodoItem, expectRecord, expectPath, expectString, optionalString, optionalNumber, optionalBoolean, snapshotFile, sameSnapshot, exists, isWithin, linesOf, IMAGE_EXTENSIONS, DOCUMENT_EXTENSIONS, BINARY_EXTENSIONS, assertReadableFileKind, assertTextBuffer, selectCompleteLinesWithinBudget, estimateTokens, renderReadLines, readTokenBudget, completeRanges, hasCompleteCoverage, mergeRanges, countOccurrences, atomicWriteFile, bashResult, truncate, textResult, byteLength, isTimeoutError, isExecError, runRipgrep, splitOutput, vcsExcludes, grepArgs, splitGlobPatterns, paginate, toWorkspaceRelative, relativizeGrepLine, relativizeCountLine, sortPathsByMtime, formatPaginatedText, formatLimitSuffix, parseCountLines;
1749
+ var execFileAsync, DEFAULT_SEARCH_LIMIT, DEFAULT_GREP_LIMIT, DEFAULT_READ_LIMIT, DEFAULT_CONTEXT_WINDOW, READ_TOKEN_BUDGET_RATIO, FULL_READ_TOKEN_BUDGET_RATIO, createCodingTools, parseReadArgs, parseWriteArgs, parseEditArgs, parseBashArgs, parseGlobArgs, parseGrepArgs, parseTodoWriteArgs, parseTodoItem, expectRecord, expectPath, expectString, optionalString, optionalNumber, optionalBoolean, snapshotFile, sameSnapshot, exists, isWithin, linesOf, IMAGE_EXTENSIONS, DOCUMENT_EXTENSIONS, BINARY_EXTENSIONS, assertReadableFileKind, assertTextBuffer, selectCompleteLinesWithinBudget, estimateTokens, renderReadLines, readTokenBudget, completeRanges, hasCompleteCoverage, mergeRanges, countOccurrences, atomicWriteFile, bashResult, resolveDefaultShell, resolveRtkCommand, rtkRewriteResult, executableRewriteCommand, readRtkGain, rtkSavedTokenDelta, withRtkSavings, nonNegativeInteger, isRecord3, shellQuote, shellCommandArgs, userShell, truncate, textResult, byteLength, isTimeoutError, isExecError, runRipgrep, splitOutput, vcsExcludes, grepArgs, splitGlobPatterns, paginate, toWorkspaceRelative, relativizeGrepLine, relativizeCountLine, sortPathsByMtime, formatPaginatedText, formatLimitSuffix, parseCountLines;
1666
1750
  var init_coding_tools = __esm({
1667
1751
  "packages/core/src/tools/coding-tools.ts"() {
1668
1752
  "use strict";
@@ -1682,6 +1766,7 @@ var init_coding_tools = __esm({
1682
1766
  const maxOutputBytes = options.maxOutputBytes ?? 16e3;
1683
1767
  const normalReadTokens = options.maxReadTokens ?? readTokenBudget(options.contextWindow, READ_TOKEN_BUDGET_RATIO);
1684
1768
  const fullReadTokens = options.maxReadTokens ?? readTokenBudget(options.contextWindow, FULL_READ_TOKEN_BUDGET_RATIO);
1769
+ const defaultShell = resolveDefaultShell(options.defaultShell);
1685
1770
  const resolveWorkspacePath = (input) => {
1686
1771
  if (input.length === 0) {
1687
1772
  throw new Error("path must not be empty");
@@ -1834,25 +1919,52 @@ String: ${input.old_string}`
1834
1919
  const commandCwd = input.cwd ? resolveWorkspacePath(input.cwd) : root;
1835
1920
  const timeoutMs = Math.min(input.timeoutMs ?? defaultTimeoutMs, maxTimeoutMs);
1836
1921
  const outputLimit = input.maxOutputBytes ?? maxOutputBytes;
1922
+ const rtk = options.tokenSaving?.rtk;
1923
+ const rtkCommand = await resolveRtkCommand(rtk, input.command);
1924
+ const command = rtkCommand.rewrittenCommand ?? input.command;
1925
+ const executionCommand = rtkCommand.executionCommand ?? input.command;
1926
+ const executable = defaultShell;
1927
+ const argv = shellCommandArgs(defaultShell, executionCommand);
1928
+ const rtkGainBefore = rtkCommand.applied && rtk?.executable ? await readRtkGain(rtk.executable, commandCwd) : void 0;
1929
+ const rtkResult = {
1930
+ enabled: rtk?.enabled === true,
1931
+ applied: rtkCommand.applied,
1932
+ ...rtk?.executable ? { executable: rtk.executable } : {},
1933
+ ...rtkCommand.rewrittenCommand ? { rewrittenCommand: rtkCommand.rewrittenCommand } : {}
1934
+ };
1837
1935
  try {
1838
- const result = await execFileAsync("/bin/bash", ["-lc", input.command], {
1936
+ const result = await execFileAsync(executable, argv, {
1839
1937
  cwd: commandCwd,
1840
1938
  timeout: timeoutMs,
1841
1939
  signal,
1842
1940
  maxBuffer: Math.max(outputLimit * 4, 1024 * 1024)
1843
1941
  });
1844
- return bashResult({ exitCode: 0, stdout: result.stdout, stderr: result.stderr, cwd: commandCwd, outputLimit });
1942
+ const rtkSavedTokens = rtk?.executable ? await rtkSavedTokenDelta(rtk.executable, commandCwd, rtkGainBefore) : void 0;
1943
+ return bashResult({
1944
+ exitCode: 0,
1945
+ stdout: result.stdout,
1946
+ stderr: result.stderr,
1947
+ cwd: commandCwd,
1948
+ outputLimit,
1949
+ shell: defaultShell,
1950
+ command,
1951
+ rtk: withRtkSavings(rtkResult, rtkSavedTokens)
1952
+ });
1845
1953
  } catch (cause) {
1846
1954
  if (isTimeoutError(cause)) {
1847
1955
  throw new Error(`Bash command timed out after ${timeoutMs}ms`);
1848
1956
  }
1849
1957
  if (isExecError(cause)) {
1958
+ const rtkSavedTokens = rtk?.executable ? await rtkSavedTokenDelta(rtk.executable, commandCwd, rtkGainBefore) : void 0;
1850
1959
  return bashResult({
1851
1960
  exitCode: typeof cause.code === "number" ? cause.code : 1,
1852
1961
  stdout: String(cause.stdout ?? ""),
1853
1962
  stderr: String(cause.stderr ?? cause.message),
1854
1963
  cwd: commandCwd,
1855
- outputLimit
1964
+ outputLimit,
1965
+ shell: defaultShell,
1966
+ command,
1967
+ rtk: withRtkSavings(rtkResult, rtkSavedTokens)
1856
1968
  });
1857
1969
  }
1858
1970
  throw cause;
@@ -1866,7 +1978,7 @@ String: ${input.old_string}`
1866
1978
  const input = parseGlobArgs(args);
1867
1979
  const limit = input.head_limit ?? DEFAULT_SEARCH_LIMIT;
1868
1980
  const offset = input.offset ?? 0;
1869
- const all = await runRipgrep(["--files", "--hidden", "--glob", input.pattern, ...vcsExcludes()], workspaceTarget(input.path), root, signal);
1981
+ const all = (await runRipgrep(["--files", "--hidden", "--glob", input.pattern, ...vcsExcludes()], workspaceTarget(input.path), root, signal)).sort((left, right) => toWorkspaceRelative(root)(left).localeCompare(toWorkspaceRelative(root)(right)));
1870
1982
  const selected = paginate(all, limit, offset);
1871
1983
  const text = selected.items.map(toWorkspaceRelative(root)).join("\n");
1872
1984
  return textResult(text || "No files found", {
@@ -2227,9 +2339,96 @@ ${stdout}
2227
2339
  stderr:
2228
2340
  ${stderr}`, {
2229
2341
  exitCode: input.exitCode,
2230
- cwd: input.cwd
2342
+ cwd: input.cwd,
2343
+ ...input.shell ? { shell: input.shell } : {},
2344
+ ...input.command ? { command: input.command } : {},
2345
+ ...input.rtk ? {
2346
+ rtk: {
2347
+ ...input.rtk,
2348
+ estimatedOutputTokens: estimateTokens(`${stdout}
2349
+ ${stderr}`)
2350
+ }
2351
+ } : {}
2231
2352
  });
2232
2353
  };
2354
+ resolveDefaultShell = (input) => {
2355
+ const shell = input || process.env.SHELL || userShell() || "/bin/sh";
2356
+ return shell.trim() || "/bin/sh";
2357
+ };
2358
+ resolveRtkCommand = async (rtk, command) => {
2359
+ if (rtk?.enabled !== true || typeof rtk.executable !== "string" || rtk.executable.length === 0) {
2360
+ return { applied: false };
2361
+ }
2362
+ try {
2363
+ const result = await execFileAsync(rtk.executable, ["rewrite", command], {
2364
+ timeout: 5e3,
2365
+ maxBuffer: 1024 * 1024
2366
+ });
2367
+ return rtkRewriteResult(result.stdout, rtk.executable);
2368
+ } catch (cause) {
2369
+ if (isExecError(cause) && typeof cause.stdout === "string") {
2370
+ return rtkRewriteResult(cause.stdout, rtk.executable);
2371
+ }
2372
+ return { applied: false };
2373
+ }
2374
+ };
2375
+ rtkRewriteResult = (stdout, executable) => {
2376
+ const rewrittenCommand = stdout.trim();
2377
+ return rewrittenCommand ? { applied: true, rewrittenCommand, executionCommand: executableRewriteCommand(rewrittenCommand, executable) } : { applied: false };
2378
+ };
2379
+ executableRewriteCommand = (command, executable) => command.replace(/^rtk(?=\s|$)/, shellQuote(executable));
2380
+ readRtkGain = async (rtkExecutable, cwd) => {
2381
+ try {
2382
+ const { stdout } = await execFileAsync(rtkExecutable, ["gain", "--project", "--format", "json"], {
2383
+ cwd,
2384
+ timeout: 5e3,
2385
+ maxBuffer: 5e6
2386
+ });
2387
+ const parsed = JSON.parse(stdout);
2388
+ if (!isRecord3(parsed) || !isRecord3(parsed.summary)) {
2389
+ return void 0;
2390
+ }
2391
+ return { savedTokens: nonNegativeInteger(parsed.summary.total_saved) };
2392
+ } catch {
2393
+ return void 0;
2394
+ }
2395
+ };
2396
+ rtkSavedTokenDelta = async (rtkExecutable, cwd, before) => {
2397
+ if (!before) {
2398
+ return void 0;
2399
+ }
2400
+ const after = await readRtkGain(rtkExecutable, cwd);
2401
+ if (!after) {
2402
+ return void 0;
2403
+ }
2404
+ return Math.max(0, after.savedTokens - before.savedTokens);
2405
+ };
2406
+ withRtkSavings = (rtk, savedTokens) => ({
2407
+ ...rtk,
2408
+ ...rtk.applied && savedTokens !== void 0 ? { estimatedSavedTokens: savedTokens } : {}
2409
+ });
2410
+ nonNegativeInteger = (value) => {
2411
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
2412
+ return 0;
2413
+ }
2414
+ return Math.floor(value);
2415
+ };
2416
+ isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2417
+ shellQuote = (value) => `'${value.replace(/'/g, "'\\''")}'`;
2418
+ shellCommandArgs = (shell, command) => {
2419
+ const name = basename2(shell).toLowerCase();
2420
+ if (name === "csh" || name === "tcsh" || name === "fish") {
2421
+ return ["-c", command];
2422
+ }
2423
+ return ["-lc", command];
2424
+ };
2425
+ userShell = () => {
2426
+ try {
2427
+ return userInfo().shell ?? void 0;
2428
+ } catch {
2429
+ return void 0;
2430
+ }
2431
+ };
2233
2432
  truncate = (value, maxBytes, label) => {
2234
2433
  const bytes = Buffer.byteLength(value);
2235
2434
  if (bytes <= maxBytes) {
@@ -2413,7 +2612,7 @@ var init_tools = __esm({
2413
2612
  });
2414
2613
 
2415
2614
  // packages/core/src/channel/index.ts
2416
- var createSendChannelMessageTool, parseSendChannelMessageInput, isRecord3;
2615
+ var createSendChannelMessageTool, parseSendChannelMessageInput, parseAttachments, optionalString2, isRecord4;
2417
2616
  var init_channel = __esm({
2418
2617
  "packages/core/src/channel/index.ts"() {
2419
2618
  "use strict";
@@ -2426,16 +2625,18 @@ var init_channel = __esm({
2426
2625
  const result = await options.sendCurrent(input);
2427
2626
  return {
2428
2627
  content: [{ type: "text", text: `Channel message sent to ${result.channel}:${result.target}` }],
2429
- details: result
2628
+ details: { ...result, attachments: result.attachments ?? input.attachments?.length ?? 0 }
2430
2629
  };
2431
2630
  }
2432
2631
  });
2433
2632
  parseSendChannelMessageInput = (value) => {
2434
- if (!isRecord3(value)) {
2633
+ if (!isRecord4(value)) {
2435
2634
  throw new Error("SendChannelMessage args must be an object");
2436
2635
  }
2437
- if (typeof value.text !== "string" || value.text.trim().length === 0) {
2438
- throw new Error("SendChannelMessage.text must be a non-empty string");
2636
+ const text = typeof value.text === "string" && value.text.trim().length > 0 ? value.text : void 0;
2637
+ const attachments = parseAttachments(value.attachments);
2638
+ if (!text && attachments.length === 0) {
2639
+ throw new Error("SendChannelMessage requires text or attachments");
2439
2640
  }
2440
2641
  if (value.channel !== void 0 && (typeof value.channel !== "string" || value.channel.trim().length === 0)) {
2441
2642
  throw new Error("SendChannelMessage.channel must be a non-empty string when provided");
@@ -2444,19 +2645,57 @@ var init_channel = __esm({
2444
2645
  throw new Error("SendChannelMessage.target must be current when provided");
2445
2646
  }
2446
2647
  return {
2447
- text: value.text,
2648
+ ...text ? { text } : {},
2649
+ ...attachments.length > 0 ? { attachments } : {},
2448
2650
  ...typeof value.channel === "string" ? { channel: value.channel } : {},
2449
2651
  ...value.target === "current" ? { target: "current" } : {}
2450
2652
  };
2451
2653
  };
2452
- isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2654
+ parseAttachments = (value) => {
2655
+ if (value === void 0) {
2656
+ return [];
2657
+ }
2658
+ if (!Array.isArray(value)) {
2659
+ throw new Error("SendChannelMessage.attachments must be an array");
2660
+ }
2661
+ return value.map((item, index) => {
2662
+ if (!isRecord4(item)) {
2663
+ throw new Error(`SendChannelMessage.attachments.${index} must be an object`);
2664
+ }
2665
+ if (item.type !== "image" && item.type !== "file") {
2666
+ throw new Error(`SendChannelMessage.attachments.${index}.type must be image or file`);
2667
+ }
2668
+ const path = optionalString2(item.path, `SendChannelMessage.attachments.${index}.path`);
2669
+ const url = optionalString2(item.url, `SendChannelMessage.attachments.${index}.url`);
2670
+ if (!path && !url) {
2671
+ throw new Error(`SendChannelMessage.attachments.${index} requires path or url`);
2672
+ }
2673
+ return {
2674
+ type: item.type,
2675
+ ...path ? { path } : {},
2676
+ ...url ? { url } : {},
2677
+ ...optionalString2(item.mimeType, `SendChannelMessage.attachments.${index}.mimeType`) ? { mimeType: optionalString2(item.mimeType, `SendChannelMessage.attachments.${index}.mimeType`) } : {},
2678
+ ...optionalString2(item.caption, `SendChannelMessage.attachments.${index}.caption`) ? { caption: optionalString2(item.caption, `SendChannelMessage.attachments.${index}.caption`) } : {}
2679
+ };
2680
+ });
2681
+ };
2682
+ optionalString2 = (value, name) => {
2683
+ if (value === void 0 || value === "") {
2684
+ return void 0;
2685
+ }
2686
+ if (typeof value !== "string") {
2687
+ throw new Error(`${name} must be a string`);
2688
+ }
2689
+ return value;
2690
+ };
2691
+ isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2453
2692
  }
2454
2693
  });
2455
2694
 
2456
2695
  // packages/core/src/extensions/index.ts
2457
2696
  import { readFile as readFile5 } from "node:fs/promises";
2458
2697
  import { dirname as dirname4, resolve as resolve2 } from "node:path";
2459
- var loadExtensionManifest, parseExtensionManifest, requireString2, requireIdentifier2, requireKind, requireRelativePath, optionalRelativePaths, isRecord4;
2698
+ var loadExtensionManifest, parseExtensionManifest, requireString2, requireIdentifier2, requireKind, requireRelativePath, optionalRelativePaths, isRecord5;
2460
2699
  var init_extensions = __esm({
2461
2700
  "packages/core/src/extensions/index.ts"() {
2462
2701
  "use strict";
@@ -2469,7 +2708,7 @@ var init_extensions = __esm({
2469
2708
  const message = cause instanceof Error ? cause.message : String(cause);
2470
2709
  throw new Error(`Invalid extension manifest JSON at ${manifestPath}: ${message}`);
2471
2710
  }
2472
- if (!isRecord4(value)) {
2711
+ if (!isRecord5(value)) {
2473
2712
  throw new Error(`Extension manifest at ${manifestPath} must be an object`);
2474
2713
  }
2475
2714
  const rootDir = dirname4(resolve2(manifestPath));
@@ -2525,7 +2764,7 @@ var init_extensions = __esm({
2525
2764
  }
2526
2765
  return value.map((item, index) => requireRelativePath(item, `${name}.${index}`, manifestPath));
2527
2766
  };
2528
- isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2767
+ isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2529
2768
  }
2530
2769
  });
2531
2770
 
@@ -2534,7 +2773,7 @@ import { existsSync } from "node:fs";
2534
2773
  import { readdir as readdir4, readFile as readFile6 } from "node:fs/promises";
2535
2774
  import { homedir as homedir2, platform, release } from "node:os";
2536
2775
  import { dirname as dirname5, join as join5, resolve as resolve3 } from "node:path";
2537
- var BASELINE_PROMPT, buildInstructionSnapshot, renderSystemPrompt, section, discoverAgentsSources, projectAgentsPaths, findGitRoot, renderAgentsBlock, renderWorkspaceBlock, renderEnvironmentBlock, renderTimeBlock, isNodeErrorCode;
2776
+ var BASELINE_PROMPT, buildInstructionSnapshot, renderSystemPrompt, section, discoverAgentsSources, projectAgentsPaths, findGitRoot, renderAgentsBlock, renderWorkspaceBlock, renderEnvironmentBlock, renderTimeBlock, isNodeErrorCode2;
2538
2777
  var init_instructions = __esm({
2539
2778
  "packages/core/src/instructions/index.ts"() {
2540
2779
  "use strict";
@@ -2598,7 +2837,7 @@ var init_instructions = __esm({
2598
2837
  content
2599
2838
  });
2600
2839
  } catch (cause) {
2601
- if (!isNodeErrorCode(cause, "ENOENT") && !isNodeErrorCode(cause, "ENOTDIR")) {
2840
+ if (!isNodeErrorCode2(cause, "ENOENT") && !isNodeErrorCode2(cause, "ENOTDIR")) {
2602
2841
  throw cause;
2603
2842
  }
2604
2843
  }
@@ -2661,7 +2900,7 @@ var init_instructions = __esm({
2661
2900
  };
2662
2901
  renderEnvironmentBlock = (env) => [`Platform: ${platform()} ${release()}`, `Shell: ${env.SHELL ?? "unknown"}`].join("\n");
2663
2902
  renderTimeBlock = (timestamp) => [`Session started at: ${new Date(timestamp).toISOString()}`, `Timezone: ${Intl.DateTimeFormat().resolvedOptions().timeZone}`].join("\n");
2664
- isNodeErrorCode = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
2903
+ isNodeErrorCode2 = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
2665
2904
  }
2666
2905
  });
2667
2906
 
@@ -2669,7 +2908,7 @@ var init_instructions = __esm({
2669
2908
  import { appendFile, mkdir as mkdir3, readFile as readFile7, writeFile as writeFile3 } from "node:fs/promises";
2670
2909
  import { homedir as homedir3 } from "node:os";
2671
2910
  import { join as join6 } from "node:path";
2672
- var memoryDate, scorelMemoryPaths, scorelSessionMemoryPaths, buildMemoryContext, renderMemoryHarness, appendDailyEntry, createAppendDailyTool, renderDailyEntry, readSessionMemory, writeSessionMemory, renderSessionMemory, ensureMemoryFiles, ensureFile, readOptional, trimForContext, compactLine, renderList, renderBullets, normalizeMarkdownFile, parseAppendDailyInput, requireString3, optionalStringArray, isRecord5, safeProjectId, isNodeErrorCode2;
2911
+ var memoryDate, scorelMemoryPaths, scorelSessionMemoryPaths, buildMemoryContext, renderMemoryHarness, appendDailyEntry, createAppendDailyTool, renderDailyEntry, readMemoryDreamState, writeMemoryDreamState, readSessionMemory, writeSessionMemory, renderSessionMemory, ensureMemoryFiles, ensureFile, readOptional, trimForContext, compactLine, renderList, renderBullets, normalizeMarkdownFile, parseAppendDailyInput, validateAppendDailyInput, isLowSignalSummary, containsNormalizedDailyEntry, normalizeDailyText, requireString3, optionalStringArray, optionalNumber2, optionalString3, parseLastFailure, isRecord6, safeProjectId, isNodeErrorCode3;
2673
2912
  var init_memory = __esm({
2674
2913
  "packages/core/src/memory/index.ts"() {
2675
2914
  "use strict";
@@ -2739,7 +2978,11 @@ var init_memory = __esm({
2739
2978
  await ensureMemoryFiles(paths);
2740
2979
  const text = options.text.trim();
2741
2980
  if (!text) {
2742
- return { path: paths.todayDailyPath, entry: "", date: paths.today };
2981
+ return { path: paths.todayDailyPath, entry: "", date: paths.today, skippedReason: "empty" };
2982
+ }
2983
+ const existing = await readOptional(paths.todayDailyPath);
2984
+ if (containsNormalizedDailyEntry(existing, text)) {
2985
+ return { path: paths.todayDailyPath, entry: "", date: paths.today, skippedReason: "duplicate" };
2743
2986
  }
2744
2987
  const time = new Date((options.now ?? Date.now)()).toISOString().slice(11, 16);
2745
2988
  const entry = `- ${time} ${text.replace(/\s+/g, " ")}
@@ -2756,6 +2999,7 @@ var init_memory = __esm({
2756
2999
  ].join(" "),
2757
3000
  execute: async (_toolCallId, args) => {
2758
3001
  const input = parseAppendDailyInput(args);
3002
+ validateAppendDailyInput(input);
2759
3003
  const result = await appendDailyEntry({
2760
3004
  projectId: options.projectId,
2761
3005
  homeDir: options.homeDir,
@@ -2766,11 +3010,12 @@ var init_memory = __esm({
2766
3010
  return {
2767
3011
  content: [{
2768
3012
  type: "text",
2769
- text: result.entry ? `Daily appended: ${result.date}` : "Daily append skipped: empty entry"
3013
+ text: result.entry ? `Daily appended: ${result.date}` : `Daily append skipped: ${result.skippedReason ?? "empty"}`
2770
3014
  }],
2771
3015
  details: {
2772
3016
  path: result.path,
2773
- date: result.date
3017
+ date: result.date,
3018
+ skippedReason: result.skippedReason
2774
3019
  }
2775
3020
  };
2776
3021
  }
@@ -2781,10 +3026,45 @@ var init_memory = __esm({
2781
3026
  renderList("Completed", input.completed),
2782
3027
  renderList("Decisions", input.decisions),
2783
3028
  renderList("Follow-ups", input.followUps),
2784
- renderList("Memory candidates", input.memoryCandidates)
3029
+ renderList("Memory candidates", input.memoryCandidates),
3030
+ renderList("Evidence", input.evidence)
2785
3031
  ].filter(Boolean);
2786
3032
  return sections.join(" ");
2787
3033
  };
3034
+ readMemoryDreamState = async (options) => {
3035
+ const paths = scorelMemoryPaths(options);
3036
+ const text = await readOptional(paths.dreamStatePath);
3037
+ if (!text.trim()) return void 0;
3038
+ try {
3039
+ const parsed = JSON.parse(text);
3040
+ if (parsed.projectId !== options.projectId) return void 0;
3041
+ return {
3042
+ projectId: options.projectId,
3043
+ dirty: Boolean(parsed.dirty),
3044
+ running: Boolean(parsed.running),
3045
+ sessionId: optionalString3(parsed.sessionId),
3046
+ clientId: optionalString3(parsed.clientId),
3047
+ lastDailyAppendAt: optionalNumber2(parsed.lastDailyAppendAt),
3048
+ lastDailyPath: optionalString3(parsed.lastDailyPath),
3049
+ scheduledFor: optionalNumber2(parsed.scheduledFor),
3050
+ lastAttemptAt: optionalNumber2(parsed.lastAttemptAt),
3051
+ lastSuccessAt: optionalNumber2(parsed.lastSuccessAt),
3052
+ lastFailure: parseLastFailure(parsed.lastFailure),
3053
+ lastProjectMemoryUpdateAt: optionalNumber2(parsed.lastProjectMemoryUpdateAt),
3054
+ lastRootMemoryUpdateAt: optionalNumber2(parsed.lastRootMemoryUpdateAt)
3055
+ };
3056
+ } catch {
3057
+ return void 0;
3058
+ }
3059
+ };
3060
+ writeMemoryDreamState = async (options) => {
3061
+ const paths = scorelMemoryPaths(options);
3062
+ await mkdir3(paths.projectDir, { recursive: true, mode: 448 });
3063
+ const state = { ...options.state, projectId: options.projectId };
3064
+ await writeFile3(paths.dreamStatePath, `${JSON.stringify(state, null, 2)}
3065
+ `, { encoding: "utf8", mode: 384 });
3066
+ return state;
3067
+ };
2788
3068
  readSessionMemory = async (options) => {
2789
3069
  const paths = scorelSessionMemoryPaths(options);
2790
3070
  return trimForContext(await readOptional(paths.sessionMemoryPath), 12e3, "tail");
@@ -2830,7 +3110,7 @@ var init_memory = __esm({
2830
3110
  try {
2831
3111
  await writeFile3(path, content, { encoding: "utf8", flag: "wx", mode: 384 });
2832
3112
  } catch (cause) {
2833
- if (!isNodeErrorCode2(cause, "EEXIST")) {
3113
+ if (!isNodeErrorCode3(cause, "EEXIST")) {
2834
3114
  throw cause;
2835
3115
  }
2836
3116
  }
@@ -2839,7 +3119,7 @@ var init_memory = __esm({
2839
3119
  try {
2840
3120
  return await readFile7(path, "utf8");
2841
3121
  } catch (cause) {
2842
- if (isNodeErrorCode2(cause, "ENOENT")) {
3122
+ if (isNodeErrorCode3(cause, "ENOENT")) {
2843
3123
  return "";
2844
3124
  }
2845
3125
  throw cause;
@@ -2860,7 +3140,7 @@ var init_memory = __esm({
2860
3140
  normalizeMarkdownFile = (value) => `${value.trimEnd()}
2861
3141
  `;
2862
3142
  parseAppendDailyInput = (value) => {
2863
- if (!isRecord5(value)) {
3143
+ if (!isRecord6(value)) {
2864
3144
  throw new Error("AppendDaily args must be an object");
2865
3145
  }
2866
3146
  const summary = requireString3(value.summary, "summary");
@@ -2869,9 +3149,45 @@ var init_memory = __esm({
2869
3149
  completed: optionalStringArray(value.completed, "completed"),
2870
3150
  decisions: optionalStringArray(value.decisions, "decisions"),
2871
3151
  followUps: optionalStringArray(value.followUps, "followUps"),
2872
- memoryCandidates: optionalStringArray(value.memoryCandidates, "memoryCandidates")
3152
+ memoryCandidates: optionalStringArray(value.memoryCandidates, "memoryCandidates"),
3153
+ evidence: optionalStringArray(value.evidence, "evidence")
2873
3154
  };
2874
3155
  };
3156
+ validateAppendDailyInput = (input) => {
3157
+ const summary = compactLine(input.summary, 500);
3158
+ if (isLowSignalSummary(summary)) {
3159
+ throw new Error("AppendDaily.summary is too generic; include concrete durable progress or a decision");
3160
+ }
3161
+ const details = [
3162
+ ...input.completed ?? [],
3163
+ ...input.decisions ?? [],
3164
+ ...input.followUps ?? [],
3165
+ ...input.memoryCandidates ?? [],
3166
+ ...input.evidence ?? []
3167
+ ].map((value) => compactLine(value, 240)).filter(Boolean);
3168
+ if (details.length === 0) {
3169
+ throw new Error("AppendDaily requires at least one completed item, decision, follow-up, memory candidate, or evidence item");
3170
+ }
3171
+ };
3172
+ isLowSignalSummary = (value) => {
3173
+ const normalized = value.toLowerCase().replace(/\s+/g, "");
3174
+ return [
3175
+ "done",
3176
+ "completed",
3177
+ "finished",
3178
+ "updated",
3179
+ "\u7EE7\u7EED\u63A8\u8FDB",
3180
+ "\u5B8C\u6210\u4EFB\u52A1",
3181
+ "\u5DF2\u5904\u7406",
3182
+ "\u5904\u7406\u5B8C\u6210",
3183
+ "\u505A\u4E86\u4E00\u4E9B\u4FEE\u6539"
3184
+ ].includes(normalized);
3185
+ };
3186
+ containsNormalizedDailyEntry = (daily, text) => {
3187
+ const needle = normalizeDailyText(text);
3188
+ return daily.split("\n").map((line) => line.replace(/^-\s+\d\d:\d\d\s+/, "")).some((line) => normalizeDailyText(line) === needle);
3189
+ };
3190
+ normalizeDailyText = (value) => value.replace(/\s+/g, " ").trim().toLowerCase();
2875
3191
  requireString3 = (value, name) => {
2876
3192
  if (typeof value !== "string" || !value.trim()) {
2877
3193
  throw new Error(`AppendDaily.${name} must be a non-empty string`);
@@ -2887,14 +3203,22 @@ var init_memory = __esm({
2887
3203
  }
2888
3204
  return value.map((item) => item.trim()).filter(Boolean);
2889
3205
  };
2890
- isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3206
+ optionalNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
3207
+ optionalString3 = (value) => typeof value === "string" && value.trim() ? value : void 0;
3208
+ parseLastFailure = (value) => {
3209
+ if (!isRecord6(value)) return void 0;
3210
+ const at = optionalNumber2(value.at);
3211
+ const message = optionalString3(value.message);
3212
+ return at !== void 0 && message ? { at, message } : void 0;
3213
+ };
3214
+ isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2891
3215
  safeProjectId = (projectId) => {
2892
3216
  if (!/^[A-Za-z0-9_-]+$/.test(projectId)) {
2893
3217
  throw new Error("projectId must contain only letters, numbers, underscores, or hyphens");
2894
3218
  }
2895
3219
  return projectId;
2896
3220
  };
2897
- isNodeErrorCode2 = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
3221
+ isNodeErrorCode3 = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
2898
3222
  }
2899
3223
  });
2900
3224
 
@@ -2921,6 +3245,8 @@ var init_pi_ai = __esm({
2921
3245
  for await (const event of stream) {
2922
3246
  if (event.type === "text_delta") {
2923
3247
  yield { type: "text_delta", delta: event.delta };
3248
+ } else if (event.type === "thinking_delta") {
3249
+ yield { type: "thinking_delta", delta: event.delta };
2924
3250
  }
2925
3251
  }
2926
3252
  return fromPiAssistant(await stream.result());
@@ -3111,7 +3437,8 @@ var init_pi_ai = __esm({
3111
3437
  completed: Type.Optional(Type.Array(Type.String())),
3112
3438
  decisions: Type.Optional(Type.Array(Type.String())),
3113
3439
  followUps: Type.Optional(Type.Array(Type.String())),
3114
- memoryCandidates: Type.Optional(Type.Array(Type.String()))
3440
+ memoryCandidates: Type.Optional(Type.Array(Type.String())),
3441
+ evidence: Type.Optional(Type.Array(Type.String()))
3115
3442
  });
3116
3443
  case "Skill":
3117
3444
  return Type.Object({
@@ -3180,7 +3507,7 @@ var init_pi_ai = __esm({
3180
3507
  });
3181
3508
 
3182
3509
  // packages/core/src/runtime/index.ts
3183
- var ScorelRuntime, normalizeAssistantMessage, isAssistantMessage, partialAssistantMessage;
3510
+ var ScorelRuntime, toolResultForContext, normalizeAssistantMessage, isAssistantMessage, partialAssistantMessage;
3184
3511
  var init_runtime = __esm({
3185
3512
  "packages/core/src/runtime/index.ts"() {
3186
3513
  "use strict";
@@ -3250,6 +3577,7 @@ var init_runtime = __esm({
3250
3577
  }
3251
3578
  async *#runProviderTurn(context, systemPrompt, options, signal) {
3252
3579
  let text = "";
3580
+ let thinking = "";
3253
3581
  yield { type: "message_start", role: "assistant" };
3254
3582
  try {
3255
3583
  const stream = this.#provider.streamTurn({
@@ -3265,7 +3593,7 @@ var init_runtime = __esm({
3265
3593
  }
3266
3594
  const next = await stream.next();
3267
3595
  if (next.done) {
3268
- const message = normalizeAssistantMessage(next.value, text, signal.aborted ? "cancelled" : "end_turn");
3596
+ const message = normalizeAssistantMessage(next.value, { thinking, text }, signal.aborted ? "cancelled" : "end_turn");
3269
3597
  if (message) {
3270
3598
  yield { type: "message_end", message };
3271
3599
  }
@@ -3274,16 +3602,19 @@ var init_runtime = __esm({
3274
3602
  if (next.value.type === "text_delta") {
3275
3603
  text += next.value.delta;
3276
3604
  yield next.value;
3605
+ } else if (next.value.type === "thinking_delta") {
3606
+ thinking += next.value.delta;
3607
+ yield next.value;
3277
3608
  }
3278
3609
  }
3279
- const cancelledMessage = partialAssistantMessage(text, "cancelled");
3610
+ const cancelledMessage = partialAssistantMessage({ thinking, text }, "cancelled");
3280
3611
  if (cancelledMessage) {
3281
3612
  yield { type: "message_end", message: cancelledMessage };
3282
3613
  }
3283
3614
  return { stopReason: "cancelled" };
3284
3615
  } catch (cause) {
3285
3616
  const error = cause instanceof Error ? cause : new Error(String(cause));
3286
- const partial = partialAssistantMessage(text, "error");
3617
+ const partial = partialAssistantMessage({ thinking, text }, "error");
3287
3618
  if (partial) {
3288
3619
  yield { type: "message_end", message: partial };
3289
3620
  }
@@ -3325,7 +3656,7 @@ var init_runtime = __esm({
3325
3656
  type: "tool_result",
3326
3657
  toolCallId: toolCall.toolCallId,
3327
3658
  toolName: toolCall.toolName,
3328
- result,
3659
+ result: toolResultForContext(result),
3329
3660
  isError
3330
3661
  };
3331
3662
  return {
@@ -3334,23 +3665,29 @@ var init_runtime = __esm({
3334
3665
  };
3335
3666
  }
3336
3667
  };
3337
- normalizeAssistantMessage = (value, text, fallbackStopReason) => {
3668
+ toolResultForContext = (result) => ({
3669
+ content: result.content
3670
+ });
3671
+ normalizeAssistantMessage = (value, streamed, fallbackStopReason) => {
3338
3672
  if (value) {
3339
3673
  if (!isAssistantMessage(value)) {
3340
3674
  throw new Error(`Provider returned ${value.role} message instead of assistant`);
3341
3675
  }
3342
3676
  return value;
3343
3677
  }
3344
- return partialAssistantMessage(text, fallbackStopReason);
3678
+ return partialAssistantMessage(streamed, fallbackStopReason);
3345
3679
  };
3346
3680
  isAssistantMessage = (message) => message.role === "assistant";
3347
- partialAssistantMessage = (text, stopReason) => {
3348
- if (text.length === 0) {
3681
+ partialAssistantMessage = (streamed, stopReason) => {
3682
+ if (streamed.thinking.length === 0 && streamed.text.length === 0) {
3349
3683
  return void 0;
3350
3684
  }
3351
3685
  return {
3352
3686
  role: "assistant",
3353
- content: [{ type: "text", text }],
3687
+ content: [
3688
+ ...streamed.thinking ? [{ type: "thinking", text: streamed.thinking }] : [],
3689
+ ...streamed.text ? [{ type: "text", text: streamed.text }] : []
3690
+ ],
3354
3691
  stopReason,
3355
3692
  meta: stopReason === "end_turn" ? void 0 : { partial: true }
3356
3693
  };
@@ -3362,7 +3699,7 @@ var init_runtime = __esm({
3362
3699
  import { appendFile as appendFile2, mkdir as mkdir4, readFile as readFile8, writeFile as writeFile4 } from "node:fs/promises";
3363
3700
  import { dirname as dirname6, join as join7 } from "node:path";
3364
3701
  function assertTreeEvent(value) {
3365
- if (!isRecord6(value)) {
3702
+ if (!isRecord7(value)) {
3366
3703
  throw new SessionStoreError("invalid_event", "Event must be an object");
3367
3704
  }
3368
3705
  if (value.type === "session_header") {
@@ -3374,7 +3711,7 @@ function assertTreeEvent(value) {
3374
3711
  if (typeof value.id !== "string" || value.parentId !== null && typeof value.parentId !== "string" || typeof value.seq !== "number" || typeof value.clientId !== "string" || typeof value.ts !== "number") {
3375
3712
  throw new SessionStoreError("invalid_event", "Event is missing required base fields");
3376
3713
  }
3377
- if ((value.type === "user_message" || value.type === "assistant_message" || value.type === "tool_result") && !isRecord6(value.message)) {
3714
+ if ((value.type === "user_message" || value.type === "assistant_message" || value.type === "tool_result") && !isRecord7(value.message)) {
3378
3715
  throw new SessionStoreError("invalid_event", "Message event is missing message payload");
3379
3716
  }
3380
3717
  if (value.type === "session_title_updated" && !isSessionTitleUpdated(value)) {
@@ -3399,7 +3736,7 @@ function assertTreeEvent(value) {
3399
3736
  throw new SessionStoreError("invalid_event", "skill_index_delta is missing delta payload");
3400
3737
  }
3401
3738
  }
3402
- var SessionStoreError, SessionTree, JsonlSession, sessionFilePath, sessionLogFilePath, createSession, loadSession, buildContext, retainedMessagesBeforeCompact, isRetainedContextStart, parseJsonLine, parseHeader, parseSessionEvent, validateSessionMatch, isConversationEvent, isInstructionSnapshot, isHarnessItem, isCompactEvent, isQueueUpdate, isSessionTitleUpdated, isSkillIndexSnapshot, isSkillIndexDelta, isSkillIndexEntry, appendHarnessItemToContext, appendReminderToToolResult, isToolResultWithContent, renderSystemReminder, compactSummaryMessage, cloneMessage, isRecord6;
3739
+ var SessionStoreError, SessionTree, JsonlSession, sessionFilePath, sessionLogFilePath, createSession, loadSession, buildContext, retainedMessagesBeforeCompact, isRetainedContextStart, parseJsonLine, parseHeader, parseSessionEvent, validateSessionMatch, isConversationEvent, isInstructionSnapshot, isHarnessItem, isCompactEvent, isQueueUpdate, isSessionTitleUpdated, isSkillIndexSnapshot, isSkillIndexDelta, isSkillIndexEntry, appendHarnessItemToContext, appendReminderToToolResult, isToolResultWithContent, renderSystemReminder, compactSummaryMessage, cloneMessage, isRecord7;
3403
3740
  var init_session = __esm({
3404
3741
  "packages/core/src/session/index.ts"() {
3405
3742
  "use strict";
@@ -3667,13 +4004,13 @@ var init_session = __esm({
3667
4004
  }
3668
4005
  };
3669
4006
  parseHeader = (value) => {
3670
- if (!isRecord6(value)) {
4007
+ if (!isRecord7(value)) {
3671
4008
  throw new SessionStoreError("invalid_header", "Session header must be an object");
3672
4009
  }
3673
4010
  if (value.version !== 1 || typeof value.sessionId !== "string" || typeof value.deviceId !== "string") {
3674
4011
  throw new SessionStoreError("invalid_header", "Session header is missing required identity fields");
3675
4012
  }
3676
- if (typeof value.createdAt !== "number" || !isRecord6(value.meta)) {
4013
+ if (typeof value.createdAt !== "number" || !isRecord7(value.meta)) {
3677
4014
  throw new SessionStoreError("invalid_header", "Session header is missing createdAt or meta");
3678
4015
  }
3679
4016
  if (typeof value.meta.projectId !== "string" || value.meta.projectId.length === 0) {
@@ -3687,7 +4024,7 @@ var init_session = __esm({
3687
4024
  return value;
3688
4025
  };
3689
4026
  validateSessionMatch = (header, value) => {
3690
- if (!isRecord6(value) || typeof value.sessionId !== "string") {
4027
+ if (!isRecord7(value) || typeof value.sessionId !== "string") {
3691
4028
  throw new SessionStoreError("invalid_header", "Event must be an object with a sessionId");
3692
4029
  }
3693
4030
  if (value.sessionId !== header.sessionId) {
@@ -3696,24 +4033,24 @@ var init_session = __esm({
3696
4033
  };
3697
4034
  isConversationEvent = (event) => event.type === "user_message" || event.type === "assistant_message" || event.type === "tool_result" || event.type === "harness_item" || event.type === "compact";
3698
4035
  isInstructionSnapshot = (value) => {
3699
- if (!isRecord6(value) || value.version !== 1 || typeof value.cwd !== "string" || !Array.isArray(value.sections)) {
4036
+ if (!isRecord7(value) || value.version !== 1 || typeof value.cwd !== "string" || !Array.isArray(value.sections)) {
3700
4037
  return false;
3701
4038
  }
3702
4039
  return value.sections.every(
3703
- (section2) => isRecord6(section2) && typeof section2.kind === "string" && typeof section2.frozenAt === "number" && typeof section2.renderedBlock === "string"
4040
+ (section2) => isRecord7(section2) && typeof section2.kind === "string" && typeof section2.frozenAt === "number" && typeof section2.renderedBlock === "string"
3704
4041
  );
3705
4042
  };
3706
- isHarnessItem = (value) => isRecord6(value) && typeof value.kind === "string" && typeof value.origin === "string" && typeof value.content === "string" && (value.visibility === "display" || value.visibility === "hidden" || value.visibility === "compact");
4043
+ isHarnessItem = (value) => isRecord7(value) && typeof value.kind === "string" && typeof value.origin === "string" && typeof value.content === "string" && (value.visibility === "display" || value.visibility === "hidden" || value.visibility === "compact");
3707
4044
  isCompactEvent = (value) => typeof value.summary === "string" && typeof value.compactedThrough === "string" && typeof value.tokensBefore === "number" && typeof value.tokensAfter === "number" && typeof value.retainedEventCount === "number";
3708
4045
  isQueueUpdate = (value) => (value.queue === "follow_up" || value.queue === "steer") && value.operation === "rewrite" && Array.isArray(value.items) && (value.anchorEventId === null || typeof value.anchorEventId === "string") && value.items.every(
3709
- (item) => isRecord6(item) && typeof item.id === "string" && Array.isArray(item.content) && typeof item.createdAt === "number" && typeof item.updatedAt === "number" && typeof item.clientId === "string"
4046
+ (item) => isRecord7(item) && typeof item.id === "string" && Array.isArray(item.content) && typeof item.createdAt === "number" && typeof item.updatedAt === "number" && typeof item.clientId === "string"
3710
4047
  );
3711
- isSessionTitleUpdated = (value) => typeof value.title === "string" && value.title.length > 0 && (value.source === "model" || value.source === "user") && (value.derivedFrom === void 0 || isRecord6(value.derivedFrom) && typeof value.derivedFrom.eventId === "string" && typeof value.derivedFrom.seq === "number");
4048
+ isSessionTitleUpdated = (value) => typeof value.title === "string" && value.title.length > 0 && (value.source === "model" || value.source === "user") && (value.derivedFrom === void 0 || isRecord7(value.derivedFrom) && typeof value.derivedFrom.eventId === "string" && typeof value.derivedFrom.seq === "number");
3712
4049
  isSkillIndexSnapshot = (value) => (value.anchorEventId === null || typeof value.anchorEventId === "string") && Array.isArray(value.entries) && value.entries.every(isSkillIndexEntry);
3713
4050
  isSkillIndexDelta = (value) => (value.anchorEventId === null || typeof value.anchorEventId === "string") && Array.isArray(value.added) && Array.isArray(value.changed) && Array.isArray(value.removed) && value.added.every(isSkillIndexEntry) && value.changed.every(isSkillIndexEntry) && value.removed.every(
3714
- (item) => isRecord6(item) && typeof item.name === "string" && typeof item.previousPath === "string"
4051
+ (item) => isRecord7(item) && typeof item.name === "string" && typeof item.previousPath === "string"
3715
4052
  );
3716
- isSkillIndexEntry = (value) => isRecord6(value) && typeof value.name === "string" && typeof value.path === "string" && (value.scope === "user" || value.scope === "project" || value.scope === "extension") && typeof value.description === "string" && typeof value.mtimeMs === "number" && typeof value.size === "number" && typeof value.contentHash === "string" && typeof value.priority === "number";
4053
+ isSkillIndexEntry = (value) => isRecord7(value) && typeof value.name === "string" && typeof value.path === "string" && (value.scope === "user" || value.scope === "project" || value.scope === "extension") && typeof value.description === "string" && typeof value.mtimeMs === "number" && typeof value.size === "number" && typeof value.contentHash === "string" && typeof value.priority === "number";
3717
4054
  appendHarnessItemToContext = (messages, event) => {
3718
4055
  const reminder = renderSystemReminder(event.item.content);
3719
4056
  const last = messages.at(-1);
@@ -3750,7 +4087,7 @@ ${reminder}` }]
3750
4087
  }
3751
4088
  return false;
3752
4089
  };
3753
- isToolResultWithContent = (value) => isRecord6(value) && Array.isArray(value.content);
4090
+ isToolResultWithContent = (value) => isRecord7(value) && Array.isArray(value.content);
3754
4091
  renderSystemReminder = (content) => `<system-reminder>
3755
4092
  ${content}
3756
4093
  </system-reminder>`;
@@ -3774,21 +4111,20 @@ ${content}
3774
4111
  cloneMessage = (message) => ({
3775
4112
  ...message,
3776
4113
  content: message.content.map((block) => {
3777
- if (block.type !== "tool_result" || !isRecord6(block.result)) {
4114
+ if (block.type !== "tool_result" || !isRecord7(block.result)) {
3778
4115
  return { ...block };
3779
4116
  }
3780
- const content = Array.isArray(block.result.content) ? { content: block.result.content.map((item) => isRecord6(item) ? { ...item } : item) } : {};
4117
+ const content = Array.isArray(block.result.content) ? { content: block.result.content.map((item) => isRecord7(item) ? { ...item } : item) } : {};
3781
4118
  return {
3782
4119
  ...block,
3783
4120
  result: {
3784
- ...block.result,
3785
- ...content
4121
+ content: content.content ?? []
3786
4122
  }
3787
4123
  };
3788
4124
  }),
3789
4125
  ...message.meta ? { meta: { ...message.meta } } : {}
3790
4126
  });
3791
- isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4127
+ isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3792
4128
  }
3793
4129
  });
3794
4130
 
@@ -3798,7 +4134,7 @@ import { existsSync as existsSync2 } from "node:fs";
3798
4134
  import { readdir as readdir5, readFile as readFile9, stat as stat4 } from "node:fs/promises";
3799
4135
  import { homedir as homedir4 } from "node:os";
3800
4136
  import { dirname as dirname7, join as join8, resolve as resolve4 } from "node:path";
3801
- var scanSkillIndex, diffSkillIndex, hasSkillIndexDelta, renderSkillListing, renderSkillDelta, createSkillTool, projectSkillRoots, readSkillEntry, parseSkillMetadata, firstParagraph, parseSkillArgs, findGitRoot2, isNodeErrorCode3;
4137
+ var scanSkillIndex, diffSkillIndex, hasSkillIndexDelta, renderSkillListing, renderSkillDelta, createSkillTool, projectSkillRoots, readSkillEntry, parseSkillMetadata, firstParagraph, parseSkillArgs, findGitRoot2, isNodeErrorCode4;
3802
4138
  var init_skills = __esm({
3803
4139
  "packages/core/src/skills/index.ts"() {
3804
4140
  "use strict";
@@ -3821,7 +4157,7 @@ var init_skills = __esm({
3821
4157
  try {
3822
4158
  children = await readdir5(root.path);
3823
4159
  } catch (cause) {
3824
- if (isNodeErrorCode3(cause, "ENOENT") || isNodeErrorCode3(cause, "ENOTDIR")) {
4160
+ if (isNodeErrorCode4(cause, "ENOENT") || isNodeErrorCode4(cause, "ENOTDIR")) {
3825
4161
  continue;
3826
4162
  }
3827
4163
  throw cause;
@@ -3934,7 +4270,7 @@ var init_skills = __esm({
3934
4270
  try {
3935
4271
  [fileStat, content] = await Promise.all([stat4(options.skillPath), readFile9(options.skillPath, "utf8")]);
3936
4272
  } catch (cause) {
3937
- if (isNodeErrorCode3(cause, "ENOENT") || isNodeErrorCode3(cause, "ENOTDIR")) {
4273
+ if (isNodeErrorCode4(cause, "ENOENT") || isNodeErrorCode4(cause, "ENOTDIR")) {
3938
4274
  return void 0;
3939
4275
  }
3940
4276
  throw cause;
@@ -4014,7 +4350,7 @@ var init_skills = __esm({
4014
4350
  current = next;
4015
4351
  }
4016
4352
  };
4017
- isNodeErrorCode3 = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
4353
+ isNodeErrorCode4 = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
4018
4354
  }
4019
4355
  });
4020
4356
 
@@ -4360,12 +4696,15 @@ var init_host_client = __esm({
4360
4696
  });
4361
4697
 
4362
4698
  // packages/daemon/src/index.ts
4699
+ import { execFile as execFile2 } from "node:child_process";
4363
4700
  import { existsSync as existsSync3 } from "node:fs";
4364
- import { appendFile as appendFile3, mkdir as mkdir6, readFile as readFile11, readdir as readdir6, rm as rm2, writeFile as writeFile6 } from "node:fs/promises";
4365
- import { dirname as dirname8, join as join10, resolve as resolve5 } from "node:path";
4701
+ import { appendFile as appendFile3, mkdir as mkdir6, readFile as readFile11, readdir as readdir6, rename as rename3, rm as rm2, writeFile as writeFile6 } from "node:fs/promises";
4702
+ import { userInfo as userInfo2 } from "node:os";
4703
+ import { basename as basename3, dirname as dirname8, join as join10, resolve as resolve5 } from "node:path";
4366
4704
  import { pathToFileURL } from "node:url";
4705
+ import { promisify as promisify2 } from "node:util";
4367
4706
  import { WebSocketServer } from "ws";
4368
- var daemonPackageName, SESSION_MEMORY_COMPACT_WAIT_MS, AUTO_COMPACT_RETAINED_EVENTS, localDaemonStateFile, createLocalDaemonState, readLocalDaemonState, removeLocalDaemonState, markDaemonStopped, daemonStateLiveness, defaultIsPidAlive, startRemoteDaemonWebSocketServer, startScorelHostWebSocketServer, closeWebSocketServer, createRealRuntime, ScorelHost, isMissingConfigError, createEmbeddedTransport, isNodeErrorCode4, wireErrorCode, hasContinuousCoverage, countContentBlocks, normalizeContent, inputText, assistantText, messageText, estimateScorelMessagesTokens, estimateTextTokens, compactLine2, parseSessionMemoryJson, stringArray, disabledMemorySettings, runtimeChannelContextFromWire, parseQueuedChannelContext, imBindingKey, defaultBuiltinExtensionsDir, runtimeModuleDir, findBuiltinExtensionsDir, isSteerMessage, stripImCommandPrefix, isRecord7, parseMemoryUpdate, normalizeMarkdownFile2, sanitizeSessionTitle, shortStack, formatDiagnosticLine, formatDiagnosticValue;
4707
+ var daemonPackageName, SESSION_MEMORY_COMPACT_WAIT_MS, AUTO_COMPACT_RETAINED_EVENTS, execFileAsync2, localDaemonStateFile, createLocalDaemonState, readLocalDaemonState, removeLocalDaemonState, markDaemonStopped, daemonStateLiveness, defaultIsPidAlive, startRemoteDaemonWebSocketServer, startScorelHostWebSocketServer, closeWebSocketServer, createRealRuntime, ScorelHost, isMissingConfigError, createEmbeddedTransport, isNodeErrorCode5, wireErrorCode, hasContinuousCoverage, countContentBlocks, normalizeContent, inputText, assistantText, messageText, estimateScorelMessagesTokens, estimateTextTokens, compactLine2, parseSessionMemoryJson, stringArray, disabledMemorySettings, detectRtk, ensureRtkAvailable, emptyRuntimeStats, readRuntimeStats, writeRuntimeStats, parseRuntimeStats, parseRuntimeStatsBuckets, addRtkSavings, addRuntimeStatsBucket, rtkSavingsFromToolResult, nonNegativeInteger2, resolveDefaultShell2, shellCommandArgs2, userShell2, runtimeChannelContextFromWire, parseQueuedChannelContext, imBindingKey, defaultBuiltinExtensionsDir, runtimeModuleDir, findBuiltinExtensionsDir, isSteerMessage, stripImCommandPrefix, isRecord8, parseMemoryUpdate, normalizeMarkdownFile2, sanitizeSessionTitle, shortStack, formatDiagnosticLine, formatDiagnosticValue;
4369
4708
  var init_src4 = __esm({
4370
4709
  "packages/daemon/src/index.ts"() {
4371
4710
  "use strict";
@@ -4380,6 +4719,7 @@ var init_src4 = __esm({
4380
4719
  daemonPackageName = "@scorel/daemon";
4381
4720
  SESSION_MEMORY_COMPACT_WAIT_MS = 5e3;
4382
4721
  AUTO_COMPACT_RETAINED_EVENTS = 8;
4722
+ execFileAsync2 = promisify2(execFile2);
4383
4723
  localDaemonStateFile = (stateDir) => join10(stateDir, "daemon.json");
4384
4724
  createLocalDaemonState = async (options) => {
4385
4725
  const state = {
@@ -4591,9 +4931,10 @@ var init_src4 = __esm({
4591
4931
  }
4592
4932
  server.close((error) => error ? reject(error) : resolve7());
4593
4933
  });
4594
- createRealRuntime = (options) => {
4934
+ createRealRuntime = async (options) => {
4595
4935
  const selection = resolveModelSelection(options.config, options.modelSelection);
4596
4936
  const model = resolvePiAiModel(selection.config);
4937
+ const rtkExecutable = options.rtkExecutable ?? (options.config.runtime.tokenSavingRtk ? (await detectRtk()).executable : void 0);
4597
4938
  const runtime = new ScorelRuntime({
4598
4939
  provider: createPiAiProvider({
4599
4940
  model,
@@ -4601,7 +4942,16 @@ var init_src4 = __esm({
4601
4942
  })
4602
4943
  });
4603
4944
  if (options.includeTools !== false) {
4604
- for (const tool of createCodingTools({ cwd: options.cwd, contextWindow: model.contextWindow })) {
4945
+ for (const tool of createCodingTools({
4946
+ cwd: options.cwd,
4947
+ contextWindow: model.contextWindow,
4948
+ tokenSaving: {
4949
+ rtk: {
4950
+ enabled: options.config.runtime.tokenSavingRtk,
4951
+ executable: rtkExecutable
4952
+ }
4953
+ }
4954
+ })) {
4605
4955
  runtime.registerTool(tool);
4606
4956
  }
4607
4957
  }
@@ -4619,6 +4969,9 @@ var init_src4 = __esm({
4619
4969
  #loadConfigProfile;
4620
4970
  #createRuntime;
4621
4971
  #memoryHomeDir;
4972
+ #onSessionListChanged;
4973
+ #idleShutdownMs;
4974
+ #onIdleShutdown;
4622
4975
  #now;
4623
4976
  #createId;
4624
4977
  #sessions = /* @__PURE__ */ new Map();
@@ -4630,6 +4983,8 @@ var init_src4 = __esm({
4630
4983
  #imExtensions = /* @__PURE__ */ new Map();
4631
4984
  #imBindings = /* @__PURE__ */ new Map();
4632
4985
  #registry;
4986
+ #runtimeStatsQueue = Promise.resolve();
4987
+ #idleShutdownTimer;
4633
4988
  #started = false;
4634
4989
  constructor(options) {
4635
4990
  this.#sessionsDir = options.sessionsDir;
@@ -4643,6 +4998,9 @@ var init_src4 = __esm({
4643
4998
  this.#loadConfigProfile = options.loadConfigProfile;
4644
4999
  this.#createRuntime = options.createRuntime;
4645
5000
  this.#memoryHomeDir = options.memoryHomeDir;
5001
+ this.#onSessionListChanged = options.onSessionListChanged;
5002
+ this.#idleShutdownMs = options.idleShutdownMs;
5003
+ this.#onIdleShutdown = options.onIdleShutdown;
4646
5004
  this.#now = options.now ?? Date.now;
4647
5005
  this.#createId = options.createId ?? (() => crypto.randomUUID());
4648
5006
  this.#registry = new ProjectRegistry({
@@ -4657,8 +5015,10 @@ var init_src4 = __esm({
4657
5015
  await mkdir6(this.#scorelHomeDir, { recursive: true });
4658
5016
  await this.#loadImBindings();
4659
5017
  await this.#startEnabledImExtensions();
5018
+ this.#scheduleIdleShutdownCheck();
4660
5019
  }
4661
5020
  async shutdown() {
5021
+ this.#clearIdleShutdownTimer();
4662
5022
  for (const schedule of this.#memoryDreams.values()) {
4663
5023
  if (schedule.timer) {
4664
5024
  clearTimeout(schedule.timer);
@@ -4673,9 +5033,11 @@ var init_src4 = __esm({
4673
5033
  this.#assertStarted();
4674
5034
  await this.#stopImExtensions();
4675
5035
  await this.#startEnabledImExtensions();
5036
+ this.#scheduleIdleShutdownCheck();
4676
5037
  }
4677
5038
  connect(connection, sessionId) {
4678
5039
  this.#assertStarted();
5040
+ this.#clearIdleShutdownTimer();
4679
5041
  connection.sessionId = sessionId;
4680
5042
  this.#connections.add(connection);
4681
5043
  if (sessionId) {
@@ -4700,6 +5062,7 @@ var init_src4 = __esm({
4700
5062
  });
4701
5063
  }
4702
5064
  this.#connections.delete(connection);
5065
+ this.#scheduleIdleShutdownCheck();
4703
5066
  }
4704
5067
  releaseSessionEventBuffer(sessionId) {
4705
5068
  this.#events.delete(sessionId);
@@ -4720,6 +5083,8 @@ var init_src4 = __esm({
4720
5083
  return;
4721
5084
  }
4722
5085
  throw cause;
5086
+ } finally {
5087
+ this.#scheduleIdleShutdownCheck();
4723
5088
  }
4724
5089
  }
4725
5090
  async listDirectories(path) {
@@ -4819,18 +5184,34 @@ var init_src4 = __esm({
4819
5184
  this.#respond(connection, message, await this.#handleUpsertModelProfile(message));
4820
5185
  break;
4821
5186
  }
5187
+ case "remove_model_provider": {
5188
+ this.#respond(connection, message, await this.#handleRemoveModelProvider(message));
5189
+ break;
5190
+ }
4822
5191
  case "fetch_provider_models": {
4823
5192
  this.#respond(connection, message, { models: await this.#fetchProviderModels(message.projectId, message.providerId) });
4824
5193
  break;
4825
5194
  }
4826
5195
  case "get_memory_settings": {
4827
- this.#respond(connection, message, { memory: await this.#memorySettingsForProject(message.projectId) });
5196
+ this.#respond(connection, message, { memory: await this.#memorySettings(message.projectId) });
5197
+ break;
5198
+ }
5199
+ case "get_memory_status": {
5200
+ this.#respond(connection, message, { status: await this.#memoryStatusForProject(message.projectId) });
4828
5201
  break;
4829
5202
  }
4830
5203
  case "upsert_memory_settings": {
4831
5204
  this.#respond(connection, message, { memory: await this.#handleUpsertMemorySettings(message) });
4832
5205
  break;
4833
5206
  }
5207
+ case "get_runtime_settings": {
5208
+ this.#respond(connection, message, { runtime: await this.#runtimeSettings(message.projectId) });
5209
+ break;
5210
+ }
5211
+ case "upsert_runtime_settings": {
5212
+ this.#respond(connection, message, { runtime: await this.#handleUpsertRuntimeSettings(message) });
5213
+ break;
5214
+ }
4834
5215
  case "get_extension_settings": {
4835
5216
  this.#respond(connection, message, { extension: await this.#extensionSettings(message.extensionId) });
4836
5217
  break;
@@ -4859,6 +5240,39 @@ var init_src4 = __esm({
4859
5240
  break;
4860
5241
  }
4861
5242
  }
5243
+ #scheduleIdleShutdownCheck() {
5244
+ this.#clearIdleShutdownTimer();
5245
+ if (!this.#shouldIdleShutdown()) {
5246
+ return;
5247
+ }
5248
+ this.#idleShutdownTimer = setTimeout(() => {
5249
+ this.#idleShutdownTimer = void 0;
5250
+ if (this.#shouldIdleShutdown()) {
5251
+ this.#onIdleShutdown?.();
5252
+ }
5253
+ }, this.#idleShutdownMs);
5254
+ }
5255
+ #clearIdleShutdownTimer() {
5256
+ if (!this.#idleShutdownTimer) {
5257
+ return;
5258
+ }
5259
+ clearTimeout(this.#idleShutdownTimer);
5260
+ this.#idleShutdownTimer = void 0;
5261
+ }
5262
+ #shouldIdleShutdown() {
5263
+ return this.#started && this.#idleShutdownMs !== void 0 && this.#idleShutdownMs > 0 && this.#connections.size === 0 && this.#imExtensions.size === 0 && !this.#hasActiveWork();
5264
+ }
5265
+ #hasActiveWork() {
5266
+ for (const lane of this.#sessions.values()) {
5267
+ if (lane.runtime.running) {
5268
+ return true;
5269
+ }
5270
+ if (lane.session.tree.controlState.queues.follow_up.length > 0 || lane.session.tree.controlState.queues.steer.length > 0) {
5271
+ return true;
5272
+ }
5273
+ }
5274
+ return false;
5275
+ }
4862
5276
  async #handleCreateSession(connection, request) {
4863
5277
  const sessionId = request.sessionId ?? asSessionId(`ses_${this.#createId()}`);
4864
5278
  const project = await this.#resolveProject(sessionId, request.meta.projectId);
@@ -4875,7 +5289,7 @@ var init_src4 = __esm({
4875
5289
  try {
4876
5290
  lane = await this.#createLane(sessionId, request.meta, project);
4877
5291
  } catch (cause) {
4878
- if (!request.sessionId || !isNodeErrorCode4(cause, "EEXIST")) {
5292
+ if (!request.sessionId || !isNodeErrorCode5(cause, "EEXIST")) {
4879
5293
  throw cause;
4880
5294
  }
4881
5295
  lane = await this.#getLane(sessionId);
@@ -4892,6 +5306,9 @@ var init_src4 = __esm({
4892
5306
  workDir: lane.project.workDir,
4893
5307
  model: request.meta.model
4894
5308
  });
5309
+ if (created) {
5310
+ this.#onSessionListChanged?.({ projectId: lane.project.projectId, sessionId });
5311
+ }
4895
5312
  this.#respond(connection, request, { sessionId });
4896
5313
  }
4897
5314
  async #handleLoadSession(connection, request) {
@@ -5332,10 +5749,21 @@ var init_src4 = __esm({
5332
5749
  delta: rawEvent.delta
5333
5750
  });
5334
5751
  break;
5752
+ case "thinking_delta":
5753
+ this.#broadcastTransient(lane.session.header.sessionId, {
5754
+ type: "thinking_delta",
5755
+ sessionId: lane.session.header.sessionId,
5756
+ clientId,
5757
+ ts: this.#now(),
5758
+ eventId: state.assistantEventId,
5759
+ delta: rawEvent.delta
5760
+ });
5761
+ break;
5335
5762
  case "message_end": {
5336
5763
  await this.#appendDiagnostic(lane.session.header.sessionId, "assistant_result", {
5337
5764
  clientId,
5338
5765
  stopReason: rawEvent.message.stopReason,
5766
+ thinkingBlocks: countContentBlocks(rawEvent.message, "thinking"),
5339
5767
  textBlocks: countContentBlocks(rawEvent.message, "text"),
5340
5768
  toolCalls: countContentBlocks(rawEvent.message, "tool_call"),
5341
5769
  inputTokens: rawEvent.message.usage?.inputTokens,
@@ -5380,6 +5808,18 @@ var init_src4 = __esm({
5380
5808
  ]
5381
5809
  }
5382
5810
  });
5811
+ const rtkSavings = rtkSavingsFromToolResult(rawEvent.result);
5812
+ if (rtkSavings) {
5813
+ await this.#recordRtkSavings({
5814
+ projectId: lane.project.projectId,
5815
+ sessionId: lane.session.header.sessionId,
5816
+ savings: rtkSavings
5817
+ }).catch(
5818
+ (cause) => this.#appendDiagnostic(lane.session.header.sessionId, "runtime_stats_update_failed", {
5819
+ message: cause instanceof Error ? cause.message : String(cause)
5820
+ })
5821
+ );
5822
+ }
5383
5823
  state.parentId = toolResultId;
5384
5824
  break;
5385
5825
  }
@@ -5531,12 +5971,21 @@ var init_src4 = __esm({
5531
5971
  homeDir: this.#memoryHomeDir,
5532
5972
  now: this.#now,
5533
5973
  onAppend: async (result) => {
5534
- await this.#appendDiagnostic(lane.session.header.sessionId, "memory_daily_appended", {
5535
- clientId,
5536
- path: result.path,
5537
- date: result.date
5538
- });
5539
- await this.#scheduleMemoryDream(lane, clientId);
5974
+ if (result.entry) {
5975
+ await this.#markMemoryDreamDirty(lane, clientId, result.path);
5976
+ }
5977
+ try {
5978
+ await this.#appendDiagnostic(lane.session.header.sessionId, "memory_daily_appended", {
5979
+ clientId,
5980
+ path: result.path,
5981
+ date: result.date,
5982
+ skippedReason: result.skippedReason
5983
+ });
5984
+ } catch {
5985
+ }
5986
+ if (result.entry) {
5987
+ await this.#scheduleMemoryDream(lane, clientId);
5988
+ }
5540
5989
  }
5541
5990
  })
5542
5991
  );
@@ -5767,7 +6216,10 @@ var init_src4 = __esm({
5767
6216
  ...context.senderDisplayName ? [`sender_display_name: ${context.senderDisplayName}`] : [],
5768
6217
  ...context.mentionedBot !== void 0 ? [`mentioned_bot: ${context.mentionedBot}`] : [],
5769
6218
  "",
5770
- "Use SendChannelMessage to reply to the current conversation when needed."
6219
+ "Use SendChannelMessage to reply to the current conversation when needed.",
6220
+ "In IM, send a short acknowledgement before long work so the user does not think the bot is stuck.",
6221
+ "For longer tasks, send concise progress updates instead of waiting until every tool call has finished.",
6222
+ "Keep replies conversational and avoid exposing internal tool names unless they help the user."
5771
6223
  ];
5772
6224
  return this.#appendPersistent(lane, {
5773
6225
  type: "harness_item",
@@ -5808,6 +6260,22 @@ var init_src4 = __esm({
5808
6260
  lastActivityAt: this.#now()
5809
6261
  };
5810
6262
  const delayMs = Math.max(0, memory.dreamIdleMinutes) * 60 * 1e3;
6263
+ const scheduledFor = this.#now() + delayMs;
6264
+ const currentState = await readMemoryDreamState({
6265
+ projectId: lane.project.projectId,
6266
+ homeDir: this.#memoryHomeDir,
6267
+ now: this.#now
6268
+ });
6269
+ await this.#writeMemoryDreamState(lane.project.projectId, {
6270
+ ...currentState ?? {},
6271
+ projectId: String(lane.project.projectId),
6272
+ dirty: true,
6273
+ running: schedule.running,
6274
+ sessionId: String(lane.session.header.sessionId),
6275
+ clientId: String(clientId),
6276
+ lastDailyAppendAt: currentState?.lastDailyAppendAt ?? schedule.lastActivityAt,
6277
+ scheduledFor
6278
+ });
5811
6279
  schedule.timer = setTimeout(() => {
5812
6280
  void this.#runIdleMemoryDream(projectId).catch((cause) => {
5813
6281
  const error = cause instanceof Error ? cause : new Error(String(cause));
@@ -5826,6 +6294,26 @@ var init_src4 = __esm({
5826
6294
  idleMinutes: memory.dreamIdleMinutes
5827
6295
  });
5828
6296
  }
6297
+ async #markMemoryDreamDirty(lane, clientId, dailyPath) {
6298
+ const current = await readMemoryDreamState({
6299
+ projectId: lane.project.projectId,
6300
+ homeDir: this.#memoryHomeDir,
6301
+ now: this.#now
6302
+ });
6303
+ await this.#writeMemoryDreamState(lane.project.projectId, {
6304
+ projectId: String(lane.project.projectId),
6305
+ dirty: true,
6306
+ running: current?.running ?? false,
6307
+ sessionId: String(lane.session.header.sessionId),
6308
+ clientId: String(clientId),
6309
+ lastDailyAppendAt: this.#now(),
6310
+ lastDailyPath: dailyPath,
6311
+ lastFailure: current?.lastFailure,
6312
+ lastSuccessAt: current?.lastSuccessAt,
6313
+ lastProjectMemoryUpdateAt: current?.lastProjectMemoryUpdateAt,
6314
+ lastRootMemoryUpdateAt: current?.lastRootMemoryUpdateAt
6315
+ });
6316
+ }
5829
6317
  async #runIdleMemoryDream(projectId) {
5830
6318
  const schedule = this.#memoryDreams.get(projectId);
5831
6319
  if (!schedule || schedule.running) {
@@ -5834,10 +6322,28 @@ var init_src4 = __esm({
5834
6322
  schedule.running = true;
5835
6323
  schedule.timer = void 0;
5836
6324
  this.#memoryDreams.set(projectId, schedule);
6325
+ const beforeRun = await readMemoryDreamState({
6326
+ projectId,
6327
+ homeDir: this.#memoryHomeDir,
6328
+ now: this.#now
6329
+ });
6330
+ await this.#writeMemoryDreamState(projectId, {
6331
+ ...beforeRun ?? { projectId: String(projectId), dirty: true },
6332
+ projectId: String(projectId),
6333
+ running: true,
6334
+ lastAttemptAt: this.#now()
6335
+ });
5837
6336
  try {
5838
6337
  const lane = await this.#getLane(schedule.sessionId);
5839
6338
  const memory = await this.#safeMemorySettingsForRuntime(lane, schedule.clientId);
5840
6339
  if (!memory.enabled || !memory.autoDream) {
6340
+ await this.#writeMemoryDreamState(projectId, {
6341
+ ...beforeRun ?? { projectId: String(projectId) },
6342
+ projectId: String(projectId),
6343
+ dirty: false,
6344
+ running: false,
6345
+ lastFailure: { at: this.#now(), message: "Memory dream disabled" }
6346
+ });
5841
6347
  return;
5842
6348
  }
5843
6349
  const generated = await this.#generateMemoryUpdate(lane, memory);
@@ -5860,10 +6366,82 @@ var init_src4 = __esm({
5860
6366
  path: paths.rootMemoryPath
5861
6367
  });
5862
6368
  }
6369
+ const now = this.#now();
6370
+ const latestState = await readMemoryDreamState({ projectId, homeDir: this.#memoryHomeDir, now: this.#now });
6371
+ const hasNewDailyDuringRun = latestState?.lastDailyAppendAt !== void 0 && beforeRun?.lastDailyAppendAt !== void 0 && latestState.lastDailyAppendAt > beforeRun.lastDailyAppendAt;
6372
+ await this.#writeMemoryDreamState(projectId, {
6373
+ ...latestState ?? { projectId: String(projectId) },
6374
+ projectId: String(projectId),
6375
+ dirty: hasNewDailyDuringRun,
6376
+ running: false,
6377
+ ...hasNewDailyDuringRun ? {} : { scheduledFor: void 0 },
6378
+ lastSuccessAt: now,
6379
+ lastFailure: void 0,
6380
+ ...generated?.projectMemory?.trim() ? { lastProjectMemoryUpdateAt: now } : {},
6381
+ ...memory.promoteRoot && generated?.rootMemory?.trim() ? { lastRootMemoryUpdateAt: now } : {}
6382
+ });
6383
+ if (hasNewDailyDuringRun) {
6384
+ await this.#scheduleMemoryDream(lane, schedule.clientId);
6385
+ }
6386
+ } catch (cause) {
6387
+ const message = cause instanceof Error ? cause.message : String(cause);
6388
+ await this.#writeMemoryDreamState(projectId, {
6389
+ ...await readMemoryDreamState({ projectId, homeDir: this.#memoryHomeDir, now: this.#now }) ?? { projectId: String(projectId) },
6390
+ projectId: String(projectId),
6391
+ dirty: true,
6392
+ running: false,
6393
+ lastFailure: { at: this.#now(), message }
6394
+ });
6395
+ throw cause;
5863
6396
  } finally {
5864
6397
  this.#memoryDreams.delete(projectId);
5865
6398
  }
5866
6399
  }
6400
+ async #memoryStatusForProject(projectId) {
6401
+ const state = await readMemoryDreamState({
6402
+ projectId,
6403
+ homeDir: this.#memoryHomeDir,
6404
+ now: this.#now
6405
+ });
6406
+ await this.#recoverMemoryDream(projectId, state);
6407
+ const recovered = await readMemoryDreamState({
6408
+ projectId,
6409
+ homeDir: this.#memoryHomeDir,
6410
+ now: this.#now
6411
+ });
6412
+ return {
6413
+ projectId,
6414
+ dirty: recovered?.dirty ?? false,
6415
+ running: recovered?.running ?? false,
6416
+ ...recovered?.lastDailyAppendAt !== void 0 ? { lastDailyAppendAt: recovered.lastDailyAppendAt } : {},
6417
+ ...recovered?.lastDailyPath ? { lastDailyPath: recovered.lastDailyPath } : {},
6418
+ ...recovered?.scheduledFor !== void 0 ? { scheduledFor: recovered.scheduledFor } : {},
6419
+ ...recovered?.lastAttemptAt !== void 0 ? { lastAttemptAt: recovered.lastAttemptAt } : {},
6420
+ ...recovered?.lastSuccessAt !== void 0 ? { lastSuccessAt: recovered.lastSuccessAt } : {},
6421
+ ...recovered?.lastFailure ? { lastFailure: recovered.lastFailure } : {},
6422
+ ...recovered?.lastProjectMemoryUpdateAt !== void 0 ? { lastProjectMemoryUpdateAt: recovered.lastProjectMemoryUpdateAt } : {},
6423
+ ...recovered?.lastRootMemoryUpdateAt !== void 0 ? { lastRootMemoryUpdateAt: recovered.lastRootMemoryUpdateAt } : {}
6424
+ };
6425
+ }
6426
+ async #recoverMemoryDream(projectId, state) {
6427
+ if (!state?.dirty || this.#memoryDreams.has(projectId)) {
6428
+ return;
6429
+ }
6430
+ const lane = [...this.#sessions.values()].find((candidate) => candidate.project.projectId === projectId);
6431
+ if (!lane) {
6432
+ return;
6433
+ }
6434
+ const clientId = state.clientId ? asClientId(state.clientId) : asClientId("client_memory_recovery");
6435
+ await this.#scheduleMemoryDream(lane, clientId);
6436
+ }
6437
+ async #writeMemoryDreamState(projectId, state) {
6438
+ await writeMemoryDreamState({
6439
+ projectId,
6440
+ homeDir: this.#memoryHomeDir,
6441
+ now: this.#now,
6442
+ state
6443
+ });
6444
+ }
5867
6445
  async #generateMemoryUpdate(lane, memory) {
5868
6446
  const selectedModel = await this.#selectedModelFromMeta(
5869
6447
  { projectId: lane.project.projectId, modelSelection: { role: "auxiliary" } },
@@ -6062,7 +6640,7 @@ var init_src4 = __esm({
6062
6640
  await this.#getLane(sessionId);
6063
6641
  return true;
6064
6642
  } catch (cause) {
6065
- if (isNodeErrorCode4(cause, "ENOENT")) {
6643
+ if (isNodeErrorCode5(cause, "ENOENT")) {
6066
6644
  return false;
6067
6645
  }
6068
6646
  throw cause;
@@ -6130,13 +6708,17 @@ var init_src4 = __esm({
6130
6708
  if (!extension) {
6131
6709
  throw new Error(`channel_adapter_unavailable: ${current.extensionId}`);
6132
6710
  }
6133
- await extension.adapter.sendMessage(current.target, { text: input.text });
6711
+ await extension.adapter.sendMessage(current.target, {
6712
+ ...input.text ? { text: input.text } : {},
6713
+ ...input.attachments ? { attachments: input.attachments } : {}
6714
+ });
6134
6715
  await this.#appendDiagnostic(lane.session.header.sessionId, "channel_message_sent", {
6135
6716
  extensionId: current.extensionId,
6136
6717
  channel: current.channel,
6137
- externalConversationId: current.externalConversationId
6718
+ externalConversationId: current.externalConversationId,
6719
+ attachments: input.attachments?.length ?? 0
6138
6720
  });
6139
- return { channel: current.channel, target: "current" };
6721
+ return { channel: current.channel, target: "current", attachments: input.attachments?.length ?? 0 };
6140
6722
  }
6141
6723
  })
6142
6724
  );
@@ -6216,7 +6798,7 @@ var init_src4 = __esm({
6216
6798
  try {
6217
6799
  children = await readdir6(root);
6218
6800
  } catch (cause) {
6219
- if (isNodeErrorCode4(cause, "ENOENT") || isNodeErrorCode4(cause, "ENOTDIR")) {
6801
+ if (isNodeErrorCode5(cause, "ENOENT") || isNodeErrorCode5(cause, "ENOTDIR")) {
6220
6802
  continue;
6221
6803
  }
6222
6804
  throw cause;
@@ -6305,6 +6887,7 @@ var init_src4 = __esm({
6305
6887
  externalConversationId,
6306
6888
  projectId: project.projectId
6307
6889
  });
6890
+ this.#onSessionListChanged?.({ projectId: project.projectId, sessionId });
6308
6891
  return binding;
6309
6892
  }
6310
6893
  async #ensureDefaultWorkspaceProject() {
@@ -6325,7 +6908,7 @@ var init_src4 = __esm({
6325
6908
  this.#imBindings.set(imBindingKey(binding.extensionId, binding.externalConversationId), binding);
6326
6909
  }
6327
6910
  } catch (cause) {
6328
- if (!isNodeErrorCode4(cause, "ENOENT")) {
6911
+ if (!isNodeErrorCode5(cause, "ENOENT")) {
6329
6912
  throw cause;
6330
6913
  }
6331
6914
  }
@@ -6339,9 +6922,13 @@ var init_src4 = __esm({
6339
6922
  #imBindingsPath() {
6340
6923
  return join10(this.#scorelHomeDir, "channels", "im-bindings.json");
6341
6924
  }
6342
- async #loadUserConfigProfile() {
6925
+ async #loadUserConfigProfile(options = {}) {
6343
6926
  try {
6344
- return await loadScorelConfigProfile({ cwd: this.#userHomeDir, homeDir: this.#userHomeDir });
6927
+ return await loadScorelConfigProfile({
6928
+ cwd: this.#userHomeDir,
6929
+ scorelHomeDir: this.#scorelHomeDir,
6930
+ includeSecrets: options.includeSecrets ?? false
6931
+ });
6345
6932
  } catch (cause) {
6346
6933
  if (isMissingConfigError(cause)) {
6347
6934
  return void 0;
@@ -6349,16 +6936,24 @@ var init_src4 = __esm({
6349
6936
  throw cause;
6350
6937
  }
6351
6938
  }
6939
+ #configWriteTarget() {
6940
+ return {
6941
+ configDir: this.#scorelHomeDir,
6942
+ configPath: join10(this.#scorelHomeDir, "config.toml"),
6943
+ workDir: this.#userHomeDir
6944
+ };
6945
+ }
6352
6946
  async #listModels(projectId) {
6353
6947
  let config;
6354
6948
  try {
6355
- config = await this.#configProfileForProject(projectId);
6949
+ config = projectId ? await this.#configProfileForProject(projectId) : await this.#loadUserConfigProfile();
6356
6950
  } catch (cause) {
6357
6951
  if (!isMissingConfigError(cause)) {
6358
6952
  throw cause;
6359
6953
  }
6360
6954
  config = void 0;
6361
6955
  }
6956
+ config ??= projectId ? void 0 : this.#modelProfile;
6362
6957
  if (!config) {
6363
6958
  return {
6364
6959
  providers: [],
@@ -6381,19 +6976,18 @@ var init_src4 = __esm({
6381
6976
  };
6382
6977
  }
6383
6978
  async #handleUpsertModelProfile(request) {
6384
- const project = await this.#registry.require(request.projectId);
6385
- const configPath = join10(project.workDir, ".scorel", "config.toml");
6979
+ const target = this.#configWriteTarget();
6386
6980
  let existingConfigText;
6387
6981
  try {
6388
- existingConfigText = await readFile11(configPath, "utf8");
6982
+ existingConfigText = await readFile11(target.configPath, "utf8");
6389
6983
  } catch (cause) {
6390
- if (!isNodeErrorCode4(cause, "ENOENT")) {
6984
+ if (!isNodeErrorCode5(cause, "ENOENT")) {
6391
6985
  throw cause;
6392
6986
  }
6393
6987
  }
6394
- await mkdir6(join10(project.workDir, ".scorel"), { recursive: true });
6988
+ await mkdir6(target.configDir, { recursive: true });
6395
6989
  await writeFile6(
6396
- configPath,
6990
+ target.configPath,
6397
6991
  renderModelProfileConfig({
6398
6992
  providerId: request.providerId,
6399
6993
  providerType: request.providerType,
@@ -6420,15 +7014,41 @@ var init_src4 = __esm({
6420
7014
  "utf8"
6421
7015
  );
6422
7016
  await this.#appendHostDiagnostic("model_profile_upserted", {
6423
- projectId: project.projectId,
6424
- workDir: project.workDir,
7017
+ ...request.projectId ? { ignoredProjectId: request.projectId } : {},
7018
+ scope: "device",
7019
+ workDir: target.workDir,
6425
7020
  providerId: request.providerId,
6426
7021
  modelId: request.modelId
6427
7022
  });
6428
- return this.#listModels(project.projectId);
7023
+ return this.#listModels();
7024
+ }
7025
+ async #handleRemoveModelProvider(request) {
7026
+ const target = this.#configWriteTarget();
7027
+ let existingConfigText;
7028
+ try {
7029
+ existingConfigText = await readFile11(target.configPath, "utf8");
7030
+ } catch (cause) {
7031
+ if (!isNodeErrorCode5(cause, "ENOENT")) {
7032
+ throw cause;
7033
+ }
7034
+ }
7035
+ await mkdir6(target.configDir, { recursive: true });
7036
+ await writeFile6(
7037
+ target.configPath,
7038
+ renderModelProfileConfig({
7039
+ removeProviderId: request.providerId,
7040
+ existingConfigText
7041
+ }),
7042
+ "utf8"
7043
+ );
7044
+ const profile = await this.#listModels();
7045
+ return { ...profile, removed: true };
6429
7046
  }
6430
7047
  async #memorySettingsForProject(projectId) {
6431
- const config = await this.#configProfileForProject(projectId).catch((cause) => {
7048
+ return this.#memorySettings(projectId);
7049
+ }
7050
+ async #memorySettings(projectId) {
7051
+ const config = await (projectId ? this.#configProfileForProject(projectId) : this.#loadUserConfigProfile()).catch((cause) => {
6432
7052
  if (isMissingConfigError(cause)) {
6433
7053
  return void 0;
6434
7054
  }
@@ -6450,19 +7070,18 @@ var init_src4 = __esm({
6450
7070
  }
6451
7071
  }
6452
7072
  async #handleUpsertMemorySettings(request) {
6453
- const project = await this.#registry.require(request.projectId);
6454
- const configPath = join10(project.workDir, ".scorel", "config.toml");
7073
+ const target = this.#configWriteTarget();
6455
7074
  let existingConfigText;
6456
7075
  try {
6457
- existingConfigText = await readFile11(configPath, "utf8");
7076
+ existingConfigText = await readFile11(target.configPath, "utf8");
6458
7077
  } catch (cause) {
6459
- if (!isNodeErrorCode4(cause, "ENOENT")) {
7078
+ if (!isNodeErrorCode5(cause, "ENOENT")) {
6460
7079
  throw cause;
6461
7080
  }
6462
7081
  }
6463
- await mkdir6(join10(project.workDir, ".scorel"), { recursive: true });
7082
+ await mkdir6(target.configDir, { recursive: true });
6464
7083
  await writeFile6(
6465
- configPath,
7084
+ target.configPath,
6466
7085
  renderMemoryConfig({
6467
7086
  enabled: request.enabled,
6468
7087
  daily: request.daily,
@@ -6476,10 +7095,66 @@ var init_src4 = __esm({
6476
7095
  "utf8"
6477
7096
  );
6478
7097
  await this.#appendHostDiagnostic("memory_settings_upserted", {
6479
- projectId: project.projectId,
6480
- workDir: project.workDir
7098
+ ...request.projectId ? { ignoredProjectId: request.projectId } : {},
7099
+ scope: "device",
7100
+ workDir: target.workDir
7101
+ });
7102
+ return this.#memorySettings();
7103
+ }
7104
+ async #runtimeSettingsForProject(projectId, installStatus) {
7105
+ return this.#runtimeSettings(projectId, installStatus);
7106
+ }
7107
+ async #runtimeSettings(projectId, installStatus) {
7108
+ const config = await (projectId ? this.#configProfileForProject(projectId) : this.#loadUserConfigProfile()).catch((cause) => {
7109
+ if (isMissingConfigError(cause)) {
7110
+ return void 0;
7111
+ }
7112
+ throw cause;
7113
+ });
7114
+ const detected = await detectRtk();
7115
+ const savings = await readRuntimeStats(this.#runtimeStatsPath());
7116
+ return {
7117
+ tokenSavingRtk: config?.runtime.tokenSavingRtk ?? false,
7118
+ rtkAvailable: detected.available,
7119
+ ...detected.executable ? { rtkExecutable: detected.executable } : {},
7120
+ ...detected.version ? { rtkVersion: detected.version } : {},
7121
+ ...installStatus?.installStatus ? { installStatus: installStatus.installStatus } : {},
7122
+ ...installStatus?.installMessage ? { installMessage: installStatus.installMessage } : {},
7123
+ estimatedOutputTokens: savings.rtk.outputTokens,
7124
+ estimatedSavedTokens: savings.rtk.savedTokens
7125
+ };
7126
+ }
7127
+ async #handleUpsertRuntimeSettings(request) {
7128
+ const target = this.#configWriteTarget();
7129
+ let existingConfigText;
7130
+ try {
7131
+ existingConfigText = await readFile11(target.configPath, "utf8");
7132
+ } catch (cause) {
7133
+ if (!isNodeErrorCode5(cause, "ENOENT")) {
7134
+ throw cause;
7135
+ }
7136
+ }
7137
+ await mkdir6(target.configDir, { recursive: true });
7138
+ await writeFile6(
7139
+ target.configPath,
7140
+ renderRuntimeConfig({
7141
+ tokenSavingRtk: request.tokenSavingRtk,
7142
+ existingConfigText
7143
+ }),
7144
+ "utf8"
7145
+ );
7146
+ const installResult = request.tokenSavingRtk === true ? await ensureRtkAvailable() : { status: "idle" };
7147
+ await this.#appendHostDiagnostic("runtime_settings_upserted", {
7148
+ ...request.projectId ? { ignoredProjectId: request.projectId } : {},
7149
+ scope: "device",
7150
+ workDir: target.workDir,
7151
+ tokenSavingRtk: request.tokenSavingRtk,
7152
+ installStatus: installResult.status
7153
+ });
7154
+ return this.#runtimeSettings(void 0, {
7155
+ installStatus: installResult.status,
7156
+ ...installResult.message ? { installMessage: installResult.message } : {}
6481
7157
  });
6482
- return this.#memorySettingsForProject(project.projectId);
6483
7158
  }
6484
7159
  async #extensionSettings(extensionId) {
6485
7160
  const config = await this.#loadUserConfigProfile().catch((cause) => {
@@ -6503,7 +7178,7 @@ var init_src4 = __esm({
6503
7178
  try {
6504
7179
  existingConfigText = await readFile11(configPath, "utf8");
6505
7180
  } catch (cause) {
6506
- if (!isNodeErrorCode4(cause, "ENOENT")) {
7181
+ if (!isNodeErrorCode5(cause, "ENOENT")) {
6507
7182
  throw cause;
6508
7183
  }
6509
7184
  }
@@ -6527,8 +7202,11 @@ var init_src4 = __esm({
6527
7202
  return this.#extensionSettings(request.extensionId);
6528
7203
  }
6529
7204
  async #fetchProviderModels(projectId, providerId) {
6530
- const project = await this.#registry.require(projectId);
6531
- const config = await loadScorelConfigProfile({ cwd: project.workDir, includeSecrets: true });
7205
+ const config = projectId ? await loadScorelConfigProfile({
7206
+ cwd: (await this.#registry.require(projectId)).workDir,
7207
+ scorelHomeDir: this.#scorelHomeDir,
7208
+ includeSecrets: true
7209
+ }) : await this.#loadUserConfigProfile({ includeSecrets: true });
6532
7210
  if (!config) {
6533
7211
  throw new Error("Model profile config is not configured");
6534
7212
  }
@@ -6613,7 +7291,7 @@ var init_src4 = __esm({
6613
7291
  }
6614
7292
  const project = await this.#registry.require(projectId);
6615
7293
  try {
6616
- return await loadScorelConfigProfile({ cwd: project.workDir });
7294
+ return await loadScorelConfigProfile({ cwd: project.workDir, scorelHomeDir: this.#scorelHomeDir });
6617
7295
  } catch (cause) {
6618
7296
  if (!isMissingConfigError(cause)) {
6619
7297
  throw cause;
@@ -6654,6 +7332,20 @@ var init_src4 = __esm({
6654
7332
  await appendFile3(join10(this.#sessionsDir, "host.log"), `${line}
6655
7333
  `, "utf8");
6656
7334
  }
7335
+ #runtimeStatsPath() {
7336
+ return join10(this.#scorelHomeDir, "runtime-stats.json");
7337
+ }
7338
+ async #recordRtkSavings(input) {
7339
+ const updateTask = this.#runtimeStatsQueue.then(async () => {
7340
+ const path = this.#runtimeStatsPath();
7341
+ const stats = await readRuntimeStats(path);
7342
+ addRtkSavings(stats, String(input.projectId), String(input.sessionId), input.savings);
7343
+ await writeRuntimeStats(path, stats);
7344
+ });
7345
+ this.#runtimeStatsQueue = updateTask.catch(() => {
7346
+ });
7347
+ await updateTask;
7348
+ }
6657
7349
  async #resolveProject(sessionId, projectId) {
6658
7350
  const project = await this.#registry.require(projectId);
6659
7351
  await this.#appendDiagnostic(sessionId, "project_resolved", {
@@ -6709,7 +7401,7 @@ var init_src4 = __esm({
6709
7401
  }
6710
7402
  };
6711
7403
  };
6712
- isNodeErrorCode4 = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
7404
+ isNodeErrorCode5 = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
6713
7405
  wireErrorCode = (cause) => {
6714
7406
  if (!(cause instanceof ProjectRegistryError)) {
6715
7407
  return "internal_error";
@@ -6760,7 +7452,7 @@ var init_src4 = __esm({
6760
7452
  return void 0;
6761
7453
  }
6762
7454
  const parsed = JSON.parse(text);
6763
- if (!isRecord7(parsed)) {
7455
+ if (!isRecord8(parsed)) {
6764
7456
  return void 0;
6765
7457
  }
6766
7458
  return {
@@ -6780,6 +7472,147 @@ var init_src4 = __esm({
6780
7472
  dreamIdleMinutes: 60,
6781
7473
  autoCompactThreshold: 0.8
6782
7474
  });
7475
+ detectRtk = async () => {
7476
+ try {
7477
+ const shell = resolveDefaultShell2();
7478
+ const path = (await execFileAsync2(shell, shellCommandArgs2(shell, "command -v rtk"), { timeout: 5e3 })).stdout.trim();
7479
+ if (!path) {
7480
+ return { available: false };
7481
+ }
7482
+ const version = await execFileAsync2(path, ["--version"], { timeout: 5e3 }).then((result) => result.stdout.trim() || result.stderr.trim()).catch(() => void 0);
7483
+ return {
7484
+ available: true,
7485
+ executable: path,
7486
+ ...version ? { version } : {}
7487
+ };
7488
+ } catch {
7489
+ return { available: false };
7490
+ }
7491
+ };
7492
+ ensureRtkAvailable = async () => {
7493
+ const existing = await detectRtk();
7494
+ if (existing.available) {
7495
+ return { status: "installed", message: existing.version ?? existing.executable };
7496
+ }
7497
+ const shell = resolveDefaultShell2();
7498
+ const brew = await execFileAsync2(shell, shellCommandArgs2(shell, "command -v brew"), { timeout: 5e3 }).then((result) => result.stdout.trim()).catch(() => "");
7499
+ if (!brew) {
7500
+ return { status: "failed", message: "Homebrew is not available; install RTK manually with `brew install rtk`." };
7501
+ }
7502
+ try {
7503
+ await execFileAsync2(brew, ["install", "rtk"], { timeout: 12e4, maxBuffer: 2e7 });
7504
+ const installed = await detectRtk();
7505
+ return installed.available ? { status: "installed", message: installed.version ?? installed.executable } : { status: "failed", message: "RTK install finished but `rtk` is still not on PATH." };
7506
+ } catch (cause) {
7507
+ const message = cause instanceof Error ? cause.message : String(cause);
7508
+ return { status: "failed", message };
7509
+ }
7510
+ };
7511
+ emptyRuntimeStats = () => ({
7512
+ version: 1,
7513
+ rtk: {
7514
+ outputTokens: 0,
7515
+ savedTokens: 0,
7516
+ byProject: {},
7517
+ bySession: {}
7518
+ }
7519
+ });
7520
+ readRuntimeStats = async (path) => {
7521
+ try {
7522
+ return parseRuntimeStats(JSON.parse(await readFile11(path, "utf8")));
7523
+ } catch (cause) {
7524
+ if (isNodeErrorCode5(cause, "ENOENT")) {
7525
+ return emptyRuntimeStats();
7526
+ }
7527
+ return emptyRuntimeStats();
7528
+ }
7529
+ };
7530
+ writeRuntimeStats = async (path, stats) => {
7531
+ await mkdir6(dirname8(path), { recursive: true });
7532
+ const tempPath = join10(dirname8(path), `.runtime-stats-${process.pid}-${Date.now()}.tmp`);
7533
+ try {
7534
+ await writeFile6(tempPath, `${JSON.stringify(stats, null, 2)}
7535
+ `, "utf8");
7536
+ await rename3(tempPath, path);
7537
+ } catch (cause) {
7538
+ await rm2(tempPath, { force: true }).catch(() => void 0);
7539
+ throw cause;
7540
+ }
7541
+ };
7542
+ parseRuntimeStats = (value) => {
7543
+ if (!isRecord8(value) || !isRecord8(value.rtk)) {
7544
+ return emptyRuntimeStats();
7545
+ }
7546
+ return {
7547
+ version: 1,
7548
+ rtk: {
7549
+ outputTokens: nonNegativeInteger2(value.rtk.outputTokens),
7550
+ savedTokens: nonNegativeInteger2(value.rtk.savedTokens),
7551
+ byProject: parseRuntimeStatsBuckets(value.rtk.byProject),
7552
+ bySession: parseRuntimeStatsBuckets(value.rtk.bySession)
7553
+ }
7554
+ };
7555
+ };
7556
+ parseRuntimeStatsBuckets = (value) => {
7557
+ if (!isRecord8(value)) {
7558
+ return {};
7559
+ }
7560
+ return Object.fromEntries(
7561
+ Object.entries(value).map(([key, bucket]) => [
7562
+ key,
7563
+ isRecord8(bucket) ? {
7564
+ outputTokens: nonNegativeInteger2(bucket.outputTokens),
7565
+ savedTokens: nonNegativeInteger2(bucket.savedTokens)
7566
+ } : { outputTokens: 0, savedTokens: 0 }
7567
+ ])
7568
+ );
7569
+ };
7570
+ addRtkSavings = (stats, projectId, sessionId, savings) => {
7571
+ addRuntimeStatsBucket(stats.rtk, savings);
7572
+ stats.rtk.byProject[projectId] = addRuntimeStatsBucket(stats.rtk.byProject[projectId] ?? { outputTokens: 0, savedTokens: 0 }, savings);
7573
+ stats.rtk.bySession[sessionId] = addRuntimeStatsBucket(stats.rtk.bySession[sessionId] ?? { outputTokens: 0, savedTokens: 0 }, savings);
7574
+ };
7575
+ addRuntimeStatsBucket = (bucket, savings) => {
7576
+ bucket.outputTokens += savings.outputTokens;
7577
+ bucket.savedTokens += savings.savedTokens;
7578
+ return bucket;
7579
+ };
7580
+ rtkSavingsFromToolResult = (result) => {
7581
+ if (!isRecord8(result) || !isRecord8(result.details)) {
7582
+ return void 0;
7583
+ }
7584
+ const rtk = result.details.rtk;
7585
+ if (!isRecord8(rtk) || rtk.applied !== true) {
7586
+ return void 0;
7587
+ }
7588
+ const outputTokens = nonNegativeInteger2(rtk.estimatedOutputTokens);
7589
+ const savedTokens = nonNegativeInteger2(rtk.estimatedSavedTokens);
7590
+ return outputTokens > 0 || savedTokens > 0 ? { outputTokens, savedTokens } : void 0;
7591
+ };
7592
+ nonNegativeInteger2 = (value) => {
7593
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
7594
+ return 0;
7595
+ }
7596
+ return Math.floor(value);
7597
+ };
7598
+ resolveDefaultShell2 = () => {
7599
+ const shell = process.env.SHELL || userShell2() || "/bin/sh";
7600
+ return shell.trim() || "/bin/sh";
7601
+ };
7602
+ shellCommandArgs2 = (shell, command) => {
7603
+ const name = basename3(shell).toLowerCase();
7604
+ if (name === "csh" || name === "tcsh" || name === "fish") {
7605
+ return ["-c", command];
7606
+ }
7607
+ return ["-lc", command];
7608
+ };
7609
+ userShell2 = () => {
7610
+ try {
7611
+ return userInfo2().shell ?? void 0;
7612
+ } catch {
7613
+ return void 0;
7614
+ }
7615
+ };
6783
7616
  runtimeChannelContextFromWire = (context) => ({
6784
7617
  extensionId: context.channel,
6785
7618
  channel: context.channel,
@@ -6794,7 +7627,7 @@ var init_src4 = __esm({
6794
7627
  ...context.data ? { data: context.data } : {}
6795
7628
  });
6796
7629
  parseQueuedChannelContext = (value) => {
6797
- if (!isRecord7(value)) {
7630
+ if (!isRecord8(value)) {
6798
7631
  return void 0;
6799
7632
  }
6800
7633
  if (typeof value.channel !== "string" || typeof value.externalConversationId !== "string") {
@@ -6806,7 +7639,7 @@ var init_src4 = __esm({
6806
7639
  ...typeof value.conversationType === "string" ? { conversationType: value.conversationType } : {},
6807
7640
  ...typeof value.senderDisplayName === "string" ? { senderDisplayName: value.senderDisplayName } : {},
6808
7641
  ...typeof value.mentionedBot === "boolean" ? { mentionedBot: value.mentionedBot } : {},
6809
- ...isRecord7(value.data) ? { data: value.data } : {}
7642
+ ...isRecord8(value.data) ? { data: value.data } : {}
6810
7643
  });
6811
7644
  };
6812
7645
  imBindingKey = (extensionId, externalConversationId) => `${extensionId}:${externalConversationId}`;
@@ -6839,7 +7672,7 @@ var init_src4 = __esm({
6839
7672
  };
6840
7673
  isSteerMessage = (text) => /^\/(?:steer|interrupt)\b/i.test(text.trim());
6841
7674
  stripImCommandPrefix = (text) => text.trim().replace(/^\/(?:steer|interrupt)\s*/i, "").trim() || text;
6842
- isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7675
+ isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6843
7676
  parseMemoryUpdate = (raw) => {
6844
7677
  const text = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
6845
7678
  if (!text) {
@@ -6946,9 +7779,11 @@ var init_relay_cli = __esm({
6946
7779
 
6947
7780
  // apps/cli/src/daemon-cli.ts
6948
7781
  import { randomUUID as randomUUID4 } from "node:crypto";
7782
+ import { spawn } from "node:child_process";
6949
7783
  import { homedir as homedir6 } from "node:os";
6950
- import { join as join12 } from "node:path";
6951
- var DEFAULT_HOST, DEFAULT_PORT, STOP_POLL_INTERVAL_MS, STOP_GRACE_MS, defaultStateDir2, isLoopbackHost, formatTimestamp, runCliDaemon, runServeCommand, stopRunningDaemon, runStatusCommand, runStopCommand, runResetCommand, formatStatusLine, parseServeFlags, parseStatusFlags, requireValue2, sleep, writeDaemonUsage;
7784
+ import { dirname as dirname9, join as join12 } from "node:path";
7785
+ import { fileURLToPath } from "node:url";
7786
+ var DEFAULT_HOST, DEFAULT_PORT, STOP_POLL_INTERVAL_MS, STOP_GRACE_MS, START_READY_TIMEOUT_MS, DEFAULT_IDLE_SHUTDOWN_MS, defaultStateDir2, isLoopbackHost, formatTimestamp, runCliDaemon, runStartCommand, runServeCommand, stopRunningDaemon, runStatusCommand, runStopCommand, runResetCommand, formatStatusLine, parseServeFlags, parseStatusFlags, requireValue2, sleep, waitForDaemonReady, detachBackgroundDaemon, nodeEntrypointArgs, writeDaemonUsage;
6952
7787
  var init_daemon_cli = __esm({
6953
7788
  "apps/cli/src/daemon-cli.ts"() {
6954
7789
  "use strict";
@@ -6958,6 +7793,8 @@ var init_daemon_cli = __esm({
6958
7793
  DEFAULT_PORT = 7777;
6959
7794
  STOP_POLL_INTERVAL_MS = 200;
6960
7795
  STOP_GRACE_MS = 5e3;
7796
+ START_READY_TIMEOUT_MS = 1e4;
7797
+ DEFAULT_IDLE_SHUTDOWN_MS = 15 * 60 * 1e3;
6961
7798
  defaultStateDir2 = () => join12(homedir6(), ".scorel");
6962
7799
  isLoopbackHost = (host) => host === "127.0.0.1" || host === "::1" || host === "localhost";
6963
7800
  formatTimestamp = (epochMs) => new Date(epochMs).toISOString();
@@ -6965,6 +7802,8 @@ var init_daemon_cli = __esm({
6965
7802
  const [command, ...rest] = argv;
6966
7803
  const stateDir = options.stateDir ?? defaultStateDir2();
6967
7804
  switch (command) {
7805
+ case "start":
7806
+ return runStartCommand(rest, { ...options, stateDir });
6968
7807
  case "serve":
6969
7808
  return runServeCommand(rest, { ...options, stateDir });
6970
7809
  case "status":
@@ -6982,6 +7821,63 @@ var init_daemon_cli = __esm({
6982
7821
  return 1;
6983
7822
  }
6984
7823
  };
7824
+ runStartCommand = async (argv, options) => {
7825
+ let flags;
7826
+ try {
7827
+ flags = parseServeFlags(argv, options.cwd ?? process.cwd(), options.env ?? process.env);
7828
+ } catch (cause) {
7829
+ options.error.write(`scorel daemon start error: ${cause.message}
7830
+ `);
7831
+ return 1;
7832
+ }
7833
+ const readState = options.readState ?? ((stateDir) => readLocalDaemonState({ stateDir }));
7834
+ const existing = await readState(options.stateDir);
7835
+ if (existing && daemonStateLiveness(existing) === "running") {
7836
+ options.output.write(`scorel host already running url=${existing.wsUrl} pid=${existing.pid}
7837
+ `);
7838
+ return 0;
7839
+ }
7840
+ const cliEntrypoint = options.cliEntrypoint ?? fileURLToPath(import.meta.url).replace(/daemon-cli\.ts$/, "index.ts");
7841
+ const child = (options.spawn ?? spawn)(process.execPath, [
7842
+ ...nodeEntrypointArgs(cliEntrypoint),
7843
+ "host",
7844
+ "serve",
7845
+ "--host",
7846
+ flags.host,
7847
+ "--port",
7848
+ String(flags.port),
7849
+ "--cwd",
7850
+ flags.cwd,
7851
+ "--idle-timeout-ms",
7852
+ String(flags.idleShutdownMs),
7853
+ ...flags.token ? ["--token", flags.token] : [],
7854
+ ...flags.relayUrl ? ["--relay", flags.relayUrl] : ["--no-relay"],
7855
+ ...flags.replace ? ["--replace"] : []
7856
+ ], {
7857
+ cwd: dirname9(cliEntrypoint),
7858
+ env: { ...process.env, ...options.env ?? {} },
7859
+ detached: true,
7860
+ stdio: ["ignore", "pipe", "pipe"]
7861
+ });
7862
+ try {
7863
+ await waitForDaemonReady(child, options.daemonReadyTimeoutMs ?? START_READY_TIMEOUT_MS);
7864
+ } catch (cause) {
7865
+ options.error.write(`scorel daemon start error: ${cause.message}
7866
+ `);
7867
+ child.kill("SIGTERM");
7868
+ return 1;
7869
+ }
7870
+ const state = await readState(options.stateDir);
7871
+ if (!state || daemonStateLiveness(state) !== "running") {
7872
+ options.error.write("scorel daemon start error: daemon state missing after start\n");
7873
+ child.kill("SIGTERM");
7874
+ return 1;
7875
+ }
7876
+ detachBackgroundDaemon(child);
7877
+ options.output.write(`scorel host started url=${state.wsUrl} pid=${state.pid}
7878
+ `);
7879
+ return 0;
7880
+ };
6985
7881
  runServeCommand = async (argv, options) => {
6986
7882
  let flags;
6987
7883
  try {
@@ -7009,16 +7905,28 @@ Use --replace to stop it and start a new one.
7009
7905
  }
7010
7906
  const token = flags.token ?? existing?.token ?? randomUUID4();
7011
7907
  const identity = await loadOrCreateHostDeviceIdentity({ stateDir: options.stateDir });
7908
+ const configScope = { scorelHomeDir: options.stateDir };
7909
+ let signalReason = "natural";
7910
+ let resolveStopWaiter;
7911
+ let stopRequested = false;
7912
+ const requestStop = (reason) => {
7913
+ signalReason = reason;
7914
+ stopRequested = true;
7915
+ resolveStopWaiter?.();
7916
+ };
7012
7917
  const daemon = new ScorelHost({
7013
7918
  sessionsDir: options.sessionsDir ?? scorelSessionsDir(homedir6()),
7014
7919
  projectsPath: join12(options.stateDir, "projects.json"),
7015
7920
  deviceId: identity.deviceId,
7016
7921
  deviceDisplayName: identity.displayName,
7017
- loadConfig: async ({ project }) => loadScorelConfig({ cwd: project.workDir }),
7018
- loadConfigProfile: async ({ project }) => loadScorelConfigProfile({ cwd: project.workDir }),
7922
+ idleShutdownMs: flags.idleShutdownMs,
7923
+ onIdleShutdown: () => requestStop("idle"),
7924
+ scorelHomeDir: options.stateDir,
7925
+ loadConfig: async ({ project }) => loadScorelConfig({ cwd: project.workDir, ...configScope }),
7926
+ loadConfigProfile: async ({ project }) => loadScorelConfigProfile({ cwd: project.workDir, ...configScope }),
7019
7927
  createRuntime: async ({ project, selectedModel, purpose }) => createRealRuntime({
7020
7928
  cwd: project.workDir,
7021
- config: await loadScorelConfig({ cwd: project.workDir }),
7929
+ config: await loadScorelConfig({ cwd: project.workDir, ...configScope }),
7022
7930
  modelSelection: selectedModel ? { modelId: selectedModel.modelId, role: selectedModel.role } : void 0,
7023
7931
  includeTools: purpose === "chat"
7024
7932
  })
@@ -7077,20 +7985,22 @@ Use --replace to stop it and start a new one.
7077
7985
  await markDaemonStopped({ stateDir: options.stateDir, stoppedAt: Date.now() });
7078
7986
  }
7079
7987
  };
7080
- let signalReason = "natural";
7081
7988
  const signalHandlers = /* @__PURE__ */ new Map();
7082
7989
  const stopWaiter = new Promise((resolve7) => {
7990
+ resolveStopWaiter = resolve7;
7991
+ if (stopRequested) {
7992
+ resolve7();
7993
+ return;
7994
+ }
7083
7995
  if (options.serveSignal) {
7084
7996
  if (options.serveSignal.aborted) {
7085
- signalReason = "abort";
7086
- resolve7();
7997
+ requestStop("abort");
7087
7998
  return;
7088
7999
  }
7089
8000
  options.serveSignal.addEventListener(
7090
8001
  "abort",
7091
8002
  () => {
7092
- signalReason = "abort";
7093
- resolve7();
8003
+ requestStop("abort");
7094
8004
  },
7095
8005
  { once: true }
7096
8006
  );
@@ -7098,8 +8008,7 @@ Use --replace to stop it and start a new one.
7098
8008
  }
7099
8009
  const installSignal = (signal) => {
7100
8010
  const handler = () => {
7101
- signalReason = signal;
7102
- resolve7();
8011
+ requestStop(signal);
7103
8012
  };
7104
8013
  signalHandlers.set(signal, handler);
7105
8014
  process.once(signal, handler);
@@ -7229,6 +8138,7 @@ Use --replace to stop it and start a new one.
7229
8138
  let token;
7230
8139
  let relayUrl = resolveDefaultRelayUrl(env);
7231
8140
  let replace = false;
8141
+ let idleShutdownMs = DEFAULT_IDLE_SHUTDOWN_MS;
7232
8142
  for (let index = 0; index < argv.length; index += 1) {
7233
8143
  const arg = argv[index];
7234
8144
  if (arg === "--host") {
@@ -7272,9 +8182,17 @@ Use --replace to stop it and start a new one.
7272
8182
  replace = true;
7273
8183
  continue;
7274
8184
  }
8185
+ if (arg === "--idle-timeout-ms") {
8186
+ idleShutdownMs = Number(requireValue2(argv, index, "--idle-timeout-ms"));
8187
+ if (!Number.isInteger(idleShutdownMs) || idleShutdownMs < 0) {
8188
+ throw new Error("--idle-timeout-ms must be a non-negative integer");
8189
+ }
8190
+ index += 1;
8191
+ continue;
8192
+ }
7275
8193
  throw new Error(`Unknown serve option: ${arg}`);
7276
8194
  }
7277
- return { host, port, token, cwd, relayUrl, replace };
8195
+ return { host, port, token, cwd, relayUrl, replace, idleShutdownMs };
7278
8196
  };
7279
8197
  parseStatusFlags = (argv) => {
7280
8198
  let showToken = false;
@@ -7297,11 +8215,66 @@ Use --replace to stop it and start a new one.
7297
8215
  sleep = (ms) => new Promise((resolve7) => {
7298
8216
  setTimeout(resolve7, ms);
7299
8217
  });
8218
+ waitForDaemonReady = (child, timeoutMs) => new Promise((resolveReady, rejectReady) => {
8219
+ if (!child.stdout) {
8220
+ rejectReady(new Error("daemon child has no stdout stream"));
8221
+ return;
8222
+ }
8223
+ let buffer = "";
8224
+ let stderrBuffer = "";
8225
+ let settled = false;
8226
+ const timer = setTimeout(() => {
8227
+ if (settled) return;
8228
+ settled = true;
8229
+ cleanup();
8230
+ rejectReady(new Error("timed out waiting for daemon ready line"));
8231
+ }, timeoutMs);
8232
+ const onData = (chunk) => {
8233
+ buffer += chunk.toString();
8234
+ if (!buffer.includes("\n")) return;
8235
+ if (buffer.includes("scorel daemon serving url=") || buffer.includes("scorel host serving url=")) {
8236
+ if (settled) return;
8237
+ settled = true;
8238
+ cleanup();
8239
+ resolveReady();
8240
+ }
8241
+ const newlineIndex = buffer.lastIndexOf("\n");
8242
+ buffer = newlineIndex >= 0 ? buffer.slice(newlineIndex + 1) : buffer;
8243
+ };
8244
+ const onStderr = (chunk) => {
8245
+ stderrBuffer += chunk.toString();
8246
+ };
8247
+ const onExit = (code) => {
8248
+ if (settled) return;
8249
+ settled = true;
8250
+ cleanup();
8251
+ const trimmed = stderrBuffer.trim();
8252
+ const detail = trimmed ? `: ${trimmed}` : "";
8253
+ rejectReady(new Error(`daemon exited before ready code=${code}${detail}`));
8254
+ };
8255
+ const cleanup = () => {
8256
+ clearTimeout(timer);
8257
+ child.stdout?.off("data", onData);
8258
+ child.stderr?.off("data", onStderr);
8259
+ child.off("exit", onExit);
8260
+ };
8261
+ child.stdout.on("data", onData);
8262
+ child.stderr?.on("data", onStderr);
8263
+ child.once("exit", onExit);
8264
+ });
8265
+ detachBackgroundDaemon = (child) => {
8266
+ child.stdout?.destroy();
8267
+ child.stderr?.destroy();
8268
+ child.unref();
8269
+ };
8270
+ nodeEntrypointArgs = (entrypoint) => entrypoint.endsWith(".ts") ? ["--import", "tsx", entrypoint] : [entrypoint];
7300
8271
  writeDaemonUsage = (output) => {
7301
8272
  output.write(
7302
8273
  [
7303
8274
  "Usage: scorel host serve [--host <h>] [--port <p>] [--token <t>] [--project <dir>]",
7304
- " [--relay <relay-url> | --no-relay] [--replace]",
8275
+ " [--relay <relay-url> | --no-relay] [--replace] [--idle-timeout-ms <ms>]",
8276
+ " scorel host start [--host <h>] [--port <p>] [--token <t>] [--project <dir>]",
8277
+ " [--relay <relay-url> | --no-relay] [--replace] [--idle-timeout-ms <ms>]",
7305
8278
  " scorel host status [--show-token]",
7306
8279
  " scorel host stop",
7307
8280
  " scorel host reset",
@@ -7925,11 +8898,11 @@ var init_relay_server_cli = __esm({
7925
8898
  });
7926
8899
 
7927
8900
  // apps/cli/src/up-cli.ts
7928
- import { spawn } from "node:child_process";
8901
+ import { spawn as spawn2 } from "node:child_process";
7929
8902
  import { homedir as homedir8 } from "node:os";
7930
- import { join as join15 } from "node:path";
7931
- import { fileURLToPath } from "node:url";
7932
- var DEFAULT_DAEMON_PORT, DEFAULT_WEBUI_PORT, DEFAULT_DAEMON_READY_TIMEOUT_MS, defaultStateDir3, defaultAttachSigint, runCliUp, parseUpFlags, requireValue4, waitForDaemonReady, pipeWithPrefix, pipeStreamLines, once;
8903
+ import { dirname as dirname10, join as join15 } from "node:path";
8904
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
8905
+ var DEFAULT_DAEMON_PORT, DEFAULT_WEBUI_PORT, DEFAULT_DAEMON_READY_TIMEOUT_MS, defaultStateDir3, defaultAttachSigint, runCliUp, parseUpFlags, requireValue4, waitForDaemonReady2, pipeWithPrefix, detachBackgroundDaemon2, nodeEntrypointArgs2, pipeStreamLines, once;
7933
8906
  var init_up_cli = __esm({
7934
8907
  "apps/cli/src/up-cli.ts"() {
7935
8908
  "use strict";
@@ -7952,8 +8925,8 @@ var init_up_cli = __esm({
7952
8925
  return 1;
7953
8926
  }
7954
8927
  const stateDir = options.stateDir ?? defaultStateDir3();
7955
- const cliEntrypoint = options.cliEntrypoint ?? fileURLToPath(import.meta.url).replace(/up-cli\.ts$/, "index.ts");
7956
- const spawnFn = options.spawn ?? spawn;
8928
+ const cliEntrypoint = options.cliEntrypoint ?? fileURLToPath2(import.meta.url).replace(/up-cli\.ts$/, "index.ts");
8929
+ const spawnFn = options.spawn ?? spawn2;
7957
8930
  const readState = options.readState ?? ((dir) => readLocalDaemonState({ stateDir: dir }));
7958
8931
  const attachSigint = options.attachSigint ?? defaultAttachSigint;
7959
8932
  const readyTimeout = options.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
@@ -7964,9 +8937,7 @@ var init_up_cli = __esm({
7964
8937
  let daemonState = existingState;
7965
8938
  if (!reuseDaemon) {
7966
8939
  const daemonArgs = [
7967
- "--import",
7968
- "tsx",
7969
- cliEntrypoint,
8940
+ ...nodeEntrypointArgs2(cliEntrypoint),
7970
8941
  "daemon",
7971
8942
  "serve",
7972
8943
  "--port",
@@ -7976,19 +8947,19 @@ var init_up_cli = __esm({
7976
8947
  "--no-relay"
7977
8948
  ];
7978
8949
  daemonChild = spawnFn(process.execPath, daemonArgs, {
7979
- cwd: flags.cwd,
8950
+ cwd: dirname10(cliEntrypoint),
7980
8951
  env: { ...process.env },
8952
+ detached: true,
7981
8953
  stdio: ["ignore", "pipe", "pipe"]
7982
8954
  });
7983
8955
  try {
7984
- await waitForDaemonReady(daemonChild, readyTimeout);
8956
+ await waitForDaemonReady2(daemonChild, readyTimeout);
7985
8957
  } catch (cause) {
7986
8958
  options.error.write(`scorel up error: ${cause.message}
7987
8959
  `);
7988
8960
  daemonChild.kill("SIGTERM");
7989
8961
  return 1;
7990
8962
  }
7991
- pipeWithPrefix(daemonChild, "[daemon]", options.output, options.error);
7992
8963
  daemonState = await readState(stateDir);
7993
8964
  }
7994
8965
  if (!daemonState) {
@@ -7996,16 +8967,17 @@ var init_up_cli = __esm({
7996
8967
  daemonChild?.kill("SIGTERM");
7997
8968
  return 1;
7998
8969
  }
8970
+ if (daemonChild) {
8971
+ detachBackgroundDaemon2(daemonChild);
8972
+ }
7999
8973
  const webuiArgs = [
8000
- "--import",
8001
- "tsx",
8002
- cliEntrypoint,
8974
+ ...nodeEntrypointArgs2(cliEntrypoint),
8003
8975
  "webui",
8004
8976
  "--port",
8005
8977
  String(flags.webuiPort)
8006
8978
  ];
8007
8979
  const webuiChild = spawnFn(process.execPath, webuiArgs, {
8008
- cwd: flags.cwd,
8980
+ cwd: dirname10(cliEntrypoint),
8009
8981
  env: { ...process.env },
8010
8982
  stdio: ["ignore", "pipe", "pipe"]
8011
8983
  });
@@ -8022,33 +8994,21 @@ var init_up_cli = __esm({
8022
8994
  return;
8023
8995
  }
8024
8996
  shuttingDown = true;
8025
- daemonChild?.kill("SIGTERM");
8026
8997
  webuiChild.kill("SIGTERM");
8027
8998
  });
8028
- const daemonExit = daemonChild ? once(daemonChild) : Promise.resolve(0);
8029
8999
  const webuiExit = once(webuiChild);
8030
- const daemonDeathWatcher = daemonChild ? daemonExit.then((code) => {
8031
- if (!shuttingDown) {
8032
- shuttingDown = true;
8033
- options.error.write(`scorel up daemon exited code=${code}
8034
- `);
8035
- webuiChild.kill("SIGTERM");
8036
- }
8037
- return code;
8038
- }) : Promise.resolve(0);
8039
9000
  const webuiDeathWatcher = webuiExit.then((code) => {
8040
9001
  if (!shuttingDown) {
8041
9002
  shuttingDown = true;
8042
9003
  options.error.write(`scorel up webui exited code=${code}
8043
9004
  `);
8044
- daemonChild?.kill("SIGTERM");
8045
9005
  }
8046
9006
  return code;
8047
9007
  });
8048
- const [daemonCode, webuiCode] = await Promise.all([daemonDeathWatcher, webuiDeathWatcher]);
9008
+ const webuiCode = await webuiDeathWatcher;
8049
9009
  detachSigint();
8050
9010
  options.output.write("scorel up stopped\n");
8051
- return daemonCode === 0 && webuiCode === 0 ? 0 : 1;
9011
+ return webuiCode === 0 ? 0 : 1;
8052
9012
  };
8053
9013
  parseUpFlags = (argv, defaultCwd) => {
8054
9014
  let daemonPort = DEFAULT_DAEMON_PORT;
@@ -8088,7 +9048,7 @@ var init_up_cli = __esm({
8088
9048
  }
8089
9049
  return value;
8090
9050
  };
8091
- waitForDaemonReady = (child, timeoutMs) => new Promise((resolveReady, rejectReady) => {
9051
+ waitForDaemonReady2 = (child, timeoutMs) => new Promise((resolveReady, rejectReady) => {
8092
9052
  if (!child.stdout) {
8093
9053
  rejectReady(new Error("daemon child has no stdout stream"));
8094
9054
  return;
@@ -8145,6 +9105,12 @@ var init_up_cli = __esm({
8145
9105
  pipeStreamLines(child.stderr, prefix, error);
8146
9106
  }
8147
9107
  };
9108
+ detachBackgroundDaemon2 = (child) => {
9109
+ child.stdout?.destroy();
9110
+ child.stderr?.destroy();
9111
+ child.unref();
9112
+ };
9113
+ nodeEntrypointArgs2 = (entrypoint) => entrypoint.endsWith(".ts") ? ["--import", "tsx", entrypoint] : [entrypoint];
8148
9114
  pipeStreamLines = (stream, prefix, destination) => {
8149
9115
  let buffer = "";
8150
9116
  stream.setEncoding?.("utf8");
@@ -8174,10 +9140,10 @@ var init_up_cli = __esm({
8174
9140
  });
8175
9141
 
8176
9142
  // apps/cli/src/webui-cli.ts
8177
- import { spawn as spawn2 } from "node:child_process";
9143
+ import { spawn as spawn3 } from "node:child_process";
8178
9144
  import { existsSync as existsSync4 } from "node:fs";
8179
- import { dirname as dirname9, resolve as resolve6 } from "node:path";
8180
- import { fileURLToPath as fileURLToPath2 } from "node:url";
9145
+ import { dirname as dirname11, resolve as resolve6 } from "node:path";
9146
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
8181
9147
  var DEFAULT_PORT3, DEFAULT_HOST3, runCliWebUi, findWebuiAppDir, buildWebUiSpawnPlan, parseWebUiFlags, requireValue5, waitForChildExit;
8182
9148
  var init_webui_cli = __esm({
8183
9149
  "apps/cli/src/webui-cli.ts"() {
@@ -8199,7 +9165,7 @@ var init_webui_cli = __esm({
8199
9165
  return 1;
8200
9166
  }
8201
9167
  const plan = buildWebUiSpawnPlan(flags, webuiAppDir);
8202
- const spawnFn = options.spawn ?? spawn2;
9168
+ const spawnFn = options.spawn ?? spawn3;
8203
9169
  const child = spawnFn(plan.command, plan.argv, {
8204
9170
  cwd: plan.cwd,
8205
9171
  env: plan.env,
@@ -8208,7 +9174,7 @@ var init_webui_cli = __esm({
8208
9174
  return await waitForChildExit(child, options);
8209
9175
  };
8210
9176
  findWebuiAppDir = () => {
8211
- let cursor = dirname9(fileURLToPath2(import.meta.url));
9177
+ let cursor = dirname11(fileURLToPath3(import.meta.url));
8212
9178
  for (let depth = 0; depth < 8; depth += 1) {
8213
9179
  const candidate = resolve6(cursor, "apps/webui/package.json");
8214
9180
  if (existsSync4(candidate)) {
@@ -8300,8 +9266,8 @@ import { createHash as createHash3 } from "node:crypto";
8300
9266
  import { appendFile as appendFile4, mkdir as mkdir8, readFile as readFile13, realpath as realpath3, readdir as readdir7, writeFile as writeFile8 } from "node:fs/promises";
8301
9267
  import { createInterface } from "node:readline/promises";
8302
9268
  import { homedir as homedir9 } from "node:os";
8303
- import { fileURLToPath as fileURLToPath3 } from "node:url";
8304
- import { basename as basename2, dirname as dirname10, join as join16 } from "node:path";
9269
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
9270
+ import { basename as basename4, dirname as dirname12, join as join16 } from "node:path";
8305
9271
  var cliAppName, cliClientDependency, cliDaemonDependency, defaultSessionsDir, defaultStateDir4, runCli, runProject, runLogs, runAttach, attachCacheScope, attachCacheFilePath, attachDiagnosticsFilePath, findAttachDiagnosticsFilePath, stateDirFromSessionsDir, AttachDiagnostics, readAttachCache, writeAttachCache, emptyAttachCacheSnapshot, mergePersistentEvents, highestSeq, highestCachedStreamSeq, updateAttachCacheSnapshot, removeCompletedTransients, isCachedTransientMessage, AsyncInputQueue, parseAttachOptions, parseLogsOptions, runChat, createSigintHandler, loadOrCreateSession, parseChatOptions, requireValue6, promptIfInteractive, writeUsage, writeProjectUsage, writeEventError, writeToolResult, redactDiagnosticFields, formatDiagnosticLine2, formatDiagnosticValue2, AttachEventRenderer, blocksToText, isCliEntrypoint;
8306
9272
  var init_index = __esm({
8307
9273
  async "apps/cli/src/index.ts"() {
@@ -8634,7 +9600,7 @@ var init_index = __esm({
8634
9600
  if (!sessionsDir) {
8635
9601
  return defaultStateDir4();
8636
9602
  }
8637
- return basename2(sessionsDir) === "sessions" ? dirname10(sessionsDir) : sessionsDir;
9603
+ return basename4(sessionsDir) === "sessions" ? dirname12(sessionsDir) : sessionsDir;
8638
9604
  };
8639
9605
  AttachDiagnostics = class {
8640
9606
  #stateDir;
@@ -8686,7 +9652,7 @@ var init_index = __esm({
8686
9652
  }
8687
9653
  const filePath = attachDiagnosticsFilePath(this.#stateDir, this.#scope, this.#sessionId);
8688
9654
  this.#writes.push(
8689
- mkdir8(dirname10(filePath), { recursive: true }).then(() => appendFile4(filePath, `${line}
9655
+ mkdir8(dirname12(filePath), { recursive: true }).then(() => appendFile4(filePath, `${line}
8690
9656
  `, "utf8"))
8691
9657
  );
8692
9658
  }
@@ -8716,7 +9682,7 @@ var init_index = __esm({
8716
9682
  const filePath = attachCacheFilePath(stateDir, scope, sessionId);
8717
9683
  const uniqueEvents = mergePersistentEvents(snapshot.events);
8718
9684
  const transients = removeCompletedTransients(snapshot.transients, uniqueEvents);
8719
- await mkdir8(dirname10(filePath), { recursive: true });
9685
+ await mkdir8(dirname12(filePath), { recursive: true });
8720
9686
  await writeFile8(
8721
9687
  filePath,
8722
9688
  `${JSON.stringify({ version: 1, scope, sessionId: String(sessionId), events: uniqueEvents, transients }, null, 2)}
@@ -8853,12 +9819,14 @@ var init_index = __esm({
8853
9819
  return { sessionId, tail, attach, remoteUrl };
8854
9820
  };
8855
9821
  runChat = async (options, io) => {
8856
- const loadProjectConfig = async (project2) => options.config ?? await loadScorelConfig({ cwd: project2.workDir });
8857
- const loadProjectConfigProfile = async (project2) => options.config ?? await loadScorelConfigProfile({ cwd: project2.workDir });
9822
+ const configScope = { scorelHomeDir: options.stateDir };
9823
+ const loadProjectConfig = async (project2) => options.config ?? await loadScorelConfig({ cwd: project2.workDir, ...configScope });
9824
+ const loadProjectConfigProfile = async (project2) => options.config ?? await loadScorelConfigProfile({ cwd: project2.workDir, ...configScope });
8858
9825
  const daemon = new ScorelHost({
8859
9826
  sessionsDir: options.sessionsDir,
8860
9827
  projectsPath: join16(options.stateDir, "projects.json"),
8861
9828
  deviceId: asDeviceId("device_local"),
9829
+ scorelHomeDir: options.stateDir,
8862
9830
  loadConfig: async ({ project: project2 }) => loadProjectConfig(project2),
8863
9831
  loadConfigProfile: async ({ project: project2 }) => loadProjectConfigProfile(project2),
8864
9832
  createRuntime: async ({ project: project2, selectedModel, purpose }) => createRealRuntime({
@@ -8992,8 +9960,10 @@ var init_index = __esm({
8992
9960
  "Usage: scorel chat [--session <id>] [--cwd <dir>]",
8993
9961
  " scorel [--session <id>] [--cwd <dir>]",
8994
9962
  " scorel attach --session <id> --remote <ws-url> --token <token>",
9963
+ " scorel host start [--host <h>] [--port <p>] [--token <t>] [--project <dir>]",
9964
+ " [--relay <relay-url> | --no-relay] [--replace] [--idle-timeout-ms <ms>]",
8995
9965
  " scorel host serve [--host <h>] [--port <p>] [--token <t>] [--project <dir>]",
8996
- " [--relay <relay-url> | --no-relay] [--replace]",
9966
+ " [--relay <relay-url> | --no-relay] [--replace] [--idle-timeout-ms <ms>]",
8997
9967
  " scorel host status [--show-token]",
8998
9968
  " scorel host stop",
8999
9969
  " scorel host reset",
@@ -9132,7 +10102,7 @@ ${text}
9132
10102
  if (!process.argv[1]) return false;
9133
10103
  const [argvPath, modulePath] = await Promise.all([
9134
10104
  realpath3(process.argv[1]).catch(() => process.argv[1]),
9135
- realpath3(fileURLToPath3(import.meta.url)).catch(() => fileURLToPath3(import.meta.url))
10105
+ realpath3(fileURLToPath4(import.meta.url)).catch(() => fileURLToPath4(import.meta.url))
9136
10106
  ]);
9137
10107
  return argvPath === modulePath;
9138
10108
  };