@the-open-engine/zeroshot 6.10.2 → 6.12.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 (76) hide show
  1. package/cli/commands/providers.js +13 -13
  2. package/cli/index.js +77 -35
  3. package/cli/lib/first-run.js +12 -11
  4. package/cli/lib/update-checker.js +517 -132
  5. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -1
  6. package/lib/agent-cli-provider/adapters/opencode.js +3 -0
  7. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -1
  8. package/lib/agent-cli-provider/types.d.ts +1 -0
  9. package/lib/agent-cli-provider/types.d.ts.map +1 -1
  10. package/lib/agent-cli-provider/types.js.map +1 -1
  11. package/lib/cluster/client.cjs +146 -0
  12. package/lib/cluster/client.d.ts +44 -0
  13. package/lib/cluster/client.mjs +141 -0
  14. package/lib/cluster/connection.cjs +382 -0
  15. package/lib/cluster/connection.d.ts +41 -0
  16. package/lib/cluster/connection.mjs +378 -0
  17. package/lib/cluster/errors.cjs +44 -0
  18. package/lib/cluster/errors.d.ts +23 -0
  19. package/lib/cluster/errors.mjs +32 -0
  20. package/lib/cluster/frames.cjs +49 -0
  21. package/lib/cluster/frames.d.ts +12 -0
  22. package/lib/cluster/frames.mjs +45 -0
  23. package/lib/cluster/generated/protocol-schema.cjs +5 -0
  24. package/lib/cluster/generated/protocol-schema.d.ts +1 -0
  25. package/lib/cluster/generated/protocol-schema.mjs +2 -0
  26. package/lib/cluster/generated/protocol.cjs +22 -0
  27. package/lib/cluster/generated/protocol.d.ts +781 -0
  28. package/lib/cluster/generated/protocol.mjs +19 -0
  29. package/lib/cluster/index.cjs +40 -0
  30. package/lib/cluster/index.d.ts +10 -0
  31. package/lib/cluster/index.mjs +6 -0
  32. package/lib/cluster/queue.cjs +71 -0
  33. package/lib/cluster/queue.d.ts +15 -0
  34. package/lib/cluster/queue.mjs +67 -0
  35. package/lib/cluster/socket.cjs +15 -0
  36. package/lib/cluster/socket.d.ts +11 -0
  37. package/lib/cluster/socket.mjs +12 -0
  38. package/lib/cluster/subscriptions.cjs +270 -0
  39. package/lib/cluster/subscriptions.d.ts +69 -0
  40. package/lib/cluster/subscriptions.mjs +264 -0
  41. package/lib/cluster/validators.cjs +46 -0
  42. package/lib/cluster/validators.d.ts +3 -0
  43. package/lib/cluster/validators.mjs +42 -0
  44. package/lib/settings.js +195 -23
  45. package/lib/setup-apply.js +45 -32
  46. package/lib/setup-undo.js +25 -16
  47. package/package.json +25 -3
  48. package/scripts/build-cluster.js +43 -0
  49. package/scripts/generate-cluster-types.js +203 -0
  50. package/scripts/release-dry-run.js +6 -6
  51. package/scripts/rust-distribution.js +933 -0
  52. package/src/agent/agent-hook-executor.js +14 -6
  53. package/src/agent/agent-lifecycle.js +25 -8
  54. package/src/agent/agent-task-executor.js +711 -139
  55. package/src/agent/output-reformatter.js +54 -41
  56. package/src/agent/pr-verification.js +11 -3
  57. package/src/agent/task-execution-handle.js +373 -0
  58. package/src/agent-cli-provider/adapters/opencode.ts +3 -0
  59. package/src/agent-cli-provider/types.ts +1 -0
  60. package/src/agent-wrapper.js +5 -2
  61. package/src/cluster/client.ts +199 -0
  62. package/src/cluster/connection.ts +300 -0
  63. package/src/cluster/errors.ts +32 -0
  64. package/src/cluster/frames.ts +44 -0
  65. package/src/cluster/generated/protocol-schema.ts +2 -0
  66. package/src/cluster/generated/protocol.ts +183 -0
  67. package/src/cluster/index.ts +38 -0
  68. package/src/cluster/queue.ts +84 -0
  69. package/src/cluster/socket.ts +31 -0
  70. package/src/cluster/subscriptions.ts +256 -0
  71. package/src/cluster/validators.ts +56 -0
  72. package/src/cluster/ws.d.ts +7 -0
  73. package/src/isolation-manager.js +16 -0
  74. package/src/issue-providers/jira-provider.js +1 -1
  75. package/src/issue-providers/linear-provider.js +4 -4
  76. package/task-lib/runner.js +3 -0
@@ -0,0 +1,256 @@
1
+ import type { AgentAttachEvent, LogRecord, SubscriptionCloseReason, WatchEvent, WatchParams, WatchResult } from './generated/protocol.js';
2
+ import { ClusterProtocolError, ClusterStateError } from './errors.js';
3
+ import { Connection } from './connection.js';
4
+ import type { SubscriptionRegistration } from './connection.js';
5
+ import { isRecord } from './frames.js';
6
+ import type { FrameRecord } from './frames.js';
7
+ import { assertDefinition } from './validators.js';
8
+
9
+ export type SubscriptionClosedItem = {
10
+ readonly type: 'closed';
11
+ readonly reason: SubscriptionCloseReason;
12
+ };
13
+ export type WatchSubscriptionClosedItem = SubscriptionClosedItem & {
14
+ readonly lastDeliveredCursor?: string | null;
15
+ };
16
+ export type SubscriptionItem<T> = { readonly type: 'event'; readonly event: T } | SubscriptionClosedItem;
17
+ export type WatchSubscriptionItem =
18
+ | { readonly type: 'event'; readonly runId: string; readonly cursor: string; readonly event: WatchEvent }
19
+ | WatchSubscriptionClosedItem;
20
+ export interface Subscription<T> extends AsyncIterator<SubscriptionItem<T>>, AsyncIterable<SubscriptionItem<T>> {
21
+ readonly subscriptionId: string;
22
+ readonly retainedCount: number;
23
+ cancel(): Promise<void>;
24
+ }
25
+
26
+ type FrameParser<T> = (frame: FrameRecord) => SubscriptionItem<T>;
27
+ class SubscriptionStream<T> implements Subscription<T> {
28
+ #done = false;
29
+ constructor(
30
+ protected readonly connection: Connection,
31
+ protected readonly registration: SubscriptionRegistration,
32
+ private readonly parse: FrameParser<T>,
33
+ ) {}
34
+ get subscriptionId(): string { return this.registration.id; }
35
+ get retainedCount(): number { return this.registration.queue.retainedCount; }
36
+ [Symbol.asyncIterator](): this { return this; }
37
+ async next(): Promise<IteratorResult<SubscriptionItem<T>>> {
38
+ if (this.#done) return { done: true, value: undefined };
39
+ const queued = await this.registration.queue.recv();
40
+ if (queued.done) {
41
+ if (this.registration.overflowed) {
42
+ this.registration.overflowed = false; this.#done = true;
43
+ return { done: false, value: { type: 'closed', reason: 'SLOW_CONSUMER' } };
44
+ }
45
+ this.#done = true; return { done: true, value: undefined };
46
+ }
47
+ try {
48
+ const value = this.parse(queued.value);
49
+ if (value.type === 'closed') this.#finishWithoutCancel();
50
+ return { done: false, value };
51
+ } catch (cause) { await this.#terminate(true); throw cause; }
52
+ }
53
+ async return(): Promise<IteratorResult<SubscriptionItem<T>>> {
54
+ await this.#terminate(true); return { done: true, value: undefined };
55
+ }
56
+ async throw(error?: unknown): Promise<IteratorResult<SubscriptionItem<T>>> {
57
+ await this.#terminate(true); throw error;
58
+ }
59
+ cancel(): Promise<void> { return this.#terminate(true); }
60
+ async #terminate(sendCancel: boolean): Promise<void> {
61
+ if (this.#done) return;
62
+ this.#done = true;
63
+ this.connection.unregisterSubscription(this.registration.id, this.registration);
64
+ this.registration.queue.closeAndDiscard();
65
+ if (sendCancel) {
66
+ try { await this.connection.cancelSubscription(this.registration); }
67
+ catch (error) { this.connection.recordDiagnostic(error); }
68
+ }
69
+ }
70
+ #finishWithoutCancel(): void {
71
+ if (this.#done) return;
72
+ this.#done = true;
73
+ this.connection.unregisterSubscription(this.registration.id, this.registration);
74
+ this.registration.queue.closeAndDiscard();
75
+ }
76
+ }
77
+
78
+ function parseCursorlessClosed(params: FrameRecord, definition: string): SubscriptionClosedItem {
79
+ assertDefinition(definition, params);
80
+ return { type: 'closed', reason: params.reason as SubscriptionCloseReason };
81
+ }
82
+ function parseWatchClosed(params: FrameRecord): WatchSubscriptionClosedItem {
83
+ assertDefinition('SubscriptionClosedNotification', params);
84
+ const cursor = params.lastDeliveredCursor as string | null | undefined;
85
+ return {
86
+ type: 'closed',
87
+ reason: params.reason as SubscriptionCloseReason,
88
+ ...(cursor === undefined ? {} : { lastDeliveredCursor: cursor }),
89
+ };
90
+ }
91
+ function subscriptionParser<T>(
92
+ field: string,
93
+ eventDefinition: string,
94
+ closedDefinition: string,
95
+ ): FrameParser<T> {
96
+ return (frame) => {
97
+ if (!isRecord(frame.params)) {
98
+ throw new ClusterProtocolError('notification params are missing', 'INVALID_SUBSCRIPTION_EVENT');
99
+ }
100
+ if (frame.method === 'subscription/closed') {
101
+ return parseCursorlessClosed(frame.params, closedDefinition);
102
+ }
103
+ if (frame.method !== 'event') {
104
+ throw new ClusterProtocolError('malformed subscription event', 'INVALID_SUBSCRIPTION_EVENT');
105
+ }
106
+ assertDefinition(eventDefinition, frame.params);
107
+ return { type: 'event', event: frame.params[field] as T };
108
+ };
109
+ }
110
+
111
+ export class LogsSubscriptionStream extends SubscriptionStream<LogRecord> {
112
+ constructor(connection: Connection, registration: SubscriptionRegistration) {
113
+ super(connection, registration, subscriptionParser('record', 'LogEventNotification', 'LogsClosedNotification'));
114
+ }
115
+ }
116
+ export class AgentAttachSubscriptionStream extends SubscriptionStream<AgentAttachEvent> {
117
+ constructor(connection: Connection, registration: SubscriptionRegistration) {
118
+ super(connection, registration, subscriptionParser('event', 'AgentAttachEventNotification', 'AgentAttachClosedNotification'));
119
+ }
120
+ }
121
+
122
+ type WatchStreamInit = {
123
+ readonly connection: Connection;
124
+ readonly registration: SubscriptionRegistration;
125
+ readonly result: WatchResult;
126
+ readonly params: WatchParams;
127
+ readonly lastSeenRunId?: string;
128
+ readonly lastSeenCursor?: string;
129
+ };
130
+ export class WatchSubscriptionStream implements AsyncIterator<WatchSubscriptionItem>, AsyncIterable<WatchSubscriptionItem> {
131
+ readonly #connection: Connection;
132
+ readonly #registration: SubscriptionRegistration;
133
+ #lastSeenRunId: string | undefined;
134
+ #lastSeenCursor: string | undefined;
135
+ #lastDelivered: string | null | undefined;
136
+ #runId: string | null | undefined;
137
+ #done = false;
138
+ #reconnectConsumed = false;
139
+ #readBarrier: Promise<void> = Promise.resolve();
140
+ constructor(init: WatchStreamInit) {
141
+ this.#connection = init.connection;
142
+ this.#registration = init.registration;
143
+ this.#lastSeenRunId = init.lastSeenRunId;
144
+ this.#lastSeenCursor = init.lastSeenCursor;
145
+ this.#lastDelivered = init.params.fromCursor;
146
+ this.#runId = init.result.runId ?? init.params.runId;
147
+ }
148
+ get subscriptionId(): string { return this.#registration.id; }
149
+ get retainedCount(): number { return this.#registration.queue.retainedCount; }
150
+ get lastDeliveredCursor(): string | null | undefined { return this.#lastDelivered; }
151
+ [Symbol.asyncIterator](): this { return this; }
152
+ next(): Promise<IteratorResult<WatchSubscriptionItem>> {
153
+ const read = this.#readBarrier.then(() => this.#nextLogical());
154
+ this.#readBarrier = read.then(() => undefined, () => undefined);
155
+ return read;
156
+ }
157
+ async #nextLogical(): Promise<IteratorResult<WatchSubscriptionItem>> {
158
+ while (!this.#done) {
159
+ const queued = await this.#registration.queue.recv();
160
+ if (queued.done) return this.#finishQueue();
161
+ const item = await this.#decode(queued.value);
162
+ if (item) return { done: false, value: item };
163
+ }
164
+ return { done: true, value: undefined };
165
+ }
166
+ #finishQueue(): IteratorResult<WatchSubscriptionItem> {
167
+ this.#done = true;
168
+ if (!this.#registration.overflowed) return { done: true, value: undefined };
169
+ this.#registration.overflowed = false;
170
+ const cursor = this.#lastDelivered;
171
+ return {
172
+ done: false,
173
+ value: { type: 'closed', reason: 'SLOW_CONSUMER', ...(cursor === undefined ? {} : { lastDeliveredCursor: cursor }) },
174
+ };
175
+ }
176
+ async #decode(frame: FrameRecord): Promise<WatchSubscriptionItem | undefined> {
177
+ if (!isRecord(frame.params)) return this.#invalid('notification params are missing');
178
+ if (frame.method === 'subscription/closed') {
179
+ const closed = parseWatchClosed(frame.params);
180
+ if (closed.lastDeliveredCursor !== undefined) this.#lastDelivered = closed.lastDeliveredCursor;
181
+ this.#finishWithoutCancel(); return closed;
182
+ }
183
+ if (frame.method !== 'event') return this.#invalid('malformed watch event');
184
+ try { assertDefinition('EventNotification', frame.params); }
185
+ catch { return this.#invalid('malformed watch event'); }
186
+ const { runId, cursor, event } = frame.params as {
187
+ readonly runId: string; readonly cursor: string; readonly event: WatchEvent;
188
+ };
189
+ if (this.#lastSeenRunId === runId && this.#lastSeenCursor === cursor) return undefined;
190
+ this.#lastSeenRunId = runId; this.#lastSeenCursor = cursor;
191
+ this.#runId = runId; this.#lastDelivered = cursor;
192
+ return { type: 'event', runId, cursor, event };
193
+ }
194
+ async return(): Promise<IteratorResult<WatchSubscriptionItem>> {
195
+ await this.#terminate(true); return { done: true, value: undefined };
196
+ }
197
+ async throw(error?: unknown): Promise<IteratorResult<WatchSubscriptionItem>> {
198
+ await this.#terminate(true); throw error;
199
+ }
200
+ cancel(): Promise<void> { return this.#terminate(true); }
201
+ reconnect(freshConnection: Connection): Promise<import('./client.js').WatchSubscription> {
202
+ if (this.#reconnectConsumed) {
203
+ throw new ClusterStateError('watch stream reconnect is one-shot', 'RECONNECT_CONSUMED');
204
+ }
205
+ this.#reconnectConsumed = true; return this.#reconnectOn(freshConnection);
206
+ }
207
+ async #reconnectOn(freshConnection: Connection): Promise<import('./client.js').WatchSubscription> {
208
+ await this.#terminate(true);
209
+ try {
210
+ const runId = this.#runId; const fromCursor = this.#lastDelivered;
211
+ const params: WatchParams = {
212
+ ...(runId === undefined ? {} : { runId }),
213
+ ...(fromCursor === undefined ? {} : { fromCursor }),
214
+ };
215
+ const established = await freshConnection.openSubscription('watch', params);
216
+ return {
217
+ result: established.result,
218
+ stream: new WatchSubscriptionStream({
219
+ connection: freshConnection,
220
+ registration: established.registration,
221
+ result: established.result,
222
+ params,
223
+ ...(this.#lastSeenRunId === undefined ? {} : { lastSeenRunId: this.#lastSeenRunId }),
224
+ ...(this.#lastSeenCursor === undefined ? {} : { lastSeenCursor: this.#lastSeenCursor }),
225
+ }),
226
+ };
227
+ } catch (error) { await freshConnection.close(); throw error; }
228
+ }
229
+ async #terminate(sendCancel: boolean): Promise<void> {
230
+ if (this.#done) return;
231
+ this.#done = true;
232
+ this.#connection.unregisterSubscription(this.#registration.id, this.#registration);
233
+ this.#registration.queue.closeAndDiscard();
234
+ if (sendCancel) {
235
+ try { await this.#connection.cancelSubscription(this.#registration); }
236
+ catch (error) { this.#connection.recordDiagnostic(error); }
237
+ }
238
+ }
239
+ #finishWithoutCancel(): void {
240
+ if (this.#done) return;
241
+ this.#done = true;
242
+ this.#connection.unregisterSubscription(this.#registration.id, this.#registration);
243
+ this.#registration.queue.closeAndDiscard();
244
+ }
245
+ async #invalid(message: string): Promise<never> {
246
+ await this.#terminate(true);
247
+ throw new ClusterProtocolError(message, 'INVALID_SUBSCRIPTION_EVENT');
248
+ }
249
+ }
250
+
251
+ Object.defineProperty(SubscriptionStream.prototype, Symbol.asyncDispose, {
252
+ configurable: true, value: SubscriptionStream.prototype.cancel,
253
+ });
254
+ Object.defineProperty(WatchSubscriptionStream.prototype, Symbol.asyncDispose, {
255
+ configurable: true, value: WatchSubscriptionStream.prototype.cancel,
256
+ });
@@ -0,0 +1,56 @@
1
+ import Ajv2020 from 'ajv/dist/2020.js';
2
+ import type { ValidateFunction } from 'ajv';
3
+ import {
4
+ METHOD_RESULT_DEFINITIONS,
5
+ PROTOCOL_VERSION,
6
+ } from './generated/protocol.js';
7
+ import type { ClusterMethod } from './generated/protocol.js';
8
+ import { CLUSTER_PROTOCOL_SCHEMA } from './generated/protocol-schema.js';
9
+ import { ClusterProtocolError } from './errors.js';
10
+
11
+ const ajv = new Ajv2020({
12
+ allErrors: true,
13
+ strict: false,
14
+ validateFormats: false,
15
+ });
16
+ ajv.addSchema(CLUSTER_PROTOCOL_SCHEMA as object, 'openengine-cluster-v1');
17
+ const validators = new Map<string, ValidateFunction>();
18
+
19
+ function validatorFor(definition: string): ValidateFunction {
20
+ const cached = validators.get(definition);
21
+ if (cached) return cached;
22
+ const validator = ajv.compile({
23
+ $ref: `openengine-cluster-v1#/$defs/${definition}`,
24
+ });
25
+ validators.set(definition, validator);
26
+ return validator;
27
+ }
28
+
29
+ export function assertDefinition(definition: string, value: unknown): void {
30
+ const validate = validatorFor(definition);
31
+ if (validate(value)) return;
32
+ const details = (validate.errors ?? []).map((error) =>
33
+ `${error.instancePath || '/'} ${error.message ?? 'is invalid'}`
34
+ ).join('; ');
35
+ throw new ClusterProtocolError(
36
+ `${definition} validation failed: ${details}`,
37
+ 'INVALID_RESPONSE',
38
+ );
39
+ }
40
+
41
+ export function assertMethodResult(method: ClusterMethod, value: unknown): void {
42
+ try {
43
+ assertDefinition(METHOD_RESULT_DEFINITIONS[method], value);
44
+ } catch (error) {
45
+ const receivedVersion = method === 'initialize' && value !== null && typeof value === 'object'
46
+ ? (value as Readonly<Record<string, unknown>>).protocolVersion
47
+ : undefined;
48
+ if (typeof receivedVersion === 'string' && receivedVersion !== PROTOCOL_VERSION) {
49
+ throw new ClusterProtocolError(
50
+ `unsupported protocol version ${receivedVersion}`,
51
+ 'UNSUPPORTED_PROTOCOL_VERSION',
52
+ );
53
+ }
54
+ throw error;
55
+ }
56
+ }
@@ -0,0 +1,7 @@
1
+ declare module 'ws' {
2
+ const WebSocket: new (
3
+ url: string,
4
+ protocols?: string | readonly string[],
5
+ ) => import('./index.js').WebSocketLike;
6
+ export default WebSocket;
7
+ }
@@ -699,6 +699,22 @@ class IsolationManager {
699
699
  });
700
700
  }
701
701
 
702
+ async getContainerEnvironmentValue(clusterId, name) {
703
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
704
+ throw new Error(`Invalid container environment variable name: ${name}`);
705
+ }
706
+ const result = await this.execInContainer(clusterId, ['printenv', name]);
707
+ if (result.code === 1) return null;
708
+ if (result.code !== 0) {
709
+ throw new Error(
710
+ `Failed to read container environment variable ${name}: ${
711
+ result.stderr || `exit ${result.code}`
712
+ }`
713
+ );
714
+ }
715
+ return result.stdout.replace(/\r?\n$/, '');
716
+ }
717
+
702
718
  /**
703
719
  * Spawn a PTY-like process inside the container
704
720
  * Returns a child process that can be used like a PTY
@@ -174,7 +174,7 @@ class JiraProvider extends IssueProvider {
174
174
  const issueKey = this._extractIssueKey(identifier, settings);
175
175
 
176
176
  // Fetch issue using jira CLI
177
- const cmd = `jira issue view ${issueKey} --template json`;
177
+ const cmd = `jira view ${issueKey} --template json`;
178
178
  const output = execSync(cmd, { encoding: 'utf8' });
179
179
  const issue = JSON.parse(output);
180
180
 
@@ -239,10 +239,10 @@ class LinearProvider extends IssueProvider {
239
239
  }
240
240
 
241
241
  const key = teams[0].key;
242
- const { loadSettings, saveSettings } = require('../../lib/settings');
243
- const current = loadSettings();
244
- current.linearTeam = key;
245
- saveSettings(current);
242
+ const { mutateSettings } = require('../../lib/settings');
243
+ mutateSettings((current) => {
244
+ current.linearTeam = key;
245
+ });
246
246
  return key;
247
247
  }
248
248
 
@@ -125,6 +125,9 @@ function buildProviderOptions(options, runtime, modelSelection) {
125
125
  ...mcpConfigOption(options),
126
126
  ...claudeSettingsFileOption(),
127
127
  ...(options.resume ? { resumeSessionId: options.resume } : {}),
128
+ ...(process.env.ZEROSHOT_OPENCODE_AGENT?.trim()
129
+ ? { agentName: process.env.ZEROSHOT_OPENCODE_AGENT.trim() }
130
+ : {}),
128
131
  ...(options.continue ? { continueSession: true } : {}),
129
132
  };
130
133
  }