@chanlerdev/scorel 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1746,7 +1746,7 @@ import { mkdir as mkdir2, readFile as readFile4, rename as rename2, rm, stat as
1746
1746
  import { userInfo } from "node:os";
1747
1747
  import { basename as basename2, dirname as dirname3, extname, isAbsolute, relative, resolve } from "node:path";
1748
1748
  import { promisify } from "node:util";
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;
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, renderFullBashResult, writeBashArtifact, safeArtifactSegment, projectBashStreams, projectOutputStream, resolveDefaultShell, resolveRtkCommand, rtkRewriteResult, executableRewriteCommand, readRtkGain, rtkSavedTokenDelta, withRtkSavings, nonNegativeInteger, isRecord3, shellQuote, shellCommandArgs, userShell, truncate, sliceBytes, textResult, byteLength, isTimeoutError, isExecError, runRipgrep, splitOutput, vcsExcludes, grepArgs, splitGlobPatterns, paginate, toWorkspaceRelative, relativizeGrepLine, relativizeCountLine, sortPathsByMtime, formatPaginatedText, formatLimitSuffix, parseCountLines;
1750
1750
  var init_coding_tools = __esm({
1751
1751
  "packages/core/src/tools/coding-tools.ts"() {
1752
1752
  "use strict";
@@ -1914,7 +1914,7 @@ String: ${input.old_string}`
1914
1914
  defineTool({
1915
1915
  name: "Bash",
1916
1916
  description: "Execute a shell command in the workspace with timeout and output truncation.",
1917
- execute: async (_toolCallId, args, signal) => {
1917
+ execute: async (toolCallId, args, signal) => {
1918
1918
  const input = parseBashArgs(args);
1919
1919
  const commandCwd = input.cwd ? resolveWorkspacePath(input.cwd) : root;
1920
1920
  const timeoutMs = Math.min(input.timeoutMs ?? defaultTimeoutMs, maxTimeoutMs);
@@ -1940,12 +1940,14 @@ String: ${input.old_string}`
1940
1940
  maxBuffer: Math.max(outputLimit * 4, 1024 * 1024)
1941
1941
  });
1942
1942
  const rtkSavedTokens = rtk?.executable ? await rtkSavedTokenDelta(rtk.executable, commandCwd, rtkGainBefore) : void 0;
1943
- return bashResult({
1943
+ return await bashResult({
1944
1944
  exitCode: 0,
1945
1945
  stdout: result.stdout,
1946
1946
  stderr: result.stderr,
1947
1947
  cwd: commandCwd,
1948
1948
  outputLimit,
1949
+ artifactDir: options.toolResultArtifacts?.dir,
1950
+ toolCallId,
1949
1951
  shell: defaultShell,
1950
1952
  command,
1951
1953
  rtk: withRtkSavings(rtkResult, rtkSavedTokens)
@@ -1956,12 +1958,14 @@ String: ${input.old_string}`
1956
1958
  }
1957
1959
  if (isExecError(cause)) {
1958
1960
  const rtkSavedTokens = rtk?.executable ? await rtkSavedTokenDelta(rtk.executable, commandCwd, rtkGainBefore) : void 0;
1959
- return bashResult({
1961
+ return await bashResult({
1960
1962
  exitCode: typeof cause.code === "number" ? cause.code : 1,
1961
1963
  stdout: String(cause.stdout ?? ""),
1962
1964
  stderr: String(cause.stderr ?? cause.message),
1963
1965
  cwd: commandCwd,
1964
1966
  outputLimit,
1967
+ artifactDir: options.toolResultArtifacts?.dir,
1968
+ toolCallId,
1965
1969
  shell: defaultShell,
1966
1970
  command,
1967
1971
  rtk: withRtkSavings(rtkResult, rtkSavedTokens)
@@ -2329,17 +2333,41 @@ ${filenames.join("\n")}`,
2329
2333
  throw cause;
2330
2334
  }
2331
2335
  };
2332
- bashResult = (input) => {
2333
- const stdout = truncate(input.stdout, input.outputLimit, "stdout");
2334
- const stderr = truncate(input.stderr, input.outputLimit, "stderr");
2335
- return textResult(`exitCode: ${input.exitCode}
2336
+ bashResult = async (input) => {
2337
+ const stdoutBytes = Buffer.byteLength(input.stdout);
2338
+ const stderrBytes = Buffer.byteLength(input.stderr);
2339
+ const fullResult = renderFullBashResult(input);
2340
+ const resultBytes = Buffer.byteLength(fullResult);
2341
+ const shouldArchive = Boolean(input.artifactDir) && resultBytes > input.outputLimit;
2342
+ const artifactPath = shouldArchive && input.artifactDir ? await writeBashArtifact(input.artifactDir, input.toolCallId, fullResult) : void 0;
2343
+ const projection = artifactPath ? projectBashStreams(input.stdout, input.stderr, input.outputLimit) : void 0;
2344
+ const stdout = projection?.stdout ?? truncate(input.stdout, input.outputLimit, "stdout");
2345
+ const stderr = projection?.stderr ?? truncate(input.stderr, input.outputLimit, "stderr");
2346
+ const text = artifactPath ? [
2347
+ `exitCode: ${input.exitCode}`,
2348
+ `cwd: ${input.cwd}`,
2349
+ `artifact: ${artifactPath}`,
2350
+ `resultBytes: ${resultBytes}`,
2351
+ `stdoutBytes: ${stdoutBytes}`,
2352
+ `stderrBytes: ${stderrBytes}`,
2353
+ ...projection?.lines ?? []
2354
+ ].join("\n") : `exitCode: ${input.exitCode}
2336
2355
  cwd: ${input.cwd}
2337
2356
  stdout:
2338
2357
  ${stdout}
2339
2358
  stderr:
2340
- ${stderr}`, {
2359
+ ${stderr}`;
2360
+ return textResult(text, {
2341
2361
  exitCode: input.exitCode,
2342
2362
  cwd: input.cwd,
2363
+ ...artifactPath ? {
2364
+ artifact: {
2365
+ path: artifactPath,
2366
+ resultBytes,
2367
+ stdoutBytes,
2368
+ stderrBytes
2369
+ }
2370
+ } : {},
2343
2371
  ...input.shell ? { shell: input.shell } : {},
2344
2372
  ...input.command ? { command: input.command } : {},
2345
2373
  ...input.rtk ? {
@@ -2351,6 +2379,57 @@ ${stderr}`)
2351
2379
  } : {}
2352
2380
  });
2353
2381
  };
2382
+ renderFullBashResult = (input) => `exitCode: ${input.exitCode}
2383
+ cwd: ${input.cwd}
2384
+ stdout:
2385
+ ${input.stdout}
2386
+ stderr:
2387
+ ${input.stderr}`;
2388
+ writeBashArtifact = async (artifactDir, toolCallId, content) => {
2389
+ const directory = resolve(artifactDir, safeArtifactSegment(toolCallId));
2390
+ await mkdir2(directory, { recursive: true });
2391
+ const path = resolve(directory, "result.txt");
2392
+ await writeFile2(path, content, { encoding: "utf8", mode: 384 });
2393
+ return path;
2394
+ };
2395
+ safeArtifactSegment = (value) => value.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 120) || "tool_call";
2396
+ projectBashStreams = (stdout, stderr, maxBytes) => {
2397
+ const streams = [
2398
+ { label: "stdout", value: stdout },
2399
+ { label: "stderr", value: stderr }
2400
+ ].filter((stream) => Buffer.byteLength(stream.value) > 0);
2401
+ if (streams.length === 0) {
2402
+ return { lines: ["stdout:", "", "stderr:", ""], stdout: "", stderr: "" };
2403
+ }
2404
+ const perStreamBudget = Math.max(1, Math.floor(maxBytes / streams.length));
2405
+ const projected = streams.map((stream) => projectOutputStream(stream.value, perStreamBudget, stream.label));
2406
+ const stdoutText = projected.find((stream) => stream.label === "stdout")?.text ?? "stdout:\n";
2407
+ const stderrText = projected.find((stream) => stream.label === "stderr")?.text ?? "stderr:\n";
2408
+ return {
2409
+ lines: projected.map((stream) => stream.text),
2410
+ stdout: stdoutText,
2411
+ stderr: stderrText
2412
+ };
2413
+ };
2414
+ projectOutputStream = (value, maxBytes, label) => {
2415
+ const bytes = Buffer.byteLength(value);
2416
+ if (bytes <= maxBytes) {
2417
+ return { label, text: `${label}:
2418
+ ${value}` };
2419
+ }
2420
+ const headBytes = Math.max(1, Math.floor(maxBytes / 2));
2421
+ const tailBytes = Math.max(1, maxBytes - headBytes);
2422
+ return {
2423
+ label,
2424
+ text: [
2425
+ `${label} head:`,
2426
+ sliceBytes(value, 0, headBytes),
2427
+ `${label} tail:`,
2428
+ sliceBytes(value, Math.max(0, bytes - tailBytes), bytes),
2429
+ `[${label} archived: ${bytes} bytes; projection budget ${maxBytes} bytes]`
2430
+ ].join("\n")
2431
+ };
2432
+ };
2354
2433
  resolveDefaultShell = (input) => {
2355
2434
  const shell = input || process.env.SHELL || userShell() || "/bin/sh";
2356
2435
  return shell.trim() || "/bin/sh";
@@ -2434,10 +2513,11 @@ ${stderr}`)
2434
2513
  if (bytes <= maxBytes) {
2435
2514
  return value;
2436
2515
  }
2437
- const truncated = Buffer.from(value).subarray(0, maxBytes).toString("utf8");
2516
+ const truncated = sliceBytes(value, 0, maxBytes);
2438
2517
  return `${truncated}
2439
2518
  [${label} truncated: ${bytes} bytes > ${maxBytes} bytes]`;
2440
2519
  };
2520
+ sliceBytes = (value, start, end) => Buffer.from(value).subarray(start, end).toString("utf8");
2441
2521
  textResult = (text, details) => ({
2442
2522
  content: [{ type: "text", text }],
2443
2523
  details
@@ -3736,7 +3816,7 @@ function assertTreeEvent(value) {
3736
3816
  throw new SessionStoreError("invalid_event", "skill_index_delta is missing delta payload");
3737
3817
  }
3738
3818
  }
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;
3819
+ var SessionStoreError, SessionTree, JsonlSession, sessionFilePath, sessionLogFilePath, sessionArtifactsDirPath, 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;
3740
3820
  var init_session = __esm({
3741
3821
  "packages/core/src/session/index.ts"() {
3742
3822
  "use strict";
@@ -3917,6 +3997,7 @@ var init_session = __esm({
3917
3997
  };
3918
3998
  sessionFilePath = (sessionsDir, sessionId) => join7(sessionsDir, `${sessionId}.jsonl`);
3919
3999
  sessionLogFilePath = (sessionsDir, sessionId) => join7(sessionsDir, `${sessionId}.log`);
4000
+ sessionArtifactsDirPath = (sessionsDir, sessionId) => join7(sessionsDir, `${sessionId}.artifacts`);
3920
4001
  createSession = async ({ sessionsDir, header }) => {
3921
4002
  const validHeader = parseHeader(header);
3922
4003
  await mkdir4(sessionsDir, { recursive: true });
@@ -4704,7 +4785,7 @@ import { basename as basename3, dirname as dirname8, join as join10, resolve as
4704
4785
  import { pathToFileURL } from "node:url";
4705
4786
  import { promisify as promisify2 } from "node:util";
4706
4787
  import { WebSocketServer } from "ws";
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;
4788
+ 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, parseQueuedModelSelection, imBindingKey, defaultBuiltinExtensionsDir, runtimeModuleDir, findBuiltinExtensionsDir, isSteerMessage, stripImCommandPrefix, isRecord8, parseMemoryUpdate, normalizeMarkdownFile2, sanitizeSessionTitle, shortStack, formatDiagnosticLine, formatDiagnosticValue;
4708
4789
  var init_src4 = __esm({
4709
4790
  "packages/daemon/src/index.ts"() {
4710
4791
  "use strict";
@@ -4945,6 +5026,7 @@ var init_src4 = __esm({
4945
5026
  for (const tool of createCodingTools({
4946
5027
  cwd: options.cwd,
4947
5028
  contextWindow: model.contextWindow,
5029
+ ...options.sessionsDir && options.sessionId ? { toolResultArtifacts: { dir: sessionArtifactsDirPath(options.sessionsDir, options.sessionId) } } : {},
4948
5030
  tokenSaving: {
4949
5031
  rtk: {
4950
5032
  enabled: options.config.runtime.tokenSavingRtk,
@@ -4985,6 +5067,7 @@ var init_src4 = __esm({
4985
5067
  #registry;
4986
5068
  #runtimeStatsQueue = Promise.resolve();
4987
5069
  #idleShutdownTimer;
5070
+ #lastActiveWorkAt;
4988
5071
  #started = false;
4989
5072
  constructor(options) {
4990
5073
  this.#sessionsDir = options.sessionsDir;
@@ -5003,6 +5086,7 @@ var init_src4 = __esm({
5003
5086
  this.#onIdleShutdown = options.onIdleShutdown;
5004
5087
  this.#now = options.now ?? Date.now;
5005
5088
  this.#createId = options.createId ?? (() => crypto.randomUUID());
5089
+ this.#lastActiveWorkAt = this.#now();
5006
5090
  this.#registry = new ProjectRegistry({
5007
5091
  sessionsDir: this.#sessionsDir,
5008
5092
  projectsPath: options.projectsPath,
@@ -5067,6 +5151,13 @@ var init_src4 = __esm({
5067
5151
  releaseSessionEventBuffer(sessionId) {
5068
5152
  this.#events.delete(sessionId);
5069
5153
  }
5154
+ activityStatus() {
5155
+ const activeWork = this.#hasActiveWork();
5156
+ if (activeWork) {
5157
+ this.#lastActiveWorkAt = this.#now();
5158
+ }
5159
+ return { activeWork, lastActiveWorkAt: this.#lastActiveWorkAt };
5160
+ }
5070
5161
  async handleMessage(connection, message) {
5071
5162
  this.#assertStarted();
5072
5163
  try {
@@ -5355,6 +5446,7 @@ var init_src4 = __esm({
5355
5446
  content: normalizeContent(request.content),
5356
5447
  parentId: request.options?.parentId,
5357
5448
  source: "user",
5449
+ modelSelection: request.options?.modelSelection,
5358
5450
  channelContext: request.options?.channelContext ? runtimeChannelContextFromWire(request.options.channelContext) : void 0,
5359
5451
  onComplete: (result) => this.#respond(connection, request, { ...result, status: "completed" })
5360
5452
  });
@@ -5380,11 +5472,14 @@ var init_src4 = __esm({
5380
5472
  });
5381
5473
  }
5382
5474
  async #runUserTurn(lane, clientId, input) {
5475
+ this.#lastActiveWorkAt = this.#now();
5383
5476
  const sessionId = lane.session.header.sessionId;
5477
+ await this.#selectChatRuntime(lane, input.modelSelection);
5384
5478
  await this.#appendDiagnostic(sessionId, "send_message_started", {
5385
5479
  clientId,
5386
5480
  activeLeafId: lane.session.activeLeafId,
5387
- source: input.source
5481
+ source: input.source,
5482
+ selectedModelId: lane.selectedModel?.modelId
5388
5483
  });
5389
5484
  const instructionSnapshot = await this.#ensureInstructionSnapshot(lane, clientId);
5390
5485
  await this.#syncSkillIndex(lane, clientId);
@@ -5588,7 +5683,7 @@ var init_src4 = __esm({
5588
5683
  createdAt: now,
5589
5684
  updatedAt: now,
5590
5685
  clientId: connection.clientId,
5591
- ...request.options?.channelContext ? { data: { channelContext: request.options.channelContext } } : {}
5686
+ ...request.options?.channelContext || request.options?.modelSelection ? { data: { channelContext: request.options.channelContext, modelSelection: request.options.modelSelection } } : {}
5592
5687
  };
5593
5688
  lane.followUpWaiters.set(item.id, { connection, request });
5594
5689
  await this.#appendQueueRewrite(lane, "follow_up", [...lane.session.tree.controlState.queues.follow_up, item], {
@@ -5641,6 +5736,7 @@ var init_src4 = __esm({
5641
5736
  parentId: lane.session.activeLeafId,
5642
5737
  source: "follow_up",
5643
5738
  queueItemId: item.id,
5739
+ modelSelection: parseQueuedModelSelection(item.data?.modelSelection),
5644
5740
  channelContext: parseQueuedChannelContext(item.data?.channelContext),
5645
5741
  onComplete: waiter ? (result) => this.#respond(waiter.connection, waiter.request, { ...result, status: "completed" }) : void 0
5646
5742
  });
@@ -6623,6 +6719,7 @@ var init_src4 = __esm({
6623
6719
  session: loaded,
6624
6720
  project,
6625
6721
  runtime,
6722
+ ...selectedModel ? { selectedModel } : {},
6626
6723
  queue: Promise.resolve(),
6627
6724
  appendQueue: Promise.resolve(),
6628
6725
  followUpWaiters: /* @__PURE__ */ new Map()
@@ -6674,6 +6771,7 @@ var init_src4 = __esm({
6674
6771
  session,
6675
6772
  project,
6676
6773
  runtime,
6774
+ ...selectedModel ? { selectedModel } : {},
6677
6775
  queue: Promise.resolve(),
6678
6776
  appendQueue: Promise.resolve(),
6679
6777
  followUpWaiters: /* @__PURE__ */ new Map()
@@ -6689,6 +6787,32 @@ var init_src4 = __esm({
6689
6787
  })
6690
6788
  );
6691
6789
  }
6790
+ async #selectChatRuntime(lane, modelSelection) {
6791
+ if (!modelSelection) {
6792
+ return;
6793
+ }
6794
+ const selectedModel = await this.#selectedModelFromMeta(
6795
+ { projectId: lane.project.projectId, modelSelection },
6796
+ lane.project
6797
+ );
6798
+ if (!selectedModel || lane.selectedModel?.modelId === selectedModel.modelId) {
6799
+ return;
6800
+ }
6801
+ lane.runtime = await this.#createRuntime({
6802
+ sessionId: lane.session.header.sessionId,
6803
+ project: lane.project,
6804
+ selectedModel,
6805
+ purpose: "chat"
6806
+ });
6807
+ lane.selectedModel = selectedModel;
6808
+ this.#registerLaneTools(lane);
6809
+ await this.#appendDiagnostic(lane.session.header.sessionId, "chat_model_selected", {
6810
+ projectId: lane.project.projectId,
6811
+ workDir: lane.project.workDir,
6812
+ selectedModelId: selectedModel.modelId,
6813
+ role: selectedModel.role
6814
+ });
6815
+ }
6692
6816
  #syncChannelTool(lane, channelContext) {
6693
6817
  if (!channelContext) {
6694
6818
  lane.runtime.unregisterTool("SendChannelMessage");
@@ -7249,9 +7373,10 @@ var init_src4 = __esm({
7249
7373
  }
7250
7374
  const persistedSelection = "selectedModel" in meta ? meta.selectedModel : void 0;
7251
7375
  const requestedSelection = "modelSelection" in meta ? meta.modelSelection : void 0;
7376
+ const selectionInput = persistedSelection ? config.models[persistedSelection.modelId] ? { modelId: persistedSelection.modelId, role: persistedSelection.role } : persistedSelection.role ? { role: persistedSelection.role } : void 0 : requestedSelection;
7252
7377
  const selection = resolveModelSelection(
7253
7378
  config,
7254
- persistedSelection ? { modelId: persistedSelection.modelId, role: persistedSelection.role } : requestedSelection
7379
+ selectionInput
7255
7380
  );
7256
7381
  const model = resolvePiAiModel(selection.config);
7257
7382
  return {
@@ -7642,6 +7767,19 @@ var init_src4 = __esm({
7642
7767
  ...isRecord8(value.data) ? { data: value.data } : {}
7643
7768
  });
7644
7769
  };
7770
+ parseQueuedModelSelection = (value) => {
7771
+ if (!isRecord8(value)) {
7772
+ return void 0;
7773
+ }
7774
+ const selection = {};
7775
+ if (typeof value.modelId === "string") {
7776
+ selection.modelId = value.modelId;
7777
+ }
7778
+ if (value.role === "primary" || value.role === "standard" || value.role === "auxiliary") {
7779
+ selection.role = value.role;
7780
+ }
7781
+ return selection.modelId || selection.role ? selection : void 0;
7782
+ };
7645
7783
  imBindingKey = (extensionId, externalConversationId) => `${extensionId}:${externalConversationId}`;
7646
7784
  defaultBuiltinExtensionsDir = () => findBuiltinExtensionsDir([
7647
7785
  runtimeModuleDir(),
@@ -7777,25 +7915,131 @@ var init_relay_cli = __esm({
7777
7915
  }
7778
7916
  });
7779
7917
 
7918
+ // apps/cli/src/update-cli.ts
7919
+ import { execFile as execFileCallback } from "node:child_process";
7920
+ import { readFile as readFile12 } from "node:fs/promises";
7921
+ import { dirname as dirname9, join as join12 } from "node:path";
7922
+ import { fileURLToPath } from "node:url";
7923
+ import { promisify as promisify3 } from "node:util";
7924
+ var SCOREL_PACKAGE_NAME, AUTO_UPDATE_INTERVAL_MS, ACTIVE_WORK_STALE_MS, execFileAsync3, compareSemver, shouldRunAutoUpdate, createNpmPackageUpdater, readInstalledScorelVersion, runCliUpdate, writeUpdateUsage, parseSemver;
7925
+ var init_update_cli = __esm({
7926
+ "apps/cli/src/update-cli.ts"() {
7927
+ "use strict";
7928
+ SCOREL_PACKAGE_NAME = "@chanlerdev/scorel";
7929
+ AUTO_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
7930
+ ACTIVE_WORK_STALE_MS = 3 * 60 * 60 * 1e3;
7931
+ execFileAsync3 = promisify3(execFileCallback);
7932
+ compareSemver = (a, b) => {
7933
+ const left = parseSemver(a);
7934
+ const right = parseSemver(b);
7935
+ for (let index = 0; index < 3; index += 1) {
7936
+ const delta = left[index] - right[index];
7937
+ if (delta !== 0) return delta;
7938
+ }
7939
+ return 0;
7940
+ };
7941
+ shouldRunAutoUpdate = (activity) => !activity.activeWork || activity.now - activity.lastActiveWorkAt >= ACTIVE_WORK_STALE_MS;
7942
+ createNpmPackageUpdater = (options) => {
7943
+ const packageName = options.packageName ?? SCOREL_PACKAGE_NAME;
7944
+ const execFile3 = options.execFile ?? ((command, argv) => execFileAsync3(command, argv));
7945
+ return {
7946
+ async checkLatest() {
7947
+ const result = await execFile3("npm", ["view", packageName, "version"]);
7948
+ const latest = result.stdout.trim();
7949
+ if (!latest) {
7950
+ throw new Error(`npm did not return a latest version for ${packageName}`);
7951
+ }
7952
+ parseSemver(latest);
7953
+ return latest;
7954
+ },
7955
+ async update() {
7956
+ const latestVersion = await this.checkLatest();
7957
+ if (compareSemver(options.currentVersion, latestVersion) >= 0) {
7958
+ return { status: "current", currentVersion: options.currentVersion, latestVersion };
7959
+ }
7960
+ await execFile3("npm", ["install", "-g", `${packageName}@${latestVersion}`]);
7961
+ return { status: "updated", currentVersion: options.currentVersion, latestVersion };
7962
+ }
7963
+ };
7964
+ };
7965
+ readInstalledScorelVersion = async () => {
7966
+ const here = dirname9(fileURLToPath(import.meta.url));
7967
+ for (const candidate of [
7968
+ join12(here, "..", "package.json"),
7969
+ join12(here, "..", "..", "package.json"),
7970
+ join12(process.cwd(), "package.json")
7971
+ ]) {
7972
+ try {
7973
+ const parsed = JSON.parse(await readFile12(candidate, "utf8"));
7974
+ if (typeof parsed.version === "string" && (parsed.name === SCOREL_PACKAGE_NAME || parsed.name === "@scorel/app-cli")) {
7975
+ return parsed.version;
7976
+ }
7977
+ } catch {
7978
+ }
7979
+ }
7980
+ return "0.0.0";
7981
+ };
7982
+ runCliUpdate = async (argv, io, options = {}) => {
7983
+ if (argv.includes("--help") || argv.includes("-h")) {
7984
+ writeUpdateUsage(io.output);
7985
+ return 0;
7986
+ }
7987
+ if (argv.length > 0) {
7988
+ writeUpdateUsage(io.error);
7989
+ return 1;
7990
+ }
7991
+ const currentVersion = options.currentVersion ?? await readInstalledScorelVersion();
7992
+ const updater = options.updater ?? createNpmPackageUpdater({ currentVersion });
7993
+ try {
7994
+ const result = await updater.update();
7995
+ if (result.status === "current") {
7996
+ io.output.write(`scorel is current (${result.currentVersion})
7997
+ `);
7998
+ } else {
7999
+ io.output.write(`updated scorel ${result.currentVersion} -> ${result.latestVersion}
8000
+ `);
8001
+ }
8002
+ return 0;
8003
+ } catch (cause) {
8004
+ io.error.write(`scorel update error: ${cause instanceof Error ? cause.message : String(cause)}
8005
+ `);
8006
+ return 1;
8007
+ }
8008
+ };
8009
+ writeUpdateUsage = (output) => {
8010
+ output.write("Usage: scorel update\n scorel upgrade\n");
8011
+ };
8012
+ parseSemver = (version) => {
8013
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version);
8014
+ if (!match) {
8015
+ throw new Error(`Invalid semver version: ${version}`);
8016
+ }
8017
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
8018
+ };
8019
+ }
8020
+ });
8021
+
7780
8022
  // apps/cli/src/daemon-cli.ts
7781
8023
  import { randomUUID as randomUUID4 } from "node:crypto";
7782
8024
  import { spawn } from "node:child_process";
7783
8025
  import { homedir as homedir6 } from "node:os";
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;
8026
+ import { dirname as dirname10, join as join13 } from "node:path";
8027
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
8028
+ var DEFAULT_HOST, DEFAULT_PORT, STOP_POLL_INTERVAL_MS, STOP_GRACE_MS, START_READY_TIMEOUT_MS, AUTO_STARTED_IDLE_SHUTDOWN_MS, FOREGROUND_IDLE_SHUTDOWN_MS, defaultStateDir2, isLoopbackHost, formatTimestamp, runCliDaemon, runStartCommand, runServeCommand, startAutoUpdateLoop, stopRunningDaemon, runStatusCommand, runStopCommand, runResetCommand, formatStatusLine, parseServeFlags, parseStatusFlags, requireValue2, sleep, waitForDaemonReady, detachBackgroundDaemon, nodeEntrypointArgs, writeDaemonUsage;
7787
8029
  var init_daemon_cli = __esm({
7788
8030
  "apps/cli/src/daemon-cli.ts"() {
7789
8031
  "use strict";
7790
8032
  init_src4();
7791
8033
  init_relay_cli();
8034
+ init_update_cli();
7792
8035
  DEFAULT_HOST = "127.0.0.1";
7793
8036
  DEFAULT_PORT = 7777;
7794
8037
  STOP_POLL_INTERVAL_MS = 200;
7795
8038
  STOP_GRACE_MS = 5e3;
7796
8039
  START_READY_TIMEOUT_MS = 1e4;
7797
- DEFAULT_IDLE_SHUTDOWN_MS = 15 * 60 * 1e3;
7798
- defaultStateDir2 = () => join12(homedir6(), ".scorel");
8040
+ AUTO_STARTED_IDLE_SHUTDOWN_MS = 15 * 60 * 1e3;
8041
+ FOREGROUND_IDLE_SHUTDOWN_MS = 0;
8042
+ defaultStateDir2 = () => join13(homedir6(), ".scorel");
7799
8043
  isLoopbackHost = (host) => host === "127.0.0.1" || host === "::1" || host === "localhost";
7800
8044
  formatTimestamp = (epochMs) => new Date(epochMs).toISOString();
7801
8045
  runCliDaemon = async (argv, options) => {
@@ -7824,7 +8068,7 @@ var init_daemon_cli = __esm({
7824
8068
  runStartCommand = async (argv, options) => {
7825
8069
  let flags;
7826
8070
  try {
7827
- flags = parseServeFlags(argv, options.cwd ?? process.cwd(), options.env ?? process.env);
8071
+ flags = parseServeFlags(argv, options.cwd ?? process.cwd(), options.env ?? process.env, FOREGROUND_IDLE_SHUTDOWN_MS);
7828
8072
  } catch (cause) {
7829
8073
  options.error.write(`scorel daemon start error: ${cause.message}
7830
8074
  `);
@@ -7837,7 +8081,7 @@ var init_daemon_cli = __esm({
7837
8081
  `);
7838
8082
  return 0;
7839
8083
  }
7840
- const cliEntrypoint = options.cliEntrypoint ?? fileURLToPath(import.meta.url).replace(/daemon-cli\.ts$/, "index.ts");
8084
+ const cliEntrypoint = options.cliEntrypoint ?? fileURLToPath2(import.meta.url).replace(/daemon-cli\.ts$/, "index.ts");
7841
8085
  const child = (options.spawn ?? spawn)(process.execPath, [
7842
8086
  ...nodeEntrypointArgs(cliEntrypoint),
7843
8087
  "host",
@@ -7854,7 +8098,7 @@ var init_daemon_cli = __esm({
7854
8098
  ...flags.relayUrl ? ["--relay", flags.relayUrl] : ["--no-relay"],
7855
8099
  ...flags.replace ? ["--replace"] : []
7856
8100
  ], {
7857
- cwd: dirname9(cliEntrypoint),
8101
+ cwd: dirname10(cliEntrypoint),
7858
8102
  env: { ...process.env, ...options.env ?? {} },
7859
8103
  detached: true,
7860
8104
  stdio: ["ignore", "pipe", "pipe"]
@@ -7881,7 +8125,7 @@ var init_daemon_cli = __esm({
7881
8125
  runServeCommand = async (argv, options) => {
7882
8126
  let flags;
7883
8127
  try {
7884
- flags = parseServeFlags(argv, options.cwd ?? process.cwd(), options.env ?? process.env);
8128
+ flags = parseServeFlags(argv, options.cwd ?? process.cwd(), options.env ?? process.env, FOREGROUND_IDLE_SHUTDOWN_MS);
7885
8129
  } catch (cause) {
7886
8130
  options.error.write(`scorel daemon serve error: ${cause.message}
7887
8131
  `);
@@ -7914,9 +8158,10 @@ Use --replace to stop it and start a new one.
7914
8158
  stopRequested = true;
7915
8159
  resolveStopWaiter?.();
7916
8160
  };
8161
+ const sessionsDir = options.sessionsDir ?? scorelSessionsDir(homedir6());
7917
8162
  const daemon = new ScorelHost({
7918
- sessionsDir: options.sessionsDir ?? scorelSessionsDir(homedir6()),
7919
- projectsPath: join12(options.stateDir, "projects.json"),
8163
+ sessionsDir,
8164
+ projectsPath: join13(options.stateDir, "projects.json"),
7920
8165
  deviceId: identity.deviceId,
7921
8166
  deviceDisplayName: identity.displayName,
7922
8167
  idleShutdownMs: flags.idleShutdownMs,
@@ -7924,15 +8169,25 @@ Use --replace to stop it and start a new one.
7924
8169
  scorelHomeDir: options.stateDir,
7925
8170
  loadConfig: async ({ project }) => loadScorelConfig({ cwd: project.workDir, ...configScope }),
7926
8171
  loadConfigProfile: async ({ project }) => loadScorelConfigProfile({ cwd: project.workDir, ...configScope }),
7927
- createRuntime: async ({ project, selectedModel, purpose }) => createRealRuntime({
8172
+ createRuntime: async ({ sessionId, project, selectedModel, purpose }) => createRealRuntime({
7928
8173
  cwd: project.workDir,
7929
8174
  config: await loadScorelConfig({ cwd: project.workDir, ...configScope }),
8175
+ sessionsDir,
8176
+ sessionId,
7930
8177
  modelSelection: selectedModel ? { modelId: selectedModel.modelId, role: selectedModel.role } : void 0,
7931
8178
  includeTools: purpose === "chat"
7932
8179
  })
7933
8180
  });
7934
8181
  await daemon.start();
7935
8182
  await daemon.registerProject(flags.cwd);
8183
+ const autoUpdater = await startAutoUpdateLoop({
8184
+ host: daemon,
8185
+ requestStop,
8186
+ output: options.output,
8187
+ error: options.error,
8188
+ updater: options.packageUpdater,
8189
+ intervalMs: options.autoUpdateIntervalMs ?? AUTO_UPDATE_INTERVAL_MS
8190
+ });
7936
8191
  const server = await startScorelHostWebSocketServer({
7937
8192
  hostService: daemon,
7938
8193
  host: flags.host,
@@ -7978,6 +8233,7 @@ Use --replace to stop it and start a new one.
7978
8233
  }
7979
8234
  const shutdown = async () => {
7980
8235
  try {
8236
+ autoUpdater.stop();
7981
8237
  relayClient?.close();
7982
8238
  await server.close();
7983
8239
  } finally {
@@ -8028,6 +8284,42 @@ Use --replace to stop it and start a new one.
8028
8284
  `);
8029
8285
  return 0;
8030
8286
  };
8287
+ startAutoUpdateLoop = async (options) => {
8288
+ const updater = options.updater ?? createNpmPackageUpdater({ currentVersion: await readInstalledScorelVersion() });
8289
+ let timer;
8290
+ let running = false;
8291
+ const tick = async () => {
8292
+ if (running) return;
8293
+ running = true;
8294
+ try {
8295
+ const activity = options.host.activityStatus();
8296
+ if (!shouldRunAutoUpdate({ ...activity, now: Date.now() })) {
8297
+ return;
8298
+ }
8299
+ const result = await updater.update();
8300
+ if (result.status === "updated") {
8301
+ options.output.write(`scorel auto-updated ${result.currentVersion} -> ${result.latestVersion}; restarting host
8302
+ `);
8303
+ options.requestStop("auto-update");
8304
+ }
8305
+ } catch (cause) {
8306
+ options.error.write(`scorel auto-update error: ${cause instanceof Error ? cause.message : String(cause)}
8307
+ `);
8308
+ } finally {
8309
+ running = false;
8310
+ }
8311
+ };
8312
+ timer = setInterval(() => void tick(), options.intervalMs);
8313
+ timer.unref?.();
8314
+ return {
8315
+ stop() {
8316
+ if (timer) {
8317
+ clearInterval(timer);
8318
+ timer = void 0;
8319
+ }
8320
+ }
8321
+ };
8322
+ };
8031
8323
  stopRunningDaemon = async (state, options) => {
8032
8324
  try {
8033
8325
  process.kill(state.pid, "SIGTERM");
@@ -8131,14 +8423,14 @@ Use --replace to stop it and start a new one.
8131
8423
  const stoppedAt = state.stoppedAt !== null ? formatTimestamp(state.stoppedAt) : "unknown";
8132
8424
  return `stopped url=${state.wsUrl} last-pid=${state.pid} stoppedAt=${stoppedAt} liveness=${liveness}`;
8133
8425
  };
8134
- parseServeFlags = (argv, defaultCwd, env) => {
8426
+ parseServeFlags = (argv, defaultCwd, env, defaultIdleShutdownMs) => {
8135
8427
  let host = DEFAULT_HOST;
8136
8428
  let port = DEFAULT_PORT;
8137
8429
  let cwd = defaultCwd;
8138
8430
  let token;
8139
8431
  let relayUrl = resolveDefaultRelayUrl(env);
8140
8432
  let replace = false;
8141
- let idleShutdownMs = DEFAULT_IDLE_SHUTDOWN_MS;
8433
+ let idleShutdownMs = defaultIdleShutdownMs;
8142
8434
  for (let index = 0; index < argv.length; index += 1) {
8143
8435
  const arg = argv[index];
8144
8436
  if (arg === "--host") {
@@ -8475,8 +8767,8 @@ var init_routing = __esm({
8475
8767
  });
8476
8768
 
8477
8769
  // apps/relay/src/store.ts
8478
- import { mkdir as mkdir7, readFile as readFile12, writeFile as writeFile7 } from "node:fs/promises";
8479
- import { join as join13 } from "node:path";
8770
+ import { mkdir as mkdir7, readFile as readFile13, writeFile as writeFile7 } from "node:fs/promises";
8771
+ import { join as join14 } from "node:path";
8480
8772
  var FileRelayStore, emptyStoreFile;
8481
8773
  var init_store = __esm({
8482
8774
  "apps/relay/src/store.ts"() {
@@ -8486,7 +8778,7 @@ var init_store = __esm({
8486
8778
  #now;
8487
8779
  #queue = Promise.resolve();
8488
8780
  constructor(options) {
8489
- this.#filePath = join13(options.dataDir, "relay-store.json");
8781
+ this.#filePath = join14(options.dataDir, "relay-store.json");
8490
8782
  this.#now = options.now ?? Date.now;
8491
8783
  }
8492
8784
  async upsertDevice(record) {
@@ -8529,7 +8821,7 @@ var init_store = __esm({
8529
8821
  this.#queue = this.#queue.then(async () => {
8530
8822
  const file = await this.#read();
8531
8823
  mutator(file);
8532
- await mkdir7(join13(this.#filePath, ".."), { recursive: true });
8824
+ await mkdir7(join14(this.#filePath, ".."), { recursive: true });
8533
8825
  await writeFile7(this.#filePath, `${JSON.stringify(file, null, 2)}
8534
8826
  `);
8535
8827
  });
@@ -8537,7 +8829,7 @@ var init_store = __esm({
8537
8829
  }
8538
8830
  async #read() {
8539
8831
  try {
8540
- const raw = JSON.parse(await readFile12(this.#filePath, "utf8"));
8832
+ const raw = JSON.parse(await readFile13(this.#filePath, "utf8"));
8541
8833
  if (raw.version !== 1 || !Array.isArray(raw.devices) || !Array.isArray(raw.clients) || !Array.isArray(raw.bindings)) {
8542
8834
  return emptyStoreFile();
8543
8835
  }
@@ -8790,7 +9082,7 @@ var init_library = __esm({
8790
9082
 
8791
9083
  // apps/cli/src/relay-server-cli.ts
8792
9084
  import { homedir as homedir7 } from "node:os";
8793
- import { join as join14 } from "node:path";
9085
+ import { join as join15 } from "node:path";
8794
9086
  var DEFAULT_HOST2, DEFAULT_PORT2, runCliRelay, runRelayServe, parseRelayServeFlags, waitForStop, requireValue3, writeRelayUsage;
8795
9087
  var init_relay_server_cli = __esm({
8796
9088
  "apps/cli/src/relay-server-cli.ts"() {
@@ -8842,7 +9134,7 @@ var init_relay_server_cli = __esm({
8842
9134
  parseRelayServeFlags = (argv) => {
8843
9135
  let host = DEFAULT_HOST2;
8844
9136
  let port = DEFAULT_PORT2;
8845
- let dataDir = join14(homedir7(), ".scorel", "relay");
9137
+ let dataDir = join15(homedir7(), ".scorel", "relay");
8846
9138
  for (let index = 0; index < argv.length; index += 1) {
8847
9139
  const arg = argv[index];
8848
9140
  if (arg === "--host") {
@@ -8900,17 +9192,18 @@ var init_relay_server_cli = __esm({
8900
9192
  // apps/cli/src/up-cli.ts
8901
9193
  import { spawn as spawn2 } from "node:child_process";
8902
9194
  import { homedir as homedir8 } from "node:os";
8903
- import { dirname as dirname10, join as join15 } from "node:path";
8904
- import { fileURLToPath as fileURLToPath2 } from "node:url";
9195
+ import { dirname as dirname11, join as join16 } from "node:path";
9196
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
8905
9197
  var DEFAULT_DAEMON_PORT, DEFAULT_WEBUI_PORT, DEFAULT_DAEMON_READY_TIMEOUT_MS, defaultStateDir3, defaultAttachSigint, runCliUp, parseUpFlags, requireValue4, waitForDaemonReady2, pipeWithPrefix, detachBackgroundDaemon2, nodeEntrypointArgs2, pipeStreamLines, once;
8906
9198
  var init_up_cli = __esm({
8907
9199
  "apps/cli/src/up-cli.ts"() {
8908
9200
  "use strict";
8909
9201
  init_src4();
9202
+ init_daemon_cli();
8910
9203
  DEFAULT_DAEMON_PORT = 7777;
8911
9204
  DEFAULT_WEBUI_PORT = 3e3;
8912
9205
  DEFAULT_DAEMON_READY_TIMEOUT_MS = 1e4;
8913
- defaultStateDir3 = () => join15(homedir8(), ".scorel");
9206
+ defaultStateDir3 = () => join16(homedir8(), ".scorel");
8914
9207
  defaultAttachSigint = (listener) => {
8915
9208
  process.on("SIGINT", listener);
8916
9209
  return () => process.off("SIGINT", listener);
@@ -8925,7 +9218,7 @@ var init_up_cli = __esm({
8925
9218
  return 1;
8926
9219
  }
8927
9220
  const stateDir = options.stateDir ?? defaultStateDir3();
8928
- const cliEntrypoint = options.cliEntrypoint ?? fileURLToPath2(import.meta.url).replace(/up-cli\.ts$/, "index.ts");
9221
+ const cliEntrypoint = options.cliEntrypoint ?? fileURLToPath3(import.meta.url).replace(/up-cli\.ts$/, "index.ts");
8929
9222
  const spawnFn = options.spawn ?? spawn2;
8930
9223
  const readState = options.readState ?? ((dir) => readLocalDaemonState({ stateDir: dir }));
8931
9224
  const attachSigint = options.attachSigint ?? defaultAttachSigint;
@@ -8944,10 +9237,12 @@ var init_up_cli = __esm({
8944
9237
  String(flags.daemonPort),
8945
9238
  "--cwd",
8946
9239
  flags.cwd,
9240
+ "--idle-timeout-ms",
9241
+ String(AUTO_STARTED_IDLE_SHUTDOWN_MS),
8947
9242
  "--no-relay"
8948
9243
  ];
8949
9244
  daemonChild = spawnFn(process.execPath, daemonArgs, {
8950
- cwd: dirname10(cliEntrypoint),
9245
+ cwd: dirname11(cliEntrypoint),
8951
9246
  env: { ...process.env },
8952
9247
  detached: true,
8953
9248
  stdio: ["ignore", "pipe", "pipe"]
@@ -8977,7 +9272,7 @@ var init_up_cli = __esm({
8977
9272
  String(flags.webuiPort)
8978
9273
  ];
8979
9274
  const webuiChild = spawnFn(process.execPath, webuiArgs, {
8980
- cwd: dirname10(cliEntrypoint),
9275
+ cwd: dirname11(cliEntrypoint),
8981
9276
  env: { ...process.env },
8982
9277
  stdio: ["ignore", "pipe", "pipe"]
8983
9278
  });
@@ -9142,8 +9437,8 @@ var init_up_cli = __esm({
9142
9437
  // apps/cli/src/webui-cli.ts
9143
9438
  import { spawn as spawn3 } from "node:child_process";
9144
9439
  import { existsSync as existsSync4 } from "node:fs";
9145
- import { dirname as dirname11, resolve as resolve6 } from "node:path";
9146
- import { fileURLToPath as fileURLToPath3 } from "node:url";
9440
+ import { dirname as dirname12, resolve as resolve6 } from "node:path";
9441
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
9147
9442
  var DEFAULT_PORT3, DEFAULT_HOST3, runCliWebUi, findWebuiAppDir, buildWebUiSpawnPlan, parseWebUiFlags, requireValue5, waitForChildExit;
9148
9443
  var init_webui_cli = __esm({
9149
9444
  "apps/cli/src/webui-cli.ts"() {
@@ -9174,7 +9469,7 @@ var init_webui_cli = __esm({
9174
9469
  return await waitForChildExit(child, options);
9175
9470
  };
9176
9471
  findWebuiAppDir = () => {
9177
- let cursor = dirname11(fileURLToPath3(import.meta.url));
9472
+ let cursor = dirname12(fileURLToPath4(import.meta.url));
9178
9473
  for (let depth = 0; depth < 8; depth += 1) {
9179
9474
  const candidate = resolve6(cursor, "apps/webui/package.json");
9180
9475
  if (existsSync4(candidate)) {
@@ -9263,11 +9558,11 @@ __export(index_exports, {
9263
9558
  runCli: () => runCli
9264
9559
  });
9265
9560
  import { createHash as createHash3 } from "node:crypto";
9266
- import { appendFile as appendFile4, mkdir as mkdir8, readFile as readFile13, realpath as realpath3, readdir as readdir7, writeFile as writeFile8 } from "node:fs/promises";
9561
+ import { appendFile as appendFile4, mkdir as mkdir8, readFile as readFile14, realpath as realpath3, readdir as readdir7, writeFile as writeFile8 } from "node:fs/promises";
9267
9562
  import { createInterface } from "node:readline/promises";
9268
9563
  import { homedir as homedir9 } from "node:os";
9269
- import { fileURLToPath as fileURLToPath4 } from "node:url";
9270
- import { basename as basename4, dirname as dirname12, join as join16 } from "node:path";
9564
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
9565
+ import { basename as basename4, dirname as dirname13, join as join17 } from "node:path";
9271
9566
  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;
9272
9567
  var init_index = __esm({
9273
9568
  async "apps/cli/src/index.ts"() {
@@ -9280,13 +9575,19 @@ var init_index = __esm({
9280
9575
  init_relay_server_cli();
9281
9576
  init_up_cli();
9282
9577
  init_webui_cli();
9578
+ init_update_cli();
9283
9579
  cliAppName = "@scorel/app-cli";
9284
9580
  cliClientDependency = clientPackageName;
9285
9581
  cliDaemonDependency = daemonPackageName;
9286
9582
  defaultSessionsDir = () => scorelSessionsDir(homedir9());
9287
- defaultStateDir4 = () => join16(homedir9(), ".scorel");
9583
+ defaultStateDir4 = () => join17(homedir9(), ".scorel");
9288
9584
  runCli = async (argv, io = { input: process.stdin, output: process.stdout, error: process.stderr }, runOptions = {}) => {
9289
9585
  const [command, ...rest] = argv;
9586
+ if (command === "--version" || command === "-v" || command === "version") {
9587
+ io.output.write(`${await readInstalledScorelVersion()}
9588
+ `);
9589
+ return 0;
9590
+ }
9290
9591
  if (!command || command === "chat") {
9291
9592
  if (rest.includes("--help") || rest.includes("-h")) {
9292
9593
  writeUsage(io.output);
@@ -9332,6 +9633,9 @@ var init_index = __esm({
9332
9633
  error: io.error
9333
9634
  });
9334
9635
  }
9636
+ if (command === "update" || command === "upgrade") {
9637
+ return runCliUpdate(rest, { output: io.output, error: io.error });
9638
+ }
9335
9639
  if (command === "attach") {
9336
9640
  try {
9337
9641
  return runAttach(parseAttachOptions(rest), {
@@ -9419,10 +9723,10 @@ var init_index = __esm({
9419
9723
  }
9420
9724
  };
9421
9725
  runLogs = async (options, io) => {
9422
- const filePath = options.attach ? await findAttachDiagnosticsFilePath(io.stateDir, options.sessionId, options.remoteUrl) : join16(io.sessionsDir, `${options.sessionId}.log`);
9726
+ const filePath = options.attach ? await findAttachDiagnosticsFilePath(io.stateDir, options.sessionId, options.remoteUrl) : join17(io.sessionsDir, `${options.sessionId}.log`);
9423
9727
  let content;
9424
9728
  try {
9425
- content = await readFile13(filePath, "utf8");
9729
+ content = await readFile14(filePath, "utf8");
9426
9730
  } catch (cause) {
9427
9731
  io.error.write(`scorel logs error: ${cause instanceof Error ? cause.message : String(cause)}
9428
9732
  `);
@@ -9576,31 +9880,31 @@ var init_index = __esm({
9576
9880
  };
9577
9881
  attachCacheFilePath = (stateDir, scope, sessionId) => {
9578
9882
  const scopeKey = createHash3("sha256").update(`${scope.kind}\0${scope.locator}`).digest("hex").slice(0, 24);
9579
- return join16(stateDir, "attach-cache", scopeKey, `${sessionId}.json`);
9883
+ return join17(stateDir, "attach-cache", scopeKey, `${sessionId}.json`);
9580
9884
  };
9581
9885
  attachDiagnosticsFilePath = (stateDir, scope, sessionId) => {
9582
9886
  const scopeKey = createHash3("sha256").update(`${scope.kind}\0${scope.locator}`).digest("hex").slice(0, 24);
9583
- return join16(stateDir, "attach-cache", scopeKey, `${sessionId}.log`);
9887
+ return join17(stateDir, "attach-cache", scopeKey, `${sessionId}.log`);
9584
9888
  };
9585
9889
  findAttachDiagnosticsFilePath = async (stateDir, sessionId, _remoteUrl) => {
9586
- const root = join16(stateDir, "attach-cache");
9890
+ const root = join17(stateDir, "attach-cache");
9587
9891
  const scopes = await readdir7(root).catch(() => []);
9588
9892
  for (const scope of scopes) {
9589
- const candidate = join16(root, scope, `${sessionId}.log`);
9893
+ const candidate = join17(root, scope, `${sessionId}.log`);
9590
9894
  try {
9591
- await readFile13(candidate, "utf8");
9895
+ await readFile14(candidate, "utf8");
9592
9896
  return candidate;
9593
9897
  } catch {
9594
9898
  continue;
9595
9899
  }
9596
9900
  }
9597
- return join16(root, "__missing__", `${sessionId}.log`);
9901
+ return join17(root, "__missing__", `${sessionId}.log`);
9598
9902
  };
9599
9903
  stateDirFromSessionsDir = (sessionsDir) => {
9600
9904
  if (!sessionsDir) {
9601
9905
  return defaultStateDir4();
9602
9906
  }
9603
- return basename4(sessionsDir) === "sessions" ? dirname12(sessionsDir) : sessionsDir;
9907
+ return basename4(sessionsDir) === "sessions" ? dirname13(sessionsDir) : sessionsDir;
9604
9908
  };
9605
9909
  AttachDiagnostics = class {
9606
9910
  #stateDir;
@@ -9652,14 +9956,14 @@ var init_index = __esm({
9652
9956
  }
9653
9957
  const filePath = attachDiagnosticsFilePath(this.#stateDir, this.#scope, this.#sessionId);
9654
9958
  this.#writes.push(
9655
- mkdir8(dirname12(filePath), { recursive: true }).then(() => appendFile4(filePath, `${line}
9959
+ mkdir8(dirname13(filePath), { recursive: true }).then(() => appendFile4(filePath, `${line}
9656
9960
  `, "utf8"))
9657
9961
  );
9658
9962
  }
9659
9963
  };
9660
9964
  readAttachCache = async (stateDir, scope, sessionId) => {
9661
9965
  try {
9662
- const raw = JSON.parse(await readFile13(attachCacheFilePath(stateDir, scope, sessionId), "utf8"));
9966
+ const raw = JSON.parse(await readFile14(attachCacheFilePath(stateDir, scope, sessionId), "utf8"));
9663
9967
  if (raw.version !== 1 || raw.sessionId !== String(sessionId) || raw.scope.kind !== scope.kind || raw.scope.locator !== scope.locator || !Array.isArray(raw.events)) {
9664
9968
  return emptyAttachCacheSnapshot();
9665
9969
  }
@@ -9682,7 +9986,7 @@ var init_index = __esm({
9682
9986
  const filePath = attachCacheFilePath(stateDir, scope, sessionId);
9683
9987
  const uniqueEvents = mergePersistentEvents(snapshot.events);
9684
9988
  const transients = removeCompletedTransients(snapshot.transients, uniqueEvents);
9685
- await mkdir8(dirname12(filePath), { recursive: true });
9989
+ await mkdir8(dirname13(filePath), { recursive: true });
9686
9990
  await writeFile8(
9687
9991
  filePath,
9688
9992
  `${JSON.stringify({ version: 1, scope, sessionId: String(sessionId), events: uniqueEvents, transients }, null, 2)}
@@ -9824,14 +10128,16 @@ var init_index = __esm({
9824
10128
  const loadProjectConfigProfile = async (project2) => options.config ?? await loadScorelConfigProfile({ cwd: project2.workDir, ...configScope });
9825
10129
  const daemon = new ScorelHost({
9826
10130
  sessionsDir: options.sessionsDir,
9827
- projectsPath: join16(options.stateDir, "projects.json"),
10131
+ projectsPath: join17(options.stateDir, "projects.json"),
9828
10132
  deviceId: asDeviceId("device_local"),
9829
10133
  scorelHomeDir: options.stateDir,
9830
10134
  loadConfig: async ({ project: project2 }) => loadProjectConfig(project2),
9831
10135
  loadConfigProfile: async ({ project: project2 }) => loadProjectConfigProfile(project2),
9832
- createRuntime: async ({ project: project2, selectedModel, purpose }) => createRealRuntime({
10136
+ createRuntime: async ({ sessionId, project: project2, selectedModel, purpose }) => createRealRuntime({
9833
10137
  cwd: project2.workDir,
9834
10138
  config: await loadProjectConfig(project2),
10139
+ sessionsDir: options.sessionsDir,
10140
+ sessionId,
9835
10141
  modelSelection: selectedModel ? { modelId: selectedModel.modelId, role: selectedModel.role } : void 0,
9836
10142
  includeTools: purpose === "chat"
9837
10143
  })
@@ -9971,6 +10277,9 @@ var init_index = __esm({
9971
10277
  " scorel relay serve [--host <h>] [--port <p>] [--data-dir <dir>]",
9972
10278
  " scorel webui [--port <p>] [--host <h>]",
9973
10279
  " scorel up [--daemon-port <p>] [--webui-port <p>] [--cwd <d>]",
10280
+ " scorel update",
10281
+ " scorel upgrade",
10282
+ " scorel version",
9974
10283
  " scorel logs [--attach] --session <id> [--remote <ws-url>] [--tail <n>]",
9975
10284
  " scorel project list",
9976
10285
  " scorel project add <dir>",
@@ -10102,7 +10411,7 @@ ${text}
10102
10411
  if (!process.argv[1]) return false;
10103
10412
  const [argvPath, modulePath] = await Promise.all([
10104
10413
  realpath3(process.argv[1]).catch(() => process.argv[1]),
10105
- realpath3(fileURLToPath4(import.meta.url)).catch(() => fileURLToPath4(import.meta.url))
10414
+ realpath3(fileURLToPath5(import.meta.url)).catch(() => fileURLToPath5(import.meta.url))
10106
10415
  ]);
10107
10416
  return argvPath === modulePath;
10108
10417
  };