@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.
- package/Dockerfile +7 -0
- package/README.md +198 -0
- package/dist/agent-runtime.d.ts +17 -0
- package/dist/agent-runtime.js +117 -0
- package/dist/agents/adapters/execution.d.ts +41 -0
- package/dist/agents/adapters/execution.js +73 -0
- package/dist/agents/adapters/mutations.d.ts +22 -0
- package/dist/agents/adapters/mutations.js +30 -0
- package/dist/agents/adapters/notification.d.ts +26 -0
- package/dist/agents/adapters/notification.js +46 -0
- package/dist/agents/adapters/repository.d.ts +28 -0
- package/dist/agents/adapters/repository.js +61 -0
- package/dist/agents/adapters/research.d.ts +26 -0
- package/dist/agents/adapters/research.js +59 -0
- package/dist/agents/adapters/verification.d.ts +36 -0
- package/dist/agents/adapters/verification.js +62 -0
- package/dist/agents/cli-tools.d.ts +1 -0
- package/dist/agents/cli-tools.js +5 -0
- package/dist/agents/cli.d.ts +15 -0
- package/dist/agents/cli.js +109 -0
- package/dist/agents/contracts/messages.d.ts +88 -0
- package/dist/agents/contracts/messages.js +138 -0
- package/dist/agents/contracts/run.d.ts +21 -0
- package/dist/agents/contracts/run.js +0 -0
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.js +5 -0
- package/dist/agents/kernel/agent-kernel.d.ts +63 -0
- package/dist/agents/kernel/agent-kernel.js +291 -0
- package/dist/agents/kernel/trigger-resolver.d.ts +19 -0
- package/dist/agents/kernel/trigger-resolver.js +157 -0
- package/dist/agents/registry-helper.d.ts +4 -0
- package/dist/agents/registry-helper.js +14 -0
- package/dist/agents/registry.d.ts +6 -0
- package/dist/agents/registry.js +98 -0
- package/dist/agents/runtime-types.d.ts +118 -0
- package/dist/agents/runtime-types.js +0 -0
- package/dist/agents/spec-loader.d.ts +18 -0
- package/dist/agents/spec-loader.js +54 -0
- package/dist/agents/spec-normalizer.d.ts +2 -0
- package/dist/agents/spec-normalizer.js +327 -0
- package/dist/agents/spec-types.d.ts +64 -0
- package/dist/agents/spec-types.js +0 -0
- package/dist/agents/testing/agents-smoke.d.ts +1 -0
- package/dist/agents/testing/agents-smoke.js +32 -0
- package/dist/agents/testing/e2e-harness.d.ts +44 -0
- package/dist/agents/testing/e2e-harness.js +503 -0
- package/dist/api/agent-routes.d.ts +13 -0
- package/dist/api/agent-routes.js +327 -0
- package/dist/api/app.d.ts +8 -0
- package/dist/api/app.js +444 -0
- package/dist/api/auth/d1-database.d.ts +3 -0
- package/dist/api/auth/d1-database.js +20 -0
- package/dist/api/auth/d1-provider.d.ts +79 -0
- package/dist/api/auth/d1-provider.js +92 -0
- package/dist/api/auth/d1-store.d.ts +114 -0
- package/dist/api/auth/d1-store.js +895 -0
- package/dist/api/auth/memory-provider.d.ts +77 -0
- package/dist/api/auth/memory-provider.js +249 -0
- package/dist/api/auth/rbac.d.ts +22 -0
- package/dist/api/auth/rbac.js +162 -0
- package/dist/api/auth/tokens.d.ts +18 -0
- package/dist/api/auth/tokens.js +56 -0
- package/dist/api/capabilities.d.ts +9 -0
- package/dist/api/capabilities.js +33 -0
- package/dist/api/config.d.ts +2 -0
- package/dist/api/config.js +77 -0
- package/dist/api/http.d.ts +28 -0
- package/dist/api/http.js +51 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/index.js +20 -0
- package/dist/api/operations-routes.d.ts +11 -0
- package/dist/api/operations-routes.js +87 -0
- package/dist/api/operations.d.ts +3 -0
- package/dist/api/operations.js +26 -0
- package/dist/api/project-routes.d.ts +8 -0
- package/dist/api/project-routes.js +585 -0
- package/dist/api/providers.d.ts +2 -0
- package/dist/api/providers.js +62 -0
- package/dist/api/railway.d.ts +51 -0
- package/dist/api/railway.js +71 -0
- package/dist/api/sdk-dispatch.d.ts +5 -0
- package/dist/api/sdk-dispatch.js +13 -0
- package/dist/api/sdk-routes.d.ts +11 -0
- package/dist/api/sdk-routes.js +29 -0
- package/dist/api/server.d.ts +2 -0
- package/dist/api/server.js +10 -0
- package/dist/api/templates.d.ts +3 -0
- package/dist/api/templates.js +31 -0
- package/dist/api/types.d.ts +237 -0
- package/dist/api/types.js +0 -0
- package/dist/env.yaml +957 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +41 -0
- package/dist/scripts/assert-release-tag-version.d.ts +1 -0
- package/dist/scripts/assert-release-tag-version.js +20 -0
- package/dist/scripts/build-dist.d.ts +1 -0
- package/dist/scripts/build-dist.js +106 -0
- package/dist/scripts/package-tools.d.ts +1 -0
- package/dist/scripts/package-tools.js +7 -0
- package/dist/scripts/publish-package.d.ts +1 -0
- package/dist/scripts/publish-package.js +24 -0
- package/dist/scripts/release-verify.d.ts +1 -0
- package/dist/scripts/release-verify.js +152 -0
- package/dist/scripts/test-smoke.d.ts +1 -0
- package/dist/scripts/test-smoke.js +23 -0
- package/dist/scripts/treeseed-agent-api.d.ts +2 -0
- package/dist/scripts/treeseed-agent-api.js +25 -0
- package/dist/scripts/treeseed-agent-service.d.ts +2 -0
- package/dist/scripts/treeseed-agent-service.js +36 -0
- package/dist/scripts/treeseed-agents.d.ts +2 -0
- package/dist/scripts/treeseed-agents.js +13 -0
- package/dist/services/agents.d.ts +17 -0
- package/dist/services/agents.js +48 -0
- package/dist/services/common.d.ts +66 -0
- package/dist/services/common.js +212 -0
- package/dist/services/index.d.ts +6 -0
- package/dist/services/index.js +19 -0
- package/dist/services/manager.d.ts +333 -0
- package/dist/services/manager.js +1368 -0
- package/dist/services/remote-runner.d.ts +30 -0
- package/dist/services/remote-runner.js +230 -0
- package/dist/services/workday-content.d.ts +53 -0
- package/dist/services/workday-content.js +190 -0
- package/dist/services/workday-manager.d.ts +391 -0
- package/dist/services/workday-manager.js +163 -0
- package/dist/services/workday-report.d.ts +238 -0
- package/dist/services/workday-report.js +17 -0
- package/dist/services/workday-start.d.ts +238 -0
- package/dist/services/workday-start.js +17 -0
- package/dist/services/worker-capacity.d.ts +58 -0
- package/dist/services/worker-capacity.js +208 -0
- package/dist/services/worker-pool-scaler.d.ts +27 -0
- package/dist/services/worker-pool-scaler.js +127 -0
- package/dist/services/worker.d.ts +19 -0
- package/dist/services/worker.js +436 -0
- package/dist/templates/github/deploy-processing.workflow.yml +119 -0
- package/package.json +136 -0
- package/templates/github/deploy-processing.workflow.yml +119 -0
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
idle: boolean;
|
|
19
|
+
reason: string;
|
|
20
|
+
} | {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
processed: number;
|
|
23
|
+
idle?: undefined;
|
|
24
|
+
reason?: undefined;
|
|
25
|
+
}>;
|
|
26
|
+
export declare function startRemoteRunnerLoop(options?: {
|
|
27
|
+
sdk?: AgentSdk;
|
|
28
|
+
config?: ReturnType<typeof resolveRemoteRunnerConfig>;
|
|
29
|
+
fetchImpl?: typeof fetch;
|
|
30
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { RemoteTreeseedClient, RemoteTreeseedRunnerClient, TreeseedOperationsSdk } 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
|
+
if (process.env.TREESEED_LOCAL_DEV_MODE?.trim()) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
throw new Error(
|
|
31
|
+
"Remote runner requires TREESEED_MARKET_API_BASE_URL (or TREESEED_API_BASE_URL) and TREESEED_PROJECT_RUNNER_TOKEN."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return new RemoteTreeseedRunnerClient(new RemoteTreeseedClient({
|
|
35
|
+
hosts: [{ id: "market", baseUrl: config.marketBaseUrl }],
|
|
36
|
+
activeHostId: "market",
|
|
37
|
+
auth: {
|
|
38
|
+
accessToken: config.runnerToken
|
|
39
|
+
}
|
|
40
|
+
}, {
|
|
41
|
+
fetchImpl
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
function asRecord(value) {
|
|
45
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
46
|
+
}
|
|
47
|
+
function runnerWorkspacePaths(projectId) {
|
|
48
|
+
const volumeRoot = envValue("TREESEED_WORKER_VOLUME_ROOT") || "/data";
|
|
49
|
+
const workspaceRoot = `${volumeRoot}/workspaces/${projectId}`;
|
|
50
|
+
return {
|
|
51
|
+
root: workspaceRoot,
|
|
52
|
+
site: `${workspaceRoot}/site`,
|
|
53
|
+
content: `${workspaceRoot}/content`,
|
|
54
|
+
parent: `${workspaceRoot}/workspace-root`
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function inputForRunnerJob(job) {
|
|
58
|
+
const input = asRecord(job.input);
|
|
59
|
+
if (job.namespace !== "content" || job.operation !== "publish") {
|
|
60
|
+
return input;
|
|
61
|
+
}
|
|
62
|
+
const paths = runnerWorkspacePaths(job.projectId);
|
|
63
|
+
return {
|
|
64
|
+
...input,
|
|
65
|
+
tenantRoot: typeof input.tenantRoot === "string" ? input.tenantRoot : paths.site,
|
|
66
|
+
contentRepositoryRoot: typeof input.contentRepositoryRoot === "string" ? input.contentRepositoryRoot : paths.content,
|
|
67
|
+
workspaceRoot: typeof input.workspaceRoot === "string" ? input.workspaceRoot : paths.root
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function prepareLaunchIntentWithCredentialSessions(runner, jobId, launchJobInput, launchIntent) {
|
|
71
|
+
const sessions = asRecord(launchJobInput.credentialSessions);
|
|
72
|
+
const nextIntent = JSON.parse(JSON.stringify(launchIntent));
|
|
73
|
+
const execution = asRecord(nextIntent.execution);
|
|
74
|
+
const providerLaunchInput = asRecord(execution.providerLaunchInput);
|
|
75
|
+
const envOverlay = {};
|
|
76
|
+
const consume = async (key) => {
|
|
77
|
+
const sessionId = typeof sessions[key] === "string" ? sessions[key].trim() : "";
|
|
78
|
+
if (!sessionId || !runner) return null;
|
|
79
|
+
return (await runner.consumeCredentialSession(jobId, sessionId)).payload;
|
|
80
|
+
};
|
|
81
|
+
const repositorySession = await consume("repositoryHost");
|
|
82
|
+
if (repositorySession?.config) {
|
|
83
|
+
const token = repositorySession.config.GH_TOKEN ?? repositorySession.config.GITHUB_TOKEN;
|
|
84
|
+
if (token) {
|
|
85
|
+
envOverlay.GH_TOKEN = token;
|
|
86
|
+
envOverlay.GITHUB_TOKEN = repositorySession.config.GITHUB_TOKEN ?? token;
|
|
87
|
+
}
|
|
88
|
+
const owner = repositorySession.config.organizationOrOwner ?? repositorySession.config.owner;
|
|
89
|
+
if (owner) {
|
|
90
|
+
nextIntent.repository = {
|
|
91
|
+
...asRecord(nextIntent.repository),
|
|
92
|
+
owner
|
|
93
|
+
};
|
|
94
|
+
providerLaunchInput.repoOwner = owner;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const webSession = await consume("webHost");
|
|
98
|
+
if (webSession?.config) {
|
|
99
|
+
providerLaunchInput.cloudflareHost = {
|
|
100
|
+
...asRecord(providerLaunchInput.cloudflareHost),
|
|
101
|
+
config: webSession.config
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const processingSession = await consume("processingHost");
|
|
105
|
+
if (processingSession?.config) {
|
|
106
|
+
providerLaunchInput.processingHost = {
|
|
107
|
+
...asRecord(providerLaunchInput.processingHost),
|
|
108
|
+
config: processingSession.config
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
nextIntent.execution = {
|
|
112
|
+
...execution,
|
|
113
|
+
providerLaunchInput
|
|
114
|
+
};
|
|
115
|
+
return { intent: nextIntent, envOverlay };
|
|
116
|
+
}
|
|
117
|
+
async function runRemoteRunnerCycle(options = {}) {
|
|
118
|
+
const config = options.config ?? resolveRemoteRunnerConfig();
|
|
119
|
+
const sdk = options.sdk ?? createServiceSdk();
|
|
120
|
+
const runner = createRunnerClient(config, options.fetchImpl);
|
|
121
|
+
if (!runner) {
|
|
122
|
+
return { ok: true, processed: 0, idle: true, reason: "registration_unconfigured" };
|
|
123
|
+
}
|
|
124
|
+
const pulled = await runner.pull(config.projectId, {
|
|
125
|
+
limit: config.batchSize,
|
|
126
|
+
runnerId: config.runnerId
|
|
127
|
+
});
|
|
128
|
+
if (pulled.payload.length === 0) {
|
|
129
|
+
return { ok: true, processed: 0 };
|
|
130
|
+
}
|
|
131
|
+
let processed = 0;
|
|
132
|
+
for (const job of pulled.payload) {
|
|
133
|
+
try {
|
|
134
|
+
await runner.progress(job.id, {
|
|
135
|
+
summary: `Running ${job.namespace}:${job.operation}`,
|
|
136
|
+
data: {
|
|
137
|
+
runnerId: config.runnerId,
|
|
138
|
+
status: "running"
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
const launchJobInput = job.input && typeof job.input === "object" ? job.input : {};
|
|
142
|
+
const launchIntent = launchJobInput.launchIntent && typeof launchJobInput.launchIntent === "object" ? launchJobInput.launchIntent : launchJobInput;
|
|
143
|
+
const isLaunchJob = job.namespace === "workflow" && job.operation === "launch_project";
|
|
144
|
+
if (isLaunchJob) {
|
|
145
|
+
await runner.progress(job.id, {
|
|
146
|
+
summary: "Validating launch plan and preparing repository topology.",
|
|
147
|
+
data: {
|
|
148
|
+
runnerId: config.runnerId,
|
|
149
|
+
phase: "preflight_running",
|
|
150
|
+
status: "running",
|
|
151
|
+
title: "Validating launch plan"
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const preparedLaunch = isLaunchJob ? await prepareLaunchIntentWithCredentialSessions(runner, job.id, launchJobInput, launchIntent) : null;
|
|
156
|
+
const result = isLaunchJob ? await new TreeseedOperationsSdk().execute({
|
|
157
|
+
operationName: launchJobInput.resume === true ? "hub.resume_launch" : "hub.execute_launch",
|
|
158
|
+
input: preparedLaunch?.intent ?? launchIntent
|
|
159
|
+
}, {
|
|
160
|
+
cwd: process.env.TREESEED_MARKET_REPO_ROOT?.trim() || process.cwd(),
|
|
161
|
+
env: {
|
|
162
|
+
...process.env,
|
|
163
|
+
...preparedLaunch?.envOverlay ?? {}
|
|
164
|
+
},
|
|
165
|
+
transport: "sdk",
|
|
166
|
+
onProgress: async (event) => {
|
|
167
|
+
if (event.kind !== "hub_launch_phase") return;
|
|
168
|
+
await runner.progress(job.id, {
|
|
169
|
+
summary: typeof event.summary === "string" ? event.summary : null,
|
|
170
|
+
data: {
|
|
171
|
+
...event,
|
|
172
|
+
runnerId: config.runnerId
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}) : await sdk.dispatch({
|
|
177
|
+
namespace: job.namespace,
|
|
178
|
+
operation: job.operation,
|
|
179
|
+
input: inputForRunnerJob(job),
|
|
180
|
+
preferredMode: "prefer_local"
|
|
181
|
+
});
|
|
182
|
+
if (isLaunchJob) {
|
|
183
|
+
await runner.progress(job.id, {
|
|
184
|
+
summary: "Launch execution completed; applying control-plane projections.",
|
|
185
|
+
data: {
|
|
186
|
+
runnerId: config.runnerId,
|
|
187
|
+
phase: "projection_running",
|
|
188
|
+
status: "running",
|
|
189
|
+
title: "Recording launch result"
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
await runner.complete(job.id, {
|
|
194
|
+
output: result.mode === "inline" ? result.payload : result
|
|
195
|
+
});
|
|
196
|
+
processed += 1;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
await runner.fail(job.id, {
|
|
199
|
+
code: "runner_execution_failed",
|
|
200
|
+
message: error instanceof Error ? error.message : String(error)
|
|
201
|
+
}).catch(() => null);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { ok: true, processed };
|
|
205
|
+
}
|
|
206
|
+
async function startRemoteRunnerLoop(options = {}) {
|
|
207
|
+
const config = options.config ?? resolveRemoteRunnerConfig();
|
|
208
|
+
for (; ; ) {
|
|
209
|
+
try {
|
|
210
|
+
await runRemoteRunnerCycle({
|
|
211
|
+
...options,
|
|
212
|
+
config
|
|
213
|
+
});
|
|
214
|
+
} catch (error) {
|
|
215
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
216
|
+
`);
|
|
217
|
+
}
|
|
218
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, config.pollIntervalMs));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
222
|
+
const entryFile = process.argv[1] ?? "";
|
|
223
|
+
if (entryFile === currentFile) {
|
|
224
|
+
await startRemoteRunnerLoop();
|
|
225
|
+
}
|
|
226
|
+
export {
|
|
227
|
+
resolveRemoteRunnerConfig,
|
|
228
|
+
runRemoteRunnerCycle,
|
|
229
|
+
startRemoteRunnerLoop
|
|
230
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { PrioritySnapshot, ScaleDecision, WorkerPoolScaleResult } from '@treeseed/sdk';
|
|
2
|
+
type JsonRecord = Record<string, unknown>;
|
|
3
|
+
export interface WorkdayContentTaskSummary {
|
|
4
|
+
id: string;
|
|
5
|
+
agentId?: string;
|
|
6
|
+
type?: string;
|
|
7
|
+
state?: string;
|
|
8
|
+
priority?: number;
|
|
9
|
+
idempotencyKey?: string;
|
|
10
|
+
createdAt?: string | null;
|
|
11
|
+
startedAt?: string | null;
|
|
12
|
+
completedAt?: string | null;
|
|
13
|
+
lastErrorCode?: string | null;
|
|
14
|
+
lastErrorMessage?: string | null;
|
|
15
|
+
lastEventKind?: string | null;
|
|
16
|
+
outputCount?: number;
|
|
17
|
+
changedFiles?: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface WorkdayContentReleaseRecord {
|
|
20
|
+
id?: string;
|
|
21
|
+
deploymentKind: string;
|
|
22
|
+
status: string;
|
|
23
|
+
releaseTag?: string | null;
|
|
24
|
+
commitSha?: string | null;
|
|
25
|
+
sourceRef?: string | null;
|
|
26
|
+
startedAt?: string | null;
|
|
27
|
+
finishedAt?: string | null;
|
|
28
|
+
createdAt?: string | null;
|
|
29
|
+
}
|
|
30
|
+
export interface WorkdayContentSnapshotInput {
|
|
31
|
+
repoRoot: string;
|
|
32
|
+
projectId: string;
|
|
33
|
+
teamId: string;
|
|
34
|
+
environment: string;
|
|
35
|
+
workDay: JsonRecord;
|
|
36
|
+
summary: JsonRecord;
|
|
37
|
+
prioritySnapshot: PrioritySnapshot | null;
|
|
38
|
+
scaleDecision: ScaleDecision;
|
|
39
|
+
scaleResult: WorkerPoolScaleResult;
|
|
40
|
+
tasks: WorkdayContentTaskSummary[];
|
|
41
|
+
changedFiles: string[];
|
|
42
|
+
releases: WorkdayContentReleaseRecord[];
|
|
43
|
+
generatedAt: string;
|
|
44
|
+
}
|
|
45
|
+
export interface WorkdayContentSnapshotResult {
|
|
46
|
+
filePath: string;
|
|
47
|
+
relativePath: string;
|
|
48
|
+
slug: string;
|
|
49
|
+
reportVersion: string;
|
|
50
|
+
title: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function writeWorkdayContentSnapshot(input: WorkdayContentSnapshotInput): WorkdayContentSnapshotResult;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { relative, resolve } from "node:path";
|
|
4
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
5
|
+
function stableHash(value) {
|
|
6
|
+
return createHash("sha256").update(value).digest("hex");
|
|
7
|
+
}
|
|
8
|
+
function sanitizeSegment(value, fallback) {
|
|
9
|
+
const normalized = value.trim().replaceAll(/[\\/]+/g, "-").replaceAll(/[^a-zA-Z0-9._-]+/g, "-").replaceAll(/-+/g, "-").replaceAll(/^-|-$/g, "");
|
|
10
|
+
return normalized || fallback;
|
|
11
|
+
}
|
|
12
|
+
function compactTimestamp(value) {
|
|
13
|
+
return value.replaceAll(/[-:]/g, "").replace(/\.\d{3}Z$/u, "Z");
|
|
14
|
+
}
|
|
15
|
+
function toIsoDate(value) {
|
|
16
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const parsed = new Date(value);
|
|
20
|
+
return Number.isFinite(parsed.valueOf()) ? parsed.toISOString() : null;
|
|
21
|
+
}
|
|
22
|
+
function bodySummary(summary) {
|
|
23
|
+
return typeof summary.summary === "string" && summary.summary.trim() ? summary.summary.trim() : `Completed ${Number(summary.completedTasks ?? 0)} tasks, with ${Number(summary.failedTasks ?? 0)} failures and ${Number(summary.remainingTaskCredits ?? 0)} remaining task credits.`;
|
|
24
|
+
}
|
|
25
|
+
function renderTasks(tasks) {
|
|
26
|
+
if (tasks.length === 0) {
|
|
27
|
+
return "- No tasks were recorded.\n";
|
|
28
|
+
}
|
|
29
|
+
return tasks.map((task) => {
|
|
30
|
+
const suffix = [
|
|
31
|
+
task.state ? `state: ${task.state}` : null,
|
|
32
|
+
task.agentId ? `agent: ${task.agentId}` : null,
|
|
33
|
+
Number.isFinite(task.priority) ? `priority: ${task.priority}` : null,
|
|
34
|
+
task.lastEventKind ? `last event: ${task.lastEventKind}` : null,
|
|
35
|
+
task.outputCount ? `outputs: ${task.outputCount}` : null
|
|
36
|
+
].filter(Boolean).join(", ");
|
|
37
|
+
return `- \`${task.id}\` ${task.type ?? "task"}${suffix ? ` (${suffix})` : ""}`;
|
|
38
|
+
}).join("\n") + "\n";
|
|
39
|
+
}
|
|
40
|
+
function renderChangedFiles(changedFiles) {
|
|
41
|
+
if (changedFiles.length === 0) {
|
|
42
|
+
return "- No changed files were reported by task outputs.\n";
|
|
43
|
+
}
|
|
44
|
+
return changedFiles.map((filePath) => `- \`${filePath}\``).join("\n") + "\n";
|
|
45
|
+
}
|
|
46
|
+
function renderReleases(releases) {
|
|
47
|
+
if (releases.length === 0) {
|
|
48
|
+
return "- No releases or deployments were recorded during this workday.\n";
|
|
49
|
+
}
|
|
50
|
+
return releases.map((release) => {
|
|
51
|
+
const label = release.releaseTag || release.commitSha || release.id || release.deploymentKind;
|
|
52
|
+
const details = [release.deploymentKind, release.status, release.sourceRef].filter(Boolean).join(", ");
|
|
53
|
+
return `- \`${label}\`${details ? ` (${details})` : ""}`;
|
|
54
|
+
}).join("\n") + "\n";
|
|
55
|
+
}
|
|
56
|
+
function renderPriorityItems(snapshot) {
|
|
57
|
+
if (!snapshot?.items?.length) {
|
|
58
|
+
return "- No priority snapshot items were captured.\n";
|
|
59
|
+
}
|
|
60
|
+
return snapshot.items.map((item) => {
|
|
61
|
+
const details = [
|
|
62
|
+
item.model,
|
|
63
|
+
Number.isFinite(item.priority) ? `priority: ${item.priority}` : null,
|
|
64
|
+
Number.isFinite(item.estimatedCredits) ? `credits: ${item.estimatedCredits}` : null
|
|
65
|
+
].filter(Boolean).join(", ");
|
|
66
|
+
return `- \`${item.id}\`${item.title ? ` ${item.title}` : ""}${details ? ` (${details})` : ""}`;
|
|
67
|
+
}).join("\n") + "\n";
|
|
68
|
+
}
|
|
69
|
+
function buildMarkdownBody(input) {
|
|
70
|
+
const summaryText = bodySummary(input.summary);
|
|
71
|
+
return [
|
|
72
|
+
summaryText,
|
|
73
|
+
"",
|
|
74
|
+
"## Budget",
|
|
75
|
+
"",
|
|
76
|
+
`- Daily task-credit budget: ${Number(input.summary.dailyTaskCreditBudget ?? 0)}`,
|
|
77
|
+
`- Used task credits: ${Number(input.summary.usedTaskCredits ?? 0)}`,
|
|
78
|
+
`- Remaining task credits: ${Number(input.summary.remainingTaskCredits ?? 0)}`,
|
|
79
|
+
`- Credit ledger entries: ${Number(input.summary.creditLedgerEntries ?? 0)}`,
|
|
80
|
+
"",
|
|
81
|
+
"## Priority Plan",
|
|
82
|
+
"",
|
|
83
|
+
renderPriorityItems(input.prioritySnapshot).trimEnd(),
|
|
84
|
+
"",
|
|
85
|
+
"## Tasks",
|
|
86
|
+
"",
|
|
87
|
+
renderTasks(input.tasks).trimEnd(),
|
|
88
|
+
"",
|
|
89
|
+
"## Changed Files",
|
|
90
|
+
"",
|
|
91
|
+
renderChangedFiles(input.changedFiles).trimEnd(),
|
|
92
|
+
"",
|
|
93
|
+
"## Releases",
|
|
94
|
+
"",
|
|
95
|
+
renderReleases(input.releases).trimEnd(),
|
|
96
|
+
"",
|
|
97
|
+
"## Final Status",
|
|
98
|
+
"",
|
|
99
|
+
`- Workday state: ${String(input.workDay.state ?? "completed")}`,
|
|
100
|
+
`- Desired workers: ${Number(input.scaleDecision.desiredWorkers ?? 0)}`,
|
|
101
|
+
`- Queue depth at report: ${Number(input.scaleDecision.observedQueueDepth ?? 0)}`,
|
|
102
|
+
`- Active leases at report: ${Number(input.scaleDecision.observedActiveLeases ?? 0)}`,
|
|
103
|
+
`- Scale provider: ${input.scaleResult.provider}`,
|
|
104
|
+
""
|
|
105
|
+
].join("\n");
|
|
106
|
+
}
|
|
107
|
+
function writeWorkdayContentSnapshot(input) {
|
|
108
|
+
const outputRoot = resolve(input.repoRoot, "src/content/workdays");
|
|
109
|
+
mkdirSync(outputRoot, { recursive: true });
|
|
110
|
+
const workDayId = String(input.workDay.id ?? "workday");
|
|
111
|
+
const startedAt = toIsoDate(input.workDay.startedAt ?? input.workDay.started_at) ?? input.generatedAt;
|
|
112
|
+
const endedAt = toIsoDate(input.workDay.endedAt ?? input.workDay.ended_at);
|
|
113
|
+
const generatedAt = toIsoDate(input.generatedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
114
|
+
const datePart = (startedAt || generatedAt).slice(0, 10);
|
|
115
|
+
const slugBase = `${datePart}/${sanitizeSegment(workDayId, "workday")}`;
|
|
116
|
+
const identityHash = stableHash(JSON.stringify({
|
|
117
|
+
workDayId,
|
|
118
|
+
generatedAt,
|
|
119
|
+
summary: input.summary,
|
|
120
|
+
changedFiles: input.changedFiles,
|
|
121
|
+
releases: input.releases
|
|
122
|
+
})).slice(0, 8);
|
|
123
|
+
const reportVersion = `${compactTimestamp(generatedAt)}-${identityHash}`;
|
|
124
|
+
const title = `Workday ${workDayId} Report ${generatedAt.slice(0, 10)}`;
|
|
125
|
+
const slug = `workdays/${slugBase}/${reportVersion}`;
|
|
126
|
+
const frontmatter = {
|
|
127
|
+
title,
|
|
128
|
+
slug,
|
|
129
|
+
workDayId,
|
|
130
|
+
reportVersion,
|
|
131
|
+
reportKind: "workday_summary",
|
|
132
|
+
projectId: input.projectId,
|
|
133
|
+
teamId: input.teamId,
|
|
134
|
+
environment: input.environment,
|
|
135
|
+
status: "live",
|
|
136
|
+
visibility: "team",
|
|
137
|
+
workdayState: String(input.workDay.state ?? "completed"),
|
|
138
|
+
startedAt,
|
|
139
|
+
endedAt,
|
|
140
|
+
generatedAt,
|
|
141
|
+
createdAt: generatedAt,
|
|
142
|
+
summary: bodySummary(input.summary),
|
|
143
|
+
dailyTaskCreditBudget: Number(input.summary.dailyTaskCreditBudget ?? 0),
|
|
144
|
+
usedTaskCredits: Number(input.summary.usedTaskCredits ?? 0),
|
|
145
|
+
remainingTaskCredits: Number(input.summary.remainingTaskCredits ?? 0),
|
|
146
|
+
creditLedgerEntries: Number(input.summary.creditLedgerEntries ?? 0),
|
|
147
|
+
prioritySnapshotId: input.prioritySnapshot?.id ?? null,
|
|
148
|
+
priorityItemCount: input.prioritySnapshot?.items.length ?? 0,
|
|
149
|
+
priorityItems: input.prioritySnapshot?.items ?? [],
|
|
150
|
+
totalTasks: Number(input.summary.totalTasks ?? input.tasks.length),
|
|
151
|
+
completedTasks: Number(input.summary.completedTasks ?? 0),
|
|
152
|
+
failedTasks: Number(input.summary.failedTasks ?? 0),
|
|
153
|
+
queuedTasks: Number(input.summary.queuedTasks ?? 0),
|
|
154
|
+
activeTasks: Number(input.summary.activeTasks ?? 0),
|
|
155
|
+
taskItems: input.tasks,
|
|
156
|
+
changedFiles: input.changedFiles,
|
|
157
|
+
releases: input.releases,
|
|
158
|
+
scaleDecision: input.scaleDecision,
|
|
159
|
+
scaleResult: input.scaleResult,
|
|
160
|
+
metadata: {
|
|
161
|
+
source: "manager",
|
|
162
|
+
projectId: input.projectId
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const markdownBody = buildMarkdownBody(input);
|
|
166
|
+
let fileName = `${datePart}-${sanitizeSegment(workDayId, "workday")}--${reportVersion}.mdx`;
|
|
167
|
+
let filePath = resolve(outputRoot, fileName);
|
|
168
|
+
let duplicateCounter = 1;
|
|
169
|
+
while (existsSync(filePath)) {
|
|
170
|
+
fileName = `${datePart}-${sanitizeSegment(workDayId, "workday")}--${reportVersion}-${duplicateCounter}.mdx`;
|
|
171
|
+
filePath = resolve(outputRoot, fileName);
|
|
172
|
+
duplicateCounter += 1;
|
|
173
|
+
}
|
|
174
|
+
const document = `---
|
|
175
|
+
${stringifyYaml(frontmatter).trimEnd()}
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
${markdownBody}`;
|
|
179
|
+
writeFileSync(filePath, document, "utf8");
|
|
180
|
+
return {
|
|
181
|
+
filePath,
|
|
182
|
+
relativePath: relative(input.repoRoot, filePath).replaceAll("\\", "/"),
|
|
183
|
+
slug,
|
|
184
|
+
reportVersion,
|
|
185
|
+
title
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
export {
|
|
189
|
+
writeWorkdayContentSnapshot
|
|
190
|
+
};
|