@treeseed/sdk 0.6.39 → 0.6.40

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 (44) hide show
  1. package/dist/capacity.d.ts +53 -0
  2. package/dist/capacity.js +100 -0
  3. package/dist/control-plane-client.d.ts +41 -1
  4. package/dist/control-plane-client.js +154 -0
  5. package/dist/control-plane.d.ts +6 -1
  6. package/dist/control-plane.js +39 -2
  7. package/dist/d1-store.d.ts +63 -1
  8. package/dist/d1-store.js +190 -1
  9. package/dist/index.d.ts +3 -1
  10. package/dist/index.js +12 -0
  11. package/dist/operations/services/config-runtime.js +2 -2
  12. package/dist/operations/services/deploy.js +3 -2
  13. package/dist/operations/services/knowledge-coop-launch.js +5 -28
  14. package/dist/operations/services/package-reference-policy.d.ts +68 -0
  15. package/dist/operations/services/package-reference-policy.js +135 -0
  16. package/dist/operations/services/project-platform.d.ts +14 -0
  17. package/dist/operations/services/project-platform.js +3 -2
  18. package/dist/operations/services/railway-api.d.ts +33 -0
  19. package/dist/operations/services/railway-api.js +273 -0
  20. package/dist/operations/services/railway-deploy.d.ts +22 -0
  21. package/dist/operations/services/railway-deploy.js +81 -19
  22. package/dist/operations/services/release-candidate.d.ts +2 -0
  23. package/dist/operations/services/release-candidate.js +28 -0
  24. package/dist/operations/services/runtime-tools.js +1 -1
  25. package/dist/operations-registry.js +1 -0
  26. package/dist/reconcile/bootstrap-systems.js +1 -1
  27. package/dist/reconcile/builtin-adapters.js +5 -9
  28. package/dist/reconcile/contracts.d.ts +1 -1
  29. package/dist/reconcile/desired-state.js +9 -17
  30. package/dist/reconcile/state.js +4 -4
  31. package/dist/reconcile/units.js +4 -8
  32. package/dist/sdk-types.d.ts +566 -3
  33. package/dist/sdk.d.ts +12 -1
  34. package/dist/sdk.js +44 -0
  35. package/dist/stores/operational-store.d.ts +12 -1
  36. package/dist/stores/operational-store.js +283 -5
  37. package/dist/treeseed/template-catalog/templates/starter-basic/template/treeseed.site.yaml +5 -24
  38. package/dist/types/agents.d.ts +27 -0
  39. package/dist/workflow/operations.d.ts +94 -2
  40. package/dist/workflow/operations.js +90 -32
  41. package/dist/workflow-state.js +3 -5
  42. package/dist/workflow.d.ts +8 -1
  43. package/dist/workflow.js +6 -0
  44. package/package.json +5 -1
package/dist/sdk.js CHANGED
@@ -265,6 +265,50 @@ class AgentSdk {
265
265
  const payload = await this.database.upsertWorkPolicy(request);
266
266
  return this.envelope("work_day", "update", payload);
267
267
  }
268
+ async createWorkdayRequest(request) {
269
+ const payload = await this.database.createWorkdayRequest(request);
270
+ return this.envelope("workday_request", "create", payload);
271
+ }
272
+ async listWorkdayRequests(projectId, environment, state) {
273
+ const payload = await this.database.listWorkdayRequests(projectId, environment, state);
274
+ return this.envelope("workday_request", "search", payload, { count: payload.length });
275
+ }
276
+ async claimWorkdayManagerLease(request) {
277
+ const payload = await this.database.claimWorkdayManagerLease(request);
278
+ return this.envelope("workday_manager_lease", "claim", payload);
279
+ }
280
+ async releaseWorkdayManagerLease(request) {
281
+ const payload = await this.database.releaseWorkdayManagerLease(request);
282
+ return this.envelope("workday_manager_lease", "release", payload);
283
+ }
284
+ async recordWorkerRunner(request) {
285
+ const payload = await this.database.recordWorkerRunner(request);
286
+ return this.envelope("worker_runner", "update", payload);
287
+ }
288
+ async listWorkerRunners(projectId, environment) {
289
+ const payload = await this.database.listWorkerRunners(projectId, environment);
290
+ return this.envelope("worker_runner", "search", payload, { count: payload.length });
291
+ }
292
+ async recordRepositoryClaim(request) {
293
+ const payload = await this.database.recordRepositoryClaim(request);
294
+ return this.envelope("repository_claim", "update", payload);
295
+ }
296
+ async listRepositoryClaims(projectId, repositoryId) {
297
+ const payload = await this.database.listRepositoryClaims(projectId, repositoryId);
298
+ return this.envelope("repository_claim", "search", payload, { count: payload.length });
299
+ }
300
+ async recordRunnerScaleDecision(request) {
301
+ const payload = await this.database.recordRunnerScaleDecision(request);
302
+ return this.envelope("runner_scale_decision", "create", payload);
303
+ }
304
+ async listRunnerScaleDecisions(projectId, environment, workDayId) {
305
+ const payload = await this.database.listRunnerScaleDecisions(projectId, environment, workDayId);
306
+ return this.envelope("runner_scale_decision", "search", payload, { count: payload.length });
307
+ }
308
+ async updateWorkDayGraph(request) {
309
+ const payload = await this.database.updateWorkDayGraph(request);
310
+ return this.envelope("work_day", "update", payload);
311
+ }
268
312
  async listPriorityOverrides(projectId) {
269
313
  const payload = await this.database.listPriorityOverrides(projectId);
270
314
  return this.envelope("task", "search", payload, { count: payload.length });
@@ -1,4 +1,4 @@
1
- import type { PrioritySnapshot, SdkCreatePrioritySnapshotRequest, SdkAppendTaskEventRequest, SdkClaimTaskRequest, SdkCloseWorkDayRequest, SdkCompleteTaskRequest, SdkCreateReportRequest, SdkCreateTaskRequest, SdkFailTaskRequest, SdkGraphRunEntity, SdkPriorityOverrideRequest, SdkReportEntity, SdkRecordScaleDecisionRequest, SdkRecordTaskCreditsRequest, SdkStartWorkDayRequest, SdkTaskEntity, SdkTaskEventEntity, SdkTaskOutputEntity, SdkTaskProgressRequest, SdkTaskSearchRequest, SdkUpsertWorkPolicyRequest, SdkWorkDayEntity, ScaleDecision, TaskCreditLedgerEntry, WorkdayPolicy } from '../sdk-types.ts';
1
+ import type { PrioritySnapshot, SdkCreatePrioritySnapshotRequest, SdkAppendTaskEventRequest, SdkClaimTaskRequest, SdkCloseWorkDayRequest, SdkCompleteTaskRequest, SdkCreateReportRequest, SdkCreateTaskRequest, SdkFailTaskRequest, SdkGraphRunEntity, SdkClaimWorkdayManagerLeaseRequest, SdkCreateWorkdayRequest, SdkPriorityOverrideRequest, SdkRecordRepositoryClaimRequest, SdkRecordRunnerScaleDecisionRequest, SdkReportEntity, SdkRecordScaleDecisionRequest, SdkRecordWorkerRunnerRequest, SdkRecordTaskCreditsRequest, SdkReleaseWorkdayManagerLeaseRequest, SdkStartWorkDayRequest, SdkTaskEntity, SdkTaskEventEntity, SdkTaskOutputEntity, SdkTaskProgressRequest, SdkTaskSearchRequest, SdkUpsertWorkPolicyRequest, SdkUpdateWorkDayGraphRequest, SdkWorkDayEntity, RepositoryClaim, RunnerScaleDecision, ScaleDecision, TaskCreditLedgerEntry, WorkdayPolicy, WorkdayManagerLease, WorkdayRequest, WorkerRunner } from '../sdk-types.ts';
2
2
  import { SqliteStoreBase, type DatabaseRow } from './helpers.ts';
3
3
  export declare class OperationalStore extends SqliteStoreBase {
4
4
  getWorkDay(id: string): Promise<SdkWorkDayEntity | null>;
@@ -23,6 +23,17 @@ export declare class OperationalStore extends SqliteStoreBase {
23
23
  getReport(id: string): Promise<SdkReportEntity | null>;
24
24
  getWorkPolicy(projectId: string, environment?: string): Promise<WorkdayPolicy | null>;
25
25
  upsertWorkPolicy(request: SdkUpsertWorkPolicyRequest): Promise<WorkdayPolicy | null>;
26
+ createWorkdayRequest(request: SdkCreateWorkdayRequest): Promise<WorkdayRequest | null>;
27
+ listWorkdayRequests(projectId: string, environment: string, state?: string | null): Promise<WorkdayRequest[]>;
28
+ claimWorkdayManagerLease(request: SdkClaimWorkdayManagerLeaseRequest): Promise<WorkdayManagerLease | null>;
29
+ releaseWorkdayManagerLease(request: SdkReleaseWorkdayManagerLeaseRequest): Promise<WorkdayManagerLease | null>;
30
+ recordWorkerRunner(request: SdkRecordWorkerRunnerRequest): Promise<WorkerRunner | null>;
31
+ listWorkerRunners(projectId: string, environment: string): Promise<WorkerRunner[]>;
32
+ recordRepositoryClaim(request: SdkRecordRepositoryClaimRequest): Promise<RepositoryClaim | null>;
33
+ listRepositoryClaims(projectId: string, repositoryId?: string | null): Promise<RepositoryClaim[]>;
34
+ recordRunnerScaleDecision(request: SdkRecordRunnerScaleDecisionRequest): Promise<RunnerScaleDecision | null>;
35
+ listRunnerScaleDecisions(projectId: string, environment: string, workDayId?: string | null): Promise<RunnerScaleDecision[]>;
36
+ updateWorkDayGraph(request: SdkUpdateWorkDayGraphRequest): Promise<SdkWorkDayEntity | null>;
26
37
  listPriorityOverrides(projectId: string): Promise<DatabaseRow[]>;
27
38
  upsertPriorityOverride(request: SdkPriorityOverrideRequest): Promise<{
28
39
  id: string;
@@ -107,6 +107,9 @@ function normalizeAutoscale(value, fallback) {
107
107
  };
108
108
  }
109
109
  function workPolicyFromRow(row) {
110
+ const metadata = parseJsonValue(row.metadata_json, {});
111
+ const autoscale = normalizeAutoscale(parseJsonValue(row.autoscale_json, {}));
112
+ const dailyCreditBudget = Number(row.daily_credit_budget ?? row.daily_task_credit_budget ?? 0);
110
113
  return {
111
114
  projectId: String(row.project_id ?? ""),
112
115
  environment: String(row.environment ?? "local"),
@@ -114,12 +117,99 @@ function workPolicyFromRow(row) {
114
117
  timezone: "UTC",
115
118
  windows: []
116
119
  }),
117
- dailyTaskCreditBudget: Number(row.daily_task_credit_budget ?? 0),
120
+ enabled: row.enabled === void 0 || row.enabled === null ? metadata.enabled !== false : Number(row.enabled) !== 0,
121
+ startCron: String(row.start_cron ?? metadata.startCron ?? "0 9 * * 1-5"),
122
+ durationMinutes: Number(row.duration_minutes ?? metadata.durationMinutes ?? 480),
123
+ maxRunners: Number(row.max_runners ?? metadata.maxRunners ?? autoscale.maxWorkers ?? 1),
124
+ maxWorkersPerRunner: Number(row.max_workers_per_runner ?? metadata.maxWorkersPerRunner ?? 4),
125
+ dailyCreditBudget,
126
+ closeoutGraceMinutes: Number(row.closeout_grace_minutes ?? metadata.closeoutGraceMinutes ?? 15),
127
+ dailyTaskCreditBudget: dailyCreditBudget,
118
128
  maxQueuedTasks: Number(row.max_queued_tasks ?? 0),
119
129
  maxQueuedCredits: Number(row.max_queued_credits ?? 0),
120
- autoscale: normalizeAutoscale(parseJsonValue(row.autoscale_json, {})),
130
+ autoscale,
121
131
  creditWeights: parseJsonValue(row.credit_weights_json, []),
122
- metadata: parseJsonValue(row.metadata_json, {})
132
+ metadata
133
+ };
134
+ }
135
+ function workdayRequestFromRow(row) {
136
+ return {
137
+ id: String(row.id ?? ""),
138
+ projectId: String(row.project_id ?? ""),
139
+ environment: String(row.environment ?? "local"),
140
+ type: String(row.type ?? "one_off_run"),
141
+ state: String(row.state ?? "pending"),
142
+ workDayId: row.work_day_id === void 0 || row.work_day_id === null ? null : String(row.work_day_id),
143
+ requestedBy: row.requested_by === void 0 || row.requested_by === null ? null : String(row.requested_by),
144
+ reason: row.reason === void 0 || row.reason === null ? null : String(row.reason),
145
+ payload: parseJsonValue(row.payload_json, {}),
146
+ metadata: parseJsonValue(row.metadata_json, {}),
147
+ createdAt: String(row.created_at ?? nowIso()),
148
+ updatedAt: String(row.updated_at ?? nowIso())
149
+ };
150
+ }
151
+ function workdayManagerLeaseFromRow(row) {
152
+ return {
153
+ id: String(row.id ?? ""),
154
+ projectId: String(row.project_id ?? ""),
155
+ environment: String(row.environment ?? "local"),
156
+ workDayId: row.work_day_id === void 0 || row.work_day_id === null ? null : String(row.work_day_id),
157
+ managerId: String(row.manager_id ?? ""),
158
+ state: String(row.state ?? "active"),
159
+ heartbeatAt: String(row.heartbeat_at ?? nowIso()),
160
+ expiresAt: String(row.expires_at ?? nowIso()),
161
+ metadata: parseJsonValue(row.metadata_json, {}),
162
+ createdAt: String(row.created_at ?? nowIso()),
163
+ updatedAt: String(row.updated_at ?? nowIso())
164
+ };
165
+ }
166
+ function workerRunnerFromRow(row) {
167
+ return {
168
+ id: String(row.id ?? ""),
169
+ projectId: String(row.project_id ?? ""),
170
+ environment: String(row.environment ?? "local"),
171
+ runnerId: String(row.runner_id ?? ""),
172
+ runnerServiceName: String(row.runner_service_name ?? ""),
173
+ volumeIdentity: String(row.volume_identity ?? ""),
174
+ state: String(row.state ?? "active"),
175
+ maxLocalWorkers: Number(row.max_local_workers ?? 4),
176
+ activeLocalWorkers: Number(row.active_local_workers ?? 0),
177
+ availableCapacity: Number(row.available_capacity ?? 0),
178
+ lastHeartbeatAt: row.last_heartbeat_at === void 0 || row.last_heartbeat_at === null ? null : String(row.last_heartbeat_at),
179
+ claimedRepositoryIds: parseJsonValue(row.claimed_repository_ids_json, []),
180
+ metadata: parseJsonValue(row.metadata_json, {}),
181
+ createdAt: String(row.created_at ?? nowIso()),
182
+ updatedAt: String(row.updated_at ?? nowIso())
183
+ };
184
+ }
185
+ function repositoryClaimFromRow(row) {
186
+ return {
187
+ id: String(row.id ?? ""),
188
+ projectId: String(row.project_id ?? ""),
189
+ repositoryId: String(row.repository_id ?? ""),
190
+ runnerId: String(row.runner_id ?? ""),
191
+ runnerServiceName: String(row.runner_service_name ?? ""),
192
+ volumeIdentity: String(row.volume_identity ?? ""),
193
+ lastSeenCommit: row.last_seen_commit === void 0 || row.last_seen_commit === null ? null : String(row.last_seen_commit),
194
+ lastTaskAt: row.last_task_at === void 0 || row.last_task_at === null ? null : String(row.last_task_at),
195
+ claimState: String(row.claim_state ?? "active"),
196
+ metadata: parseJsonValue(row.metadata_json, {}),
197
+ createdAt: String(row.created_at ?? nowIso()),
198
+ updatedAt: String(row.updated_at ?? nowIso())
199
+ };
200
+ }
201
+ function runnerScaleDecisionFromRow(row) {
202
+ return {
203
+ id: String(row.id ?? ""),
204
+ projectId: String(row.project_id ?? ""),
205
+ environment: String(row.environment ?? "local"),
206
+ workDayId: row.work_day_id === void 0 || row.work_day_id === null ? null : String(row.work_day_id),
207
+ runnerId: row.runner_id === void 0 || row.runner_id === null ? null : String(row.runner_id),
208
+ runnerServiceName: row.runner_service_name === void 0 || row.runner_service_name === null ? null : String(row.runner_service_name),
209
+ action: String(row.action ?? "noop"),
210
+ reason: String(row.reason ?? "reconcile"),
211
+ metadata: parseJsonValue(row.metadata_json, {}),
212
+ createdAt: String(row.created_at ?? nowIso())
123
213
  };
124
214
  }
125
215
  function prioritySnapshotFromRow(row) {
@@ -356,14 +446,22 @@ class OperationalStore extends SqliteStoreBase {
356
446
  }
357
447
  async upsertWorkPolicy(request) {
358
448
  const timestamp = nowIso();
449
+ const dailyCreditBudget = Number(request.dailyCreditBudget ?? request.dailyTaskCreditBudget ?? 0);
359
450
  await this.execute(
360
451
  `INSERT OR REPLACE INTO work_policies (
361
- project_id, environment, schedule_json, daily_task_credit_budget, max_queued_tasks, max_queued_credits, autoscale_json, credit_weights_json, metadata_json, created_at, updated_at
452
+ project_id, environment, schedule_json, enabled, start_cron, duration_minutes, max_runners, max_workers_per_runner, daily_credit_budget, closeout_grace_minutes, daily_task_credit_budget, max_queued_tasks, max_queued_credits, autoscale_json, credit_weights_json, metadata_json, created_at, updated_at
362
453
  ) VALUES (
363
454
  ${toSqlValue(request.projectId)},
364
455
  ${toSqlValue(request.environment)},
365
456
  ${toSqlValue(json(request.schedule))},
366
- ${Number(request.dailyTaskCreditBudget ?? 0)},
457
+ ${request.enabled === false ? 0 : 1},
458
+ ${toSqlValue(request.startCron ?? "0 9 * * 1-5")},
459
+ ${Number(request.durationMinutes ?? 480)},
460
+ ${Number(request.maxRunners ?? request.autoscale.maxWorkers ?? 1)},
461
+ ${Number(request.maxWorkersPerRunner ?? 4)},
462
+ ${dailyCreditBudget},
463
+ ${Number(request.closeoutGraceMinutes ?? 15)},
464
+ ${dailyCreditBudget},
367
465
  ${Number(request.maxQueuedTasks ?? 0)},
368
466
  ${Number(request.maxQueuedCredits ?? 0)},
369
467
  ${toSqlValue(json(request.autoscale))},
@@ -375,6 +473,186 @@ class OperationalStore extends SqliteStoreBase {
375
473
  );
376
474
  return this.getWorkPolicy(request.projectId, request.environment);
377
475
  }
476
+ async createWorkdayRequest(request) {
477
+ const id = request.id ?? crypto.randomUUID();
478
+ const timestamp = nowIso();
479
+ await this.execute(
480
+ `INSERT INTO workday_requests (
481
+ id, project_id, environment, type, state, work_day_id, requested_by, reason, payload_json, metadata_json, created_at, updated_at
482
+ ) VALUES (
483
+ ${toSqlValue(id)},
484
+ ${toSqlValue(request.projectId)},
485
+ ${toSqlValue(request.environment)},
486
+ ${toSqlValue(request.type)},
487
+ ${toSqlValue(request.state ?? "pending")},
488
+ ${toSqlValue(request.workDayId ?? null)},
489
+ ${toSqlValue(request.requestedBy ?? null)},
490
+ ${toSqlValue(request.reason ?? null)},
491
+ ${toSqlValue(json(request.payload ?? {}))},
492
+ ${toSqlValue(json(request.metadata ?? {}))},
493
+ ${toSqlValue(timestamp)},
494
+ ${toSqlValue(timestamp)}
495
+ )`
496
+ );
497
+ const row = await this.selectFirst(`SELECT * FROM workday_requests WHERE id = ${toSqlValue(id)} LIMIT 1`);
498
+ return row ? workdayRequestFromRow(row) : null;
499
+ }
500
+ async listWorkdayRequests(projectId, environment, state) {
501
+ if (!await this.tableExists("workday_requests")) return [];
502
+ const rows = await this.selectAll(
503
+ `SELECT * FROM workday_requests WHERE project_id = ${toSqlValue(projectId)} AND environment = ${toSqlValue(environment)}${state ? ` AND state = ${toSqlValue(state)}` : ""} ORDER BY created_at ASC`
504
+ );
505
+ return rows.map(workdayRequestFromRow);
506
+ }
507
+ async claimWorkdayManagerLease(request) {
508
+ const timestamp = request.now ?? nowIso();
509
+ const active = await this.selectFirst(
510
+ `SELECT * FROM workday_manager_leases WHERE project_id = ${toSqlValue(request.projectId)} AND environment = ${toSqlValue(request.environment)} AND state = 'active' ORDER BY updated_at DESC LIMIT 1`
511
+ );
512
+ if (active && String(active.manager_id ?? "") !== request.managerId) {
513
+ const heartbeatMs = Date.parse(String(active.heartbeat_at ?? ""));
514
+ const nowMs = Date.parse(timestamp);
515
+ const staleAfterMs = (request.staleAfterSeconds ?? request.ttlSeconds) * 1e3;
516
+ if (Number.isFinite(heartbeatMs) && Number.isFinite(nowMs) && nowMs - heartbeatMs <= staleAfterMs) {
517
+ return null;
518
+ }
519
+ }
520
+ const id = active ? String(active.id) : request.id ?? crypto.randomUUID();
521
+ const expiresAt = new Date(Date.parse(timestamp) + request.ttlSeconds * 1e3).toISOString();
522
+ await this.execute(
523
+ `INSERT OR REPLACE INTO workday_manager_leases (
524
+ id, project_id, environment, work_day_id, manager_id, state, heartbeat_at, expires_at, metadata_json, created_at, updated_at
525
+ ) VALUES (
526
+ ${toSqlValue(id)},
527
+ ${toSqlValue(request.projectId)},
528
+ ${toSqlValue(request.environment)},
529
+ ${toSqlValue(request.workDayId ?? active?.work_day_id ?? null)},
530
+ ${toSqlValue(request.managerId)},
531
+ 'active',
532
+ ${toSqlValue(timestamp)},
533
+ ${toSqlValue(expiresAt)},
534
+ ${toSqlValue(json(request.metadata ?? parseJsonValue(active?.metadata_json, {})))},
535
+ COALESCE((SELECT created_at FROM workday_manager_leases WHERE id = ${toSqlValue(id)}), ${toSqlValue(timestamp)}),
536
+ ${toSqlValue(timestamp)}
537
+ )`
538
+ );
539
+ const row = await this.selectFirst(`SELECT * FROM workday_manager_leases WHERE id = ${toSqlValue(id)} LIMIT 1`);
540
+ return row ? workdayManagerLeaseFromRow(row) : null;
541
+ }
542
+ async releaseWorkdayManagerLease(request) {
543
+ const timestamp = nowIso();
544
+ await this.execute(
545
+ `UPDATE workday_manager_leases SET state = 'released', updated_at = ${toSqlValue(timestamp)} WHERE id = ${toSqlValue(request.id)} AND manager_id = ${toSqlValue(request.managerId)}`
546
+ );
547
+ const row = await this.selectFirst(`SELECT * FROM workday_manager_leases WHERE id = ${toSqlValue(request.id)} LIMIT 1`);
548
+ return row ? workdayManagerLeaseFromRow(row) : null;
549
+ }
550
+ async recordWorkerRunner(request) {
551
+ const timestamp = nowIso();
552
+ const id = request.id ?? `${request.projectId}:${request.environment}:${request.runnerId}`;
553
+ const maxLocalWorkers = Number(request.maxLocalWorkers ?? 4);
554
+ const activeLocalWorkers = Number(request.activeLocalWorkers ?? 0);
555
+ await this.execute(
556
+ `INSERT OR REPLACE INTO worker_runners (
557
+ id, project_id, environment, runner_id, runner_service_name, volume_identity, state, max_local_workers, active_local_workers, available_capacity, last_heartbeat_at, claimed_repository_ids_json, metadata_json, created_at, updated_at
558
+ ) VALUES (
559
+ ${toSqlValue(id)},
560
+ ${toSqlValue(request.projectId)},
561
+ ${toSqlValue(request.environment)},
562
+ ${toSqlValue(request.runnerId)},
563
+ ${toSqlValue(request.runnerServiceName)},
564
+ ${toSqlValue(request.volumeIdentity)},
565
+ ${toSqlValue(request.state ?? "active")},
566
+ ${maxLocalWorkers},
567
+ ${activeLocalWorkers},
568
+ ${Math.max(0, maxLocalWorkers - activeLocalWorkers)},
569
+ ${toSqlValue(timestamp)},
570
+ ${toSqlValue(json(request.claimedRepositoryIds ?? []))},
571
+ ${toSqlValue(json(request.metadata ?? {}))},
572
+ COALESCE((SELECT created_at FROM worker_runners WHERE id = ${toSqlValue(id)}), ${toSqlValue(timestamp)}),
573
+ ${toSqlValue(timestamp)}
574
+ )`
575
+ );
576
+ const row = await this.selectFirst(`SELECT * FROM worker_runners WHERE id = ${toSqlValue(id)} LIMIT 1`);
577
+ return row ? workerRunnerFromRow(row) : null;
578
+ }
579
+ async listWorkerRunners(projectId, environment) {
580
+ if (!await this.tableExists("worker_runners")) return [];
581
+ const rows = await this.selectAll(
582
+ `SELECT * FROM worker_runners WHERE project_id = ${toSqlValue(projectId)} AND environment = ${toSqlValue(environment)} ORDER BY runner_id ASC`
583
+ );
584
+ return rows.map(workerRunnerFromRow);
585
+ }
586
+ async recordRepositoryClaim(request) {
587
+ const timestamp = nowIso();
588
+ const id = request.id ?? `${request.projectId}:${request.repositoryId}:${request.runnerId}`;
589
+ await this.execute(
590
+ `INSERT OR REPLACE INTO repository_claims (
591
+ id, project_id, repository_id, runner_id, runner_service_name, volume_identity, last_seen_commit, last_task_at, claim_state, metadata_json, created_at, updated_at
592
+ ) VALUES (
593
+ ${toSqlValue(id)},
594
+ ${toSqlValue(request.projectId)},
595
+ ${toSqlValue(request.repositoryId)},
596
+ ${toSqlValue(request.runnerId)},
597
+ ${toSqlValue(request.runnerServiceName)},
598
+ ${toSqlValue(request.volumeIdentity)},
599
+ ${toSqlValue(request.lastSeenCommit ?? null)},
600
+ ${toSqlValue(request.lastTaskAt ?? timestamp)},
601
+ ${toSqlValue(request.claimState ?? "active")},
602
+ ${toSqlValue(json(request.metadata ?? {}))},
603
+ COALESCE((SELECT created_at FROM repository_claims WHERE id = ${toSqlValue(id)}), ${toSqlValue(timestamp)}),
604
+ ${toSqlValue(timestamp)}
605
+ )`
606
+ );
607
+ const row = await this.selectFirst(`SELECT * FROM repository_claims WHERE id = ${toSqlValue(id)} LIMIT 1`);
608
+ return row ? repositoryClaimFromRow(row) : null;
609
+ }
610
+ async listRepositoryClaims(projectId, repositoryId) {
611
+ if (!await this.tableExists("repository_claims")) return [];
612
+ const rows = await this.selectAll(
613
+ `SELECT * FROM repository_claims WHERE project_id = ${toSqlValue(projectId)}${repositoryId ? ` AND repository_id = ${toSqlValue(repositoryId)}` : ""} ORDER BY updated_at DESC`
614
+ );
615
+ return rows.map(repositoryClaimFromRow);
616
+ }
617
+ async recordRunnerScaleDecision(request) {
618
+ const id = request.id ?? crypto.randomUUID();
619
+ const timestamp = nowIso();
620
+ await this.execute(
621
+ `INSERT INTO runner_scale_decisions (
622
+ id, project_id, environment, work_day_id, runner_id, runner_service_name, action, reason, metadata_json, created_at
623
+ ) VALUES (
624
+ ${toSqlValue(id)},
625
+ ${toSqlValue(request.projectId)},
626
+ ${toSqlValue(request.environment)},
627
+ ${toSqlValue(request.workDayId ?? null)},
628
+ ${toSqlValue(request.runnerId ?? null)},
629
+ ${toSqlValue(request.runnerServiceName ?? null)},
630
+ ${toSqlValue(request.action)},
631
+ ${toSqlValue(request.reason)},
632
+ ${toSqlValue(json(request.metadata ?? {}))},
633
+ ${toSqlValue(timestamp)}
634
+ )`
635
+ );
636
+ const row = await this.selectFirst(`SELECT * FROM runner_scale_decisions WHERE id = ${toSqlValue(id)} LIMIT 1`);
637
+ return row ? runnerScaleDecisionFromRow(row) : null;
638
+ }
639
+ async listRunnerScaleDecisions(projectId, environment, workDayId) {
640
+ if (!await this.tableExists("runner_scale_decisions")) return [];
641
+ const rows = await this.selectAll(
642
+ `SELECT * FROM runner_scale_decisions WHERE project_id = ${toSqlValue(projectId)} AND environment = ${toSqlValue(environment)}${workDayId ? ` AND work_day_id = ${toSqlValue(workDayId)}` : ""} ORDER BY created_at DESC`
643
+ );
644
+ return rows.map(runnerScaleDecisionFromRow);
645
+ }
646
+ async updateWorkDayGraph(request) {
647
+ const existing = await this.getWorkDay(request.id);
648
+ if (!existing) return null;
649
+ const currentSummary = parseJsonValue(existing.summaryJson, {});
650
+ const timestamp = nowIso();
651
+ await this.execute(
652
+ `UPDATE work_days SET graph_version = ${toSqlValue(request.graphVersion)}, summary_json = ${toSqlValue(json({ ...currentSummary, ...request.summaryPatch ?? {} }))}, updated_at = ${toSqlValue(timestamp)} WHERE id = ${toSqlValue(request.id)}`
653
+ );
654
+ return this.getWorkDay(request.id);
655
+ }
378
656
  async listPriorityOverrides(projectId) {
379
657
  if (!await this.tableExists("priority_overrides")) {
380
658
  return [];
@@ -50,41 +50,22 @@ services:
50
50
  environments:
51
51
  local:
52
52
  baseUrl: http://127.0.0.1:3000
53
- manager:
53
+ workdayManager:
54
54
  enabled: true
55
55
  provider: railway
56
56
  railway:
57
- serviceName: __SITE_SLUG__-manager
57
+ serviceName: __SITE_SLUG__-workday-manager
58
58
  rootDir: .
59
59
  buildCommand: npm run build
60
- startCommand: node ./node_modules/@treeseed/core/dist/services/manager.js
61
- schedule: '*/5 * * * *'
62
- worker:
63
- enabled: true
64
- provider: railway
65
- railway:
66
- serviceName: __SITE_SLUG__-worker
67
- rootDir: .
68
- buildCommand: npm run build
69
- startCommand: node ./node_modules/@treeseed/core/dist/services/worker.js
70
- workdayStart:
71
- enabled: true
72
- provider: railway
73
- railway:
74
- serviceName: __SITE_SLUG__-workday-start
75
- rootDir: .
76
- buildCommand: npm run build
77
- startCommand: node ./node_modules/@treeseed/core/dist/services/workday-start.js
60
+ startCommand: node ./node_modules/@treeseed/core/dist/services/workday-manager.js
78
61
  schedule: '0 9 * * 1-5'
79
- workdayReport:
62
+ workerRunner:
80
63
  enabled: true
81
64
  provider: railway
82
65
  railway:
83
- serviceName: __SITE_SLUG__-workday-report
84
66
  rootDir: .
85
67
  buildCommand: npm run build
86
- startCommand: node ./node_modules/@treeseed/core/dist/services/workday-report.js
87
- schedule: '5 17 * * 1-5'
68
+ startCommand: node ./node_modules/@treeseed/core/dist/services/worker.js
88
69
  plugins:
89
70
  - package: '@treeseed/core/plugin-default'
90
71
  providers:
@@ -33,6 +33,33 @@ export interface AgentExecutionConfig {
33
33
  leaseSeconds: number;
34
34
  retryLimit: number;
35
35
  branchPrefix: string;
36
+ providerProfile?: AgentProviderProfile;
37
+ }
38
+ export type AgentProviderFallbackPolicy = 'allow_substitution' | 'require_same_model_class' | 'fail_if_unavailable' | 'ask_for_approval';
39
+ export interface AgentProviderLanePreference {
40
+ providerId?: string;
41
+ provider?: string;
42
+ laneId?: string;
43
+ model?: string;
44
+ modelClass?: string;
45
+ weight: number;
46
+ reason?: string;
47
+ }
48
+ export interface AgentProviderFallback {
49
+ providerId?: string;
50
+ provider?: string;
51
+ laneId?: string;
52
+ model?: string;
53
+ modelClass?: string;
54
+ maxQualityPenalty?: number;
55
+ }
56
+ export interface AgentProviderProfile {
57
+ requiredCapabilities: string[];
58
+ preferredLanes: AgentProviderLanePreference[];
59
+ acceptableFallbacks: AgentProviderFallback[];
60
+ disallowedProviders?: string[];
61
+ disallowedRegions?: string[];
62
+ fallbackPolicy: AgentProviderFallbackPolicy;
36
63
  }
37
64
  export interface AgentTriggerPolicy {
38
65
  maxRunsPerCycle?: number;
@@ -1,8 +1,9 @@
1
1
  import { type ReleaseCandidateReport } from '../operations/services/release-candidate.ts';
2
+ import { type DevTagBranchScope } from '../operations/services/package-reference-policy.ts';
2
3
  import { resolveTreeseedWorkflowState, type TreeseedWorkflowStatusOptions } from '../workflow-state.ts';
3
4
  import { type TreeseedWorkflowRunCommand, type TreeseedWorkflowRunJournal } from './runs.ts';
4
5
  import { type TreeseedWorkflowMode } from './session.ts';
5
- import type { TreeseedCloseInput, TreeseedCiInput, TreeseedConfigInput, TreeseedDestroyInput, TreeseedExportInput, TreeseedReleaseInput, TreeseedRecoverInput, TreeseedResumeInput, TreeseedSaveInput, TreeseedStageInput, TreeseedSwitchInput, TreeseedTaskBranchMetadata, TreeseedWorkflowContext, TreeseedWorkflowDevInput, TreeseedWorkflowOperationId, TreeseedWorkflowResult, TreeseedWorkflowWorktreeMode } from '../workflow.ts';
6
+ import type { TreeseedCloseInput, TreeseedCiInput, TreeseedConfigInput, TreeseedDestroyInput, TreeseedExportInput, TreeseedReleaseInput, TreeseedRecoverInput, TreeseedResumeInput, TreeseedSaveInput, TreeseedStageInput, TreeseedSwitchInput, TreeseedTagsCleanupInput, TreeseedTaskBranchMetadata, TreeseedWorkflowContext, TreeseedWorkflowDevInput, TreeseedWorkflowOperationId, TreeseedWorkflowResult, TreeseedWorkflowWorktreeMode } from '../workflow.ts';
6
7
  type WorkflowWrite = NonNullable<TreeseedWorkflowContext['write']>;
7
8
  type WorkflowStatePayload = ReturnType<typeof resolveTreeseedWorkflowState>;
8
9
  export type TreeseedWorkflowErrorCode = 'validation_failed' | 'merge_conflict' | 'missing_runtime_auth' | 'deployment_timeout' | 'confirmation_required' | 'unsupported_transport' | 'unsupported_state' | 'workflow_locked' | 'resume_unavailable' | 'workflow_contract_missing' | 'github_workflow_failed' | 'github_auth_unavailable';
@@ -796,6 +797,54 @@ export declare function workflowStage(helpers: WorkflowOperationHelpers, input:
796
797
  } & {
797
798
  finalState?: WorkflowStatePayload;
798
799
  }>>;
800
+ export declare function workflowTagsCleanup(helpers: WorkflowOperationHelpers, input?: TreeseedTagsCleanupInput): Promise<TreeseedWorkflowResult<{
801
+ mode: string;
802
+ status: string;
803
+ branchScope: DevTagBranchScope;
804
+ includePackages: string[];
805
+ repos: ({
806
+ status: string;
807
+ packageName: string;
808
+ currentVersion: string;
809
+ branchScope: DevTagBranchScope;
810
+ candidates: {
811
+ tagName: string;
812
+ reason: string;
813
+ branch: string | null | undefined;
814
+ branchSlug: string | undefined;
815
+ version: string | null | undefined;
816
+ }[];
817
+ candidateCount: number;
818
+ cleaned: string[];
819
+ cleanedCount: number;
820
+ skipped: {
821
+ tagName: string;
822
+ reason: string;
823
+ }[];
824
+ skippedCount: number;
825
+ name: any;
826
+ path: string;
827
+ } | {
828
+ status: string;
829
+ candidateCount: number;
830
+ cleaned: string[];
831
+ cleanedCount: number;
832
+ skippedCount: number;
833
+ packageName: string;
834
+ currentVersion: string;
835
+ branchScope: DevTagBranchScope;
836
+ candidates: import("../operations/services/package-reference-policy.ts").StaleDevTagClassification[];
837
+ skipped: import("../operations/services/package-reference-policy.ts").StaleDevTagClassification[];
838
+ tags: import("../operations/services/package-reference-policy.ts").StaleDevTagClassification[];
839
+ name: any;
840
+ path: string;
841
+ })[];
842
+ candidateCount: number;
843
+ cleanedCount: number;
844
+ skippedCount: number;
845
+ } & {
846
+ finalState?: WorkflowStatePayload;
847
+ }>>;
799
848
  export declare function workflowRelease(helpers: WorkflowOperationHelpers, input: TreeseedReleaseInput): Promise<TreeseedWorkflowResult<{
800
849
  autoResumeCandidate: {
801
850
  runId: string;
@@ -960,8 +1009,51 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
960
1009
  })[];
961
1010
  releaseInstalls: Record<string, unknown>[];
962
1011
  devTagCleanup: {
1012
+ replacedDevReferenceCount: number;
1013
+ releasedPackageDevTagCount: number;
963
1014
  status: string;
964
- repos: Record<string, unknown>[];
1015
+ branchScope: DevTagBranchScope;
1016
+ includePackages: string[];
1017
+ repos: ({
1018
+ status: string;
1019
+ packageName: string;
1020
+ currentVersion: string;
1021
+ branchScope: DevTagBranchScope;
1022
+ candidates: {
1023
+ tagName: string;
1024
+ reason: string;
1025
+ branch: string | null | undefined;
1026
+ branchSlug: string | undefined;
1027
+ version: string | null | undefined;
1028
+ }[];
1029
+ candidateCount: number;
1030
+ cleaned: string[];
1031
+ cleanedCount: number;
1032
+ skipped: {
1033
+ tagName: string;
1034
+ reason: string;
1035
+ }[];
1036
+ skippedCount: number;
1037
+ name: any;
1038
+ path: string;
1039
+ } | {
1040
+ status: string;
1041
+ candidateCount: number;
1042
+ cleaned: string[];
1043
+ cleanedCount: number;
1044
+ skippedCount: number;
1045
+ packageName: string;
1046
+ currentVersion: string;
1047
+ branchScope: DevTagBranchScope;
1048
+ candidates: import("../operations/services/package-reference-policy.ts").StaleDevTagClassification[];
1049
+ skipped: import("../operations/services/package-reference-policy.ts").StaleDevTagClassification[];
1050
+ tags: import("../operations/services/package-reference-policy.ts").StaleDevTagClassification[];
1051
+ name: any;
1052
+ path: string;
1053
+ })[];
1054
+ candidateCount: number;
1055
+ cleanedCount: number;
1056
+ skippedCount: number;
965
1057
  } | {
966
1058
  status: string;
967
1059
  reason: string;