@treeseed/agent 0.1.2
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 +62 -0
- package/dist/agent-runtime.js +111 -0
- package/dist/agents/adapters/execution.js +90 -0
- package/dist/agents/adapters/mutations.js +30 -0
- package/dist/agents/adapters/notification.js +16 -0
- package/dist/agents/adapters/repository.js +61 -0
- package/dist/agents/adapters/research.js +25 -0
- package/dist/agents/adapters/verification.js +62 -0
- package/dist/agents/cli-tools.js +5 -0
- package/dist/agents/cli.js +77 -0
- package/dist/agents/content-store.js +1 -0
- package/dist/agents/contracts/messages.js +138 -0
- package/dist/agents/contracts/run.js +0 -0
- package/dist/agents/d1-store.js +1 -0
- package/dist/agents/frontmatter.js +1 -0
- package/dist/agents/git-runtime.js +1 -0
- package/dist/agents/index.js +5 -0
- package/dist/agents/kernel/agent-kernel.js +284 -0
- package/dist/agents/kernel/trigger-resolver.js +153 -0
- package/dist/agents/model-registry.js +1 -0
- package/dist/agents/registry-helper.js +14 -0
- package/dist/agents/registry.js +91 -0
- package/dist/agents/runtime-types.js +0 -0
- package/dist/agents/sdk-filters.js +1 -0
- package/dist/agents/sdk-types.js +1 -0
- package/dist/agents/sdk.js +1 -0
- package/dist/agents/spec-loader.js +53 -0
- package/dist/agents/spec-normalizer.js +257 -0
- package/dist/agents/spec-types.js +0 -0
- package/dist/agents/stores/cursor-store.js +1 -0
- package/dist/agents/stores/helpers.js +1 -0
- package/dist/agents/stores/lease-store.js +1 -0
- package/dist/agents/stores/message-store.js +1 -0
- package/dist/agents/stores/run-store.js +1 -0
- package/dist/agents/stores/subscription-store.js +1 -0
- package/dist/agents/testing/agents-smoke.js +32 -0
- package/dist/agents/testing/e2e-harness.js +435 -0
- package/dist/agents/wrangler-d1.js +1 -0
- package/dist/index.js +9 -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 +98 -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 +19 -0
- package/dist/scripts/release-verify.d.ts +1 -0
- package/dist/scripts/release-verify.js +143 -0
- package/dist/scripts/test-smoke.d.ts +1 -0
- package/dist/scripts/test-smoke.js +23 -0
- package/dist/scripts/treeseed-agents.d.ts +2 -0
- package/dist/scripts/treeseed-agents.js +13 -0
- package/dist/src/agent-runtime.d.ts +17 -0
- package/dist/src/agents/adapters/execution.d.ts +46 -0
- package/dist/src/agents/adapters/mutations.d.ts +22 -0
- package/dist/src/agents/adapters/notification.d.ts +11 -0
- package/dist/src/agents/adapters/repository.d.ts +28 -0
- package/dist/src/agents/adapters/research.d.ts +14 -0
- package/dist/src/agents/adapters/verification.d.ts +36 -0
- package/dist/src/agents/cli-tools.d.ts +1 -0
- package/dist/src/agents/cli.d.ts +6 -0
- package/dist/src/agents/content-store.d.ts +1 -0
- package/dist/src/agents/contracts/messages.d.ts +88 -0
- package/dist/src/agents/contracts/run.d.ts +20 -0
- package/dist/src/agents/d1-store.d.ts +1 -0
- package/dist/src/agents/frontmatter.d.ts +1 -0
- package/dist/src/agents/git-runtime.d.ts +1 -0
- package/dist/src/agents/index.d.ts +1 -0
- package/dist/src/agents/kernel/agent-kernel.d.ts +52 -0
- package/dist/src/agents/kernel/trigger-resolver.d.ts +18 -0
- package/dist/src/agents/model-registry.d.ts +1 -0
- package/dist/src/agents/registry-helper.d.ts +4 -0
- package/dist/src/agents/registry.d.ts +7 -0
- package/dist/src/agents/runtime-types.d.ts +117 -0
- package/dist/src/agents/sdk-filters.d.ts +1 -0
- package/dist/src/agents/sdk-types.d.ts +1 -0
- package/dist/src/agents/sdk.d.ts +1 -0
- package/dist/src/agents/spec-loader.d.ts +18 -0
- package/dist/src/agents/spec-normalizer.d.ts +2 -0
- package/dist/src/agents/spec-types.d.ts +64 -0
- package/dist/src/agents/stores/cursor-store.d.ts +1 -0
- package/dist/src/agents/stores/helpers.d.ts +1 -0
- package/dist/src/agents/stores/lease-store.d.ts +1 -0
- package/dist/src/agents/stores/message-store.d.ts +1 -0
- package/dist/src/agents/stores/run-store.d.ts +1 -0
- package/dist/src/agents/stores/subscription-store.d.ts +1 -0
- package/dist/src/agents/testing/agents-smoke.d.ts +1 -0
- package/dist/src/agents/testing/e2e-harness.d.ts +44 -0
- package/dist/src/agents/wrangler-d1.d.ts +1 -0
- package/dist/src/index.d.ts +3 -0
- package/package.json +54 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const AGENT_MESSAGE_TYPES = [
|
|
2
|
+
"question_priority_updated",
|
|
3
|
+
"objective_priority_updated",
|
|
4
|
+
"architecture_updated",
|
|
5
|
+
"subscriber_notified",
|
|
6
|
+
"research_started",
|
|
7
|
+
"research_completed",
|
|
8
|
+
"task_complete",
|
|
9
|
+
"task_waiting",
|
|
10
|
+
"task_failed",
|
|
11
|
+
"task_verified",
|
|
12
|
+
"review_failed",
|
|
13
|
+
"review_waiting",
|
|
14
|
+
"release_started",
|
|
15
|
+
"release_completed",
|
|
16
|
+
"release_failed"
|
|
17
|
+
];
|
|
18
|
+
function ensureString(value, label) {
|
|
19
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
20
|
+
throw new Error(`Invalid ${label}: expected non-empty string.`);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function ensureOptionalString(value, label) {
|
|
25
|
+
if (value === null || value === void 0) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return ensureString(value, label);
|
|
29
|
+
}
|
|
30
|
+
function ensureStringArray(value, label) {
|
|
31
|
+
if (!Array.isArray(value)) {
|
|
32
|
+
throw new Error(`Invalid ${label}: expected array.`);
|
|
33
|
+
}
|
|
34
|
+
return value.map((entry, index) => ensureString(entry, `${label}[${index}]`));
|
|
35
|
+
}
|
|
36
|
+
function ensureNumber(value, label) {
|
|
37
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
38
|
+
throw new Error(`Invalid ${label}: expected number.`);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
function parseAgentMessagePayload(type, payloadJson) {
|
|
43
|
+
const parsed = JSON.parse(payloadJson);
|
|
44
|
+
switch (type) {
|
|
45
|
+
case "question_priority_updated":
|
|
46
|
+
return {
|
|
47
|
+
questionId: ensureString(parsed.questionId, "questionId"),
|
|
48
|
+
reason: ensureString(parsed.reason, "reason"),
|
|
49
|
+
plannerRunId: ensureString(parsed.plannerRunId, "plannerRunId")
|
|
50
|
+
};
|
|
51
|
+
case "objective_priority_updated":
|
|
52
|
+
return {
|
|
53
|
+
objectiveId: ensureString(parsed.objectiveId, "objectiveId"),
|
|
54
|
+
reason: ensureString(parsed.reason, "reason"),
|
|
55
|
+
plannerRunId: ensureString(parsed.plannerRunId, "plannerRunId")
|
|
56
|
+
};
|
|
57
|
+
case "architecture_updated":
|
|
58
|
+
return {
|
|
59
|
+
objectiveId: ensureString(parsed.objectiveId, "objectiveId"),
|
|
60
|
+
knowledgeId: ensureString(parsed.knowledgeId, "knowledgeId"),
|
|
61
|
+
architectRunId: ensureString(parsed.architectRunId, "architectRunId")
|
|
62
|
+
};
|
|
63
|
+
case "subscriber_notified":
|
|
64
|
+
return {
|
|
65
|
+
email: ensureString(parsed.email, "email"),
|
|
66
|
+
itemCount: ensureNumber(parsed.itemCount, "itemCount"),
|
|
67
|
+
notifierRunId: ensureString(parsed.notifierRunId, "notifierRunId")
|
|
68
|
+
};
|
|
69
|
+
case "research_started":
|
|
70
|
+
return {
|
|
71
|
+
questionId: ensureString(parsed.questionId, "questionId"),
|
|
72
|
+
researcherRunId: ensureString(parsed.researcherRunId, "researcherRunId")
|
|
73
|
+
};
|
|
74
|
+
case "research_completed":
|
|
75
|
+
return {
|
|
76
|
+
questionId: ensureString(parsed.questionId, "questionId"),
|
|
77
|
+
knowledgeId: ensureOptionalString(parsed.knowledgeId, "knowledgeId"),
|
|
78
|
+
researcherRunId: ensureString(parsed.researcherRunId, "researcherRunId")
|
|
79
|
+
};
|
|
80
|
+
case "task_complete":
|
|
81
|
+
return {
|
|
82
|
+
branchName: ensureOptionalString(parsed.branchName, "branchName"),
|
|
83
|
+
changedTargets: ensureStringArray(parsed.changedTargets, "changedTargets"),
|
|
84
|
+
engineerRunId: ensureString(parsed.engineerRunId, "engineerRunId")
|
|
85
|
+
};
|
|
86
|
+
case "task_waiting":
|
|
87
|
+
return {
|
|
88
|
+
blockingReason: ensureString(parsed.blockingReason, "blockingReason"),
|
|
89
|
+
engineerRunId: ensureString(parsed.engineerRunId, "engineerRunId")
|
|
90
|
+
};
|
|
91
|
+
case "task_failed":
|
|
92
|
+
return {
|
|
93
|
+
failureSummary: ensureString(parsed.failureSummary, "failureSummary"),
|
|
94
|
+
engineerRunId: ensureString(parsed.engineerRunId, "engineerRunId")
|
|
95
|
+
};
|
|
96
|
+
case "task_verified":
|
|
97
|
+
return {
|
|
98
|
+
branchName: ensureOptionalString(parsed.branchName, "branchName"),
|
|
99
|
+
reviewerRunId: ensureString(parsed.reviewerRunId, "reviewerRunId")
|
|
100
|
+
};
|
|
101
|
+
case "review_failed":
|
|
102
|
+
return {
|
|
103
|
+
failureSummary: ensureString(parsed.failureSummary, "failureSummary"),
|
|
104
|
+
reviewerRunId: ensureString(parsed.reviewerRunId, "reviewerRunId")
|
|
105
|
+
};
|
|
106
|
+
case "review_waiting":
|
|
107
|
+
return {
|
|
108
|
+
blockingReason: ensureString(parsed.blockingReason, "blockingReason"),
|
|
109
|
+
reviewerRunId: ensureString(parsed.reviewerRunId, "reviewerRunId")
|
|
110
|
+
};
|
|
111
|
+
case "release_started":
|
|
112
|
+
return {
|
|
113
|
+
taskRunId: ensureOptionalString(parsed.taskRunId, "taskRunId"),
|
|
114
|
+
releaserRunId: ensureString(parsed.releaserRunId, "releaserRunId")
|
|
115
|
+
};
|
|
116
|
+
case "release_completed":
|
|
117
|
+
return {
|
|
118
|
+
releaseSummary: ensureString(parsed.releaseSummary, "releaseSummary"),
|
|
119
|
+
releaserRunId: ensureString(parsed.releaserRunId, "releaserRunId")
|
|
120
|
+
};
|
|
121
|
+
case "release_failed":
|
|
122
|
+
return {
|
|
123
|
+
failureSummary: ensureString(parsed.failureSummary, "failureSummary"),
|
|
124
|
+
releaserRunId: ensureString(parsed.releaserRunId, "releaserRunId")
|
|
125
|
+
};
|
|
126
|
+
default:
|
|
127
|
+
throw new Error(`Unsupported message type "${type}".`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function serializeAgentMessagePayload(type, payload) {
|
|
131
|
+
parseAgentMessagePayload(type, JSON.stringify(payload));
|
|
132
|
+
return payload;
|
|
133
|
+
}
|
|
134
|
+
export {
|
|
135
|
+
AGENT_MESSAGE_TYPES,
|
|
136
|
+
parseAgentMessagePayload,
|
|
137
|
+
serializeAgentMessagePayload
|
|
138
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@treeseed/sdk/d1-store";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@treeseed/sdk/frontmatter";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@treeseed/sdk/git-runtime";
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { createExecutionAdapter } from "../adapters/execution.js";
|
|
2
|
+
import { LocalBranchMutationAdapter } from "../adapters/mutations.js";
|
|
3
|
+
import { createNotificationAdapter } from "../adapters/notification.js";
|
|
4
|
+
import { createRepositoryInspectionAdapter } from "../adapters/repository.js";
|
|
5
|
+
import { createResearchAdapter } from "../adapters/research.js";
|
|
6
|
+
import { createVerificationAdapter } from "../adapters/verification.js";
|
|
7
|
+
import { resolveAgentHandler } from "../registry.js";
|
|
8
|
+
import { resolveTriggerDecision } from "./trigger-resolver.js";
|
|
9
|
+
import { loadActiveAgentSpecs, loadAllAgentSpecs, summarizeAgentSpec } from "../spec-loader.js";
|
|
10
|
+
import { getTreeseedAgentProviderSelections } from "@treeseed/core/deploy/runtime";
|
|
11
|
+
import { resolveAgentRuntimeProviders } from "../../agent-runtime.js";
|
|
12
|
+
function nowIso() {
|
|
13
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
14
|
+
}
|
|
15
|
+
class AgentKernel {
|
|
16
|
+
constructor(sdk, repoRoot, options) {
|
|
17
|
+
this.sdk = sdk;
|
|
18
|
+
this.repoRoot = repoRoot;
|
|
19
|
+
const runtimeProviders = resolveAgentRuntimeProviders(repoRoot, getTreeseedAgentProviderSelections());
|
|
20
|
+
this.execution = options?.execution ?? runtimeProviders.execution ?? createExecutionAdapter();
|
|
21
|
+
this.mutations = options?.mutations ?? runtimeProviders.mutations ?? new LocalBranchMutationAdapter(repoRoot);
|
|
22
|
+
this.repository = options?.repository ?? runtimeProviders.repository ?? createRepositoryInspectionAdapter();
|
|
23
|
+
this.verification = options?.verification ?? runtimeProviders.verification ?? createVerificationAdapter();
|
|
24
|
+
this.notifications = options?.notifications ?? runtimeProviders.notifications ?? createNotificationAdapter();
|
|
25
|
+
this.research = options?.research ?? runtimeProviders.research ?? createResearchAdapter();
|
|
26
|
+
}
|
|
27
|
+
sdk;
|
|
28
|
+
repoRoot;
|
|
29
|
+
execution;
|
|
30
|
+
mutations;
|
|
31
|
+
repository;
|
|
32
|
+
verification;
|
|
33
|
+
notifications;
|
|
34
|
+
research;
|
|
35
|
+
activeRuns = /* @__PURE__ */ new Set();
|
|
36
|
+
lastRunAt = /* @__PURE__ */ new Map();
|
|
37
|
+
async doctor() {
|
|
38
|
+
const { specs, diagnostics } = await loadAllAgentSpecs(this.sdk);
|
|
39
|
+
for (const agent of specs.filter((entry) => entry.enabled)) {
|
|
40
|
+
resolveAgentHandler(agent.handler);
|
|
41
|
+
}
|
|
42
|
+
const errors = diagnostics.filter((entry) => entry.severity === "error");
|
|
43
|
+
if (errors.length) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Agent spec validation failed: ${errors.map((entry) => `${entry.slug}:${entry.field}:${entry.message}`).join(" | ")}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
agents: specs.map(summarizeAgentSpec),
|
|
50
|
+
diagnostics
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
sortAgents(agents) {
|
|
54
|
+
const priority = {
|
|
55
|
+
planner: 10,
|
|
56
|
+
researcher: 20,
|
|
57
|
+
architect: 30,
|
|
58
|
+
engineer: 40,
|
|
59
|
+
reviewer: 50,
|
|
60
|
+
releaser: 60,
|
|
61
|
+
notifier: 70
|
|
62
|
+
};
|
|
63
|
+
return [...agents].sort(
|
|
64
|
+
(left, right) => (priority[left.handler] ?? 100) - (priority[right.handler] ?? 100)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
async resolveTrigger(agent, mode = "auto") {
|
|
68
|
+
const decision = await resolveTriggerDecision({
|
|
69
|
+
agent,
|
|
70
|
+
mode,
|
|
71
|
+
isRunning: this.activeRuns.has(agent.slug),
|
|
72
|
+
lastRunAt: this.lastRunAt.get(agent.slug),
|
|
73
|
+
sdk: this.sdk.scopeForAgent(agent)
|
|
74
|
+
});
|
|
75
|
+
return decision.kind === "ready" ? decision.invocation ?? null : null;
|
|
76
|
+
}
|
|
77
|
+
async recordRunTrace(trace) {
|
|
78
|
+
await this.sdk.recordRun({ run: trace });
|
|
79
|
+
}
|
|
80
|
+
buildTrace(agent, runId, trigger, overrides) {
|
|
81
|
+
return {
|
|
82
|
+
runId,
|
|
83
|
+
agentSlug: agent.slug,
|
|
84
|
+
handlerKind: agent.handler,
|
|
85
|
+
triggerKind: trigger.kind,
|
|
86
|
+
triggerSource: trigger.source,
|
|
87
|
+
claimedMessageId: trigger.message?.id ?? null,
|
|
88
|
+
selectedItemKey: null,
|
|
89
|
+
branchName: null,
|
|
90
|
+
commitSha: null,
|
|
91
|
+
changedPaths: [],
|
|
92
|
+
summary: null,
|
|
93
|
+
error: null,
|
|
94
|
+
errorCategory: null,
|
|
95
|
+
startedAt: nowIso(),
|
|
96
|
+
finishedAt: null,
|
|
97
|
+
status: "running",
|
|
98
|
+
...overrides
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
categorizeError(error) {
|
|
102
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
103
|
+
if (message.includes("not allowed")) {
|
|
104
|
+
return "permission_error";
|
|
105
|
+
}
|
|
106
|
+
if (message.includes("message")) {
|
|
107
|
+
return "message_claim_error";
|
|
108
|
+
}
|
|
109
|
+
if (message.includes("lease")) {
|
|
110
|
+
return "lease_error";
|
|
111
|
+
}
|
|
112
|
+
if (message.includes("commit") || message.includes("worktree") || message.includes("artifact")) {
|
|
113
|
+
return "mutation_error";
|
|
114
|
+
}
|
|
115
|
+
if (message.includes("Copilot") || message.includes("execution")) {
|
|
116
|
+
return "execution_error";
|
|
117
|
+
}
|
|
118
|
+
return "sdk_error";
|
|
119
|
+
}
|
|
120
|
+
async executeAgent(agent, trigger) {
|
|
121
|
+
if (this.activeRuns.has(agent.slug)) {
|
|
122
|
+
return {
|
|
123
|
+
status: "waiting",
|
|
124
|
+
summary: `Agent ${agent.slug} is already running.`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
this.activeRuns.add(agent.slug);
|
|
128
|
+
const runId = crypto.randomUUID();
|
|
129
|
+
const handler = resolveAgentHandler(agent.handler);
|
|
130
|
+
const scopedSdk = this.sdk.scopeForAgent(agent);
|
|
131
|
+
const context = {
|
|
132
|
+
runId,
|
|
133
|
+
repoRoot: this.repoRoot,
|
|
134
|
+
agent,
|
|
135
|
+
sdk: scopedSdk,
|
|
136
|
+
trigger,
|
|
137
|
+
execution: this.execution,
|
|
138
|
+
mutations: this.mutations,
|
|
139
|
+
repository: this.repository,
|
|
140
|
+
verification: this.verification,
|
|
141
|
+
notifications: this.notifications,
|
|
142
|
+
research: this.research
|
|
143
|
+
};
|
|
144
|
+
await this.recordRunTrace(this.buildTrace(agent, runId, trigger, {}));
|
|
145
|
+
try {
|
|
146
|
+
const inputs = await handler.resolveInputs(context);
|
|
147
|
+
const result = await handler.execute(context, inputs);
|
|
148
|
+
const output = await handler.emitOutputs(context, result);
|
|
149
|
+
if (trigger.message) {
|
|
150
|
+
await scopedSdk.ackMessage({
|
|
151
|
+
id: trigger.message.id,
|
|
152
|
+
status: output.status === "completed" ? "completed" : output.status === "waiting" ? "pending" : "failed"
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
await this.recordRunTrace(
|
|
156
|
+
this.buildTrace(agent, runId, trigger, {
|
|
157
|
+
status: output.status,
|
|
158
|
+
branchName: output.metadata?.branchName ?? null,
|
|
159
|
+
commitSha: output.metadata?.commitSha ?? null,
|
|
160
|
+
changedPaths: output.metadata?.changedPaths ?? [],
|
|
161
|
+
summary: output.summary,
|
|
162
|
+
error: output.status === "failed" ? output.stderr ?? output.summary : null,
|
|
163
|
+
errorCategory: output.status === "failed" ? output.errorCategory ?? "execution_error" : null,
|
|
164
|
+
finishedAt: nowIso()
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
await this.sdk.upsertCursor({
|
|
168
|
+
agentSlug: agent.slug,
|
|
169
|
+
cursorKey: "last_run_at",
|
|
170
|
+
cursorValue: nowIso()
|
|
171
|
+
});
|
|
172
|
+
this.lastRunAt.set(agent.slug, Date.now());
|
|
173
|
+
return output;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (trigger.message) {
|
|
176
|
+
await scopedSdk.ackMessage({
|
|
177
|
+
id: trigger.message.id,
|
|
178
|
+
status: "failed"
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
await this.recordRunTrace(
|
|
182
|
+
this.buildTrace(agent, runId, trigger, {
|
|
183
|
+
status: "failed",
|
|
184
|
+
error: error instanceof Error ? error.message : String(error),
|
|
185
|
+
errorCategory: this.categorizeError(error),
|
|
186
|
+
finishedAt: nowIso()
|
|
187
|
+
})
|
|
188
|
+
);
|
|
189
|
+
throw error;
|
|
190
|
+
} finally {
|
|
191
|
+
this.activeRuns.delete(agent.slug);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async runAgent(slug, mode = "manual") {
|
|
195
|
+
const { specs, diagnostics } = await loadActiveAgentSpecs(this.sdk);
|
|
196
|
+
const errors = diagnostics.filter((entry) => entry.severity === "error");
|
|
197
|
+
if (errors.length) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Agent spec validation failed: ${errors.map((entry) => `${entry.slug}:${entry.field}:${entry.message}`).join(" | ")}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const agents = this.sortAgents(specs);
|
|
203
|
+
const agent = agents.find((entry) => entry.slug === slug);
|
|
204
|
+
if (!agent) {
|
|
205
|
+
throw new Error(`Unknown or disabled agent "${slug}".`);
|
|
206
|
+
}
|
|
207
|
+
const trigger = await this.resolveTrigger(agent, mode);
|
|
208
|
+
if (!trigger) {
|
|
209
|
+
return {
|
|
210
|
+
status: "waiting",
|
|
211
|
+
summary: `No runnable trigger found for ${slug}.`
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return this.executeAgent(agent, trigger);
|
|
215
|
+
}
|
|
216
|
+
async runCycle() {
|
|
217
|
+
const { specs, diagnostics } = await loadActiveAgentSpecs(this.sdk);
|
|
218
|
+
const errors = diagnostics.filter((entry) => entry.severity === "error");
|
|
219
|
+
if (errors.length) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Agent spec validation failed: ${errors.map((entry) => `${entry.slug}:${entry.field}:${entry.message}`).join(" | ")}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
const agents = this.sortAgents(specs);
|
|
225
|
+
const results = [];
|
|
226
|
+
for (const agent of agents) {
|
|
227
|
+
const runsThisCycle = agent.triggerPolicy?.maxRunsPerCycle ?? 1;
|
|
228
|
+
for (let index = 0; index < runsThisCycle; index += 1) {
|
|
229
|
+
const trigger = await this.resolveTrigger(agent, "auto");
|
|
230
|
+
if (!trigger) {
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
results.push({
|
|
234
|
+
slug: agent.slug,
|
|
235
|
+
result: await this.executeAgent(agent, trigger)
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return results;
|
|
240
|
+
}
|
|
241
|
+
async start(intervalMs = Number(process.env.TREESEED_AGENT_SUPERVISOR_INTERVAL_MS ?? 6e4)) {
|
|
242
|
+
await this.runCycle();
|
|
243
|
+
setInterval(() => {
|
|
244
|
+
void this.runCycle();
|
|
245
|
+
}, intervalMs);
|
|
246
|
+
}
|
|
247
|
+
async drainMessages() {
|
|
248
|
+
const { specs, diagnostics } = await loadActiveAgentSpecs(this.sdk);
|
|
249
|
+
const errors = diagnostics.filter((entry) => entry.severity === "error");
|
|
250
|
+
if (errors.length) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`Agent spec validation failed: ${errors.map((entry) => `${entry.slug}:${entry.field}:${entry.message}`).join(" | ")}`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
const agents = this.sortAgents(specs);
|
|
256
|
+
const messageAgents = agents.filter(
|
|
257
|
+
(agent) => agent.triggers.some((trigger) => trigger.type === "message")
|
|
258
|
+
);
|
|
259
|
+
const results = [];
|
|
260
|
+
for (const agent of messageAgents) {
|
|
261
|
+
results.push({
|
|
262
|
+
slug: agent.slug,
|
|
263
|
+
result: await this.runAgent(agent.slug, "auto")
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return results;
|
|
267
|
+
}
|
|
268
|
+
releaseLeases() {
|
|
269
|
+
return this.sdk.releaseAllLeases();
|
|
270
|
+
}
|
|
271
|
+
async replayMessage(id) {
|
|
272
|
+
await this.sdk.ackMessage({
|
|
273
|
+
id,
|
|
274
|
+
status: "pending"
|
|
275
|
+
});
|
|
276
|
+
return {
|
|
277
|
+
id,
|
|
278
|
+
status: "pending"
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
export {
|
|
283
|
+
AgentKernel
|
|
284
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
function buildManualInvocation(agent) {
|
|
3
|
+
const scheduleLike = agent.triggers.find((trigger) => trigger.type === "schedule" || trigger.type === "startup");
|
|
4
|
+
if (!scheduleLike) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
return {
|
|
8
|
+
kind: "manual",
|
|
9
|
+
source: "manual",
|
|
10
|
+
trigger: scheduleLike
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function evaluateCooldown(agent, lastRunAt) {
|
|
14
|
+
const cooldownMs = agent.execution.cooldownSeconds * 1e3;
|
|
15
|
+
return (lastRunAt ?? 0) > 0 && Date.now() - (lastRunAt ?? 0) < cooldownMs;
|
|
16
|
+
}
|
|
17
|
+
async function resolveMessageTrigger(agent, sdk, trigger, messageBatchSize) {
|
|
18
|
+
for (let index = 0; index < messageBatchSize; index += 1) {
|
|
19
|
+
const claimed = await sdk.claimMessage({
|
|
20
|
+
workerId: `${agent.slug}-${crypto.randomUUID()}`,
|
|
21
|
+
messageTypes: trigger.messageTypes ?? [],
|
|
22
|
+
leaseSeconds: agent.execution.leaseSeconds
|
|
23
|
+
});
|
|
24
|
+
if (claimed.payload) {
|
|
25
|
+
return {
|
|
26
|
+
kind: "ready",
|
|
27
|
+
selectedTrigger: trigger,
|
|
28
|
+
invocation: {
|
|
29
|
+
kind: "message",
|
|
30
|
+
source: "message",
|
|
31
|
+
trigger,
|
|
32
|
+
message: claimed.payload
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
kind: "no_message_available",
|
|
39
|
+
selectedTrigger: trigger,
|
|
40
|
+
reason: `No matching messages for ${agent.slug}.`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function resolveFollowTrigger(agent, sdk, trigger) {
|
|
44
|
+
const models = trigger.models ?? [];
|
|
45
|
+
const since = (await sdk.getCursor({
|
|
46
|
+
agentSlug: agent.slug,
|
|
47
|
+
cursorKey: `follow:${models.join(",") || "all"}`
|
|
48
|
+
})).payload ?? trigger.sinceField ?? (/* @__PURE__ */ new Date(0)).toISOString();
|
|
49
|
+
for (const model of models) {
|
|
50
|
+
const followed = await sdk.follow({
|
|
51
|
+
model,
|
|
52
|
+
since: String(since)
|
|
53
|
+
});
|
|
54
|
+
if (followed.payload.items.length) {
|
|
55
|
+
return {
|
|
56
|
+
kind: "ready",
|
|
57
|
+
selectedTrigger: trigger,
|
|
58
|
+
invocation: {
|
|
59
|
+
kind: "follow",
|
|
60
|
+
source: "follow",
|
|
61
|
+
trigger,
|
|
62
|
+
followModels: models,
|
|
63
|
+
cursorValue: String(since)
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
kind: "no_follow_activity",
|
|
70
|
+
selectedTrigger: trigger,
|
|
71
|
+
reason: `No followed activity for ${agent.slug}.`
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function resolveScheduleLikeTrigger(agent, trigger, mode, lastRunAt) {
|
|
75
|
+
if (mode !== "manual" && evaluateCooldown(agent, lastRunAt)) {
|
|
76
|
+
return {
|
|
77
|
+
kind: "blocked_by_cooldown",
|
|
78
|
+
selectedTrigger: trigger,
|
|
79
|
+
reason: `Agent ${agent.slug} is cooling down.`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
kind: "ready",
|
|
84
|
+
selectedTrigger: trigger,
|
|
85
|
+
invocation: {
|
|
86
|
+
kind: mode === "manual" ? "manual" : trigger.type === "startup" || trigger.runOnStart ? "startup" : "schedule",
|
|
87
|
+
source: mode === "manual" ? "manual" : trigger.type === "startup" || trigger.runOnStart ? "startup" : "schedule",
|
|
88
|
+
trigger
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function resolveTriggerDecision(input) {
|
|
93
|
+
if (input.isRunning) {
|
|
94
|
+
return {
|
|
95
|
+
kind: "blocked_by_concurrency",
|
|
96
|
+
reason: `Agent ${input.agent.slug} is already running.`
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const mode = input.mode ?? "auto";
|
|
100
|
+
const messageBatchSize = input.agent.triggerPolicy?.messageBatchSize ?? 1;
|
|
101
|
+
const triggerGroups = {
|
|
102
|
+
message: input.agent.triggers.filter((trigger) => trigger.type === "message"),
|
|
103
|
+
follow: input.agent.triggers.filter((trigger) => trigger.type === "follow"),
|
|
104
|
+
schedule: input.agent.triggers.filter((trigger) => trigger.type === "startup" || trigger.type === "schedule")
|
|
105
|
+
};
|
|
106
|
+
for (const trigger of triggerGroups.message) {
|
|
107
|
+
const decision = await resolveMessageTrigger(input.agent, input.sdk, trigger, messageBatchSize);
|
|
108
|
+
if (!decision) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (decision.kind === "ready") {
|
|
112
|
+
return decision;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const trigger of triggerGroups.follow) {
|
|
116
|
+
const decision = await resolveFollowTrigger(input.agent, input.sdk, trigger);
|
|
117
|
+
if (decision.kind === "ready") {
|
|
118
|
+
return decision;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const trigger of triggerGroups.schedule) {
|
|
122
|
+
const decision = resolveScheduleLikeTrigger(input.agent, trigger, mode, input.lastRunAt);
|
|
123
|
+
if (decision.kind === "ready") {
|
|
124
|
+
return decision;
|
|
125
|
+
}
|
|
126
|
+
if (decision.kind === "blocked_by_cooldown") {
|
|
127
|
+
return decision;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (mode === "manual") {
|
|
131
|
+
const invocation = buildManualInvocation(input.agent);
|
|
132
|
+
return invocation ? { kind: "ready", invocation, selectedTrigger: invocation.trigger } : { kind: "no_trigger_available", reason: `No manual-capable trigger found for ${input.agent.slug}.` };
|
|
133
|
+
}
|
|
134
|
+
if (triggerGroups.message.length) {
|
|
135
|
+
return {
|
|
136
|
+
kind: "no_message_available",
|
|
137
|
+
reason: `No matching messages for ${input.agent.slug}.`
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (triggerGroups.follow.length) {
|
|
141
|
+
return {
|
|
142
|
+
kind: "no_follow_activity",
|
|
143
|
+
reason: `No followed activity for ${input.agent.slug}.`
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
kind: "no_trigger_available",
|
|
148
|
+
reason: `No runnable triggers defined for ${input.agent.slug}.`
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
resolveTriggerDecision
|
|
153
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "@treeseed/sdk/models";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function defineAgentHandlerRegistry(registry) {
|
|
2
|
+
return registry;
|
|
3
|
+
}
|
|
4
|
+
function resolveAgentHandlerFromRegistry(registry, kind) {
|
|
5
|
+
const handler = registry[kind];
|
|
6
|
+
if (!handler) {
|
|
7
|
+
throw new Error(`No runtime handler is registered for agent handler "${kind}".`);
|
|
8
|
+
}
|
|
9
|
+
return handler;
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
defineAgentHandlerRegistry,
|
|
13
|
+
resolveAgentHandlerFromRegistry
|
|
14
|
+
};
|