@meistrari/agent-core 0.2.0-experimental.1 → 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.
- package/README.md +50 -0
- package/docs/consumer-capability-matrix.md +374 -0
- package/package.json +17 -10
- package/scripts/generate-codex-protocol.ts +711 -0
- package/src/agents/agent-operation.ts +8 -0
- package/src/agents/agent-provider.ts +3 -0
- package/src/agents/agent-run.ts +14 -2
- package/src/agents/agent-runtime-error.ts +28 -0
- package/src/agents/agent-session-events.ts +33 -0
- package/src/agents/agent-tool-runner.ts +12 -11
- package/src/agents/agent-tool.ts +12 -4
- package/src/agents/author-context.ts +1 -1
- package/src/agents/claude/claude-auth.ts +40 -0
- package/src/agents/claude/claude-browser-turn-metadata.ts +70 -0
- package/src/agents/claude/claude-code-process.ts +98 -0
- package/src/agents/claude/claude-command-mapper.ts +63 -3
- package/src/agents/claude/claude-desktop.ts +55 -0
- package/src/agents/claude/claude-event-mapper.ts +864 -135
- package/src/agents/claude/claude-file-change.ts +128 -0
- package/src/agents/claude/claude-provider.ts +344 -29
- package/src/agents/claude/claude-run.ts +893 -130
- package/src/agents/claude/claude-selected-repository-instructions.ts +91 -0
- package/src/agents/claude/claude-skill-catalog.ts +75 -0
- package/src/agents/claude/claude-steered-notifications.ts +180 -0
- package/src/agents/claude/claude-tool-mapper.ts +81 -2
- package/src/agents/claude/claude-workspace-skills.ts +87 -0
- package/src/agents/claude/index.ts +2 -0
- package/src/agents/codex/codex-auth-mapper.ts +27 -0
- package/src/agents/codex/codex-auth.ts +19 -0
- package/src/agents/codex/codex-browser-auth-broker.ts +320 -0
- package/src/agents/codex/codex-browser-auth-status.ts +55 -0
- package/src/agents/codex/codex-command-mapper.ts +38 -3
- package/src/agents/codex/codex-event-mapper.ts +375 -64
- package/src/agents/codex/codex-json-rpc-client.ts +66 -21
- package/src/agents/codex/codex-provider.ts +118 -236
- package/src/agents/codex/codex-run-auth.ts +113 -0
- package/src/agents/codex/codex-run.ts +423 -53
- package/src/agents/codex/codex-tool-mapper.ts +2 -2
- package/src/agents/codex/codex.errors.ts +8 -0
- package/src/agents/codex/generated/meta.gen.ts +144 -6
- package/src/agents/codex/generated/namespaces.gen.ts +92 -3
- package/src/agents/codex/generated/schema.gen.ts +24698 -15638
- package/src/agents/codex/lazy-codex-run-auth.ts +47 -0
- package/src/agents/index.ts +5 -0
- package/src/agents/input-attachment-preparation.ts +13 -0
- package/src/agents/mcp-tool-content.ts +82 -0
- package/src/agents/model-rebinding-run.ts +14 -3
- package/src/agents/selected-repository-instructions.ts +202 -0
- package/src/agents/session-context.ts +41 -0
- package/src/agents/subscription-token-refresh.ts +73 -0
- package/src/agents/user-input-request.ts +19 -20
- package/src/errors/index.ts +1 -1
- package/src/errors/validation-error.ts +6 -0
- package/src/protocol/agent-catalog.ts +159 -0
- package/src/protocol/agent-command.ts +52 -7
- package/src/protocol/agent-content.ts +36 -16
- package/src/protocol/agent-event.ts +210 -73
- package/src/protocol/agent-identity.ts +29 -0
- package/src/protocol/agent-json.ts +122 -8
- package/src/protocol/agent-metadata.ts +2 -2
- package/src/protocol/agent-model.ts +13 -1
- package/src/protocol/agent-notification.ts +110 -0
- package/src/protocol/agent-presentation.ts +71 -20
- package/src/protocol/agent-tool.ts +66 -10
- package/src/protocol/agent-usage.ts +3 -1
- package/src/protocol/agent-user-input.ts +4 -4
- package/src/protocol/agent-work.ts +12 -10
- package/src/protocol/index.ts +2 -0
- package/src/protocol/rejected-keys.ts +11 -0
- package/src/provenance.gen.ts +3 -3
- package/src/supervisor-protocol/agent-event-wrapper.ts +30 -0
|
@@ -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 {
|
|
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
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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: () =>
|
|
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({
|
|
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
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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 (
|
|
722
|
-
throw new AgentProviderProtocolError({ message: 'Codex requested ChatGPT token refresh
|
|
723
|
-
const refreshed = await
|
|
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
|
-
|
|
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
|
-
|
|
870
|
-
|
|
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
|
|
999
|
+
function spawnedSubagentThreadIds(notification: CodexNotification): string[] {
|
|
1118
1000
|
if (notification.method !== 'item/completed')
|
|
1119
|
-
return
|
|
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
|
|
1005
|
+
return []
|
|
1124
1006
|
|
|
1125
|
-
return item.receiverThreadIds.
|
|
1007
|
+
return [...new Set(item.receiverThreadIds.filter(threadId => threadId.length > 0))]
|
|
1126
1008
|
}
|