chapterhouse 0.3.13 → 0.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +2 -69
  2. package/dist/api/server.js +8 -155
  3. package/dist/api/server.test.js +1 -1
  4. package/dist/cli.js +0 -30
  5. package/dist/config.js +0 -3
  6. package/dist/copilot/agent-event-bus.js +41 -0
  7. package/dist/copilot/agent-event-bus.test.js +23 -0
  8. package/dist/copilot/agents.js +4 -59
  9. package/dist/copilot/orchestrator.js +20 -39
  10. package/dist/copilot/orchestrator.test.js +73 -158
  11. package/dist/copilot/task-event-log.js +5 -5
  12. package/dist/copilot/task-event-log.test.js +68 -142
  13. package/dist/copilot/tools.js +9 -85
  14. package/dist/daemon.js +0 -22
  15. package/dist/store/db.js +2 -50
  16. package/dist/store/db.test.js +0 -45
  17. package/package.json +1 -3
  18. package/web/dist/assets/index-BlIWCM11.js +217 -0
  19. package/web/dist/assets/index-BlIWCM11.js.map +1 -0
  20. package/web/dist/assets/{index-BtAcw3EP.css → index-lvHFM_ut.css} +1 -1
  21. package/web/dist/index.html +2 -2
  22. package/dist/api/ralph.js +0 -153
  23. package/dist/api/ralph.test.js +0 -101
  24. package/dist/copilot/agents.squad.test.js +0 -72
  25. package/dist/copilot/hooks.js +0 -157
  26. package/dist/copilot/hooks.test.js +0 -315
  27. package/dist/copilot/squad-event-bus.js +0 -27
  28. package/dist/copilot/tools.squad.test.js +0 -168
  29. package/dist/squad/charter.js +0 -125
  30. package/dist/squad/charter.test.js +0 -89
  31. package/dist/squad/context.js +0 -48
  32. package/dist/squad/context.test.js +0 -59
  33. package/dist/squad/discovery.js +0 -268
  34. package/dist/squad/discovery.test.js +0 -154
  35. package/dist/squad/index.js +0 -9
  36. package/dist/squad/init-cli.js +0 -109
  37. package/dist/squad/init.js +0 -395
  38. package/dist/squad/init.test.js +0 -351
  39. package/dist/squad/mirror.js +0 -83
  40. package/dist/squad/mirror.scheduler.js +0 -80
  41. package/dist/squad/mirror.scheduler.test.js +0 -197
  42. package/dist/squad/mirror.test.js +0 -172
  43. package/dist/squad/registry.js +0 -162
  44. package/dist/squad/registry.test.js +0 -31
  45. package/dist/squad/squad-coordinator-system-message.test.js +0 -190
  46. package/dist/squad/squad-session-routing.test.js +0 -260
  47. package/dist/squad/types.js +0 -4
  48. package/dist/squad/worktree.js +0 -295
  49. package/dist/squad/worktree.test.js +0 -189
  50. package/dist/store/squad-sessions.test.js +0 -341
  51. package/web/dist/assets/index-IgSOXx_a.js +0 -219
  52. package/web/dist/assets/index-IgSOXx_a.js.map +0 -1
@@ -1,190 +0,0 @@
1
- /**
2
- * Phase 4 — Squad coordinator system message tests
3
- *
4
- * Covers:
5
- * - Error thrown when no squad.agent.md found
6
- * - Loads from .github/agents/squad.agent.md (primary)
7
- * - Loads from squad.agent.md (fallback)
8
- * - Includes project charter from .squad/team.md
9
- * - Includes recent decisions from .squad/decisions.md
10
- * - Graceful when team.md missing (warning comment, no throw)
11
- * - Does NOT include generic Chapterhouse system message sections
12
- */
13
- import assert from "node:assert/strict";
14
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
15
- import { join } from "node:path";
16
- import test from "node:test";
17
- const repoRoot = process.cwd();
18
- const sandboxBase = join(repoRoot, ".test-work", `squad-charter-${process.pid}`);
19
- async function loadCharterModule() {
20
- return await import(new URL(`./charter.js?v=${Date.now()}-${Math.random()}`, import.meta.url).href);
21
- }
22
- /** Scaffold a minimal project directory structure for testing. */
23
- function makeProjectDir(name, opts = {}) {
24
- const projectRoot = join(sandboxBase, name);
25
- mkdirSync(join(projectRoot, ".squad"), { recursive: true });
26
- if (opts.githubAgentFile !== undefined) {
27
- mkdirSync(join(projectRoot, ".github", "agents"), { recursive: true });
28
- writeFileSync(join(projectRoot, ".github", "agents", "squad.agent.md"), opts.githubAgentFile);
29
- }
30
- if (opts.rootAgentFile !== undefined) {
31
- writeFileSync(join(projectRoot, "squad.agent.md"), opts.rootAgentFile);
32
- }
33
- if (opts.teamMd !== undefined) {
34
- writeFileSync(join(projectRoot, ".squad", "team.md"), opts.teamMd);
35
- }
36
- if (opts.decisionsMd !== undefined) {
37
- writeFileSync(join(projectRoot, ".squad", "decisions.md"), opts.decisionsMd);
38
- }
39
- return projectRoot;
40
- }
41
- test.before(() => {
42
- mkdirSync(sandboxBase, { recursive: true });
43
- });
44
- test.after(() => {
45
- rmSync(sandboxBase, { recursive: true, force: true });
46
- });
47
- // ---------------------------------------------------------------------------
48
- // 1. Throws when no squad.agent.md found
49
- // ---------------------------------------------------------------------------
50
- test("getSquadCoordinatorSystemMessage throws when no squad.agent.md file exists", async () => {
51
- const charter = await loadCharterModule();
52
- assert.equal(typeof charter.getSquadCoordinatorSystemMessage, "function", "getSquadCoordinatorSystemMessage should be exported from charter module");
53
- const fn = charter.getSquadCoordinatorSystemMessage;
54
- const projectRoot = makeProjectDir("no-agent", { teamMd: "# Team" });
55
- await assert.rejects(async () => { await fn(projectRoot); }, (err) => {
56
- assert.ok(err instanceof Error, "should throw an Error");
57
- assert.ok(err.message.toLowerCase().includes("squad.agent.md") || err.message.toLowerCase().includes("agent"), `error message should reference squad.agent.md, got: "${err.message}"`);
58
- return true;
59
- }, "should reject with a clear error when no squad.agent.md is found");
60
- });
61
- // ---------------------------------------------------------------------------
62
- // 2. Loads from .github/agents/squad.agent.md (primary location)
63
- // ---------------------------------------------------------------------------
64
- test("getSquadCoordinatorSystemMessage loads from .github/agents/squad.agent.md", async () => {
65
- const charter = await loadCharterModule();
66
- const fn = charter.getSquadCoordinatorSystemMessage;
67
- const AGENT_CONTENT = "# Squad Coordinator\nYou are the coordinator for this project.";
68
- const projectRoot = makeProjectDir("github-agent", {
69
- githubAgentFile: AGENT_CONTENT,
70
- teamMd: "# Team",
71
- decisionsMd: "## Decision A\nUse TypeScript.",
72
- });
73
- const result = await fn(projectRoot);
74
- assert.ok(typeof result === "string", "result should be a string");
75
- assert.ok(result.includes("You are the coordinator for this project."), `result should include .github/agents/squad.agent.md content, got: ${result.slice(0, 300)}`);
76
- });
77
- // ---------------------------------------------------------------------------
78
- // 3. Loads from squad.agent.md (fallback when .github/agents/ not present)
79
- // ---------------------------------------------------------------------------
80
- test("getSquadCoordinatorSystemMessage falls back to squad.agent.md at project root", async () => {
81
- const charter = await loadCharterModule();
82
- const fn = charter.getSquadCoordinatorSystemMessage;
83
- const AGENT_CONTENT = "# Root Squad Agent\nFallback coordinator instructions.";
84
- const projectRoot = makeProjectDir("root-agent", {
85
- rootAgentFile: AGENT_CONTENT,
86
- teamMd: "# Team",
87
- });
88
- const result = await fn(projectRoot);
89
- assert.ok(result.includes("Fallback coordinator instructions."), `result should include root-level squad.agent.md content, got: ${result.slice(0, 300)}`);
90
- });
91
- // ---------------------------------------------------------------------------
92
- // 4. Includes project charter (.squad/team.md)
93
- // ---------------------------------------------------------------------------
94
- test("getSquadCoordinatorSystemMessage includes .squad/team.md content as project charter", async () => {
95
- const charter = await loadCharterModule();
96
- const fn = charter.getSquadCoordinatorSystemMessage;
97
- const TEAM_CONTENT = "# Project Team\n## Members\n- Alice (Lead)\n- Bob (Backend)";
98
- const projectRoot = makeProjectDir("with-team", {
99
- githubAgentFile: "# Coordinator",
100
- teamMd: TEAM_CONTENT,
101
- decisionsMd: "## Dec 1\nSome decision.",
102
- });
103
- const result = await fn(projectRoot);
104
- assert.ok(result.includes("Alice (Lead)"), `result should include team.md content, got: ${result.slice(0, 400)}`);
105
- assert.ok(result.includes("Bob (Backend)"), `result should include all team members, got: ${result.slice(0, 400)}`);
106
- });
107
- // ---------------------------------------------------------------------------
108
- // 5. Includes recent decisions (.squad/decisions.md)
109
- // ---------------------------------------------------------------------------
110
- test("getSquadCoordinatorSystemMessage includes recent decisions from .squad/decisions.md", async () => {
111
- const charter = await loadCharterModule();
112
- const fn = charter.getSquadCoordinatorSystemMessage;
113
- const DECISIONS = "## Decision Alpha\nUse strict TypeScript.\n\n## Decision Beta\nCache with Redis.";
114
- const projectRoot = makeProjectDir("with-decisions", {
115
- githubAgentFile: "# Coordinator",
116
- teamMd: "# Team",
117
- decisionsMd: DECISIONS,
118
- });
119
- const result = await fn(projectRoot);
120
- assert.ok(result.includes("Decision Alpha") || result.includes("Use strict TypeScript"), `result should include decisions content, got: ${result.slice(0, 500)}`);
121
- });
122
- // ---------------------------------------------------------------------------
123
- // 6. Graceful when .squad/team.md is missing
124
- // ---------------------------------------------------------------------------
125
- test("getSquadCoordinatorSystemMessage does not throw when .squad/team.md is absent, includes warning", async () => {
126
- const charter = await loadCharterModule();
127
- const fn = charter.getSquadCoordinatorSystemMessage;
128
- const projectRoot = makeProjectDir("no-team-md", {
129
- githubAgentFile: "# Coordinator",
130
- // no teamMd
131
- decisionsMd: "## Dec\nsome decision",
132
- });
133
- let result;
134
- await assert.doesNotReject(async () => { result = await fn(projectRoot); }, "should not throw when team.md is missing");
135
- assert.ok(typeof result === "string", "should return a string even without team.md");
136
- // Should include some indication that team.md is missing
137
- assert.ok(result.includes("team.md") || result.toLowerCase().includes("not found") || result.includes("create .squad"), `result should include a warning about missing team.md, got: ${result?.slice(0, 400)}`);
138
- });
139
- // ---------------------------------------------------------------------------
140
- // 7. Does NOT include generic Chapterhouse system message sections
141
- // ---------------------------------------------------------------------------
142
- test("getSquadCoordinatorSystemMessage does not include generic Chapterhouse sections", async () => {
143
- const charter = await loadCharterModule();
144
- const fn = charter.getSquadCoordinatorSystemMessage;
145
- const projectRoot = makeProjectDir("no-ch-sections", {
146
- githubAgentFile: "# Squad Coordinator\nProject-specific coordinator.",
147
- teamMd: "# Team",
148
- decisionsMd: "## Dec\nDecision text.",
149
- });
150
- const result = await fn(projectRoot);
151
- // These are markers from the default Chapterhouse orchestrator system message
152
- // — they must NOT appear in a Squad coordinator message
153
- const forbiddenPhrases = [
154
- "You are Chapterhouse",
155
- "@mention",
156
- "delegate_to_agent",
157
- ];
158
- for (const phrase of forbiddenPhrases) {
159
- assert.equal(result.includes(phrase), false, `result should NOT include Chapterhouse-specific phrase: "${phrase}"`);
160
- }
161
- });
162
- // ---------------------------------------------------------------------------
163
- // 8. .github/agents/squad.agent.md takes priority over root squad.agent.md
164
- // ---------------------------------------------------------------------------
165
- test("getSquadCoordinatorSystemMessage prefers .github/agents/squad.agent.md over root fallback", async () => {
166
- const charter = await loadCharterModule();
167
- const fn = charter.getSquadCoordinatorSystemMessage;
168
- const projectRoot = makeProjectDir("both-agent-files", {
169
- githubAgentFile: "PRIMARY_AGENT_CONTENT",
170
- rootAgentFile: "FALLBACK_AGENT_CONTENT",
171
- teamMd: "# Team",
172
- });
173
- const result = await fn(projectRoot);
174
- assert.ok(result.includes("PRIMARY_AGENT_CONTENT"), "should use .github/agents/squad.agent.md (primary) when both files exist");
175
- assert.equal(result.includes("FALLBACK_AGENT_CONTENT"), false, "should NOT use root squad.agent.md when .github/agents/squad.agent.md exists");
176
- });
177
- // ---------------------------------------------------------------------------
178
- // 9. Result contains project context block with project root
179
- // ---------------------------------------------------------------------------
180
- test("getSquadCoordinatorSystemMessage includes project root path in output", async () => {
181
- const charter = await loadCharterModule();
182
- const fn = charter.getSquadCoordinatorSystemMessage;
183
- const projectRoot = makeProjectDir("with-root-in-output", {
184
- githubAgentFile: "# Coordinator",
185
- teamMd: "# Team",
186
- });
187
- const result = await fn(projectRoot);
188
- assert.ok(result.includes(projectRoot), `result should include the project root path (${projectRoot}), got: ${result.slice(0, 400)}`);
189
- });
190
- //# sourceMappingURL=squad-coordinator-system-message.test.js.map
@@ -1,260 +0,0 @@
1
- /**
2
- * Phase 4 — Session key derivation and API routing tests
3
- *
4
- * Covers:
5
- * - encodeProjectRef + decodeProjectRef roundtrip (pure functions inlined)
6
- * - projectSessionKey format
7
- * - Session key derived from projectPath in POST /api/message
8
- * - Explicit sessionKey overrides derived sessionKey
9
- * - agent_tasks.session_key is set to originating session
10
- *
11
- * Note: encodeProjectRef / decodeProjectRef live in web/src/lib/projectRef.ts
12
- * and are browser-safe pure functions. They are reimplemented inline here so
13
- * the server-side test suite can validate the contract without importing
14
- * frontend code.
15
- */
16
- import assert from "node:assert/strict";
17
- import { mkdirSync, rmSync } from "node:fs";
18
- import { join } from "node:path";
19
- import test from "node:test";
20
- const repoRoot = process.cwd();
21
- const sandboxRoot = join(repoRoot, ".test-work", `squad-routing-${process.pid}`);
22
- const chapterhouseHome = join(sandboxRoot, ".chapterhouse");
23
- const dbPath = join(chapterhouseHome, "chapterhouse.db");
24
- process.env.CHAPTERHOUSE_HOME = sandboxRoot;
25
- // ---------------------------------------------------------------------------
26
- // Pure helper re-implementations (browser-safe, no Node.js imports needed)
27
- // These mirror what Wash will deliver in web/src/lib/projectRef.ts
28
- // ---------------------------------------------------------------------------
29
- /** Encode a project root path to a URL-safe base64url string. */
30
- function encodeProjectRef(projectRoot) {
31
- return Buffer.from(projectRoot, "utf-8").toString("base64url");
32
- }
33
- /** Decode a base64url string back to the original project root path. */
34
- function decodeProjectRef(projectRef) {
35
- return Buffer.from(projectRef, "base64url").toString("utf-8");
36
- }
37
- /** Build the canonical session key for a project session. */
38
- function projectSessionKey(normalizedRoot) {
39
- return `project:${normalizedRoot}`;
40
- }
41
- async function loadDbModule() {
42
- return await import(new URL(`../store/db.js?case=${Date.now()}-${Math.random()}`, import.meta.url).href);
43
- }
44
- async function loadContextModule() {
45
- return await import(new URL(`./context.js?v=${Date.now()}-${Math.random()}`, import.meta.url).href);
46
- }
47
- function resetSandbox() {
48
- mkdirSync(join(repoRoot, ".test-work"), { recursive: true });
49
- rmSync(sandboxRoot, { recursive: true, force: true });
50
- mkdirSync(chapterhouseHome, { recursive: true });
51
- }
52
- test.beforeEach(() => {
53
- resetSandbox();
54
- });
55
- test.after(() => {
56
- rmSync(sandboxRoot, { recursive: true, force: true });
57
- });
58
- // ---------------------------------------------------------------------------
59
- // 1. encodeProjectRef + decodeProjectRef roundtrip
60
- // ---------------------------------------------------------------------------
61
- test("encodeProjectRef + decodeProjectRef roundtrip returns original path", () => {
62
- const paths = [
63
- "/home/user/myrepo",
64
- "/home/alice/projects/chapterhouse",
65
- "/var/srv/prod-api",
66
- "/some/path with spaces/and-dashes",
67
- ];
68
- for (const original of paths) {
69
- const encoded = encodeProjectRef(original);
70
- const decoded = decodeProjectRef(encoded);
71
- assert.equal(decoded, original, `roundtrip failed for path: ${original}`);
72
- }
73
- });
74
- test("encodeProjectRef produces URL-safe output (no +, /, = characters)", () => {
75
- const path = "/home/user/some+tricky/path=value";
76
- const encoded = encodeProjectRef(path);
77
- assert.equal(encoded.includes("+"), false, "encoded ref should not contain +");
78
- assert.equal(encoded.includes("/"), false, "encoded ref should not contain /");
79
- assert.equal(encoded.includes("="), false, "encoded ref should not contain =");
80
- });
81
- // ---------------------------------------------------------------------------
82
- // 2. projectSessionKey format
83
- // ---------------------------------------------------------------------------
84
- test("projectSessionKey produces 'project:<path>' format", () => {
85
- const path = "/home/user/repo";
86
- const key = projectSessionKey(path);
87
- assert.equal(key, "project:/home/user/repo");
88
- assert.match(key, /^project:\//, "session key should start with 'project:/'");
89
- });
90
- test("projectSessionKey is stable — same input produces same output", () => {
91
- const path = "/home/user/consistent-repo";
92
- assert.equal(projectSessionKey(path), projectSessionKey(path));
93
- });
94
- // ---------------------------------------------------------------------------
95
- // 3. Session key from projectPath in API message handler
96
- // ---------------------------------------------------------------------------
97
- test("POST /api/message request body: sessionKey derived from projectPath uses 'project:' prefix", async () => {
98
- // This test validates the _contract_ that the /api/message handler must honour:
99
- // when a request arrives with projectPath set, the derived session key must be
100
- // project:${normalizeProjectPath(projectPath)}.
101
- const context = await loadContextModule();
102
- const projectPath = "/home/tester/my-app";
103
- const normalized = context.normalizeProjectPath(projectPath);
104
- const derived = projectSessionKey(normalized);
105
- assert.equal(derived, `project:${normalized}`, "session key derived from projectPath must be project:${normalized}");
106
- assert.match(derived, /^project:\//, "derived session key must start with 'project:/'");
107
- });
108
- // ---------------------------------------------------------------------------
109
- // 4. Explicit sessionKey overrides derived key
110
- // ---------------------------------------------------------------------------
111
- test("explicit sessionKey in request body overrides projectPath-derived key", () => {
112
- // Pure logic test: when both sessionKey and projectPath are present,
113
- // sessionKey takes precedence. This mimics the handler contract.
114
- function deriveEffectiveSessionKey(opts) {
115
- if (opts.sessionKey)
116
- return opts.sessionKey;
117
- if (opts.projectPath)
118
- return projectSessionKey(opts.normalizeProjectPath(opts.projectPath));
119
- return "default";
120
- }
121
- const result = deriveEffectiveSessionKey({
122
- sessionKey: "project:/explicit/override",
123
- projectPath: "/some/other/path",
124
- normalizeProjectPath: (p) => p,
125
- });
126
- assert.equal(result, "project:/explicit/override", "explicit sessionKey must win over projectPath-derived key");
127
- });
128
- test("no sessionKey and no projectPath defaults to 'default' session", () => {
129
- function deriveEffectiveSessionKey(opts) {
130
- if (opts.sessionKey)
131
- return opts.sessionKey;
132
- if (opts.projectPath)
133
- return projectSessionKey(opts.normalizeProjectPath(opts.projectPath));
134
- return "default";
135
- }
136
- const result = deriveEffectiveSessionKey({
137
- normalizeProjectPath: (p) => p,
138
- });
139
- assert.equal(result, "default", "absence of both sessionKey and projectPath must yield 'default'");
140
- });
141
- // ---------------------------------------------------------------------------
142
- // 5. agent_tasks.session_key — task created in project session stores correct key
143
- // ---------------------------------------------------------------------------
144
- test("agent_tasks session_key is persisted and matches the originating session", async () => {
145
- const dbModule = await loadDbModule();
146
- try {
147
- const db = dbModule.getDb();
148
- // Verify the column exists
149
- const cols = db.prepare(`PRAGMA table_info(agent_tasks)`).all();
150
- const colNames = new Set(cols.map((c) => c.name));
151
- assert.equal(colNames.has("session_key"), true, "agent_tasks should have session_key column");
152
- // Insert a task with a project session key (simulating a delegated task)
153
- const sessionKey = "project:/home/tester/repo-x";
154
- db.prepare(`
155
- INSERT INTO agent_tasks (task_id, agent_slug, description, status, session_key)
156
- VALUES (?, ?, ?, ?, ?)
157
- `).run("task-001", "coder", "Implement feature X", "running", sessionKey);
158
- const row = db.prepare(`SELECT session_key FROM agent_tasks WHERE task_id = 'task-001'`).get();
159
- assert.ok(row, "task row should exist");
160
- assert.equal(row.session_key, sessionKey, "stored session_key must match the originating session");
161
- }
162
- finally {
163
- dbModule.closeDb();
164
- }
165
- });
166
- // ---------------------------------------------------------------------------
167
- // 6. /projects/chat/:projectRef route — decoding produces valid session key
168
- // ---------------------------------------------------------------------------
169
- test("decoding :projectRef from route produces valid project session key", () => {
170
- const projectRoot = "/home/user/my-project";
171
- const projectRef = encodeProjectRef(projectRoot);
172
- // Simulate what the frontend router does: decode the param and derive session key
173
- const decoded = decodeProjectRef(projectRef);
174
- const sessionKey = projectSessionKey(decoded);
175
- assert.equal(decoded, projectRoot, "decoded path must match original");
176
- assert.equal(sessionKey, `project:${projectRoot}`, "session key must be project:${path}");
177
- });
178
- // ---------------------------------------------------------------------------
179
- // 7. SSE / final message payload includes sessionKey field
180
- // ---------------------------------------------------------------------------
181
- test("SSE message payload contract: sessionKey field is present and matches active session", () => {
182
- function buildSsePayload(sessionKey, content, projectPath) {
183
- const payload = { sessionKey, content };
184
- if (projectPath)
185
- payload.projectPath = projectPath;
186
- return payload;
187
- }
188
- const defaultPayload = buildSsePayload("default", "Hello from default chat");
189
- assert.equal(defaultPayload.sessionKey, "default");
190
- assert.equal(defaultPayload.projectPath, undefined);
191
- const projectPayload = buildSsePayload("project:/home/user/repo", "Hello from project", "/home/user/repo");
192
- assert.equal(projectPayload.sessionKey, "project:/home/user/repo");
193
- assert.equal(projectPayload.projectPath, "/home/user/repo");
194
- });
195
- // ---------------------------------------------------------------------------
196
- // 8. copilot_sessions upsert is idempotent (re-upsert updates copilot_session_id)
197
- // ---------------------------------------------------------------------------
198
- test("upsertCopilotSession is idempotent — second upsert updates copilot_session_id", async () => {
199
- const dbModule = await loadDbModule();
200
- try {
201
- dbModule.getDb();
202
- if (typeof dbModule.upsertCopilotSession !== "function" || typeof dbModule.getCopilotSession !== "function") {
203
- // Functions not yet implemented — test will be completed when Mal ships Phase 1
204
- return;
205
- }
206
- const key = "project:/home/user/idempotent-test";
207
- dbModule.upsertCopilotSession(key, "project", "session-v1", "/home/user/idempotent-test");
208
- dbModule.upsertCopilotSession(key, "project", "session-v2", "/home/user/idempotent-test");
209
- const row = dbModule.getCopilotSession(key);
210
- assert.ok(row, "row should exist after double upsert");
211
- assert.equal(row.copilotSessionId, "session-v2", "second upsert should overwrite copilot_session_id");
212
- }
213
- finally {
214
- dbModule.closeDb();
215
- }
216
- });
217
- // ---------------------------------------------------------------------------
218
- // 9. Default session and project session have independent copilot_session_ids
219
- // ---------------------------------------------------------------------------
220
- test("default session and project session store independent copilot_session_ids", async () => {
221
- const dbModule = await loadDbModule();
222
- try {
223
- dbModule.getDb();
224
- if (typeof dbModule.upsertCopilotSession !== "function" || typeof dbModule.getCopilotSession !== "function") {
225
- return; // Not yet implemented
226
- }
227
- dbModule.upsertCopilotSession("default", "default", "default-sdk-session");
228
- dbModule.upsertCopilotSession("project:/home/user/proj-a", "project", "project-a-sdk-session", "/home/user/proj-a");
229
- dbModule.upsertCopilotSession("project:/home/user/proj-b", "project", "project-b-sdk-session", "/home/user/proj-b");
230
- const defaultRow = dbModule.getCopilotSession("default");
231
- const projARow = dbModule.getCopilotSession("project:/home/user/proj-a");
232
- const projBRow = dbModule.getCopilotSession("project:/home/user/proj-b");
233
- assert.equal(defaultRow?.copilotSessionId, "default-sdk-session");
234
- assert.equal(projARow?.copilotSessionId, "project-a-sdk-session");
235
- assert.equal(projBRow?.copilotSessionId, "project-b-sdk-session");
236
- // All three must be distinct
237
- const ids = [defaultRow?.copilotSessionId, projARow?.copilotSessionId, projBRow?.copilotSessionId];
238
- assert.equal(new Set(ids).size, 3, "all three sessions must have distinct copilot_session_ids");
239
- }
240
- finally {
241
- dbModule.closeDb();
242
- }
243
- });
244
- // ---------------------------------------------------------------------------
245
- // 10. SSE cancelled payload includes sessionKey
246
- // ---------------------------------------------------------------------------
247
- test("SSE cancelled payload contract: sessionKey field is present", () => {
248
- function buildCancelledPayload(sessionKey) {
249
- return { type: "cancelled", sessionKey };
250
- }
251
- const defaultPayload = buildCancelledPayload("default");
252
- assert.equal(defaultPayload.type, "cancelled", "type must be 'cancelled'");
253
- assert.equal(defaultPayload.sessionKey, "default", "sessionKey must be present for default session");
254
- const projectPayload = buildCancelledPayload("project:/home/user/repo");
255
- assert.equal(projectPayload.type, "cancelled");
256
- assert.equal(projectPayload.sessionKey, "project:/home/user/repo", "sessionKey must be present and match for project session");
257
- assert.ok("sessionKey" in defaultPayload, "sessionKey field must exist on cancelled payload");
258
- assert.ok("sessionKey" in projectPayload, "sessionKey field must exist on project cancelled payload");
259
- });
260
- //# sourceMappingURL=squad-session-routing.test.js.map
@@ -1,4 +0,0 @@
1
- // New runtime types for the Squad integration layer.
2
- // These are pure TypeScript types — no runtime dependencies.
3
- export {};
4
- //# sourceMappingURL=types.js.map