@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
package/dist/providers/codex.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { createLogger } from "../logger.js";
|
|
5
5
|
const CODEX_HOME_ENV = "CODEX_HOME";
|
|
6
6
|
const CODEX_AUTH_FILE = "auth.json";
|
|
7
|
+
const CODEX_REFRESH_TOKEN_URL_OVERRIDE_ENV = "CODEX_REFRESH_TOKEN_URL_OVERRIDE";
|
|
8
|
+
const DEFAULT_CODEX_REFRESH_TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
9
|
+
const TOKEN_REFRESH_BUFFER_MS = 300000;
|
|
7
10
|
const DEFAULT_CODEX_INSTRUCTIONS = "You are Wingman, a coding assistant. Follow the user's request exactly and keep tool usage focused.";
|
|
8
11
|
const logger = createLogger();
|
|
9
12
|
function getCodexAuthPath() {
|
|
@@ -13,53 +16,77 @@ function getCodexAuthPath() {
|
|
|
13
16
|
}
|
|
14
17
|
function resolveCodexAuthFromFile() {
|
|
15
18
|
const authPath = getCodexAuthPath();
|
|
16
|
-
|
|
19
|
+
const root = readCodexAuthRoot(authPath);
|
|
20
|
+
if (!root) return {
|
|
21
|
+
authPath
|
|
22
|
+
};
|
|
23
|
+
const tokens = root.tokens && "object" == typeof root.tokens ? root.tokens : void 0;
|
|
24
|
+
const accessToken = firstNonEmptyString([
|
|
25
|
+
tokens?.access_token,
|
|
26
|
+
root.access_token
|
|
27
|
+
]);
|
|
28
|
+
const refreshToken = firstNonEmptyString([
|
|
29
|
+
tokens?.refresh_token,
|
|
30
|
+
root.refresh_token
|
|
31
|
+
]);
|
|
32
|
+
const idToken = firstNonEmptyString([
|
|
33
|
+
tokens?.id_token,
|
|
34
|
+
root.id_token
|
|
35
|
+
]);
|
|
36
|
+
const accountId = firstNonEmptyString([
|
|
37
|
+
tokens?.account_id,
|
|
38
|
+
root.account_id,
|
|
39
|
+
extractAccountIdFromIdToken(idToken)
|
|
40
|
+
]);
|
|
41
|
+
return {
|
|
42
|
+
accessToken,
|
|
43
|
+
refreshToken,
|
|
44
|
+
idToken,
|
|
45
|
+
accountId,
|
|
17
46
|
authPath
|
|
18
47
|
};
|
|
19
|
-
try {
|
|
20
|
-
const parsed = JSON.parse(readFileSync(authPath, "utf-8"));
|
|
21
|
-
if (!parsed || "object" != typeof parsed) return {
|
|
22
|
-
authPath
|
|
23
|
-
};
|
|
24
|
-
const root = parsed;
|
|
25
|
-
const tokens = root.tokens && "object" == typeof root.tokens ? root.tokens : void 0;
|
|
26
|
-
const accessToken = firstNonEmptyString([
|
|
27
|
-
tokens?.access_token,
|
|
28
|
-
root.access_token
|
|
29
|
-
]);
|
|
30
|
-
const accountId = firstNonEmptyString([
|
|
31
|
-
tokens?.account_id,
|
|
32
|
-
root.account_id
|
|
33
|
-
]);
|
|
34
|
-
return {
|
|
35
|
-
accessToken,
|
|
36
|
-
accountId,
|
|
37
|
-
authPath
|
|
38
|
-
};
|
|
39
|
-
} catch {
|
|
40
|
-
return {
|
|
41
|
-
authPath
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
48
|
}
|
|
45
49
|
function createCodexFetch(options = {}) {
|
|
46
50
|
const baseFetch = options.baseFetch || globalThis.fetch.bind(globalThis);
|
|
47
51
|
return async (input, init)=>{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
let codexAuth = await maybeRefreshCodexAuth({
|
|
53
|
+
authState: resolveCodexAuthFromFile(),
|
|
54
|
+
baseFetch
|
|
55
|
+
});
|
|
56
|
+
let accessToken = codexAuth.accessToken || options.fallbackToken;
|
|
57
|
+
let accountId = codexAuth.accountId || options.fallbackAccountId;
|
|
51
58
|
if (!accessToken) throw new Error("Codex credentials missing. Run `codex login` or set CODEX_ACCESS_TOKEN.");
|
|
52
|
-
const headers = new Headers(init?.headers || {});
|
|
53
|
-
headers.delete("authorization");
|
|
54
|
-
headers.delete("x-api-key");
|
|
55
|
-
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
56
|
-
if (accountId) headers.set("ChatGPT-Account-ID", accountId);
|
|
57
59
|
const body = withCodexRequestDefaults(init?.body);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
let response = await dispatchCodexRequest({
|
|
61
|
+
input,
|
|
62
|
+
init,
|
|
63
|
+
baseFetch,
|
|
64
|
+
accessToken,
|
|
65
|
+
accountId,
|
|
61
66
|
body
|
|
62
67
|
});
|
|
68
|
+
if ((401 === response.status || 403 === response.status) && canRetryCodexRequest(body) && codexAuth.refreshToken) {
|
|
69
|
+
const refreshed = await maybeRefreshCodexAuth({
|
|
70
|
+
authState: codexAuth,
|
|
71
|
+
baseFetch,
|
|
72
|
+
force: true
|
|
73
|
+
});
|
|
74
|
+
const refreshedAccessToken = refreshed.accessToken || options.fallbackToken;
|
|
75
|
+
const refreshedAccountId = refreshed.accountId || options.fallbackAccountId;
|
|
76
|
+
if (refreshedAccessToken && refreshedAccessToken !== accessToken) {
|
|
77
|
+
codexAuth = refreshed;
|
|
78
|
+
accessToken = refreshedAccessToken;
|
|
79
|
+
accountId = refreshedAccountId;
|
|
80
|
+
response = await dispatchCodexRequest({
|
|
81
|
+
input,
|
|
82
|
+
init,
|
|
83
|
+
baseFetch,
|
|
84
|
+
accessToken,
|
|
85
|
+
accountId,
|
|
86
|
+
body
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
63
90
|
if (!response.ok) {
|
|
64
91
|
let responseBody = "";
|
|
65
92
|
try {
|
|
@@ -75,6 +102,172 @@ function createCodexFetch(options = {}) {
|
|
|
75
102
|
return response;
|
|
76
103
|
};
|
|
77
104
|
}
|
|
105
|
+
async function dispatchCodexRequest(input) {
|
|
106
|
+
const headers = new Headers(input.init?.headers || {});
|
|
107
|
+
headers.delete("authorization");
|
|
108
|
+
headers.delete("x-api-key");
|
|
109
|
+
headers.set("Authorization", `Bearer ${input.accessToken}`);
|
|
110
|
+
if (input.accountId) headers.set("ChatGPT-Account-ID", input.accountId);
|
|
111
|
+
return input.baseFetch(input.input, {
|
|
112
|
+
...input.init,
|
|
113
|
+
headers,
|
|
114
|
+
body: input.body
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function canRetryCodexRequest(body) {
|
|
118
|
+
return null == body || "string" == typeof body || body instanceof URLSearchParams;
|
|
119
|
+
}
|
|
120
|
+
async function maybeRefreshCodexAuth(input) {
|
|
121
|
+
const { authState, baseFetch, force = false } = input;
|
|
122
|
+
if (!authState.refreshToken) return authState;
|
|
123
|
+
const shouldRefresh = force || !authState.accessToken || isTokenExpiredOrExpiring(authState.accessToken);
|
|
124
|
+
if (!shouldRefresh) return authState;
|
|
125
|
+
try {
|
|
126
|
+
const refreshed = await refreshCodexAuthToken(authState, baseFetch);
|
|
127
|
+
if (refreshed) return refreshed;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
logger.warn("Failed to refresh Codex token", {
|
|
130
|
+
error: error instanceof Error ? error.message : String(error)
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return authState;
|
|
134
|
+
}
|
|
135
|
+
async function refreshCodexAuthToken(authState, baseFetch) {
|
|
136
|
+
const refreshToken = authState.refreshToken;
|
|
137
|
+
if (!refreshToken) return;
|
|
138
|
+
const clientId = extractClientIdForRefresh(authState);
|
|
139
|
+
const tokenUrl = resolveCodexRefreshTokenUrl();
|
|
140
|
+
const form = new URLSearchParams({
|
|
141
|
+
grant_type: "refresh_token",
|
|
142
|
+
refresh_token: refreshToken
|
|
143
|
+
});
|
|
144
|
+
if (clientId) form.set("client_id", clientId);
|
|
145
|
+
const response = await baseFetch(tokenUrl, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: {
|
|
148
|
+
Accept: "application/json",
|
|
149
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
150
|
+
},
|
|
151
|
+
body: form.toString()
|
|
152
|
+
});
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
const preview = await readResponsePreview(response);
|
|
155
|
+
logger.warn("Codex token refresh failed", {
|
|
156
|
+
status: response.status,
|
|
157
|
+
statusText: response.statusText || null,
|
|
158
|
+
bodyPreview: preview || null
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const payload = await response.json();
|
|
163
|
+
const accessToken = firstNonEmptyString([
|
|
164
|
+
payload.access_token
|
|
165
|
+
]);
|
|
166
|
+
if (!accessToken) return void logger.warn("Codex token refresh failed: missing access_token");
|
|
167
|
+
const idToken = firstNonEmptyString([
|
|
168
|
+
payload.id_token,
|
|
169
|
+
authState.idToken
|
|
170
|
+
]);
|
|
171
|
+
const refreshed = {
|
|
172
|
+
accessToken,
|
|
173
|
+
refreshToken: firstNonEmptyString([
|
|
174
|
+
payload.refresh_token,
|
|
175
|
+
authState.refreshToken
|
|
176
|
+
]),
|
|
177
|
+
idToken,
|
|
178
|
+
accountId: firstNonEmptyString([
|
|
179
|
+
extractAccountIdFromIdToken(idToken),
|
|
180
|
+
authState.accountId
|
|
181
|
+
])
|
|
182
|
+
};
|
|
183
|
+
persistCodexAuthUpdate(authState.authPath, refreshed);
|
|
184
|
+
return resolveCodexAuthFromFile();
|
|
185
|
+
}
|
|
186
|
+
async function readResponsePreview(response) {
|
|
187
|
+
try {
|
|
188
|
+
const text = await response.text();
|
|
189
|
+
return text.trim().slice(0, 1200);
|
|
190
|
+
} catch {
|
|
191
|
+
return "";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function persistCodexAuthUpdate(authPath, updated) {
|
|
195
|
+
const root = readCodexAuthRoot(authPath) || {};
|
|
196
|
+
const existingTokens = root.tokens && "object" == typeof root.tokens && !Array.isArray(root.tokens) ? root.tokens : {};
|
|
197
|
+
const tokens = {
|
|
198
|
+
...existingTokens,
|
|
199
|
+
access_token: updated.accessToken
|
|
200
|
+
};
|
|
201
|
+
if (updated.refreshToken) tokens.refresh_token = updated.refreshToken;
|
|
202
|
+
if (updated.idToken) tokens.id_token = updated.idToken;
|
|
203
|
+
if (updated.accountId) tokens.account_id = updated.accountId;
|
|
204
|
+
root.tokens = tokens;
|
|
205
|
+
root.last_refresh = new Date().toISOString();
|
|
206
|
+
writeFileSync(authPath, `${JSON.stringify(root, null, 2)}\n`, "utf-8");
|
|
207
|
+
}
|
|
208
|
+
function readCodexAuthRoot(authPath) {
|
|
209
|
+
if (!existsSync(authPath)) return;
|
|
210
|
+
try {
|
|
211
|
+
const parsed = JSON.parse(readFileSync(authPath, "utf-8"));
|
|
212
|
+
if (!parsed || "object" != typeof parsed || Array.isArray(parsed)) return;
|
|
213
|
+
return parsed;
|
|
214
|
+
} catch {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function resolveCodexRefreshTokenUrl() {
|
|
219
|
+
const override = process.env[CODEX_REFRESH_TOKEN_URL_OVERRIDE_ENV];
|
|
220
|
+
if (override?.trim()) return override.trim();
|
|
221
|
+
return DEFAULT_CODEX_REFRESH_TOKEN_URL;
|
|
222
|
+
}
|
|
223
|
+
function extractClientIdForRefresh(authState) {
|
|
224
|
+
const accessTokenClaims = parseJwtPayload(authState.accessToken);
|
|
225
|
+
const accessTokenClientId = accessTokenClaims && "string" == typeof accessTokenClaims.client_id ? accessTokenClaims.client_id : void 0;
|
|
226
|
+
if (accessTokenClientId?.trim()) return accessTokenClientId.trim();
|
|
227
|
+
const idTokenClaims = parseJwtPayload(authState.idToken);
|
|
228
|
+
if (!idTokenClaims) return;
|
|
229
|
+
const aud = idTokenClaims.aud;
|
|
230
|
+
if ("string" == typeof aud && aud.trim()) return aud.trim();
|
|
231
|
+
if (Array.isArray(aud)) {
|
|
232
|
+
for (const value of aud)if ("string" == typeof value && value.trim()) return value.trim();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function isTokenExpiredOrExpiring(token) {
|
|
236
|
+
const expiryMs = extractTokenExpiryMs(token);
|
|
237
|
+
if (!expiryMs) return false;
|
|
238
|
+
return expiryMs <= Date.now() + TOKEN_REFRESH_BUFFER_MS;
|
|
239
|
+
}
|
|
240
|
+
function extractTokenExpiryMs(token) {
|
|
241
|
+
const payload = parseJwtPayload(token);
|
|
242
|
+
if (!payload || "number" != typeof payload.exp) return;
|
|
243
|
+
return 1000 * payload.exp;
|
|
244
|
+
}
|
|
245
|
+
function extractAccountIdFromIdToken(idToken) {
|
|
246
|
+
const payload = parseJwtPayload(idToken);
|
|
247
|
+
if (!payload) return;
|
|
248
|
+
const nested = payload["https://api.openai.com/auth"];
|
|
249
|
+
if (nested && "object" == typeof nested && !Array.isArray(nested)) {
|
|
250
|
+
const accountId = nested.chatgpt_account_id;
|
|
251
|
+
if ("string" == typeof accountId && accountId.trim()) return accountId.trim();
|
|
252
|
+
}
|
|
253
|
+
const direct = payload.chatgpt_account_id;
|
|
254
|
+
if ("string" == typeof direct && direct.trim()) return direct.trim();
|
|
255
|
+
}
|
|
256
|
+
function parseJwtPayload(token) {
|
|
257
|
+
if (!token) return;
|
|
258
|
+
const parts = token.split(".");
|
|
259
|
+
if (3 !== parts.length) return;
|
|
260
|
+
try {
|
|
261
|
+
const payload = parts[1];
|
|
262
|
+
const normalized = payload + "=".repeat((4 - payload.length % 4) % 4);
|
|
263
|
+
const decoded = Buffer.from(normalized, "base64url").toString("utf-8");
|
|
264
|
+
const parsed = JSON.parse(decoded);
|
|
265
|
+
if (!parsed || "object" != typeof parsed || Array.isArray(parsed)) return;
|
|
266
|
+
return parsed;
|
|
267
|
+
} catch {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
78
271
|
function withCodexRequestDefaults(body) {
|
|
79
272
|
if ("string" != typeof body || !body.trim()) return body;
|
|
80
273
|
try {
|
|
@@ -348,42 +348,6 @@ const parseConfig = (input)=>{
|
|
|
348
348
|
})).toBeUndefined();
|
|
349
349
|
});
|
|
350
350
|
});
|
|
351
|
-
(0, external_vitest_namespaceObject.describe)("evaluateStreamingCompletion", ()=>{
|
|
352
|
-
(0, external_vitest_namespaceObject.it)("blocks with stream_error when no assistant text and stream error exists", ()=>{
|
|
353
|
-
const result = (0, agentInvoker_cjs_namespaceObject.evaluateStreamingCompletion)({
|
|
354
|
-
sawAssistantText: false,
|
|
355
|
-
fallbackText: void 0,
|
|
356
|
-
streamErrorMessage: "provider timeout"
|
|
357
|
-
});
|
|
358
|
-
(0, external_vitest_namespaceObject.expect)(result).toEqual({
|
|
359
|
-
status: "blocked",
|
|
360
|
-
reason: "stream_error",
|
|
361
|
-
message: "Model call failed: provider timeout"
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
(0, external_vitest_namespaceObject.it)("blocks with empty_stream_response when no text or fallback is present", ()=>{
|
|
365
|
-
const result = (0, agentInvoker_cjs_namespaceObject.evaluateStreamingCompletion)({
|
|
366
|
-
sawAssistantText: false,
|
|
367
|
-
fallbackText: void 0,
|
|
368
|
-
streamErrorMessage: void 0
|
|
369
|
-
});
|
|
370
|
-
(0, external_vitest_namespaceObject.expect)(result).toEqual({
|
|
371
|
-
status: "blocked",
|
|
372
|
-
reason: "empty_stream_response",
|
|
373
|
-
message: "Model completed without a response. Check provider logs for request errors."
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
(0, external_vitest_namespaceObject.it)("returns ok when assistant text exists", ()=>{
|
|
377
|
-
const result = (0, agentInvoker_cjs_namespaceObject.evaluateStreamingCompletion)({
|
|
378
|
-
sawAssistantText: true,
|
|
379
|
-
fallbackText: void 0,
|
|
380
|
-
streamErrorMessage: void 0
|
|
381
|
-
});
|
|
382
|
-
(0, external_vitest_namespaceObject.expect)(result).toEqual({
|
|
383
|
-
status: "ok"
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
});
|
|
387
351
|
(0, external_vitest_namespaceObject.describe)("LangGraph lifecycle termination", ()=>{
|
|
388
352
|
(0, external_vitest_namespaceObject.it)("tracks root LangGraph run id from parentless on_chain_start", ()=>{
|
|
389
353
|
const rootRunId = (0, agentInvoker_cjs_namespaceObject.trackRootLangGraphRunId)(void 0, {
|
|
@@ -441,12 +405,67 @@ const parseConfig = (input)=>{
|
|
|
441
405
|
run_id: "root-run",
|
|
442
406
|
parent_ids: []
|
|
443
407
|
}, "root-run")).toBe(false);
|
|
408
|
+
});
|
|
409
|
+
(0, external_vitest_namespaceObject.it)("treats root LangGraph on_chain_end as terminal even without tracked run id", ()=>{
|
|
444
410
|
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.isRootLangGraphTerminalEvent)({
|
|
445
411
|
event: "on_chain_end",
|
|
446
412
|
name: "LangGraph",
|
|
447
413
|
run_id: "root-run",
|
|
448
414
|
parent_ids: []
|
|
449
|
-
}, void 0)).toBe(
|
|
415
|
+
}, void 0)).toBe(true);
|
|
416
|
+
});
|
|
417
|
+
(0, external_vitest_namespaceObject.it)("treats root LangGraph on_chain_end as terminal when run id is missing", ()=>{
|
|
418
|
+
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.isRootLangGraphTerminalEvent)({
|
|
419
|
+
event: "on_chain_end",
|
|
420
|
+
name: "LangGraph",
|
|
421
|
+
parent_ids: []
|
|
422
|
+
}, "root-run")).toBe(true);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
(0, external_vitest_namespaceObject.describe)("emitCompletionAndContinuePostProcessing", ()=>{
|
|
426
|
+
(0, external_vitest_namespaceObject.it)("emits completion before post-processing resolves", ()=>{
|
|
427
|
+
const callOrder = [];
|
|
428
|
+
let resolvePostProcess;
|
|
429
|
+
const postProcess = ()=>new Promise((resolve)=>{
|
|
430
|
+
callOrder.push("post-process-start");
|
|
431
|
+
resolvePostProcess = resolve;
|
|
432
|
+
});
|
|
433
|
+
(0, agentInvoker_cjs_namespaceObject.emitCompletionAndContinuePostProcessing)({
|
|
434
|
+
outputManager: {
|
|
435
|
+
emitAgentComplete: ()=>{
|
|
436
|
+
callOrder.push("emit-complete");
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
result: {
|
|
440
|
+
ok: true
|
|
441
|
+
},
|
|
442
|
+
postProcess
|
|
443
|
+
});
|
|
444
|
+
(0, external_vitest_namespaceObject.expect)(callOrder).toEqual([
|
|
445
|
+
"emit-complete",
|
|
446
|
+
"post-process-start"
|
|
447
|
+
]);
|
|
448
|
+
resolvePostProcess?.();
|
|
449
|
+
});
|
|
450
|
+
(0, external_vitest_namespaceObject.it)("logs and swallows post-processing failures", async ()=>{
|
|
451
|
+
const debug = external_vitest_namespaceObject.vi.fn();
|
|
452
|
+
(0, agentInvoker_cjs_namespaceObject.emitCompletionAndContinuePostProcessing)({
|
|
453
|
+
outputManager: {
|
|
454
|
+
emitAgentComplete: external_vitest_namespaceObject.vi.fn()
|
|
455
|
+
},
|
|
456
|
+
result: {
|
|
457
|
+
ok: true
|
|
458
|
+
},
|
|
459
|
+
postProcess: async ()=>{
|
|
460
|
+
throw new Error("materialization failed");
|
|
461
|
+
},
|
|
462
|
+
logger: {
|
|
463
|
+
debug
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
await Promise.resolve();
|
|
467
|
+
await Promise.resolve();
|
|
468
|
+
(0, external_vitest_namespaceObject.expect)(debug).toHaveBeenCalledWith("Failed post-completion processing for streamed agent response", external_vitest_namespaceObject.expect.any(Error));
|
|
450
469
|
});
|
|
451
470
|
});
|
|
452
471
|
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { validateConfig } from "../cli/config/schema.js";
|
|
3
|
-
import { chunkHasAssistantText, configureDeepAgentSummarizationMiddleware, detectStreamErrorMessage, detectToolEventContext,
|
|
3
|
+
import { chunkHasAssistantText, configureDeepAgentSummarizationMiddleware, detectStreamErrorMessage, detectToolEventContext, emitCompletionAndContinuePostProcessing, isRootLangGraphTerminalEvent, resolveHumanInTheLoopSettings, resolveModelRetryMiddlewareSettings, resolveSummarizationMiddlewareSettings, resolveToolRetryMiddlewareSettings, selectStreamingFallbackText, trackRootLangGraphRunId } from "../cli/core/agentInvoker.js";
|
|
4
4
|
const parseConfig = (input)=>{
|
|
5
5
|
const result = validateConfig(input);
|
|
6
6
|
if (!result.success || !result.data) throw new Error(result.error || "Expected config validation to succeed");
|
|
@@ -346,42 +346,6 @@ describe("detectStreamErrorMessage", ()=>{
|
|
|
346
346
|
})).toBeUndefined();
|
|
347
347
|
});
|
|
348
348
|
});
|
|
349
|
-
describe("evaluateStreamingCompletion", ()=>{
|
|
350
|
-
it("blocks with stream_error when no assistant text and stream error exists", ()=>{
|
|
351
|
-
const result = evaluateStreamingCompletion({
|
|
352
|
-
sawAssistantText: false,
|
|
353
|
-
fallbackText: void 0,
|
|
354
|
-
streamErrorMessage: "provider timeout"
|
|
355
|
-
});
|
|
356
|
-
expect(result).toEqual({
|
|
357
|
-
status: "blocked",
|
|
358
|
-
reason: "stream_error",
|
|
359
|
-
message: "Model call failed: provider timeout"
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
it("blocks with empty_stream_response when no text or fallback is present", ()=>{
|
|
363
|
-
const result = evaluateStreamingCompletion({
|
|
364
|
-
sawAssistantText: false,
|
|
365
|
-
fallbackText: void 0,
|
|
366
|
-
streamErrorMessage: void 0
|
|
367
|
-
});
|
|
368
|
-
expect(result).toEqual({
|
|
369
|
-
status: "blocked",
|
|
370
|
-
reason: "empty_stream_response",
|
|
371
|
-
message: "Model completed without a response. Check provider logs for request errors."
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
it("returns ok when assistant text exists", ()=>{
|
|
375
|
-
const result = evaluateStreamingCompletion({
|
|
376
|
-
sawAssistantText: true,
|
|
377
|
-
fallbackText: void 0,
|
|
378
|
-
streamErrorMessage: void 0
|
|
379
|
-
});
|
|
380
|
-
expect(result).toEqual({
|
|
381
|
-
status: "ok"
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
});
|
|
385
349
|
describe("LangGraph lifecycle termination", ()=>{
|
|
386
350
|
it("tracks root LangGraph run id from parentless on_chain_start", ()=>{
|
|
387
351
|
const rootRunId = trackRootLangGraphRunId(void 0, {
|
|
@@ -439,11 +403,66 @@ describe("LangGraph lifecycle termination", ()=>{
|
|
|
439
403
|
run_id: "root-run",
|
|
440
404
|
parent_ids: []
|
|
441
405
|
}, "root-run")).toBe(false);
|
|
406
|
+
});
|
|
407
|
+
it("treats root LangGraph on_chain_end as terminal even without tracked run id", ()=>{
|
|
442
408
|
expect(isRootLangGraphTerminalEvent({
|
|
443
409
|
event: "on_chain_end",
|
|
444
410
|
name: "LangGraph",
|
|
445
411
|
run_id: "root-run",
|
|
446
412
|
parent_ids: []
|
|
447
|
-
}, void 0)).toBe(
|
|
413
|
+
}, void 0)).toBe(true);
|
|
414
|
+
});
|
|
415
|
+
it("treats root LangGraph on_chain_end as terminal when run id is missing", ()=>{
|
|
416
|
+
expect(isRootLangGraphTerminalEvent({
|
|
417
|
+
event: "on_chain_end",
|
|
418
|
+
name: "LangGraph",
|
|
419
|
+
parent_ids: []
|
|
420
|
+
}, "root-run")).toBe(true);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
describe("emitCompletionAndContinuePostProcessing", ()=>{
|
|
424
|
+
it("emits completion before post-processing resolves", ()=>{
|
|
425
|
+
const callOrder = [];
|
|
426
|
+
let resolvePostProcess;
|
|
427
|
+
const postProcess = ()=>new Promise((resolve)=>{
|
|
428
|
+
callOrder.push("post-process-start");
|
|
429
|
+
resolvePostProcess = resolve;
|
|
430
|
+
});
|
|
431
|
+
emitCompletionAndContinuePostProcessing({
|
|
432
|
+
outputManager: {
|
|
433
|
+
emitAgentComplete: ()=>{
|
|
434
|
+
callOrder.push("emit-complete");
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
result: {
|
|
438
|
+
ok: true
|
|
439
|
+
},
|
|
440
|
+
postProcess
|
|
441
|
+
});
|
|
442
|
+
expect(callOrder).toEqual([
|
|
443
|
+
"emit-complete",
|
|
444
|
+
"post-process-start"
|
|
445
|
+
]);
|
|
446
|
+
resolvePostProcess?.();
|
|
447
|
+
});
|
|
448
|
+
it("logs and swallows post-processing failures", async ()=>{
|
|
449
|
+
const debug = vi.fn();
|
|
450
|
+
emitCompletionAndContinuePostProcessing({
|
|
451
|
+
outputManager: {
|
|
452
|
+
emitAgentComplete: vi.fn()
|
|
453
|
+
},
|
|
454
|
+
result: {
|
|
455
|
+
ok: true
|
|
456
|
+
},
|
|
457
|
+
postProcess: async ()=>{
|
|
458
|
+
throw new Error("materialization failed");
|
|
459
|
+
},
|
|
460
|
+
logger: {
|
|
461
|
+
debug
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
await Promise.resolve();
|
|
465
|
+
await Promise.resolve();
|
|
466
|
+
expect(debug).toHaveBeenCalledWith("Failed post-completion processing for streamed agent response", expect.any(Error));
|
|
448
467
|
});
|
|
449
468
|
});
|
|
@@ -21,6 +21,8 @@ var __webpack_require__ = {};
|
|
|
21
21
|
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
22
22
|
})();
|
|
23
23
|
var __webpack_exports__ = {};
|
|
24
|
+
const external_node_fs_namespaceObject = require("node:fs");
|
|
25
|
+
const external_node_os_namespaceObject = require("node:os");
|
|
24
26
|
const external_node_path_namespaceObject = require("node:path");
|
|
25
27
|
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
26
28
|
const external_vitest_namespaceObject = require("vitest");
|
|
@@ -83,6 +85,21 @@ const agentInvoker_cjs_namespaceObject = require("../cli/core/agentInvoker.cjs")
|
|
|
83
85
|
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.resolveExecutionWorkspace)(workspace, absolute)).toBe(external_node_path_default().normalize(absolute));
|
|
84
86
|
});
|
|
85
87
|
});
|
|
88
|
+
(0, external_vitest_namespaceObject.describe)("resolveAgentExecutionWorkspace", ()=>{
|
|
89
|
+
const workspace = external_node_path_default().resolve("workspace");
|
|
90
|
+
(0, external_vitest_namespaceObject.it)("prefers explicit workdir over default output dir", ()=>{
|
|
91
|
+
const workdir = external_node_path_default().resolve("outside", "session-output");
|
|
92
|
+
const defaultOutputDir = external_node_path_default().resolve("outside", "default-output");
|
|
93
|
+
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.resolveAgentExecutionWorkspace)(workspace, workdir, defaultOutputDir)).toBe(external_node_path_default().normalize(workdir));
|
|
94
|
+
});
|
|
95
|
+
(0, external_vitest_namespaceObject.it)("uses default output dir when session workdir is unset", ()=>{
|
|
96
|
+
const defaultOutputDir = external_node_path_default().resolve("outside", "default-output");
|
|
97
|
+
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.resolveAgentExecutionWorkspace)(workspace, null, defaultOutputDir)).toBe(external_node_path_default().normalize(defaultOutputDir));
|
|
98
|
+
});
|
|
99
|
+
(0, external_vitest_namespaceObject.it)("falls back to workspace when neither workdir nor default output dir exist", ()=>{
|
|
100
|
+
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.resolveAgentExecutionWorkspace)(workspace, null, null)).toBe(external_node_path_default().normalize(workspace));
|
|
101
|
+
});
|
|
102
|
+
});
|
|
86
103
|
(0, external_vitest_namespaceObject.describe)("toWorkspaceAliasVirtualPath", ()=>{
|
|
87
104
|
(0, external_vitest_namespaceObject.it)("builds an alias path for absolute workspaces", ()=>{
|
|
88
105
|
const absolute = external_node_path_default().resolve("outside", "session-output");
|
|
@@ -94,6 +111,39 @@ const agentInvoker_cjs_namespaceObject = require("../cli/core/agentInvoker.cjs")
|
|
|
94
111
|
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.toWorkspaceAliasVirtualPath)("relative/workspace")).toBeNull();
|
|
95
112
|
});
|
|
96
113
|
});
|
|
114
|
+
(0, external_vitest_namespaceObject.describe)("resolveAgentMemorySources", ()=>{
|
|
115
|
+
const tempDirs = [];
|
|
116
|
+
(0, external_vitest_namespaceObject.afterEach)(()=>{
|
|
117
|
+
for (const dir of tempDirs)(0, external_node_fs_namespaceObject.rmSync)(dir, {
|
|
118
|
+
recursive: true,
|
|
119
|
+
force: true
|
|
120
|
+
});
|
|
121
|
+
tempDirs.length = 0;
|
|
122
|
+
});
|
|
123
|
+
(0, external_vitest_namespaceObject.it)("returns /AGENTS.md when present in execution workspace", ()=>{
|
|
124
|
+
const workspace = (0, external_node_fs_namespaceObject.mkdtempSync)(external_node_path_default().join((0, external_node_os_namespaceObject.tmpdir)(), "wingman-memory-"));
|
|
125
|
+
tempDirs.push(workspace);
|
|
126
|
+
(0, external_node_fs_namespaceObject.writeFileSync)(external_node_path_default().join(workspace, "AGENTS.md"), "# Agent Memory");
|
|
127
|
+
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.resolveAgentMemorySources)(workspace)).toEqual([
|
|
128
|
+
"/AGENTS.md"
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
(0, external_vitest_namespaceObject.it)("returns no sources when AGENTS.md is absent", ()=>{
|
|
132
|
+
const workspace = (0, external_node_fs_namespaceObject.mkdtempSync)(external_node_path_default().join((0, external_node_os_namespaceObject.tmpdir)(), "wingman-memory-"));
|
|
133
|
+
tempDirs.push(workspace);
|
|
134
|
+
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.resolveAgentMemorySources)(workspace)).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
(0, external_vitest_namespaceObject.it)("ignores .deepagents/AGENTS.md when top-level AGENTS.md is missing", ()=>{
|
|
137
|
+
const workspace = (0, external_node_fs_namespaceObject.mkdtempSync)(external_node_path_default().join((0, external_node_os_namespaceObject.tmpdir)(), "wingman-memory-"));
|
|
138
|
+
tempDirs.push(workspace);
|
|
139
|
+
const deepagentsDir = external_node_path_default().join(workspace, ".deepagents");
|
|
140
|
+
(0, external_node_fs_namespaceObject.mkdirSync)(deepagentsDir, {
|
|
141
|
+
recursive: true
|
|
142
|
+
});
|
|
143
|
+
(0, external_node_fs_namespaceObject.writeFileSync)(external_node_path_default().join(deepagentsDir, "AGENTS.md"), "# Nested Memory");
|
|
144
|
+
(0, external_vitest_namespaceObject.expect)((0, agentInvoker_cjs_namespaceObject.resolveAgentMemorySources)(workspace)).toEqual([]);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
97
147
|
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
98
148
|
Object.defineProperty(exports, '__esModule', {
|
|
99
149
|
value: true
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
1
3
|
import node_path from "node:path";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { OUTPUT_VIRTUAL_PATH, WORKDIR_VIRTUAL_PATH, resolveExecutionWorkspace, resolveExternalOutputMount, toWorkspaceAliasVirtualPath } from "../cli/core/agentInvoker.js";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { OUTPUT_VIRTUAL_PATH, WORKDIR_VIRTUAL_PATH, resolveAgentExecutionWorkspace, resolveAgentMemorySources, resolveExecutionWorkspace, resolveExternalOutputMount, toWorkspaceAliasVirtualPath } from "../cli/core/agentInvoker.js";
|
|
4
6
|
describe("resolveExternalOutputMount", ()=>{
|
|
5
7
|
const workspace = node_path.resolve("workspace");
|
|
6
8
|
it("mounts external workdir paths", ()=>{
|
|
@@ -59,6 +61,21 @@ describe("resolveExecutionWorkspace", ()=>{
|
|
|
59
61
|
expect(resolveExecutionWorkspace(workspace, absolute)).toBe(node_path.normalize(absolute));
|
|
60
62
|
});
|
|
61
63
|
});
|
|
64
|
+
describe("resolveAgentExecutionWorkspace", ()=>{
|
|
65
|
+
const workspace = node_path.resolve("workspace");
|
|
66
|
+
it("prefers explicit workdir over default output dir", ()=>{
|
|
67
|
+
const workdir = node_path.resolve("outside", "session-output");
|
|
68
|
+
const defaultOutputDir = node_path.resolve("outside", "default-output");
|
|
69
|
+
expect(resolveAgentExecutionWorkspace(workspace, workdir, defaultOutputDir)).toBe(node_path.normalize(workdir));
|
|
70
|
+
});
|
|
71
|
+
it("uses default output dir when session workdir is unset", ()=>{
|
|
72
|
+
const defaultOutputDir = node_path.resolve("outside", "default-output");
|
|
73
|
+
expect(resolveAgentExecutionWorkspace(workspace, null, defaultOutputDir)).toBe(node_path.normalize(defaultOutputDir));
|
|
74
|
+
});
|
|
75
|
+
it("falls back to workspace when neither workdir nor default output dir exist", ()=>{
|
|
76
|
+
expect(resolveAgentExecutionWorkspace(workspace, null, null)).toBe(node_path.normalize(workspace));
|
|
77
|
+
});
|
|
78
|
+
});
|
|
62
79
|
describe("toWorkspaceAliasVirtualPath", ()=>{
|
|
63
80
|
it("builds an alias path for absolute workspaces", ()=>{
|
|
64
81
|
const absolute = node_path.resolve("outside", "session-output");
|
|
@@ -70,3 +87,36 @@ describe("toWorkspaceAliasVirtualPath", ()=>{
|
|
|
70
87
|
expect(toWorkspaceAliasVirtualPath("relative/workspace")).toBeNull();
|
|
71
88
|
});
|
|
72
89
|
});
|
|
90
|
+
describe("resolveAgentMemorySources", ()=>{
|
|
91
|
+
const tempDirs = [];
|
|
92
|
+
afterEach(()=>{
|
|
93
|
+
for (const dir of tempDirs)rmSync(dir, {
|
|
94
|
+
recursive: true,
|
|
95
|
+
force: true
|
|
96
|
+
});
|
|
97
|
+
tempDirs.length = 0;
|
|
98
|
+
});
|
|
99
|
+
it("returns /AGENTS.md when present in execution workspace", ()=>{
|
|
100
|
+
const workspace = mkdtempSync(node_path.join(tmpdir(), "wingman-memory-"));
|
|
101
|
+
tempDirs.push(workspace);
|
|
102
|
+
writeFileSync(node_path.join(workspace, "AGENTS.md"), "# Agent Memory");
|
|
103
|
+
expect(resolveAgentMemorySources(workspace)).toEqual([
|
|
104
|
+
"/AGENTS.md"
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
107
|
+
it("returns no sources when AGENTS.md is absent", ()=>{
|
|
108
|
+
const workspace = mkdtempSync(node_path.join(tmpdir(), "wingman-memory-"));
|
|
109
|
+
tempDirs.push(workspace);
|
|
110
|
+
expect(resolveAgentMemorySources(workspace)).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
it("ignores .deepagents/AGENTS.md when top-level AGENTS.md is missing", ()=>{
|
|
113
|
+
const workspace = mkdtempSync(node_path.join(tmpdir(), "wingman-memory-"));
|
|
114
|
+
tempDirs.push(workspace);
|
|
115
|
+
const deepagentsDir = node_path.join(workspace, ".deepagents");
|
|
116
|
+
mkdirSync(deepagentsDir, {
|
|
117
|
+
recursive: true
|
|
118
|
+
});
|
|
119
|
+
writeFileSync(node_path.join(deepagentsDir, "AGENTS.md"), "# Nested Memory");
|
|
120
|
+
expect(resolveAgentMemorySources(workspace)).toEqual([]);
|
|
121
|
+
});
|
|
122
|
+
});
|