@awak-app/simy-cli 0.7.9 → 0.7.10

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.
@@ -58,6 +58,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
58
58
  const deferred = [];
59
59
 
60
60
  for (let record of await readRecords()) {
61
+ if (signal?.aborted) break;
61
62
  try {
62
63
  let run = record.run;
63
64
  if (upgradeEligible(record) && (policyAdoptionPending(record) || run.managed.controllerContractVersion !== 2 || run.state === 'paused' || run.state === 'verification_pending')) {
@@ -98,6 +99,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
98
99
  let link;
99
100
  try { link = await requireProviderLink({ root, provider: record.provider }); } catch { continue; }
100
101
  const authorized = async () => {
102
+ signal?.throwIfAborted();
101
103
  await requireProviderLink({ root, provider: record.provider, expectedEpoch: link.epoch });
102
104
  const latest = await readRecord(root, record.provider, record.session);
103
105
  if (!latest || !automaticContinuationAllowed(latest) || latest.run?.id !== run.id || latest.autoContinueEpoch !== record.autoContinueEpoch || ['paused', 'stopped'].includes(latest.run.state)) throw new Error('Purpose observation authorization changed.');
@@ -105,7 +107,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
105
107
  const observe = async read => { await authorized(); const result = await read(); await authorized(); return result; };
106
108
  const persist = mutate => writeRecord(root, record.provider, record.session, async current => {
107
109
  try { await requireProviderLink({ root, provider: record.provider, expectedEpoch: link.epoch }); } catch { return current; }
108
- if (!current || !automaticContinuationAllowed(current) || current.autoContinueEpoch !== record.autoContinueEpoch) return current;
110
+ if (signal?.aborted || !current || !automaticContinuationAllowed(current) || current.autoContinueEpoch !== record.autoContinueEpoch) return current;
109
111
  return mutate(current);
110
112
  });
111
113
  // Recheck the current budget and boundaries under the existing record lock.
@@ -203,7 +205,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
203
205
  if (current?.run?.id !== run.id || current.run.state !== 'blocked' || current.run.revision !== run.revision || current.run.updatedAt !== run.updatedAt) return current;
204
206
  current.run.managed.authenticationWait.nextCheckAt = now + 60_000;
205
207
  if (authentication === 'available') {
206
- const queued = queuePurposeDiagnosis(current.run, 'Configured provider login is available again. Inspect the original purpose and any interrupted action before choosing authorized work.', `authentication-restored:${run.managed.authenticationWait.since}`, now);
208
+ const queued = queuePurposeDiagnosis(current.run, 'Configured provider login is available again. Inspect the original purpose and any interrupted action before choosing authorized work.', `authentication-restored:${run.managed.authenticationWait.since}`, now, 'authentication');
207
209
  if (queued) delete current.run.managed.authenticationWait;
208
210
  }
209
211
  return current;
@@ -219,7 +221,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
219
221
  current.run.managed.purpose.observationState = observed.state;
220
222
  return current;
221
223
  });
222
- if (observed.state === 'idle') obstacle = { type: 'idle_without_checkpoint', detail: observed.turnId };
224
+ if (observed.state === 'idle' && (!observed.session || observed.session === record.session)) obstacle = { type: 'idle_without_checkpoint', detail: observed.turnId };
223
225
  } else if (run.state === 'blocked' && ['missing_information', 'authorization', 'external_dependency'].includes(run.blocker)) {
224
226
  if (run.blockerSource === 'provider' || run.managed.attempts?.at(-1)?.errorCode === 'provider_approval') {
225
227
  if (now < (run.managed.purpose.lastObservedAt || 0) + 60_000) continue;
@@ -309,7 +311,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
309
311
  if (['native_wait_updated', 'native_wait_diagnosis'].includes(obstacle.type) && !nativeCurrent(current)) return current;
310
312
  if (searchEnabled(current.run.managed) && ['original_context_updated', 'native_wait_updated'].includes(obstacle.type)) current.run.managed.purpose.nextSearchAt = 0;
311
313
  const queued = queuePurposeDiagnosis(current.run,
312
- `Reassess the original purpose after ${obstacle.type}: ${obstacle.detail}. Gather evidence before deciding that a person is needed. Preserve unresolved approvals/dependencies; choose independent work or a safe alternative where possible.`, key, Date.now());
314
+ `Reassess the original purpose after ${obstacle.type}: ${obstacle.detail}. Use the shared scene reasoning cycle to select the next useful check. Preserve unresolved approvals/dependencies; choose independent work or a safe alternative where possible, then return to the original unfinished work.`, key, now, obstacle.type);
313
315
  if (queued && obstacle.type === 'external_dependency') {
314
316
  const purpose = current.run.managed.purpose;
315
317
  purpose.dependencyChecks = (purpose.dependencyChecks || 0) + 1;
@@ -318,6 +320,7 @@ export async function reconcilePurposeTasks({ root, signal, prepareCandidate = p
318
320
  return current;
319
321
  });
320
322
  } catch (error) {
323
+ if (signal?.aborted) break;
321
324
  if (!/^Delivery state (?:is busy; retry the command\.|lock owner is unavailable; inspect the lock before retrying\.)$/.test(error.message)) throw error;
322
325
  // One unavailable record cannot prevent independent conversations from
323
326
  // progressing. Never delete or reclaim an ambiguous writer lock.
@@ -26,6 +26,7 @@ export async function claudePluginFiles() {
26
26
  files.set("skills/delivery-loop/SKILL.md", await readFile(
27
27
  new URL("../delivery-hooks/claude-workflow.md", import.meta.url), "utf8"));
28
28
  files.set("hooks/delivery-loop.mjs", await readFile(path.join(SOURCE, "hooks/delivery-loop.mjs"), "utf8"));
29
+ files.set("hooks/recovery-incident.mjs", await readFile(path.join(SOURCE, "hooks/recovery-incident.mjs"), "utf8"));
29
30
  files.set("hooks/minimal-sufficient.mjs", await readFile(path.join(SOURCE, "hooks/minimal-sufficient.mjs"), "utf8"));
30
31
  files.set("hooks/outcome-progress.mjs", await readFile(path.join(SOURCE, "hooks/outcome-progress.mjs"), "utf8"));
31
32
  files.set("hooks/continuation-policy.mjs", await readFile(path.join(SOURCE, "hooks/continuation-policy.mjs"), "utf8"));
@@ -38,6 +39,7 @@ export async function claudePluginFiles() {
38
39
  files.set("hooks/completion-binding.mjs", await readFile(path.join(SOURCE, "hooks/completion-binding.mjs"), "utf8"));
39
40
  files.set("hooks/consistency-handoff.mjs", await readFile(path.join(SOURCE, "hooks/consistency-handoff.mjs"), "utf8"));
40
41
  files.set("hooks/purpose-evidence.mjs", await readFile(path.join(SOURCE, "hooks/purpose-evidence.mjs"), "utf8"));
42
+ files.set("hooks/scene-reasoning.mjs", await readFile(path.join(SOURCE, "hooks/scene-reasoning.mjs"), "utf8"));
41
43
  files.set("hooks/purpose-cycle.mjs", await readFile(path.join(SOURCE, "hooks/purpose-cycle.mjs"), "utf8"));
42
44
  files.set("hooks/purpose-rule-policy.mjs", await readFile(path.join(SOURCE, "hooks/purpose-rule-policy.mjs"), "utf8"));
43
45
  files.set("hooks/acceptance-policy.mjs", await readFile(path.join(SOURCE, "hooks/acceptance-policy.mjs"), "utf8"));
@@ -8,6 +8,7 @@ import path from 'node:path';
8
8
  // This catalog is shipped with the CLI; a repository cannot supply shell commands.
9
9
  const profiles = {
10
10
  'delivery-loop': [
11
+ { id: 'autonomous-recovery', files: ['test/autonomous-recovery.test.js', 'test/scene-reasoning.test.js', 'test/supervisor-watchdog.test.js'] },
11
12
  { id: 'perspective-memory', files: ['test/perspective-memory.test.js'] },
12
13
  { id: 'optional-reasoning', files: ['test/purpose-selective-rules.test.js', 'test/purpose-evidence.test.js', 'test/purpose-rule-review.test.js'] },
13
14
  // This connected controller group now contains over 200 cases. Keep a
@@ -1,3 +1,4 @@
1
+ import { withSceneReasoning } from '../codex-plugins/simy/hooks/scene-reasoning.mjs';
1
2
  import { requireProviderSubscription } from './subscription-auth.js';
2
3
  import { signalExecutorProcess } from "../runner.js";
3
4
  import spawn from "cross-spawn";
@@ -398,6 +399,8 @@ export function runStructuredCodexReview(request, options) {
398
399
  }
399
400
 
400
401
  export async function runStructuredProviderReview(provider, request, options = {}) {
402
+ // Keep malformed requests invalid; the reasoning policy must not fill missing authority or inputs.
403
+ if (typeof request?.system === 'string' && request.system.trim()) request = { ...request, system: withSceneReasoning(request.system) };
401
404
  const fallbackProviders = options.enableEngineFallback === true && options.providerReportingSupported === true && options.readOnlyAnalysis !== false && Array.isArray(options.allowedFallbackProviders)
402
405
  ? [...new Set(options.allowedFallbackProviders.filter(candidate => ["codex", "claude"].includes(candidate) && candidate !== provider))] : [];
403
406
  if (!fallbackProviders.length) return runSingleProviderReview(provider, request, options);