@treeseed/sdk 0.10.6 → 0.10.8

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 (50) hide show
  1. package/dist/api/auth/d1-provider.d.ts +5 -0
  2. package/dist/api/auth/d1-provider.js +3 -0
  3. package/dist/api/auth/d1-store.d.ts +5 -0
  4. package/dist/api/auth/d1-store.js +62 -0
  5. package/dist/api/auth/memory-provider.d.ts +5 -0
  6. package/dist/api/auth/memory-provider.js +41 -0
  7. package/dist/api/types.d.ts +5 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +58 -0
  10. package/dist/market-client.d.ts +119 -0
  11. package/dist/market-client.js +79 -0
  12. package/dist/operations/repository-operations.d.ts +129 -0
  13. package/dist/operations/repository-operations.js +634 -0
  14. package/dist/operations/services/config-runtime.d.ts +7 -6
  15. package/dist/operations/services/config-runtime.js +45 -25
  16. package/dist/operations/services/deploy.d.ts +42 -0
  17. package/dist/operations/services/deploy.js +1 -1
  18. package/dist/operations/services/project-platform.d.ts +41 -1
  19. package/dist/operations/services/project-platform.js +14 -1
  20. package/dist/operations/services/railway-api.d.ts +35 -1
  21. package/dist/operations/services/railway-api.js +240 -35
  22. package/dist/operations/services/railway-deploy.d.ts +16 -234
  23. package/dist/operations/services/railway-deploy.js +177 -62
  24. package/dist/operations/services/release-candidate.js +1 -2
  25. package/dist/operations/services/runtime-tools.d.ts +14 -0
  26. package/dist/operations/services/runtime-tools.js +15 -1
  27. package/dist/operations/services/workspace-save.d.ts +24 -0
  28. package/dist/operations/services/workspace-save.js +143 -3
  29. package/dist/operations/services/workspace-tools.js +1 -1
  30. package/dist/platform/env.yaml +163 -2
  31. package/dist/platform/environment.d.ts +1 -0
  32. package/dist/platform/environment.js +9 -0
  33. package/dist/platform-operation-store.d.ts +90 -0
  34. package/dist/platform-operation-store.js +505 -0
  35. package/dist/platform-operations.d.ts +265 -0
  36. package/dist/platform-operations.js +421 -0
  37. package/dist/reconcile/bootstrap-systems.js +3 -3
  38. package/dist/reconcile/builtin-adapters.js +225 -29
  39. package/dist/reconcile/contracts.d.ts +1 -1
  40. package/dist/reconcile/desired-state.d.ts +14 -0
  41. package/dist/reconcile/desired-state.js +4 -0
  42. package/dist/reconcile/engine.d.ts +28 -0
  43. package/dist/reconcile/state.js +3 -0
  44. package/dist/reconcile/units.js +2 -0
  45. package/dist/workflow/operations.d.ts +13 -5
  46. package/dist/workflow/operations.js +69 -12
  47. package/dist/workflow-state.d.ts +2 -0
  48. package/dist/workflow-state.js +7 -2
  49. package/dist/workflow.d.ts +2 -0
  50. package/package.json +15 -2
@@ -0,0 +1,265 @@
1
+ export declare const PLATFORM_OPERATION_ENDPOINTS: {
2
+ readonly operations: "/v1/platform/operations";
3
+ readonly operation: (operationId: string) => string;
4
+ readonly operationEvents: (operationId: string) => string;
5
+ readonly cancelOperation: (operationId: string) => string;
6
+ readonly retryOperation: (operationId: string) => string;
7
+ readonly registerRunner: "/v1/platform/runners/register";
8
+ readonly heartbeatRunner: "/v1/platform/runners/heartbeat";
9
+ readonly claimJob: "/v1/platform/runners/jobs/claim";
10
+ readonly runnerJob: (operationId: string) => string;
11
+ readonly jobEvents: (operationId: string) => string;
12
+ readonly renewLeaseJob: (operationId: string) => string;
13
+ readonly checkpointJob: (operationId: string) => string;
14
+ readonly completeJob: (operationId: string) => string;
15
+ readonly failJob: (operationId: string) => string;
16
+ };
17
+ export declare const PLATFORM_OPERATION_SCOPES: readonly ["platform:runners:register", "platform:runners:claim", "platform:runners:update", "platform:operations:create", "platform:operations:read", "platform:operations:cancel", "platform:operations:retry", "platform:repository:write", "platform:deploy:write", "platform:database:migrate"];
18
+ export declare const PLATFORM_OPERATION_NAMESPACES: readonly ["market", "repository", "deploy", "database", "seed", "infrastructure", "catalog"];
19
+ export declare const PLATFORM_OPERATION_STATUSES: readonly ["queued", "leased", "running", "waiting_for_approval", "succeeded", "failed", "cancelled"];
20
+ export declare const PLATFORM_OPERATION_TARGETS: readonly ["market_operations_runner", "github_actions", "cli", "railway_job"];
21
+ export type PlatformOperationScope = (typeof PLATFORM_OPERATION_SCOPES)[number];
22
+ export type PlatformOperationNamespace = (typeof PLATFORM_OPERATION_NAMESPACES)[number] | string;
23
+ export type PlatformOperationStatus = (typeof PLATFORM_OPERATION_STATUSES)[number] | string;
24
+ export type PlatformOperationTarget = (typeof PLATFORM_OPERATION_TARGETS)[number] | string;
25
+ export interface PlatformOperationRequestedBy {
26
+ type: 'user' | 'service' | 'team_api_key' | 'platform_runner' | string;
27
+ id?: string | null;
28
+ }
29
+ export interface PlatformOperation {
30
+ id: string;
31
+ namespace: PlatformOperationNamespace;
32
+ operation: string;
33
+ status: PlatformOperationStatus;
34
+ target: PlatformOperationTarget;
35
+ idempotencyKey?: string | null;
36
+ input: Record<string, unknown>;
37
+ output?: unknown;
38
+ error?: unknown;
39
+ requestedByType: string;
40
+ requestedById?: string | null;
41
+ assignedRunnerId?: string | null;
42
+ leaseExpiresAt?: string | null;
43
+ createdAt: string;
44
+ updatedAt: string;
45
+ startedAt?: string | null;
46
+ finishedAt?: string | null;
47
+ cancelledAt?: string | null;
48
+ }
49
+ export interface PlatformOperationEvent {
50
+ id: string;
51
+ operationId: string;
52
+ seq: number;
53
+ kind: string;
54
+ data: Record<string, unknown>;
55
+ createdAt: string;
56
+ }
57
+ export interface PlatformOperationInput {
58
+ namespace: PlatformOperationNamespace;
59
+ operation: string;
60
+ target?: PlatformOperationTarget;
61
+ idempotencyKey?: string | null;
62
+ input?: Record<string, unknown>;
63
+ requestedBy?: PlatformOperationRequestedBy;
64
+ status?: PlatformOperationStatus;
65
+ }
66
+ export interface PlatformOperationEventInput {
67
+ kind: string;
68
+ data?: Record<string, unknown>;
69
+ }
70
+ export interface PlatformRunnerRegistrationRequest {
71
+ runnerId: string;
72
+ runnerKey?: string;
73
+ name?: string;
74
+ environment: string;
75
+ version?: string;
76
+ capabilities?: string[];
77
+ maxConcurrentJobs?: number;
78
+ metadata?: Record<string, unknown>;
79
+ }
80
+ export interface PlatformRunnerHeartbeatRequest {
81
+ runnerId: string;
82
+ status?: 'online' | 'offline' | 'degraded' | string;
83
+ activeJobCount?: number;
84
+ maxConcurrentJobs?: number;
85
+ capabilities?: string[];
86
+ metadata?: Record<string, unknown>;
87
+ version?: string;
88
+ environment?: string;
89
+ }
90
+ export interface PlatformRunnerClaimRequest {
91
+ runnerId: string;
92
+ limit?: number;
93
+ leaseSeconds?: number;
94
+ operationId?: string;
95
+ namespaces?: string[];
96
+ capabilities?: string[];
97
+ }
98
+ export interface PlatformRunnerJobUpdateRequest {
99
+ runnerId: string;
100
+ output?: unknown;
101
+ error?: unknown;
102
+ event?: PlatformOperationEventInput;
103
+ }
104
+ export interface PlatformRunnerClientOptions {
105
+ marketUrl: string;
106
+ marketId: string;
107
+ runnerSecret: string;
108
+ fetchImpl?: typeof fetch;
109
+ userAgent?: string;
110
+ }
111
+ export interface PlatformOperationExecutorContext {
112
+ operation: PlatformOperation;
113
+ operationId: string;
114
+ workspaceRoot: string;
115
+ environment: string;
116
+ emit(event: PlatformOperationEventInput): Promise<void>;
117
+ checkpoint(output: unknown, event?: PlatformOperationEventInput): Promise<void>;
118
+ renewLease(leaseSeconds?: number): Promise<PlatformOperation>;
119
+ throwIfCancelled(): Promise<void>;
120
+ }
121
+ export interface PlatformOperationExecutor<TInput = Record<string, unknown>, TOutput = unknown> {
122
+ namespace: PlatformOperationNamespace;
123
+ operation: string;
124
+ run(input: TInput, context: PlatformOperationExecutorContext): Promise<TOutput>;
125
+ }
126
+ export interface PlatformOperationRunnerCoreClient {
127
+ claimJob(request: PlatformRunnerClaimRequest): Promise<{
128
+ ok: true;
129
+ operation: PlatformOperation | null;
130
+ }>;
131
+ getOperation?(operationId: string): Promise<{
132
+ ok: true;
133
+ operation: PlatformOperation;
134
+ }>;
135
+ appendEvent(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
136
+ ok: true;
137
+ event: PlatformOperationEvent;
138
+ }>;
139
+ renewLease?(operationId: string, request: PlatformRunnerJobUpdateRequest & {
140
+ leaseSeconds?: number;
141
+ }): Promise<{
142
+ ok: true;
143
+ operation: PlatformOperation;
144
+ }>;
145
+ checkpoint(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
146
+ ok: true;
147
+ operation: PlatformOperation;
148
+ }>;
149
+ complete(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
150
+ ok: true;
151
+ operation: PlatformOperation;
152
+ }>;
153
+ fail(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
154
+ ok: true;
155
+ operation: PlatformOperation;
156
+ }>;
157
+ }
158
+ export interface PlatformOperationRunnerCoreOptions {
159
+ client: PlatformOperationRunnerCoreClient;
160
+ runnerId: string;
161
+ workspaceRoot: string;
162
+ environment: string;
163
+ executors: PlatformOperationExecutor[];
164
+ operationId?: string | null;
165
+ leaseSeconds?: number;
166
+ limit?: number;
167
+ throwIfCancelled?: (operation: PlatformOperation) => Promise<void>;
168
+ }
169
+ export interface PlatformOperationRunOnceResult {
170
+ ok: boolean;
171
+ claimed: boolean;
172
+ operation: PlatformOperation | null;
173
+ output?: unknown;
174
+ error?: unknown;
175
+ }
176
+ export interface PlatformOperationNavigationResult {
177
+ href: string | null;
178
+ changedPaths: string[];
179
+ branch: string | null;
180
+ commitSha: string | null;
181
+ }
182
+ export interface PlatformOperationPollOptions {
183
+ operationId: string;
184
+ fetchOperation(operationId: string): Promise<PlatformOperation>;
185
+ fetchEvents?(operationId: string): Promise<PlatformOperationEvent[]>;
186
+ onUpdate?(snapshot: {
187
+ operation: PlatformOperation;
188
+ events: PlatformOperationEvent[];
189
+ terminal: boolean;
190
+ }): void | Promise<void>;
191
+ intervalMs?: number;
192
+ timeoutMs?: number;
193
+ sleep?(ms: number): Promise<void>;
194
+ }
195
+ export interface PlatformOperationPollResult {
196
+ operation: PlatformOperation;
197
+ events: PlatformOperationEvent[];
198
+ terminal: boolean;
199
+ navigation: PlatformOperationNavigationResult;
200
+ }
201
+ export declare function buildPlatformRunnerAuthHeaders(secret: string): Record<string, string>;
202
+ export declare function isPlatformOperationTerminal(operation: Pick<PlatformOperation, 'status'> | null | undefined): boolean;
203
+ export declare function isPlatformOperationSuccessful(operation: Pick<PlatformOperation, 'status'> | null | undefined): boolean;
204
+ export declare function derivePlatformOperationNavigation(operation: PlatformOperation): PlatformOperationNavigationResult;
205
+ export declare function pollPlatformOperation(options: PlatformOperationPollOptions): Promise<PlatformOperationPollResult>;
206
+ export declare class PlatformOperationApiError extends Error {
207
+ readonly status: number;
208
+ readonly payload: unknown;
209
+ constructor(message: string, status: number, payload: unknown);
210
+ }
211
+ export declare function assertPlatformOperationOkEnvelope(value: unknown, label?: string): void;
212
+ export declare function assertPlatformOperation(value: unknown, label?: string): asserts value is PlatformOperation;
213
+ export declare function assertPlatformOperationEvent(value: unknown, label?: string): asserts value is PlatformOperationEvent;
214
+ export declare function createPlatformOperationExecutorRegistry(executors: PlatformOperationExecutor[]): {
215
+ get(operation: PlatformOperation): PlatformOperationExecutor<Record<string, unknown>, unknown> | null;
216
+ keys(): string[];
217
+ };
218
+ export declare function runPlatformOperationOnce(options: PlatformOperationRunnerCoreOptions): Promise<PlatformOperationRunOnceResult>;
219
+ export declare class PlatformRunnerClient {
220
+ private readonly marketUrl;
221
+ private readonly marketId;
222
+ private readonly runnerSecret;
223
+ private readonly fetchImpl;
224
+ private readonly userAgent?;
225
+ constructor(options: PlatformRunnerClientOptions);
226
+ private requestJson;
227
+ register(request: PlatformRunnerRegistrationRequest): Promise<{
228
+ ok: true;
229
+ runner: Record<string, unknown>;
230
+ }>;
231
+ heartbeat(request: PlatformRunnerHeartbeatRequest): Promise<{
232
+ ok: true;
233
+ runner: Record<string, unknown>;
234
+ }>;
235
+ claimJob(request: PlatformRunnerClaimRequest): Promise<{
236
+ ok: true;
237
+ operation: PlatformOperation | null;
238
+ }>;
239
+ getOperation(operationId: string): Promise<{
240
+ ok: true;
241
+ operation: PlatformOperation;
242
+ }>;
243
+ appendEvent(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
244
+ ok: true;
245
+ event: PlatformOperationEvent;
246
+ }>;
247
+ renewLease(operationId: string, request: PlatformRunnerJobUpdateRequest & {
248
+ leaseSeconds?: number;
249
+ }): Promise<{
250
+ ok: true;
251
+ operation: PlatformOperation;
252
+ }>;
253
+ checkpoint(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
254
+ ok: true;
255
+ operation: PlatformOperation;
256
+ }>;
257
+ complete(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
258
+ ok: true;
259
+ operation: PlatformOperation;
260
+ }>;
261
+ fail(operationId: string, request: PlatformRunnerJobUpdateRequest): Promise<{
262
+ ok: true;
263
+ operation: PlatformOperation;
264
+ }>;
265
+ }
@@ -0,0 +1,421 @@
1
+ import {
2
+ TREESEED_REMOTE_CONTRACT_HEADER,
3
+ TREESEED_REMOTE_CONTRACT_VERSION
4
+ } from "./remote.js";
5
+ const PLATFORM_OPERATION_ENDPOINTS = {
6
+ operations: "/v1/platform/operations",
7
+ operation: (operationId) => `/v1/platform/operations/${encodeURIComponent(operationId)}`,
8
+ operationEvents: (operationId) => `/v1/platform/operations/${encodeURIComponent(operationId)}/events`,
9
+ cancelOperation: (operationId) => `/v1/platform/operations/${encodeURIComponent(operationId)}/cancel`,
10
+ retryOperation: (operationId) => `/v1/platform/operations/${encodeURIComponent(operationId)}/retry`,
11
+ registerRunner: "/v1/platform/runners/register",
12
+ heartbeatRunner: "/v1/platform/runners/heartbeat",
13
+ claimJob: "/v1/platform/runners/jobs/claim",
14
+ runnerJob: (operationId) => `/v1/platform/runners/jobs/${encodeURIComponent(operationId)}`,
15
+ jobEvents: (operationId) => `/v1/platform/runners/jobs/${encodeURIComponent(operationId)}/events`,
16
+ renewLeaseJob: (operationId) => `/v1/platform/runners/jobs/${encodeURIComponent(operationId)}/renew-lease`,
17
+ checkpointJob: (operationId) => `/v1/platform/runners/jobs/${encodeURIComponent(operationId)}/checkpoint`,
18
+ completeJob: (operationId) => `/v1/platform/runners/jobs/${encodeURIComponent(operationId)}/complete`,
19
+ failJob: (operationId) => `/v1/platform/runners/jobs/${encodeURIComponent(operationId)}/fail`
20
+ };
21
+ const PLATFORM_OPERATION_SCOPES = [
22
+ "platform:runners:register",
23
+ "platform:runners:claim",
24
+ "platform:runners:update",
25
+ "platform:operations:create",
26
+ "platform:operations:read",
27
+ "platform:operations:cancel",
28
+ "platform:operations:retry",
29
+ "platform:repository:write",
30
+ "platform:deploy:write",
31
+ "platform:database:migrate"
32
+ ];
33
+ const PLATFORM_OPERATION_NAMESPACES = [
34
+ "market",
35
+ "repository",
36
+ "deploy",
37
+ "database",
38
+ "seed",
39
+ "infrastructure",
40
+ "catalog"
41
+ ];
42
+ const PLATFORM_OPERATION_STATUSES = [
43
+ "queued",
44
+ "leased",
45
+ "running",
46
+ "waiting_for_approval",
47
+ "succeeded",
48
+ "failed",
49
+ "cancelled"
50
+ ];
51
+ const PLATFORM_OPERATION_TARGETS = [
52
+ "market_operations_runner",
53
+ "github_actions",
54
+ "cli",
55
+ "railway_job"
56
+ ];
57
+ function buildPlatformRunnerAuthHeaders(secret) {
58
+ return {
59
+ authorization: `Bearer ${secret}`
60
+ };
61
+ }
62
+ function isPlatformOperationTerminal(operation) {
63
+ return ["succeeded", "failed", "cancelled"].includes(String(operation?.status ?? ""));
64
+ }
65
+ function isPlatformOperationSuccessful(operation) {
66
+ return String(operation?.status ?? "") === "succeeded";
67
+ }
68
+ function nestedRecord(value, keys) {
69
+ let current = value;
70
+ for (const key of keys) {
71
+ if (!isRecord(current)) return null;
72
+ current = current[key];
73
+ }
74
+ return isRecord(current) ? current : null;
75
+ }
76
+ function firstString(...values) {
77
+ for (const value of values) {
78
+ if (typeof value === "string" && value.trim()) return value.trim();
79
+ }
80
+ return null;
81
+ }
82
+ function stringArray(value) {
83
+ return Array.isArray(value) ? value.map((entry) => String(entry).trim()).filter(Boolean) : [];
84
+ }
85
+ function derivePlatformOperationNavigation(operation) {
86
+ const output = isRecord(operation.output) ? operation.output : {};
87
+ const nestedOutput = nestedRecord(output, ["output"]) ?? {};
88
+ const record = nestedRecord(output, ["record"]) ?? nestedRecord(nestedOutput, ["record"]);
89
+ const child = nestedRecord(output, ["child"]) ?? nestedRecord(nestedOutput, ["child"]);
90
+ const decision = nestedRecord(output, ["decision"]) ?? nestedRecord(nestedOutput, ["decision"]);
91
+ const changedPaths = [
92
+ ...stringArray(output.changedPaths),
93
+ ...stringArray(nestedOutput.changedPaths)
94
+ ];
95
+ return {
96
+ href: firstString(output.href, nestedOutput.href, record?.href, child?.href, decision?.href),
97
+ changedPaths: [...new Set(changedPaths)],
98
+ branch: firstString(output.branch, nestedOutput.branch),
99
+ commitSha: firstString(output.commitSha, nestedOutput.commitSha)
100
+ };
101
+ }
102
+ async function pollPlatformOperation(options) {
103
+ const intervalMs = Math.max(0, options.intervalMs ?? 1e3);
104
+ const timeoutMs = Math.max(intervalMs, options.timeoutMs ?? 12e4);
105
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
106
+ const startedAt = Date.now();
107
+ let latestOperation = null;
108
+ let latestEvents = [];
109
+ while (Date.now() - startedAt <= timeoutMs) {
110
+ latestOperation = await options.fetchOperation(options.operationId);
111
+ latestEvents = options.fetchEvents ? await options.fetchEvents(options.operationId) : [];
112
+ const terminal = isPlatformOperationTerminal(latestOperation);
113
+ await options.onUpdate?.({ operation: latestOperation, events: latestEvents, terminal });
114
+ if (terminal) {
115
+ return {
116
+ operation: latestOperation,
117
+ events: latestEvents,
118
+ terminal,
119
+ navigation: derivePlatformOperationNavigation(latestOperation)
120
+ };
121
+ }
122
+ await sleep(intervalMs);
123
+ }
124
+ if (!latestOperation) {
125
+ throw new Error(`Platform operation "${options.operationId}" was not found before polling timed out.`);
126
+ }
127
+ return {
128
+ operation: latestOperation,
129
+ events: latestEvents,
130
+ terminal: false,
131
+ navigation: derivePlatformOperationNavigation(latestOperation)
132
+ };
133
+ }
134
+ function normalizeBaseUrl(value) {
135
+ return value.trim().replace(/\/+$/u, "");
136
+ }
137
+ function isRecord(value) {
138
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
139
+ }
140
+ class PlatformOperationApiError extends Error {
141
+ status;
142
+ payload;
143
+ constructor(message, status, payload) {
144
+ super(message);
145
+ this.name = "PlatformOperationApiError";
146
+ this.status = status;
147
+ this.payload = payload;
148
+ }
149
+ }
150
+ function assertPlatformOperationOkEnvelope(value, label = "Platform operation response") {
151
+ if (!isRecord(value) || value.ok !== true) {
152
+ throw new Error(`${label} is missing ok: true.`);
153
+ }
154
+ }
155
+ function assertPlatformOperation(value, label = "Platform operation") {
156
+ if (!isRecord(value)) throw new Error(`${label} must be an object.`);
157
+ for (const key of ["id", "namespace", "operation", "status", "target", "createdAt", "updatedAt"]) {
158
+ if (typeof value[key] !== "string" || !String(value[key]).trim()) {
159
+ throw new Error(`${label} is missing ${key}.`);
160
+ }
161
+ }
162
+ if (!isRecord(value.input)) throw new Error(`${label} is missing input.`);
163
+ }
164
+ function assertPlatformOperationEvent(value, label = "Platform operation event") {
165
+ if (!isRecord(value)) throw new Error(`${label} must be an object.`);
166
+ for (const key of ["id", "operationId", "kind", "createdAt"]) {
167
+ if (typeof value[key] !== "string" || !String(value[key]).trim()) {
168
+ throw new Error(`${label} is missing ${key}.`);
169
+ }
170
+ }
171
+ if (!Number.isFinite(Number(value.seq))) throw new Error(`${label} is missing seq.`);
172
+ if (!isRecord(value.data)) throw new Error(`${label} is missing data.`);
173
+ }
174
+ function createPlatformOperationExecutorRegistry(executors) {
175
+ const registry = /* @__PURE__ */ new Map();
176
+ for (const executor of executors) {
177
+ registry.set(`${executor.namespace}:${executor.operation}`, executor);
178
+ }
179
+ return {
180
+ get(operation) {
181
+ return registry.get(`${operation.namespace}:${operation.operation}`) ?? null;
182
+ },
183
+ keys() {
184
+ return [...registry.keys()];
185
+ }
186
+ };
187
+ }
188
+ async function runPlatformOperationOnce(options) {
189
+ const registry = createPlatformOperationExecutorRegistry(options.executors);
190
+ const claimed = await options.client.claimJob({
191
+ runnerId: options.runnerId,
192
+ operationId: options.operationId ?? void 0,
193
+ limit: options.limit ?? 1,
194
+ leaseSeconds: options.leaseSeconds ?? 300
195
+ });
196
+ let operation = claimed.operation;
197
+ if (!operation) {
198
+ return { ok: true, claimed: false, operation: null };
199
+ }
200
+ const executor = registry.get(operation);
201
+ if (!executor) {
202
+ const message = `No executor registered for platform operation "${operation.namespace}:${operation.operation}".`;
203
+ const failed = await options.client.fail(operation.id, {
204
+ runnerId: options.runnerId,
205
+ error: { message },
206
+ event: { kind: "runner.executor_missing", data: { namespace: operation.namespace, operation: operation.operation } }
207
+ });
208
+ return { ok: false, claimed: true, operation: failed.operation, error: { message } };
209
+ }
210
+ const context = {
211
+ operation,
212
+ operationId: operation.id,
213
+ workspaceRoot: options.workspaceRoot,
214
+ environment: options.environment,
215
+ emit: async (event) => {
216
+ await options.client.appendEvent(operation.id, {
217
+ runnerId: options.runnerId,
218
+ event
219
+ });
220
+ },
221
+ checkpoint: async (output, event) => {
222
+ await context.throwIfCancelled();
223
+ await options.client.checkpoint(operation.id, {
224
+ runnerId: options.runnerId,
225
+ output,
226
+ event
227
+ });
228
+ },
229
+ renewLease: async (leaseSeconds) => {
230
+ if (!options.client.renewLease) return operation;
231
+ const renewed = await options.client.renewLease(operation.id, {
232
+ runnerId: options.runnerId,
233
+ leaseSeconds,
234
+ event: { kind: "runner.lease_renewed", data: { leaseSeconds: leaseSeconds ?? options.leaseSeconds ?? 300 } }
235
+ });
236
+ operation = renewed.operation;
237
+ return renewed.operation;
238
+ },
239
+ throwIfCancelled: async () => {
240
+ const latest = options.client.getOperation ? (await options.client.getOperation(operation.id)).operation : operation;
241
+ operation = latest;
242
+ if (latest.status === "cancelled") throw new Error("Platform operation was cancelled.");
243
+ await options.throwIfCancelled?.(operation);
244
+ }
245
+ };
246
+ try {
247
+ await context.emit({ kind: "runner.started", data: { namespace: operation.namespace, operation: operation.operation } });
248
+ await context.throwIfCancelled();
249
+ await context.renewLease(options.leaseSeconds);
250
+ const output = await executor.run(operation.input, context);
251
+ await context.throwIfCancelled();
252
+ const completed = await options.client.complete(operation.id, {
253
+ runnerId: options.runnerId,
254
+ output
255
+ });
256
+ return { ok: true, claimed: true, operation: completed.operation, output };
257
+ } catch (error) {
258
+ const failure = {
259
+ message: error instanceof Error ? error.message : String(error)
260
+ };
261
+ const eventKind = failure.message.toLowerCase().includes("cancel") ? "runner.cancelled" : "runner.retry_safe_failure";
262
+ if (eventKind === "runner.cancelled" && options.client.getOperation) {
263
+ await options.client.appendEvent(operation.id, {
264
+ runnerId: options.runnerId,
265
+ event: { kind: eventKind, data: failure }
266
+ }).catch(() => {
267
+ });
268
+ const latest = await options.client.getOperation(operation.id);
269
+ return { ok: false, claimed: true, operation: latest.operation, error: failure };
270
+ }
271
+ const failed = await options.client.fail(operation.id, {
272
+ runnerId: options.runnerId,
273
+ error: failure,
274
+ event: { kind: eventKind, data: failure }
275
+ });
276
+ return { ok: false, claimed: true, operation: failed.operation, error: failure };
277
+ }
278
+ }
279
+ class PlatformRunnerClient {
280
+ marketUrl;
281
+ marketId;
282
+ runnerSecret;
283
+ fetchImpl;
284
+ userAgent;
285
+ constructor(options) {
286
+ this.marketUrl = normalizeBaseUrl(options.marketUrl);
287
+ this.marketId = options.marketId.trim();
288
+ this.runnerSecret = options.runnerSecret.trim();
289
+ if (!this.marketUrl) throw new Error("Market API URL is required.");
290
+ if (!this.marketId) throw new Error("Market ID is required.");
291
+ if (!this.runnerSecret) throw new Error("Platform runner secret is required.");
292
+ this.fetchImpl = options.fetchImpl ?? fetch;
293
+ this.userAgent = options.userAgent;
294
+ }
295
+ async requestJson(path, options = {}) {
296
+ const headers = {
297
+ accept: "application/json",
298
+ [TREESEED_REMOTE_CONTRACT_HEADER]: String(TREESEED_REMOTE_CONTRACT_VERSION),
299
+ ...buildPlatformRunnerAuthHeaders(this.runnerSecret)
300
+ };
301
+ if (this.userAgent) headers["user-agent"] = this.userAgent;
302
+ if (options.body !== void 0) headers["content-type"] = "application/json";
303
+ const response = await this.fetchImpl(`${this.marketUrl}${path}`, {
304
+ method: options.method ?? "GET",
305
+ headers,
306
+ body: options.body === void 0 ? void 0 : JSON.stringify(options.body)
307
+ });
308
+ const payload = await response.json().catch(() => ({}));
309
+ if (!response.ok) {
310
+ const message = isRecord(payload) && typeof payload.error === "string" ? payload.error : `Platform operation request failed with ${response.status}.`;
311
+ throw new PlatformOperationApiError(message, response.status, payload);
312
+ }
313
+ return payload;
314
+ }
315
+ register(request) {
316
+ return this.requestJson(PLATFORM_OPERATION_ENDPOINTS.registerRunner, {
317
+ method: "POST",
318
+ body: { ...request, marketId: this.marketId }
319
+ }).then((response) => {
320
+ assertPlatformOperationOkEnvelope(response, "Platform runner registration response");
321
+ return response;
322
+ });
323
+ }
324
+ heartbeat(request) {
325
+ return this.requestJson(PLATFORM_OPERATION_ENDPOINTS.heartbeatRunner, {
326
+ method: "POST",
327
+ body: { ...request, marketId: this.marketId }
328
+ }).then((response) => {
329
+ assertPlatformOperationOkEnvelope(response, "Platform runner heartbeat response");
330
+ return response;
331
+ });
332
+ }
333
+ claimJob(request) {
334
+ return this.requestJson(PLATFORM_OPERATION_ENDPOINTS.claimJob, {
335
+ method: "POST",
336
+ body: { ...request, marketId: this.marketId }
337
+ }).then((response) => {
338
+ assertPlatformOperationOkEnvelope(response, "Platform runner claim response");
339
+ if (response.operation !== null) assertPlatformOperation(response.operation, "Claimed platform operation");
340
+ return response;
341
+ });
342
+ }
343
+ getOperation(operationId) {
344
+ return this.requestJson(PLATFORM_OPERATION_ENDPOINTS.runnerJob(operationId), {
345
+ method: "GET"
346
+ }).then((response) => {
347
+ assertPlatformOperationOkEnvelope(response, "Platform operation response");
348
+ assertPlatformOperation(response.operation);
349
+ return response;
350
+ });
351
+ }
352
+ appendEvent(operationId, request) {
353
+ return this.requestJson(PLATFORM_OPERATION_ENDPOINTS.jobEvents(operationId), {
354
+ method: "POST",
355
+ body: { ...request, marketId: this.marketId }
356
+ }).then((response) => {
357
+ assertPlatformOperationOkEnvelope(response, "Platform operation event response");
358
+ assertPlatformOperationEvent(response.event);
359
+ return response;
360
+ });
361
+ }
362
+ renewLease(operationId, request) {
363
+ return this.requestJson(PLATFORM_OPERATION_ENDPOINTS.renewLeaseJob(operationId), {
364
+ method: "POST",
365
+ body: { ...request, marketId: this.marketId }
366
+ }).then((response) => {
367
+ assertPlatformOperationOkEnvelope(response, "Platform operation lease renewal response");
368
+ assertPlatformOperation(response.operation);
369
+ return response;
370
+ });
371
+ }
372
+ checkpoint(operationId, request) {
373
+ return this.requestJson(PLATFORM_OPERATION_ENDPOINTS.checkpointJob(operationId), {
374
+ method: "POST",
375
+ body: { ...request, marketId: this.marketId }
376
+ }).then((response) => {
377
+ assertPlatformOperationOkEnvelope(response, "Platform operation checkpoint response");
378
+ assertPlatformOperation(response.operation);
379
+ return response;
380
+ });
381
+ }
382
+ complete(operationId, request) {
383
+ return this.requestJson(PLATFORM_OPERATION_ENDPOINTS.completeJob(operationId), {
384
+ method: "POST",
385
+ body: { ...request, marketId: this.marketId }
386
+ }).then((response) => {
387
+ assertPlatformOperationOkEnvelope(response, "Platform operation completion response");
388
+ assertPlatformOperation(response.operation);
389
+ return response;
390
+ });
391
+ }
392
+ fail(operationId, request) {
393
+ return this.requestJson(PLATFORM_OPERATION_ENDPOINTS.failJob(operationId), {
394
+ method: "POST",
395
+ body: { ...request, marketId: this.marketId }
396
+ }).then((response) => {
397
+ assertPlatformOperationOkEnvelope(response, "Platform operation failure response");
398
+ assertPlatformOperation(response.operation);
399
+ return response;
400
+ });
401
+ }
402
+ }
403
+ export {
404
+ PLATFORM_OPERATION_ENDPOINTS,
405
+ PLATFORM_OPERATION_NAMESPACES,
406
+ PLATFORM_OPERATION_SCOPES,
407
+ PLATFORM_OPERATION_STATUSES,
408
+ PLATFORM_OPERATION_TARGETS,
409
+ PlatformOperationApiError,
410
+ PlatformRunnerClient,
411
+ assertPlatformOperation,
412
+ assertPlatformOperationEvent,
413
+ assertPlatformOperationOkEnvelope,
414
+ buildPlatformRunnerAuthHeaders,
415
+ createPlatformOperationExecutorRegistry,
416
+ derivePlatformOperationNavigation,
417
+ isPlatformOperationSuccessful,
418
+ isPlatformOperationTerminal,
419
+ pollPlatformOperation,
420
+ runPlatformOperationOnce
421
+ };