@awak-app/simy-cli 0.5.72 → 0.5.73
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/README.md +84 -0
- package/package.json +2 -2
- package/src/agent.js +191 -11
- package/src/backend-executable.js +21 -1
- package/src/cli-contract.js +1 -1
- package/src/codex-plugins/simy/hooks/README.md +22 -0
- package/src/codex-plugins/simy/hooks/continuation-policy.mjs +27 -0
- package/src/codex-plugins/simy/hooks/delivery-loop.mjs +179 -34
- package/src/codex-plugins/simy/hooks/outcome-progress.mjs +129 -0
- package/src/coding-run-recovery.js +88 -0
- package/src/coding-run-store.js +2 -2
- package/src/continuation-policy.js +2 -0
- package/src/delivery-hooks/command.js +11 -5
- package/src/delivery-hooks/desktop.js +12 -3
- package/src/delivery-hooks/supervisor.js +14 -10
- package/src/desktop-host.js +8 -6
- package/src/desktop-sqm-settings.js +24 -7
- package/src/device-signing-identity.js +65 -0
- package/src/diagnostic-log.js +68 -0
- package/src/doctor.js +55 -0
- package/src/index.js +7 -0
- package/src/local-task.js +68 -3
- package/src/orchestrator/budget.js +55 -4
- package/src/orchestrator/charter-validation.js +33 -0
- package/src/orchestrator/completion-contract.js +1 -1
- package/src/orchestrator/contract.js +2 -0
- package/src/orchestrator/delivery/references/state-and-receipts.md +21 -3
- package/src/orchestrator/independent-audit.js +9 -0
- package/src/orchestrator/instruction.js +6 -1
- package/src/orchestrator/loop.js +38 -41
- package/src/orchestrator/outcome-monitor.js +58 -38
- package/src/orchestrator/outcome-progress.js +2 -0
- package/src/purpose-local-proof.js +380 -0
- package/src/runner.js +224 -36
- package/src/session-supervision/codex-app-server.js +3 -2
- package/src/session-supervision/index.js +16 -4
- package/src/sqm/bundle-store.js +11 -7
- package/src/sqm/claude-plugin.js +2 -0
- package/src/sqm/command.js +36 -4
- package/src/sqm/diagnostics.js +1 -1
- package/src/sqm/evidence-client.js +23 -7
- package/src/sqm/history-store.js +4 -0
- package/src/sqm/index.js +104 -14
- package/src/sqm/proof.js +3 -23
- package/src/sqm/request-recovery.js +82 -0
- package/src/sqm/review-worker.js +59 -12
- package/src/sqm/session.js +33 -3
- package/src/startup-preflight.js +44 -0
package/src/sqm/review-worker.js
CHANGED
|
@@ -3,6 +3,7 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
|
|
6
|
+
import { inspectProviderStatus } from "../desktop-status.js";
|
|
6
7
|
import { inspectDesktopExecutor } from "../desktop-executor.js";
|
|
7
8
|
import {
|
|
8
9
|
webApiErrorMessage,
|
|
@@ -88,7 +89,8 @@ export function createSqmLocalReviewWorker({
|
|
|
88
89
|
leaseSeconds = DEFAULT_LEASE_SECONDS,
|
|
89
90
|
heartbeatIntervalMs = Math.max(10_000, Math.floor(leaseSeconds * 500)),
|
|
90
91
|
getProvider = () => "codex",
|
|
91
|
-
runReview = (request, provider) => runStructuredProviderReview(provider, request),
|
|
92
|
+
runReview = (request, provider, options) => runStructuredProviderReview(provider, request, { ...options, beforeDispatch: options?.beforeLeaseDispatch }),
|
|
93
|
+
getEnvironmentKey = () => null,
|
|
92
94
|
beginWork = () => () => {},
|
|
93
95
|
onError = () => {},
|
|
94
96
|
onChange = () => {},
|
|
@@ -104,7 +106,7 @@ export function createSqmLocalReviewWorker({
|
|
|
104
106
|
let activeJobId = null;
|
|
105
107
|
let pollPromise = null;
|
|
106
108
|
let closed = false;
|
|
107
|
-
let issue = null;
|
|
109
|
+
let issue = null, pausedEnvironment = null, pauseReason = null;
|
|
108
110
|
let issueContext = null;
|
|
109
111
|
const failureDetails = new WeakMap();
|
|
110
112
|
const pauses = new Map();
|
|
@@ -132,7 +134,7 @@ export function createSqmLocalReviewWorker({
|
|
|
132
134
|
item.state = state; item.finishedAt = new Date().toISOString();
|
|
133
135
|
item.result = state === "completed" ? "SQM review submitted." : "";
|
|
134
136
|
item.error = state === "failed" ? formatFailure(failure, failureReported) : "";
|
|
135
|
-
if (failure) { item.phase = failure.stage; item.errorCode = failure.code; }
|
|
137
|
+
if (failure) { item.phase = failure.stage; item.errorStage = failure.stage; item.errorCode = failure.code; item.httpStatus = failure.status; }
|
|
136
138
|
await persist(scope, item);
|
|
137
139
|
}
|
|
138
140
|
onChange();
|
|
@@ -148,11 +150,11 @@ export function createSqmLocalReviewWorker({
|
|
|
148
150
|
|
|
149
151
|
// Repair only a rejected model result, under the same live lease. Network,
|
|
150
152
|
// authorization, and persistence failures must not repeat provider execution.
|
|
151
|
-
const validatedReview = async (request, provider, code, submit, setStage) => {
|
|
153
|
+
const validatedReview = async (request, provider, code, submit, setStage, reviewOptions) => {
|
|
152
154
|
let nextRequest = request;
|
|
153
155
|
for (let attempt = 0; ; attempt += 1) {
|
|
154
156
|
setStage(false);
|
|
155
|
-
const review = await runReview(nextRequest, provider);
|
|
157
|
+
const review = await runReview(nextRequest, provider, reviewOptions);
|
|
156
158
|
setStage(true);
|
|
157
159
|
try {
|
|
158
160
|
return { review, response: await submit(review) };
|
|
@@ -180,22 +182,33 @@ export function createSqmLocalReviewWorker({
|
|
|
180
182
|
heartbeatTimer = setInterval(heartbeat, heartbeatIntervalMs);
|
|
181
183
|
heartbeatTimer.unref?.();
|
|
182
184
|
let stage = "analysis";
|
|
185
|
+
const reviewOptions = { providerReportingSupported: claim.capabilities?.reported_executor_provider === true,
|
|
186
|
+
beforeLeaseDispatch: () => api.request({ action: "heartbeat", ...leaseBody(claim, provider) }) };
|
|
187
|
+
const providerReport = (review, selectedProvider = provider) => claim.capabilities?.reported_executor_provider === true
|
|
188
|
+
? { reported_executor_provider: review.provider || selectedProvider } : {};
|
|
183
189
|
try {
|
|
184
190
|
const { review: analysis, response: advanced } = await validatedReview(claim.request, provider, "invalid_sqm_analysis_output", analysis => api.request({
|
|
185
191
|
action: "advance",
|
|
186
192
|
...leaseBody(claim, provider),
|
|
187
193
|
analysis_output: analysis.output,
|
|
188
194
|
executor_model: analysis.model,
|
|
189
|
-
|
|
195
|
+
...providerReport(analysis),
|
|
196
|
+
}), submitting => { stage = submitting ? "advance" : "analysis"; if (submitting) pauses.delete(contextKey(provider, historyScope)); }, reviewOptions);
|
|
197
|
+
const analysisProvider = analysis.provider || provider;
|
|
198
|
+
const item = history.find(item => item.id === claim.job_id);
|
|
199
|
+
if (item) item.provider = analysisProvider;
|
|
190
200
|
if (advanced.completed) { await updateTask(claim.job_id, "completed", historyScope); return advanced; }
|
|
191
201
|
stage = "adjudication";
|
|
192
|
-
const { response: completed } = await validatedReview(advanced.request,
|
|
202
|
+
const { review: adjudication, response: completed } = await validatedReview(advanced.request, analysisProvider, "invalid_sqm_adjudication_output", adjudication => api.request({
|
|
193
203
|
action: "complete",
|
|
194
204
|
...leaseBody(claim, provider),
|
|
195
205
|
analysis_output: analysis.output,
|
|
196
206
|
adjudication_output: adjudication.output,
|
|
197
207
|
executor_model: adjudication.model || analysis.model,
|
|
198
|
-
|
|
208
|
+
...providerReport(adjudication, analysisProvider),
|
|
209
|
+
...(claim.capabilities?.reported_executor_provider === true ? { analysis_executor_provider: analysisProvider, analysis_executor_model: analysis.model } : {}),
|
|
210
|
+
}), submitting => { stage = submitting ? "complete" : "adjudication"; if (submitting) pauses.delete(contextKey(provider, historyScope)); }, reviewOptions);
|
|
211
|
+
if (item) item.provider = adjudication.provider || analysisProvider;
|
|
199
212
|
await updateTask(claim.job_id, "completed", historyScope);
|
|
200
213
|
return completed;
|
|
201
214
|
} catch (error) {
|
|
@@ -221,7 +234,7 @@ export function createSqmLocalReviewWorker({
|
|
|
221
234
|
return {
|
|
222
235
|
snapshot() {
|
|
223
236
|
const pause = currentPause();
|
|
224
|
-
return { active_job_id: activeJobId, polling: Boolean(pollPromise), closed, issue: issueContext === contextKey(getProvider(), getHistoryScope()) ? issue : null,
|
|
237
|
+
return { active_job_id: activeJobId, polling: Boolean(pollPromise), closed, paused: pausedEnvironment !== null, pause_reason: pauseReason, issue: issueContext === contextKey(getProvider(), getHistoryScope()) ? issue : null,
|
|
225
238
|
...(pause ? { provider_pause: { code: pause.code, status: pause.status, retry_at: new Date(pause.retryAt).toISOString(), message: pause.message } } : {}) };
|
|
226
239
|
},
|
|
227
240
|
desktopTasks() { return { activeCount: activeJobId ? 1 : 0, items: visibleHistory().map(item => ({ ...item })) }; },
|
|
@@ -229,9 +242,13 @@ export function createSqmLocalReviewWorker({
|
|
|
229
242
|
const item = visibleHistory().find(item => item.key === key);
|
|
230
243
|
return item ? { key, instruction: `SQM local review · ${item.provider}`, result: item.state === "completed" ? "SQM review submitted." : "", error: item.error || "", links: item.webUrl ? [{ label: "Analysis details", url: item.webUrl }] : [] } : null;
|
|
231
244
|
},
|
|
232
|
-
|
|
245
|
+
retry() { if (closed || pollPromise) return false; pausedEnvironment = null; pauseReason = null; pauses.delete(contextKey(getProvider(), getHistoryScope())); issue = null; onChange(); return true; },
|
|
246
|
+
resetHistory() { if (!pollPromise) { history.splice(0); issue = null; pausedEnvironment = null; pauseReason = null; } },
|
|
233
247
|
poll() {
|
|
234
248
|
if (closed || pollPromise) return pollPromise || Promise.resolve(null);
|
|
249
|
+
const environmentKey = getEnvironmentKey();
|
|
250
|
+
if (pausedEnvironment !== null && pausedEnvironment === environmentKey) return Promise.resolve(null);
|
|
251
|
+
if (pausedEnvironment !== null) { pausedEnvironment = null; pauseReason = null; pauses.delete(contextKey(getProvider(), getHistoryScope())); issue = null; }
|
|
235
252
|
const provider = getProvider();
|
|
236
253
|
if (provider == null) return Promise.resolve(null);
|
|
237
254
|
assertProvider(provider);
|
|
@@ -273,6 +290,9 @@ export function createSqmLocalReviewWorker({
|
|
|
273
290
|
history.unshift(item); historyScopes.set(item, JSON.stringify(scope)); history.splice(50);
|
|
274
291
|
await persist(scope, item);
|
|
275
292
|
}
|
|
293
|
+
if (["provider_model_unsupported", "codex_local_unavailable", "claude_local_unavailable", "provider_sign_in_required", "provider_status_unknown"].includes(error.code)) {
|
|
294
|
+
pausedEnvironment = environmentKey; pauseReason = error.code;
|
|
295
|
+
}
|
|
276
296
|
issueContext = key;
|
|
277
297
|
issue = error.status === 403 ? "forbidden" : error.status === 401 ? "signed_out" : "unavailable";
|
|
278
298
|
onError(error);
|
|
@@ -296,7 +316,33 @@ export function runStructuredCodexReview(request, options) {
|
|
|
296
316
|
return runStructuredProviderReview("codex", request, options);
|
|
297
317
|
}
|
|
298
318
|
|
|
299
|
-
export async function runStructuredProviderReview(
|
|
319
|
+
export async function runStructuredProviderReview(provider, request, options = {}) {
|
|
320
|
+
const fallbackProviders = options.enableEngineFallback === true && options.providerReportingSupported === true && options.readOnlyAnalysis !== false && Array.isArray(options.allowedFallbackProviders)
|
|
321
|
+
? [...new Set(options.allowedFallbackProviders.filter(candidate => ["codex", "claude"].includes(candidate) && candidate !== provider))] : [];
|
|
322
|
+
if (!fallbackProviders.length) return runSingleProviderReview(provider, request, options);
|
|
323
|
+
const deadlineAt = Date.now() + (options.executionTimeoutMs ?? DEFAULT_EXECUTION_TIMEOUT_MS);
|
|
324
|
+
const attempts = [];
|
|
325
|
+
for (const candidate of [provider, ...fallbackProviders]) {
|
|
326
|
+
if (options.signal?.aborted) throw new SqmLocalReviewError("Local analysis was cancelled.", { code: "analysis_cancelled" });
|
|
327
|
+
if (Date.now() >= deadlineAt) throw new SqmLocalReviewError("Local analysis timed out.", { code: "analysis_timeout" });
|
|
328
|
+
if (candidate !== provider) {
|
|
329
|
+
await options.beforeDispatch?.(candidate);
|
|
330
|
+
const readiness = await (options.inspectProviderStatus || inspectProviderStatus)(candidate);
|
|
331
|
+
if (readiness.state !== "ready") throw new SqmLocalReviewError("Fallback provider is not signed in and ready.", {
|
|
332
|
+
code: readiness.state === "signed_out" ? "provider_sign_in_required" : "provider_status_unknown" });
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const result = await runSingleProviderReview(candidate, request, { ...options, deadlineAt,
|
|
336
|
+
beforeDispatch: () => options.beforeDispatch?.(candidate) });
|
|
337
|
+
return { ...result, provider: candidate, provider_attempts: [...attempts, { provider: candidate, code: "completed" }] };
|
|
338
|
+
} catch (error) {
|
|
339
|
+
attempts.push({ provider: candidate, code: error.code });
|
|
340
|
+
if (!["provider_model_unsupported", "codex_local_unavailable", "claude_local_unavailable"].includes(error.code) || candidate === fallbackProviders.at(-1)) throw error;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function runSingleProviderReview(
|
|
300
346
|
provider,
|
|
301
347
|
request,
|
|
302
348
|
{
|
|
@@ -308,6 +354,7 @@ export async function runStructuredProviderReview(
|
|
|
308
354
|
readOnlyAnalysis = true,
|
|
309
355
|
terminationGraceMs = 1_000,
|
|
310
356
|
beforeDispatch = async () => {},
|
|
357
|
+
deadlineAt,
|
|
311
358
|
} = {},
|
|
312
359
|
) {
|
|
313
360
|
assertProvider(provider);
|
|
@@ -330,7 +377,7 @@ export async function runStructuredProviderReview(
|
|
|
330
377
|
"Return only JSON that strictly matches the provided output schema.",
|
|
331
378
|
request.prompt,
|
|
332
379
|
].join("\n\n");
|
|
333
|
-
const deadline = Date.now() + executionTimeoutMs;
|
|
380
|
+
const deadline = deadlineAt ?? Date.now() + executionTimeoutMs;
|
|
334
381
|
const models = provider === "codex" ? SQM_CODEX_MODELS : [null];
|
|
335
382
|
let stdout;
|
|
336
383
|
let selectedModel;
|
package/src/sqm/session.js
CHANGED
|
@@ -18,10 +18,18 @@ export async function ensureSqmSession(
|
|
|
18
18
|
startAgentImpl = startAgent,
|
|
19
19
|
openAuthorizationUrlImpl = openAuthorizationUrl,
|
|
20
20
|
createAgentShutdownImpl = createAgentShutdown,
|
|
21
|
+
expectedScope,
|
|
22
|
+
signal,
|
|
23
|
+
rejectedToken,
|
|
21
24
|
} = {},
|
|
22
25
|
) {
|
|
23
26
|
let session = await readSessionImpl(webOrigin);
|
|
24
|
-
|
|
27
|
+
const pinnedScope = expectedScope || sqmSessionScope(session, webOrigin);
|
|
28
|
+
signal?.throwIfAborted();
|
|
29
|
+
if (session?.token !== rejectedToken && sqmSessionReady(session, webOrigin, now(), isSessionValidImpl)) {
|
|
30
|
+
assertSqmSessionScope(session, webOrigin, pinnedScope);
|
|
31
|
+
return session;
|
|
32
|
+
}
|
|
25
33
|
|
|
26
34
|
const agent = await startAgentImpl({ requestedPort: 0, webOrigin, quiet: true });
|
|
27
35
|
const shutdown = createAgentShutdownImpl(agent);
|
|
@@ -35,19 +43,41 @@ export async function ensureSqmSession(
|
|
|
35
43
|
|
|
36
44
|
const deadline = now() + timeoutMs;
|
|
37
45
|
while (now() < deadline) {
|
|
46
|
+
signal?.throwIfAborted();
|
|
38
47
|
await sleep(Math.min(500, Math.max(1, deadline - now())));
|
|
39
48
|
session = await readSessionImpl(webOrigin);
|
|
40
|
-
if (sqmSessionReady(session, webOrigin, now(), isSessionValidImpl)) {
|
|
49
|
+
if (session?.token !== rejectedToken && sqmSessionReady(session, webOrigin, now(), isSessionValidImpl)) {
|
|
50
|
+
assertSqmSessionScope(session, webOrigin, pinnedScope);
|
|
41
51
|
logger.log("SIMY CLI authorization completed. Continuing SQM check.");
|
|
42
52
|
return session;
|
|
43
53
|
}
|
|
44
54
|
}
|
|
45
|
-
|
|
55
|
+
const error = new Error("SIMY authorization timed out. Complete the browser sign-in and rerun the SQM command.");
|
|
56
|
+
error.code = "authorization_timeout";
|
|
57
|
+
throw error;
|
|
46
58
|
} finally {
|
|
47
59
|
await shutdown();
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
62
|
|
|
63
|
+
export function sqmSessionScope(session, webOrigin) {
|
|
64
|
+
return { account_id: session?.account_id || session?.auth_user_id || null,
|
|
65
|
+
organization_id: session?.organization_id || session?.org_id || null,
|
|
66
|
+
device_id: session?.device_id || null, api_base_url: session?.api_base_url || null,
|
|
67
|
+
web_origin: new URL(webOrigin).origin };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function assertSqmSessionScope(session, webOrigin, expected) {
|
|
71
|
+
const current = sqmSessionScope(session, webOrigin);
|
|
72
|
+
for (const field of ["account_id", "organization_id", "device_id", "api_base_url", "web_origin"]) {
|
|
73
|
+
if (expected?.[field] && expected[field] !== current[field]) {
|
|
74
|
+
const error = new Error(`SQM authorization changed its ${field}; resume the original authorized scope.`);
|
|
75
|
+
error.code = "authorization_scope_changed";
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
51
81
|
function sqmSessionReady(session, webOrigin, now, isSessionValidImpl) {
|
|
52
82
|
return Boolean(
|
|
53
83
|
isSessionValidImpl(session, now, webOrigin) &&
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createCodingLoopSnapshot } from "./orchestrator/contract.js";
|
|
2
|
+
import { validateCharter } from "./orchestrator/charter-validation.js";
|
|
3
|
+
|
|
4
|
+
// Reuse the execution charter rather than inventing a second approval policy.
|
|
5
|
+
// These checks inspect local metadata only; model access requires real dispatch.
|
|
6
|
+
export function startupRequirementChecks(request = {}) {
|
|
7
|
+
const checks = [{ key: "model_access", status: "unknown", blocking: false,
|
|
8
|
+
summary: "Signing in does not prove access to the requested model. Access is checked on dispatch without a paid preflight request.",
|
|
9
|
+
action: { id: "review_model_access", type: "inspect_provider", label: "Review provider and model settings" } }];
|
|
10
|
+
if (!String(request.requirement || "").trim()) {
|
|
11
|
+
checks.push({ key: "task_requirements", status: "unknown", blocking: false,
|
|
12
|
+
summary: "Environment check only. Supply the task requirement to check acceptance criteria and design approval.",
|
|
13
|
+
action: { id: "provide_requirement", type: "edit_requirements", label: "Provide task requirements" } });
|
|
14
|
+
return checks;
|
|
15
|
+
}
|
|
16
|
+
const { charter } = createCodingLoopSnapshot({ runId: "preflight", request });
|
|
17
|
+
const findings = validateCharter(charter);
|
|
18
|
+
for (const [key, code, passed, actionId, label] of [
|
|
19
|
+
["acceptance_criteria", "EXPLICIT_ACCEPTANCE_CRITERIA_REQUIRED", "Acceptance criteria satisfy the task risk policy.", "provide_acceptance_criteria", "Provide explicit acceptance criteria"],
|
|
20
|
+
["design_review", "DESIGN_REVIEW_REQUIRED", "Design approval satisfies the task risk policy.", "approve_design", "Provide design review and approval evidence"],
|
|
21
|
+
["required_checks", "REQUIRED_CI_CHECKS_MISSING", "Required checks satisfy the task risk policy.", "provide_required_checks", "Provide CI and security check names"],
|
|
22
|
+
]) {
|
|
23
|
+
const finding = findings.find(item => item.code === code);
|
|
24
|
+
checks.push({ key, status: finding ? "failed" : "passed", blocking: Boolean(finding),
|
|
25
|
+
summary: finding?.explanation || passed,
|
|
26
|
+
...(finding ? { code, action: { id: actionId, type: "edit_requirements", label } } : {}) });
|
|
27
|
+
}
|
|
28
|
+
checks.push({ key: "assurance_selection", status: "unknown", blocking: false,
|
|
29
|
+
summary: "The executor must verify the five-level comparison and user selection in existing decision evidence before material implementation.",
|
|
30
|
+
action: { id: "review_assurance_selection", type: "review_decision_evidence", label: "Review assurance level and selection evidence" } });
|
|
31
|
+
checks.push({ key: "external_dependencies", status: "unknown", blocking: false,
|
|
32
|
+
summary: "Confirm test accounts, external resources and permissions required by the acceptance criteria. Local readiness does not certify Cloud entitlement.",
|
|
33
|
+
action: { id: "confirm_external_dependencies", type: "edit_requirements", label: "Confirm external inputs and permissions" } });
|
|
34
|
+
return checks;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function summarizeStartupChecks(checks) {
|
|
38
|
+
const blockers = checks.filter(check => check.status === "failed" && check.blocking !== false);
|
|
39
|
+
const unknown = checks.filter(check => check.status === "unknown");
|
|
40
|
+
return { status: blockers.length ? "blocked" : unknown.length ? "needs_confirmation" : "ready",
|
|
41
|
+
blocker_keys: blockers.map(check => check.key), unknown_keys: unknown.map(check => check.key),
|
|
42
|
+
actions: checks.flatMap(check => check.action ? [{ check_key: check.key, ...check.action }]
|
|
43
|
+
: (check.recovery?.actions || []).map(action => ({ check_key: check.key, ...action }))) };
|
|
44
|
+
}
|