@metabob/minibob 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +255 -0
- package/CHANGELOG.md +112 -0
- package/README.md +380 -0
- package/bin/minibob.js +36 -0
- package/dist/acp-gossip.d.ts +72 -0
- package/dist/acp-gossip.d.ts.map +1 -0
- package/dist/acp-gossip.js +156 -0
- package/dist/acp-gossip.js.map +1 -0
- package/dist/acp.d.ts +62 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +292 -0
- package/dist/acp.js.map +1 -0
- package/dist/activity.d.ts +157 -0
- package/dist/activity.d.ts.map +1 -0
- package/dist/activity.js +518 -0
- package/dist/activity.js.map +1 -0
- package/dist/agent-runtime.d.ts +104 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/boredom.d.ts +125 -0
- package/dist/boredom.d.ts.map +1 -0
- package/dist/boredom.js +244 -0
- package/dist/boredom.js.map +1 -0
- package/dist/cli/acp-server.d.ts +23 -0
- package/dist/cli/acp-server.d.ts.map +1 -0
- package/dist/cli/burrow.d.ts +26 -0
- package/dist/cli/burrow.d.ts.map +1 -0
- package/dist/cli/doctor.d.ts +22 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/goal.d.ts +22 -0
- package/dist/cli/goal.d.ts.map +1 -0
- package/dist/cli/index.d.ts +47 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/instance-registry.d.ts +78 -0
- package/dist/cli/instance-registry.d.ts.map +1 -0
- package/dist/cli/observe.d.ts +35 -0
- package/dist/cli/observe.d.ts.map +1 -0
- package/dist/cli/vessel.d.ts +14 -0
- package/dist/cli/vessel.d.ts.map +1 -0
- package/dist/composition-observer.d.ts +96 -0
- package/dist/composition-observer.d.ts.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +128 -0
- package/dist/config.js.map +1 -0
- package/dist/docker/Dockerfile +35 -0
- package/dist/environment.d.ts +72 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +142 -0
- package/dist/environment.js.map +1 -0
- package/dist/goal-processor.d.ts +165 -0
- package/dist/goal-processor.d.ts.map +1 -0
- package/dist/helm/minibob-cluster/Chart.yaml +13 -0
- package/dist/helm/minibob-cluster/templates/_helpers.tpl +60 -0
- package/dist/helm/minibob-cluster/templates/configmap.yaml +11 -0
- package/dist/helm/minibob-cluster/templates/deployment.yaml +108 -0
- package/dist/helm/minibob-cluster/templates/secret.yaml +10 -0
- package/dist/helm/minibob-cluster/templates/service.yaml +37 -0
- package/dist/helm/minibob-cluster/values-local.yaml +41 -0
- package/dist/helm/minibob-cluster/values-production.yaml +57 -0
- package/dist/helm/minibob-cluster/values-testing-cluster.yaml +43 -0
- package/dist/helm/minibob-cluster/values.yaml +127 -0
- package/dist/improviser.d.ts +74 -0
- package/dist/improviser.d.ts.map +1 -0
- package/dist/impulse-filter.d.ts +74 -0
- package/dist/impulse-filter.d.ts.map +1 -0
- package/dist/impulse.d.ts +92 -0
- package/dist/impulse.d.ts.map +1 -0
- package/dist/impulse.js +234 -0
- package/dist/impulse.js.map +1 -0
- package/dist/lib.d.ts +29 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +18561 -0
- package/dist/lib.js.map +98 -0
- package/dist/lifecycle-hooks.d.ts +99 -0
- package/dist/lifecycle-hooks.d.ts.map +1 -0
- package/dist/lifecycle-hooks.js +135 -0
- package/dist/lifecycle-hooks.js.map +1 -0
- package/dist/llm.d.ts +31 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +349 -0
- package/dist/llm.js.map +1 -0
- package/dist/mcp-activity-bridge.d.ts +66 -0
- package/dist/mcp-activity-bridge.d.ts.map +1 -0
- package/dist/mcp-activity-bridge.js +126 -0
- package/dist/mcp-activity-bridge.js.map +1 -0
- package/dist/mcp.d.ts +216 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +292 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory-agent.d.ts +92 -0
- package/dist/memory-agent.d.ts.map +1 -0
- package/dist/memory-agent.js +277 -0
- package/dist/memory-agent.js.map +1 -0
- package/dist/runtime-mapping.d.ts +97 -0
- package/dist/runtime-mapping.d.ts.map +1 -0
- package/dist/search-first-executor.d.ts +113 -0
- package/dist/search-first-executor.d.ts.map +1 -0
- package/dist/session.d.ts +48 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/template-extractor.d.ts +9 -0
- package/dist/template-extractor.d.ts.map +1 -0
- package/dist/template-generator.d.ts +12 -0
- package/dist/template-generator.d.ts.map +1 -0
- package/dist/tools.d.ts +58 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +771 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +503 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/understanding/analyzer.d.ts +55 -0
- package/dist/understanding/analyzer.d.ts.map +1 -0
- package/dist/understanding/explorer.d.ts +73 -0
- package/dist/understanding/explorer.d.ts.map +1 -0
- package/dist/understanding/index.d.ts +7 -0
- package/dist/understanding/index.d.ts.map +1 -0
- package/dist/understanding/types.d.ts +136 -0
- package/dist/understanding/types.d.ts.map +1 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +106 -0
- package/dist/validation.js.map +1 -0
- package/dist/vessel-bootstrap.d.ts +190 -0
- package/dist/vessel-bootstrap.d.ts.map +1 -0
- package/dist/vessel-registry.d.ts +229 -0
- package/dist/vessel-registry.d.ts.map +1 -0
- package/index.ts +1329 -0
- package/package.json +54 -0
- package/src/acp-gossip.ts +193 -0
- package/src/acp.ts +362 -0
- package/src/activity.ts +1464 -0
- package/src/agent-runtime.ts +365 -0
- package/src/boredom.ts +423 -0
- package/src/cli/acp-server.ts +377 -0
- package/src/cli/burrow.ts +896 -0
- package/src/cli/doctor.ts +526 -0
- package/src/cli/goal.ts +224 -0
- package/src/cli/index.ts +147 -0
- package/src/cli/instance-registry.ts +271 -0
- package/src/cli/observe.ts +682 -0
- package/src/cli/vessel.ts +287 -0
- package/src/components/SystemOverview.tsx +331 -0
- package/src/composition-observer.ts +449 -0
- package/src/config.ts +172 -0
- package/src/environment.ts +167 -0
- package/src/goal-processor.ts +654 -0
- package/src/improviser.ts +591 -0
- package/src/impulse-filter.ts +273 -0
- package/src/impulse.ts +311 -0
- package/src/lib.ts +147 -0
- package/src/lifecycle-hooks.ts +181 -0
- package/src/llm.ts +434 -0
- package/src/mcp-activity-bridge.ts +158 -0
- package/src/mcp.ts +747 -0
- package/src/memory-agent.ts +316 -0
- package/src/runtime-mapping.ts +527 -0
- package/src/search-first-executor.ts +666 -0
- package/src/session.ts +141 -0
- package/src/template-extractor.ts +256 -0
- package/src/template-generator.ts +130 -0
- package/src/tools.ts +924 -0
- package/src/types.ts +497 -0
- package/src/understanding/analyzer.ts +354 -0
- package/src/understanding/explorer.ts +488 -0
- package/src/understanding/index.ts +27 -0
- package/src/understanding/types.ts +153 -0
- package/src/validation.ts +125 -0
- package/src/vessel-bootstrap.ts +440 -0
- package/src/vessel-registry.ts +621 -0
- package/templates/core/edit-file.json +85 -0
- package/templates/understanding/diagnose-problem.json +32 -0
- package/templates/understanding/explore-codebase-v2.json +57 -0
- package/templates/understanding/explore-codebase.json +37 -0
package/src/session.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* minibob Session Tracker
|
|
3
|
+
*
|
|
4
|
+
* Tracks execution sequences across multiple activity executions in a session.
|
|
5
|
+
* Enables learning of successful activity sequences for goals.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ActivityExecution } from "./types"
|
|
9
|
+
import { getMCPClient, isMCPEnabled } from "./mcp"
|
|
10
|
+
|
|
11
|
+
export interface SessionExecutionItem {
|
|
12
|
+
activityId: string
|
|
13
|
+
executionId: string
|
|
14
|
+
order: number
|
|
15
|
+
triggerType: 'goal' | 'nested' | 'boredom' | 'manual'
|
|
16
|
+
parentExecutionId?: string
|
|
17
|
+
success: boolean
|
|
18
|
+
durationMs: number
|
|
19
|
+
costUsd: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SessionTracker {
|
|
23
|
+
sessionId: string
|
|
24
|
+
goalContext?: string
|
|
25
|
+
executions: SessionExecutionItem[]
|
|
26
|
+
startedAt: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Global session registry
|
|
30
|
+
const activeSessions = new Map<string, SessionTracker>()
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a new session for tracking execution sequences
|
|
34
|
+
*/
|
|
35
|
+
export function createSession(goalContext?: string): SessionTracker {
|
|
36
|
+
const sessionId = `session_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
37
|
+
|
|
38
|
+
const session: SessionTracker = {
|
|
39
|
+
sessionId,
|
|
40
|
+
goalContext,
|
|
41
|
+
executions: [],
|
|
42
|
+
startedAt: Date.now(),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
activeSessions.set(sessionId, session)
|
|
46
|
+
console.log(`[Session] Created: ${sessionId}${goalContext ? ` (${goalContext})` : ''}`)
|
|
47
|
+
|
|
48
|
+
return session
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get existing session by ID
|
|
53
|
+
*/
|
|
54
|
+
export function getSession(sessionId: string): SessionTracker | undefined {
|
|
55
|
+
return activeSessions.get(sessionId)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Record an execution in the session
|
|
60
|
+
*/
|
|
61
|
+
export function recordExecution(
|
|
62
|
+
sessionId: string,
|
|
63
|
+
execution: ActivityExecution,
|
|
64
|
+
triggerType: 'goal' | 'nested' | 'boredom' | 'manual' = 'manual',
|
|
65
|
+
parentExecutionId?: string
|
|
66
|
+
): void {
|
|
67
|
+
const session = activeSessions.get(sessionId)
|
|
68
|
+
if (!session) {
|
|
69
|
+
console.warn(`[Session] Session not found: ${sessionId}`)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const item: SessionExecutionItem = {
|
|
74
|
+
activityId: execution.templateId,
|
|
75
|
+
executionId: execution.id,
|
|
76
|
+
order: session.executions.length,
|
|
77
|
+
triggerType,
|
|
78
|
+
parentExecutionId,
|
|
79
|
+
success: execution.status === 'completed',
|
|
80
|
+
durationMs: execution.metrics?.duration || 0,
|
|
81
|
+
costUsd: execution.metrics?.cost || 0,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
session.executions.push(item)
|
|
85
|
+
|
|
86
|
+
console.log(`[Session] Recorded execution ${item.order + 1}: ${execution.templateId} (${execution.status})`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Complete session and report to backend
|
|
91
|
+
*/
|
|
92
|
+
export async function completeSession(
|
|
93
|
+
sessionId: string,
|
|
94
|
+
outcome: 'success' | 'partial' | 'failure' = 'success'
|
|
95
|
+
): Promise<boolean> {
|
|
96
|
+
const session = activeSessions.get(sessionId)
|
|
97
|
+
if (!session) {
|
|
98
|
+
console.warn(`[Session] Session not found: ${sessionId}`)
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Report to MCP backend if enabled
|
|
103
|
+
if (isMCPEnabled() && session.executions.length > 0) {
|
|
104
|
+
const mcp = getMCPClient()
|
|
105
|
+
if (mcp) {
|
|
106
|
+
console.log(`[Session] Reporting ${session.executions.length} executions to backend...`)
|
|
107
|
+
const reported = await mcp.recordExecutionSequence({
|
|
108
|
+
sessionId: session.sessionId,
|
|
109
|
+
goalContext: session.goalContext,
|
|
110
|
+
sequence: session.executions,
|
|
111
|
+
outcome,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
if (reported) {
|
|
115
|
+
console.log(`[Session] ✓ Sequence reported to backend`)
|
|
116
|
+
} else {
|
|
117
|
+
console.warn(`[Session] ⚠ Failed to report sequence to backend`)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Clean up session
|
|
123
|
+
activeSessions.delete(sessionId)
|
|
124
|
+
console.log(`[Session] Completed: ${sessionId} (${outcome})`)
|
|
125
|
+
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get all active sessions
|
|
131
|
+
*/
|
|
132
|
+
export function getActiveSessions(): SessionTracker[] {
|
|
133
|
+
return Array.from(activeSessions.values())
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Clear all sessions (for testing)
|
|
138
|
+
*/
|
|
139
|
+
export function clearSessions(): void {
|
|
140
|
+
activeSessions.clear()
|
|
141
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Extractor - Extract activity templates from improvisation traces
|
|
3
|
+
*
|
|
4
|
+
* The ribosome pattern: successful improvisation → reusable template
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ImprovisationTrace, ImprovisationStep } from './improviser'
|
|
8
|
+
import type { ActivityTemplate, ActivityTask } from './types'
|
|
9
|
+
|
|
10
|
+
export async function extractTemplateFromImprovisation(
|
|
11
|
+
trace: ImprovisationTrace
|
|
12
|
+
): Promise<ActivityTemplate> {
|
|
13
|
+
|
|
14
|
+
console.log(`[TemplateExtractor] Extracting template from ${trace.execution_id} (${trace.steps.length} steps)`)
|
|
15
|
+
|
|
16
|
+
// 1. Identify task boundaries (logical groupings)
|
|
17
|
+
const taskGroups = identifyTaskBoundaries(trace.steps)
|
|
18
|
+
|
|
19
|
+
// 2. Convert each group into a task
|
|
20
|
+
const tasks: ActivityTask[] = taskGroups.map((group, index) => {
|
|
21
|
+
const taskId = `task-${index + 1}`
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
id: taskId,
|
|
25
|
+
description: summarizeTaskGroup(group),
|
|
26
|
+
prompt: {
|
|
27
|
+
template: extractPromptPattern(group),
|
|
28
|
+
variables: identifyVariables(group)
|
|
29
|
+
},
|
|
30
|
+
validation: extractValidation(group),
|
|
31
|
+
dependencies: index > 0 ? [`task-${index}`] : []
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// 3. Create template
|
|
36
|
+
const template: ActivityTemplate = {
|
|
37
|
+
id: `tpl_${Date.now()}_${randomId()}`,
|
|
38
|
+
name: capitalizeGoal(trace.goal),
|
|
39
|
+
category: inferCategory(trace.goal, trace.outcome),
|
|
40
|
+
description: trace.goal,
|
|
41
|
+
tasks,
|
|
42
|
+
variables: [], // Extracted from task prompts
|
|
43
|
+
metadata: {
|
|
44
|
+
extractedFrom: trace.execution_id,
|
|
45
|
+
extractionMethod: 'improvisation',
|
|
46
|
+
firstSuccessMetrics: {
|
|
47
|
+
duration: trace.outcome.total_duration_ms,
|
|
48
|
+
cost: trace.outcome.total_cost,
|
|
49
|
+
steps: trace.steps.length
|
|
50
|
+
},
|
|
51
|
+
createdAt: Date.now(),
|
|
52
|
+
author: 'ribosome'
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`[TemplateExtractor] Template extracted: ${template.id} (${template.tasks.length} tasks)`)
|
|
57
|
+
|
|
58
|
+
return template
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ==========================================================================
|
|
62
|
+
// HELPER FUNCTIONS
|
|
63
|
+
// ==========================================================================
|
|
64
|
+
|
|
65
|
+
function identifyTaskBoundaries(steps: ImprovisationStep[]): ImprovisationStep[][] {
|
|
66
|
+
// Group steps into logical tasks based on:
|
|
67
|
+
// 1. Clusters of similar actions
|
|
68
|
+
// 2. Natural breakpoints (read → analyze → modify pattern)
|
|
69
|
+
// 3. File boundaries (all operations on same file)
|
|
70
|
+
|
|
71
|
+
const groups: ImprovisationStep[][] = []
|
|
72
|
+
let currentGroup: ImprovisationStep[] = []
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < steps.length; i++) {
|
|
75
|
+
const step = steps[i]
|
|
76
|
+
const prevStep = i > 0 ? steps[i - 1] : null
|
|
77
|
+
|
|
78
|
+
currentGroup.push(step)
|
|
79
|
+
|
|
80
|
+
// Break into new group if:
|
|
81
|
+
// - Action changes significantly (read → write)
|
|
82
|
+
// - Working on different file
|
|
83
|
+
// - Group size >= 5 steps
|
|
84
|
+
const shouldBreak =
|
|
85
|
+
currentGroup.length >= 5 ||
|
|
86
|
+
(prevStep && isSignificantActionChange(prevStep.action, step.action)) ||
|
|
87
|
+
(prevStep && isDifferentFile(prevStep, step))
|
|
88
|
+
|
|
89
|
+
if (shouldBreak && i < steps.length - 1) {
|
|
90
|
+
groups.push([...currentGroup])
|
|
91
|
+
currentGroup = []
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (currentGroup.length > 0) {
|
|
96
|
+
groups.push(currentGroup)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return groups
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function summarizeTaskGroup(group: ImprovisationStep[]): string {
|
|
103
|
+
// Use the first step's thought as basis, generalized
|
|
104
|
+
const firstThought = group[0].thought
|
|
105
|
+
const actions = group.map(s => s.action)
|
|
106
|
+
|
|
107
|
+
// Create descriptive summary
|
|
108
|
+
if (actions.every(a => a === 'read')) {
|
|
109
|
+
return 'Analyze and understand current state'
|
|
110
|
+
} else if (actions.includes('write')) {
|
|
111
|
+
return 'Create new files and components'
|
|
112
|
+
} else if (actions.includes('edit')) {
|
|
113
|
+
return 'Modify existing files'
|
|
114
|
+
} else if (actions.includes('bash') && actions.some(a => a.includes('grep'))) {
|
|
115
|
+
return 'Search and verify configuration'
|
|
116
|
+
} else {
|
|
117
|
+
return firstThought.substring(0, 80) // Use LLM's reasoning
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function extractPromptPattern(group: ImprovisationStep[]): string {
|
|
122
|
+
// Convert the improvised steps into a reusable prompt template
|
|
123
|
+
const thoughtsAndActions = group.map(s =>
|
|
124
|
+
`Thought: ${s.thought}\nAction: ${s.action} with ${JSON.stringify(s.params)}`
|
|
125
|
+
).join('\n\n')
|
|
126
|
+
|
|
127
|
+
return `Based on the goal and following the pattern from successful execution:
|
|
128
|
+
|
|
129
|
+
${thoughtsAndActions}
|
|
130
|
+
|
|
131
|
+
Adapt these actions as needed for the specific context, maintaining the same logical flow.`
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function identifyVariables(group: ImprovisationStep[]): Array<{name: string, source: string, type: string}> {
|
|
135
|
+
// Extract variable values that should be parameterized
|
|
136
|
+
const varsMap = new Map<string, {name: string, source: string, type: string}>()
|
|
137
|
+
|
|
138
|
+
group.forEach(step => {
|
|
139
|
+
// Look for file paths, names, etc that should be variables
|
|
140
|
+
if (step.params.file_path) {
|
|
141
|
+
varsMap.set('target_file', {
|
|
142
|
+
name: 'target_file',
|
|
143
|
+
source: 'variable',
|
|
144
|
+
type: 'string'
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
if (step.params.directory) {
|
|
148
|
+
varsMap.set('target_directory', {
|
|
149
|
+
name: 'target_directory',
|
|
150
|
+
source: 'variable',
|
|
151
|
+
type: 'string'
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
if (step.params.content && typeof step.params.content === 'string') {
|
|
155
|
+
// Check if content looks like it contains variables
|
|
156
|
+
const contentStr = step.params.content as string
|
|
157
|
+
if (contentStr.includes('{{') || contentStr.includes('${')) {
|
|
158
|
+
varsMap.set('content_template', {
|
|
159
|
+
name: 'content_template',
|
|
160
|
+
source: 'variable',
|
|
161
|
+
type: 'string'
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
return Array.from(varsMap.values())
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function extractValidation(group: ImprovisationStep[]): any {
|
|
171
|
+
// Extract validation based on what the improvisation checked
|
|
172
|
+
const validation: any = {}
|
|
173
|
+
|
|
174
|
+
const filesCreated = group
|
|
175
|
+
.filter(s => s.action === 'write')
|
|
176
|
+
.map(s => s.params.file_path)
|
|
177
|
+
.filter(Boolean) as string[]
|
|
178
|
+
|
|
179
|
+
if (filesCreated.length > 0) {
|
|
180
|
+
validation.requiredFiles = filesCreated
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const filesModified = group
|
|
184
|
+
.filter(s => s.action === 'edit')
|
|
185
|
+
.map(s => s.params.file_path)
|
|
186
|
+
.filter(Boolean) as string[]
|
|
187
|
+
|
|
188
|
+
if (filesModified.length > 0) {
|
|
189
|
+
validation.modifiedFiles = filesModified
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Check if any bash commands verified success
|
|
193
|
+
const verificationCommands = group
|
|
194
|
+
.filter(s => s.action === 'bash' && s.result.success)
|
|
195
|
+
.map(s => s.params.command)
|
|
196
|
+
.filter(Boolean) as string[]
|
|
197
|
+
|
|
198
|
+
if (verificationCommands.length > 0) {
|
|
199
|
+
validation.verificationCommands = verificationCommands
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return Object.keys(validation).length > 0 ? validation : undefined
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function capitalizeGoal(goal: string): string {
|
|
206
|
+
// Convert goal into title case template name
|
|
207
|
+
return goal
|
|
208
|
+
.split(' ')
|
|
209
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
210
|
+
.join(' ')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function inferCategory(goal: string, outcome: any): 'feature' | 'bugfix' | 'refactor' | 'tool' | 'infrastructure' {
|
|
214
|
+
const goalLower = goal.toLowerCase()
|
|
215
|
+
|
|
216
|
+
if (goalLower.includes('fix') || goalLower.includes('bug') || goalLower.includes('error')) {
|
|
217
|
+
return 'bugfix'
|
|
218
|
+
} else if (goalLower.includes('refactor') || goalLower.includes('clean') || goalLower.includes('improve')) {
|
|
219
|
+
return 'refactor'
|
|
220
|
+
} else if (goalLower.includes('tool') || goalLower.includes('script') || goalLower.includes('automation')) {
|
|
221
|
+
return 'tool'
|
|
222
|
+
} else if (goalLower.includes('deploy') || goalLower.includes('infrastructure') || goalLower.includes('setup')) {
|
|
223
|
+
return 'infrastructure'
|
|
224
|
+
} else {
|
|
225
|
+
return 'feature'
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isSignificantActionChange(prevAction: string, currentAction: string): boolean {
|
|
230
|
+
// Define significant transitions that indicate task boundaries
|
|
231
|
+
const transitions = [
|
|
232
|
+
['read', 'write'],
|
|
233
|
+
['read', 'edit'],
|
|
234
|
+
['bash', 'write'],
|
|
235
|
+
['bash', 'edit'],
|
|
236
|
+
['write', 'bash'],
|
|
237
|
+
['edit', 'bash']
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
return transitions.some(([from, to]) =>
|
|
241
|
+
prevAction === from && currentAction === to
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function isDifferentFile(prevStep: ImprovisationStep, currentStep: ImprovisationStep): boolean {
|
|
246
|
+
const prevFile = prevStep.params.file_path as string | undefined
|
|
247
|
+
const currentFile = currentStep.params.file_path as string | undefined
|
|
248
|
+
|
|
249
|
+
if (!prevFile || !currentFile) return false
|
|
250
|
+
|
|
251
|
+
return prevFile !== currentFile
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function randomId(): string {
|
|
255
|
+
return Math.random().toString(36).substring(7)
|
|
256
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Generator - Create activity templates from execution traces
|
|
3
|
+
*
|
|
4
|
+
* This module enables self-development: successful goal executions
|
|
5
|
+
* are automatically converted into reusable activity templates.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ActivityExecution, ActivityTemplate, ActivityTask, ExecutedTask, TaskValidation } from "./types"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate activity template from successful execution
|
|
12
|
+
*/
|
|
13
|
+
export function assembleTemplateFromExecution(
|
|
14
|
+
execution: ActivityExecution,
|
|
15
|
+
templateName: string,
|
|
16
|
+
category: "feature" | "bugfix" | "refactor" | "tool" | "infrastructure"
|
|
17
|
+
): ActivityTemplate {
|
|
18
|
+
if (!execution.executionTrace) {
|
|
19
|
+
throw new Error("Execution trace not available. Enable recordExecutionTrace in ExecutorConfig.")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const trace = execution.executionTrace
|
|
23
|
+
|
|
24
|
+
// Extract tasks from execution trace
|
|
25
|
+
const tasks: ActivityTask[] = trace.tasks.map((executedTask, index) => {
|
|
26
|
+
// Find corresponding TaskResult to get token information
|
|
27
|
+
const taskResult = execution.taskResults.find(tr => tr.taskId === executedTask.id)
|
|
28
|
+
const outputTokens = taskResult?.tokens?.output || 16000
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
id: `task-${index + 1}`,
|
|
32
|
+
subagent: "general",
|
|
33
|
+
description: executedTask.description || inferTaskDescription(executedTask),
|
|
34
|
+
dependencies: index > 0 ? [`task-${index}`] : [],
|
|
35
|
+
prompt: {
|
|
36
|
+
template: executedTask.actualPrompt,
|
|
37
|
+
variables: [], // No variables in generated template (prompt is already substituted)
|
|
38
|
+
// Use output tokens from execution, capped at model limit (64000 for Claude Sonnet 4)
|
|
39
|
+
// Fallback to 16000 if no token data available
|
|
40
|
+
maxTokens: Math.min(outputTokens, 64000),
|
|
41
|
+
compressionStrategy: "filter" as const,
|
|
42
|
+
},
|
|
43
|
+
validation: extractValidation(executedTask),
|
|
44
|
+
retry: {
|
|
45
|
+
maxAttempts: 2,
|
|
46
|
+
strategy: "simple" as const,
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
id: `tpl_${Date.now()}_${Math.random().toString(36).substring(7)}`,
|
|
53
|
+
name: templateName,
|
|
54
|
+
description: trace.goalContext?.goal || "Activity generated from successful execution",
|
|
55
|
+
category,
|
|
56
|
+
tasks,
|
|
57
|
+
variables: [], // No template-level variables (prompts already substituted)
|
|
58
|
+
metadata: {
|
|
59
|
+
generatedFrom: "execution",
|
|
60
|
+
sourceExecutionId: execution.id,
|
|
61
|
+
firstExecutionMetrics: {
|
|
62
|
+
duration: execution.metrics?.duration || 0,
|
|
63
|
+
cost: execution.metrics?.cost || 0,
|
|
64
|
+
tokens: execution.metrics?.totalTokens || { input: 0, output: 0 },
|
|
65
|
+
status: execution.status,
|
|
66
|
+
},
|
|
67
|
+
createdAt: Date.now(),
|
|
68
|
+
author: "ribosome",
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extract validation from executed task
|
|
75
|
+
*
|
|
76
|
+
* Maps executedTask.validationResults to TaskValidation structure.
|
|
77
|
+
* Returns undefined if no validation was performed.
|
|
78
|
+
*/
|
|
79
|
+
function extractValidation(executedTask: ExecutedTask): TaskValidation | undefined {
|
|
80
|
+
if (!executedTask.validationResults) {
|
|
81
|
+
return undefined
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const validation: TaskValidation = {}
|
|
85
|
+
|
|
86
|
+
// Extract required files (only include files that existed)
|
|
87
|
+
if (executedTask.validationResults.requiredFiles) {
|
|
88
|
+
const existingFiles = executedTask.validationResults.requiredFiles
|
|
89
|
+
.filter(f => f.exists)
|
|
90
|
+
.map(f => f.path)
|
|
91
|
+
|
|
92
|
+
if (existingFiles.length > 0) {
|
|
93
|
+
validation.requiredFiles = existingFiles
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Extract required patterns (need to guess file from pattern context)
|
|
98
|
+
// Since executedTask.validationResults doesn't have file info, we skip patterns
|
|
99
|
+
// This is a known limitation - validation patterns in traces don't include file paths
|
|
100
|
+
|
|
101
|
+
// Extract forbidden patterns (same limitation)
|
|
102
|
+
|
|
103
|
+
// No command extraction from validationResults
|
|
104
|
+
// Commands would need to be in the original task definition
|
|
105
|
+
|
|
106
|
+
return Object.keys(validation).length > 0 ? validation : undefined
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Infer task description from execution
|
|
111
|
+
*/
|
|
112
|
+
function inferTaskDescription(executedTask: ExecutedTask): string {
|
|
113
|
+
// Try to extract from prompt
|
|
114
|
+
const prompt = executedTask.actualPrompt
|
|
115
|
+
const lines = prompt.split("\n")
|
|
116
|
+
|
|
117
|
+
// Look for task markers
|
|
118
|
+
const taskLine = lines.find(l => l.match(/your task|objective|goal|\*\*your task\*\*/i))
|
|
119
|
+
if (taskLine) {
|
|
120
|
+
return taskLine.replace(/.*:\s*/i, "").replace(/\*/g, "").trim().substring(0, 100)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fallback: summarize tool calls
|
|
124
|
+
const tools = executedTask.toolCalls.map(tc => tc.name).filter((t, i, arr) => arr.indexOf(t) === i)
|
|
125
|
+
if (tools.length > 0) {
|
|
126
|
+
return `Execute task using: ${tools.join(", ")}`
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return "Execute task"
|
|
130
|
+
}
|