@provos/ironcurtain 0.1.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.
Files changed (160) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +311 -0
  3. package/dist/agent/index.d.ts +10 -0
  4. package/dist/agent/index.js +71 -0
  5. package/dist/agent/index.js.map +1 -0
  6. package/dist/agent/prompts.d.ts +5 -0
  7. package/dist/agent/prompts.js +26 -0
  8. package/dist/agent/prompts.js.map +1 -0
  9. package/dist/agent/tools.d.ts +13 -0
  10. package/dist/agent/tools.js +51 -0
  11. package/dist/agent/tools.js.map +1 -0
  12. package/dist/cli.d.ts +2 -0
  13. package/dist/cli.js +78 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config/constitution.md +16 -0
  16. package/dist/config/generated/compiled-policy.json +236 -0
  17. package/dist/config/generated/test-scenarios.json +765 -0
  18. package/dist/config/generated/tool-annotations.json +955 -0
  19. package/dist/config/index.d.ts +25 -0
  20. package/dist/config/index.js +151 -0
  21. package/dist/config/index.js.map +1 -0
  22. package/dist/config/mcp-servers.json +22 -0
  23. package/dist/config/model-provider.d.ts +49 -0
  24. package/dist/config/model-provider.js +78 -0
  25. package/dist/config/model-provider.js.map +1 -0
  26. package/dist/config/paths.d.ts +59 -0
  27. package/dist/config/paths.js +96 -0
  28. package/dist/config/paths.js.map +1 -0
  29. package/dist/config/types.d.ts +89 -0
  30. package/dist/config/types.js +2 -0
  31. package/dist/config/types.js.map +1 -0
  32. package/dist/config/user-config.d.ts +93 -0
  33. package/dist/config/user-config.js +309 -0
  34. package/dist/config/user-config.js.map +1 -0
  35. package/dist/hash.d.ts +17 -0
  36. package/dist/hash.js +34 -0
  37. package/dist/hash.js.map +1 -0
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.js +61 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/logger.d.ts +11 -0
  42. package/dist/logger.js +93 -0
  43. package/dist/logger.js.map +1 -0
  44. package/dist/pipeline/annotate.d.ts +9 -0
  45. package/dist/pipeline/annotate.js +136 -0
  46. package/dist/pipeline/annotate.js.map +1 -0
  47. package/dist/pipeline/compile.d.ts +23 -0
  48. package/dist/pipeline/compile.js +386 -0
  49. package/dist/pipeline/compile.js.map +1 -0
  50. package/dist/pipeline/constitution-compiler.d.ts +22 -0
  51. package/dist/pipeline/constitution-compiler.js +197 -0
  52. package/dist/pipeline/constitution-compiler.js.map +1 -0
  53. package/dist/pipeline/generate-with-repair.d.ts +22 -0
  54. package/dist/pipeline/generate-with-repair.js +64 -0
  55. package/dist/pipeline/generate-with-repair.js.map +1 -0
  56. package/dist/pipeline/handwritten-scenarios.d.ts +9 -0
  57. package/dist/pipeline/handwritten-scenarios.js +321 -0
  58. package/dist/pipeline/handwritten-scenarios.js.map +1 -0
  59. package/dist/pipeline/llm-logger.d.ts +42 -0
  60. package/dist/pipeline/llm-logger.js +78 -0
  61. package/dist/pipeline/llm-logger.js.map +1 -0
  62. package/dist/pipeline/pipeline-shared.d.ts +47 -0
  63. package/dist/pipeline/pipeline-shared.js +145 -0
  64. package/dist/pipeline/pipeline-shared.js.map +1 -0
  65. package/dist/pipeline/policy-verifier.d.ts +46 -0
  66. package/dist/pipeline/policy-verifier.js +277 -0
  67. package/dist/pipeline/policy-verifier.js.map +1 -0
  68. package/dist/pipeline/scenario-generator.d.ts +11 -0
  69. package/dist/pipeline/scenario-generator.js +128 -0
  70. package/dist/pipeline/scenario-generator.js.map +1 -0
  71. package/dist/pipeline/tool-annotator.d.ts +24 -0
  72. package/dist/pipeline/tool-annotator.js +201 -0
  73. package/dist/pipeline/tool-annotator.js.map +1 -0
  74. package/dist/pipeline/types.d.ts +122 -0
  75. package/dist/pipeline/types.js +10 -0
  76. package/dist/pipeline/types.js.map +1 -0
  77. package/dist/sandbox/index.d.ts +39 -0
  78. package/dist/sandbox/index.js +178 -0
  79. package/dist/sandbox/index.js.map +1 -0
  80. package/dist/session/agent-session.d.ts +83 -0
  81. package/dist/session/agent-session.js +382 -0
  82. package/dist/session/agent-session.js.map +1 -0
  83. package/dist/session/cli-transport.d.ts +61 -0
  84. package/dist/session/cli-transport.js +320 -0
  85. package/dist/session/cli-transport.js.map +1 -0
  86. package/dist/session/errors.d.ts +19 -0
  87. package/dist/session/errors.js +33 -0
  88. package/dist/session/errors.js.map +1 -0
  89. package/dist/session/index.d.ts +29 -0
  90. package/dist/session/index.js +104 -0
  91. package/dist/session/index.js.map +1 -0
  92. package/dist/session/message-compactor.d.ts +32 -0
  93. package/dist/session/message-compactor.js +81 -0
  94. package/dist/session/message-compactor.js.map +1 -0
  95. package/dist/session/prompts.d.ts +5 -0
  96. package/dist/session/prompts.js +62 -0
  97. package/dist/session/prompts.js.map +1 -0
  98. package/dist/session/resource-budget-tracker.d.ts +124 -0
  99. package/dist/session/resource-budget-tracker.js +327 -0
  100. package/dist/session/resource-budget-tracker.js.map +1 -0
  101. package/dist/session/step-loop-detector.d.ts +63 -0
  102. package/dist/session/step-loop-detector.js +136 -0
  103. package/dist/session/step-loop-detector.js.map +1 -0
  104. package/dist/session/transport.d.ts +24 -0
  105. package/dist/session/transport.js +2 -0
  106. package/dist/session/transport.js.map +1 -0
  107. package/dist/session/truncate-result.d.ts +35 -0
  108. package/dist/session/truncate-result.js +71 -0
  109. package/dist/session/truncate-result.js.map +1 -0
  110. package/dist/session/types.d.ts +220 -0
  111. package/dist/session/types.js +6 -0
  112. package/dist/session/types.js.map +1 -0
  113. package/dist/trusted-process/audit-log.d.ts +7 -0
  114. package/dist/trusted-process/audit-log.js +21 -0
  115. package/dist/trusted-process/audit-log.js.map +1 -0
  116. package/dist/trusted-process/call-circuit-breaker.d.ts +33 -0
  117. package/dist/trusted-process/call-circuit-breaker.js +61 -0
  118. package/dist/trusted-process/call-circuit-breaker.js.map +1 -0
  119. package/dist/trusted-process/escalation.d.ts +7 -0
  120. package/dist/trusted-process/escalation.js +38 -0
  121. package/dist/trusted-process/escalation.js.map +1 -0
  122. package/dist/trusted-process/index.d.ts +32 -0
  123. package/dist/trusted-process/index.js +151 -0
  124. package/dist/trusted-process/index.js.map +1 -0
  125. package/dist/trusted-process/mcp-client-manager.d.ts +25 -0
  126. package/dist/trusted-process/mcp-client-manager.js +90 -0
  127. package/dist/trusted-process/mcp-client-manager.js.map +1 -0
  128. package/dist/trusted-process/mcp-proxy-server.d.ts +24 -0
  129. package/dist/trusted-process/mcp-proxy-server.js +451 -0
  130. package/dist/trusted-process/mcp-proxy-server.js.map +1 -0
  131. package/dist/trusted-process/path-utils.d.ts +50 -0
  132. package/dist/trusted-process/path-utils.js +158 -0
  133. package/dist/trusted-process/path-utils.js.map +1 -0
  134. package/dist/trusted-process/policy-engine.d.ts +88 -0
  135. package/dist/trusted-process/policy-engine.js +523 -0
  136. package/dist/trusted-process/policy-engine.js.map +1 -0
  137. package/dist/trusted-process/policy-roots.d.ts +50 -0
  138. package/dist/trusted-process/policy-roots.js +67 -0
  139. package/dist/trusted-process/policy-roots.js.map +1 -0
  140. package/dist/trusted-process/policy-types.d.ts +6 -0
  141. package/dist/trusted-process/policy-types.js +2 -0
  142. package/dist/trusted-process/policy-types.js.map +1 -0
  143. package/dist/trusted-process/sandbox-integration.d.ts +92 -0
  144. package/dist/trusted-process/sandbox-integration.js +184 -0
  145. package/dist/trusted-process/sandbox-integration.js.map +1 -0
  146. package/dist/types/argument-roles.d.ts +112 -0
  147. package/dist/types/argument-roles.js +344 -0
  148. package/dist/types/argument-roles.js.map +1 -0
  149. package/dist/types/audit.d.ts +18 -0
  150. package/dist/types/audit.js +2 -0
  151. package/dist/types/audit.js.map +1 -0
  152. package/dist/types/mcp.d.ts +20 -0
  153. package/dist/types/mcp.js +2 -0
  154. package/dist/types/mcp.js.map +1 -0
  155. package/package.json +83 -0
  156. package/src/config/constitution.md +16 -0
  157. package/src/config/generated/compiled-policy.json +236 -0
  158. package/src/config/generated/test-scenarios.json +765 -0
  159. package/src/config/generated/tool-annotations.json +955 -0
  160. package/src/config/mcp-servers.json +22 -0
@@ -0,0 +1,63 @@
1
+ /**
2
+ * StepLoopDetector -- Agent-level loop detection.
3
+ *
4
+ * Analyzes each execute_code step (code + result) using a 2x2 progress
5
+ * matrix to detect stuck or stagnating agents. The unit of analysis is
6
+ * the step, not individual MCP calls.
7
+ *
8
+ * Progress matrix:
9
+ * - Full progress: new approach + new outcome → reset concern
10
+ * - World changed: repeated approach + new outcome → reset concern
11
+ * - Stuck: new approach + repeated outcome → increment stuck
12
+ * - Full stagnation: repeated approach + repeated outcome → increment stagnation
13
+ */
14
+ export type ProgressCategory = 'full_progress' | 'world_changed' | 'stuck' | 'full_stagnation';
15
+ export type BlockVerdict = {
16
+ action: 'block';
17
+ message: string;
18
+ category: ProgressCategory;
19
+ };
20
+ export type StepVerdict = {
21
+ action: 'allow';
22
+ } | {
23
+ action: 'warn';
24
+ message: string;
25
+ category: ProgressCategory;
26
+ } | BlockVerdict;
27
+ export interface StepLoopDetectorConfig {
28
+ stagnation: {
29
+ warn: number;
30
+ block: number;
31
+ };
32
+ stuck: {
33
+ warn: number;
34
+ block: number;
35
+ };
36
+ }
37
+ export declare class StepLoopDetector {
38
+ private readonly config;
39
+ private approachHashes;
40
+ private outcomeHashes;
41
+ private stagnationStreak;
42
+ private stuckStreak;
43
+ private blocked;
44
+ private blockVerdict;
45
+ constructor(config?: Partial<StepLoopDetectorConfig>);
46
+ /**
47
+ * Check if execution is blocked before running code.
48
+ * Returns the block verdict if blocked, null otherwise.
49
+ */
50
+ isBlocked(): BlockVerdict | null;
51
+ /**
52
+ * Analyze a completed step and return a verdict.
53
+ *
54
+ * @param code - The TypeScript code that was executed
55
+ * @param result - The execution result (will be hashed)
56
+ */
57
+ analyzeStep(code: string, result: unknown): StepVerdict;
58
+ /** Reset all state. */
59
+ reset(): void;
60
+ private classify;
61
+ private updateStreaks;
62
+ private checkThresholds;
63
+ }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * StepLoopDetector -- Agent-level loop detection.
3
+ *
4
+ * Analyzes each execute_code step (code + result) using a 2x2 progress
5
+ * matrix to detect stuck or stagnating agents. The unit of analysis is
6
+ * the step, not individual MCP calls.
7
+ *
8
+ * Progress matrix:
9
+ * - Full progress: new approach + new outcome → reset concern
10
+ * - World changed: repeated approach + new outcome → reset concern
11
+ * - Stuck: new approach + repeated outcome → increment stuck
12
+ * - Full stagnation: repeated approach + repeated outcome → increment stagnation
13
+ */
14
+ import { computeHash } from '../hash.js';
15
+ const DEFAULT_CONFIG = {
16
+ stagnation: { warn: 3, block: 5 },
17
+ stuck: { warn: 5, block: 8 },
18
+ };
19
+ export class StepLoopDetector {
20
+ config;
21
+ approachHashes = new Set();
22
+ outcomeHashes = new Set();
23
+ stagnationStreak = 0;
24
+ stuckStreak = 0;
25
+ blocked = false;
26
+ blockVerdict = null;
27
+ constructor(config) {
28
+ this.config = {
29
+ stagnation: { ...DEFAULT_CONFIG.stagnation, ...config?.stagnation },
30
+ stuck: { ...DEFAULT_CONFIG.stuck, ...config?.stuck },
31
+ };
32
+ }
33
+ /**
34
+ * Check if execution is blocked before running code.
35
+ * Returns the block verdict if blocked, null otherwise.
36
+ */
37
+ isBlocked() {
38
+ return this.blocked ? this.blockVerdict : null;
39
+ }
40
+ /**
41
+ * Analyze a completed step and return a verdict.
42
+ *
43
+ * @param code - The TypeScript code that was executed
44
+ * @param result - The execution result (will be hashed)
45
+ */
46
+ analyzeStep(code, result) {
47
+ const approachHash = computeHash(code);
48
+ const outcomeHash = computeHash(result);
49
+ const isNewApproach = !this.approachHashes.has(approachHash);
50
+ const isNewOutcome = !this.outcomeHashes.has(outcomeHash);
51
+ this.approachHashes.add(approachHash);
52
+ this.outcomeHashes.add(outcomeHash);
53
+ const category = this.classify(isNewApproach, isNewOutcome);
54
+ this.updateStreaks(category);
55
+ return this.checkThresholds();
56
+ }
57
+ /** Reset all state. */
58
+ reset() {
59
+ this.approachHashes.clear();
60
+ this.outcomeHashes.clear();
61
+ this.stagnationStreak = 0;
62
+ this.stuckStreak = 0;
63
+ this.blocked = false;
64
+ this.blockVerdict = null;
65
+ }
66
+ classify(isNewApproach, isNewOutcome) {
67
+ if (isNewApproach && isNewOutcome)
68
+ return 'full_progress';
69
+ if (!isNewApproach && isNewOutcome)
70
+ return 'world_changed';
71
+ if (isNewApproach && !isNewOutcome)
72
+ return 'stuck';
73
+ return 'full_stagnation';
74
+ }
75
+ updateStreaks(category) {
76
+ switch (category) {
77
+ case 'full_progress':
78
+ case 'world_changed':
79
+ this.stagnationStreak = 0;
80
+ this.stuckStreak = 0;
81
+ break;
82
+ case 'stuck':
83
+ this.stuckStreak++;
84
+ this.stagnationStreak = 0;
85
+ break;
86
+ case 'full_stagnation':
87
+ this.stagnationStreak++;
88
+ this.stuckStreak = 0;
89
+ break;
90
+ }
91
+ }
92
+ checkThresholds() {
93
+ // Check block thresholds first
94
+ if (this.stagnationStreak >= this.config.stagnation.block) {
95
+ const verdict = {
96
+ action: 'block',
97
+ message: 'LOOP DETECTED: You have been repeating the same code with the same result. ' +
98
+ 'Execution is now blocked. Summarize what you have accomplished and stop.',
99
+ category: 'full_stagnation',
100
+ };
101
+ this.blocked = true;
102
+ this.blockVerdict = verdict;
103
+ return verdict;
104
+ }
105
+ if (this.stuckStreak >= this.config.stuck.block) {
106
+ const verdict = {
107
+ action: 'block',
108
+ message: 'LOOP DETECTED: You keep trying different approaches but getting the same result. ' +
109
+ 'Execution is now blocked. Summarize what you have accomplished and stop.',
110
+ category: 'stuck',
111
+ };
112
+ this.blocked = true;
113
+ this.blockVerdict = verdict;
114
+ return verdict;
115
+ }
116
+ // Check warn thresholds
117
+ if (this.stagnationStreak >= this.config.stagnation.warn) {
118
+ return {
119
+ action: 'warn',
120
+ message: 'WARNING: You are repeating the same code with the same result. ' +
121
+ `Try a fundamentally different approach. (${this.stagnationStreak}/${this.config.stagnation.block} before block)`,
122
+ category: 'full_stagnation',
123
+ };
124
+ }
125
+ if (this.stuckStreak >= this.config.stuck.warn) {
126
+ return {
127
+ action: 'warn',
128
+ message: 'WARNING: Different approaches are producing the same result. ' +
129
+ `Re-examine your assumptions. (${this.stuckStreak}/${this.config.stuck.block} before block)`,
130
+ category: 'stuck',
131
+ };
132
+ }
133
+ return { action: 'allow' };
134
+ }
135
+ }
136
+ //# sourceMappingURL=step-loop-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-loop-detector.js","sourceRoot":"","sources":["../../src/session/step-loop-detector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { Session } from './types.js';
2
+ /**
3
+ * A transport delivers messages between an external source and a session.
4
+ * It is responsible for:
5
+ * - Reading input from its source (stdin, HTTP, WebSocket, etc.)
6
+ * - Calling session.sendMessage() with each input
7
+ * - Delivering the response back to the source
8
+ * - Handling slash commands (including escalation approval)
9
+ * - Signaling when the conversation should end
10
+ *
11
+ * The transport does NOT own the session -- the caller creates the session
12
+ * and passes it to the transport. This allows the same session to be
13
+ * used with different transports (e.g., migrate from CLI to web mid-session).
14
+ */
15
+ export interface Transport {
16
+ /**
17
+ * Starts the transport's message loop. Returns when the transport
18
+ * is done (user typed /quit, connection closed, etc.).
19
+ *
20
+ * The transport must handle errors from session.sendMessage()
21
+ * gracefully (display to user, continue accepting input).
22
+ */
23
+ run(session: Session): Promise<void>;
24
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/session/transport.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Tool result truncation utility.
3
+ *
4
+ * Truncates oversized tool results before they reach the LLM to prevent
5
+ * "prompt is too long" errors. The audit log (written at the trusted
6
+ * process level) preserves full untruncated results for forensics.
7
+ *
8
+ * Strategy: serialize the value to JSON, measure that, and if it exceeds
9
+ * the budget, truncate the JSON string with a head/tail split.
10
+ */
11
+ /** 100 KB ≈ 25K tokens, matching Claude Code's cap. */
12
+ export declare const DEFAULT_RESULT_SIZE_LIMIT = 100000;
13
+ /** Returns the configured result size limit (bytes). */
14
+ export declare function getResultSizeLimit(): number;
15
+ export interface TruncationResult {
16
+ value: unknown;
17
+ truncated: boolean;
18
+ originalSize: number;
19
+ finalSize: number;
20
+ }
21
+ /**
22
+ * Truncates a single string to fit within `maxBytes`, keeping ~80% from
23
+ * the head and ~20% from the tail with a marker in between.
24
+ */
25
+ export declare function truncateString(s: string, maxBytes: number): string;
26
+ /**
27
+ * Truncates a tool result value to fit within `budget` bytes.
28
+ *
29
+ * Serializes the value to JSON, and if it exceeds the budget, replaces
30
+ * it with a truncated string (head/tail with marker). When the value
31
+ * fits, it's returned as-is (zero-copy).
32
+ */
33
+ export declare function truncateResult(value: unknown, budget?: number): TruncationResult;
34
+ /** Formats bytes as a human-readable KB string. */
35
+ export declare function formatKB(bytes: number): string;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Tool result truncation utility.
3
+ *
4
+ * Truncates oversized tool results before they reach the LLM to prevent
5
+ * "prompt is too long" errors. The audit log (written at the trusted
6
+ * process level) preserves full untruncated results for forensics.
7
+ *
8
+ * Strategy: serialize the value to JSON, measure that, and if it exceeds
9
+ * the budget, truncate the JSON string with a head/tail split.
10
+ */
11
+ /** 100 KB ≈ 25K tokens, matching Claude Code's cap. */
12
+ export const DEFAULT_RESULT_SIZE_LIMIT = 100_000;
13
+ /** Returns the configured result size limit (bytes). */
14
+ export function getResultSizeLimit() {
15
+ const envVal = process.env['RESULT_SIZE_LIMIT'];
16
+ if (envVal) {
17
+ const parsed = parseInt(envVal, 10);
18
+ if (Number.isFinite(parsed) && parsed > 0)
19
+ return parsed;
20
+ }
21
+ return DEFAULT_RESULT_SIZE_LIMIT;
22
+ }
23
+ /**
24
+ * Truncates a single string to fit within `maxBytes`, keeping ~80% from
25
+ * the head and ~20% from the tail with a marker in between.
26
+ */
27
+ export function truncateString(s, maxBytes) {
28
+ const buf = Buffer.from(s, 'utf-8');
29
+ if (buf.length <= maxBytes)
30
+ return s;
31
+ const truncatedBytes = buf.length - maxBytes;
32
+ const realMarker = `\n[... truncated ${truncatedBytes} bytes ...]\n`;
33
+ const markerBytes = Buffer.byteLength(realMarker, 'utf-8');
34
+ const available = maxBytes - markerBytes;
35
+ if (available <= 0)
36
+ return realMarker;
37
+ const headBytes = Math.floor(available * 0.8);
38
+ const tailBytes = available - headBytes;
39
+ const head = buf.subarray(0, headBytes).toString('utf-8');
40
+ const tail = tailBytes > 0
41
+ ? buf.subarray(buf.length - tailBytes).toString('utf-8')
42
+ : '';
43
+ return head + realMarker + tail;
44
+ }
45
+ /**
46
+ * Truncates a tool result value to fit within `budget` bytes.
47
+ *
48
+ * Serializes the value to JSON, and if it exceeds the budget, replaces
49
+ * it with a truncated string (head/tail with marker). When the value
50
+ * fits, it's returned as-is (zero-copy).
51
+ */
52
+ export function truncateResult(value, budget) {
53
+ const limit = budget ?? getResultSizeLimit();
54
+ const json = JSON.stringify(value);
55
+ // JSON.stringify returns undefined for undefined input
56
+ if (json === undefined) {
57
+ return { value, truncated: false, originalSize: 0, finalSize: 0 };
58
+ }
59
+ const originalSize = Buffer.byteLength(json, 'utf-8');
60
+ if (originalSize <= limit) {
61
+ return { value, truncated: false, originalSize, finalSize: originalSize };
62
+ }
63
+ const truncated = truncateString(json, limit);
64
+ const finalSize = Buffer.byteLength(truncated, 'utf-8');
65
+ return { value: truncated, truncated: true, originalSize, finalSize };
66
+ }
67
+ /** Formats bytes as a human-readable KB string. */
68
+ export function formatKB(bytes) {
69
+ return `${(bytes / 1024).toFixed(1)}KB`;
70
+ }
71
+ //# sourceMappingURL=truncate-result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"truncate-result.js","sourceRoot":"","sources":["../../src/session/truncate-result.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,220 @@
1
+ import type { IronCurtainConfig } from '../config/types.js';
2
+ import type { Sandbox } from '../sandbox/index.js';
3
+ import type { ResolvedResourceBudgetConfig } from '../config/user-config.js';
4
+ import type { CumulativeBudgetSnapshot } from './resource-budget-tracker.js';
5
+ /**
6
+ * Unique identifier for a session. Branded to prevent accidental
7
+ * mixing with other string identifiers.
8
+ */
9
+ export type SessionId = string & {
10
+ readonly __brand: 'SessionId';
11
+ };
12
+ /** Creates a new unique SessionId. */
13
+ export declare function createSessionId(): SessionId;
14
+ /**
15
+ * The possible states a session can be in. Linear progression:
16
+ * initializing -> ready -> (processing <-> ready) -> closed.
17
+ *
18
+ * - initializing: sandbox and resources being set up
19
+ * - ready: accepting messages
20
+ * - processing: a message is being processed (generateText in flight)
21
+ * - closed: resources released, no more messages accepted
22
+ */
23
+ export type SessionStatus = 'initializing' | 'ready' | 'processing' | 'closed';
24
+ /**
25
+ * A single turn in the conversation. Captures what the user said,
26
+ * what the agent responded, and metadata about the turn.
27
+ */
28
+ export interface ConversationTurn {
29
+ /** 1-based turn number within this session. */
30
+ readonly turnNumber: number;
31
+ /** The user's input for this turn. */
32
+ readonly userMessage: string;
33
+ /** The agent's final text response for this turn. */
34
+ readonly assistantResponse: string;
35
+ /** Token usage for this turn (prompt + completion). */
36
+ readonly usage: {
37
+ promptTokens: number;
38
+ completionTokens: number;
39
+ totalTokens: number;
40
+ };
41
+ /** ISO 8601 timestamp when this turn started. */
42
+ readonly timestamp: string;
43
+ }
44
+ /**
45
+ * A diagnostic event emitted during message processing.
46
+ * Transports decide how (or whether) to display these.
47
+ */
48
+ export type DiagnosticEvent = {
49
+ readonly kind: 'tool_call';
50
+ readonly toolName: string;
51
+ readonly preview: string;
52
+ } | {
53
+ readonly kind: 'agent_text';
54
+ readonly preview: string;
55
+ } | {
56
+ readonly kind: 'step_finish';
57
+ readonly stepIndex: number;
58
+ } | {
59
+ readonly kind: 'loop_detection';
60
+ readonly action: 'warn' | 'block';
61
+ readonly category: string;
62
+ readonly message: string;
63
+ } | {
64
+ readonly kind: 'result_truncation';
65
+ readonly originalKB: number;
66
+ readonly finalKB: number;
67
+ } | {
68
+ readonly kind: 'budget_warning';
69
+ readonly dimension: string;
70
+ readonly percentUsed: number;
71
+ readonly message: string;
72
+ } | {
73
+ readonly kind: 'budget_exhausted';
74
+ readonly dimension: string;
75
+ readonly message: string;
76
+ } | {
77
+ readonly kind: 'message_compaction';
78
+ readonly originalMessageCount: number;
79
+ readonly newMessageCount: number;
80
+ readonly summaryPreview: string;
81
+ };
82
+ /**
83
+ * Budget status: current consumption snapshot plus configured limits.
84
+ * Exposed to transports for the /budget command and end-of-session summary.
85
+ */
86
+ export interface BudgetStatus {
87
+ readonly totalInputTokens: number;
88
+ readonly totalOutputTokens: number;
89
+ readonly totalTokens: number;
90
+ readonly stepCount: number;
91
+ readonly elapsedSeconds: number;
92
+ readonly estimatedCostUsd: number;
93
+ readonly limits: ResolvedResourceBudgetConfig;
94
+ readonly cumulative: CumulativeBudgetSnapshot;
95
+ }
96
+ /**
97
+ * Read-only snapshot of session state. Exposed to transports
98
+ * and external observers without giving them mutation access.
99
+ */
100
+ export interface SessionInfo {
101
+ readonly id: SessionId;
102
+ readonly status: SessionStatus;
103
+ readonly turnCount: number;
104
+ readonly createdAt: string;
105
+ }
106
+ /**
107
+ * Factory function for creating sandbox instances.
108
+ * The default creates a real Sandbox wrapping UTCP Code Mode's V8 isolate.
109
+ * Tests provide a factory returning a mock.
110
+ */
111
+ export type SandboxFactory = (config: IronCurtainConfig) => Promise<Sandbox>;
112
+ /**
113
+ * Escalation request data surfaced to the transport.
114
+ * Decoupled from ToolCallRequest to avoid leaking internal types
115
+ * to transport implementations.
116
+ */
117
+ export interface EscalationRequest {
118
+ /** Unique ID for this escalation, used to match approve/deny responses. */
119
+ readonly escalationId: string;
120
+ readonly toolName: string;
121
+ readonly serverName: string;
122
+ readonly arguments: Record<string, unknown>;
123
+ readonly reason: string;
124
+ }
125
+ /**
126
+ * Options for creating a session. Extends the base config with
127
+ * session-specific overrides.
128
+ */
129
+ export interface SessionOptions {
130
+ /** Base configuration. If omitted, loaded from environment. */
131
+ config?: IronCurtainConfig;
132
+ /** If provided, reuses the sandbox from this previous session via symlink. */
133
+ resumeSessionId?: string;
134
+ /**
135
+ * Maximum number of messages to retain in history before pruning.
136
+ * Defined as an extension point but not enforced in the initial
137
+ * implementation. When the context window is exceeded,
138
+ * generateText() throws and the error propagates to the transport.
139
+ */
140
+ maxHistoryMessages?: number;
141
+ /**
142
+ * Factory for creating sandbox instances.
143
+ * Default: creates a real Sandbox (UTCP Code Mode V8 isolate).
144
+ * Tests provide a factory returning a mock.
145
+ */
146
+ sandboxFactory?: SandboxFactory;
147
+ /**
148
+ * Callback invoked when the proxy surfaces an escalation.
149
+ * The transport uses this to notify the user and collect approval.
150
+ * If not provided, escalations are auto-denied.
151
+ */
152
+ onEscalation?: (request: EscalationRequest) => void;
153
+ /**
154
+ * Callback invoked during message processing with diagnostic events.
155
+ * Transports use this to display progress (e.g., tool call previews).
156
+ * If not provided, diagnostics are silently dropped.
157
+ */
158
+ onDiagnostic?: (event: DiagnosticEvent) => void;
159
+ }
160
+ /**
161
+ * The core session contract. A session is a stateful conversation
162
+ * that owns its sandbox, policy engine, and message history.
163
+ *
164
+ * Invariants:
165
+ * - sendMessage() can only be called when status is 'ready'
166
+ * - sendMessage() is not reentrant (status transitions to 'processing')
167
+ * - After close(), no methods except getInfo() are valid
168
+ * - The session ID is unique and immutable for the session's lifetime
169
+ */
170
+ export interface Session {
171
+ /** Returns a read-only snapshot of session state. */
172
+ getInfo(): SessionInfo;
173
+ /**
174
+ * Sends a user message and returns the agent's response.
175
+ *
176
+ * Appends the user message to conversation history, calls the LLM
177
+ * with the full history, appends the response messages, and returns
178
+ * the agent's text.
179
+ *
180
+ * @throws {SessionNotReadyError} if status is not 'ready'
181
+ * @throws {SessionClosedError} if session has been closed
182
+ */
183
+ sendMessage(userMessage: string): Promise<string>;
184
+ /**
185
+ * Returns the conversation history as turn summaries.
186
+ * Does not expose raw ModelMessage[] to avoid coupling
187
+ * callers to the AI SDK's internal message format.
188
+ */
189
+ getHistory(): readonly ConversationTurn[];
190
+ /**
191
+ * Returns accumulated diagnostic events from all turns.
192
+ * Transports can use this for a /logs command or similar.
193
+ */
194
+ getDiagnosticLog(): readonly DiagnosticEvent[];
195
+ /**
196
+ * Resolves a pending escalation. Called by the transport
197
+ * when the user approves or denies via a slash command.
198
+ *
199
+ * Writes the response to the escalation directory so the
200
+ * proxy process can pick it up and continue.
201
+ *
202
+ * @throws {Error} if no escalation with this ID is pending
203
+ */
204
+ resolveEscalation(escalationId: string, decision: 'approved' | 'denied'): Promise<void>;
205
+ /**
206
+ * Returns any currently pending escalation, or undefined.
207
+ */
208
+ getPendingEscalation(): EscalationRequest | undefined;
209
+ /**
210
+ * Returns current resource budget consumption and configured limits.
211
+ * Used by transports for /budget display and end-of-session summary.
212
+ */
213
+ getBudgetStatus(): BudgetStatus;
214
+ /**
215
+ * Releases all session resources: sandbox, MCP connections,
216
+ * audit log, escalation directory. Idempotent -- safe to call
217
+ * multiple times. After close(), status becomes 'closed'.
218
+ */
219
+ close(): Promise<void>;
220
+ }
@@ -0,0 +1,6 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ /** Creates a new unique SessionId. */
3
+ export function createSessionId() {
4
+ return randomUUID();
5
+ }
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/session/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAYzC,sCAAsC;AACtC,MAAM,UAAU,eAAe;IAC7B,OAAO,UAAU,EAAe,CAAC;AACnC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { AuditEntry } from '../types/audit.js';
2
+ export declare class AuditLog {
3
+ private stream;
4
+ constructor(path: string);
5
+ log(entry: AuditEntry): void;
6
+ close(): Promise<void>;
7
+ }
@@ -0,0 +1,21 @@
1
+ import { createWriteStream } from 'node:fs';
2
+ export class AuditLog {
3
+ stream;
4
+ constructor(path) {
5
+ this.stream = createWriteStream(path, { flags: 'a' });
6
+ }
7
+ log(entry) {
8
+ this.stream.write(JSON.stringify(entry) + '\n');
9
+ }
10
+ async close() {
11
+ return new Promise((resolve, reject) => {
12
+ this.stream.end((err) => {
13
+ if (err)
14
+ reject(err);
15
+ else
16
+ resolve();
17
+ });
18
+ });
19
+ }
20
+ }
21
+ //# sourceMappingURL=audit-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.js","sourceRoot":"","sources":["../../src/trusted-process/audit-log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAoB,MAAM,SAAS,CAAC;AAG9D,MAAM,OAAO,QAAQ;IACX,MAAM,CAAc;IAE5B,YAAY,IAAY;QACtB,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,GAAG,CAAC,KAAiB;QACnB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,KAAK;QACT,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAA6B,EAAE,EAAE;gBAChD,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * CallCircuitBreaker -- Proxy-level rate limiter for MCP tool calls.
3
+ *
4
+ * Protects against runaway sandbox code that hammers the same tool
5
+ * with identical arguments. Uses a sliding-window approach: if the
6
+ * same (tool, argsHash) pair appears more than `threshold` times
7
+ * within `windowMs`, the call is denied.
8
+ *
9
+ * Runs AFTER policy evaluation so every call is always audited.
10
+ */
11
+ export interface CircuitBreakerConfig {
12
+ windowMs: number;
13
+ threshold: number;
14
+ }
15
+ export type CircuitBreakerVerdict = {
16
+ allowed: true;
17
+ } | {
18
+ allowed: false;
19
+ reason: string;
20
+ };
21
+ export declare class CallCircuitBreaker {
22
+ private readonly config;
23
+ private windows;
24
+ constructor(config?: Partial<CircuitBreakerConfig>);
25
+ /**
26
+ * Check whether a tool call should be allowed.
27
+ *
28
+ * @returns `{ allowed: true }` or `{ allowed: false, reason: string }`
29
+ */
30
+ check(toolName: string, args: Record<string, unknown>): CircuitBreakerVerdict;
31
+ /** Reset all state. */
32
+ reset(): void;
33
+ }