@the-open-engine/zeroshot 6.33.1 → 6.34.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 (48) hide show
  1. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
  2. package/lib/agent-cli-provider/adapters/codex.js +3 -1
  3. package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
  4. package/lib/hosted-target/adapter-request.cjs +55 -0
  5. package/lib/hosted-target/adapter-request.d.cts +9 -0
  6. package/lib/hosted-target/adapter-request.d.mts +9 -0
  7. package/lib/hosted-target/adapter-request.d.ts +9 -0
  8. package/lib/hosted-target/adapter-request.mjs +55 -1
  9. package/lib/hosted-target/adapter-types.d.cts +1 -0
  10. package/lib/hosted-target/adapter-types.d.mts +1 -0
  11. package/lib/hosted-target/adapter-types.d.ts +1 -0
  12. package/lib/hosted-target/response-status.cjs +18 -0
  13. package/lib/hosted-target/response-status.d.cts +2 -0
  14. package/lib/hosted-target/response-status.d.mts +2 -0
  15. package/lib/hosted-target/response-status.d.ts +2 -0
  16. package/lib/hosted-target/response-status.mjs +15 -0
  17. package/lib/hosted-target/retry-executor.d.cts +1 -1
  18. package/lib/hosted-target/retry-executor.d.mts +1 -1
  19. package/lib/hosted-target/retry-executor.d.ts +1 -1
  20. package/lib/hosted-target/runtime-install.cjs +45 -0
  21. package/lib/hosted-target/runtime-install.d.cts +14 -0
  22. package/lib/hosted-target/runtime-install.d.mts +14 -0
  23. package/lib/hosted-target/runtime-install.d.ts +14 -0
  24. package/lib/hosted-target/runtime-install.mjs +42 -0
  25. package/lib/hosted-target/zero-cloud-v1-adapter.cjs +35 -66
  26. package/lib/hosted-target/zero-cloud-v1-adapter.d.cts +1 -0
  27. package/lib/hosted-target/zero-cloud-v1-adapter.d.mts +1 -0
  28. package/lib/hosted-target/zero-cloud-v1-adapter.d.ts +1 -0
  29. package/lib/hosted-target/zero-cloud-v1-adapter.mjs +37 -68
  30. package/lib/target/discovery-validation.cjs +11 -18
  31. package/lib/target/discovery-validation.d.cts +1 -12
  32. package/lib/target/discovery-validation.d.mts +1 -12
  33. package/lib/target/discovery-validation.d.ts +1 -12
  34. package/lib/target/discovery-validation.js +11 -18
  35. package/lib/target/discovery-validation.mjs +11 -18
  36. package/npm-shrinkwrap.json +2 -2
  37. package/package.json +2 -1
  38. package/src/agent/agent-task-executor.js +109 -30
  39. package/src/agent-cli-provider/adapters/codex.ts +3 -1
  40. package/src/hosted-target/adapter-request.ts +75 -1
  41. package/src/hosted-target/adapter-types.ts +6 -0
  42. package/src/hosted-target/response-status.ts +21 -0
  43. package/src/hosted-target/retry-executor.ts +8 -1
  44. package/src/hosted-target/runtime-install.ts +83 -0
  45. package/src/hosted-target/zero-cloud-v1-adapter.ts +65 -100
  46. package/src/target/discovery-validation.ts +26 -28
  47. package/task-lib/runner.js +14 -1
  48. package/task-lib/watcher-output-runtime.js +80 -1
@@ -1,12 +1,50 @@
1
1
  import type { RouteTemplate, TargetDiscoveryDescriptor } from '../target/discovery.js';
2
- import { TargetProtocolError } from './errors.js';
2
+ import {
3
+ TargetAdapterError,
4
+ TargetAuthError,
5
+ TargetProtocolError,
6
+ TargetTransportError,
7
+ } from './errors.js';
3
8
  import type { TargetOperation } from './retry-executor.js';
9
+ import type { HttpTransport, TargetAccessTokenProvider } from './types.js';
10
+
11
+ const DEFAULT_TRANSPORT: HttpTransport = {
12
+ fetch(url, init) {
13
+ return globalThis.fetch(url, init);
14
+ },
15
+ };
16
+
17
+ function throwIfAborted(signal?: AbortSignal): void {
18
+ if (signal?.aborted)
19
+ throw signal.reason ?? new globalThis.DOMException('The operation was aborted', 'AbortError');
20
+ }
21
+
22
+ async function resolveAccessToken(
23
+ tokenProvider: TargetAccessTokenProvider,
24
+ accessToken: string | undefined,
25
+ signal: AbortSignal | undefined
26
+ ): Promise<string> {
27
+ if (accessToken !== undefined) return accessToken;
28
+ try {
29
+ return await tokenProvider.getAccessToken(signal);
30
+ } catch {
31
+ throw new TargetAuthError('Target access authorization failed');
32
+ }
33
+ }
4
34
 
5
35
  export type AdapterRequest = {
6
36
  readonly body?: string;
7
37
  readonly headers?: Readonly<Record<string, string>>;
8
38
  };
9
39
 
40
+ export type AdapterRequester = (input: {
41
+ readonly method: string;
42
+ readonly path: string;
43
+ readonly signal: AbortSignal | undefined;
44
+ readonly request: AdapterRequest;
45
+ readonly accessToken: string | undefined;
46
+ }) => Promise<Response>;
47
+
10
48
  export type ExecuteArguments<T> = [
11
49
  operation: TargetOperation,
12
50
  method: string,
@@ -31,3 +69,39 @@ export function requestUrl(path: string, descriptor: TargetDiscoveryDescriptor):
31
69
  }
32
70
  return url;
33
71
  }
72
+
73
+ export function createAdapterRequester(
74
+ descriptor: TargetDiscoveryDescriptor,
75
+ tokenProvider: TargetAccessTokenProvider,
76
+ transport: HttpTransport = DEFAULT_TRANSPORT
77
+ ): AdapterRequester {
78
+ return async ({ method, path, signal, request, accessToken }) => {
79
+ throwIfAborted(signal);
80
+ const url = requestUrl(path, descriptor);
81
+ const token = await resolveAccessToken(tokenProvider, accessToken, signal);
82
+ const init: RequestInit & { redirect: 'manual' } = {
83
+ method,
84
+ headers: {
85
+ Accept: 'application/json',
86
+ Authorization: `Bearer ${token}`,
87
+ ...(request.body === undefined ? {} : { 'Content-Type': 'application/json' }),
88
+ ...request.headers,
89
+ },
90
+ redirect: 'manual',
91
+ };
92
+ if (request.body !== undefined) init.body = request.body;
93
+ if (signal !== undefined) init.signal = signal;
94
+ try {
95
+ const response = await transport.fetch(url.href, init);
96
+ if (response.url && new globalThis.URL(response.url).href !== url.href) {
97
+ await response.body?.cancel().catch(() => undefined);
98
+ throw new TargetProtocolError('Capsule response changed target route');
99
+ }
100
+ return response;
101
+ } catch (error) {
102
+ if (error instanceof TargetAdapterError) throw error;
103
+ throwIfAborted(signal);
104
+ throw new TargetTransportError('Capsule transport failed');
105
+ }
106
+ };
107
+ }
@@ -26,6 +26,12 @@ export interface TargetAdapter {
26
26
  terminate(capsuleId: string, signal?: AbortSignal): Promise<Capsule>;
27
27
  limits(signal?: AbortSignal): Promise<CapsuleLimits>;
28
28
  access(capsuleId: string, signal?: AbortSignal): Promise<CapsuleAccess>;
29
+ installRuntime(
30
+ capsuleId: string,
31
+ runtime: unknown,
32
+ accessToken: string,
33
+ signal?: AbortSignal
34
+ ): Promise<void>;
29
35
  readonly credentialInstall: CredentialInstallCapability;
30
36
  }
31
37
 
@@ -0,0 +1,21 @@
1
+ import { throwCapsuleServerError } from './capsule-error-response.js';
2
+ import { TargetProtocolError } from './errors.js';
3
+ import type { Clock } from './types.js';
4
+
5
+ export async function assertCapsuleResponseStatus(
6
+ response: Response,
7
+ expectedStatus: number,
8
+ readJson: (response: Response) => Promise<unknown>,
9
+ clock: Clock
10
+ ): Promise<void> {
11
+ if (response.status >= 300 && response.status < 400) {
12
+ await response.body?.cancel().catch(() => undefined);
13
+ throw new TargetProtocolError('Capsule redirects are forbidden');
14
+ }
15
+ if (response.status === expectedStatus) return;
16
+ if (response.status >= 200 && response.status < 300) {
17
+ await response.body?.cancel().catch(() => undefined);
18
+ throw new TargetProtocolError('Target returned an unexpected success status');
19
+ }
20
+ await throwCapsuleServerError(response, readJson, clock);
21
+ }
@@ -2,7 +2,14 @@ import { MAX_RETRY_ELAPSED_MS } from './bounds.js';
2
2
  import { TargetAdapterError } from './errors.js';
3
3
  import type { Clock, RetryPolicy } from './types.js';
4
4
 
5
- export type TargetOperation = 'allocate' | 'list' | 'inspect' | 'terminate' | 'limits' | 'access';
5
+ export type TargetOperation =
6
+ | 'allocate'
7
+ | 'list'
8
+ | 'inspect'
9
+ | 'terminate'
10
+ | 'limits'
11
+ | 'access'
12
+ | 'installRuntime';
6
13
 
7
14
  function throwIfAborted(signal?: AbortSignal): void {
8
15
  if (signal?.aborted) {
@@ -0,0 +1,83 @@
1
+ import type { CredentialInstallDescriptor } from '../target/discovery.js';
2
+ import { readBoundedResponseJson } from '../target/bounded-response.js';
3
+ import { MAX_RESPONSE_BYTES } from './bounds.js';
4
+ import { TargetProtocolError } from './errors.js';
5
+ import { assertCapsuleResponseStatus } from './response-status.js';
6
+ import { withTargetRetry } from './retry-executor.js';
7
+ import type { Clock, RetryPolicy } from './types.js';
8
+
9
+ type RuntimeInstallOptions = {
10
+ readonly capsuleId: string;
11
+ readonly runtime: unknown;
12
+ readonly accessToken: string;
13
+ readonly descriptor: CredentialInstallDescriptor;
14
+ readonly signal?: AbortSignal;
15
+ readonly clock: Clock;
16
+ readonly retryPolicy: RetryPolicy;
17
+ readonly request: (
18
+ method: string,
19
+ path: string,
20
+ signal: AbortSignal | undefined,
21
+ body: string,
22
+ accessToken: string
23
+ ) => Promise<Response>;
24
+ };
25
+
26
+ function validOpaque(value: string, field: string): void {
27
+ if (value.length === 0 || value.length > 1024) {
28
+ throw new TargetProtocolError(`${field} is invalid`);
29
+ }
30
+ }
31
+
32
+ function runtimeBody(runtime: unknown, maximum: number): string {
33
+ let body: string | undefined;
34
+ try {
35
+ body = JSON.stringify(runtime);
36
+ } catch {
37
+ throw new TargetProtocolError('Runtime bundle is not serializable');
38
+ }
39
+ if (body === undefined || Buffer.byteLength(body) > maximum) {
40
+ throw new TargetProtocolError('Runtime bundle exceeds the advertised size bound');
41
+ }
42
+ return body;
43
+ }
44
+
45
+ export async function installRuntime(options: RuntimeInstallOptions): Promise<void> {
46
+ validOpaque(options.capsuleId, 'capsule id');
47
+ validOpaque(options.accessToken, 'capsule access token');
48
+ let path: string;
49
+ try {
50
+ path = options.descriptor.install.routeTemplate.expand({
51
+ capsule_id: options.capsuleId,
52
+ });
53
+ } catch {
54
+ throw new TargetProtocolError('Runtime install route expansion is unsafe');
55
+ }
56
+ const body = runtimeBody(options.runtime, options.descriptor.maxBodyBytes);
57
+ await withTargetRetry(
58
+ 'installRuntime',
59
+ async () => {
60
+ const response = await options.request(
61
+ options.descriptor.install.method,
62
+ path,
63
+ options.signal,
64
+ body,
65
+ options.accessToken
66
+ );
67
+ await assertCapsuleResponseStatus(
68
+ response,
69
+ 204,
70
+ (errorResponse) =>
71
+ readBoundedResponseJson(
72
+ errorResponse,
73
+ MAX_RESPONSE_BYTES,
74
+ () => new TargetProtocolError('Capsule error response is malformed')
75
+ ),
76
+ options.clock
77
+ );
78
+ await response.body?.cancel().catch(() => undefined);
79
+ },
80
+ options.signal,
81
+ { clock: options.clock, policy: options.retryPolicy }
82
+ );
83
+ }
@@ -1,10 +1,5 @@
1
1
  import { IDEMPOTENCY_KEY_PATTERN, MAX_RESPONSE_BYTES } from './bounds.js';
2
- import {
3
- TargetAdapterError,
4
- TargetAuthError,
5
- TargetProtocolError,
6
- TargetTransportError,
7
- } from './errors.js';
2
+ import { TargetProtocolError } from './errors.js';
8
3
  import { DefaultRetryPolicy } from './retry.js';
9
4
  import {
10
5
  assertCapsule,
@@ -20,34 +15,23 @@ import type {
20
15
  CapsuleLimits,
21
16
  CapsuleListPage,
22
17
  Clock,
23
- HttpTransport,
24
18
  ListRequest,
25
19
  RetryPolicy,
26
- TargetAccessTokenProvider,
27
20
  } from './types.js';
28
21
  import type { TargetDiscoveryDescriptor } from '../target/discovery.js';
29
22
  import { readBoundedResponseJson } from '../target/bounded-response.js';
30
- import { throwCapsuleServerError } from './capsule-error-response.js';
31
23
  import { validateAccessUrl } from './access-url.js';
32
24
  import { withTargetRetry } from './retry-executor.js';
25
+ import { installRuntime as installOpaqueRuntime } from './runtime-install.js';
26
+ import { assertCapsuleResponseStatus } from './response-status.js';
33
27
  import {
34
- requestUrl,
35
- type AdapterRequest,
28
+ createAdapterRequester,
29
+ type AdapterRequester,
36
30
  type ExecuteArguments,
37
31
  } from './adapter-request.js';
38
32
 
39
- const DEFAULT_TRANSPORT: HttpTransport = {
40
- fetch(url, init) {
41
- return globalThis.fetch(url, init);
42
- },
43
- };
44
33
  const DEFAULT_CLOCK: Clock = { now: () => Date.now() };
45
34
 
46
- function throwIfAborted(signal?: AbortSignal): void {
47
- if (signal?.aborted)
48
- throw signal.reason ?? new globalThis.DOMException('The operation was aborted', 'AbortError');
49
- }
50
-
51
35
  function validOpaque(value: string, field: string): void {
52
36
  if (value.length === 0 || value.length > 1024)
53
37
  throw new TargetProtocolError(`${field} is invalid`);
@@ -57,12 +41,10 @@ function jsonRequest(body: unknown): string {
57
41
  return JSON.stringify(body);
58
42
  }
59
43
 
60
-
61
44
  export class ZeroCloudV1TargetAdapter implements TargetAdapter {
62
45
  readonly #descriptor: TargetDiscoveryDescriptor;
63
46
  readonly #organizationId: string;
64
- readonly #tokenProvider: TargetAccessTokenProvider;
65
- readonly #transport: HttpTransport;
47
+ readonly #request: AdapterRequester;
66
48
  readonly #clock: Clock;
67
49
  readonly #retryPolicy: RetryPolicy;
68
50
 
@@ -70,8 +52,11 @@ export class ZeroCloudV1TargetAdapter implements TargetAdapter {
70
52
  this.#descriptor = options.descriptor;
71
53
  this.#organizationId = options.organization.id;
72
54
  validOpaque(this.#organizationId, 'organization id');
73
- this.#tokenProvider = options.tokenProvider;
74
- this.#transport = options.transport ?? DEFAULT_TRANSPORT;
55
+ this.#request = createAdapterRequester(
56
+ options.descriptor,
57
+ options.tokenProvider,
58
+ options.transport
59
+ );
75
60
  this.#clock = options.clock ?? DEFAULT_CLOCK;
76
61
  this.#retryPolicy = options.retryPolicy ?? new DefaultRetryPolicy();
77
62
  }
@@ -199,21 +184,42 @@ export class ZeroCloudV1TargetAdapter implements TargetAdapter {
199
184
  return result;
200
185
  }
201
186
 
187
+ async installRuntime(
188
+ capsuleId: string,
189
+ runtime: unknown,
190
+ accessToken: string,
191
+ signal?: AbortSignal
192
+ ): Promise<void> {
193
+ const descriptor = this.#descriptor.credentialInstall;
194
+ if (descriptor === null) {
195
+ throw new TargetProtocolError('Target does not advertise runtime installation');
196
+ }
197
+ await installOpaqueRuntime({
198
+ capsuleId,
199
+ runtime,
200
+ accessToken,
201
+ descriptor,
202
+ ...(signal === undefined ? {} : { signal }),
203
+ clock: this.#clock,
204
+ retryPolicy: this.#retryPolicy,
205
+ request: (method, path, requestSignal, body, token) =>
206
+ this.#request({
207
+ method,
208
+ path,
209
+ signal: requestSignal,
210
+ request: { body },
211
+ accessToken: token,
212
+ }),
213
+ });
214
+ }
215
+
202
216
  #execute<T>(...args: ExecuteArguments<T>): Promise<T> {
203
217
  return Promise.resolve().then(() => this.#executeExpanded(args));
204
218
  }
205
219
 
206
220
  #executeExpanded<T>(args: ExecuteArguments<T>): Promise<T> {
207
- const [
208
- operation,
209
- method,
210
- template,
211
- values,
212
- expectedStatus,
213
- validate,
214
- signal,
215
- request = {},
216
- ] = args;
221
+ const [operation, method, template, values, expectedStatus, validate, signal, request = {}] =
222
+ args;
217
223
  let path: string;
218
224
  try {
219
225
  path = template.expand(values);
@@ -223,77 +229,36 @@ export class ZeroCloudV1TargetAdapter implements TargetAdapter {
223
229
  return withTargetRetry(
224
230
  operation,
225
231
  async () => {
226
- const response = await this.#request(method, path, signal, request);
227
- if (response.status >= 300 && response.status < 400) {
228
- await response.body?.cancel().catch(() => undefined);
229
- throw new TargetProtocolError('Capsule redirects are forbidden');
230
- }
231
- if (response.status !== expectedStatus) {
232
- if (response.status >= 200 && response.status < 300) {
233
- await response.body?.cancel().catch(() => undefined);
234
- throw new TargetProtocolError('Target returned an unexpected success status');
235
- }
236
- await throwCapsuleServerError(
237
- response,
238
- (errorResponse) => this.#readJson(errorResponse),
239
- this.#clock,
240
- );
241
- }
232
+ const response = await this.#request({
233
+ method,
234
+ path,
235
+ signal,
236
+ request,
237
+ accessToken: undefined,
238
+ });
239
+ await assertCapsuleResponseStatus(
240
+ response,
241
+ expectedStatus,
242
+ (errorResponse) => this.#readJson(errorResponse),
243
+ this.#clock
244
+ );
242
245
  return validate(await this.#readJson(response));
243
246
  },
244
247
  signal,
245
- { clock: this.#clock, policy: this.#retryPolicy },
248
+ { clock: this.#clock, policy: this.#retryPolicy }
246
249
  );
247
250
  }
248
251
 
249
- async #request(
250
- method: string,
251
- path: string,
252
- signal: AbortSignal | undefined,
253
- request: AdapterRequest,
254
- ): Promise<Response> {
255
- throwIfAborted(signal);
256
- const url = requestUrl(path, this.#descriptor);
257
- let token: string;
258
- try {
259
- token = await this.#tokenProvider.getAccessToken(signal);
260
- } catch {
261
- throw new TargetAuthError('Target access authorization failed');
262
- }
263
- const init: RequestInit & { redirect: 'manual' } = {
264
- method,
265
- headers: {
266
- Accept: 'application/json',
267
- Authorization: `Bearer ${token}`,
268
- ...(request.body === undefined ? {} : { 'Content-Type': 'application/json' }),
269
- ...request.headers,
270
- },
271
- redirect: 'manual',
272
- };
273
- if (request.body !== undefined) init.body = request.body;
274
- if (signal !== undefined) init.signal = signal;
275
- try {
276
- const response = await this.#transport.fetch(url.href, init);
277
- if (response.url && new globalThis.URL(response.url).href !== url.href) {
278
- await response.body?.cancel().catch(() => undefined);
279
- throw new TargetProtocolError('Capsule response changed target route');
280
- }
281
- return response;
282
- } catch (error) {
283
- if (error instanceof TargetAdapterError) throw error;
284
- throwIfAborted(signal);
285
- throw new TargetTransportError('Capsule transport failed');
286
- }
287
- }
288
-
289
-
290
252
  #readJson(response: Response): Promise<unknown> {
291
- return readBoundedResponseJson(response, MAX_RESPONSE_BYTES, (kind) =>
292
- new TargetProtocolError(
293
- kind === 'size'
294
- ? 'Capsule response exceeds the size limit'
295
- : 'Capsule response is not valid UTF-8 JSON',
296
- ),
253
+ return readBoundedResponseJson(
254
+ response,
255
+ MAX_RESPONSE_BYTES,
256
+ (kind) =>
257
+ new TargetProtocolError(
258
+ kind === 'size'
259
+ ? 'Capsule response exceeds the size limit'
260
+ : 'Capsule response is not valid UTF-8 JSON'
261
+ )
297
262
  );
298
263
  }
299
264
  }
@@ -3,16 +3,8 @@ import { routeTemplate, type RouteTemplate } from './route-template.js';
3
3
 
4
4
  export interface CredentialInstallDescriptor {
5
5
  readonly kind: 'openengine.capsule-credential-install/v1';
6
- readonly grant: { readonly routeTemplate: RouteTemplate; readonly method: 'POST' };
7
6
  readonly install: { readonly routeTemplate: RouteTemplate; readonly method: 'PUT' };
8
- readonly uploadUrlOrigin: 'same_origin';
9
- readonly sealedEnvelopeAlgorithms: readonly ['RSA-OAEP-3072-SHA256'];
10
- readonly bounds: {
11
- readonly maxEnvelopeBytes: number;
12
- readonly maxBodyBytes: number;
13
- readonly grantTtlSeconds: number;
14
- readonly maxClockSkewSeconds: number;
15
- };
7
+ readonly maxBodyBytes: number;
16
8
  }
17
9
 
18
10
  export function record(value: unknown, field: string): Record<string, unknown> {
@@ -116,29 +108,35 @@ export function sameOriginUrl(
116
108
  export function parseCredentialInstall(value: unknown): CredentialInstallDescriptor | null {
117
109
  if (value === undefined || value === null) return null;
118
110
  const extension = closedRecord(value, 'extensions.credential_install', [
119
- 'kind', 'grant', 'install', 'upload_url_origin', 'sealed_envelope_algorithms', 'bounds',
111
+ 'kind',
112
+ 'install',
113
+ 'max_body_bytes',
120
114
  ]);
121
- exact(extension.kind, 'openengine.capsule-credential-install/v1', 'extensions.credential_install.kind');
122
- const grant = closedRecord(extension.grant, 'extensions.credential_install.grant', ['route_template', 'method']);
123
- const install = closedRecord(extension.install, 'extensions.credential_install.install', ['route_template', 'method']);
124
- exact(grant.method, 'POST', 'extensions.credential_install.grant.method');
125
- exact(install.method, 'PUT', 'extensions.credential_install.install.method');
126
- exact(extension.upload_url_origin, 'same_origin', 'extensions.credential_install.upload_url_origin');
127
- exactStringSet(extension.sealed_envelope_algorithms, 'extensions.credential_install.sealed_envelope_algorithms', ['RSA-OAEP-3072-SHA256']);
128
- const bounds = closedRecord(extension.bounds, 'extensions.credential_install.bounds', [
129
- 'max_envelope_bytes', 'max_body_bytes', 'grant_ttl_seconds', 'max_clock_skew_seconds',
115
+ exact(
116
+ extension.kind,
117
+ 'openengine.capsule-credential-install/v1',
118
+ 'extensions.credential_install.kind'
119
+ );
120
+ const install = closedRecord(extension.install, 'extensions.credential_install.install', [
121
+ 'route_template',
122
+ 'method',
130
123
  ]);
124
+ exact(install.method, 'PUT', 'extensions.credential_install.install.method');
131
125
  return Object.freeze({
132
126
  kind: 'openengine.capsule-credential-install/v1' as const,
133
- grant: Object.freeze({ routeTemplate: routeTemplate(grant.route_template, 'extensions.credential_install.grant.route_template', ['capsule_id']), method: 'POST' as const }),
134
- install: Object.freeze({ routeTemplate: routeTemplate(install.route_template, 'extensions.credential_install.install.route_template', ['capsule_id']), method: 'PUT' as const }),
135
- uploadUrlOrigin: 'same_origin' as const,
136
- sealedEnvelopeAlgorithms: Object.freeze(['RSA-OAEP-3072-SHA256'] as const),
137
- bounds: Object.freeze({
138
- maxEnvelopeBytes: integer(bounds.max_envelope_bytes, 'extensions.credential_install.bounds.max_envelope_bytes', 1, 1_048_576),
139
- maxBodyBytes: integer(bounds.max_body_bytes, 'extensions.credential_install.bounds.max_body_bytes', 1, 1_048_576),
140
- grantTtlSeconds: integer(bounds.grant_ttl_seconds, 'extensions.credential_install.bounds.grant_ttl_seconds', 1, 3_600),
141
- maxClockSkewSeconds: integer(bounds.max_clock_skew_seconds, 'extensions.credential_install.bounds.max_clock_skew_seconds', 0, 300),
127
+ install: Object.freeze({
128
+ routeTemplate: routeTemplate(
129
+ install.route_template,
130
+ 'extensions.credential_install.install.route_template',
131
+ ['capsule_id']
132
+ ),
133
+ method: 'PUT' as const,
142
134
  }),
135
+ maxBodyBytes: integer(
136
+ extension.max_body_bytes,
137
+ 'extensions.credential_install.max_body_bytes',
138
+ 1,
139
+ 4 * 1024 * 1024
140
+ ),
143
141
  });
144
142
  }
@@ -43,6 +43,8 @@ const {
43
43
  partitionPathFor,
44
44
  createOmpSessionPartitionDirectory,
45
45
  } = require('../src/omp-session-partition');
46
+ const TASK_EXECUTION_CONTEXT_ENV = 'ZEROSHOT_TASK_EXECUTION_CONTEXT';
47
+ const TASK_EXECUTION_CONTEXTS = new Set(['host', 'detached', 'docker', 'benchmark']);
46
48
  export {
47
49
  isOwnedProcessTreeRunning,
48
50
  isProcessRunning,
@@ -413,7 +415,7 @@ function buildProviderOptions(options, runtime, modelSelection) {
413
415
  outputFormat: runtime.outputFormat,
414
416
  jsonSchema: runtime.jsonSchema,
415
417
  cwd: runtime.cwd,
416
- executionContext: 'detached',
418
+ executionContext: resolveTaskExecutionContext(),
417
419
  autoApprove: !structuredOutputRecovery,
418
420
  ...(modelSelection === undefined ? {} : { modelSpec: modelSelection.modelSpec }),
419
421
  ...(structuredOutputRecovery ? {} : mcpConfigOption(options)),
@@ -428,6 +430,17 @@ function buildProviderOptions(options, runtime, modelSelection) {
428
430
  };
429
431
  }
430
432
 
433
+ export function resolveTaskExecutionContext(environment = process.env) {
434
+ const context = environment[TASK_EXECUTION_CONTEXT_ENV];
435
+ if (context === undefined) return 'detached';
436
+ if (!TASK_EXECUTION_CONTEXTS.has(context)) {
437
+ throw new Error(
438
+ `${TASK_EXECUTION_CONTEXT_ENV} must be one of: ${[...TASK_EXECUTION_CONTEXTS].join(', ')}.`
439
+ );
440
+ }
441
+ return context;
442
+ }
443
+
431
444
  function claudeSettingsFileOption() {
432
445
  const settingsPath = process.env[CLAUDE_SETTINGS_ENV]?.trim();
433
446
  return settingsPath ? { claudeSettingsFile: settingsPath } : {};
@@ -1,4 +1,5 @@
1
1
  import { spawn } from 'child_process';
2
+ import { StringDecoder } from 'string_decoder';
2
3
  import {
3
4
  detectProviderFatalError,
4
5
  detectProviderStreamingModeError,
@@ -9,6 +10,8 @@ import { terminateProcess } from './process-termination.js';
9
10
 
10
11
  export const COMMAND_CLEANUP_UNINITIALIZED = Symbol('command-cleanup-uninitialized');
11
12
 
13
+ const MAX_CODEX_CONTROL_RECORD_BYTES = 64 * 1024;
14
+
12
15
  export function spawnWatcherProvider(command, finalArgs, options) {
13
16
  return spawn(command, finalArgs, {
14
17
  ...options,
@@ -38,6 +41,72 @@ function splitBufferLines(buffer, chunk) {
38
41
  return { lines: lines.slice(0, -1), remaining: lines.at(-1) || '' };
39
42
  }
40
43
 
44
+ function createCodexOutputPassthrough({ log, captureProviderSession }) {
45
+ const decoder = new StringDecoder('utf8');
46
+ let atLineStart = true;
47
+ let inspectable = true;
48
+ let inspectionBytes = 0;
49
+ let inspectionParts = [];
50
+
51
+ function inspectPart(part) {
52
+ if (!inspectable || !part) return;
53
+ inspectionBytes += Buffer.byteLength(part);
54
+ if (inspectionBytes > MAX_CODEX_CONTROL_RECORD_BYTES) {
55
+ inspectable = false;
56
+ inspectionParts = [];
57
+ return;
58
+ }
59
+ inspectionParts.push(part);
60
+ }
61
+
62
+ function finishLine() {
63
+ if (inspectable) captureProviderSession(inspectionParts.join(''));
64
+ atLineStart = true;
65
+ inspectable = true;
66
+ inspectionBytes = 0;
67
+ inspectionParts = [];
68
+ }
69
+
70
+ function writeText(text, timestamp) {
71
+ if (!text) return;
72
+ const logged = [];
73
+ let offset = 0;
74
+ while (offset < text.length) {
75
+ if (atLineStart) {
76
+ logged.push(`[${timestamp}]`);
77
+ atLineStart = false;
78
+ }
79
+ const newline = text.indexOf('\n', offset);
80
+ if (newline === -1) {
81
+ const part = text.slice(offset);
82
+ inspectPart(part);
83
+ logged.push(part);
84
+ break;
85
+ }
86
+ const part = text.slice(offset, newline);
87
+ inspectPart(part);
88
+ logged.push(part, '\n');
89
+ finishLine();
90
+ offset = newline + 1;
91
+ }
92
+ log(logged.join(''));
93
+ }
94
+
95
+ return {
96
+ consume(chunk) {
97
+ const text = typeof chunk === 'string' ? chunk : decoder.write(chunk);
98
+ writeText(text, Date.now());
99
+ },
100
+ flush() {
101
+ writeText(decoder.end(), Date.now());
102
+ if (!atLineStart) {
103
+ finishLine();
104
+ log('\n');
105
+ }
106
+ },
107
+ };
108
+ }
109
+
41
110
  export function resolveWatcherCommand(config, commandSpec, fallbackArgs, normalizeProviderName) {
42
111
  return {
43
112
  providerName: normalizeProviderName(config.provider || 'claude'),
@@ -189,6 +258,8 @@ export function createWatcherOutputRuntime({
189
258
  let streamingModeError = null;
190
259
  let fatalError = null;
191
260
  const captureProviderSession = providerSessionCapture?.captureLine || (() => {});
261
+ const codexOutputPassthrough =
262
+ providerName === 'codex' ? createCodexOutputPassthrough({ log, captureProviderSession }) : null;
192
263
 
193
264
  function maybeHandleFatalError(line, timestamp) {
194
265
  if (fatalError) return false;
@@ -230,6 +301,10 @@ export function createWatcherOutputRuntime({
230
301
  }
231
302
 
232
303
  function consumeOutput(buffer, chunk) {
304
+ if (codexOutputPassthrough) {
305
+ codexOutputPassthrough.consume(chunk);
306
+ return '';
307
+ }
233
308
  const timestamp = Date.now();
234
309
  const { lines, remaining } = splitBufferLines(buffer, chunk.toString());
235
310
  for (const line of lines) handleOutputLine(line, timestamp);
@@ -284,7 +359,11 @@ export function createWatcherOutputRuntime({
284
359
 
285
360
  function complete({ code, signal, outputBuffer, stderrBuffer = null }) {
286
361
  const timestamp = Date.now();
287
- flushOutput(outputBuffer, timestamp);
362
+ if (codexOutputPassthrough) {
363
+ codexOutputPassthrough.flush();
364
+ } else {
365
+ flushOutput(outputBuffer, timestamp);
366
+ }
288
367
  if (stderrBuffer !== null) flushStderr(stderrBuffer, timestamp);
289
368
  const recovered = attemptRecovery(code, timestamp);
290
369
  const sessionIdentityError = providerSessionCapture?.getCompletionError() || null;