@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,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
+ };