index.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. /**
  2. * Fixed Codex one-shot subagent provider. Every accepted run starts a fresh
  3. * official `codex app-server --stdio` process in the delegating Session's
  4. * workspace and publishes only after an ephemeral thread exists.
  5. *
  6. * @module @deepseek-ai/dsh-subagent-codex
  7. */
  8. import type { Context } from '@deepseek-ai/cordis'
  9. import z from '@deepseek-ai/schemastery'
  10. import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
  11. import {
  12. assertPositiveFinite,
  13. NO_START_CAPABILITIES,
  14. resolveChildCwd,
  15. type ResolvedSubagentStartRequest,
  16. type SubagentCapabilities,
  17. type SubagentProvider,
  18. } from '@deepseek-ai/dsh-subagent'
  19. import {
  20. DEFAULT_DISPOSE_GRACE_MS,
  21. startCodexRun,
  22. type CodexRunSpec,
  23. } from './run.ts'
  24. export const name = 'subagent-codex'
  25. export const inject = ['subagents', 'subprocess']
  26. /** Deployment-owned environment and process-release bound. */
  27. export interface Config {
  28. /**
  29. * Explicit environment entries layered over the subprocess seam's
  30. * credential-scrubbed parent environment.
  31. */
  32. env?: Record<string, string>
  33. /** Grace in milliseconds for app-server process-tree termination. */
  34. disposeGraceMs?: number
  35. }
  36. export const Config: z<Config> = z.object({
  37. env: z.dict(z.string()).default({}),
  38. disposeGraceMs: z.number().default(DEFAULT_DISPOSE_GRACE_MS),
  39. })
  40. type ResolvedConfig = Required<Config>
  41. class CodexProvider implements SubagentProvider {
  42. readonly name = 'codex'
  43. readonly capabilities: SubagentCapabilities = NO_START_CAPABILITIES
  44. readonly inheritsParentContext = false
  45. constructor(
  46. private readonly ctx: Context,
  47. private readonly config: ResolvedConfig,
  48. ) {}
  49. start(request: ResolvedSubagentStartRequest) {
  50. const parentCwd = request.parent.session.header.cwd
  51. if (parentCwd === undefined) {
  52. throw new Error(
  53. 'subagent-codex: no working directory for the child — delegate from a parent session that has one',
  54. )
  55. }
  56. const spec: CodexRunSpec = {
  57. cwd: resolveChildCwd(
  58. 'subagent-codex',
  59. undefined,
  60. parentCwd,
  61. ),
  62. env: this.config.env,
  63. disposeGraceMs: this.config.disposeGraceMs,
  64. spawn: spawnSpec => this.ctx.subprocess.spawn(spawnSpec),
  65. onError: (error, stopReason) => {
  66. this.ctx.logger.warn(
  67. `subagent-codex: child run failed (${stopReason}): ${error.message}`,
  68. )
  69. },
  70. }
  71. return startCodexRun(request, spec)
  72. }
  73. }
  74. /**
  75. * Register the fixed `codex` provider.
  76. * @param ctx - context carrying shared subagent and subprocess services.
  77. * @param config - explicit child environment and disposal grace.
  78. */
  79. export function apply(ctx: Context, config: Config): void {
  80. const resolved = config as ResolvedConfig
  81. assertPositiveFinite(
  82. 'subagent-codex',
  83. 'disposeGraceMs',
  84. resolved.disposeGraceMs,
  85. )
  86. if (resolved.disposeGraceMs > MAX_TIMER_DELAY_MS) {
  87. throw new Error(
  88. `subagent-codex: disposeGraceMs must be no greater than ${MAX_TIMER_DELAY_MS}`,
  89. )
  90. }
  91. ctx.subagents.registerProvider(new CodexProvider(ctx, resolved))
  92. }