@foldspace_npm/harness 0.1.2 → 0.1.3
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/README.md +61 -12
- package/bin/attach.mjs +779 -107
- package/bin/cli.mjs +122 -44
- package/bin/deploy.mjs +37 -33
- package/bin/inject.mjs +64 -295
- package/package.json +5 -2
- package/src/action-observer.mjs +271 -0
- package/src/attach-helpers.mjs +162 -0
- package/src/attach-preflight.mjs +332 -0
- package/src/bootstrap-script.mjs +120 -0
- package/src/cdp-request-manager.mjs +61 -0
- package/src/cli-help.mjs +143 -0
- package/src/cli-registry.mjs +309 -0
- package/src/diagnostics.mjs +482 -0
- package/src/init.mjs +2 -2
- package/src/project-config.mjs +37 -0
- package/src/protocol.mjs +181 -0
- package/src/session-summary.mjs +133 -0
- package/templates/agent-starter/CLAUDE.md +31 -8
- package/templates/agent-starter/README.md +26 -3
- package/templates/agent-starter/foldspace.dev.json +1 -3
- package/bin/buildExtension.mjs +0 -90
- package/bin/packageExtension.mjs +0 -29
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CAPABILITIES,
|
|
3
|
+
ERROR_CODES,
|
|
4
|
+
createDiagnosticRequest,
|
|
5
|
+
diagnosticFailure,
|
|
6
|
+
diagnosticSuccess,
|
|
7
|
+
} from "./protocol.mjs";
|
|
8
|
+
|
|
9
|
+
// Diagnostic run functions are deliberately self-contained: CDP and extension
|
|
10
|
+
// adapters serialize them into the page, so they cannot close over this module.
|
|
11
|
+
export const DIAGNOSTICS = Object.freeze({
|
|
12
|
+
inspectSdkState: Object.freeze({
|
|
13
|
+
description:
|
|
14
|
+
"Report Foldspace SDK presence, configured key, script sources, and existing agent instance IDs.",
|
|
15
|
+
risk: "observe",
|
|
16
|
+
capabilities: Object.freeze([CAPABILITIES.PAGE_EVALUATE]),
|
|
17
|
+
run: function inspectSdkState(args = {}) {
|
|
18
|
+
const root = globalThis.window || globalThis;
|
|
19
|
+
const namespace =
|
|
20
|
+
typeof root.__FOLD_SPACE__ === "string" && root.__FOLD_SPACE__
|
|
21
|
+
? root.__FOLD_SPACE__
|
|
22
|
+
: "foldspace";
|
|
23
|
+
const sdk = root[namespace];
|
|
24
|
+
let agentIds = [];
|
|
25
|
+
let agentIdsError = null;
|
|
26
|
+
try {
|
|
27
|
+
if (typeof sdk?.agentIds === "function") {
|
|
28
|
+
const value = sdk.agentIds();
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
agentIds = value
|
|
31
|
+
.filter((id) => typeof id === "string")
|
|
32
|
+
.slice(0, 50);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
agentIdsError = String(error?.message || error).slice(0, 500);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let scripts = [];
|
|
40
|
+
try {
|
|
41
|
+
scripts = Array.from(root.document?.scripts || [])
|
|
42
|
+
.map((script) => script?.src)
|
|
43
|
+
.filter(
|
|
44
|
+
(source) =>
|
|
45
|
+
typeof source === "string" &&
|
|
46
|
+
/foldspace|eucera/i.test(source),
|
|
47
|
+
)
|
|
48
|
+
.slice(0, 20)
|
|
49
|
+
.map((source) => source.slice(0, 2_000));
|
|
50
|
+
} catch {
|
|
51
|
+
// A document is optional for SDK-only fixtures.
|
|
52
|
+
}
|
|
53
|
+
let trackerKey = null;
|
|
54
|
+
try {
|
|
55
|
+
const value = sdk?.getTracker?.()?.getOriginalKey?.();
|
|
56
|
+
if (typeof value === "string") trackerKey = value.slice(0, 500);
|
|
57
|
+
} catch {
|
|
58
|
+
// Older SDKs may not expose their tracker.
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const expectedApiName =
|
|
62
|
+
typeof args.expectedApiName === "string" ? args.expectedApiName : null;
|
|
63
|
+
const expectedMode =
|
|
64
|
+
typeof args.expectedMode === "string"
|
|
65
|
+
? args.expectedMode.toLowerCase()
|
|
66
|
+
: null;
|
|
67
|
+
const matchingAgentIds = expectedApiName
|
|
68
|
+
? agentIds.filter((id) => {
|
|
69
|
+
const separator = id.indexOf("-");
|
|
70
|
+
if (separator < 1) return false;
|
|
71
|
+
const mode = id.slice(0, separator).toLowerCase();
|
|
72
|
+
const apiName = id.slice(separator + 1);
|
|
73
|
+
return (
|
|
74
|
+
apiName === expectedApiName &&
|
|
75
|
+
(expectedMode === null || mode === expectedMode)
|
|
76
|
+
);
|
|
77
|
+
})
|
|
78
|
+
: [];
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
pageUrl:
|
|
82
|
+
typeof root.location?.href === "string"
|
|
83
|
+
? root.location.href.slice(0, 2_000)
|
|
84
|
+
: null,
|
|
85
|
+
documentReadyState:
|
|
86
|
+
typeof root.document?.readyState === "string"
|
|
87
|
+
? root.document.readyState
|
|
88
|
+
: null,
|
|
89
|
+
sdkPresent: sdk !== undefined && sdk !== null,
|
|
90
|
+
sdkReady: typeof sdk?.agent === "function",
|
|
91
|
+
namespace,
|
|
92
|
+
sdkKey:
|
|
93
|
+
typeof sdk?.k === "string" ? sdk.k.slice(0, 500) : null,
|
|
94
|
+
trackerKey,
|
|
95
|
+
agentIds,
|
|
96
|
+
agentIdsError,
|
|
97
|
+
scripts,
|
|
98
|
+
expectedApiName,
|
|
99
|
+
expectedMode,
|
|
100
|
+
matchingAgentIds,
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
}),
|
|
104
|
+
|
|
105
|
+
inspectRegistration: Object.freeze({
|
|
106
|
+
description:
|
|
107
|
+
"Read the exact action names registered on an existing Foldspace agent instance.",
|
|
108
|
+
risk: "observe",
|
|
109
|
+
capabilities: Object.freeze([CAPABILITIES.PAGE_EVALUATE]),
|
|
110
|
+
run: async function inspectRegistration(args = {}) {
|
|
111
|
+
const root = globalThis.window || globalThis;
|
|
112
|
+
const namespace =
|
|
113
|
+
typeof root.__FOLD_SPACE__ === "string" && root.__FOLD_SPACE__
|
|
114
|
+
? root.__FOLD_SPACE__
|
|
115
|
+
: "foldspace";
|
|
116
|
+
const sdk = root[namespace];
|
|
117
|
+
if (typeof sdk?.agentIds !== "function" || typeof sdk?.agent !== "function") {
|
|
118
|
+
return {
|
|
119
|
+
sdkPresent: sdk !== undefined && sdk !== null,
|
|
120
|
+
sdkReady: false,
|
|
121
|
+
agentFound: false,
|
|
122
|
+
actionNames: [],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const apiName =
|
|
127
|
+
typeof args.apiName === "string" ? args.apiName : "";
|
|
128
|
+
const requestedMode =
|
|
129
|
+
typeof args.mode === "string" ? args.mode.toLowerCase() : null;
|
|
130
|
+
const ownerAgentId =
|
|
131
|
+
typeof args.ownerAgentId === "string" ? args.ownerAgentId : null;
|
|
132
|
+
const ids = sdk
|
|
133
|
+
.agentIds()
|
|
134
|
+
.filter((id) => typeof id === "string")
|
|
135
|
+
.slice(0, 50);
|
|
136
|
+
const matchedId = ownerAgentId
|
|
137
|
+
? ids.find((id) => id === ownerAgentId)
|
|
138
|
+
: ids.find((id) => {
|
|
139
|
+
const separator = id.indexOf("-");
|
|
140
|
+
if (separator < 1) return false;
|
|
141
|
+
const mode = id.slice(0, separator).toLowerCase();
|
|
142
|
+
const name = id.slice(separator + 1);
|
|
143
|
+
return (
|
|
144
|
+
name === apiName &&
|
|
145
|
+
(requestedMode === null || requestedMode === mode)
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
if (!matchedId) {
|
|
149
|
+
return {
|
|
150
|
+
sdkPresent: true,
|
|
151
|
+
sdkReady: true,
|
|
152
|
+
agentFound: false,
|
|
153
|
+
agentIds: ids,
|
|
154
|
+
actionNames: [],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const separator = matchedId.indexOf("-");
|
|
159
|
+
const mode = matchedId.slice(0, separator);
|
|
160
|
+
const agent = sdk.agent({
|
|
161
|
+
apiName: matchedId.slice(separator + 1),
|
|
162
|
+
mode: mode.toUpperCase(),
|
|
163
|
+
});
|
|
164
|
+
const available = await agent?.getAvailableActions?.();
|
|
165
|
+
const actionNames =
|
|
166
|
+
available && typeof available === "object"
|
|
167
|
+
? Object.keys(available).sort().slice(0, 200)
|
|
168
|
+
: [];
|
|
169
|
+
const expectedActionNames = Array.isArray(args.expectedActionNames)
|
|
170
|
+
? args.expectedActionNames
|
|
171
|
+
.filter((name) => typeof name === "string")
|
|
172
|
+
.slice(0, 200)
|
|
173
|
+
: [];
|
|
174
|
+
const actionSet = new Set(actionNames);
|
|
175
|
+
const expectedSet = new Set(expectedActionNames);
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
sdkPresent: true,
|
|
179
|
+
sdkReady: true,
|
|
180
|
+
agentFound: true,
|
|
181
|
+
agentId: matchedId,
|
|
182
|
+
actionNames,
|
|
183
|
+
expectedActionNames,
|
|
184
|
+
missingActionNames: expectedActionNames.filter(
|
|
185
|
+
(name) => !actionSet.has(name),
|
|
186
|
+
),
|
|
187
|
+
unexpectedActionNames: actionNames.filter(
|
|
188
|
+
(name) => !expectedSet.has(name),
|
|
189
|
+
),
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
|
|
194
|
+
verifyWidgetVisible: Object.freeze({
|
|
195
|
+
description:
|
|
196
|
+
"Report whether a Foldspace widget candidate is mounted and has a visible bounding box.",
|
|
197
|
+
risk: "observe",
|
|
198
|
+
capabilities: Object.freeze([CAPABILITIES.PAGE_EVALUATE]),
|
|
199
|
+
run: function verifyWidgetVisible(args = {}) {
|
|
200
|
+
const root = globalThis.window || globalThis;
|
|
201
|
+
const selectors =
|
|
202
|
+
Array.isArray(args.selectors) && args.selectors.length
|
|
203
|
+
? args.selectors
|
|
204
|
+
.filter((selector) => typeof selector === "string")
|
|
205
|
+
.slice(0, 10)
|
|
206
|
+
: [
|
|
207
|
+
"#eucera-agent-view",
|
|
208
|
+
"#foldspace-container",
|
|
209
|
+
"[data-foldspace-agent]",
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
const candidates = [];
|
|
213
|
+
const seen = new Set();
|
|
214
|
+
for (const selector of selectors) {
|
|
215
|
+
let elements = [];
|
|
216
|
+
try {
|
|
217
|
+
if (typeof root.document?.querySelectorAll === "function") {
|
|
218
|
+
elements = Array.from(root.document.querySelectorAll(selector));
|
|
219
|
+
} else {
|
|
220
|
+
const element = root.document?.querySelector?.(selector);
|
|
221
|
+
if (element) elements = [element];
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
for (const element of elements.slice(0, 30)) {
|
|
227
|
+
if (!element || seen.has(element)) continue;
|
|
228
|
+
seen.add(element);
|
|
229
|
+
const rect = element.getBoundingClientRect?.();
|
|
230
|
+
const width = Math.max(0, Math.round(rect?.width || 0));
|
|
231
|
+
const height = Math.max(0, Math.round(rect?.height || 0));
|
|
232
|
+
candidates.push({
|
|
233
|
+
selector,
|
|
234
|
+
visible: width > 0 && height > 0,
|
|
235
|
+
size: { width, height },
|
|
236
|
+
ownerAgentId:
|
|
237
|
+
element.getAttribute?.("data-foldspace-dev-agent-id") || null,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const visibleCandidates = candidates.filter(
|
|
243
|
+
(candidate) => candidate.visible,
|
|
244
|
+
);
|
|
245
|
+
const expectedOwner =
|
|
246
|
+
typeof args.ownerAgentId === "string" ? args.ownerAgentId : null;
|
|
247
|
+
const first = candidates[0] || null;
|
|
248
|
+
return {
|
|
249
|
+
mounted: candidates.length > 0,
|
|
250
|
+
visible: visibleCandidates.length > 0,
|
|
251
|
+
soleVisible: visibleCandidates.length === 1,
|
|
252
|
+
visibleCount: visibleCandidates.length,
|
|
253
|
+
ownerMatches:
|
|
254
|
+
expectedOwner === null ||
|
|
255
|
+
(visibleCandidates.length === 1 &&
|
|
256
|
+
visibleCandidates[0].ownerAgentId === expectedOwner),
|
|
257
|
+
expectedOwner,
|
|
258
|
+
selector: first?.selector || null,
|
|
259
|
+
size: first?.size || null,
|
|
260
|
+
candidates,
|
|
261
|
+
};
|
|
262
|
+
},
|
|
263
|
+
}),
|
|
264
|
+
|
|
265
|
+
inspectActionObservation: Object.freeze({
|
|
266
|
+
description:
|
|
267
|
+
"Report the local action registry and bounded passive action lifecycle evidence captured in the page.",
|
|
268
|
+
risk: "observe",
|
|
269
|
+
capabilities: Object.freeze([CAPABILITIES.PAGE_EVALUATE]),
|
|
270
|
+
run: function inspectActionObservation(args = {}) {
|
|
271
|
+
const root = globalThis.window || globalThis;
|
|
272
|
+
const state = root.__FOLDSPACE_DEV_ACTION_OBSERVER__;
|
|
273
|
+
if (!state || state.version !== 1) {
|
|
274
|
+
return {
|
|
275
|
+
installed: false,
|
|
276
|
+
targetAgentId: null,
|
|
277
|
+
expectedActionNames: [],
|
|
278
|
+
actionNameLimitExceeded: false,
|
|
279
|
+
captureCount: 0,
|
|
280
|
+
subscribed: false,
|
|
281
|
+
eventCount: 0,
|
|
282
|
+
droppedEvents: 0,
|
|
283
|
+
events: [],
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const expectedTarget =
|
|
288
|
+
typeof args.ownerAgentId === "string" ? args.ownerAgentId : null;
|
|
289
|
+
const events = Array.isArray(state.events)
|
|
290
|
+
? state.events.slice(-100).map((event) => ({
|
|
291
|
+
source:
|
|
292
|
+
typeof event?.source === "string"
|
|
293
|
+
? event.source.slice(0, 100)
|
|
294
|
+
: "unknown",
|
|
295
|
+
actionName:
|
|
296
|
+
typeof event?.actionName === "string"
|
|
297
|
+
? event.actionName.slice(0, 200)
|
|
298
|
+
: null,
|
|
299
|
+
phase:
|
|
300
|
+
typeof event?.phase === "string"
|
|
301
|
+
? event.phase.slice(0, 100)
|
|
302
|
+
: "unknown",
|
|
303
|
+
status:
|
|
304
|
+
typeof event?.status === "string"
|
|
305
|
+
? event.status.slice(0, 100)
|
|
306
|
+
: "unknown",
|
|
307
|
+
durationMs: Number.isFinite(event?.durationMs)
|
|
308
|
+
? Math.max(0, Math.round(event.durationMs))
|
|
309
|
+
: null,
|
|
310
|
+
parameterKeys: Array.isArray(event?.parameterKeys)
|
|
311
|
+
? event.parameterKeys
|
|
312
|
+
.filter((key) => typeof key === "string")
|
|
313
|
+
.slice(0, 50)
|
|
314
|
+
: [],
|
|
315
|
+
}))
|
|
316
|
+
: [];
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
installed: true,
|
|
320
|
+
targetAgentId:
|
|
321
|
+
typeof state.targetAgentId === "string"
|
|
322
|
+
? state.targetAgentId.slice(0, 200)
|
|
323
|
+
: null,
|
|
324
|
+
ownerMatches:
|
|
325
|
+
expectedTarget === null || state.targetAgentId === expectedTarget,
|
|
326
|
+
expectedActionNames: Array.isArray(state.expectedActionNames)
|
|
327
|
+
? state.expectedActionNames
|
|
328
|
+
.filter((name) => typeof name === "string")
|
|
329
|
+
.sort()
|
|
330
|
+
.slice(0, 200)
|
|
331
|
+
: [],
|
|
332
|
+
actionNameLimitExceeded:
|
|
333
|
+
state.actionNameLimitExceeded === true,
|
|
334
|
+
captureCount: Number.isFinite(state.captureCount)
|
|
335
|
+
? Math.max(0, Math.round(state.captureCount))
|
|
336
|
+
: 0,
|
|
337
|
+
subscribed: state.subscribed === true,
|
|
338
|
+
eventCount: Array.isArray(state.events) ? state.events.length : 0,
|
|
339
|
+
droppedEvents: Number.isFinite(state.droppedEvents)
|
|
340
|
+
? Math.max(0, Math.round(state.droppedEvents))
|
|
341
|
+
: 0,
|
|
342
|
+
events,
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
}),
|
|
346
|
+
|
|
347
|
+
runActionCheck: Object.freeze({
|
|
348
|
+
description:
|
|
349
|
+
"Invoke one action on an existing Foldspace instance without relying on model action selection.",
|
|
350
|
+
risk: "test",
|
|
351
|
+
capabilities: Object.freeze([
|
|
352
|
+
CAPABILITIES.PAGE_EVALUATE,
|
|
353
|
+
CAPABILITIES.PAGE_MUTATE,
|
|
354
|
+
]),
|
|
355
|
+
run: async function runActionCheck(args = {}) {
|
|
356
|
+
const root = globalThis.window || globalThis;
|
|
357
|
+
const namespace =
|
|
358
|
+
typeof root.__FOLD_SPACE__ === "string" && root.__FOLD_SPACE__
|
|
359
|
+
? root.__FOLD_SPACE__
|
|
360
|
+
: "foldspace";
|
|
361
|
+
const sdk = root[namespace];
|
|
362
|
+
if (typeof sdk?.agentIds !== "function" || typeof sdk?.agent !== "function") {
|
|
363
|
+
return { invoked: false, reason: "SDK_NOT_FOUND" };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const apiName =
|
|
367
|
+
typeof args.apiName === "string" ? args.apiName : "";
|
|
368
|
+
const actionName =
|
|
369
|
+
typeof args.actionName === "string" ? args.actionName : "";
|
|
370
|
+
const requestedMode =
|
|
371
|
+
typeof args.mode === "string" ? args.mode.toLowerCase() : null;
|
|
372
|
+
const matchedId = sdk.agentIds().find((id) => {
|
|
373
|
+
if (typeof id !== "string") return false;
|
|
374
|
+
const separator = id.indexOf("-");
|
|
375
|
+
if (separator < 1) return false;
|
|
376
|
+
const mode = id.slice(0, separator).toLowerCase();
|
|
377
|
+
const name = id.slice(separator + 1);
|
|
378
|
+
return (
|
|
379
|
+
name === apiName &&
|
|
380
|
+
(requestedMode === null || requestedMode === mode)
|
|
381
|
+
);
|
|
382
|
+
});
|
|
383
|
+
if (!matchedId) {
|
|
384
|
+
return { invoked: false, reason: "AGENT_NOT_FOUND" };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const separator = matchedId.indexOf("-");
|
|
388
|
+
const agent = sdk.agent({
|
|
389
|
+
apiName: matchedId.slice(separator + 1),
|
|
390
|
+
mode: matchedId.slice(0, separator).toUpperCase(),
|
|
391
|
+
});
|
|
392
|
+
if (typeof agent?.runAction !== "function") {
|
|
393
|
+
return { invoked: false, reason: "RUN_ACTION_UNAVAILABLE" };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const value = await agent.runAction(
|
|
397
|
+
actionName,
|
|
398
|
+
args.params && typeof args.params === "object" ? args.params : {},
|
|
399
|
+
);
|
|
400
|
+
return { invoked: true, agentId: matchedId, actionName, value };
|
|
401
|
+
},
|
|
402
|
+
}),
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
export function diagnosticCatalogue() {
|
|
406
|
+
return Object.entries(DIAGNOSTICS).map(([name, diagnostic]) => ({
|
|
407
|
+
name,
|
|
408
|
+
description: diagnostic.description,
|
|
409
|
+
risk: diagnostic.risk,
|
|
410
|
+
capabilities: [...diagnostic.capabilities],
|
|
411
|
+
}));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function serializeDiagnostic(name, args = {}) {
|
|
415
|
+
const diagnostic = DIAGNOSTICS[name];
|
|
416
|
+
if (!diagnostic) {
|
|
417
|
+
throw new Error(`Unknown diagnostic: ${name}`);
|
|
418
|
+
}
|
|
419
|
+
return `Promise.resolve((${diagnostic.run.toString()})(${JSON.stringify(args)}))`;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export async function executeDiagnostic(input, options = {}) {
|
|
423
|
+
let request;
|
|
424
|
+
try {
|
|
425
|
+
request = createDiagnosticRequest(input);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
const fallback = {
|
|
428
|
+
id: typeof input?.id === "string" ? input.id : "invalid-request",
|
|
429
|
+
name: typeof input?.name === "string" ? input.name : "invalid-diagnostic",
|
|
430
|
+
};
|
|
431
|
+
return diagnosticFailure(fallback, {
|
|
432
|
+
code: ERROR_CODES.INVALID_REQUEST,
|
|
433
|
+
message: error instanceof Error ? error.message : String(error),
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const diagnostic = DIAGNOSTICS[request.name];
|
|
438
|
+
if (!diagnostic) {
|
|
439
|
+
return diagnosticFailure(request, {
|
|
440
|
+
code: ERROR_CODES.INVALID_REQUEST,
|
|
441
|
+
message: `Unknown diagnostic: ${request.name}`,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const availableCapabilities = new Set(options.capabilities || []);
|
|
446
|
+
const missingCapabilities = diagnostic.capabilities.filter(
|
|
447
|
+
(capability) => !availableCapabilities.has(capability),
|
|
448
|
+
);
|
|
449
|
+
if (missingCapabilities.length) {
|
|
450
|
+
return diagnosticFailure(request, {
|
|
451
|
+
code: ERROR_CODES.CAPABILITY_DISABLED,
|
|
452
|
+
message: `Diagnostic requires: ${missingCapabilities.join(", ")}`,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const startedAt = Date.now();
|
|
457
|
+
let timeout;
|
|
458
|
+
try {
|
|
459
|
+
const result = await Promise.race([
|
|
460
|
+
Promise.resolve(diagnostic.run(request.args)),
|
|
461
|
+
new Promise((_, reject) => {
|
|
462
|
+
timeout = setTimeout(
|
|
463
|
+
() => reject(new Error("Diagnostic timed out")),
|
|
464
|
+
request.timeoutMs,
|
|
465
|
+
);
|
|
466
|
+
}),
|
|
467
|
+
]);
|
|
468
|
+
return diagnosticSuccess(request, result, {
|
|
469
|
+
durationMs: Date.now() - startedAt,
|
|
470
|
+
});
|
|
471
|
+
} catch (error) {
|
|
472
|
+
return diagnosticFailure(request, {
|
|
473
|
+
code:
|
|
474
|
+
error instanceof Error && error.message === "Diagnostic timed out"
|
|
475
|
+
? ERROR_CODES.TIMEOUT
|
|
476
|
+
: ERROR_CODES.DIAGNOSTIC_FAILED,
|
|
477
|
+
message: error instanceof Error ? error.message : String(error),
|
|
478
|
+
});
|
|
479
|
+
} finally {
|
|
480
|
+
clearTimeout(timeout);
|
|
481
|
+
}
|
|
482
|
+
}
|
package/src/init.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import readline from "node:readline/promises";
|
|
5
5
|
import { stdin as input, stdout as output } from "node:process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { commandByName } from "./cli-registry.mjs";
|
|
7
8
|
|
|
8
9
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
10
|
const defaultTemplateRoot = path.join(packageRoot, "templates", "agent-starter");
|
|
@@ -11,8 +12,7 @@ const allowedFlags = new Set(["name", "product-id", "agent-api-name", "domain"])
|
|
|
11
12
|
const tokenPattern = /\{\{([A-Z0-9_]+)\}\}/g;
|
|
12
13
|
const defaultDirectory = "my-agent";
|
|
13
14
|
|
|
14
|
-
export const initUsage =
|
|
15
|
-
"foldspace init [<directory>] [--product-id <id>] [--agent-api-name <name>] [--domain <host>] [--name <display-name>]";
|
|
15
|
+
export const initUsage = commandByName("init").usage;
|
|
16
16
|
|
|
17
17
|
function assertSupportedNode() {
|
|
18
18
|
const major = Number.parseInt(process.versions.node.split(".", 1)[0], 10);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export function readProjectConfig(projectDir) {
|
|
5
|
+
const configPath = path.join(projectDir, "foldspace.dev.json");
|
|
6
|
+
if (!fs.existsSync(configPath)) {
|
|
7
|
+
throw new Error("foldspace.dev.json not found");
|
|
8
|
+
}
|
|
9
|
+
let config;
|
|
10
|
+
try {
|
|
11
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`foldspace.dev.json is invalid: ${
|
|
15
|
+
error instanceof Error ? error.message : String(error)
|
|
16
|
+
}`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return config;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function resolveConfiguredTarget(config, requestedTarget) {
|
|
23
|
+
const targetName = requestedTarget || config?.defaultTarget;
|
|
24
|
+
if (typeof targetName !== "string" || !targetName) {
|
|
25
|
+
throw new Error("foldspace.dev.json must define defaultTarget");
|
|
26
|
+
}
|
|
27
|
+
const target = config?.targets?.[targetName];
|
|
28
|
+
if (!target || typeof target !== "object") {
|
|
29
|
+
throw new Error(`target "${targetName}" is not defined`);
|
|
30
|
+
}
|
|
31
|
+
for (const field of ["productId", "agentApiName"]) {
|
|
32
|
+
if (typeof target[field] !== "string" || !target[field]) {
|
|
33
|
+
throw new Error(`target "${targetName}" must define ${field}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { targetName, target };
|
|
37
|
+
}
|