@wingman-ai/gateway 0.2.1 → 0.2.3

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 (72) hide show
  1. package/.wingman/agents/README.md +1 -0
  2. package/.wingman/agents/coding/agent.md +179 -112
  3. package/.wingman/agents/coding/implementor.md +50 -3
  4. package/.wingman/agents/main/agent.md +4 -0
  5. package/README.md +1 -0
  6. package/dist/agent/config/agentConfig.cjs +30 -1
  7. package/dist/agent/config/agentConfig.js +30 -1
  8. package/dist/agent/config/modelFactory.cjs +22 -2
  9. package/dist/agent/config/modelFactory.d.ts +2 -0
  10. package/dist/agent/config/modelFactory.js +22 -2
  11. package/dist/agent/tests/agentConfig.test.cjs +39 -0
  12. package/dist/agent/tests/agentConfig.test.js +39 -0
  13. package/dist/agent/tests/modelFactory.test.cjs +12 -5
  14. package/dist/agent/tests/modelFactory.test.js +12 -5
  15. package/dist/cli/commands/init.cjs +7 -6
  16. package/dist/cli/commands/init.js +7 -6
  17. package/dist/cli/commands/provider.cjs +17 -3
  18. package/dist/cli/commands/provider.js +17 -3
  19. package/dist/cli/config/loader.cjs +27 -0
  20. package/dist/cli/config/loader.js +27 -0
  21. package/dist/cli/config/schema.cjs +80 -2
  22. package/dist/cli/config/schema.d.ts +88 -0
  23. package/dist/cli/config/schema.js +67 -1
  24. package/dist/cli/core/agentInvoker.cjs +242 -17
  25. package/dist/cli/core/agentInvoker.d.ts +46 -4
  26. package/dist/cli/core/agentInvoker.js +214 -13
  27. package/dist/cli/core/sessionManager.cjs +32 -5
  28. package/dist/cli/core/sessionManager.js +32 -5
  29. package/dist/cli/index.cjs +6 -5
  30. package/dist/cli/index.js +6 -5
  31. package/dist/cli/types.d.ts +32 -0
  32. package/dist/gateway/http/sessions.cjs +7 -7
  33. package/dist/gateway/http/sessions.js +7 -7
  34. package/dist/gateway/server.cjs +230 -28
  35. package/dist/gateway/server.d.ts +11 -1
  36. package/dist/gateway/server.js +230 -28
  37. package/dist/gateway/types.d.ts +5 -1
  38. package/dist/gateway/validation.cjs +1 -0
  39. package/dist/gateway/validation.d.ts +2 -0
  40. package/dist/gateway/validation.js +1 -0
  41. package/dist/providers/codex.cjs +167 -0
  42. package/dist/providers/codex.d.ts +15 -0
  43. package/dist/providers/codex.js +127 -0
  44. package/dist/providers/credentials.cjs +8 -0
  45. package/dist/providers/credentials.js +8 -0
  46. package/dist/providers/registry.cjs +11 -0
  47. package/dist/providers/registry.d.ts +1 -1
  48. package/dist/providers/registry.js +11 -0
  49. package/dist/tests/agentInvokerSummarization.test.cjs +296 -0
  50. package/dist/tests/agentInvokerSummarization.test.d.ts +1 -0
  51. package/dist/tests/agentInvokerSummarization.test.js +290 -0
  52. package/dist/tests/cli-config-loader.test.cjs +88 -0
  53. package/dist/tests/cli-config-loader.test.js +88 -0
  54. package/dist/tests/codex-credentials-precedence.test.cjs +94 -0
  55. package/dist/tests/codex-credentials-precedence.test.d.ts +1 -0
  56. package/dist/tests/codex-credentials-precedence.test.js +88 -0
  57. package/dist/tests/codex-provider.test.cjs +186 -0
  58. package/dist/tests/codex-provider.test.d.ts +1 -0
  59. package/dist/tests/codex-provider.test.js +180 -0
  60. package/dist/tests/gateway.test.cjs +173 -1
  61. package/dist/tests/gateway.test.js +173 -1
  62. package/dist/tests/provider-command-codex.test.cjs +57 -0
  63. package/dist/tests/provider-command-codex.test.d.ts +1 -0
  64. package/dist/tests/provider-command-codex.test.js +51 -0
  65. package/dist/tests/sessionStateMessages.test.cjs +38 -0
  66. package/dist/tests/sessionStateMessages.test.js +38 -0
  67. package/dist/webui/assets/index-BVMavpud.css +11 -0
  68. package/dist/webui/assets/index-DCB2aVVf.js +182 -0
  69. package/dist/webui/index.html +2 -2
  70. package/package.json +3 -1
  71. package/dist/webui/assets/index-BytPznA_.css +0 -1
  72. package/dist/webui/assets/index-u_5qlVip.js +0 -176
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ var __webpack_exports__ = {};
3
+ const external_node_fs_namespaceObject = require("node:fs");
4
+ const external_node_os_namespaceObject = require("node:os");
5
+ const external_node_path_namespaceObject = require("node:path");
6
+ const external_vitest_namespaceObject = require("vitest");
7
+ const codex_cjs_namespaceObject = require("../providers/codex.cjs");
8
+ (0, external_vitest_namespaceObject.describe)("codex provider", ()=>{
9
+ let codexHome;
10
+ const originalCodexHome = process.env.CODEX_HOME;
11
+ (0, external_vitest_namespaceObject.beforeEach)(()=>{
12
+ codexHome = (0, external_node_fs_namespaceObject.mkdtempSync)((0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.tmpdir)(), "wingman-codex-"));
13
+ process.env.CODEX_HOME = codexHome;
14
+ delete process.env.CODEX_ACCESS_TOKEN;
15
+ delete process.env.CHATGPT_ACCESS_TOKEN;
16
+ });
17
+ (0, external_vitest_namespaceObject.afterEach)(()=>{
18
+ if (void 0 === originalCodexHome) delete process.env.CODEX_HOME;
19
+ else process.env.CODEX_HOME = originalCodexHome;
20
+ if ((0, external_node_fs_namespaceObject.existsSync)(codexHome)) (0, external_node_fs_namespaceObject.rmSync)(codexHome, {
21
+ recursive: true,
22
+ force: true
23
+ });
24
+ });
25
+ (0, external_vitest_namespaceObject.it)("reads access token and account id from codex auth file", ()=>{
26
+ writeCodexAuth({
27
+ tokens: {
28
+ access_token: "codex-access-token",
29
+ account_id: "acct_123"
30
+ }
31
+ });
32
+ const resolved = (0, codex_cjs_namespaceObject.resolveCodexAuthFromFile)();
33
+ (0, external_vitest_namespaceObject.expect)(resolved.accessToken).toBe("codex-access-token");
34
+ (0, external_vitest_namespaceObject.expect)(resolved.accountId).toBe("acct_123");
35
+ (0, external_vitest_namespaceObject.expect)(resolved.authPath).toBe((0, external_node_path_namespaceObject.join)(codexHome, "auth.json"));
36
+ });
37
+ (0, external_vitest_namespaceObject.it)("applies codex auth headers and forces store=false", async ()=>{
38
+ writeCodexAuth({
39
+ tokens: {
40
+ access_token: "file-token",
41
+ account_id: "acct_file"
42
+ }
43
+ });
44
+ const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
45
+ status: 200
46
+ }));
47
+ const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
48
+ baseFetch
49
+ });
50
+ await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
51
+ method: "POST",
52
+ headers: {
53
+ "x-api-key": "placeholder"
54
+ },
55
+ body: JSON.stringify({
56
+ model: "codex-mini-latest",
57
+ input: "hello",
58
+ temperature: 1
59
+ })
60
+ });
61
+ (0, external_vitest_namespaceObject.expect)(baseFetch).toHaveBeenCalledTimes(1);
62
+ const requestInit = baseFetch.mock.calls[0]?.[1];
63
+ (0, external_vitest_namespaceObject.expect)(requestInit).toBeDefined();
64
+ const headers = new Headers(requestInit?.headers);
65
+ const payload = JSON.parse(String(requestInit?.body));
66
+ (0, external_vitest_namespaceObject.expect)(headers.get("authorization")).toBe("Bearer file-token");
67
+ (0, external_vitest_namespaceObject.expect)(headers.get("chatgpt-account-id")).toBe("acct_file");
68
+ (0, external_vitest_namespaceObject.expect)(headers.get("x-api-key")).toBeNull();
69
+ (0, external_vitest_namespaceObject.expect)(payload.store).toBe(false);
70
+ (0, external_vitest_namespaceObject.expect)(payload.temperature).toBeUndefined();
71
+ (0, external_vitest_namespaceObject.expect)(typeof payload.instructions).toBe("string");
72
+ (0, external_vitest_namespaceObject.expect)(payload.instructions.length).toBeGreaterThan(0);
73
+ });
74
+ (0, external_vitest_namespaceObject.it)("derives instructions from system/developer input when missing", async ()=>{
75
+ writeCodexAuth({
76
+ tokens: {
77
+ access_token: "file-token"
78
+ }
79
+ });
80
+ const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
81
+ status: 200
82
+ }));
83
+ const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
84
+ baseFetch
85
+ });
86
+ await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
87
+ method: "POST",
88
+ body: JSON.stringify({
89
+ model: "gpt-5.3-codex",
90
+ input: [
91
+ {
92
+ role: "developer",
93
+ content: [
94
+ {
95
+ type: "input_text",
96
+ text: "Always run tests first."
97
+ }
98
+ ]
99
+ },
100
+ {
101
+ role: "user",
102
+ content: [
103
+ {
104
+ type: "input_text",
105
+ text: "Fix the bug."
106
+ }
107
+ ]
108
+ }
109
+ ]
110
+ })
111
+ });
112
+ const requestInit = baseFetch.mock.calls[0]?.[1];
113
+ const payload = JSON.parse(String(requestInit?.body));
114
+ (0, external_vitest_namespaceObject.expect)(payload.instructions).toBe("Always run tests first.");
115
+ });
116
+ (0, external_vitest_namespaceObject.it)("preserves explicit instructions when provided", async ()=>{
117
+ writeCodexAuth({
118
+ tokens: {
119
+ access_token: "file-token"
120
+ }
121
+ });
122
+ const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
123
+ status: 200
124
+ }));
125
+ const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
126
+ baseFetch
127
+ });
128
+ await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
129
+ method: "POST",
130
+ body: JSON.stringify({
131
+ model: "gpt-5.3-codex",
132
+ instructions: "Use concise answers.",
133
+ input: "hello"
134
+ })
135
+ });
136
+ const requestInit = baseFetch.mock.calls[0]?.[1];
137
+ const payload = JSON.parse(String(requestInit?.body));
138
+ (0, external_vitest_namespaceObject.expect)(payload.instructions).toBe("Use concise answers.");
139
+ });
140
+ (0, external_vitest_namespaceObject.it)("uses fallback token when codex auth file is unavailable", async ()=>{
141
+ const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
142
+ status: 200
143
+ }));
144
+ const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
145
+ baseFetch,
146
+ fallbackToken: "fallback-token"
147
+ });
148
+ await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
149
+ method: "POST",
150
+ body: JSON.stringify({
151
+ model: "codex-mini-latest",
152
+ input: "hello"
153
+ })
154
+ });
155
+ const requestInit = baseFetch.mock.calls[0]?.[1];
156
+ (0, external_vitest_namespaceObject.expect)(requestInit).toBeDefined();
157
+ const headers = new Headers(requestInit?.headers);
158
+ (0, external_vitest_namespaceObject.expect)(headers.get("authorization")).toBe("Bearer fallback-token");
159
+ });
160
+ (0, external_vitest_namespaceObject.it)("throws when no codex token is available", async ()=>{
161
+ const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
162
+ status: 200
163
+ }));
164
+ const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
165
+ baseFetch
166
+ });
167
+ await (0, external_vitest_namespaceObject.expect)(codexFetch("https://chatgpt.com/backend-api/codex/responses", {
168
+ method: "POST",
169
+ body: JSON.stringify({
170
+ model: "codex-mini-latest",
171
+ input: "hello"
172
+ })
173
+ })).rejects.toThrow(/Codex credentials missing/);
174
+ });
175
+ });
176
+ function writeCodexAuth(payload) {
177
+ const authPath = (0, codex_cjs_namespaceObject.getCodexAuthPath)();
178
+ (0, external_node_fs_namespaceObject.mkdirSync)((0, external_node_path_namespaceObject.dirname)(authPath), {
179
+ recursive: true
180
+ });
181
+ (0, external_node_fs_namespaceObject.writeFileSync)(authPath, JSON.stringify(payload, null, 2));
182
+ }
183
+ for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
184
+ Object.defineProperty(exports, '__esModule', {
185
+ value: true
186
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,180 @@
1
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { createCodexFetch, getCodexAuthPath, resolveCodexAuthFromFile } from "../providers/codex.js";
6
+ describe("codex provider", ()=>{
7
+ let codexHome;
8
+ const originalCodexHome = process.env.CODEX_HOME;
9
+ beforeEach(()=>{
10
+ codexHome = mkdtempSync(join(tmpdir(), "wingman-codex-"));
11
+ process.env.CODEX_HOME = codexHome;
12
+ delete process.env.CODEX_ACCESS_TOKEN;
13
+ delete process.env.CHATGPT_ACCESS_TOKEN;
14
+ });
15
+ afterEach(()=>{
16
+ if (void 0 === originalCodexHome) delete process.env.CODEX_HOME;
17
+ else process.env.CODEX_HOME = originalCodexHome;
18
+ if (existsSync(codexHome)) rmSync(codexHome, {
19
+ recursive: true,
20
+ force: true
21
+ });
22
+ });
23
+ it("reads access token and account id from codex auth file", ()=>{
24
+ writeCodexAuth({
25
+ tokens: {
26
+ access_token: "codex-access-token",
27
+ account_id: "acct_123"
28
+ }
29
+ });
30
+ const resolved = resolveCodexAuthFromFile();
31
+ expect(resolved.accessToken).toBe("codex-access-token");
32
+ expect(resolved.accountId).toBe("acct_123");
33
+ expect(resolved.authPath).toBe(join(codexHome, "auth.json"));
34
+ });
35
+ it("applies codex auth headers and forces store=false", async ()=>{
36
+ writeCodexAuth({
37
+ tokens: {
38
+ access_token: "file-token",
39
+ account_id: "acct_file"
40
+ }
41
+ });
42
+ const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
43
+ status: 200
44
+ }));
45
+ const codexFetch = createCodexFetch({
46
+ baseFetch
47
+ });
48
+ await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
49
+ method: "POST",
50
+ headers: {
51
+ "x-api-key": "placeholder"
52
+ },
53
+ body: JSON.stringify({
54
+ model: "codex-mini-latest",
55
+ input: "hello",
56
+ temperature: 1
57
+ })
58
+ });
59
+ expect(baseFetch).toHaveBeenCalledTimes(1);
60
+ const requestInit = baseFetch.mock.calls[0]?.[1];
61
+ expect(requestInit).toBeDefined();
62
+ const headers = new Headers(requestInit?.headers);
63
+ const payload = JSON.parse(String(requestInit?.body));
64
+ expect(headers.get("authorization")).toBe("Bearer file-token");
65
+ expect(headers.get("chatgpt-account-id")).toBe("acct_file");
66
+ expect(headers.get("x-api-key")).toBeNull();
67
+ expect(payload.store).toBe(false);
68
+ expect(payload.temperature).toBeUndefined();
69
+ expect(typeof payload.instructions).toBe("string");
70
+ expect(payload.instructions.length).toBeGreaterThan(0);
71
+ });
72
+ it("derives instructions from system/developer input when missing", async ()=>{
73
+ writeCodexAuth({
74
+ tokens: {
75
+ access_token: "file-token"
76
+ }
77
+ });
78
+ const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
79
+ status: 200
80
+ }));
81
+ const codexFetch = createCodexFetch({
82
+ baseFetch
83
+ });
84
+ await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
85
+ method: "POST",
86
+ body: JSON.stringify({
87
+ model: "gpt-5.3-codex",
88
+ input: [
89
+ {
90
+ role: "developer",
91
+ content: [
92
+ {
93
+ type: "input_text",
94
+ text: "Always run tests first."
95
+ }
96
+ ]
97
+ },
98
+ {
99
+ role: "user",
100
+ content: [
101
+ {
102
+ type: "input_text",
103
+ text: "Fix the bug."
104
+ }
105
+ ]
106
+ }
107
+ ]
108
+ })
109
+ });
110
+ const requestInit = baseFetch.mock.calls[0]?.[1];
111
+ const payload = JSON.parse(String(requestInit?.body));
112
+ expect(payload.instructions).toBe("Always run tests first.");
113
+ });
114
+ it("preserves explicit instructions when provided", async ()=>{
115
+ writeCodexAuth({
116
+ tokens: {
117
+ access_token: "file-token"
118
+ }
119
+ });
120
+ const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
121
+ status: 200
122
+ }));
123
+ const codexFetch = createCodexFetch({
124
+ baseFetch
125
+ });
126
+ await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
127
+ method: "POST",
128
+ body: JSON.stringify({
129
+ model: "gpt-5.3-codex",
130
+ instructions: "Use concise answers.",
131
+ input: "hello"
132
+ })
133
+ });
134
+ const requestInit = baseFetch.mock.calls[0]?.[1];
135
+ const payload = JSON.parse(String(requestInit?.body));
136
+ expect(payload.instructions).toBe("Use concise answers.");
137
+ });
138
+ it("uses fallback token when codex auth file is unavailable", async ()=>{
139
+ const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
140
+ status: 200
141
+ }));
142
+ const codexFetch = createCodexFetch({
143
+ baseFetch,
144
+ fallbackToken: "fallback-token"
145
+ });
146
+ await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
147
+ method: "POST",
148
+ body: JSON.stringify({
149
+ model: "codex-mini-latest",
150
+ input: "hello"
151
+ })
152
+ });
153
+ const requestInit = baseFetch.mock.calls[0]?.[1];
154
+ expect(requestInit).toBeDefined();
155
+ const headers = new Headers(requestInit?.headers);
156
+ expect(headers.get("authorization")).toBe("Bearer fallback-token");
157
+ });
158
+ it("throws when no codex token is available", async ()=>{
159
+ const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
160
+ status: 200
161
+ }));
162
+ const codexFetch = createCodexFetch({
163
+ baseFetch
164
+ });
165
+ await expect(codexFetch("https://chatgpt.com/backend-api/codex/responses", {
166
+ method: "POST",
167
+ body: JSON.stringify({
168
+ model: "codex-mini-latest",
169
+ input: "hello"
170
+ })
171
+ })).rejects.toThrow(/Codex credentials missing/);
172
+ });
173
+ });
174
+ function writeCodexAuth(payload) {
175
+ const authPath = getCodexAuthPath();
176
+ mkdirSync(dirname(authPath), {
177
+ recursive: true
178
+ });
179
+ writeFileSync(authPath, JSON.stringify(payload, null, 2));
180
+ }
@@ -2,15 +2,53 @@
2
2
  var __webpack_exports__ = {};
3
3
  const external_vitest_namespaceObject = require("vitest");
4
4
  const index_cjs_namespaceObject = require("../gateway/index.cjs");
5
+ function _define_property(obj, key, value) {
6
+ if (key in obj) Object.defineProperty(obj, key, {
7
+ value: value,
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true
11
+ });
12
+ else obj[key] = value;
13
+ return obj;
14
+ }
5
15
  const isBun = void 0 !== globalThis.Bun;
6
16
  const describeIfBun = isBun ? external_vitest_namespaceObject.describe : external_vitest_namespaceObject.describe.skip;
7
17
  external_vitest_namespaceObject.vi.mock("@/cli/core/agentInvoker.js", ()=>({
8
18
  AgentInvoker: class {
9
- async invokeAgent() {
19
+ async invokeAgent(_agentId, content, _sessionId, _attachments, options) {
20
+ if ("throw-no-event" === content) throw new Error("Synthetic invocation failure");
21
+ const signal = options?.signal;
22
+ await new Promise((resolve)=>{
23
+ const timer = setTimeout(resolve, 75);
24
+ if (signal) {
25
+ const onAbort = ()=>{
26
+ clearTimeout(timer);
27
+ resolve();
28
+ };
29
+ if (signal.aborted) return void onAbort();
30
+ signal.addEventListener("abort", onAbort, {
31
+ once: true
32
+ });
33
+ }
34
+ });
35
+ if (signal?.aborted) {
36
+ this.outputManager?.emitAgentError?.("Request cancelled");
37
+ return {
38
+ cancelled: true
39
+ };
40
+ }
41
+ this.outputManager?.emitAgentComplete?.({
42
+ streaming: true
43
+ });
10
44
  return {
11
45
  streaming: true
12
46
  };
13
47
  }
48
+ constructor(options){
49
+ _define_property(this, "outputManager", void 0);
50
+ this.outputManager = options?.outputManager;
51
+ }
14
52
  }
15
53
  }));
16
54
  describeIfBun("Gateway", ()=>{
@@ -296,6 +334,140 @@ describeIfBun("Gateway", ()=>{
296
334
  desktopClient.close();
297
335
  requester.close();
298
336
  });
337
+ (0, external_vitest_namespaceObject.it)("should emit agent-error to requester when invocation throws without emitting", async ()=>{
338
+ const requester = await connectClient("session-error-requester");
339
+ const requestId = "req-invocation-error";
340
+ const sessionId = "session-error-test";
341
+ requester.send(JSON.stringify({
342
+ type: "req:agent",
343
+ id: requestId,
344
+ payload: {
345
+ agentId: "main",
346
+ sessionKey: sessionId,
347
+ content: "throw-no-event"
348
+ },
349
+ timestamp: Date.now()
350
+ }));
351
+ const errorMsg = await waitForMessage(requester, (msg)=>"event:agent" === msg.type && msg.id === requestId && msg.payload?.type === "agent-error");
352
+ (0, external_vitest_namespaceObject.expect)(errorMsg.payload?.error).toContain("Synthetic invocation failure");
353
+ (0, external_vitest_namespaceObject.expect)(errorMsg.payload?.sessionId).toBe(sessionId);
354
+ (0, external_vitest_namespaceObject.expect)(errorMsg.payload?.agentId).toBe("main");
355
+ requester.close();
356
+ });
357
+ (0, external_vitest_namespaceObject.it)("should cancel an in-flight agent request", async ()=>{
358
+ const requester = await connectClient("session-cancel-requester");
359
+ const requestId = "req-cancel-test";
360
+ requester.send(JSON.stringify({
361
+ type: "req:agent",
362
+ id: requestId,
363
+ payload: {
364
+ agentId: "main",
365
+ sessionKey: "session-cancel-test",
366
+ content: "cancel me"
367
+ },
368
+ timestamp: Date.now()
369
+ }));
370
+ requester.send(JSON.stringify({
371
+ type: "req:agent:cancel",
372
+ id: "cancel-req-cancel-test",
373
+ payload: {
374
+ requestId
375
+ },
376
+ timestamp: Date.now()
377
+ }));
378
+ const ack = await waitForMessage(requester, (msg)=>"ack" === msg.type && msg.payload?.action === "req:agent:cancel" && msg.payload?.requestId === requestId);
379
+ (0, external_vitest_namespaceObject.expect)([
380
+ "cancelled",
381
+ "not_found"
382
+ ]).toContain(ack.payload?.status);
383
+ requester.close();
384
+ });
385
+ (0, external_vitest_namespaceObject.it)("should queue and dequeue requests for the same session", async ()=>{
386
+ const requester = await connectClient("session-queue-requester");
387
+ const sessionId = `session-queue-${Date.now()}`;
388
+ const firstRequestId = `req-queue-first-${Date.now()}`;
389
+ const secondRequestId = `req-queue-second-${Date.now()}`;
390
+ const firstCompletePromise = waitForMessage(requester, (msg)=>"event:agent" === msg.type && msg.id === firstRequestId && msg.payload?.type === "agent-complete", 10000);
391
+ const queuedAckPromise = waitForMessage(requester, (msg)=>"ack" === msg.type && msg.id === secondRequestId && msg.payload?.action === "req:agent" && msg.payload?.status === "queued", 10000);
392
+ const queuedEventPromise = waitForMessage(requester, (msg)=>"event:agent" === msg.type && msg.id === secondRequestId && msg.payload?.type === "request-queued", 10000);
393
+ const dequeuedAckPromise = waitForMessage(requester, (msg)=>"ack" === msg.type && msg.id === secondRequestId && msg.payload?.action === "req:agent" && msg.payload?.status === "dequeued", 10000);
394
+ const secondCompletePromise = waitForMessage(requester, (msg)=>"event:agent" === msg.type && msg.id === secondRequestId && msg.payload?.type === "agent-complete", 10000);
395
+ requester.send(JSON.stringify({
396
+ type: "req:agent",
397
+ id: firstRequestId,
398
+ payload: {
399
+ agentId: "main",
400
+ sessionKey: sessionId,
401
+ content: "First queued request"
402
+ },
403
+ timestamp: Date.now()
404
+ }));
405
+ requester.send(JSON.stringify({
406
+ type: "req:agent",
407
+ id: secondRequestId,
408
+ payload: {
409
+ agentId: "main",
410
+ sessionKey: sessionId,
411
+ content: "Second queued request"
412
+ },
413
+ timestamp: Date.now()
414
+ }));
415
+ const queuedAck = await queuedAckPromise;
416
+ (0, external_vitest_namespaceObject.expect)(queuedAck.payload?.position).toBe(1);
417
+ const queuedEvent = await queuedEventPromise;
418
+ (0, external_vitest_namespaceObject.expect)(queuedEvent.payload?.position).toBe(1);
419
+ (0, external_vitest_namespaceObject.expect)(queuedEvent.payload?.sessionId).toBe(sessionId);
420
+ await firstCompletePromise;
421
+ const dequeuedAck = await dequeuedAckPromise;
422
+ (0, external_vitest_namespaceObject.expect)(dequeuedAck.payload?.remaining).toBe(0);
423
+ await secondCompletePromise;
424
+ requester.close();
425
+ });
426
+ (0, external_vitest_namespaceObject.it)("should cancel a queued request", async ()=>{
427
+ const requester = await connectClient("session-cancel-queued-requester");
428
+ const sessionId = `session-cancel-queued-${Date.now()}`;
429
+ const firstRequestId = `req-cancel-queued-first-${Date.now()}`;
430
+ const secondRequestId = `req-cancel-queued-second-${Date.now()}`;
431
+ const queuedAckPromise = waitForMessage(requester, (msg)=>"ack" === msg.type && msg.id === secondRequestId && msg.payload?.action === "req:agent" && msg.payload?.status === "queued", 10000);
432
+ requester.send(JSON.stringify({
433
+ type: "req:agent",
434
+ id: firstRequestId,
435
+ payload: {
436
+ agentId: "main",
437
+ sessionKey: sessionId,
438
+ content: "First request"
439
+ },
440
+ timestamp: Date.now()
441
+ }));
442
+ requester.send(JSON.stringify({
443
+ type: "req:agent",
444
+ id: secondRequestId,
445
+ payload: {
446
+ agentId: "main",
447
+ sessionKey: sessionId,
448
+ content: "Second request"
449
+ },
450
+ timestamp: Date.now()
451
+ }));
452
+ await queuedAckPromise;
453
+ requester.send(JSON.stringify({
454
+ type: "req:agent:cancel",
455
+ id: `cancel-${secondRequestId}`,
456
+ payload: {
457
+ requestId: secondRequestId
458
+ },
459
+ timestamp: Date.now()
460
+ }));
461
+ const cancelAck = await waitForMessage(requester, (msg)=>"ack" === msg.type && msg.payload?.action === "req:agent:cancel" && msg.payload?.requestId === secondRequestId && msg.payload?.status === "cancelled_queued", 10000);
462
+ (0, external_vitest_namespaceObject.expect)(cancelAck.payload?.status).toBe("cancelled_queued");
463
+ await waitForMessage(requester, (msg)=>"event:agent" === msg.type && msg.id === firstRequestId && msg.payload?.type === "agent-complete", 10000);
464
+ const queuedRequests = server.queuedSessionRequests;
465
+ const isStillQueued = [
466
+ ...queuedRequests.values()
467
+ ].some((queue)=>queue.some((item)=>item.msg?.id === secondRequestId));
468
+ (0, external_vitest_namespaceObject.expect)(isStillQueued).toBe(false);
469
+ requester.close();
470
+ });
299
471
  (0, external_vitest_namespaceObject.it)("should clear session messages via API", async ()=>{
300
472
  const createRes = await fetch(`http://localhost:${port}/api/sessions`, {
301
473
  method: "POST",