oh-my-opencode 3.8.5 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/bin/oh-my-opencode.js +96 -34
  2. package/bin/platform.d.ts +14 -0
  3. package/bin/platform.js +44 -0
  4. package/bin/platform.test.ts +56 -1
  5. package/dist/agents/atlas/agent.d.ts +1 -1
  6. package/dist/agents/env-context.d.ts +1 -1
  7. package/dist/agents/hephaestus.d.ts +1 -1
  8. package/dist/agents/sisyphus.d.ts +1 -1
  9. package/dist/cli/config-manager/antigravity-provider-configuration.d.ts +3 -3
  10. package/dist/cli/index.js +199 -59
  11. package/dist/cli/run/event-state.d.ts +2 -0
  12. package/dist/cli/run/poll-for-completion.d.ts +2 -0
  13. package/dist/config/schema/agent-overrides.d.ts +1 -0
  14. package/dist/config/schema/categories.d.ts +2 -0
  15. package/dist/config/schema/oh-my-opencode-config.d.ts +3 -12
  16. package/dist/features/background-agent/manager.d.ts +9 -0
  17. package/dist/features/boulder-state/storage.d.ts +1 -1
  18. package/dist/features/boulder-state/types.d.ts +2 -0
  19. package/dist/features/builtin-commands/templates/start-work.d.ts +1 -1
  20. package/dist/hooks/anthropic-context-window-limit-recovery/types.d.ts +1 -0
  21. package/dist/hooks/atlas/boulder-continuation-injector.d.ts +1 -0
  22. package/dist/hooks/atlas/types.d.ts +1 -0
  23. package/dist/hooks/background-notification/hook.d.ts +11 -0
  24. package/dist/hooks/no-hephaestus-non-gpt/hook.d.ts +5 -1
  25. package/dist/hooks/ralph-loop/completion-promise-detector.d.ts +1 -0
  26. package/dist/hooks/ralph-loop/loop-state-controller.d.ts +2 -0
  27. package/dist/hooks/ralph-loop/ralph-loop-hook.d.ts +1 -0
  28. package/dist/hooks/ralph-loop/types.d.ts +1 -0
  29. package/dist/hooks/session-notification.d.ts +1 -0
  30. package/dist/hooks/start-work/index.d.ts +3 -0
  31. package/dist/hooks/start-work/parse-user-request.d.ts +5 -0
  32. package/dist/hooks/start-work/worktree-detector.d.ts +1 -0
  33. package/dist/hooks/stop-continuation-guard/hook.d.ts +6 -1
  34. package/dist/hooks/think-mode/hook.d.ts +14 -2
  35. package/dist/hooks/think-mode/switcher.d.ts +0 -56
  36. package/dist/hooks/think-mode/types.d.ts +1 -15
  37. package/dist/hooks/todo-continuation-enforcer/constants.d.ts +1 -1
  38. package/dist/hooks/todo-continuation-enforcer/pending-question-detection.d.ts +14 -0
  39. package/dist/index.js +1310 -503
  40. package/dist/oh-my-opencode.schema.json +9 -13
  41. package/dist/shared/model-suggestion-retry.d.ts +4 -2
  42. package/dist/shared/prompt-timeout-context.d.ts +12 -0
  43. package/dist/shared/spawn-with-windows-hide.d.ts +15 -0
  44. package/dist/tools/delegate-task/category-resolver.d.ts +1 -0
  45. package/dist/tools/delegate-task/skill-resolver.d.ts +1 -0
  46. package/dist/tools/delegate-task/token-limiter.d.ts +4 -0
  47. package/dist/tools/delegate-task/types.d.ts +9 -0
  48. package/dist/tools/hashline-edit/tool-description.d.ts +1 -1
  49. package/dist/tools/hashline-edit/validation.d.ts +1 -0
  50. package/package.json +13 -8
  51. package/postinstall.mjs +23 -7
package/dist/index.js CHANGED
@@ -4,25 +4,43 @@ var __getProtoOf = Object.getPrototypeOf;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
7
12
  var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
8
20
  target = mod != null ? __create(__getProtoOf(mod)) : {};
9
21
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
22
  for (let key of __getOwnPropNames(mod))
11
23
  if (!__hasOwnProp.call(to, key))
12
24
  __defProp(to, key, {
13
- get: () => mod[key],
25
+ get: __accessProp.bind(mod, key),
14
26
  enumerable: true
15
27
  });
28
+ if (canCache)
29
+ cache.set(mod, to);
16
30
  return to;
17
31
  };
18
32
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __returnValue = (v) => v;
34
+ function __exportSetter(name, newValue) {
35
+ this[name] = __returnValue.bind(null, newValue);
36
+ }
19
37
  var __export = (target, all) => {
20
38
  for (var name in all)
21
39
  __defProp(target, name, {
22
40
  get: all[name],
23
41
  enumerable: true,
24
42
  configurable: true,
25
- set: (newValue) => all[name] = () => newValue
43
+ set: __exportSetter.bind(all, name)
26
44
  });
27
45
  };
28
46
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -5248,10 +5266,10 @@ WHY THIS FORMAT IS MANDATORY:
5248
5266
  `, PLAN_AGENT_NAMES, PLAN_FAMILY_NAMES;
5249
5267
  var init_constants = __esm(() => {
5250
5268
  DEFAULT_CATEGORIES = {
5251
- "visual-engineering": { model: "google/gemini-3-pro", variant: "high" },
5269
+ "visual-engineering": { model: "google/gemini-3.1-pro", variant: "high" },
5252
5270
  ultrabrain: { model: "openai/gpt-5.3-codex", variant: "xhigh" },
5253
5271
  deep: { model: "openai/gpt-5.3-codex", variant: "medium" },
5254
- artistry: { model: "google/gemini-3-pro", variant: "high" },
5272
+ artistry: { model: "google/gemini-3.1-pro", variant: "high" },
5255
5273
  quick: { model: "anthropic/claude-haiku-4-5" },
5256
5274
  "unspecified-low": { model: "anthropic/claude-sonnet-4-6" },
5257
5275
  "unspecified-high": { model: "anthropic/claude-opus-4-6", variant: "max" },
@@ -12239,7 +12257,7 @@ var COUNTDOWN_SECONDS = 2;
12239
12257
  var TOAST_DURATION_MS = 900;
12240
12258
  var COUNTDOWN_GRACE_PERIOD_MS = 500;
12241
12259
  var ABORT_WINDOW_MS = 3000;
12242
- var CONTINUATION_COOLDOWN_MS = 30000;
12260
+ var CONTINUATION_COOLDOWN_MS = 5000;
12243
12261
  var MAX_CONSECUTIVE_FAILURES = 5;
12244
12262
  var FAILURE_RESET_WINDOW_MS = 5 * 60 * 1000;
12245
12263
  // src/features/run-continuation-state/constants.ts
@@ -17101,14 +17119,15 @@ var AGENT_MODEL_REQUIREMENTS = {
17101
17119
  },
17102
17120
  hephaestus: {
17103
17121
  fallbackChain: [
17104
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.3-codex", variant: "medium" }
17122
+ { providers: ["openai", "venice", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
17123
+ { providers: ["github-copilot"], model: "gpt-5.2", variant: "medium" }
17105
17124
  ],
17106
- requiresProvider: ["openai", "github-copilot", "opencode"]
17125
+ requiresProvider: ["openai", "github-copilot", "venice", "opencode"]
17107
17126
  },
17108
17127
  oracle: {
17109
17128
  fallbackChain: [
17110
17129
  { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
17111
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
17130
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
17112
17131
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" }
17113
17132
  ]
17114
17133
  },
@@ -17141,7 +17160,7 @@ var AGENT_MODEL_REQUIREMENTS = {
17141
17160
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
17142
17161
  { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
17143
17162
  { providers: ["opencode"], model: "kimi-k2.5-free" },
17144
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" }
17163
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" }
17145
17164
  ]
17146
17165
  },
17147
17166
  metis: {
@@ -17149,14 +17168,14 @@ var AGENT_MODEL_REQUIREMENTS = {
17149
17168
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
17150
17169
  { providers: ["opencode"], model: "kimi-k2.5-free" },
17151
17170
  { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
17152
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" }
17171
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" }
17153
17172
  ]
17154
17173
  },
17155
17174
  momus: {
17156
17175
  fallbackChain: [
17157
17176
  { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "medium" },
17158
17177
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
17159
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" }
17178
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" }
17160
17179
  ]
17161
17180
  },
17162
17181
  atlas: {
@@ -17170,33 +17189,33 @@ var AGENT_MODEL_REQUIREMENTS = {
17170
17189
  var CATEGORY_MODEL_REQUIREMENTS = {
17171
17190
  "visual-engineering": {
17172
17191
  fallbackChain: [
17173
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
17192
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
17174
17193
  { providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
17175
17194
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" }
17176
17195
  ]
17177
17196
  },
17178
17197
  ultrabrain: {
17179
17198
  fallbackChain: [
17180
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.3-codex", variant: "xhigh" },
17181
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
17199
+ { providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "xhigh" },
17200
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
17182
17201
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" }
17183
17202
  ]
17184
17203
  },
17185
17204
  deep: {
17186
17205
  fallbackChain: [
17187
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
17206
+ { providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
17188
17207
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
17189
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" }
17208
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" }
17190
17209
  ],
17191
17210
  requiresModel: "gpt-5.3-codex"
17192
17211
  },
17193
17212
  artistry: {
17194
17213
  fallbackChain: [
17195
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "high" },
17214
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
17196
17215
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
17197
17216
  { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" }
17198
17217
  ],
17199
- requiresModel: "gemini-3-pro"
17218
+ requiresModel: "gemini-3.1-pro"
17200
17219
  },
17201
17220
  quick: {
17202
17221
  fallbackChain: [
@@ -17208,7 +17227,7 @@ var CATEGORY_MODEL_REQUIREMENTS = {
17208
17227
  "unspecified-low": {
17209
17228
  fallbackChain: [
17210
17229
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" },
17211
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
17230
+ { providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
17212
17231
  { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" }
17213
17232
  ]
17214
17233
  },
@@ -17216,11 +17235,12 @@ var CATEGORY_MODEL_REQUIREMENTS = {
17216
17235
  fallbackChain: [
17217
17236
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
17218
17237
  { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
17219
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" }
17238
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" }
17220
17239
  ]
17221
17240
  },
17222
17241
  writing: {
17223
17242
  fallbackChain: [
17243
+ { providers: ["opencode"], model: "kimi-k2.5-free" },
17224
17244
  { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
17225
17245
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" }
17226
17246
  ]
@@ -17684,10 +17704,10 @@ function isModelCacheAvailable() {
17684
17704
  // src/shared/provider-model-id-transform.ts
17685
17705
  function transformModelForProvider(provider, model) {
17686
17706
  if (provider === "github-copilot") {
17687
- return model.replace("claude-opus-4-6", "claude-opus-4.6").replace("claude-sonnet-4-6", "claude-sonnet-4.6").replace("claude-sonnet-4-5", "claude-sonnet-4.5").replace("claude-haiku-4-5", "claude-haiku-4.5").replace("claude-sonnet-4", "claude-sonnet-4").replace(/gemini-3-pro(?!-)/g, "gemini-3-pro-preview").replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview");
17707
+ return model.replace("claude-opus-4-6", "claude-opus-4.6").replace("claude-sonnet-4-6", "claude-sonnet-4.6").replace("claude-sonnet-4-5", "claude-sonnet-4.5").replace("claude-haiku-4-5", "claude-haiku-4.5").replace("claude-sonnet-4", "claude-sonnet-4").replace(/gemini-3\.1-pro(?!-)/g, "gemini-3.1-pro-preview").replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview");
17688
17708
  }
17689
17709
  if (provider === "google") {
17690
- return model.replace(/gemini-3-pro(?!-)/g, "gemini-3-pro-preview").replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview");
17710
+ return model.replace(/gemini-3\.1-pro(?!-)/g, "gemini-3.1-pro-preview").replace(/gemini-3-flash(?!-)/g, "gemini-3-flash-preview");
17691
17711
  }
17692
17712
  return model;
17693
17713
  }
@@ -18545,6 +18565,42 @@ async function enforceMainPaneWidth(mainPaneId, windowWidth, mainPaneSizeOrOptio
18545
18565
  }
18546
18566
  // src/shared/model-suggestion-retry.ts
18547
18567
  init_logger();
18568
+
18569
+ // src/shared/prompt-timeout-context.ts
18570
+ var PROMPT_TIMEOUT_MS = 120000;
18571
+ function createPromptTimeoutContext(args, timeoutMs) {
18572
+ const timeoutController = new AbortController;
18573
+ let timeoutID = null;
18574
+ let timedOut = false;
18575
+ const abortOnUpstreamSignal = () => {
18576
+ timeoutController.abort(args.signal?.reason);
18577
+ };
18578
+ if (args.signal) {
18579
+ if (args.signal.aborted) {
18580
+ timeoutController.abort(args.signal.reason);
18581
+ } else {
18582
+ args.signal.addEventListener("abort", abortOnUpstreamSignal, { once: true });
18583
+ }
18584
+ }
18585
+ timeoutID = setTimeout(() => {
18586
+ timedOut = true;
18587
+ timeoutController.abort(new Error(`prompt timed out after ${timeoutMs}ms`));
18588
+ }, timeoutMs);
18589
+ return {
18590
+ signal: timeoutController.signal,
18591
+ wasTimedOut: () => timedOut,
18592
+ cleanup: () => {
18593
+ if (timeoutID !== null) {
18594
+ clearTimeout(timeoutID);
18595
+ }
18596
+ if (args.signal) {
18597
+ args.signal.removeEventListener("abort", abortOnUpstreamSignal);
18598
+ }
18599
+ }
18600
+ };
18601
+ }
18602
+
18603
+ // src/shared/model-suggestion-retry.ts
18548
18604
  function extractMessage(error) {
18549
18605
  if (typeof error === "string")
18550
18606
  return error;
@@ -18602,24 +18658,47 @@ function parseModelSuggestion(error) {
18602
18658
  }
18603
18659
  return null;
18604
18660
  }
18605
- async function promptWithModelSuggestionRetry(client, args) {
18606
- const promptPromise = client.session.promptAsync(args);
18607
- let timeoutID = null;
18608
- const timeoutPromise = new Promise((_, reject) => {
18609
- timeoutID = setTimeout(() => {
18610
- reject(new Error("promptAsync timed out after 120000ms"));
18611
- }, 120000);
18661
+ async function promptWithModelSuggestionRetry(client, args, options = {}) {
18662
+ const timeoutMs = options.timeoutMs ?? PROMPT_TIMEOUT_MS;
18663
+ const timeoutContext = createPromptTimeoutContext(args, timeoutMs);
18664
+ const promptPromise = client.session.promptAsync({
18665
+ ...args,
18666
+ signal: timeoutContext.signal
18612
18667
  });
18613
18668
  try {
18614
- await Promise.race([promptPromise, timeoutPromise]);
18669
+ await promptPromise;
18670
+ if (timeoutContext.wasTimedOut()) {
18671
+ throw new Error(`promptAsync timed out after ${timeoutMs}ms`);
18672
+ }
18673
+ } catch (error) {
18674
+ if (timeoutContext.wasTimedOut()) {
18675
+ throw new Error(`promptAsync timed out after ${timeoutMs}ms`);
18676
+ }
18677
+ throw error;
18615
18678
  } finally {
18616
- if (timeoutID !== null)
18617
- clearTimeout(timeoutID);
18679
+ timeoutContext.cleanup();
18618
18680
  }
18619
18681
  }
18620
- async function promptSyncWithModelSuggestionRetry(client, args) {
18682
+ async function promptSyncWithModelSuggestionRetry(client, args, options = {}) {
18683
+ const timeoutMs = options.timeoutMs ?? PROMPT_TIMEOUT_MS;
18621
18684
  try {
18622
- await client.session.prompt(args);
18685
+ const timeoutContext = createPromptTimeoutContext(args, timeoutMs);
18686
+ try {
18687
+ await client.session.prompt({
18688
+ ...args,
18689
+ signal: timeoutContext.signal
18690
+ });
18691
+ if (timeoutContext.wasTimedOut()) {
18692
+ throw new Error(`prompt timed out after ${timeoutMs}ms`);
18693
+ }
18694
+ } catch (error) {
18695
+ if (timeoutContext.wasTimedOut()) {
18696
+ throw new Error(`prompt timed out after ${timeoutMs}ms`);
18697
+ }
18698
+ throw error;
18699
+ } finally {
18700
+ timeoutContext.cleanup();
18701
+ }
18623
18702
  } catch (error) {
18624
18703
  const suggestion = parseModelSuggestion(error);
18625
18704
  if (!suggestion || !args.body.model) {
@@ -18629,7 +18708,7 @@ async function promptSyncWithModelSuggestionRetry(client, args) {
18629
18708
  original: `${suggestion.providerID}/${suggestion.modelID}`,
18630
18709
  suggested: suggestion.suggestion
18631
18710
  });
18632
- await client.session.prompt({
18711
+ const retryArgs = {
18633
18712
  ...args,
18634
18713
  body: {
18635
18714
  ...args.body,
@@ -18638,7 +18717,24 @@ async function promptSyncWithModelSuggestionRetry(client, args) {
18638
18717
  modelID: suggestion.suggestion
18639
18718
  }
18640
18719
  }
18641
- });
18720
+ };
18721
+ const timeoutContext = createPromptTimeoutContext(retryArgs, timeoutMs);
18722
+ try {
18723
+ await client.session.prompt({
18724
+ ...retryArgs,
18725
+ signal: timeoutContext.signal
18726
+ });
18727
+ if (timeoutContext.wasTimedOut()) {
18728
+ throw new Error(`prompt timed out after ${timeoutMs}ms`);
18729
+ }
18730
+ } catch (retryError) {
18731
+ if (timeoutContext.wasTimedOut()) {
18732
+ throw new Error(`prompt timed out after ${timeoutMs}ms`);
18733
+ }
18734
+ throw retryError;
18735
+ } finally {
18736
+ timeoutContext.cleanup();
18737
+ }
18642
18738
  }
18643
18739
  }
18644
18740
  // src/shared/opencode-server-auth.ts
@@ -19159,9 +19255,31 @@ function isLastAssistantMessageAborted(messages) {
19159
19255
  return errorName === "MessageAbortedError" || errorName === "AbortError";
19160
19256
  }
19161
19257
 
19258
+ // src/hooks/todo-continuation-enforcer/pending-question-detection.ts
19259
+ init_logger();
19260
+ function hasUnansweredQuestion(messages) {
19261
+ if (!messages || messages.length === 0)
19262
+ return false;
19263
+ for (let i2 = messages.length - 1;i2 >= 0; i2--) {
19264
+ const msg = messages[i2];
19265
+ const role = msg.info?.role ?? msg.role;
19266
+ if (role === "user")
19267
+ return false;
19268
+ if (role === "assistant" && msg.parts) {
19269
+ const hasQuestion = msg.parts.some((part) => (part.type === "tool_use" || part.type === "tool-invocation") && (part.name === "question" || part.toolName === "question"));
19270
+ if (hasQuestion) {
19271
+ log(`[${HOOK_NAME}] Detected pending question tool in last assistant message`);
19272
+ return true;
19273
+ }
19274
+ return false;
19275
+ }
19276
+ }
19277
+ return false;
19278
+ }
19279
+
19162
19280
  // src/hooks/todo-continuation-enforcer/todo.ts
19163
19281
  function getIncompleteCount(todos) {
19164
- return todos.filter((todo) => todo.status !== "completed" && todo.status !== "cancelled").length;
19282
+ return todos.filter((todo) => todo.status !== "completed" && todo.status !== "cancelled" && todo.status !== "blocked" && todo.status !== "deleted").length;
19165
19283
  }
19166
19284
 
19167
19285
  // src/hooks/todo-continuation-enforcer/countdown.ts
@@ -19377,6 +19495,10 @@ async function handleSessionIdle(args) {
19377
19495
  log(`[${HOOK_NAME}] Skipped: last assistant message was aborted (API fallback)`, { sessionID });
19378
19496
  return;
19379
19497
  }
19498
+ if (hasUnansweredQuestion(messages)) {
19499
+ log(`[${HOOK_NAME}] Skipped: pending question awaiting user response`, { sessionID });
19500
+ return;
19501
+ }
19380
19502
  } catch (error) {
19381
19503
  log(`[${HOOK_NAME}] Messages fetch failed, continuing`, { sessionID, error: String(error) });
19382
19504
  }
@@ -20103,6 +20225,7 @@ function createSessionNotification(ctx, config = {}) {
20103
20225
  idleConfirmationDelay: 1500,
20104
20226
  skipIfIncompleteTodos: true,
20105
20227
  maxTrackedSessions: 100,
20228
+ enforceMainSessionFilter: true,
20106
20229
  ...config
20107
20230
  };
20108
20231
  const scheduler = createIdleNotificationScheduler({
@@ -20135,9 +20258,11 @@ function createSessionNotification(ctx, config = {}) {
20135
20258
  const shouldNotifyForSession = (sessionID) => {
20136
20259
  if (subagentSessions.has(sessionID))
20137
20260
  return false;
20138
- const mainSessionID = getMainSessionID();
20139
- if (mainSessionID && sessionID !== mainSessionID)
20140
- return false;
20261
+ if (mergedConfig.enforceMainSessionFilter) {
20262
+ const mainSessionID = getMainSessionID();
20263
+ if (mainSessionID && sessionID !== mainSessionID)
20264
+ return false;
20265
+ }
20141
20266
  return true;
20142
20267
  };
20143
20268
  const getEventToolName = (properties) => {
@@ -34526,7 +34651,7 @@ var TRUNCATE_CONFIG = {
34526
34651
  function getOrCreateRetryState(autoCompactState, sessionID) {
34527
34652
  let state2 = autoCompactState.retryStateBySession.get(sessionID);
34528
34653
  if (!state2) {
34529
- state2 = { attempt: 0, lastAttemptTime: 0 };
34654
+ state2 = { attempt: 0, lastAttemptTime: 0, firstAttemptTime: 0 };
34530
34655
  autoCompactState.retryStateBySession.set(sessionID, state2);
34531
34656
  }
34532
34657
  return state2;
@@ -35222,8 +35347,26 @@ function resolveCompactionModel(pluginConfig, sessionID, originalProviderID, ori
35222
35347
  }
35223
35348
 
35224
35349
  // src/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.ts
35350
+ var SUMMARIZE_RETRY_TOTAL_TIMEOUT_MS = 120000;
35225
35351
  async function runSummarizeRetryStrategy(params) {
35226
35352
  const retryState = getOrCreateRetryState(params.autoCompactState, params.sessionID);
35353
+ const now = Date.now();
35354
+ if (retryState.firstAttemptTime === 0) {
35355
+ retryState.firstAttemptTime = now;
35356
+ }
35357
+ const elapsedTimeMs = now - retryState.firstAttemptTime;
35358
+ if (elapsedTimeMs >= SUMMARIZE_RETRY_TOTAL_TIMEOUT_MS) {
35359
+ clearSessionState(params.autoCompactState, params.sessionID);
35360
+ await params.client.tui.showToast({
35361
+ body: {
35362
+ title: "Auto Compact Timed Out",
35363
+ message: "Compaction retries exceeded the timeout window. Please start a new session.",
35364
+ variant: "error",
35365
+ duration: 5000
35366
+ }
35367
+ }).catch(() => {});
35368
+ return;
35369
+ }
35227
35370
  if (params.errorType?.includes("non-empty content")) {
35228
35371
  const attempt = getEmptyContentAttempt(params.autoCompactState, params.sessionID);
35229
35372
  if (attempt < 3) {
@@ -35253,6 +35396,7 @@ async function runSummarizeRetryStrategy(params) {
35253
35396
  }
35254
35397
  if (Date.now() - retryState.lastAttemptTime > 300000) {
35255
35398
  retryState.attempt = 0;
35399
+ retryState.firstAttemptTime = Date.now();
35256
35400
  params.autoCompactState.truncateStateBySession.delete(params.sessionID);
35257
35401
  }
35258
35402
  if (retryState.attempt < RETRY_CONFIG.maxAttempts) {
@@ -35280,8 +35424,21 @@ async function runSummarizeRetryStrategy(params) {
35280
35424
  });
35281
35425
  return;
35282
35426
  } catch {
35427
+ const remainingTimeMs = SUMMARIZE_RETRY_TOTAL_TIMEOUT_MS - (Date.now() - retryState.firstAttemptTime);
35428
+ if (remainingTimeMs <= 0) {
35429
+ clearSessionState(params.autoCompactState, params.sessionID);
35430
+ await params.client.tui.showToast({
35431
+ body: {
35432
+ title: "Auto Compact Timed Out",
35433
+ message: "Compaction retries exceeded the timeout window. Please start a new session.",
35434
+ variant: "error",
35435
+ duration: 5000
35436
+ }
35437
+ }).catch(() => {});
35438
+ return;
35439
+ }
35283
35440
  const delay3 = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
35284
- const cappedDelay = Math.min(delay3, RETRY_CONFIG.maxDelayMs);
35441
+ const cappedDelay = Math.min(delay3, RETRY_CONFIG.maxDelayMs, remainingTimeMs);
35285
35442
  setTimeout(() => {
35286
35443
  runSummarizeRetryStrategy(params);
35287
35444
  }, cappedDelay);
@@ -35877,24 +36034,11 @@ function extractModelPrefix(modelID) {
35877
36034
  function normalizeModelID(modelID) {
35878
36035
  return modelID.replace(/\.(\d+)/g, "-$1");
35879
36036
  }
35880
- function resolveProvider(providerID, modelID) {
35881
- if (providerID === "github-copilot") {
35882
- const modelLower = modelID.toLowerCase();
35883
- if (modelLower.includes("claude"))
35884
- return "anthropic";
35885
- if (modelLower.includes("gemini"))
35886
- return "google";
35887
- if (modelLower.includes("gpt") || modelLower.includes("o1") || modelLower.includes("o3")) {
35888
- return "openai";
35889
- }
35890
- }
35891
- return providerID;
35892
- }
35893
36037
  var HIGH_VARIANT_MAP = {
35894
36038
  "claude-sonnet-4-6": "claude-sonnet-4-6-high",
35895
36039
  "claude-opus-4-6": "claude-opus-4-6-high",
35896
- "gemini-3-pro": "gemini-3-pro-high",
35897
- "gemini-3-pro-low": "gemini-3-pro-high",
36040
+ "gemini-3-1-pro": "gemini-3-1-pro-high",
36041
+ "gemini-3-1-pro-low": "gemini-3-1-pro-high",
35898
36042
  "gemini-3-flash": "gemini-3-flash-high",
35899
36043
  "gpt-5": "gpt-5-high",
35900
36044
  "gpt-5-mini": "gpt-5-mini-high",
@@ -35909,74 +36053,10 @@ var HIGH_VARIANT_MAP = {
35909
36053
  "gpt-5-2": "gpt-5-2-high",
35910
36054
  "gpt-5-2-chat-latest": "gpt-5-2-chat-latest-high",
35911
36055
  "gpt-5-2-pro": "gpt-5-2-pro-high",
35912
- "antigravity-gemini-3-pro": "antigravity-gemini-3-pro-high",
36056
+ "antigravity-gemini-3-1-pro": "antigravity-gemini-3-1-pro-high",
35913
36057
  "antigravity-gemini-3-flash": "antigravity-gemini-3-flash-high"
35914
36058
  };
35915
36059
  var ALREADY_HIGH = new Set(Object.values(HIGH_VARIANT_MAP));
35916
- var THINKING_CONFIGS = {
35917
- anthropic: {
35918
- thinking: {
35919
- type: "enabled",
35920
- budgetTokens: 64000
35921
- },
35922
- maxTokens: 128000
35923
- },
35924
- "google-vertex-anthropic": {
35925
- thinking: {
35926
- type: "enabled",
35927
- budgetTokens: 64000
35928
- },
35929
- maxTokens: 128000
35930
- },
35931
- "amazon-bedrock": {
35932
- reasoningConfig: {
35933
- type: "enabled",
35934
- budgetTokens: 32000
35935
- },
35936
- maxTokens: 64000
35937
- },
35938
- google: {
35939
- providerOptions: {
35940
- google: {
35941
- thinkingConfig: {
35942
- thinkingLevel: "HIGH"
35943
- }
35944
- }
35945
- }
35946
- },
35947
- "google-vertex": {
35948
- providerOptions: {
35949
- "google-vertex": {
35950
- thinkingConfig: {
35951
- thinkingLevel: "HIGH"
35952
- }
35953
- }
35954
- }
35955
- },
35956
- openai: {
35957
- reasoning_effort: "high"
35958
- },
35959
- "zai-coding-plan": {
35960
- providerOptions: {
35961
- "zai-coding-plan": {
35962
- extra_body: {
35963
- thinking: {
35964
- type: "disabled"
35965
- }
35966
- }
35967
- }
35968
- }
35969
- }
35970
- };
35971
- var THINKING_CAPABLE_MODELS = {
35972
- anthropic: ["claude-sonnet-4", "claude-opus-4", "claude-3"],
35973
- "google-vertex-anthropic": ["claude-sonnet-4", "claude-opus-4", "claude-3"],
35974
- "amazon-bedrock": ["claude", "anthropic"],
35975
- google: ["gemini-2", "gemini-3"],
35976
- "google-vertex": ["gemini-2", "gemini-3"],
35977
- openai: ["gpt-5", "o1", "o3"],
35978
- "zai-coding-plan": ["glm"]
35979
- };
35980
36060
  function getHighVariant(modelID) {
35981
36061
  const normalized = normalizeModelID(modelID);
35982
36062
  const { prefix, base } = extractModelPrefix(normalized);
@@ -35994,65 +36074,28 @@ function isAlreadyHighVariant(modelID) {
35994
36074
  const { base } = extractModelPrefix(normalized);
35995
36075
  return ALREADY_HIGH.has(base) || base.endsWith("-high");
35996
36076
  }
35997
- function isThinkingProvider(provider) {
35998
- return provider in THINKING_CONFIGS;
35999
- }
36000
- function getThinkingConfig(providerID, modelID) {
36001
- const normalized = normalizeModelID(modelID);
36002
- const { base } = extractModelPrefix(normalized);
36003
- if (isAlreadyHighVariant(normalized)) {
36004
- return null;
36005
- }
36006
- const resolvedProvider = resolveProvider(providerID, modelID);
36007
- if (!isThinkingProvider(resolvedProvider)) {
36008
- return null;
36009
- }
36010
- const config2 = THINKING_CONFIGS[resolvedProvider];
36011
- const capablePatterns = THINKING_CAPABLE_MODELS[resolvedProvider];
36012
- const baseLower = base.toLowerCase();
36013
- const isCapable = capablePatterns.some((pattern) => baseLower.includes(pattern.toLowerCase()));
36014
- return isCapable ? config2 : null;
36015
- }
36016
36077
  // src/hooks/think-mode/hook.ts
36017
36078
  var thinkModeState = new Map;
36018
36079
  function createThinkModeHook() {
36019
- function isDisabledThinkingConfig(config2) {
36020
- const thinkingConfig = config2.thinking;
36021
- if (typeof thinkingConfig === "object" && thinkingConfig !== null && "type" in thinkingConfig && thinkingConfig.type === "disabled") {
36022
- return true;
36023
- }
36024
- const providerOptions = config2.providerOptions;
36025
- if (typeof providerOptions !== "object" || providerOptions === null) {
36026
- return false;
36027
- }
36028
- return Object.values(providerOptions).some((providerConfig) => {
36029
- if (typeof providerConfig !== "object" || providerConfig === null) {
36030
- return false;
36031
- }
36032
- const providerConfigMap = providerConfig;
36033
- const extraBody = providerConfigMap.extra_body;
36034
- if (typeof extraBody !== "object" || extraBody === null) {
36035
- return false;
36036
- }
36037
- const extraBodyMap = extraBody;
36038
- const extraThinking = extraBodyMap.thinking;
36039
- return typeof extraThinking === "object" && extraThinking !== null && extraThinking.type === "disabled";
36040
- });
36041
- }
36042
36080
  return {
36043
- "chat.params": async (output, sessionID) => {
36081
+ "chat.message": async (input, output) => {
36044
36082
  const promptText = extractPromptText(output.parts);
36083
+ const sessionID = input.sessionID;
36045
36084
  const state3 = {
36046
36085
  requested: false,
36047
36086
  modelSwitched: false,
36048
- thinkingConfigInjected: false
36087
+ variantSet: false
36049
36088
  };
36050
36089
  if (!detectThinkKeyword(promptText)) {
36051
36090
  thinkModeState.set(sessionID, state3);
36052
36091
  return;
36053
36092
  }
36054
36093
  state3.requested = true;
36055
- const currentModel = output.message.model;
36094
+ if (typeof output.message.variant === "string") {
36095
+ thinkModeState.set(sessionID, state3);
36096
+ return;
36097
+ }
36098
+ const currentModel = input.model;
36056
36099
  if (!currentModel) {
36057
36100
  thinkModeState.set(sessionID, state3);
36058
36101
  return;
@@ -36064,50 +36107,20 @@ function createThinkModeHook() {
36064
36107
  return;
36065
36108
  }
36066
36109
  const highVariant = getHighVariant(currentModel.modelID);
36067
- const thinkingConfig = getThinkingConfig(currentModel.providerID, currentModel.modelID);
36068
36110
  if (highVariant) {
36069
36111
  output.message.model = {
36070
36112
  providerID: currentModel.providerID,
36071
36113
  modelID: highVariant
36072
36114
  };
36115
+ output.message.variant = "high";
36073
36116
  state3.modelSwitched = true;
36117
+ state3.variantSet = true;
36074
36118
  log("Think mode: model switched to high variant", {
36075
36119
  sessionID,
36076
36120
  from: currentModel.modelID,
36077
36121
  to: highVariant
36078
36122
  });
36079
36123
  }
36080
- if (thinkingConfig) {
36081
- const messageData = output.message;
36082
- const agentThinking = messageData.thinking;
36083
- const agentProviderOptions = messageData.providerOptions;
36084
- const agentDisabledThinking = agentThinking?.type === "disabled";
36085
- const agentHasCustomProviderOptions = Boolean(agentProviderOptions);
36086
- if (agentDisabledThinking) {
36087
- log("Think mode: skipping - agent has thinking disabled", {
36088
- sessionID,
36089
- provider: currentModel.providerID
36090
- });
36091
- } else if (agentHasCustomProviderOptions) {
36092
- log("Think mode: skipping - agent has custom providerOptions", {
36093
- sessionID,
36094
- provider: currentModel.providerID
36095
- });
36096
- } else if (!isDisabledThinkingConfig(thinkingConfig)) {
36097
- Object.assign(output.message, thinkingConfig);
36098
- state3.thinkingConfigInjected = true;
36099
- log("Think mode: thinking config injected", {
36100
- sessionID,
36101
- provider: currentModel.providerID,
36102
- config: thinkingConfig
36103
- });
36104
- } else {
36105
- log("Think mode: skipping disabled thinking config", {
36106
- sessionID,
36107
- provider: currentModel.providerID
36108
- });
36109
- }
36110
- }
36111
36124
  thinkModeState.set(sessionID, state3);
36112
36125
  },
36113
36126
  event: async ({ event }) => {
@@ -38165,7 +38178,11 @@ function createBackgroundNotificationHook(manager) {
38165
38178
  const eventHandler = async ({ event }) => {
38166
38179
  manager.handleEvent(event);
38167
38180
  };
38181
+ const chatMessageHandler = async (input, output) => {
38182
+ manager.injectPendingNotificationsIntoChatMessage(output, input.sessionID);
38183
+ };
38168
38184
  return {
38185
+ "chat.message": chatMessageHandler,
38169
38186
  event: eventHandler
38170
38187
  };
38171
38188
  }
@@ -38481,6 +38498,64 @@ function getConfigContext() {
38481
38498
  function getConfigDir() {
38482
38499
  return getConfigContext().paths.configDir;
38483
38500
  }
38501
+ // src/shared/spawn-with-windows-hide.ts
38502
+ var {spawn: bunSpawn } = globalThis.Bun;
38503
+ import { spawn as nodeSpawn } from "child_process";
38504
+ import { Readable } from "stream";
38505
+ function toReadableStream(stream) {
38506
+ if (!stream) {
38507
+ return;
38508
+ }
38509
+ return Readable.toWeb(stream);
38510
+ }
38511
+ function wrapNodeProcess(proc) {
38512
+ let resolveExited;
38513
+ let exitCode = null;
38514
+ const exited = new Promise((resolve5) => {
38515
+ resolveExited = resolve5;
38516
+ });
38517
+ proc.on("exit", (code) => {
38518
+ exitCode = code ?? 1;
38519
+ resolveExited(exitCode);
38520
+ });
38521
+ proc.on("error", () => {
38522
+ if (exitCode === null) {
38523
+ exitCode = 1;
38524
+ resolveExited(1);
38525
+ }
38526
+ });
38527
+ return {
38528
+ get exitCode() {
38529
+ return exitCode;
38530
+ },
38531
+ exited,
38532
+ stdout: toReadableStream(proc.stdout),
38533
+ stderr: toReadableStream(proc.stderr),
38534
+ kill(signal) {
38535
+ try {
38536
+ if (!signal) {
38537
+ proc.kill();
38538
+ return;
38539
+ }
38540
+ proc.kill(signal);
38541
+ } catch {}
38542
+ }
38543
+ };
38544
+ }
38545
+ function spawnWithWindowsHide(command, options) {
38546
+ if (process.platform !== "win32") {
38547
+ return bunSpawn(command, options);
38548
+ }
38549
+ const [cmd, ...args] = command;
38550
+ const proc = nodeSpawn(cmd, args, {
38551
+ cwd: options.cwd,
38552
+ env: options.env,
38553
+ stdio: [options.stdin ?? "pipe", options.stdout ?? "pipe", options.stderr ?? "pipe"],
38554
+ windowsHide: true,
38555
+ shell: true
38556
+ });
38557
+ return wrapNodeProcess(proc);
38558
+ }
38484
38559
  // src/cli/config-manager/bun-install.ts
38485
38560
  var BUN_INSTALL_TIMEOUT_SECONDS = 60;
38486
38561
  var BUN_INSTALL_TIMEOUT_MS = BUN_INSTALL_TIMEOUT_SECONDS * 1000;
@@ -38490,7 +38565,7 @@ async function runBunInstall() {
38490
38565
  }
38491
38566
  async function runBunInstallWithDetails() {
38492
38567
  try {
38493
- const proc = Bun.spawn(["bun", "install"], {
38568
+ const proc = spawnWithWindowsHide(["bun", "install"], {
38494
38569
  cwd: getConfigDir(),
38495
38570
  stdout: "inherit",
38496
38571
  stderr: "inherit"
@@ -39000,12 +39075,9 @@ function createAgentUsageReminderHook(_ctx) {
39000
39075
  function extractModelName(model) {
39001
39076
  return model.includes("/") ? model.split("/").pop() ?? model : model;
39002
39077
  }
39003
- var GPT_MODEL_PREFIXES = ["gpt-", "gpt4", "o1", "o3", "o4"];
39004
39078
  function isGptModel(model) {
39005
- if (model.startsWith("openai/") || model.startsWith("github-copilot/gpt-"))
39006
- return true;
39007
39079
  const modelName = extractModelName(model).toLowerCase();
39008
- return GPT_MODEL_PREFIXES.some((prefix) => modelName.startsWith(prefix));
39080
+ return modelName.includes("gpt");
39009
39081
  }
39010
39082
  var GEMINI_PROVIDERS = ["google/", "google-vertex/"];
39011
39083
  function isGeminiModel(model) {
@@ -40241,7 +40313,7 @@ function isOmoSession(sessionName) {
40241
40313
  async function killAllTrackedSessions(state3) {
40242
40314
  for (const sessionName of state3.tmuxSessions) {
40243
40315
  try {
40244
- const proc = Bun.spawn(["tmux", "kill-session", "-t", sessionName], {
40316
+ const proc = spawnWithWindowsHide(["tmux", "kill-session", "-t", sessionName], {
40245
40317
  stdout: "ignore",
40246
40318
  stderr: "ignore"
40247
40319
  });
@@ -40561,6 +40633,7 @@ function readState(directory, customPath) {
40561
40633
  active: isActive,
40562
40634
  iteration: iterationNum,
40563
40635
  max_iterations: Number(data.max_iterations) || DEFAULT_MAX_ITERATIONS,
40636
+ message_count_at_start: typeof data.message_count_at_start === "number" ? data.message_count_at_start : typeof data.message_count_at_start === "string" && data.message_count_at_start.trim() !== "" ? Number(data.message_count_at_start) : undefined,
40564
40637
  completion_promise: stripQuotes(data.completion_promise) || DEFAULT_COMPLETION_PROMISE,
40565
40638
  started_at: stripQuotes(data.started_at) || new Date().toISOString(),
40566
40639
  prompt: body.trim(),
@@ -40584,6 +40657,8 @@ function writeState(directory, state3, customPath) {
40584
40657
  const ultraworkLine = state3.ultrawork !== undefined ? `ultrawork: ${state3.ultrawork}
40585
40658
  ` : "";
40586
40659
  const strategyLine = state3.strategy ? `strategy: "${state3.strategy}"
40660
+ ` : "";
40661
+ const messageCountAtStartLine = typeof state3.message_count_at_start === "number" ? `message_count_at_start: ${state3.message_count_at_start}
40587
40662
  ` : "";
40588
40663
  const content = `---
40589
40664
  active: ${state3.active}
@@ -40591,7 +40666,7 @@ iteration: ${state3.iteration}
40591
40666
  max_iterations: ${state3.max_iterations}
40592
40667
  completion_promise: "${state3.completion_promise}"
40593
40668
  started_at: "${state3.started_at}"
40594
- ${sessionIdLine}${ultraworkLine}${strategyLine}---
40669
+ ${sessionIdLine}${ultraworkLine}${strategyLine}${messageCountAtStartLine}---
40595
40670
  ${state3.prompt}
40596
40671
  `;
40597
40672
  writeFileSync16(filePath, content, "utf-8");
@@ -40662,6 +40737,7 @@ function createLoopStateController(options) {
40662
40737
  active: true,
40663
40738
  iteration: 1,
40664
40739
  max_iterations: loopOptions?.maxIterations ?? config2?.default_max_iterations ?? DEFAULT_MAX_ITERATIONS,
40740
+ message_count_at_start: loopOptions?.messageCountAtStart,
40665
40741
  completion_promise: loopOptions?.completionPromise ?? DEFAULT_COMPLETION_PROMISE,
40666
40742
  ultrawork: loopOptions?.ultrawork,
40667
40743
  strategy: loopOptions?.strategy ?? config2?.default_strategy ?? "continue",
@@ -40709,6 +40785,17 @@ function createLoopStateController(options) {
40709
40785
  return null;
40710
40786
  }
40711
40787
  return state3;
40788
+ },
40789
+ setMessageCountAtStart(sessionID, messageCountAtStart) {
40790
+ const state3 = readState(directory, stateDir);
40791
+ if (!state3 || state3.session_id !== sessionID) {
40792
+ return null;
40793
+ }
40794
+ state3.message_count_at_start = messageCountAtStart;
40795
+ if (!writeState(directory, state3, stateDir)) {
40796
+ return null;
40797
+ }
40798
+ return state3;
40712
40799
  }
40713
40800
  };
40714
40801
  }
@@ -40779,12 +40866,13 @@ async function detectCompletionInSessionMessages(ctx, options) {
40779
40866
  const messagesResponse = response;
40780
40867
  const responseData = typeof messagesResponse === "object" && messagesResponse !== null && "data" in messagesResponse ? messagesResponse.data : undefined;
40781
40868
  const messageArray = Array.isArray(messagesResponse) ? messagesResponse : Array.isArray(responseData) ? responseData : [];
40782
- const assistantMessages = messageArray.filter((msg) => msg.info?.role === "assistant");
40869
+ const scopedMessages = typeof options.sinceMessageIndex === "number" && options.sinceMessageIndex >= 0 && options.sinceMessageIndex < messageArray.length ? messageArray.slice(options.sinceMessageIndex) : messageArray;
40870
+ const assistantMessages = scopedMessages.filter((msg) => msg.info?.role === "assistant");
40783
40871
  if (assistantMessages.length === 0)
40784
40872
  return false;
40785
40873
  const pattern = buildPromisePattern(options.promise);
40786
- const recentAssistants = assistantMessages.slice(-3);
40787
- for (const assistant of recentAssistants) {
40874
+ for (let index = assistantMessages.length - 1;index >= 0; index -= 1) {
40875
+ const assistant = assistantMessages[index];
40788
40876
  if (!assistant.parts)
40789
40877
  continue;
40790
40878
  let responseText = "";
@@ -40936,14 +41024,6 @@ async function continueIteration(ctx, state3, options) {
40936
41024
  if (!newSessionID) {
40937
41025
  return;
40938
41026
  }
40939
- const boundState = options.loopState.setSessionID(newSessionID);
40940
- if (!boundState) {
40941
- log(`[${HOOK_NAME3}] Failed to bind loop state to new session`, {
40942
- previousSessionID: options.previousSessionID,
40943
- newSessionID
40944
- });
40945
- return;
40946
- }
40947
41027
  await injectContinuationPrompt(ctx, {
40948
41028
  sessionID: newSessionID,
40949
41029
  inheritFromSessionID: options.previousSessionID,
@@ -40952,6 +41032,14 @@ async function continueIteration(ctx, state3, options) {
40952
41032
  apiTimeoutMs: options.apiTimeoutMs
40953
41033
  });
40954
41034
  await selectSessionInTui(ctx.client, newSessionID);
41035
+ const boundState = options.loopState.setSessionID(newSessionID);
41036
+ if (!boundState) {
41037
+ log(`[${HOOK_NAME3}] Failed to bind loop state to new session`, {
41038
+ previousSessionID: options.previousSessionID,
41039
+ newSessionID
41040
+ });
41041
+ return;
41042
+ }
40955
41043
  return;
40956
41044
  }
40957
41045
  await injectContinuationPrompt(ctx, {
@@ -40964,106 +41052,117 @@ async function continueIteration(ctx, state3, options) {
40964
41052
 
40965
41053
  // src/hooks/ralph-loop/ralph-loop-event-handler.ts
40966
41054
  function createRalphLoopEventHandler(ctx, options) {
41055
+ const inFlightSessions = new Set;
40967
41056
  return async ({ event }) => {
40968
41057
  const props = event.properties;
40969
41058
  if (event.type === "session.idle") {
40970
41059
  const sessionID = props?.sessionID;
40971
41060
  if (!sessionID)
40972
41061
  return;
40973
- if (options.sessionRecovery.isRecovering(sessionID)) {
40974
- log(`[${HOOK_NAME3}] Skipped: in recovery`, { sessionID });
41062
+ if (inFlightSessions.has(sessionID)) {
41063
+ log(`[${HOOK_NAME3}] Skipped: handler in flight`, { sessionID });
40975
41064
  return;
40976
41065
  }
40977
- const state3 = options.loopState.getState();
40978
- if (!state3 || !state3.active) {
40979
- return;
40980
- }
40981
- if (state3.session_id && state3.session_id !== sessionID) {
40982
- if (options.checkSessionExists) {
40983
- try {
40984
- const exists = await options.checkSessionExists(state3.session_id);
40985
- if (!exists) {
40986
- options.loopState.clear();
40987
- log(`[${HOOK_NAME3}] Cleared orphaned state from deleted session`, {
40988
- orphanedSessionId: state3.session_id,
40989
- currentSessionId: sessionID
41066
+ inFlightSessions.add(sessionID);
41067
+ try {
41068
+ if (options.sessionRecovery.isRecovering(sessionID)) {
41069
+ log(`[${HOOK_NAME3}] Skipped: in recovery`, { sessionID });
41070
+ return;
41071
+ }
41072
+ const state3 = options.loopState.getState();
41073
+ if (!state3 || !state3.active) {
41074
+ return;
41075
+ }
41076
+ if (state3.session_id && state3.session_id !== sessionID) {
41077
+ if (options.checkSessionExists) {
41078
+ try {
41079
+ const exists = await options.checkSessionExists(state3.session_id);
41080
+ if (!exists) {
41081
+ options.loopState.clear();
41082
+ log(`[${HOOK_NAME3}] Cleared orphaned state from deleted session`, {
41083
+ orphanedSessionId: state3.session_id,
41084
+ currentSessionId: sessionID
41085
+ });
41086
+ return;
41087
+ }
41088
+ } catch (err) {
41089
+ log(`[${HOOK_NAME3}] Failed to check session existence`, {
41090
+ sessionId: state3.session_id,
41091
+ error: String(err)
40990
41092
  });
40991
- return;
40992
41093
  }
40993
- } catch (err) {
40994
- log(`[${HOOK_NAME3}] Failed to check session existence`, {
40995
- sessionId: state3.session_id,
40996
- error: String(err)
40997
- });
40998
41094
  }
41095
+ return;
40999
41096
  }
41000
- return;
41001
- }
41002
- const transcriptPath = options.getTranscriptPath(sessionID);
41003
- const completionViaTranscript = detectCompletionInTranscript(transcriptPath, state3.completion_promise);
41004
- const completionViaApi = completionViaTranscript ? false : await detectCompletionInSessionMessages(ctx, {
41005
- sessionID,
41006
- promise: state3.completion_promise,
41007
- apiTimeoutMs: options.apiTimeoutMs,
41008
- directory: options.directory
41009
- });
41010
- if (completionViaTranscript || completionViaApi) {
41011
- log(`[${HOOK_NAME3}] Completion detected!`, {
41097
+ const transcriptPath = options.getTranscriptPath(sessionID);
41098
+ const completionViaTranscript = detectCompletionInTranscript(transcriptPath, state3.completion_promise);
41099
+ const completionViaApi = completionViaTranscript ? false : await detectCompletionInSessionMessages(ctx, {
41012
41100
  sessionID,
41013
- iteration: state3.iteration,
41014
41101
  promise: state3.completion_promise,
41015
- detectedVia: completionViaTranscript ? "transcript_file" : "session_messages_api"
41102
+ apiTimeoutMs: options.apiTimeoutMs,
41103
+ directory: options.directory,
41104
+ sinceMessageIndex: state3.message_count_at_start
41016
41105
  });
41017
- options.loopState.clear();
41018
- const title = state3.ultrawork ? "ULTRAWORK LOOP COMPLETE!" : "Ralph Loop Complete!";
41019
- const message = state3.ultrawork ? `JUST ULW ULW! Task completed after ${state3.iteration} iteration(s)` : `Task completed after ${state3.iteration} iteration(s)`;
41020
- await ctx.client.tui?.showToast?.({ body: { title, message, variant: "success", duration: 5000 } }).catch(() => {});
41021
- return;
41022
- }
41023
- if (state3.iteration >= state3.max_iterations) {
41024
- log(`[${HOOK_NAME3}] Max iterations reached`, {
41106
+ if (completionViaTranscript || completionViaApi) {
41107
+ log(`[${HOOK_NAME3}] Completion detected!`, {
41108
+ sessionID,
41109
+ iteration: state3.iteration,
41110
+ promise: state3.completion_promise,
41111
+ detectedVia: completionViaTranscript ? "transcript_file" : "session_messages_api"
41112
+ });
41113
+ options.loopState.clear();
41114
+ const title = state3.ultrawork ? "ULTRAWORK LOOP COMPLETE!" : "Ralph Loop Complete!";
41115
+ const message = state3.ultrawork ? `JUST ULW ULW! Task completed after ${state3.iteration} iteration(s)` : `Task completed after ${state3.iteration} iteration(s)`;
41116
+ await ctx.client.tui?.showToast?.({ body: { title, message, variant: "success", duration: 5000 } }).catch(() => {});
41117
+ return;
41118
+ }
41119
+ if (state3.iteration >= state3.max_iterations) {
41120
+ log(`[${HOOK_NAME3}] Max iterations reached`, {
41121
+ sessionID,
41122
+ iteration: state3.iteration,
41123
+ max: state3.max_iterations
41124
+ });
41125
+ options.loopState.clear();
41126
+ await ctx.client.tui?.showToast?.({
41127
+ body: { title: "Ralph Loop Stopped", message: `Max iterations (${state3.max_iterations}) reached without completion`, variant: "warning", duration: 5000 }
41128
+ }).catch(() => {});
41129
+ return;
41130
+ }
41131
+ const newState = options.loopState.incrementIteration();
41132
+ if (!newState) {
41133
+ log(`[${HOOK_NAME3}] Failed to increment iteration`, { sessionID });
41134
+ return;
41135
+ }
41136
+ log(`[${HOOK_NAME3}] Continuing loop`, {
41025
41137
  sessionID,
41026
- iteration: state3.iteration,
41027
- max: state3.max_iterations
41138
+ iteration: newState.iteration,
41139
+ max: newState.max_iterations
41028
41140
  });
41029
- options.loopState.clear();
41030
41141
  await ctx.client.tui?.showToast?.({
41031
- body: { title: "Ralph Loop Stopped", message: `Max iterations (${state3.max_iterations}) reached without completion`, variant: "warning", duration: 5000 }
41142
+ body: {
41143
+ title: "Ralph Loop",
41144
+ message: `Iteration ${newState.iteration}/${newState.max_iterations}`,
41145
+ variant: "info",
41146
+ duration: 2000
41147
+ }
41032
41148
  }).catch(() => {});
41033
- return;
41034
- }
41035
- const newState = options.loopState.incrementIteration();
41036
- if (!newState) {
41037
- log(`[${HOOK_NAME3}] Failed to increment iteration`, { sessionID });
41038
- return;
41039
- }
41040
- log(`[${HOOK_NAME3}] Continuing loop`, {
41041
- sessionID,
41042
- iteration: newState.iteration,
41043
- max: newState.max_iterations
41044
- });
41045
- await ctx.client.tui?.showToast?.({
41046
- body: {
41047
- title: "Ralph Loop",
41048
- message: `Iteration ${newState.iteration}/${newState.max_iterations}`,
41049
- variant: "info",
41050
- duration: 2000
41149
+ try {
41150
+ await continueIteration(ctx, newState, {
41151
+ previousSessionID: sessionID,
41152
+ directory: options.directory,
41153
+ apiTimeoutMs: options.apiTimeoutMs,
41154
+ loopState: options.loopState
41155
+ });
41156
+ } catch (err) {
41157
+ log(`[${HOOK_NAME3}] Failed to inject continuation`, {
41158
+ sessionID,
41159
+ error: String(err)
41160
+ });
41051
41161
  }
41052
- }).catch(() => {});
41053
- try {
41054
- await continueIteration(ctx, newState, {
41055
- previousSessionID: sessionID,
41056
- directory: options.directory,
41057
- apiTimeoutMs: options.apiTimeoutMs,
41058
- loopState: options.loopState
41059
- });
41060
- } catch (err) {
41061
- log(`[${HOOK_NAME3}] Failed to inject continuation`, {
41062
- sessionID,
41063
- error: String(err)
41064
- });
41162
+ return;
41163
+ } finally {
41164
+ inFlightSessions.delete(sessionID);
41065
41165
  }
41066
- return;
41067
41166
  }
41068
41167
  if (event.type === "session.deleted") {
41069
41168
  const sessionInfo = props?.info;
@@ -41100,6 +41199,16 @@ function createRalphLoopEventHandler(ctx, options) {
41100
41199
 
41101
41200
  // src/hooks/ralph-loop/ralph-loop-hook.ts
41102
41201
  var DEFAULT_API_TIMEOUT = 5000;
41202
+ function getMessageCountFromResponse(messagesResponse) {
41203
+ if (Array.isArray(messagesResponse)) {
41204
+ return messagesResponse.length;
41205
+ }
41206
+ if (typeof messagesResponse === "object" && messagesResponse !== null && "data" in messagesResponse) {
41207
+ const data = messagesResponse.data;
41208
+ return Array.isArray(data) ? data.length : 0;
41209
+ }
41210
+ return 0;
41211
+ }
41103
41212
  function createRalphLoopHook(ctx, options) {
41104
41213
  const config2 = options?.config;
41105
41214
  const stateDir = config2?.state_dir;
@@ -41122,7 +41231,20 @@ function createRalphLoopHook(ctx, options) {
41122
41231
  });
41123
41232
  return {
41124
41233
  event,
41125
- startLoop: loopState.startLoop,
41234
+ startLoop: (sessionID, prompt, loopOptions) => {
41235
+ const startSuccess = loopState.startLoop(sessionID, prompt, loopOptions);
41236
+ if (!startSuccess || typeof loopOptions?.messageCountAtStart === "number") {
41237
+ return startSuccess;
41238
+ }
41239
+ ctx.client.session.messages({
41240
+ path: { id: sessionID },
41241
+ query: { directory: ctx.directory }
41242
+ }).then((messagesResponse) => {
41243
+ const messageCountAtStart = getMessageCountFromResponse(messagesResponse);
41244
+ loopState.setMessageCountAtStart(sessionID, messageCountAtStart);
41245
+ }).catch(() => {});
41246
+ return startSuccess;
41247
+ },
41126
41248
  cancelLoop: loopState.cancelLoop,
41127
41249
  getState: loopState.getState
41128
41250
  };
@@ -41177,12 +41299,12 @@ var TOAST_MESSAGE2 = [
41177
41299
  ].join(`
41178
41300
  `);
41179
41301
  var SISYPHUS_DISPLAY = getAgentDisplayName("sisyphus");
41180
- function showToast2(ctx, sessionID) {
41302
+ function showToast2(ctx, sessionID, variant) {
41181
41303
  ctx.client.tui.showToast({
41182
41304
  body: {
41183
41305
  title: TOAST_TITLE2,
41184
41306
  message: TOAST_MESSAGE2,
41185
- variant: "error",
41307
+ variant,
41186
41308
  duration: 1e4
41187
41309
  }
41188
41310
  }).catch((error45) => {
@@ -41192,14 +41314,18 @@ function showToast2(ctx, sessionID) {
41192
41314
  });
41193
41315
  });
41194
41316
  }
41195
- function createNoHephaestusNonGptHook(ctx) {
41317
+ function createNoHephaestusNonGptHook(ctx, options) {
41196
41318
  return {
41197
41319
  "chat.message": async (input, output) => {
41198
41320
  const rawAgent = input.agent ?? getSessionAgent(input.sessionID) ?? "";
41199
41321
  const agentKey = getAgentConfigKey(rawAgent);
41200
41322
  const modelID = input.model?.modelID;
41323
+ const allowNonGptModel = options?.allowNonGptModel === true;
41201
41324
  if (agentKey === "hephaestus" && modelID && !isGptModel(modelID)) {
41202
- showToast2(ctx, input.sessionID);
41325
+ showToast2(ctx, input.sessionID, allowNonGptModel ? "warning" : "error");
41326
+ if (allowNonGptModel) {
41327
+ return;
41328
+ }
41203
41329
  input.agent = SISYPHUS_DISPLAY;
41204
41330
  if (output?.message) {
41205
41331
  output.message.agent = SISYPHUS_DISPLAY;
@@ -42269,6 +42395,15 @@ $ARGUMENTS
42269
42395
  // src/features/builtin-commands/templates/start-work.ts
42270
42396
  var START_WORK_TEMPLATE = `You are starting a Sisyphus work session.
42271
42397
 
42398
+ ## ARGUMENTS
42399
+
42400
+ - \`/start-work [plan-name] [--worktree <path>]\`
42401
+ - \`plan-name\` (optional): name or partial match of the plan to start
42402
+ - \`--worktree <path>\` (optional): absolute path to an existing git worktree to work in
42403
+ - If specified and valid: hook pre-sets worktree_path in boulder.json
42404
+ - If specified but invalid: you must run \`git worktree add <path> <branch>\` first
42405
+ - If omitted: you MUST choose or create a worktree (see Worktree Setup below)
42406
+
42272
42407
  ## WHAT TO DO
42273
42408
 
42274
42409
  1. **Find available plans**: Search for Prometheus-generated plan files at \`.sisyphus/plans/\`
@@ -42284,17 +42419,24 @@ var START_WORK_TEMPLATE = `You are starting a Sisyphus work session.
42284
42419
  - If ONE plan: auto-select it
42285
42420
  - If MULTIPLE plans: show list with timestamps, ask user to select
42286
42421
 
42287
- 4. **Create/Update boulder.json**:
42422
+ 4. **Worktree Setup** (when \`worktree_path\` not already set in boulder.json):
42423
+ 1. \`git worktree list --porcelain\` \u2014 see available worktrees
42424
+ 2. Create: \`git worktree add <absolute-path> <branch-or-HEAD>\`
42425
+ 3. Update boulder.json to add \`"worktree_path": "<absolute-path>"\`
42426
+ 4. All work happens inside that worktree directory
42427
+
42428
+ 5. **Create/Update boulder.json**:
42288
42429
  \`\`\`json
42289
42430
  {
42290
42431
  "active_plan": "/absolute/path/to/plan.md",
42291
42432
  "started_at": "ISO_TIMESTAMP",
42292
42433
  "session_ids": ["session_id_1", "session_id_2"],
42293
- "plan_name": "plan-name"
42434
+ "plan_name": "plan-name",
42435
+ "worktree_path": "/absolute/path/to/git/worktree"
42294
42436
  }
42295
42437
  \`\`\`
42296
42438
 
42297
- 5. **Read the plan file** and start executing tasks according to atlas workflow
42439
+ 6. **Read the plan file** and start executing tasks according to atlas workflow
42298
42440
 
42299
42441
  ## OUTPUT FORMAT
42300
42442
 
@@ -42318,6 +42460,7 @@ Resuming Work Session
42318
42460
  Active Plan: {plan-name}
42319
42461
  Progress: {completed}/{total} tasks
42320
42462
  Sessions: {count} (appending current session)
42463
+ Worktree: {worktree_path}
42321
42464
 
42322
42465
  Reading plan and continuing from last incomplete task...
42323
42466
  \`\`\`
@@ -42329,6 +42472,7 @@ Starting Work Session
42329
42472
  Plan: {plan-name}
42330
42473
  Session ID: {session_id}
42331
42474
  Started: {timestamp}
42475
+ Worktree: {worktree_path}
42332
42476
 
42333
42477
  Reading plan and beginning execution...
42334
42478
  \`\`\`
@@ -42337,6 +42481,7 @@ Reading plan and beginning execution...
42337
42481
 
42338
42482
  - The session_id is injected by the hook - use it directly
42339
42483
  - Always update boulder.json BEFORE starting work
42484
+ - Always set worktree_path in boulder.json before executing any tasks
42340
42485
  - Read the FULL plan file before delegating any tasks
42341
42486
  - Follow atlas delegation protocols (7-section format)`;
42342
42487
 
@@ -45838,8 +45983,8 @@ function getPlanProgress(planPath) {
45838
45983
  }
45839
45984
  try {
45840
45985
  const content = readFileSync35(planPath, "utf-8");
45841
- const uncheckedMatches = content.match(/^[-*]\s*\[\s*\]/gm) || [];
45842
- const checkedMatches = content.match(/^[-*]\s*\[[xX]\]/gm) || [];
45986
+ const uncheckedMatches = content.match(/^\s*[-*]\s*\[\s*\]/gm) || [];
45987
+ const checkedMatches = content.match(/^\s*[-*]\s*\[[xX]\]/gm) || [];
45843
45988
  const total = uncheckedMatches.length + checkedMatches.length;
45844
45989
  const completed = checkedMatches.length;
45845
45990
  return {
@@ -45854,13 +45999,14 @@ function getPlanProgress(planPath) {
45854
45999
  function getPlanName(planPath) {
45855
46000
  return basename3(planPath, ".md");
45856
46001
  }
45857
- function createBoulderState(planPath, sessionId, agent) {
46002
+ function createBoulderState(planPath, sessionId, agent, worktreePath) {
45858
46003
  return {
45859
46004
  active_plan: planPath,
45860
46005
  started_at: new Date().toISOString(),
45861
46006
  session_ids: [sessionId],
45862
46007
  plan_name: getPlanName(planPath),
45863
- ...agent !== undefined ? { agent } : {}
46008
+ ...agent !== undefined ? { agent } : {},
46009
+ ...worktreePath !== undefined ? { worktree_path: worktreePath } : {}
45864
46010
  };
45865
46011
  }
45866
46012
  // src/hooks/prometheus-md-only/agent-resolution.ts
@@ -46060,19 +46206,48 @@ to continue: task(session_id="${sessionId}", prompt="...")`;
46060
46206
  };
46061
46207
  }
46062
46208
  // src/hooks/start-work/start-work-hook.ts
46209
+ import { statSync as statSync6 } from "fs";
46063
46210
  init_logger();
46064
- var HOOK_NAME6 = "start-work";
46065
- var KEYWORD_PATTERN = /\b(ultrawork|ulw)\b/gi;
46066
- function extractUserRequestPlanName(promptText) {
46067
- const userRequestMatch = promptText.match(/<user-request>\s*([\s\S]*?)\s*<\/user-request>/i);
46068
- if (!userRequestMatch)
46211
+
46212
+ // src/hooks/start-work/worktree-detector.ts
46213
+ import { execFileSync as execFileSync2 } from "child_process";
46214
+ function detectWorktreePath(directory) {
46215
+ try {
46216
+ return execFileSync2("git", ["rev-parse", "--show-toplevel"], {
46217
+ cwd: directory,
46218
+ encoding: "utf-8",
46219
+ timeout: 5000,
46220
+ stdio: ["pipe", "pipe", "pipe"]
46221
+ }).trim();
46222
+ } catch {
46069
46223
  return null;
46070
- const rawArg = userRequestMatch[1].trim();
46224
+ }
46225
+ }
46226
+
46227
+ // src/hooks/start-work/parse-user-request.ts
46228
+ var KEYWORD_PATTERN = /\b(ultrawork|ulw)\b/gi;
46229
+ var WORKTREE_FLAG_PATTERN = /--worktree(?:\s+(\S+))?/;
46230
+ function parseUserRequest(promptText) {
46231
+ const match = promptText.match(/<user-request>\s*([\s\S]*?)\s*<\/user-request>/i);
46232
+ if (!match)
46233
+ return { planName: null, explicitWorktreePath: null };
46234
+ let rawArg = match[1].trim();
46071
46235
  if (!rawArg)
46072
- return null;
46236
+ return { planName: null, explicitWorktreePath: null };
46237
+ const worktreeMatch = rawArg.match(WORKTREE_FLAG_PATTERN);
46238
+ const explicitWorktreePath = worktreeMatch ? worktreeMatch[1] ?? null : null;
46239
+ if (worktreeMatch) {
46240
+ rawArg = rawArg.replace(worktreeMatch[0], "").trim();
46241
+ }
46073
46242
  const cleanedArg = rawArg.replace(KEYWORD_PATTERN, "").trim();
46074
- return cleanedArg || null;
46243
+ return {
46244
+ planName: cleanedArg || null,
46245
+ explicitWorktreePath
46246
+ };
46075
46247
  }
46248
+
46249
+ // src/hooks/start-work/start-work-hook.ts
46250
+ var HOOK_NAME6 = "start-work";
46076
46251
  function findPlanByName(plans, requestedName) {
46077
46252
  const lowerName = requestedName.toLowerCase();
46078
46253
  const exactMatch = plans.find((p) => getPlanName(p).toLowerCase() === lowerName);
@@ -46081,29 +46256,48 @@ function findPlanByName(plans, requestedName) {
46081
46256
  const partialMatch = plans.find((p) => getPlanName(p).toLowerCase().includes(lowerName));
46082
46257
  return partialMatch || null;
46083
46258
  }
46259
+ var MODEL_DECIDES_WORKTREE_BLOCK = `
46260
+ ## Worktree Setup Required
46261
+
46262
+ No worktree specified. Before starting work, you MUST choose or create one:
46263
+
46264
+ 1. \`git worktree list --porcelain\` \u2014 list existing worktrees
46265
+ 2. Create if needed: \`git worktree add <absolute-path> <branch-or-HEAD>\`
46266
+ 3. Update \`.sisyphus/boulder.json\` \u2014 add \`"worktree_path": "<absolute-path>"\`
46267
+ 4. Work exclusively inside that worktree directory`;
46268
+ function resolveWorktreeContext(explicitWorktreePath) {
46269
+ if (explicitWorktreePath === null) {
46270
+ return { worktreePath: undefined, block: MODEL_DECIDES_WORKTREE_BLOCK };
46271
+ }
46272
+ const validatedPath = detectWorktreePath(explicitWorktreePath);
46273
+ if (validatedPath) {
46274
+ return { worktreePath: validatedPath, block: `
46275
+ **Worktree**: ${validatedPath}` };
46276
+ }
46277
+ return {
46278
+ worktreePath: undefined,
46279
+ block: `
46280
+ **Worktree** (needs setup): \`git worktree add ${explicitWorktreePath} <branch>\`, then add \`"worktree_path"\` to boulder.json`
46281
+ };
46282
+ }
46084
46283
  function createStartWorkHook(ctx) {
46085
46284
  return {
46086
46285
  "chat.message": async (input, output) => {
46087
46286
  const parts = output.parts;
46088
46287
  const promptText = parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
46089
46288
  `).trim() || "";
46090
- const isStartWorkCommand = promptText.includes("<session-context>");
46091
- if (!isStartWorkCommand) {
46289
+ if (!promptText.includes("<session-context>"))
46092
46290
  return;
46093
- }
46094
- log(`[${HOOK_NAME6}] Processing start-work command`, {
46095
- sessionID: input.sessionID
46096
- });
46291
+ log(`[${HOOK_NAME6}] Processing start-work command`, { sessionID: input.sessionID });
46097
46292
  updateSessionAgent(input.sessionID, "atlas");
46098
46293
  const existingState = readBoulderState(ctx.directory);
46099
46294
  const sessionId = input.sessionID;
46100
46295
  const timestamp2 = new Date().toISOString();
46296
+ const { planName: explicitPlanName, explicitWorktreePath } = parseUserRequest(promptText);
46297
+ const { worktreePath, block: worktreeBlock } = resolveWorktreeContext(explicitWorktreePath);
46101
46298
  let contextInfo = "";
46102
- const explicitPlanName = extractUserRequestPlanName(promptText);
46103
46299
  if (explicitPlanName) {
46104
- log(`[${HOOK_NAME6}] Explicit plan name requested: ${explicitPlanName}`, {
46105
- sessionID: input.sessionID
46106
- });
46300
+ log(`[${HOOK_NAME6}] Explicit plan name requested: ${explicitPlanName}`, { sessionID: input.sessionID });
46107
46301
  const allPlans = findPrometheusPlans(ctx.directory);
46108
46302
  const matchedPlan = findPlanByName(allPlans, explicitPlanName);
46109
46303
  if (matchedPlan) {
@@ -46115,10 +46309,9 @@ function createStartWorkHook(ctx) {
46115
46309
  The requested plan "${getPlanName(matchedPlan)}" has been completed.
46116
46310
  All ${progress.total} tasks are done. Create a new plan with: /plan "your task"`;
46117
46311
  } else {
46118
- if (existingState) {
46312
+ if (existingState)
46119
46313
  clearBoulderState(ctx.directory);
46120
- }
46121
- const newState = createBoulderState(matchedPlan, sessionId, "atlas");
46314
+ const newState = createBoulderState(matchedPlan, sessionId, "atlas", worktreePath);
46122
46315
  writeBoulderState(ctx.directory, newState);
46123
46316
  contextInfo = `
46124
46317
  ## Auto-Selected Plan
@@ -46128,6 +46321,7 @@ All ${progress.total} tasks are done. Create a new plan with: /plan "your task"`
46128
46321
  **Progress**: ${progress.completed}/${progress.total} tasks
46129
46322
  **Session ID**: ${sessionId}
46130
46323
  **Started**: ${timestamp2}
46324
+ ${worktreeBlock}
46131
46325
 
46132
46326
  boulder.json has been created. Read the plan and begin execution.`;
46133
46327
  }
@@ -46159,7 +46353,19 @@ No incomplete plans available. Create a new plan with: /plan "your task"`;
46159
46353
  } else if (existingState) {
46160
46354
  const progress = getPlanProgress(existingState.active_plan);
46161
46355
  if (!progress.isComplete) {
46162
- appendSessionId(ctx.directory, sessionId);
46356
+ const effectiveWorktree = worktreePath ?? existingState.worktree_path;
46357
+ if (worktreePath !== undefined) {
46358
+ const updatedSessions = existingState.session_ids.includes(sessionId) ? existingState.session_ids : [...existingState.session_ids, sessionId];
46359
+ writeBoulderState(ctx.directory, {
46360
+ ...existingState,
46361
+ worktree_path: worktreePath,
46362
+ session_ids: updatedSessions
46363
+ });
46364
+ } else {
46365
+ appendSessionId(ctx.directory, sessionId);
46366
+ }
46367
+ const worktreeDisplay = effectiveWorktree ? `
46368
+ **Worktree**: ${effectiveWorktree}` : worktreeBlock;
46163
46369
  contextInfo = `
46164
46370
  ## Active Work Session Found
46165
46371
 
@@ -46169,6 +46375,7 @@ No incomplete plans available. Create a new plan with: /plan "your task"`;
46169
46375
  **Progress**: ${progress.completed}/${progress.total} tasks completed
46170
46376
  **Sessions**: ${existingState.session_ids.length + 1} (current session appended)
46171
46377
  **Started**: ${existingState.started_at}
46378
+ ${worktreeDisplay}
46172
46379
 
46173
46380
  The current session (${sessionId}) has been added to session_ids.
46174
46381
  Read the plan file and continue from the first unchecked task.`;
@@ -46185,7 +46392,6 @@ Looking for new plans...`;
46185
46392
  const incompletePlans = plans.filter((p) => !getPlanProgress(p).isComplete);
46186
46393
  if (plans.length === 0) {
46187
46394
  contextInfo += `
46188
-
46189
46395
  ## No Plans Found
46190
46396
 
46191
46397
  No Prometheus plan files found at .sisyphus/plans/
@@ -46199,7 +46405,7 @@ All ${plans.length} plan(s) are complete. Create a new plan with: /plan "your ta
46199
46405
  } else if (incompletePlans.length === 1) {
46200
46406
  const planPath = incompletePlans[0];
46201
46407
  const progress = getPlanProgress(planPath);
46202
- const newState = createBoulderState(planPath, sessionId, "atlas");
46408
+ const newState = createBoulderState(planPath, sessionId, "atlas", worktreePath);
46203
46409
  writeBoulderState(ctx.directory, newState);
46204
46410
  contextInfo += `
46205
46411
 
@@ -46210,13 +46416,13 @@ All ${plans.length} plan(s) are complete. Create a new plan with: /plan "your ta
46210
46416
  **Progress**: ${progress.completed}/${progress.total} tasks
46211
46417
  **Session ID**: ${sessionId}
46212
46418
  **Started**: ${timestamp2}
46419
+ ${worktreeBlock}
46213
46420
 
46214
46421
  boulder.json has been created. Read the plan and begin execution.`;
46215
46422
  } else {
46216
46423
  const planList = incompletePlans.map((p, i2) => {
46217
46424
  const progress = getPlanProgress(p);
46218
- const stat = __require("fs").statSync(p);
46219
- const modified = new Date(stat.mtimeMs).toISOString();
46425
+ const modified = new Date(statSync6(p).mtimeMs).toISOString();
46220
46426
  return `${i2 + 1}. [${getPlanName(p)}] - Modified: ${modified} - Progress: ${progress.completed}/${progress.total}`;
46221
46427
  }).join(`
46222
46428
  `);
@@ -46231,6 +46437,7 @@ Session ID: ${sessionId}
46231
46437
  ${planList}
46232
46438
 
46233
46439
  Ask the user which plan to work on. Present the options above and wait for their response.
46440
+ ${worktreeBlock}
46234
46441
  </system-reminder>`;
46235
46442
  }
46236
46443
  }
@@ -46244,7 +46451,8 @@ ${contextInfo}`;
46244
46451
  }
46245
46452
  log(`[${HOOK_NAME6}] Context injected`, {
46246
46453
  sessionID: input.sessionID,
46247
- hasExistingState: !!existingState
46454
+ hasExistingState: !!existingState,
46455
+ worktreePath
46248
46456
  });
46249
46457
  }
46250
46458
  };
@@ -46496,6 +46704,7 @@ async function injectBoulderContinuation(input) {
46496
46704
  remaining,
46497
46705
  total,
46498
46706
  agent,
46707
+ worktreePath,
46499
46708
  backgroundManager,
46500
46709
  sessionState
46501
46710
  } = input;
@@ -46504,9 +46713,12 @@ async function injectBoulderContinuation(input) {
46504
46713
  log(`[${HOOK_NAME7}] Skipped injection: background tasks running`, { sessionID });
46505
46714
  return;
46506
46715
  }
46716
+ const worktreeContext = worktreePath ? `
46717
+
46718
+ [Worktree: ${worktreePath}]` : "";
46507
46719
  const prompt = BOULDER_CONTINUATION_PROMPT.replace(/{PLAN_NAME}/g, planName) + `
46508
46720
 
46509
- [Status: ${total - remaining}/${total} completed, ${remaining} remaining]`;
46721
+ [Status: ${total - remaining}/${total} completed, ${remaining} remaining]` + worktreeContext;
46510
46722
  try {
46511
46723
  log(`[${HOOK_NAME7}] Injecting boulder continuation`, { sessionID, planName, remaining });
46512
46724
  const promptContext = await resolveRecentPromptContextForSession(ctx, sessionID);
@@ -46525,6 +46737,7 @@ async function injectBoulderContinuation(input) {
46525
46737
  log(`[${HOOK_NAME7}] Boulder continuation injected`, { sessionID });
46526
46738
  } catch (err) {
46527
46739
  sessionState.promptFailureCount += 1;
46740
+ sessionState.lastFailureAt = Date.now();
46528
46741
  log(`[${HOOK_NAME7}] Boulder continuation failed`, {
46529
46742
  sessionID,
46530
46743
  error: String(err),
@@ -46549,6 +46762,7 @@ async function getLastAgentFromSession(sessionID, client) {
46549
46762
 
46550
46763
  // src/hooks/atlas/event-handler.ts
46551
46764
  var CONTINUATION_COOLDOWN_MS2 = 5000;
46765
+ var FAILURE_BACKOFF_MS = 5 * 60 * 1000;
46552
46766
  function createAtlasEventHandler(input) {
46553
46767
  const { ctx, options, sessions, getState } = input;
46554
46768
  return async ({ event }) => {
@@ -46576,17 +46790,24 @@ function createAtlasEventHandler(input) {
46576
46790
  return;
46577
46791
  }
46578
46792
  const state3 = getState(sessionID);
46793
+ const now = Date.now();
46579
46794
  if (state3.lastEventWasAbortError) {
46580
46795
  state3.lastEventWasAbortError = false;
46581
46796
  log(`[${HOOK_NAME7}] Skipped: abort error immediately before idle`, { sessionID });
46582
46797
  return;
46583
46798
  }
46584
46799
  if (state3.promptFailureCount >= 2) {
46585
- log(`[${HOOK_NAME7}] Skipped: continuation disabled after repeated prompt failures`, {
46586
- sessionID,
46587
- promptFailureCount: state3.promptFailureCount
46588
- });
46589
- return;
46800
+ const timeSinceLastFailure = state3.lastFailureAt !== undefined ? now - state3.lastFailureAt : Number.POSITIVE_INFINITY;
46801
+ if (timeSinceLastFailure < FAILURE_BACKOFF_MS) {
46802
+ log(`[${HOOK_NAME7}] Skipped: continuation in backoff after repeated failures`, {
46803
+ sessionID,
46804
+ promptFailureCount: state3.promptFailureCount,
46805
+ backoffRemaining: FAILURE_BACKOFF_MS - timeSinceLastFailure
46806
+ });
46807
+ return;
46808
+ }
46809
+ state3.promptFailureCount = 0;
46810
+ state3.lastFailureAt = undefined;
46590
46811
  }
46591
46812
  const backgroundManager = options?.backgroundManager;
46592
46813
  const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
@@ -46606,17 +46827,15 @@ function createAtlasEventHandler(input) {
46606
46827
  const lastAgentKey = getAgentConfigKey(lastAgent ?? "");
46607
46828
  const requiredAgent = getAgentConfigKey(boulderState.agent ?? "atlas");
46608
46829
  const lastAgentMatchesRequired = lastAgentKey === requiredAgent;
46609
- const boulderAgentWasNotExplicitlySet = boulderState.agent === undefined;
46610
46830
  const boulderAgentDefaultsToAtlas = requiredAgent === "atlas";
46611
46831
  const lastAgentIsSisyphus = lastAgentKey === "sisyphus";
46612
- const allowSisyphusWhenDefaultAtlas = boulderAgentWasNotExplicitlySet && boulderAgentDefaultsToAtlas && lastAgentIsSisyphus;
46613
- const agentMatches = lastAgentMatchesRequired || allowSisyphusWhenDefaultAtlas;
46832
+ const allowSisyphusForAtlasBoulder = boulderAgentDefaultsToAtlas && lastAgentIsSisyphus;
46833
+ const agentMatches = lastAgentMatchesRequired || allowSisyphusForAtlasBoulder;
46614
46834
  if (!agentMatches) {
46615
46835
  log(`[${HOOK_NAME7}] Skipped: last agent does not match boulder agent`, {
46616
46836
  sessionID,
46617
46837
  lastAgent: lastAgent ?? "unknown",
46618
- requiredAgent,
46619
- boulderAgentExplicitlySet: boulderState.agent !== undefined
46838
+ requiredAgent
46620
46839
  });
46621
46840
  return;
46622
46841
  }
@@ -46625,7 +46844,6 @@ function createAtlasEventHandler(input) {
46625
46844
  log(`[${HOOK_NAME7}] Boulder complete`, { sessionID, plan: boulderState.plan_name });
46626
46845
  return;
46627
46846
  }
46628
- const now = Date.now();
46629
46847
  if (state3.lastContinuationInjectedAt && now - state3.lastContinuationInjectedAt < CONTINUATION_COOLDOWN_MS2) {
46630
46848
  log(`[${HOOK_NAME7}] Skipped: continuation cooldown active`, {
46631
46849
  sessionID,
@@ -46643,6 +46861,7 @@ function createAtlasEventHandler(input) {
46643
46861
  remaining,
46644
46862
  total: progress.total,
46645
46863
  agent: boulderState.agent,
46864
+ worktreePath: boulderState.worktree_path,
46646
46865
  backgroundManager,
46647
46866
  sessionState: state3
46648
46867
  });
@@ -47125,12 +47344,36 @@ function createQuestionLabelTruncatorHook() {
47125
47344
  // src/hooks/stop-continuation-guard/hook.ts
47126
47345
  init_logger();
47127
47346
  var HOOK_NAME8 = "stop-continuation-guard";
47128
- function createStopContinuationGuardHook(ctx) {
47347
+ function createStopContinuationGuardHook(ctx, options) {
47129
47348
  const stoppedSessions = new Set;
47130
47349
  const stop = (sessionID) => {
47131
47350
  stoppedSessions.add(sessionID);
47132
47351
  setContinuationMarkerSource(ctx.directory, sessionID, "stop", "stopped", "continuation stopped");
47133
47352
  log(`[${HOOK_NAME8}] Continuation stopped for session`, { sessionID });
47353
+ const backgroundManager = options?.backgroundManager;
47354
+ if (!backgroundManager) {
47355
+ return;
47356
+ }
47357
+ const cancellableTasks = backgroundManager.getAllDescendantTasks(sessionID).filter((task) => task.status === "running" || task.status === "pending");
47358
+ if (cancellableTasks.length === 0) {
47359
+ return;
47360
+ }
47361
+ Promise.allSettled(cancellableTasks.map(async (task) => {
47362
+ await backgroundManager.cancelTask(task.id, {
47363
+ source: "stop-continuation",
47364
+ reason: "Continuation stopped via /stop-continuation",
47365
+ abortSession: task.status === "running",
47366
+ skipNotification: true
47367
+ });
47368
+ })).then((results) => {
47369
+ const cancelledCount = results.filter((result) => result.status === "fulfilled").length;
47370
+ const failedCount = results.length - cancelledCount;
47371
+ log(`[${HOOK_NAME8}] Cancelled background tasks for stopped session`, {
47372
+ sessionID,
47373
+ cancelledCount,
47374
+ failedCount
47375
+ });
47376
+ });
47134
47377
  };
47135
47378
  const isStopped = (sessionID) => {
47136
47379
  return stoppedSessions.has(sessionID);
@@ -47531,10 +47774,24 @@ function createUnstableAgentBabysitterHook(ctx, options) {
47531
47774
  // src/hooks/preemptive-compaction.ts
47532
47775
  init_logger();
47533
47776
  var DEFAULT_ACTUAL_LIMIT = 200000;
47777
+ var PREEMPTIVE_COMPACTION_TIMEOUT_MS = 120000;
47534
47778
  function getAnthropicActualLimit3(modelCacheState) {
47535
47779
  return (modelCacheState?.anthropicContext1MEnabled ?? false) || process.env.ANTHROPIC_1M_CONTEXT === "true" || process.env.VERTEX_ANTHROPIC_1M_CONTEXT === "true" ? 1e6 : DEFAULT_ACTUAL_LIMIT;
47536
47780
  }
47537
47781
  var PREEMPTIVE_COMPACTION_THRESHOLD = 0.78;
47782
+ function withTimeout2(promise2, timeoutMs, errorMessage) {
47783
+ let timeoutID;
47784
+ const timeoutPromise = new Promise((_, reject) => {
47785
+ timeoutID = setTimeout(() => {
47786
+ reject(new Error(errorMessage));
47787
+ }, timeoutMs);
47788
+ });
47789
+ return Promise.race([promise2, timeoutPromise]).finally(() => {
47790
+ if (timeoutID !== undefined) {
47791
+ clearTimeout(timeoutID);
47792
+ }
47793
+ });
47794
+ }
47538
47795
  function isAnthropicProvider2(providerID) {
47539
47796
  return providerID === "anthropic" || providerID === "google-vertex-anthropic";
47540
47797
  }
@@ -47561,11 +47818,11 @@ function createPreemptiveCompactionHook(ctx, pluginConfig, modelCacheState) {
47561
47818
  compactionInProgress.add(sessionID);
47562
47819
  try {
47563
47820
  const { providerID: targetProviderID, modelID: targetModelID } = resolveCompactionModel(pluginConfig, sessionID, cached2.providerID, modelID);
47564
- await ctx.client.session.summarize({
47821
+ await withTimeout2(ctx.client.session.summarize({
47565
47822
  path: { id: sessionID },
47566
47823
  body: { providerID: targetProviderID, modelID: targetModelID, auto: true },
47567
47824
  query: { directory: ctx.directory }
47568
- });
47825
+ }), PREEMPTIVE_COMPACTION_TIMEOUT_MS, `Compaction summarize timed out after ${PREEMPTIVE_COMPACTION_TIMEOUT_MS}ms`);
47569
47826
  compactedSessions.add(sessionID);
47570
47827
  } catch (error45) {
47571
47828
  log("[preemptive-compaction] Compaction failed", { sessionID, error: String(error45) });
@@ -47773,7 +48030,9 @@ var AgentOverridesSchema = exports_external.object({
47773
48030
  build: AgentOverrideConfigSchema.optional(),
47774
48031
  plan: AgentOverrideConfigSchema.optional(),
47775
48032
  sisyphus: AgentOverrideConfigSchema.optional(),
47776
- hephaestus: AgentOverrideConfigSchema.optional(),
48033
+ hephaestus: AgentOverrideConfigSchema.extend({
48034
+ allow_non_gpt_model: exports_external.boolean().optional()
48035
+ }).optional(),
47777
48036
  "sisyphus-junior": AgentOverrideConfigSchema.optional(),
47778
48037
  "OpenCode-Builder": AgentOverrideConfigSchema.optional(),
47779
48038
  prometheus: AgentOverrideConfigSchema.optional(),
@@ -47824,6 +48083,7 @@ var CategoryConfigSchema = exports_external.object({
47824
48083
  textVerbosity: exports_external.enum(["low", "medium", "high"]).optional(),
47825
48084
  tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
47826
48085
  prompt_append: exports_external.string().optional(),
48086
+ max_prompt_tokens: exports_external.number().int().positive().optional(),
47827
48087
  is_unstable_agent: exports_external.boolean().optional(),
47828
48088
  disable: exports_external.boolean().optional()
47829
48089
  });
@@ -48066,7 +48326,7 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
48066
48326
  new_task_system_enabled: exports_external.boolean().optional(),
48067
48327
  default_run_agent: exports_external.string().optional(),
48068
48328
  disabled_mcps: exports_external.array(AnyMcpNameSchema).optional(),
48069
- disabled_agents: exports_external.array(BuiltinAgentNameSchema).optional(),
48329
+ disabled_agents: exports_external.array(exports_external.string()).optional(),
48070
48330
  disabled_skills: exports_external.array(BuiltinSkillNameSchema).optional(),
48071
48331
  disabled_hooks: exports_external.array(exports_external.string()).optional(),
48072
48332
  disabled_commands: exports_external.array(BuiltinCommandNameSchema).optional(),
@@ -50019,9 +50279,9 @@ function getLanguageId(ext) {
50019
50279
  }
50020
50280
  // src/tools/lsp/lsp-process.ts
50021
50281
  init_logger();
50022
- var {spawn: bunSpawn } = globalThis.Bun;
50023
- import { spawn as nodeSpawn } from "child_process";
50024
- import { existsSync as existsSync51, statSync as statSync6 } from "fs";
50282
+ var {spawn: bunSpawn2 } = globalThis.Bun;
50283
+ import { spawn as nodeSpawn2 } from "child_process";
50284
+ import { existsSync as existsSync51, statSync as statSync7 } from "fs";
50025
50285
  function shouldUseNodeSpawn() {
50026
50286
  return process.platform === "win32";
50027
50287
  }
@@ -50030,7 +50290,7 @@ function validateCwd(cwd) {
50030
50290
  if (!existsSync51(cwd)) {
50031
50291
  return { valid: false, error: `Working directory does not exist: ${cwd}` };
50032
50292
  }
50033
- const stats = statSync6(cwd);
50293
+ const stats = statSync7(cwd);
50034
50294
  if (!stats.isDirectory()) {
50035
50295
  return { valid: false, error: `Path is not a directory: ${cwd}` };
50036
50296
  }
@@ -50039,7 +50299,7 @@ function validateCwd(cwd) {
50039
50299
  return { valid: false, error: `Cannot access working directory: ${cwd} (${err instanceof Error ? err.message : String(err)})` };
50040
50300
  }
50041
50301
  }
50042
- function wrapNodeProcess(proc) {
50302
+ function wrapNodeProcess2(proc) {
50043
50303
  let resolveExited;
50044
50304
  let exitCode = null;
50045
50305
  const exitedPromise = new Promise((resolve8) => {
@@ -50140,16 +50400,16 @@ function spawnProcess(command, options) {
50140
50400
  if (shouldUseNodeSpawn()) {
50141
50401
  const [cmd, ...args] = command;
50142
50402
  log("[LSP] Using Node.js child_process on Windows to avoid Bun spawn segfault");
50143
- const proc2 = nodeSpawn(cmd, args, {
50403
+ const proc2 = nodeSpawn2(cmd, args, {
50144
50404
  cwd: options.cwd,
50145
50405
  env: options.env,
50146
50406
  stdio: ["pipe", "pipe", "pipe"],
50147
50407
  windowsHide: true,
50148
50408
  shell: true
50149
50409
  });
50150
- return wrapNodeProcess(proc2);
50410
+ return wrapNodeProcess2(proc2);
50151
50411
  }
50152
- const proc = bunSpawn(command, {
50412
+ const proc = bunSpawn2(command, {
50153
50413
  stdin: "pipe",
50154
50414
  stdout: "pipe",
50155
50415
  stderr: "pipe",
@@ -50168,7 +50428,7 @@ import { pathToFileURL } from "url";
50168
50428
 
50169
50429
  // src/tools/lsp/lsp-client-transport.ts
50170
50430
  var import_node = __toESM(require_main(), 1);
50171
- import { Readable, Writable } from "stream";
50431
+ import { Readable as Readable2, Writable } from "stream";
50172
50432
  init_logger();
50173
50433
 
50174
50434
  class LSPClientTransport {
@@ -50204,7 +50464,7 @@ class LSPClientTransport {
50204
50464
  stderr: ${stderr}` : ""));
50205
50465
  }
50206
50466
  const stdoutReader = this.proc.stdout.getReader();
50207
- const nodeReadable = new Readable({
50467
+ const nodeReadable = new Readable2({
50208
50468
  async read() {
50209
50469
  try {
50210
50470
  const { done, value } = await stdoutReader.read();
@@ -51301,7 +51561,7 @@ var DEFAULT_MAX_MATCHES = 500;
51301
51561
  // src/tools/ast-grep/sg-cli-path.ts
51302
51562
  import { createRequire as createRequire4 } from "module";
51303
51563
  import { dirname as dirname16, join as join63 } from "path";
51304
- import { existsSync as existsSync54, statSync as statSync7 } from "fs";
51564
+ import { existsSync as existsSync54, statSync as statSync8 } from "fs";
51305
51565
 
51306
51566
  // src/tools/ast-grep/downloader.ts
51307
51567
  import { existsSync as existsSync53 } from "fs";
@@ -51388,7 +51648,7 @@ async function ensureAstGrepBinary() {
51388
51648
  // src/tools/ast-grep/sg-cli-path.ts
51389
51649
  function isValidBinary(filePath) {
51390
51650
  try {
51391
- return statSync7(filePath).size > 1e4;
51651
+ return statSync8(filePath).size > 1e4;
51392
51652
  } catch {
51393
51653
  return false;
51394
51654
  }
@@ -53470,7 +53730,7 @@ async function searchInSession(sessionID, query, caseSensitive = false, maxResul
53470
53730
  // src/tools/session-manager/tools.ts
53471
53731
  var SEARCH_TIMEOUT_MS = 60000;
53472
53732
  var MAX_SESSIONS_TO_SCAN = 50;
53473
- function withTimeout2(promise2, ms, operation) {
53733
+ function withTimeout3(promise2, ms, operation) {
53474
53734
  return Promise.race([
53475
53735
  promise2,
53476
53736
  new Promise((_, reject) => setTimeout(() => reject(new Error(`${operation} timed out after ${ms}ms`)), ms))
@@ -53557,7 +53817,7 @@ function createSessionManagerTools(ctx) {
53557
53817
  }
53558
53818
  return allResults.slice(0, resultLimit);
53559
53819
  };
53560
- const results = await withTimeout2(searchOperation(), SEARCH_TIMEOUT_MS, "Search");
53820
+ const results = await withTimeout3(searchOperation(), SEARCH_TIMEOUT_MS, "Search");
53561
53821
  return formatSearchResults(results);
53562
53822
  } catch (e) {
53563
53823
  return `Error: ${e instanceof Error ? e.message : String(e)}`;
@@ -53675,7 +53935,7 @@ tmux capture-pane -p -t ${sessionName} -S -1000
53675
53935
 
53676
53936
  The Bash tool can execute these commands directly. Do NOT retry with interactive_bash.`;
53677
53937
  }
53678
- const proc = Bun.spawn([tmuxPath2, ...parts], {
53938
+ const proc = spawnWithWindowsHide([tmuxPath2, ...parts], {
53679
53939
  stdout: "pipe",
53680
53940
  stderr: "pipe"
53681
53941
  });
@@ -54250,6 +54510,14 @@ function formatResolvedTitle(task) {
54250
54510
  const label = task.agent === SISYPHUS_JUNIOR_AGENT && task.category ? task.category : task.agent;
54251
54511
  return `${label} - ${task.description}`;
54252
54512
  }
54513
+ function isTaskActiveStatus(status) {
54514
+ return status === "pending" || status === "running";
54515
+ }
54516
+ function appendTimeoutNote(output, timeoutMs) {
54517
+ return `${output}
54518
+
54519
+ > **Timed out waiting** after ${timeoutMs}ms. Task is still running; showing latest available output.`;
54520
+ }
54253
54521
  function createBackgroundOutput(manager, client2) {
54254
54522
  return tool({
54255
54523
  description: BACKGROUND_OUTPUT_DESCRIPTION,
@@ -54290,7 +54558,8 @@ function createBackgroundOutput(manager, client2) {
54290
54558
  const timeoutMs = Math.min(args.timeout ?? 60000, 600000);
54291
54559
  const fullSession = args.full_session ?? true;
54292
54560
  let resolvedTask = task;
54293
- if (shouldBlock && (task.status === "pending" || task.status === "running")) {
54561
+ let didTimeoutWhileActive = false;
54562
+ if (shouldBlock && isTaskActiveStatus(task.status)) {
54294
54563
  const startTime = Date.now();
54295
54564
  while (Date.now() - startTime < timeoutMs) {
54296
54565
  await delay3(1000);
@@ -54298,27 +54567,33 @@ function createBackgroundOutput(manager, client2) {
54298
54567
  if (!currentTask) {
54299
54568
  return `Task was deleted: ${args.task_id}`;
54300
54569
  }
54301
- if (currentTask.status !== "pending" && currentTask.status !== "running") {
54302
- resolvedTask = currentTask;
54570
+ resolvedTask = currentTask;
54571
+ if (!isTaskActiveStatus(currentTask.status)) {
54303
54572
  break;
54304
54573
  }
54305
54574
  }
54306
- const finalCheck = manager.getTask(args.task_id);
54307
- if (finalCheck) {
54308
- resolvedTask = finalCheck;
54575
+ if (isTaskActiveStatus(resolvedTask.status)) {
54576
+ const finalCheck = manager.getTask(args.task_id);
54577
+ if (finalCheck) {
54578
+ resolvedTask = finalCheck;
54579
+ }
54580
+ }
54581
+ if (isTaskActiveStatus(resolvedTask.status)) {
54582
+ didTimeoutWhileActive = true;
54309
54583
  }
54310
54584
  }
54311
- const isActive = resolvedTask.status === "pending" || resolvedTask.status === "running";
54585
+ const isActive = isTaskActiveStatus(resolvedTask.status);
54312
54586
  const includeThinking = isActive || (args.include_thinking ?? false);
54313
54587
  const includeToolResults = isActive || (args.include_tool_results ?? false);
54314
54588
  if (fullSession) {
54315
- return await formatFullSession(resolvedTask, client2, {
54589
+ const output = await formatFullSession(resolvedTask, client2, {
54316
54590
  includeThinking,
54317
54591
  messageLimit: args.message_limit,
54318
54592
  sinceMessageId: args.since_message_id,
54319
54593
  includeToolResults,
54320
54594
  thinkingMaxChars: args.thinking_max_chars
54321
54595
  });
54596
+ return didTimeoutWhileActive ? appendTimeoutNote(output, timeoutMs) : output;
54322
54597
  }
54323
54598
  if (resolvedTask.status === "completed") {
54324
54599
  return await formatTaskResult(resolvedTask, client2);
@@ -54326,7 +54601,8 @@ function createBackgroundOutput(manager, client2) {
54326
54601
  if (resolvedTask.status === "error" || resolvedTask.status === "cancelled" || resolvedTask.status === "interrupt") {
54327
54602
  return formatTaskStatus(resolvedTask);
54328
54603
  }
54329
- return formatTaskStatus(resolvedTask);
54604
+ const statusOutput = formatTaskStatus(resolvedTask);
54605
+ return didTimeoutWhileActive ? appendTimeoutNote(statusOutput, timeoutMs) : statusOutput;
54330
54606
  } catch (error45) {
54331
54607
  return `Error getting output: ${error45 instanceof Error ? error45.message : String(error45)}`;
54332
54608
  }
@@ -55062,47 +55338,153 @@ init_logger();
55062
55338
 
55063
55339
  // src/tools/delegate-task/prompt-builder.ts
55064
55340
  init_constants();
55341
+
55342
+ // src/tools/delegate-task/token-limiter.ts
55343
+ var CHARACTERS_PER_TOKEN = 4;
55344
+ function estimateTokenCount(text) {
55345
+ if (!text) {
55346
+ return 0;
55347
+ }
55348
+ return Math.ceil(text.length / CHARACTERS_PER_TOKEN);
55349
+ }
55350
+ function truncateToTokenBudget(content, maxTokens) {
55351
+ if (!content || maxTokens <= 0) {
55352
+ return "";
55353
+ }
55354
+ const maxCharacters = maxTokens * CHARACTERS_PER_TOKEN;
55355
+ if (content.length <= maxCharacters) {
55356
+ return content;
55357
+ }
55358
+ const sliced = content.slice(0, maxCharacters);
55359
+ const lastNewline = sliced.lastIndexOf(`
55360
+ `);
55361
+ if (lastNewline > 0) {
55362
+ return `${sliced.slice(0, lastNewline)}
55363
+ [TRUNCATED]`;
55364
+ }
55365
+ return `${sliced}
55366
+ [TRUNCATED]`;
55367
+ }
55368
+ function joinSystemParts(parts) {
55369
+ const filtered = parts.filter((part) => part.trim().length > 0);
55370
+ if (filtered.length === 0) {
55371
+ return;
55372
+ }
55373
+ return filtered.join(`
55374
+
55375
+ `);
55376
+ }
55377
+ function reduceSegmentToFitBudget(content, overflowTokens) {
55378
+ if (overflowTokens <= 0 || !content) {
55379
+ return content;
55380
+ }
55381
+ const currentTokens = estimateTokenCount(content);
55382
+ const nextBudget = Math.max(0, currentTokens - overflowTokens);
55383
+ return truncateToTokenBudget(content, nextBudget);
55384
+ }
55385
+ function buildSystemContentWithTokenLimit(input, maxTokens) {
55386
+ const skillParts = input.skillContents?.length ? [...input.skillContents] : input.skillContent ? [input.skillContent] : [];
55387
+ const categoryPromptAppend = input.categoryPromptAppend ?? "";
55388
+ const agentsContext = input.agentsContext ?? input.planAgentPrepend ?? "";
55389
+ if (maxTokens === undefined) {
55390
+ return joinSystemParts([agentsContext, ...skillParts, categoryPromptAppend]);
55391
+ }
55392
+ let nextSkills = [...skillParts];
55393
+ let nextCategoryPromptAppend = categoryPromptAppend;
55394
+ let nextAgentsContext = agentsContext;
55395
+ const buildCurrentContent = () => joinSystemParts([nextAgentsContext, ...nextSkills, nextCategoryPromptAppend]);
55396
+ let systemContent = buildCurrentContent();
55397
+ if (!systemContent) {
55398
+ return;
55399
+ }
55400
+ let overflowTokens = estimateTokenCount(systemContent) - maxTokens;
55401
+ if (overflowTokens > 0) {
55402
+ for (let index = 0;index < nextSkills.length && overflowTokens > 0; index += 1) {
55403
+ const skill2 = nextSkills[index];
55404
+ const reducedSkill = reduceSegmentToFitBudget(skill2, overflowTokens);
55405
+ nextSkills[index] = reducedSkill;
55406
+ systemContent = buildCurrentContent();
55407
+ if (!systemContent) {
55408
+ return;
55409
+ }
55410
+ overflowTokens = estimateTokenCount(systemContent) - maxTokens;
55411
+ }
55412
+ nextSkills = nextSkills.filter((skill2) => skill2.trim().length > 0);
55413
+ systemContent = buildCurrentContent();
55414
+ if (!systemContent) {
55415
+ return;
55416
+ }
55417
+ overflowTokens = estimateTokenCount(systemContent) - maxTokens;
55418
+ }
55419
+ if (overflowTokens > 0 && nextCategoryPromptAppend) {
55420
+ nextCategoryPromptAppend = reduceSegmentToFitBudget(nextCategoryPromptAppend, overflowTokens);
55421
+ systemContent = buildCurrentContent();
55422
+ if (!systemContent) {
55423
+ return;
55424
+ }
55425
+ overflowTokens = estimateTokenCount(systemContent) - maxTokens;
55426
+ }
55427
+ if (overflowTokens > 0 && nextAgentsContext) {
55428
+ nextAgentsContext = reduceSegmentToFitBudget(nextAgentsContext, overflowTokens);
55429
+ systemContent = buildCurrentContent();
55430
+ if (!systemContent) {
55431
+ return;
55432
+ }
55433
+ }
55434
+ if (!systemContent) {
55435
+ return;
55436
+ }
55437
+ return truncateToTokenBudget(systemContent, maxTokens);
55438
+ }
55439
+
55440
+ // src/tools/delegate-task/prompt-builder.ts
55441
+ var FREE_OR_LOCAL_PROMPT_TOKEN_LIMIT = 24000;
55442
+ function usesFreeOrLocalModel(model) {
55443
+ if (!model) {
55444
+ return false;
55445
+ }
55446
+ const provider = model.providerID.toLowerCase();
55447
+ const modelId = model.modelID.toLowerCase();
55448
+ return provider.includes("local") || provider === "ollama" || provider === "lmstudio" || modelId.includes("free");
55449
+ }
55065
55450
  function buildSystemContent(input) {
55066
55451
  const {
55067
55452
  skillContent,
55453
+ skillContents,
55068
55454
  categoryPromptAppend,
55455
+ agentsContext,
55456
+ maxPromptTokens,
55457
+ model,
55069
55458
  agentName,
55070
55459
  availableCategories,
55071
55460
  availableSkills
55072
55461
  } = input;
55073
55462
  const planAgentPrepend = isPlanAgent(agentName) ? buildPlanAgentSystemPrepend(availableCategories, availableSkills) : "";
55074
- if (!skillContent && !categoryPromptAppend && !planAgentPrepend) {
55075
- return;
55076
- }
55077
- const parts = [];
55078
- if (planAgentPrepend) {
55079
- parts.push(planAgentPrepend);
55080
- }
55081
- if (skillContent) {
55082
- parts.push(skillContent);
55083
- }
55084
- if (categoryPromptAppend) {
55085
- parts.push(categoryPromptAppend);
55086
- }
55087
- return parts.join(`
55088
-
55089
- `) || undefined;
55463
+ const effectiveMaxPromptTokens = maxPromptTokens ?? (usesFreeOrLocalModel(model) ? FREE_OR_LOCAL_PROMPT_TOKEN_LIMIT : undefined);
55464
+ return buildSystemContentWithTokenLimit({
55465
+ skillContent,
55466
+ skillContents,
55467
+ categoryPromptAppend,
55468
+ agentsContext: agentsContext ?? planAgentPrepend,
55469
+ planAgentPrepend
55470
+ }, effectiveMaxPromptTokens);
55090
55471
  }
55091
55472
 
55092
55473
  // src/tools/delegate-task/skill-resolver.ts
55093
55474
  async function resolveSkillContent2(skills2, options) {
55094
55475
  if (skills2.length === 0) {
55095
- return { content: undefined, error: null };
55476
+ return { content: undefined, contents: [], error: null };
55096
55477
  }
55097
55478
  const { resolved, notFound } = await resolveMultipleSkillsAsync(skills2, options);
55098
55479
  if (notFound.length > 0) {
55099
55480
  const allSkills = await discoverSkills({ includeClaudeCodePaths: true, directory: options?.directory });
55100
55481
  const available = allSkills.map((s) => s.name).join(", ");
55101
- return { content: undefined, error: `Skills not found: ${notFound.join(", ")}. Available: ${available}` };
55482
+ return { content: undefined, contents: [], error: `Skills not found: ${notFound.join(", ")}. Available: ${available}` };
55102
55483
  }
55103
- return { content: Array.from(resolved.values()).join(`
55484
+ const contents = Array.from(resolved.values());
55485
+ return { content: contents.join(`
55104
55486
 
55105
- `), error: null };
55487
+ `), contents, error: null };
55106
55488
  }
55107
55489
  // src/tools/delegate-task/parent-context-resolver.ts
55108
55490
  init_logger();
@@ -56173,6 +56555,7 @@ async function resolveCategoryExecution(args, executorCtx, inheritedModel, syste
56173
56555
  agentToUse: "",
56174
56556
  categoryModel: undefined,
56175
56557
  categoryPromptAppend: undefined,
56558
+ maxPromptTokens: undefined,
56176
56559
  modelInfo: undefined,
56177
56560
  actualModel: undefined,
56178
56561
  isUnstableAgent: false,
@@ -56189,6 +56572,7 @@ Available categories: ${allCategoryNames}`
56189
56572
  agentToUse: "",
56190
56573
  categoryModel: undefined,
56191
56574
  categoryPromptAppend: undefined,
56575
+ maxPromptTokens: undefined,
56192
56576
  modelInfo: undefined,
56193
56577
  actualModel: undefined,
56194
56578
  isUnstableAgent: false,
@@ -56222,6 +56606,7 @@ Available categories: ${allCategoryNames}`
56222
56606
  agentToUse: "",
56223
56607
  categoryModel: undefined,
56224
56608
  categoryPromptAppend: undefined,
56609
+ maxPromptTokens: undefined,
56225
56610
  modelInfo: undefined,
56226
56611
  actualModel: undefined,
56227
56612
  isUnstableAgent: false,
@@ -56247,6 +56632,7 @@ Available categories: ${allCategoryNames}`
56247
56632
  agentToUse: "",
56248
56633
  categoryModel: undefined,
56249
56634
  categoryPromptAppend: undefined,
56635
+ maxPromptTokens: undefined,
56250
56636
  modelInfo: undefined,
56251
56637
  actualModel: undefined,
56252
56638
  isUnstableAgent: false,
@@ -56268,6 +56654,7 @@ Available categories: ${categoryNames.join(", ")}`
56268
56654
  agentToUse: SISYPHUS_JUNIOR_AGENT2,
56269
56655
  categoryModel,
56270
56656
  categoryPromptAppend,
56657
+ maxPromptTokens: resolved.config.max_prompt_tokens,
56271
56658
  modelInfo,
56272
56659
  actualModel,
56273
56660
  isUnstableAgent,
@@ -56477,7 +56864,7 @@ function createDelegateTask(options) {
56477
56864
  throw new Error(`Invalid arguments: load_skills=null is not allowed. Pass [] if no skills needed.`);
56478
56865
  }
56479
56866
  const runInBackground = args.run_in_background === true;
56480
- const { content: skillContent, error: skillError } = await resolveSkillContent2(args.load_skills, {
56867
+ const { content: skillContent, contents: skillContents, error: skillError } = await resolveSkillContent2(args.load_skills, {
56481
56868
  gitMasterConfig: options.gitMasterConfig,
56482
56869
  browserProvider: options.browserProvider,
56483
56870
  disabledSkills: options.disabledSkills,
@@ -56511,6 +56898,7 @@ function createDelegateTask(options) {
56511
56898
  let actualModel;
56512
56899
  let isUnstableAgent = false;
56513
56900
  let fallbackChain;
56901
+ let maxPromptTokens;
56514
56902
  if (args.category) {
56515
56903
  const resolution = await resolveCategoryExecution(args, options, inheritedModel, systemDefaultModel);
56516
56904
  if (resolution.error) {
@@ -56523,6 +56911,7 @@ function createDelegateTask(options) {
56523
56911
  actualModel = resolution.actualModel;
56524
56912
  isUnstableAgent = resolution.isUnstableAgent;
56525
56913
  fallbackChain = resolution.fallbackChain;
56914
+ maxPromptTokens = resolution.maxPromptTokens;
56526
56915
  const isRunInBackgroundExplicitlyFalse = args.run_in_background === false || args.run_in_background === "false";
56527
56916
  log("[task] unstable agent detection", {
56528
56917
  category: args.category,
@@ -56536,8 +56925,11 @@ function createDelegateTask(options) {
56536
56925
  if (isUnstableAgent && isRunInBackgroundExplicitlyFalse) {
56537
56926
  const systemContent2 = buildSystemContent({
56538
56927
  skillContent,
56928
+ skillContents,
56539
56929
  categoryPromptAppend,
56540
56930
  agentName: agentToUse,
56931
+ maxPromptTokens,
56932
+ model: categoryModel,
56541
56933
  availableCategories,
56542
56934
  availableSkills
56543
56935
  });
@@ -56554,8 +56946,11 @@ function createDelegateTask(options) {
56554
56946
  }
56555
56947
  const systemContent = buildSystemContent({
56556
56948
  skillContent,
56949
+ skillContents,
56557
56950
  categoryPromptAppend,
56558
56951
  agentName: agentToUse,
56952
+ maxPromptTokens,
56953
+ model: categoryModel,
56559
56954
  availableCategories,
56560
56955
  availableSkills
56561
56956
  });
@@ -57356,14 +57751,19 @@ function normalizeEditPayload(payload) {
57356
57751
  return toNewLines(payload).join(`
57357
57752
  `);
57358
57753
  }
57754
+ function canonicalAnchor(anchor) {
57755
+ if (!anchor)
57756
+ return "";
57757
+ return normalizeLineRef(anchor);
57758
+ }
57359
57759
  function buildDedupeKey(edit) {
57360
57760
  switch (edit.op) {
57361
57761
  case "replace":
57362
- return `replace|${edit.pos}|${edit.end ?? ""}|${normalizeEditPayload(edit.lines)}`;
57762
+ return `replace|${canonicalAnchor(edit.pos)}|${edit.end ? canonicalAnchor(edit.end) : ""}|${normalizeEditPayload(edit.lines)}`;
57363
57763
  case "append":
57364
- return `append|${edit.pos ?? ""}|${normalizeEditPayload(edit.lines)}`;
57764
+ return `append|${canonicalAnchor(edit.pos)}|${normalizeEditPayload(edit.lines)}`;
57365
57765
  case "prepend":
57366
- return `prepend|${edit.pos ?? ""}|${normalizeEditPayload(edit.lines)}`;
57766
+ return `prepend|${canonicalAnchor(edit.pos)}|${normalizeEditPayload(edit.lines)}`;
57367
57767
  default:
57368
57768
  return JSON.stringify(edit);
57369
57769
  }
@@ -57748,66 +58148,448 @@ function applyHashlineEditsWithReport(content, edits) {
57748
58148
  deduplicatedEdits: dedupeResult.deduplicatedEdits
57749
58149
  };
57750
58150
  }
57751
- // src/tools/hashline-edit/diff-utils.ts
57752
- function generateUnifiedDiff(oldContent, newContent, filePath) {
57753
- const oldLines = oldContent.split(`
57754
- `);
57755
- const newLines = newContent.split(`
58151
+ // node_modules/diff/libesm/diff/base.js
58152
+ class Diff {
58153
+ diff(oldStr, newStr, options = {}) {
58154
+ let callback;
58155
+ if (typeof options === "function") {
58156
+ callback = options;
58157
+ options = {};
58158
+ } else if ("callback" in options) {
58159
+ callback = options.callback;
58160
+ }
58161
+ const oldString = this.castInput(oldStr, options);
58162
+ const newString = this.castInput(newStr, options);
58163
+ const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
58164
+ const newTokens = this.removeEmpty(this.tokenize(newString, options));
58165
+ return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
58166
+ }
58167
+ diffWithOptionsObj(oldTokens, newTokens, options, callback) {
58168
+ var _a;
58169
+ const done = (value) => {
58170
+ value = this.postProcess(value, options);
58171
+ if (callback) {
58172
+ setTimeout(function() {
58173
+ callback(value);
58174
+ }, 0);
58175
+ return;
58176
+ } else {
58177
+ return value;
58178
+ }
58179
+ };
58180
+ const newLen = newTokens.length, oldLen = oldTokens.length;
58181
+ let editLength = 1;
58182
+ let maxEditLength = newLen + oldLen;
58183
+ if (options.maxEditLength != null) {
58184
+ maxEditLength = Math.min(maxEditLength, options.maxEditLength);
58185
+ }
58186
+ const maxExecutionTime = (_a = options.timeout) !== null && _a !== undefined ? _a : Infinity;
58187
+ const abortAfterTimestamp = Date.now() + maxExecutionTime;
58188
+ const bestPath = [{ oldPos: -1, lastComponent: undefined }];
58189
+ let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
58190
+ if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
58191
+ return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
58192
+ }
58193
+ let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
58194
+ const execEditLength = () => {
58195
+ for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength);diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
58196
+ let basePath;
58197
+ const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
58198
+ if (removePath) {
58199
+ bestPath[diagonalPath - 1] = undefined;
58200
+ }
58201
+ let canAdd = false;
58202
+ if (addPath) {
58203
+ const addPathNewPos = addPath.oldPos - diagonalPath;
58204
+ canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
58205
+ }
58206
+ const canRemove = removePath && removePath.oldPos + 1 < oldLen;
58207
+ if (!canAdd && !canRemove) {
58208
+ bestPath[diagonalPath] = undefined;
58209
+ continue;
58210
+ }
58211
+ if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) {
58212
+ basePath = this.addToPath(addPath, true, false, 0, options);
58213
+ } else {
58214
+ basePath = this.addToPath(removePath, false, true, 1, options);
58215
+ }
58216
+ newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
58217
+ if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
58218
+ return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
58219
+ } else {
58220
+ bestPath[diagonalPath] = basePath;
58221
+ if (basePath.oldPos + 1 >= oldLen) {
58222
+ maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
58223
+ }
58224
+ if (newPos + 1 >= newLen) {
58225
+ minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
58226
+ }
58227
+ }
58228
+ }
58229
+ editLength++;
58230
+ };
58231
+ if (callback) {
58232
+ (function exec2() {
58233
+ setTimeout(function() {
58234
+ if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
58235
+ return callback(undefined);
58236
+ }
58237
+ if (!execEditLength()) {
58238
+ exec2();
58239
+ }
58240
+ }, 0);
58241
+ })();
58242
+ } else {
58243
+ while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
58244
+ const ret = execEditLength();
58245
+ if (ret) {
58246
+ return ret;
58247
+ }
58248
+ }
58249
+ }
58250
+ }
58251
+ addToPath(path11, added, removed, oldPosInc, options) {
58252
+ const last = path11.lastComponent;
58253
+ if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
58254
+ return {
58255
+ oldPos: path11.oldPos + oldPosInc,
58256
+ lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
58257
+ };
58258
+ } else {
58259
+ return {
58260
+ oldPos: path11.oldPos + oldPosInc,
58261
+ lastComponent: { count: 1, added, removed, previousComponent: last }
58262
+ };
58263
+ }
58264
+ }
58265
+ extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
58266
+ const newLen = newTokens.length, oldLen = oldTokens.length;
58267
+ let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
58268
+ while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
58269
+ newPos++;
58270
+ oldPos++;
58271
+ commonCount++;
58272
+ if (options.oneChangePerToken) {
58273
+ basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
58274
+ }
58275
+ }
58276
+ if (commonCount && !options.oneChangePerToken) {
58277
+ basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
58278
+ }
58279
+ basePath.oldPos = oldPos;
58280
+ return newPos;
58281
+ }
58282
+ equals(left, right, options) {
58283
+ if (options.comparator) {
58284
+ return options.comparator(left, right);
58285
+ } else {
58286
+ return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
58287
+ }
58288
+ }
58289
+ removeEmpty(array2) {
58290
+ const ret = [];
58291
+ for (let i2 = 0;i2 < array2.length; i2++) {
58292
+ if (array2[i2]) {
58293
+ ret.push(array2[i2]);
58294
+ }
58295
+ }
58296
+ return ret;
58297
+ }
58298
+ castInput(value, options) {
58299
+ return value;
58300
+ }
58301
+ tokenize(value, options) {
58302
+ return Array.from(value);
58303
+ }
58304
+ join(chars) {
58305
+ return chars.join("");
58306
+ }
58307
+ postProcess(changeObjects, options) {
58308
+ return changeObjects;
58309
+ }
58310
+ get useLongestToken() {
58311
+ return false;
58312
+ }
58313
+ buildValues(lastComponent, newTokens, oldTokens) {
58314
+ const components = [];
58315
+ let nextComponent;
58316
+ while (lastComponent) {
58317
+ components.push(lastComponent);
58318
+ nextComponent = lastComponent.previousComponent;
58319
+ delete lastComponent.previousComponent;
58320
+ lastComponent = nextComponent;
58321
+ }
58322
+ components.reverse();
58323
+ const componentLen = components.length;
58324
+ let componentPos = 0, newPos = 0, oldPos = 0;
58325
+ for (;componentPos < componentLen; componentPos++) {
58326
+ const component = components[componentPos];
58327
+ if (!component.removed) {
58328
+ if (!component.added && this.useLongestToken) {
58329
+ let value = newTokens.slice(newPos, newPos + component.count);
58330
+ value = value.map(function(value2, i2) {
58331
+ const oldValue = oldTokens[oldPos + i2];
58332
+ return oldValue.length > value2.length ? oldValue : value2;
58333
+ });
58334
+ component.value = this.join(value);
58335
+ } else {
58336
+ component.value = this.join(newTokens.slice(newPos, newPos + component.count));
58337
+ }
58338
+ newPos += component.count;
58339
+ if (!component.added) {
58340
+ oldPos += component.count;
58341
+ }
58342
+ } else {
58343
+ component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
58344
+ oldPos += component.count;
58345
+ }
58346
+ }
58347
+ return components;
58348
+ }
58349
+ }
58350
+
58351
+ // node_modules/diff/libesm/diff/line.js
58352
+ class LineDiff extends Diff {
58353
+ constructor() {
58354
+ super(...arguments);
58355
+ this.tokenize = tokenize;
58356
+ }
58357
+ equals(left, right, options) {
58358
+ if (options.ignoreWhitespace) {
58359
+ if (!options.newlineIsToken || !left.includes(`
58360
+ `)) {
58361
+ left = left.trim();
58362
+ }
58363
+ if (!options.newlineIsToken || !right.includes(`
58364
+ `)) {
58365
+ right = right.trim();
58366
+ }
58367
+ } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
58368
+ if (left.endsWith(`
58369
+ `)) {
58370
+ left = left.slice(0, -1);
58371
+ }
58372
+ if (right.endsWith(`
58373
+ `)) {
58374
+ right = right.slice(0, -1);
58375
+ }
58376
+ }
58377
+ return super.equals(left, right, options);
58378
+ }
58379
+ }
58380
+ var lineDiff = new LineDiff;
58381
+ function diffLines(oldStr, newStr, options) {
58382
+ return lineDiff.diff(oldStr, newStr, options);
58383
+ }
58384
+ function tokenize(value, options) {
58385
+ if (options.stripTrailingCr) {
58386
+ value = value.replace(/\r\n/g, `
57756
58387
  `);
57757
- const maxLines = Math.max(oldLines.length, newLines.length);
57758
- let diff = `--- ${filePath}
57759
- +++ ${filePath}
57760
- `;
57761
- let inHunk = false;
57762
- let oldStart = 1;
57763
- let newStart = 1;
57764
- let oldCount = 0;
57765
- let newCount = 0;
57766
- let hunkLines = [];
57767
- for (let i2 = 0;i2 < maxLines; i2++) {
57768
- const oldLine = oldLines[i2] ?? "";
57769
- const newLine = newLines[i2] ?? "";
57770
- if (oldLine !== newLine) {
57771
- if (!inHunk) {
57772
- oldStart = i2 + 1;
57773
- newStart = i2 + 1;
57774
- oldCount = 0;
57775
- newCount = 0;
57776
- hunkLines = [];
57777
- inHunk = true;
57778
- }
57779
- if (oldLines[i2] !== undefined) {
57780
- hunkLines.push(`-${oldLine}`);
57781
- oldCount++;
57782
- }
57783
- if (newLines[i2] !== undefined) {
57784
- hunkLines.push(`+${newLine}`);
57785
- newCount++;
57786
- }
57787
- } else if (inHunk) {
57788
- hunkLines.push(` ${oldLine}`);
57789
- oldCount++;
57790
- newCount++;
57791
- if (hunkLines.length > 6) {
57792
- diff += `@@ -${oldStart},${oldCount} +${newStart},${newCount} @@
57793
- `;
57794
- diff += hunkLines.join(`
57795
- `) + `
57796
- `;
57797
- inHunk = false;
58388
+ }
58389
+ const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
58390
+ if (!linesAndNewlines[linesAndNewlines.length - 1]) {
58391
+ linesAndNewlines.pop();
58392
+ }
58393
+ for (let i2 = 0;i2 < linesAndNewlines.length; i2++) {
58394
+ const line = linesAndNewlines[i2];
58395
+ if (i2 % 2 && !options.newlineIsToken) {
58396
+ retLines[retLines.length - 1] += line;
58397
+ } else {
58398
+ retLines.push(line);
58399
+ }
58400
+ }
58401
+ return retLines;
58402
+ }
58403
+
58404
+ // node_modules/diff/libesm/patch/create.js
58405
+ var INCLUDE_HEADERS = {
58406
+ includeIndex: true,
58407
+ includeUnderline: true,
58408
+ includeFileHeaders: true
58409
+ };
58410
+ function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
58411
+ let optionsObj;
58412
+ if (!options) {
58413
+ optionsObj = {};
58414
+ } else if (typeof options === "function") {
58415
+ optionsObj = { callback: options };
58416
+ } else {
58417
+ optionsObj = options;
58418
+ }
58419
+ if (typeof optionsObj.context === "undefined") {
58420
+ optionsObj.context = 4;
58421
+ }
58422
+ const context = optionsObj.context;
58423
+ if (optionsObj.newlineIsToken) {
58424
+ throw new Error("newlineIsToken may not be used with patch-generation functions, only with diffing functions");
58425
+ }
58426
+ if (!optionsObj.callback) {
58427
+ return diffLinesResultToPatch(diffLines(oldStr, newStr, optionsObj));
58428
+ } else {
58429
+ const { callback } = optionsObj;
58430
+ diffLines(oldStr, newStr, Object.assign(Object.assign({}, optionsObj), { callback: (diff) => {
58431
+ const patch = diffLinesResultToPatch(diff);
58432
+ callback(patch);
58433
+ } }));
58434
+ }
58435
+ function diffLinesResultToPatch(diff) {
58436
+ if (!diff) {
58437
+ return;
58438
+ }
58439
+ diff.push({ value: "", lines: [] });
58440
+ function contextLines(lines) {
58441
+ return lines.map(function(entry) {
58442
+ return " " + entry;
58443
+ });
58444
+ }
58445
+ const hunks = [];
58446
+ let oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1;
58447
+ for (let i2 = 0;i2 < diff.length; i2++) {
58448
+ const current = diff[i2], lines = current.lines || splitLines(current.value);
58449
+ current.lines = lines;
58450
+ if (current.added || current.removed) {
58451
+ if (!oldRangeStart) {
58452
+ const prev = diff[i2 - 1];
58453
+ oldRangeStart = oldLine;
58454
+ newRangeStart = newLine;
58455
+ if (prev) {
58456
+ curRange = context > 0 ? contextLines(prev.lines.slice(-context)) : [];
58457
+ oldRangeStart -= curRange.length;
58458
+ newRangeStart -= curRange.length;
58459
+ }
58460
+ }
58461
+ for (const line of lines) {
58462
+ curRange.push((current.added ? "+" : "-") + line);
58463
+ }
58464
+ if (current.added) {
58465
+ newLine += lines.length;
58466
+ } else {
58467
+ oldLine += lines.length;
58468
+ }
58469
+ } else {
58470
+ if (oldRangeStart) {
58471
+ if (lines.length <= context * 2 && i2 < diff.length - 2) {
58472
+ for (const line of contextLines(lines)) {
58473
+ curRange.push(line);
58474
+ }
58475
+ } else {
58476
+ const contextSize = Math.min(lines.length, context);
58477
+ for (const line of contextLines(lines.slice(0, contextSize))) {
58478
+ curRange.push(line);
58479
+ }
58480
+ const hunk = {
58481
+ oldStart: oldRangeStart,
58482
+ oldLines: oldLine - oldRangeStart + contextSize,
58483
+ newStart: newRangeStart,
58484
+ newLines: newLine - newRangeStart + contextSize,
58485
+ lines: curRange
58486
+ };
58487
+ hunks.push(hunk);
58488
+ oldRangeStart = 0;
58489
+ newRangeStart = 0;
58490
+ curRange = [];
58491
+ }
58492
+ }
58493
+ oldLine += lines.length;
58494
+ newLine += lines.length;
58495
+ }
58496
+ }
58497
+ for (const hunk of hunks) {
58498
+ for (let i2 = 0;i2 < hunk.lines.length; i2++) {
58499
+ if (hunk.lines[i2].endsWith(`
58500
+ `)) {
58501
+ hunk.lines[i2] = hunk.lines[i2].slice(0, -1);
58502
+ } else {
58503
+ hunk.lines.splice(i2 + 1, 0, "\");
58504
+ i2++;
58505
+ }
57798
58506
  }
57799
58507
  }
58508
+ return {
58509
+ oldFileName,
58510
+ newFileName,
58511
+ oldHeader,
58512
+ newHeader,
58513
+ hunks
58514
+ };
57800
58515
  }
57801
- if (inHunk && hunkLines.length > 0) {
57802
- diff += `@@ -${oldStart},${oldCount} +${newStart},${newCount} @@
57803
- `;
57804
- diff += hunkLines.join(`
58516
+ }
58517
+ function formatPatch(patch, headerOptions) {
58518
+ if (!headerOptions) {
58519
+ headerOptions = INCLUDE_HEADERS;
58520
+ }
58521
+ if (Array.isArray(patch)) {
58522
+ if (patch.length > 1 && !headerOptions.includeFileHeaders) {
58523
+ throw new Error("Cannot omit file headers on a multi-file patch. " + "(The result would be unparseable; how would a tool trying to apply " + "the patch know which changes are to which file?)");
58524
+ }
58525
+ return patch.map((p) => formatPatch(p, headerOptions)).join(`
58526
+ `);
58527
+ }
58528
+ const ret = [];
58529
+ if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName) {
58530
+ ret.push("Index: " + patch.oldFileName);
58531
+ }
58532
+ if (headerOptions.includeUnderline) {
58533
+ ret.push("===================================================================");
58534
+ }
58535
+ if (headerOptions.includeFileHeaders) {
58536
+ ret.push("--- " + patch.oldFileName + (typeof patch.oldHeader === "undefined" ? "" : "\t" + patch.oldHeader));
58537
+ ret.push("+++ " + patch.newFileName + (typeof patch.newHeader === "undefined" ? "" : "\t" + patch.newHeader));
58538
+ }
58539
+ for (let i2 = 0;i2 < patch.hunks.length; i2++) {
58540
+ const hunk = patch.hunks[i2];
58541
+ if (hunk.oldLines === 0) {
58542
+ hunk.oldStart -= 1;
58543
+ }
58544
+ if (hunk.newLines === 0) {
58545
+ hunk.newStart -= 1;
58546
+ }
58547
+ ret.push("@@ -" + hunk.oldStart + "," + hunk.oldLines + " +" + hunk.newStart + "," + hunk.newLines + " @@");
58548
+ for (const line of hunk.lines) {
58549
+ ret.push(line);
58550
+ }
58551
+ }
58552
+ return ret.join(`
57805
58553
  `) + `
57806
58554
  `;
58555
+ }
58556
+ function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
58557
+ if (typeof options === "function") {
58558
+ options = { callback: options };
57807
58559
  }
57808
- return diff || `--- ${filePath}
57809
- +++ ${filePath}
57810
- `;
58560
+ if (!(options === null || options === undefined ? undefined : options.callback)) {
58561
+ const patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options);
58562
+ if (!patchObj) {
58563
+ return;
58564
+ }
58565
+ return formatPatch(patchObj, options === null || options === undefined ? undefined : options.headerOptions);
58566
+ } else {
58567
+ const { callback } = options;
58568
+ structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, Object.assign(Object.assign({}, options), { callback: (patchObj) => {
58569
+ if (!patchObj) {
58570
+ callback(undefined);
58571
+ } else {
58572
+ callback(formatPatch(patchObj, options.headerOptions));
58573
+ }
58574
+ } }));
58575
+ }
58576
+ }
58577
+ function splitLines(text) {
58578
+ const hasTrailingNl = text.endsWith(`
58579
+ `);
58580
+ const result = text.split(`
58581
+ `).map((line) => line + `
58582
+ `);
58583
+ if (hasTrailingNl) {
58584
+ result.pop();
58585
+ } else {
58586
+ result.push(result.pop().slice(0, -1));
58587
+ }
58588
+ return result;
58589
+ }
58590
+ // src/tools/hashline-edit/diff-utils.ts
58591
+ function generateUnifiedDiff(oldContent, newContent, filePath) {
58592
+ return createTwoFilesPatch(filePath, filePath, oldContent, newContent, undefined, undefined, { context: 3 });
57811
58593
  }
57812
58594
  function countLineDiffs(oldContent, newContent) {
57813
58595
  const oldLines = oldContent.split(`
@@ -57979,7 +58761,7 @@ function resolveToolCallID2(ctx) {
57979
58761
  function canCreateFromMissingFile(edits) {
57980
58762
  if (edits.length === 0)
57981
58763
  return false;
57982
- return edits.every((edit) => edit.op === "append" || edit.op === "prepend");
58764
+ return edits.every((edit) => (edit.op === "append" || edit.op === "prepend") && !edit.pos);
57983
58765
  }
57984
58766
  function buildSuccessMeta(effectivePath, beforeContent, afterContent, noopEdits, deduplicatedEdits) {
57985
58767
  const unifiedDiff = generateUnifiedDiff(beforeContent, afterContent, effectivePath);
@@ -58023,16 +58805,16 @@ async function executeHashlineEditTool(args, context) {
58023
58805
  const metadataContext = context;
58024
58806
  const filePath = args.filePath;
58025
58807
  const { delete: deleteMode, rename } = args;
58026
- if (!deleteMode && (!args.edits || !Array.isArray(args.edits) || args.edits.length === 0)) {
58027
- return "Error: edits parameter must be a non-empty array";
58028
- }
58029
- const edits = deleteMode ? [] : normalizeHashlineEdits(args.edits);
58030
58808
  if (deleteMode && rename) {
58031
58809
  return "Error: delete and rename cannot be used together";
58032
58810
  }
58033
- if (deleteMode && edits.length > 0) {
58811
+ if (deleteMode && args.edits.length > 0) {
58034
58812
  return "Error: delete mode requires edits to be an empty array";
58035
58813
  }
58814
+ if (!deleteMode && (!args.edits || !Array.isArray(args.edits) || args.edits.length === 0)) {
58815
+ return "Error: edits parameter must be a non-empty array";
58816
+ }
58817
+ const edits = deleteMode ? [] : normalizeHashlineEdits(args.edits);
58036
58818
  const file2 = Bun.file(filePath);
58037
58819
  const exists = await file2.exists();
58038
58820
  if (!exists && !deleteMode && !canCreateFromMissingFile(edits)) {
@@ -58097,7 +58879,7 @@ WORKFLOW:
58097
58879
  VALIDATION:
58098
58880
  Payload shape: { "filePath": string, "edits": [...], "delete"?: boolean, "rename"?: string }
58099
58881
  Each edit must be one of: replace, append, prepend
58100
- Edit shape: { "op": "replace"|"append"|"prepend", "pos"?: "LINE#ID", "end"?: "LINE#ID", "lines"?: string|string[]|null }
58882
+ Edit shape: { "op": "replace"|"append"|"prepend", "pos"?: "LINE#ID", "end"?: "LINE#ID", "lines": string|string[]|null }
58101
58883
  lines must contain plain replacement text only (no LINE#ID prefixes, no diff + markers)
58102
58884
  CRITICAL: all operations validate against the same pre-edit file snapshot and apply bottom-up. Refs/tags are interpreted against the last-read version of the file.
58103
58885
 
@@ -58171,7 +58953,7 @@ function createHashlineEditTool() {
58171
58953
  ]).describe("Hashline edit operation mode"),
58172
58954
  pos: tool.schema.string().optional().describe("Primary anchor in LINE#ID format"),
58173
58955
  end: tool.schema.string().optional().describe("Range end anchor in LINE#ID format"),
58174
- lines: tool.schema.union([tool.schema.string(), tool.schema.array(tool.schema.string()), tool.schema.null()]).optional().describe("Replacement or inserted lines. null/[] deletes with replace")
58956
+ lines: tool.schema.union([tool.schema.string(), tool.schema.array(tool.schema.string()), tool.schema.null()]).describe("Replacement or inserted lines. null/[] deletes with replace")
58175
58957
  })).describe("Array of edit operations to apply (empty when delete=true)")
58176
58958
  },
58177
58959
  execute: async (args, context) => executeHashlineEditTool(args, context)
@@ -58281,7 +59063,9 @@ function createSessionHooks(args) {
58281
59063
  const prometheusMdOnly = isHookEnabled("prometheus-md-only") ? safeHook("prometheus-md-only", () => createPrometheusMdOnlyHook(ctx)) : null;
58282
59064
  const sisyphusJuniorNotepad = isHookEnabled("sisyphus-junior-notepad") ? safeHook("sisyphus-junior-notepad", () => createSisyphusJuniorNotepadHook(ctx)) : null;
58283
59065
  const noSisyphusGpt = isHookEnabled("no-sisyphus-gpt") ? safeHook("no-sisyphus-gpt", () => createNoSisyphusGptHook(ctx)) : null;
58284
- const noHephaestusNonGpt = isHookEnabled("no-hephaestus-non-gpt") ? safeHook("no-hephaestus-non-gpt", () => createNoHephaestusNonGptHook(ctx)) : null;
59066
+ const noHephaestusNonGpt = isHookEnabled("no-hephaestus-non-gpt") ? safeHook("no-hephaestus-non-gpt", () => createNoHephaestusNonGptHook(ctx, {
59067
+ allowNonGptModel: pluginConfig.agents?.hephaestus?.allow_non_gpt_model
59068
+ })) : null;
58285
59069
  const questionLabelTruncator = isHookEnabled("question-label-truncator") ? safeHook("question-label-truncator", () => createQuestionLabelTruncatorHook()) : null;
58286
59070
  const taskResumeInfo = isHookEnabled("task-resume-info") ? safeHook("task-resume-info", () => createTaskResumeInfoHook()) : null;
58287
59071
  const anthropicEffort = isHookEnabled("anthropic-effort") ? safeHook("anthropic-effort", () => createAnthropicEffortHook()) : null;
@@ -58591,7 +59375,9 @@ function createContinuationHooks(args) {
58591
59375
  sessionRecovery
58592
59376
  } = args;
58593
59377
  const safeHook = (hookName, factory) => safeCreateHook(hookName, factory, { enabled: safeHookEnabled });
58594
- const stopContinuationGuard = isHookEnabled("stop-continuation-guard") ? safeHook("stop-continuation-guard", () => createStopContinuationGuardHook(ctx)) : null;
59378
+ const stopContinuationGuard = isHookEnabled("stop-continuation-guard") ? safeHook("stop-continuation-guard", () => createStopContinuationGuardHook(ctx, {
59379
+ backgroundManager
59380
+ })) : null;
58595
59381
  const compactionContextInjector = isHookEnabled("compaction-context-injector") ? safeHook("compaction-context-injector", () => createCompactionContextInjector(backgroundManager)) : null;
58596
59382
  const compactionTodoPreserver = isHookEnabled("compaction-todo-preserver") ? safeHook("compaction-todo-preserver", () => createCompactionTodoPreserverHook(ctx)) : null;
58597
59383
  const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? safeHook("todo-continuation-enforcer", () => createTodoContinuationEnforcer(ctx, {
@@ -59307,6 +60093,7 @@ async function checkAndInterruptStaleTasks(args) {
59307
60093
  class BackgroundManager {
59308
60094
  tasks;
59309
60095
  notifications;
60096
+ pendingNotifications;
59310
60097
  pendingByParent;
59311
60098
  client;
59312
60099
  directory;
@@ -59328,6 +60115,7 @@ class BackgroundManager {
59328
60115
  constructor(ctx, config3, options) {
59329
60116
  this.tasks = new Map;
59330
60117
  this.notifications = new Map;
60118
+ this.pendingNotifications = new Map;
59331
60119
  this.pendingByParent = new Map;
59332
60120
  this.client = ctx.client;
59333
60121
  this.directory = ctx.directory;
@@ -59893,6 +60681,7 @@ class BackgroundManager {
59893
60681
  for (const descendant of this.getAllDescendantTasks(sessionID)) {
59894
60682
  tasksToCancel.set(descendant.id, descendant);
59895
60683
  }
60684
+ this.pendingNotifications.delete(sessionID);
59896
60685
  if (tasksToCancel.size === 0)
59897
60686
  return;
59898
60687
  for (const task of tasksToCancel.values()) {
@@ -59926,6 +60715,11 @@ class BackgroundManager {
59926
60715
  subagentSessions.delete(task.sessionID);
59927
60716
  }
59928
60717
  }
60718
+ for (const task of tasksToCancel.values()) {
60719
+ if (task.parentSessionID) {
60720
+ this.pendingNotifications.delete(task.parentSessionID);
60721
+ }
60722
+ }
59929
60723
  SessionCategoryRegistry.remove(sessionID);
59930
60724
  }
59931
60725
  if (event.type === "session.status") {
@@ -59969,6 +60763,34 @@ class BackgroundManager {
59969
60763
  clearNotifications(sessionID) {
59970
60764
  this.notifications.delete(sessionID);
59971
60765
  }
60766
+ queuePendingNotification(sessionID, notification2) {
60767
+ if (!sessionID)
60768
+ return;
60769
+ const existingNotifications = this.pendingNotifications.get(sessionID) ?? [];
60770
+ existingNotifications.push(notification2);
60771
+ this.pendingNotifications.set(sessionID, existingNotifications);
60772
+ }
60773
+ injectPendingNotificationsIntoChatMessage(output, sessionID) {
60774
+ const pendingNotifications = this.pendingNotifications.get(sessionID);
60775
+ if (!pendingNotifications || pendingNotifications.length === 0) {
60776
+ return;
60777
+ }
60778
+ this.pendingNotifications.delete(sessionID);
60779
+ const notificationContent = pendingNotifications.join(`
60780
+
60781
+ `);
60782
+ const firstTextPartIndex = output.parts.findIndex((part) => part.type === "text");
60783
+ if (firstTextPartIndex === -1) {
60784
+ output.parts.unshift(createInternalAgentTextPart(notificationContent));
60785
+ return;
60786
+ }
60787
+ const originalText = output.parts[firstTextPartIndex].text ?? "";
60788
+ output.parts[firstTextPartIndex].text = `${notificationContent}
60789
+
60790
+ ---
60791
+
60792
+ ${originalText}`;
60793
+ }
59972
60794
  async validateSessionHasOutput(sessionID) {
59973
60795
  try {
59974
60796
  const response = await this.client.session.messages({
@@ -60264,6 +61086,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
60264
61086
  taskId: task.id,
60265
61087
  parentSessionID: task.parentSessionID
60266
61088
  });
61089
+ this.queuePendingNotification(task.parentSessionID, notification2);
60267
61090
  } else {
60268
61091
  log("[background-agent] Failed to send notification:", error45);
60269
61092
  }
@@ -60452,6 +61275,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
60452
61275
  this.concurrencyManager.clear();
60453
61276
  this.tasks.clear();
60454
61277
  this.notifications.clear();
61278
+ this.pendingNotifications.clear();
60455
61279
  this.pendingByParent.clear();
60456
61280
  this.notificationQueueByParent.clear();
60457
61281
  this.queuesByKey.clear();
@@ -63834,7 +64658,7 @@ function createParser(callbacks) {
63834
64658
  const { onEvent = noop, onError = noop, onRetry = noop, onComment } = callbacks;
63835
64659
  let incompleteLine = "", isFirstChunk = true, id, data = "", eventType = "";
63836
64660
  function feed(newChunk) {
63837
- const chunk = isFirstChunk ? newChunk.replace(/^\xEF\xBB\xBF/, "") : newChunk, [complete, incomplete] = splitLines(`${incompleteLine}${chunk}`);
64661
+ const chunk = isFirstChunk ? newChunk.replace(/^\xEF\xBB\xBF/, "") : newChunk, [complete, incomplete] = splitLines2(`${incompleteLine}${chunk}`);
63838
64662
  for (const line of complete)
63839
64663
  parseLine(line);
63840
64664
  incompleteLine = incomplete, isFirstChunk = false;
@@ -63893,7 +64717,7 @@ function createParser(callbacks) {
63893
64717
  }
63894
64718
  return { feed, reset };
63895
64719
  }
63896
- function splitLines(chunk) {
64720
+ function splitLines2(chunk) {
63897
64721
  const lines = [];
63898
64722
  let incompleteLine = "", searchIndex = 0;
63899
64723
  for (;searchIndex < chunk.length; ) {
@@ -66834,7 +67658,7 @@ For implementation tasks, actively decompose and delegate to \`deep\` category a
66834
67658
  }
66835
67659
 
66836
67660
  // src/agents/sisyphus.ts
66837
- var MODE = "primary";
67661
+ var MODE = "all";
66838
67662
  function buildTaskManagementSection(useTaskSystem) {
66839
67663
  if (useTaskSystem) {
66840
67664
  return `<Task_Management>
@@ -69544,7 +70368,7 @@ ${agentRows.join(`
69544
70368
  }
69545
70369
 
69546
70370
  // src/agents/atlas/agent.ts
69547
- var MODE7 = "primary";
70371
+ var MODE7 = "all";
69548
70372
  function getAtlasPromptSource(model) {
69549
70373
  if (model && isGptModel(model)) {
69550
70374
  return "gpt";
@@ -69585,18 +70409,13 @@ function buildDynamicOrchestratorPrompt(ctx) {
69585
70409
  return basePrompt.replace("{CATEGORY_SECTION}", categorySection).replace("{AGENT_SECTION}", agentSection).replace("{DECISION_MATRIX}", decisionMatrix).replace("{SKILLS_SECTION}", skillsSection).replace("{{CATEGORY_SKILLS_DELEGATION_GUIDE}}", categorySkillsGuide);
69586
70410
  }
69587
70411
  function createAtlasAgent(ctx) {
69588
- const restrictions = createAgentToolRestrictions([
69589
- "task",
69590
- "call_omo_agent"
69591
- ]);
69592
70412
  const baseConfig = {
69593
70413
  description: "Orchestrates work via task() to complete ALL tasks in a todo list until fully done. (Atlas - OhMyOpenCode)",
69594
70414
  mode: MODE7,
69595
70415
  ...ctx.model ? { model: ctx.model } : {},
69596
70416
  temperature: 0.1,
69597
70417
  prompt: buildDynamicOrchestratorPrompt(ctx),
69598
- color: "#10B981",
69599
- ...restrictions
70418
+ color: "#10B981"
69600
70419
  };
69601
70420
  return baseConfig;
69602
70421
  }
@@ -69847,7 +70666,7 @@ var momusPromptMetadata = {
69847
70666
  };
69848
70667
 
69849
70668
  // src/agents/hephaestus.ts
69850
- var MODE9 = "primary";
70669
+ var MODE9 = "all";
69851
70670
  function buildTodoDisciplineSection(useTaskSystem) {
69852
70671
  if (useTaskSystem) {
69853
70672
  return `## Task Discipline (NON-NEGOTIABLE)
@@ -70452,25 +71271,10 @@ function applyOverrides(config3, override, mergedCategories, directory) {
70452
71271
 
70453
71272
  // src/agents/env-context.ts
70454
71273
  function createEnvContext() {
70455
- const now = new Date;
70456
71274
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
70457
71275
  const locale = Intl.DateTimeFormat().resolvedOptions().locale;
70458
- const dateStr = now.toLocaleDateString(locale, {
70459
- weekday: "short",
70460
- year: "numeric",
70461
- month: "short",
70462
- day: "numeric"
70463
- });
70464
- const timeStr = now.toLocaleTimeString(locale, {
70465
- hour: "2-digit",
70466
- minute: "2-digit",
70467
- second: "2-digit",
70468
- hour12: true
70469
- });
70470
71276
  return `
70471
71277
  <omo-env>
70472
- Current date: ${dateStr}
70473
- Current time: ${timeStr}
70474
71278
  Timezone: ${timezone}
70475
71279
  Locale: ${locale}
70476
71280
  </omo-env>`;
@@ -71412,6 +72216,7 @@ function remapAgentKeysToDisplayNames(agents) {
71412
72216
  const displayName = AGENT_DISPLAY_NAMES[key];
71413
72217
  if (displayName && displayName !== key) {
71414
72218
  result[displayName] = value;
72219
+ result[key] = value;
71415
72220
  } else {
71416
72221
  result[key] = value;
71417
72222
  }
@@ -73701,6 +74506,8 @@ async function applyAgentConfig(params) {
73701
74506
  key,
73702
74507
  value ? migrateAgentConfig(value) : value
73703
74508
  ]));
74509
+ const disabledAgentNames = new Set((migratedDisabledAgents ?? []).map((a) => a.toLowerCase()));
74510
+ const filterDisabledAgents = (agents) => Object.fromEntries(Object.entries(agents).filter(([name]) => !disabledAgentNames.has(name.toLowerCase())));
73704
74511
  const isSisyphusEnabled = params.pluginConfig.sisyphus_agent?.disabled !== true;
73705
74512
  const builderEnabled = params.pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
73706
74513
  const plannerEnabled = params.pluginConfig.sisyphus_agent?.planner_enabled ?? true;
@@ -73754,9 +74561,9 @@ async function applyAgentConfig(params) {
73754
74561
  params.config.agent = {
73755
74562
  ...agentConfig,
73756
74563
  ...Object.fromEntries(Object.entries(builtinAgents).filter(([key]) => key !== "sisyphus")),
73757
- ...userAgents,
73758
- ...projectAgents,
73759
- ...pluginAgents,
74564
+ ...filterDisabledAgents(userAgents),
74565
+ ...filterDisabledAgents(projectAgents),
74566
+ ...filterDisabledAgents(pluginAgents),
73760
74567
  ...filteredConfigAgents,
73761
74568
  build: { ...migratedBuild, mode: "subagent", hidden: true },
73762
74569
  ...planDemoteConfig ? { plan: planDemoteConfig } : {}
@@ -73764,9 +74571,9 @@ async function applyAgentConfig(params) {
73764
74571
  } else {
73765
74572
  params.config.agent = {
73766
74573
  ...builtinAgents,
73767
- ...userAgents,
73768
- ...projectAgents,
73769
- ...pluginAgents,
74574
+ ...filterDisabledAgents(userAgents),
74575
+ ...filterDisabledAgents(projectAgents),
74576
+ ...filterDisabledAgents(pluginAgents),
73770
74577
  ...configAgent
73771
74578
  };
73772
74579
  }
@@ -74719,6 +75526,7 @@ function applyToolConfig(params) {
74719
75526
  function createConfigHandler(deps) {
74720
75527
  const { ctx, pluginConfig, modelCacheState } = deps;
74721
75528
  return async (config3) => {
75529
+ const formatterConfig = config3.formatter;
74722
75530
  applyProviderConfig({ config: config3, modelCacheState });
74723
75531
  const pluginComponents = await loadPluginComponents({ pluginConfig });
74724
75532
  const agentResult = await applyAgentConfig({
@@ -74730,6 +75538,7 @@ function createConfigHandler(deps) {
74730
75538
  applyToolConfig({ config: config3, pluginConfig, agentResult });
74731
75539
  await applyMcpConfig({ config: config3, pluginConfig, pluginComponents });
74732
75540
  await applyCommandConfig({ config: config3, pluginConfig, ctx, pluginComponents });
75541
+ config3.formatter = formatterConfig;
74733
75542
  log("[config-handler] config handler applied", {
74734
75543
  agentCount: Object.keys(agentResult).length,
74735
75544
  commandCount: Object.keys(config3.command ?? {}).length
@@ -75313,11 +76122,11 @@ function applyUltraworkModelOverrideOnMessage(pluginConfig, inputAgentName, outp
75313
76122
  const override = resolveUltraworkOverride(pluginConfig, inputAgentName, output, sessionID);
75314
76123
  if (!override)
75315
76124
  return;
76125
+ if (override.variant) {
76126
+ output.message["variant"] = override.variant;
76127
+ output.message["thinking"] = override.variant;
76128
+ }
75316
76129
  if (!override.providerID || !override.modelID) {
75317
- if (override.variant) {
75318
- output.message["variant"] = override.variant;
75319
- output.message["thinking"] = override.variant;
75320
- }
75321
76130
  return;
75322
76131
  }
75323
76132
  const targetModel = { providerID: override.providerID, modelID: override.modelID };
@@ -75329,10 +76138,6 @@ function applyUltraworkModelOverrideOnMessage(pluginConfig, inputAgentName, outp
75329
76138
  if (!messageId) {
75330
76139
  log("[ultrawork-model-override] No message ID found, falling back to direct mutation");
75331
76140
  output.message.model = targetModel;
75332
- if (override.variant) {
75333
- output.message["variant"] = override.variant;
75334
- output.message["thinking"] = override.variant;
75335
- }
75336
76141
  return;
75337
76142
  }
75338
76143
  const fromModel = output.message.model?.modelID ?? "unknown";
@@ -75404,8 +76209,10 @@ function createChatMessageHandler3(args) {
75404
76209
  setSessionModel(input.sessionID, input.model);
75405
76210
  }
75406
76211
  await hooks2.stopContinuationGuard?.["chat.message"]?.(input);
76212
+ await hooks2.backgroundNotificationHook?.["chat.message"]?.(input, output);
75407
76213
  await hooks2.runtimeFallback?.["chat.message"]?.(input, output);
75408
76214
  await hooks2.keywordDetector?.["chat.message"]?.(input, output);
76215
+ await hooks2.thinkMode?.["chat.message"]?.(input, output);
75409
76216
  await hooks2.claudeCodeHooks?.["chat.message"]?.(input, output);
75410
76217
  await hooks2.autoSlashCommand?.["chat.message"]?.(input, output);
75411
76218
  await hooks2.noSisyphusGpt?.["chat.message"]?.(input, output);