@spotpatch/dev-server 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1919 -459
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +109 -4
- package/dist/index.d.ts +109 -4
- package/dist/index.js +1587 -107
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/dist/index.cjs
CHANGED
|
@@ -30,10 +30,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
ContextualAskError: () => ContextualAskError,
|
|
33
34
|
DEFAULT_EXCLUDE: () => DEFAULT_EXCLUDE,
|
|
34
35
|
DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
|
|
35
36
|
applyIntegrationPlan: () => applyIntegrationPlan,
|
|
37
|
+
captureAskReadSnapshot: () => captureAskReadSnapshot,
|
|
38
|
+
composeContextualAskExecutors: () => composeContextualAskExecutors,
|
|
36
39
|
createAgentJobManager: () => createAgentJobManager,
|
|
40
|
+
createConfiguredKeyAskExecutors: () => createConfiguredKeyAskExecutors,
|
|
41
|
+
createContextualAskManager: () => createContextualAskManager,
|
|
37
42
|
createExternalHandoffService: () => createExternalHandoffService,
|
|
38
43
|
createIntegrationFileChange: () => createIntegrationFileChange,
|
|
39
44
|
createRuntimeAiConfig: () => createRuntimeAiConfig,
|
|
@@ -42,9 +47,12 @@ __export(index_exports, {
|
|
|
42
47
|
createSourceRegistrationService: () => createSourceRegistrationService,
|
|
43
48
|
createSourceRegistry: () => createSourceRegistry,
|
|
44
49
|
createSpotPatchMiddleware: () => createSpotPatchMiddleware,
|
|
50
|
+
createWorkspaceActivityCoordinator: () => createWorkspaceActivityCoordinator,
|
|
45
51
|
discoverProjectValidationCheck: () => discoverProjectValidationCheck,
|
|
52
|
+
handleContextualAskRequest: () => handleContextualAskRequest,
|
|
46
53
|
integrationPathExists: () => integrationPathExists,
|
|
47
54
|
isLoopbackHostname: () => isLoopbackHostname,
|
|
55
|
+
matchContextualAskPath: () => matchContextualAskPath,
|
|
48
56
|
parseSerializedSpotPatchOptions: () => parseSerializedSpotPatchOptions,
|
|
49
57
|
readIntegrationFile: () => readIntegrationFile,
|
|
50
58
|
readJsonRequestBody: () => readJsonRequestBody,
|
|
@@ -221,6 +229,10 @@ function createAgentJobManager(options) {
|
|
|
221
229
|
job.updatedAt = dependencies.now();
|
|
222
230
|
emitSnapshot(job);
|
|
223
231
|
emitPhase(job, phaseMessage);
|
|
232
|
+
if (!isActive(status)) {
|
|
233
|
+
job.lease?.release();
|
|
234
|
+
job.lease = void 0;
|
|
235
|
+
}
|
|
224
236
|
};
|
|
225
237
|
const probeResolved = async (selection, signal) => {
|
|
226
238
|
const key = capabilityCacheKey(selection.provider, selection.model);
|
|
@@ -410,6 +422,7 @@ function createAgentJobManager(options) {
|
|
|
410
422
|
[...jobs.values()].map((job) => job.runPromise).filter((promise) => promise !== void 0)
|
|
411
423
|
);
|
|
412
424
|
capabilityCache.clear();
|
|
425
|
+
for (const job of jobs.values()) job.lease?.release();
|
|
413
426
|
jobs.clear();
|
|
414
427
|
},
|
|
415
428
|
create(request) {
|
|
@@ -434,8 +447,13 @@ function createAgentJobManager(options) {
|
|
|
434
447
|
request.providerProfileId,
|
|
435
448
|
request.modelProfileId
|
|
436
449
|
);
|
|
450
|
+
const lease = options.coordinator?.acquire("change");
|
|
451
|
+
if (options.coordinator !== void 0 && lease === void 0) {
|
|
452
|
+
throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.AGENT_BUSY);
|
|
453
|
+
}
|
|
437
454
|
const id = dependencies.createJobId();
|
|
438
455
|
if (!JOB_ID_PATTERN.test(id) || jobs.has(id)) {
|
|
456
|
+
lease?.release();
|
|
439
457
|
throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.INTERNAL_ERROR);
|
|
440
458
|
}
|
|
441
459
|
const timestamp = dependencies.now();
|
|
@@ -449,6 +467,7 @@ function createAgentJobManager(options) {
|
|
|
449
467
|
events: [],
|
|
450
468
|
id,
|
|
451
469
|
listeners: /* @__PURE__ */ new Set(),
|
|
470
|
+
lease,
|
|
452
471
|
model: selection.model,
|
|
453
472
|
phaseMessage: "Agent job queued.",
|
|
454
473
|
preparedChange: void 0,
|
|
@@ -491,6 +510,11 @@ function createAgentJobManager(options) {
|
|
|
491
510
|
hasActiveJob(job.id) ? import_shared.ERROR_CODES.AGENT_BUSY : import_shared.ERROR_CODES.APPLY_CONFLICT
|
|
492
511
|
);
|
|
493
512
|
}
|
|
513
|
+
const lease = options.coordinator?.acquire("change");
|
|
514
|
+
if (options.coordinator !== void 0 && lease === void 0) {
|
|
515
|
+
throw new import_shared.SpotPatchError(import_shared.ERROR_CODES.AGENT_BUSY);
|
|
516
|
+
}
|
|
517
|
+
job.lease = lease;
|
|
494
518
|
transition(job, "reverting", "Reverting the applied Agent change.");
|
|
495
519
|
try {
|
|
496
520
|
await dependencies.revertChange(job.preparedChange);
|
|
@@ -1262,12 +1286,12 @@ function createExternalHandoffStore(options) {
|
|
|
1262
1286
|
}
|
|
1263
1287
|
return current.snapshot;
|
|
1264
1288
|
};
|
|
1265
|
-
const findReplay = (requestId,
|
|
1289
|
+
const findReplay = (requestId, fingerprint2) => {
|
|
1266
1290
|
requireOpen();
|
|
1267
1291
|
sweep();
|
|
1268
1292
|
const record = idempotency.get(requestId);
|
|
1269
1293
|
if (record === void 0) return void 0;
|
|
1270
|
-
if (record.fingerprint !==
|
|
1294
|
+
if (record.fingerprint !== fingerprint2) {
|
|
1271
1295
|
throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
|
|
1272
1296
|
}
|
|
1273
1297
|
return replayResult(record);
|
|
@@ -1512,12 +1536,12 @@ function createExternalHandoffService(options) {
|
|
|
1512
1536
|
},
|
|
1513
1537
|
async publish(request, authorize) {
|
|
1514
1538
|
requireReady();
|
|
1515
|
-
const
|
|
1516
|
-
const replayed = store.replay(request.requestId,
|
|
1539
|
+
const fingerprint2 = fingerprintExternalHandoffAnnotation(request.annotation);
|
|
1540
|
+
const replayed = store.replay(request.requestId, fingerprint2);
|
|
1517
1541
|
if (replayed !== void 0) return replayed;
|
|
1518
1542
|
const pending = inFlight.get(request.requestId);
|
|
1519
1543
|
if (pending !== void 0) {
|
|
1520
|
-
if (pending.fingerprint !==
|
|
1544
|
+
if (pending.fingerprint !== fingerprint2) {
|
|
1521
1545
|
throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
|
|
1522
1546
|
}
|
|
1523
1547
|
return asReplay(await pending.promise);
|
|
@@ -1527,12 +1551,12 @@ function createExternalHandoffService(options) {
|
|
|
1527
1551
|
const annotation = await authorize(request.annotation);
|
|
1528
1552
|
return store.publish({
|
|
1529
1553
|
annotation,
|
|
1530
|
-
fingerprint,
|
|
1554
|
+
fingerprint: fingerprint2,
|
|
1531
1555
|
requestId: request.requestId,
|
|
1532
1556
|
reserve: activeRegistry.reserve
|
|
1533
1557
|
});
|
|
1534
1558
|
})();
|
|
1535
|
-
const activePublish = Object.freeze({ fingerprint, promise });
|
|
1559
|
+
const activePublish = Object.freeze({ fingerprint: fingerprint2, promise });
|
|
1536
1560
|
inFlight.set(request.requestId, activePublish);
|
|
1537
1561
|
try {
|
|
1538
1562
|
return await promise;
|
|
@@ -1874,7 +1898,8 @@ var DEFAULT_OPTIONS = Object.freeze({
|
|
|
1874
1898
|
runtime: "dispatch",
|
|
1875
1899
|
limits: import_shared8.DEFAULT_DATA_FLOW_LIMITS
|
|
1876
1900
|
}),
|
|
1877
|
-
externalAgent: Object.freeze({ enabled: false })
|
|
1901
|
+
externalAgent: Object.freeze({ enabled: false }),
|
|
1902
|
+
contextualAsk: Object.freeze({ enabled: false })
|
|
1878
1903
|
});
|
|
1879
1904
|
var PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
1880
1905
|
var ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{1,127}$/;
|
|
@@ -2184,6 +2209,35 @@ function resolveDataFlowOptions(options) {
|
|
|
2184
2209
|
limits: import_shared8.DEFAULT_DATA_FLOW_LIMITS
|
|
2185
2210
|
});
|
|
2186
2211
|
}
|
|
2212
|
+
function resolveContextualAskOptions(value, ai) {
|
|
2213
|
+
if (value === void 0 || value === false) return DEFAULT_OPTIONS.contextualAsk;
|
|
2214
|
+
const parsed = import_shared8.contextualAskOptionsSchema.safeParse(value);
|
|
2215
|
+
if (!parsed.success) {
|
|
2216
|
+
throw new RangeError("SpotPatch contextualAsk configuration is invalid.");
|
|
2217
|
+
}
|
|
2218
|
+
const configured = typeof parsed.data === "boolean" ? {} : parsed.data;
|
|
2219
|
+
const preference = configured.defaultExecutor;
|
|
2220
|
+
if (preference?.kind === "configured-key") {
|
|
2221
|
+
const provider = ai === false ? void 0 : ai.providers[preference.providerProfileId];
|
|
2222
|
+
if (provider?.models[preference.modelProfileId] === void 0) {
|
|
2223
|
+
throw new RangeError(
|
|
2224
|
+
"SpotPatch contextualAsk default Key executor must reference a configured AI model."
|
|
2225
|
+
);
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
if (ai !== false && Object.values(ai.providers).reduce(
|
|
2229
|
+
(total, provider) => total + Object.keys(provider.models).length,
|
|
2230
|
+
0
|
|
2231
|
+
) >= import_shared8.CONTEXTUAL_ASK_LIMITS.maximumExecutors) {
|
|
2232
|
+
throw new RangeError(
|
|
2233
|
+
"SpotPatch contextualAsk configuration exceeds the executor limit."
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
return Object.freeze({
|
|
2237
|
+
enabled: true,
|
|
2238
|
+
...preference === void 0 ? {} : { defaultExecutor: Object.freeze({ ...preference }) }
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2187
2241
|
function createRuntimeDataFlowConfig(options) {
|
|
2188
2242
|
return Object.freeze({
|
|
2189
2243
|
enabled: options.enabled,
|
|
@@ -2222,6 +2276,7 @@ function resolveOptions(options = {}, environmentAi) {
|
|
|
2222
2276
|
`SpotPatch maxTargets must be an integer between 1 and ${String(import_shared8.MAX_ANNOTATION_TARGETS)}.`
|
|
2223
2277
|
);
|
|
2224
2278
|
}
|
|
2279
|
+
const ai = resolveAiOptions(options.ai ?? environmentAi);
|
|
2225
2280
|
const resolved = {
|
|
2226
2281
|
enabled: options.enabled ?? DEFAULT_OPTIONS.enabled,
|
|
2227
2282
|
include: Object.freeze([...options.include ?? DEFAULT_OPTIONS.include]),
|
|
@@ -2234,11 +2289,12 @@ function resolveOptions(options = {}, environmentAi) {
|
|
|
2234
2289
|
debug: options.debug ?? DEFAULT_OPTIONS.debug,
|
|
2235
2290
|
locale,
|
|
2236
2291
|
maxTargets,
|
|
2237
|
-
ai
|
|
2292
|
+
ai,
|
|
2238
2293
|
dataFlow: resolveDataFlowOptions(options.dataFlow),
|
|
2239
2294
|
externalAgent: Object.freeze({
|
|
2240
2295
|
enabled: options.externalAgent ?? DEFAULT_OPTIONS.externalAgent.enabled
|
|
2241
|
-
})
|
|
2296
|
+
}),
|
|
2297
|
+
contextualAsk: resolveContextualAskOptions(options.contextualAsk, ai)
|
|
2242
2298
|
};
|
|
2243
2299
|
if (resolved.shortcut.trim().length === 0 || resolved.shortcut.length > 128 || resolved.shortcut.includes("\0")) {
|
|
2244
2300
|
throw new RangeError("SpotPatch shortcut is invalid.");
|
|
@@ -2472,10 +2528,14 @@ function createSourceRegistry(options = {}) {
|
|
|
2472
2528
|
);
|
|
2473
2529
|
}
|
|
2474
2530
|
componentIdsByPath.set(normalizedPath, currentIds);
|
|
2531
|
+
return fileId;
|
|
2475
2532
|
},
|
|
2476
2533
|
resolve(fileId) {
|
|
2477
2534
|
return idToPath.get(fileId);
|
|
2478
2535
|
},
|
|
2536
|
+
findRegisteredId(absolutePath) {
|
|
2537
|
+
return pathToId.get(normalizeAbsolutePath(absolutePath));
|
|
2538
|
+
},
|
|
2479
2539
|
resolveDataFlowComponent(componentSourceId) {
|
|
2480
2540
|
return componentAnchors.get(componentSourceId);
|
|
2481
2541
|
},
|
|
@@ -2488,179 +2548,1478 @@ function createSourceRegistry(options = {}) {
|
|
|
2488
2548
|
});
|
|
2489
2549
|
}
|
|
2490
2550
|
|
|
2491
|
-
// src/
|
|
2492
|
-
var
|
|
2493
|
-
|
|
2494
|
-
// src/external-handoff/browser-http.ts
|
|
2495
|
-
var import_shared13 = require("@spotpatch/shared");
|
|
2496
|
-
|
|
2497
|
-
// src/server/annotation-authorizer.ts
|
|
2498
|
-
var import_promises6 = require("fs/promises");
|
|
2499
|
-
var import_node_path7 = __toESM(require("path"), 1);
|
|
2500
|
-
var import_shared12 = require("@spotpatch/shared");
|
|
2501
|
-
|
|
2502
|
-
// src/server/source-context.ts
|
|
2503
|
-
var import_promises5 = require("fs/promises");
|
|
2504
|
-
var import_node_path6 = __toESM(require("path"), 1);
|
|
2551
|
+
// src/contextual-ask/manager.ts
|
|
2552
|
+
var import_node_crypto10 = require("crypto");
|
|
2505
2553
|
var import_shared11 = require("@spotpatch/shared");
|
|
2506
2554
|
|
|
2507
|
-
// src/
|
|
2508
|
-
var
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
case "TSSatisfiesExpression":
|
|
2516
|
-
case "TSTypeAssertion":
|
|
2517
|
-
case "TSNonNullExpression":
|
|
2518
|
-
case "TSInstantiationExpression":
|
|
2519
|
-
return unwrapTypeExpression(expression.expression);
|
|
2520
|
-
default:
|
|
2521
|
-
return expression;
|
|
2522
|
-
}
|
|
2523
|
-
}
|
|
2524
|
-
function calleeName(expression) {
|
|
2525
|
-
const unwrapped = unwrapTypeExpression(expression);
|
|
2526
|
-
if (unwrapped.type === "Identifier") {
|
|
2527
|
-
return unwrapped.name;
|
|
2555
|
+
// src/contextual-ask/error.ts
|
|
2556
|
+
var import_agent2 = require("@spotpatch/agent");
|
|
2557
|
+
var ContextualAskError = class extends Error {
|
|
2558
|
+
code;
|
|
2559
|
+
constructor(code, options = {}) {
|
|
2560
|
+
super(code, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
2561
|
+
this.name = "ContextualAskError";
|
|
2562
|
+
this.code = code;
|
|
2528
2563
|
}
|
|
2529
|
-
|
|
2530
|
-
|
|
2564
|
+
};
|
|
2565
|
+
function asContextualAskError(error) {
|
|
2566
|
+
if (error instanceof ContextualAskError) return error;
|
|
2567
|
+
if (error instanceof import_agent2.ContextualAskExecutorError) {
|
|
2568
|
+
return new ContextualAskError(error.code, { cause: error });
|
|
2531
2569
|
}
|
|
2532
|
-
return
|
|
2570
|
+
return new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE", { cause: error });
|
|
2533
2571
|
}
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2572
|
+
|
|
2573
|
+
// src/contextual-ask/read-snapshot.ts
|
|
2574
|
+
var import_node_crypto9 = require("crypto");
|
|
2575
|
+
var import_node_fs = require("fs");
|
|
2576
|
+
var import_promises4 = require("fs/promises");
|
|
2577
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
2578
|
+
var import_shared10 = require("@spotpatch/shared");
|
|
2579
|
+
var import_oxc_parser = require("oxc-parser");
|
|
2580
|
+
var SOURCE_EXTENSIONS = Object.freeze([
|
|
2581
|
+
".ts",
|
|
2582
|
+
".tsx",
|
|
2583
|
+
".js",
|
|
2584
|
+
".jsx",
|
|
2585
|
+
".mjs",
|
|
2586
|
+
".cjs",
|
|
2587
|
+
".css",
|
|
2588
|
+
".scss",
|
|
2589
|
+
".sass",
|
|
2590
|
+
".less"
|
|
2591
|
+
]);
|
|
2592
|
+
var PROTECTED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
2593
|
+
".git",
|
|
2594
|
+
".ssh",
|
|
2595
|
+
".spotpatch",
|
|
2596
|
+
"coverage",
|
|
2597
|
+
"dist",
|
|
2598
|
+
"node_modules"
|
|
2599
|
+
]);
|
|
2600
|
+
var PROTECTED_FILES = /* @__PURE__ */ new Set([
|
|
2601
|
+
".gitmodules",
|
|
2602
|
+
".git-credentials",
|
|
2603
|
+
".netrc",
|
|
2604
|
+
".npmrc",
|
|
2605
|
+
".pnpmrc",
|
|
2606
|
+
".pypirc",
|
|
2607
|
+
".yarnrc",
|
|
2608
|
+
"bun.lock",
|
|
2609
|
+
"bun.lockb",
|
|
2610
|
+
"npm-shrinkwrap.json",
|
|
2611
|
+
"package-lock.json",
|
|
2612
|
+
"pnpm-lock.yaml",
|
|
2613
|
+
"yarn.lock"
|
|
2614
|
+
]);
|
|
2615
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
2616
|
+
function deny(code) {
|
|
2617
|
+
throw new ContextualAskError(code);
|
|
2537
2618
|
}
|
|
2538
|
-
function
|
|
2539
|
-
const
|
|
2540
|
-
if (
|
|
2541
|
-
return
|
|
2542
|
-
}
|
|
2543
|
-
if (unwrapped.type !== "CallExpression") {
|
|
2544
|
-
return false;
|
|
2619
|
+
function toPosixRelative(realRoot, absolutePath) {
|
|
2620
|
+
const relative = import_node_path5.default.relative(realRoot, absolutePath);
|
|
2621
|
+
if (relative.length === 0 || relative === ".." || relative.startsWith(`..${import_node_path5.default.sep}`) || import_node_path5.default.isAbsolute(relative)) {
|
|
2622
|
+
return deny("ASK_SOURCE_SCOPE_DENIED");
|
|
2545
2623
|
}
|
|
2546
|
-
const
|
|
2547
|
-
|
|
2548
|
-
|
|
2624
|
+
const normalized = relative.split(import_node_path5.default.sep).join("/");
|
|
2625
|
+
const segments = normalized.toLowerCase().split("/");
|
|
2626
|
+
const fileName = segments.at(-1) ?? "";
|
|
2627
|
+
if (segments.some((segment) => PROTECTED_DIRECTORIES.has(segment)) || PROTECTED_FILES.has(fileName) || fileName === ".env" || fileName === ".envrc" || fileName === ".dev.vars" || fileName.startsWith(".env.") || fileName.endsWith(".pem") || fileName.endsWith(".key") || fileName.endsWith(".p12") || fileName.endsWith(".pfx") || fileName.startsWith("id_rsa")) {
|
|
2628
|
+
return deny("ASK_SOURCE_SCOPE_DENIED");
|
|
2549
2629
|
}
|
|
2550
|
-
|
|
2551
|
-
return firstArgument !== void 0 && firstArgument.type !== "SpreadElement" && (isFunctionExpression(firstArgument) || isSupportedComponentInitializer(firstArgument));
|
|
2630
|
+
return normalized;
|
|
2552
2631
|
}
|
|
2553
|
-
function
|
|
2554
|
-
|
|
2555
|
-
|
|
2632
|
+
async function assertNoSymlinkPath(realRoot, absolutePath) {
|
|
2633
|
+
const relative = toPosixRelative(realRoot, absolutePath);
|
|
2634
|
+
let current = realRoot;
|
|
2635
|
+
for (const segment of relative.split("/")) {
|
|
2636
|
+
current = import_node_path5.default.join(current, segment);
|
|
2637
|
+
const metadata = await (0, import_promises4.lstat)(current).catch(() => void 0);
|
|
2638
|
+
if (metadata === void 0 || metadata.isSymbolicLink()) {
|
|
2639
|
+
deny("ASK_SOURCE_SCOPE_DENIED");
|
|
2640
|
+
}
|
|
2556
2641
|
}
|
|
2557
|
-
return Object.freeze({ start: node.start, end: node.end, name: node.id.name });
|
|
2558
|
-
}
|
|
2559
|
-
function functionComponent(node) {
|
|
2560
|
-
return node.id !== null && isComponentName(node.id.name) && node.body !== null ? Object.freeze({ start: node.start, end: node.end, name: node.id.name }) : void 0;
|
|
2561
2642
|
}
|
|
2562
|
-
function
|
|
2563
|
-
|
|
2564
|
-
|
|
2643
|
+
async function readSecureSource(realRoot, absolutePath) {
|
|
2644
|
+
const normalizedAbsolute = import_node_path5.default.normalize(import_node_path5.default.resolve(absolutePath));
|
|
2645
|
+
const relativePath = toPosixRelative(realRoot, normalizedAbsolute);
|
|
2646
|
+
await assertNoSymlinkPath(realRoot, normalizedAbsolute);
|
|
2647
|
+
const canonical = await (0, import_promises4.realpath)(normalizedAbsolute).catch(() => void 0);
|
|
2648
|
+
if (canonical === void 0 || canonical !== normalizedAbsolute) {
|
|
2649
|
+
return deny("ASK_SOURCE_SCOPE_DENIED");
|
|
2565
2650
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2651
|
+
let handle;
|
|
2652
|
+
try {
|
|
2653
|
+
handle = await (0, import_promises4.open)(normalizedAbsolute, import_node_fs.constants.O_RDONLY | import_node_fs.constants.O_NOFOLLOW);
|
|
2654
|
+
const before = await handle.stat();
|
|
2655
|
+
if (!before.isFile() || before.nlink !== 1) {
|
|
2656
|
+
return deny("ASK_SOURCE_SCOPE_DENIED");
|
|
2657
|
+
}
|
|
2658
|
+
if (before.size > import_shared10.CONTEXTUAL_ASK_LIMITS.maximumReadBytesPerFile) {
|
|
2659
|
+
throw new ContextualAskError("ASK_LIMIT_EXCEEDED");
|
|
2660
|
+
}
|
|
2661
|
+
const bytes = await handle.readFile();
|
|
2662
|
+
const after = await handle.stat();
|
|
2663
|
+
if (bytes.byteLength !== before.size || before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs || bytes.includes(0)) {
|
|
2664
|
+
return deny("ASK_SOURCE_SCOPE_DENIED");
|
|
2665
|
+
}
|
|
2666
|
+
let content;
|
|
2667
|
+
try {
|
|
2668
|
+
content = decoder.decode(bytes);
|
|
2669
|
+
} catch (error) {
|
|
2670
|
+
throw new ContextualAskError("ASK_SOURCE_SCOPE_DENIED", { cause: error });
|
|
2671
|
+
}
|
|
2672
|
+
return Object.freeze({
|
|
2673
|
+
content,
|
|
2674
|
+
contentHash: (0, import_node_crypto9.createHash)("sha256").update(bytes).digest("hex"),
|
|
2675
|
+
relativePath,
|
|
2676
|
+
size: bytes.byteLength
|
|
2677
|
+
});
|
|
2678
|
+
} finally {
|
|
2679
|
+
await handle?.close().catch(() => void 0);
|
|
2569
2680
|
}
|
|
2570
|
-
return unwrapped.type === "MemberExpression" && !unwrapped.computed && unwrapped.object.type === "Identifier" && unwrapped.object.name === "React" && (unwrapped.property.name === "Component" || unwrapped.property.name === "PureComponent");
|
|
2571
2681
|
}
|
|
2572
|
-
function
|
|
2573
|
-
return
|
|
2682
|
+
function confidence(value) {
|
|
2683
|
+
return value === "exact" || value === "probable" ? value : "approximate";
|
|
2574
2684
|
}
|
|
2575
|
-
function
|
|
2576
|
-
const
|
|
2577
|
-
|
|
2578
|
-
|
|
2685
|
+
async function authorizeSeeds(selection, registry, root, realRoot) {
|
|
2686
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
2687
|
+
for (const target of selection.targets) {
|
|
2688
|
+
let fileId = target.react.source?.fileId ?? target.source.fileId;
|
|
2689
|
+
let sourceVersion;
|
|
2690
|
+
if (target.react.componentSourceId !== void 0) {
|
|
2691
|
+
const anchor = registry.resolveDataFlowComponent(target.react.componentSourceId);
|
|
2692
|
+
if (anchor === void 0 || target.react.sourceVersion !== anchor.sourceVersion) {
|
|
2693
|
+
deny("ASK_SELECTION_STALE");
|
|
2694
|
+
}
|
|
2695
|
+
fileId = anchor.fileId;
|
|
2696
|
+
sourceVersion = anchor.sourceVersion;
|
|
2697
|
+
}
|
|
2698
|
+
if (fileId === void 0) continue;
|
|
2699
|
+
const registered = registry.resolve(fileId);
|
|
2700
|
+
if (registered === void 0) deny("ASK_SELECTION_STALE");
|
|
2701
|
+
const canonical = await (0, import_promises4.realpath)(registered).catch(() => void 0);
|
|
2702
|
+
if (canonical === void 0) deny("ASK_SELECTION_STALE");
|
|
2703
|
+
const absolutePath = import_node_path5.default.normalize(canonical);
|
|
2704
|
+
const registeredRelative = toPosixRelative(realRoot, absolutePath);
|
|
2705
|
+
const claimedRelative = target.react.source?.relativePath ?? target.source.relativePath;
|
|
2706
|
+
if (claimedRelative !== void 0 && claimedRelative.split("\\").join("/") !== registeredRelative) {
|
|
2707
|
+
deny("ASK_SELECTION_STALE");
|
|
2708
|
+
}
|
|
2709
|
+
const existing = byPath.get(absolutePath);
|
|
2710
|
+
const targetIds = Object.freeze([
|
|
2711
|
+
...existing?.targetIds ?? [],
|
|
2712
|
+
...existing?.targetIds.includes(target.targetId) === true ? [] : [target.targetId]
|
|
2713
|
+
]);
|
|
2714
|
+
byPath.set(
|
|
2715
|
+
absolutePath,
|
|
2716
|
+
Object.freeze({
|
|
2717
|
+
absolutePath,
|
|
2718
|
+
fileId,
|
|
2719
|
+
confidence: confidence(
|
|
2720
|
+
target.react.source?.confidence ?? target.source.confidence
|
|
2721
|
+
),
|
|
2722
|
+
targetIds,
|
|
2723
|
+
...sourceVersion === void 0 ? {} : { sourceVersion }
|
|
2724
|
+
})
|
|
2725
|
+
);
|
|
2579
2726
|
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2727
|
+
for (const target of selection.targets) {
|
|
2728
|
+
for (const rule of target.styles.matchedRules) {
|
|
2729
|
+
const claimed = rule.source;
|
|
2730
|
+
if (claimed === void 0 || claimed.startsWith("/") || claimed.includes("\\") || claimed.includes("\0") || claimed.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
2731
|
+
continue;
|
|
2732
|
+
}
|
|
2733
|
+
const registeredPath = import_node_path5.default.resolve(root, ...claimed.split("/"));
|
|
2734
|
+
const fileId = registry.findRegisteredId(registeredPath);
|
|
2735
|
+
if (fileId === void 0) continue;
|
|
2736
|
+
const canonical = await (0, import_promises4.realpath)(registeredPath).catch(() => void 0);
|
|
2737
|
+
if (canonical === void 0) continue;
|
|
2738
|
+
const absolutePath = import_node_path5.default.normalize(canonical);
|
|
2739
|
+
toPosixRelative(realRoot, absolutePath);
|
|
2740
|
+
const existing = byPath.get(absolutePath);
|
|
2741
|
+
const targetIds = Object.freeze([
|
|
2742
|
+
...existing?.targetIds ?? [],
|
|
2743
|
+
...existing?.targetIds.includes(target.targetId) === true ? [] : [target.targetId]
|
|
2744
|
+
]);
|
|
2745
|
+
byPath.set(
|
|
2746
|
+
absolutePath,
|
|
2747
|
+
Object.freeze({
|
|
2748
|
+
absolutePath,
|
|
2749
|
+
fileId,
|
|
2750
|
+
confidence: existing?.confidence ?? "probable",
|
|
2751
|
+
targetIds,
|
|
2752
|
+
...existing?.sourceVersion === void 0 ? {} : { sourceVersion: existing.sourceVersion }
|
|
2753
|
+
})
|
|
2754
|
+
);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
if (byPath.size === 0) deny("ASK_SELECTION_REQUIRED");
|
|
2758
|
+
return [...byPath.values()];
|
|
2583
2759
|
}
|
|
2584
|
-
function
|
|
2585
|
-
const
|
|
2586
|
-
if (
|
|
2587
|
-
|
|
2760
|
+
function localSpecifiers(filePath, content) {
|
|
2761
|
+
const found = /* @__PURE__ */ new Set();
|
|
2762
|
+
if (/\.(?:css|scss|sass|less)$/iu.test(filePath)) {
|
|
2763
|
+
const pattern = /@import\s+(?:url\(\s*)?["']([^"']+)["']/gu;
|
|
2764
|
+
for (const match of content.matchAll(pattern)) {
|
|
2765
|
+
if (match[1]?.startsWith(".")) found.add(match[1]);
|
|
2766
|
+
}
|
|
2767
|
+
return [...found].sort();
|
|
2588
2768
|
}
|
|
2589
|
-
let parseResult;
|
|
2590
2769
|
try {
|
|
2591
|
-
|
|
2592
|
-
|
|
2770
|
+
const parsed = (0, import_oxc_parser.parseSync)(filePath, content, { sourceType: "module" });
|
|
2771
|
+
if (parsed.errors.length > 0) return [];
|
|
2772
|
+
const add = (value) => {
|
|
2773
|
+
if (typeof value === "string" && value.startsWith(".")) found.add(value);
|
|
2774
|
+
};
|
|
2775
|
+
const visitor = new import_oxc_parser.Visitor({
|
|
2776
|
+
ImportDeclaration(node) {
|
|
2777
|
+
add(node.source.value);
|
|
2778
|
+
},
|
|
2779
|
+
ExportNamedDeclaration(node) {
|
|
2780
|
+
add(node.source?.value);
|
|
2781
|
+
},
|
|
2782
|
+
ExportAllDeclaration(node) {
|
|
2783
|
+
add(node.source.value);
|
|
2784
|
+
},
|
|
2785
|
+
ImportExpression(node) {
|
|
2786
|
+
if ("value" in node.source) add(node.source.value);
|
|
2787
|
+
}
|
|
2593
2788
|
});
|
|
2789
|
+
visitor.visit(parsed.program);
|
|
2594
2790
|
} catch {
|
|
2595
|
-
return
|
|
2791
|
+
return [];
|
|
2596
2792
|
}
|
|
2597
|
-
|
|
2598
|
-
|
|
2793
|
+
return [...found].sort();
|
|
2794
|
+
}
|
|
2795
|
+
function importCandidates(importer, specifier) {
|
|
2796
|
+
const base = import_node_path5.default.resolve(import_node_path5.default.dirname(importer), specifier);
|
|
2797
|
+
if (import_node_path5.default.extname(base) !== "") return Object.freeze([base]);
|
|
2798
|
+
return Object.freeze([
|
|
2799
|
+
...SOURCE_EXTENSIONS.map((extension) => `${base}${extension}`),
|
|
2800
|
+
...SOURCE_EXTENSIONS.map((extension) => import_node_path5.default.join(base, `index${extension}`))
|
|
2801
|
+
]);
|
|
2802
|
+
}
|
|
2803
|
+
function countLines(content) {
|
|
2804
|
+
return content.length === 0 ? 1 : content.split("\n").length;
|
|
2805
|
+
}
|
|
2806
|
+
function publicSourceEntry(source) {
|
|
2807
|
+
return Object.freeze({
|
|
2808
|
+
handleId: source.handleId,
|
|
2809
|
+
fileId: source.fileId,
|
|
2810
|
+
relativePath: source.relativePath,
|
|
2811
|
+
label: source.label,
|
|
2812
|
+
lineCount: source.lineCount,
|
|
2813
|
+
size: source.size,
|
|
2814
|
+
contentHash: source.contentHash,
|
|
2815
|
+
confidence: source.confidence,
|
|
2816
|
+
targetIds: source.targetIds,
|
|
2817
|
+
...source.sourceVersion === void 0 ? {} : { sourceVersion: source.sourceVersion }
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
function assertSelectionEvidenceCurrent(selection, captured) {
|
|
2821
|
+
for (const target of selection.targets) {
|
|
2822
|
+
if (target.code === void 0) continue;
|
|
2823
|
+
const source = captured.find(
|
|
2824
|
+
(candidate) => candidate.relativePath === target.code?.relativePath
|
|
2825
|
+
);
|
|
2826
|
+
if (source === void 0) deny("ASK_SELECTION_STALE");
|
|
2827
|
+
const lines = source.content.split(/\r?\n/u);
|
|
2828
|
+
if (target.code.endLine > lines.length) deny("ASK_SELECTION_STALE");
|
|
2829
|
+
const currentExcerpt = lines.slice(target.code.startLine - 1, target.code.endLine).join("\n");
|
|
2830
|
+
if (currentExcerpt !== target.code.excerpt) deny("ASK_SELECTION_STALE");
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
async function captureAskReadSnapshot(options) {
|
|
2834
|
+
options.signal?.throwIfAborted();
|
|
2835
|
+
const realRoot = await (0, import_promises4.realpath)(options.root).catch(() => void 0);
|
|
2836
|
+
if (realRoot === void 0) deny("ASK_SOURCE_SCOPE_DENIED");
|
|
2837
|
+
const seeds = await authorizeSeeds(
|
|
2838
|
+
options.selection,
|
|
2839
|
+
options.registry,
|
|
2840
|
+
options.root,
|
|
2841
|
+
realRoot
|
|
2842
|
+
);
|
|
2843
|
+
const createHandleId = options.createHandleId ?? (() => (0, import_node_crypto9.randomBytes)(16).toString("base64url"));
|
|
2844
|
+
const captured = [];
|
|
2845
|
+
const capturedPaths = /* @__PURE__ */ new Set();
|
|
2846
|
+
const queue = [...seeds];
|
|
2847
|
+
let totalBytes = 0;
|
|
2848
|
+
let truncated = false;
|
|
2849
|
+
while (queue.length > 0) {
|
|
2850
|
+
options.signal?.throwIfAborted();
|
|
2851
|
+
const candidate = queue.shift();
|
|
2852
|
+
if (candidate === void 0 || capturedPaths.has(candidate.absolutePath)) continue;
|
|
2853
|
+
if (captured.length >= import_shared10.CONTEXTUAL_ASK_LIMITS.maximumReadFiles) {
|
|
2854
|
+
truncated = true;
|
|
2855
|
+
break;
|
|
2856
|
+
}
|
|
2857
|
+
let read;
|
|
2858
|
+
try {
|
|
2859
|
+
read = await readSecureSource(realRoot, candidate.absolutePath);
|
|
2860
|
+
options.signal?.throwIfAborted();
|
|
2861
|
+
} catch (error) {
|
|
2862
|
+
if (captured.length < seeds.length) throw error;
|
|
2863
|
+
truncated = true;
|
|
2864
|
+
continue;
|
|
2865
|
+
}
|
|
2866
|
+
if (totalBytes + read.size > import_shared10.CONTEXTUAL_ASK_LIMITS.maximumReadBytesTotal) {
|
|
2867
|
+
if (captured.length < seeds.length)
|
|
2868
|
+
throw new ContextualAskError("ASK_LIMIT_EXCEEDED");
|
|
2869
|
+
truncated = true;
|
|
2870
|
+
continue;
|
|
2871
|
+
}
|
|
2872
|
+
capturedPaths.add(candidate.absolutePath);
|
|
2873
|
+
totalBytes += read.size;
|
|
2874
|
+
const entry = Object.freeze({
|
|
2875
|
+
handleId: createHandleId(),
|
|
2876
|
+
fileId: candidate.fileId,
|
|
2877
|
+
relativePath: read.relativePath,
|
|
2878
|
+
label: import_node_path5.default.basename(read.relativePath),
|
|
2879
|
+
lineCount: countLines(read.content),
|
|
2880
|
+
size: read.size,
|
|
2881
|
+
contentHash: read.contentHash,
|
|
2882
|
+
confidence: candidate.confidence,
|
|
2883
|
+
targetIds: Object.freeze([...candidate.targetIds]),
|
|
2884
|
+
...candidate.sourceVersion === void 0 ? {} : { sourceVersion: candidate.sourceVersion },
|
|
2885
|
+
absolutePath: candidate.absolutePath,
|
|
2886
|
+
content: read.content
|
|
2887
|
+
});
|
|
2888
|
+
captured.push(entry);
|
|
2889
|
+
for (const specifier of localSpecifiers(candidate.absolutePath, read.content)) {
|
|
2890
|
+
options.signal?.throwIfAborted();
|
|
2891
|
+
for (const importPath of importCandidates(candidate.absolutePath, specifier)) {
|
|
2892
|
+
if (capturedPaths.has(importPath)) continue;
|
|
2893
|
+
const metadata = await (0, import_promises4.lstat)(importPath).catch(() => void 0);
|
|
2894
|
+
if (metadata?.isFile() !== true || metadata.isSymbolicLink()) continue;
|
|
2895
|
+
const fileId = options.registry.findRegisteredId(importPath) ?? options.registry.register(importPath);
|
|
2896
|
+
queue.push(
|
|
2897
|
+
Object.freeze({
|
|
2898
|
+
absolutePath: import_node_path5.default.normalize(importPath),
|
|
2899
|
+
fileId,
|
|
2900
|
+
confidence: "probable",
|
|
2901
|
+
targetIds: candidate.targetIds
|
|
2902
|
+
})
|
|
2903
|
+
);
|
|
2904
|
+
break;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2599
2907
|
}
|
|
2600
|
-
|
|
2601
|
-
const
|
|
2602
|
-
const
|
|
2603
|
-
|
|
2604
|
-
|
|
2908
|
+
assertSelectionEvidenceCurrent(options.selection, captured);
|
|
2909
|
+
const sourceByHandle = new Map(captured.map((source) => [source.handleId, source]));
|
|
2910
|
+
const publicEntries = Object.freeze(
|
|
2911
|
+
captured.map((source) => publicSourceEntry(source))
|
|
2912
|
+
);
|
|
2913
|
+
const contextHash = (0, import_node_crypto9.createHash)("sha256").update(
|
|
2914
|
+
JSON.stringify({
|
|
2915
|
+
selectionId: options.selection.selectionId,
|
|
2916
|
+
sources: publicEntries.map((source) => ({
|
|
2917
|
+
relativePath: source.relativePath,
|
|
2918
|
+
contentHash: source.contentHash,
|
|
2919
|
+
targetIds: source.targetIds,
|
|
2920
|
+
sourceVersion: source.sourceVersion ?? null
|
|
2921
|
+
}))
|
|
2922
|
+
})
|
|
2923
|
+
).digest("hex");
|
|
2924
|
+
let disposed = false;
|
|
2925
|
+
const requireSource = (handleId) => {
|
|
2926
|
+
if (disposed) throw new ContextualAskError("ASK_RESULT_EXPIRED");
|
|
2927
|
+
const source = sourceByHandle.get(handleId);
|
|
2928
|
+
if (source === void 0) throw new ContextualAskError("ASK_SOURCE_SCOPE_DENIED");
|
|
2929
|
+
return source;
|
|
2930
|
+
};
|
|
2931
|
+
const snapshot2 = Object.freeze({
|
|
2932
|
+
manifest: () => {
|
|
2933
|
+
if (disposed) throw new ContextualAskError("ASK_RESULT_EXPIRED");
|
|
2934
|
+
return publicEntries;
|
|
2605
2935
|
},
|
|
2606
|
-
|
|
2607
|
-
|
|
2936
|
+
read(handleId, range = {}) {
|
|
2937
|
+
const source = requireSource(handleId);
|
|
2938
|
+
const startLine = range.startLine ?? 1;
|
|
2939
|
+
const endLine = range.endLine ?? source.lineCount;
|
|
2940
|
+
if (!Number.isSafeInteger(startLine) || !Number.isSafeInteger(endLine) || startLine < 1 || endLine < startLine || endLine > source.lineCount) {
|
|
2941
|
+
throw new ContextualAskError("ASK_SOURCE_SCOPE_DENIED");
|
|
2942
|
+
}
|
|
2943
|
+
return Object.freeze({
|
|
2944
|
+
handleId,
|
|
2945
|
+
startLine,
|
|
2946
|
+
endLine,
|
|
2947
|
+
content: source.content.split("\n").slice(startLine - 1, endLine).join("\n")
|
|
2948
|
+
});
|
|
2608
2949
|
},
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2950
|
+
search(query) {
|
|
2951
|
+
if (query.length === 0 || query.length > import_shared10.CONTEXTUAL_ASK_LIMITS.maximumSearchQueryCharacters || query.includes("\0")) {
|
|
2952
|
+
throw new ContextualAskError("ASK_SOURCE_SCOPE_DENIED");
|
|
2953
|
+
}
|
|
2954
|
+
const normalized = query.toLocaleLowerCase("en-US");
|
|
2955
|
+
const matches = [];
|
|
2956
|
+
for (const source of captured) {
|
|
2957
|
+
for (const [index, line] of source.content.split("\n").entries()) {
|
|
2958
|
+
if (line.toLocaleLowerCase("en-US").includes(normalized)) {
|
|
2959
|
+
matches.push(
|
|
2960
|
+
Object.freeze({
|
|
2961
|
+
handleId: source.handleId,
|
|
2962
|
+
line: index + 1,
|
|
2963
|
+
preview: line.slice(
|
|
2964
|
+
0,
|
|
2965
|
+
import_shared10.CONTEXTUAL_ASK_LIMITS.maximumSearchPreviewCharacters
|
|
2966
|
+
)
|
|
2967
|
+
})
|
|
2968
|
+
);
|
|
2969
|
+
if (matches.length >= import_shared10.CONTEXTUAL_ASK_LIMITS.maximumSearchResults) {
|
|
2970
|
+
return Object.freeze(matches);
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2613
2974
|
}
|
|
2975
|
+
return Object.freeze(matches);
|
|
2976
|
+
}
|
|
2977
|
+
});
|
|
2978
|
+
return Object.freeze({
|
|
2979
|
+
grant: Object.freeze({ contextHash, truncated, sources: publicEntries }),
|
|
2980
|
+
snapshot: snapshot2,
|
|
2981
|
+
dispose() {
|
|
2982
|
+
disposed = true;
|
|
2983
|
+
sourceByHandle.clear();
|
|
2984
|
+
captured.splice(0, captured.length);
|
|
2614
2985
|
},
|
|
2615
|
-
|
|
2616
|
-
const
|
|
2617
|
-
|
|
2618
|
-
|
|
2986
|
+
async isStale() {
|
|
2987
|
+
for (const source of captured) {
|
|
2988
|
+
try {
|
|
2989
|
+
const current = await readSecureSource(realRoot, source.absolutePath);
|
|
2990
|
+
if (current.contentHash !== source.contentHash) return true;
|
|
2991
|
+
} catch {
|
|
2992
|
+
return true;
|
|
2993
|
+
}
|
|
2619
2994
|
}
|
|
2995
|
+
return false;
|
|
2620
2996
|
},
|
|
2621
|
-
|
|
2622
|
-
const
|
|
2623
|
-
if (
|
|
2624
|
-
|
|
2997
|
+
projectCitation(handleId, startLine, endLine) {
|
|
2998
|
+
const source = requireSource(handleId);
|
|
2999
|
+
if (!Number.isSafeInteger(startLine) || !Number.isSafeInteger(endLine) || startLine < 1 || endLine < startLine || endLine > source.lineCount) {
|
|
3000
|
+
throw new ContextualAskError("ASK_ANSWER_INVALID");
|
|
2625
3001
|
}
|
|
3002
|
+
return Object.freeze({ ...publicSourceEntry(source), startLine, endLine });
|
|
2626
3003
|
}
|
|
2627
3004
|
});
|
|
2628
|
-
visitor.visit(parseResult.program);
|
|
2629
|
-
const selectedJsx = jsxNodes.filter((node) => node.start <= offset && node.end >= offset).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
|
|
2630
|
-
if (selectedJsx === void 0) {
|
|
2631
|
-
return void 0;
|
|
2632
|
-
}
|
|
2633
|
-
return components.filter(
|
|
2634
|
-
(component) => component.start <= selectedJsx.start && component.end >= selectedJsx.end
|
|
2635
|
-
).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
|
|
2636
|
-
}
|
|
2637
|
-
function lineAtOffset(source, offset) {
|
|
2638
|
-
let line = 1;
|
|
2639
|
-
for (let index = 0; index < offset; index += 1) {
|
|
2640
|
-
if (source[index] === "\n") {
|
|
2641
|
-
line += 1;
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
return line;
|
|
2645
3005
|
}
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
3006
|
+
|
|
3007
|
+
// src/contextual-ask/manager.ts
|
|
3008
|
+
var ACTIVE_STATUSES = /* @__PURE__ */ new Set([
|
|
3009
|
+
"queued",
|
|
3010
|
+
"authorizing",
|
|
3011
|
+
"running",
|
|
3012
|
+
"cancelling"
|
|
3013
|
+
]);
|
|
3014
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["answered", "cancelled", "failed"]);
|
|
3015
|
+
var DEFAULT_DEPENDENCIES2 = Object.freeze({
|
|
3016
|
+
captureSnapshot: captureAskReadSnapshot,
|
|
3017
|
+
createId: () => (0, import_node_crypto10.randomBytes)(16).toString("base64url"),
|
|
3018
|
+
now: () => /* @__PURE__ */ new Date()
|
|
3019
|
+
});
|
|
3020
|
+
function fingerprint(request) {
|
|
3021
|
+
return (0, import_node_crypto10.createHash)("sha256").update(
|
|
3022
|
+
JSON.stringify({
|
|
3023
|
+
envelope: request.envelope,
|
|
3024
|
+
executorId: request.executorId,
|
|
3025
|
+
providerDataConsent: request.providerDataConsent
|
|
3026
|
+
})
|
|
3027
|
+
).digest("hex");
|
|
3028
|
+
}
|
|
3029
|
+
function fileCountBucket(count) {
|
|
3030
|
+
if (count <= 1) return "one";
|
|
3031
|
+
if (count <= 5) return "few";
|
|
3032
|
+
return "many";
|
|
3033
|
+
}
|
|
3034
|
+
function raceWithAbort(operation, signal) {
|
|
3035
|
+
const reason = () => signal.reason instanceof Error ? signal.reason : new ContextualAskError("ASK_CANCELLED");
|
|
3036
|
+
if (signal.aborted) return Promise.reject(reason());
|
|
3037
|
+
return new Promise((resolve, reject) => {
|
|
3038
|
+
const onAbort = () => {
|
|
3039
|
+
reject(reason());
|
|
3040
|
+
};
|
|
3041
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3042
|
+
operation.then(
|
|
3043
|
+
(value) => {
|
|
3044
|
+
signal.removeEventListener("abort", onAbort);
|
|
3045
|
+
resolve(value);
|
|
3046
|
+
},
|
|
3047
|
+
(error) => {
|
|
3048
|
+
signal.removeEventListener("abort", onAbort);
|
|
3049
|
+
reject(
|
|
3050
|
+
error instanceof Error ? error : new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE", { cause: error })
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
);
|
|
2650
3054
|
});
|
|
2651
3055
|
}
|
|
2652
|
-
function
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
3056
|
+
function createContextualAskManager(options) {
|
|
3057
|
+
const dependencies = Object.freeze({
|
|
3058
|
+
...DEFAULT_DEPENDENCIES2,
|
|
3059
|
+
...options.dependencies
|
|
3060
|
+
});
|
|
3061
|
+
const executors = /* @__PURE__ */ new Map();
|
|
3062
|
+
for (const executor of options.executors ?? []) {
|
|
3063
|
+
if (executors.has(executor.executorId)) {
|
|
3064
|
+
throw new TypeError("Contextual Ask executor IDs must be unique.");
|
|
3065
|
+
}
|
|
3066
|
+
executors.set(executor.executorId, executor);
|
|
2658
3067
|
}
|
|
2659
|
-
const
|
|
2660
|
-
const
|
|
2661
|
-
const
|
|
2662
|
-
|
|
2663
|
-
|
|
3068
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
3069
|
+
const requests = /* @__PURE__ */ new Map();
|
|
3070
|
+
const pendingRequests = /* @__PURE__ */ new Map();
|
|
3071
|
+
let closed = false;
|
|
3072
|
+
const isClosed = () => closed;
|
|
3073
|
+
const cleanupExpired = () => {
|
|
3074
|
+
const now = dependencies.now().getTime();
|
|
3075
|
+
for (const [jobId, job] of jobs) {
|
|
3076
|
+
if (job.expiresAt !== void 0 && now >= job.expiresAt) {
|
|
3077
|
+
job.snapshot?.dispose();
|
|
3078
|
+
jobs.delete(jobId);
|
|
3079
|
+
const request = requests.get(job.request.requestId);
|
|
3080
|
+
if (request?.jobId === jobId) requests.delete(job.request.requestId);
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
const requireJob = (jobId) => {
|
|
3085
|
+
cleanupExpired();
|
|
3086
|
+
const job = jobs.get(jobId);
|
|
3087
|
+
if (job === void 0) throw new ContextualAskError("ASK_RESULT_EXPIRED");
|
|
3088
|
+
return job;
|
|
3089
|
+
};
|
|
3090
|
+
const publicSnapshot = (job) => import_shared11.askJobSnapshotSchema.parse({
|
|
3091
|
+
schemaVersion: import_shared11.CONTEXTUAL_ASK_SCHEMA_VERSION,
|
|
3092
|
+
jobId: job.id,
|
|
3093
|
+
selectionId: job.request.envelope.selection.selectionId,
|
|
3094
|
+
status: job.status,
|
|
3095
|
+
executor: {
|
|
3096
|
+
executorId: job.executorCapability.executorId,
|
|
3097
|
+
kind: job.executorCapability.kind,
|
|
3098
|
+
label: job.executorCapability.label,
|
|
3099
|
+
modelLabel: job.executorCapability.effectiveModelLabel
|
|
3100
|
+
},
|
|
3101
|
+
...job.phaseMessage === void 0 ? {} : { phaseMessage: job.phaseMessage },
|
|
3102
|
+
createdAt: job.createdAt,
|
|
3103
|
+
updatedAt: job.updatedAt,
|
|
3104
|
+
canCancel: ["queued", "authorizing", "running"].includes(job.status),
|
|
3105
|
+
...job.errorCode === void 0 ? {} : { errorCode: job.errorCode }
|
|
3106
|
+
});
|
|
3107
|
+
const appendEvent = (job, event) => {
|
|
3108
|
+
job.events.push(import_shared11.askJobEventSchema.parse(event));
|
|
3109
|
+
if (job.events.length > import_shared11.CONTEXTUAL_ASK_LIMITS.maximumRetainedEvents) {
|
|
3110
|
+
job.events.splice(
|
|
3111
|
+
0,
|
|
3112
|
+
job.events.length - import_shared11.CONTEXTUAL_ASK_LIMITS.maximumRetainedEvents
|
|
3113
|
+
);
|
|
3114
|
+
}
|
|
3115
|
+
for (const listener of job.listeners) listener(event);
|
|
3116
|
+
};
|
|
3117
|
+
const eventBase = (job) => {
|
|
3118
|
+
job.sequence += 1;
|
|
3119
|
+
return {
|
|
3120
|
+
schemaVersion: import_shared11.CONTEXTUAL_ASK_SCHEMA_VERSION,
|
|
3121
|
+
sequence: job.sequence,
|
|
3122
|
+
jobId: job.id,
|
|
3123
|
+
status: job.status,
|
|
3124
|
+
timestamp: dependencies.now().toISOString()
|
|
3125
|
+
};
|
|
3126
|
+
};
|
|
3127
|
+
const emitSnapshot = (job) => {
|
|
3128
|
+
appendEvent(job, {
|
|
3129
|
+
...eventBase(job),
|
|
3130
|
+
type: "snapshot",
|
|
3131
|
+
snapshot: publicSnapshot(job)
|
|
3132
|
+
});
|
|
3133
|
+
};
|
|
3134
|
+
const emitPhase = (job, message) => {
|
|
3135
|
+
appendEvent(job, { ...eventBase(job), type: "phase", message });
|
|
3136
|
+
};
|
|
3137
|
+
const emitReadActivity = (job, entry, state) => {
|
|
3138
|
+
appendEvent(job, {
|
|
3139
|
+
...eventBase(job),
|
|
3140
|
+
type: "read-activity",
|
|
3141
|
+
activity: entry === void 0 ? {
|
|
3142
|
+
kind: "file-count",
|
|
3143
|
+
bucket: fileCountBucket(job.snapshot?.grant.sources.length ?? 0)
|
|
3144
|
+
} : {
|
|
3145
|
+
kind: "source",
|
|
3146
|
+
sourceId: entry.fileId,
|
|
3147
|
+
relativePath: entry.relativePath
|
|
3148
|
+
},
|
|
3149
|
+
state
|
|
3150
|
+
});
|
|
3151
|
+
};
|
|
3152
|
+
const transition = (job, status, phaseMessage, errorCode) => {
|
|
3153
|
+
job.status = status;
|
|
3154
|
+
job.phaseMessage = phaseMessage;
|
|
3155
|
+
job.errorCode = errorCode;
|
|
3156
|
+
job.updatedAt = dependencies.now().toISOString();
|
|
3157
|
+
emitSnapshot(job);
|
|
3158
|
+
if (!TERMINAL_STATUSES.has(status)) emitPhase(job, phaseMessage);
|
|
3159
|
+
};
|
|
3160
|
+
const finish = (job) => {
|
|
3161
|
+
job.expiresAt = dependencies.now().getTime() + import_shared11.CONTEXTUAL_ASK_LIMITS.resultTtlMs;
|
|
3162
|
+
job.lease.release();
|
|
3163
|
+
job.snapshot?.dispose();
|
|
3164
|
+
job.snapshot = void 0;
|
|
3165
|
+
};
|
|
3166
|
+
const fail = (job, error) => {
|
|
3167
|
+
if (TERMINAL_STATUSES.has(job.status)) return;
|
|
3168
|
+
const normalized = asContextualAskError(error);
|
|
3169
|
+
const abortReason = job.controller.signal.reason;
|
|
3170
|
+
const cancelled = normalized.code === "ASK_CANCELLED" || abortReason instanceof ContextualAskError && abortReason.code === "ASK_CANCELLED";
|
|
3171
|
+
transition(
|
|
3172
|
+
job,
|
|
3173
|
+
cancelled ? "cancelled" : "failed",
|
|
3174
|
+
cancelled ? "Question cancelled." : "Question failed.",
|
|
3175
|
+
cancelled ? "ASK_CANCELLED" : normalized.code
|
|
3176
|
+
);
|
|
3177
|
+
appendEvent(job, {
|
|
3178
|
+
...eventBase(job),
|
|
3179
|
+
type: "error",
|
|
3180
|
+
errorCode: cancelled ? "ASK_CANCELLED" : normalized.code
|
|
3181
|
+
});
|
|
3182
|
+
finish(job);
|
|
3183
|
+
};
|
|
3184
|
+
const instrumentSnapshot = (job, snapshot2, onLimitExceeded) => {
|
|
3185
|
+
const byHandle = new Map(
|
|
3186
|
+
snapshot2.manifest().map((entry) => [entry.handleId, entry])
|
|
3187
|
+
);
|
|
3188
|
+
let readOperations = 0;
|
|
3189
|
+
const beginOperation = () => {
|
|
3190
|
+
readOperations += 1;
|
|
3191
|
+
if (readOperations > import_shared11.CONTEXTUAL_ASK_LIMITS.maximumToolCalls) {
|
|
3192
|
+
onLimitExceeded();
|
|
3193
|
+
throw new ContextualAskError("ASK_LIMIT_EXCEEDED");
|
|
3194
|
+
}
|
|
3195
|
+
};
|
|
3196
|
+
return Object.freeze({
|
|
3197
|
+
manifest: () => snapshot2.manifest(),
|
|
3198
|
+
read(handleId, range) {
|
|
3199
|
+
beginOperation();
|
|
3200
|
+
const entry = byHandle.get(handleId);
|
|
3201
|
+
if (job.status === "running") emitReadActivity(job, entry, "started");
|
|
3202
|
+
try {
|
|
3203
|
+
const result = snapshot2.read(handleId, range);
|
|
3204
|
+
if (job.status === "running") emitReadActivity(job, entry, "succeeded");
|
|
3205
|
+
return result;
|
|
3206
|
+
} catch (error) {
|
|
3207
|
+
if (job.status === "running") emitReadActivity(job, entry, "failed");
|
|
3208
|
+
throw error;
|
|
3209
|
+
}
|
|
3210
|
+
},
|
|
3211
|
+
search(query) {
|
|
3212
|
+
beginOperation();
|
|
3213
|
+
if (job.status === "running") emitReadActivity(job, void 0, "started");
|
|
3214
|
+
try {
|
|
3215
|
+
const result = snapshot2.search(query);
|
|
3216
|
+
if (job.status === "running") {
|
|
3217
|
+
emitReadActivity(job, void 0, "succeeded");
|
|
3218
|
+
}
|
|
3219
|
+
return result;
|
|
3220
|
+
} catch (error) {
|
|
3221
|
+
if (job.status === "running") emitReadActivity(job, void 0, "failed");
|
|
3222
|
+
throw error;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
3226
|
+
};
|
|
3227
|
+
const projectAnswer = async (job, draft, captured) => {
|
|
3228
|
+
const sourceIds = /* @__PURE__ */ new Map();
|
|
3229
|
+
const sources = /* @__PURE__ */ new Map();
|
|
3230
|
+
const projectCitations = (citations) => citations.map((citation) => {
|
|
3231
|
+
const key = `${citation.handleId}:${String(citation.startLine)}:${String(citation.endLine)}`;
|
|
3232
|
+
let sourceId = sourceIds.get(key);
|
|
3233
|
+
if (sourceId === void 0) {
|
|
3234
|
+
sourceId = dependencies.createId();
|
|
3235
|
+
sourceIds.set(key, sourceId);
|
|
3236
|
+
sources.set(
|
|
3237
|
+
sourceId,
|
|
3238
|
+
captured.projectCitation(
|
|
3239
|
+
citation.handleId,
|
|
3240
|
+
citation.startLine,
|
|
3241
|
+
citation.endLine
|
|
3242
|
+
)
|
|
3243
|
+
);
|
|
3244
|
+
}
|
|
3245
|
+
return sourceId;
|
|
3246
|
+
});
|
|
3247
|
+
const blocks = draft.blocks.map((block) => {
|
|
3248
|
+
if (block.kind === "list") {
|
|
3249
|
+
return {
|
|
3250
|
+
kind: "list",
|
|
3251
|
+
items: block.items.map((item) => ({
|
|
3252
|
+
text: item.text,
|
|
3253
|
+
sourceIds: projectCitations(item.citations)
|
|
3254
|
+
}))
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
return Object.freeze({
|
|
3258
|
+
kind: block.kind,
|
|
3259
|
+
...block.kind === "paragraph" ? { text: block.text } : { code: block.code },
|
|
3260
|
+
...block.kind === "code" && block.language !== void 0 ? { language: block.language } : {},
|
|
3261
|
+
sourceIds: projectCitations(block.citations)
|
|
3262
|
+
});
|
|
3263
|
+
});
|
|
3264
|
+
const warnings = [...draft.warnings];
|
|
3265
|
+
if (captured.grant.truncated && !warnings.some((warning) => warning.code === "source-truncated")) {
|
|
3266
|
+
warnings.push(Object.freeze({ code: "source-truncated" }));
|
|
3267
|
+
}
|
|
3268
|
+
if (await captured.isStale() && !warnings.some((warning) => warning.code === "source-stale")) {
|
|
3269
|
+
warnings.push(Object.freeze({ code: "source-stale" }));
|
|
3270
|
+
}
|
|
3271
|
+
const created = dependencies.now();
|
|
3272
|
+
return import_shared11.askAnswerResultSchema.parse({
|
|
3273
|
+
schemaVersion: import_shared11.CONTEXTUAL_ASK_SCHEMA_VERSION,
|
|
3274
|
+
jobId: job.id,
|
|
3275
|
+
selectionId: job.request.envelope.selection.selectionId,
|
|
3276
|
+
contextHash: captured.grant.contextHash,
|
|
3277
|
+
executor: {
|
|
3278
|
+
executorId: job.executorCapability.executorId,
|
|
3279
|
+
kind: job.executorCapability.kind,
|
|
3280
|
+
label: job.executorCapability.label,
|
|
3281
|
+
modelLabel: job.executorCapability.effectiveModelLabel
|
|
3282
|
+
},
|
|
3283
|
+
blocks,
|
|
3284
|
+
sources: [...sources].map(([sourceId, source]) => ({
|
|
3285
|
+
sourceId,
|
|
3286
|
+
label: source.label,
|
|
3287
|
+
relativePath: source.relativePath,
|
|
3288
|
+
fileId: source.fileId,
|
|
3289
|
+
startLine: source.startLine,
|
|
3290
|
+
endLine: source.endLine,
|
|
3291
|
+
confidence: source.confidence,
|
|
3292
|
+
targetIds: source.targetIds,
|
|
3293
|
+
...source.sourceVersion === void 0 ? {} : { sourceVersion: source.sourceVersion },
|
|
3294
|
+
contentHash: source.contentHash
|
|
3295
|
+
})),
|
|
3296
|
+
warnings,
|
|
3297
|
+
createdAt: created.toISOString(),
|
|
3298
|
+
expiresAt: new Date(
|
|
3299
|
+
created.getTime() + import_shared11.CONTEXTUAL_ASK_LIMITS.resultTtlMs
|
|
3300
|
+
).toISOString()
|
|
3301
|
+
});
|
|
3302
|
+
};
|
|
3303
|
+
const run = async (job) => {
|
|
3304
|
+
const timeout = setTimeout(() => {
|
|
3305
|
+
job.controller.abort(new ContextualAskError("ASK_TIMEOUT"));
|
|
3306
|
+
}, import_shared11.CONTEXTUAL_ASK_LIMITS.jobTimeoutMs);
|
|
3307
|
+
timeout.unref();
|
|
3308
|
+
try {
|
|
3309
|
+
transition(job, "authorizing", "Authorizing selected sources.");
|
|
3310
|
+
const captured = await dependencies.captureSnapshot({
|
|
3311
|
+
root: options.root,
|
|
3312
|
+
registry: options.registry,
|
|
3313
|
+
selection: job.request.envelope.selection,
|
|
3314
|
+
signal: job.controller.signal,
|
|
3315
|
+
createHandleId: dependencies.createId
|
|
3316
|
+
});
|
|
3317
|
+
if (job.controller.signal.aborted) {
|
|
3318
|
+
captured.dispose();
|
|
3319
|
+
}
|
|
3320
|
+
job.controller.signal.throwIfAborted();
|
|
3321
|
+
job.snapshot = captured;
|
|
3322
|
+
transition(job, "running", "Analyzing the authorized snapshot.");
|
|
3323
|
+
emitReadActivity(job, void 0, "succeeded");
|
|
3324
|
+
const readLimit = { exceeded: false };
|
|
3325
|
+
const execution = await raceWithAbort(
|
|
3326
|
+
job.executor.execute(
|
|
3327
|
+
{
|
|
3328
|
+
jobId: job.id,
|
|
3329
|
+
envelope: job.request.envelope,
|
|
3330
|
+
grant: captured.grant,
|
|
3331
|
+
snapshot: instrumentSnapshot(job, captured.snapshot, () => {
|
|
3332
|
+
readLimit.exceeded = true;
|
|
3333
|
+
})
|
|
3334
|
+
},
|
|
3335
|
+
job.controller.signal
|
|
3336
|
+
),
|
|
3337
|
+
job.controller.signal
|
|
3338
|
+
);
|
|
3339
|
+
const draft = import_shared11.askAnswerDraftSchema.parse(execution);
|
|
3340
|
+
const effectiveModelLabel = job.executor.effectiveModelLabel?.();
|
|
3341
|
+
if (effectiveModelLabel !== void 0) {
|
|
3342
|
+
job.executorCapability = import_shared11.contextualAskExecutorCapabilitySchema.parse({
|
|
3343
|
+
...job.executorCapability,
|
|
3344
|
+
effectiveModelLabel
|
|
3345
|
+
});
|
|
3346
|
+
}
|
|
3347
|
+
if (readLimit.exceeded) throw new ContextualAskError("ASK_LIMIT_EXCEEDED");
|
|
3348
|
+
job.controller.signal.throwIfAborted();
|
|
3349
|
+
const result = await projectAnswer(job, draft, captured);
|
|
3350
|
+
job.controller.signal.throwIfAborted();
|
|
3351
|
+
job.result = result;
|
|
3352
|
+
transition(job, "answered", "Answer ready.");
|
|
3353
|
+
appendEvent(job, { ...eventBase(job), type: "answer-ready" });
|
|
3354
|
+
finish(job);
|
|
3355
|
+
} catch (error) {
|
|
3356
|
+
fail(
|
|
3357
|
+
job,
|
|
3358
|
+
error instanceof Error && error.name === "ZodError" ? new ContextualAskError("ASK_ANSWER_INVALID", { cause: error }) : asContextualAskError(error)
|
|
3359
|
+
);
|
|
3360
|
+
} finally {
|
|
3361
|
+
clearTimeout(timeout);
|
|
3362
|
+
}
|
|
3363
|
+
};
|
|
3364
|
+
return Object.freeze({
|
|
3365
|
+
async capability(signal) {
|
|
3366
|
+
if (!options.enabled || closed) {
|
|
3367
|
+
return import_shared11.contextualAskCapabilitySchema.parse({
|
|
3368
|
+
schemaVersion: import_shared11.CONTEXTUAL_ASK_SCHEMA_VERSION,
|
|
3369
|
+
enabled: false,
|
|
3370
|
+
executors: [],
|
|
3371
|
+
safety: {
|
|
3372
|
+
selectionRequired: true,
|
|
3373
|
+
singleTurn: true,
|
|
3374
|
+
writesAllowed: false,
|
|
3375
|
+
historyStored: false
|
|
3376
|
+
},
|
|
3377
|
+
checkedAt: dependencies.now().toISOString()
|
|
3378
|
+
});
|
|
3379
|
+
}
|
|
3380
|
+
const capabilities = (await Promise.all(
|
|
3381
|
+
[...executors.values()].map(async (executor) => {
|
|
3382
|
+
try {
|
|
3383
|
+
return import_shared11.contextualAskExecutorCapabilitySchema.parse(
|
|
3384
|
+
await raceWithAbort(executor.capability(signal), signal)
|
|
3385
|
+
);
|
|
3386
|
+
} catch {
|
|
3387
|
+
return void 0;
|
|
3388
|
+
}
|
|
3389
|
+
})
|
|
3390
|
+
)).filter((capability) => capability !== void 0);
|
|
3391
|
+
return import_shared11.contextualAskCapabilitySchema.parse({
|
|
3392
|
+
schemaVersion: import_shared11.CONTEXTUAL_ASK_SCHEMA_VERSION,
|
|
3393
|
+
enabled: true,
|
|
3394
|
+
executors: capabilities,
|
|
3395
|
+
safety: {
|
|
3396
|
+
selectionRequired: true,
|
|
3397
|
+
singleTurn: true,
|
|
3398
|
+
writesAllowed: false,
|
|
3399
|
+
historyStored: false
|
|
3400
|
+
},
|
|
3401
|
+
checkedAt: dependencies.now().toISOString()
|
|
3402
|
+
});
|
|
3403
|
+
},
|
|
3404
|
+
async create(rawRequest) {
|
|
3405
|
+
cleanupExpired();
|
|
3406
|
+
if (!options.enabled || closed) throw new ContextualAskError("ASK_DISABLED");
|
|
3407
|
+
const parsed = import_shared11.askJobCreateRequestSchema.safeParse(rawRequest);
|
|
3408
|
+
if (!parsed.success) throw new ContextualAskError("ASK_QUESTION_INVALID");
|
|
3409
|
+
const request = parsed.data;
|
|
3410
|
+
const requestFingerprint = fingerprint(request);
|
|
3411
|
+
const previous = requests.get(request.requestId);
|
|
3412
|
+
if (previous !== void 0) {
|
|
3413
|
+
if (previous.fingerprint !== requestFingerprint) {
|
|
3414
|
+
throw new ContextualAskError("ASK_IDEMPOTENCY_CONFLICT");
|
|
3415
|
+
}
|
|
3416
|
+
return publicSnapshot(requireJob(previous.jobId));
|
|
3417
|
+
}
|
|
3418
|
+
const pending = pendingRequests.get(request.requestId);
|
|
3419
|
+
if (pending !== void 0) {
|
|
3420
|
+
if (pending.fingerprint !== requestFingerprint) {
|
|
3421
|
+
throw new ContextualAskError("ASK_IDEMPOTENCY_CONFLICT");
|
|
3422
|
+
}
|
|
3423
|
+
return await pending.promise;
|
|
3424
|
+
}
|
|
3425
|
+
if ([...jobs.values()].some((job) => ACTIVE_STATUSES.has(job.status))) {
|
|
3426
|
+
throw new ContextualAskError("ASK_BUSY");
|
|
3427
|
+
}
|
|
3428
|
+
const executor = executors.get(request.executorId);
|
|
3429
|
+
if (executor === void 0)
|
|
3430
|
+
throw new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE");
|
|
3431
|
+
const lease = options.coordinator.acquire("ask");
|
|
3432
|
+
if (lease === void 0) throw new ContextualAskError("ASK_BUSY");
|
|
3433
|
+
const controller = new AbortController();
|
|
3434
|
+
let leaseTransferred = false;
|
|
3435
|
+
const creation = (async () => {
|
|
3436
|
+
try {
|
|
3437
|
+
const capabilitySignal = AbortSignal.any([
|
|
3438
|
+
controller.signal,
|
|
3439
|
+
AbortSignal.timeout(import_shared11.CONTEXTUAL_ASK_LIMITS.capabilityTimeoutMs)
|
|
3440
|
+
]);
|
|
3441
|
+
const capability = import_shared11.contextualAskExecutorCapabilitySchema.parse(
|
|
3442
|
+
await raceWithAbort(
|
|
3443
|
+
executor.capability(capabilitySignal),
|
|
3444
|
+
capabilitySignal
|
|
3445
|
+
)
|
|
3446
|
+
);
|
|
3447
|
+
if (isClosed()) throw new ContextualAskError("ASK_DISABLED");
|
|
3448
|
+
if (capability.executorId !== executor.executorId || capability.state !== "ready" || !capability.readOnlyProven) {
|
|
3449
|
+
throw new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE");
|
|
3450
|
+
}
|
|
3451
|
+
for (const [jobId, existing] of jobs) {
|
|
3452
|
+
if (TERMINAL_STATUSES.has(existing.status)) {
|
|
3453
|
+
existing.snapshot?.dispose();
|
|
3454
|
+
jobs.delete(jobId);
|
|
3455
|
+
requests.delete(existing.request.requestId);
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
const createdAt = dependencies.now().toISOString();
|
|
3459
|
+
const job = {
|
|
3460
|
+
controller: new AbortController(),
|
|
3461
|
+
createdAt,
|
|
3462
|
+
events: [],
|
|
3463
|
+
executor,
|
|
3464
|
+
executorCapability: capability,
|
|
3465
|
+
fingerprint: requestFingerprint,
|
|
3466
|
+
id: dependencies.createId(),
|
|
3467
|
+
lease,
|
|
3468
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
3469
|
+
request,
|
|
3470
|
+
errorCode: void 0,
|
|
3471
|
+
expiresAt: void 0,
|
|
3472
|
+
phaseMessage: "Question queued.",
|
|
3473
|
+
result: void 0,
|
|
3474
|
+
runPromise: void 0,
|
|
3475
|
+
sequence: 0,
|
|
3476
|
+
snapshot: void 0,
|
|
3477
|
+
status: "queued",
|
|
3478
|
+
updatedAt: createdAt
|
|
3479
|
+
};
|
|
3480
|
+
jobs.set(job.id, job);
|
|
3481
|
+
requests.set(request.requestId, {
|
|
3482
|
+
fingerprint: requestFingerprint,
|
|
3483
|
+
jobId: job.id
|
|
3484
|
+
});
|
|
3485
|
+
leaseTransferred = true;
|
|
3486
|
+
emitSnapshot(job);
|
|
3487
|
+
emitPhase(job, "Question queued.");
|
|
3488
|
+
job.runPromise = run(job);
|
|
3489
|
+
return publicSnapshot(job);
|
|
3490
|
+
} catch (error) {
|
|
3491
|
+
throw error instanceof ContextualAskError ? error : new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE", {
|
|
3492
|
+
cause: error
|
|
3493
|
+
});
|
|
3494
|
+
} finally {
|
|
3495
|
+
if (!leaseTransferred) lease.release();
|
|
3496
|
+
pendingRequests.delete(request.requestId);
|
|
3497
|
+
}
|
|
3498
|
+
})();
|
|
3499
|
+
pendingRequests.set(request.requestId, {
|
|
3500
|
+
controller,
|
|
3501
|
+
fingerprint: requestFingerprint,
|
|
3502
|
+
promise: creation
|
|
3503
|
+
});
|
|
3504
|
+
return await creation;
|
|
3505
|
+
},
|
|
3506
|
+
cancel(jobId) {
|
|
3507
|
+
const job = requireJob(jobId);
|
|
3508
|
+
if (TERMINAL_STATUSES.has(job.status)) return publicSnapshot(job);
|
|
3509
|
+
if (job.status !== "cancelling") {
|
|
3510
|
+
transition(job, "cancelling", "Cancelling question.");
|
|
3511
|
+
job.controller.abort(new ContextualAskError("ASK_CANCELLED"));
|
|
3512
|
+
job.snapshot?.dispose();
|
|
3513
|
+
job.snapshot = void 0;
|
|
3514
|
+
}
|
|
3515
|
+
return publicSnapshot(job);
|
|
3516
|
+
},
|
|
3517
|
+
events(jobId, afterSequence = 0) {
|
|
3518
|
+
if (!Number.isSafeInteger(afterSequence) || afterSequence < 0) {
|
|
3519
|
+
throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
|
|
3520
|
+
}
|
|
3521
|
+
return Object.freeze(
|
|
3522
|
+
requireJob(jobId).events.filter((event) => event.sequence > afterSequence)
|
|
3523
|
+
);
|
|
3524
|
+
},
|
|
3525
|
+
async result(jobId) {
|
|
3526
|
+
const job = requireJob(jobId);
|
|
3527
|
+
await job.runPromise;
|
|
3528
|
+
const current = requireJob(jobId);
|
|
3529
|
+
return import_shared11.askJobResultResponseSchema.parse({
|
|
3530
|
+
snapshot: publicSnapshot(current),
|
|
3531
|
+
...current.result === void 0 ? {} : { result: current.result }
|
|
3532
|
+
});
|
|
3533
|
+
},
|
|
3534
|
+
snapshot(jobId) {
|
|
3535
|
+
return publicSnapshot(requireJob(jobId));
|
|
3536
|
+
},
|
|
3537
|
+
subscribe(jobId, listener) {
|
|
3538
|
+
const job = requireJob(jobId);
|
|
3539
|
+
if (job.listeners.size >= import_shared11.CONTEXTUAL_ASK_LIMITS.maximumEventSubscribers) {
|
|
3540
|
+
throw new ContextualAskError("ASK_LIMIT_EXCEEDED");
|
|
3541
|
+
}
|
|
3542
|
+
job.listeners.add(listener);
|
|
3543
|
+
return () => job.listeners.delete(listener);
|
|
3544
|
+
},
|
|
3545
|
+
async close() {
|
|
3546
|
+
if (closed) return;
|
|
3547
|
+
closed = true;
|
|
3548
|
+
for (const pending of pendingRequests.values()) {
|
|
3549
|
+
pending.controller.abort(new ContextualAskError("ASK_CANCELLED"));
|
|
3550
|
+
}
|
|
3551
|
+
await Promise.allSettled(
|
|
3552
|
+
[...pendingRequests.values()].map((pending) => pending.promise)
|
|
3553
|
+
);
|
|
3554
|
+
for (const job of jobs.values()) {
|
|
3555
|
+
if (ACTIVE_STATUSES.has(job.status)) {
|
|
3556
|
+
job.controller.abort(new ContextualAskError("ASK_CANCELLED"));
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
await Promise.allSettled(
|
|
3560
|
+
[...jobs.values()].map((job) => job.runPromise ?? Promise.resolve())
|
|
3561
|
+
);
|
|
3562
|
+
for (const job of jobs.values()) {
|
|
3563
|
+
job.snapshot?.dispose();
|
|
3564
|
+
job.listeners.clear();
|
|
3565
|
+
job.lease.release();
|
|
3566
|
+
}
|
|
3567
|
+
jobs.clear();
|
|
3568
|
+
requests.clear();
|
|
3569
|
+
pendingRequests.clear();
|
|
3570
|
+
await Promise.allSettled(
|
|
3571
|
+
[...executors.values()].map(async (executor) => executor.dispose?.())
|
|
3572
|
+
);
|
|
3573
|
+
}
|
|
3574
|
+
});
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
// src/contextual-ask/configured-key-executors.ts
|
|
3578
|
+
var import_agent3 = require("@spotpatch/agent");
|
|
3579
|
+
function createConfiguredKeyAskExecutors(options) {
|
|
3580
|
+
const preferred = options.defaultExecutor?.kind === "configured-key" ? options.defaultExecutor : Object.freeze({
|
|
3581
|
+
kind: "configured-key",
|
|
3582
|
+
providerProfileId: options.ai.defaultProvider,
|
|
3583
|
+
modelProfileId: options.ai.providers[options.ai.defaultProvider]?.defaultModel ?? ""
|
|
3584
|
+
});
|
|
3585
|
+
const providers = Object.values(options.ai.providers).sort(
|
|
3586
|
+
(left, right) => left.id === preferred.providerProfileId ? -1 : right.id === preferred.providerProfileId ? 1 : 0
|
|
3587
|
+
);
|
|
3588
|
+
return Object.freeze(
|
|
3589
|
+
providers.flatMap((provider) => {
|
|
3590
|
+
const credential = (0, import_agent3.resolveProviderCredential)(
|
|
3591
|
+
provider.apiKeyEnv,
|
|
3592
|
+
options.environment
|
|
3593
|
+
);
|
|
3594
|
+
const models = Object.values(provider.models).sort(
|
|
3595
|
+
(left, right) => provider.id !== preferred.providerProfileId ? 0 : left.id === preferred.modelProfileId ? -1 : right.id === preferred.modelProfileId ? 1 : 0
|
|
3596
|
+
);
|
|
3597
|
+
return models.map(
|
|
3598
|
+
(model) => (0, import_agent3.createConfiguredKeyAskExecutor)({
|
|
3599
|
+
provider,
|
|
3600
|
+
model,
|
|
3601
|
+
credential,
|
|
3602
|
+
limits: options.ai.execution.limits,
|
|
3603
|
+
...options.fetch === void 0 ? {} : { fetch: options.fetch }
|
|
3604
|
+
})
|
|
3605
|
+
);
|
|
3606
|
+
})
|
|
3607
|
+
);
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
// src/contextual-ask/executors.ts
|
|
3611
|
+
function composeContextualAskExecutors(options) {
|
|
3612
|
+
return Object.freeze(
|
|
3613
|
+
options.defaultExecutor?.kind === "managed-codex" ? [options.managedCodex, ...options.configuredKey] : [...options.configuredKey, options.managedCodex]
|
|
3614
|
+
);
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3617
|
+
// src/workspace/activity-coordinator.ts
|
|
3618
|
+
function createWorkspaceActivityCoordinator() {
|
|
3619
|
+
let active;
|
|
3620
|
+
return Object.freeze({
|
|
3621
|
+
acquire(kind) {
|
|
3622
|
+
if (active !== void 0) return void 0;
|
|
3623
|
+
active = kind;
|
|
3624
|
+
let released = false;
|
|
3625
|
+
return Object.freeze({
|
|
3626
|
+
release() {
|
|
3627
|
+
if (released) return;
|
|
3628
|
+
released = true;
|
|
3629
|
+
if (active === kind) active = void 0;
|
|
3630
|
+
}
|
|
3631
|
+
});
|
|
3632
|
+
},
|
|
3633
|
+
current() {
|
|
3634
|
+
return active;
|
|
3635
|
+
}
|
|
3636
|
+
});
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
// src/server/middleware.ts
|
|
3640
|
+
var import_shared23 = require("@spotpatch/shared");
|
|
3641
|
+
|
|
3642
|
+
// src/contextual-ask/http.ts
|
|
3643
|
+
var import_shared12 = require("@spotpatch/shared");
|
|
3644
|
+
var STATUS_BY_ERROR = Object.freeze({
|
|
3645
|
+
ASK_DISABLED: 404,
|
|
3646
|
+
ASK_SELECTION_REQUIRED: 422,
|
|
3647
|
+
ASK_SELECTION_STALE: 409,
|
|
3648
|
+
ASK_QUESTION_INVALID: 422,
|
|
3649
|
+
ASK_EXECUTOR_UNAVAILABLE: 503,
|
|
3650
|
+
ASK_TIMEOUT: 504,
|
|
3651
|
+
ASK_CONSENT_REQUIRED: 403,
|
|
3652
|
+
ASK_BUSY: 409,
|
|
3653
|
+
ASK_IDEMPOTENCY_CONFLICT: 409,
|
|
3654
|
+
ASK_SOURCE_SCOPE_DENIED: 403,
|
|
3655
|
+
ASK_LIMIT_EXCEEDED: 413,
|
|
3656
|
+
ASK_ANSWER_INVALID: 502,
|
|
3657
|
+
ASK_WRITE_ATTEMPTED: 403,
|
|
3658
|
+
ASK_CANCELLED: 409,
|
|
3659
|
+
ASK_RESULT_EXPIRED: 410,
|
|
3660
|
+
ASK_PROTOCOL_INCOMPATIBLE: 400
|
|
3661
|
+
});
|
|
3662
|
+
var PUBLIC_MESSAGES = Object.freeze({
|
|
3663
|
+
ASK_DISABLED: "Contextual Ask is not enabled.",
|
|
3664
|
+
ASK_SELECTION_REQUIRED: "Select at least one source-backed element.",
|
|
3665
|
+
ASK_SELECTION_STALE: "The selected source is stale.",
|
|
3666
|
+
ASK_QUESTION_INVALID: "The question is invalid.",
|
|
3667
|
+
ASK_EXECUTOR_UNAVAILABLE: "The selected Ask executor is unavailable.",
|
|
3668
|
+
ASK_TIMEOUT: "The Ask executor did not answer before the deadline.",
|
|
3669
|
+
ASK_CONSENT_REQUIRED: "Provider data consent is required.",
|
|
3670
|
+
ASK_BUSY: "Another workspace task is active.",
|
|
3671
|
+
ASK_IDEMPOTENCY_CONFLICT: "The request ID was reused with different content.",
|
|
3672
|
+
ASK_SOURCE_SCOPE_DENIED: "The requested source scope was denied.",
|
|
3673
|
+
ASK_LIMIT_EXCEEDED: "The Ask request exceeded a safety limit.",
|
|
3674
|
+
ASK_ANSWER_INVALID: "The executor returned an invalid answer.",
|
|
3675
|
+
ASK_WRITE_ATTEMPTED: "Ask executors cannot write project files.",
|
|
3676
|
+
ASK_CANCELLED: "The Ask job was cancelled.",
|
|
3677
|
+
ASK_RESULT_EXPIRED: "The Ask result has expired.",
|
|
3678
|
+
ASK_PROTOCOL_INCOMPATIBLE: "The Ask protocol request is incompatible."
|
|
3679
|
+
});
|
|
3680
|
+
function writeJson2(response, status, data) {
|
|
3681
|
+
response.statusCode = status;
|
|
3682
|
+
response.setHeader("Cache-Control", "no-store");
|
|
3683
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
3684
|
+
response.end(JSON.stringify({ ok: true, data }));
|
|
3685
|
+
}
|
|
3686
|
+
function writeError(response, error, logger) {
|
|
3687
|
+
const normalized = error instanceof ContextualAskError ? error : new ContextualAskError("ASK_EXECUTOR_UNAVAILABLE", { cause: error });
|
|
3688
|
+
if (normalized.code === "ASK_EXECUTOR_UNAVAILABLE" && error !== normalized) {
|
|
3689
|
+
logger?.warn("[spotpatch:ask] Internal contextual Ask request failure.");
|
|
3690
|
+
}
|
|
3691
|
+
response.statusCode = STATUS_BY_ERROR[normalized.code];
|
|
3692
|
+
response.setHeader("Cache-Control", "no-store");
|
|
3693
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
3694
|
+
response.end(
|
|
3695
|
+
JSON.stringify({
|
|
3696
|
+
ok: false,
|
|
3697
|
+
error: {
|
|
3698
|
+
code: normalized.code,
|
|
3699
|
+
message: PUBLIC_MESSAGES[normalized.code]
|
|
3700
|
+
}
|
|
3701
|
+
})
|
|
3702
|
+
);
|
|
3703
|
+
}
|
|
3704
|
+
function matchContextualAskPath(pathname) {
|
|
3705
|
+
if (pathname === import_shared12.SPOTPATCH_ENDPOINTS.askCapability) {
|
|
3706
|
+
return Object.freeze({ kind: "capability" });
|
|
3707
|
+
}
|
|
3708
|
+
if (pathname === import_shared12.SPOTPATCH_ENDPOINTS.askJobs) {
|
|
3709
|
+
return Object.freeze({ kind: "create" });
|
|
3710
|
+
}
|
|
3711
|
+
const prefix = `${import_shared12.SPOTPATCH_ENDPOINTS.askJobs}/`;
|
|
3712
|
+
if (!pathname.startsWith(prefix)) return void 0;
|
|
3713
|
+
const segments = pathname.slice(prefix.length).split("/");
|
|
3714
|
+
if (segments.length !== 2) return void 0;
|
|
3715
|
+
let jobId;
|
|
3716
|
+
try {
|
|
3717
|
+
jobId = decodeURIComponent(segments[0] ?? "");
|
|
3718
|
+
} catch {
|
|
3719
|
+
return void 0;
|
|
3720
|
+
}
|
|
3721
|
+
const action = segments[1];
|
|
3722
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/u.test(jobId) || action !== "events" && action !== "result" && action !== "cancel") {
|
|
3723
|
+
return void 0;
|
|
3724
|
+
}
|
|
3725
|
+
return Object.freeze({ kind: "job", action, jobId });
|
|
3726
|
+
}
|
|
3727
|
+
function parseAfterSequence(request) {
|
|
3728
|
+
let url;
|
|
3729
|
+
try {
|
|
3730
|
+
url = new URL(request.url ?? "/", "http://spotpatch.invalid");
|
|
3731
|
+
} catch {
|
|
3732
|
+
throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
|
|
3733
|
+
}
|
|
3734
|
+
if ([...url.searchParams.keys()].some((key) => key !== "afterSequence")) {
|
|
3735
|
+
throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
|
|
3736
|
+
}
|
|
3737
|
+
const raw = url.searchParams.get("afterSequence");
|
|
3738
|
+
if (raw === null) return 0;
|
|
3739
|
+
if (!/^\d+$/u.test(raw)) throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
|
|
3740
|
+
const parsed = import_shared12.askJobEventsRequestSchema.safeParse({ afterSequence: Number(raw) });
|
|
3741
|
+
if (!parsed.success || !Number.isSafeInteger(parsed.data.afterSequence)) {
|
|
3742
|
+
throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
|
|
3743
|
+
}
|
|
3744
|
+
return parsed.data.afterSequence ?? 0;
|
|
3745
|
+
}
|
|
3746
|
+
function streamEvents(request, response, manager, jobId) {
|
|
3747
|
+
const afterSequence = parseAfterSequence(request);
|
|
3748
|
+
response.statusCode = 200;
|
|
3749
|
+
response.setHeader("Cache-Control", "no-store");
|
|
3750
|
+
response.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
|
|
3751
|
+
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
3752
|
+
let unsubscribe = () => void 0;
|
|
3753
|
+
const heartbeat = setInterval(
|
|
3754
|
+
() => response.write("\n"),
|
|
3755
|
+
import_shared12.CONTEXTUAL_ASK_LIMITS.eventHeartbeatMs
|
|
3756
|
+
);
|
|
3757
|
+
heartbeat.unref();
|
|
3758
|
+
const cleanup = () => {
|
|
3759
|
+
clearInterval(heartbeat);
|
|
3760
|
+
unsubscribe();
|
|
3761
|
+
};
|
|
3762
|
+
unsubscribe = manager.subscribe(jobId, (event) => {
|
|
3763
|
+
response.write(`${JSON.stringify(event)}
|
|
3764
|
+
`);
|
|
3765
|
+
if (["answered", "cancelled", "failed"].includes(event.status)) {
|
|
3766
|
+
cleanup();
|
|
3767
|
+
response.end();
|
|
3768
|
+
}
|
|
3769
|
+
});
|
|
3770
|
+
const replay = manager.events(jobId, afterSequence);
|
|
3771
|
+
for (const event of replay) response.write(`${JSON.stringify(event)}
|
|
3772
|
+
`);
|
|
3773
|
+
const latest = replay.at(-1);
|
|
3774
|
+
const currentStatus = latest?.status ?? manager.snapshot(jobId).status;
|
|
3775
|
+
if (["answered", "cancelled", "failed"].includes(currentStatus)) {
|
|
3776
|
+
cleanup();
|
|
3777
|
+
response.end();
|
|
3778
|
+
return;
|
|
3779
|
+
}
|
|
3780
|
+
request.once("close", cleanup);
|
|
3781
|
+
response.once("close", cleanup);
|
|
3782
|
+
}
|
|
3783
|
+
function hasEmptySelection(value) {
|
|
3784
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3785
|
+
const envelope = value.envelope;
|
|
3786
|
+
if (typeof envelope !== "object" || envelope === null) return false;
|
|
3787
|
+
const selection = envelope.selection;
|
|
3788
|
+
if (typeof selection !== "object" || selection === null) return false;
|
|
3789
|
+
return Array.isArray(selection.targets) && selection.targets.length === 0;
|
|
3790
|
+
}
|
|
3791
|
+
function lacksProviderDataConsent(value) {
|
|
3792
|
+
return typeof value === "object" && value !== null && value.providerDataConsent !== true;
|
|
3793
|
+
}
|
|
3794
|
+
async function handleContextualAskRequest(request, response, route, manager, logger) {
|
|
3795
|
+
try {
|
|
3796
|
+
if (manager === void 0) throw new ContextualAskError("ASK_DISABLED");
|
|
3797
|
+
if (route.kind === "capability") {
|
|
3798
|
+
if (request.method !== "GET")
|
|
3799
|
+
throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
|
|
3800
|
+
writeJson2(
|
|
3801
|
+
response,
|
|
3802
|
+
200,
|
|
3803
|
+
await manager.capability(
|
|
3804
|
+
AbortSignal.timeout(import_shared12.CONTEXTUAL_ASK_LIMITS.capabilityTimeoutMs)
|
|
3805
|
+
)
|
|
3806
|
+
);
|
|
3807
|
+
return;
|
|
3808
|
+
}
|
|
3809
|
+
if (route.kind === "create") {
|
|
3810
|
+
if (request.method !== "POST")
|
|
3811
|
+
throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
|
|
3812
|
+
const raw = await readJsonRequestBody(
|
|
3813
|
+
request,
|
|
3814
|
+
import_shared12.CONTEXTUAL_ASK_LIMITS.maximumRequestBodyBytes
|
|
3815
|
+
).catch((error) => {
|
|
3816
|
+
throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE", { cause: error });
|
|
3817
|
+
});
|
|
3818
|
+
const parsed = import_shared12.askJobCreateRequestSchema.safeParse(raw);
|
|
3819
|
+
if (!parsed.success) {
|
|
3820
|
+
throw new ContextualAskError(
|
|
3821
|
+
hasEmptySelection(raw) ? "ASK_SELECTION_REQUIRED" : lacksProviderDataConsent(raw) ? "ASK_CONSENT_REQUIRED" : "ASK_PROTOCOL_INCOMPATIBLE"
|
|
3822
|
+
);
|
|
3823
|
+
}
|
|
3824
|
+
writeJson2(response, 202, await manager.create(parsed.data));
|
|
3825
|
+
return;
|
|
3826
|
+
}
|
|
3827
|
+
if (route.action === "events") {
|
|
3828
|
+
if (request.method !== "GET")
|
|
3829
|
+
throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
|
|
3830
|
+
streamEvents(request, response, manager, route.jobId);
|
|
3831
|
+
return;
|
|
3832
|
+
}
|
|
3833
|
+
if (route.action === "result") {
|
|
3834
|
+
if (request.method !== "GET")
|
|
3835
|
+
throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
|
|
3836
|
+
writeJson2(response, 200, await manager.result(route.jobId));
|
|
3837
|
+
return;
|
|
3838
|
+
}
|
|
3839
|
+
if (request.method !== "POST")
|
|
3840
|
+
throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
|
|
3841
|
+
const action = import_shared12.askJobActionRequestSchema.safeParse(
|
|
3842
|
+
await readJsonRequestBody(request).catch((error) => {
|
|
3843
|
+
throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE", { cause: error });
|
|
3844
|
+
})
|
|
3845
|
+
);
|
|
3846
|
+
if (!action.success) throw new ContextualAskError("ASK_PROTOCOL_INCOMPATIBLE");
|
|
3847
|
+
writeJson2(response, 200, manager.cancel(route.jobId));
|
|
3848
|
+
} catch (error) {
|
|
3849
|
+
writeError(response, error, logger);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
|
|
3853
|
+
// src/external-handoff/browser-http.ts
|
|
3854
|
+
var import_shared16 = require("@spotpatch/shared");
|
|
3855
|
+
|
|
3856
|
+
// src/server/annotation-authorizer.ts
|
|
3857
|
+
var import_promises7 = require("fs/promises");
|
|
3858
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
3859
|
+
var import_shared15 = require("@spotpatch/shared");
|
|
3860
|
+
|
|
3861
|
+
// src/server/source-context.ts
|
|
3862
|
+
var import_promises6 = require("fs/promises");
|
|
3863
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
3864
|
+
var import_shared14 = require("@spotpatch/shared");
|
|
3865
|
+
|
|
3866
|
+
// src/server/extract-code-context.ts
|
|
3867
|
+
var import_oxc_parser2 = require("oxc-parser");
|
|
3868
|
+
function isComponentName(name) {
|
|
3869
|
+
return /^[A-Z]/u.test(name);
|
|
3870
|
+
}
|
|
3871
|
+
function unwrapTypeExpression(expression) {
|
|
3872
|
+
switch (expression.type) {
|
|
3873
|
+
case "TSAsExpression":
|
|
3874
|
+
case "TSSatisfiesExpression":
|
|
3875
|
+
case "TSTypeAssertion":
|
|
3876
|
+
case "TSNonNullExpression":
|
|
3877
|
+
case "TSInstantiationExpression":
|
|
3878
|
+
return unwrapTypeExpression(expression.expression);
|
|
3879
|
+
default:
|
|
3880
|
+
return expression;
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
function calleeName(expression) {
|
|
3884
|
+
const unwrapped = unwrapTypeExpression(expression);
|
|
3885
|
+
if (unwrapped.type === "Identifier") {
|
|
3886
|
+
return unwrapped.name;
|
|
3887
|
+
}
|
|
3888
|
+
if (unwrapped.type === "MemberExpression" && !unwrapped.computed) {
|
|
3889
|
+
return unwrapped.property.type === "Identifier" ? unwrapped.property.name : void 0;
|
|
3890
|
+
}
|
|
3891
|
+
return void 0;
|
|
3892
|
+
}
|
|
3893
|
+
function isFunctionExpression(expression) {
|
|
3894
|
+
const unwrapped = unwrapTypeExpression(expression);
|
|
3895
|
+
return unwrapped.type === "ArrowFunctionExpression" || unwrapped.type === "FunctionExpression";
|
|
3896
|
+
}
|
|
3897
|
+
function isSupportedComponentInitializer(expression) {
|
|
3898
|
+
const unwrapped = unwrapTypeExpression(expression);
|
|
3899
|
+
if (isFunctionExpression(unwrapped)) {
|
|
3900
|
+
return true;
|
|
3901
|
+
}
|
|
3902
|
+
if (unwrapped.type !== "CallExpression") {
|
|
3903
|
+
return false;
|
|
3904
|
+
}
|
|
3905
|
+
const name = calleeName(unwrapped.callee);
|
|
3906
|
+
if (name !== "memo" && name !== "forwardRef") {
|
|
3907
|
+
return false;
|
|
3908
|
+
}
|
|
3909
|
+
const firstArgument = unwrapped.arguments[0];
|
|
3910
|
+
return firstArgument !== void 0 && firstArgument.type !== "SpreadElement" && (isFunctionExpression(firstArgument) || isSupportedComponentInitializer(firstArgument));
|
|
3911
|
+
}
|
|
3912
|
+
function variableComponent(node) {
|
|
3913
|
+
if (node.id.type !== "Identifier" || !isComponentName(node.id.name) || node.init === null || !isSupportedComponentInitializer(node.init)) {
|
|
3914
|
+
return void 0;
|
|
3915
|
+
}
|
|
3916
|
+
return Object.freeze({ start: node.start, end: node.end, name: node.id.name });
|
|
3917
|
+
}
|
|
3918
|
+
function functionComponent(node) {
|
|
3919
|
+
return node.id !== null && isComponentName(node.id.name) && node.body !== null ? Object.freeze({ start: node.start, end: node.end, name: node.id.name }) : void 0;
|
|
3920
|
+
}
|
|
3921
|
+
function isReactComponentSuperclass(expression) {
|
|
3922
|
+
if (expression === null) {
|
|
3923
|
+
return false;
|
|
3924
|
+
}
|
|
3925
|
+
const unwrapped = unwrapTypeExpression(expression);
|
|
3926
|
+
if (unwrapped.type === "Identifier") {
|
|
3927
|
+
return unwrapped.name === "Component" || unwrapped.name === "PureComponent";
|
|
3928
|
+
}
|
|
3929
|
+
return unwrapped.type === "MemberExpression" && !unwrapped.computed && unwrapped.object.type === "Identifier" && unwrapped.object.name === "React" && (unwrapped.property.name === "Component" || unwrapped.property.name === "PureComponent");
|
|
3930
|
+
}
|
|
3931
|
+
function classComponent(node) {
|
|
3932
|
+
return node.id !== null && isComponentName(node.id.name) && isReactComponentSuperclass(node.superClass) ? Object.freeze({ start: node.start, end: node.end, name: node.id.name }) : void 0;
|
|
3933
|
+
}
|
|
3934
|
+
function selectedOffset(source, line, column) {
|
|
3935
|
+
const lines = source.split(/\r?\n/u);
|
|
3936
|
+
if (line < 1 || line > lines.length) {
|
|
3937
|
+
return void 0;
|
|
3938
|
+
}
|
|
3939
|
+
const lineStart = lines.slice(0, line - 1).reduce((total, value) => total + value.length + 1, 0);
|
|
3940
|
+
const lineLength = lines[line - 1]?.length ?? 0;
|
|
3941
|
+
return lineStart + Math.min(Math.max(0, column - 1), lineLength);
|
|
3942
|
+
}
|
|
3943
|
+
function findComponentSpan(options) {
|
|
3944
|
+
const offset = selectedOffset(options.source, options.line, options.column);
|
|
3945
|
+
if (offset === void 0) {
|
|
3946
|
+
return void 0;
|
|
3947
|
+
}
|
|
3948
|
+
let parseResult;
|
|
3949
|
+
try {
|
|
3950
|
+
parseResult = (0, import_oxc_parser2.parseSync)(options.sourcePath, options.source, {
|
|
3951
|
+
sourceType: "module"
|
|
3952
|
+
});
|
|
3953
|
+
} catch {
|
|
3954
|
+
return void 0;
|
|
3955
|
+
}
|
|
3956
|
+
if (parseResult.errors.length > 0) {
|
|
3957
|
+
return void 0;
|
|
3958
|
+
}
|
|
3959
|
+
const jsxNodes = [];
|
|
3960
|
+
const components = [];
|
|
3961
|
+
const visitor = new import_oxc_parser2.Visitor({
|
|
3962
|
+
JSXElement(node) {
|
|
3963
|
+
jsxNodes.push(node);
|
|
3964
|
+
},
|
|
3965
|
+
JSXFragment(node) {
|
|
3966
|
+
jsxNodes.push(node);
|
|
3967
|
+
},
|
|
3968
|
+
FunctionDeclaration(node) {
|
|
3969
|
+
const candidate = functionComponent(node);
|
|
3970
|
+
if (candidate !== void 0) {
|
|
3971
|
+
components.push(candidate);
|
|
3972
|
+
}
|
|
3973
|
+
},
|
|
3974
|
+
VariableDeclarator(node) {
|
|
3975
|
+
const candidate = variableComponent(node);
|
|
3976
|
+
if (candidate !== void 0) {
|
|
3977
|
+
components.push(candidate);
|
|
3978
|
+
}
|
|
3979
|
+
},
|
|
3980
|
+
ClassDeclaration(node) {
|
|
3981
|
+
const candidate = classComponent(node);
|
|
3982
|
+
if (candidate !== void 0) {
|
|
3983
|
+
components.push(candidate);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
});
|
|
3987
|
+
visitor.visit(parseResult.program);
|
|
3988
|
+
const selectedJsx = jsxNodes.filter((node) => node.start <= offset && node.end >= offset).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
|
|
3989
|
+
if (selectedJsx === void 0) {
|
|
3990
|
+
return void 0;
|
|
3991
|
+
}
|
|
3992
|
+
return components.filter(
|
|
3993
|
+
(component) => component.start <= selectedJsx.start && component.end >= selectedJsx.end
|
|
3994
|
+
).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
|
|
3995
|
+
}
|
|
3996
|
+
function lineAtOffset(source, offset) {
|
|
3997
|
+
let line = 1;
|
|
3998
|
+
for (let index = 0; index < offset; index += 1) {
|
|
3999
|
+
if (source[index] === "\n") {
|
|
4000
|
+
line += 1;
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
return line;
|
|
4004
|
+
}
|
|
4005
|
+
function componentRange(source, component) {
|
|
4006
|
+
return Object.freeze({
|
|
4007
|
+
startLine: lineAtOffset(source, component.start),
|
|
4008
|
+
endLine: lineAtOffset(source, Math.max(component.start, component.end - 1))
|
|
4009
|
+
});
|
|
4010
|
+
}
|
|
4011
|
+
function truncateSelectedLine(line, column, maxCharacters) {
|
|
4012
|
+
if (line.length <= maxCharacters) {
|
|
4013
|
+
return line;
|
|
4014
|
+
}
|
|
4015
|
+
if (maxCharacters === 1) {
|
|
4016
|
+
return "\u2026";
|
|
4017
|
+
}
|
|
4018
|
+
const contentCharacters = maxCharacters - 2;
|
|
4019
|
+
const desiredStart = Math.max(0, column - 1 - Math.floor(contentCharacters / 2));
|
|
4020
|
+
const start = Math.min(desiredStart, line.length - contentCharacters);
|
|
4021
|
+
const end = start + contentCharacters;
|
|
4022
|
+
return `${start > 0 ? "\u2026" : ""}${line.slice(start, end)}${end < line.length ? "\u2026" : ""}`.slice(
|
|
2664
4023
|
0,
|
|
2665
4024
|
maxCharacters
|
|
2666
4025
|
);
|
|
@@ -2731,9 +4090,9 @@ function extractCodeContext(options) {
|
|
|
2731
4090
|
}
|
|
2732
4091
|
|
|
2733
4092
|
// src/server/source-file.ts
|
|
2734
|
-
var
|
|
2735
|
-
var
|
|
2736
|
-
var
|
|
4093
|
+
var import_promises5 = require("fs/promises");
|
|
4094
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
4095
|
+
var import_shared13 = require("@spotpatch/shared");
|
|
2737
4096
|
var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
|
|
2738
4097
|
function isMissingFileError(error) {
|
|
2739
4098
|
return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
|
|
@@ -2743,56 +4102,56 @@ async function assertInsideRoot(root, candidate) {
|
|
|
2743
4102
|
let realCandidate;
|
|
2744
4103
|
try {
|
|
2745
4104
|
[realRoot, realCandidate] = await Promise.all([
|
|
2746
|
-
(0,
|
|
2747
|
-
(0,
|
|
4105
|
+
(0, import_promises5.realpath)(root),
|
|
4106
|
+
(0, import_promises5.realpath)(candidate)
|
|
2748
4107
|
]);
|
|
2749
4108
|
} catch (error) {
|
|
2750
4109
|
if (isMissingFileError(error)) {
|
|
2751
|
-
throw new
|
|
4110
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
|
|
2752
4111
|
cause: error
|
|
2753
4112
|
});
|
|
2754
4113
|
}
|
|
2755
4114
|
throw error;
|
|
2756
4115
|
}
|
|
2757
|
-
const relative =
|
|
2758
|
-
const outside = relative.startsWith(`..${
|
|
4116
|
+
const relative = import_node_path6.default.relative(realRoot, realCandidate);
|
|
4117
|
+
const outside = relative.startsWith(`..${import_node_path6.default.sep}`) || relative === ".." || import_node_path6.default.isAbsolute(relative);
|
|
2759
4118
|
if (outside) {
|
|
2760
|
-
throw new
|
|
4119
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
|
|
2761
4120
|
}
|
|
2762
4121
|
return realCandidate;
|
|
2763
4122
|
}
|
|
2764
4123
|
async function resolveSourceFile(options) {
|
|
2765
4124
|
const registeredPath = options.registry.resolve(options.fileId);
|
|
2766
4125
|
if (registeredPath === void 0) {
|
|
2767
|
-
throw new
|
|
4126
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_NOT_FOUND);
|
|
2768
4127
|
}
|
|
2769
4128
|
const sourcePath = await assertInsideRoot(options.root, registeredPath);
|
|
2770
|
-
if (!ALLOWED_EXTENSIONS.has(
|
|
2771
|
-
throw new
|
|
4129
|
+
if (!ALLOWED_EXTENSIONS.has(import_node_path6.default.extname(sourcePath).toLowerCase())) {
|
|
4130
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_NOT_FOUND);
|
|
2772
4131
|
}
|
|
2773
4132
|
let sourceStat;
|
|
2774
4133
|
try {
|
|
2775
|
-
sourceStat = await (0,
|
|
4134
|
+
sourceStat = await (0, import_promises5.stat)(sourcePath);
|
|
2776
4135
|
} catch (error) {
|
|
2777
4136
|
if (isMissingFileError(error)) {
|
|
2778
|
-
throw new
|
|
4137
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
|
|
2779
4138
|
cause: error
|
|
2780
4139
|
});
|
|
2781
4140
|
}
|
|
2782
4141
|
throw error;
|
|
2783
4142
|
}
|
|
2784
4143
|
if (!sourceStat.isFile()) {
|
|
2785
|
-
throw new
|
|
4144
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_NOT_FOUND);
|
|
2786
4145
|
}
|
|
2787
4146
|
if (sourceStat.size > MAX_SOURCE_FILE_BYTES) {
|
|
2788
|
-
throw new
|
|
4147
|
+
throw new import_shared13.SpotPatchError(import_shared13.ERROR_CODES.SOURCE_TOO_LARGE);
|
|
2789
4148
|
}
|
|
2790
4149
|
return sourcePath;
|
|
2791
4150
|
}
|
|
2792
4151
|
|
|
2793
4152
|
// src/server/source-context.ts
|
|
2794
4153
|
function toDisplayPath(root, sourcePath) {
|
|
2795
|
-
return
|
|
4154
|
+
return import_node_path7.default.relative(root, sourcePath).split(import_node_path7.default.sep).join("/");
|
|
2796
4155
|
}
|
|
2797
4156
|
async function readSourceContext(options) {
|
|
2798
4157
|
const sourcePath = await resolveSourceFile({
|
|
@@ -2802,10 +4161,10 @@ async function readSourceContext(options) {
|
|
|
2802
4161
|
});
|
|
2803
4162
|
let source;
|
|
2804
4163
|
try {
|
|
2805
|
-
source = await (0,
|
|
4164
|
+
source = await (0, import_promises6.readFile)(sourcePath, "utf8");
|
|
2806
4165
|
} catch (error) {
|
|
2807
4166
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
|
|
2808
|
-
throw new
|
|
4167
|
+
throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
|
|
2809
4168
|
cause: error
|
|
2810
4169
|
});
|
|
2811
4170
|
}
|
|
@@ -2813,13 +4172,13 @@ async function readSourceContext(options) {
|
|
|
2813
4172
|
}
|
|
2814
4173
|
const lines = source.split(/\r?\n/);
|
|
2815
4174
|
if (options.request.line > lines.length) {
|
|
2816
|
-
throw new
|
|
4175
|
+
throw new import_shared14.SpotPatchError(import_shared14.ERROR_CODES.INVALID_REQUEST);
|
|
2817
4176
|
}
|
|
2818
|
-
const extension =
|
|
4177
|
+
const extension = import_node_path7.default.extname(sourcePath).toLowerCase();
|
|
2819
4178
|
return extractCodeContext({
|
|
2820
4179
|
source,
|
|
2821
4180
|
sourcePath,
|
|
2822
|
-
relativePath: toDisplayPath(await (0,
|
|
4181
|
+
relativePath: toDisplayPath(await (0, import_promises6.realpath)(options.root), sourcePath),
|
|
2823
4182
|
language: extension === ".tsx" ? "tsx" : "jsx",
|
|
2824
4183
|
line: options.request.line,
|
|
2825
4184
|
column: options.request.column,
|
|
@@ -2831,9 +4190,9 @@ async function readSourceContext(options) {
|
|
|
2831
4190
|
// src/server/annotation-authorizer.ts
|
|
2832
4191
|
function sanitizePageContext(page) {
|
|
2833
4192
|
return Object.freeze({
|
|
2834
|
-
url: (0,
|
|
2835
|
-
pathname: (0,
|
|
2836
|
-
title: (0,
|
|
4193
|
+
url: (0, import_shared15.sanitizeUrl)(page.url, page.url),
|
|
4194
|
+
pathname: (0, import_shared15.redactSensitiveText)(page.pathname),
|
|
4195
|
+
title: (0, import_shared15.redactSensitiveText)(page.title),
|
|
2837
4196
|
viewportWidth: page.viewportWidth,
|
|
2838
4197
|
viewportHeight: page.viewportHeight,
|
|
2839
4198
|
devicePixelRatio: page.devicePixelRatio
|
|
@@ -2852,18 +4211,18 @@ function compactSourceRef(source) {
|
|
|
2852
4211
|
async function authorizeSourceRef(source, registry, root) {
|
|
2853
4212
|
const markerOrigin = source.origin === "jsx-host" || source.origin === "dom-ancestor";
|
|
2854
4213
|
if (markerOrigin && (source.fileId === void 0 || source.line === void 0 || source.column === void 0)) {
|
|
2855
|
-
throw new
|
|
4214
|
+
throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
|
|
2856
4215
|
}
|
|
2857
4216
|
if (source.fileId === void 0) {
|
|
2858
4217
|
if (source.origin === "none" && source.relativePath !== void 0) {
|
|
2859
|
-
throw new
|
|
4218
|
+
throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
|
|
2860
4219
|
}
|
|
2861
4220
|
return compactSourceRef(source);
|
|
2862
4221
|
}
|
|
2863
4222
|
const sourcePath = await resolveSourceFile({ fileId: source.fileId, registry, root });
|
|
2864
|
-
const relativePath =
|
|
4223
|
+
const relativePath = import_node_path8.default.relative(await (0, import_promises7.realpath)(root), sourcePath).split(import_node_path8.default.sep).join("/");
|
|
2865
4224
|
if (source.relativePath !== void 0 && source.relativePath !== relativePath) {
|
|
2866
|
-
throw new
|
|
4225
|
+
throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
|
|
2867
4226
|
}
|
|
2868
4227
|
return Object.freeze({ ...compactSourceRef(source), relativePath });
|
|
2869
4228
|
}
|
|
@@ -2899,7 +4258,7 @@ async function authorizeTarget(target, input) {
|
|
|
2899
4258
|
maxLines: input.options.budget.maxCodeLines
|
|
2900
4259
|
});
|
|
2901
4260
|
if (marker === void 0 && target.code !== void 0) {
|
|
2902
|
-
throw new
|
|
4261
|
+
throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
|
|
2903
4262
|
}
|
|
2904
4263
|
const code = marker === void 0 ? void 0 : await readSourceContext({
|
|
2905
4264
|
request: marker,
|
|
@@ -2909,7 +4268,7 @@ async function authorizeTarget(target, input) {
|
|
|
2909
4268
|
maxLines: input.options.budget.maxCodeLines
|
|
2910
4269
|
});
|
|
2911
4270
|
if (target.code !== void 0 && target.code.relativePath !== code?.relativePath) {
|
|
2912
|
-
throw new
|
|
4271
|
+
throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
|
|
2913
4272
|
}
|
|
2914
4273
|
return Object.freeze({
|
|
2915
4274
|
instruction: target.instruction,
|
|
@@ -2944,11 +4303,11 @@ async function authorizeTarget(target, input) {
|
|
|
2944
4303
|
async function authorizeAnnotation(input) {
|
|
2945
4304
|
const requestedTargets = input.annotation.targets;
|
|
2946
4305
|
if (requestedTargets.length > input.options.maxTargets) {
|
|
2947
|
-
throw new
|
|
4306
|
+
throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
|
|
2948
4307
|
}
|
|
2949
4308
|
const identities = requestedTargets.map(targetIdentity);
|
|
2950
4309
|
if (new Set(identities).size !== identities.length) {
|
|
2951
|
-
throw new
|
|
4310
|
+
throw new import_shared15.SpotPatchError(import_shared15.ERROR_CODES.INVALID_REQUEST);
|
|
2952
4311
|
}
|
|
2953
4312
|
const targets = Object.freeze(
|
|
2954
4313
|
await Promise.all(requestedTargets.map((target) => authorizeTarget(target, input)))
|
|
@@ -2964,30 +4323,30 @@ async function authorizeAnnotation(input) {
|
|
|
2964
4323
|
}
|
|
2965
4324
|
|
|
2966
4325
|
// src/external-handoff/browser-http.ts
|
|
2967
|
-
function matchExternalHandoffBrowserPath(
|
|
2968
|
-
if (
|
|
2969
|
-
if (
|
|
2970
|
-
if (
|
|
2971
|
-
if (
|
|
4326
|
+
function matchExternalHandoffBrowserPath(path10) {
|
|
4327
|
+
if (path10 === import_shared16.SPOTPATCH_ENDPOINTS.externalHandoffCapability) return "capability";
|
|
4328
|
+
if (path10 === import_shared16.SPOTPATCH_ENDPOINTS.externalHandoffPublish) return "publish";
|
|
4329
|
+
if (path10 === import_shared16.SPOTPATCH_ENDPOINTS.externalHandoffStatus) return "status";
|
|
4330
|
+
if (path10 === import_shared16.SPOTPATCH_ENDPOINTS.externalHandoffResolveDelivery) {
|
|
2972
4331
|
return "resolve-delivery";
|
|
2973
4332
|
}
|
|
2974
4333
|
return void 0;
|
|
2975
4334
|
}
|
|
2976
4335
|
function requireService(options) {
|
|
2977
4336
|
if (!options.options.externalAgent.enabled || options.service === void 0) {
|
|
2978
|
-
throw new
|
|
4337
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
|
|
2979
4338
|
}
|
|
2980
4339
|
return options.service;
|
|
2981
4340
|
}
|
|
2982
4341
|
function remapAuthorizationError(error) {
|
|
2983
|
-
if (error instanceof
|
|
2984
|
-
if (error.code ===
|
|
2985
|
-
throw new
|
|
4342
|
+
if (error instanceof import_shared16.SpotPatchError) {
|
|
4343
|
+
if (error.code === import_shared16.ERROR_CODES.SOURCE_NOT_FOUND || error.code === import_shared16.ERROR_CODES.SOURCE_OUTSIDE_ROOT || error.code === import_shared16.ERROR_CODES.SOURCE_TOO_LARGE) {
|
|
4344
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.HANDOFF_SOURCE_STALE, void 0, {
|
|
2986
4345
|
cause: error
|
|
2987
4346
|
});
|
|
2988
4347
|
}
|
|
2989
|
-
if (error.code ===
|
|
2990
|
-
throw new
|
|
4348
|
+
if (error.code === import_shared16.ERROR_CODES.INVALID_REQUEST) {
|
|
4349
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.HANDOFF_VALIDATION_FAILED, void 0, {
|
|
2991
4350
|
cause: error
|
|
2992
4351
|
});
|
|
2993
4352
|
}
|
|
@@ -2996,38 +4355,38 @@ function remapAuthorizationError(error) {
|
|
|
2996
4355
|
}
|
|
2997
4356
|
async function handleExternalHandoffBrowserRequest(request, response, route, options, writeSuccess) {
|
|
2998
4357
|
if (request.method !== "POST") {
|
|
2999
|
-
throw new
|
|
4358
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3000
4359
|
}
|
|
3001
4360
|
const service = requireService(options);
|
|
3002
4361
|
if (route === "capability") {
|
|
3003
|
-
const parsed2 =
|
|
4362
|
+
const parsed2 = import_shared16.externalHandoffCapabilityRequestSchema.safeParse(
|
|
3004
4363
|
await readJsonRequestBody(request)
|
|
3005
4364
|
);
|
|
3006
|
-
if (!parsed2.success) throw new
|
|
4365
|
+
if (!parsed2.success) throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3007
4366
|
writeSuccess(response, 200, service.capability());
|
|
3008
4367
|
return;
|
|
3009
4368
|
}
|
|
3010
4369
|
if (route === "status") {
|
|
3011
|
-
const parsed2 =
|
|
4370
|
+
const parsed2 = import_shared16.externalHandoffStatusRequestSchema.safeParse(
|
|
3012
4371
|
await readJsonRequestBody(request)
|
|
3013
4372
|
);
|
|
3014
|
-
if (!parsed2.success) throw new
|
|
4373
|
+
if (!parsed2.success) throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3015
4374
|
writeSuccess(response, 200, service.status(parsed2.data.cursor));
|
|
3016
4375
|
return;
|
|
3017
4376
|
}
|
|
3018
4377
|
if (route === "resolve-delivery") {
|
|
3019
|
-
const parsed2 =
|
|
4378
|
+
const parsed2 = import_shared16.externalHandoffResolveDeliveryRequestSchema.safeParse(
|
|
3020
4379
|
await readJsonRequestBody(request)
|
|
3021
4380
|
);
|
|
3022
|
-
if (!parsed2.success) throw new
|
|
4381
|
+
if (!parsed2.success) throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.INVALID_REQUEST);
|
|
3023
4382
|
writeSuccess(response, 200, service.resolveDelivery(parsed2.data.cursor));
|
|
3024
4383
|
return;
|
|
3025
4384
|
}
|
|
3026
|
-
const parsed =
|
|
3027
|
-
await readJsonRequestBody(request,
|
|
4385
|
+
const parsed = import_shared16.externalHandoffPublishRequestSchema.safeParse(
|
|
4386
|
+
await readJsonRequestBody(request, import_shared16.EXTERNAL_HANDOFF_LIMITS.maximumPublishBodyBytes)
|
|
3028
4387
|
);
|
|
3029
4388
|
if (!parsed.success) {
|
|
3030
|
-
throw new
|
|
4389
|
+
throw new import_shared16.SpotPatchError(import_shared16.ERROR_CODES.HANDOFF_VALIDATION_FAILED);
|
|
3031
4390
|
}
|
|
3032
4391
|
const result = await service.publish(parsed.data, async (annotation) => {
|
|
3033
4392
|
try {
|
|
@@ -3045,41 +4404,41 @@ async function handleExternalHandoffBrowserRequest(request, response, route, opt
|
|
|
3045
4404
|
}
|
|
3046
4405
|
|
|
3047
4406
|
// src/external-agent/browser-http.ts
|
|
3048
|
-
var
|
|
3049
|
-
function matchExternalAgentBrowserPath(
|
|
3050
|
-
if (
|
|
3051
|
-
if (
|
|
3052
|
-
if (
|
|
4407
|
+
var import_shared17 = require("@spotpatch/shared");
|
|
4408
|
+
function matchExternalAgentBrowserPath(path10) {
|
|
4409
|
+
if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentControlStatus) return "status";
|
|
4410
|
+
if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentControlConnect) return "connect";
|
|
4411
|
+
if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentControlDisconnect) {
|
|
3053
4412
|
return "disconnect";
|
|
3054
4413
|
}
|
|
3055
|
-
if (
|
|
3056
|
-
if (
|
|
3057
|
-
if (
|
|
4414
|
+
if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentControlCancel) return "cancel";
|
|
4415
|
+
if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentEvents) return "events";
|
|
4416
|
+
if (path10 === import_shared17.SPOTPATCH_ENDPOINTS.externalAgentResult) return "result";
|
|
3058
4417
|
return void 0;
|
|
3059
4418
|
}
|
|
3060
4419
|
function writeEvent(response, event) {
|
|
3061
|
-
response.write(`${JSON.stringify(
|
|
4420
|
+
response.write(`${JSON.stringify(import_shared17.externalAgentEventSchema.parse(event))}
|
|
3062
4421
|
`);
|
|
3063
4422
|
}
|
|
3064
4423
|
function createExternalAgentBrowserController(port) {
|
|
3065
4424
|
const streams = /* @__PURE__ */ new Set();
|
|
3066
4425
|
let disposed = false;
|
|
3067
4426
|
const handleEvents = async (request, response) => {
|
|
3068
|
-
const parsed =
|
|
4427
|
+
const parsed = import_shared17.externalAgentEventsRequestSchema.safeParse(
|
|
3069
4428
|
await readJsonRequestBody(request)
|
|
3070
4429
|
);
|
|
3071
4430
|
if (!parsed.success) {
|
|
3072
|
-
throw new
|
|
4431
|
+
throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
|
|
3073
4432
|
}
|
|
3074
|
-
if (disposed || streams.size >=
|
|
3075
|
-
throw new
|
|
4433
|
+
if (disposed || streams.size >= import_shared17.EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventSubscribers) {
|
|
4434
|
+
throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.BRIDGE_BUSY);
|
|
3076
4435
|
}
|
|
3077
4436
|
response.statusCode = 200;
|
|
3078
4437
|
response.setHeader("Cache-Control", "no-store");
|
|
3079
4438
|
response.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
|
|
3080
4439
|
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
3081
4440
|
streams.add(response);
|
|
3082
|
-
const initial =
|
|
4441
|
+
const initial = import_shared17.externalAgentControlStatusSchema.parse(port.getStatus());
|
|
3083
4442
|
writeEvent(response, { type: "status", data: initial });
|
|
3084
4443
|
const unsubscribe = port.subscribe((status) => {
|
|
3085
4444
|
if (!response.destroyed && status.sequence > (parsed.data.afterSequence ?? -1)) {
|
|
@@ -3095,7 +4454,7 @@ function createExternalAgentBrowserController(port) {
|
|
|
3095
4454
|
emittedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3096
4455
|
});
|
|
3097
4456
|
}
|
|
3098
|
-
},
|
|
4457
|
+
}, import_shared17.EXTERNAL_AGENT_CONTROL_LIMITS.eventHeartbeatMs);
|
|
3099
4458
|
heartbeat.unref();
|
|
3100
4459
|
await new Promise((resolve) => {
|
|
3101
4460
|
const close = () => {
|
|
@@ -3111,29 +4470,29 @@ function createExternalAgentBrowserController(port) {
|
|
|
3111
4470
|
return Object.freeze({
|
|
3112
4471
|
async handle(request, response, route, writeSuccess) {
|
|
3113
4472
|
if (request.method !== "POST" || disposed) {
|
|
3114
|
-
throw new
|
|
4473
|
+
throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
|
|
3115
4474
|
}
|
|
3116
4475
|
if (route === "events") {
|
|
3117
4476
|
await handleEvents(request, response);
|
|
3118
4477
|
return;
|
|
3119
4478
|
}
|
|
3120
4479
|
if (route === "status") {
|
|
3121
|
-
const parsed2 =
|
|
4480
|
+
const parsed2 = import_shared17.externalAgentControlStatusRequestSchema.safeParse(
|
|
3122
4481
|
await readJsonRequestBody(request)
|
|
3123
4482
|
);
|
|
3124
|
-
if (!parsed2.success) throw new
|
|
4483
|
+
if (!parsed2.success) throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
|
|
3125
4484
|
writeSuccess(
|
|
3126
4485
|
response,
|
|
3127
4486
|
200,
|
|
3128
|
-
|
|
4487
|
+
import_shared17.externalAgentControlStatusSchema.parse(port.getStatus())
|
|
3129
4488
|
);
|
|
3130
4489
|
return;
|
|
3131
4490
|
}
|
|
3132
4491
|
if (route === "connect") {
|
|
3133
|
-
const parsed2 =
|
|
4492
|
+
const parsed2 = import_shared17.externalAgentControlConnectRequestSchema.safeParse(
|
|
3134
4493
|
await readJsonRequestBody(request)
|
|
3135
4494
|
);
|
|
3136
|
-
if (!parsed2.success) throw new
|
|
4495
|
+
if (!parsed2.success) throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
|
|
3137
4496
|
const controller = new AbortController();
|
|
3138
4497
|
response.once("close", () => {
|
|
3139
4498
|
if (!response.writableEnded) controller.abort("browser-disconnected");
|
|
@@ -3141,45 +4500,45 @@ function createExternalAgentBrowserController(port) {
|
|
|
3141
4500
|
writeSuccess(
|
|
3142
4501
|
response,
|
|
3143
4502
|
200,
|
|
3144
|
-
|
|
4503
|
+
import_shared17.externalAgentControlStatusSchema.parse(
|
|
3145
4504
|
await port.connect(parsed2.data, controller.signal)
|
|
3146
4505
|
)
|
|
3147
4506
|
);
|
|
3148
4507
|
return;
|
|
3149
4508
|
}
|
|
3150
4509
|
if (route === "disconnect") {
|
|
3151
|
-
const parsed2 =
|
|
4510
|
+
const parsed2 = import_shared17.externalAgentControlDisconnectRequestSchema.safeParse(
|
|
3152
4511
|
await readJsonRequestBody(request)
|
|
3153
4512
|
);
|
|
3154
|
-
if (!parsed2.success) throw new
|
|
4513
|
+
if (!parsed2.success) throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
|
|
3155
4514
|
writeSuccess(
|
|
3156
4515
|
response,
|
|
3157
4516
|
200,
|
|
3158
|
-
|
|
4517
|
+
import_shared17.externalAgentControlStatusSchema.parse(await port.disconnect(parsed2.data))
|
|
3159
4518
|
);
|
|
3160
4519
|
return;
|
|
3161
4520
|
}
|
|
3162
4521
|
if (route === "cancel") {
|
|
3163
|
-
const parsed2 =
|
|
4522
|
+
const parsed2 = import_shared17.externalAgentControlCancelRequestSchema.safeParse(
|
|
3164
4523
|
await readJsonRequestBody(request)
|
|
3165
4524
|
);
|
|
3166
|
-
if (!parsed2.success) throw new
|
|
4525
|
+
if (!parsed2.success) throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
|
|
3167
4526
|
writeSuccess(
|
|
3168
4527
|
response,
|
|
3169
4528
|
200,
|
|
3170
|
-
|
|
4529
|
+
import_shared17.externalAgentControlStatusSchema.parse(await port.cancel(parsed2.data))
|
|
3171
4530
|
);
|
|
3172
4531
|
return;
|
|
3173
4532
|
}
|
|
3174
|
-
const parsed =
|
|
4533
|
+
const parsed = import_shared17.externalAgentResultRequestSchema.safeParse(
|
|
3175
4534
|
await readJsonRequestBody(request)
|
|
3176
4535
|
);
|
|
3177
|
-
if (!parsed.success) throw new
|
|
4536
|
+
if (!parsed.success) throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.INVALID_REQUEST);
|
|
3178
4537
|
const result = port.getResult(parsed.data.revision);
|
|
3179
4538
|
if (result === void 0) {
|
|
3180
|
-
throw new
|
|
4539
|
+
throw new import_shared17.SpotPatchError(import_shared17.ERROR_CODES.HANDOFF_NOT_FOUND);
|
|
3181
4540
|
}
|
|
3182
|
-
writeSuccess(response, 200,
|
|
4541
|
+
writeSuccess(response, 200, import_shared17.externalAgentManagedResultSchema.parse(result));
|
|
3183
4542
|
},
|
|
3184
4543
|
dispose() {
|
|
3185
4544
|
if (disposed) return;
|
|
@@ -3191,10 +4550,10 @@ function createExternalAgentBrowserController(port) {
|
|
|
3191
4550
|
}
|
|
3192
4551
|
|
|
3193
4552
|
// src/server/agent-http.ts
|
|
3194
|
-
var
|
|
4553
|
+
var import_shared19 = require("@spotpatch/shared");
|
|
3195
4554
|
|
|
3196
4555
|
// src/server/agent-request.ts
|
|
3197
|
-
var
|
|
4556
|
+
var import_shared18 = require("@spotpatch/shared");
|
|
3198
4557
|
async function authorizeAgentJobRequest(input) {
|
|
3199
4558
|
const annotation = await authorizeAnnotation({
|
|
3200
4559
|
annotation: input.request.annotation,
|
|
@@ -3230,21 +4589,21 @@ var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
|
|
|
3230
4589
|
"reverted",
|
|
3231
4590
|
"failed"
|
|
3232
4591
|
]);
|
|
3233
|
-
function matchAgentRequestPath(
|
|
3234
|
-
if (
|
|
4592
|
+
function matchAgentRequestPath(path10) {
|
|
4593
|
+
if (path10 === import_shared19.SPOTPATCH_ENDPOINTS.agentCapability) {
|
|
3235
4594
|
return Object.freeze({ kind: "capability" });
|
|
3236
4595
|
}
|
|
3237
|
-
if (
|
|
4596
|
+
if (path10 === import_shared19.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
|
|
3238
4597
|
return Object.freeze({ kind: "workspace-health" });
|
|
3239
4598
|
}
|
|
3240
|
-
if (
|
|
4599
|
+
if (path10 === import_shared19.SPOTPATCH_ENDPOINTS.agentJobs) {
|
|
3241
4600
|
return Object.freeze({ kind: "create-job" });
|
|
3242
4601
|
}
|
|
3243
|
-
const prefix = `${
|
|
3244
|
-
if (!
|
|
4602
|
+
const prefix = `${import_shared19.SPOTPATCH_ENDPOINTS.agentJobs}/`;
|
|
4603
|
+
if (!path10.startsWith(prefix)) {
|
|
3245
4604
|
return void 0;
|
|
3246
4605
|
}
|
|
3247
|
-
const segments =
|
|
4606
|
+
const segments = path10.slice(prefix.length).split("/");
|
|
3248
4607
|
const jobId = segments[0];
|
|
3249
4608
|
const action = segments[1];
|
|
3250
4609
|
if (segments.length !== 2 || jobId === void 0 || !AGENT_JOB_ID_PATTERN.test(jobId) || action === void 0 || !AGENT_JOB_ACTIONS.has(action)) {
|
|
@@ -3258,7 +4617,7 @@ function matchAgentRequestPath(path9) {
|
|
|
3258
4617
|
}
|
|
3259
4618
|
function requireAgentManager(options) {
|
|
3260
4619
|
if (options.agentManager === void 0 || options.options.ai === false) {
|
|
3261
|
-
throw new
|
|
4620
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.AI_DISABLED);
|
|
3262
4621
|
}
|
|
3263
4622
|
return options.agentManager;
|
|
3264
4623
|
}
|
|
@@ -3311,13 +4670,13 @@ function streamAgentJobEvents(response, manager, jobId) {
|
|
|
3311
4670
|
}
|
|
3312
4671
|
async function handleCapability(request, response, options, writeSuccess) {
|
|
3313
4672
|
if (request.method !== "POST") {
|
|
3314
|
-
throw new
|
|
4673
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
|
|
3315
4674
|
}
|
|
3316
|
-
const parsed =
|
|
4675
|
+
const parsed = import_shared19.agentCapabilityRequestSchema.safeParse(
|
|
3317
4676
|
await readJsonRequestBody(request)
|
|
3318
4677
|
);
|
|
3319
4678
|
if (!parsed.success) {
|
|
3320
|
-
throw new
|
|
4679
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
|
|
3321
4680
|
}
|
|
3322
4681
|
const controller = new AbortController();
|
|
3323
4682
|
const abort = () => {
|
|
@@ -3336,13 +4695,13 @@ async function handleCapability(request, response, options, writeSuccess) {
|
|
|
3336
4695
|
}
|
|
3337
4696
|
async function handleCreateJob(request, response, options, writeSuccess) {
|
|
3338
4697
|
if (request.method !== "POST") {
|
|
3339
|
-
throw new
|
|
4698
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
|
|
3340
4699
|
}
|
|
3341
|
-
const parsed =
|
|
4700
|
+
const parsed = import_shared19.agentJobCreateRequestSchema.safeParse(
|
|
3342
4701
|
await readJsonRequestBody(request, MAX_AGENT_REQUEST_BODY_BYTES)
|
|
3343
4702
|
);
|
|
3344
4703
|
if (!parsed.success) {
|
|
3345
|
-
throw new
|
|
4704
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
|
|
3346
4705
|
}
|
|
3347
4706
|
const authorizedRequest = await authorizeAgentJobRequest({
|
|
3348
4707
|
request: parsed.data,
|
|
@@ -3355,13 +4714,13 @@ async function handleCreateJob(request, response, options, writeSuccess) {
|
|
|
3355
4714
|
}
|
|
3356
4715
|
async function handleWorkspaceHealth(request, response, options, writeSuccess) {
|
|
3357
4716
|
if (request.method !== "POST") {
|
|
3358
|
-
throw new
|
|
4717
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
|
|
3359
4718
|
}
|
|
3360
|
-
const parsed =
|
|
4719
|
+
const parsed = import_shared19.agentWorkspaceHealthRequestSchema.safeParse(
|
|
3361
4720
|
await readJsonRequestBody(request)
|
|
3362
4721
|
);
|
|
3363
4722
|
if (!parsed.success) {
|
|
3364
|
-
throw new
|
|
4723
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
|
|
3365
4724
|
}
|
|
3366
4725
|
const controller = new AbortController();
|
|
3367
4726
|
const abort = () => {
|
|
@@ -3378,13 +4737,13 @@ async function handleWorkspaceHealth(request, response, options, writeSuccess) {
|
|
|
3378
4737
|
async function handleJobAction(request, response, options, route, writeSuccess) {
|
|
3379
4738
|
const manager = requireAgentManager(options);
|
|
3380
4739
|
if (request.method !== "POST") {
|
|
3381
|
-
throw new
|
|
4740
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
|
|
3382
4741
|
}
|
|
3383
|
-
const parsed =
|
|
4742
|
+
const parsed = import_shared19.agentJobActionRequestSchema.safeParse(
|
|
3384
4743
|
await readJsonRequestBody(request)
|
|
3385
4744
|
);
|
|
3386
4745
|
if (!parsed.success) {
|
|
3387
|
-
throw new
|
|
4746
|
+
throw new import_shared19.SpotPatchError(import_shared19.ERROR_CODES.INVALID_REQUEST);
|
|
3388
4747
|
}
|
|
3389
4748
|
if (route.action === "events") {
|
|
3390
4749
|
streamAgentJobEvents(response, manager, route.jobId);
|
|
@@ -3438,13 +4797,13 @@ function detectIntegratedEditor(environment) {
|
|
|
3438
4797
|
function editorCommand(editor) {
|
|
3439
4798
|
return editor === "cursor" ? "cursor" : "code";
|
|
3440
4799
|
}
|
|
3441
|
-
var
|
|
4800
|
+
var DEFAULT_DEPENDENCIES3 = Object.freeze({
|
|
3442
4801
|
environment: process.env,
|
|
3443
4802
|
fallbackLauncher: import_launch_editor.default,
|
|
3444
4803
|
processSpawner: (command, arguments_, options) => (0, import_node_child_process2.spawn)(command, [...arguments_], options),
|
|
3445
4804
|
startupGraceMs: EDITOR_STARTUP_GRACE_MS
|
|
3446
4805
|
});
|
|
3447
|
-
function createEditorLauncher(dependencies =
|
|
4806
|
+
function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES3) {
|
|
3448
4807
|
return (target, configuredEditor) => {
|
|
3449
4808
|
const integratedEditor = detectIntegratedEditor(dependencies.environment);
|
|
3450
4809
|
const resolvedEditor = configuredEditor === "auto" ? integratedEditor : configuredEditor;
|
|
@@ -3499,27 +4858,27 @@ function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
|
|
|
3499
4858
|
var launchConfiguredEditor = createEditorLauncher();
|
|
3500
4859
|
|
|
3501
4860
|
// src/server/data-flow-http.ts
|
|
3502
|
-
var
|
|
4861
|
+
var import_node_crypto11 = require("crypto");
|
|
3503
4862
|
var import_analyzer = require("@spotpatch/analyzer");
|
|
3504
|
-
var
|
|
4863
|
+
var import_shared20 = require("@spotpatch/shared");
|
|
3505
4864
|
function envelopeBytes(report) {
|
|
3506
4865
|
return Buffer.byteLength(JSON.stringify({ ok: true, data: report }), "utf8");
|
|
3507
4866
|
}
|
|
3508
4867
|
function limitDataFlowReportToBytes(report, maximumBytes) {
|
|
3509
|
-
const structurallyLimited = (0,
|
|
4868
|
+
const structurallyLimited = (0, import_shared20.limitDataFlowReportCollections)(report);
|
|
3510
4869
|
if (envelopeBytes(structurallyLimited) <= maximumBytes) {
|
|
3511
4870
|
return structurallyLimited;
|
|
3512
4871
|
}
|
|
3513
|
-
let limited = (0,
|
|
4872
|
+
let limited = (0, import_shared20.limitDataFlowReportCollections)(structurallyLimited, {
|
|
3514
4873
|
forceTruncation: true,
|
|
3515
4874
|
maximumDependencies: 0,
|
|
3516
4875
|
truncatedBy: "bytes"
|
|
3517
4876
|
});
|
|
3518
4877
|
if (envelopeBytes(limited) > maximumBytes) {
|
|
3519
|
-
throw new
|
|
4878
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INTERNAL_ERROR);
|
|
3520
4879
|
}
|
|
3521
4880
|
for (let maximumDependencies = 1; maximumDependencies <= structurallyLimited.dependencies.length; maximumDependencies += 1) {
|
|
3522
|
-
const candidate = (0,
|
|
4881
|
+
const candidate = (0, import_shared20.limitDataFlowReportCollections)(structurallyLimited, {
|
|
3523
4882
|
forceTruncation: true,
|
|
3524
4883
|
maximumDependencies,
|
|
3525
4884
|
truncatedBy: "bytes"
|
|
@@ -3545,7 +4904,7 @@ async function analyzeTarget(request, analyzer, options) {
|
|
|
3545
4904
|
request.componentSourceId
|
|
3546
4905
|
);
|
|
3547
4906
|
if (anchor?.sourceVersion !== request.sourceVersion) {
|
|
3548
|
-
throw new
|
|
4907
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
|
|
3549
4908
|
}
|
|
3550
4909
|
return anchor;
|
|
3551
4910
|
}
|
|
@@ -3562,7 +4921,7 @@ async function analyzeTarget(request, analyzer, options) {
|
|
|
3562
4921
|
column: resolvedRequest.column
|
|
3563
4922
|
});
|
|
3564
4923
|
if (resolvedRequest.sourceVersion !== void 0 && resolvedRequest.sourceVersion !== report.component.source.sourceVersion) {
|
|
3565
|
-
throw new
|
|
4924
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.DATA_FLOW_SOURCE_STALE);
|
|
3566
4925
|
}
|
|
3567
4926
|
return limitDataFlowReportToBytes(
|
|
3568
4927
|
report,
|
|
@@ -3571,31 +4930,31 @@ async function analyzeTarget(request, analyzer, options) {
|
|
|
3571
4930
|
}
|
|
3572
4931
|
function requireAnalyzer(analyzer) {
|
|
3573
4932
|
if (analyzer === void 0) {
|
|
3574
|
-
throw new
|
|
4933
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.DATA_FLOW_DISABLED);
|
|
3575
4934
|
}
|
|
3576
4935
|
return analyzer;
|
|
3577
4936
|
}
|
|
3578
4937
|
async function handleComponentDataFlowReport(request, analyzer, options) {
|
|
3579
|
-
const parsed =
|
|
4938
|
+
const parsed = import_shared20.dataFlowComponentReportRequestSchema.safeParse(
|
|
3580
4939
|
await readJsonRequestBody(
|
|
3581
4940
|
request,
|
|
3582
4941
|
options.options.dataFlow.limits.protocolRequestMaxBytes
|
|
3583
4942
|
)
|
|
3584
4943
|
);
|
|
3585
4944
|
if (!parsed.success) {
|
|
3586
|
-
throw new
|
|
4945
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
|
|
3587
4946
|
}
|
|
3588
4947
|
return analyzeTarget(parsed.data, requireAnalyzer(analyzer), options);
|
|
3589
4948
|
}
|
|
3590
4949
|
async function handlePageDataFlowReport(request, analyzer, options) {
|
|
3591
|
-
const parsed =
|
|
4950
|
+
const parsed = import_shared20.dataFlowPageReportRequestSchema.safeParse(
|
|
3592
4951
|
await readJsonRequestBody(
|
|
3593
4952
|
request,
|
|
3594
4953
|
options.options.dataFlow.limits.protocolRequestMaxBytes
|
|
3595
4954
|
)
|
|
3596
4955
|
);
|
|
3597
4956
|
if (!parsed.success) {
|
|
3598
|
-
throw new
|
|
4957
|
+
throw new import_shared20.SpotPatchError(import_shared20.ERROR_CODES.INVALID_REQUEST);
|
|
3599
4958
|
}
|
|
3600
4959
|
const activeAnalyzer = requireAnalyzer(analyzer);
|
|
3601
4960
|
const componentReports = await Promise.all(
|
|
@@ -3619,10 +4978,10 @@ async function handlePageDataFlowReport(request, analyzer, options) {
|
|
|
3619
4978
|
const analyzedVersions = new Set(
|
|
3620
4979
|
componentReports.flatMap((report2) => report2.baseline.analyzedSourceVersions)
|
|
3621
4980
|
);
|
|
3622
|
-
const reportId = `page_${(0,
|
|
4981
|
+
const reportId = `page_${(0, import_node_crypto11.createHash)("sha256").update(componentReports.map((report2) => report2.reportId).join("\0")).digest("base64url").slice(0, 22)}`;
|
|
3623
4982
|
const complete = componentReports.every((report2) => report2.completeness.complete);
|
|
3624
4983
|
const report = Object.freeze({
|
|
3625
|
-
schemaVersion:
|
|
4984
|
+
schemaVersion: import_shared20.DATA_FLOW_SCHEMA_VERSION,
|
|
3626
4985
|
reportId,
|
|
3627
4986
|
baseline: Object.freeze({
|
|
3628
4987
|
registryEpoch: options.session.id,
|
|
@@ -3666,9 +5025,9 @@ async function handlePageDataFlowReport(request, analyzer, options) {
|
|
|
3666
5025
|
}
|
|
3667
5026
|
|
|
3668
5027
|
// src/server/request-security.ts
|
|
3669
|
-
var
|
|
5028
|
+
var import_node_crypto12 = require("crypto");
|
|
3670
5029
|
var import_node_net = require("net");
|
|
3671
|
-
var
|
|
5030
|
+
var import_shared21 = require("@spotpatch/shared");
|
|
3672
5031
|
function getSingleHeader(request, name) {
|
|
3673
5032
|
const value = request.headers[name.toLowerCase()];
|
|
3674
5033
|
return Array.isArray(value) ? value[0] : value;
|
|
@@ -3679,7 +5038,7 @@ function tokensMatch2(actual, expected) {
|
|
|
3679
5038
|
}
|
|
3680
5039
|
const actualBytes = Buffer.from(actual);
|
|
3681
5040
|
const expectedBytes = Buffer.from(expected);
|
|
3682
|
-
return actualBytes.byteLength === expectedBytes.byteLength && (0,
|
|
5041
|
+
return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto12.timingSafeEqual)(actualBytes, expectedBytes);
|
|
3683
5042
|
}
|
|
3684
5043
|
function isLoopbackHostname(hostname) {
|
|
3685
5044
|
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
@@ -3712,33 +5071,42 @@ function parseOrigin(value) {
|
|
|
3712
5071
|
return void 0;
|
|
3713
5072
|
}
|
|
3714
5073
|
}
|
|
5074
|
+
function isSameOriginBrowserGet(request) {
|
|
5075
|
+
return request.method === "GET" && getSingleHeader(request, "sec-fetch-site")?.toLowerCase() === "same-origin" && getSingleHeader(request, "sec-fetch-mode")?.toLowerCase() === "cors" && getSingleHeader(request, "sec-fetch-dest")?.toLowerCase() === "empty";
|
|
5076
|
+
}
|
|
3715
5077
|
function assertRequestAuthorized(request, options) {
|
|
3716
|
-
const actualToken = getSingleHeader(request,
|
|
5078
|
+
const actualToken = getSingleHeader(request, import_shared21.SPOTPATCH_TOKEN_HEADER);
|
|
3717
5079
|
if (!tokensMatch2(actualToken, options.sessionToken)) {
|
|
3718
|
-
throw new
|
|
5080
|
+
throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.INVALID_TOKEN);
|
|
3719
5081
|
}
|
|
3720
5082
|
const hostHeader = getSingleHeader(request, "host");
|
|
3721
5083
|
const originHeader = getSingleHeader(request, "origin");
|
|
3722
5084
|
const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
|
|
3723
5085
|
const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
|
|
3724
|
-
if (host === void 0
|
|
3725
|
-
throw new
|
|
5086
|
+
if (host === void 0) {
|
|
5087
|
+
throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.ORIGIN_NOT_ALLOWED);
|
|
3726
5088
|
}
|
|
3727
5089
|
const hostIsLoopback = isLoopbackHostname(host.hostname);
|
|
5090
|
+
if (origin === void 0) {
|
|
5091
|
+
if (!isSameOriginBrowserGet(request) || !options.allowLan && !hostIsLoopback) {
|
|
5092
|
+
throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.ORIGIN_NOT_ALLOWED);
|
|
5093
|
+
}
|
|
5094
|
+
return;
|
|
5095
|
+
}
|
|
3728
5096
|
const originIsLoopback = isLoopbackHostname(origin.hostname);
|
|
3729
5097
|
if (!options.allowLan) {
|
|
3730
5098
|
if (!hostIsLoopback || !originIsLoopback) {
|
|
3731
|
-
throw new
|
|
5099
|
+
throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.ORIGIN_NOT_ALLOWED);
|
|
3732
5100
|
}
|
|
3733
5101
|
return;
|
|
3734
5102
|
}
|
|
3735
5103
|
if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
|
|
3736
|
-
throw new
|
|
5104
|
+
throw new import_shared21.SpotPatchError(import_shared21.ERROR_CODES.ORIGIN_NOT_ALLOWED);
|
|
3737
5105
|
}
|
|
3738
5106
|
}
|
|
3739
5107
|
|
|
3740
5108
|
// src/server/runtime-bootstrap.ts
|
|
3741
|
-
var
|
|
5109
|
+
var import_shared22 = require("@spotpatch/shared");
|
|
3742
5110
|
function getSingleHeader2(request, name) {
|
|
3743
5111
|
const value = request.headers[name.toLowerCase()];
|
|
3744
5112
|
return Array.isArray(value) ? value[0] : value;
|
|
@@ -3753,7 +5121,7 @@ function resolveRuntimeBootstrapOptions(options) {
|
|
|
3753
5121
|
if (expectedOrigin.origin !== options.expectedOrigin || expectedOrigin.protocol !== "http:" || !isLoopbackHostname(expectedOrigin.hostname)) {
|
|
3754
5122
|
throw new TypeError("The SpotPatch bootstrap origin must be a loopback origin.");
|
|
3755
5123
|
}
|
|
3756
|
-
const parsedConfig =
|
|
5124
|
+
const parsedConfig = import_shared22.runtimeConfigSchema.safeParse(options.runtimeConfig);
|
|
3757
5125
|
if (!parsedConfig.success) {
|
|
3758
5126
|
throw new TypeError("The SpotPatch Runtime configuration is invalid.");
|
|
3759
5127
|
}
|
|
@@ -3765,7 +5133,7 @@ function resolveRuntimeBootstrapOptions(options) {
|
|
|
3765
5133
|
function assertRuntimeBootstrapRequest(request, expectedOrigin) {
|
|
3766
5134
|
const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
3767
5135
|
if (request.method !== "POST" || contentType !== "application/json") {
|
|
3768
|
-
throw new
|
|
5136
|
+
throw new import_shared22.SpotPatchError(import_shared22.ERROR_CODES.INVALID_REQUEST);
|
|
3769
5137
|
}
|
|
3770
5138
|
const host = getSingleHeader2(request, "host");
|
|
3771
5139
|
let hostIsLoopback = false;
|
|
@@ -3777,152 +5145,152 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
|
|
|
3777
5145
|
}
|
|
3778
5146
|
}
|
|
3779
5147
|
if (!hostIsLoopback || getSingleHeader2(request, "origin") !== expectedOrigin || getSingleHeader2(request, "sec-fetch-site") !== "same-origin") {
|
|
3780
|
-
throw new
|
|
5148
|
+
throw new import_shared22.SpotPatchError(import_shared22.ERROR_CODES.ORIGIN_NOT_ALLOWED);
|
|
3781
5149
|
}
|
|
3782
5150
|
}
|
|
3783
5151
|
async function readRuntimeBootstrap(request, options) {
|
|
3784
5152
|
assertRuntimeBootstrapRequest(request, options.expectedOrigin);
|
|
3785
|
-
const parsedBody =
|
|
5153
|
+
const parsedBody = import_shared22.runtimeBootstrapRequestSchema.safeParse(
|
|
3786
5154
|
await readJsonRequestBody(request)
|
|
3787
5155
|
);
|
|
3788
5156
|
if (!parsedBody.success) {
|
|
3789
|
-
throw new
|
|
5157
|
+
throw new import_shared22.SpotPatchError(import_shared22.ERROR_CODES.INVALID_REQUEST);
|
|
3790
5158
|
}
|
|
3791
5159
|
return options.runtimeConfig;
|
|
3792
5160
|
}
|
|
3793
5161
|
|
|
3794
5162
|
// src/server/middleware.ts
|
|
3795
|
-
var
|
|
3796
|
-
[
|
|
3797
|
-
[
|
|
3798
|
-
[
|
|
3799
|
-
[
|
|
3800
|
-
[
|
|
3801
|
-
[
|
|
3802
|
-
[
|
|
3803
|
-
[
|
|
3804
|
-
[
|
|
3805
|
-
[
|
|
3806
|
-
[
|
|
3807
|
-
[
|
|
3808
|
-
[
|
|
3809
|
-
[
|
|
3810
|
-
[
|
|
3811
|
-
[
|
|
3812
|
-
[
|
|
3813
|
-
[
|
|
3814
|
-
[
|
|
3815
|
-
[
|
|
3816
|
-
[
|
|
3817
|
-
[
|
|
3818
|
-
[
|
|
3819
|
-
[
|
|
3820
|
-
[
|
|
3821
|
-
[
|
|
3822
|
-
[
|
|
3823
|
-
[
|
|
3824
|
-
[
|
|
3825
|
-
[
|
|
3826
|
-
[
|
|
3827
|
-
[
|
|
3828
|
-
[
|
|
3829
|
-
[
|
|
3830
|
-
[
|
|
3831
|
-
[
|
|
3832
|
-
[
|
|
3833
|
-
[
|
|
3834
|
-
[
|
|
3835
|
-
[
|
|
3836
|
-
[
|
|
3837
|
-
[
|
|
3838
|
-
[
|
|
3839
|
-
[
|
|
3840
|
-
[
|
|
3841
|
-
[
|
|
3842
|
-
[
|
|
3843
|
-
[
|
|
3844
|
-
[
|
|
3845
|
-
[
|
|
3846
|
-
[
|
|
3847
|
-
[
|
|
3848
|
-
[
|
|
3849
|
-
[
|
|
5163
|
+
var STATUS_BY_ERROR2 = Object.freeze({
|
|
5164
|
+
[import_shared23.ERROR_CODES.INVALID_REQUEST]: 400,
|
|
5165
|
+
[import_shared23.ERROR_CODES.INVALID_TOKEN]: 401,
|
|
5166
|
+
[import_shared23.ERROR_CODES.ORIGIN_NOT_ALLOWED]: 403,
|
|
5167
|
+
[import_shared23.ERROR_CODES.SOURCE_NOT_FOUND]: 404,
|
|
5168
|
+
[import_shared23.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: 403,
|
|
5169
|
+
[import_shared23.ERROR_CODES.SOURCE_TOO_LARGE]: 413,
|
|
5170
|
+
[import_shared23.ERROR_CODES.EDITOR_OPEN_FAILED]: 500,
|
|
5171
|
+
[import_shared23.ERROR_CODES.DATA_FLOW_DISABLED]: 404,
|
|
5172
|
+
[import_shared23.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: 409,
|
|
5173
|
+
[import_shared23.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
|
|
5174
|
+
[import_shared23.ERROR_CODES.AI_DISABLED]: 404,
|
|
5175
|
+
[import_shared23.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: 503,
|
|
5176
|
+
[import_shared23.ERROR_CODES.PROVIDER_AUTH_FAILED]: 502,
|
|
5177
|
+
[import_shared23.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
|
|
5178
|
+
[import_shared23.ERROR_CODES.MODEL_NOT_ALLOWED]: 400,
|
|
5179
|
+
[import_shared23.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
|
|
5180
|
+
[import_shared23.ERROR_CODES.PROVIDER_RATE_LIMITED]: 429,
|
|
5181
|
+
[import_shared23.ERROR_CODES.AGENT_BUSY]: 409,
|
|
5182
|
+
[import_shared23.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: 413,
|
|
5183
|
+
[import_shared23.ERROR_CODES.AGENT_CANCELLED]: 409,
|
|
5184
|
+
[import_shared23.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: 404,
|
|
5185
|
+
[import_shared23.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: 503,
|
|
5186
|
+
[import_shared23.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: 422,
|
|
5187
|
+
[import_shared23.ERROR_CODES.HANDOFF_SOURCE_STALE]: 409,
|
|
5188
|
+
[import_shared23.ERROR_CODES.HANDOFF_NOT_FOUND]: 404,
|
|
5189
|
+
[import_shared23.ERROR_CODES.HANDOFF_EXPIRED]: 410,
|
|
5190
|
+
[import_shared23.ERROR_CODES.HANDOFF_CURSOR_INVALID]: 409,
|
|
5191
|
+
[import_shared23.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: 413,
|
|
5192
|
+
[import_shared23.ERROR_CODES.BRIDGE_UNAUTHORIZED]: 401,
|
|
5193
|
+
[import_shared23.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: 409,
|
|
5194
|
+
[import_shared23.ERROR_CODES.BRIDGE_BUSY]: 429,
|
|
5195
|
+
[import_shared23.ERROR_CODES.EXTERNAL_AGENT_BUSY]: 409,
|
|
5196
|
+
[import_shared23.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: 409,
|
|
5197
|
+
[import_shared23.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: 409,
|
|
5198
|
+
[import_shared23.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: 409,
|
|
5199
|
+
[import_shared23.ERROR_CODES.SESSION_NOT_FOUND]: 404,
|
|
5200
|
+
[import_shared23.ERROR_CODES.SESSION_AMBIGUOUS]: 409,
|
|
5201
|
+
[import_shared23.ERROR_CODES.SESSION_CLOSED]: 410,
|
|
5202
|
+
[import_shared23.ERROR_CODES.WORKTREE_DIRTY]: 409,
|
|
5203
|
+
[import_shared23.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: 409,
|
|
5204
|
+
[import_shared23.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: 409,
|
|
5205
|
+
[import_shared23.ERROR_CODES.WORKTREE_CONFLICTED]: 409,
|
|
5206
|
+
[import_shared23.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
|
|
5207
|
+
[import_shared23.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
|
|
5208
|
+
[import_shared23.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
|
|
5209
|
+
[import_shared23.ERROR_CODES.TOOL_DENIED]: 403,
|
|
5210
|
+
[import_shared23.ERROR_CODES.TOOL_INPUT_INVALID]: 422,
|
|
5211
|
+
[import_shared23.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: 422,
|
|
5212
|
+
[import_shared23.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: 422,
|
|
5213
|
+
[import_shared23.ERROR_CODES.TOOL_PATH_DENIED]: 403,
|
|
5214
|
+
[import_shared23.ERROR_CODES.PATCH_REJECTED]: 422,
|
|
5215
|
+
[import_shared23.ERROR_CODES.VALIDATION_FAILED]: 422,
|
|
5216
|
+
[import_shared23.ERROR_CODES.APPLY_CONFLICT]: 409,
|
|
5217
|
+
[import_shared23.ERROR_CODES.INTERNAL_ERROR]: 500
|
|
3850
5218
|
});
|
|
3851
|
-
var
|
|
3852
|
-
[
|
|
3853
|
-
[
|
|
3854
|
-
[
|
|
3855
|
-
[
|
|
3856
|
-
[
|
|
3857
|
-
[
|
|
3858
|
-
[
|
|
3859
|
-
[
|
|
3860
|
-
[
|
|
3861
|
-
[
|
|
3862
|
-
[
|
|
3863
|
-
[
|
|
3864
|
-
[
|
|
3865
|
-
[
|
|
3866
|
-
[
|
|
3867
|
-
[
|
|
3868
|
-
[
|
|
3869
|
-
[
|
|
3870
|
-
[
|
|
3871
|
-
[
|
|
3872
|
-
[
|
|
3873
|
-
[
|
|
3874
|
-
[
|
|
3875
|
-
[
|
|
3876
|
-
[
|
|
3877
|
-
[
|
|
3878
|
-
[
|
|
3879
|
-
[
|
|
3880
|
-
[
|
|
3881
|
-
[
|
|
3882
|
-
[
|
|
3883
|
-
[
|
|
3884
|
-
[
|
|
3885
|
-
[
|
|
3886
|
-
[
|
|
3887
|
-
[
|
|
3888
|
-
[
|
|
3889
|
-
[
|
|
3890
|
-
[
|
|
3891
|
-
[
|
|
3892
|
-
[
|
|
3893
|
-
[
|
|
3894
|
-
[
|
|
3895
|
-
[
|
|
3896
|
-
[
|
|
3897
|
-
[
|
|
3898
|
-
[
|
|
3899
|
-
[
|
|
3900
|
-
[
|
|
3901
|
-
[
|
|
3902
|
-
[
|
|
3903
|
-
[
|
|
3904
|
-
[
|
|
3905
|
-
[
|
|
5219
|
+
var PUBLIC_MESSAGES2 = Object.freeze({
|
|
5220
|
+
[import_shared23.ERROR_CODES.INVALID_REQUEST]: "The request is invalid.",
|
|
5221
|
+
[import_shared23.ERROR_CODES.INVALID_TOKEN]: "The session token is invalid.",
|
|
5222
|
+
[import_shared23.ERROR_CODES.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
|
|
5223
|
+
[import_shared23.ERROR_CODES.SOURCE_NOT_FOUND]: "The source file is unavailable.",
|
|
5224
|
+
[import_shared23.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
|
|
5225
|
+
[import_shared23.ERROR_CODES.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
|
|
5226
|
+
[import_shared23.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
|
|
5227
|
+
[import_shared23.ERROR_CODES.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
|
|
5228
|
+
[import_shared23.ERROR_CODES.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
|
|
5229
|
+
[import_shared23.ERROR_CODES.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
|
|
5230
|
+
[import_shared23.ERROR_CODES.AI_DISABLED]: "AI execution is not enabled.",
|
|
5231
|
+
[import_shared23.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
|
|
5232
|
+
[import_shared23.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
|
|
5233
|
+
[import_shared23.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
|
|
5234
|
+
[import_shared23.ERROR_CODES.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
|
|
5235
|
+
[import_shared23.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
|
|
5236
|
+
[import_shared23.ERROR_CODES.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
|
|
5237
|
+
[import_shared23.ERROR_CODES.AGENT_BUSY]: "Another Agent job is already running.",
|
|
5238
|
+
[import_shared23.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
|
|
5239
|
+
[import_shared23.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
|
|
5240
|
+
[import_shared23.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED]: "External Agent handoff is not enabled.",
|
|
5241
|
+
[import_shared23.ERROR_CODES.EXTERNAL_HANDOFF_UNAVAILABLE]: "External Agent handoff is temporarily unavailable.",
|
|
5242
|
+
[import_shared23.ERROR_CODES.HANDOFF_VALIDATION_FAILED]: "The handoff content is invalid.",
|
|
5243
|
+
[import_shared23.ERROR_CODES.HANDOFF_SOURCE_STALE]: "The selected source is stale.",
|
|
5244
|
+
[import_shared23.ERROR_CODES.HANDOFF_NOT_FOUND]: "No current handoff is available.",
|
|
5245
|
+
[import_shared23.ERROR_CODES.HANDOFF_EXPIRED]: "The handoff has expired.",
|
|
5246
|
+
[import_shared23.ERROR_CODES.HANDOFF_CURSOR_INVALID]: "The handoff cursor is invalid.",
|
|
5247
|
+
[import_shared23.ERROR_CODES.HANDOFF_RESPONSE_TOO_LARGE]: "The handoff exceeds the size limit.",
|
|
5248
|
+
[import_shared23.ERROR_CODES.BRIDGE_UNAUTHORIZED]: "The local bridge request is unauthorized.",
|
|
5249
|
+
[import_shared23.ERROR_CODES.BRIDGE_PROTOCOL_MISMATCH]: "The local bridge protocol is incompatible.",
|
|
5250
|
+
[import_shared23.ERROR_CODES.BRIDGE_BUSY]: "The local bridge is busy.",
|
|
5251
|
+
[import_shared23.ERROR_CODES.EXTERNAL_AGENT_BUSY]: "The connected external Agent is busy.",
|
|
5252
|
+
[import_shared23.ERROR_CODES.ACTIVE_ADAPTER_CONFLICT]: "Another active Agent adapter is connected.",
|
|
5253
|
+
[import_shared23.ERROR_CODES.ACTIVE_ADAPTER_LEASE_INVALID]: "The active Agent adapter lease is invalid.",
|
|
5254
|
+
[import_shared23.ERROR_CODES.ACTIVE_DISPATCH_INVALID]: "The active Agent dispatch transition is invalid.",
|
|
5255
|
+
[import_shared23.ERROR_CODES.SESSION_NOT_FOUND]: "No active SpotPatch session was found.",
|
|
5256
|
+
[import_shared23.ERROR_CODES.SESSION_AMBIGUOUS]: "More than one SpotPatch session matches.",
|
|
5257
|
+
[import_shared23.ERROR_CODES.SESSION_CLOSED]: "The SpotPatch session has closed.",
|
|
5258
|
+
[import_shared23.ERROR_CODES.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
|
|
5259
|
+
[import_shared23.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
|
|
5260
|
+
[import_shared23.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
|
|
5261
|
+
[import_shared23.ERROR_CODES.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
|
|
5262
|
+
[import_shared23.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
|
|
5263
|
+
[import_shared23.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
|
|
5264
|
+
[import_shared23.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
|
|
5265
|
+
[import_shared23.ERROR_CODES.TOOL_DENIED]: "The Agent tool request was denied.",
|
|
5266
|
+
[import_shared23.ERROR_CODES.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
|
|
5267
|
+
[import_shared23.ERROR_CODES.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
|
|
5268
|
+
[import_shared23.ERROR_CODES.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
|
|
5269
|
+
[import_shared23.ERROR_CODES.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
|
|
5270
|
+
[import_shared23.ERROR_CODES.PATCH_REJECTED]: "The proposed patch was rejected.",
|
|
5271
|
+
[import_shared23.ERROR_CODES.VALIDATION_FAILED]: "The proposed change failed validation.",
|
|
5272
|
+
[import_shared23.ERROR_CODES.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
|
|
5273
|
+
[import_shared23.ERROR_CODES.INTERNAL_ERROR]: "The request could not be completed."
|
|
3906
5274
|
});
|
|
3907
|
-
function
|
|
5275
|
+
function writeJson3(response, status, payload) {
|
|
3908
5276
|
response.statusCode = status;
|
|
3909
5277
|
response.setHeader("Cache-Control", "no-store");
|
|
3910
5278
|
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
3911
5279
|
response.end(JSON.stringify(payload));
|
|
3912
5280
|
}
|
|
3913
5281
|
function asSpotPatchError(error) {
|
|
3914
|
-
return error instanceof
|
|
5282
|
+
return error instanceof import_shared23.SpotPatchError ? error : new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
|
|
3915
5283
|
}
|
|
3916
|
-
function
|
|
5284
|
+
function writeError2(response, error, logger) {
|
|
3917
5285
|
const normalized = asSpotPatchError(error);
|
|
3918
|
-
if (normalized.code ===
|
|
5286
|
+
if (normalized.code === import_shared23.ERROR_CODES.INTERNAL_ERROR) {
|
|
3919
5287
|
logger?.warn("[spotpatch:server] Internal request failure.");
|
|
3920
5288
|
}
|
|
3921
|
-
|
|
5289
|
+
writeJson3(response, STATUS_BY_ERROR2[normalized.code], {
|
|
3922
5290
|
ok: false,
|
|
3923
5291
|
error: {
|
|
3924
5292
|
code: normalized.code,
|
|
3925
|
-
message:
|
|
5293
|
+
message: PUBLIC_MESSAGES2[normalized.code]
|
|
3926
5294
|
}
|
|
3927
5295
|
});
|
|
3928
5296
|
}
|
|
@@ -3934,11 +5302,11 @@ function requestPath(request) {
|
|
|
3934
5302
|
}
|
|
3935
5303
|
}
|
|
3936
5304
|
async function handleSourceContext(request, options) {
|
|
3937
|
-
const parsed =
|
|
5305
|
+
const parsed = import_shared23.sourceContextRequestSchema.safeParse(
|
|
3938
5306
|
await readJsonRequestBody(request)
|
|
3939
5307
|
);
|
|
3940
5308
|
if (!parsed.success) {
|
|
3941
|
-
throw new
|
|
5309
|
+
throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
|
|
3942
5310
|
}
|
|
3943
5311
|
return readSourceContext({
|
|
3944
5312
|
request: parsed.data,
|
|
@@ -3949,9 +5317,9 @@ async function handleSourceContext(request, options) {
|
|
|
3949
5317
|
});
|
|
3950
5318
|
}
|
|
3951
5319
|
async function handleOpenEditor(request, options) {
|
|
3952
|
-
const parsed =
|
|
5320
|
+
const parsed = import_shared23.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
|
|
3953
5321
|
if (!parsed.success) {
|
|
3954
|
-
throw new
|
|
5322
|
+
throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
|
|
3955
5323
|
}
|
|
3956
5324
|
const body = parsed.data;
|
|
3957
5325
|
const sourcePath = await resolveSourceFile({
|
|
@@ -3968,7 +5336,7 @@ async function handleOpenEditor(request, options) {
|
|
|
3968
5336
|
options.logger?.warn(
|
|
3969
5337
|
`[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
|
|
3970
5338
|
);
|
|
3971
|
-
throw new
|
|
5339
|
+
throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
|
|
3972
5340
|
cause: error
|
|
3973
5341
|
});
|
|
3974
5342
|
}
|
|
@@ -3978,73 +5346,84 @@ function createSpotPatchMiddleware(options) {
|
|
|
3978
5346
|
const dataFlowAnalyzer = createDataFlowAnalyzer(options);
|
|
3979
5347
|
const externalAgentController = options.externalAgentControl === void 0 ? void 0 : createExternalAgentBrowserController(options.externalAgentControl);
|
|
3980
5348
|
const middleware = (request, response, next) => {
|
|
3981
|
-
const
|
|
3982
|
-
const agentRoute = matchAgentRequestPath(
|
|
3983
|
-
const
|
|
3984
|
-
const
|
|
3985
|
-
|
|
5349
|
+
const path10 = requestPath(request);
|
|
5350
|
+
const agentRoute = matchAgentRequestPath(path10);
|
|
5351
|
+
const askRoute = matchContextualAskPath(path10);
|
|
5352
|
+
const externalAgentRoute = matchExternalAgentBrowserPath(path10);
|
|
5353
|
+
const externalHandoffRoute = matchExternalHandoffBrowserPath(path10);
|
|
5354
|
+
if (path10 !== import_shared23.SPOTPATCH_ENDPOINTS.sourceContext && path10 !== import_shared23.SPOTPATCH_ENDPOINTS.openEditor && path10 !== import_shared23.SPOTPATCH_ENDPOINTS.dataFlowComponentReport && path10 !== import_shared23.SPOTPATCH_ENDPOINTS.dataFlowPageReport && agentRoute === void 0 && askRoute === void 0 && externalAgentRoute === void 0 && externalHandoffRoute === void 0 && !path10.startsWith(`${import_shared23.SPOTPATCH_API_BASE}/`)) {
|
|
3986
5355
|
next();
|
|
3987
5356
|
return;
|
|
3988
5357
|
}
|
|
3989
5358
|
const handle = async () => {
|
|
3990
|
-
if (
|
|
5359
|
+
if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.bootstrap && bootstrap !== void 0) {
|
|
3991
5360
|
const data = await readRuntimeBootstrap(
|
|
3992
5361
|
request,
|
|
3993
5362
|
bootstrap
|
|
3994
5363
|
);
|
|
3995
|
-
|
|
5364
|
+
writeJson3(response, 200, { ok: true, data });
|
|
3996
5365
|
return;
|
|
3997
5366
|
}
|
|
3998
5367
|
assertRequestAuthorized(request, {
|
|
3999
5368
|
allowLan: options.options.allowLan,
|
|
4000
5369
|
sessionToken: options.session.token
|
|
4001
5370
|
});
|
|
4002
|
-
if (
|
|
5371
|
+
if (askRoute !== void 0) {
|
|
5372
|
+
await handleContextualAskRequest(
|
|
5373
|
+
request,
|
|
5374
|
+
response,
|
|
5375
|
+
askRoute,
|
|
5376
|
+
options.contextualAskManager,
|
|
5377
|
+
options.logger
|
|
5378
|
+
);
|
|
5379
|
+
return;
|
|
5380
|
+
}
|
|
5381
|
+
if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.sourceContext) {
|
|
4003
5382
|
if (request.method !== "POST") {
|
|
4004
|
-
throw new
|
|
5383
|
+
throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
|
|
4005
5384
|
}
|
|
4006
5385
|
const data = await handleSourceContext(request, options);
|
|
4007
|
-
|
|
5386
|
+
writeJson3(response, 200, { ok: true, data });
|
|
4008
5387
|
return;
|
|
4009
5388
|
}
|
|
4010
|
-
if (
|
|
5389
|
+
if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.openEditor) {
|
|
4011
5390
|
if (request.method !== "POST") {
|
|
4012
|
-
throw new
|
|
5391
|
+
throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
|
|
4013
5392
|
}
|
|
4014
5393
|
const data = await handleOpenEditor(request, options);
|
|
4015
|
-
|
|
5394
|
+
writeJson3(response, 200, { ok: true, data });
|
|
4016
5395
|
return;
|
|
4017
5396
|
}
|
|
4018
|
-
if (
|
|
5397
|
+
if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.dataFlowComponentReport) {
|
|
4019
5398
|
if (request.method !== "POST") {
|
|
4020
|
-
throw new
|
|
5399
|
+
throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
|
|
4021
5400
|
}
|
|
4022
5401
|
const data = await handleComponentDataFlowReport(
|
|
4023
5402
|
request,
|
|
4024
5403
|
dataFlowAnalyzer,
|
|
4025
5404
|
options
|
|
4026
5405
|
);
|
|
4027
|
-
|
|
5406
|
+
writeJson3(response, 200, { ok: true, data });
|
|
4028
5407
|
return;
|
|
4029
5408
|
}
|
|
4030
|
-
if (
|
|
5409
|
+
if (path10 === import_shared23.SPOTPATCH_ENDPOINTS.dataFlowPageReport) {
|
|
4031
5410
|
if (request.method !== "POST") {
|
|
4032
|
-
throw new
|
|
5411
|
+
throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
|
|
4033
5412
|
}
|
|
4034
5413
|
const data = await handlePageDataFlowReport(request, dataFlowAnalyzer, options);
|
|
4035
|
-
|
|
5414
|
+
writeJson3(response, 200, { ok: true, data });
|
|
4036
5415
|
return;
|
|
4037
5416
|
}
|
|
4038
5417
|
if (externalAgentRoute !== void 0) {
|
|
4039
5418
|
if (!options.options.externalAgent.enabled || externalAgentController === void 0) {
|
|
4040
|
-
throw new
|
|
5419
|
+
throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.EXTERNAL_HANDOFF_DISABLED);
|
|
4041
5420
|
}
|
|
4042
5421
|
await externalAgentController.handle(
|
|
4043
5422
|
request,
|
|
4044
5423
|
response,
|
|
4045
5424
|
externalAgentRoute,
|
|
4046
5425
|
(target, status, data) => {
|
|
4047
|
-
|
|
5426
|
+
writeJson3(target, status, { ok: true, data });
|
|
4048
5427
|
}
|
|
4049
5428
|
);
|
|
4050
5429
|
return;
|
|
@@ -4061,13 +5440,13 @@ function createSpotPatchMiddleware(options) {
|
|
|
4061
5440
|
...options.externalHandoffService === void 0 ? {} : { service: options.externalHandoffService }
|
|
4062
5441
|
},
|
|
4063
5442
|
(target, status, data) => {
|
|
4064
|
-
|
|
5443
|
+
writeJson3(target, status, { ok: true, data });
|
|
4065
5444
|
}
|
|
4066
5445
|
);
|
|
4067
5446
|
return;
|
|
4068
5447
|
}
|
|
4069
5448
|
if (agentRoute === void 0) {
|
|
4070
|
-
throw new
|
|
5449
|
+
throw new import_shared23.SpotPatchError(import_shared23.ERROR_CODES.INVALID_REQUEST);
|
|
4071
5450
|
}
|
|
4072
5451
|
await handleAgentRequest(
|
|
4073
5452
|
request,
|
|
@@ -4075,12 +5454,12 @@ function createSpotPatchMiddleware(options) {
|
|
|
4075
5454
|
options,
|
|
4076
5455
|
agentRoute,
|
|
4077
5456
|
(target, status, data) => {
|
|
4078
|
-
|
|
5457
|
+
writeJson3(target, status, { ok: true, data });
|
|
4079
5458
|
}
|
|
4080
5459
|
);
|
|
4081
5460
|
};
|
|
4082
5461
|
void handle().catch((error) => {
|
|
4083
|
-
|
|
5462
|
+
writeError2(response, error, options.logger);
|
|
4084
5463
|
});
|
|
4085
5464
|
};
|
|
4086
5465
|
return Object.assign(middleware, {
|
|
@@ -4091,18 +5470,30 @@ function createSpotPatchMiddleware(options) {
|
|
|
4091
5470
|
}
|
|
4092
5471
|
|
|
4093
5472
|
// src/server/source-registration.ts
|
|
4094
|
-
var
|
|
4095
|
-
var
|
|
4096
|
-
var
|
|
5473
|
+
var import_node_crypto13 = require("crypto");
|
|
5474
|
+
var import_promises8 = require("fs/promises");
|
|
5475
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
4097
5476
|
var import_compiler = require("@spotpatch/compiler");
|
|
5477
|
+
var import_shared24 = require("@spotpatch/shared");
|
|
4098
5478
|
var import_zod2 = require("zod");
|
|
4099
|
-
var REGISTRATION_BODY_LIMIT_BYTES =
|
|
5479
|
+
var REGISTRATION_BODY_LIMIT_BYTES = import_shared24.DEFAULT_DATA_FLOW_LIMITS.protocolRequestMaxBytes;
|
|
4100
5480
|
var REGISTRATION_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{16,128}$/u;
|
|
5481
|
+
var DATA_FLOW_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
|
|
4101
5482
|
var INTERNAL_SECRET_HEADER = "x-spotpatch-internal";
|
|
4102
5483
|
var FORBIDDEN_SOURCE_SEGMENTS = /* @__PURE__ */ new Set([".next", "node_modules"]);
|
|
4103
5484
|
var registrationRequestSchema = import_zod2.z.strictObject({
|
|
4104
5485
|
epoch: import_zod2.z.string().regex(REGISTRATION_IDENTITY_PATTERN),
|
|
4105
|
-
resourcePath: import_zod2.z.string().min(1).max(3072)
|
|
5486
|
+
resourcePath: import_zod2.z.string().min(1).max(3072),
|
|
5487
|
+
dataFlow: import_zod2.z.strictObject({
|
|
5488
|
+
sourceVersion: import_zod2.z.string().regex(DATA_FLOW_IDENTITY_PATTERN),
|
|
5489
|
+
components: import_zod2.z.array(
|
|
5490
|
+
import_zod2.z.strictObject({
|
|
5491
|
+
componentSourceId: import_zod2.z.string().regex(DATA_FLOW_IDENTITY_PATTERN),
|
|
5492
|
+
line: import_zod2.z.number().int().positive().max(1e7),
|
|
5493
|
+
column: import_zod2.z.number().int().positive().max(1e7)
|
|
5494
|
+
})
|
|
5495
|
+
).max(import_shared24.DEFAULT_DATA_FLOW_LIMITS.sourceMaxComponents)
|
|
5496
|
+
}).optional()
|
|
4106
5497
|
});
|
|
4107
5498
|
function getSingleHeader3(request, name) {
|
|
4108
5499
|
const value = request.headers[name.toLowerCase()];
|
|
@@ -4114,16 +5505,16 @@ function identitiesMatch(actual, expected) {
|
|
|
4114
5505
|
}
|
|
4115
5506
|
const actualBytes = Buffer.from(actual);
|
|
4116
5507
|
const expectedBytes = Buffer.from(expected);
|
|
4117
|
-
return actualBytes.byteLength === expectedBytes.byteLength && (0,
|
|
5508
|
+
return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto13.timingSafeEqual)(actualBytes, expectedBytes);
|
|
4118
5509
|
}
|
|
4119
5510
|
function isWithinRoot(root, candidate) {
|
|
4120
|
-
const relative =
|
|
4121
|
-
return relative === "" || !relative.startsWith(`..${
|
|
5511
|
+
const relative = import_node_path9.default.relative(root, candidate);
|
|
5512
|
+
return relative === "" || !relative.startsWith(`..${import_node_path9.default.sep}`) && relative !== ".." && !import_node_path9.default.isAbsolute(relative);
|
|
4122
5513
|
}
|
|
4123
5514
|
function hasForbiddenSegment(root, candidate) {
|
|
4124
|
-
return
|
|
5515
|
+
return import_node_path9.default.relative(root, candidate).split(import_node_path9.default.sep).some((segment) => FORBIDDEN_SOURCE_SEGMENTS.has(segment));
|
|
4125
5516
|
}
|
|
4126
|
-
function
|
|
5517
|
+
function writeJson4(response, statusCode, payload) {
|
|
4127
5518
|
const body = JSON.stringify(payload);
|
|
4128
5519
|
response.statusCode = statusCode;
|
|
4129
5520
|
response.setHeader("Cache-Control", "no-store");
|
|
@@ -4143,15 +5534,15 @@ function requestComesFromLoopbackWorker(request) {
|
|
|
4143
5534
|
}
|
|
4144
5535
|
}
|
|
4145
5536
|
async function resolveAuthorizedSource(root, requestedPath, shouldTransform) {
|
|
4146
|
-
if (!
|
|
5537
|
+
if (!import_node_path9.default.isAbsolute(requestedPath) || requestedPath.includes("\0")) {
|
|
4147
5538
|
return void 0;
|
|
4148
5539
|
}
|
|
4149
5540
|
try {
|
|
4150
|
-
const sourceStat = await (0,
|
|
5541
|
+
const sourceStat = await (0, import_promises8.lstat)(requestedPath);
|
|
4151
5542
|
if (!sourceStat.isFile() || sourceStat.isSymbolicLink()) {
|
|
4152
5543
|
return void 0;
|
|
4153
5544
|
}
|
|
4154
|
-
const resolvedPath = await (0,
|
|
5545
|
+
const resolvedPath = await (0, import_promises8.realpath)(requestedPath);
|
|
4155
5546
|
if (!isWithinRoot(root, resolvedPath) || hasForbiddenSegment(root, resolvedPath) || !shouldTransform(resolvedPath)) {
|
|
4156
5547
|
return void 0;
|
|
4157
5548
|
}
|
|
@@ -4164,8 +5555,35 @@ async function createSourceRegistrationService(input) {
|
|
|
4164
5555
|
if (!REGISTRATION_IDENTITY_PATTERN.test(input.internalSecret) || !REGISTRATION_IDENTITY_PATTERN.test(input.registryEpoch)) {
|
|
4165
5556
|
throw new TypeError("The source registration identity is invalid.");
|
|
4166
5557
|
}
|
|
4167
|
-
const root = await (0,
|
|
5558
|
+
const root = await (0, import_promises8.realpath)(input.root);
|
|
4168
5559
|
const sourceFilter = (0, import_compiler.createSourceFilter)(root, input.options);
|
|
5560
|
+
const dataFlowFilter = (0, import_compiler.createDataFlowSourceFilter)(root, input.options);
|
|
5561
|
+
const registrationQueues = /* @__PURE__ */ new Map();
|
|
5562
|
+
async function registerCurrentSource(sourcePath, dataFlow) {
|
|
5563
|
+
if (dataFlow === void 0) {
|
|
5564
|
+
return input.registry.register(sourcePath);
|
|
5565
|
+
}
|
|
5566
|
+
let fileId;
|
|
5567
|
+
const previous = registrationQueues.get(sourcePath) ?? Promise.resolve();
|
|
5568
|
+
const current = previous.catch(() => void 0).then(async () => {
|
|
5569
|
+
const currentSource = await (0, import_promises8.readFile)(sourcePath, "utf8");
|
|
5570
|
+
if ((0, import_compiler.createDataFlowSourceVersion)(currentSource) !== dataFlow.sourceVersion) {
|
|
5571
|
+
return;
|
|
5572
|
+
}
|
|
5573
|
+
fileId = input.registry.registerDataFlowComponents(
|
|
5574
|
+
sourcePath,
|
|
5575
|
+
dataFlow.sourceVersion,
|
|
5576
|
+
dataFlow.components
|
|
5577
|
+
);
|
|
5578
|
+
});
|
|
5579
|
+
registrationQueues.set(sourcePath, current);
|
|
5580
|
+
await current.finally(() => {
|
|
5581
|
+
if (registrationQueues.get(sourcePath) === current) {
|
|
5582
|
+
registrationQueues.delete(sourcePath);
|
|
5583
|
+
}
|
|
5584
|
+
});
|
|
5585
|
+
return fileId;
|
|
5586
|
+
}
|
|
4169
5587
|
const handler = (request, response) => {
|
|
4170
5588
|
const handle = async () => {
|
|
4171
5589
|
const contentType = getSingleHeader3(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
@@ -4173,33 +5591,35 @@ async function createSourceRegistrationService(input) {
|
|
|
4173
5591
|
getSingleHeader3(request, INTERNAL_SECRET_HEADER),
|
|
4174
5592
|
input.internalSecret
|
|
4175
5593
|
)) {
|
|
4176
|
-
|
|
5594
|
+
writeJson4(response, 403, { ok: false });
|
|
4177
5595
|
return;
|
|
4178
5596
|
}
|
|
4179
5597
|
const parsed = registrationRequestSchema.safeParse(
|
|
4180
5598
|
await readJsonRequestBody(request, REGISTRATION_BODY_LIMIT_BYTES)
|
|
4181
5599
|
);
|
|
4182
5600
|
if (!parsed.success || parsed.data.epoch !== input.registryEpoch) {
|
|
4183
|
-
|
|
5601
|
+
writeJson4(response, 400, { ok: false });
|
|
4184
5602
|
return;
|
|
4185
5603
|
}
|
|
4186
5604
|
const sourcePath = await resolveAuthorizedSource(
|
|
4187
5605
|
root,
|
|
4188
5606
|
parsed.data.resourcePath,
|
|
4189
|
-
(absolutePath) => sourceFilter.shouldTransform(absolutePath, "<")
|
|
5607
|
+
(absolutePath) => parsed.data.dataFlow === void 0 ? sourceFilter.shouldTransform(absolutePath, "<") : dataFlowFilter.shouldTransform(absolutePath, "")
|
|
4190
5608
|
);
|
|
4191
5609
|
if (sourcePath === void 0) {
|
|
4192
|
-
|
|
5610
|
+
writeJson4(response, 403, { ok: false });
|
|
4193
5611
|
return;
|
|
4194
5612
|
}
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
5613
|
+
const fileId = await registerCurrentSource(sourcePath, parsed.data.dataFlow);
|
|
5614
|
+
if (fileId === void 0) {
|
|
5615
|
+
writeJson4(response, 409, { ok: false });
|
|
5616
|
+
return;
|
|
5617
|
+
}
|
|
5618
|
+
writeJson4(response, 200, { epoch: input.registryEpoch, fileId });
|
|
4199
5619
|
};
|
|
4200
5620
|
void handle().catch(() => {
|
|
4201
5621
|
if (!response.headersSent) {
|
|
4202
|
-
|
|
5622
|
+
writeJson4(response, 400, { ok: false });
|
|
4203
5623
|
} else {
|
|
4204
5624
|
response.destroy();
|
|
4205
5625
|
}
|
|
@@ -4209,11 +5629,11 @@ async function createSourceRegistrationService(input) {
|
|
|
4209
5629
|
}
|
|
4210
5630
|
|
|
4211
5631
|
// src/session/session.ts
|
|
4212
|
-
var
|
|
5632
|
+
var import_node_crypto14 = require("crypto");
|
|
4213
5633
|
function createSession() {
|
|
4214
5634
|
return Object.freeze({
|
|
4215
|
-
id: (0,
|
|
4216
|
-
token: (0,
|
|
5635
|
+
id: (0, import_node_crypto14.randomBytes)(16).toString("base64url"),
|
|
5636
|
+
token: (0, import_node_crypto14.randomBytes)(16).toString("base64url")
|
|
4217
5637
|
});
|
|
4218
5638
|
}
|
|
4219
5639
|
|
|
@@ -4224,6 +5644,7 @@ var OPTION_KEYS = Object.freeze([
|
|
|
4224
5644
|
"budget",
|
|
4225
5645
|
"debug",
|
|
4226
5646
|
"dataFlow",
|
|
5647
|
+
"contextualAsk",
|
|
4227
5648
|
"editor",
|
|
4228
5649
|
"enabled",
|
|
4229
5650
|
"externalAgent",
|
|
@@ -4315,6 +5736,9 @@ function serializeResolvedSpotPatchOptions(options) {
|
|
|
4315
5736
|
dataFlow: options.dataFlow.enabled ? Object.freeze({
|
|
4316
5737
|
runtime: options.dataFlow.runtime
|
|
4317
5738
|
}) : false,
|
|
5739
|
+
contextualAsk: options.contextualAsk.enabled ? Object.freeze({
|
|
5740
|
+
...options.contextualAsk.defaultExecutor === void 0 ? {} : { defaultExecutor: options.contextualAsk.defaultExecutor }
|
|
5741
|
+
}) : false,
|
|
4318
5742
|
editor: options.editor,
|
|
4319
5743
|
enabled: options.enabled,
|
|
4320
5744
|
externalAgent: options.externalAgent.enabled,
|
|
@@ -4367,6 +5791,33 @@ function parseDataFlow(value) {
|
|
|
4367
5791
|
runtime: value.runtime
|
|
4368
5792
|
});
|
|
4369
5793
|
}
|
|
5794
|
+
function parseContextualAsk(value) {
|
|
5795
|
+
if (value === false || value === true) return value;
|
|
5796
|
+
if (!isRecord2(value)) {
|
|
5797
|
+
throw new TypeError("The SpotPatch contextual Ask transport is invalid.");
|
|
5798
|
+
}
|
|
5799
|
+
const keys = Object.keys(value);
|
|
5800
|
+
if (keys.length === 0) return Object.freeze({});
|
|
5801
|
+
if (!hasExactKeys(value, ["defaultExecutor"]) || !isRecord2(value.defaultExecutor)) {
|
|
5802
|
+
throw new TypeError("The SpotPatch contextual Ask transport is invalid.");
|
|
5803
|
+
}
|
|
5804
|
+
const preference = value.defaultExecutor;
|
|
5805
|
+
if (preference.kind === "managed-codex" && hasExactKeys(preference, ["kind"])) {
|
|
5806
|
+
return Object.freeze({
|
|
5807
|
+
defaultExecutor: Object.freeze({ kind: "managed-codex" })
|
|
5808
|
+
});
|
|
5809
|
+
}
|
|
5810
|
+
if (preference.kind === "configured-key" && hasExactKeys(preference, ["kind", "modelProfileId", "providerProfileId"]) && typeof preference.providerProfileId === "string" && typeof preference.modelProfileId === "string") {
|
|
5811
|
+
return Object.freeze({
|
|
5812
|
+
defaultExecutor: Object.freeze({
|
|
5813
|
+
kind: "configured-key",
|
|
5814
|
+
providerProfileId: preference.providerProfileId,
|
|
5815
|
+
modelProfileId: preference.modelProfileId
|
|
5816
|
+
})
|
|
5817
|
+
});
|
|
5818
|
+
}
|
|
5819
|
+
throw new TypeError("The SpotPatch contextual Ask transport is invalid.");
|
|
5820
|
+
}
|
|
4370
5821
|
function parseSerializedSpotPatchOptions(value) {
|
|
4371
5822
|
if (!isRecord2(value) || !hasExactKeys(value, OPTION_KEYS)) {
|
|
4372
5823
|
throw new TypeError("The SpotPatch options transport is invalid.");
|
|
@@ -4381,6 +5832,7 @@ function parseSerializedSpotPatchOptions(value) {
|
|
|
4381
5832
|
budget: parseBudget(value.budget),
|
|
4382
5833
|
debug: value.debug,
|
|
4383
5834
|
dataFlow: parseDataFlow(value.dataFlow),
|
|
5835
|
+
contextualAsk: parseContextualAsk(value.contextualAsk),
|
|
4384
5836
|
editor: value.editor,
|
|
4385
5837
|
enabled: value.enabled,
|
|
4386
5838
|
externalAgent: value.externalAgent,
|
|
@@ -4399,10 +5851,15 @@ function parseSerializedSpotPatchOptions(value) {
|
|
|
4399
5851
|
}
|
|
4400
5852
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4401
5853
|
0 && (module.exports = {
|
|
5854
|
+
ContextualAskError,
|
|
4402
5855
|
DEFAULT_EXCLUDE,
|
|
4403
5856
|
DEFAULT_OPTIONS,
|
|
4404
5857
|
applyIntegrationPlan,
|
|
5858
|
+
captureAskReadSnapshot,
|
|
5859
|
+
composeContextualAskExecutors,
|
|
4405
5860
|
createAgentJobManager,
|
|
5861
|
+
createConfiguredKeyAskExecutors,
|
|
5862
|
+
createContextualAskManager,
|
|
4406
5863
|
createExternalHandoffService,
|
|
4407
5864
|
createIntegrationFileChange,
|
|
4408
5865
|
createRuntimeAiConfig,
|
|
@@ -4411,9 +5868,12 @@ function parseSerializedSpotPatchOptions(value) {
|
|
|
4411
5868
|
createSourceRegistrationService,
|
|
4412
5869
|
createSourceRegistry,
|
|
4413
5870
|
createSpotPatchMiddleware,
|
|
5871
|
+
createWorkspaceActivityCoordinator,
|
|
4414
5872
|
discoverProjectValidationCheck,
|
|
5873
|
+
handleContextualAskRequest,
|
|
4415
5874
|
integrationPathExists,
|
|
4416
5875
|
isLoopbackHostname,
|
|
5876
|
+
matchContextualAskPath,
|
|
4417
5877
|
parseSerializedSpotPatchOptions,
|
|
4418
5878
|
readIntegrationFile,
|
|
4419
5879
|
readJsonRequestBody,
|