supipowers 1.5.2 → 2.0.0
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/README.md +14 -8
- package/bin/install.mjs +20 -5
- package/bin/install.ts +95 -0
- package/package.json +8 -4
- package/skills/context-mode/SKILL.md +17 -10
- package/skills/harness/SKILL.md +94 -0
- package/skills/ui-design/SKILL.md +63 -0
- package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
- package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
- package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
- package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
- package/skills/ultraplan-discover/SKILL.md +96 -0
- package/skills/ultraplan-intake/SKILL.md +89 -0
- package/skills/ultraplan-research/SKILL.md +129 -0
- package/skills/ultraplan-review/SKILL.md +86 -0
- package/skills/ultraplan-review-scope/SKILL.md +111 -0
- package/skills/ultraplan-review-structure/SKILL.md +120 -0
- package/skills/ultraplan-review-tdd/SKILL.md +142 -0
- package/skills/ultraplan-scout/SKILL.md +110 -0
- package/skills/ultraplan-synthesize/SKILL.md +124 -0
- package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
- package/src/ai/schema-text.ts +129 -0
- package/src/ai/structured-output.ts +274 -0
- package/src/ai/template.ts +27 -0
- package/src/bootstrap.ts +63 -28
- package/src/commands/agents.ts +149 -45
- package/src/commands/ai-review.ts +251 -30
- package/src/commands/clear.ts +434 -0
- package/src/commands/commit.ts +1 -0
- package/src/commands/config.ts +242 -44
- package/src/commands/context.ts +55 -28
- package/src/commands/doctor.ts +234 -6
- package/src/commands/fix-pr.ts +306 -131
- package/src/commands/generate.ts +111 -21
- package/src/commands/memory.ts +192 -0
- package/src/commands/model-picker.ts +28 -21
- package/src/commands/model.ts +19 -9
- package/src/commands/optimize-context.ts +408 -29
- package/src/commands/plan.ts +2 -0
- package/src/commands/qa.ts +312 -137
- package/src/commands/release.ts +259 -76
- package/src/commands/review.ts +293 -59
- package/src/commands/status.ts +200 -13
- package/src/commands/supi.ts +3 -35
- package/src/commands/ui-design.ts +394 -0
- package/src/commands/ultraplan.ts +1518 -0
- package/src/commands/update.ts +86 -0
- package/src/config/defaults.ts +62 -0
- package/src/config/loader.ts +448 -60
- package/src/config/schema.ts +108 -2
- package/src/context/optimizer.ts +25 -33
- package/src/context/rule-renderer.ts +223 -0
- package/src/context/savings.ts +258 -0
- package/src/context/startup-check.ts +380 -0
- package/src/context/startup-optimizer.ts +355 -0
- package/src/context/tokenignore.ts +146 -0
- package/src/context-mode/cache-handle.ts +49 -0
- package/src/context-mode/cache-preview.ts +71 -0
- package/src/context-mode/cache-store.ts +738 -0
- package/src/context-mode/compressor.ts +131 -26
- package/src/context-mode/dedup.ts +108 -0
- package/src/context-mode/detector.ts +35 -4
- package/src/context-mode/event-extractor.ts +14 -12
- package/src/context-mode/event-store.ts +91 -36
- package/src/context-mode/hooks.ts +798 -56
- package/src/context-mode/knowledge/store.ts +255 -11
- package/src/context-mode/memory-store.ts +325 -0
- package/src/context-mode/metrics-recorder.ts +158 -0
- package/src/context-mode/metrics-store.ts +765 -0
- package/src/context-mode/model.ts +24 -0
- package/src/context-mode/processor-keys.ts +29 -0
- package/src/context-mode/processors/build.ts +66 -0
- package/src/context-mode/processors/docker.ts +57 -0
- package/src/context-mode/processors/git.ts +111 -0
- package/src/context-mode/processors/json.ts +112 -0
- package/src/context-mode/processors/k8s.ts +67 -0
- package/src/context-mode/processors/lint.ts +67 -0
- package/src/context-mode/processors/log.ts +86 -0
- package/src/context-mode/processors/registry.ts +116 -0
- package/src/context-mode/processors/test-runner.ts +102 -0
- package/src/context-mode/processors/types.ts +20 -0
- package/src/context-mode/repomap.ts +400 -0
- package/src/context-mode/routing.ts +97 -24
- package/src/context-mode/sandbox/runners.ts +5 -1
- package/src/context-mode/snapshot-builder.ts +106 -11
- package/src/context-mode/source-hash.ts +173 -0
- package/src/context-mode/tool-name.ts +11 -0
- package/src/context-mode/tools.ts +654 -22
- package/src/context-mode/web/fetcher.ts +31 -12
- package/src/debug/logger.ts +2 -1
- package/src/deps/registry.ts +1 -1
- package/src/discipline/failure-summarizer.ts +170 -0
- package/src/discipline/failure-taxonomy.ts +131 -0
- package/src/discipline/workflow-invariants.ts +125 -0
- package/src/discovery/index.ts +31 -0
- package/src/discovery/lsp.ts +87 -0
- package/src/discovery/rank.ts +144 -0
- package/src/discovery/sources.ts +89 -0
- package/src/discovery/workflow.ts +87 -0
- package/src/docs/contracts.ts +39 -0
- package/src/docs/drift.ts +117 -87
- package/src/fix-pr/assessment.ts +200 -0
- package/src/fix-pr/contracts.ts +47 -0
- package/src/fix-pr/fetch-comments.ts +80 -0
- package/src/fix-pr/prompt-builder.ts +58 -40
- package/src/fix-pr/scripts/exec.ts +34 -0
- package/src/fix-pr/scripts/trigger-review.ts +106 -0
- package/src/fix-pr/scripts/wait-and-check.ts +108 -0
- package/src/fix-pr/types.ts +4 -0
- package/src/git/branch-finish.ts +5 -0
- package/src/git/commit-contract.ts +83 -0
- package/src/git/commit.ts +121 -184
- package/src/git/status.ts +62 -8
- package/src/harness/anti_slop/architecture-parser.ts +210 -0
- package/src/harness/anti_slop/backend-factory.ts +30 -0
- package/src/harness/anti_slop/backend.ts +140 -0
- package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
- package/src/harness/anti_slop/fallow-adapter.ts +305 -0
- package/src/harness/anti_slop/installer.ts +227 -0
- package/src/harness/anti_slop/queue.ts +216 -0
- package/src/harness/anti_slop/recommend.ts +84 -0
- package/src/harness/anti_slop/score.ts +180 -0
- package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
- package/src/harness/artifacts/agents-md.ts +88 -0
- package/src/harness/artifacts/checks-wiring.ts +57 -0
- package/src/harness/artifacts/docs-tree.ts +79 -0
- package/src/harness/artifacts/lint-configs.ts +136 -0
- package/src/harness/artifacts/review-agents.ts +67 -0
- package/src/harness/bare-entry.ts +108 -0
- package/src/harness/command.ts +1010 -0
- package/src/harness/default-agents/design.md +23 -0
- package/src/harness/default-agents/discover.md +18 -0
- package/src/harness/default-agents/implement.md +24 -0
- package/src/harness/default-agents/plan.md +19 -0
- package/src/harness/default-agents/research.md +21 -0
- package/src/harness/default-agents/validate.md +22 -0
- package/src/harness/gc/reporter.ts +28 -0
- package/src/harness/gc/runner.ts +136 -0
- package/src/harness/hooks/layer-context-inject.ts +155 -0
- package/src/harness/hooks/post-session-sweep.ts +130 -0
- package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
- package/src/harness/hooks/register.ts +118 -0
- package/src/harness/model.ts +117 -0
- package/src/harness/pipeline.ts +348 -0
- package/src/harness/project-paths.ts +235 -0
- package/src/harness/stage-runner.ts +107 -0
- package/src/harness/stages/design.ts +386 -0
- package/src/harness/stages/discover.ts +454 -0
- package/src/harness/stages/implement.ts +162 -0
- package/src/harness/stages/plan.ts +335 -0
- package/src/harness/stages/research.ts +263 -0
- package/src/harness/stages/validate.ts +684 -0
- package/src/harness/storage.ts +467 -0
- package/src/harness/tools.ts +426 -0
- package/src/lsp/bridge.ts +56 -95
- package/src/lsp/capabilities.ts +108 -0
- package/src/lsp/contracts.ts +35 -0
- package/src/lsp/detector.ts +8 -12
- package/src/markdown-frontmatter.ts +68 -0
- package/src/mempalace/bridge.ts +129 -0
- package/src/mempalace/config.ts +75 -0
- package/src/mempalace/format.ts +163 -0
- package/src/mempalace/hooks.ts +370 -0
- package/src/mempalace/installer-helper.ts +194 -0
- package/src/mempalace/python/mempalace_bridge.py +440 -0
- package/src/mempalace/runtime.ts +565 -0
- package/src/mempalace/schema.ts +264 -0
- package/src/mempalace/session-summary.ts +198 -0
- package/src/mempalace/tool.ts +186 -0
- package/src/mempalace/uv.ts +256 -0
- package/src/migrate/runner.ts +354 -0
- package/src/planning/approval-flow.ts +206 -9
- package/src/planning/plan-writer-prompt.ts +4 -3
- package/src/planning/planning-ask-tool.ts +39 -0
- package/src/planning/render-markdown.ts +74 -0
- package/src/planning/spec.ts +42 -0
- package/src/planning/system-prompt.ts +11 -8
- package/src/planning/validate.ts +84 -0
- package/src/platform/omp.ts +15 -2
- package/src/platform/system-prompt.ts +37 -0
- package/src/platform/test-utils.ts +3 -0
- package/src/platform/types.ts +6 -1
- package/src/qa/config.ts +12 -6
- package/src/qa/detect-app-type.ts +13 -6
- package/src/qa/matrix.ts +12 -6
- package/src/qa/prompt-builder.ts +28 -30
- package/src/qa/scripts/dev-server-utils.ts +72 -0
- package/src/qa/scripts/run-e2e-tests.ts +226 -0
- package/src/qa/scripts/start-dev-server.ts +138 -0
- package/src/qa/scripts/stop-dev-server.ts +77 -0
- package/src/qa/session.ts +13 -7
- package/src/quality/ai-setup.ts +27 -25
- package/src/quality/contracts.ts +34 -0
- package/src/quality/gates/ai-review.ts +20 -58
- package/src/quality/gates/command.ts +249 -46
- package/src/quality/review-gates.ts +18 -2
- package/src/quality/runner.ts +63 -22
- package/src/quality/schemas.ts +37 -2
- package/src/quality/setup.ts +96 -16
- package/src/release/changelog.ts +1 -1
- package/src/release/channels/custom.ts +13 -3
- package/src/release/channels/types.ts +5 -0
- package/src/release/contracts.ts +90 -0
- package/src/release/executor.ts +122 -45
- package/src/release/prompt.ts +18 -2
- package/src/release/targets.ts +86 -0
- package/src/release/version.ts +96 -71
- package/src/review/agent-loader.ts +298 -127
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +115 -14
- package/src/review/output.ts +12 -139
- package/src/review/runner.ts +12 -6
- package/src/review/scope.ts +144 -24
- package/src/review/types.ts +11 -20
- package/src/review/validator.ts +12 -6
- package/src/storage/fix-pr-sessions.ts +21 -14
- package/src/storage/plans.ts +14 -5
- package/src/storage/qa-sessions.ts +25 -19
- package/src/storage/reliability-metrics.ts +180 -0
- package/src/storage/reports.ts +8 -7
- package/src/storage/review-sessions.ts +55 -20
- package/src/tool-catalog/active-tool-controller.ts +164 -0
- package/src/tool-catalog/active-tool-planner.ts +212 -0
- package/src/tool-catalog/tool-groups.ts +102 -0
- package/src/types.ts +1401 -5
- package/src/ui-design/backend-adapter.ts +78 -0
- package/src/ui-design/backends/local-html.ts +82 -0
- package/src/ui-design/backends/pencil-mcp.ts +111 -0
- package/src/ui-design/components-scanner.ts +124 -0
- package/src/ui-design/config.ts +55 -0
- package/src/ui-design/pen-scanner.ts +95 -0
- package/src/ui-design/pen-selector.ts +72 -0
- package/src/ui-design/prompt-builder.ts +73 -0
- package/src/ui-design/scanner.ts +136 -0
- package/src/ui-design/session.ts +974 -0
- package/src/ui-design/system-prompt.ts +312 -0
- package/src/ui-design/tokens-scanner.ts +181 -0
- package/src/ui-design/types.ts +96 -0
- package/src/ultraplan/agent-catalog.ts +522 -0
- package/src/ultraplan/authoring/agent-catalog.ts +310 -0
- package/src/ultraplan/authoring/authoring-tools.ts +552 -0
- package/src/ultraplan/authoring/command-handlers.ts +339 -0
- package/src/ultraplan/authoring/markdown.ts +510 -0
- package/src/ultraplan/authoring/model.ts +162 -0
- package/src/ultraplan/authoring/pipeline.ts +319 -0
- package/src/ultraplan/authoring/stage-runner.ts +141 -0
- package/src/ultraplan/authoring/stages/approve.ts +249 -0
- package/src/ultraplan/authoring/stages/discover.ts +289 -0
- package/src/ultraplan/authoring/stages/intake.ts +203 -0
- package/src/ultraplan/authoring/stages/research.ts +399 -0
- package/src/ultraplan/authoring/stages/review.ts +333 -0
- package/src/ultraplan/authoring/stages/scout.ts +188 -0
- package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
- package/src/ultraplan/authoring/storage.ts +594 -0
- package/src/ultraplan/authoring/synth-gate.ts +165 -0
- package/src/ultraplan/authoring-draft.ts +653 -0
- package/src/ultraplan/authoring-persist.ts +180 -0
- package/src/ultraplan/authoring-tool.ts +608 -0
- package/src/ultraplan/authoring-wizard.ts +587 -0
- package/src/ultraplan/batch/merge.ts +98 -0
- package/src/ultraplan/batch/planner.ts +150 -0
- package/src/ultraplan/batch/presenter.ts +97 -0
- package/src/ultraplan/batch/storage.ts +420 -0
- package/src/ultraplan/batch/supervisor.ts +317 -0
- package/src/ultraplan/batch/worker.ts +26 -0
- package/src/ultraplan/batch/worktree.ts +110 -0
- package/src/ultraplan/contracts.ts +1593 -0
- package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
- package/src/ultraplan/default-agents/authoring/intake.md +12 -0
- package/src/ultraplan/default-agents/authoring/planner.md +12 -0
- package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
- package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/scout.md +12 -0
- package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
- package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-executor.md +10 -0
- package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-tester.md +10 -0
- package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-executor.md +10 -0
- package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-tester.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
- package/src/ultraplan/execution/contract.ts +71 -0
- package/src/ultraplan/execution/policy.ts +217 -0
- package/src/ultraplan/execution/runtime-tools.ts +107 -0
- package/src/ultraplan/execution/session-runner.ts +281 -0
- package/src/ultraplan/next-router.ts +85 -0
- package/src/ultraplan/presenter.ts +359 -0
- package/src/ultraplan/project-paths.ts +342 -0
- package/src/ultraplan/runtime/active-execution.ts +72 -0
- package/src/ultraplan/runtime/apply-mutation.ts +416 -0
- package/src/ultraplan/runtime/blockers.ts +243 -0
- package/src/ultraplan/runtime/hook-bridge.ts +486 -0
- package/src/ultraplan/runtime/launch-context.ts +207 -0
- package/src/ultraplan/runtime/migration.ts +524 -0
- package/src/ultraplan/runtime/normalize.ts +281 -0
- package/src/ultraplan/runtime/proof.ts +260 -0
- package/src/ultraplan/runtime/reducer.ts +416 -0
- package/src/ultraplan/runtime/repair.ts +251 -0
- package/src/ultraplan/runtime/tracker-storage.ts +368 -0
- package/src/ultraplan/session-selection.ts +291 -0
- package/src/ultraplan/storage.ts +374 -0
- package/src/utils/editor.ts +38 -0
- package/src/utils/executable.ts +80 -0
- package/src/utils/paths.ts +1 -20
- package/src/utils/shell.ts +31 -0
- package/src/visual/companion.ts +2 -1
- package/src/visual/scripts/frame-template.html +60 -0
- package/src/visual/scripts/index.js +59 -13
- package/src/visual/scripts/package.json +3 -0
- package/src/visual/start-server.ts +2 -1
- package/src/workspace/git-scope.ts +64 -0
- package/src/workspace/locks.ts +23 -0
- package/src/workspace/package-manager.ts +117 -0
- package/src/workspace/path-mapping.ts +75 -0
- package/src/workspace/project-slug.ts +92 -0
- package/src/workspace/repo-root.ts +137 -0
- package/src/workspace/selector.ts +115 -0
- package/src/workspace/state-paths.ts +118 -0
- package/src/workspace/targets.ts +313 -0
- package/src/fix-pr/scripts/diff-comments.sh +0 -33
- package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
- package/src/fix-pr/scripts/trigger-review.sh +0 -36
- package/src/fix-pr/scripts/wait-and-check.sh +0 -37
- package/src/qa/scripts/detect-app-type.sh +0 -68
- package/src/qa/scripts/discover-routes.sh +0 -143
- package/src/qa/scripts/run-e2e-tests.sh +0 -131
- package/src/qa/scripts/start-dev-server.sh +0 -46
- package/src/qa/scripts/stop-dev-server.sh +0 -36
- package/src/review/prompts/fix-output-schema.md +0 -18
- package/src/review/prompts/review-output-schema.md +0 -38
- package/src/review/template.ts +0 -15
- /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/context-mode/hooks.ts
|
|
2
2
|
import type { Platform } from "../platform/types.js";
|
|
3
3
|
import type { SupipowersConfig } from "../types.js";
|
|
4
|
-
import {
|
|
4
|
+
import { OMP_MINIMIZER_FOOTER_RE, runEmissionPipeline } from "./compressor.js";
|
|
5
5
|
import { detectContextMode, type ContextModeStatus } from "./detector.js";
|
|
6
6
|
import { EventStore } from "./event-store.js";
|
|
7
7
|
import { extractEvents, extractPromptEvents } from "./event-extractor.js";
|
|
@@ -9,10 +9,27 @@ import { buildResumeSnapshot } from "./snapshot-builder.js";
|
|
|
9
9
|
import { routeToolCall } from "./routing.js";
|
|
10
10
|
import { KnowledgeStore } from "./knowledge/store.js";
|
|
11
11
|
import { registerContextModeTools } from "./tools.js";
|
|
12
|
+
import { MetricsStore, __setMetricsStoreForTest, _resetMetricsStoreCache } from "./metrics-store.js";
|
|
13
|
+
import { _resetAutoIndexAttempts, _forgetAutoIndexSession } from "./tools.js";
|
|
14
|
+
import { CacheStore } from "./cache-store.js";
|
|
15
|
+
import { MemoryStore, _setMemoryStoreRef } from "./memory-store.js";
|
|
16
|
+
import { toMetricRow } from "./metrics-recorder.js";
|
|
17
|
+
import { combinedTextOf, createDedupState, maybeSubstitute, type DedupState } from "./dedup.js";
|
|
18
|
+
import { uniqueSourceHash } from "./source-hash.js";
|
|
19
|
+
import { basename } from "node:path";
|
|
20
|
+
import { getProjectStateDir } from "../workspace/state-paths.js";
|
|
12
21
|
import { createHash } from "node:crypto";
|
|
13
|
-
import { readFileSync,
|
|
14
|
-
import { join
|
|
15
|
-
import {
|
|
22
|
+
import { mkdirSync, readFileSync, statSync } from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { registerUltraPlanHookBridge } from "../ultraplan/runtime/hook-bridge.js";
|
|
25
|
+
import { getProjectStatePath } from "../workspace/state-paths.js";
|
|
26
|
+
import { compressToolResultWithLLM } from "./compressor.js";
|
|
27
|
+
import { COMPACTION_SUMMARIZER_ACTION_ID } from "./model.js";
|
|
28
|
+
import { loadModelConfig } from "../config/model-config.js";
|
|
29
|
+
import { modelRegistry } from "../config/model-registry-instance.js";
|
|
30
|
+
import { createModelBridge, resolveModelForAction } from "../config/model-resolver.js";
|
|
31
|
+
import { extractFinalAssistantText } from "../ai/final-message.js";
|
|
32
|
+
import { normalizeSystemPromptBlocks } from "../platform/system-prompt.js";
|
|
16
33
|
|
|
17
34
|
type SessionContextLike = {
|
|
18
35
|
cwd?: string;
|
|
@@ -22,14 +39,56 @@ type SessionContextLike = {
|
|
|
22
39
|
// Cached detection result
|
|
23
40
|
let cachedStatus: ContextModeStatus | null = null;
|
|
24
41
|
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
function buildActiveRoutingGuidance(status: ContextModeStatus): string | null {
|
|
43
|
+
if (!status.available) return null;
|
|
44
|
+
|
|
45
|
+
const activeTools = activeContextToolNames(status);
|
|
46
|
+
const rescueTools = activeTools.filter((tool) =>
|
|
47
|
+
tool === "ctx_execute" || tool === "ctx_search" || tool === "ctx_batch_execute",
|
|
48
|
+
);
|
|
49
|
+
const lines = [
|
|
50
|
+
"# supi-context-mode",
|
|
51
|
+
"Use active `ctx_*` tools shown in the tool catalog for high-output work; inactive ctx tools are intentionally unavailable this turn.",
|
|
52
|
+
`Active context-mode rescue tools: ${rescueTools.length > 0 ? rescueTools.join(", ") : "none"}.`,
|
|
53
|
+
"Routing blocks native tools only when the named replacement is active. If a specialized ctx tool is absent, use an active rescue tool or proceed with the native tool.",
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const searchGatherTools = ["ctx_search", "ctx_batch_execute"].filter((tool) =>
|
|
57
|
+
tool === "ctx_search" ? status.tools.ctxSearch : status.tools.ctxBatchExecute,
|
|
58
|
+
);
|
|
59
|
+
if (searchGatherTools.length > 0) {
|
|
60
|
+
lines.push(`For search/gather work, prefer active ${searchGatherTools.map((tool) => `\`${tool}\``).join(" or ")} over Search/Find outputs.`);
|
|
61
|
+
}
|
|
62
|
+
if (status.tools.ctxExecute) {
|
|
63
|
+
lines.push("Use active `ctx_execute` for shell/data processing that may emit large output.");
|
|
64
|
+
}
|
|
65
|
+
if (status.tools.ctxFetchAndIndex) {
|
|
66
|
+
lines.push("Use active `ctx_fetch_and_index` for URLs, curl/wget, Fetch/WebFetch, or web docs.");
|
|
32
67
|
}
|
|
68
|
+
if (status.tools.ctxExecuteFile) {
|
|
69
|
+
lines.push("Use active `ctx_execute_file` for analysis-only large-file processing without loading the file into context.");
|
|
70
|
+
}
|
|
71
|
+
if (status.tools.ctxOpenCached) {
|
|
72
|
+
lines.push("Use active `ctx_open_cached` to read `cache://<sha>` handles in bounded slices via offset/limit.");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return lines.join("\n");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function activeContextToolNames(status: ContextModeStatus): string[] {
|
|
79
|
+
const names: string[] = [];
|
|
80
|
+
if (status.tools.ctxExecute) names.push("ctx_execute");
|
|
81
|
+
if (status.tools.ctxSearch) names.push("ctx_search");
|
|
82
|
+
if (status.tools.ctxBatchExecute) names.push("ctx_batch_execute");
|
|
83
|
+
if (status.tools.ctxExecuteFile) names.push("ctx_execute_file");
|
|
84
|
+
if (status.tools.ctxFetchAndIndex) names.push("ctx_fetch_and_index");
|
|
85
|
+
if (status.tools.ctxOpenCached) names.push("ctx_open_cached");
|
|
86
|
+
if (status.tools.ctxIndex) names.push("ctx_index");
|
|
87
|
+
if (status.tools.ctxStats) names.push("ctx_stats");
|
|
88
|
+
if (status.tools.ctxPurge) names.push("ctx_purge");
|
|
89
|
+
if (status.tools.ctxRepomap) names.push("ctx_repomap");
|
|
90
|
+
if (status.tools.ctxSymbol) names.push("ctx_symbol");
|
|
91
|
+
return names;
|
|
33
92
|
}
|
|
34
93
|
|
|
35
94
|
function resolveSessionCwd(ctx?: SessionContextLike): string {
|
|
@@ -48,8 +107,278 @@ function deriveSessionId(ctx?: SessionContextLike): string {
|
|
|
48
107
|
return `session-${Date.now()}`;
|
|
49
108
|
}
|
|
50
109
|
|
|
110
|
+
function textContentBytes(content: Array<{ type: string; text?: string }> | undefined): number {
|
|
111
|
+
if (!content) return 0;
|
|
112
|
+
return new TextEncoder().encode(combinedTextOf(content)).byteLength;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isTextOnlyContent(content: Array<{ type: string; text?: string }>): boolean {
|
|
116
|
+
return content.every((entry) => entry.type === "text");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function pickMemoryBody(category: string, data: Record<string, unknown>): string | null {
|
|
120
|
+
switch (category) {
|
|
121
|
+
case "decision": {
|
|
122
|
+
const prompt = typeof data.prompt === "string" ? data.prompt.trim() : "";
|
|
123
|
+
return prompt ? `decision: ${prompt.slice(0, 240)}` : null;
|
|
124
|
+
}
|
|
125
|
+
case "task": {
|
|
126
|
+
const input = data.input as Record<string, unknown> | undefined;
|
|
127
|
+
if (!input) return null;
|
|
128
|
+
const text = JSON.stringify(input).slice(0, 240);
|
|
129
|
+
return text ? `task: ${text}` : null;
|
|
130
|
+
}
|
|
131
|
+
case "intent": {
|
|
132
|
+
const intent = typeof data.intent === "string" ? data.intent : null;
|
|
133
|
+
const prompt = typeof data.prompt === "string" ? data.prompt.slice(0, 200) : "";
|
|
134
|
+
if (!intent) return null;
|
|
135
|
+
return `intent ${intent}: ${prompt}`.trim();
|
|
136
|
+
}
|
|
137
|
+
case "rule": {
|
|
138
|
+
const file = typeof data.path === "string" ? data.path : null;
|
|
139
|
+
return file ? `rule loaded: ${file}` : null;
|
|
140
|
+
}
|
|
141
|
+
default:
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function memoryTypeFor(category: string): "observation" | "decision" | "task" {
|
|
147
|
+
switch (category) {
|
|
148
|
+
case "decision":
|
|
149
|
+
return "decision";
|
|
150
|
+
case "task":
|
|
151
|
+
return "task";
|
|
152
|
+
default:
|
|
153
|
+
return "observation";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function buildMemoryInjectionBlock(
|
|
158
|
+
memoryStore: MemoryStore | null,
|
|
159
|
+
sessionId: string,
|
|
160
|
+
config: { byteBudget: number; maxRows: number },
|
|
161
|
+
): string | null {
|
|
162
|
+
if (!memoryStore) return null;
|
|
163
|
+
let rows;
|
|
164
|
+
try {
|
|
165
|
+
rows = memoryStore.retrieve({
|
|
166
|
+
sessionId,
|
|
167
|
+
byteBudget: config.byteBudget,
|
|
168
|
+
limit: config.maxRows,
|
|
169
|
+
});
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
if (rows.length === 0) return null;
|
|
174
|
+
const lines = ["# Cross-session memory"];
|
|
175
|
+
for (const row of rows) {
|
|
176
|
+
lines.push(`- [${row.type}] ${row.body}`);
|
|
177
|
+
}
|
|
178
|
+
return lines.join("\n");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function buildFocusChainBlock(
|
|
182
|
+
eventStore: EventStore | null,
|
|
183
|
+
sessionId: string,
|
|
184
|
+
opts: { cadence: number; turnCount: number },
|
|
185
|
+
): string | null {
|
|
186
|
+
if (!eventStore) return null;
|
|
187
|
+
// Cadence gate: turn 1 always injects; subsequent turns inject only every Nth.
|
|
188
|
+
// cadence < 1 is a config error guarded upstream by the schema; treat as 1
|
|
189
|
+
// here for defensive behavior.
|
|
190
|
+
const cadence = Math.max(1, Math.floor(opts.cadence));
|
|
191
|
+
const turnCount = Math.max(1, Math.floor(opts.turnCount));
|
|
192
|
+
if (turnCount !== 1 && turnCount % cadence !== 0) return null;
|
|
193
|
+
let events;
|
|
194
|
+
try {
|
|
195
|
+
events = eventStore.getEvents(sessionId, { categories: ["task"], limit: 1 });
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
if (events.length === 0) return null;
|
|
200
|
+
let data: any;
|
|
201
|
+
try {
|
|
202
|
+
data = JSON.parse(events[0].data);
|
|
203
|
+
} catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const ops = (data?.input?.ops as Array<Record<string, unknown>> | undefined) ?? [];
|
|
207
|
+
const summary = ops
|
|
208
|
+
.map((op) => {
|
|
209
|
+
const verb = typeof op.op === "string" ? op.op : "";
|
|
210
|
+
const target = (typeof op.task === "string" && op.task)
|
|
211
|
+
|| (typeof op.phase === "string" && op.phase)
|
|
212
|
+
|| "";
|
|
213
|
+
if (!verb) return null;
|
|
214
|
+
return target ? `${verb}: ${target}` : verb;
|
|
215
|
+
})
|
|
216
|
+
.filter((line): line is string => Boolean(line));
|
|
217
|
+
if (summary.length === 0) return null;
|
|
218
|
+
return ["# Focus chain", ...summary.slice(0, 5).map((line) => `- ${line}`)].join("\n");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function readCompactOverride(paths: Platform["paths"], cwd: string): string | null {
|
|
222
|
+
try {
|
|
223
|
+
const filePath = paths.project(cwd, "compact.md");
|
|
224
|
+
const stat = statSync(filePath);
|
|
225
|
+
if (!stat.isFile()) return null;
|
|
226
|
+
if (stat.size > 32 * 1024) return null;
|
|
227
|
+
const text = readFileSync(filePath, "utf8");
|
|
228
|
+
return text.trim() || null;
|
|
229
|
+
} catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
51
234
|
function getSessionDbPath(platform: Platform, cwd: string): string {
|
|
52
|
-
return join(platform.paths
|
|
235
|
+
return join(getProjectStatePath(platform.paths, cwd, "sessions"), "events.db");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function byteLengthOf(text: string): number {
|
|
239
|
+
return new TextEncoder().encode(text).byteLength;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Default per-call timeout for the compaction-time LLM summarizer. Compaction
|
|
244
|
+
* already runs synchronously from OMP's perspective; bounding the LLM step
|
|
245
|
+
* keeps it from extending compaction indefinitely on slow models or stuck
|
|
246
|
+
* streams. The deterministic snapshot is persisted before this runs, so a
|
|
247
|
+
* timeout fail is safe.
|
|
248
|
+
*/
|
|
249
|
+
const COMPACTION_LLM_TIMEOUT_MS = 10_000;
|
|
250
|
+
|
|
251
|
+
function buildSummarizeCallback(
|
|
252
|
+
platform: Platform,
|
|
253
|
+
cwd: string,
|
|
254
|
+
modelId: string | undefined,
|
|
255
|
+
thinkingLevel: string | null,
|
|
256
|
+
timeoutMs: number,
|
|
257
|
+
): (text: string, toolName: string) => Promise<string> {
|
|
258
|
+
return async (text: string, _toolName: string): Promise<string> => {
|
|
259
|
+
if (typeof platform.createAgentSession !== "function") {
|
|
260
|
+
// Platform stub or unavailable session API: degrade silently.
|
|
261
|
+
return "";
|
|
262
|
+
}
|
|
263
|
+
let session: Awaited<ReturnType<Platform["createAgentSession"]>> | null = null;
|
|
264
|
+
try {
|
|
265
|
+
session = await platform.createAgentSession({
|
|
266
|
+
cwd,
|
|
267
|
+
...(modelId ? { model: modelId } : {}),
|
|
268
|
+
thinkingLevel: thinkingLevel ?? null,
|
|
269
|
+
});
|
|
270
|
+
} catch (e) {
|
|
271
|
+
(platform as any).logger?.debug?.(
|
|
272
|
+
"supi-context-mode: createAgentSession unavailable for summarizer",
|
|
273
|
+
e,
|
|
274
|
+
);
|
|
275
|
+
return "";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
279
|
+
try {
|
|
280
|
+
const promptPromise = session.prompt(text, { expandPromptTemplates: false });
|
|
281
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
282
|
+
timer = setTimeout(() => reject(new Error("compaction-summarizer timeout")), timeoutMs);
|
|
283
|
+
});
|
|
284
|
+
await Promise.race([promptPromise, timeoutPromise]);
|
|
285
|
+
const finalText = extractFinalAssistantText(session.state.messages);
|
|
286
|
+
return finalText ?? "";
|
|
287
|
+
} catch (e) {
|
|
288
|
+
(platform as any).logger?.debug?.("supi-context-mode: summarizer prompt failed", e);
|
|
289
|
+
return "";
|
|
290
|
+
} finally {
|
|
291
|
+
if (timer) clearTimeout(timer);
|
|
292
|
+
try {
|
|
293
|
+
await session.dispose();
|
|
294
|
+
} catch {
|
|
295
|
+
// best effort
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
interface SummarizeSnapshotOpts {
|
|
302
|
+
platform: Platform;
|
|
303
|
+
cwd: string;
|
|
304
|
+
sessionId: string;
|
|
305
|
+
snapshot: string;
|
|
306
|
+
eventCount: number;
|
|
307
|
+
eventStore: EventStore;
|
|
308
|
+
compressionThreshold: number;
|
|
309
|
+
llmThreshold: number;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Wrap the deterministic resume snapshot in a synthetic ToolResultEventLike
|
|
314
|
+
* and ask `compressToolResultWithLLM` to summarize it. On a successful
|
|
315
|
+
* non-empty replacement, overwrite the resume row. On any failure, the
|
|
316
|
+
* deterministic snapshot already persisted at the call site remains in place.
|
|
317
|
+
*/
|
|
318
|
+
async function summarizeSnapshotIfBudget(opts: SummarizeSnapshotOpts): Promise<void> {
|
|
319
|
+
const modelConfig = (() => {
|
|
320
|
+
try {
|
|
321
|
+
return loadModelConfig(opts.platform.paths, opts.cwd);
|
|
322
|
+
} catch {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
})();
|
|
326
|
+
if (!modelConfig) return;
|
|
327
|
+
|
|
328
|
+
const resolved = resolveModelForAction(
|
|
329
|
+
COMPACTION_SUMMARIZER_ACTION_ID,
|
|
330
|
+
modelRegistry,
|
|
331
|
+
modelConfig,
|
|
332
|
+
createModelBridge(opts.platform),
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
// No model resolved at all (no action override, no default, no role, no
|
|
336
|
+
// main session model) — skip silently. The deterministic snapshot stays.
|
|
337
|
+
if (!resolved.model) {
|
|
338
|
+
(opts.platform as any).logger?.debug?.(
|
|
339
|
+
"supi-context-mode: no model resolved for compaction-summarizer; skipping",
|
|
340
|
+
);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const summarize = buildSummarizeCallback(
|
|
345
|
+
opts.platform,
|
|
346
|
+
opts.cwd,
|
|
347
|
+
resolved.model,
|
|
348
|
+
resolved.thinkingLevel,
|
|
349
|
+
COMPACTION_LLM_TIMEOUT_MS,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const syntheticEvent = {
|
|
353
|
+
toolName: "context-mode-snapshot",
|
|
354
|
+
input: {},
|
|
355
|
+
content: [{ type: "text", text: opts.snapshot }],
|
|
356
|
+
isError: false,
|
|
357
|
+
details: null,
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const result = await compressToolResultWithLLM(
|
|
361
|
+
syntheticEvent,
|
|
362
|
+
opts.compressionThreshold,
|
|
363
|
+
opts.llmThreshold,
|
|
364
|
+
summarize,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
if (!result || !result.content || result.content.length === 0) return;
|
|
368
|
+
const summarized = result.content
|
|
369
|
+
.map((entry) => (entry.type === "text" && typeof entry.text === "string" ? entry.text : ""))
|
|
370
|
+
.join("\n")
|
|
371
|
+
.trim();
|
|
372
|
+
if (!summarized) return;
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
opts.eventStore.upsertResume(opts.sessionId, summarized, opts.eventCount);
|
|
376
|
+
} catch (e) {
|
|
377
|
+
(opts.platform as any).logger?.warn?.(
|
|
378
|
+
"supi-context-mode: failed to overwrite resume with summary",
|
|
379
|
+
e,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
53
382
|
}
|
|
54
383
|
|
|
55
384
|
/** Register supi-context-mode hooks on the platform */
|
|
@@ -58,8 +387,17 @@ export function registerContextModeHooks(platform: Platform, config: SupipowersC
|
|
|
58
387
|
|
|
59
388
|
let eventStore: EventStore | null = null;
|
|
60
389
|
let eventStorePath: string | null = null;
|
|
390
|
+
let metricsStore: MetricsStore | null = null;
|
|
391
|
+
let metricsStorePath: string | null = null;
|
|
392
|
+
let cacheStore: CacheStore | null = null;
|
|
393
|
+
let cacheStorePath: string | null = null;
|
|
394
|
+
let memoryStore: MemoryStore | null = null;
|
|
395
|
+
let memoryStorePath: string | null = null;
|
|
396
|
+
let knowledgeStore: KnowledgeStore | null = null;
|
|
397
|
+
let knowledgeStorePath: string | null = null;
|
|
61
398
|
let sessionCwd = process.cwd();
|
|
62
399
|
let sessionId = deriveSessionId();
|
|
400
|
+
let dedupState: DedupState = createDedupState();
|
|
63
401
|
|
|
64
402
|
const ensureEventStore = (cwd: string): EventStore | null => {
|
|
65
403
|
if (!config.contextMode.eventTracking) return null;
|
|
@@ -76,7 +414,7 @@ export function registerContextModeHooks(platform: Platform, config: SupipowersC
|
|
|
76
414
|
}
|
|
77
415
|
|
|
78
416
|
try {
|
|
79
|
-
mkdirSync(platform.paths
|
|
417
|
+
mkdirSync(getProjectStatePath(platform.paths, cwd, "sessions"), { recursive: true });
|
|
80
418
|
eventStore = new EventStore(dbPath);
|
|
81
419
|
eventStore.init();
|
|
82
420
|
eventStore.pruneOldSessions(7);
|
|
@@ -92,44 +430,193 @@ export function registerContextModeHooks(platform: Platform, config: SupipowersC
|
|
|
92
430
|
}
|
|
93
431
|
};
|
|
94
432
|
|
|
433
|
+
/** Mirror of ensureEventStore for the metrics sidecar; cwd-keyed.
|
|
434
|
+
* Closes any prior store when the cwd changes so we never write rows to
|
|
435
|
+
* the wrong project's metrics.db. */
|
|
436
|
+
const ensureMetricsStore = (cwd: string): MetricsStore | null => {
|
|
437
|
+
const dbPath = join(getProjectStatePath(platform.paths, cwd, "sessions"), "metrics.db");
|
|
438
|
+
if (metricsStore && metricsStorePath === dbPath) return metricsStore;
|
|
439
|
+
|
|
440
|
+
if (metricsStore) {
|
|
441
|
+
try {
|
|
442
|
+
metricsStore.close();
|
|
443
|
+
} catch {
|
|
444
|
+
// Best effort: we are about to reopen against the active project's metrics.db.
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
try {
|
|
449
|
+
mkdirSync(getProjectStatePath(platform.paths, cwd, "sessions"), { recursive: true });
|
|
450
|
+
const slug = basename(getProjectStateDir(platform.paths, cwd));
|
|
451
|
+
metricsStore = new MetricsStore({ dbPath, projectSlug: slug });
|
|
452
|
+
metricsStore.init();
|
|
453
|
+
metricsStorePath = dbPath;
|
|
454
|
+
__setMetricsStoreForTest(metricsStore);
|
|
455
|
+
return metricsStore;
|
|
456
|
+
} catch (e) {
|
|
457
|
+
metricsStore = null;
|
|
458
|
+
metricsStorePath = null;
|
|
459
|
+
__setMetricsStoreForTest(null);
|
|
460
|
+
(platform as any).logger?.error?.("supi-context-mode: failed to initialize metrics store", e);
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const ensureCacheStore = (cwd: string): CacheStore | null => {
|
|
466
|
+
const sessionsDir = getProjectStatePath(platform.paths, cwd, "sessions");
|
|
467
|
+
const dbPath = join(sessionsDir, "cache.db");
|
|
468
|
+
const payloadRoot = join(sessionsDir, "cache-payloads");
|
|
469
|
+
if (cacheStore && cacheStorePath === dbPath) {
|
|
470
|
+
cacheStore.setMetricsRecorder(metricsStore, sessionId);
|
|
471
|
+
return cacheStore;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (cacheStore) {
|
|
475
|
+
try {
|
|
476
|
+
cacheStore.close();
|
|
477
|
+
} catch {
|
|
478
|
+
// Best effort: we are about to reopen against the active project's cache.db.
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
mkdirSync(sessionsDir, { recursive: true });
|
|
484
|
+
const slug = basename(getProjectStateDir(platform.paths, cwd));
|
|
485
|
+
cacheStore = new CacheStore({
|
|
486
|
+
dbPath,
|
|
487
|
+
payloadRoot,
|
|
488
|
+
projectSlug: slug,
|
|
489
|
+
metricsStore,
|
|
490
|
+
metricsSessionId: sessionId,
|
|
491
|
+
});
|
|
492
|
+
cacheStore.init();
|
|
493
|
+
cacheStorePath = dbPath;
|
|
494
|
+
_cacheStoreRef = cacheStore;
|
|
495
|
+
return cacheStore;
|
|
496
|
+
} catch (e) {
|
|
497
|
+
cacheStore = null;
|
|
498
|
+
cacheStorePath = null;
|
|
499
|
+
_cacheStoreRef = null;
|
|
500
|
+
(platform as any).logger?.error?.("supi-context-mode: failed to initialize cache store", e);
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
const ensureMemoryStore = (cwd: string): MemoryStore | null => {
|
|
506
|
+
if (!config.contextMode.memory.enabled) return null;
|
|
507
|
+
const sessionsDir = getProjectStatePath(platform.paths, cwd, "sessions");
|
|
508
|
+
const dbPath = join(sessionsDir, "memory.db");
|
|
509
|
+
if (memoryStore && memoryStorePath === dbPath) return memoryStore;
|
|
510
|
+
|
|
511
|
+
if (memoryStore) {
|
|
512
|
+
try { memoryStore.close(); } catch { /* best effort */ }
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
try {
|
|
516
|
+
mkdirSync(sessionsDir, { recursive: true });
|
|
517
|
+
const slug = basename(getProjectStateDir(platform.paths, cwd));
|
|
518
|
+
memoryStore = new MemoryStore({ dbPath, projectSlug: slug });
|
|
519
|
+
memoryStore.init();
|
|
520
|
+
memoryStorePath = dbPath;
|
|
521
|
+
_setMemoryStoreRef(memoryStore);
|
|
522
|
+
return memoryStore;
|
|
523
|
+
} catch (e) {
|
|
524
|
+
memoryStore = null;
|
|
525
|
+
memoryStorePath = null;
|
|
526
|
+
_setMemoryStoreRef(null);
|
|
527
|
+
(platform as any).logger?.error?.("supi-context-mode: failed to initialize memory store", e);
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
const ensureKnowledgeStore = (cwd: string): KnowledgeStore | null => {
|
|
534
|
+
const sessionsDir = getProjectStatePath(platform.paths, cwd, "sessions");
|
|
535
|
+
const dbPath = join(sessionsDir, "knowledge.db");
|
|
536
|
+
if (knowledgeStore && knowledgeStorePath === dbPath) return knowledgeStore;
|
|
537
|
+
|
|
538
|
+
if (knowledgeStore) {
|
|
539
|
+
try {
|
|
540
|
+
knowledgeStore.close();
|
|
541
|
+
} catch {
|
|
542
|
+
// Best effort: we are about to reopen against the active project's knowledge.db.
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
try {
|
|
547
|
+
mkdirSync(sessionsDir, { recursive: true });
|
|
548
|
+
knowledgeStore = new KnowledgeStore(dbPath);
|
|
549
|
+
knowledgeStore.init();
|
|
550
|
+
knowledgeStorePath = dbPath;
|
|
551
|
+
_knowledgeStoreRef = knowledgeStore;
|
|
552
|
+
return knowledgeStore;
|
|
553
|
+
} catch (e) {
|
|
554
|
+
knowledgeStore = null;
|
|
555
|
+
knowledgeStorePath = null;
|
|
556
|
+
_knowledgeStoreRef = null;
|
|
557
|
+
(platform as any).logger?.error?.("supi-context-mode: failed to initialize knowledge store", e);
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
};
|
|
95
561
|
ensureEventStore(sessionCwd);
|
|
562
|
+
ensureMetricsStore(sessionCwd);
|
|
563
|
+
ensureCacheStore(sessionCwd);
|
|
564
|
+
ensureMemoryStore(sessionCwd);
|
|
565
|
+
const initialKnowledgeStore = ensureKnowledgeStore(sessionCwd);
|
|
96
566
|
|
|
97
567
|
_sessionIdRef = sessionId;
|
|
98
568
|
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
569
|
+
// Register native context-mode tools. Store-dependent knowledge tools are
|
|
570
|
+
// omitted when the knowledge DB cannot initialize, so routing never steers
|
|
571
|
+
// the agent toward ctx_search/ctx_index calls that cannot satisfy requests.
|
|
572
|
+
registerContextModeTools(platform, () => knowledgeStore, {
|
|
573
|
+
knowledgeToolsEnabled: initialKnowledgeStore !== null,
|
|
574
|
+
repomap: {
|
|
575
|
+
enabled: config.contextMode.repomap.enabled,
|
|
576
|
+
tokenBudget: config.contextMode.repomap.tokenBudget,
|
|
577
|
+
maxFiles: config.contextMode.repomap.maxFiles,
|
|
578
|
+
},
|
|
579
|
+
});
|
|
580
|
+
// Slice-2: register the UltraPlan hook bridge. The bridge is the only UltraPlan runtime module
|
|
581
|
+
// this file imports; business decisions (normalization, reducer, migration, repair, tracker
|
|
582
|
+
// storage) all live inside the bridge. When no canonical UltraPlan session is active, the
|
|
583
|
+
// bridge's handlers are no-ops.
|
|
584
|
+
registerUltraPlanHookBridge(platform);
|
|
116
585
|
|
|
117
586
|
platform.on("session_start", (_event, ctx) => {
|
|
118
587
|
sessionCwd = resolveSessionCwd(ctx as SessionContextLike | undefined);
|
|
119
588
|
sessionId = deriveSessionId(ctx as SessionContextLike | undefined);
|
|
120
589
|
_sessionIdRef = sessionId;
|
|
590
|
+
dedupState = createDedupState();
|
|
591
|
+
// Reset focus-chain turn counter so the next before_agent_start re-arms turn 1.
|
|
592
|
+
_focusChainTurnCounters.delete(sessionId);
|
|
121
593
|
|
|
122
594
|
const store = ensureEventStore(sessionCwd);
|
|
123
|
-
if (
|
|
595
|
+
if (store) {
|
|
596
|
+
try {
|
|
597
|
+
store.upsertMeta(sessionId, sessionCwd);
|
|
598
|
+
} catch (e) {
|
|
599
|
+
(platform as any).logger?.warn?.("supi-context-mode: failed to initialize session metadata", e);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
124
602
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
603
|
+
const metrics = ensureMetricsStore(sessionCwd);
|
|
604
|
+
if (metrics) {
|
|
605
|
+
try {
|
|
606
|
+
metrics.upsertSession({ session_id: sessionId, cwd: sessionCwd });
|
|
607
|
+
} catch (e) {
|
|
608
|
+
(platform as any).logger?.warn?.("supi-context-mode: failed to initialize metrics session metadata", e);
|
|
609
|
+
}
|
|
129
610
|
}
|
|
611
|
+
|
|
612
|
+
ensureCacheStore(sessionCwd);
|
|
613
|
+
ensureMemoryStore(sessionCwd);
|
|
614
|
+
ensureKnowledgeStore(sessionCwd);
|
|
130
615
|
});
|
|
131
616
|
|
|
132
617
|
platform.on("session_shutdown", () => {
|
|
618
|
+
dedupState = createDedupState();
|
|
619
|
+
_forgetAutoIndexSession(sessionId);
|
|
133
620
|
// Close knowledge store
|
|
134
621
|
if (knowledgeStore) {
|
|
135
622
|
try {
|
|
@@ -139,9 +626,78 @@ export function registerContextModeHooks(platform: Platform, config: SupipowersC
|
|
|
139
626
|
} finally {
|
|
140
627
|
knowledgeStore = null;
|
|
141
628
|
_knowledgeStoreRef = null;
|
|
629
|
+
knowledgeStorePath = null;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// Promote high-priority observations into memory before closing event store.
|
|
633
|
+
if (memoryStore && eventStore && config.contextMode.memory.enabled) {
|
|
634
|
+
try {
|
|
635
|
+
const events = eventStore.getEvents(sessionId, {
|
|
636
|
+
categories: ["decision", "task", "intent", "rule"],
|
|
637
|
+
limit: 50,
|
|
638
|
+
});
|
|
639
|
+
for (const event of events) {
|
|
640
|
+
let data: any = null;
|
|
641
|
+
try { data = JSON.parse(event.data); } catch { /* skip malformed payloads */ }
|
|
642
|
+
if (!data) continue;
|
|
643
|
+
const body = pickMemoryBody(event.category, data);
|
|
644
|
+
if (!body) continue;
|
|
645
|
+
memoryStore.put({
|
|
646
|
+
ownerScope: "project",
|
|
647
|
+
type: memoryTypeFor(event.category),
|
|
648
|
+
body,
|
|
649
|
+
priority: event.priority,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
} catch (e) {
|
|
653
|
+
(platform as any).logger?.warn?.("supi-context-mode: memory promotion failed", e);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Prune + close cache store before metrics so L3 prune rows can be recorded.
|
|
658
|
+
if (cacheStore) {
|
|
659
|
+
try {
|
|
660
|
+
cacheStore.setMetricsRecorder(metricsStore, sessionId);
|
|
661
|
+
cacheStore.pruneOldSessions(7);
|
|
662
|
+
cacheStore.close();
|
|
663
|
+
} catch (e) {
|
|
664
|
+
(platform as any).logger?.warn?.("supi-context-mode: failed to close cache store", e);
|
|
665
|
+
} finally {
|
|
666
|
+
cacheStore = null;
|
|
667
|
+
cacheStorePath = null;
|
|
668
|
+
_cacheStoreRef = null;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
// Prune + close metrics store independently of event store status.
|
|
674
|
+
if (metricsStore) {
|
|
675
|
+
try {
|
|
676
|
+
metricsStore.pruneOldSessions(7);
|
|
677
|
+
metricsStore.close();
|
|
678
|
+
} catch (e) {
|
|
679
|
+
(platform as any).logger?.warn?.("supi-context-mode: failed to close metrics store", e);
|
|
680
|
+
} finally {
|
|
681
|
+
metricsStore = null;
|
|
682
|
+
metricsStorePath = null;
|
|
683
|
+
__setMetricsStoreForTest(null);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (memoryStore) {
|
|
688
|
+
try {
|
|
689
|
+
memoryStore.pruneOld(config.contextMode.memory.retentionDays);
|
|
690
|
+
memoryStore.close();
|
|
691
|
+
} catch (e) {
|
|
692
|
+
(platform as any).logger?.warn?.("supi-context-mode: failed to close memory store", e);
|
|
693
|
+
} finally {
|
|
694
|
+
memoryStore = null;
|
|
695
|
+
memoryStorePath = null;
|
|
696
|
+
_setMemoryStoreRef(null);
|
|
142
697
|
}
|
|
143
698
|
}
|
|
144
699
|
|
|
700
|
+
|
|
145
701
|
if (!eventStore) {
|
|
146
702
|
_eventStoreRef = null;
|
|
147
703
|
_sessionIdRef = "";
|
|
@@ -163,19 +719,121 @@ export function registerContextModeHooks(platform: Platform, config: SupipowersC
|
|
|
163
719
|
|
|
164
720
|
// Phase 1: Result compression + Phase 2: Event extraction
|
|
165
721
|
platform.on("tool_result", (event) => {
|
|
166
|
-
|
|
167
|
-
|
|
722
|
+
const projectSlug = basename(getProjectStateDir(platform.paths, sessionCwd));
|
|
723
|
+
|
|
724
|
+
// Phase 1: compression + forward-only same-source dedup
|
|
725
|
+
const pipeline = runEmissionPipeline(event, config.contextMode.compressionThreshold, {
|
|
726
|
+
processors: config.contextMode.processors,
|
|
727
|
+
});
|
|
728
|
+
let compressed = pipeline.result;
|
|
729
|
+
let processorKey = pipeline.processorKey;
|
|
730
|
+
const sourceHash = uniqueSourceHash({
|
|
731
|
+
tool: event.toolName,
|
|
732
|
+
input: event.input,
|
|
733
|
+
cwd: sessionCwd,
|
|
734
|
+
projectSlug,
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
try {
|
|
738
|
+
const processedBytes = compressed?.content
|
|
739
|
+
? new TextEncoder().encode(combinedTextOf(compressed.content)).byteLength
|
|
740
|
+
: 0;
|
|
741
|
+
const deduped = maybeSubstitute({
|
|
742
|
+
result: compressed,
|
|
743
|
+
processorKey,
|
|
744
|
+
sourceHash,
|
|
745
|
+
dedupState,
|
|
746
|
+
processedBytes,
|
|
747
|
+
});
|
|
748
|
+
compressed = deduped.result;
|
|
749
|
+
processorKey = deduped.processorKey;
|
|
750
|
+
} catch (e) {
|
|
751
|
+
(platform as any).logger?.warn?.("supi-context-mode: dedup substitution failed", e);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Phase 3: optional L3 cache-handle spill for oversized current emissions.
|
|
755
|
+
if (config.contextMode.cacheHandles.enabled && cacheStore && !event.isError && isTextOnlyContent(event.content)) {
|
|
756
|
+
const finalContent = compressed?.content ?? event.content;
|
|
757
|
+
const finalText = combinedTextOf(finalContent);
|
|
758
|
+
const finalBytes = textContentBytes(finalContent);
|
|
759
|
+
const originalText = combinedTextOf(event.content);
|
|
760
|
+
if (
|
|
761
|
+
finalBytes > config.contextMode.cacheHandles.spillThresholdBytes
|
|
762
|
+
&& originalText.length > 0
|
|
763
|
+
&& !OMP_MINIMIZER_FOOTER_RE.test(finalText)
|
|
764
|
+
&& !OMP_MINIMIZER_FOOTER_RE.test(originalText)
|
|
765
|
+
) {
|
|
766
|
+
try {
|
|
767
|
+
const cached = cacheStore.putText({
|
|
768
|
+
sessionId,
|
|
769
|
+
text: originalText,
|
|
770
|
+
sourceTool: event.toolName,
|
|
771
|
+
sourceHash,
|
|
772
|
+
previewBytes: config.contextMode.cacheHandles.previewBytes,
|
|
773
|
+
recordMetric: false,
|
|
774
|
+
});
|
|
775
|
+
const replacementText = [
|
|
776
|
+
`Cached oversized ${event.toolName} result as ${cached.handle}.`,
|
|
777
|
+
`Original size: ${cached.sizeBytes} bytes. Preview below is bounded to ${config.contextMode.cacheHandles.previewBytes} chars.`,
|
|
778
|
+
`Open the full payload with ctx_open_cached(handle: "${cached.handle}", offset: 0, limit: <chars>).`,
|
|
779
|
+
"",
|
|
780
|
+
"--- preview ---",
|
|
781
|
+
cached.preview,
|
|
782
|
+
].join("\n");
|
|
783
|
+
compressed = { content: [{ type: "text", text: replacementText }] };
|
|
784
|
+
processorKey = "cache-spill";
|
|
785
|
+
} catch (e) {
|
|
786
|
+
(platform as any).logger?.warn?.("supi-context-mode: cache spill failed", e);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
168
790
|
|
|
169
791
|
// Phase 2: event extraction (fire-and-forget)
|
|
170
792
|
if (eventStore && config.contextMode.eventTracking) {
|
|
171
793
|
try {
|
|
172
|
-
const events = extractEvents(event, sessionId);
|
|
794
|
+
const events = extractEvents(event, sessionId, sourceHash);
|
|
173
795
|
if (events.length > 0) eventStore.writeEvents(events);
|
|
174
796
|
} catch (e) {
|
|
175
797
|
(platform as any).logger?.warn?.("supi-context-mode: event extraction failed", e);
|
|
176
798
|
}
|
|
177
799
|
}
|
|
178
800
|
|
|
801
|
+
// Metrics recording (fire-and-forget; never throws back to the agent).
|
|
802
|
+
if (metricsStore) {
|
|
803
|
+
try {
|
|
804
|
+
const usage = (() => {
|
|
805
|
+
try {
|
|
806
|
+
const raw = (event as any).contextUsage
|
|
807
|
+
?? (event as any).context
|
|
808
|
+
?? null;
|
|
809
|
+
if (!raw || typeof raw !== "object") return null;
|
|
810
|
+
return {
|
|
811
|
+
tokens: typeof raw.tokens === "number" ? raw.tokens : null,
|
|
812
|
+
contextWindow: typeof raw.contextWindow === "number" ? raw.contextWindow : null,
|
|
813
|
+
percent: typeof raw.percent === "number" ? raw.percent : null,
|
|
814
|
+
};
|
|
815
|
+
} catch {
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
})();
|
|
819
|
+
const row = toMetricRow({
|
|
820
|
+
event,
|
|
821
|
+
compressed,
|
|
822
|
+
sessionId,
|
|
823
|
+
cwd: sessionCwd,
|
|
824
|
+
projectSlug,
|
|
825
|
+
contextUsage: usage,
|
|
826
|
+
ts: Date.now(),
|
|
827
|
+
processorKey,
|
|
828
|
+
sourceHash,
|
|
829
|
+
layer: processorKey === "cache-spill" ? "L3" : "L2",
|
|
830
|
+
});
|
|
831
|
+
metricsStore.record(row);
|
|
832
|
+
} catch (e) {
|
|
833
|
+
(platform as any).logger?.warn?.("supi-context-mode: metrics record failed", e);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
179
837
|
return compressed;
|
|
180
838
|
});
|
|
181
839
|
|
|
@@ -192,7 +850,7 @@ export function registerContextModeHooks(platform: Platform, config: SupipowersC
|
|
|
192
850
|
});
|
|
193
851
|
|
|
194
852
|
// Phase 1: Routing instructions + Phase 2: Prompt event extraction
|
|
195
|
-
platform.on("before_agent_start", (event) => {
|
|
853
|
+
platform.on("before_agent_start", (event, ctx) => {
|
|
196
854
|
// Phase 2: prompt event extraction (fire-and-forget)
|
|
197
855
|
if (eventStore && config.contextMode.eventTracking) {
|
|
198
856
|
try {
|
|
@@ -206,16 +864,39 @@ export function registerContextModeHooks(platform: Platform, config: SupipowersC
|
|
|
206
864
|
}
|
|
207
865
|
}
|
|
208
866
|
|
|
209
|
-
// Phase 1
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
867
|
+
// Phase 1 routing instructions are gated by config; memory + focus chain are always considered.
|
|
868
|
+
const routingEnabled = config.contextMode.routingInstructions || config.contextMode.enforceRouting;
|
|
869
|
+
const status = detectContextMode(platform.getActiveTools());
|
|
870
|
+
const guidance = routingEnabled ? buildActiveRoutingGuidance(status) : null;
|
|
871
|
+
const memoryBlock = config.mempalace?.enabled
|
|
872
|
+
? null
|
|
873
|
+
: buildMemoryInjectionBlock(memoryStore, sessionId, config.contextMode.memory);
|
|
874
|
+
// Increment per-session turn counter for focus-chain cadence gating.
|
|
875
|
+
// First call after session_start is turn 1 (always injects).
|
|
876
|
+
const prevTurn = _focusChainTurnCounters.get(sessionId) ?? 0;
|
|
877
|
+
const turnCount = prevTurn + 1;
|
|
878
|
+
_focusChainTurnCounters.set(sessionId, turnCount);
|
|
879
|
+
const focusBlock = buildFocusChainBlock(eventStore, sessionId, {
|
|
880
|
+
cadence: config.contextMode.memory.focusChainCadence,
|
|
881
|
+
turnCount,
|
|
882
|
+
});
|
|
883
|
+
const sections = [guidance, memoryBlock, focusBlock].filter((s): s is string => Boolean(s));
|
|
884
|
+
if (sections.length === 0) return;
|
|
885
|
+
const injection = sections.join("\n\n");
|
|
215
886
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
887
|
+
let systemPromptBlocks = normalizeSystemPromptBlocks((event as any).systemPrompt);
|
|
888
|
+
const getSystemPrompt = (ctx as any)?.getSystemPrompt;
|
|
889
|
+
if (typeof getSystemPrompt === "function") {
|
|
890
|
+
try {
|
|
891
|
+
const currentPrompt = getSystemPrompt();
|
|
892
|
+
if (currentPrompt != null) {
|
|
893
|
+
systemPromptBlocks = normalizeSystemPromptBlocks(currentPrompt);
|
|
894
|
+
}
|
|
895
|
+
} catch (e) {
|
|
896
|
+
(platform as any).logger?.warn?.("supi-context-mode: failed to read current system prompt", e);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return { systemPrompt: [...systemPromptBlocks, injection] };
|
|
219
900
|
});
|
|
220
901
|
|
|
221
902
|
// Phase 3: Compaction integration
|
|
@@ -229,7 +910,7 @@ export function registerContextModeHooks(platform: Platform, config: SupipowersC
|
|
|
229
910
|
// Non-fatal: metadata is supplementary
|
|
230
911
|
}
|
|
231
912
|
|
|
232
|
-
platform.on("session_before_compact", () => {
|
|
913
|
+
platform.on("session_before_compact", async () => {
|
|
233
914
|
// Re-detect MCP tools: they may have loaded since init
|
|
234
915
|
const status = cachedStatus ?? detectContextMode(platform.getActiveTools());
|
|
235
916
|
const searchAvailable = status.tools.ctxSearch;
|
|
@@ -250,25 +931,56 @@ export function registerContextModeHooks(platform: Platform, config: SupipowersC
|
|
|
250
931
|
// Non-fatal
|
|
251
932
|
}
|
|
252
933
|
|
|
934
|
+
let snapshot: string | null = null;
|
|
935
|
+
let eventCount = 0;
|
|
253
936
|
try {
|
|
254
|
-
const
|
|
937
|
+
const compactOverride = readCompactOverride(platform.paths, sessionCwd);
|
|
938
|
+
let built = buildResumeSnapshot(eventStore!, sessionId, {
|
|
255
939
|
compactCount,
|
|
256
940
|
searchTool,
|
|
257
941
|
searchAvailable,
|
|
258
942
|
});
|
|
943
|
+
if (compactOverride) {
|
|
944
|
+
built = `${compactOverride}\n\n${built ?? ""}`.trim();
|
|
945
|
+
}
|
|
259
946
|
|
|
260
|
-
// Persist to DB so it survives crashes
|
|
261
|
-
|
|
262
|
-
|
|
947
|
+
// Persist deterministic snapshot to DB so it survives crashes — this
|
|
948
|
+
// is the contract. The LLM step below is a best-effort improvement.
|
|
949
|
+
if (built) {
|
|
950
|
+
eventCount = Object.values(eventStore!.getEventCounts(sessionId))
|
|
263
951
|
.reduce((a, b) => a + b, 0);
|
|
264
|
-
eventStore!.upsertResume(sessionId,
|
|
952
|
+
eventStore!.upsertResume(sessionId, built, eventCount);
|
|
953
|
+
snapshot = built;
|
|
265
954
|
}
|
|
266
|
-
|
|
267
|
-
return undefined; // don't cancel or replace compaction
|
|
268
955
|
} catch (e) {
|
|
269
956
|
(platform as any).logger?.warn?.("context-mode: snapshot build failed", e);
|
|
270
957
|
return undefined;
|
|
271
958
|
}
|
|
959
|
+
|
|
960
|
+
// Best-effort LLM summarization, gated by config + size. Failures keep
|
|
961
|
+
// the deterministic snapshot already persisted above.
|
|
962
|
+
if (
|
|
963
|
+
snapshot
|
|
964
|
+
&& config.contextMode.llmSummarization
|
|
965
|
+
&& byteLengthOf(snapshot) > config.contextMode.llmThreshold
|
|
966
|
+
) {
|
|
967
|
+
try {
|
|
968
|
+
await summarizeSnapshotIfBudget({
|
|
969
|
+
platform,
|
|
970
|
+
cwd: sessionCwd,
|
|
971
|
+
sessionId,
|
|
972
|
+
snapshot,
|
|
973
|
+
eventCount,
|
|
974
|
+
eventStore: eventStore!,
|
|
975
|
+
compressionThreshold: config.contextMode.compressionThreshold,
|
|
976
|
+
llmThreshold: config.contextMode.llmThreshold,
|
|
977
|
+
});
|
|
978
|
+
} catch (e) {
|
|
979
|
+
(platform as any).logger?.warn?.("context-mode: LLM summarization failed", e);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
return undefined; // don't cancel or replace compaction
|
|
272
984
|
});
|
|
273
985
|
|
|
274
986
|
platform.on("session_compact", () => {
|
|
@@ -309,6 +1021,24 @@ export function getEventStore(): EventStore | null {
|
|
|
309
1021
|
return _eventStoreRef;
|
|
310
1022
|
}
|
|
311
1023
|
|
|
1024
|
+
/** Get the metrics store instance (for use by /supi:context, ctx_stats, /supi:clear). */
|
|
1025
|
+
export { getMetricsStore } from "./metrics-store.js";
|
|
1026
|
+
|
|
1027
|
+
/** Get the active knowledge store (for use by /supi:clear and scoped context reset). */
|
|
1028
|
+
export function getKnowledgeStore(): KnowledgeStore | null {
|
|
1029
|
+
return _knowledgeStoreRef;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/** Get the cache store instance (for use by ctx_open_cached and /supi:clear). */
|
|
1033
|
+
export function getCacheStore(): CacheStore | null {
|
|
1034
|
+
return _cacheStoreRef;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/** Test-only cache store setter for tool/command tests. */
|
|
1038
|
+
export function __setCacheStoreForTest(store: CacheStore | null): void {
|
|
1039
|
+
_cacheStoreRef = store;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
312
1042
|
/** Get the session ID (for use by compaction hooks) */
|
|
313
1043
|
export function getSessionId(): string {
|
|
314
1044
|
return _sessionIdRef;
|
|
@@ -317,12 +1047,24 @@ export function getSessionId(): string {
|
|
|
317
1047
|
// Module-level refs updated by registerContextModeHooks
|
|
318
1048
|
let _eventStoreRef: EventStore | null = null;
|
|
319
1049
|
let _knowledgeStoreRef: KnowledgeStore | null = null;
|
|
1050
|
+
let _cacheStoreRef: CacheStore | null = null;
|
|
320
1051
|
let _sessionIdRef = "";
|
|
321
1052
|
|
|
1053
|
+
/**
|
|
1054
|
+
* Per-session turn counter for focus-chain cadence gating. Held in-memory
|
|
1055
|
+
* only — see L5 design spec D5: loss across crashes is acceptable, worst case
|
|
1056
|
+
* is one extra reinjection on resume. Keyed by sessionId.
|
|
1057
|
+
*/
|
|
1058
|
+
const _focusChainTurnCounters = new Map<string, number>();
|
|
1059
|
+
|
|
322
1060
|
/** Reset cached state (for testing) */
|
|
323
1061
|
export function _resetCache(): void {
|
|
324
1062
|
cachedStatus = null;
|
|
325
1063
|
_eventStoreRef = null;
|
|
326
1064
|
_knowledgeStoreRef = null;
|
|
1065
|
+
_cacheStoreRef = null;
|
|
327
1066
|
_sessionIdRef = "";
|
|
1067
|
+
_focusChainTurnCounters.clear();
|
|
1068
|
+
_resetMetricsStoreCache();
|
|
1069
|
+
_resetAutoIndexAttempts();
|
|
328
1070
|
}
|