@cjhyy/code-shell-server 0.9.51 → 0.10.1

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.
Files changed (93) hide show
  1. package/dist/account/cli.d.ts +23 -0
  2. package/dist/account/cli.js +249 -0
  3. package/dist/account/cloud-contract.d.ts +39 -0
  4. package/dist/account/cloud-contract.js +134 -0
  5. package/dist/account/cloud-types.d.ts +47 -0
  6. package/dist/account/cloud-types.js +1 -0
  7. package/dist/account/feature-service.d.ts +47 -0
  8. package/dist/account/feature-service.js +184 -0
  9. package/dist/account/http.d.ts +17 -0
  10. package/dist/account/http.js +158 -0
  11. package/dist/account/links.d.ts +13 -0
  12. package/dist/account/links.js +137 -0
  13. package/dist/account/manager.d.ts +96 -0
  14. package/dist/account/manager.js +976 -0
  15. package/dist/account/native-lock.d.ts +6 -0
  16. package/dist/account/native-lock.js +26 -0
  17. package/dist/account/native-store.d.ts +32 -0
  18. package/dist/account/native-store.js +162 -0
  19. package/dist/account/profile.d.ts +4 -0
  20. package/dist/account/profile.js +46 -0
  21. package/dist/account/store.d.ts +34 -0
  22. package/dist/account/store.js +118 -0
  23. package/dist/account/types.d.ts +138 -0
  24. package/dist/account/types.js +2 -0
  25. package/dist/desktop-web/http-api.d.ts +4 -0
  26. package/dist/desktop-web/http-api.js +12 -4
  27. package/dist/environment-identity.d.ts +1 -1
  28. package/dist/environment-identity.js +2 -1
  29. package/dist/headless-worker-env.d.ts +2 -0
  30. package/dist/headless-worker-env.js +15 -0
  31. package/dist/hub/approval-lease.js +6 -1
  32. package/dist/hub/configuration.d.ts +11 -0
  33. package/dist/hub/configuration.js +34 -1
  34. package/dist/hub/model-parameter-metadata.d.ts +26 -0
  35. package/dist/hub/model-parameter-metadata.js +92 -0
  36. package/dist/hub/run-replay.js +1 -1
  37. package/dist/hub/session-management.js +1 -1
  38. package/dist/index.account.d.ts +12 -0
  39. package/dist/index.account.js +12 -0
  40. package/dist/index.mobile-remote.d.ts +2 -0
  41. package/dist/index.mobile-remote.js +1 -0
  42. package/dist/index.worker.d.ts +1 -0
  43. package/dist/index.worker.js +1 -0
  44. package/dist/mobile-remote/account-relay-access.d.ts +59 -0
  45. package/dist/mobile-remote/account-relay-access.js +174 -0
  46. package/dist/mobile-remote/approval-clock.d.ts +3 -0
  47. package/dist/mobile-remote/approval-clock.js +15 -0
  48. package/dist/mobile-remote/codex-room-agent.d.ts +6 -0
  49. package/dist/mobile-remote/codex-room-agent.js +17 -1
  50. package/dist/mobile-remote/mobile-chat-turn.d.ts +4 -0
  51. package/dist/mobile-remote/mobile-chat-turn.js +3 -0
  52. package/dist/mobile-remote/mobile-client-event-validator.js +47 -2
  53. package/dist/mobile-remote/mobile-upload-service.d.ts +9 -0
  54. package/dist/mobile-remote/mobile-upload-service.js +25 -0
  55. package/dist/mobile-remote/remote-host-manager.d.ts +21 -4
  56. package/dist/mobile-remote/remote-host-manager.js +142 -25
  57. package/dist/mobile-remote/resident-agent.d.ts +14 -0
  58. package/dist/mobile-remote/resident-agent.js +38 -11
  59. package/dist/mobile-remote/room-history.d.ts +7 -0
  60. package/dist/mobile-remote/room-history.js +56 -1
  61. package/dist/mobile-remote/room-manager.d.ts +216 -9
  62. package/dist/mobile-remote/room-manager.js +700 -65
  63. package/dist/mobile-remote/room-process-stop.d.ts +15 -0
  64. package/dist/mobile-remote/room-process-stop.js +41 -0
  65. package/dist/mobile-remote/trusted-device-store.d.ts +12 -0
  66. package/dist/mobile-remote/trusted-device-store.js +54 -1
  67. package/dist/mobile-remote/turn-queue.d.ts +183 -0
  68. package/dist/mobile-remote/turn-queue.js +422 -0
  69. package/dist/mobile-remote/types.d.ts +1 -1
  70. package/dist/panels/agent-task-host.js +6 -12
  71. package/dist/panels/agent-task-service.d.ts +2 -0
  72. package/dist/panels/agent-task-service.js +7 -0
  73. package/dist/panels/browser-capture.d.ts +1 -15
  74. package/dist/panels/browser-capture.js +1 -35
  75. package/dist/panels/directory-picker.d.ts +48 -0
  76. package/dist/panels/directory-picker.js +247 -0
  77. package/dist/panels/http.d.ts +15 -15
  78. package/dist/panels/management-http.d.ts +9 -9
  79. package/dist/panels/process-service.d.ts +5 -4
  80. package/dist/panels/process-service.js +54 -16
  81. package/dist/panels/runtime.js +148 -30
  82. package/dist/panels/tool-jobs-storage.d.ts +4 -0
  83. package/dist/panels/tool-jobs-storage.js +111 -34
  84. package/dist/panels/types.d.ts +1 -97
  85. package/dist/remote-relay/connector.d.ts +8 -0
  86. package/dist/remote-relay/connector.js +29 -2
  87. package/dist/remote-relay/protocol.d.ts +21 -0
  88. package/dist/remote-relay/protocol.js +31 -0
  89. package/dist/serve/cli.js +6 -0
  90. package/dist/serve/headless-server.js +24 -19
  91. package/dist/sessions-service.d.ts +4 -0
  92. package/dist/sessions-service.js +14 -1
  93. package/package.json +10 -6
@@ -0,0 +1,23 @@
1
+ import { CloudAccountManager } from "./manager.js";
2
+ export declare const ACCOUNT_HELP = "Usage: account <login|status|features|links|logout|register> [options]\n --origin <https-origin> Account service (default: https://115.159.45.55)\n --username <name> Password login; password is prompted without echo\n --password-stdin Read one password from stdin; never put it in arguments\n --github Sign in using GitHub in the browser\n --memory Keep an interactive account session in this process only\n --no-open Print browser URLs instead of opening them\n\nCredentials use the OS credential store; no plaintext fallback. Registration opens\n<origin>/register. Local tasks and serve's Hub/passcode identity remain independent.\nWith --memory, login keeps an account console open: status, features, links, logout,\nregister, exit. Closing this console revokes its temporary login. No device relay\nor upload is started. Use the same --origin on later account commands. Persistent\ncommands for one origin are serialized; finish an active login before retrying.";
3
+ export interface AccountCliOptions {
4
+ action: "login" | "status" | "features" | "links" | "logout" | "register";
5
+ origin: string;
6
+ username?: string;
7
+ github: boolean;
8
+ passwordStdin: boolean;
9
+ memory: boolean;
10
+ open: boolean;
11
+ }
12
+ export declare function parseAccountArgs(argv: string[]): AccountCliOptions;
13
+ export interface AccountCliDependencies {
14
+ createManager?: (options: AccountCliOptions, openExternal: (url: string) => Promise<void>) => CloudAccountManager;
15
+ write?: (text: string) => void;
16
+ open?: (url: string) => Promise<void>;
17
+ prompt?: (label: string, secret?: boolean) => Promise<string>;
18
+ readPassword?: () => Promise<string>;
19
+ interactive?: boolean;
20
+ acquireLock?: (origin: string) => Promise<() => Promise<void>>;
21
+ }
22
+ /** Shared entry for TUI and serve; it never changes Host/Hub authentication. */
23
+ export declare function runAccountCli(argv: string[], deps?: AccountCliDependencies): Promise<void>;
@@ -0,0 +1,249 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createInterface } from "node:readline/promises";
3
+ import { Writable } from "node:stream";
4
+ import { CloudAccountManager } from "./manager.js";
5
+ import { DEFAULT_CLOUD_ACCOUNT_ORIGIN } from "./types.js";
6
+ import { MemoryAccountStore, NativeAccountStore } from "./native-store.js";
7
+ import { acquireNativeAccountLock } from "./native-lock.js";
8
+ import { accountRegistrationUrl, readAccountFeatures, resolveAccountFeatures, } from "@cjhyy/code-shell-web/account";
9
+ export const ACCOUNT_HELP = `Usage: account <login|status|features|links|logout|register> [options]
10
+ --origin <https-origin> Account service (default: ${DEFAULT_CLOUD_ACCOUNT_ORIGIN})
11
+ --username <name> Password login; password is prompted without echo
12
+ --password-stdin Read one password from stdin; never put it in arguments
13
+ --github Sign in using GitHub in the browser
14
+ --memory Keep an interactive account session in this process only
15
+ --no-open Print browser URLs instead of opening them
16
+
17
+ Credentials use the OS credential store; no plaintext fallback. Registration opens
18
+ <origin>/register. Local tasks and serve's Hub/passcode identity remain independent.
19
+ With --memory, login keeps an account console open: status, features, links, logout,
20
+ register, exit. Closing this console revokes its temporary login. No device relay
21
+ or upload is started. Use the same --origin on later account commands. Persistent
22
+ commands for one origin are serialized; finish an active login before retrying.`;
23
+ export function parseAccountArgs(argv) {
24
+ const [action, ...flags] = argv;
25
+ if (!["login", "status", "features", "links", "logout", "register"].includes(action))
26
+ throw new Error(ACCOUNT_HELP);
27
+ const options = {
28
+ action: action,
29
+ origin: DEFAULT_CLOUD_ACCOUNT_ORIGIN,
30
+ github: false,
31
+ passwordStdin: false,
32
+ memory: false,
33
+ open: true,
34
+ };
35
+ for (let i = 0; i < flags.length; i++) {
36
+ const flag = flags[i];
37
+ if (flag === "--origin" || flag === "--username") {
38
+ const value = flags[++i];
39
+ if (!value || value.startsWith("--"))
40
+ throw new Error(`Missing value for ${flag}`);
41
+ if (flag === "--origin")
42
+ options.origin = value;
43
+ else
44
+ options.username = value;
45
+ }
46
+ else if (flag === "--github")
47
+ options.github = true;
48
+ else if (flag === "--password-stdin")
49
+ options.passwordStdin = true;
50
+ else if (flag === "--memory")
51
+ options.memory = true;
52
+ else if (flag === "--no-open")
53
+ options.open = false;
54
+ else
55
+ throw new Error("Unknown account option. Passwords must use the hidden prompt or --password-stdin.");
56
+ }
57
+ accountRegistrationUrl(options.origin);
58
+ if (options.github && (options.username || options.passwordStdin))
59
+ throw new Error("Choose GitHub or password login.");
60
+ if (options.action !== "login" &&
61
+ (options.github || options.username || options.passwordStdin || options.memory))
62
+ throw new Error("Login options require account login.");
63
+ return options;
64
+ }
65
+ async function openBrowser(url) {
66
+ const command = process.platform === "darwin"
67
+ ? "open"
68
+ : process.platform === "win32"
69
+ ? "rundll32.exe"
70
+ : "xdg-open";
71
+ const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
72
+ await new Promise((resolve, reject) => {
73
+ const child = spawn(command, args, { stdio: "ignore", windowsHide: true });
74
+ child.on("error", () => reject(new Error("无法打开浏览器,请手动访问显示的地址。")));
75
+ child.on("exit", (code) => code === 0 ? resolve() : reject(new Error("无法打开浏览器,请手动访问显示的地址。")));
76
+ });
77
+ }
78
+ async function question(label, secret = false) {
79
+ if (!process.stdin.isTTY)
80
+ throw new Error("Interactive login needs a terminal. Use --username and --password-stdin.");
81
+ let muted = false;
82
+ const output = new Writable({
83
+ write(chunk, _encoding, done) {
84
+ if (!muted)
85
+ process.stderr.write(chunk);
86
+ done();
87
+ },
88
+ });
89
+ const rl = createInterface({ input: process.stdin, output, terminal: true });
90
+ try {
91
+ const answer = rl.question(label);
92
+ muted = secret;
93
+ return await answer;
94
+ }
95
+ finally {
96
+ rl.close();
97
+ if (secret)
98
+ process.stderr.write("\n");
99
+ }
100
+ }
101
+ async function stdinPassword() {
102
+ if (process.stdin.isTTY)
103
+ throw new Error("--password-stdin expects piped input.");
104
+ const chunks = [];
105
+ let size = 0;
106
+ for await (const chunk of process.stdin) {
107
+ const bytes = Buffer.from(chunk);
108
+ size += bytes.length;
109
+ if (size > 4096)
110
+ throw new Error("Password input exceeds its limit.");
111
+ chunks.push(bytes);
112
+ }
113
+ const password = Buffer.concat(chunks)
114
+ .toString("utf8")
115
+ .replace(/\r?\n$/, "");
116
+ if (!password || /[\r\n]/.test(password))
117
+ throw new Error("Provide exactly one password on stdin.");
118
+ return password;
119
+ }
120
+ /** Shared entry for TUI and serve; it never changes Host/Hub authentication. */
121
+ export async function runAccountCli(argv, deps = {}) {
122
+ const write = deps.write ?? ((text) => console.log(text));
123
+ if (!argv.length || argv.includes("--help") || argv.includes("-h")) {
124
+ write(ACCOUNT_HELP);
125
+ return;
126
+ }
127
+ const options = parseAccountArgs(argv);
128
+ const navigate = async (url) => {
129
+ write(url);
130
+ if (options.open)
131
+ await (deps.open ?? openBrowser)(url);
132
+ };
133
+ if (options.memory && (!(deps.interactive ?? process.stdin.isTTY) || options.passwordStdin))
134
+ throw new Error("--memory requires an interactive terminal and keeps the session open. Use OS storage for one-shot stdin login.");
135
+ const release = options.memory || options.action === "register"
136
+ ? undefined
137
+ : await (deps.acquireLock ?? acquireNativeAccountLock)(options.origin);
138
+ try {
139
+ const manager = deps.createManager?.(options, navigate) ??
140
+ new CloudAccountManager({
141
+ store: options.memory || options.action === "register"
142
+ ? new MemoryAccountStore()
143
+ : new NativeAccountStore(options.origin),
144
+ openExternal: navigate,
145
+ changed() { },
146
+ });
147
+ const register = async () => {
148
+ const capabilities = await manager.capabilities(options.origin);
149
+ if (!capabilities.registrationAllowed)
150
+ throw new Error("此账号服务暂未开放注册。");
151
+ await navigate(accountRegistrationUrl(options.origin));
152
+ };
153
+ const prompt = deps.prompt ?? question;
154
+ const display = async (action) => {
155
+ await manager.resume();
156
+ const status = manager.status();
157
+ if (action === "status") {
158
+ write(JSON.stringify(status, null, 2));
159
+ return;
160
+ }
161
+ const deployment = await manager.capabilities(options.origin);
162
+ const overview = status.state === "signed-in" && status.account && status.origin === options.origin
163
+ ? await manager.overview({ origin: options.origin, accountId: status.account.id })
164
+ : undefined;
165
+ const deploymentFeatures = readAccountFeatures(deployment);
166
+ const features = resolveAccountFeatures({
167
+ deployment: deploymentFeatures,
168
+ account: status.state === "signed-in" ? readAccountFeatures(overview) : undefined,
169
+ signedIn: status.state === "signed-in",
170
+ // This CLI currently implements account-owned Link listing only.
171
+ supported: deployment.linkManagementEnabled === true ? ["links.remote"] : [],
172
+ });
173
+ if (action === "links") {
174
+ if (features["links.remote"].state !== "ready" || !status.account)
175
+ throw new Error(`Account Link list is ${features["links.remote"].state}. Sign in to a service with Link management enabled.`);
176
+ write(JSON.stringify(await manager.manageLinks({
177
+ action: "list",
178
+ origin: options.origin,
179
+ accountId: status.account.id,
180
+ }), null, 2));
181
+ }
182
+ else
183
+ write(JSON.stringify({ ...status, features }, null, 2));
184
+ };
185
+ try {
186
+ if (options.action === "register") {
187
+ await register();
188
+ return;
189
+ }
190
+ if (options.action === "login") {
191
+ if (options.github)
192
+ await manager.signInWithGitHub({ origin: options.origin, connectGitHub: false });
193
+ else
194
+ await manager.signIn("login", {
195
+ origin: options.origin,
196
+ username: options.username ?? (await prompt("Username: ")),
197
+ password: options.passwordStdin
198
+ ? await (deps.readPassword ?? stdinPassword)()
199
+ : await prompt("Password: ", true),
200
+ });
201
+ const signedIn = manager.status();
202
+ write(JSON.stringify(signedIn, null, 2));
203
+ if (signedIn.state !== "signed-in")
204
+ return;
205
+ try {
206
+ await display("features");
207
+ }
208
+ catch {
209
+ write("Signed in. Feature status could not be loaded; retry with account features.");
210
+ }
211
+ if (options.memory) {
212
+ write("Temporary account session active. Commands: status, features, links, logout, register, exit.");
213
+ while (true) {
214
+ const command = (await prompt("account> ")).trim();
215
+ if (command === "exit" || command === "logout")
216
+ break;
217
+ try {
218
+ if (command === "status" || command === "features" || command === "links")
219
+ await display(command);
220
+ else if (command === "register")
221
+ await register();
222
+ else
223
+ write("Commands: status, features, links, logout, register, exit");
224
+ }
225
+ catch {
226
+ write("Account operation could not finish. Retry features, links, status, or exit.");
227
+ }
228
+ }
229
+ }
230
+ }
231
+ else if (options.action === "logout")
232
+ write(JSON.stringify(await manager.logout()));
233
+ else
234
+ await display(options.action);
235
+ }
236
+ finally {
237
+ try {
238
+ if (options.memory)
239
+ await manager.logout();
240
+ }
241
+ finally {
242
+ manager.dispose();
243
+ }
244
+ }
245
+ }
246
+ finally {
247
+ await release?.();
248
+ }
249
+ }
@@ -0,0 +1,39 @@
1
+ import type { CloudManagementRequest, CloudProject } from "./cloud-types.js";
2
+ export declare const cloudToken: (value: unknown) => value is string;
3
+ export declare function cloudObject(value: unknown): Record<string, unknown>;
4
+ export declare function cloudId(value: unknown): string;
5
+ export declare function cloudOrigin(value: unknown): string;
6
+ export declare function cloudManagementRequest(value: unknown): CloudManagementRequest;
7
+ export declare function cloudProject(value: unknown): CloudProject;
8
+ export interface ManagedCloudGrant {
9
+ token: string;
10
+ session: {
11
+ id: string;
12
+ kind: "cloud-management";
13
+ audience: string;
14
+ installationId: string;
15
+ accountOrigin: string;
16
+ accountId: string;
17
+ managementEpoch: string;
18
+ expiresAt: number;
19
+ };
20
+ }
21
+ export interface CloudManagementTarget {
22
+ version: 2;
23
+ mode: "cloud-account";
24
+ cloudOrigin: string;
25
+ accountOrigin: string;
26
+ installationId: string;
27
+ }
28
+ export interface CloudManagementTicket {
29
+ ticket: string;
30
+ expiresAt: number;
31
+ cloudOrigin: string;
32
+ accountOrigin: string;
33
+ installationId: string;
34
+ }
35
+ export declare function cloudManagementTarget(value: unknown, input: CloudManagementRequest): CloudManagementTarget;
36
+ export declare function cloudManagementTicket(value: unknown, target: Omit<CloudManagementTarget, "version" | "mode">, now: number): CloudManagementTicket;
37
+ export declare function managedCloudGrant(value: unknown, input: CloudManagementRequest, now: number): ManagedCloudGrant;
38
+ /** Include the grant incarnation so an old cleanup cannot erase a new login's cookie. */
39
+ export declare function managedCloudPartition(grant: ManagedCloudGrant): string;
@@ -0,0 +1,134 @@
1
+ import { createHash } from "node:crypto";
2
+ import { isRelayOrigin } from "../remote-relay/protocol.js";
3
+ const UUID = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/;
4
+ export const cloudToken = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{43}$/.test(value);
5
+ const invalid = () => new Error("云端管理信息无效,请重新连接。");
6
+ export function cloudObject(value) {
7
+ if (!value || typeof value !== "object" || Array.isArray(value))
8
+ throw invalid();
9
+ return value;
10
+ }
11
+ export function cloudId(value) {
12
+ if (typeof value !== "string" || !UUID.test(value))
13
+ throw invalid();
14
+ return value;
15
+ }
16
+ function text(value, max) {
17
+ if (typeof value !== "string" ||
18
+ !value.length ||
19
+ value.length > max ||
20
+ /[\x00-\x1f\x7f]/.test(value))
21
+ throw invalid();
22
+ return value;
23
+ }
24
+ function time(value) {
25
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
26
+ throw invalid();
27
+ return value;
28
+ }
29
+ export function cloudOrigin(value) {
30
+ if (!isRelayOrigin(value) || value.length > 2048)
31
+ throw invalid();
32
+ return value;
33
+ }
34
+ export function cloudManagementRequest(value) {
35
+ const input = cloudObject(value);
36
+ const identity = {
37
+ accountOrigin: cloudOrigin(input.accountOrigin),
38
+ accountId: cloudId(input.accountId),
39
+ cloudOrigin: cloudOrigin(input.cloudOrigin),
40
+ };
41
+ let fields = ["accountOrigin", "accountId", "cloudOrigin", "action"];
42
+ if (input.action === "create")
43
+ fields = [...fields, "name"];
44
+ else if (["start", "stop", "open"].includes(input.action))
45
+ fields = [...fields, "projectId"];
46
+ else if (input.action !== "list" && input.action !== "logout")
47
+ throw invalid();
48
+ if (Object.keys(input).length !== fields.length || fields.some((key) => !(key in input)))
49
+ throw invalid();
50
+ if (input.action === "create") {
51
+ const name = text(input.name, 80).trim();
52
+ if (!name)
53
+ throw invalid();
54
+ return { ...identity, action: "create", name };
55
+ }
56
+ if (input.action === "list" || input.action === "logout")
57
+ return { ...identity, action: input.action };
58
+ return {
59
+ ...identity,
60
+ action: input.action,
61
+ projectId: cloudId(input.projectId),
62
+ };
63
+ }
64
+ export function cloudProject(value) {
65
+ const row = cloudObject(value);
66
+ if (!["stopped", "starting", "running", "stopping", "error"].includes(row.status))
67
+ throw invalid();
68
+ return {
69
+ id: cloudId(row.id),
70
+ name: text(row.name, 80),
71
+ status: row.status,
72
+ generation: time(row.generation),
73
+ createdAt: time(row.createdAt),
74
+ updatedAt: time(row.updatedAt),
75
+ ...(row.error === undefined ? {} : { error: text(row.error, 2048) }),
76
+ };
77
+ }
78
+ export function cloudManagementTarget(value, input) {
79
+ const row = cloudObject(value);
80
+ if (row.version !== 2 ||
81
+ row.mode !== "cloud-account" ||
82
+ row.cloudOrigin !== input.cloudOrigin ||
83
+ row.accountOrigin !== input.accountOrigin)
84
+ throw invalid();
85
+ return {
86
+ version: 2,
87
+ mode: "cloud-account",
88
+ cloudOrigin: input.cloudOrigin,
89
+ accountOrigin: input.accountOrigin,
90
+ installationId: cloudId(row.installationId),
91
+ };
92
+ }
93
+ export function cloudManagementTicket(value, target, now) {
94
+ const row = cloudObject(value);
95
+ if (!cloudToken(row.ticket) ||
96
+ row.cloudOrigin !== target.cloudOrigin ||
97
+ row.accountOrigin !== target.accountOrigin ||
98
+ row.installationId !== target.installationId ||
99
+ time(row.expiresAt) <= now ||
100
+ row.expiresAt > now + 60_000)
101
+ throw invalid();
102
+ return { ticket: row.ticket, expiresAt: row.expiresAt, ...target };
103
+ }
104
+ export function managedCloudGrant(value, input, now) {
105
+ const row = cloudObject(value), s = cloudObject(row.session);
106
+ if (!cloudToken(row.token) ||
107
+ s.kind !== "cloud-management" ||
108
+ s.audience !== input.cloudOrigin ||
109
+ s.accountOrigin !== input.accountOrigin ||
110
+ s.accountId !== input.accountId ||
111
+ time(s.expiresAt) <= now ||
112
+ s.expiresAt > now + 600_000)
113
+ throw invalid();
114
+ return {
115
+ token: row.token,
116
+ session: {
117
+ id: cloudId(s.id),
118
+ kind: "cloud-management",
119
+ audience: input.cloudOrigin,
120
+ installationId: cloudId(s.installationId),
121
+ accountOrigin: input.accountOrigin,
122
+ accountId: input.accountId,
123
+ managementEpoch: cloudId(s.managementEpoch),
124
+ expiresAt: s.expiresAt,
125
+ },
126
+ };
127
+ }
128
+ /** Include the grant incarnation so an old cleanup cannot erase a new login's cookie. */
129
+ export function managedCloudPartition(grant) {
130
+ const s = grant.session;
131
+ return `persist:codeshell-account-cloud-${createHash("sha256")
132
+ .update(JSON.stringify([s.audience, s.accountOrigin, s.accountId, s.managementEpoch, s.id]))
133
+ .digest("hex")}`;
134
+ }
@@ -0,0 +1,47 @@
1
+ /** Public Cloud management contract. All credentials and browser cookies remain in Main. */
2
+ export interface CloudProject {
3
+ id: string;
4
+ name: string;
5
+ status: "stopped" | "starting" | "running" | "stopping" | "error";
6
+ generation: number;
7
+ createdAt: number;
8
+ updatedAt: number;
9
+ error?: string;
10
+ }
11
+ export type CloudManagementRequest = {
12
+ accountOrigin: string;
13
+ accountId: string;
14
+ cloudOrigin: string;
15
+ } & ({
16
+ action: "list";
17
+ } | {
18
+ action: "logout";
19
+ } | {
20
+ action: "create";
21
+ name: string;
22
+ } | {
23
+ action: "start" | "stop" | "open";
24
+ projectId: string;
25
+ });
26
+ export type CloudManagementResult = {
27
+ action: "list";
28
+ available: boolean;
29
+ error?: string;
30
+ projects: CloudProject[];
31
+ } | {
32
+ action: "create" | "start" | "stop";
33
+ project: CloudProject;
34
+ } | {
35
+ action: "open";
36
+ project: CloudProject;
37
+ address: string;
38
+ } | {
39
+ action: "logout";
40
+ revoked: true;
41
+ };
42
+ export interface CloudWorkbenchApi {
43
+ (address: string): Promise<{
44
+ address: string;
45
+ }>;
46
+ (input: CloudManagementRequest): Promise<CloudManagementResult>;
47
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ import { type AccountFeatureId, type AccountFeatureSnapshot, type ResolveAccountFeaturesInput } from "@cjhyy/code-shell-web/account";
2
+ import { type CloudAccountStatus, type CloudAccountCapabilities, type CloudAccountOverview } from "./types.js";
3
+ export interface AccountFeatureAuthority {
4
+ status(): CloudAccountStatus;
5
+ onStatus(listener: (status: CloudAccountStatus) => void): () => void;
6
+ capabilities(origin: string, signal?: AbortSignal): Promise<CloudAccountCapabilities>;
7
+ overview(identity: {
8
+ origin: string;
9
+ accountId: string;
10
+ }, authorized?: () => boolean, signal?: AbortSignal): Promise<CloudAccountOverview>;
11
+ }
12
+ /** Host-owned cache, bound to an account generation. Snapshot reads never perform I/O. */
13
+ export declare class AccountFeatureService {
14
+ private readonly deps;
15
+ private origin;
16
+ private subject;
17
+ private generation;
18
+ private deployment?;
19
+ private account?;
20
+ private reachable;
21
+ private controller?;
22
+ private inFlight?;
23
+ private lastAttempt;
24
+ private timer?;
25
+ private disposed;
26
+ private readonly listeners;
27
+ private readonly unsubscribe;
28
+ constructor(deps: {
29
+ account: AccountFeatureAuthority;
30
+ origin?: string;
31
+ supported: readonly AccountFeatureId[];
32
+ local?: (status: CloudAccountStatus) => ResolveAccountFeaturesInput["local"];
33
+ now?: () => number;
34
+ });
35
+ private synchronize;
36
+ snapshot(): AccountFeatureSnapshot;
37
+ private resolve;
38
+ onChanged(listener: (snapshot: AccountFeatureSnapshot) => void): () => void;
39
+ private publish;
40
+ localChanged(): void;
41
+ /** Checks remote entitlement before a setup action, independent of its local prerequisite. */
42
+ requireEntitled(id: AccountFeatureId): void;
43
+ requireReady(id: AccountFeatureId): void;
44
+ private requireView;
45
+ refresh(origin?: string, minIntervalMs?: number): Promise<AccountFeatureSnapshot>;
46
+ dispose(): void;
47
+ }