@gotgenes/pi-permission-system 5.5.0 → 5.6.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/CHANGELOG.md +23 -0
- package/package.json +1 -1
- package/src/handlers/before-agent-start.ts +7 -7
- package/src/handlers/gates/bash-external-directory.ts +70 -67
- package/src/handlers/gates/descriptor.ts +115 -0
- package/src/handlers/gates/external-directory.ts +62 -130
- package/src/handlers/gates/index.ts +12 -4
- package/src/handlers/gates/runner.ts +144 -0
- package/src/handlers/gates/skill-read.ts +40 -59
- package/src/handlers/gates/tool.ts +35 -104
- package/src/handlers/gates/types.ts +0 -2
- package/src/handlers/input.ts +3 -3
- package/src/handlers/lifecycle.ts +21 -21
- package/src/handlers/tool-call.ts +121 -20
- package/src/handlers/types.ts +20 -7
- package/src/index.ts +6 -1
- package/src/runtime.ts +17 -9
- package/tests/handlers/before-agent-start.test.ts +17 -27
- package/tests/handlers/gates/bash-external-directory.test.ts +129 -184
- package/tests/handlers/gates/external-directory.test.ts +118 -264
- package/tests/handlers/gates/runner.test.ts +361 -0
- package/tests/handlers/gates/skill-read.test.ts +86 -137
- package/tests/handlers/gates/tool.test.ts +109 -346
- package/tests/handlers/input-events.test.ts +10 -21
- package/tests/handlers/input.test.ts +26 -43
- package/tests/handlers/lifecycle.test.ts +47 -66
- package/tests/handlers/tool-call-events.test.ts +29 -40
- package/tests/handlers/tool-call.test.ts +19 -30
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
import { toRecord } from "../common";
|
|
4
|
+
import { emitDecisionEvent } from "../permission-events";
|
|
4
5
|
import {
|
|
5
6
|
formatMissingToolNameReason,
|
|
6
7
|
formatUnknownToolReason,
|
|
@@ -9,12 +10,15 @@ import {
|
|
|
9
10
|
checkRequestedToolRegistration,
|
|
10
11
|
getToolNameFromValue,
|
|
11
12
|
} from "../tool-registry";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
13
|
+
import { describeBashExternalDirectoryGate } from "./gates/bash-external-directory";
|
|
14
|
+
import type { GateRunnerDeps } from "./gates/descriptor";
|
|
15
|
+
import { isGateBypass } from "./gates/descriptor";
|
|
16
|
+
import { describeExternalDirectoryGate } from "./gates/external-directory";
|
|
17
|
+
import { runGateCheck } from "./gates/runner";
|
|
18
|
+
import { describeSkillReadGate } from "./gates/skill-read";
|
|
19
|
+
import { describeToolGate } from "./gates/tool";
|
|
16
20
|
import type { ToolCallContext } from "./gates/types";
|
|
17
|
-
import type { HandlerDeps } from "./types";
|
|
21
|
+
import type { HandlerDeps, PromptPermissionDetails } from "./types";
|
|
18
22
|
|
|
19
23
|
/**
|
|
20
24
|
* Extract the tool input from an event, checking both `input` and `arguments`
|
|
@@ -39,7 +43,7 @@ export async function handleToolCall(
|
|
|
39
43
|
event: unknown,
|
|
40
44
|
ctx: ExtensionContext,
|
|
41
45
|
): Promise<{ block?: true; reason?: string }> {
|
|
42
|
-
deps.
|
|
46
|
+
deps.session.runtimeContext = ctx;
|
|
43
47
|
deps.startForwardedPermissionPolling(ctx);
|
|
44
48
|
|
|
45
49
|
const agentName = deps.resolveAgentName(ctx);
|
|
@@ -81,26 +85,123 @@ export async function handleToolCall(
|
|
|
81
85
|
cwd: ctx.cwd,
|
|
82
86
|
};
|
|
83
87
|
|
|
84
|
-
// ──
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
// ── Shared gate adapter closures ───────────────────────────────────────
|
|
89
|
+
const canConfirm = () => deps.canRequestPermissionConfirmation(ctx);
|
|
90
|
+
const promptPermission = (details: PromptPermissionDetails) =>
|
|
91
|
+
deps.promptPermission(ctx, details);
|
|
92
|
+
const emitDecision: GateRunnerDeps["emitDecision"] = (e) =>
|
|
93
|
+
emitDecisionEvent(deps.events, e);
|
|
94
|
+
const { writeReviewLog } = deps;
|
|
95
|
+
const checkPermission: GateRunnerDeps["checkPermission"] = (
|
|
96
|
+
surface,
|
|
97
|
+
input,
|
|
98
|
+
agent,
|
|
99
|
+
sessionRules,
|
|
100
|
+
) =>
|
|
101
|
+
deps.session.permissionManager.checkPermission(
|
|
102
|
+
surface,
|
|
103
|
+
input,
|
|
104
|
+
agent,
|
|
105
|
+
sessionRules,
|
|
106
|
+
);
|
|
107
|
+
const getSessionRuleset = () => deps.session.sessionRules.getRuleset();
|
|
108
|
+
const approveSessionRule = (surface: string, pattern: string) =>
|
|
109
|
+
deps.session.sessionRules.approve(surface, pattern);
|
|
110
|
+
|
|
111
|
+
// ── Shared runner deps (built once, reused for all gates) ─────────────
|
|
112
|
+
const runnerDeps: GateRunnerDeps = {
|
|
113
|
+
checkPermission,
|
|
114
|
+
getSessionRuleset,
|
|
115
|
+
approveSessionRule,
|
|
116
|
+
writeReviewLog,
|
|
117
|
+
emitDecision,
|
|
118
|
+
canConfirm,
|
|
119
|
+
promptPermission,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// ── Skill-read gate (descriptor + runner) ────────────────────────────────
|
|
123
|
+
const skillDescriptor = describeSkillReadGate(
|
|
124
|
+
tcc,
|
|
125
|
+
() => deps.session.activeSkillEntries,
|
|
126
|
+
);
|
|
127
|
+
if (skillDescriptor) {
|
|
128
|
+
const skillResult = await runGateCheck(
|
|
129
|
+
skillDescriptor,
|
|
130
|
+
tcc.agentName,
|
|
131
|
+
tcc.toolCallId,
|
|
132
|
+
runnerDeps,
|
|
133
|
+
);
|
|
134
|
+
if (skillResult.action === "block") {
|
|
135
|
+
return { block: true, reason: skillResult.reason };
|
|
136
|
+
}
|
|
88
137
|
}
|
|
89
138
|
|
|
90
|
-
// ── External-directory gate (
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
139
|
+
// ── External-directory gate (descriptor + runner) ─────────────────────────
|
|
140
|
+
const infraDirs = [
|
|
141
|
+
...deps.piInfrastructureDirs,
|
|
142
|
+
...deps.getPiInfrastructureReadPaths(),
|
|
143
|
+
];
|
|
144
|
+
const extDirDesc = describeExternalDirectoryGate(tcc, infraDirs);
|
|
145
|
+
if (extDirDesc) {
|
|
146
|
+
if (isGateBypass(extDirDesc)) {
|
|
147
|
+
if (extDirDesc.log) {
|
|
148
|
+
writeReviewLog(extDirDesc.log.event, extDirDesc.log.details);
|
|
149
|
+
}
|
|
150
|
+
if (extDirDesc.decision) {
|
|
151
|
+
emitDecision(extDirDesc.decision);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
const extDirResult = await runGateCheck(
|
|
155
|
+
extDirDesc,
|
|
156
|
+
tcc.agentName,
|
|
157
|
+
tcc.toolCallId,
|
|
158
|
+
runnerDeps,
|
|
159
|
+
);
|
|
160
|
+
if (extDirResult.action === "block") {
|
|
161
|
+
return { block: true, reason: extDirResult.reason };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
94
164
|
}
|
|
95
165
|
|
|
96
|
-
// ── Bash external-directory gate
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
166
|
+
// ── Bash external-directory gate (descriptor + runner) ─────────────────────
|
|
167
|
+
const bashExtDesc = await describeBashExternalDirectoryGate(
|
|
168
|
+
tcc,
|
|
169
|
+
checkPermission,
|
|
170
|
+
getSessionRuleset,
|
|
171
|
+
);
|
|
172
|
+
if (bashExtDesc) {
|
|
173
|
+
if (isGateBypass(bashExtDesc)) {
|
|
174
|
+
if (bashExtDesc.log) {
|
|
175
|
+
writeReviewLog(bashExtDesc.log.event, bashExtDesc.log.details);
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
const bashExtResult = await runGateCheck(
|
|
179
|
+
bashExtDesc,
|
|
180
|
+
tcc.agentName,
|
|
181
|
+
tcc.toolCallId,
|
|
182
|
+
runnerDeps,
|
|
183
|
+
);
|
|
184
|
+
if (bashExtResult.action === "block") {
|
|
185
|
+
return { block: true, reason: bashExtResult.reason };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
100
188
|
}
|
|
101
189
|
|
|
102
|
-
// ── Normal tool permission gate
|
|
103
|
-
const
|
|
190
|
+
// ── Normal tool permission gate (descriptor + runner) ───────────────────────────
|
|
191
|
+
const toolCheck = checkPermission(
|
|
192
|
+
tcc.toolName,
|
|
193
|
+
tcc.input,
|
|
194
|
+
tcc.agentName ?? undefined,
|
|
195
|
+
getSessionRuleset(),
|
|
196
|
+
);
|
|
197
|
+
const toolDescriptor = describeToolGate(tcc, toolCheck);
|
|
198
|
+
toolDescriptor.preCheck = toolCheck;
|
|
199
|
+
const toolResult = await runGateCheck(
|
|
200
|
+
toolDescriptor,
|
|
201
|
+
tcc.agentName,
|
|
202
|
+
tcc.toolCallId,
|
|
203
|
+
runnerDeps,
|
|
204
|
+
);
|
|
104
205
|
if (toolResult.action === "block") {
|
|
105
206
|
return { block: true, reason: toolResult.reason };
|
|
106
207
|
}
|
package/src/handlers/types.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
|
3
3
|
import type { PermissionPromptDecision } from "../permission-dialog";
|
|
4
4
|
import type { PermissionEventBus } from "../permission-events";
|
|
5
5
|
import type { PermissionManager } from "../permission-manager";
|
|
6
|
-
import type {
|
|
6
|
+
import type { SessionState } from "../runtime";
|
|
7
7
|
|
|
8
8
|
export type PermissionReviewSource = "tool_call" | "skill_input" | "skill_read";
|
|
9
9
|
|
|
@@ -27,13 +27,26 @@ export interface PromptPermissionDetails {
|
|
|
27
27
|
/**
|
|
28
28
|
* Explicit dependency bag passed to each extracted event handler.
|
|
29
29
|
*
|
|
30
|
-
* Mutable state lives in `
|
|
31
|
-
* directly
|
|
30
|
+
* Mutable session state lives in `session`; handlers read and write
|
|
31
|
+
* `deps.session.*` directly. Logging, infrastructure paths, and the
|
|
32
|
+
* event bus are promoted to top-level fields so handlers and gate
|
|
33
|
+
* adapters never reach through nested objects for leaf operations.
|
|
32
34
|
*/
|
|
33
35
|
export interface HandlerDeps {
|
|
34
|
-
// ──
|
|
35
|
-
/**
|
|
36
|
-
readonly
|
|
36
|
+
// ── Session state ─────────────────────────────────────────────────────
|
|
37
|
+
/** Mutable session state: permissionManager, sessionRules, cache keys. */
|
|
38
|
+
readonly session: SessionState;
|
|
39
|
+
|
|
40
|
+
// ── Logging (promoted from runtime) ───────────────────────────────────
|
|
41
|
+
writeDebugLog(event: string, details?: Record<string, unknown>): void;
|
|
42
|
+
writeReviewLog(event: string, details?: Record<string, unknown>): void;
|
|
43
|
+
|
|
44
|
+
// ── Immutable infrastructure paths ───────────────────────────────────
|
|
45
|
+
readonly piInfrastructureDirs: string[];
|
|
46
|
+
/** Returns config-derived infrastructure read paths (current at call time). */
|
|
47
|
+
getPiInfrastructureReadPaths(): string[];
|
|
48
|
+
|
|
49
|
+
// ── Event bus ────────────────────────────────────────────────────────
|
|
37
50
|
/** Event bus for emitting permissions:decision broadcast events. */
|
|
38
51
|
readonly events: PermissionEventBus;
|
|
39
52
|
|
|
@@ -54,7 +67,7 @@ export interface HandlerDeps {
|
|
|
54
67
|
// ── Permission helpers ─────────────────────────────────────────────────
|
|
55
68
|
/**
|
|
56
69
|
* Resolve the active agent name from the session context or system prompt.
|
|
57
|
-
* Updates
|
|
70
|
+
* Updates session.lastKnownActiveAgentName as a side effect.
|
|
58
71
|
*/
|
|
59
72
|
resolveAgentName(ctx: ExtensionContext, systemPrompt?: string): string | null;
|
|
60
73
|
/** Whether the current context can show an interactive permission prompt. */
|
package/src/index.ts
CHANGED
|
@@ -78,7 +78,12 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
const deps: HandlerDeps = {
|
|
81
|
-
runtime,
|
|
81
|
+
session: runtime,
|
|
82
|
+
writeDebugLog: (event, details) => runtime.writeDebugLog(event, details),
|
|
83
|
+
writeReviewLog: (event, details) => runtime.writeReviewLog(event, details),
|
|
84
|
+
piInfrastructureDirs: runtime.piInfrastructureDirs,
|
|
85
|
+
getPiInfrastructureReadPaths: () =>
|
|
86
|
+
runtime.config.piInfrastructureReadPaths ?? [],
|
|
82
87
|
events: pi.events,
|
|
83
88
|
createPermissionManagerForCwd: (cwd) =>
|
|
84
89
|
createPermissionManagerForCwd(runtime.agentDir, cwd),
|
package/src/runtime.ts
CHANGED
|
@@ -47,6 +47,21 @@ import type { SkillPromptEntry } from "./skill-prompt-sanitizer";
|
|
|
47
47
|
import { syncPermissionSystemStatus } from "./status";
|
|
48
48
|
import { isSubagentExecutionContext } from "./subagent-context";
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Mutable session state — the subset of ExtensionRuntime that handlers
|
|
52
|
+
* read and write. Lifecycle handlers reset fields here on session
|
|
53
|
+
* start/shutdown; gate adapters read permissionManager and sessionRules.
|
|
54
|
+
*/
|
|
55
|
+
export interface SessionState {
|
|
56
|
+
runtimeContext: ExtensionContext | null;
|
|
57
|
+
permissionManager: PermissionManager;
|
|
58
|
+
readonly sessionRules: SessionRules;
|
|
59
|
+
activeSkillEntries: SkillPromptEntry[];
|
|
60
|
+
lastKnownActiveAgentName: string | null;
|
|
61
|
+
lastActiveToolsCacheKey: string | null;
|
|
62
|
+
lastPromptStateCacheKey: string | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
/**
|
|
51
66
|
* Runtime context object created once inside `piPermissionSystemExtension()`.
|
|
52
67
|
*
|
|
@@ -58,7 +73,7 @@ import { isSubagentExecutionContext } from "./subagent-context";
|
|
|
58
73
|
* Tests construct this via `createExtensionRuntime({ agentDir: tmpDir })`
|
|
59
74
|
* without timing issues around `PI_CODING_AGENT_DIR`.
|
|
60
75
|
*/
|
|
61
|
-
export interface ExtensionRuntime {
|
|
76
|
+
export interface ExtensionRuntime extends SessionState {
|
|
62
77
|
// ── Immutable paths (derived from agentDir at construction) ───────────
|
|
63
78
|
readonly agentDir: string;
|
|
64
79
|
readonly sessionsDir: string;
|
|
@@ -74,16 +89,9 @@ export interface ExtensionRuntime {
|
|
|
74
89
|
*/
|
|
75
90
|
readonly piInfrastructureDirs: string[];
|
|
76
91
|
|
|
77
|
-
// ── Mutable state
|
|
92
|
+
// ── Mutable state (beyond SessionState) ───────────────────────────────────
|
|
78
93
|
config: PermissionSystemExtensionConfig;
|
|
79
|
-
runtimeContext: ExtensionContext | null;
|
|
80
|
-
permissionManager: PermissionManager;
|
|
81
|
-
activeSkillEntries: SkillPromptEntry[];
|
|
82
|
-
lastKnownActiveAgentName: string | null;
|
|
83
|
-
lastActiveToolsCacheKey: string | null;
|
|
84
|
-
lastPromptStateCacheKey: string | null;
|
|
85
94
|
lastConfigWarning: string | null;
|
|
86
|
-
readonly sessionRules: SessionRules;
|
|
87
95
|
|
|
88
96
|
// ── Forwarding polling state ───────────────────────────────────────────
|
|
89
97
|
permissionForwardingContext: ExtensionContext | null;
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "../../src/handlers/before-agent-start";
|
|
8
8
|
import type { HandlerDeps } from "../../src/handlers/types";
|
|
9
9
|
import type { PermissionManager } from "../../src/permission-manager";
|
|
10
|
-
import type {
|
|
10
|
+
import type { SessionState } from "../../src/runtime";
|
|
11
11
|
import type { SkillPromptEntry } from "../../src/skill-prompt-sanitizer";
|
|
12
12
|
|
|
13
13
|
// ── SDK stubs ──────────────────────────────────────────────────────────────
|
|
@@ -57,40 +57,30 @@ function makePm(
|
|
|
57
57
|
} as unknown as PermissionManager;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function
|
|
61
|
-
overrides: Partial<ExtensionRuntime> = {},
|
|
62
|
-
): ExtensionRuntime {
|
|
60
|
+
function makeSession(overrides: Partial<SessionState> = {}): SessionState {
|
|
63
61
|
return {
|
|
64
|
-
agentDir: "/test/agent",
|
|
65
|
-
sessionsDir: "/test/agent/sessions",
|
|
66
|
-
subagentSessionsDir: "/test/agent/subagent-sessions",
|
|
67
|
-
forwardingDir: "/test/agent/sessions/permission-forwarding",
|
|
68
|
-
globalLogsDir: "/test/agent/extensions/pi-permission-system/logs",
|
|
69
|
-
config: { debugLog: false, permissionReviewLog: true, yoloMode: false },
|
|
70
62
|
runtimeContext: null,
|
|
71
63
|
permissionManager: makePm() as unknown as PermissionManager,
|
|
72
64
|
activeSkillEntries: [] as SkillPromptEntry[],
|
|
73
65
|
lastKnownActiveAgentName: null,
|
|
74
66
|
lastActiveToolsCacheKey: null,
|
|
75
67
|
lastPromptStateCacheKey: null,
|
|
76
|
-
lastConfigWarning: null,
|
|
77
68
|
sessionRules: {
|
|
78
69
|
approve: vi.fn(),
|
|
79
70
|
getRuleset: vi.fn().mockReturnValue([]),
|
|
80
71
|
clear: vi.fn(),
|
|
81
|
-
} as unknown as
|
|
82
|
-
permissionForwardingContext: null,
|
|
83
|
-
permissionForwardingTimer: null,
|
|
84
|
-
isProcessingForwardedRequests: false,
|
|
85
|
-
writeDebugLog: vi.fn(),
|
|
86
|
-
writeReviewLog: vi.fn(),
|
|
72
|
+
} as unknown as SessionState["sessionRules"],
|
|
87
73
|
...overrides,
|
|
88
|
-
}
|
|
74
|
+
};
|
|
89
75
|
}
|
|
90
76
|
|
|
91
77
|
function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
|
|
92
78
|
return {
|
|
93
|
-
|
|
79
|
+
session: makeSession(),
|
|
80
|
+
writeDebugLog: vi.fn(),
|
|
81
|
+
writeReviewLog: vi.fn(),
|
|
82
|
+
piInfrastructureDirs: ["/test/agent", "/test/agent/git"],
|
|
83
|
+
getPiInfrastructureReadPaths: vi.fn().mockReturnValue([]),
|
|
94
84
|
createPermissionManagerForCwd: vi.fn().mockReturnValue(makePm()),
|
|
95
85
|
refreshExtensionConfig: vi.fn(),
|
|
96
86
|
notifyWarning: vi.fn(),
|
|
@@ -176,7 +166,7 @@ describe("handleBeforeAgentStart", () => {
|
|
|
176
166
|
it("filters out denied tools from allowed list", async () => {
|
|
177
167
|
const pm = makePm("deny");
|
|
178
168
|
const deps = makeDeps({
|
|
179
|
-
|
|
169
|
+
session: makeSession({
|
|
180
170
|
permissionManager: pm as unknown as PermissionManager,
|
|
181
171
|
}),
|
|
182
172
|
getAllTools: vi
|
|
@@ -191,7 +181,7 @@ describe("handleBeforeAgentStart", () => {
|
|
|
191
181
|
it("includes allowed and ask tools in the active list", async () => {
|
|
192
182
|
const pm = makePm("allow");
|
|
193
183
|
const deps = makeDeps({
|
|
194
|
-
|
|
184
|
+
session: makeSession({
|
|
195
185
|
permissionManager: pm as unknown as PermissionManager,
|
|
196
186
|
}),
|
|
197
187
|
getAllTools: vi
|
|
@@ -207,7 +197,7 @@ describe("handleBeforeAgentStart", () => {
|
|
|
207
197
|
getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
|
|
208
198
|
});
|
|
209
199
|
await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
|
|
210
|
-
expect(deps.
|
|
200
|
+
expect(deps.session.lastActiveToolsCacheKey).not.toBeNull();
|
|
211
201
|
});
|
|
212
202
|
|
|
213
203
|
it("skips setActiveTools when cache key is unchanged", async () => {
|
|
@@ -217,7 +207,7 @@ describe("handleBeforeAgentStart", () => {
|
|
|
217
207
|
);
|
|
218
208
|
const key = createActiveToolsCacheKey(["read"]);
|
|
219
209
|
const deps = makeDeps({
|
|
220
|
-
|
|
210
|
+
session: makeSession({ lastActiveToolsCacheKey: key }),
|
|
221
211
|
getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
|
|
222
212
|
});
|
|
223
213
|
await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
|
|
@@ -238,7 +228,7 @@ describe("handleBeforeAgentStart", () => {
|
|
|
238
228
|
);
|
|
239
229
|
// The prompt was modified, so systemPrompt should be returned
|
|
240
230
|
expect(result).toHaveProperty("systemPrompt");
|
|
241
|
-
expect(deps.
|
|
231
|
+
expect(deps.session.lastPromptStateCacheKey).not.toBeNull();
|
|
242
232
|
});
|
|
243
233
|
|
|
244
234
|
it("returns empty object when systemPrompt is unchanged", async () => {
|
|
@@ -259,7 +249,7 @@ describe("handleBeforeAgentStart", () => {
|
|
|
259
249
|
getAllTools: vi.fn().mockReturnValue([]),
|
|
260
250
|
});
|
|
261
251
|
await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
|
|
262
|
-
expect(deps.
|
|
252
|
+
expect(deps.session.activeSkillEntries).toEqual(expect.any(Array));
|
|
263
253
|
});
|
|
264
254
|
|
|
265
255
|
it("returns empty object and skips prompt work when prompt cache key is unchanged", async () => {
|
|
@@ -277,7 +267,7 @@ describe("handleBeforeAgentStart", () => {
|
|
|
277
267
|
allowedToolNames: allowedTools,
|
|
278
268
|
});
|
|
279
269
|
const deps = makeDeps({
|
|
280
|
-
|
|
270
|
+
session: makeSession({
|
|
281
271
|
permissionManager: pm as unknown as PermissionManager,
|
|
282
272
|
lastPromptStateCacheKey: key,
|
|
283
273
|
}),
|
|
@@ -286,6 +276,6 @@ describe("handleBeforeAgentStart", () => {
|
|
|
286
276
|
const result = await handleBeforeAgentStart(deps, makeEvent("hello"), ctx);
|
|
287
277
|
expect(result).toEqual({});
|
|
288
278
|
// activeSkillEntries was not assigned by the handler (early return)
|
|
289
|
-
expect(deps.
|
|
279
|
+
expect(deps.session.activeSkillEntries).toEqual([]);
|
|
290
280
|
});
|
|
291
281
|
});
|