@cjhyy/code-shell-server 0.9.26 → 0.9.28
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/links/browser-handoff.d.ts +16 -0
- package/dist/links/browser-handoff.js +150 -0
- package/dist/links/http.d.ts +8 -2
- package/dist/links/http.js +63 -4
- package/dist/links/remote-catalog.d.ts +94 -0
- package/dist/links/remote-catalog.js +90 -0
- package/dist/links/service.d.ts +8 -1
- package/dist/links/service.js +558 -76
- package/dist/links/types.d.ts +1 -1
- package/dist/project-runtime/proxy.js +60 -4
- package/dist/serve/headless-server.js +28 -2
- package/package.json +6 -6
package/dist/links/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { LinkAuthorization, LinkConnectionInput, LinkErrorCode, LinkSnapshot, MaskedLinkConnection, TokenConnectionInput, } from "@cjhyy/code-shell-link";
|
|
1
|
+
export type { LinkAuthorization, LinkAuthMode, LinkAuthorizationInput, LinkAuthorizationResponse, LinkAuthorizationStep, LinkConnectionInput, LinkErrorCode, LinkSnapshot, MaskedLinkConnection, TokenConnectionInput, } from "@cjhyy/code-shell-link";
|
|
2
2
|
export interface LinkOperationContext {
|
|
3
3
|
ownerId: string;
|
|
4
4
|
/** Rechecked after every external await and immediately before persistence. */
|
|
@@ -2,6 +2,7 @@ import { request as httpRequest } from "node:http";
|
|
|
2
2
|
import { Transform } from "node:stream";
|
|
3
3
|
import { WebSocket, WebSocketServer } from "ws";
|
|
4
4
|
import { HUB_SESSION_COOKIE } from "../hub/auth-http.js";
|
|
5
|
+
import { createLinkBrowserHandoff } from "../links/browser-handoff.js";
|
|
5
6
|
const UUID = "[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}";
|
|
6
7
|
const PROJECT_PATH = new RegExp(`^/p/(${UUID})(/.*)$`);
|
|
7
8
|
const API_PATH = /^\/api\/v1\/(?:configuration|mcp|skills|links|panels|panel-assets|files|sessions|uploads)(?:\/|$)/;
|
|
@@ -53,6 +54,7 @@ class ProxyError extends Error {
|
|
|
53
54
|
}
|
|
54
55
|
/** Private runtime credentials and cookies never cross this control-plane boundary. */
|
|
55
56
|
export function createProjectRuntimeProxy(options) {
|
|
57
|
+
const linkBrowser = createLinkBrowserHandoff();
|
|
56
58
|
const leases = new Map();
|
|
57
59
|
const assets = new Map();
|
|
58
60
|
const revokedOwners = new Set();
|
|
@@ -249,10 +251,10 @@ export function createProjectRuntimeProxy(options) {
|
|
|
249
251
|
}
|
|
250
252
|
return result;
|
|
251
253
|
}
|
|
252
|
-
async function innerJson(target, path, body, cookie, timeout = 10_000) {
|
|
254
|
+
async function innerJson(target, path, body, cookie, timeout = 10_000, method = "POST") {
|
|
253
255
|
return await new Promise((resolve, reject) => {
|
|
254
256
|
const request = httpRequest(new URL(path, target.url), {
|
|
255
|
-
method
|
|
257
|
+
method,
|
|
256
258
|
headers: {
|
|
257
259
|
origin: options.publicOrigin(),
|
|
258
260
|
"content-type": "application/json",
|
|
@@ -338,12 +340,24 @@ export function createProjectRuntimeProxy(options) {
|
|
|
338
340
|
const renew = req.method === "POST" &&
|
|
339
341
|
incoming.statusCode === 200 &&
|
|
340
342
|
/^\/api\/v1\/panels\/runtime\/([A-Za-z0-9_-]+)\/renew$/.exec(route.pathname);
|
|
341
|
-
const
|
|
343
|
+
const linkCatalog = req.method === "GET" &&
|
|
344
|
+
route.pathname === "/api/v1/links" &&
|
|
345
|
+
incoming.statusCode === 200;
|
|
346
|
+
let bytes = prepare || renew || linkCatalog
|
|
347
|
+
? await readBounded(incoming, MAX_PREPARE_BYTES)
|
|
348
|
+
: undefined;
|
|
342
349
|
if (bytes) {
|
|
343
350
|
await validate(lease);
|
|
344
351
|
if (settled)
|
|
345
352
|
return;
|
|
346
|
-
if (
|
|
353
|
+
if (linkCatalog) {
|
|
354
|
+
const snapshot = JSON.parse(bytes.toString("utf8"));
|
|
355
|
+
if (snapshot.capabilities?.authorizationSteps === 1 &&
|
|
356
|
+
snapshot.capabilities?.remoteAuth === true)
|
|
357
|
+
snapshot.capabilities.browserHandoff = 1;
|
|
358
|
+
bytes = Buffer.from(JSON.stringify(snapshot));
|
|
359
|
+
}
|
|
360
|
+
else if (prepare)
|
|
347
361
|
rememberPrepare(lease, bytes);
|
|
348
362
|
else if (renew) {
|
|
349
363
|
const { expiresAt } = JSON.parse(bytes.toString("utf8"));
|
|
@@ -361,6 +375,8 @@ export function createProjectRuntimeProxy(options) {
|
|
|
361
375
|
(incoming.statusCode ?? 500) < 300)
|
|
362
376
|
forgetAssets(lease, route.pathname.split("/").at(-1));
|
|
363
377
|
for (const name of RESPONSE_HEADERS) {
|
|
378
|
+
if (linkCatalog && name === "content-length")
|
|
379
|
+
continue;
|
|
364
380
|
const value = incoming.headers[name];
|
|
365
381
|
if (value !== undefined)
|
|
366
382
|
res.setHeader(name, value);
|
|
@@ -383,6 +399,8 @@ export function createProjectRuntimeProxy(options) {
|
|
|
383
399
|
});
|
|
384
400
|
}
|
|
385
401
|
async function handle(req, res) {
|
|
402
|
+
if (await linkBrowser.handle(req, res))
|
|
403
|
+
return true;
|
|
386
404
|
if (!(req.url ?? "").startsWith("/p/"))
|
|
387
405
|
return false;
|
|
388
406
|
try {
|
|
@@ -394,6 +412,43 @@ export function createProjectRuntimeProxy(options) {
|
|
|
394
412
|
: await acquire(req, route.projectId);
|
|
395
413
|
if (req.aborted || res.destroyed)
|
|
396
414
|
return true;
|
|
415
|
+
const handoff = /^\/api\/v1\/links\/authorizations\/([a-f0-9-]{36})\/browser$/.exec(route.pathname);
|
|
416
|
+
if (handoff && req.method === "POST") {
|
|
417
|
+
const input = JSON.parse((await readBounded(req, 1024)).toString("utf8"));
|
|
418
|
+
if (!input ||
|
|
419
|
+
Array.isArray(input) ||
|
|
420
|
+
typeof input !== "object" ||
|
|
421
|
+
Object.keys(input).length)
|
|
422
|
+
throw new ProxyError(400, "Invalid browser authorization");
|
|
423
|
+
const target = `/api/v1/links/authorizations/${handoff[1]}`;
|
|
424
|
+
const readJob = async () => {
|
|
425
|
+
await validate(lease);
|
|
426
|
+
const result = await innerJson(lease.target, target, "", lease.cookie, 10_000, "GET");
|
|
427
|
+
await validate(lease);
|
|
428
|
+
const job = JSON.parse(result.bytes.toString("utf8"));
|
|
429
|
+
if (job.id !== handoff[1] || job.state !== "pending")
|
|
430
|
+
throw new ProxyError(409, "Authorization is no longer pending");
|
|
431
|
+
return job;
|
|
432
|
+
};
|
|
433
|
+
const launch = linkBrowser.create(await readJob(), {
|
|
434
|
+
check: async () => {
|
|
435
|
+
await readJob();
|
|
436
|
+
},
|
|
437
|
+
complete: async (callbackUrl) => {
|
|
438
|
+
await validate(lease);
|
|
439
|
+
const result = await innerJson(lease.target, target + "/complete", JSON.stringify({ callbackUrl }), lease.cookie, 30_000);
|
|
440
|
+
await validate(lease);
|
|
441
|
+
return JSON.parse(result.bytes.toString("utf8"));
|
|
442
|
+
},
|
|
443
|
+
cancel: async () => {
|
|
444
|
+
await validate(lease);
|
|
445
|
+
return innerJson(lease.target, target, "", lease.cookie, 10_000, "DELETE");
|
|
446
|
+
},
|
|
447
|
+
}, options.publicOrigin());
|
|
448
|
+
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
449
|
+
res.end(JSON.stringify(launch));
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
397
452
|
await forwardHttp(req, res, route, lease);
|
|
398
453
|
}
|
|
399
454
|
catch (error) {
|
|
@@ -588,6 +643,7 @@ export function createProjectRuntimeProxy(options) {
|
|
|
588
643
|
revokeProject,
|
|
589
644
|
async close() {
|
|
590
645
|
closed = true;
|
|
646
|
+
linkBrowser.close();
|
|
591
647
|
clearInterval(timer);
|
|
592
648
|
for (const cancel of upgrades)
|
|
593
649
|
cancel();
|
|
@@ -22,6 +22,7 @@ import { basename, dirname, extname, join, resolve } from "node:path";
|
|
|
22
22
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
23
23
|
import { WebSocketServer } from "ws";
|
|
24
24
|
import { SessionManager, SettingsManager } from "@cjhyy/code-shell-core";
|
|
25
|
+
import { readOutputJournal, readOutputJournalLegacyBase } from "@cjhyy/code-shell-core/internal";
|
|
25
26
|
import { AccessPasscode } from "../mobile-remote/access-passcode.js";
|
|
26
27
|
import { resolveSafe } from "../mobile-remote/mobile-static.js";
|
|
27
28
|
import { contentTypeFor } from "../static-files.js";
|
|
@@ -450,6 +451,7 @@ export async function startHeadlessServer(opts) {
|
|
|
450
451
|
links = hubAuth
|
|
451
452
|
? createLinkHttp({
|
|
452
453
|
...configurationOptions,
|
|
454
|
+
browserHandoff: !opts.publicPathPrefix,
|
|
453
455
|
remoteLink: () => remoteLink,
|
|
454
456
|
onChanged: () => notify("serve/configurationChanged", {}),
|
|
455
457
|
})
|
|
@@ -563,6 +565,8 @@ export async function startHeadlessServer(opts) {
|
|
|
563
565
|
return;
|
|
564
566
|
}
|
|
565
567
|
if (hubAuth) {
|
|
568
|
+
if (await links?.handlePublic(req, res))
|
|
569
|
+
return;
|
|
566
570
|
if (await panels?.handleAssets(req, res))
|
|
567
571
|
return;
|
|
568
572
|
if (await hubAuth.handle(req, res))
|
|
@@ -1131,7 +1135,7 @@ function replyToHostSessionQuery(message, sessionManager, workspaceCwd, isRunnin
|
|
|
1131
1135
|
result: { type: "sessions", data: sessions },
|
|
1132
1136
|
});
|
|
1133
1137
|
}
|
|
1134
|
-
if (queryType !== "session_detail")
|
|
1138
|
+
if (queryType !== "session_detail" && queryType !== "output_journal")
|
|
1135
1139
|
return undefined;
|
|
1136
1140
|
const sessionId = message.params?.sessionId;
|
|
1137
1141
|
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
@@ -1146,7 +1150,28 @@ function replyToHostSessionQuery(message, sessionManager, workspaceCwd, isRunnin
|
|
|
1146
1150
|
if (resolve(state.cwd) !== workspaceCwd) {
|
|
1147
1151
|
return hostQueryError(message.id, -32001, "Session not found in this workspace");
|
|
1148
1152
|
}
|
|
1149
|
-
|
|
1153
|
+
if (queryType === "output_journal") {
|
|
1154
|
+
const data = readOutputJournal(sessionManager.getStorageDir(), sessionId, {
|
|
1155
|
+
after: message.params?.after,
|
|
1156
|
+
through: message.params?.through,
|
|
1157
|
+
maxBytes: message.params?.maxBytes,
|
|
1158
|
+
maxFrames: message.params?.maxFrames,
|
|
1159
|
+
});
|
|
1160
|
+
return JSON.stringify({
|
|
1161
|
+
jsonrpc: "2.0",
|
|
1162
|
+
id: message.id,
|
|
1163
|
+
result: { type: "output_journal", data },
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
const journal = message.params?.outputRecovery === true
|
|
1167
|
+
? readOutputJournal(sessionManager.getStorageDir(), sessionId)
|
|
1168
|
+
: undefined;
|
|
1169
|
+
const legacy = journal && journal.status !== "unavailable"
|
|
1170
|
+
? readOutputJournalLegacyBase(sessionManager.getStorageDir(), sessionId, journal.legacyBaseThroughEventId)
|
|
1171
|
+
: undefined;
|
|
1172
|
+
const transcript = legacy
|
|
1173
|
+
? legacy.events
|
|
1174
|
+
: readHubTranscript(sessionManager.getStorageDir(), sessionId);
|
|
1150
1175
|
return JSON.stringify({
|
|
1151
1176
|
jsonrpc: "2.0",
|
|
1152
1177
|
id: message.id,
|
|
@@ -1154,6 +1179,7 @@ function replyToHostSessionQuery(message, sessionManager, workspaceCwd, isRunnin
|
|
|
1154
1179
|
type: "session_detail",
|
|
1155
1180
|
data: {
|
|
1156
1181
|
state,
|
|
1182
|
+
...(legacy ? { outputJournal: journal, legacyBaseComplete: legacy.complete } : {}),
|
|
1157
1183
|
...(runReplay ? runReplay.snapshot(sessionId, transcript) : { transcript }),
|
|
1158
1184
|
running: isRunning(sessionId),
|
|
1159
1185
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cjhyy/code-shell-server",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.28",
|
|
4
4
|
"description": "CodeShell server transport layer — mobile remote HTTP/WS host, pairing, passcode gate, tunnel, rooms, uploads, and session/attachment services. Electron-free; precursor of the auth gateway.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,13 +60,13 @@
|
|
|
60
60
|
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@cjhyy/code-shell-capability-coding": "0.9.
|
|
64
|
-
"@cjhyy/code-shell-core": "0.9.
|
|
65
|
-
"@cjhyy/code-shell-web": "0.9.
|
|
66
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
63
|
+
"@cjhyy/code-shell-capability-coding": "0.9.28",
|
|
64
|
+
"@cjhyy/code-shell-core": "0.9.28",
|
|
65
|
+
"@cjhyy/code-shell-web": "0.9.28",
|
|
66
|
+
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
67
67
|
"@types/ws": "^8.18.1",
|
|
68
68
|
"ws": "^8.21.0",
|
|
69
|
-
"@cjhyy/code-shell-link": "0.9.
|
|
69
|
+
"@cjhyy/code-shell-link": "0.9.28"
|
|
70
70
|
},
|
|
71
71
|
"engines": {
|
|
72
72
|
"node": ">=20.10"
|