@neuralnomads/codenomad-dev 0.20.0-dev-20260911-ea37f97b → 0.20.0-dev-20260917-aa51cbb9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/api-types.js.map +1 -1
- package/dist/opencode/automation-plugin.test.js +32 -38
- package/dist/opencode/automation-plugin.test.js.map +1 -1
- package/dist/opencode/compatibility/contract-fixture.js +15 -0
- package/dist/opencode/compatibility/contract-fixture.js.map +1 -0
- package/dist/opencode/compatibility/events.js +9 -0
- package/dist/opencode/compatibility/events.js.map +1 -0
- package/dist/opencode/compatibility/location.js +89 -0
- package/dist/opencode/compatibility/location.js.map +1 -0
- package/dist/opencode/compatibility/location.test.js +92 -0
- package/dist/opencode/compatibility/location.test.js.map +1 -0
- package/dist/opencode/compatibility/negotiate.js +73 -0
- package/dist/opencode/compatibility/negotiate.js.map +1 -0
- package/dist/opencode/compatibility/negotiate.test.js +61 -0
- package/dist/opencode/compatibility/negotiate.test.js.map +1 -0
- package/dist/opencode/compatibility/proxy-locations.js +137 -0
- package/dist/opencode/compatibility/proxy-locations.js.map +1 -0
- package/dist/opencode/compatibility/proxy-locations.test.js +42 -0
- package/dist/opencode/compatibility/proxy-locations.test.js.map +1 -0
- package/dist/opencode/compatibility/proxy.js +60 -0
- package/dist/opencode/compatibility/proxy.js.map +1 -0
- package/dist/opencode/compatibility/proxy.test.js +47 -0
- package/dist/opencode/compatibility/proxy.test.js.map +1 -0
- package/dist/opencode/compatibility/requests.js +54 -0
- package/dist/opencode/compatibility/requests.js.map +1 -0
- package/dist/opencode/compatibility/runtime.js +26 -0
- package/dist/opencode/compatibility/runtime.js.map +1 -0
- package/dist/opencode/compatibility/transport.js +112 -0
- package/dist/opencode/compatibility/transport.js.map +1 -0
- package/dist/opencode/compatibility/transport.test.js +83 -0
- package/dist/opencode/compatibility/transport.test.js.map +1 -0
- package/dist/opencode/session-pruning/location.js +16 -0
- package/dist/opencode/session-pruning/location.js.map +1 -0
- package/dist/opencode/session-pruning/package.test.js +37 -0
- package/dist/opencode/session-pruning/package.test.js.map +1 -0
- package/dist/opencode/session-pruning/plugin.js +5 -2
- package/dist/opencode/session-pruning/plugin.js.map +1 -1
- package/dist/opencode/session-pruning/preview-store.js +3 -2
- package/dist/opencode/session-pruning/preview-store.js.map +1 -1
- package/dist/opencode/session-pruning/pruning.test.js +7 -5
- package/dist/opencode/session-pruning/pruning.test.js.map +1 -1
- package/dist/opencode/session-pruning/service.js +5 -4
- package/dist/opencode/session-pruning/service.js.map +1 -1
- package/dist/opencode/session-pruning/service.test.js +65 -53
- package/dist/opencode/session-pruning/service.test.js.map +1 -1
- package/dist/permissions/opencode-replier.js +2 -2
- package/dist/permissions/opencode-replier.js.map +1 -1
- package/dist/permissions/opencode-replier.test.js +1 -1
- package/dist/permissions/opencode-replier.test.js.map +1 -1
- package/dist/permissions/opencode-yolo-metadata.js.map +1 -1
- package/dist/permissions/opencode-yolo-metadata.test.js.map +1 -1
- package/dist/plugins/session-pruning/plugin.mjs +422 -271
- package/dist/server/__tests__/instance-proxy-legacy.test.js +111 -0
- package/dist/server/__tests__/instance-proxy-legacy.test.js.map +1 -0
- package/dist/server/__tests__/instance-proxy.test.js +95 -34
- package/dist/server/__tests__/instance-proxy.test.js.map +1 -1
- package/dist/server/http-server.js +147 -237
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/routes/session-pruning.js +4 -2
- package/dist/server/routes/session-pruning.js.map +1 -1
- package/dist/server/routes/session-pruning.test.js +16 -2
- package/dist/server/routes/session-pruning.test.js.map +1 -1
- package/dist/server/routes/worktrees.js +1 -0
- package/dist/server/routes/worktrees.js.map +1 -1
- package/dist/server/routes/worktrees.test.js +54 -1
- package/dist/server/routes/worktrees.test.js.map +1 -1
- package/dist/workspaces/host-opencode-service.test.js +56 -7
- package/dist/workspaces/host-opencode-service.test.js.map +1 -1
- package/dist/workspaces/instance-client.js.map +1 -1
- package/dist/workspaces/instance-events.js +71 -57
- package/dist/workspaces/instance-events.js.map +1 -1
- package/dist/workspaces/instance-events.test.js +122 -9
- package/dist/workspaces/instance-events.test.js.map +1 -1
- package/dist/workspaces/manager.js +24 -39
- package/dist/workspaces/manager.js.map +1 -1
- package/dist/workspaces/manager.test.js +43 -7
- package/dist/workspaces/manager.test.js.map +1 -1
- package/dist/workspaces/opencode-cli-service.js +56 -29
- package/dist/workspaces/opencode-cli-service.js.map +1 -1
- package/dist/workspaces/opencode-connection-generation.test.js +57 -0
- package/dist/workspaces/opencode-connection-generation.test.js.map +1 -0
- package/dist/workspaces/opencode-service.js +70 -16
- package/dist/workspaces/opencode-service.js.map +1 -1
- package/dist/workspaces/opencode-service.test.js +3 -4
- package/dist/workspaces/opencode-service.test.js.map +1 -1
- package/dist/workspaces/worktree-session-evacuation.js +31 -11
- package/dist/workspaces/worktree-session-evacuation.js.map +1 -1
- package/dist/workspaces/worktree-session-evacuation.test.js +211 -0
- package/dist/workspaces/worktree-session-evacuation.test.js.map +1 -1
- package/dist/workspaces/wsl-opencode-service.test.js +64 -24
- package/dist/workspaces/wsl-opencode-service.test.js.map +1 -1
- package/package.json +3 -3
- package/public/assets/App-BROhwPo9.js +54 -0
- package/public/assets/App-BROhwPo9.js.map +1 -0
- package/public/assets/{FilesTab-CRpGDs1B.js → FilesTab-DY3Vr-3r.js} +3 -3
- package/public/assets/{FilesTab-CRpGDs1B.js.map → FilesTab-DY3Vr-3r.js.map} +1 -1
- package/public/assets/{GitChangesTab-DxFebTiq.js → GitChangesTab-BD6CJngE.js} +3 -3
- package/public/assets/{GitChangesTab-DxFebTiq.js.map → GitChangesTab-BD6CJngE.js.map} +1 -1
- package/public/assets/{SplitFilePanel-DgTMrrO-.js → SplitFilePanel-BdY6d_6m.js} +2 -2
- package/public/assets/{SplitFilePanel-DgTMrrO-.js.map → SplitFilePanel-BdY6d_6m.js.map} +1 -1
- package/public/assets/StatusTab-C-_2yaPa.js +2 -0
- package/public/assets/StatusTab-C-_2yaPa.js.map +1 -0
- package/public/assets/{align-justify-v_yPXSys.js → align-justify-DI8VTZ4G.js} +2 -2
- package/public/assets/{align-justify-v_yPXSys.js.map → align-justify-DI8VTZ4G.js.map} +1 -1
- package/public/assets/{bundle-full-HzL_Jtqk.js → bundle-full-D_M65Eb_.js} +2 -2
- package/public/assets/{bundle-full-HzL_Jtqk.js.map → bundle-full-D_M65Eb_.js.map} +1 -1
- package/public/assets/{client-state-CF-PJ0GG.js → client-state-CWCzCOz6.js} +2 -2
- package/public/assets/{client-state-CF-PJ0GG.js.map → client-state-CWCzCOz6.js.map} +1 -1
- package/public/assets/{diff-viewer-BFDQBDVy.js → diff-viewer-CGPsYEr3.js} +2 -2
- package/public/assets/{diff-viewer-BFDQBDVy.js.map → diff-viewer-CGPsYEr3.js.map} +1 -1
- package/public/assets/index-B8-lAqjr.js +2 -0
- package/public/assets/index-B8-lAqjr.js.map +1 -0
- package/public/assets/index-BeUewQym.js +2 -0
- package/public/assets/index-BeUewQym.js.map +1 -0
- package/public/assets/index-BxRFHpJd.js +2 -0
- package/public/assets/{index-D2UHSOG5.js.map → index-BxRFHpJd.js.map} +1 -1
- package/public/assets/index-Bzv1kU-S.js +2 -0
- package/public/assets/{index-CTKYD_VJ.js.map → index-Bzv1kU-S.js.map} +1 -1
- package/public/assets/index-C9tI4CRG.js +2 -0
- package/public/assets/index-C9tI4CRG.js.map +1 -0
- package/public/assets/index-CGYHFZ6F.js +2 -0
- package/public/assets/index-CGYHFZ6F.js.map +1 -0
- package/public/assets/{index-CHYmKl10.js → index-CMEAjBFp.js} +2 -2
- package/public/assets/index-CMEAjBFp.js.map +1 -0
- package/public/assets/index-Cazypvyc.js +2 -0
- package/public/assets/index-Cazypvyc.js.map +1 -0
- package/public/assets/index-CwqzofWP.js +2 -0
- package/public/assets/index-CwqzofWP.js.map +1 -0
- package/public/assets/index-Da9yOa6q.js +2 -0
- package/public/assets/index-Da9yOa6q.js.map +1 -0
- package/public/assets/{index-RK9gf0uk.js → index-Ddjkirek.js} +2 -2
- package/public/assets/{index-RK9gf0uk.js.map → index-Ddjkirek.js.map} +1 -1
- package/public/assets/index-HHgvEOAH.js +3 -0
- package/public/assets/index-HHgvEOAH.js.map +1 -0
- package/public/assets/index-MeFffHqb.css +1 -0
- package/public/assets/loading-CdhHiSED.css +1 -0
- package/public/assets/{loading-BSB82snY.js → loading-Gqh6NPUr.js} +2 -2
- package/public/assets/{loading-BSB82snY.js.map → loading-Gqh6NPUr.js.map} +1 -1
- package/public/assets/main-Cbx7KdTr.js +38 -0
- package/public/assets/main-Cbx7KdTr.js.map +1 -0
- package/public/assets/{markdown-DtidoL2Z.js → markdown-BWnjyvnX.js} +4 -4
- package/public/assets/{markdown-DtidoL2Z.js.map → markdown-BWnjyvnX.js.map} +1 -1
- package/public/assets/monaco-viewer-BEJIiZWL.js +27 -0
- package/public/assets/monaco-viewer-BEJIiZWL.js.map +1 -0
- package/public/assets/{startup-state-settings-card-BY741Rt0.js → startup-state-settings-card-C4E2Itop.js} +2 -2
- package/public/assets/{startup-state-settings-card-BY741Rt0.js.map → startup-state-settings-card-C4E2Itop.js.map} +1 -1
- package/public/assets/{terminal-square-BdC_jwm3.js → terminal-square-C3wutdV0.js} +2 -2
- package/public/assets/{terminal-square-BdC_jwm3.js.map → terminal-square-C3wutdV0.js.map} +1 -1
- package/public/assets/tool-call-DNsAQ_m4.js +60 -0
- package/public/assets/tool-call-DNsAQ_m4.js.map +1 -0
- package/public/assets/{unified-picker-C0eo1chJ.js → unified-picker-tB7y17Qz.js} +2 -2
- package/public/assets/unified-picker-tB7y17Qz.js.map +1 -0
- package/public/assets/{wrap-text-BuMtdvMx.js → wrap-text-CkBxzt04.js} +2 -2
- package/public/assets/{wrap-text-BuMtdvMx.js.map → wrap-text-CkBxzt04.js.map} +1 -1
- package/public/index.html +4 -4
- package/public/loading.html +5 -5
- package/public/sw.js +1 -1
- package/public/sw.js.map +1 -1
- package/public/assets/App-0-FcHzPS.js +0 -44
- package/public/assets/App-0-FcHzPS.js.map +0 -1
- package/public/assets/StatusTab-LKUA9cbH.js +0 -2
- package/public/assets/StatusTab-LKUA9cbH.js.map +0 -1
- package/public/assets/index-8iPlm2r0.js +0 -2
- package/public/assets/index-8iPlm2r0.js.map +0 -1
- package/public/assets/index-B185mC7m.js +0 -2
- package/public/assets/index-B185mC7m.js.map +0 -1
- package/public/assets/index-Bbj1xF8i.js +0 -2
- package/public/assets/index-Bbj1xF8i.js.map +0 -1
- package/public/assets/index-BsmFN3k1.js +0 -2
- package/public/assets/index-BsmFN3k1.js.map +0 -1
- package/public/assets/index-Bz_OQJYA.css +0 -1
- package/public/assets/index-C52CfqK3.js +0 -2
- package/public/assets/index-C52CfqK3.js.map +0 -1
- package/public/assets/index-CHYmKl10.js.map +0 -1
- package/public/assets/index-CTKYD_VJ.js +0 -2
- package/public/assets/index-D2UHSOG5.js +0 -2
- package/public/assets/index-DDoB_Z36.js +0 -2
- package/public/assets/index-DDoB_Z36.js.map +0 -1
- package/public/assets/index-DcwAtZw-.js +0 -3
- package/public/assets/index-DcwAtZw-.js.map +0 -1
- package/public/assets/index-DxUD3RYr.js +0 -2
- package/public/assets/index-DxUD3RYr.js.map +0 -1
- package/public/assets/loading-CmEVQgyj.css +0 -1
- package/public/assets/main-CZztwrfv.js +0 -48
- package/public/assets/main-CZztwrfv.js.map +0 -1
- package/public/assets/monaco-viewer-CpxDfB-6.js +0 -27
- package/public/assets/monaco-viewer-CpxDfB-6.js.map +0 -1
- package/public/assets/tool-call-DbtCsJ4x.js +0 -60
- package/public/assets/tool-call-DbtCsJ4x.js.map +0 -1
- package/public/assets/unified-picker-C0eo1chJ.js.map +0 -1
|
@@ -7,7 +7,7 @@ import { connect as connectTcp, isIP } from "net";
|
|
|
7
7
|
import path from "path";
|
|
8
8
|
import { connect as connectTls } from "tls";
|
|
9
9
|
import { fetch } from "undici";
|
|
10
|
-
import { isInvalidRequestError, isPtyNotFoundError, isSessionNotFoundError, isShellNotFoundError } from "@opencode
|
|
10
|
+
import { isInvalidRequestError, isPtyNotFoundError, isSessionNotFoundError, isShellNotFoundError } from "@opencode/client";
|
|
11
11
|
import { registerWorkspaceRoutes } from "./routes/workspaces";
|
|
12
12
|
import { registerSettingsRoutes } from "./routes/settings";
|
|
13
13
|
import { registerFilesystemRoutes } from "./routes/filesystem";
|
|
@@ -28,6 +28,10 @@ import { registerUsageRoutes } from "./routes/usage";
|
|
|
28
28
|
import { registerAuthRoutes } from "./routes/auth";
|
|
29
29
|
import { sendUnauthorized, wantsHtml } from "../auth/http-auth";
|
|
30
30
|
import { buildPreviewRuntimeBridge, rewritePreviewImportMap, rewritePreviewJavaScriptImports } from "../previews/runtime-bridge";
|
|
31
|
+
import { forwardRuntimeRequest } from "../opencode/compatibility/proxy";
|
|
32
|
+
import { contractProfile, runtimeIdentity } from "../opencode/compatibility/runtime";
|
|
33
|
+
import { LOCATION_CONTEXT_HEADER, locationRequestOptions, readLocationContext } from "../opencode/compatibility/location";
|
|
34
|
+
import { decodeSessionListScope, prepareLocationImport, readRequestLocations } from "../opencode/compatibility/proxy-locations";
|
|
31
35
|
import { createOpenCodeUpdateService } from "../opencode-update/service";
|
|
32
36
|
import { WorktreeDeletionFence } from "../workspaces/worktree-session-evacuation";
|
|
33
37
|
import { isAutomationPluginRequest, registerAutomationPluginRoute } from "./routes/automation-plugin";
|
|
@@ -492,11 +496,21 @@ async function proxyWorkspaceRequest(args) {
|
|
|
492
496
|
reply.code(403).send({ error: "OpenCode route is not available through a workspace" });
|
|
493
497
|
return;
|
|
494
498
|
}
|
|
495
|
-
const
|
|
499
|
+
const connection = await workspaceManager.getSharedServiceConnection?.(workspaceId);
|
|
500
|
+
const endpoint = connection?.endpoint ?? await workspaceManager.getSharedServiceEndpoint(workspaceId);
|
|
501
|
+
const clientForRequest = () => connection ? Promise.resolve(connection.client) : workspaceManager.getSharedServiceClient();
|
|
496
502
|
if (!endpoint) {
|
|
497
503
|
reply.code(502).send({ error: "OpenCode service is not ready" });
|
|
498
504
|
return;
|
|
499
505
|
}
|
|
506
|
+
const profile = connection ? await connection.profile() : contractProfile(runtimeIdentity(endpoint));
|
|
507
|
+
let locationContext;
|
|
508
|
+
try {
|
|
509
|
+
locationContext = readLocationContext(request.headers[LOCATION_CONTEXT_HEADER], profile);
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
return reply.code(400).send({ error: "Invalid location context" });
|
|
513
|
+
}
|
|
500
514
|
const targetUrl = buildInstanceTargetUrl(endpoint.url, args.pathSuffix);
|
|
501
515
|
if (!targetUrl) {
|
|
502
516
|
reply.code(400).send({ error: "Invalid workspace instance path" });
|
|
@@ -508,22 +522,22 @@ async function proxyWorkspaceRequest(args) {
|
|
|
508
522
|
reply.code(405).send({ error: "Method not allowed" });
|
|
509
523
|
return;
|
|
510
524
|
}
|
|
511
|
-
const client = await
|
|
525
|
+
const client = await clientForRequest();
|
|
512
526
|
const active = await client.session.active();
|
|
513
527
|
const entries = await Promise.all(Object.entries(active).map(async ([sessionId, status]) => {
|
|
514
528
|
try {
|
|
515
529
|
const session = await client.session.get({ sessionID: sessionId });
|
|
516
|
-
return await workspaceManager.
|
|
530
|
+
return await workspaceManager.ownsLocation(workspaceId, session.location, client) ? [sessionId, status] : null;
|
|
517
531
|
}
|
|
518
532
|
catch {
|
|
519
533
|
return null;
|
|
520
534
|
}
|
|
521
535
|
}));
|
|
522
|
-
reply.send(Object.fromEntries(entries.filter((entry) => entry !== null)));
|
|
536
|
+
reply.send({ data: Object.fromEntries(entries.filter((entry) => entry !== null)) });
|
|
523
537
|
return;
|
|
524
538
|
}
|
|
525
539
|
if (pathname.replace(/\/+$/, "") === "/api/project") {
|
|
526
|
-
const projects = await (await
|
|
540
|
+
const projects = await (await clientForRequest()).project.list();
|
|
527
541
|
const ownedProjects = await Promise.all(projects.map(async (project) => {
|
|
528
542
|
if (!await workspaceManager.ownsDirectory(workspaceId, project.canonical))
|
|
529
543
|
return null;
|
|
@@ -535,8 +549,8 @@ async function proxyWorkspaceRequest(args) {
|
|
|
535
549
|
}
|
|
536
550
|
const sessionListHasScope = request.method === "GET"
|
|
537
551
|
&& pathname.replace(/\/+$/, "") === "/api/session"
|
|
538
|
-
&& (targetUrl.searchParams.has("cursor") || targetUrl.searchParams.has("project")
|
|
539
|
-
const sessionListScope = await authorizeSessionList(targetUrl, request.method, workspaceManager, workspaceId);
|
|
552
|
+
&& (targetUrl.searchParams.has("cursor") || targetUrl.searchParams.has("project"));
|
|
553
|
+
const sessionListScope = await authorizeSessionList(targetUrl, request.method, workspaceManager, workspaceId, profile, connection?.client);
|
|
540
554
|
if (sessionListScope !== "allowed") {
|
|
541
555
|
reply.code(sessionListScope === "invalid" ? 400 : 403).send({ error: "Session list does not belong to workspace" });
|
|
542
556
|
return;
|
|
@@ -545,8 +559,7 @@ async function proxyWorkspaceRequest(args) {
|
|
|
545
559
|
let globalFormLocation;
|
|
546
560
|
if (isGlobalFormAction(pathname, request.method)) {
|
|
547
561
|
const directoryHeader = request.headers["x-opencode-directory"];
|
|
548
|
-
|
|
549
|
-
if (Array.isArray(directoryHeader) || Array.isArray(workspaceHeader)) {
|
|
562
|
+
if (Array.isArray(directoryHeader) || request.headers["x-opencode-workspace"] !== undefined) {
|
|
550
563
|
reply.code(400).send({ error: "Invalid Form location" });
|
|
551
564
|
return;
|
|
552
565
|
}
|
|
@@ -564,19 +577,19 @@ async function proxyWorkspaceRequest(args) {
|
|
|
564
577
|
return;
|
|
565
578
|
}
|
|
566
579
|
}
|
|
567
|
-
if (
|
|
568
|
-
reply.code(400).send({ error: "
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
if (directory !== undefined || workspaceHeader !== undefined) {
|
|
572
|
-
globalFormLocation = {
|
|
573
|
-
directory: directory ?? workspace.path,
|
|
574
|
-
...(workspaceHeader ? { workspaceID: workspaceHeader } : {}),
|
|
575
|
-
};
|
|
580
|
+
if (locationContext && locationContext.directory !== (directory ?? serviceDirectory)) {
|
|
581
|
+
return reply.code(400).send({ error: "Conflicting Form location" });
|
|
576
582
|
}
|
|
583
|
+
if (directory !== undefined)
|
|
584
|
+
globalFormLocation = { ...locationContext, directory };
|
|
585
|
+
}
|
|
586
|
+
const imported = prepareSessionImport(pathname, request.method, stripLocationSelectors(targetUrl, request.body, workspace.path, serviceDirectory), serviceDirectory, profile);
|
|
587
|
+
const requestLocations = readRequestLocations(targetUrl, imported.body, workspace.path, profile);
|
|
588
|
+
const explicitLocations = [...requestLocations.locations];
|
|
589
|
+
if (locationContext) {
|
|
590
|
+
requestLocations.directories.push(locationContext.directory);
|
|
591
|
+
requestLocations.locations.push(locationContext);
|
|
577
592
|
}
|
|
578
|
-
const imported = prepareSessionImport(pathname, request.method, stripLocationSelectors(targetUrl, request.body, workspace.path, serviceDirectory), serviceDirectory);
|
|
579
|
-
const requestLocations = readRequestDirectories(targetUrl, imported.body, workspace.path);
|
|
580
593
|
if (globalFormLocation) {
|
|
581
594
|
requestLocations.directories.push(globalFormLocation.directory);
|
|
582
595
|
requestLocations.locations.push(globalFormLocation);
|
|
@@ -588,7 +601,7 @@ async function proxyWorkspaceRequest(args) {
|
|
|
588
601
|
const promptFiles = readPromptFilePaths(pathname, request.method, imported.body);
|
|
589
602
|
if (requestLocations.invalid
|
|
590
603
|
|| !(await allDirectoriesOwned(workspaceManager, workspaceId, requestLocations.directories))
|
|
591
|
-
|| !(await allLocationsOwned(workspaceManager, workspaceId, requestLocations.locations))) {
|
|
604
|
+
|| !(await allLocationsOwned(workspaceManager, workspaceId, requestLocations.locations, connection?.client))) {
|
|
592
605
|
reply.code(requestLocations.invalid ? 400 : 403).send({ error: "Location does not belong to workspace" });
|
|
593
606
|
return;
|
|
594
607
|
}
|
|
@@ -603,6 +616,29 @@ async function proxyWorkspaceRequest(args) {
|
|
|
603
616
|
}
|
|
604
617
|
translatedDirectories.set(directory, translated);
|
|
605
618
|
}
|
|
619
|
+
if (locationContext && explicitLocations.some(location => translatedDirectories.get(location.directory) !== translatedDirectories.get(locationContext.directory)
|
|
620
|
+
|| (location.workspaceID !== undefined && location.workspaceID !== locationContext.workspaceID))) {
|
|
621
|
+
return reply.code(400).send({ error: "Conflicting location context" });
|
|
622
|
+
}
|
|
623
|
+
const fileListPath = readFileListPath(targetUrl, request.method, requestLocations.directories[0] ?? workspace.path);
|
|
624
|
+
if (fileListPath.invalid) {
|
|
625
|
+
reply.code(400).send({ error: "Invalid filesystem path" });
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
if (fileListPath.candidate) {
|
|
629
|
+
if (!(await workspaceManager.ownsPath(workspaceId, fileListPath.candidate))) {
|
|
630
|
+
reply.code(403).send({ error: "Filesystem path does not belong to workspace" });
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const translated = workspaceManager.getServicePathForPath
|
|
634
|
+
? await workspaceManager.getServicePathForPath(workspaceId, fileListPath.candidate)
|
|
635
|
+
: fileListPath.candidate;
|
|
636
|
+
if (!translated) {
|
|
637
|
+
reply.code(403).send({ error: "Filesystem path does not belong to workspace" });
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
targetUrl.searchParams.set("path", translated);
|
|
641
|
+
}
|
|
606
642
|
const mutationIdentities = new Set();
|
|
607
643
|
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
608
644
|
for (const directory of requestLocations.directories) {
|
|
@@ -632,9 +668,13 @@ async function proxyWorkspaceRequest(args) {
|
|
|
632
668
|
}
|
|
633
669
|
const promptBody = replacePromptFileUris(serviceBody, translatedPromptPaths);
|
|
634
670
|
const requestedDirectory = requestLocations.directories[0];
|
|
635
|
-
const runtimeLocation = {
|
|
671
|
+
const runtimeLocation = {
|
|
672
|
+
directory: requestedDirectory ? translatedDirectories.get(requestedDirectory) ?? serviceDirectory : serviceDirectory,
|
|
673
|
+
workspaceID: locationContext?.workspaceID ?? explicitLocations.find(location => location.directory === requestedDirectory)?.workspaceID,
|
|
674
|
+
};
|
|
675
|
+
const runtimeLocationOptions = locationRequestOptions(runtimeLocation);
|
|
636
676
|
if (pathname.replace(/\/+$/, "") === "/api/pty" && request.method === "GET") {
|
|
637
|
-
const result = await (await
|
|
677
|
+
const result = await (await clientForRequest()).pty.list({ location: { directory: runtimeLocation.directory } }, runtimeLocationOptions);
|
|
638
678
|
const ownership = await Promise.all(result.data.map((pty) => workspaceManager.ownsDirectory(workspaceId, pty.cwd)));
|
|
639
679
|
reply.send({ ...result, data: result.data.filter((_, index) => ownership[index]) });
|
|
640
680
|
return;
|
|
@@ -642,7 +682,7 @@ async function proxyWorkspaceRequest(args) {
|
|
|
642
682
|
const ptyId = getPtyRouteId(pathname);
|
|
643
683
|
if (ptyId) {
|
|
644
684
|
try {
|
|
645
|
-
const pty = await (await
|
|
685
|
+
const pty = await (await clientForRequest()).pty.get({ ptyID: ptyId, location: { directory: runtimeLocation.directory } }, runtimeLocationOptions);
|
|
646
686
|
if (!(await workspaceManager.ownsDirectory(workspaceId, pty.data.cwd))) {
|
|
647
687
|
reply.code(403).send({ error: "PTY does not belong to workspace" });
|
|
648
688
|
return;
|
|
@@ -657,7 +697,7 @@ async function proxyWorkspaceRequest(args) {
|
|
|
657
697
|
}
|
|
658
698
|
}
|
|
659
699
|
if (pathname.replace(/\/+$/, "") === "/api/shell" && request.method === "GET") {
|
|
660
|
-
const result = await (await
|
|
700
|
+
const result = await (await clientForRequest()).shell.list({ location: { directory: runtimeLocation.directory } }, runtimeLocationOptions);
|
|
661
701
|
const ownership = await Promise.all(result.data.map((shell) => workspaceManager.ownsDirectory(workspaceId, shell.cwd)));
|
|
662
702
|
reply.send({ ...result, data: result.data.filter((_, index) => ownership[index]) });
|
|
663
703
|
return;
|
|
@@ -665,7 +705,7 @@ async function proxyWorkspaceRequest(args) {
|
|
|
665
705
|
const shellId = getShellRouteId(pathname);
|
|
666
706
|
if (shellId) {
|
|
667
707
|
try {
|
|
668
|
-
const shell = await (await
|
|
708
|
+
const shell = await (await clientForRequest()).shell.get({ id: shellId, location: { directory: runtimeLocation.directory } }, runtimeLocationOptions);
|
|
669
709
|
if (!(await workspaceManager.ownsDirectory(workspaceId, shell.data.cwd))) {
|
|
670
710
|
reply.code(403).send({ error: "Shell does not belong to workspace" });
|
|
671
711
|
return;
|
|
@@ -683,7 +723,7 @@ async function proxyWorkspaceRequest(args) {
|
|
|
683
723
|
if (sessionId && !isGlobalFormAction(pathname, request.method)) {
|
|
684
724
|
let session;
|
|
685
725
|
try {
|
|
686
|
-
session = await (await
|
|
726
|
+
session = await (await clientForRequest()).session.get({ sessionID: sessionId });
|
|
687
727
|
}
|
|
688
728
|
catch (error) {
|
|
689
729
|
if (isInvalidRequestError(error)) {
|
|
@@ -696,7 +736,7 @@ async function proxyWorkspaceRequest(args) {
|
|
|
696
736
|
}
|
|
697
737
|
throw error;
|
|
698
738
|
}
|
|
699
|
-
if (!(await workspaceManager.
|
|
739
|
+
if (!(await workspaceManager.ownsLocation(workspaceId, session.location, await clientForRequest()))) {
|
|
700
740
|
reply.code(403).send({ error: "Session does not belong to workspace" });
|
|
701
741
|
return;
|
|
702
742
|
}
|
|
@@ -718,14 +758,26 @@ async function proxyWorkspaceRequest(args) {
|
|
|
718
758
|
}
|
|
719
759
|
logger.debug({ workspaceId, method: request.method, targetUrl: targetUrl.toString() }, "Proxying request to instance");
|
|
720
760
|
try {
|
|
761
|
+
connection?.assertCurrent();
|
|
762
|
+
if (connection || workspaceManager.getSharedServiceFetch) {
|
|
763
|
+
const headers = sanitizeInstanceProxyRequestHeaders(request.headers, instanceAuthHeader);
|
|
764
|
+
delete headers[LOCATION_CONTEXT_HEADER];
|
|
765
|
+
if (locationContext)
|
|
766
|
+
Object.assign(headers, locationRequestOptions({ ...locationContext, directory: translatedDirectories.get(locationContext.directory) }, { includeDirectory: true })?.headers);
|
|
767
|
+
if (globalFormLocation)
|
|
768
|
+
headers["x-opencode-directory"] = encodeURIComponent(translatedDirectories.get(globalFormLocation.directory));
|
|
769
|
+
return await forwardRuntimeRequest({
|
|
770
|
+
request, reply, url: targetUrl.toString(), body, headers,
|
|
771
|
+
fetch: connection?.fetch ?? await workspaceManager.getSharedServiceFetch(), release: releaseMutation,
|
|
772
|
+
invalidate: () => connection ? connection.invalidate() : workspaceManager.invalidateSharedServiceConnection?.(),
|
|
773
|
+
});
|
|
774
|
+
}
|
|
721
775
|
return reply.from(targetUrl.toString(), {
|
|
722
776
|
...(body !== request.body ? { body } : {}),
|
|
723
777
|
rewriteRequestHeaders: (_originalRequest, headers) => {
|
|
724
778
|
const outgoingHeaders = sanitizeInstanceProxyRequestHeaders(headers, instanceAuthHeader);
|
|
725
779
|
if (globalFormLocation) {
|
|
726
780
|
outgoingHeaders["x-opencode-directory"] = encodeURIComponent(translatedDirectories.get(globalFormLocation.directory));
|
|
727
|
-
if (globalFormLocation.workspaceID)
|
|
728
|
-
outgoingHeaders["x-opencode-workspace"] = globalFormLocation.workspaceID;
|
|
729
781
|
}
|
|
730
782
|
if (logger.isLevelEnabled("trace")) {
|
|
731
783
|
logger.trace({
|
|
@@ -776,77 +828,6 @@ function appendIncomingQuery(targetUrl, incomingUrl) {
|
|
|
776
828
|
targetUrl.searchParams.append(key, value);
|
|
777
829
|
return targetUrl;
|
|
778
830
|
}
|
|
779
|
-
function readRequestDirectories(targetUrl, body, defaultDirectory) {
|
|
780
|
-
const directories = [];
|
|
781
|
-
const locations = [];
|
|
782
|
-
let invalid = false;
|
|
783
|
-
for (const key of ["location[directory]", "directory"]) {
|
|
784
|
-
for (const value of targetUrl.searchParams.getAll(key)) {
|
|
785
|
-
if (value.trim())
|
|
786
|
-
directories.push(value);
|
|
787
|
-
else
|
|
788
|
-
invalid = true;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
const queryWorkspaces = targetUrl.searchParams.getAll("location[workspace]");
|
|
792
|
-
const queryDirectories = targetUrl.searchParams.getAll("location[directory]");
|
|
793
|
-
if (queryWorkspaces.length > 1 || queryDirectories.length > 1)
|
|
794
|
-
invalid = true;
|
|
795
|
-
if (queryWorkspaces.length === 1) {
|
|
796
|
-
const workspaceID = queryWorkspaces[0];
|
|
797
|
-
if (!workspaceID.trim())
|
|
798
|
-
invalid = true;
|
|
799
|
-
else
|
|
800
|
-
locations.push({ directory: queryDirectories[0] ?? defaultDirectory, workspaceID });
|
|
801
|
-
}
|
|
802
|
-
if (targetUrl.pathname.replace(/\/+$/, "") !== "/api/session" && targetUrl.searchParams.has("workspace"))
|
|
803
|
-
invalid = true;
|
|
804
|
-
if (targetUrl.searchParams.has("workspaceID") || targetUrl.searchParams.has("location[workspaceID]"))
|
|
805
|
-
invalid = true;
|
|
806
|
-
if (body && typeof body === "object" && !Array.isArray(body) && !Buffer.isBuffer(body)) {
|
|
807
|
-
const input = body;
|
|
808
|
-
if ("directory" in input) {
|
|
809
|
-
if (typeof input.directory === "string" && input.directory.trim())
|
|
810
|
-
directories.push(input.directory);
|
|
811
|
-
else
|
|
812
|
-
invalid = true;
|
|
813
|
-
}
|
|
814
|
-
if ("location" in input) {
|
|
815
|
-
const location = input.location;
|
|
816
|
-
if (location && typeof location === "object" && !Array.isArray(location)) {
|
|
817
|
-
const source = location;
|
|
818
|
-
const directory = source.directory;
|
|
819
|
-
if (typeof directory === "string" && directory.trim())
|
|
820
|
-
directories.push(directory);
|
|
821
|
-
else
|
|
822
|
-
invalid = true;
|
|
823
|
-
if (source.workspace !== undefined)
|
|
824
|
-
invalid = true;
|
|
825
|
-
if (source.workspaceID !== undefined) {
|
|
826
|
-
if (typeof source.workspaceID === "string" && source.workspaceID.trim() && typeof directory === "string" && directory.trim()) {
|
|
827
|
-
locations.push({ directory, workspaceID: source.workspaceID });
|
|
828
|
-
}
|
|
829
|
-
else
|
|
830
|
-
invalid = true;
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
else if (location !== null && location !== undefined) {
|
|
834
|
-
invalid = true;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
if (/^\/api\/session\/[^/]+\/move\/?$/.test(targetUrl.pathname) && "workspaceID" in input) {
|
|
838
|
-
if (typeof input.workspaceID === "string" && input.workspaceID.trim() && typeof input.directory === "string" && input.directory.trim()) {
|
|
839
|
-
locations.push({ directory: input.directory, workspaceID: input.workspaceID });
|
|
840
|
-
}
|
|
841
|
-
else
|
|
842
|
-
invalid = true;
|
|
843
|
-
}
|
|
844
|
-
else if ("workspaceID" in input) {
|
|
845
|
-
invalid = true;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
return { directories, locations, invalid };
|
|
849
|
-
}
|
|
850
831
|
function readNativeCwd(targetUrl, body, locations) {
|
|
851
832
|
if (!/^\/api\/(?:shell|pty)\/?$/.test(targetUrl.pathname) || !body || typeof body !== "object" || Array.isArray(body) || Buffer.isBuffer(body))
|
|
852
833
|
return;
|
|
@@ -858,11 +839,33 @@ function readNativeCwd(targetUrl, body, locations) {
|
|
|
858
839
|
else
|
|
859
840
|
locations.invalid = true;
|
|
860
841
|
}
|
|
842
|
+
function readFileListPath(targetUrl, method, defaultDirectory) {
|
|
843
|
+
if (method !== "GET" || targetUrl.pathname.replace(/\/+$/, "") !== "/api/fs/list") {
|
|
844
|
+
return { invalid: false };
|
|
845
|
+
}
|
|
846
|
+
const values = targetUrl.searchParams.getAll("path");
|
|
847
|
+
if (values.length > 1)
|
|
848
|
+
return { invalid: true };
|
|
849
|
+
const requested = values[0]?.trim() || ".";
|
|
850
|
+
if (requested.includes("\0"))
|
|
851
|
+
return { invalid: true };
|
|
852
|
+
const windowsBase = /^[A-Za-z]:[\\/]/.test(defaultDirectory) || /^[\\/]{2}[^\\/]/.test(defaultDirectory);
|
|
853
|
+
if (!windowsBase && path.win32.isAbsolute(requested) && !path.posix.isAbsolute(requested)) {
|
|
854
|
+
return { candidate: path.win32.normalize(requested), invalid: false };
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
candidate: windowsBase
|
|
858
|
+
? path.win32.resolve(defaultDirectory, requested)
|
|
859
|
+
: path.posix.resolve(defaultDirectory, requested),
|
|
860
|
+
invalid: false,
|
|
861
|
+
};
|
|
862
|
+
}
|
|
861
863
|
function sanitizeInstanceProxyRequestHeaders(headers, authorization) {
|
|
862
864
|
const blocked = new Set([
|
|
863
865
|
"authorization", "connection", "cookie", "forwarded", "host", "keep-alive", "proxy-authenticate",
|
|
864
866
|
"proxy-authorization", "proxy-connection", "set-cookie", "te", "trailer", "transfer-encoding", "upgrade",
|
|
865
867
|
"x-forwarded-for", "x-forwarded-host", "x-forwarded-port", "x-forwarded-proto",
|
|
868
|
+
LOCATION_CONTEXT_HEADER,
|
|
866
869
|
]);
|
|
867
870
|
const connection = headers.connection;
|
|
868
871
|
for (const name of (Array.isArray(connection) ? connection.join(",") : connection ?? "").split(","))
|
|
@@ -900,9 +903,9 @@ export function redactSecrets(value) {
|
|
|
900
903
|
async function allDirectoriesOwned(manager, workspaceId, directories) {
|
|
901
904
|
return (await Promise.all(directories.map((directory) => manager.ownsDirectory(workspaceId, directory)))).every(Boolean);
|
|
902
905
|
}
|
|
903
|
-
async function allLocationsOwned(manager, workspaceId, locations) {
|
|
904
|
-
const unique = new Map(locations.map((location) => [
|
|
905
|
-
return (await Promise.all([...unique.values()].map((location) => manager.ownsLocation(workspaceId, location)))).every(Boolean);
|
|
906
|
+
async function allLocationsOwned(manager, workspaceId, locations, client) {
|
|
907
|
+
const unique = new Map(locations.map((location) => [JSON.stringify([location.directory, location.workspaceID]), location]));
|
|
908
|
+
return (await Promise.all([...unique.values()].map((location) => manager.ownsLocation(workspaceId, location, client)))).every(Boolean);
|
|
906
909
|
}
|
|
907
910
|
async function allPathsOwned(manager, workspaceId, paths) {
|
|
908
911
|
return (await Promise.all(paths.map((candidate) => manager.ownsPath(workspaceId, candidate)))).every(Boolean);
|
|
@@ -910,6 +913,9 @@ async function allPathsOwned(manager, workspaceId, paths) {
|
|
|
910
913
|
function applyDefaultWorkspaceLocation(targetUrl, body, method, directory, hasLocation, sessionRoute) {
|
|
911
914
|
if (hasLocation || sessionRoute)
|
|
912
915
|
return body;
|
|
916
|
+
if (/^\/api\/(?:credential|project)(?:\/|$)/.test(targetUrl.pathname)
|
|
917
|
+
|| /^\/api\/permission\/saved(?:\/|$)/.test(targetUrl.pathname))
|
|
918
|
+
return body;
|
|
913
919
|
if (targetUrl.pathname === "/api/session" && method === "GET") {
|
|
914
920
|
targetUrl.searchParams.set("directory", directory);
|
|
915
921
|
return body;
|
|
@@ -930,99 +936,53 @@ function getSessionRouteId(pathname) {
|
|
|
930
936
|
return match[1];
|
|
931
937
|
}
|
|
932
938
|
function isGlobalFormAction(pathname, method) {
|
|
933
|
-
return method === "POST" && /^\/api\/session\/global\/form\/[^/]+\/
|
|
939
|
+
return (method === "POST" && /^\/api\/session\/global\/form\/[^/]+\/reply\/?$/.test(pathname))
|
|
940
|
+
|| (method === "DELETE" && /^\/api\/session\/global\/form\/[^/]+\/?$/.test(pathname));
|
|
934
941
|
}
|
|
935
|
-
async function authorizeSessionList(targetUrl, method, manager, workspaceId) {
|
|
942
|
+
async function authorizeSessionList(targetUrl, method, manager, workspaceId, profile, client) {
|
|
936
943
|
if (method !== "GET" || targetUrl.pathname.replace(/\/+$/, "") !== "/api/session")
|
|
937
944
|
return "allowed";
|
|
938
945
|
const cursors = targetUrl.searchParams.getAll("cursor");
|
|
939
946
|
if (cursors.length > 1)
|
|
940
947
|
return "invalid";
|
|
941
948
|
if (cursors.length === 1) {
|
|
942
|
-
const scope =
|
|
949
|
+
const scope = decodeSessionListScope(cursors[0], profile);
|
|
943
950
|
if (!scope)
|
|
944
951
|
return "invalid";
|
|
945
|
-
for (const key of ["directory", "
|
|
952
|
+
for (const key of ["directory", "location[directory]", "project", "subpath", ...(profile === "legacy" ? ["workspace", "location[workspace]"] : [])]) {
|
|
946
953
|
targetUrl.searchParams.delete(key);
|
|
947
954
|
}
|
|
948
|
-
return ownsSessionListScope(manager, workspaceId, scope);
|
|
955
|
+
return ownsSessionListScope(manager, workspaceId, scope, client);
|
|
949
956
|
}
|
|
950
957
|
const projects = targetUrl.searchParams.getAll("project");
|
|
951
958
|
const subpaths = targetUrl.searchParams.getAll("subpath");
|
|
952
|
-
const workspaces = targetUrl.searchParams.getAll("workspace");
|
|
953
959
|
const directories = targetUrl.searchParams.getAll("directory");
|
|
954
|
-
if (projects.length > 1 || subpaths.length > 1 ||
|
|
960
|
+
if (projects.length > 1 || subpaths.length > 1 || directories.length > 1
|
|
955
961
|
|| (subpaths.length && !projects.length))
|
|
956
962
|
return "invalid";
|
|
957
|
-
const workspace = workspaces[0];
|
|
958
963
|
const directory = directories[0];
|
|
959
|
-
if (
|
|
964
|
+
if (directory !== undefined && !directory.trim())
|
|
960
965
|
return "invalid";
|
|
961
966
|
if (!projects.length) {
|
|
962
|
-
if (workspace && directory)
|
|
963
|
-
return ownsSessionListScope(manager, workspaceId, { workspace, directory });
|
|
964
|
-
if (workspace)
|
|
965
|
-
return ownsSessionListScope(manager, workspaceId, { workspace });
|
|
966
967
|
return "allowed";
|
|
967
968
|
}
|
|
968
969
|
const project = projects[0];
|
|
969
970
|
const subpath = subpaths[0];
|
|
970
971
|
if (!project || (subpath !== undefined && !isSafeRelativePath(subpath)))
|
|
971
972
|
return "invalid";
|
|
972
|
-
return ownsSessionListScope(manager, workspaceId, {
|
|
973
|
-
}
|
|
974
|
-
function decodeSessionListCursor(cursor) {
|
|
975
|
-
if (!cursor || !/^[A-Za-z0-9_-]+$/.test(cursor))
|
|
976
|
-
return null;
|
|
977
|
-
try {
|
|
978
|
-
const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
979
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
980
|
-
return null;
|
|
981
|
-
const anchor = value.anchor;
|
|
982
|
-
if (!anchor || typeof anchor !== "object" || Array.isArray(anchor)
|
|
983
|
-
|| typeof anchor.id !== "string" || !anchor.id
|
|
984
|
-
|| typeof anchor.time !== "number" || !Number.isFinite(anchor.time)
|
|
985
|
-
|| (anchor.direction !== "previous" && anchor.direction !== "next"))
|
|
986
|
-
return null;
|
|
987
|
-
if (value.workspace !== undefined && (typeof value.workspace !== "string" || !value.workspace.trim()))
|
|
988
|
-
return null;
|
|
989
|
-
if (value.search !== undefined && typeof value.search !== "string")
|
|
990
|
-
return null;
|
|
991
|
-
if (value.order !== undefined && value.order !== "asc" && value.order !== "desc")
|
|
992
|
-
return null;
|
|
993
|
-
if (typeof value.directory === "string" && value.directory.trim() && value.project === undefined && value.subpath === undefined) {
|
|
994
|
-
return { ...(typeof value.workspace === "string" ? { workspace: value.workspace } : {}), directory: value.directory };
|
|
995
|
-
}
|
|
996
|
-
if (typeof value.project === "string" && value.project.trim() && value.directory === undefined) {
|
|
997
|
-
if (value.subpath === undefined) {
|
|
998
|
-
return { ...(typeof value.workspace === "string" ? { workspace: value.workspace } : {}), project: value.project };
|
|
999
|
-
}
|
|
1000
|
-
if (typeof value.subpath === "string" && isSafeRelativePath(value.subpath)) {
|
|
1001
|
-
return { ...(typeof value.workspace === "string" ? { workspace: value.workspace } : {}), project: value.project, subpath: value.subpath };
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
if (typeof value.workspace === "string" && value.directory === undefined && value.project === undefined && value.subpath === undefined) {
|
|
1005
|
-
return { workspace: value.workspace };
|
|
1006
|
-
}
|
|
1007
|
-
return null;
|
|
1008
|
-
}
|
|
1009
|
-
catch {
|
|
1010
|
-
return null;
|
|
1011
|
-
}
|
|
973
|
+
return ownsSessionListScope(manager, workspaceId, { project, subpath, workspaceID: targetUrl.searchParams.get("workspace") ?? undefined }, client);
|
|
1012
974
|
}
|
|
1013
975
|
function isSafeRelativePath(value) {
|
|
1014
976
|
return value === "" || (!path.posix.isAbsolute(value) && !path.win32.isAbsolute(value) && !value.split(/[\\/]/).includes(".."));
|
|
1015
977
|
}
|
|
1016
|
-
async function ownsSessionListScope(manager, workspaceId, scope) {
|
|
978
|
+
async function ownsSessionListScope(manager, workspaceId, scope, client) {
|
|
1017
979
|
if (scope.directory) {
|
|
1018
|
-
const owned = scope.
|
|
1019
|
-
? await manager.ownsLocation(workspaceId, { directory: scope.directory, workspaceID: scope.workspace })
|
|
1020
|
-
: await manager.ownsDirectory(workspaceId, scope.directory);
|
|
980
|
+
const owned = await manager.ownsLocation(workspaceId, { directory: scope.directory, workspaceID: scope.workspaceID }, client);
|
|
1021
981
|
return owned ? "allowed" : "foreign";
|
|
1022
982
|
}
|
|
1023
983
|
if (!scope.project)
|
|
1024
|
-
return
|
|
1025
|
-
const project = (await (await manager.getSharedServiceClient()).project.list()).find((candidate) => candidate.id === scope.project);
|
|
984
|
+
return "foreign";
|
|
985
|
+
const project = (await (client ?? await manager.getSharedServiceClient()).project.list()).find((candidate) => candidate.id === scope.project);
|
|
1026
986
|
if (!project)
|
|
1027
987
|
return "foreign";
|
|
1028
988
|
const directory = scope.subpath === undefined
|
|
@@ -1030,16 +990,14 @@ async function ownsSessionListScope(manager, workspaceId, scope) {
|
|
|
1030
990
|
: /^[A-Za-z]:[\\/]|^\\\\/.test(project.canonical)
|
|
1031
991
|
? path.win32.resolve(project.canonical, scope.subpath)
|
|
1032
992
|
: path.posix.resolve(project.canonical, scope.subpath);
|
|
1033
|
-
const owned = scope.
|
|
1034
|
-
? await manager.ownsLocation(workspaceId, { directory, workspaceID: scope.workspace })
|
|
1035
|
-
: await manager.ownsDirectory(workspaceId, directory);
|
|
993
|
+
const owned = await manager.ownsLocation(workspaceId, { directory, workspaceID: scope.workspaceID }, client);
|
|
1036
994
|
return owned ? "allowed" : "foreign";
|
|
1037
995
|
}
|
|
1038
996
|
function getPtyRouteId(pathname) {
|
|
1039
997
|
return pathname.replace(/\/+$/, "").match(/^\/api\/pty\/([^/]+)$/)?.[1] ?? null;
|
|
1040
998
|
}
|
|
1041
999
|
function getShellRouteId(pathname) {
|
|
1042
|
-
return pathname.replace(/\/+$/, "").match(/^\/api\/shell\/([^/]+)(?:\/output
|
|
1000
|
+
return pathname.replace(/\/+$/, "").match(/^\/api\/shell\/([^/]+)(?:\/output)?$/)?.[1] ?? null;
|
|
1043
1001
|
}
|
|
1044
1002
|
function buildInstanceTargetUrl(endpoint, pathSuffix) {
|
|
1045
1003
|
const suffix = pathSuffix ?? "";
|
|
@@ -1078,39 +1036,42 @@ function isAllowedInstanceApiRoute(method, pathname) {
|
|
|
1078
1036
|
["GET", /^\/api\/(?:mcp\/resource|websearch\/provider)$/],
|
|
1079
1037
|
["GET", /^\/api\/agent\/[^/]+$/],
|
|
1080
1038
|
["GET", /^\/api\/model\/default$/],
|
|
1081
|
-
["GET", /^\/api\/
|
|
1082
|
-
["GET", /^\/api\/form
|
|
1083
|
-
["GET", /^\/api\/project\/current$/],
|
|
1039
|
+
["GET", /^\/api\/permission\/request$/],
|
|
1040
|
+
["GET", /^\/api\/form$/],
|
|
1084
1041
|
["GET", /^\/api\/project$/],
|
|
1085
1042
|
["GET", /^\/api\/vcs(?:\/status)?$/],
|
|
1086
1043
|
["GET", /^\/api\/fs\/(?:list|read\/.+)$/],
|
|
1087
|
-
["GET", /^\/api\/
|
|
1044
|
+
["GET", /^\/api\/pty(?:\/[^/]+)?$/],
|
|
1045
|
+
["GET", /^\/api\/shell(?:\/[^/]+(?:\/output)?)?$/],
|
|
1088
1046
|
["POST", /^\/api\/(?:pty|shell)$/],
|
|
1089
1047
|
["PUT", /^\/api\/pty\/[^/]+$/],
|
|
1090
1048
|
["DELETE", /^\/api\/pty\/[^/]+$/],
|
|
1091
1049
|
["DELETE", /^\/api\/shell\/[^/]+$/],
|
|
1092
|
-
["
|
|
1093
|
-
["POST", /^\/api\/mcp\/[^/]+\/(?:connect|disconnect)$/],
|
|
1050
|
+
["POST", /^\/api\/experimental\/mcp\/[^/]+\/(?:connect|disconnect)$/],
|
|
1094
1051
|
["DELETE", /^\/api\/credential\/[^/]+$/],
|
|
1095
1052
|
["POST", /^\/api\/integration\/[^/]+\/connect\/(?:key|oauth|command)$/],
|
|
1096
1053
|
["GET", /^\/api\/integration\/[^/]+\/connect\/(?:oauth|command)\/[^/]+$/],
|
|
1097
1054
|
["DELETE", /^\/api\/integration\/[^/]+\/connect\/(?:oauth|command)\/[^/]+$/],
|
|
1098
1055
|
["POST", /^\/api\/integration\/[^/]+\/connect\/oauth\/[^/]+\/complete$/],
|
|
1099
1056
|
["GET", /^\/api\/session(?:\/active)?$/],
|
|
1100
|
-
["POST", /^\/api\/session
|
|
1057
|
+
["POST", /^\/api\/session$/],
|
|
1058
|
+
["POST", /^\/api\/experimental\/session\/import$/],
|
|
1101
1059
|
["GET", /^\/api\/session\/[^/]+(?:\/message(?:\/[^/]+)?)?$/],
|
|
1102
1060
|
["GET", /^\/api\/session\/[^/]+\/inbox$/],
|
|
1103
1061
|
["GET", /^\/api\/session\/[^/]+\/(?:permission|form)$/],
|
|
1104
1062
|
["DELETE", /^\/api\/session\/[^/]+$/],
|
|
1105
1063
|
["DELETE", /^\/api\/session\/[^/]+\/inbox\/[^/]+$/],
|
|
1106
|
-
["
|
|
1107
|
-
["POST", /^\/api\/session\/[^/]+\/
|
|
1108
|
-
["POST", /^\/api\/session\/[^/]+\/
|
|
1109
|
-
["
|
|
1110
|
-
["
|
|
1064
|
+
["PATCH", /^\/api\/session\/[^/]+$/],
|
|
1065
|
+
["POST", /^\/api\/session\/[^/]+\/(?:agent|model|move|prompt|command|shell|compact|interrupt|background|fork)$/],
|
|
1066
|
+
["POST", /^\/api\/experimental\/session\/[^/]+\/wait$/],
|
|
1067
|
+
["PATCH", /^\/api\/session\/[^/]+\/inbox\/[^/]+$/],
|
|
1068
|
+
["POST", /^\/api\/session\/[^/]+\/revert\/stage$/],
|
|
1069
|
+
["DELETE", /^\/api\/session\/[^/]+\/revert$/],
|
|
1070
|
+
["PUT", /^\/api\/experimental\/session\/[^/]+\/instructions\/entries\/[^/]+$/],
|
|
1071
|
+
["DELETE", /^\/api\/experimental\/session\/[^/]+\/instructions\/entries\/[^/]+$/],
|
|
1111
1072
|
["POST", /^\/api\/session\/[^/]+\/permission\/[^/]+\/reply$/],
|
|
1112
|
-
["POST", /^\/api\/session\/[^/]+\/
|
|
1113
|
-
["
|
|
1073
|
+
["POST", /^\/api\/session\/[^/]+\/form\/[^/]+\/reply$/],
|
|
1074
|
+
["DELETE", /^\/api\/session\/[^/]+\/form\/[^/]+$/],
|
|
1114
1075
|
["GET", /^\/api\/experimental\/session\/[^/]+\/log$/],
|
|
1115
1076
|
];
|
|
1116
1077
|
return allowed.some(([allowedMethod, pattern]) => method === allowedMethod && pattern.test(route));
|
|
@@ -1141,7 +1102,7 @@ function replaceRequestDirectories(targetUrl, body, replacements, pathname, meth
|
|
|
1141
1102
|
input[key] = replacements.get(value);
|
|
1142
1103
|
}
|
|
1143
1104
|
input.location = replaceLocation(input.location);
|
|
1144
|
-
if (pathname !== "/api/session/import" || method !== "POST")
|
|
1105
|
+
if (pathname !== "/api/experimental/session/import" || method !== "POST")
|
|
1145
1106
|
return input;
|
|
1146
1107
|
input.info = input.info && typeof input.info === "object" && !Array.isArray(input.info) && !Buffer.isBuffer(input.info)
|
|
1147
1108
|
? { ...input.info, location: replaceLocation(input.info.location) }
|
|
@@ -1267,62 +1228,11 @@ function replacePromptFileUris(body, replacements) {
|
|
|
1267
1228
|
}),
|
|
1268
1229
|
};
|
|
1269
1230
|
}
|
|
1270
|
-
function prepareSessionImport(pathname, method, body, directory) {
|
|
1231
|
+
function prepareSessionImport(pathname, method, body, directory, profile) {
|
|
1271
1232
|
const result = { body, directories: [], locations: [], invalid: false };
|
|
1272
|
-
if (pathname !== "/api/session/import" || method !== "POST")
|
|
1273
|
-
return result;
|
|
1274
|
-
if (!body || typeof body !== "object" || Array.isArray(body) || Buffer.isBuffer(body)) {
|
|
1275
|
-
result.invalid = true;
|
|
1233
|
+
if (pathname !== "/api/experimental/session/import" || method !== "POST")
|
|
1276
1234
|
return result;
|
|
1277
|
-
|
|
1278
|
-
const input = body;
|
|
1279
|
-
const addLocation = (owner, key) => {
|
|
1280
|
-
const value = owner[key];
|
|
1281
|
-
if (value === null || value === undefined) {
|
|
1282
|
-
owner[key] = { directory };
|
|
1283
|
-
result.directories.push(directory);
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || Buffer.isBuffer(value)) {
|
|
1287
|
-
result.invalid = true;
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1290
|
-
const location = value;
|
|
1291
|
-
if (location.directory === null || location.directory === undefined)
|
|
1292
|
-
location.directory = directory;
|
|
1293
|
-
if (typeof location.directory !== "string" || !location.directory.trim()) {
|
|
1294
|
-
result.invalid = true;
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
result.directories.push(location.directory);
|
|
1298
|
-
if (location.workspace !== undefined)
|
|
1299
|
-
result.invalid = true;
|
|
1300
|
-
if (location.workspaceID !== undefined) {
|
|
1301
|
-
if (typeof location.workspaceID === "string" && location.workspaceID.trim()) {
|
|
1302
|
-
result.locations.push({ directory: location.directory, workspaceID: location.workspaceID });
|
|
1303
|
-
}
|
|
1304
|
-
else
|
|
1305
|
-
result.invalid = true;
|
|
1306
|
-
}
|
|
1307
|
-
};
|
|
1308
|
-
addLocation(input, "location");
|
|
1309
|
-
if (input.info && typeof input.info === "object" && !Array.isArray(input.info) && !Buffer.isBuffer(input.info)) {
|
|
1310
|
-
addLocation(input.info, "location");
|
|
1311
|
-
}
|
|
1312
|
-
if (Array.isArray(input.messages)) {
|
|
1313
|
-
for (const value of input.messages) {
|
|
1314
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || Buffer.isBuffer(value))
|
|
1315
|
-
continue;
|
|
1316
|
-
const message = value;
|
|
1317
|
-
if (message.type !== "location-switched")
|
|
1318
|
-
continue;
|
|
1319
|
-
addLocation(message, "location");
|
|
1320
|
-
if (message.previous && typeof message.previous === "object" && !Array.isArray(message.previous) && !Buffer.isBuffer(message.previous)) {
|
|
1321
|
-
addLocation(message.previous, "location");
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
return result;
|
|
1235
|
+
return prepareLocationImport(body, directory, profile);
|
|
1326
1236
|
}
|
|
1327
1237
|
function normalizeInstanceSuffix(pathSuffix) {
|
|
1328
1238
|
if (!pathSuffix || pathSuffix === "/") {
|