stagent 0.1.10 → 0.1.12
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 +58 -27
- package/package.json +3 -3
- package/src/__tests__/e2e/blueprint.test.ts +63 -0
- package/src/__tests__/e2e/cross-runtime.test.ts +77 -0
- package/src/__tests__/e2e/helpers.ts +286 -0
- package/src/__tests__/e2e/parallel-workflow.test.ts +120 -0
- package/src/__tests__/e2e/sequence-workflow.test.ts +109 -0
- package/src/__tests__/e2e/setup.ts +156 -0
- package/src/__tests__/e2e/single-task.test.ts +170 -0
- package/src/app/api/command-palette/recent/route.ts +41 -18
- package/src/app/api/context/batch/route.ts +44 -0
- package/src/app/api/permissions/presets/route.ts +80 -0
- package/src/app/api/playbook/status/route.ts +15 -0
- package/src/app/api/profiles/route.ts +23 -21
- package/src/app/api/settings/pricing/route.ts +15 -0
- package/src/app/costs/page.tsx +53 -43
- package/src/app/globals.css +0 -5
- package/src/app/playbook/[slug]/page.tsx +76 -0
- package/src/app/playbook/page.tsx +54 -0
- package/src/app/profiles/page.tsx +7 -4
- package/src/app/settings/page.tsx +2 -2
- package/src/app/tasks/page.tsx +5 -0
- package/src/components/costs/cost-dashboard.tsx +226 -320
- package/src/components/dashboard/activity-feed.tsx +6 -2
- package/src/components/notifications/batch-proposal-review.tsx +150 -0
- package/src/components/notifications/notification-item.tsx +6 -3
- package/src/components/notifications/pending-approval-host.tsx +57 -11
- package/src/components/playbook/adoption-heatmap.tsx +69 -0
- package/src/components/playbook/journey-card.tsx +110 -0
- package/src/components/playbook/playbook-action-button.tsx +22 -0
- package/src/components/playbook/playbook-browser.tsx +143 -0
- package/src/components/playbook/playbook-card.tsx +102 -0
- package/src/components/playbook/playbook-detail-view.tsx +223 -0
- package/src/components/playbook/playbook-homepage.tsx +142 -0
- package/src/components/playbook/playbook-toc.tsx +90 -0
- package/src/components/playbook/playbook-updated-badge.tsx +23 -0
- package/src/components/playbook/related-docs.tsx +30 -0
- package/src/components/profiles/__tests__/learned-context-panel.test.tsx +175 -0
- package/src/components/profiles/context-proposal-review.tsx +7 -3
- package/src/components/profiles/learned-context-panel.tsx +116 -8
- package/src/components/profiles/profile-detail-view.tsx +7 -19
- package/src/components/profiles/profile-form-view.tsx +0 -22
- package/src/components/settings/__tests__/auth-config-section.test.tsx +147 -0
- package/src/components/settings/api-key-form.tsx +5 -43
- package/src/components/settings/auth-config-section.tsx +10 -6
- package/src/components/settings/auth-status-badge.tsx +8 -0
- package/src/components/settings/budget-guardrails-section.tsx +403 -620
- package/src/components/settings/connection-test-control.tsx +63 -0
- package/src/components/settings/permissions-section.tsx +85 -75
- package/src/components/settings/permissions-sections.tsx +24 -0
- package/src/components/settings/presets-section.tsx +159 -0
- package/src/components/settings/pricing-registry-panel.tsx +164 -0
- package/src/components/shared/app-sidebar.tsx +2 -0
- package/src/components/shared/command-palette.tsx +30 -0
- package/src/components/shared/light-markdown.tsx +134 -0
- package/src/components/workflows/loop-status-view.tsx +8 -4
- package/src/components/workflows/workflow-status-view.tsx +16 -9
- package/src/lib/agents/__tests__/claude-agent.test.ts +7 -2
- package/src/lib/agents/__tests__/learned-context.test.ts +500 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +243 -0
- package/src/lib/agents/__tests__/sweep.test.ts +202 -0
- package/src/lib/agents/claude-agent.ts +104 -78
- package/src/lib/agents/learned-context.ts +32 -28
- package/src/lib/agents/learning-session.ts +234 -0
- package/src/lib/agents/pattern-extractor.ts +34 -64
- package/src/lib/agents/profiles/__tests__/sort.test.ts +42 -0
- package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/general/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/researcher/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/sweep/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +0 -1
- package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +0 -1
- package/src/lib/agents/profiles/registry.ts +0 -1
- package/src/lib/agents/profiles/sort.ts +7 -0
- package/src/lib/agents/profiles/types.ts +0 -1
- package/src/lib/agents/runtime/catalog.ts +1 -1
- package/src/lib/agents/runtime/claude.ts +66 -0
- package/src/lib/constants/settings.ts +1 -0
- package/src/lib/constants/task-status.ts +6 -0
- package/src/lib/data/seed-data/profiles.ts +0 -3
- package/src/lib/db/schema.ts +3 -0
- package/src/lib/docs/adoption.ts +105 -0
- package/src/lib/docs/journey-tracker.ts +21 -0
- package/src/lib/docs/reader.ts +102 -0
- package/src/lib/docs/types.ts +54 -0
- package/src/lib/docs/usage-stage.ts +60 -0
- package/src/lib/notifications/actionable.ts +18 -10
- package/src/lib/settings/__tests__/budget-guardrails.test.ts +86 -24
- package/src/lib/settings/budget-guardrails.ts +213 -85
- package/src/lib/settings/permission-presets.ts +150 -0
- package/src/lib/settings/runtime-setup.ts +71 -0
- package/src/lib/usage/__tests__/ledger.test.ts +29 -5
- package/src/lib/usage/__tests__/pricing-registry.test.ts +78 -0
- package/src/lib/usage/ledger.ts +4 -2
- package/src/lib/usage/pricing-registry.ts +570 -0
- package/src/lib/usage/pricing.ts +15 -41
- package/src/lib/utils/__tests__/learned-context-history.test.ts +171 -0
- package/src/lib/utils/learned-context-history.ts +150 -0
- package/src/lib/validators/__tests__/profile.test.ts +0 -15
- package/src/lib/validators/__tests__/settings.test.ts +23 -16
- package/src/lib/validators/profile.ts +0 -1
- package/src/lib/validators/settings.ts +3 -9
- package/src/lib/workflows/__tests__/engine.test.ts +2 -0
- package/src/lib/workflows/engine.ts +20 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { LearnedContextRow } from "@/lib/db/schema";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildLearnedContextHistoryEntries,
|
|
5
|
+
buildUnifiedDiff,
|
|
6
|
+
hasMeaningfulDerivedDiff,
|
|
7
|
+
} from "../learned-context-history";
|
|
8
|
+
|
|
9
|
+
function makeRow(
|
|
10
|
+
overrides: Partial<LearnedContextRow> & {
|
|
11
|
+
id: string;
|
|
12
|
+
version: number;
|
|
13
|
+
changeType: LearnedContextRow["changeType"];
|
|
14
|
+
}
|
|
15
|
+
): LearnedContextRow {
|
|
16
|
+
return {
|
|
17
|
+
id: overrides.id,
|
|
18
|
+
profileId: overrides.profileId ?? "general",
|
|
19
|
+
version: overrides.version,
|
|
20
|
+
content: overrides.content ?? null,
|
|
21
|
+
diff: overrides.diff ?? null,
|
|
22
|
+
changeType: overrides.changeType,
|
|
23
|
+
sourceTaskId: overrides.sourceTaskId ?? null,
|
|
24
|
+
proposalNotificationId: overrides.proposalNotificationId ?? null,
|
|
25
|
+
proposedAdditions: overrides.proposedAdditions ?? null,
|
|
26
|
+
approvedBy: overrides.approvedBy ?? null,
|
|
27
|
+
createdAt: overrides.createdAt ?? new Date("2026-03-17T12:00:00.000Z"),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("buildUnifiedDiff", () => {
|
|
32
|
+
it("treats the first version as all additions", () => {
|
|
33
|
+
expect(buildUnifiedDiff(null, "Alpha\nBeta")).toEqual([
|
|
34
|
+
{ kind: "added", value: "Alpha" },
|
|
35
|
+
{ kind: "added", value: "Beta" },
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("builds a unified diff for appended content", () => {
|
|
40
|
+
expect(buildUnifiedDiff("Alpha", "Alpha\nBeta")).toEqual([
|
|
41
|
+
{ kind: "context", value: "Alpha" },
|
|
42
|
+
{ kind: "added", value: "Beta" },
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("buildLearnedContextHistoryEntries", () => {
|
|
48
|
+
it("derives rollback diffs from the previous snapshot version", () => {
|
|
49
|
+
const history = [
|
|
50
|
+
makeRow({
|
|
51
|
+
id: "v3",
|
|
52
|
+
version: 3,
|
|
53
|
+
changeType: "rollback",
|
|
54
|
+
content: "Alpha",
|
|
55
|
+
diff: "Rolled back to version 1",
|
|
56
|
+
}),
|
|
57
|
+
makeRow({
|
|
58
|
+
id: "v2",
|
|
59
|
+
version: 2,
|
|
60
|
+
changeType: "approved",
|
|
61
|
+
content: "Alpha\nBeta",
|
|
62
|
+
diff: "Beta",
|
|
63
|
+
}),
|
|
64
|
+
makeRow({
|
|
65
|
+
id: "v1",
|
|
66
|
+
version: 1,
|
|
67
|
+
changeType: "approved",
|
|
68
|
+
content: "Alpha",
|
|
69
|
+
diff: "Alpha",
|
|
70
|
+
}),
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const entries = buildLearnedContextHistoryEntries(history);
|
|
74
|
+
const rollbackEntry = entries[0];
|
|
75
|
+
|
|
76
|
+
expect(rollbackEntry.snapshotContent).toBe("Alpha");
|
|
77
|
+
expect(rollbackEntry.derivedDiff).toEqual({
|
|
78
|
+
previousVersion: 2,
|
|
79
|
+
lines: [
|
|
80
|
+
{ kind: "context", value: "Alpha" },
|
|
81
|
+
{ kind: "removed", value: "Beta" },
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("derives summarization diffs from the previous snapshot version", () => {
|
|
87
|
+
const history = [
|
|
88
|
+
makeRow({
|
|
89
|
+
id: "v3",
|
|
90
|
+
version: 3,
|
|
91
|
+
changeType: "summarization",
|
|
92
|
+
content: "Alpha\nGamma",
|
|
93
|
+
diff: "Summarized from 14 to 11 chars",
|
|
94
|
+
}),
|
|
95
|
+
makeRow({
|
|
96
|
+
id: "v2",
|
|
97
|
+
version: 2,
|
|
98
|
+
changeType: "rejected",
|
|
99
|
+
content: "Alpha\nBeta",
|
|
100
|
+
diff: "Rejected addition",
|
|
101
|
+
}),
|
|
102
|
+
makeRow({
|
|
103
|
+
id: "v1",
|
|
104
|
+
version: 1,
|
|
105
|
+
changeType: "approved",
|
|
106
|
+
content: "Alpha\nBeta",
|
|
107
|
+
diff: "Alpha\nBeta",
|
|
108
|
+
}),
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const entries = buildLearnedContextHistoryEntries(history);
|
|
112
|
+
const summarizationEntry = entries[0];
|
|
113
|
+
|
|
114
|
+
expect(summarizationEntry.derivedDiff).toEqual({
|
|
115
|
+
previousVersion: 1,
|
|
116
|
+
lines: [
|
|
117
|
+
{ kind: "context", value: "Alpha" },
|
|
118
|
+
{ kind: "removed", value: "Beta" },
|
|
119
|
+
{ kind: "added", value: "Gamma" },
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("does not create derived diffs for proposal-only rows", () => {
|
|
125
|
+
const history = [
|
|
126
|
+
makeRow({
|
|
127
|
+
id: "proposal",
|
|
128
|
+
version: 2,
|
|
129
|
+
changeType: "proposal",
|
|
130
|
+
diff: "Add retries",
|
|
131
|
+
}),
|
|
132
|
+
makeRow({
|
|
133
|
+
id: "approved",
|
|
134
|
+
version: 1,
|
|
135
|
+
changeType: "approved",
|
|
136
|
+
content: "Validate inputs",
|
|
137
|
+
diff: "Validate inputs",
|
|
138
|
+
}),
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const entries = buildLearnedContextHistoryEntries(history);
|
|
142
|
+
|
|
143
|
+
expect(entries[0].snapshotContent).toBeNull();
|
|
144
|
+
expect(entries[0].derivedDiff).toBeNull();
|
|
145
|
+
expect(entries[1].derivedDiff?.previousVersion).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe("hasMeaningfulDerivedDiff", () => {
|
|
150
|
+
it("detects when a derived diff contains additions or removals", () => {
|
|
151
|
+
expect(
|
|
152
|
+
hasMeaningfulDerivedDiff({
|
|
153
|
+
previousVersion: 1,
|
|
154
|
+
lines: [
|
|
155
|
+
{ kind: "context", value: "Alpha" },
|
|
156
|
+
{ kind: "added", value: "Beta" },
|
|
157
|
+
],
|
|
158
|
+
})
|
|
159
|
+
).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("returns false for null or unchanged diffs", () => {
|
|
163
|
+
expect(hasMeaningfulDerivedDiff(null)).toBe(false);
|
|
164
|
+
expect(
|
|
165
|
+
hasMeaningfulDerivedDiff({
|
|
166
|
+
previousVersion: 1,
|
|
167
|
+
lines: [{ kind: "context", value: "Alpha" }],
|
|
168
|
+
})
|
|
169
|
+
).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { LearnedContextRow } from "@/lib/db/schema";
|
|
2
|
+
|
|
3
|
+
export type LearnedContextSnapshotType =
|
|
4
|
+
| "approved"
|
|
5
|
+
| "rollback"
|
|
6
|
+
| "summarization";
|
|
7
|
+
|
|
8
|
+
export type LearnedContextDiffKind = "context" | "added" | "removed";
|
|
9
|
+
|
|
10
|
+
export interface LearnedContextDiffLine {
|
|
11
|
+
kind: LearnedContextDiffKind;
|
|
12
|
+
value: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LearnedContextDerivedDiff {
|
|
16
|
+
previousVersion: number | null;
|
|
17
|
+
lines: LearnedContextDiffLine[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface LearnedContextHistoryEntry {
|
|
21
|
+
row: LearnedContextRow;
|
|
22
|
+
snapshotContent: string | null;
|
|
23
|
+
derivedDiff: LearnedContextDerivedDiff | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SNAPSHOT_CHANGE_TYPES = new Set<LearnedContextSnapshotType>([
|
|
27
|
+
"approved",
|
|
28
|
+
"rollback",
|
|
29
|
+
"summarization",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
export function isSnapshotVersion(
|
|
33
|
+
row: LearnedContextRow
|
|
34
|
+
): row is LearnedContextRow & {
|
|
35
|
+
changeType: LearnedContextSnapshotType;
|
|
36
|
+
content: string;
|
|
37
|
+
} {
|
|
38
|
+
return (
|
|
39
|
+
SNAPSHOT_CHANGE_TYPES.has(row.changeType as LearnedContextSnapshotType) &&
|
|
40
|
+
typeof row.content === "string"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeLines(content: string | null): string[] {
|
|
45
|
+
if (!content) return [];
|
|
46
|
+
return content.replace(/\r\n/g, "\n").split("\n");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildLcsTable(previous: string[], next: string[]): number[][] {
|
|
50
|
+
const table = Array.from({ length: previous.length + 1 }, () =>
|
|
51
|
+
Array(next.length + 1).fill(0)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
for (let i = previous.length - 1; i >= 0; i -= 1) {
|
|
55
|
+
for (let j = next.length - 1; j >= 0; j -= 1) {
|
|
56
|
+
if (previous[i] === next[j]) {
|
|
57
|
+
table[i][j] = table[i + 1][j + 1] + 1;
|
|
58
|
+
} else {
|
|
59
|
+
table[i][j] = Math.max(table[i + 1][j], table[i][j + 1]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return table;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function buildUnifiedDiff(
|
|
68
|
+
previousContent: string | null,
|
|
69
|
+
nextContent: string
|
|
70
|
+
): LearnedContextDiffLine[] {
|
|
71
|
+
const previousLines = normalizeLines(previousContent);
|
|
72
|
+
const nextLines = normalizeLines(nextContent);
|
|
73
|
+
|
|
74
|
+
if (previousLines.length === 0) {
|
|
75
|
+
return nextLines.map((value) => ({ kind: "added", value }));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const table = buildLcsTable(previousLines, nextLines);
|
|
79
|
+
const lines: LearnedContextDiffLine[] = [];
|
|
80
|
+
|
|
81
|
+
let i = 0;
|
|
82
|
+
let j = 0;
|
|
83
|
+
|
|
84
|
+
while (i < previousLines.length && j < nextLines.length) {
|
|
85
|
+
if (previousLines[i] === nextLines[j]) {
|
|
86
|
+
lines.push({ kind: "context", value: previousLines[i] });
|
|
87
|
+
i += 1;
|
|
88
|
+
j += 1;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (table[i + 1][j] >= table[i][j + 1]) {
|
|
93
|
+
lines.push({ kind: "removed", value: previousLines[i] });
|
|
94
|
+
i += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lines.push({ kind: "added", value: nextLines[j] });
|
|
99
|
+
j += 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
while (i < previousLines.length) {
|
|
103
|
+
lines.push({ kind: "removed", value: previousLines[i] });
|
|
104
|
+
i += 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
while (j < nextLines.length) {
|
|
108
|
+
lines.push({ kind: "added", value: nextLines[j] });
|
|
109
|
+
j += 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return lines;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function hasMeaningfulDerivedDiff(
|
|
116
|
+
diff: LearnedContextDerivedDiff | null
|
|
117
|
+
): boolean {
|
|
118
|
+
return Boolean(
|
|
119
|
+
diff?.lines.some((line) => line.kind === "added" || line.kind === "removed")
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function buildLearnedContextHistoryEntries(
|
|
124
|
+
history: LearnedContextRow[]
|
|
125
|
+
): LearnedContextHistoryEntry[] {
|
|
126
|
+
const derivedDiffById = new Map<string, LearnedContextDerivedDiff>();
|
|
127
|
+
let previousSnapshot: (LearnedContextRow & {
|
|
128
|
+
changeType: LearnedContextSnapshotType;
|
|
129
|
+
content: string;
|
|
130
|
+
}) | null = null;
|
|
131
|
+
|
|
132
|
+
const ascending = [...history].sort((a, b) => a.version - b.version);
|
|
133
|
+
|
|
134
|
+
for (const row of ascending) {
|
|
135
|
+
if (!isSnapshotVersion(row)) continue;
|
|
136
|
+
|
|
137
|
+
derivedDiffById.set(row.id, {
|
|
138
|
+
previousVersion: previousSnapshot?.version ?? null,
|
|
139
|
+
lines: buildUnifiedDiff(previousSnapshot?.content ?? null, row.content),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
previousSnapshot = row;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return history.map((row) => ({
|
|
146
|
+
row,
|
|
147
|
+
snapshotContent: isSnapshotVersion(row) ? row.content : null,
|
|
148
|
+
derivedDiff: derivedDiffById.get(row.id) ?? null,
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
@@ -28,7 +28,6 @@ describe("ProfileConfigSchema", () => {
|
|
|
28
28
|
preToolCall: ["echo pre"],
|
|
29
29
|
postToolCall: ["echo post"],
|
|
30
30
|
},
|
|
31
|
-
temperature: 0.5,
|
|
32
31
|
maxTurns: 20,
|
|
33
32
|
outputFormat: "markdown",
|
|
34
33
|
author: "stagent",
|
|
@@ -77,20 +76,6 @@ describe("ProfileConfigSchema", () => {
|
|
|
77
76
|
expect(result.success).toBe(false);
|
|
78
77
|
});
|
|
79
78
|
|
|
80
|
-
it("rejects temperature out of range", () => {
|
|
81
|
-
const tooHigh = ProfileConfigSchema.safeParse({
|
|
82
|
-
...validProfile,
|
|
83
|
-
temperature: 1.5,
|
|
84
|
-
});
|
|
85
|
-
expect(tooHigh.success).toBe(false);
|
|
86
|
-
|
|
87
|
-
const tooLow = ProfileConfigSchema.safeParse({
|
|
88
|
-
...validProfile,
|
|
89
|
-
temperature: -0.1,
|
|
90
|
-
});
|
|
91
|
-
expect(tooLow.success).toBe(false);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
79
|
it("rejects invalid source URL", () => {
|
|
95
80
|
const result = ProfileConfigSchema.safeParse({
|
|
96
81
|
...validProfile,
|
|
@@ -99,24 +99,18 @@ describe("updateOpenAISettingsSchema", () => {
|
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
describe("updateBudgetPolicySchema", () => {
|
|
102
|
-
it("accepts
|
|
102
|
+
it("accepts a monthly-only budget payload", () => {
|
|
103
103
|
const result = updateBudgetPolicySchema.safeParse({
|
|
104
104
|
overall: {
|
|
105
|
-
dailySpendCapUsd: null,
|
|
106
105
|
monthlySpendCapUsd: 50,
|
|
107
106
|
},
|
|
108
107
|
runtimes: {
|
|
109
108
|
"claude-code": {
|
|
110
|
-
dailySpendCapUsd: 10,
|
|
111
109
|
monthlySpendCapUsd: 100,
|
|
112
|
-
|
|
113
|
-
monthlyTokenCap: null,
|
|
110
|
+
claudeOAuthPlan: "max_5x",
|
|
114
111
|
},
|
|
115
112
|
"openai-codex-app-server": {
|
|
116
|
-
dailySpendCapUsd: null,
|
|
117
113
|
monthlySpendCapUsd: null,
|
|
118
|
-
dailyTokenCap: null,
|
|
119
|
-
monthlyTokenCap: 50000,
|
|
120
114
|
},
|
|
121
115
|
},
|
|
122
116
|
});
|
|
@@ -127,21 +121,34 @@ describe("updateBudgetPolicySchema", () => {
|
|
|
127
121
|
it("rejects zero and negative values", () => {
|
|
128
122
|
const result = updateBudgetPolicySchema.safeParse({
|
|
129
123
|
overall: {
|
|
130
|
-
dailySpendCapUsd: 0,
|
|
131
124
|
monthlySpendCapUsd: -1,
|
|
132
125
|
},
|
|
133
126
|
runtimes: {
|
|
134
127
|
"claude-code": {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
dailyTokenCap: 0,
|
|
138
|
-
monthlyTokenCap: null,
|
|
128
|
+
monthlySpendCapUsd: 0,
|
|
129
|
+
claudeOAuthPlan: "pro",
|
|
139
130
|
},
|
|
140
131
|
"openai-codex-app-server": {
|
|
141
|
-
dailySpendCapUsd: null,
|
|
142
132
|
monthlySpendCapUsd: null,
|
|
143
|
-
|
|
144
|
-
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(result.success).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("rejects invalid Claude OAuth plans", () => {
|
|
141
|
+
const result = updateBudgetPolicySchema.safeParse({
|
|
142
|
+
overall: {
|
|
143
|
+
monthlySpendCapUsd: 300,
|
|
144
|
+
},
|
|
145
|
+
runtimes: {
|
|
146
|
+
"claude-code": {
|
|
147
|
+
monthlySpendCapUsd: 150,
|
|
148
|
+
claudeOAuthPlan: "enterprise",
|
|
149
|
+
},
|
|
150
|
+
"openai-codex-app-server": {
|
|
151
|
+
monthlySpendCapUsd: 150,
|
|
145
152
|
},
|
|
146
153
|
},
|
|
147
154
|
});
|
|
@@ -38,7 +38,6 @@ export const ProfileConfigSchema = z.object({
|
|
|
38
38
|
postToolCall: z.array(z.string()).optional(),
|
|
39
39
|
})
|
|
40
40
|
.optional(),
|
|
41
|
-
temperature: z.number().min(0).max(1).optional(),
|
|
42
41
|
maxTurns: z.number().positive().optional(),
|
|
43
42
|
outputFormat: z.string().optional(),
|
|
44
43
|
author: z.string().optional(),
|
|
@@ -25,22 +25,15 @@ const nullablePositiveNumber = z.preprocess((value) => {
|
|
|
25
25
|
return value;
|
|
26
26
|
}, z.number().finite().positive().nullable());
|
|
27
27
|
|
|
28
|
-
const
|
|
29
|
-
if (value === "" || value == null) return null;
|
|
30
|
-
if (typeof value === "string") return Number(value);
|
|
31
|
-
return value;
|
|
32
|
-
}, z.number().int().positive().nullable());
|
|
28
|
+
export const claudeOAuthPlanSchema = z.enum(["pro", "max_5x", "max_20x"]);
|
|
33
29
|
|
|
34
30
|
export const runtimeBudgetPolicySchema = z.object({
|
|
35
|
-
dailySpendCapUsd: nullablePositiveNumber,
|
|
36
31
|
monthlySpendCapUsd: nullablePositiveNumber,
|
|
37
|
-
|
|
38
|
-
monthlyTokenCap: nullablePositiveInteger,
|
|
32
|
+
claudeOAuthPlan: claudeOAuthPlanSchema.optional(),
|
|
39
33
|
});
|
|
40
34
|
|
|
41
35
|
export const budgetPolicySchema = z.object({
|
|
42
36
|
overall: z.object({
|
|
43
|
-
dailySpendCapUsd: nullablePositiveNumber,
|
|
44
37
|
monthlySpendCapUsd: nullablePositiveNumber,
|
|
45
38
|
}),
|
|
46
39
|
runtimes: z.object(
|
|
@@ -55,3 +48,4 @@ export const updateBudgetPolicySchema = budgetPolicySchema;
|
|
|
55
48
|
export type RuntimeBudgetPolicy = z.infer<typeof runtimeBudgetPolicySchema>;
|
|
56
49
|
export type BudgetPolicy = z.infer<typeof budgetPolicySchema>;
|
|
57
50
|
export type UpdateBudgetPolicyInput = z.infer<typeof updateBudgetPolicySchema>;
|
|
51
|
+
export type ClaudeOAuthPlan = z.infer<typeof claudeOAuthPlanSchema>;
|
|
@@ -103,6 +103,8 @@ describe("executeWorkflow", () => {
|
|
|
103
103
|
.mockResolvedValueOnce([workflow])
|
|
104
104
|
.mockResolvedValueOnce([failedTask])
|
|
105
105
|
.mockResolvedValueOnce([workflow])
|
|
106
|
+
.mockResolvedValueOnce([workflow])
|
|
107
|
+
// syncSourceTaskStatus reads the workflow to find sourceTaskId
|
|
106
108
|
.mockResolvedValueOnce([workflow]);
|
|
107
109
|
|
|
108
110
|
const { executeWorkflow } = await import("../engine");
|
|
@@ -16,6 +16,10 @@ import {
|
|
|
16
16
|
buildSwarmWorkerPrompt,
|
|
17
17
|
getSwarmWorkflowStructure,
|
|
18
18
|
} from "./swarm";
|
|
19
|
+
import {
|
|
20
|
+
openLearningSession,
|
|
21
|
+
closeLearningSession,
|
|
22
|
+
} from "@/lib/agents/learning-session";
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
25
|
* Execute a workflow by advancing through its steps according to the pattern.
|
|
@@ -43,6 +47,10 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
43
47
|
timestamp: new Date(),
|
|
44
48
|
});
|
|
45
49
|
|
|
50
|
+
// Open a learning session to buffer context proposals during execution.
|
|
51
|
+
// Proposals are collected and presented as a single batch at workflow end.
|
|
52
|
+
openLearningSession(workflowId);
|
|
53
|
+
|
|
46
54
|
// Loop pattern manages its own lifecycle — delegate fully
|
|
47
55
|
if (definition.pattern === "loop") {
|
|
48
56
|
try {
|
|
@@ -72,6 +80,11 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
72
80
|
}),
|
|
73
81
|
timestamp: new Date(),
|
|
74
82
|
});
|
|
83
|
+
} finally {
|
|
84
|
+
// Close learning session — flush buffered proposals as batch notification
|
|
85
|
+
await closeLearningSession(workflowId).catch((err) => {
|
|
86
|
+
console.error("[workflow-engine] Failed to close learning session:", err);
|
|
87
|
+
});
|
|
75
88
|
}
|
|
76
89
|
return;
|
|
77
90
|
}
|
|
@@ -128,6 +141,11 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
128
141
|
}),
|
|
129
142
|
timestamp: new Date(),
|
|
130
143
|
});
|
|
144
|
+
} finally {
|
|
145
|
+
// Close learning session — flush buffered proposals as batch notification
|
|
146
|
+
await closeLearningSession(workflowId).catch((err) => {
|
|
147
|
+
console.error("[workflow-engine] Failed to close learning session:", err);
|
|
148
|
+
});
|
|
131
149
|
}
|
|
132
150
|
}
|
|
133
151
|
|
|
@@ -837,11 +855,12 @@ async function syncSourceTaskStatus(
|
|
|
837
855
|
status: "completed" | "failed"
|
|
838
856
|
): Promise<void> {
|
|
839
857
|
try {
|
|
840
|
-
const
|
|
858
|
+
const result = await db
|
|
841
859
|
.select()
|
|
842
860
|
.from(workflows)
|
|
843
861
|
.where(eq(workflows.id, workflowId));
|
|
844
862
|
|
|
863
|
+
const workflow = Array.isArray(result) ? result[0] : undefined;
|
|
845
864
|
if (!workflow) return;
|
|
846
865
|
|
|
847
866
|
const def = JSON.parse(workflow.definition);
|