@dreb/coding-agent 2.61.0 → 2.61.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +5 -3
  2. package/dist/cli/args.d.ts +1 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +2 -2
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-session.d.ts +2 -0
  7. package/dist/core/agent-session.d.ts.map +1 -1
  8. package/dist/core/agent-session.js +11 -3
  9. package/dist/core/agent-session.js.map +1 -1
  10. package/dist/core/dispatch-arbiter.d.ts.map +1 -1
  11. package/dist/core/dispatch-arbiter.js +2 -2
  12. package/dist/core/dispatch-arbiter.js.map +1 -1
  13. package/dist/core/model-registry.d.ts.map +1 -1
  14. package/dist/core/model-registry.js +1 -0
  15. package/dist/core/model-registry.js.map +1 -1
  16. package/dist/core/settings-manager.d.ts +4 -4
  17. package/dist/core/settings-manager.d.ts.map +1 -1
  18. package/dist/core/settings-manager.js.map +1 -1
  19. package/dist/core/thinking.d.ts.map +1 -1
  20. package/dist/core/thinking.js +9 -1
  21. package/dist/core/thinking.js.map +1 -1
  22. package/dist/core/tools/index.d.ts +6 -6
  23. package/dist/core/tools/subagent.d.ts +9 -9
  24. package/dist/core/tools/subagent.d.ts.map +1 -1
  25. package/dist/core/tools/subagent.js +4 -2
  26. package/dist/core/tools/subagent.js.map +1 -1
  27. package/dist/core/tools/watch-github-ci.d.ts.map +1 -1
  28. package/dist/core/tools/watch-github-ci.js +110 -67
  29. package/dist/core/tools/watch-github-ci.js.map +1 -1
  30. package/dist/main.d.ts.map +1 -1
  31. package/dist/main.js +6 -1
  32. package/dist/main.js.map +1 -1
  33. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  34. package/dist/modes/interactive/components/settings-selector.js +3 -2
  35. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  36. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  37. package/dist/modes/interactive/components/thinking-selector.js +2 -1
  38. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  39. package/dist/modes/interactive/theme/dark.json +1 -6
  40. package/dist/modes/interactive/theme/light.json +1 -6
  41. package/dist/modes/interactive/theme/theme-schema.json +10 -2
  42. package/dist/modes/interactive/theme/theme.d.ts +2 -2
  43. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  44. package/dist/modes/interactive/theme/theme.js +9 -3
  45. package/dist/modes/interactive/theme/theme.js.map +1 -1
  46. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  47. package/dist/modes/rpc/rpc-mode.js +1 -0
  48. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  49. package/dist/modes/rpc/rpc-types.d.ts +2 -0
  50. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  51. package/dist/modes/rpc/rpc-types.js.map +1 -1
  52. package/docs/agent-models.md +1 -1
  53. package/docs/custom-provider.md +2 -2
  54. package/docs/extensions.md +1 -1
  55. package/docs/models.md +3 -1
  56. package/docs/rpc.md +5 -4
  57. package/docs/sdk.md +1 -1
  58. package/docs/settings.md +2 -2
  59. package/docs/themes.md +3 -1
  60. package/docs/tui.md +1 -1
  61. package/examples/extensions/dynamic-resources/dynamic.json +1 -0
  62. package/examples/extensions/preset.ts +1 -1
  63. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"watch-github-ci.d.ts","sourceRoot":"","sources":["../../../src/core/tools/watch-github-ci.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AAGtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAK7D,QAAA,MAAM,mBAAmB;;EAOvB,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAExE,MAAM,WAAW,wBAAwB;IACxC,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,CACL,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;QACR,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;KACvB,KACG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,wBAAwB;IACxC,UAAU,CAAC,EAAE,kBAAkB,CAAC;CAChC;AAgCD,wBAAgB,6BAA6B,IAAI,kBAAkB,CA0ClE;AAYD,wBAAgB,iCAAiC,CAChD,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,wBAAwB,GAChC,cAAc,CAAC,OAAO,mBAAmB,EAAE,wBAAwB,CAAC,CAsHtE;AAED,wBAAgB,uBAAuB,CACtC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,wBAAwB,GAChC,SAAS,CAAC,OAAO,mBAAmB,CAAC,CAEvC;AAED,eAAO,MAAM,2BAA2B;;kCAAmD,CAAC;AAC5F,eAAO,MAAM,iBAAiB;;QAAyC,CAAC","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { waitForChildProcess } from \"../../utils/child-process.js\";\nimport { getShellEnv, killProcessTree } from \"../../utils/shell.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { renderTerminalOutput } from \"./terminal-render.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./truncate.js\";\n\nconst watchGithubCiSchema = Type.Object({\n\tpr: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Optional pull request number, URL, or branch. Omit to use the pull request for the current branch.\",\n\t\t}),\n\t),\n});\n\nexport type WatchGithubCiToolInput = Static<typeof watchGithubCiSchema>;\n\nexport interface WatchGithubCiToolDetails {\n\tstatus: \"watching\" | \"passed\" | \"failed\";\n\texitCode?: number;\n\toutputTruncated: boolean;\n}\n\nexport interface GithubCiOperations {\n\texec: (\n\t\targs: readonly string[],\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\tenv: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\nexport interface WatchGithubCiToolOptions {\n\toperations?: GithubCiOperations;\n}\n\n/**\n * Substrings (matched case-insensitively) that identify a `gh pr checks` CLI-level\n * failure rather than a real failed-check result. `gh pr checks` exits 1 for both,\n * and `--json` is incompatible with `--watch`, so the captured output is the only\n * way to tell them apart. These signatures are specific enough that they will not\n * appear in a normal check-status table.\n */\nconst GH_CLI_FAILURE_SIGNATURES = [\n\t\"no pull requests found\",\n\t\"could not resolve to a pullrequest\",\n\t\"failed to run git\",\n\t\"not a git repository\",\n\t\"http 401\",\n\t\"bad credentials\",\n\t\"could not parse\",\n\t\"invalid pull request\",\n\t\"authentication required\",\n\t\"could not find repository\",\n];\n\nfunction detectGhCliFailure(output: string): string | null {\n\tconst lower = output.toLowerCase();\n\tfor (const signature of GH_CLI_FAILURE_SIGNATURES) {\n\t\tif (lower.includes(signature)) {\n\t\t\treturn signature;\n\t\t}\n\t}\n\treturn null;\n}\n\nexport function createLocalGithubCiOperations(): GithubCiOperations {\n\treturn {\n\t\texec: (args, cwd, { onData, signal, env }) =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tif (!existsSync(cwd)) {\n\t\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst child = spawn(\"gh\", [...args], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tdetached: true,\n\t\t\t\t\tenv,\n\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t});\n\t\t\t\tchild.stdout?.on(\"data\", onData);\n\t\t\t\tchild.stderr?.on(\"data\", onData);\n\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\twaitForChildProcess(child)\n\t\t\t\t\t.then((exitCode) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolve({ exitCode });\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t});\n\t\t\t}),\n\t};\n}\n\nfunction formatOutput(chunks: readonly Buffer[], omittedEarlierOutput: boolean): { text: string; truncated: boolean } {\n\tconst rendered = renderTerminalOutput(Buffer.concat(chunks).toString(\"utf-8\"));\n\tconst truncation = truncateTail(rendered);\n\tconst prefix = omittedEarlierOutput ? \"[Earlier watch output omitted]\\n\" : \"\";\n\treturn {\n\t\ttext: `${prefix}${truncation.content || \"(no output)\"}`,\n\t\ttruncated: omittedEarlierOutput || truncation.truncated,\n\t};\n}\n\nexport function createWatchGithubCiToolDefinition(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): ToolDefinition<typeof watchGithubCiSchema, WatchGithubCiToolDetails> {\n\tconst operations = options?.operations ?? createLocalGithubCiOperations();\n\n\treturn {\n\t\tname: \"watch_github_ci\",\n\t\tlabel: \"watch GitHub CI\",\n\t\tdescription:\n\t\t\t\"Watch GitHub pull-request checks until they pass or fail. Uses the current branch's pull request by default, or an optional pull request number, URL, or branch. Returns the final GitHub CLI output without requiring user input.\",\n\t\tpromptSnippet: \"Watch GitHub pull-request CI until checks pass or fail\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `watch_github_ci` to wait for GitHub pull-request checks instead of asking the user, polling with repeated commands, sleeping, or using `wait`\",\n\t\t\t\"`watch_github_ci` blocks until the selected pull request's checks pass or fail; omit `pr` to use the current branch's pull request\",\n\t\t],\n\t\tparameters: watchGithubCiSchema,\n\t\tasync execute(_toolCallId, params, signal, onUpdate) {\n\t\t\tconst selector = params.pr?.trim();\n\t\t\tif (params.pr !== undefined && !selector) {\n\t\t\t\tthrow new Error(\"Pull request selector must not be blank.\");\n\t\t\t}\n\t\t\tif (selector?.startsWith(\"-\")) {\n\t\t\t\tthrow new Error(\"Pull request selector must not begin with '-'.\");\n\t\t\t}\n\n\t\t\tconst args = [\"pr\", \"checks\", ...(selector ? [selector] : []), \"--watch\", \"--fail-fast\"];\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\tlet chunksBytes = 0;\n\t\t\tlet omittedEarlierOutput = false;\n\t\t\tlet outputTruncated = false;\n\t\t\tconst maxBufferedBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t\tconst details = (status: WatchGithubCiToolDetails[\"status\"], exitCode?: number) => ({\n\t\t\t\tstatus,\n\t\t\t\texitCode,\n\t\t\t\toutputTruncated,\n\t\t\t});\n\t\t\tconst currentOutput = (): string => {\n\t\t\t\tconst formatted = formatOutput(chunks, omittedEarlierOutput);\n\t\t\t\toutputTruncated = formatted.truncated;\n\t\t\t\treturn formatted.text;\n\t\t\t};\n\t\t\tconst onData = (data: Buffer) => {\n\t\t\t\tchunks.push(data);\n\t\t\t\tchunksBytes += data.length;\n\t\t\t\twhile (chunksBytes > maxBufferedBytes) {\n\t\t\t\t\tconst excess = chunksBytes - maxBufferedBytes;\n\t\t\t\t\tconst first = chunks[0];\n\t\t\t\t\tif (first.length <= excess) {\n\t\t\t\t\t\tchunks.shift();\n\t\t\t\t\t\tchunksBytes -= first.length;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tchunks[0] = first.subarray(excess);\n\t\t\t\t\t\tchunksBytes -= excess;\n\t\t\t\t\t}\n\t\t\t\t\tomittedEarlierOutput = true;\n\t\t\t\t}\n\t\t\t\tonUpdate?.({\n\t\t\t\t\tcontent: [{ type: \"text\", text: currentOutput() }],\n\t\t\t\t\tdetails: details(\"watching\"),\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tonUpdate?.({ content: [], details: details(\"watching\") });\n\n\t\t\tlet exitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode } = await operations.exec(args, cwd, {\n\t\t\t\t\tonData,\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv: {\n\t\t\t\t\t\t...getShellEnv(),\n\t\t\t\t\t\tGH_PAGER: \"cat\",\n\t\t\t\t\t\tGH_EDITOR: \"cat\",\n\t\t\t\t\t\tNO_COLOR: \"1\",\n\t\t\t\t\t},\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = chunks.length > 0 ? `${currentOutput()}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI watch aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI watch could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst output = currentOutput();\n\t\t\tif (exitCode === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `GitHub CI checks passed.\\n\\n${output}` }],\n\t\t\t\t\tdetails: details(\"passed\", exitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (exitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(output);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched \"${cliFailure}\"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \\`gh auth status\\`.\\n\\n${output}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\\n\\n${output}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: details(\"failed\", exitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (exitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\\n\\n${output}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t`GitHub CI watch exited unexpectedly${exitCode === null ? \" without an exit code\" : ` with code ${exitCode}`}.\\n\\n${output}`,\n\t\t\t);\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiTool(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): AgentTool<typeof watchGithubCiSchema> {\n\treturn wrapToolDefinition(createWatchGithubCiToolDefinition(cwd, options));\n}\n\nexport const watchGithubCiToolDefinition = createWatchGithubCiToolDefinition(process.cwd());\nexport const watchGithubCiTool = createWatchGithubCiTool(process.cwd());\n"]}
1
+ {"version":3,"file":"watch-github-ci.d.ts","sourceRoot":"","sources":["../../../src/core/tools/watch-github-ci.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AAGtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAK7D,QAAA,MAAM,mBAAmB;;EAOvB,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAExE,MAAM,WAAW,wBAAwB;IACxC,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,CACL,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;QACR,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;KACvB,KACG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,wBAAwB;IACxC,UAAU,CAAC,EAAE,kBAAkB,CAAC;CAChC;AAgCD,wBAAgB,6BAA6B,IAAI,kBAAkB,CA0ClE;AA8CD,wBAAgB,iCAAiC,CAChD,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,wBAAwB,GAChC,cAAc,CAAC,OAAO,mBAAmB,EAAE,wBAAwB,CAAC,CA6ItE;AAED,wBAAgB,uBAAuB,CACtC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,wBAAwB,GAChC,SAAS,CAAC,OAAO,mBAAmB,CAAC,CAEvC;AAED,eAAO,MAAM,2BAA2B;;kCAAmD,CAAC;AAC5F,eAAO,MAAM,iBAAiB;;QAAyC,CAAC","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { waitForChildProcess } from \"../../utils/child-process.js\";\nimport { getShellEnv, killProcessTree } from \"../../utils/shell.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { renderTerminalOutput } from \"./terminal-render.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./truncate.js\";\n\nconst watchGithubCiSchema = Type.Object({\n\tpr: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Optional pull request number, URL, or branch. Omit to use the pull request for the current branch.\",\n\t\t}),\n\t),\n});\n\nexport type WatchGithubCiToolInput = Static<typeof watchGithubCiSchema>;\n\nexport interface WatchGithubCiToolDetails {\n\tstatus: \"watching\" | \"passed\" | \"failed\";\n\texitCode?: number;\n\toutputTruncated: boolean;\n}\n\nexport interface GithubCiOperations {\n\texec: (\n\t\targs: readonly string[],\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\tenv: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\nexport interface WatchGithubCiToolOptions {\n\toperations?: GithubCiOperations;\n}\n\n/**\n * Substrings (matched case-insensitively) that identify a `gh pr checks` CLI-level\n * failure rather than a real failed-check result. `gh pr checks` exits 1 for both,\n * and `--json` is incompatible with `--watch`, so the captured output is the only\n * way to tell them apart. These signatures are specific enough that they will not\n * appear in a normal check-status table.\n */\nconst GH_CLI_FAILURE_SIGNATURES = [\n\t\"no pull requests found\",\n\t\"could not resolve to a pullrequest\",\n\t\"failed to run git\",\n\t\"not a git repository\",\n\t\"http 401\",\n\t\"bad credentials\",\n\t\"could not parse\",\n\t\"invalid pull request\",\n\t\"authentication required\",\n\t\"could not find repository\",\n];\n\nfunction detectGhCliFailure(output: string): string | null {\n\tconst lower = output.toLowerCase();\n\tfor (const signature of GH_CLI_FAILURE_SIGNATURES) {\n\t\tif (lower.includes(signature)) {\n\t\t\treturn signature;\n\t\t}\n\t}\n\treturn null;\n}\n\nexport function createLocalGithubCiOperations(): GithubCiOperations {\n\treturn {\n\t\texec: (args, cwd, { onData, signal, env }) =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tif (!existsSync(cwd)) {\n\t\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst child = spawn(\"gh\", [...args], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tdetached: true,\n\t\t\t\t\tenv,\n\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t});\n\t\t\t\tchild.stdout?.on(\"data\", onData);\n\t\t\t\tchild.stderr?.on(\"data\", onData);\n\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\twaitForChildProcess(child)\n\t\t\t\t\t.then((exitCode) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolve({ exitCode });\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t});\n\t\t\t}),\n\t};\n}\n\ninterface OutputBuffer {\n\tappend(data: Buffer): void;\n\tformat(): { text: string; truncated: boolean };\n\thasOutput(): boolean;\n}\n\nfunction createOutputBuffer(omittedPrefix: string): OutputBuffer {\n\tconst chunks: Buffer[] = [];\n\tlet chunksBytes = 0;\n\tlet omittedEarlierOutput = false;\n\tconst maxBufferedBytes = DEFAULT_MAX_BYTES * 2;\n\n\treturn {\n\t\tappend(data) {\n\t\t\tchunks.push(data);\n\t\t\tchunksBytes += data.length;\n\t\t\twhile (chunksBytes > maxBufferedBytes) {\n\t\t\t\tconst excess = chunksBytes - maxBufferedBytes;\n\t\t\t\tconst first = chunks[0];\n\t\t\t\tif (first.length <= excess) {\n\t\t\t\t\tchunks.shift();\n\t\t\t\t\tchunksBytes -= first.length;\n\t\t\t\t} else {\n\t\t\t\t\tchunks[0] = first.subarray(excess);\n\t\t\t\t\tchunksBytes -= excess;\n\t\t\t\t}\n\t\t\t\tomittedEarlierOutput = true;\n\t\t\t}\n\t\t},\n\t\tformat() {\n\t\t\tconst rendered = renderTerminalOutput(Buffer.concat(chunks).toString(\"utf-8\"));\n\t\t\tconst truncation = truncateTail(rendered);\n\t\t\tconst prefix = omittedEarlierOutput ? `${omittedPrefix}\\n` : \"\";\n\t\t\treturn {\n\t\t\t\ttext: `${prefix}${truncation.content || \"(no output)\"}`,\n\t\t\t\ttruncated: omittedEarlierOutput || truncation.truncated,\n\t\t\t};\n\t\t},\n\t\thasOutput() {\n\t\t\treturn chunks.length > 0;\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiToolDefinition(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): ToolDefinition<typeof watchGithubCiSchema, WatchGithubCiToolDetails> {\n\tconst operations = options?.operations ?? createLocalGithubCiOperations();\n\n\treturn {\n\t\tname: \"watch_github_ci\",\n\t\tlabel: \"watch GitHub CI\",\n\t\tdescription:\n\t\t\t\"Watch GitHub pull-request checks until they pass or fail. Uses the current branch's pull request by default, or an optional pull request number, URL, or branch. Returns the final GitHub CLI output without requiring user input.\",\n\t\tpromptSnippet: \"Watch GitHub pull-request CI until checks pass or fail\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `watch_github_ci` to wait for GitHub pull-request checks instead of asking the user, polling with repeated commands, sleeping, or using `wait`\",\n\t\t\t\"`watch_github_ci` blocks until the selected pull request's checks pass or fail; omit `pr` to use the current branch's pull request\",\n\t\t],\n\t\tparameters: watchGithubCiSchema,\n\t\tasync execute(_toolCallId, params, signal, onUpdate) {\n\t\t\tconst selector = params.pr?.trim();\n\t\t\tif (params.pr !== undefined && !selector) {\n\t\t\t\tthrow new Error(\"Pull request selector must not be blank.\");\n\t\t\t}\n\t\t\tif (selector?.startsWith(\"-\")) {\n\t\t\t\tthrow new Error(\"Pull request selector must not begin with '-'.\");\n\t\t\t}\n\n\t\t\tconst watchArgs = [\"pr\", \"checks\", ...(selector ? [selector] : []), \"--watch\", \"--fail-fast\"];\n\t\t\tconst finalArgs = [\"pr\", \"checks\", ...(selector ? [selector] : [])];\n\t\t\tconst env = {\n\t\t\t\t...getShellEnv(),\n\t\t\t\tGH_PAGER: \"cat\",\n\t\t\t\tGH_EDITOR: \"cat\",\n\t\t\t\tNO_COLOR: \"1\",\n\t\t\t};\n\t\t\tconst details = (status: WatchGithubCiToolDetails[\"status\"], outputTruncated: boolean, exitCode?: number) => ({\n\t\t\t\tstatus,\n\t\t\t\texitCode,\n\t\t\t\toutputTruncated,\n\t\t\t});\n\n\t\t\tconst watchOutput = createOutputBuffer(\"[Earlier watch output omitted]\");\n\t\t\tconst onWatchData = (data: Buffer) => {\n\t\t\t\twatchOutput.append(data);\n\t\t\t\tconst formatted = watchOutput.format();\n\t\t\t\tonUpdate?.({\n\t\t\t\t\tcontent: [{ type: \"text\", text: formatted.text }],\n\t\t\t\t\tdetails: details(\"watching\", formatted.truncated),\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tonUpdate?.({ content: [], details: details(\"watching\", false) });\n\n\t\t\tlet watchExitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode: watchExitCode } = await operations.exec(watchArgs, cwd, {\n\t\t\t\t\tonData: onWatchData,\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv,\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = watchOutput.hasOutput() ? `${watchOutput.format().text}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI watch aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI watch could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst formattedWatchOutput = watchOutput.format();\n\t\t\tif (watchExitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(formattedWatchOutput.text);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched \"${cliFailure}\"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \\`gh auth status\\`.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else if (watchExitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t);\n\t\t\t} else if (watchExitCode !== 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited unexpectedly${watchExitCode === null ? \" without an exit code\" : ` with code ${watchExitCode}`}.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst finalOutput = createOutputBuffer(\"[Earlier final checks output omitted]\");\n\t\t\tlet finalExitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode: finalExitCode } = await operations.exec(finalArgs, cwd, {\n\t\t\t\t\tonData: (data) => finalOutput.append(data),\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv,\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = finalOutput.hasOutput() ? `${finalOutput.format().text}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI final checks query aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI final checks query could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst formattedFinalOutput = finalOutput.format();\n\t\t\tif (finalExitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(formattedFinalOutput.text);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI final checks query failed: the GitHub CLI reported an error (matched \"${cliFailure}\"). Re-check the selector and \\`gh auth status\\`.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else if (finalExitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI final checks query found checks still pending (exit code 8) after the watch reported a terminal result.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t} else if (finalExitCode !== 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI final checks query exited unexpectedly${finalExitCode === null ? \" without an exit code\" : ` with code ${finalExitCode}`}.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (watchExitCode !== finalExitCode) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI status changed between watch completion (exit code ${watchExitCode}) and the final checks query (exit code ${finalExitCode}); refusing to report a contradictory result.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (finalExitCode === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `GitHub CI checks passed.\\n\\n${formattedFinalOutput.text}` }],\n\t\t\t\t\tdetails: details(\"passed\", formattedFinalOutput.truncated, finalExitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: details(\"failed\", formattedFinalOutput.truncated, finalExitCode),\n\t\t\t};\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiTool(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): AgentTool<typeof watchGithubCiSchema> {\n\treturn wrapToolDefinition(createWatchGithubCiToolDefinition(cwd, options));\n}\n\nexport const watchGithubCiToolDefinition = createWatchGithubCiToolDefinition(process.cwd());\nexport const watchGithubCiTool = createWatchGithubCiTool(process.cwd());\n"]}
@@ -79,13 +79,41 @@ export function createLocalGithubCiOperations() {
79
79
  }),
80
80
  };
81
81
  }
82
- function formatOutput(chunks, omittedEarlierOutput) {
83
- const rendered = renderTerminalOutput(Buffer.concat(chunks).toString("utf-8"));
84
- const truncation = truncateTail(rendered);
85
- const prefix = omittedEarlierOutput ? "[Earlier watch output omitted]\n" : "";
82
+ function createOutputBuffer(omittedPrefix) {
83
+ const chunks = [];
84
+ let chunksBytes = 0;
85
+ let omittedEarlierOutput = false;
86
+ const maxBufferedBytes = DEFAULT_MAX_BYTES * 2;
86
87
  return {
87
- text: `${prefix}${truncation.content || "(no output)"}`,
88
- truncated: omittedEarlierOutput || truncation.truncated,
88
+ append(data) {
89
+ chunks.push(data);
90
+ chunksBytes += data.length;
91
+ while (chunksBytes > maxBufferedBytes) {
92
+ const excess = chunksBytes - maxBufferedBytes;
93
+ const first = chunks[0];
94
+ if (first.length <= excess) {
95
+ chunks.shift();
96
+ chunksBytes -= first.length;
97
+ }
98
+ else {
99
+ chunks[0] = first.subarray(excess);
100
+ chunksBytes -= excess;
101
+ }
102
+ omittedEarlierOutput = true;
103
+ }
104
+ },
105
+ format() {
106
+ const rendered = renderTerminalOutput(Buffer.concat(chunks).toString("utf-8"));
107
+ const truncation = truncateTail(rendered);
108
+ const prefix = omittedEarlierOutput ? `${omittedPrefix}\n` : "";
109
+ return {
110
+ text: `${prefix}${truncation.content || "(no output)"}`,
111
+ truncated: omittedEarlierOutput || truncation.truncated,
112
+ };
113
+ },
114
+ hasOutput() {
115
+ return chunks.length > 0;
116
+ },
89
117
  };
90
118
  }
91
119
  export function createWatchGithubCiToolDefinition(cwd, options) {
@@ -108,91 +136,106 @@ export function createWatchGithubCiToolDefinition(cwd, options) {
108
136
  if (selector?.startsWith("-")) {
109
137
  throw new Error("Pull request selector must not begin with '-'.");
110
138
  }
111
- const args = ["pr", "checks", ...(selector ? [selector] : []), "--watch", "--fail-fast"];
112
- const chunks = [];
113
- let chunksBytes = 0;
114
- let omittedEarlierOutput = false;
115
- let outputTruncated = false;
116
- const maxBufferedBytes = DEFAULT_MAX_BYTES * 2;
117
- const details = (status, exitCode) => ({
139
+ const watchArgs = ["pr", "checks", ...(selector ? [selector] : []), "--watch", "--fail-fast"];
140
+ const finalArgs = ["pr", "checks", ...(selector ? [selector] : [])];
141
+ const env = {
142
+ ...getShellEnv(),
143
+ GH_PAGER: "cat",
144
+ GH_EDITOR: "cat",
145
+ NO_COLOR: "1",
146
+ };
147
+ const details = (status, outputTruncated, exitCode) => ({
118
148
  status,
119
149
  exitCode,
120
150
  outputTruncated,
121
151
  });
122
- const currentOutput = () => {
123
- const formatted = formatOutput(chunks, omittedEarlierOutput);
124
- outputTruncated = formatted.truncated;
125
- return formatted.text;
126
- };
127
- const onData = (data) => {
128
- chunks.push(data);
129
- chunksBytes += data.length;
130
- while (chunksBytes > maxBufferedBytes) {
131
- const excess = chunksBytes - maxBufferedBytes;
132
- const first = chunks[0];
133
- if (first.length <= excess) {
134
- chunks.shift();
135
- chunksBytes -= first.length;
136
- }
137
- else {
138
- chunks[0] = first.subarray(excess);
139
- chunksBytes -= excess;
140
- }
141
- omittedEarlierOutput = true;
142
- }
152
+ const watchOutput = createOutputBuffer("[Earlier watch output omitted]");
153
+ const onWatchData = (data) => {
154
+ watchOutput.append(data);
155
+ const formatted = watchOutput.format();
143
156
  onUpdate?.({
144
- content: [{ type: "text", text: currentOutput() }],
145
- details: details("watching"),
157
+ content: [{ type: "text", text: formatted.text }],
158
+ details: details("watching", formatted.truncated),
146
159
  });
147
160
  };
148
- onUpdate?.({ content: [], details: details("watching") });
149
- let exitCode;
161
+ onUpdate?.({ content: [], details: details("watching", false) });
162
+ let watchExitCode;
150
163
  try {
151
- ({ exitCode } = await operations.exec(args, cwd, {
152
- onData,
164
+ ({ exitCode: watchExitCode } = await operations.exec(watchArgs, cwd, {
165
+ onData: onWatchData,
153
166
  signal,
154
- env: {
155
- ...getShellEnv(),
156
- GH_PAGER: "cat",
157
- GH_EDITOR: "cat",
158
- NO_COLOR: "1",
159
- },
167
+ env,
160
168
  }));
161
169
  }
162
170
  catch (error) {
163
- const output = chunks.length > 0 ? `${currentOutput()}\n\n` : "";
171
+ const output = watchOutput.hasOutput() ? `${watchOutput.format().text}\n\n` : "";
164
172
  if (error instanceof Error && error.message === "aborted") {
165
173
  throw new Error(`${output}GitHub CI watch aborted.`);
166
174
  }
167
175
  const message = error instanceof Error ? error.message : String(error);
168
176
  throw new Error(`${output}GitHub CI watch could not run: ${message}`);
169
177
  }
170
- const output = currentOutput();
171
- if (exitCode === 0) {
172
- return {
173
- content: [{ type: "text", text: `GitHub CI checks passed.\n\n${output}` }],
174
- details: details("passed", exitCode),
175
- };
178
+ const formattedWatchOutput = watchOutput.format();
179
+ if (watchExitCode === 1) {
180
+ const cliFailure = detectGhCliFailure(formattedWatchOutput.text);
181
+ if (cliFailure !== null) {
182
+ throw new Error(`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched "${cliFailure}"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \`gh auth status\`.\n\n${formattedWatchOutput.text}`);
183
+ }
184
+ }
185
+ else if (watchExitCode === 8) {
186
+ throw new Error(`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\n\n${formattedWatchOutput.text}`);
187
+ }
188
+ else if (watchExitCode !== 0) {
189
+ throw new Error(`GitHub CI watch exited unexpectedly${watchExitCode === null ? " without an exit code" : ` with code ${watchExitCode}`}.\n\n${formattedWatchOutput.text}`);
176
190
  }
177
- if (exitCode === 1) {
178
- const cliFailure = detectGhCliFailure(output);
191
+ const finalOutput = createOutputBuffer("[Earlier final checks output omitted]");
192
+ let finalExitCode;
193
+ try {
194
+ ({ exitCode: finalExitCode } = await operations.exec(finalArgs, cwd, {
195
+ onData: (data) => finalOutput.append(data),
196
+ signal,
197
+ env,
198
+ }));
199
+ }
200
+ catch (error) {
201
+ const output = finalOutput.hasOutput() ? `${finalOutput.format().text}\n\n` : "";
202
+ if (error instanceof Error && error.message === "aborted") {
203
+ throw new Error(`${output}GitHub CI final checks query aborted.`);
204
+ }
205
+ const message = error instanceof Error ? error.message : String(error);
206
+ throw new Error(`${output}GitHub CI final checks query could not run: ${message}`);
207
+ }
208
+ const formattedFinalOutput = finalOutput.format();
209
+ if (finalExitCode === 1) {
210
+ const cliFailure = detectGhCliFailure(formattedFinalOutput.text);
179
211
  if (cliFailure !== null) {
180
- throw new Error(`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched "${cliFailure}"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \`gh auth status\`.\n\n${output}`);
212
+ throw new Error(`GitHub CI final checks query failed: the GitHub CLI reported an error (matched "${cliFailure}"). Re-check the selector and \`gh auth status\`.\n\n${formattedFinalOutput.text}`);
181
213
  }
214
+ }
215
+ else if (finalExitCode === 8) {
216
+ throw new Error(`GitHub CI final checks query found checks still pending (exit code 8) after the watch reported a terminal result.\n\n${formattedFinalOutput.text}`);
217
+ }
218
+ else if (finalExitCode !== 0) {
219
+ throw new Error(`GitHub CI final checks query exited unexpectedly${finalExitCode === null ? " without an exit code" : ` with code ${finalExitCode}`}.\n\n${formattedFinalOutput.text}`);
220
+ }
221
+ if (watchExitCode !== finalExitCode) {
222
+ throw new Error(`GitHub CI status changed between watch completion (exit code ${watchExitCode}) and the final checks query (exit code ${finalExitCode}); refusing to report a contradictory result.\n\n${formattedFinalOutput.text}`);
223
+ }
224
+ if (finalExitCode === 0) {
182
225
  return {
183
- content: [
184
- {
185
- type: "text",
186
- text: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\n\n${output}`,
187
- },
188
- ],
189
- details: details("failed", exitCode),
226
+ content: [{ type: "text", text: `GitHub CI checks passed.\n\n${formattedFinalOutput.text}` }],
227
+ details: details("passed", formattedFinalOutput.truncated, finalExitCode),
190
228
  };
191
229
  }
192
- if (exitCode === 8) {
193
- throw new Error(`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\n\n${output}`);
194
- }
195
- throw new Error(`GitHub CI watch exited unexpectedly${exitCode === null ? " without an exit code" : ` with code ${exitCode}`}.\n\n${output}`);
230
+ return {
231
+ content: [
232
+ {
233
+ type: "text",
234
+ text: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\n\n${formattedFinalOutput.text}`,
235
+ },
236
+ ],
237
+ details: details("failed", formattedFinalOutput.truncated, finalExitCode),
238
+ };
196
239
  },
197
240
  };
198
241
  }
@@ -1 +1 @@
1
- {"version":3,"file":"watch-github-ci.js","sourceRoot":"","sources":["../../../src/core/tools/watch-github-ci.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { waitForChildProcess } from \"../../utils/child-process.js\";\nimport { getShellEnv, killProcessTree } from \"../../utils/shell.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { renderTerminalOutput } from \"./terminal-render.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./truncate.js\";\n\nconst watchGithubCiSchema = Type.Object({\n\tpr: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Optional pull request number, URL, or branch. Omit to use the pull request for the current branch.\",\n\t\t}),\n\t),\n});\n\nexport type WatchGithubCiToolInput = Static<typeof watchGithubCiSchema>;\n\nexport interface WatchGithubCiToolDetails {\n\tstatus: \"watching\" | \"passed\" | \"failed\";\n\texitCode?: number;\n\toutputTruncated: boolean;\n}\n\nexport interface GithubCiOperations {\n\texec: (\n\t\targs: readonly string[],\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\tenv: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\nexport interface WatchGithubCiToolOptions {\n\toperations?: GithubCiOperations;\n}\n\n/**\n * Substrings (matched case-insensitively) that identify a `gh pr checks` CLI-level\n * failure rather than a real failed-check result. `gh pr checks` exits 1 for both,\n * and `--json` is incompatible with `--watch`, so the captured output is the only\n * way to tell them apart. These signatures are specific enough that they will not\n * appear in a normal check-status table.\n */\nconst GH_CLI_FAILURE_SIGNATURES = [\n\t\"no pull requests found\",\n\t\"could not resolve to a pullrequest\",\n\t\"failed to run git\",\n\t\"not a git repository\",\n\t\"http 401\",\n\t\"bad credentials\",\n\t\"could not parse\",\n\t\"invalid pull request\",\n\t\"authentication required\",\n\t\"could not find repository\",\n];\n\nfunction detectGhCliFailure(output: string): string | null {\n\tconst lower = output.toLowerCase();\n\tfor (const signature of GH_CLI_FAILURE_SIGNATURES) {\n\t\tif (lower.includes(signature)) {\n\t\t\treturn signature;\n\t\t}\n\t}\n\treturn null;\n}\n\nexport function createLocalGithubCiOperations(): GithubCiOperations {\n\treturn {\n\t\texec: (args, cwd, { onData, signal, env }) =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tif (!existsSync(cwd)) {\n\t\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst child = spawn(\"gh\", [...args], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tdetached: true,\n\t\t\t\t\tenv,\n\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t});\n\t\t\t\tchild.stdout?.on(\"data\", onData);\n\t\t\t\tchild.stderr?.on(\"data\", onData);\n\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\twaitForChildProcess(child)\n\t\t\t\t\t.then((exitCode) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolve({ exitCode });\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t});\n\t\t\t}),\n\t};\n}\n\nfunction formatOutput(chunks: readonly Buffer[], omittedEarlierOutput: boolean): { text: string; truncated: boolean } {\n\tconst rendered = renderTerminalOutput(Buffer.concat(chunks).toString(\"utf-8\"));\n\tconst truncation = truncateTail(rendered);\n\tconst prefix = omittedEarlierOutput ? \"[Earlier watch output omitted]\\n\" : \"\";\n\treturn {\n\t\ttext: `${prefix}${truncation.content || \"(no output)\"}`,\n\t\ttruncated: omittedEarlierOutput || truncation.truncated,\n\t};\n}\n\nexport function createWatchGithubCiToolDefinition(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): ToolDefinition<typeof watchGithubCiSchema, WatchGithubCiToolDetails> {\n\tconst operations = options?.operations ?? createLocalGithubCiOperations();\n\n\treturn {\n\t\tname: \"watch_github_ci\",\n\t\tlabel: \"watch GitHub CI\",\n\t\tdescription:\n\t\t\t\"Watch GitHub pull-request checks until they pass or fail. Uses the current branch's pull request by default, or an optional pull request number, URL, or branch. Returns the final GitHub CLI output without requiring user input.\",\n\t\tpromptSnippet: \"Watch GitHub pull-request CI until checks pass or fail\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `watch_github_ci` to wait for GitHub pull-request checks instead of asking the user, polling with repeated commands, sleeping, or using `wait`\",\n\t\t\t\"`watch_github_ci` blocks until the selected pull request's checks pass or fail; omit `pr` to use the current branch's pull request\",\n\t\t],\n\t\tparameters: watchGithubCiSchema,\n\t\tasync execute(_toolCallId, params, signal, onUpdate) {\n\t\t\tconst selector = params.pr?.trim();\n\t\t\tif (params.pr !== undefined && !selector) {\n\t\t\t\tthrow new Error(\"Pull request selector must not be blank.\");\n\t\t\t}\n\t\t\tif (selector?.startsWith(\"-\")) {\n\t\t\t\tthrow new Error(\"Pull request selector must not begin with '-'.\");\n\t\t\t}\n\n\t\t\tconst args = [\"pr\", \"checks\", ...(selector ? [selector] : []), \"--watch\", \"--fail-fast\"];\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\tlet chunksBytes = 0;\n\t\t\tlet omittedEarlierOutput = false;\n\t\t\tlet outputTruncated = false;\n\t\t\tconst maxBufferedBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t\tconst details = (status: WatchGithubCiToolDetails[\"status\"], exitCode?: number) => ({\n\t\t\t\tstatus,\n\t\t\t\texitCode,\n\t\t\t\toutputTruncated,\n\t\t\t});\n\t\t\tconst currentOutput = (): string => {\n\t\t\t\tconst formatted = formatOutput(chunks, omittedEarlierOutput);\n\t\t\t\toutputTruncated = formatted.truncated;\n\t\t\t\treturn formatted.text;\n\t\t\t};\n\t\t\tconst onData = (data: Buffer) => {\n\t\t\t\tchunks.push(data);\n\t\t\t\tchunksBytes += data.length;\n\t\t\t\twhile (chunksBytes > maxBufferedBytes) {\n\t\t\t\t\tconst excess = chunksBytes - maxBufferedBytes;\n\t\t\t\t\tconst first = chunks[0];\n\t\t\t\t\tif (first.length <= excess) {\n\t\t\t\t\t\tchunks.shift();\n\t\t\t\t\t\tchunksBytes -= first.length;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tchunks[0] = first.subarray(excess);\n\t\t\t\t\t\tchunksBytes -= excess;\n\t\t\t\t\t}\n\t\t\t\t\tomittedEarlierOutput = true;\n\t\t\t\t}\n\t\t\t\tonUpdate?.({\n\t\t\t\t\tcontent: [{ type: \"text\", text: currentOutput() }],\n\t\t\t\t\tdetails: details(\"watching\"),\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tonUpdate?.({ content: [], details: details(\"watching\") });\n\n\t\t\tlet exitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode } = await operations.exec(args, cwd, {\n\t\t\t\t\tonData,\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv: {\n\t\t\t\t\t\t...getShellEnv(),\n\t\t\t\t\t\tGH_PAGER: \"cat\",\n\t\t\t\t\t\tGH_EDITOR: \"cat\",\n\t\t\t\t\t\tNO_COLOR: \"1\",\n\t\t\t\t\t},\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = chunks.length > 0 ? `${currentOutput()}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI watch aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI watch could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst output = currentOutput();\n\t\t\tif (exitCode === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `GitHub CI checks passed.\\n\\n${output}` }],\n\t\t\t\t\tdetails: details(\"passed\", exitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (exitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(output);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched \"${cliFailure}\"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \\`gh auth status\\`.\\n\\n${output}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\\n\\n${output}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: details(\"failed\", exitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (exitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\\n\\n${output}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t`GitHub CI watch exited unexpectedly${exitCode === null ? \" without an exit code\" : ` with code ${exitCode}`}.\\n\\n${output}`,\n\t\t\t);\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiTool(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): AgentTool<typeof watchGithubCiSchema> {\n\treturn wrapToolDefinition(createWatchGithubCiToolDefinition(cwd, options));\n}\n\nexport const watchGithubCiToolDefinition = createWatchGithubCiToolDefinition(process.cwd());\nexport const watchGithubCiTool = createWatchGithubCiTool(process.cwd());\n"]}
1
+ {"version":3,"file":"watch-github-ci.js","sourceRoot":"","sources":["../../../src/core/tools/watch-github-ci.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { waitForChildProcess } from \"../../utils/child-process.js\";\nimport { getShellEnv, killProcessTree } from \"../../utils/shell.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { renderTerminalOutput } from \"./terminal-render.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./truncate.js\";\n\nconst watchGithubCiSchema = Type.Object({\n\tpr: Type.Optional(\n\t\tType.String({\n\t\t\tdescription:\n\t\t\t\t\"Optional pull request number, URL, or branch. Omit to use the pull request for the current branch.\",\n\t\t}),\n\t),\n});\n\nexport type WatchGithubCiToolInput = Static<typeof watchGithubCiSchema>;\n\nexport interface WatchGithubCiToolDetails {\n\tstatus: \"watching\" | \"passed\" | \"failed\";\n\texitCode?: number;\n\toutputTruncated: boolean;\n}\n\nexport interface GithubCiOperations {\n\texec: (\n\t\targs: readonly string[],\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\tenv: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\nexport interface WatchGithubCiToolOptions {\n\toperations?: GithubCiOperations;\n}\n\n/**\n * Substrings (matched case-insensitively) that identify a `gh pr checks` CLI-level\n * failure rather than a real failed-check result. `gh pr checks` exits 1 for both,\n * and `--json` is incompatible with `--watch`, so the captured output is the only\n * way to tell them apart. These signatures are specific enough that they will not\n * appear in a normal check-status table.\n */\nconst GH_CLI_FAILURE_SIGNATURES = [\n\t\"no pull requests found\",\n\t\"could not resolve to a pullrequest\",\n\t\"failed to run git\",\n\t\"not a git repository\",\n\t\"http 401\",\n\t\"bad credentials\",\n\t\"could not parse\",\n\t\"invalid pull request\",\n\t\"authentication required\",\n\t\"could not find repository\",\n];\n\nfunction detectGhCliFailure(output: string): string | null {\n\tconst lower = output.toLowerCase();\n\tfor (const signature of GH_CLI_FAILURE_SIGNATURES) {\n\t\tif (lower.includes(signature)) {\n\t\t\treturn signature;\n\t\t}\n\t}\n\treturn null;\n}\n\nexport function createLocalGithubCiOperations(): GithubCiOperations {\n\treturn {\n\t\texec: (args, cwd, { onData, signal, env }) =>\n\t\t\tnew Promise((resolve, reject) => {\n\t\t\t\tif (!existsSync(cwd)) {\n\t\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst child = spawn(\"gh\", [...args], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tdetached: true,\n\t\t\t\t\tenv,\n\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t\t});\n\t\t\t\tchild.stdout?.on(\"data\", onData);\n\t\t\t\tchild.stderr?.on(\"data\", onData);\n\n\t\t\t\tconst onAbort = () => {\n\t\t\t\t\tif (child.pid) killProcessTree(child.pid);\n\t\t\t\t};\n\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\twaitForChildProcess(child)\n\t\t\t\t\t.then((exitCode) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolve({ exitCode });\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t});\n\t\t\t}),\n\t};\n}\n\ninterface OutputBuffer {\n\tappend(data: Buffer): void;\n\tformat(): { text: string; truncated: boolean };\n\thasOutput(): boolean;\n}\n\nfunction createOutputBuffer(omittedPrefix: string): OutputBuffer {\n\tconst chunks: Buffer[] = [];\n\tlet chunksBytes = 0;\n\tlet omittedEarlierOutput = false;\n\tconst maxBufferedBytes = DEFAULT_MAX_BYTES * 2;\n\n\treturn {\n\t\tappend(data) {\n\t\t\tchunks.push(data);\n\t\t\tchunksBytes += data.length;\n\t\t\twhile (chunksBytes > maxBufferedBytes) {\n\t\t\t\tconst excess = chunksBytes - maxBufferedBytes;\n\t\t\t\tconst first = chunks[0];\n\t\t\t\tif (first.length <= excess) {\n\t\t\t\t\tchunks.shift();\n\t\t\t\t\tchunksBytes -= first.length;\n\t\t\t\t} else {\n\t\t\t\t\tchunks[0] = first.subarray(excess);\n\t\t\t\t\tchunksBytes -= excess;\n\t\t\t\t}\n\t\t\t\tomittedEarlierOutput = true;\n\t\t\t}\n\t\t},\n\t\tformat() {\n\t\t\tconst rendered = renderTerminalOutput(Buffer.concat(chunks).toString(\"utf-8\"));\n\t\t\tconst truncation = truncateTail(rendered);\n\t\t\tconst prefix = omittedEarlierOutput ? `${omittedPrefix}\\n` : \"\";\n\t\t\treturn {\n\t\t\t\ttext: `${prefix}${truncation.content || \"(no output)\"}`,\n\t\t\t\ttruncated: omittedEarlierOutput || truncation.truncated,\n\t\t\t};\n\t\t},\n\t\thasOutput() {\n\t\t\treturn chunks.length > 0;\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiToolDefinition(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): ToolDefinition<typeof watchGithubCiSchema, WatchGithubCiToolDetails> {\n\tconst operations = options?.operations ?? createLocalGithubCiOperations();\n\n\treturn {\n\t\tname: \"watch_github_ci\",\n\t\tlabel: \"watch GitHub CI\",\n\t\tdescription:\n\t\t\t\"Watch GitHub pull-request checks until they pass or fail. Uses the current branch's pull request by default, or an optional pull request number, URL, or branch. Returns the final GitHub CLI output without requiring user input.\",\n\t\tpromptSnippet: \"Watch GitHub pull-request CI until checks pass or fail\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use `watch_github_ci` to wait for GitHub pull-request checks instead of asking the user, polling with repeated commands, sleeping, or using `wait`\",\n\t\t\t\"`watch_github_ci` blocks until the selected pull request's checks pass or fail; omit `pr` to use the current branch's pull request\",\n\t\t],\n\t\tparameters: watchGithubCiSchema,\n\t\tasync execute(_toolCallId, params, signal, onUpdate) {\n\t\t\tconst selector = params.pr?.trim();\n\t\t\tif (params.pr !== undefined && !selector) {\n\t\t\t\tthrow new Error(\"Pull request selector must not be blank.\");\n\t\t\t}\n\t\t\tif (selector?.startsWith(\"-\")) {\n\t\t\t\tthrow new Error(\"Pull request selector must not begin with '-'.\");\n\t\t\t}\n\n\t\t\tconst watchArgs = [\"pr\", \"checks\", ...(selector ? [selector] : []), \"--watch\", \"--fail-fast\"];\n\t\t\tconst finalArgs = [\"pr\", \"checks\", ...(selector ? [selector] : [])];\n\t\t\tconst env = {\n\t\t\t\t...getShellEnv(),\n\t\t\t\tGH_PAGER: \"cat\",\n\t\t\t\tGH_EDITOR: \"cat\",\n\t\t\t\tNO_COLOR: \"1\",\n\t\t\t};\n\t\t\tconst details = (status: WatchGithubCiToolDetails[\"status\"], outputTruncated: boolean, exitCode?: number) => ({\n\t\t\t\tstatus,\n\t\t\t\texitCode,\n\t\t\t\toutputTruncated,\n\t\t\t});\n\n\t\t\tconst watchOutput = createOutputBuffer(\"[Earlier watch output omitted]\");\n\t\t\tconst onWatchData = (data: Buffer) => {\n\t\t\t\twatchOutput.append(data);\n\t\t\t\tconst formatted = watchOutput.format();\n\t\t\t\tonUpdate?.({\n\t\t\t\t\tcontent: [{ type: \"text\", text: formatted.text }],\n\t\t\t\t\tdetails: details(\"watching\", formatted.truncated),\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tonUpdate?.({ content: [], details: details(\"watching\", false) });\n\n\t\t\tlet watchExitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode: watchExitCode } = await operations.exec(watchArgs, cwd, {\n\t\t\t\t\tonData: onWatchData,\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv,\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = watchOutput.hasOutput() ? `${watchOutput.format().text}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI watch aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI watch could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst formattedWatchOutput = watchOutput.format();\n\t\t\tif (watchExitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(formattedWatchOutput.text);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI watch could not query checks: the GitHub CLI reported an error (matched \"${cliFailure}\"). This is a CLI-level failure such as a bad pull-request selector, no pull request for the branch, a missing repository, or an authentication problem — not a failed-check result. Re-check the selector and \\`gh auth status\\`.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else if (watchExitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited while checks were still pending (exit code 8); refusing to treat this as a terminal result.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t);\n\t\t\t} else if (watchExitCode !== 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI watch exited unexpectedly${watchExitCode === null ? \" without an exit code\" : ` with code ${watchExitCode}`}.\\n\\n${formattedWatchOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst finalOutput = createOutputBuffer(\"[Earlier final checks output omitted]\");\n\t\t\tlet finalExitCode: number | null;\n\t\t\ttry {\n\t\t\t\t({ exitCode: finalExitCode } = await operations.exec(finalArgs, cwd, {\n\t\t\t\t\tonData: (data) => finalOutput.append(data),\n\t\t\t\t\tsignal,\n\t\t\t\t\tenv,\n\t\t\t\t}));\n\t\t\t} catch (error) {\n\t\t\t\tconst output = finalOutput.hasOutput() ? `${finalOutput.format().text}\\n\\n` : \"\";\n\t\t\t\tif (error instanceof Error && error.message === \"aborted\") {\n\t\t\t\t\tthrow new Error(`${output}GitHub CI final checks query aborted.`);\n\t\t\t\t}\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`${output}GitHub CI final checks query could not run: ${message}`);\n\t\t\t}\n\n\t\t\tconst formattedFinalOutput = finalOutput.format();\n\t\t\tif (finalExitCode === 1) {\n\t\t\t\tconst cliFailure = detectGhCliFailure(formattedFinalOutput.text);\n\t\t\t\tif (cliFailure !== null) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`GitHub CI final checks query failed: the GitHub CLI reported an error (matched \"${cliFailure}\"). Re-check the selector and \\`gh auth status\\`.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else if (finalExitCode === 8) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI final checks query found checks still pending (exit code 8) after the watch reported a terminal result.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t} else if (finalExitCode !== 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI final checks query exited unexpectedly${finalExitCode === null ? \" without an exit code\" : ` with code ${finalExitCode}`}.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (watchExitCode !== finalExitCode) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub CI status changed between watch completion (exit code ${watchExitCode}) and the final checks query (exit code ${finalExitCode}); refusing to report a contradictory result.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (finalExitCode === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `GitHub CI checks passed.\\n\\n${formattedFinalOutput.text}` }],\n\t\t\t\t\tdetails: details(\"passed\", formattedFinalOutput.truncated, finalExitCode),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `GitHub CI checks did not pass. The GitHub CLI exited with code 1.\\n\\n${formattedFinalOutput.text}`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: details(\"failed\", formattedFinalOutput.truncated, finalExitCode),\n\t\t\t};\n\t\t},\n\t};\n}\n\nexport function createWatchGithubCiTool(\n\tcwd: string,\n\toptions?: WatchGithubCiToolOptions,\n): AgentTool<typeof watchGithubCiSchema> {\n\treturn wrapToolDefinition(createWatchGithubCiToolDefinition(cwd, options));\n}\n\nexport const watchGithubCiToolDefinition = createWatchGithubCiToolDefinition(process.cwd());\nexport const watchGithubCiTool = createWatchGithubCiTool(process.cwd());\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,KAAK,IAAI,EAAwB,MAAM,eAAe,CAAC;AAYhE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAsC,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIhG,OAAO,EAAE,KAAK,yBAAyB,EAAsB,MAAM,eAAe,CAAC;AACnF,OAAO,EAAoB,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAme7D,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,IAAI,EACZ,YAAY,EAAE,WAAW,EAAE,EAC3B,cAAc,EAAE,cAAc,GAAG,SAAS,EAC1C,aAAa,EAAE,aAAa,EAC5B,eAAe,EAAE,eAAe,GAC9B;IAAE,OAAO,EAAE,yBAAyB,CAAC;IAAC,oBAAoB,EAAE,OAAO,CAAA;CAAE,CAyFvE;AAgED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,iBA0UxC","sourcesContent":["/**\n * Main entry point for the coding agent CLI.\n *\n * This file handles CLI argument parsing and translates them into\n * createAgentSession() options. The SDK does the heavy lifting.\n */\n\nimport { type ImageContent, modelsAreEqual, supportsXhigh } from \"@dreb/ai\";\nimport chalk from \"chalk\";\nimport { createInterface } from \"readline\";\nimport { type Args, parseArgs, printHelp } from \"./cli/args.js\";\nimport { selectConfig } from \"./cli/config-selector.js\";\nimport { processFileArguments } from \"./cli/file-processor.js\";\nimport { buildInitialMessage } from \"./cli/initial-message.js\";\nimport { listModels } from \"./cli/list-models.js\";\nimport { selectSession } from \"./cli/session-picker.js\";\nimport { APP_NAME, getAgentDir, getModelsPath, loadProvidersEnv, VERSION } from \"./config.js\";\nimport { AuthStorage } from \"./core/auth-storage.js\";\nimport { exportFromFile } from \"./core/export-html/index.js\";\nimport type { LoadExtensionsResult } from \"./core/extensions/index.js\";\nimport { migrateKeybindingsConfigFile } from \"./core/keybindings.js\";\nimport { log } from \"./core/logger.js\";\nimport { ModelRegistry } from \"./core/model-registry.js\";\nimport { resolveCliModel, resolveModelScope, type ScopedModel } from \"./core/model-resolver.js\";\nimport { restoreStdout, takeOverStdout } from \"./core/output-guard.js\";\nimport { DefaultPackageManager } from \"./core/package-manager.js\";\nimport { DefaultResourceLoader } from \"./core/resource-loader.js\";\nimport { type CreateAgentSessionOptions, createAgentSession } from \"./core/sdk.js\";\nimport { type SessionInfo, SessionManager } from \"./core/session-manager.js\";\nimport { SettingsManager } from \"./core/settings-manager.js\";\nimport { printTimings, resetTimings, time } from \"./core/timings.js\";\nimport { allTools } from \"./core/tools/index.js\";\nimport { runMigrations, showDeprecationWarnings } from \"./migrations.js\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.js\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.js\";\n\n/**\n * Read all content from piped stdin.\n * Returns undefined if stdin is a TTY (interactive terminal).\n */\nasync function readPipedStdin(): Promise<string | undefined> {\n\t// If stdin is a TTY, we're running interactively - don't read stdin\n\tif (process.stdin.isTTY) {\n\t\treturn undefined;\n\t}\n\n\treturn new Promise((resolve) => {\n\t\tlet data = \"\";\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.on(\"data\", (chunk) => {\n\t\t\tdata += chunk;\n\t\t});\n\t\tprocess.stdin.on(\"end\", () => {\n\t\t\tresolve(data.trim() || undefined);\n\t\t});\n\t\tprocess.stdin.resume();\n\t});\n}\n\nfunction reportSettingsErrors(settingsManager: SettingsManager, context: string): void {\n\tconst errors = settingsManager.drainErrors();\n\tfor (const { scope, error } of errors) {\n\t\tlog.warn(chalk.yellow(`Warning (${context}, ${scope} settings): ${error.message}`));\n\t\tif (error.stack) {\n\t\t\tlog.warn(chalk.dim(error.stack));\n\t\t}\n\t}\n}\n\nfunction isTruthyEnvFlag(value: string | undefined): boolean {\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ntype PackageCommand = \"install\" | \"remove\" | \"update\" | \"list\";\n\ninterface PackageCommandOptions {\n\tcommand: PackageCommand;\n\tsource?: string;\n\tlocal: boolean;\n\thelp: boolean;\n\tinvalidOption?: string;\n}\n\nfunction getPackageCommandUsage(command: PackageCommand): string {\n\tswitch (command) {\n\t\tcase \"install\":\n\t\t\treturn `${APP_NAME} install <source> [-l]`;\n\t\tcase \"remove\":\n\t\t\treturn `${APP_NAME} remove <source> [-l]`;\n\t\tcase \"update\":\n\t\t\treturn `${APP_NAME} update [source]`;\n\t\tcase \"list\":\n\t\t\treturn `${APP_NAME} list`;\n\t}\n}\n\nfunction printPackageCommandHelp(command: PackageCommand): void {\n\tswitch (command) {\n\t\tcase \"install\":\n\t\t\tconsole.log(`${chalk.bold(\"Usage:\")}\n ${getPackageCommandUsage(\"install\")}\n\nInstall a package and add it to settings.\n\nOptions:\n -l, --local Install project-locally (.dreb/settings.json)\n\nExamples:\n ${APP_NAME} install npm:@foo/bar\n ${APP_NAME} install git:github.com/user/repo\n ${APP_NAME} install git:git@github.com:user/repo\n ${APP_NAME} install https://github.com/user/repo\n ${APP_NAME} install ssh://git@github.com/user/repo\n ${APP_NAME} install ./local/path\n`);\n\t\t\treturn;\n\n\t\tcase \"remove\":\n\t\t\tconsole.log(`${chalk.bold(\"Usage:\")}\n ${getPackageCommandUsage(\"remove\")}\n\nRemove a package and its source from settings.\nAlias: ${APP_NAME} uninstall <source> [-l]\n\nOptions:\n -l, --local Remove from project settings (.dreb/settings.json)\n\nExamples:\n ${APP_NAME} remove npm:@foo/bar\n ${APP_NAME} uninstall npm:@foo/bar\n`);\n\t\t\treturn;\n\n\t\tcase \"update\":\n\t\t\tconsole.log(`${chalk.bold(\"Usage:\")}\n ${getPackageCommandUsage(\"update\")}\n\nUpdate installed packages.\nIf <source> is provided, only that package is updated.\n`);\n\t\t\treturn;\n\n\t\tcase \"list\":\n\t\t\tconsole.log(`${chalk.bold(\"Usage:\")}\n ${getPackageCommandUsage(\"list\")}\n\nList installed packages from user and project settings.\n`);\n\t\t\treturn;\n\t}\n}\n\nfunction parsePackageCommand(args: string[]): PackageCommandOptions | undefined {\n\tconst [rawCommand, ...rest] = args;\n\tlet command: PackageCommand | undefined;\n\tif (rawCommand === \"uninstall\") {\n\t\tcommand = \"remove\";\n\t} else if (rawCommand === \"install\" || rawCommand === \"remove\" || rawCommand === \"update\" || rawCommand === \"list\") {\n\t\tcommand = rawCommand;\n\t}\n\tif (!command) {\n\t\treturn undefined;\n\t}\n\n\tlet local = false;\n\tlet help = false;\n\tlet invalidOption: string | undefined;\n\tlet source: string | undefined;\n\n\tfor (const arg of rest) {\n\t\tif (arg === \"-h\" || arg === \"--help\") {\n\t\t\thelp = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (arg === \"-l\" || arg === \"--local\") {\n\t\t\tif (command === \"install\" || command === \"remove\") {\n\t\t\t\tlocal = true;\n\t\t\t} else {\n\t\t\t\tinvalidOption = invalidOption ?? arg;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (arg.startsWith(\"-\")) {\n\t\t\tinvalidOption = invalidOption ?? arg;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!source) {\n\t\t\tsource = arg;\n\t\t}\n\t}\n\n\treturn { command, source, local, help, invalidOption };\n}\n\nasync function handlePackageCommand(args: string[]): Promise<boolean> {\n\tconst options = parsePackageCommand(args);\n\tif (!options) {\n\t\treturn false;\n\t}\n\n\tif (options.help) {\n\t\tprintPackageCommandHelp(options.command);\n\t\treturn true;\n\t}\n\n\tif (options.invalidOption) {\n\t\tlog.error(chalk.red(`Unknown option ${options.invalidOption} for \"${options.command}\".`));\n\t\tlog.error(chalk.dim(`Use \"${APP_NAME} --help\" or \"${getPackageCommandUsage(options.command)}\".`));\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n\n\tconst source = options.source;\n\tif ((options.command === \"install\" || options.command === \"remove\") && !source) {\n\t\tlog.error(chalk.red(`Missing ${options.command} source.`));\n\t\tlog.error(chalk.dim(`Usage: ${getPackageCommandUsage(options.command)}`));\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst settingsManager = SettingsManager.create(cwd, agentDir);\n\treportSettingsErrors(settingsManager, \"package command\");\n\tconst packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });\n\n\tpackageManager.setProgressCallback((event) => {\n\t\tif (event.type === \"start\") {\n\t\t\tprocess.stdout.write(chalk.dim(`${event.message}\\n`));\n\t\t}\n\t});\n\n\ttry {\n\t\tswitch (options.command) {\n\t\t\tcase \"install\":\n\t\t\t\tawait packageManager.install(source!, { local: options.local });\n\t\t\t\tpackageManager.addSourceToSettings(source!, { local: options.local });\n\t\t\t\tconsole.log(chalk.green(`Installed ${source}`));\n\t\t\t\treturn true;\n\n\t\t\tcase \"remove\": {\n\t\t\t\tawait packageManager.remove(source!, { local: options.local });\n\t\t\t\tconst removed = packageManager.removeSourceFromSettings(source!, { local: options.local });\n\t\t\t\tif (!removed) {\n\t\t\t\t\tlog.error(chalk.red(`No matching package found for ${source}`));\n\t\t\t\t\tprocess.exitCode = 1;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tconsole.log(chalk.green(`Removed ${source}`));\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"list\": {\n\t\t\t\tconst globalSettings = settingsManager.getGlobalSettings();\n\t\t\t\tconst projectSettings = settingsManager.getProjectSettings();\n\t\t\t\tconst globalPackages = globalSettings.packages ?? [];\n\t\t\t\tconst projectPackages = projectSettings.packages ?? [];\n\n\t\t\t\tif (globalPackages.length === 0 && projectPackages.length === 0) {\n\t\t\t\t\tconsole.log(chalk.dim(\"No packages installed.\"));\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tconst formatPackage = (pkg: (typeof globalPackages)[number], scope: \"user\" | \"project\") => {\n\t\t\t\t\tconst source = typeof pkg === \"string\" ? pkg : pkg.source;\n\t\t\t\t\tconst filtered = typeof pkg === \"object\";\n\t\t\t\t\tconst display = filtered ? `${source} (filtered)` : source;\n\t\t\t\t\tconsole.log(` ${display}`);\n\t\t\t\t\tconst path = packageManager.getInstalledPath(source, scope);\n\t\t\t\t\tif (path) {\n\t\t\t\t\t\tconsole.log(chalk.dim(` ${path}`));\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tif (globalPackages.length > 0) {\n\t\t\t\t\tconsole.log(chalk.bold(\"User packages:\"));\n\t\t\t\t\tfor (const pkg of globalPackages) {\n\t\t\t\t\t\tformatPackage(pkg, \"user\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (projectPackages.length > 0) {\n\t\t\t\t\tif (globalPackages.length > 0) console.log();\n\t\t\t\t\tconsole.log(chalk.bold(\"Project packages:\"));\n\t\t\t\t\tfor (const pkg of projectPackages) {\n\t\t\t\t\t\tformatPackage(pkg, \"project\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"update\":\n\t\t\t\tawait packageManager.update(source);\n\t\t\t\tif (source) {\n\t\t\t\t\tconsole.log(chalk.green(`Updated ${source}`));\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(chalk.green(\"Updated packages\"));\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : \"Unknown package command error\";\n\t\tlog.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n}\n\nasync function prepareInitialMessage(\n\tparsed: Args,\n\tautoResizeImages: boolean,\n\tstdinContent?: string,\n): Promise<{\n\tinitialMessage?: string;\n\tinitialImages?: ImageContent[];\n}> {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn buildInitialMessage({ parsed, stdinContent });\n\t}\n\n\tconst { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages });\n\treturn buildInitialMessage({\n\t\tparsed,\n\t\tfileText: text,\n\t\tfileImages: images,\n\t\tstdinContent,\n\t});\n}\n\n/** Result from resolving a session argument */\ntype ResolvedSession =\n\t| { type: \"path\"; path: string } // Direct file path\n\t| { type: \"local\"; path: string } // Found in current project\n\t| { type: \"global\"; path: string; cwd: string } // Found in different project\n\t| { type: \"not_found\"; arg: string }; // Not found anywhere\n\n/**\n * Resolve a session argument to a file path.\n * If it looks like a path, use as-is. Otherwise try to match as session ID prefix.\n */\nasync function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): Promise<ResolvedSession> {\n\t// If it looks like a file path, use as-is\n\tif (sessionArg.includes(\"/\") || sessionArg.includes(\"\\\\\") || sessionArg.endsWith(\".jsonl\")) {\n\t\treturn { type: \"path\", path: sessionArg };\n\t}\n\n\t// Try to match as session ID in current project first\n\tconst localSessions = await SessionManager.list(cwd, sessionDir);\n\tconst localMatches = localSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (localMatches.length >= 1) {\n\t\treturn { type: \"local\", path: localMatches[0].path };\n\t}\n\n\t// Try global search across all projects. listAll fails loud on real I/O errors —\n\t// convert a rejection into a clean, actionable exit instead of a raw stack dump,\n\t// matching the RPC (success:false) and TUI (\"Failed to load sessions\") consumers.\n\tlet allSessions: SessionInfo[];\n\ttry {\n\t\tallSessions = await SessionManager.listAll();\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tlog.error(chalk.red(`Failed to list sessions while resolving '${sessionArg}': ${message}`));\n\t\tprocess.exit(1);\n\t}\n\tconst globalMatches = allSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (globalMatches.length >= 1) {\n\t\tconst match = globalMatches[0];\n\t\treturn { type: \"global\", path: match.path, cwd: match.cwd };\n\t}\n\n\t// Not found anywhere\n\treturn { type: \"not_found\", arg: sessionArg };\n}\n\n/** Prompt user for yes/no confirmation */\nasync function promptConfirm(message: string): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createInterface({\n\t\t\tinput: process.stdin,\n\t\t\toutput: process.stdout,\n\t\t});\n\t\trl.question(`${message} [y/N] `, (answer) => {\n\t\t\trl.close();\n\t\t\tresolve(answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\");\n\t\t});\n\t});\n}\n\n/** Helper to call CLI-only session_directory handlers before the initial session manager is created */\nasync function callSessionDirectoryHook(extensions: LoadExtensionsResult, cwd: string): Promise<string | undefined> {\n\tlet customSessionDir: string | undefined;\n\n\tfor (const ext of extensions.extensions) {\n\t\tconst handlers = ext.handlers.get(\"session_directory\");\n\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\tfor (const handler of handlers) {\n\t\t\ttry {\n\t\t\t\tconst event = { type: \"session_directory\" as const, cwd };\n\t\t\t\tconst result = (await handler(event)) as { sessionDir?: string } | undefined;\n\n\t\t\t\tif (result?.sessionDir) {\n\t\t\t\t\tcustomSessionDir = result.sessionDir;\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\tlog.warn(chalk.red(`Extension \"${ext.path}\" session_directory handler failed: ${message}`));\n\t\t\t}\n\t\t}\n\t}\n\n\treturn customSessionDir;\n}\n\nfunction validateForkFlags(parsed: Args): void {\n\tif (!parsed.fork) return;\n\n\tconst conflictingFlags = [\n\t\tparsed.session ? \"--session\" : undefined,\n\t\tparsed.continue ? \"--continue\" : undefined,\n\t\tparsed.resume ? \"--resume\" : undefined,\n\t\tparsed.noSession ? \"--no-session\" : undefined,\n\t].filter((flag): flag is string => flag !== undefined);\n\n\tif (conflictingFlags.length > 0) {\n\t\tlog.error(chalk.red(`Error: --fork cannot be combined with ${conflictingFlags.join(\", \")}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nfunction forkSessionOrExit(sourcePath: string, cwd: string, sessionDir?: string): SessionManager {\n\ttry {\n\t\treturn SessionManager.forkFrom(sourcePath, cwd, sessionDir);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tlog.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nasync function createSessionManager(\n\tparsed: Args,\n\tcwd: string,\n\textensions: LoadExtensionsResult,\n\tsettingsManager: SettingsManager,\n): Promise<SessionManager | undefined> {\n\tif (parsed.noSession) {\n\t\treturn SessionManager.inMemory();\n\t}\n\n\t// Priority: CLI flag > settings.json > extension hook\n\tconst effectiveSessionDir =\n\t\tparsed.sessionDir ?? settingsManager.getSessionDir() ?? (await callSessionDirectoryHook(extensions, cwd));\n\n\tif (parsed.fork) {\n\t\tconst resolved = await resolveSessionPath(parsed.fork, cwd, effectiveSessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\tcase \"global\":\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, effectiveSessionDir);\n\n\t\t\tcase \"not_found\":\n\t\t\t\tlog.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.session) {\n\t\tconst resolved = await resolveSessionPath(parsed.session, cwd, effectiveSessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\t\treturn SessionManager.open(resolved.path, effectiveSessionDir);\n\n\t\t\tcase \"global\": {\n\t\t\t\t// Session found in different project - ask user if they want to fork\n\t\t\t\tconsole.log(chalk.yellow(`Session found in different project: ${resolved.cwd}`));\n\t\t\t\tconst shouldFork = await promptConfirm(\"Fork this session into current directory?\");\n\t\t\t\tif (!shouldFork) {\n\t\t\t\t\tconsole.log(chalk.dim(\"Aborted.\"));\n\t\t\t\t\tprocess.exit(0);\n\t\t\t\t}\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, effectiveSessionDir);\n\t\t\t}\n\n\t\t\tcase \"not_found\":\n\t\t\t\tlog.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\tif (parsed.continue) {\n\t\treturn SessionManager.continueRecent(cwd, effectiveSessionDir);\n\t}\n\t// --resume is handled separately (needs picker UI)\n\t// If effective session dir is set, create new session there\n\tif (effectiveSessionDir) {\n\t\treturn SessionManager.create(cwd, effectiveSessionDir);\n\t}\n\t// Default case (new session) returns undefined, SDK will create one\n\treturn undefined;\n}\n\nexport function buildSessionOptions(\n\tparsed: Args,\n\tscopedModels: ScopedModel[],\n\tsessionManager: SessionManager | undefined,\n\tmodelRegistry: ModelRegistry,\n\tsettingsManager: SettingsManager,\n): { options: CreateAgentSessionOptions; cliThinkingFromModel: boolean } {\n\tconst options: CreateAgentSessionOptions = {};\n\tlet cliThinkingFromModel = false;\n\n\tif (sessionManager) {\n\t\toptions.sessionManager = sessionManager;\n\t}\n\n\t// Model from CLI\n\t// - supports --provider <name> --model <pattern>\n\t// - supports --model <provider>/<pattern>\n\tif (parsed.model) {\n\t\tconst resolved = resolveCliModel({\n\t\t\tcliProvider: parsed.provider,\n\t\t\tcliModel: parsed.model,\n\t\t\tmodelRegistry,\n\t\t});\n\t\tif (resolved.warning) {\n\t\t\tconsole.warn(chalk.yellow(`Warning: ${resolved.warning}`));\n\t\t}\n\t\tif (resolved.error) {\n\t\t\tlog.error(chalk.red(resolved.error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (resolved.model) {\n\t\t\toptions.model = resolved.model;\n\t\t\t// Allow \"--model <pattern>:<thinking>\" as a shorthand.\n\t\t\t// Explicit --thinking still takes precedence (applied later).\n\t\t\tif (!parsed.thinking && resolved.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = resolved.thinkingLevel;\n\t\t\t\tcliThinkingFromModel = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!options.model && scopedModels.length > 0 && !parsed.continue && !parsed.resume && !parsed.session) {\n\t\t// Check if saved default is in scoped models - use it if so, otherwise first scoped model\n\t\tconst savedProvider = settingsManager.getDefaultProvider();\n\t\tconst savedModelId = settingsManager.getDefaultModel();\n\t\tconst savedModel = savedProvider && savedModelId ? modelRegistry.find(savedProvider, savedModelId) : undefined;\n\t\tconst savedInScope = savedModel ? scopedModels.find((sm) => modelsAreEqual(sm.model, savedModel)) : undefined;\n\n\t\tif (savedInScope) {\n\t\t\toptions.model = savedInScope.model;\n\t\t\t// Use thinking level from scoped model config if explicitly set\n\t\t\tif (!parsed.thinking && savedInScope.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = savedInScope.thinkingLevel;\n\t\t\t}\n\t\t} else {\n\t\t\toptions.model = scopedModels[0].model;\n\t\t\t// Use thinking level from first scoped model if explicitly set\n\t\t\tif (!parsed.thinking && scopedModels[0].thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = scopedModels[0].thinkingLevel;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Thinking level from CLI (takes precedence over scoped model thinking levels set above)\n\tif (parsed.thinking) {\n\t\toptions.thinkingLevel = parsed.thinking;\n\t}\n\n\t// Scoped models for Ctrl+P cycling\n\t// Keep thinking level undefined when not explicitly set in the model pattern.\n\t// Undefined means \"inherit current session thinking level\" during cycling.\n\tif (scopedModels.length > 0) {\n\t\toptions.scopedModels = scopedModels.map((sm) => ({\n\t\t\tmodel: sm.model,\n\t\t\tthinkingLevel: sm.thinkingLevel,\n\t\t}));\n\t}\n\n\t// API key from CLI - set in authStorage\n\t// (handled by caller before createAgentSession)\n\n\t// Tools\n\tif (parsed.noTools) {\n\t\t// --no-tools: start with no built-in tools\n\t\t// --tools can still add specific ones back\n\t\tif (parsed.tools && parsed.tools.length > 0) {\n\t\t\toptions.tools = parsed.tools.map((name) => allTools[name]);\n\t\t} else {\n\t\t\toptions.tools = [];\n\t\t}\n\t} else if (parsed.tools) {\n\t\toptions.tools = parsed.tools.map((name) => allTools[name]);\n\t}\n\n\treturn { options, cliThinkingFromModel };\n}\n\n/**\n * Handle `dreb dashboard [...args]` — delegate to the @dreb/dashboard package.\n *\n * The dashboard is a separate workspace package; coding-agent must not depend\n * on it (the dashboard depends on coding-agent — a hard dependency here would\n * be a cycle). Resolution is dynamic: installed → hand over argv; missing →\n * fail loudly with install instructions.\n */\nasync function handleDashboardCommand(args: string[]): Promise<boolean> {\n\tif (args[0] !== \"dashboard\") {\n\t\treturn false;\n\t}\n\tlet entryHref: string;\n\ttry {\n\t\tentryHref = import.meta.resolve(\"@dreb/dashboard\");\n\t} catch {\n\t\tconsole.error(\n\t\t\t\"The dashboard package is not installed.\\n\\n\" +\n\t\t\t\t\"Install it with:\\n\" +\n\t\t\t\t\" npm install -g @dreb/dashboard\\n\\n\" +\n\t\t\t\t\"Then run `dreb dashboard` again, or run `dreb-dashboard` directly.\",\n\t\t);\n\t\tprocess.exit(1);\n\t}\n\t// Re-exec the dashboard bin with the remaining args so its own arg parser\n\t// and process lifecycle (SIGINT shutdown of pooled runtimes) apply.\n\tconst { spawn } = await import(\"node:child_process\");\n\tconst { fileURLToPath } = await import(\"node:url\");\n\tconst child = spawn(process.execPath, [fileURLToPath(entryHref), ...args.slice(1)], {\n\t\tstdio: \"inherit\",\n\t});\n\tchild.on(\"exit\", (code) => process.exit(code ?? 0));\n\tchild.on(\"error\", (err) => {\n\t\tconsole.error(`Failed to launch dashboard: ${err.message}`);\n\t\tprocess.exit(1);\n\t});\n\treturn true;\n}\n\nasync function handleConfigCommand(args: string[]): Promise<boolean> {\n\tif (args[0] !== \"config\") {\n\t\treturn false;\n\t}\n\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst settingsManager = SettingsManager.create(cwd, agentDir);\n\treportSettingsErrors(settingsManager, \"config command\");\n\tconst packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });\n\n\tconst resolvedPaths = await packageManager.resolve();\n\n\tawait selectConfig({\n\t\tresolvedPaths,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tagentDir,\n\t});\n\n\tprocess.exit(0);\n}\n\nexport async function main(args: string[]) {\n\tresetTimings();\n\n\t// Load API keys and provider config from ~/.dreb/secrets/providers.env\n\t// Must happen before model resolution or provider initialization\n\tloadProvidersEnv();\n\n\tconst offlineMode = args.includes(\"--offline\") || isTruthyEnvFlag(process.env.DREB_OFFLINE);\n\tif (offlineMode) {\n\t\tprocess.env.DREB_OFFLINE = \"1\";\n\t}\n\n\tif (await handlePackageCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleDashboardCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleConfigCommand(args)) {\n\t\treturn;\n\t}\n\n\t// First pass: parse args to get --extension paths\n\tconst firstPass = parseArgs(args);\n\ttime(\"parseArgs.firstPass\");\n\tconst shouldTakeOverStdout = firstPass.mode !== undefined || firstPass.print || !process.stdin.isTTY;\n\tif (shouldTakeOverStdout) {\n\t\ttakeOverStdout();\n\t}\n\n\t// Run migrations (pass cwd for project-local migrations)\n\tconst { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());\n\ttime(\"runMigrations\");\n\n\t// Early load extensions to discover their CLI flags\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst settingsManager = SettingsManager.create(cwd, agentDir);\n\treportSettingsErrors(settingsManager, \"startup\");\n\tconst authStorage = AuthStorage.create();\n\tconst modelRegistry = new ModelRegistry(authStorage, getModelsPath());\n\n\tconst resourceLoader = new DefaultResourceLoader({\n\t\tcwd,\n\t\tagentDir,\n\t\tsettingsManager,\n\t\tadditionalExtensionPaths: firstPass.extensions,\n\t\tadditionalSkillPaths: firstPass.skills,\n\t\tadditionalPromptTemplatePaths: firstPass.promptTemplates,\n\t\tadditionalThemePaths: firstPass.themes,\n\t\tnoExtensions: firstPass.noExtensions,\n\t\tnoSkills: firstPass.noSkills,\n\t\tnoPromptTemplates: firstPass.noPromptTemplates,\n\t\tnoThemes: firstPass.noThemes,\n\t\tsystemPrompt: firstPass.systemPrompt,\n\t\tappendSystemPrompt: firstPass.appendSystemPrompt,\n\t});\n\ttime(\"createResourceLoader\");\n\tawait resourceLoader.reload();\n\ttime(\"resourceLoader.reload\");\n\n\tconst extensionsResult: LoadExtensionsResult = resourceLoader.getExtensions();\n\tfor (const { path, error } of extensionsResult.errors) {\n\t\tlog.warn(chalk.red(`Failed to load extension \"${path}\": ${error}`));\n\t}\n\n\t// Apply pending provider registrations from extensions immediately\n\t// so they're available for model resolution before AgentSession is created\n\tfor (const { name, config, extensionPath } of extensionsResult.runtime.pendingProviderRegistrations) {\n\t\ttry {\n\t\t\tmodelRegistry.registerProvider(name, config);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tlog.warn(chalk.red(`Extension \"${extensionPath}\" error: ${message}`));\n\t\t}\n\t}\n\textensionsResult.runtime.pendingProviderRegistrations = [];\n\n\tconst extensionFlags = new Map<string, { type: \"boolean\" | \"string\" }>();\n\tfor (const ext of extensionsResult.extensions) {\n\t\tfor (const [name, flag] of ext.flags) {\n\t\t\textensionFlags.set(name, { type: flag.type });\n\t\t}\n\t}\n\n\t// Second pass: parse args with extension flags\n\tconst parsed = parseArgs(args, extensionFlags);\n\ttime(\"parseArgs.secondPass\");\n\n\t// Pass flag values to extensions via runtime\n\tfor (const [name, value] of parsed.unknownFlags) {\n\t\textensionsResult.runtime.flagValues.set(name, value);\n\t}\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.help) {\n\t\tprintHelp();\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(modelRegistry, searchPattern);\n\t\tprocess.exit(0);\n\t}\n\n\t// Read piped stdin content (if any) - skip for RPC mode which uses stdin for JSON-RPC\n\tlet stdinContent: string | undefined;\n\tif (parsed.mode !== \"rpc\") {\n\t\tstdinContent = await readPipedStdin();\n\t\tif (stdinContent !== undefined) {\n\t\t\t// Force print mode since interactive mode requires a TTY for keyboard input\n\t\t\tparsed.print = true;\n\t\t}\n\t}\n\ttime(\"readPipedStdin\");\n\n\tif (parsed.export) {\n\t\tlet result: string;\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tresult = await exportFromFile(parsed.export, outputPath);\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tlog.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconsole.log(`Exported to: ${result}`);\n\t\tprocess.exit(0);\n\t}\n\n\tmigrateKeybindingsConfigFile(agentDir);\n\ttime(\"migrateKeybindingsConfigFile\");\n\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tlog.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tvalidateForkFlags(parsed);\n\n\tconst { initialMessage, initialImages } = await prepareInitialMessage(\n\t\tparsed,\n\t\tsettingsManager.getImageAutoResize(),\n\t\tstdinContent,\n\t);\n\ttime(\"prepareInitialMessage\");\n\tconst isInteractive = !parsed.print && parsed.mode === undefined;\n\tconst startupBenchmark = isTruthyEnvFlag(process.env.DREB_STARTUP_BENCHMARK);\n\tif (startupBenchmark && !isInteractive) {\n\t\tlog.error(chalk.red(\"Error: DREB_STARTUP_BENCHMARK only supports interactive mode\"));\n\t\tprocess.exit(1);\n\t}\n\tconst mode = parsed.mode || \"text\";\n\tinitTheme(settingsManager.getTheme(), isInteractive);\n\ttime(\"initTheme\");\n\n\t// Show deprecation warnings in interactive mode\n\tif (isInteractive && deprecationWarnings.length > 0) {\n\t\tawait showDeprecationWarnings(deprecationWarnings);\n\t}\n\n\t// Warn if running as root (non-interactive modes only — interactive mode shows its own TUI warning)\n\tif (!isInteractive && process.getuid?.() === 0) {\n\t\tlog.warn(\n\t\t\tchalk.yellow(\n\t\t\t\t\"Warning: Running as root (UID 0). The agent has unrestricted system access — all commands execute with full root privileges.\",\n\t\t\t),\n\t\t);\n\t}\n\n\tlet scopedModels: ScopedModel[] = [];\n\tconst modelPatterns = parsed.models ?? settingsManager.getEnabledModels();\n\tif (modelPatterns && modelPatterns.length > 0) {\n\t\tscopedModels = await resolveModelScope(modelPatterns, modelRegistry);\n\t}\n\ttime(\"resolveModelScope\");\n\n\t// Create session manager based on CLI flags\n\tlet sessionManager = await createSessionManager(parsed, cwd, extensionsResult, settingsManager);\n\ttime(\"createSessionManager\");\n\n\t// Set agent type metadata for subagent child processes (must happen before first flush)\n\tif (parsed.agentType && sessionManager) {\n\t\tsessionManager.setAgentType(parsed.agentType);\n\t}\n\t// Set parent session file metadata for subagent child processes (must happen before first flush)\n\tif (parsed.parentSession && sessionManager) {\n\t\tsessionManager.setParentSession(parsed.parentSession);\n\t}\n\n\t// Handle --resume: show session picker\n\tif (parsed.resume) {\n\t\t// Compute effective session dir for resume (same logic as createSessionManager)\n\t\tconst effectiveSessionDir =\n\t\t\tparsed.sessionDir ??\n\t\t\tsettingsManager.getSessionDir() ??\n\t\t\t(await callSessionDirectoryHook(extensionsResult, cwd));\n\n\t\tconst selectedPath = await selectSession(\n\t\t\t(onProgress) => SessionManager.list(cwd, effectiveSessionDir, onProgress),\n\t\t\tSessionManager.listAll,\n\t\t);\n\t\tif (!selectedPath) {\n\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\tstopThemeWatcher();\n\t\t\tprocess.exit(0);\n\t\t}\n\t\tsessionManager = SessionManager.open(selectedPath, effectiveSessionDir);\n\t}\n\n\tconst { options: sessionOptions, cliThinkingFromModel } = buildSessionOptions(\n\t\tparsed,\n\t\tscopedModels,\n\t\tsessionManager,\n\t\tmodelRegistry,\n\t\tsettingsManager,\n\t);\n\tsessionOptions.authStorage = authStorage;\n\tsessionOptions.modelRegistry = modelRegistry;\n\tsessionOptions.resourceLoader = resourceLoader;\n\n\t// Set UI type: explicit --ui flag > mode-based default\n\tif (parsed.ui) {\n\t\tsessionOptions.uiType = parsed.ui;\n\t} else if (mode === \"rpc\") {\n\t\tsessionOptions.uiType = \"rpc\";\n\t} else if (isInteractive) {\n\t\tsessionOptions.uiType = \"tui\";\n\t} else {\n\t\tsessionOptions.uiType = \"cli\";\n\t}\n\n\t// Handle CLI --api-key as runtime override (not persisted)\n\tif (parsed.apiKey) {\n\t\tif (!sessionOptions.model) {\n\t\t\tlog.error(\n\t\t\t\tchalk.red(\"--api-key requires a model to be specified via --model, --provider/--model, or --models\"),\n\t\t\t);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tauthStorage.setRuntimeApiKey(sessionOptions.model.provider, parsed.apiKey);\n\t}\n\n\tconst { session, modelFallbackMessage } = await createAgentSession(sessionOptions);\n\ttime(\"createAgentSession\");\n\n\tif (!isInteractive && !session.model) {\n\t\tlog.error(chalk.red(\"No models available.\"));\n\t\tlog.error(chalk.yellow(\"\\nSet an API key environment variable:\"));\n\t\tlog.error(\" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.\");\n\t\tlog.error(chalk.yellow(`\\nOr create ${getModelsPath()}`));\n\t\tprocess.exit(1);\n\t}\n\n\t// Clamp thinking level to model capabilities for CLI-provided thinking levels.\n\t// This covers both --thinking <level> and --model <pattern>:<thinking>.\n\tconst cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;\n\tif (session.model && cliThinkingOverride) {\n\t\tlet effectiveThinking = session.thinkingLevel;\n\t\tif (!session.model.reasoning) {\n\t\t\teffectiveThinking = \"off\";\n\t\t} else if (effectiveThinking === \"xhigh\" && !supportsXhigh(session.model)) {\n\t\t\teffectiveThinking = \"high\";\n\t\t}\n\t\tif (effectiveThinking !== session.thinkingLevel) {\n\t\t\tsession.setThinkingLevel(effectiveThinking);\n\t\t}\n\t}\n\n\tif (mode === \"rpc\") {\n\t\tprintTimings();\n\t\tawait runRpcMode(session, modelFallbackMessage);\n\t} else if (isInteractive) {\n\t\tif (scopedModels.length > 0 && (parsed.verbose || !settingsManager.getQuietStartup())) {\n\t\t\tconst modelList = scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconsole.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray(\"(Ctrl+P to cycle)\")}`));\n\t\t}\n\n\t\tconst interactiveMode = new InteractiveMode(session, {\n\t\t\tmigratedProviders,\n\t\t\tmodelFallbackMessage,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t\tinitialMessages: parsed.messages,\n\t\t\tverbose: parsed.verbose,\n\t\t});\n\t\tif (startupBenchmark) {\n\t\t\tawait interactiveMode.init();\n\t\t\ttime(\"interactiveMode.init\");\n\t\t\tprintTimings();\n\t\t\tinteractiveMode.stop();\n\t\t\tstopThemeWatcher();\n\t\t\tif (process.stdout.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stdout.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tif (process.stderr.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stderr.once(\"drain\", resolve));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tprintTimings();\n\t\tawait interactiveMode.run();\n\t} else {\n\t\tprintTimings();\n\t\tconst exitCode = await runPrintMode(session, {\n\t\t\tmode,\n\t\t\tmessages: parsed.messages,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t});\n\t\tstopThemeWatcher();\n\t\trestoreStdout();\n\t\tif (exitCode !== 0) {\n\t\t\tprocess.exitCode = exitCode;\n\t\t}\n\t\treturn;\n\t}\n}\n"]}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,KAAK,IAAI,EAAwB,MAAM,eAAe,CAAC;AAYhE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAsC,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIhG,OAAO,EAAE,KAAK,yBAAyB,EAAsB,MAAM,eAAe,CAAC;AACnF,OAAO,EAAoB,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAme7D,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,IAAI,EACZ,YAAY,EAAE,WAAW,EAAE,EAC3B,cAAc,EAAE,cAAc,GAAG,SAAS,EAC1C,aAAa,EAAE,aAAa,EAC5B,eAAe,EAAE,eAAe,GAC9B;IAAE,OAAO,EAAE,yBAAyB,CAAC;IAAC,oBAAoB,EAAE,OAAO,CAAA;CAAE,CAyFvE;AAgED,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,iBAmVxC","sourcesContent":["/**\n * Main entry point for the coding agent CLI.\n *\n * This file handles CLI argument parsing and translates them into\n * createAgentSession() options. The SDK does the heavy lifting.\n */\n\nimport { type ImageContent, modelsAreEqual, supportsMax, supportsXhigh } from \"@dreb/ai\";\nimport chalk from \"chalk\";\nimport { createInterface } from \"readline\";\nimport { type Args, parseArgs, printHelp } from \"./cli/args.js\";\nimport { selectConfig } from \"./cli/config-selector.js\";\nimport { processFileArguments } from \"./cli/file-processor.js\";\nimport { buildInitialMessage } from \"./cli/initial-message.js\";\nimport { listModels } from \"./cli/list-models.js\";\nimport { selectSession } from \"./cli/session-picker.js\";\nimport { APP_NAME, getAgentDir, getModelsPath, loadProvidersEnv, VERSION } from \"./config.js\";\nimport { AuthStorage } from \"./core/auth-storage.js\";\nimport { exportFromFile } from \"./core/export-html/index.js\";\nimport type { LoadExtensionsResult } from \"./core/extensions/index.js\";\nimport { migrateKeybindingsConfigFile } from \"./core/keybindings.js\";\nimport { log } from \"./core/logger.js\";\nimport { ModelRegistry } from \"./core/model-registry.js\";\nimport { resolveCliModel, resolveModelScope, type ScopedModel } from \"./core/model-resolver.js\";\nimport { restoreStdout, takeOverStdout } from \"./core/output-guard.js\";\nimport { DefaultPackageManager } from \"./core/package-manager.js\";\nimport { DefaultResourceLoader } from \"./core/resource-loader.js\";\nimport { type CreateAgentSessionOptions, createAgentSession } from \"./core/sdk.js\";\nimport { type SessionInfo, SessionManager } from \"./core/session-manager.js\";\nimport { SettingsManager } from \"./core/settings-manager.js\";\nimport { printTimings, resetTimings, time } from \"./core/timings.js\";\nimport { allTools } from \"./core/tools/index.js\";\nimport { runMigrations, showDeprecationWarnings } from \"./migrations.js\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.js\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.js\";\n\n/**\n * Read all content from piped stdin.\n * Returns undefined if stdin is a TTY (interactive terminal).\n */\nasync function readPipedStdin(): Promise<string | undefined> {\n\t// If stdin is a TTY, we're running interactively - don't read stdin\n\tif (process.stdin.isTTY) {\n\t\treturn undefined;\n\t}\n\n\treturn new Promise((resolve) => {\n\t\tlet data = \"\";\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.on(\"data\", (chunk) => {\n\t\t\tdata += chunk;\n\t\t});\n\t\tprocess.stdin.on(\"end\", () => {\n\t\t\tresolve(data.trim() || undefined);\n\t\t});\n\t\tprocess.stdin.resume();\n\t});\n}\n\nfunction reportSettingsErrors(settingsManager: SettingsManager, context: string): void {\n\tconst errors = settingsManager.drainErrors();\n\tfor (const { scope, error } of errors) {\n\t\tlog.warn(chalk.yellow(`Warning (${context}, ${scope} settings): ${error.message}`));\n\t\tif (error.stack) {\n\t\t\tlog.warn(chalk.dim(error.stack));\n\t\t}\n\t}\n}\n\nfunction isTruthyEnvFlag(value: string | undefined): boolean {\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ntype PackageCommand = \"install\" | \"remove\" | \"update\" | \"list\";\n\ninterface PackageCommandOptions {\n\tcommand: PackageCommand;\n\tsource?: string;\n\tlocal: boolean;\n\thelp: boolean;\n\tinvalidOption?: string;\n}\n\nfunction getPackageCommandUsage(command: PackageCommand): string {\n\tswitch (command) {\n\t\tcase \"install\":\n\t\t\treturn `${APP_NAME} install <source> [-l]`;\n\t\tcase \"remove\":\n\t\t\treturn `${APP_NAME} remove <source> [-l]`;\n\t\tcase \"update\":\n\t\t\treturn `${APP_NAME} update [source]`;\n\t\tcase \"list\":\n\t\t\treturn `${APP_NAME} list`;\n\t}\n}\n\nfunction printPackageCommandHelp(command: PackageCommand): void {\n\tswitch (command) {\n\t\tcase \"install\":\n\t\t\tconsole.log(`${chalk.bold(\"Usage:\")}\n ${getPackageCommandUsage(\"install\")}\n\nInstall a package and add it to settings.\n\nOptions:\n -l, --local Install project-locally (.dreb/settings.json)\n\nExamples:\n ${APP_NAME} install npm:@foo/bar\n ${APP_NAME} install git:github.com/user/repo\n ${APP_NAME} install git:git@github.com:user/repo\n ${APP_NAME} install https://github.com/user/repo\n ${APP_NAME} install ssh://git@github.com/user/repo\n ${APP_NAME} install ./local/path\n`);\n\t\t\treturn;\n\n\t\tcase \"remove\":\n\t\t\tconsole.log(`${chalk.bold(\"Usage:\")}\n ${getPackageCommandUsage(\"remove\")}\n\nRemove a package and its source from settings.\nAlias: ${APP_NAME} uninstall <source> [-l]\n\nOptions:\n -l, --local Remove from project settings (.dreb/settings.json)\n\nExamples:\n ${APP_NAME} remove npm:@foo/bar\n ${APP_NAME} uninstall npm:@foo/bar\n`);\n\t\t\treturn;\n\n\t\tcase \"update\":\n\t\t\tconsole.log(`${chalk.bold(\"Usage:\")}\n ${getPackageCommandUsage(\"update\")}\n\nUpdate installed packages.\nIf <source> is provided, only that package is updated.\n`);\n\t\t\treturn;\n\n\t\tcase \"list\":\n\t\t\tconsole.log(`${chalk.bold(\"Usage:\")}\n ${getPackageCommandUsage(\"list\")}\n\nList installed packages from user and project settings.\n`);\n\t\t\treturn;\n\t}\n}\n\nfunction parsePackageCommand(args: string[]): PackageCommandOptions | undefined {\n\tconst [rawCommand, ...rest] = args;\n\tlet command: PackageCommand | undefined;\n\tif (rawCommand === \"uninstall\") {\n\t\tcommand = \"remove\";\n\t} else if (rawCommand === \"install\" || rawCommand === \"remove\" || rawCommand === \"update\" || rawCommand === \"list\") {\n\t\tcommand = rawCommand;\n\t}\n\tif (!command) {\n\t\treturn undefined;\n\t}\n\n\tlet local = false;\n\tlet help = false;\n\tlet invalidOption: string | undefined;\n\tlet source: string | undefined;\n\n\tfor (const arg of rest) {\n\t\tif (arg === \"-h\" || arg === \"--help\") {\n\t\t\thelp = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (arg === \"-l\" || arg === \"--local\") {\n\t\t\tif (command === \"install\" || command === \"remove\") {\n\t\t\t\tlocal = true;\n\t\t\t} else {\n\t\t\t\tinvalidOption = invalidOption ?? arg;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (arg.startsWith(\"-\")) {\n\t\t\tinvalidOption = invalidOption ?? arg;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!source) {\n\t\t\tsource = arg;\n\t\t}\n\t}\n\n\treturn { command, source, local, help, invalidOption };\n}\n\nasync function handlePackageCommand(args: string[]): Promise<boolean> {\n\tconst options = parsePackageCommand(args);\n\tif (!options) {\n\t\treturn false;\n\t}\n\n\tif (options.help) {\n\t\tprintPackageCommandHelp(options.command);\n\t\treturn true;\n\t}\n\n\tif (options.invalidOption) {\n\t\tlog.error(chalk.red(`Unknown option ${options.invalidOption} for \"${options.command}\".`));\n\t\tlog.error(chalk.dim(`Use \"${APP_NAME} --help\" or \"${getPackageCommandUsage(options.command)}\".`));\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n\n\tconst source = options.source;\n\tif ((options.command === \"install\" || options.command === \"remove\") && !source) {\n\t\tlog.error(chalk.red(`Missing ${options.command} source.`));\n\t\tlog.error(chalk.dim(`Usage: ${getPackageCommandUsage(options.command)}`));\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst settingsManager = SettingsManager.create(cwd, agentDir);\n\treportSettingsErrors(settingsManager, \"package command\");\n\tconst packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });\n\n\tpackageManager.setProgressCallback((event) => {\n\t\tif (event.type === \"start\") {\n\t\t\tprocess.stdout.write(chalk.dim(`${event.message}\\n`));\n\t\t}\n\t});\n\n\ttry {\n\t\tswitch (options.command) {\n\t\t\tcase \"install\":\n\t\t\t\tawait packageManager.install(source!, { local: options.local });\n\t\t\t\tpackageManager.addSourceToSettings(source!, { local: options.local });\n\t\t\t\tconsole.log(chalk.green(`Installed ${source}`));\n\t\t\t\treturn true;\n\n\t\t\tcase \"remove\": {\n\t\t\t\tawait packageManager.remove(source!, { local: options.local });\n\t\t\t\tconst removed = packageManager.removeSourceFromSettings(source!, { local: options.local });\n\t\t\t\tif (!removed) {\n\t\t\t\t\tlog.error(chalk.red(`No matching package found for ${source}`));\n\t\t\t\t\tprocess.exitCode = 1;\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\tconsole.log(chalk.green(`Removed ${source}`));\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"list\": {\n\t\t\t\tconst globalSettings = settingsManager.getGlobalSettings();\n\t\t\t\tconst projectSettings = settingsManager.getProjectSettings();\n\t\t\t\tconst globalPackages = globalSettings.packages ?? [];\n\t\t\t\tconst projectPackages = projectSettings.packages ?? [];\n\n\t\t\t\tif (globalPackages.length === 0 && projectPackages.length === 0) {\n\t\t\t\t\tconsole.log(chalk.dim(\"No packages installed.\"));\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tconst formatPackage = (pkg: (typeof globalPackages)[number], scope: \"user\" | \"project\") => {\n\t\t\t\t\tconst source = typeof pkg === \"string\" ? pkg : pkg.source;\n\t\t\t\t\tconst filtered = typeof pkg === \"object\";\n\t\t\t\t\tconst display = filtered ? `${source} (filtered)` : source;\n\t\t\t\t\tconsole.log(` ${display}`);\n\t\t\t\t\tconst path = packageManager.getInstalledPath(source, scope);\n\t\t\t\t\tif (path) {\n\t\t\t\t\t\tconsole.log(chalk.dim(` ${path}`));\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tif (globalPackages.length > 0) {\n\t\t\t\t\tconsole.log(chalk.bold(\"User packages:\"));\n\t\t\t\t\tfor (const pkg of globalPackages) {\n\t\t\t\t\t\tformatPackage(pkg, \"user\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (projectPackages.length > 0) {\n\t\t\t\t\tif (globalPackages.length > 0) console.log();\n\t\t\t\t\tconsole.log(chalk.bold(\"Project packages:\"));\n\t\t\t\t\tfor (const pkg of projectPackages) {\n\t\t\t\t\t\tformatPackage(pkg, \"project\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tcase \"update\":\n\t\t\t\tawait packageManager.update(source);\n\t\t\t\tif (source) {\n\t\t\t\t\tconsole.log(chalk.green(`Updated ${source}`));\n\t\t\t\t} else {\n\t\t\t\t\tconsole.log(chalk.green(\"Updated packages\"));\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t}\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : \"Unknown package command error\";\n\t\tlog.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exitCode = 1;\n\t\treturn true;\n\t}\n}\n\nasync function prepareInitialMessage(\n\tparsed: Args,\n\tautoResizeImages: boolean,\n\tstdinContent?: string,\n): Promise<{\n\tinitialMessage?: string;\n\tinitialImages?: ImageContent[];\n}> {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn buildInitialMessage({ parsed, stdinContent });\n\t}\n\n\tconst { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages });\n\treturn buildInitialMessage({\n\t\tparsed,\n\t\tfileText: text,\n\t\tfileImages: images,\n\t\tstdinContent,\n\t});\n}\n\n/** Result from resolving a session argument */\ntype ResolvedSession =\n\t| { type: \"path\"; path: string } // Direct file path\n\t| { type: \"local\"; path: string } // Found in current project\n\t| { type: \"global\"; path: string; cwd: string } // Found in different project\n\t| { type: \"not_found\"; arg: string }; // Not found anywhere\n\n/**\n * Resolve a session argument to a file path.\n * If it looks like a path, use as-is. Otherwise try to match as session ID prefix.\n */\nasync function resolveSessionPath(sessionArg: string, cwd: string, sessionDir?: string): Promise<ResolvedSession> {\n\t// If it looks like a file path, use as-is\n\tif (sessionArg.includes(\"/\") || sessionArg.includes(\"\\\\\") || sessionArg.endsWith(\".jsonl\")) {\n\t\treturn { type: \"path\", path: sessionArg };\n\t}\n\n\t// Try to match as session ID in current project first\n\tconst localSessions = await SessionManager.list(cwd, sessionDir);\n\tconst localMatches = localSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (localMatches.length >= 1) {\n\t\treturn { type: \"local\", path: localMatches[0].path };\n\t}\n\n\t// Try global search across all projects. listAll fails loud on real I/O errors —\n\t// convert a rejection into a clean, actionable exit instead of a raw stack dump,\n\t// matching the RPC (success:false) and TUI (\"Failed to load sessions\") consumers.\n\tlet allSessions: SessionInfo[];\n\ttry {\n\t\tallSessions = await SessionManager.listAll();\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tlog.error(chalk.red(`Failed to list sessions while resolving '${sessionArg}': ${message}`));\n\t\tprocess.exit(1);\n\t}\n\tconst globalMatches = allSessions.filter((s) => s.id.startsWith(sessionArg));\n\n\tif (globalMatches.length >= 1) {\n\t\tconst match = globalMatches[0];\n\t\treturn { type: \"global\", path: match.path, cwd: match.cwd };\n\t}\n\n\t// Not found anywhere\n\treturn { type: \"not_found\", arg: sessionArg };\n}\n\n/** Prompt user for yes/no confirmation */\nasync function promptConfirm(message: string): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createInterface({\n\t\t\tinput: process.stdin,\n\t\t\toutput: process.stdout,\n\t\t});\n\t\trl.question(`${message} [y/N] `, (answer) => {\n\t\t\trl.close();\n\t\t\tresolve(answer.toLowerCase() === \"y\" || answer.toLowerCase() === \"yes\");\n\t\t});\n\t});\n}\n\n/** Helper to call CLI-only session_directory handlers before the initial session manager is created */\nasync function callSessionDirectoryHook(extensions: LoadExtensionsResult, cwd: string): Promise<string | undefined> {\n\tlet customSessionDir: string | undefined;\n\n\tfor (const ext of extensions.extensions) {\n\t\tconst handlers = ext.handlers.get(\"session_directory\");\n\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\tfor (const handler of handlers) {\n\t\t\ttry {\n\t\t\t\tconst event = { type: \"session_directory\" as const, cwd };\n\t\t\t\tconst result = (await handler(event)) as { sessionDir?: string } | undefined;\n\n\t\t\t\tif (result?.sessionDir) {\n\t\t\t\t\tcustomSessionDir = result.sessionDir;\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\tlog.warn(chalk.red(`Extension \"${ext.path}\" session_directory handler failed: ${message}`));\n\t\t\t}\n\t\t}\n\t}\n\n\treturn customSessionDir;\n}\n\nfunction validateForkFlags(parsed: Args): void {\n\tif (!parsed.fork) return;\n\n\tconst conflictingFlags = [\n\t\tparsed.session ? \"--session\" : undefined,\n\t\tparsed.continue ? \"--continue\" : undefined,\n\t\tparsed.resume ? \"--resume\" : undefined,\n\t\tparsed.noSession ? \"--no-session\" : undefined,\n\t].filter((flag): flag is string => flag !== undefined);\n\n\tif (conflictingFlags.length > 0) {\n\t\tlog.error(chalk.red(`Error: --fork cannot be combined with ${conflictingFlags.join(\", \")}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nfunction forkSessionOrExit(sourcePath: string, cwd: string, sessionDir?: string): SessionManager {\n\ttry {\n\t\treturn SessionManager.forkFrom(sourcePath, cwd, sessionDir);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tlog.error(chalk.red(`Error: ${message}`));\n\t\tprocess.exit(1);\n\t}\n}\n\nasync function createSessionManager(\n\tparsed: Args,\n\tcwd: string,\n\textensions: LoadExtensionsResult,\n\tsettingsManager: SettingsManager,\n): Promise<SessionManager | undefined> {\n\tif (parsed.noSession) {\n\t\treturn SessionManager.inMemory();\n\t}\n\n\t// Priority: CLI flag > settings.json > extension hook\n\tconst effectiveSessionDir =\n\t\tparsed.sessionDir ?? settingsManager.getSessionDir() ?? (await callSessionDirectoryHook(extensions, cwd));\n\n\tif (parsed.fork) {\n\t\tconst resolved = await resolveSessionPath(parsed.fork, cwd, effectiveSessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\tcase \"global\":\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, effectiveSessionDir);\n\n\t\t\tcase \"not_found\":\n\t\t\t\tlog.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tif (parsed.session) {\n\t\tconst resolved = await resolveSessionPath(parsed.session, cwd, effectiveSessionDir);\n\n\t\tswitch (resolved.type) {\n\t\t\tcase \"path\":\n\t\t\tcase \"local\":\n\t\t\t\treturn SessionManager.open(resolved.path, effectiveSessionDir);\n\n\t\t\tcase \"global\": {\n\t\t\t\t// Session found in different project - ask user if they want to fork\n\t\t\t\tconsole.log(chalk.yellow(`Session found in different project: ${resolved.cwd}`));\n\t\t\t\tconst shouldFork = await promptConfirm(\"Fork this session into current directory?\");\n\t\t\t\tif (!shouldFork) {\n\t\t\t\t\tconsole.log(chalk.dim(\"Aborted.\"));\n\t\t\t\t\tprocess.exit(0);\n\t\t\t\t}\n\t\t\t\treturn forkSessionOrExit(resolved.path, cwd, effectiveSessionDir);\n\t\t\t}\n\n\t\t\tcase \"not_found\":\n\t\t\t\tlog.error(chalk.red(`No session found matching '${resolved.arg}'`));\n\t\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\tif (parsed.continue) {\n\t\treturn SessionManager.continueRecent(cwd, effectiveSessionDir);\n\t}\n\t// --resume is handled separately (needs picker UI)\n\t// If effective session dir is set, create new session there\n\tif (effectiveSessionDir) {\n\t\treturn SessionManager.create(cwd, effectiveSessionDir);\n\t}\n\t// Default case (new session) returns undefined, SDK will create one\n\treturn undefined;\n}\n\nexport function buildSessionOptions(\n\tparsed: Args,\n\tscopedModels: ScopedModel[],\n\tsessionManager: SessionManager | undefined,\n\tmodelRegistry: ModelRegistry,\n\tsettingsManager: SettingsManager,\n): { options: CreateAgentSessionOptions; cliThinkingFromModel: boolean } {\n\tconst options: CreateAgentSessionOptions = {};\n\tlet cliThinkingFromModel = false;\n\n\tif (sessionManager) {\n\t\toptions.sessionManager = sessionManager;\n\t}\n\n\t// Model from CLI\n\t// - supports --provider <name> --model <pattern>\n\t// - supports --model <provider>/<pattern>\n\tif (parsed.model) {\n\t\tconst resolved = resolveCliModel({\n\t\t\tcliProvider: parsed.provider,\n\t\t\tcliModel: parsed.model,\n\t\t\tmodelRegistry,\n\t\t});\n\t\tif (resolved.warning) {\n\t\t\tconsole.warn(chalk.yellow(`Warning: ${resolved.warning}`));\n\t\t}\n\t\tif (resolved.error) {\n\t\t\tlog.error(chalk.red(resolved.error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (resolved.model) {\n\t\t\toptions.model = resolved.model;\n\t\t\t// Allow \"--model <pattern>:<thinking>\" as a shorthand.\n\t\t\t// Explicit --thinking still takes precedence (applied later).\n\t\t\tif (!parsed.thinking && resolved.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = resolved.thinkingLevel;\n\t\t\t\tcliThinkingFromModel = true;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!options.model && scopedModels.length > 0 && !parsed.continue && !parsed.resume && !parsed.session) {\n\t\t// Check if saved default is in scoped models - use it if so, otherwise first scoped model\n\t\tconst savedProvider = settingsManager.getDefaultProvider();\n\t\tconst savedModelId = settingsManager.getDefaultModel();\n\t\tconst savedModel = savedProvider && savedModelId ? modelRegistry.find(savedProvider, savedModelId) : undefined;\n\t\tconst savedInScope = savedModel ? scopedModels.find((sm) => modelsAreEqual(sm.model, savedModel)) : undefined;\n\n\t\tif (savedInScope) {\n\t\t\toptions.model = savedInScope.model;\n\t\t\t// Use thinking level from scoped model config if explicitly set\n\t\t\tif (!parsed.thinking && savedInScope.thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = savedInScope.thinkingLevel;\n\t\t\t}\n\t\t} else {\n\t\t\toptions.model = scopedModels[0].model;\n\t\t\t// Use thinking level from first scoped model if explicitly set\n\t\t\tif (!parsed.thinking && scopedModels[0].thinkingLevel) {\n\t\t\t\toptions.thinkingLevel = scopedModels[0].thinkingLevel;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Thinking level from CLI (takes precedence over scoped model thinking levels set above)\n\tif (parsed.thinking) {\n\t\toptions.thinkingLevel = parsed.thinking;\n\t}\n\n\t// Scoped models for Ctrl+P cycling\n\t// Keep thinking level undefined when not explicitly set in the model pattern.\n\t// Undefined means \"inherit current session thinking level\" during cycling.\n\tif (scopedModels.length > 0) {\n\t\toptions.scopedModels = scopedModels.map((sm) => ({\n\t\t\tmodel: sm.model,\n\t\t\tthinkingLevel: sm.thinkingLevel,\n\t\t}));\n\t}\n\n\t// API key from CLI - set in authStorage\n\t// (handled by caller before createAgentSession)\n\n\t// Tools\n\tif (parsed.noTools) {\n\t\t// --no-tools: start with no built-in tools\n\t\t// --tools can still add specific ones back\n\t\tif (parsed.tools && parsed.tools.length > 0) {\n\t\t\toptions.tools = parsed.tools.map((name) => allTools[name]);\n\t\t} else {\n\t\t\toptions.tools = [];\n\t\t}\n\t} else if (parsed.tools) {\n\t\toptions.tools = parsed.tools.map((name) => allTools[name]);\n\t}\n\n\treturn { options, cliThinkingFromModel };\n}\n\n/**\n * Handle `dreb dashboard [...args]` — delegate to the @dreb/dashboard package.\n *\n * The dashboard is a separate workspace package; coding-agent must not depend\n * on it (the dashboard depends on coding-agent — a hard dependency here would\n * be a cycle). Resolution is dynamic: installed → hand over argv; missing →\n * fail loudly with install instructions.\n */\nasync function handleDashboardCommand(args: string[]): Promise<boolean> {\n\tif (args[0] !== \"dashboard\") {\n\t\treturn false;\n\t}\n\tlet entryHref: string;\n\ttry {\n\t\tentryHref = import.meta.resolve(\"@dreb/dashboard\");\n\t} catch {\n\t\tconsole.error(\n\t\t\t\"The dashboard package is not installed.\\n\\n\" +\n\t\t\t\t\"Install it with:\\n\" +\n\t\t\t\t\" npm install -g @dreb/dashboard\\n\\n\" +\n\t\t\t\t\"Then run `dreb dashboard` again, or run `dreb-dashboard` directly.\",\n\t\t);\n\t\tprocess.exit(1);\n\t}\n\t// Re-exec the dashboard bin with the remaining args so its own arg parser\n\t// and process lifecycle (SIGINT shutdown of pooled runtimes) apply.\n\tconst { spawn } = await import(\"node:child_process\");\n\tconst { fileURLToPath } = await import(\"node:url\");\n\tconst child = spawn(process.execPath, [fileURLToPath(entryHref), ...args.slice(1)], {\n\t\tstdio: \"inherit\",\n\t});\n\tchild.on(\"exit\", (code) => process.exit(code ?? 0));\n\tchild.on(\"error\", (err) => {\n\t\tconsole.error(`Failed to launch dashboard: ${err.message}`);\n\t\tprocess.exit(1);\n\t});\n\treturn true;\n}\n\nasync function handleConfigCommand(args: string[]): Promise<boolean> {\n\tif (args[0] !== \"config\") {\n\t\treturn false;\n\t}\n\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst settingsManager = SettingsManager.create(cwd, agentDir);\n\treportSettingsErrors(settingsManager, \"config command\");\n\tconst packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });\n\n\tconst resolvedPaths = await packageManager.resolve();\n\n\tawait selectConfig({\n\t\tresolvedPaths,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tagentDir,\n\t});\n\n\tprocess.exit(0);\n}\n\nexport async function main(args: string[]) {\n\tresetTimings();\n\n\t// Load API keys and provider config from ~/.dreb/secrets/providers.env\n\t// Must happen before model resolution or provider initialization\n\tloadProvidersEnv();\n\n\tconst offlineMode = args.includes(\"--offline\") || isTruthyEnvFlag(process.env.DREB_OFFLINE);\n\tif (offlineMode) {\n\t\tprocess.env.DREB_OFFLINE = \"1\";\n\t}\n\n\tif (await handlePackageCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleDashboardCommand(args)) {\n\t\treturn;\n\t}\n\n\tif (await handleConfigCommand(args)) {\n\t\treturn;\n\t}\n\n\t// First pass: parse args to get --extension paths\n\tconst firstPass = parseArgs(args);\n\ttime(\"parseArgs.firstPass\");\n\tconst shouldTakeOverStdout = firstPass.mode !== undefined || firstPass.print || !process.stdin.isTTY;\n\tif (shouldTakeOverStdout) {\n\t\ttakeOverStdout();\n\t}\n\n\t// Run migrations (pass cwd for project-local migrations)\n\tconst { migratedAuthProviders: migratedProviders, deprecationWarnings } = runMigrations(process.cwd());\n\ttime(\"runMigrations\");\n\n\t// Early load extensions to discover their CLI flags\n\tconst cwd = process.cwd();\n\tconst agentDir = getAgentDir();\n\tconst settingsManager = SettingsManager.create(cwd, agentDir);\n\treportSettingsErrors(settingsManager, \"startup\");\n\tconst authStorage = AuthStorage.create();\n\tconst modelRegistry = new ModelRegistry(authStorage, getModelsPath());\n\n\tconst resourceLoader = new DefaultResourceLoader({\n\t\tcwd,\n\t\tagentDir,\n\t\tsettingsManager,\n\t\tadditionalExtensionPaths: firstPass.extensions,\n\t\tadditionalSkillPaths: firstPass.skills,\n\t\tadditionalPromptTemplatePaths: firstPass.promptTemplates,\n\t\tadditionalThemePaths: firstPass.themes,\n\t\tnoExtensions: firstPass.noExtensions,\n\t\tnoSkills: firstPass.noSkills,\n\t\tnoPromptTemplates: firstPass.noPromptTemplates,\n\t\tnoThemes: firstPass.noThemes,\n\t\tsystemPrompt: firstPass.systemPrompt,\n\t\tappendSystemPrompt: firstPass.appendSystemPrompt,\n\t});\n\ttime(\"createResourceLoader\");\n\tawait resourceLoader.reload();\n\ttime(\"resourceLoader.reload\");\n\n\tconst extensionsResult: LoadExtensionsResult = resourceLoader.getExtensions();\n\tfor (const { path, error } of extensionsResult.errors) {\n\t\tlog.warn(chalk.red(`Failed to load extension \"${path}\": ${error}`));\n\t}\n\n\t// Apply pending provider registrations from extensions immediately\n\t// so they're available for model resolution before AgentSession is created\n\tfor (const { name, config, extensionPath } of extensionsResult.runtime.pendingProviderRegistrations) {\n\t\ttry {\n\t\t\tmodelRegistry.registerProvider(name, config);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tlog.warn(chalk.red(`Extension \"${extensionPath}\" error: ${message}`));\n\t\t}\n\t}\n\textensionsResult.runtime.pendingProviderRegistrations = [];\n\n\tconst extensionFlags = new Map<string, { type: \"boolean\" | \"string\" }>();\n\tfor (const ext of extensionsResult.extensions) {\n\t\tfor (const [name, flag] of ext.flags) {\n\t\t\textensionFlags.set(name, { type: flag.type });\n\t\t}\n\t}\n\n\t// Second pass: parse args with extension flags\n\tconst parsed = parseArgs(args, extensionFlags);\n\ttime(\"parseArgs.secondPass\");\n\n\t// Pass flag values to extensions via runtime\n\tfor (const [name, value] of parsed.unknownFlags) {\n\t\textensionsResult.runtime.flagValues.set(name, value);\n\t}\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.help) {\n\t\tprintHelp();\n\t\tprocess.exit(0);\n\t}\n\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(modelRegistry, searchPattern);\n\t\tprocess.exit(0);\n\t}\n\n\t// Read piped stdin content (if any) - skip for RPC mode which uses stdin for JSON-RPC\n\tlet stdinContent: string | undefined;\n\tif (parsed.mode !== \"rpc\") {\n\t\tstdinContent = await readPipedStdin();\n\t\tif (stdinContent !== undefined) {\n\t\t\t// Force print mode since interactive mode requires a TTY for keyboard input\n\t\t\tparsed.print = true;\n\t\t}\n\t}\n\ttime(\"readPipedStdin\");\n\n\tif (parsed.export) {\n\t\tlet result: string;\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tresult = await exportFromFile(parsed.export, outputPath);\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tlog.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconsole.log(`Exported to: ${result}`);\n\t\tprocess.exit(0);\n\t}\n\n\tmigrateKeybindingsConfigFile(agentDir);\n\ttime(\"migrateKeybindingsConfigFile\");\n\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tlog.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\tvalidateForkFlags(parsed);\n\n\tconst { initialMessage, initialImages } = await prepareInitialMessage(\n\t\tparsed,\n\t\tsettingsManager.getImageAutoResize(),\n\t\tstdinContent,\n\t);\n\ttime(\"prepareInitialMessage\");\n\tconst isInteractive = !parsed.print && parsed.mode === undefined;\n\tconst startupBenchmark = isTruthyEnvFlag(process.env.DREB_STARTUP_BENCHMARK);\n\tif (startupBenchmark && !isInteractive) {\n\t\tlog.error(chalk.red(\"Error: DREB_STARTUP_BENCHMARK only supports interactive mode\"));\n\t\tprocess.exit(1);\n\t}\n\tconst mode = parsed.mode || \"text\";\n\tinitTheme(settingsManager.getTheme(), isInteractive);\n\ttime(\"initTheme\");\n\n\t// Show deprecation warnings in interactive mode\n\tif (isInteractive && deprecationWarnings.length > 0) {\n\t\tawait showDeprecationWarnings(deprecationWarnings);\n\t}\n\n\t// Warn if running as root (non-interactive modes only — interactive mode shows its own TUI warning)\n\tif (!isInteractive && process.getuid?.() === 0) {\n\t\tlog.warn(\n\t\t\tchalk.yellow(\n\t\t\t\t\"Warning: Running as root (UID 0). The agent has unrestricted system access — all commands execute with full root privileges.\",\n\t\t\t),\n\t\t);\n\t}\n\n\tlet scopedModels: ScopedModel[] = [];\n\tconst modelPatterns = parsed.models ?? settingsManager.getEnabledModels();\n\tif (modelPatterns && modelPatterns.length > 0) {\n\t\tscopedModels = await resolveModelScope(modelPatterns, modelRegistry);\n\t}\n\ttime(\"resolveModelScope\");\n\n\t// Create session manager based on CLI flags\n\tlet sessionManager = await createSessionManager(parsed, cwd, extensionsResult, settingsManager);\n\ttime(\"createSessionManager\");\n\n\t// Set agent type metadata for subagent child processes (must happen before first flush)\n\tif (parsed.agentType && sessionManager) {\n\t\tsessionManager.setAgentType(parsed.agentType);\n\t}\n\t// Set parent session file metadata for subagent child processes (must happen before first flush)\n\tif (parsed.parentSession && sessionManager) {\n\t\tsessionManager.setParentSession(parsed.parentSession);\n\t}\n\n\t// Handle --resume: show session picker\n\tif (parsed.resume) {\n\t\t// Compute effective session dir for resume (same logic as createSessionManager)\n\t\tconst effectiveSessionDir =\n\t\t\tparsed.sessionDir ??\n\t\t\tsettingsManager.getSessionDir() ??\n\t\t\t(await callSessionDirectoryHook(extensionsResult, cwd));\n\n\t\tconst selectedPath = await selectSession(\n\t\t\t(onProgress) => SessionManager.list(cwd, effectiveSessionDir, onProgress),\n\t\t\tSessionManager.listAll,\n\t\t);\n\t\tif (!selectedPath) {\n\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\tstopThemeWatcher();\n\t\t\tprocess.exit(0);\n\t\t}\n\t\tsessionManager = SessionManager.open(selectedPath, effectiveSessionDir);\n\t}\n\n\tconst { options: sessionOptions, cliThinkingFromModel } = buildSessionOptions(\n\t\tparsed,\n\t\tscopedModels,\n\t\tsessionManager,\n\t\tmodelRegistry,\n\t\tsettingsManager,\n\t);\n\tsessionOptions.authStorage = authStorage;\n\tsessionOptions.modelRegistry = modelRegistry;\n\tsessionOptions.resourceLoader = resourceLoader;\n\n\t// Set UI type: explicit --ui flag > mode-based default\n\tif (parsed.ui) {\n\t\tsessionOptions.uiType = parsed.ui;\n\t} else if (mode === \"rpc\") {\n\t\tsessionOptions.uiType = \"rpc\";\n\t} else if (isInteractive) {\n\t\tsessionOptions.uiType = \"tui\";\n\t} else {\n\t\tsessionOptions.uiType = \"cli\";\n\t}\n\n\t// Handle CLI --api-key as runtime override (not persisted)\n\tif (parsed.apiKey) {\n\t\tif (!sessionOptions.model) {\n\t\t\tlog.error(\n\t\t\t\tchalk.red(\"--api-key requires a model to be specified via --model, --provider/--model, or --models\"),\n\t\t\t);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tauthStorage.setRuntimeApiKey(sessionOptions.model.provider, parsed.apiKey);\n\t}\n\n\tconst { session, modelFallbackMessage } = await createAgentSession(sessionOptions);\n\ttime(\"createAgentSession\");\n\n\tif (!isInteractive && !session.model) {\n\t\tlog.error(chalk.red(\"No models available.\"));\n\t\tlog.error(chalk.yellow(\"\\nSet an API key environment variable:\"));\n\t\tlog.error(\" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.\");\n\t\tlog.error(chalk.yellow(`\\nOr create ${getModelsPath()}`));\n\t\tprocess.exit(1);\n\t}\n\n\t// Clamp thinking level to model capabilities for CLI-provided thinking levels.\n\t// This covers both --thinking <level> and --model <pattern>:<thinking>.\n\tconst cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;\n\tif (session.model && cliThinkingOverride) {\n\t\tconst requestedThinking = parsed.thinking ?? sessionOptions.thinkingLevel;\n\t\tif (requestedThinking === \"max\" && !supportsMax(session.model)) {\n\t\t\tlog.error(\n\t\t\t\tchalk.red(\n\t\t\t\t\t`Thinking level \"max\" is not supported by model \"${session.model.provider}/${session.model.id}\". Use \"xhigh\" or choose a max-capable model.`,\n\t\t\t\t),\n\t\t\t);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tlet effectiveThinking = session.thinkingLevel;\n\t\tif (!session.model.reasoning) {\n\t\t\teffectiveThinking = \"off\";\n\t\t} else if (effectiveThinking === \"xhigh\" && !supportsXhigh(session.model)) {\n\t\t\teffectiveThinking = \"high\";\n\t\t}\n\t\tif (effectiveThinking !== session.thinkingLevel) {\n\t\t\tsession.setThinkingLevel(effectiveThinking);\n\t\t}\n\t}\n\n\tif (mode === \"rpc\") {\n\t\tprintTimings();\n\t\tawait runRpcMode(session, modelFallbackMessage);\n\t} else if (isInteractive) {\n\t\tif (scopedModels.length > 0 && (parsed.verbose || !settingsManager.getQuietStartup())) {\n\t\t\tconst modelList = scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconsole.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray(\"(Ctrl+P to cycle)\")}`));\n\t\t}\n\n\t\tconst interactiveMode = new InteractiveMode(session, {\n\t\t\tmigratedProviders,\n\t\t\tmodelFallbackMessage,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t\tinitialMessages: parsed.messages,\n\t\t\tverbose: parsed.verbose,\n\t\t});\n\t\tif (startupBenchmark) {\n\t\t\tawait interactiveMode.init();\n\t\t\ttime(\"interactiveMode.init\");\n\t\t\tprintTimings();\n\t\t\tinteractiveMode.stop();\n\t\t\tstopThemeWatcher();\n\t\t\tif (process.stdout.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stdout.once(\"drain\", resolve));\n\t\t\t}\n\t\t\tif (process.stderr.writableLength > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => process.stderr.once(\"drain\", resolve));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tprintTimings();\n\t\tawait interactiveMode.run();\n\t} else {\n\t\tprintTimings();\n\t\tconst exitCode = await runPrintMode(session, {\n\t\t\tmode,\n\t\t\tmessages: parsed.messages,\n\t\t\tinitialMessage,\n\t\t\tinitialImages,\n\t\t});\n\t\tstopThemeWatcher();\n\t\trestoreStdout();\n\t\tif (exitCode !== 0) {\n\t\t\tprocess.exitCode = exitCode;\n\t\t}\n\t\treturn;\n\t}\n}\n"]}
package/dist/main.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * This file handles CLI argument parsing and translates them into
5
5
  * createAgentSession() options. The SDK does the heavy lifting.
6
6
  */
7
- import { modelsAreEqual, supportsXhigh } from "@dreb/ai";
7
+ import { modelsAreEqual, supportsMax, supportsXhigh } from "@dreb/ai";
8
8
  import chalk from "chalk";
9
9
  import { createInterface } from "readline";
10
10
  import { parseArgs, printHelp } from "./cli/args.js";
@@ -783,6 +783,11 @@ export async function main(args) {
783
783
  // This covers both --thinking <level> and --model <pattern>:<thinking>.
784
784
  const cliThinkingOverride = parsed.thinking !== undefined || cliThinkingFromModel;
785
785
  if (session.model && cliThinkingOverride) {
786
+ const requestedThinking = parsed.thinking ?? sessionOptions.thinkingLevel;
787
+ if (requestedThinking === "max" && !supportsMax(session.model)) {
788
+ log.error(chalk.red(`Thinking level "max" is not supported by model "${session.model.provider}/${session.model.id}". Use "xhigh" or choose a max-capable model.`));
789
+ process.exit(1);
790
+ }
786
791
  let effectiveThinking = session.thinkingLevel;
787
792
  if (!session.model.reasoning) {
788
793
  effectiveThinking = "off";