@wingman-ai/gateway 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.wingman/agents/coding/agent.md +5 -0
- package/.wingman/agents/coding-v2/agent.md +58 -0
- package/.wingman/agents/game-dev/agent.md +94 -0
- package/.wingman/agents/game-dev/art-generation.md +37 -0
- package/.wingman/agents/game-dev/asset-refinement.md +17 -0
- package/.wingman/agents/game-dev/planning-idea.md +17 -0
- package/.wingman/agents/game-dev/ui-specialist.md +17 -0
- package/.wingman/agents/main/agent.md +2 -0
- package/README.md +1 -0
- package/dist/agent/config/agentConfig.d.ts +4 -0
- package/dist/agent/config/mcpClientManager.cjs +44 -10
- package/dist/agent/config/mcpClientManager.d.ts +6 -2
- package/dist/agent/config/mcpClientManager.js +44 -10
- package/dist/agent/config/toolRegistry.cjs +3 -1
- package/dist/agent/config/toolRegistry.js +3 -1
- package/dist/agent/tests/mcpClientManager.test.cjs +124 -0
- package/dist/agent/tests/mcpClientManager.test.d.ts +1 -0
- package/dist/agent/tests/mcpClientManager.test.js +118 -0
- package/dist/agent/tools/command_execute.cjs +1 -1
- package/dist/agent/tools/command_execute.js +1 -1
- package/dist/cli/config/schema.d.ts +2 -0
- package/dist/cli/core/agentInvoker.cjs +55 -66
- package/dist/cli/core/agentInvoker.d.ts +10 -13
- package/dist/cli/core/agentInvoker.js +42 -62
- package/dist/cli/core/imagePersistence.cjs +125 -0
- package/dist/cli/core/imagePersistence.d.ts +24 -0
- package/dist/cli/core/imagePersistence.js +85 -0
- package/dist/cli/core/sessionManager.cjs +297 -40
- package/dist/cli/core/sessionManager.d.ts +9 -0
- package/dist/cli/core/sessionManager.js +297 -40
- package/dist/debug/terminalProbe.cjs +57 -0
- package/dist/debug/terminalProbe.d.ts +10 -0
- package/dist/debug/terminalProbe.js +20 -0
- package/dist/debug/terminalProbeAuth.cjs +140 -0
- package/dist/debug/terminalProbeAuth.d.ts +20 -0
- package/dist/debug/terminalProbeAuth.js +97 -0
- package/dist/gateway/http/fs.cjs +19 -0
- package/dist/gateway/http/fs.js +19 -0
- package/dist/gateway/http/sessions.cjs +25 -5
- package/dist/gateway/http/sessions.js +25 -5
- package/dist/gateway/server.cjs +112 -11
- package/dist/gateway/server.d.ts +2 -0
- package/dist/gateway/server.js +112 -11
- 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/agentInvokerSummarization.test.cjs +56 -37
- package/dist/tests/agentInvokerSummarization.test.js +58 -39
- package/dist/tests/agentInvokerWorkdir.test.cjs +50 -0
- package/dist/tests/agentInvokerWorkdir.test.js +52 -2
- package/dist/tests/cli-init.test.cjs +36 -0
- package/dist/tests/cli-init.test.js +36 -0
- package/dist/tests/codex-provider.test.cjs +173 -0
- package/dist/tests/codex-provider.test.js +174 -1
- package/dist/tests/falRuntime.test.cjs +78 -0
- package/dist/tests/falRuntime.test.d.ts +1 -0
- package/dist/tests/falRuntime.test.js +72 -0
- package/dist/tests/falSummary.test.cjs +51 -0
- package/dist/tests/falSummary.test.d.ts +1 -0
- package/dist/tests/falSummary.test.js +45 -0
- package/dist/tests/gateway.test.cjs +109 -1
- package/dist/tests/gateway.test.js +109 -1
- package/dist/tests/imagePersistence.test.cjs +143 -0
- package/dist/tests/imagePersistence.test.d.ts +1 -0
- package/dist/tests/imagePersistence.test.js +137 -0
- package/dist/tests/sessionMessageAttachments.test.cjs +30 -0
- package/dist/tests/sessionMessageAttachments.test.js +30 -0
- package/dist/tests/sessionStateMessages.test.cjs +126 -0
- package/dist/tests/sessionStateMessages.test.js +126 -0
- package/dist/tests/sessions-api.test.cjs +117 -3
- package/dist/tests/sessions-api.test.js +118 -4
- package/dist/tests/terminalProbe.test.cjs +45 -0
- package/dist/tests/terminalProbe.test.d.ts +1 -0
- package/dist/tests/terminalProbe.test.js +39 -0
- package/dist/tests/terminalProbeAuth.test.cjs +85 -0
- package/dist/tests/terminalProbeAuth.test.d.ts +1 -0
- package/dist/tests/terminalProbeAuth.test.js +79 -0
- package/dist/tools/fal/runtime.cjs +103 -0
- package/dist/tools/fal/runtime.d.ts +10 -0
- package/dist/tools/fal/runtime.js +60 -0
- package/dist/tools/fal/summary.cjs +78 -0
- package/dist/tools/fal/summary.d.ts +22 -0
- package/dist/tools/fal/summary.js +41 -0
- package/dist/tools/mcp-fal-ai.cjs +1041 -0
- package/dist/tools/mcp-fal-ai.d.ts +1 -0
- package/dist/tools/mcp-fal-ai.js +1025 -0
- package/dist/types/mcp.cjs +2 -0
- package/dist/types/mcp.d.ts +8 -0
- package/dist/types/mcp.js +3 -1
- package/dist/webui/assets/index-0nUBsUUq.js +278 -0
- package/dist/webui/assets/index-kk7OrD-G.css +11 -0
- package/dist/webui/index.html +2 -2
- package/package.json +16 -13
- package/dist/webui/assets/index-DVWQluit.css +0 -11
- package/dist/webui/assets/index-Dlyzwalc.js +0 -270
|
@@ -43,6 +43,7 @@ const init_cjs_namespaceObject = require("../cli/commands/init.cjs");
|
|
|
43
43
|
(0, external_vitest_namespaceObject.expect)((0, external_node_fs_namespaceObject.existsSync)(codingAgentPath)).toBe(true);
|
|
44
44
|
const codingPrompt = (0, external_node_fs_namespaceObject.readFileSync)(codingAgentPath, "utf-8");
|
|
45
45
|
(0, external_vitest_namespaceObject.expect)(codingPrompt).toContain("write_todos");
|
|
46
|
+
(0, external_vitest_namespaceObject.expect)(codingPrompt).toContain("read_todos");
|
|
46
47
|
(0, external_vitest_namespaceObject.expect)(codingPrompt).not.toContain("update_plan");
|
|
47
48
|
(0, external_vitest_namespaceObject.expect)(codingPrompt).not.toContain("subAgents:");
|
|
48
49
|
(0, external_vitest_namespaceObject.expect)(codingPrompt).toContain("Do not delegate coding work to subagents");
|
|
@@ -55,6 +56,7 @@ const init_cjs_namespaceObject = require("../cli/commands/init.cjs");
|
|
|
55
56
|
(0, external_vitest_namespaceObject.expect)(codingV2Prompt).toContain("promptFile: ./implementor.md");
|
|
56
57
|
(0, external_vitest_namespaceObject.expect)(codingV2Prompt).toContain("`task` tool");
|
|
57
58
|
(0, external_vitest_namespaceObject.expect)(codingV2Prompt).toContain("write_todos");
|
|
59
|
+
(0, external_vitest_namespaceObject.expect)(codingV2Prompt).toContain("read_todos");
|
|
58
60
|
const codingV2ImplementorPath = (0, external_node_path_namespaceObject.join)(workspace, ".wingman", "agents", "coding-v2", "implementor.md");
|
|
59
61
|
const codingV2PlannerPath = (0, external_node_path_namespaceObject.join)(workspace, ".wingman", "agents", "coding-v2", "planner.md");
|
|
60
62
|
const codingV2ReviewerPath = (0, external_node_path_namespaceObject.join)(workspace, ".wingman", "agents", "coding-v2", "reviewer.md");
|
|
@@ -63,6 +65,40 @@ const init_cjs_namespaceObject = require("../cli/commands/init.cjs");
|
|
|
63
65
|
(0, external_vitest_namespaceObject.expect)((0, external_node_fs_namespaceObject.existsSync)(codingV2PlannerPath)).toBe(false);
|
|
64
66
|
(0, external_vitest_namespaceObject.expect)((0, external_node_fs_namespaceObject.existsSync)(codingV2ReviewerPath)).toBe(false);
|
|
65
67
|
(0, external_vitest_namespaceObject.expect)((0, external_node_fs_namespaceObject.existsSync)(codingV2ResearcherPath)).toBe(false);
|
|
68
|
+
const gameDevAgentPath = (0, external_node_path_namespaceObject.join)(workspace, ".wingman", "agents", "game-dev", "agent.md");
|
|
69
|
+
(0, external_vitest_namespaceObject.expect)((0, external_node_fs_namespaceObject.existsSync)(gameDevAgentPath)).toBe(true);
|
|
70
|
+
const gameDevPrompt = (0, external_node_fs_namespaceObject.readFileSync)(gameDevAgentPath, "utf-8");
|
|
71
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("name: game-dev");
|
|
72
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("subAgents:");
|
|
73
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("name: art-generation");
|
|
74
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("promptFile: ./art-generation.md");
|
|
75
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("name: asset-refinement");
|
|
76
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("promptFile: ./asset-refinement.md");
|
|
77
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("name: planning-idea");
|
|
78
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("promptFile: ./planning-idea.md");
|
|
79
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("name: ui-specialist");
|
|
80
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("promptFile: ./ui-specialist.md");
|
|
81
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("write_todos");
|
|
82
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("read_todos");
|
|
83
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("UV-aware texture planning");
|
|
84
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("MeshStandardMaterial");
|
|
85
|
+
(0, external_vitest_namespaceObject.expect)(gameDevPrompt).toContain("uv`/`uv2");
|
|
86
|
+
const gameDevArtGenerationPath = (0, external_node_path_namespaceObject.join)(workspace, ".wingman", "agents", "game-dev", "art-generation.md");
|
|
87
|
+
const gameDevAssetRefinementPath = (0, external_node_path_namespaceObject.join)(workspace, ".wingman", "agents", "game-dev", "asset-refinement.md");
|
|
88
|
+
const gameDevPlanningIdeaPath = (0, external_node_path_namespaceObject.join)(workspace, ".wingman", "agents", "game-dev", "planning-idea.md");
|
|
89
|
+
const gameDevUiSpecialistPath = (0, external_node_path_namespaceObject.join)(workspace, ".wingman", "agents", "game-dev", "ui-specialist.md");
|
|
90
|
+
(0, external_vitest_namespaceObject.expect)((0, external_node_fs_namespaceObject.existsSync)(gameDevArtGenerationPath)).toBe(true);
|
|
91
|
+
(0, external_vitest_namespaceObject.expect)((0, external_node_fs_namespaceObject.existsSync)(gameDevAssetRefinementPath)).toBe(true);
|
|
92
|
+
(0, external_vitest_namespaceObject.expect)((0, external_node_fs_namespaceObject.existsSync)(gameDevPlanningIdeaPath)).toBe(true);
|
|
93
|
+
(0, external_vitest_namespaceObject.expect)((0, external_node_fs_namespaceObject.existsSync)(gameDevUiSpecialistPath)).toBe(true);
|
|
94
|
+
const gameDevArtGenerationPrompt = (0, external_node_fs_namespaceObject.readFileSync)(gameDevArtGenerationPath, "utf-8");
|
|
95
|
+
(0, external_vitest_namespaceObject.expect)(gameDevArtGenerationPrompt).toContain("UV set(s) or UDIM tiles");
|
|
96
|
+
(0, external_vitest_namespaceObject.expect)(gameDevArtGenerationPrompt).toContain("texel density targets");
|
|
97
|
+
(0, external_vitest_namespaceObject.expect)(gameDevArtGenerationPrompt).toContain("Texture-to-geometry mapping notes");
|
|
98
|
+
(0, external_vitest_namespaceObject.expect)(gameDevArtGenerationPrompt).toContain("material slot");
|
|
99
|
+
(0, external_vitest_namespaceObject.expect)(gameDevArtGenerationPrompt).toContain("need `uv`, and `aoMap`");
|
|
100
|
+
(0, external_vitest_namespaceObject.expect)(gameDevArtGenerationPrompt).toContain("flipY = false");
|
|
101
|
+
(0, external_vitest_namespaceObject.expect)(gameDevArtGenerationPrompt).toContain("RepeatWrapping");
|
|
66
102
|
});
|
|
67
103
|
(0, external_vitest_namespaceObject.it)("merges existing config when --merge is set", async ()=>{
|
|
68
104
|
const configDir = (0, external_node_path_namespaceObject.join)(workspace, ".wingman");
|
|
@@ -41,6 +41,7 @@ describe("CLI init", ()=>{
|
|
|
41
41
|
expect(existsSync(codingAgentPath)).toBe(true);
|
|
42
42
|
const codingPrompt = readFileSync(codingAgentPath, "utf-8");
|
|
43
43
|
expect(codingPrompt).toContain("write_todos");
|
|
44
|
+
expect(codingPrompt).toContain("read_todos");
|
|
44
45
|
expect(codingPrompt).not.toContain("update_plan");
|
|
45
46
|
expect(codingPrompt).not.toContain("subAgents:");
|
|
46
47
|
expect(codingPrompt).toContain("Do not delegate coding work to subagents");
|
|
@@ -53,6 +54,7 @@ describe("CLI init", ()=>{
|
|
|
53
54
|
expect(codingV2Prompt).toContain("promptFile: ./implementor.md");
|
|
54
55
|
expect(codingV2Prompt).toContain("`task` tool");
|
|
55
56
|
expect(codingV2Prompt).toContain("write_todos");
|
|
57
|
+
expect(codingV2Prompt).toContain("read_todos");
|
|
56
58
|
const codingV2ImplementorPath = join(workspace, ".wingman", "agents", "coding-v2", "implementor.md");
|
|
57
59
|
const codingV2PlannerPath = join(workspace, ".wingman", "agents", "coding-v2", "planner.md");
|
|
58
60
|
const codingV2ReviewerPath = join(workspace, ".wingman", "agents", "coding-v2", "reviewer.md");
|
|
@@ -61,6 +63,40 @@ describe("CLI init", ()=>{
|
|
|
61
63
|
expect(existsSync(codingV2PlannerPath)).toBe(false);
|
|
62
64
|
expect(existsSync(codingV2ReviewerPath)).toBe(false);
|
|
63
65
|
expect(existsSync(codingV2ResearcherPath)).toBe(false);
|
|
66
|
+
const gameDevAgentPath = join(workspace, ".wingman", "agents", "game-dev", "agent.md");
|
|
67
|
+
expect(existsSync(gameDevAgentPath)).toBe(true);
|
|
68
|
+
const gameDevPrompt = readFileSync(gameDevAgentPath, "utf-8");
|
|
69
|
+
expect(gameDevPrompt).toContain("name: game-dev");
|
|
70
|
+
expect(gameDevPrompt).toContain("subAgents:");
|
|
71
|
+
expect(gameDevPrompt).toContain("name: art-generation");
|
|
72
|
+
expect(gameDevPrompt).toContain("promptFile: ./art-generation.md");
|
|
73
|
+
expect(gameDevPrompt).toContain("name: asset-refinement");
|
|
74
|
+
expect(gameDevPrompt).toContain("promptFile: ./asset-refinement.md");
|
|
75
|
+
expect(gameDevPrompt).toContain("name: planning-idea");
|
|
76
|
+
expect(gameDevPrompt).toContain("promptFile: ./planning-idea.md");
|
|
77
|
+
expect(gameDevPrompt).toContain("name: ui-specialist");
|
|
78
|
+
expect(gameDevPrompt).toContain("promptFile: ./ui-specialist.md");
|
|
79
|
+
expect(gameDevPrompt).toContain("write_todos");
|
|
80
|
+
expect(gameDevPrompt).toContain("read_todos");
|
|
81
|
+
expect(gameDevPrompt).toContain("UV-aware texture planning");
|
|
82
|
+
expect(gameDevPrompt).toContain("MeshStandardMaterial");
|
|
83
|
+
expect(gameDevPrompt).toContain("uv`/`uv2");
|
|
84
|
+
const gameDevArtGenerationPath = join(workspace, ".wingman", "agents", "game-dev", "art-generation.md");
|
|
85
|
+
const gameDevAssetRefinementPath = join(workspace, ".wingman", "agents", "game-dev", "asset-refinement.md");
|
|
86
|
+
const gameDevPlanningIdeaPath = join(workspace, ".wingman", "agents", "game-dev", "planning-idea.md");
|
|
87
|
+
const gameDevUiSpecialistPath = join(workspace, ".wingman", "agents", "game-dev", "ui-specialist.md");
|
|
88
|
+
expect(existsSync(gameDevArtGenerationPath)).toBe(true);
|
|
89
|
+
expect(existsSync(gameDevAssetRefinementPath)).toBe(true);
|
|
90
|
+
expect(existsSync(gameDevPlanningIdeaPath)).toBe(true);
|
|
91
|
+
expect(existsSync(gameDevUiSpecialistPath)).toBe(true);
|
|
92
|
+
const gameDevArtGenerationPrompt = readFileSync(gameDevArtGenerationPath, "utf-8");
|
|
93
|
+
expect(gameDevArtGenerationPrompt).toContain("UV set(s) or UDIM tiles");
|
|
94
|
+
expect(gameDevArtGenerationPrompt).toContain("texel density targets");
|
|
95
|
+
expect(gameDevArtGenerationPrompt).toContain("Texture-to-geometry mapping notes");
|
|
96
|
+
expect(gameDevArtGenerationPrompt).toContain("material slot");
|
|
97
|
+
expect(gameDevArtGenerationPrompt).toContain("need `uv`, and `aoMap`");
|
|
98
|
+
expect(gameDevArtGenerationPrompt).toContain("flipY = false");
|
|
99
|
+
expect(gameDevArtGenerationPrompt).toContain("RepeatWrapping");
|
|
64
100
|
});
|
|
65
101
|
it("merges existing config when --merge is set", async ()=>{
|
|
66
102
|
const configDir = join(workspace, ".wingman");
|
|
@@ -161,6 +161,171 @@ const codex_cjs_namespaceObject = require("../providers/codex.cjs");
|
|
|
161
161
|
const payload = JSON.parse(String(requestInit?.body));
|
|
162
162
|
(0, external_vitest_namespaceObject.expect)(payload.store).toBe(false);
|
|
163
163
|
});
|
|
164
|
+
(0, external_vitest_namespaceObject.it)("refreshes tokens when the codex access token is expiring", async ()=>{
|
|
165
|
+
const expiringAccessToken = createJwt({
|
|
166
|
+
exp: Math.floor((Date.now() + 30000) / 1000),
|
|
167
|
+
client_id: "app_client_123"
|
|
168
|
+
});
|
|
169
|
+
const refreshedAccessToken = createJwt({
|
|
170
|
+
exp: Math.floor((Date.now() + 86400000) / 1000),
|
|
171
|
+
client_id: "app_client_123"
|
|
172
|
+
});
|
|
173
|
+
const staleIdToken = createJwt({
|
|
174
|
+
aud: [
|
|
175
|
+
"app_client_123"
|
|
176
|
+
],
|
|
177
|
+
"https://api.openai.com/auth": {
|
|
178
|
+
chatgpt_account_id: "acct_old"
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
const refreshedIdToken = createJwt({
|
|
182
|
+
aud: [
|
|
183
|
+
"app_client_123"
|
|
184
|
+
],
|
|
185
|
+
"https://api.openai.com/auth": {
|
|
186
|
+
chatgpt_account_id: "acct_refreshed"
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
writeCodexAuth({
|
|
190
|
+
tokens: {
|
|
191
|
+
access_token: expiringAccessToken,
|
|
192
|
+
refresh_token: "refresh-old",
|
|
193
|
+
id_token: staleIdToken,
|
|
194
|
+
account_id: "acct_old"
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
const baseFetch = external_vitest_namespaceObject.vi.fn(async (input, init)=>{
|
|
198
|
+
const url = "string" == typeof input ? input : input instanceof URL ? input.toString() : input.url;
|
|
199
|
+
if ("https://auth.openai.com/oauth/token" === url) {
|
|
200
|
+
const params = new URLSearchParams(String(init?.body ?? ""));
|
|
201
|
+
(0, external_vitest_namespaceObject.expect)(params.get("grant_type")).toBe("refresh_token");
|
|
202
|
+
(0, external_vitest_namespaceObject.expect)(params.get("refresh_token")).toBe("refresh-old");
|
|
203
|
+
(0, external_vitest_namespaceObject.expect)(params.get("client_id")).toBe("app_client_123");
|
|
204
|
+
return new Response(JSON.stringify({
|
|
205
|
+
access_token: refreshedAccessToken,
|
|
206
|
+
refresh_token: "refresh-new",
|
|
207
|
+
id_token: refreshedIdToken,
|
|
208
|
+
token_type: "bearer",
|
|
209
|
+
expires_in: 864000
|
|
210
|
+
}), {
|
|
211
|
+
status: 200,
|
|
212
|
+
headers: {
|
|
213
|
+
"content-type": "application/json"
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
(0, external_vitest_namespaceObject.expect)(url).toBe("https://chatgpt.com/backend-api/codex/responses");
|
|
218
|
+
const headers = new Headers(init?.headers);
|
|
219
|
+
(0, external_vitest_namespaceObject.expect)(headers.get("authorization")).toBe(`Bearer ${refreshedAccessToken}`);
|
|
220
|
+
(0, external_vitest_namespaceObject.expect)(headers.get("chatgpt-account-id")).toBe("acct_refreshed");
|
|
221
|
+
return new Response("{}", {
|
|
222
|
+
status: 200
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
|
|
226
|
+
baseFetch
|
|
227
|
+
});
|
|
228
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
229
|
+
method: "POST",
|
|
230
|
+
body: JSON.stringify({
|
|
231
|
+
model: "codex-mini-latest",
|
|
232
|
+
input: "hello"
|
|
233
|
+
})
|
|
234
|
+
});
|
|
235
|
+
(0, external_vitest_namespaceObject.expect)(baseFetch).toHaveBeenCalledTimes(2);
|
|
236
|
+
const persisted = JSON.parse((0, external_node_fs_namespaceObject.readFileSync)((0, codex_cjs_namespaceObject.getCodexAuthPath)(), "utf-8"));
|
|
237
|
+
(0, external_vitest_namespaceObject.expect)(persisted.tokens?.access_token).toBe(refreshedAccessToken);
|
|
238
|
+
(0, external_vitest_namespaceObject.expect)(persisted.tokens?.refresh_token).toBe("refresh-new");
|
|
239
|
+
(0, external_vitest_namespaceObject.expect)(persisted.tokens?.id_token).toBe(refreshedIdToken);
|
|
240
|
+
(0, external_vitest_namespaceObject.expect)(persisted.tokens?.account_id).toBe("acct_refreshed");
|
|
241
|
+
(0, external_vitest_namespaceObject.expect)(typeof persisted.last_refresh).toBe("string");
|
|
242
|
+
});
|
|
243
|
+
(0, external_vitest_namespaceObject.it)("retries once after auth failure by refreshing codex token", async ()=>{
|
|
244
|
+
const initialAccessToken = createJwt({
|
|
245
|
+
exp: Math.floor((Date.now() + 86400000) / 1000),
|
|
246
|
+
client_id: "app_client_retry"
|
|
247
|
+
});
|
|
248
|
+
const refreshedAccessToken = createJwt({
|
|
249
|
+
exp: Math.floor((Date.now() + 172800000) / 1000),
|
|
250
|
+
client_id: "app_client_retry"
|
|
251
|
+
});
|
|
252
|
+
const idToken = createJwt({
|
|
253
|
+
aud: [
|
|
254
|
+
"app_client_retry"
|
|
255
|
+
],
|
|
256
|
+
"https://api.openai.com/auth": {
|
|
257
|
+
chatgpt_account_id: "acct_retry"
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
const refreshedIdToken = createJwt({
|
|
261
|
+
aud: [
|
|
262
|
+
"app_client_retry"
|
|
263
|
+
],
|
|
264
|
+
"https://api.openai.com/auth": {
|
|
265
|
+
chatgpt_account_id: "acct_retry_new"
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
writeCodexAuth({
|
|
269
|
+
tokens: {
|
|
270
|
+
access_token: initialAccessToken,
|
|
271
|
+
refresh_token: "refresh-retry",
|
|
272
|
+
id_token: idToken,
|
|
273
|
+
account_id: "acct_retry"
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
let codexCallCount = 0;
|
|
277
|
+
const baseFetch = external_vitest_namespaceObject.vi.fn(async (input, init)=>{
|
|
278
|
+
const url = "string" == typeof input ? input : input instanceof URL ? input.toString() : input.url;
|
|
279
|
+
if ("https://auth.openai.com/oauth/token" === url) {
|
|
280
|
+
const params = new URLSearchParams(String(init?.body ?? ""));
|
|
281
|
+
(0, external_vitest_namespaceObject.expect)(params.get("grant_type")).toBe("refresh_token");
|
|
282
|
+
(0, external_vitest_namespaceObject.expect)(params.get("refresh_token")).toBe("refresh-retry");
|
|
283
|
+
(0, external_vitest_namespaceObject.expect)(params.get("client_id")).toBe("app_client_retry");
|
|
284
|
+
return new Response(JSON.stringify({
|
|
285
|
+
access_token: refreshedAccessToken,
|
|
286
|
+
refresh_token: "refresh-retry-new",
|
|
287
|
+
id_token: refreshedIdToken,
|
|
288
|
+
token_type: "bearer",
|
|
289
|
+
expires_in: 864000
|
|
290
|
+
}), {
|
|
291
|
+
status: 200,
|
|
292
|
+
headers: {
|
|
293
|
+
"content-type": "application/json"
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
codexCallCount += 1;
|
|
298
|
+
const headers = new Headers(init?.headers);
|
|
299
|
+
if (1 === codexCallCount) {
|
|
300
|
+
(0, external_vitest_namespaceObject.expect)(headers.get("authorization")).toBe(`Bearer ${initialAccessToken}`);
|
|
301
|
+
return new Response("unauthorized", {
|
|
302
|
+
status: 401
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
(0, external_vitest_namespaceObject.expect)(codexCallCount).toBe(2);
|
|
306
|
+
(0, external_vitest_namespaceObject.expect)(headers.get("authorization")).toBe(`Bearer ${refreshedAccessToken}`);
|
|
307
|
+
(0, external_vitest_namespaceObject.expect)(headers.get("chatgpt-account-id")).toBe("acct_retry_new");
|
|
308
|
+
return new Response("{}", {
|
|
309
|
+
status: 200
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
const codexFetch = (0, codex_cjs_namespaceObject.createCodexFetch)({
|
|
313
|
+
baseFetch
|
|
314
|
+
});
|
|
315
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
316
|
+
method: "POST",
|
|
317
|
+
body: JSON.stringify({
|
|
318
|
+
model: "codex-mini-latest",
|
|
319
|
+
input: "hello"
|
|
320
|
+
})
|
|
321
|
+
});
|
|
322
|
+
(0, external_vitest_namespaceObject.expect)(codexCallCount).toBe(2);
|
|
323
|
+
(0, external_vitest_namespaceObject.expect)(baseFetch).toHaveBeenCalledTimes(3);
|
|
324
|
+
const persisted = JSON.parse((0, external_node_fs_namespaceObject.readFileSync)((0, codex_cjs_namespaceObject.getCodexAuthPath)(), "utf-8"));
|
|
325
|
+
(0, external_vitest_namespaceObject.expect)(persisted.tokens?.access_token).toBe(refreshedAccessToken);
|
|
326
|
+
(0, external_vitest_namespaceObject.expect)(persisted.tokens?.refresh_token).toBe("refresh-retry-new");
|
|
327
|
+
(0, external_vitest_namespaceObject.expect)(persisted.tokens?.account_id).toBe("acct_retry_new");
|
|
328
|
+
});
|
|
164
329
|
(0, external_vitest_namespaceObject.it)("uses fallback token when codex auth file is unavailable", async ()=>{
|
|
165
330
|
const baseFetch = external_vitest_namespaceObject.vi.fn(async (_input, _init)=>new Response("{}", {
|
|
166
331
|
status: 200
|
|
@@ -204,6 +369,14 @@ function writeCodexAuth(payload) {
|
|
|
204
369
|
});
|
|
205
370
|
(0, external_node_fs_namespaceObject.writeFileSync)(authPath, JSON.stringify(payload, null, 2));
|
|
206
371
|
}
|
|
372
|
+
function createJwt(payload) {
|
|
373
|
+
const header = Buffer.from(JSON.stringify({
|
|
374
|
+
alg: "HS256",
|
|
375
|
+
typ: "JWT"
|
|
376
|
+
})).toString("base64url");
|
|
377
|
+
const body = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
378
|
+
return `${header}.${body}.signature`;
|
|
379
|
+
}
|
|
207
380
|
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
208
381
|
Object.defineProperty(exports, '__esModule', {
|
|
209
382
|
value: true
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
@@ -159,6 +159,171 @@ describe("codex provider", ()=>{
|
|
|
159
159
|
const payload = JSON.parse(String(requestInit?.body));
|
|
160
160
|
expect(payload.store).toBe(false);
|
|
161
161
|
});
|
|
162
|
+
it("refreshes tokens when the codex access token is expiring", async ()=>{
|
|
163
|
+
const expiringAccessToken = createJwt({
|
|
164
|
+
exp: Math.floor((Date.now() + 30000) / 1000),
|
|
165
|
+
client_id: "app_client_123"
|
|
166
|
+
});
|
|
167
|
+
const refreshedAccessToken = createJwt({
|
|
168
|
+
exp: Math.floor((Date.now() + 86400000) / 1000),
|
|
169
|
+
client_id: "app_client_123"
|
|
170
|
+
});
|
|
171
|
+
const staleIdToken = createJwt({
|
|
172
|
+
aud: [
|
|
173
|
+
"app_client_123"
|
|
174
|
+
],
|
|
175
|
+
"https://api.openai.com/auth": {
|
|
176
|
+
chatgpt_account_id: "acct_old"
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
const refreshedIdToken = createJwt({
|
|
180
|
+
aud: [
|
|
181
|
+
"app_client_123"
|
|
182
|
+
],
|
|
183
|
+
"https://api.openai.com/auth": {
|
|
184
|
+
chatgpt_account_id: "acct_refreshed"
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
writeCodexAuth({
|
|
188
|
+
tokens: {
|
|
189
|
+
access_token: expiringAccessToken,
|
|
190
|
+
refresh_token: "refresh-old",
|
|
191
|
+
id_token: staleIdToken,
|
|
192
|
+
account_id: "acct_old"
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
const baseFetch = vi.fn(async (input, init)=>{
|
|
196
|
+
const url = "string" == typeof input ? input : input instanceof URL ? input.toString() : input.url;
|
|
197
|
+
if ("https://auth.openai.com/oauth/token" === url) {
|
|
198
|
+
const params = new URLSearchParams(String(init?.body ?? ""));
|
|
199
|
+
expect(params.get("grant_type")).toBe("refresh_token");
|
|
200
|
+
expect(params.get("refresh_token")).toBe("refresh-old");
|
|
201
|
+
expect(params.get("client_id")).toBe("app_client_123");
|
|
202
|
+
return new Response(JSON.stringify({
|
|
203
|
+
access_token: refreshedAccessToken,
|
|
204
|
+
refresh_token: "refresh-new",
|
|
205
|
+
id_token: refreshedIdToken,
|
|
206
|
+
token_type: "bearer",
|
|
207
|
+
expires_in: 864000
|
|
208
|
+
}), {
|
|
209
|
+
status: 200,
|
|
210
|
+
headers: {
|
|
211
|
+
"content-type": "application/json"
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
expect(url).toBe("https://chatgpt.com/backend-api/codex/responses");
|
|
216
|
+
const headers = new Headers(init?.headers);
|
|
217
|
+
expect(headers.get("authorization")).toBe(`Bearer ${refreshedAccessToken}`);
|
|
218
|
+
expect(headers.get("chatgpt-account-id")).toBe("acct_refreshed");
|
|
219
|
+
return new Response("{}", {
|
|
220
|
+
status: 200
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
const codexFetch = createCodexFetch({
|
|
224
|
+
baseFetch
|
|
225
|
+
});
|
|
226
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
227
|
+
method: "POST",
|
|
228
|
+
body: JSON.stringify({
|
|
229
|
+
model: "codex-mini-latest",
|
|
230
|
+
input: "hello"
|
|
231
|
+
})
|
|
232
|
+
});
|
|
233
|
+
expect(baseFetch).toHaveBeenCalledTimes(2);
|
|
234
|
+
const persisted = JSON.parse(readFileSync(getCodexAuthPath(), "utf-8"));
|
|
235
|
+
expect(persisted.tokens?.access_token).toBe(refreshedAccessToken);
|
|
236
|
+
expect(persisted.tokens?.refresh_token).toBe("refresh-new");
|
|
237
|
+
expect(persisted.tokens?.id_token).toBe(refreshedIdToken);
|
|
238
|
+
expect(persisted.tokens?.account_id).toBe("acct_refreshed");
|
|
239
|
+
expect(typeof persisted.last_refresh).toBe("string");
|
|
240
|
+
});
|
|
241
|
+
it("retries once after auth failure by refreshing codex token", async ()=>{
|
|
242
|
+
const initialAccessToken = createJwt({
|
|
243
|
+
exp: Math.floor((Date.now() + 86400000) / 1000),
|
|
244
|
+
client_id: "app_client_retry"
|
|
245
|
+
});
|
|
246
|
+
const refreshedAccessToken = createJwt({
|
|
247
|
+
exp: Math.floor((Date.now() + 172800000) / 1000),
|
|
248
|
+
client_id: "app_client_retry"
|
|
249
|
+
});
|
|
250
|
+
const idToken = createJwt({
|
|
251
|
+
aud: [
|
|
252
|
+
"app_client_retry"
|
|
253
|
+
],
|
|
254
|
+
"https://api.openai.com/auth": {
|
|
255
|
+
chatgpt_account_id: "acct_retry"
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
const refreshedIdToken = createJwt({
|
|
259
|
+
aud: [
|
|
260
|
+
"app_client_retry"
|
|
261
|
+
],
|
|
262
|
+
"https://api.openai.com/auth": {
|
|
263
|
+
chatgpt_account_id: "acct_retry_new"
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
writeCodexAuth({
|
|
267
|
+
tokens: {
|
|
268
|
+
access_token: initialAccessToken,
|
|
269
|
+
refresh_token: "refresh-retry",
|
|
270
|
+
id_token: idToken,
|
|
271
|
+
account_id: "acct_retry"
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
let codexCallCount = 0;
|
|
275
|
+
const baseFetch = vi.fn(async (input, init)=>{
|
|
276
|
+
const url = "string" == typeof input ? input : input instanceof URL ? input.toString() : input.url;
|
|
277
|
+
if ("https://auth.openai.com/oauth/token" === url) {
|
|
278
|
+
const params = new URLSearchParams(String(init?.body ?? ""));
|
|
279
|
+
expect(params.get("grant_type")).toBe("refresh_token");
|
|
280
|
+
expect(params.get("refresh_token")).toBe("refresh-retry");
|
|
281
|
+
expect(params.get("client_id")).toBe("app_client_retry");
|
|
282
|
+
return new Response(JSON.stringify({
|
|
283
|
+
access_token: refreshedAccessToken,
|
|
284
|
+
refresh_token: "refresh-retry-new",
|
|
285
|
+
id_token: refreshedIdToken,
|
|
286
|
+
token_type: "bearer",
|
|
287
|
+
expires_in: 864000
|
|
288
|
+
}), {
|
|
289
|
+
status: 200,
|
|
290
|
+
headers: {
|
|
291
|
+
"content-type": "application/json"
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
codexCallCount += 1;
|
|
296
|
+
const headers = new Headers(init?.headers);
|
|
297
|
+
if (1 === codexCallCount) {
|
|
298
|
+
expect(headers.get("authorization")).toBe(`Bearer ${initialAccessToken}`);
|
|
299
|
+
return new Response("unauthorized", {
|
|
300
|
+
status: 401
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
expect(codexCallCount).toBe(2);
|
|
304
|
+
expect(headers.get("authorization")).toBe(`Bearer ${refreshedAccessToken}`);
|
|
305
|
+
expect(headers.get("chatgpt-account-id")).toBe("acct_retry_new");
|
|
306
|
+
return new Response("{}", {
|
|
307
|
+
status: 200
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
const codexFetch = createCodexFetch({
|
|
311
|
+
baseFetch
|
|
312
|
+
});
|
|
313
|
+
await codexFetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
314
|
+
method: "POST",
|
|
315
|
+
body: JSON.stringify({
|
|
316
|
+
model: "codex-mini-latest",
|
|
317
|
+
input: "hello"
|
|
318
|
+
})
|
|
319
|
+
});
|
|
320
|
+
expect(codexCallCount).toBe(2);
|
|
321
|
+
expect(baseFetch).toHaveBeenCalledTimes(3);
|
|
322
|
+
const persisted = JSON.parse(readFileSync(getCodexAuthPath(), "utf-8"));
|
|
323
|
+
expect(persisted.tokens?.access_token).toBe(refreshedAccessToken);
|
|
324
|
+
expect(persisted.tokens?.refresh_token).toBe("refresh-retry-new");
|
|
325
|
+
expect(persisted.tokens?.account_id).toBe("acct_retry_new");
|
|
326
|
+
});
|
|
162
327
|
it("uses fallback token when codex auth file is unavailable", async ()=>{
|
|
163
328
|
const baseFetch = vi.fn(async (_input, _init)=>new Response("{}", {
|
|
164
329
|
status: 200
|
|
@@ -202,3 +367,11 @@ function writeCodexAuth(payload) {
|
|
|
202
367
|
});
|
|
203
368
|
writeFileSync(authPath, JSON.stringify(payload, null, 2));
|
|
204
369
|
}
|
|
370
|
+
function createJwt(payload) {
|
|
371
|
+
const header = Buffer.from(JSON.stringify({
|
|
372
|
+
alg: "HS256",
|
|
373
|
+
typ: "JWT"
|
|
374
|
+
})).toString("base64url");
|
|
375
|
+
const body = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
376
|
+
return `${header}.${body}.signature`;
|
|
377
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_exports__ = {};
|
|
3
|
+
const external_vitest_namespaceObject = require("vitest");
|
|
4
|
+
const runtime_cjs_namespaceObject = require("../tools/fal/runtime.cjs");
|
|
5
|
+
(0, external_vitest_namespaceObject.describe)("fal runtime paths", ()=>{
|
|
6
|
+
(0, external_vitest_namespaceObject.it)("uses WINGMAN_WORKDIR when provided", ()=>{
|
|
7
|
+
const workdir = (0, runtime_cjs_namespaceObject.resolveFalWorkdir)({
|
|
8
|
+
env: {
|
|
9
|
+
WINGMAN_WORKDIR: "/tmp/wingman-session"
|
|
10
|
+
},
|
|
11
|
+
cwd: "/repo/apps/wingman"
|
|
12
|
+
});
|
|
13
|
+
(0, external_vitest_namespaceObject.expect)(workdir).toBe("/tmp/wingman-session");
|
|
14
|
+
});
|
|
15
|
+
(0, external_vitest_namespaceObject.it)("resolves relative WINGMAN_WORKDIR against cwd", ()=>{
|
|
16
|
+
const workdir = (0, runtime_cjs_namespaceObject.resolveFalWorkdir)({
|
|
17
|
+
env: {
|
|
18
|
+
WINGMAN_WORKDIR: "projects/game"
|
|
19
|
+
},
|
|
20
|
+
cwd: "/Users/test"
|
|
21
|
+
});
|
|
22
|
+
(0, external_vitest_namespaceObject.expect)(workdir).toBe("/Users/test/projects/game");
|
|
23
|
+
});
|
|
24
|
+
(0, external_vitest_namespaceObject.it)("prefers explicit FAL_MCP_STATE_DIR", ()=>{
|
|
25
|
+
const stateDir = (0, runtime_cjs_namespaceObject.resolveFalStateDir)({
|
|
26
|
+
env: {
|
|
27
|
+
WINGMAN_WORKDIR: "/tmp/session",
|
|
28
|
+
FAL_MCP_STATE_DIR: "/tmp/custom-state"
|
|
29
|
+
},
|
|
30
|
+
cwd: "/repo/apps/wingman"
|
|
31
|
+
});
|
|
32
|
+
(0, external_vitest_namespaceObject.expect)(stateDir).toBe("/tmp/custom-state");
|
|
33
|
+
});
|
|
34
|
+
(0, external_vitest_namespaceObject.it)("defaults state dir under working folder", ()=>{
|
|
35
|
+
const stateDir = (0, runtime_cjs_namespaceObject.resolveFalStateDir)({
|
|
36
|
+
env: {
|
|
37
|
+
WINGMAN_WORKDIR: "/tmp/session"
|
|
38
|
+
},
|
|
39
|
+
cwd: "/repo/apps/wingman"
|
|
40
|
+
});
|
|
41
|
+
(0, external_vitest_namespaceObject.expect)(stateDir).toBe("/tmp/session/.wingman/fal-ai");
|
|
42
|
+
});
|
|
43
|
+
(0, external_vitest_namespaceObject.it)("prefers explicit FAL_MCP_OUTPUT_DIR", ()=>{
|
|
44
|
+
const outputDir = (0, runtime_cjs_namespaceObject.resolveFalOutputDir)({
|
|
45
|
+
env: {
|
|
46
|
+
WINGMAN_WORKDIR: "/tmp/session",
|
|
47
|
+
FAL_MCP_OUTPUT_DIR: "assets/out"
|
|
48
|
+
},
|
|
49
|
+
cwd: "/repo/apps/wingman"
|
|
50
|
+
});
|
|
51
|
+
(0, external_vitest_namespaceObject.expect)(outputDir).toBe("/tmp/session/assets/out");
|
|
52
|
+
});
|
|
53
|
+
(0, external_vitest_namespaceObject.it)("defaults output dir under working folder", ()=>{
|
|
54
|
+
const outputDir = (0, runtime_cjs_namespaceObject.resolveFalOutputDir)({
|
|
55
|
+
env: {
|
|
56
|
+
WINGMAN_WORKDIR: "/tmp/session"
|
|
57
|
+
},
|
|
58
|
+
cwd: "/repo/apps/wingman"
|
|
59
|
+
});
|
|
60
|
+
(0, external_vitest_namespaceObject.expect)(outputDir).toBe("/tmp/session/generated");
|
|
61
|
+
});
|
|
62
|
+
(0, external_vitest_namespaceObject.it)("resolves local relative media paths against working folder", ()=>{
|
|
63
|
+
const resolved = (0, runtime_cjs_namespaceObject.resolveFalLocalMediaPath)("images/source.png", "/tmp/session", {
|
|
64
|
+
cwd: "/repo/apps/wingman"
|
|
65
|
+
});
|
|
66
|
+
(0, external_vitest_namespaceObject.expect)(resolved).toBe("/tmp/session/images/source.png");
|
|
67
|
+
});
|
|
68
|
+
(0, external_vitest_namespaceObject.it)("keeps local absolute media paths unchanged", ()=>{
|
|
69
|
+
const resolved = (0, runtime_cjs_namespaceObject.resolveFalLocalMediaPath)("/tmp/session/images/source.png", "/tmp/session", {
|
|
70
|
+
cwd: "/repo/apps/wingman"
|
|
71
|
+
});
|
|
72
|
+
(0, external_vitest_namespaceObject.expect)(resolved).toBe("/tmp/session/images/source.png");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
76
|
+
Object.defineProperty(exports, '__esModule', {
|
|
77
|
+
value: true
|
|
78
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { resolveFalLocalMediaPath, resolveFalOutputDir, resolveFalStateDir, resolveFalWorkdir } from "../tools/fal/runtime.js";
|
|
3
|
+
describe("fal runtime paths", ()=>{
|
|
4
|
+
it("uses WINGMAN_WORKDIR when provided", ()=>{
|
|
5
|
+
const workdir = resolveFalWorkdir({
|
|
6
|
+
env: {
|
|
7
|
+
WINGMAN_WORKDIR: "/tmp/wingman-session"
|
|
8
|
+
},
|
|
9
|
+
cwd: "/repo/apps/wingman"
|
|
10
|
+
});
|
|
11
|
+
expect(workdir).toBe("/tmp/wingman-session");
|
|
12
|
+
});
|
|
13
|
+
it("resolves relative WINGMAN_WORKDIR against cwd", ()=>{
|
|
14
|
+
const workdir = resolveFalWorkdir({
|
|
15
|
+
env: {
|
|
16
|
+
WINGMAN_WORKDIR: "projects/game"
|
|
17
|
+
},
|
|
18
|
+
cwd: "/Users/test"
|
|
19
|
+
});
|
|
20
|
+
expect(workdir).toBe("/Users/test/projects/game");
|
|
21
|
+
});
|
|
22
|
+
it("prefers explicit FAL_MCP_STATE_DIR", ()=>{
|
|
23
|
+
const stateDir = resolveFalStateDir({
|
|
24
|
+
env: {
|
|
25
|
+
WINGMAN_WORKDIR: "/tmp/session",
|
|
26
|
+
FAL_MCP_STATE_DIR: "/tmp/custom-state"
|
|
27
|
+
},
|
|
28
|
+
cwd: "/repo/apps/wingman"
|
|
29
|
+
});
|
|
30
|
+
expect(stateDir).toBe("/tmp/custom-state");
|
|
31
|
+
});
|
|
32
|
+
it("defaults state dir under working folder", ()=>{
|
|
33
|
+
const stateDir = resolveFalStateDir({
|
|
34
|
+
env: {
|
|
35
|
+
WINGMAN_WORKDIR: "/tmp/session"
|
|
36
|
+
},
|
|
37
|
+
cwd: "/repo/apps/wingman"
|
|
38
|
+
});
|
|
39
|
+
expect(stateDir).toBe("/tmp/session/.wingman/fal-ai");
|
|
40
|
+
});
|
|
41
|
+
it("prefers explicit FAL_MCP_OUTPUT_DIR", ()=>{
|
|
42
|
+
const outputDir = resolveFalOutputDir({
|
|
43
|
+
env: {
|
|
44
|
+
WINGMAN_WORKDIR: "/tmp/session",
|
|
45
|
+
FAL_MCP_OUTPUT_DIR: "assets/out"
|
|
46
|
+
},
|
|
47
|
+
cwd: "/repo/apps/wingman"
|
|
48
|
+
});
|
|
49
|
+
expect(outputDir).toBe("/tmp/session/assets/out");
|
|
50
|
+
});
|
|
51
|
+
it("defaults output dir under working folder", ()=>{
|
|
52
|
+
const outputDir = resolveFalOutputDir({
|
|
53
|
+
env: {
|
|
54
|
+
WINGMAN_WORKDIR: "/tmp/session"
|
|
55
|
+
},
|
|
56
|
+
cwd: "/repo/apps/wingman"
|
|
57
|
+
});
|
|
58
|
+
expect(outputDir).toBe("/tmp/session/generated");
|
|
59
|
+
});
|
|
60
|
+
it("resolves local relative media paths against working folder", ()=>{
|
|
61
|
+
const resolved = resolveFalLocalMediaPath("images/source.png", "/tmp/session", {
|
|
62
|
+
cwd: "/repo/apps/wingman"
|
|
63
|
+
});
|
|
64
|
+
expect(resolved).toBe("/tmp/session/images/source.png");
|
|
65
|
+
});
|
|
66
|
+
it("keeps local absolute media paths unchanged", ()=>{
|
|
67
|
+
const resolved = resolveFalLocalMediaPath("/tmp/session/images/source.png", "/tmp/session", {
|
|
68
|
+
cwd: "/repo/apps/wingman"
|
|
69
|
+
});
|
|
70
|
+
expect(resolved).toBe("/tmp/session/images/source.png");
|
|
71
|
+
});
|
|
72
|
+
});
|