@gotgenes/pi-permission-system 10.7.2 → 10.8.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 +12 -0
- package/package.json +1 -1
- package/src/before-agent-start-cache.ts +0 -7
- package/src/cache-key-gate.ts +32 -0
- package/src/handlers/before-agent-start.ts +21 -25
- package/src/permission-session.ts +9 -27
- package/test/before-agent-start-cache.test.ts +2 -32
- package/test/cache-key-gate.test.ts +85 -0
- package/test/handlers/before-agent-start.test.ts +6 -24
- package/test/permission-session.test.ts +30 -45
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [10.8.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.7.2...pi-permission-system-v10.8.0) (2026-06-10)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add CacheKeyGate for agent-start cache keys ([#365](https://github.com/gotgenes/pi-packages/issues/365)) ([e99285c](https://github.com/gotgenes/pi-packages/commit/e99285c50fef3f6fd8ea7dac00080eeb9957adaa))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
* mark Phase 5 Step 4 complete ([#365](https://github.com/gotgenes/pi-packages/issues/365)) ([4bd0e30](https://github.com/gotgenes/pi-packages/commit/4bd0e30fb4cb03d6cff76242f75955e9698c7d0d))
|
|
19
|
+
|
|
8
20
|
## [10.7.2](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.7.1...pi-permission-system-v10.7.2) (2026-06-10)
|
|
9
21
|
|
|
10
22
|
|
package/package.json
CHANGED
|
@@ -35,10 +35,3 @@ export function createBeforeAgentStartPromptStateKey(
|
|
|
35
35
|
normalizePrompt(input.systemPrompt),
|
|
36
36
|
]);
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
export function shouldApplyCachedAgentStartState(
|
|
40
|
-
previousKey: string | null,
|
|
41
|
-
nextKey: string,
|
|
42
|
-
): boolean {
|
|
43
|
-
return previousKey !== nextKey;
|
|
44
|
-
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns a previous cache key and conditionally runs an effect when the key changes.
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates the prev !== next comparison that previously lived in three places:
|
|
5
|
+
* the session's inline `!==`, the handler's ask-then-tell orchestration, and the
|
|
6
|
+
* (test-only-alive) `shouldApplyCachedAgentStartState` free function.
|
|
7
|
+
*
|
|
8
|
+
* Semantics:
|
|
9
|
+
* - On a changed key: runs `effect`, commits `nextKey`, returns the effect's value.
|
|
10
|
+
* - On an unchanged key: skips `effect`, returns `undefined`.
|
|
11
|
+
* - `reset()` re-arms the gate (used by session lifecycle: `resetForNewSession`,
|
|
12
|
+
* `shutdown`, `reload`).
|
|
13
|
+
*
|
|
14
|
+
* Commit ordering is run-then-commit: the key is saved only after `effect` returns.
|
|
15
|
+
* If `effect` throws, the key stays uncommitted and the next call retries.
|
|
16
|
+
*/
|
|
17
|
+
export class CacheKeyGate {
|
|
18
|
+
private previousKey: string | null = null;
|
|
19
|
+
|
|
20
|
+
runIfChanged<T>(nextKey: string, effect: () => T): T | undefined {
|
|
21
|
+
if (this.previousKey === nextKey) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const result = effect();
|
|
25
|
+
this.previousKey = nextKey;
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
reset(): void {
|
|
30
|
+
this.previousKey = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -74,10 +74,9 @@ export class AgentPrepHandler {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
const activeToolsCacheKey = createActiveToolsCacheKey(allowedTools);
|
|
77
|
-
|
|
77
|
+
this.session.activeToolsGate.runIfChanged(activeToolsCacheKey, () => {
|
|
78
78
|
this.toolRegistry.setActive(allowedTools);
|
|
79
|
-
|
|
80
|
-
}
|
|
79
|
+
});
|
|
81
80
|
|
|
82
81
|
const promptStateCacheKey = createBeforeAgentStartPromptStateKey({
|
|
83
82
|
agentName,
|
|
@@ -89,28 +88,25 @@ export class AgentPrepHandler {
|
|
|
89
88
|
allowedToolNames: allowedTools,
|
|
90
89
|
});
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
const promptResult = this.session.promptStateGate.runIfChanged(
|
|
92
|
+
promptStateCacheKey,
|
|
93
|
+
() => {
|
|
94
|
+
const toolPromptResult = sanitizeAvailableToolsSection(
|
|
95
|
+
event.systemPrompt,
|
|
96
|
+
allowedTools,
|
|
97
|
+
);
|
|
98
|
+
const skillPromptResult = resolveSkillPromptEntries(
|
|
99
|
+
toolPromptResult.prompt,
|
|
100
|
+
this.resolver,
|
|
101
|
+
agentName,
|
|
102
|
+
ctx.cwd,
|
|
103
|
+
);
|
|
104
|
+
this.session.setActiveSkillEntries(skillPromptResult.entries);
|
|
105
|
+
return skillPromptResult.prompt !== event.systemPrompt
|
|
106
|
+
? { systemPrompt: skillPromptResult.prompt }
|
|
107
|
+
: {};
|
|
108
|
+
},
|
|
101
109
|
);
|
|
102
|
-
|
|
103
|
-
toolPromptResult.prompt,
|
|
104
|
-
this.resolver,
|
|
105
|
-
agentName,
|
|
106
|
-
ctx.cwd,
|
|
107
|
-
);
|
|
108
|
-
this.session.setActiveSkillEntries(skillPromptResult.entries);
|
|
109
|
-
|
|
110
|
-
if (skillPromptResult.prompt !== event.systemPrompt) {
|
|
111
|
-
return { systemPrompt: skillPromptResult.prompt };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return {};
|
|
110
|
+
return promptResult ?? {};
|
|
115
111
|
}
|
|
116
112
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
|
|
2
|
+
import { CacheKeyGate } from "#src/cache-key-gate";
|
|
3
3
|
import {
|
|
4
4
|
getActiveAgentName,
|
|
5
5
|
getActiveAgentNameFromSystemPrompt,
|
|
@@ -38,8 +38,8 @@ export class PermissionSession implements ToolCallGateInputs {
|
|
|
38
38
|
private context: ExtensionContext | null = null;
|
|
39
39
|
private skillEntries: SkillPromptEntry[] = [];
|
|
40
40
|
private knownAgentName: string | null = null;
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
readonly activeToolsGate = new CacheKeyGate();
|
|
42
|
+
readonly promptStateGate = new CacheKeyGate();
|
|
43
43
|
|
|
44
44
|
constructor(
|
|
45
45
|
private readonly paths: ExtensionPaths,
|
|
@@ -89,8 +89,8 @@ export class PermissionSession implements ToolCallGateInputs {
|
|
|
89
89
|
resetForNewSession(ctx: ExtensionContext): void {
|
|
90
90
|
this.permissionManager.configureForCwd(ctx.cwd);
|
|
91
91
|
this.skillEntries = [];
|
|
92
|
-
this.
|
|
93
|
-
this.
|
|
92
|
+
this.activeToolsGate.reset();
|
|
93
|
+
this.promptStateGate.reset();
|
|
94
94
|
this.activate(ctx);
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -101,8 +101,8 @@ export class PermissionSession implements ToolCallGateInputs {
|
|
|
101
101
|
shutdown(): void {
|
|
102
102
|
this.sessionRules.clear();
|
|
103
103
|
this.skillEntries = [];
|
|
104
|
-
this.
|
|
105
|
-
this.
|
|
104
|
+
this.activeToolsGate.reset();
|
|
105
|
+
this.promptStateGate.reset();
|
|
106
106
|
this.deactivate();
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -113,26 +113,8 @@ export class PermissionSession implements ToolCallGateInputs {
|
|
|
113
113
|
reload(): void {
|
|
114
114
|
this.permissionManager.configureForCwd(this.context?.cwd);
|
|
115
115
|
this.skillEntries = [];
|
|
116
|
-
this.
|
|
117
|
-
this.
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ── Agent-start caching ────────────────────────────────────────────────
|
|
121
|
-
|
|
122
|
-
shouldUpdateActiveTools(cacheKey: string): boolean {
|
|
123
|
-
return this.toolsCacheKey !== cacheKey;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
commitActiveToolsCacheKey(cacheKey: string): void {
|
|
127
|
-
this.toolsCacheKey = cacheKey;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
shouldUpdatePromptState(cacheKey: string): boolean {
|
|
131
|
-
return this.promptCacheKey !== cacheKey;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
commitPromptStateCacheKey(cacheKey: string): void {
|
|
135
|
-
this.promptCacheKey = cacheKey;
|
|
116
|
+
this.activeToolsGate.reset();
|
|
117
|
+
this.promptStateGate.reset();
|
|
136
118
|
}
|
|
137
119
|
|
|
138
120
|
// ── Skill entries ──────────────────────────────────────────────────────
|
|
@@ -1,33 +1,8 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
2
|
import { expect, test } from "vitest";
|
|
3
|
-
import {
|
|
4
|
-
createActiveToolsCacheKey,
|
|
5
|
-
createBeforeAgentStartPromptStateKey,
|
|
6
|
-
shouldApplyCachedAgentStartState,
|
|
7
|
-
} from "#src/before-agent-start-cache";
|
|
3
|
+
import { createBeforeAgentStartPromptStateKey } from "#src/before-agent-start-cache";
|
|
8
4
|
import { createManager } from "#test/helpers/manager-harness";
|
|
9
5
|
|
|
10
|
-
test("Before-agent-start cache dedupes unchanged active-tool exposure and prompt state", () => {
|
|
11
|
-
const allowedTools = ["read", "mcp"];
|
|
12
|
-
const activeToolsKey = createActiveToolsCacheKey(allowedTools);
|
|
13
|
-
const promptStateKey = createBeforeAgentStartPromptStateKey({
|
|
14
|
-
agentName: "code",
|
|
15
|
-
cwd: "C:/workspace/project",
|
|
16
|
-
permissionStamp: "permissions-v1",
|
|
17
|
-
systemPrompt: "Available tools:\n- read\n- mcp",
|
|
18
|
-
allowedToolNames: allowedTools,
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
expect(shouldApplyCachedAgentStartState(null, activeToolsKey)).toBe(true);
|
|
22
|
-
expect(shouldApplyCachedAgentStartState(activeToolsKey, activeToolsKey)).toBe(
|
|
23
|
-
false,
|
|
24
|
-
);
|
|
25
|
-
expect(shouldApplyCachedAgentStartState(null, promptStateKey)).toBe(true);
|
|
26
|
-
expect(shouldApplyCachedAgentStartState(promptStateKey, promptStateKey)).toBe(
|
|
27
|
-
false,
|
|
28
|
-
);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
6
|
test("Before-agent-start prompt cache invalidates on permission changes while runtime enforcement stays authoritative", () => {
|
|
32
7
|
const { manager, globalConfigPath, cleanup } = createManager({
|
|
33
8
|
permission: { "*": "allow", write: "deny" },
|
|
@@ -43,9 +18,6 @@ test("Before-agent-start prompt cache invalidates on permission changes while ru
|
|
|
43
18
|
allowedToolNames: ["read"],
|
|
44
19
|
});
|
|
45
20
|
|
|
46
|
-
expect(shouldApplyCachedAgentStartState(baselineKey, baselineKey)).toBe(
|
|
47
|
-
false,
|
|
48
|
-
);
|
|
49
21
|
expect(manager.checkPermission("write", {}, undefined).state).toBe("deny");
|
|
50
22
|
|
|
51
23
|
const updatedConfig = `${JSON.stringify(
|
|
@@ -79,9 +51,7 @@ test("Before-agent-start prompt cache invalidates on permission changes while ru
|
|
|
79
51
|
allowedToolNames: ["read", "write"],
|
|
80
52
|
});
|
|
81
53
|
|
|
82
|
-
expect(
|
|
83
|
-
true,
|
|
84
|
-
);
|
|
54
|
+
expect(invalidatedKey).not.toBe(baselineKey);
|
|
85
55
|
expect(manager.checkPermission("write", {}, undefined).state).toBe("allow");
|
|
86
56
|
} finally {
|
|
87
57
|
cleanup();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { CacheKeyGate } from "#src/cache-key-gate";
|
|
4
|
+
|
|
5
|
+
describe("CacheKeyGate", () => {
|
|
6
|
+
describe("runIfChanged", () => {
|
|
7
|
+
it("runs the effect and returns its value when the key is new (null previous)", () => {
|
|
8
|
+
const gate = new CacheKeyGate();
|
|
9
|
+
const effect = vi.fn(() => "result");
|
|
10
|
+
|
|
11
|
+
const result = gate.runIfChanged("key-a", effect);
|
|
12
|
+
|
|
13
|
+
expect(effect).toHaveBeenCalledOnce();
|
|
14
|
+
expect(result).toBe("result");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("commits the key so a second call with the same key skips the effect", () => {
|
|
18
|
+
const gate = new CacheKeyGate();
|
|
19
|
+
const effect = vi.fn(() => "result");
|
|
20
|
+
|
|
21
|
+
gate.runIfChanged("key-a", effect);
|
|
22
|
+
const result = gate.runIfChanged("key-a", effect);
|
|
23
|
+
|
|
24
|
+
expect(effect).toHaveBeenCalledOnce();
|
|
25
|
+
expect(result).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("runs the effect when the key changes", () => {
|
|
29
|
+
const gate = new CacheKeyGate();
|
|
30
|
+
const effect = vi.fn((n: number) => n);
|
|
31
|
+
|
|
32
|
+
gate.runIfChanged("key-a", () => effect(1));
|
|
33
|
+
const result = gate.runIfChanged("key-b", () => effect(2));
|
|
34
|
+
|
|
35
|
+
expect(effect).toHaveBeenCalledTimes(2);
|
|
36
|
+
expect(result).toBe(2);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("returns undefined when the key is unchanged", () => {
|
|
40
|
+
const gate = new CacheKeyGate();
|
|
41
|
+
gate.runIfChanged("key-a", vi.fn());
|
|
42
|
+
|
|
43
|
+
const result = gate.runIfChanged("key-a", vi.fn());
|
|
44
|
+
|
|
45
|
+
expect(result).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("does not commit the key if the effect throws", () => {
|
|
49
|
+
const gate = new CacheKeyGate();
|
|
50
|
+
const throwing = vi.fn(() => {
|
|
51
|
+
throw new Error("oops");
|
|
52
|
+
});
|
|
53
|
+
const fallback = vi.fn(() => "ok");
|
|
54
|
+
|
|
55
|
+
expect(() => gate.runIfChanged("key-a", throwing)).toThrow("oops");
|
|
56
|
+
|
|
57
|
+
// Same key should run again since the first call threw
|
|
58
|
+
gate.runIfChanged("key-a", fallback);
|
|
59
|
+
expect(fallback).toHaveBeenCalledOnce();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("reset", () => {
|
|
64
|
+
it("re-arms the gate so the same key runs again on the next call", () => {
|
|
65
|
+
const gate = new CacheKeyGate();
|
|
66
|
+
const effect = vi.fn(() => "ok");
|
|
67
|
+
|
|
68
|
+
gate.runIfChanged("key-a", effect);
|
|
69
|
+
gate.reset();
|
|
70
|
+
gate.runIfChanged("key-a", effect);
|
|
71
|
+
|
|
72
|
+
expect(effect).toHaveBeenCalledTimes(2);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("is idempotent when called on a fresh gate", () => {
|
|
76
|
+
const gate = new CacheKeyGate();
|
|
77
|
+
gate.reset();
|
|
78
|
+
const effect = vi.fn(() => "ok");
|
|
79
|
+
|
|
80
|
+
gate.runIfChanged("key-a", effect);
|
|
81
|
+
|
|
82
|
+
expect(effect).toHaveBeenCalledOnce();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -143,42 +143,24 @@ describe("AgentPrepHandler.handle", () => {
|
|
|
143
143
|
expect(toolRegistry.setActive).toHaveBeenCalledWith(["read", "write"]);
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
-
it("
|
|
147
|
-
const { handler,
|
|
146
|
+
it("calls setActive once across repeated calls with the same allowed tools", async () => {
|
|
147
|
+
const { handler, toolRegistry } = makeSetup({
|
|
148
148
|
toolRegistry: {
|
|
149
149
|
getAll: vi.fn().mockReturnValue([{ name: "read" }]),
|
|
150
150
|
},
|
|
151
151
|
});
|
|
152
|
-
const spy = vi.spyOn(session, "commitActiveToolsCacheKey");
|
|
153
152
|
await handler.handle(makeEvent(), makeCtx());
|
|
154
|
-
expect(spy).toHaveBeenCalled();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("skips setActive when cache key is unchanged", async () => {
|
|
158
|
-
const { handler, session, toolRegistry } = makeSetup({
|
|
159
|
-
toolRegistry: {
|
|
160
|
-
getAll: vi.fn().mockReturnValue([{ name: "read" }]),
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
vi.spyOn(session, "shouldUpdateActiveTools").mockReturnValue(false);
|
|
164
153
|
await handler.handle(makeEvent(), makeCtx());
|
|
165
|
-
expect(toolRegistry.setActive).
|
|
154
|
+
expect(toolRegistry.setActive).toHaveBeenCalledOnce();
|
|
166
155
|
});
|
|
167
156
|
|
|
168
|
-
it("returns empty object
|
|
169
|
-
const { handler
|
|
170
|
-
|
|
157
|
+
it("returns empty object on repeated calls with unchanged inputs", async () => {
|
|
158
|
+
const { handler } = makeSetup();
|
|
159
|
+
await handler.handle(makeEvent(), makeCtx());
|
|
171
160
|
const result = await handler.handle(makeEvent(), makeCtx());
|
|
172
161
|
expect(result).toEqual({});
|
|
173
162
|
});
|
|
174
163
|
|
|
175
|
-
it("commits prompt-state cache key and processes prompt when cache is new", async () => {
|
|
176
|
-
const { handler, session } = makeSetup();
|
|
177
|
-
const spy = vi.spyOn(session, "commitPromptStateCacheKey");
|
|
178
|
-
await handler.handle(makeEvent(), makeCtx());
|
|
179
|
-
expect(spy).toHaveBeenCalled();
|
|
180
|
-
});
|
|
181
|
-
|
|
182
164
|
it("stores resolved skill entries on the session", async () => {
|
|
183
165
|
const { handler, session } = makeSetup();
|
|
184
166
|
const spy = vi.spyOn(session, "setActiveSkillEntries");
|
|
@@ -105,16 +105,19 @@ describe("PermissionSession", () => {
|
|
|
105
105
|
|
|
106
106
|
it("clears cache keys", () => {
|
|
107
107
|
const { session } = createSession();
|
|
108
|
-
|
|
109
|
-
session.
|
|
110
|
-
|
|
111
|
-
expect(session.shouldUpdatePromptState("key-2")).toBe(false);
|
|
108
|
+
// Prime both gates with a key
|
|
109
|
+
session.activeToolsGate.runIfChanged("key-1", () => {});
|
|
110
|
+
session.promptStateGate.runIfChanged("key-2", () => {});
|
|
112
111
|
|
|
113
112
|
session.resetForNewSession(makeCtx());
|
|
114
113
|
|
|
115
|
-
// After reset, same keys should
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
// After reset, the same keys should run the effect again
|
|
115
|
+
const toolsEffect = vi.fn();
|
|
116
|
+
const promptEffect = vi.fn();
|
|
117
|
+
session.activeToolsGate.runIfChanged("key-1", toolsEffect);
|
|
118
|
+
session.promptStateGate.runIfChanged("key-2", promptEffect);
|
|
119
|
+
expect(toolsEffect).toHaveBeenCalledOnce();
|
|
120
|
+
expect(promptEffect).toHaveBeenCalledOnce();
|
|
118
121
|
});
|
|
119
122
|
|
|
120
123
|
it("clears skill entries", () => {
|
|
@@ -162,13 +165,19 @@ describe("PermissionSession", () => {
|
|
|
162
165
|
|
|
163
166
|
it("clears cache keys", () => {
|
|
164
167
|
const { session } = createSession();
|
|
165
|
-
|
|
166
|
-
session.
|
|
168
|
+
// Prime both gates with a key
|
|
169
|
+
session.activeToolsGate.runIfChanged("k1", () => {});
|
|
170
|
+
session.promptStateGate.runIfChanged("k2", () => {});
|
|
167
171
|
|
|
168
172
|
session.shutdown();
|
|
169
173
|
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
// After shutdown, the same keys should run the effect again
|
|
175
|
+
const toolsEffect = vi.fn();
|
|
176
|
+
const promptEffect = vi.fn();
|
|
177
|
+
session.activeToolsGate.runIfChanged("k1", toolsEffect);
|
|
178
|
+
session.promptStateGate.runIfChanged("k2", promptEffect);
|
|
179
|
+
expect(toolsEffect).toHaveBeenCalledOnce();
|
|
180
|
+
expect(promptEffect).toHaveBeenCalledOnce();
|
|
172
181
|
});
|
|
173
182
|
|
|
174
183
|
it("clears skill entries", () => {
|
|
@@ -190,36 +199,6 @@ describe("PermissionSession", () => {
|
|
|
190
199
|
});
|
|
191
200
|
});
|
|
192
201
|
|
|
193
|
-
describe("cache key methods", () => {
|
|
194
|
-
it("shouldUpdateActiveTools returns true for new key", () => {
|
|
195
|
-
const { session } = createSession();
|
|
196
|
-
expect(session.shouldUpdateActiveTools("key-1")).toBe(true);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("shouldUpdateActiveTools returns false for committed key", () => {
|
|
200
|
-
const { session } = createSession();
|
|
201
|
-
session.commitActiveToolsCacheKey("key-1");
|
|
202
|
-
expect(session.shouldUpdateActiveTools("key-1")).toBe(false);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("shouldUpdateActiveTools returns true for different key", () => {
|
|
206
|
-
const { session } = createSession();
|
|
207
|
-
session.commitActiveToolsCacheKey("key-1");
|
|
208
|
-
expect(session.shouldUpdateActiveTools("key-2")).toBe(true);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it("shouldUpdatePromptState returns true for new key", () => {
|
|
212
|
-
const { session } = createSession();
|
|
213
|
-
expect(session.shouldUpdatePromptState("key-1")).toBe(true);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("shouldUpdatePromptState returns false for committed key", () => {
|
|
217
|
-
const { session } = createSession();
|
|
218
|
-
session.commitPromptStateCacheKey("key-1");
|
|
219
|
-
expect(session.shouldUpdatePromptState("key-1")).toBe(false);
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
202
|
describe("skill entries", () => {
|
|
224
203
|
it("get/set skill entries", () => {
|
|
225
204
|
const { session } = createSession();
|
|
@@ -356,14 +335,20 @@ describe("PermissionSession", () => {
|
|
|
356
335
|
|
|
357
336
|
it("clears caches and skill entries", () => {
|
|
358
337
|
const { session } = createSession();
|
|
359
|
-
|
|
360
|
-
session.
|
|
338
|
+
// Prime both gates with a key
|
|
339
|
+
session.activeToolsGate.runIfChanged("k1", () => {});
|
|
340
|
+
session.promptStateGate.runIfChanged("k2", () => {});
|
|
361
341
|
session.setActiveSkillEntries([makeSkillEntry("s")]);
|
|
362
342
|
|
|
363
343
|
session.reload();
|
|
364
344
|
|
|
365
|
-
|
|
366
|
-
|
|
345
|
+
// After reload, the same keys should run the effect again
|
|
346
|
+
const toolsEffect = vi.fn();
|
|
347
|
+
const promptEffect = vi.fn();
|
|
348
|
+
session.activeToolsGate.runIfChanged("k1", toolsEffect);
|
|
349
|
+
session.promptStateGate.runIfChanged("k2", promptEffect);
|
|
350
|
+
expect(toolsEffect).toHaveBeenCalledOnce();
|
|
351
|
+
expect(promptEffect).toHaveBeenCalledOnce();
|
|
367
352
|
expect(session.getActiveSkillEntries()).toEqual([]);
|
|
368
353
|
});
|
|
369
354
|
});
|