@awak-app/simy-cli 0.5.57 → 0.5.59
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/src/agent.js +15 -2
- package/src/cli-contract.js +1 -1
- package/src/desktop-access.js +3 -1
- package/src/desktop-host.js +20 -6
- package/src/session-supervision/analysis-manager.js +12 -5
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -378,8 +378,21 @@ export async function startAgent({
|
|
|
378
378
|
});
|
|
379
379
|
|
|
380
380
|
let providerReadInFlight = null;
|
|
381
|
+
let providerReadForced = false;
|
|
382
|
+
let providerForcedFollowup = null;
|
|
381
383
|
const readProviderStatuses = ({ force = false } = {}) => {
|
|
382
|
-
if (providerReadInFlight)
|
|
384
|
+
if (providerReadInFlight) {
|
|
385
|
+
if (!force || providerReadForced) return providerReadInFlight;
|
|
386
|
+
// A manual refresh must observe changes made after a background probe
|
|
387
|
+
// began. Queue one fresh batch without overlapping provider subprocesses.
|
|
388
|
+
if (!providerForcedFollowup) {
|
|
389
|
+
providerForcedFollowup = providerReadInFlight
|
|
390
|
+
.then(() => readProviderStatuses({ force: true }))
|
|
391
|
+
.finally(() => { providerForcedFollowup = null; });
|
|
392
|
+
}
|
|
393
|
+
return providerForcedFollowup;
|
|
394
|
+
}
|
|
395
|
+
providerReadForced = force;
|
|
383
396
|
providerReadInFlight = (async () => {
|
|
384
397
|
const previous = providerStatuses;
|
|
385
398
|
await Promise.all(["codex", "claude"].map(async (provider) => {
|
|
@@ -400,7 +413,7 @@ export async function startAgent({
|
|
|
400
413
|
refreshAvailableCapabilities();
|
|
401
414
|
}
|
|
402
415
|
return providerStatuses;
|
|
403
|
-
})().finally(() => { providerReadInFlight = null; });
|
|
416
|
+
})().finally(() => { providerReadInFlight = null; providerReadForced = false; });
|
|
404
417
|
return providerReadInFlight;
|
|
405
418
|
};
|
|
406
419
|
|
package/src/cli-contract.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./durable-local-task-steps-contract.js";
|
|
14
14
|
import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
|
|
15
15
|
|
|
16
|
-
export const CLI_VERSION = "0.5.
|
|
16
|
+
export const CLI_VERSION = "0.5.59";
|
|
17
17
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
18
18
|
|
|
19
19
|
export function withCliContract(
|
package/src/desktop-access.js
CHANGED
|
@@ -37,7 +37,9 @@ export function createDesktopAccessPolicy({ readSession, isValid, fetchAccess, i
|
|
|
37
37
|
if (base.state !== 'checking' || isSwitching()) return base;
|
|
38
38
|
const session = readSession();
|
|
39
39
|
if (pending?.session === session) return pending.promise;
|
|
40
|
-
if (!force && readSession() === verifiedSession && now() - verifiedAt < ttlMs && verified) return snapshot();
|
|
40
|
+
if (!force && readSession() === verifiedSession && now() - verifiedAt < ttlMs - Math.min(5000, ttlMs / 5) && verified) return snapshot();
|
|
41
|
+
// Renew before expiry while the existing grant is still valid. snapshot()
|
|
42
|
+
// continues to expire closed at the original TTL if the network stalls.
|
|
41
43
|
onChange();
|
|
42
44
|
const job = { session, promise: null };
|
|
43
45
|
job.promise = (async () => {
|
package/src/desktop-host.js
CHANGED
|
@@ -67,7 +67,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
67
67
|
}
|
|
68
68
|
const lock = candidate ? null : await acquireDesktopProcessLock(root, { webOrigin });
|
|
69
69
|
const supervisorToken = lock?.nonce || env.SIMY_DESKTOP_SUPERVISOR_TOKEN;
|
|
70
|
-
let agent, updater, analysisManager, automaticContinuation, stopWorkflows = async () => {}, timer, closed = false, relay = null, publishing = null, publishAgain = false;
|
|
70
|
+
let agent, updater, analysisManager, automaticContinuation, stopWorkflows = async () => {}, timer, closed = false, relay = null, publishing = null, publishAgain = false, scheduledPublish = null;
|
|
71
71
|
const makeActionAssociations = () => (dependencies.createActionAssociationHost || createActionAssociationHost)({ root: join(root, "delivery-hooks"), sessionRoot: root, webOrigin, onChange: () => { if (!closed) setImmediate(() => { void publish().catch(() => {}); }); } });
|
|
72
72
|
let actionAssociations = makeActionAssociations();
|
|
73
73
|
let currentAccess = { state: 'checking', organizationId: null, organizationName: null };
|
|
@@ -125,12 +125,14 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
125
125
|
}
|
|
126
126
|
return sessionStatus;
|
|
127
127
|
};
|
|
128
|
-
const selectedSession = async command => {
|
|
128
|
+
const selectedSession = async (command, { reuseDiscovery = false } = {}) => {
|
|
129
129
|
const scope = await authorizeWork();
|
|
130
|
-
|
|
130
|
+
// Analysis joins an existing discovery instead of scheduling a second
|
|
131
|
+
// full scan. Enrollment retains its existing fresh-discovery boundary.
|
|
132
|
+
if (!reuseDiscovery || !sessionRefresh) refreshSessions(true);
|
|
131
133
|
await sessionRefresh;
|
|
132
134
|
// A forced refresh requested during discovery schedules one follow-up.
|
|
133
|
-
if (sessionRefresh) await sessionRefresh;
|
|
135
|
+
if (!reuseDiscovery && sessionRefresh) await sessionRefresh;
|
|
134
136
|
const candidate = sessionStatus.sessions.find(item => item.provider === command.provider && item.session === command.session);
|
|
135
137
|
if (!candidate) throw new Error('This conversation is unavailable; refresh its provider.');
|
|
136
138
|
await authorizeWork(scope);
|
|
@@ -162,7 +164,19 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
162
164
|
},
|
|
163
165
|
onChange: () => { if (!closed && !relay) setImmediate(() => { void publish().catch(() => {}); }); },
|
|
164
166
|
});
|
|
167
|
+
// Batch burst notifications and maintenance completions into one frame. The
|
|
168
|
+
// short window leaves commands responsive and the regular heartbeat intact.
|
|
165
169
|
const publish = () => {
|
|
170
|
+
if (closed || relay || !agent) return Promise.resolve();
|
|
171
|
+
if (!scheduledPublish) {
|
|
172
|
+
let resolve, reject;
|
|
173
|
+
const promise = new Promise((yes, no) => { resolve = yes; reject = no; });
|
|
174
|
+
scheduledPublish = { promise };
|
|
175
|
+
setTimeout(() => { scheduledPublish = null; projectStatus().then(resolve, reject); }, 75);
|
|
176
|
+
}
|
|
177
|
+
return scheduledPublish.promise;
|
|
178
|
+
};
|
|
179
|
+
const projectStatus = () => {
|
|
166
180
|
if (closed || relay || !agent) return Promise.resolve();
|
|
167
181
|
if (publishing) { publishAgain = true; return publishing; }
|
|
168
182
|
publishing = Promise.resolve(agent.getDesktopStatus()).then(async (status) => {
|
|
@@ -471,8 +485,8 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
471
485
|
maxContinuations: command.maxContinuations, maxFailures: command.maxFailures, maxNoProgressMinutes: command.maxNoProgressMinutes, maxTokens: command.maxTokens });
|
|
472
486
|
await refreshSessions(true);
|
|
473
487
|
} else if (action === "session_analyze") {
|
|
474
|
-
const candidate = await selectedSession(command);
|
|
475
|
-
await analysisManager.request(candidate);
|
|
488
|
+
const candidate = await selectedSession(command, { reuseDiscovery: true });
|
|
489
|
+
await analysisManager.request(candidate, { requestId: command.analysisRequestId || command.id });
|
|
476
490
|
if (candidate.autoContinue === true && candidate.capabilities?.canContinue === true) automaticContinuation.retry(candidate.provider, candidate.session);
|
|
477
491
|
} else if (action === "session_continue") {
|
|
478
492
|
const candidate = await selectedSession(command);
|
|
@@ -78,6 +78,7 @@ export async function createContinuationAnalysisManager({ root, analyze = analyz
|
|
|
78
78
|
entries.set(key, { revision, analysisVersion: CONTINUATION_ANALYSIS_VERSION, cwd: candidate.cwd,
|
|
79
79
|
forceAnalysis: forceAnalysis || entry?.forceAnalysis === true && ['queued', 'analyzing'].includes(entry.result?.state),
|
|
80
80
|
settledResult: entry?.cwd === candidate.cwd ? entry.settledResult : null,
|
|
81
|
+
requestId: entry?.cwd === candidate.cwd && ['queued', 'analyzing'].includes(entry.result?.state) ? entry.requestId : undefined,
|
|
81
82
|
result: { state: 'queued' } });
|
|
82
83
|
return true;
|
|
83
84
|
};
|
|
@@ -135,7 +136,7 @@ export async function createContinuationAnalysisManager({ root, analyze = analyz
|
|
|
135
136
|
}
|
|
136
137
|
const settledResult = finishedAnalysis(result, selected.previousAnalysis || entry.settledResult);
|
|
137
138
|
entries.set(selected.key, { revision: selected.revision, analysisVersion: CONTINUATION_ANALYSIS_VERSION, cwd: current.cwd, settledResult,
|
|
138
|
-
result: { ...result, analyzedAt: settledResult?.analyzedAt || now() } });
|
|
139
|
+
requestId: selected.entry.requestId, result: { ...result, analyzedAt: settledResult?.analyzedAt || now() } });
|
|
139
140
|
await persist(); changed();
|
|
140
141
|
});
|
|
141
142
|
};
|
|
@@ -149,7 +150,9 @@ export async function createContinuationAnalysisManager({ root, analyze = analyz
|
|
|
149
150
|
const selected = await exclusive(async () => {
|
|
150
151
|
if (closed) return [];
|
|
151
152
|
const batch = [];
|
|
152
|
-
|
|
153
|
+
// Explicit retries precede historical backfill without interrupting owned jobs.
|
|
154
|
+
const ordered = [...entries].sort((a, b) => Number(Boolean(b[1].requestId)) - Number(Boolean(a[1].requestId)));
|
|
155
|
+
for (const [key, entry] of ordered) {
|
|
153
156
|
if (jobs.size >= concurrency) break;
|
|
154
157
|
if (!runnable(key, entry)) continue;
|
|
155
158
|
const candidate = candidates.get(key), controller = new AbortController();
|
|
@@ -233,7 +236,7 @@ export async function createContinuationAnalysisManager({ root, analyze = analyz
|
|
|
233
236
|
},
|
|
234
237
|
project(candidate) {
|
|
235
238
|
const revision = continuationAnalysisRevision(candidate), entry = entries.get(keyOf(candidate));
|
|
236
|
-
let analysis = entry?.revision === revision ? { ...entry.result, revision } : { state: 'unanalysed', revision };
|
|
239
|
+
let analysis = entry?.revision === revision ? { ...entry.result, revision, requestId: entry.requestId } : { state: 'unanalysed', revision };
|
|
237
240
|
const previous = entry?.cwd === candidate.cwd ? entry.settledResult : null;
|
|
238
241
|
if (candidate.isRunning === true || analysis.awaitingTurn === true) {
|
|
239
242
|
analysis = { state: 'waiting', awaitingTurn: true, revision,
|
|
@@ -244,12 +247,16 @@ export async function createContinuationAnalysisManager({ root, analyze = analyz
|
|
|
244
247
|
}
|
|
245
248
|
return { ...candidate, analysis };
|
|
246
249
|
},
|
|
247
|
-
async request(candidate) {
|
|
250
|
+
async request(candidate, { requestId } = {}) {
|
|
251
|
+
if (requestId !== undefined && (typeof requestId !== 'string' || !requestId || requestId.length > 128)) throw new Error('Invalid analysis request identity.');
|
|
248
252
|
if (!candidate || !eligible(candidate)) throw new Error('This conversation cannot be analyzed.');
|
|
249
253
|
await exclusive(async () => {
|
|
250
254
|
if (closed) throw new Error('Session analysis is unavailable.');
|
|
251
255
|
candidates.set(keyOf(candidate), candidate);
|
|
252
|
-
|
|
256
|
+
let dirty = enqueue(candidate, true, true);
|
|
257
|
+
const entry = entries.get(keyOf(candidate));
|
|
258
|
+
if (requestId && entry.requestId !== requestId) { entry.requestId = requestId; dirty = true; }
|
|
259
|
+
if (dirty) { await persist(); changed(); }
|
|
253
260
|
});
|
|
254
261
|
startDrain();
|
|
255
262
|
},
|