@spotpatch/dev-server 0.6.0 → 0.8.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 +478 -251
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -4
- package/dist/index.d.ts +32 -4
- package/dist/index.js +443 -198
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -2256,6 +2256,9 @@ import { access as access2, lstat as lstat3, readFile as readFile2, realpath as
|
|
|
2256
2256
|
import { createRequire } from "module";
|
|
2257
2257
|
import path3 from "path";
|
|
2258
2258
|
import { promisify } from "util";
|
|
2259
|
+
import {
|
|
2260
|
+
DEFAULT_AGENT_LIMITS as DEFAULT_AGENT_LIMITS2
|
|
2261
|
+
} from "@spotpatch/shared";
|
|
2259
2262
|
var execFileAsync = promisify(execFile);
|
|
2260
2263
|
var TYPESCRIPT_CHECK_ID = "spotpatch-typecheck";
|
|
2261
2264
|
var TYPESCRIPT_CHECK_LABEL = "TypeScript";
|
|
@@ -2323,6 +2326,15 @@ async function resolveTypeScriptCli(appRoot) {
|
|
|
2323
2326
|
function portableRelativePath(from, to) {
|
|
2324
2327
|
return path3.relative(from, to).split(path3.sep).join("/");
|
|
2325
2328
|
}
|
|
2329
|
+
function hasRequiredCheck(checks) {
|
|
2330
|
+
return Object.values(checks).some((check) => check.required);
|
|
2331
|
+
}
|
|
2332
|
+
function availableCheckId(checks, preferred) {
|
|
2333
|
+
if (checks[preferred] === void 0) return preferred;
|
|
2334
|
+
let suffix = 2;
|
|
2335
|
+
while (checks[`${preferred}-${String(suffix)}`] !== void 0) suffix += 1;
|
|
2336
|
+
return `${preferred}-${String(suffix)}`;
|
|
2337
|
+
}
|
|
2326
2338
|
async function discoverProjectValidationCheck(options) {
|
|
2327
2339
|
const appRoot = await realpath2(options.appRoot);
|
|
2328
2340
|
const tsconfigPath = path3.join(appRoot, "tsconfig.json");
|
|
@@ -2351,6 +2363,8 @@ async function discoverProjectValidationCheck(options) {
|
|
|
2351
2363
|
"--noEmit",
|
|
2352
2364
|
"--pretty",
|
|
2353
2365
|
"false",
|
|
2366
|
+
"--incremental",
|
|
2367
|
+
"false",
|
|
2354
2368
|
"--project",
|
|
2355
2369
|
projectPath
|
|
2356
2370
|
]),
|
|
@@ -2358,21 +2372,30 @@ async function discoverProjectValidationCheck(options) {
|
|
|
2358
2372
|
timeoutMs: options.timeoutMs
|
|
2359
2373
|
});
|
|
2360
2374
|
}
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2375
|
+
async function resolveProjectValidationChecks(options) {
|
|
2376
|
+
if (hasRequiredCheck(options.checks)) return options.checks;
|
|
2377
|
+
const discovered = await discoverProjectValidationCheck(options);
|
|
2378
|
+
if (discovered === void 0) return options.checks;
|
|
2379
|
+
const id = availableCheckId(options.checks, discovered.id);
|
|
2380
|
+
return Object.freeze({
|
|
2381
|
+
...options.checks,
|
|
2382
|
+
[id]: Object.freeze({ ...discovered, id })
|
|
2383
|
+
});
|
|
2365
2384
|
}
|
|
2366
|
-
function
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2385
|
+
async function resolveManagedExecutionValidation(options) {
|
|
2386
|
+
const checks = options.ai === false ? Object.freeze({}) : options.ai.execution.checks;
|
|
2387
|
+
const limits = options.ai === false ? DEFAULT_AGENT_LIMITS2 : options.ai.execution.limits;
|
|
2388
|
+
return Object.freeze({
|
|
2389
|
+
checks: await resolveProjectValidationChecks({
|
|
2390
|
+
appRoot: options.appRoot,
|
|
2391
|
+
checks,
|
|
2392
|
+
timeoutMs: limits.checkTimeoutMs
|
|
2393
|
+
}),
|
|
2394
|
+
limits
|
|
2395
|
+
});
|
|
2375
2396
|
}
|
|
2397
|
+
|
|
2398
|
+
// src/project-options.ts
|
|
2376
2399
|
async function resolveProjectOptions(input) {
|
|
2377
2400
|
const userOptions = input.options ?? {};
|
|
2378
2401
|
const resolved = resolveOptions(userOptions, input.environmentAi);
|
|
@@ -2384,22 +2407,15 @@ async function resolveProjectOptions(input) {
|
|
|
2384
2407
|
"SpotPatch trustedFastMode cannot be combined with applyMode auto."
|
|
2385
2408
|
);
|
|
2386
2409
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
);
|
|
2397
|
-
}
|
|
2398
|
-
const id = availableCheckId(checks, discovered.id);
|
|
2399
|
-
checks = Object.freeze({
|
|
2400
|
-
...checks,
|
|
2401
|
-
[id]: Object.freeze({ ...discovered, id })
|
|
2402
|
-
});
|
|
2410
|
+
const checks = await resolveProjectValidationChecks({
|
|
2411
|
+
appRoot: input.appRoot,
|
|
2412
|
+
checks: resolved.ai.execution.checks,
|
|
2413
|
+
timeoutMs: resolved.ai.execution.limits.checkTimeoutMs
|
|
2414
|
+
});
|
|
2415
|
+
if (!Object.values(checks).some((check) => check.required)) {
|
|
2416
|
+
throw new RangeError(
|
|
2417
|
+
"SpotPatch trustedFastMode requires a configured required check or a local TypeScript project with tsconfig.json."
|
|
2418
|
+
);
|
|
2403
2419
|
}
|
|
2404
2420
|
const ai = Object.freeze({
|
|
2405
2421
|
...resolved.ai,
|
|
@@ -2462,6 +2478,7 @@ function createSourceRegistry(options = {}) {
|
|
|
2462
2478
|
);
|
|
2463
2479
|
}
|
|
2464
2480
|
componentIdsByPath.set(normalizedPath, currentIds);
|
|
2481
|
+
return fileId;
|
|
2465
2482
|
},
|
|
2466
2483
|
resolve(fileId) {
|
|
2467
2484
|
return idToPath.get(fileId);
|
|
@@ -2480,10 +2497,10 @@ function createSourceRegistry(options = {}) {
|
|
|
2480
2497
|
|
|
2481
2498
|
// src/server/middleware.ts
|
|
2482
2499
|
import {
|
|
2483
|
-
ERROR_CODES as
|
|
2500
|
+
ERROR_CODES as ERROR_CODES16,
|
|
2484
2501
|
SPOTPATCH_API_BASE,
|
|
2485
|
-
SPOTPATCH_ENDPOINTS as
|
|
2486
|
-
SpotPatchError as
|
|
2502
|
+
SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS4,
|
|
2503
|
+
SpotPatchError as SpotPatchError16,
|
|
2487
2504
|
openEditorRequestSchema,
|
|
2488
2505
|
sourceContextRequestSchema
|
|
2489
2506
|
} from "@spotpatch/shared";
|
|
@@ -3061,11 +3078,171 @@ async function handleExternalHandoffBrowserRequest(request, response, route, opt
|
|
|
3061
3078
|
writeSuccess(response, result.replayed ? 200 : 201, result);
|
|
3062
3079
|
}
|
|
3063
3080
|
|
|
3064
|
-
// src/
|
|
3081
|
+
// src/external-agent/browser-http.ts
|
|
3065
3082
|
import {
|
|
3066
3083
|
ERROR_CODES as ERROR_CODES11,
|
|
3084
|
+
EXTERNAL_AGENT_CONTROL_LIMITS,
|
|
3067
3085
|
SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS2,
|
|
3068
3086
|
SpotPatchError as SpotPatchError11,
|
|
3087
|
+
externalAgentControlCancelRequestSchema,
|
|
3088
|
+
externalAgentControlConnectRequestSchema,
|
|
3089
|
+
externalAgentControlDisconnectRequestSchema,
|
|
3090
|
+
externalAgentControlStatusRequestSchema,
|
|
3091
|
+
externalAgentControlStatusSchema,
|
|
3092
|
+
externalAgentEventSchema,
|
|
3093
|
+
externalAgentEventsRequestSchema,
|
|
3094
|
+
externalAgentManagedResultSchema,
|
|
3095
|
+
externalAgentResultRequestSchema
|
|
3096
|
+
} from "@spotpatch/shared";
|
|
3097
|
+
function matchExternalAgentBrowserPath(path9) {
|
|
3098
|
+
if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentControlStatus) return "status";
|
|
3099
|
+
if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentControlConnect) return "connect";
|
|
3100
|
+
if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentControlDisconnect) {
|
|
3101
|
+
return "disconnect";
|
|
3102
|
+
}
|
|
3103
|
+
if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentControlCancel) return "cancel";
|
|
3104
|
+
if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentEvents) return "events";
|
|
3105
|
+
if (path9 === SPOTPATCH_ENDPOINTS2.externalAgentResult) return "result";
|
|
3106
|
+
return void 0;
|
|
3107
|
+
}
|
|
3108
|
+
function writeEvent(response, event) {
|
|
3109
|
+
response.write(`${JSON.stringify(externalAgentEventSchema.parse(event))}
|
|
3110
|
+
`);
|
|
3111
|
+
}
|
|
3112
|
+
function createExternalAgentBrowserController(port) {
|
|
3113
|
+
const streams = /* @__PURE__ */ new Set();
|
|
3114
|
+
let disposed = false;
|
|
3115
|
+
const handleEvents = async (request, response) => {
|
|
3116
|
+
const parsed = externalAgentEventsRequestSchema.safeParse(
|
|
3117
|
+
await readJsonRequestBody(request)
|
|
3118
|
+
);
|
|
3119
|
+
if (!parsed.success) {
|
|
3120
|
+
throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
|
|
3121
|
+
}
|
|
3122
|
+
if (disposed || streams.size >= EXTERNAL_AGENT_CONTROL_LIMITS.maximumEventSubscribers) {
|
|
3123
|
+
throw new SpotPatchError11(ERROR_CODES11.BRIDGE_BUSY);
|
|
3124
|
+
}
|
|
3125
|
+
response.statusCode = 200;
|
|
3126
|
+
response.setHeader("Cache-Control", "no-store");
|
|
3127
|
+
response.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
|
|
3128
|
+
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
3129
|
+
streams.add(response);
|
|
3130
|
+
const initial = externalAgentControlStatusSchema.parse(port.getStatus());
|
|
3131
|
+
writeEvent(response, { type: "status", data: initial });
|
|
3132
|
+
const unsubscribe = port.subscribe((status) => {
|
|
3133
|
+
if (!response.destroyed && status.sequence > (parsed.data.afterSequence ?? -1)) {
|
|
3134
|
+
writeEvent(response, { type: "status", data: status });
|
|
3135
|
+
}
|
|
3136
|
+
});
|
|
3137
|
+
const heartbeat = setInterval(() => {
|
|
3138
|
+
if (!response.destroyed) {
|
|
3139
|
+
const status = port.getStatus();
|
|
3140
|
+
writeEvent(response, {
|
|
3141
|
+
type: "heartbeat",
|
|
3142
|
+
sequence: status.sequence,
|
|
3143
|
+
emittedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3146
|
+
}, EXTERNAL_AGENT_CONTROL_LIMITS.eventHeartbeatMs);
|
|
3147
|
+
heartbeat.unref();
|
|
3148
|
+
await new Promise((resolve) => {
|
|
3149
|
+
const close = () => {
|
|
3150
|
+
response.off("close", close);
|
|
3151
|
+
clearInterval(heartbeat);
|
|
3152
|
+
unsubscribe();
|
|
3153
|
+
streams.delete(response);
|
|
3154
|
+
resolve();
|
|
3155
|
+
};
|
|
3156
|
+
response.once("close", close);
|
|
3157
|
+
});
|
|
3158
|
+
};
|
|
3159
|
+
return Object.freeze({
|
|
3160
|
+
async handle(request, response, route, writeSuccess) {
|
|
3161
|
+
if (request.method !== "POST" || disposed) {
|
|
3162
|
+
throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
|
|
3163
|
+
}
|
|
3164
|
+
if (route === "events") {
|
|
3165
|
+
await handleEvents(request, response);
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
if (route === "status") {
|
|
3169
|
+
const parsed2 = externalAgentControlStatusRequestSchema.safeParse(
|
|
3170
|
+
await readJsonRequestBody(request)
|
|
3171
|
+
);
|
|
3172
|
+
if (!parsed2.success) throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
|
|
3173
|
+
writeSuccess(
|
|
3174
|
+
response,
|
|
3175
|
+
200,
|
|
3176
|
+
externalAgentControlStatusSchema.parse(port.getStatus())
|
|
3177
|
+
);
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
if (route === "connect") {
|
|
3181
|
+
const parsed2 = externalAgentControlConnectRequestSchema.safeParse(
|
|
3182
|
+
await readJsonRequestBody(request)
|
|
3183
|
+
);
|
|
3184
|
+
if (!parsed2.success) throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
|
|
3185
|
+
const controller = new AbortController();
|
|
3186
|
+
response.once("close", () => {
|
|
3187
|
+
if (!response.writableEnded) controller.abort("browser-disconnected");
|
|
3188
|
+
});
|
|
3189
|
+
writeSuccess(
|
|
3190
|
+
response,
|
|
3191
|
+
200,
|
|
3192
|
+
externalAgentControlStatusSchema.parse(
|
|
3193
|
+
await port.connect(parsed2.data, controller.signal)
|
|
3194
|
+
)
|
|
3195
|
+
);
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
3198
|
+
if (route === "disconnect") {
|
|
3199
|
+
const parsed2 = externalAgentControlDisconnectRequestSchema.safeParse(
|
|
3200
|
+
await readJsonRequestBody(request)
|
|
3201
|
+
);
|
|
3202
|
+
if (!parsed2.success) throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
|
|
3203
|
+
writeSuccess(
|
|
3204
|
+
response,
|
|
3205
|
+
200,
|
|
3206
|
+
externalAgentControlStatusSchema.parse(await port.disconnect(parsed2.data))
|
|
3207
|
+
);
|
|
3208
|
+
return;
|
|
3209
|
+
}
|
|
3210
|
+
if (route === "cancel") {
|
|
3211
|
+
const parsed2 = externalAgentControlCancelRequestSchema.safeParse(
|
|
3212
|
+
await readJsonRequestBody(request)
|
|
3213
|
+
);
|
|
3214
|
+
if (!parsed2.success) throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
|
|
3215
|
+
writeSuccess(
|
|
3216
|
+
response,
|
|
3217
|
+
200,
|
|
3218
|
+
externalAgentControlStatusSchema.parse(await port.cancel(parsed2.data))
|
|
3219
|
+
);
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
const parsed = externalAgentResultRequestSchema.safeParse(
|
|
3223
|
+
await readJsonRequestBody(request)
|
|
3224
|
+
);
|
|
3225
|
+
if (!parsed.success) throw new SpotPatchError11(ERROR_CODES11.INVALID_REQUEST);
|
|
3226
|
+
const result = port.getResult(parsed.data.revision);
|
|
3227
|
+
if (result === void 0) {
|
|
3228
|
+
throw new SpotPatchError11(ERROR_CODES11.HANDOFF_NOT_FOUND);
|
|
3229
|
+
}
|
|
3230
|
+
writeSuccess(response, 200, externalAgentManagedResultSchema.parse(result));
|
|
3231
|
+
},
|
|
3232
|
+
dispose() {
|
|
3233
|
+
if (disposed) return;
|
|
3234
|
+
disposed = true;
|
|
3235
|
+
for (const response of streams) response.end();
|
|
3236
|
+
streams.clear();
|
|
3237
|
+
}
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
// src/server/agent-http.ts
|
|
3242
|
+
import {
|
|
3243
|
+
ERROR_CODES as ERROR_CODES12,
|
|
3244
|
+
SPOTPATCH_ENDPOINTS as SPOTPATCH_ENDPOINTS3,
|
|
3245
|
+
SpotPatchError as SpotPatchError12,
|
|
3069
3246
|
agentCapabilityRequestSchema,
|
|
3070
3247
|
agentJobActionRequestSchema,
|
|
3071
3248
|
agentJobCreateRequestSchema,
|
|
@@ -3110,16 +3287,16 @@ var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
|
|
|
3110
3287
|
"failed"
|
|
3111
3288
|
]);
|
|
3112
3289
|
function matchAgentRequestPath(path9) {
|
|
3113
|
-
if (path9 ===
|
|
3290
|
+
if (path9 === SPOTPATCH_ENDPOINTS3.agentCapability) {
|
|
3114
3291
|
return Object.freeze({ kind: "capability" });
|
|
3115
3292
|
}
|
|
3116
|
-
if (path9 ===
|
|
3293
|
+
if (path9 === SPOTPATCH_ENDPOINTS3.agentWorkspaceHealth) {
|
|
3117
3294
|
return Object.freeze({ kind: "workspace-health" });
|
|
3118
3295
|
}
|
|
3119
|
-
if (path9 ===
|
|
3296
|
+
if (path9 === SPOTPATCH_ENDPOINTS3.agentJobs) {
|
|
3120
3297
|
return Object.freeze({ kind: "create-job" });
|
|
3121
3298
|
}
|
|
3122
|
-
const prefix = `${
|
|
3299
|
+
const prefix = `${SPOTPATCH_ENDPOINTS3.agentJobs}/`;
|
|
3123
3300
|
if (!path9.startsWith(prefix)) {
|
|
3124
3301
|
return void 0;
|
|
3125
3302
|
}
|
|
@@ -3137,7 +3314,7 @@ function matchAgentRequestPath(path9) {
|
|
|
3137
3314
|
}
|
|
3138
3315
|
function requireAgentManager(options) {
|
|
3139
3316
|
if (options.agentManager === void 0 || options.options.ai === false) {
|
|
3140
|
-
throw new
|
|
3317
|
+
throw new SpotPatchError12(ERROR_CODES12.AI_DISABLED);
|
|
3141
3318
|
}
|
|
3142
3319
|
return options.agentManager;
|
|
3143
3320
|
}
|
|
@@ -3190,13 +3367,13 @@ function streamAgentJobEvents(response, manager, jobId) {
|
|
|
3190
3367
|
}
|
|
3191
3368
|
async function handleCapability(request, response, options, writeSuccess) {
|
|
3192
3369
|
if (request.method !== "POST") {
|
|
3193
|
-
throw new
|
|
3370
|
+
throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
|
|
3194
3371
|
}
|
|
3195
3372
|
const parsed = agentCapabilityRequestSchema.safeParse(
|
|
3196
3373
|
await readJsonRequestBody(request)
|
|
3197
3374
|
);
|
|
3198
3375
|
if (!parsed.success) {
|
|
3199
|
-
throw new
|
|
3376
|
+
throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
|
|
3200
3377
|
}
|
|
3201
3378
|
const controller = new AbortController();
|
|
3202
3379
|
const abort = () => {
|
|
@@ -3215,13 +3392,13 @@ async function handleCapability(request, response, options, writeSuccess) {
|
|
|
3215
3392
|
}
|
|
3216
3393
|
async function handleCreateJob(request, response, options, writeSuccess) {
|
|
3217
3394
|
if (request.method !== "POST") {
|
|
3218
|
-
throw new
|
|
3395
|
+
throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
|
|
3219
3396
|
}
|
|
3220
3397
|
const parsed = agentJobCreateRequestSchema.safeParse(
|
|
3221
3398
|
await readJsonRequestBody(request, MAX_AGENT_REQUEST_BODY_BYTES)
|
|
3222
3399
|
);
|
|
3223
3400
|
if (!parsed.success) {
|
|
3224
|
-
throw new
|
|
3401
|
+
throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
|
|
3225
3402
|
}
|
|
3226
3403
|
const authorizedRequest = await authorizeAgentJobRequest({
|
|
3227
3404
|
request: parsed.data,
|
|
@@ -3234,13 +3411,13 @@ async function handleCreateJob(request, response, options, writeSuccess) {
|
|
|
3234
3411
|
}
|
|
3235
3412
|
async function handleWorkspaceHealth(request, response, options, writeSuccess) {
|
|
3236
3413
|
if (request.method !== "POST") {
|
|
3237
|
-
throw new
|
|
3414
|
+
throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
|
|
3238
3415
|
}
|
|
3239
3416
|
const parsed = agentWorkspaceHealthRequestSchema.safeParse(
|
|
3240
3417
|
await readJsonRequestBody(request)
|
|
3241
3418
|
);
|
|
3242
3419
|
if (!parsed.success) {
|
|
3243
|
-
throw new
|
|
3420
|
+
throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
|
|
3244
3421
|
}
|
|
3245
3422
|
const controller = new AbortController();
|
|
3246
3423
|
const abort = () => {
|
|
@@ -3257,13 +3434,13 @@ async function handleWorkspaceHealth(request, response, options, writeSuccess) {
|
|
|
3257
3434
|
async function handleJobAction(request, response, options, route, writeSuccess) {
|
|
3258
3435
|
const manager = requireAgentManager(options);
|
|
3259
3436
|
if (request.method !== "POST") {
|
|
3260
|
-
throw new
|
|
3437
|
+
throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
|
|
3261
3438
|
}
|
|
3262
3439
|
const parsed = agentJobActionRequestSchema.safeParse(
|
|
3263
3440
|
await readJsonRequestBody(request)
|
|
3264
3441
|
);
|
|
3265
3442
|
if (!parsed.success) {
|
|
3266
|
-
throw new
|
|
3443
|
+
throw new SpotPatchError12(ERROR_CODES12.INVALID_REQUEST);
|
|
3267
3444
|
}
|
|
3268
3445
|
if (route.action === "events") {
|
|
3269
3446
|
streamAgentJobEvents(response, manager, route.jobId);
|
|
@@ -3384,8 +3561,8 @@ import {
|
|
|
3384
3561
|
} from "@spotpatch/analyzer";
|
|
3385
3562
|
import {
|
|
3386
3563
|
DATA_FLOW_SCHEMA_VERSION,
|
|
3387
|
-
ERROR_CODES as
|
|
3388
|
-
SpotPatchError as
|
|
3564
|
+
ERROR_CODES as ERROR_CODES13,
|
|
3565
|
+
SpotPatchError as SpotPatchError13,
|
|
3389
3566
|
dataFlowComponentReportRequestSchema,
|
|
3390
3567
|
dataFlowPageReportRequestSchema,
|
|
3391
3568
|
limitDataFlowReportCollections
|
|
@@ -3404,7 +3581,7 @@ function limitDataFlowReportToBytes(report, maximumBytes) {
|
|
|
3404
3581
|
truncatedBy: "bytes"
|
|
3405
3582
|
});
|
|
3406
3583
|
if (envelopeBytes(limited) > maximumBytes) {
|
|
3407
|
-
throw new
|
|
3584
|
+
throw new SpotPatchError13(ERROR_CODES13.INTERNAL_ERROR);
|
|
3408
3585
|
}
|
|
3409
3586
|
for (let maximumDependencies = 1; maximumDependencies <= structurallyLimited.dependencies.length; maximumDependencies += 1) {
|
|
3410
3587
|
const candidate = limitDataFlowReportCollections(structurallyLimited, {
|
|
@@ -3433,7 +3610,7 @@ async function analyzeTarget(request, analyzer, options) {
|
|
|
3433
3610
|
request.componentSourceId
|
|
3434
3611
|
);
|
|
3435
3612
|
if (anchor?.sourceVersion !== request.sourceVersion) {
|
|
3436
|
-
throw new
|
|
3613
|
+
throw new SpotPatchError13(ERROR_CODES13.DATA_FLOW_SOURCE_STALE);
|
|
3437
3614
|
}
|
|
3438
3615
|
return anchor;
|
|
3439
3616
|
}
|
|
@@ -3450,7 +3627,7 @@ async function analyzeTarget(request, analyzer, options) {
|
|
|
3450
3627
|
column: resolvedRequest.column
|
|
3451
3628
|
});
|
|
3452
3629
|
if (resolvedRequest.sourceVersion !== void 0 && resolvedRequest.sourceVersion !== report.component.source.sourceVersion) {
|
|
3453
|
-
throw new
|
|
3630
|
+
throw new SpotPatchError13(ERROR_CODES13.DATA_FLOW_SOURCE_STALE);
|
|
3454
3631
|
}
|
|
3455
3632
|
return limitDataFlowReportToBytes(
|
|
3456
3633
|
report,
|
|
@@ -3459,7 +3636,7 @@ async function analyzeTarget(request, analyzer, options) {
|
|
|
3459
3636
|
}
|
|
3460
3637
|
function requireAnalyzer(analyzer) {
|
|
3461
3638
|
if (analyzer === void 0) {
|
|
3462
|
-
throw new
|
|
3639
|
+
throw new SpotPatchError13(ERROR_CODES13.DATA_FLOW_DISABLED);
|
|
3463
3640
|
}
|
|
3464
3641
|
return analyzer;
|
|
3465
3642
|
}
|
|
@@ -3471,7 +3648,7 @@ async function handleComponentDataFlowReport(request, analyzer, options) {
|
|
|
3471
3648
|
)
|
|
3472
3649
|
);
|
|
3473
3650
|
if (!parsed.success) {
|
|
3474
|
-
throw new
|
|
3651
|
+
throw new SpotPatchError13(ERROR_CODES13.INVALID_REQUEST);
|
|
3475
3652
|
}
|
|
3476
3653
|
return analyzeTarget(parsed.data, requireAnalyzer(analyzer), options);
|
|
3477
3654
|
}
|
|
@@ -3483,7 +3660,7 @@ async function handlePageDataFlowReport(request, analyzer, options) {
|
|
|
3483
3660
|
)
|
|
3484
3661
|
);
|
|
3485
3662
|
if (!parsed.success) {
|
|
3486
|
-
throw new
|
|
3663
|
+
throw new SpotPatchError13(ERROR_CODES13.INVALID_REQUEST);
|
|
3487
3664
|
}
|
|
3488
3665
|
const activeAnalyzer = requireAnalyzer(analyzer);
|
|
3489
3666
|
const componentReports = await Promise.all(
|
|
@@ -3556,7 +3733,7 @@ async function handlePageDataFlowReport(request, analyzer, options) {
|
|
|
3556
3733
|
// src/server/request-security.ts
|
|
3557
3734
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3558
3735
|
import { isIP } from "net";
|
|
3559
|
-
import { ERROR_CODES as
|
|
3736
|
+
import { ERROR_CODES as ERROR_CODES14, SPOTPATCH_TOKEN_HEADER, SpotPatchError as SpotPatchError14 } from "@spotpatch/shared";
|
|
3560
3737
|
function getSingleHeader(request, name) {
|
|
3561
3738
|
const value = request.headers[name.toLowerCase()];
|
|
3562
3739
|
return Array.isArray(value) ? value[0] : value;
|
|
@@ -3603,32 +3780,32 @@ function parseOrigin(value) {
|
|
|
3603
3780
|
function assertRequestAuthorized(request, options) {
|
|
3604
3781
|
const actualToken = getSingleHeader(request, SPOTPATCH_TOKEN_HEADER);
|
|
3605
3782
|
if (!tokensMatch2(actualToken, options.sessionToken)) {
|
|
3606
|
-
throw new
|
|
3783
|
+
throw new SpotPatchError14(ERROR_CODES14.INVALID_TOKEN);
|
|
3607
3784
|
}
|
|
3608
3785
|
const hostHeader = getSingleHeader(request, "host");
|
|
3609
3786
|
const originHeader = getSingleHeader(request, "origin");
|
|
3610
3787
|
const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
|
|
3611
3788
|
const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
|
|
3612
3789
|
if (host === void 0 || origin === void 0) {
|
|
3613
|
-
throw new
|
|
3790
|
+
throw new SpotPatchError14(ERROR_CODES14.ORIGIN_NOT_ALLOWED);
|
|
3614
3791
|
}
|
|
3615
3792
|
const hostIsLoopback = isLoopbackHostname(host.hostname);
|
|
3616
3793
|
const originIsLoopback = isLoopbackHostname(origin.hostname);
|
|
3617
3794
|
if (!options.allowLan) {
|
|
3618
3795
|
if (!hostIsLoopback || !originIsLoopback) {
|
|
3619
|
-
throw new
|
|
3796
|
+
throw new SpotPatchError14(ERROR_CODES14.ORIGIN_NOT_ALLOWED);
|
|
3620
3797
|
}
|
|
3621
3798
|
return;
|
|
3622
3799
|
}
|
|
3623
3800
|
if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
|
|
3624
|
-
throw new
|
|
3801
|
+
throw new SpotPatchError14(ERROR_CODES14.ORIGIN_NOT_ALLOWED);
|
|
3625
3802
|
}
|
|
3626
3803
|
}
|
|
3627
3804
|
|
|
3628
3805
|
// src/server/runtime-bootstrap.ts
|
|
3629
3806
|
import {
|
|
3630
|
-
ERROR_CODES as
|
|
3631
|
-
SpotPatchError as
|
|
3807
|
+
ERROR_CODES as ERROR_CODES15,
|
|
3808
|
+
SpotPatchError as SpotPatchError15,
|
|
3632
3809
|
runtimeBootstrapRequestSchema,
|
|
3633
3810
|
runtimeConfigSchema
|
|
3634
3811
|
} from "@spotpatch/shared";
|
|
@@ -3658,7 +3835,7 @@ function resolveRuntimeBootstrapOptions(options) {
|
|
|
3658
3835
|
function assertRuntimeBootstrapRequest(request, expectedOrigin) {
|
|
3659
3836
|
const contentType = getSingleHeader2(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
3660
3837
|
if (request.method !== "POST" || contentType !== "application/json") {
|
|
3661
|
-
throw new
|
|
3838
|
+
throw new SpotPatchError15(ERROR_CODES15.INVALID_REQUEST);
|
|
3662
3839
|
}
|
|
3663
3840
|
const host = getSingleHeader2(request, "host");
|
|
3664
3841
|
let hostIsLoopback = false;
|
|
@@ -3670,7 +3847,7 @@ function assertRuntimeBootstrapRequest(request, expectedOrigin) {
|
|
|
3670
3847
|
}
|
|
3671
3848
|
}
|
|
3672
3849
|
if (!hostIsLoopback || getSingleHeader2(request, "origin") !== expectedOrigin || getSingleHeader2(request, "sec-fetch-site") !== "same-origin") {
|
|
3673
|
-
throw new
|
|
3850
|
+
throw new SpotPatchError15(ERROR_CODES15.ORIGIN_NOT_ALLOWED);
|
|
3674
3851
|
}
|
|
3675
3852
|
}
|
|
3676
3853
|
async function readRuntimeBootstrap(request, options) {
|
|
@@ -3679,123 +3856,123 @@ async function readRuntimeBootstrap(request, options) {
|
|
|
3679
3856
|
await readJsonRequestBody(request)
|
|
3680
3857
|
);
|
|
3681
3858
|
if (!parsedBody.success) {
|
|
3682
|
-
throw new
|
|
3859
|
+
throw new SpotPatchError15(ERROR_CODES15.INVALID_REQUEST);
|
|
3683
3860
|
}
|
|
3684
3861
|
return options.runtimeConfig;
|
|
3685
3862
|
}
|
|
3686
3863
|
|
|
3687
3864
|
// src/server/middleware.ts
|
|
3688
3865
|
var STATUS_BY_ERROR = Object.freeze({
|
|
3689
|
-
[
|
|
3690
|
-
[
|
|
3691
|
-
[
|
|
3692
|
-
[
|
|
3693
|
-
[
|
|
3694
|
-
[
|
|
3695
|
-
[
|
|
3696
|
-
[
|
|
3697
|
-
[
|
|
3698
|
-
[
|
|
3699
|
-
[
|
|
3700
|
-
[
|
|
3701
|
-
[
|
|
3702
|
-
[
|
|
3703
|
-
[
|
|
3704
|
-
[
|
|
3705
|
-
[
|
|
3706
|
-
[
|
|
3707
|
-
[
|
|
3708
|
-
[
|
|
3709
|
-
[
|
|
3710
|
-
[
|
|
3711
|
-
[
|
|
3712
|
-
[
|
|
3713
|
-
[
|
|
3714
|
-
[
|
|
3715
|
-
[
|
|
3716
|
-
[
|
|
3717
|
-
[
|
|
3718
|
-
[
|
|
3719
|
-
[
|
|
3720
|
-
[
|
|
3721
|
-
[
|
|
3722
|
-
[
|
|
3723
|
-
[
|
|
3724
|
-
[
|
|
3725
|
-
[
|
|
3726
|
-
[
|
|
3727
|
-
[
|
|
3728
|
-
[
|
|
3729
|
-
[
|
|
3730
|
-
[
|
|
3731
|
-
[
|
|
3732
|
-
[
|
|
3733
|
-
[
|
|
3734
|
-
[
|
|
3735
|
-
[
|
|
3736
|
-
[
|
|
3737
|
-
[
|
|
3738
|
-
[
|
|
3739
|
-
[
|
|
3740
|
-
[
|
|
3741
|
-
[
|
|
3742
|
-
[
|
|
3866
|
+
[ERROR_CODES16.INVALID_REQUEST]: 400,
|
|
3867
|
+
[ERROR_CODES16.INVALID_TOKEN]: 401,
|
|
3868
|
+
[ERROR_CODES16.ORIGIN_NOT_ALLOWED]: 403,
|
|
3869
|
+
[ERROR_CODES16.SOURCE_NOT_FOUND]: 404,
|
|
3870
|
+
[ERROR_CODES16.SOURCE_OUTSIDE_ROOT]: 403,
|
|
3871
|
+
[ERROR_CODES16.SOURCE_TOO_LARGE]: 413,
|
|
3872
|
+
[ERROR_CODES16.EDITOR_OPEN_FAILED]: 500,
|
|
3873
|
+
[ERROR_CODES16.DATA_FLOW_DISABLED]: 404,
|
|
3874
|
+
[ERROR_CODES16.DATA_FLOW_SOURCE_STALE]: 409,
|
|
3875
|
+
[ERROR_CODES16.DATA_FLOW_ANALYSIS_CANCELLED]: 409,
|
|
3876
|
+
[ERROR_CODES16.AI_DISABLED]: 404,
|
|
3877
|
+
[ERROR_CODES16.PROVIDER_NOT_CONFIGURED]: 503,
|
|
3878
|
+
[ERROR_CODES16.PROVIDER_AUTH_FAILED]: 502,
|
|
3879
|
+
[ERROR_CODES16.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
|
|
3880
|
+
[ERROR_CODES16.MODEL_NOT_ALLOWED]: 400,
|
|
3881
|
+
[ERROR_CODES16.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
|
|
3882
|
+
[ERROR_CODES16.PROVIDER_RATE_LIMITED]: 429,
|
|
3883
|
+
[ERROR_CODES16.AGENT_BUSY]: 409,
|
|
3884
|
+
[ERROR_CODES16.AGENT_LIMIT_EXCEEDED]: 413,
|
|
3885
|
+
[ERROR_CODES16.AGENT_CANCELLED]: 409,
|
|
3886
|
+
[ERROR_CODES16.EXTERNAL_HANDOFF_DISABLED]: 404,
|
|
3887
|
+
[ERROR_CODES16.EXTERNAL_HANDOFF_UNAVAILABLE]: 503,
|
|
3888
|
+
[ERROR_CODES16.HANDOFF_VALIDATION_FAILED]: 422,
|
|
3889
|
+
[ERROR_CODES16.HANDOFF_SOURCE_STALE]: 409,
|
|
3890
|
+
[ERROR_CODES16.HANDOFF_NOT_FOUND]: 404,
|
|
3891
|
+
[ERROR_CODES16.HANDOFF_EXPIRED]: 410,
|
|
3892
|
+
[ERROR_CODES16.HANDOFF_CURSOR_INVALID]: 409,
|
|
3893
|
+
[ERROR_CODES16.HANDOFF_RESPONSE_TOO_LARGE]: 413,
|
|
3894
|
+
[ERROR_CODES16.BRIDGE_UNAUTHORIZED]: 401,
|
|
3895
|
+
[ERROR_CODES16.BRIDGE_PROTOCOL_MISMATCH]: 409,
|
|
3896
|
+
[ERROR_CODES16.BRIDGE_BUSY]: 429,
|
|
3897
|
+
[ERROR_CODES16.EXTERNAL_AGENT_BUSY]: 409,
|
|
3898
|
+
[ERROR_CODES16.ACTIVE_ADAPTER_CONFLICT]: 409,
|
|
3899
|
+
[ERROR_CODES16.ACTIVE_ADAPTER_LEASE_INVALID]: 409,
|
|
3900
|
+
[ERROR_CODES16.ACTIVE_DISPATCH_INVALID]: 409,
|
|
3901
|
+
[ERROR_CODES16.SESSION_NOT_FOUND]: 404,
|
|
3902
|
+
[ERROR_CODES16.SESSION_AMBIGUOUS]: 409,
|
|
3903
|
+
[ERROR_CODES16.SESSION_CLOSED]: 410,
|
|
3904
|
+
[ERROR_CODES16.WORKTREE_DIRTY]: 409,
|
|
3905
|
+
[ERROR_CODES16.WORKTREE_NOT_REPOSITORY]: 409,
|
|
3906
|
+
[ERROR_CODES16.WORKTREE_OPERATION_IN_PROGRESS]: 409,
|
|
3907
|
+
[ERROR_CODES16.WORKTREE_CONFLICTED]: 409,
|
|
3908
|
+
[ERROR_CODES16.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
|
|
3909
|
+
[ERROR_CODES16.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
|
|
3910
|
+
[ERROR_CODES16.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
|
|
3911
|
+
[ERROR_CODES16.TOOL_DENIED]: 403,
|
|
3912
|
+
[ERROR_CODES16.TOOL_INPUT_INVALID]: 422,
|
|
3913
|
+
[ERROR_CODES16.TOOL_ARGUMENTS_INVALID]: 422,
|
|
3914
|
+
[ERROR_CODES16.TOOL_CALL_ID_CONFLICT]: 422,
|
|
3915
|
+
[ERROR_CODES16.TOOL_PATH_DENIED]: 403,
|
|
3916
|
+
[ERROR_CODES16.PATCH_REJECTED]: 422,
|
|
3917
|
+
[ERROR_CODES16.VALIDATION_FAILED]: 422,
|
|
3918
|
+
[ERROR_CODES16.APPLY_CONFLICT]: 409,
|
|
3919
|
+
[ERROR_CODES16.INTERNAL_ERROR]: 500
|
|
3743
3920
|
});
|
|
3744
3921
|
var PUBLIC_MESSAGES = Object.freeze({
|
|
3745
|
-
[
|
|
3746
|
-
[
|
|
3747
|
-
[
|
|
3748
|
-
[
|
|
3749
|
-
[
|
|
3750
|
-
[
|
|
3751
|
-
[
|
|
3752
|
-
[
|
|
3753
|
-
[
|
|
3754
|
-
[
|
|
3755
|
-
[
|
|
3756
|
-
[
|
|
3757
|
-
[
|
|
3758
|
-
[
|
|
3759
|
-
[
|
|
3760
|
-
[
|
|
3761
|
-
[
|
|
3762
|
-
[
|
|
3763
|
-
[
|
|
3764
|
-
[
|
|
3765
|
-
[
|
|
3766
|
-
[
|
|
3767
|
-
[
|
|
3768
|
-
[
|
|
3769
|
-
[
|
|
3770
|
-
[
|
|
3771
|
-
[
|
|
3772
|
-
[
|
|
3773
|
-
[
|
|
3774
|
-
[
|
|
3775
|
-
[
|
|
3776
|
-
[
|
|
3777
|
-
[
|
|
3778
|
-
[
|
|
3779
|
-
[
|
|
3780
|
-
[
|
|
3781
|
-
[
|
|
3782
|
-
[
|
|
3783
|
-
[
|
|
3784
|
-
[
|
|
3785
|
-
[
|
|
3786
|
-
[
|
|
3787
|
-
[
|
|
3788
|
-
[
|
|
3789
|
-
[
|
|
3790
|
-
[
|
|
3791
|
-
[
|
|
3792
|
-
[
|
|
3793
|
-
[
|
|
3794
|
-
[
|
|
3795
|
-
[
|
|
3796
|
-
[
|
|
3797
|
-
[
|
|
3798
|
-
[
|
|
3922
|
+
[ERROR_CODES16.INVALID_REQUEST]: "The request is invalid.",
|
|
3923
|
+
[ERROR_CODES16.INVALID_TOKEN]: "The session token is invalid.",
|
|
3924
|
+
[ERROR_CODES16.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
|
|
3925
|
+
[ERROR_CODES16.SOURCE_NOT_FOUND]: "The source file is unavailable.",
|
|
3926
|
+
[ERROR_CODES16.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
|
|
3927
|
+
[ERROR_CODES16.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
|
|
3928
|
+
[ERROR_CODES16.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
|
|
3929
|
+
[ERROR_CODES16.DATA_FLOW_DISABLED]: "Component data-flow analysis is not enabled.",
|
|
3930
|
+
[ERROR_CODES16.DATA_FLOW_SOURCE_STALE]: "The selected source version is stale.",
|
|
3931
|
+
[ERROR_CODES16.DATA_FLOW_ANALYSIS_CANCELLED]: "The data-flow analysis was cancelled.",
|
|
3932
|
+
[ERROR_CODES16.AI_DISABLED]: "AI execution is not enabled.",
|
|
3933
|
+
[ERROR_CODES16.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
|
|
3934
|
+
[ERROR_CODES16.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
|
|
3935
|
+
[ERROR_CODES16.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
|
|
3936
|
+
[ERROR_CODES16.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
|
|
3937
|
+
[ERROR_CODES16.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
|
|
3938
|
+
[ERROR_CODES16.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
|
|
3939
|
+
[ERROR_CODES16.AGENT_BUSY]: "Another Agent job is already running.",
|
|
3940
|
+
[ERROR_CODES16.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
|
|
3941
|
+
[ERROR_CODES16.AGENT_CANCELLED]: "The Agent job was cancelled.",
|
|
3942
|
+
[ERROR_CODES16.EXTERNAL_HANDOFF_DISABLED]: "External Agent handoff is not enabled.",
|
|
3943
|
+
[ERROR_CODES16.EXTERNAL_HANDOFF_UNAVAILABLE]: "External Agent handoff is temporarily unavailable.",
|
|
3944
|
+
[ERROR_CODES16.HANDOFF_VALIDATION_FAILED]: "The handoff content is invalid.",
|
|
3945
|
+
[ERROR_CODES16.HANDOFF_SOURCE_STALE]: "The selected source is stale.",
|
|
3946
|
+
[ERROR_CODES16.HANDOFF_NOT_FOUND]: "No current handoff is available.",
|
|
3947
|
+
[ERROR_CODES16.HANDOFF_EXPIRED]: "The handoff has expired.",
|
|
3948
|
+
[ERROR_CODES16.HANDOFF_CURSOR_INVALID]: "The handoff cursor is invalid.",
|
|
3949
|
+
[ERROR_CODES16.HANDOFF_RESPONSE_TOO_LARGE]: "The handoff exceeds the size limit.",
|
|
3950
|
+
[ERROR_CODES16.BRIDGE_UNAUTHORIZED]: "The local bridge request is unauthorized.",
|
|
3951
|
+
[ERROR_CODES16.BRIDGE_PROTOCOL_MISMATCH]: "The local bridge protocol is incompatible.",
|
|
3952
|
+
[ERROR_CODES16.BRIDGE_BUSY]: "The local bridge is busy.",
|
|
3953
|
+
[ERROR_CODES16.EXTERNAL_AGENT_BUSY]: "The connected external Agent is busy.",
|
|
3954
|
+
[ERROR_CODES16.ACTIVE_ADAPTER_CONFLICT]: "Another active Agent adapter is connected.",
|
|
3955
|
+
[ERROR_CODES16.ACTIVE_ADAPTER_LEASE_INVALID]: "The active Agent adapter lease is invalid.",
|
|
3956
|
+
[ERROR_CODES16.ACTIVE_DISPATCH_INVALID]: "The active Agent dispatch transition is invalid.",
|
|
3957
|
+
[ERROR_CODES16.SESSION_NOT_FOUND]: "No active SpotPatch session was found.",
|
|
3958
|
+
[ERROR_CODES16.SESSION_AMBIGUOUS]: "More than one SpotPatch session matches.",
|
|
3959
|
+
[ERROR_CODES16.SESSION_CLOSED]: "The SpotPatch session has closed.",
|
|
3960
|
+
[ERROR_CODES16.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
|
|
3961
|
+
[ERROR_CODES16.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
|
|
3962
|
+
[ERROR_CODES16.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
|
|
3963
|
+
[ERROR_CODES16.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
|
|
3964
|
+
[ERROR_CODES16.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
|
|
3965
|
+
[ERROR_CODES16.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
|
|
3966
|
+
[ERROR_CODES16.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
|
|
3967
|
+
[ERROR_CODES16.TOOL_DENIED]: "The Agent tool request was denied.",
|
|
3968
|
+
[ERROR_CODES16.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
|
|
3969
|
+
[ERROR_CODES16.TOOL_ARGUMENTS_INVALID]: "The Agent tool arguments are invalid.",
|
|
3970
|
+
[ERROR_CODES16.TOOL_CALL_ID_CONFLICT]: "A tool call ID conflicts within one Agent turn.",
|
|
3971
|
+
[ERROR_CODES16.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
|
|
3972
|
+
[ERROR_CODES16.PATCH_REJECTED]: "The proposed patch was rejected.",
|
|
3973
|
+
[ERROR_CODES16.VALIDATION_FAILED]: "The proposed change failed validation.",
|
|
3974
|
+
[ERROR_CODES16.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
|
|
3975
|
+
[ERROR_CODES16.INTERNAL_ERROR]: "The request could not be completed."
|
|
3799
3976
|
});
|
|
3800
3977
|
function writeJson2(response, status, payload) {
|
|
3801
3978
|
response.statusCode = status;
|
|
@@ -3804,11 +3981,11 @@ function writeJson2(response, status, payload) {
|
|
|
3804
3981
|
response.end(JSON.stringify(payload));
|
|
3805
3982
|
}
|
|
3806
3983
|
function asSpotPatchError(error) {
|
|
3807
|
-
return error instanceof
|
|
3984
|
+
return error instanceof SpotPatchError16 ? error : new SpotPatchError16(ERROR_CODES16.INTERNAL_ERROR, void 0, { cause: error });
|
|
3808
3985
|
}
|
|
3809
3986
|
function writeError(response, error, logger) {
|
|
3810
3987
|
const normalized = asSpotPatchError(error);
|
|
3811
|
-
if (normalized.code ===
|
|
3988
|
+
if (normalized.code === ERROR_CODES16.INTERNAL_ERROR) {
|
|
3812
3989
|
logger?.warn("[spotpatch:server] Internal request failure.");
|
|
3813
3990
|
}
|
|
3814
3991
|
writeJson2(response, STATUS_BY_ERROR[normalized.code], {
|
|
@@ -3831,7 +4008,7 @@ async function handleSourceContext(request, options) {
|
|
|
3831
4008
|
await readJsonRequestBody(request)
|
|
3832
4009
|
);
|
|
3833
4010
|
if (!parsed.success) {
|
|
3834
|
-
throw new
|
|
4011
|
+
throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
|
|
3835
4012
|
}
|
|
3836
4013
|
return readSourceContext({
|
|
3837
4014
|
request: parsed.data,
|
|
@@ -3844,7 +4021,7 @@ async function handleSourceContext(request, options) {
|
|
|
3844
4021
|
async function handleOpenEditor(request, options) {
|
|
3845
4022
|
const parsed = openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
|
|
3846
4023
|
if (!parsed.success) {
|
|
3847
|
-
throw new
|
|
4024
|
+
throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
|
|
3848
4025
|
}
|
|
3849
4026
|
const body = parsed.data;
|
|
3850
4027
|
const sourcePath = await resolveSourceFile({
|
|
@@ -3861,7 +4038,7 @@ async function handleOpenEditor(request, options) {
|
|
|
3861
4038
|
options.logger?.warn(
|
|
3862
4039
|
`[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
|
|
3863
4040
|
);
|
|
3864
|
-
throw new
|
|
4041
|
+
throw new SpotPatchError16(ERROR_CODES16.EDITOR_OPEN_FAILED, void 0, {
|
|
3865
4042
|
cause: error
|
|
3866
4043
|
});
|
|
3867
4044
|
}
|
|
@@ -3869,16 +4046,18 @@ async function handleOpenEditor(request, options) {
|
|
|
3869
4046
|
function createSpotPatchMiddleware(options) {
|
|
3870
4047
|
const bootstrap = options.bootstrap === void 0 ? void 0 : resolveRuntimeBootstrapOptions(options.bootstrap);
|
|
3871
4048
|
const dataFlowAnalyzer = createDataFlowAnalyzer(options);
|
|
3872
|
-
|
|
4049
|
+
const externalAgentController = options.externalAgentControl === void 0 ? void 0 : createExternalAgentBrowserController(options.externalAgentControl);
|
|
4050
|
+
const middleware = (request, response, next) => {
|
|
3873
4051
|
const path9 = requestPath(request);
|
|
3874
4052
|
const agentRoute = matchAgentRequestPath(path9);
|
|
4053
|
+
const externalAgentRoute = matchExternalAgentBrowserPath(path9);
|
|
3875
4054
|
const externalHandoffRoute = matchExternalHandoffBrowserPath(path9);
|
|
3876
|
-
if (path9 !==
|
|
4055
|
+
if (path9 !== SPOTPATCH_ENDPOINTS4.sourceContext && path9 !== SPOTPATCH_ENDPOINTS4.openEditor && path9 !== SPOTPATCH_ENDPOINTS4.dataFlowComponentReport && path9 !== SPOTPATCH_ENDPOINTS4.dataFlowPageReport && agentRoute === void 0 && externalAgentRoute === void 0 && externalHandoffRoute === void 0 && !path9.startsWith(`${SPOTPATCH_API_BASE}/`)) {
|
|
3877
4056
|
next();
|
|
3878
4057
|
return;
|
|
3879
4058
|
}
|
|
3880
4059
|
const handle = async () => {
|
|
3881
|
-
if (path9 ===
|
|
4060
|
+
if (path9 === SPOTPATCH_ENDPOINTS4.bootstrap && bootstrap !== void 0) {
|
|
3882
4061
|
const data = await readRuntimeBootstrap(
|
|
3883
4062
|
request,
|
|
3884
4063
|
bootstrap
|
|
@@ -3890,25 +4069,25 @@ function createSpotPatchMiddleware(options) {
|
|
|
3890
4069
|
allowLan: options.options.allowLan,
|
|
3891
4070
|
sessionToken: options.session.token
|
|
3892
4071
|
});
|
|
3893
|
-
if (path9 ===
|
|
4072
|
+
if (path9 === SPOTPATCH_ENDPOINTS4.sourceContext) {
|
|
3894
4073
|
if (request.method !== "POST") {
|
|
3895
|
-
throw new
|
|
4074
|
+
throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
|
|
3896
4075
|
}
|
|
3897
4076
|
const data = await handleSourceContext(request, options);
|
|
3898
4077
|
writeJson2(response, 200, { ok: true, data });
|
|
3899
4078
|
return;
|
|
3900
4079
|
}
|
|
3901
|
-
if (path9 ===
|
|
4080
|
+
if (path9 === SPOTPATCH_ENDPOINTS4.openEditor) {
|
|
3902
4081
|
if (request.method !== "POST") {
|
|
3903
|
-
throw new
|
|
4082
|
+
throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
|
|
3904
4083
|
}
|
|
3905
4084
|
const data = await handleOpenEditor(request, options);
|
|
3906
4085
|
writeJson2(response, 200, { ok: true, data });
|
|
3907
4086
|
return;
|
|
3908
4087
|
}
|
|
3909
|
-
if (path9 ===
|
|
4088
|
+
if (path9 === SPOTPATCH_ENDPOINTS4.dataFlowComponentReport) {
|
|
3910
4089
|
if (request.method !== "POST") {
|
|
3911
|
-
throw new
|
|
4090
|
+
throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
|
|
3912
4091
|
}
|
|
3913
4092
|
const data = await handleComponentDataFlowReport(
|
|
3914
4093
|
request,
|
|
@@ -3918,14 +4097,28 @@ function createSpotPatchMiddleware(options) {
|
|
|
3918
4097
|
writeJson2(response, 200, { ok: true, data });
|
|
3919
4098
|
return;
|
|
3920
4099
|
}
|
|
3921
|
-
if (path9 ===
|
|
4100
|
+
if (path9 === SPOTPATCH_ENDPOINTS4.dataFlowPageReport) {
|
|
3922
4101
|
if (request.method !== "POST") {
|
|
3923
|
-
throw new
|
|
4102
|
+
throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
|
|
3924
4103
|
}
|
|
3925
4104
|
const data = await handlePageDataFlowReport(request, dataFlowAnalyzer, options);
|
|
3926
4105
|
writeJson2(response, 200, { ok: true, data });
|
|
3927
4106
|
return;
|
|
3928
4107
|
}
|
|
4108
|
+
if (externalAgentRoute !== void 0) {
|
|
4109
|
+
if (!options.options.externalAgent.enabled || externalAgentController === void 0) {
|
|
4110
|
+
throw new SpotPatchError16(ERROR_CODES16.EXTERNAL_HANDOFF_DISABLED);
|
|
4111
|
+
}
|
|
4112
|
+
await externalAgentController.handle(
|
|
4113
|
+
request,
|
|
4114
|
+
response,
|
|
4115
|
+
externalAgentRoute,
|
|
4116
|
+
(target, status, data) => {
|
|
4117
|
+
writeJson2(target, status, { ok: true, data });
|
|
4118
|
+
}
|
|
4119
|
+
);
|
|
4120
|
+
return;
|
|
4121
|
+
}
|
|
3929
4122
|
if (externalHandoffRoute !== void 0) {
|
|
3930
4123
|
await handleExternalHandoffBrowserRequest(
|
|
3931
4124
|
request,
|
|
@@ -3944,7 +4137,7 @@ function createSpotPatchMiddleware(options) {
|
|
|
3944
4137
|
return;
|
|
3945
4138
|
}
|
|
3946
4139
|
if (agentRoute === void 0) {
|
|
3947
|
-
throw new
|
|
4140
|
+
throw new SpotPatchError16(ERROR_CODES16.INVALID_REQUEST);
|
|
3948
4141
|
}
|
|
3949
4142
|
await handleAgentRequest(
|
|
3950
4143
|
request,
|
|
@@ -3960,21 +4153,42 @@ function createSpotPatchMiddleware(options) {
|
|
|
3960
4153
|
writeError(response, error, options.logger);
|
|
3961
4154
|
});
|
|
3962
4155
|
};
|
|
4156
|
+
return Object.assign(middleware, {
|
|
4157
|
+
dispose() {
|
|
4158
|
+
externalAgentController?.dispose();
|
|
4159
|
+
}
|
|
4160
|
+
});
|
|
3963
4161
|
}
|
|
3964
4162
|
|
|
3965
4163
|
// src/server/source-registration.ts
|
|
3966
4164
|
import { timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
3967
|
-
import { lstat as lstat4, realpath as realpath6 } from "fs/promises";
|
|
4165
|
+
import { lstat as lstat4, readFile as readFile4, realpath as realpath6 } from "fs/promises";
|
|
3968
4166
|
import path8 from "path";
|
|
3969
|
-
import {
|
|
4167
|
+
import {
|
|
4168
|
+
createDataFlowSourceFilter,
|
|
4169
|
+
createDataFlowSourceVersion,
|
|
4170
|
+
createSourceFilter
|
|
4171
|
+
} from "@spotpatch/compiler";
|
|
4172
|
+
import { DEFAULT_DATA_FLOW_LIMITS as DEFAULT_DATA_FLOW_LIMITS2 } from "@spotpatch/shared";
|
|
3970
4173
|
import { z as z2 } from "zod";
|
|
3971
|
-
var REGISTRATION_BODY_LIMIT_BYTES =
|
|
4174
|
+
var REGISTRATION_BODY_LIMIT_BYTES = DEFAULT_DATA_FLOW_LIMITS2.protocolRequestMaxBytes;
|
|
3972
4175
|
var REGISTRATION_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{16,128}$/u;
|
|
4176
|
+
var DATA_FLOW_IDENTITY_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
|
|
3973
4177
|
var INTERNAL_SECRET_HEADER = "x-spotpatch-internal";
|
|
3974
4178
|
var FORBIDDEN_SOURCE_SEGMENTS = /* @__PURE__ */ new Set([".next", "node_modules"]);
|
|
3975
4179
|
var registrationRequestSchema = z2.strictObject({
|
|
3976
4180
|
epoch: z2.string().regex(REGISTRATION_IDENTITY_PATTERN),
|
|
3977
|
-
resourcePath: z2.string().min(1).max(3072)
|
|
4181
|
+
resourcePath: z2.string().min(1).max(3072),
|
|
4182
|
+
dataFlow: z2.strictObject({
|
|
4183
|
+
sourceVersion: z2.string().regex(DATA_FLOW_IDENTITY_PATTERN),
|
|
4184
|
+
components: z2.array(
|
|
4185
|
+
z2.strictObject({
|
|
4186
|
+
componentSourceId: z2.string().regex(DATA_FLOW_IDENTITY_PATTERN),
|
|
4187
|
+
line: z2.number().int().positive().max(1e7),
|
|
4188
|
+
column: z2.number().int().positive().max(1e7)
|
|
4189
|
+
})
|
|
4190
|
+
).max(DEFAULT_DATA_FLOW_LIMITS2.sourceMaxComponents)
|
|
4191
|
+
}).optional()
|
|
3978
4192
|
});
|
|
3979
4193
|
function getSingleHeader3(request, name) {
|
|
3980
4194
|
const value = request.headers[name.toLowerCase()];
|
|
@@ -4038,6 +4252,33 @@ async function createSourceRegistrationService(input) {
|
|
|
4038
4252
|
}
|
|
4039
4253
|
const root = await realpath6(input.root);
|
|
4040
4254
|
const sourceFilter = createSourceFilter(root, input.options);
|
|
4255
|
+
const dataFlowFilter = createDataFlowSourceFilter(root, input.options);
|
|
4256
|
+
const registrationQueues = /* @__PURE__ */ new Map();
|
|
4257
|
+
async function registerCurrentSource(sourcePath, dataFlow) {
|
|
4258
|
+
if (dataFlow === void 0) {
|
|
4259
|
+
return input.registry.register(sourcePath);
|
|
4260
|
+
}
|
|
4261
|
+
let fileId;
|
|
4262
|
+
const previous = registrationQueues.get(sourcePath) ?? Promise.resolve();
|
|
4263
|
+
const current = previous.catch(() => void 0).then(async () => {
|
|
4264
|
+
const currentSource = await readFile4(sourcePath, "utf8");
|
|
4265
|
+
if (createDataFlowSourceVersion(currentSource) !== dataFlow.sourceVersion) {
|
|
4266
|
+
return;
|
|
4267
|
+
}
|
|
4268
|
+
fileId = input.registry.registerDataFlowComponents(
|
|
4269
|
+
sourcePath,
|
|
4270
|
+
dataFlow.sourceVersion,
|
|
4271
|
+
dataFlow.components
|
|
4272
|
+
);
|
|
4273
|
+
});
|
|
4274
|
+
registrationQueues.set(sourcePath, current);
|
|
4275
|
+
await current.finally(() => {
|
|
4276
|
+
if (registrationQueues.get(sourcePath) === current) {
|
|
4277
|
+
registrationQueues.delete(sourcePath);
|
|
4278
|
+
}
|
|
4279
|
+
});
|
|
4280
|
+
return fileId;
|
|
4281
|
+
}
|
|
4041
4282
|
const handler = (request, response) => {
|
|
4042
4283
|
const handle = async () => {
|
|
4043
4284
|
const contentType = getSingleHeader3(request, "content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
@@ -4058,16 +4299,18 @@ async function createSourceRegistrationService(input) {
|
|
|
4058
4299
|
const sourcePath = await resolveAuthorizedSource(
|
|
4059
4300
|
root,
|
|
4060
4301
|
parsed.data.resourcePath,
|
|
4061
|
-
(absolutePath) => sourceFilter.shouldTransform(absolutePath, "<")
|
|
4302
|
+
(absolutePath) => parsed.data.dataFlow === void 0 ? sourceFilter.shouldTransform(absolutePath, "<") : dataFlowFilter.shouldTransform(absolutePath, "")
|
|
4062
4303
|
);
|
|
4063
4304
|
if (sourcePath === void 0) {
|
|
4064
4305
|
writeJson3(response, 403, { ok: false });
|
|
4065
4306
|
return;
|
|
4066
4307
|
}
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4308
|
+
const fileId = await registerCurrentSource(sourcePath, parsed.data.dataFlow);
|
|
4309
|
+
if (fileId === void 0) {
|
|
4310
|
+
writeJson3(response, 409, { ok: false });
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
4313
|
+
writeJson3(response, 200, { epoch: input.registryEpoch, fileId });
|
|
4071
4314
|
};
|
|
4072
4315
|
void handle().catch(() => {
|
|
4073
4316
|
if (!response.headersSent) {
|
|
@@ -4291,8 +4534,10 @@ export {
|
|
|
4291
4534
|
readRuntimeBootstrap,
|
|
4292
4535
|
resolveCredentialEnvironment,
|
|
4293
4536
|
resolveEnvironmentAiConfiguration,
|
|
4537
|
+
resolveManagedExecutionValidation,
|
|
4294
4538
|
resolveOptions,
|
|
4295
4539
|
resolveProjectOptions,
|
|
4540
|
+
resolveProjectValidationChecks,
|
|
4296
4541
|
resolveRuntimeBootstrapOptions,
|
|
4297
4542
|
serializeResolvedSpotPatchOptions
|
|
4298
4543
|
};
|