@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.
- package/package.json +4 -3
- package/src/cli-contract.js +1 -1
- package/src/codex-plugins/simy/hooks/delivery-loop.mjs +12 -2
- package/src/codex-plugins/simy/hooks/hypothesis-sheet.mjs +1 -1
- package/src/codex-plugins/simy/hooks/purpose-cycle.mjs +6 -5
- package/src/codex-plugins/simy/hooks/recovery-incident.mjs +191 -0
- package/src/codex-plugins/simy/hooks/scene-reasoning.mjs +22 -0
- package/src/delivery-hooks/command.js +34 -1
- package/src/delivery-hooks/diagnosis-sheet.js +2 -2
- package/src/delivery-hooks/diagnosis.js +5 -3
- package/src/delivery-hooks/local-supervisor.js +7 -1
- package/src/delivery-hooks/recovery.js +33 -0
- package/src/delivery-hooks/supervisor-owner.js +74 -0
- package/src/delivery-hooks/supervisor-service.js +65 -0
- package/src/delivery-hooks/supervisor-watchdog.js +151 -0
- package/src/delivery-hooks/supervisor.js +26 -0
- package/src/direct-executor.js +2 -1
- package/src/orchestrator/instruction.js +4 -4
- package/src/orchestrator/loop.js +1 -1
- package/src/orchestrator/problem-solving.js +2 -2
- package/src/session-supervision/codex-app-server.js +9 -5
- package/src/session-supervision/continuation-analysis.js +3 -2
- package/src/session-supervision/purpose-instruction.js +10 -2
- package/src/session-supervision/purpose-recovery.js +7 -4
- package/src/sqm/claude-plugin.js +2 -0
- package/src/sqm/local-tests.js +1 -0
- package/src/sqm/review-worker.js +3 -0
|
@@ -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}.
|
|
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.
|
package/src/sqm/claude-plugin.js
CHANGED
|
@@ -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"));
|
package/src/sqm/local-tests.js
CHANGED
|
@@ -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
|
package/src/sqm/review-worker.js
CHANGED
|
@@ -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);
|