@wingman-ai/gateway 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.wingman/agents/README.md +7 -1
- package/.wingman/agents/coding/agent.md +295 -202
- package/.wingman/agents/coding-v2/agent.md +127 -0
- package/.wingman/agents/coding-v2/implementor.md +89 -0
- package/dist/agent/config/agentConfig.cjs +31 -17
- package/dist/agent/config/agentConfig.d.ts +23 -1
- package/dist/agent/config/agentConfig.js +30 -19
- package/dist/agent/config/agentLoader.cjs +26 -8
- package/dist/agent/config/agentLoader.d.ts +4 -2
- package/dist/agent/config/agentLoader.js +26 -8
- package/dist/agent/config/modelFactory.cjs +77 -27
- package/dist/agent/config/modelFactory.d.ts +11 -1
- package/dist/agent/config/modelFactory.js +77 -27
- package/dist/agent/config/toolRegistry.cjs +19 -6
- package/dist/agent/config/toolRegistry.d.ts +5 -2
- package/dist/agent/config/toolRegistry.js +19 -6
- package/dist/agent/middleware/hooks/types.cjs +13 -13
- package/dist/agent/middleware/hooks/types.d.ts +1 -1
- package/dist/agent/middleware/hooks/types.js +14 -14
- package/dist/agent/tests/agentConfig.test.cjs +22 -2
- package/dist/agent/tests/agentConfig.test.js +22 -2
- package/dist/agent/tests/agentLoader.test.cjs +38 -1
- package/dist/agent/tests/agentLoader.test.js +38 -1
- package/dist/agent/tests/backgroundTerminal.test.cjs +70 -0
- package/dist/agent/tests/backgroundTerminal.test.d.ts +1 -0
- package/dist/agent/tests/backgroundTerminal.test.js +64 -0
- package/dist/agent/tests/commandExecuteTool.test.cjs +29 -0
- package/dist/agent/tests/commandExecuteTool.test.d.ts +1 -0
- package/dist/agent/tests/commandExecuteTool.test.js +23 -0
- package/dist/agent/tests/modelFactory.test.cjs +35 -0
- package/dist/agent/tests/modelFactory.test.js +35 -0
- package/dist/agent/tests/terminalSessionManager.test.cjs +121 -0
- package/dist/agent/tests/terminalSessionManager.test.d.ts +1 -0
- package/dist/agent/tests/terminalSessionManager.test.js +115 -0
- package/dist/agent/tests/toolRegistry.test.cjs +14 -2
- package/dist/agent/tests/toolRegistry.test.js +14 -2
- package/dist/agent/tools/background_terminal.cjs +128 -0
- package/dist/agent/tools/background_terminal.d.ts +41 -0
- package/dist/agent/tools/background_terminal.js +94 -0
- package/dist/agent/tools/code_search.cjs +6 -6
- package/dist/agent/tools/code_search.d.ts +1 -1
- package/dist/agent/tools/code_search.js +7 -7
- package/dist/agent/tools/command_execute.cjs +22 -7
- package/dist/agent/tools/command_execute.d.ts +3 -2
- package/dist/agent/tools/command_execute.js +23 -8
- package/dist/agent/tools/git_status.cjs +3 -3
- package/dist/agent/tools/git_status.d.ts +1 -1
- package/dist/agent/tools/git_status.js +4 -4
- package/dist/agent/tools/internet_search.cjs +6 -6
- package/dist/agent/tools/internet_search.d.ts +1 -1
- package/dist/agent/tools/internet_search.js +7 -7
- package/dist/agent/tools/terminal_session_manager.cjs +321 -0
- package/dist/agent/tools/terminal_session_manager.d.ts +77 -0
- package/dist/agent/tools/terminal_session_manager.js +284 -0
- package/dist/agent/tools/think.cjs +4 -4
- package/dist/agent/tools/think.d.ts +1 -1
- package/dist/agent/tools/think.js +5 -5
- package/dist/agent/tools/ui_registry.cjs +13 -13
- package/dist/agent/tools/ui_registry.d.ts +4 -4
- package/dist/agent/tools/ui_registry.js +14 -14
- package/dist/agent/tools/web_crawler.cjs +4 -4
- package/dist/agent/tools/web_crawler.d.ts +1 -1
- package/dist/agent/tools/web_crawler.js +5 -5
- package/dist/agent/utils.cjs +2 -1
- package/dist/agent/utils.js +2 -1
- package/dist/cli/config/schema.cjs +89 -89
- package/dist/cli/config/schema.d.ts +1 -1
- package/dist/cli/config/schema.js +90 -90
- package/dist/cli/core/agentInvoker.cjs +170 -21
- package/dist/cli/core/agentInvoker.d.ts +25 -4
- package/dist/cli/core/agentInvoker.js +157 -20
- package/dist/cli/core/streamParser.cjs +15 -0
- package/dist/cli/core/streamParser.js +15 -0
- package/dist/cli/ui/toolDisplayHelpers.cjs +2 -0
- package/dist/cli/ui/toolDisplayHelpers.js +2 -0
- package/dist/gateway/hooks/registry.cjs +2 -1
- package/dist/gateway/hooks/registry.d.ts +1 -1
- package/dist/gateway/hooks/registry.js +2 -1
- package/dist/gateway/hooks/types.cjs +11 -11
- package/dist/gateway/hooks/types.d.ts +1 -1
- package/dist/gateway/hooks/types.js +12 -12
- package/dist/gateway/http/agents.cjs +67 -4
- package/dist/gateway/http/agents.js +67 -4
- package/dist/gateway/http/types.d.ts +5 -3
- package/dist/gateway/http/webhooks.cjs +6 -5
- package/dist/gateway/http/webhooks.js +6 -5
- package/dist/gateway/server.cjs +7 -0
- package/dist/gateway/server.d.ts +1 -0
- package/dist/gateway/server.js +7 -0
- package/dist/gateway/validation.cjs +39 -39
- package/dist/gateway/validation.d.ts +1 -1
- package/dist/gateway/validation.js +40 -40
- package/dist/providers/codex.cjs +230 -37
- package/dist/providers/codex.d.ts +2 -0
- package/dist/providers/codex.js +231 -38
- package/dist/tests/additionalMessageMiddleware.test.cjs +3 -0
- package/dist/tests/additionalMessageMiddleware.test.js +3 -0
- package/dist/tests/agentInvokerSummarization.test.cjs +171 -12
- package/dist/tests/agentInvokerSummarization.test.js +172 -13
- package/dist/tests/agents-api.test.cjs +45 -5
- package/dist/tests/agents-api.test.js +45 -5
- package/dist/tests/cli-init.test.cjs +27 -3
- package/dist/tests/cli-init.test.js +27 -3
- package/dist/tests/codex-provider.test.cjs +197 -0
- package/dist/tests/codex-provider.test.js +198 -1
- package/dist/tests/gateway.test.cjs +7 -7
- package/dist/tests/gateway.test.js +7 -7
- package/dist/tests/toolDisplayHelpers.test.cjs +3 -0
- package/dist/tests/toolDisplayHelpers.test.js +3 -0
- package/dist/tools/mcp-finance.cjs +48 -48
- package/dist/tools/mcp-finance.js +48 -48
- package/dist/types/mcp.cjs +15 -15
- package/dist/types/mcp.d.ts +1 -1
- package/dist/types/mcp.js +16 -16
- package/dist/types/voice.cjs +21 -21
- package/dist/types/voice.d.ts +1 -1
- package/dist/types/voice.js +22 -22
- package/dist/webui/assets/index-C7EuTbnE.js +270 -0
- package/dist/webui/assets/index-DVWQluit.css +11 -0
- package/dist/webui/favicon-32x32.png +0 -0
- package/dist/webui/favicon-64x64.png +0 -0
- package/dist/webui/favicon.webp +0 -0
- package/dist/webui/index.html +4 -2
- package/package.json +13 -12
- package/.wingman/agents/coding/implementor.md +0 -103
- package/dist/webui/assets/index-BVMavpud.css +0 -11
- package/dist/webui/assets/index-DCB2aVVf.js +0 -182
|
@@ -23,16 +23,51 @@ describe("ModelFactory", ()=>{
|
|
|
23
23
|
it("should create a Codex model", ()=>{
|
|
24
24
|
const model = ModelFactory.createModel("codex:codex-mini-latest");
|
|
25
25
|
expect(model).toBeInstanceOf(ChatOpenAI);
|
|
26
|
+
expect(model.useResponsesApi).toBe(true);
|
|
27
|
+
expect(model.zdrEnabled).toBe(true);
|
|
26
28
|
});
|
|
27
29
|
it("should force OpenAI models onto the responses API", ()=>{
|
|
28
30
|
const model = ModelFactory.createModel("openai:gpt-5.2-codex");
|
|
29
31
|
expect(model).toBeInstanceOf(ChatOpenAI);
|
|
30
32
|
expect(model.useResponsesApi).toBe(true);
|
|
31
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
|
+
});
|
|
32
60
|
it("should create an OpenRouter model", ()=>{
|
|
33
61
|
const model = ModelFactory.createModel("openrouter:openai/gpt-4o");
|
|
34
62
|
expect(model).toBeInstanceOf(ChatOpenAI);
|
|
35
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
|
+
});
|
|
36
71
|
it("should create a Copilot model", ()=>{
|
|
37
72
|
const model = ModelFactory.createModel("copilot:gpt-4o");
|
|
38
73
|
expect(model).toBeInstanceOf(ChatOpenAI);
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import type { TerminalSessionManager, TerminalSessionStatus } from "./terminal_session_manager.js";
|
|
3
|
+
export interface BackgroundTerminalToolOptions {
|
|
4
|
+
workspace: string;
|
|
5
|
+
ownerId: string;
|
|
6
|
+
sessionManager: TerminalSessionManager;
|
|
7
|
+
envVariables?: Record<string, string>;
|
|
8
|
+
blockedCommands?: string[];
|
|
9
|
+
allowScriptExecution?: boolean;
|
|
10
|
+
commandTimeout?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const createBackgroundTerminalTool: (options: BackgroundTerminalToolOptions) => import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
13
|
+
command: z.ZodOptional<z.ZodString>;
|
|
14
|
+
session_id: z.ZodOptional<z.ZodString>;
|
|
15
|
+
chars: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
16
|
+
wait_ms: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
17
|
+
max_output_chars: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
18
|
+
}, z.core.$strip>, {
|
|
19
|
+
command?: string;
|
|
20
|
+
session_id?: string;
|
|
21
|
+
chars?: string;
|
|
22
|
+
wait_ms?: number;
|
|
23
|
+
max_output_chars?: number;
|
|
24
|
+
}, {
|
|
25
|
+
command?: string | undefined;
|
|
26
|
+
session_id?: string | undefined;
|
|
27
|
+
chars?: string | undefined;
|
|
28
|
+
wait_ms?: number | undefined;
|
|
29
|
+
max_output_chars?: number | undefined;
|
|
30
|
+
}, {
|
|
31
|
+
session_id?: string;
|
|
32
|
+
status?: TerminalSessionStatus;
|
|
33
|
+
output?: string;
|
|
34
|
+
has_more?: boolean;
|
|
35
|
+
exit_code?: number | null;
|
|
36
|
+
signal?: NodeJS.Signals | null;
|
|
37
|
+
command?: string;
|
|
38
|
+
cwd?: string;
|
|
39
|
+
dropped_chars?: number;
|
|
40
|
+
error?: string;
|
|
41
|
+
}, "background_terminal">;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { tool } from "langchain";
|
|
2
|
+
import { number, object, string } from "zod";
|
|
3
|
+
import { DEFAULT_BLOCKED_COMMANDS } from "./command_execute.js";
|
|
4
|
+
const normalizeCommandParts = (command)=>{
|
|
5
|
+
const commandParts = command.trim().split(/\s+/);
|
|
6
|
+
const commandName = (commandParts[0] || "").toLowerCase();
|
|
7
|
+
return commandName.split(/[\\/]/).pop() || "";
|
|
8
|
+
};
|
|
9
|
+
const isScriptCommand = (command)=>{
|
|
10
|
+
const normalized = command.trim().toLowerCase();
|
|
11
|
+
return normalized.endsWith(".sh") || normalized.endsWith(".bash") || normalized.endsWith(".zsh") || normalized.endsWith(".ps1") || normalized.endsWith(".cmd") || normalized.endsWith(".bat");
|
|
12
|
+
};
|
|
13
|
+
const createSafeEnv = (envVariables)=>{
|
|
14
|
+
const { NODE_OPTIONS, NODE_DEBUG, VSCODE_INSPECTOR_OPTIONS, ...cleanEnv } = process.env;
|
|
15
|
+
return {
|
|
16
|
+
...cleanEnv,
|
|
17
|
+
FORCE_COLOR: "0",
|
|
18
|
+
NO_COLOR: "1",
|
|
19
|
+
GIT_PAGER: "cat",
|
|
20
|
+
...envVariables ?? {}
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
const buildTerminalResponse = (payload)=>payload;
|
|
24
|
+
const createBackgroundTerminalTool = (options)=>{
|
|
25
|
+
const { workspace, ownerId, sessionManager, envVariables, blockedCommands = DEFAULT_BLOCKED_COMMANDS, allowScriptExecution = true, commandTimeout = 300000 } = options;
|
|
26
|
+
return tool(async ({ command, session_id, chars, wait_ms, max_output_chars })=>{
|
|
27
|
+
try {
|
|
28
|
+
if (command && session_id) return buildTerminalResponse({
|
|
29
|
+
error: "Provide either command or session_id, not both"
|
|
30
|
+
});
|
|
31
|
+
if (!command && !session_id) return buildTerminalResponse({
|
|
32
|
+
error: "Provide command to start a session or session_id to poll/write"
|
|
33
|
+
});
|
|
34
|
+
let resolvedSessionId = session_id;
|
|
35
|
+
if (command) {
|
|
36
|
+
const baseCommand = normalizeCommandParts(command);
|
|
37
|
+
if (blockedCommands.includes(baseCommand)) return buildTerminalResponse({
|
|
38
|
+
error: `Command "${command}" rejected by blockedCommands policy`
|
|
39
|
+
});
|
|
40
|
+
if (!allowScriptExecution && isScriptCommand(command)) return buildTerminalResponse({
|
|
41
|
+
error: `Command "${command}" rejected because script execution is disabled`
|
|
42
|
+
});
|
|
43
|
+
const session = sessionManager.startSession({
|
|
44
|
+
ownerId,
|
|
45
|
+
command,
|
|
46
|
+
cwd: workspace,
|
|
47
|
+
env: createSafeEnv(envVariables),
|
|
48
|
+
runtimeLimitMs: commandTimeout
|
|
49
|
+
});
|
|
50
|
+
resolvedSessionId = session.sessionId;
|
|
51
|
+
}
|
|
52
|
+
if (!resolvedSessionId) return buildTerminalResponse({
|
|
53
|
+
error: "session_id is required"
|
|
54
|
+
});
|
|
55
|
+
if (chars && chars.length > 0) sessionManager.writeSession({
|
|
56
|
+
ownerId,
|
|
57
|
+
sessionId: resolvedSessionId,
|
|
58
|
+
chars
|
|
59
|
+
});
|
|
60
|
+
const pollResult = await sessionManager.pollSession({
|
|
61
|
+
ownerId,
|
|
62
|
+
sessionId: resolvedSessionId,
|
|
63
|
+
waitMs: wait_ms,
|
|
64
|
+
maxOutputChars: max_output_chars
|
|
65
|
+
});
|
|
66
|
+
return buildTerminalResponse({
|
|
67
|
+
session_id: pollResult.sessionId,
|
|
68
|
+
status: pollResult.status,
|
|
69
|
+
output: pollResult.output,
|
|
70
|
+
has_more: pollResult.hasMore,
|
|
71
|
+
exit_code: pollResult.exitCode,
|
|
72
|
+
signal: pollResult.signal,
|
|
73
|
+
command: pollResult.command,
|
|
74
|
+
cwd: pollResult.cwd,
|
|
75
|
+
dropped_chars: pollResult.droppedChars
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return buildTerminalResponse({
|
|
79
|
+
error: error instanceof Error ? error.message : String(error)
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}, {
|
|
83
|
+
name: "background_terminal",
|
|
84
|
+
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.",
|
|
85
|
+
schema: object({
|
|
86
|
+
command: string().optional().describe("Command to start a new terminal session"),
|
|
87
|
+
session_id: string().optional().describe("Existing terminal session id for poll/write"),
|
|
88
|
+
chars: string().optional().default("").describe("Optional stdin text to write before polling"),
|
|
89
|
+
wait_ms: number().min(0).max(30000).optional().default(1000).describe("How long to wait for output before returning"),
|
|
90
|
+
max_output_chars: number().min(1).max(200000).optional().default(8000).describe("Maximum output characters to return")
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
export { createBackgroundTerminalTool };
|
|
@@ -90,12 +90,12 @@ const createCodeSearchTool = (workspace)=>{
|
|
|
90
90
|
}), {
|
|
91
91
|
name: "code_search",
|
|
92
92
|
description: "Search code patterns across the codebase using ripgrep (or grep as fallback). Fast and efficient for finding function definitions, variables, imports, or any text pattern. Returns file paths with line numbers and context.",
|
|
93
|
-
schema: external_zod_namespaceObject.
|
|
94
|
-
pattern: external_zod_namespaceObject.
|
|
95
|
-
path: external_zod_namespaceObject.
|
|
96
|
-
type: external_zod_namespaceObject.
|
|
97
|
-
context: external_zod_namespaceObject.
|
|
98
|
-
caseSensitive: external_zod_namespaceObject.
|
|
93
|
+
schema: external_zod_namespaceObject.object({
|
|
94
|
+
pattern: external_zod_namespaceObject.string().describe("The pattern to search for (regex or literal string). Examples: 'function.*processData', 'import.*React', 'TODO'"),
|
|
95
|
+
path: external_zod_namespaceObject.string().optional().describe("Optional: Directory or file to search in (e.g., 'src/', 'src/utils.ts'). Defaults to the active execution workspace."),
|
|
96
|
+
type: external_zod_namespaceObject.string().optional().describe("Optional: File type filter (e.g., 'ts', 'js', 'py', 'go'). Searches only files of this type."),
|
|
97
|
+
context: external_zod_namespaceObject.number().optional().default(2).describe("Optional: Number of context lines to show around matches. Default is 2."),
|
|
98
|
+
caseSensitive: external_zod_namespaceObject.boolean().optional().default(false).describe("Optional: Whether search should be case-sensitive.")
|
|
99
99
|
})
|
|
100
100
|
});
|
|
101
101
|
};
|