muonroi-cli 1.7.2 → 1.8.1
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/dist/packages/agent-harness-core/src/event-filter.js +1 -0
- package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
- package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
- package/dist/packages/agent-harness-core/src/selector.js +2 -2
- package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
- package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
- package/dist/packages/agent-harness-opentui/src/install.js +1 -1
- package/dist/src/__test-stubs__/vitest-setup.js +4 -0
- package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
- package/dist/src/agent-harness/mock-model.d.ts +1 -1
- package/dist/src/agent-harness/mock-model.js +143 -6
- package/dist/src/agent-harness/test-spawn.js +3 -1
- package/dist/src/chat/chat-keychain.js +9 -1
- package/dist/src/cli/cost-forensics.js +7 -4
- package/dist/src/cli/keys.d.ts +2 -2
- package/dist/src/cli/keys.js +107 -28
- package/dist/src/cli/usage-report.js +2 -2
- package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
- package/dist/src/council/__tests__/abort-threading.test.js +193 -0
- package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
- package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
- package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
- package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
- package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
- package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
- package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
- package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
- package/dist/src/council/__tests__/research-tools.test.js +4 -0
- package/dist/src/council/__tests__/round-tools.test.js +6 -0
- package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
- package/dist/src/council/clarifier-question-cap.test.js +73 -0
- package/dist/src/council/clarifier.d.ts +29 -1
- package/dist/src/council/clarifier.js +64 -1
- package/dist/src/council/debate-planner.d.ts +2 -1
- package/dist/src/council/debate-planner.js +5 -3
- package/dist/src/council/debate.d.ts +9 -0
- package/dist/src/council/debate.js +88 -8
- package/dist/src/council/index.d.ts +46 -1
- package/dist/src/council/index.js +125 -19
- package/dist/src/council/llm.js +41 -13
- package/dist/src/council/prompts.js +36 -1
- package/dist/src/council/types.d.ts +14 -1
- package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
- package/dist/src/ee/bridge.d.ts +3 -1
- package/dist/src/ee/bridge.js +9 -3
- package/dist/src/ee/bridge.test.js +1 -2
- package/dist/src/ee/client.js +15 -2
- package/dist/src/ee/recall-ledger.d.ts +20 -0
- package/dist/src/ee/recall-ledger.js +40 -1
- package/dist/src/ee/search.d.ts +25 -0
- package/dist/src/ee/search.js +73 -0
- package/dist/src/ee/types.d.ts +15 -1
- package/dist/src/ee/who-am-i.d.ts +41 -0
- package/dist/src/ee/who-am-i.js +143 -0
- package/dist/src/ee/who-am-i.test.d.ts +1 -0
- package/dist/src/ee/who-am-i.test.js +101 -0
- package/dist/src/flow/compaction/index.d.ts +1 -0
- package/dist/src/flow/compaction/index.js +4 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/headless/council-answers.js +1 -0
- package/dist/src/headless/output.d.ts +30 -1
- package/dist/src/headless/output.js +180 -0
- package/dist/src/headless/output.test.js +61 -1
- package/dist/src/hooks/index.js +48 -2
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +59 -62
- package/dist/src/lsp/client-normalize.test.d.ts +12 -0
- package/dist/src/lsp/client-normalize.test.js +34 -0
- package/dist/src/lsp/client.d.ts +1 -0
- package/dist/src/lsp/client.js +10 -2
- package/dist/src/lsp/manager.d.ts +1 -1
- package/dist/src/lsp/manager.js +13 -5
- package/dist/src/lsp/manager.test.js +41 -2
- package/dist/src/lsp/runtime.d.ts +1 -1
- package/dist/src/lsp/runtime.js +2 -2
- package/dist/src/maintain/__tests__/gh-create-pr.test.js +7 -3
- package/dist/src/maintain/__tests__/pr-builder.test.js +7 -3
- package/dist/src/mcp/__tests__/client-pool.spec.js +3 -3
- package/dist/src/mcp/__tests__/ee-tools.test.js +42 -14
- package/dist/src/mcp/__tests__/forensics-tools.test.js +3 -3
- package/dist/src/mcp/__tests__/lsp-tools.test.js +4 -4
- package/dist/src/mcp/auto-setup.js +0 -8
- package/dist/src/mcp/client-pool.d.ts +1 -1
- package/dist/src/mcp/client-pool.js +25 -9
- package/dist/src/mcp/ee-tools.d.ts +10 -0
- package/dist/src/mcp/ee-tools.js +34 -0
- package/dist/src/mcp/self-verify-jobs.d.ts +1 -0
- package/dist/src/mcp/self-verify-jobs.js +1 -3
- package/dist/src/mcp/self-verify-runner.js +1 -1
- package/dist/src/models/catalog-gemini.test.js +12 -10
- package/dist/src/models/catalog.json +106 -38
- package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
- package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +3 -0
- package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
- package/dist/src/orchestrator/__tests__/message-processor.test.js +2 -0
- package/dist/src/orchestrator/__tests__/prompts.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/prompts.test.js +88 -0
- package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
- package/dist/src/orchestrator/__tests__/sub-session-delegation.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/sub-session-delegation.test.js +507 -0
- package/dist/src/orchestrator/agent.test.js +5 -2
- package/dist/src/orchestrator/auto-commit.d.ts +110 -0
- package/dist/src/orchestrator/auto-commit.js +366 -0
- package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
- package/dist/src/orchestrator/batch-turn-runner.js +2 -1
- package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
- package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
- package/dist/src/orchestrator/compaction.d.ts +22 -0
- package/dist/src/orchestrator/compaction.js +42 -0
- package/dist/src/orchestrator/delegations.d.ts +1 -0
- package/dist/src/orchestrator/delegations.js +41 -0
- package/dist/src/orchestrator/error-utils.js +27 -2
- package/dist/src/orchestrator/message-processor.d.ts +23 -5
- package/dist/src/orchestrator/message-processor.js +226 -2573
- package/dist/src/orchestrator/orchestrator.d.ts +11 -2
- package/dist/src/orchestrator/orchestrator.js +530 -43
- package/dist/src/orchestrator/pending-calls.js +2 -1
- package/dist/src/orchestrator/preprocessor.d.ts +13 -0
- package/dist/src/orchestrator/preprocessor.js +146 -0
- package/dist/src/orchestrator/prompts.d.ts +16 -0
- package/dist/src/orchestrator/prompts.js +130 -67
- package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
- package/dist/src/orchestrator/safety-askcard.js +74 -0
- package/dist/src/orchestrator/sandbox.test.js +8 -5
- package/dist/src/orchestrator/scope-ceiling.js +1 -1
- package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
- package/dist/src/orchestrator/stall-watchdog.js +23 -0
- package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
- package/dist/src/orchestrator/stream-runner.js +52 -30
- package/dist/src/orchestrator/sub-agent-cap.d.ts +2 -0
- package/dist/src/orchestrator/sub-agent-cap.js +12 -4
- package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
- package/dist/src/orchestrator/subagent-compactor.d.ts +21 -0
- package/dist/src/orchestrator/subagent-compactor.js +148 -24
- package/dist/src/orchestrator/subagent-compactor.spec.js +249 -1
- package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
- package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
- package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
- package/dist/src/orchestrator/tool-args-repair.js +1 -1
- package/dist/src/orchestrator/tool-engine.d.ts +153 -0
- package/dist/src/orchestrator/tool-engine.js +3138 -0
- package/dist/src/orchestrator/turn-runner-deps.d.ts +3 -0
- package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
- package/dist/src/pil/__tests__/discovery.test.js +89 -145
- package/dist/src/pil/__tests__/dual-run.test.js +2 -0
- package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
- package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
- package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
- package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
- package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
- package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
- package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
- package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
- package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
- package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
- package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
- package/dist/src/pil/__tests__/llm-classify.test.js +79 -1
- package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
- package/dist/src/pil/__tests__/pipeline.test.js +11 -8
- package/dist/src/pil/__tests__/response-tools.test.js +52 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +5 -3
- package/dist/src/pil/agent-operating-contract.test.js +2 -2
- package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
- package/dist/src/pil/cheap-model-playbook.js +1 -1
- package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
- package/dist/src/pil/cheap-model-workbooks.js +1 -1
- package/dist/src/pil/config.d.ts +20 -0
- package/dist/src/pil/config.js +35 -0
- package/dist/src/pil/discovery-types.d.ts +32 -9
- package/dist/src/pil/discovery.d.ts +3 -3
- package/dist/src/pil/discovery.js +138 -244
- package/dist/src/pil/index.d.ts +1 -1
- package/dist/src/pil/index.js +1 -1
- package/dist/src/pil/layer1-intent.d.ts +9 -1
- package/dist/src/pil/layer1-intent.js +62 -7
- package/dist/src/pil/layer1-intent.test.js +112 -2
- package/dist/src/pil/layer15-context-scan.js +1 -1
- package/dist/src/pil/layer16-clarity.d.ts +21 -25
- package/dist/src/pil/layer16-clarity.js +52 -88
- package/dist/src/pil/layer18-acceptance.d.ts +13 -3
- package/dist/src/pil/layer18-acceptance.js +1 -75
- package/dist/src/pil/layer2-personality.js +7 -8
- package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
- package/dist/src/pil/layer2_5-ponytail.js +41 -0
- package/dist/src/pil/layer3-ee-injection.js +180 -14
- package/dist/src/pil/layer4-gsd.js +22 -8
- package/dist/src/pil/layer5-context.d.ts +1 -0
- package/dist/src/pil/layer5-context.js +26 -11
- package/dist/src/pil/layer6-output.js +23 -5
- package/dist/src/pil/llm-classify.d.ts +11 -0
- package/dist/src/pil/llm-classify.js +196 -3
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +4 -3
- package/dist/src/pil/pipeline.js +15 -1
- package/dist/src/pil/response-tools.d.ts +16 -0
- package/dist/src/pil/response-tools.js +41 -0
- package/dist/src/pil/schema.d.ts +8 -0
- package/dist/src/pil/schema.js +24 -3
- package/dist/src/pil/task-tier-map.js +7 -7
- package/dist/src/pil/types.d.ts +9 -3
- package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
- package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
- package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
- package/dist/src/product-loop/discovery-interview.d.ts +20 -0
- package/dist/src/product-loop/discovery-interview.js +121 -7
- package/dist/src/product-loop/gather.js +19 -5
- package/dist/src/product-loop/loop-driver.js +2 -2
- package/dist/src/product-loop/sprint-self-verify.js +2 -2
- package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
- package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
- package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
- package/dist/src/providers/auth/gcloud.d.ts +28 -0
- package/dist/src/providers/auth/gcloud.js +102 -0
- package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
- package/dist/src/providers/auth/gemini-oauth.js +292 -105
- package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
- package/dist/src/providers/auth/grok-oauth.js +1 -1
- package/dist/src/providers/auth/registry.js +28 -6
- package/dist/src/providers/gemini.d.ts +1 -1
- package/dist/src/providers/gemini.js +10 -3
- package/dist/src/providers/keychain.d.ts +2 -2
- package/dist/src/providers/keychain.js +20 -2
- package/dist/src/providers/openai.js +5 -1
- package/dist/src/providers/runtime.d.ts +1 -1
- package/dist/src/providers/runtime.js +11 -2
- package/dist/src/providers/siliconflow-sse-repair.js +1 -1
- package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
- package/dist/src/providers/strategies/google.strategy.js +159 -9
- package/dist/src/providers/strategies/thinking-mode.js +19 -6
- package/dist/src/providers/types.d.ts +1 -1
- package/dist/src/router/decide.test.js +40 -8
- package/dist/src/router/step-router.d.ts +20 -0
- package/dist/src/router/step-router.js +73 -0
- package/dist/src/self-qa/agentic-loop.js +30 -21
- package/dist/src/self-qa/orchestrator.js +5 -5
- package/dist/src/self-qa/scenario-planner.js +1 -1
- package/dist/src/self-qa/spec-emitter.js +4 -4
- package/dist/src/storage/__tests__/hierarchical-session.test.d.ts +1 -0
- package/dist/src/storage/__tests__/hierarchical-session.test.js +161 -0
- package/dist/src/storage/__tests__/sweep-stale-pending.test.js +2 -2
- package/dist/src/storage/index.d.ts +1 -1
- package/dist/src/storage/index.js +1 -1
- package/dist/src/storage/migrations.js +13 -1
- package/dist/src/storage/transcript-response-entry.test.js +18 -5
- package/dist/src/storage/transcript-view.js +7 -1
- package/dist/src/storage/transcript.d.ts +3 -0
- package/dist/src/storage/transcript.js +254 -73
- package/dist/src/storage/ui-interaction-log.d.ts +17 -0
- package/dist/src/storage/usage.js +22 -5
- package/dist/src/tools/__tests__/native-tools.test.js +6 -0
- package/dist/src/tools/bash.d.ts +36 -23
- package/dist/src/tools/bash.js +59 -301
- package/dist/src/tools/bash.test.js +2 -257
- package/dist/src/tools/file-tracker.d.ts +5 -0
- package/dist/src/tools/file-tracker.js +14 -0
- package/dist/src/tools/file.js +2 -0
- package/dist/src/tools/git-safety.d.ts +38 -1
- package/dist/src/tools/git-safety.js +94 -4
- package/dist/src/tools/git-safety.test.js +45 -1
- package/dist/src/tools/native-tools.d.ts +1 -1
- package/dist/src/tools/native-tools.js +66 -1
- package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
- package/dist/src/tools/registry-git-safety.test.js +17 -6
- package/dist/src/tools/registry.d.ts +6 -0
- package/dist/src/tools/registry.js +215 -13
- package/dist/src/types/index.d.ts +8 -2
- package/dist/src/ui/app.d.ts +1 -0
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/components/council-question-card.js +1 -0
- package/dist/src/ui/components/message-view.js +37 -3
- package/dist/src/ui/components/structured-response-view.js +7 -0
- package/dist/src/ui/components/task-list-panel.js +3 -12
- package/dist/src/ui/containers/chat-feed.d.ts +40 -0
- package/dist/src/ui/containers/chat-feed.js +66 -0
- package/dist/src/ui/containers/modals-layer.d.ts +87 -0
- package/dist/src/ui/containers/modals-layer.js +18 -0
- package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
- package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
- package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
- package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
- package/dist/src/ui/slash/expand.js +14 -1
- package/dist/src/ui/slash/export.js +16 -1
- package/dist/src/ui/slash/menu-items.js +1 -0
- package/dist/src/ui/slash/ponytail.d.ts +2 -0
- package/dist/src/ui/slash/ponytail.js +19 -0
- package/dist/src/ui/status-bar/store.js +0 -1
- package/dist/src/ui/status-bar/store.test.js +2 -2
- package/dist/src/ui/use-app-logic.d.ts +26 -0
- package/dist/src/ui/use-app-logic.js +0 -0
- package/dist/src/ui/utils/relaunch.js +1 -1
- package/dist/src/ui/utils/text.d.ts +11 -0
- package/dist/src/ui/utils/text.js +21 -1
- package/dist/src/ui/utils/text.test.js +25 -1
- package/dist/src/ui/utils/tools.js +3 -1
- package/dist/src/usage/cost-log.js +2 -2
- package/dist/src/usage/estimator.d.ts +14 -0
- package/dist/src/usage/estimator.js +22 -0
- package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
- package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
- package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
- package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
- package/dist/src/utils/__tests__/llm-deadline.test.js +16 -4
- package/dist/src/utils/__tests__/logger.test.d.ts +1 -0
- package/dist/src/utils/__tests__/logger.test.js +115 -0
- package/dist/src/utils/__tests__/settings-agent-first.test.d.ts +1 -0
- package/dist/src/utils/__tests__/settings-agent-first.test.js +48 -0
- package/dist/src/utils/install-manager.d.ts +1 -0
- package/dist/src/utils/install-manager.js +76 -7
- package/dist/src/utils/install-manager.test.js +43 -1
- package/dist/src/utils/llm-deadline.d.ts +9 -1
- package/dist/src/utils/llm-deadline.js +31 -2
- package/dist/src/utils/logger.d.ts +28 -0
- package/dist/src/utils/logger.js +171 -0
- package/dist/src/utils/permission-mode.d.ts +33 -0
- package/dist/src/utils/permission-mode.js +164 -3
- package/dist/src/utils/permission-mode.test.js +92 -1
- package/dist/src/utils/settings.d.ts +36 -32
- package/dist/src/utils/settings.js +151 -123
- package/dist/src/utils/update-checker.test.js +5 -1
- package/package.json +1 -1
|
@@ -26,7 +26,80 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { getModelByTier } from "../models/registry.js";
|
|
28
28
|
import { detectProviderForModel } from "../providers/runtime.js";
|
|
29
|
+
import { readTimeoutEnv } from "../utils/ee-logger.js";
|
|
29
30
|
import { isModelDisabled, isProviderDisabled, loadUserSettings } from "../utils/settings.js";
|
|
31
|
+
// ─── EE-guided SAMR override ─────────────────────────────────────────────────
|
|
32
|
+
// EE_SAMR_TIMEOUT: per-call budget for consulting the EE brain about SAMR.
|
|
33
|
+
// Default 2s, range 500ms..5s. Falls back to heuristics on timeout/error.
|
|
34
|
+
const EE_SAMR_TIMEOUT_MS = readTimeoutEnv("MUONROI_EE_SAMR_TIMEOUT_MS", 2000, 500, 5000);
|
|
35
|
+
/**
|
|
36
|
+
* Consult the EE brain + local heuristics to decide whether SAMR should be
|
|
37
|
+
* enabled for this turn even when the user config has it disabled.
|
|
38
|
+
*
|
|
39
|
+
* Resolution order:
|
|
40
|
+
* 1. Fast heuristic — mechanical/simple tasks skip EE entirely (zero cost).
|
|
41
|
+
* 2. EE brain — asks classifyViaBrain for a yes/no + execution tier.
|
|
42
|
+
* 3. Heuristic fallback — when EE is unreachable, uses taskType + complexity.
|
|
43
|
+
*/
|
|
44
|
+
export async function eeSamrGuidance(params) {
|
|
45
|
+
const { userMessage, taskType, taskConfidence, complexitySize, taskComplexity } = params;
|
|
46
|
+
// Step 1: fast heuristic — mechanical tasks never benefit from SAMR
|
|
47
|
+
const mechanicalTypes = new Set(["general", "documentation", "generate", "build", "chitchat"]);
|
|
48
|
+
if (taskType && mechanicalTypes.has(taskType)) {
|
|
49
|
+
return { overrideConfig: null, reason: `mechanical taskType=${taskType} — no SAMR benefit` };
|
|
50
|
+
}
|
|
51
|
+
// Step 2: fast heuristic — trivial tasks don't need a split
|
|
52
|
+
if (taskComplexity === "low" && (complexitySize === "small" || complexitySize === undefined)) {
|
|
53
|
+
return { overrideConfig: null, reason: "low complexity + small — no SAMR benefit" };
|
|
54
|
+
}
|
|
55
|
+
// Step 3: ask EE brain
|
|
56
|
+
try {
|
|
57
|
+
const { classifyViaBrain } = await import("../ee/bridge.js");
|
|
58
|
+
const eePrompt = [
|
|
59
|
+
`Task: ${userMessage.slice(0, 200)}`,
|
|
60
|
+
`Context: type=${taskType ?? "unknown"} complexity=${taskComplexity ?? "unknown"} size=${complexitySize ?? "unknown"}`,
|
|
61
|
+
`Question: Would splitting the work into (1) premium reasoning then (2) cheap execution save tokens without hurting quality?`,
|
|
62
|
+
`Reply valid JSON: {"samr":true,"executionTier":"balanced"} or {"samr":false}`,
|
|
63
|
+
].join("\n");
|
|
64
|
+
const response = await classifyViaBrain(eePrompt, EE_SAMR_TIMEOUT_MS, {
|
|
65
|
+
systemPrompt: 'You are a SAMR (Step-Aware Model Routing) evaluator. Given a task description, context, and question, reply ONLY with valid JSON: {"samr":true,"executionTier":"balanced"} or {"samr":false}. No extra text, no markdown.',
|
|
66
|
+
responseFormat: { type: "json_object" },
|
|
67
|
+
maxTokens: 100,
|
|
68
|
+
});
|
|
69
|
+
if (!response) {
|
|
70
|
+
// Fallback heuristic when EE unreachable
|
|
71
|
+
return samrHeuristicFallback(taskType, taskComplexity, complexitySize);
|
|
72
|
+
}
|
|
73
|
+
const parsed = JSON.parse(response);
|
|
74
|
+
if (parsed.samr === true) {
|
|
75
|
+
const tier = parsed.executionTier === "fast" ? "fast" : "balanced";
|
|
76
|
+
return {
|
|
77
|
+
overrideConfig: { enabled: true, toolExecutionTier: tier, premiumSynthesis: false },
|
|
78
|
+
reason: `ee-guided: ${tier} execution phase`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return { overrideConfig: null, reason: "ee-declined" };
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return samrHeuristicFallback(taskType, taskComplexity, complexitySize);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Deterministic fallback when EE brain is unreachable.
|
|
89
|
+
* Reasoning-heavy tasks with high complexity are strong SAMR candidates.
|
|
90
|
+
*/
|
|
91
|
+
function samrHeuristicFallback(taskType, taskComplexity, complexitySize) {
|
|
92
|
+
const reasoningTypes = new Set(["plan", "analyze", "refactor", "debug", "architect"]);
|
|
93
|
+
if (taskType && reasoningTypes.has(taskType)) {
|
|
94
|
+
if (taskComplexity === "high" || complexitySize === "large") {
|
|
95
|
+
return {
|
|
96
|
+
overrideConfig: { enabled: true, toolExecutionTier: "balanced", premiumSynthesis: false },
|
|
97
|
+
reason: `heuristic: ${taskType}/${taskComplexity ?? "?"}/${complexitySize ?? "?"} benefits from SAMR`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { overrideConfig: null, reason: "heuristic: no benefit" };
|
|
102
|
+
}
|
|
30
103
|
// ─── Defaults ────────────────────────────────────────────────────────────────
|
|
31
104
|
const DEFAULT_CONFIG = {
|
|
32
105
|
enabled: false,
|
|
@@ -128,8 +128,8 @@ export async function createLLMBrain(opts) {
|
|
|
128
128
|
// flows without reasoning prose leaking through.
|
|
129
129
|
// biome-ignore lint/suspicious/noExplicitAny: global flag is AI SDK's docs convention
|
|
130
130
|
globalThis.AI_SDK_LOG_WARNINGS = false;
|
|
131
|
-
if (process.env
|
|
132
|
-
process.env
|
|
131
|
+
if (process.env.MUONROI_DEEPSEEK_DISABLE_THINKING === undefined) {
|
|
132
|
+
process.env.MUONROI_DEEPSEEK_DISABLE_THINKING = "1";
|
|
133
133
|
}
|
|
134
134
|
// Resolve the catalog before any model lookup — resolveModelRuntime needs it
|
|
135
135
|
// loaded or it throws "not found in catalog". self-verify --agentic is a
|
|
@@ -281,35 +281,35 @@ function extractJsonCandidates(text) {
|
|
|
281
281
|
function tryParseDecisionSlice(slice) {
|
|
282
282
|
try {
|
|
283
283
|
const obj = JSON.parse(slice);
|
|
284
|
-
if (typeof obj
|
|
284
|
+
if (typeof obj.action !== "string")
|
|
285
285
|
return null;
|
|
286
|
-
const action = obj
|
|
287
|
-
const reason = typeof obj
|
|
286
|
+
const action = obj.action;
|
|
287
|
+
const reason = typeof obj.reason === "string" ? obj.reason : "";
|
|
288
288
|
switch (action) {
|
|
289
289
|
case "type":
|
|
290
|
-
if (typeof obj
|
|
291
|
-
return { action, text: obj
|
|
290
|
+
if (typeof obj.text === "string")
|
|
291
|
+
return { action, text: obj.text, reason };
|
|
292
292
|
return null;
|
|
293
293
|
case "press":
|
|
294
|
-
if (typeof obj
|
|
295
|
-
return { action, key: obj
|
|
294
|
+
if (typeof obj.key === "string")
|
|
295
|
+
return { action, key: obj.key, reason };
|
|
296
296
|
return null;
|
|
297
297
|
case "wait_for": {
|
|
298
298
|
const w = {
|
|
299
299
|
action,
|
|
300
300
|
reason,
|
|
301
|
-
timeoutMs: typeof obj
|
|
301
|
+
timeoutMs: typeof obj.timeoutMs === "number" ? obj.timeoutMs : 5_000,
|
|
302
302
|
};
|
|
303
|
-
if (obj
|
|
303
|
+
if (obj.idle === true)
|
|
304
304
|
w.idle = true;
|
|
305
|
-
if (typeof obj
|
|
306
|
-
w.selector = obj
|
|
307
|
-
if (typeof obj
|
|
308
|
-
w.event = obj
|
|
305
|
+
if (typeof obj.selector === "string")
|
|
306
|
+
w.selector = obj.selector;
|
|
307
|
+
if (typeof obj.event === "string")
|
|
308
|
+
w.event = obj.event;
|
|
309
309
|
return w;
|
|
310
310
|
}
|
|
311
311
|
case "done": {
|
|
312
|
-
const v = obj
|
|
312
|
+
const v = obj.verdict;
|
|
313
313
|
if (v === "pass" || v === "fail" || v === "inconclusive") {
|
|
314
314
|
return { action, verdict: v, reason };
|
|
315
315
|
}
|
|
@@ -330,8 +330,14 @@ export async function runAgenticLoop(opts) {
|
|
|
330
330
|
const events = [];
|
|
331
331
|
let lastFrame = null;
|
|
332
332
|
const entry = opts.entry ?? resolve("src/index.ts");
|
|
333
|
-
const
|
|
334
|
-
|
|
333
|
+
const args = [entry, "--agent-mode"];
|
|
334
|
+
if (opts.mockLlmDir !== "none") {
|
|
335
|
+
const mockDir = opts.mockLlmDir ?? resolve("tests/harness/fixtures/llm");
|
|
336
|
+
args.push("--mock-llm", mockDir);
|
|
337
|
+
}
|
|
338
|
+
if (opts.extraArgs) {
|
|
339
|
+
args.push(...opts.extraArgs);
|
|
340
|
+
}
|
|
335
341
|
const env = {
|
|
336
342
|
...process.env,
|
|
337
343
|
MUONROI_TEST_NO_PERSIST: "1",
|
|
@@ -438,6 +444,9 @@ export async function runAgenticLoop(opts) {
|
|
|
438
444
|
}
|
|
439
445
|
try {
|
|
440
446
|
await executeDecision(driver, decision);
|
|
447
|
+
if (decision.action === "type" || decision.action === "press") {
|
|
448
|
+
await new Promise((res) => setTimeout(res, 300));
|
|
449
|
+
}
|
|
441
450
|
}
|
|
442
451
|
catch (err) {
|
|
443
452
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -501,11 +510,11 @@ function wireDriver(inWrite, outRead) {
|
|
|
501
510
|
const splitter = createLineSplitter((line) => {
|
|
502
511
|
try {
|
|
503
512
|
const msg = JSON.parse(line);
|
|
504
|
-
if (msg
|
|
513
|
+
if (msg.mode === "live")
|
|
505
514
|
driver._ingest({ kind: "frame", frame: msg });
|
|
506
|
-
else if (msg
|
|
515
|
+
else if (msg.t === "idle")
|
|
507
516
|
driver._ingest({ kind: "idle" });
|
|
508
|
-
else if (msg
|
|
517
|
+
else if (msg.t === "event")
|
|
509
518
|
driver._ingest({ kind: "event", event: msg });
|
|
510
519
|
}
|
|
511
520
|
catch { }
|
|
@@ -119,19 +119,19 @@ export async function runScenarios(scenarios, opts = {}) {
|
|
|
119
119
|
}
|
|
120
120
|
function wireDriver(inWrite, outRead) {
|
|
121
121
|
const driver = createDriver({
|
|
122
|
-
sendKey: (k) => inWrite.write(JSON.stringify({ op: "press", key: k })
|
|
123
|
-
sendType: (t) => inWrite.write(JSON.stringify({ op: "type", text: t })
|
|
122
|
+
sendKey: (k) => inWrite.write(`${JSON.stringify({ op: "press", key: k })}\n`),
|
|
123
|
+
sendType: (t) => inWrite.write(`${JSON.stringify({ op: "type", text: t })}\n`),
|
|
124
124
|
});
|
|
125
125
|
const splitter = createLineSplitter((line) => {
|
|
126
126
|
try {
|
|
127
127
|
const msg = JSON.parse(line);
|
|
128
|
-
if (msg
|
|
128
|
+
if (msg.mode === "live") {
|
|
129
129
|
driver._ingest({ kind: "frame", frame: msg });
|
|
130
130
|
}
|
|
131
|
-
else if (msg
|
|
131
|
+
else if (msg.t === "idle") {
|
|
132
132
|
driver._ingest({ kind: "idle" });
|
|
133
133
|
}
|
|
134
|
-
else if (msg
|
|
134
|
+
else if (msg.t === "event") {
|
|
135
135
|
driver._ingest({ kind: "event", event: msg });
|
|
136
136
|
}
|
|
137
137
|
}
|
|
@@ -99,7 +99,7 @@ function parseAttrs(chunk) {
|
|
|
99
99
|
}
|
|
100
100
|
// `isModal` may appear without `={true}` in JSX (boolean shorthand). Detect.
|
|
101
101
|
if (/\bisModal\b(?!\s*=)/.test(chunk))
|
|
102
|
-
out
|
|
102
|
+
out.isModal = true;
|
|
103
103
|
return out;
|
|
104
104
|
}
|
|
105
105
|
function buildScenariosFromHits(hits, allFiles, maxScenarios) {
|
|
@@ -95,12 +95,12 @@ function stepToCode(step) {
|
|
|
95
95
|
case "wait_for": {
|
|
96
96
|
const obj = {};
|
|
97
97
|
if (step.idle)
|
|
98
|
-
obj
|
|
98
|
+
obj.idle = true;
|
|
99
99
|
if (step.selector)
|
|
100
|
-
obj
|
|
100
|
+
obj.selector = step.selector;
|
|
101
101
|
if (step.event)
|
|
102
|
-
obj
|
|
103
|
-
obj
|
|
102
|
+
obj.event = step.event;
|
|
103
|
+
obj.timeoutMs = step.timeoutMs ?? 5_000;
|
|
104
104
|
return `await driver.wait_for(${JSON.stringify(obj)});`;
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
const sessionsDb = new Map();
|
|
3
|
+
const messagesDb = new Map();
|
|
4
|
+
const toolCallsDb = new Map();
|
|
5
|
+
const toolResultsDb = new Map();
|
|
6
|
+
const compactionsDb = new Map();
|
|
7
|
+
vi.mock("../db", () => {
|
|
8
|
+
return {
|
|
9
|
+
getDatabase: () => ({
|
|
10
|
+
prepare: (sql) => {
|
|
11
|
+
const lower = sql.toLowerCase();
|
|
12
|
+
return {
|
|
13
|
+
get: (param) => {
|
|
14
|
+
if (lower.includes("from sessions")) {
|
|
15
|
+
if (lower.includes("where parent_session_id =")) {
|
|
16
|
+
for (const [id, val] of sessionsDb.entries()) {
|
|
17
|
+
if (val.parent_session_id === param) {
|
|
18
|
+
return { id };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return sessionsDb.get(param) || { parent_session_id: null };
|
|
24
|
+
}
|
|
25
|
+
if (lower.includes("from tool_calls") && lower.includes("todo_write")) {
|
|
26
|
+
const calls = toolCallsDb.get(param) || [];
|
|
27
|
+
const todo = calls.find((c) => c.tool_name === "todo_write");
|
|
28
|
+
return todo ? { args_json: todo.args_json } : undefined;
|
|
29
|
+
}
|
|
30
|
+
if (lower.includes("from compactions")) {
|
|
31
|
+
return compactionsDb.get(param);
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
},
|
|
35
|
+
all: (...params) => {
|
|
36
|
+
if (lower.includes("from messages")) {
|
|
37
|
+
return messagesDb.get(params[0]) || [];
|
|
38
|
+
}
|
|
39
|
+
if (lower.includes("from tool_results")) {
|
|
40
|
+
return toolResultsDb.get(params[0]) || [];
|
|
41
|
+
}
|
|
42
|
+
if (lower.includes("from sessions")) {
|
|
43
|
+
if (lower.includes("where parent_session_id =")) {
|
|
44
|
+
const results = [];
|
|
45
|
+
for (const [id, val] of sessionsDb.entries()) {
|
|
46
|
+
if (val.parent_session_id === params[0]) {
|
|
47
|
+
results.push({ id });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
if (lower.includes("where id in")) {
|
|
53
|
+
return params.map((id) => ({ id }));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return [];
|
|
57
|
+
},
|
|
58
|
+
run: () => ({ changes: 0 }),
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
exec: () => undefined,
|
|
62
|
+
pragma: () => undefined,
|
|
63
|
+
transaction: (fn) => fn,
|
|
64
|
+
}),
|
|
65
|
+
withTransaction: (fn) => fn({}),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
import { buildChatEntries, getLastTodoWriteArgs, getSessionChain } from "../transcript.js";
|
|
69
|
+
describe("Hierarchical Sessions (Silent Rotation Support)", () => {
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
sessionsDb.clear();
|
|
72
|
+
messagesDb.clear();
|
|
73
|
+
toolCallsDb.clear();
|
|
74
|
+
toolResultsDb.clear();
|
|
75
|
+
compactionsDb.clear();
|
|
76
|
+
});
|
|
77
|
+
describe("getSessionChain", () => {
|
|
78
|
+
it("returns correct chain for a single session", () => {
|
|
79
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
80
|
+
expect(getSessionChain("sessionA")).toEqual(["sessionA"]);
|
|
81
|
+
});
|
|
82
|
+
it("traverses up the parent session chain recursively", () => {
|
|
83
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
84
|
+
sessionsDb.set("sessionB", { parent_session_id: "sessionA" });
|
|
85
|
+
sessionsDb.set("sessionC", { parent_session_id: "sessionB" });
|
|
86
|
+
expect(getSessionChain("sessionC")).toEqual(["sessionA", "sessionB", "sessionC"]);
|
|
87
|
+
});
|
|
88
|
+
it("handles loops gracefully and does not infinite loop", () => {
|
|
89
|
+
sessionsDb.set("sessionA", { parent_session_id: "sessionB" });
|
|
90
|
+
sessionsDb.set("sessionB", { parent_session_id: "sessionA" });
|
|
91
|
+
// Should break loop and return traversed list in some order
|
|
92
|
+
const chain = getSessionChain("sessionB");
|
|
93
|
+
expect(chain.length).toBe(2);
|
|
94
|
+
expect(chain).toContain("sessionA");
|
|
95
|
+
expect(chain).toContain("sessionB");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe("getLastTodoWriteArgs", () => {
|
|
99
|
+
it("returns null if no todo_write exists in chain", () => {
|
|
100
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
101
|
+
expect(getLastTodoWriteArgs("sessionA")).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
it("retrieves todo_write arguments from the current session", () => {
|
|
104
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
105
|
+
toolCallsDb.set("sessionA", [
|
|
106
|
+
{ id: 1, tool_call_id: "call_1", tool_name: "todo_write", args_json: '{"items":[]}' },
|
|
107
|
+
]);
|
|
108
|
+
expect(getLastTodoWriteArgs("sessionA")).toBe('{"items":[]}');
|
|
109
|
+
});
|
|
110
|
+
it("retrieves todo_write arguments recursively from parent session", () => {
|
|
111
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
112
|
+
sessionsDb.set("sessionB", { parent_session_id: "sessionA" });
|
|
113
|
+
toolCallsDb.set("sessionA", [
|
|
114
|
+
{ id: 1, tool_call_id: "call_1", tool_name: "todo_write", args_json: '{"items":["task1"]}' },
|
|
115
|
+
]);
|
|
116
|
+
// sessionB has no todo_write calls
|
|
117
|
+
expect(getLastTodoWriteArgs("sessionB")).toBe('{"items":["task1"]}');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe("buildChatEntries", () => {
|
|
121
|
+
it("merges messages from parent and child session chronologically", () => {
|
|
122
|
+
sessionsDb.set("sessionA", { parent_session_id: null });
|
|
123
|
+
sessionsDb.set("sessionB", { parent_session_id: "sessionA" });
|
|
124
|
+
messagesDb.set("sessionA", [
|
|
125
|
+
{
|
|
126
|
+
seq: 1,
|
|
127
|
+
role: "user",
|
|
128
|
+
message_json: JSON.stringify({ role: "user", content: "Hello A" }),
|
|
129
|
+
created_at: "2026-06-25T12:00:00Z",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
seq: 2,
|
|
133
|
+
role: "assistant",
|
|
134
|
+
message_json: JSON.stringify({ role: "assistant", content: "Reply A" }),
|
|
135
|
+
created_at: "2026-06-25T12:01:00Z",
|
|
136
|
+
},
|
|
137
|
+
]);
|
|
138
|
+
messagesDb.set("sessionB", [
|
|
139
|
+
{
|
|
140
|
+
seq: 1,
|
|
141
|
+
role: "system",
|
|
142
|
+
// compaction summary (should be skipped since it's the first message of a child)
|
|
143
|
+
message_json: JSON.stringify({ role: "system", content: "[Context checkpoint summary]\nEarlier work" }),
|
|
144
|
+
created_at: "2026-06-25T12:02:00Z",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
seq: 2,
|
|
148
|
+
role: "user",
|
|
149
|
+
message_json: JSON.stringify({ role: "user", content: "Hello B" }),
|
|
150
|
+
created_at: "2026-06-25T12:03:00Z",
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
const entries = buildChatEntries("sessionB");
|
|
154
|
+
expect(entries.length).toBe(3);
|
|
155
|
+
expect(entries[0]).toEqual(expect.objectContaining({ type: "user", content: "Hello A" }));
|
|
156
|
+
expect(entries[1]).toEqual(expect.objectContaining({ type: "assistant", content: "Reply A" }));
|
|
157
|
+
expect(entries[2]).toEqual(expect.objectContaining({ type: "user", content: "Hello B" }));
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=hierarchical-session.test.js.map
|
|
@@ -89,8 +89,8 @@ describe("sweepStalePendingRows", () => {
|
|
|
89
89
|
vi.spyOn(Date, "now").mockReturnValue(fixedNow);
|
|
90
90
|
sweepStalePendingRows(60_000);
|
|
91
91
|
const toolCallCall = preparedCalls.find((c) => c.sql.toLowerCase().includes("update tool_calls"));
|
|
92
|
-
// run args: [
|
|
93
|
-
const cutoff = toolCallCall?.runArgs[
|
|
92
|
+
// run args: [cutoff]
|
|
93
|
+
const cutoff = toolCallCall?.runArgs[0];
|
|
94
94
|
expect(cutoff).toBe("2026-05-22T09:59:00.000Z");
|
|
95
95
|
});
|
|
96
96
|
});
|
|
@@ -3,7 +3,7 @@ export { loadConfig, type MuonroiConfig } from "./config.js";
|
|
|
3
3
|
export { getDatabasePath } from "./db.js";
|
|
4
4
|
export { type InteractionEventType, logInteraction } from "./interaction-log.js";
|
|
5
5
|
export { SessionStore } from "./sessions.js";
|
|
6
|
-
export { appendCompaction, appendMessages, appendSystemMessage, buildChatEntries, getNextMessageSequence, loadLatestCompaction, loadRawTranscript, loadTranscript, loadTranscriptState, markMessageCompleted, markMessageErrored, markToolCallErrored, persistMessageWriteAhead, persistToolCallWriteAhead, } from "./transcript.js";
|
|
6
|
+
export { appendCompaction, appendMessages, appendSystemMessage, buildChatEntries, getLastTodoWriteArgs, getNextMessageSequence, getSessionChain, loadLatestCompaction, loadRawTranscript, loadTranscript, loadTranscriptState, markMessageCompleted, markMessageErrored, markToolCallErrored, persistMessageWriteAhead, persistToolCallWriteAhead, revertLatestCompaction, } from "./transcript.js";
|
|
7
7
|
export { buildEffectiveTranscript, type LoadedTranscriptState, type PersistedCompaction } from "./transcript-view.js";
|
|
8
8
|
export { logUIInteraction, type UIInteractionSubtype } from "./ui-interaction-log.js";
|
|
9
9
|
export { getSessionTotalTokens, listSessionUsage, recordUsageEvent, type TokenUsageLike } from "./usage.js";
|
|
@@ -4,7 +4,7 @@ export { loadConfig } from "./config.js";
|
|
|
4
4
|
export { getDatabasePath } from "./db.js";
|
|
5
5
|
export { logInteraction } from "./interaction-log.js";
|
|
6
6
|
export { SessionStore } from "./sessions.js";
|
|
7
|
-
export { appendCompaction, appendMessages, appendSystemMessage, buildChatEntries, getNextMessageSequence, loadLatestCompaction, loadRawTranscript, loadTranscript, loadTranscriptState, markMessageCompleted, markMessageErrored, markToolCallErrored, persistMessageWriteAhead, persistToolCallWriteAhead, } from "./transcript.js";
|
|
7
|
+
export { appendCompaction, appendMessages, appendSystemMessage, buildChatEntries, getLastTodoWriteArgs, getNextMessageSequence, getSessionChain, loadLatestCompaction, loadRawTranscript, loadTranscript, loadTranscriptState, markMessageCompleted, markMessageErrored, markToolCallErrored, persistMessageWriteAhead, persistToolCallWriteAhead, revertLatestCompaction, } from "./transcript.js";
|
|
8
8
|
export { buildEffectiveTranscript } from "./transcript-view.js";
|
|
9
9
|
export { logUIInteraction } from "./ui-interaction-log.js";
|
|
10
10
|
export { getSessionTotalTokens, listSessionUsage, recordUsageEvent } from "./usage.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const LATEST_DB_VERSION =
|
|
1
|
+
const LATEST_DB_VERSION = 8;
|
|
2
2
|
export function applyMigrations(db) {
|
|
3
3
|
const version = Number(db.pragma("user_version", { simple: true })) || 0;
|
|
4
4
|
if (version >= LATEST_DB_VERSION)
|
|
@@ -87,6 +87,15 @@ export function applyMigrations(db) {
|
|
|
87
87
|
}
|
|
88
88
|
db.pragma("user_version = 7");
|
|
89
89
|
}
|
|
90
|
+
if (version < 8) {
|
|
91
|
+
const cols = db.prepare("PRAGMA table_info(sessions)").all();
|
|
92
|
+
const colNames = new Set(cols.map((c) => c.name));
|
|
93
|
+
if (!colNames.has("parent_session_id")) {
|
|
94
|
+
db.exec("ALTER TABLE sessions ADD COLUMN parent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL");
|
|
95
|
+
}
|
|
96
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_sessions_parent_id ON sessions(parent_session_id)");
|
|
97
|
+
db.pragma("user_version = 8");
|
|
98
|
+
}
|
|
90
99
|
});
|
|
91
100
|
migrate();
|
|
92
101
|
}
|
|
@@ -104,6 +113,7 @@ function createInitialSchema(db) {
|
|
|
104
113
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
105
114
|
id TEXT PRIMARY KEY,
|
|
106
115
|
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
116
|
+
parent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
107
117
|
title TEXT,
|
|
108
118
|
model TEXT NOT NULL,
|
|
109
119
|
mode TEXT NOT NULL,
|
|
@@ -172,6 +182,8 @@ function createInitialSchema(db) {
|
|
|
172
182
|
|
|
173
183
|
CREATE INDEX IF NOT EXISTS idx_sessions_workspace_updated
|
|
174
184
|
ON sessions(workspace_id, updated_at DESC);
|
|
185
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_parent_id
|
|
186
|
+
ON sessions(parent_session_id);
|
|
175
187
|
CREATE INDEX IF NOT EXISTS idx_messages_session_seq
|
|
176
188
|
ON messages(session_id, seq);
|
|
177
189
|
CREATE INDEX IF NOT EXISTS idx_tool_calls_session_seq
|
|
@@ -18,11 +18,24 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
18
18
|
let MESSAGE_ROWS = [];
|
|
19
19
|
vi.mock("./db", () => ({
|
|
20
20
|
getDatabase: () => ({
|
|
21
|
-
prepare: (sql) =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
prepare: (sql) => {
|
|
22
|
+
const lower = sql.toLowerCase();
|
|
23
|
+
return {
|
|
24
|
+
all: (...params) => {
|
|
25
|
+
if (lower.includes("from messages")) {
|
|
26
|
+
return MESSAGE_ROWS;
|
|
27
|
+
}
|
|
28
|
+
if (lower.includes("from sessions")) {
|
|
29
|
+
if (lower.includes("where id in")) {
|
|
30
|
+
return params.map((id) => ({ id }));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
},
|
|
35
|
+
get: () => undefined,
|
|
36
|
+
run: () => undefined,
|
|
37
|
+
};
|
|
38
|
+
},
|
|
26
39
|
}),
|
|
27
40
|
withTransaction: (fn) => fn({}),
|
|
28
41
|
}));
|
|
@@ -9,7 +9,13 @@ export function buildEffectiveTranscript(messages, seqs, timestamps, compaction)
|
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
const firstKeptIndex = seqs.findIndex((seq) => seq >= compaction.firstKeptSeq);
|
|
12
|
-
|
|
12
|
+
let keptIndex = firstKeptIndex >= 0 ? firstKeptIndex : messages.length;
|
|
13
|
+
// Anti-mù / Resilience: if firstKeptSeq landed on a `tool` message (e.g. due to seq array
|
|
14
|
+
// misalignment or forced cut), we MUST step back to include its preceding `assistant` message.
|
|
15
|
+
// Otherwise Vercel AI SDK throws: "Messages with role 'tool' must be a response to a preceding message with 'tool_calls'"
|
|
16
|
+
if (keptIndex > 0 && messages[keptIndex]?.role === "tool") {
|
|
17
|
+
keptIndex -= 1;
|
|
18
|
+
}
|
|
13
19
|
const keptMessages = messages.slice(keptIndex);
|
|
14
20
|
const keptSeqs = seqs.slice(keptIndex);
|
|
15
21
|
const keptTimestamps = timestamps.slice(keptIndex);
|
|
@@ -104,4 +104,7 @@ export declare function sweepStalePendingRows(staleAfterMs?: number): {
|
|
|
104
104
|
};
|
|
105
105
|
export declare function markToolCallErrored(sessionId: string, toolCallId: string, errorMessage: string): void;
|
|
106
106
|
export declare function appendCompaction(sessionId: string, firstKeptSeq: number, summary: string, tokensBefore: number): void;
|
|
107
|
+
export declare function revertLatestCompaction(sessionId: string): void;
|
|
108
|
+
export declare function getSessionChain(sessionId: string): string[];
|
|
107
109
|
export declare function buildChatEntries(sessionId: string): ChatEntry[];
|
|
110
|
+
export declare function getLastTodoWriteArgs(sessionId: string): string | null;
|