@cjhyy/code-shell-host 0.0.0-stage → 0.9.44

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 CHANGED
@@ -1,3 +1,26 @@
1
- # Temporary Holding Version
1
+ # CodeShell Host
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Minimal HostRuntime foundation shared by Desktop, serve and Panel task workers.
4
+ Depends only on Core. The registry owns capability declarations, legacy worker
5
+ request classification and dispatch, and synchronous adapter/service lifecycle.
6
+ Adapters supply platform handlers; no Electron, transport or coding pack is loaded.
7
+
8
+ `interceptHostRequest` consumes unsupported worker requests before client routing.
9
+ Supported handlers keep their existing wire format. Ordinary approvals keep the
10
+ host client approval path; AskUser requires a real text-answer UI. Desktop/Web
11
+ retain that path, while Panel task hosts immediately return a typed `unavailable`
12
+ decision with an explicit unsupported reason. They never turn a yes/no confirmation
13
+ into a user answer; Core retires asynchronous questions without injecting an answer
14
+ or starting a follow-up run. This package does not implement
15
+ Protocol v2, credential storage, automation scheduling or the later service moves.
16
+
17
+ Desktop bundles this package into its main process, so its dependency belongs in
18
+ Desktop `devDependencies`. The production workspace materialization list used by
19
+ `predist` also drives the packaging guard; bundled Host is not copied as a runtime
20
+ workspace symlink.
21
+
22
+ Panel workers use `CODE_SHELL_CREDENTIAL_ACCESS=local`: they read Core's local
23
+ credential store, not Desktop's credential IPC/safeStorage service. Foreign
24
+ safeStorage ciphertext fails closed; supported plaintext tags and legacy bare
25
+ plaintext remain readable under the existing local store rules. This is not
26
+ credential-service parity or a separate per-task vault.
@@ -0,0 +1,49 @@
1
+ import type { HostRuntime } from "./runtime.js";
2
+ export interface ServeHostCapabilities {
3
+ panels: boolean;
4
+ }
5
+ /** Compatibility surface for S0 callers. */
6
+ export declare function unsupportedLoopbackCapability(toolName: unknown, host: ServeHostCapabilities): string | undefined;
7
+ export declare function unsupportedLoopbackAnswer(capability: string, hostLabel?: string): string;
8
+ /** AskUser expects human text, so an unsupported host must never approve a synthetic answer. */
9
+ export declare function unsupportedAskUserDecision(hostLabel: string): {
10
+ approved: false;
11
+ failure: "unavailable";
12
+ reason: string;
13
+ };
14
+ export declare function isHostAuthorityRequest(message: {
15
+ id?: unknown;
16
+ method?: unknown;
17
+ }): message is {
18
+ id: string | number;
19
+ method: string;
20
+ };
21
+ export declare function unsupportedHostRequestResponse(id: string | number, method: string, hostLabel?: string): string;
22
+ export interface HostApproval {
23
+ sessionId: string;
24
+ requestId: string;
25
+ connectionId?: unknown;
26
+ generation?: unknown;
27
+ request?: {
28
+ toolName?: unknown;
29
+ args?: Record<string, unknown>;
30
+ };
31
+ childHost?: {
32
+ activate?: unknown;
33
+ release?: unknown;
34
+ };
35
+ }
36
+ export interface HostRequest {
37
+ kind: "authority" | "loopback";
38
+ capability: string;
39
+ message: Record<string, unknown>;
40
+ line: string;
41
+ approval?: HostApproval;
42
+ }
43
+ export declare function classifyHostRequest(message: unknown, line: string): HostRequest | undefined;
44
+ /** Consume private worker requests before they enter untrusted client routing. */
45
+ export declare function interceptHostRequest(message: unknown, line: string, options: {
46
+ runtime: HostRuntime;
47
+ sendLine(line: string): void;
48
+ approve(params: Record<string, unknown>): void;
49
+ }): boolean;
@@ -0,0 +1,98 @@
1
+ const LOOPBACKS = Object.freeze({
2
+ __browser_action__: "browser",
3
+ __credential_action__: "credential.browserCookie",
4
+ __workspace_action__: "workspace.switch",
5
+ __panel_action__: "panel",
6
+ });
7
+ function loopbackCapability(toolName) {
8
+ if (typeof toolName !== "string" || toolName === "__ask_user__")
9
+ return undefined;
10
+ if (Object.hasOwn(LOOPBACKS, toolName))
11
+ return LOOPBACKS[toolName];
12
+ return toolName.startsWith("__") ? toolName : undefined;
13
+ }
14
+ function unsupportedText(capability, hostLabel) {
15
+ return `${capability} is not supported by this host (${hostLabel})`;
16
+ }
17
+ /** Compatibility surface for S0 callers. */
18
+ export function unsupportedLoopbackCapability(toolName, host) {
19
+ const capability = loopbackCapability(toolName);
20
+ return capability === "panel" && host.panels ? undefined : capability;
21
+ }
22
+ export function unsupportedLoopbackAnswer(capability, hostLabel = "headless serve") {
23
+ const text = unsupportedText(capability, hostLabel);
24
+ return JSON.stringify({
25
+ ok: false,
26
+ failure: "unsupported",
27
+ capability,
28
+ error: text,
29
+ detail: text,
30
+ });
31
+ }
32
+ /** AskUser expects human text, so an unsupported host must never approve a synthetic answer. */
33
+ export function unsupportedAskUserDecision(hostLabel) {
34
+ return {
35
+ approved: false,
36
+ failure: "unavailable",
37
+ reason: unsupportedText("user.ask", hostLabel),
38
+ };
39
+ }
40
+ export function isHostAuthorityRequest(message) {
41
+ return ((typeof message.id === "string" || typeof message.id === "number") &&
42
+ typeof message.method === "string" &&
43
+ message.method.startsWith("desktop/"));
44
+ }
45
+ export function unsupportedHostRequestResponse(id, method, hostLabel = "headless serve") {
46
+ return JSON.stringify({
47
+ jsonrpc: "2.0",
48
+ id,
49
+ error: {
50
+ code: -32601,
51
+ message: unsupportedText(method, hostLabel),
52
+ data: { failure: "unsupported", capability: method },
53
+ },
54
+ });
55
+ }
56
+ export function classifyHostRequest(message, line) {
57
+ if (!message || typeof message !== "object" || Array.isArray(message))
58
+ return undefined;
59
+ const frame = message;
60
+ if (isHostAuthorityRequest(frame))
61
+ return { kind: "authority", capability: frame.method, message: frame, line };
62
+ if (frame.method !== "agent/approvalRequest")
63
+ return undefined;
64
+ const approval = frame.params;
65
+ if (!approval || typeof approval.requestId !== "string" || typeof approval.sessionId !== "string")
66
+ return undefined;
67
+ const capability = loopbackCapability(approval.request?.toolName);
68
+ return capability ? { kind: "loopback", capability, message: frame, line, approval } : undefined;
69
+ }
70
+ /** Consume private worker requests before they enter untrusted client routing. */
71
+ export function interceptHostRequest(message, line, options) {
72
+ const request = classifyHostRequest(message, line);
73
+ if (!request)
74
+ return false;
75
+ if (options.runtime.supports(request.capability))
76
+ return options.runtime.dispatch(request);
77
+ if (request.kind === "authority") {
78
+ const frame = request.message;
79
+ options.sendLine(unsupportedHostRequestResponse(frame.id, frame.method, options.runtime.adapter.label));
80
+ }
81
+ else {
82
+ const approval = request.approval;
83
+ // Child browser lifetime frames are one-way; no pending approval exists.
84
+ if (approval.childHost?.activate === true || approval.childHost?.release === true)
85
+ return true;
86
+ options.approve({
87
+ sessionId: approval.sessionId,
88
+ requestId: approval.requestId,
89
+ connectionId: approval.connectionId,
90
+ generation: approval.generation,
91
+ decision: {
92
+ approved: true,
93
+ answer: unsupportedLoopbackAnswer(request.capability, options.runtime.adapter.label),
94
+ },
95
+ });
96
+ }
97
+ return true;
98
+ }
@@ -0,0 +1,2 @@
1
+ export { HostRuntime, type HostAdapter, type HostService } from "./runtime.js";
2
+ export { classifyHostRequest, interceptHostRequest, isHostAuthorityRequest, unsupportedAskUserDecision, unsupportedHostRequestResponse, unsupportedLoopbackAnswer, unsupportedLoopbackCapability, type HostApproval, type HostRequest, type ServeHostCapabilities, } from "./host-requests.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { HostRuntime } from "./runtime.js";
2
+ export { classifyHostRequest, interceptHostRequest, isHostAuthorityRequest, unsupportedAskUserDecision, unsupportedHostRequestResponse, unsupportedLoopbackAnswer, unsupportedLoopbackCapability, } from "./host-requests.js";
@@ -0,0 +1,34 @@
1
+ import type { Contracts } from "@cjhyy/code-shell-core";
2
+ import type { HostRequest } from "./host-requests.js";
3
+ /** Platform mechanisms stay in the adapter; services do not import Electron. */
4
+ export interface HostAdapter {
5
+ kind: "electron" | "headless" | "container";
6
+ label: string;
7
+ id?: string;
8
+ contracts?: Contracts;
9
+ start?(): void;
10
+ stop?(): void;
11
+ }
12
+ export interface HostService {
13
+ id: string;
14
+ capabilities: readonly string[];
15
+ /** Missing handlers mean the adapter's existing downstream route owns the request. */
16
+ hostRequests?: Readonly<Record<string, (request: HostRequest) => boolean>>;
17
+ start?(adapter: HostAdapter): void;
18
+ stop?(): void;
19
+ }
20
+ /** Step 1.1 registry. Large services and the v2 client transport migrate separately. */
21
+ export declare class HostRuntime {
22
+ readonly adapter: HostAdapter;
23
+ private readonly services;
24
+ private readonly owners;
25
+ private readonly started;
26
+ private state;
27
+ constructor(adapter: HostAdapter, services?: readonly HostService[]);
28
+ register(service: HostService): void;
29
+ start(): void;
30
+ stop(): void;
31
+ capabilities(): string[];
32
+ supports(capability: string): boolean;
33
+ dispatch(request: HostRequest): boolean;
34
+ }
@@ -0,0 +1,101 @@
1
+ /** Step 1.1 registry. Large services and the v2 client transport migrate separately. */
2
+ export class HostRuntime {
3
+ adapter;
4
+ services = new Map();
5
+ owners = new Map();
6
+ started = [];
7
+ state = "created";
8
+ constructor(adapter, services = []) {
9
+ this.adapter = adapter;
10
+ for (const service of services)
11
+ this.register(service);
12
+ }
13
+ register(service) {
14
+ if (this.state !== "created")
15
+ throw new Error("Host services must register before start");
16
+ if (!service.id || this.services.has(service.id))
17
+ throw new Error("Duplicate host service");
18
+ const capabilities = new Set();
19
+ for (const capability of service.capabilities) {
20
+ if (!capability || capabilities.has(capability) || this.owners.has(capability))
21
+ throw new Error(`Duplicate host capability: ${capability}`);
22
+ capabilities.add(capability);
23
+ }
24
+ for (const capability of Object.keys(service.hostRequests ?? {})) {
25
+ if (!service.capabilities.includes(capability))
26
+ throw new Error(`Undeclared host capability: ${capability}`);
27
+ }
28
+ this.services.set(service.id, service);
29
+ for (const capability of service.capabilities)
30
+ this.owners.set(capability, service);
31
+ }
32
+ start() {
33
+ if (this.state === "running")
34
+ return;
35
+ if (this.state === "stopped")
36
+ throw new Error("Host runtime is stopped");
37
+ if (this.state === "starting")
38
+ throw new Error("Host runtime is already starting");
39
+ this.state = "starting";
40
+ try {
41
+ this.adapter.start?.();
42
+ if (this.state !== "starting")
43
+ throw new Error("Host runtime stopped during start");
44
+ for (const service of this.services.values()) {
45
+ this.started.push(service);
46
+ service.start?.(this.adapter);
47
+ if (this.state !== "starting")
48
+ throw new Error("Host runtime stopped during start");
49
+ }
50
+ this.state = "running";
51
+ }
52
+ catch (error) {
53
+ let cleanupErrors;
54
+ try {
55
+ this.stop();
56
+ }
57
+ catch (cleanup) {
58
+ cleanupErrors = cleanup instanceof AggregateError ? cleanup.errors : [cleanup];
59
+ }
60
+ if (cleanupErrors) {
61
+ throw new AggregateError([error, ...cleanupErrors], "Host startup and rollback failed", {
62
+ cause: error,
63
+ });
64
+ }
65
+ throw error;
66
+ }
67
+ }
68
+ stop() {
69
+ if (this.state === "stopped")
70
+ return;
71
+ this.state = "stopped";
72
+ const errors = [];
73
+ for (const service of this.started.splice(0).reverse()) {
74
+ try {
75
+ service.stop?.();
76
+ }
77
+ catch (error) {
78
+ errors.push(error);
79
+ }
80
+ }
81
+ try {
82
+ this.adapter.stop?.();
83
+ }
84
+ catch (error) {
85
+ errors.push(error);
86
+ }
87
+ if (errors.length)
88
+ throw new AggregateError(errors, "Host shutdown failed");
89
+ }
90
+ capabilities() {
91
+ return [...this.owners.keys()];
92
+ }
93
+ supports(capability) {
94
+ return this.state === "running" && this.owners.has(capability);
95
+ }
96
+ dispatch(request) {
97
+ if (!this.supports(request.capability))
98
+ return false;
99
+ return (this.owners.get(request.capability)?.hostRequests?.[request.capability]?.(request) ?? false);
100
+ }
101
+ }
package/package.json CHANGED
@@ -1,6 +1,38 @@
1
1
  {
2
2
  "name": "@cjhyy/code-shell-host",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.9.44",
4
+ "description": "Host-neutral capability registry and worker request dispatch for CodeShell hosts.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "bun run clean && tsc -p tsconfig.json",
20
+ "typecheck": "tsc -p tsconfig.json --noEmit",
21
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
22
+ },
23
+ "dependencies": {
24
+ "@cjhyy/code-shell-core": "0.9.44"
25
+ },
26
+ "engines": {
27
+ "node": ">=20.10"
28
+ },
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/cjhyy/codeshell.git",
32
+ "directory": "packages/host"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "license": "MIT"
38
+ }