oh-my-opencode-slim 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18302,7 +18302,7 @@ var ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES];
18302
18302
  var PROTECTED_AGENTS = new Set(["orchestrator", "councillor"]);
18303
18303
  var DEFAULT_MODELS = {
18304
18304
  orchestrator: undefined,
18305
- oracle: "openai/gpt-5.4",
18305
+ oracle: "openai/gpt-5.5",
18306
18306
  librarian: "openai/gpt-5.4-mini",
18307
18307
  explorer: "openai/gpt-5.4-mini",
18308
18308
  designer: "openai/gpt-5.4-mini",
@@ -18316,7 +18316,7 @@ var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
18316
18316
  var MAX_POLL_TIME_MS = 5 * 60 * 1000;
18317
18317
  var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
18318
18318
  var PHASE_REMINDER_TEXT = `!IMPORTANT! Recall the workflow rules:
18319
- Understand → choose the best parallelized path based on your agents delegation rules → don't prefer solo → execute → verify.
18319
+ Understand → choose the best parallelized path based on your capabilities and agents delegation rules → execute → verify.
18320
18320
  If delegating, launch the specialist in the same turn you mention it !END!`;
18321
18321
  var TMUX_SPAWN_DELAY_MS = 500;
18322
18322
  var COUNCILLOR_STAGGER_MS = 250;
@@ -18525,6 +18525,9 @@ var InterviewConfigSchema = z2.object({
18525
18525
  port: z2.number().int().min(0).max(65535).default(0),
18526
18526
  dashboard: z2.boolean().default(false)
18527
18527
  });
18528
+ var SessionManagerConfigSchema = z2.object({
18529
+ maxSessionsPerAgent: z2.number().int().min(1).max(10).default(2)
18530
+ });
18528
18531
  var TodoContinuationConfigSchema = z2.object({
18529
18532
  maxContinuations: z2.number().int().min(1).max(50).default(5).describe("Maximum consecutive auto-continuations before stopping to ask user"),
18530
18533
  cooldownMs: z2.number().int().min(0).max(30000).default(3000).describe("Delay in ms before auto-continuing (gives user time to abort)"),
@@ -18566,6 +18569,7 @@ var PluginConfigSchema = z2.object({
18566
18569
  scoringEngineVersion: z2.enum(["v1", "v2-shadow", "v2"]).optional(),
18567
18570
  balanceProviderUsage: z2.boolean().optional(),
18568
18571
  showStartupToast: z2.boolean().optional().describe("Show the startup activation toast when OpenCode starts. Defaults to true."),
18572
+ autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
18569
18573
  manualPlan: ManualPlanSchema.optional(),
18570
18574
  presets: z2.record(z2.string(), PresetSchema).optional(),
18571
18575
  agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
@@ -18575,6 +18579,7 @@ var PluginConfigSchema = z2.object({
18575
18579
  tmux: TmuxConfigSchema.optional(),
18576
18580
  websearch: WebsearchConfigSchema.optional(),
18577
18581
  interview: InterviewConfigSchema.optional(),
18582
+ sessionManager: SessionManagerConfigSchema.optional(),
18578
18583
  todoContinuation: TodoContinuationConfigSchema.optional(),
18579
18584
  fallback: FailoverConfigSchema.optional(),
18580
18585
  council: CouncilConfigSchema.optional()
@@ -18660,6 +18665,7 @@ function loadPluginConfig(directory) {
18660
18665
  tmux: deepMerge(config.tmux, projectConfig.tmux),
18661
18666
  multiplexer: deepMerge(config.multiplexer, projectConfig.multiplexer),
18662
18667
  interview: deepMerge(config.interview, projectConfig.interview),
18668
+ sessionManager: deepMerge(config.sessionManager, projectConfig.sessionManager),
18663
18669
  fallback: deepMerge(config.fallback, projectConfig.fallback),
18664
18670
  council: deepMerge(config.council, projectConfig.council)
18665
18671
  };
@@ -18931,6 +18937,12 @@ ${enabledParallelExamples}
18931
18937
 
18932
18938
  Balance: respect dependencies, avoid parallelizing what must be sequential.
18933
18939
 
18940
+ ### OpenCode subagent execution model
18941
+ - A delegated specialist runs in a separate child session.
18942
+ - Delegation is blocking for the parent at that point: send work out, then continue that line after results return.
18943
+ - Parallel delegation means launching multiple independent child-session branches.
18944
+ - Only parallelize branches that are truly independent; reconcile dependent steps after delegated results come back.
18945
+
18934
18946
  ## 5. Execute
18935
18947
  1. Break complex tasks into todos
18936
18948
  2. Fire parallel research/implementation
@@ -18938,6 +18950,12 @@ Balance: respect dependencies, avoid parallelizing what must be sequential.
18938
18950
  4. Integrate results
18939
18951
  5. Adjust if needed
18940
18952
 
18953
+ ### Session Reuse
18954
+ - Reuse an available specialist session only for clear follow-up work on the same thread.
18955
+ - Prefer a fresh session for unrelated work, even with the same specialist.
18956
+ - If multiple remembered sessions fit, prefer the most recently used matching session.
18957
+ - If reuse is unclear, start a fresh session.
18958
+
18941
18959
  ### Auto-Continue
18942
18960
  When working through multi-step tasks, consider enabling auto-continue to avoid stopping between batches:
18943
18961
  - **Enable when:** User requests autonomous/batch work, or you create 4+ todos in a session
@@ -22057,6 +22075,185 @@ function hasInternalInitiatorMarker(part) {
22057
22075
  }
22058
22076
  return part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER);
22059
22077
  }
22078
+ // src/utils/session-manager.ts
22079
+ function aliasPrefix(agentType) {
22080
+ switch (agentType) {
22081
+ case "explorer":
22082
+ return "exp";
22083
+ case "librarian":
22084
+ return "lib";
22085
+ case "oracle":
22086
+ return "ora";
22087
+ case "designer":
22088
+ return "des";
22089
+ case "fixer":
22090
+ return "fix";
22091
+ case "observer":
22092
+ return "obs";
22093
+ case "council":
22094
+ return "cnc";
22095
+ case "councillor":
22096
+ return "clr";
22097
+ case "orchestrator":
22098
+ return "orc";
22099
+ }
22100
+ }
22101
+ function normalizeWhitespace(value) {
22102
+ return value.replace(/\s+/g, " ").trim();
22103
+ }
22104
+ function deriveTaskSessionLabel(input) {
22105
+ const preferred = normalizeWhitespace(input.description ?? "");
22106
+ if (preferred) {
22107
+ return preferred.slice(0, 48);
22108
+ }
22109
+ const firstPromptLine = (input.prompt ?? "").split(/\r?\n/).map((line) => normalizeWhitespace(line)).find(Boolean);
22110
+ if (firstPromptLine) {
22111
+ return firstPromptLine.slice(0, 48);
22112
+ }
22113
+ return `recent ${input.agentType} task`;
22114
+ }
22115
+
22116
+ class SessionManager {
22117
+ maxSessionsPerAgent;
22118
+ sessionsByParent = new Map;
22119
+ nextAliasIndexByParent = new Map;
22120
+ orderCounter = 0;
22121
+ constructor(maxSessionsPerAgent) {
22122
+ this.maxSessionsPerAgent = maxSessionsPerAgent;
22123
+ }
22124
+ remember(input) {
22125
+ const now = this.nextOrder();
22126
+ const group = this.getAgentGroup(input.parentSessionId, input.agentType, true);
22127
+ if (!group) {
22128
+ throw new Error("Failed to initialize session group");
22129
+ }
22130
+ const existing = group.find((entry) => entry.taskId === input.taskId);
22131
+ if (existing) {
22132
+ existing.label = input.label;
22133
+ existing.lastUsedAt = this.nextOrder();
22134
+ return existing;
22135
+ }
22136
+ const remembered = {
22137
+ alias: this.nextAlias(input.parentSessionId, input.agentType),
22138
+ taskId: input.taskId,
22139
+ agentType: input.agentType,
22140
+ label: input.label,
22141
+ createdAt: now,
22142
+ lastUsedAt: now
22143
+ };
22144
+ group.push(remembered);
22145
+ this.trimGroup(group);
22146
+ return remembered;
22147
+ }
22148
+ markUsed(parentSessionId, agentType, key) {
22149
+ const group = this.getAgentGroup(parentSessionId, agentType, false);
22150
+ const match = group?.find((entry) => entry.alias === key || entry.taskId === key);
22151
+ if (match) {
22152
+ match.lastUsedAt = this.nextOrder();
22153
+ }
22154
+ }
22155
+ resolve(parentSessionId, agentType, key) {
22156
+ const group = this.getAgentGroup(parentSessionId, agentType, false);
22157
+ return group?.find((entry) => entry.alias === key || entry.taskId === key);
22158
+ }
22159
+ drop(parentSessionId, agentType, key) {
22160
+ const group = this.getAgentGroup(parentSessionId, agentType, false);
22161
+ if (!group)
22162
+ return;
22163
+ const next = group.filter((entry) => entry.alias !== key && entry.taskId !== key);
22164
+ this.setAgentGroup(parentSessionId, agentType, next);
22165
+ }
22166
+ dropTask(taskId) {
22167
+ for (const [parentSessionId, groups] of this.sessionsByParent.entries()) {
22168
+ for (const [agentType, group] of groups.entries()) {
22169
+ const next = group.filter((entry) => entry.taskId !== taskId);
22170
+ this.setAgentGroup(parentSessionId, agentType, next);
22171
+ }
22172
+ }
22173
+ }
22174
+ clearParent(parentSessionId) {
22175
+ this.sessionsByParent.delete(parentSessionId);
22176
+ this.nextAliasIndexByParent.delete(parentSessionId);
22177
+ }
22178
+ formatForPrompt(parentSessionId) {
22179
+ const groups = this.sessionsByParent.get(parentSessionId);
22180
+ if (!groups || groups.size === 0)
22181
+ return;
22182
+ const lines = [...groups.entries()].map(([agentType, entries]) => [
22183
+ agentType,
22184
+ [...entries].sort((a, b) => b.lastUsedAt - a.lastUsedAt)
22185
+ ]).filter(([, entries]) => entries.length > 0).sort((a, b) => b[1][0].lastUsedAt - a[1][0].lastUsedAt).map(([agentType, entries]) => `- ${agentType}: ${entries.map((entry) => `${entry.alias} ${entry.label}`).join("; ")}`);
22186
+ if (lines.length === 0)
22187
+ return;
22188
+ return [
22189
+ "### Resumable Sessions",
22190
+ "Reuse only for clear continuation of the same thread. Otherwise start fresh.",
22191
+ "",
22192
+ ...lines
22193
+ ].join(`
22194
+ `);
22195
+ }
22196
+ getAgentGroup(parentSessionId, agentType, create) {
22197
+ let groups = this.sessionsByParent.get(parentSessionId);
22198
+ if (!groups && create) {
22199
+ groups = new Map;
22200
+ this.sessionsByParent.set(parentSessionId, groups);
22201
+ }
22202
+ let group = groups?.get(agentType);
22203
+ if (!group && create && groups) {
22204
+ group = [];
22205
+ groups.set(agentType, group);
22206
+ }
22207
+ return group;
22208
+ }
22209
+ setAgentGroup(parentSessionId, agentType, entries) {
22210
+ const groups = this.sessionsByParent.get(parentSessionId);
22211
+ if (!groups)
22212
+ return;
22213
+ if (entries.length === 0) {
22214
+ groups.delete(agentType);
22215
+ if (groups.size === 0) {
22216
+ this.sessionsByParent.delete(parentSessionId);
22217
+ this.nextAliasIndexByParent.delete(parentSessionId);
22218
+ }
22219
+ return;
22220
+ }
22221
+ groups.set(agentType, entries);
22222
+ }
22223
+ nextAlias(parentSessionId, agentType) {
22224
+ let counters = this.nextAliasIndexByParent.get(parentSessionId);
22225
+ if (!counters) {
22226
+ counters = new Map;
22227
+ this.nextAliasIndexByParent.set(parentSessionId, counters);
22228
+ }
22229
+ const next = (counters.get(agentType) ?? 0) + 1;
22230
+ counters.set(agentType, next);
22231
+ return `${aliasPrefix(agentType)}-${next}`;
22232
+ }
22233
+ trimGroup(group) {
22234
+ group.sort((a, b) => b.lastUsedAt - a.lastUsedAt);
22235
+ if (group.length > this.maxSessionsPerAgent) {
22236
+ group.length = this.maxSessionsPerAgent;
22237
+ }
22238
+ }
22239
+ nextOrder() {
22240
+ this.orderCounter += 1;
22241
+ return this.orderCounter;
22242
+ }
22243
+ }
22244
+ // src/utils/task.ts
22245
+ function parseTaskIdFromTaskOutput(output) {
22246
+ const lines = output.split(/\r?\n/);
22247
+ for (const line of lines) {
22248
+ const trimmed = line.trim();
22249
+ const match = /^task_id:\s*([^\s()]+)(?:\s*\(.*)?$/.exec(trimmed);
22250
+ if (!match) {
22251
+ continue;
22252
+ }
22253
+ return match[1];
22254
+ }
22255
+ return;
22256
+ }
22060
22257
  // src/utils/zip-extractor.ts
22061
22258
  import { spawnSync } from "node:child_process";
22062
22259
  import { release } from "node:os";
@@ -22372,6 +22569,8 @@ var RATE_LIMIT_PATTERNS = [
22372
22569
  /too many requests/i,
22373
22570
  /quota.?exceeded/i,
22374
22571
  /usage.?exceeded/i,
22572
+ /ExceededBudget/i,
22573
+ /over.?budget/i,
22375
22574
  /usage limit/i,
22376
22575
  /overloaded/i,
22377
22576
  /resource.?exhausted/i,
@@ -22450,7 +22649,7 @@ class ForegroundFallbackManager {
22450
22649
  if (!props?.sessionID || props.status?.type !== "retry")
22451
22650
  break;
22452
22651
  const msg = props.status.message?.toLowerCase() ?? "";
22453
- if (msg.includes("rate limit") || msg.includes("usage limit") || msg.includes("usage exceeded") || msg.includes("quota exceeded") || msg.includes("high concurrency") || msg.includes("reduce concurrency")) {
22652
+ if (msg.includes("rate limit") || msg.includes("usage limit") || msg.includes("usage exceeded") || msg.includes("quota exceeded") || msg.includes("exceededbudget") || msg.includes("over budget") || msg.includes("high concurrency") || msg.includes("reduce concurrency")) {
22454
22653
  await this.tryFallback(props.sessionID);
22455
22654
  }
22456
22655
  break;
@@ -22880,6 +23079,154 @@ function createPostFileToolNudgeHook(options = {}) {
22880
23079
  }
22881
23080
  };
22882
23081
  }
23082
+ // src/hooks/task-session-manager/index.ts
23083
+ var AGENT_NAME_SET = new Set([
23084
+ "orchestrator",
23085
+ "oracle",
23086
+ "designer",
23087
+ "explorer",
23088
+ "librarian",
23089
+ "fixer",
23090
+ "observer",
23091
+ "council",
23092
+ "councillor"
23093
+ ]);
23094
+ var MAX_PENDING_TASK_CALLS = 100;
23095
+ function isAgentName(value) {
23096
+ return typeof value === "string" && AGENT_NAME_SET.has(value);
23097
+ }
23098
+ function isObjectRecord(value) {
23099
+ return typeof value === "object" && value !== null;
23100
+ }
23101
+ function createTaskSessionManagerHook(_ctx, options) {
23102
+ const sessionManager = new SessionManager(options.maxSessionsPerAgent);
23103
+ const pendingCalls = new Map;
23104
+ const pendingCallOrder = [];
23105
+ function isMissingRememberedSessionError(output) {
23106
+ const firstLine = output.split(/\r?\n/, 1)[0]?.trim().toLowerCase() ?? "";
23107
+ return firstLine.startsWith("[error]") && firstLine.includes("session") && (firstLine.includes("not found") || firstLine.includes("no session"));
23108
+ }
23109
+ function rememberPendingCall(call) {
23110
+ const existingIndex = pendingCallOrder.indexOf(call.callId);
23111
+ if (existingIndex >= 0) {
23112
+ pendingCallOrder.splice(existingIndex, 1);
23113
+ }
23114
+ pendingCalls.set(call.callId, call);
23115
+ pendingCallOrder.push(call.callId);
23116
+ while (pendingCallOrder.length > MAX_PENDING_TASK_CALLS) {
23117
+ const evictedCallId = pendingCallOrder.shift();
23118
+ if (!evictedCallId) {
23119
+ break;
23120
+ }
23121
+ pendingCalls.delete(evictedCallId);
23122
+ }
23123
+ }
23124
+ function takePendingCall(callId) {
23125
+ if (!callId)
23126
+ return;
23127
+ const pending = pendingCalls.get(callId);
23128
+ pendingCalls.delete(callId);
23129
+ const orderIndex = pendingCallOrder.indexOf(callId);
23130
+ if (orderIndex >= 0) {
23131
+ pendingCallOrder.splice(orderIndex, 1);
23132
+ }
23133
+ return pending;
23134
+ }
23135
+ return {
23136
+ "tool.execute.before": async (input, output) => {
23137
+ if (input.tool.toLowerCase() !== "task")
23138
+ return;
23139
+ if (!input.sessionID || !options.shouldManageSession(input.sessionID)) {
23140
+ return;
23141
+ }
23142
+ if (!isObjectRecord(output.args))
23143
+ return;
23144
+ const args = output.args;
23145
+ if (!isAgentName(args.subagent_type))
23146
+ return;
23147
+ const label = deriveTaskSessionLabel({
23148
+ description: typeof args.description === "string" ? args.description : undefined,
23149
+ prompt: typeof args.prompt === "string" ? args.prompt : undefined,
23150
+ agentType: args.subagent_type
23151
+ });
23152
+ if (input.callID) {
23153
+ rememberPendingCall({
23154
+ callId: input.callID,
23155
+ parentSessionId: input.sessionID,
23156
+ agentType: args.subagent_type,
23157
+ label
23158
+ });
23159
+ }
23160
+ if (typeof args.task_id !== "string" || args.task_id.trim() === "") {
23161
+ return;
23162
+ }
23163
+ const requested = args.task_id.trim();
23164
+ const remembered = sessionManager.resolve(input.sessionID, args.subagent_type, requested);
23165
+ if (!remembered) {
23166
+ delete args.task_id;
23167
+ return;
23168
+ }
23169
+ args.task_id = remembered.taskId;
23170
+ sessionManager.markUsed(input.sessionID, args.subagent_type, remembered.taskId);
23171
+ if (input.callID) {
23172
+ rememberPendingCall({
23173
+ callId: input.callID,
23174
+ parentSessionId: input.sessionID,
23175
+ agentType: args.subagent_type,
23176
+ label,
23177
+ resumedTaskId: remembered.taskId
23178
+ });
23179
+ }
23180
+ },
23181
+ "tool.execute.after": async (input, output) => {
23182
+ if (input.tool.toLowerCase() !== "task")
23183
+ return;
23184
+ const pending = takePendingCall(input.callID);
23185
+ if (!pending || typeof output.output !== "string")
23186
+ return;
23187
+ const taskId = parseTaskIdFromTaskOutput(output.output);
23188
+ if (!taskId) {
23189
+ if (pending.resumedTaskId && isMissingRememberedSessionError(output.output)) {
23190
+ sessionManager.drop(pending.parentSessionId, pending.agentType, pending.resumedTaskId);
23191
+ }
23192
+ return;
23193
+ }
23194
+ if (pending.resumedTaskId && pending.resumedTaskId !== taskId) {
23195
+ sessionManager.drop(pending.parentSessionId, pending.agentType, pending.resumedTaskId);
23196
+ }
23197
+ sessionManager.remember({
23198
+ parentSessionId: pending.parentSessionId,
23199
+ taskId,
23200
+ agentType: pending.agentType,
23201
+ label: pending.label
23202
+ });
23203
+ },
23204
+ "experimental.chat.system.transform": async (input, output) => {
23205
+ if (!input.sessionID || !options.shouldManageSession(input.sessionID)) {
23206
+ return;
23207
+ }
23208
+ const reminder = sessionManager.formatForPrompt(input.sessionID);
23209
+ if (!reminder)
23210
+ return;
23211
+ output.system.push(reminder);
23212
+ },
23213
+ event: async (input) => {
23214
+ if (input.event.type !== "session.deleted")
23215
+ return;
23216
+ const sessionId = input.event.properties?.info?.id ?? input.event.properties?.sessionID;
23217
+ if (!sessionId)
23218
+ return;
23219
+ sessionManager.clearParent(sessionId);
23220
+ sessionManager.dropTask(sessionId);
23221
+ for (const [callId, pending] of pendingCalls.entries()) {
23222
+ if (pending.parentSessionId !== sessionId) {
23223
+ continue;
23224
+ }
23225
+ takePendingCall(callId);
23226
+ }
23227
+ }
23228
+ };
23229
+ }
22883
23230
  // src/hooks/todo-continuation/index.ts
22884
23231
  import { tool } from "@opencode-ai/plugin/tool";
22885
23232
 
@@ -23764,6 +24111,7 @@ async function readJsonBody(request) {
23764
24111
  }
23765
24112
 
23766
24113
  // src/interview/ui.ts
24114
+ var BRAND_LOGO_URL = "https://ohmyopencodeslim.com/android-chrome-512x512.png";
23767
24115
  function escapeHtml(value) {
23768
24116
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
23769
24117
  }
@@ -23861,12 +24209,8 @@ function sharedStyles() {
23861
24209
  }
23862
24210
  .footer { margin-top: 32px; text-align: center; font-size: 13px; color: rgba(255,255,255,0.4); }`;
23863
24211
  }
23864
- function brandSvg(size) {
23865
- return `<svg class="brand-mark" viewBox="0 0 144 144" role="img" aria-label="Oh My Opencode Slim" style="width:${size}px;height:${size}px">
23866
- <rect x="12" y="12" width="120" height="120" rx="32" fill="rgba(255,255,255,0.08)" stroke="rgba(255,255,255,0.18)" stroke-width="2"/>
23867
- <path d="M50 48h18c16 0 26 10 26 24s-10 24-26 24H50z" fill="none" stroke="white" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
23868
- <path d="M74 48h20c10 0 18 8 18 18v12c0 10-8 18-18 18H74" fill="none" stroke="white" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" opacity="0.65"/>
23869
- </svg>`;
24212
+ function brandImage(size) {
24213
+ return `<img class="brand-mark" src="${BRAND_LOGO_URL}" alt="Oh My Opencode Slim" width="${size}" height="${size}" />`;
23870
24214
  }
23871
24215
  function renderDashboardPage(interviews, files, outputFolder) {
23872
24216
  const activeHtml = interviews.length === 0 ? "" : interviews.map((item) => {
@@ -24075,7 +24419,7 @@ function renderDashboardPage(interviews, files, outputFolder) {
24075
24419
  <body>
24076
24420
  <div class="wrap">
24077
24421
  <div class="brand-header">
24078
- ${brandSvg(96)}
24422
+ ${brandImage(96)}
24079
24423
  <h1>Interviews</h1>
24080
24424
  <p class="muted">${totalCount} item${totalCount === 1 ? "" : "s"}</p>
24081
24425
  </div>
@@ -24276,6 +24620,37 @@ function renderInterviewPage(interviewId, resumeSlug) {
24276
24620
  }
24277
24621
 
24278
24622
  .options { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
24623
+ .question-hint {
24624
+ display: flex;
24625
+ flex-wrap: wrap;
24626
+ gap: 8px;
24627
+ margin: -4px 0 16px;
24628
+ color: rgba(255,255,255,0.5);
24629
+ font-size: 13px;
24630
+ line-height: 1.5;
24631
+ transition: color 0.2s ease;
24632
+ }
24633
+ .active-question .question-hint {
24634
+ color: rgba(255,255,255,0.78);
24635
+ }
24636
+ .hint-chip {
24637
+ display: inline-flex;
24638
+ align-items: center;
24639
+ gap: 6px;
24640
+ padding: 4px 10px;
24641
+ border-radius: 999px;
24642
+ background: rgba(255,255,255,0.06);
24643
+ border: 1px solid rgba(255,255,255,0.08);
24644
+ }
24645
+ .hint-chip kbd {
24646
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
24647
+ font-size: 12px;
24648
+ padding: 2px 6px;
24649
+ border-radius: 6px;
24650
+ background: rgba(255,255,255,0.12);
24651
+ border: 1px solid rgba(255,255,255,0.08);
24652
+ color: rgba(255,255,255,0.95);
24653
+ }
24279
24654
 
24280
24655
  .option {
24281
24656
  border: 1px solid rgba(255,255,255,0.1);
@@ -24555,7 +24930,7 @@ function renderInterviewPage(interviewId, resumeSlug) {
24555
24930
  <div class="wrap">
24556
24931
  <a href="/" class="back-link">← All Interviews</a>
24557
24932
  <div class="brand-header">
24558
- ${brandSvg(144)}
24933
+ ${brandImage(144)}
24559
24934
  </div>
24560
24935
  <h1 id="idea">Connecting...</h1>
24561
24936
  <p class="muted" id="summary">Preparing interview session</p>
@@ -24596,7 +24971,15 @@ function renderInterviewPage(interviewId, resumeSlug) {
24596
24971
  ${clipboardHelperJs()}
24597
24972
  const interviewId = ${JSON.stringify(interviewId).replace(/</g, "\\u003c")};
24598
24973
  const resumeSlug = ${JSON.stringify(resumeSlug).replace(/</g, "\\u003c")};
24599
- const state = { data: null, answers: {}, activeQuestionIndex: 0, lastSig: null, customMode: {} };
24974
+ const state = {
24975
+ data: null,
24976
+ answers: {},
24977
+ activeQuestionIndex: 0,
24978
+ lastQuestionIds: [],
24979
+ lastSig: null,
24980
+ customMode: {},
24981
+ isSubmitting: false,
24982
+ };
24600
24983
 
24601
24984
  function updateSubmitButton() {
24602
24985
  const button = document.getElementById('submitButton');
@@ -24609,7 +24992,11 @@ function renderInterviewPage(interviewId, resumeSlug) {
24609
24992
  const allAnswered = questions.every((question) =>
24610
24993
  (state.answers[question.id] || '').trim().length > 0,
24611
24994
  );
24612
- button.disabled = state.data.isBusy || !questions.length || !allAnswered;
24995
+ button.disabled =
24996
+ state.data.isBusy ||
24997
+ state.isSubmitting ||
24998
+ !questions.length ||
24999
+ !allAnswered;
24613
25000
  const hideSubmit = ['completed', 'session-disconnected'];
24614
25001
  button.style.display = hideSubmit.includes(state.data.mode) ? 'none' : '';
24615
25002
 
@@ -24760,6 +25147,54 @@ function renderInterviewPage(interviewId, resumeSlug) {
24760
25147
  });
24761
25148
  }
24762
25149
 
25150
+ function scrollToActiveQuestion(behavior) {
25151
+ const questions = state.data?.questions || [];
25152
+ const activeQ = questions[state.activeQuestionIndex];
25153
+ if (!activeQ) return;
25154
+
25155
+ const wrapper = document.getElementById('question-' + activeQ.id);
25156
+ if (wrapper) {
25157
+ wrapper.scrollIntoView({ behavior, block: 'center' });
25158
+ }
25159
+ }
25160
+
25161
+ function syncActiveQuestionIndex(questions) {
25162
+ if (!questions.length) {
25163
+ state.activeQuestionIndex = 0;
25164
+ state.lastQuestionIds = [];
25165
+ return;
25166
+ }
25167
+
25168
+ const nextQuestionIds = questions.map((question) => question.id);
25169
+ const previousQuestionIds = state.lastQuestionIds || [];
25170
+ const activeQuestionId = previousQuestionIds[state.activeQuestionIndex];
25171
+ const nextActiveIndex = activeQuestionId
25172
+ ? nextQuestionIds.indexOf(activeQuestionId)
25173
+ : -1;
25174
+
25175
+ if (nextActiveIndex >= 0) {
25176
+ state.activeQuestionIndex = nextActiveIndex;
25177
+ } else {
25178
+ state.activeQuestionIndex = 0;
25179
+ }
25180
+
25181
+ state.lastQuestionIds = nextQuestionIds;
25182
+ }
25183
+
25184
+ function isTextEntryTarget(target) {
25185
+ return target &&
25186
+ (target.tagName === 'TEXTAREA' ||
25187
+ target.tagName === 'INPUT' ||
25188
+ target.isContentEditable);
25189
+ }
25190
+
25191
+ function isShortcutBlockedTarget(target) {
25192
+ if (!target) return false;
25193
+ return !!target.closest(
25194
+ 'button, a, select, summary, textarea, input, [contenteditable="true"]',
25195
+ );
25196
+ }
25197
+
24763
25198
  document.addEventListener('keydown', (e) => {
24764
25199
  const isSubmitShortcut =
24765
25200
  (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) ||
@@ -24773,12 +25208,41 @@ function renderInterviewPage(interviewId, resumeSlug) {
24773
25208
  return;
24774
25209
  }
24775
25210
 
24776
- if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') return;
25211
+ if (isTextEntryTarget(e.target)) return;
24777
25212
  if (e.ctrlKey || e.metaKey || e.altKey) return;
24778
25213
 
24779
25214
  const questions = state.data?.questions || [];
24780
25215
  if (!questions.length) return;
24781
25216
 
25217
+ if (e.key === 'Enter') {
25218
+ if (e.repeat) {
25219
+ e.preventDefault();
25220
+ return;
25221
+ }
25222
+ if (isShortcutBlockedTarget(e.target)) return;
25223
+ if (state.data.isBusy || state.isSubmitting) return;
25224
+
25225
+ const activeQ = questions[state.activeQuestionIndex];
25226
+ if (!activeQ) return;
25227
+
25228
+ const answer = (state.answers[activeQ.id] || '').trim();
25229
+ if (!answer) return;
25230
+
25231
+ const isLastQuestion =
25232
+ state.activeQuestionIndex === questions.length - 1;
25233
+ if (isLastQuestion) {
25234
+ const submitBtn = document.getElementById('submitButton');
25235
+ if (submitBtn && !submitBtn.disabled) {
25236
+ submitBtn.click();
25237
+ }
25238
+ } else {
25239
+ advanceToNextQuestion(activeQ.id);
25240
+ }
25241
+
25242
+ e.preventDefault();
25243
+ return;
25244
+ }
25245
+
24782
25246
  const num = parseInt(e.key, 10);
24783
25247
  if (num >= 1 && num <= 9) {
24784
25248
  const activeQ = questions[state.activeQuestionIndex];
@@ -24946,6 +25410,10 @@ function renderInterviewPage(interviewId, resumeSlug) {
24946
25410
  function renderQuestions(questions) {
24947
25411
  const sig = JSON.stringify([questions, state.data?.mode]);
24948
25412
  const container = document.getElementById('questions');
25413
+ const previousActiveQuestionId =
25414
+ state.lastQuestionIds[state.activeQuestionIndex];
25415
+
25416
+ syncActiveQuestionIndex(questions);
24949
25417
 
24950
25418
  if (state.lastSig === sig) {
24951
25419
  questions.forEach((q) => updateOptionsDOM(q.id));
@@ -24986,6 +25454,15 @@ function renderInterviewPage(interviewId, resumeSlug) {
24986
25454
  wrapper.appendChild(title);
24987
25455
 
24988
25456
  const predefined = question.options || [];
25457
+ if (predefined.length) {
25458
+ const hint = document.createElement('div');
25459
+ hint.className = 'question-hint';
25460
+ hint.innerHTML =
25461
+ '<span class="hint-chip"><kbd>1-9</kbd><span>Choose an option</span></span>' +
25462
+ '<span class="hint-chip"><kbd>Enter</kbd><span>Accept selected answer</span></span>';
25463
+ wrapper.appendChild(hint);
25464
+ }
25465
+
24989
25466
  if (predefined.length) {
24990
25467
  const options = document.createElement('div');
24991
25468
  options.className = 'options';
@@ -25021,6 +25498,13 @@ function renderInterviewPage(interviewId, resumeSlug) {
25021
25498
 
25022
25499
  updateActiveQuestionFocus();
25023
25500
  questions.forEach(q => updateOptionsDOM(q.id));
25501
+ const currentActiveQuestionId = questions[state.activeQuestionIndex]?.id;
25502
+ if (
25503
+ questions.length > 0 &&
25504
+ previousActiveQuestionId !== currentActiveQuestionId
25505
+ ) {
25506
+ scrollToActiveQuestion('smooth');
25507
+ }
25024
25508
  }
25025
25509
 
25026
25510
  function render(data) {
@@ -25094,7 +25578,8 @@ function renderInterviewPage(interviewId, resumeSlug) {
25094
25578
  }
25095
25579
 
25096
25580
  document.getElementById('submitButton').addEventListener('click', async () => {
25097
- if (!state.data) return;
25581
+ if (!state.data || state.isSubmitting) return;
25582
+ document.getElementById('submitButton').blur();
25098
25583
  const answers = (state.data.questions || []).map((question) => {
25099
25584
  return {
25100
25585
  questionId: question.id,
@@ -25102,6 +25587,9 @@ function renderInterviewPage(interviewId, resumeSlug) {
25102
25587
  };
25103
25588
  });
25104
25589
 
25590
+ state.isSubmitting = true;
25591
+ updateSubmitButton();
25592
+
25105
25593
  const overlay = document.getElementById('loadingOverlay');
25106
25594
  const overlayText = document.getElementById('loadingText');
25107
25595
  overlay.classList.add('active');
@@ -25119,6 +25607,8 @@ function renderInterviewPage(interviewId, resumeSlug) {
25119
25607
  } catch (err) {
25120
25608
  document.getElementById('submitStatus').textContent = 'Error submitting answers.';
25121
25609
  }
25610
+ state.isSubmitting = false;
25611
+ updateSubmitButton();
25122
25612
  try {
25123
25613
  await refresh();
25124
25614
  } catch (_error) {
@@ -26438,6 +26928,7 @@ function createInterviewService(ctx, config, deps) {
26438
26928
  const activeInterviewIds = new Map;
26439
26929
  const interviewsById = new Map;
26440
26930
  const sessionBusy = new Map;
26931
+ const sessionModel = new Map;
26441
26932
  const browserOpened = new Set;
26442
26933
  let resolveBaseUrl = null;
26443
26934
  let onStateChange = null;
@@ -26693,10 +27184,12 @@ function createInterviewService(ctx, config, deps) {
26693
27184
  }
26694
27185
  await appendInterviewAnswers(interview, state.questions, answers);
26695
27186
  const prompt = buildAnswerPrompt(answers, state.questions, maxQuestions);
27187
+ const model = sessionModel.get(interview.sessionID);
26696
27188
  await ctx.client.session.promptAsync({
26697
27189
  path: { id: interview.sessionID },
26698
27190
  body: {
26699
- parts: [createInternalAgentTextPart(prompt)]
27191
+ parts: [createInternalAgentTextPart(prompt)],
27192
+ ...model ? { model: parseModelReference(model) ?? undefined } : {}
26700
27193
  }
26701
27194
  });
26702
27195
  promptSent = true;
@@ -26746,12 +27239,23 @@ function createInterviewService(ctx, config, deps) {
26746
27239
  }
26747
27240
  return;
26748
27241
  }
27242
+ if (event.type === "message.updated") {
27243
+ const info = properties;
27244
+ const sessionID = info?.info?.sessionID;
27245
+ const providerID = info?.info?.providerID;
27246
+ const modelID = info?.info?.modelID;
27247
+ if (sessionID && providerID && modelID) {
27248
+ sessionModel.set(sessionID, `${providerID}/${modelID}`);
27249
+ }
27250
+ return;
27251
+ }
26749
27252
  if (event.type === "session.deleted") {
26750
27253
  const deletedSessionId = (properties.info?.id ?? properties.sessionID) || null;
26751
27254
  if (!deletedSessionId) {
26752
27255
  return;
26753
27256
  }
26754
27257
  sessionBusy.delete(deletedSessionId);
27258
+ sessionModel.delete(deletedSessionId);
26755
27259
  const interviewId = activeInterviewIds.get(deletedSessionId);
26756
27260
  if (!interviewId) {
26757
27261
  return;
@@ -26848,10 +27352,12 @@ function createInterviewService(ctx, config, deps) {
26848
27352
  ].join(`
26849
27353
  `);
26850
27354
  }
27355
+ const model = sessionModel.get(interview.sessionID);
26851
27356
  await ctx.client.session.promptAsync({
26852
27357
  path: { id: interview.sessionID },
26853
27358
  body: {
26854
- parts: [createInternalAgentTextPart(prompt)]
27359
+ parts: [createInternalAgentTextPart(prompt)],
27360
+ ...model ? { model: parseModelReference(model) ?? undefined } : {}
26855
27361
  }
26856
27362
  });
26857
27363
  promptSent = true;
@@ -27825,6 +28331,8 @@ class MultiplexerSessionManager {
27825
28331
  directory;
27826
28332
  multiplexer = null;
27827
28333
  sessions = new Map;
28334
+ knownSessions = new Map;
28335
+ spawningSessions = new Set;
27828
28336
  pollInterval;
27829
28337
  enabled = false;
27830
28338
  constructor(ctx, config) {
@@ -27853,45 +28361,59 @@ class MultiplexerSessionManager {
27853
28361
  const parentId = info.parentID;
27854
28362
  const title = info.title ?? "Subagent";
27855
28363
  const directory = info.directory ?? this.directory;
27856
- if (this.sessions.has(sessionId)) {
27857
- log("[multiplexer-session-manager] session already tracked", {
28364
+ this.knownSessions.set(sessionId, {
28365
+ parentId,
28366
+ title,
28367
+ directory
28368
+ });
28369
+ if (this.isTrackedOrSpawning(sessionId)) {
28370
+ log("[multiplexer-session-manager] session already tracked or spawning", {
27858
28371
  sessionId
27859
28372
  });
27860
28373
  return;
27861
28374
  }
27862
- const serverRunning = await isServerRunning(this.serverUrl);
27863
- if (!serverRunning) {
27864
- log("[multiplexer-session-manager] server not running, skipping", {
27865
- serverUrl: this.serverUrl
27866
- });
27867
- return;
27868
- }
27869
- log("[multiplexer-session-manager] child session created, spawning pane", {
27870
- sessionId,
27871
- parentId,
27872
- title
27873
- });
27874
- const paneResult = await this.multiplexer.spawnPane(sessionId, title, this.serverUrl, directory).catch((err) => {
27875
- log("[multiplexer-session-manager] failed to spawn pane", {
27876
- error: String(err)
27877
- });
27878
- return { success: false, paneId: undefined };
27879
- });
27880
- if (paneResult.success && paneResult.paneId) {
27881
- const now = Date.now();
27882
- this.sessions.set(sessionId, {
28375
+ this.spawningSessions.add(sessionId);
28376
+ try {
28377
+ const serverRunning = await isServerRunning(this.serverUrl);
28378
+ if (!serverRunning) {
28379
+ log("[multiplexer-session-manager] server not running, skipping", {
28380
+ serverUrl: this.serverUrl
28381
+ });
28382
+ return;
28383
+ }
28384
+ if (this.sessions.has(sessionId)) {
28385
+ return;
28386
+ }
28387
+ log("[multiplexer-session-manager] child session created, spawning pane", {
27883
28388
  sessionId,
27884
- paneId: paneResult.paneId,
27885
28389
  parentId,
27886
- title,
27887
- createdAt: now,
27888
- lastSeenAt: now
28390
+ title
27889
28391
  });
27890
- log("[multiplexer-session-manager] pane spawned", {
27891
- sessionId,
27892
- paneId: paneResult.paneId
28392
+ const paneResult = await this.multiplexer.spawnPane(sessionId, title, this.serverUrl, directory).catch((err) => {
28393
+ log("[multiplexer-session-manager] failed to spawn pane", {
28394
+ error: String(err)
28395
+ });
28396
+ return { success: false, paneId: undefined };
27893
28397
  });
27894
- this.startPolling();
28398
+ if (paneResult.success && paneResult.paneId) {
28399
+ const now = Date.now();
28400
+ this.sessions.set(sessionId, {
28401
+ sessionId,
28402
+ paneId: paneResult.paneId,
28403
+ parentId,
28404
+ title,
28405
+ directory,
28406
+ createdAt: now,
28407
+ lastSeenAt: now
28408
+ });
28409
+ log("[multiplexer-session-manager] pane spawned", {
28410
+ sessionId,
28411
+ paneId: paneResult.paneId
28412
+ });
28413
+ this.startPolling();
28414
+ }
28415
+ } finally {
28416
+ this.spawningSessions.delete(sessionId);
27895
28417
  }
27896
28418
  }
27897
28419
  async onSessionStatus(event) {
@@ -27904,6 +28426,10 @@ class MultiplexerSessionManager {
27904
28426
  return;
27905
28427
  if (event.properties?.status?.type === "idle") {
27906
28428
  await this.closeSession(sessionId);
28429
+ return;
28430
+ }
28431
+ if (event.properties?.status?.type === "busy") {
28432
+ await this.respawnIfKnown(sessionId);
27907
28433
  }
27908
28434
  }
27909
28435
  async onSessionDeleted(event) {
@@ -27918,6 +28444,7 @@ class MultiplexerSessionManager {
27918
28444
  sessionId
27919
28445
  });
27920
28446
  await this.closeSession(sessionId);
28447
+ this.knownSessions.delete(sessionId);
27921
28448
  }
27922
28449
  startPolling() {
27923
28450
  if (this.pollInterval)
@@ -27978,6 +28505,61 @@ class MultiplexerSessionManager {
27978
28505
  this.stopPolling();
27979
28506
  }
27980
28507
  }
28508
+ async respawnIfKnown(sessionId) {
28509
+ if (!this.enabled || !this.multiplexer)
28510
+ return;
28511
+ if (this.isTrackedOrSpawning(sessionId))
28512
+ return;
28513
+ const known = this.knownSessions.get(sessionId);
28514
+ if (!known)
28515
+ return;
28516
+ this.spawningSessions.add(sessionId);
28517
+ try {
28518
+ const serverRunning = await isServerRunning(this.serverUrl);
28519
+ if (!serverRunning) {
28520
+ log("[multiplexer-session-manager] server not running, skipping busy respawn", {
28521
+ serverUrl: this.serverUrl,
28522
+ sessionId
28523
+ });
28524
+ return;
28525
+ }
28526
+ if (this.sessions.has(sessionId))
28527
+ return;
28528
+ log("[multiplexer-session-manager] child session busy again, respawning pane", {
28529
+ sessionId,
28530
+ parentId: known.parentId,
28531
+ title: known.title
28532
+ });
28533
+ const paneResult = await this.multiplexer.spawnPane(sessionId, known.title, this.serverUrl, known.directory).catch((err) => {
28534
+ log("[multiplexer-session-manager] failed to respawn pane", {
28535
+ error: String(err)
28536
+ });
28537
+ return { success: false, paneId: undefined };
28538
+ });
28539
+ if (!paneResult.success || !paneResult.paneId)
28540
+ return;
28541
+ const now = Date.now();
28542
+ this.sessions.set(sessionId, {
28543
+ sessionId,
28544
+ paneId: paneResult.paneId,
28545
+ parentId: known.parentId,
28546
+ title: known.title,
28547
+ directory: known.directory,
28548
+ createdAt: now,
28549
+ lastSeenAt: now
28550
+ });
28551
+ log("[multiplexer-session-manager] pane respawned on busy", {
28552
+ sessionId,
28553
+ paneId: paneResult.paneId
28554
+ });
28555
+ this.startPolling();
28556
+ } finally {
28557
+ this.spawningSessions.delete(sessionId);
28558
+ }
28559
+ }
28560
+ isTrackedOrSpawning(sessionId) {
28561
+ return this.sessions.has(sessionId) || this.spawningSessions.has(sessionId);
28562
+ }
27981
28563
  async cleanup() {
27982
28564
  this.stopPolling();
27983
28565
  if (this.sessions.size > 0 && this.multiplexer) {
@@ -27992,6 +28574,8 @@ class MultiplexerSessionManager {
27992
28574
  await Promise.all(closePromises);
27993
28575
  this.sessions.clear();
27994
28576
  }
28577
+ this.knownSessions.clear();
28578
+ this.spawningSessions.clear();
27995
28579
  log("[multiplexer-session-manager] cleanup complete");
27996
28580
  }
27997
28581
  }
@@ -31099,6 +31683,17 @@ class SubagentDepthTracker {
31099
31683
  }
31100
31684
  }
31101
31685
 
31686
+ // src/utils/system-collapse.ts
31687
+ function collapseSystemInPlace(system2) {
31688
+ const joined = system2.join(`
31689
+
31690
+ `);
31691
+ system2.length = 0;
31692
+ if (joined) {
31693
+ system2.push(joined);
31694
+ }
31695
+ }
31696
+
31102
31697
  // src/index.ts
31103
31698
  async function appLog(ctx, level, message) {
31104
31699
  try {
@@ -31148,6 +31743,7 @@ var OhMyOpenCodeLite = async (ctx) => {
31148
31743
  let jsonErrorRecoveryHook;
31149
31744
  let foregroundFallback;
31150
31745
  let todoContinuationHook;
31746
+ let taskSessionManagerHook;
31151
31747
  let interviewManager;
31152
31748
  let presetManager;
31153
31749
  let councilTools;
@@ -31207,7 +31803,7 @@ var OhMyOpenCodeLite = async (ctx) => {
31207
31803
  multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig);
31208
31804
  autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
31209
31805
  showStartupToast: config.showStartupToast ?? true,
31210
- autoUpdate: true
31806
+ autoUpdate: config.autoUpdate ?? true
31211
31807
  });
31212
31808
  phaseReminderHook = createPhaseReminderHook();
31213
31809
  filterAvailableSkillsHook = createFilterAvailableSkillsHook(ctx, config);
@@ -31226,6 +31822,10 @@ var OhMyOpenCodeLite = async (ctx) => {
31226
31822
  autoEnable: config.todoContinuation?.autoEnable ?? false,
31227
31823
  autoEnableThreshold: config.todoContinuation?.autoEnableThreshold ?? 4
31228
31824
  });
31825
+ taskSessionManagerHook = createTaskSessionManagerHook(ctx, {
31826
+ maxSessionsPerAgent: config.sessionManager?.maxSessionsPerAgent ?? 2,
31827
+ shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
31828
+ });
31229
31829
  interviewManager = createInterviewManager(ctx, config);
31230
31830
  presetManager = createPresetManager(ctx, config);
31231
31831
  toolCount = Object.keys(councilTools).length + Object.keys(todoContinuationHook.tool).length + 1 + 2;
@@ -31401,6 +32001,7 @@ var OhMyOpenCodeLite = async (ctx) => {
31401
32001
  await multiplexerSessionManager.onSessionDeleted(event);
31402
32002
  await interviewManager.handleEvent(input);
31403
32003
  await postFileToolNudgeHook.event(input);
32004
+ await taskSessionManagerHook.event(input);
31404
32005
  if (input.event.type === "session.deleted") {
31405
32006
  const props = input.event.properties;
31406
32007
  const sessionID = props?.info?.id ?? props?.sessionID;
@@ -31414,6 +32015,7 @@ var OhMyOpenCodeLite = async (ctx) => {
31414
32015
  },
31415
32016
  "tool.execute.before": async (input, output) => {
31416
32017
  await applyPatchHook["tool.execute.before"](input, output);
32018
+ await taskSessionManagerHook["tool.execute.before"](input, output);
31417
32019
  },
31418
32020
  "command.execute.before": async (input, output) => {
31419
32021
  await todoContinuationHook.handleCommandExecuteBefore(input, output);
@@ -31449,10 +32051,8 @@ ${output.system[0]}` : "");
31449
32051
  }
31450
32052
  await todoContinuationHook.handleChatSystemTransform(input, output);
31451
32053
  await postFileToolNudgeHook["experimental.chat.system.transform"](input, output);
31452
- const joined = output.system.join(`
31453
-
31454
- `);
31455
- output.system = joined ? [joined] : [];
32054
+ await taskSessionManagerHook["experimental.chat.system.transform"](input, output);
32055
+ collapseSystemInPlace(output.system);
31456
32056
  },
31457
32057
  "experimental.chat.messages.transform": async (input, output) => {
31458
32058
  const typedOutput = output;
@@ -31484,6 +32084,7 @@ ${output.system[0]}` : "");
31484
32084
  await jsonErrorRecoveryHook["tool.execute.after"](input, output);
31485
32085
  await todoContinuationHook.handleToolExecuteAfter(input);
31486
32086
  await postFileToolNudgeHook["tool.execute.after"](input, output);
32087
+ await taskSessionManagerHook["tool.execute.after"](input, output);
31487
32088
  }
31488
32089
  };
31489
32090
  };