@awak-app/simy-cli 0.7.49 → 0.7.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
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.50";
|
|
18
18
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
19
19
|
|
|
20
20
|
export function narrativeImageCapabilityMetadata() {
|
|
@@ -87,9 +87,8 @@ function applyProviderConsent(record, link) {
|
|
|
87
87
|
...(record.run.managed ? { managed: { ...record.run.managed, autoRecovery: false } } : {}) } } : {}) };
|
|
88
88
|
}
|
|
89
89
|
const readArchiveFence = async file => JSON.parse(await readFile(file, 'utf8')).archived !== false;
|
|
90
|
-
async function applyArchiveFence(root, record, readFence = readArchiveFence) {
|
|
90
|
+
async function applyArchiveFence(root, record, readFence = readArchiveFence, file = record && `${recordPath(root, record.provider, record.session)}.archive-fence`) {
|
|
91
91
|
if (!record) return record;
|
|
92
|
-
const file = `${recordPath(root, record.provider, record.session)}.archive-fence`;
|
|
93
92
|
let archived;
|
|
94
93
|
try { archived = await readFence(file); }
|
|
95
94
|
catch (error) { if (error.code === 'ENOENT') return record; archived = true; }
|
|
@@ -111,15 +110,16 @@ export async function recordProviderArchive(root, provider, session, archived, o
|
|
|
111
110
|
await rename(temporary, file);
|
|
112
111
|
} finally { await rm(temporary, { force: true }); await releaseStateLock(lock, token); }
|
|
113
112
|
}
|
|
114
|
-
async function currentProviderConsent(root, records, readFence = readArchiveFence) {
|
|
113
|
+
async function currentProviderConsent(root, records, readFence = readArchiveFence, recordNames) {
|
|
115
114
|
const links = Object.fromEntries(await Promise.all(['codex', 'claude'].map(async provider => [provider, await providerLinkState(root, provider)])));
|
|
116
115
|
let archiveNames;
|
|
117
116
|
try { archiveNames = new Set((await readdir(root)).filter(name => name.endsWith('.archive-fence'))); }
|
|
118
117
|
catch { /* A failed index read falls back to fresh per-record fencing. */ }
|
|
119
118
|
return Promise.all(records.map(record => {
|
|
120
119
|
const current = applyProviderConsent(record, links[record.provider] || {});
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
const name = recordNames?.get(record) || basename(recordPath(root, record.provider, record.session));
|
|
121
|
+
if (archiveNames && !archiveNames.has(`${name}.archive-fence`)) return current;
|
|
122
|
+
return applyArchiveFence(root, current, readFence, join(root, `${name}.archive-fence`));
|
|
123
123
|
}));
|
|
124
124
|
}
|
|
125
125
|
export async function linkedProvider(root, provider) {
|
|
@@ -413,7 +413,7 @@ export async function listRecords(root = rootDirectory()) {
|
|
|
413
413
|
export function createRecordReader(root = rootDirectory(), { cacheEntries = RECORD_CACHE_LIMIT, cacheBytes = RECORD_CACHE_BYTES } = {}) {
|
|
414
414
|
if (!Number.isSafeInteger(cacheEntries) || cacheEntries < 1 || cacheEntries > RECORD_CACHE_LIMIT ||
|
|
415
415
|
!Number.isSafeInteger(cacheBytes) || cacheBytes < 1 || cacheBytes > RECORD_CACHE_BYTES) throw new Error('Invalid record cache limits.');
|
|
416
|
-
const cache = new Map();
|
|
416
|
+
const cache = new Map(), validatedNames = new WeakMap();
|
|
417
417
|
let inFlight = null, closed = false, retainedBytes = 0;
|
|
418
418
|
const discard = name => {
|
|
419
419
|
const entry = cache.get(name);
|
|
@@ -469,6 +469,9 @@ export function createRecordReader(root = rootDirectory(), { cacheEntries = RECO
|
|
|
469
469
|
const cached = cache.get(name);
|
|
470
470
|
if (cached?.key === key) return cached.record;
|
|
471
471
|
const record = before.size <= BigInt(MAX_STATE_BYTES) ? await readNamedRecord(root, name) : null;
|
|
472
|
+
// A private, validated identity need not be hashed again on every list.
|
|
473
|
+
// Weak keys share the existing record lifetime and cache budgets.
|
|
474
|
+
if (record) validatedNames.set(record, name);
|
|
472
475
|
// Do not retain a value when a write races the read; the next list must
|
|
473
476
|
// inspect it again. This also prevents malformed transient JSON sticking.
|
|
474
477
|
const after = await stat(path, { bigint: true });
|
|
@@ -488,7 +491,9 @@ export function createRecordReader(root = rootDirectory(), { cacheEntries = RECO
|
|
|
488
491
|
if (!inFlight) inFlight = scan().finally(() => { inFlight = null; });
|
|
489
492
|
// Consumers can decorate/mutate their own snapshots without corrupting the
|
|
490
493
|
// cached records or a simultaneous caller's view.
|
|
491
|
-
|
|
494
|
+
// Consent/fence projection never mutates private records. Detach after
|
|
495
|
+
// projection so their validated filenames survive through fresh fencing.
|
|
496
|
+
return structuredClone(await currentProviderConsent(root, await inFlight, readCachedFence, validatedNames));
|
|
492
497
|
},
|
|
493
498
|
async close() { closed = true; await inFlight?.catch(() => {}); cache.clear(); retainedBytes = 0; },
|
|
494
499
|
};
|
package/src/desktop-host.js
CHANGED
|
@@ -799,12 +799,13 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
799
799
|
for (const item of incoming || []) rows.set(`${item.provider}:${item.session}`, item);
|
|
800
800
|
return [...rows.values()];
|
|
801
801
|
};
|
|
802
|
+
const firstUsablePage = sessionStatus.initialLoading !== false;
|
|
802
803
|
sessionStatus = { ...sessionStatus, ...value,
|
|
803
804
|
sessions: linkedSessions(merge(sessionStatus.sessions, value.sessions)).sort(compareSessionWork),
|
|
804
805
|
selectionCandidates: merge(sessionStatus.selectionCandidates || [], value.selectionCandidates),
|
|
805
806
|
initialLoading: false, completeProviders: [...complete], discoveryAttempt: attempt, catalogRevision: ++catalogRevision };
|
|
806
807
|
sessionRefreshedAt = Date.now();
|
|
807
|
-
void publish().catch(() => {});
|
|
808
|
+
void (firstUsablePage || force ? publish() : publishBackground()).catch(() => {});
|
|
808
809
|
void refreshCatalogAnalysis();
|
|
809
810
|
};
|
|
810
811
|
const firstPageTimeoutMs = dependencies.discoveryTimeoutMs ?? 30_000;
|
|
@@ -825,9 +826,9 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
825
826
|
refresh.complete(!failed);
|
|
826
827
|
sessionStatus = { ...sessionStatus, historyLoading: false,
|
|
827
828
|
discoveryAttempt: { ...attempt, state: failed ? 'failed' : 'succeeded', completedAt: Date.now() } };
|
|
828
|
-
//
|
|
829
|
-
//
|
|
830
|
-
void publish().catch(() => {});
|
|
829
|
+
// The first usable page already published without waiting for optional
|
|
830
|
+
// enrichment. Coalesce later pages/completion with background updates.
|
|
831
|
+
void (force ? publish() : publishBackground()).catch(() => {});
|
|
831
832
|
const enrichment = enrichmentController = new AbortController();
|
|
832
833
|
const catalog = sessionStatus;
|
|
833
834
|
sessionEnrichment = boundedDiscovery(async signal => {
|
|
@@ -838,7 +839,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
838
839
|
if (closed || relay || signal.aborted || !desktopAccessReady(latestAccess) || accessScope(latestAccess) !== requestedScope || sessionStatus !== catalog) return;
|
|
839
840
|
sessionStatus = { ...sessionStatus, sessions: linkedSessions(sessions) };
|
|
840
841
|
await queue;
|
|
841
|
-
if (!signal.aborted) void publish().catch(() => {});
|
|
842
|
+
if (!signal.aborted) void (force ? publish() : publishBackground()).catch(() => {});
|
|
842
843
|
}, { signal: enrichment.signal, timeoutMs: dependencies.enrichmentTimeoutMs ?? 10_000, onDrain: trackDiscoveryDrain })
|
|
843
844
|
.catch(() => {}).finally(() => { if (enrichmentController === enrichment) { enrichmentController = null; sessionEnrichment = null; } });
|
|
844
845
|
})
|
|
@@ -846,7 +847,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
846
847
|
historyLoading: false, discoveryAttempt: { ...attempt, state: 'failed', completedAt: Date.now() },
|
|
847
848
|
providers: { codex: { discovery: "unavailable", reason: "Conversation discovery unavailable." }, claude: { discovery: "unavailable", reason: "Conversation discovery unavailable." } } }; sessionRefreshedAt = Date.now(); })
|
|
848
849
|
.finally(() => { if (discoveryController === controller) discoveryController = null; sessionRefresh = null; if (sessionRefreshAgain && !closed && !relay) { sessionRefreshAgain = false; refreshSessions(true); }
|
|
849
|
-
if (!closed) setImmediate(() => { void publish().catch(() => {}); }); });
|
|
850
|
+
if (!closed) setImmediate(() => { void (force ? publish() : publishBackground()).catch(() => {}); }); });
|
|
850
851
|
}
|
|
851
852
|
return sessionStatus;
|
|
852
853
|
};
|
|
@@ -1053,17 +1054,29 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
1053
1054
|
setImmediate(() => { void publish().catch(() => {}); });
|
|
1054
1055
|
} },
|
|
1055
1056
|
});
|
|
1056
|
-
//
|
|
1057
|
-
//
|
|
1058
|
-
const
|
|
1057
|
+
// Background producers share the normal heartbeat window. Explicit commands
|
|
1058
|
+
// promote a pending background publication instead of inheriting its delay.
|
|
1059
|
+
const schedulePublication = delay => {
|
|
1059
1060
|
if (closed || relay || !agent) return Promise.resolve();
|
|
1060
|
-
|
|
1061
|
+
const dueAt = Date.now() + delay;
|
|
1062
|
+
if (scheduledPublish && scheduledPublish.dueAt <= dueAt) return scheduledPublish.promise;
|
|
1063
|
+
let pending = scheduledPublish;
|
|
1064
|
+
if (pending) clearTimeout(pending.timer);
|
|
1065
|
+
else {
|
|
1061
1066
|
let resolve, reject;
|
|
1062
1067
|
const promise = new Promise((yes, no) => { resolve = yes; reject = no; });
|
|
1063
|
-
|
|
1068
|
+
pending = { promise, resolve, reject };
|
|
1064
1069
|
}
|
|
1065
|
-
|
|
1070
|
+
pending.dueAt = dueAt;
|
|
1071
|
+
pending.timer = setTimeout(() => {
|
|
1072
|
+
scheduledPublish = null;
|
|
1073
|
+
projectStatus().then(pending.resolve, pending.reject);
|
|
1074
|
+
}, delay);
|
|
1075
|
+
scheduledPublish = pending;
|
|
1076
|
+
return pending.promise;
|
|
1066
1077
|
};
|
|
1078
|
+
const publish = () => schedulePublication(75);
|
|
1079
|
+
const publishBackground = () => schedulePublication(3000);
|
|
1067
1080
|
let lastDeadlineFailureAt = -Infinity;
|
|
1068
1081
|
let connectionPublishing = null;
|
|
1069
1082
|
let connectionRevision = 0;
|
|
@@ -1419,7 +1432,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
1419
1432
|
deferStartupSynchronization: true, enforceDesktopAccess: true,
|
|
1420
1433
|
suspendOrganizationAnalyses, resumeOrganizationAnalyses,
|
|
1421
1434
|
onDesktopStatusChange: () => {
|
|
1422
|
-
//
|
|
1435
|
+
// Connection/auth/task observations must not wait for background metadata.
|
|
1423
1436
|
setImmediate(() => { void publish().catch(() => {}); });
|
|
1424
1437
|
},
|
|
1425
1438
|
});
|