@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awak-app/simy-cli",
3
- "version": "0.5.70",
3
+ "version": "0.5.71",
4
4
  "description": "Local SIMY executor for non-coding Local Tasks and Agentic Loop runs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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.70";
16
+ export const CLI_VERSION = "0.5.71";
17
17
  export const CLI_API_CONTRACT_VERSION = 10;
18
18
 
19
19
  export function withCliContract(
@@ -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
- // Pin SQM independently of the user's interactive model and CLI defaults.
18
- export const SQM_CODEX_MODEL = "gpt-6.1-sol";
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
- await beforeDispatch();
297
- if (signal?.aborted) throw new SqmLocalReviewError('Local analysis was cancelled.', { code: 'analysis_cancelled' });
298
- const stdout = await spawnReview(
299
- inspection.executable,
300
- provider === "claude" ? [
301
- "--print", "--output-format", "json",
302
- "--json-schema", JSON.stringify(request.output_schema),
303
- "--no-session-persistence", "--tools", "",
304
- "--setting-sources", "", "--settings", '{"disableAllHooks":true}',
305
- "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}',
306
- ...(readOnlyAnalysis ? ["--system-prompt", request.system, "--max-turns", "1"] : []),
307
- ] : [
308
- "exec",
309
- "--model",
310
- SQM_CODEX_MODEL,
311
- "--skip-git-repo-check",
312
- "--sandbox",
313
- "read-only",
314
- "--ephemeral",
315
- "--ignore-user-config",
316
- "--ignore-rules",
317
- ...(readOnlyAnalysis ? codexAnalysisArguments(request.system) : []),
318
- "--output-schema",
319
- schemaPath,
320
- "--output-last-message",
321
- outputPath,
322
- "-",
323
- ],
324
- prompt,
325
- { spawnImpl, executionTimeoutMs, label, cwd: workDir, environment, provider, signal, readOnlyAnalysis,
326
- terminationGraceMs: Number.isInteger(terminationGraceMs) ? Math.max(1, Math.min(terminationGraceMs, 5_000)) : 1_000 },
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" ? SQM_CODEX_MODEL : `${provider}-local/${inspection.installed_version || "unknown"}`,
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) && providerSignInRequired(event.message ?? event.error?.message)) {
415
- halt(new SqmLocalReviewError('Sign in to the local AI app again.', { code: 'provider_sign_in_required' }));
416
- return;
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."