@gotgenes/pi-permission-system 7.1.4 → 7.3.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 +26 -0
- package/package.json +2 -2
- package/src/active-agent.ts +1 -1
- package/src/bash-arity.ts +1 -0
- package/src/config-modal.ts +2 -0
- package/src/forwarded-permissions/io.ts +4 -2
- package/src/forwarded-permissions/polling.ts +22 -9
- package/src/forwarding-manager.ts +3 -1
- package/src/handlers/before-agent-start.ts +7 -6
- package/src/handlers/gates/bash-path-extractor.ts +3 -5
- package/src/handlers/gates/bash-path.ts +1 -1
- package/src/handlers/gates/runner.ts +3 -0
- package/src/handlers/lifecycle.ts +9 -8
- package/src/handlers/permission-gate-handler.ts +12 -7
- package/src/index.ts +19 -1
- package/src/logging.ts +3 -0
- package/src/node-modules-discovery.ts +1 -1
- package/src/normalize.ts +1 -0
- package/src/permission-event-rpc.ts +2 -0
- package/src/permission-forwarding.ts +15 -0
- package/src/permission-manager.ts +7 -6
- package/src/permission-merge.ts +4 -2
- package/src/permission-prompter.ts +7 -0
- package/src/permission-prompts.ts +1 -1
- package/src/policy-loader.ts +5 -5
- package/src/service.ts +37 -1
- package/src/skill-prompt-sanitizer.ts +3 -3
- package/src/subagent-context.ts +14 -1
- package/src/subagent-registry.ts +60 -0
- package/src/tool-registry.ts +1 -1
- package/src/yolo-mode.ts +2 -1
- package/test/config-modal.test.ts +6 -8
- package/test/forwarding-manager.test.ts +1 -0
- package/test/handlers/before-agent-start.test.ts +1 -1
- package/test/handlers/external-directory-integration.test.ts +1 -1
- package/test/handlers/gates/skill-read.test.ts +8 -10
- package/test/handlers/gates/tool.test.ts +1 -1
- package/test/handlers/input-events.test.ts +1 -1
- package/test/handlers/input.test.ts +1 -1
- package/test/handlers/tool-call-events.test.ts +1 -1
- package/test/handlers/tool-call.test.ts +1 -1
- package/test/permission-event-rpc.test.ts +1 -0
- package/test/permission-events.test.ts +2 -0
- package/test/permission-forwarding.test.ts +98 -0
- package/test/permission-manager-unified.test.ts +4 -2
- package/test/permission-session.test.ts +2 -2
- package/test/permission-system.test.ts +8 -8
- package/test/service.test.ts +100 -6
- package/test/subagent-context.test.ts +65 -0
- package/test/subagent-registry.test.ts +94 -0
|
@@ -112,6 +112,7 @@ type ExtensionHarnessOptions = {
|
|
|
112
112
|
|
|
113
113
|
const INHERITED_SUBAGENT_ENV_KEYS = [
|
|
114
114
|
...SUBAGENT_ENV_HINT_KEYS,
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- test uses deprecated alias intentionally
|
|
115
116
|
SUBAGENT_PARENT_SESSION_ENV_KEY,
|
|
116
117
|
] as const;
|
|
117
118
|
|
|
@@ -121,6 +122,7 @@ async function withIsolatedSubagentEnv<T>(
|
|
|
121
122
|
const originalValues = new Map<string, string | undefined>();
|
|
122
123
|
for (const key of INHERITED_SUBAGENT_ENV_KEYS) {
|
|
123
124
|
originalValues.set(key, process.env[key]);
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- process.env cleanup requires dynamic delete
|
|
124
126
|
delete process.env[key];
|
|
125
127
|
}
|
|
126
128
|
|
|
@@ -129,6 +131,7 @@ async function withIsolatedSubagentEnv<T>(
|
|
|
129
131
|
} finally {
|
|
130
132
|
for (const [key, value] of originalValues.entries()) {
|
|
131
133
|
if (value === undefined) {
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- process.env cleanup requires dynamic delete
|
|
132
135
|
delete process.env[key];
|
|
133
136
|
} else {
|
|
134
137
|
process.env[key] = value;
|
|
@@ -143,7 +146,7 @@ function createToolCallHarness(
|
|
|
143
146
|
options: ExtensionHarnessOptions = {},
|
|
144
147
|
): ExtensionHarness {
|
|
145
148
|
const baseDir = mkdtempSync(join(tmpdir(), "pi-permission-system-runtime-"));
|
|
146
|
-
const cwd = options.cwd
|
|
149
|
+
const cwd = options.cwd ?? baseDir;
|
|
147
150
|
const prompts: string[] = [];
|
|
148
151
|
const handlers: Record<string, MockHandler> = {};
|
|
149
152
|
const originalAgentDir = process.env.PI_CODING_AGENT_DIR;
|
|
@@ -188,10 +191,7 @@ function createToolCallHarness(
|
|
|
188
191
|
prompts,
|
|
189
192
|
cleanup: async (): Promise<void> => {
|
|
190
193
|
await Promise.resolve(
|
|
191
|
-
handlers.session_shutdown
|
|
192
|
-
{},
|
|
193
|
-
createMockContext(cwd, prompts, options),
|
|
194
|
-
),
|
|
194
|
+
handlers.session_shutdown({}, createMockContext(cwd, prompts, options)),
|
|
195
195
|
);
|
|
196
196
|
rmSync(baseDir, { recursive: true, force: true });
|
|
197
197
|
},
|
|
@@ -236,7 +236,7 @@ async function runToolCall(
|
|
|
236
236
|
handler(event, createMockContext(harness.cwd, harness.prompts, options)),
|
|
237
237
|
),
|
|
238
238
|
);
|
|
239
|
-
return
|
|
239
|
+
return result ?? {};
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
test("Yolo mode only auto-approves ask-state permissions", () => {
|
|
@@ -1515,7 +1515,7 @@ test("REGRESSION: resolveSkillPromptEntries sanitizes every available_skills blo
|
|
|
1515
1515
|
|
|
1516
1516
|
expect(result.prompt).not.toContain("denied-skill");
|
|
1517
1517
|
expect(result.prompt).toContain("visible-skill");
|
|
1518
|
-
expect((result.prompt.match(/<available_skills>/g)
|
|
1518
|
+
expect((result.prompt.match(/<available_skills>/g) ?? []).length).toBe(1);
|
|
1519
1519
|
expect(result.entries.map((entry) => entry.name)).toEqual([
|
|
1520
1520
|
"visible-skill",
|
|
1521
1521
|
]);
|
|
@@ -2371,7 +2371,7 @@ test("session approval: session_shutdown clears session approvals", async () =>
|
|
|
2371
2371
|
hasUI: true,
|
|
2372
2372
|
selectResponse: "Yes",
|
|
2373
2373
|
});
|
|
2374
|
-
await Promise.resolve(harness.handlers.session_shutdown
|
|
2374
|
+
await Promise.resolve(harness.handlers.session_shutdown({}, shutdownCtx));
|
|
2375
2375
|
|
|
2376
2376
|
// Access same path again — should prompt because cache was cleared
|
|
2377
2377
|
const result = await runToolCall(
|
package/test/service.test.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
publishPermissionsService,
|
|
7
7
|
unpublishPermissionsService,
|
|
8
8
|
} from "#src/service";
|
|
9
|
+
import { SubagentSessionRegistry } from "#src/subagent-registry";
|
|
9
10
|
import type { PermissionCheckResult } from "#src/types";
|
|
10
11
|
|
|
11
12
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
@@ -15,6 +16,9 @@ function makeService(
|
|
|
15
16
|
): PermissionsService {
|
|
16
17
|
return {
|
|
17
18
|
checkPermission: vi.fn(),
|
|
19
|
+
registerSubagentSession: vi.fn(),
|
|
20
|
+
unregisterSubagentSession: vi.fn(),
|
|
21
|
+
getToolPermission: vi.fn(),
|
|
18
22
|
...overrides,
|
|
19
23
|
};
|
|
20
24
|
}
|
|
@@ -85,12 +89,12 @@ describe("service adapter delegation", () => {
|
|
|
85
89
|
];
|
|
86
90
|
|
|
87
91
|
// Build the adapter the same way index.ts will
|
|
88
|
-
const service
|
|
92
|
+
const service = makeService({
|
|
89
93
|
checkPermission(surface, value, agentName) {
|
|
90
94
|
const input = buildInputForSurface(surface, value);
|
|
91
95
|
return checkPermission(surface, input, agentName, sessionRules);
|
|
92
96
|
},
|
|
93
|
-
};
|
|
97
|
+
});
|
|
94
98
|
|
|
95
99
|
publishPermissionsService(service);
|
|
96
100
|
const retrieved = getPermissionsService()!;
|
|
@@ -108,12 +112,12 @@ describe("service adapter delegation", () => {
|
|
|
108
112
|
it("checkPermission passes agentName through", () => {
|
|
109
113
|
const checkPermission = vi.fn().mockReturnValue(fakeResult);
|
|
110
114
|
|
|
111
|
-
const service
|
|
115
|
+
const service = makeService({
|
|
112
116
|
checkPermission(surface, value, agentName) {
|
|
113
117
|
const input = buildInputForSurface(surface, value);
|
|
114
118
|
return checkPermission(surface, input, agentName, []);
|
|
115
119
|
},
|
|
116
|
-
};
|
|
120
|
+
});
|
|
117
121
|
|
|
118
122
|
publishPermissionsService(service);
|
|
119
123
|
getPermissionsService()!.checkPermission("skill", "my-skill", "Explore");
|
|
@@ -126,15 +130,105 @@ describe("service adapter delegation", () => {
|
|
|
126
130
|
);
|
|
127
131
|
});
|
|
128
132
|
|
|
133
|
+
it("registerSubagentSession delegates to the registry", () => {
|
|
134
|
+
const registry = new SubagentSessionRegistry();
|
|
135
|
+
const service: PermissionsService = {
|
|
136
|
+
checkPermission: vi.fn(),
|
|
137
|
+
registerSubagentSession(key, info) {
|
|
138
|
+
registry.register(key, info);
|
|
139
|
+
},
|
|
140
|
+
unregisterSubagentSession(key) {
|
|
141
|
+
registry.unregister(key);
|
|
142
|
+
},
|
|
143
|
+
getToolPermission: vi.fn((): "allow" => "allow"),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
publishPermissionsService(service);
|
|
147
|
+
getPermissionsService()!.registerSubagentSession("/sessions/task-1", {
|
|
148
|
+
agentName: "Explore",
|
|
149
|
+
parentSessionId: "parent-abc",
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(registry.has("/sessions/task-1")).toBe(true);
|
|
153
|
+
expect(registry.get("/sessions/task-1")).toEqual({
|
|
154
|
+
agentName: "Explore",
|
|
155
|
+
parentSessionId: "parent-abc",
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("unregisterSubagentSession delegates to the registry", () => {
|
|
160
|
+
const registry = new SubagentSessionRegistry();
|
|
161
|
+
const service: PermissionsService = {
|
|
162
|
+
checkPermission: vi.fn(),
|
|
163
|
+
registerSubagentSession(key, info) {
|
|
164
|
+
registry.register(key, info);
|
|
165
|
+
},
|
|
166
|
+
unregisterSubagentSession(key) {
|
|
167
|
+
registry.unregister(key);
|
|
168
|
+
},
|
|
169
|
+
getToolPermission: vi.fn((): "allow" => "allow"),
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
publishPermissionsService(service);
|
|
173
|
+
const svc = getPermissionsService()!;
|
|
174
|
+
svc.registerSubagentSession("/sessions/task-1", { agentName: "Explore" });
|
|
175
|
+
svc.unregisterSubagentSession("/sessions/task-1");
|
|
176
|
+
|
|
177
|
+
expect(registry.has("/sessions/task-1")).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("getToolPermission delegates to the permission manager", () => {
|
|
181
|
+
const getToolPermissionFn = vi.fn(
|
|
182
|
+
(_t: string, _a?: string): "deny" => "deny",
|
|
183
|
+
);
|
|
184
|
+
const service: PermissionsService = {
|
|
185
|
+
checkPermission: vi.fn(),
|
|
186
|
+
registerSubagentSession: vi.fn(),
|
|
187
|
+
unregisterSubagentSession: vi.fn(),
|
|
188
|
+
getToolPermission(toolName, agentName) {
|
|
189
|
+
return getToolPermissionFn(toolName, agentName);
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
publishPermissionsService(service);
|
|
194
|
+
const result = getPermissionsService()!.getToolPermission(
|
|
195
|
+
"bash",
|
|
196
|
+
"Explore",
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(result).toBe("deny");
|
|
200
|
+
expect(getToolPermissionFn).toHaveBeenCalledWith("bash", "Explore");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("getToolPermission works without agentName", () => {
|
|
204
|
+
const getToolPermissionFn = vi.fn(
|
|
205
|
+
(_t: string, _a?: string): "ask" => "ask",
|
|
206
|
+
);
|
|
207
|
+
const service: PermissionsService = {
|
|
208
|
+
checkPermission: vi.fn(),
|
|
209
|
+
registerSubagentSession: vi.fn(),
|
|
210
|
+
unregisterSubagentSession: vi.fn(),
|
|
211
|
+
getToolPermission(toolName, agentName) {
|
|
212
|
+
return getToolPermissionFn(toolName, agentName);
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
publishPermissionsService(service);
|
|
217
|
+
const result = getPermissionsService()!.getToolPermission("write");
|
|
218
|
+
|
|
219
|
+
expect(result).toBe("ask");
|
|
220
|
+
expect(getToolPermissionFn).toHaveBeenCalledWith("write", undefined);
|
|
221
|
+
});
|
|
222
|
+
|
|
129
223
|
it("checkPermission uses empty object for unknown surfaces", () => {
|
|
130
224
|
const checkPermission = vi.fn().mockReturnValue(fakeResult);
|
|
131
225
|
|
|
132
|
-
const service
|
|
226
|
+
const service = makeService({
|
|
133
227
|
checkPermission(surface, value, agentName) {
|
|
134
228
|
const input = buildInputForSurface(surface, value);
|
|
135
229
|
return checkPermission(surface, input, agentName, []);
|
|
136
230
|
},
|
|
137
|
-
};
|
|
231
|
+
});
|
|
138
232
|
|
|
139
233
|
publishPermissionsService(service);
|
|
140
234
|
getPermissionsService()!.checkPermission("read", "/tmp/file");
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
isSubagentExecutionContext,
|
|
6
6
|
normalizeFilesystemPath,
|
|
7
7
|
} from "#src/subagent-context";
|
|
8
|
+
import { SubagentSessionRegistry } from "#src/subagent-registry";
|
|
8
9
|
|
|
9
10
|
afterEach(() => {
|
|
10
11
|
vi.unstubAllEnvs();
|
|
@@ -197,3 +198,67 @@ describe("isSubagentExecutionContext — session dir detection", () => {
|
|
|
197
198
|
expect(isSubagentExecutionContext(makeCtx(""), subagentRoot)).toBe(false);
|
|
198
199
|
});
|
|
199
200
|
});
|
|
201
|
+
|
|
202
|
+
describe("isSubagentExecutionContext — registry detection", () => {
|
|
203
|
+
const subagentRoot = "/home/user/.pi/agent/sessions/subagents";
|
|
204
|
+
const outsideDir =
|
|
205
|
+
"/home/user/projects/my-app/.pi/agent/sessions/parent/tasks";
|
|
206
|
+
|
|
207
|
+
test("returns true when session dir is registered (no env vars, outside filesystem root)", () => {
|
|
208
|
+
const registry = new SubagentSessionRegistry();
|
|
209
|
+
registry.register(outsideDir, { agentName: "Explore" });
|
|
210
|
+
expect(
|
|
211
|
+
isSubagentExecutionContext(makeCtx(outsideDir), subagentRoot, registry),
|
|
212
|
+
).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("returns true when registered session has a parentSessionId", () => {
|
|
216
|
+
const registry = new SubagentSessionRegistry();
|
|
217
|
+
registry.register(outsideDir, {
|
|
218
|
+
agentName: "Plan",
|
|
219
|
+
parentSessionId: "parent-123",
|
|
220
|
+
});
|
|
221
|
+
expect(
|
|
222
|
+
isSubagentExecutionContext(makeCtx(outsideDir), subagentRoot, registry),
|
|
223
|
+
).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("returns false when registry is provided but session dir is not registered", () => {
|
|
227
|
+
const registry = new SubagentSessionRegistry();
|
|
228
|
+
expect(
|
|
229
|
+
isSubagentExecutionContext(makeCtx(outsideDir), subagentRoot, registry),
|
|
230
|
+
).toBe(false);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("returns false when session dir is null and registry has no matching entry", () => {
|
|
234
|
+
const registry = new SubagentSessionRegistry();
|
|
235
|
+
expect(
|
|
236
|
+
isSubagentExecutionContext(makeCtx(null), subagentRoot, registry),
|
|
237
|
+
).toBe(false);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("registry check takes priority over env var detection", () => {
|
|
241
|
+
// Registry says registered; env var not set — should still return true.
|
|
242
|
+
const registry = new SubagentSessionRegistry();
|
|
243
|
+
registry.register(outsideDir, { agentName: "Explore" });
|
|
244
|
+
// Confirm no env var is set
|
|
245
|
+
expect(process.env.PI_IS_SUBAGENT).toBeUndefined();
|
|
246
|
+
expect(
|
|
247
|
+
isSubagentExecutionContext(makeCtx(outsideDir), subagentRoot, registry),
|
|
248
|
+
).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("unregistered session falls through to env var detection", () => {
|
|
252
|
+
vi.stubEnv("PI_IS_SUBAGENT", "true");
|
|
253
|
+
const registry = new SubagentSessionRegistry(); // empty — outsideDir not registered
|
|
254
|
+
// Env var present → still true even without registry entry
|
|
255
|
+
expect(
|
|
256
|
+
isSubagentExecutionContext(makeCtx(outsideDir), subagentRoot, registry),
|
|
257
|
+
).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("no registry passed — existing behaviour unchanged", () => {
|
|
261
|
+
// Ensure the parameter is truly optional (no registry arg)
|
|
262
|
+
expect(isSubagentExecutionContext(makeCtx(null), subagentRoot)).toBe(false);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
type SubagentSessionInfo,
|
|
4
|
+
SubagentSessionRegistry,
|
|
5
|
+
} from "#src/subagent-registry";
|
|
6
|
+
|
|
7
|
+
function makeInfo(
|
|
8
|
+
overrides: Partial<SubagentSessionInfo> = {},
|
|
9
|
+
): SubagentSessionInfo {
|
|
10
|
+
return {
|
|
11
|
+
agentName: "Explore",
|
|
12
|
+
...overrides,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("SubagentSessionRegistry", () => {
|
|
17
|
+
test("has() returns false for an unregistered key", () => {
|
|
18
|
+
const registry = new SubagentSessionRegistry();
|
|
19
|
+
expect(registry.has("/sessions/task-abc")).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("get() returns undefined for an unregistered key", () => {
|
|
23
|
+
const registry = new SubagentSessionRegistry();
|
|
24
|
+
expect(registry.get("/sessions/task-abc")).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("has() returns true after register()", () => {
|
|
28
|
+
const registry = new SubagentSessionRegistry();
|
|
29
|
+
registry.register("/sessions/task-abc", makeInfo());
|
|
30
|
+
expect(registry.has("/sessions/task-abc")).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("get() returns the registered info after register()", () => {
|
|
34
|
+
const registry = new SubagentSessionRegistry();
|
|
35
|
+
const info = makeInfo({ parentSessionId: "parent-123" });
|
|
36
|
+
registry.register("/sessions/task-abc", info);
|
|
37
|
+
expect(registry.get("/sessions/task-abc")).toEqual(info);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("register() stores agentName without parentSessionId", () => {
|
|
41
|
+
const registry = new SubagentSessionRegistry();
|
|
42
|
+
registry.register("/sessions/task-abc", makeInfo());
|
|
43
|
+
expect(registry.get("/sessions/task-abc")).toEqual({
|
|
44
|
+
agentName: "Explore",
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("has() returns false after unregister()", () => {
|
|
49
|
+
const registry = new SubagentSessionRegistry();
|
|
50
|
+
registry.register("/sessions/task-abc", makeInfo());
|
|
51
|
+
registry.unregister("/sessions/task-abc");
|
|
52
|
+
expect(registry.has("/sessions/task-abc")).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("get() returns undefined after unregister()", () => {
|
|
56
|
+
const registry = new SubagentSessionRegistry();
|
|
57
|
+
registry.register("/sessions/task-abc", makeInfo());
|
|
58
|
+
registry.unregister("/sessions/task-abc");
|
|
59
|
+
expect(registry.get("/sessions/task-abc")).toBeUndefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("unregister() is a no-op for an unknown key", () => {
|
|
63
|
+
const registry = new SubagentSessionRegistry();
|
|
64
|
+
expect(() => registry.unregister("/sessions/nonexistent")).not.toThrow();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("register() overwrites a previous entry for the same key", () => {
|
|
68
|
+
const registry = new SubagentSessionRegistry();
|
|
69
|
+
registry.register(
|
|
70
|
+
"/sessions/task-abc",
|
|
71
|
+
makeInfo({ parentSessionId: "parent-1" }),
|
|
72
|
+
);
|
|
73
|
+
registry.register(
|
|
74
|
+
"/sessions/task-abc",
|
|
75
|
+
makeInfo({ parentSessionId: "parent-2" }),
|
|
76
|
+
);
|
|
77
|
+
expect(registry.get("/sessions/task-abc")?.parentSessionId).toBe(
|
|
78
|
+
"parent-2",
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("multiple keys are independent", () => {
|
|
83
|
+
const registry = new SubagentSessionRegistry();
|
|
84
|
+
registry.register("/sessions/task-1", makeInfo({ agentName: "Explore" }));
|
|
85
|
+
registry.register("/sessions/task-2", makeInfo({ agentName: "Plan" }));
|
|
86
|
+
|
|
87
|
+
expect(registry.get("/sessions/task-1")?.agentName).toBe("Explore");
|
|
88
|
+
expect(registry.get("/sessions/task-2")?.agentName).toBe("Plan");
|
|
89
|
+
|
|
90
|
+
registry.unregister("/sessions/task-1");
|
|
91
|
+
expect(registry.has("/sessions/task-1")).toBe(false);
|
|
92
|
+
expect(registry.has("/sessions/task-2")).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
});
|