@meistrari/agent-core 0.0.0 → 0.1.1

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 (85) hide show
  1. package/README.md +36 -3
  2. package/bin/supervisor.ts +116 -0
  3. package/package.json +42 -3
  4. package/scripts/build-supervisor-executable.ts +32 -0
  5. package/src/agents/agent-error-serializer.ts +23 -0
  6. package/src/agents/agent-event-stream.ts +81 -0
  7. package/src/agents/agent-id.ts +17 -0
  8. package/src/agents/agent-operation.ts +11 -0
  9. package/src/agents/agent-provider.ts +37 -0
  10. package/src/agents/agent-run.ts +32 -0
  11. package/src/agents/agent-runtime-error.ts +161 -0
  12. package/src/agents/agent-session-events.ts +32 -0
  13. package/src/agents/agent-tool-runner.ts +83 -0
  14. package/src/agents/agent-tool.ts +111 -0
  15. package/src/agents/author-context.ts +36 -0
  16. package/src/agents/claude/claude-command-mapper.ts +234 -0
  17. package/src/agents/claude/claude-event-mapper.ts +748 -0
  18. package/src/agents/claude/claude-provider.ts +191 -0
  19. package/src/agents/claude/claude-run.ts +468 -0
  20. package/src/agents/claude/claude-tool-mapper.ts +186 -0
  21. package/src/agents/claude/index.ts +1 -0
  22. package/src/agents/codex/codex-auth.ts +34 -0
  23. package/src/agents/codex/codex-command-mapper.ts +78 -0
  24. package/src/agents/codex/codex-event-mapper.ts +721 -0
  25. package/src/agents/codex/codex-json-rpc-client.ts +326 -0
  26. package/src/agents/codex/codex-protocol.ts +36 -0
  27. package/src/agents/codex/codex-provider.ts +1050 -0
  28. package/src/agents/codex/codex-run.ts +404 -0
  29. package/src/agents/codex/codex-skill-catalog.ts +158 -0
  30. package/src/agents/codex/codex-skill-roots.ts +19 -0
  31. package/src/agents/codex/codex-tool-mapper.ts +55 -0
  32. package/src/agents/codex/codex.errors.ts +68 -0
  33. package/src/agents/codex/generated/meta.gen.ts +606 -0
  34. package/src/agents/codex/generated/namespaces.gen.ts +311 -0
  35. package/src/agents/codex/generated/schema.gen.ts +34883 -0
  36. package/src/agents/codex/index.ts +2 -0
  37. package/src/agents/index.ts +14 -0
  38. package/src/agents/input-attachment-preparation.ts +86 -0
  39. package/src/agents/input-attachment.errors.ts +16 -0
  40. package/src/agents/instructions.ts +8 -0
  41. package/src/agents/materialized-input-attachment.ts +26 -0
  42. package/src/agents/message-id.ts +23 -0
  43. package/src/agents/normalize.ts +8 -0
  44. package/src/agents/sandbox-environment.ts +1 -0
  45. package/src/agents/tools/ping.tool.ts +13 -0
  46. package/src/agents/user-input-request.ts +470 -0
  47. package/src/protocol/agent-event.ts +2 -1
  48. package/src/protocol/agent-usage.ts +14 -0
  49. package/src/provenance.gen.ts +3 -3
  50. package/src/supervisor/agent-provider-factory.ts +189 -0
  51. package/src/supervisor/bootstrap-binder.ts +133 -0
  52. package/src/supervisor/config.ts +49 -0
  53. package/src/supervisor/control-authority-verifier.ts +135 -0
  54. package/src/supervisor/create-supervisor-runtime.ts +25 -0
  55. package/src/supervisor/errors.ts +24 -0
  56. package/src/supervisor/index.ts +34 -0
  57. package/src/supervisor/persistence/json.ts +21 -0
  58. package/src/supervisor/persistence/state-discovery.ts +56 -0
  59. package/src/supervisor/persistence/supervisor-store.ts +364 -0
  60. package/src/supervisor/ports/index.ts +109 -0
  61. package/src/supervisor/provider-factory.ts +37 -0
  62. package/src/supervisor/resident.ts +143 -0
  63. package/src/supervisor/rpc-client.ts +120 -0
  64. package/src/supervisor/runtime-handler.ts +309 -0
  65. package/src/supervisor/websocket-server.ts +435 -0
  66. package/src/supervisor-protocol/bootstrap.ts +1 -1
  67. package/src/template-onboarding.ts +47 -0
  68. package/src/testing/es256-test-keys.ts +73 -0
  69. package/src/testing/in-memory-runtime-control-plane.ts +206 -0
  70. package/src/testing/index.ts +6 -0
  71. package/src/testing/loopback-supervisor-connection.ts +71 -0
  72. package/src/testing/scripted-provider.ts +64 -0
  73. package/src/worker-runtime-client/command-pump.ts +127 -0
  74. package/src/worker-runtime-client/connection-attempt.ts +349 -0
  75. package/src/worker-runtime-client/control-authority-signer.ts +100 -0
  76. package/src/worker-runtime-client/e2b-supervisor-connection.ts +102 -0
  77. package/src/worker-runtime-client/frame-processor.ts +178 -0
  78. package/src/worker-runtime-client/index.ts +27 -0
  79. package/src/worker-runtime-client/lease-reconciler.ts +14 -0
  80. package/src/worker-runtime-client/ports.ts +139 -0
  81. package/src/worker-runtime-client/postgres-notification-listener.ts +91 -0
  82. package/src/worker-runtime-client/rpc-dispatcher.ts +27 -0
  83. package/src/worker-runtime-client/rpc-request-manager.ts +141 -0
  84. package/src/worker-runtime-client/sandbox-connection-runtime.ts +301 -0
  85. package/src/worker-runtime-client/token-crypto.ts +46 -0
package/README.md CHANGED
@@ -7,12 +7,20 @@ Shared contracts and runtime modules for Tela coding-agent sandboxes.
7
7
  | `@meistrari/agent-core/protocol` | Provider-neutral agent commands, events, content, tools, usage, work items (Claude and Codex share it). |
8
8
  | `@meistrari/agent-core/supervisor-protocol/*` | Strict wire contracts between a worker runtime and the resident sandbox supervisor: bootstrap, commands, events, reverse RPC, control authority. |
9
9
  | `@meistrari/agent-core/errors`, `/logger`, `/provenance` | Shared primitives. |
10
- | `@meistrari/agent-core/supervisor` | Sandbox-resident supervisor runtime (Bun, SQLite). |
11
- | `@meistrari/agent-core/worker-runtime-client` | Control-plane WSS client: leases, generation fencing, command pump, durable event acking, reverse RPC dispatch. |
12
- | `@meistrari/agent-core/claude`, `/codex` | Harness adapters. |
10
+ | `@meistrari/agent-core/supervisor` | Sandbox-resident supervisor runtime (Bun, WebSocket, SQLite), provider factory, and product-facing ports. |
11
+ | `@meistrari/agent-core/worker-runtime-client` | Control-plane WSS client: leases, generation fencing, command pump, durable event ACKs, reverse RPC dispatch, ES256 authority, token crypto, and PG notifications. |
12
+ | `@meistrari/agent-core/testing` | In-memory control-plane, scripted provider, loopback connection, and ES256 test fixtures. |
13
+ | `@meistrari/agent-core/template-onboarding` | Validated service-owned E2B template registration and immutable provenance contract. |
14
+ | `@meistrari/agent-core/agents` | Provider-neutral agent, run, tool, event-stream, and instruction-composer contracts. |
15
+ | `@meistrari/agent-core/claude` | Claude Agent SDK provider with normalized lifecycle, message, tool, work, usage, and user-input events. |
16
+ | `@meistrari/agent-core/codex` | Codex app-server provider, JSON-RPC transport, normalized event mapping, and pluggable authentication. |
17
+
18
+ The supervisor accepts the Claude and Codex adapters through `createAgentSupervisorProviderFactory`, preserving the same durable wire protocol for either harness.
13
19
 
14
20
  The package ships TypeScript source and targets Bun `>=1.3.10`. There is no wire protocol version: a sandbox keeps the exact agent-core build it was created with for its lifetime (immutable sandbox invariant). Incompatible wire changes require reprovisioning sandboxes, never in-place upgrades.
15
21
 
22
+ agent-core does not own a universal E2B template. Each consuming service builds and onboards its own template against the shared contracts and records that template's agent-core provenance. agent-api and Remy may therefore use different base images, harness versions, system packages, and startup hooks without forking the control protocol.
23
+
16
24
  ## Development
17
25
 
18
26
  ```bash
@@ -21,6 +29,31 @@ bun run lint
21
29
  bun run typecheck
22
30
  bun run test # unit tests (src/**/*.test.ts)
23
31
  bun run test:e2e # loopback worker <-> supervisor e2e
32
+ bun run build:supervisor # cross-compile the Linux x64 resident executable
24
33
  ```
25
34
 
35
+ ## Resident supervisor entry
36
+
37
+ `agent-core-supervisor` loads a product-owned extensions module so credentials, tools, workspace preparation, hooks, payload storage, and provider construction remain outside the shared runtime:
38
+
39
+ ```bash
40
+ bunx agent-core-supervisor \
41
+ --extensions ./supervisor-extensions.ts \
42
+ --authority-jwks-path ./control-authority.json
43
+ ```
44
+
45
+ Use `--loopback-tcp` for local and image-build probes. Production images default to the protected Unix socket. The extension module exports `default`, `extensions`, or `createSupervisorExtensions()` matching `SupervisorExtensions`.
46
+
26
47
  Publishing runs from `main` through `.github/workflows/publish.yml` (conventional commits decide the bump).
48
+
49
+ ## Durable command outcomes
50
+
51
+ A supervisor `received` ACK advances the command delivery cursor only. Implementations of
52
+ `DurableEventSink.record` must atomically store each durable event, project
53
+ `supervisor.command.applied` or `supervisor.command.failed` into command state, and advance the
54
+ ACK floor. Duplicate events must not repeat projections. This keeps outcomes consistent when
55
+ the worker disconnects after persistence but before sending its event ACK.
56
+
57
+ The former `ConnectionAuthorityStore.projectAppliedCommand` callback has been removed.
58
+ Move that projection into the durable-event transaction; receiving a command does not prove
59
+ that the provider executed it. See `InMemoryRuntimeControlPlane.record` for the test fixture.
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env bun
2
+ import type { SupervisorExtensions } from '../src/supervisor/create-supervisor-runtime'
3
+ import { resolve } from 'node:path'
4
+ import { pathToFileURL } from 'node:url'
5
+ import { createLogger } from '../src/logger'
6
+ import {
7
+ createSupervisorRuntime,
8
+ residentSupervisorConfig,
9
+ TERMINAL_ERROR_CODE,
10
+ } from '../src/supervisor'
11
+
12
+ interface SupervisorExtensionsModule {
13
+ default?: SupervisorExtensions
14
+ extensions?: SupervisorExtensions
15
+ createSupervisorExtensions?: (input: {
16
+ developmentFaultDirectory?: string
17
+ }) => SupervisorExtensions | Promise<SupervisorExtensions>
18
+ }
19
+
20
+ const arguments_ = process.argv.slice(2)
21
+ if (arguments_.includes('--help')) {
22
+ process.stdout.write(`agent-core-supervisor
23
+
24
+ Required:
25
+ --extensions <path> Supervisor extensions module (or AGENT_CORE_SUPERVISOR_EXTENSIONS)
26
+
27
+ Options:
28
+ --loopback-tcp Listen on 127.0.0.1:8080 instead of the protected Unix socket
29
+ --port <number> Override the loopback TCP port
30
+ --authority-jwks-path <path> Override the ES256 public JWKS configuration
31
+ --sandbox-id-path <path> Override the local E2B sandbox identity file
32
+ --state-root <path> Override the supervisor SQLite state root
33
+ --development-fault-directory <path>
34
+ --help
35
+ `)
36
+ process.exit(0)
37
+ }
38
+
39
+ await main()
40
+
41
+ async function main(): Promise<void> {
42
+ const logger = createLogger({ base: { runtime: 'agent-core-supervisor' } })
43
+ try {
44
+ const developmentFaultDirectory = option('--development-fault-directory')
45
+ const extensions = await loadExtensions({
46
+ path: option('--extensions') ?? process.env.AGENT_CORE_SUPERVISOR_EXTENSIONS,
47
+ developmentFaultDirectory,
48
+ })
49
+ const portValue = option('--port')
50
+ const port = portValue === undefined ? undefined : parsePort(portValue)
51
+ const runtime = await createSupervisorRuntime({
52
+ config: residentSupervisorConfig({
53
+ listen: arguments_.includes('--loopback-tcp') ? 'loopback-tcp' : 'protected-unix',
54
+ ...(port === undefined ? {} : { port }),
55
+ ...(option('--authority-jwks-path') ? { authorityJwksPath: option('--authority-jwks-path') } : {}),
56
+ ...(option('--sandbox-id-path') ? { sandboxIdPath: option('--sandbox-id-path') } : {}),
57
+ ...(option('--state-root') ? { stateRoot: option('--state-root') } : {}),
58
+ }),
59
+ extensions,
60
+ logger,
61
+ })
62
+ let stopping: Promise<void> | undefined
63
+ const stop = (reason: 'sigterm' | 'sigint') => {
64
+ stopping ??= runtime.stop({ reason }).then(() => {
65
+ process.exitCode = 0
66
+ })
67
+ return stopping
68
+ }
69
+ process.once('SIGTERM', () => void stop('sigterm'))
70
+ process.once('SIGINT', () => void stop('sigint'))
71
+ runtime.start()
72
+ logger.info({
73
+ transport: arguments_.includes('--loopback-tcp') ? 'loopback-tcp' : 'protected-unix',
74
+ port: runtime.port(),
75
+ developmentFaults: developmentFaultDirectory ? 'enabled' : 'disabled',
76
+ }, 'resident supervisor is listening')
77
+ }
78
+ catch (error) {
79
+ logger.error({ err: error }, 'resident supervisor failed to start')
80
+ process.exitCode = TERMINAL_ERROR_CODE
81
+ }
82
+ }
83
+
84
+ async function loadExtensions(input: {
85
+ path: string | undefined
86
+ developmentFaultDirectory: string | undefined
87
+ }): Promise<SupervisorExtensions> {
88
+ if (!input.path)
89
+ throw new Error('--extensions or AGENT_CORE_SUPERVISOR_EXTENSIONS is required.')
90
+ const module = await import(pathToFileURL(resolve(input.path)).href) as SupervisorExtensionsModule
91
+ const extensions = module.createSupervisorExtensions
92
+ ? await module.createSupervisorExtensions({
93
+ ...(input.developmentFaultDirectory ? { developmentFaultDirectory: input.developmentFaultDirectory } : {}),
94
+ })
95
+ : module.default ?? module.extensions
96
+ if (!extensions)
97
+ throw new Error('Supervisor extensions module must export default, extensions, or createSupervisorExtensions().')
98
+ return extensions
99
+ }
100
+
101
+ function option(name: string): string | undefined {
102
+ const index = arguments_.indexOf(name)
103
+ if (index < 0)
104
+ return undefined
105
+ const value = arguments_[index + 1]
106
+ if (!value || value.startsWith('--'))
107
+ throw new Error(`${name} requires a value.`)
108
+ return value
109
+ }
110
+
111
+ function parsePort(value: string): number {
112
+ const port = Number(value)
113
+ if (!Number.isSafeInteger(port) || port < 0 || port > 65_535)
114
+ throw new Error('--port must be an integer between 0 and 65535.')
115
+ return port
116
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@meistrari/agent-core",
3
3
  "type": "module",
4
- "version": "0.0.0",
4
+ "version": "0.1.1",
5
5
  "packageManager": "bun@1.3.12",
6
6
  "description": "Shared contracts and runtime modules for Tela coding-agent sandboxes: agent protocol, supervisor wire protocol, resident supervisor, worker runtime client, and Claude/Codex harness adapters.",
7
7
  "license": "UNLICENSED",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/meistrari/agent-core.git"
10
+ "url": "git+https://github.com/meistrari/agent-core.git"
11
11
  },
12
12
  "publishConfig": {
13
13
  "access": "public",
@@ -22,6 +22,9 @@
22
22
  "./protocol/agent-content": "./src/protocol/agent-content.ts",
23
23
  "./protocol/agent-tool-name": "./src/protocol/agent-tool-name.ts",
24
24
  "./protocol/agent-work": "./src/protocol/agent-work.ts",
25
+ "./agents": "./src/agents/index.ts",
26
+ "./claude": "./src/agents/claude/index.ts",
27
+ "./codex": "./src/agents/codex/index.ts",
25
28
  "./supervisor-protocol/agent-event-wrapper": "./src/supervisor-protocol/agent-event-wrapper.ts",
26
29
  "./supervisor-protocol/bootstrap": "./src/supervisor-protocol/bootstrap.ts",
27
30
  "./supervisor-protocol/bootstrap-rejection-receipt": "./src/supervisor-protocol/bootstrap-rejection-receipt.ts",
@@ -42,7 +45,15 @@
42
45
  "./supervisor-protocol/supervisor-agent-run-snapshot": "./src/supervisor-protocol/supervisor-agent-run-snapshot.ts",
43
46
  "./supervisor-protocol/supervisor-event": "./src/supervisor-protocol/supervisor-event.ts",
44
47
  "./supervisor-protocol/tela-page-content": "./src/supervisor-protocol/tela-page-content.ts",
45
- "./supervisor-protocol/wire-codec": "./src/supervisor-protocol/wire-codec.ts"
48
+ "./supervisor-protocol/wire-codec": "./src/supervisor-protocol/wire-codec.ts",
49
+ "./template-onboarding": "./src/template-onboarding.ts",
50
+ "./supervisor": "./src/supervisor/index.ts",
51
+ "./supervisor/provider-factory": "./src/supervisor/provider-factory.ts",
52
+ "./testing": "./src/testing/index.ts",
53
+ "./worker-runtime-client": "./src/worker-runtime-client/index.ts"
54
+ },
55
+ "bin": {
56
+ "agent-core-supervisor": "bin/supervisor.ts"
46
57
  },
47
58
  "engines": {
48
59
  "bun": ">=1.3.10"
@@ -53,17 +64,45 @@
53
64
  "typecheck": "tsc --noEmit",
54
65
  "test": "bun test src/",
55
66
  "test:e2e": "bun test --pass-with-no-tests e2e/",
67
+ "build:supervisor": "bun scripts/build-supervisor-executable.ts",
56
68
  "provenance": "bun scripts/write-provenance.ts",
57
69
  "build": "bun scripts/write-provenance.ts"
58
70
  },
71
+ "peerDependencies": {
72
+ "@anthropic-ai/claude-agent-sdk": ">=0.3.207 <0.4.0",
73
+ "@anthropic-ai/sdk": "^0.93.0",
74
+ "@modelcontextprotocol/sdk": "^1.29.0",
75
+ "pg": "^8.16.3"
76
+ },
77
+ "peerDependenciesMeta": {
78
+ "@anthropic-ai/claude-agent-sdk": {
79
+ "optional": true
80
+ },
81
+ "@anthropic-ai/sdk": {
82
+ "optional": true
83
+ },
84
+ "@modelcontextprotocol/sdk": {
85
+ "optional": true
86
+ },
87
+ "pg": {
88
+ "optional": true
89
+ }
90
+ },
59
91
  "dependencies": {
92
+ "jose": "6.2.11",
60
93
  "pino": "^9.7.0",
94
+ "ulid": "3.0.2",
61
95
  "zod": "^4.1.12"
62
96
  },
63
97
  "devDependencies": {
64
98
  "@antfu/eslint-config": "4.16.2",
99
+ "@anthropic-ai/claude-agent-sdk": "0.3.224",
100
+ "@anthropic-ai/sdk": "0.93.0",
101
+ "@modelcontextprotocol/sdk": "1.29.0",
65
102
  "@types/bun": "1.3.12",
103
+ "@types/pg": "8.23.1",
66
104
  "eslint": "^9.30.0",
105
+ "pg": "8.23.0",
67
106
  "typescript": "5.9.3"
68
107
  }
69
108
  }
@@ -0,0 +1,32 @@
1
+ import { mkdir } from 'node:fs/promises'
2
+ import { dirname, resolve } from 'node:path'
3
+
4
+ const outputArgument = process.argv.indexOf('--outfile')
5
+ const output = resolve(outputArgument >= 0
6
+ ? requiredArgument('--outfile', outputArgument)
7
+ : 'dist/agent-core-supervisor-linux-x64')
8
+ const targetArgument = process.argv.indexOf('--target')
9
+ const target = targetArgument >= 0
10
+ ? requiredArgument('--target', targetArgument) as Bun.Build.CompileTarget
11
+ : 'bun-linux-x64'
12
+
13
+ await mkdir(dirname(output), { recursive: true })
14
+ const result = await Bun.build({
15
+ entrypoints: [resolve('bin/supervisor.ts')],
16
+ compile: { target, outfile: output },
17
+ minify: true,
18
+ sourcemap: 'none',
19
+ })
20
+ if (!result.success) {
21
+ for (const log of result.logs)
22
+ console.error(log)
23
+ process.exit(1)
24
+ }
25
+ console.log(output)
26
+
27
+ function requiredArgument(name: string, index: number): string {
28
+ const value = process.argv[index + 1]
29
+ if (!value || value.startsWith('--'))
30
+ throw new Error(`${name} requires a value.`)
31
+ return value
32
+ }
@@ -0,0 +1,23 @@
1
+ import type { AgentError } from '../protocol'
2
+
3
+ type ErrorWithCode = Error & { code?: unknown, cause?: unknown }
4
+
5
+ const MAX_CAUSE_DEPTH = 3
6
+
7
+ export function serializeAgentError(error: unknown, depth = 0): AgentError {
8
+ if (error instanceof Error) {
9
+ const withCode = error as ErrorWithCode
10
+ const serialized: AgentError = { message: error.message || error.name || 'Unknown error' }
11
+ if (typeof withCode.code === 'string' && withCode.code.trim())
12
+ serialized.code = withCode.code
13
+ if (error.name && error.name !== 'Error')
14
+ serialized.name = error.name
15
+ if (error.stack)
16
+ serialized.stack = error.stack
17
+ if (withCode.cause !== undefined && depth < MAX_CAUSE_DEPTH)
18
+ serialized.cause = serializeAgentError(withCode.cause, depth + 1)
19
+ return serialized
20
+ }
21
+
22
+ return { message: String(error) }
23
+ }
@@ -0,0 +1,81 @@
1
+ import type { AgentEvent, AgentProviderId } from '../protocol'
2
+ import { agentEventSchema, InvalidAgentEventError } from '../protocol'
3
+ import { createAgentEventId } from './agent-id'
4
+
5
+ type AgentEventEnvelopeField = 'eventId' | 'provider' | 'sessionId' | 'providerSessionId' | 'timestamp' | 'sequence'
6
+ export type AgentEventDraft = AgentEvent extends infer Event
7
+ ? Event extends AgentEvent
8
+ ? Omit<Event, AgentEventEnvelopeField>
9
+ : never
10
+ : never
11
+
12
+ export class AgentEventStream implements AsyncIterable<AgentEvent> {
13
+ private readonly items: AgentEvent[] = []
14
+ private readonly resolvers: Array<(result: IteratorResult<AgentEvent>) => void> = []
15
+ private ended = false
16
+ private sequence = 0
17
+
18
+ constructor(
19
+ private readonly envelope: {
20
+ provider: AgentProviderId
21
+ sessionId: string
22
+ providerSessionId: string
23
+ },
24
+ ) {}
25
+
26
+ push(draft: AgentEventDraft): AgentEvent {
27
+ if (this.ended) {
28
+ throw new InvalidAgentEventError({ message: 'Cannot push an event after the stream has ended.' })
29
+ }
30
+
31
+ const parsed = agentEventSchema.safeParse({
32
+ ...draft,
33
+ eventId: createAgentEventId(),
34
+ provider: this.envelope.provider,
35
+ sessionId: this.envelope.sessionId,
36
+ providerSessionId: this.envelope.providerSessionId,
37
+ timestamp: new Date().toISOString(),
38
+ sequence: this.sequence,
39
+ })
40
+ if (!parsed.success) {
41
+ throw new InvalidAgentEventError({
42
+ message: 'Agent event draft is invalid.',
43
+ cause: parsed.error,
44
+ })
45
+ }
46
+ const event = parsed.data
47
+ this.sequence += 1
48
+
49
+ const resolver = this.resolvers.shift()
50
+ if (resolver) {
51
+ resolver({ value: event, done: false })
52
+ }
53
+ else {
54
+ this.items.push(event)
55
+ }
56
+
57
+ return event
58
+ }
59
+
60
+ end(): void {
61
+ if (this.ended)
62
+ return
63
+ this.ended = true
64
+ for (const resolver of this.resolvers.splice(0)) {
65
+ resolver({ value: undefined, done: true })
66
+ }
67
+ }
68
+
69
+ [Symbol.asyncIterator](): AsyncIterator<AgentEvent> {
70
+ return {
71
+ next: async () => {
72
+ const item = this.items.shift()
73
+ if (item)
74
+ return await Promise.resolve({ value: item, done: false })
75
+ if (this.ended)
76
+ return await Promise.resolve({ value: undefined, done: true })
77
+ return await new Promise<IteratorResult<AgentEvent>>(resolve => this.resolvers.push(resolve))
78
+ },
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,17 @@
1
+ import { ulid } from 'ulid'
2
+
3
+ export function createAgentEventId(): string {
4
+ return `evt_${ulid()}`
5
+ }
6
+
7
+ export function createUserInputRequestId(): string {
8
+ return `req_${ulid()}`
9
+ }
10
+
11
+ export function createRuntimeTurnId(): string {
12
+ return `turn_${ulid()}`
13
+ }
14
+
15
+ export function createRuntimeWorkItemId(): string {
16
+ return `work_${ulid()}`
17
+ }
@@ -0,0 +1,11 @@
1
+ import type { AgentInputAttachmentPreparation } from './materialized-input-attachment'
2
+
3
+ export type AgentSenderContext
4
+ = | { kind: 'application' }
5
+ | { kind: 'slack', mention: string }
6
+
7
+ export interface AgentOperationOptions {
8
+ signal?: AbortSignal
9
+ inputAttachmentPreparation?: AgentInputAttachmentPreparation
10
+ senderContext?: AgentSenderContext
11
+ }
@@ -0,0 +1,37 @@
1
+ import type {
2
+ AgentModelId,
3
+ AgentProviderId,
4
+ AgentReasoningEffort,
5
+ AgentToolDefinition,
6
+ } from '../protocol'
7
+ import type { AgentOperationOptions } from './agent-operation'
8
+ import type { AgentRun } from './agent-run'
9
+
10
+ interface AgentSessionConfigInput {
11
+ sessionId: string
12
+ cwd: string
13
+ workspaceRepositoryRoots: readonly string[]
14
+ reasoningEffort?: AgentReasoningEffort
15
+ tools?: AgentToolDefinition[]
16
+ }
17
+
18
+ // Distributive over the provider so each arm keeps its provider correlated with its own model union:
19
+ // the default `AgentProviderId` reproduces the `codex | claude` discriminated union, while a single
20
+ // provider narrows to exactly that arm.
21
+ export type AgentSessionOpenInput<Provider extends AgentProviderId = AgentProviderId>
22
+ = Provider extends AgentProviderId
23
+ ? AgentSessionConfigInput & { provider: Provider, model?: AgentModelId<Provider> }
24
+ : never
25
+
26
+ export type AgentSessionResumeInput<Provider extends AgentProviderId = AgentProviderId>
27
+ = Provider extends AgentProviderId
28
+ ? AgentSessionConfigInput & { provider: Provider, providerSessionId: string, model?: AgentModelId<Provider> }
29
+ : never
30
+
31
+ // Lifecycle only: openSession/resumeSession never apply user input. Callers submit the durable
32
+ // command through the returned run afterwards, so the provider binding can be persisted first.
33
+ export interface AgentProvider<Provider extends AgentProviderId = AgentProviderId> {
34
+ readonly id: Provider
35
+ openSession: (input: AgentSessionOpenInput<Provider>, options?: AgentOperationOptions) => Promise<AgentRun>
36
+ resumeSession: (input: AgentSessionResumeInput<Provider>, options?: AgentOperationOptions) => Promise<AgentRun>
37
+ }
@@ -0,0 +1,32 @@
1
+ import type {
2
+ AgentEvent,
3
+ AgentRunMetadata,
4
+ InterruptAgentCommand,
5
+ RespondUserInputAgentCommand,
6
+ SendPromptCommand,
7
+ StopAgentCommand,
8
+ } from '../protocol'
9
+ import type { AgentOperationOptions } from './agent-operation'
10
+
11
+ // Whether respond-user-input submitted provider input. `already_delivered` is the durable-store
12
+ // idempotence fact for a redriven duplicate, not a provider prompt-acceptance receipt.
13
+ export type AgentUserInputSubmission = 'submitted' | 'already_delivered'
14
+
15
+ // Positive prompt-submission receipt. `turn` carries the provider's real turn identity (Codex: nested
16
+ // `turn.id` from turn/start, flat `turnId` from turn/steer). `submitted` is the stream-submission arm
17
+ // for providers whose input boundary carries no turn identity.
18
+ export type AgentPromptAcceptance
19
+ = | { kind: 'turn', turnId: string, placement: 'started' | 'steered' }
20
+ | { kind: 'submitted' }
21
+
22
+ export interface AgentRun {
23
+ readonly metadata: AgentRunMetadata
24
+ readonly events: AsyncIterable<AgentEvent>
25
+ prepareWorkspace: (options?: AgentOperationOptions) => Promise<void>
26
+ // Providers without root registration accept the root without provider I/O.
27
+ addWorkspaceRepositoryRoot: (root: string, options?: AgentOperationOptions) => Promise<void>
28
+ sendPrompt: (command: SendPromptCommand, options?: AgentOperationOptions) => Promise<AgentPromptAcceptance>
29
+ respondUserInput: (command: RespondUserInputAgentCommand, options?: AgentOperationOptions) => Promise<AgentUserInputSubmission>
30
+ interrupt: (command: InterruptAgentCommand, options?: AgentOperationOptions) => Promise<void>
31
+ stop: (command: StopAgentCommand, options?: AgentOperationOptions) => Promise<void>
32
+ }
@@ -0,0 +1,161 @@
1
+ import type { InfrastructureErrorInput, ProviderErrorInput, ValidationErrorInput } from '../errors'
2
+ import { InfrastructureError, ProviderError, ValidationError } from '../errors'
3
+
4
+ type RuntimeProviderErrorInput = Omit<ProviderErrorInput, 'code'> & { code?: string }
5
+ type RuntimeValidationErrorInput = Omit<ValidationErrorInput, 'code'> & { code?: string }
6
+ type RuntimeInfrastructureErrorInput = Omit<InfrastructureErrorInput, 'code'> & { code?: string }
7
+
8
+ export class AgentProviderRuntimeError extends ProviderError {
9
+ constructor(input: RuntimeProviderErrorInput) {
10
+ const { code = 'agents.provider-error', ...rest } = input
11
+ super({ code, ...rest })
12
+ }
13
+ }
14
+
15
+ // The one recovery consequence the supervisor loop performs for an input/operation the boundary
16
+ // proved was not accepted by the provider or coordinator.
17
+ export type KnownNotAcceptedRecovery
18
+ = | { kind: 'retry', reason: string, retryAfterMs?: number }
19
+ | { kind: 'external_action', reason: string }
20
+ | { kind: 'terminal', reason: string }
21
+
22
+ export class AgentNotAcceptedError extends AgentProviderRuntimeError {
23
+ readonly recovery: KnownNotAcceptedRecovery
24
+
25
+ constructor(input: Omit<RuntimeProviderErrorInput, 'code' | 'retryable'> & { recovery: KnownNotAcceptedRecovery }) {
26
+ const { recovery, ...rest } = input
27
+ super({
28
+ code: 'agents.input-not-accepted',
29
+ publicMessage: 'Agent provider did not accept the operation.',
30
+ retryable: recovery.kind === 'retry',
31
+ ...rest,
32
+ })
33
+ this.recovery = recovery
34
+ }
35
+ }
36
+
37
+ // Turn-boundary race fact: the prompt was proven not accepted. Two producers exist — an adapter
38
+ // pre-defers a `next` while its state shows an active turn (no request issued), and a `turn/steer`
39
+ // answered -32600 is a post-write provider response proving the input was not accepted. Consumed
40
+ // by the supervisor loop, which returns the durable command to pending, stops the drain pass, and
41
+ // re-dispatches on the next work-relevant session-state change — no retry timer, no backoff.
42
+ export class AgentPromptDeferredError extends AgentProviderRuntimeError {
43
+ constructor(input: Omit<RuntimeProviderErrorInput, 'code' | 'retryable'>) {
44
+ super({
45
+ code: 'agents.prompt-deferred',
46
+ publicMessage: 'Agent provider deferred the prompt at a turn boundary race.',
47
+ retryable: false,
48
+ ...input,
49
+ })
50
+ }
51
+ }
52
+
53
+ // Transport/response loss after the input may have been written: a delivery-unknown invalidates the
54
+ // run's ownership but does not prove the run has closed (a Codex stdin write failure need not set
55
+ // `stopped`). The supervisor explicitly relinquishes — stops the run and drains its consumer — then
56
+ // durably waits before redriving the same command id; a duplicate is the accepted at-least-once cost.
57
+ export class AgentDeliveryUnknownError extends AgentProviderRuntimeError {
58
+ constructor(input: Omit<RuntimeProviderErrorInput, 'code' | 'retryable'>) {
59
+ super({
60
+ code: 'agents.delivery-unknown',
61
+ publicMessage: 'Agent provider delivery outcome is unknown.',
62
+ retryable: false,
63
+ ...input,
64
+ })
65
+ }
66
+ }
67
+
68
+ export class AgentProviderLaunchError extends AgentProviderRuntimeError {
69
+ constructor(input: Omit<RuntimeProviderErrorInput, 'code'>) {
70
+ super({
71
+ code: 'agents.provider-launch-failed',
72
+ publicMessage: 'Agent provider failed to start.',
73
+ retryable: true,
74
+ ...input,
75
+ })
76
+ }
77
+ }
78
+
79
+ export class AgentProviderProtocolError extends AgentProviderRuntimeError {
80
+ constructor(input: Omit<RuntimeProviderErrorInput, 'code'>) {
81
+ super({
82
+ code: 'agents.provider-protocol-error',
83
+ publicMessage: 'Agent provider returned an invalid response.',
84
+ retryable: false,
85
+ ...input,
86
+ })
87
+ }
88
+ }
89
+
90
+ export class AgentProviderMetadataError extends AgentProviderRuntimeError {
91
+ constructor(input: Omit<RuntimeProviderErrorInput, 'code'>) {
92
+ super({
93
+ code: 'agents.provider-metadata-missing',
94
+ publicMessage: 'Agent provider did not return required session metadata.',
95
+ retryable: false,
96
+ ...input,
97
+ })
98
+ }
99
+ }
100
+
101
+ export class AgentRuntimeValidationError extends ValidationError {
102
+ constructor(input: RuntimeValidationErrorInput) {
103
+ const { code = 'agents.validation-failed', ...rest } = input
104
+ super({ code, ...rest })
105
+ }
106
+ }
107
+
108
+ export class AgentUnsupportedPromptBlockError extends AgentRuntimeValidationError {
109
+ constructor(input: { provider: string, blockType: string }) {
110
+ super({
111
+ code: 'agents.unsupported-prompt-block',
112
+ message: `Provider ${input.provider} does not support prompt block type ${input.blockType}.`,
113
+ publicMessage: 'Agent prompt contains unsupported content for the selected provider.',
114
+ details: input,
115
+ })
116
+ }
117
+ }
118
+
119
+ export class AgentToolValidationError extends AgentRuntimeValidationError {
120
+ constructor(input: Omit<RuntimeValidationErrorInput, 'code'>) {
121
+ super({
122
+ code: 'agents.tool-validation-failed',
123
+ publicMessage: 'Agent tool configuration is invalid.',
124
+ ...input,
125
+ })
126
+ }
127
+ }
128
+
129
+ export class AgentRunStateError extends AgentRuntimeValidationError {
130
+ constructor(input: Omit<RuntimeValidationErrorInput, 'code'>) {
131
+ super({
132
+ code: 'agents.invalid-run-state',
133
+ publicMessage: 'Agent run is not in the required state for this operation.',
134
+ ...input,
135
+ })
136
+ }
137
+ }
138
+
139
+ export class AgentUserInputRequestStoreError extends InfrastructureError {
140
+ constructor(input: Omit<RuntimeInfrastructureErrorInput, 'code'>) {
141
+ super({
142
+ code: 'agents.user-input-request-store-error',
143
+ publicMessage: 'Agent user-input request state is unavailable.',
144
+ retryable: false,
145
+ ...input,
146
+ })
147
+ }
148
+ }
149
+
150
+ export class AgentOperationAbortedError extends InfrastructureError {
151
+ constructor(input: Omit<RuntimeInfrastructureErrorInput, 'code' | 'message'> & { message?: string } = {}) {
152
+ super({
153
+ code: 'agents.operation-aborted',
154
+ message: input.message ?? 'Agent operation was aborted.',
155
+ publicMessage: 'Agent operation was aborted.',
156
+ retryable: false,
157
+ details: input.details,
158
+ cause: input.cause,
159
+ })
160
+ }
161
+ }