@awak-app/simy-cli 0.7.9 → 0.7.11

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.
Files changed (36) hide show
  1. package/README.md +17 -1
  2. package/package.json +4 -3
  3. package/src/cli-contract.js +1 -1
  4. package/src/codex-plugins/simy/hooks/delivery-loop.mjs +12 -2
  5. package/src/codex-plugins/simy/hooks/hypothesis-sheet.mjs +1 -1
  6. package/src/codex-plugins/simy/hooks/purpose-cycle.mjs +6 -5
  7. package/src/codex-plugins/simy/hooks/recovery-incident.mjs +191 -0
  8. package/src/codex-plugins/simy/hooks/scene-reasoning.mjs +22 -0
  9. package/src/delivery-hooks/command.js +34 -1
  10. package/src/delivery-hooks/desktop.js +1 -1
  11. package/src/delivery-hooks/diagnosis-sheet.js +2 -2
  12. package/src/delivery-hooks/diagnosis.js +5 -3
  13. package/src/delivery-hooks/local-supervisor.js +7 -1
  14. package/src/delivery-hooks/recovery.js +33 -0
  15. package/src/delivery-hooks/supervisor-owner.js +74 -0
  16. package/src/delivery-hooks/supervisor-service.js +65 -0
  17. package/src/delivery-hooks/supervisor-watchdog.js +151 -0
  18. package/src/delivery-hooks/supervisor.js +26 -0
  19. package/src/desktop-host.js +36 -7
  20. package/src/direct-executor.js +2 -1
  21. package/src/orchestrator/instruction.js +4 -4
  22. package/src/orchestrator/loop.js +1 -1
  23. package/src/orchestrator/problem-solving.js +2 -2
  24. package/src/provider-stream.js +3 -0
  25. package/src/session-supervision/claude-desktop-lineage.js +19 -5
  26. package/src/session-supervision/claude-sessions.js +16 -10
  27. package/src/session-supervision/codex-app-server.js +17 -6
  28. package/src/session-supervision/continuation-analysis.js +3 -2
  29. package/src/session-supervision/index.js +25 -14
  30. package/src/session-supervision/pull-requests.js +52 -0
  31. package/src/session-supervision/purpose-instruction.js +10 -2
  32. package/src/session-supervision/purpose-recovery.js +7 -4
  33. package/src/session-supervision/work-state.js +84 -0
  34. package/src/sqm/claude-plugin.js +2 -0
  35. package/src/sqm/local-tests.js +1 -0
  36. package/src/sqm/review-worker.js +3 -0
@@ -0,0 +1,84 @@
1
+ // Display-only state. Tracking/auto-continue consent is never evidence of execution.
2
+ export const WORK_STATE_TTL_MS = 45_000;
3
+ const freshObservation = (checkedAt, now) => Number.isFinite(checkedAt) && checkedAt <= now && now - checkedAt <= WORK_STATE_TTL_MS;
4
+ export function sessionWorkState(candidate, run, { now = Date.now(), provider } = {}) {
5
+ const checkedAt = candidate.observation?.observedAt ?? null;
6
+ const stale = !freshObservation(checkedAt, now);
7
+ const fetchFailed = provider?.discovery === 'unavailable';
8
+ const freshness = { checkedAt, stale, fetchFailed };
9
+ const result = (state, label, reason, nextAction = null, needsAttention = false) =>
10
+ ({ state, label, reason, nextAction, needsAttention, ...freshness });
11
+ if (candidate.archived || candidate.localHidden) return result('archived', 'アーカイブ済み', candidate.localHidden ? 'この一覧だけで非表示にしています。' : '元の会話がアーカイブされています。', 'restore');
12
+ let nativeRuntimeUnknown = false;
13
+ if (Object.hasOwn(candidate.observation || {}, 'activeFlags')) {
14
+ const flags = candidate.observation.activeFlags;
15
+ nativeRuntimeUnknown = stale || fetchFailed || !Array.isArray(flags) || flags.some(flag => !['waitingOnApproval', 'waitingOnUserInput'].includes(flag));
16
+ if (!nativeRuntimeUnknown && flags.includes('waitingOnApproval')) return result('approval', '承認待ち', '元のAIが操作の承認を待っています。', 'review_approval', true);
17
+ if (!nativeRuntimeUnknown && flags.includes('waitingOnUserInput')) return result('question', '回答待ち', '元のAIが回答を待っています。', 'answer', true);
18
+ }
19
+ // Never display a cached active flag as a live observation after a sleep/outage.
20
+ if (!nativeRuntimeUnknown && !stale && !fetchFailed && candidate.isRunning === true) return result('running', 'AIが対応中', '元のAIが実行中です。');
21
+ const approval = run?.pendingApproval || run?.managed?.pendingApproval;
22
+ const question = run?.understandingQuestion || run?.managed?.understandingQuestion || run?.managed?.userQuestion;
23
+ const phase = run?.phase || run?.managed?.phase;
24
+ const stopped = run?.state === 'stopped' || run?.stopReason?.actor === 'user' ||
25
+ run?.state === 'paused' && /^(Paused by user\.|Automatic continuation paused by user\.|User interrupted the provider\.|Stopped by user\.)$/.test(run.reason || '');
26
+ if (stopped) return result('stopped', '自分で停止', '停止の指示を維持しています。', 'resume');
27
+ if (approval && !approval.denied || candidate.decisionRequest?.kind === 'approval')
28
+ return result('approval', '承認待ち', candidate.decisionRequest?.context || '実行する操作の確認が必要です。', 'review_approval', true);
29
+ if (question || phase === 'needs_clarification' || candidate.analysis?.state === 'needs_user' || candidate.decisionRequest?.kind === 'question')
30
+ return result('question', '回答待ち', candidate.decisionRequest?.question || '続けるために回答が必要です。', 'answer', true);
31
+ if ((run?.autoRecovery || run?.managed?.autoRecovery) && ['recovery_required', 'auto_resuming'].includes(run.state)) return result('checking', 'AIが状況確認中', '自動復旧の結果を待っています。');
32
+ if (['failed', 'recovery_required', 'budget_exhausted'].includes(run?.state) || candidate.analysis?.state === 'failed' || candidate.autoContinueStatus === 'error')
33
+ return result('error', '対応が必要', run?.reason || '作業が中断されています。', run?.canResume || run?.canReplan ? 'resume' : 'open_original', true);
34
+ if (run?.dependencyWait || run?.managed?.dependencyWait) return result('waiting', '依存条件待ち', '条件が変わるまで待機します。');
35
+ if (['blocked', 'verification_pending', 'reported_done', 'paused'].includes(run?.state) || candidate.analysis?.state === 'continue' && candidate.autoContinue !== true || candidate.autoContinueStatus === 'needs_user')
36
+ return result('unfinished', '残りの作業あり', run?.reason || candidate.analysis?.reason || '未完了の作業を再開できます。', 'resume', true);
37
+ if (nativeRuntimeUnknown) return result('unknown', '実行状態を確認できません', '元のAIの最新の実行状態を確認できません。', 'refresh');
38
+ const prs = candidate.pullRequests || [];
39
+ if (prs.some(pr => pr.state === 'OPEN' && pr.reviewDecision === 'CHANGES_REQUESTED')) return result('changes_requested', 'PRの修正要求', '関連PRに修正要求があります。', 'open_pr', true);
40
+ if (prs.some(pr => pr.state === 'OPEN')) return result('review', 'PRレビュー待ち', '関連PRのレビュー・統合を待っています。', 'open_pr');
41
+ if (prs.some(pr => pr.state === 'UNKNOWN' || pr.fetchFailed || pr.stale)) return result('pr_unknown', 'PRの状態確認待ち', '関連PRの最新状態を確認できません。', 'refresh');
42
+ // A merge or no-follow-up recommendation is not task completion. no_action
43
+ // also covers casual chat, cancellation and optional future improvements.
44
+ if (run?.state === 'completed' || !run && candidate.providerGoal?.status === 'complete')
45
+ return result('completed', '完了', '会話の作業完了を確認しました。');
46
+ if (candidate.isRunning === true && (stale || fetchFailed)) return result('unknown', '実行状態を確認できません', '最終確認後の実行状態は不明です。', 'refresh');
47
+ if (run && ['active', 'auto_resuming'].includes(run.state) || ['queued', 'analyzing'].includes(candidate.analysis?.state)) return result('checking', 'AIの対応待ち', '次の処理・状態確認を待っています。');
48
+ return result('idle', '次の依頼待ち', '現在の実行や対応依頼は確認されていません。');
49
+ }
50
+
51
+ export function compareSessionWork(a, b) {
52
+ const priority = item => item.workState?.state === 'running' ? 0 : item.workState?.needsAttention ? 1 : 2;
53
+ return priority(a) - priority(b) || (b.lastActivityAt || 0) - (a.lastActivityAt || 0) || `${a.provider}:${a.session}`.localeCompare(`${b.provider}:${b.session}`);
54
+ }
55
+
56
+ // Completion belongs to the whole known family. Idle history alone does not
57
+ // create a human task; positively unfinished descendants do prevent completion.
58
+ export function reconcileFamilyCompletion(sessions) {
59
+ const byKey = new Map(sessions.map(item => [`${item.provider}:${item.session}`, item]));
60
+ for (const child of sessions) {
61
+ if (['idle', 'completed', 'archived'].includes(child.workState?.state)) continue;
62
+ const seen = new Set(); let parent = child.parentSession;
63
+ while (parent) {
64
+ const key = `${parent.provider}:${parent.session}`;
65
+ if (seen.has(key)) break;
66
+ seen.add(key); const ancestor = byKey.get(key);
67
+ if (!ancestor) break;
68
+ if (ancestor.workState?.state === 'completed') ancestor.workState = { ...ancestor.workState,
69
+ state: 'family_unfinished', label: '子の作業が未完了', reason: '関連する子の作業が残っています。', nextAction: 'review_children', needsAttention: false };
70
+ parent = ancestor.parentSession;
71
+ }
72
+ }
73
+ return sessions;
74
+ }
75
+
76
+ // Fast connection heartbeats may carry the previous full projection while a
77
+ // reader is blocked. Expire live labels there too, without losing pending work.
78
+ export function expireSessionWorkState(candidate, now = Date.now()) {
79
+ const previous = candidate.workState;
80
+ if (!previous || freshObservation(previous.checkedAt, now)) return candidate;
81
+ if (previous.state !== 'running') return previous.stale ? candidate : { ...candidate, workState: { ...previous, stale: true } };
82
+ return { ...candidate, workState: { ...previous, state: 'unknown', label: '実行状態を確認できません',
83
+ reason: '最終確認後の実行状態は不明です。', nextAction: 'refresh', needsAttention: false, stale: true } };
84
+ }
@@ -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);