@meistrari/agent-core 0.2.0-experimental.0 → 0.2.0-experimental.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 (90) hide show
  1. package/README.md +82 -0
  2. package/docs/consumer-capability-matrix.md +374 -0
  3. package/package.json +17 -10
  4. package/scripts/generate-codex-protocol.ts +711 -0
  5. package/src/agents/agent-operation.ts +8 -0
  6. package/src/agents/agent-provider.ts +3 -0
  7. package/src/agents/agent-run.ts +14 -2
  8. package/src/agents/agent-runtime-error.ts +28 -0
  9. package/src/agents/agent-session-events.ts +33 -0
  10. package/src/agents/agent-tool-runner.ts +12 -11
  11. package/src/agents/agent-tool.ts +12 -4
  12. package/src/agents/author-context.ts +1 -1
  13. package/src/agents/claude/claude-auth.ts +40 -0
  14. package/src/agents/claude/claude-browser-turn-metadata.ts +70 -0
  15. package/src/agents/claude/claude-code-process.ts +98 -0
  16. package/src/agents/claude/claude-command-mapper.ts +63 -3
  17. package/src/agents/claude/claude-desktop.ts +55 -0
  18. package/src/agents/claude/claude-event-mapper.ts +876 -124
  19. package/src/agents/claude/claude-file-change.ts +128 -0
  20. package/src/agents/claude/claude-provider.ts +344 -29
  21. package/src/agents/claude/claude-run.ts +922 -91
  22. package/src/agents/claude/claude-selected-repository-instructions.ts +91 -0
  23. package/src/agents/claude/claude-skill-catalog.ts +75 -0
  24. package/src/agents/claude/claude-steered-notifications.ts +180 -0
  25. package/src/agents/claude/claude-tool-mapper.ts +81 -2
  26. package/src/agents/claude/claude-workspace-skills.ts +87 -0
  27. package/src/agents/claude/index.ts +2 -0
  28. package/src/agents/codex/codex-auth-mapper.ts +27 -0
  29. package/src/agents/codex/codex-auth.ts +19 -0
  30. package/src/agents/codex/codex-browser-auth-broker.ts +320 -0
  31. package/src/agents/codex/codex-browser-auth-status.ts +55 -0
  32. package/src/agents/codex/codex-command-mapper.ts +38 -3
  33. package/src/agents/codex/codex-event-mapper.ts +375 -64
  34. package/src/agents/codex/codex-json-rpc-client.ts +66 -21
  35. package/src/agents/codex/codex-provider.ts +118 -236
  36. package/src/agents/codex/codex-run-auth.ts +113 -0
  37. package/src/agents/codex/codex-run.ts +428 -48
  38. package/src/agents/codex/codex-tool-mapper.ts +2 -2
  39. package/src/agents/codex/codex.errors.ts +8 -0
  40. package/src/agents/codex/generated/meta.gen.ts +144 -6
  41. package/src/agents/codex/generated/namespaces.gen.ts +92 -3
  42. package/src/agents/codex/generated/schema.gen.ts +24698 -15638
  43. package/src/agents/codex/lazy-codex-run-auth.ts +47 -0
  44. package/src/agents/index.ts +5 -0
  45. package/src/agents/input-attachment-preparation.ts +13 -0
  46. package/src/agents/mcp-tool-content.ts +82 -0
  47. package/src/agents/model-rebinding-run.ts +14 -3
  48. package/src/agents/selected-repository-instructions.ts +202 -0
  49. package/src/agents/session-context.ts +41 -0
  50. package/src/agents/subscription-token-refresh.ts +73 -0
  51. package/src/agents/user-input-request.ts +19 -20
  52. package/src/errors/index.ts +1 -1
  53. package/src/errors/validation-error.ts +6 -0
  54. package/src/protocol/agent-catalog.ts +159 -0
  55. package/src/protocol/agent-command.ts +54 -7
  56. package/src/protocol/agent-content.ts +36 -16
  57. package/src/protocol/agent-event.ts +210 -73
  58. package/src/protocol/agent-identity.ts +29 -0
  59. package/src/protocol/agent-json.ts +122 -8
  60. package/src/protocol/agent-metadata.ts +2 -2
  61. package/src/protocol/agent-model.ts +13 -1
  62. package/src/protocol/agent-notification.ts +110 -0
  63. package/src/protocol/agent-presentation.ts +71 -20
  64. package/src/protocol/agent-tool.ts +66 -10
  65. package/src/protocol/agent-usage.ts +4 -1
  66. package/src/protocol/agent-user-input.ts +4 -4
  67. package/src/protocol/agent-work.ts +12 -10
  68. package/src/protocol/index.ts +2 -0
  69. package/src/protocol/rejected-keys.ts +11 -0
  70. package/src/provenance.gen.ts +3 -3
  71. package/src/steps/contract.ts +4 -1
  72. package/src/steps/projector.ts +17 -1
  73. package/src/supervisor/agent-provider-factory.ts +60 -5
  74. package/src/supervisor/bootstrap-binder.ts +3 -2
  75. package/src/supervisor/index.ts +1 -1
  76. package/src/supervisor/persistence/supervisor-store.ts +22 -8
  77. package/src/supervisor/runtime-handler.ts +30 -2
  78. package/src/supervisor-protocol/agent-event-wrapper.ts +30 -0
  79. package/src/supervisor-protocol/bootstrap.ts +4 -1
  80. package/src/supervisor-protocol/command-body.ts +4 -1
  81. package/src/supervisor-protocol/envelopes/supervisor-to-control-plane.ts +1 -0
  82. package/src/testing/in-memory-runtime-control-plane.ts +118 -12
  83. package/src/testing/index.ts +1 -1
  84. package/src/worker-runtime-client/command-pump.ts +85 -10
  85. package/src/worker-runtime-client/connection-attempt.ts +95 -20
  86. package/src/worker-runtime-client/frame-processor.ts +242 -58
  87. package/src/worker-runtime-client/index.ts +8 -2
  88. package/src/worker-runtime-client/ports.ts +34 -2
  89. package/src/worker-runtime-client/rpc-request-manager.ts +152 -107
  90. package/src/worker-runtime-client/sandbox-connection-runtime.ts +72 -35
@@ -4,27 +4,33 @@ import type { AgentProvider, AgentSessionOpenInput, AgentSessionResumeInput } fr
4
4
  import type { AgentRun } from '../agent-run'
5
5
  import type { AgentTool } from '../agent-tool'
6
6
  import type { InstructionComposer } from '../instructions'
7
- import type { CodexAuthentication, CodexAuthProvider, CodexAuthRefreshInput, CodexAuthTokens, CodexBedrockCredentials } from './codex-auth'
7
+ import type { SessionContextComposer } from '../session-context'
8
+ import type { CodexAuthentication, CodexAuthProvider, CodexAuthTokens, CodexBedrockCredentials } from './codex-auth'
8
9
  import type { CodexEventMapperState, CodexNotification } from './codex-event-mapper'
9
10
  import type { CodexJsonRpcMessage, CodexServerRequest } from './codex-json-rpc-client'
10
- import type { ClientRequestResponsesByMethod, CodexChatGPTAuthTokensRefreshParams } from './codex-protocol'
11
- import { randomUUID } from 'node:crypto'
12
- import { lstat, open, readFile, realpath, rename, unlink } from 'node:fs/promises'
13
- import { basename, dirname, join, resolve } from 'node:path'
11
+ import type { ClientRequestResponsesByMethod } from './codex-protocol'
12
+ import { realpath } from 'node:fs/promises'
13
+ import { resolve } from 'node:path'
14
14
  import { codexModelIdSchema } from '../../protocol'
15
15
  import { serializeAgentError } from '../agent-error-serializer'
16
- import { AgentNotAcceptedError, AgentProviderMetadataError, AgentProviderProtocolError, AgentProviderRuntimeError } from '../agent-runtime-error'
16
+ import { AgentProviderMetadataError, AgentProviderProtocolError, AgentProviderRuntimeError } from '../agent-runtime-error'
17
17
  import { sessionConfiguredEvent } from '../agent-session-events'
18
18
  import { selectAgentTools, toolByName } from '../agent-tool'
19
19
  import { emptyInstructionComposer } from '../instructions'
20
20
  import { ModelRebindingRun } from '../model-rebinding-run'
21
+ import { reconcileSelectedRepositoryInstructions, repositoryInstructionsUnavailable } from '../selected-repository-instructions'
22
+ import { pendingSessionContext } from '../session-context'
21
23
  import { UserInputRequestStore, userInputRequestStoreRootFromTranscriptPath } from '../user-input-request'
24
+ import { toAuthRefreshInput, toCodexLoginParams, toCodexRefreshResponse } from './codex-auth-mapper'
22
25
  import { bedrockDiagnosticMessage } from './codex-bedrock-diagnostics'
23
26
  import { CodexBedrockSessionAuth, validateBedrockCredentials } from './codex-bedrock-session-auth'
27
+ import { CodexBrowserAuthBroker } from './codex-browser-auth-broker'
28
+ import { codexThreadConfig } from './codex-command-mapper'
24
29
  import { cacheCodexThreadMetadata, codexNotificationFromMessage, createCodexEventMapperState, mapCodexNotification } from './codex-event-mapper'
25
30
  import { CodexJsonRpcClient, isCodexServerRequest } from './codex-json-rpc-client'
26
31
  import { CodexRun } from './codex-run'
27
- import { catalogRepositorySkillRoots, stableRepositoryRoots, workspaceSkillRoots } from './codex-skill-roots'
32
+ import { CodexRunAuth, CodexRunAuthClosedError } from './codex-run-auth'
33
+ import { catalogRepositorySkillRoots, workspaceSkillRoots } from './codex-skill-roots'
28
34
  import { runCodexDynamicTool, toCodexDynamicTools } from './codex-tool-mapper'
29
35
 
30
36
  export interface CodexStartupStageFact {
@@ -42,10 +48,18 @@ export interface CodexProviderConfig {
42
48
  environment?: Record<string, string | undefined>
43
49
  onStartupStage?: (fact: CodexStartupStageFact) => void
44
50
  instructions?: InstructionComposer
51
+ // Per-session context delivered once with the first prompt of a session that has a web URL.
52
+ sessionContext?: SessionContextComposer
53
+ // Product context published with the selected-repository instructions, even with no repository.
54
+ selectedRepositoryContext?: string
45
55
  /** Service-owned public model to inference model mapping. */
46
56
  resolveInferenceModel?: (model: string) => string
47
57
  /** Native plugin/marketplace discovery. Does not disable repository skill roots. */
48
58
  pluginsEnabled?: boolean
59
+ /** Serves the run's ChatGPT credential to Browser Use through a per-run socket (ChatGPT auth only). */
60
+ browserAuth?: boolean
61
+ /** Opt in to the pinned CLI’s experimental active-turn model switching. */
62
+ stepModelSwitching?: boolean
49
63
  }
50
64
 
51
65
  interface CodexRunHolder {
@@ -53,6 +67,8 @@ interface CodexRunHolder {
53
67
  run?: CodexRun
54
68
  authTokens?: CodexAuthTokens
55
69
  bedrockAuth?: CodexBedrockSessionAuth
70
+ browserAuthBroker?: CodexBrowserAuthBroker
71
+ auth?: CodexRunAuth
56
72
  startupFailure?: Error
57
73
  }
58
74
  type CodexAuthMaterial
@@ -73,6 +89,7 @@ type CodexThreadBootstrapResult
73
89
  | ClientRequestResponsesByMethod['thread/resume']
74
90
  interface CodexSessionBootstrapConfig {
75
91
  sessionId: string
92
+ sessionWebUrl?: string
76
93
  cwd: string
77
94
  workspaceRepositoryRoots: readonly string[]
78
95
  model?: string
@@ -88,9 +105,13 @@ export class CodexProvider implements AgentProvider<'codex'> {
88
105
  private readonly auth: CodexAuthentication
89
106
  private readonly onStartupStage: ((fact: CodexStartupStageFact) => void) | undefined
90
107
  private readonly instructions: InstructionComposer
108
+ private readonly sessionContext: SessionContextComposer | undefined
109
+ private readonly selectedRepositoryContext: string | undefined
91
110
  private readonly resolveInferenceModel: (model: string) => string
92
111
  private readonly hasInferenceModelMapping: boolean
93
112
  private readonly pluginsEnabled: boolean | undefined
113
+ private readonly browserAuth: boolean
114
+ private readonly stepModelSwitching: boolean
94
115
 
95
116
  private constructor(config: CodexProviderConfig) {
96
117
  this.auth = normalizeCodexAuthentication(config.auth)
@@ -99,9 +120,13 @@ export class CodexProvider implements AgentProvider<'codex'> {
99
120
  this.environment = config.environment ?? {}
100
121
  this.onStartupStage = config.onStartupStage
101
122
  this.instructions = config.instructions ?? emptyInstructionComposer
123
+ this.sessionContext = config.sessionContext
124
+ this.selectedRepositoryContext = config.selectedRepositoryContext
102
125
  this.resolveInferenceModel = config.resolveInferenceModel ?? (model => model)
103
126
  this.hasInferenceModelMapping = config.resolveInferenceModel !== undefined
104
127
  this.pluginsEnabled = config.pluginsEnabled
128
+ this.browserAuth = config.browserAuth === true && this.auth.kind === 'chatgpt'
129
+ this.stepModelSwitching = config.stepModelSwitching === true
105
130
  }
106
131
 
107
132
  static create(config: CodexProviderConfig): CodexProvider {
@@ -120,7 +145,8 @@ export class CodexProvider implements AgentProvider<'codex'> {
120
145
  ephemeral: false,
121
146
  approvalPolicy: 'never',
122
147
  sandbox: 'danger-full-access',
123
- developerInstructions: this.instructions({ provider: 'codex', tools }),
148
+ config: codexThreadConfig(input.reasoningEffort, this.stepModelSwitching),
149
+ developerInstructions: input.instructions ?? this.instructions({ provider: 'codex', tools }),
124
150
  dynamicTools: toCodexDynamicTools(tools),
125
151
  }),
126
152
  })
@@ -131,6 +157,8 @@ export class CodexProvider implements AgentProvider<'codex'> {
131
157
  return this.wrapBedrockRun(await this.resumeRawSession(input, options), input)
132
158
  }
133
159
 
160
+ // Bedrock model bindings are fixed per app-server session, so a model change resumes the same
161
+ // thread under the new binding at an idle turn boundary.
134
162
  private wrapBedrockRun(run: CodexRun, input: AgentSessionOpenInput<'codex'>): AgentRun {
135
163
  if (this.auth.kind !== 'amazon-bedrock')
136
164
  return run
@@ -153,9 +181,10 @@ export class CodexProvider implements AgentProvider<'codex'> {
153
181
  cwd: input.cwd,
154
182
  approvalPolicy: 'never',
155
183
  sandbox: 'danger-full-access',
184
+ config: codexThreadConfig(input.reasoningEffort, this.stepModelSwitching),
156
185
  model: input.model ? this.resolveInferenceModel(input.model) : undefined,
157
186
  modelProvider: this.modelProvider(),
158
- developerInstructions: this.instructions({ provider: 'codex', tools }),
187
+ developerInstructions: input.instructions ?? this.instructions({ provider: 'codex', tools }),
159
188
  }),
160
189
  validateThread: (result) => {
161
190
  // Resume must rebind the same provider session; adopting a different thread would silently
@@ -182,7 +211,16 @@ export class CodexProvider implements AgentProvider<'codex'> {
182
211
  }): Promise<CodexRun> {
183
212
  input.options.signal?.throwIfAborted()
184
213
  const tools = selectAgentTools(this.tools, input.config.tools)
185
- const holder: CodexRunHolder = { toolsByName: toolByName(tools) }
214
+ const auth = this.auth.kind === 'chatgpt' ? new CodexRunAuth(this.auth.provider) : undefined
215
+ const browserAuthBroker = this.browserAuth && auth
216
+ ? await CodexBrowserAuthBroker.open({
217
+ executable: this.executable,
218
+ cwd: input.config.cwd,
219
+ environment: this.environment,
220
+ auth,
221
+ })
222
+ : undefined
223
+ const holder: CodexRunHolder = { toolsByName: toolByName(tools), browserAuthBroker, auth }
186
224
  const authStartedAt = performance.now()
187
225
  let authObserved = false
188
226
  const instructionBridge = this.prepareSessionBootstrap({
@@ -197,6 +235,7 @@ export class CodexProvider implements AgentProvider<'codex'> {
197
235
  options: input.options,
198
236
  holder,
199
237
  }).catch(async (error: unknown) => {
238
+ await holder.browserAuthBroker?.close()
200
239
  await instructionBridge.catch(() => undefined)
201
240
  this.observeStartupStage({
202
241
  stage: 'auth-acquisition-login',
@@ -233,6 +272,7 @@ export class CodexProvider implements AgentProvider<'codex'> {
233
272
 
234
273
  return await this.bootstrapRun({
235
274
  config: input.config,
275
+ stage: input.stage,
236
276
  result,
237
277
  client,
238
278
  abortController,
@@ -252,6 +292,7 @@ export class CodexProvider implements AgentProvider<'codex'> {
252
292
  })
253
293
  }
254
294
  await stopCodexClientBestEffort(client)
295
+ await holder.browserAuthBroker?.close()
255
296
  input.options.signal?.throwIfAborted()
256
297
  throw holder.startupFailure ?? error
257
298
  }
@@ -269,6 +310,7 @@ export class CodexProvider implements AgentProvider<'codex'> {
269
310
  const instructionBridgePath = await reconcileSelectedRepositoryInstructionBridge({
270
311
  cwd: input.cwd,
271
312
  repositoryRoots: input.workspaceRepositoryRoots,
313
+ sharedContext: this.selectedRepositoryContext,
272
314
  })
273
315
  input.startupSignal?.throwIfAborted()
274
316
  return instructionBridgePath
@@ -314,7 +356,7 @@ export class CodexProvider implements AgentProvider<'codex'> {
314
356
  (value): StartupTaskResult<'connection', CodexConnection> => ({ source: 'connection', result: { status: 'fulfilled', value } }),
315
357
  (reason): StartupTaskResult<'connection', CodexConnection> => ({ source: 'connection', result: { status: 'rejected', reason } }),
316
358
  )
317
- const authTask = this.getAuthMaterial(authAbortController.signal).then(
359
+ const authTask = this.getAuthMaterial(input.holder, authAbortController.signal).then(
318
360
  (value): StartupTaskResult<'auth', CodexAuthMaterial> => ({ source: 'auth', result: { status: 'fulfilled', value } }),
319
361
  (reason): StartupTaskResult<'auth', CodexAuthMaterial> => ({ source: 'auth', result: { status: 'rejected', reason } }),
320
362
  )
@@ -341,6 +383,16 @@ export class CodexProvider implements AgentProvider<'codex'> {
341
383
  return { ...connection.result.value, authMaterial: first.result.value }
342
384
  }
343
385
 
386
+ // Process-close revocation must not replace the initialize error that caused it.
387
+ // The native process is already closed, so its connection task can settle without
388
+ // injecting a new cancellation reason into that error path.
389
+ if (first.result.reason instanceof CodexRunAuthClosedError) {
390
+ const connection = await connectionTask
391
+ if (connection.result.status === 'rejected')
392
+ throw connection.result.reason
393
+ await stopCodexClientBestEffort(connection.result.value.client)
394
+ throw first.result.reason
395
+ }
344
396
  connectionAbortController.abort(first.result.reason)
345
397
  const interruptedConnection = await connectionTask
346
398
  if (interruptedConnection.result.status === 'fulfilled')
@@ -352,7 +404,7 @@ export class CodexProvider implements AgentProvider<'codex'> {
352
404
  }
353
405
  }
354
406
 
355
- private async getAuthMaterial(signal: AbortSignal): Promise<CodexAuthMaterial> {
407
+ private async getAuthMaterial(holder: CodexRunHolder, signal: AbortSignal): Promise<CodexAuthMaterial> {
356
408
  if (this.auth.kind === 'amazon-bedrock') {
357
409
  const credentials = await this.auth.provider.getCredentials({ signal })
358
410
  signal.throwIfAborted()
@@ -363,7 +415,9 @@ export class CodexProvider implements AgentProvider<'codex'> {
363
415
  const credentials = await this.auth.provider.getApiKey({ signal })
364
416
  return { kind: 'api-key', apiKey: credentials.apiKey }
365
417
  }
366
- const tokens = await this.auth.provider.getAuthTokens({ signal })
418
+ if (!holder.auth)
419
+ throw new AgentProviderProtocolError({ message: 'Codex ChatGPT authentication is not configured for this run.' })
420
+ const tokens = await holder.auth.acquireInitialTokens({ signal })
367
421
  return { kind: 'chatgpt', tokens }
368
422
  }
369
423
 
@@ -389,7 +443,9 @@ export class CodexProvider implements AgentProvider<'codex'> {
389
443
  try {
390
444
  client = new CodexJsonRpcClient(this.executable, {
391
445
  cwd,
392
- environment: this.environment,
446
+ environment: holder.browserAuthBroker
447
+ ? { ...this.environment, CODING_AGENT_BROWSER_AUTH_SOCKET: holder.browserAuthBroker.socketPath }
448
+ : this.environment,
393
449
  configOverrides: [
394
450
  ...(this.auth.kind === 'amazon-bedrock'
395
451
  ? ['cli_auth_credentials_store="ephemeral"', 'model_provider="amazon-bedrock"', `model_providers.amazon-bedrock.aws.region=${JSON.stringify(this.auth.region)}`, 'model_providers.amazon-bedrock.aws.wire_api="responses"']
@@ -417,6 +473,9 @@ export class CodexProvider implements AgentProvider<'codex'> {
417
473
  })
418
474
  throw error
419
475
  }
476
+ client.child.once('close', () => {
477
+ void holder.browserAuthBroker?.close()
478
+ })
420
479
  const stopForStartupAbort = () => {
421
480
  abortController.abort(startupSignal.reason)
422
481
  void stopCodexClientBestEffort(client)
@@ -519,6 +578,7 @@ export class CodexProvider implements AgentProvider<'codex'> {
519
578
 
520
579
  private async bootstrapRun(input: {
521
580
  config: CodexSessionBootstrapConfig
581
+ stage: 'thread-start' | 'thread-resume'
522
582
  result: CodexThreadBootstrapResult
523
583
  client: CodexJsonRpcClient
524
584
  abortController: AbortController
@@ -540,6 +600,7 @@ export class CodexProvider implements AgentProvider<'codex'> {
540
600
  if (this.hasInferenceModelMapping && config.model && result.model && result.model !== this.resolveInferenceModel(config.model))
541
601
  throw new AgentProviderProtocolError({ message: 'Codex returned a model that does not match the configured inference binding.' })
542
602
 
603
+ state.mainThreadId = thread.id
543
604
  const metadata = {
544
605
  provider: 'codex' as const,
545
606
  sessionId: config.sessionId,
@@ -549,7 +610,7 @@ export class CodexProvider implements AgentProvider<'codex'> {
549
610
  transcriptPath: thread.path,
550
611
  }
551
612
  throwIfStartupFailed(holder)
552
- const [, userInputRequests] = await Promise.all([
613
+ const [, userInputRequests, sessionContext] = await Promise.all([
553
614
  this.attestSessionBootstrap({
554
615
  holder,
555
616
  instructionBridgePath,
@@ -561,13 +622,19 @@ export class CodexProvider implements AgentProvider<'codex'> {
561
622
  sessionId: metadata.sessionId,
562
623
  rootDir: userInputRequestStoreRootFromTranscriptPath(metadata.transcriptPath),
563
624
  }),
625
+ pendingSessionContext({
626
+ compose: this.sessionContext,
627
+ sessionWebUrl: config.sessionWebUrl,
628
+ resumedTranscriptPath: input.stage === 'thread-resume' ? metadata.transcriptPath : undefined,
629
+ }),
564
630
  ])
565
631
  startupSignal?.throwIfAborted()
566
632
  const run = new CodexRun({
567
- beforePrompt: holder.bedrockAuth ? signal => holder.bedrockAuth!.beforePrompt(signal) : undefined,
568
- metadata,
569
- reasoningEffort: config.reasoningEffort,
633
+ beforePrompt: holder.bedrockAuth ? async signal => await holder.bedrockAuth!.beforePrompt(signal) : undefined,
570
634
  resolveInferenceModel: this.resolveInferenceModel,
635
+ metadata,
636
+ sessionContext,
637
+ settings: { model: effectiveModel, effort: config.reasoningEffort },
571
638
  client,
572
639
  requestedCwd: config.cwd,
573
640
  workspaceRepositoryRoots: config.workspaceRepositoryRoots,
@@ -578,18 +645,24 @@ export class CodexProvider implements AgentProvider<'codex'> {
578
645
  runtimeCwd: metadata.cwd,
579
646
  }),
580
647
  reconcileInstructionBridge: async (repositoryRoots) => {
581
- await reconcileSelectedRepositoryInstructionBridge({ cwd: config.cwd, repositoryRoots })
648
+ await reconcileSelectedRepositoryInstructionBridge({ cwd: config.cwd, repositoryRoots, sharedContext: this.selectedRepositoryContext })
582
649
  },
583
650
  userInputRequests,
584
651
  eventMapperState: state,
585
652
  abortSignal: abortController.signal,
586
- onStop: () => abortController.abort(),
653
+ onStop: async () => {
654
+ abortController.abort()
655
+ await holder.browserAuthBroker?.close()
656
+ },
587
657
  })
588
658
  holder.run = run
589
659
 
590
660
  run.pushEvent({ type: 'session.started', payload: run.metadata })
591
661
  run.pushEvent(sessionConfiguredEvent({ model: run.metadata.model ?? effectiveModel, cwd: run.metadata.cwd, reasoningEffort: config.reasoningEffort, tools: config.tools, environment: this.environment }))
592
- run.pushEvent({ type: 'session.state.changed', payload: { state: 'ready' } })
662
+ run.pushEvent({
663
+ type: 'session.state.changed',
664
+ payload: { state: userInputRequests.hasUnresolvedUserInput() ? 'waiting_for_user_input' : 'ready' },
665
+ })
593
666
  return run
594
667
  }
595
668
 
@@ -651,20 +724,19 @@ export class CodexProvider implements AgentProvider<'codex'> {
651
724
  }
652
725
 
653
726
  private async cacheSpawnedSubagentThreadMetadata(input: { notification: CodexNotification, state: CodexEventMapperState, client: CodexJsonRpcClient }): Promise<void> {
654
- const threadId = spawnedSubagentThreadId(input.notification)
655
- if (!threadId)
656
- return
657
-
658
- let thread: Parameters<typeof cacheCodexThreadMetadata>[0]
659
- try {
660
- const result = await input.client.request('thread/read', { threadId, includeTurns: false })
661
- thread = result.thread
662
- }
663
- catch {
664
- // Metadata improves display labels only; lifecycle mapping can proceed without it.
665
- return
666
- }
667
- cacheCodexThreadMetadata(thread, input.state)
727
+ const threadIds = spawnedSubagentThreadIds(input.notification)
728
+ await Promise.all(threadIds.map(async (threadId) => {
729
+ let thread: Parameters<typeof cacheCodexThreadMetadata>[0]
730
+ try {
731
+ const result = await input.client.request('thread/read', { threadId, includeTurns: false })
732
+ thread = result.thread
733
+ }
734
+ catch {
735
+ // Metadata improves display labels only; lifecycle mapping can proceed without it.
736
+ return
737
+ }
738
+ cacheCodexThreadMetadata(thread, input.state)
739
+ }))
668
740
  }
669
741
 
670
742
  private async login(input: {
@@ -718,9 +790,9 @@ export class CodexProvider implements AgentProvider<'codex'> {
718
790
  return
719
791
  }
720
792
  try {
721
- if (this.auth.kind !== 'chatgpt')
722
- throw new AgentProviderProtocolError({ message: 'Codex requested ChatGPT token refresh while using API-key authentication.' })
723
- const refreshed = await this.auth.provider.refreshAuthTokens(toAuthRefreshInput(request.params, current))
793
+ if (!holder.auth)
794
+ throw new AgentProviderProtocolError({ message: 'Codex requested ChatGPT token refresh without ChatGPT authentication.' })
795
+ const refreshed = await holder.auth.refreshAuthTokens(toAuthRefreshInput(request.params, current))
724
796
  holder.authTokens = refreshed
725
797
  client.respond(request.id, toCodexRefreshResponse(refreshed))
726
798
  }
@@ -812,135 +884,15 @@ function throwIfStartupFailed(holder: CodexRunHolder): void {
812
884
  }
813
885
 
814
886
  const SELECTED_REPOSITORY_INSTRUCTION_BRIDGE_FILENAME = 'AGENTS.override.md'
815
- const SELECTED_REPOSITORY_INSTRUCTION_BRIDGE_SENTINEL = '<!-- coding-agent-managed-selected-repository-instructions -->'
816
-
817
- type InstructionBridgeOwnership = 'missing' | 'managed' | 'unowned'
818
887
 
819
- async function reconcileSelectedRepositoryInstructionBridge(input: { cwd: string, repositoryRoots: readonly string[] }): Promise<string> {
888
+ async function reconcileSelectedRepositoryInstructionBridge(input: { cwd: string, repositoryRoots: readonly string[], sharedContext?: string }): Promise<string> {
820
889
  const instructionBridgePath = resolve(input.cwd, SELECTED_REPOSITORY_INSTRUCTION_BRIDGE_FILENAME)
821
- let ownership: InstructionBridgeOwnership
822
- try {
823
- ownership = await readInstructionBridgeOwnership(instructionBridgePath)
824
- }
825
- catch (error) {
826
- if (!(error instanceof AgentProviderRuntimeError))
827
- throw error
828
- throw repositoryInstructionsUnavailable({ recoveryKind: 'retry', cause: error })
829
- }
830
- const repositoryRoots = stableRepositoryRoots(input.repositoryRoots)
831
-
832
- if (repositoryRoots.length === 0) {
833
- if (ownership === 'managed') {
834
- try {
835
- await removeManagedInstructionBridge(instructionBridgePath)
836
- }
837
- catch (error) {
838
- if (!(error instanceof AgentProviderRuntimeError))
839
- throw error
840
- throw repositoryInstructionsUnavailable({ recoveryKind: 'retry', cause: error })
841
- }
842
- }
843
- return instructionBridgePath
844
- }
845
-
846
- if (ownership === 'unowned') {
847
- const cause = new AgentProviderRuntimeError({
848
- message: 'Refusing to overwrite an AGENTS.override.md not owned by the Coding Agent runtime.',
849
- details: { instructionBridgePath },
850
- retryable: false,
851
- })
852
- throw repositoryInstructionsUnavailable({ recoveryKind: 'terminal', cause })
853
- }
854
-
855
- try {
856
- await writeInstructionBridgeAtomically({
857
- instructionBridgePath,
858
- content: selectedRepositoryInstructionBridge(repositoryRoots),
859
- })
860
- }
861
- catch (error) {
862
- if (!(error instanceof AgentProviderRuntimeError))
863
- throw error
864
- throw repositoryInstructionsUnavailable({ recoveryKind: 'retry', cause: error })
865
- }
890
+ await reconcileSelectedRepositoryInstructions({ provider: 'codex', path: instructionBridgePath, repositoryRoots: input.repositoryRoots, sharedContext: input.sharedContext })
866
891
  return instructionBridgePath
867
892
  }
868
893
 
869
- async function readInstructionBridgeOwnership(instructionBridgePath: string): Promise<InstructionBridgeOwnership> {
870
- let entry
871
- try {
872
- entry = await lstat(instructionBridgePath)
873
- }
874
- catch (error) {
875
- if (isNodeError(error) && error.code === 'ENOENT')
876
- return 'missing'
877
- throw new AgentProviderRuntimeError({
878
- message: 'Failed to inspect the selected-repository instruction bridge.',
879
- details: { instructionBridgePath },
880
- retryable: true,
881
- cause: error,
882
- })
883
- }
884
-
885
- if (!entry.isFile() || entry.isSymbolicLink())
886
- return 'unowned'
887
-
888
- let content: string
889
- try {
890
- content = await readFile(instructionBridgePath, 'utf8')
891
- }
892
- catch (error) {
893
- throw new AgentProviderRuntimeError({
894
- message: 'Failed to inspect the selected-repository instruction bridge.',
895
- details: { instructionBridgePath },
896
- retryable: true,
897
- cause: error,
898
- })
899
- }
900
-
901
- return firstLine(content) === SELECTED_REPOSITORY_INSTRUCTION_BRIDGE_SENTINEL ? 'managed' : 'unowned'
902
- }
903
-
904
- async function writeInstructionBridgeAtomically(input: { instructionBridgePath: string, content: string }): Promise<void> {
905
- const tempPath = join(
906
- dirname(input.instructionBridgePath),
907
- `.${basename(input.instructionBridgePath)}.${process.pid}.${randomUUID()}.tmp`,
908
- )
909
- let tempFile: Awaited<ReturnType<typeof open>> | undefined
910
- try {
911
- tempFile = await open(tempPath, 'wx', 0o640)
912
- await tempFile.chmod(0o640)
913
- await tempFile.writeFile(input.content, 'utf8')
914
- await tempFile.close()
915
- tempFile = undefined
916
- await rename(tempPath, input.instructionBridgePath)
917
- }
918
- catch (error) {
919
- await tempFile?.close().catch(() => undefined)
920
- await unlink(tempPath).catch(() => undefined)
921
- throw new AgentProviderRuntimeError({
922
- message: 'Failed to publish the selected-repository instruction bridge.',
923
- details: { instructionBridgePath: input.instructionBridgePath },
924
- retryable: true,
925
- cause: error,
926
- })
927
- }
928
- }
929
-
930
- async function removeManagedInstructionBridge(instructionBridgePath: string): Promise<void> {
931
- try {
932
- await unlink(instructionBridgePath)
933
- }
934
- catch (error) {
935
- if (isNodeError(error) && error.code === 'ENOENT')
936
- return
937
- throw new AgentProviderRuntimeError({
938
- message: 'Failed to remove the stale selected-repository instruction bridge.',
939
- details: { instructionBridgePath },
940
- retryable: true,
941
- cause: error,
942
- })
943
- }
894
+ function normalizeCodexAuthentication(auth: CodexAuthProvider | CodexAuthentication): CodexAuthentication {
895
+ return 'kind' in auth ? auth : { kind: 'chatgpt', provider: auth }
944
896
  }
945
897
 
946
898
  async function requireSelectedRepositoryInstructionSource(input: {
@@ -997,42 +949,6 @@ async function requireSelectedRepositoryInstructionSource(input: {
997
949
  throw repositoryInstructionsUnavailable({ recoveryKind: 'terminal', cause })
998
950
  }
999
951
 
1000
- function repositoryInstructionsUnavailable(input: {
1001
- recoveryKind: 'retry' | 'terminal'
1002
- cause: AgentProviderRuntimeError
1003
- }): AgentNotAcceptedError {
1004
- return new AgentNotAcceptedError({
1005
- message: 'Selected repository instructions are unavailable.',
1006
- recovery: { kind: input.recoveryKind, reason: 'repository_instructions_unavailable' },
1007
- cause: input.cause,
1008
- })
1009
- }
1010
-
1011
- function selectedRepositoryInstructionBridge(repositoryRoots: readonly string[]): string {
1012
- return [
1013
- SELECTED_REPOSITORY_INSTRUCTION_BRIDGE_SENTINEL,
1014
- '# Selected repository instructions',
1015
- '',
1016
- 'Selected repository roots:',
1017
- ...repositoryRoots.map(root => `- ${JSON.stringify(root)}`),
1018
- '',
1019
- 'For each repository root, you must:',
1020
- '- Inspect `AGENTS.override.md` first; if it does not exist, inspect `AGENTS.md`.',
1021
- '- Follow every documentation file referenced by the applicable AGENTS file.',
1022
- '- Before editing nested files, re-check for more-local `AGENTS.override.md` or `AGENTS.md` files.',
1023
- '',
1024
- 'Runtime and developer instructions take precedence over these repository instructions.',
1025
- 'Repository instructions apply only within their own repository.',
1026
- 'If selected repositories contain irreconcilable instructions, report the conflict instead of resolving it by repository input order.',
1027
- '',
1028
- ].join('\n')
1029
- }
1030
-
1031
- function firstLine(content: string): string {
1032
- const lineEnd = content.indexOf('\n')
1033
- return lineEnd === -1 ? content : content.slice(0, lineEnd)
1034
- }
1035
-
1036
952
  function isNodeError(error: unknown): error is NodeJS.ErrnoException {
1037
953
  return error instanceof Error && 'code' in error
1038
954
  }
@@ -1056,40 +972,6 @@ async function stopCodexClientBestEffort(client: CodexJsonRpcClient): Promise<vo
1056
972
  await client.stop().catch(() => undefined)
1057
973
  }
1058
974
 
1059
- function toCodexLoginParams(tokens: CodexAuthTokens): {
1060
- type: 'chatgptAuthTokens'
1061
- accessToken: string
1062
- chatgptAccountId: string
1063
- chatgptPlanType?: string | null
1064
- } {
1065
- return {
1066
- type: 'chatgptAuthTokens',
1067
- accessToken: tokens.accessToken,
1068
- chatgptAccountId: tokens.chatgptAccountId,
1069
- chatgptPlanType: tokens.chatgptPlanType ?? null,
1070
- }
1071
- }
1072
-
1073
- function normalizeCodexAuthentication(auth: CodexAuthProvider | CodexAuthentication): CodexAuthentication {
1074
- return 'kind' in auth ? auth : { kind: 'chatgpt', provider: auth }
1075
- }
1076
-
1077
- function toAuthRefreshInput(params: CodexChatGPTAuthTokensRefreshParams, tokens: CodexAuthTokens): CodexAuthRefreshInput {
1078
- return {
1079
- reason: params.reason,
1080
- rejectedTokenGeneration: tokens.accessTokenGeneration,
1081
- rejectedTokenFingerprint: tokens.accessTokenFingerprint,
1082
- }
1083
- }
1084
-
1085
- function toCodexRefreshResponse(tokens: CodexAuthTokens): { accessToken: string, chatgptAccountId: string, chatgptPlanType?: string | null } {
1086
- return {
1087
- accessToken: tokens.accessToken,
1088
- chatgptAccountId: tokens.chatgptAccountId,
1089
- chatgptPlanType: tokens.chatgptPlanType ?? null,
1090
- }
1091
- }
1092
-
1093
975
  function redactSerializedError(error: unknown, accessToken: string): { message: string, code?: string, name?: string, stack?: string } {
1094
976
  return redactAgentError(serializeAgentError(error), accessToken)
1095
977
  }
@@ -1114,13 +996,13 @@ function firstCodexModelId(...values: Array<string | null | undefined>): CodexMo
1114
996
  return undefined
1115
997
  }
1116
998
 
1117
- function spawnedSubagentThreadId(notification: CodexNotification): string | undefined {
999
+ function spawnedSubagentThreadIds(notification: CodexNotification): string[] {
1118
1000
  if (notification.method !== 'item/completed')
1119
- return undefined
1001
+ return []
1120
1002
 
1121
1003
  const item = notification.params.item
1122
1004
  if (item.type !== 'collabAgentToolCall' || item.tool !== 'spawnAgent' || item.status === 'failed')
1123
- return undefined
1005
+ return []
1124
1006
 
1125
- return item.receiverThreadIds.at(0)
1007
+ return [...new Set(item.receiverThreadIds.filter(threadId => threadId.length > 0))]
1126
1008
  }