create-walle 0.9.25 → 0.9.26
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 +8 -0
- package/bin/create-walle.js +815 -45
- package/package.json +2 -2
- package/template/bin/ctm-dev-cleanup.js +90 -4
- package/template/bin/ctm-launch.sh +49 -1
- package/template/bin/dev.sh +45 -1
- package/template/bin/ensure-stable-node.js +132 -0
- package/template/bin/install-service.sh +9 -0
- package/template/claude-task-manager/api-prompts.js +899 -119
- package/template/claude-task-manager/approval-agent.js +360 -40
- package/template/claude-task-manager/bin/ctm-disclaim.c +42 -0
- package/template/claude-task-manager/bin/ctm-hotkey.swift +67 -81
- package/template/claude-task-manager/bin/ctm-screen-auth.swift +37 -0
- package/template/claude-task-manager/bin/install-hotkey.sh +97 -49
- package/template/claude-task-manager/bin/restart-ctm.sh +14 -0
- package/template/claude-task-manager/db.js +399 -48
- package/template/claude-task-manager/docs/approval-hook-sandbox.md +84 -0
- package/template/claude-task-manager/docs/codex-app-server-approvals.md +72 -0
- package/template/claude-task-manager/docs/codex-native-sandbox.md +47 -0
- package/template/claude-task-manager/docs/prompt-editing-tree-design.md +18 -1
- package/template/claude-task-manager/lib/approval-hook.js +200 -0
- package/template/claude-task-manager/lib/approval-self-adapt.js +1 -0
- package/template/claude-task-manager/lib/auth-rules.js +11 -0
- package/template/claude-task-manager/lib/background-llm.js +32 -4
- package/template/claude-task-manager/lib/codesign-identity.js +140 -0
- package/template/claude-task-manager/lib/codex-app-server-client.js +119 -0
- package/template/claude-task-manager/lib/codex-approval-bridge.js +118 -0
- package/template/claude-task-manager/lib/codex-history-terminal-renderer.js +571 -0
- package/template/claude-task-manager/lib/codex-paths.js +73 -0
- package/template/claude-task-manager/lib/codex-rollout-snapshot.js +164 -0
- package/template/claude-task-manager/lib/codex-rollout-tail.js +72 -0
- package/template/claude-task-manager/lib/codex-sandbox-args.js +47 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +118 -71
- package/template/claude-task-manager/lib/command-targets.js +163 -0
- package/template/claude-task-manager/lib/conversation-tail-merge.js +61 -19
- package/template/claude-task-manager/lib/db-owner-worker-client.js +29 -1
- package/template/claude-task-manager/lib/escalation-review.js +80 -3
- package/template/claude-task-manager/lib/flow-control.js +52 -0
- package/template/claude-task-manager/lib/fs-watcher.js +24 -15
- package/template/claude-task-manager/lib/ingest-cooldown.js +68 -0
- package/template/claude-task-manager/lib/jsonl-conversation-parser.js +8 -4
- package/template/claude-task-manager/lib/launchd-recovery.js +92 -0
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +207 -52
- package/template/claude-task-manager/lib/mobile-push-store.js +7 -0
- package/template/claude-task-manager/lib/model-overview-brain-fallback.js +102 -1
- package/template/claude-task-manager/lib/model-overview-cache.js +1 -0
- package/template/claude-task-manager/lib/oauth-proxy-supervisor.js +2 -1
- package/template/claude-task-manager/lib/perf-tracker.js +29 -2
- package/template/claude-task-manager/lib/permission-match.js +146 -16
- package/template/claude-task-manager/lib/project-slug.js +33 -0
- package/template/claude-task-manager/lib/prompt-intent.js +51 -4
- package/template/claude-task-manager/lib/read-pool-client.js +48 -3
- package/template/claude-task-manager/lib/real-node.js +73 -0
- package/template/claude-task-manager/lib/runtime-work-registry.js +131 -14
- package/template/claude-task-manager/lib/session-content-backfill.js +24 -5
- package/template/claude-task-manager/lib/session-diagnostics-batch.js +87 -0
- package/template/claude-task-manager/lib/session-history.js +5 -7
- package/template/claude-task-manager/lib/session-host-manager.js +19 -0
- package/template/claude-task-manager/lib/session-jobs.js +6 -0
- package/template/claude-task-manager/lib/session-message-response-cache.js +89 -0
- package/template/claude-task-manager/lib/session-messages-page.js +211 -0
- package/template/claude-task-manager/lib/session-messages-projection.js +170 -0
- package/template/claude-task-manager/lib/session-standup.js +8 -0
- package/template/claude-task-manager/lib/session-timeline-summary.js +16 -2
- package/template/claude-task-manager/lib/session-token-usage.js +30 -8
- package/template/claude-task-manager/lib/session-workspace-binding.js +29 -15
- package/template/claude-task-manager/lib/storage-migration.js +2 -1
- package/template/claude-task-manager/lib/transcript-store.js +179 -12
- package/template/claude-task-manager/lib/walle-ctm-history.js +298 -11
- package/template/claude-task-manager/lib/walle-permission-reply.js +49 -0
- package/template/claude-task-manager/lib/walle-session-cache.js +22 -1
- package/template/claude-task-manager/lib/walle-supervisor.js +42 -3
- package/template/claude-task-manager/package.json +5 -2
- package/template/claude-task-manager/prompt-harvest.js +31 -11
- package/template/claude-task-manager/providers/claude-code.js +29 -1
- package/template/claude-task-manager/providers/codex.js +13 -1
- package/template/claude-task-manager/public/css/setup.css +11 -0
- package/template/claude-task-manager/public/css/walle-session.css +132 -4
- package/template/claude-task-manager/public/css/walle.css +89 -0
- package/template/claude-task-manager/public/icon-16.png +0 -0
- package/template/claude-task-manager/public/icon-32.png +0 -0
- package/template/claude-task-manager/public/icon-512.png +0 -0
- package/template/claude-task-manager/public/index.html +2483 -165
- package/template/claude-task-manager/public/js/activation-render-check.js +55 -0
- package/template/claude-task-manager/public/js/flow-control-policy.js +52 -0
- package/template/claude-task-manager/public/js/message-renderer.js +60 -1
- package/template/claude-task-manager/public/js/prompts.js +13 -1
- package/template/claude-task-manager/public/js/session-status-precedence.js +9 -3
- package/template/claude-task-manager/public/js/setup.js +54 -10
- package/template/claude-task-manager/public/js/stream-resize-policy.js +80 -0
- package/template/claude-task-manager/public/js/stream-view.js +78 -0
- package/template/claude-task-manager/public/js/terminal-reconciler.js +52 -2
- package/template/claude-task-manager/public/js/tool-state.js +155 -0
- package/template/claude-task-manager/public/js/walle-session.js +887 -326
- package/template/claude-task-manager/public/js/walle.js +306 -195
- package/template/claude-task-manager/public/m/app.css +1 -0
- package/template/claude-task-manager/public/m/app.js +33 -3
- package/template/claude-task-manager/queue-engine.js +45 -1
- package/template/claude-task-manager/server.js +3367 -540
- package/template/claude-task-manager/workers/approval-blocklist.js +130 -17
- package/template/claude-task-manager/workers/db-owner-worker.js +31 -1
- package/template/claude-task-manager/workers/read-pool-worker.js +92 -5
- package/template/claude-task-manager/workers/session-host-process.js +10 -0
- package/template/claude-task-manager/workers/state-detectors/codex.js +58 -7
- package/template/package.json +2 -3
- package/template/shared/icons/AppIcon-ctm.icns +0 -0
- package/template/shared/icons/AppIcon-walle.icns +0 -0
- package/template/wall-e/agent.js +139 -18
- package/template/wall-e/api-walle.js +201 -22
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +1981 -0
- package/template/wall-e/brain.js +1049 -39
- package/template/wall-e/chat.js +427 -86
- package/template/wall-e/coding/acceptance-contract.js +26 -1
- package/template/wall-e/coding/action-memory-policy.js +353 -0
- package/template/wall-e/coding/action-memory-store.js +814 -0
- package/template/wall-e/coding/initial-messages.js +197 -0
- package/template/wall-e/coding/no-progress-guard.js +327 -0
- package/template/wall-e/coding/permission-service.js +88 -22
- package/template/wall-e/coding/session-workspaces.js +81 -0
- package/template/wall-e/coding/shell-sandbox.js +124 -0
- package/template/wall-e/coding/stream-processor.js +63 -2
- package/template/wall-e/coding/tool-execution-controller.js +14 -1
- package/template/wall-e/coding/tool-registry.js +1 -1
- package/template/wall-e/coding/transcript-writer.js +3 -0
- package/template/wall-e/coding-orchestrator.js +636 -35
- package/template/wall-e/coding-prompts.js +51 -2
- package/template/wall-e/docs/model-routing-policy.md +59 -0
- package/template/wall-e/docs/walle-shell-sandbox.md +61 -0
- package/template/wall-e/extraction/knowledge-extractor.js +76 -23
- package/template/wall-e/http/chat-api.js +30 -12
- package/template/wall-e/http/model-admin.js +93 -1
- package/template/wall-e/lib/background-lanes.js +133 -0
- package/template/wall-e/lib/boot-profile.js +11 -0
- package/template/wall-e/lib/brain-owner-worker-client.js +324 -0
- package/template/wall-e/lib/brain-read-pool-client.js +311 -0
- package/template/wall-e/lib/diagnostics-flags.js +87 -0
- package/template/wall-e/lib/event-loop-monitor.js +74 -3
- package/template/wall-e/lib/mcp-integration.js +7 -1
- package/template/wall-e/lib/real-node.js +98 -0
- package/template/wall-e/lib/runtime-health.js +206 -0
- package/template/wall-e/lib/runtime-worker-pool.js +101 -0
- package/template/wall-e/lib/scheduler-worker-jobs.js +231 -0
- package/template/wall-e/lib/scheduler.js +446 -17
- package/template/wall-e/lib/service-health.js +61 -2
- package/template/wall-e/lib/service-readiness.js +258 -0
- package/template/wall-e/lib/usage.js +152 -0
- package/template/wall-e/lib/worker-thread-pool.js +389 -0
- package/template/wall-e/llm/client.js +81 -4
- package/template/wall-e/llm/default-fallback.js +54 -8
- package/template/wall-e/llm/mlx.js +536 -73
- package/template/wall-e/llm/mlx.plugin.json +1 -1
- package/template/wall-e/llm/ollama.js +342 -43
- package/template/wall-e/llm/provider-error.js +18 -1
- package/template/wall-e/llm/provider-health-state.js +176 -0
- package/template/wall-e/llm/routing-policy.js +796 -0
- package/template/wall-e/llm/supported-models.js +5 -0
- package/template/wall-e/loops/tasks.js +60 -14
- package/template/wall-e/loops/think.js +89 -24
- package/template/wall-e/mcp-server.js +192 -28
- package/template/wall-e/server.js +32 -7
- package/template/wall-e/skills/script-skill-runner.js +8 -1
- package/template/wall-e/skills/skill-planner.js +64 -1
- package/template/wall-e/tools/builtin-middleware.js +67 -2
- package/template/wall-e/tools/local-tools.js +116 -26
- package/template/wall-e/tools/permission-checker.js +52 -4
- package/template/wall-e/tools/permission-rules.js +36 -0
- package/template/wall-e/tools/shell-analyzer.js +46 -1
- package/template/wall-e/training/gemma-e4b-qlora.js +314 -0
- package/template/wall-e/training/real-trajectory-miner.js +2617 -0
- package/template/wall-e/training/replay-eval-analysis.js +151 -0
- package/template/wall-e/training/run-shell-command-selector.js +277 -0
- package/template/wall-e/training/tool-sft-dataset.js +312 -0
- package/template/wall-e/training/tool-sft-renderers.js +144 -0
- package/template/wall-e/training/tool-trace-harvester.js +1440 -0
- package/template/wall-e/training/trajectory-action-selector.js +364 -0
- package/template/wall-e/weather-runtime.js +232 -0
- package/template/wall-e/workers/brain-owner-worker.js +162 -0
- package/template/wall-e/workers/brain-read-worker.js +148 -0
- package/template/wall-e/workers/runtime-worker.js +145 -0
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const Database = requireBetterSqlite3();
|
|
9
|
+
const { enforceSqliteStoragePolicy } = require('../shared/sqlite-storage-policy');
|
|
10
|
+
const { installSqliteWriteLock } = require('../shared/sqlite-write-lock');
|
|
11
|
+
|
|
12
|
+
const SCHEMA_VERSION = 1;
|
|
13
|
+
const DEFAULT_LIMIT = 50;
|
|
14
|
+
const DEFAULT_PREVIEW_BYTES = 4096;
|
|
15
|
+
const POSITIVE_OUTCOMES = new Set(['unknown', 'succeeded', 'applied', 'verified_passed']);
|
|
16
|
+
|
|
17
|
+
class ActionMemoryStore {
|
|
18
|
+
constructor({
|
|
19
|
+
dbPath = defaultActionMemoryDbPath(),
|
|
20
|
+
artifactsDir = defaultActionMemoryArtifactsDir(),
|
|
21
|
+
env = process.env,
|
|
22
|
+
now = () => new Date().toISOString(),
|
|
23
|
+
logger = console,
|
|
24
|
+
db = null,
|
|
25
|
+
previewBytes = DEFAULT_PREVIEW_BYTES,
|
|
26
|
+
} = {}) {
|
|
27
|
+
this.dbPath = dbPath;
|
|
28
|
+
this.artifactsDir = artifactsDir;
|
|
29
|
+
this.env = env;
|
|
30
|
+
this.now = now;
|
|
31
|
+
this.logger = logger;
|
|
32
|
+
this.db = db;
|
|
33
|
+
this.ownsDb = !db;
|
|
34
|
+
this.previewBytes = previewBytes;
|
|
35
|
+
this.initialized = false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
init() {
|
|
39
|
+
if (this.initialized) return this;
|
|
40
|
+
fs.mkdirSync(path.dirname(this.dbPath), { recursive: true });
|
|
41
|
+
fs.mkdirSync(this.artifactsDir, { recursive: true });
|
|
42
|
+
enforceSqliteStoragePolicy(this.dbPath, {
|
|
43
|
+
env: this.env,
|
|
44
|
+
prefix: 'WALL_E_ACTION_MEMORY',
|
|
45
|
+
label: 'Wall-E action-memory SQLite database',
|
|
46
|
+
logger: this.logger,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!this.db) this.db = new Database(this.dbPath, { timeout: sqliteTimeoutMs(this.env) });
|
|
50
|
+
installSqliteWriteLock(this.db, this.dbPath, {
|
|
51
|
+
label: 'wall-e-action-memory',
|
|
52
|
+
timeoutMs: sqliteWriteLockTimeoutMs(this.env),
|
|
53
|
+
staleMs: Number(this.env.WALL_E_ACTION_MEMORY_SQLITE_WRITE_LOCK_STALE_MS || 10 * 60 * 1000),
|
|
54
|
+
});
|
|
55
|
+
this.db.pragma('journal_mode = WAL');
|
|
56
|
+
this.db.pragma(`busy_timeout = ${sqliteTimeoutMs(this.env)}`);
|
|
57
|
+
this.db.pragma('foreign_keys = ON');
|
|
58
|
+
this.createTables();
|
|
59
|
+
this.initialized = true;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
close() {
|
|
64
|
+
if (this.db && this.ownsDb) this.db.close();
|
|
65
|
+
this.db = null;
|
|
66
|
+
this.initialized = false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
createTables() {
|
|
70
|
+
const db = this.requireDb();
|
|
71
|
+
db.exec(`
|
|
72
|
+
CREATE TABLE IF NOT EXISTS action_memory_entries (
|
|
73
|
+
id TEXT PRIMARY KEY,
|
|
74
|
+
created_at TEXT NOT NULL,
|
|
75
|
+
updated_at TEXT NOT NULL,
|
|
76
|
+
source_kind TEXT NOT NULL,
|
|
77
|
+
source_session_id TEXT,
|
|
78
|
+
source_tool_call_id TEXT,
|
|
79
|
+
source_row_id TEXT,
|
|
80
|
+
source_ts TEXT,
|
|
81
|
+
provider TEXT,
|
|
82
|
+
model_id TEXT,
|
|
83
|
+
tool_format TEXT,
|
|
84
|
+
tool_catalog_id TEXT,
|
|
85
|
+
workflow_family_id TEXT,
|
|
86
|
+
repo_key TEXT,
|
|
87
|
+
cwd_hash TEXT,
|
|
88
|
+
intent TEXT,
|
|
89
|
+
task_fingerprint TEXT,
|
|
90
|
+
task_preview TEXT,
|
|
91
|
+
state_signature_hash TEXT,
|
|
92
|
+
state_sequence TEXT,
|
|
93
|
+
last_tool TEXT,
|
|
94
|
+
tool_name TEXT NOT NULL,
|
|
95
|
+
target_kind TEXT,
|
|
96
|
+
target_hint TEXT,
|
|
97
|
+
target_path_suffix TEXT,
|
|
98
|
+
command_hash TEXT,
|
|
99
|
+
payload_sha256 TEXT NOT NULL,
|
|
100
|
+
payload_artifact_path TEXT NOT NULL,
|
|
101
|
+
payload_bytes INTEGER NOT NULL,
|
|
102
|
+
payload_preview TEXT,
|
|
103
|
+
outcome_status TEXT NOT NULL DEFAULT 'unknown',
|
|
104
|
+
confidence REAL DEFAULT 0.5,
|
|
105
|
+
redaction_version INTEGER DEFAULT 1,
|
|
106
|
+
schema_version INTEGER DEFAULT ${SCHEMA_VERSION},
|
|
107
|
+
disabled_at TEXT,
|
|
108
|
+
metadata_json TEXT
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_action_memory_tool
|
|
112
|
+
ON action_memory_entries(tool_name, disabled_at, outcome_status);
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_action_memory_context
|
|
114
|
+
ON action_memory_entries(workflow_family_id, repo_key, intent, state_sequence);
|
|
115
|
+
CREATE INDEX IF NOT EXISTS idx_action_memory_target
|
|
116
|
+
ON action_memory_entries(tool_name, target_path_suffix, command_hash);
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_action_memory_source
|
|
118
|
+
ON action_memory_entries(source_kind, source_session_id, source_tool_call_id);
|
|
119
|
+
|
|
120
|
+
CREATE TABLE IF NOT EXISTS action_memory_selection_log (
|
|
121
|
+
id TEXT PRIMARY KEY,
|
|
122
|
+
created_at TEXT NOT NULL,
|
|
123
|
+
mode TEXT NOT NULL,
|
|
124
|
+
provider TEXT,
|
|
125
|
+
model_id TEXT,
|
|
126
|
+
session_id TEXT,
|
|
127
|
+
tool_name TEXT,
|
|
128
|
+
candidate_count INTEGER DEFAULT 0,
|
|
129
|
+
selected_entry_id TEXT,
|
|
130
|
+
selected_score REAL,
|
|
131
|
+
accepted INTEGER DEFAULT 0,
|
|
132
|
+
reason TEXT,
|
|
133
|
+
prompt_hash TEXT,
|
|
134
|
+
prediction_json TEXT,
|
|
135
|
+
context_json TEXT,
|
|
136
|
+
outcome_json TEXT,
|
|
137
|
+
FOREIGN KEY(selected_entry_id) REFERENCES action_memory_entries(id)
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
PRAGMA user_version = ${SCHEMA_VERSION};
|
|
141
|
+
`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
recordActionMemoryEntry({ context = {}, action = {}, outcome = {}, source = {}, model = {}, metadata = {} } = {}) {
|
|
145
|
+
this.init();
|
|
146
|
+
const normalizedAction = normalizeAction(action);
|
|
147
|
+
if (!normalizedAction.toolName) throw new Error('recordActionMemoryEntry requires action.toolName/name/tool');
|
|
148
|
+
const normalizedContext = normalizeActionContext(context, normalizedAction);
|
|
149
|
+
const normalizedOutcome = normalizeOutcome(outcome);
|
|
150
|
+
const normalizedSource = normalizeSource(source);
|
|
151
|
+
const normalizedModel = normalizeModel(model);
|
|
152
|
+
const payload = {
|
|
153
|
+
schema_version: SCHEMA_VERSION,
|
|
154
|
+
tool: normalizedAction.toolName,
|
|
155
|
+
input: normalizedAction.input,
|
|
156
|
+
context: normalizedContext.publicContext,
|
|
157
|
+
outcome: normalizedOutcome,
|
|
158
|
+
source: normalizedSource,
|
|
159
|
+
model: normalizedModel,
|
|
160
|
+
metadata: safeJsonValue(metadata),
|
|
161
|
+
};
|
|
162
|
+
const artifact = this.storePayload(payload, {
|
|
163
|
+
sessionId: normalizedSource.sessionId,
|
|
164
|
+
toolCallId: normalizedSource.toolCallId,
|
|
165
|
+
toolName: normalizedAction.toolName,
|
|
166
|
+
});
|
|
167
|
+
const now = this.now();
|
|
168
|
+
const id = source.id || `am_${sha256([
|
|
169
|
+
normalizedSource.kind,
|
|
170
|
+
normalizedSource.sessionId,
|
|
171
|
+
normalizedSource.toolCallId,
|
|
172
|
+
normalizedSource.rowId,
|
|
173
|
+
normalizedAction.toolName,
|
|
174
|
+
artifact.sha256,
|
|
175
|
+
].join('\0')).slice(0, 24)}`;
|
|
176
|
+
|
|
177
|
+
const row = {
|
|
178
|
+
id,
|
|
179
|
+
created_at: now,
|
|
180
|
+
updated_at: now,
|
|
181
|
+
source_kind: normalizedSource.kind,
|
|
182
|
+
source_session_id: normalizedSource.sessionId,
|
|
183
|
+
source_tool_call_id: normalizedSource.toolCallId,
|
|
184
|
+
source_row_id: normalizedSource.rowId,
|
|
185
|
+
source_ts: normalizedSource.timestamp,
|
|
186
|
+
provider: normalizedModel.provider,
|
|
187
|
+
model_id: normalizedModel.modelId,
|
|
188
|
+
tool_format: normalizedModel.toolFormat,
|
|
189
|
+
tool_catalog_id: normalizedContext.toolCatalogId,
|
|
190
|
+
workflow_family_id: normalizedContext.familyId,
|
|
191
|
+
repo_key: normalizedContext.repoKey,
|
|
192
|
+
cwd_hash: normalizedContext.cwdHash,
|
|
193
|
+
intent: normalizedContext.intent,
|
|
194
|
+
task_fingerprint: normalizedContext.taskFingerprint,
|
|
195
|
+
task_preview: normalizedContext.taskPreview,
|
|
196
|
+
state_signature_hash: normalizedContext.stateSignatureHash,
|
|
197
|
+
state_sequence: normalizedContext.sequence,
|
|
198
|
+
last_tool: normalizedContext.lastTool,
|
|
199
|
+
tool_name: normalizedAction.toolName,
|
|
200
|
+
target_kind: normalizedAction.targetKind,
|
|
201
|
+
target_hint: normalizedAction.targetHint,
|
|
202
|
+
target_path_suffix: normalizedAction.targetPathSuffix,
|
|
203
|
+
command_hash: normalizedAction.commandHash,
|
|
204
|
+
payload_sha256: artifact.sha256,
|
|
205
|
+
payload_artifact_path: artifact.path,
|
|
206
|
+
payload_bytes: artifact.bytes,
|
|
207
|
+
payload_preview: artifact.preview,
|
|
208
|
+
outcome_status: normalizedOutcome.status,
|
|
209
|
+
confidence: normalizedOutcome.confidence,
|
|
210
|
+
redaction_version: 1,
|
|
211
|
+
schema_version: SCHEMA_VERSION,
|
|
212
|
+
disabled_at: null,
|
|
213
|
+
metadata_json: stringifyJson({
|
|
214
|
+
...safeJsonObject(metadata),
|
|
215
|
+
source: normalizedSource.metadata,
|
|
216
|
+
context: normalizedContext.metadata,
|
|
217
|
+
model: normalizedModel.metadata,
|
|
218
|
+
}),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
this.requireDb().prepare(`
|
|
222
|
+
INSERT INTO action_memory_entries (
|
|
223
|
+
id, created_at, updated_at, source_kind, source_session_id, source_tool_call_id,
|
|
224
|
+
source_row_id, source_ts, provider, model_id, tool_format, tool_catalog_id,
|
|
225
|
+
workflow_family_id, repo_key, cwd_hash, intent, task_fingerprint, task_preview,
|
|
226
|
+
state_signature_hash, state_sequence, last_tool, tool_name, target_kind,
|
|
227
|
+
target_hint, target_path_suffix, command_hash, payload_sha256, payload_artifact_path,
|
|
228
|
+
payload_bytes, payload_preview, outcome_status, confidence, redaction_version,
|
|
229
|
+
schema_version, disabled_at, metadata_json
|
|
230
|
+
) VALUES (
|
|
231
|
+
@id, @created_at, @updated_at, @source_kind, @source_session_id, @source_tool_call_id,
|
|
232
|
+
@source_row_id, @source_ts, @provider, @model_id, @tool_format, @tool_catalog_id,
|
|
233
|
+
@workflow_family_id, @repo_key, @cwd_hash, @intent, @task_fingerprint, @task_preview,
|
|
234
|
+
@state_signature_hash, @state_sequence, @last_tool, @tool_name, @target_kind,
|
|
235
|
+
@target_hint, @target_path_suffix, @command_hash, @payload_sha256, @payload_artifact_path,
|
|
236
|
+
@payload_bytes, @payload_preview, @outcome_status, @confidence, @redaction_version,
|
|
237
|
+
@schema_version, @disabled_at, @metadata_json
|
|
238
|
+
)
|
|
239
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
240
|
+
updated_at = excluded.updated_at,
|
|
241
|
+
outcome_status = excluded.outcome_status,
|
|
242
|
+
confidence = excluded.confidence,
|
|
243
|
+
payload_preview = excluded.payload_preview,
|
|
244
|
+
metadata_json = excluded.metadata_json,
|
|
245
|
+
disabled_at = NULL
|
|
246
|
+
`).run(row);
|
|
247
|
+
|
|
248
|
+
return this.getEntry(id);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
getEntry(id) {
|
|
252
|
+
this.init();
|
|
253
|
+
const row = this.requireDb().prepare('SELECT * FROM action_memory_entries WHERE id = ?').get(String(id || ''));
|
|
254
|
+
return row ? this.rowToEntry(row) : null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
searchActionMemoryCandidates({
|
|
258
|
+
context = {},
|
|
259
|
+
prediction = null,
|
|
260
|
+
toolName = '',
|
|
261
|
+
limit = DEFAULT_LIMIT,
|
|
262
|
+
includeDisabled = false,
|
|
263
|
+
positiveOnly = true,
|
|
264
|
+
} = {}) {
|
|
265
|
+
this.init();
|
|
266
|
+
const action = normalizeAction(prediction || { toolName, input: {} });
|
|
267
|
+
const normalizedTool = String(toolName || action.toolName || '').trim();
|
|
268
|
+
const normalizedContext = normalizeActionContext(context, action);
|
|
269
|
+
const where = [];
|
|
270
|
+
const params = {};
|
|
271
|
+
if (normalizedTool) {
|
|
272
|
+
where.push('tool_name = @toolName');
|
|
273
|
+
params.toolName = normalizedTool;
|
|
274
|
+
}
|
|
275
|
+
if (!includeDisabled) where.push('disabled_at IS NULL');
|
|
276
|
+
if (positiveOnly) where.push(`outcome_status IN (${Array.from(POSITIVE_OUTCOMES).map((_, idx) => `@outcome${idx}`).join(', ')})`);
|
|
277
|
+
if (positiveOnly) Array.from(POSITIVE_OUTCOMES).forEach((value, idx) => { params[`outcome${idx}`] = value; });
|
|
278
|
+
params.limit = Math.max(1, Math.min(500, Number(limit) || DEFAULT_LIMIT));
|
|
279
|
+
|
|
280
|
+
const db = this.requireDb();
|
|
281
|
+
const rows = [];
|
|
282
|
+
const seen = new Set();
|
|
283
|
+
const addRows = (extraWhere = [], extraParams = {}, orderBy = 'created_at DESC') => {
|
|
284
|
+
const sql = `
|
|
285
|
+
SELECT *
|
|
286
|
+
FROM action_memory_entries
|
|
287
|
+
${where.length || extraWhere.length ? `WHERE ${[...where, ...extraWhere].join(' AND ')}` : ''}
|
|
288
|
+
ORDER BY ${orderBy}
|
|
289
|
+
LIMIT @limit
|
|
290
|
+
`;
|
|
291
|
+
for (const row of db.prepare(sql).all({ ...params, ...extraParams })) {
|
|
292
|
+
if (seen.has(row.id)) continue;
|
|
293
|
+
seen.add(row.id);
|
|
294
|
+
rows.push(row);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const contextual = [];
|
|
299
|
+
const contextualParams = {};
|
|
300
|
+
if (normalizedContext.familyId) {
|
|
301
|
+
contextual.push('workflow_family_id = @ctxFamilyId');
|
|
302
|
+
contextualParams.ctxFamilyId = normalizedContext.familyId;
|
|
303
|
+
}
|
|
304
|
+
if (normalizedContext.repoKey && normalizedContext.intent) {
|
|
305
|
+
contextual.push('(repo_key = @ctxRepoKey AND intent = @ctxIntent)');
|
|
306
|
+
contextualParams.ctxRepoKey = normalizedContext.repoKey;
|
|
307
|
+
contextualParams.ctxIntent = normalizedContext.intent;
|
|
308
|
+
} else if (normalizedContext.repoKey) {
|
|
309
|
+
contextual.push('repo_key = @ctxRepoKey');
|
|
310
|
+
contextualParams.ctxRepoKey = normalizedContext.repoKey;
|
|
311
|
+
}
|
|
312
|
+
if (normalizedContext.sequence) {
|
|
313
|
+
contextual.push('state_sequence = @ctxSequence');
|
|
314
|
+
contextualParams.ctxSequence = normalizedContext.sequence;
|
|
315
|
+
}
|
|
316
|
+
if (normalizedContext.taskFingerprint) {
|
|
317
|
+
contextual.push('task_fingerprint = @ctxTaskFingerprint');
|
|
318
|
+
contextualParams.ctxTaskFingerprint = normalizedContext.taskFingerprint;
|
|
319
|
+
}
|
|
320
|
+
if (contextual.length) {
|
|
321
|
+
addRows([`(${contextual.join(' OR ')})`], contextualParams, 'updated_at DESC');
|
|
322
|
+
}
|
|
323
|
+
addRows([], {}, 'created_at DESC');
|
|
324
|
+
|
|
325
|
+
return rows
|
|
326
|
+
.map((row) => this.rowToCandidate(row, normalizedContext))
|
|
327
|
+
.filter(Boolean)
|
|
328
|
+
.sort((left, right) => {
|
|
329
|
+
const leftScore = scoreActionMemoryCandidate(left, normalizedContext, action.input || {}).score;
|
|
330
|
+
const rightScore = scoreActionMemoryCandidate(right, normalizedContext, action.input || {}).score;
|
|
331
|
+
if (rightScore !== leftScore) return rightScore - leftScore;
|
|
332
|
+
return String(left.id).localeCompare(String(right.id));
|
|
333
|
+
})
|
|
334
|
+
.slice(0, params.limit);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
selectActionMemoryCandidate({
|
|
338
|
+
context = {},
|
|
339
|
+
prediction = null,
|
|
340
|
+
toolName = '',
|
|
341
|
+
minScore = 100,
|
|
342
|
+
minMargin = 0,
|
|
343
|
+
limit = DEFAULT_LIMIT,
|
|
344
|
+
} = {}) {
|
|
345
|
+
const action = normalizeAction(prediction || { toolName, input: {} });
|
|
346
|
+
const normalizedContext = normalizeActionContext(context, action);
|
|
347
|
+
const candidates = this.searchActionMemoryCandidates({
|
|
348
|
+
context: normalizedContext.publicContext,
|
|
349
|
+
prediction: action,
|
|
350
|
+
toolName: toolName || action.toolName,
|
|
351
|
+
limit,
|
|
352
|
+
}).map((candidate) => {
|
|
353
|
+
const scored = scoreActionMemoryCandidate(candidate, normalizedContext, action.input || {});
|
|
354
|
+
return { ...candidate, score: scored.score, reasons: scored.reasons };
|
|
355
|
+
});
|
|
356
|
+
const top = candidates[0] || null;
|
|
357
|
+
if (!top) return { selection: null, candidates };
|
|
358
|
+
const second = candidates[1] || null;
|
|
359
|
+
const margin = second ? top.score - second.score : top.score;
|
|
360
|
+
const accepted = top.score >= minScore && margin >= minMargin;
|
|
361
|
+
return {
|
|
362
|
+
selection: {
|
|
363
|
+
id: top.id,
|
|
364
|
+
tool: top.tool,
|
|
365
|
+
input: accepted ? top.input : undefined,
|
|
366
|
+
score: top.score,
|
|
367
|
+
reasons: top.reasons,
|
|
368
|
+
accepted,
|
|
369
|
+
sourceKind: top.sourceKind,
|
|
370
|
+
sourceRowId: top.sourceRowId,
|
|
371
|
+
sourceSessionId: top.sourceSessionId,
|
|
372
|
+
sourceToolCallId: top.sourceToolCallId,
|
|
373
|
+
targetHint: top.targetHint,
|
|
374
|
+
},
|
|
375
|
+
candidates,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
recordSelectionAudit({
|
|
380
|
+
mode = 'audit',
|
|
381
|
+
context = {},
|
|
382
|
+
prediction = null,
|
|
383
|
+
selected = null,
|
|
384
|
+
candidates = [],
|
|
385
|
+
outcome = {},
|
|
386
|
+
reason = '',
|
|
387
|
+
model = {},
|
|
388
|
+
sessionId = '',
|
|
389
|
+
prompt = '',
|
|
390
|
+
} = {}) {
|
|
391
|
+
this.init();
|
|
392
|
+
const normalizedModel = normalizeModel(model);
|
|
393
|
+
const normalizedPrediction = normalizeAction(prediction || {});
|
|
394
|
+
const normalizedContext = normalizeActionContext(context, normalizedPrediction);
|
|
395
|
+
const row = {
|
|
396
|
+
id: `ams_${sha256([
|
|
397
|
+
this.now(),
|
|
398
|
+
mode,
|
|
399
|
+
sessionId,
|
|
400
|
+
selected?.id || '',
|
|
401
|
+
candidates.length,
|
|
402
|
+
crypto.randomBytes(8).toString('hex'),
|
|
403
|
+
].join('\0')).slice(0, 24)}`,
|
|
404
|
+
created_at: this.now(),
|
|
405
|
+
mode: String(mode || 'audit'),
|
|
406
|
+
provider: normalizedModel.provider,
|
|
407
|
+
model_id: normalizedModel.modelId,
|
|
408
|
+
session_id: String(sessionId || normalizedContext.sessionId || ''),
|
|
409
|
+
tool_name: normalizedPrediction.toolName || selected?.tool || '',
|
|
410
|
+
candidate_count: Number(candidates.length || 0),
|
|
411
|
+
selected_entry_id: selected?.id || null,
|
|
412
|
+
selected_score: Number.isFinite(Number(selected?.score)) ? Number(selected.score) : null,
|
|
413
|
+
accepted: selected?.accepted ? 1 : 0,
|
|
414
|
+
reason: String(reason || selected?.reason || ''),
|
|
415
|
+
prompt_hash: prompt ? sha256(String(prompt)) : '',
|
|
416
|
+
prediction_json: stringifyJson(prediction || null),
|
|
417
|
+
context_json: stringifyJson(normalizedContext.publicContext),
|
|
418
|
+
outcome_json: stringifyJson(outcome || null),
|
|
419
|
+
};
|
|
420
|
+
this.requireDb().prepare(`
|
|
421
|
+
INSERT INTO action_memory_selection_log (
|
|
422
|
+
id, created_at, mode, provider, model_id, session_id, tool_name,
|
|
423
|
+
candidate_count, selected_entry_id, selected_score, accepted, reason,
|
|
424
|
+
prompt_hash, prediction_json, context_json, outcome_json
|
|
425
|
+
) VALUES (
|
|
426
|
+
@id, @created_at, @mode, @provider, @model_id, @session_id, @tool_name,
|
|
427
|
+
@candidate_count, @selected_entry_id, @selected_score, @accepted, @reason,
|
|
428
|
+
@prompt_hash, @prediction_json, @context_json, @outcome_json
|
|
429
|
+
)
|
|
430
|
+
`).run(row);
|
|
431
|
+
return row;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
disableEntry(id, { reason = '' } = {}) {
|
|
435
|
+
this.init();
|
|
436
|
+
const metadata = { disabled_reason: String(reason || '') };
|
|
437
|
+
return this.requireDb().prepare(`
|
|
438
|
+
UPDATE action_memory_entries
|
|
439
|
+
SET disabled_at = @disabledAt,
|
|
440
|
+
updated_at = @updatedAt,
|
|
441
|
+
metadata_json = json_patch(COALESCE(metadata_json, '{}'), @metadata)
|
|
442
|
+
WHERE id = @id
|
|
443
|
+
`).run({
|
|
444
|
+
id: String(id || ''),
|
|
445
|
+
disabledAt: this.now(),
|
|
446
|
+
updatedAt: this.now(),
|
|
447
|
+
metadata: stringifyJson(metadata),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
storePayload(payload, { sessionId = '', toolCallId = '', toolName = '' } = {}) {
|
|
452
|
+
const text = stringifyJson(payload, 2);
|
|
453
|
+
const bytes = Buffer.byteLength(text, 'utf8');
|
|
454
|
+
const hash = sha256(text);
|
|
455
|
+
const dir = path.join(this.artifactsDir, hash.slice(0, 2), hash.slice(2, 4));
|
|
456
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
457
|
+
const name = `${safeName(toolName || 'action')}-${safeName(sessionId || 'session')}-${safeName(toolCallId || hash.slice(0, 12))}-${hash.slice(0, 12)}.json`;
|
|
458
|
+
const filePath = path.join(dir, name);
|
|
459
|
+
if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, text, { encoding: 'utf8', mode: 0o600 });
|
|
460
|
+
return {
|
|
461
|
+
path: filePath,
|
|
462
|
+
sha256: hash,
|
|
463
|
+
bytes,
|
|
464
|
+
preview: truncateUtf8(text, this.previewBytes),
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
readPayload(rowOrEntry = {}) {
|
|
469
|
+
const filePath = rowOrEntry.payload_artifact_path || rowOrEntry.payloadArtifactPath || '';
|
|
470
|
+
if (!filePath) return null;
|
|
471
|
+
try {
|
|
472
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
473
|
+
} catch {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
rowToEntry(row) {
|
|
479
|
+
const payload = this.readPayload(row);
|
|
480
|
+
return {
|
|
481
|
+
id: row.id,
|
|
482
|
+
createdAt: row.created_at,
|
|
483
|
+
updatedAt: row.updated_at,
|
|
484
|
+
sourceKind: row.source_kind,
|
|
485
|
+
sourceSessionId: row.source_session_id,
|
|
486
|
+
sourceToolCallId: row.source_tool_call_id,
|
|
487
|
+
sourceRowId: row.source_row_id,
|
|
488
|
+
provider: row.provider,
|
|
489
|
+
modelId: row.model_id,
|
|
490
|
+
toolFormat: row.tool_format,
|
|
491
|
+
tool: row.tool_name,
|
|
492
|
+
input: payload?.input || {},
|
|
493
|
+
familyId: row.workflow_family_id,
|
|
494
|
+
repo: row.repo_key,
|
|
495
|
+
intent: row.intent,
|
|
496
|
+
taskFingerprint: row.task_fingerprint,
|
|
497
|
+
taskPreview: row.task_preview,
|
|
498
|
+
sequence: row.state_sequence,
|
|
499
|
+
lastTool: row.last_tool,
|
|
500
|
+
targetHint: row.target_hint,
|
|
501
|
+
targetPathSuffix: row.target_path_suffix,
|
|
502
|
+
commandHash: row.command_hash,
|
|
503
|
+
payloadSha256: row.payload_sha256,
|
|
504
|
+
payloadArtifactPath: row.payload_artifact_path,
|
|
505
|
+
payloadBytes: row.payload_bytes,
|
|
506
|
+
payloadPreview: row.payload_preview,
|
|
507
|
+
outcomeStatus: row.outcome_status,
|
|
508
|
+
confidence: row.confidence,
|
|
509
|
+
disabledAt: row.disabled_at,
|
|
510
|
+
metadata: parseJson(row.metadata_json, {}),
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
rowToCandidate(row, context = {}) {
|
|
515
|
+
const entry = this.rowToEntry(row);
|
|
516
|
+
if (!entry.tool) return null;
|
|
517
|
+
const scored = scoreActionMemoryCandidate(entry, context, {});
|
|
518
|
+
return {
|
|
519
|
+
id: entry.id,
|
|
520
|
+
tool: entry.tool,
|
|
521
|
+
input: entry.input || {},
|
|
522
|
+
familyId: entry.familyId || null,
|
|
523
|
+
repo: entry.repo || null,
|
|
524
|
+
intent: entry.intent || null,
|
|
525
|
+
sequence: entry.sequence || null,
|
|
526
|
+
lastTool: entry.lastTool || null,
|
|
527
|
+
taskFingerprint: entry.taskFingerprint || null,
|
|
528
|
+
targetHint: entry.targetHint || null,
|
|
529
|
+
commandHash: entry.commandHash || null,
|
|
530
|
+
sourceKind: entry.sourceKind || null,
|
|
531
|
+
sourceSessionId: entry.sourceSessionId || null,
|
|
532
|
+
sourceToolCallId: entry.sourceToolCallId || null,
|
|
533
|
+
sourceRowId: entry.sourceRowId || null,
|
|
534
|
+
provider: entry.provider || null,
|
|
535
|
+
modelId: entry.modelId || null,
|
|
536
|
+
toolFormat: entry.toolFormat || null,
|
|
537
|
+
outcomeStatus: entry.outcomeStatus || null,
|
|
538
|
+
confidence: entry.confidence,
|
|
539
|
+
payloadSha256: entry.payloadSha256,
|
|
540
|
+
payloadArtifactPath: entry.payloadArtifactPath,
|
|
541
|
+
score: scored.score,
|
|
542
|
+
reasons: scored.reasons,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
requireDb() {
|
|
547
|
+
if (!this.db) throw new Error('ActionMemoryStore is not initialized');
|
|
548
|
+
return this.db;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function defaultActionMemoryDir(env = process.env) {
|
|
553
|
+
const dataDir = env.WALL_E_DATA_DIR || env.WALLE_DATA_DIR || path.join(os.homedir(), '.walle', 'data');
|
|
554
|
+
return path.join(dataDir, 'action-memory');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function defaultActionMemoryDbPath(env = process.env) {
|
|
558
|
+
return path.join(defaultActionMemoryDir(env), 'action-memory.db');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function defaultActionMemoryArtifactsDir(env = process.env) {
|
|
562
|
+
return path.join(defaultActionMemoryDir(env), 'artifacts');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function normalizeAction(action = {}) {
|
|
566
|
+
const toolName = String(action.toolName || action.name || action.tool || '').trim();
|
|
567
|
+
const input = safeJsonObject(action.input || action.args || action.arguments || {});
|
|
568
|
+
const targetHint = targetHintForAction(toolName, input);
|
|
569
|
+
const command = toolName === 'run_shell' ? String(input.command || input.cmd || '').trim() : '';
|
|
570
|
+
return {
|
|
571
|
+
toolName,
|
|
572
|
+
input,
|
|
573
|
+
targetKind: command ? 'command' : (targetHint ? 'path' : ''),
|
|
574
|
+
targetHint,
|
|
575
|
+
targetPathSuffix: targetHint ? pathSuffix(targetHint) : '',
|
|
576
|
+
commandHash: command ? sha256(normalizeCommand(command)) : '',
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function normalizeActionContext(context = {}, action = {}) {
|
|
581
|
+
const taskText = String(context.taskText || context.task || context.userTask || context.taskPreview || '').trim();
|
|
582
|
+
const sequence = String(context.sequence || context.stateSequence || context.toolSequence || '').trim();
|
|
583
|
+
const cwd = String(context.cwd || context.projectRoot || '').trim();
|
|
584
|
+
const publicContext = {
|
|
585
|
+
familyId: emptyToNull(context.familyId || context.workflowFamilyId || context.workflow_family_id),
|
|
586
|
+
repoKey: emptyToNull(context.repoKey || context.repo || context.workflowRepo || context.workflow_repo),
|
|
587
|
+
intent: emptyToNull(context.intent || context.workflowIntent || context.workflow_intent),
|
|
588
|
+
intendedTool: emptyToNull(context.intendedTool || context.toolName || context.tool_name || action.toolName),
|
|
589
|
+
taskFingerprint: emptyToNull(context.taskFingerprint || (taskText ? shortHash(taskText) : '')),
|
|
590
|
+
taskPreview: taskText ? truncateChars(taskText.replace(/\s+/g, ' '), 500) : '',
|
|
591
|
+
stateSignatureHash: emptyToNull(context.stateSignatureHash || context.stateHash || (sequence ? shortHash(sequence) : '')),
|
|
592
|
+
sequence: emptyToNull(sequence),
|
|
593
|
+
lastTool: emptyToNull(context.lastTool || context.last_tool),
|
|
594
|
+
toolCatalogId: emptyToNull(context.toolCatalogId || context.tool_catalog),
|
|
595
|
+
cwdHash: cwd ? sha256(cwd) : '',
|
|
596
|
+
targetHint: action.targetHint || emptyToNull(context.targetHint || context.target_path || context.targetPath),
|
|
597
|
+
sessionId: emptyToNull(context.sessionId || context.sourceSessionId),
|
|
598
|
+
};
|
|
599
|
+
return {
|
|
600
|
+
...publicContext,
|
|
601
|
+
publicContext,
|
|
602
|
+
metadata: safeJsonObject(context.metadata || {}),
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function normalizeOutcome(outcome = {}) {
|
|
607
|
+
const status = String(outcome.status || outcome.outcomeStatus || (outcome.verified ? 'verified_passed' : 'unknown')).trim() || 'unknown';
|
|
608
|
+
const confidence = Number(outcome.confidence);
|
|
609
|
+
return {
|
|
610
|
+
status,
|
|
611
|
+
confidence: Number.isFinite(confidence) ? Math.max(0, Math.min(1, confidence)) : 0.5,
|
|
612
|
+
verified: Boolean(outcome.verified || status === 'verified_passed'),
|
|
613
|
+
metadata: safeJsonObject(outcome.metadata || {}),
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function normalizeSource(source = {}) {
|
|
618
|
+
return {
|
|
619
|
+
kind: String(source.kind || source.sourceKind || 'manual').trim() || 'manual',
|
|
620
|
+
sessionId: String(source.sessionId || source.sourceSessionId || '').trim(),
|
|
621
|
+
toolCallId: String(source.toolCallId || source.sourceToolCallId || '').trim(),
|
|
622
|
+
rowId: String(source.rowId || source.sourceRowId || source.id || '').trim(),
|
|
623
|
+
timestamp: String(source.timestamp || source.sourceTs || '').trim(),
|
|
624
|
+
metadata: safeJsonObject(source.metadata || {}),
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function normalizeModel(model = {}) {
|
|
629
|
+
return {
|
|
630
|
+
provider: String(model.provider || model.providerId || '').trim(),
|
|
631
|
+
modelId: String(model.modelId || model.model || model.name || '').trim(),
|
|
632
|
+
toolFormat: String(model.toolFormat || model.tool_format || model.chatTemplate || '').trim(),
|
|
633
|
+
metadata: safeJsonObject(model.metadata || {}),
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function scoreActionMemoryCandidate(candidate = {}, context = {}, predictedInput = {}) {
|
|
638
|
+
let score = 0;
|
|
639
|
+
const reasons = [];
|
|
640
|
+
if (candidate.familyId && candidate.familyId === context.familyId) {
|
|
641
|
+
score += 70;
|
|
642
|
+
reasons.push('same_family');
|
|
643
|
+
}
|
|
644
|
+
if (candidate.tool && context.intendedTool && candidate.tool === context.intendedTool) {
|
|
645
|
+
score += 20;
|
|
646
|
+
reasons.push('same_tool');
|
|
647
|
+
}
|
|
648
|
+
if (candidate.sequence && candidate.sequence === context.sequence) {
|
|
649
|
+
score += 35;
|
|
650
|
+
reasons.push('same_state_sequence');
|
|
651
|
+
}
|
|
652
|
+
if (candidate.lastTool && candidate.lastTool === context.lastTool) {
|
|
653
|
+
score += 10;
|
|
654
|
+
reasons.push('same_last_tool');
|
|
655
|
+
}
|
|
656
|
+
if (candidate.repo && candidate.repo === context.repoKey) {
|
|
657
|
+
score += 8;
|
|
658
|
+
reasons.push('same_repo');
|
|
659
|
+
}
|
|
660
|
+
if (candidate.intent && candidate.intent === context.intent) {
|
|
661
|
+
score += 8;
|
|
662
|
+
reasons.push('same_intent');
|
|
663
|
+
}
|
|
664
|
+
if (candidate.taskFingerprint && candidate.taskFingerprint === context.taskFingerprint) {
|
|
665
|
+
score += 14;
|
|
666
|
+
reasons.push('same_task_fingerprint');
|
|
667
|
+
}
|
|
668
|
+
const predictedTarget = targetHintForAction(candidate.tool, predictedInput || {});
|
|
669
|
+
if (predictedTarget && candidate.targetHint && samePathish(candidate.targetHint, predictedTarget)) {
|
|
670
|
+
score += 12;
|
|
671
|
+
reasons.push('same_predicted_target');
|
|
672
|
+
}
|
|
673
|
+
if (candidate.commandHash && predictedInput?.command && candidate.commandHash === sha256(normalizeCommand(predictedInput.command))) {
|
|
674
|
+
score += 25;
|
|
675
|
+
reasons.push('same_predicted_command');
|
|
676
|
+
}
|
|
677
|
+
if (candidate.outcomeStatus === 'verified_passed') {
|
|
678
|
+
score += 5;
|
|
679
|
+
reasons.push('verified_passed');
|
|
680
|
+
}
|
|
681
|
+
const confidence = Number(candidate.confidence);
|
|
682
|
+
if (Number.isFinite(confidence) && confidence >= 0.75) {
|
|
683
|
+
score += 3;
|
|
684
|
+
reasons.push('high_confidence');
|
|
685
|
+
}
|
|
686
|
+
return { score, reasons };
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function targetHintForAction(toolName, input = {}) {
|
|
690
|
+
if (toolName === 'apply_patch') return firstPatchFile(input.patch_text || input.patch || '');
|
|
691
|
+
return input.file_path || input.path || input.target_file || input.targetFile || null;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function firstPatchFile(patchText = '') {
|
|
695
|
+
const match = String(patchText || '').match(/^\*\*\* (?:Update|Add|Delete) File:\s*(.+)$/m);
|
|
696
|
+
return match ? match[1].trim() : null;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function pathSuffix(value = '') {
|
|
700
|
+
const normalized = normalizePathish(value);
|
|
701
|
+
if (!normalized) return '';
|
|
702
|
+
const parts = normalized.split('/').filter(Boolean);
|
|
703
|
+
return parts.slice(-3).join('/');
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function samePathish(a, b) {
|
|
707
|
+
const left = normalizePathish(a);
|
|
708
|
+
const right = normalizePathish(b);
|
|
709
|
+
return Boolean(left && right && (left === right || left.endsWith(`/${right}`) || right.endsWith(`/${left}`)));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function normalizePathish(value) {
|
|
713
|
+
return String(value || '').replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '') || '';
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function normalizeCommand(command = '') {
|
|
717
|
+
return String(command || '').replace(/\r\n/g, '\n').replace(/[ \t]+/g, ' ').trim();
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function sha256(value = '') {
|
|
721
|
+
return crypto.createHash('sha256').update(String(value)).digest('hex');
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function shortHash(value = '') {
|
|
725
|
+
return sha256(String(value)).slice(0, 16);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function stringifyJson(value, space = 0) {
|
|
729
|
+
return JSON.stringify(safeJsonValue(value), null, space);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function parseJson(value, fallback) {
|
|
733
|
+
try {
|
|
734
|
+
return JSON.parse(String(value || ''));
|
|
735
|
+
} catch {
|
|
736
|
+
return fallback;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function safeJsonObject(value) {
|
|
741
|
+
const safe = safeJsonValue(value);
|
|
742
|
+
return safe && typeof safe === 'object' && !Array.isArray(safe) ? safe : {};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function safeJsonValue(value) {
|
|
746
|
+
try {
|
|
747
|
+
return JSON.parse(JSON.stringify(value ?? null));
|
|
748
|
+
} catch {
|
|
749
|
+
return String(value ?? '');
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function safeName(value) {
|
|
754
|
+
return String(value || 'value')
|
|
755
|
+
.replace(/[^A-Za-z0-9_.:-]+/g, '_')
|
|
756
|
+
.replace(/^_+|_+$/g, '')
|
|
757
|
+
.slice(0, 80) || 'value';
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function truncateUtf8(text, maxBytes) {
|
|
761
|
+
const value = String(text || '');
|
|
762
|
+
if (Buffer.byteLength(value, 'utf8') <= maxBytes) return value;
|
|
763
|
+
let bytes = 0;
|
|
764
|
+
let out = '';
|
|
765
|
+
for (const char of value) {
|
|
766
|
+
const next = Buffer.byteLength(char, 'utf8');
|
|
767
|
+
if (bytes + next > maxBytes) break;
|
|
768
|
+
out += char;
|
|
769
|
+
bytes += next;
|
|
770
|
+
}
|
|
771
|
+
return `${out}\n[action memory payload truncated; full content stored as side artifact]`;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function truncateChars(text, maxChars) {
|
|
775
|
+
const value = String(text || '');
|
|
776
|
+
return value.length <= maxChars ? value : value.slice(0, maxChars);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function emptyToNull(value) {
|
|
780
|
+
const text = String(value || '').trim();
|
|
781
|
+
return text || null;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function sqliteTimeoutMs(env = process.env) {
|
|
785
|
+
const value = Number(env.WALL_E_ACTION_MEMORY_SQLITE_BUSY_TIMEOUT_MS || env.WALL_E_SQLITE_BUSY_TIMEOUT_MS || 1000);
|
|
786
|
+
return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 1000;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function sqliteWriteLockTimeoutMs(env = process.env) {
|
|
790
|
+
const value = Number(env.WALL_E_ACTION_MEMORY_SQLITE_WRITE_LOCK_TIMEOUT_MS || env.WALL_E_SQLITE_WRITE_LOCK_TIMEOUT_MS || 0);
|
|
791
|
+
return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function requireBetterSqlite3() {
|
|
795
|
+
try {
|
|
796
|
+
return require('better-sqlite3');
|
|
797
|
+
} catch (err) {
|
|
798
|
+
if (err?.code !== 'MODULE_NOT_FOUND' || !String(err.message || '').includes('better-sqlite3')) throw err;
|
|
799
|
+
return require(path.join(__dirname, '..', 'node_modules', 'better-sqlite3'));
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
module.exports = {
|
|
804
|
+
ActionMemoryStore,
|
|
805
|
+
DEFAULT_PREVIEW_BYTES,
|
|
806
|
+
POSITIVE_OUTCOMES,
|
|
807
|
+
SCHEMA_VERSION,
|
|
808
|
+
defaultActionMemoryArtifactsDir,
|
|
809
|
+
defaultActionMemoryDbPath,
|
|
810
|
+
defaultActionMemoryDir,
|
|
811
|
+
normalizeAction,
|
|
812
|
+
normalizeActionContext,
|
|
813
|
+
scoreActionMemoryCandidate,
|
|
814
|
+
};
|