@awak-app/simy-cli 0.7.41 → 0.7.42
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
package/src/cli-contract.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./durable-local-task-steps-contract.js";
|
|
15
15
|
import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
|
|
16
16
|
|
|
17
|
-
export const CLI_VERSION = "0.7.
|
|
17
|
+
export const CLI_VERSION = "0.7.42";
|
|
18
18
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
19
19
|
|
|
20
20
|
export function narrativeImageCapabilityMetadata() {
|
|
@@ -1,20 +1,53 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const catalogScope = status => JSON.stringify([status.webOrigin, status.auth?.userId, status.auth?.state,
|
|
4
|
+
status.access?.organizationId, status.access?.revision, status.access?.state]);
|
|
5
|
+
|
|
6
|
+
function updateRows(previous, incoming, full, { unchanged = false, immutableInput = false, copyOnWrite = false } = {}) {
|
|
4
7
|
if (full || previous === null || previous.length !== incoming.length) {
|
|
5
|
-
const rows = structuredClone(incoming);
|
|
8
|
+
const rows = immutableInput ? incoming : structuredClone(incoming);
|
|
6
9
|
return { rows, payload: { rows } };
|
|
7
10
|
}
|
|
8
11
|
if (unchanged) return { rows: previous, payload: { count: previous.length, changes: [] } };
|
|
9
12
|
const changes = [];
|
|
13
|
+
let rows = previous;
|
|
10
14
|
for (let index = 0; index < incoming.length; index++) {
|
|
11
15
|
if (!isDeepStrictEqual(previous[index], incoming[index])) {
|
|
12
|
-
const row = structuredClone(incoming[index]);
|
|
13
|
-
|
|
16
|
+
const row = immutableInput ? incoming[index] : structuredClone(incoming[index]);
|
|
17
|
+
if (!immutableInput) {
|
|
18
|
+
if (copyOnWrite && rows === previous) rows = previous.slice();
|
|
19
|
+
rows[index] = row;
|
|
20
|
+
}
|
|
14
21
|
changes.push([index, row]);
|
|
15
22
|
}
|
|
16
23
|
}
|
|
17
|
-
return { rows:
|
|
24
|
+
return { rows: immutableInput ? incoming : rows, payload: { count: incoming.length, changes } };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Detach mutable producer data once. Unchanged catalog rows may be shared only
|
|
28
|
+
// between these owned read-only snapshots; changed rows get detached copies and
|
|
29
|
+
// a new array. Older queued frames and durable writes must remain immutable.
|
|
30
|
+
export function createDesktopStatusSnapshot() {
|
|
31
|
+
let rows = null, candidates = null, scope = null;
|
|
32
|
+
return status => {
|
|
33
|
+
const supervision = status?.sessionSupervision;
|
|
34
|
+
if (!Array.isArray(supervision?.sessions)) {
|
|
35
|
+
rows = candidates = scope = null;
|
|
36
|
+
return structuredClone(status);
|
|
37
|
+
}
|
|
38
|
+
const nextScope = catalogScope(status);
|
|
39
|
+
if (nextScope !== scope) { rows = candidates = null; scope = nextScope; }
|
|
40
|
+
const hasCandidates = Array.isArray(supervision.selectionCandidates);
|
|
41
|
+
const detached = structuredClone({ ...status, sessionSupervision: { ...supervision,
|
|
42
|
+
sessions: [], ...(hasCandidates ? { selectionCandidates: [] } : {}) } });
|
|
43
|
+
rows = updateRows(rows, supervision.sessions, false, { copyOnWrite: true }).rows;
|
|
44
|
+
detached.sessionSupervision.sessions = rows;
|
|
45
|
+
if (hasCandidates) {
|
|
46
|
+
candidates = updateRows(candidates, supervision.selectionCandidates, false, { copyOnWrite: true }).rows;
|
|
47
|
+
detached.sessionSupervision.selectionCandidates = candidates;
|
|
48
|
+
} else candidates = null;
|
|
49
|
+
return detached;
|
|
50
|
+
};
|
|
18
51
|
}
|
|
19
52
|
|
|
20
53
|
// One encoder per ordered output stream. Call only when a frame actually enters
|
|
@@ -30,11 +63,10 @@ export function createDesktopCatalogEncoder({ immutableInput = false } = {}) {
|
|
|
30
63
|
}
|
|
31
64
|
const status = frame.status;
|
|
32
65
|
const incoming = status.sessionSupervision.sessions;
|
|
33
|
-
const identity =
|
|
34
|
-
status.access?.revision, status.access?.state]);
|
|
66
|
+
const identity = catalogScope(status);
|
|
35
67
|
const previous = revision++;
|
|
36
68
|
const full = rows === null || scope !== identity || rows.length !== incoming.length;
|
|
37
|
-
const snapshot = updateRows(rows, incoming, full, immutableInput && sourceRows === incoming);
|
|
69
|
+
const snapshot = updateRows(rows, incoming, full, { immutableInput, unchanged: immutableInput && sourceRows === incoming });
|
|
38
70
|
rows = snapshot.rows; sourceRows = incoming; scope = identity;
|
|
39
71
|
const catalog = { version: 1, revision, scope, ...(full ? {} : { baseRevision: previous }), ...snapshot.payload };
|
|
40
72
|
const { sessions, ...metadata } = status.sessionSupervision;
|
|
@@ -42,7 +74,8 @@ export function createDesktopCatalogEncoder({ immutableInput = false } = {}) {
|
|
|
42
74
|
if (Array.isArray(metadata.selectionCandidates)) {
|
|
43
75
|
// This is another complete directory, used by the conversation picker.
|
|
44
76
|
// Do not silently retransmit it as supposedly small status metadata.
|
|
45
|
-
const snapshot = updateRows(candidates, metadata.selectionCandidates, full,
|
|
77
|
+
const snapshot = updateRows(candidates, metadata.selectionCandidates, full,
|
|
78
|
+
{ immutableInput, unchanged: immutableInput && sourceCandidates === metadata.selectionCandidates });
|
|
46
79
|
candidates = snapshot.rows; sourceCandidates = metadata.selectionCandidates; catalog.selectionCandidates = snapshot.payload;
|
|
47
80
|
const { selectionCandidates, ...small } = metadata;
|
|
48
81
|
supervision = small;
|
package/src/desktop-host.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createDesktopFrameWriter } from './desktop-frame-writer.js';
|
|
2
|
-
import { createDesktopCatalogEncoder } from './desktop-catalog-frames.js';
|
|
2
|
+
import { createDesktopCatalogEncoder, createDesktopStatusSnapshot } from './desktop-catalog-frames.js';
|
|
3
3
|
import { boundedDiscovery } from './session-supervision/progressive-discovery.js';
|
|
4
4
|
import { createCatalogRefreshPolicy } from './session-supervision/catalog-refresh.js';
|
|
5
5
|
import { supervisedSession } from './session-supervision/discovery-window.js';
|
|
@@ -197,6 +197,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
197
197
|
// deltas later, after backpressure coalescing. Registries may mutate objects,
|
|
198
198
|
// so the comparison snapshot must remain detached from producer references.
|
|
199
199
|
let encodedStatus = null, lastStatus = null, lastStatusFrame = null, updateProgress = null;
|
|
200
|
+
const snapshotStatus = createDesktopStatusSnapshot();
|
|
200
201
|
let projectCatalogFrame = status => status;
|
|
201
202
|
let resolveFirstStatus;
|
|
202
203
|
const firstStatus = new Promise(resolve => { resolveFirstStatus = resolve; });
|
|
@@ -206,6 +207,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
206
207
|
let acceptanceReadbackSequence = 0;
|
|
207
208
|
let remoteHosts;
|
|
208
209
|
const emitStatus = status => {
|
|
210
|
+
let catalogObservation;
|
|
209
211
|
status = projectCatalogFrame(status);
|
|
210
212
|
status = { ...status, windowUpdate: { version: 1, preservesWork: Boolean(ownerTransport), prepared: windowUpdatePrepared } };
|
|
211
213
|
const updateSettings = updater?.snapshot?.() || {};
|
|
@@ -229,16 +231,20 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
229
231
|
if (preferenceScope && desktopAccessReady(status.access) && status.sessionSupervision?.sessions) {
|
|
230
232
|
const catalogScope = preferenceScope;
|
|
231
233
|
status = { ...status, sessionSupervision: { ...status.sessionSupervision, sessions: status.sessionSupervision.sessions.filter(row => controllerPreferences.get(catalogScope, row).deleted !== true).map(row => { const saved = controllerPreferences.get(catalogScope, row); return { ...row, controllerPreferences: saved, ...(saved.pinned === undefined ? {} : { listPinned: saved.pinned, listPinScopes: saved.pinScopes }), ...(saved.hidden === undefined ? {} : { localHidden: saved.hidden }) }; }) } };
|
|
232
|
-
|
|
233
|
-
void controllerPreferences.replaceCatalog(catalogScope, catalog).catch(() => {});
|
|
234
|
+
catalogObservation = { scope: catalogScope, observedAt: Date.now() };
|
|
234
235
|
}
|
|
235
236
|
if (updateProgress) status = { ...status, update: { ...status.update, ...updateProgress } };
|
|
236
237
|
const { sampledAt, ...content } = status;
|
|
237
238
|
if (!lastStatus || !isDeepStrictEqual(content, lastStatus)) {
|
|
238
239
|
if (!catalogEncoder) encodedStatus = JSON.stringify(content).slice(1, -1);
|
|
239
|
-
lastStatus =
|
|
240
|
+
lastStatus = snapshotStatus(content);
|
|
240
241
|
}
|
|
241
242
|
const ownedStatus = { ...lastStatus, ...(sampledAt === undefined ? {} : { sampledAt }) };
|
|
243
|
+
if (catalogObservation) {
|
|
244
|
+
const catalog = { observedAt: catalogObservation.observedAt, sessions: ownedStatus.sessionSupervision.sessions,
|
|
245
|
+
hosts: ownedStatus.remoteHosts?.items || [], providerLinks: structuredClone(providerLinks) };
|
|
246
|
+
void controllerPreferences.replaceCatalog(catalogObservation.scope, catalog, { immutableInput: true }).catch(() => {});
|
|
247
|
+
}
|
|
242
248
|
lastStatusFrame = ownedStatus;
|
|
243
249
|
ownerTransport?.publishDesktop?.({ type: 'status', status: ownedStatus });
|
|
244
250
|
const observation = sampledAt === undefined ? '' : `"sampledAt":${JSON.stringify(sampledAt)}${encodedStatus ? ',' : ''}`;
|
|
@@ -41,11 +41,13 @@ export function createControllerStore(root) {
|
|
|
41
41
|
await persist(scope, next); return result;
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
-
function replaceCatalog(scope, catalog) {
|
|
44
|
+
function replaceCatalog(scope, catalog, { immutableInput = false } = {}) {
|
|
45
45
|
// Own the incoming snapshot immediately, but do not clone the previous
|
|
46
46
|
// multi-MiB catalog just to replace it. Preference writes still serialize
|
|
47
47
|
// on the same queue and commit only after their atomic write succeeds.
|
|
48
|
-
|
|
48
|
+
// Only the desktop's detached read-only snapshots may be borrowed. Generic
|
|
49
|
+
// callers retain immediate mutation isolation, including while queued.
|
|
50
|
+
const snapshot = immutableInput ? catalog : structuredClone(catalog);
|
|
49
51
|
return enqueue(async () => {
|
|
50
52
|
const settings = { ...await load(scope) };
|
|
51
53
|
delete settings.catalog;
|