@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,436 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { AgentKernel } from "../agents/kernel/agent-kernel.js";
6
+ import { createControlPlaneReporter } from "@treeseed/sdk";
7
+ import { buildTaskContext, createQueueClient, createServiceSdk, resolveServiceRepoRoot, resolveWorkerConfig } from "./common.js";
8
+ function parseTaskPayload(task) {
9
+ const raw = typeof task?.payloadJson === "string" ? task.payloadJson : "{}";
10
+ try {
11
+ return JSON.parse(raw);
12
+ } catch {
13
+ return {};
14
+ }
15
+ }
16
+ function asRecord(value) {
17
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
18
+ }
19
+ function readCapacityEnvelope(payload) {
20
+ const envelope = asRecord(payload.capacityEnvelope);
21
+ return Object.keys(envelope).length > 0 ? envelope : null;
22
+ }
23
+ function runnerRepositoryPath(volumeRoot, repositoryId, taskId) {
24
+ const repositoryRoot = join(volumeRoot, "repositories", repositoryId);
25
+ return {
26
+ repositoryRoot,
27
+ bareGit: join(repositoryRoot, "bare.git"),
28
+ worktree: join(repositoryRoot, "worktrees", taskId)
29
+ };
30
+ }
31
+ function runnerComposedWorkspacePath(volumeRoot, hubId) {
32
+ const workspaceRoot = join(volumeRoot, "workspaces", hubId);
33
+ return {
34
+ root: workspaceRoot,
35
+ parent: join(workspaceRoot, "workspace-root"),
36
+ site: join(workspaceRoot, "site"),
37
+ content: join(workspaceRoot, "content"),
38
+ manifest: join(workspaceRoot, ".treeseed", "workspace.json")
39
+ };
40
+ }
41
+ async function ensureRunnerComposedWorkspace(volumeRoot, task) {
42
+ const payload = parseTaskPayload(task);
43
+ const workspace = asRecord(payload.workspace);
44
+ const hubId = String(workspace.hubId ?? payload.projectId ?? task.projectId ?? "").trim();
45
+ if (!hubId) return null;
46
+ const paths = runnerComposedWorkspacePath(volumeRoot, hubId);
47
+ await mkdir(paths.parent, { recursive: true });
48
+ await mkdir(paths.site, { recursive: true });
49
+ await mkdir(paths.content, { recursive: true });
50
+ await mkdir(join(paths.root, ".treeseed"), { recursive: true });
51
+ await writeFile(paths.manifest, `${JSON.stringify({
52
+ schemaVersion: 1,
53
+ kind: "treeseed_composed_workspace",
54
+ hubId,
55
+ softwareRepository: workspace.softwareRepository ?? null,
56
+ contentRepository: workspace.contentRepository ?? null,
57
+ parentRepository: workspace.parentRepository ?? null,
58
+ paths: {
59
+ workspaceRoot: paths.parent,
60
+ site: paths.site,
61
+ content: paths.content
62
+ },
63
+ allowedWriteTargets: Array.isArray(workspace.allowedWriteTargets) ? workspace.allowedWriteTargets : ["content"],
64
+ credentialSessionScopes: workspace.credentialSessionScopes ?? {
65
+ software: ["repository:software"],
66
+ content: ["repository:content"],
67
+ parentWorkspace: []
68
+ },
69
+ credentialScopes: workspace.credentialScopes ?? {
70
+ software: ["repository:software"],
71
+ content: ["repository:content"],
72
+ parentWorkspace: []
73
+ },
74
+ contentOverlay: workspace.contentOverlay ?? "src_content_when_present"
75
+ }, null, 2)}
76
+ `, "utf8");
77
+ return paths;
78
+ }
79
+ class WorkerPausedForApproval extends Error {
80
+ constructor(request) {
81
+ super(String(request.summary ?? request.title ?? "Task paused for approval."));
82
+ this.request = request;
83
+ }
84
+ request;
85
+ }
86
+ async function executeQueuedTask(options) {
87
+ const context = await buildTaskContext(options.sdk, options.taskId);
88
+ const task = context.task;
89
+ const payload = parseTaskPayload(task);
90
+ await ensureRunnerComposedWorkspace(options.volumeRoot, {
91
+ ...task ?? {},
92
+ payloadJson: JSON.stringify(payload)
93
+ });
94
+ const capacityEnvelope = readCapacityEnvelope(payload);
95
+ const explicitApproval = asRecord(payload.approvalRequest);
96
+ if (Object.keys(explicitApproval).length > 0 || capacityEnvelope?.maxCredits === 0) {
97
+ throw new WorkerPausedForApproval({
98
+ kind: String(explicitApproval.kind ?? "capacity_boundary"),
99
+ title: String(explicitApproval.title ?? "Task paused for approval"),
100
+ summary: String(explicitApproval.summary ?? "The task reached a boundary outside its approved execution envelope."),
101
+ severity: explicitApproval.severity ?? "medium",
102
+ workDayId: task?.workDayId ?? task?.work_day_id ?? null,
103
+ taskId: options.taskId,
104
+ options: Array.isArray(explicitApproval.options) ? explicitApproval.options : [],
105
+ recommendation: asRecord(explicitApproval.recommendation),
106
+ policySnapshot: {
107
+ capacityEnvelope,
108
+ ...asRecord(explicitApproval.policySnapshot)
109
+ }
110
+ });
111
+ }
112
+ const executionKind = typeof payload.executionKind === "string" ? payload.executionKind : null;
113
+ if (String(task?.type ?? task?.taskType ?? "") === "refresh_project_graph") {
114
+ const config = resolveWorkerConfig();
115
+ const projectId = typeof payload.projectId === "string" ? payload.projectId : config.projectId;
116
+ const repositoryId = typeof payload.repositoryId === "string" ? payload.repositoryId : projectId;
117
+ const paths = runnerRepositoryPath(config.volumeRoot, repositoryId, options.taskId);
118
+ await mkdir(paths.bareGit, { recursive: true });
119
+ await mkdir(paths.worktree, { recursive: true });
120
+ const graphRefresh = await options.sdk.refreshGraph();
121
+ const graphVersion = graphRefresh.snapshotRoot;
122
+ await options.sdk.create({
123
+ model: "graph_run",
124
+ data: {
125
+ id: `${options.taskId}:graph`,
126
+ workDayId: String(task?.workDayId ?? task?.work_day_id ?? ""),
127
+ corpusHash: graphVersion,
128
+ graphVersion,
129
+ statsJson: JSON.stringify(graphRefresh),
130
+ snapshotRef: graphVersion
131
+ },
132
+ actor: "worker"
133
+ });
134
+ if (typeof options.sdk.updateWorkDayGraph === "function") {
135
+ await options.sdk.updateWorkDayGraph({
136
+ id: String(task?.workDayId ?? task?.work_day_id ?? ""),
137
+ graphVersion,
138
+ summaryPatch: {
139
+ graphRefresh: {
140
+ state: "completed",
141
+ graphVersion,
142
+ snapshotRef: graphVersion,
143
+ runnerId: config.workerId
144
+ }
145
+ }
146
+ });
147
+ }
148
+ if (typeof options.sdk.recordRepositoryClaim === "function") {
149
+ await options.sdk.recordRepositoryClaim({
150
+ projectId,
151
+ repositoryId,
152
+ runnerId: config.workerId,
153
+ runnerServiceName: config.runnerServiceName,
154
+ volumeIdentity: config.volumeIdentity,
155
+ lastSeenCommit: typeof payload.commitSha === "string" ? payload.commitSha : null,
156
+ metadata: {
157
+ bareGit: paths.bareGit,
158
+ worktree: paths.worktree
159
+ }
160
+ });
161
+ }
162
+ return {
163
+ workerId: options.workerId,
164
+ queueAttempt: options.queueAttempt,
165
+ graphVersion,
166
+ snapshotRef: graphVersion,
167
+ repositoryId,
168
+ paths,
169
+ summary: {
170
+ status: "completed",
171
+ workerId: options.workerId,
172
+ summary: `Refreshed project graph ${graphVersion}`
173
+ }
174
+ };
175
+ }
176
+ if (executionKind === "workflow_dispatch" || executionKind === "sdk_dispatch") {
177
+ const namespace = typeof payload.namespace === "string" ? payload.namespace : "workflow";
178
+ const operation = typeof payload.operation === "string" ? payload.operation : "";
179
+ if (!operation) {
180
+ throw new Error(`Task ${options.taskId} does not define a dispatch operation.`);
181
+ }
182
+ const input = payload.input && typeof payload.input === "object" ? payload.input : {};
183
+ const result = await options.sdk.dispatch({
184
+ namespace,
185
+ operation,
186
+ input,
187
+ preferredMode: "prefer_local"
188
+ });
189
+ return {
190
+ workerId: options.workerId,
191
+ queueAttempt: options.queueAttempt,
192
+ executionKind,
193
+ namespace,
194
+ operation,
195
+ result,
196
+ summary: {
197
+ status: "completed",
198
+ workerId: options.workerId,
199
+ summary: `Executed ${namespace}:${operation}`
200
+ }
201
+ };
202
+ }
203
+ const agentSlug = typeof payload.agentSlug === "string" && payload.agentSlug ? payload.agentSlug : typeof context.agent?.slug === "string" && context.agent.slug ? context.agent.slug : typeof task?.agentId === "string" && task.agentId ? task.agentId : typeof task?.agent_id === "string" && task.agent_id ? task.agent_id : "";
204
+ if (!agentSlug) {
205
+ throw new Error(`Task ${options.taskId} does not resolve to an agent slug.`);
206
+ }
207
+ const invocation = payload.invocation && typeof payload.invocation === "object" ? payload.invocation : null;
208
+ const agentResult = await options.kernel.runAgent(agentSlug, invocation ? "manual" : "auto", invocation);
209
+ return {
210
+ workerId: options.workerId,
211
+ queueAttempt: options.queueAttempt,
212
+ agentSlug,
213
+ result: agentResult,
214
+ summary: {
215
+ status: agentResult.status,
216
+ workerId: options.workerId,
217
+ summary: agentResult.summary
218
+ },
219
+ capacityUsage: capacityEnvelope?.providerId && capacityEnvelope?.laneId ? {
220
+ capacityProviderId: capacityEnvelope.providerId,
221
+ laneId: capacityEnvelope.laneId,
222
+ reservationId: capacityEnvelope.reservationIds?.[0] ?? null,
223
+ credits: Number(payload.estimatedCredits ?? capacityEnvelope.maxCredits ?? 1),
224
+ source: "worker"
225
+ } : null
226
+ };
227
+ }
228
+ async function runWorkerCycle() {
229
+ const sdk = createServiceSdk();
230
+ const queue = createQueueClient();
231
+ const config = resolveWorkerConfig();
232
+ const kernel = new AgentKernel(sdk, resolveServiceRepoRoot());
233
+ if (typeof sdk.recordWorkerRunner === "function") {
234
+ await sdk.recordWorkerRunner({
235
+ projectId: config.projectId,
236
+ environment: config.environment,
237
+ runnerId: config.workerId,
238
+ runnerServiceName: config.runnerServiceName,
239
+ volumeIdentity: config.volumeIdentity,
240
+ state: "active",
241
+ maxLocalWorkers: config.maxLocalWorkers,
242
+ activeLocalWorkers: 0,
243
+ metadata: {
244
+ volumeRoot: config.volumeRoot
245
+ }
246
+ }).catch(() => null);
247
+ }
248
+ if (!queue) {
249
+ if (process.env.TREESEED_LOCAL_DEV_MODE?.trim()) {
250
+ return { ok: true, processed: 0, idle: true, reason: "queue_unconfigured" };
251
+ }
252
+ throw new Error("Worker requires CLOUDFLARE_ACCOUNT_ID, TREESEED_QUEUE_ID, and TREESEED_QUEUE_PULL_TOKEN.");
253
+ }
254
+ const pulled = await queue.pull({
255
+ batchSize: config.batchSize,
256
+ visibilityTimeoutMs: config.visibilityTimeoutMs
257
+ });
258
+ if (pulled.messages.length === 0) {
259
+ return { ok: true, processed: 0 };
260
+ }
261
+ const maxLocalWorkers = Number.isFinite(Number(config.maxLocalWorkers)) ? Math.max(1, Number(config.maxLocalWorkers)) : 1;
262
+ const selectedMessages = pulled.messages.slice(0, maxLocalWorkers);
263
+ const results = await Promise.all(selectedMessages.map(async (message) => {
264
+ try {
265
+ await sdk.claimTask({
266
+ id: message.body.taskId,
267
+ workerId: config.workerId,
268
+ leaseSeconds: config.leaseSeconds,
269
+ actor: "worker"
270
+ });
271
+ await sdk.recordTaskProgress({
272
+ id: message.body.taskId,
273
+ workerId: config.workerId,
274
+ state: "running",
275
+ appendEvent: {
276
+ kind: "worker_started",
277
+ data: { workerId: config.workerId, queueAttempt: message.attempts }
278
+ },
279
+ actor: "worker"
280
+ });
281
+ let output;
282
+ try {
283
+ output = await executeQueuedTask({
284
+ sdk,
285
+ kernel,
286
+ taskId: message.body.taskId,
287
+ workerId: config.workerId,
288
+ queueAttempt: message.attempts,
289
+ volumeRoot: config.volumeRoot
290
+ });
291
+ } catch (error) {
292
+ if (error instanceof WorkerPausedForApproval) {
293
+ const reporter = createControlPlaneReporter();
294
+ const context = await buildTaskContext(sdk, message.body.taskId);
295
+ const task = context.task;
296
+ const projectId = String(process.env.TREESEED_PROJECT_ID ?? "");
297
+ await reporter.createApprovalRequest({
298
+ projectId,
299
+ teamId: String(error.request.teamId ?? process.env.TREESEED_TEAM_ID ?? ""),
300
+ workDayId: typeof error.request.workDayId === "string" ? error.request.workDayId : String(task?.workDayId ?? task?.work_day_id ?? ""),
301
+ taskId: message.body.taskId,
302
+ kind: String(error.request.kind ?? "capacity_boundary"),
303
+ severity: error.request.severity === "high" || error.request.severity === "low" ? error.request.severity : "medium",
304
+ requestedByType: "worker",
305
+ requestedById: config.workerId,
306
+ title: String(error.request.title ?? "Task paused for approval"),
307
+ summary: String(error.request.summary ?? error.message),
308
+ options: Array.isArray(error.request.options) ? error.request.options : [],
309
+ recommendation: asRecord(error.request.recommendation),
310
+ policySnapshot: asRecord(error.request.policySnapshot)
311
+ }).catch(() => null);
312
+ await sdk.recordTaskProgress({
313
+ id: message.body.taskId,
314
+ workerId: config.workerId,
315
+ state: "paused_for_approval",
316
+ appendEvent: {
317
+ kind: "paused_for_approval",
318
+ data: error.request
319
+ },
320
+ actor: "worker"
321
+ });
322
+ await queue.ack([message.leaseId]);
323
+ return 1;
324
+ }
325
+ throw error;
326
+ }
327
+ await sdk.completeTask({
328
+ id: message.body.taskId,
329
+ output,
330
+ summary: output.summary,
331
+ actor: "worker"
332
+ });
333
+ if (output.capacityUsage?.capacityProviderId && output.capacityUsage?.laneId) {
334
+ const reporter = createControlPlaneReporter();
335
+ await reporter.reportCapacityUsage({
336
+ capacityProviderId: output.capacityUsage.capacityProviderId,
337
+ laneId: output.capacityUsage.laneId,
338
+ reservationId: output.capacityUsage.reservationId,
339
+ teamId: String(process.env.TREESEED_TEAM_ID ?? ""),
340
+ projectId: String(process.env.TREESEED_PROJECT_ID ?? ""),
341
+ workDayId: message.body.workDayId,
342
+ taskId: message.body.taskId,
343
+ phase: "consume",
344
+ credits: output.capacityUsage.credits,
345
+ source: "worker",
346
+ metadata: {
347
+ workerId: config.workerId,
348
+ queueAttempt: message.attempts
349
+ }
350
+ }).catch(() => null);
351
+ }
352
+ await queue.ack([message.leaseId]);
353
+ return 1;
354
+ } catch (error) {
355
+ const retryDelaySeconds = Math.min(300, Math.max(15, message.attempts * 30));
356
+ await sdk.failTask({
357
+ id: message.body.taskId,
358
+ errorMessage: error instanceof Error ? error.message : String(error),
359
+ retryable: true,
360
+ nextVisibleAt: new Date(Date.now() + retryDelaySeconds * 1e3).toISOString(),
361
+ actor: "worker"
362
+ }).catch(() => null);
363
+ await queue.retry([{ leaseId: message.leaseId, delaySeconds: retryDelaySeconds }]);
364
+ return 0;
365
+ }
366
+ }));
367
+ return { ok: true, processed: results.reduce((sum, value) => sum + value, 0) };
368
+ }
369
+ function shouldExitWorkerLoopAfterIdle(options) {
370
+ const idleExitMs = Number(options.idleExitMs ?? 0);
371
+ if (!Number.isFinite(idleExitMs) || idleExitMs <= 0) {
372
+ return false;
373
+ }
374
+ if (options.processed > 0 || options.idleSince === null) {
375
+ return false;
376
+ }
377
+ return options.now - options.idleSince >= idleExitMs;
378
+ }
379
+ async function recordWorkerLoopExitState(config) {
380
+ const sdk = createServiceSdk();
381
+ if (typeof sdk.recordWorkerRunner !== "function") {
382
+ return;
383
+ }
384
+ await sdk.recordWorkerRunner({
385
+ projectId: config.projectId,
386
+ environment: config.environment,
387
+ runnerId: config.workerId,
388
+ runnerServiceName: config.runnerServiceName,
389
+ volumeIdentity: config.volumeIdentity,
390
+ state: "sleeping",
391
+ maxLocalWorkers: config.maxLocalWorkers,
392
+ activeLocalWorkers: 0,
393
+ metadata: {
394
+ volumeRoot: config.volumeRoot,
395
+ reason: "idle_exit"
396
+ }
397
+ }).catch(() => null);
398
+ }
399
+ async function startWorkerLoop() {
400
+ const config = resolveWorkerConfig();
401
+ let idleSince = null;
402
+ for (; ; ) {
403
+ try {
404
+ const result = await runWorkerCycle();
405
+ const processed = Number(result.processed ?? 0);
406
+ if (processed > 0) {
407
+ idleSince = null;
408
+ } else {
409
+ idleSince ??= Date.now();
410
+ if (shouldExitWorkerLoopAfterIdle({
411
+ idleExitMs: config.idleExitMs,
412
+ idleSince,
413
+ now: Date.now(),
414
+ processed
415
+ })) {
416
+ await recordWorkerLoopExitState(config);
417
+ return;
418
+ }
419
+ }
420
+ } catch (error) {
421
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
422
+ `);
423
+ }
424
+ await new Promise((resolve) => setTimeout(resolve, config.pollIntervalMs));
425
+ }
426
+ }
427
+ const currentFile = fileURLToPath(import.meta.url);
428
+ const entryFile = process.argv[1] ?? "";
429
+ if (entryFile === currentFile) {
430
+ await startWorkerLoop();
431
+ }
432
+ export {
433
+ runWorkerCycle,
434
+ shouldExitWorkerLoopAfterIdle,
435
+ startWorkerLoop
436
+ };
@@ -0,0 +1,119 @@
1
+ name: Treeseed Processing Deploy
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ environment:
7
+ required: true
8
+ type: string
9
+ action_kind:
10
+ required: true
11
+ type: string
12
+ project_id:
13
+ required: false
14
+ type: string
15
+ preview_id:
16
+ required: false
17
+ type: string
18
+ workflow_dispatch:
19
+ inputs:
20
+ environment:
21
+ required: true
22
+ default: staging
23
+ type: choice
24
+ options:
25
+ - staging
26
+ - prod
27
+ action_kind:
28
+ required: true
29
+ default: deploy_processing
30
+ type: choice
31
+ options:
32
+ - deploy_processing
33
+ - monitor
34
+ project_id:
35
+ required: false
36
+ type: string
37
+ preview_id:
38
+ required: false
39
+ type: string
40
+
41
+ jobs:
42
+ processing:
43
+ runs-on: ubuntu-latest
44
+ permissions:
45
+ contents: read
46
+ environment: ${{ inputs.environment == 'prod' && 'production' || 'staging' }}
47
+ env:
48
+ TREESEED_BOOTSTRAP_MODE: auto
49
+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
50
+ CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
51
+ RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }}
52
+ RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
53
+ TREESEED_RAILWAY_WORKSPACE: ${{ vars.TREESEED_RAILWAY_WORKSPACE }}
54
+ TREESEED_API_BASE_URL: ${{ vars.TREESEED_API_BASE_URL }}
55
+ TREESEED_API_AUTH_SECRET: ${{ secrets.TREESEED_API_AUTH_SECRET }}
56
+ TREESEED_API_D1_DATABASE_ID: ${{ vars.TREESEED_API_D1_DATABASE_ID }}
57
+ TREESEED_API_WEB_SERVICE_ID: ${{ vars.TREESEED_API_WEB_SERVICE_ID }}
58
+ TREESEED_API_WEB_SERVICE_SECRET: ${{ secrets.TREESEED_API_WEB_SERVICE_SECRET }}
59
+ TREESEED_API_WEB_ASSERTION_SECRET: ${{ secrets.TREESEED_API_WEB_ASSERTION_SECRET }}
60
+ TREESEED_API_BOOTSTRAP_ADMIN_ALLOWLIST: ${{ vars.TREESEED_API_BOOTSTRAP_ADMIN_ALLOWLIST }}
61
+ TREESEED_PROJECT_ID: ${{ inputs.project_id || vars.TREESEED_PROJECT_ID }}
62
+ TREESEED_PROJECT_RUNNER_TOKEN: ${{ secrets.TREESEED_PROJECT_RUNNER_TOKEN }}
63
+ TREESEED_WORKER_POOL_SCALER: ${{ vars.TREESEED_WORKER_POOL_SCALER }}
64
+ TREESEED_AGENT_POOL_MIN_WORKERS: ${{ vars.TREESEED_AGENT_POOL_MIN_WORKERS }}
65
+ TREESEED_AGENT_POOL_MAX_WORKERS: ${{ vars.TREESEED_AGENT_POOL_MAX_WORKERS }}
66
+ TREESEED_AGENT_POOL_TARGET_QUEUE_DEPTH: ${{ vars.TREESEED_AGENT_POOL_TARGET_QUEUE_DEPTH }}
67
+ TREESEED_AGENT_POOL_COOLDOWN_SECONDS: ${{ vars.TREESEED_AGENT_POOL_COOLDOWN_SECONDS }}
68
+ TREESEED_WORKDAY_TIMEZONE: ${{ vars.TREESEED_WORKDAY_TIMEZONE }}
69
+ TREESEED_WORKDAY_WINDOWS_JSON: ${{ vars.TREESEED_WORKDAY_WINDOWS_JSON }}
70
+ TREESEED_WORKDAY_TASK_CREDIT_BUDGET: ${{ vars.TREESEED_WORKDAY_TASK_CREDIT_BUDGET }}
71
+ TREESEED_MANAGER_MAX_QUEUED_TASKS: ${{ vars.TREESEED_MANAGER_MAX_QUEUED_TASKS }}
72
+ TREESEED_MANAGER_MAX_QUEUED_CREDITS: ${{ vars.TREESEED_MANAGER_MAX_QUEUED_CREDITS }}
73
+ TREESEED_MANAGER_PRIORITY_MODELS: ${{ vars.TREESEED_MANAGER_PRIORITY_MODELS }}
74
+ TREESEED_TASK_CREDIT_WEIGHTS_JSON: ${{ vars.TREESEED_TASK_CREDIT_WEIGHTS_JSON }}
75
+ TREESEED_RAILWAY_PROJECT_ID: ${{ vars.TREESEED_RAILWAY_PROJECT_ID }}
76
+ TREESEED_RAILWAY_ENVIRONMENT_ID: ${{ vars.TREESEED_RAILWAY_ENVIRONMENT_ID }}
77
+ TREESEED_RAILWAY_WORKER_SERVICE_ID: ${{ vars.TREESEED_RAILWAY_WORKER_SERVICE_ID }}
78
+ TREESEED_CAPACITY_PROVIDER_ID: ${{ vars.TREESEED_CAPACITY_PROVIDER_ID }}
79
+ TREESEED_CAPACITY_PROVIDER_TEAM_ID: ${{ vars.TREESEED_CAPACITY_PROVIDER_TEAM_ID }}
80
+ TREESEED_CAPACITY_PROVIDER_SERVICE_BASE_URL: ${{ vars.TREESEED_CAPACITY_PROVIDER_SERVICE_BASE_URL }}
81
+ TREESEED_PROCESSING_DRAIN: ${{ vars.TREESEED_PROCESSING_DRAIN }}
82
+ TREESEED_WORKFLOW_ACTION: ${{ inputs.action_kind }}
83
+ TREESEED_WORKFLOW_ENVIRONMENT: ${{ inputs.environment }}
84
+ TREESEED_WORKFLOW_PROJECT: ${{ inputs.project_id || vars.TREESEED_PROJECT_ID }}
85
+ TREESEED_WORKFLOW_PREVIEW_ID: ${{ inputs.preview_id }}
86
+ steps:
87
+ - uses: actions/checkout@v4
88
+ with:
89
+ submodules: recursive
90
+
91
+ - uses: actions/setup-node@v4
92
+ with:
93
+ node-version: 22
94
+ cache: npm
95
+ cache-dependency-path: package-lock.json
96
+
97
+ - name: Install dependencies
98
+ shell: bash
99
+ run: |
100
+ set -euo pipefail
101
+ node -e "const fs = require('fs'); for (const file of ['packages/sdk/package.json', 'packages/agent/package.json', 'packages/core/package.json', 'packages/cli/package.json']) { if (!fs.existsSync(file)) continue; const p = JSON.parse(fs.readFileSync(file, 'utf8')); if (p.scripts) delete p.scripts.prepare; fs.writeFileSync(file, JSON.stringify(p, null, '\t') + '\n'); }"
102
+ npm ci --ignore-scripts
103
+
104
+ - name: Build package artifacts
105
+ shell: bash
106
+ run: |
107
+ set -euo pipefail
108
+ for dir in packages/sdk packages/agent packages/core packages/cli; do
109
+ if test -f "${dir}/package.json"; then npm --prefix "${dir}" run build:dist; fi
110
+ done
111
+
112
+ - name: Run processing workflow action
113
+ shell: bash
114
+ run: |
115
+ set -euo pipefail
116
+ EXTRA_ARGS=()
117
+ if [[ -n "${TREESEED_WORKFLOW_PROJECT:-}" ]]; then EXTRA_ARGS+=(--project-id "${TREESEED_WORKFLOW_PROJECT}"); fi
118
+ if [[ -n "${TREESEED_WORKFLOW_PREVIEW_ID:-}" ]]; then EXTRA_ARGS+=(--preview-id "${TREESEED_WORKFLOW_PREVIEW_ID}"); fi
119
+ node ./packages/sdk/scripts/run-ts.mjs ./packages/sdk/scripts/tenant-workflow-action.ts --action "${TREESEED_WORKFLOW_ACTION}" --environment "${TREESEED_WORKFLOW_ENVIRONMENT}" "${EXTRA_ARGS[@]}"
package/package.json ADDED
@@ -0,0 +1,136 @@
1
+ {
2
+ "name": "@treeseed/agent",
3
+ "version": "0.8.5",
4
+ "description": "Treeseed agent service runtime package.",
5
+ "license": "AGPL-3.0-only",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/treeseed-ai/agent.git",
9
+ "directory": "."
10
+ },
11
+ "homepage": "https://treeseed.ai",
12
+ "bugs": {
13
+ "url": "https://github.com/treeseed-ai/agent/issues"
14
+ },
15
+ "type": "module",
16
+ "engines": {
17
+ "node": ">=22"
18
+ },
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./api": {
26
+ "types": "./dist/api/index.d.ts",
27
+ "default": "./dist/api/index.js"
28
+ },
29
+ "./api/app": {
30
+ "types": "./dist/api/app.d.ts",
31
+ "default": "./dist/api/app.js"
32
+ },
33
+ "./api/auth/d1-provider": {
34
+ "types": "./dist/api/auth/d1-provider.d.ts",
35
+ "default": "./dist/api/auth/d1-provider.js"
36
+ },
37
+ "./services/worker": {
38
+ "types": "./dist/services/worker.d.ts",
39
+ "default": "./dist/services/worker.js"
40
+ },
41
+ "./services/manager": {
42
+ "types": "./dist/services/manager.d.ts",
43
+ "default": "./dist/services/manager.js"
44
+ },
45
+ "./services/agents": {
46
+ "types": "./dist/services/agents.d.ts",
47
+ "default": "./dist/services/agents.js"
48
+ },
49
+ "./services/remote-runner": {
50
+ "types": "./dist/services/remote-runner.d.ts",
51
+ "default": "./dist/services/remote-runner.js"
52
+ },
53
+ "./services/workday-manager": {
54
+ "types": "./dist/services/workday-manager.d.ts",
55
+ "default": "./dist/services/workday-manager.js"
56
+ },
57
+ "./services/workday-start": {
58
+ "types": "./dist/services/workday-start.d.ts",
59
+ "default": "./dist/services/workday-start.js"
60
+ },
61
+ "./services/workday-report": {
62
+ "types": "./dist/services/workday-report.d.ts",
63
+ "default": "./dist/services/workday-report.js"
64
+ },
65
+ "./runtime-types": {
66
+ "types": "./dist/agents/runtime-types.d.ts",
67
+ "default": "./dist/agents/runtime-types.js"
68
+ },
69
+ "./cli": {
70
+ "types": "./dist/agents/cli.d.ts",
71
+ "default": "./dist/agents/cli.js"
72
+ },
73
+ "./contracts/messages": {
74
+ "types": "./dist/agents/contracts/messages.d.ts",
75
+ "default": "./dist/agents/contracts/messages.js"
76
+ },
77
+ "./contracts/run": {
78
+ "types": "./dist/agents/contracts/run.d.ts",
79
+ "default": "./dist/agents/contracts/run.js"
80
+ }
81
+ },
82
+ "files": [
83
+ "README.md",
84
+ "dist",
85
+ "Dockerfile",
86
+ "templates"
87
+ ],
88
+ "publishConfig": {
89
+ "access": "public"
90
+ },
91
+ "scripts": {
92
+ "setup": "npm install",
93
+ "setup:ci": "npm ci",
94
+ "build": "npm run build:dist",
95
+ "build:dist": "node ./scripts/run-ts.mjs ./scripts/build-dist.ts",
96
+ "dev:manager": "node ./scripts/run-ts.mjs ./src/services/manager.ts",
97
+ "dev:worker": "node ./scripts/run-ts.mjs ./src/services/worker.ts",
98
+ "dev:workday-start": "node ./scripts/run-ts.mjs ./src/services/workday-start.ts",
99
+ "dev:workday-report": "node ./scripts/run-ts.mjs ./src/services/workday-report.ts",
100
+ "dev:remote-runner": "node ./scripts/run-ts.mjs ./src/services/remote-runner.ts",
101
+ "start:manager": "node ./dist/services/manager.js",
102
+ "start:worker": "node ./dist/services/worker.js",
103
+ "start:workday-start": "node ./dist/services/workday-start.js",
104
+ "start:workday-report": "node ./dist/services/workday-report.js",
105
+ "start:local-manager-cloudflare": "bash ./scripts/run-local-manager-cloudflare.sh",
106
+ "lint": "npm run build:dist",
107
+ "test": "npm run test:unit && npm run test:smoke",
108
+ "test:unit": "vitest run --config ./vitest.config.ts",
109
+ "prepack": "npm run build:dist",
110
+ "test:smoke": "node ./scripts/run-ts.mjs ./scripts/test-smoke.ts",
111
+ "verify:direct": "npm run release:verify",
112
+ "verify:local": "node --input-type=module -e \"process.env.TREESEED_VERIFY_DRIVER='direct'; await import('@treeseed/sdk/scripts/verify-driver')\"",
113
+ "verify:action": "node --input-type=module -e \"process.env.TREESEED_VERIFY_DRIVER='act'; await import('@treeseed/sdk/scripts/verify-driver')\"",
114
+ "verify": "node --input-type=module -e \"await import('@treeseed/sdk/scripts/verify-driver')\"",
115
+ "release:setup": "npm run setup:ci",
116
+ "release:check-tag": "node ./scripts/run-ts.mjs ./scripts/assert-release-tag-version.ts",
117
+ "release:verify": "node ./scripts/run-ts.mjs ./scripts/release-verify.ts",
118
+ "release:publish": "node ./scripts/run-ts.mjs ./scripts/publish-package.ts"
119
+ },
120
+ "dependencies": {
121
+ "@treeseed/sdk": "0.8.5",
122
+ "esbuild": "0.28.0",
123
+ "hono": "^4.8.2",
124
+ "typescript": "^5.9.3",
125
+ "yaml": "^2.8.1"
126
+ },
127
+ "devDependencies": {
128
+ "@types/node": "^24.6.0",
129
+ "vitest": "^4.1.2"
130
+ },
131
+ "bin": {
132
+ "treeseed-agents": "dist/scripts/treeseed-agents.js",
133
+ "treeseed-agent-api": "dist/scripts/treeseed-agent-api.js",
134
+ "treeseed-agent-service": "dist/scripts/treeseed-agent-service.js"
135
+ }
136
+ }