amalgm 0.1.48 → 0.1.50
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/package.json +1 -1
- package/runtime/scripts/amalgm-mcp/config.js +1 -1
- package/runtime/scripts/amalgm-mcp/events/executor.js +31 -0
- package/runtime/scripts/amalgm-mcp/events/store.js +202 -2
- package/runtime/scripts/amalgm-mcp/fs/rest.js +348 -16
- package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +26 -5
- package/runtime/scripts/amalgm-mcp/server/http.js +2 -1
- package/runtime/scripts/amalgm-mcp/state/db.js +72 -0
- package/runtime/scripts/amalgm-mcp/state/snapshot.js +12 -1
- package/runtime/scripts/amalgm-mcp/tasks/executor.js +13 -4
- package/runtime/scripts/amalgm-mcp/tasks/scheduler.js +60 -22
- package/runtime/scripts/amalgm-mcp/tasks/store.js +783 -55
- package/runtime/scripts/amalgm-mcp/tasks/tools.js +12 -4
- package/runtime/scripts/amalgm-mcp/tests/tasks-store.test.js +113 -0
- package/runtime/scripts/chat-core/adapters/claude.js +7 -4
- package/runtime/scripts/chat-core/adapters/codex.js +12 -4
- package/runtime/scripts/chat-core/tests/native-config.test.js +127 -0
- package/runtime/scripts/chat-core/tooling/native-config.js +129 -18
- package/runtime/scripts/local-gateway.js +13 -0
package/package.json
CHANGED
|
@@ -49,7 +49,7 @@ function cleanString(value) {
|
|
|
49
49
|
// Chat server (local Next.js/Electron) — same process tree; no cloud hop.
|
|
50
50
|
const CHAT_SERVER_URL = `http://localhost:${runtimePort('chat-server')}`;
|
|
51
51
|
|
|
52
|
-
const SCHEDULER_INTERVAL_MS =
|
|
52
|
+
const SCHEDULER_INTERVAL_MS = 60_000;
|
|
53
53
|
|
|
54
54
|
// Default working directory for automated runs (was /workspace in the container).
|
|
55
55
|
const DEFAULT_CWD = process.env.AMALGM_DEFAULT_CWD || os.homedir();
|
|
@@ -12,6 +12,7 @@ const os = require('os');
|
|
|
12
12
|
const { AMALGM_COMPUTER_ID, AMALGM_USER_ID, DEFAULT_CWD } = require('../config');
|
|
13
13
|
const { runThroughChatServer } = require('../lib/chat-runner');
|
|
14
14
|
const { hasSupabase, supabaseInsert, supabasePatch } = require('../lib/supabase');
|
|
15
|
+
const { recordEventRun } = require('./store');
|
|
15
16
|
const {
|
|
16
17
|
chatInputToLegacyFields,
|
|
17
18
|
normalizeChatInput,
|
|
@@ -124,6 +125,7 @@ async function executeArtifactEvent(artifactOrTrigger, eventDef, payload, opts =
|
|
|
124
125
|
projectPath,
|
|
125
126
|
});
|
|
126
127
|
const cwd = legacy.cwd || DEFAULT_CWD;
|
|
128
|
+
const runTriggerId = triggerId || artifactOrTrigger.id;
|
|
127
129
|
const modelSelection = resolveModelSelection(
|
|
128
130
|
harness,
|
|
129
131
|
modelIdForSettings(legacy.modelId || model, harness, modelSettings),
|
|
@@ -135,6 +137,17 @@ async function executeArtifactEvent(artifactOrTrigger, eventDef, payload, opts =
|
|
|
135
137
|
`[AmalgmMCP:Event] Starting agent run for ${artifactOrTrigger.id}:${eventDef.name} (session: ${codeSessionId}, cwd: ${cwd})`,
|
|
136
138
|
);
|
|
137
139
|
|
|
140
|
+
recordEventRun(runTriggerId, {
|
|
141
|
+
runId: codeSessionId,
|
|
142
|
+
sessionId: codeSessionId,
|
|
143
|
+
triggerName: eventDef.name,
|
|
144
|
+
startedAt,
|
|
145
|
+
status: 'running',
|
|
146
|
+
harness,
|
|
147
|
+
projectPath: projectPath || null,
|
|
148
|
+
prompt: legacy.prompt,
|
|
149
|
+
}, { source: 'event_runs:start' });
|
|
150
|
+
|
|
138
151
|
if (hasSupabase()) {
|
|
139
152
|
try {
|
|
140
153
|
const metadata = {
|
|
@@ -195,6 +208,16 @@ async function executeArtifactEvent(artifactOrTrigger, eventDef, payload, opts =
|
|
|
195
208
|
`[AmalgmMCP:Event] ${artifactOrTrigger.id}:${eventDef.name} completed in ${durationMs}ms (output: ${outputText.length} chars)`,
|
|
196
209
|
);
|
|
197
210
|
|
|
211
|
+
recordEventRun(runTriggerId, {
|
|
212
|
+
runId: codeSessionId,
|
|
213
|
+
sessionId: codeSessionId,
|
|
214
|
+
triggerName: eventDef.name,
|
|
215
|
+
finishedAt: new Date().toISOString(),
|
|
216
|
+
status: 'completed',
|
|
217
|
+
durationMs,
|
|
218
|
+
outputLength: outputText.length,
|
|
219
|
+
}, { source: 'event_runs:complete' });
|
|
220
|
+
|
|
198
221
|
if (hasSupabase()) {
|
|
199
222
|
supabasePatch('sessions', 'id', codeSessionId, {
|
|
200
223
|
last_message_at: new Date().toISOString(),
|
|
@@ -207,6 +230,14 @@ async function executeArtifactEvent(artifactOrTrigger, eventDef, payload, opts =
|
|
|
207
230
|
`[AmalgmMCP:Event] ${artifactOrTrigger.id}:${eventDef.name} failed:`,
|
|
208
231
|
err.message,
|
|
209
232
|
);
|
|
233
|
+
recordEventRun(runTriggerId, {
|
|
234
|
+
runId: codeSessionId,
|
|
235
|
+
sessionId: codeSessionId,
|
|
236
|
+
triggerName: eventDef.name,
|
|
237
|
+
finishedAt: new Date().toISOString(),
|
|
238
|
+
status: 'failed',
|
|
239
|
+
error: err.message,
|
|
240
|
+
}, { source: 'event_runs:failed' });
|
|
210
241
|
if (hasSupabase()) {
|
|
211
242
|
supabasePatch('sessions', 'id', codeSessionId, {
|
|
212
243
|
last_message_at: new Date().toISOString(),
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const { EVENT_TRIGGERS_FILE, STORAGE_DIR } = require('../config');
|
|
6
6
|
const { ensureDir, readJson, writeJsonAtomic } = require('../lib/storage');
|
|
7
|
+
const { openLocalDb } = require('../state/db');
|
|
7
8
|
const {
|
|
8
9
|
chatInputToLegacyFields,
|
|
9
10
|
getChatInputText,
|
|
@@ -11,7 +12,22 @@ const {
|
|
|
11
12
|
} = require('../../../lib/chatInput');
|
|
12
13
|
const { DEFAULT_SELECTED_MODELS, getSelectedModel } = require('../lib/prefs');
|
|
13
14
|
const credentialAdapter = require('../../credential-adapter');
|
|
14
|
-
const { appendStateEvent } = require('../state/events');
|
|
15
|
+
const { appendStateEvent, insertStateEvent, publishStateEvent } = require('../state/events');
|
|
16
|
+
|
|
17
|
+
const DEFAULT_RUN_LIMIT = 100;
|
|
18
|
+
|
|
19
|
+
function nowIso() {
|
|
20
|
+
return new Date().toISOString();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseJson(value, fallback = null) {
|
|
24
|
+
if (typeof value !== 'string' || !value) return fallback;
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(value);
|
|
27
|
+
} catch {
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
15
31
|
|
|
16
32
|
function normalizeStoredTrigger(trigger) {
|
|
17
33
|
const harness =
|
|
@@ -78,6 +94,7 @@ function migrateTriggersData(data) {
|
|
|
78
94
|
|
|
79
95
|
function ensureTriggersDirs() {
|
|
80
96
|
ensureDir(STORAGE_DIR);
|
|
97
|
+
openLocalDb();
|
|
81
98
|
if (!readJson(EVENT_TRIGGERS_FILE, null)) {
|
|
82
99
|
writeJsonAtomic(EVENT_TRIGGERS_FILE, { triggers: [] });
|
|
83
100
|
}
|
|
@@ -110,4 +127,187 @@ function saveEventTriggers(data, options = {}) {
|
|
|
110
127
|
publishEventTriggersChange(data, options.source || 'event-triggers:save');
|
|
111
128
|
}
|
|
112
129
|
|
|
113
|
-
|
|
130
|
+
function rowToEventRun(row) {
|
|
131
|
+
if (!row) return null;
|
|
132
|
+
const parsed = parseJson(row.run_json, {});
|
|
133
|
+
return {
|
|
134
|
+
...parsed,
|
|
135
|
+
id: row.id,
|
|
136
|
+
runId: row.id,
|
|
137
|
+
triggerId: row.trigger_id,
|
|
138
|
+
status: row.status,
|
|
139
|
+
startedAt: row.started_at || parsed.startedAt || null,
|
|
140
|
+
finishedAt: row.finished_at || parsed.finishedAt || null,
|
|
141
|
+
sessionId: row.session_id || parsed.sessionId || null,
|
|
142
|
+
harness: row.harness || parsed.harness || null,
|
|
143
|
+
projectPath: row.project_path || parsed.projectPath || null,
|
|
144
|
+
error: row.error || parsed.error || null,
|
|
145
|
+
createdAt: row.created_at,
|
|
146
|
+
updatedAt: row.updated_at,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function eventRunRowParams(run) {
|
|
151
|
+
const timestamp = nowIso();
|
|
152
|
+
const createdAt = run.createdAt || timestamp;
|
|
153
|
+
const updatedAt = run.updatedAt || timestamp;
|
|
154
|
+
const runJson = {
|
|
155
|
+
...run,
|
|
156
|
+
id: run.id,
|
|
157
|
+
runId: run.id,
|
|
158
|
+
triggerId: run.triggerId,
|
|
159
|
+
status: run.status || 'running',
|
|
160
|
+
startedAt: run.startedAt || null,
|
|
161
|
+
finishedAt: run.finishedAt || null,
|
|
162
|
+
sessionId: run.sessionId || null,
|
|
163
|
+
harness: run.harness || null,
|
|
164
|
+
projectPath: run.projectPath || null,
|
|
165
|
+
error: run.error || null,
|
|
166
|
+
createdAt,
|
|
167
|
+
updatedAt,
|
|
168
|
+
};
|
|
169
|
+
return {
|
|
170
|
+
id: run.id,
|
|
171
|
+
trigger_id: run.triggerId,
|
|
172
|
+
status: run.status || 'running',
|
|
173
|
+
started_at: run.startedAt || null,
|
|
174
|
+
finished_at: run.finishedAt || null,
|
|
175
|
+
session_id: run.sessionId || null,
|
|
176
|
+
harness: run.harness || null,
|
|
177
|
+
project_path: run.projectPath || null,
|
|
178
|
+
error: run.error || null,
|
|
179
|
+
run_json: JSON.stringify(runJson),
|
|
180
|
+
created_at: createdAt,
|
|
181
|
+
updated_at: updatedAt,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function upsertEventRunRow(db, run) {
|
|
186
|
+
const params = eventRunRowParams(run);
|
|
187
|
+
db.prepare(`
|
|
188
|
+
INSERT INTO event_runs (
|
|
189
|
+
id,
|
|
190
|
+
trigger_id,
|
|
191
|
+
status,
|
|
192
|
+
started_at,
|
|
193
|
+
finished_at,
|
|
194
|
+
session_id,
|
|
195
|
+
harness,
|
|
196
|
+
project_path,
|
|
197
|
+
error,
|
|
198
|
+
run_json,
|
|
199
|
+
created_at,
|
|
200
|
+
updated_at
|
|
201
|
+
)
|
|
202
|
+
VALUES (
|
|
203
|
+
@id,
|
|
204
|
+
@trigger_id,
|
|
205
|
+
@status,
|
|
206
|
+
@started_at,
|
|
207
|
+
@finished_at,
|
|
208
|
+
@session_id,
|
|
209
|
+
@harness,
|
|
210
|
+
@project_path,
|
|
211
|
+
@error,
|
|
212
|
+
@run_json,
|
|
213
|
+
@created_at,
|
|
214
|
+
@updated_at
|
|
215
|
+
)
|
|
216
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
217
|
+
trigger_id = excluded.trigger_id,
|
|
218
|
+
status = excluded.status,
|
|
219
|
+
started_at = excluded.started_at,
|
|
220
|
+
finished_at = excluded.finished_at,
|
|
221
|
+
session_id = excluded.session_id,
|
|
222
|
+
harness = excluded.harness,
|
|
223
|
+
project_path = excluded.project_path,
|
|
224
|
+
error = excluded.error,
|
|
225
|
+
run_json = excluded.run_json,
|
|
226
|
+
updated_at = excluded.updated_at
|
|
227
|
+
`).run(params);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function mergeEventRunEntry(existing, triggerId, entry) {
|
|
231
|
+
const timestamp = nowIso();
|
|
232
|
+
const startedAt = entry.startedAt || existing?.startedAt || timestamp;
|
|
233
|
+
const finishedAt = entry.finishedAt || existing?.finishedAt || null;
|
|
234
|
+
const events = Array.isArray(existing?.events) ? existing.events.slice(-99) : [];
|
|
235
|
+
events.push({ ...entry, recordedAt: timestamp });
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
...(existing || {}),
|
|
239
|
+
id: entry.runId || entry.id || existing?.id || entry.sessionId || timestamp,
|
|
240
|
+
runId: entry.runId || entry.id || existing?.id || entry.sessionId || null,
|
|
241
|
+
triggerId,
|
|
242
|
+
triggerName: entry.triggerName || existing?.triggerName || null,
|
|
243
|
+
status: entry.status || existing?.status || 'running',
|
|
244
|
+
startedAt,
|
|
245
|
+
finishedAt,
|
|
246
|
+
sessionId: entry.sessionId || existing?.sessionId || null,
|
|
247
|
+
harness: entry.harness || existing?.harness || null,
|
|
248
|
+
projectPath: entry.projectPath || existing?.projectPath || null,
|
|
249
|
+
error: entry.error || existing?.error || null,
|
|
250
|
+
durationMs: entry.durationMs ?? existing?.durationMs ?? null,
|
|
251
|
+
outputLength: entry.outputLength ?? existing?.outputLength ?? null,
|
|
252
|
+
prompt: entry.prompt || existing?.prompt || null,
|
|
253
|
+
events,
|
|
254
|
+
createdAt: existing?.createdAt || startedAt,
|
|
255
|
+
updatedAt: timestamp,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function listEventRuns(options = {}) {
|
|
260
|
+
ensureTriggersDirs();
|
|
261
|
+
const limit = Math.max(1, Math.min(Number(options.limit) || DEFAULT_RUN_LIMIT, 1000));
|
|
262
|
+
const db = openLocalDb();
|
|
263
|
+
const rows = options.triggerId
|
|
264
|
+
? db.prepare(`
|
|
265
|
+
SELECT * FROM event_runs
|
|
266
|
+
WHERE trigger_id = ?
|
|
267
|
+
ORDER BY datetime(updated_at) DESC, datetime(started_at) DESC
|
|
268
|
+
LIMIT ?
|
|
269
|
+
`).all(options.triggerId, limit)
|
|
270
|
+
: db.prepare(`
|
|
271
|
+
SELECT * FROM event_runs
|
|
272
|
+
ORDER BY datetime(updated_at) DESC, datetime(started_at) DESC
|
|
273
|
+
LIMIT ?
|
|
274
|
+
`).all(limit);
|
|
275
|
+
return rows.map(rowToEventRun).filter(Boolean);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function recordEventRun(triggerId, entry, options = {}) {
|
|
279
|
+
if (!triggerId || (!entry?.runId && !entry?.id && !entry?.sessionId)) return null;
|
|
280
|
+
ensureTriggersDirs();
|
|
281
|
+
|
|
282
|
+
const db = openLocalDb();
|
|
283
|
+
const source = options.source || entry.source || 'event_runs:record';
|
|
284
|
+
let run = null;
|
|
285
|
+
let event = null;
|
|
286
|
+
|
|
287
|
+
db.transaction(() => {
|
|
288
|
+
const runId = entry.runId || entry.id || entry.sessionId;
|
|
289
|
+
const existingRow = db.prepare('SELECT * FROM event_runs WHERE id = ?').get(runId);
|
|
290
|
+
const existing = rowToEventRun(existingRow);
|
|
291
|
+
run = mergeEventRunEntry(existing, triggerId, { ...entry, runId });
|
|
292
|
+
upsertEventRunRow(db, run);
|
|
293
|
+
run = rowToEventRun(db.prepare('SELECT * FROM event_runs WHERE id = ?').get(run.id));
|
|
294
|
+
event = insertStateEvent(db, {
|
|
295
|
+
resource: 'event_runs',
|
|
296
|
+
op: existing ? 'update' : 'insert',
|
|
297
|
+
id: run.id,
|
|
298
|
+
value: run,
|
|
299
|
+
source,
|
|
300
|
+
});
|
|
301
|
+
})();
|
|
302
|
+
|
|
303
|
+
if (event) publishStateEvent(event);
|
|
304
|
+
return run;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
ensureTriggersDirs,
|
|
309
|
+
listEventRuns,
|
|
310
|
+
loadEventTriggers,
|
|
311
|
+
recordEventRun,
|
|
312
|
+
saveEventTriggers,
|
|
313
|
+
};
|