elanous 0.2.31 → 0.2.32
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/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1834-50625904996a8fa8.js → 1834-98b64172bc1efe77.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/69-0a2c8209c6d4a158.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8097-9530c03bcc550f02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-504204b58c82c253.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3535a4a69f1fafaa.js → page-a9717a67c2eecbaf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-5c9ec30aee62dfbf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-584191533721fa16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-73c4b4bb3ac7ab5f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/{page-5a503541353e60a8.js → page-c0f1ca371f710b06.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-6025101384e361a5.js → page-46430d45a9e04eac.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b2ebf1dc64ff0be.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-a645f00a06483e9a.js → page-a27b1e5a0d1b308f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-25654b82094f7d22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-e9bab7413b028b28.js → page-8ea49a4c6dd2c6e7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-ce5113aefea0a8d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-e547e6ce55f1fb33.js → page-0e5a41fac1c75396.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-d27e1b9850fd54bf.js → page-31aaf1ed37c6df9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-3dcc1fa411e1f4e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-0fec29c7ba01894b.js → page-c69247e7134d329d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-339a07410cd6f36b.js → layout-66dc5cd3a0999abe.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-26ff7ff651a7217d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-617b3976981efa96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-2fd93ed8c9f16580.js → page-d256f5d5d9552823.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-9b564dbff0fefc35.js → page-ce8f1907b8a6101c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-33e182a316263116.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-814ba51385225776.js → page-32866485913d6a04.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-397d479626e5a459.js → not-found-7607cca9b84950c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-af1bba4e3251b728.js → page-eeab86176b03dc3b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-3ad3be5e7d3fdce9.js → page-5b93dbae3432b996.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-77a6cb90a9cf2b10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-4da93ad35d4a8d28.js → page-f47e768b5b242b5a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-eb41aa3367fc2505.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-4d13ab0b8a267e3f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-84dad32ff0fc0831.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-3a658d5fa6d16a25.js → page-95306f892aa2d0ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-0157de6e85f8bb6b.js → page-940fb6c217f6b470.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0a07363b130182a1.js → page-8c66a3b24a745630.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-841d73d4ac44ed49.js → page-7b675dfd35c36fc9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-6f43a3d1146e6377.js → page-8bd0d3cf7665bcfc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-521ee23ac8c0abfa.js → page-c219f2ed171131be.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-a9992ee285efaa83.js → page-40f8fa21bdae955b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-3d48e85fa89ec04f.js → page-468f8313e4f1fbd9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-7bbb8fa353a246c4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-04d00ae9b0890349.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/{page-233c71cd5f68f0d7.js → page-4214b855d9233ea8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-70151d78de2f375e.js → page-936752c7aa58762e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-dcac33a3e2cb72c0.js → page-ec052ad359df1797.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-8305570551091338.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-0448145ccd5c45c1.js → page-fad650e4793adb65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-d90e64743826e1d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-a16bcfd49af86a32.js +1 -0
- package/apps/pwa/out/_next/static/css/b5d88eba78a3d7bf.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -1
- package/apps/pwa/out/decisions/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +17 -14
- package/apps/pwa/out/live-run/index.html +1 -1
- package/apps/pwa/out/live-run/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/graphs/implement-loop.yaml +5 -0
- package/package.json +1 -1
- package/scripts/ci-typecheck-changed.ts +26 -1
- package/scripts/measure-si-task-cpu.ts +143 -0
- package/scripts/ratchets/test-interference-patterns.allowlist.txt +0 -2
- package/scripts/release-loop/prepare-node.ts +102 -4
- package/src/acp/core-turn-bridge.ts +16 -4
- package/src/acp/elanous-extensions.ts +3 -0
- package/src/acp/server.ts +3 -0
- package/src/acp/tool-trace.ts +186 -11
- package/src/agent/self-ambient-cache.ts +156 -0
- package/src/agent/self-ambient.ts +8 -1
- package/src/agent/task-notification.ts +5 -0
- package/src/agent-substrate/pr-reviewer.ts +3 -1
- package/src/ask-user-question/tool.ts +22 -0
- package/src/autopilot/build/llm-conflict-merge.ts +56 -10
- package/src/autopilot/pr-manager.ts +23 -5
- package/src/capture/frame-state-detect.ts +39 -0
- package/src/capture/tui-self-report.ts +134 -6
- package/src/chat/index.ts +61 -15
- package/src/chat/pickers/modal-runtime.ts +6 -0
- package/src/chat/pickers/modals.ts +8 -2
- package/src/cli/ask-bare.ts +1 -0
- package/src/cli/import-cli.ts +12 -0
- package/src/cli/log-event-names.ts +10 -0
- package/src/cli/msg-cli.ts +1 -0
- package/src/cli/pod-cli.ts +14 -3
- package/src/cli/pr-cli.ts +67 -32
- package/src/cli/pr-land-base-read.ts +34 -0
- package/src/cli/pr-land-heavy-remote.ts +81 -25
- package/src/cli/pr-land-overlap-evidence.ts +46 -2
- package/src/cli/pr-land-pod-shadow-job.ts +26 -3
- package/src/cli/pr-land-refusal.ts +20 -5
- package/src/cli/pty-takeover-cli.ts +88 -14
- package/src/cli/sim-cli.ts +18 -0
- package/src/context-bus/ops-events.ts +6 -2
- package/src/core-turn/run-core-turn.ts +1 -0
- package/src/core-turn/run-goal-loop.ts +21 -0
- package/src/core-turn/types.ts +3 -0
- package/src/dashboard/acp-active-model.ts +42 -1
- package/src/dashboard/background-jobs-status.ts +66 -0
- package/src/dashboard/index.ts +117 -44
- package/src/dashboard/input/agent-wake.ts +242 -0
- package/src/dashboard/input/chat-main-entry-runtime.ts +5 -0
- package/src/dashboard/input/external-input-host.ts +23 -9
- package/src/dashboard/input/mouse-wiring.ts +108 -11
- package/src/dashboard/log-click-runtime.ts +118 -2
- package/src/dashboard/turn-preamble.ts +116 -40
- package/src/dashboard/turn-session-layers.ts +4 -2
- package/src/dashboard/turn-start-segments.ts +222 -0
- package/src/display/prompt-row-owner.ts +146 -0
- package/src/display/resize-listener.ts +33 -2
- package/src/fold-stack.ts +4 -2
- package/src/git-fs/index.ts +263 -36
- package/src/harness/gate-reserve.ts +87 -0
- package/src/harness/harness-incidents.ts +13 -0
- package/src/harness/harness-queue.ts +15 -0
- package/src/index.ts +8 -6
- package/src/input/external-input-queue.ts +34 -9
- package/src/llm/route-decision.ts +36 -0
- package/src/llm/tool-loop-steered-rejection.ts +8 -0
- package/src/llm-request-shape.ts +209 -0
- package/src/llm.ts +166 -267
- package/src/loops/orchestrator/tick.ts +24 -2
- package/src/maturity/feature-maturity.ts +1 -1
- package/src/mss/logging/render-categories.ts +5 -0
- package/src/ops/overview.ts +59 -2
- package/src/pr-tool/census.ts +19 -4
- package/src/pr-tool/inflight-registry.ts +143 -0
- package/src/pty-shell/pty-manifest.ts +12 -3
- package/src/pty-shell/tui-run-state.ts +34 -3
- package/src/seat-loop/traffic-signal.ts +2 -1
- package/src/self-dev/ask-launch-flow.ts +13 -0
- package/src/self-dev/draft-residue.ts +243 -24
- package/src/self-dev/shard-plan.ts +11 -3
- package/src/self-dev/shard-run.ts +103 -10
- package/src/self-implement/elanous-tui-spawn.ts +14 -0
- package/src/self-implement/follow-up-placement.ts +79 -0
- package/src/self-implement/gate-artifacts.ts +15 -4
- package/src/self-implement/gate-baseline.ts +96 -5
- package/src/self-implement/gate-cli.ts +7 -2
- package/src/self-implement/gate-remote.ts +26 -1
- package/src/self-implement/host-regate.ts +36 -4
- package/src/self-implement/orchestrator.ts +101 -11
- package/src/self-implement/run-ledger.ts +16 -14
- package/src/session-runtime/index.ts +22 -267
- package/src/shell-runner/background-surface.ts +15 -5
- package/src/skills/tools/agent.ts +39 -6
- package/src/skills/tools/list-dir.ts +11 -0
- package/src/skills/tools/schedule-wakeup.ts +408 -0
- package/src/skills/tools/shell.ts +245 -3
- package/src/status/bar.ts +26 -0
- package/src/steward/digest-delivery.ts +118 -0
- package/src/steward/hold-hygiene.ts +120 -0
- package/src/steward/triage.ts +10 -0
- package/src/surface/shell-runner-boot.ts +5 -2
- package/src/task-agent/task-hand.ts +24 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +65 -11
- package/src/task-orchestrator/surfaces/pod-lease.ts +98 -0
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-pool.ts +3 -1
- package/src/task-orchestrator/surfaces/pod-reuse-cleanup.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-warm-worker-pod.ts +525 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-runner.ts +90 -0
- package/src/task-orchestrator/surfaces/pod-warm-worker-runtime.ts +156 -2
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +7 -5
- package/src/tui/chat/execution-badge.ts +5 -1
- package/src/tui/input-lag-probe.ts +230 -0
- package/src/tui.ts +4 -0
- package/src/ux-sim/scenario.ts +255 -0
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/8097-92f87bd108322f66.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-bf3634a117322df7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b9f51447f757fbf2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-2ebb8fd59b9461f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a2fc4e49a4fa4ba8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-f4b630cc8b5273cd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-61ac22cbab1ee4ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c992eafe028b642e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-805ac2e137b7a121.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-349f2752d8cd37ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live-run/page-d5c6dcf31c25cecc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-62f4475d3c38fb9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-f219ccdbc0bf27df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-456666766e5ce6fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-7404e377e8adb8c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-14456240ed2a36ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-d2ced71132fac9b2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-f08358bdf251adb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-149dc1caba411bb9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9cb0c444903da65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-17e504c396e0e389.js +0 -1
- package/apps/pwa/out/_next/static/css/3f5fc6f96e2cf7b2.css +0 -1
- /package/apps/pwa/out/_next/static/{LSXfY0u9QtfDbejY0Ukf_ → IVKr3AlRUVLKsw2fh7eXF}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{LSXfY0u9QtfDbejY0Ukf_ → IVKr3AlRUVLKsw2fh7eXF}/_ssgManifest.js +0 -0
package/src/llm.ts
CHANGED
|
@@ -48,7 +48,8 @@ import {
|
|
|
48
48
|
type ProviderFallbackAttempt,
|
|
49
49
|
type ProviderFallbackTerminalVerdict,
|
|
50
50
|
} from './session-runtime/retry-policy.js';
|
|
51
|
-
import { fetchApiWithRetry, OverloadFailoverError, type FetchApiWithRetryOpts } from './session-runtime/retry-api.js';
|
|
51
|
+
import { ApiHttpError, fetchApiWithRetry, OverloadFailoverError, type FetchApiWithRetryOpts } from './session-runtime/retry-api.js';
|
|
52
|
+
import { isShapeRelevantRejection, summarizeCodexRequestShape } from './llm-request-shape.js';
|
|
52
53
|
import { BedrockProvider, isBedrockCompatibleModel, makeBedrockProvider } from './llm/bedrock-provider.js';
|
|
53
54
|
import { OPEN_WEIGHT_TOOL_DISCIPLINE } from './llm/open-weight-discipline.js';
|
|
54
55
|
import { makeZaiCodingPlanProvider, zaiCodingPlanAvailable } from './llm/zai-coding-plan.js';
|
|
@@ -61,8 +62,6 @@ import { undoTurnRuntime } from './tool-runtime/undo-turn-runtime.js';
|
|
|
61
62
|
import { listSnapshots } from './undo-turn/index.js';
|
|
62
63
|
// Zero-dep spec module (no tool-runtime registry) — safe for this hot path.
|
|
63
64
|
import { takeHydratedTools } from './skills/tools/tool-search-spec.js';
|
|
64
|
-
import { dispatchAskUserQuestion } from './ask-user-question/index.js';
|
|
65
|
-
import type { AskUserQuestionResult } from './ask-user-question/index.js';
|
|
66
65
|
import { isPlanModeActive } from './plan-mode/session.js';
|
|
67
66
|
import { mintTurnUri } from './mss/uri/builder.js';
|
|
68
67
|
import type { TurnUri } from './mss/uri/brand.js';
|
|
@@ -406,6 +405,10 @@ export interface LLMOpts {
|
|
|
406
405
|
* ③ 종결 신호를 받은 뒤의 빈 턴 포기에도 이미 쓴 답이 있으면 `[NO FINAL SYNTHESIS]` 를 붙이지 않는다.
|
|
407
406
|
* 비우면(기본) 종전 동작 그대로. */
|
|
408
407
|
terminalToolNames?: readonly string[];
|
|
408
|
+
/** TUI-H1-GOALLOOP-EMPTY-TAIL — 호출자가 이 스트림 «앞»에 사람에게 답을 이미 보였다(예: goal loop 의 앞 iteration).
|
|
409
|
+
* 그러면 종결 신호 뒤 빈 턴은 이 스트림에 쓴 답이 없어도 재촉·`[NO FINAL SYNTHESIS]` 없이 빈 답으로 끝낸다.
|
|
410
|
+
* 비우면(기본) 종전대로 — 답이 «어디에도» 없을 때의 경고는 그대로 보인다. */
|
|
411
|
+
terminalPriorAnswerVisible?: boolean;
|
|
409
412
|
/** Prompt-caching toggle. Honored by Anthropic (attaches
|
|
410
413
|
* cache_control markers on system + tools + 2nd-to-last history
|
|
411
414
|
* message) and OpenAI (passes stream_options.include_usage so
|
|
@@ -425,12 +428,9 @@ export interface LLMOpts {
|
|
|
425
428
|
* every tool-loop turn gets a stable checkpoint identity without
|
|
426
429
|
* forcing existing callers to thread the brand through. */
|
|
427
430
|
turnUri?: TurnUri;
|
|
428
|
-
/** Chat session id
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
* `elanous/ask/*` envelope instead of the raw `[ASK USER]` text
|
|
432
|
-
* fallback. Absent for CLI / startup-script callers without a chat
|
|
433
|
-
* session context. */
|
|
431
|
+
/** Chat session id (context compaction · provider sticky routing).
|
|
432
|
+
* Absent for CLI / startup-script callers without a chat session
|
|
433
|
+
* context. */
|
|
434
434
|
sessionId?: string;
|
|
435
435
|
/** Server-side built-in tools the provider should advertise to the
|
|
436
436
|
* model alongside the caller-supplied `tools`. Each provider has
|
|
@@ -2195,6 +2195,31 @@ export async function* streamCodexResponsesEvents(
|
|
|
2195
2195
|
// applies the same tool/SSE conventions
|
|
2196
2196
|
const promptCache = extra.promptCache !== false;
|
|
2197
2197
|
const cacheKey = promptCache ? computePromptCacheKey(body.model, body.instructions) : undefined;
|
|
2198
|
+
// TUI-OBS-400-SHAPE(#69) — 전송 객체·문자열을 한 번 만들어 둔다: 4xx 거절 때 «실제로 보낸 것» 의 모양을 잰다.
|
|
2199
|
+
const wireBody: Record<string, unknown> = {
|
|
2200
|
+
...body,
|
|
2201
|
+
stream: true,
|
|
2202
|
+
// store=true wave: when caller opts in, the backend persists the
|
|
2203
|
+
// response and the next request can thread `previous_response_id`
|
|
2204
|
+
// to send only the delta input items. Default still false so any
|
|
2205
|
+
// caller that doesn't opt in (tests building bodies directly)
|
|
2206
|
+
// sees the legacy wire shape.
|
|
2207
|
+
store: body.store === true,
|
|
2208
|
+
...(body.tool_choice ? { tool_choice: body.tool_choice } : {}),
|
|
2209
|
+
...(body.previousResponseId ? { previous_response_id: body.previousResponseId } : {}),
|
|
2210
|
+
...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
|
|
2211
|
+
// Reasoning opt-in (gpt-5 family). When the caller sets
|
|
2212
|
+
// body.reasoning we mirror it onto the wire AND append the
|
|
2213
|
+
// include directive that lets multi-turn requests thread the
|
|
2214
|
+
// model's encrypted reasoning context. Omit both when absent
|
|
2215
|
+
// so non-reasoning models / opt-out users see the legacy
|
|
2216
|
+
// wire shape unchanged.
|
|
2217
|
+
...(body.reasoning ? {
|
|
2218
|
+
reasoning: body.reasoning,
|
|
2219
|
+
include: ['reasoning.encrypted_content'],
|
|
2220
|
+
} : {}),
|
|
2221
|
+
};
|
|
2222
|
+
const wireJson = JSON.stringify(wireBody);
|
|
2198
2223
|
const response = await fetchApiWithRetry(
|
|
2199
2224
|
url,
|
|
2200
2225
|
{
|
|
@@ -2212,33 +2237,14 @@ export async function* streamCodexResponsesEvents(
|
|
|
2212
2237
|
// Codex". See getCodexUserAgent (oauth/codex.ts).
|
|
2213
2238
|
'User-Agent': getCodexUserAgent(),
|
|
2214
2239
|
},
|
|
2215
|
-
body:
|
|
2216
|
-
...body,
|
|
2217
|
-
stream: true,
|
|
2218
|
-
// store=true wave: when caller opts in, the backend persists the
|
|
2219
|
-
// response and the next request can thread `previous_response_id`
|
|
2220
|
-
// to send only the delta input items. Default still false so any
|
|
2221
|
-
// caller that doesn't opt in (tests building bodies directly)
|
|
2222
|
-
// sees the legacy wire shape.
|
|
2223
|
-
store: body.store === true,
|
|
2224
|
-
...(body.tool_choice ? { tool_choice: body.tool_choice } : {}),
|
|
2225
|
-
...(body.previousResponseId ? { previous_response_id: body.previousResponseId } : {}),
|
|
2226
|
-
...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
|
|
2227
|
-
// Reasoning opt-in (gpt-5 family). When the caller sets
|
|
2228
|
-
// body.reasoning we mirror it onto the wire AND append the
|
|
2229
|
-
// include directive that lets multi-turn requests thread the
|
|
2230
|
-
// model's encrypted reasoning context. Omit both when absent
|
|
2231
|
-
// so non-reasoning models / opt-out users see the legacy
|
|
2232
|
-
// wire shape unchanged.
|
|
2233
|
-
...(body.reasoning ? {
|
|
2234
|
-
reasoning: body.reasoning,
|
|
2235
|
-
include: ['reasoning.encrypted_content'],
|
|
2236
|
-
} : {}),
|
|
2237
|
-
}),
|
|
2240
|
+
body: wireJson,
|
|
2238
2241
|
signal,
|
|
2239
2242
|
},
|
|
2240
2243
|
retryOptsFor('codex', 'Codex API', extra.remainingFallbacks),
|
|
2241
|
-
)
|
|
2244
|
+
).catch((err: unknown) => {
|
|
2245
|
+
observeCodexRequestRejection(err, wireBody, wireJson);
|
|
2246
|
+
throw err;
|
|
2247
|
+
});
|
|
2242
2248
|
debug.log('llm.response.status', `codex ${response.status}`, {
|
|
2243
2249
|
url, status: response.status, elapsedMs: Date.now() - startedAt,
|
|
2244
2250
|
});
|
|
@@ -2376,6 +2382,19 @@ export async function* streamCodexResponsesEvents(
|
|
|
2376
2382
|
}
|
|
2377
2383
|
}
|
|
2378
2384
|
|
|
2385
|
+
/** TUI-OBS-400-SHAPE(#69) — Codex 가 요청을 4xx(401·403·429 제외)로 거절하면 보낸 요청의 «모양» 만 남긴다.
|
|
2386
|
+
* Codex 400 은 `{"detail":"Bad Request"}` 뿐이라 크기·메시지 수·도구 짝 깨짐을 여기서 잰다.
|
|
2387
|
+
* ⛔ 본문·프롬프트·도구 인자/출력·키는 싣지 않는다(수·바이트·종류·call_id 해시 앞 8자만).
|
|
2388
|
+
* 관측 실패는 오류 계약을 바꾸지 않는다 — 호출부는 원래 오류를 그대로 던진다. */
|
|
2389
|
+
function observeCodexRequestRejection(err: unknown, wireBody: Record<string, unknown>, wireJson: string): void {
|
|
2390
|
+
if (!(err instanceof ApiHttpError) || !isShapeRelevantRejection(err.status)) return;
|
|
2391
|
+
try {
|
|
2392
|
+
debug.log('llm.codex', 'request-rejected-shape', summarizeCodexRequestShape(wireBody, wireJson, err.status, err.bodyText));
|
|
2393
|
+
} catch {
|
|
2394
|
+
// Observability must not interfere with the provider error contract.
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2379
2398
|
function toolNames(raw: unknown): string[] | undefined {
|
|
2380
2399
|
if (!Array.isArray(raw)) return undefined;
|
|
2381
2400
|
return raw.map(item => {
|
|
@@ -4998,6 +5017,14 @@ export interface StreamWithToolsHandlers {
|
|
|
4998
5017
|
* before the `onText('', enriched)` re-emit so presentation hosts can
|
|
4999
5018
|
* keep the user answer to `answer` and send `supplement` to logs. */
|
|
5000
5019
|
onFinalAnswerSupplement?(info: { answer: string; supplement: string; finalText: string }): void;
|
|
5020
|
+
/** 검증 보충 — final-answer supplement attachment point. Called after the
|
|
5021
|
+
* final answer is settled (and after any finalization-snapshot enrichment)
|
|
5022
|
+
* so callers can append verification-recency / loop-closure wording after
|
|
5023
|
+
* the final answer. Return a non-empty string to append it to the final
|
|
5024
|
+
* answer output (it will be joined after `finalText` and the combined
|
|
5025
|
+
* supplement surfaces via `onFinalAnswerSupplement`); return
|
|
5026
|
+
* undefined/null/empty to keep the prior behavior unchanged. */
|
|
5027
|
+
onAppendFinalAnswerSupplement?(req: FinalAnswerSupplementAttachmentRequest): string | null | undefined;
|
|
5001
5028
|
/** Optional: notify when a tool_call is about to fire (for log line,
|
|
5002
5029
|
* HUD segment, etc). */
|
|
5003
5030
|
onToolCall?(call: { id: string; name: string; args: Record<string, unknown> }): void;
|
|
@@ -5856,44 +5883,15 @@ function buildAutoUndoRepairNotice(result: { output?: string; restoredId?: strin
|
|
|
5856
5883
|
].filter(Boolean).join('\n');
|
|
5857
5884
|
}
|
|
5858
5885
|
|
|
5859
|
-
//
|
|
5860
|
-
// reachable (no resolver, no sessionId, dispatch error). The marker
|
|
5861
|
-
// `[ASK USER]` is preserved so `isUserInterventionRequired` downstream
|
|
5862
|
-
// still skips force-synthesis — that gate predates the bridge route and
|
|
5863
|
-
// other surfaces (TUI) still rely on the marker as a hard-stop signal.
|
|
5864
|
-
function buildAutoUndoAskUserNotice(reason: DoomLoopReason, detail?: string | null): string {
|
|
5865
|
-
const why = reason === 'plan-mode'
|
|
5866
|
-
? 'Auto-undo was skipped because plan mode is active.'
|
|
5867
|
-
: reason === 'no-undo-available'
|
|
5868
|
-
? 'Auto-undo was skipped because no undo snapshot is available.'
|
|
5869
|
-
: reason === 'undo-failed'
|
|
5870
|
-
? 'Auto-undo was attempted but the undo runtime failed.'
|
|
5871
|
-
: 'Retry policy did not authorize auto-undo for this failure.';
|
|
5872
|
-
return [
|
|
5873
|
-
'[ASK USER] The same tool call kept failing — stopping to avoid wasting more turns.',
|
|
5874
|
-
why,
|
|
5875
|
-
detail ? `Detail: ${detail}` : '',
|
|
5876
|
-
'Reply with how to proceed (retry, change approach, or stop).',
|
|
5877
|
-
].filter(Boolean).join(' ');
|
|
5878
|
-
}
|
|
5879
|
-
|
|
5880
|
-
// ── Doom-loop → AskUserQuestion routing ─────────────────────────
|
|
5881
|
-
//
|
|
5882
|
-
// When the tool-loop trips the doom-loop gate (same tool error 3x) it
|
|
5883
|
-
// previously emitted a raw `[ASK USER]` chat message describing what
|
|
5884
|
-
// went wrong. With the AskUserQuestion bridge (PR #2611 PWA, #2612
|
|
5885
|
-
// iOS, #2613 multi-option fan-out) live across PWA + iOS surfaces, we
|
|
5886
|
-
// can self-dispatch a structured prompt instead so the user sees a
|
|
5887
|
-
// modal with retry / different-approach / stop options rather than
|
|
5888
|
-
// raw English instructions intended for the LLM.
|
|
5889
|
-
//
|
|
5890
|
-
// Falls back to the legacy `[ASK USER]` chat text when:
|
|
5891
|
-
// - no `sessionId` plumbed (CLI / startup script callers),
|
|
5892
|
-
// - no resolver wired (TUI-only or dispatch error), or
|
|
5893
|
-
// - the bridge throws (peer disconnected, surface mismatch).
|
|
5886
|
+
// ── Doom-loop self-heal ─────────────────────────────────────────
|
|
5894
5887
|
//
|
|
5895
|
-
//
|
|
5896
|
-
//
|
|
5888
|
+
// When the tool-loop trips the doom-loop gate (same tool error 3x) and
|
|
5889
|
+
// auto-undo cannot run, the loop heals itself instead of asking the user
|
|
5890
|
+
// (대표 10-17 — doom-loop is not a user-facing notice: in 40 h of runs it
|
|
5891
|
+
// tripped 3 times and the AskUser prompt was answered 0 times, each one
|
|
5892
|
+
// sitting 5 min until timeout). First trip → a «change approach» note for
|
|
5893
|
+
// the model and the turn continues; a second trip in the same turn → the
|
|
5894
|
+
// turn ends with a plain stop text (no question to the user).
|
|
5897
5895
|
|
|
5898
5896
|
export type DoomLoopReason = 'plan-mode' | 'no-undo-available' | 'undo-failed' | 'policy';
|
|
5899
5897
|
|
|
@@ -5983,174 +5981,35 @@ function stringifyDoomFailureDetail(value: unknown): string {
|
|
|
5983
5981
|
}
|
|
5984
5982
|
|
|
5985
5983
|
export type DoomLoopRouteOutcome =
|
|
5986
|
-
| { kind: 'retry'; interventionClass: DoomLoopInterventionClass }
|
|
5987
5984
|
| { kind: 'guidance'; userMessage: string; interventionClass: DoomLoopInterventionClass }
|
|
5988
|
-
| { kind: 'stop'; hardStopText: string; interventionClass: DoomLoopInterventionClass }
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5985
|
+
| { kind: 'stop'; hardStopText: string; interventionClass: DoomLoopInterventionClass };
|
|
5986
|
+
|
|
5987
|
+
/** Visible text when the turn ends on a second doom trip. Deliberately not a
|
|
5988
|
+
* question and without the `[ASK USER]` marker — nobody is asked. */
|
|
5989
|
+
export const DOOM_LOOP_SELF_HEAL_STOP_TEXT =
|
|
5990
|
+
'같은 도구 호출이 방법을 바꾼 뒤에도 계속 실패해 이번 턴을 여기서 마칩니다.';
|
|
5991
|
+
|
|
5992
|
+
export interface DoomLoopSelfHealInput {
|
|
5993
|
+
/** 1 = first trip in this turn, 2+ = tripped again after the guidance. */
|
|
5994
|
+
attempt: number;
|
|
5995
|
+
tool?: string;
|
|
5996
|
+
fingerprint: string;
|
|
5994
5997
|
interventionClass?: DoomLoopInterventionClass | null;
|
|
5995
|
-
detail?: string | null;
|
|
5996
|
-
doomWindow: string[];
|
|
5997
|
-
sessionId?: string;
|
|
5998
|
-
signal?: AbortSignal;
|
|
5999
|
-
/** Override the AskUser gate timeout (ms). Defaults to
|
|
6000
|
-
* DOOM_ASK_TIMEOUT_MS. Primarily a test seam. */
|
|
6001
|
-
timeoutMs?: number;
|
|
6002
|
-
}
|
|
6003
|
-
|
|
6004
|
-
export function buildDoomLoopAskRequest(opts: DoomLoopRouteOpts): Record<string, unknown> {
|
|
6005
|
-
const interventionClass = normalizeDoomLoopInterventionClass(opts.interventionClass);
|
|
6006
|
-
const why = opts.reason === 'plan-mode'
|
|
6007
|
-
? 'plan mode is active so auto-undo was skipped'
|
|
6008
|
-
: opts.reason === 'undo-failed'
|
|
6009
|
-
? `auto-undo was attempted but failed (${opts.detail ?? 'no detail'})`
|
|
6010
|
-
: `auto-undo was not authorized by the retry policy (${opts.detail ?? 'no detail'})`;
|
|
6011
|
-
const lastFingerprint = opts.doomWindow[opts.doomWindow.length - 1] ?? 'unknown';
|
|
6012
|
-
return {
|
|
6013
|
-
interventionClass,
|
|
6014
|
-
questions: [
|
|
6015
|
-
{
|
|
6016
|
-
id: 'doom_loop_next_step',
|
|
6017
|
-
header: 'Loop blocked',
|
|
6018
|
-
question:
|
|
6019
|
-
'The same tool call kept failing after 3 attempts — '
|
|
6020
|
-
+ `${why}. How should I proceed?\n\nLast failure: ${lastFingerprint}`,
|
|
6021
|
-
options: [
|
|
6022
|
-
{
|
|
6023
|
-
label: 'Retry once more',
|
|
6024
|
-
description: 'Reset the retry tracker and let me try the same approach one more time.',
|
|
6025
|
-
},
|
|
6026
|
-
{
|
|
6027
|
-
label: 'Different approach',
|
|
6028
|
-
description: 'Type guidance in Other and I will follow it on the next turn.',
|
|
6029
|
-
},
|
|
6030
|
-
{
|
|
6031
|
-
label: 'Stop here',
|
|
6032
|
-
description: 'Halt this run. I will summarize what was tried and what was blocked.',
|
|
6033
|
-
},
|
|
6034
|
-
],
|
|
6035
|
-
includeOther: true,
|
|
6036
|
-
},
|
|
6037
|
-
],
|
|
6038
|
-
};
|
|
6039
|
-
}
|
|
6040
|
-
|
|
6041
|
-
export function interpretDoomLoopAskResult(
|
|
6042
|
-
result: AskUserQuestionResult,
|
|
6043
|
-
interventionClass: DoomLoopInterventionClass = 'unknown',
|
|
6044
|
-
): DoomLoopRouteOutcome {
|
|
6045
|
-
if (result.cancelled === true) {
|
|
6046
|
-
return {
|
|
6047
|
-
kind: 'stop',
|
|
6048
|
-
interventionClass,
|
|
6049
|
-
hardStopText:
|
|
6050
|
-
'[ASK USER] The same tool call kept failing and you cancelled the prompt — stopping this run.',
|
|
6051
|
-
};
|
|
6052
|
-
}
|
|
6053
|
-
const answer = result.answers?.['doom_loop_next_step'];
|
|
6054
|
-
const picked = Array.isArray(answer) ? (answer[0] ?? '') : (answer ?? '');
|
|
6055
|
-
const otherText = result.otherText?.['doom_loop_next_step']?.trim();
|
|
6056
|
-
|
|
6057
|
-
if (picked.startsWith('Retry')) {
|
|
6058
|
-
return { kind: 'retry', interventionClass };
|
|
6059
|
-
}
|
|
6060
|
-
if (picked.startsWith('Different') || picked === 'Other') {
|
|
6061
|
-
const guidance = otherText && otherText.length > 0
|
|
6062
|
-
? otherText
|
|
6063
|
-
: 'Please try a different approach to the same goal — the previous tool call kept failing.';
|
|
6064
|
-
return { kind: 'guidance', userMessage: guidance, interventionClass };
|
|
6065
|
-
}
|
|
6066
|
-
if (picked.startsWith('Stop')) {
|
|
6067
|
-
return {
|
|
6068
|
-
kind: 'stop',
|
|
6069
|
-
interventionClass,
|
|
6070
|
-
hardStopText: '[ASK USER] Stopped at your request after the repeated tool failure.',
|
|
6071
|
-
};
|
|
6072
|
-
}
|
|
6073
|
-
return {
|
|
6074
|
-
kind: 'stop',
|
|
6075
|
-
interventionClass,
|
|
6076
|
-
hardStopText:
|
|
6077
|
-
'[ASK USER] The same tool call kept failing and the response was unrecognized — stopping this run.',
|
|
6078
|
-
};
|
|
6079
5998
|
}
|
|
6080
5999
|
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
// a timer). In an autonomous drive there is no operator to answer, so the
|
|
6085
|
-
// dispatch region freezes indefinitely — between tool-loop turns, invisible to
|
|
6086
|
-
// the stream idle-watchdog (armed only inside the stream loop) and to the
|
|
6087
|
-
// goal-loop (which awaits the hung iteration). This was the 16-minute freeze in
|
|
6088
|
-
// RESEARCH-autonomous-runaway-discipline-2026-07-19 (R2). Bound the wait: after
|
|
6089
|
-
// this budget, fall back to the same auto-undo notice used when no session /
|
|
6090
|
-
// no cap-able peer is present. Generous enough that a present operator answers
|
|
6091
|
-
// first; short enough that an absent one doesn't stall the run for long.
|
|
6092
|
-
const DOOM_ASK_TIMEOUT_MS = 5 * 60_000;
|
|
6093
|
-
|
|
6094
|
-
export async function routeDoomLoopToAskUser(opts: DoomLoopRouteOpts): Promise<DoomLoopRouteOutcome> {
|
|
6095
|
-
const interventionClass = normalizeDoomLoopInterventionClass(opts.interventionClass);
|
|
6096
|
-
if (!opts.sessionId) {
|
|
6097
|
-
return {
|
|
6098
|
-
kind: 'fallback',
|
|
6099
|
-
interventionClass,
|
|
6100
|
-
hardStopText: buildAutoUndoAskUserNotice(opts.reason, opts.detail),
|
|
6101
|
-
};
|
|
6102
|
-
}
|
|
6103
|
-
try {
|
|
6104
|
-
const dispatchCtx: { sessionId: string; signal?: AbortSignal } = { sessionId: opts.sessionId };
|
|
6105
|
-
if (opts.signal) dispatchCtx.signal = opts.signal;
|
|
6106
|
-
const timeoutMs = opts.timeoutMs ?? DOOM_ASK_TIMEOUT_MS;
|
|
6107
|
-
const TIMED_OUT = Symbol('doom-ask-timeout');
|
|
6108
|
-
let askTimer: ReturnType<typeof setTimeout> | undefined;
|
|
6109
|
-
const raced = await Promise.race([
|
|
6110
|
-
dispatchAskUserQuestion(buildDoomLoopAskRequest(opts), dispatchCtx),
|
|
6111
|
-
new Promise<typeof TIMED_OUT>((res) => {
|
|
6112
|
-
askTimer = setTimeout(() => res(TIMED_OUT), timeoutMs);
|
|
6113
|
-
}),
|
|
6114
|
-
]);
|
|
6115
|
-
if (askTimer) clearTimeout(askTimer);
|
|
6116
|
-
if (raced === TIMED_OUT) {
|
|
6117
|
-
debug.log('llm.tool-loop.retry', 'doom-loop-ask-timeout', {
|
|
6118
|
-
reason: opts.reason,
|
|
6119
|
-
interventionClass,
|
|
6120
|
-
timeoutMs,
|
|
6121
|
-
}, { level: 'warn' });
|
|
6122
|
-
return {
|
|
6123
|
-
kind: 'fallback',
|
|
6124
|
-
interventionClass,
|
|
6125
|
-
hardStopText: buildAutoUndoAskUserNotice(opts.reason, opts.detail),
|
|
6126
|
-
};
|
|
6127
|
-
}
|
|
6128
|
-
const r = raced;
|
|
6129
|
-
if (!r.result) {
|
|
6130
|
-
debug.log('llm.tool-loop.retry', 'doom-loop-ask-route-failed', {
|
|
6131
|
-
error: r.output,
|
|
6132
|
-
reason: opts.reason,
|
|
6133
|
-
interventionClass,
|
|
6134
|
-
}, { level: 'error' });
|
|
6135
|
-
return {
|
|
6136
|
-
kind: 'fallback',
|
|
6137
|
-
interventionClass,
|
|
6138
|
-
hardStopText: buildAutoUndoAskUserNotice(opts.reason, opts.detail),
|
|
6139
|
-
};
|
|
6140
|
-
}
|
|
6141
|
-
return interpretDoomLoopAskResult(r.result, interventionClass);
|
|
6142
|
-
} catch (err) {
|
|
6143
|
-
debug.log('llm.tool-loop.retry', 'doom-loop-ask-route-failed', {
|
|
6144
|
-
error: err instanceof Error ? err.message : String(err),
|
|
6145
|
-
reason: opts.reason,
|
|
6146
|
-
interventionClass,
|
|
6147
|
-
}, { level: 'error' });
|
|
6000
|
+
export function decideDoomLoopSelfHeal(input: DoomLoopSelfHealInput): DoomLoopRouteOutcome {
|
|
6001
|
+
const interventionClass = normalizeDoomLoopInterventionClass(input.interventionClass);
|
|
6002
|
+
if (input.attempt <= 1) {
|
|
6148
6003
|
return {
|
|
6149
|
-
kind: '
|
|
6004
|
+
kind: 'guidance',
|
|
6150
6005
|
interventionClass,
|
|
6151
|
-
|
|
6006
|
+
userMessage:
|
|
6007
|
+
`The same ${input.tool ?? 'tool'} call failed 3 times in a row (last failure: ${input.fingerprint}). `
|
|
6008
|
+
+ 'Do not call it again with the same arguments. Change approach — a different tool or different '
|
|
6009
|
+
+ 'arguments, or first verify the precondition that keeps failing — or state plainly what is blocked.',
|
|
6152
6010
|
};
|
|
6153
6011
|
}
|
|
6012
|
+
return { kind: 'stop', interventionClass, hardStopText: DOOM_LOOP_SELF_HEAL_STOP_TEXT };
|
|
6154
6013
|
}
|
|
6155
6014
|
|
|
6156
6015
|
interface AutoNarrowedInspection {
|
|
@@ -8164,9 +8023,10 @@ export function billingRouteForProvider(provider: string | undefined, model: str
|
|
|
8164
8023
|
// local 은 호출이 «일어났다면» 언제나 local 이다 — 주소가 env 가 아니라 config rotation 에 있어도(자격 판정은 env 만 본다).
|
|
8165
8024
|
if (name === 'local') return 'local';
|
|
8166
8025
|
try {
|
|
8026
|
+
// A recorded rotation decision is the authoritative route for that Codex call;
|
|
8027
|
+
// auth-kind inference alone can otherwise relabel credit spend as subscription/API.
|
|
8028
|
+
if (name === 'openai-codex' && codexCreditSpendReason()) return 'credit';
|
|
8167
8029
|
const auth = authKindForProvider(name, model);
|
|
8168
|
-
// CREDIT-BILLING-LABEL(10-14) — codex 회전이 크레딧 소진을 정한 호출은 구독이 아니라 크레딧이다.
|
|
8169
|
-
if (auth === 'oauth' && name === 'openai-codex' && codexCreditSpendReason()) return 'credit';
|
|
8170
8030
|
return auth === 'oauth' ? 'subscription' : auth === 'local' ? 'local' : auth === 'apikey' ? 'api' : 'unknown';
|
|
8171
8031
|
} catch {
|
|
8172
8032
|
return 'unknown';
|
|
@@ -8196,6 +8056,41 @@ export function logAgentTurnUsage(model: string, usage: LLMUsage, providerNameOr
|
|
|
8196
8056
|
} catch { /* usage observation must not change the turn */ }
|
|
8197
8057
|
}
|
|
8198
8058
|
|
|
8059
|
+
/** 검증 보충 — final-answer supplement attachment point.
|
|
8060
|
+
* Request passed to `StreamWithToolsHandlers.onAppendFinalAnswerSupplement`
|
|
8061
|
+
* after the final answer is settled: `answer` is the visible answer text and
|
|
8062
|
+
* `finalText` is the current final answer output (including any
|
|
8063
|
+
* finalization-snapshot enrichment from the onFinalAnswerSupplement
|
|
8064
|
+
* foundation). */
|
|
8065
|
+
export interface FinalAnswerSupplementAttachmentRequest {
|
|
8066
|
+
answer: string;
|
|
8067
|
+
finalText: string;
|
|
8068
|
+
}
|
|
8069
|
+
|
|
8070
|
+
/** Callback type for the attachment point. Return a non-empty string to have
|
|
8071
|
+
* it appended after the final answer; undefined/null/empty keeps the final
|
|
8072
|
+
* answer output unchanged. */
|
|
8073
|
+
export type FinalAnswerSupplementAttachment = (
|
|
8074
|
+
req: FinalAnswerSupplementAttachmentRequest,
|
|
8075
|
+
) => string | null | undefined;
|
|
8076
|
+
|
|
8077
|
+
/** Final-answer supplement attachment point: call the optional
|
|
8078
|
+
* `onAppendFinalAnswerSupplement` handler and return the final answer text
|
|
8079
|
+
* with its supplement appended (or the original text when the handler is
|
|
8080
|
+
* absent or yields nothing). No numeric thresholds are applied. */
|
|
8081
|
+
export function attachFinalAnswerSupplement(
|
|
8082
|
+
handlers: { onAppendFinalAnswerSupplement?: FinalAnswerSupplementAttachment },
|
|
8083
|
+
req: FinalAnswerSupplementAttachmentRequest,
|
|
8084
|
+
): string {
|
|
8085
|
+
const provider = handlers.onAppendFinalAnswerSupplement;
|
|
8086
|
+
if (!provider) return req.finalText;
|
|
8087
|
+
const supplement = provider(req);
|
|
8088
|
+
if (typeof supplement !== 'string' || supplement.trim().length === 0) return req.finalText;
|
|
8089
|
+
const base = req.finalText;
|
|
8090
|
+
if (base.length === 0 || base.endsWith('\n')) return base + supplement.trim();
|
|
8091
|
+
return base + '\n\n' + supplement.trim();
|
|
8092
|
+
}
|
|
8093
|
+
|
|
8199
8094
|
export async function streamLLMWithTools(
|
|
8200
8095
|
messages: LLMMessage[],
|
|
8201
8096
|
handlers: StreamWithToolsHandlers,
|
|
@@ -8519,6 +8414,8 @@ export async function streamLLMWithTools(
|
|
|
8519
8414
|
// broken dispatch forever. Window=3 mirrors opencode's
|
|
8520
8415
|
// session/processor.ts DOOM_LOOP_THRESHOLD.
|
|
8521
8416
|
const toolErrorDoomTracker = new DoomLoopTracker(3);
|
|
8417
|
+
// Doom-loop self-heal attempts in this turn (guidance first, then stop).
|
|
8418
|
+
let doomSelfHealAttempts = 0;
|
|
8522
8419
|
// A (RESEARCH-autonomous-runaway-discipline-2026-07-19 R1) — the error tracker
|
|
8523
8420
|
// above only fires on *failing* calls; identical *successful* calls (the model
|
|
8524
8421
|
// re-reading / re-grepping the same file over and over — the grep-thrash that
|
|
@@ -9022,13 +8919,17 @@ export async function streamLLMWithTools(
|
|
|
9022
8919
|
verificationLoopState,
|
|
9023
8920
|
finalization,
|
|
9024
8921
|
);
|
|
9025
|
-
|
|
8922
|
+
// 검증 보충 — final-answer supplement attachment point: let the caller
|
|
8923
|
+
// append verification-recency / loop-closure wording after the final
|
|
8924
|
+
// answer (on top of the onFinalAnswerSupplement foundation from PR #27520).
|
|
8925
|
+
const attachedText = attachFinalAnswerSupplement(handlers, { answer: visibleText.trim(), finalText });
|
|
8926
|
+
if (attachedText !== visibleText) {
|
|
9026
8927
|
const answer = visibleText.trim();
|
|
9027
|
-
handlers.onFinalAnswerSupplement?.({ answer, supplement:
|
|
8928
|
+
handlers.onFinalAnswerSupplement?.({ answer, supplement: attachedText.slice(answer.length).trim(), finalText: attachedText });
|
|
9028
8929
|
}
|
|
9029
|
-
if (
|
|
9030
|
-
emitTurnComplete(
|
|
9031
|
-
return
|
|
8930
|
+
if (attachedText !== fullText) handlers.onText('', attachedText);
|
|
8931
|
+
emitTurnComplete(attachedText);
|
|
8932
|
+
return attachedText;
|
|
9032
8933
|
}
|
|
9033
8934
|
|
|
9034
8935
|
// No tool calls + empty text = model stalled. Re-prompt up to
|
|
@@ -9037,12 +8938,15 @@ export async function streamLLMWithTools(
|
|
|
9037
8938
|
if (pendingCalls.length === 0 && turnText.length === 0) {
|
|
9038
8939
|
// TUI-GOAL-LOOP-FRICTION(#63) — 종결 신호(update_goal)를 이미 받았고 답도 이미 썼다 = 할 말이 없는 게 맞다.
|
|
9039
8940
|
// 빈 턴을 «정체»로 보고 재촉·`[NO FINAL SYNTHESIS]` 를 붙이지 않고 그 답으로 끝낸다.
|
|
9040
|
-
|
|
9041
|
-
|
|
8941
|
+
// TUI-H1-GOALLOOP-EMPTY-TAIL — 답이 이 스트림 «앞»에 이미 보였어도 같다(opts.terminalPriorAnswerVisible —
|
|
8942
|
+
// 예: Bash → «끝» → 재주입 → update_goal 만 → 빈 턴). 이 스트림에 답이 없으면 빈 문자열로 끝내고 화면은
|
|
8943
|
+
// 건드리지 않는다(onText 생략 · emitTurnComplete 는 빈 답을 이력에 안 싣는다). 마감은 호출자(goal loop) 몫.
|
|
8944
|
+
if (terminalSignalDispatched && (lastToolRoundText.trim().length > 0 || opts.terminalPriorAnswerVisible === true)) {
|
|
8945
|
+
const answer = lastToolRoundText.trim().length > 0 ? lastToolRoundText : '';
|
|
9042
8946
|
debug.log('goal.loop', 'empty-turn-after-terminal-signal', {
|
|
9043
8947
|
turn, emptyRetries, answerChars: answer.length,
|
|
9044
8948
|
}, { level: 'info' });
|
|
9045
|
-
if (answer !== fullText) handlers.onText('', answer);
|
|
8949
|
+
if (answer.length > 0 && answer !== fullText) handlers.onText('', answer);
|
|
9046
8950
|
emitTurnComplete(answer);
|
|
9047
8951
|
return answer;
|
|
9048
8952
|
}
|
|
@@ -10659,41 +10563,36 @@ export async function streamLLMWithTools(
|
|
|
10659
10563
|
debug.log('llm.tool-loop.retry', 'doom-loop-detected', {
|
|
10660
10564
|
turn, tool: doomToolName, window: doomWindow,
|
|
10661
10565
|
}, { level: 'warn' });
|
|
10662
|
-
const
|
|
10566
|
+
const applySelfHeal = (reason: DoomLoopReason, detail: string | null) => {
|
|
10663
10567
|
const interventionClass = classifyDoomLoopIntervention({
|
|
10664
10568
|
detail: [detail, doomFailureDetail].filter(Boolean).join('\n'), doomWindow,
|
|
10665
10569
|
});
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
}
|
|
10570
|
+
doomSelfHealAttempts += 1;
|
|
10571
|
+
const fingerprint = doomWindow[doomWindow.length - 1] ?? 'unknown';
|
|
10572
|
+
const outcome = decideDoomLoopSelfHeal({
|
|
10573
|
+
attempt: doomSelfHealAttempts, tool: doomToolName, fingerprint, interventionClass,
|
|
10574
|
+
});
|
|
10575
|
+
debug.log('llm.tool-loop.retry', 'doom-loop-self-heal', {
|
|
10576
|
+
turn,
|
|
10577
|
+
tool: doomToolName,
|
|
10578
|
+
attempt: doomSelfHealAttempts,
|
|
10579
|
+
action: outcome.kind,
|
|
10580
|
+
fingerprint,
|
|
10581
|
+
reason,
|
|
10582
|
+
interventionClass: outcome.interventionClass,
|
|
10583
|
+
}, { level: 'warn' });
|
|
10584
|
+
if (outcome.kind === 'guidance') {
|
|
10680
10585
|
toolErrorDoomTracker.reset();
|
|
10681
10586
|
postToolResultUserNote = outcome.userMessage;
|
|
10682
|
-
debug.log('llm.tool-loop.retry', 'doom-loop-ask-guidance', {
|
|
10683
|
-
turn, reason, interventionClass: outcome.interventionClass,
|
|
10684
|
-
});
|
|
10685
10587
|
} else {
|
|
10686
10588
|
hardStopText = outcome.hardStopText;
|
|
10687
|
-
debug.log('llm.tool-loop.retry', outcome.kind === 'fallback' ? 'doom-loop-ask-fallback' : 'doom-loop-ask-stop', {
|
|
10688
|
-
turn, reason, interventionClass: outcome.interventionClass, window: doomWindow,
|
|
10689
|
-
});
|
|
10690
10589
|
}
|
|
10691
10590
|
};
|
|
10692
10591
|
if (retryDecision.action === 'auto-undo') {
|
|
10693
10592
|
if (isPlanModeActive()) {
|
|
10694
|
-
|
|
10593
|
+
applySelfHeal('plan-mode', retryDecision.reason);
|
|
10695
10594
|
} else if (listSnapshots().length === 0) {
|
|
10696
|
-
|
|
10595
|
+
applySelfHeal('no-undo-available', 'UndoTurn: no snapshots available');
|
|
10697
10596
|
} else {
|
|
10698
10597
|
try {
|
|
10699
10598
|
const undoResult = await undoTurnRuntime.run({}, { surface: 'tui' });
|
|
@@ -10707,11 +10606,11 @@ export async function streamLLMWithTools(
|
|
|
10707
10606
|
window: doomWindow,
|
|
10708
10607
|
});
|
|
10709
10608
|
} catch (err) {
|
|
10710
|
-
|
|
10609
|
+
applySelfHeal('undo-failed', err instanceof Error ? err.message : String(err));
|
|
10711
10610
|
}
|
|
10712
10611
|
}
|
|
10713
10612
|
} else {
|
|
10714
|
-
|
|
10613
|
+
applySelfHeal('policy', retryDecision.reason);
|
|
10715
10614
|
}
|
|
10716
10615
|
}
|
|
10717
10616
|
if (executionDoomDetectedThisTurn && !hardStopText) {
|
|
@@ -10911,8 +10810,8 @@ export async function streamLLMWithTools(
|
|
|
10911
10810
|
if (postToolResultUserNote) {
|
|
10912
10811
|
// Appended after the tool_result user turn so the wire order is
|
|
10913
10812
|
// assistant(tool_use) → user(tool_result) → user(intervention).
|
|
10914
|
-
//
|
|
10915
|
-
//
|
|
10813
|
+
// Also carries the doom-loop self-heal guidance — see
|
|
10814
|
+
// decideDoomLoopSelfHeal above.
|
|
10916
10815
|
history.push({ role: LOOP_SELF_NOTE_ROLE, content: postToolResultUserNote });
|
|
10917
10816
|
}
|
|
10918
10817
|
// 신호뿐인 턴 · 섞인 턴(정상 디스패치 · 부분 거부 · 통째 거부의 수신 확인) 모두 — 종결 신호가 이 턴에 «받아들여졌다».
|
|
@@ -3,7 +3,9 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renam
|
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { CardStore, taskCardsDir, type TaskCard } from '../../task-cards/card-store.js';
|
|
5
5
|
import { Database } from 'bun:sqlite';
|
|
6
|
-
import { type ChecklistItem } from '../../release-loop/checklist.js';
|
|
6
|
+
import { checklistDevVersion, listChecklist, type ChecklistItem } from '../../release-loop/checklist.js';
|
|
7
|
+
import { readTrafficSignal } from '../../seat-loop/traffic-signal.js';
|
|
8
|
+
import { runHoldHygiene, type HygieneCell, type HygieneDeps } from '../../steward/hold-hygiene.js';
|
|
7
9
|
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
8
10
|
import { getUserConfig, ORCHESTRATOR_DEFAULTS, resolveConfiguredAgentRoleLlm, type OrchestratorLoopConfig } from '../../user-config.js';
|
|
9
11
|
import { rubricPriority } from '../../release-loop/rubric.js';
|
|
@@ -63,6 +65,8 @@ export type RebalanceAdapter = (version: string) => Promise<{ decisions: unknown
|
|
|
63
65
|
export interface TickDeps {
|
|
64
66
|
root?: string; now?: Date | (() => Date); mode?: Mode; runId?: string; window?: Window;
|
|
65
67
|
heartbeatIntervalMs?: number;
|
|
68
|
+
hygieneCells?: () => HygieneCell[];
|
|
69
|
+
hygienePaths?: Pick<HygieneDeps, 'trafficPath' | 'metaPath' | 'dispatcherDir'>;
|
|
66
70
|
cards?: () => TaskCard[]; split?: SplitAdapter; placeCell?: PlaceAdapter;
|
|
67
71
|
degradation?: () => { llm: boolean; board: boolean };
|
|
68
72
|
gridHosts?: readonly GridHost[];
|
|
@@ -888,7 +892,25 @@ export async function runOrchestratorNode(node: Node, deps: TickDeps = {}): Prom
|
|
|
888
892
|
}
|
|
889
893
|
}
|
|
890
894
|
}
|
|
891
|
-
} else if (node === 'report')
|
|
895
|
+
} else if (node === 'report') {
|
|
896
|
+
if (mode === 'live') {
|
|
897
|
+
try {
|
|
898
|
+
const signal = readTrafficSignal(deps.hygienePaths?.trafficPath);
|
|
899
|
+
const versions = signal && !('unreadable' in signal) && signal.versions?.length ? signal.versions : [checklistDevVersion()];
|
|
900
|
+
let cells: HygieneCell[] = [];
|
|
901
|
+
if (deps.hygieneCells) cells = deps.hygieneCells();
|
|
902
|
+
else if (signal && !('unreadable' in signal) && signal.hold?.length) {
|
|
903
|
+
for (const version of versions) {
|
|
904
|
+
try { cells.push(...listChecklist(version).items); }
|
|
905
|
+
catch (error) { observe('hold-hygiene-checklist-unreadable', `${version}:${String(error).slice(0, 100)}`, 0); }
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
const sent = runHoldHygiene({ root, now, cells, ...deps.hygienePaths });
|
|
909
|
+
for (const notice of sent) observe('exchange', `hold-hygiene:${notice.key}`, 1, SEAT_LOOP_IDS[notice.to]);
|
|
910
|
+
} catch (error) { observe('hold-hygiene-failed', String(error).slice(0, 160), 0); }
|
|
911
|
+
}
|
|
912
|
+
(deps.print ?? console.log)(reportLine(state));
|
|
913
|
+
}
|
|
892
914
|
state.nodes[node] = 'ok';
|
|
893
915
|
} else if (state.nodes[node] !== 'ok') {
|
|
894
916
|
// A rerun of a node that already completed keeps its result (review round 3): only never-run nodes become skipped.
|
|
@@ -82,7 +82,7 @@ export const FEATURE_MATURITY = {
|
|
|
82
82
|
onboarding: 'beta', login: 'stable', session: 'beta', chat: 'stable', repl: 'stable',
|
|
83
83
|
agent: 'stable', registry: 'beta', tier: 'stable', local: 'beta', scheduler: 'beta',
|
|
84
84
|
attach: 'beta', wf: 'beta', nexus: 'stable', phone: 'beta', token: 'beta',
|
|
85
|
-
acp: 'beta', voice: 'beta',
|
|
85
|
+
acp: 'beta', voice: 'beta', sim: 'beta',
|
|
86
86
|
'run-detached': 'system', dogfood: 'system', run: 'system',
|
|
87
87
|
},
|
|
88
88
|
// Canonical names only; aliases inherit their command's grade at lookup.
|