amalgm 0.1.73 → 0.1.74
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amalgm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.74",
|
|
4
4
|
"description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
17
|
"sync-runtime": "node ../../scripts/sync-npm-package-runtime.mjs",
|
|
18
|
+
"prepack": "node ../../scripts/sync-npm-package-runtime.mjs",
|
|
18
19
|
"pack:dry": "npm pack --dry-run",
|
|
19
20
|
"check": "node --check bin/amalgm.js && node --check lib/auth-store.js && node --check lib/cli.js && node --check lib/paths.js && node --check lib/process-cleanup.js && node --check lib/runtime-identity.js && node --check lib/runtime-manifest.js && node --check lib/service.js && node --check lib/state-migration.js && node --check lib/supervisor.js && node --check lib/tunnel-chat.js && node --check lib/tunnel-events.js && node --check runtime/lib/runtime-manifest.js && node --check runtime/scripts/runtime-auth.js && node --check runtime/scripts/proxy-token-store.js && node --check runtime/scripts/local-gateway.js && node --check runtime/scripts/port-monitor.js && node --check runtime/scripts/chat-server.js && node --check runtime/scripts/chat-server/index.js && node --check runtime/scripts/chat-server/config.js && node --check runtime/scripts/chat-core/tooling/native-binaries.js && node --check runtime/scripts/chat-core/tooling/package-import.js && node --check runtime/scripts/chat-core/tooling/runtime-home.js && node --check runtime/scripts/amalgm-mcp/index.js && node --check runtime/scripts/amalgm-mcp/config.js && node --check runtime/scripts/lib/project-paths.js && node --check runtime/scripts/lib/runtime-paths.js"
|
|
20
21
|
},
|
|
@@ -217,9 +217,19 @@ class ChatCore {
|
|
|
217
217
|
let stopReason = 'end_turn';
|
|
218
218
|
let sawDone = false;
|
|
219
219
|
let finalDoneEvent = null;
|
|
220
|
+
let finalDoneChunk = null;
|
|
220
221
|
|
|
221
222
|
const append = (e, options = {}) => {
|
|
222
223
|
const emitFrame = options.emitFrame !== false;
|
|
224
|
+
const visible = options.visible !== false;
|
|
225
|
+
const nextProviderSessionId = e.providerSessionId || providerSessionId;
|
|
226
|
+
const nextUsage = e.type === 'usage.final' ? e.usage : usage;
|
|
227
|
+
let chunk = null;
|
|
228
|
+
if (emitFrame) {
|
|
229
|
+
const frame = frameFor(e, { sessionId: contract.sessionId, providerSessionId: nextProviderSessionId, usage: nextUsage });
|
|
230
|
+
chunk = this.turns.addChunk(turnId, (index) => withSseId(frame, index), { visible, seal: e.type === 'done' });
|
|
231
|
+
if (!chunk) throw new Error('Raw stream write failed before event application');
|
|
232
|
+
}
|
|
223
233
|
if (e.providerSessionId) providerSessionId = e.providerSessionId;
|
|
224
234
|
if (e.type === 'usage.final') {
|
|
225
235
|
usage = e.usage;
|
|
@@ -229,27 +239,26 @@ class ChatCore {
|
|
|
229
239
|
sawDone = true;
|
|
230
240
|
stopReason = e.stopReason || stopReason;
|
|
231
241
|
finalDoneEvent = e;
|
|
242
|
+
finalDoneChunk = chunk;
|
|
232
243
|
}
|
|
233
244
|
parts.apply(e);
|
|
234
|
-
|
|
235
|
-
const frame = frameFor(e, { sessionId: contract.sessionId, providerSessionId, usage });
|
|
236
|
-
this.turns.addChunk(turnId, (index) => withSseId(frame, index));
|
|
245
|
+
return chunk;
|
|
237
246
|
};
|
|
238
247
|
|
|
239
248
|
try {
|
|
240
249
|
try {
|
|
241
250
|
const stream = await this.runtime.prompt(contract, runtimeInput);
|
|
242
|
-
for await (const e of stream) append(e, {
|
|
251
|
+
for await (const e of stream) append(e, { visible: e.type !== 'done' });
|
|
243
252
|
const current = this.turns.get(turnId);
|
|
244
253
|
if (!sawDone) {
|
|
245
254
|
append(done({
|
|
246
255
|
providerSessionId,
|
|
247
256
|
stopReason: current?.status === 'cancelling' ? 'cancelled' : stopReason,
|
|
248
|
-
}), {
|
|
257
|
+
}), { visible: false });
|
|
249
258
|
}
|
|
250
259
|
} catch (err) {
|
|
251
260
|
append(errorEvent(err.message, { providerSessionId }));
|
|
252
|
-
append(done({ providerSessionId, stopReason: 'error' }), {
|
|
261
|
+
append(done({ providerSessionId, stopReason: 'error' }), { visible: false });
|
|
253
262
|
stopReason = 'error';
|
|
254
263
|
}
|
|
255
264
|
entry.streamEnded = true;
|
|
@@ -279,7 +288,11 @@ class ChatCore {
|
|
|
279
288
|
this.turns.mark(turnId, 'save_failed');
|
|
280
289
|
throw err;
|
|
281
290
|
}
|
|
282
|
-
|
|
291
|
+
if (finalDoneChunk) {
|
|
292
|
+
this.turns.releaseChunk(turnId, finalDoneChunk.index);
|
|
293
|
+
} else {
|
|
294
|
+
append(finalDoneEvent || done({ providerSessionId, stopReason }));
|
|
295
|
+
}
|
|
283
296
|
this.turns.mark(turnId, stopReason === 'error' ? 'error' : stopReason === 'cancelled' ? 'cancelled' : 'complete');
|
|
284
297
|
this.turns.clear(turnId);
|
|
285
298
|
return { providerSessionId, usage, parts: savedParts, stopReason };
|
|
@@ -41,13 +41,28 @@ class TurnStore {
|
|
|
41
41
|
return this.turns.get(turnId) || null;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
addChunk(turnId, frame) {
|
|
44
|
+
addChunk(turnId, frame, options = {}) {
|
|
45
45
|
const entry = this.turns.get(turnId);
|
|
46
46
|
if (!entry || !['streaming', 'cancelling'].includes(entry.status)) return null;
|
|
47
|
+
if (Number.isInteger(entry.sealedIndex)) return null;
|
|
47
48
|
const index = entry.chunks.length;
|
|
48
49
|
const data = typeof frame === 'function' ? frame(index) : frame;
|
|
49
|
-
const
|
|
50
|
+
const visible = options.visible !== false;
|
|
51
|
+
const chunk = { index, data, timestamp: Date.now(), visible };
|
|
50
52
|
entry.chunks.push(chunk);
|
|
53
|
+
if (options.seal === true) entry.sealedIndex = index;
|
|
54
|
+
entry.updatedAt = Date.now();
|
|
55
|
+
if (visible) this.emit(turnId, chunk, entry);
|
|
56
|
+
return chunk;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
releaseChunk(turnId, index) {
|
|
60
|
+
const entry = this.turns.get(turnId);
|
|
61
|
+
if (!entry || !['streaming', 'cancelling'].includes(entry.status)) return null;
|
|
62
|
+
const chunk = entry.chunks.find((candidate) => candidate.index === index);
|
|
63
|
+
if (!chunk) return null;
|
|
64
|
+
if (chunk.visible !== false) return chunk;
|
|
65
|
+
chunk.visible = true;
|
|
51
66
|
entry.updatedAt = Date.now();
|
|
52
67
|
this.emit(turnId, chunk, entry);
|
|
53
68
|
return chunk;
|
|
@@ -75,9 +90,12 @@ class TurnStore {
|
|
|
75
90
|
this.listeners.delete(turnId);
|
|
76
91
|
}
|
|
77
92
|
|
|
78
|
-
chunksAfter(turnId, afterIndex = -1) {
|
|
93
|
+
chunksAfter(turnId, afterIndex = -1, options = {}) {
|
|
79
94
|
const entry = this.turns.get(turnId);
|
|
80
|
-
|
|
95
|
+
const visibleOnly = options.visibleOnly === true;
|
|
96
|
+
return entry
|
|
97
|
+
? entry.chunks.filter((chunk) => chunk.index > afterIndex && (!visibleOnly || chunk.visible !== false))
|
|
98
|
+
: [];
|
|
81
99
|
}
|
|
82
100
|
|
|
83
101
|
subscribe(turnId, callback) {
|
|
@@ -59,6 +59,10 @@ function normalizeAfterIndex(afterIndex) {
|
|
|
59
59
|
return Number.isFinite(afterIndex) ? afterIndex : -1;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function visibleChunks(entry) {
|
|
63
|
+
return entry.chunks.filter((chunk) => chunk.visible !== false);
|
|
64
|
+
}
|
|
65
|
+
|
|
62
66
|
function writeTurnTail(core, entry, sse, options = {}) {
|
|
63
67
|
const format = options.format || 'frame';
|
|
64
68
|
let lastSent = normalizeAfterIndex(options.afterIndex);
|
|
@@ -69,7 +73,7 @@ function writeTurnTail(core, entry, sse, options = {}) {
|
|
|
69
73
|
sse.write(namedSse('chunk', {
|
|
70
74
|
codeSessionId: current.sessionId,
|
|
71
75
|
chunk,
|
|
72
|
-
total: current.
|
|
76
|
+
total: visibleChunks(current).length,
|
|
73
77
|
}));
|
|
74
78
|
} else {
|
|
75
79
|
sse.write(chunk.data);
|
|
@@ -81,7 +85,7 @@ function writeTurnTail(core, entry, sse, options = {}) {
|
|
|
81
85
|
if (format === 'raw-temp') {
|
|
82
86
|
sse.write(namedSse('complete', {
|
|
83
87
|
codeSessionId: current.sessionId,
|
|
84
|
-
total: current.
|
|
88
|
+
total: visibleChunks(current).length,
|
|
85
89
|
}));
|
|
86
90
|
sse.end();
|
|
87
91
|
return;
|
|
@@ -101,7 +105,7 @@ function writeTurnTail(core, entry, sse, options = {}) {
|
|
|
101
105
|
});
|
|
102
106
|
sse.addCleanup(off);
|
|
103
107
|
|
|
104
|
-
const existing = entry.
|
|
108
|
+
const existing = visibleChunks(entry).filter((chunk) => chunk.index > lastSent);
|
|
105
109
|
if (format === 'raw-temp') {
|
|
106
110
|
sse.write(namedSse('existing', {
|
|
107
111
|
codeSessionId: entry.sessionId,
|
|
@@ -129,6 +129,11 @@ test('complete frame waits for assistant message save', async () => {
|
|
|
129
129
|
assert.equal(frames.some((frame) => frame.includes('agent_message_chunk')), true, 'content should still stream immediately');
|
|
130
130
|
assert.equal(frames.some((frame) => frame.includes('"complete"')), false, 'complete must not outrun assistant save');
|
|
131
131
|
assert.equal(core.active('session-test').active, true, 'turn remains active while persistence is pending');
|
|
132
|
+
const pendingEntry = core.turns.latest('session-test');
|
|
133
|
+
const pendingComplete = pendingEntry.chunks.find((chunk) => chunk.data.includes('"_type":"complete"'));
|
|
134
|
+
assert.ok(pendingComplete, 'raw-temp stores final complete before assistant save');
|
|
135
|
+
assert.equal(pendingComplete.visible, false, 'final complete is hidden from UI until assistant save succeeds');
|
|
136
|
+
assert.equal(pendingEntry.sealedIndex, pendingComplete.index, 'final complete seals the raw-temp log');
|
|
132
137
|
|
|
133
138
|
resolveAssistantSave();
|
|
134
139
|
await run;
|