@awak-app/simy-cli 0.7.39 → 0.7.40

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": "@awak-app/simy-cli",
3
- "version": "0.7.39",
3
+ "version": "0.7.40",
4
4
  "description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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.39";
17
+ export const CLI_VERSION = "0.7.40";
18
18
  export const CLI_API_CONTRACT_VERSION = 10;
19
19
 
20
20
  export function narrativeImageCapabilityMetadata() {
@@ -222,8 +222,8 @@ export async function startDesktopHost({ input = process.stdin, output = process
222
222
  if (preferenceScope && desktopAccessReady(status.access) && status.sessionSupervision?.sessions) {
223
223
  const catalogScope = preferenceScope;
224
224
  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 }) }; }) } };
225
- const catalog = { observedAt: Date.now(), sessions: structuredClone(status.sessionSupervision.sessions), hosts: structuredClone(status.remoteHosts?.items || []), providerLinks: structuredClone(providerLinks) };
226
- void controllerPreferences.change(catalogScope, state => { state.catalog = catalog; }).catch(() => {});
225
+ const catalog = { observedAt: Date.now(), sessions: status.sessionSupervision.sessions, hosts: status.remoteHosts?.items || [], providerLinks };
226
+ void controllerPreferences.replaceCatalog(catalogScope, catalog).catch(() => {});
227
227
  }
228
228
  if (updateProgress) status = { ...status, update: { ...status.update, ...updateProgress } };
229
229
  ownerTransport?.publishDesktop?.({ type: 'status', status });
@@ -987,9 +987,9 @@ export async function startDesktopHost({ input = process.stdin, output = process
987
987
  // their replacement is ready, while fresh access/provider data wins.
988
988
  if (cached) status = { ...cached, ...status };
989
989
  status.deliveryHooks = sameScope && lastStatus?.deliveryHooks
990
- ? structuredClone(lastStatus.deliveryHooks) : { runs: [], providers: {} };
990
+ ? cached?.deliveryHooks || structuredClone(lastStatus.deliveryHooks) : { runs: [], providers: {} };
991
991
  status.sessionSupervision = sameScope && lastStatus?.sessionSupervision
992
- ? structuredClone(lastStatus.sessionSupervision) : { sessions: [], providers: {}, refreshedAt: null, initialLoading: desktopAccessReady(currentAccess) };
992
+ ? cached?.sessionSupervision || structuredClone(lastStatus.sessionSupervision) : { sessions: [], providers: {}, refreshedAt: null, initialLoading: desktopAccessReady(currentAccess) };
993
993
  // Complete fast frames must retain the current saved preference so OFF
994
994
  // remains available while unrelated conversation enrichment is waiting.
995
995
  status.nativeDevelopmentReports = sameScope && sameReportController(nativeDevelopmentReportsController, {
@@ -1023,7 +1023,15 @@ export async function startDesktopHost({ input = process.stdin, output = process
1023
1023
  status.sqmChecks = { activeCount: 0, items: [] };
1024
1024
  status.sqmTasks = { activeCount: status.sqmTasks?.activeCount || 0, items: [] };
1025
1025
  }
1026
- if (!closed && !relay) { connectionRevision += 1; latestConnectionStatus = structuredClone(status); emitStatus(status); }
1026
+ if (!closed && !relay) {
1027
+ connectionRevision += 1;
1028
+ // A finishing full projection supplies these two fields itself. Keep
1029
+ // only the fresh connection envelope for that merge, not a second copy
1030
+ // of the complete historical catalog that will immediately be discarded.
1031
+ const { deliveryHooks, sessionSupervision, ...connection } = status;
1032
+ latestConnectionStatus = structuredClone(connection);
1033
+ emitStatus(status);
1034
+ }
1027
1035
  }).finally(() => { connectionPublishing = null; });
1028
1036
  return connectionPublishing;
1029
1037
  };
@@ -23,19 +23,36 @@ export function createControllerStore(root) {
23
23
  if (state.version !== 1 || !state.conversations || !state.events) throw new Error('Invalid controller settings.');
24
24
  states.set(scope, state); return state;
25
25
  }
26
+ function enqueue(work) {
27
+ const pending = queue.catch(() => {}).then(work);
28
+ queue = pending; return pending;
29
+ }
30
+ async function persist(scope, next) {
31
+ await mkdir(join(root, 'controller'), { recursive: true, mode: 0o700 });
32
+ const path = file(scope), temp = `${path}.${randomUUID()}.tmp`;
33
+ try { await writeFile(temp, JSON.stringify(next) + '\n', { mode: 0o600 }); await rename(temp, path); }
34
+ finally { await rm(temp, { force: true }); }
35
+ states.set(scope, next);
36
+ }
26
37
  function change(scope, mutate) {
27
- const work = queue.catch(() => {}).then(async () => {
38
+ return enqueue(async () => {
28
39
  const next = structuredClone(await load(scope));
29
40
  const result = await mutate(next);
30
- await mkdir(join(root, 'controller'), { recursive: true, mode: 0o700 });
31
- const path = file(scope), temp = `${path}.${randomUUID()}.tmp`;
32
- try { await writeFile(temp, JSON.stringify(next) + '\n', { mode: 0o600 }); await rename(temp, path); }
33
- finally { await rm(temp, { force: true }); }
34
- states.set(scope, next); return result;
41
+ await persist(scope, next); return result;
42
+ });
43
+ }
44
+ function replaceCatalog(scope, catalog) {
45
+ // Own the incoming snapshot immediately, but do not clone the previous
46
+ // multi-MiB catalog just to replace it. Preference writes still serialize
47
+ // on the same queue and commit only after their atomic write succeeds.
48
+ const snapshot = structuredClone(catalog);
49
+ return enqueue(async () => {
50
+ const settings = { ...await load(scope) };
51
+ delete settings.catalog;
52
+ await persist(scope, { ...structuredClone(settings), catalog: snapshot });
35
53
  });
36
- queue = work; return work;
37
54
  }
38
- return { load, change, peek: scope => states.get(scope),
55
+ return { load, change, replaceCatalog, peek: scope => states.get(scope),
39
56
  get(scope, target) { return states.get(scope)?.conversations[conversationKey(target)] || {}; },
40
57
  async set(scope, target, values) {
41
58
  return change(scope, state => { const key = conversationKey(target); state.conversations[key] = { ...state.conversations[key], ...values }; });