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.
Files changed (81) hide show
  1. package/README.md +144 -62
  2. package/package.json +1 -2
  3. package/public/readme/cost-usage-list.png +0 -0
  4. package/public/readme/dashboard-bulk-select.png +0 -0
  5. package/public/readme/dashboard-card-edit.png +0 -0
  6. package/public/readme/dashboard-create-form-ai-applied.png +0 -0
  7. package/public/readme/dashboard-create-form-ai-assist.png +0 -0
  8. package/public/readme/dashboard-create-form-empty.png +0 -0
  9. package/public/readme/dashboard-create-form-filled.png +0 -0
  10. package/public/readme/dashboard-filtered.png +0 -0
  11. package/public/readme/dashboard-list.png +0 -0
  12. package/public/readme/dashboard-sorted.png +0 -0
  13. package/public/readme/dashboard-workflow-confirm.png +0 -0
  14. package/public/readme/documents-grid.png +0 -0
  15. package/public/readme/documents-list.png +0 -0
  16. package/public/readme/home-below-fold.png +0 -0
  17. package/public/readme/home-list.png +0 -0
  18. package/public/readme/inbox-list.png +0 -0
  19. package/public/readme/monitor-list.png +0 -0
  20. package/public/readme/profiles-list.png +0 -0
  21. package/public/readme/projects-detail.png +0 -0
  22. package/public/readme/projects-list.png +0 -0
  23. package/public/readme/schedules-list.png +0 -0
  24. package/public/readme/settings-list.png +0 -0
  25. package/public/readme/workflows-list.png +0 -0
  26. package/src/app/api/profiles/route.ts +0 -1
  27. package/src/app/api/workflows/from-assist/route.ts +143 -0
  28. package/src/app/dashboard/page.tsx +24 -2
  29. package/src/app/globals.css +0 -5
  30. package/src/app/tasks/page.tsx +5 -0
  31. package/src/app/workflows/from-assist/page.tsx +35 -0
  32. package/src/components/profiles/profile-detail-view.tsx +1 -16
  33. package/src/components/profiles/profile-form-view.tsx +0 -22
  34. package/src/components/projects/project-card.tsx +47 -35
  35. package/src/components/tasks/ai-assist-panel.tsx +31 -10
  36. package/src/components/tasks/task-card.tsx +16 -1
  37. package/src/components/tasks/task-create-panel.tsx +39 -0
  38. package/src/components/workflows/workflow-confirmation-view.tsx +447 -0
  39. package/src/lib/agents/__tests__/claude-agent.test.ts +7 -2
  40. package/src/lib/agents/__tests__/learned-context.test.ts +500 -0
  41. package/src/lib/agents/__tests__/pattern-extractor.test.ts +243 -0
  42. package/src/lib/agents/__tests__/sweep.test.ts +202 -0
  43. package/src/lib/agents/claude-agent.ts +104 -78
  44. package/src/lib/agents/learned-context.ts +5 -13
  45. package/src/lib/agents/pattern-extractor.ts +15 -64
  46. package/src/lib/agents/profiles/__tests__/suggest.test.ts +67 -0
  47. package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +0 -1
  48. package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +0 -1
  49. package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +0 -1
  50. package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +0 -1
  51. package/src/lib/agents/profiles/builtins/general/profile.yaml +0 -1
  52. package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +0 -1
  53. package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +0 -1
  54. package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +0 -1
  55. package/src/lib/agents/profiles/builtins/researcher/profile.yaml +0 -1
  56. package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +0 -1
  57. package/src/lib/agents/profiles/builtins/sweep/profile.yaml +0 -1
  58. package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +0 -1
  59. package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +0 -1
  60. package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +0 -1
  61. package/src/lib/agents/profiles/registry.ts +0 -1
  62. package/src/lib/agents/profiles/suggest.ts +36 -0
  63. package/src/lib/agents/profiles/types.ts +0 -1
  64. package/src/lib/agents/runtime/catalog.ts +1 -1
  65. package/src/lib/agents/runtime/claude.ts +102 -6
  66. package/src/lib/agents/runtime/task-assist-types.ts +12 -2
  67. package/src/lib/constants/task-status.ts +6 -0
  68. package/src/lib/data/__tests__/clear.test.ts +42 -0
  69. package/src/lib/data/clear.ts +3 -0
  70. package/src/lib/data/seed-data/profiles.ts +0 -3
  71. package/src/lib/notifications/permissions.ts +6 -2
  72. package/src/lib/usage/__tests__/ledger.test.ts +29 -5
  73. package/src/lib/usage/ledger.ts +3 -1
  74. package/src/lib/usage/pricing.ts +61 -7
  75. package/src/lib/validators/__tests__/profile.test.ts +0 -15
  76. package/src/lib/validators/profile.ts +0 -1
  77. package/src/lib/workflows/__tests__/assist-builder.test.ts +255 -0
  78. package/src/lib/workflows/__tests__/engine.test.ts +2 -0
  79. package/src/lib/workflows/assist-builder.ts +248 -0
  80. package/src/lib/workflows/assist-session.ts +78 -0
  81. 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 Anthropic from "@anthropic-ai/sdk";
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 client = new Anthropic();
247
- const response = await client.messages.create({
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
- * Makes a focused Claude API call, then proposes additions if patterns found.
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 client = new Anthropic();
103
- const response = await client.messages.create({
104
- model: "claude-sonnet-4-20250514",
105
- max_tokens: 1024,
106
- tools: [PATTERN_TOOL],
107
- tool_choice: { type: "tool", name: "propose_learned_patterns" },
108
- messages: [
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
- // Extract the tool use result
132
- const toolBlock = response.content.find(
133
- (block) => block.type === "tool_use" && block.name === "propose_learned_patterns"
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
- const proposal = toolBlock.input as PatternProposal;
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 = proposal.patterns
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
+ });
@@ -15,7 +15,6 @@ canUseToolPolicy:
15
15
  autoApprove: [Read, Grep, Glob]
16
16
  autoDeny: []
17
17
 
18
- temperature: 0.3
19
18
  maxTurns: 20
20
19
  outputFormat: structured-findings
21
20
 
@@ -15,7 +15,6 @@ canUseToolPolicy:
15
15
  autoApprove: [Read, Grep, Glob]
16
16
  autoDeny: []
17
17
 
18
- temperature: 0.3
19
18
  maxTurns: 30
20
19
 
21
20
  author: stagent
@@ -15,7 +15,6 @@ canUseToolPolicy:
15
15
  autoApprove: [Read, Grep, Glob]
16
16
  autoDeny: []
17
17
 
18
- temperature: 0.3
19
18
  maxTurns: 30
20
19
 
21
20
  author: stagent
@@ -14,7 +14,6 @@ canUseToolPolicy:
14
14
  autoApprove: [Read]
15
15
  autoDeny: []
16
16
 
17
- temperature: 0.5
18
17
  maxTurns: 20
19
18
  outputFormat: markdown-document
20
19
 
@@ -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
@@ -14,7 +14,6 @@ canUseToolPolicy:
14
14
  autoApprove: [WebSearch, WebFetch, Read]
15
15
  autoDeny: [Bash, Write, Edit]
16
16
 
17
- temperature: 0.6
18
17
  maxTurns: 20
19
18
 
20
19
  author: stagent
@@ -14,7 +14,6 @@ canUseToolPolicy:
14
14
  autoApprove: [WebSearch, WebFetch, Read]
15
15
  autoDeny: [Bash, Write, Edit]
16
16
 
17
- temperature: 0.5
18
17
  maxTurns: 25
19
18
 
20
19
  author: stagent
@@ -14,7 +14,6 @@ canUseToolPolicy:
14
14
  autoApprove: [Read, Grep, Glob]
15
15
  autoDeny: []
16
16
 
17
- temperature: 0.4
18
17
  maxTurns: 25
19
18
 
20
19
  author: stagent
@@ -15,7 +15,6 @@ canUseToolPolicy:
15
15
  autoApprove: [WebSearch, WebFetch, Read]
16
16
  autoDeny: []
17
17
 
18
- temperature: 0.4
19
18
  maxTurns: 25
20
19
 
21
20
  author: stagent
@@ -14,7 +14,6 @@ canUseToolPolicy:
14
14
  autoApprove: [WebSearch, WebFetch, Read]
15
15
  autoDeny: [Bash, Write, Edit]
16
16
 
17
- temperature: 0.5
18
17
  maxTurns: 20
19
18
 
20
19
  author: stagent
@@ -5,7 +5,6 @@ domain: work
5
5
  tags: [sweep, audit, improvement, maintenance]
6
6
  supportedRuntimes: [claude-code]
7
7
 
8
- temperature: 0.3
9
8
  maxTurns: 50
10
9
  outputFormat: json
11
10
 
@@ -16,7 +16,6 @@ canUseToolPolicy:
16
16
  autoApprove: [Read, Grep, Glob]
17
17
  autoDeny: []
18
18
 
19
- temperature: 0.4
20
19
  maxTurns: 20
21
20
  outputFormat: markdown
22
21
 
@@ -14,7 +14,6 @@ canUseToolPolicy:
14
14
  autoApprove: [WebSearch, WebFetch, Read]
15
15
  autoDeny: [Bash, Write, Edit]
16
16
 
17
- temperature: 0.6
18
17
  maxTurns: 25
19
18
 
20
19
  author: stagent
@@ -14,7 +14,6 @@ canUseToolPolicy:
14
14
  autoApprove: [Read]
15
15
  autoDeny: [Bash, Write, Edit]
16
16
 
17
- temperature: 0.3
18
17
  maxTurns: 20
19
18
 
20
19
  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
+ }
@@ -31,7 +31,6 @@ export interface AgentProfile {
31
31
  allowedTools?: string[];
32
32
  mcpServers?: Record<string, unknown>;
33
33
  canUseToolPolicy?: CanUseToolPolicy;
34
- temperature?: number;
35
34
  maxTurns?: number;
36
35
  outputFormat?: string;
37
36
  version?: string;
@@ -50,7 +50,7 @@ const RUNTIME_CATALOG: Record<AgentRuntimeId, RuntimeCatalogEntry> = {
50
50
  resume: true,
51
51
  cancel: true,
52
52
  approvals: true,
53
- mcpServers: true,
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
- const TASK_ASSIST_SYSTEM_PROMPT = `You are an AI task definition assistant. Analyze the given task and return ONLY a JSON object (no markdown, no code fences) with:
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 {title, description} sub-tasks if complex (empty array if simple)
29
- - "recommendedPattern": "single", "sequence", "planner-executor", or "checkpoint"
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 prompt = `${TASK_ASSIST_SYSTEM_PROMPT}\n\n${userMessage}`;
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: { title: string; description: string }[];
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
+ });
@@ -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
  }