@cjhyy/code-shell-core 0.8.6 → 0.8.7
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/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/panel-apps/manifest.d.ts +11 -11
- package/dist/panel-apps/manifest.js +12 -1
- package/dist/prompt/sections/browser.md +2 -2
- package/dist/protocol/server.js +1 -0
- package/dist/tool-system/browser-bridge.d.ts +3 -0
- package/dist/tool-system/builtin/browser-tools.js +13 -0
- package/dist/tool-system/path-policy.js +84 -5
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Public API exports.
|
|
5
5
|
*/
|
|
6
|
-
export declare const VERSION = "0.8.
|
|
6
|
+
export declare const VERSION = "0.8.7";
|
|
7
7
|
export type { Message, ContentBlock, ToolDefinition, ToolCall, ToolResult, RegisteredTool, TranscriptEvent, TranscriptEventType, SessionState, SessionKind, SessionWorkspace, SessionForkLineage, ContextUsageAnchor, SessionStatus, TokenUsage, CompiledInput, PermissionDecision, PermissionMode, PermissionRule, TurnPhase, TurnResult, TerminalReason, TurnCompletionKind, StreamEvent, StreamCallback, LLMConfig, ClientDefaults, LLMResponse, Settings, MCPServerConfig, } from "./types.js";
|
|
8
8
|
export type { GoalConfig, GoalLifecycleConfig, GoalLifecyclePhase, GoalLifecycleTerminalReason, GoalLifecycleV1, } from "./goal/lifecycle.js";
|
|
9
9
|
export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, SandboxUnavailableError, } from "./exceptions.js";
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Public API exports.
|
|
5
5
|
*/
|
|
6
|
-
export const VERSION = "0.8.
|
|
6
|
+
export const VERSION = "0.8.7";
|
|
7
7
|
// ─── Exceptions ──────────────────────────────────────────────────
|
|
8
8
|
export { FrameworkError, LLMError, LLMRateLimitError, ContextLimitError, ToolError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError, PermissionDeniedError, SessionError, TranscriptError, ConfigError, SandboxUnavailableError, } from "./exceptions.js";
|
|
9
9
|
// ─── Engine (primary API) ────────────────────────────────────────
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const PANEL_APP_MANIFEST_FILE = ".codeshell-panel/panel.json";
|
|
3
|
-
export declare const PANEL_APP_PERMISSIONS: readonly ["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send"];
|
|
3
|
+
export declare const PANEL_APP_PERMISSIONS: readonly ["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send", "credentials.cookies", "automations.manage"];
|
|
4
4
|
export declare const PANEL_APP_ICONS: readonly ["panel", "activity", "bar-chart-3", "chart", "file-text", "globe", "image", "layout-dashboard", "line-chart", "palette", "pie-chart", "table", "terminal"];
|
|
5
5
|
export declare const PanelAppAgentTool: z.ZodEffects<z.ZodObject<{
|
|
6
6
|
name: z.ZodString;
|
|
@@ -116,12 +116,12 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
116
116
|
icon: z.ZodDefault<z.ZodEnum<["panel", "activity", "bar-chart-3", "chart", "file-text", "globe", "image", "layout-dashboard", "line-chart", "palette", "pie-chart", "table", "terminal"]>>;
|
|
117
117
|
placement: z.ZodDefault<z.ZodLiteral<"right-dock">>;
|
|
118
118
|
singleton: z.ZodDefault<z.ZodBoolean>;
|
|
119
|
-
permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send"]>, "many">>;
|
|
119
|
+
permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send", "credentials.cookies", "automations.manage"]>, "many">>;
|
|
120
120
|
schemaVersion: z.ZodLiteral<1>;
|
|
121
121
|
}, "strict", z.ZodTypeAny, {
|
|
122
122
|
id: string;
|
|
123
123
|
version: string;
|
|
124
|
-
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[];
|
|
124
|
+
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[];
|
|
125
125
|
entry: string;
|
|
126
126
|
title: {
|
|
127
127
|
default: string;
|
|
@@ -144,7 +144,7 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
144
144
|
};
|
|
145
145
|
schemaVersion: 1;
|
|
146
146
|
description?: string | undefined;
|
|
147
|
-
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[] | undefined;
|
|
147
|
+
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[] | undefined;
|
|
148
148
|
icon?: "terminal" | "image" | "panel" | "activity" | "bar-chart-3" | "chart" | "file-text" | "globe" | "layout-dashboard" | "line-chart" | "palette" | "pie-chart" | "table" | undefined;
|
|
149
149
|
placement?: "right-dock" | undefined;
|
|
150
150
|
singleton?: boolean | undefined;
|
|
@@ -230,12 +230,12 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
230
230
|
icon: z.ZodDefault<z.ZodEnum<["panel", "activity", "bar-chart-3", "chart", "file-text", "globe", "image", "layout-dashboard", "line-chart", "palette", "pie-chart", "table", "terminal"]>>;
|
|
231
231
|
placement: z.ZodDefault<z.ZodLiteral<"right-dock">>;
|
|
232
232
|
singleton: z.ZodDefault<z.ZodBoolean>;
|
|
233
|
-
permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send"]>, "many">>;
|
|
233
|
+
permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<["context.session", "context.workspace", "storage", "external.open", "agent.submitPrompt", "workspace.info", "workspace.read", "workspace.write", "notifications.send", "credentials.cookies", "automations.manage"]>, "many">>;
|
|
234
234
|
schemaVersion: z.ZodLiteral<2>;
|
|
235
235
|
}, "strict", z.ZodTypeAny, {
|
|
236
236
|
id: string;
|
|
237
237
|
version: string;
|
|
238
|
-
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[];
|
|
238
|
+
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[];
|
|
239
239
|
entry: string;
|
|
240
240
|
title: {
|
|
241
241
|
default: string;
|
|
@@ -276,14 +276,14 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
276
276
|
}[] | undefined;
|
|
277
277
|
} | undefined;
|
|
278
278
|
description?: string | undefined;
|
|
279
|
-
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[] | undefined;
|
|
279
|
+
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[] | undefined;
|
|
280
280
|
icon?: "terminal" | "image" | "panel" | "activity" | "bar-chart-3" | "chart" | "file-text" | "globe" | "layout-dashboard" | "line-chart" | "palette" | "pie-chart" | "table" | undefined;
|
|
281
281
|
placement?: "right-dock" | undefined;
|
|
282
282
|
singleton?: boolean | undefined;
|
|
283
283
|
}>]>, {
|
|
284
284
|
id: string;
|
|
285
285
|
version: string;
|
|
286
|
-
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[];
|
|
286
|
+
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[];
|
|
287
287
|
entry: string;
|
|
288
288
|
title: {
|
|
289
289
|
default: string;
|
|
@@ -298,7 +298,7 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
298
298
|
} | {
|
|
299
299
|
id: string;
|
|
300
300
|
version: string;
|
|
301
|
-
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[];
|
|
301
|
+
permissions: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[];
|
|
302
302
|
entry: string;
|
|
303
303
|
title: {
|
|
304
304
|
default: string;
|
|
@@ -330,7 +330,7 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
330
330
|
};
|
|
331
331
|
schemaVersion: 1;
|
|
332
332
|
description?: string | undefined;
|
|
333
|
-
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[] | undefined;
|
|
333
|
+
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[] | undefined;
|
|
334
334
|
icon?: "terminal" | "image" | "panel" | "activity" | "bar-chart-3" | "chart" | "file-text" | "globe" | "layout-dashboard" | "line-chart" | "palette" | "pie-chart" | "table" | undefined;
|
|
335
335
|
placement?: "right-dock" | undefined;
|
|
336
336
|
singleton?: boolean | undefined;
|
|
@@ -354,7 +354,7 @@ export declare const PanelAppManifest: z.ZodEffects<z.ZodDiscriminatedUnion<"sch
|
|
|
354
354
|
}[] | undefined;
|
|
355
355
|
} | undefined;
|
|
356
356
|
description?: string | undefined;
|
|
357
|
-
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send")[] | undefined;
|
|
357
|
+
permissions?: ("context.session" | "context.workspace" | "storage" | "external.open" | "agent.submitPrompt" | "workspace.info" | "workspace.read" | "workspace.write" | "notifications.send" | "credentials.cookies" | "automations.manage")[] | undefined;
|
|
358
358
|
icon?: "terminal" | "image" | "panel" | "activity" | "bar-chart-3" | "chart" | "file-text" | "globe" | "layout-dashboard" | "line-chart" | "palette" | "pie-chart" | "table" | undefined;
|
|
359
359
|
placement?: "right-dock" | undefined;
|
|
360
360
|
singleton?: boolean | undefined;
|
|
@@ -11,6 +11,8 @@ export const PANEL_APP_PERMISSIONS = [
|
|
|
11
11
|
"workspace.read",
|
|
12
12
|
"workspace.write",
|
|
13
13
|
"notifications.send",
|
|
14
|
+
"credentials.cookies",
|
|
15
|
+
"automations.manage",
|
|
14
16
|
];
|
|
15
17
|
export const PANEL_APP_ICONS = [
|
|
16
18
|
"panel",
|
|
@@ -136,7 +138,7 @@ const PanelAppManifestFields = {
|
|
|
136
138
|
icon: z.enum(PANEL_APP_ICONS).default("panel"),
|
|
137
139
|
placement: z.literal("right-dock").default("right-dock"),
|
|
138
140
|
singleton: z.boolean().default(true),
|
|
139
|
-
permissions: z.array(z.enum(PANEL_APP_PERMISSIONS)).max(
|
|
141
|
+
permissions: z.array(z.enum(PANEL_APP_PERMISSIONS)).max(12).default([]),
|
|
140
142
|
};
|
|
141
143
|
/**
|
|
142
144
|
* Schema v2 keeps one installable Panel App identity while allowing it to
|
|
@@ -186,4 +188,13 @@ export const PanelAppManifest = z
|
|
|
186
188
|
message: "workspace.read and workspace.write require context.workspace",
|
|
187
189
|
});
|
|
188
190
|
}
|
|
191
|
+
if (value.permissions.includes("automations.manage") &&
|
|
192
|
+
(!value.permissions.includes("context.session") ||
|
|
193
|
+
!value.permissions.includes("context.workspace"))) {
|
|
194
|
+
ctx.addIssue({
|
|
195
|
+
code: z.ZodIssueCode.custom,
|
|
196
|
+
path: ["permissions"],
|
|
197
|
+
message: "automations.manage requires context.session and context.workspace",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
189
200
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
## Browser automation (browser_observe / browser_act / browser_navigate)
|
|
2
2
|
|
|
3
3
|
- When a task needs you to operate a real web page — search a site, open a result, read/extract an article, fill a form — use the three browser tools. They drive a task-owned background tab in CodeShell's in-app browser profile. The tab can share that profile's login state, but it never controls a user-opened tab unless the user explicitly grants that exact tab. The user's regular Chrome profile is available only through an explicit Chrome extension grant.
|
|
4
|
-
- The Browser Runtime starts lazily in the background. Call `browser_navigate{url}`, then `browser_act{action:"wait"}` and `browser_observe`. If login, 2FA, or a high-consequence action needs the user
|
|
4
|
+
- The Browser Runtime starts lazily in the background. Call `browser_navigate{url}`, then `browser_act{action:"wait"}` and `browser_observe`. If login, 2FA, CAPTCHA, or a high-consequence action needs the user—or the user explicitly asks to see the page—call `browser_act{action:"request_takeover"}`. CodeShell reveals the exact same runtime-owned in-app target; do not send a second link and claim it is the page you operated. Scheduled and explicitly isolated work may use a separate Dedicated Playwright profile instead.
|
|
5
5
|
- The loop is observe → act → re-observe: ALWAYS `browser_observe` first (default mode `snapshot`) to see the page's interactive elements (each tagged `[ref=eN]`), then `browser_act` on elements BY THAT ref: `{action:"click",ref}`, `{action:"type",ref,text}`, `{action:"select",ref,value}` (native `<select>`), `{action:"hover",ref}`. Refs are only valid for the most recent snapshot — after any navigation, content-loading click, or `{action:"scroll"}`, run `{action:"wait"}` then `browser_observe` again. If an action says a ref is stale, re-observe.
|
|
6
6
|
- To submit a search: `browser_act{action:"type",ref,text}` into the search box, then `browser_act{action:"press_key",key:"Enter"}`, then `{action:"wait"}` + `browser_observe`. `press_key` also does Tab / Escape / arrows / combos like `Control+a`.
|
|
7
7
|
- To extract/summarize page content, navigate/click to it, `browser_act{action:"wait"}`, then `browser_observe{mode:"read"}`. If it returns `nextCursor`, continue with `browser_observe{mode:"read",cursor:"..."}` until `Read: complete`. Do NOT scroll merely to obtain the next text chunk. Use scroll only to load lazy/infinite content; stop when it reports `NO_PROGRESS` or `(end)`. For real link/image/video URLs, use `browser_observe{mode:"extract"}` (media is tagged [ref=imgN/vidN]).
|
|
8
8
|
- To SEE an actual image's content (e.g. a 小红书 笔记配图 or product photo), `browser_observe{mode:"extract"}` to get image refs, then `browser_observe{mode:"image", refs:["img3"]}` — it loads the real pixels (works behind hotlink protection). A vidN ref grabs the video's current frame. To see the rendered layout/canvas/chart, `browser_observe{mode:"vision"}` (optionally `ref` for one region). Use images sparingly — they cost tokens; prefer snapshot/read. (Both need a vision-capable model; on a non-vision model they're skipped.) Do NOT repeat `vision` on the same page hoping for a clearer view — one screenshot is all you get. If snapshot/screenshot don't reveal what you need (infinite-scroll/canvas feeds), scroll + re-observe or extract the URLs and act on them — don't loop screenshots.
|
|
9
9
|
- Multiple tabs: `browser_act{action:"list_tabs"}` shows open tabs (tabId/url/title/active); `browser_act{action:"switch_tab", tabId}` makes another the active one (then re-observe). Any action also accepts `tabId` to target a specific tab (it switches first). Refs are per-tab — re-observe after switching.
|
|
10
|
-
- If `browser_observe` reports a sign-in is required (or you hit a login wall / 2FA), STOP and ask the user to
|
|
10
|
+
- If `browser_observe` reports a sign-in is required (or you hit a login wall / 2FA / CAPTCHA), call `browser_act{action:"request_takeover"}`, STOP, and ask the user to act in the revealed Browser Runtime window. Continue only after the user finishes; do not attempt to enter credentials yourself. Sensitive actions (payment, delete, entering card/password values) require user approval.
|
|
11
11
|
- If the tools report that no Browser Runtime is available, the current host does not provide browser automation — say so rather than retrying.
|
package/dist/protocol/server.js
CHANGED
|
@@ -2790,6 +2790,7 @@ export class AgentServer {
|
|
|
2790
2790
|
makeBrowserBridge(session, sessionId) {
|
|
2791
2791
|
const call = (action, payload) => this.requestBrowserActionForSession(session, sessionId, action, payload);
|
|
2792
2792
|
return {
|
|
2793
|
+
requestHumanTakeover: () => call("requestTakeover", {}),
|
|
2793
2794
|
snapshot: () => call("snapshot", {}),
|
|
2794
2795
|
click: (ref) => call("click", { ref }),
|
|
2795
2796
|
type: (ref, text) => call("type", { ref, text }),
|
|
@@ -145,6 +145,9 @@ export interface BrowserImageData {
|
|
|
145
145
|
detail?: string;
|
|
146
146
|
}
|
|
147
147
|
export interface BrowserBridge {
|
|
148
|
+
/** Reveal the exact task-owned browser target so the user can complete a
|
|
149
|
+
* login, 2FA, CAPTCHA, or another interaction that requires human control. */
|
|
150
|
+
requestHumanTakeover?(): Promise<BrowserResult>;
|
|
148
151
|
snapshot(): Promise<BrowserSnapshot>;
|
|
149
152
|
click(ref: string): Promise<BrowserResult>;
|
|
150
153
|
type(ref: string, text: string): Promise<BrowserResult>;
|
|
@@ -209,6 +209,9 @@ export const browserActToolDef = {
|
|
|
209
209
|
"- hover {ref}: hover to reveal menus/tooltips.\n" +
|
|
210
210
|
"- scroll {direction: up|down, amount?}: scroll the page, then re-observe.\n" +
|
|
211
211
|
"- wait {timeout_ms?}: wait for the page to finish loading before observing.\n" +
|
|
212
|
+
"- request_takeover: reveal the exact task-owned Browser Runtime page so the " +
|
|
213
|
+
"user can see it and complete login, 2FA, CAPTCHA, or another required manual step. " +
|
|
214
|
+
"Use only when the user asks to see the page or human interaction is required.\n" +
|
|
212
215
|
"- list_tabs: list open browser tabs (tabId, url, title, which is active).\n" +
|
|
213
216
|
"- switch_tab {tabId}: make another tab the active one that actions drive.\n" +
|
|
214
217
|
"Pass tabId on any action to target a specific tab (switches to it first). " +
|
|
@@ -226,6 +229,7 @@ export const browserActToolDef = {
|
|
|
226
229
|
"hover",
|
|
227
230
|
"scroll",
|
|
228
231
|
"wait",
|
|
232
|
+
"request_takeover",
|
|
229
233
|
"list_tabs",
|
|
230
234
|
"switch_tab",
|
|
231
235
|
],
|
|
@@ -260,6 +264,15 @@ export async function browserActTool(args, ctx) {
|
|
|
260
264
|
return `Error: could not switch to tab ${tabId} — ${sw.detail ?? "not found"}`;
|
|
261
265
|
}
|
|
262
266
|
switch (action) {
|
|
267
|
+
case "request_takeover": {
|
|
268
|
+
if (!b.requestHumanTakeover) {
|
|
269
|
+
return "Error: this Browser Runtime cannot reveal its page for user takeover";
|
|
270
|
+
}
|
|
271
|
+
const r = await b.requestHumanTakeover();
|
|
272
|
+
return r.ok
|
|
273
|
+
? `Browser Runtime is visible for user takeover${r.detail ? ` — ${r.detail}` : ""}`
|
|
274
|
+
: `Error: ${r.detail ?? "could not reveal Browser Runtime"}`;
|
|
275
|
+
}
|
|
263
276
|
case "list_tabs": {
|
|
264
277
|
const tabs = await b.listTabs();
|
|
265
278
|
if (tabs.length === 0)
|
|
@@ -432,9 +432,10 @@ function isSkillTreeResource(resolved, skillsRoot) {
|
|
|
432
432
|
*
|
|
433
433
|
* Keep the exception narrow:
|
|
434
434
|
* - read-only (the caller checks the operation);
|
|
435
|
-
* - user Skills,
|
|
435
|
+
* - user Skills, an installed plugin recorded in the V2 registry, or a
|
|
436
|
+
* declared Skill in the installed Panel App registry;
|
|
436
437
|
* - plugin installs must realpath beneath the managed plugin cache;
|
|
437
|
-
* - the target must remain inside
|
|
438
|
+
* - the target must remain inside the registered Skill directory.
|
|
438
439
|
*/
|
|
439
440
|
function isRegisteredSkillResourceRead(resolved) {
|
|
440
441
|
let codeShellRoot;
|
|
@@ -448,6 +449,8 @@ function isRegisteredSkillResourceRead(resolved) {
|
|
|
448
449
|
return false;
|
|
449
450
|
if (isSkillTreeResource(resolved, join(codeShellRoot, "skills")))
|
|
450
451
|
return true;
|
|
452
|
+
if (isInstalledPanelAppSkillResourceRead(resolved, codeShellRoot))
|
|
453
|
+
return true;
|
|
451
454
|
let cacheRoot;
|
|
452
455
|
try {
|
|
453
456
|
cacheRoot = realpathSync(join(codeShellRoot, "plugins", "cache"));
|
|
@@ -475,6 +478,84 @@ function isRegisteredSkillResourceRead(resolved) {
|
|
|
475
478
|
}
|
|
476
479
|
return false;
|
|
477
480
|
}
|
|
481
|
+
/**
|
|
482
|
+
* Panel App Skills live under the otherwise-sensitive
|
|
483
|
+
* `~/.code-shell/panel-apps` tree. Installation already reviews and copies the
|
|
484
|
+
* package, and the Skill scanner exposes only entries declared by the app
|
|
485
|
+
* manifest. Mirror that exact boundary here so reading a Skill reference does
|
|
486
|
+
* not trigger a second approval prompt.
|
|
487
|
+
*
|
|
488
|
+
* Registry, app root, manifest, declared SKILL.md and target are all
|
|
489
|
+
* realpathed and containment-checked. This deliberately does not trust an
|
|
490
|
+
* undeclared Skill directory or a symlink escaping the installed app.
|
|
491
|
+
*/
|
|
492
|
+
function isInstalledPanelAppSkillResourceRead(resolved, codeShellRoot) {
|
|
493
|
+
let appsRoot;
|
|
494
|
+
let registryPath;
|
|
495
|
+
try {
|
|
496
|
+
appsRoot = realpathSync(join(codeShellRoot, "panel-apps"));
|
|
497
|
+
if (!isInsideDir(appsRoot, codeShellRoot))
|
|
498
|
+
return false;
|
|
499
|
+
registryPath = realpathSync(join(appsRoot, "installed.json"));
|
|
500
|
+
if (!isInsideDir(registryPath, appsRoot))
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
let registry;
|
|
507
|
+
try {
|
|
508
|
+
registry = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
if (registry.version !== 1 || !Array.isArray(registry.apps))
|
|
514
|
+
return false;
|
|
515
|
+
for (const rawEntry of registry.apps) {
|
|
516
|
+
if (!rawEntry || typeof rawEntry !== "object")
|
|
517
|
+
continue;
|
|
518
|
+
const id = rawEntry.id;
|
|
519
|
+
if (typeof id !== "string" || !/^[a-z][a-z0-9-]{0,63}$/.test(id))
|
|
520
|
+
continue;
|
|
521
|
+
try {
|
|
522
|
+
const appRoot = realpathSync(join(appsRoot, id));
|
|
523
|
+
if (appRoot === appsRoot || !isInsideDir(appRoot, appsRoot))
|
|
524
|
+
continue;
|
|
525
|
+
const manifestPath = realpathSync(join(appRoot, ".codeshell-panel", "panel.json"));
|
|
526
|
+
if (!isInsideDir(manifestPath, appRoot) || !statSync(manifestPath).isFile())
|
|
527
|
+
continue;
|
|
528
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
529
|
+
if (manifest.schemaVersion !== 2 ||
|
|
530
|
+
manifest.id !== id ||
|
|
531
|
+
!Array.isArray(manifest.agent?.skills)) {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
for (const skillEntry of manifest.agent.skills) {
|
|
535
|
+
if (typeof skillEntry !== "string")
|
|
536
|
+
continue;
|
|
537
|
+
const segments = skillEntry.split("/");
|
|
538
|
+
if (segments.length !== 4 ||
|
|
539
|
+
segments[0] !== "agent" ||
|
|
540
|
+
segments[1] !== "skills" ||
|
|
541
|
+
!/^[a-z][a-z0-9-]{0,63}$/.test(segments[2] ?? "") ||
|
|
542
|
+
segments[3] !== "SKILL.md") {
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
const skillManifest = realpathSync(join(appRoot, ...segments));
|
|
546
|
+
if (!isInsideDir(skillManifest, appRoot) || !statSync(skillManifest).isFile())
|
|
547
|
+
continue;
|
|
548
|
+
const skillRoot = dirname(skillManifest);
|
|
549
|
+
if (isInsideDir(resolved, skillRoot))
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
// A stale, malformed, or tampered Panel App entry grants no read access.
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
478
559
|
/**
|
|
479
560
|
* Returns the matching sensitive-dir entry (with the user's home prefix) if
|
|
480
561
|
* `resolved` lives underneath any sensitive directory, else undefined.
|
|
@@ -564,9 +645,7 @@ export function classifyPath(rawPath, opts) {
|
|
|
564
645
|
// files through the ordinary Read tool as well. A credential-shaped basename
|
|
565
646
|
// (.env, token.txt, key files, ...) deliberately keeps the sensitive-file
|
|
566
647
|
// gate even inside a Skill tree.
|
|
567
|
-
if (opts.operation === "read" &&
|
|
568
|
-
!sensitiveFile &&
|
|
569
|
-
isRegisteredSkillResourceRead(resolved)) {
|
|
648
|
+
if (opts.operation === "read" && !sensitiveFile && isRegisteredSkillResourceRead(resolved)) {
|
|
570
649
|
return {
|
|
571
650
|
decision: "allow",
|
|
572
651
|
reason: "registered Skill resource read",
|
package/package.json
CHANGED