@percena/weft 0.4.0-next.0 → 0.4.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/action-bridge.cjs +323 -0
  2. package/dist/action-bridge.d.cts +8 -0
  3. package/dist/action-bridge.d.ts +8 -0
  4. package/dist/action-bridge.js +291 -0
  5. package/dist/chat.cjs +3851 -36
  6. package/dist/chat.d.cts +1 -874
  7. package/dist/chat.d.ts +1 -874
  8. package/dist/chat.js +3870 -36
  9. package/dist/index.cjs +12085 -1488
  10. package/dist/index.d.cts +553 -240
  11. package/dist/index.d.ts +553 -240
  12. package/dist/index.js +12118 -1447
  13. package/dist/providers-flitro.cjs +50 -307
  14. package/dist/providers-flitro.d.cts +75 -52
  15. package/dist/providers-flitro.d.ts +75 -52
  16. package/dist/providers-flitro.js +45 -287
  17. package/dist/styles/fonts/KaTeX_AMS-Regular.ttf +0 -0
  18. package/dist/styles/fonts/KaTeX_AMS-Regular.woff +0 -0
  19. package/dist/styles/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  20. package/dist/styles/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  21. package/dist/styles/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  22. package/dist/styles/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  23. package/dist/styles/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  24. package/dist/styles/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  25. package/dist/styles/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  26. package/dist/styles/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  27. package/dist/styles/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  28. package/dist/styles/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  29. package/dist/styles/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  30. package/dist/styles/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  31. package/dist/styles/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  32. package/dist/styles/fonts/KaTeX_Main-Bold.ttf +0 -0
  33. package/dist/styles/fonts/KaTeX_Main-Bold.woff +0 -0
  34. package/dist/styles/fonts/KaTeX_Main-Bold.woff2 +0 -0
  35. package/dist/styles/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  36. package/dist/styles/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  37. package/dist/styles/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  38. package/dist/styles/fonts/KaTeX_Main-Italic.ttf +0 -0
  39. package/dist/styles/fonts/KaTeX_Main-Italic.woff +0 -0
  40. package/dist/styles/fonts/KaTeX_Main-Italic.woff2 +0 -0
  41. package/dist/styles/fonts/KaTeX_Main-Regular.ttf +0 -0
  42. package/dist/styles/fonts/KaTeX_Main-Regular.woff +0 -0
  43. package/dist/styles/fonts/KaTeX_Main-Regular.woff2 +0 -0
  44. package/dist/styles/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  45. package/dist/styles/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  46. package/dist/styles/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  47. package/dist/styles/fonts/KaTeX_Math-Italic.ttf +0 -0
  48. package/dist/styles/fonts/KaTeX_Math-Italic.woff +0 -0
  49. package/dist/styles/fonts/KaTeX_Math-Italic.woff2 +0 -0
  50. package/dist/styles/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  51. package/dist/styles/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  52. package/dist/styles/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  53. package/dist/styles/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  54. package/dist/styles/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  55. package/dist/styles/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  56. package/dist/styles/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  57. package/dist/styles/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  58. package/dist/styles/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  59. package/dist/styles/fonts/KaTeX_Script-Regular.ttf +0 -0
  60. package/dist/styles/fonts/KaTeX_Script-Regular.woff +0 -0
  61. package/dist/styles/fonts/KaTeX_Script-Regular.woff2 +0 -0
  62. package/dist/styles/fonts/KaTeX_Size1-Regular.ttf +0 -0
  63. package/dist/styles/fonts/KaTeX_Size1-Regular.woff +0 -0
  64. package/dist/styles/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  65. package/dist/styles/fonts/KaTeX_Size2-Regular.ttf +0 -0
  66. package/dist/styles/fonts/KaTeX_Size2-Regular.woff +0 -0
  67. package/dist/styles/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  68. package/dist/styles/fonts/KaTeX_Size3-Regular.ttf +0 -0
  69. package/dist/styles/fonts/KaTeX_Size3-Regular.woff +0 -0
  70. package/dist/styles/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  71. package/dist/styles/fonts/KaTeX_Size4-Regular.ttf +0 -0
  72. package/dist/styles/fonts/KaTeX_Size4-Regular.woff +0 -0
  73. package/dist/styles/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  74. package/dist/styles/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  75. package/dist/styles/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  76. package/dist/styles/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  77. package/dist/styles/index.css +2 -212
  78. package/package.json +23 -49
  79. package/dist/auth.cjs +0 -241
  80. package/dist/auth.d.cts +0 -21
  81. package/dist/auth.d.ts +0 -21
  82. package/dist/auth.js +0 -208
  83. package/dist/automations.cjs +0 -3044
  84. package/dist/automations.d.cts +0 -4774
  85. package/dist/automations.d.ts +0 -4774
  86. package/dist/automations.js +0 -2965
  87. package/dist/factory.cjs +0 -5057
  88. package/dist/factory.d.cts +0 -7909
  89. package/dist/factory.d.ts +0 -7909
  90. package/dist/factory.js +0 -5008
  91. package/dist/local-runtime.cjs +0 -1387
  92. package/dist/local-runtime.d.cts +0 -3314
  93. package/dist/local-runtime.d.ts +0 -3314
  94. package/dist/local-runtime.js +0 -1345
  95. package/dist/providers.cjs +0 -6154
  96. package/dist/providers.d.cts +0 -6024
  97. package/dist/providers.d.ts +0 -6024
  98. package/dist/providers.js +0 -6110
  99. package/dist/server.cjs +0 -9137
  100. package/dist/server.d.cts +0 -9868
  101. package/dist/server.d.ts +0 -9868
  102. package/dist/server.js +0 -9118
  103. package/dist/skills-browser.cjs +0 -118
  104. package/dist/skills-browser.d.cts +0 -105
  105. package/dist/skills-browser.d.ts +0 -105
  106. package/dist/skills-browser.js +0 -88
  107. package/dist/skills.cjs +0 -505
  108. package/dist/skills.d.cts +0 -218
  109. package/dist/skills.d.ts +0 -218
  110. package/dist/skills.js +0 -458
  111. package/dist/sources.cjs +0 -1710
  112. package/dist/sources.d.cts +0 -3978
  113. package/dist/sources.d.ts +0 -3978
  114. package/dist/sources.js +0 -1675
package/dist/sources.cjs DELETED
@@ -1,1710 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __esm = (fn, res) => function __init() {
7
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
- };
9
- var __export = (target, all) => {
10
- for (var name in all)
11
- __defProp(target, name, { get: all[name], enumerable: true });
12
- };
13
- var __copyProps = (to, from, except, desc) => {
14
- if (from && typeof from === "object" || typeof from === "function") {
15
- for (let key of __getOwnPropNames(from))
16
- if (!__hasOwnProp.call(to, key) && key !== except)
17
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
- }
19
- return to;
20
- };
21
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
-
23
- // ../packages/sources/dist/chunk-QOB7RMSO.js
24
- function validateIconValue(value, context) {
25
- if (value === void 0 || value === null) return void 0;
26
- if (typeof value !== "string") return void 0;
27
- if (/^[\p{Emoji}\u200d\ufe0f]+$/u.test(value)) return value;
28
- if (value.startsWith("http://") || value.startsWith("https://")) return value;
29
- console.warn(`[${context}] Invalid icon value: "${value}". Only emoji and URLs are supported.`);
30
- return void 0;
31
- }
32
- function findIconFile(_dirPath) {
33
- return void 0;
34
- }
35
- async function downloadIcon(_dirPath, _iconUrl, _context) {
36
- return null;
37
- }
38
- function needsIconDownload(icon, iconPath) {
39
- if (!icon) return false;
40
- if (icon.startsWith("http://") || icon.startsWith("https://")) {
41
- return !iconPath;
42
- }
43
- return false;
44
- }
45
- function isIconUrl(value) {
46
- return value.startsWith("http://") || value.startsWith("https://");
47
- }
48
- var init_chunk_QOB7RMSO = __esm({
49
- "../packages/sources/dist/chunk-QOB7RMSO.js"() {
50
- "use strict";
51
- }
52
- });
53
-
54
- // ../packages/sources/dist/chunk-DGUM43GV.js
55
- var __require;
56
- var init_chunk_DGUM43GV = __esm({
57
- "../packages/sources/dist/chunk-DGUM43GV.js"() {
58
- "use strict";
59
- __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
60
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
61
- }) : x)(function(x) {
62
- if (typeof require !== "undefined") return require.apply(this, arguments);
63
- throw Error('Dynamic require of "' + x + '" is not supported');
64
- });
65
- }
66
- });
67
-
68
- // ../packages/sources/dist/logo-V5LSKQOU.js
69
- var logo_V5LSKQOU_exports = {};
70
- __export(logo_V5LSKQOU_exports, {
71
- deriveServiceUrl: () => deriveServiceUrl,
72
- getHighQualityLogoUrl: () => getHighQualityLogoUrl
73
- });
74
- function deriveServiceUrl(_input) {
75
- return void 0;
76
- }
77
- async function getHighQualityLogoUrl(_serviceUrl, _provider) {
78
- return void 0;
79
- }
80
- var init_logo_V5LSKQOU = __esm({
81
- "../packages/sources/dist/logo-V5LSKQOU.js"() {
82
- "use strict";
83
- init_chunk_DGUM43GV();
84
- }
85
- });
86
-
87
- // ../packages/sources/dist/icon-GXJSPBS6.js
88
- var icon_GXJSPBS6_exports = {};
89
- __export(icon_GXJSPBS6_exports, {
90
- downloadIcon: () => downloadIcon,
91
- findIconFile: () => findIconFile,
92
- isIconUrl: () => isIconUrl,
93
- needsIconDownload: () => needsIconDownload,
94
- validateIconValue: () => validateIconValue
95
- });
96
- var init_icon_GXJSPBS6 = __esm({
97
- "../packages/sources/dist/icon-GXJSPBS6.js"() {
98
- "use strict";
99
- init_chunk_QOB7RMSO();
100
- init_chunk_DGUM43GV();
101
- }
102
- });
103
-
104
- // src/sources.ts
105
- var sources_exports = {};
106
- __export(sources_exports, {
107
- EncryptedCredentialBackend: () => EncryptedCredentialBackend,
108
- InMemoryCredentialManager: () => InMemoryCredentialManager,
109
- SourceCredentialManager: () => SourceCredentialManager,
110
- StubSourceOAuthProvider: () => StubSourceOAuthProvider,
111
- createSource: () => createSource,
112
- createSourceActivationPlan: () => createSourceActivationPlan,
113
- createSourceRuntimeAssemblyPlan: () => createSourceRuntimeAssemblyPlan,
114
- createSourceRuntimeProviderOptions: () => createSourceRuntimeProviderOptions,
115
- createSourceToolAssemblyPlan: () => createSourceToolAssemblyPlan,
116
- deleteSource: () => deleteSource,
117
- getBuiltinSources: () => getBuiltinSources,
118
- getCredentialManager: () => getCredentialManager,
119
- getMachineId: () => getMachineId,
120
- isMultiHeaderCredential: () => isMultiHeaderCredential,
121
- loadAllSources: () => loadAllSources,
122
- loadSource: () => loadSource,
123
- loadSourceConfig: () => loadSourceConfig,
124
- providerAuthToSourceStatus: () => providerAuthToSourceStatus,
125
- resetCredentialManager: () => resetCredentialManager,
126
- saveSourceConfig: () => saveSourceConfig,
127
- sourceExists: () => sourceExists,
128
- validateSourceConfig: () => validateSourceConfig
129
- });
130
- module.exports = __toCommonJS(sources_exports);
131
-
132
- // ../packages/sources/dist/index.js
133
- init_chunk_QOB7RMSO();
134
- init_chunk_DGUM43GV();
135
-
136
- // ../packages/runtime-core/dist/index.js
137
- function sanitizeProviderSourceTools(sourceTools) {
138
- if (!sourceTools || sourceTools.length === 0) return [];
139
- const sanitized = [];
140
- for (const sourceTool of sourceTools) {
141
- if (sourceTool.kind === "api-source") {
142
- const defaultHeaders = sourceTool.defaultHeaders ? copyNonCredentialStringRecord(sourceTool.defaultHeaders) : void 0;
143
- sanitized.push({
144
- kind: "api-source",
145
- sourceSlug: sourceTool.sourceSlug,
146
- baseUrl: sourceTool.baseUrl,
147
- authType: sourceTool.authType,
148
- ...defaultHeaders && Object.keys(defaultHeaders).length > 0 ? { defaultHeaders } : {},
149
- ...sourceTool.credentialRef ? { credentialRef: sanitizeSourceCredentialRef(sourceTool.credentialRef) } : {}
150
- });
151
- continue;
152
- }
153
- if (sourceTool.kind === "local-source") {
154
- sanitized.push({
155
- kind: "local-source",
156
- sourceSlug: sourceTool.sourceSlug,
157
- path: sourceTool.path,
158
- ...sourceTool.format ? { format: sourceTool.format } : {}
159
- });
160
- continue;
161
- }
162
- if (sourceTool.kind === "mcp-server") {
163
- const env = sourceTool.env ? copyNonCredentialStringRecord(sourceTool.env) : void 0;
164
- const headers = sourceTool.headers ? copyNonCredentialStringRecord(sourceTool.headers) : void 0;
165
- sanitized.push({
166
- kind: "mcp-server",
167
- sourceSlug: sourceTool.sourceSlug,
168
- transport: sourceTool.transport,
169
- ...sourceTool.url ? { url: sourceTool.url } : {},
170
- ...sourceTool.command ? { command: sourceTool.command } : {},
171
- ...sourceTool.args ? { args: sourceTool.args.filter((value) => typeof value === "string") } : {},
172
- ...env && Object.keys(env).length > 0 ? { env } : {},
173
- ...headers && Object.keys(headers).length > 0 ? { headers } : {},
174
- ...sourceTool.credentialRef ? { credentialRef: sanitizeSourceCredentialRef(sourceTool.credentialRef) } : {}
175
- });
176
- }
177
- }
178
- return sanitized;
179
- }
180
- function sanitizeSourceCredentialRef(credentialRef) {
181
- return {
182
- type: credentialRef.type,
183
- sourceSlug: credentialRef.sourceSlug,
184
- ...credentialRef.workspaceId ? { workspaceId: credentialRef.workspaceId } : {}
185
- };
186
- }
187
- function copyStringRecord(record) {
188
- return Object.fromEntries(
189
- Object.entries(record).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string")
190
- );
191
- }
192
- function copyNonCredentialStringRecord(record) {
193
- return Object.fromEntries(
194
- Object.entries(copyStringRecord(record)).filter(([key]) => !isCredentialKey(key))
195
- );
196
- }
197
- function isCredentialKey(key) {
198
- const normalized = key.toLowerCase();
199
- return normalized === "authorization" || normalized === "proxy-authorization" || normalized === "cookie" || normalized === "set-cookie" || normalized.includes("api-key") || normalized.includes("apikey") || normalized.includes("token") || normalized.includes("secret") || normalized.includes("password");
200
- }
201
-
202
- // ../packages/sources/dist/index.js
203
- var import_crypto = require("crypto");
204
- var import_fs = require("fs");
205
- var import_path = require("path");
206
- var import_os = require("os");
207
- var import_child_process = require("child_process");
208
- var import_fs2 = require("fs");
209
- var import_os2 = require("os");
210
- var import_fs3 = require("fs");
211
- var import_path2 = require("path");
212
- var import_crypto2 = require("crypto");
213
- var import_fs4 = require("fs");
214
- var import_path3 = require("path");
215
- function getBuiltinSources(_workspaceId, _workspaceRootPath) {
216
- return [];
217
- }
218
- function createSourceActivationPlan(options) {
219
- const requestedSourceSlugs = unique(options.requestedSourceSlugs);
220
- const states = new Map(options.sourceStates.map((source) => [source.sourceSlug, source]));
221
- const activeSourceSlugs = [];
222
- const authRequiredSourceSlugs = [];
223
- const missingSourceSlugs = [];
224
- const blockedSourceSlugs = [];
225
- const timelineItems = [];
226
- for (const sourceSlug of requestedSourceSlugs) {
227
- const state = states.get(sourceSlug) ?? {
228
- sourceSlug,
229
- enabled: false,
230
- authenticated: false,
231
- status: "missing"
232
- };
233
- if (state.enabled && state.authenticated && state.status === "active") {
234
- activeSourceSlugs.push(sourceSlug);
235
- } else if (state.enabled && !state.authenticated) {
236
- authRequiredSourceSlugs.push(sourceSlug);
237
- } else if (state.status === "missing") {
238
- missingSourceSlugs.push(sourceSlug);
239
- } else {
240
- blockedSourceSlugs.push(sourceSlug);
241
- }
242
- timelineItems.push({
243
- type: "source_state_changed",
244
- source: {
245
- sourceSlug: state.sourceSlug,
246
- status: state.status,
247
- enabled: state.enabled,
248
- authenticated: state.authenticated,
249
- ...state.reason ? { reason: state.reason } : {}
250
- }
251
- });
252
- }
253
- return {
254
- requestedSourceSlugs,
255
- activeSourceSlugs,
256
- authRequiredSourceSlugs,
257
- missingSourceSlugs,
258
- blockedSourceSlugs,
259
- timelineItems
260
- };
261
- }
262
- function unique(values) {
263
- return [...new Set(values)];
264
- }
265
- function createSourceToolAssemblyPlan(options) {
266
- const bySlug = new Map(options.sources.map((source) => [source.config.slug, source]));
267
- const tools = [];
268
- const blockedSourceSlugs = [];
269
- for (const sourceSlug of unique2(options.activeSourceSlugs)) {
270
- const source = bySlug.get(sourceSlug);
271
- if (!source) {
272
- blockedSourceSlugs.push(sourceSlug);
273
- continue;
274
- }
275
- const descriptor = descriptorFromSource(
276
- source,
277
- options.credentialRefs?.[sourceSlug],
278
- options.allowedStdioEnvKeys ?? []
279
- );
280
- if (descriptor) {
281
- tools.push(descriptor);
282
- } else {
283
- blockedSourceSlugs.push(sourceSlug);
284
- }
285
- }
286
- return { tools, blockedSourceSlugs };
287
- }
288
- function descriptorFromSource(source, credentialRef, allowedStdioEnvKeys) {
289
- const { config } = source;
290
- if (config.type === "api" && config.api) {
291
- return {
292
- kind: "api-source",
293
- sourceSlug: config.slug,
294
- baseUrl: config.api.baseUrl,
295
- authType: config.api.authType,
296
- ...headersOption("defaultHeaders", config.api.defaultHeaders),
297
- ...credentialRef ? { credentialRef } : {}
298
- };
299
- }
300
- if (config.type === "local" && config.local) {
301
- return {
302
- kind: "local-source",
303
- sourceSlug: config.slug,
304
- path: config.local.path,
305
- ...config.local.format ? { format: config.local.format } : {}
306
- };
307
- }
308
- if (config.type === "mcp" && config.mcp) {
309
- const transport = config.mcp.transport ?? "http";
310
- return {
311
- kind: "mcp-server",
312
- sourceSlug: config.slug,
313
- transport,
314
- ...config.mcp.url ? { url: config.mcp.url } : {},
315
- ...config.mcp.command ? { command: config.mcp.command } : {},
316
- ...config.mcp.args ? { args: [...config.mcp.args] } : {},
317
- ...transport === "stdio" ? { env: scrubEnv(config.mcp.env, allowedStdioEnvKeys) } : {},
318
- ...transport !== "stdio" ? headersOption("headers", config.mcp.headers) : {},
319
- ...credentialRef ? { credentialRef } : {}
320
- };
321
- }
322
- return void 0;
323
- }
324
- function scrubEnv(env, allowedKeys) {
325
- if (!env) return void 0;
326
- const allowed = new Set(allowedKeys);
327
- const scrubbed = Object.fromEntries(
328
- Object.entries(env).filter(([key]) => allowed.has(key))
329
- );
330
- return Object.keys(scrubbed).length > 0 ? scrubbed : void 0;
331
- }
332
- function headersOption(key, headers) {
333
- const scrubbed = scrubCredentialHeaders(headers);
334
- return scrubbed ? { [key]: scrubbed } : {};
335
- }
336
- function scrubCredentialHeaders(headers) {
337
- if (!headers) return void 0;
338
- const scrubbed = Object.fromEntries(
339
- Object.entries(headers).filter(([key]) => !isCredentialHeader(key))
340
- );
341
- return Object.keys(scrubbed).length > 0 ? scrubbed : void 0;
342
- }
343
- function isCredentialHeader(key) {
344
- const normalized = key.toLowerCase();
345
- return normalized === "authorization" || normalized === "proxy-authorization" || normalized === "cookie" || normalized === "set-cookie" || normalized.includes("api-key") || normalized.includes("apikey") || normalized.includes("token") || normalized.includes("secret");
346
- }
347
- function unique2(values) {
348
- return [...new Set(values)];
349
- }
350
- function createSourceRuntimeAssemblyPlan(options) {
351
- const activation = createSourceActivationPlan({
352
- requestedSourceSlugs: options.requestedSourceSlugs,
353
- sourceStates: options.sourceStates
354
- });
355
- const toolAssembly = createSourceToolAssemblyPlan({
356
- activeSourceSlugs: activation.activeSourceSlugs,
357
- sources: options.sources,
358
- credentialRefs: options.credentialRefs,
359
- allowedStdioEnvKeys: options.allowedStdioEnvKeys
360
- });
361
- return {
362
- requestedSourceSlugs: activation.requestedSourceSlugs,
363
- activeSourceSlugs: activation.activeSourceSlugs,
364
- authRequiredSourceSlugs: activation.authRequiredSourceSlugs,
365
- missingSourceSlugs: activation.missingSourceSlugs,
366
- blockedSourceSlugs: unique3([
367
- ...activation.blockedSourceSlugs,
368
- ...toolAssembly.blockedSourceSlugs
369
- ]),
370
- toolBlockedSourceSlugs: toolAssembly.blockedSourceSlugs,
371
- sourceTools: toolAssembly.tools,
372
- timelineItems: activation.timelineItems
373
- };
374
- }
375
- function unique3(values) {
376
- return [...new Set(values)];
377
- }
378
- function createSourceRuntimeProviderOptions(options) {
379
- const plan = createSourceRuntimeAssemblyPlan(options);
380
- const sourceTools = sanitizeProviderSourceTools(
381
- scrubProviderCredentialHeaders(plan.sourceTools)
382
- );
383
- return {
384
- extensions: {
385
- sources: {
386
- enabledSourceSlugs: plan.activeSourceSlugs
387
- }
388
- },
389
- sourceTools,
390
- timelineItems: plan.timelineItems,
391
- capabilityDegradation: {
392
- authRequiredSourceSlugs: plan.authRequiredSourceSlugs,
393
- missingSourceSlugs: plan.missingSourceSlugs,
394
- blockedSourceSlugs: plan.blockedSourceSlugs,
395
- toolBlockedSourceSlugs: plan.toolBlockedSourceSlugs
396
- }
397
- };
398
- }
399
- function scrubProviderCredentialHeaders(sourceTools) {
400
- return sourceTools.map((sourceTool) => {
401
- if (sourceTool.kind === "api-source" && sourceTool.defaultHeaders) {
402
- const defaultHeaders = scrubCredentialHeaders2(sourceTool.defaultHeaders);
403
- return {
404
- ...sourceTool,
405
- ...Object.keys(defaultHeaders).length > 0 ? { defaultHeaders } : { defaultHeaders: void 0 }
406
- };
407
- }
408
- if (sourceTool.kind === "mcp-server" && sourceTool.headers) {
409
- const headers = scrubCredentialHeaders2(sourceTool.headers);
410
- return {
411
- ...sourceTool,
412
- ...Object.keys(headers).length > 0 ? { headers } : { headers: void 0 }
413
- };
414
- }
415
- return sourceTool;
416
- });
417
- }
418
- function scrubCredentialHeaders2(headers) {
419
- const scrubbed = {};
420
- for (const [key, value] of Object.entries(headers)) {
421
- if (isCredentialHeader2(key)) continue;
422
- scrubbed[key] = value;
423
- }
424
- return scrubbed;
425
- }
426
- function isCredentialHeader2(key) {
427
- const normalized = key.toLowerCase();
428
- return normalized === "authorization" || normalized === "proxy-authorization" || normalized === "cookie" || normalized === "set-cookie" || normalized.includes("api-key") || normalized.includes("apikey") || normalized.includes("token") || normalized.includes("secret");
429
- }
430
- var SOURCE_TYPES = /* @__PURE__ */ new Set(["api", "mcp", "local"]);
431
- var API_AUTH_TYPES = /* @__PURE__ */ new Set(["bearer", "header", "query", "basic", "oauth", "none"]);
432
- var MCP_TRANSPORTS = /* @__PURE__ */ new Set(["http", "sse", "stdio"]);
433
- var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set(["authorization", "x-api-key", "api-key", "x-auth-token"]);
434
- function validateSourceConfig(config) {
435
- const errors = [];
436
- const warnings = [];
437
- const record = asRecord(config);
438
- const file = sourceConfigFile(record);
439
- if (!record) {
440
- errors.push(issue(file, "", "Source config must be an object", "error"));
441
- return result(errors, warnings);
442
- }
443
- validateRequiredString(record, "id", file, errors);
444
- validateRequiredString(record, "name", file, errors);
445
- validateRequiredString(record, "slug", file, errors);
446
- validateRequiredString(record, "provider", file, errors);
447
- if (typeof record.enabled !== "boolean") {
448
- errors.push(issue(file, "enabled", "Source config requires boolean enabled", "error"));
449
- }
450
- if (!SOURCE_TYPES.has(record.type)) {
451
- errors.push(issue(file, "type", `Invalid source type: ${String(record.type)}`, "error", "Use one of: api, mcp, local."));
452
- return result(errors, warnings);
453
- }
454
- if (record.type === "api") validateApiSource(record, file, errors, warnings);
455
- if (record.type === "mcp") validateMcpSource(record, file, errors);
456
- if (record.type === "local") validateLocalSource(record, file, errors);
457
- return result(errors, warnings);
458
- }
459
- function validateApiSource(config, file, errors, warnings) {
460
- if (!config.api || typeof config.api !== "object") {
461
- errors.push(issue(file, "api", "API sources require an api config block", "error"));
462
- return;
463
- }
464
- if (!isNonEmptyString(config.api.baseUrl) || !isValidUrl(config.api.baseUrl)) {
465
- errors.push(issue(file, "api.baseUrl", "API sources require a valid baseUrl", "error"));
466
- }
467
- if (!API_AUTH_TYPES.has(config.api.authType)) {
468
- errors.push(issue(file, "api.authType", `Invalid API authType: ${String(config.api.authType)}`, "error"));
469
- }
470
- for (const key of Object.keys(config.api.defaultHeaders ?? {})) {
471
- if (CREDENTIAL_HEADER_NAMES.has(key.toLowerCase())) {
472
- warnings.push(issue(
473
- file,
474
- `api.defaultHeaders.${key}`,
475
- "Source config should not store credential-bearing headers",
476
- "warning",
477
- "Store secret values in the source credential gateway and keep only credentialRef metadata in runtime descriptors."
478
- ));
479
- }
480
- }
481
- }
482
- function validateMcpSource(config, file, errors) {
483
- if (!config.mcp || typeof config.mcp !== "object") {
484
- errors.push(issue(file, "mcp", "MCP sources require an mcp config block", "error"));
485
- return;
486
- }
487
- const transport = config.mcp.transport ?? "http";
488
- if (!MCP_TRANSPORTS.has(transport)) {
489
- errors.push(issue(file, "mcp.transport", `Invalid MCP transport: ${String(transport)}`, "error"));
490
- return;
491
- }
492
- if (transport === "stdio") {
493
- if (!isNonEmptyString(config.mcp.command)) {
494
- errors.push(issue(
495
- file,
496
- "mcp.command",
497
- "Stdio MCP sources require a command",
498
- "error",
499
- "Set mcp.command to the executable used to start the MCP server."
500
- ));
501
- }
502
- return;
503
- }
504
- if (!isNonEmptyString(config.mcp.url) || !isValidUrl(config.mcp.url)) {
505
- errors.push(issue(file, "mcp.url", "HTTP/SSE MCP sources require a valid url", "error"));
506
- }
507
- }
508
- function validateLocalSource(config, file, errors) {
509
- if (!config.local || typeof config.local !== "object") {
510
- errors.push(issue(file, "local", "Local sources require a local config block", "error"));
511
- return;
512
- }
513
- if (!isNonEmptyString(config.local.path)) {
514
- errors.push(issue(file, "local.path", "Local sources require a path", "error"));
515
- }
516
- }
517
- function validateRequiredString(record, key, file, errors) {
518
- if (!isNonEmptyString(record[key])) {
519
- errors.push(issue(file, key, `Source config requires ${key}`, "error"));
520
- }
521
- }
522
- function sourceConfigFile(record) {
523
- const slug = typeof record?.slug === "string" && record.slug.length > 0 ? record.slug : "<unknown>";
524
- return `sources/${slug}/config.json`;
525
- }
526
- function issue(file, path, message, severity, suggestion) {
527
- return {
528
- file,
529
- path,
530
- message,
531
- severity,
532
- ...suggestion ? { suggestion } : {}
533
- };
534
- }
535
- function result(errors, warnings) {
536
- return {
537
- valid: errors.length === 0,
538
- errors,
539
- warnings
540
- };
541
- }
542
- function asRecord(value) {
543
- return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
544
- }
545
- function isNonEmptyString(value) {
546
- return typeof value === "string" && value.trim().length > 0;
547
- }
548
- function isValidUrl(value) {
549
- try {
550
- new URL(value);
551
- return true;
552
- } catch {
553
- return false;
554
- }
555
- }
556
- function providerAuthToSourceStatus(detection) {
557
- if (detection.mode === "provider-owned") {
558
- return {
559
- mode: detection.mode,
560
- configured: detection.configured,
561
- description: detection.configured ? `Provider auth configured (${detection.method ?? "unknown method"})` : detection.error ?? "Provider auth not configured"
562
- };
563
- }
564
- if (detection.mode === "managed") {
565
- return {
566
- mode: detection.mode,
567
- configured: detection.configured,
568
- description: detection.configured ? `Credentials available via env vars (${detection.method ?? "unknown method"})` : detection.error ?? "No credentials found in env vars"
569
- };
570
- }
571
- return {
572
- mode: detection.mode,
573
- configured: true,
574
- description: "No auth required"
575
- };
576
- }
577
- var StubSourceOAuthProvider = class {
578
- async authenticate() {
579
- return {
580
- success: false,
581
- error: "Source OAuth not available \u2014 no host OAuthProvider injected. The host application must provide a SourceOAuthProvider implementation."
582
- };
583
- }
584
- async refresh() {
585
- return {
586
- success: false,
587
- error: "Source OAuth refresh not available \u2014 no host OAuthProvider injected."
588
- };
589
- }
590
- };
591
- function inferGoogleServiceFromUrl(baseUrl) {
592
- if (!baseUrl) return void 0;
593
- let hostname;
594
- let pathname;
595
- try {
596
- const parsed = new URL(baseUrl);
597
- hostname = parsed.hostname.toLowerCase();
598
- pathname = parsed.pathname.toLowerCase();
599
- } catch {
600
- return void 0;
601
- }
602
- if (hostname === "calendar.googleapis.com") return "calendar";
603
- if (hostname === "drive.googleapis.com") return "drive";
604
- if (hostname === "gmail.googleapis.com") return "gmail";
605
- if (hostname === "docs.googleapis.com") return "docs";
606
- if (hostname === "sheets.googleapis.com") return "sheets";
607
- if (hostname === "youtube.googleapis.com") return "youtube";
608
- if (hostname === "searchconsole.googleapis.com" || hostname === "webmasters.googleapis.com") return "searchconsole";
609
- if (hostname === "www.googleapis.com" || hostname === "googleapis.com") {
610
- if (pathname.startsWith("/calendar/")) return "calendar";
611
- if (pathname.startsWith("/drive/")) return "drive";
612
- if (pathname.startsWith("/gmail/")) return "gmail";
613
- if (pathname.startsWith("/v1/documents") || pathname.startsWith("/documents/")) return "docs";
614
- if (pathname.startsWith("/v4/spreadsheets") || pathname.startsWith("/spreadsheets/")) return "sheets";
615
- if (pathname.startsWith("/youtube/")) return "youtube";
616
- if (pathname.startsWith("/webmasters/")) return "searchconsole";
617
- }
618
- return void 0;
619
- }
620
- function inferSlackServiceFromUrl(baseUrl) {
621
- if (!baseUrl) return void 0;
622
- let hostname;
623
- try {
624
- const parsed = new URL(baseUrl);
625
- hostname = parsed.hostname.toLowerCase();
626
- } catch {
627
- return void 0;
628
- }
629
- if (hostname === "slack.com" || hostname === "api.slack.com") {
630
- return "full";
631
- }
632
- return void 0;
633
- }
634
- function inferMicrosoftServiceFromUrl(baseUrl) {
635
- if (!baseUrl) return void 0;
636
- let hostname;
637
- let pathname;
638
- try {
639
- const parsed = new URL(baseUrl);
640
- hostname = parsed.hostname.toLowerCase();
641
- pathname = parsed.pathname.toLowerCase();
642
- } catch {
643
- return void 0;
644
- }
645
- if (hostname === "graph.microsoft.com") {
646
- if (pathname.includes("/me/messages") || pathname.includes("/me/mailfolders") || pathname.includes("/mail")) {
647
- return "outlook";
648
- }
649
- if (pathname.includes("/me/calendar") || pathname.includes("/me/events")) {
650
- return "microsoft-calendar";
651
- }
652
- if (pathname.includes("/me/drive") || pathname.includes("/drives")) {
653
- return "onedrive";
654
- }
655
- if (pathname.includes("/teams") || pathname.includes("/chats")) {
656
- return "teams";
657
- }
658
- if (pathname.includes("/sites")) {
659
- return "sharepoint";
660
- }
661
- return void 0;
662
- }
663
- if (hostname === "outlook.office.com" || hostname === "outlook.office365.com") {
664
- return "outlook";
665
- }
666
- return void 0;
667
- }
668
- var API_OAUTH_PROVIDERS = ["google", "microsoft", "slack"];
669
- function isApiOAuthProvider(provider) {
670
- return API_OAUTH_PROVIDERS.includes(provider);
671
- }
672
- function hasRenewEndpoint(source) {
673
- return source.config.type === "api" && !!source.config.api?.renewEndpoint?.path;
674
- }
675
- function debug(message, ...args) {
676
- console.debug(`[sources] ${message}`, ...args);
677
- }
678
- var MAX_DOWNLOAD_SIZE = 50 * 1024 * 1024;
679
- function buildAuthorizationHeader(authScheme, token) {
680
- const scheme = authScheme ?? "Bearer";
681
- return scheme ? `${scheme} ${token}` : token;
682
- }
683
- function getMachineId() {
684
- const os = (0, import_os2.platform)();
685
- try {
686
- if (os === "darwin") {
687
- const out = (0, import_child_process.execSync)(
688
- "ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID",
689
- { encoding: "utf-8", timeout: 3e3 }
690
- );
691
- const match = out.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
692
- if (match?.[1]) return match[1];
693
- }
694
- if (os === "linux") {
695
- for (const path of ["/var/lib/dbus/machine-id", "/etc/machine-id"]) {
696
- if ((0, import_fs2.existsSync)(path)) {
697
- const id = (0, import_fs2.readFileSync)(path, "utf-8").trim();
698
- if (id.length > 0) return id;
699
- }
700
- }
701
- }
702
- if (os === "win32") {
703
- const out = (0, import_child_process.execSync)(
704
- "reg query HKLM\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid",
705
- { encoding: "utf-8", timeout: 3e3 }
706
- );
707
- const match = out.match(/MachineGuid\s+REG_SZ\s+(.+)/);
708
- if (match?.[1]) return match[1].trim();
709
- }
710
- } catch {
711
- }
712
- const info = (0, import_os2.userInfo)();
713
- return `${info.username}:${(0, import_os2.homedir)()}`;
714
- }
715
- var MAGIC = Buffer.from("WEFT01\0\0");
716
- var HEADER_SIZE = 64;
717
- var SALT_SIZE = 32;
718
- var IV_SIZE = 12;
719
- var TAG_SIZE = 16;
720
- var PBKDF2_ITERATIONS = 1e5;
721
- var KEY_LENGTH = 32;
722
- var DEFAULT_PATH = (0, import_path.join)((0, import_os.homedir)(), ".weft", "credentials.enc");
723
- var EncryptedCredentialBackend = class {
724
- filePath;
725
- cache = null;
726
- constructor(filePath) {
727
- this.filePath = filePath ?? DEFAULT_PATH;
728
- }
729
- async get(id) {
730
- const store = this.load();
731
- return store[credentialKey(id)] ?? null;
732
- }
733
- async set(id, credential) {
734
- const store = this.load();
735
- store[credentialKey(id)] = credential;
736
- this.save(store);
737
- }
738
- async delete(id) {
739
- const store = this.load();
740
- const key = credentialKey(id);
741
- if (!(key in store)) return false;
742
- delete store[key];
743
- this.save(store);
744
- return true;
745
- }
746
- async list(filter) {
747
- const store = this.load();
748
- return Object.keys(store).map(parseCredentialKey).filter((id) => {
749
- if (!id) return false;
750
- if (filter?.type && id.type !== filter.type) return false;
751
- if (filter?.workspaceId && id.workspaceId !== filter.workspaceId) return false;
752
- if (filter?.sourceId && id.sourceId !== filter.sourceId) return false;
753
- return true;
754
- });
755
- }
756
- load() {
757
- if (this.cache) return this.cache;
758
- if (!(0, import_fs.existsSync)(this.filePath)) {
759
- this.cache = {};
760
- return this.cache;
761
- }
762
- const buf = (0, import_fs.readFileSync)(this.filePath);
763
- if (buf.length < HEADER_SIZE + IV_SIZE + TAG_SIZE) {
764
- this.cache = {};
765
- return this.cache;
766
- }
767
- const magic = buf.subarray(0, MAGIC.length);
768
- if (!magic.equals(MAGIC)) {
769
- throw new Error("Corrupt credential file: invalid magic bytes");
770
- }
771
- const salt = buf.subarray(12, 12 + SALT_SIZE);
772
- const payload = buf.subarray(HEADER_SIZE);
773
- const iv = payload.subarray(0, IV_SIZE);
774
- const tag = payload.subarray(IV_SIZE, IV_SIZE + TAG_SIZE);
775
- const ciphertext = payload.subarray(IV_SIZE + TAG_SIZE);
776
- const key = deriveKey(salt);
777
- const decipher = (0, import_crypto.createDecipheriv)("aes-256-gcm", key, iv);
778
- decipher.setAuthTag(tag);
779
- let plaintext;
780
- try {
781
- plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
782
- } catch {
783
- throw new Error("Failed to decrypt credential file \u2014 machine identity may have changed");
784
- }
785
- try {
786
- this.cache = JSON.parse(plaintext.toString("utf-8"));
787
- } catch {
788
- throw new Error("Corrupt credential file: invalid JSON payload");
789
- }
790
- return this.cache;
791
- }
792
- save(store) {
793
- this.cache = store;
794
- const dir = (0, import_path.dirname)(this.filePath);
795
- if (!(0, import_fs.existsSync)(dir)) (0, import_fs.mkdirSync)(dir, { recursive: true, mode: 448 });
796
- const salt = (0, import_crypto.randomBytes)(SALT_SIZE);
797
- const key = deriveKey(salt);
798
- const iv = (0, import_crypto.randomBytes)(IV_SIZE);
799
- const cipher = (0, import_crypto.createCipheriv)("aes-256-gcm", key, iv);
800
- const plaintext = Buffer.from(JSON.stringify(store), "utf-8");
801
- const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
802
- const tag = cipher.getAuthTag();
803
- const header = Buffer.alloc(HEADER_SIZE);
804
- MAGIC.copy(header, 0);
805
- header.writeUInt32LE(0, 8);
806
- salt.copy(header, 12);
807
- const output = Buffer.concat([header, iv, tag, ciphertext]);
808
- const tmpPath = `${this.filePath}.tmp`;
809
- (0, import_fs.writeFileSync)(tmpPath, output, { mode: 384 });
810
- try {
811
- (0, import_fs.renameSync)(tmpPath, this.filePath);
812
- } catch (err) {
813
- try {
814
- (0, import_fs.unlinkSync)(tmpPath);
815
- } catch {
816
- }
817
- throw err;
818
- }
819
- }
820
- };
821
- function deriveKey(salt) {
822
- const machineId = getMachineId();
823
- const seed = (0, import_crypto.createHash)("sha256").update(`${machineId}:weft-credentials-v1`).digest();
824
- return (0, import_crypto.pbkdf2Sync)(seed, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
825
- }
826
- function credentialKey(id) {
827
- return `${id.type}::${id.workspaceId}::${id.sourceId}`;
828
- }
829
- function parseCredentialKey(key) {
830
- const parts = key.split("::");
831
- if (parts.length < 3) return null;
832
- const type = parts[0];
833
- if (!["source_oauth", "source_bearer", "source_apikey", "source_basic"].includes(type)) return null;
834
- return { type, workspaceId: parts[1], sourceId: parts.slice(2).join("::") };
835
- }
836
- var InMemoryCredentialManager = class {
837
- store = /* @__PURE__ */ new Map();
838
- key(id) {
839
- return `${id.type}::${id.workspaceId}::${id.sourceId}`;
840
- }
841
- async get(id) {
842
- return this.store.get(this.key(id)) ?? null;
843
- }
844
- async set(id, credential) {
845
- this.store.set(this.key(id), credential);
846
- }
847
- async delete(id) {
848
- return this.store.delete(this.key(id));
849
- }
850
- };
851
- var instance = null;
852
- function getCredentialManager(options) {
853
- if (!instance) {
854
- const backend = options?.backend ?? "encrypted";
855
- if (backend === "memory") {
856
- instance = new InMemoryCredentialManager();
857
- } else {
858
- instance = new EncryptedCredentialBackend(options?.filePath);
859
- }
860
- }
861
- return instance;
862
- }
863
- function resetCredentialManager() {
864
- instance = null;
865
- }
866
- function readJsonFileSync(filePath) {
867
- const content = (0, import_fs4.readFileSync)(filePath, "utf-8");
868
- return JSON.parse(content);
869
- }
870
- function expandPath(path) {
871
- if (path.startsWith("~")) {
872
- const { homedir: homedir3 } = __require("os");
873
- return path.replace("~", homedir3());
874
- }
875
- return path;
876
- }
877
- function toPortablePath(path) {
878
- const { homedir: homedir3 } = __require("os");
879
- const home = homedir3();
880
- if (path.startsWith(home)) {
881
- return "~" + path.slice(home.length);
882
- }
883
- return path;
884
- }
885
- function getWorkspaceSourcesPath(workspaceRootPath) {
886
- return (0, import_path3.join)(workspaceRootPath, "sources");
887
- }
888
- function getSourcePath(workspaceRootPath, sourceSlug) {
889
- return (0, import_path2.join)(getWorkspaceSourcesPath(workspaceRootPath), sourceSlug);
890
- }
891
- function ensureSourcesDir(workspaceRootPath) {
892
- const dir = getWorkspaceSourcesPath(workspaceRootPath);
893
- if (!(0, import_fs3.existsSync)(dir)) {
894
- (0, import_fs3.mkdirSync)(dir, { recursive: true });
895
- }
896
- }
897
- function loadSourceConfig(workspaceRootPath, sourceSlug) {
898
- const configPath = (0, import_path2.join)(getSourcePath(workspaceRootPath, sourceSlug), "config.json");
899
- if (!(0, import_fs3.existsSync)(configPath)) return null;
900
- try {
901
- const config = readJsonFileSync(configPath);
902
- if (config.type === "local" && config.local?.path) {
903
- config.local.path = expandPath(config.local.path);
904
- }
905
- return config;
906
- } catch {
907
- return null;
908
- }
909
- }
910
- function markSourceAuthenticated(workspaceRootPath, sourceSlug) {
911
- const config = loadSourceConfig(workspaceRootPath, sourceSlug);
912
- if (!config) {
913
- debug(`[markSourceAuthenticated] Source ${sourceSlug} not found`);
914
- return false;
915
- }
916
- config.isAuthenticated = true;
917
- config.connectionStatus = "connected";
918
- config.connectionError = void 0;
919
- saveSourceConfig(workspaceRootPath, config);
920
- debug(`[markSourceAuthenticated] Marked ${sourceSlug} as authenticated`);
921
- return true;
922
- }
923
- function saveSourceConfig(workspaceRootPath, config) {
924
- const validation = validateSourceConfig(config);
925
- if (!validation.valid) {
926
- const errorMessages = validation.errors.map((e) => `${e.path}: ${e.message}`).join(", ");
927
- debug("[saveSourceConfig] Validation failed:", errorMessages);
928
- throw new Error(`Invalid source config: ${errorMessages}`);
929
- }
930
- const dir = getSourcePath(workspaceRootPath, config.slug);
931
- if (!(0, import_fs3.existsSync)(dir)) {
932
- (0, import_fs3.mkdirSync)(dir, { recursive: true });
933
- }
934
- const storageConfig = { ...config, updatedAt: Date.now() };
935
- if (storageConfig.type === "local" && storageConfig.local?.path) {
936
- storageConfig.local = {
937
- ...storageConfig.local,
938
- path: toPortablePath(storageConfig.local.path)
939
- };
940
- }
941
- (0, import_fs3.writeFileSync)((0, import_path2.join)(dir, "config.json"), JSON.stringify(storageConfig, null, 2));
942
- }
943
- function parseGuideMarkdown(raw) {
944
- const guide = { raw };
945
- const sectionRegex = /^## (Scope|Guidelines|Context|API Notes|Cache)\n([\s\S]*?)(?=\n## |\Z)/gim;
946
- let match;
947
- while ((match = sectionRegex.exec(raw)) !== null) {
948
- const sectionName = (match[1] ?? "").toLowerCase().replace(/\s+/g, "");
949
- const content = (match[2] ?? "").trim();
950
- switch (sectionName) {
951
- case "scope":
952
- guide.scope = content;
953
- break;
954
- case "guidelines":
955
- guide.guidelines = content;
956
- break;
957
- case "context":
958
- guide.context = content;
959
- break;
960
- case "apinotes":
961
- guide.apiNotes = content;
962
- break;
963
- case "cache":
964
- const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/);
965
- if (jsonMatch && jsonMatch[1]) {
966
- try {
967
- guide.cache = JSON.parse(jsonMatch[1]);
968
- } catch {
969
- }
970
- }
971
- break;
972
- }
973
- }
974
- return guide;
975
- }
976
- function loadSourceGuide(workspaceRootPath, sourceSlug) {
977
- const guidePath = (0, import_path2.join)(getSourcePath(workspaceRootPath, sourceSlug), "guide.md");
978
- if (!(0, import_fs3.existsSync)(guidePath)) return null;
979
- try {
980
- const raw = (0, import_fs3.readFileSync)(guidePath, "utf-8");
981
- return parseGuideMarkdown(raw);
982
- } catch {
983
- return null;
984
- }
985
- }
986
- function saveSourceGuide(workspaceRootPath, sourceSlug, guide) {
987
- const dir = getSourcePath(workspaceRootPath, sourceSlug);
988
- if (!(0, import_fs3.existsSync)(dir)) {
989
- (0, import_fs3.mkdirSync)(dir, { recursive: true });
990
- }
991
- (0, import_fs3.writeFileSync)((0, import_path2.join)(dir, "guide.md"), guide.raw);
992
- }
993
- function loadSource(workspaceRootPath, sourceSlug) {
994
- const folderPath = getSourcePath(workspaceRootPath, sourceSlug);
995
- const config = loadSourceConfig(workspaceRootPath, sourceSlug);
996
- if (!config) return null;
997
- const workspaceId = (0, import_path2.basename)(workspaceRootPath);
998
- const iconPath = findIconFile(folderPath);
999
- return {
1000
- config,
1001
- guide: loadSourceGuide(workspaceRootPath, sourceSlug),
1002
- folderPath,
1003
- workspaceRootPath,
1004
- workspaceId,
1005
- iconPath
1006
- };
1007
- }
1008
- function loadWorkspaceSources(workspaceRootPath) {
1009
- ensureSourcesDir(workspaceRootPath);
1010
- const sources = [];
1011
- const sourcesDir = getWorkspaceSourcesPath(workspaceRootPath);
1012
- if (!(0, import_fs3.existsSync)(sourcesDir)) return sources;
1013
- const entries = (0, import_fs3.readdirSync)(sourcesDir, { withFileTypes: true });
1014
- for (const entry of entries) {
1015
- if (entry.isDirectory()) {
1016
- const source = loadSource(workspaceRootPath, entry.name);
1017
- if (source) {
1018
- sources.push(source);
1019
- }
1020
- }
1021
- }
1022
- return sources;
1023
- }
1024
- function loadAllSources(workspaceRootPath) {
1025
- const workspaceId = (0, import_path2.basename)(workspaceRootPath);
1026
- const userSources = loadWorkspaceSources(workspaceRootPath);
1027
- const builtinSources = getBuiltinSources(workspaceId, workspaceRootPath);
1028
- return [...userSources, ...builtinSources];
1029
- }
1030
- function generateSourceSlug(workspaceRootPath, name) {
1031
- let slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").substring(0, 50);
1032
- if (!slug) {
1033
- slug = "source";
1034
- }
1035
- const sourcesDir = getWorkspaceSourcesPath(workspaceRootPath);
1036
- const existingSlugs = /* @__PURE__ */ new Set();
1037
- if ((0, import_fs3.existsSync)(sourcesDir)) {
1038
- const entries = (0, import_fs3.readdirSync)(sourcesDir, { withFileTypes: true });
1039
- for (const entry of entries) {
1040
- if (entry.isDirectory()) {
1041
- existingSlugs.add(entry.name);
1042
- }
1043
- }
1044
- }
1045
- if (!existingSlugs.has(slug)) {
1046
- return slug;
1047
- }
1048
- let counter = 2;
1049
- while (existingSlugs.has(`${slug}-${counter}`)) {
1050
- counter++;
1051
- }
1052
- return `${slug}-${counter}`;
1053
- }
1054
- async function createSource(workspaceRootPath, input) {
1055
- const slug = generateSourceSlug(workspaceRootPath, input.name);
1056
- const now = Date.now();
1057
- const config = {
1058
- // ID format: {slug}_{random} for easy identification (e.g., "linear_a1b2c3d4")
1059
- id: `${slug}_${(0, import_crypto2.randomUUID)().slice(0, 8)}`,
1060
- name: input.name,
1061
- slug,
1062
- enabled: input.enabled ?? true,
1063
- provider: input.provider,
1064
- type: input.type,
1065
- createdAt: now,
1066
- updatedAt: now
1067
- };
1068
- switch (input.type) {
1069
- case "mcp":
1070
- if (input.mcp) {
1071
- config.mcp = input.mcp;
1072
- }
1073
- break;
1074
- case "api":
1075
- if (input.api) {
1076
- config.api = input.api;
1077
- }
1078
- break;
1079
- case "local":
1080
- if (input.local) {
1081
- config.local = input.local;
1082
- }
1083
- break;
1084
- }
1085
- if (input.icon) {
1086
- const validatedIcon = validateIconValue(input.icon, "Sources");
1087
- if (validatedIcon) {
1088
- config.icon = validatedIcon;
1089
- }
1090
- }
1091
- saveSourceConfig(workspaceRootPath, config);
1092
- const sourcePath = getSourcePath(workspaceRootPath, slug);
1093
- if (config.icon && isIconUrl(config.icon)) {
1094
- const iconPath = await downloadIcon(sourcePath, config.icon, "Sources");
1095
- if (iconPath) {
1096
- debug(`[createSource] Icon downloaded for ${slug}: ${iconPath}`);
1097
- }
1098
- } else if (!config.icon) {
1099
- const { deriveServiceUrl: deriveServiceUrl2, getHighQualityLogoUrl: getHighQualityLogoUrl2 } = await Promise.resolve().then(() => (init_logo_V5LSKQOU(), logo_V5LSKQOU_exports));
1100
- const { downloadIcon: downloadIcon2 } = await Promise.resolve().then(() => (init_icon_GXJSPBS6(), icon_GXJSPBS6_exports));
1101
- const serviceUrl = deriveServiceUrl2(input);
1102
- if (serviceUrl) {
1103
- const logoUrl = await getHighQualityLogoUrl2(serviceUrl, input.provider);
1104
- if (logoUrl) {
1105
- const iconPath = await downloadIcon2(sourcePath, logoUrl, `createSource:${slug}`);
1106
- if (iconPath) {
1107
- config.icon = logoUrl;
1108
- saveSourceConfig(workspaceRootPath, config);
1109
- }
1110
- }
1111
- }
1112
- }
1113
- const guideContent = `# ${input.name}
1114
-
1115
- ## Guidelines
1116
-
1117
- (Add usage guidelines here)
1118
-
1119
- ## Context
1120
-
1121
- (Add context about this source)
1122
- `;
1123
- saveSourceGuide(workspaceRootPath, slug, { raw: guideContent });
1124
- return config;
1125
- }
1126
- function deleteSource(workspaceRootPath, sourceSlug) {
1127
- const dir = getSourcePath(workspaceRootPath, sourceSlug);
1128
- if ((0, import_fs3.existsSync)(dir)) {
1129
- (0, import_fs3.rmSync)(dir, { recursive: true });
1130
- }
1131
- }
1132
- function sourceExists(workspaceRootPath, sourceSlug) {
1133
- return (0, import_fs3.existsSync)((0, import_path2.join)(getSourcePath(workspaceRootPath, sourceSlug), "config.json"));
1134
- }
1135
- function isMultiHeaderCredential(cred) {
1136
- return typeof cred === "object" && cred !== null && !("username" in cred && "password" in cred);
1137
- }
1138
- var SourceCredentialManager = class {
1139
- // Track in-flight refresh promises to prevent concurrent refreshes for the same source
1140
- // This prevents race conditions (especially important for Microsoft which rotates refresh tokens)
1141
- pendingRefreshes = /* @__PURE__ */ new Map();
1142
- /**
1143
- * Host-injected OAuth provider for source-level auth flows.
1144
- * Weft does NOT own browser callback servers or PKCE — the host app
1145
- * (Electron, web UI, CLI) provides the actual implementation.
1146
- * Defaults to StubSourceOAuthProvider which returns errors for all operations.
1147
- */
1148
- oauthProvider;
1149
- /**
1150
- * Construct a SourceCredentialManager with optional OAuth provider injection.
1151
- *
1152
- * If no provider is given, defaults to StubSourceOAuthProvider which
1153
- * returns errors for all OAuth operations. The host app should either:
1154
- * 1. Pass its SourceOAuthProvider at construction
1155
- * 2. Call setOAuthProvider() after construction
1156
- */
1157
- constructor(oauthProvider) {
1158
- this.oauthProvider = oauthProvider ?? new StubSourceOAuthProvider();
1159
- }
1160
- /**
1161
- * Set the host OAuth provider. Called by the host application at startup
1162
- * to inject its OAuth implementation (browser callbacks, PKCE, etc).
1163
- *
1164
- * This can also be used to replace the provider at runtime (e.g. when
1165
- * the Electron app initializes after the credential manager singleton
1166
- * has already been created).
1167
- */
1168
- setOAuthProvider(provider) {
1169
- this.oauthProvider = provider;
1170
- }
1171
- // ============================================================
1172
- // Core CRUD Operations
1173
- // ============================================================
1174
- /**
1175
- * Save credential for a source
1176
- */
1177
- async save(source, credential) {
1178
- const credentialId = this.getCredentialId(source);
1179
- const manager = getCredentialManager();
1180
- await manager.set(credentialId, credential);
1181
- debug(`[SourceCredentialManager] Saved ${credentialId.type} for ${source.config.slug}`);
1182
- }
1183
- /**
1184
- * Load credential for a source
1185
- *
1186
- * For MCP sources, tries both OAuth and bearer credentials as fallback
1187
- * (credentials may have been stored via different auth modes)
1188
- */
1189
- async load(source) {
1190
- const manager = getCredentialManager();
1191
- if (source.config.type === "mcp" && source.config.mcp?.transport !== "stdio" && source.config.mcp?.authType !== "none") {
1192
- return this.loadMcpCredential(source);
1193
- }
1194
- const credentialId = this.getCredentialId(source);
1195
- const cred = await manager.get(credentialId);
1196
- if (cred) {
1197
- debug(`[SourceCredentialManager] Found ${credentialId.type} for ${source.config.slug}`);
1198
- }
1199
- return cred;
1200
- }
1201
- /**
1202
- * Load MCP credential with fallback (OAuth -> bearer)
1203
- */
1204
- async loadMcpCredential(source) {
1205
- const manager = getCredentialManager();
1206
- const baseId = {
1207
- workspaceId: source.workspaceId,
1208
- sourceId: source.config.slug
1209
- };
1210
- const oauthCreds = await manager.get({ type: "source_oauth", ...baseId });
1211
- if (oauthCreds?.value) {
1212
- debug(`[SourceCredentialManager] Found source_oauth for ${source.config.slug}`);
1213
- return oauthCreds;
1214
- }
1215
- const bearerCreds = await manager.get({ type: "source_bearer", ...baseId });
1216
- if (bearerCreds?.value) {
1217
- debug(`[SourceCredentialManager] Found source_bearer for ${source.config.slug}`);
1218
- return bearerCreds;
1219
- }
1220
- debug(`[SourceCredentialManager] No credential found for MCP source ${source.config.slug}`);
1221
- return null;
1222
- }
1223
- /**
1224
- * Delete credential for a source
1225
- */
1226
- async delete(source) {
1227
- const credentialId = this.getCredentialId(source);
1228
- const manager = getCredentialManager();
1229
- const deleted = await manager.delete(credentialId);
1230
- if (deleted) {
1231
- debug(`[SourceCredentialManager] Deleted ${credentialId.type} for ${source.config.slug}`);
1232
- }
1233
- return deleted;
1234
- }
1235
- /**
1236
- * Get token value for a source (convenience method)
1237
- * Returns null if no credential exists or if expired
1238
- */
1239
- async getToken(source) {
1240
- const cred = await this.load(source);
1241
- if (!cred?.value) return null;
1242
- if (this.isExpired(cred)) {
1243
- debug(`[SourceCredentialManager] Token expired for ${source.config.slug}`);
1244
- return null;
1245
- }
1246
- return cred.value;
1247
- }
1248
- /**
1249
- * Get API credential for a source (handles basic auth and multi-header JSON parsing)
1250
- */
1251
- async getApiCredential(source) {
1252
- const cred = await this.load(source);
1253
- const headerNames = source.config.api?.headerNames || source.config.mcp?.headerNames;
1254
- debug(`[SourceCredentialManager] getApiCredential for ${source.config.slug}: cred.value exists=${!!cred?.value}, headerNames=${JSON.stringify(headerNames)}`);
1255
- if (!cred?.value) return null;
1256
- if (headerNames?.length) {
1257
- debug(`[SourceCredentialManager] Attempting multi-header parse for ${source.config.slug}, raw value length=${cred.value.length}`);
1258
- try {
1259
- const parsed = JSON.parse(cred.value);
1260
- debug(`[SourceCredentialManager] Parsed JSON keys: ${Object.keys(parsed).join(", ")}`);
1261
- const hasAllHeaders = headerNames.every((h) => h in parsed);
1262
- debug(`[SourceCredentialManager] hasAllHeaders=${hasAllHeaders}`);
1263
- if (hasAllHeaders) {
1264
- return parsed;
1265
- }
1266
- } catch (e) {
1267
- debug(`[SourceCredentialManager] JSON parse failed: ${e}`);
1268
- }
1269
- }
1270
- if (source.config.api?.authType === "basic") {
1271
- try {
1272
- const parsed = JSON.parse(cred.value);
1273
- if (parsed.username && parsed.password) {
1274
- return parsed;
1275
- }
1276
- } catch {
1277
- }
1278
- }
1279
- return cred.value;
1280
- }
1281
- // ============================================================
1282
- // Credential ID Resolution
1283
- // ============================================================
1284
- /**
1285
- * Get the credential ID for a source
1286
- *
1287
- * Determines the correct credential type based on:
1288
- * - Source type (mcp, api, local)
1289
- * - Auth type (oauth, bearer, header, etc.)
1290
- */
1291
- getCredentialId(source) {
1292
- const mcp = source.config.mcp;
1293
- const api = source.config.api;
1294
- let type;
1295
- if (source.config.type === "mcp") {
1296
- type = mcp?.authType === "bearer" ? "source_bearer" : "source_oauth";
1297
- } else if (source.config.type === "api") {
1298
- if (isApiOAuthProvider(source.config.provider)) {
1299
- type = "source_oauth";
1300
- } else if (api?.authType === "oauth") {
1301
- type = "source_oauth";
1302
- } else if (api?.authType === "bearer") {
1303
- type = "source_bearer";
1304
- } else if (api?.authType === "basic") {
1305
- type = "source_basic";
1306
- } else {
1307
- type = "source_apikey";
1308
- }
1309
- } else {
1310
- type = "source_oauth";
1311
- }
1312
- return {
1313
- type,
1314
- workspaceId: source.workspaceId,
1315
- sourceId: source.config.slug
1316
- };
1317
- }
1318
- // ============================================================
1319
- // Expiry Checking
1320
- // ============================================================
1321
- /**
1322
- * Check if a credential is expired
1323
- */
1324
- isExpired(credential) {
1325
- if (!credential.expiresAt) return false;
1326
- return Date.now() > credential.expiresAt;
1327
- }
1328
- /**
1329
- * Check if a credential needs refresh (within 5 min of expiry)
1330
- */
1331
- needsRefresh(credential) {
1332
- if (!credential.expiresAt) return false;
1333
- const fiveMinutes = 5 * 60 * 1e3;
1334
- return Date.now() > credential.expiresAt - fiveMinutes;
1335
- }
1336
- /**
1337
- * Mark a source as needing re-authentication.
1338
- * Called when token is missing/expired or token refresh fails.
1339
- * Updates config.json so the UI shows "needs auth" and the agent gets proper context.
1340
- */
1341
- markSourceNeedsReauth(source, errorMessage) {
1342
- try {
1343
- const config = loadSourceConfig(source.workspaceRootPath, source.config.slug);
1344
- if (config) {
1345
- config.isAuthenticated = false;
1346
- config.connectionStatus = "needs_auth";
1347
- config.connectionError = errorMessage;
1348
- saveSourceConfig(source.workspaceRootPath, config);
1349
- debug(`[SourceCredentialManager] Marked ${source.config.slug} as needing re-auth: ${errorMessage}`);
1350
- }
1351
- } catch (error) {
1352
- debug(`[SourceCredentialManager] Failed to mark ${source.config.slug} as needing re-auth:`, error);
1353
- }
1354
- }
1355
- /**
1356
- * Check if source has valid (non-expired) credentials
1357
- */
1358
- async hasValidCredentials(source) {
1359
- const token = await this.getToken(source);
1360
- return token !== null;
1361
- }
1362
- // ============================================================
1363
- // OAuth Provider Detection & Routing
1364
- // ============================================================
1365
- /**
1366
- * Detect the OAuth provider for a source.
1367
- */
1368
- detectProvider(source) {
1369
- if (source.config.provider === "google") return "google";
1370
- if (source.config.provider === "slack") return "slack";
1371
- if (source.config.provider === "microsoft") return "microsoft";
1372
- if (source.config.api?.authType === "oauth") return "generic";
1373
- return "mcp";
1374
- }
1375
- /**
1376
- * Check whether a source uses OAuth authentication.
1377
- * Used to gate authenticate() and refresh() before delegating to oauthProvider.
1378
- */
1379
- isOAuthSource(source) {
1380
- if (source.config.provider === "google") return true;
1381
- if (source.config.provider === "slack") return true;
1382
- if (source.config.provider === "microsoft") return true;
1383
- if (source.config.api?.authType === "oauth") return true;
1384
- if (source.config.type === "mcp" && source.config.mcp?.authType === "oauth") return true;
1385
- return false;
1386
- }
1387
- /**
1388
- * Build SourceOAuthOptions from source config for a given provider.
1389
- *
1390
- * Extracts provider-specific fields (service, scopes, clientId, etc.)
1391
- * and maps them to the generalized SourceOAuthOptions interface.
1392
- * The host app's SourceOAuthProvider implementation interprets these
1393
- * fields as needed for its provider-specific OAuth flow.
1394
- */
1395
- buildOAuthOptions(source, provider, options) {
1396
- const oauthOptions = {
1397
- callbackPort: options?.callbackPort,
1398
- callbackUrl: options?.callbackUrl,
1399
- sessionContext: options?.sessionContext
1400
- };
1401
- const api = source.config.api;
1402
- switch (provider) {
1403
- case "google": {
1404
- let service;
1405
- let scopes;
1406
- if (api?.googleScopes && api.googleScopes.length > 0) {
1407
- scopes = api.googleScopes;
1408
- } else if (api?.googleService) {
1409
- service = api.googleService;
1410
- } else {
1411
- service = inferGoogleServiceFromUrl(api?.baseUrl);
1412
- }
1413
- oauthOptions.service = service;
1414
- oauthOptions.scopes = scopes;
1415
- oauthOptions.appType = "electron";
1416
- oauthOptions.clientId = api?.googleOAuthClientId;
1417
- oauthOptions.clientSecret = api?.googleOAuthClientSecret;
1418
- break;
1419
- }
1420
- case "slack": {
1421
- let service;
1422
- let scopes;
1423
- if (api?.slackUserScopes && api.slackUserScopes.length > 0) {
1424
- scopes = api.slackUserScopes;
1425
- } else if (api?.slackService) {
1426
- service = api.slackService;
1427
- } else {
1428
- service = inferSlackServiceFromUrl(api?.baseUrl) || "full";
1429
- }
1430
- oauthOptions.service = service;
1431
- oauthOptions.scopes = scopes;
1432
- oauthOptions.appType = "electron";
1433
- break;
1434
- }
1435
- case "microsoft": {
1436
- let service;
1437
- let scopes;
1438
- if (api?.microsoftScopes && api.microsoftScopes.length > 0) {
1439
- scopes = api.microsoftScopes;
1440
- } else if (api?.microsoftService) {
1441
- service = api.microsoftService;
1442
- } else {
1443
- service = inferMicrosoftServiceFromUrl(api?.baseUrl);
1444
- }
1445
- oauthOptions.service = service;
1446
- oauthOptions.scopes = scopes;
1447
- oauthOptions.appType = "electron";
1448
- break;
1449
- }
1450
- case "generic": {
1451
- const oauthConfig = api?.oauth;
1452
- if (oauthConfig) {
1453
- oauthOptions.service = api?.baseUrl;
1454
- oauthOptions.clientId = oauthConfig.clientId;
1455
- oauthOptions.clientSecret = oauthConfig.clientSecret;
1456
- } else {
1457
- oauthOptions.service = api?.baseUrl;
1458
- }
1459
- break;
1460
- }
1461
- case "mcp": {
1462
- oauthOptions.service = source.config.mcp?.url;
1463
- break;
1464
- }
1465
- }
1466
- return oauthOptions;
1467
- }
1468
- // ============================================================
1469
- // OAuth Authentication (Delegated to Host App)
1470
- // ============================================================
1471
- /**
1472
- * Authenticate a source via OAuth, delegating to the host app.
1473
- *
1474
- * The host app's SourceOAuthProvider handles the entire flow:
1475
- * browser redirect, callback server, token exchange, and credential storage.
1476
- * On success, the source is marked as authenticated in config.json.
1477
- *
1478
- * This replaces the previous prepare/exchange split (prepareOAuth + exchangeAndStore).
1479
- * If a host app needs prepare/exchange separation, it implements that in its
1480
- * SourceOAuthProvider.
1481
- */
1482
- async authenticate(source, options) {
1483
- if (!this.isOAuthSource(source)) {
1484
- return {
1485
- success: false,
1486
- error: `Source ${source.config.slug} does not use OAuth authentication`
1487
- };
1488
- }
1489
- const provider = this.detectProvider(source);
1490
- const oauthOptions = this.buildOAuthOptions(source, provider, options);
1491
- const result2 = await this.oauthProvider.authenticate(
1492
- source.config.slug,
1493
- provider,
1494
- oauthOptions
1495
- );
1496
- if (result2.success) {
1497
- if (result2.accessToken) {
1498
- await this.save(source, {
1499
- value: result2.accessToken,
1500
- refreshToken: result2.refreshToken,
1501
- expiresAt: result2.expiresAt
1502
- });
1503
- debug(`[SourceCredentialManager] Saved token from host OAuthProvider for ${source.config.slug}`);
1504
- }
1505
- markSourceAuthenticated(source.workspaceRootPath, source.config.slug);
1506
- debug(`[SourceCredentialManager] OAuth authenticate complete for ${source.config.slug}`);
1507
- } else {
1508
- debug(`[SourceCredentialManager] OAuth authenticate failed for ${source.config.slug}: ${result2.error}`);
1509
- }
1510
- return { success: result2.success, error: result2.error, email: result2.email };
1511
- }
1512
- // ============================================================
1513
- // Token Refresh (Delegated to Host App for OAuth)
1514
- // ============================================================
1515
- /**
1516
- * Refresh token for a source
1517
- *
1518
- * Returns the new access token, or null if refresh fails.
1519
- * On success, credentials are automatically updated by the host app.
1520
- *
1521
- * Uses promise deduplication to prevent concurrent refresh requests for the same source.
1522
- * This is important because:
1523
- * - Multiple API calls may hit refresh simultaneously when token is expiring
1524
- * - Microsoft rotates refresh tokens, so concurrent refreshes could cause token invalidation
1525
- */
1526
- async refresh(source) {
1527
- const key = source.config.slug;
1528
- const pending = this.pendingRefreshes.get(key);
1529
- if (pending) {
1530
- debug(`[SourceCredentialManager] Reusing pending refresh for ${key}`);
1531
- return pending;
1532
- }
1533
- const refreshPromise = this.doRefresh(source).finally(() => {
1534
- this.pendingRefreshes.delete(key);
1535
- });
1536
- this.pendingRefreshes.set(key, refreshPromise);
1537
- return refreshPromise;
1538
- }
1539
- /**
1540
- * Internal refresh implementation.
1541
- *
1542
- * Routes to:
1543
- * 1. refreshApiRenew — for custom API renew endpoints (non-OAuth)
1544
- * 2. oauthProvider.refresh() — for all OAuth providers (Google, Slack, Microsoft, Generic, MCP)
1545
- *
1546
- * The host app's refresh() implementation handles token refresh and credential storage.
1547
- * After successful refresh, we reload the credential to return the new access token.
1548
- */
1549
- async doRefresh(source) {
1550
- const cred = await this.load(source);
1551
- if (!cred) {
1552
- debug(`[SourceCredentialManager] No credential for ${source.config.slug}`);
1553
- return null;
1554
- }
1555
- if (hasRenewEndpoint(source)) {
1556
- return this.refreshApiRenew(source, cred);
1557
- }
1558
- if (!cred.refreshToken) {
1559
- debug(`[SourceCredentialManager] No refresh token for ${source.config.slug}`);
1560
- return null;
1561
- }
1562
- if (!this.isOAuthSource(source)) {
1563
- debug(`[SourceCredentialManager] Source ${source.config.slug} is not an OAuth source`);
1564
- return null;
1565
- }
1566
- const provider = this.detectProvider(source);
1567
- try {
1568
- const result2 = await this.oauthProvider.refresh(
1569
- source.config.slug,
1570
- provider,
1571
- cred.refreshToken
1572
- );
1573
- if (!result2.success) {
1574
- debug(`[SourceCredentialManager] OAuth refresh failed for ${source.config.slug}: ${result2.error}`);
1575
- this.markSourceNeedsReauth(source, `Token refresh failed: ${result2.error ?? "unknown error"}`);
1576
- return null;
1577
- }
1578
- if (result2.accessToken) {
1579
- await this.save(source, {
1580
- value: result2.accessToken,
1581
- refreshToken: result2.refreshToken ?? cred.refreshToken,
1582
- expiresAt: result2.expiresAt
1583
- });
1584
- debug(`[SourceCredentialManager] Saved refreshed token from host OAuthProvider for ${source.config.slug}`);
1585
- return result2.accessToken;
1586
- }
1587
- const updatedCred = await this.load(source);
1588
- if (updatedCred?.value) {
1589
- debug(`[SourceCredentialManager] Refreshed token for ${source.config.slug}`);
1590
- return updatedCred.value;
1591
- }
1592
- debug(`[SourceCredentialManager] No credential found after refresh for ${source.config.slug}`);
1593
- this.markSourceNeedsReauth(source, "Token not found after refresh");
1594
- return null;
1595
- } catch (error) {
1596
- const errorMsg = error instanceof Error ? error.message : String(error);
1597
- debug(`[SourceCredentialManager] OAuth refresh error for ${source.config.slug}:`, error);
1598
- this.markSourceNeedsReauth(source, `Token refresh failed: ${errorMsg}`);
1599
- return null;
1600
- }
1601
- }
1602
- /**
1603
- * Refresh token via a custom API renew endpoint (non-OAuth).
1604
- * Uses the current access token for renewal — no separate refresh token needed.
1605
- */
1606
- async refreshApiRenew(source, cred) {
1607
- const renewConfig = source.config.api?.renewEndpoint;
1608
- if (!renewConfig?.path) return null;
1609
- const baseUrl = source.config.api.baseUrl;
1610
- const authScheme = source.config.api.authScheme;
1611
- const currentToken = cred.value;
1612
- try {
1613
- const url = renewConfig.path.startsWith("http") ? renewConfig.path : new URL(renewConfig.path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).toString();
1614
- const headers = {
1615
- "Content-Type": "application/json",
1616
- ...source.config.api.defaultHeaders,
1617
- ...substituteTokenInHeaders(renewConfig.headers, currentToken)
1618
- };
1619
- if (!renewConfig.headers?.["Authorization"] && !renewConfig.headers?.["authorization"]) {
1620
- headers["Authorization"] = buildAuthorizationHeader(authScheme, currentToken);
1621
- }
1622
- const method = renewConfig.method ?? "POST";
1623
- const fetchOptions = { method, headers };
1624
- if (renewConfig.body && method !== "GET") {
1625
- fetchOptions.body = JSON.stringify(substituteTokenInBody(renewConfig.body, currentToken));
1626
- }
1627
- const response = await fetch(url, fetchOptions);
1628
- if (!response.ok) {
1629
- const errorText = await response.text().catch(() => "");
1630
- throw new Error(`Renew endpoint returned ${response.status}: ${errorText.slice(0, 200)}`);
1631
- }
1632
- const json = await response.json();
1633
- const tokenField = renewConfig.tokenField ?? "access_token";
1634
- const newToken = json[tokenField];
1635
- if (typeof newToken !== "string" || !newToken) {
1636
- throw new Error(`Renew response missing "${tokenField}" field`);
1637
- }
1638
- const expiresInField = renewConfig.expiresInField ?? "expires_in";
1639
- const expiresInRaw = json[expiresInField];
1640
- let expiresAt;
1641
- if (typeof expiresInRaw === "number" && expiresInRaw > 0) {
1642
- expiresAt = Date.now() + expiresInRaw * 1e3;
1643
- } else if (renewConfig.fallbackTtlSecs) {
1644
- expiresAt = Date.now() + renewConfig.fallbackTtlSecs * 1e3;
1645
- }
1646
- await this.save(source, {
1647
- ...cred,
1648
- value: newToken,
1649
- ...expiresAt !== void 0 ? { expiresAt } : {}
1650
- });
1651
- debug(`[SourceCredentialManager] Refreshed token via renew endpoint for ${source.config.slug}`);
1652
- return newToken;
1653
- } catch (error) {
1654
- const errorMsg = error instanceof Error ? error.message : String(error);
1655
- debug(`[SourceCredentialManager] Renew endpoint refresh failed for ${source.config.slug}:`, error);
1656
- this.markSourceNeedsReauth(source, `Token refresh failed: ${errorMsg}`);
1657
- return null;
1658
- }
1659
- }
1660
- };
1661
- function substituteTokenInBody(obj, token) {
1662
- const result2 = {};
1663
- for (const [key, value] of Object.entries(obj)) {
1664
- if (typeof value === "string") {
1665
- result2[key] = value.replace(/\{\{token\}\}/g, token);
1666
- } else if (Array.isArray(value)) {
1667
- result2[key] = value.map(
1668
- (item) => typeof item === "string" ? item.replace(/\{\{token\}\}/g, token) : item && typeof item === "object" ? substituteTokenInBody(item, token) : item
1669
- );
1670
- } else if (value && typeof value === "object") {
1671
- result2[key] = substituteTokenInBody(value, token);
1672
- } else {
1673
- result2[key] = value;
1674
- }
1675
- }
1676
- return result2;
1677
- }
1678
- function substituteTokenInHeaders(headers, token) {
1679
- if (!headers) return {};
1680
- const result2 = {};
1681
- for (const [key, value] of Object.entries(headers)) {
1682
- result2[key] = value.replace(/\{\{token\}\}/g, token);
1683
- }
1684
- return result2;
1685
- }
1686
- // Annotate the CommonJS export names for ESM import in node:
1687
- 0 && (module.exports = {
1688
- EncryptedCredentialBackend,
1689
- InMemoryCredentialManager,
1690
- SourceCredentialManager,
1691
- StubSourceOAuthProvider,
1692
- createSource,
1693
- createSourceActivationPlan,
1694
- createSourceRuntimeAssemblyPlan,
1695
- createSourceRuntimeProviderOptions,
1696
- createSourceToolAssemblyPlan,
1697
- deleteSource,
1698
- getBuiltinSources,
1699
- getCredentialManager,
1700
- getMachineId,
1701
- isMultiHeaderCredential,
1702
- loadAllSources,
1703
- loadSource,
1704
- loadSourceConfig,
1705
- providerAuthToSourceStatus,
1706
- resetCredentialManager,
1707
- saveSourceConfig,
1708
- sourceExists,
1709
- validateSourceConfig
1710
- });