@det-acp/core 0.2.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 +21 -0
- package/README.md +492 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +308 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +32 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +234 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/templates.d.ts +27 -0
- package/dist/cli/templates.d.ts.map +1 -0
- package/dist/cli/templates.js +266 -0
- package/dist/cli/templates.js.map +1 -0
- package/dist/engine/action-registry.d.ts +49 -0
- package/dist/engine/action-registry.d.ts.map +1 -0
- package/dist/engine/action-registry.js +95 -0
- package/dist/engine/action-registry.js.map +1 -0
- package/dist/engine/gate.d.ts +57 -0
- package/dist/engine/gate.d.ts.map +1 -0
- package/dist/engine/gate.js +145 -0
- package/dist/engine/gate.js.map +1 -0
- package/dist/engine/runtime.d.ts +98 -0
- package/dist/engine/runtime.d.ts.map +1 -0
- package/dist/engine/runtime.js +138 -0
- package/dist/engine/runtime.js.map +1 -0
- package/dist/engine/session.d.ts +74 -0
- package/dist/engine/session.d.ts.map +1 -0
- package/dist/engine/session.js +343 -0
- package/dist/engine/session.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger/ledger.d.ts +58 -0
- package/dist/ledger/ledger.d.ts.map +1 -0
- package/dist/ledger/ledger.js +188 -0
- package/dist/ledger/ledger.js.map +1 -0
- package/dist/ledger/query.d.ts +29 -0
- package/dist/ledger/query.d.ts.map +1 -0
- package/dist/ledger/query.js +61 -0
- package/dist/ledger/query.js.map +1 -0
- package/dist/ledger/types.d.ts +27 -0
- package/dist/ledger/types.d.ts.map +1 -0
- package/dist/ledger/types.js +5 -0
- package/dist/ledger/types.js.map +1 -0
- package/dist/policy/evaluator.d.ts +21 -0
- package/dist/policy/evaluator.d.ts.map +1 -0
- package/dist/policy/evaluator.js +383 -0
- package/dist/policy/evaluator.js.map +1 -0
- package/dist/policy/loader.d.ts +27 -0
- package/dist/policy/loader.d.ts.map +1 -0
- package/dist/policy/loader.js +69 -0
- package/dist/policy/loader.js.map +1 -0
- package/dist/policy/schema.d.ts +168 -0
- package/dist/policy/schema.d.ts.map +1 -0
- package/dist/policy/schema.js +107 -0
- package/dist/policy/schema.js.map +1 -0
- package/dist/proxy/mcp-proxy.d.ts +43 -0
- package/dist/proxy/mcp-proxy.d.ts.map +1 -0
- package/dist/proxy/mcp-proxy.js +240 -0
- package/dist/proxy/mcp-proxy.js.map +1 -0
- package/dist/proxy/mcp-types.d.ts +79 -0
- package/dist/proxy/mcp-types.d.ts.map +1 -0
- package/dist/proxy/mcp-types.js +28 -0
- package/dist/proxy/mcp-types.js.map +1 -0
- package/dist/proxy/shell-proxy.d.ts +52 -0
- package/dist/proxy/shell-proxy.d.ts.map +1 -0
- package/dist/proxy/shell-proxy.js +92 -0
- package/dist/proxy/shell-proxy.js.map +1 -0
- package/dist/rollback/manager.d.ts +62 -0
- package/dist/rollback/manager.d.ts.map +1 -0
- package/dist/rollback/manager.js +151 -0
- package/dist/rollback/manager.js.map +1 -0
- package/dist/server/server.d.ts +24 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +200 -0
- package/dist/server/server.js.map +1 -0
- package/dist/tools/base.d.ts +58 -0
- package/dist/tools/base.d.ts.map +1 -0
- package/dist/tools/base.js +48 -0
- package/dist/tools/base.js.map +1 -0
- package/dist/tools/command-run.d.ts +30 -0
- package/dist/tools/command-run.d.ts.map +1 -0
- package/dist/tools/command-run.js +87 -0
- package/dist/tools/command-run.js.map +1 -0
- package/dist/tools/file-read.d.ts +34 -0
- package/dist/tools/file-read.d.ts.map +1 -0
- package/dist/tools/file-read.js +67 -0
- package/dist/tools/file-read.js.map +1 -0
- package/dist/tools/file-write.d.ts +39 -0
- package/dist/tools/file-write.d.ts.map +1 -0
- package/dist/tools/file-write.js +158 -0
- package/dist/tools/file-write.js.map +1 -0
- package/dist/tools/git.d.ts +48 -0
- package/dist/tools/git.d.ts.map +1 -0
- package/dist/tools/git.js +193 -0
- package/dist/tools/git.js.map +1 -0
- package/dist/tools/http-request.d.ts +48 -0
- package/dist/tools/http-request.d.ts.map +1 -0
- package/dist/tools/http-request.js +91 -0
- package/dist/tools/http-request.js.map +1 -0
- package/dist/types.d.ts +257 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/examples/coding-agent.policy.yaml +80 -0
- package/examples/devops-deploy.policy.yaml +107 -0
- package/examples/mcp-proxy.config.yaml +34 -0
- package/examples/simple-session.ts +161 -0
- package/examples/video-upscaler.policy.yaml +86 -0
- package/package.json +92 -0
- package/schemas/generate.ts +18 -0
- package/schemas/policy.schema.json +7 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Gateway — the core entry point for governing agent actions.
|
|
3
|
+
*
|
|
4
|
+
* The gateway operates as a policy evaluation layer that does NOT execute
|
|
5
|
+
* actions itself. Instead, it evaluates each action request against the
|
|
6
|
+
* session's policy and records results reported by external executors.
|
|
7
|
+
*
|
|
8
|
+
* This is the main entry point for both the library SDK and the sidecar server.
|
|
9
|
+
*
|
|
10
|
+
* Flow:
|
|
11
|
+
* Agent → gateway.evaluate(sessionId, action)
|
|
12
|
+
* → policy check → allow/deny/gate
|
|
13
|
+
* Agent executes action externally
|
|
14
|
+
* Agent → gateway.recordResult(sessionId, actionId, result)
|
|
15
|
+
* → evidence recorded in ledger
|
|
16
|
+
*/
|
|
17
|
+
import type { ActionRequest, ActionResult, EvaluateResponse, GateDecision, Session, SessionReport, SessionState } from '../types.js';
|
|
18
|
+
import { ActionRegistry } from './action-registry.js';
|
|
19
|
+
import { SessionManager } from './session.js';
|
|
20
|
+
import { GateManager } from './gate.js';
|
|
21
|
+
import { EvidenceLedger } from '../ledger/ledger.js';
|
|
22
|
+
export interface GatewayConfig {
|
|
23
|
+
/** Directory for ledger files. One file per session. */
|
|
24
|
+
ledgerDir: string;
|
|
25
|
+
/** Optional: pre-configured action registry (for tool validation). */
|
|
26
|
+
registry?: ActionRegistry;
|
|
27
|
+
/** Optional: pre-configured gate manager. */
|
|
28
|
+
gateManager?: GateManager;
|
|
29
|
+
/** Callback invoked when a session needs human approval. */
|
|
30
|
+
onGateRequest?: (sessionId: string, actionId: string, gate: unknown) => void;
|
|
31
|
+
/** Callback invoked on each session state transition. */
|
|
32
|
+
onStateChange?: (sessionId: string, from: SessionState, to: SessionState) => void;
|
|
33
|
+
/** Callback invoked when a session is terminated. */
|
|
34
|
+
onSessionTerminated?: (sessionId: string, report: SessionReport) => void;
|
|
35
|
+
}
|
|
36
|
+
export declare class AgentGateway {
|
|
37
|
+
private registry;
|
|
38
|
+
private gateManager;
|
|
39
|
+
private sessionManager;
|
|
40
|
+
private config;
|
|
41
|
+
private constructor();
|
|
42
|
+
/**
|
|
43
|
+
* Create and initialize a new gateway instance.
|
|
44
|
+
*/
|
|
45
|
+
static create(config: GatewayConfig): Promise<AgentGateway>;
|
|
46
|
+
/**
|
|
47
|
+
* Create a new governance session.
|
|
48
|
+
* Loads the policy from a file path or inline YAML string.
|
|
49
|
+
*/
|
|
50
|
+
createSession(policySource: string, metadata?: Record<string, unknown>): Promise<Session>;
|
|
51
|
+
/**
|
|
52
|
+
* Evaluate a single action against the session's policy.
|
|
53
|
+
* Returns allow/deny/gate verdict without executing the action.
|
|
54
|
+
*/
|
|
55
|
+
evaluate(sessionId: string, action: ActionRequest): Promise<EvaluateResponse>;
|
|
56
|
+
/**
|
|
57
|
+
* Record the result of an externally-executed action.
|
|
58
|
+
*/
|
|
59
|
+
recordResult(sessionId: string, actionId: string, result: ActionResult): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a pending gate (approve or reject).
|
|
62
|
+
*/
|
|
63
|
+
resolveGate(sessionId: string, actionId: string, decision: GateDecision, respondedBy?: string, reason?: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Terminate a session and get the final report.
|
|
66
|
+
*/
|
|
67
|
+
terminateSession(sessionId: string, reason?: string): Promise<SessionReport>;
|
|
68
|
+
/**
|
|
69
|
+
* Get a session by ID.
|
|
70
|
+
*/
|
|
71
|
+
getSession(sessionId: string): Session | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* List all sessions.
|
|
74
|
+
*/
|
|
75
|
+
listSessions(): Session[];
|
|
76
|
+
/**
|
|
77
|
+
* Get a report for a session.
|
|
78
|
+
*/
|
|
79
|
+
getSessionReport(sessionId: string): SessionReport;
|
|
80
|
+
/**
|
|
81
|
+
* Get the evidence ledger for a session.
|
|
82
|
+
*/
|
|
83
|
+
getSessionLedger(sessionId: string): EvidenceLedger | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Get the action registry (for registering custom tool adapters).
|
|
86
|
+
*/
|
|
87
|
+
getRegistry(): ActionRegistry;
|
|
88
|
+
/**
|
|
89
|
+
* Get the gate manager.
|
|
90
|
+
*/
|
|
91
|
+
getGateManager(): GateManager;
|
|
92
|
+
/**
|
|
93
|
+
* Get the session manager.
|
|
94
|
+
*/
|
|
95
|
+
getSessionManager(): SessionManager;
|
|
96
|
+
private loadPolicy;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/engine/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,YAAY,EAEZ,OAAO,EACP,aAAa,EACb,YAAY,EACb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAyB,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,cAAc,EAA6B,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD,MAAM,WAAW,aAAa;IAC5B,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,6CAA6C;IAC7C,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,4DAA4D;IAC5D,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7E,yDAAyD;IACzD,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,KAAK,IAAI,CAAC;IAClF,qDAAqD;IACrD,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;CAC1E;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAgB;IAE9B,OAAO;IAgBP;;OAEG;WACU,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IASjE;;;OAGG;IACG,aAAa,CACjB,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,OAAO,CAAC;IAKnB;;;OAGG;IACG,QAAQ,CACZ,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,gBAAgB,CAAC;IAI5B;;OAEG;IACG,YAAY,CAChB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC;IAIhB;;OAEG;IACG,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,YAAY,EACtB,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IAUhB;;OAEG;IACG,gBAAgB,CACpB,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC;IAQzB;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAIlD;;OAEG;IACH,YAAY,IAAI,OAAO,EAAE;IAIzB;;OAEG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa;IAIlD;;OAEG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAI/D;;OAEG;IACH,WAAW,IAAI,cAAc;IAI7B;;OAEG;IACH,cAAc,IAAI,WAAW;IAI7B;;OAEG;IACH,iBAAiB,IAAI,cAAc;IAQnC,OAAO,CAAC,UAAU;CAOnB"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Gateway — the core entry point for governing agent actions.
|
|
3
|
+
*
|
|
4
|
+
* The gateway operates as a policy evaluation layer that does NOT execute
|
|
5
|
+
* actions itself. Instead, it evaluates each action request against the
|
|
6
|
+
* session's policy and records results reported by external executors.
|
|
7
|
+
*
|
|
8
|
+
* This is the main entry point for both the library SDK and the sidecar server.
|
|
9
|
+
*
|
|
10
|
+
* Flow:
|
|
11
|
+
* Agent → gateway.evaluate(sessionId, action)
|
|
12
|
+
* → policy check → allow/deny/gate
|
|
13
|
+
* Agent executes action externally
|
|
14
|
+
* Agent → gateway.recordResult(sessionId, actionId, result)
|
|
15
|
+
* → evidence recorded in ledger
|
|
16
|
+
*/
|
|
17
|
+
import { createDefaultRegistry } from './action-registry.js';
|
|
18
|
+
import { SessionManager } from './session.js';
|
|
19
|
+
import { GateManager } from './gate.js';
|
|
20
|
+
import { loadPolicyFromFile, parsePolicyYaml } from '../policy/loader.js';
|
|
21
|
+
export class AgentGateway {
|
|
22
|
+
registry;
|
|
23
|
+
gateManager;
|
|
24
|
+
sessionManager;
|
|
25
|
+
config;
|
|
26
|
+
constructor(config, registry) {
|
|
27
|
+
this.config = config;
|
|
28
|
+
this.registry = config.registry ?? registry;
|
|
29
|
+
this.gateManager = config.gateManager ?? new GateManager();
|
|
30
|
+
const sessionConfig = {
|
|
31
|
+
ledgerDir: config.ledgerDir,
|
|
32
|
+
gateManager: this.gateManager,
|
|
33
|
+
onGateRequest: config.onGateRequest,
|
|
34
|
+
onStateChange: config.onStateChange,
|
|
35
|
+
onSessionTerminated: config.onSessionTerminated,
|
|
36
|
+
};
|
|
37
|
+
this.sessionManager = new SessionManager(sessionConfig);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create and initialize a new gateway instance.
|
|
41
|
+
*/
|
|
42
|
+
static async create(config) {
|
|
43
|
+
const registry = config.registry ?? await createDefaultRegistry();
|
|
44
|
+
return new AgentGateway(config, registry);
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Session management
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
/**
|
|
50
|
+
* Create a new governance session.
|
|
51
|
+
* Loads the policy from a file path or inline YAML string.
|
|
52
|
+
*/
|
|
53
|
+
async createSession(policySource, metadata) {
|
|
54
|
+
const policy = this.loadPolicy(policySource);
|
|
55
|
+
return this.sessionManager.createSession(policy, metadata);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Evaluate a single action against the session's policy.
|
|
59
|
+
* Returns allow/deny/gate verdict without executing the action.
|
|
60
|
+
*/
|
|
61
|
+
async evaluate(sessionId, action) {
|
|
62
|
+
return this.sessionManager.evaluate(sessionId, action);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Record the result of an externally-executed action.
|
|
66
|
+
*/
|
|
67
|
+
async recordResult(sessionId, actionId, result) {
|
|
68
|
+
return this.sessionManager.recordResult(sessionId, actionId, result);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve a pending gate (approve or reject).
|
|
72
|
+
*/
|
|
73
|
+
async resolveGate(sessionId, actionId, decision, respondedBy, reason) {
|
|
74
|
+
return this.sessionManager.resolveGate(sessionId, actionId, decision, respondedBy, reason);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Terminate a session and get the final report.
|
|
78
|
+
*/
|
|
79
|
+
async terminateSession(sessionId, reason) {
|
|
80
|
+
return this.sessionManager.terminate(sessionId, reason);
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Introspection
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
/**
|
|
86
|
+
* Get a session by ID.
|
|
87
|
+
*/
|
|
88
|
+
getSession(sessionId) {
|
|
89
|
+
return this.sessionManager.getSession(sessionId);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* List all sessions.
|
|
93
|
+
*/
|
|
94
|
+
listSessions() {
|
|
95
|
+
return this.sessionManager.listSessions();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get a report for a session.
|
|
99
|
+
*/
|
|
100
|
+
getSessionReport(sessionId) {
|
|
101
|
+
return this.sessionManager.getReport(sessionId);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get the evidence ledger for a session.
|
|
105
|
+
*/
|
|
106
|
+
getSessionLedger(sessionId) {
|
|
107
|
+
return this.sessionManager.getLedger(sessionId);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get the action registry (for registering custom tool adapters).
|
|
111
|
+
*/
|
|
112
|
+
getRegistry() {
|
|
113
|
+
return this.registry;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get the gate manager.
|
|
117
|
+
*/
|
|
118
|
+
getGateManager() {
|
|
119
|
+
return this.gateManager;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get the session manager.
|
|
123
|
+
*/
|
|
124
|
+
getSessionManager() {
|
|
125
|
+
return this.sessionManager;
|
|
126
|
+
}
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// Private helpers
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
loadPolicy(policySource) {
|
|
131
|
+
// Check if it's inline YAML (contains newlines or colons)
|
|
132
|
+
if (policySource.includes('\n') || policySource.includes(':')) {
|
|
133
|
+
return parsePolicyYaml(policySource);
|
|
134
|
+
}
|
|
135
|
+
return loadPolicyFromFile(policySource);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/engine/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAYH,OAAO,EAAkB,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,cAAc,EAA6B,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAiB1E,MAAM,OAAO,YAAY;IACf,QAAQ,CAAiB;IACzB,WAAW,CAAc;IACzB,cAAc,CAAiB;IAC/B,MAAM,CAAgB;IAE9B,YAAoB,MAAqB,EAAE,QAAwB;QACjE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,QAAQ,CAAC;QAC5C,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,IAAI,WAAW,EAAE,CAAC;QAE3D,MAAM,aAAa,GAAyB;YAC1C,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,mBAAmB,EAAE,MAAM,CAAC,mBAAmB;SAChD,CAAC;QAEF,IAAI,CAAC,cAAc,GAAG,IAAI,cAAc,CAAC,aAAa,CAAC,CAAC;IAC1D,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAqB;QACvC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,qBAAqB,EAAE,CAAC;QAClE,OAAO,IAAI,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAED,8EAA8E;IAC9E,qBAAqB;IACrB,8EAA8E;IAE9E;;;OAGG;IACH,KAAK,CAAC,aAAa,CACjB,YAAoB,EACpB,QAAkC;QAElC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC7D,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,QAAQ,CACZ,SAAiB,EACjB,MAAqB;QAErB,OAAO,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACzD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY,CAChB,SAAiB,EACjB,QAAgB,EAChB,MAAoB;QAEpB,OAAO,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACvE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW,CACf,SAAiB,EACjB,QAAgB,EAChB,QAAsB,EACtB,WAAoB,EACpB,MAAe;QAEf,OAAO,IAAI,CAAC,cAAc,CAAC,WAAW,CACpC,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,MAAM,CACP,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,gBAAgB,CACpB,SAAiB,EACjB,MAAe;QAEf,OAAO,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC1D,CAAC;IAED,8EAA8E;IAC9E,gBAAgB;IAChB,8EAA8E;IAE9E;;OAEG;IACH,UAAU,CAAC,SAAiB;QAC1B,OAAO,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,YAAY;QACV,OAAO,IAAI,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC;IAC5C,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,SAAiB;QAChC,OAAO,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,SAAiB;QAChC,OAAO,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAClD,CAAC;IAED;;OAEG;IACH,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,cAAc;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED;;OAEG;IACH,iBAAiB;QACf,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED,8EAA8E;IAC9E,kBAAkB;IAClB,8EAA8E;IAEtE,UAAU,CAAC,YAAoB;QACrC,0DAA0D;QAC1D,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9D,OAAO,eAAe,CAAC,YAAY,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAC1C,CAAC;CACF"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Manager — manages the lifecycle of governance sessions.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the batch Job model with a streaming, action-at-a-time model.
|
|
5
|
+
* Each session tracks an ongoing interaction between an agent and the
|
|
6
|
+
* control plane, evaluating actions one at a time against a policy.
|
|
7
|
+
*
|
|
8
|
+
* Key behaviors:
|
|
9
|
+
* - evaluate(): checks a single action against policy + session state
|
|
10
|
+
* - recordResult(): records the outcome of an externally-executed action
|
|
11
|
+
* - terminate(): ends the session and generates a report
|
|
12
|
+
* - Budget tracking is cumulative across the session
|
|
13
|
+
*/
|
|
14
|
+
import type { ActionRequest, ActionResult, EvaluateResponse, GateDecision, Policy, Session, SessionAction, SessionReport, SessionState } from '../types.js';
|
|
15
|
+
import { GateManager } from './gate.js';
|
|
16
|
+
import { EvidenceLedger } from '../ledger/ledger.js';
|
|
17
|
+
export interface SessionManagerConfig {
|
|
18
|
+
/** Directory for ledger files. One file per session. */
|
|
19
|
+
ledgerDir: string;
|
|
20
|
+
/** Gate manager for handling approval flows */
|
|
21
|
+
gateManager: GateManager;
|
|
22
|
+
/** Callback invoked when a gate is requested */
|
|
23
|
+
onGateRequest?: (sessionId: string, actionId: string, gate: SessionAction['validation']['gate']) => void;
|
|
24
|
+
/** Callback invoked when session state changes */
|
|
25
|
+
onStateChange?: (sessionId: string, from: SessionState, to: SessionState) => void;
|
|
26
|
+
/** Callback invoked when a session is terminated */
|
|
27
|
+
onSessionTerminated?: (sessionId: string, report: SessionReport) => void;
|
|
28
|
+
}
|
|
29
|
+
export declare class SessionManager {
|
|
30
|
+
private sessions;
|
|
31
|
+
private config;
|
|
32
|
+
constructor(config: SessionManagerConfig);
|
|
33
|
+
/**
|
|
34
|
+
* Create a new session with a loaded policy.
|
|
35
|
+
*/
|
|
36
|
+
createSession(policy: Policy, metadata?: Record<string, unknown>): Promise<Session>;
|
|
37
|
+
/**
|
|
38
|
+
* Evaluate a single action against the session's policy + state.
|
|
39
|
+
* Returns allow/deny/gate verdict without executing the action.
|
|
40
|
+
*/
|
|
41
|
+
evaluate(sessionId: string, action: ActionRequest): Promise<EvaluateResponse>;
|
|
42
|
+
/**
|
|
43
|
+
* Record the result of an externally-executed action.
|
|
44
|
+
*/
|
|
45
|
+
recordResult(sessionId: string, actionId: string, result: ActionResult): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a gate (approve or reject a pending action).
|
|
48
|
+
*/
|
|
49
|
+
resolveGate(sessionId: string, actionId: string, decision: GateDecision, respondedBy?: string, reason?: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Terminate a session.
|
|
52
|
+
*/
|
|
53
|
+
terminate(sessionId: string, reason?: string): Promise<SessionReport>;
|
|
54
|
+
/**
|
|
55
|
+
* Get a session by ID.
|
|
56
|
+
*/
|
|
57
|
+
getSession(sessionId: string): Session | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* List all sessions.
|
|
60
|
+
*/
|
|
61
|
+
listSessions(): Session[];
|
|
62
|
+
/**
|
|
63
|
+
* Get the ledger for a session.
|
|
64
|
+
*/
|
|
65
|
+
getLedger(sessionId: string): EvidenceLedger | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Generate a report for a session.
|
|
68
|
+
*/
|
|
69
|
+
getReport(sessionId: string): SessionReport;
|
|
70
|
+
private getEntryRequired;
|
|
71
|
+
private generateReport;
|
|
72
|
+
private getBudgetSnapshot;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/engine/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EAGZ,gBAAgB,EAChB,YAAY,EACZ,MAAM,EACN,OAAO,EACP,aAAa,EACb,aAAa,EACb,YAAY,EACb,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,WAAW,EAAE,WAAW,CAAC;IACzB,gDAAgD;IAChD,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACzG,kDAAkD;IAClD,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,KAAK,IAAI,CAAC;IAClF,oDAAoD;IACpD,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;CAC1E;AASD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,MAAM,CAAuB;gBAEzB,MAAM,EAAE,oBAAoB;IAQxC;;OAEG;IACG,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,OAAO,CAAC;IA4CnB;;;OAGG;IACG,QAAQ,CACZ,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,gBAAgB,CAAC;IAkI5B;;OAEG;IACG,YAAY,CAChB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC;IAsChB;;OAEG;IACG,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,YAAY,EACtB,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IAgChB;;OAEG;IACG,SAAS,CACb,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,aAAa,CAAC;IAqCzB;;OAEG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAIlD;;OAEG;IACH,YAAY,IAAI,OAAO,EAAE;IAIzB;;OAEG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAIxD;;OAEG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa;IAS3C,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,iBAAiB;CAW1B"}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Manager — manages the lifecycle of governance sessions.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the batch Job model with a streaming, action-at-a-time model.
|
|
5
|
+
* Each session tracks an ongoing interaction between an agent and the
|
|
6
|
+
* control plane, evaluating actions one at a time against a policy.
|
|
7
|
+
*
|
|
8
|
+
* Key behaviors:
|
|
9
|
+
* - evaluate(): checks a single action against policy + session state
|
|
10
|
+
* - recordResult(): records the outcome of an externally-executed action
|
|
11
|
+
* - terminate(): ends the session and generates a report
|
|
12
|
+
* - Budget tracking is cumulative across the session
|
|
13
|
+
*/
|
|
14
|
+
import { nanoid } from 'nanoid';
|
|
15
|
+
import { evaluateSessionAction } from '../policy/evaluator.js';
|
|
16
|
+
import { EvidenceLedger } from '../ledger/ledger.js';
|
|
17
|
+
export class SessionManager {
|
|
18
|
+
sessions = new Map();
|
|
19
|
+
config;
|
|
20
|
+
constructor(config) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
}
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Session lifecycle
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* Create a new session with a loaded policy.
|
|
28
|
+
*/
|
|
29
|
+
async createSession(policy, metadata) {
|
|
30
|
+
const id = nanoid(16);
|
|
31
|
+
const now = new Date().toISOString();
|
|
32
|
+
const budget = {
|
|
33
|
+
startedAt: Date.now(),
|
|
34
|
+
filesChanged: 0,
|
|
35
|
+
totalOutputBytes: 0,
|
|
36
|
+
retries: 0,
|
|
37
|
+
costUsd: 0,
|
|
38
|
+
actionsEvaluated: 0,
|
|
39
|
+
actionsDenied: 0,
|
|
40
|
+
};
|
|
41
|
+
const session = {
|
|
42
|
+
id,
|
|
43
|
+
policy,
|
|
44
|
+
state: 'active',
|
|
45
|
+
budget,
|
|
46
|
+
actions: [],
|
|
47
|
+
metadata,
|
|
48
|
+
createdAt: now,
|
|
49
|
+
updatedAt: now,
|
|
50
|
+
};
|
|
51
|
+
// Initialize ledger
|
|
52
|
+
const ledger = new EvidenceLedger(`${this.config.ledgerDir}/${id}.jsonl`);
|
|
53
|
+
await ledger.init();
|
|
54
|
+
await ledger.append(id, 'session:start', {
|
|
55
|
+
policy: policy.name,
|
|
56
|
+
version: policy.version,
|
|
57
|
+
metadata: metadata ?? {},
|
|
58
|
+
});
|
|
59
|
+
this.sessions.set(id, {
|
|
60
|
+
session,
|
|
61
|
+
ledger,
|
|
62
|
+
recentActionTimestamps: [],
|
|
63
|
+
});
|
|
64
|
+
return session;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Evaluate a single action against the session's policy + state.
|
|
68
|
+
* Returns allow/deny/gate verdict without executing the action.
|
|
69
|
+
*/
|
|
70
|
+
async evaluate(sessionId, action) {
|
|
71
|
+
const entry = this.getEntryRequired(sessionId);
|
|
72
|
+
const { session, ledger } = entry;
|
|
73
|
+
// Generate action ID
|
|
74
|
+
const actionId = nanoid(12);
|
|
75
|
+
const actionIndex = session.actions.length;
|
|
76
|
+
// Evaluate against policy + session state
|
|
77
|
+
const result = evaluateSessionAction(action, session.policy, session);
|
|
78
|
+
// Update budget counters
|
|
79
|
+
session.budget.actionsEvaluated++;
|
|
80
|
+
if (result.verdict === 'deny') {
|
|
81
|
+
session.budget.actionsDenied++;
|
|
82
|
+
}
|
|
83
|
+
// Create session action record
|
|
84
|
+
const sessionAction = {
|
|
85
|
+
id: actionId,
|
|
86
|
+
index: actionIndex,
|
|
87
|
+
request: action,
|
|
88
|
+
validation: result,
|
|
89
|
+
timestamp: new Date().toISOString(),
|
|
90
|
+
};
|
|
91
|
+
session.actions.push(sessionAction);
|
|
92
|
+
session.updatedAt = new Date().toISOString();
|
|
93
|
+
// Track timestamp for rate limiting
|
|
94
|
+
entry.recentActionTimestamps.push(Date.now());
|
|
95
|
+
// Prune timestamps older than 2 minutes
|
|
96
|
+
const twoMinutesAgo = Date.now() - 120_000;
|
|
97
|
+
entry.recentActionTimestamps = entry.recentActionTimestamps.filter((t) => t >= twoMinutesAgo);
|
|
98
|
+
// Log to ledger
|
|
99
|
+
await ledger.append(sessionId, 'action:evaluate', {
|
|
100
|
+
actionId,
|
|
101
|
+
actionIndex,
|
|
102
|
+
tool: action.tool,
|
|
103
|
+
verdict: result.verdict,
|
|
104
|
+
reasons: result.reasons,
|
|
105
|
+
});
|
|
106
|
+
// Handle gate verdict
|
|
107
|
+
if (result.verdict === 'gate' && result.gate) {
|
|
108
|
+
const prevState = session.state;
|
|
109
|
+
session.state = 'paused';
|
|
110
|
+
this.config.onStateChange?.(sessionId, prevState, 'paused');
|
|
111
|
+
// Request approval through gate manager
|
|
112
|
+
const gateResponse = await this.config.gateManager.requestApproval(sessionId, actionId, action, result.gate);
|
|
113
|
+
await ledger.append(sessionId, 'gate:requested', {
|
|
114
|
+
actionId,
|
|
115
|
+
tool: action.tool,
|
|
116
|
+
gate: result.gate,
|
|
117
|
+
});
|
|
118
|
+
// If gate was auto-approved or handler-approved, resume
|
|
119
|
+
if (gateResponse.decision === 'approved') {
|
|
120
|
+
session.state = 'active';
|
|
121
|
+
this.config.onStateChange?.(sessionId, 'paused', 'active');
|
|
122
|
+
await ledger.append(sessionId, 'gate:approved', {
|
|
123
|
+
actionId,
|
|
124
|
+
respondedBy: gateResponse.respondedBy,
|
|
125
|
+
reason: gateResponse.reason,
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
actionId,
|
|
129
|
+
decision: 'allow',
|
|
130
|
+
reasons: [`Gate approved: ${gateResponse.reason ?? 'approved'}`],
|
|
131
|
+
budgetRemaining: this.getBudgetSnapshot(session),
|
|
132
|
+
warnings: result.warnings,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (gateResponse.decision === 'rejected') {
|
|
136
|
+
session.state = 'active';
|
|
137
|
+
this.config.onStateChange?.(sessionId, 'paused', 'active');
|
|
138
|
+
await ledger.append(sessionId, 'gate:rejected', {
|
|
139
|
+
actionId,
|
|
140
|
+
respondedBy: gateResponse.respondedBy,
|
|
141
|
+
reason: gateResponse.reason,
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
actionId,
|
|
145
|
+
decision: 'deny',
|
|
146
|
+
reasons: [`Gate rejected: ${gateResponse.reason ?? 'rejected'}`],
|
|
147
|
+
budgetRemaining: this.getBudgetSnapshot(session),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// Decision is 'pending' — session stays paused
|
|
151
|
+
this.config.onGateRequest?.(sessionId, actionId, result.gate);
|
|
152
|
+
return {
|
|
153
|
+
actionId,
|
|
154
|
+
decision: 'gate',
|
|
155
|
+
reasons: result.reasons,
|
|
156
|
+
gate: result.gate,
|
|
157
|
+
budgetRemaining: this.getBudgetSnapshot(session),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// Check if session should be auto-terminated (max denials exceeded)
|
|
161
|
+
if (session.policy.session?.max_denials != null &&
|
|
162
|
+
session.budget.actionsDenied >= session.policy.session.max_denials) {
|
|
163
|
+
await this.terminate(sessionId, `Maximum denial limit reached (${session.budget.actionsDenied})`);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
actionId,
|
|
167
|
+
decision: result.verdict,
|
|
168
|
+
reasons: result.reasons,
|
|
169
|
+
budgetRemaining: this.getBudgetSnapshot(session),
|
|
170
|
+
warnings: result.warnings,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Record the result of an externally-executed action.
|
|
175
|
+
*/
|
|
176
|
+
async recordResult(sessionId, actionId, result) {
|
|
177
|
+
const entry = this.getEntryRequired(sessionId);
|
|
178
|
+
const { session, ledger } = entry;
|
|
179
|
+
const action = session.actions.find((a) => a.id === actionId);
|
|
180
|
+
if (!action) {
|
|
181
|
+
throw new Error(`Action "${actionId}" not found in session "${sessionId}"`);
|
|
182
|
+
}
|
|
183
|
+
if (action.result) {
|
|
184
|
+
throw new Error(`Result already recorded for action "${actionId}"`);
|
|
185
|
+
}
|
|
186
|
+
action.result = result;
|
|
187
|
+
session.updatedAt = new Date().toISOString();
|
|
188
|
+
// Update budget based on result
|
|
189
|
+
if (result.artifacts) {
|
|
190
|
+
for (const artifact of result.artifacts) {
|
|
191
|
+
if (artifact.type === 'diff' || artifact.type === 'checksum') {
|
|
192
|
+
session.budget.filesChanged++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (result.output != null) {
|
|
197
|
+
const outputSize = JSON.stringify(result.output).length;
|
|
198
|
+
session.budget.totalOutputBytes += outputSize;
|
|
199
|
+
}
|
|
200
|
+
await ledger.append(sessionId, 'action:result', {
|
|
201
|
+
actionId,
|
|
202
|
+
tool: action.request.tool,
|
|
203
|
+
success: result.success,
|
|
204
|
+
durationMs: result.durationMs,
|
|
205
|
+
error: result.error,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Resolve a gate (approve or reject a pending action).
|
|
210
|
+
*/
|
|
211
|
+
async resolveGate(sessionId, actionId, decision, respondedBy, reason) {
|
|
212
|
+
const entry = this.getEntryRequired(sessionId);
|
|
213
|
+
const { session, ledger } = entry;
|
|
214
|
+
// Resolve through gate manager
|
|
215
|
+
const response = this.config.gateManager.resolve(sessionId, actionId, decision, respondedBy, reason);
|
|
216
|
+
const eventType = decision === 'approved' ? 'gate:approved' : 'gate:rejected';
|
|
217
|
+
await ledger.append(sessionId, eventType, {
|
|
218
|
+
actionId,
|
|
219
|
+
respondedBy: response.respondedBy,
|
|
220
|
+
reason: response.reason,
|
|
221
|
+
});
|
|
222
|
+
// Resume session if no more pending gates
|
|
223
|
+
if (session.state === 'paused') {
|
|
224
|
+
const pending = this.config.gateManager.getPendingForSession(sessionId);
|
|
225
|
+
if (pending.length === 0) {
|
|
226
|
+
const prevState = session.state;
|
|
227
|
+
session.state = 'active';
|
|
228
|
+
session.updatedAt = new Date().toISOString();
|
|
229
|
+
this.config.onStateChange?.(sessionId, prevState, 'active');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Terminate a session.
|
|
235
|
+
*/
|
|
236
|
+
async terminate(sessionId, reason) {
|
|
237
|
+
const entry = this.getEntryRequired(sessionId);
|
|
238
|
+
const { session, ledger } = entry;
|
|
239
|
+
const prevState = session.state;
|
|
240
|
+
session.state = 'terminated';
|
|
241
|
+
session.terminatedAt = new Date().toISOString();
|
|
242
|
+
session.terminationReason = reason;
|
|
243
|
+
session.updatedAt = session.terminatedAt;
|
|
244
|
+
this.config.onStateChange?.(sessionId, prevState, 'terminated');
|
|
245
|
+
// Clear any pending gates
|
|
246
|
+
this.config.gateManager.clearSession(sessionId);
|
|
247
|
+
const report = this.generateReport(session);
|
|
248
|
+
await ledger.append(sessionId, 'session:terminate', {
|
|
249
|
+
reason,
|
|
250
|
+
totalActions: report.totalActions,
|
|
251
|
+
allowed: report.allowed,
|
|
252
|
+
denied: report.denied,
|
|
253
|
+
gated: report.gated,
|
|
254
|
+
durationMs: report.durationMs,
|
|
255
|
+
});
|
|
256
|
+
await ledger.close();
|
|
257
|
+
this.config.onSessionTerminated?.(sessionId, report);
|
|
258
|
+
return report;
|
|
259
|
+
}
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Introspection
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
/**
|
|
264
|
+
* Get a session by ID.
|
|
265
|
+
*/
|
|
266
|
+
getSession(sessionId) {
|
|
267
|
+
return this.sessions.get(sessionId)?.session;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* List all sessions.
|
|
271
|
+
*/
|
|
272
|
+
listSessions() {
|
|
273
|
+
return Array.from(this.sessions.values()).map((e) => e.session);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Get the ledger for a session.
|
|
277
|
+
*/
|
|
278
|
+
getLedger(sessionId) {
|
|
279
|
+
return this.sessions.get(sessionId)?.ledger;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Generate a report for a session.
|
|
283
|
+
*/
|
|
284
|
+
getReport(sessionId) {
|
|
285
|
+
const entry = this.getEntryRequired(sessionId);
|
|
286
|
+
return this.generateReport(entry.session);
|
|
287
|
+
}
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// Private helpers
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
getEntryRequired(sessionId) {
|
|
292
|
+
const entry = this.sessions.get(sessionId);
|
|
293
|
+
if (!entry) {
|
|
294
|
+
throw new Error(`Session "${sessionId}" not found`);
|
|
295
|
+
}
|
|
296
|
+
return entry;
|
|
297
|
+
}
|
|
298
|
+
generateReport(session) {
|
|
299
|
+
const startedAt = new Date(session.createdAt).getTime();
|
|
300
|
+
const endedAt = session.terminatedAt
|
|
301
|
+
? new Date(session.terminatedAt).getTime()
|
|
302
|
+
: Date.now();
|
|
303
|
+
let allowed = 0;
|
|
304
|
+
let denied = 0;
|
|
305
|
+
let gated = 0;
|
|
306
|
+
for (const action of session.actions) {
|
|
307
|
+
switch (action.validation.verdict) {
|
|
308
|
+
case 'allow':
|
|
309
|
+
allowed++;
|
|
310
|
+
break;
|
|
311
|
+
case 'deny':
|
|
312
|
+
denied++;
|
|
313
|
+
break;
|
|
314
|
+
case 'gate':
|
|
315
|
+
gated++;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
sessionId: session.id,
|
|
321
|
+
state: session.state,
|
|
322
|
+
totalActions: session.actions.length,
|
|
323
|
+
allowed,
|
|
324
|
+
denied,
|
|
325
|
+
gated,
|
|
326
|
+
durationMs: endedAt - startedAt,
|
|
327
|
+
budgetUsed: { ...session.budget },
|
|
328
|
+
actions: session.actions,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
getBudgetSnapshot(session) {
|
|
332
|
+
const elapsed = Date.now() - session.budget.startedAt;
|
|
333
|
+
return {
|
|
334
|
+
runtimeMs: elapsed,
|
|
335
|
+
filesChanged: session.budget.filesChanged,
|
|
336
|
+
totalOutputBytes: session.budget.totalOutputBytes,
|
|
337
|
+
actionsEvaluated: session.budget.actionsEvaluated,
|
|
338
|
+
actionsDenied: session.budget.actionsDenied,
|
|
339
|
+
costUsd: session.budget.costUsd,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=session.js.map
|