@f5-sales-demo/xcsh 21.1.0 → 21.2.0
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/package.json +8 -8
- package/src/browser/capabilities.generated.ts +21 -1
- package/src/browser/chat-handler.ts +13 -9
- package/src/browser/extension-contract.ts +62 -0
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/extension-tools.generated.ts +4 -0
- package/src/internal-urls/xcsh-protocol.ts +6 -5
- package/src/prompts/internal-urls/containment.md +4 -0
- package/src/sandbox/containment.ts +12 -5
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "21.
|
|
4
|
+
"version": "21.2.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
64
|
-
"@f5-sales-demo/pi-agent-core": "21.
|
|
65
|
-
"@f5-sales-demo/pi-ai": "21.
|
|
66
|
-
"@f5-sales-demo/pi-natives": "21.
|
|
67
|
-
"@f5-sales-demo/pi-resource-management": "21.
|
|
68
|
-
"@f5-sales-demo/pi-tui": "21.
|
|
69
|
-
"@f5-sales-demo/pi-utils": "21.
|
|
70
|
-
"@f5-sales-demo/xcsh-stats": "21.
|
|
64
|
+
"@f5-sales-demo/pi-agent-core": "21.2.0",
|
|
65
|
+
"@f5-sales-demo/pi-ai": "21.2.0",
|
|
66
|
+
"@f5-sales-demo/pi-natives": "21.2.0",
|
|
67
|
+
"@f5-sales-demo/pi-resource-management": "21.2.0",
|
|
68
|
+
"@f5-sales-demo/pi-tui": "21.2.0",
|
|
69
|
+
"@f5-sales-demo/pi-utils": "21.2.0",
|
|
70
|
+
"@f5-sales-demo/xcsh-stats": "21.2.0",
|
|
71
71
|
"@mozilla/readability": "^0.6",
|
|
72
72
|
"@sinclair/typebox": "^0.34",
|
|
73
73
|
"@xterm/headless": "^6.0",
|
|
@@ -15,13 +15,33 @@ export interface ExtensionToolDef {
|
|
|
15
15
|
readonly flags?: ExtensionToolFlags;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export type ExtensionInteractionMode =
|
|
19
|
+
| "educational"
|
|
20
|
+
| "presentation"
|
|
21
|
+
| "configuration"
|
|
22
|
+
| "screenshot"
|
|
23
|
+
| "annotation";
|
|
24
|
+
|
|
25
|
+
export interface ExtensionChatPromptHints {
|
|
26
|
+
readonly role: string;
|
|
27
|
+
readonly grounding: string;
|
|
28
|
+
readonly referenceLinks: string;
|
|
29
|
+
readonly toolUse: string;
|
|
30
|
+
readonly modes: Readonly<Record<ExtensionInteractionMode, string>>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ExtensionFeatures {
|
|
34
|
+
readonly chat: { readonly promptHints: ExtensionChatPromptHints; readonly [key: string]: unknown };
|
|
35
|
+
readonly [key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
18
38
|
export interface ExtensionCapabilities {
|
|
19
39
|
readonly version: string;
|
|
20
40
|
readonly contractVersion: string;
|
|
21
41
|
readonly multiPortDiscovery?: boolean;
|
|
22
42
|
readonly protocol: string;
|
|
23
43
|
readonly tools: readonly ExtensionToolDef[];
|
|
24
|
-
readonly features:
|
|
44
|
+
readonly features: ExtensionFeatures;
|
|
25
45
|
}
|
|
26
46
|
|
|
27
47
|
export const EXTENSION_CAPABILITIES: ExtensionCapabilities = {
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { LITELLM_LOGIN_MODEL_CHOICES } from "../modes/controllers/login-model";
|
|
22
22
|
import { extractReferences } from "../references";
|
|
23
23
|
import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
24
|
+
import { EXTENSION_CAPABILITIES } from "./capabilities.generated";
|
|
24
25
|
import {
|
|
25
26
|
type ChatDelta,
|
|
26
27
|
type ChatDone,
|
|
@@ -689,14 +690,6 @@ export function classifyChatErrorReason(message: string): ChatErrorReason {
|
|
|
689
690
|
return "provider-5xx";
|
|
690
691
|
}
|
|
691
692
|
|
|
692
|
-
const MODE_INSTRUCTIONS: Record<InteractionMode, string> = {
|
|
693
|
-
educational: "Explain concepts and settings in depth. Help the user understand what they're looking at and why.",
|
|
694
|
-
presentation: "Guide a structured walkthrough. Narrate each step clearly for a live audience.",
|
|
695
|
-
configuration: "Help the user build or modify F5 XC configuration. Be precise and action-oriented.",
|
|
696
|
-
screenshot: "Focus on capturing annotated screenshots that document the current state.",
|
|
697
|
-
annotation: "Create on-page teaching annotations that highlight key elements and explain their purpose.",
|
|
698
|
-
};
|
|
699
|
-
|
|
700
693
|
/** Bases a user-attached context path must fall under. Confines grants to the user's
|
|
701
694
|
* own space (home, the project cwd, temp, external volumes, /opt) and thereby blocks
|
|
702
695
|
* a client from widening the sandbox to system/credential dirs (`/etc`, `/var`,
|
|
@@ -779,11 +772,22 @@ export function composeChatPrompt(
|
|
|
779
772
|
const profile = hostProfile(host);
|
|
780
773
|
parts.push(profile.systemPrompt);
|
|
781
774
|
|
|
775
|
+
const promptHints = EXTENSION_CAPABILITIES.features.chat.promptHints;
|
|
776
|
+
|
|
782
777
|
// Browser hosts ALSO get an interaction mode + the page-context block. Document
|
|
783
778
|
// hosts get NEITHER: Office sends no page context and has no browser modes; its
|
|
784
779
|
// tools + document state arrive at runtime via set_host_tools.
|
|
785
780
|
if (profile.kind === "browser") {
|
|
786
|
-
parts.push(
|
|
781
|
+
parts.push(
|
|
782
|
+
[
|
|
783
|
+
"[Published extension chat contract]",
|
|
784
|
+
promptHints.role,
|
|
785
|
+
promptHints.grounding,
|
|
786
|
+
promptHints.referenceLinks,
|
|
787
|
+
promptHints.toolUse,
|
|
788
|
+
].join("\n"),
|
|
789
|
+
);
|
|
790
|
+
parts.push(`[Chat mode: ${mode}] ${promptHints.modes[mode]}`);
|
|
787
791
|
if (context) composeBrowserPageContext(parts, context);
|
|
788
792
|
}
|
|
789
793
|
|
|
@@ -46,3 +46,65 @@ export function extractRequestedTools(source: string): string[] {
|
|
|
46
46
|
while ((m = re.exec(source)) !== null) out.add(m[1]);
|
|
47
47
|
return [...out];
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
interface ToolReferenceManifest {
|
|
51
|
+
readonly contractVersion: string;
|
|
52
|
+
readonly tools: readonly {
|
|
53
|
+
readonly name: string;
|
|
54
|
+
readonly summary: string;
|
|
55
|
+
readonly category: string;
|
|
56
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
57
|
+
readonly flags?: {
|
|
58
|
+
readonly readOnly?: boolean;
|
|
59
|
+
readonly mutates?: boolean;
|
|
60
|
+
readonly requiresExplainMode?: boolean;
|
|
61
|
+
};
|
|
62
|
+
}[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function compareText(left: string, right: string): number {
|
|
66
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Stable JSON with object keys sorted recursively and array order preserved. */
|
|
70
|
+
function canonicalJson(value: unknown): string {
|
|
71
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
72
|
+
if (value !== null && typeof value === "object") {
|
|
73
|
+
const entries = Object.entries(value as Record<string, unknown>).sort(([left], [right]) =>
|
|
74
|
+
compareText(left, right),
|
|
75
|
+
);
|
|
76
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
|
|
77
|
+
}
|
|
78
|
+
return JSON.stringify(value) ?? "null";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Render the complete extension tool surface as deterministic Markdown. */
|
|
82
|
+
export function renderToolReference(manifest: ToolReferenceManifest): string {
|
|
83
|
+
const categories = new Map<string, ToolReferenceManifest["tools"][number][]>();
|
|
84
|
+
for (const tool of manifest.tools) {
|
|
85
|
+
const tools = categories.get(tool.category) ?? [];
|
|
86
|
+
tools.push(tool);
|
|
87
|
+
categories.set(tool.category, tools);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const lines = [
|
|
91
|
+
"# Chrome Extension Tool Signatures",
|
|
92
|
+
"",
|
|
93
|
+
`Generated from extension capability contract \`${manifest.contractVersion}\`.`,
|
|
94
|
+
];
|
|
95
|
+
for (const category of [...categories.keys()].sort(compareText)) {
|
|
96
|
+
lines.push("", `## ${category}`);
|
|
97
|
+
for (const tool of (categories.get(category) ?? []).sort((left, right) => compareText(left.name, right.name))) {
|
|
98
|
+
lines.push(
|
|
99
|
+
"",
|
|
100
|
+
`### \`${tool.name}\``,
|
|
101
|
+
"",
|
|
102
|
+
tool.summary,
|
|
103
|
+
"",
|
|
104
|
+
`- Parameters: \`${canonicalJson(tool.params)}\``,
|
|
105
|
+
`- Semantic flags: \`${canonicalJson(tool.flags ?? {})}\``,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return `${lines.join("\n")}\n`;
|
|
110
|
+
}
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "21.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "21.2.0",
|
|
21
|
+
"commit": "d217f62f68fae54fe18d88c7d90a8f7dc4703c0d",
|
|
22
|
+
"shortCommit": "d217f62",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v21.
|
|
25
|
-
"commitDate": "2026-08-
|
|
26
|
-
"buildDate": "2026-08-
|
|
24
|
+
"tag": "v21.2.0",
|
|
25
|
+
"commitDate": "2026-08-30T03:18:04+00:00",
|
|
26
|
+
"buildDate": "2026-08-30T03:46:25.618Z",
|
|
27
27
|
"dirty": true,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5-sales-demo/xcsh",
|
|
30
30
|
"repoSlug": "f5-sales-demo/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v21.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/d217f62f68fae54fe18d88c7d90a8f7dc4703c0d",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v21.2.0"
|
|
33
33
|
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-extension-capabilities.ts - DO NOT EDIT
|
|
2
|
+
// Source: src/browser/capabilities.json (the Chrome extension's published contract).
|
|
3
|
+
|
|
4
|
+
export const EXTENSION_TOOL_REFERENCE = "# Chrome Extension Tool Signatures\n\nGenerated from extension capability contract `2.1.0`.\n\n## annotation\n\n### `annotate`\n\nDraw an overlay annotation (fingerprint/highlight). No-op unless explain mode is on.\n\n- Parameters: `{\"properties\":{\"h\":{\"type\":\"number\"},\"kind\":{\"type\":\"string\"},\"ref\":{\"type\":\"string\"},\"w\":{\"type\":\"number\"},\"x\":{\"type\":\"number\"},\"y\":{\"type\":\"number\"}},\"required\":[\"kind\"],\"type\":\"object\"}`\n- Semantic flags: `{\"requiresExplainMode\":true}`\n\n### `set_explain_mode`\n\nEnter/leave explain mode — the gate for all on-page annotation overlays.\n\n- Parameters: `{\"properties\":{\"enabled\":{\"type\":\"boolean\"}},\"type\":\"object\"}`\n- Semantic flags: `{}`\n\n## interaction\n\n### `click`\n\nDeterministic click of an AX-ref element (layout-engine coords + hit-test).\n\n- Parameters: `{\"properties\":{\"ref\":{\"type\":\"string\"}},\"required\":[\"ref\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `click_element`\n\nClick the element returned by a JS expression (polls; occlusion-safe).\n\n- Parameters: `{\"properties\":{\"js\":{\"type\":\"string\"},\"wait_ms\":{\"type\":\"number\"}},\"required\":[\"js\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `click_xy`\n\nTrusted click at explicit viewport coordinates.\n\n- Parameters: `{\"properties\":{\"x\":{\"type\":\"number\"},\"y\":{\"type\":\"number\"}},\"required\":[\"x\",\"y\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `file_upload`\n\nUpload files (base64 data URIs) to a file input by AX ref.\n\n- Parameters: `{\"properties\":{\"files\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"ref\":{\"type\":\"string\"}},\"required\":[\"ref\",\"files\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `form_input`\n\nSet a form field value by AX ref.\n\n- Parameters: `{\"properties\":{\"ref\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"ref\",\"value\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `key_press`\n\nDispatch a key press.\n\n- Parameters: `{\"properties\":{\"key\":{\"type\":\"string\"}},\"required\":[\"key\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `label_select`\n\nType into a CDK-portal typeahead and click the matching option.\n\n- Parameters: `{\"properties\":{\"label_value\":{\"type\":\"string\"},\"selector\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"},\"wait_ms\":{\"type\":\"number\"}},\"required\":[\"selector\",\"value\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `select_option`\n\nSelect an option in a native <select> by AX ref.\n\n- Parameters: `{\"properties\":{\"ref\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"ref\",\"value\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `type_text`\n\nType text into the focused element (trusted input).\n\n- Parameters: `{\"properties\":{\"text\":{\"type\":\"string\"}},\"required\":[\"text\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n## meta\n\n### `capabilities`\n\nReturn this self-describing capability manifest (tools + features + versions).\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `debug_exec`\n\nDiagnostic: probe in-page bridge availability.\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `detach`\n\nDetach the debugger from the target tab.\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `ping`\n\nLiveness check; returns { ok, version }.\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `reload`\n\nReload the extension (re-reads dist/ from disk).\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `set_bridge_port`\n\nSet the WebSocket bridge port (persists across reload; enables multi-session on different ports).\n\n- Parameters: `{\"properties\":{\"port\":{\"type\":\"number\"}},\"required\":[\"port\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n## navigation\n\n### `navigate`\n\nNavigate the console tab to a scoped https URL.\n\n- Parameters: `{\"properties\":{\"url\":{\"type\":\"string\"}},\"required\":[\"url\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `resize_window`\n\nResize the browser window.\n\n- Parameters: `{\"properties\":{\"height\":{\"type\":\"number\"},\"width\":{\"type\":\"number\"}},\"required\":[\"width\",\"height\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `scroll_to`\n\nScroll an AX-ref element into view.\n\n- Parameters: `{\"properties\":{\"ref\":{\"type\":\"string\"}},\"required\":[\"ref\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `tabs_close`\n\nClose a tab by id.\n\n- Parameters: `{\"properties\":{\"tabId\":{\"type\":\"number\"}},\"required\":[\"tabId\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `tabs_create`\n\nOpen a new tab at a scoped URL.\n\n- Parameters: `{\"properties\":{\"url\":{\"type\":\"string\"}},\"required\":[\"url\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `tabs_list`\n\nList scoped console tabs.\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{}`\n\n## read\n\n### `assert_text`\n\nAssert an element contains expected text.\n\n- Parameters: `{\"properties\":{\"context\":{\"type\":\"string\"},\"expected\":{\"type\":\"string\"},\"selector\":{\"type\":\"string\"}},\"required\":[\"selector\",\"expected\"],\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `diag_activation`\n\nDiagnostic: per-gate tab-activation readiness timings (bridge/worker/page), cold/warm.\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{}`\n\n### `diag_bridges`\n\nList discovered xcsh bridge health without tenant, environment, or session identifiers.\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{}`\n\n### `diag_suspension`\n\nDiagnostic: SW-lifecycle event buffer + suspension summary (Phase 0a).\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{}`\n\n### `diag_ttft`\n\nDiagnostic: init→first-token timeline (per-stage ms, total, dominant, cold/warm).\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{}`\n\n### `find`\n\nFind AX nodes matching a locator.\n\n- Parameters: `{\"properties\":{\"selector\":{\"type\":\"string\"}},\"required\":[\"selector\"],\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `get_page_context`\n\nReturn a snapshot of the active console page (url, AX tree, captured XC API body) for chat grounding.\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `get_page_text`\n\nReturn the page text.\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `query_dom`\n\nDirect DOM.querySelector at wire speed — bypasses Runtime.evaluate for simple CSS selectors.\n\n- Parameters: `{\"properties\":{\"selector\":{\"type\":\"string\"}},\"required\":[\"selector\"],\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `read_ax`\n\nRead the accessibility tree of the page.\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `read_console`\n\nRead buffered console messages, optionally filtered by pattern.\n\n- Parameters: `{\"properties\":{\"pattern\":{\"type\":\"string\"}},\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `read_network`\n\nRead buffered network events, optionally filtered by pattern.\n\n- Parameters: `{\"properties\":{\"pattern\":{\"type\":\"string\"}},\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `screenshot`\n\nCapture a screenshot (base64 PNG).\n\n- Parameters: `{\"properties\":{},\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `wait_for`\n\nWait for an AX node matching a locator to appear.\n\n- Parameters: `{\"properties\":{\"context\":{\"type\":\"string\"},\"selector\":{\"type\":\"string\"},\"timeoutMs\":{\"type\":\"number\"}},\"required\":[\"selector\"],\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n### `wait_for_api_response`\n\nWait for a network response whose URL matches a pattern.\n\n- Parameters: `{\"properties\":{\"pattern\":{\"type\":\"string\"},\"timeout_ms\":{\"type\":\"number\"}},\"type\":\"object\"}`\n- Semantic flags: `{\"readOnly\":true}`\n\n## script\n\n### `browser_batch`\n\nRun a batch of { tool, params } actions in sequence.\n\n- Parameters: `{\"properties\":{\"actions\":{\"items\":{\"properties\":{\"params\":{},\"tool\":{\"type\":\"string\"}},\"required\":[\"tool\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"actions\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n\n### `javascript_tool`\n\nEvaluate arbitrary JS in the page (length-capped).\n\n- Parameters: `{\"properties\":{\"code\":{\"type\":\"string\"}},\"required\":[\"code\"],\"type\":\"object\"}`\n- Semantic flags: `{\"mutates\":true}`\n";
|
|
@@ -44,6 +44,7 @@ import { type ConsoleFieldMetadataData, EMPTY_CONSOLE_FIELD_METADATA } from "./c
|
|
|
44
44
|
import { type ConsoleResolver, createConsoleResolver } from "./console-resolve";
|
|
45
45
|
import { EMBEDDED_DOC_FILENAMES, EMBEDDED_DOCS } from "./docs-index.generated";
|
|
46
46
|
import extensionApiContent from "./extension-api.md" with { type: "text" };
|
|
47
|
+
import { EXTENSION_TOOL_REFERENCE } from "./extension-tools.generated";
|
|
47
48
|
import { createFleetResolver, type FleetDeps, type FleetResolver } from "./fleet-resolve";
|
|
48
49
|
import { createPluginResolver, type GetPluginRoots, type PluginResolver } from "./plugin-resolve";
|
|
49
50
|
import { createRegistryResolver, type RegistryResolver, type RegistryResolverDeps } from "./registry-resolve";
|
|
@@ -66,6 +67,7 @@ const CHANGES_HOST = "changes";
|
|
|
66
67
|
const SOURCE_HOST = "source";
|
|
67
68
|
const FLEET_HOST = "fleet";
|
|
68
69
|
const SITECLI_HOST = "sitecli";
|
|
70
|
+
const EXTENSION_CONTENT = `${extensionApiContent.trimEnd()}\n\n---\n\n${EXTENSION_TOOL_REFERENCE}`;
|
|
69
71
|
const EMPTY_INDEX: ApiSpecIndex = { version: "unavailable", timestamp: "", domains: [] };
|
|
70
72
|
const EMPTY_CATALOG_INDEX: ApiCatalogIndex = {
|
|
71
73
|
version: "unavailable",
|
|
@@ -490,15 +492,14 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
|
|
|
490
492
|
return this.#readDoc(filename, url);
|
|
491
493
|
}
|
|
492
494
|
|
|
493
|
-
/** Serve the Chrome extension bridge
|
|
494
|
-
*
|
|
495
|
-
* at `xcsh://extension`. */
|
|
495
|
+
/** Serve the Chrome extension bridge guidance plus generated tool signatures.
|
|
496
|
+
* Both are imported at compile time and served on demand at `xcsh://extension`. */
|
|
496
497
|
#resolveExtension(url: InternalUrl): InternalResource {
|
|
497
498
|
return {
|
|
498
499
|
url: url.href,
|
|
499
|
-
content:
|
|
500
|
+
content: EXTENSION_CONTENT,
|
|
500
501
|
contentType: "text/markdown",
|
|
501
|
-
size: Buffer.byteLength(
|
|
502
|
+
size: Buffer.byteLength(EXTENSION_CONTENT, "utf-8"),
|
|
502
503
|
sourcePath: `${SCHEME_PREFIX}${EXTENSION_HOST}`,
|
|
503
504
|
};
|
|
504
505
|
}
|
|
@@ -61,6 +61,10 @@ Without a runtime backend, Bash child-process reads cannot enforce protected-con
|
|
|
61
61
|
xcsh-private-root denial. Structured tools still enforce those rules, and ordinary work remains
|
|
62
62
|
unrestricted. The persistent `python` kernel is likewise unfenced beyond its explicit `cwd` check.
|
|
63
63
|
|
|
64
|
+
On Windows, mounted drive letters other than the workspace drive lose root enumeration in structured
|
|
65
|
+
tools. A UNC share that is not mapped to a drive letter cannot be enumerated and is outside this
|
|
66
|
+
courtesy boundary; map it to a drive or grant a known path explicitly when it belongs to the task.
|
|
67
|
+
|
|
64
68
|
Treat the boundary as a statement of intent rather than a guarantee, and do not go looking for paths
|
|
65
69
|
outside the session directory on the assumption that something would stop you.
|
|
66
70
|
{{/if}}
|
|
@@ -352,12 +352,11 @@ function resolveGrants(roots: readonly string[] | undefined, home: string): Set<
|
|
|
352
352
|
* kernel backend, so this exact-enumeration rule is enforced for structured tools but cannot confine a
|
|
353
353
|
* Bash child process.
|
|
354
354
|
*
|
|
355
|
-
*
|
|
356
|
-
* tested is the exact-enumeration protection, not discovery — see the test, which injects the list.
|
|
355
|
+
* Native Windows UAT exercises this production probe against a real second mounted volume.
|
|
357
356
|
*
|
|
358
357
|
* UNC paths (`\\server\share`) have no enumerable root and are not covered.
|
|
359
358
|
*/
|
|
360
|
-
function otherFilesystemRoots(fsRoot: string): string[] {
|
|
359
|
+
export function otherFilesystemRoots(fsRoot: string): string[] {
|
|
361
360
|
if (process.platform !== "win32") return [];
|
|
362
361
|
const roots: string[] = [];
|
|
363
362
|
for (let letter = "A".charCodeAt(0); letter <= "Z".charCodeAt(0); letter++) {
|
|
@@ -532,8 +531,16 @@ export function buildContainmentFence(options: ContainmentOptions): ContainmentF
|
|
|
532
531
|
if (resolved === fsRoot) continue; // never the root the workspace lives on
|
|
533
532
|
// A directory containing home is normally too broad to protect because it would hide unrelated
|
|
534
533
|
// operational entries. Account containers are the deliberate exception: their listing is the
|
|
535
|
-
// discovery surface, while every named account remains reachable (#2788, #2931).
|
|
536
|
-
|
|
534
|
+
// discovery surface, while every named account remains reachable (#2788, #2931). A separate
|
|
535
|
+
// Windows drive is also an exception: only its exact listing is hidden, so home and every named
|
|
536
|
+
// descendant retain their normal access.
|
|
537
|
+
if (
|
|
538
|
+
!rootScoped.has(resolved) &&
|
|
539
|
+
home !== undefined &&
|
|
540
|
+
pathIsWithin(resolved, home) &&
|
|
541
|
+
!accountRoots.has(resolved)
|
|
542
|
+
)
|
|
543
|
+
continue;
|
|
537
544
|
// The session workspace and explicit read/full grants retain enumeration. A write-only grant does
|
|
538
545
|
// not imply permission to learn directory entries.
|
|
539
546
|
if (resolved === workspace) continue;
|