oh-my-opencode-slim 2.2.2 → 2.2.3

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
@@ -18495,7 +18495,6 @@ var DEFAULT_MODELS = {
18495
18495
  };
18496
18496
  var POLL_INTERVAL_BACKGROUND_MS = 2000;
18497
18497
  var MAX_POLL_TIME_MS = 5 * 60 * 1000;
18498
- var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
18499
18498
  var PHASE_REMINDER_TEXT = `!IMPORTANT! Scheduler workflow: First choose the lightest workflow that fits the work. If direct execution is justified, complete it and verify proportionately. Otherwise: plan lanes/dependencies → dispatch background specialists → track task IDs → wait for hook-driven completion → reconcile terminal results → verify. Do not poll running jobs, consume running-job output, or advance dependent work. !END!`;
18500
18499
  function formatSystemReminder(text) {
18501
18500
  return `<system-reminder>
@@ -18746,6 +18745,7 @@ var MultiplexerTypeSchema = z2.enum([
18746
18745
  "tmux",
18747
18746
  "zellij",
18748
18747
  "herdr",
18748
+ "kitty",
18749
18749
  "cmux",
18750
18750
  "none"
18751
18751
  ]);
@@ -19188,7 +19188,7 @@ async function promptWithTimeout(client, args, timeoutMs, signal) {
19188
19188
  }
19189
19189
  await Promise.race(racers);
19190
19190
  } catch (error) {
19191
- if (error instanceof OperationTimeoutError) {
19191
+ if (isPromptCancellationError(error)) {
19192
19192
  try {
19193
19193
  await abortSessionWithTimeout(client, sessionId);
19194
19194
  } catch {}
@@ -19200,6 +19200,11 @@ async function promptWithTimeout(client, args, timeoutMs, signal) {
19200
19200
  signal?.removeEventListener("abort", onAbort);
19201
19201
  }
19202
19202
  }
19203
+ function isPromptCancellationError(error) {
19204
+ if (error instanceof OperationTimeoutError)
19205
+ return true;
19206
+ return error instanceof Error && error.message === "Prompt cancelled";
19207
+ }
19203
19208
  async function extractSessionResult(client, sessionId, options) {
19204
19209
  const includeReasoning = options?.includeReasoning ?? true;
19205
19210
  const messagesResult = await client.session.messages({
@@ -21287,17 +21292,15 @@ class CouncilManager {
21287
21292
  client;
21288
21293
  directory;
21289
21294
  config;
21290
- depthTracker;
21291
21295
  tmuxEnabled;
21292
21296
  deprecatedFields;
21293
21297
  legacyMasterModel;
21294
- constructor(ctx, config, depthTracker, tmuxEnabled = false) {
21298
+ constructor(ctx, config, tmuxEnabled = false) {
21295
21299
  this.client = ctx.client;
21296
21300
  this.directory = ctx.directory;
21297
21301
  this.config = config;
21298
21302
  this.deprecatedFields = config?.council?._deprecated;
21299
21303
  this.legacyMasterModel = config?.council?._legacyMasterModel;
21300
- this.depthTracker = depthTracker;
21301
21304
  this.tmuxEnabled = tmuxEnabled;
21302
21305
  }
21303
21306
  getDeprecatedFields() {
@@ -21307,21 +21310,6 @@ class CouncilManager {
21307
21310
  return this.legacyMasterModel;
21308
21311
  }
21309
21312
  async runCouncil(prompt, presetName, parentSessionId) {
21310
- if (this.depthTracker) {
21311
- const parentDepth = this.depthTracker.getDepth(parentSessionId);
21312
- if (parentDepth + 1 > this.depthTracker.maxDepth) {
21313
- log("[council-manager] spawn blocked: max depth exceeded", {
21314
- parentSessionId,
21315
- parentDepth,
21316
- maxDepth: this.depthTracker.maxDepth
21317
- });
21318
- return {
21319
- success: false,
21320
- error: "Subagent depth exceeded",
21321
- councillorResults: []
21322
- };
21323
- }
21324
- }
21325
21313
  const councilConfig = this.config?.council;
21326
21314
  if (!councilConfig) {
21327
21315
  log("[council-manager] Council configuration not found");
@@ -21413,12 +21401,6 @@ class CouncilManager {
21413
21401
  throw new Error("Failed to create session");
21414
21402
  }
21415
21403
  sessionId = session.data.id;
21416
- if (this.depthTracker) {
21417
- const registered = this.depthTracker.registerChild(options.parentSessionId, sessionId);
21418
- if (!registered) {
21419
- throw new Error("Subagent depth exceeded");
21420
- }
21421
- }
21422
21404
  if (this.tmuxEnabled) {
21423
21405
  await new Promise((r) => setTimeout(r, TMUX_SPAWN_DELAY_MS));
21424
21406
  }
@@ -21457,9 +21439,6 @@ class CouncilManager {
21457
21439
  } finally {
21458
21440
  if (sessionId) {
21459
21441
  this.client.session.abort({ path: { id: sessionId } }).catch(() => {});
21460
- if (this.depthTracker) {
21461
- this.depthTracker.cleanup(sessionId);
21462
- }
21463
21442
  }
21464
21443
  }
21465
21444
  }
@@ -25434,6 +25413,7 @@ function activationPrompt(task2) {
25434
25413
  "Deepwork requirements:",
25435
25414
  "- before planning, delegation, or creating state, inspect existing `.gitignore` and `.ignore`; add only missing entries without duplicates: `.gitignore` must contain `.slim/deepwork/`, and `.ignore` must contain `!.slim/deepwork/` and `!.slim/deepwork/**`; this keeps state git-local yet OpenCode-readable;",
25436
25415
  "- create/update a `.slim/deepwork/` progress file;",
25416
+ "- save code/doc deliverables to project paths (e.g. `src/`, `docs/`); reserve `.slim/deepwork/` strictly for progress files;",
25437
25417
  "- keep OpenCode todos synced with the current phase;",
25438
25418
  "- draft a plan and get `@oracle` review before implementation;",
25439
25419
  "- create and review a phased implementation/delegation plan;",
@@ -26421,23 +26401,21 @@ function createPhaseReminderHook(options = {}) {
26421
26401
  if (agent !== "orchestrator" || !sessionID || options.shouldInject && !options.shouldInject(sessionID)) {
26422
26402
  return;
26423
26403
  }
26424
- const textPartIndex = lastUserMessage.parts.findIndex((p) => p.type === "text" && p.text !== undefined);
26425
- if (textPartIndex === -1) {
26426
- return;
26427
- }
26428
- const originalPart = lastUserMessage.parts[textPartIndex];
26429
- if (isInternalInitiatorPart(originalPart)) {
26430
- return;
26431
- }
26432
- if (lastUserMessage.parts.some(hasPhaseReminder)) {
26433
- return;
26404
+ for (const message of messages) {
26405
+ if (!isUserMessageWithParts(message) || message.info.agent !== "orchestrator" || message.info.sessionID !== sessionID) {
26406
+ continue;
26407
+ }
26408
+ const textPart = message.parts.find((part) => part.type === "text" && part.text !== undefined);
26409
+ if (!textPart || isInternalInitiatorPart(textPart) || message.parts.some(hasPhaseReminder)) {
26410
+ continue;
26411
+ }
26412
+ message.parts.push({
26413
+ type: "text",
26414
+ synthetic: true,
26415
+ text: PHASE_REMINDER,
26416
+ metadata: { [PHASE_REMINDER_METADATA_KEY]: true }
26417
+ });
26434
26418
  }
26435
- lastUserMessage.parts.push({
26436
- type: "text",
26437
- synthetic: true,
26438
- text: PHASE_REMINDER,
26439
- metadata: { [PHASE_REMINDER_METADATA_KEY]: true }
26440
- });
26441
26419
  }
26442
26420
  };
26443
26421
  }
@@ -26611,6 +26589,13 @@ function createPendingCallTracker() {
26611
26589
  pendingCalls.delete(callId);
26612
26590
  return pending;
26613
26591
  },
26592
+ peekByParent(parentSessionId) {
26593
+ for (const call of pendingCalls.values()) {
26594
+ if (call.parentSessionId === parentSessionId)
26595
+ return call;
26596
+ }
26597
+ return;
26598
+ },
26614
26599
  clearSession(sessionId) {
26615
26600
  for (const [callId, pending] of pendingCalls.entries()) {
26616
26601
  if (pending.parentSessionId === sessionId) {
@@ -26706,7 +26691,7 @@ var BACKGROUND_COMPLETION_FAILED = /^Background task failed: /;
26706
26691
  var MAX_PROCESSED_INJECTED_COMPLETIONS = 500;
26707
26692
  var RAW_SESSION_ID_PATTERN = /^ses_[A-Za-z0-9_-]+$/;
26708
26693
  var IDLE_RECONCILE_DELAY_MS = 2000;
26709
- var idleReconcileTimers = new Map;
26694
+ var CONTINUATION_NUDGE = "Continue coordinating the remaining incomplete todos. Do not finalize while work remains.";
26710
26695
  function djb2Hash(str) {
26711
26696
  let hash = 5381;
26712
26697
  for (let i = 0;i < str.length; i++) {
@@ -26747,8 +26732,137 @@ function createTaskSessionManagerHook(_ctx, options) {
26747
26732
  const processedInjectedCompletions = new Set;
26748
26733
  const processedInjectedCompletionOrder = [];
26749
26734
  const terminalJobsInjectedByParent = new Map;
26735
+ const idleReconcileTimers = new Map;
26736
+ const continuationSessionTokens = new Map;
26737
+ const activeContinuationEvaluations = new Map;
26738
+ const continuationConsumed = new Set;
26739
+ const idleReconcileDelayMs = options.idleReconcileDelayMs ?? IDLE_RECONCILE_DELAY_MS;
26740
+ const sessionSdk = _ctx.client.session;
26741
+ function getContinuationSessionToken(sessionID) {
26742
+ const existing = continuationSessionTokens.get(sessionID);
26743
+ if (existing)
26744
+ return existing;
26745
+ const token = Symbol(sessionID);
26746
+ continuationSessionTokens.set(sessionID, token);
26747
+ return token;
26748
+ }
26749
+ function isCurrentContinuation(sessionID, sessionToken, evaluationToken) {
26750
+ return continuationSessionTokens.get(sessionID) === sessionToken && (evaluationToken === undefined || activeContinuationEvaluations.get(sessionID)?.has(evaluationToken) === true);
26751
+ }
26752
+ function invalidateContinuation(sessionID) {
26753
+ const timer = idleReconcileTimers.get(sessionID);
26754
+ if (timer) {
26755
+ clearTimeout(timer);
26756
+ idleReconcileTimers.delete(sessionID);
26757
+ }
26758
+ continuationSessionTokens.delete(sessionID);
26759
+ activeContinuationEvaluations.delete(sessionID);
26760
+ }
26761
+ function clearContinuation(sessionID) {
26762
+ invalidateContinuation(sessionID);
26763
+ continuationConsumed.delete(sessionID);
26764
+ }
26765
+ function isActiveStatus(status, sessionID) {
26766
+ return Object.hasOwn(status, sessionID);
26767
+ }
26768
+ async function evaluateContinuation(parentSessionID, sessionToken) {
26769
+ const evaluationToken = Symbol(parentSessionID);
26770
+ const activeEvaluations = activeContinuationEvaluations.get(parentSessionID) ?? new Set;
26771
+ activeEvaluations.add(evaluationToken);
26772
+ activeContinuationEvaluations.set(parentSessionID, activeEvaluations);
26773
+ if (continuationConsumed.has(parentSessionID) || !isCurrentContinuation(parentSessionID, sessionToken, evaluationToken) || options.isFallbackInProgress?.(parentSessionID) || backgroundJobBoard.hasTerminalUnreconciled(parentSessionID) || !sessionSdk?.todo || !sessionSdk.children || !sessionSdk.status || !sessionSdk.promptAsync) {
26774
+ activeEvaluations.delete(evaluationToken);
26775
+ if (activeEvaluations.size === 0) {
26776
+ activeContinuationEvaluations.delete(parentSessionID);
26777
+ }
26778
+ return;
26779
+ }
26780
+ try {
26781
+ const [todoResponse, childrenResponse, statusResponse] = await Promise.all([
26782
+ sessionSdk.todo({
26783
+ path: { id: parentSessionID },
26784
+ throwOnError: true
26785
+ }),
26786
+ sessionSdk.children({
26787
+ path: { id: parentSessionID },
26788
+ throwOnError: true
26789
+ }),
26790
+ sessionSdk.status({ throwOnError: true })
26791
+ ]);
26792
+ if (!Array.isArray(todoResponse.data) || !Array.isArray(childrenResponse.data) || !isRecord2(statusResponse.data)) {
26793
+ return;
26794
+ }
26795
+ const todos = todoResponse.data;
26796
+ const children = childrenResponse.data;
26797
+ const status = statusResponse.data;
26798
+ if (!todos.every((todo) => isRecord2(todo) && typeof todo.status === "string") || !children.every((child) => isRecord2(child) && typeof child.id === "string")) {
26799
+ return;
26800
+ }
26801
+ if (!todos.some((todo) => todo.status !== "completed" && todo.status !== "cancelled")) {
26802
+ return;
26803
+ }
26804
+ const childIDs = children.map((child) => child.id);
26805
+ if (isActiveStatus(status, parentSessionID) || childIDs.some((childID) => isActiveStatus(status, childID))) {
26806
+ return;
26807
+ }
26808
+ const [latestChildrenResponse, latestStatusResponse] = await Promise.all([
26809
+ sessionSdk.children({
26810
+ path: { id: parentSessionID },
26811
+ throwOnError: true
26812
+ }),
26813
+ sessionSdk.status({ throwOnError: true })
26814
+ ]);
26815
+ if (!Array.isArray(latestChildrenResponse.data) || !isRecord2(latestStatusResponse.data) || !latestChildrenResponse.data.every((child) => isRecord2(child) && typeof child.id === "string") || continuationConsumed.has(parentSessionID) || !isCurrentContinuation(parentSessionID, sessionToken, evaluationToken) || options.isFallbackInProgress?.(parentSessionID) || backgroundJobBoard.hasTerminalUnreconciled(parentSessionID)) {
26816
+ return;
26817
+ }
26818
+ const latestChildIDs = latestChildrenResponse.data.map((child) => child.id);
26819
+ const latestStatus = latestStatusResponse.data;
26820
+ if (isActiveStatus(latestStatus, parentSessionID) || latestChildIDs.some((childID) => isActiveStatus(latestStatus, childID))) {
26821
+ return;
26822
+ }
26823
+ if (continuationConsumed.has(parentSessionID) || !isCurrentContinuation(parentSessionID, sessionToken, evaluationToken) || options.isFallbackInProgress?.(parentSessionID) || backgroundJobBoard.hasTerminalUnreconciled(parentSessionID)) {
26824
+ return;
26825
+ }
26826
+ continuationConsumed.add(parentSessionID);
26827
+ await sessionSdk.promptAsync({
26828
+ path: { id: parentSessionID },
26829
+ body: { parts: [createInternalAgentTextPart(CONTINUATION_NUDGE)] },
26830
+ throwOnError: true
26831
+ });
26832
+ } catch (error) {
26833
+ log("[task-session-manager] continuation nudge suppressed after SDK error", {
26834
+ parentSessionID,
26835
+ error: error instanceof Error ? error.message : String(error)
26836
+ });
26837
+ } finally {
26838
+ const evaluations = activeContinuationEvaluations.get(parentSessionID);
26839
+ evaluations?.delete(evaluationToken);
26840
+ if (evaluations?.size === 0) {
26841
+ activeContinuationEvaluations.delete(parentSessionID);
26842
+ }
26843
+ }
26844
+ }
26845
+ function scheduleIdleReconciliation(parentSessionID) {
26846
+ if (idleReconcileTimers.has(parentSessionID) || options.isFallbackInProgress?.(parentSessionID)) {
26847
+ return;
26848
+ }
26849
+ const sessionToken = getContinuationSessionToken(parentSessionID);
26850
+ const timer = setTimeout(() => {
26851
+ idleReconcileTimers.delete(parentSessionID);
26852
+ if (!isCurrentContinuation(parentSessionID, sessionToken)) {
26853
+ return;
26854
+ }
26855
+ const hadTerminalUnreconciled = backgroundJobBoard.hasTerminalUnreconciled(parentSessionID);
26856
+ reconcileInjectedTerminalJobs(parentSessionID);
26857
+ if (!hadTerminalUnreconciled) {
26858
+ evaluateContinuation(parentSessionID, sessionToken);
26859
+ }
26860
+ }, idleReconcileDelayMs).unref?.();
26861
+ idleReconcileTimers.set(parentSessionID, timer);
26862
+ }
26750
26863
  if (options.coordinator) {
26751
26864
  options.coordinator.onSessionDeleted((sessionId) => {
26865
+ clearContinuation(sessionId);
26752
26866
  if (!options.isFallbackInProgress?.(sessionId)) {
26753
26867
  backgroundJobBoard.drop(sessionId);
26754
26868
  backgroundJobBoard.clearParent(sessionId);
@@ -26908,6 +27022,18 @@ function createTaskSessionManagerHook(_ctx, options) {
26908
27022
  terminalJobsInjectedByParent.delete(parentSessionID);
26909
27023
  }
26910
27024
  return {
27025
+ observeChatMessage: (input, output) => {
27026
+ const inputMessage = isRecord2(input) ? input : undefined;
27027
+ const outputRecord = isRecord2(output) ? output : undefined;
27028
+ const outputMessage = isRecord2(outputRecord?.message) ? outputRecord.message : undefined;
27029
+ const sessionID = typeof outputMessage?.sessionID === "string" ? outputMessage.sessionID : typeof inputMessage?.sessionID === "string" ? inputMessage.sessionID : undefined;
27030
+ const parts = Array.isArray(outputRecord?.parts) ? outputRecord.parts : inputMessage?.parts;
27031
+ if (!sessionID || typeof outputMessage?.role === "string" && outputMessage.role !== "user" || !options.shouldManageSession(sessionID) || !Array.isArray(parts) || !parts.some((part) => isRecord2(part) && part.type === "text" && typeof part.text === "string" && part.synthetic !== true && !isInternalInitiatorPart(part))) {
27032
+ return;
27033
+ }
27034
+ invalidateContinuation(sessionID);
27035
+ continuationConsumed.delete(sessionID);
27036
+ },
26911
27037
  "tool.execute.before": async (input, output) => {
26912
27038
  const toolName = input.tool.toLowerCase();
26913
27039
  if (toolName !== "task")
@@ -27133,6 +27259,34 @@ function createTaskSessionManagerHook(_ctx, options) {
27133
27259
  });
27134
27260
  if (info?.id && info.parentID && options.shouldManageSession(info.parentID)) {
27135
27261
  taskContextTracker.pendingManagedTaskIds.add(info.id);
27262
+ const pending = pendingCallTracker.peekByParent(info.parentID);
27263
+ if (pending && !pending.resumedTaskId && !backgroundJobBoard.get(info.id)) {
27264
+ const record = backgroundJobBoard.registerLaunch({
27265
+ taskID: info.id,
27266
+ parentSessionID: pending.parentSessionId,
27267
+ agent: pending.agentType,
27268
+ description: pending.label,
27269
+ objective: pending.label
27270
+ });
27271
+ log("[task-session-manager] early board registration from session.created", {
27272
+ taskID: record.taskID,
27273
+ alias: record.alias,
27274
+ parentSessionID: record.parentSessionID,
27275
+ agent: record.agent
27276
+ });
27277
+ }
27278
+ }
27279
+ return;
27280
+ }
27281
+ if (input.event.type === "server.instance.disposed") {
27282
+ const continuationSessionIDs = new Set([
27283
+ ...idleReconcileTimers.keys(),
27284
+ ...continuationSessionTokens.keys(),
27285
+ ...activeContinuationEvaluations.keys(),
27286
+ ...continuationConsumed
27287
+ ]);
27288
+ for (const sessionID of continuationSessionIDs) {
27289
+ clearContinuation(sessionID);
27136
27290
  }
27137
27291
  return;
27138
27292
  }
@@ -27146,11 +27300,7 @@ function createTaskSessionManagerHook(_ctx, options) {
27146
27300
  runningJobForSession: job?.state === "running" || false
27147
27301
  });
27148
27302
  if (sessionId2 && options.shouldManageSession(sessionId2)) {
27149
- const timer2 = setTimeout(() => {
27150
- idleReconcileTimers.delete(sessionId2);
27151
- reconcileInjectedTerminalJobs(sessionId2);
27152
- }, IDLE_RECONCILE_DELAY_MS).unref?.();
27153
- idleReconcileTimers.set(sessionId2, timer2);
27303
+ scheduleIdleReconciliation(sessionId2);
27154
27304
  }
27155
27305
  if (job && sessionId2 && job.state === "running" && !options.isFallbackInProgress?.(sessionId2)) {
27156
27306
  log("[task-session-manager] reconciled running job from idle", {
@@ -27172,13 +27322,8 @@ function createTaskSessionManagerHook(_ctx, options) {
27172
27322
  }
27173
27323
  if (input.event.type === "session.error") {
27174
27324
  const sessionId2 = input.event.properties?.info?.id || input.event.properties?.sessionID;
27175
- if (sessionId2) {
27176
- const timer2 = idleReconcileTimers.get(sessionId2);
27177
- if (timer2) {
27178
- clearTimeout(timer2);
27179
- idleReconcileTimers.delete(sessionId2);
27180
- }
27181
- }
27325
+ if (sessionId2)
27326
+ invalidateContinuation(sessionId2);
27182
27327
  if (sessionId2 && options.shouldManageSession(sessionId2)) {
27183
27328
  const props = input.event.properties;
27184
27329
  if (!props?.error || !isFailoverError(props.error)) {
@@ -27187,14 +27332,13 @@ function createTaskSessionManagerHook(_ctx, options) {
27187
27332
  }
27188
27333
  return;
27189
27334
  }
27190
- if (input.event.type === "session.status" && input.event.properties?.status?.type === "busy") {
27335
+ if (input.event.type === "session.status") {
27191
27336
  const sessionId2 = input.event.properties?.info?.id || input.event.properties?.sessionID;
27192
- if (sessionId2) {
27193
- const timer2 = idleReconcileTimers.get(sessionId2);
27194
- if (timer2) {
27195
- clearTimeout(timer2);
27196
- idleReconcileTimers.delete(sessionId2);
27197
- }
27337
+ const statusType = input.event.properties?.status?.type;
27338
+ if (sessionId2)
27339
+ invalidateContinuation(sessionId2);
27340
+ if (statusType !== "busy") {
27341
+ return;
27198
27342
  }
27199
27343
  const before = sessionId2 ? backgroundJobBoard.get(sessionId2) : undefined;
27200
27344
  const updated = sessionId2 ? backgroundJobBoard.markRunningFromLiveSession(sessionId2) : undefined;
@@ -27225,11 +27369,7 @@ function createTaskSessionManagerHook(_ctx, options) {
27225
27369
  const sessionId = input.event.properties?.info?.id || input.event.properties?.sessionID;
27226
27370
  if (!sessionId)
27227
27371
  return;
27228
- const timer = idleReconcileTimers.get(sessionId);
27229
- if (timer) {
27230
- clearTimeout(timer);
27231
- idleReconcileTimers.delete(sessionId);
27232
- }
27372
+ clearContinuation(sessionId);
27233
27373
  log("[task-session-manager] session.deleted observed", {
27234
27374
  sessionID: sessionId
27235
27375
  });
@@ -27275,6 +27415,15 @@ function formatCancelledTaskStatusOutput(taskID, summary = "cancelled") {
27275
27415
  ].join(`
27276
27416
  `);
27277
27417
  }
27418
+ // src/index-event.ts
27419
+ async function handleTaskSessionEvent(input, invalidateTaskSessions, handleMultiplexerEvent, cleanupInstance) {
27420
+ await invalidateTaskSessions(input);
27421
+ await handleMultiplexerEvent();
27422
+ if (input.event.type === "server.instance.disposed") {
27423
+ await cleanupInstance();
27424
+ }
27425
+ }
27426
+
27278
27427
  // src/interview/dashboard.ts
27279
27428
  import crypto2 from "node:crypto";
27280
27429
  import * as fsSync2 from "node:fs";
@@ -32317,6 +32466,42 @@ function buildOpencodeAttachCommand(sessionId, serverUrl, directory, executable
32317
32466
  quoteShellArg(attachDir)
32318
32467
  ].join(" ");
32319
32468
  }
32469
+ function resolveOpencodeExecutable() {
32470
+ return resolveHostOpencodeBinary() ?? "opencode";
32471
+ }
32472
+ function buildShellLaunchArgs(command) {
32473
+ const shell = process.env.SHELL || "/bin/sh";
32474
+ const name = (shell.split(/[/\\]/).at(-1) ?? "sh").replace(/\.(exe|EXE)$/, "");
32475
+ if (name === "nu" || name === "fish") {
32476
+ return [shell, "-c", command];
32477
+ }
32478
+ if (name === "zsh") {
32479
+ return [
32480
+ shell,
32481
+ "-l",
32482
+ "-c",
32483
+ `[[ -f "\${ZDOTDIR:-$HOME}/.zshrc" ]] && source "\${ZDOTDIR:-$HOME}/.zshrc" >/dev/null 2>&1
32484
+ ${command}`
32485
+ ];
32486
+ }
32487
+ if (name === "bash") {
32488
+ return [
32489
+ shell,
32490
+ "-l",
32491
+ "-c",
32492
+ `shopt -s expand_aliases
32493
+ [[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true
32494
+ ${command}`
32495
+ ];
32496
+ }
32497
+ if (name === "cmd") {
32498
+ return [shell, "/c", command];
32499
+ }
32500
+ if (name === "pwsh" || name === "powershell") {
32501
+ return [shell, "-NoProfile", "-Command", command];
32502
+ }
32503
+ return [shell, "-c", command];
32504
+ }
32320
32505
  function resolveHostOpencodeBinary(options = {}) {
32321
32506
  const pathExists = options.pathExists ?? existsSync9;
32322
32507
  for (const candidate of [
@@ -32393,13 +32578,15 @@ async function gracefulClosePane(binary, paneId, options) {
32393
32578
  try {
32394
32579
  const ctrlCProc = crossSpawn([binary, ...options.ctrlC], {
32395
32580
  stdout: "ignore",
32396
- stderr: "ignore"
32581
+ stderr: "ignore",
32582
+ env: options.env
32397
32583
  });
32398
32584
  await ctrlCProc.exited;
32399
32585
  await new Promise((r) => setTimeout(r, GRACEFUL_SHUTDOWN_DELAY_MS));
32400
32586
  const proc = crossSpawn([binary, ...options.close], {
32401
32587
  stdout: "ignore",
32402
- stderr: "ignore"
32588
+ stderr: "ignore",
32589
+ env: options.env
32403
32590
  });
32404
32591
  const exitCode = await proc.exited;
32405
32592
  if (exitCode === 0)
@@ -33130,6 +33317,189 @@ function getPaneDirection(layout) {
33130
33317
  }
33131
33318
  }
33132
33319
 
33320
+ // src/multiplexer/kitty/index.ts
33321
+ init_compat();
33322
+ class KittyMultiplexer {
33323
+ type = "kitty";
33324
+ binaryPath = null;
33325
+ hasChecked = false;
33326
+ storedLayout;
33327
+ appliedLayout = null;
33328
+ constructor(layout = "main-vertical", mainPaneSize = 60) {
33329
+ this.storedLayout = layout;
33330
+ }
33331
+ async isAvailable() {
33332
+ if (this.hasChecked) {
33333
+ return this.binaryPath !== null;
33334
+ }
33335
+ this.binaryPath = await findBinary("kitten") ?? await findBinary("kitty");
33336
+ this.hasChecked = true;
33337
+ return this.binaryPath !== null;
33338
+ }
33339
+ isInsideSession() {
33340
+ return !!process.env.KITTY_PID || !!process.env.KITTY_WINDOW_ID;
33341
+ }
33342
+ hasListenOn() {
33343
+ return !!process.env.KITTY_LISTEN_ON;
33344
+ }
33345
+ async spawnPane(sessionId, description, serverUrl, directory) {
33346
+ const kitten = await this.getBinary();
33347
+ if (!kitten) {
33348
+ log("[kitty] spawnPane: kitten/kitty binary not found");
33349
+ return { success: false };
33350
+ }
33351
+ if (!this.isInsideSession()) {
33352
+ log("[kitty] spawnPane: OpenCode is not running inside a kitty session; " + "set multiplexer.type to a different backend or run OpenCode inside kitty");
33353
+ return { success: false };
33354
+ }
33355
+ if (!this.hasListenOn()) {
33356
+ log("[kitty] spawnPane: KITTY_LISTEN_ON is not set. Add `listen_on " + "unix:/tmp/kitty-rc-$(USER)` to kitty.conf and restart kitty so the " + "plugin can drive kitty via the socket.");
33357
+ return { success: false };
33358
+ }
33359
+ const { kittyLayout, location } = getKittyLayoutConfig(this.storedLayout);
33360
+ await this.ensureLayout(kittyLayout);
33361
+ try {
33362
+ const opencodeCmd = buildOpencodeAttachCommand(sessionId, serverUrl, directory, resolveOpencodeExecutable());
33363
+ const attachDir = normalizePathForShell(directory);
33364
+ const shellArgs = buildShellLaunchArgs(opencodeCmd);
33365
+ const args = [
33366
+ "@",
33367
+ "launch",
33368
+ "--type=window",
33369
+ `--location=${location}`,
33370
+ `--title=${description.slice(0, 60)}`,
33371
+ `--cwd=${attachDir}`,
33372
+ "--keep-focus",
33373
+ "--",
33374
+ ...shellArgs
33375
+ ];
33376
+ log("[kitty] spawnPane: executing", { kitten, args });
33377
+ const proc = crossSpawn([kitten, ...args], {
33378
+ stdout: "pipe",
33379
+ stderr: "pipe",
33380
+ env: this.kittyEnv()
33381
+ });
33382
+ const exitCode = await proc.exited;
33383
+ const stdout = await proc.stdout();
33384
+ const stderr = await proc.stderr();
33385
+ const lines = stdout.trim().split(`
33386
+ `).map((l) => l.trim()).filter(Boolean);
33387
+ const windowId = lines.length > 0 ? lines[lines.length - 1] : "";
33388
+ log("[kitty] spawnPane: result", {
33389
+ exitCode,
33390
+ windowId,
33391
+ stderr: stderr.trim()
33392
+ });
33393
+ if (exitCode === 0 && windowId) {
33394
+ log("[kitty] spawnPane: SUCCESS", { windowId });
33395
+ return { success: true, paneId: windowId };
33396
+ }
33397
+ return { success: false };
33398
+ } catch (err) {
33399
+ log("[kitty] spawnPane: exception", { error: String(err) });
33400
+ return { success: false };
33401
+ }
33402
+ }
33403
+ async closePane(paneId) {
33404
+ if (!this.isInsideSession()) {
33405
+ log("[kitty] closePane: OpenCode is not running inside a kitty session; " + "cannot target a kitty instance");
33406
+ return false;
33407
+ }
33408
+ if (!this.hasListenOn()) {
33409
+ log("[kitty] closePane: KITTY_LISTEN_ON is not set; cannot target kitty. " + "Add `listen_on` to kitty.conf and restart kitty.");
33410
+ return false;
33411
+ }
33412
+ const kitten = await this.getBinary();
33413
+ return await gracefulClosePane(kitten, paneId, {
33414
+ ctrlC: ["@", "send-key", "--match", `id:${paneId}`, "ctrl+c"],
33415
+ close: ["@", "close-window", "--match", `id:${paneId}`],
33416
+ acceptExitCode1: true,
33417
+ emptyPaneReturnsTrue: true,
33418
+ env: this.kittyEnv()
33419
+ });
33420
+ }
33421
+ async applyLayout(layout, mainPaneSize) {
33422
+ if (!this.isInsideSession()) {
33423
+ log("[kitty] applyLayout: OpenCode is not running inside a kitty session; " + "cannot target a kitty instance");
33424
+ return;
33425
+ }
33426
+ if (!this.hasListenOn()) {
33427
+ log("[kitty] applyLayout: KITTY_LISTEN_ON is not set; cannot target kitty. " + "Add `listen_on` to kitty.conf and restart kitty.");
33428
+ return;
33429
+ }
33430
+ this.storedLayout = layout;
33431
+ const { kittyLayout } = getKittyLayoutConfig(layout);
33432
+ await this.ensureLayout(kittyLayout);
33433
+ }
33434
+ async runKitty(kitten, args) {
33435
+ const proc = crossSpawn([kitten, ...args], {
33436
+ stdout: "pipe",
33437
+ stderr: "pipe",
33438
+ env: this.kittyEnv()
33439
+ });
33440
+ const [exitCode, , stderr] = await Promise.all([
33441
+ proc.exited,
33442
+ proc.stdout(),
33443
+ proc.stderr()
33444
+ ]);
33445
+ if (exitCode !== 0) {
33446
+ log("[kitty] command failed", {
33447
+ command: args[1],
33448
+ args: [kitten, ...args],
33449
+ exitCode,
33450
+ stderr: stderr.trim()
33451
+ });
33452
+ }
33453
+ return exitCode;
33454
+ }
33455
+ async ensureLayout(kittyLayout) {
33456
+ if (this.appliedLayout === kittyLayout)
33457
+ return;
33458
+ const kitten = await this.getBinary();
33459
+ if (!kitten)
33460
+ return;
33461
+ try {
33462
+ const exitCode = await this.runKitty(kitten, [
33463
+ "@",
33464
+ "goto-layout",
33465
+ kittyLayout
33466
+ ]);
33467
+ if (exitCode === 0)
33468
+ this.appliedLayout = kittyLayout;
33469
+ } catch (err) {
33470
+ log("[kitty] ensureLayout: exception", { error: String(err) });
33471
+ }
33472
+ }
33473
+ async getBinary() {
33474
+ await this.isAvailable();
33475
+ return this.binaryPath;
33476
+ }
33477
+ kittyEnv() {
33478
+ const listenOn = process.env.KITTY_LISTEN_ON;
33479
+ if (!listenOn)
33480
+ return;
33481
+ return { ...process.env, KITTY_LISTEN_ON: listenOn };
33482
+ }
33483
+ }
33484
+ function getKittyLayoutConfig(layout) {
33485
+ switch (layout) {
33486
+ case "main-vertical":
33487
+ return { kittyLayout: "tall", location: "after" };
33488
+ case "main-horizontal":
33489
+ return { kittyLayout: "fat", location: "after" };
33490
+ case "tiled":
33491
+ return { kittyLayout: "grid", location: "after" };
33492
+ case "even-horizontal":
33493
+ return { kittyLayout: "horizontal", location: "after" };
33494
+ case "even-vertical":
33495
+ return { kittyLayout: "vertical", location: "after" };
33496
+ default: {
33497
+ const _exhaustive = layout;
33498
+ return _exhaustive;
33499
+ }
33500
+ }
33501
+ }
33502
+
33133
33503
  // src/multiplexer/tmux/index.ts
33134
33504
  init_compat();
33135
33505
  var TMUX_LAYOUT_DEBOUNCE_MS = 150;
@@ -33677,6 +34047,10 @@ function getMultiplexer(config) {
33677
34047
  multiplexer = new CmuxMultiplexer;
33678
34048
  actualType = "cmux";
33679
34049
  break;
34050
+ case "kitty":
34051
+ multiplexer = new KittyMultiplexer(config.layout, config.main_pane_size);
34052
+ actualType = "kitty";
34053
+ break;
33680
34054
  case "auto": {
33681
34055
  if (process.env.CMUX_SOCKET_PATH && process.env.CMUX_WORKSPACE_ID && process.env.CMUX_SURFACE_ID) {
33682
34056
  multiplexer = new CmuxMultiplexer;
@@ -33690,6 +34064,9 @@ function getMultiplexer(config) {
33690
34064
  } else if (process.env.HERDR_ENV || process.env.HERDR_PANE_ID) {
33691
34065
  multiplexer = new HerdrMultiplexer(config.layout, config.main_pane_size);
33692
34066
  actualType = "herdr";
34067
+ } else if (process.env.KITTY_PID || process.env.KITTY_WINDOW_ID) {
34068
+ multiplexer = new KittyMultiplexer(config.layout, config.main_pane_size);
34069
+ actualType = "kitty";
33693
34070
  } else {
33694
34071
  log("[multiplexer] auto: not inside any session, disabling");
33695
34072
  return null;
@@ -38834,47 +39211,6 @@ function isPluginDisabledByEnv(env = process.env) {
38834
39211
  return isTruthyEnvValue(env[PLUGIN_DISABLE_ENV]);
38835
39212
  }
38836
39213
 
38837
- // src/utils/subagent-depth.ts
38838
- class SubagentDepthTracker {
38839
- depthBySession = new Map;
38840
- _maxDepth;
38841
- constructor(maxDepth = DEFAULT_MAX_SUBAGENT_DEPTH) {
38842
- this._maxDepth = maxDepth;
38843
- }
38844
- get maxDepth() {
38845
- return this._maxDepth;
38846
- }
38847
- getDepth(sessionId) {
38848
- return this.depthBySession.get(sessionId) ?? 0;
38849
- }
38850
- registerChild(parentSessionId, childSessionId) {
38851
- const parentDepth = this.getDepth(parentSessionId);
38852
- const childDepth = parentDepth + 1;
38853
- if (childDepth > this.maxDepth) {
38854
- log("[subagent-depth] spawn blocked: max depth exceeded", {
38855
- parentSessionId,
38856
- parentDepth,
38857
- childDepth,
38858
- maxDepth: this.maxDepth
38859
- });
38860
- return false;
38861
- }
38862
- this.depthBySession.set(childSessionId, childDepth);
38863
- log("[subagent-depth] child registered", {
38864
- parentSessionId,
38865
- childSessionId,
38866
- childDepth
38867
- });
38868
- return true;
38869
- }
38870
- cleanup(sessionId) {
38871
- this.depthBySession.delete(sessionId);
38872
- }
38873
- cleanupAll() {
38874
- this.depthBySession.clear();
38875
- }
38876
- }
38877
-
38878
39214
  // src/utils/system-collapse.ts
38879
39215
  function collapseSystemInPlace(system2) {
38880
39216
  if (system2.length === 0) {
@@ -38938,7 +39274,6 @@ var OhMyOpenCodeLite = async (ctx) => {
38938
39274
  let runtimeChains;
38939
39275
  let multiplexerConfig;
38940
39276
  let multiplexerEnabled;
38941
- let depthTracker;
38942
39277
  let multiplexerSessionManager;
38943
39278
  let autoUpdateChecker;
38944
39279
  let sessionAgentMap;
@@ -39010,8 +39345,7 @@ var OhMyOpenCodeLite = async (ctx) => {
39010
39345
  if (multiplexerEnabled) {
39011
39346
  startAvailabilityCheck(multiplexerConfig);
39012
39347
  }
39013
- depthTracker = new SubagentDepthTracker;
39014
- councilTools = config.council ? createCouncilTool(ctx, new CouncilManager(ctx, config, depthTracker, multiplexerEnabled)) : {};
39348
+ councilTools = config.council ? createCouncilTool(ctx, new CouncilManager(ctx, config, multiplexerEnabled)) : {};
39015
39349
  mcps = createBuiltinMcps(config.disabled_mcps, config.websearch);
39016
39350
  acpRunTools = Object.keys(config.acpAgents ?? {}).length > 0 ? { acp_run: createAcpRunTool(config.acpAgents) } : {};
39017
39351
  webfetch = createWebfetchTool(ctx);
@@ -39383,27 +39717,22 @@ var OhMyOpenCodeLite = async (ctx) => {
39383
39717
  }
39384
39718
  }
39385
39719
  if (event.type === "session.created") {
39386
- const childSessionId = event.properties?.info?.id;
39387
- const parentSessionId = event.properties?.info?.parentID;
39388
- if (depthTracker && childSessionId && parentSessionId) {
39389
- depthTracker.registerChild(parentSessionId, childSessionId);
39390
- }
39391
39720
  const createdSessionId = event.properties?.info?.id;
39392
39721
  const createdSessionDir = event.properties?.info?.directory;
39393
39722
  if (createdSessionId && createdSessionDir) {
39394
39723
  sessionDirectories.set(createdSessionId, createdSessionDir);
39395
39724
  }
39396
39725
  }
39397
- await multiplexerSessionManager.onSessionCreated(event);
39398
- await multiplexerSessionManager.onSessionStatus(event);
39399
- await multiplexerSessionManager.onSessionDeleted(event);
39400
- if (event.type === "server.instance.disposed") {
39726
+ await handleTaskSessionEvent(input, taskSessionManagerHook.event, async () => {
39727
+ await multiplexerSessionManager.onSessionCreated(event);
39728
+ await multiplexerSessionManager.onSessionStatus(event);
39729
+ await multiplexerSessionManager.onSessionDeleted(event);
39730
+ }, async () => {
39401
39731
  await multiplexerSessionManager.cleanupOnInstanceDisposed();
39402
- }
39732
+ });
39403
39733
  await foregroundFallback.handleEvent(input.event);
39404
39734
  await autoUpdateChecker.event(input);
39405
39735
  await interviewManager.handleEvent(input);
39406
- await taskSessionManagerHook.event(input);
39407
39736
  if (event.type === "permission.asked" || event.type === "question.asked") {
39408
39737
  companionManager.onWaitingInput();
39409
39738
  }
@@ -39426,9 +39755,6 @@ var OhMyOpenCodeLite = async (ctx) => {
39426
39755
  sessionLifecycle.dispatchSessionDeleted(sessionID);
39427
39756
  }
39428
39757
  companionManager.onSessionDeleted(sessionID);
39429
- if (depthTracker && sessionID) {
39430
- depthTracker.cleanup(sessionID);
39431
- }
39432
39758
  if (sessionID) {
39433
39759
  sessionAgentMap.delete(sessionID);
39434
39760
  sessionDirectories.delete(sessionID);
@@ -39462,6 +39788,7 @@ var OhMyOpenCodeLite = async (ctx) => {
39462
39788
  status: "busy"
39463
39789
  });
39464
39790
  }
39791
+ taskSessionManagerHook.observeChatMessage(input, output);
39465
39792
  },
39466
39793
  "experimental.chat.system.transform": async (input, output) => {
39467
39794
  const agentName = input.sessionID ? sessionAgentMap.get(input.sessionID) : undefined;