@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
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionMemoryAgent - Automatic context preparation for activity execution
|
|
3
|
+
*
|
|
4
|
+
* Moved from metabob-opencode to minibob as part of library integration.
|
|
5
|
+
* This version is simplified to work with minibob's minimal dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Design goals:
|
|
8
|
+
* - Fast analysis (<2s with Claude Haiku)
|
|
9
|
+
* - Automatic impulse creation based on intent
|
|
10
|
+
* - Self-contained (no opencode dependencies)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Impulse } from "./types"
|
|
14
|
+
import { createAnthropicClient } from "./llm"
|
|
15
|
+
import { createImpulse, getImpulseStore } from "./impulse"
|
|
16
|
+
import z from "zod"
|
|
17
|
+
|
|
18
|
+
export namespace SessionMemoryAgent {
|
|
19
|
+
/**
|
|
20
|
+
* User intent classification schema
|
|
21
|
+
*/
|
|
22
|
+
const IntentSchema = z.object({
|
|
23
|
+
type: z.enum(["code_fix", "feature_request", "question", "refactor", "exploration", "other"]),
|
|
24
|
+
confidence: z.number().min(0).max(1),
|
|
25
|
+
reasoning: z.string(),
|
|
26
|
+
suggestedImpulses: z.array(
|
|
27
|
+
z.object({
|
|
28
|
+
id: z.string(),
|
|
29
|
+
type: z.enum(["file", "metabobIssue", "history", "memo", "bashOutput"]),
|
|
30
|
+
description: z.string(),
|
|
31
|
+
priority: z.enum(["critical", "high", "medium", "low"]),
|
|
32
|
+
budget: z.number().positive(),
|
|
33
|
+
pointer: z.union([
|
|
34
|
+
z.object({
|
|
35
|
+
type: z.literal("file"),
|
|
36
|
+
path: z.string(),
|
|
37
|
+
offset: z.number().optional(),
|
|
38
|
+
limit: z.number().optional(),
|
|
39
|
+
}),
|
|
40
|
+
z.object({
|
|
41
|
+
type: z.literal("memo"),
|
|
42
|
+
content: z.string(),
|
|
43
|
+
}),
|
|
44
|
+
z.object({
|
|
45
|
+
type: z.literal("custom"),
|
|
46
|
+
resolver: z.string(),
|
|
47
|
+
data: z.record(z.string(), z.unknown()),
|
|
48
|
+
}),
|
|
49
|
+
]),
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
export type Intent = z.infer<typeof IntentSchema>
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Configuration for session memory agent
|
|
58
|
+
*/
|
|
59
|
+
export interface Config {
|
|
60
|
+
enabled: boolean
|
|
61
|
+
timeout: number // Max time for analysis (ms)
|
|
62
|
+
model: string // Model to use (e.g., "claude-3-5-haiku-20241022")
|
|
63
|
+
defaultBudget: number // Default token budget per impulse
|
|
64
|
+
maxImpulses: number // Max impulses per turn
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const DEFAULT_CONFIG: Config = {
|
|
68
|
+
enabled: true,
|
|
69
|
+
timeout: 3000,
|
|
70
|
+
model: "claude-3-5-haiku-20241022",
|
|
71
|
+
defaultBudget: 2000,
|
|
72
|
+
maxImpulses: 5,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Analyze user intent from prompt text
|
|
77
|
+
* Returns intent classification with suggested impulses
|
|
78
|
+
*/
|
|
79
|
+
export async function analyzeIntent(input: {
|
|
80
|
+
promptText: string
|
|
81
|
+
workingDirectory?: string
|
|
82
|
+
config?: Partial<Config>
|
|
83
|
+
}): Promise<Intent> {
|
|
84
|
+
const cfg = { ...DEFAULT_CONFIG, ...input.config }
|
|
85
|
+
const start = Date.now()
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
console.log("[MemoryAgent] Analyzing intent", {
|
|
89
|
+
promptLength: input.promptText.length,
|
|
90
|
+
model: cfg.model,
|
|
91
|
+
timeout: cfg.timeout,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
95
|
+
throw new Error("ANTHROPIC_API_KEY environment variable not set")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Create LLM client
|
|
99
|
+
const llmClient = createAnthropicClient(process.env.ANTHROPIC_API_KEY)
|
|
100
|
+
|
|
101
|
+
// Build system prompt
|
|
102
|
+
const systemPrompt = `You are the Session Memory Agent - an intent analyzer for conversational coding assistance.
|
|
103
|
+
|
|
104
|
+
## Your Role
|
|
105
|
+
|
|
106
|
+
Analyze user messages to understand their intent and recommend context (impulses) to load.
|
|
107
|
+
You DO NOT execute tasks. You ONLY analyze intent and suggest what context might be helpful.
|
|
108
|
+
|
|
109
|
+
For trivial messages (greetings, acknowledgments), classify as type="other" with empty suggestedImpulses array.
|
|
110
|
+
|
|
111
|
+
## Intent Types
|
|
112
|
+
|
|
113
|
+
- **code_fix**: User reports a bug, error, or unexpected behavior
|
|
114
|
+
- **feature_request**: User wants to add/modify functionality
|
|
115
|
+
- **question**: User asks about code, architecture, or how things work
|
|
116
|
+
- **refactor**: User wants to improve existing code structure
|
|
117
|
+
- **exploration**: User is exploring codebase or investigating
|
|
118
|
+
- **other**: Greetings, acknowledgments, or messages that don't need coding context
|
|
119
|
+
|
|
120
|
+
## Impulse Types
|
|
121
|
+
|
|
122
|
+
1. **file**: Specific source files
|
|
123
|
+
- For code_fix: error file, related tests
|
|
124
|
+
- For feature_request: files to modify, similar features
|
|
125
|
+
- For question: files being asked about
|
|
126
|
+
|
|
127
|
+
2. **memo**: Inline context notes
|
|
128
|
+
- For important constraints, requirements, or decisions
|
|
129
|
+
- Short-term memory across turns
|
|
130
|
+
|
|
131
|
+
3. **custom**: Custom pointer types for specialized resolvers
|
|
132
|
+
|
|
133
|
+
## Budget Guidelines
|
|
134
|
+
|
|
135
|
+
- file: 1500-3000 tokens (depends on file size)
|
|
136
|
+
- memo: 200-500 tokens
|
|
137
|
+
- custom: Varies by resolver
|
|
138
|
+
|
|
139
|
+
## Priority Guidelines
|
|
140
|
+
|
|
141
|
+
- **critical**: Must-have for understanding request
|
|
142
|
+
- **high**: Very important for request
|
|
143
|
+
- **medium**: Helpful but not essential
|
|
144
|
+
- **low**: Background context
|
|
145
|
+
|
|
146
|
+
## Output Format
|
|
147
|
+
|
|
148
|
+
Return JSON with:
|
|
149
|
+
- type: Intent classification
|
|
150
|
+
- confidence: 0-1 (how confident you are)
|
|
151
|
+
- reasoning: Why you classified this way (1 sentence)
|
|
152
|
+
- suggestedImpulses: Array of recommended impulses (0-${cfg.maxImpulses})
|
|
153
|
+
|
|
154
|
+
## Examples
|
|
155
|
+
|
|
156
|
+
**Input**: "Fix the TypeError in calculator.ts"
|
|
157
|
+
**Output**:
|
|
158
|
+
{
|
|
159
|
+
"type": "code_fix",
|
|
160
|
+
"confidence": 0.95,
|
|
161
|
+
"reasoning": "User reports specific error with file reference",
|
|
162
|
+
"suggestedImpulses": [
|
|
163
|
+
{
|
|
164
|
+
"id": "errorFile",
|
|
165
|
+
"type": "file",
|
|
166
|
+
"description": "File containing the error",
|
|
167
|
+
"priority": "high",
|
|
168
|
+
"budget": 2000,
|
|
169
|
+
"pointer": { "type": "file", "path": "calculator.ts" }
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
**Input**: "hi"
|
|
175
|
+
**Output**:
|
|
176
|
+
{
|
|
177
|
+
"type": "other",
|
|
178
|
+
"confidence": 1.0,
|
|
179
|
+
"reasoning": "Simple greeting, no coding context needed",
|
|
180
|
+
"suggestedImpulses": []
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
Be conservative with impulses. Quality over quantity. Only suggest what's clearly needed.`
|
|
184
|
+
|
|
185
|
+
// Call LLM
|
|
186
|
+
const result = await llmClient.complete({
|
|
187
|
+
model: cfg.model,
|
|
188
|
+
messages: [
|
|
189
|
+
{
|
|
190
|
+
role: "system",
|
|
191
|
+
content: systemPrompt + "\n\nIMPORTANT: Return ONLY valid JSON matching this schema. No markdown, no explanations.",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
role: "user",
|
|
195
|
+
content: `Analyze this user message and suggest context impulses:\n\n"${input.promptText}"\n\nReturn JSON only.`,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
maxTokens: 2000,
|
|
199
|
+
temperature: 0.2,
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// Parse JSON response
|
|
203
|
+
let intent: Intent
|
|
204
|
+
try {
|
|
205
|
+
// Remove markdown code blocks if present
|
|
206
|
+
let jsonStr = result.content.trim()
|
|
207
|
+
if (jsonStr.startsWith("```json")) {
|
|
208
|
+
jsonStr = jsonStr.slice(7)
|
|
209
|
+
}
|
|
210
|
+
if (jsonStr.startsWith("```")) {
|
|
211
|
+
jsonStr = jsonStr.slice(3)
|
|
212
|
+
}
|
|
213
|
+
if (jsonStr.endsWith("```")) {
|
|
214
|
+
jsonStr = jsonStr.slice(0, -3)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const parsed = JSON.parse(jsonStr.trim())
|
|
218
|
+
intent = IntentSchema.parse(parsed)
|
|
219
|
+
} catch (parseError) {
|
|
220
|
+
console.warn("[MemoryAgent] Failed to parse LLM response as JSON", {
|
|
221
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
222
|
+
response: result.content.slice(0, 200),
|
|
223
|
+
})
|
|
224
|
+
throw new Error("Failed to parse intent analysis response")
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log("[MemoryAgent] Analysis complete", {
|
|
228
|
+
type: intent.type,
|
|
229
|
+
confidence: intent.confidence,
|
|
230
|
+
impulseCount: intent.suggestedImpulses.length,
|
|
231
|
+
elapsed: Date.now() - start,
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
return intent
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.warn("[MemoryAgent] Analysis failed, returning fallback", {
|
|
237
|
+
error: error instanceof Error ? error.message : String(error),
|
|
238
|
+
elapsed: Date.now() - start,
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// Fallback: return "other" with no impulses
|
|
242
|
+
return {
|
|
243
|
+
type: "other",
|
|
244
|
+
confidence: 0,
|
|
245
|
+
reasoning: "Analysis failed, proceeding without context",
|
|
246
|
+
suggestedImpulses: [],
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Prepare impulses based on intent analysis
|
|
253
|
+
* Creates impulses in the impulse store
|
|
254
|
+
*/
|
|
255
|
+
export async function prepare(input: { intent: Intent }): Promise<{
|
|
256
|
+
impulsesCreated: number
|
|
257
|
+
impulseIds: string[]
|
|
258
|
+
}> {
|
|
259
|
+
const start = Date.now()
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const store = getImpulseStore()
|
|
263
|
+
let created = 0
|
|
264
|
+
const impulseIds: string[] = []
|
|
265
|
+
|
|
266
|
+
// Create impulses from suggestions
|
|
267
|
+
for (const suggestion of input.intent.suggestedImpulses) {
|
|
268
|
+
// Check if impulse already exists
|
|
269
|
+
const existing = store.get(suggestion.id)
|
|
270
|
+
if (existing) {
|
|
271
|
+
console.log("[MemoryAgent] Impulse already exists, skipping", { impulseId: suggestion.id })
|
|
272
|
+
continue
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Create impulse (match Impulse type minus loaded/createdAt)
|
|
276
|
+
const impulse = createImpulse({
|
|
277
|
+
id: suggestion.id,
|
|
278
|
+
pointer: suggestion.pointer as any,
|
|
279
|
+
budget: suggestion.budget,
|
|
280
|
+
priority: suggestion.priority,
|
|
281
|
+
tags: ["memory-agent", `type:${suggestion.type}`],
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
created++
|
|
285
|
+
impulseIds.push(impulse.id)
|
|
286
|
+
|
|
287
|
+
console.log("[MemoryAgent] Impulse created", {
|
|
288
|
+
impulseId: impulse.id,
|
|
289
|
+
type: suggestion.type,
|
|
290
|
+
priority: suggestion.priority,
|
|
291
|
+
budget: suggestion.budget,
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
console.log("[MemoryAgent] Preparation complete", {
|
|
296
|
+
created,
|
|
297
|
+
elapsed: Date.now() - start,
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
impulsesCreated: created,
|
|
302
|
+
impulseIds,
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error("[MemoryAgent] Preparation failed", {
|
|
306
|
+
error: error instanceof Error ? error.message : String(error),
|
|
307
|
+
elapsed: Date.now() - start,
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
impulsesCreated: 0,
|
|
312
|
+
impulseIds: [],
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|