@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.
Files changed (174) hide show
  1. package/ARCHITECTURE.md +255 -0
  2. package/CHANGELOG.md +112 -0
  3. package/README.md +380 -0
  4. package/bin/minibob.js +36 -0
  5. package/dist/acp-gossip.d.ts +72 -0
  6. package/dist/acp-gossip.d.ts.map +1 -0
  7. package/dist/acp-gossip.js +156 -0
  8. package/dist/acp-gossip.js.map +1 -0
  9. package/dist/acp.d.ts +62 -0
  10. package/dist/acp.d.ts.map +1 -0
  11. package/dist/acp.js +292 -0
  12. package/dist/acp.js.map +1 -0
  13. package/dist/activity.d.ts +157 -0
  14. package/dist/activity.d.ts.map +1 -0
  15. package/dist/activity.js +518 -0
  16. package/dist/activity.js.map +1 -0
  17. package/dist/agent-runtime.d.ts +104 -0
  18. package/dist/agent-runtime.d.ts.map +1 -0
  19. package/dist/boredom.d.ts +125 -0
  20. package/dist/boredom.d.ts.map +1 -0
  21. package/dist/boredom.js +244 -0
  22. package/dist/boredom.js.map +1 -0
  23. package/dist/cli/acp-server.d.ts +23 -0
  24. package/dist/cli/acp-server.d.ts.map +1 -0
  25. package/dist/cli/burrow.d.ts +26 -0
  26. package/dist/cli/burrow.d.ts.map +1 -0
  27. package/dist/cli/doctor.d.ts +22 -0
  28. package/dist/cli/doctor.d.ts.map +1 -0
  29. package/dist/cli/goal.d.ts +22 -0
  30. package/dist/cli/goal.d.ts.map +1 -0
  31. package/dist/cli/index.d.ts +47 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/instance-registry.d.ts +78 -0
  34. package/dist/cli/instance-registry.d.ts.map +1 -0
  35. package/dist/cli/observe.d.ts +35 -0
  36. package/dist/cli/observe.d.ts.map +1 -0
  37. package/dist/cli/vessel.d.ts +14 -0
  38. package/dist/cli/vessel.d.ts.map +1 -0
  39. package/dist/composition-observer.d.ts +96 -0
  40. package/dist/composition-observer.d.ts.map +1 -0
  41. package/dist/config.d.ts +36 -0
  42. package/dist/config.d.ts.map +1 -0
  43. package/dist/config.js +128 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/docker/Dockerfile +35 -0
  46. package/dist/environment.d.ts +72 -0
  47. package/dist/environment.d.ts.map +1 -0
  48. package/dist/environment.js +142 -0
  49. package/dist/environment.js.map +1 -0
  50. package/dist/goal-processor.d.ts +165 -0
  51. package/dist/goal-processor.d.ts.map +1 -0
  52. package/dist/helm/minibob-cluster/Chart.yaml +13 -0
  53. package/dist/helm/minibob-cluster/templates/_helpers.tpl +60 -0
  54. package/dist/helm/minibob-cluster/templates/configmap.yaml +11 -0
  55. package/dist/helm/minibob-cluster/templates/deployment.yaml +108 -0
  56. package/dist/helm/minibob-cluster/templates/secret.yaml +10 -0
  57. package/dist/helm/minibob-cluster/templates/service.yaml +37 -0
  58. package/dist/helm/minibob-cluster/values-local.yaml +41 -0
  59. package/dist/helm/minibob-cluster/values-production.yaml +57 -0
  60. package/dist/helm/minibob-cluster/values-testing-cluster.yaml +43 -0
  61. package/dist/helm/minibob-cluster/values.yaml +127 -0
  62. package/dist/improviser.d.ts +74 -0
  63. package/dist/improviser.d.ts.map +1 -0
  64. package/dist/impulse-filter.d.ts +74 -0
  65. package/dist/impulse-filter.d.ts.map +1 -0
  66. package/dist/impulse.d.ts +92 -0
  67. package/dist/impulse.d.ts.map +1 -0
  68. package/dist/impulse.js +234 -0
  69. package/dist/impulse.js.map +1 -0
  70. package/dist/lib.d.ts +29 -0
  71. package/dist/lib.d.ts.map +1 -0
  72. package/dist/lib.js +18561 -0
  73. package/dist/lib.js.map +98 -0
  74. package/dist/lifecycle-hooks.d.ts +99 -0
  75. package/dist/lifecycle-hooks.d.ts.map +1 -0
  76. package/dist/lifecycle-hooks.js +135 -0
  77. package/dist/lifecycle-hooks.js.map +1 -0
  78. package/dist/llm.d.ts +31 -0
  79. package/dist/llm.d.ts.map +1 -0
  80. package/dist/llm.js +349 -0
  81. package/dist/llm.js.map +1 -0
  82. package/dist/mcp-activity-bridge.d.ts +66 -0
  83. package/dist/mcp-activity-bridge.d.ts.map +1 -0
  84. package/dist/mcp-activity-bridge.js +126 -0
  85. package/dist/mcp-activity-bridge.js.map +1 -0
  86. package/dist/mcp.d.ts +216 -0
  87. package/dist/mcp.d.ts.map +1 -0
  88. package/dist/mcp.js +292 -0
  89. package/dist/mcp.js.map +1 -0
  90. package/dist/memory-agent.d.ts +92 -0
  91. package/dist/memory-agent.d.ts.map +1 -0
  92. package/dist/memory-agent.js +277 -0
  93. package/dist/memory-agent.js.map +1 -0
  94. package/dist/runtime-mapping.d.ts +97 -0
  95. package/dist/runtime-mapping.d.ts.map +1 -0
  96. package/dist/search-first-executor.d.ts +113 -0
  97. package/dist/search-first-executor.d.ts.map +1 -0
  98. package/dist/session.d.ts +48 -0
  99. package/dist/session.d.ts.map +1 -0
  100. package/dist/template-extractor.d.ts +9 -0
  101. package/dist/template-extractor.d.ts.map +1 -0
  102. package/dist/template-generator.d.ts +12 -0
  103. package/dist/template-generator.d.ts.map +1 -0
  104. package/dist/tools.d.ts +58 -0
  105. package/dist/tools.d.ts.map +1 -0
  106. package/dist/tools.js +771 -0
  107. package/dist/tools.js.map +1 -0
  108. package/dist/types.d.ts +503 -0
  109. package/dist/types.d.ts.map +1 -0
  110. package/dist/types.js +8 -0
  111. package/dist/types.js.map +1 -0
  112. package/dist/understanding/analyzer.d.ts +55 -0
  113. package/dist/understanding/analyzer.d.ts.map +1 -0
  114. package/dist/understanding/explorer.d.ts +73 -0
  115. package/dist/understanding/explorer.d.ts.map +1 -0
  116. package/dist/understanding/index.d.ts +7 -0
  117. package/dist/understanding/index.d.ts.map +1 -0
  118. package/dist/understanding/types.d.ts +136 -0
  119. package/dist/understanding/types.d.ts.map +1 -0
  120. package/dist/validation.d.ts +29 -0
  121. package/dist/validation.d.ts.map +1 -0
  122. package/dist/validation.js +106 -0
  123. package/dist/validation.js.map +1 -0
  124. package/dist/vessel-bootstrap.d.ts +190 -0
  125. package/dist/vessel-bootstrap.d.ts.map +1 -0
  126. package/dist/vessel-registry.d.ts +229 -0
  127. package/dist/vessel-registry.d.ts.map +1 -0
  128. package/index.ts +1329 -0
  129. package/package.json +54 -0
  130. package/src/acp-gossip.ts +193 -0
  131. package/src/acp.ts +362 -0
  132. package/src/activity.ts +1464 -0
  133. package/src/agent-runtime.ts +365 -0
  134. package/src/boredom.ts +423 -0
  135. package/src/cli/acp-server.ts +377 -0
  136. package/src/cli/burrow.ts +896 -0
  137. package/src/cli/doctor.ts +526 -0
  138. package/src/cli/goal.ts +224 -0
  139. package/src/cli/index.ts +147 -0
  140. package/src/cli/instance-registry.ts +271 -0
  141. package/src/cli/observe.ts +682 -0
  142. package/src/cli/vessel.ts +287 -0
  143. package/src/components/SystemOverview.tsx +331 -0
  144. package/src/composition-observer.ts +449 -0
  145. package/src/config.ts +172 -0
  146. package/src/environment.ts +167 -0
  147. package/src/goal-processor.ts +654 -0
  148. package/src/improviser.ts +591 -0
  149. package/src/impulse-filter.ts +273 -0
  150. package/src/impulse.ts +311 -0
  151. package/src/lib.ts +147 -0
  152. package/src/lifecycle-hooks.ts +181 -0
  153. package/src/llm.ts +434 -0
  154. package/src/mcp-activity-bridge.ts +158 -0
  155. package/src/mcp.ts +747 -0
  156. package/src/memory-agent.ts +316 -0
  157. package/src/runtime-mapping.ts +527 -0
  158. package/src/search-first-executor.ts +666 -0
  159. package/src/session.ts +141 -0
  160. package/src/template-extractor.ts +256 -0
  161. package/src/template-generator.ts +130 -0
  162. package/src/tools.ts +924 -0
  163. package/src/types.ts +497 -0
  164. package/src/understanding/analyzer.ts +354 -0
  165. package/src/understanding/explorer.ts +488 -0
  166. package/src/understanding/index.ts +27 -0
  167. package/src/understanding/types.ts +153 -0
  168. package/src/validation.ts +125 -0
  169. package/src/vessel-bootstrap.ts +440 -0
  170. package/src/vessel-registry.ts +621 -0
  171. package/templates/core/edit-file.json +85 -0
  172. package/templates/understanding/diagnose-problem.json +32 -0
  173. package/templates/understanding/explore-codebase-v2.json +57 -0
  174. 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
+ }