@cjhyy/code-shell-server 0.9.44 → 0.9.46
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/attachment-service.d.ts +2 -0
- package/dist/attachment-service.js +40 -27
- package/dist/bin/code-shell-link-cleanup.d.ts +2 -0
- package/dist/bin/code-shell-link-cleanup.js +3 -0
- package/dist/desktop-web/http-api.d.ts +3 -0
- package/dist/desktop-web/http-api.js +16 -1
- package/dist/hub/configuration.d.ts +6 -3
- package/dist/hub/configuration.js +19 -6
- package/dist/hub/mcp-probe.d.ts +4 -24
- package/dist/hub/mcp-probe.js +18 -389
- package/dist/hub/model-probe.d.ts +2 -20
- package/dist/hub/model-probe.js +2 -193
- package/dist/hub/run-replay.d.ts +7 -1
- package/dist/hub/run-replay.js +10 -2
- package/dist/hub/session-management.js +18 -53
- package/dist/hub/skills-management.js +6 -8
- package/dist/hub/uploads.d.ts +10 -0
- package/dist/hub/uploads.js +107 -15
- package/dist/index.desktop-web.d.ts +1 -0
- package/dist/index.desktop-web.js +1 -0
- package/dist/index.links.d.ts +3 -0
- package/dist/index.links.js +2 -0
- package/dist/index.panels.d.ts +1 -0
- package/dist/index.panels.js +1 -0
- package/dist/index.serve.d.ts +1 -0
- package/dist/index.serve.js +1 -0
- package/dist/index.storage.d.ts +1 -0
- package/dist/index.storage.js +1 -0
- package/dist/links/catalog.js +2 -0
- package/dist/links/diagnostics.d.ts +60 -0
- package/dist/links/diagnostics.js +184 -0
- package/dist/links/http.d.ts +10 -8
- package/dist/links/http.js +144 -125
- package/dist/links/legacy-cleanup-cli.d.ts +3 -0
- package/dist/links/legacy-cleanup-cli.js +64 -0
- package/dist/links/providers/defaults.d.ts +5 -0
- package/dist/links/providers/defaults.js +782 -0
- package/dist/links/providers/index.d.ts +7 -0
- package/dist/links/providers/index.js +22 -0
- package/dist/links/remote-catalog.d.ts +10 -3
- package/dist/links/remote-catalog.js +76 -29
- package/dist/links/runtime/operation.d.ts +9 -0
- package/dist/links/runtime/operation.js +16 -0
- package/dist/links/service.d.ts +18 -2
- package/dist/links/service.js +593 -62
- package/dist/links/types.d.ts +21 -1
- package/dist/mobile-remote/codex-parse.d.ts +3 -1
- package/dist/mobile-remote/codex-parse.js +45 -11
- package/dist/mobile-remote/mobile-chat-turn.d.ts +1 -0
- package/dist/mobile-remote/mobile-chat-turn.js +3 -0
- package/dist/mobile-remote/mobile-client-event-validator.js +13 -0
- package/dist/mobile-remote/mobile-history.d.ts +10 -1
- package/dist/mobile-remote/mobile-history.js +24 -16
- package/dist/mobile-remote/remote-host-manager.d.ts +8 -0
- package/dist/mobile-remote/remote-host-manager.js +23 -8
- package/dist/mobile-remote/resident-agent.js +17 -4
- package/dist/mobile-remote/room-manager.d.ts +23 -13
- package/dist/mobile-remote/room-manager.js +102 -20
- package/dist/mobile-remote/room-model-tracing.d.ts +18 -0
- package/dist/mobile-remote/room-model-tracing.js +196 -0
- package/dist/model-trace-history.d.ts +29 -0
- package/dist/model-trace-history.js +198 -0
- package/dist/model-trace-http.d.ts +14 -0
- package/dist/model-trace-http.js +105 -0
- package/dist/panels/agent-task-host.js +1 -0
- package/dist/panels/directory-bookmarks.js +62 -29
- package/dist/panels/http.d.ts +1 -0
- package/dist/panels/hub-binding.js +5 -5
- package/dist/panels/model-trace-channel.d.ts +21 -0
- package/dist/panels/model-trace-channel.js +110 -0
- package/dist/panels/model-traces.d.ts +39 -0
- package/dist/panels/model-traces.js +222 -0
- package/dist/panels/process-service.d.ts +12 -2
- package/dist/panels/process-service.js +49 -6
- package/dist/panels/runtime.d.ts +1 -0
- package/dist/panels/runtime.js +54 -6
- package/dist/panels/tool-jobs.d.ts +2 -0
- package/dist/panels/tool-jobs.js +21 -1
- package/dist/serve/cli.js +8 -1
- package/dist/serve/headless-server.js +68 -14
- package/dist/sessions-service.js +17 -27
- package/dist/worker-bridge-core.js +11 -0
- package/package.json +8 -7
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { performance } from "node:perf_hooks";
|
|
4
|
+
const defaults = {
|
|
5
|
+
authorization: "authorization_check_failed",
|
|
6
|
+
body_validation: "request_body_invalid",
|
|
7
|
+
input_validation: "input_validation_failed",
|
|
8
|
+
host_store_read: "host_store_read_failed",
|
|
9
|
+
remote_configuration: "remote_configuration_invalid",
|
|
10
|
+
remote_catalog: "catalog_unexpected_error",
|
|
11
|
+
mode_selection: "auth_mode_unavailable",
|
|
12
|
+
connection_review: "connection_review_failed",
|
|
13
|
+
job_creation: "authorization_job_failed",
|
|
14
|
+
final_authorization: "final_authorization_failed",
|
|
15
|
+
};
|
|
16
|
+
const internalCodes = new Set([
|
|
17
|
+
...Object.values(defaults),
|
|
18
|
+
"host_store_unreadable",
|
|
19
|
+
"remote_configuration_missing",
|
|
20
|
+
"catalog_network",
|
|
21
|
+
"catalog_timeout",
|
|
22
|
+
"catalog_http_status",
|
|
23
|
+
"catalog_invalid_json",
|
|
24
|
+
"catalog_invalid_schema",
|
|
25
|
+
"catalog_unreviewed_capability",
|
|
26
|
+
"catalog_unsupported_auth",
|
|
27
|
+
"catalog_response_limit",
|
|
28
|
+
"catalog_unavailable",
|
|
29
|
+
"provider_capability_missing",
|
|
30
|
+
"job_capacity_exceeded",
|
|
31
|
+
"remote_authorization_invalid",
|
|
32
|
+
]);
|
|
33
|
+
const publicCodes = new Set([
|
|
34
|
+
"invalid_request",
|
|
35
|
+
"login_required",
|
|
36
|
+
"not_found",
|
|
37
|
+
"read_only",
|
|
38
|
+
"conflict",
|
|
39
|
+
"busy",
|
|
40
|
+
"provider_rejected",
|
|
41
|
+
"cli_unavailable",
|
|
42
|
+
"authorization_failed",
|
|
43
|
+
"authorization_expired",
|
|
44
|
+
"cancelled",
|
|
45
|
+
"unavailable",
|
|
46
|
+
]);
|
|
47
|
+
const authorizationStates = new Set(["pending", "connected", "failed", "cancelled"]);
|
|
48
|
+
const activeTrace = new AsyncLocalStorage();
|
|
49
|
+
class AuthorizationTrace {
|
|
50
|
+
sink;
|
|
51
|
+
transport;
|
|
52
|
+
requestId = randomUUID();
|
|
53
|
+
startedAt = performance.now();
|
|
54
|
+
active = true;
|
|
55
|
+
stage = "authorization";
|
|
56
|
+
failure;
|
|
57
|
+
identity = {};
|
|
58
|
+
constructor(sink, transport = "http") {
|
|
59
|
+
this.sink = sink;
|
|
60
|
+
this.transport = transport;
|
|
61
|
+
}
|
|
62
|
+
run(operation) {
|
|
63
|
+
return activeTrace.run(this, operation);
|
|
64
|
+
}
|
|
65
|
+
isActive() {
|
|
66
|
+
return this.active;
|
|
67
|
+
}
|
|
68
|
+
setStage(stage) {
|
|
69
|
+
if (!this.active)
|
|
70
|
+
return;
|
|
71
|
+
this.stage = stage;
|
|
72
|
+
this.failure = undefined;
|
|
73
|
+
this.emit({ outcome: "stage", stage });
|
|
74
|
+
}
|
|
75
|
+
setIdentity(identity) {
|
|
76
|
+
if (this.active)
|
|
77
|
+
this.identity = { ...identity };
|
|
78
|
+
}
|
|
79
|
+
setFailure(failure) {
|
|
80
|
+
if (!this.active)
|
|
81
|
+
return;
|
|
82
|
+
if (failure.internalCode === "authorization_check_failed" &&
|
|
83
|
+
this.stage === "final_authorization")
|
|
84
|
+
failure = { stage: "final_authorization", internalCode: "final_authorization_failed" };
|
|
85
|
+
this.failure = {
|
|
86
|
+
stage: failure.stage,
|
|
87
|
+
internalCode: internalCodes.has(failure.internalCode)
|
|
88
|
+
? failure.internalCode
|
|
89
|
+
: defaults[failure.stage],
|
|
90
|
+
...(Number.isInteger(failure.upstreamStatus) &&
|
|
91
|
+
failure.upstreamStatus >= 100 &&
|
|
92
|
+
failure.upstreamStatus <= 599
|
|
93
|
+
? { upstreamStatus: failure.upstreamStatus }
|
|
94
|
+
: {}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
complete(httpStatus, publicCode, authorizationState) {
|
|
98
|
+
if (!this.active)
|
|
99
|
+
return;
|
|
100
|
+
this.emit(publicCode
|
|
101
|
+
? {
|
|
102
|
+
outcome: "failure",
|
|
103
|
+
...(this.failure ?? { stage: this.stage, internalCode: defaults[this.stage] }),
|
|
104
|
+
publicCode: publicCodes.has(publicCode) ? publicCode : "unavailable",
|
|
105
|
+
httpStatus: this.httpStatus(httpStatus),
|
|
106
|
+
}
|
|
107
|
+
: {
|
|
108
|
+
outcome: "success",
|
|
109
|
+
stage: this.stage,
|
|
110
|
+
httpStatus: this.httpStatus(httpStatus),
|
|
111
|
+
...(authorizationState && authorizationStates.has(authorizationState)
|
|
112
|
+
? { authorizationState }
|
|
113
|
+
: {}),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
httpStatus(status) {
|
|
117
|
+
return Number.isInteger(status) && status >= 100 && status <= 599 ? status : 503;
|
|
118
|
+
}
|
|
119
|
+
/** Detached authorization jobs inherit ALS; they must lose authority to edit this trace. */
|
|
120
|
+
deactivate() {
|
|
121
|
+
this.active = false;
|
|
122
|
+
}
|
|
123
|
+
emit(value) {
|
|
124
|
+
const event = Object.freeze({
|
|
125
|
+
event: "link_authorization",
|
|
126
|
+
transport: this.transport,
|
|
127
|
+
requestId: this.requestId,
|
|
128
|
+
elapsedMs: Math.max(0, Math.round(performance.now() - this.startedAt)),
|
|
129
|
+
...this.identity,
|
|
130
|
+
...value,
|
|
131
|
+
});
|
|
132
|
+
try {
|
|
133
|
+
if (this.sink) {
|
|
134
|
+
void Promise.resolve(this.sink(event)).catch(() => { });
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
process.stderr.write(`${JSON.stringify(event)}\n`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Observability must never change an authorization result or reveal the sink's exception.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export function createLinkAuthorizationTrace(sink) {
|
|
146
|
+
return new AuthorizationTrace(sink);
|
|
147
|
+
}
|
|
148
|
+
/** Native Host callers share the same diagnostics; HTTP already owns its longer request lifetime. */
|
|
149
|
+
export async function withLinkAuthorizationTrace(sink, operation, failure) {
|
|
150
|
+
if (activeTrace.getStore()?.isActive())
|
|
151
|
+
return operation();
|
|
152
|
+
const trace = new AuthorizationTrace(sink, "host");
|
|
153
|
+
return trace.run(async () => {
|
|
154
|
+
try {
|
|
155
|
+
trace.setStage("authorization");
|
|
156
|
+
const result = await operation();
|
|
157
|
+
trace.complete(200, undefined, result.state);
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
try {
|
|
162
|
+
const mapped = failure(error);
|
|
163
|
+
trace.complete(mapped.status, mapped.code);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
trace.complete(503, "unavailable");
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
trace.deactivate();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
export function authorizationDiagnosticStage(stage) {
|
|
176
|
+
activeTrace.getStore()?.setStage(stage);
|
|
177
|
+
}
|
|
178
|
+
/** Callers must select identity values from the compiled provider/method/mode catalog. */
|
|
179
|
+
export function authorizationDiagnosticIdentity(identity) {
|
|
180
|
+
activeTrace.getStore()?.setIdentity(identity);
|
|
181
|
+
}
|
|
182
|
+
export function authorizationDiagnosticFailure(stage, internalCode, upstreamStatus) {
|
|
183
|
+
activeTrace.getStore()?.setFailure({ stage, internalCode, upstreamStatus });
|
|
184
|
+
}
|
package/dist/links/http.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
2
|
import { type LinkServiceOptions } from "./service.js";
|
|
3
|
-
import type { LinkAuthorizationResponse, LinkConnectionInput, LinkOperationContext, TokenConnectionInput } from "./types.js";
|
|
3
|
+
import type { LinkAuthorizationResponse, LinkAuthorization, LinkConnectionInput, LinkOperationContext, TokenConnectionInput } from "./types.js";
|
|
4
4
|
export interface LinkHttpOptions extends LinkServiceOptions {
|
|
5
5
|
/** Project gateways own the public callback; private runtimes must not advertise it themselves. */
|
|
6
6
|
browserHandoff?: boolean;
|
|
@@ -16,6 +16,7 @@ export declare function createLinkHttp(options: LinkHttpOptions): {
|
|
|
16
16
|
connectToken: (context: LinkOperationContext, input: TokenConnectionInput) => Promise<import("@cjhyy/code-shell-link").MaskedLinkConnection>;
|
|
17
17
|
connectCli: (context: LinkOperationContext, input: LinkConnectionInput) => Promise<import("@cjhyy/code-shell-link").MaskedLinkConnection>;
|
|
18
18
|
cliStatus: (context: LinkOperationContext, rawProviderId: unknown) => Promise<{
|
|
19
|
+
accountId?: string | undefined;
|
|
19
20
|
account?: string | undefined;
|
|
20
21
|
providerId: import("@cjhyy/code-shell-core").CliLinkProviderId;
|
|
21
22
|
command: string;
|
|
@@ -24,14 +25,15 @@ export declare function createLinkHttp(options: LinkHttpOptions): {
|
|
|
24
25
|
}>;
|
|
25
26
|
rename: (context: LinkOperationContext, id: string, label: string, expectedRevision: string) => Promise<import("@cjhyy/code-shell-link").MaskedLinkConnection>;
|
|
26
27
|
disconnect: (context: LinkOperationContext, id: string, expectedRevision: string) => Promise<void>;
|
|
27
|
-
startDeviceAuth: (context: LinkOperationContext, input: LinkConnectionInput) => Promise<
|
|
28
|
-
startAuthorization: (context: LinkOperationContext, input: LinkConnectionInput, rawAuthModeId: unknown) => Promise<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
startDeviceAuth: (context: LinkOperationContext, input: LinkConnectionInput) => Promise<LinkAuthorization>;
|
|
29
|
+
startAuthorization: (context: LinkOperationContext, input: LinkConnectionInput, rawAuthModeId: unknown) => Promise<LinkAuthorization>;
|
|
30
|
+
openHostAuthorization: (context: LinkOperationContext, id: string) => Promise<boolean>;
|
|
31
|
+
respondAuthorization: (context: LinkOperationContext, id: string, response: LinkAuthorizationResponse) => Promise<LinkAuthorization>;
|
|
32
|
+
refreshRemoteCatalog: (context: LinkOperationContext, force?: boolean) => Promise<void>;
|
|
33
|
+
startRemoteAuth: (context: LinkOperationContext, input: LinkConnectionInput) => Promise<LinkAuthorization>;
|
|
34
|
+
completeRemoteAuth: (context: LinkOperationContext, id: string, callbackUrl: string) => Promise<LinkAuthorization>;
|
|
33
35
|
retryRemoteCleanup: () => Promise<void>;
|
|
34
|
-
authorization: (context: LinkOperationContext, id: string) => Promise<
|
|
36
|
+
authorization: (context: LinkOperationContext, id: string) => Promise<LinkAuthorization>;
|
|
35
37
|
cancelAuthorization: (context: LinkOperationContext, id: string) => Promise<void>;
|
|
36
38
|
cancelOwner: (ownerId: string) => void;
|
|
37
39
|
close: () => void;
|
package/dist/links/http.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createLinkBrowserHandoff } from "./browser-handoff.js";
|
|
2
|
+
import { authorizationDiagnosticStage, createLinkAuthorizationTrace } from "./diagnostics.js";
|
|
2
3
|
import { createLinkService, LinkServiceError, publicLinkError, } from "./service.js";
|
|
3
4
|
const ROOT = "/api/v1/links";
|
|
4
5
|
const MAX_BODY_BYTES = 32 * 1024;
|
|
@@ -77,137 +78,155 @@ export function createLinkHttp(options) {
|
|
|
77
78
|
abandoned = true;
|
|
78
79
|
};
|
|
79
80
|
response.once("close", onClose);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
new URL(configured.redirectUri).pathname === "/link/callback"
|
|
95
|
-
? { browserHandoff: 1 }
|
|
96
|
-
: {}),
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
const cli = /^\/api\/v1\/links\/providers\/([^/]+)\/cli$/.exec(url.pathname);
|
|
102
|
-
const connection = /^\/api\/v1\/links\/connections\/([^/]+)$/.exec(url.pathname);
|
|
103
|
-
const remoteComplete = /^\/api\/v1\/links\/authorizations\/([^/]+)\/complete$/.exec(url.pathname);
|
|
104
|
-
const handoff = /^\/api\/v1\/links\/authorizations\/([^/]+)\/browser$/.exec(url.pathname);
|
|
105
|
-
const authorization = /^\/api\/v1\/links\/authorizations\/([^/]+)$/.exec(url.pathname);
|
|
106
|
-
const respond = /^\/api\/v1\/links\/authorizations\/([^/]+)\/responses$/.exec(url.pathname);
|
|
107
|
-
if (url.pathname === ROOT + "/authorizations" && method === "POST") {
|
|
108
|
-
const input = await body(request);
|
|
109
|
-
fields(input, [
|
|
110
|
-
"providerId",
|
|
111
|
-
"methodId",
|
|
112
|
-
"label",
|
|
113
|
-
"connectionId",
|
|
114
|
-
"expectedRevision",
|
|
115
|
-
"authModeId",
|
|
116
|
-
"resourceUrl",
|
|
117
|
-
]);
|
|
118
|
-
result = await service.startAuthorization(owner, input, input.authModeId);
|
|
119
|
-
}
|
|
120
|
-
else if (handoff && method === "POST") {
|
|
121
|
-
fields(await body(request), []);
|
|
122
|
-
const id = decodeURIComponent(handoff[1]);
|
|
123
|
-
const check = async () => {
|
|
124
|
-
const job = await service.authorization(owner, id);
|
|
125
|
-
if (job.state !== "pending")
|
|
126
|
-
throw new LinkServiceError(409, "conflict");
|
|
127
|
-
return job;
|
|
128
|
-
};
|
|
81
|
+
const trace = url.pathname === ROOT + "/authorizations" && request.method === "POST"
|
|
82
|
+
? createLinkAuthorizationTrace(options.onAuthorizationDiagnostic)
|
|
83
|
+
: undefined;
|
|
84
|
+
if (trace)
|
|
85
|
+
response.setHeader("X-Request-ID", trace.requestId);
|
|
86
|
+
const handle = async () => {
|
|
87
|
+
try {
|
|
88
|
+
authorizationDiagnosticStage("authorization");
|
|
89
|
+
const owner = await context(request, () => abandoned || request.aborted);
|
|
90
|
+
const method = request.method;
|
|
91
|
+
let result;
|
|
92
|
+
if (url.pathname === ROOT && method === "GET") {
|
|
93
|
+
await service.refreshRemoteCatalog(owner);
|
|
94
|
+
const snapshot = service.snapshot();
|
|
129
95
|
const configured = options.remoteLink?.();
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
96
|
+
result = {
|
|
97
|
+
...snapshot,
|
|
98
|
+
capabilities: {
|
|
99
|
+
...snapshot.capabilities,
|
|
100
|
+
...(options.browserHandoff !== false &&
|
|
101
|
+
configured &&
|
|
102
|
+
new URL(configured.redirectUri).pathname === "/link/callback"
|
|
103
|
+
? { browserHandoff: 1 }
|
|
104
|
+
: {}),
|
|
135
105
|
},
|
|
136
|
-
|
|
137
|
-
cancel: () => service.cancelAuthorization(owner, id),
|
|
138
|
-
}, new URL(configured.redirectUri).origin);
|
|
139
|
-
}
|
|
140
|
-
else if (respond && method === "POST") {
|
|
141
|
-
const input = await body(request);
|
|
142
|
-
fields(input, ["stepId", "operation", "input"]);
|
|
143
|
-
result = await service.respondAuthorization(owner, decodeURIComponent(respond[1]), input);
|
|
144
|
-
}
|
|
145
|
-
else if (remoteComplete && method === "POST") {
|
|
146
|
-
const input = await body(request);
|
|
147
|
-
fields(input, ["callbackUrl"]);
|
|
148
|
-
result = await service.completeRemoteAuth(owner, decodeURIComponent(remoteComplete[1]), input.callbackUrl);
|
|
149
|
-
}
|
|
150
|
-
else if (cli && method === "GET")
|
|
151
|
-
result = await service.cliStatus(owner, decodeURIComponent(cli[1]));
|
|
152
|
-
else if (authorization && method === "GET")
|
|
153
|
-
result = await service.authorization(owner, decodeURIComponent(authorization[1]));
|
|
154
|
-
else if (authorization && method === "DELETE") {
|
|
155
|
-
await service.cancelAuthorization(owner, decodeURIComponent(authorization[1]));
|
|
156
|
-
result = { cancelled: true };
|
|
157
|
-
}
|
|
158
|
-
else if (method === "POST" &&
|
|
159
|
-
[
|
|
160
|
-
ROOT + "/connections/token",
|
|
161
|
-
ROOT + "/connections/cli",
|
|
162
|
-
ROOT + "/authorizations/device",
|
|
163
|
-
ROOT + "/authorizations/remote",
|
|
164
|
-
].includes(url.pathname)) {
|
|
165
|
-
const input = await body(request);
|
|
166
|
-
const token = url.pathname.endsWith("/token");
|
|
167
|
-
fields(input, [
|
|
168
|
-
"providerId",
|
|
169
|
-
"methodId",
|
|
170
|
-
"label",
|
|
171
|
-
"connectionId",
|
|
172
|
-
"expectedRevision",
|
|
173
|
-
...(token ? ["token"] : []),
|
|
174
|
-
...(url.pathname.endsWith("/remote") ? ["resourceUrl"] : []),
|
|
175
|
-
]);
|
|
176
|
-
result = token
|
|
177
|
-
? await service.connectToken(owner, input)
|
|
178
|
-
: url.pathname.endsWith("/cli")
|
|
179
|
-
? await service.connectCli(owner, input)
|
|
180
|
-
: url.pathname.endsWith("/remote")
|
|
181
|
-
? await service.startRemoteAuth(owner, input)
|
|
182
|
-
: await service.startDeviceAuth(owner, input);
|
|
106
|
+
};
|
|
183
107
|
}
|
|
184
|
-
else
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
108
|
+
else {
|
|
109
|
+
const cli = /^\/api\/v1\/links\/providers\/([^/]+)\/cli$/.exec(url.pathname);
|
|
110
|
+
const connection = /^\/api\/v1\/links\/connections\/([^/]+)$/.exec(url.pathname);
|
|
111
|
+
const remoteComplete = /^\/api\/v1\/links\/authorizations\/([^/]+)\/complete$/.exec(url.pathname);
|
|
112
|
+
const handoff = /^\/api\/v1\/links\/authorizations\/([^/]+)\/browser$/.exec(url.pathname);
|
|
113
|
+
const authorization = /^\/api\/v1\/links\/authorizations\/([^/]+)$/.exec(url.pathname);
|
|
114
|
+
const respond = /^\/api\/v1\/links\/authorizations\/([^/]+)\/responses$/.exec(url.pathname);
|
|
115
|
+
if (url.pathname === ROOT + "/authorizations" && method === "POST") {
|
|
116
|
+
authorizationDiagnosticStage("body_validation");
|
|
117
|
+
const input = await body(request);
|
|
118
|
+
fields(input, [
|
|
119
|
+
"providerId",
|
|
120
|
+
"methodId",
|
|
121
|
+
"label",
|
|
122
|
+
"connectionId",
|
|
123
|
+
"expectedRevision",
|
|
124
|
+
"authModeId",
|
|
125
|
+
"resourceUrl",
|
|
126
|
+
]);
|
|
127
|
+
result = await service.startAuthorization(owner, input, input.authModeId);
|
|
128
|
+
}
|
|
129
|
+
else if (handoff && method === "POST") {
|
|
130
|
+
fields(await body(request), []);
|
|
131
|
+
const id = decodeURIComponent(handoff[1]);
|
|
132
|
+
const check = async () => {
|
|
133
|
+
const job = await service.authorization(owner, id);
|
|
134
|
+
if (job.state !== "pending")
|
|
135
|
+
throw new LinkServiceError(409, "conflict");
|
|
136
|
+
return job;
|
|
137
|
+
};
|
|
138
|
+
const configured = options.remoteLink?.();
|
|
139
|
+
if (!configured)
|
|
140
|
+
throw new LinkServiceError(503, "unavailable");
|
|
141
|
+
result = browser.create(await check(), {
|
|
142
|
+
check: async () => {
|
|
143
|
+
await check();
|
|
144
|
+
},
|
|
145
|
+
complete: (callbackUrl) => service.completeRemoteAuth(owner, id, callbackUrl),
|
|
146
|
+
cancel: () => service.cancelAuthorization(owner, id),
|
|
147
|
+
}, new URL(configured.redirectUri).origin);
|
|
193
148
|
}
|
|
149
|
+
else if (respond && method === "POST") {
|
|
150
|
+
const input = await body(request);
|
|
151
|
+
fields(input, ["stepId", "operation", "input"]);
|
|
152
|
+
result = await service.respondAuthorization(owner, decodeURIComponent(respond[1]), input);
|
|
153
|
+
}
|
|
154
|
+
else if (remoteComplete && method === "POST") {
|
|
155
|
+
const input = await body(request);
|
|
156
|
+
fields(input, ["callbackUrl"]);
|
|
157
|
+
result = await service.completeRemoteAuth(owner, decodeURIComponent(remoteComplete[1]), input.callbackUrl);
|
|
158
|
+
}
|
|
159
|
+
else if (cli && method === "GET")
|
|
160
|
+
result = await service.cliStatus(owner, decodeURIComponent(cli[1]));
|
|
161
|
+
else if (authorization && method === "GET")
|
|
162
|
+
result = await service.authorization(owner, decodeURIComponent(authorization[1]));
|
|
163
|
+
else if (authorization && method === "DELETE") {
|
|
164
|
+
await service.cancelAuthorization(owner, decodeURIComponent(authorization[1]));
|
|
165
|
+
result = { cancelled: true };
|
|
166
|
+
}
|
|
167
|
+
else if (method === "POST" &&
|
|
168
|
+
[
|
|
169
|
+
ROOT + "/connections/token",
|
|
170
|
+
ROOT + "/connections/cli",
|
|
171
|
+
ROOT + "/authorizations/device",
|
|
172
|
+
ROOT + "/authorizations/remote",
|
|
173
|
+
].includes(url.pathname)) {
|
|
174
|
+
const input = await body(request);
|
|
175
|
+
const token = url.pathname.endsWith("/token");
|
|
176
|
+
fields(input, [
|
|
177
|
+
"providerId",
|
|
178
|
+
"methodId",
|
|
179
|
+
"label",
|
|
180
|
+
"connectionId",
|
|
181
|
+
"expectedRevision",
|
|
182
|
+
...(token ? ["token"] : []),
|
|
183
|
+
...(url.pathname.endsWith("/remote") ? ["resourceUrl"] : []),
|
|
184
|
+
]);
|
|
185
|
+
result = token
|
|
186
|
+
? await service.connectToken(owner, input)
|
|
187
|
+
: url.pathname.endsWith("/cli")
|
|
188
|
+
? await service.connectCli(owner, input)
|
|
189
|
+
: url.pathname.endsWith("/remote")
|
|
190
|
+
? await service.startRemoteAuth(owner, input)
|
|
191
|
+
: await service.startDeviceAuth(owner, input);
|
|
192
|
+
}
|
|
193
|
+
else if (connection && (method === "PATCH" || method === "DELETE")) {
|
|
194
|
+
const input = await body(request);
|
|
195
|
+
fields(input, method === "PATCH" ? ["label", "expectedRevision"] : ["expectedRevision"]);
|
|
196
|
+
const id = decodeURIComponent(connection[1]);
|
|
197
|
+
if (method === "PATCH")
|
|
198
|
+
result = await service.rename(owner, id, input.label, input.expectedRevision);
|
|
199
|
+
else {
|
|
200
|
+
await service.disconnect(owner, id, input.expectedRevision);
|
|
201
|
+
result = { removed: true };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else
|
|
205
|
+
throw new LinkServiceError(404, "not_found");
|
|
194
206
|
}
|
|
195
|
-
|
|
196
|
-
|
|
207
|
+
authorizationDiagnosticStage("final_authorization");
|
|
208
|
+
await service.assertAuthorized(owner);
|
|
209
|
+
json(response, 200, result);
|
|
210
|
+
trace?.complete(200, undefined, result.state);
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
const failure = error instanceof URIError
|
|
214
|
+
? new LinkServiceError(400, "invalid_request")
|
|
215
|
+
: publicLinkError(error);
|
|
216
|
+
trace?.complete(failure.status, failure.code);
|
|
217
|
+
if (!response.destroyed)
|
|
218
|
+
json(response, failure.status, {
|
|
219
|
+
error: failure.message,
|
|
220
|
+
code: failure.code,
|
|
221
|
+
...(trace ? { requestId: trace.requestId } : {}),
|
|
222
|
+
});
|
|
197
223
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
: publicLinkError(error);
|
|
205
|
-
if (!response.destroyed)
|
|
206
|
-
json(response, failure.status, { error: failure.message, code: failure.code });
|
|
207
|
-
}
|
|
208
|
-
finally {
|
|
209
|
-
response.off("close", onClose);
|
|
210
|
-
}
|
|
224
|
+
finally {
|
|
225
|
+
trace?.deactivate();
|
|
226
|
+
response.off("close", onClose);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
await (trace ? trace.run(handle) : handle());
|
|
211
230
|
return true;
|
|
212
231
|
},
|
|
213
232
|
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
const HELP = `Usage: code-shell-link-cleanup <inspect|remove> [options]
|
|
3
|
+
|
|
4
|
+
Offline cleanup of legacy Link-owned connection records. Stop every CodeShell
|
|
5
|
+
Host/worker sharing these credentials before remove. Ordinary OAuth/MCP/model
|
|
6
|
+
credentials, sessions, operation evidence and files are preserved. External CLI
|
|
7
|
+
logins are retained. Remote grants remain in private retirement custody for retry;
|
|
8
|
+
removing a local record does not report successful remote revocation.
|
|
9
|
+
|
|
10
|
+
--project <directory> Project credential layer (default: current directory)
|
|
11
|
+
--scope <full|project> Full includes user + project (default: full)
|
|
12
|
+
--user-dir <directory> Explicit user credential directory override
|
|
13
|
+
--operations-root <dir> Repeat for every custom shared operation/session root
|
|
14
|
+
-h, --help Show help without reading credentials
|
|
15
|
+
`;
|
|
16
|
+
export async function runLegacyLinkCleanupCli(argv = process.argv.slice(2), io = { write: (value) => process.stdout.write(value) }) {
|
|
17
|
+
try {
|
|
18
|
+
const { values, positionals } = parseArgs({
|
|
19
|
+
args: argv,
|
|
20
|
+
strict: true,
|
|
21
|
+
allowPositionals: true,
|
|
22
|
+
options: {
|
|
23
|
+
project: { type: "string" },
|
|
24
|
+
scope: { type: "string" },
|
|
25
|
+
"user-dir": { type: "string" },
|
|
26
|
+
"operations-root": { type: "string", multiple: true },
|
|
27
|
+
help: { type: "boolean", short: "h" },
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
if (values.help) {
|
|
31
|
+
io.write(HELP);
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
const action = positionals[0];
|
|
35
|
+
const scope = values.scope ?? "full";
|
|
36
|
+
if (positionals.length !== 1 ||
|
|
37
|
+
!["inspect", "remove"].includes(action) ||
|
|
38
|
+
!["full", "project"].includes(scope) ||
|
|
39
|
+
Object.entries(values).some(([key, value]) => key !== "help" &&
|
|
40
|
+
(Array.isArray(value)
|
|
41
|
+
? value.some((item) => !item.trim())
|
|
42
|
+
: typeof value === "string" && !value.trim())))
|
|
43
|
+
throw new Error("arguments");
|
|
44
|
+
const { CredentialStore, sessionsRoot } = await import("@cjhyy/code-shell-core");
|
|
45
|
+
const { previewLegacyLinkCleanup, cleanupLegacyLinkConnections } = await import("@cjhyy/code-shell-core/internal");
|
|
46
|
+
const store = new CredentialStore(values.project ?? process.cwd(), undefined, values["user-dir"]);
|
|
47
|
+
const preview = previewLegacyLinkCleanup(store, scope);
|
|
48
|
+
if (action === "inspect") {
|
|
49
|
+
io.write(`${JSON.stringify({ count: preview.length, connections: preview })}\n`);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
const result = cleanupLegacyLinkConnections({
|
|
53
|
+
store,
|
|
54
|
+
scope: scope,
|
|
55
|
+
operationRoots: [sessionsRoot(), ...(values["operations-root"] ?? [])],
|
|
56
|
+
});
|
|
57
|
+
io.write(`${JSON.stringify(result)}\n`);
|
|
58
|
+
return result.changed.length ? 1 : 0;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
io.write("Legacy Link cleanup failed; no successful cleanup is implied. Check arguments, stop Hosts and ensure stores are readable.\n");
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { LocalLinkProviderSpec } from "@cjhyy/code-shell-core/extension";
|
|
2
|
+
/** Temporary injectable helper seam; keeps source and built Host module identities consistent. */
|
|
3
|
+
export type LinkProviderRuntime = Pick<typeof import("@cjhyy/code-shell-core/extension"), "GITHUB_SOURCE_ACTIONS" | "githubSourceEndpoint" | "normalizeGithubSourceResult" | "slackSourceParams" | "normalizeSlackSourceResult" | "githubIssueStateParameters" | "githubRepositoryParameters" | "githubSetStarredParameters" | "FIGMA_RESPONSE_BYTES" | "FIGMA_RESPONSE_TOO_LARGE" | "figmaActionEndpoint" | "figmaActionParameters" | "normalizeFigmaActionResult" | "asArray" | "asRecord" | "firstString" | "intParam" | "linkRequestJson" | "linkRequestStatus" | "pathParam" | "pathSegmentParam" | "pick" | "stringParam">;
|
|
4
|
+
/** Product-owned HTTP implementations. Importing the factory does not install providers. */
|
|
5
|
+
export declare function createDefaultLinkProviders(runtime?: LinkProviderRuntime): readonly LocalLinkProviderSpec[];
|