@code-yeongyu/senpi 2026.8.6 → 2026.8.9

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 (154) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/core/agent-session.d.ts +10 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +71 -50
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/extensions/builtin/bash-timeout/index.d.ts +2 -2
  7. package/dist/core/extensions/builtin/bash-timeout/index.d.ts.map +1 -1
  8. package/dist/core/extensions/builtin/bash-timeout/index.js +17 -20
  9. package/dist/core/extensions/builtin/bash-timeout/index.js.map +1 -1
  10. package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts +7 -12
  11. package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts.map +1 -1
  12. package/dist/core/extensions/builtin/bash-timeout/timeout.js +11 -19
  13. package/dist/core/extensions/builtin/bash-timeout/timeout.js.map +1 -1
  14. package/dist/core/extensions/builtin/goal/cache-warm.d.ts +2 -0
  15. package/dist/core/extensions/builtin/goal/cache-warm.d.ts.map +1 -1
  16. package/dist/core/extensions/builtin/goal/cache-warm.js.map +1 -1
  17. package/dist/core/extensions/builtin/goal/monitor-continuation-types.d.ts +1 -0
  18. package/dist/core/extensions/builtin/goal/monitor-continuation-types.d.ts.map +1 -1
  19. package/dist/core/extensions/builtin/goal/monitor-continuation-types.js.map +1 -1
  20. package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
  21. package/dist/core/extensions/builtin/goal/monitor-continuation.js +76 -35
  22. package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
  23. package/dist/core/extensions/builtin/goal/prompt.d.ts +1 -1
  24. package/dist/core/extensions/builtin/goal/prompt.d.ts.map +1 -1
  25. package/dist/core/extensions/builtin/goal/prompt.js +21 -7
  26. package/dist/core/extensions/builtin/goal/prompt.js.map +1 -1
  27. package/dist/core/extensions/builtin/goal/wait-progress.d.ts +3 -1
  28. package/dist/core/extensions/builtin/goal/wait-progress.d.ts.map +1 -1
  29. package/dist/core/extensions/builtin/goal/wait-progress.js +27 -3
  30. package/dist/core/extensions/builtin/goal/wait-progress.js.map +1 -1
  31. package/dist/core/extensions/builtin/goal/wait-ticker.d.ts +4 -3
  32. package/dist/core/extensions/builtin/goal/wait-ticker.d.ts.map +1 -1
  33. package/dist/core/extensions/builtin/goal/wait-ticker.js +6 -6
  34. package/dist/core/extensions/builtin/goal/wait-ticker.js.map +1 -1
  35. package/dist/core/extensions/builtin/import-repro.d.ts.map +1 -1
  36. package/dist/core/extensions/builtin/import-repro.js +9 -0
  37. package/dist/core/extensions/builtin/import-repro.js.map +1 -1
  38. package/dist/core/extensions/builtin/model-fallback/settings.d.ts.map +1 -1
  39. package/dist/core/extensions/builtin/model-fallback/settings.js +21 -1
  40. package/dist/core/extensions/builtin/model-fallback/settings.js.map +1 -1
  41. package/dist/core/extensions/builtin/resumption-channel-event.d.ts +13 -0
  42. package/dist/core/extensions/builtin/resumption-channel-event.d.ts.map +1 -0
  43. package/dist/core/extensions/builtin/resumption-channel-event.js +13 -0
  44. package/dist/core/extensions/builtin/resumption-channel-event.js.map +1 -0
  45. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  46. package/dist/core/extensions/builtin/terminal/extension.js +20 -0
  47. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  48. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts +5 -1
  49. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
  50. package/dist/core/extensions/builtin/terminal/session-bundle.js +19 -2
  51. package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
  52. package/dist/core/extensions/builtin/terminal/tools/bash.d.ts.map +1 -1
  53. package/dist/core/extensions/builtin/terminal/tools/bash.js +24 -10
  54. package/dist/core/extensions/builtin/terminal/tools/bash.js.map +1 -1
  55. package/dist/core/extensions/builtin/terminal/tools/context.d.ts +2 -0
  56. package/dist/core/extensions/builtin/terminal/tools/context.d.ts.map +1 -1
  57. package/dist/core/extensions/builtin/terminal/tools/context.js.map +1 -1
  58. package/dist/core/extensions/builtin/terminal/tools/foreground-window.d.ts +11 -0
  59. package/dist/core/extensions/builtin/terminal/tools/foreground-window.d.ts.map +1 -0
  60. package/dist/core/extensions/builtin/terminal/tools/foreground-window.js +16 -0
  61. package/dist/core/extensions/builtin/terminal/tools/foreground-window.js.map +1 -0
  62. package/dist/core/extensions/builtin/terminal/tools/sleep-wait.d.ts +28 -0
  63. package/dist/core/extensions/builtin/terminal/tools/sleep-wait.d.ts.map +1 -0
  64. package/dist/core/extensions/builtin/terminal/tools/sleep-wait.js +75 -0
  65. package/dist/core/extensions/builtin/terminal/tools/sleep-wait.js.map +1 -0
  66. package/dist/core/extensions/builtin/tps.d.ts.map +1 -1
  67. package/dist/core/extensions/builtin/tps.js +3 -3
  68. package/dist/core/extensions/builtin/tps.js.map +1 -1
  69. package/dist/core/retry-fallback/chains.d.ts +13 -1
  70. package/dist/core/retry-fallback/chains.d.ts.map +1 -1
  71. package/dist/core/retry-fallback/chains.js +80 -8
  72. package/dist/core/retry-fallback/chains.js.map +1 -1
  73. package/dist/core/retry-fallback/controller.d.ts +2 -0
  74. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  75. package/dist/core/retry-fallback/controller.js.map +1 -1
  76. package/dist/core/retry-fallback/expansion.d.ts +46 -0
  77. package/dist/core/retry-fallback/expansion.d.ts.map +1 -0
  78. package/dist/core/retry-fallback/expansion.js +113 -0
  79. package/dist/core/retry-fallback/expansion.js.map +1 -0
  80. package/dist/core/retry-fallback/hint-policy.d.ts +14 -0
  81. package/dist/core/retry-fallback/hint-policy.d.ts.map +1 -1
  82. package/dist/core/retry-fallback/hint-policy.js +14 -0
  83. package/dist/core/retry-fallback/hint-policy.js.map +1 -1
  84. package/dist/core/retry-fallback/settings.d.ts +6 -0
  85. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  86. package/dist/core/retry-fallback/settings.js +12 -10
  87. package/dist/core/retry-fallback/settings.js.map +1 -1
  88. package/dist/core/retry-fallback/validate.d.ts.map +1 -1
  89. package/dist/core/retry-fallback/validate.js +14 -1
  90. package/dist/core/retry-fallback/validate.js.map +1 -1
  91. package/dist/main.d.ts.map +1 -1
  92. package/dist/main.js +18 -3
  93. package/dist/main.js.map +1 -1
  94. package/dist/modes/interactive/components/favorite-models-selector.d.ts +4 -0
  95. package/dist/modes/interactive/components/favorite-models-selector.d.ts.map +1 -1
  96. package/dist/modes/interactive/components/favorite-models-selector.js +23 -7
  97. package/dist/modes/interactive/components/favorite-models-selector.js.map +1 -1
  98. package/dist/modes/interactive/components/model-selector.d.ts +1 -0
  99. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  100. package/dist/modes/interactive/components/model-selector.js +11 -7
  101. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  102. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  103. package/dist/modes/interactive/interactive-mode.js +25 -6
  104. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  105. package/dist/modes/interactive/model-search-rank.d.ts +13 -0
  106. package/dist/modes/interactive/model-search-rank.d.ts.map +1 -0
  107. package/dist/modes/interactive/model-search-rank.js +200 -0
  108. package/dist/modes/interactive/model-search-rank.js.map +1 -0
  109. package/dist/modes/interactive/model-search.d.ts +0 -5
  110. package/dist/modes/interactive/model-search.d.ts.map +1 -1
  111. package/dist/modes/interactive/model-search.js +0 -9
  112. package/dist/modes/interactive/model-search.js.map +1 -1
  113. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  114. package/dist/modes/rpc/session-command-router.js +3 -2
  115. package/dist/modes/rpc/session-command-router.js.map +1 -1
  116. package/dist/modes/rpc/session-registry.d.ts +1 -1
  117. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  118. package/dist/modes/rpc/session-registry.js +3 -3
  119. package/dist/modes/rpc/session-registry.js.map +1 -1
  120. package/docs/settings.md +1 -1
  121. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +36 -0
  122. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  123. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/resumption-channel.ts +29 -0
  124. package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +10 -0
  125. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +26 -1
  126. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +2 -1
  127. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.d.ts.map +1 -1
  128. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js +4 -7
  129. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js.map +1 -1
  130. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  131. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  132. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  133. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  134. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  135. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  136. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  137. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  138. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  139. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  140. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  141. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  142. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  143. package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.d.ts +4 -0
  144. package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.d.ts.map +1 -0
  145. package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.js +8 -0
  146. package/node_modules/@earendil-works/pi-ai/dist/utils/visible-text.js.map +1 -0
  147. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  148. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.d.ts +12 -0
  149. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.d.ts.map +1 -1
  150. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.js +34 -2
  151. package/node_modules/@earendil-works/pi-pty/dist/pipe-fallback.js.map +1 -1
  152. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  153. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  154. package/package.json +6 -6
@@ -1 +1 @@
1
- {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/terminal/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Static, Type } from \"typebox\";\nimport type { AgentToolUpdateCallback } from \"../../../types.ts\";\nimport { formatTerminalToolOutput } from \"../output-format.ts\";\nimport type { TerminalRuntimeSession } from \"../runtime-session.ts\";\nimport {\n\tBACKGROUND_START_GRACE_MS,\n\tDEFAULT_COLS,\n\tDEFAULT_ROWS,\n\tFOREGROUND_ENV_OVERRIDES,\n\tKILLED_SESSION_EXIT_GRACE_MS,\n\tTERMINAL_BASH_TOOL,\n} from \"../shared.ts\";\nimport { errorResult, type TerminalToolContext, type TerminalToolResult, textResult } from \"./context.ts\";\nimport { createForegroundDetachGate } from \"./foreground-detach.ts\";\nimport { describeExit, spawnCommandSession } from \"./spawn.ts\";\n\nexport const ptyBashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Shell command to execute in a PTY-backed session.\" }),\n\ttimeout: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription:\n\t\t\t\t\"Foreground kill deadline in seconds. Ignored for run_in_background sessions (they live until exit or kill_bash).\",\n\t\t}),\n\t),\n\tdescription: Type.Optional(Type.String({ description: \"Short human-readable label for this command.\" })),\n\trun_in_background: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Run as a persistent background session. Returns a bash_id immediately; query it with bash_output, steer with bash_input, tear down with kill_bash.\",\n\t\t}),\n\t),\n\tcols: Type.Optional(Type.Number({ description: \"PTY width in columns (default 120).\" })),\n\trows: Type.Optional(Type.Number({ description: \"PTY height in rows (default 40).\" })),\n});\n\nexport type PtyBashInput = Static<typeof ptyBashSchema>;\n\nfunction resolveDimension(value: number | undefined, fallback: number): number {\n\tif (value === undefined || !Number.isFinite(value) || value < 1) return fallback;\n\treturn Math.trunc(value);\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nconst MAX_TIMEOUT_MS = 2_147_483_647;\nconst BASH_UPDATE_THROTTLE_MS = 100;\n\nexport interface ThrottledEmitter {\n\tschedule(): void;\n\tflush(): void;\n\tdispose(): void;\n}\n\n/**\n * Emit immediately, then coalesce subsequent schedules into one trailing update.\n * `dispose` intentionally discards a pending update so callers can stop cleanly.\n */\nexport function createThrottledEmitter(emit: () => void, throttleMs = BASH_UPDATE_THROTTLE_MS): ThrottledEmitter {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet dirty = false;\n\tlet lastEmissionAt: number | undefined;\n\n\tconst emitIfDirty = () => {\n\t\tif (!dirty) return;\n\t\tdirty = false;\n\t\tlastEmissionAt = Date.now();\n\t\temit();\n\t};\n\n\tconst clearTimer = () => {\n\t\tif (timer !== undefined) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t}\n\t};\n\n\treturn {\n\t\tschedule() {\n\t\t\tdirty = true;\n\t\t\tif (timer !== undefined) return;\n\t\t\tconst elapsed = lastEmissionAt === undefined ? throttleMs : Date.now() - lastEmissionAt;\n\t\t\tif (elapsed >= throttleMs) {\n\t\t\t\temitIfDirty();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\ttimer = undefined;\n\t\t\t\temitIfDirty();\n\t\t\t}, throttleMs - elapsed);\n\t\t},\n\t\tflush() {\n\t\t\tclearTimer();\n\t\t\temitIfDirty();\n\t\t},\n\t\tdispose() {\n\t\t\tclearTimer();\n\t\t\tdirty = false;\n\t\t\tlastEmissionAt = undefined;\n\t\t},\n\t};\n}\n\ntype ForegroundWaitOutcome = \"exited\" | \"abort_grace\" | \"timeout_grace\";\n\ntype SessionEnvSource = {\n\tsessionManager?: { getSessionId: () => string; getSessionFile: () => string | undefined };\n\tmodel?: { provider: string; id: string };\n\tthinkingLevel?: string;\n\tcwd?: string;\n};\n\nfunction sessionEnvOverrides(ctx: TerminalToolContext, execCtx?: SessionEnvSource): Record<string, string> {\n\tconst session = execCtx ?? ctx.getSessionContext?.();\n\tif (!session?.sessionManager) return {};\n\tconst env: Record<string, string> = {};\n\tenv.PI_SESSION_ID = session.sessionManager.getSessionId();\n\tconst sessionFile = session.sessionManager.getSessionFile();\n\tif (sessionFile) env.PI_SESSION_FILE = sessionFile;\n\tif (session.model) {\n\t\tenv.PI_PROVIDER = session.model.provider;\n\t\tenv.PI_MODEL = session.model.id;\n\t}\n\tif (session.thinkingLevel) env.PI_REASONING_LEVEL = session.thinkingLevel;\n\treturn env;\n}\n\n/**\n * Wait for the session's exit to settle, but stop waiting a bounded grace after\n * the session has been killed. The native wait joins the PTY reader thread,\n * which blocks while any surviving descendant holds the PTY open — without the\n * grace, an aborted or timed-out command can pin the agent forever.\n */\nfunction raceExitWithKillGrace(\n\truntime: TerminalRuntimeSession,\n\tsignal: AbortSignal | undefined,\n\ttimeoutMs: number | undefined,\n): Promise<ForegroundWaitOutcome> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet settled = false;\n\t\tlet abortGraceTimer: ReturnType<typeof setTimeout> | undefined;\n\t\tlet timeoutGraceTimer: ReturnType<typeof setTimeout> | undefined;\n\t\tlet armAbortGrace: (() => void) | undefined;\n\t\tconst settle = (finish: () => void) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (abortGraceTimer) clearTimeout(abortGraceTimer);\n\t\t\tif (timeoutGraceTimer) clearTimeout(timeoutGraceTimer);\n\t\t\tif (signal && armAbortGrace) signal.removeEventListener(\"abort\", armAbortGrace);\n\t\t\tfinish();\n\t\t};\n\t\truntime.session.waitExit().then(\n\t\t\t() => settle(() => resolve(\"exited\")),\n\t\t\t(error: unknown) => settle(() => reject(error instanceof Error ? error : new Error(String(error)))),\n\t\t);\n\t\tif (timeoutMs !== undefined) {\n\t\t\tconst timeoutGraceDeadlineMs = timeoutMs + KILLED_SESSION_EXIT_GRACE_MS;\n\t\t\t// Beyond the 32-bit setTimeout range the deadline cannot be represented;\n\t\t\t// keep the unbounded wait rather than firing a false early timeout.\n\t\t\tif (timeoutGraceDeadlineMs <= MAX_TIMEOUT_MS) {\n\t\t\t\ttimeoutGraceTimer = setTimeout(() => settle(() => resolve(\"timeout_grace\")), timeoutGraceDeadlineMs);\n\t\t\t}\n\t\t}\n\t\tif (signal) {\n\t\t\tarmAbortGrace = () => {\n\t\t\t\tabortGraceTimer ??= setTimeout(() => settle(() => resolve(\"abort_grace\")), KILLED_SESSION_EXIT_GRACE_MS);\n\t\t\t};\n\t\t\tif (signal.aborted) armAbortGrace();\n\t\t\telse signal.addEventListener(\"abort\", armAbortGrace, { once: true });\n\t\t}\n\t});\n}\n\nfunction resolveAutoDetachDelayMs(ctx: TerminalToolContext, input: PtyBashInput): number | undefined {\n\tif (ctx.timeoutAction === \"kill\") return undefined;\n\tconst budgetSeconds = ctx.getSessionContext?.()?.getPromptCacheSafeWaitSeconds?.();\n\tif (budgetSeconds === undefined || !Number.isFinite(budgetSeconds)) return undefined;\n\tconst timeoutSeconds = input.timeout !== undefined && Number.isFinite(input.timeout) ? input.timeout : Infinity;\n\tif (timeoutSeconds <= budgetSeconds) return undefined;\n\treturn Math.max(0, Math.trunc(budgetSeconds * 1000));\n}\n\nfunction scheduleDetachedSweep(\n\tctx: TerminalToolContext,\n\tid: string,\n\truntime: TerminalRuntimeSession,\n\ttimeoutMs: number | undefined,\n\tstartedAt: number,\n): void {\n\tif (timeoutMs === undefined) return;\n\tconst deadlineMs = timeoutMs + KILLED_SESSION_EXIT_GRACE_MS;\n\tif (!Number.isFinite(deadlineMs) || deadlineMs > MAX_TIMEOUT_MS || runtime.exited) return;\n\n\tconst remainingMs = Math.max(0, deadlineMs - (Date.now() - startedAt));\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet unsubscribeExit: (() => void) | undefined;\n\tconst clearSweep = () => {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\ttimer = undefined;\n\t\tunsubscribeExit?.();\n\t\tunsubscribeExit = undefined;\n\t};\n\tunsubscribeExit = runtime.session.onExit(clearSweep);\n\tif (runtime.exited) {\n\t\tclearSweep();\n\t\treturn;\n\t}\n\ttimer = setTimeout(() => {\n\t\tclearSweep();\n\t\tif (!runtime.exited) void ctx.manager.stop(id).catch(() => {});\n\t}, remainingMs);\n}\n\nasync function runForeground(\n\tctx: TerminalToolContext,\n\tinput: PtyBashInput,\n\tcols: number,\n\trows: number,\n\tsignal: AbortSignal | undefined,\n\tcwd: string | undefined,\n\tonUpdate: AgentToolUpdateCallback | undefined,\n\texecCtx?: SessionEnvSource,\n): Promise<TerminalToolResult> {\n\tif (signal?.aborted) return errorResult(\"Command aborted\");\n\tconst timeoutMs = input.timeout !== undefined ? Math.trunc(input.timeout * 1000) : undefined;\n\tconst { id, runtime } = await spawnCommandSession(ctx, {\n\t\tcommand: input.command,\n\t\tcols,\n\t\trows,\n\t\ttimeoutMs,\n\t\tcwd,\n\t\tenvOverrides: { ...sessionEnvOverrides(ctx, execCtx), ...FOREGROUND_ENV_OVERRIDES },\n\t});\n\tconst startedAt = Date.now();\n\tconst activity = `running ${input.command.slice(0, 80)}`;\n\tconst emitOutputUpdate = () => {\n\t\tconst text = formatTerminalToolOutput(runtime.fullOutput()).text.slice(-2000);\n\t\tonUpdate?.({ content: [{ type: \"text\", text }], details: { progress: { activity, startedAt } } });\n\t};\n\tconst updateEmitter = onUpdate ? createThrottledEmitter(emitOutputUpdate) : undefined;\n\tonUpdate?.({ content: [], details: undefined });\n\tconst unsubscribeOutput = onUpdate ? runtime.onOutput(() => updateEmitter?.schedule()) : undefined;\n\n\tconst detachDelayMs = resolveAutoDetachDelayMs(ctx, input);\n\tlet outcome: ForegroundWaitOutcome | \"detached\";\n\tif (detachDelayMs === undefined) {\n\t\t// Interrupt means \"stop now\": SIGKILL the whole process group in one shot.\n\t\t// kill() is one-shot idempotent, so a gentle SIGTERM first would block any\n\t\t// escalation, and a SIGTERM-ignoring command would pin the agent forever.\n\t\tconst onAbort = () => runtime.session.kill(\"SIGKILL\");\n\t\tif (signal) {\n\t\t\tif (signal.aborted) onAbort();\n\t\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t}\n\t\ttry {\n\t\t\toutcome = await raceExitWithKillGrace(runtime, signal, timeoutMs);\n\t\t} finally {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tunsubscribeOutput?.();\n\t\t\tupdateEmitter?.flush();\n\t\t\tupdateEmitter?.dispose();\n\t\t}\n\t} else {\n\t\tconst abortGate = new AbortController();\n\t\tconst onAbort = () => {\n\t\t\truntime.session.kill(\"SIGKILL\");\n\t\t\tabortGate.abort();\n\t\t};\n\t\tif (signal) {\n\t\t\tif (signal.aborted) onAbort();\n\t\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t}\n\t\tconst detachGate = createForegroundDetachGate({\n\t\t\truntime,\n\t\t\tsignal,\n\t\t\tdelayMs: detachDelayMs,\n\t\t\tremoveAbortListener: () => signal?.removeEventListener(\"abort\", onAbort),\n\t\t});\n\t\ttry {\n\t\t\toutcome = await Promise.race([\n\t\t\t\traceExitWithKillGrace(runtime, abortGate.signal, timeoutMs),\n\t\t\t\tdetachGate.detached.then(() => \"detached\" as const),\n\t\t\t]);\n\t\t} finally {\n\t\t\tdetachGate.cancel();\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tunsubscribeOutput?.();\n\t\t\tupdateEmitter?.flush();\n\t\t\tupdateEmitter?.dispose();\n\t\t}\n\t}\n\n\tif (outcome === \"detached\") {\n\t\tscheduleDetachedSweep(ctx, id, runtime, timeoutMs, startedAt);\n\t\tif (ctx.onBackgroundExit) runtime.session.onExit(() => ctx.onBackgroundExit?.(id, runtime));\n\t\tconst delta = runtime.readDelta();\n\t\tconst partialOutput = formatTerminalToolOutput(delta.text).text || \"(no output yet)\";\n\t\tconst timeoutNote =\n\t\t\tinput.timeout !== undefined && Number.isFinite(input.timeout)\n\t\t\t\t? `not killed; the original ${input.timeout}s timeout still applies`\n\t\t\t\t: \"not killed; it will run until exit or kill_bash\";\n\t\treturn textResult(\n\t\t\t`Command is still running; auto-detached to background with ID: ${id} (${timeoutNote}).\\n\\nPartial output:\\n${partialOutput}\\n\\nContinue other work; completion will be reported automatically with exit status and output tail. Use bash_output({ bash_id: \"${id}\" }) only to peek at new output. monitor cannot attach to this session; use it for future event-driven launches. Use kill_bash({ bash_id: \"${id}\" }) to stop this session.`,\n\t\t\t{\n\t\t\t\tdetails: {\n\t\t\t\t\tbash_id: id,\n\t\t\t\t\tbackground: true,\n\t\t\t\t\tauto_detached: true,\n\t\t\t\t\tstatus: \"running\",\n\t\t\t\t\t...(delta.droppedChars > 0 ? { droppedChars: delta.droppedChars } : {}),\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\tif (outcome !== \"exited\") {\n\t\t// Sweep the never-settling session: the bounded registry stop marks it\n\t\t// `stopping`, so later /exit teardown cannot hang on its exit wait.\n\t\tvoid ctx.manager.stop(id).catch(() => {});\n\t}\n\tconst formatted = formatTerminalToolOutput(runtime.fullOutput());\n\tconst output = formatted.text;\n\t// Aborted runs report \"Command aborted\" (core bash parity) whether the exit\n\t// settled normally or the kill grace released the wait. `outcome` matters\n\t// beyond that only for the timeout grace, where exitResult is still null.\n\tif (signal?.aborted) {\n\t\treturn errorResult(`${output ? `${output}\\n\\n` : \"\"}Command aborted`);\n\t}\n\tconst exit = runtime.exitResult;\n\tif (outcome === \"timeout_grace\" || exit?.timedOut) {\n\t\treturn errorResult(`${output ? `${output}\\n\\n` : \"\"}Command timed out after ${input.timeout} seconds`);\n\t}\n\tconst status = describeExit(runtime);\n\tif (exit && exit.exitCode !== 0 && exit.exitCode !== null) {\n\t\treturn errorResult(`${output ? `${output}\\n\\n` : \"\"}Command exited with code ${exit.exitCode}`);\n\t}\n\treturn textResult(output || \"(no output)\", {\n\t\tdetails: {\n\t\t\tstatus,\n\t\t\t...(formatted.truncated ? { truncation: formatted.truncation } : {}),\n\t\t},\n\t});\n}\n\nasync function runBackground(\n\tctx: TerminalToolContext,\n\tinput: PtyBashInput,\n\tcols: number,\n\trows: number,\n\tcwd: string | undefined,\n\texecCtx?: SessionEnvSource,\n): Promise<TerminalToolResult> {\n\t// Background sessions are NEVER killed by `timeout` (bash-timeout injects a default into\n\t// every bash call); they live until exit or kill_bash, so no timeoutMs is passed.\n\tconst { id, runtime } = await spawnCommandSession(ctx, {\n\t\tcommand: input.command,\n\t\tcols,\n\t\trows,\n\t\tcwd,\n\t\tenvOverrides: sessionEnvOverrides(ctx, execCtx),\n\t});\n\tif (ctx.onBackgroundExit) runtime.session.onExit(() => ctx.onBackgroundExit?.(id, runtime));\n\n\t// Capture any output the command emits within a short grace window (or its exit).\n\tawait Promise.race([delay(BACKGROUND_START_GRACE_MS), runtime.session.waitExit()]);\n\tconst early = formatTerminalToolOutput(runtime.readDelta().text).text;\n\tconst header = `Command running in background with ID: ${id}`;\n\tconst backendNote =\n\t\truntime.backend === \"pipe-fallback\"\n\t\t\t? \"\\n(native PTY unavailable — running via pipe fallback; bash_input/bash_resize are limited.)\"\n\t\t\t: \"\";\n\tconst earlyBlock = early ? `\\n\\n${early}` : \"\";\n\treturn textResult(`${header}${backendNote}${earlyBlock}`, { details: { bash_id: id, background: true } });\n}\n\n/** Build the PTY-backed `bash` tool definition. */\nexport function createPtyBashTool(ctx: TerminalToolContext) {\n\treturn {\n\t\tname: TERMINAL_BASH_TOOL,\n\t\tlabel: \"bash\",\n\t\tdescription:\n\t\t\t\"Execute a shell command in a persistent PTY-backed session. Set run_in_background:true for long-lived or interactive sessions; steer them with bash_input, snapshot with bash_output, tear down with kill_bash. To wait on observable state (a build finishing, a server coming up, a log line), never run sleep or poll loops — subscribe with the monitor tool instead. Foreground timeout is a kill deadline in seconds.\",\n\t\tpromptSnippet: \"Run shell commands; run_in_background:true for long-lived/interactive PTY sessions\",\n\t\tpromptGuidelines: [\"Inspect PI_* environment variables for current model and session details.\"],\n\t\tparameters: ptyBashSchema,\n\t\tasync execute(\n\t\t\t_toolCallId: string,\n\t\t\tinput: PtyBashInput,\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?: AgentToolUpdateCallback,\n\t\t\texecCtx?: SessionEnvSource,\n\t\t): Promise<TerminalToolResult> {\n\t\t\tconst cols = resolveDimension(input.cols, ctx.defaultCols || DEFAULT_COLS);\n\t\t\tconst rows = resolveDimension(input.rows, ctx.defaultRows || DEFAULT_ROWS);\n\t\t\tconst cwd = execCtx?.cwd;\n\t\t\tif (input.run_in_background) return runBackground(ctx, input, cols, rows, cwd, execCtx);\n\t\t\treturn runForeground(ctx, input, cols, rows, signal, cwd, onUpdate, execCtx);\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/terminal/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Static, Type } from \"typebox\";\nimport type { AgentToolUpdateCallback } from \"../../../types.ts\";\nimport { formatTerminalToolOutput } from \"../output-format.ts\";\nimport type { TerminalRuntimeSession } from \"../runtime-session.ts\";\nimport {\n\tBACKGROUND_START_GRACE_MS,\n\tDEFAULT_COLS,\n\tDEFAULT_ROWS,\n\tFOREGROUND_ENV_OVERRIDES,\n\tKILLED_SESSION_EXIT_GRACE_MS,\n\tTERMINAL_BASH_TOOL,\n} from \"../shared.ts\";\nimport { errorResult, type TerminalToolContext, type TerminalToolResult, textResult } from \"./context.ts\";\nimport { createForegroundDetachGate } from \"./foreground-detach.ts\";\nimport { resolveForegroundWindowSeconds, SLEEP_WAIT_WINDOW_SECONDS } from \"./foreground-window.ts\";\nimport { classifySleepWait, type SleepWaitClassification } from \"./sleep-wait.ts\";\nimport { describeExit, spawnCommandSession } from \"./spawn.ts\";\n\nexport const ptyBashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Shell command to execute in a PTY-backed session.\" }),\n\ttimeout: Type.Optional(\n\t\tType.Number({\n\t\t\tdescription:\n\t\t\t\t\"Process kill deadline in seconds (default 1800). Foreground blocking stops at the ~60s window: a command still running then auto-detaches to a live background session instead of being killed. Ignored for run_in_background sessions (they live until exit or kill_bash).\",\n\t\t}),\n\t),\n\tdescription: Type.Optional(Type.String({ description: \"Short human-readable label for this command.\" })),\n\trun_in_background: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription:\n\t\t\t\t\"Run as a persistent background session. Returns a bash_id immediately; query it with bash_output, steer with bash_input, tear down with kill_bash.\",\n\t\t}),\n\t),\n\tcols: Type.Optional(Type.Number({ description: \"PTY width in columns (default 120).\" })),\n\trows: Type.Optional(Type.Number({ description: \"PTY height in rows (default 40).\" })),\n});\n\nexport type PtyBashInput = Static<typeof ptyBashSchema>;\n\nfunction resolveDimension(value: number | undefined, fallback: number): number {\n\tif (value === undefined || !Number.isFinite(value) || value < 1) return fallback;\n\treturn Math.trunc(value);\n}\n\nfunction delay(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nconst MAX_TIMEOUT_MS = 2_147_483_647;\nconst BASH_UPDATE_THROTTLE_MS = 100;\n\nexport interface ThrottledEmitter {\n\tschedule(): void;\n\tflush(): void;\n\tdispose(): void;\n}\n\n/**\n * Emit immediately, then coalesce subsequent schedules into one trailing update.\n * `dispose` intentionally discards a pending update so callers can stop cleanly.\n */\nexport function createThrottledEmitter(emit: () => void, throttleMs = BASH_UPDATE_THROTTLE_MS): ThrottledEmitter {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet dirty = false;\n\tlet lastEmissionAt: number | undefined;\n\n\tconst emitIfDirty = () => {\n\t\tif (!dirty) return;\n\t\tdirty = false;\n\t\tlastEmissionAt = Date.now();\n\t\temit();\n\t};\n\n\tconst clearTimer = () => {\n\t\tif (timer !== undefined) {\n\t\t\tclearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t}\n\t};\n\n\treturn {\n\t\tschedule() {\n\t\t\tdirty = true;\n\t\t\tif (timer !== undefined) return;\n\t\t\tconst elapsed = lastEmissionAt === undefined ? throttleMs : Date.now() - lastEmissionAt;\n\t\t\tif (elapsed >= throttleMs) {\n\t\t\t\temitIfDirty();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\ttimer = undefined;\n\t\t\t\temitIfDirty();\n\t\t\t}, throttleMs - elapsed);\n\t\t},\n\t\tflush() {\n\t\t\tclearTimer();\n\t\t\temitIfDirty();\n\t\t},\n\t\tdispose() {\n\t\t\tclearTimer();\n\t\t\tdirty = false;\n\t\t\tlastEmissionAt = undefined;\n\t\t},\n\t};\n}\n\ntype ForegroundWaitOutcome = \"exited\" | \"abort_grace\" | \"timeout_grace\";\n\ntype SessionEnvSource = {\n\tsessionManager?: { getSessionId: () => string; getSessionFile: () => string | undefined };\n\tmodel?: { provider: string; id: string };\n\tthinkingLevel?: string;\n\tcwd?: string;\n};\n\nfunction sessionEnvOverrides(ctx: TerminalToolContext, execCtx?: SessionEnvSource): Record<string, string> {\n\tconst session = execCtx ?? ctx.getSessionContext?.();\n\tif (!session?.sessionManager) return {};\n\tconst env: Record<string, string> = {};\n\tenv.PI_SESSION_ID = session.sessionManager.getSessionId();\n\tconst sessionFile = session.sessionManager.getSessionFile();\n\tif (sessionFile) env.PI_SESSION_FILE = sessionFile;\n\tif (session.model) {\n\t\tenv.PI_PROVIDER = session.model.provider;\n\t\tenv.PI_MODEL = session.model.id;\n\t}\n\tif (session.thinkingLevel) env.PI_REASONING_LEVEL = session.thinkingLevel;\n\treturn env;\n}\n\n/**\n * Wait for the session's exit to settle, but stop waiting a bounded grace after\n * the session has been killed. The native wait joins the PTY reader thread,\n * which blocks while any surviving descendant holds the PTY open — without the\n * grace, an aborted or timed-out command can pin the agent forever.\n */\nfunction raceExitWithKillGrace(\n\truntime: TerminalRuntimeSession,\n\tsignal: AbortSignal | undefined,\n\ttimeoutMs: number | undefined,\n): Promise<ForegroundWaitOutcome> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet settled = false;\n\t\tlet abortGraceTimer: ReturnType<typeof setTimeout> | undefined;\n\t\tlet timeoutGraceTimer: ReturnType<typeof setTimeout> | undefined;\n\t\tlet armAbortGrace: (() => void) | undefined;\n\t\tconst settle = (finish: () => void) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tif (abortGraceTimer) clearTimeout(abortGraceTimer);\n\t\t\tif (timeoutGraceTimer) clearTimeout(timeoutGraceTimer);\n\t\t\tif (signal && armAbortGrace) signal.removeEventListener(\"abort\", armAbortGrace);\n\t\t\tfinish();\n\t\t};\n\t\truntime.session.waitExit().then(\n\t\t\t() => settle(() => resolve(\"exited\")),\n\t\t\t(error: unknown) => settle(() => reject(error instanceof Error ? error : new Error(String(error)))),\n\t\t);\n\t\tif (timeoutMs !== undefined) {\n\t\t\tconst timeoutGraceDeadlineMs = timeoutMs + KILLED_SESSION_EXIT_GRACE_MS;\n\t\t\t// Beyond the 32-bit setTimeout range the deadline cannot be represented;\n\t\t\t// keep the unbounded wait rather than firing a false early timeout.\n\t\t\tif (timeoutGraceDeadlineMs <= MAX_TIMEOUT_MS) {\n\t\t\t\ttimeoutGraceTimer = setTimeout(() => settle(() => resolve(\"timeout_grace\")), timeoutGraceDeadlineMs);\n\t\t\t}\n\t\t}\n\t\tif (signal) {\n\t\t\tarmAbortGrace = () => {\n\t\t\t\tabortGraceTimer ??= setTimeout(() => settle(() => resolve(\"abort_grace\")), KILLED_SESSION_EXIT_GRACE_MS);\n\t\t\t};\n\t\t\tif (signal.aborted) armAbortGrace();\n\t\t\telse signal.addEventListener(\"abort\", armAbortGrace, { once: true });\n\t\t}\n\t});\n}\n\n/**\n * How long the model blocks before a still-running command is handed to a live\n * background session. A sleep-wait detaches almost immediately: its remaining\n * time is pure waiting, which the completion notification delivers for free.\n */\nfunction resolveAutoDetachDelayMs(\n\tctx: TerminalToolContext,\n\tinput: PtyBashInput,\n\tsleepWait: SleepWaitClassification | undefined,\n): number | undefined {\n\tif (ctx.timeoutAction === \"kill\") return undefined;\n\tconst windowSeconds = sleepWait\n\t\t? Math.min(SLEEP_WAIT_WINDOW_SECONDS, resolveForegroundWindowSeconds(process.env))\n\t\t: resolveForegroundWindowSeconds(process.env);\n\tconst timeoutSeconds = input.timeout !== undefined && Number.isFinite(input.timeout) ? input.timeout : Infinity;\n\tif (timeoutSeconds <= windowSeconds) return undefined;\n\treturn Math.max(0, Math.trunc(windowSeconds * 1000));\n}\n\nfunction scheduleDetachedSweep(\n\tctx: TerminalToolContext,\n\tid: string,\n\truntime: TerminalRuntimeSession,\n\ttimeoutMs: number | undefined,\n\tstartedAt: number,\n): void {\n\tif (timeoutMs === undefined) return;\n\tconst deadlineMs = timeoutMs + KILLED_SESSION_EXIT_GRACE_MS;\n\tif (!Number.isFinite(deadlineMs) || deadlineMs > MAX_TIMEOUT_MS || runtime.exited) return;\n\n\tconst remainingMs = Math.max(0, deadlineMs - (Date.now() - startedAt));\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tlet unsubscribeExit: (() => void) | undefined;\n\tconst clearSweep = () => {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\ttimer = undefined;\n\t\tunsubscribeExit?.();\n\t\tunsubscribeExit = undefined;\n\t};\n\tunsubscribeExit = runtime.session.onExit(clearSweep);\n\tif (runtime.exited) {\n\t\tclearSweep();\n\t\treturn;\n\t}\n\ttimer = setTimeout(() => {\n\t\tclearSweep();\n\t\tif (!runtime.exited) void ctx.manager.stop(id).catch(() => {});\n\t}, remainingMs);\n}\n\nasync function runForeground(\n\tctx: TerminalToolContext,\n\tinput: PtyBashInput,\n\tcols: number,\n\trows: number,\n\tsignal: AbortSignal | undefined,\n\tcwd: string | undefined,\n\tonUpdate: AgentToolUpdateCallback | undefined,\n\texecCtx?: SessionEnvSource,\n): Promise<TerminalToolResult> {\n\tif (signal?.aborted) return errorResult(\"Command aborted\");\n\tconst timeoutMs = input.timeout !== undefined ? Math.trunc(input.timeout * 1000) : undefined;\n\tconst { id, runtime } = await spawnCommandSession(ctx, {\n\t\tcommand: input.command,\n\t\tcols,\n\t\trows,\n\t\ttimeoutMs,\n\t\tcwd,\n\t\tenvOverrides: { ...sessionEnvOverrides(ctx, execCtx), ...FOREGROUND_ENV_OVERRIDES },\n\t});\n\tconst startedAt = Date.now();\n\tconst activity = `running ${input.command.slice(0, 80)}`;\n\tconst emitOutputUpdate = () => {\n\t\tconst text = formatTerminalToolOutput(runtime.fullOutput()).text.slice(-2000);\n\t\tonUpdate?.({ content: [{ type: \"text\", text }], details: { progress: { activity, startedAt } } });\n\t};\n\tconst updateEmitter = onUpdate ? createThrottledEmitter(emitOutputUpdate) : undefined;\n\tonUpdate?.({ content: [], details: undefined });\n\tconst unsubscribeOutput = onUpdate ? runtime.onOutput(() => updateEmitter?.schedule()) : undefined;\n\n\tconst sleepWait = classifySleepWait(input.command);\n\tconst detachDelayMs = resolveAutoDetachDelayMs(ctx, input, sleepWait);\n\tlet outcome: ForegroundWaitOutcome | \"detached\";\n\tif (detachDelayMs === undefined) {\n\t\t// Interrupt means \"stop now\": SIGKILL the whole process group in one shot.\n\t\t// kill() is one-shot idempotent, so a gentle SIGTERM first would block any\n\t\t// escalation, and a SIGTERM-ignoring command would pin the agent forever.\n\t\tconst onAbort = () => runtime.session.kill(\"SIGKILL\");\n\t\tif (signal) {\n\t\t\tif (signal.aborted) onAbort();\n\t\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t}\n\t\ttry {\n\t\t\toutcome = await raceExitWithKillGrace(runtime, signal, timeoutMs);\n\t\t} finally {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tunsubscribeOutput?.();\n\t\t\tupdateEmitter?.flush();\n\t\t\tupdateEmitter?.dispose();\n\t\t}\n\t} else {\n\t\tconst abortGate = new AbortController();\n\t\tconst onAbort = () => {\n\t\t\truntime.session.kill(\"SIGKILL\");\n\t\t\tabortGate.abort();\n\t\t};\n\t\tif (signal) {\n\t\t\tif (signal.aborted) onAbort();\n\t\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t}\n\t\tconst detachGate = createForegroundDetachGate({\n\t\t\truntime,\n\t\t\tsignal,\n\t\t\tdelayMs: detachDelayMs,\n\t\t\tremoveAbortListener: () => signal?.removeEventListener(\"abort\", onAbort),\n\t\t});\n\t\ttry {\n\t\t\toutcome = await Promise.race([\n\t\t\t\traceExitWithKillGrace(runtime, abortGate.signal, timeoutMs),\n\t\t\t\tdetachGate.detached.then(() => \"detached\" as const),\n\t\t\t]);\n\t\t} finally {\n\t\t\tdetachGate.cancel();\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tunsubscribeOutput?.();\n\t\t\tupdateEmitter?.flush();\n\t\t\tupdateEmitter?.dispose();\n\t\t}\n\t}\n\n\tif (outcome === \"detached\") {\n\t\tscheduleDetachedSweep(ctx, id, runtime, timeoutMs, startedAt);\n\t\tctx.onBackgroundStart?.(id, input.description ?? input.command, startedAt);\n\t\tif (ctx.onBackgroundExit) runtime.session.onExit(() => ctx.onBackgroundExit?.(id, runtime));\n\t\tconst delta = runtime.readDelta();\n\t\tconst partialOutput = formatTerminalToolOutput(delta.text).text || \"(no output yet)\";\n\t\tconst timeoutNote =\n\t\t\tinput.timeout !== undefined && Number.isFinite(input.timeout)\n\t\t\t\t? `not killed; the original ${input.timeout}s timeout still applies`\n\t\t\t\t: \"not killed; it will run until exit or kill_bash\";\n\t\tconst guidance = sleepWait\n\t\t\t? `This command is a wait (${sleepWait.seconds}s sleep), so it detached immediately; the wait continues in the background. Do nothing and end your turn — completion will be reported automatically with exit status and output tail. Do NOT poll bash_output({ bash_id: \"${id}\" }) for it. When you are waiting for a pattern in a command's output, launch it with monitor({ command, filter }) instead so matching lines arrive as events. Use kill_bash({ bash_id: \"${id}\" }) to stop this session.`\n\t\t\t: `Continue other work; completion will be reported automatically with exit status and output tail. Use bash_output({ bash_id: \"${id}\" }) only to peek at new output. monitor cannot attach to this session; use it for future event-driven launches. Use kill_bash({ bash_id: \"${id}\" }) to stop this session.`;\n\t\treturn textResult(\n\t\t\t`Command is still running; auto-detached to background with ID: ${id} (${timeoutNote}).\\n\\nPartial output:\\n${partialOutput}\\n\\n${guidance}`,\n\t\t\t{\n\t\t\t\tdetails: {\n\t\t\t\t\tbash_id: id,\n\t\t\t\t\tbackground: true,\n\t\t\t\t\tauto_detached: true,\n\t\t\t\t\t...(sleepWait ? { sleep_wait: true } : {}),\n\t\t\t\t\tstatus: \"running\",\n\t\t\t\t\t...(delta.droppedChars > 0 ? { droppedChars: delta.droppedChars } : {}),\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t}\n\n\tif (outcome !== \"exited\") {\n\t\t// Sweep the never-settling session: the bounded registry stop marks it\n\t\t// `stopping`, so later /exit teardown cannot hang on its exit wait.\n\t\tvoid ctx.manager.stop(id).catch(() => {});\n\t}\n\tconst formatted = formatTerminalToolOutput(runtime.fullOutput());\n\tconst output = formatted.text;\n\t// Aborted runs report \"Command aborted\" (core bash parity) whether the exit\n\t// settled normally or the kill grace released the wait. `outcome` matters\n\t// beyond that only for the timeout grace, where exitResult is still null.\n\tif (signal?.aborted) {\n\t\treturn errorResult(`${output ? `${output}\\n\\n` : \"\"}Command aborted`);\n\t}\n\tconst exit = runtime.exitResult;\n\tif (outcome === \"timeout_grace\" || exit?.timedOut) {\n\t\treturn errorResult(`${output ? `${output}\\n\\n` : \"\"}Command timed out after ${input.timeout} seconds`);\n\t}\n\tconst status = describeExit(runtime);\n\tif (exit && exit.exitCode !== 0 && exit.exitCode !== null) {\n\t\treturn errorResult(`${output ? `${output}\\n\\n` : \"\"}Command exited with code ${exit.exitCode}`);\n\t}\n\treturn textResult(output || \"(no output)\", {\n\t\tdetails: {\n\t\t\tstatus,\n\t\t\t...(formatted.truncated ? { truncation: formatted.truncation } : {}),\n\t\t},\n\t});\n}\n\nasync function runBackground(\n\tctx: TerminalToolContext,\n\tinput: PtyBashInput,\n\tcols: number,\n\trows: number,\n\tcwd: string | undefined,\n\texecCtx?: SessionEnvSource,\n): Promise<TerminalToolResult> {\n\t// Background sessions are NEVER killed by `timeout` (bash-timeout injects a default into\n\t// every bash call); they live until exit or kill_bash, so no timeoutMs is passed.\n\tconst { id, runtime } = await spawnCommandSession(ctx, {\n\t\tcommand: input.command,\n\t\tcols,\n\t\trows,\n\t\tcwd,\n\t\tenvOverrides: sessionEnvOverrides(ctx, execCtx),\n\t});\n\tctx.onBackgroundStart?.(id, input.description ?? input.command, Date.now());\n\tif (ctx.onBackgroundExit) runtime.session.onExit(() => ctx.onBackgroundExit?.(id, runtime));\n\n\t// Capture any output the command emits within a short grace window (or its exit).\n\tawait Promise.race([delay(BACKGROUND_START_GRACE_MS), runtime.session.waitExit()]);\n\tconst early = formatTerminalToolOutput(runtime.readDelta().text).text;\n\tconst header = `Command running in background with ID: ${id}`;\n\tconst backendNote =\n\t\truntime.backend === \"pipe-fallback\"\n\t\t\t? \"\\n(native PTY unavailable — running via pipe fallback; bash_input/bash_resize are limited.)\"\n\t\t\t: \"\";\n\tconst earlyBlock = early ? `\\n\\n${early}` : \"\";\n\treturn textResult(`${header}${backendNote}${earlyBlock}`, { details: { bash_id: id, background: true } });\n}\n\n/** Build the PTY-backed `bash` tool definition. */\nexport function createPtyBashTool(ctx: TerminalToolContext) {\n\treturn {\n\t\tname: TERMINAL_BASH_TOOL,\n\t\tlabel: \"bash\",\n\t\tdescription:\n\t\t\t\"Execute a shell command in a persistent PTY-backed session. Set run_in_background:true for long-lived or interactive sessions; steer them with bash_input, snapshot with bash_output, tear down with kill_bash. To wait on observable state (a build finishing, a server coming up, a log line), never run sleep or poll loops — subscribe with the monitor tool instead. Foreground blocking stops at the ~60s window and a still-running command auto-detaches to a live background session; `timeout` is the process kill deadline in seconds.\",\n\t\tpromptSnippet: \"Run shell commands; run_in_background:true for long-lived/interactive PTY sessions\",\n\t\tpromptGuidelines: [\"Inspect PI_* environment variables for current model and session details.\"],\n\t\tparameters: ptyBashSchema,\n\t\tasync execute(\n\t\t\t_toolCallId: string,\n\t\t\tinput: PtyBashInput,\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?: AgentToolUpdateCallback,\n\t\t\texecCtx?: SessionEnvSource,\n\t\t): Promise<TerminalToolResult> {\n\t\t\tconst cols = resolveDimension(input.cols, ctx.defaultCols || DEFAULT_COLS);\n\t\t\tconst rows = resolveDimension(input.rows, ctx.defaultRows || DEFAULT_ROWS);\n\t\t\tconst cwd = execCtx?.cwd;\n\t\t\tif (input.run_in_background) return runBackground(ctx, input, cols, rows, cwd, execCtx);\n\t\t\treturn runForeground(ctx, input, cols, rows, signal, cwd, onUpdate, execCtx);\n\t\t},\n\t};\n}\n"]}
@@ -19,6 +19,8 @@ export interface TerminalToolContext {
19
19
  * expose PI_* session metadata to spawned commands like the core bash tool does.
20
20
  */
21
21
  readonly getSessionContext?: () => import("../../../types.ts").ExtensionContext | undefined;
22
+ /** Registers a session when it transitions to background liveness. */
23
+ readonly onBackgroundStart?: (id: string, description: string, startedAtMs: number) => void;
22
24
  /** Notified when a background session exits, so the notify layer can wake the agent. */
23
25
  readonly onBackgroundExit?: (id: string, runtime: TerminalRuntimeSession) => void;
24
26
  /** Session-scoped monitor state, sharing the terminal manager's bash-id namespace. */
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/terminal/tools/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8FAA8F;IAC9F,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,sFAAsF;IACtF,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC,UAAU,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,OAAO,mBAAmB,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC5F,wFAAwF;IACxF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAClF,sFAAsF;IACtF,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACxD,4FAA4F;IAC5F,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC7C,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,UAAU,CACzB,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9D,kBAAkB,CAEpB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAE5D"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/terminal/tools/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,iEAAiE;AACjE,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8FAA8F;IAC9F,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,sFAAsF;IACtF,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC,UAAU,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,OAAO,mBAAmB,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC5F,sEAAsE;IACtE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5F,wFAAwF;IACxF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAClF,sFAAsF;IACtF,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACxD,4FAA4F;IAC5F,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAED,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC7C,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,UAAU,CACzB,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9D,kBAAkB,CAEpB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAE5D"}
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/terminal/tools/context.ts"],"names":[],"mappings":"AAuCA,MAAM,UAAU,UAAU,CACzB,IAAY,EACZ,KAAgE;IAEhE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAChG,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACvC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACjF,CAAC","sourcesContent":["import type { TerminalManager } from \"../manager.ts\";\nimport type { MonitorEvent, MonitorRegistry } from \"../monitor-registry.ts\";\nimport type { TerminalRuntimeSession } from \"../runtime-session.ts\";\nimport type { TimeoutAction } from \"../settings.ts\";\n\n/** Shared dependencies handed to every terminal tool factory. */\nexport interface TerminalToolContext {\n\treadonly manager: TerminalManager;\n\treadonly cwd: string;\n\t/** Explicit shell path from settings (settings-manager `shellPath`), if any. */\n\treadonly shellPath?: string;\n\treadonly defaultCols: number;\n\treadonly defaultRows: number;\n\t/** Configured foreground deadline behavior; omitted direct contexts default to background. */\n\treadonly timeoutAction?: TimeoutAction;\n\t/** Resolve the environment for spawned sessions (mirrors core bash `getShellEnv`). */\n\treadonly getEnv: () => NodeJS.ProcessEnv;\n\t/**\n\t * Current extension session context (set on session_start/model_select), used to\n\t * expose PI_* session metadata to spawned commands like the core bash tool does.\n\t */\n\treadonly getSessionContext?: () => import(\"../../../types.ts\").ExtensionContext | undefined;\n\t/** Notified when a background session exits, so the notify layer can wake the agent. */\n\treadonly onBackgroundExit?: (id: string, runtime: TerminalRuntimeSession) => void;\n\t/** Session-scoped monitor state, sharing the terminal manager's bash-id namespace. */\n\treadonly monitorRegistry?: MonitorRegistry;\n\t/** Receives filtered monitor line and terminal-summary events. */\n\treadonly onMonitorEvent?: (event: MonitorEvent) => void;\n\t/** Resets session-global wake-budget delivery for a fresh or explicitly rearmed monitor. */\n\treadonly onMonitorRearmed?: (id: string) => void;\n}\n\n/** Minimal tool-result shape returned by the terminal tools. */\nexport interface TerminalToolResult {\n\tcontent: Array<{ type: \"text\"; text: string }>;\n\tdetails: Record<string, unknown> | undefined;\n\tisError?: boolean;\n}\n\nexport function textResult(\n\ttext: string,\n\textra?: { details?: Record<string, unknown>; isError?: boolean },\n): TerminalToolResult {\n\treturn { content: [{ type: \"text\", text }], details: extra?.details, isError: extra?.isError };\n}\n\nexport function errorResult(text: string): TerminalToolResult {\n\treturn { content: [{ type: \"text\", text }], details: undefined, isError: true };\n}\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/terminal/tools/context.ts"],"names":[],"mappings":"AAyCA,MAAM,UAAU,UAAU,CACzB,IAAY,EACZ,KAAgE;IAEhE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAChG,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACvC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACjF,CAAC","sourcesContent":["import type { TerminalManager } from \"../manager.ts\";\nimport type { MonitorEvent, MonitorRegistry } from \"../monitor-registry.ts\";\nimport type { TerminalRuntimeSession } from \"../runtime-session.ts\";\nimport type { TimeoutAction } from \"../settings.ts\";\n\n/** Shared dependencies handed to every terminal tool factory. */\nexport interface TerminalToolContext {\n\treadonly manager: TerminalManager;\n\treadonly cwd: string;\n\t/** Explicit shell path from settings (settings-manager `shellPath`), if any. */\n\treadonly shellPath?: string;\n\treadonly defaultCols: number;\n\treadonly defaultRows: number;\n\t/** Configured foreground deadline behavior; omitted direct contexts default to background. */\n\treadonly timeoutAction?: TimeoutAction;\n\t/** Resolve the environment for spawned sessions (mirrors core bash `getShellEnv`). */\n\treadonly getEnv: () => NodeJS.ProcessEnv;\n\t/**\n\t * Current extension session context (set on session_start/model_select), used to\n\t * expose PI_* session metadata to spawned commands like the core bash tool does.\n\t */\n\treadonly getSessionContext?: () => import(\"../../../types.ts\").ExtensionContext | undefined;\n\t/** Registers a session when it transitions to background liveness. */\n\treadonly onBackgroundStart?: (id: string, description: string, startedAtMs: number) => void;\n\t/** Notified when a background session exits, so the notify layer can wake the agent. */\n\treadonly onBackgroundExit?: (id: string, runtime: TerminalRuntimeSession) => void;\n\t/** Session-scoped monitor state, sharing the terminal manager's bash-id namespace. */\n\treadonly monitorRegistry?: MonitorRegistry;\n\t/** Receives filtered monitor line and terminal-summary events. */\n\treadonly onMonitorEvent?: (event: MonitorEvent) => void;\n\t/** Resets session-global wake-budget delivery for a fresh or explicitly rearmed monitor. */\n\treadonly onMonitorRearmed?: (id: string) => void;\n}\n\n/** Minimal tool-result shape returned by the terminal tools. */\nexport interface TerminalToolResult {\n\tcontent: Array<{ type: \"text\"; text: string }>;\n\tdetails: Record<string, unknown> | undefined;\n\tisError?: boolean;\n}\n\nexport function textResult(\n\ttext: string,\n\textra?: { details?: Record<string, unknown>; isError?: boolean },\n): TerminalToolResult {\n\treturn { content: [{ type: \"text\", text }], details: extra?.details, isError: extra?.isError };\n}\n\nexport function errorResult(text: string): TerminalToolResult {\n\treturn { content: [{ type: \"text\", text }], details: undefined, isError: true };\n}\n"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The foreground window is how long a bash tool call may block the model, which
3
+ * is a different concern from `timeout` — the deadline that kills the process.
4
+ * A command still running at the window is handed to a live background session
5
+ * instead of being killed, so the turn is freed without losing the work.
6
+ */
7
+ export declare const DEFAULT_FOREGROUND_WINDOW_SECONDS = 60;
8
+ /** A wait has nothing to show in the foreground; detach it almost at once. */
9
+ export declare const SLEEP_WAIT_WINDOW_SECONDS = 5;
10
+ export declare function resolveForegroundWindowSeconds(env: Record<string, string | undefined>): number;
11
+ //# sourceMappingURL=foreground-window.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foreground-window.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/terminal/tools/foreground-window.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,iCAAiC,KAAK,CAAC;AACpD,8EAA8E;AAC9E,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,CAI9F"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The foreground window is how long a bash tool call may block the model, which
3
+ * is a different concern from `timeout` — the deadline that kills the process.
4
+ * A command still running at the window is handed to a live background session
5
+ * instead of being killed, so the turn is freed without losing the work.
6
+ */
7
+ export const DEFAULT_FOREGROUND_WINDOW_SECONDS = 60;
8
+ /** A wait has nothing to show in the foreground; detach it almost at once. */
9
+ export const SLEEP_WAIT_WINDOW_SECONDS = 5;
10
+ export function resolveForegroundWindowSeconds(env) {
11
+ const parsed = Number.parseFloat(env.PI_BASH_FOREGROUND_SECONDS ?? "");
12
+ if (!Number.isFinite(parsed) || parsed <= 0)
13
+ return DEFAULT_FOREGROUND_WINDOW_SECONDS;
14
+ return parsed;
15
+ }
16
+ //# sourceMappingURL=foreground-window.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foreground-window.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/terminal/tools/foreground-window.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,iCAAiC,GAAG,EAAE,CAAC;AACpD,8EAA8E;AAC9E,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAE3C,MAAM,UAAU,8BAA8B,CAAC,GAAuC;IACrF,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC;IACvE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,iCAAiC,CAAC;IACtF,OAAO,MAAM,CAAC;AACf,CAAC","sourcesContent":["/**\n * The foreground window is how long a bash tool call may block the model, which\n * is a different concern from `timeout` — the deadline that kills the process.\n * A command still running at the window is handed to a live background session\n * instead of being killed, so the turn is freed without losing the work.\n */\n\nexport const DEFAULT_FOREGROUND_WINDOW_SECONDS = 60;\n/** A wait has nothing to show in the foreground; detach it almost at once. */\nexport const SLEEP_WAIT_WINDOW_SECONDS = 5;\n\nexport function resolveForegroundWindowSeconds(env: Record<string, string | undefined>): number {\n\tconst parsed = Number.parseFloat(env.PI_BASH_FOREGROUND_SECONDS ?? \"\");\n\tif (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_FOREGROUND_WINDOW_SECONDS;\n\treturn parsed;\n}\n"]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Detects commands whose whole purpose is to *wait* — `sleep 270; git log`,
3
+ * `for ...; do ...; sleep 5; done`, a bare `sleep 30`. Such a command should not
4
+ * hold the foreground: it detaches early so the session can keep working while
5
+ * the wait finishes in the background and its completion notification lands.
6
+ *
7
+ * Settle sleeps (`pkill; sleep 1`, a 2s server warm-up) are deliberately NOT
8
+ * sleep-waits: they are short by construction and detaching them would cost a
9
+ * notification round-trip for nothing.
10
+ */
11
+ /** Minimum `sleep` seconds for a non-loop command to count as a wait. */
12
+ export declare const SLEEP_WAIT_THRESHOLD_SECONDS = 10;
13
+ /** Minimum per-iteration `sleep` seconds for a polling loop to count as a wait. */
14
+ export declare const SLEEP_WAIT_LOOP_THRESHOLD_SECONDS = 2;
15
+ export type SleepWaitRule = "R1" | "R2" | "R3" | "R4";
16
+ export interface SleepWaitClassification {
17
+ readonly kind: "sleep-wait";
18
+ /** R1 pure, R2 leading chain, R3 polling loop, R4 trailing. */
19
+ readonly rule: SleepWaitRule;
20
+ /** Longest `sleep` argument that made the command match. */
21
+ readonly seconds: number;
22
+ }
23
+ /**
24
+ * Classify a bash command as a sleep-wait, or `undefined` when it is ordinary
25
+ * work (including short settle sleeps below the thresholds).
26
+ */
27
+ export declare function classifySleepWait(command: string): SleepWaitClassification | undefined;
28
+ //# sourceMappingURL=sleep-wait.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep-wait.d.ts","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/terminal/tools/sleep-wait.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,yEAAyE;AACzE,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAC/C,mFAAmF;AACnF,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAEnD,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEtD,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAuCD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,CActF"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Detects commands whose whole purpose is to *wait* — `sleep 270; git log`,
3
+ * `for ...; do ...; sleep 5; done`, a bare `sleep 30`. Such a command should not
4
+ * hold the foreground: it detaches early so the session can keep working while
5
+ * the wait finishes in the background and its completion notification lands.
6
+ *
7
+ * Settle sleeps (`pkill; sleep 1`, a 2s server warm-up) are deliberately NOT
8
+ * sleep-waits: they are short by construction and detaching them would cost a
9
+ * notification round-trip for nothing.
10
+ */
11
+ /** Minimum `sleep` seconds for a non-loop command to count as a wait. */
12
+ export const SLEEP_WAIT_THRESHOLD_SECONDS = 10;
13
+ /** Minimum per-iteration `sleep` seconds for a polling loop to count as a wait. */
14
+ export const SLEEP_WAIT_LOOP_THRESHOLD_SECONDS = 2;
15
+ /** `env A=1 bash -lc '...'` wrappers hide the real command from every rule below. */
16
+ const SHELL_WRAPPER = /^(?:env\s+(?:[\w.]+=[^\s]*\s+)*)?(?:\/(?:usr\/)?bin\/)?(?:ba|z|da|k)?sh\s+-[a-z]*c\s+/i;
17
+ /** Power management takes a `sleep` argument that has nothing to do with waiting. */
18
+ const POWER_MANAGEMENT = /\b(?:pmset|systemsetup|caffeinate|displaysleep|disksleep)\b/;
19
+ /** `sleep N` as a real command word — never `./sleepless 300` or `--sleep=3`. */
20
+ const SLEEP_CALL = /(?<![\w.\-/])(?:\/(?:usr\/)?bin\/)?sleep\s+(\d+(?:\.\d+)?)/g;
21
+ const PURE = /^sleep\s+\d+(?:\.\d+)?$/;
22
+ const LEADING = /^\(?\s*(?:\/(?:usr\/)?bin\/)?sleep\s+\d+(?:\.\d+)?\s*(?:[;&|]|$)/;
23
+ const LOOP = /\b(?:while|until|for)\b[\s\S]*?(?<![\w.\-/])(?:\/(?:usr\/)?bin\/)?sleep\s+\d/;
24
+ const TRAILING = /[;&|]\s*(?:\/(?:usr\/)?bin\/)?sleep\s+\d+(?:\.\d+)?\s*\)?\s*$/;
25
+ function unwrap(command) {
26
+ let current = command.trim();
27
+ for (let depth = 0; depth < 3; depth += 1) {
28
+ const stripped = current.replace(SHELL_WRAPPER, "");
29
+ if (stripped === current)
30
+ break;
31
+ current = stripped.trim();
32
+ const quote = current[0];
33
+ if ((quote === "'" || quote === '"') && current.endsWith(quote) && current.length > 1) {
34
+ current = current.slice(1, -1).trim();
35
+ }
36
+ }
37
+ return current;
38
+ }
39
+ function longestSleepSeconds(command) {
40
+ SLEEP_CALL.lastIndex = 0;
41
+ let longest;
42
+ for (const match of command.matchAll(SLEEP_CALL)) {
43
+ const seconds = Number.parseFloat(match[1] ?? "");
44
+ if (!Number.isFinite(seconds))
45
+ continue;
46
+ if (longest === undefined || seconds > longest)
47
+ longest = seconds;
48
+ }
49
+ return longest;
50
+ }
51
+ /**
52
+ * Classify a bash command as a sleep-wait, or `undefined` when it is ordinary
53
+ * work (including short settle sleeps below the thresholds).
54
+ */
55
+ export function classifySleepWait(command) {
56
+ if (POWER_MANAGEMENT.test(command))
57
+ return undefined;
58
+ const unwrapped = unwrap(command);
59
+ const seconds = longestSleepSeconds(unwrapped);
60
+ if (seconds === undefined)
61
+ return undefined;
62
+ if (LOOP.test(unwrapped)) {
63
+ return seconds >= SLEEP_WAIT_LOOP_THRESHOLD_SECONDS ? { kind: "sleep-wait", rule: "R3", seconds } : undefined;
64
+ }
65
+ if (seconds < SLEEP_WAIT_THRESHOLD_SECONDS)
66
+ return undefined;
67
+ if (PURE.test(unwrapped))
68
+ return { kind: "sleep-wait", rule: "R1", seconds };
69
+ if (LEADING.test(unwrapped))
70
+ return { kind: "sleep-wait", rule: "R2", seconds };
71
+ if (TRAILING.test(unwrapped))
72
+ return { kind: "sleep-wait", rule: "R4", seconds };
73
+ return undefined;
74
+ }
75
+ //# sourceMappingURL=sleep-wait.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep-wait.js","sourceRoot":"","sources":["../../../../../../src/core/extensions/builtin/terminal/tools/sleep-wait.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,yEAAyE;AACzE,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AAC/C,mFAAmF;AACnF,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC;AAYnD,qFAAqF;AACrF,MAAM,aAAa,GAAG,wFAAwF,CAAC;AAC/G,qFAAqF;AACrF,MAAM,gBAAgB,GAAG,6DAA6D,CAAC;AACvF,iFAAiF;AACjF,MAAM,UAAU,GAAG,6DAA6D,CAAC;AAEjF,MAAM,IAAI,GAAG,yBAAyB,CAAC;AACvC,MAAM,OAAO,GAAG,kEAAkE,CAAC;AACnF,MAAM,IAAI,GAAG,8EAA8E,CAAC;AAC5F,MAAM,QAAQ,GAAG,+DAA+D,CAAC;AAEjF,SAAS,MAAM,CAAC,OAAe;IAC9B,IAAI,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAC7B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACpD,IAAI,QAAQ,KAAK,OAAO;YAAE,MAAM;QAChC,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvF,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvC,CAAC;IACF,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,SAAS,mBAAmB,CAAC,OAAe;IAC3C,UAAU,CAAC,SAAS,GAAG,CAAC,CAAC;IACzB,IAAI,OAA2B,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAClD,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,SAAS;QACxC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,GAAG,OAAO;YAAE,OAAO,GAAG,OAAO,CAAC;IACnE,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAChD,IAAI,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IACrD,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,OAAO,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAE5C,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,OAAO,OAAO,IAAI,iCAAiC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/G,CAAC;IACD,IAAI,OAAO,GAAG,4BAA4B;QAAE,OAAO,SAAS,CAAC;IAC7D,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC7E,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAChF,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACjF,OAAO,SAAS,CAAC;AAClB,CAAC","sourcesContent":["/**\n * Detects commands whose whole purpose is to *wait* — `sleep 270; git log`,\n * `for ...; do ...; sleep 5; done`, a bare `sleep 30`. Such a command should not\n * hold the foreground: it detaches early so the session can keep working while\n * the wait finishes in the background and its completion notification lands.\n *\n * Settle sleeps (`pkill; sleep 1`, a 2s server warm-up) are deliberately NOT\n * sleep-waits: they are short by construction and detaching them would cost a\n * notification round-trip for nothing.\n */\n\n/** Minimum `sleep` seconds for a non-loop command to count as a wait. */\nexport const SLEEP_WAIT_THRESHOLD_SECONDS = 10;\n/** Minimum per-iteration `sleep` seconds for a polling loop to count as a wait. */\nexport const SLEEP_WAIT_LOOP_THRESHOLD_SECONDS = 2;\n\nexport type SleepWaitRule = \"R1\" | \"R2\" | \"R3\" | \"R4\";\n\nexport interface SleepWaitClassification {\n\treadonly kind: \"sleep-wait\";\n\t/** R1 pure, R2 leading chain, R3 polling loop, R4 trailing. */\n\treadonly rule: SleepWaitRule;\n\t/** Longest `sleep` argument that made the command match. */\n\treadonly seconds: number;\n}\n\n/** `env A=1 bash -lc '...'` wrappers hide the real command from every rule below. */\nconst SHELL_WRAPPER = /^(?:env\\s+(?:[\\w.]+=[^\\s]*\\s+)*)?(?:\\/(?:usr\\/)?bin\\/)?(?:ba|z|da|k)?sh\\s+-[a-z]*c\\s+/i;\n/** Power management takes a `sleep` argument that has nothing to do with waiting. */\nconst POWER_MANAGEMENT = /\\b(?:pmset|systemsetup|caffeinate|displaysleep|disksleep)\\b/;\n/** `sleep N` as a real command word — never `./sleepless 300` or `--sleep=3`. */\nconst SLEEP_CALL = /(?<![\\w.\\-/])(?:\\/(?:usr\\/)?bin\\/)?sleep\\s+(\\d+(?:\\.\\d+)?)/g;\n\nconst PURE = /^sleep\\s+\\d+(?:\\.\\d+)?$/;\nconst LEADING = /^\\(?\\s*(?:\\/(?:usr\\/)?bin\\/)?sleep\\s+\\d+(?:\\.\\d+)?\\s*(?:[;&|]|$)/;\nconst LOOP = /\\b(?:while|until|for)\\b[\\s\\S]*?(?<![\\w.\\-/])(?:\\/(?:usr\\/)?bin\\/)?sleep\\s+\\d/;\nconst TRAILING = /[;&|]\\s*(?:\\/(?:usr\\/)?bin\\/)?sleep\\s+\\d+(?:\\.\\d+)?\\s*\\)?\\s*$/;\n\nfunction unwrap(command: string): string {\n\tlet current = command.trim();\n\tfor (let depth = 0; depth < 3; depth += 1) {\n\t\tconst stripped = current.replace(SHELL_WRAPPER, \"\");\n\t\tif (stripped === current) break;\n\t\tcurrent = stripped.trim();\n\t\tconst quote = current[0];\n\t\tif ((quote === \"'\" || quote === '\"') && current.endsWith(quote) && current.length > 1) {\n\t\t\tcurrent = current.slice(1, -1).trim();\n\t\t}\n\t}\n\treturn current;\n}\n\nfunction longestSleepSeconds(command: string): number | undefined {\n\tSLEEP_CALL.lastIndex = 0;\n\tlet longest: number | undefined;\n\tfor (const match of command.matchAll(SLEEP_CALL)) {\n\t\tconst seconds = Number.parseFloat(match[1] ?? \"\");\n\t\tif (!Number.isFinite(seconds)) continue;\n\t\tif (longest === undefined || seconds > longest) longest = seconds;\n\t}\n\treturn longest;\n}\n\n/**\n * Classify a bash command as a sleep-wait, or `undefined` when it is ordinary\n * work (including short settle sleeps below the thresholds).\n */\nexport function classifySleepWait(command: string): SleepWaitClassification | undefined {\n\tif (POWER_MANAGEMENT.test(command)) return undefined;\n\tconst unwrapped = unwrap(command);\n\tconst seconds = longestSleepSeconds(unwrapped);\n\tif (seconds === undefined) return undefined;\n\n\tif (LOOP.test(unwrapped)) {\n\t\treturn seconds >= SLEEP_WAIT_LOOP_THRESHOLD_SECONDS ? { kind: \"sleep-wait\", rule: \"R3\", seconds } : undefined;\n\t}\n\tif (seconds < SLEEP_WAIT_THRESHOLD_SECONDS) return undefined;\n\tif (PURE.test(unwrapped)) return { kind: \"sleep-wait\", rule: \"R1\", seconds };\n\tif (LEADING.test(unwrapped)) return { kind: \"sleep-wait\", rule: \"R2\", seconds };\n\tif (TRAILING.test(unwrapped)) return { kind: \"sleep-wait\", rule: \"R4\", seconds };\n\treturn undefined;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"tps.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/builtin/tps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAmBhD,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,QAmExC"}
1
+ {"version":3,"file":"tps.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/builtin/tps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAkBhD,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,QAmExC"}
@@ -47,7 +47,6 @@ export default function (pi) {
47
47
  let output = 0;
48
48
  let cacheRead = 0;
49
49
  let cacheWrite = 0;
50
- let totalTokens = 0;
51
50
  for (const message of event.messages) {
52
51
  if (!isAssistantMessage(message))
53
52
  continue;
@@ -55,13 +54,14 @@ export default function (pi) {
55
54
  output += message.usage?.output ?? 0;
56
55
  cacheRead += message.usage?.cacheRead ?? 0;
57
56
  cacheWrite += message.usage?.cacheWrite ?? 0;
58
- totalTokens += message.usage?.totalTokens ?? 0;
59
57
  }
60
58
  if (output <= 0)
61
59
  return;
62
60
  const elapsedSeconds = elapsedMs / 1000;
63
61
  const tokensPerSecond = output / elapsedSeconds;
64
- const message = `TPS ${tokensPerSecond.toFixed(1)} tok/s. out ${output.toLocaleString()}, in ${input.toLocaleString()}, cache r/w ${cacheRead.toLocaleString()}/${cacheWrite.toLocaleString()}, total ${totalTokens.toLocaleString()}, ${elapsedSeconds.toFixed(1)}s`;
62
+ const promptTokens = input + cacheRead + cacheWrite;
63
+ const cacheHitRate = promptTokens > 0 ? (cacheRead / promptTokens) * 100 : 0;
64
+ const message = `TPS ${tokensPerSecond.toFixed(1)} tok/s. Cache hit ${cacheHitRate.toFixed(1)}%, ${elapsedSeconds.toFixed(1)}s`;
65
65
  ctx.ui.notify(message, "info");
66
66
  });
67
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tps.js","sourceRoot":"","sources":["../../../../src/core/extensions/builtin/tps.ts"],"names":[],"mappings":"AAaA,SAAS,kBAAkB,CAAC,OAAgB;IAC3C,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,IAAI,GAAI,OAA8B,CAAC,IAAI,CAAC;IAClD,OAAO,IAAI,KAAK,WAAW,CAAC;AAC7B,CAAC;AAED,MAAM,CAAC,OAAO,WAAW,EAAgB;IACxC,IAAI,sBAAsB,GAAkB,IAAI,CAAC;IACjD,IAAI,kBAAkB,GAAG,CAAC,CAAC;IAE3B,MAAM,2BAA2B,GAAG,GAAG,EAAE;QACxC,IAAI,sBAAsB,KAAK,IAAI;YAAE,OAAO;QAE5C,qEAAqE;QACrE,mEAAmE;QACnE,sEAAsE;QACtE,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,sBAAsB,CAAC;QAC7D,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YACnB,kBAAkB,IAAI,SAAS,CAAC;QACjC,CAAC;QACD,sBAAsB,GAAG,IAAI,CAAC;IAC/B,CAAC,CAAC;IAEF,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,GAAG,EAAE;QACzB,sBAAsB,GAAG,IAAI,CAAC;QAC9B,kBAAkB,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE;QAChC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,OAAO,CAAC;YAAE,OAAO;QAE/C,2BAA2B,EAAE,CAAC;QAC9B,sBAAsB,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE;QAC9B,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,OAAO,CAAC;YAAE,OAAO;QAE/C,2BAA2B,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACjC,2BAA2B,EAAE,CAAC;QAE9B,MAAM,SAAS,GAAG,kBAAkB,CAAC;QACrC,sBAAsB,GAAG,IAAI,CAAC;QAC9B,kBAAkB,GAAG,CAAC,CAAC;QAEvB,IAAI,CAAC,GAAG,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,SAAS,IAAI,CAAC;YAAE,OAAO;QAE3B,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,WAAW,GAAG,CAAC,CAAC;QAEpB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACtC,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC;gBAAE,SAAS;YAC3C,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,CAAC;YACnC,MAAM,IAAI,OAAO,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC;YACrC,SAAS,IAAI,OAAO,CAAC,KAAK,EAAE,SAAS,IAAI,CAAC,CAAC;YAC3C,UAAU,IAAI,OAAO,CAAC,KAAK,EAAE,UAAU,IAAI,CAAC,CAAC;YAC7C,WAAW,IAAI,OAAO,CAAC,KAAK,EAAE,WAAW,IAAI,CAAC,CAAC;QAChD,CAAC;QAED,IAAI,MAAM,IAAI,CAAC;YAAE,OAAO;QAExB,MAAM,cAAc,GAAG,SAAS,GAAG,IAAI,CAAC;QACxC,MAAM,eAAe,GAAG,MAAM,GAAG,cAAc,CAAC;QAChD,MAAM,OAAO,GAAG,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,MAAM,CAAC,cAAc,EAAE,QAAQ,KAAK,CAAC,cAAc,EAAE,eAAe,SAAS,CAAC,cAAc,EAAE,IAAI,UAAU,CAAC,cAAc,EAAE,WAAW,WAAW,CAAC,cAAc,EAAE,KAAK,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;QACtQ,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { ExtensionAPI } from \"../types.ts\";\n\ntype AssistantMessageLike = {\n\trole: \"assistant\";\n\tusage?: {\n\t\tinput?: number;\n\t\toutput?: number;\n\t\tcacheRead?: number;\n\t\tcacheWrite?: number;\n\t\ttotalTokens?: number;\n\t};\n};\n\nfunction isAssistantMessage(message: unknown): message is AssistantMessageLike {\n\tif (!message || typeof message !== \"object\") return false;\n\tconst role = (message as { role?: unknown }).role;\n\treturn role === \"assistant\";\n}\n\nexport default function (pi: ExtensionAPI) {\n\tlet activeAssistantStartMs: number | null = null;\n\tlet assistantElapsedMs = 0;\n\n\tconst finishActiveAssistantTiming = () => {\n\t\tif (activeAssistantStartMs === null) return;\n\n\t\t// Use the monotonic clock so a wall-clock jump backward (NTP skew or\n\t\t// manual time change) between message_start and message_end cannot\n\t\t// produce a non-positive interval that suppresses a valid TPS notice.\n\t\tconst elapsedMs = performance.now() - activeAssistantStartMs;\n\t\tif (elapsedMs > 0) {\n\t\t\tassistantElapsedMs += elapsedMs;\n\t\t}\n\t\tactiveAssistantStartMs = null;\n\t};\n\n\tpi.on(\"agent_start\", () => {\n\t\tactiveAssistantStartMs = null;\n\t\tassistantElapsedMs = 0;\n\t});\n\n\tpi.on(\"message_start\", (event) => {\n\t\tif (!isAssistantMessage(event.message)) return;\n\n\t\tfinishActiveAssistantTiming();\n\t\tactiveAssistantStartMs = performance.now();\n\t});\n\n\tpi.on(\"message_end\", (event) => {\n\t\tif (!isAssistantMessage(event.message)) return;\n\n\t\tfinishActiveAssistantTiming();\n\t});\n\n\tpi.on(\"agent_end\", (event, ctx) => {\n\t\tfinishActiveAssistantTiming();\n\n\t\tconst elapsedMs = assistantElapsedMs;\n\t\tactiveAssistantStartMs = null;\n\t\tassistantElapsedMs = 0;\n\n\t\tif (!ctx.hasUI) return;\n\t\tif (elapsedMs <= 0) return;\n\n\t\tlet input = 0;\n\t\tlet output = 0;\n\t\tlet cacheRead = 0;\n\t\tlet cacheWrite = 0;\n\t\tlet totalTokens = 0;\n\n\t\tfor (const message of event.messages) {\n\t\t\tif (!isAssistantMessage(message)) continue;\n\t\t\tinput += message.usage?.input ?? 0;\n\t\t\toutput += message.usage?.output ?? 0;\n\t\t\tcacheRead += message.usage?.cacheRead ?? 0;\n\t\t\tcacheWrite += message.usage?.cacheWrite ?? 0;\n\t\t\ttotalTokens += message.usage?.totalTokens ?? 0;\n\t\t}\n\n\t\tif (output <= 0) return;\n\n\t\tconst elapsedSeconds = elapsedMs / 1000;\n\t\tconst tokensPerSecond = output / elapsedSeconds;\n\t\tconst message = `TPS ${tokensPerSecond.toFixed(1)} tok/s. out ${output.toLocaleString()}, in ${input.toLocaleString()}, cache r/w ${cacheRead.toLocaleString()}/${cacheWrite.toLocaleString()}, total ${totalTokens.toLocaleString()}, ${elapsedSeconds.toFixed(1)}s`;\n\t\tctx.ui.notify(message, \"info\");\n\t});\n}\n"]}
1
+ {"version":3,"file":"tps.js","sourceRoot":"","sources":["../../../../src/core/extensions/builtin/tps.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExtensionAPI } from \"../types.ts\";\n\ntype AssistantMessageLike = {\n\trole: \"assistant\";\n\tusage?: {\n\t\tinput?: number;\n\t\toutput?: number;\n\t\tcacheRead?: number;\n\t\tcacheWrite?: number;\n\t};\n};\n\nfunction isAssistantMessage(message: unknown): message is AssistantMessageLike {\n\tif (!message || typeof message !== \"object\") return false;\n\tconst role = (message as { role?: unknown }).role;\n\treturn role === \"assistant\";\n}\n\nexport default function (pi: ExtensionAPI) {\n\tlet activeAssistantStartMs: number | null = null;\n\tlet assistantElapsedMs = 0;\n\n\tconst finishActiveAssistantTiming = () => {\n\t\tif (activeAssistantStartMs === null) return;\n\n\t\t// Use the monotonic clock so a wall-clock jump backward (NTP skew or\n\t\t// manual time change) between message_start and message_end cannot\n\t\t// produce a non-positive interval that suppresses a valid TPS notice.\n\t\tconst elapsedMs = performance.now() - activeAssistantStartMs;\n\t\tif (elapsedMs > 0) {\n\t\t\tassistantElapsedMs += elapsedMs;\n\t\t}\n\t\tactiveAssistantStartMs = null;\n\t};\n\n\tpi.on(\"agent_start\", () => {\n\t\tactiveAssistantStartMs = null;\n\t\tassistantElapsedMs = 0;\n\t});\n\n\tpi.on(\"message_start\", (event) => {\n\t\tif (!isAssistantMessage(event.message)) return;\n\n\t\tfinishActiveAssistantTiming();\n\t\tactiveAssistantStartMs = performance.now();\n\t});\n\n\tpi.on(\"message_end\", (event) => {\n\t\tif (!isAssistantMessage(event.message)) return;\n\n\t\tfinishActiveAssistantTiming();\n\t});\n\n\tpi.on(\"agent_end\", (event, ctx) => {\n\t\tfinishActiveAssistantTiming();\n\n\t\tconst elapsedMs = assistantElapsedMs;\n\t\tactiveAssistantStartMs = null;\n\t\tassistantElapsedMs = 0;\n\n\t\tif (!ctx.hasUI) return;\n\t\tif (elapsedMs <= 0) return;\n\n\t\tlet input = 0;\n\t\tlet output = 0;\n\t\tlet cacheRead = 0;\n\t\tlet cacheWrite = 0;\n\n\t\tfor (const message of event.messages) {\n\t\t\tif (!isAssistantMessage(message)) continue;\n\t\t\tinput += message.usage?.input ?? 0;\n\t\t\toutput += message.usage?.output ?? 0;\n\t\t\tcacheRead += message.usage?.cacheRead ?? 0;\n\t\t\tcacheWrite += message.usage?.cacheWrite ?? 0;\n\t\t}\n\n\t\tif (output <= 0) return;\n\n\t\tconst elapsedSeconds = elapsedMs / 1000;\n\t\tconst tokensPerSecond = output / elapsedSeconds;\n\t\tconst promptTokens = input + cacheRead + cacheWrite;\n\t\tconst cacheHitRate = promptTokens > 0 ? (cacheRead / promptTokens) * 100 : 0;\n\t\tconst message = `TPS ${tokensPerSecond.toFixed(1)} tok/s. Cache hit ${cacheHitRate.toFixed(1)}%, ${elapsedSeconds.toFixed(1)}s`;\n\t\tctx.ui.notify(message, \"info\");\n\t});\n}\n"]}
@@ -9,7 +9,11 @@ export interface FallbackSelector {
9
9
  export type FallbackChains = Readonly<Record<string, readonly string[]>>;
10
10
  export type FallbackModelLookup = readonly Model<Api>[] | {
11
11
  getAll(): Model<Api>[];
12
+ isUsingOAuth?(model: Model<Api>): boolean;
13
+ hasConfiguredAuth?(model: Model<Api>): boolean;
12
14
  };
15
+ /** An empty entry list is the documented opt-out; it survives as a tombstone. */
16
+ export declare function isChainTombstone(entries: readonly string[] | undefined): boolean;
13
17
  /**
14
18
  * Resolves only complete provider/model selectors. The full input is resolved first
15
19
  * so colons belonging to a model id remain part of that id; aliases then resolve
@@ -18,7 +22,15 @@ export type FallbackModelLookup = readonly Model<Api>[] | {
18
22
  export declare function parseFallbackSelector(raw: string, lookup: FallbackModelLookup): FallbackSelector | undefined;
19
23
  export declare function formatSelector(model: Model<Api>, thinkingLevel?: ThinkingLevel): string;
20
24
  export declare function baseSelector(selector: Pick<FallbackSelector, "provider" | "id">): string;
21
- /** Converts validated configuration to canonical selector strings for runtime lookup. */
25
+ /**
26
+ * Converts validated configuration to canonical selector strings for runtime lookup.
27
+ *
28
+ * A bare key (no provider prefix) is a model-family policy: it expands to one
29
+ * canonical key per provider serving that family, so a chain shipped for
30
+ * `claude-fable-5` applies no matter which provider the user attached it through.
31
+ * Provider-qualified keys and entries keep exact semantics, and an explicit key
32
+ * always overrides the expansion it collides with.
33
+ */
22
34
  export declare function canonicalizeFallbackChains(chains: FallbackChains, lookup: FallbackModelLookup): FallbackChains;
23
35
  export declare function resolveChainKey(currentModel: Model<Api>, currentThinking: ThinkingLevel | undefined, chains: FallbackChains): string | undefined;
24
36
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"chains.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/chains.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAIxD,MAAM,WAAW,gBAAgB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAEzE,MAAM,MAAM,mBAAmB,GAAG,SAAS,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG;IAAE,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAA;CAAE,CAAC;AAmBrF;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,SAAS,CAkC5G;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,EAAE,aAAa,GAAG,MAAM,CAGvF;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,IAAI,CAAC,GAAG,MAAM,CAExF;AAED,yFAAyF;AACzF,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,mBAAmB,GAAG,cAAc,CAiB9G;AAED,wBAAgB,eAAe,CAC9B,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,EACxB,eAAe,EAAE,aAAa,GAAG,SAAS,EAC1C,MAAM,EAAE,cAAc,GACpB,MAAM,GAAG,SAAS,CAKpB;AAoBD;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,eAAe,EAAE,gBAAgB,GAAG,MAAM,GACxC,SAAS,MAAM,EAAE,CAQnB"}
1
+ {"version":3,"file":"chains.d.ts","sourceRoot":"","sources":["../../../src/core/retry-fallback/chains.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAKxD,MAAM,WAAW,gBAAgB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAEzE,MAAM,MAAM,mBAAmB,GAC5B,SAAS,KAAK,CAAC,GAAG,CAAC,EAAE,GACrB;IACA,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IACvB,YAAY,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;IAC1C,iBAAiB,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC;CAC9C,CAAC;AAyBL,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,OAAO,CAEhF;AAeD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,SAAS,CAkC5G;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,EAAE,aAAa,GAAG,MAAM,CAGvF;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,IAAI,CAAC,GAAG,MAAM,CAExF;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,mBAAmB,GAAG,cAAc,CA4D9G;AAED,wBAAgB,eAAe,CAC9B,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,EACxB,eAAe,EAAE,aAAa,GAAG,SAAS,EAC1C,MAAM,EAAE,cAAc,GACpB,MAAM,GAAG,SAAS,CAKpB;AAoBD;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,eAAe,EAAE,gBAAgB,GAAG,MAAM,GACxC,SAAS,MAAM,EAAE,CAQnB"}
@@ -1,8 +1,28 @@
1
1
  import { isValidThinkingLevel } from "../../cli/args.js";
2
2
  import { findExactModelReferenceMatch, parseModelPattern } from "../model-resolver.js";
3
+ import { MAX_PROVIDERS_PER_FAMILY, parseBareSelector, rankFamilyModels } from "./expansion.js";
3
4
  function availableModels(lookup) {
4
5
  return "getAll" in lookup ? lookup.getAll() : [...lookup];
5
6
  }
7
+ /**
8
+ * Auth tier is optional so array lookups and older callers keep working; without
9
+ * it every provider lands in the non-OAuth tier and the precedence table decides.
10
+ */
11
+ function authTiers(lookup) {
12
+ if (Array.isArray(lookup))
13
+ return { isUsingOAuth: () => false };
14
+ const registry = lookup;
15
+ return {
16
+ isUsingOAuth: (model) => registry.isUsingOAuth?.(model) === true,
17
+ hasConfiguredAuth: typeof registry.hasConfiguredAuth === "function"
18
+ ? (model) => registry.hasConfiguredAuth?.(model) === true
19
+ : undefined,
20
+ };
21
+ }
22
+ /** An empty entry list is the documented opt-out; it survives as a tombstone. */
23
+ export function isChainTombstone(entries) {
24
+ return Array.isArray(entries) && entries.length === 0;
25
+ }
6
26
  function selectorReference(raw) {
7
27
  const trimmed = raw.trim();
8
28
  if (!trimmed.includes("/") || trimmed.includes("*"))
@@ -59,20 +79,72 @@ export function formatSelector(model, thinkingLevel) {
59
79
  export function baseSelector(selector) {
60
80
  return `${selector.provider}/${selector.id}`;
61
81
  }
62
- /** Converts validated configuration to canonical selector strings for runtime lookup. */
82
+ /**
83
+ * Converts validated configuration to canonical selector strings for runtime lookup.
84
+ *
85
+ * A bare key (no provider prefix) is a model-family policy: it expands to one
86
+ * canonical key per provider serving that family, so a chain shipped for
87
+ * `claude-fable-5` applies no matter which provider the user attached it through.
88
+ * Provider-qualified keys and entries keep exact semantics, and an explicit key
89
+ * always overrides the expansion it collides with.
90
+ */
63
91
  export function canonicalizeFallbackChains(chains, lookup) {
92
+ const models = availableModels(lookup);
93
+ const tiers = authTiers(lookup);
64
94
  const canonical = {};
95
+ const explicitKeys = new Set();
96
+ const tombstones = new Set();
97
+ const expandEntries = (entries, keySelector) => entries.flatMap((entry) => {
98
+ const bare = parseBareSelector(entry);
99
+ if (bare) {
100
+ return rankFamilyModels(models, bare.family, tiers, { limit: MAX_PROVIDERS_PER_FAMILY })
101
+ .map((model) => bare.thinkingLevel ? `${formatSelector(model)}:${bare.thinkingLevel}` : formatSelector(model))
102
+ .filter((selector) => normalizedBase(selector) !== normalizedBase(keySelector));
103
+ }
104
+ const parsedEntry = parseFallbackSelector(entry, models);
105
+ return parsedEntry ? [formatParsedSelector(parsedEntry)] : [];
106
+ });
107
+ // Bare keys expand first so a same-named explicit key can overwrite them.
65
108
  for (const [key, entries] of Object.entries(chains)) {
66
- const parsedKey = parseFallbackSelector(key, lookup);
109
+ const bareKey = parseBareSelector(key);
110
+ if (!bareKey || !Array.isArray(entries))
111
+ continue;
112
+ if (isChainTombstone(entries)) {
113
+ for (const model of rankFamilyModels(models, bareKey.family, tiers)) {
114
+ tombstones.add(formatSelector(model).toLowerCase());
115
+ }
116
+ continue;
117
+ }
118
+ for (const model of rankFamilyModels(models, bareKey.family, tiers)) {
119
+ const keySelector = bareKey.thinkingLevel
120
+ ? `${formatSelector(model)}:${bareKey.thinkingLevel}`
121
+ : formatSelector(model);
122
+ const canonicalEntries = expandEntries(entries, keySelector);
123
+ if (canonicalEntries.length > 0)
124
+ canonical[keySelector] = canonicalEntries;
125
+ }
126
+ }
127
+ for (const [key, entries] of Object.entries(chains)) {
128
+ if (parseBareSelector(key))
129
+ continue;
130
+ const parsedKey = parseFallbackSelector(key, models);
67
131
  if (!parsedKey || !Array.isArray(entries))
68
132
  continue;
69
- const canonicalEntries = entries.flatMap((entry) => {
70
- const parsedEntry = parseFallbackSelector(entry, lookup);
71
- return parsedEntry ? [formatParsedSelector(parsedEntry)] : [];
72
- });
73
- if (canonicalEntries.length > 0) {
74
- canonical[formatParsedSelector(parsedKey)] = canonicalEntries;
133
+ const keySelector = formatParsedSelector(parsedKey);
134
+ if (isChainTombstone(entries)) {
135
+ tombstones.add(normalizedBase(keySelector));
136
+ continue;
75
137
  }
138
+ explicitKeys.add(keySelector.toLowerCase());
139
+ const canonicalEntries = expandEntries(entries, keySelector);
140
+ if (canonicalEntries.length > 0)
141
+ canonical[keySelector] = canonicalEntries;
142
+ }
143
+ for (const key of Object.keys(canonical)) {
144
+ if (explicitKeys.has(key.toLowerCase()))
145
+ continue;
146
+ if (tombstones.has(normalizedBase(key)))
147
+ delete canonical[key];
76
148
  }
77
149
  return canonical;
78
150
  }