@f5-sales-demo/xcsh 19.56.1 → 19.57.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/scripts/worker-tools-harness.ts +151 -0
- package/src/browser/extension-bridge-tools.ts +71 -0
- package/src/commands/worker.ts +8 -1
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/locales/ar.json +2 -0
- package/src/locales/de.json +2 -0
- package/src/locales/en.json +2 -0
- package/src/locales/es.json +2 -0
- package/src/locales/fr.json +2 -0
- package/src/locales/hi.json +2 -0
- package/src/locales/it.json +2 -0
- package/src/locales/ja.json +2 -0
- package/src/locales/ko.json +2 -0
- package/src/locales/pt-br.json +2 -0
- package/src/locales/th.json +2 -0
- package/src/locales/zh-cn.json +2 -0
- package/src/locales/zh-tw.json +2 -0
- package/src/main.ts +35 -48
- package/src/modes/components/welcome-checks.ts +11 -104
- package/src/modes/components/welcome.ts +39 -189
- package/src/modes/controllers/input-controller.ts +17 -1
- package/src/modes/controllers/login-model.ts +32 -0
- package/src/modes/controllers/selector-controller.ts +4 -0
- package/src/modes/interactive-mode.ts +18 -164
- package/src/modes/types.ts +2 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "19.
|
|
4
|
+
"version": "19.57.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",
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
57
57
|
"@mozilla/readability": "^0.6",
|
|
58
|
-
"@f5-sales-demo/xcsh-stats": "19.
|
|
59
|
-
"@f5-sales-demo/pi-agent-core": "19.
|
|
60
|
-
"@f5-sales-demo/pi-ai": "19.
|
|
61
|
-
"@f5-sales-demo/pi-natives": "19.
|
|
62
|
-
"@f5-sales-demo/pi-resource-management": "19.
|
|
63
|
-
"@f5-sales-demo/pi-tui": "19.
|
|
64
|
-
"@f5-sales-demo/pi-utils": "19.
|
|
58
|
+
"@f5-sales-demo/xcsh-stats": "19.57.0",
|
|
59
|
+
"@f5-sales-demo/pi-agent-core": "19.57.0",
|
|
60
|
+
"@f5-sales-demo/pi-ai": "19.57.0",
|
|
61
|
+
"@f5-sales-demo/pi-natives": "19.57.0",
|
|
62
|
+
"@f5-sales-demo/pi-resource-management": "19.57.0",
|
|
63
|
+
"@f5-sales-demo/pi-tui": "19.57.0",
|
|
64
|
+
"@f5-sales-demo/pi-utils": "19.57.0",
|
|
65
65
|
"@sinclair/typebox": "^0.34",
|
|
66
66
|
"@xterm/headless": "^6.0",
|
|
67
67
|
"ajv": "^8.20",
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Headless worker-tool harness — no Chrome, no F5 login.
|
|
2
|
+
//
|
|
3
|
+
// Spawns a real `xcsh worker`, connects a MOCK extension bridge client (exactly
|
|
4
|
+
// the hello handshake + origin the real service worker uses), sends a chat_request
|
|
5
|
+
// asking it to navigate, and observes:
|
|
6
|
+
// (a) which tools the worker's agent actually has (from the daily log), and
|
|
7
|
+
// (b) whether the agent emits a `navigate` tool_request over the bridge.
|
|
8
|
+
//
|
|
9
|
+
// This isolates the "agent responds but never drives" bug to the WORKER's tool
|
|
10
|
+
// registration, deterministically and repeatably.
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
// XCSH_DEV=1 → run the local source build (`bun src/cli.ts worker`) so source
|
|
15
|
+
// edits are testable; otherwise the installed Homebrew binary.
|
|
16
|
+
const XCSH_REPO = new URL("..", import.meta.url).pathname; // packages/coding-agent
|
|
17
|
+
const DEV = process.env.XCSH_DEV === "1";
|
|
18
|
+
const SPAWN_CMD = DEV ? ["bun", "src/cli.ts", "worker"] : ["/opt/homebrew/bin/xcsh", "worker"];
|
|
19
|
+
const SPAWN_CWD = DEV ? XCSH_REPO : process.cwd();
|
|
20
|
+
const EXT_ID = "klajkjdoehjidngligegnpknogmjjhkc";
|
|
21
|
+
const ORIGIN = `chrome-extension://${EXT_ID}`; // bridge origin check: exact, no trailing slash
|
|
22
|
+
const PORT = 19260; // away from the real backend range (19222-19241)
|
|
23
|
+
const TENANT = process.env.HARNESS_TENANT ?? "f5-sales-demo|production";
|
|
24
|
+
const PROMPT = process.env.HARNESS_PROMPT ?? "Navigate the browser to the origin pools list.";
|
|
25
|
+
|
|
26
|
+
function log(...a: unknown[]) {
|
|
27
|
+
console.log("[harness]", ...a);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 1) Spawn the worker with a forced bridge port + tenant identity.
|
|
31
|
+
const worker = Bun.spawn(SPAWN_CMD, {
|
|
32
|
+
cwd: SPAWN_CWD,
|
|
33
|
+
env: {
|
|
34
|
+
...process.env,
|
|
35
|
+
XCSH_BRIDGE_PORT: String(PORT),
|
|
36
|
+
XCSH_SESSION_ID: "tab-9999",
|
|
37
|
+
XCSH_SESSION_TENANT: TENANT,
|
|
38
|
+
},
|
|
39
|
+
stdout: "pipe",
|
|
40
|
+
stderr: "pipe",
|
|
41
|
+
});
|
|
42
|
+
log(`spawned worker pid=${worker.pid} (${DEV ? "DEV build" : "homebrew"}), port=${PORT}, tenant=${TENANT}`);
|
|
43
|
+
|
|
44
|
+
// Stream worker stderr; resolve when the bridge is listening.
|
|
45
|
+
let bridgeUp = false;
|
|
46
|
+
const decoder = new TextDecoder();
|
|
47
|
+
(async () => {
|
|
48
|
+
for await (const chunk of worker.stderr) {
|
|
49
|
+
const s = decoder.decode(chunk);
|
|
50
|
+
for (const line of s.split("\n")) {
|
|
51
|
+
if (line.trim()) log("worker⟩", line.trim());
|
|
52
|
+
if (line.includes("extension bridge listening")) bridgeUp = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
})();
|
|
56
|
+
|
|
57
|
+
// Wait for the bridge, then give the (heavier) agent session time to init + attach
|
|
58
|
+
// the chat handler (chatHandler.attach() runs AFTER createAgentSession).
|
|
59
|
+
for (let i = 0; i < 120 && !bridgeUp; i++) await Bun.sleep(250);
|
|
60
|
+
if (!bridgeUp) {
|
|
61
|
+
log("FAIL: bridge never came up");
|
|
62
|
+
worker.kill();
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
log("bridge up; waiting 12s for agent session init…");
|
|
66
|
+
await Bun.sleep(12_000);
|
|
67
|
+
|
|
68
|
+
// 2) Connect the mock extension client with the correct Origin.
|
|
69
|
+
const toolRequests: string[] = [];
|
|
70
|
+
let chatText = "";
|
|
71
|
+
let chatErr: string | null = null;
|
|
72
|
+
let done = false;
|
|
73
|
+
|
|
74
|
+
const ws = new WebSocket(`ws://127.0.0.1:${PORT}`, { headers: { Origin: ORIGIN } } as unknown as string[]);
|
|
75
|
+
ws.onopen = () => {
|
|
76
|
+
log("client connected; sending hello");
|
|
77
|
+
ws.send(JSON.stringify({ type: "hello", contractVersion: "1.5.0", extensionId: "harness" }));
|
|
78
|
+
};
|
|
79
|
+
ws.onerror = () => log("client ws error (origin rejected?)");
|
|
80
|
+
ws.onmessage = e => {
|
|
81
|
+
let m: Record<string, unknown>;
|
|
82
|
+
try {
|
|
83
|
+
m = JSON.parse(e.data as string);
|
|
84
|
+
} catch {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
switch (m.type) {
|
|
88
|
+
case "hello_ack":
|
|
89
|
+
log(`hello_ack: tenant=${m.tenant} sessionId=${m.sessionId} contractVersion=${m.contractVersion}`);
|
|
90
|
+
log(`sending chat_request: "${PROMPT}"`);
|
|
91
|
+
ws.send(
|
|
92
|
+
JSON.stringify({
|
|
93
|
+
type: "chat_request",
|
|
94
|
+
id: "c-harness-1",
|
|
95
|
+
text: PROMPT,
|
|
96
|
+
context: null,
|
|
97
|
+
mode: "configuration",
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
break;
|
|
101
|
+
case "tool_request":
|
|
102
|
+
log(`◆ TOOL_REQUEST from agent: tool="${m.tool}" id=${m.id}`);
|
|
103
|
+
toolRequests.push(m.tool as string);
|
|
104
|
+
// Reply so the agent's turn can proceed.
|
|
105
|
+
ws.send(JSON.stringify({ type: "tool_result", id: m.id, content: "ok (harness stub)", is_error: false }));
|
|
106
|
+
break;
|
|
107
|
+
case "chat_delta":
|
|
108
|
+
chatText += (m.delta as string) ?? "";
|
|
109
|
+
break;
|
|
110
|
+
case "chat_done":
|
|
111
|
+
done = true;
|
|
112
|
+
break;
|
|
113
|
+
case "chat_error":
|
|
114
|
+
chatErr = (m.error as string) ?? "unknown";
|
|
115
|
+
done = true;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// 3) Wait for the turn to finish (or time out).
|
|
121
|
+
for (let i = 0; i < 240 && !done; i++) await Bun.sleep(250);
|
|
122
|
+
|
|
123
|
+
// 4) Read the worker's actual tool set from the daily log.
|
|
124
|
+
let activeTools = "(not found in daily log)";
|
|
125
|
+
try {
|
|
126
|
+
const logPath = join(homedir(), ".xcsh", "logs", `xcsh.${new Date().toISOString().slice(0, 10)}.log`);
|
|
127
|
+
const text = await Bun.file(logPath).text();
|
|
128
|
+
const lines = text.split("\n").filter(l => l.includes(`"pid":${worker.pid}`) && l.includes("activeToolNames"));
|
|
129
|
+
if (lines.length) {
|
|
130
|
+
const parsed = JSON.parse(lines[lines.length - 1]);
|
|
131
|
+
activeTools = JSON.stringify(parsed.activeToolNames);
|
|
132
|
+
}
|
|
133
|
+
} catch {}
|
|
134
|
+
|
|
135
|
+
console.log("\n==================== RESULT ====================");
|
|
136
|
+
console.log("agent tool_requests emitted :", toolRequests.length ? toolRequests.join(", ") : "(none)");
|
|
137
|
+
console.log("worker activeToolNames :", activeTools);
|
|
138
|
+
console.log("has navigate tool? :", activeTools.includes('"navigate"') ? "YES" : "NO");
|
|
139
|
+
console.log("emitted a navigate? :", toolRequests.includes("navigate") ? "YES" : "NO");
|
|
140
|
+
console.log("chat error :", chatErr ?? "(none)");
|
|
141
|
+
console.log("agent said (first 240 chars) :", chatText.slice(0, 240).replace(/\n/g, " "));
|
|
142
|
+
if (toolRequests.includes("navigate")) console.log("\n[PASS] worker agent HAS + CALLS navigate — tool registration OK");
|
|
143
|
+
else console.log("\n[FAIL] worker never called navigate — browser tools missing from the agent");
|
|
144
|
+
console.log("===============================================");
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
ws.close();
|
|
148
|
+
} catch {}
|
|
149
|
+
worker.kill();
|
|
150
|
+
await Bun.sleep(300);
|
|
151
|
+
process.exit(0);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tools for the Chrome extension's browser actions.
|
|
3
|
+
*
|
|
4
|
+
* The extension advertises a fixed set of tools (EXTENSION_CAPABILITIES.tools:
|
|
5
|
+
* navigate, click, read_ax, type_text, …). Before this factory, those names were
|
|
6
|
+
* requested by the worker (BROWSER_TOOL_NAMES) but never registered as agent
|
|
7
|
+
* tools — so the agent could only run the form-driven `catalog_workflow_runner`
|
|
8
|
+
* and had no way to `navigate`/`click`, i.e. it replied "Navigating…" and never
|
|
9
|
+
* drove the browser. This turns each advertised tool into a `CustomTool` whose
|
|
10
|
+
* `execute` proxies the call to the connected extension over the bridge.
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentToolResult, AgentToolUpdateCallback } from "@f5-sales-demo/pi-agent-core";
|
|
13
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
14
|
+
import type { CustomTool, CustomToolContext } from "../extensibility/custom-tools/types";
|
|
15
|
+
import { EXTENSION_CAPABILITIES, type ExtensionToolDef } from "./capabilities.generated";
|
|
16
|
+
import type { BridgeServer } from "./extension-bridge";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extension tools that are transport/diagnostic plumbing, not agent actions —
|
|
20
|
+
* never exposed to the LLM. Everything else in EXTENSION_CAPABILITIES becomes a
|
|
21
|
+
* callable agent tool.
|
|
22
|
+
*/
|
|
23
|
+
const INTERNAL_TOOLS = new Set<string>([
|
|
24
|
+
"ping",
|
|
25
|
+
"capabilities",
|
|
26
|
+
"reload",
|
|
27
|
+
"debug_exec",
|
|
28
|
+
"detach",
|
|
29
|
+
"set_bridge_port",
|
|
30
|
+
"diag_suspension",
|
|
31
|
+
"diag_bridges",
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
/** Build a CustomTool that proxies one extension tool to `bridge.request`. */
|
|
35
|
+
function bridgeTool(bridge: BridgeServer, def: ExtensionToolDef): CustomTool<TSchema, unknown> {
|
|
36
|
+
return {
|
|
37
|
+
name: def.name,
|
|
38
|
+
label: def.name,
|
|
39
|
+
description: def.summary,
|
|
40
|
+
// The extension's JSON-Schema `params` is a TypeBox-compatible schema object.
|
|
41
|
+
parameters: (def.params ?? { type: "object", properties: {} }) as unknown as TSchema,
|
|
42
|
+
async execute(
|
|
43
|
+
_toolCallId: string,
|
|
44
|
+
params: unknown,
|
|
45
|
+
_onUpdate: AgentToolUpdateCallback<unknown, TSchema> | undefined,
|
|
46
|
+
_ctx: CustomToolContext,
|
|
47
|
+
_signal?: AbortSignal,
|
|
48
|
+
): Promise<AgentToolResult<unknown, TSchema>> {
|
|
49
|
+
const res = await bridge.request(def.name, (params ?? {}) as Record<string, unknown>);
|
|
50
|
+
const raw = typeof res.content === "string" ? res.content : JSON.stringify(res.content);
|
|
51
|
+
// AgentToolResult has no isError flag — surface the extension's error in the
|
|
52
|
+
// text so the model sees the failure and can react.
|
|
53
|
+
const text = res.is_error ? `Error: ${raw}` : raw;
|
|
54
|
+
return { content: [{ type: "text" as const, text }] };
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Every agent-facing extension tool as a bridge-proxying CustomTool. Pass the
|
|
61
|
+
* result to `createAgentSession({ customTools })` so the worker's agent can drive
|
|
62
|
+
* the browser. Requires `bridge` (the worker's live extension-bridge server).
|
|
63
|
+
*/
|
|
64
|
+
export function createExtensionBridgeTools(bridge: BridgeServer): CustomTool<TSchema, unknown>[] {
|
|
65
|
+
return EXTENSION_CAPABILITIES.tools.filter(def => !INTERNAL_TOOLS.has(def.name)).map(def => bridgeTool(bridge, def));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Names of the agent-facing extension tools (for tool-scoping / tests). */
|
|
69
|
+
export const EXTENSION_AGENT_TOOL_NAMES: readonly string[] = EXTENSION_CAPABILITIES.tools
|
|
70
|
+
.filter(def => !INTERNAL_TOOLS.has(def.name))
|
|
71
|
+
.map(def => def.name);
|
package/src/commands/worker.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { getProjectDir, getXCSHConfigDir } from "@f5-sales-demo/pi-utils";
|
|
|
17
17
|
import { Command } from "@f5-sales-demo/pi-utils/cli";
|
|
18
18
|
import { ChatHandler } from "../browser/chat-handler";
|
|
19
19
|
import { startBridgeServer } from "../browser/extension-bridge";
|
|
20
|
+
import { createExtensionBridgeTools, EXTENSION_AGENT_TOOL_NAMES } from "../browser/extension-bridge-tools";
|
|
20
21
|
import { setSharedBridgeServer } from "../browser/provider";
|
|
21
22
|
import { initializeWithSettings } from "../discovery";
|
|
22
23
|
import { createAgentSession } from "../sdk";
|
|
@@ -118,10 +119,16 @@ export default class Worker extends Command {
|
|
|
118
119
|
bridge.setSessionInfo(sessionInfoForWorker);
|
|
119
120
|
ContextService.onContextChange(() => bridge.broadcastTenantChanged());
|
|
120
121
|
|
|
122
|
+
// The extension's browser actions (navigate/click/read_ax/…) are not builtin
|
|
123
|
+
// tools — turn each into a bridge-proxying CustomTool so the agent can drive
|
|
124
|
+
// the browser (without this the agent only has catalog_workflow_runner and
|
|
125
|
+
// merely narrates "Navigating…"). Include their names in the tool scope.
|
|
126
|
+
const extensionTools = createExtensionBridgeTools(bridge);
|
|
121
127
|
const { session } = await createAgentSession({
|
|
122
128
|
cwd,
|
|
123
129
|
hasUI: false,
|
|
124
|
-
toolNames: BROWSER_TOOL_NAMES,
|
|
130
|
+
toolNames: [...new Set([...BROWSER_TOOL_NAMES, ...EXTENSION_AGENT_TOOL_NAMES])],
|
|
131
|
+
customTools: extensionTools,
|
|
125
132
|
// Headless worker: no MCP discovery, no LSP warmup, no extension discovery —
|
|
126
133
|
// keep startup lean and free of network calls / blocking prompts.
|
|
127
134
|
enableMCP: false,
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.57.0",
|
|
21
|
+
"commit": "9b508d8af3633290ce801f2ca9956dd22df78e30",
|
|
22
|
+
"shortCommit": "9b508d8",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-07-
|
|
26
|
-
"buildDate": "2026-07-
|
|
24
|
+
"tag": "v19.57.0",
|
|
25
|
+
"commitDate": "2026-07-04T20:19:08Z",
|
|
26
|
+
"buildDate": "2026-07-04T20:54:40.287Z",
|
|
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/v19.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/9b508d8af3633290ce801f2ca9956dd22df78e30",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.57.0"
|
|
33
33
|
};
|
package/src/locales/ar.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "مثبتة",
|
|
286
288
|
"plugins.tabs.recommended": "موصى بها",
|
|
287
289
|
"plugins.tabs.discover": "استكشاف",
|
package/src/locales/de.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "Installiert",
|
|
286
288
|
"plugins.tabs.recommended": "Empfohlen",
|
|
287
289
|
"plugins.tabs.discover": "Entdecken",
|
package/src/locales/en.json
CHANGED
|
@@ -303,6 +303,8 @@
|
|
|
303
303
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
304
304
|
"login.wizard.failed": "Connection failed — {error}",
|
|
305
305
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
306
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
307
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
306
308
|
|
|
307
309
|
"plugins.tabs.installed": "Installed",
|
|
308
310
|
"plugins.tabs.recommended": "Recommended",
|
package/src/locales/es.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "Instalados",
|
|
286
288
|
"plugins.tabs.recommended": "Recomendados",
|
|
287
289
|
"plugins.tabs.discover": "Descubrir",
|
package/src/locales/fr.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "Installés",
|
|
286
288
|
"plugins.tabs.recommended": "Recommandés",
|
|
287
289
|
"plugins.tabs.discover": "Découvrir",
|
package/src/locales/hi.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "इंस्टॉल किए गए",
|
|
286
288
|
"plugins.tabs.recommended": "अनुशंसित",
|
|
287
289
|
"plugins.tabs.discover": "खोजें",
|
package/src/locales/it.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "Installati",
|
|
286
288
|
"plugins.tabs.recommended": "Consigliati",
|
|
287
289
|
"plugins.tabs.discover": "Scopri",
|
package/src/locales/ja.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "インストール済み",
|
|
286
288
|
"plugins.tabs.recommended": "推奨",
|
|
287
289
|
"plugins.tabs.discover": "検索",
|
package/src/locales/ko.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "설치됨",
|
|
286
288
|
"plugins.tabs.recommended": "권장",
|
|
287
289
|
"plugins.tabs.discover": "검색",
|
package/src/locales/pt-br.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "Instalados",
|
|
286
288
|
"plugins.tabs.recommended": "Recomendados",
|
|
287
289
|
"plugins.tabs.discover": "Descobrir",
|
package/src/locales/th.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "ติดตั้งแล้ว",
|
|
286
288
|
"plugins.tabs.recommended": "แนะนำ",
|
|
287
289
|
"plugins.tabs.discover": "ค้นหา",
|
package/src/locales/zh-cn.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "已安装",
|
|
286
288
|
"plugins.tabs.recommended": "推荐",
|
|
287
289
|
"plugins.tabs.discover": "发现",
|
package/src/locales/zh-tw.json
CHANGED
|
@@ -282,6 +282,8 @@
|
|
|
282
282
|
"login.wizard.configSaved": "Configuration saved. You're ready to go!",
|
|
283
283
|
"login.wizard.failed": "Connection failed — {error}",
|
|
284
284
|
"login.wizard.retryHint": "Press Escape to skip, or enter a new value:",
|
|
285
|
+
"gate.noProvider": "No LLM provider configured — run /login to connect one before sending a message.",
|
|
286
|
+
"gate.noProviderBlock": "No LLM provider configured. Run /login to connect one, then retry. (/help and /model still work.)",
|
|
285
287
|
"plugins.tabs.installed": "已安裝",
|
|
286
288
|
"plugins.tabs.recommended": "推薦",
|
|
287
289
|
"plugins.tabs.discover": "探索",
|
package/src/main.ts
CHANGED
|
@@ -165,33 +165,16 @@ async function runInteractiveMode(
|
|
|
165
165
|
versionCheckPromise.catch(() => undefined),
|
|
166
166
|
new Promise<string | undefined>(resolve => setTimeout(() => resolve(undefined), INITIAL_UPDATE_CHECK_TIMEOUT_MS)),
|
|
167
167
|
]);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
: undefined;
|
|
171
|
-
|
|
172
|
-
const mode = new InteractiveMode(
|
|
173
|
-
session,
|
|
174
|
-
version,
|
|
175
|
-
initialUpdateStatus,
|
|
176
|
-
setExtensionUIContext,
|
|
177
|
-
lspServers,
|
|
178
|
-
mcpManager,
|
|
179
|
-
eventBus,
|
|
180
|
-
);
|
|
168
|
+
|
|
169
|
+
const mode = new InteractiveMode(session, version, setExtensionUIContext, lspServers, mcpManager, eventBus);
|
|
181
170
|
|
|
182
171
|
await mode.init();
|
|
183
172
|
|
|
184
|
-
if
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
if (newVersion) {
|
|
191
|
-
mode.setUpdateStatus({ available: true, latestVersion: newVersion });
|
|
192
|
-
}
|
|
193
|
-
})
|
|
194
|
-
.catch(() => {});
|
|
173
|
+
// Non-blocking, one-shot update notice: only surface it if the background check
|
|
174
|
+
// already resolved within the startup race (no waiting, no live re-render). If it
|
|
175
|
+
// resolves later, the update is available on demand via /plugins.
|
|
176
|
+
if (initialUpdateVersion && settings.get("startup.checkUpdate")) {
|
|
177
|
+
mode.showStatus(`Update available: v${initialUpdateVersion} — run: xcsh update`, { dim: true });
|
|
195
178
|
}
|
|
196
179
|
|
|
197
180
|
mode.renderInitialMessages();
|
|
@@ -717,34 +700,38 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
717
700
|
sessionManager = await SessionManager.open(selectedPath);
|
|
718
701
|
}
|
|
719
702
|
|
|
720
|
-
// Refresh stale marketplace clones
|
|
703
|
+
// Refresh stale marketplace clones in the background so startup never blocks on
|
|
704
|
+
// git/network. Upgraded plugin code applies on the next launch (same trade-off as
|
|
705
|
+
// the "notify" branch below). Offline/corrupt data is tolerated by the try/catch.
|
|
721
706
|
const autoUpdate = settings.get("marketplace.autoUpdate");
|
|
722
707
|
if (autoUpdate !== "off") {
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
708
|
+
void (async () => {
|
|
709
|
+
try {
|
|
710
|
+
const startupMgr = new MarketplaceManager({
|
|
711
|
+
marketplacesRegistryPath: getMarketplacesRegistryPath(),
|
|
712
|
+
installedRegistryPath: getInstalledPluginsRegistryPath(),
|
|
713
|
+
projectInstalledRegistryPath: (await resolveActiveProjectRegistryPath(getProjectDir())) ?? undefined,
|
|
714
|
+
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
715
|
+
pluginsCacheDir: getPluginsCacheDir(),
|
|
716
|
+
clearPluginRootsCache: (extraPaths?: readonly string[]) => {
|
|
717
|
+
const h = os.homedir();
|
|
718
|
+
invalidateFsCache(path.join(h, getConfigDirName(), "plugins", "installed_plugins.json"));
|
|
719
|
+
for (const p of extraPaths ?? []) invalidateFsCache(p);
|
|
720
|
+
clearXcshPluginRootsCache();
|
|
721
|
+
},
|
|
722
|
+
});
|
|
723
|
+
await startupMgr.refreshStaleMarketplaces();
|
|
724
|
+
if (autoUpdate === "auto") {
|
|
725
|
+
const updates = await startupMgr.checkForUpdates();
|
|
726
|
+
if (updates.length > 0) {
|
|
727
|
+
await startupMgr.upgradeAllPlugins();
|
|
728
|
+
logger.debug(`Auto-upgraded ${updates.length} marketplace plugin(s) at startup`);
|
|
729
|
+
}
|
|
743
730
|
}
|
|
731
|
+
} catch {
|
|
732
|
+
// Network failure, corrupt data, offline — proceed with cached plugins.
|
|
744
733
|
}
|
|
745
|
-
}
|
|
746
|
-
// Network failure, corrupt data, offline — proceed with cached plugins.
|
|
747
|
-
}
|
|
734
|
+
})();
|
|
748
735
|
}
|
|
749
736
|
|
|
750
737
|
// Wire --plugin-dir and preload plugin roots for sync consumers (LSP config)
|