@awak-app/simy-cli 0.5.70 → 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/cli-contract.js +1 -1
- package/src/sqm/review-worker.js +101 -39
package/package.json
CHANGED
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,8 +14,10 @@ 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
23
|
constructor(message, { status = null, code = "sqm_local_review_error", stage = null, jobId = null, failureRecorded = false, validationFeedback = "" } = {}) {
|
|
@@ -293,38 +295,54 @@ export async function runStructuredProviderReview(
|
|
|
293
295
|
"Return only JSON that strictly matches the provided output schema.",
|
|
294
296
|
request.prompt,
|
|
295
297
|
].join("\n\n");
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
+
}
|
|
328
346
|
const envelope = provider === "claude" ? JSON.parse(stdout) : null;
|
|
329
347
|
if (envelope?.is_error || (envelope && envelope.subtype !== "success")) {
|
|
330
348
|
if (readOnlyAnalysis && envelope?.is_error === true && providerSignInRequired(envelope.result)) {
|
|
@@ -340,7 +358,7 @@ export async function runStructuredProviderReview(
|
|
|
340
358
|
}
|
|
341
359
|
return {
|
|
342
360
|
output,
|
|
343
|
-
model: provider === "codex" ?
|
|
361
|
+
model: provider === "codex" ? selectedModel : `${provider}-local/${inspection.installed_version || "unknown"}`,
|
|
344
362
|
};
|
|
345
363
|
} catch (error) {
|
|
346
364
|
if (error instanceof SqmLocalReviewError || error?.code === 'desktop_access_required') throw error;
|
|
@@ -365,7 +383,7 @@ function codexAnalysisArguments(system) {
|
|
|
365
383
|
'-c', `developer_instructions=${JSON.stringify(system)}`];
|
|
366
384
|
}
|
|
367
385
|
|
|
368
|
-
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 }) {
|
|
369
387
|
return new Promise((resolve, reject) => {
|
|
370
388
|
if (signal?.aborted) return reject(new Error('Local analysis was cancelled'));
|
|
371
389
|
const child = spawnImpl(command, args, {
|
|
@@ -376,6 +394,7 @@ function spawnReview(command, args, prompt, { spawnImpl, executionTimeoutMs, lab
|
|
|
376
394
|
});
|
|
377
395
|
let stdout = "";
|
|
378
396
|
let stderr = "";
|
|
397
|
+
let stderrPending = "";
|
|
379
398
|
let settled = false;
|
|
380
399
|
let terminalError = null;
|
|
381
400
|
let killTimer = null;
|
|
@@ -411,9 +430,16 @@ function spawnReview(command, args, prompt, { spawnImpl, executionTimeoutMs, lab
|
|
|
411
430
|
for (const line of stdout.split('\n').filter(Boolean)) {
|
|
412
431
|
let event;
|
|
413
432
|
try { event = JSON.parse(line); } catch { continue; }
|
|
414
|
-
if (['error', 'turn.failed'].includes(event.type)
|
|
415
|
-
|
|
416
|
-
|
|
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
|
+
}
|
|
417
443
|
}
|
|
418
444
|
if (/command_execution|mcp_tool_call|web_search|file_change|tool_call/.test(event.item?.type || '')) {
|
|
419
445
|
halt(new Error('Read-only analysis attempted to use a tool'));
|
|
@@ -429,9 +455,32 @@ function spawnReview(command, args, prompt, { spawnImpl, executionTimeoutMs, lab
|
|
|
429
455
|
child.stderr?.setEncoding?.("utf8");
|
|
430
456
|
child.stderr?.on?.("data", (chunk) => {
|
|
431
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
|
+
}
|
|
432
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
|
+
};
|
|
433
481
|
child.on("error", finish);
|
|
434
482
|
child.on("close", (code) => {
|
|
483
|
+
if (provider === "codex") checkStderr(stderrPending);
|
|
435
484
|
if (readOnlyAnalysis && provider === 'claude' && !terminalError) {
|
|
436
485
|
let envelope;
|
|
437
486
|
try { envelope = JSON.parse(stdout); } catch { /* Non-JSON failure remains unavailable. */ }
|
|
@@ -448,6 +497,18 @@ function spawnReview(command, args, prompt, { spawnImpl, executionTimeoutMs, lab
|
|
|
448
497
|
});
|
|
449
498
|
}
|
|
450
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
|
+
|
|
451
512
|
function providerSignInRequired(message) {
|
|
452
513
|
// Match explicit provider authentication failures only, never general 403s
|
|
453
514
|
// (which can be permissions/quotas) or model-generated prose/tool output.
|
|
@@ -511,6 +572,7 @@ function reviewFailure(error, stage, provider) {
|
|
|
511
572
|
function formatFailure(failure, failureReported = null) {
|
|
512
573
|
if (!failure) return "SQM analysis failed. No failure diagnostics were recorded.";
|
|
513
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."
|
|
514
576
|
: failure.code === "analysis_timeout" ? "The local AI analysis exceeded its time limit."
|
|
515
577
|
: failure.code === "sqm_source_loading_failed" ? "SIMY could not load evidence for this analysis."
|
|
516
578
|
: failure.status === 403 || failure.code === "desktop_access_required" ? "Check SQM and Developer access for the selected organization."
|