@wingman-ai/gateway 0.2.2 → 0.2.4

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 (160) hide show
  1. package/.wingman/agents/README.md +7 -1
  2. package/.wingman/agents/coding/agent.md +299 -201
  3. package/.wingman/agents/coding-v2/agent.md +127 -0
  4. package/.wingman/agents/coding-v2/implementor.md +89 -0
  5. package/.wingman/agents/main/agent.md +4 -0
  6. package/README.md +1 -0
  7. package/dist/agent/config/agentConfig.cjs +31 -17
  8. package/dist/agent/config/agentConfig.d.ts +23 -1
  9. package/dist/agent/config/agentConfig.js +30 -19
  10. package/dist/agent/config/agentLoader.cjs +26 -8
  11. package/dist/agent/config/agentLoader.d.ts +4 -2
  12. package/dist/agent/config/agentLoader.js +26 -8
  13. package/dist/agent/config/modelFactory.cjs +95 -25
  14. package/dist/agent/config/modelFactory.d.ts +13 -1
  15. package/dist/agent/config/modelFactory.js +95 -25
  16. package/dist/agent/config/toolRegistry.cjs +19 -6
  17. package/dist/agent/config/toolRegistry.d.ts +5 -2
  18. package/dist/agent/config/toolRegistry.js +19 -6
  19. package/dist/agent/middleware/hooks/types.cjs +13 -13
  20. package/dist/agent/middleware/hooks/types.d.ts +1 -1
  21. package/dist/agent/middleware/hooks/types.js +14 -14
  22. package/dist/agent/tests/agentConfig.test.cjs +22 -2
  23. package/dist/agent/tests/agentConfig.test.js +22 -2
  24. package/dist/agent/tests/agentLoader.test.cjs +38 -1
  25. package/dist/agent/tests/agentLoader.test.js +38 -1
  26. package/dist/agent/tests/backgroundTerminal.test.cjs +70 -0
  27. package/dist/agent/tests/backgroundTerminal.test.d.ts +1 -0
  28. package/dist/agent/tests/backgroundTerminal.test.js +64 -0
  29. package/dist/agent/tests/commandExecuteTool.test.cjs +29 -0
  30. package/dist/agent/tests/commandExecuteTool.test.d.ts +1 -0
  31. package/dist/agent/tests/commandExecuteTool.test.js +23 -0
  32. package/dist/agent/tests/modelFactory.test.cjs +47 -5
  33. package/dist/agent/tests/modelFactory.test.js +47 -5
  34. package/dist/agent/tests/terminalSessionManager.test.cjs +121 -0
  35. package/dist/agent/tests/terminalSessionManager.test.d.ts +1 -0
  36. package/dist/agent/tests/terminalSessionManager.test.js +115 -0
  37. package/dist/agent/tests/toolRegistry.test.cjs +14 -2
  38. package/dist/agent/tests/toolRegistry.test.js +14 -2
  39. package/dist/agent/tools/background_terminal.cjs +128 -0
  40. package/dist/agent/tools/background_terminal.d.ts +41 -0
  41. package/dist/agent/tools/background_terminal.js +94 -0
  42. package/dist/agent/tools/code_search.cjs +6 -6
  43. package/dist/agent/tools/code_search.d.ts +1 -1
  44. package/dist/agent/tools/code_search.js +7 -7
  45. package/dist/agent/tools/command_execute.cjs +22 -7
  46. package/dist/agent/tools/command_execute.d.ts +3 -2
  47. package/dist/agent/tools/command_execute.js +23 -8
  48. package/dist/agent/tools/git_status.cjs +3 -3
  49. package/dist/agent/tools/git_status.d.ts +1 -1
  50. package/dist/agent/tools/git_status.js +4 -4
  51. package/dist/agent/tools/internet_search.cjs +6 -6
  52. package/dist/agent/tools/internet_search.d.ts +1 -1
  53. package/dist/agent/tools/internet_search.js +7 -7
  54. package/dist/agent/tools/terminal_session_manager.cjs +321 -0
  55. package/dist/agent/tools/terminal_session_manager.d.ts +77 -0
  56. package/dist/agent/tools/terminal_session_manager.js +284 -0
  57. package/dist/agent/tools/think.cjs +4 -4
  58. package/dist/agent/tools/think.d.ts +1 -1
  59. package/dist/agent/tools/think.js +5 -5
  60. package/dist/agent/tools/ui_registry.cjs +13 -13
  61. package/dist/agent/tools/ui_registry.d.ts +4 -4
  62. package/dist/agent/tools/ui_registry.js +14 -14
  63. package/dist/agent/tools/web_crawler.cjs +4 -4
  64. package/dist/agent/tools/web_crawler.d.ts +1 -1
  65. package/dist/agent/tools/web_crawler.js +5 -5
  66. package/dist/agent/utils.cjs +2 -1
  67. package/dist/agent/utils.js +2 -1
  68. package/dist/cli/commands/init.cjs +7 -6
  69. package/dist/cli/commands/init.js +7 -6
  70. package/dist/cli/commands/provider.cjs +17 -3
  71. package/dist/cli/commands/provider.js +17 -3
  72. package/dist/cli/config/loader.cjs +27 -0
  73. package/dist/cli/config/loader.js +27 -0
  74. package/dist/cli/config/schema.cjs +146 -68
  75. package/dist/cli/config/schema.d.ts +89 -1
  76. package/dist/cli/config/schema.js +134 -68
  77. package/dist/cli/core/agentInvoker.cjs +344 -17
  78. package/dist/cli/core/agentInvoker.d.ts +63 -3
  79. package/dist/cli/core/agentInvoker.js +303 -12
  80. package/dist/cli/core/sessionManager.cjs +32 -5
  81. package/dist/cli/core/sessionManager.js +32 -5
  82. package/dist/cli/core/streamParser.cjs +15 -0
  83. package/dist/cli/core/streamParser.js +15 -0
  84. package/dist/cli/index.cjs +6 -5
  85. package/dist/cli/index.js +6 -5
  86. package/dist/cli/types.d.ts +32 -0
  87. package/dist/cli/ui/toolDisplayHelpers.cjs +2 -0
  88. package/dist/cli/ui/toolDisplayHelpers.js +2 -0
  89. package/dist/gateway/hooks/registry.cjs +2 -1
  90. package/dist/gateway/hooks/registry.d.ts +1 -1
  91. package/dist/gateway/hooks/registry.js +2 -1
  92. package/dist/gateway/hooks/types.cjs +11 -11
  93. package/dist/gateway/hooks/types.d.ts +1 -1
  94. package/dist/gateway/hooks/types.js +12 -12
  95. package/dist/gateway/http/agents.cjs +67 -4
  96. package/dist/gateway/http/agents.js +67 -4
  97. package/dist/gateway/http/sessions.cjs +7 -7
  98. package/dist/gateway/http/sessions.js +7 -7
  99. package/dist/gateway/http/types.d.ts +5 -3
  100. package/dist/gateway/http/webhooks.cjs +6 -5
  101. package/dist/gateway/http/webhooks.js +6 -5
  102. package/dist/gateway/server.cjs +198 -41
  103. package/dist/gateway/server.d.ts +9 -1
  104. package/dist/gateway/server.js +198 -41
  105. package/dist/gateway/types.d.ts +1 -0
  106. package/dist/gateway/validation.cjs +39 -39
  107. package/dist/gateway/validation.d.ts +1 -1
  108. package/dist/gateway/validation.js +40 -40
  109. package/dist/providers/codex.cjs +167 -0
  110. package/dist/providers/codex.d.ts +15 -0
  111. package/dist/providers/codex.js +127 -0
  112. package/dist/providers/credentials.cjs +8 -0
  113. package/dist/providers/credentials.js +8 -0
  114. package/dist/providers/registry.cjs +11 -0
  115. package/dist/providers/registry.d.ts +1 -1
  116. package/dist/providers/registry.js +11 -0
  117. package/dist/tests/additionalMessageMiddleware.test.cjs +3 -0
  118. package/dist/tests/additionalMessageMiddleware.test.js +3 -0
  119. package/dist/tests/agentInvokerSummarization.test.cjs +455 -0
  120. package/dist/tests/agentInvokerSummarization.test.d.ts +1 -0
  121. package/dist/tests/agentInvokerSummarization.test.js +449 -0
  122. package/dist/tests/agents-api.test.cjs +45 -5
  123. package/dist/tests/agents-api.test.js +45 -5
  124. package/dist/tests/cli-config-loader.test.cjs +88 -0
  125. package/dist/tests/cli-config-loader.test.js +88 -0
  126. package/dist/tests/cli-init.test.cjs +27 -3
  127. package/dist/tests/cli-init.test.js +27 -3
  128. package/dist/tests/codex-credentials-precedence.test.cjs +94 -0
  129. package/dist/tests/codex-credentials-precedence.test.d.ts +1 -0
  130. package/dist/tests/codex-credentials-precedence.test.js +88 -0
  131. package/dist/tests/codex-provider.test.cjs +210 -0
  132. package/dist/tests/codex-provider.test.d.ts +1 -0
  133. package/dist/tests/codex-provider.test.js +204 -0
  134. package/dist/tests/gateway.test.cjs +115 -8
  135. package/dist/tests/gateway.test.js +115 -8
  136. package/dist/tests/provider-command-codex.test.cjs +57 -0
  137. package/dist/tests/provider-command-codex.test.d.ts +1 -0
  138. package/dist/tests/provider-command-codex.test.js +51 -0
  139. package/dist/tests/sessionStateMessages.test.cjs +38 -0
  140. package/dist/tests/sessionStateMessages.test.js +38 -0
  141. package/dist/tests/toolDisplayHelpers.test.cjs +3 -0
  142. package/dist/tests/toolDisplayHelpers.test.js +3 -0
  143. package/dist/tools/mcp-finance.cjs +48 -48
  144. package/dist/tools/mcp-finance.js +48 -48
  145. package/dist/types/mcp.cjs +15 -15
  146. package/dist/types/mcp.d.ts +1 -1
  147. package/dist/types/mcp.js +16 -16
  148. package/dist/types/voice.cjs +21 -21
  149. package/dist/types/voice.d.ts +1 -1
  150. package/dist/types/voice.js +22 -22
  151. package/dist/webui/assets/index-DVWQluit.css +11 -0
  152. package/dist/webui/assets/index-Dlyzwalc.js +270 -0
  153. package/dist/webui/favicon-32x32.png +0 -0
  154. package/dist/webui/favicon-64x64.png +0 -0
  155. package/dist/webui/favicon.webp +0 -0
  156. package/dist/webui/index.html +4 -2
  157. package/package.json +13 -12
  158. package/.wingman/agents/coding/implementor.md +0 -79
  159. package/dist/webui/assets/index-CPhfGPHc.js +0 -182
  160. package/dist/webui/assets/index-DDsMIOTX.css +0 -11
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  var __webpack_exports__ = {};
3
- const external_vitest_namespaceObject = require("vitest");
4
- const modelFactory_cjs_namespaceObject = require("../config/modelFactory.cjs");
5
3
  const anthropic_namespaceObject = require("@langchain/anthropic");
6
4
  const openai_namespaceObject = require("@langchain/openai");
5
+ const external_vitest_namespaceObject = require("vitest");
6
+ const modelFactory_cjs_namespaceObject = require("../config/modelFactory.cjs");
7
7
  const originalAnthropicApiKey = process.env.ANTHROPIC_API_KEY;
8
8
  (0, external_vitest_namespaceObject.beforeEach)(()=>{
9
9
  process.env.ANTHROPIC_API_KEY = "test-anthropic-api-key";
@@ -22,15 +22,54 @@ const originalAnthropicApiKey = process.env.ANTHROPIC_API_KEY;
22
22
  const model = modelFactory_cjs_namespaceObject.ModelFactory.createModel("openai:gpt-4o");
23
23
  (0, external_vitest_namespaceObject.expect)(model).toBeInstanceOf(openai_namespaceObject.ChatOpenAI);
24
24
  });
25
+ (0, external_vitest_namespaceObject.it)("should create a Codex model", ()=>{
26
+ const model = modelFactory_cjs_namespaceObject.ModelFactory.createModel("codex:codex-mini-latest");
27
+ (0, external_vitest_namespaceObject.expect)(model).toBeInstanceOf(openai_namespaceObject.ChatOpenAI);
28
+ (0, external_vitest_namespaceObject.expect)(model.useResponsesApi).toBe(true);
29
+ (0, external_vitest_namespaceObject.expect)(model.zdrEnabled).toBe(true);
30
+ });
25
31
  (0, external_vitest_namespaceObject.it)("should force OpenAI models onto the responses API", ()=>{
26
32
  const model = modelFactory_cjs_namespaceObject.ModelFactory.createModel("openai:gpt-5.2-codex");
27
33
  (0, external_vitest_namespaceObject.expect)(model).toBeInstanceOf(openai_namespaceObject.ChatOpenAI);
28
34
  (0, external_vitest_namespaceObject.expect)(model.useResponsesApi).toBe(true);
29
35
  });
36
+ (0, external_vitest_namespaceObject.it)("should apply reasoning effort for supported OpenAI reasoning models", ()=>{
37
+ const model = modelFactory_cjs_namespaceObject.ModelFactory.createModel("openai:gpt-5.2-codex", {
38
+ reasoningEffort: "high"
39
+ });
40
+ (0, external_vitest_namespaceObject.expect)(model).toBeInstanceOf(openai_namespaceObject.ChatOpenAI);
41
+ (0, external_vitest_namespaceObject.expect)(model.reasoning).toEqual({
42
+ effort: "high"
43
+ });
44
+ });
45
+ (0, external_vitest_namespaceObject.it)("should ignore reasoning effort for unsupported OpenAI models", ()=>{
46
+ const model = modelFactory_cjs_namespaceObject.ModelFactory.createModel("openai:gpt-4o", {
47
+ reasoningEffort: "medium"
48
+ });
49
+ (0, external_vitest_namespaceObject.expect)(model).toBeInstanceOf(openai_namespaceObject.ChatOpenAI);
50
+ (0, external_vitest_namespaceObject.expect)(model.reasoning).toBeUndefined();
51
+ });
52
+ (0, external_vitest_namespaceObject.it)("should map reasoning effort to anthropic thinking budget", ()=>{
53
+ const model = modelFactory_cjs_namespaceObject.ModelFactory.createModel("anthropic:claude-sonnet-4-5", {
54
+ reasoningEffort: "medium"
55
+ });
56
+ (0, external_vitest_namespaceObject.expect)(model).toBeInstanceOf(anthropic_namespaceObject.ChatAnthropic);
57
+ (0, external_vitest_namespaceObject.expect)(model.thinking).toEqual({
58
+ type: "enabled",
59
+ budget_tokens: 4096
60
+ });
61
+ });
30
62
  (0, external_vitest_namespaceObject.it)("should create an OpenRouter model", ()=>{
31
63
  const model = modelFactory_cjs_namespaceObject.ModelFactory.createModel("openrouter:openai/gpt-4o");
32
64
  (0, external_vitest_namespaceObject.expect)(model).toBeInstanceOf(openai_namespaceObject.ChatOpenAI);
33
65
  });
66
+ (0, external_vitest_namespaceObject.it)("should ignore reasoning effort for unsupported providers", ()=>{
67
+ const model = modelFactory_cjs_namespaceObject.ModelFactory.createModel("openrouter:openai/gpt-5", {
68
+ reasoningEffort: "high"
69
+ });
70
+ (0, external_vitest_namespaceObject.expect)(model).toBeInstanceOf(openai_namespaceObject.ChatOpenAI);
71
+ (0, external_vitest_namespaceObject.expect)(model.reasoning).toBeUndefined();
72
+ });
34
73
  (0, external_vitest_namespaceObject.it)("should create a Copilot model", ()=>{
35
74
  const model = modelFactory_cjs_namespaceObject.ModelFactory.createModel("copilot:gpt-4o");
36
75
  (0, external_vitest_namespaceObject.expect)(model).toBeInstanceOf(openai_namespaceObject.ChatOpenAI);
@@ -79,6 +118,7 @@ const originalAnthropicApiKey = process.env.ANTHROPIC_API_KEY;
79
118
  const validModels = [
80
119
  "anthropic:claude-opus-4-5",
81
120
  "openai:gpt-4o",
121
+ "codex:codex-mini-latest",
82
122
  "anthropic:claude-sonnet-4-5-20250929",
83
123
  "openrouter:openai/gpt-4o",
84
124
  "copilot:gpt-4o",
@@ -115,14 +155,16 @@ const originalAnthropicApiKey = process.env.ANTHROPIC_API_KEY;
115
155
  (0, external_vitest_namespaceObject.it)("should accept case variations of providers", ()=>{
116
156
  const result1 = modelFactory_cjs_namespaceObject.ModelFactory.validateModelString("Anthropic:model");
117
157
  const result2 = modelFactory_cjs_namespaceObject.ModelFactory.validateModelString("OPENAI:model");
118
- const result3 = modelFactory_cjs_namespaceObject.ModelFactory.validateModelString("Copilot:model");
119
- const result4 = modelFactory_cjs_namespaceObject.ModelFactory.validateModelString("LMStudio:model");
120
- const result5 = modelFactory_cjs_namespaceObject.ModelFactory.validateModelString("OLLAMA:model");
158
+ const result3 = modelFactory_cjs_namespaceObject.ModelFactory.validateModelString("CODEX:model");
159
+ const result4 = modelFactory_cjs_namespaceObject.ModelFactory.validateModelString("Copilot:model");
160
+ const result5 = modelFactory_cjs_namespaceObject.ModelFactory.validateModelString("LMStudio:model");
161
+ const result6 = modelFactory_cjs_namespaceObject.ModelFactory.validateModelString("OLLAMA:model");
121
162
  (0, external_vitest_namespaceObject.expect)(result1.valid).toBe(true);
122
163
  (0, external_vitest_namespaceObject.expect)(result2.valid).toBe(true);
123
164
  (0, external_vitest_namespaceObject.expect)(result3.valid).toBe(true);
124
165
  (0, external_vitest_namespaceObject.expect)(result4.valid).toBe(true);
125
166
  (0, external_vitest_namespaceObject.expect)(result5.valid).toBe(true);
167
+ (0, external_vitest_namespaceObject.expect)(result6.valid).toBe(true);
126
168
  });
127
169
  });
128
170
  });
@@ -1,7 +1,7 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
- import { ModelFactory } from "../config/modelFactory.js";
3
1
  import { ChatAnthropic } from "@langchain/anthropic";
4
2
  import { ChatOpenAI } from "@langchain/openai";
3
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
4
+ import { ModelFactory } from "../config/modelFactory.js";
5
5
  const originalAnthropicApiKey = process.env.ANTHROPIC_API_KEY;
6
6
  beforeEach(()=>{
7
7
  process.env.ANTHROPIC_API_KEY = "test-anthropic-api-key";
@@ -20,15 +20,54 @@ describe("ModelFactory", ()=>{
20
20
  const model = ModelFactory.createModel("openai:gpt-4o");
21
21
  expect(model).toBeInstanceOf(ChatOpenAI);
22
22
  });
23
+ it("should create a Codex model", ()=>{
24
+ const model = ModelFactory.createModel("codex:codex-mini-latest");
25
+ expect(model).toBeInstanceOf(ChatOpenAI);
26
+ expect(model.useResponsesApi).toBe(true);
27
+ expect(model.zdrEnabled).toBe(true);
28
+ });
23
29
  it("should force OpenAI models onto the responses API", ()=>{
24
30
  const model = ModelFactory.createModel("openai:gpt-5.2-codex");
25
31
  expect(model).toBeInstanceOf(ChatOpenAI);
26
32
  expect(model.useResponsesApi).toBe(true);
27
33
  });
34
+ it("should apply reasoning effort for supported OpenAI reasoning models", ()=>{
35
+ const model = ModelFactory.createModel("openai:gpt-5.2-codex", {
36
+ reasoningEffort: "high"
37
+ });
38
+ expect(model).toBeInstanceOf(ChatOpenAI);
39
+ expect(model.reasoning).toEqual({
40
+ effort: "high"
41
+ });
42
+ });
43
+ it("should ignore reasoning effort for unsupported OpenAI models", ()=>{
44
+ const model = ModelFactory.createModel("openai:gpt-4o", {
45
+ reasoningEffort: "medium"
46
+ });
47
+ expect(model).toBeInstanceOf(ChatOpenAI);
48
+ expect(model.reasoning).toBeUndefined();
49
+ });
50
+ it("should map reasoning effort to anthropic thinking budget", ()=>{
51
+ const model = ModelFactory.createModel("anthropic:claude-sonnet-4-5", {
52
+ reasoningEffort: "medium"
53
+ });
54
+ expect(model).toBeInstanceOf(ChatAnthropic);
55
+ expect(model.thinking).toEqual({
56
+ type: "enabled",
57
+ budget_tokens: 4096
58
+ });
59
+ });
28
60
  it("should create an OpenRouter model", ()=>{
29
61
  const model = ModelFactory.createModel("openrouter:openai/gpt-4o");
30
62
  expect(model).toBeInstanceOf(ChatOpenAI);
31
63
  });
64
+ it("should ignore reasoning effort for unsupported providers", ()=>{
65
+ const model = ModelFactory.createModel("openrouter:openai/gpt-5", {
66
+ reasoningEffort: "high"
67
+ });
68
+ expect(model).toBeInstanceOf(ChatOpenAI);
69
+ expect(model.reasoning).toBeUndefined();
70
+ });
32
71
  it("should create a Copilot model", ()=>{
33
72
  const model = ModelFactory.createModel("copilot:gpt-4o");
34
73
  expect(model).toBeInstanceOf(ChatOpenAI);
@@ -77,6 +116,7 @@ describe("ModelFactory", ()=>{
77
116
  const validModels = [
78
117
  "anthropic:claude-opus-4-5",
79
118
  "openai:gpt-4o",
119
+ "codex:codex-mini-latest",
80
120
  "anthropic:claude-sonnet-4-5-20250929",
81
121
  "openrouter:openai/gpt-4o",
82
122
  "copilot:gpt-4o",
@@ -113,14 +153,16 @@ describe("ModelFactory", ()=>{
113
153
  it("should accept case variations of providers", ()=>{
114
154
  const result1 = ModelFactory.validateModelString("Anthropic:model");
115
155
  const result2 = ModelFactory.validateModelString("OPENAI:model");
116
- const result3 = ModelFactory.validateModelString("Copilot:model");
117
- const result4 = ModelFactory.validateModelString("LMStudio:model");
118
- const result5 = ModelFactory.validateModelString("OLLAMA:model");
156
+ const result3 = ModelFactory.validateModelString("CODEX:model");
157
+ const result4 = ModelFactory.validateModelString("Copilot:model");
158
+ const result5 = ModelFactory.validateModelString("LMStudio:model");
159
+ const result6 = ModelFactory.validateModelString("OLLAMA:model");
119
160
  expect(result1.valid).toBe(true);
120
161
  expect(result2.valid).toBe(true);
121
162
  expect(result3.valid).toBe(true);
122
163
  expect(result4.valid).toBe(true);
123
164
  expect(result5.valid).toBe(true);
165
+ expect(result6.valid).toBe(true);
124
166
  });
125
167
  });
126
168
  });
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __webpack_exports__ = {};
3
+ const external_vitest_namespaceObject = require("vitest");
4
+ const terminal_session_manager_cjs_namespaceObject = require("../tools/terminal_session_manager.cjs");
5
+ async function waitForTerminalCompletion(manager, ownerId, sessionId, timeoutMs = 5000) {
6
+ const started = Date.now();
7
+ let lastOutput = "";
8
+ while(Date.now() - started < timeoutMs){
9
+ const poll = await manager.pollSession({
10
+ ownerId,
11
+ sessionId,
12
+ waitMs: 200,
13
+ maxOutputChars: 2000
14
+ });
15
+ lastOutput += poll.output;
16
+ if ("running" !== poll.status) return {
17
+ ...poll,
18
+ output: lastOutput
19
+ };
20
+ }
21
+ throw new Error(`Timed out waiting for terminal session ${sessionId}`);
22
+ }
23
+ (0, external_vitest_namespaceObject.describe)("TerminalSessionManager", ()=>{
24
+ (0, external_vitest_namespaceObject.it)("runs commands and returns output", async ()=>{
25
+ const manager = new terminal_session_manager_cjs_namespaceObject.TerminalSessionManager();
26
+ try {
27
+ const session = manager.startSession({
28
+ ownerId: "owner-a",
29
+ command: "node -e \"process.stdout.write('hello\\n')\"",
30
+ cwd: process.cwd(),
31
+ env: process.env
32
+ });
33
+ const finalState = await waitForTerminalCompletion(manager, "owner-a", session.sessionId);
34
+ (0, external_vitest_namespaceObject.expect)(finalState.output).toContain("hello");
35
+ (0, external_vitest_namespaceObject.expect)([
36
+ "completed",
37
+ "error",
38
+ "killed",
39
+ "timed_out"
40
+ ]).toContain(finalState.status);
41
+ } finally{
42
+ manager.dispose();
43
+ }
44
+ });
45
+ (0, external_vitest_namespaceObject.it)("enforces session ownership", async ()=>{
46
+ const manager = new terminal_session_manager_cjs_namespaceObject.TerminalSessionManager();
47
+ try {
48
+ const session = manager.startSession({
49
+ ownerId: "owner-allowed",
50
+ command: 'node -e "setTimeout(() => {}, 500)"',
51
+ cwd: process.cwd(),
52
+ env: process.env
53
+ });
54
+ await (0, external_vitest_namespaceObject.expect)(manager.pollSession({
55
+ ownerId: "owner-denied",
56
+ sessionId: session.sessionId,
57
+ waitMs: 0
58
+ })).rejects.toThrow("not accessible");
59
+ } finally{
60
+ manager.dispose();
61
+ }
62
+ });
63
+ (0, external_vitest_namespaceObject.it)("supports stdin writes and kill", async ()=>{
64
+ const manager = new terminal_session_manager_cjs_namespaceObject.TerminalSessionManager();
65
+ try {
66
+ const session = manager.startSession({
67
+ ownerId: "owner-b",
68
+ command: "cat",
69
+ cwd: process.cwd(),
70
+ env: process.env
71
+ });
72
+ manager.writeSession({
73
+ ownerId: "owner-b",
74
+ sessionId: session.sessionId,
75
+ chars: "ping\n"
76
+ });
77
+ const poll = await manager.pollSession({
78
+ ownerId: "owner-b",
79
+ sessionId: session.sessionId,
80
+ waitMs: 1000,
81
+ maxOutputChars: 2000
82
+ });
83
+ (0, external_vitest_namespaceObject.expect)(poll.output).toContain("ping");
84
+ const killed = manager.killSession({
85
+ ownerId: "owner-b",
86
+ sessionId: session.sessionId,
87
+ signal: "SIGTERM"
88
+ });
89
+ (0, external_vitest_namespaceObject.expect)([
90
+ "running",
91
+ "killed",
92
+ "timed_out",
93
+ "error",
94
+ "completed"
95
+ ]).toContain(killed.status);
96
+ } finally{
97
+ manager.dispose();
98
+ }
99
+ });
100
+ (0, external_vitest_namespaceObject.it)("drops buffered output when max buffer is exceeded", async ()=>{
101
+ const manager = new terminal_session_manager_cjs_namespaceObject.TerminalSessionManager({
102
+ maxBufferedCharsPerSession: 32
103
+ });
104
+ try {
105
+ const session = manager.startSession({
106
+ ownerId: "owner-c",
107
+ command: "node -e \"process.stdout.write('abcdefghijklmnopqrstuvwxyz0123456789')\"",
108
+ cwd: process.cwd(),
109
+ env: process.env
110
+ });
111
+ const finalState = await waitForTerminalCompletion(manager, "owner-c", session.sessionId);
112
+ (0, external_vitest_namespaceObject.expect)(finalState.droppedChars).toBeGreaterThan(0);
113
+ } finally{
114
+ manager.dispose();
115
+ }
116
+ });
117
+ });
118
+ for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
119
+ Object.defineProperty(exports, '__esModule', {
120
+ value: true
121
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { TerminalSessionManager } from "../tools/terminal_session_manager.js";
3
+ async function waitForTerminalCompletion(manager, ownerId, sessionId, timeoutMs = 5000) {
4
+ const started = Date.now();
5
+ let lastOutput = "";
6
+ while(Date.now() - started < timeoutMs){
7
+ const poll = await manager.pollSession({
8
+ ownerId,
9
+ sessionId,
10
+ waitMs: 200,
11
+ maxOutputChars: 2000
12
+ });
13
+ lastOutput += poll.output;
14
+ if ("running" !== poll.status) return {
15
+ ...poll,
16
+ output: lastOutput
17
+ };
18
+ }
19
+ throw new Error(`Timed out waiting for terminal session ${sessionId}`);
20
+ }
21
+ describe("TerminalSessionManager", ()=>{
22
+ it("runs commands and returns output", async ()=>{
23
+ const manager = new TerminalSessionManager();
24
+ try {
25
+ const session = manager.startSession({
26
+ ownerId: "owner-a",
27
+ command: "node -e \"process.stdout.write('hello\\n')\"",
28
+ cwd: process.cwd(),
29
+ env: process.env
30
+ });
31
+ const finalState = await waitForTerminalCompletion(manager, "owner-a", session.sessionId);
32
+ expect(finalState.output).toContain("hello");
33
+ expect([
34
+ "completed",
35
+ "error",
36
+ "killed",
37
+ "timed_out"
38
+ ]).toContain(finalState.status);
39
+ } finally{
40
+ manager.dispose();
41
+ }
42
+ });
43
+ it("enforces session ownership", async ()=>{
44
+ const manager = new TerminalSessionManager();
45
+ try {
46
+ const session = manager.startSession({
47
+ ownerId: "owner-allowed",
48
+ command: 'node -e "setTimeout(() => {}, 500)"',
49
+ cwd: process.cwd(),
50
+ env: process.env
51
+ });
52
+ await expect(manager.pollSession({
53
+ ownerId: "owner-denied",
54
+ sessionId: session.sessionId,
55
+ waitMs: 0
56
+ })).rejects.toThrow("not accessible");
57
+ } finally{
58
+ manager.dispose();
59
+ }
60
+ });
61
+ it("supports stdin writes and kill", async ()=>{
62
+ const manager = new TerminalSessionManager();
63
+ try {
64
+ const session = manager.startSession({
65
+ ownerId: "owner-b",
66
+ command: "cat",
67
+ cwd: process.cwd(),
68
+ env: process.env
69
+ });
70
+ manager.writeSession({
71
+ ownerId: "owner-b",
72
+ sessionId: session.sessionId,
73
+ chars: "ping\n"
74
+ });
75
+ const poll = await manager.pollSession({
76
+ ownerId: "owner-b",
77
+ sessionId: session.sessionId,
78
+ waitMs: 1000,
79
+ maxOutputChars: 2000
80
+ });
81
+ expect(poll.output).toContain("ping");
82
+ const killed = manager.killSession({
83
+ ownerId: "owner-b",
84
+ sessionId: session.sessionId,
85
+ signal: "SIGTERM"
86
+ });
87
+ expect([
88
+ "running",
89
+ "killed",
90
+ "timed_out",
91
+ "error",
92
+ "completed"
93
+ ]).toContain(killed.status);
94
+ } finally{
95
+ manager.dispose();
96
+ }
97
+ });
98
+ it("drops buffered output when max buffer is exceeded", async ()=>{
99
+ const manager = new TerminalSessionManager({
100
+ maxBufferedCharsPerSession: 32
101
+ });
102
+ try {
103
+ const session = manager.startSession({
104
+ ownerId: "owner-c",
105
+ command: "node -e \"process.stdout.write('abcdefghijklmnopqrstuvwxyz0123456789')\"",
106
+ cwd: process.cwd(),
107
+ env: process.env
108
+ });
109
+ const finalState = await waitForTerminalCompletion(manager, "owner-c", session.sessionId);
110
+ expect(finalState.droppedChars).toBeGreaterThan(0);
111
+ } finally{
112
+ manager.dispose();
113
+ }
114
+ });
115
+ });
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  var __webpack_exports__ = {};
3
- const external_vitest_namespaceObject = require("vitest");
4
3
  const external_node_fs_namespaceObject = require("node:fs");
5
- const external_node_path_namespaceObject = require("node:path");
6
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
7
  const toolRegistry_cjs_namespaceObject = require("../config/toolRegistry.cjs");
8
+ const terminal_session_manager_cjs_namespaceObject = require("../tools/terminal_session_manager.cjs");
8
9
  (0, external_vitest_namespaceObject.describe)("Tool Registry", ()=>{
9
10
  (0, external_vitest_namespaceObject.describe)("createTool", ()=>{
10
11
  (0, external_vitest_namespaceObject.it)("should create internet_search tool", ()=>{
@@ -35,6 +36,15 @@ const toolRegistry_cjs_namespaceObject = require("../config/toolRegistry.cjs");
35
36
  (0, external_vitest_namespaceObject.expect)(tool).not.toBeNull();
36
37
  (0, external_vitest_namespaceObject.expect)(tool?.name).toBe("command_execute");
37
38
  });
39
+ (0, external_vitest_namespaceObject.it)("should create terminal tools", ()=>{
40
+ const manager = new terminal_session_manager_cjs_namespaceObject.TerminalSessionManager();
41
+ const terminalTool = (0, toolRegistry_cjs_namespaceObject.createTool)("background_terminal", {
42
+ terminalOwnerId: "owner-1",
43
+ terminalSessionManager: manager
44
+ });
45
+ (0, external_vitest_namespaceObject.expect)(terminalTool?.name).toBe("background_terminal");
46
+ manager.dispose();
47
+ });
38
48
  (0, external_vitest_namespaceObject.it)("should execute command tools in executionWorkspace when provided", async ()=>{
39
49
  const executionWorkspace = (0, external_node_fs_namespaceObject.mkdtempSync)((0, external_node_path_namespaceObject.join)((0, external_node_os_namespaceObject.tmpdir)(), "wingman-tool-workspace-"));
40
50
  const tool = (0, toolRegistry_cjs_namespaceObject.createTool)("command_execute", {
@@ -42,6 +52,7 @@ const toolRegistry_cjs_namespaceObject = require("../config/toolRegistry.cjs");
42
52
  executionWorkspace
43
53
  });
44
54
  (0, external_vitest_namespaceObject.expect)(tool).not.toBeNull();
55
+ if (!tool) throw new Error("Expected command_execute tool to be created");
45
56
  const result = await tool.invoke({
46
57
  command: 'node -e "process.stdout.write(process.cwd())"'
47
58
  });
@@ -106,6 +117,7 @@ const toolRegistry_cjs_namespaceObject = require("../config/toolRegistry.cjs");
106
117
  "internet_search",
107
118
  "web_crawler",
108
119
  "command_execute",
120
+ "background_terminal",
109
121
  "think",
110
122
  "code_search",
111
123
  "git_status",
@@ -1,8 +1,9 @@
1
- import { describe, expect, it } from "vitest";
2
1
  import { mkdtempSync } from "node:fs";
3
- import { join } from "node:path";
4
2
  import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
5
  import { createTool, createTools, getAvailableTools } from "../config/toolRegistry.js";
6
+ import { TerminalSessionManager } from "../tools/terminal_session_manager.js";
6
7
  describe("Tool Registry", ()=>{
7
8
  describe("createTool", ()=>{
8
9
  it("should create internet_search tool", ()=>{
@@ -33,6 +34,15 @@ describe("Tool Registry", ()=>{
33
34
  expect(tool).not.toBeNull();
34
35
  expect(tool?.name).toBe("command_execute");
35
36
  });
37
+ it("should create terminal tools", ()=>{
38
+ const manager = new TerminalSessionManager();
39
+ const terminalTool = createTool("background_terminal", {
40
+ terminalOwnerId: "owner-1",
41
+ terminalSessionManager: manager
42
+ });
43
+ expect(terminalTool?.name).toBe("background_terminal");
44
+ manager.dispose();
45
+ });
36
46
  it("should execute command tools in executionWorkspace when provided", async ()=>{
37
47
  const executionWorkspace = mkdtempSync(join(tmpdir(), "wingman-tool-workspace-"));
38
48
  const tool = createTool("command_execute", {
@@ -40,6 +50,7 @@ describe("Tool Registry", ()=>{
40
50
  executionWorkspace
41
51
  });
42
52
  expect(tool).not.toBeNull();
53
+ if (!tool) throw new Error("Expected command_execute tool to be created");
43
54
  const result = await tool.invoke({
44
55
  command: 'node -e "process.stdout.write(process.cwd())"'
45
56
  });
@@ -104,6 +115,7 @@ describe("Tool Registry", ()=>{
104
115
  "internet_search",
105
116
  "web_crawler",
106
117
  "command_execute",
118
+ "background_terminal",
107
119
  "think",
108
120
  "code_search",
109
121
  "git_status",
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ createBackgroundTerminalTool: ()=>createBackgroundTerminalTool
28
+ });
29
+ const external_langchain_namespaceObject = require("langchain");
30
+ const external_zod_namespaceObject = require("zod");
31
+ const external_command_execute_cjs_namespaceObject = require("./command_execute.cjs");
32
+ const normalizeCommandParts = (command)=>{
33
+ const commandParts = command.trim().split(/\s+/);
34
+ const commandName = (commandParts[0] || "").toLowerCase();
35
+ return commandName.split(/[\\/]/).pop() || "";
36
+ };
37
+ const isScriptCommand = (command)=>{
38
+ const normalized = command.trim().toLowerCase();
39
+ return normalized.endsWith(".sh") || normalized.endsWith(".bash") || normalized.endsWith(".zsh") || normalized.endsWith(".ps1") || normalized.endsWith(".cmd") || normalized.endsWith(".bat");
40
+ };
41
+ const createSafeEnv = (envVariables)=>{
42
+ const { NODE_OPTIONS, NODE_DEBUG, VSCODE_INSPECTOR_OPTIONS, ...cleanEnv } = process.env;
43
+ return {
44
+ ...cleanEnv,
45
+ FORCE_COLOR: "0",
46
+ NO_COLOR: "1",
47
+ GIT_PAGER: "cat",
48
+ ...envVariables ?? {}
49
+ };
50
+ };
51
+ const buildTerminalResponse = (payload)=>payload;
52
+ const createBackgroundTerminalTool = (options)=>{
53
+ const { workspace, ownerId, sessionManager, envVariables, blockedCommands = external_command_execute_cjs_namespaceObject.DEFAULT_BLOCKED_COMMANDS, allowScriptExecution = true, commandTimeout = 300000 } = options;
54
+ return (0, external_langchain_namespaceObject.tool)(async ({ command, session_id, chars, wait_ms, max_output_chars })=>{
55
+ try {
56
+ if (command && session_id) return buildTerminalResponse({
57
+ error: "Provide either command or session_id, not both"
58
+ });
59
+ if (!command && !session_id) return buildTerminalResponse({
60
+ error: "Provide command to start a session or session_id to poll/write"
61
+ });
62
+ let resolvedSessionId = session_id;
63
+ if (command) {
64
+ const baseCommand = normalizeCommandParts(command);
65
+ if (blockedCommands.includes(baseCommand)) return buildTerminalResponse({
66
+ error: `Command "${command}" rejected by blockedCommands policy`
67
+ });
68
+ if (!allowScriptExecution && isScriptCommand(command)) return buildTerminalResponse({
69
+ error: `Command "${command}" rejected because script execution is disabled`
70
+ });
71
+ const session = sessionManager.startSession({
72
+ ownerId,
73
+ command,
74
+ cwd: workspace,
75
+ env: createSafeEnv(envVariables),
76
+ runtimeLimitMs: commandTimeout
77
+ });
78
+ resolvedSessionId = session.sessionId;
79
+ }
80
+ if (!resolvedSessionId) return buildTerminalResponse({
81
+ error: "session_id is required"
82
+ });
83
+ if (chars && chars.length > 0) sessionManager.writeSession({
84
+ ownerId,
85
+ sessionId: resolvedSessionId,
86
+ chars
87
+ });
88
+ const pollResult = await sessionManager.pollSession({
89
+ ownerId,
90
+ sessionId: resolvedSessionId,
91
+ waitMs: wait_ms,
92
+ maxOutputChars: max_output_chars
93
+ });
94
+ return buildTerminalResponse({
95
+ session_id: pollResult.sessionId,
96
+ status: pollResult.status,
97
+ output: pollResult.output,
98
+ has_more: pollResult.hasMore,
99
+ exit_code: pollResult.exitCode,
100
+ signal: pollResult.signal,
101
+ command: pollResult.command,
102
+ cwd: pollResult.cwd,
103
+ dropped_chars: pollResult.droppedChars
104
+ });
105
+ } catch (error) {
106
+ return buildTerminalResponse({
107
+ error: error instanceof Error ? error.message : String(error)
108
+ });
109
+ }
110
+ }, {
111
+ name: "background_terminal",
112
+ description: "Single background terminal interface for long-running sessions. Use this for commands that may not exit on their own (for example web servers, test watchers, or log tailing). Start a session with command, then poll or write using session_id. Use normal shell commands in-session (for example ps/jobs/kill) and control chars like \\u0003 to interrupt running programs.",
113
+ schema: external_zod_namespaceObject.object({
114
+ command: external_zod_namespaceObject.string().optional().describe("Command to start a new terminal session"),
115
+ session_id: external_zod_namespaceObject.string().optional().describe("Existing terminal session id for poll/write"),
116
+ chars: external_zod_namespaceObject.string().optional().default("").describe("Optional stdin text to write before polling"),
117
+ wait_ms: external_zod_namespaceObject.number().min(0).max(30000).optional().default(1000).describe("How long to wait for output before returning"),
118
+ max_output_chars: external_zod_namespaceObject.number().min(1).max(200000).optional().default(8000).describe("Maximum output characters to return")
119
+ })
120
+ });
121
+ };
122
+ exports.createBackgroundTerminalTool = __webpack_exports__.createBackgroundTerminalTool;
123
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
124
+ "createBackgroundTerminalTool"
125
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
126
+ Object.defineProperty(exports, '__esModule', {
127
+ value: true
128
+ });