gsd-pi 2.24.0 → 2.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/headless.js +24 -4
- package/dist/models-resolver.d.ts +0 -11
- package/dist/models-resolver.js +0 -15
- package/dist/resource-loader.d.ts +0 -1
- package/dist/resource-loader.js +0 -9
- package/dist/resources/GSD-WORKFLOW.md +12 -9
- package/dist/resources/extensions/async-jobs/index.ts +9 -1
- package/dist/resources/extensions/bg-shell/index.ts +3 -2
- package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
- package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
- package/dist/resources/extensions/gsd/activity-log.ts +5 -3
- package/dist/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +7 -4
- package/dist/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/dist/resources/extensions/gsd/auto.ts +265 -48
- package/dist/resources/extensions/gsd/cache.ts +3 -1
- package/dist/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/dist/resources/extensions/gsd/doctor.ts +26 -1
- package/dist/resources/extensions/gsd/files.ts +13 -2
- package/dist/resources/extensions/gsd/git-service.ts +74 -14
- package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +54 -22
- package/dist/resources/extensions/gsd/index.ts +62 -8
- package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/dist/resources/extensions/gsd/memory-store.ts +441 -0
- package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
- package/dist/resources/extensions/gsd/migrate/writer.ts +39 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/dist/resources/extensions/gsd/preferences.ts +2 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/dist/resources/extensions/gsd/prompts/discuss.md +5 -5
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +3 -3
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/dist/resources/extensions/gsd/state.ts +17 -6
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
- package/dist/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
- package/dist/resources/extensions/gsd/types.ts +2 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/dist/resources/extensions/gsd/worktree.ts +9 -2
- package/dist/resources/extensions/search-the-web/native-search.ts +19 -5
- package/dist/resources/extensions/shared/path-display.ts +19 -0
- package/package.json +1 -6
- package/packages/pi-agent-core/dist/agent-loop.js +2 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +64 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/mistral.js +3 -0
- package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +23 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +65 -1
- package/packages/pi-ai/src/providers/mistral.ts +3 -0
- package/packages/pi-ai/src/types.ts +19 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +12 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +7 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +8 -3
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +2 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +5 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +41 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +301 -62
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +5 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +135 -30
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts +8 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js +60 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js +32 -6
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts +34 -0
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js +36 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js.map +1 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -0
- package/packages/pi-coding-agent/src/core/keybindings.ts +1 -1
- package/packages/pi-coding-agent/src/core/lsp/client.ts +11 -1
- package/packages/pi-coding-agent/src/core/lsp/index.ts +7 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +2 -1
- package/packages/pi-coding-agent/src/index.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +347 -62
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +124 -4
- package/packages/pi-coding-agent/src/tests/path-display.test.ts +85 -0
- package/packages/pi-coding-agent/src/utils/clipboard-image.ts +33 -6
- package/packages/pi-coding-agent/src/utils/path-display.ts +36 -0
- package/src/resources/GSD-WORKFLOW.md +12 -9
- package/src/resources/extensions/async-jobs/index.ts +9 -1
- package/src/resources/extensions/bg-shell/index.ts +3 -2
- package/src/resources/extensions/bg-shell/overlay.ts +18 -17
- package/src/resources/extensions/get-secrets-from-user.ts +5 -23
- package/src/resources/extensions/gsd/activity-log.ts +5 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +7 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/src/resources/extensions/gsd/auto.ts +265 -48
- package/src/resources/extensions/gsd/cache.ts +3 -1
- package/src/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/src/resources/extensions/gsd/doctor.ts +26 -1
- package/src/resources/extensions/gsd/files.ts +13 -2
- package/src/resources/extensions/gsd/git-service.ts +74 -14
- package/src/resources/extensions/gsd/gsd-db.ts +78 -1
- package/src/resources/extensions/gsd/guided-flow.ts +54 -22
- package/src/resources/extensions/gsd/index.ts +62 -8
- package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/src/resources/extensions/gsd/memory-store.ts +441 -0
- package/src/resources/extensions/gsd/migrate/command.ts +2 -2
- package/src/resources/extensions/gsd/migrate/writer.ts +39 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/src/resources/extensions/gsd/preferences.ts +2 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/src/resources/extensions/gsd/prompts/discuss.md +5 -5
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +3 -3
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/src/resources/extensions/gsd/state.ts +17 -6
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/src/resources/extensions/gsd/triage-ui.ts +1 -1
- package/src/resources/extensions/gsd/types.ts +2 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/src/resources/extensions/gsd/worktree.ts +9 -2
- package/src/resources/extensions/search-the-web/native-search.ts +19 -5
- package/src/resources/extensions/shared/path-display.ts +19 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// GSD Memory Extractor — Background LLM extraction from activity logs
|
|
2
|
+
//
|
|
3
|
+
// After each unit completes, extracts durable knowledge from the session
|
|
4
|
+
// transcript and stores it as memory entries. One extraction at a time
|
|
5
|
+
// (mutex guard). Fire-and-forget — never blocks auto-mode.
|
|
6
|
+
|
|
7
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
8
|
+
import type { ExtensionContext } from '@gsd/pi-coding-agent';
|
|
9
|
+
import type { Api, AssistantMessage, Model } from '@gsd/pi-ai';
|
|
10
|
+
import {
|
|
11
|
+
getActiveMemories,
|
|
12
|
+
isUnitProcessed,
|
|
13
|
+
markUnitProcessed,
|
|
14
|
+
applyMemoryActions,
|
|
15
|
+
decayStaleMemories,
|
|
16
|
+
} from './memory-store.js';
|
|
17
|
+
import type { MemoryAction } from './memory-store.js';
|
|
18
|
+
|
|
19
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export type LLMCallFn = (system: string, user: string) => Promise<string>;
|
|
22
|
+
|
|
23
|
+
// ─── Concurrency Guard ──────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
let _extracting = false;
|
|
26
|
+
let _lastExtractionTime = 0;
|
|
27
|
+
|
|
28
|
+
const MIN_EXTRACTION_INTERVAL_MS = 30_000;
|
|
29
|
+
|
|
30
|
+
// ─── Skip Conditions ────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const SKIP_TYPES = new Set([
|
|
33
|
+
'complete-slice',
|
|
34
|
+
'rewrite-docs',
|
|
35
|
+
'triage-captures',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
const MIN_ACTIVITY_SIZE = 1024; // 1KB
|
|
39
|
+
|
|
40
|
+
// ─── Secret Redaction ───────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
const SECRET_PATTERNS = [
|
|
43
|
+
/(?:sk|pk|api[_-]?key|token|secret|password|credential|auth)[_-]?\w*[\s:=]+['"]?[\w\-./+=]{20,}['"]?/gi,
|
|
44
|
+
/AKIA[0-9A-Z]{16}/g,
|
|
45
|
+
/gh[pousr]_[A-Za-z0-9_]{36,}/g,
|
|
46
|
+
/[rsp]k_(?:live|test)_[A-Za-z0-9]{20,}/g,
|
|
47
|
+
/eyJ[A-Za-z0-9_-]{20,}\.eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+/g,
|
|
48
|
+
/-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
|
|
49
|
+
/(?:Bearer\s+)[A-Za-z0-9\-._~+/]+=*/gi,
|
|
50
|
+
/npm_[A-Za-z0-9]{36,}/g,
|
|
51
|
+
/sk-ant-[A-Za-z0-9\-_]{20,}/g,
|
|
52
|
+
/sk-[A-Za-z0-9]{40,}/g,
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
function redactSecrets(text: string): string {
|
|
56
|
+
let result = text;
|
|
57
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
58
|
+
// Reset lastIndex for global regexes
|
|
59
|
+
pattern.lastIndex = 0;
|
|
60
|
+
result = result.replace(pattern, '[REDACTED]');
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Model Selection ────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Build an LLM call function using the cheapest available model (preferring Haiku).
|
|
69
|
+
* Returns null if no models available.
|
|
70
|
+
*/
|
|
71
|
+
export function buildMemoryLLMCall(ctx: ExtensionContext): LLMCallFn | null {
|
|
72
|
+
try {
|
|
73
|
+
const available = ctx.modelRegistry.getAvailable();
|
|
74
|
+
if (!available || available.length === 0) return null;
|
|
75
|
+
|
|
76
|
+
// Prefer Haiku by ID substring match
|
|
77
|
+
let model = available.find(m =>
|
|
78
|
+
m.id.toLowerCase().includes('haiku'),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Fallback: cheapest by input cost
|
|
82
|
+
if (!model) {
|
|
83
|
+
model = [...available].sort((a, b) => a.cost.input - b.cost.input)[0];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!model) return null;
|
|
87
|
+
|
|
88
|
+
const selectedModel = model as Model<Api>;
|
|
89
|
+
|
|
90
|
+
return async (system: string, user: string): Promise<string> => {
|
|
91
|
+
const { completeSimple } = await import('@gsd/pi-ai');
|
|
92
|
+
const result: AssistantMessage = await completeSimple(selectedModel, {
|
|
93
|
+
systemPrompt: system,
|
|
94
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: user }], timestamp: Date.now() }],
|
|
95
|
+
}, {
|
|
96
|
+
maxTokens: 2048,
|
|
97
|
+
temperature: 0,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Extract text from response
|
|
101
|
+
const textParts = result.content
|
|
102
|
+
.filter((c): c is { type: 'text'; text: string } => c.type === 'text')
|
|
103
|
+
.map(c => c.text);
|
|
104
|
+
return textParts.join('');
|
|
105
|
+
};
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── Extraction Prompts ─────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
const EXTRACTION_SYSTEM = `You are a memory extraction agent for a software project. Analyze the session
|
|
114
|
+
transcript and identify durable knowledge worth remembering for future sessions.
|
|
115
|
+
|
|
116
|
+
Categories: architecture, convention, gotcha, preference, environment, pattern
|
|
117
|
+
|
|
118
|
+
Actions (return JSON array):
|
|
119
|
+
- CREATE: {"action": "CREATE", "category": "<cat>", "content": "<text>", "confidence": <0.6-0.95>}
|
|
120
|
+
- UPDATE: {"action": "UPDATE", "id": "<MEM###>", "content": "<revised text>"}
|
|
121
|
+
- REINFORCE: {"action": "REINFORCE", "id": "<MEM###>"}
|
|
122
|
+
- SUPERSEDE: {"action": "SUPERSEDE", "id": "<MEM###>", "superseded_by": "<MEM###>"}
|
|
123
|
+
|
|
124
|
+
Rules:
|
|
125
|
+
- Don't create memories for one-off bug fixes or temporary state
|
|
126
|
+
- Don't duplicate existing memories — use REINFORCE or UPDATE
|
|
127
|
+
- Keep content to 1-3 sentences
|
|
128
|
+
- Confidence: 0.6 tentative, 0.8 solid, 0.95 well-confirmed
|
|
129
|
+
- Prefer fewer high-quality memories over many low-quality ones
|
|
130
|
+
- Return empty array [] if nothing worth remembering
|
|
131
|
+
- NEVER include secrets, API keys, or passwords
|
|
132
|
+
|
|
133
|
+
Return ONLY a valid JSON array.`;
|
|
134
|
+
|
|
135
|
+
function buildExtractionUserPrompt(
|
|
136
|
+
unitType: string,
|
|
137
|
+
unitId: string,
|
|
138
|
+
existingMemories: { id: string; category: string; content: string }[],
|
|
139
|
+
transcript: string,
|
|
140
|
+
): string {
|
|
141
|
+
let memoriesSection: string;
|
|
142
|
+
if (existingMemories.length === 0) {
|
|
143
|
+
memoriesSection = '(none yet)';
|
|
144
|
+
} else {
|
|
145
|
+
memoriesSection = existingMemories
|
|
146
|
+
.map((m, i) => `${i + 1}. [${m.id}] (${m.category}) ${m.content}`)
|
|
147
|
+
.join('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return `## Current Active Memories\n${memoriesSection}\n\n## Session Transcript (${unitType}: ${unitId})\n${transcript}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── Activity JSONL Parsing ─────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Extract assistant message text from activity JSONL.
|
|
157
|
+
* Returns concatenated text content from assistant role entries.
|
|
158
|
+
*/
|
|
159
|
+
function extractTranscriptFromActivity(raw: string, maxChars = 30_000): string {
|
|
160
|
+
const lines = raw.split('\n');
|
|
161
|
+
const parts: string[] = [];
|
|
162
|
+
let totalChars = 0;
|
|
163
|
+
|
|
164
|
+
for (const line of lines) {
|
|
165
|
+
if (!line.trim()) continue;
|
|
166
|
+
try {
|
|
167
|
+
const entry = JSON.parse(line);
|
|
168
|
+
if (entry.role !== 'assistant') continue;
|
|
169
|
+
|
|
170
|
+
// Handle content array or direct text
|
|
171
|
+
if (Array.isArray(entry.content)) {
|
|
172
|
+
for (const block of entry.content) {
|
|
173
|
+
if (block.type === 'text' && block.text) {
|
|
174
|
+
const text = block.text;
|
|
175
|
+
if (totalChars + text.length > maxChars) {
|
|
176
|
+
parts.push(text.substring(0, maxChars - totalChars));
|
|
177
|
+
return parts.join('\n\n');
|
|
178
|
+
}
|
|
179
|
+
parts.push(text);
|
|
180
|
+
totalChars += text.length;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} else if (typeof entry.content === 'string') {
|
|
184
|
+
const text = entry.content;
|
|
185
|
+
if (totalChars + text.length > maxChars) {
|
|
186
|
+
parts.push(text.substring(0, maxChars - totalChars));
|
|
187
|
+
return parts.join('\n\n');
|
|
188
|
+
}
|
|
189
|
+
parts.push(text);
|
|
190
|
+
totalChars += text.length;
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
// Skip malformed lines
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return parts.join('\n\n');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── Response Parsing ───────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Parse the LLM response into memory actions.
|
|
204
|
+
* Strips markdown fences, validates required fields.
|
|
205
|
+
* Returns [] on any parse failure.
|
|
206
|
+
*/
|
|
207
|
+
export function parseMemoryResponse(raw: string): MemoryAction[] {
|
|
208
|
+
try {
|
|
209
|
+
// Strip markdown code fences
|
|
210
|
+
let cleaned = raw.trim();
|
|
211
|
+
if (cleaned.startsWith('```')) {
|
|
212
|
+
cleaned = cleaned.replace(/^```(?:json)?\s*\n?/, '').replace(/\n?```\s*$/, '');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const parsed = JSON.parse(cleaned);
|
|
216
|
+
if (!Array.isArray(parsed)) return [];
|
|
217
|
+
|
|
218
|
+
const actions: MemoryAction[] = [];
|
|
219
|
+
for (const item of parsed) {
|
|
220
|
+
if (!item || typeof item !== 'object' || !item.action) continue;
|
|
221
|
+
|
|
222
|
+
switch (item.action) {
|
|
223
|
+
case 'CREATE':
|
|
224
|
+
if (typeof item.category === 'string' && typeof item.content === 'string') {
|
|
225
|
+
actions.push({
|
|
226
|
+
action: 'CREATE',
|
|
227
|
+
category: item.category,
|
|
228
|
+
content: item.content,
|
|
229
|
+
confidence: typeof item.confidence === 'number' ? item.confidence : undefined,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
case 'UPDATE':
|
|
234
|
+
if (typeof item.id === 'string' && typeof item.content === 'string') {
|
|
235
|
+
actions.push({
|
|
236
|
+
action: 'UPDATE',
|
|
237
|
+
id: item.id,
|
|
238
|
+
content: item.content,
|
|
239
|
+
confidence: typeof item.confidence === 'number' ? item.confidence : undefined,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
case 'REINFORCE':
|
|
244
|
+
if (typeof item.id === 'string') {
|
|
245
|
+
actions.push({ action: 'REINFORCE', id: item.id });
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
case 'SUPERSEDE':
|
|
249
|
+
if (typeof item.id === 'string' && typeof item.superseded_by === 'string') {
|
|
250
|
+
actions.push({
|
|
251
|
+
action: 'SUPERSEDE',
|
|
252
|
+
id: item.id,
|
|
253
|
+
superseded_by: item.superseded_by,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return actions;
|
|
261
|
+
} catch {
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ─── Main Extraction Function ───────────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Extract memories from a completed unit's activity log.
|
|
270
|
+
* Fire-and-forget — never throws, mutex-guarded, respects rate limiting.
|
|
271
|
+
*/
|
|
272
|
+
export async function extractMemoriesFromUnit(
|
|
273
|
+
activityFile: string,
|
|
274
|
+
unitType: string,
|
|
275
|
+
unitId: string,
|
|
276
|
+
llmCallFn: LLMCallFn,
|
|
277
|
+
): Promise<void> {
|
|
278
|
+
// Mutex guard
|
|
279
|
+
if (_extracting) return;
|
|
280
|
+
|
|
281
|
+
// Rate limit
|
|
282
|
+
const now = Date.now();
|
|
283
|
+
if (now - _lastExtractionTime < MIN_EXTRACTION_INTERVAL_MS) return;
|
|
284
|
+
|
|
285
|
+
// Skip certain unit types
|
|
286
|
+
if (SKIP_TYPES.has(unitType)) return;
|
|
287
|
+
|
|
288
|
+
const unitKey = `${unitType}/${unitId}`;
|
|
289
|
+
|
|
290
|
+
// Already processed
|
|
291
|
+
if (isUnitProcessed(unitKey)) return;
|
|
292
|
+
|
|
293
|
+
// Check file size
|
|
294
|
+
try {
|
|
295
|
+
const stat = statSync(activityFile);
|
|
296
|
+
if (stat.size < MIN_ACTIVITY_SIZE) return;
|
|
297
|
+
} catch {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
_extracting = true;
|
|
302
|
+
_lastExtractionTime = now;
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
// Read and parse activity file
|
|
306
|
+
const raw = readFileSync(activityFile, 'utf-8');
|
|
307
|
+
const transcript = extractTranscriptFromActivity(raw);
|
|
308
|
+
if (!transcript.trim()) return;
|
|
309
|
+
|
|
310
|
+
// Redact secrets
|
|
311
|
+
const safeTranscript = redactSecrets(transcript);
|
|
312
|
+
|
|
313
|
+
// Get current memories for context
|
|
314
|
+
const activeMemories = getActiveMemories().map(m => ({
|
|
315
|
+
id: m.id,
|
|
316
|
+
category: m.category,
|
|
317
|
+
content: m.content,
|
|
318
|
+
}));
|
|
319
|
+
|
|
320
|
+
// Build prompts
|
|
321
|
+
const userPrompt = buildExtractionUserPrompt(unitType, unitId, activeMemories, safeTranscript);
|
|
322
|
+
|
|
323
|
+
// Call LLM
|
|
324
|
+
const response = await llmCallFn(EXTRACTION_SYSTEM, userPrompt);
|
|
325
|
+
|
|
326
|
+
// Parse response
|
|
327
|
+
const actions = parseMemoryResponse(response);
|
|
328
|
+
|
|
329
|
+
// Apply actions
|
|
330
|
+
if (actions.length > 0) {
|
|
331
|
+
applyMemoryActions(actions, unitType, unitId);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Decay stale memories periodically
|
|
335
|
+
decayStaleMemories(20);
|
|
336
|
+
|
|
337
|
+
// Mark unit as processed
|
|
338
|
+
markUnitProcessed(unitKey, activityFile);
|
|
339
|
+
} catch {
|
|
340
|
+
// Non-fatal — memory extraction failure should never affect auto-mode
|
|
341
|
+
} finally {
|
|
342
|
+
_extracting = false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ─── Testing Helpers ────────────────────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
/** Reset extraction state (testing only). */
|
|
349
|
+
export function _resetExtractionState(): void {
|
|
350
|
+
_extracting = false;
|
|
351
|
+
_lastExtractionTime = 0;
|
|
352
|
+
}
|