@treeseed/core 0.4.13 → 0.5.3
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/dist/agents/adapters/notification.d.ts +16 -1
- package/dist/agents/adapters/notification.js +31 -1
- package/dist/agents/adapters/research.d.ts +13 -1
- package/dist/agents/adapters/research.js +35 -1
- package/dist/agents/contracts/run.d.ts +1 -0
- package/dist/agents/kernel/agent-kernel.d.ts +2 -2
- package/dist/agents/kernel/agent-kernel.js +10 -3
- package/dist/agents/kernel/trigger-resolver.d.ts +1 -0
- package/dist/agents/kernel/trigger-resolver.js +5 -1
- package/dist/agents/runtime-types.d.ts +1 -0
- package/dist/api/app.js +10 -0
- package/dist/api/auth/d1-store.js +5 -0
- package/dist/api/auth/memory-provider.js +6 -1
- package/dist/api/auth/rbac.d.ts +2 -2
- package/dist/api/auth/rbac.js +2 -0
- package/dist/api/capabilities.d.ts +9 -0
- package/dist/api/capabilities.js +33 -0
- package/dist/api/operations-routes.d.ts +4 -0
- package/dist/api/operations-routes.js +49 -1
- package/dist/api/project-routes.d.ts +8 -0
- package/dist/api/project-routes.js +586 -0
- package/dist/api/types.d.ts +7 -0
- package/dist/components/site/NotesList.astro +13 -2
- package/dist/components/site/PublishedContentBody.astro +5 -0
- package/dist/content.js +77 -9
- package/dist/dev.d.ts +2 -2
- package/dist/dev.js +0 -15
- package/dist/env.yaml +39 -26
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -1
- package/dist/launch.d.ts +3 -0
- package/dist/launch.js +8 -0
- package/dist/layouts/AuthoredEntryLayout.astro +76 -28
- package/dist/layouts/ProfileLayout.astro +9 -5
- package/dist/middleware.js +11 -0
- package/dist/pages/[slug].astro +10 -6
- package/dist/pages/agents/[slug].astro +17 -7
- package/dist/pages/agents/index.astro +2 -1
- package/dist/pages/books/[slug].astro +10 -5
- package/dist/pages/books/index.astro +4 -1
- package/dist/pages/decisions/[slug].astro +73 -0
- package/dist/pages/decisions/index.astro +47 -0
- package/dist/pages/docs-runtime/[...slug].astro +102 -0
- package/dist/pages/docs-runtime/index.astro +89 -0
- package/dist/pages/feed.xml.js +2 -1
- package/dist/pages/index.astro +160 -16
- package/dist/pages/notes/[slug].astro +10 -5
- package/dist/pages/notes/index.astro +6 -3
- package/dist/pages/objectives/[slug].astro +27 -9
- package/dist/pages/objectives/index.astro +19 -2
- package/dist/pages/people/[slug].astro +17 -7
- package/dist/pages/people/index.astro +2 -1
- package/dist/pages/proposals/[slug].astro +72 -0
- package/dist/pages/proposals/index.astro +47 -0
- package/dist/pages/questions/[slug].astro +27 -9
- package/dist/pages/questions/index.astro +19 -2
- package/dist/scripts/dev-platform.js +0 -1
- package/dist/scripts/release-verify.js +29 -2
- package/dist/scripts/tenant-build.js +4 -1
- package/dist/scripts/tenant-check.js +4 -1
- package/dist/services/agents.d.ts +1 -12
- package/dist/services/agents.js +28 -9
- package/dist/services/index.d.ts +0 -2
- package/dist/services/index.js +0 -6
- package/dist/services/manager.d.ts +4 -4
- package/dist/services/manager.js +123 -50
- package/dist/services/workday-report.d.ts +3 -3
- package/dist/services/workday-start.d.ts +3 -3
- package/dist/services/worker-capacity.d.ts +58 -0
- package/dist/services/worker-capacity.js +208 -0
- package/dist/services/worker.js +70 -13
- package/dist/site.js +18 -5
- package/dist/tenant/runtime-config.js +8 -1
- package/dist/utils/hub-content.js +14 -0
- package/dist/utils/published-content.js +13 -0
- package/dist/utils/site-config.js +20 -0
- package/dist/utils/site-content-runtime.js +185 -0
- package/dist/utils/web-cache.js +149 -0
- package/package.json +11 -6
- package/scripts/verify-driver.mjs +34 -0
- package/templates/github/deploy.workflow.yml +11 -1
|
@@ -3,9 +3,26 @@ import MainLayout from '../../layouts/MainLayout.astro';
|
|
|
3
3
|
import SectionIntro from '../../components/site/SectionIntro.astro';
|
|
4
4
|
import ChronicleList from '../../components/site/ChronicleList.astro';
|
|
5
5
|
import { getPublishedQuestions, resolveContributorsForEntries } from '../../utils/hub-content';
|
|
6
|
+
import { isPublishedRuntimeContentMode, loadPublishedCollection, resolvePublishedContributor, metadataFromPublishedContent, loadPublishedEntry } from '../../utils/site-content-runtime';
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
const
|
|
8
|
+
const publishedRuntime = isPublishedRuntimeContentMode();
|
|
9
|
+
const questions = publishedRuntime
|
|
10
|
+
? (await loadPublishedCollection(Astro.locals, 'questions')).sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
|
|
11
|
+
: await getPublishedQuestions();
|
|
12
|
+
const contributors = publishedRuntime
|
|
13
|
+
? new Map(
|
|
14
|
+
await Promise.all(
|
|
15
|
+
questions.map(async (question) => {
|
|
16
|
+
const detail = await loadPublishedEntry(Astro.locals, 'questions', question.id);
|
|
17
|
+
const contributor = await resolvePublishedContributor(
|
|
18
|
+
Astro.locals,
|
|
19
|
+
metadataFromPublishedContent(detail?.content)?.primaryContributor,
|
|
20
|
+
);
|
|
21
|
+
return [question.id, contributor ?? null] as const;
|
|
22
|
+
}),
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
: await resolveContributorsForEntries(questions);
|
|
9
26
|
---
|
|
10
27
|
|
|
11
28
|
<MainLayout title="Questions" description="Open questions guiding the TreeSeed working site." currentPath="/questions/">
|
|
@@ -9,9 +9,9 @@ const forbiddenPatterns = [
|
|
|
9
9
|
/['"`](?:\.\.\/|\.\/)[^'"`\n]*src\/[^'"`\n]*\.(?:[cm]?js|ts|tsx|json|astro|css)['"`]/,
|
|
10
10
|
/['"`][^'"`\n]*\/packages\/[^'"`\n]*\/src\/[^'"`\n]*['"`]/,
|
|
11
11
|
];
|
|
12
|
-
function run(command, args) {
|
|
12
|
+
function run(command, args, cwd = packageRoot) {
|
|
13
13
|
const result = spawnSync(command, args, {
|
|
14
|
-
cwd
|
|
14
|
+
cwd,
|
|
15
15
|
stdio: 'inherit',
|
|
16
16
|
env: process.env,
|
|
17
17
|
});
|
|
@@ -19,6 +19,32 @@ function run(command, args) {
|
|
|
19
19
|
process.exit(result.status ?? 1);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
function assertNoLocalDependencyLinks() {
|
|
23
|
+
const packageJson = JSON.parse(readFileSync(resolve(packageRoot, 'package.json'), 'utf8'));
|
|
24
|
+
for (const sectionName of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
|
|
25
|
+
for (const [dependencyName, version] of Object.entries(packageJson[sectionName] ?? {})) {
|
|
26
|
+
if (version.startsWith('workspace:') || version.startsWith('file:')) {
|
|
27
|
+
throw new Error(`package.json ${sectionName}.${dependencyName} must not use local dependency specifiers: ${version}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const lockfile = JSON.parse(readFileSync(resolve(packageRoot, 'package-lock.json'), 'utf8'));
|
|
32
|
+
for (const [entryKey, entryValue] of Object.entries(lockfile.packages ?? {})) {
|
|
33
|
+
if (entryKey.startsWith('../') || entryKey.includes('/../')) {
|
|
34
|
+
throw new Error(`package-lock.json contains forbidden local package entry: ${entryKey}`);
|
|
35
|
+
}
|
|
36
|
+
if (entryValue.link) {
|
|
37
|
+
throw new Error(`package-lock.json contains forbidden linked dependency entry: ${entryKey}`);
|
|
38
|
+
}
|
|
39
|
+
const resolved = entryValue.resolved ?? '';
|
|
40
|
+
if (resolved.startsWith('../')
|
|
41
|
+
|| resolved.startsWith('./')
|
|
42
|
+
|| resolved.startsWith('file:')
|
|
43
|
+
|| resolved.startsWith('workspace:')) {
|
|
44
|
+
throw new Error(`package-lock.json contains forbidden local resolution for ${entryKey}: ${resolved}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
22
48
|
function walkFiles(root) {
|
|
23
49
|
const files = [];
|
|
24
50
|
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
@@ -43,6 +69,7 @@ function scanDirectory(root) {
|
|
|
43
69
|
}
|
|
44
70
|
}
|
|
45
71
|
}
|
|
72
|
+
assertNoLocalDependencyLinks();
|
|
46
73
|
run('npm', ['run', 'lint']);
|
|
47
74
|
scanDirectory(resolve(packageRoot, 'dist'));
|
|
48
75
|
run('npm', ['run', 'test:unit']);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { astroBin, createProductionBuildEnv, packageScriptPath, runNodeBinary, runNodeScript } from './package-tools.js';
|
|
2
2
|
process.env.TREESEED_LOCAL_DEV_MODE = process.env.TREESEED_LOCAL_DEV_MODE ?? 'cloudflare';
|
|
3
|
+
const publishedRuntime = process.env.TREESEED_CONTENT_SERVING_MODE === 'published_runtime';
|
|
3
4
|
runNodeScript(packageScriptPath('patch-starlight-content-path'), [], { cwd: process.cwd() });
|
|
4
|
-
|
|
5
|
+
if (!publishedRuntime) {
|
|
6
|
+
runNodeScript(packageScriptPath('aggregate-book'), [], { cwd: process.cwd() });
|
|
7
|
+
}
|
|
5
8
|
runNodeBinary(astroBin, ['build'], {
|
|
6
9
|
cwd: process.cwd(),
|
|
7
10
|
env: createProductionBuildEnv({
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createProductionBuildEnv, packageScriptPath, runNodeScript } from './package-tools.js';
|
|
2
|
+
const publishedRuntime = process.env.TREESEED_CONTENT_SERVING_MODE === 'published_runtime';
|
|
2
3
|
runNodeScript(packageScriptPath('patch-starlight-content-path'), [], { cwd: process.cwd() });
|
|
3
|
-
|
|
4
|
+
if (!publishedRuntime) {
|
|
5
|
+
runNodeScript(packageScriptPath('aggregate-book'), [], { cwd: process.cwd() });
|
|
6
|
+
}
|
|
4
7
|
runNodeScript(packageScriptPath('tenant-build'), [], {
|
|
5
8
|
cwd: process.cwd(),
|
|
6
9
|
env: createProductionBuildEnv(),
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
export declare function resolveAgentsServiceConfig(): {
|
|
3
3
|
serviceName: string;
|
|
4
|
-
marketBaseUrl: string;
|
|
5
|
-
projectId: string;
|
|
6
|
-
runnerToken: string;
|
|
7
|
-
runnerId: string;
|
|
8
|
-
batchSize: number;
|
|
9
4
|
pollIntervalMs: number;
|
|
10
5
|
};
|
|
11
6
|
export declare function runAgentsCycle(): Promise<{
|
|
12
7
|
ok: boolean;
|
|
13
8
|
processed: number;
|
|
14
|
-
|
|
15
|
-
reason: string;
|
|
16
|
-
} | {
|
|
17
|
-
ok: boolean;
|
|
18
|
-
processed: number;
|
|
19
|
-
idle?: undefined;
|
|
20
|
-
reason?: undefined;
|
|
9
|
+
results: any[];
|
|
21
10
|
}>;
|
|
22
11
|
export declare function startAgentsLoop(): Promise<void>;
|
package/dist/services/agents.js
CHANGED
|
@@ -1,21 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import {
|
|
3
|
+
import { AgentKernel } from "../agents/kernel/agent-kernel.js";
|
|
4
|
+
import { createServiceSdk, resolveServiceRepoRoot } 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
|
+
}
|
|
4
11
|
function resolveAgentsServiceConfig() {
|
|
5
12
|
return {
|
|
6
|
-
|
|
7
|
-
|
|
13
|
+
serviceName: process.env.TREESEED_AGENTS_SERVICE_NAME?.trim() || "agents",
|
|
14
|
+
pollIntervalMs: integerFromEnv("TREESEED_AGENTS_POLL_INTERVAL_MS", 3e4)
|
|
8
15
|
};
|
|
9
16
|
}
|
|
10
17
|
async function runAgentsCycle() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
const sdk = createServiceSdk();
|
|
19
|
+
const kernel = new AgentKernel(sdk, resolveServiceRepoRoot());
|
|
20
|
+
const results = await kernel.runCycle();
|
|
21
|
+
return {
|
|
22
|
+
ok: true,
|
|
23
|
+
processed: results.length,
|
|
24
|
+
results
|
|
25
|
+
};
|
|
14
26
|
}
|
|
15
27
|
async function startAgentsLoop() {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
const config = resolveAgentsServiceConfig();
|
|
29
|
+
for (; ; ) {
|
|
30
|
+
try {
|
|
31
|
+
await runAgentsCycle();
|
|
32
|
+
} catch (error) {
|
|
33
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
await new Promise((resolve) => setTimeout(resolve, config.pollIntervalMs));
|
|
37
|
+
}
|
|
19
38
|
}
|
|
20
39
|
const currentFile = fileURLToPath(import.meta.url);
|
|
21
40
|
const entryFile = process.argv[1] ?? "";
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export { runManagerAction, runManagerCycle, startManagerLoop } from './manager.ts';
|
|
2
2
|
export { runWorkerCycle, startWorkerLoop } from './worker.ts';
|
|
3
|
-
export { runRemoteRunnerCycle, startRemoteRunnerLoop } from './remote-runner.ts';
|
|
4
|
-
export { runAgentsCycle, startAgentsLoop } from './agents.ts';
|
|
5
3
|
export { runWorkdayStart } from './workday-start.ts';
|
|
6
4
|
export { runWorkdayReport } from './workday-report.ts';
|
|
7
5
|
export { createWorkerPoolScaler, RailwayWorkerPoolScaler, NoopWorkerPoolScaler } from './worker-pool-scaler.ts';
|
package/dist/services/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { runManagerAction, runManagerCycle, startManagerLoop } from "./manager.js";
|
|
2
2
|
import { runWorkerCycle, startWorkerLoop } from "./worker.js";
|
|
3
|
-
import { runRemoteRunnerCycle, startRemoteRunnerLoop } from "./remote-runner.js";
|
|
4
|
-
import { runAgentsCycle, startAgentsLoop } from "./agents.js";
|
|
5
3
|
import { runWorkdayStart } from "./workday-start.js";
|
|
6
4
|
import { runWorkdayReport } from "./workday-report.js";
|
|
7
5
|
import { createWorkerPoolScaler, RailwayWorkerPoolScaler, NoopWorkerPoolScaler } from "./worker-pool-scaler.js";
|
|
@@ -9,15 +7,11 @@ export {
|
|
|
9
7
|
NoopWorkerPoolScaler,
|
|
10
8
|
RailwayWorkerPoolScaler,
|
|
11
9
|
createWorkerPoolScaler,
|
|
12
|
-
runAgentsCycle,
|
|
13
10
|
runManagerAction,
|
|
14
11
|
runManagerCycle,
|
|
15
|
-
runRemoteRunnerCycle,
|
|
16
12
|
runWorkdayReport,
|
|
17
13
|
runWorkdayStart,
|
|
18
14
|
runWorkerCycle,
|
|
19
|
-
startAgentsLoop,
|
|
20
15
|
startManagerLoop,
|
|
21
|
-
startRemoteRunnerLoop,
|
|
22
16
|
startWorkerLoop
|
|
23
17
|
};
|
|
@@ -48,7 +48,7 @@ export declare function runManagerAction(options?: {
|
|
|
48
48
|
mode: "reconcile";
|
|
49
49
|
managerId: string;
|
|
50
50
|
projectId: string;
|
|
51
|
-
environment: "local" | "
|
|
51
|
+
environment: "local" | "staging" | "prod";
|
|
52
52
|
insideWorkWindow: boolean;
|
|
53
53
|
workPolicy: WorkdayPolicy;
|
|
54
54
|
workDay: Record<string, unknown>;
|
|
@@ -98,7 +98,7 @@ export declare function runManagerAction(options?: {
|
|
|
98
98
|
title: string;
|
|
99
99
|
};
|
|
100
100
|
projectId: string;
|
|
101
|
-
environment: "local" | "
|
|
101
|
+
environment: "local" | "staging" | "prod";
|
|
102
102
|
workDayId: string;
|
|
103
103
|
state: string;
|
|
104
104
|
totalTasks: number;
|
|
@@ -165,7 +165,7 @@ export declare function runManagerAction(options?: {
|
|
|
165
165
|
title: string;
|
|
166
166
|
};
|
|
167
167
|
projectId: string;
|
|
168
|
-
environment: "local" | "
|
|
168
|
+
environment: "local" | "staging" | "prod";
|
|
169
169
|
workDayId: string;
|
|
170
170
|
state: string;
|
|
171
171
|
totalTasks: number;
|
|
@@ -226,7 +226,7 @@ export declare function runManagerCycle(options?: {
|
|
|
226
226
|
mode: "reconcile";
|
|
227
227
|
managerId: string;
|
|
228
228
|
projectId: string;
|
|
229
|
-
environment: "local" | "
|
|
229
|
+
environment: "local" | "staging" | "prod";
|
|
230
230
|
insideWorkWindow: boolean;
|
|
231
231
|
workPolicy: WorkdayPolicy;
|
|
232
232
|
workDay: Record<string, unknown>;
|
package/dist/services/manager.js
CHANGED
|
@@ -3,7 +3,15 @@ import { fileURLToPath } from "node:url";
|
|
|
3
3
|
import {
|
|
4
4
|
createControlPlaneReporter
|
|
5
5
|
} from "@treeseed/sdk";
|
|
6
|
+
import { loadActiveAgentSpecs } from "../agents/spec-loader.js";
|
|
7
|
+
import { followCursorKey, resolveTriggerDecision } from "../agents/kernel/trigger-resolver.js";
|
|
6
8
|
import { createQueuePushClient, createServiceSdk, queueEnvelopeForTask, resolveManagerConfig } from "./common.js";
|
|
9
|
+
import {
|
|
10
|
+
applyInteractiveWakeUpOverride,
|
|
11
|
+
applyScaleCooldown,
|
|
12
|
+
collectTaskMetrics,
|
|
13
|
+
computeDesiredWorkerCount
|
|
14
|
+
} from "./worker-capacity.js";
|
|
7
15
|
import { writeWorkdayContentSnapshot } from "./workday-content.js";
|
|
8
16
|
import { createWorkerPoolScaler } from "./worker-pool-scaler.js";
|
|
9
17
|
const DEFAULT_WORK_DAYS = [1, 2, 3, 4, 5];
|
|
@@ -167,6 +175,9 @@ function readDate(record, ...keys) {
|
|
|
167
175
|
const parsed = new Date(raw);
|
|
168
176
|
return Number.isFinite(parsed.valueOf()) ? parsed : null;
|
|
169
177
|
}
|
|
178
|
+
function asRecord(value) {
|
|
179
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
180
|
+
}
|
|
170
181
|
function parseJsonString(value, fallback = {}) {
|
|
171
182
|
if (typeof value !== "string" || !value.trim()) {
|
|
172
183
|
return fallback;
|
|
@@ -491,34 +502,6 @@ async function openWorkday(sdk, config, policy, now) {
|
|
|
491
502
|
});
|
|
492
503
|
return created.payload;
|
|
493
504
|
}
|
|
494
|
-
async function collectTaskMetrics(sdk, workDayId) {
|
|
495
|
-
const [queuedEnvelope, activeEnvelope] = await Promise.all([
|
|
496
|
-
sdk.searchTasks({
|
|
497
|
-
workDayId: workDayId ?? void 0,
|
|
498
|
-
limit: 500,
|
|
499
|
-
state: ["pending", "queued"]
|
|
500
|
-
}),
|
|
501
|
-
sdk.searchTasks({
|
|
502
|
-
workDayId: workDayId ?? void 0,
|
|
503
|
-
limit: 500,
|
|
504
|
-
state: ["claimed", "running"]
|
|
505
|
-
})
|
|
506
|
-
]);
|
|
507
|
-
const queuedTasks = asRecords(queuedEnvelope.payload);
|
|
508
|
-
const activeTasks = asRecords(activeEnvelope.payload);
|
|
509
|
-
const queuedCredits = queuedTasks.reduce((total, task) => {
|
|
510
|
-
const payload = parseJson(String(task.payloadJson ?? "{}"), {});
|
|
511
|
-
const credits = readNumber(payload, "estimatedCredits") ?? 1;
|
|
512
|
-
return total + credits;
|
|
513
|
-
}, 0);
|
|
514
|
-
return {
|
|
515
|
-
queuedTasks,
|
|
516
|
-
activeTasks,
|
|
517
|
-
queuedCount: queuedTasks.length,
|
|
518
|
-
activeLeases: activeTasks.length,
|
|
519
|
-
queuedCredits
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
505
|
function remainingCredits(workDay, policy) {
|
|
523
506
|
if (!workDay) {
|
|
524
507
|
return policy.dailyTaskCreditBudget;
|
|
@@ -531,7 +514,7 @@ function chooseAgentId(agentSpecs) {
|
|
|
531
514
|
const preferred = agentSpecs.find((spec) => {
|
|
532
515
|
const triggers = Array.isArray(spec.triggers) ? spec.triggers : [];
|
|
533
516
|
return triggers.some((trigger) => {
|
|
534
|
-
const type = typeof trigger === "string" ? trigger : readString(trigger, "type");
|
|
517
|
+
const type = typeof trigger === "string" ? trigger : readString(asRecord(trigger), "type");
|
|
535
518
|
return type === "startup" || type === "schedule";
|
|
536
519
|
});
|
|
537
520
|
});
|
|
@@ -640,31 +623,107 @@ async function topUpQueuedTasks(sdk, config, policy, workDay, snapshot, now) {
|
|
|
640
623
|
remainingCredits: availableCredits
|
|
641
624
|
};
|
|
642
625
|
}
|
|
643
|
-
function
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
626
|
+
function parseCursorTimestamp(value) {
|
|
627
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
628
|
+
return void 0;
|
|
629
|
+
}
|
|
630
|
+
const timestamp = new Date(value).valueOf();
|
|
631
|
+
return Number.isFinite(timestamp) ? timestamp : void 0;
|
|
632
|
+
}
|
|
633
|
+
function triggerPriority(invocation) {
|
|
634
|
+
switch (invocation.kind) {
|
|
635
|
+
case "message":
|
|
636
|
+
return 90;
|
|
637
|
+
case "follow":
|
|
638
|
+
return 80;
|
|
639
|
+
case "startup":
|
|
640
|
+
return 70;
|
|
641
|
+
case "schedule":
|
|
642
|
+
case "manual":
|
|
643
|
+
default:
|
|
644
|
+
return 60;
|
|
647
645
|
}
|
|
648
|
-
const requiredByQueue = Math.ceil(metrics.queuedCount / Math.max(1, targetQueueDepth));
|
|
649
|
-
const minimumActive = metrics.activeLeases > 0 ? 1 : 0;
|
|
650
|
-
return Math.max(minWorkers, Math.min(maxWorkers, Math.max(requiredByQueue, minimumActive)));
|
|
651
646
|
}
|
|
652
|
-
function
|
|
653
|
-
if (
|
|
654
|
-
return
|
|
647
|
+
function triggerTaskIdempotencyKey(workDayId, agent, invocation) {
|
|
648
|
+
if (invocation.kind === "message" && invocation.message?.id) {
|
|
649
|
+
return `${workDayId}:trigger:${agent.slug}:message:${invocation.message.id}`;
|
|
655
650
|
}
|
|
656
|
-
if (
|
|
657
|
-
return
|
|
651
|
+
if (invocation.kind === "follow") {
|
|
652
|
+
return `${workDayId}:trigger:${agent.slug}:follow:${followCursorKey(invocation.followModels)}:${invocation.cursorValue ?? "none"}`;
|
|
658
653
|
}
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
654
|
+
const triggerKey = readString(
|
|
655
|
+
asRecord(invocation.trigger),
|
|
656
|
+
"name",
|
|
657
|
+
"type"
|
|
658
|
+
) || invocation.kind;
|
|
659
|
+
return `${workDayId}:trigger:${agent.slug}:${invocation.kind}:${triggerKey}`;
|
|
660
|
+
}
|
|
661
|
+
async function materializeAgentTriggerTasks(sdk, workDay, now) {
|
|
662
|
+
const workDayId = String(workDay.id ?? "");
|
|
663
|
+
if (!workDayId) {
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
666
|
+
const [{ specs, diagnostics }, existingTasksEnvelope] = await Promise.all([
|
|
667
|
+
loadActiveAgentSpecs(sdk),
|
|
668
|
+
sdk.searchTasks({ workDayId, limit: 1e3 })
|
|
669
|
+
]);
|
|
670
|
+
const errors = diagnostics.filter((entry) => entry.severity === "error");
|
|
671
|
+
if (errors.length > 0) {
|
|
672
|
+
throw new Error(
|
|
673
|
+
`Agent spec validation failed: ${errors.map((entry) => `${entry.slug}:${entry.field}:${entry.message}`).join(" | ")}`
|
|
674
|
+
);
|
|
662
675
|
}
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
676
|
+
const existingKeys = new Set(
|
|
677
|
+
asRecords(existingTasksEnvelope.payload).map((task) => readString(task, "idempotencyKey", "idempotency_key"))
|
|
678
|
+
);
|
|
679
|
+
const createdTasks = [];
|
|
680
|
+
for (const agent of [...specs].sort((left, right) => left.slug.localeCompare(right.slug))) {
|
|
681
|
+
const scopedSdk = sdk.scopeForAgent(agent);
|
|
682
|
+
const lastRunAt = parseCursorTimestamp((await sdk.getCursor({
|
|
683
|
+
agentSlug: agent.slug,
|
|
684
|
+
cursorKey: "last_run_at"
|
|
685
|
+
})).payload);
|
|
686
|
+
const runsThisCycle = agent.triggerPolicy?.maxRunsPerCycle ?? 1;
|
|
687
|
+
for (let index = 0; index < runsThisCycle; index += 1) {
|
|
688
|
+
const decision = await resolveTriggerDecision({
|
|
689
|
+
agent,
|
|
690
|
+
mode: "auto",
|
|
691
|
+
isRunning: false,
|
|
692
|
+
lastRunAt,
|
|
693
|
+
sdk: scopedSdk
|
|
694
|
+
});
|
|
695
|
+
if (decision.kind !== "ready" || !decision.invocation) {
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
const invocation = decision.invocation;
|
|
699
|
+
const idempotencyKey = triggerTaskIdempotencyKey(workDayId, agent, invocation);
|
|
700
|
+
if (existingKeys.has(idempotencyKey)) {
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
const created = await sdk.createTask({
|
|
704
|
+
workDayId,
|
|
705
|
+
agentId: agent.slug,
|
|
706
|
+
type: "agent_trigger",
|
|
707
|
+
priority: triggerPriority(invocation),
|
|
708
|
+
idempotencyKey,
|
|
709
|
+
payload: {
|
|
710
|
+
executionKind: "agent_trigger",
|
|
711
|
+
agentSlug: agent.slug,
|
|
712
|
+
invocation,
|
|
713
|
+
createdAt: now.toISOString()
|
|
714
|
+
},
|
|
715
|
+
graphVersion: typeof workDay.graphVersion === "string" ? workDay.graphVersion : null,
|
|
716
|
+
actor: "manager"
|
|
717
|
+
});
|
|
718
|
+
if (!created.payload) {
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
await maybeEnqueueTask(sdk, created.payload);
|
|
722
|
+
createdTasks.push(created.payload);
|
|
723
|
+
existingKeys.add(idempotencyKey);
|
|
724
|
+
}
|
|
666
725
|
}
|
|
667
|
-
return
|
|
726
|
+
return createdTasks;
|
|
668
727
|
}
|
|
669
728
|
async function registerHeartbeat(reporter, config, policy, desiredWorkers, metrics) {
|
|
670
729
|
await reporter.registerAgentPoolHeartbeat({
|
|
@@ -878,10 +937,24 @@ async function reconcileManager(options) {
|
|
|
878
937
|
if (activeWorkDay && insideWorkWindow && seedResult.remainingCredits > 0) {
|
|
879
938
|
seedResult = await topUpQueuedTasks(sdk, config, policy, activeWorkDay, currentSnapshot, now);
|
|
880
939
|
}
|
|
940
|
+
if (activeWorkDay && insideWorkWindow) {
|
|
941
|
+
const triggerTasks = await materializeAgentTriggerTasks(sdk, activeWorkDay, now);
|
|
942
|
+
if (triggerTasks.length > 0) {
|
|
943
|
+
seedResult = {
|
|
944
|
+
...seedResult,
|
|
945
|
+
createdTasks: [...seedResult.createdTasks, ...triggerTasks]
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
}
|
|
881
949
|
const metrics = await collectTaskMetrics(sdk, activeWorkDay ? String(activeWorkDay.id ?? "") : null);
|
|
882
|
-
const rawDesiredWorkers = activeWorkDay ?
|
|
950
|
+
const rawDesiredWorkers = activeWorkDay ? computeDesiredWorkerCount(policy.autoscale, metrics) : 0;
|
|
883
951
|
const latestScaleDecision = await sdk.getLatestScaleDecision(config.projectId, config.environment, config.poolName);
|
|
884
|
-
const desiredWorkers =
|
|
952
|
+
const desiredWorkers = applyInteractiveWakeUpOverride({
|
|
953
|
+
priorityClass: "background",
|
|
954
|
+
queuedCount: metrics.queuedCount,
|
|
955
|
+
currentWorkers: Number(latestScaleDecision.payload?.desiredWorkers ?? 0),
|
|
956
|
+
desiredWorkers: applyScaleCooldown(policy.autoscale, latestScaleDecision.payload, rawDesiredWorkers, now)
|
|
957
|
+
});
|
|
885
958
|
const scaleDecision = {
|
|
886
959
|
projectId: config.projectId,
|
|
887
960
|
environment: config.environment,
|
|
@@ -4,7 +4,7 @@ export declare function runWorkdayReport(): Promise<{
|
|
|
4
4
|
mode: "reconcile";
|
|
5
5
|
managerId: string;
|
|
6
6
|
projectId: string;
|
|
7
|
-
environment: "local" | "
|
|
7
|
+
environment: "local" | "staging" | "prod";
|
|
8
8
|
insideWorkWindow: boolean;
|
|
9
9
|
workPolicy: import("@treeseed/sdk").WorkdayPolicy;
|
|
10
10
|
workDay: Record<string, unknown>;
|
|
@@ -56,7 +56,7 @@ export declare function runWorkdayReport(): Promise<{
|
|
|
56
56
|
title: string;
|
|
57
57
|
};
|
|
58
58
|
projectId: string;
|
|
59
|
-
environment: "local" | "
|
|
59
|
+
environment: "local" | "staging" | "prod";
|
|
60
60
|
workDayId: string;
|
|
61
61
|
state: string;
|
|
62
62
|
totalTasks: number;
|
|
@@ -123,7 +123,7 @@ export declare function runWorkdayReport(): Promise<{
|
|
|
123
123
|
title: string;
|
|
124
124
|
};
|
|
125
125
|
projectId: string;
|
|
126
|
-
environment: "local" | "
|
|
126
|
+
environment: "local" | "staging" | "prod";
|
|
127
127
|
workDayId: string;
|
|
128
128
|
state: string;
|
|
129
129
|
totalTasks: number;
|
|
@@ -4,7 +4,7 @@ export declare function runWorkdayStart(): Promise<{
|
|
|
4
4
|
mode: "reconcile";
|
|
5
5
|
managerId: string;
|
|
6
6
|
projectId: string;
|
|
7
|
-
environment: "local" | "
|
|
7
|
+
environment: "local" | "staging" | "prod";
|
|
8
8
|
insideWorkWindow: boolean;
|
|
9
9
|
workPolicy: import("@treeseed/sdk").WorkdayPolicy;
|
|
10
10
|
workDay: Record<string, unknown>;
|
|
@@ -56,7 +56,7 @@ export declare function runWorkdayStart(): Promise<{
|
|
|
56
56
|
title: string;
|
|
57
57
|
};
|
|
58
58
|
projectId: string;
|
|
59
|
-
environment: "local" | "
|
|
59
|
+
environment: "local" | "staging" | "prod";
|
|
60
60
|
workDayId: string;
|
|
61
61
|
state: string;
|
|
62
62
|
totalTasks: number;
|
|
@@ -123,7 +123,7 @@ export declare function runWorkdayStart(): Promise<{
|
|
|
123
123
|
title: string;
|
|
124
124
|
};
|
|
125
125
|
projectId: string;
|
|
126
|
-
environment: "local" | "
|
|
126
|
+
environment: "local" | "staging" | "prod";
|
|
127
127
|
workDayId: string;
|
|
128
128
|
state: string;
|
|
129
129
|
totalTasks: number;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { AgentPoolAutoscalePolicy, AgentSdk, ProjectEnvironmentName, ScaleDecision, WorkerPoolScaleResult, WorkerPoolScaler } from '@treeseed/sdk';
|
|
2
|
+
export interface TaskMetricsSnapshot {
|
|
3
|
+
queuedTasks: Array<Record<string, unknown>>;
|
|
4
|
+
activeTasks: Array<Record<string, unknown>>;
|
|
5
|
+
queuedCount: number;
|
|
6
|
+
activeLeases: number;
|
|
7
|
+
queuedCredits: number;
|
|
8
|
+
}
|
|
9
|
+
export interface WorkerPoolIdentity {
|
|
10
|
+
projectId: string;
|
|
11
|
+
environment: ProjectEnvironmentName | 'local';
|
|
12
|
+
poolName: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CapacityAssuranceResult {
|
|
15
|
+
ok: true;
|
|
16
|
+
taskId: string;
|
|
17
|
+
queued: true;
|
|
18
|
+
workerState: 'warm' | 'cold_starting';
|
|
19
|
+
desiredWorkers: number;
|
|
20
|
+
scaleApplied: boolean;
|
|
21
|
+
scaleReason: string;
|
|
22
|
+
scaleDecision: ScaleDecision;
|
|
23
|
+
scaleResult: WorkerPoolScaleResult;
|
|
24
|
+
metrics: TaskMetricsSnapshot;
|
|
25
|
+
}
|
|
26
|
+
export declare function resolveAutoscalePolicyFromEnv(): AgentPoolAutoscalePolicy;
|
|
27
|
+
export declare function resolveWorkerPoolIdentityFromEnv(projectId?: string): WorkerPoolIdentity;
|
|
28
|
+
export declare function computeDesiredWorkerCount(autoscale: AgentPoolAutoscalePolicy, metrics: Pick<TaskMetricsSnapshot, 'queuedCount' | 'activeLeases'>): number;
|
|
29
|
+
export declare function applyScaleCooldown(autoscale: AgentPoolAutoscalePolicy, latestDecision: ScaleDecision | null, nextDesired: number, now: Date): number;
|
|
30
|
+
export declare function applyInteractiveWakeUpOverride(options: {
|
|
31
|
+
priorityClass?: 'interactive' | 'background';
|
|
32
|
+
queuedCount: number;
|
|
33
|
+
currentWorkers: number;
|
|
34
|
+
desiredWorkers: number;
|
|
35
|
+
}): number;
|
|
36
|
+
export declare function collectTaskMetrics(sdk: AgentSdk, workDayId?: string | null): Promise<TaskMetricsSnapshot>;
|
|
37
|
+
export declare function enqueueTaskAndEnsureCapacity(sdk: AgentSdk, request: {
|
|
38
|
+
taskId: string;
|
|
39
|
+
actor?: string;
|
|
40
|
+
queueName?: string;
|
|
41
|
+
deliveryDelaySeconds?: number;
|
|
42
|
+
priorityClass?: 'interactive' | 'background';
|
|
43
|
+
projectId?: string;
|
|
44
|
+
identity?: WorkerPoolIdentity;
|
|
45
|
+
autoscale?: AgentPoolAutoscalePolicy;
|
|
46
|
+
scaler?: WorkerPoolScaler;
|
|
47
|
+
now?: Date;
|
|
48
|
+
enqueueTask: (sdk: AgentSdk, request: {
|
|
49
|
+
taskId: string;
|
|
50
|
+
queueName?: string;
|
|
51
|
+
deliveryDelaySeconds?: number;
|
|
52
|
+
actor?: string;
|
|
53
|
+
}) => Promise<{
|
|
54
|
+
ok: boolean;
|
|
55
|
+
taskId: string;
|
|
56
|
+
queued: boolean;
|
|
57
|
+
}>;
|
|
58
|
+
}): Promise<CapacityAssuranceResult>;
|