@treeseed/core 0.4.5 → 0.4.7
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 +8 -16
- package/dist/agent-runtime.d.ts +17 -0
- package/dist/agent-runtime.js +111 -0
- package/dist/agent.d.ts +11 -0
- package/dist/agent.js +25 -0
- package/dist/agents/adapters/execution.d.ts +46 -0
- package/dist/agents/adapters/execution.js +90 -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 +11 -0
- package/dist/agents/adapters/notification.js +16 -0
- package/dist/agents/adapters/repository.d.ts +23 -0
- package/dist/agents/adapters/repository.js +61 -0
- package/dist/agents/adapters/research.d.ts +14 -0
- package/dist/agents/adapters/research.js +25 -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 +20 -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 +51 -0
- package/dist/agents/kernel/agent-kernel.js +285 -0
- package/dist/agents/kernel/trigger-resolver.d.ts +18 -0
- package/dist/agents/kernel/trigger-resolver.js +153 -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 +117 -0
- package/dist/agents/runtime-types.js +0 -0
- package/dist/agents/spec-loader.d.ts +18 -0
- package/dist/agents/spec-loader.js +55 -0
- package/dist/agents/spec-normalizer.d.ts +2 -0
- package/dist/agents/spec-normalizer.js +257 -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 +504 -0
- package/dist/api/agent-routes.js +2 -10
- package/dist/scripts/build-dist.js +5 -0
- package/dist/scripts/run-fixture-astro-command.js +2 -170
- package/dist/scripts/test-smoke.js +15 -1
- package/dist/scripts/workspace-bootstrap.js +0 -1
- package/dist/services/common.d.ts +20 -0
- package/dist/services/common.js +66 -0
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.js +11 -0
- package/dist/services/manager.d.ts +4 -0
- package/dist/services/manager.js +199 -0
- package/dist/services/workday-report.d.ts +17 -0
- package/dist/services/workday-report.js +44 -0
- package/dist/services/workday-start.d.ts +2 -0
- package/dist/services/workday-start.js +27 -0
- package/dist/services/worker.d.ts +6 -0
- package/dist/services/worker.js +108 -0
- package/package.json +43 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, rmSync, symlinkSync
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import { dirname, resolve } from 'node:path';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
@@ -54,176 +54,8 @@ function ensureFixtureWorkspacePackage(packageName, workspaceDir) {
|
|
|
54
54
|
rmSync(packageDir, { recursive: true, force: true });
|
|
55
55
|
symlinkSync(resolvedPackageRoot, packageDir, 'dir');
|
|
56
56
|
}
|
|
57
|
-
function ensureFixtureAgentContractPackage() {
|
|
58
|
-
const packageDir = resolve(fixtureRoot, 'node_modules', '@treeseed', 'agent');
|
|
59
|
-
mkdirSync(resolve(packageDir, 'contracts'), { recursive: true });
|
|
60
|
-
writeFileSync(resolve(packageDir, 'package.json'), JSON.stringify({
|
|
61
|
-
name: '@treeseed/agent',
|
|
62
|
-
type: 'module',
|
|
63
|
-
exports: {
|
|
64
|
-
'./runtime-types': {
|
|
65
|
-
types: './runtime-types.d.js',
|
|
66
|
-
default: './runtime-types.js',
|
|
67
|
-
},
|
|
68
|
-
'./contracts/messages': {
|
|
69
|
-
types: './contracts/messages.d.js',
|
|
70
|
-
default: './contracts/messages.js',
|
|
71
|
-
},
|
|
72
|
-
'./contracts/run': {
|
|
73
|
-
types: './contracts/run.d.js',
|
|
74
|
-
default: './contracts/run.js',
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
}, null, 2), 'utf8');
|
|
78
|
-
writeFileSync(resolve(packageDir, 'runtime-types.js'), 'export {};\n', 'utf8');
|
|
79
|
-
writeFileSync(resolve(packageDir, 'runtime-types.d.ts'), [
|
|
80
|
-
"import type { AgentHandlerKind, AgentRunStatus } from '@treeseed/sdk/types/agents';",
|
|
81
|
-
'export interface AgentTriggerInvocation {',
|
|
82
|
-
"\tkind: 'startup' | 'schedule' | 'message' | 'manual' | 'follow';",
|
|
83
|
-
'\tsource: string;',
|
|
84
|
-
'\tmessage?: { id?: string | number; type?: string; payloadJson?: string | null } | null;',
|
|
85
|
-
'}',
|
|
86
|
-
'export interface AgentExecutionResult {',
|
|
87
|
-
'\tstatus: AgentRunStatus;',
|
|
88
|
-
'\tsummary: string;',
|
|
89
|
-
'\tstdout?: string;',
|
|
90
|
-
'\tstderr?: string;',
|
|
91
|
-
"\terrorCategory?: import('./contracts/run.js').AgentErrorCategory | null;",
|
|
92
|
-
'\tmetadata?: Record<string, unknown>;',
|
|
93
|
-
'}',
|
|
94
|
-
'export interface AgentContext {',
|
|
95
|
-
'\trunId: string;',
|
|
96
|
-
'\trepoRoot: string;',
|
|
97
|
-
'\tagent: any;',
|
|
98
|
-
'\tsdk: any;',
|
|
99
|
-
'\ttrigger: AgentTriggerInvocation;',
|
|
100
|
-
'\texecution: any;',
|
|
101
|
-
'\tmutations: any;',
|
|
102
|
-
'\trepository: any;',
|
|
103
|
-
'\tverification: any;',
|
|
104
|
-
'\tnotifications: any;',
|
|
105
|
-
'\tresearch: any;',
|
|
106
|
-
'}',
|
|
107
|
-
'export interface AgentHandler<TInputs = unknown, TResult = unknown> {',
|
|
108
|
-
'\tkind: AgentHandlerKind;',
|
|
109
|
-
'\tresolveInputs(context: AgentContext): Promise<TInputs>;',
|
|
110
|
-
'\texecute(context: AgentContext, inputs: TInputs): Promise<TResult>;',
|
|
111
|
-
'\temitOutputs(context: AgentContext, result: TResult): Promise<AgentExecutionResult>;',
|
|
112
|
-
'}',
|
|
113
|
-
'',
|
|
114
|
-
].join('\n'), 'utf8');
|
|
115
|
-
writeFileSync(resolve(packageDir, 'contracts', 'messages.js'), [
|
|
116
|
-
'export const AGENT_MESSAGE_TYPES = {};',
|
|
117
|
-
'export function parseAgentMessagePayload(_type, payloadJson) {',
|
|
118
|
-
'\treturn JSON.parse(payloadJson);',
|
|
119
|
-
'}',
|
|
120
|
-
'',
|
|
121
|
-
].join('\n'), 'utf8');
|
|
122
|
-
writeFileSync(resolve(packageDir, 'contracts', 'messages.d.ts'), [
|
|
123
|
-
'export interface QuestionPriorityUpdatedMessage {',
|
|
124
|
-
'\tquestionId: string;',
|
|
125
|
-
'\treason: string;',
|
|
126
|
-
'\tplannerRunId: string;',
|
|
127
|
-
'}',
|
|
128
|
-
'export interface ObjectivePriorityUpdatedMessage {',
|
|
129
|
-
'\tobjectiveId: string;',
|
|
130
|
-
'\treason: string;',
|
|
131
|
-
'\tplannerRunId: string;',
|
|
132
|
-
'}',
|
|
133
|
-
'export interface ArchitectureUpdatedMessage {',
|
|
134
|
-
'\tobjectiveId: string;',
|
|
135
|
-
'\tknowledgeId: string;',
|
|
136
|
-
'\tarchitectRunId: string;',
|
|
137
|
-
'}',
|
|
138
|
-
'export interface SubscriberNotifiedMessage {',
|
|
139
|
-
'\temail: string;',
|
|
140
|
-
'\titemCount: number;',
|
|
141
|
-
'\tnotifierRunId: string;',
|
|
142
|
-
'}',
|
|
143
|
-
'export interface ResearchStartedMessage {',
|
|
144
|
-
'\tquestionId: string;',
|
|
145
|
-
'\tresearcherRunId: string;',
|
|
146
|
-
'}',
|
|
147
|
-
'export interface ResearchCompletedMessage {',
|
|
148
|
-
'\tquestionId: string;',
|
|
149
|
-
'\tknowledgeId: string | null;',
|
|
150
|
-
'\tresearcherRunId: string;',
|
|
151
|
-
'}',
|
|
152
|
-
'export interface TaskCompleteMessage {',
|
|
153
|
-
'\tbranchName: string | null;',
|
|
154
|
-
'\tchangedTargets: string[];',
|
|
155
|
-
'\tengineerRunId: string;',
|
|
156
|
-
'}',
|
|
157
|
-
'export interface TaskWaitingMessage {',
|
|
158
|
-
'\tblockingReason: string;',
|
|
159
|
-
'\tengineerRunId: string;',
|
|
160
|
-
'}',
|
|
161
|
-
'export interface TaskFailedMessage {',
|
|
162
|
-
'\tfailureSummary: string;',
|
|
163
|
-
'\tengineerRunId: string;',
|
|
164
|
-
'}',
|
|
165
|
-
'export interface TaskVerifiedMessage {',
|
|
166
|
-
'\tbranchName: string | null;',
|
|
167
|
-
'\treviewerRunId: string;',
|
|
168
|
-
'}',
|
|
169
|
-
'export interface ReviewFailedMessage {',
|
|
170
|
-
'\tfailureSummary: string;',
|
|
171
|
-
'\treviewerRunId: string;',
|
|
172
|
-
'}',
|
|
173
|
-
'export interface ReviewWaitingMessage {',
|
|
174
|
-
'\tblockingReason: string;',
|
|
175
|
-
'\treviewerRunId: string;',
|
|
176
|
-
'}',
|
|
177
|
-
'export interface ReleaseStartedMessage {',
|
|
178
|
-
'\ttaskRunId: string | null;',
|
|
179
|
-
'\treleaserRunId: string;',
|
|
180
|
-
'}',
|
|
181
|
-
'export interface ReleaseCompletedMessage {',
|
|
182
|
-
'\treleaseSummary: string;',
|
|
183
|
-
'\treleaserRunId: string;',
|
|
184
|
-
'}',
|
|
185
|
-
'export interface ReleaseFailedMessage {',
|
|
186
|
-
'\tfailureSummary: string;',
|
|
187
|
-
'\treleaserRunId: string;',
|
|
188
|
-
'}',
|
|
189
|
-
'export interface AgentMessageContracts {',
|
|
190
|
-
'\tquestion_priority_updated: QuestionPriorityUpdatedMessage;',
|
|
191
|
-
'\tobjective_priority_updated: ObjectivePriorityUpdatedMessage;',
|
|
192
|
-
'\tarchitecture_updated: ArchitectureUpdatedMessage;',
|
|
193
|
-
'\tsubscriber_notified: SubscriberNotifiedMessage;',
|
|
194
|
-
'\tresearch_started: ResearchStartedMessage;',
|
|
195
|
-
'\tresearch_completed: ResearchCompletedMessage;',
|
|
196
|
-
'\ttask_complete: TaskCompleteMessage;',
|
|
197
|
-
'\ttask_waiting: TaskWaitingMessage;',
|
|
198
|
-
'\ttask_failed: TaskFailedMessage;',
|
|
199
|
-
'\ttask_verified: TaskVerifiedMessage;',
|
|
200
|
-
'\treview_failed: ReviewFailedMessage;',
|
|
201
|
-
'\treview_waiting: ReviewWaitingMessage;',
|
|
202
|
-
'\trelease_started: ReleaseStartedMessage;',
|
|
203
|
-
'\trelease_completed: ReleaseCompletedMessage;',
|
|
204
|
-
'\trelease_failed: ReleaseFailedMessage;',
|
|
205
|
-
'}',
|
|
206
|
-
'export type AgentMessageType = keyof AgentMessageContracts;',
|
|
207
|
-
'export type AgentMessagePayload<TType extends AgentMessageType> = AgentMessageContracts[TType];',
|
|
208
|
-
'export declare const AGENT_MESSAGE_TYPES: readonly AgentMessageType[];',
|
|
209
|
-
'export declare function parseAgentMessagePayload<TType extends AgentMessageType>(_type: TType, payloadJson: string): AgentMessagePayload<TType>;',
|
|
210
|
-
'',
|
|
211
|
-
].join('\n'), 'utf8');
|
|
212
|
-
writeFileSync(resolve(packageDir, 'contracts', 'run.js'), 'export {};\n', 'utf8');
|
|
213
|
-
writeFileSync(resolve(packageDir, 'contracts', 'run.d.ts'), [
|
|
214
|
-
"export type AgentErrorCategory = 'execution_error' | 'mutation_error' | 'verification_error' | 'notification_error' | 'research_error' | 'sdk_error' | 'unknown';",
|
|
215
|
-
'',
|
|
216
|
-
].join('\n'), 'utf8');
|
|
217
|
-
}
|
|
218
57
|
ensureFixtureWorkspacePackage('@treeseed/sdk', resolve(packageRoot, '..', 'sdk'));
|
|
219
|
-
|
|
220
|
-
if (existsSync(workspaceAgentRoot) || resolveInstalledPackageRoot('@treeseed/agent')) {
|
|
221
|
-
ensureFixtureWorkspacePackage('@treeseed/agent', workspaceAgentRoot);
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
rmSync(resolve(fixtureRoot, 'node_modules', '@treeseed', 'agent'), { recursive: true, force: true });
|
|
225
|
-
ensureFixtureAgentContractPackage();
|
|
226
|
-
}
|
|
58
|
+
ensureFixtureWorkspacePackage('@treeseed/core', packageRoot);
|
|
227
59
|
const result = spawnSync('npx', ['astro', command, '--root', fixtureRoot, ...rest], {
|
|
228
60
|
cwd: packageRoot,
|
|
229
61
|
stdio: 'inherit',
|
|
@@ -108,7 +108,21 @@ try {
|
|
|
108
108
|
installPackagedPackage(extractRoot, installRoot, coreTarball, 'core');
|
|
109
109
|
mirrorDependencies(installRoot);
|
|
110
110
|
writeFileSync(resolve(installRoot, 'package.json'), `${JSON.stringify({ name: 'treeseed-core-smoke', private: true, type: 'module' }, null, 2)}\n`, 'utf8');
|
|
111
|
-
run(process.execPath, [
|
|
111
|
+
run(process.execPath, [
|
|
112
|
+
'--input-type=module',
|
|
113
|
+
'-e',
|
|
114
|
+
[
|
|
115
|
+
'await import("@treeseed/core");',
|
|
116
|
+
'await import("@treeseed/core/agent/cli");',
|
|
117
|
+
'await import("@treeseed/core/runtime-types");',
|
|
118
|
+
'await import("@treeseed/core/contracts/messages");',
|
|
119
|
+
'await import("@treeseed/core/contracts/run");',
|
|
120
|
+
'await import("@treeseed/core/services/manager");',
|
|
121
|
+
'await import("@treeseed/core/services/worker");',
|
|
122
|
+
'await import("@treeseed/core/services/workday-start");',
|
|
123
|
+
'await import("@treeseed/core/services/workday-report");',
|
|
124
|
+
].join(' '),
|
|
125
|
+
], installRoot);
|
|
112
126
|
console.log('Core packed-install smoke passed.');
|
|
113
127
|
}
|
|
114
128
|
finally {
|
|
@@ -8,7 +8,6 @@ const require = createRequire(import.meta.url);
|
|
|
8
8
|
const requiredPackages = [
|
|
9
9
|
{ name: '@treeseed/sdk', dir: 'packages/sdk', build: true },
|
|
10
10
|
{ name: '@treeseed/core', dir: 'packages/core', build: true },
|
|
11
|
-
{ name: '@treeseed/agent', dir: 'packages/agent', build: true },
|
|
12
11
|
{ name: '@treeseed/cli', dir: 'packages/cli', build: true, binName: 'treeseed' },
|
|
13
12
|
];
|
|
14
13
|
function packageState(root, entry) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AgentSdk } from '@treeseed/sdk/sdk';
|
|
2
|
+
import { TreeseedGatewayClient, CloudflareQueuePullClient } from '@treeseed/sdk/remote';
|
|
3
|
+
export declare function resolveServiceRepoRoot(): string;
|
|
4
|
+
export declare function createServiceSdk(): AgentSdk;
|
|
5
|
+
export declare function createGatewayClient(): TreeseedGatewayClient;
|
|
6
|
+
export declare function createQueueClient(): CloudflareQueuePullClient;
|
|
7
|
+
export declare function resolveManagerConfig(): {
|
|
8
|
+
host: string;
|
|
9
|
+
port: number;
|
|
10
|
+
projectId: string;
|
|
11
|
+
defaultCapacityBudget: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function resolveWorkerConfig(): {
|
|
14
|
+
workerId: string;
|
|
15
|
+
batchSize: number;
|
|
16
|
+
visibilityTimeoutMs: number;
|
|
17
|
+
pollIntervalMs: number;
|
|
18
|
+
leaseSeconds: number;
|
|
19
|
+
managerBaseUrl: string;
|
|
20
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { AgentSdk } from "@treeseed/sdk/sdk";
|
|
2
|
+
import { TreeseedGatewayClient, CloudflareQueuePullClient } from "@treeseed/sdk/remote";
|
|
3
|
+
function integerFromEnv(name, fallback) {
|
|
4
|
+
const value = process.env[name];
|
|
5
|
+
if (!value) return fallback;
|
|
6
|
+
const parsed = Number.parseInt(value, 10);
|
|
7
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
8
|
+
}
|
|
9
|
+
function resolveServiceRepoRoot() {
|
|
10
|
+
return process.env.TREESEED_AGENT_REPO_ROOT?.trim() || process.cwd();
|
|
11
|
+
}
|
|
12
|
+
function createServiceSdk() {
|
|
13
|
+
return AgentSdk.createLocal({
|
|
14
|
+
repoRoot: resolveServiceRepoRoot(),
|
|
15
|
+
databaseName: process.env.TREESEED_AGENT_D1_DATABASE ?? "karyon-docs-site-data",
|
|
16
|
+
persistTo: process.env.TREESEED_AGENT_D1_PERSIST_TO ?? void 0
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function createGatewayClient() {
|
|
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() {
|
|
28
|
+
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID?.trim();
|
|
29
|
+
const queueId = process.env.TREESEED_QUEUE_ID?.trim();
|
|
30
|
+
const token = process.env.TREESEED_QUEUE_PULL_TOKEN?.trim();
|
|
31
|
+
if (!accountId || !queueId || !token) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return new CloudflareQueuePullClient({
|
|
35
|
+
accountId,
|
|
36
|
+
queueId,
|
|
37
|
+
token,
|
|
38
|
+
apiBaseUrl: process.env.TREESEED_QUEUE_API_BASE_URL?.trim() || void 0
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function resolveManagerConfig() {
|
|
42
|
+
return {
|
|
43
|
+
host: process.env.HOST?.trim() || "0.0.0.0",
|
|
44
|
+
port: integerFromEnv("PORT", 3100),
|
|
45
|
+
projectId: process.env.TREESEED_PROJECT_ID?.trim() || "treeseed-market",
|
|
46
|
+
defaultCapacityBudget: integerFromEnv("TREESEED_WORKDAY_CAPACITY_BUDGET", 100)
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function resolveWorkerConfig() {
|
|
50
|
+
return {
|
|
51
|
+
workerId: process.env.TREESEED_WORKER_ID?.trim() || `worker-${process.pid}`,
|
|
52
|
+
batchSize: integerFromEnv("TREESEED_QUEUE_BATCH_SIZE", 1),
|
|
53
|
+
visibilityTimeoutMs: integerFromEnv("TREESEED_QUEUE_VISIBILITY_TIMEOUT_MS", 12e4),
|
|
54
|
+
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)}`
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
createGatewayClient,
|
|
61
|
+
createQueueClient,
|
|
62
|
+
createServiceSdk,
|
|
63
|
+
resolveManagerConfig,
|
|
64
|
+
resolveServiceRepoRoot,
|
|
65
|
+
resolveWorkerConfig
|
|
66
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createManagerApp } from "./manager.js";
|
|
2
|
+
import { runWorkerCycle, startWorkerLoop } from "./worker.js";
|
|
3
|
+
import { runWorkdayStart } from "./workday-start.js";
|
|
4
|
+
import { runWorkdayReport } from "./workday-report.js";
|
|
5
|
+
export {
|
|
6
|
+
createManagerApp,
|
|
7
|
+
runWorkdayReport,
|
|
8
|
+
runWorkdayStart,
|
|
9
|
+
runWorkerCycle,
|
|
10
|
+
startWorkerLoop
|
|
11
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
import { AgentSdk } from "@treeseed/sdk/sdk";
|
|
7
|
+
import { createServiceSdk, resolveManagerConfig } from "./common.js";
|
|
8
|
+
async function honoNodeHandler(app, request, response) {
|
|
9
|
+
const origin = request.headers.host ? `http://${request.headers.host}` : "http://127.0.0.1";
|
|
10
|
+
const url = new URL(request.url ?? "/", origin);
|
|
11
|
+
const webRequest = new Request(url, {
|
|
12
|
+
method: request.method,
|
|
13
|
+
headers: request.headers,
|
|
14
|
+
body: request.method !== "GET" && request.method !== "HEAD" ? request : void 0,
|
|
15
|
+
duplex: "half"
|
|
16
|
+
});
|
|
17
|
+
const webResponse = await app.fetch(webRequest);
|
|
18
|
+
response.statusCode = webResponse.status;
|
|
19
|
+
webResponse.headers.forEach((value, key) => response.setHeader(key, value));
|
|
20
|
+
if (!webResponse.body) {
|
|
21
|
+
response.end();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
Readable.fromWeb(webResponse.body).pipe(response);
|
|
25
|
+
}
|
|
26
|
+
async function seedRootTasks(sdk, workDayId) {
|
|
27
|
+
const specs = await sdk.listAgentSpecs({ enabled: true });
|
|
28
|
+
const created = [];
|
|
29
|
+
for (const spec of specs) {
|
|
30
|
+
const hasStartTrigger = spec.triggers.some((trigger) => trigger.type === "startup" || trigger.type === "schedule");
|
|
31
|
+
if (!hasStartTrigger) continue;
|
|
32
|
+
created.push(await sdk.createTask({
|
|
33
|
+
workDayId,
|
|
34
|
+
agentId: spec.slug,
|
|
35
|
+
type: "agent_root",
|
|
36
|
+
priority: 100,
|
|
37
|
+
idempotencyKey: `${workDayId}:${spec.slug}:root`,
|
|
38
|
+
payload: {
|
|
39
|
+
agentSlug: spec.slug,
|
|
40
|
+
handler: spec.handler,
|
|
41
|
+
triggerKinds: spec.triggers.map((entry) => entry.type)
|
|
42
|
+
},
|
|
43
|
+
graphVersion: null,
|
|
44
|
+
actor: "manager"
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
return created;
|
|
48
|
+
}
|
|
49
|
+
function createManagerApp(sdk = createServiceSdk()) {
|
|
50
|
+
const config = resolveManagerConfig();
|
|
51
|
+
const app = new Hono();
|
|
52
|
+
app.get("/internal/healthz", (c) => c.json({ ok: true, service: "manager" }));
|
|
53
|
+
app.post("/internal/workdays/start", async (c) => {
|
|
54
|
+
const body = await c.req.json().catch(() => ({}));
|
|
55
|
+
const graphRefresh = await sdk.refreshGraph();
|
|
56
|
+
const workDay = await sdk.startWorkDay({
|
|
57
|
+
id: typeof body.id === "string" ? body.id : void 0,
|
|
58
|
+
projectId: config.projectId,
|
|
59
|
+
capacityBudget: Number(body.capacityBudget ?? config.defaultCapacityBudget),
|
|
60
|
+
graphVersion: graphRefresh.snapshotRoot,
|
|
61
|
+
summary: { graphRefresh },
|
|
62
|
+
actor: "manager"
|
|
63
|
+
});
|
|
64
|
+
const tasks = workDay.payload ? await seedRootTasks(sdk, String(workDay.payload.id)) : [];
|
|
65
|
+
return c.json({
|
|
66
|
+
ok: true,
|
|
67
|
+
workDay: workDay.payload,
|
|
68
|
+
seededTasks: tasks.map((entry) => entry.payload).filter(Boolean)
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
app.post("/internal/workdays/:id/close", async (c) => {
|
|
72
|
+
const body = await c.req.json().catch(() => ({}));
|
|
73
|
+
const result = await sdk.closeWorkDay({
|
|
74
|
+
id: c.req.param("id"),
|
|
75
|
+
state: body.state,
|
|
76
|
+
summary: body.summary ?? null,
|
|
77
|
+
actor: "manager"
|
|
78
|
+
});
|
|
79
|
+
return c.json({ ok: true, payload: result.payload });
|
|
80
|
+
});
|
|
81
|
+
app.post("/internal/context/resolve-task", async (c) => {
|
|
82
|
+
const body = await c.req.json().catch(() => ({}));
|
|
83
|
+
const taskId = String(body.taskId ?? "");
|
|
84
|
+
const context = await sdk.getManagerContext(taskId);
|
|
85
|
+
const task = context.payload.task;
|
|
86
|
+
const agent = task ? (await sdk.get({ model: "agent", slug: String(task.agentId) })).payload : null;
|
|
87
|
+
return c.json({
|
|
88
|
+
ok: true,
|
|
89
|
+
payload: {
|
|
90
|
+
...context.payload,
|
|
91
|
+
agent
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
app.post("/internal/graph/search", async (c) => {
|
|
96
|
+
const body = await c.req.json().catch(() => ({}));
|
|
97
|
+
const query = String(body.query ?? "");
|
|
98
|
+
const scope = String(body.scope ?? "sections");
|
|
99
|
+
const payload = scope === "files" ? await sdk.searchFiles(query, body.options) : scope === "entities" ? await sdk.searchEntities(query, body.options) : await sdk.searchSections(query, body.options);
|
|
100
|
+
return c.json({ ok: true, payload });
|
|
101
|
+
});
|
|
102
|
+
app.post("/internal/graph/subgraph", async (c) => {
|
|
103
|
+
const body = await c.req.json().catch(() => ({}));
|
|
104
|
+
const payload = await sdk.getSubgraph(
|
|
105
|
+
Array.isArray(body.seedIds) ? body.seedIds.map(String) : [],
|
|
106
|
+
body.options
|
|
107
|
+
);
|
|
108
|
+
return c.json({ ok: true, payload });
|
|
109
|
+
});
|
|
110
|
+
app.post("/internal/graph/query", async (c) => {
|
|
111
|
+
const body = await c.req.json().catch(() => ({}));
|
|
112
|
+
const payload = await sdk.queryGraph(body);
|
|
113
|
+
if (typeof body.workDayId === "string" && body.workDayId) {
|
|
114
|
+
await sdk.create({
|
|
115
|
+
model: "graph_run",
|
|
116
|
+
data: {
|
|
117
|
+
workDayId: body.workDayId,
|
|
118
|
+
corpusHash: String(body.corpusHash ?? "query-graph"),
|
|
119
|
+
graphVersion: String(body.graphVersion ?? ""),
|
|
120
|
+
queryJson: JSON.stringify(body ?? {}),
|
|
121
|
+
seedIdsJson: JSON.stringify(payload.seedIds),
|
|
122
|
+
selectedNodeIdsJson: JSON.stringify(payload.nodes.map((entry) => entry.node.id)),
|
|
123
|
+
statsJson: JSON.stringify({ nodeCount: payload.nodes.length, edgeCount: payload.edges.length })
|
|
124
|
+
},
|
|
125
|
+
actor: "manager"
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return c.json({ ok: true, payload });
|
|
129
|
+
});
|
|
130
|
+
app.post("/internal/graph/context-pack", async (c) => {
|
|
131
|
+
const body = await c.req.json().catch(() => ({}));
|
|
132
|
+
const payload = await sdk.buildContextPack(body);
|
|
133
|
+
if (typeof body.workDayId === "string" && body.workDayId) {
|
|
134
|
+
await sdk.create({
|
|
135
|
+
model: "graph_run",
|
|
136
|
+
data: {
|
|
137
|
+
workDayId: body.workDayId,
|
|
138
|
+
corpusHash: String(body.corpusHash ?? "context-pack"),
|
|
139
|
+
graphVersion: String(body.graphVersion ?? ""),
|
|
140
|
+
queryJson: JSON.stringify(body ?? {}),
|
|
141
|
+
seedIdsJson: JSON.stringify(payload.seedIds),
|
|
142
|
+
selectedNodeIdsJson: JSON.stringify(payload.includedNodeIds),
|
|
143
|
+
statsJson: JSON.stringify({ nodeCount: payload.nodes.length, edgeCount: payload.edges.length, totalTokenEstimate: payload.totalTokenEstimate })
|
|
144
|
+
},
|
|
145
|
+
actor: "manager"
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return c.json({ ok: true, payload });
|
|
149
|
+
});
|
|
150
|
+
app.post("/internal/graph/parse-dsl", async (c) => {
|
|
151
|
+
const body = await c.req.json().catch(() => ({}));
|
|
152
|
+
const payload = await sdk.parseGraphDsl(String(body.source ?? body.query ?? ""));
|
|
153
|
+
return c.json({ ok: true, payload });
|
|
154
|
+
});
|
|
155
|
+
app.get("/internal/graph/node/:id", async (c) => {
|
|
156
|
+
const payload = await sdk.getGraphNode(c.req.param("id"));
|
|
157
|
+
return payload ? c.json({ ok: true, payload }) : c.json({ ok: false, error: "Unknown graph node." }, 404);
|
|
158
|
+
});
|
|
159
|
+
app.post("/internal/tasks/:id/followups", async (c) => {
|
|
160
|
+
const body = await c.req.json().catch(() => ({}));
|
|
161
|
+
const current = await sdk.get({ model: "task", id: c.req.param("id") });
|
|
162
|
+
if (!current.payload) {
|
|
163
|
+
return c.json({ ok: false, error: "Unknown task." }, 404);
|
|
164
|
+
}
|
|
165
|
+
const followups = Array.isArray(body.followups) ? body.followups : [];
|
|
166
|
+
const created = [];
|
|
167
|
+
for (const followup of followups) {
|
|
168
|
+
created.push(await sdk.createTask({
|
|
169
|
+
workDayId: String(current.payload.workDayId ?? ""),
|
|
170
|
+
agentId: String(followup.agentId ?? current.payload.agentId ?? ""),
|
|
171
|
+
type: String(followup.type ?? "followup"),
|
|
172
|
+
priority: Number(followup.priority ?? 0),
|
|
173
|
+
idempotencyKey: String(followup.idempotencyKey ?? `${c.req.param("id")}:${created.length}`),
|
|
174
|
+
payload: followup.payload ?? {},
|
|
175
|
+
graphVersion: typeof followup.graphVersion === "string" ? followup.graphVersion : null,
|
|
176
|
+
parentTaskId: c.req.param("id"),
|
|
177
|
+
actor: "manager"
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
return c.json({ ok: true, payload: created.map((entry) => entry.payload) });
|
|
181
|
+
});
|
|
182
|
+
return app;
|
|
183
|
+
}
|
|
184
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
185
|
+
const entryFile = process.argv[1] ?? "";
|
|
186
|
+
if (entryFile === currentFile) {
|
|
187
|
+
const config = resolveManagerConfig();
|
|
188
|
+
const app = createManagerApp();
|
|
189
|
+
const server = createServer((req, res) => {
|
|
190
|
+
void honoNodeHandler(app, req, res);
|
|
191
|
+
});
|
|
192
|
+
server.listen(config.port, config.host, () => {
|
|
193
|
+
process.stdout.write(`Treeseed manager listening on http://${config.host}:${config.port}
|
|
194
|
+
`);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
export {
|
|
198
|
+
createManagerApp
|
|
199
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export declare function runWorkdayReport(): Promise<{
|
|
3
|
+
ok: boolean;
|
|
4
|
+
skipped: boolean;
|
|
5
|
+
workDayId?: undefined;
|
|
6
|
+
summary?: undefined;
|
|
7
|
+
} | {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
workDayId: string;
|
|
10
|
+
summary: {
|
|
11
|
+
totalTasks: number;
|
|
12
|
+
completedTasks: number;
|
|
13
|
+
failedTasks: number;
|
|
14
|
+
pendingTasks: number;
|
|
15
|
+
};
|
|
16
|
+
skipped?: undefined;
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { createGatewayClient, createServiceSdk } from "./common.js";
|
|
4
|
+
async function runWorkdayReport() {
|
|
5
|
+
const sdk = createServiceSdk();
|
|
6
|
+
const gateway = createGatewayClient();
|
|
7
|
+
const workDays = await sdk.search({ model: "work_day", limit: 1 });
|
|
8
|
+
const active = workDays.payload[0];
|
|
9
|
+
if (!active || typeof active.id !== "string") {
|
|
10
|
+
return { ok: true, skipped: true };
|
|
11
|
+
}
|
|
12
|
+
const tasks = await sdk.searchTasks({ workDayId: active.id, limit: 200 });
|
|
13
|
+
const summary = {
|
|
14
|
+
totalTasks: tasks.payload.length,
|
|
15
|
+
completedTasks: tasks.payload.filter((entry) => entry.state === "completed").length,
|
|
16
|
+
failedTasks: tasks.payload.filter((entry) => entry.state === "failed").length,
|
|
17
|
+
pendingTasks: tasks.payload.filter((entry) => entry.state !== "completed" && entry.state !== "failed").length
|
|
18
|
+
};
|
|
19
|
+
if (gateway) {
|
|
20
|
+
await gateway.requestJson("/reports", {
|
|
21
|
+
body: {
|
|
22
|
+
workDayId: active.id,
|
|
23
|
+
kind: "workday_summary",
|
|
24
|
+
body: summary
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
await gateway.requestJson(`/workdays/${encodeURIComponent(active.id)}/close`, {
|
|
28
|
+
body: {
|
|
29
|
+
state: "completed",
|
|
30
|
+
summary
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return { ok: true, workDayId: active.id, summary };
|
|
35
|
+
}
|
|
36
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
37
|
+
const entryFile = process.argv[1] ?? "";
|
|
38
|
+
if (entryFile === currentFile) {
|
|
39
|
+
process.stdout.write(`${JSON.stringify(await runWorkdayReport(), null, 2)}
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
runWorkdayReport
|
|
44
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { resolveWorkerConfig } from "./common.js";
|
|
4
|
+
async function runWorkdayStart() {
|
|
5
|
+
const managerBaseUrl = resolveWorkerConfig().managerBaseUrl;
|
|
6
|
+
const response = await fetch(`${managerBaseUrl}/internal/workdays/start`, {
|
|
7
|
+
method: "POST",
|
|
8
|
+
headers: {
|
|
9
|
+
accept: "application/json",
|
|
10
|
+
"content-type": "application/json"
|
|
11
|
+
},
|
|
12
|
+
body: JSON.stringify({})
|
|
13
|
+
});
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
throw new Error(`Workday start failed with ${response.status}.`);
|
|
16
|
+
}
|
|
17
|
+
return response.json();
|
|
18
|
+
}
|
|
19
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
20
|
+
const entryFile = process.argv[1] ?? "";
|
|
21
|
+
if (entryFile === currentFile) {
|
|
22
|
+
process.stdout.write(`${JSON.stringify(await runWorkdayStart(), null, 2)}
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
runWorkdayStart
|
|
27
|
+
};
|