@awak-app/simy-cli 0.5.69 → 0.5.71
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 +1 -1
- package/src/cli-contract.js +1 -1
- package/src/sqm/review-worker.js +127 -50
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -875,7 +875,7 @@ export async function startAgent({
|
|
|
875
875
|
runReview: async (request, provider) => {
|
|
876
876
|
const scope = await authorizeDesktopWork();
|
|
877
877
|
const beforeDispatch = () => authorizeDesktopWork(scope);
|
|
878
|
-
return (dependencies.runSqmLocalReview || ((value, selected, options) => runStructuredProviderReview(selected, value, options)))(request, provider, { beforeDispatch });
|
|
878
|
+
return (dependencies.runSqmLocalReview || ((value, selected, options) => runStructuredProviderReview(selected, value, options)))(request, provider, { beforeDispatch, readOnlyAnalysis: true });
|
|
879
879
|
},
|
|
880
880
|
onChange: notifyDesktopStatusChange,
|
|
881
881
|
getWorkspaceUrl: () => {
|
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.71";
|
|
17
17
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
18
18
|
|
|
19
19
|
export function withCliContract(
|
package/src/sqm/review-worker.js
CHANGED
|
@@ -14,16 +14,19 @@ const DEFAULT_LEASE_SECONDS = 300;
|
|
|
14
14
|
// Claim can retrieve and freeze organization-scoped Slack evidence (45s budget).
|
|
15
15
|
const DEFAULT_REQUEST_TIMEOUT_MS = 75_000;
|
|
16
16
|
const DEFAULT_EXECUTION_TIMEOUT_MS = 240_000;
|
|
17
|
-
//
|
|
18
|
-
|
|
17
|
+
// Keep analysis independent of interactive config. Availability varies by
|
|
18
|
+
// account and CLI; only an explicit model rejection may try the next model.
|
|
19
|
+
export const SQM_CODEX_MODELS = Object.freeze(["gpt-6-astra", "gpt-6.1-sol"]);
|
|
20
|
+
export const SQM_CODEX_MODEL = SQM_CODEX_MODELS[0];
|
|
19
21
|
|
|
20
22
|
export class SqmLocalReviewError extends Error {
|
|
21
|
-
constructor(message, { status = null, code = "sqm_local_review_error", stage = null, jobId = null, failureRecorded = false } = {}) {
|
|
23
|
+
constructor(message, { status = null, code = "sqm_local_review_error", stage = null, jobId = null, failureRecorded = false, validationFeedback = "" } = {}) {
|
|
22
24
|
super(message);
|
|
23
25
|
this.name = "SqmLocalReviewError";
|
|
24
26
|
this.status = status;
|
|
25
27
|
this.code = code;
|
|
26
28
|
this.stage = stage; this.jobId = jobId; this.failureRecorded = failureRecorded;
|
|
29
|
+
this.validationFeedback = typeof validationFeedback === "string" ? validationFeedback.slice(0, 2000) : "";
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -71,6 +74,7 @@ export function createSqmLocalReviewApi({
|
|
|
71
74
|
status: response.status,
|
|
72
75
|
code: machineCode(payload?.code || payload?.detail?.code),
|
|
73
76
|
stage: payload?.detail?.stage, jobId: payload?.detail?.job_id, failureRecorded: payload?.detail?.failure_recorded === true,
|
|
77
|
+
validationFeedback: payload?.detail?.validation_feedback,
|
|
74
78
|
});
|
|
75
79
|
}
|
|
76
80
|
return payload;
|
|
@@ -124,6 +128,23 @@ export function createSqmLocalReviewWorker({
|
|
|
124
128
|
lease_token: claim.lease_token,
|
|
125
129
|
});
|
|
126
130
|
|
|
131
|
+
// Repair only a rejected model result, under the same live lease. Network,
|
|
132
|
+
// authorization, and persistence failures must not repeat provider execution.
|
|
133
|
+
const validatedReview = async (request, provider, code, submit, setStage) => {
|
|
134
|
+
let nextRequest = request;
|
|
135
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
136
|
+
setStage(false);
|
|
137
|
+
const review = await runReview(nextRequest, provider);
|
|
138
|
+
setStage(true);
|
|
139
|
+
try {
|
|
140
|
+
return { review, response: await submit(review) };
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (attempt >= 1 || error.status !== 422 || error.code !== code || !error.validationFeedback) throw error;
|
|
143
|
+
nextRequest = { ...request, prompt: `${request.prompt}\n\nThe previous result failed validation. Return a corrected complete JSON object using only the supplied evidence. Fix these constraints:\n${error.validationFeedback}` };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
127
148
|
const processClaim = async (claim, provider, historyScope, workspaceUrl) => {
|
|
128
149
|
activeJobId = claim.job_id;
|
|
129
150
|
const priorIndex = history.findIndex(item => item.id === claim.job_id);
|
|
@@ -142,25 +163,21 @@ export function createSqmLocalReviewWorker({
|
|
|
142
163
|
heartbeatTimer.unref?.();
|
|
143
164
|
let stage = "analysis";
|
|
144
165
|
try {
|
|
145
|
-
const analysis = await
|
|
146
|
-
stage = "advance";
|
|
147
|
-
const advanced = await api.request({
|
|
166
|
+
const { review: analysis, response: advanced } = await validatedReview(claim.request, provider, "invalid_sqm_analysis_output", analysis => api.request({
|
|
148
167
|
action: "advance",
|
|
149
168
|
...leaseBody(claim, provider),
|
|
150
169
|
analysis_output: analysis.output,
|
|
151
170
|
executor_model: analysis.model,
|
|
152
|
-
});
|
|
171
|
+
}), submitting => { stage = submitting ? "advance" : "analysis"; });
|
|
153
172
|
if (advanced.completed) { await updateTask(claim.job_id, "completed", historyScope); return advanced; }
|
|
154
173
|
stage = "adjudication";
|
|
155
|
-
const
|
|
156
|
-
stage = "complete";
|
|
157
|
-
const completed = await api.request({
|
|
174
|
+
const { response: completed } = await validatedReview(advanced.request, provider, "invalid_sqm_adjudication_output", adjudication => api.request({
|
|
158
175
|
action: "complete",
|
|
159
176
|
...leaseBody(claim, provider),
|
|
160
177
|
analysis_output: analysis.output,
|
|
161
178
|
adjudication_output: adjudication.output,
|
|
162
179
|
executor_model: adjudication.model || analysis.model,
|
|
163
|
-
});
|
|
180
|
+
}), submitting => { stage = submitting ? "complete" : "adjudication"; });
|
|
164
181
|
await updateTask(claim.job_id, "completed", historyScope);
|
|
165
182
|
return completed;
|
|
166
183
|
} catch (error) {
|
|
@@ -253,7 +270,7 @@ export async function runStructuredProviderReview(
|
|
|
253
270
|
executionTimeoutMs = DEFAULT_EXECUTION_TIMEOUT_MS,
|
|
254
271
|
environment = process.env,
|
|
255
272
|
signal,
|
|
256
|
-
readOnlyAnalysis =
|
|
273
|
+
readOnlyAnalysis = true,
|
|
257
274
|
terminationGraceMs = 1_000,
|
|
258
275
|
beforeDispatch = async () => {},
|
|
259
276
|
} = {},
|
|
@@ -278,38 +295,54 @@ export async function runStructuredProviderReview(
|
|
|
278
295
|
"Return only JSON that strictly matches the provided output schema.",
|
|
279
296
|
request.prompt,
|
|
280
297
|
].join("\n\n");
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
298
|
+
const deadline = Date.now() + executionTimeoutMs;
|
|
299
|
+
const models = provider === "codex" ? SQM_CODEX_MODELS : [null];
|
|
300
|
+
let stdout;
|
|
301
|
+
let selectedModel;
|
|
302
|
+
for (const model of models) {
|
|
303
|
+
await beforeDispatch();
|
|
304
|
+
if (signal?.aborted) throw new SqmLocalReviewError('Local analysis was cancelled.', { code: 'analysis_cancelled' });
|
|
305
|
+
const remaining = deadline - Date.now();
|
|
306
|
+
if (remaining <= 0) throw new SqmLocalReviewError('Local analysis timed out.', { code: 'analysis_timeout' });
|
|
307
|
+
// Never consume a partial/stale output file left by a rejected model.
|
|
308
|
+
await rm(outputPath, { force: true });
|
|
309
|
+
try {
|
|
310
|
+
stdout = await spawnReview(
|
|
311
|
+
inspection.executable,
|
|
312
|
+
provider === "claude" ? [
|
|
313
|
+
"--print", "--output-format", "json",
|
|
314
|
+
"--json-schema", JSON.stringify(request.output_schema),
|
|
315
|
+
"--no-session-persistence", "--tools", "",
|
|
316
|
+
"--setting-sources", "", "--settings", '{"disableAllHooks":true}',
|
|
317
|
+
"--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}',
|
|
318
|
+
...(readOnlyAnalysis ? ["--system-prompt", request.system, "--max-turns", "1"] : []),
|
|
319
|
+
] : [
|
|
320
|
+
"exec",
|
|
321
|
+
"--model",
|
|
322
|
+
model,
|
|
323
|
+
"--skip-git-repo-check",
|
|
324
|
+
"--sandbox",
|
|
325
|
+
"read-only",
|
|
326
|
+
"--ephemeral",
|
|
327
|
+
"--ignore-user-config",
|
|
328
|
+
"--ignore-rules",
|
|
329
|
+
...(readOnlyAnalysis ? codexAnalysisArguments(request.system) : []),
|
|
330
|
+
"--output-schema",
|
|
331
|
+
schemaPath,
|
|
332
|
+
"--output-last-message",
|
|
333
|
+
outputPath,
|
|
334
|
+
"-",
|
|
335
|
+
],
|
|
336
|
+
prompt,
|
|
337
|
+
{ spawnImpl, executionTimeoutMs: remaining, label, cwd: workDir, environment, provider, signal, readOnlyAnalysis, model,
|
|
338
|
+
terminationGraceMs: Number.isInteger(terminationGraceMs) ? Math.max(1, Math.min(terminationGraceMs, 5_000)) : 1_000 },
|
|
339
|
+
);
|
|
340
|
+
selectedModel = model;
|
|
341
|
+
break;
|
|
342
|
+
} catch (error) {
|
|
343
|
+
if (error.code !== "provider_model_unsupported" || model === models.at(-1)) throw error;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
313
346
|
const envelope = provider === "claude" ? JSON.parse(stdout) : null;
|
|
314
347
|
if (envelope?.is_error || (envelope && envelope.subtype !== "success")) {
|
|
315
348
|
if (readOnlyAnalysis && envelope?.is_error === true && providerSignInRequired(envelope.result)) {
|
|
@@ -325,7 +358,7 @@ export async function runStructuredProviderReview(
|
|
|
325
358
|
}
|
|
326
359
|
return {
|
|
327
360
|
output,
|
|
328
|
-
model: provider === "codex" ?
|
|
361
|
+
model: provider === "codex" ? selectedModel : `${provider}-local/${inspection.installed_version || "unknown"}`,
|
|
329
362
|
};
|
|
330
363
|
} catch (error) {
|
|
331
364
|
if (error instanceof SqmLocalReviewError || error?.code === 'desktop_access_required') throw error;
|
|
@@ -350,7 +383,7 @@ function codexAnalysisArguments(system) {
|
|
|
350
383
|
'-c', `developer_instructions=${JSON.stringify(system)}`];
|
|
351
384
|
}
|
|
352
385
|
|
|
353
|
-
function spawnReview(command, args, prompt, { spawnImpl, executionTimeoutMs, label, cwd, environment, provider, signal, readOnlyAnalysis, terminationGraceMs }) {
|
|
386
|
+
function spawnReview(command, args, prompt, { spawnImpl, executionTimeoutMs, label, cwd, environment, provider, signal, readOnlyAnalysis, terminationGraceMs, model }) {
|
|
354
387
|
return new Promise((resolve, reject) => {
|
|
355
388
|
if (signal?.aborted) return reject(new Error('Local analysis was cancelled'));
|
|
356
389
|
const child = spawnImpl(command, args, {
|
|
@@ -361,6 +394,7 @@ function spawnReview(command, args, prompt, { spawnImpl, executionTimeoutMs, lab
|
|
|
361
394
|
});
|
|
362
395
|
let stdout = "";
|
|
363
396
|
let stderr = "";
|
|
397
|
+
let stderrPending = "";
|
|
364
398
|
let settled = false;
|
|
365
399
|
let terminalError = null;
|
|
366
400
|
let killTimer = null;
|
|
@@ -396,9 +430,16 @@ function spawnReview(command, args, prompt, { spawnImpl, executionTimeoutMs, lab
|
|
|
396
430
|
for (const line of stdout.split('\n').filter(Boolean)) {
|
|
397
431
|
let event;
|
|
398
432
|
try { event = JSON.parse(line); } catch { continue; }
|
|
399
|
-
if (['error', 'turn.failed'].includes(event.type)
|
|
400
|
-
|
|
401
|
-
|
|
433
|
+
if (['error', 'turn.failed'].includes(event.type)) {
|
|
434
|
+
const message = event.message ?? event.error?.message;
|
|
435
|
+
if (providerSignInRequired(message)) {
|
|
436
|
+
halt(new SqmLocalReviewError('Sign in to the local AI app again.', { code: 'provider_sign_in_required' }));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (providerModelUnsupported(message, model)) {
|
|
440
|
+
halt(modelUnsupportedError(model));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
402
443
|
}
|
|
403
444
|
if (/command_execution|mcp_tool_call|web_search|file_change|tool_call/.test(event.item?.type || '')) {
|
|
404
445
|
halt(new Error('Read-only analysis attempted to use a tool'));
|
|
@@ -414,9 +455,32 @@ function spawnReview(command, args, prompt, { spawnImpl, executionTimeoutMs, lab
|
|
|
414
455
|
child.stderr?.setEncoding?.("utf8");
|
|
415
456
|
child.stderr?.on?.("data", (chunk) => {
|
|
416
457
|
if (!readOnlyAnalysis) stderr = `${stderr}${chunk}`.slice(-4000);
|
|
458
|
+
if (provider === "codex") {
|
|
459
|
+
stderrPending = `${stderrPending}${chunk}`.slice(-8000);
|
|
460
|
+
const lines = stderrPending.split(/\r?\n/);
|
|
461
|
+
stderrPending = lines.pop() || "";
|
|
462
|
+
for (const line of lines) checkStderr(line);
|
|
463
|
+
}
|
|
417
464
|
});
|
|
465
|
+
const checkStderr = line => {
|
|
466
|
+
// Native CLI diagnostics only; never match warnings or assistant text.
|
|
467
|
+
const match = line.match(/^\s*ERROR:\s*(.*)$/);
|
|
468
|
+
if (!match) return;
|
|
469
|
+
let message = match[1];
|
|
470
|
+
try {
|
|
471
|
+
const event = JSON.parse(message);
|
|
472
|
+
if (event.type !== "error") return;
|
|
473
|
+
message = event.error?.message ?? event.message;
|
|
474
|
+
} catch { /* Older CLIs print a plain native ERROR line. */ }
|
|
475
|
+
if (providerSignInRequired(message)) {
|
|
476
|
+
halt(new SqmLocalReviewError('Sign in to the local AI app again.', { code: 'provider_sign_in_required' }));
|
|
477
|
+
} else if (providerModelUnsupported(message, model)) {
|
|
478
|
+
halt(modelUnsupportedError(model));
|
|
479
|
+
}
|
|
480
|
+
};
|
|
418
481
|
child.on("error", finish);
|
|
419
482
|
child.on("close", (code) => {
|
|
483
|
+
if (provider === "codex") checkStderr(stderrPending);
|
|
420
484
|
if (readOnlyAnalysis && provider === 'claude' && !terminalError) {
|
|
421
485
|
let envelope;
|
|
422
486
|
try { envelope = JSON.parse(stdout); } catch { /* Non-JSON failure remains unavailable. */ }
|
|
@@ -433,10 +497,22 @@ function spawnReview(command, args, prompt, { spawnImpl, executionTimeoutMs, lab
|
|
|
433
497
|
});
|
|
434
498
|
}
|
|
435
499
|
|
|
500
|
+
function providerModelUnsupported(message, model) {
|
|
501
|
+
if (typeof message !== "string" || typeof model !== "string") return false;
|
|
502
|
+
const rejection = message.trim().match(/^The ['"]([^'"]+)['"] model is not supported(?: when using Codex with a ChatGPT account)?\.?$/i);
|
|
503
|
+
return rejection?.[1] === model;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function modelUnsupportedError(model) {
|
|
507
|
+
return new SqmLocalReviewError(`The local Codex account does not support ${model}. Update Codex or check the models available to this account.`, {
|
|
508
|
+
code: "provider_model_unsupported", status: 400,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
436
512
|
function providerSignInRequired(message) {
|
|
437
513
|
// Match explicit provider authentication failures only, never general 403s
|
|
438
514
|
// (which can be permissions/quotas) or model-generated prose/tool output.
|
|
439
|
-
return typeof message === 'string' && /(
|
|
515
|
+
return typeof message === 'string' && /(?:\b401\s+Unauthorized\b|not logged in|please (?:log|sign) in|invalid api key|authentication token (?:is )?(?:invalid|expired)|refresh token (?:has )?expired)/i.test(message.slice(0, 4000));
|
|
440
516
|
}
|
|
441
517
|
|
|
442
518
|
function safeReviewEnvironment(environment, provider) {
|
|
@@ -496,6 +572,7 @@ function reviewFailure(error, stage, provider) {
|
|
|
496
572
|
function formatFailure(failure, failureReported = null) {
|
|
497
573
|
if (!failure) return "SQM analysis failed. No failure diagnostics were recorded.";
|
|
498
574
|
const hint = failure.code === "provider_sign_in_required" ? "Sign in to the local AI app again."
|
|
575
|
+
: failure.code === "provider_model_unsupported" ? "The local Codex account rejected the configured SQM models. Update Codex or check this account's available models."
|
|
499
576
|
: failure.code === "analysis_timeout" ? "The local AI analysis exceeded its time limit."
|
|
500
577
|
: failure.code === "sqm_source_loading_failed" ? "SIMY could not load evidence for this analysis."
|
|
501
578
|
: failure.status === 403 || failure.code === "desktop_access_required" ? "Check SQM and Developer access for the selected organization."
|