@treeseed/agent 0.8.5

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 (138) hide show
  1. package/Dockerfile +7 -0
  2. package/README.md +198 -0
  3. package/dist/agent-runtime.d.ts +17 -0
  4. package/dist/agent-runtime.js +117 -0
  5. package/dist/agents/adapters/execution.d.ts +41 -0
  6. package/dist/agents/adapters/execution.js +73 -0
  7. package/dist/agents/adapters/mutations.d.ts +22 -0
  8. package/dist/agents/adapters/mutations.js +30 -0
  9. package/dist/agents/adapters/notification.d.ts +26 -0
  10. package/dist/agents/adapters/notification.js +46 -0
  11. package/dist/agents/adapters/repository.d.ts +28 -0
  12. package/dist/agents/adapters/repository.js +61 -0
  13. package/dist/agents/adapters/research.d.ts +26 -0
  14. package/dist/agents/adapters/research.js +59 -0
  15. package/dist/agents/adapters/verification.d.ts +36 -0
  16. package/dist/agents/adapters/verification.js +62 -0
  17. package/dist/agents/cli-tools.d.ts +1 -0
  18. package/dist/agents/cli-tools.js +5 -0
  19. package/dist/agents/cli.d.ts +15 -0
  20. package/dist/agents/cli.js +109 -0
  21. package/dist/agents/contracts/messages.d.ts +88 -0
  22. package/dist/agents/contracts/messages.js +138 -0
  23. package/dist/agents/contracts/run.d.ts +21 -0
  24. package/dist/agents/contracts/run.js +0 -0
  25. package/dist/agents/index.d.ts +1 -0
  26. package/dist/agents/index.js +5 -0
  27. package/dist/agents/kernel/agent-kernel.d.ts +63 -0
  28. package/dist/agents/kernel/agent-kernel.js +291 -0
  29. package/dist/agents/kernel/trigger-resolver.d.ts +19 -0
  30. package/dist/agents/kernel/trigger-resolver.js +157 -0
  31. package/dist/agents/registry-helper.d.ts +4 -0
  32. package/dist/agents/registry-helper.js +14 -0
  33. package/dist/agents/registry.d.ts +6 -0
  34. package/dist/agents/registry.js +98 -0
  35. package/dist/agents/runtime-types.d.ts +118 -0
  36. package/dist/agents/runtime-types.js +0 -0
  37. package/dist/agents/spec-loader.d.ts +18 -0
  38. package/dist/agents/spec-loader.js +54 -0
  39. package/dist/agents/spec-normalizer.d.ts +2 -0
  40. package/dist/agents/spec-normalizer.js +327 -0
  41. package/dist/agents/spec-types.d.ts +64 -0
  42. package/dist/agents/spec-types.js +0 -0
  43. package/dist/agents/testing/agents-smoke.d.ts +1 -0
  44. package/dist/agents/testing/agents-smoke.js +32 -0
  45. package/dist/agents/testing/e2e-harness.d.ts +44 -0
  46. package/dist/agents/testing/e2e-harness.js +503 -0
  47. package/dist/api/agent-routes.d.ts +13 -0
  48. package/dist/api/agent-routes.js +327 -0
  49. package/dist/api/app.d.ts +8 -0
  50. package/dist/api/app.js +444 -0
  51. package/dist/api/auth/d1-database.d.ts +3 -0
  52. package/dist/api/auth/d1-database.js +20 -0
  53. package/dist/api/auth/d1-provider.d.ts +79 -0
  54. package/dist/api/auth/d1-provider.js +92 -0
  55. package/dist/api/auth/d1-store.d.ts +114 -0
  56. package/dist/api/auth/d1-store.js +895 -0
  57. package/dist/api/auth/memory-provider.d.ts +77 -0
  58. package/dist/api/auth/memory-provider.js +249 -0
  59. package/dist/api/auth/rbac.d.ts +22 -0
  60. package/dist/api/auth/rbac.js +162 -0
  61. package/dist/api/auth/tokens.d.ts +18 -0
  62. package/dist/api/auth/tokens.js +56 -0
  63. package/dist/api/capabilities.d.ts +9 -0
  64. package/dist/api/capabilities.js +33 -0
  65. package/dist/api/config.d.ts +2 -0
  66. package/dist/api/config.js +77 -0
  67. package/dist/api/http.d.ts +28 -0
  68. package/dist/api/http.js +51 -0
  69. package/dist/api/index.d.ts +9 -0
  70. package/dist/api/index.js +20 -0
  71. package/dist/api/operations-routes.d.ts +11 -0
  72. package/dist/api/operations-routes.js +87 -0
  73. package/dist/api/operations.d.ts +3 -0
  74. package/dist/api/operations.js +26 -0
  75. package/dist/api/project-routes.d.ts +8 -0
  76. package/dist/api/project-routes.js +585 -0
  77. package/dist/api/providers.d.ts +2 -0
  78. package/dist/api/providers.js +62 -0
  79. package/dist/api/railway.d.ts +51 -0
  80. package/dist/api/railway.js +71 -0
  81. package/dist/api/sdk-dispatch.d.ts +5 -0
  82. package/dist/api/sdk-dispatch.js +13 -0
  83. package/dist/api/sdk-routes.d.ts +11 -0
  84. package/dist/api/sdk-routes.js +29 -0
  85. package/dist/api/server.d.ts +2 -0
  86. package/dist/api/server.js +10 -0
  87. package/dist/api/templates.d.ts +3 -0
  88. package/dist/api/templates.js +31 -0
  89. package/dist/api/types.d.ts +237 -0
  90. package/dist/api/types.js +0 -0
  91. package/dist/env.yaml +957 -0
  92. package/dist/index.d.ts +14 -0
  93. package/dist/index.js +41 -0
  94. package/dist/scripts/assert-release-tag-version.d.ts +1 -0
  95. package/dist/scripts/assert-release-tag-version.js +20 -0
  96. package/dist/scripts/build-dist.d.ts +1 -0
  97. package/dist/scripts/build-dist.js +106 -0
  98. package/dist/scripts/package-tools.d.ts +1 -0
  99. package/dist/scripts/package-tools.js +7 -0
  100. package/dist/scripts/publish-package.d.ts +1 -0
  101. package/dist/scripts/publish-package.js +24 -0
  102. package/dist/scripts/release-verify.d.ts +1 -0
  103. package/dist/scripts/release-verify.js +152 -0
  104. package/dist/scripts/test-smoke.d.ts +1 -0
  105. package/dist/scripts/test-smoke.js +23 -0
  106. package/dist/scripts/treeseed-agent-api.d.ts +2 -0
  107. package/dist/scripts/treeseed-agent-api.js +25 -0
  108. package/dist/scripts/treeseed-agent-service.d.ts +2 -0
  109. package/dist/scripts/treeseed-agent-service.js +36 -0
  110. package/dist/scripts/treeseed-agents.d.ts +2 -0
  111. package/dist/scripts/treeseed-agents.js +13 -0
  112. package/dist/services/agents.d.ts +17 -0
  113. package/dist/services/agents.js +48 -0
  114. package/dist/services/common.d.ts +66 -0
  115. package/dist/services/common.js +212 -0
  116. package/dist/services/index.d.ts +6 -0
  117. package/dist/services/index.js +19 -0
  118. package/dist/services/manager.d.ts +333 -0
  119. package/dist/services/manager.js +1368 -0
  120. package/dist/services/remote-runner.d.ts +30 -0
  121. package/dist/services/remote-runner.js +230 -0
  122. package/dist/services/workday-content.d.ts +53 -0
  123. package/dist/services/workday-content.js +190 -0
  124. package/dist/services/workday-manager.d.ts +391 -0
  125. package/dist/services/workday-manager.js +163 -0
  126. package/dist/services/workday-report.d.ts +238 -0
  127. package/dist/services/workday-report.js +17 -0
  128. package/dist/services/workday-start.d.ts +238 -0
  129. package/dist/services/workday-start.js +17 -0
  130. package/dist/services/worker-capacity.d.ts +58 -0
  131. package/dist/services/worker-capacity.js +208 -0
  132. package/dist/services/worker-pool-scaler.d.ts +27 -0
  133. package/dist/services/worker-pool-scaler.js +127 -0
  134. package/dist/services/worker.d.ts +19 -0
  135. package/dist/services/worker.js +436 -0
  136. package/dist/templates/github/deploy-processing.workflow.yml +119 -0
  137. package/package.json +136 -0
  138. package/templates/github/deploy-processing.workflow.yml +119 -0
@@ -0,0 +1,391 @@
1
+ #!/usr/bin/env node
2
+ import { createServiceSdk } from './common.ts';
3
+ import { resolveManagerServiceConfig } from './manager.ts';
4
+ type WorkdayManagerSdk = ReturnType<typeof createServiceSdk>;
5
+ type WorkdayManagerConfig = ReturnType<typeof resolveManagerServiceConfig>;
6
+ export declare function runScheduledWorkdayManager(options?: {
7
+ sdk?: WorkdayManagerSdk;
8
+ config?: WorkdayManagerConfig;
9
+ now?: Date;
10
+ }): Promise<{
11
+ ok: boolean;
12
+ skipped: boolean;
13
+ reason: string;
14
+ initial?: undefined;
15
+ latest?: undefined;
16
+ closed?: undefined;
17
+ } | {
18
+ ok: boolean;
19
+ skipped: boolean;
20
+ reason: string;
21
+ initial: {
22
+ ok: boolean;
23
+ mode: "reconcile";
24
+ managerId: string;
25
+ projectId: string;
26
+ environment: "local" | "staging" | "prod";
27
+ insideWorkWindow: boolean;
28
+ workPolicy: import("@treeseed/sdk").WorkdayPolicy;
29
+ workDay: null;
30
+ prioritySnapshot: null;
31
+ seededTasks: never[];
32
+ queuedCount: number;
33
+ activeLeases: number;
34
+ desiredWorkers: number;
35
+ scaleResult: {
36
+ applied: boolean;
37
+ provider: string;
38
+ desiredWorkers: number;
39
+ metadata: {
40
+ reason: string;
41
+ };
42
+ };
43
+ workdaySummary: null;
44
+ } | {
45
+ ok: boolean;
46
+ mode: "reconcile";
47
+ managerId: string;
48
+ projectId: string;
49
+ environment: "local" | "staging" | "prod";
50
+ insideWorkWindow: boolean;
51
+ workPolicy: import("@treeseed/sdk").WorkdayPolicy;
52
+ workDay: Record<string, unknown>;
53
+ prioritySnapshot: import("@treeseed/sdk").PrioritySnapshot | null;
54
+ seededTasks: {
55
+ [x: string]: unknown;
56
+ }[];
57
+ queuedCount: number;
58
+ activeLeases: number;
59
+ desiredWorkers: number;
60
+ scaleResult: import("@treeseed/sdk").WorkerPoolScaleResult;
61
+ workdaySummary: Record<string, unknown> | null;
62
+ };
63
+ latest?: undefined;
64
+ closed?: undefined;
65
+ } | {
66
+ ok: boolean;
67
+ initial: {
68
+ ok: boolean;
69
+ mode: "reconcile";
70
+ managerId: string;
71
+ projectId: string;
72
+ environment: "local" | "staging" | "prod";
73
+ insideWorkWindow: boolean;
74
+ workPolicy: import("@treeseed/sdk").WorkdayPolicy;
75
+ workDay: null;
76
+ prioritySnapshot: null;
77
+ seededTasks: never[];
78
+ queuedCount: number;
79
+ activeLeases: number;
80
+ desiredWorkers: number;
81
+ scaleResult: {
82
+ applied: boolean;
83
+ provider: string;
84
+ desiredWorkers: number;
85
+ metadata: {
86
+ reason: string;
87
+ };
88
+ };
89
+ workdaySummary: null;
90
+ } | {
91
+ ok: boolean;
92
+ mode: "reconcile";
93
+ managerId: string;
94
+ projectId: string;
95
+ environment: "local" | "staging" | "prod";
96
+ insideWorkWindow: boolean;
97
+ workPolicy: import("@treeseed/sdk").WorkdayPolicy;
98
+ workDay: Record<string, unknown>;
99
+ prioritySnapshot: import("@treeseed/sdk").PrioritySnapshot | null;
100
+ seededTasks: {
101
+ [x: string]: unknown;
102
+ }[];
103
+ queuedCount: number;
104
+ activeLeases: number;
105
+ desiredWorkers: number;
106
+ scaleResult: import("@treeseed/sdk").WorkerPoolScaleResult;
107
+ workdaySummary: Record<string, unknown> | null;
108
+ };
109
+ latest: {
110
+ ok: boolean;
111
+ mode: "reconcile";
112
+ managerId: string;
113
+ projectId: string;
114
+ environment: "local" | "staging" | "prod";
115
+ insideWorkWindow: boolean;
116
+ workPolicy: import("@treeseed/sdk").WorkdayPolicy;
117
+ workDay: null;
118
+ prioritySnapshot: null;
119
+ seededTasks: never[];
120
+ queuedCount: number;
121
+ activeLeases: number;
122
+ desiredWorkers: number;
123
+ scaleResult: {
124
+ applied: boolean;
125
+ provider: string;
126
+ desiredWorkers: number;
127
+ metadata: {
128
+ reason: string;
129
+ };
130
+ };
131
+ workdaySummary: null;
132
+ } | {
133
+ ok: boolean;
134
+ mode: "reconcile";
135
+ managerId: string;
136
+ projectId: string;
137
+ environment: "local" | "staging" | "prod";
138
+ insideWorkWindow: boolean;
139
+ workPolicy: import("@treeseed/sdk").WorkdayPolicy;
140
+ workDay: Record<string, unknown>;
141
+ prioritySnapshot: import("@treeseed/sdk").PrioritySnapshot | null;
142
+ seededTasks: {
143
+ [x: string]: unknown;
144
+ }[];
145
+ queuedCount: number;
146
+ activeLeases: number;
147
+ desiredWorkers: number;
148
+ scaleResult: import("@treeseed/sdk").WorkerPoolScaleResult;
149
+ workdaySummary: Record<string, unknown> | null;
150
+ };
151
+ closed: {
152
+ ok: boolean;
153
+ mode: "reconcile";
154
+ managerId: string;
155
+ projectId: string;
156
+ environment: "local" | "staging" | "prod";
157
+ insideWorkWindow: boolean;
158
+ workPolicy: import("@treeseed/sdk").WorkdayPolicy;
159
+ workDay: null;
160
+ prioritySnapshot: null;
161
+ seededTasks: never[];
162
+ queuedCount: number;
163
+ activeLeases: number;
164
+ desiredWorkers: number;
165
+ scaleResult: {
166
+ applied: boolean;
167
+ provider: string;
168
+ desiredWorkers: number;
169
+ metadata: {
170
+ reason: string;
171
+ };
172
+ };
173
+ workdaySummary: null;
174
+ } | {
175
+ ok: boolean;
176
+ mode: "reconcile";
177
+ managerId: string;
178
+ projectId: string;
179
+ environment: "local" | "staging" | "prod";
180
+ insideWorkWindow: boolean;
181
+ workPolicy: import("@treeseed/sdk").WorkdayPolicy;
182
+ workDay: Record<string, unknown>;
183
+ prioritySnapshot: import("@treeseed/sdk").PrioritySnapshot | null;
184
+ seededTasks: {
185
+ [x: string]: unknown;
186
+ }[];
187
+ queuedCount: number;
188
+ activeLeases: number;
189
+ desiredWorkers: number;
190
+ scaleResult: import("@treeseed/sdk").WorkerPoolScaleResult;
191
+ workdaySummary: Record<string, unknown> | null;
192
+ } | {
193
+ ok: boolean;
194
+ created: boolean;
195
+ workDay: Record<string, unknown>;
196
+ skipped?: undefined;
197
+ reason?: undefined;
198
+ prioritySnapshot?: undefined;
199
+ } | {
200
+ ok: boolean;
201
+ created: boolean;
202
+ skipped: boolean;
203
+ reason: string;
204
+ workDay?: undefined;
205
+ prioritySnapshot?: undefined;
206
+ } | {
207
+ ok: boolean;
208
+ created: boolean;
209
+ workDay: import("@treeseed/sdk").SdkWorkDayEntity | null;
210
+ prioritySnapshot: import("@treeseed/sdk").PrioritySnapshot | null;
211
+ skipped?: undefined;
212
+ reason?: undefined;
213
+ } | {
214
+ ok: boolean;
215
+ skipped: boolean;
216
+ reason: string;
217
+ workDay?: undefined;
218
+ summary?: undefined;
219
+ scale?: undefined;
220
+ } | {
221
+ ok: boolean;
222
+ workDay: import("@treeseed/sdk").SdkWorkDayEntity | null;
223
+ summary: {
224
+ contentSnapshot: {
225
+ relativePath: string;
226
+ slug: string;
227
+ reportVersion: string;
228
+ title: string;
229
+ };
230
+ capacity: {
231
+ providerSplit: {
232
+ providerId: string;
233
+ laneId: string;
234
+ state: import("@treeseed/sdk/types").CapacityReservationState;
235
+ reservedCredits: number;
236
+ consumedCredits: number;
237
+ reservedProviderUnits: number | null;
238
+ consumedProviderUnits: number | null;
239
+ reservedUsd: number | null;
240
+ consumedUsd: number | null;
241
+ }[];
242
+ grantedDailyCredits: number;
243
+ reservedCredits: number;
244
+ consumedCredits: number;
245
+ remainingDailyCredits: number | null;
246
+ providerCount: number;
247
+ laneCount: number;
248
+ grantCount: number;
249
+ } | null;
250
+ projectId: string;
251
+ environment: "local" | "staging" | "prod";
252
+ workDayId: string;
253
+ state: string;
254
+ totalTasks: number;
255
+ completedTasks: number;
256
+ failedTasks: number;
257
+ queuedTasks: number;
258
+ activeTasks: number;
259
+ dailyTaskCreditBudget: number;
260
+ usedTaskCredits: number;
261
+ remainingTaskCredits: number;
262
+ creditLedgerEntries: number;
263
+ prioritySnapshotId: string | null;
264
+ priorityItemCount: number;
265
+ priorityItems: import("@treeseed/sdk").PrioritySnapshotItem[];
266
+ taskItems: {
267
+ id: string;
268
+ agentId: string | undefined;
269
+ type: string | undefined;
270
+ state: string | undefined;
271
+ priority: number | undefined;
272
+ idempotencyKey: string | undefined;
273
+ createdAt: string | null;
274
+ startedAt: string | null;
275
+ completedAt: string | null;
276
+ lastErrorCode: string | null;
277
+ lastErrorMessage: string | null;
278
+ lastEventKind: string | null;
279
+ outputCount: number;
280
+ changedFiles: string[];
281
+ }[];
282
+ changedFiles: string[];
283
+ releases: {
284
+ id: string | undefined;
285
+ deploymentKind: string;
286
+ status: string;
287
+ releaseTag: string | null;
288
+ commitSha: string | null;
289
+ sourceRef: string | null;
290
+ startedAt: string | null;
291
+ finishedAt: string | null;
292
+ createdAt: string | null;
293
+ }[];
294
+ scaleDecision: import("@treeseed/sdk").ScaleDecision;
295
+ scaleResult: import("@treeseed/sdk").WorkerPoolScaleResult;
296
+ generatedAt: string;
297
+ };
298
+ scale: import("@treeseed/sdk").WorkerPoolScaleResult;
299
+ skipped?: undefined;
300
+ reason?: undefined;
301
+ } | {
302
+ ok: boolean;
303
+ skipped: boolean;
304
+ reason: string;
305
+ workDayId?: undefined;
306
+ summary?: undefined;
307
+ } | {
308
+ ok: boolean;
309
+ workDayId: unknown;
310
+ summary: {
311
+ contentSnapshot: {
312
+ relativePath: string;
313
+ slug: string;
314
+ reportVersion: string;
315
+ title: string;
316
+ };
317
+ capacity: {
318
+ providerSplit: {
319
+ providerId: string;
320
+ laneId: string;
321
+ state: import("@treeseed/sdk/types").CapacityReservationState;
322
+ reservedCredits: number;
323
+ consumedCredits: number;
324
+ reservedProviderUnits: number | null;
325
+ consumedProviderUnits: number | null;
326
+ reservedUsd: number | null;
327
+ consumedUsd: number | null;
328
+ }[];
329
+ grantedDailyCredits: number;
330
+ reservedCredits: number;
331
+ consumedCredits: number;
332
+ remainingDailyCredits: number | null;
333
+ providerCount: number;
334
+ laneCount: number;
335
+ grantCount: number;
336
+ } | null;
337
+ projectId: string;
338
+ environment: "local" | "staging" | "prod";
339
+ workDayId: string;
340
+ state: string;
341
+ totalTasks: number;
342
+ completedTasks: number;
343
+ failedTasks: number;
344
+ queuedTasks: number;
345
+ activeTasks: number;
346
+ dailyTaskCreditBudget: number;
347
+ usedTaskCredits: number;
348
+ remainingTaskCredits: number;
349
+ creditLedgerEntries: number;
350
+ prioritySnapshotId: string | null;
351
+ priorityItemCount: number;
352
+ priorityItems: import("@treeseed/sdk").PrioritySnapshotItem[];
353
+ taskItems: {
354
+ id: string;
355
+ agentId: string | undefined;
356
+ type: string | undefined;
357
+ state: string | undefined;
358
+ priority: number | undefined;
359
+ idempotencyKey: string | undefined;
360
+ createdAt: string | null;
361
+ startedAt: string | null;
362
+ completedAt: string | null;
363
+ lastErrorCode: string | null;
364
+ lastErrorMessage: string | null;
365
+ lastEventKind: string | null;
366
+ outputCount: number;
367
+ changedFiles: string[];
368
+ }[];
369
+ changedFiles: string[];
370
+ releases: {
371
+ id: string | undefined;
372
+ deploymentKind: string;
373
+ status: string;
374
+ releaseTag: string | null;
375
+ commitSha: string | null;
376
+ sourceRef: string | null;
377
+ startedAt: string | null;
378
+ finishedAt: string | null;
379
+ createdAt: string | null;
380
+ }[];
381
+ scaleDecision: import("@treeseed/sdk").ScaleDecision;
382
+ scaleResult: import("@treeseed/sdk").WorkerPoolScaleResult;
383
+ generatedAt: string;
384
+ };
385
+ skipped?: undefined;
386
+ reason?: undefined;
387
+ };
388
+ skipped?: undefined;
389
+ reason?: undefined;
390
+ }>;
391
+ export {};
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { createServiceSdk } from "./common.js";
4
+ import {
5
+ resolveManagerServiceConfig,
6
+ runManagerAction,
7
+ runManagerCycle
8
+ } from "./manager.js";
9
+ function readDate(value) {
10
+ if (typeof value !== "string") return null;
11
+ const parsed = new Date(value);
12
+ return Number.isFinite(parsed.valueOf()) ? parsed : null;
13
+ }
14
+ function workdayCloseDeadline(workDay, durationMinutes, now) {
15
+ const startedAt = readDate(workDay?.startedAt) ?? readDate(workDay?.started_at) ?? now;
16
+ return new Date(startedAt.valueOf() + durationMinutes * 60 * 1e3);
17
+ }
18
+ async function sleep(ms) {
19
+ await new Promise((resolve) => setTimeout(resolve, ms));
20
+ }
21
+ async function acquireLease(sdk, config, workDayId) {
22
+ if (typeof sdk.claimWorkdayManagerLease !== "function") {
23
+ return { payload: { id: `local:${config.managerId}`, managerId: config.managerId } };
24
+ }
25
+ return sdk.claimWorkdayManagerLease({
26
+ projectId: config.projectId,
27
+ environment: config.environment,
28
+ workDayId,
29
+ managerId: config.managerId,
30
+ ttlSeconds: Math.max(60, Math.ceil(config.pollIntervalMs / 1e3) * 4),
31
+ staleAfterSeconds: Math.max(120, Math.ceil(config.pollIntervalMs / 1e3) * 8),
32
+ metadata: {
33
+ service: "workdayManager"
34
+ }
35
+ });
36
+ }
37
+ async function heartbeatLease(sdk, config, leaseId, workDayId) {
38
+ if (typeof sdk.claimWorkdayManagerLease !== "function") return;
39
+ await sdk.claimWorkdayManagerLease({
40
+ id: leaseId,
41
+ projectId: config.projectId,
42
+ environment: config.environment,
43
+ workDayId,
44
+ managerId: config.managerId,
45
+ ttlSeconds: Math.max(60, Math.ceil(config.pollIntervalMs / 1e3) * 4),
46
+ metadata: {
47
+ service: "workdayManager",
48
+ heartbeat: true
49
+ }
50
+ });
51
+ }
52
+ async function releaseLease(sdk, managerId, leaseId) {
53
+ if (!leaseId || typeof sdk.releaseWorkdayManagerLease !== "function") return;
54
+ await sdk.releaseWorkdayManagerLease({ id: leaseId, managerId }).catch(() => null);
55
+ }
56
+ async function recordRunnerCloseDecision(sdk, config, workDayId, action, reason) {
57
+ if (typeof sdk.recordRunnerScaleDecision !== "function") return;
58
+ const runners = typeof sdk.listWorkerRunners === "function" ? (await sdk.listWorkerRunners(config.projectId, config.environment).catch(() => ({ payload: [] }))).payload ?? [] : [];
59
+ if (runners.length === 0) {
60
+ await sdk.recordRunnerScaleDecision({
61
+ projectId: config.projectId,
62
+ environment: config.environment,
63
+ workDayId,
64
+ action: "noop",
65
+ reason: `${reason}:no_runners`,
66
+ metadata: { service: "workdayManager" }
67
+ }).catch(() => null);
68
+ return;
69
+ }
70
+ for (const runner of runners) {
71
+ await sdk.recordRunnerScaleDecision({
72
+ projectId: config.projectId,
73
+ environment: config.environment,
74
+ workDayId,
75
+ runnerId: typeof runner.runnerId === "string" ? runner.runnerId : null,
76
+ runnerServiceName: typeof runner.runnerServiceName === "string" ? runner.runnerServiceName : null,
77
+ action,
78
+ reason,
79
+ metadata: { service: "workdayManager" }
80
+ }).catch(() => null);
81
+ }
82
+ }
83
+ async function runScheduledWorkdayManager(options = {}) {
84
+ const sdk = options.sdk ?? createServiceSdk();
85
+ const config = options.config ?? {
86
+ ...resolveManagerServiceConfig(),
87
+ mode: "reconcile"
88
+ };
89
+ let now = options.now ?? /* @__PURE__ */ new Date();
90
+ const policyEnvelope = await sdk.getWorkPolicy(config.projectId, config.environment);
91
+ const policy = policyEnvelope.payload ?? await sdk.upsertWorkPolicy({
92
+ projectId: config.projectId,
93
+ environment: config.environment,
94
+ schedule: config.defaultSchedule,
95
+ enabled: true,
96
+ startCron: process.env.TREESEED_WORKDAY_START_CRON?.trim() || "0 9 * * 1-5",
97
+ durationMinutes: Number(process.env.TREESEED_WORKDAY_DURATION_MINUTES ?? 480),
98
+ maxRunners: config.autoscale.maxWorkers,
99
+ maxWorkersPerRunner: Number(process.env.TREESEED_RUNNER_MAX_LOCAL_WORKERS ?? 4),
100
+ dailyCreditBudget: config.dailyTaskCreditBudget,
101
+ closeoutGraceMinutes: Number(process.env.TREESEED_WORKDAY_CLOSEOUT_GRACE_MINUTES ?? 15),
102
+ dailyTaskCreditBudget: config.dailyTaskCreditBudget,
103
+ maxQueuedTasks: config.maxQueuedTasks,
104
+ maxQueuedCredits: config.maxQueuedCredits,
105
+ autoscale: config.autoscale,
106
+ creditWeights: config.creditWeights,
107
+ metadata: { managedBy: "workdayManager" }
108
+ }).then((created) => created.payload);
109
+ if (!policy?.enabled) {
110
+ return { ok: true, skipped: true, reason: "workday_policy_disabled" };
111
+ }
112
+ const requests = typeof sdk.listWorkdayRequests === "function" ? (await sdk.listWorkdayRequests(config.projectId, config.environment, "pending").catch(() => ({ payload: [] }))).payload ?? [] : [];
113
+ const oneOffRunRequested = requests.some((entry) => entry.type === "one_off_run");
114
+ const initial = await runManagerCycle({ sdk, config, now });
115
+ const workDay = initial.workDay;
116
+ if (!workDay && !oneOffRunRequested) {
117
+ return { ok: true, skipped: true, reason: "no_workday_started", initial };
118
+ }
119
+ const lease = await acquireLease(sdk, config, workDay ? String(workDay.id ?? "") : null);
120
+ if (!lease.payload) {
121
+ return { ok: true, skipped: true, reason: "healthy_manager_lease_exists" };
122
+ }
123
+ const leaseId = String(lease.payload.id ?? "");
124
+ let latest = initial;
125
+ try {
126
+ let activeWorkDay = workDay;
127
+ let closeDeadline = workdayCloseDeadline(activeWorkDay, Number(policy.durationMinutes ?? 480), now);
128
+ while (Date.now() < closeDeadline.valueOf()) {
129
+ await heartbeatLease(sdk, config, leaseId, activeWorkDay ? String(activeWorkDay.id ?? "") : null);
130
+ await sleep(config.pollIntervalMs);
131
+ now = /* @__PURE__ */ new Date();
132
+ latest = await runManagerCycle({ sdk, config, now });
133
+ activeWorkDay = latest.workDay;
134
+ closeDeadline = workdayCloseDeadline(activeWorkDay, Number(policy.durationMinutes ?? 480), now);
135
+ }
136
+ const workDayId = activeWorkDay ? String(activeWorkDay.id ?? "") : null;
137
+ await recordRunnerCloseDecision(sdk, config, workDayId, "drain", "workday_closeout");
138
+ const graceMs = Number(policy.closeoutGraceMinutes ?? 15) * 60 * 1e3;
139
+ const graceEnd = Date.now() + graceMs;
140
+ while (Date.now() < graceEnd) {
141
+ const result = await runManagerCycle({ sdk, config, now: /* @__PURE__ */ new Date() });
142
+ latest = result;
143
+ if (Number(result.queuedCount ?? 0) === 0 && Number(result.activeLeases ?? 0) === 0) {
144
+ break;
145
+ }
146
+ await sleep(config.pollIntervalMs);
147
+ }
148
+ const closed = await runManagerAction({ sdk, config, mode: "close-workday" });
149
+ await recordRunnerCloseDecision(sdk, config, workDayId, "sleep", "workday_closed");
150
+ return { ok: true, initial, latest, closed };
151
+ } finally {
152
+ await releaseLease(sdk, config.managerId, leaseId);
153
+ }
154
+ }
155
+ const currentFile = fileURLToPath(import.meta.url);
156
+ const entryFile = process.argv[1] ?? "";
157
+ if (entryFile === currentFile) {
158
+ process.stdout.write(`${JSON.stringify(await runScheduledWorkdayManager(), null, 2)}
159
+ `);
160
+ }
161
+ export {
162
+ runScheduledWorkdayManager
163
+ };