@treeseed/core 0.4.8 → 0.4.10
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 +1 -2
- package/dist/agent.d.ts +0 -1
- package/dist/agent.js +0 -2
- package/dist/agents/spec-types.d.ts +10 -10
- package/dist/api/agent-routes.d.ts +2 -2
- package/dist/api/agent-routes.js +51 -125
- package/dist/api/app.js +56 -4
- package/dist/api/auth/d1-store.d.ts +1 -0
- package/dist/api/auth/d1-store.js +21 -1
- package/dist/api/config.js +4 -0
- package/dist/api/http.d.ts +4 -0
- package/dist/api/http.js +7 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +2 -2
- package/dist/api/operations-routes.d.ts +1 -0
- package/dist/api/operations-routes.js +6 -1
- package/dist/api/railway.d.ts +4 -0
- package/dist/api/sdk-dispatch.d.ts +2 -11
- package/dist/api/sdk-dispatch.js +1 -133
- package/dist/api/sdk-routes.d.ts +1 -0
- package/dist/api/sdk-routes.js +5 -1
- package/dist/api/types.d.ts +32 -16
- package/dist/dev.js +24 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/scripts/aggregate-book.js +13 -108
- package/dist/scripts/test-smoke.js +80 -24
- package/dist/services/common.d.ts +37 -4
- package/dist/services/common.js +135 -17
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +3 -2
- package/dist/services/remote-runner.d.ts +23 -0
- package/dist/services/remote-runner.js +105 -0
- package/dist/services/workday-report.js +13 -17
- package/dist/services/workday-start.d.ts +5 -1
- package/dist/services/workday-start.js +7 -13
- package/dist/services/worker.js +38 -57
- package/package.json +7 -11
- package/dist/api/gateway.d.ts +0 -5
- package/dist/api/gateway.js +0 -35
- package/dist/services/manager.d.ts +0 -4
- package/dist/services/manager.js +0 -199
|
@@ -1,9 +1,43 @@
|
|
|
1
|
-
import { AgentSdk } from '@treeseed/sdk
|
|
2
|
-
import {
|
|
1
|
+
import { AgentSdk } from '@treeseed/sdk';
|
|
2
|
+
import { CloudflareQueuePullClient } from '@treeseed/sdk/remote';
|
|
3
|
+
import type { SdkQueueMessageEnvelope } from '@treeseed/sdk';
|
|
3
4
|
export declare function resolveServiceRepoRoot(): string;
|
|
4
5
|
export declare function createServiceSdk(): AgentSdk;
|
|
5
|
-
export declare function createGatewayClient(): TreeseedGatewayClient;
|
|
6
6
|
export declare function createQueueClient(): CloudflareQueuePullClient;
|
|
7
|
+
export declare function createQueuePushClient(): {
|
|
8
|
+
enqueue(request: {
|
|
9
|
+
message: SdkQueueMessageEnvelope;
|
|
10
|
+
delaySeconds?: number;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export declare function queueEnvelopeForTask(task: Record<string, unknown>): SdkQueueMessageEnvelope;
|
|
14
|
+
export declare function enqueueTaskFromSdk(sdk: AgentSdk, request: {
|
|
15
|
+
taskId: string;
|
|
16
|
+
queueName?: string;
|
|
17
|
+
deliveryDelaySeconds?: number;
|
|
18
|
+
actor?: string;
|
|
19
|
+
}): Promise<{
|
|
20
|
+
ok: boolean;
|
|
21
|
+
taskId: string;
|
|
22
|
+
queued: boolean;
|
|
23
|
+
}>;
|
|
24
|
+
export declare function buildTaskContext(sdk: AgentSdk, taskId: string): Promise<{
|
|
25
|
+
agent: Record<string, unknown> | import("@treeseed/sdk").SdkContentEntry;
|
|
26
|
+
task: import("@treeseed/sdk").SdkTaskEntity | null;
|
|
27
|
+
workDay: import("@treeseed/sdk").SdkWorkDayEntity | null;
|
|
28
|
+
graph: Record<string, unknown> | null;
|
|
29
|
+
}>;
|
|
30
|
+
export declare function seedRootTasks(sdk: AgentSdk, workDayId: string): Promise<any[]>;
|
|
31
|
+
export declare function startAndSeedWorkday(sdk: AgentSdk, request: {
|
|
32
|
+
id?: string;
|
|
33
|
+
projectId: string;
|
|
34
|
+
capacityBudget: number;
|
|
35
|
+
actor?: string;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
ok: boolean;
|
|
38
|
+
workDay: import("@treeseed/sdk").SdkWorkDayEntity;
|
|
39
|
+
seededTasks: any[];
|
|
40
|
+
}>;
|
|
7
41
|
export declare function resolveManagerConfig(): {
|
|
8
42
|
host: string;
|
|
9
43
|
port: number;
|
|
@@ -16,5 +50,4 @@ export declare function resolveWorkerConfig(): {
|
|
|
16
50
|
visibilityTimeoutMs: number;
|
|
17
51
|
pollIntervalMs: number;
|
|
18
52
|
leaseSeconds: number;
|
|
19
|
-
managerBaseUrl: string;
|
|
20
53
|
};
|
package/dist/services/common.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { AgentSdk } from "@treeseed/sdk";
|
|
3
|
+
import { CloudflareQueuePullClient } from "@treeseed/sdk/remote";
|
|
3
4
|
function integerFromEnv(name, fallback) {
|
|
4
5
|
const value = process.env[name];
|
|
5
6
|
if (!value) return fallback;
|
|
@@ -16,27 +17,140 @@ function createServiceSdk() {
|
|
|
16
17
|
persistTo: process.env.TREESEED_AGENT_D1_PERSIST_TO ?? void 0
|
|
17
18
|
});
|
|
18
19
|
}
|
|
19
|
-
function
|
|
20
|
-
const baseUrl = process.env.TREESEED_GATEWAY_BASE_URL?.trim();
|
|
21
|
-
const bearerToken = process.env.TREESEED_GATEWAY_BEARER_TOKEN?.trim();
|
|
22
|
-
if (!baseUrl || !bearerToken) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
return new TreeseedGatewayClient({ baseUrl, bearerToken });
|
|
26
|
-
}
|
|
27
|
-
function createQueueClient() {
|
|
20
|
+
function createQueueClientConfig(token) {
|
|
28
21
|
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID?.trim();
|
|
29
22
|
const queueId = process.env.TREESEED_QUEUE_ID?.trim();
|
|
30
|
-
const token = process.env.TREESEED_QUEUE_PULL_TOKEN?.trim();
|
|
31
23
|
if (!accountId || !queueId || !token) {
|
|
32
24
|
return null;
|
|
33
25
|
}
|
|
34
|
-
return
|
|
26
|
+
return {
|
|
35
27
|
accountId,
|
|
36
28
|
queueId,
|
|
37
29
|
token,
|
|
38
30
|
apiBaseUrl: process.env.TREESEED_QUEUE_API_BASE_URL?.trim() || void 0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function createQueueClient() {
|
|
34
|
+
const config = createQueueClientConfig(process.env.TREESEED_QUEUE_PULL_TOKEN?.trim() || "");
|
|
35
|
+
if (!config) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return new CloudflareQueuePullClient(config);
|
|
39
|
+
}
|
|
40
|
+
function createQueuePushClient() {
|
|
41
|
+
const config = createQueueClientConfig(
|
|
42
|
+
process.env.TREESEED_QUEUE_PUSH_TOKEN?.trim() || process.env.CLOUDFLARE_API_TOKEN?.trim() || ""
|
|
43
|
+
);
|
|
44
|
+
if (!config) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const apiBaseUrl = config.apiBaseUrl ?? "https://api.cloudflare.com/client/v4/accounts";
|
|
48
|
+
const baseUrl = `${apiBaseUrl.replace(/\/$/u, "")}/${config.accountId}/queues/${config.queueId}`;
|
|
49
|
+
return {
|
|
50
|
+
async enqueue(request) {
|
|
51
|
+
const response = await fetch(`${baseUrl}/messages`, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: {
|
|
54
|
+
accept: "application/json",
|
|
55
|
+
authorization: `Bearer ${config.token}`,
|
|
56
|
+
"content-type": "application/json"
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
body: request.message,
|
|
60
|
+
content_type: "json",
|
|
61
|
+
delay_seconds: request.delaySeconds ?? 0
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
const payload = await response.json().catch(() => ({}));
|
|
65
|
+
if (!response.ok || payload.success === false) {
|
|
66
|
+
throw new Error(payload.errors?.[0]?.message ?? `Queue request failed with ${response.status}.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function queueEnvelopeForTask(task) {
|
|
72
|
+
return {
|
|
73
|
+
messageId: crypto.randomUUID(),
|
|
74
|
+
taskId: String(task.id ?? ""),
|
|
75
|
+
workDayId: String(task.workDayId ?? task.work_day_id ?? ""),
|
|
76
|
+
agentId: String(task.agentId ?? task.agent_id ?? ""),
|
|
77
|
+
taskType: String(task.type ?? ""),
|
|
78
|
+
idempotencyKey: String(task.idempotencyKey ?? task.idempotency_key ?? ""),
|
|
79
|
+
attempt: Number(task.attemptCount ?? task.attempt_count ?? 0) + 1,
|
|
80
|
+
payloadRef: `d1:tasks/${String(task.id ?? "")}`,
|
|
81
|
+
graphVersion: task.graphVersion !== void 0 && task.graphVersion !== null ? String(task.graphVersion) : task.graph_version !== void 0 && task.graph_version !== null ? String(task.graph_version) : null,
|
|
82
|
+
budgetHint: 1
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function enqueueTaskFromSdk(sdk, request) {
|
|
86
|
+
const queue = createQueuePushClient();
|
|
87
|
+
if (!queue) {
|
|
88
|
+
throw new Error("Queue push client not configured.");
|
|
89
|
+
}
|
|
90
|
+
const task = await sdk.get({ model: "task", id: request.taskId });
|
|
91
|
+
if (!task.payload) {
|
|
92
|
+
throw new Error("Unknown task.");
|
|
93
|
+
}
|
|
94
|
+
await queue.enqueue({
|
|
95
|
+
message: queueEnvelopeForTask(task.payload),
|
|
96
|
+
delaySeconds: request.deliveryDelaySeconds ?? 0
|
|
97
|
+
});
|
|
98
|
+
await sdk.recordTaskProgress({
|
|
99
|
+
id: request.taskId,
|
|
100
|
+
state: "queued",
|
|
101
|
+
appendEvent: { kind: "queued", data: { queueName: request.queueName ?? null } },
|
|
102
|
+
actor: request.actor
|
|
39
103
|
});
|
|
104
|
+
return { ok: true, taskId: request.taskId, queued: true };
|
|
105
|
+
}
|
|
106
|
+
async function buildTaskContext(sdk, taskId) {
|
|
107
|
+
const context = await sdk.getManagerContext(taskId);
|
|
108
|
+
const task = context.payload.task;
|
|
109
|
+
const agent = task ? (await sdk.get({ model: "agent", slug: String(task.agentId) })).payload : null;
|
|
110
|
+
return {
|
|
111
|
+
...context.payload,
|
|
112
|
+
agent
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
async function seedRootTasks(sdk, workDayId) {
|
|
116
|
+
const specs = await sdk.listAgentSpecs({ enabled: true });
|
|
117
|
+
const created = [];
|
|
118
|
+
for (const spec of specs) {
|
|
119
|
+
const hasStartTrigger = spec.triggers.some((trigger) => trigger.type === "startup" || trigger.type === "schedule");
|
|
120
|
+
if (!hasStartTrigger) continue;
|
|
121
|
+
created.push(await sdk.createTask({
|
|
122
|
+
workDayId,
|
|
123
|
+
agentId: spec.slug,
|
|
124
|
+
type: "agent_root",
|
|
125
|
+
priority: 100,
|
|
126
|
+
idempotencyKey: `${workDayId}:${spec.slug}:root`,
|
|
127
|
+
payload: {
|
|
128
|
+
agentSlug: spec.slug,
|
|
129
|
+
handler: spec.handler,
|
|
130
|
+
triggerKinds: spec.triggers.map((entry) => entry.type)
|
|
131
|
+
},
|
|
132
|
+
graphVersion: null,
|
|
133
|
+
actor: "manager"
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
return created;
|
|
137
|
+
}
|
|
138
|
+
async function startAndSeedWorkday(sdk, request) {
|
|
139
|
+
const graphRefresh = await sdk.refreshGraph();
|
|
140
|
+
const workDay = await sdk.startWorkDay({
|
|
141
|
+
id: request.id,
|
|
142
|
+
projectId: request.projectId,
|
|
143
|
+
capacityBudget: request.capacityBudget,
|
|
144
|
+
graphVersion: graphRefresh.snapshotRoot,
|
|
145
|
+
summary: { graphRefresh },
|
|
146
|
+
actor: request.actor ?? "manager"
|
|
147
|
+
});
|
|
148
|
+
const tasks = workDay.payload ? await seedRootTasks(sdk, String(workDay.payload.id)) : [];
|
|
149
|
+
return {
|
|
150
|
+
ok: true,
|
|
151
|
+
workDay: workDay.payload,
|
|
152
|
+
seededTasks: tasks.map((entry) => entry.payload).filter(Boolean)
|
|
153
|
+
};
|
|
40
154
|
}
|
|
41
155
|
function resolveManagerConfig() {
|
|
42
156
|
return {
|
|
@@ -52,15 +166,19 @@ function resolveWorkerConfig() {
|
|
|
52
166
|
batchSize: integerFromEnv("TREESEED_QUEUE_BATCH_SIZE", 1),
|
|
53
167
|
visibilityTimeoutMs: integerFromEnv("TREESEED_QUEUE_VISIBILITY_TIMEOUT_MS", 12e4),
|
|
54
168
|
pollIntervalMs: integerFromEnv("TREESEED_WORKER_POLL_INTERVAL_MS", 5e3),
|
|
55
|
-
leaseSeconds: integerFromEnv("TREESEED_TASK_LEASE_SECONDS", 120)
|
|
56
|
-
managerBaseUrl: process.env.TREESEED_MANAGER_BASE_URL?.trim() || `http://${process.env.TREESEED_MANAGER_HOST?.trim() || "manager.railway.internal"}:${integerFromEnv("TREESEED_MANAGER_PORT", 3100)}`
|
|
169
|
+
leaseSeconds: integerFromEnv("TREESEED_TASK_LEASE_SECONDS", 120)
|
|
57
170
|
};
|
|
58
171
|
}
|
|
59
172
|
export {
|
|
60
|
-
|
|
173
|
+
buildTaskContext,
|
|
61
174
|
createQueueClient,
|
|
175
|
+
createQueuePushClient,
|
|
62
176
|
createServiceSdk,
|
|
177
|
+
enqueueTaskFromSdk,
|
|
178
|
+
queueEnvelopeForTask,
|
|
63
179
|
resolveManagerConfig,
|
|
64
180
|
resolveServiceRepoRoot,
|
|
65
|
-
resolveWorkerConfig
|
|
181
|
+
resolveWorkerConfig,
|
|
182
|
+
seedRootTasks,
|
|
183
|
+
startAndSeedWorkday
|
|
66
184
|
};
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createManagerApp } from './manager.ts';
|
|
2
1
|
export { runWorkerCycle, startWorkerLoop } from './worker.ts';
|
|
2
|
+
export { runRemoteRunnerCycle, startRemoteRunnerLoop } from './remote-runner.ts';
|
|
3
3
|
export { runWorkdayStart } from './workday-start.ts';
|
|
4
4
|
export { runWorkdayReport } from './workday-report.ts';
|
package/dist/services/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { createManagerApp } from "./manager.js";
|
|
2
1
|
import { runWorkerCycle, startWorkerLoop } from "./worker.js";
|
|
2
|
+
import { runRemoteRunnerCycle, startRemoteRunnerLoop } from "./remote-runner.js";
|
|
3
3
|
import { runWorkdayStart } from "./workday-start.js";
|
|
4
4
|
import { runWorkdayReport } from "./workday-report.js";
|
|
5
5
|
export {
|
|
6
|
-
|
|
6
|
+
runRemoteRunnerCycle,
|
|
7
7
|
runWorkdayReport,
|
|
8
8
|
runWorkdayStart,
|
|
9
9
|
runWorkerCycle,
|
|
10
|
+
startRemoteRunnerLoop,
|
|
10
11
|
startWorkerLoop
|
|
11
12
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { AgentSdk } from '@treeseed/sdk';
|
|
3
|
+
export declare function resolveRemoteRunnerConfig(): {
|
|
4
|
+
marketBaseUrl: string;
|
|
5
|
+
projectId: string;
|
|
6
|
+
runnerToken: string;
|
|
7
|
+
runnerId: string;
|
|
8
|
+
batchSize: number;
|
|
9
|
+
pollIntervalMs: number;
|
|
10
|
+
};
|
|
11
|
+
export declare function runRemoteRunnerCycle(options?: {
|
|
12
|
+
sdk?: AgentSdk;
|
|
13
|
+
config?: ReturnType<typeof resolveRemoteRunnerConfig>;
|
|
14
|
+
fetchImpl?: typeof fetch;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
ok: boolean;
|
|
17
|
+
processed: number;
|
|
18
|
+
}>;
|
|
19
|
+
export declare function startRemoteRunnerLoop(options?: {
|
|
20
|
+
sdk?: AgentSdk;
|
|
21
|
+
config?: ReturnType<typeof resolveRemoteRunnerConfig>;
|
|
22
|
+
fetchImpl?: typeof fetch;
|
|
23
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { AgentSdk, RemoteTreeseedClient, RemoteTreeseedRunnerClient } from "@treeseed/sdk";
|
|
4
|
+
import { createServiceSdk } from "./common.js";
|
|
5
|
+
function integerFromEnv(name, fallback) {
|
|
6
|
+
const value = process.env[name];
|
|
7
|
+
if (!value) return fallback;
|
|
8
|
+
const parsed = Number.parseInt(value, 10);
|
|
9
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
10
|
+
}
|
|
11
|
+
function envValue(name) {
|
|
12
|
+
const value = process.env[name]?.trim();
|
|
13
|
+
return value ? value : "";
|
|
14
|
+
}
|
|
15
|
+
function resolveRemoteRunnerConfig() {
|
|
16
|
+
return {
|
|
17
|
+
marketBaseUrl: envValue("TREESEED_MARKET_API_BASE_URL") || envValue("TREESEED_API_BASE_URL"),
|
|
18
|
+
projectId: envValue("TREESEED_PROJECT_ID") || "treeseed-market",
|
|
19
|
+
runnerToken: envValue("TREESEED_PROJECT_RUNNER_TOKEN"),
|
|
20
|
+
runnerId: envValue("TREESEED_REMOTE_RUNNER_ID") || `remote-runner-${process.pid}`,
|
|
21
|
+
batchSize: integerFromEnv("TREESEED_REMOTE_RUNNER_BATCH_SIZE", 1),
|
|
22
|
+
pollIntervalMs: integerFromEnv("TREESEED_REMOTE_RUNNER_POLL_INTERVAL_MS", 5e3)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function createRunnerClient(config, fetchImpl) {
|
|
26
|
+
if (!config.marketBaseUrl || !config.runnerToken) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"Remote runner requires TREESEED_MARKET_API_BASE_URL (or TREESEED_API_BASE_URL) and TREESEED_PROJECT_RUNNER_TOKEN."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return new RemoteTreeseedRunnerClient(new RemoteTreeseedClient({
|
|
32
|
+
hosts: [{ id: "market", baseUrl: config.marketBaseUrl }],
|
|
33
|
+
activeHostId: "market",
|
|
34
|
+
auth: {
|
|
35
|
+
accessToken: config.runnerToken
|
|
36
|
+
}
|
|
37
|
+
}, {
|
|
38
|
+
fetchImpl
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
async function runRemoteRunnerCycle(options = {}) {
|
|
42
|
+
const config = options.config ?? resolveRemoteRunnerConfig();
|
|
43
|
+
const sdk = options.sdk ?? createServiceSdk();
|
|
44
|
+
const runner = createRunnerClient(config, options.fetchImpl);
|
|
45
|
+
const pulled = await runner.pull(config.projectId, {
|
|
46
|
+
limit: config.batchSize,
|
|
47
|
+
runnerId: config.runnerId
|
|
48
|
+
});
|
|
49
|
+
if (pulled.payload.length === 0) {
|
|
50
|
+
return { ok: true, processed: 0 };
|
|
51
|
+
}
|
|
52
|
+
let processed = 0;
|
|
53
|
+
for (const job of pulled.payload) {
|
|
54
|
+
try {
|
|
55
|
+
await runner.progress(job.id, {
|
|
56
|
+
summary: `Running ${job.namespace}:${job.operation}`,
|
|
57
|
+
data: {
|
|
58
|
+
runnerId: config.runnerId,
|
|
59
|
+
status: "running"
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const result = await sdk.dispatch({
|
|
63
|
+
namespace: job.namespace,
|
|
64
|
+
operation: job.operation,
|
|
65
|
+
input: job.input ?? {},
|
|
66
|
+
preferredMode: "prefer_local"
|
|
67
|
+
});
|
|
68
|
+
await runner.complete(job.id, {
|
|
69
|
+
output: result.mode === "inline" ? result.payload : result
|
|
70
|
+
});
|
|
71
|
+
processed += 1;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
await runner.fail(job.id, {
|
|
74
|
+
code: "runner_execution_failed",
|
|
75
|
+
message: error instanceof Error ? error.message : String(error)
|
|
76
|
+
}).catch(() => null);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { ok: true, processed };
|
|
80
|
+
}
|
|
81
|
+
async function startRemoteRunnerLoop(options = {}) {
|
|
82
|
+
const config = options.config ?? resolveRemoteRunnerConfig();
|
|
83
|
+
for (; ; ) {
|
|
84
|
+
try {
|
|
85
|
+
await runRemoteRunnerCycle({
|
|
86
|
+
...options,
|
|
87
|
+
config
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
91
|
+
`);
|
|
92
|
+
}
|
|
93
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, config.pollIntervalMs));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
97
|
+
const entryFile = process.argv[1] ?? "";
|
|
98
|
+
if (entryFile === currentFile) {
|
|
99
|
+
await startRemoteRunnerLoop();
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
resolveRemoteRunnerConfig,
|
|
103
|
+
runRemoteRunnerCycle,
|
|
104
|
+
startRemoteRunnerLoop
|
|
105
|
+
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import {
|
|
3
|
+
import { createServiceSdk } from "./common.js";
|
|
4
4
|
async function runWorkdayReport() {
|
|
5
5
|
const sdk = createServiceSdk();
|
|
6
|
-
const gateway = createGatewayClient();
|
|
7
6
|
const workDays = await sdk.search({ model: "work_day", limit: 1 });
|
|
8
7
|
const active = workDays.payload[0];
|
|
9
8
|
if (!active || typeof active.id !== "string") {
|
|
@@ -16,21 +15,18 @@ async function runWorkdayReport() {
|
|
|
16
15
|
failedTasks: tasks.payload.filter((entry) => entry.state === "failed").length,
|
|
17
16
|
pendingTasks: tasks.payload.filter((entry) => entry.state !== "completed" && entry.state !== "failed").length
|
|
18
17
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
}
|
|
18
|
+
await sdk.createReport({
|
|
19
|
+
workDayId: active.id,
|
|
20
|
+
kind: "workday_summary",
|
|
21
|
+
body: summary,
|
|
22
|
+
actor: "workday-report"
|
|
23
|
+
});
|
|
24
|
+
await sdk.closeWorkDay({
|
|
25
|
+
id: active.id,
|
|
26
|
+
state: "completed",
|
|
27
|
+
summary,
|
|
28
|
+
actor: "workday-report"
|
|
29
|
+
});
|
|
34
30
|
return { ok: true, workDayId: active.id, summary };
|
|
35
31
|
}
|
|
36
32
|
const currentFile = fileURLToPath(import.meta.url);
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import {
|
|
3
|
+
import { createServiceSdk, resolveManagerConfig, startAndSeedWorkday } from "./common.js";
|
|
4
4
|
async function runWorkdayStart() {
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
},
|
|
12
|
-
body: JSON.stringify({})
|
|
5
|
+
const sdk = createServiceSdk();
|
|
6
|
+
const config = resolveManagerConfig();
|
|
7
|
+
return startAndSeedWorkday(sdk, {
|
|
8
|
+
projectId: config.projectId,
|
|
9
|
+
capacityBudget: config.defaultCapacityBudget,
|
|
10
|
+
actor: "manager"
|
|
13
11
|
});
|
|
14
|
-
if (!response.ok) {
|
|
15
|
-
throw new Error(`Workday start failed with ${response.status}.`);
|
|
16
|
-
}
|
|
17
|
-
return response.json();
|
|
18
12
|
}
|
|
19
13
|
const currentFile = fileURLToPath(import.meta.url);
|
|
20
14
|
const entryFile = process.argv[1] ?? "";
|
package/dist/services/worker.js
CHANGED
|
@@ -1,27 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import {
|
|
4
|
-
async function managerRequest(baseUrl, path, body) {
|
|
5
|
-
const response = await fetch(`${baseUrl}${path}`, {
|
|
6
|
-
method: "POST",
|
|
7
|
-
headers: {
|
|
8
|
-
accept: "application/json",
|
|
9
|
-
"content-type": "application/json"
|
|
10
|
-
},
|
|
11
|
-
body: JSON.stringify(body)
|
|
12
|
-
});
|
|
13
|
-
const payload = await response.json().catch(() => ({}));
|
|
14
|
-
if (!response.ok) {
|
|
15
|
-
throw new Error(typeof payload.error === "string" ? payload.error : `Manager request failed with ${response.status}.`);
|
|
16
|
-
}
|
|
17
|
-
return payload;
|
|
18
|
-
}
|
|
3
|
+
import { buildTaskContext, createQueueClient, createServiceSdk, resolveWorkerConfig } from "./common.js";
|
|
19
4
|
async function runWorkerCycle() {
|
|
20
|
-
const
|
|
5
|
+
const sdk = createServiceSdk();
|
|
21
6
|
const queue = createQueueClient();
|
|
22
7
|
const config = resolveWorkerConfig();
|
|
23
|
-
if (!
|
|
24
|
-
throw new Error("Worker requires
|
|
8
|
+
if (!queue) {
|
|
9
|
+
throw new Error("Worker requires CLOUDFLARE_ACCOUNT_ID, TREESEED_QUEUE_ID, and TREESEED_QUEUE_PULL_TOKEN.");
|
|
25
10
|
}
|
|
26
11
|
const pulled = await queue.pull({
|
|
27
12
|
batchSize: config.batchSize,
|
|
@@ -33,52 +18,48 @@ async function runWorkerCycle() {
|
|
|
33
18
|
let processed = 0;
|
|
34
19
|
for (const message of pulled.messages) {
|
|
35
20
|
try {
|
|
36
|
-
await
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
21
|
+
await sdk.claimTask({
|
|
22
|
+
id: message.body.taskId,
|
|
23
|
+
workerId: config.workerId,
|
|
24
|
+
leaseSeconds: config.leaseSeconds,
|
|
25
|
+
actor: "worker"
|
|
41
26
|
});
|
|
42
|
-
const context = await
|
|
43
|
-
|
|
44
|
-
"/internal/context/resolve-task",
|
|
45
|
-
{ taskId: message.body.taskId }
|
|
46
|
-
);
|
|
47
|
-
const task = context.payload.task;
|
|
27
|
+
const context = await buildTaskContext(sdk, message.body.taskId);
|
|
28
|
+
const task = context.task;
|
|
48
29
|
const payload = task && typeof task.payloadJson === "string" ? JSON.parse(task.payloadJson) : {};
|
|
49
|
-
await
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
30
|
+
await sdk.recordTaskProgress({
|
|
31
|
+
id: message.body.taskId,
|
|
32
|
+
workerId: config.workerId,
|
|
33
|
+
state: "running",
|
|
34
|
+
appendEvent: {
|
|
35
|
+
kind: "worker_started",
|
|
36
|
+
data: { workerId: config.workerId, queueAttempt: message.attempts }
|
|
37
|
+
},
|
|
38
|
+
actor: "worker"
|
|
58
39
|
});
|
|
59
|
-
await
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
40
|
+
await sdk.completeTask({
|
|
41
|
+
id: message.body.taskId,
|
|
42
|
+
output: {
|
|
43
|
+
workerId: config.workerId,
|
|
44
|
+
queueAttempt: message.attempts,
|
|
45
|
+
payload
|
|
46
|
+
},
|
|
47
|
+
summary: {
|
|
48
|
+
status: "completed",
|
|
49
|
+
workerId: config.workerId
|
|
50
|
+
},
|
|
51
|
+
actor: "worker"
|
|
71
52
|
});
|
|
72
53
|
await queue.ack([message.leaseId]);
|
|
73
54
|
processed += 1;
|
|
74
55
|
} catch (error) {
|
|
75
56
|
const retryDelaySeconds = Math.min(300, Math.max(15, message.attempts * 30));
|
|
76
|
-
await
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
57
|
+
await sdk.failTask({
|
|
58
|
+
id: message.body.taskId,
|
|
59
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
60
|
+
retryable: true,
|
|
61
|
+
nextVisibleAt: new Date(Date.now() + retryDelaySeconds * 1e3).toISOString(),
|
|
62
|
+
actor: "worker"
|
|
82
63
|
}).catch(() => null);
|
|
83
64
|
await queue.retry([{ leaseId: message.leaseId, delaySeconds: retryDelaySeconds }]);
|
|
84
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treeseed/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"description": "Treeseed integrated platform starter for Astro/Starlight web runtimes and Hono API runtimes.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"dev": "node ./scripts/run-ts.mjs ./scripts/dev-platform.ts",
|
|
41
41
|
"dev:web": "node ./scripts/run-ts.mjs ./scripts/dev-platform.ts --surface web",
|
|
42
42
|
"dev:api": "node ./scripts/run-ts.mjs ./scripts/dev-platform.ts --surface api",
|
|
43
|
-
"dev:manager": "node ./scripts/run-ts.mjs ./src/services/manager.ts",
|
|
44
43
|
"dev:worker": "node ./scripts/run-ts.mjs ./src/services/worker.ts",
|
|
44
|
+
"dev:remote-runner": "node ./scripts/run-ts.mjs ./src/services/remote-runner.ts",
|
|
45
45
|
"dev:workday-start": "node ./scripts/run-ts.mjs ./src/services/workday-start.ts",
|
|
46
46
|
"dev:workday-report": "node ./scripts/run-ts.mjs ./src/services/workday-report.ts",
|
|
47
47
|
"dev:watch": "node ./scripts/run-ts.mjs ./scripts/dev-platform.ts --watch",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"release:publish": "node ./scripts/run-ts.mjs ./scripts/publish-package.ts"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@treeseed/sdk": "^0.4.
|
|
70
|
+
"@treeseed/sdk": "^0.4.9",
|
|
71
71
|
"@astrojs/check": "^0.9.8",
|
|
72
72
|
"@astrojs/cloudflare": "^12.6.13",
|
|
73
73
|
"@astrojs/sitemap": "3.7.0",
|
|
@@ -159,14 +159,14 @@
|
|
|
159
159
|
"./tenant": "./dist/tenant/bridge.js",
|
|
160
160
|
"./scripts/dev-platform": "./dist/scripts/dev-platform.js",
|
|
161
161
|
"./scripts/workspace-bootstrap": "./dist/scripts/workspace-bootstrap.js",
|
|
162
|
-
"./services/manager": {
|
|
163
|
-
"types": "./dist/services/manager.d.ts",
|
|
164
|
-
"default": "./dist/services/manager.js"
|
|
165
|
-
},
|
|
166
162
|
"./services/worker": {
|
|
167
163
|
"types": "./dist/services/worker.d.ts",
|
|
168
164
|
"default": "./dist/services/worker.js"
|
|
169
165
|
},
|
|
166
|
+
"./services/remote-runner": {
|
|
167
|
+
"types": "./dist/services/remote-runner.d.ts",
|
|
168
|
+
"default": "./dist/services/remote-runner.js"
|
|
169
|
+
},
|
|
170
170
|
"./services/workday-start": {
|
|
171
171
|
"types": "./dist/services/workday-start.d.ts",
|
|
172
172
|
"default": "./dist/services/workday-start.js"
|
|
@@ -179,10 +179,6 @@
|
|
|
179
179
|
"types": "./dist/api/app.d.ts",
|
|
180
180
|
"default": "./dist/api/app.js"
|
|
181
181
|
},
|
|
182
|
-
"./api/gateway": {
|
|
183
|
-
"types": "./dist/api/gateway.d.ts",
|
|
184
|
-
"default": "./dist/api/gateway.js"
|
|
185
|
-
},
|
|
186
182
|
"./site-resources": {
|
|
187
183
|
"types": "./dist/site-resources.d.ts",
|
|
188
184
|
"default": "./dist/site-resources.js"
|
package/dist/api/gateway.d.ts
DELETED