@treeseed/core 0.8.3 → 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.
- package/README.md +7 -11
- package/dist/dev-watch.js +1 -1
- package/dist/dev.d.ts +2 -4
- package/dist/dev.js +4 -124
- package/dist/env.yaml +23 -175
- package/dist/index.d.ts +0 -4
- package/dist/index.js +0 -6
- package/dist/scripts/build-dist.js +3 -3
- package/dist/scripts/dev-platform.js +1 -7
- package/dist/scripts/run-fixture-astro-command.js +25 -51
- package/dist/scripts/test-smoke.js +50 -7
- package/package.json +5 -78
- package/templates/github/deploy-web.workflow.yml +111 -0
- package/templates/github/hosted-project.workflow.yml +4 -4
- package/dist/agent-runtime.d.ts +0 -17
- package/dist/agent-runtime.js +0 -117
- package/dist/agent.d.ts +0 -11
- package/dist/agent.js +0 -25
- package/dist/agents/adapters/execution.d.ts +0 -41
- package/dist/agents/adapters/execution.js +0 -73
- package/dist/agents/adapters/mutations.d.ts +0 -22
- package/dist/agents/adapters/mutations.js +0 -30
- package/dist/agents/adapters/notification.d.ts +0 -26
- package/dist/agents/adapters/notification.js +0 -46
- package/dist/agents/adapters/repository.d.ts +0 -23
- package/dist/agents/adapters/repository.js +0 -61
- package/dist/agents/adapters/research.d.ts +0 -26
- package/dist/agents/adapters/research.js +0 -59
- package/dist/agents/adapters/verification.d.ts +0 -36
- package/dist/agents/adapters/verification.js +0 -62
- package/dist/agents/cli-tools.d.ts +0 -1
- package/dist/agents/cli-tools.js +0 -5
- package/dist/agents/cli.d.ts +0 -15
- package/dist/agents/cli.js +0 -109
- package/dist/agents/contracts/messages.d.ts +0 -88
- package/dist/agents/contracts/messages.js +0 -138
- package/dist/agents/contracts/run.d.ts +0 -21
- package/dist/agents/contracts/run.js +0 -0
- package/dist/agents/index.d.ts +0 -1
- package/dist/agents/index.js +0 -5
- package/dist/agents/kernel/agent-kernel.d.ts +0 -51
- package/dist/agents/kernel/agent-kernel.js +0 -292
- package/dist/agents/kernel/trigger-resolver.d.ts +0 -19
- package/dist/agents/kernel/trigger-resolver.js +0 -157
- package/dist/agents/registry-helper.d.ts +0 -4
- package/dist/agents/registry-helper.js +0 -14
- package/dist/agents/registry.d.ts +0 -6
- package/dist/agents/registry.js +0 -98
- package/dist/agents/runtime-types.d.ts +0 -118
- package/dist/agents/runtime-types.js +0 -0
- package/dist/agents/spec-loader.d.ts +0 -18
- package/dist/agents/spec-loader.js +0 -55
- package/dist/agents/spec-normalizer.d.ts +0 -2
- package/dist/agents/spec-normalizer.js +0 -327
- package/dist/agents/spec-types.d.ts +0 -64
- package/dist/agents/spec-types.js +0 -0
- package/dist/agents/testing/agents-smoke.d.ts +0 -1
- package/dist/agents/testing/agents-smoke.js +0 -32
- package/dist/agents/testing/e2e-harness.d.ts +0 -44
- package/dist/agents/testing/e2e-harness.js +0 -504
- package/dist/api/agent-routes.d.ts +0 -13
- package/dist/api/agent-routes.js +0 -327
- package/dist/api/app.d.ts +0 -5
- package/dist/api/app.js +0 -361
- package/dist/api/auth/d1-database.d.ts +0 -3
- package/dist/api/auth/d1-database.js +0 -20
- package/dist/api/auth/d1-provider.d.ts +0 -79
- package/dist/api/auth/d1-provider.js +0 -92
- package/dist/api/auth/d1-store.d.ts +0 -114
- package/dist/api/auth/d1-store.js +0 -895
- package/dist/api/auth/memory-provider.d.ts +0 -77
- package/dist/api/auth/memory-provider.js +0 -249
- package/dist/api/auth/rbac.d.ts +0 -22
- package/dist/api/auth/rbac.js +0 -162
- package/dist/api/auth/tokens.d.ts +0 -18
- package/dist/api/auth/tokens.js +0 -56
- package/dist/api/capabilities.d.ts +0 -9
- package/dist/api/capabilities.js +0 -33
- package/dist/api/config.d.ts +0 -2
- package/dist/api/config.js +0 -77
- package/dist/api/http.d.ts +0 -28
- package/dist/api/http.js +0 -51
- package/dist/api/index.d.ts +0 -9
- package/dist/api/index.js +0 -18
- package/dist/api/operations-routes.d.ts +0 -11
- package/dist/api/operations-routes.js +0 -87
- package/dist/api/operations.d.ts +0 -3
- package/dist/api/operations.js +0 -26
- package/dist/api/project-routes.d.ts +0 -8
- package/dist/api/project-routes.js +0 -586
- package/dist/api/providers.d.ts +0 -2
- package/dist/api/providers.js +0 -62
- package/dist/api/railway.d.ts +0 -50
- package/dist/api/railway.js +0 -69
- package/dist/api/sdk-dispatch.d.ts +0 -5
- package/dist/api/sdk-dispatch.js +0 -13
- package/dist/api/sdk-routes.d.ts +0 -11
- package/dist/api/sdk-routes.js +0 -29
- package/dist/api/server.d.ts +0 -2
- package/dist/api/server.js +0 -10
- package/dist/api/templates.d.ts +0 -3
- package/dist/api/templates.js +0 -31
- package/dist/api/types.d.ts +0 -231
- package/dist/api/types.js +0 -0
- package/dist/api.d.ts +0 -1
- package/dist/api.js +0 -1
- package/dist/railway.d.ts +0 -1
- package/dist/railway.js +0 -4
- package/dist/services/agents.d.ts +0 -11
- package/dist/services/agents.js +0 -48
- package/dist/services/common.d.ts +0 -66
- package/dist/services/common.js +0 -212
- package/dist/services/index.d.ts +0 -6
- package/dist/services/index.js +0 -19
- package/dist/services/manager.d.ts +0 -267
- package/dist/services/manager.js +0 -1368
- package/dist/services/remote-runner.d.ts +0 -30
- package/dist/services/remote-runner.js +0 -230
- package/dist/services/workday-content.d.ts +0 -53
- package/dist/services/workday-content.js +0 -190
- package/dist/services/workday-manager.d.ts +0 -279
- package/dist/services/workday-manager.js +0 -163
- package/dist/services/workday-report.d.ts +0 -195
- package/dist/services/workday-report.js +0 -17
- package/dist/services/workday-start.d.ts +0 -195
- package/dist/services/workday-start.js +0 -17
- package/dist/services/worker-capacity.d.ts +0 -58
- package/dist/services/worker-capacity.js +0 -208
- package/dist/services/worker-pool-scaler.d.ts +0 -27
- package/dist/services/worker-pool-scaler.js +0 -127
- package/dist/services/worker.d.ts +0 -19
- package/dist/services/worker.js +0 -436
- package/templates/github/deploy.workflow.yml +0 -577
package/dist/services/worker.js
DELETED
|
@@ -1,436 +0,0 @@
|
|
|
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
|
-
};
|