@meistrari/agent-core 0.0.0 → 0.1.0

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 (83) hide show
  1. package/README.md +24 -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 +736 -0
  18. package/src/agents/claude/claude-provider.ts +191 -0
  19. package/src/agents/claude/claude-run.ts +464 -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 +708 -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/provenance.gen.ts +3 -3
  48. package/src/supervisor/agent-provider-factory.ts +189 -0
  49. package/src/supervisor/bootstrap-binder.ts +125 -0
  50. package/src/supervisor/config.ts +49 -0
  51. package/src/supervisor/control-authority-verifier.ts +135 -0
  52. package/src/supervisor/create-supervisor-runtime.ts +25 -0
  53. package/src/supervisor/errors.ts +24 -0
  54. package/src/supervisor/index.ts +34 -0
  55. package/src/supervisor/persistence/json.ts +21 -0
  56. package/src/supervisor/persistence/state-discovery.ts +56 -0
  57. package/src/supervisor/persistence/supervisor-store.ts +364 -0
  58. package/src/supervisor/ports/index.ts +109 -0
  59. package/src/supervisor/provider-factory.ts +37 -0
  60. package/src/supervisor/resident.ts +143 -0
  61. package/src/supervisor/rpc-client.ts +120 -0
  62. package/src/supervisor/runtime-handler.ts +309 -0
  63. package/src/supervisor/websocket-server.ts +434 -0
  64. package/src/supervisor-protocol/bootstrap.ts +1 -1
  65. package/src/template-onboarding.ts +47 -0
  66. package/src/testing/es256-test-keys.ts +73 -0
  67. package/src/testing/in-memory-runtime-control-plane.ts +205 -0
  68. package/src/testing/index.ts +6 -0
  69. package/src/testing/loopback-supervisor-connection.ts +71 -0
  70. package/src/testing/scripted-provider.ts +64 -0
  71. package/src/worker-runtime-client/command-pump.ts +132 -0
  72. package/src/worker-runtime-client/connection-attempt.ts +340 -0
  73. package/src/worker-runtime-client/control-authority-signer.ts +100 -0
  74. package/src/worker-runtime-client/e2b-supervisor-connection.ts +102 -0
  75. package/src/worker-runtime-client/frame-processor.ts +178 -0
  76. package/src/worker-runtime-client/index.ts +27 -0
  77. package/src/worker-runtime-client/lease-reconciler.ts +14 -0
  78. package/src/worker-runtime-client/ports.ts +137 -0
  79. package/src/worker-runtime-client/postgres-notification-listener.ts +91 -0
  80. package/src/worker-runtime-client/rpc-dispatcher.ts +27 -0
  81. package/src/worker-runtime-client/rpc-request-manager.ts +141 -0
  82. package/src/worker-runtime-client/sandbox-connection-runtime.ts +300 -0
  83. package/src/worker-runtime-client/token-crypto.ts +46 -0
@@ -0,0 +1,326 @@
1
+ import type { ChildProcessWithoutNullStreams } from 'node:child_process'
2
+ import type {
3
+ ClientNotificationMethod,
4
+ ClientNotificationParamsByMethod,
5
+ ClientRequestMethod,
6
+ ClientRequestParamsByMethod,
7
+ ClientRequestResponsesByMethod,
8
+ ServerNotificationMethod,
9
+ ServerNotificationParamsByMethod,
10
+ ServerRequestMethod,
11
+ ServerRequestParamsByMethod,
12
+ } from './codex-protocol'
13
+ import { spawn } from 'node:child_process'
14
+ import { createInterface } from 'node:readline'
15
+ import { CodexJsonRpcResponseError, CodexJsonRpcTransportClosedError } from './codex.errors'
16
+
17
+ type JsonRpcId = string | number | null
18
+
19
+ export type CodexServerNotification = {
20
+ [Method in ServerNotificationMethod]: {
21
+ id?: undefined
22
+ method: Method
23
+ params: ServerNotificationParamsByMethod[Method]
24
+ }
25
+ }[ServerNotificationMethod]
26
+
27
+ export type CodexServerRequest = {
28
+ [Method in ServerRequestMethod]: {
29
+ id: JsonRpcId
30
+ method: Method
31
+ params: ServerRequestParamsByMethod[Method]
32
+ }
33
+ }[ServerRequestMethod]
34
+
35
+ interface CodexClientResponse {
36
+ id: JsonRpcId
37
+ method?: undefined
38
+ params?: undefined
39
+ result?: unknown
40
+ error?: unknown
41
+ }
42
+
43
+ export type CodexJsonRpcMessage = CodexServerNotification | CodexServerRequest | CodexClientResponse
44
+
45
+ export function isCodexServerRequest(message: CodexJsonRpcMessage): message is CodexServerRequest {
46
+ return message.method !== undefined && message.id !== undefined
47
+ }
48
+
49
+ export function isCodexServerNotification(message: CodexJsonRpcMessage): message is CodexServerNotification {
50
+ return message.method !== undefined && message.id === undefined
51
+ }
52
+
53
+ const PROCESS_SHUTDOWN_GRACE_MS = 5_000
54
+
55
+ export { CodexJsonRpcResponseError } from './codex.errors'
56
+
57
+ export class CodexJsonRpcClient {
58
+ readonly child: ChildProcessWithoutNullStreams
59
+ private nextId = 0
60
+ private stopped = false
61
+ private readonly closed: Promise<void>
62
+ private readonly pending = new Map<string, {
63
+ method: ClientRequestMethod
64
+ resolve: (value: unknown) => void
65
+ reject: (error: Error) => void
66
+ }>()
67
+
68
+ constructor(
69
+ executable: string,
70
+ options: {
71
+ cwd: string
72
+ environment?: Record<string, string | undefined>
73
+ onMessage: (message: CodexJsonRpcMessage) => void
74
+ onInvalidLine: (line: string, error: unknown) => void
75
+ onStderr: (line: string) => void
76
+ },
77
+ ) {
78
+ this.child = spawn(executable, ['app-server', '--listen', 'stdio://'], {
79
+ cwd: options.cwd,
80
+ env: options.environment,
81
+ stdio: 'pipe',
82
+ })
83
+ this.closed = new Promise(resolve => this.child.once('close', () => resolve()))
84
+
85
+ this.child.on('error', (error) => {
86
+ // The process never started, so no buffered request reached a live Codex: before_write.
87
+ // The client is also terminal — later requests must reject immediately, never hang.
88
+ this.stopped = true
89
+ for (const [, waiter] of this.pending) {
90
+ waiter.reject(new CodexJsonRpcTransportClosedError({
91
+ method: waiter.method,
92
+ writeState: 'before_write',
93
+ message: `Codex app-server process failed to start (${executable}).`,
94
+ cause: error,
95
+ }))
96
+ }
97
+ this.pending.clear()
98
+ })
99
+
100
+ this.child.on('close', (code, signal) => {
101
+ this.stopped = true
102
+ if (this.pending.size === 0)
103
+ return
104
+ // The request was written to a live process that died before responding: the input may
105
+ // already have been consumed, so non-delivery cannot be proven (possible_write).
106
+ for (const [, waiter] of this.pending) {
107
+ waiter.reject(new CodexJsonRpcTransportClosedError({
108
+ method: waiter.method,
109
+ writeState: 'possible_write',
110
+ message: `Codex app-server closed before responding (exit ${code ?? 'null'}, signal ${signal ?? 'null'}).`,
111
+ }))
112
+ }
113
+ this.pending.clear()
114
+ })
115
+
116
+ // stdin can error independently of process exit (e.g. EPIPE when the child closed its
117
+ // read end); unhandled it would crash the host process, and any written request is
118
+ // unprovable: possible_write.
119
+ this.child.stdin.on('error', (error) => {
120
+ this.stopped = true
121
+ for (const [, waiter] of this.pending) {
122
+ waiter.reject(new CodexJsonRpcTransportClosedError({
123
+ method: waiter.method,
124
+ writeState: 'possible_write',
125
+ message: 'Codex app-server stdin failed.',
126
+ cause: error,
127
+ }))
128
+ }
129
+ this.pending.clear()
130
+ })
131
+
132
+ readLines(this.child.stdout, (line) => {
133
+ if (this.stopped)
134
+ return
135
+ try {
136
+ const parsed = JSON.parse(line) as CodexJsonRpcMessage
137
+ this.handleMessage(parsed, options.onMessage)
138
+ }
139
+ catch (error) {
140
+ if (!this.stopped)
141
+ options.onInvalidLine(line, error)
142
+ }
143
+ })
144
+ readLines(this.child.stderr, (line) => {
145
+ if (!this.stopped)
146
+ options.onStderr(line)
147
+ })
148
+ }
149
+
150
+ async request<M extends ClientRequestMethod>(
151
+ method: M,
152
+ params: ClientRequestParamsByMethod[M],
153
+ ): Promise<ClientRequestResponsesByMethod[M]> {
154
+ if (this.stopped)
155
+ throw closedClientError(method, 'before_write')
156
+
157
+ const id = this.nextId
158
+ this.nextId += 1
159
+
160
+ return await new Promise<ClientRequestResponsesByMethod[M]>((resolve, reject) => {
161
+ // Register the waiter before the write: a close/error racing the write must find and
162
+ // reject it instead of leaving the request hanging forever.
163
+ this.pending.set(String(id), {
164
+ method,
165
+ resolve: value => resolve(value as ClientRequestResponsesByMethod[M]),
166
+ reject,
167
+ })
168
+ const settleWriteFailure = (cause: unknown) => {
169
+ const waiter = this.pending.get(String(id))
170
+ if (!waiter)
171
+ return
172
+ this.pending.delete(String(id))
173
+ // A failed write attempt cannot prove zero bytes reached the process: possible_write.
174
+ waiter.reject(new CodexJsonRpcTransportClosedError({
175
+ method,
176
+ writeState: 'possible_write',
177
+ message: 'Codex app-server stdin write failed.',
178
+ cause,
179
+ }))
180
+ }
181
+ try {
182
+ this.child.stdin.write(`${JSON.stringify({ id, method, params })}\n`, (error: Error | null | undefined) => {
183
+ if (error)
184
+ settleWriteFailure(error)
185
+ })
186
+ }
187
+ catch (error) {
188
+ settleWriteFailure(error)
189
+ }
190
+ })
191
+ }
192
+
193
+ notify<M extends ClientNotificationMethod>(method: M, params: ClientNotificationParamsByMethod[M]): void {
194
+ if (this.stopped)
195
+ return
196
+ this.write(params === undefined ? { method } : { method, params })
197
+ }
198
+
199
+ respond(id: JsonRpcId, result: unknown): void {
200
+ if (this.stopped)
201
+ return
202
+ this.write({ id, result })
203
+ }
204
+
205
+ respondError(id: JsonRpcId, error: { code: number, message: string }): void {
206
+ if (this.stopped)
207
+ return
208
+ this.write({ id, error })
209
+ }
210
+
211
+ async respondErrorAndFlush(id: JsonRpcId, error: { code: number, message: string }): Promise<void> {
212
+ if (this.stopped)
213
+ return
214
+ await this.writeAndFlush({ id, error })
215
+ }
216
+
217
+ async closeInputAndStop(): Promise<void> {
218
+ if (this.stopped) {
219
+ await this.closed
220
+ return
221
+ }
222
+ this.stopped = true
223
+ for (const waiter of this.pending.values()) {
224
+ waiter.reject(closedClientError(waiter.method, 'possible_write'))
225
+ }
226
+ this.pending.clear()
227
+ await endWritable(this.child.stdin)
228
+ if (await waitForClosed(this.closed, PROCESS_SHUTDOWN_GRACE_MS))
229
+ return
230
+ await terminateChild(this.child, this.closed)
231
+ }
232
+
233
+ async stop(): Promise<void> {
234
+ await this.closeInputAndStop()
235
+ }
236
+
237
+ private write(message: object): void {
238
+ if (this.stopped)
239
+ return
240
+ this.child.stdin.write(`${JSON.stringify(message)}\n`)
241
+ }
242
+
243
+ private async writeAndFlush(message: object): Promise<void> {
244
+ if (this.stopped)
245
+ return
246
+ await new Promise<void>((resolve, reject) => {
247
+ this.child.stdin.write(`${JSON.stringify(message)}\n`, (error: Error | null | undefined) => {
248
+ if (error)
249
+ reject(error)
250
+ else
251
+ resolve()
252
+ })
253
+ })
254
+ }
255
+
256
+ private handleMessage(message: CodexJsonRpcMessage, onMessage: (message: CodexJsonRpcMessage) => void): void {
257
+ if (message.id !== undefined && !message.method && ('result' in message || 'error' in message)) {
258
+ const waiter = this.pending.get(String(message.id))
259
+ if (!waiter)
260
+ return
261
+ this.pending.delete(String(message.id))
262
+ if ('error' in message) {
263
+ waiter.reject(new CodexJsonRpcResponseError({ method: waiter.method, responseError: message.error }))
264
+ return
265
+ }
266
+ waiter.resolve(message.result)
267
+ return
268
+ }
269
+
270
+ onMessage(message)
271
+ }
272
+ }
273
+
274
+ async function endWritable(stream: NodeJS.WritableStream): Promise<void> {
275
+ await new Promise<void>(resolve => stream.end(resolve))
276
+ }
277
+
278
+ async function terminateChild(child: ChildProcessWithoutNullStreams, closed: Promise<void>): Promise<void> {
279
+ if (child.exitCode !== null || child.signalCode !== null) {
280
+ await closed
281
+ return
282
+ }
283
+
284
+ child.kill('SIGINT')
285
+ if (await waitForClosed(closed, PROCESS_SHUTDOWN_GRACE_MS))
286
+ return
287
+
288
+ if (child.exitCode === null && child.signalCode === null)
289
+ child.kill('SIGTERM')
290
+ if (await waitForClosed(closed, PROCESS_SHUTDOWN_GRACE_MS))
291
+ return
292
+
293
+ if (child.exitCode === null && child.signalCode === null)
294
+ child.kill('SIGKILL')
295
+ await closed
296
+ }
297
+
298
+ async function waitForClosed(closed: Promise<void>, timeoutMs: number): Promise<boolean> {
299
+ return await new Promise<boolean>((resolve) => {
300
+ const timer = setTimeout(() => resolve(false), timeoutMs)
301
+ timer.unref?.()
302
+ void closed.then(
303
+ () => {
304
+ clearTimeout(timer)
305
+ resolve(true)
306
+ },
307
+ () => {
308
+ clearTimeout(timer)
309
+ resolve(false)
310
+ },
311
+ )
312
+ })
313
+ }
314
+
315
+ function closedClientError(method: ClientRequestMethod, writeState: 'before_write' | 'possible_write'): CodexJsonRpcTransportClosedError {
316
+ return new CodexJsonRpcTransportClosedError({ method, writeState, message: 'Codex app-server client is closed.' })
317
+ }
318
+
319
+ function readLines(stream: NodeJS.ReadableStream, onLine: (line: string) => void): void {
320
+ const rl = createInterface({ input: stream })
321
+ rl.on('line', (line) => {
322
+ const trimmed = line.trim()
323
+ if (trimmed)
324
+ onLine(trimmed)
325
+ })
326
+ }
@@ -0,0 +1,36 @@
1
+ import type {
2
+ ClientRequestParamsByMethod,
3
+ ServerRequestParamsByMethod,
4
+ ServerRequestResponsesByMethod,
5
+ } from './generated/meta.gen'
6
+ import type { V2ThreadStartParams__DynamicToolSpec, V2TurnStartParams__UserInput } from './generated/schema.gen'
7
+
8
+ export type {
9
+ ClientNotificationMethod,
10
+ ClientNotificationParamsByMethod,
11
+ ClientRequestMethod,
12
+ ClientRequestParamsByMethod,
13
+ ClientRequestResponsesByMethod,
14
+ ServerNotificationMethod,
15
+ ServerNotificationParamsByMethod,
16
+ ServerRequestMethod,
17
+ ServerRequestParamsByMethod,
18
+ ServerRequestResponsesByMethod,
19
+ } from './generated/meta.gen'
20
+
21
+ export type CodexUserInput = V2TurnStartParams__UserInput
22
+ export type CodexDynamicToolSpec = V2ThreadStartParams__DynamicToolSpec
23
+ export type CodexDynamicToolCallParams = ServerRequestParamsByMethod['item/tool/call']
24
+ export type CodexDynamicToolCallResponse = ServerRequestResponsesByMethod['item/tool/call']
25
+ export type CodexDynamicToolOutputContentItem = CodexDynamicToolCallResponse['contentItems'][number]
26
+ export type CodexLoginStartParams = ClientRequestParamsByMethod['account/login/start']
27
+ export type CodexChatGPTAuthTokensRefreshParams = ServerRequestParamsByMethod['account/chatgptAuthTokens/refresh']
28
+ export type CodexChatGPTAuthTokensRefreshResponse = ServerRequestResponsesByMethod['account/chatgptAuthTokens/refresh']
29
+
30
+ export type CodexThreadStartParams = ClientRequestParamsByMethod['thread/start'] & {
31
+ dynamicTools?: readonly CodexDynamicToolSpec[] | null
32
+ }
33
+ export type CodexThreadResumeParams = ClientRequestParamsByMethod['thread/resume']
34
+ export type CodexTurnStartParams = ClientRequestParamsByMethod['turn/start']
35
+ export type CodexTurnSteerParams = ClientRequestParamsByMethod['turn/steer']
36
+ export type CodexTurnInterruptParams = ClientRequestParamsByMethod['turn/interrupt']