scream-code 0.11.0 → 0.11.2

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 (30) hide show
  1. package/dist/{app-CuAxNnOv.mjs → app-8_tNuOds.mjs} +447 -81
  2. package/dist/main.mjs +1 -1
  3. package/dist/public/assets/index-BJI4roHX.css +1 -0
  4. package/dist/public/assets/index-mUEA7L6U.js +90 -0
  5. package/dist/public/assets/index-mUEA7L6U.js.map +1 -0
  6. package/dist/public/index.html +2 -2
  7. package/package.json +1 -1
  8. package/dist/public/assets/bash-Yzrsuije.js +0 -2
  9. package/dist/public/assets/bash-Yzrsuije.js.map +0 -1
  10. package/dist/public/assets/css-DPfMkruS.js +0 -2
  11. package/dist/public/assets/css-DPfMkruS.js.map +0 -1
  12. package/dist/public/assets/diff-D97Zzqfu.js +0 -2
  13. package/dist/public/assets/diff-D97Zzqfu.js.map +0 -1
  14. package/dist/public/assets/html-GMplVEZG.js +0 -2
  15. package/dist/public/assets/html-GMplVEZG.js.map +0 -1
  16. package/dist/public/assets/index-2xNGacMZ.js +0 -91
  17. package/dist/public/assets/index-2xNGacMZ.js.map +0 -1
  18. package/dist/public/assets/index-CufIY2bi.css +0 -1
  19. package/dist/public/assets/javascript-wDzz0qaB.js +0 -2
  20. package/dist/public/assets/javascript-wDzz0qaB.js.map +0 -1
  21. package/dist/public/assets/json-Cp-IABpG.js +0 -2
  22. package/dist/public/assets/json-Cp-IABpG.js.map +0 -1
  23. package/dist/public/assets/python-B6aJPvgy.js +0 -2
  24. package/dist/public/assets/python-B6aJPvgy.js.map +0 -1
  25. package/dist/public/assets/typescript-BPQ3VLAy.js +0 -2
  26. package/dist/public/assets/typescript-BPQ3VLAy.js.map +0 -1
  27. package/dist/public/assets/vue-DVfSmUF7.js +0 -2
  28. package/dist/public/assets/vue-DVfSmUF7.js.map +0 -1
  29. package/dist/public/assets/yaml-Buea-lGh.js +0 -2
  30. package/dist/public/assets/yaml-Buea-lGh.js.map +0 -1
@@ -717,7 +717,7 @@ function isOrphanedToolCallMessage(lowerMessage) {
717
717
  */
718
718
  function isOrphanedToolCallError(error) {
719
719
  if (error instanceof APIOrphanedToolCallError) return true;
720
- return isOrphanedToolCallMessage(errorMessage$6(error).toLowerCase());
720
+ return isOrphanedToolCallMessage(errorMessage$7(error).toLowerCase());
721
721
  }
722
722
  /**
723
723
  * The API returned an empty response (no content, no tool calls).
@@ -764,7 +764,7 @@ function isContextOverflowStatusError(statusCode, message) {
764
764
  const lowerMessage = message.toLowerCase();
765
765
  return CONTEXT_OVERFLOW_MESSAGE_PATTERNS.some((pattern) => pattern.test(lowerMessage));
766
766
  }
767
- function errorMessage$6(error) {
767
+ function errorMessage$7(error) {
768
768
  return error instanceof Error ? error.message : String(error);
769
769
  }
770
770
  //#endregion
@@ -47296,7 +47296,7 @@ function isAbortError$1(err) {
47296
47296
  if (err instanceof Error) return err.name === "AbortError";
47297
47297
  return false;
47298
47298
  }
47299
- function errorMessage$5(err) {
47299
+ function errorMessage$6(err) {
47300
47300
  if (err instanceof Error) return err.message;
47301
47301
  return String(err);
47302
47302
  }
@@ -52494,7 +52494,7 @@ var GoalMode = class {
52494
52494
  const state = this.state;
52495
52495
  if (state === void 0 || state.status !== "active") return null;
52496
52496
  state.tokensUsed += Math.max(0, tokenDelta);
52497
- this.persistState(state, { silent: true });
52497
+ this.persistState(state);
52498
52498
  this.appendGoalUpdate({ tokensUsed: state.tokensUsed });
52499
52499
  return this.toSnapshot(state);
52500
52500
  }
@@ -52516,7 +52516,7 @@ var GoalMode = class {
52516
52516
  time: Date.now()
52517
52517
  });
52518
52518
  if (state.notes.length > MAX_GOAL_NOTES) state.notes = state.notes.slice(-60);
52519
- this.persistState(state, { silent: true });
52519
+ this.persistState(state);
52520
52520
  return this.toSnapshot(state);
52521
52521
  }
52522
52522
  clearInternal(actor, opts = {}) {
@@ -52581,7 +52581,7 @@ var GoalMode = class {
52581
52581
  wallClockMs: liveWallClockMs(state, Date.now()),
52582
52582
  budget: computeBudgetReport(state, Date.now()),
52583
52583
  terminalReason: state.terminalReason,
52584
- notes: state.notes
52584
+ notes: state.notes.map((note) => ({ ...note }))
52585
52585
  };
52586
52586
  }
52587
52587
  };
@@ -52865,7 +52865,7 @@ var UpdateGoalTool = class {
52865
52865
  try {
52866
52866
  await goal.pauseGoal({ reason: "verifying" }, "system");
52867
52867
  } catch (error) {
52868
- return toolError(`Failed to pause goal for verification: ${errorMessage$4(error)}`, goal);
52868
+ return toolError(`Failed to pause goal for verification: ${errorMessage$5(error)}`, goal);
52869
52869
  }
52870
52870
  let rawGrade;
52871
52871
  try {
@@ -52873,7 +52873,7 @@ var UpdateGoalTool = class {
52873
52873
  } catch (error) {
52874
52874
  const resumeError = await resumeAfterGrading(goal);
52875
52875
  if (resumeError !== void 0) return resumeError;
52876
- const reason = `Goal verification could not be completed: ${errorMessage$4(error)}`;
52876
+ const reason = `Goal verification could not be completed: ${errorMessage$5(error)}`;
52877
52877
  this.appendGradingFeedback(reason);
52878
52878
  return {
52879
52879
  isError: true,
@@ -52901,7 +52901,7 @@ var UpdateGoalTool = class {
52901
52901
  name: GOAL_COMPLETION_REMINDER_NAME
52902
52902
  });
52903
52903
  } catch (error) {
52904
- return toolError(`Failed to mark verified goal complete: ${errorMessage$4(error)}`, goal);
52904
+ return toolError(`Failed to mark verified goal complete: ${errorMessage$5(error)}`, goal);
52905
52905
  }
52906
52906
  return {
52907
52907
  output: `Goal verified and marked complete.\n${grade.reason}`,
@@ -52936,7 +52936,7 @@ async function resumeAfterGrading(goal) {
52936
52936
  await goal.resumeGoal({}, "system");
52937
52937
  return;
52938
52938
  } catch (error) {
52939
- return toolError(`Failed to restore active goal after verification: ${errorMessage$4(error)}`, goal);
52939
+ return toolError(`Failed to restore active goal after verification: ${errorMessage$5(error)}`, goal);
52940
52940
  }
52941
52941
  }
52942
52942
  function toolError(message, goal) {
@@ -52945,7 +52945,7 @@ function toolError(message, goal) {
52945
52945
  output: `${message}. Current goal status: ${goal.getGoal().goal?.status ?? "missing"}.`
52946
52946
  };
52947
52947
  }
52948
- function errorMessage$4(error) {
52948
+ function errorMessage$5(error) {
52949
52949
  return error instanceof Error ? error.message : String(error);
52950
52950
  }
52951
52951
  //#endregion
@@ -78351,7 +78351,7 @@ async function runHook(command, input, options) {
78351
78351
  detached: process.platform !== "win32"
78352
78352
  });
78353
78353
  } catch (error) {
78354
- return allowResult({ stderr: errorMessage$3(error) });
78354
+ return allowResult({ stderr: errorMessage$4(error) });
78355
78355
  }
78356
78356
  return new Promise((resolve) => {
78357
78357
  let stdout = "";
@@ -78399,7 +78399,7 @@ async function runHook(command, input, options) {
78399
78399
  child.on("error", (error) => {
78400
78400
  settle(allowResult({
78401
78401
  stdout,
78402
- stderr: stderr + errorMessage$3(error)
78402
+ stderr: stderr + errorMessage$4(error)
78403
78403
  }));
78404
78404
  });
78405
78405
  child.on("close", (code) => {
@@ -78495,7 +78495,7 @@ function tryKillProcess(child, signal) {
78495
78495
  function isRecord$5(value) {
78496
78496
  return typeof value === "object" && value !== null && !Array.isArray(value);
78497
78497
  }
78498
- function errorMessage$3(error) {
78498
+ function errorMessage$4(error) {
78499
78499
  return error instanceof Error ? error.message : String(error);
78500
78500
  }
78501
78501
  //#endregion
@@ -89581,7 +89581,7 @@ function parseToolCallArguments(raw) {
89581
89581
  } catch {
89582
89582
  return {
89583
89583
  success: false,
89584
- error: errorMessage$5(error)
89584
+ error: errorMessage$6(error)
89585
89585
  };
89586
89586
  }
89587
89587
  }
@@ -89661,7 +89661,7 @@ async function prepareToolCall(step, call) {
89661
89661
  toolCallId: call.toolCall.id,
89662
89662
  error
89663
89663
  });
89664
- return settleError(effectiveArgs, error instanceof PathSecurityError ? error.message : `Tool "${call.toolName}" failed to resolve execution: ${errorMessage$5(error)}`);
89664
+ return settleError(effectiveArgs, error instanceof PathSecurityError ? error.message : `Tool "${call.toolName}" failed to resolve execution: ${errorMessage$6(error)}`);
89665
89665
  }
89666
89666
  const displayFields = toolCallDisplayFieldsFromExecution(execution);
89667
89667
  const settleAborted = () => settleError(effectiveArgs, abortedToolOutput(call.toolName, step.signal), displayFields);
@@ -89720,7 +89720,7 @@ async function runPrepareToolExecutionHook(step, call) {
89720
89720
  return {
89721
89721
  kind: "hookFailed",
89722
89722
  args,
89723
- output: `prepareToolExecution hook failed for "${call.toolName}": ${errorMessage$5(error)}`
89723
+ output: `prepareToolExecution hook failed for "${call.toolName}": ${errorMessage$6(error)}`
89724
89724
  };
89725
89725
  }
89726
89726
  const effectiveArgs = hookResult?.updatedArgs ?? args;
@@ -89761,7 +89761,7 @@ async function runAuthorizeToolExecutionHook(step, call, args, execution) {
89761
89761
  };
89762
89762
  return {
89763
89763
  block: true,
89764
- reason: `authorizeToolExecution hook failed for "${call.toolName}": ${errorMessage$5(error)}`
89764
+ reason: `authorizeToolExecution hook failed for "${call.toolName}": ${errorMessage$6(error)}`
89765
89765
  };
89766
89766
  }
89767
89767
  }
@@ -89788,7 +89788,7 @@ async function runRunnableToolCall(step, call, effectiveArgs, metadata, executio
89788
89788
  toolCallId: toolCall.id,
89789
89789
  error
89790
89790
  });
89791
- return makeErrorToolResult(call, effectiveArgs, aborted ? abortedToolOutput(toolName, signal) : `Tool "${toolName}" failed: ${errorMessage$5(error)}`);
89791
+ return makeErrorToolResult(call, effectiveArgs, aborted ? abortedToolOutput(toolName, signal) : `Tool "${toolName}" failed: ${errorMessage$6(error)}`);
89792
89792
  }
89793
89793
  return makeToolResult(call, effectiveArgs, toolResult);
89794
89794
  }
@@ -89820,7 +89820,7 @@ async function finalizePendingToolResult(step, pendingResult) {
89820
89820
  toolCallId: pendingResult.toolCall.id,
89821
89821
  error
89822
89822
  });
89823
- const output = aborted ? `Tool "${pendingResult.toolName}" aborted during finalizeToolResult hook.` : `finalizeToolResult hook failed for "${pendingResult.toolName}": ${errorMessage$5(error)}`;
89823
+ const output = aborted ? `Tool "${pendingResult.toolName}" aborted during finalizeToolResult hook.` : `finalizeToolResult hook failed for "${pendingResult.toolName}": ${errorMessage$6(error)}`;
89824
89824
  return {
89825
89825
  ...pendingResult,
89826
89826
  stopTurn: pendingResult.stopTurn,
@@ -90178,7 +90178,7 @@ async function runTurn(input) {
90178
90178
  usage
90179
90179
  };
90180
90180
  }
90181
- dispatchEvent(makeInterruptedEvent(isMaxStepsExceededError(error) ? "max_steps" : "error", steps, activeStep, errorMessage$5(error)));
90181
+ dispatchEvent(makeInterruptedEvent(isMaxStepsExceededError(error) ? "max_steps" : "error", steps, activeStep, errorMessage$6(error)));
90182
90182
  throw error;
90183
90183
  }
90184
90184
  return {
@@ -94455,6 +94455,13 @@ var LspRegistry = class {
94455
94455
  };
94456
94456
  //#endregion
94457
94457
  //#region ../../packages/agent-core/src/agent/tool/index.ts
94458
+ function cloneTodos(todos) {
94459
+ return todos.map((todo) => ({
94460
+ title: todo.title,
94461
+ status: todo.status,
94462
+ phase: todo.phase
94463
+ }));
94464
+ }
94458
94465
  const CRITERIA_SYSTEM_PROMPT = [
94459
94466
  "You generate concrete, verifiable acceptance criteria for a given objective.",
94460
94467
  "Criteria must be specific and testable — state what should work end-to-end, not just what should exist.",
@@ -94645,12 +94652,20 @@ var ToolManager = class {
94645
94652
  });
94646
94653
  }
94647
94654
  updateStore(key, value) {
94655
+ const storedValue = key === "todo" ? cloneTodos(value ?? []) : value;
94648
94656
  this.agent.records.logRecord({
94649
94657
  type: "tools.update_store",
94650
94658
  key,
94651
- value
94659
+ value: storedValue
94660
+ });
94661
+ this.store[key] = storedValue;
94662
+ if (key === "todo") this.agent.emitEvent({
94663
+ type: "todo.updated",
94664
+ todos: this.getTodos()
94652
94665
  });
94653
- this.store[key] = value;
94666
+ }
94667
+ getTodos() {
94668
+ return cloneTodos(this.store.todo ?? []);
94654
94669
  }
94655
94670
  registerUserTool(input) {
94656
94671
  this.agent.records.logRecord({
@@ -96700,6 +96715,9 @@ var Agent = class {
96700
96715
  getGoal: () => {
96701
96716
  return this.goal.getGoal();
96702
96717
  },
96718
+ getTodos: () => {
96719
+ return this.tools.getTodos();
96720
+ },
96703
96721
  setGoalBudget: async (payload) => {
96704
96722
  const { value, unit } = payload;
96705
96723
  let budgetLimits;
@@ -114592,7 +114610,7 @@ function markitConversionCacheKey(bytes, extension) {
114592
114610
  function cacheEntryPath(key) {
114593
114611
  return path$8.join(documentConversionCacheDir(), `${key}.json`);
114594
114612
  }
114595
- function errorMessage$2(error) {
114613
+ function errorMessage$3(error) {
114596
114614
  return error instanceof Error ? error.message : String(error);
114597
114615
  }
114598
114616
  function parseCacheEntry(raw) {
@@ -114611,14 +114629,14 @@ async function readMarkitConversionCache(key) {
114611
114629
  try {
114612
114630
  raw = await fs$1.readFile(target, "utf8");
114613
114631
  } catch (error) {
114614
- if (!isEnoent(error)) log.debug("document conversion cache read failed", { error: errorMessage$2(error) });
114632
+ if (!isEnoent(error)) log.debug("document conversion cache read failed", { error: errorMessage$3(error) });
114615
114633
  return { status: "miss" };
114616
114634
  }
114617
114635
  let entry;
114618
114636
  try {
114619
114637
  entry = parseCacheEntry(raw);
114620
114638
  } catch (error) {
114621
- log.debug("document conversion cache read failed", { error: errorMessage$2(error) });
114639
+ log.debug("document conversion cache read failed", { error: errorMessage$3(error) });
114622
114640
  entry = null;
114623
114641
  }
114624
114642
  if (entry === null) {
@@ -114635,7 +114653,7 @@ async function pruneMarkitConversionCache(cacheDir) {
114635
114653
  try {
114636
114654
  names = await fs$1.readdir(cacheDir);
114637
114655
  } catch (error) {
114638
- if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$2(error) });
114656
+ if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$3(error) });
114639
114657
  return;
114640
114658
  }
114641
114659
  const now = Date.now();
@@ -114647,7 +114665,7 @@ async function pruneMarkitConversionCache(cacheDir) {
114647
114665
  try {
114648
114666
  stat = await fs$1.stat(entryPath);
114649
114667
  } catch (error) {
114650
- if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$2(error) });
114668
+ if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$3(error) });
114651
114669
  continue;
114652
114670
  }
114653
114671
  if (!stat.isFile()) continue;
@@ -114671,7 +114689,7 @@ async function pruneMarkitConversionCache(cacheDir) {
114671
114689
  await fs$1.rm(entry.path, { force: true });
114672
114690
  totalBytes -= entry.size;
114673
114691
  } catch (error) {
114674
- if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$2(error) });
114692
+ if (!isEnoent(error)) log.debug("document conversion cache prune failed", { error: errorMessage$3(error) });
114675
114693
  }
114676
114694
  }
114677
114695
  }
@@ -114689,11 +114707,11 @@ async function writeMarkitConversionCache(key, content) {
114689
114707
  await fs$1.rename(tempPath, target);
114690
114708
  } catch (error) {
114691
114709
  await fs$1.rm(tempPath, { force: true }).catch(() => void 0);
114692
- log.debug("document conversion cache write failed", { error: errorMessage$2(error) });
114710
+ log.debug("document conversion cache write failed", { error: errorMessage$3(error) });
114693
114711
  return;
114694
114712
  }
114695
114713
  pruneMarkitConversionCache(cacheDir).catch((error) => {
114696
- log.debug("document conversion cache prune failed", { error: errorMessage$2(error) });
114714
+ log.debug("document conversion cache prune failed", { error: errorMessage$3(error) });
114697
114715
  });
114698
114716
  }
114699
114717
  //#endregion
@@ -116759,6 +116777,9 @@ var SessionAPIImpl = class {
116759
116777
  getGoal({ agentId, ...payload }) {
116760
116778
  return this.getAgent(agentId).getGoal(payload);
116761
116779
  }
116780
+ getTodos({ agentId, ...payload }) {
116781
+ return this.getAgent(agentId).getTodos(payload);
116782
+ }
116762
116783
  setGoalBudget({ agentId, ...payload }) {
116763
116784
  return this.getAgent(agentId).setGoalBudget(payload);
116764
116785
  }
@@ -118504,6 +118525,9 @@ var ScreamCore = class {
118504
118525
  getGoal({ sessionId, ...payload }) {
118505
118526
  return this.sessionApi(sessionId).getGoal(payload);
118506
118527
  }
118528
+ getTodos({ sessionId, ...payload }) {
118529
+ return this.sessionApi(sessionId).getTodos(payload);
118530
+ }
118507
118531
  setGoalBudget({ sessionId, ...payload }) {
118508
118532
  return this.sessionApi(sessionId).setGoalBudget(payload);
118509
118533
  }
@@ -118970,6 +118994,12 @@ var SDKRpcClient = class {
118970
118994
  agentId: this.interactiveAgentId
118971
118995
  });
118972
118996
  }
118997
+ async getTodos(input) {
118998
+ return (await this.getRpc()).getTodos({
118999
+ sessionId: input.sessionId,
119000
+ agentId: this.interactiveAgentId
119001
+ });
119002
+ }
118973
119003
  async setGoalBudget(input) {
118974
119004
  return (await this.getRpc()).setGoalBudget({
118975
119005
  sessionId: input.sessionId,
@@ -119289,7 +119319,7 @@ var SDKRpcClient = class {
119289
119319
  type: "error",
119290
119320
  sessionId: request.sessionId,
119291
119321
  agentId: request.agentId,
119292
- ...makeErrorPayload(ErrorCodes.SESSION_APPROVAL_HANDLER_ERROR, errorMessage$1(error))
119322
+ ...makeErrorPayload(ErrorCodes.SESSION_APPROVAL_HANDLER_ERROR, errorMessage$2(error))
119293
119323
  });
119294
119324
  return {
119295
119325
  decision: "cancelled",
@@ -119307,7 +119337,7 @@ var SDKRpcClient = class {
119307
119337
  type: "error",
119308
119338
  sessionId: request.sessionId,
119309
119339
  agentId: request.agentId,
119310
- ...makeErrorPayload(ErrorCodes.SESSION_QUESTION_HANDLER_ERROR, errorMessage$1(error))
119340
+ ...makeErrorPayload(ErrorCodes.SESSION_QUESTION_HANDLER_ERROR, errorMessage$2(error))
119311
119341
  });
119312
119342
  return null;
119313
119343
  }
@@ -119342,7 +119372,7 @@ var ClientAPI = class {
119342
119372
  return this.client.toolCall(request);
119343
119373
  }
119344
119374
  };
119345
- function errorMessage$1(error) {
119375
+ function errorMessage$2(error) {
119346
119376
  return error instanceof Error ? error.message : String(error);
119347
119377
  }
119348
119378
  //#endregion
@@ -119733,6 +119763,10 @@ var Session = class {
119733
119763
  this.ensureOpen();
119734
119764
  return this.rpc.getGoal({ sessionId: this.id });
119735
119765
  }
119766
+ async getTodos() {
119767
+ this.ensureOpen();
119768
+ return this.rpc.getTodos({ sessionId: this.id });
119769
+ }
119736
119770
  async setGoalBudget(value, unit) {
119737
119771
  this.ensureOpen();
119738
119772
  return this.rpc.setGoalBudget({
@@ -120184,7 +120218,7 @@ function optionalBuildString(value) {
120184
120218
  return typeof value === "string" && value.length > 0 ? value : void 0;
120185
120219
  }
120186
120220
  const SCREAM_BUILD_INFO = {
120187
- version: optionalBuildString("0.11.0"),
120221
+ version: optionalBuildString("0.11.2"),
120188
120222
  channel: optionalBuildString(""),
120189
120223
  commit: optionalBuildString(""),
120190
120224
  buildTarget: optionalBuildString("darwin-arm64")
@@ -120273,7 +120307,7 @@ async function handleExport(deps, sessionId, output, opts) {
120273
120307
  });
120274
120308
  deps.stdout.write(`${result.zipPath}\n`);
120275
120309
  } catch (error) {
120276
- deps.stderr.write(`${errorMessage(error)}\n`);
120310
+ deps.stderr.write(`${errorMessage$1(error)}\n`);
120277
120311
  deps.exit(1);
120278
120312
  }
120279
120313
  }
@@ -120336,7 +120370,7 @@ async function confirmPreviousSession(summary) {
120336
120370
  rl.close();
120337
120371
  }
120338
120372
  }
120339
- function errorMessage(error) {
120373
+ function errorMessage$1(error) {
120340
120374
  return error instanceof Error ? error.message : String(error);
120341
120375
  }
120342
120376
  //#endregion
@@ -125537,6 +125571,28 @@ function detectGoalConflict(state, _action) {
125537
125571
  return null;
125538
125572
  }
125539
125573
  //#endregion
125574
+ //#region src/utils/goal-refiner.md
125575
+ var goal_refiner_default = "You are a goal refiner. Given a brief task description, produce one clear, actionable objective sentence of at most 200 characters. Return only the objective: no explanation, quotation marks, labels, or prefixes.\n";
125576
+ /**
125577
+ * Normalize an LLM-refined objective. Invalid output falls back to the user's
125578
+ * original description so refinement never loses the requested task.
125579
+ */
125580
+ function parseRefinedGoal(rawOutput, fallback) {
125581
+ const normalized = rawOutput.trim().replace(/^```(?:text)?\s*/i, "").replace(/\s*```$/, "").trim().replace(/^(?:objective|goal)\s*:\s*/i, "").replace(/^["'“‘](.*)["'”’]$/s, "$1").replaceAll(/\s+/g, " ").trim();
125582
+ if (normalized.length === 0 || normalized.length > 200) return fallback.trim();
125583
+ return normalized;
125584
+ }
125585
+ /** Refine a goal through the session LLM, with a deterministic raw-input fallback. */
125586
+ async function refineGoal(session, description) {
125587
+ const fallback = description.trim();
125588
+ if (fallback.length === 0) return "";
125589
+ try {
125590
+ return parseRefinedGoal(await session.generateText(goal_refiner_default, fallback), fallback);
125591
+ } catch {
125592
+ return fallback;
125593
+ }
125594
+ }
125595
+ //#endregion
125540
125596
  //#region src/tui/commands/goal.ts
125541
125597
  const GOAL_STATUS_DISMISS_MS = 1e4;
125542
125598
  let activeGoalPanel;
@@ -125633,7 +125689,6 @@ async function createGoal(host, parsed) {
125633
125689
  }
125634
125690
  await showGoalConfigWizard(host, session, parsed.objective, parsed.replace);
125635
125691
  }
125636
- const GOAL_REFINER_SYSTEM_PROMPT = "You are a goal refiner. Given a brief task description, produce a single clear, actionable objective sentence (max 200 chars). Do not add explanations, quotes, or prefixes.";
125637
125692
  /**
125638
125693
  * Guided goal creation: collect a brief task description, refine it via the
125639
125694
  * LLM into a single objective sentence, let the user confirm/edit, then enter
@@ -125659,13 +125714,7 @@ async function guidedGoalSetup(host) {
125659
125714
  });
125660
125715
  if (initialDesc === void 0) return;
125661
125716
  host.showStatus(t("goal.setup_refining"));
125662
- let objective;
125663
- try {
125664
- objective = (await session.generateText(GOAL_REFINER_SYSTEM_PROMPT, initialDesc)).trim();
125665
- if (objective.length === 0) objective = initialDesc;
125666
- } catch {
125667
- objective = initialDesc;
125668
- }
125717
+ const objective = await refineGoal(session, initialDesc);
125669
125718
  const confirmed = await promptText(host, TextInputDialogComponent, {
125670
125719
  title: t("goal.setup_title_confirm"),
125671
125720
  subtitle: t("goal.setup_confirm_hint"),
@@ -131153,7 +131202,7 @@ async function readMarketplaceText(location, fetchImpl) {
131153
131202
  }
131154
131203
  function parseMarketplaceEntry(value, index, location) {
131155
131204
  if (!isRecord$3(value)) throw new TypeError(`插件市场条目 ${index + 1} 必须是一个对象.`);
131156
- const id = requiredString(value, "id", index);
131205
+ const id = requiredString$1(value, "id", index);
131157
131206
  const source = stringField$1(value, "source") ?? stringField$1(value, "url") ?? stringField$1(value, "downloadUrl");
131158
131207
  if (source === void 0) throw new Error(`插件市场条目 ${id} 必须定义 "source".`);
131159
131208
  return {
@@ -131188,7 +131237,7 @@ function resolveLocalPath(input, workDir) {
131188
131237
  if (input.startsWith("~/")) return join(homedir(), input.slice(2));
131189
131238
  return isAbsolute(input) ? input : resolve(workDir, input);
131190
131239
  }
131191
- function requiredString(value, field, index) {
131240
+ function requiredString$1(value, field, index) {
131192
131241
  const result = stringField$1(value, field);
131193
131242
  if (result === void 0) throw new Error(`插件市场条目 ${index + 1} 必须定义 "${field}".`);
131194
131243
  return result;
@@ -135727,6 +135776,9 @@ var SessionEventHandler = class {
135727
135776
  case "goal.updated":
135728
135777
  this.handleGoalUpdated(event);
135729
135778
  break;
135779
+ case "todo.updated":
135780
+ this.handleTodoUpdated(event);
135781
+ break;
135730
135782
  default: break;
135731
135783
  }
135732
135784
  }
@@ -135830,7 +135882,7 @@ var SessionEventHandler = class {
135830
135882
  handleTurnEnd(event, sendQueued) {
135831
135883
  this.host.setAppState({ reconnectAttempt: 0 });
135832
135884
  const todos = this.host.state.todoPanel.getTodos();
135833
- if (todos.length > 0 && todos.every((t) => t.status === "done")) this.host.streamingUI.setTodoList([]);
135885
+ if (todos.length > 0 && todos.every((todo) => todo.status === "done")) this.host.streamingUI.setTodoList([]);
135834
135886
  this.host.streamingUI.resetToolUi();
135835
135887
  this.host.streamingUI.finalizeTurn(sendQueued);
135836
135888
  }
@@ -136001,17 +136053,7 @@ var SessionEventHandler = class {
136001
136053
  synthetic: event.synthetic,
136002
136054
  display: event.display
136003
136055
  };
136004
- const matchedCall = streamingUI.completeToolResult(event.toolCallId, resultData);
136005
- if (matchedCall !== void 0 && matchedCall.name === "TodoList" && !event.isError) {
136006
- const rawTodos = matchedCall.args.todos;
136007
- if (Array.isArray(rawTodos)) {
136008
- const sanitized = rawTodos.filter((todo) => isTodoItemShape(todo)).map((t) => ({
136009
- title: t.title,
136010
- status: t.status
136011
- }));
136012
- streamingUI.setTodoList(sanitized);
136013
- }
136014
- }
136056
+ streamingUI.completeToolResult(event.toolCallId, resultData);
136015
136057
  if (canTransitionTo(this.host.state.appState.streamingPhase, "waiting")) this.host.setAppState({ streamingPhase: "waiting" });
136016
136058
  }
136017
136059
  handleStatusUpdate(event) {
@@ -136155,6 +136197,9 @@ var SessionEventHandler = class {
136155
136197
  goalActive: snapshot.status === "active"
136156
136198
  });
136157
136199
  }
136200
+ handleTodoUpdated(event) {
136201
+ this.host.streamingUI.setTodoList(event.todos);
136202
+ }
136158
136203
  handleCompactionBegin(event) {
136159
136204
  this.host.streamingUI.finalizeLiveTextBuffers();
136160
136205
  this.lastCompactionTrigger = event.trigger;
@@ -146297,9 +146342,11 @@ function installStreamJsonTerminationHandlers(cleanup) {
146297
146342
  /** Error carrying an HTTP status code for REST handlers. */
146298
146343
  var HttpError = class extends Error {
146299
146344
  statusCode;
146300
- constructor(statusCode, message) {
146345
+ code;
146346
+ constructor(statusCode, message, code) {
146301
146347
  super(message);
146302
146348
  this.statusCode = statusCode;
146349
+ this.code = code;
146303
146350
  }
146304
146351
  };
146305
146352
  const HEARTBEAT_INTERVAL_MS = 3e4;
@@ -146331,6 +146378,16 @@ const contentTypes = {
146331
146378
  ttf: "font/ttf"
146332
146379
  };
146333
146380
  /** Reads and parses a JSON request body (64 KiB cap). */
146381
+ function cloneSnapshot(value) {
146382
+ return structuredClone(value);
146383
+ }
146384
+ function errorMessage(error) {
146385
+ return error instanceof Error ? error.message : String(error);
146386
+ }
146387
+ function sendJson(res, statusCode, body) {
146388
+ res.writeHead(statusCode, { "Content-Type": "application/json" });
146389
+ res.end(JSON.stringify(body));
146390
+ }
146334
146391
  function readJsonBody(req) {
146335
146392
  return new Promise((resolve, reject) => {
146336
146393
  let data = "";
@@ -146354,7 +146411,12 @@ function readJsonBody(req) {
146354
146411
  return;
146355
146412
  }
146356
146413
  try {
146357
- resolve(JSON.parse(data));
146414
+ const parsed = JSON.parse(data);
146415
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
146416
+ reject(new HttpError(400, "JSON body must be an object"));
146417
+ return;
146418
+ }
146419
+ resolve(parsed);
146358
146420
  } catch {
146359
146421
  reject(new HttpError(400, "Invalid JSON body"));
146360
146422
  }
@@ -146362,6 +146424,75 @@ function readJsonBody(req) {
146362
146424
  req.on("error", reject);
146363
146425
  });
146364
146426
  }
146427
+ function requiredString(body, field) {
146428
+ const value = body[field];
146429
+ if (typeof value !== "string" || value.trim().length === 0) throw new HttpError(400, `Missing or empty ${field} field`, "request.invalid");
146430
+ return value.trim();
146431
+ }
146432
+ function optionalString(body, field) {
146433
+ const value = body[field];
146434
+ if (value === void 0) return void 0;
146435
+ if (typeof value !== "string" || value.trim().length === 0) throw new HttpError(400, `${field} must be a non-empty string`, "request.invalid");
146436
+ return value.trim();
146437
+ }
146438
+ function optionalBoolean(body, field, fallback) {
146439
+ const value = body[field];
146440
+ if (value === void 0) return fallback;
146441
+ if (typeof value !== "boolean") throw new HttpError(400, `${field} must be a boolean`, "request.invalid");
146442
+ return value;
146443
+ }
146444
+ function parseGoalBudgets(body) {
146445
+ const raw = body["budgets"];
146446
+ if (raw === void 0) return [];
146447
+ if (!Array.isArray(raw)) throw new HttpError(400, "budgets must be an array", "request.invalid");
146448
+ const validUnits = new Set([
146449
+ "turns",
146450
+ "tokens",
146451
+ "milliseconds",
146452
+ "seconds",
146453
+ "minutes",
146454
+ "hours"
146455
+ ]);
146456
+ const seen = /* @__PURE__ */ new Set();
146457
+ return raw.map((item) => {
146458
+ if (item === null || typeof item !== "object" || Array.isArray(item)) throw new HttpError(400, "Each budget must be an object", "request.invalid");
146459
+ const value = item["value"];
146460
+ const unit = item["unit"];
146461
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new HttpError(400, "Budget value must be a positive safe integer", "request.invalid");
146462
+ if (typeof unit !== "string" || !validUnits.has(unit)) throw new HttpError(400, "Budget unit must be turns, tokens, milliseconds, seconds, minutes, or hours", "request.invalid");
146463
+ const typedUnit = unit;
146464
+ const dimension = typedUnit === "turns" || typedUnit === "tokens" ? typedUnit : "time";
146465
+ if (seen.has(dimension)) throw new HttpError(400, `Duplicate budget dimension: ${dimension}`, "request.invalid");
146466
+ seen.add(dimension);
146467
+ return {
146468
+ value,
146469
+ unit: typedUnit
146470
+ };
146471
+ });
146472
+ }
146473
+ function toHttpError(error) {
146474
+ if (error instanceof HttpError) return error;
146475
+ if (isScreamError(error)) switch (error.code) {
146476
+ case ErrorCodes.GOAL_OBJECTIVE_EMPTY:
146477
+ case ErrorCodes.GOAL_OBJECTIVE_TOO_LONG:
146478
+ case ErrorCodes.REQUEST_INVALID: return new HttpError(400, error.message, error.code);
146479
+ case ErrorCodes.GOAL_NOT_FOUND:
146480
+ case ErrorCodes.SESSION_NOT_FOUND: return new HttpError(404, error.message, error.code);
146481
+ case ErrorCodes.GOAL_ALREADY_EXISTS:
146482
+ case ErrorCodes.GOAL_NOT_RESUMABLE:
146483
+ case ErrorCodes.GOAL_STATUS_INVALID:
146484
+ case ErrorCodes.TURN_AGENT_BUSY: return new HttpError(409, error.message, error.code);
146485
+ default: return new HttpError(500, error.message, error.code);
146486
+ }
146487
+ return new HttpError(500, errorMessage(error));
146488
+ }
146489
+ function sendHttpError(res, error) {
146490
+ const mapped = toHttpError(error);
146491
+ sendJson(res, mapped.statusCode, {
146492
+ code: mapped.code ?? mapped.statusCode,
146493
+ message: mapped.message
146494
+ });
146495
+ }
146365
146496
  function isVolatileEvent(event) {
146366
146497
  switch (event.type) {
146367
146498
  case "assistant.delta":
@@ -146549,6 +146680,14 @@ var WebSession = class {
146549
146680
  nextSeq = 1;
146550
146681
  epoch = 1;
146551
146682
  cachedStatus = null;
146683
+ cachedGoal = null;
146684
+ cachedTodos = [];
146685
+ goalEventRevision = 0;
146686
+ todoEventRevision = 0;
146687
+ goalMutationTail = Promise.resolve();
146688
+ pendingGoalMutations = 0;
146689
+ coreSessionId;
146690
+ ready;
146552
146691
  title = "New Session";
146553
146692
  pendingApprovals = /* @__PURE__ */ new Map();
146554
146693
  heartbeatTimer = null;
@@ -146570,13 +146709,14 @@ var WebSession = class {
146570
146709
  this.yolo = opts.yolo;
146571
146710
  this.homeDir = opts.homeDir ?? null;
146572
146711
  this.createdAt = opts.createdAt;
146712
+ this.coreSessionId = session?.id ?? opts.coreSessionId ?? opts.sessionId;
146573
146713
  this.onFork = opts.onFork;
146574
146714
  if (opts.title) this.title = opts.title;
146575
146715
  if (session) {
146576
- this.refreshStatus();
146577
146716
  this.subscribeEvents();
146578
146717
  this.setupApprovalHandler();
146579
- }
146718
+ this.ready = this.initializeCoreState();
146719
+ } else this.ready = Promise.resolve();
146580
146720
  this.startHeartbeat();
146581
146721
  }
146582
146722
  get isActive() {
@@ -146646,6 +146786,8 @@ var WebSession = class {
146646
146786
  const je = entry;
146647
146787
  this.journal.push(je);
146648
146788
  this.nextSeq = Math.max(this.nextSeq, je.seq + 1);
146789
+ if (this.session === null && je.payload.agentId === "main" && je.payload.type === "goal.updated") this.cachedGoal = cloneSnapshot(je.payload.snapshot);
146790
+ else if (this.session === null && je.payload.agentId === "main" && je.payload.type === "todo.updated") this.cachedTodos = cloneSnapshot(je.payload.todos);
146649
146791
  }
146650
146792
  const msgs = this.buildMessages();
146651
146793
  if (msgs.length > 0 && !this.customTitle) this.title = deriveTitle(msgs);
@@ -146661,6 +146803,7 @@ var WebSession = class {
146661
146803
  getMetadata() {
146662
146804
  return {
146663
146805
  sessionId: this.sessionId,
146806
+ coreSessionId: this.coreSessionId,
146664
146807
  workDir: this.workDir,
146665
146808
  title: this.title,
146666
146809
  createdAt: this.createdAt,
@@ -146669,11 +146812,12 @@ var WebSession = class {
146669
146812
  };
146670
146813
  }
146671
146814
  appendEvent(event) {
146815
+ const payload = event.type === "goal.updated" || event.type === "todo.updated" ? cloneSnapshot(event) : event;
146672
146816
  const entry = {
146673
146817
  seq: this.nextSeq++,
146674
146818
  epoch: this.epoch,
146675
- volatile: isVolatileEvent(event),
146676
- payload: event
146819
+ volatile: isVolatileEvent(payload),
146820
+ payload
146677
146821
  };
146678
146822
  this.journal.push(entry);
146679
146823
  if (!entry.volatile) this.persistEntry(entry);
@@ -146682,10 +146826,17 @@ var WebSession = class {
146682
146826
  subscribeEvents() {
146683
146827
  if (!this.session) return;
146684
146828
  this.unsubscribe = this.session.onEvent((event) => {
146829
+ if ((event.type === "goal.updated" || event.type === "todo.updated") && event.agentId !== "main") return;
146685
146830
  if (event.type === "turn.started") this.busy = true;
146686
146831
  else if (event.type === "turn.ended") {
146687
146832
  this.busy = false;
146688
146833
  this.updateTitle();
146834
+ } else if (event.type === "goal.updated") {
146835
+ this.goalEventRevision += 1;
146836
+ this.cachedGoal = cloneSnapshot(event.snapshot);
146837
+ } else if (event.type === "todo.updated") {
146838
+ this.todoEventRevision += 1;
146839
+ this.cachedTodos = cloneSnapshot(event.todos);
146689
146840
  }
146690
146841
  if (event.type === "session.meta.updated" || event.type === "turn.ended") this.refreshStatus();
146691
146842
  const entry = this.appendEvent(event);
@@ -146693,7 +146844,7 @@ var WebSession = class {
146693
146844
  type: "event",
146694
146845
  seq: entry.seq,
146695
146846
  epoch: entry.epoch,
146696
- payload: event
146847
+ payload: entry.payload
146697
146848
  }, entry.volatile);
146698
146849
  if (event.type === "compaction.completed") {
146699
146850
  const msgId = this.pendingCompactionMsgId;
@@ -146719,6 +146870,20 @@ var WebSession = class {
146719
146870
  }
146720
146871
  });
146721
146872
  }
146873
+ async initializeCoreState() {
146874
+ const session = this.session;
146875
+ if (!session) return;
146876
+ const goalRevision = this.goalEventRevision;
146877
+ const todoRevision = this.todoEventRevision;
146878
+ const [status, goal, todos] = await Promise.all([
146879
+ session.getStatus().catch(() => null),
146880
+ session.getGoal(),
146881
+ session.getTodos()
146882
+ ]);
146883
+ if (status !== null) this.cachedStatus = status;
146884
+ if (this.goalEventRevision === goalRevision) this.cachedGoal = cloneSnapshot(goal.goal);
146885
+ if (this.todoEventRevision === todoRevision) this.cachedTodos = cloneSnapshot(todos);
146886
+ }
146722
146887
  async refreshStatus() {
146723
146888
  if (!this.session) return;
146724
146889
  try {
@@ -146809,7 +146974,7 @@ var WebSession = class {
146809
146974
  });
146810
146975
  return;
146811
146976
  }
146812
- if (!text || this.busy) {
146977
+ if (!text || this.busy || this.pendingGoalMutations > 0) {
146813
146978
  this.send(ws, {
146814
146979
  type: "error",
146815
146980
  code: "session.busy",
@@ -146890,7 +147055,7 @@ var WebSession = class {
146890
147055
  });
146891
147056
  return;
146892
147057
  }
146893
- const missing = this.journal.slice(lastSeq);
147058
+ const missing = this.journal.filter((entry) => entry.seq > lastSeq);
146894
147059
  for (const entry of missing) if (!entry.volatile) this.send(ws, {
146895
147060
  type: "event",
146896
147061
  seq: entry.seq,
@@ -146988,7 +147153,7 @@ var WebSession = class {
146988
147153
  return;
146989
147154
  }
146990
147155
  try {
146991
- const result = await this.onFork(this.session.id);
147156
+ const result = await this.onFork(this.sessionId);
146992
147157
  if (!result) {
146993
147158
  fail("Fork 失败:无法复制当前会话。");
146994
147159
  return;
@@ -147071,7 +147236,7 @@ var WebSession = class {
147071
147236
  }, HEARTBEAT_INTERVAL_MS);
147072
147237
  }
147073
147238
  getSnapshot() {
147074
- return {
147239
+ return cloneSnapshot({
147075
147240
  sessionId: this.sessionId,
147076
147241
  workDir: this.workDir,
147077
147242
  seq: this.nextSeq - 1,
@@ -147097,8 +147262,10 @@ var WebSession = class {
147097
147262
  },
147098
147263
  busy: this.busy,
147099
147264
  createdAt: this.createdAt,
147100
- title: this.title
147101
- };
147265
+ title: this.title,
147266
+ goal: this.cachedGoal,
147267
+ todos: this.cachedTodos
147268
+ });
147102
147269
  }
147103
147270
  getListItem() {
147104
147271
  return {
@@ -147135,6 +147302,114 @@ var WebSession = class {
147135
147302
  status: this.cachedStatus
147136
147303
  }, false);
147137
147304
  }
147305
+ getCoreSessionId() {
147306
+ return this.coreSessionId;
147307
+ }
147308
+ async refineGoalDescription(description) {
147309
+ await this.ready;
147310
+ return refineGoal(this.requireGoalSession(), description);
147311
+ }
147312
+ async createGoal(objective, completionCriterion, replace, budgets) {
147313
+ return this.serializeGoalMutation(async (session) => {
147314
+ let goal = await session.createGoal(objective, {
147315
+ completionCriterion,
147316
+ replace
147317
+ });
147318
+ try {
147319
+ for (const budget of budgets) goal = await session.setGoalBudget(budget.value, budget.unit);
147320
+ } catch (error) {
147321
+ throw new HttpError(500, `Goal configuration failed and the new goal was ${await this.pauseOrClearUnsafeActiveGoal(session)}: ${errorMessage(error)}`);
147322
+ }
147323
+ try {
147324
+ await this.startGoalExecution(session, objective);
147325
+ } catch (error) {
147326
+ throw new HttpError(500, `Goal was created but execution could not start; it was ${await this.pauseOrClearUnsafeActiveGoal(session)}: ${errorMessage(error)}`);
147327
+ }
147328
+ return cloneSnapshot(goal);
147329
+ });
147330
+ }
147331
+ async updateGoal(objective, budgets) {
147332
+ return this.serializeGoalMutation(async (session) => {
147333
+ const current = await session.getGoal();
147334
+ if (current.goal === null) throw new HttpError(404, "Goal not found", ErrorCodes.GOAL_NOT_FOUND);
147335
+ let goal = current.goal;
147336
+ if (objective !== void 0) goal = await session.updateGoalObjective(objective);
147337
+ for (const budget of budgets) goal = await session.setGoalBudget(budget.value, budget.unit);
147338
+ return cloneSnapshot(goal);
147339
+ });
147340
+ }
147341
+ async pauseGoal() {
147342
+ return this.serializeGoalMutation(async (session) => {
147343
+ const goal = await session.updateGoalStatus("paused");
147344
+ if (goal === null) throw new HttpError(404, "Goal not found", ErrorCodes.GOAL_NOT_FOUND);
147345
+ return cloneSnapshot(goal);
147346
+ }, true);
147347
+ }
147348
+ async resumeGoal() {
147349
+ return this.serializeGoalMutation(async (session) => {
147350
+ if ((await session.getGoal()).goal === null) throw new HttpError(404, "Goal not found", ErrorCodes.GOAL_NOT_FOUND);
147351
+ const goal = await session.updateGoalStatus("active");
147352
+ if (goal === null) throw new HttpError(404, "Goal not found", ErrorCodes.GOAL_NOT_FOUND);
147353
+ try {
147354
+ await this.startGoalExecution(session, "Continue working toward the active goal. Review current progress and remaining work before proceeding.");
147355
+ } catch (error) {
147356
+ throw new HttpError(500, `Goal was resumed but execution could not continue; it was ${await this.pauseOrClearUnsafeActiveGoal(session)}: ${errorMessage(error)}`);
147357
+ }
147358
+ return cloneSnapshot(goal);
147359
+ });
147360
+ }
147361
+ async cancelGoal() {
147362
+ await this.serializeGoalMutation(async (session) => {
147363
+ if ((await session.getGoal()).goal === null) throw new HttpError(404, "Goal not found", ErrorCodes.GOAL_NOT_FOUND);
147364
+ await session.cancelGoal();
147365
+ }, true);
147366
+ }
147367
+ requireGoalSession(allowBusy = false) {
147368
+ if (!this.session) throw new HttpError(409, "Session is archived (read-only)", ErrorCodes.SESSION_CLOSED);
147369
+ if (!allowBusy && this.busy) throw new HttpError(409, "Session is busy", ErrorCodes.TURN_AGENT_BUSY);
147370
+ return this.session;
147371
+ }
147372
+ serializeGoalMutation(mutation, allowBusy = false) {
147373
+ this.pendingGoalMutations += 1;
147374
+ const run = this.goalMutationTail.then(async () => {
147375
+ await this.ready;
147376
+ return mutation(this.requireGoalSession(allowBusy));
147377
+ });
147378
+ this.goalMutationTail = run.then(() => void 0, () => void 0);
147379
+ return run.finally(() => {
147380
+ this.pendingGoalMutations -= 1;
147381
+ });
147382
+ }
147383
+ async startGoalExecution(session, prompt) {
147384
+ this.busy = true;
147385
+ await this.persistUserMessage(prompt, this.nextSeq);
147386
+ this.broadcast({
147387
+ type: "user_message",
147388
+ text: prompt
147389
+ }, false);
147390
+ try {
147391
+ await session.prompt(prompt);
147392
+ } catch (error) {
147393
+ this.busy = false;
147394
+ throw error;
147395
+ }
147396
+ }
147397
+ async clearUnsafeActiveGoal(session) {
147398
+ try {
147399
+ await session.cancelGoal();
147400
+ } catch (rollbackError) {
147401
+ throw new HttpError(500, `Goal setup failed and rollback also failed: ${errorMessage(rollbackError)}`);
147402
+ }
147403
+ }
147404
+ async pauseOrClearUnsafeActiveGoal(session) {
147405
+ try {
147406
+ await session.updateGoalStatus("paused");
147407
+ return "paused";
147408
+ } catch {
147409
+ await this.clearUnsafeActiveGoal(session);
147410
+ return "cancelled";
147411
+ }
147412
+ }
147138
147413
  get connectionCount() {
147139
147414
  return this.connections.size;
147140
147415
  }
@@ -147220,6 +147495,64 @@ var WebSession = class {
147220
147495
  if (this.homeDir) await deletePersistedSession(this.homeDir, this.sessionId);
147221
147496
  }
147222
147497
  };
147498
+ async function handleGoalRoute(req, res, url, resolveSession) {
147499
+ const match = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/goal(?:/(refine|pause|resume|cancel))?$`).exec(url);
147500
+ if (!match) return false;
147501
+ const webSession = resolveSession(match[1]);
147502
+ if (!webSession) {
147503
+ sendJson(res, 404, {
147504
+ code: ErrorCodes.SESSION_NOT_FOUND,
147505
+ message: "Session not found"
147506
+ });
147507
+ return true;
147508
+ }
147509
+ const operation = match[2];
147510
+ const method = req.method ?? "GET";
147511
+ const expectedMethod = operation === void 0 ? new Set(["POST", "PATCH"]) : new Set(["POST"]);
147512
+ if (!expectedMethod.has(method)) {
147513
+ res.setHeader("Allow", [...expectedMethod].join(", "));
147514
+ sendJson(res, 405, {
147515
+ code: 405,
147516
+ message: "Method not allowed"
147517
+ });
147518
+ return true;
147519
+ }
147520
+ try {
147521
+ const body = await readJsonBody(req);
147522
+ if (operation === "refine") {
147523
+ sendJson(res, 200, { objective: await webSession.refineGoalDescription(requiredString(body, "description")) });
147524
+ return true;
147525
+ }
147526
+ if (operation === "pause") {
147527
+ await webSession.pauseGoal();
147528
+ sendJson(res, 202, { ok: true });
147529
+ return true;
147530
+ }
147531
+ if (operation === "resume") {
147532
+ await webSession.resumeGoal();
147533
+ sendJson(res, 202, { ok: true });
147534
+ return true;
147535
+ }
147536
+ if (operation === "cancel") {
147537
+ await webSession.cancelGoal();
147538
+ sendJson(res, 202, { ok: true });
147539
+ return true;
147540
+ }
147541
+ const budgets = parseGoalBudgets(body);
147542
+ if (method === "POST") {
147543
+ await webSession.createGoal(requiredString(body, "objective"), optionalString(body, "completionCriterion"), optionalBoolean(body, "replace", false), budgets);
147544
+ sendJson(res, 202, { ok: true });
147545
+ return true;
147546
+ }
147547
+ const objective = optionalString(body, "objective");
147548
+ if (objective === void 0 && body["budgets"] === void 0) throw new HttpError(400, "PATCH requires objective or budgets", "request.invalid");
147549
+ await webSession.updateGoal(objective, budgets);
147550
+ sendJson(res, 202, { ok: true });
147551
+ } catch (error) {
147552
+ sendHttpError(res, error);
147553
+ }
147554
+ return true;
147555
+ }
147223
147556
  var SessionManager = class {
147224
147557
  sessions = /* @__PURE__ */ new Map();
147225
147558
  /** In-flight activation promises to deduplicate concurrent calls. */
@@ -147245,6 +147578,7 @@ var SessionManager = class {
147245
147578
  const entries = await loadJournal(this.homeDir, meta.sessionId);
147246
147579
  const ws = new WebSession(null, {
147247
147580
  sessionId: meta.sessionId,
147581
+ coreSessionId: meta.coreSessionId ?? meta.sessionId,
147248
147582
  workDir: meta.workDir,
147249
147583
  permission: meta.permission,
147250
147584
  yolo: this.yolo,
@@ -147274,6 +147608,12 @@ var SessionManager = class {
147274
147608
  createdAt,
147275
147609
  onFork: (id) => this.forkSession(id)
147276
147610
  });
147611
+ try {
147612
+ await webSession.ready;
147613
+ } catch (error) {
147614
+ await webSession.close();
147615
+ throw error;
147616
+ }
147277
147617
  await saveMetadata(this.homeDir, webSession.getMetadata());
147278
147618
  this.sessions.set(sessionId, webSession);
147279
147619
  log.info("web: session created", {
@@ -147297,13 +147637,8 @@ var SessionManager = class {
147297
147637
  const existing = this.sessions.get(sessionId);
147298
147638
  if (!existing) return null;
147299
147639
  if (existing.isActive) return existing;
147300
- const session = await this.harness.createSession({
147301
- workDir: existing.workDir,
147302
- model: this.model,
147303
- permission: this.permission
147304
- });
147305
147640
  const meta = existing.getMetadata();
147306
- await existing.close();
147641
+ const session = await this.harness.resumeSession({ id: meta.coreSessionId ?? meta.sessionId });
147307
147642
  const reactivated = new WebSession(session, {
147308
147643
  sessionId,
147309
147644
  workDir: meta.workDir,
@@ -147312,10 +147647,18 @@ var SessionManager = class {
147312
147647
  homeDir: this.homeDir,
147313
147648
  createdAt: meta.createdAt,
147314
147649
  title: meta.title,
147650
+ coreSessionId: session.id,
147315
147651
  onFork: (id) => this.forkSession(id)
147316
147652
  });
147317
147653
  const entries = await loadJournal(this.homeDir, sessionId);
147318
147654
  reactivated.loadFromPersisted(entries);
147655
+ try {
147656
+ await reactivated.ready;
147657
+ } catch (error) {
147658
+ await reactivated.close();
147659
+ throw error;
147660
+ }
147661
+ await existing.close();
147319
147662
  this.sessions.set(sessionId, reactivated);
147320
147663
  await saveMetadata(this.homeDir, reactivated.getMetadata());
147321
147664
  log.info("web: session reactivated", { sessionId });
@@ -147333,7 +147676,7 @@ var SessionManager = class {
147333
147676
  const source = this.sessions.get(sourceId);
147334
147677
  if (!source || !source.isActive || source.isBusy) return null;
147335
147678
  try {
147336
- const session = await this.harness.forkSession({ id: sourceId });
147679
+ const session = await this.harness.forkSession({ id: source.getCoreSessionId() });
147337
147680
  const newId = session.id;
147338
147681
  const createdAt = Date.now();
147339
147682
  const title = `${source.getTitle()} (fork)`;
@@ -147353,6 +147696,12 @@ var SessionManager = class {
147353
147696
  await writeFile(getJournalPath(this.homeDir, newId), lines);
147354
147697
  }
147355
147698
  webSession.loadFromPersisted(entries);
147699
+ try {
147700
+ await webSession.ready;
147701
+ } catch (error) {
147702
+ await webSession.close();
147703
+ throw error;
147704
+ }
147356
147705
  await saveMetadata(this.homeDir, webSession.getMetadata());
147357
147706
  this.sessions.set(newId, webSession);
147358
147707
  log.info("web: session forked", {
@@ -147520,6 +147869,7 @@ async function runWebServer(opts) {
147520
147869
  }
147521
147870
  return;
147522
147871
  }
147872
+ if (await handleGoalRoute(req, res, url, (sessionId) => manager.get(sessionId))) return;
147523
147873
  const snapshotMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/snapshot$`).exec(url);
147524
147874
  if (snapshotMatch && method === "GET") {
147525
147875
  const sessionId = snapshotMatch[1];
@@ -147532,8 +147882,12 @@ async function runWebServer(opts) {
147532
147882
  }));
147533
147883
  return;
147534
147884
  }
147535
- res.writeHead(200, { "Content-Type": "application/json" });
147536
- res.end(JSON.stringify(ws.getSnapshot()));
147885
+ try {
147886
+ await ws.ready;
147887
+ sendJson(res, 200, ws.getSnapshot());
147888
+ } catch (error) {
147889
+ sendHttpError(res, error);
147890
+ }
147537
147891
  return;
147538
147892
  }
147539
147893
  if (url === `${API_PREFIX}/git/status` && method === "GET") {
@@ -147701,12 +148055,24 @@ async function runWebServer(opts) {
147701
148055
  manager.activateSession(sessionId).then((reactivated) => {
147702
148056
  if (reactivated) reactivated.addConnection(ws);
147703
148057
  else ws.close(1008, "Session not found");
148058
+ }).catch((error) => {
148059
+ log.warn("web: session activation failed", {
148060
+ sessionId,
148061
+ error: errorMessage(error)
148062
+ });
148063
+ ws.close(1011, "Session activation failed");
147704
148064
  });
147705
148065
  return;
147706
148066
  }
147707
148067
  manager.activateSession(sessionId).then((reactivated) => {
147708
148068
  if (reactivated) reactivated.addConnection(ws);
147709
148069
  else ws.close(1008, "Session not found");
148070
+ }).catch((error) => {
148071
+ log.warn("web: session activation failed", {
148072
+ sessionId,
148073
+ error: errorMessage(error)
148074
+ });
148075
+ ws.close(1011, "Session activation failed");
147710
148076
  });
147711
148077
  return;
147712
148078
  }