fixo-cli 1.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/LICENSE +201 -0
- package/README.md +530 -0
- package/dist/agent/agent-client.d.ts +108 -0
- package/dist/agent/agent-client.d.ts.map +1 -0
- package/dist/agent/agent-client.js +1247 -0
- package/dist/agent/agent-client.js.map +1 -0
- package/dist/agent/agent-pool.d.ts +20 -0
- package/dist/agent/agent-pool.d.ts.map +1 -0
- package/dist/agent/agent-pool.js +217 -0
- package/dist/agent/agent-pool.js.map +1 -0
- package/dist/agent/background-awareness.d.ts +55 -0
- package/dist/agent/background-awareness.d.ts.map +1 -0
- package/dist/agent/background-awareness.js +104 -0
- package/dist/agent/background-awareness.js.map +1 -0
- package/dist/agent/command-parser.d.ts +33 -0
- package/dist/agent/command-parser.d.ts.map +1 -0
- package/dist/agent/command-parser.js +120 -0
- package/dist/agent/command-parser.js.map +1 -0
- package/dist/agent/context-budget.d.ts +91 -0
- package/dist/agent/context-budget.d.ts.map +1 -0
- package/dist/agent/context-budget.js +219 -0
- package/dist/agent/context-budget.js.map +1 -0
- package/dist/agent/conversation.d.ts +190 -0
- package/dist/agent/conversation.d.ts.map +1 -0
- package/dist/agent/conversation.js +547 -0
- package/dist/agent/conversation.js.map +1 -0
- package/dist/agent/hooks.d.ts +72 -0
- package/dist/agent/hooks.d.ts.map +1 -0
- package/dist/agent/hooks.js +214 -0
- package/dist/agent/hooks.js.map +1 -0
- package/dist/agent/mcp-bridge.d.ts +13 -0
- package/dist/agent/mcp-bridge.d.ts.map +1 -0
- package/dist/agent/mcp-bridge.js +86 -0
- package/dist/agent/mcp-bridge.js.map +1 -0
- package/dist/agent/mcp-client.d.ts +24 -0
- package/dist/agent/mcp-client.d.ts.map +1 -0
- package/dist/agent/mcp-client.js +146 -0
- package/dist/agent/mcp-client.js.map +1 -0
- package/dist/agent/mcp-manager.d.ts +13 -0
- package/dist/agent/mcp-manager.d.ts.map +1 -0
- package/dist/agent/mcp-manager.js +84 -0
- package/dist/agent/mcp-manager.js.map +1 -0
- package/dist/agent/mcp-registry.d.ts +45 -0
- package/dist/agent/mcp-registry.d.ts.map +1 -0
- package/dist/agent/mcp-registry.js +98 -0
- package/dist/agent/mcp-registry.js.map +1 -0
- package/dist/agent/orchestrator.d.ts +14 -0
- package/dist/agent/orchestrator.d.ts.map +1 -0
- package/dist/agent/orchestrator.js +118 -0
- package/dist/agent/orchestrator.js.map +1 -0
- package/dist/agent/parser-adapter.d.ts +120 -0
- package/dist/agent/parser-adapter.d.ts.map +1 -0
- package/dist/agent/parser-adapter.js +265 -0
- package/dist/agent/parser-adapter.js.map +1 -0
- package/dist/agent/parsers/imports.d.ts +11 -0
- package/dist/agent/parsers/imports.d.ts.map +1 -0
- package/dist/agent/parsers/imports.js +94 -0
- package/dist/agent/parsers/imports.js.map +1 -0
- package/dist/agent/parsers/shell.d.ts +23 -0
- package/dist/agent/parsers/shell.d.ts.map +1 -0
- package/dist/agent/parsers/shell.js +200 -0
- package/dist/agent/parsers/shell.js.map +1 -0
- package/dist/agent/parsers/symbols.d.ts +17 -0
- package/dist/agent/parsers/symbols.d.ts.map +1 -0
- package/dist/agent/parsers/symbols.js +103 -0
- package/dist/agent/parsers/symbols.js.map +1 -0
- package/dist/agent/permissions.d.ts +65 -0
- package/dist/agent/permissions.d.ts.map +1 -0
- package/dist/agent/permissions.js +219 -0
- package/dist/agent/permissions.js.map +1 -0
- package/dist/agent/predictive-gate.d.ts +69 -0
- package/dist/agent/predictive-gate.d.ts.map +1 -0
- package/dist/agent/predictive-gate.js +128 -0
- package/dist/agent/predictive-gate.js.map +1 -0
- package/dist/agent/provider-cooldown.d.ts +144 -0
- package/dist/agent/provider-cooldown.d.ts.map +1 -0
- package/dist/agent/provider-cooldown.js +300 -0
- package/dist/agent/provider-cooldown.js.map +1 -0
- package/dist/agent/providers-manager.d.ts +109 -0
- package/dist/agent/providers-manager.d.ts.map +1 -0
- package/dist/agent/providers-manager.js +464 -0
- package/dist/agent/providers-manager.js.map +1 -0
- package/dist/agent/repo-map.d.ts +6 -0
- package/dist/agent/repo-map.d.ts.map +1 -0
- package/dist/agent/repo-map.js +221 -0
- package/dist/agent/repo-map.js.map +1 -0
- package/dist/agent/retry.d.ts +103 -0
- package/dist/agent/retry.d.ts.map +1 -0
- package/dist/agent/retry.js +276 -0
- package/dist/agent/retry.js.map +1 -0
- package/dist/agent/search/index.d.ts +61 -0
- package/dist/agent/search/index.d.ts.map +1 -0
- package/dist/agent/search/index.js +314 -0
- package/dist/agent/search/index.js.map +1 -0
- package/dist/agent/single-agent.d.ts +76 -0
- package/dist/agent/single-agent.d.ts.map +1 -0
- package/dist/agent/single-agent.js +697 -0
- package/dist/agent/single-agent.js.map +1 -0
- package/dist/agent/skills.d.ts +22 -0
- package/dist/agent/skills.d.ts.map +1 -0
- package/dist/agent/skills.js +139 -0
- package/dist/agent/skills.js.map +1 -0
- package/dist/agent/stream-glue.d.ts +85 -0
- package/dist/agent/stream-glue.d.ts.map +1 -0
- package/dist/agent/stream-glue.js +120 -0
- package/dist/agent/stream-glue.js.map +1 -0
- package/dist/agent/subagent.d.ts +72 -0
- package/dist/agent/subagent.d.ts.map +1 -0
- package/dist/agent/subagent.js +193 -0
- package/dist/agent/subagent.js.map +1 -0
- package/dist/agent/telemetry.d.ts +192 -0
- package/dist/agent/telemetry.d.ts.map +1 -0
- package/dist/agent/telemetry.js +400 -0
- package/dist/agent/telemetry.js.map +1 -0
- package/dist/agent/tokenizer.d.ts +42 -0
- package/dist/agent/tokenizer.d.ts.map +1 -0
- package/dist/agent/tokenizer.js +107 -0
- package/dist/agent/tokenizer.js.map +1 -0
- package/dist/agent/tool-executor.d.ts +289 -0
- package/dist/agent/tool-executor.d.ts.map +1 -0
- package/dist/agent/tool-executor.js +2519 -0
- package/dist/agent/tool-executor.js.map +1 -0
- package/dist/agent/web-impl.d.ts +2 -0
- package/dist/agent/web-impl.d.ts.map +1 -0
- package/dist/agent/web-impl.js +34 -0
- package/dist/agent/web-impl.js.map +1 -0
- package/dist/agent/web.d.ts +8 -0
- package/dist/agent/web.d.ts.map +1 -0
- package/dist/agent/web.js +8 -0
- package/dist/agent/web.js.map +1 -0
- package/dist/agent/worker-agent.d.ts +27 -0
- package/dist/agent/worker-agent.d.ts.map +1 -0
- package/dist/agent/worker-agent.js +503 -0
- package/dist/agent/worker-agent.js.map +1 -0
- package/dist/config.d.ts +162 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +138 -0
- package/dist/config.js.map +1 -0
- package/dist/context/fixo-md-watcher.d.ts +42 -0
- package/dist/context/fixo-md-watcher.d.ts.map +1 -0
- package/dist/context/fixo-md-watcher.js +126 -0
- package/dist/context/fixo-md-watcher.js.map +1 -0
- package/dist/context/fixo-md.d.ts +50 -0
- package/dist/context/fixo-md.d.ts.map +1 -0
- package/dist/context/fixo-md.js +118 -0
- package/dist/context/fixo-md.js.map +1 -0
- package/dist/context/todo.d.ts +65 -0
- package/dist/context/todo.d.ts.map +1 -0
- package/dist/context/todo.js +194 -0
- package/dist/context/todo.js.map +1 -0
- package/dist/git/git-manager.d.ts +33 -0
- package/dist/git/git-manager.d.ts.map +1 -0
- package/dist/git/git-manager.js +293 -0
- package/dist/git/git-manager.js.map +1 -0
- package/dist/git/git-ops.d.ts +10 -0
- package/dist/git/git-ops.d.ts.map +1 -0
- package/dist/git/git-ops.js +131 -0
- package/dist/git/git-ops.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +352 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer.d.ts +30 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +273 -0
- package/dist/indexer.js.map +1 -0
- package/dist/lsp/lsp-client.d.ts +24 -0
- package/dist/lsp/lsp-client.d.ts.map +1 -0
- package/dist/lsp/lsp-client.js +205 -0
- package/dist/lsp/lsp-client.js.map +1 -0
- package/dist/lsp/lsp-manager.d.ts +17 -0
- package/dist/lsp/lsp-manager.d.ts.map +1 -0
- package/dist/lsp/lsp-manager.js +154 -0
- package/dist/lsp/lsp-manager.js.map +1 -0
- package/dist/lsp/lsp-pre-save.d.ts +137 -0
- package/dist/lsp/lsp-pre-save.d.ts.map +1 -0
- package/dist/lsp/lsp-pre-save.js +245 -0
- package/dist/lsp/lsp-pre-save.js.map +1 -0
- package/dist/lsp/syntax-fallback.d.ts +83 -0
- package/dist/lsp/syntax-fallback.d.ts.map +1 -0
- package/dist/lsp/syntax-fallback.js +275 -0
- package/dist/lsp/syntax-fallback.js.map +1 -0
- package/dist/model-outcomes.d.ts +12 -0
- package/dist/model-outcomes.d.ts.map +1 -0
- package/dist/model-outcomes.js +46 -0
- package/dist/model-outcomes.js.map +1 -0
- package/dist/planner.d.ts +32 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +163 -0
- package/dist/planner.js.map +1 -0
- package/dist/project-memory.d.ts +29 -0
- package/dist/project-memory.d.ts.map +1 -0
- package/dist/project-memory.js +349 -0
- package/dist/project-memory.js.map +1 -0
- package/dist/review.d.ts +2 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +61 -0
- package/dist/review.js.map +1 -0
- package/dist/runtime/background-jobs.d.ts +97 -0
- package/dist/runtime/background-jobs.d.ts.map +1 -0
- package/dist/runtime/background-jobs.js +331 -0
- package/dist/runtime/background-jobs.js.map +1 -0
- package/dist/runtime/credential-vault.d.ts +124 -0
- package/dist/runtime/credential-vault.d.ts.map +1 -0
- package/dist/runtime/credential-vault.js +184 -0
- package/dist/runtime/credential-vault.js.map +1 -0
- package/dist/runtime/loop-trap.d.ts +197 -0
- package/dist/runtime/loop-trap.d.ts.map +1 -0
- package/dist/runtime/loop-trap.js +420 -0
- package/dist/runtime/loop-trap.js.map +1 -0
- package/dist/runtime/policy.d.ts +15 -0
- package/dist/runtime/policy.d.ts.map +1 -0
- package/dist/runtime/policy.js +60 -0
- package/dist/runtime/policy.js.map +1 -0
- package/dist/runtime/redaction.d.ts +66 -0
- package/dist/runtime/redaction.d.ts.map +1 -0
- package/dist/runtime/redaction.js +155 -0
- package/dist/runtime/redaction.js.map +1 -0
- package/dist/runtime/session-snapshots.d.ts +76 -0
- package/dist/runtime/session-snapshots.d.ts.map +1 -0
- package/dist/runtime/session-snapshots.js +166 -0
- package/dist/runtime/session-snapshots.js.map +1 -0
- package/dist/runtime/staging.d.ts +205 -0
- package/dist/runtime/staging.d.ts.map +1 -0
- package/dist/runtime/staging.js +526 -0
- package/dist/runtime/staging.js.map +1 -0
- package/dist/runtime/task-session.d.ts +95 -0
- package/dist/runtime/task-session.d.ts.map +1 -0
- package/dist/runtime/task-session.js +263 -0
- package/dist/runtime/task-session.js.map +1 -0
- package/dist/runtime/worktree.d.ts +55 -0
- package/dist/runtime/worktree.d.ts.map +1 -0
- package/dist/runtime/worktree.js +175 -0
- package/dist/runtime/worktree.js.map +1 -0
- package/dist/setup-wizard.d.ts +8 -0
- package/dist/setup-wizard.d.ts.map +1 -0
- package/dist/setup-wizard.js +73 -0
- package/dist/setup-wizard.js.map +1 -0
- package/dist/shared/content.d.ts +43 -0
- package/dist/shared/content.d.ts.map +1 -0
- package/dist/shared/content.js +61 -0
- package/dist/shared/content.js.map +1 -0
- package/dist/shared/types.d.ts +217 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +3 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/test-runner.d.ts +5 -0
- package/dist/test-runner.d.ts.map +1 -0
- package/dist/test-runner.js +42 -0
- package/dist/test-runner.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/ascii.d.ts +23 -0
- package/dist/ui/ascii.d.ts.map +1 -0
- package/dist/ui/ascii.js +45 -0
- package/dist/ui/ascii.js.map +1 -0
- package/dist/ui/colors.d.ts +111 -0
- package/dist/ui/colors.d.ts.map +1 -0
- package/dist/ui/colors.js +166 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/image-attach.d.ts +27 -0
- package/dist/ui/image-attach.d.ts.map +1 -0
- package/dist/ui/image-attach.js +100 -0
- package/dist/ui/image-attach.js.map +1 -0
- package/dist/ui/index.d.ts +18 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +18 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/markdown-stream.d.ts +91 -0
- package/dist/ui/markdown-stream.d.ts.map +1 -0
- package/dist/ui/markdown-stream.js +524 -0
- package/dist/ui/markdown-stream.js.map +1 -0
- package/dist/ui/plan-renderer.d.ts +36 -0
- package/dist/ui/plan-renderer.d.ts.map +1 -0
- package/dist/ui/plan-renderer.js +79 -0
- package/dist/ui/plan-renderer.js.map +1 -0
- package/dist/ui/prompt.d.ts +11 -0
- package/dist/ui/prompt.d.ts.map +1 -0
- package/dist/ui/prompt.js +1960 -0
- package/dist/ui/prompt.js.map +1 -0
- package/dist/ui/render-primitives.d.ts +117 -0
- package/dist/ui/render-primitives.d.ts.map +1 -0
- package/dist/ui/render-primitives.js +322 -0
- package/dist/ui/render-primitives.js.map +1 -0
- package/dist/ui/render.d.ts +133 -0
- package/dist/ui/render.d.ts.map +1 -0
- package/dist/ui/render.js +547 -0
- package/dist/ui/render.js.map +1 -0
- package/dist/ui/session-header.d.ts +30 -0
- package/dist/ui/session-header.d.ts.map +1 -0
- package/dist/ui/session-header.js +74 -0
- package/dist/ui/session-header.js.map +1 -0
- package/dist/workspace-guard.d.ts +68 -0
- package/dist/workspace-guard.d.ts.map +1 -0
- package/dist/workspace-guard.js +168 -0
- package/dist/workspace-guard.js.map +1 -0
- package/dist/workspace-lock.d.ts +27 -0
- package/dist/workspace-lock.d.ts.map +1 -0
- package/dist/workspace-lock.js +95 -0
- package/dist/workspace-lock.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop-Trap Defenses — sha256-based repetition detection + semantic
|
|
3
|
+
* target-frequency detection.
|
|
4
|
+
*
|
|
5
|
+
* The module owns two independent detectors that run side-by-side:
|
|
6
|
+
*
|
|
7
|
+
* 1. {@link LoopTrapDetector} — the original Pillar 1 detector.
|
|
8
|
+
* Operates on a three-layer composite fingerprint (tool-args,
|
|
9
|
+
* tool-result, workspace). A model that re-issues the *same*
|
|
10
|
+
* tool call with *the same* arguments on the *same* workspace
|
|
11
|
+
* triggers it.
|
|
12
|
+
*
|
|
13
|
+
* 2. {@link SemanticLoopDetector} — Pillar 2 of the post-mortem
|
|
14
|
+
* refit. Operates on the *resolved target path* of every file-
|
|
15
|
+
* touching tool call. Two tool calls with completely different
|
|
16
|
+
* arguments but the same target still trip this detector. The
|
|
17
|
+
* algorithm is the one specified in the architectural refit:
|
|
18
|
+
*
|
|
19
|
+
* F(p) = Σ 𝟙(P(Tᵢ) = p) over sliding window W of last N turns
|
|
20
|
+
*
|
|
21
|
+
* Defaults: windowSize = 5, triggerCount = 3 (warn), hardAbortCount
|
|
22
|
+
* = 6 (throw). All three are tunable.
|
|
23
|
+
*
|
|
24
|
+
* Both detectors throw a typed error class (LoopTrapAbortedError /
|
|
25
|
+
* SemanticLoopAbortedError) when they hard-abort. Both expose a
|
|
26
|
+
* static {@link toSafetyAlertDirective} helper that produces the
|
|
27
|
+
* non-negotiable system-prompt directive the caller should inject.
|
|
28
|
+
*
|
|
29
|
+
* The module is pure and dependency-free apart from node:crypto,
|
|
30
|
+
* node:fs, and node:path. The semantic detector is fully
|
|
31
|
+
* synchronous.
|
|
32
|
+
*/
|
|
33
|
+
/** A single turn's three-layer fingerprint. */
|
|
34
|
+
export interface LoopSnapshot {
|
|
35
|
+
/** 0-based index of the turn that produced this snapshot. */
|
|
36
|
+
readonly turnIndex: number;
|
|
37
|
+
/** sha256 of canonicalised tool-call arguments. */
|
|
38
|
+
readonly toolCallFingerprint: string;
|
|
39
|
+
/** sha256 of the tail of the tool result. */
|
|
40
|
+
readonly toolResultFingerprint: string;
|
|
41
|
+
/** sha256 of the (path, content-hash) walk over the workspace. */
|
|
42
|
+
readonly workspaceFingerprint: string;
|
|
43
|
+
/** ISO timestamp the snapshot was recorded. */
|
|
44
|
+
readonly ts: string;
|
|
45
|
+
}
|
|
46
|
+
/** Which layers contributed to a `trap-detected` verdict. */
|
|
47
|
+
export type LoopTrapLayer = 'tool-args' | 'tool-result' | 'workspace';
|
|
48
|
+
/** The detector's verdict for a turn. */
|
|
49
|
+
export type LoopTrapVerdict = {
|
|
50
|
+
readonly state: 'ok';
|
|
51
|
+
} | {
|
|
52
|
+
readonly state: 'trap-detected';
|
|
53
|
+
/** Composite fingerprint of the repeated turns. */
|
|
54
|
+
readonly fingerprint: string;
|
|
55
|
+
/** Layers that contributed to the detection. */
|
|
56
|
+
readonly layers: ReadonlyArray<LoopTrapLayer>;
|
|
57
|
+
/** Index of the turn that triggered the verdict. */
|
|
58
|
+
readonly turnIndex: number;
|
|
59
|
+
/** Number of consecutive equivalent turns. */
|
|
60
|
+
readonly consecutiveCount: number;
|
|
61
|
+
} | {
|
|
62
|
+
readonly state: 'hard-abort';
|
|
63
|
+
/** Composite fingerprint of the repeated turns. */
|
|
64
|
+
readonly fingerprint: string;
|
|
65
|
+
/** Number of consecutive equivalent turns. */
|
|
66
|
+
readonly consecutiveCount: number;
|
|
67
|
+
};
|
|
68
|
+
/** Tunable thresholds. Mirrored under `preferences.safety.loopTrap`. */
|
|
69
|
+
export interface LoopTrapPreferences {
|
|
70
|
+
/** Number of consecutive equivalent turns that triggers a directive. */
|
|
71
|
+
readonly triggerCount: number;
|
|
72
|
+
/** Number of consecutive equivalent turns that triggers a hard abort. */
|
|
73
|
+
readonly hardAbortCount: number;
|
|
74
|
+
/** Maximum number of tool-result bytes to include in the fingerprint. */
|
|
75
|
+
readonly toolResultTailBytes: number;
|
|
76
|
+
/** Hard cap on in-memory history to bound memory growth. */
|
|
77
|
+
readonly maxHistory: number;
|
|
78
|
+
}
|
|
79
|
+
/** Default preferences — safe and well-tested. */
|
|
80
|
+
export declare const DEFAULT_LOOP_TRAP_PREFS: LoopTrapPreferences;
|
|
81
|
+
/** Thrown when the loop trap fires its hard-abort threshold. */
|
|
82
|
+
export declare class LoopTrapAbortedError extends Error {
|
|
83
|
+
readonly compositeFingerprint: string;
|
|
84
|
+
readonly consecutiveCount: number;
|
|
85
|
+
constructor(compositeFingerprint: string, consecutiveCount: number);
|
|
86
|
+
}
|
|
87
|
+
/** Tool names whose `path` argument should be tracked by the semantic
|
|
88
|
+
* detector. Anything not in this set contributes 0 to F(p). */
|
|
89
|
+
export declare const SEMANTIC_LOOP_TARGET_TOOLS: ReadonlySet<string>;
|
|
90
|
+
/** Tunable thresholds for the semantic detector. */
|
|
91
|
+
export interface SemanticLoopPreferences {
|
|
92
|
+
/** Master kill-switch. */
|
|
93
|
+
readonly enabled: boolean;
|
|
94
|
+
/** Width of the sliding window W. */
|
|
95
|
+
readonly windowSize: number;
|
|
96
|
+
/** F(p) >= triggerCount → warn directive. */
|
|
97
|
+
readonly triggerCount: number;
|
|
98
|
+
/** F(p) >= hardAbortCount → throw. */
|
|
99
|
+
readonly hardAbortCount: number;
|
|
100
|
+
}
|
|
101
|
+
/** Default preferences — matches the architectural spec. */
|
|
102
|
+
export declare const DEFAULT_SEMANTIC_LOOP_PREFS: SemanticLoopPreferences;
|
|
103
|
+
/** A single resolved file access within the sliding window. */
|
|
104
|
+
export interface FileAccessRecord {
|
|
105
|
+
readonly turnIndex: number;
|
|
106
|
+
readonly tool: string;
|
|
107
|
+
/** Resolved absolute path. */
|
|
108
|
+
readonly target: string;
|
|
109
|
+
}
|
|
110
|
+
/** The semantic detector's verdict for a turn. */
|
|
111
|
+
export type SemanticLoopVerdict = {
|
|
112
|
+
readonly state: 'ok';
|
|
113
|
+
target: string;
|
|
114
|
+
count: number;
|
|
115
|
+
} | {
|
|
116
|
+
readonly state: 'warn';
|
|
117
|
+
target: string;
|
|
118
|
+
count: number;
|
|
119
|
+
windowSize: number;
|
|
120
|
+
} | {
|
|
121
|
+
readonly state: 'hard-abort';
|
|
122
|
+
target: string;
|
|
123
|
+
count: number;
|
|
124
|
+
windowSize: number;
|
|
125
|
+
};
|
|
126
|
+
/** Thrown when the semantic detector hard-aborts. */
|
|
127
|
+
export declare class SemanticLoopAbortedError extends Error {
|
|
128
|
+
readonly target: string;
|
|
129
|
+
readonly count: number;
|
|
130
|
+
readonly windowSize: number;
|
|
131
|
+
constructor(target: string, count: number, windowSize: number);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Canonicalise a tool-call argument object for stable hashing. Sorts
|
|
135
|
+
* keys, drops `undefined` values, and JSON-stringifies. Order of
|
|
136
|
+
* keys in the input does not affect the fingerprint.
|
|
137
|
+
*/
|
|
138
|
+
export declare function canonicaliseArgs(args: Record<string, unknown>): string;
|
|
139
|
+
export declare class LoopTrapDetector {
|
|
140
|
+
private readonly history;
|
|
141
|
+
private readonly prefs;
|
|
142
|
+
constructor(prefs?: LoopTrapPreferences);
|
|
143
|
+
fingerprintToolCall(args: Record<string, unknown>): string;
|
|
144
|
+
fingerprintToolResult(result: string): string;
|
|
145
|
+
fingerprintWorkspace(cwd: string, extraExclude?: ReadonlyArray<string>): Promise<string>;
|
|
146
|
+
getHistory(): ReadonlyArray<LoopSnapshot>;
|
|
147
|
+
reset(): void;
|
|
148
|
+
record(snapshot: LoopSnapshot): LoopTrapVerdict;
|
|
149
|
+
private compositeFingerprint;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Tracks the frequency of every file target across a sliding window
|
|
153
|
+
* of the last N turns. Two tool calls with completely different
|
|
154
|
+
* arguments but the same resolved target still collide here, which
|
|
155
|
+
* is exactly what the composite (Pillar 1) detector misses.
|
|
156
|
+
*
|
|
157
|
+
* The detector is independent of the agent loop and safe to call
|
|
158
|
+
* from synchronous code.
|
|
159
|
+
*/
|
|
160
|
+
export declare class SemanticLoopDetector {
|
|
161
|
+
private readonly window;
|
|
162
|
+
private readonly freq;
|
|
163
|
+
private readonly prefs;
|
|
164
|
+
constructor(prefs?: SemanticLoopPreferences);
|
|
165
|
+
/** Read-only view of the current sliding window. */
|
|
166
|
+
getWindow(): ReadonlyArray<FileAccessRecord>;
|
|
167
|
+
/** Frequency map (target → count in window). */
|
|
168
|
+
getFrequencies(): ReadonlyMap<string, number>;
|
|
169
|
+
/** Active preferences. Useful for callers that want to read the
|
|
170
|
+
* `enabled` flag without re-passing the config object. */
|
|
171
|
+
get preference(): SemanticLoopPreferences;
|
|
172
|
+
/** Wipe state — call after a successful compaction. */
|
|
173
|
+
reset(): void;
|
|
174
|
+
/**
|
|
175
|
+
* Record a tool call against a target. Returns the verdict:
|
|
176
|
+
*
|
|
177
|
+
* - 'ok' — frequency is below triggerCount.
|
|
178
|
+
* - 'warn' — frequency >= triggerCount. The caller should
|
|
179
|
+
* inject the [Safety-Alert] directive.
|
|
180
|
+
* - 'hard-abort' — frequency >= hardAbortCount. The caller
|
|
181
|
+
* should throw {@link SemanticLoopAbortedError}.
|
|
182
|
+
*
|
|
183
|
+
* Non-file tools (anything not in
|
|
184
|
+
* {@link SEMANTIC_LOOP_TARGET_TOOLS}) and tools without a `path`
|
|
185
|
+
* argument do not contribute to F(p).
|
|
186
|
+
*/
|
|
187
|
+
record(turnIndex: number, tool: string, args: Record<string, unknown>, cwd: string): SemanticLoopVerdict;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Build the non-negotiable [Safety-Alert] directive the caller
|
|
191
|
+
* should inject into the next system prompt when the semantic
|
|
192
|
+
* detector warns. The wording matches the architectural spec.
|
|
193
|
+
*/
|
|
194
|
+
export declare function toSafetyAlertDirective(verdict: SemanticLoopVerdict): string | null;
|
|
195
|
+
/** Build the [Loop-Trap] directive used by the composite detector. */
|
|
196
|
+
export declare function toLoopTrapDirective(verdict: LoopTrapVerdict): string | null;
|
|
197
|
+
//# sourceMappingURL=loop-trap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-trap.d.ts","sourceRoot":"","sources":["../../src/runtime/loop-trap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loop-Trap Defenses — sha256-based repetition detection + semantic
|
|
3
|
+
* target-frequency detection.
|
|
4
|
+
*
|
|
5
|
+
* The module owns two independent detectors that run side-by-side:
|
|
6
|
+
*
|
|
7
|
+
* 1. {@link LoopTrapDetector} — the original Pillar 1 detector.
|
|
8
|
+
* Operates on a three-layer composite fingerprint (tool-args,
|
|
9
|
+
* tool-result, workspace). A model that re-issues the *same*
|
|
10
|
+
* tool call with *the same* arguments on the *same* workspace
|
|
11
|
+
* triggers it.
|
|
12
|
+
*
|
|
13
|
+
* 2. {@link SemanticLoopDetector} — Pillar 2 of the post-mortem
|
|
14
|
+
* refit. Operates on the *resolved target path* of every file-
|
|
15
|
+
* touching tool call. Two tool calls with completely different
|
|
16
|
+
* arguments but the same target still trip this detector. The
|
|
17
|
+
* algorithm is the one specified in the architectural refit:
|
|
18
|
+
*
|
|
19
|
+
* F(p) = Σ 𝟙(P(Tᵢ) = p) over sliding window W of last N turns
|
|
20
|
+
*
|
|
21
|
+
* Defaults: windowSize = 5, triggerCount = 3 (warn), hardAbortCount
|
|
22
|
+
* = 6 (throw). All three are tunable.
|
|
23
|
+
*
|
|
24
|
+
* Both detectors throw a typed error class (LoopTrapAbortedError /
|
|
25
|
+
* SemanticLoopAbortedError) when they hard-abort. Both expose a
|
|
26
|
+
* static {@link toSafetyAlertDirective} helper that produces the
|
|
27
|
+
* non-negotiable system-prompt directive the caller should inject.
|
|
28
|
+
*
|
|
29
|
+
* The module is pure and dependency-free apart from node:crypto,
|
|
30
|
+
* node:fs, and node:path. The semantic detector is fully
|
|
31
|
+
* synchronous.
|
|
32
|
+
*/
|
|
33
|
+
import crypto from 'node:crypto';
|
|
34
|
+
import fs from 'node:fs';
|
|
35
|
+
import path from 'node:path';
|
|
36
|
+
/** Default preferences — safe and well-tested. */
|
|
37
|
+
export const DEFAULT_LOOP_TRAP_PREFS = {
|
|
38
|
+
triggerCount: 3,
|
|
39
|
+
hardAbortCount: 6,
|
|
40
|
+
toolResultTailBytes: 1024,
|
|
41
|
+
maxHistory: 64,
|
|
42
|
+
};
|
|
43
|
+
/** Thrown when the loop trap fires its hard-abort threshold. */
|
|
44
|
+
export class LoopTrapAbortedError extends Error {
|
|
45
|
+
compositeFingerprint;
|
|
46
|
+
consecutiveCount;
|
|
47
|
+
constructor(compositeFingerprint, consecutiveCount) {
|
|
48
|
+
super(`Loop-trap hard-abort after ${consecutiveCount} consecutive equivalent turns ` +
|
|
49
|
+
`(composite sha256: ${compositeFingerprint.slice(0, 16)}…).`);
|
|
50
|
+
this.name = 'LoopTrapAbortedError';
|
|
51
|
+
this.compositeFingerprint = compositeFingerprint;
|
|
52
|
+
this.consecutiveCount = consecutiveCount;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Public types — semantic (Pillar 2) detector
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
/** Tool names whose `path` argument should be tracked by the semantic
|
|
59
|
+
* detector. Anything not in this set contributes 0 to F(p). */
|
|
60
|
+
export const SEMANTIC_LOOP_TARGET_TOOLS = new Set([
|
|
61
|
+
'read_file',
|
|
62
|
+
'write_file',
|
|
63
|
+
'apply_patch',
|
|
64
|
+
'replace_range',
|
|
65
|
+
'insert_after',
|
|
66
|
+
'rename_file',
|
|
67
|
+
'delete_file',
|
|
68
|
+
]);
|
|
69
|
+
/** Default preferences — matches the architectural spec. */
|
|
70
|
+
export const DEFAULT_SEMANTIC_LOOP_PREFS = {
|
|
71
|
+
enabled: true,
|
|
72
|
+
windowSize: 5,
|
|
73
|
+
triggerCount: 3,
|
|
74
|
+
hardAbortCount: 6,
|
|
75
|
+
};
|
|
76
|
+
/** Thrown when the semantic detector hard-aborts. */
|
|
77
|
+
export class SemanticLoopAbortedError extends Error {
|
|
78
|
+
target;
|
|
79
|
+
count;
|
|
80
|
+
windowSize;
|
|
81
|
+
constructor(target, count, windowSize) {
|
|
82
|
+
super(`Semantic loop-trap hard-abort: target '${target}' was accessed ` +
|
|
83
|
+
`${count} times within the last ${windowSize} turns. ` +
|
|
84
|
+
`The agent is forbidden from accessing this file again.`);
|
|
85
|
+
this.name = 'SemanticLoopAbortedError';
|
|
86
|
+
this.target = target;
|
|
87
|
+
this.count = count;
|
|
88
|
+
this.windowSize = windowSize;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Helpers
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
const sha256 = (input) => crypto.createHash('sha256').update(input, 'utf-8').digest('hex');
|
|
95
|
+
const DEFAULT_WORKSPACE_EXCLUDES = [
|
|
96
|
+
'.fixo',
|
|
97
|
+
'.git',
|
|
98
|
+
'node_modules',
|
|
99
|
+
'dist',
|
|
100
|
+
'.next',
|
|
101
|
+
'out',
|
|
102
|
+
'build',
|
|
103
|
+
'coverage',
|
|
104
|
+
'.cache',
|
|
105
|
+
'.turbo',
|
|
106
|
+
];
|
|
107
|
+
/**
|
|
108
|
+
* Canonicalise a tool-call argument object for stable hashing. Sorts
|
|
109
|
+
* keys, drops `undefined` values, and JSON-stringifies. Order of
|
|
110
|
+
* keys in the input does not affect the fingerprint.
|
|
111
|
+
*/
|
|
112
|
+
export function canonicaliseArgs(args) {
|
|
113
|
+
const sortedKeys = Object.keys(args).sort();
|
|
114
|
+
const filtered = {};
|
|
115
|
+
for (const key of sortedKeys) {
|
|
116
|
+
const value = args[key];
|
|
117
|
+
if (value === undefined)
|
|
118
|
+
continue;
|
|
119
|
+
filtered[key] = value;
|
|
120
|
+
}
|
|
121
|
+
return JSON.stringify(filtered);
|
|
122
|
+
}
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// LoopTrapDetector (Pillar 1 — composite fingerprint)
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
export class LoopTrapDetector {
|
|
127
|
+
history = [];
|
|
128
|
+
prefs;
|
|
129
|
+
constructor(prefs = DEFAULT_LOOP_TRAP_PREFS) {
|
|
130
|
+
if (prefs.triggerCount < 1) {
|
|
131
|
+
throw new Error('LoopTrapPreferences.triggerCount must be >= 1');
|
|
132
|
+
}
|
|
133
|
+
if (prefs.hardAbortCount < prefs.triggerCount) {
|
|
134
|
+
throw new Error('LoopTrapPreferences.hardAbortCount must be >= triggerCount');
|
|
135
|
+
}
|
|
136
|
+
if (prefs.toolResultTailBytes < 64) {
|
|
137
|
+
throw new Error('LoopTrapPreferences.toolResultTailBytes must be >= 64');
|
|
138
|
+
}
|
|
139
|
+
if (prefs.maxHistory < prefs.hardAbortCount) {
|
|
140
|
+
throw new Error('LoopTrapPreferences.maxHistory must be >= hardAbortCount');
|
|
141
|
+
}
|
|
142
|
+
this.prefs = prefs;
|
|
143
|
+
}
|
|
144
|
+
fingerprintToolCall(args) {
|
|
145
|
+
return sha256(canonicaliseArgs(args));
|
|
146
|
+
}
|
|
147
|
+
fingerprintToolResult(result) {
|
|
148
|
+
const tail = result.length > this.prefs.toolResultTailBytes
|
|
149
|
+
? result.slice(result.length - this.prefs.toolResultTailBytes)
|
|
150
|
+
: result;
|
|
151
|
+
return sha256(tail);
|
|
152
|
+
}
|
|
153
|
+
async fingerprintWorkspace(cwd, extraExclude = []) {
|
|
154
|
+
const exclude = new Set([...DEFAULT_WORKSPACE_EXCLUDES, ...extraExclude]);
|
|
155
|
+
const root = path.resolve(cwd);
|
|
156
|
+
const entries = [];
|
|
157
|
+
const MAX_FILES = 100_000;
|
|
158
|
+
const walk = (dir) => {
|
|
159
|
+
if (entries.length >= MAX_FILES)
|
|
160
|
+
return false;
|
|
161
|
+
let names;
|
|
162
|
+
try {
|
|
163
|
+
names = fs.readdirSync(dir);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
for (const name of names) {
|
|
169
|
+
if (exclude.has(name))
|
|
170
|
+
continue;
|
|
171
|
+
if (entries.length >= MAX_FILES)
|
|
172
|
+
return false;
|
|
173
|
+
const full = path.join(dir, name);
|
|
174
|
+
let stat;
|
|
175
|
+
try {
|
|
176
|
+
stat = fs.lstatSync(full);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (stat.isSymbolicLink())
|
|
182
|
+
continue;
|
|
183
|
+
if (stat.isDirectory()) {
|
|
184
|
+
if (!walk(full))
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
else if (stat.isFile()) {
|
|
188
|
+
const rel = path.relative(root, full);
|
|
189
|
+
let content;
|
|
190
|
+
try {
|
|
191
|
+
content = fs.readFileSync(full);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
entries.push([rel, sha256(content.toString('binary'))]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
};
|
|
201
|
+
try {
|
|
202
|
+
walk(root);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// Unreadable root — return an empty fingerprint so we still
|
|
206
|
+
// hash the other two layers deterministically.
|
|
207
|
+
}
|
|
208
|
+
entries.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
209
|
+
const materialised = entries.map(([p, h]) => `${p}\t${h}`).join('\n');
|
|
210
|
+
return sha256(materialised);
|
|
211
|
+
}
|
|
212
|
+
getHistory() {
|
|
213
|
+
return this.history.slice();
|
|
214
|
+
}
|
|
215
|
+
reset() {
|
|
216
|
+
this.history.length = 0;
|
|
217
|
+
}
|
|
218
|
+
record(snapshot) {
|
|
219
|
+
this.history.push(snapshot);
|
|
220
|
+
if (this.history.length > this.prefs.maxHistory) {
|
|
221
|
+
this.history.splice(0, this.history.length - this.prefs.maxHistory);
|
|
222
|
+
}
|
|
223
|
+
const composite = this.compositeFingerprint(snapshot);
|
|
224
|
+
let consecutive = 1;
|
|
225
|
+
const layers = [];
|
|
226
|
+
if (this.history.length >= 2) {
|
|
227
|
+
const prev = this.history[this.history.length - 2];
|
|
228
|
+
if (prev.toolCallFingerprint === snapshot.toolCallFingerprint) {
|
|
229
|
+
layers.push('tool-args');
|
|
230
|
+
}
|
|
231
|
+
if (prev.toolResultFingerprint === snapshot.toolResultFingerprint) {
|
|
232
|
+
layers.push('tool-result');
|
|
233
|
+
}
|
|
234
|
+
if (prev.workspaceFingerprint === snapshot.workspaceFingerprint) {
|
|
235
|
+
layers.push('workspace');
|
|
236
|
+
}
|
|
237
|
+
for (let i = this.history.length - 2; i >= 0; i--) {
|
|
238
|
+
const h = this.history[i];
|
|
239
|
+
if (h.toolCallFingerprint === snapshot.toolCallFingerprint &&
|
|
240
|
+
h.toolResultFingerprint === snapshot.toolResultFingerprint &&
|
|
241
|
+
h.workspaceFingerprint === snapshot.workspaceFingerprint) {
|
|
242
|
+
consecutive += 1;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (consecutive >= this.prefs.hardAbortCount) {
|
|
250
|
+
return { state: 'hard-abort', fingerprint: composite, consecutiveCount: consecutive };
|
|
251
|
+
}
|
|
252
|
+
if (consecutive >= this.prefs.triggerCount) {
|
|
253
|
+
return {
|
|
254
|
+
state: 'trap-detected',
|
|
255
|
+
fingerprint: composite,
|
|
256
|
+
layers,
|
|
257
|
+
turnIndex: snapshot.turnIndex,
|
|
258
|
+
consecutiveCount: consecutive,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return { state: 'ok' };
|
|
262
|
+
}
|
|
263
|
+
compositeFingerprint(snapshot) {
|
|
264
|
+
return sha256(snapshot.toolCallFingerprint +
|
|
265
|
+
snapshot.toolResultFingerprint +
|
|
266
|
+
snapshot.workspaceFingerprint);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// SemanticLoopDetector (Pillar 2 — sliding-window target frequency)
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
/**
|
|
273
|
+
* Tracks the frequency of every file target across a sliding window
|
|
274
|
+
* of the last N turns. Two tool calls with completely different
|
|
275
|
+
* arguments but the same resolved target still collide here, which
|
|
276
|
+
* is exactly what the composite (Pillar 1) detector misses.
|
|
277
|
+
*
|
|
278
|
+
* The detector is independent of the agent loop and safe to call
|
|
279
|
+
* from synchronous code.
|
|
280
|
+
*/
|
|
281
|
+
export class SemanticLoopDetector {
|
|
282
|
+
window = [];
|
|
283
|
+
freq = new Map();
|
|
284
|
+
prefs;
|
|
285
|
+
constructor(prefs = DEFAULT_SEMANTIC_LOOP_PREFS) {
|
|
286
|
+
if (prefs.windowSize < 1) {
|
|
287
|
+
throw new Error('SemanticLoopPreferences.windowSize must be >= 1');
|
|
288
|
+
}
|
|
289
|
+
if (prefs.triggerCount < 1) {
|
|
290
|
+
throw new Error('SemanticLoopPreferences.triggerCount must be >= 1');
|
|
291
|
+
}
|
|
292
|
+
if (prefs.hardAbortCount < prefs.triggerCount) {
|
|
293
|
+
throw new Error('SemanticLoopPreferences.hardAbortCount must be >= triggerCount');
|
|
294
|
+
}
|
|
295
|
+
this.prefs = prefs;
|
|
296
|
+
}
|
|
297
|
+
/** Read-only view of the current sliding window. */
|
|
298
|
+
getWindow() {
|
|
299
|
+
return this.window.slice();
|
|
300
|
+
}
|
|
301
|
+
/** Frequency map (target → count in window). */
|
|
302
|
+
getFrequencies() {
|
|
303
|
+
return new Map(this.freq);
|
|
304
|
+
}
|
|
305
|
+
/** Active preferences. Useful for callers that want to read the
|
|
306
|
+
* `enabled` flag without re-passing the config object. */
|
|
307
|
+
get preference() {
|
|
308
|
+
return this.prefs;
|
|
309
|
+
}
|
|
310
|
+
/** Wipe state — call after a successful compaction. */
|
|
311
|
+
reset() {
|
|
312
|
+
this.window.length = 0;
|
|
313
|
+
this.freq.clear();
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Record a tool call against a target. Returns the verdict:
|
|
317
|
+
*
|
|
318
|
+
* - 'ok' — frequency is below triggerCount.
|
|
319
|
+
* - 'warn' — frequency >= triggerCount. The caller should
|
|
320
|
+
* inject the [Safety-Alert] directive.
|
|
321
|
+
* - 'hard-abort' — frequency >= hardAbortCount. The caller
|
|
322
|
+
* should throw {@link SemanticLoopAbortedError}.
|
|
323
|
+
*
|
|
324
|
+
* Non-file tools (anything not in
|
|
325
|
+
* {@link SEMANTIC_LOOP_TARGET_TOOLS}) and tools without a `path`
|
|
326
|
+
* argument do not contribute to F(p).
|
|
327
|
+
*/
|
|
328
|
+
record(turnIndex, tool, args, cwd) {
|
|
329
|
+
if (!this.prefs.enabled) {
|
|
330
|
+
return { state: 'ok', target: '', count: 0 };
|
|
331
|
+
}
|
|
332
|
+
if (!SEMANTIC_LOOP_TARGET_TOOLS.has(tool)) {
|
|
333
|
+
return { state: 'ok', target: '', count: 0 };
|
|
334
|
+
}
|
|
335
|
+
const rawTarget = pickTargetArg(tool, args);
|
|
336
|
+
if (!rawTarget) {
|
|
337
|
+
return { state: 'ok', target: '', count: 0 };
|
|
338
|
+
}
|
|
339
|
+
const resolved = safeResolve(cwd, rawTarget);
|
|
340
|
+
if (!resolved) {
|
|
341
|
+
return { state: 'ok', target: '', count: 0 };
|
|
342
|
+
}
|
|
343
|
+
// Slide the window: evict the oldest record if full.
|
|
344
|
+
if (this.window.length >= this.prefs.windowSize) {
|
|
345
|
+
const dropped = this.window.shift();
|
|
346
|
+
if (dropped) {
|
|
347
|
+
const cur = this.freq.get(dropped.target) ?? 1;
|
|
348
|
+
if (cur <= 1)
|
|
349
|
+
this.freq.delete(dropped.target);
|
|
350
|
+
else
|
|
351
|
+
this.freq.set(dropped.target, cur - 1);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const rec = { turnIndex, tool, target: resolved };
|
|
355
|
+
this.window.push(rec);
|
|
356
|
+
const f = (this.freq.get(resolved) ?? 0) + 1;
|
|
357
|
+
this.freq.set(resolved, f);
|
|
358
|
+
if (f >= this.prefs.hardAbortCount) {
|
|
359
|
+
return { state: 'hard-abort', target: resolved, count: f, windowSize: this.window.length };
|
|
360
|
+
}
|
|
361
|
+
if (f >= this.prefs.triggerCount) {
|
|
362
|
+
return { state: 'warn', target: resolved, count: f, windowSize: this.window.length };
|
|
363
|
+
}
|
|
364
|
+
return { state: 'ok', target: resolved, count: f };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
// Shared helpers
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
/** Pick the right argument to fingerprint for each tool. */
|
|
371
|
+
function pickTargetArg(tool, args) {
|
|
372
|
+
switch (tool) {
|
|
373
|
+
case 'read_file':
|
|
374
|
+
case 'write_file':
|
|
375
|
+
case 'replace_range':
|
|
376
|
+
case 'insert_after':
|
|
377
|
+
case 'delete_file':
|
|
378
|
+
return typeof args.path === 'string' ? args.path : null;
|
|
379
|
+
case 'rename_file':
|
|
380
|
+
return typeof args.to === 'string' ? args.to : typeof args.from === 'string' ? args.from : null;
|
|
381
|
+
case 'apply_patch': {
|
|
382
|
+
// apply_patch takes a unified diff — we can't resolve individual
|
|
383
|
+
// targets synchronously, so return null and let the caller
|
|
384
|
+
// fall back to the composite detector.
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
default:
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function safeResolve(cwd, target) {
|
|
392
|
+
try {
|
|
393
|
+
return path.resolve(cwd, target);
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Build the non-negotiable [Safety-Alert] directive the caller
|
|
401
|
+
* should inject into the next system prompt when the semantic
|
|
402
|
+
* detector warns. The wording matches the architectural spec.
|
|
403
|
+
*/
|
|
404
|
+
export function toSafetyAlertDirective(verdict) {
|
|
405
|
+
if (verdict.state !== 'warn' && verdict.state !== 'hard-abort')
|
|
406
|
+
return null;
|
|
407
|
+
return (`[Safety-Alert] You have queried or modified the target path '${verdict.target}' ` +
|
|
408
|
+
`more than 3 times in your recent sequence. Your current approach is looping. ` +
|
|
409
|
+
`You are forbidden from calling read_file or replace_file on this path in your next turn. ` +
|
|
410
|
+
`You must either analyze alternative file dependencies, consult different workspace ` +
|
|
411
|
+
`symbols, or request direct user guidance.`);
|
|
412
|
+
}
|
|
413
|
+
/** Build the [Loop-Trap] directive used by the composite detector. */
|
|
414
|
+
export function toLoopTrapDirective(verdict) {
|
|
415
|
+
if (verdict.state !== 'trap-detected')
|
|
416
|
+
return null;
|
|
417
|
+
return (`[Loop-Trap] Detected ${verdict.consecutiveCount} consecutive equivalent turns. ` +
|
|
418
|
+
`Reconsider your strategy before issuing the next tool call.`);
|
|
419
|
+
}
|
|
420
|
+
//# sourceMappingURL=loop-trap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-trap.js","sourceRoot":"","sources":["../../src/runtime/loop-trap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type PolicyProfile = 'read-only' | 'workspace-write' | 'shell-confirm' | 'trusted-project' | 'dangerous-deny';
|
|
2
|
+
export type RiskLevel = 'low' | 'medium' | 'high' | 'blocked';
|
|
3
|
+
export interface PolicyDecision {
|
|
4
|
+
allowed: boolean;
|
|
5
|
+
needsConfirmation: boolean;
|
|
6
|
+
risk: RiskLevel;
|
|
7
|
+
reason: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AllowRuleStore {
|
|
10
|
+
commands: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare const DANGEROUS_COMMANDS: RegExp[];
|
|
13
|
+
export declare function classifyCommand(command: string): RiskLevel;
|
|
14
|
+
export declare function decidePolicy(profile: PolicyProfile, action: 'read' | 'write' | 'delete' | 'command', detail?: string, allowRules?: AllowRuleStore): PolicyDecision;
|
|
15
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/runtime/policy.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GACrB,WAAW,GACX,iBAAiB,GACjB,eAAe,GACf,iBAAiB,GACjB,gBAAgB,CAAC;AAErB,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE9D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AASD,eAAO,MAAM,kBAAkB,UAa9B,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAQ1D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,EAAE,MAAM,SAAK,EAAE,UAAU,GAAE,cAAiC,GAAG,cAAc,CAwBhL"}
|