@treeseed/sdk 0.12.41 → 0.12.43

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.
@@ -1,6 +1,176 @@
1
1
  import type { CapacityGrant, CapacityLedgerEntry, CapacityPlan as LegacyCapacityPlan, CapacityProvider, CapacityReservation, ExecutionProvider, TaskUsageActual } from './sdk-types.js';
2
+ import type { TreeseedContentRef } from './content-operations.js';
2
3
  import type { AgentRuntimeSpec, AgentWorkPackage, ExecutionCapabilityDemand, ExecutionCapabilitySupply, ExecutionProviderDescriptor, ExecutionResourceNeed } from './types/agents.js';
3
4
  export type AgentExecutionMode = 'planning' | 'acting';
5
+ export type AgentPlanningActivityType = 'planning' | 'estimating' | 'reviewing' | 'reporting';
6
+ export type ContentRef = TreeseedContentRef;
7
+ export type AgentEstimateConfidence = 'low' | 'medium' | 'high';
8
+ export type AgentEstimateRiskLevel = 'low' | 'medium' | 'high';
9
+ export type DecisionDependencyType = 'artifact' | 'capability' | 'decision' | 'external-resource' | 'human-input';
10
+ export interface AgentOutputRequirement {
11
+ id?: string;
12
+ outputType: string;
13
+ description?: string;
14
+ contentModel?: string;
15
+ required?: boolean;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+ export interface DecisionDependencySpec {
19
+ id: string;
20
+ type: DecisionDependencyType;
21
+ requiredBefore: 'start' | 'complete' | 'review' | 'release';
22
+ optional?: boolean;
23
+ deliverableType?: string;
24
+ capability?: string;
25
+ agentClass?: string;
26
+ contentRefs?: string[];
27
+ humanInputPolicy?: {
28
+ requiredFrom: 'team-human' | 'any-human' | 'any-human-or-agent';
29
+ teamId?: string | null;
30
+ };
31
+ summary?: string;
32
+ }
33
+ export interface StructuredAgentEstimate {
34
+ id: string;
35
+ teamId: string;
36
+ projectId: string;
37
+ decisionId?: string | null;
38
+ proposalId?: string | null;
39
+ workUnitId?: string | null;
40
+ agentClass: string;
41
+ agentId?: string | null;
42
+ minCredits: number;
43
+ expectedCredits: number;
44
+ maxCredits: number;
45
+ confidence: AgentEstimateConfidence;
46
+ riskLevel: AgentEstimateRiskLevel;
47
+ assumptions: string[];
48
+ blockers: string[];
49
+ dependencies: DecisionDependencySpec[];
50
+ expectedOutputs: AgentOutputRequirement[];
51
+ acceptanceCriteria: string[];
52
+ completionEvidence: string[];
53
+ createdAt?: string | null;
54
+ metadata?: Record<string, unknown>;
55
+ }
56
+ export interface DeliverableContract {
57
+ id: string;
58
+ teamId: string;
59
+ projectId: string;
60
+ decisionId: string;
61
+ deliverableType: string;
62
+ producerAgentClasses: string[];
63
+ reviewerAgentClasses?: string[];
64
+ requiredSections?: string[];
65
+ acceptanceCriteria: string[];
66
+ status: 'required' | 'draft' | 'submitted' | 'approved' | 'rejected';
67
+ metadata?: Record<string, unknown>;
68
+ }
69
+ export interface DeliverableManifest {
70
+ id: string;
71
+ deliverableContractId: string;
72
+ projectId: string;
73
+ decisionId: string;
74
+ producedRefs: ContentRef[];
75
+ coverage?: Record<string, ContentRef[]>;
76
+ summary: string;
77
+ readyForReview: boolean;
78
+ submittedByAgentId?: string | null;
79
+ submittedAt?: string | null;
80
+ metadata?: Record<string, unknown>;
81
+ }
82
+ export interface DecisionAssignmentGraphNode {
83
+ id: string;
84
+ decisionId: string;
85
+ projectId: string;
86
+ targetAgentClass: string;
87
+ activityType: AgentPlanningActivityType | 'acting';
88
+ handler?: string | null;
89
+ requiredCapabilities: string[];
90
+ requiredDeliverableContractIds: string[];
91
+ inputRefs: ContentRef[];
92
+ outputRequirements: AgentOutputRequirement[];
93
+ capacity: {
94
+ expectedCredits: number;
95
+ maxCredits: number;
96
+ };
97
+ status: 'pending' | 'ready' | 'leased' | 'running' | 'blocked' | 'completed' | 'failed' | 'cancelled';
98
+ metadata?: Record<string, unknown>;
99
+ }
100
+ export interface DecisionAssignmentGraphEdge {
101
+ fromNodeId: string;
102
+ toNodeId: string;
103
+ edgeType: 'blocks-start' | 'blocks-completion' | 'blocks-release';
104
+ reason?: string;
105
+ }
106
+ export interface DecisionAssignmentGraph {
107
+ id: string;
108
+ teamId: string;
109
+ projectId: string;
110
+ decisionId: string;
111
+ version: number;
112
+ status: 'draft' | 'compiled' | 'ready' | 'executing' | 'completed' | 'blocked';
113
+ estimateIds: string[];
114
+ deliverableContracts: DeliverableContract[];
115
+ nodes: DecisionAssignmentGraphNode[];
116
+ edges: DecisionAssignmentGraphEdge[];
117
+ compiledAt?: string | null;
118
+ compiledBy: 'api-control-plane';
119
+ metadata?: Record<string, unknown>;
120
+ }
121
+ export interface AgentCapacityContractDiagnostic {
122
+ severity: 'info' | 'warning' | 'error';
123
+ code: string;
124
+ message: string;
125
+ path?: string;
126
+ }
127
+ export interface AgentCapacityContractValidationResult {
128
+ ok: boolean;
129
+ diagnostics: AgentCapacityContractDiagnostic[];
130
+ }
131
+ export interface DecisionAssignmentGraphCompileResult {
132
+ graph: DecisionAssignmentGraph;
133
+ diagnostics: AgentCapacityContractDiagnostic[];
134
+ }
135
+ export interface WorkdayModeSplitPolicy {
136
+ targetPercent: number;
137
+ minPercent?: number;
138
+ maxPercent?: number;
139
+ hardCapPercent?: number;
140
+ }
141
+ export interface WorkdayModeSplits {
142
+ planning?: WorkdayModeSplitPolicy;
143
+ acting?: WorkdayModeSplitPolicy;
144
+ }
145
+ export interface PlanningReservationMetadata {
146
+ source: 'provider_assignment_synthesis' | string;
147
+ planningSource: 'live_workday' | 'planning_input_request' | 'decision_estimation' | 'decision_review' | 'fallback_planning' | string;
148
+ activityType: AgentPlanningActivityType;
149
+ projectAgentClassId?: string | null;
150
+ agentSlug?: string | null;
151
+ workdayId?: string | null;
152
+ allocationSetId?: string | null;
153
+ modeSplitVersion?: string | null;
154
+ }
155
+ export interface ModeCapacityReservationAvailabilityInput {
156
+ teamId: string;
157
+ capacityProviderId: string;
158
+ projectId: string;
159
+ workDayId?: string | null;
160
+ allocationSetId?: string | null;
161
+ projectAgentClassId?: string | null;
162
+ agentSlug?: string | null;
163
+ mode: AgentExecutionMode;
164
+ activityType?: AgentPlanningActivityType | 'acting' | string | null;
165
+ reservedCredits: number;
166
+ }
167
+ export interface ModeCapacityReservationAvailabilityResult {
168
+ ok: boolean;
169
+ hold?: boolean;
170
+ reason?: string | null;
171
+ gates: Record<string, unknown>;
172
+ }
173
+ export declare const DEFAULT_WORKDAY_MODE_SPLITS: Required<WorkdayModeSplits>;
4
174
  export type AllocationSetStatus = 'draft' | 'active' | 'superseded' | 'archived';
5
175
  export type ProjectAgentClassStatus = 'active' | 'paused' | 'archived';
6
176
  export type ProviderAvailabilitySessionStatus = 'open' | 'draining' | 'closed' | 'expired';
@@ -728,6 +898,20 @@ export interface CapacitySettlementSummary {
728
898
  metadata?: Record<string, unknown>;
729
899
  createdAt?: string;
730
900
  }
901
+ export declare function validateDecisionDependencySpec(dependency: DecisionDependencySpec, path?: string): AgentCapacityContractValidationResult;
902
+ export declare function validateStructuredAgentEstimate(estimate: StructuredAgentEstimate): AgentCapacityContractValidationResult;
903
+ export declare function validateDeliverableContract(contract: DeliverableContract): AgentCapacityContractValidationResult;
904
+ export declare function validateDeliverableManifest(manifest: DeliverableManifest): AgentCapacityContractValidationResult;
905
+ export declare function validateDecisionAssignmentGraph(graph: DecisionAssignmentGraph): AgentCapacityContractValidationResult;
906
+ export declare function compileDecisionAssignmentGraphFromEstimates(input: {
907
+ id?: string;
908
+ teamId: string;
909
+ projectId: string;
910
+ decisionId: string;
911
+ version?: number;
912
+ estimates: StructuredAgentEstimate[];
913
+ compiledAt?: string | null;
914
+ }): DecisionAssignmentGraphCompileResult;
731
915
  export declare function computeDecisionScopeHash(scope: unknown): string;
732
916
  export declare function isDecisionReadyForActing(status?: Pick<DecisionPlanningStatus, 'executionReadiness' | 'planningInputsStatus'> | null): boolean;
733
917
  export declare function isPlanningInputOpen(request: Pick<PlanningInputRequest, 'status'>): boolean;
@@ -1,3 +1,7 @@
1
+ const DEFAULT_WORKDAY_MODE_SPLITS = {
2
+ planning: { targetPercent: 25, minPercent: 10, maxPercent: 40 },
3
+ acting: { targetPercent: 75, minPercent: 60, maxPercent: 90 }
4
+ };
1
5
  const AGENT_ASSIGNMENT_WORKSPACE_ACCESS_MODES = [
2
6
  "context_only",
3
7
  "workspace_write",
@@ -44,6 +48,208 @@ function firstArray(...values) {
44
48
  }
45
49
  return [];
46
50
  }
51
+ function diagnostic(diagnostics, code, message, path, severity = "error") {
52
+ diagnostics.push({ severity, code, message, path });
53
+ }
54
+ function validateNonEmptyString(diagnostics, value, field, path = field) {
55
+ if (typeof value !== "string" || !value.trim()) diagnostic(diagnostics, "required_string_missing", `${field} is required.`, path);
56
+ }
57
+ function validateNonNegativeNumber(diagnostics, value, field, path = field) {
58
+ if (!Number.isFinite(Number(value)) || Number(value) < 0) {
59
+ diagnostic(diagnostics, "non_negative_number_required", `${field} must be a non-negative number.`, path);
60
+ }
61
+ }
62
+ function validationResult(diagnostics) {
63
+ return { ok: diagnostics.every((entry) => entry.severity !== "error"), diagnostics };
64
+ }
65
+ function dependencyToContractId(projectId, decisionId, dependency) {
66
+ const deliverable = dependency.deliverableType || dependency.capability || dependency.id;
67
+ return `${projectId}:${decisionId}:deliverable:${deliverable}`.replace(/[^a-zA-Z0-9:_-]+/gu, "-");
68
+ }
69
+ function edgeTypeForDependency(dependency) {
70
+ if (dependency.requiredBefore === "complete" || dependency.requiredBefore === "review") return "blocks-completion";
71
+ if (dependency.requiredBefore === "release") return "blocks-release";
72
+ return "blocks-start";
73
+ }
74
+ function validateDecisionDependencySpec(dependency, path = "dependency") {
75
+ const diagnostics = [];
76
+ validateNonEmptyString(diagnostics, dependency.id, "id", `${path}.id`);
77
+ if (!["artifact", "capability", "decision", "external-resource", "human-input"].includes(dependency.type)) {
78
+ diagnostic(diagnostics, "invalid_dependency_type", `Dependency ${dependency.id || "<unknown>"} has an invalid type.`, `${path}.type`);
79
+ }
80
+ if (!["start", "complete", "review", "release"].includes(dependency.requiredBefore)) {
81
+ diagnostic(diagnostics, "invalid_dependency_required_before", `Dependency ${dependency.id || "<unknown>"} has an invalid requiredBefore value.`, `${path}.requiredBefore`);
82
+ }
83
+ if (dependency.type === "artifact" && !dependency.deliverableType) diagnostic(diagnostics, "artifact_dependency_missing_deliverable_type", "Artifact dependencies must declare deliverableType.", `${path}.deliverableType`);
84
+ if (dependency.type === "capability" && !dependency.capability && !dependency.agentClass) diagnostic(diagnostics, "capability_dependency_missing_capability", "Capability dependencies must declare capability or agentClass.", `${path}.capability`);
85
+ if (dependency.type === "human-input") {
86
+ const policy = dependency.humanInputPolicy;
87
+ if (!policy || !["team-human", "any-human", "any-human-or-agent"].includes(policy.requiredFrom)) {
88
+ diagnostic(diagnostics, "human_input_policy_missing", "Human-input dependencies must declare a valid humanInputPolicy.requiredFrom.", `${path}.humanInputPolicy.requiredFrom`);
89
+ }
90
+ }
91
+ return validationResult(diagnostics);
92
+ }
93
+ function validateStructuredAgentEstimate(estimate) {
94
+ const diagnostics = [];
95
+ validateNonEmptyString(diagnostics, estimate.id, "id");
96
+ validateNonEmptyString(diagnostics, estimate.teamId, "teamId");
97
+ validateNonEmptyString(diagnostics, estimate.projectId, "projectId");
98
+ validateNonEmptyString(diagnostics, estimate.agentClass, "agentClass");
99
+ if (!estimate.decisionId && !estimate.proposalId) diagnostic(diagnostics, "estimate_missing_subject", "Structured estimates must reference a decisionId or proposalId.", "decisionId");
100
+ validateNonNegativeNumber(diagnostics, estimate.minCredits, "minCredits");
101
+ validateNonNegativeNumber(diagnostics, estimate.expectedCredits, "expectedCredits");
102
+ validateNonNegativeNumber(diagnostics, estimate.maxCredits, "maxCredits");
103
+ if (Number(estimate.minCredits) > Number(estimate.expectedCredits) || Number(estimate.expectedCredits) > Number(estimate.maxCredits)) {
104
+ diagnostic(diagnostics, "estimate_credit_bounds_invalid", "Estimate credit bounds must satisfy min <= expected <= max.", "expectedCredits");
105
+ }
106
+ if (!["low", "medium", "high"].includes(estimate.confidence)) diagnostic(diagnostics, "estimate_confidence_invalid", "Estimate confidence must be low, medium, or high.", "confidence");
107
+ if (!["low", "medium", "high"].includes(estimate.riskLevel)) diagnostic(diagnostics, "estimate_risk_level_invalid", "Estimate riskLevel must be low, medium, or high.", "riskLevel");
108
+ for (const [index, dependency] of (estimate.dependencies ?? []).entries()) {
109
+ diagnostics.push(...validateDecisionDependencySpec(dependency, `dependencies.${index}`).diagnostics);
110
+ }
111
+ return validationResult(diagnostics);
112
+ }
113
+ function validateDeliverableContract(contract) {
114
+ const diagnostics = [];
115
+ validateNonEmptyString(diagnostics, contract.id, "id");
116
+ validateNonEmptyString(diagnostics, contract.teamId, "teamId");
117
+ validateNonEmptyString(diagnostics, contract.projectId, "projectId");
118
+ validateNonEmptyString(diagnostics, contract.decisionId, "decisionId");
119
+ validateNonEmptyString(diagnostics, contract.deliverableType, "deliverableType");
120
+ if (!Array.isArray(contract.producerAgentClasses) || contract.producerAgentClasses.length === 0) diagnostic(diagnostics, "deliverable_contract_missing_producer", "Deliverable contracts must declare at least one producerAgentClass.", "producerAgentClasses");
121
+ if (!["required", "draft", "submitted", "approved", "rejected"].includes(contract.status)) diagnostic(diagnostics, "deliverable_contract_status_invalid", "Deliverable contract has an invalid status.", "status");
122
+ return validationResult(diagnostics);
123
+ }
124
+ function validateDeliverableManifest(manifest) {
125
+ const diagnostics = [];
126
+ validateNonEmptyString(diagnostics, manifest.id, "id");
127
+ validateNonEmptyString(diagnostics, manifest.deliverableContractId, "deliverableContractId");
128
+ validateNonEmptyString(diagnostics, manifest.projectId, "projectId");
129
+ validateNonEmptyString(diagnostics, manifest.decisionId, "decisionId");
130
+ if (!Array.isArray(manifest.producedRefs) || manifest.producedRefs.length === 0) diagnostic(diagnostics, "deliverable_manifest_missing_refs", "Deliverable manifests must map the contract to at least one produced content ref.", "producedRefs");
131
+ validateNonEmptyString(diagnostics, manifest.summary, "summary");
132
+ return validationResult(diagnostics);
133
+ }
134
+ function validateDecisionAssignmentGraph(graph) {
135
+ const diagnostics = [];
136
+ validateNonEmptyString(diagnostics, graph.id, "id");
137
+ validateNonEmptyString(diagnostics, graph.teamId, "teamId");
138
+ validateNonEmptyString(diagnostics, graph.projectId, "projectId");
139
+ validateNonEmptyString(diagnostics, graph.decisionId, "decisionId");
140
+ if (!Number.isInteger(graph.version) || graph.version < 1) diagnostic(diagnostics, "graph_version_invalid", "Decision assignment graph version must be a positive integer.", "version");
141
+ if (graph.compiledBy !== "api-control-plane") diagnostic(diagnostics, "graph_compiler_invalid", "Decision assignment graphs must be compiled by api-control-plane.", "compiledBy");
142
+ const nodeIds = new Set(graph.nodes.map((node) => node.id));
143
+ for (const [index, node] of graph.nodes.entries()) {
144
+ validateNonEmptyString(diagnostics, node.id, "node.id", `nodes.${index}.id`);
145
+ validateNonEmptyString(diagnostics, node.targetAgentClass, "node.targetAgentClass", `nodes.${index}.targetAgentClass`);
146
+ validateNonNegativeNumber(diagnostics, node.capacity.expectedCredits, "node.capacity.expectedCredits", `nodes.${index}.capacity.expectedCredits`);
147
+ validateNonNegativeNumber(diagnostics, node.capacity.maxCredits, "node.capacity.maxCredits", `nodes.${index}.capacity.maxCredits`);
148
+ }
149
+ for (const [index, edge] of graph.edges.entries()) {
150
+ if (!nodeIds.has(edge.fromNodeId)) diagnostic(diagnostics, "graph_edge_from_missing", `Edge ${index} references missing fromNodeId.`, `edges.${index}.fromNodeId`);
151
+ if (!nodeIds.has(edge.toNodeId)) diagnostic(diagnostics, "graph_edge_to_missing", `Edge ${index} references missing toNodeId.`, `edges.${index}.toNodeId`);
152
+ }
153
+ for (const [index, contract] of graph.deliverableContracts.entries()) {
154
+ diagnostics.push(...validateDeliverableContract(contract).diagnostics.map((entry) => ({ ...entry, path: `deliverableContracts.${index}${entry.path ? `.${entry.path}` : ""}` })));
155
+ }
156
+ return validationResult(diagnostics);
157
+ }
158
+ function compileDecisionAssignmentGraphFromEstimates(input) {
159
+ const diagnostics = [];
160
+ const estimates = [...input.estimates ?? []].sort((left, right) => left.agentClass.localeCompare(right.agentClass) || String(left.agentId ?? "").localeCompare(String(right.agentId ?? "")) || left.id.localeCompare(right.id));
161
+ for (const [index, estimate] of estimates.entries()) {
162
+ diagnostics.push(...validateStructuredAgentEstimate(estimate).diagnostics.map((entry) => ({ ...entry, path: `estimates.${index}${entry.path ? `.${entry.path}` : ""}` })));
163
+ }
164
+ const contractMap = /* @__PURE__ */ new Map();
165
+ const deliverableProducerNodes = /* @__PURE__ */ new Map();
166
+ const nodes = [];
167
+ const edges = [];
168
+ for (const estimate of estimates) {
169
+ for (const dependency of estimate.dependencies.filter((entry) => entry.type === "artifact" && entry.deliverableType)) {
170
+ const contractId = dependencyToContractId(input.projectId, input.decisionId, dependency);
171
+ if (!contractMap.has(contractId)) {
172
+ const producerClass = dependency.agentClass || dependency.capability || dependency.deliverableType || "producer";
173
+ contractMap.set(contractId, {
174
+ id: contractId,
175
+ teamId: input.teamId,
176
+ projectId: input.projectId,
177
+ decisionId: input.decisionId,
178
+ deliverableType: dependency.deliverableType ?? dependency.id,
179
+ producerAgentClasses: [producerClass],
180
+ acceptanceCriteria: dependency.summary ? [dependency.summary] : [],
181
+ status: "required",
182
+ metadata: { sourceDependencyId: dependency.id }
183
+ });
184
+ const producerNodeId = `${contractId}:produce`;
185
+ deliverableProducerNodes.set(contractId, producerNodeId);
186
+ nodes.push({
187
+ id: producerNodeId,
188
+ decisionId: input.decisionId,
189
+ projectId: input.projectId,
190
+ targetAgentClass: producerClass,
191
+ activityType: "acting",
192
+ handler: null,
193
+ requiredCapabilities: uniqueStrings([dependency.capability ?? ""].filter(Boolean)),
194
+ requiredDeliverableContractIds: [],
195
+ inputRefs: [],
196
+ outputRequirements: [{ id: `${contractId}:output`, outputType: dependency.deliverableType ?? dependency.id, description: dependency.summary, required: true }],
197
+ capacity: { expectedCredits: 1, maxCredits: 1 },
198
+ status: "pending",
199
+ metadata: { deliverableContractId: contractId, generatedFromDependency: dependency.id }
200
+ });
201
+ }
202
+ }
203
+ }
204
+ for (const estimate of estimates) {
205
+ const nodeId = estimate.workUnitId || `estimate:${estimate.id}:work`;
206
+ const artifactDependencies = estimate.dependencies.filter((dependency) => dependency.type === "artifact" && dependency.deliverableType);
207
+ const requiredDeliverableContractIds = artifactDependencies.map((dependency) => dependencyToContractId(input.projectId, input.decisionId, dependency));
208
+ const inputRefs = estimate.dependencies.flatMap((dependency) => (dependency.contentRefs ?? []).map((ref) => ({ model: "note", collection: "notes", slug: ref, id: ref })));
209
+ nodes.push({
210
+ id: nodeId,
211
+ decisionId: input.decisionId,
212
+ projectId: input.projectId,
213
+ targetAgentClass: estimate.agentClass,
214
+ activityType: "acting",
215
+ handler: null,
216
+ requiredCapabilities: uniqueStrings(estimate.dependencies.map((dependency) => dependency.capability ?? dependency.agentClass ?? "").filter(Boolean)),
217
+ requiredDeliverableContractIds,
218
+ inputRefs,
219
+ outputRequirements: estimate.expectedOutputs,
220
+ capacity: { expectedCredits: estimate.expectedCredits, maxCredits: estimate.maxCredits },
221
+ status: "pending",
222
+ metadata: {
223
+ estimateId: estimate.id,
224
+ confidence: estimate.confidence,
225
+ riskLevel: estimate.riskLevel,
226
+ humanInputDependencies: estimate.dependencies.filter((dependency) => dependency.type === "human-input")
227
+ }
228
+ });
229
+ for (const dependency of artifactDependencies) {
230
+ const contractId = dependencyToContractId(input.projectId, input.decisionId, dependency);
231
+ const producerNodeId = deliverableProducerNodes.get(contractId);
232
+ if (producerNodeId) edges.push({ fromNodeId: producerNodeId, toNodeId: nodeId, edgeType: edgeTypeForDependency(dependency), reason: dependency.summary ?? dependency.deliverableType });
233
+ }
234
+ }
235
+ const graph = {
236
+ id: input.id ?? `${input.projectId}:${input.decisionId}:graph:v${input.version ?? 1}`,
237
+ teamId: input.teamId,
238
+ projectId: input.projectId,
239
+ decisionId: input.decisionId,
240
+ version: input.version ?? 1,
241
+ status: diagnostics.some((entry) => entry.severity === "error") ? "blocked" : "compiled",
242
+ estimateIds: estimates.map((estimate) => estimate.id),
243
+ deliverableContracts: [...contractMap.values()].sort((left, right) => left.id.localeCompare(right.id)),
244
+ nodes: nodes.sort((left, right) => left.id.localeCompare(right.id)),
245
+ edges: edges.sort((left, right) => left.fromNodeId.localeCompare(right.fromNodeId) || left.toNodeId.localeCompare(right.toNodeId) || left.edgeType.localeCompare(right.edgeType)),
246
+ compiledAt: input.compiledAt ?? null,
247
+ compiledBy: "api-control-plane",
248
+ metadata: { compiler: "compileDecisionAssignmentGraphFromEstimates" }
249
+ };
250
+ diagnostics.push(...validateDecisionAssignmentGraph(graph).diagnostics);
251
+ return { graph, diagnostics };
252
+ }
47
253
  function booleanOrNull(value) {
48
254
  return typeof value === "boolean" ? value : null;
49
255
  }
@@ -1122,6 +1328,15 @@ function validateAgentKernelModeExecutionInput(input) {
1122
1328
  { retryable: true, metadata: { reservationId: capacity.reservationId ?? null, reservedCredits: capacity.reservedCredits ?? null } }
1123
1329
  );
1124
1330
  }
1331
+ const activityType = String(record(decision.metadata).activityType ?? record(capacity.metadata).activityType ?? "");
1332
+ const deterministicSystemReport = mode === "planning" && activityType === "reporting" && record(capacity.metadata).deterministicSystemReport === true;
1333
+ if (mode === "planning" && !deterministicSystemReport && (!capacity.reservationId || Number(capacity.reservedCredits ?? 0) <= 0)) {
1334
+ return createAgentKernelModeFallback(
1335
+ "assignment_capacity_not_reserved",
1336
+ `Assignment ${assignment.id} is missing reserved capacity for planning execution.`,
1337
+ { retryable: true, metadata: { reservationId: capacity.reservationId ?? null, reservedCredits: capacity.reservedCredits ?? null, activityType: activityType || null } }
1338
+ );
1339
+ }
1125
1340
  if (mode === "acting" && !hasAcceptedCapacityPlanProvenance({ assignment, decisionInput: decision, capacityEnvelope: capacity })) {
1126
1341
  return createAgentKernelModeFallback(
1127
1342
  "assignment_capacity_plan_not_accepted",
@@ -1490,9 +1705,9 @@ function summarizeCapacityRuntimeDiagnostics(input) {
1490
1705
  }
1491
1706
  }
1492
1707
  }
1493
- const uniqueDiagnostics = Array.from(new Map(diagnostics.map((diagnostic) => [
1494
- `${diagnostic.assignmentId ?? "global"}:${diagnostic.code}`,
1495
- diagnostic
1708
+ const uniqueDiagnostics = Array.from(new Map(diagnostics.map((diagnostic2) => [
1709
+ `${diagnostic2.assignmentId ?? "global"}:${diagnostic2.code}`,
1710
+ diagnostic2
1496
1711
  ])).values());
1497
1712
  return {
1498
1713
  projectId: input.projectId,
@@ -1555,9 +1770,11 @@ function validateCapacitySettlementInvariant(input) {
1555
1770
  }
1556
1771
  export {
1557
1772
  AGENT_ASSIGNMENT_WORKSPACE_ACCESS_MODES,
1773
+ DEFAULT_WORKDAY_MODE_SPLITS,
1558
1774
  buildAgentCapacityPlanDraft,
1559
1775
  buildExecutionProviderAssignmentExplanation,
1560
1776
  buildProviderAssignmentExplanation,
1777
+ compileDecisionAssignmentGraphFromEstimates,
1561
1778
  compileExecutionCapabilityDemand,
1562
1779
  compileExecutionCapabilitySupply,
1563
1780
  computeDecisionScopeHash,
@@ -1595,6 +1812,11 @@ export {
1595
1812
  validateAgentKernelModeExecutionInput,
1596
1813
  validateAgentKernelOutputs,
1597
1814
  validateCapacitySettlementInvariant,
1815
+ validateDecisionAssignmentGraph,
1816
+ validateDecisionDependencySpec,
1817
+ validateDeliverableContract,
1818
+ validateDeliverableManifest,
1598
1819
  validateProviderAssignmentCapabilityHandles,
1820
+ validateStructuredAgentEstimate,
1599
1821
  validateTreeDxProxyHandle
1600
1822
  };
@@ -82,6 +82,7 @@ export type TreeseedGuaranteeManifest = {
82
82
  optional?: string[];
83
83
  };
84
84
  notes?: string[];
85
+ dependsOnGuarantees?: string[];
85
86
  };
86
87
  export type TreeseedLoadedGuarantee = {
87
88
  sourcePath: string;
@@ -362,6 +363,7 @@ export declare function exportTreeseedGuaranteesJson(input: {
362
363
  optional?: string[];
363
364
  };
364
365
  notes?: string[];
366
+ dependsOnGuarantees?: string[];
365
367
  sourcePath: string;
366
368
  }[];
367
369
  };
@@ -155,6 +155,11 @@ function parseGuaranteeManifest(value, diagnostics, sourcePath) {
155
155
  if (subtype && !TAXONOMY_PATTERN.test(subtype)) diagnostics.push(diagnostic("error", "guarantee.invalid_subtype", `Guarantee subtype must be lowercase kebab-case: ${subtype}.`, "subtype", sourcePath));
156
156
  if (status && !KNOWN_STATUSES.has(status)) diagnostics.push(diagnostic("error", "guarantee.invalid_status", `Unsupported guarantee status "${status}".`, "status", sourcePath));
157
157
  if (surface && !KNOWN_SURFACES.has(surface)) diagnostics.push(diagnostic("error", "guarantee.invalid_surface", `Unsupported guarantee surface "${surface}".`, "surface", sourcePath));
158
+ const dependsOnGuarantees = Array.isArray(value.dependsOnGuarantees) ? value.dependsOnGuarantees.map((entry) => {
159
+ if (typeof entry === "string") return entry;
160
+ if (!isRecord(entry)) return "";
161
+ return typeof entry.ref === "string" && typeof entry.ownerPackage === "string" ? `${entry.ownerPackage}:${entry.ref}` : stringValue(entry.ref);
162
+ }).filter(Boolean) : [];
158
163
  const dependencies = isRecord(value.dependencies) ? value.dependencies : {};
159
164
  const actors = isRecord(value.actors) ? value.actors : {};
160
165
  const devices = isRecord(value.devices) ? value.devices : {};
@@ -212,7 +217,8 @@ function parseGuaranteeManifest(value, diagnostics, sourcePath) {
212
217
  required: stringArray(evidence.required),
213
218
  optional: stringArray(evidence.optional)
214
219
  },
215
- notes: stringArray(value.notes)
220
+ notes: stringArray(value.notes),
221
+ dependsOnGuarantees
216
222
  };
217
223
  if (manifest.status === "active") {
218
224
  const hasContract = Boolean(manifest.scene?.required || manifest.api?.required || manifest.content?.required || manifest.audit?.required);
@@ -400,6 +406,13 @@ function validateTreeseedGuaranteeRegistry(input) {
400
406
  if (entry.manifest.journeyIndex && dep >= entry.manifest.journeyIndex) diagnostics.push(diagnostic("error", "guarantee.forward_journey_dependency", `Journey dependency ${dep} must be lower than ${entry.manifest.journeyIndex}.`, "dependencies.journeys", entry.sourcePath));
401
407
  }
402
408
  }
409
+ for (const entry of valid) {
410
+ for (const dep of entry.manifest.dependsOnGuarantees ?? []) {
411
+ const [ownerPackage, ref] = dep.includes(":") ? dep.split(/:(.+)/u).filter(Boolean) : ["", dep];
412
+ const match = valid.find((candidate) => (!ownerPackage || candidate.manifest.ownerPackage === ownerPackage) && candidate.manifest.status === "active" && (candidate.manifest.id === ref || allVerifierRefs(candidate.manifest).includes(ref)));
413
+ if (!match) diagnostics.push(diagnostic("error", "guarantee.missing_depends_on_guarantee", `Missing active guarantee dependency "${dep}".`, "dependsOnGuarantees", entry.sourcePath));
414
+ }
415
+ }
403
416
  const visiting = /* @__PURE__ */ new Set();
404
417
  const visited = /* @__PURE__ */ new Set();
405
418
  const visit = (id, chain) => {
@@ -359,7 +359,7 @@ function buildProfileFromDeployConfig(input) {
359
359
  sourceCommit: sourcePolicy.sourceCommit,
360
360
  sourceRootDirectory: sourcePolicy.sourceRootDirectory,
361
361
  dockerfilePath: sourcePolicy.sourceMode === "git" ? service.railway?.dockerfilePath ?? null : null,
362
- buildCommand: sourcePolicy.imageRef ? null : service.railway?.buildCommand ?? null,
362
+ buildCommand: sourcePolicy.imageRef || sourcePolicy.sourceMode === "git" && service.railway?.dockerfilePath ? null : service.railway?.buildCommand ?? null,
363
363
  startCommand: sourcePolicy.imageRef ? null : service.railway?.startCommand ?? null,
364
364
  healthcheckPath: service.railway?.healthcheckPath ?? null,
365
365
  runtimeMode: service.railway?.runtimeMode ?? null,
@@ -1,4 +1,4 @@
1
- import { spawn } from "node:child_process";
1
+ import { execFileSync, spawn } from "node:child_process";
2
2
  import { closeSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, appendFileSync, openSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  function splitSurfaces(value) {
@@ -13,6 +13,44 @@ function pidAlive(pid) {
13
13
  return false;
14
14
  }
15
15
  }
16
+ function portListenerPids(port) {
17
+ if (!port || !Number.isFinite(port)) return [];
18
+ try {
19
+ const output = execFileSync("lsof", [`-tiTCP:${port}`, "-sTCP:LISTEN", "-n", "-P"], {
20
+ encoding: "utf8",
21
+ stdio: ["ignore", "pipe", "ignore"]
22
+ });
23
+ return output.split(/\r?\n/u).map((entry) => Number(entry.trim())).filter((pid) => Number.isInteger(pid) && pid > 0);
24
+ } catch {
25
+ return [];
26
+ }
27
+ }
28
+ async function waitForPidsToExit(pids, timeoutMs = 3e3) {
29
+ const startedAt = Date.now();
30
+ while (pids.some((pid) => pidAlive(pid)) && Date.now() - startedAt < timeoutMs) {
31
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 100));
32
+ }
33
+ }
34
+ async function stopConflictingPortListeners(spec, allowedPid = null) {
35
+ const conflicts = portListenerPids(spec.port).filter((pid) => pid !== process.pid && pid !== allowedPid);
36
+ if (conflicts.length === 0) return [];
37
+ for (const pid of conflicts) {
38
+ try {
39
+ process.kill(pid, "SIGTERM");
40
+ } catch {
41
+ }
42
+ }
43
+ await waitForPidsToExit(conflicts);
44
+ const remaining = conflicts.filter((pid) => pidAlive(pid));
45
+ for (const pid of remaining) {
46
+ try {
47
+ process.kill(pid, "SIGKILL");
48
+ } catch {
49
+ }
50
+ }
51
+ await waitForPidsToExit(remaining, 1e3);
52
+ return conflicts;
53
+ }
16
54
  function scopeId(tenantRoot) {
17
55
  return Buffer.from(tenantRoot).toString("base64url").slice(0, 32);
18
56
  }
@@ -240,7 +278,7 @@ async function instanceFromSpecWithHealth(spec) {
240
278
  const instance = instanceFromSpec(spec);
241
279
  const healthStatus = await checkHealth(spec);
242
280
  const healthy = healthStatus.length === 0 || healthStatus.every((entry) => entry.ok);
243
- const running = healthStatus.length > 0 ? healthy : instance.running;
281
+ const running = healthStatus.length > 0 ? instance.running && healthy : instance.running;
244
282
  return {
245
283
  ...instance,
246
284
  healthStatus,
@@ -270,7 +308,7 @@ function stopSpec(spec) {
270
308
  rmSync(spec.instancePath, { force: true });
271
309
  return instance;
272
310
  }
273
- async function startSpec(spec, force = false) {
311
+ async function startSpec(spec, force = false, forceConflicts = false) {
274
312
  const existing = instanceFromSpec(spec);
275
313
  if (existing.running && !force) {
276
314
  return existing;
@@ -278,6 +316,9 @@ async function startSpec(spec, force = false) {
278
316
  if (existing.running && force) {
279
317
  stopSpec(spec);
280
318
  }
319
+ if (forceConflicts) {
320
+ await stopConflictingPortListeners(spec, existing.pid);
321
+ }
281
322
  mkdirSync(resolve(spec.pidPath, ".."), { recursive: true });
282
323
  mkdirSync(resolve(spec.instancePath, ".."), { recursive: true });
283
324
  mkdirSync(resolve(spec.logPath, ".."), { recursive: true });
@@ -311,7 +352,7 @@ async function startTreeseedManagedDev(options = {}) {
311
352
  const plan = createTreeseedIntegratedDevPlan(options);
312
353
  const instances = [];
313
354
  for (const spec of plan.processes) {
314
- await startSpec(spec, options.force === true);
355
+ await startSpec(spec, options.force === true, options.forceConflicts === true);
315
356
  instances.push(await waitForHealthySpec(spec));
316
357
  }
317
358
  return { ok: instances.every((entry) => entry.running), action: "start", plan, instances };
@@ -50,19 +50,30 @@ const CONTENT_DEFAULTS = {
50
50
  idPrefix: "agent",
51
51
  extension: "mdx",
52
52
  fields: {
53
+ slug: "",
54
+ title: "",
53
55
  name: "",
54
- handler: "plan",
56
+ agentClass: "general",
55
57
  enabled: true,
56
58
  operator: "TreeSeed platform",
57
59
  runtimeStatus: "active",
58
60
  capabilities: [],
59
61
  tags: ["agent"],
60
- systemPrompt: "Use the core objective as the first context message. Keep work observable, governed, and grounded in project content.",
61
- persona: "Helpful, careful, and accountable.",
62
- triggers: [{ type: "message", messageTypes: [] }],
63
- permissions: [],
64
- execution: { provider: "codex", model: "gpt-5.5", approvalPolicy: "never", sandboxMode: "read_only", reasoningEffort: "medium" },
65
- outputs: {}
62
+ identity: {
63
+ purpose: "",
64
+ responsibilities: [],
65
+ durableInstructions: "Keep work observable, governed, and grounded in project content."
66
+ },
67
+ activityProfiles: {
68
+ planning: {
69
+ enabled: true,
70
+ handler: "writer",
71
+ prompt: { system: "Use TreeDX-backed content tools and stay scoped to this project." },
72
+ branchPolicy: { kind: "read-only", base: "main" },
73
+ tools: { allowed: ["treeseed.content.query", "treeseed.content.read"] },
74
+ outputs: { messageTypes: [], modelMutations: [] }
75
+ }
76
+ }
66
77
  },
67
78
  body: "Describe this agent role, operating boundaries, and expected outputs."
68
79
  }