stagent 0.1.9 → 0.1.11
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 +144 -62
- package/package.json +1 -2
- package/public/readme/cost-usage-list.png +0 -0
- package/public/readme/dashboard-bulk-select.png +0 -0
- package/public/readme/dashboard-card-edit.png +0 -0
- package/public/readme/dashboard-create-form-ai-applied.png +0 -0
- package/public/readme/dashboard-create-form-ai-assist.png +0 -0
- package/public/readme/dashboard-create-form-empty.png +0 -0
- package/public/readme/dashboard-create-form-filled.png +0 -0
- package/public/readme/dashboard-filtered.png +0 -0
- package/public/readme/dashboard-list.png +0 -0
- package/public/readme/dashboard-sorted.png +0 -0
- package/public/readme/dashboard-workflow-confirm.png +0 -0
- package/public/readme/documents-grid.png +0 -0
- package/public/readme/documents-list.png +0 -0
- package/public/readme/home-below-fold.png +0 -0
- package/public/readme/home-list.png +0 -0
- package/public/readme/inbox-list.png +0 -0
- package/public/readme/monitor-list.png +0 -0
- package/public/readme/profiles-list.png +0 -0
- package/public/readme/projects-detail.png +0 -0
- package/public/readme/projects-list.png +0 -0
- package/public/readme/schedules-list.png +0 -0
- package/public/readme/settings-list.png +0 -0
- package/public/readme/workflows-list.png +0 -0
- package/src/app/api/profiles/route.ts +0 -1
- package/src/app/api/workflows/from-assist/route.ts +143 -0
- package/src/app/dashboard/page.tsx +24 -2
- package/src/app/globals.css +0 -5
- package/src/app/tasks/page.tsx +5 -0
- package/src/app/workflows/from-assist/page.tsx +35 -0
- package/src/components/profiles/profile-detail-view.tsx +1 -16
- package/src/components/profiles/profile-form-view.tsx +0 -22
- package/src/components/projects/project-card.tsx +47 -35
- package/src/components/tasks/ai-assist-panel.tsx +31 -10
- package/src/components/tasks/task-card.tsx +16 -1
- package/src/components/tasks/task-create-panel.tsx +39 -0
- package/src/components/workflows/workflow-confirmation-view.tsx +447 -0
- 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 +5 -13
- package/src/lib/agents/pattern-extractor.ts +15 -64
- package/src/lib/agents/profiles/__tests__/suggest.test.ts +67 -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/suggest.ts +36 -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 +102 -6
- package/src/lib/agents/runtime/task-assist-types.ts +12 -2
- package/src/lib/constants/task-status.ts +6 -0
- package/src/lib/data/__tests__/clear.test.ts +42 -0
- package/src/lib/data/clear.ts +3 -0
- package/src/lib/data/seed-data/profiles.ts +0 -3
- package/src/lib/notifications/permissions.ts +6 -2
- package/src/lib/usage/__tests__/ledger.test.ts +29 -5
- package/src/lib/usage/ledger.ts +3 -1
- package/src/lib/usage/pricing.ts +61 -7
- package/src/lib/validators/__tests__/profile.test.ts +0 -15
- package/src/lib/validators/profile.ts +0 -1
- package/src/lib/workflows/__tests__/assist-builder.test.ts +255 -0
- package/src/lib/workflows/__tests__/engine.test.ts +2 -0
- package/src/lib/workflows/assist-builder.ts +248 -0
- package/src/lib/workflows/assist-session.ts +78 -0
- package/src/lib/workflows/engine.ts +47 -1
|
@@ -2,7 +2,7 @@ import { db } from "@/lib/db";
|
|
|
2
2
|
import { learnedContext, notifications } from "@/lib/db/schema";
|
|
3
3
|
import { and, desc, eq } from "drizzle-orm";
|
|
4
4
|
import type { LearnedContextRow } from "@/lib/db/schema";
|
|
5
|
-
import
|
|
5
|
+
import { runMetaCompletion } from "./runtime/claude";
|
|
6
6
|
|
|
7
7
|
const CONTEXT_CHAR_LIMIT = 8_000;
|
|
8
8
|
const SUMMARIZATION_THRESHOLD = 6_000;
|
|
@@ -243,14 +243,8 @@ export async function summarizeContext(profileId: string): Promise<void> {
|
|
|
243
243
|
const content = getActiveLearnedContext(profileId);
|
|
244
244
|
if (!content || content.length <= SUMMARIZATION_THRESHOLD) return;
|
|
245
245
|
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
model: "claude-sonnet-4-20250514",
|
|
249
|
-
max_tokens: 2048,
|
|
250
|
-
messages: [
|
|
251
|
-
{
|
|
252
|
-
role: "user",
|
|
253
|
-
content: `You are condensing learned context for an AI agent profile "${profileId}".
|
|
246
|
+
const { text } = await runMetaCompletion({
|
|
247
|
+
prompt: `You are condensing learned context for an AI agent profile "${profileId}".
|
|
254
248
|
The current context has grown to ${content.length} characters and needs to be summarized to under ${SUMMARIZATION_THRESHOLD} characters while preserving all key patterns, best practices, and important insights.
|
|
255
249
|
|
|
256
250
|
Current learned context:
|
|
@@ -266,12 +260,10 @@ Produce a condensed version that:
|
|
|
266
260
|
5. Stays under ${SUMMARIZATION_THRESHOLD} characters
|
|
267
261
|
|
|
268
262
|
Output ONLY the condensed context, no preamble.`,
|
|
269
|
-
|
|
270
|
-
],
|
|
263
|
+
activityType: "context_summarization",
|
|
271
264
|
});
|
|
272
265
|
|
|
273
|
-
const summarized =
|
|
274
|
-
response.content[0].type === "text" ? response.content[0].text : "";
|
|
266
|
+
const summarized = text.trim();
|
|
275
267
|
|
|
276
268
|
if (!summarized || summarized.length >= content.length) return;
|
|
277
269
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import Anthropic from "@anthropic-ai/sdk";
|
|
2
1
|
import { db } from "@/lib/db";
|
|
3
2
|
import { tasks, agentLogs } from "@/lib/db/schema";
|
|
4
3
|
import { eq, desc } from "drizzle-orm";
|
|
@@ -6,6 +5,7 @@ import {
|
|
|
6
5
|
getActiveLearnedContext,
|
|
7
6
|
proposeContextAddition,
|
|
8
7
|
} from "./learned-context";
|
|
8
|
+
import { runMetaCompletion } from "./runtime/claude";
|
|
9
9
|
|
|
10
10
|
export interface PatternEntry {
|
|
11
11
|
title: string;
|
|
@@ -17,50 +17,9 @@ export interface PatternProposal {
|
|
|
17
17
|
patterns: PatternEntry[];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const PATTERN_TOOL: Anthropic.Messages.Tool = {
|
|
21
|
-
name: "propose_learned_patterns",
|
|
22
|
-
description:
|
|
23
|
-
"Propose patterns learned from this task execution that should be remembered for future tasks with this profile.",
|
|
24
|
-
input_schema: {
|
|
25
|
-
type: "object" as const,
|
|
26
|
-
properties: {
|
|
27
|
-
patterns: {
|
|
28
|
-
type: "array",
|
|
29
|
-
items: {
|
|
30
|
-
type: "object",
|
|
31
|
-
properties: {
|
|
32
|
-
title: {
|
|
33
|
-
type: "string",
|
|
34
|
-
description: "Short pattern name (2-6 words)",
|
|
35
|
-
},
|
|
36
|
-
description: {
|
|
37
|
-
type: "string",
|
|
38
|
-
description:
|
|
39
|
-
"Concise description of the pattern or lesson (1-2 sentences)",
|
|
40
|
-
},
|
|
41
|
-
category: {
|
|
42
|
-
type: "string",
|
|
43
|
-
enum: [
|
|
44
|
-
"error_resolution",
|
|
45
|
-
"best_practice",
|
|
46
|
-
"shortcut",
|
|
47
|
-
"preference",
|
|
48
|
-
],
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
required: ["title", "description", "category"],
|
|
52
|
-
},
|
|
53
|
-
description:
|
|
54
|
-
"Patterns worth remembering. Return empty array if nothing notable.",
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
required: ["patterns"],
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
20
|
/**
|
|
62
21
|
* Analyze a completed task for patterns worth learning.
|
|
63
|
-
*
|
|
22
|
+
* Routes through the Claude Agent SDK runtime (no direct Anthropic SDK usage).
|
|
64
23
|
* Returns the notification ID if a proposal was created, null otherwise.
|
|
65
24
|
*/
|
|
66
25
|
export async function analyzeForLearnedPatterns(
|
|
@@ -99,16 +58,13 @@ export async function analyzeForLearnedPatterns(
|
|
|
99
58
|
})
|
|
100
59
|
.join("\n");
|
|
101
60
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{
|
|
110
|
-
role: "user",
|
|
111
|
-
content: `Analyze this completed task for patterns worth learning for the "${profileId}" agent profile.
|
|
61
|
+
const { text } = await runMetaCompletion({
|
|
62
|
+
prompt: `Analyze this completed task for patterns worth learning for the "${profileId}" agent profile.
|
|
63
|
+
|
|
64
|
+
Return ONLY a JSON array (no markdown, no code fences):
|
|
65
|
+
[{"title": "...", "description": "...", "category": "error_resolution|best_practice|shortcut|preference"}]
|
|
66
|
+
|
|
67
|
+
Return an empty array [] if no noteworthy patterns.
|
|
112
68
|
|
|
113
69
|
## Task
|
|
114
70
|
Title: ${task.title}
|
|
@@ -124,22 +80,17 @@ ${logSummary.slice(0, 2000)}
|
|
|
124
80
|
${currentContext ?? "(none yet)"}
|
|
125
81
|
|
|
126
82
|
Extract ONLY genuinely useful patterns — things that would help this profile avoid mistakes or work more efficiently on similar future tasks. If this task was routine with nothing notable, return an empty patterns array. Do NOT repeat patterns already in the learned context.`,
|
|
127
|
-
|
|
128
|
-
],
|
|
83
|
+
activityType: "pattern_extraction",
|
|
129
84
|
});
|
|
130
85
|
|
|
131
|
-
//
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
if (!toolBlock || toolBlock.type !== "tool_use") return null;
|
|
86
|
+
// Parse JSON array from response text
|
|
87
|
+
const jsonMatch = text.match(/\[[\s\S]*\]/);
|
|
88
|
+
const patterns: PatternEntry[] = jsonMatch ? JSON.parse(jsonMatch[0]) : [];
|
|
137
89
|
|
|
138
|
-
|
|
139
|
-
if (!proposal.patterns || proposal.patterns.length === 0) return null;
|
|
90
|
+
if (patterns.length === 0) return null;
|
|
140
91
|
|
|
141
92
|
// Format patterns as text for the proposal
|
|
142
|
-
const formattedAdditions =
|
|
93
|
+
const formattedAdditions = patterns
|
|
143
94
|
.map(
|
|
144
95
|
(p) =>
|
|
145
96
|
`### ${p.title} [${p.category}]\n${p.description}`
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { suggestProfileForStep } from "../suggest";
|
|
3
|
+
|
|
4
|
+
const ALL_PROFILES = [
|
|
5
|
+
"general",
|
|
6
|
+
"researcher",
|
|
7
|
+
"code-reviewer",
|
|
8
|
+
"document-writer",
|
|
9
|
+
"devops-engineer",
|
|
10
|
+
"data-analyst",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
describe("suggestProfileForStep", () => {
|
|
14
|
+
it("suggests researcher for research-related tasks", () => {
|
|
15
|
+
expect(
|
|
16
|
+
suggestProfileForStep("Research API patterns", "Investigate best practices", ALL_PROFILES)
|
|
17
|
+
).toBe("researcher");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("suggests code-reviewer for review tasks", () => {
|
|
21
|
+
expect(
|
|
22
|
+
suggestProfileForStep("Security audit", "Review code for vulnerabilities", ALL_PROFILES)
|
|
23
|
+
).toBe("code-reviewer");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("suggests document-writer for writing tasks", () => {
|
|
27
|
+
expect(
|
|
28
|
+
suggestProfileForStep("Write documentation", "Document the API endpoints", ALL_PROFILES)
|
|
29
|
+
).toBe("document-writer");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("suggests devops-engineer for deployment tasks", () => {
|
|
33
|
+
expect(
|
|
34
|
+
suggestProfileForStep("Deploy to production", "Set up CI pipeline and infrastructure", ALL_PROFILES)
|
|
35
|
+
).toBe("devops-engineer");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("suggests data-analyst for data tasks", () => {
|
|
39
|
+
expect(
|
|
40
|
+
suggestProfileForStep("Analyze metrics", "Aggregate data and create statistics", ALL_PROFILES)
|
|
41
|
+
).toBe("data-analyst");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('returns "auto" when no keywords match', () => {
|
|
45
|
+
expect(
|
|
46
|
+
suggestProfileForStep("Fix the thing", "Make it work", ALL_PROFILES)
|
|
47
|
+
).toBe("auto");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("only suggests from available profiles", () => {
|
|
51
|
+
expect(
|
|
52
|
+
suggestProfileForStep("Research API patterns", "Investigate", ["general"])
|
|
53
|
+
).toBe("auto");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("picks highest-scoring profile when multiple match", () => {
|
|
57
|
+
// "review" + "security" + "vulnerability" = 3 hits for code-reviewer
|
|
58
|
+
// "investigate" = 1 hit for researcher
|
|
59
|
+
expect(
|
|
60
|
+
suggestProfileForStep(
|
|
61
|
+
"Security review",
|
|
62
|
+
"Investigate vulnerability audit",
|
|
63
|
+
ALL_PROFILES
|
|
64
|
+
)
|
|
65
|
+
).toBe("code-reviewer");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -12,7 +12,6 @@ runtimeOverrides:
|
|
|
12
12
|
Stay pragmatic, execute the requested work directly, and prefer concise operational updates.
|
|
13
13
|
Keep outputs grounded in the current workspace and call out blocked actions explicitly.
|
|
14
14
|
|
|
15
|
-
temperature: 0.5
|
|
16
15
|
maxTurns: 30
|
|
17
16
|
|
|
18
17
|
author: stagent
|
|
@@ -174,7 +174,6 @@ function scanProfiles(): Map<string, AgentProfile> {
|
|
|
174
174
|
allowedTools: config.allowedTools,
|
|
175
175
|
mcpServers: config.mcpServers as Record<string, unknown>,
|
|
176
176
|
canUseToolPolicy: config.canUseToolPolicy,
|
|
177
|
-
temperature: config.temperature,
|
|
178
177
|
maxTurns: config.maxTurns,
|
|
179
178
|
outputFormat: config.outputFormat,
|
|
180
179
|
version: config.version,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight keyword-based profile suggestion.
|
|
3
|
+
* Used as fallback when AI doesn't suggest a profile for a step.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const KEYWORD_MAP: Record<string, string[]> = {
|
|
7
|
+
researcher: ["research", "search", "investigate", "explore", "find", "discover", "analyze data", "survey"],
|
|
8
|
+
"code-reviewer": ["review", "audit", "security", "vulnerability", "lint", "inspect", "code quality"],
|
|
9
|
+
"document-writer": ["write", "document", "report", "summarize", "draft", "compose", "blog", "article"],
|
|
10
|
+
"devops-engineer": ["deploy", "ci", "infrastructure", "pipeline", "docker", "kubernetes", "terraform", "monitor"],
|
|
11
|
+
"data-analyst": ["analyze", "data", "statistics", "metrics", "chart", "visualization", "dashboard", "aggregate"],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function suggestProfileForStep(
|
|
15
|
+
title: string,
|
|
16
|
+
description: string,
|
|
17
|
+
availableProfileIds: string[]
|
|
18
|
+
): string {
|
|
19
|
+
const text = `${title} ${description}`.toLowerCase();
|
|
20
|
+
let bestProfile = "auto";
|
|
21
|
+
let bestScore = 0;
|
|
22
|
+
|
|
23
|
+
for (const [profileId, keywords] of Object.entries(KEYWORD_MAP)) {
|
|
24
|
+
if (!availableProfileIds.includes(profileId)) continue;
|
|
25
|
+
let score = 0;
|
|
26
|
+
for (const keyword of keywords) {
|
|
27
|
+
if (text.includes(keyword)) score++;
|
|
28
|
+
}
|
|
29
|
+
if (score > bestScore) {
|
|
30
|
+
bestScore = score;
|
|
31
|
+
bestProfile = profileId;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return bestScore >= 1 ? bestProfile : "auto";
|
|
36
|
+
}
|
|
@@ -50,7 +50,7 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
|
|
|
50
50
|
resume: true,
|
|
51
51
|
cancel: true,
|
|
52
52
|
approvals: true,
|
|
53
|
-
mcpServers:
|
|
53
|
+
mcpServers: false, // Not yet wired — configs not passed to codex subprocess
|
|
54
54
|
profileTests: false,
|
|
55
55
|
taskAssist: true,
|
|
56
56
|
authHealthCheck: true,
|
|
@@ -4,7 +4,7 @@ import { tasks } from "@/lib/db/schema";
|
|
|
4
4
|
import { eq } from "drizzle-orm";
|
|
5
5
|
import { updateAuthStatus, getAuthEnv } from "@/lib/settings/auth";
|
|
6
6
|
import { getExecution, removeExecution } from "@/lib/agents/execution-manager";
|
|
7
|
-
import { getProfile } from "@/lib/agents/profiles/registry";
|
|
7
|
+
import { getProfile, listProfiles } from "@/lib/agents/profiles/registry";
|
|
8
8
|
import { resolveProfileRuntimePayload } from "@/lib/agents/profiles/compatibility";
|
|
9
9
|
import { executeClaudeTask, resumeClaudeTask } from "@/lib/agents/claude-agent";
|
|
10
10
|
import { getRuntimeCapabilities, getRuntimeCatalogEntry } from "./catalog";
|
|
@@ -23,13 +23,41 @@ import {
|
|
|
23
23
|
type UsageSnapshot,
|
|
24
24
|
} from "@/lib/usage/ledger";
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
function buildTaskAssistSystemPrompt(profileIds: string[]): string {
|
|
27
|
+
const profileList = profileIds.length > 0
|
|
28
|
+
? `Available agent profiles: ${profileIds.join(", ")}\nUse "auto" if unsure which profile fits a step.`
|
|
29
|
+
: `No explicit profiles available. Use "auto" for suggestedProfile.`;
|
|
30
|
+
|
|
31
|
+
return `You are an AI task definition assistant. Analyze the given task and return ONLY a JSON object (no markdown, no code fences) with:
|
|
27
32
|
- "improvedDescription": A clearer version of the task for an AI agent to execute
|
|
28
|
-
- "breakdown": Array of
|
|
29
|
-
- "
|
|
33
|
+
- "breakdown": Array of step objects if complex (empty array if simple). Each step: {title, description, suggestedProfile?, requiresApproval?, dependsOn?}
|
|
34
|
+
- "suggestedProfile": one of the available profile IDs or "auto"
|
|
35
|
+
- "requiresApproval": true if the step involves irreversible actions needing human review
|
|
36
|
+
- "dependsOn": array of step indices (0-based) this step depends on (for parallel/swarm patterns)
|
|
37
|
+
- "recommendedPattern": one of "single", "sequence", "planner-executor", "checkpoint", "parallel", "loop", "swarm"
|
|
38
|
+
- "sequence": steps run one after another in order
|
|
39
|
+
- "planner-executor": first step plans, remaining steps execute the plan
|
|
40
|
+
- "checkpoint": like sequence but certain steps pause for human approval
|
|
41
|
+
- "parallel": independent steps run concurrently, a final synthesis step merges results (use dependsOn to mark the synthesis step)
|
|
42
|
+
- "loop": a single step repeats iteratively until a goal is met (include suggestedLoopConfig)
|
|
43
|
+
- "swarm": first step is the mayor (coordinator), middle steps are workers (run in parallel), last step is the refinery (merges results)
|
|
30
44
|
- "complexity": "simple", "moderate", or "complex"
|
|
31
45
|
- "needsCheckpoint": true if irreversible actions or needs human review
|
|
32
|
-
- "reasoning": Brief explanation
|
|
46
|
+
- "reasoning": Brief explanation of why you chose this pattern
|
|
47
|
+
- "suggestedLoopConfig": {maxIterations, timeBudgetMs?} — only for loop pattern
|
|
48
|
+
- "suggestedSwarmConfig": {workerConcurrencyLimit?} — only for swarm pattern
|
|
49
|
+
|
|
50
|
+
${profileList}
|
|
51
|
+
|
|
52
|
+
Pattern selection guide:
|
|
53
|
+
- Use "single" for simple, atomic tasks
|
|
54
|
+
- Use "sequence" for ordered multi-step work where each step builds on the previous
|
|
55
|
+
- Use "planner-executor" when the task needs analysis before action
|
|
56
|
+
- Use "checkpoint" when steps involve deployments, deletions, or other irreversible actions
|
|
57
|
+
- Use "parallel" when sub-tasks are independent and can run concurrently (research, analysis)
|
|
58
|
+
- Use "loop" for iterative refinement (code review cycles, optimization passes)
|
|
59
|
+
- Use "swarm" for complex tasks needing multiple specialized agents coordinated by a lead`;
|
|
60
|
+
}
|
|
33
61
|
|
|
34
62
|
async function collectResultText(
|
|
35
63
|
response: AsyncIterable<Record<string, unknown>>
|
|
@@ -224,6 +252,71 @@ async function runClaudeProfileTests(profileId: string): Promise<ProfileTestRepo
|
|
|
224
252
|
};
|
|
225
253
|
}
|
|
226
254
|
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Lightweight meta-completion (pattern extraction, context summarization, etc.)
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
export async function runMetaCompletion(input: {
|
|
260
|
+
prompt: string;
|
|
261
|
+
activityType: string;
|
|
262
|
+
}): Promise<{ text: string; usage: UsageSnapshot }> {
|
|
263
|
+
const authEnv = await getAuthEnv();
|
|
264
|
+
const startedAt = new Date();
|
|
265
|
+
let usage: UsageSnapshot = {};
|
|
266
|
+
const abortController = new AbortController();
|
|
267
|
+
const timeout = setTimeout(() => abortController.abort(), 60_000);
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const response = query({
|
|
271
|
+
prompt: input.prompt,
|
|
272
|
+
options: {
|
|
273
|
+
abortController,
|
|
274
|
+
includePartialMessages: true,
|
|
275
|
+
cwd: process.cwd(),
|
|
276
|
+
env: buildClaudeSdkEnv(authEnv),
|
|
277
|
+
allowedTools: [],
|
|
278
|
+
maxTurns: 1,
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const collected = await collectResultText(
|
|
283
|
+
response as AsyncIterable<Record<string, unknown>>
|
|
284
|
+
);
|
|
285
|
+
usage = collected.usage;
|
|
286
|
+
|
|
287
|
+
await recordUsageLedgerEntry({
|
|
288
|
+
activityType: input.activityType as import("@/lib/usage/ledger").UsageActivityType,
|
|
289
|
+
runtimeId: "claude-code",
|
|
290
|
+
providerId: "anthropic",
|
|
291
|
+
modelId: usage.modelId ?? null,
|
|
292
|
+
inputTokens: usage.inputTokens ?? null,
|
|
293
|
+
outputTokens: usage.outputTokens ?? null,
|
|
294
|
+
totalTokens: usage.totalTokens ?? null,
|
|
295
|
+
status: "completed",
|
|
296
|
+
startedAt,
|
|
297
|
+
finishedAt: new Date(),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return { text: collected.resultText, usage };
|
|
301
|
+
} catch (error) {
|
|
302
|
+
await recordUsageLedgerEntry({
|
|
303
|
+
activityType: input.activityType as import("@/lib/usage/ledger").UsageActivityType,
|
|
304
|
+
runtimeId: "claude-code",
|
|
305
|
+
providerId: "anthropic",
|
|
306
|
+
modelId: usage.modelId ?? null,
|
|
307
|
+
inputTokens: usage.inputTokens ?? null,
|
|
308
|
+
outputTokens: usage.outputTokens ?? null,
|
|
309
|
+
totalTokens: usage.totalTokens ?? null,
|
|
310
|
+
status: "failed",
|
|
311
|
+
startedAt,
|
|
312
|
+
finishedAt: new Date(),
|
|
313
|
+
});
|
|
314
|
+
throw error;
|
|
315
|
+
} finally {
|
|
316
|
+
clearTimeout(timeout);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
227
320
|
async function runClaudeTaskAssist(
|
|
228
321
|
input: TaskAssistInput
|
|
229
322
|
): Promise<TaskAssistResponse> {
|
|
@@ -235,7 +328,9 @@ async function runClaudeTaskAssist(
|
|
|
235
328
|
.join("\n");
|
|
236
329
|
|
|
237
330
|
const authEnv = await getAuthEnv();
|
|
238
|
-
const
|
|
331
|
+
const profileIds = listProfiles().map((p) => p.id);
|
|
332
|
+
const systemPrompt = buildTaskAssistSystemPrompt(profileIds);
|
|
333
|
+
const prompt = `${systemPrompt}\n\n${userMessage}`;
|
|
239
334
|
const startedAt = new Date();
|
|
240
335
|
let usage: UsageSnapshot = {};
|
|
241
336
|
|
|
@@ -315,6 +410,7 @@ async function testClaudeConnection(): Promise<RuntimeConnectionResult> {
|
|
|
315
410
|
options: {
|
|
316
411
|
abortController,
|
|
317
412
|
maxTurns: 1,
|
|
413
|
+
includePartialMessages: false,
|
|
318
414
|
cwd: process.cwd(),
|
|
319
415
|
env: buildClaudeSdkEnv(authEnv),
|
|
320
416
|
},
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
export interface TaskAssistBreakdownStep {
|
|
2
|
+
title: string;
|
|
3
|
+
description: string;
|
|
4
|
+
suggestedProfile?: string;
|
|
5
|
+
requiresApproval?: boolean;
|
|
6
|
+
dependsOn?: number[];
|
|
7
|
+
}
|
|
8
|
+
|
|
1
9
|
export interface TaskAssistResponse {
|
|
2
10
|
improvedDescription: string;
|
|
3
|
-
breakdown:
|
|
4
|
-
recommendedPattern: "single" | "sequence" | "planner-executor" | "checkpoint";
|
|
11
|
+
breakdown: TaskAssistBreakdownStep[];
|
|
12
|
+
recommendedPattern: "single" | "sequence" | "planner-executor" | "checkpoint" | "parallel" | "loop" | "swarm";
|
|
5
13
|
complexity: "simple" | "moderate" | "complex";
|
|
6
14
|
needsCheckpoint: boolean;
|
|
7
15
|
reasoning: string;
|
|
16
|
+
suggestedLoopConfig?: { maxIterations: number; timeBudgetMs?: number };
|
|
17
|
+
suggestedSwarmConfig?: { workerConcurrencyLimit?: number };
|
|
8
18
|
}
|
|
@@ -47,3 +47,9 @@ export function isValidDragTransition(from: TaskStatus, to: TaskStatus): boolean
|
|
|
47
47
|
|
|
48
48
|
/** Maximum number of times a task can be resumed before requiring a fresh start */
|
|
49
49
|
export const MAX_RESUME_COUNT = 3;
|
|
50
|
+
|
|
51
|
+
/** Default max turns for agent task execution (safety net) */
|
|
52
|
+
export const DEFAULT_MAX_TURNS = 50;
|
|
53
|
+
|
|
54
|
+
/** Default per-execution budget cap in USD */
|
|
55
|
+
export const DEFAULT_MAX_BUDGET_USD = 2.0;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import * as schema from "@/lib/db/schema";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Safety-net test: every table exported from schema.ts must appear in clear.ts
|
|
8
|
+
* (except `settings`, which is intentionally preserved across clears).
|
|
9
|
+
*
|
|
10
|
+
* When you add a new table to schema.ts, this test will fail until you add a
|
|
11
|
+
* corresponding db.delete() call to clear.ts in the correct FK-safe order.
|
|
12
|
+
*/
|
|
13
|
+
describe("clearAllData coverage", () => {
|
|
14
|
+
const INTENTIONALLY_PRESERVED = ["settings"];
|
|
15
|
+
|
|
16
|
+
it("deletes every schema table (except settings)", () => {
|
|
17
|
+
const clearSource = readFileSync(
|
|
18
|
+
join(__dirname, "..", "clear.ts"),
|
|
19
|
+
"utf-8"
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Collect all sqliteTable exports from schema
|
|
23
|
+
const tableExports = Object.entries(schema)
|
|
24
|
+
.filter(
|
|
25
|
+
([, value]) =>
|
|
26
|
+
value != null &&
|
|
27
|
+
typeof value === "object" &&
|
|
28
|
+
"getSQL" in (value as Record<string, unknown>)
|
|
29
|
+
)
|
|
30
|
+
.map(([name]) => name);
|
|
31
|
+
|
|
32
|
+
expect(tableExports.length).toBeGreaterThan(0);
|
|
33
|
+
|
|
34
|
+
const missing = tableExports.filter(
|
|
35
|
+
(name) =>
|
|
36
|
+
!INTENTIONALLY_PRESERVED.includes(name) &&
|
|
37
|
+
!clearSource.includes(`db.delete(${name})`)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(missing, `Tables missing from clear.ts: ${missing.join(", ")}`).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
});
|
package/src/lib/data/clear.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
agentLogs,
|
|
4
4
|
notifications,
|
|
5
5
|
documents,
|
|
6
|
+
learnedContext,
|
|
6
7
|
tasks,
|
|
7
8
|
workflows,
|
|
8
9
|
schedules,
|
|
@@ -31,6 +32,7 @@ export function clearAllData() {
|
|
|
31
32
|
const logsDeleted = db.delete(agentLogs).run().changes;
|
|
32
33
|
const notificationsDeleted = db.delete(notifications).run().changes;
|
|
33
34
|
const documentsDeleted = db.delete(documents).run().changes;
|
|
35
|
+
const learnedContextDeleted = db.delete(learnedContext).run().changes;
|
|
34
36
|
const tasksDeleted = db.delete(tasks).run().changes;
|
|
35
37
|
const workflowsDeleted = db.delete(workflows).run().changes;
|
|
36
38
|
const schedulesDeleted = db.delete(schedules).run().changes;
|
|
@@ -58,6 +60,7 @@ export function clearAllData() {
|
|
|
58
60
|
agentLogs: logsDeleted,
|
|
59
61
|
notifications: notificationsDeleted,
|
|
60
62
|
documents: documentsDeleted,
|
|
63
|
+
learnedContext: learnedContextDeleted,
|
|
61
64
|
files: filesDeleted,
|
|
62
65
|
};
|
|
63
66
|
}
|