scream-code 0.13.7 → 0.13.9

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.
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import { i as __require, o as __toESM, r as __exportAll, t as __commonJSMin } from "./chunk-D90kvbyJ.mjs";
7
7
  import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-BH9W5k24.mjs";
8
8
  import { t as require_base64_js } from "./base64-js-DzVmk6Nb.mjs";
9
- import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-DstXBOl9.mjs";
9
+ import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-BzVVVncg.mjs";
10
10
  import { createRequire } from "node:module";
11
11
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
12
12
  import * as fs$1 from "node:fs/promises";
@@ -59275,7 +59275,8 @@ const DOCUMENTATION_MARKDOWN_LOWER$1 = new Set([
59275
59275
  "code_of_conduct.md",
59276
59276
  "architecture.md",
59277
59277
  "design.md",
59278
- "notes.md"
59278
+ "notes.md",
59279
+ "agents.md"
59279
59280
  ]);
59280
59281
  async function resolveSkillRoots(options) {
59281
59282
  const isDir = options.isDir ?? defaultIsDir;
@@ -66386,7 +66387,6 @@ const BARE_SKILL_PATH = "SKILL.md";
66386
66387
  const UNSUPPORTED_RUNTIME_FIELDS = [
66387
66388
  "tools",
66388
66389
  "commands",
66389
- "hooks",
66390
66390
  "apps",
66391
66391
  "inject",
66392
66392
  "configFile",
@@ -66485,6 +66485,8 @@ async function parseManifest(pluginRoot) {
66485
66485
  const skillInstructions = typeof raw["skillInstructions"] === "string" ? raw["skillInstructions"] : void 0;
66486
66486
  const config = typeof raw["config"] === "object" && raw["config"] !== null && !Array.isArray(raw["config"]) ? raw["config"] : void 0;
66487
66487
  recordUnsupportedRuntimeFields(raw, diagnostics);
66488
+ const entryPoint = await readEntryPoint(pluginRoot, raw["entryPoint"], diagnostics);
66489
+ const hooks = readHooks(pluginRoot, raw["hooks"], diagnostics);
66488
66490
  return {
66489
66491
  manifest: {
66490
66492
  name,
@@ -66499,7 +66501,9 @@ async function parseManifest(pluginRoot) {
66499
66501
  mcpServers: await readMcpServers(pluginRoot, raw["mcpServers"], diagnostics),
66500
66502
  interface: readInterface(raw["interface"]),
66501
66503
  skillInstructions,
66502
- config
66504
+ config,
66505
+ entryPoint,
66506
+ hooks
66503
66507
  },
66504
66508
  manifestKind,
66505
66509
  manifestPath,
@@ -66516,6 +66520,75 @@ function recordUnsupportedRuntimeFields(raw, diagnostics) {
66516
66520
  });
66517
66521
  }
66518
66522
  }
66523
+ async function readEntryPoint(pluginRoot, raw, diagnostics) {
66524
+ if (raw === void 0) return void 0;
66525
+ if (typeof raw !== "string") {
66526
+ diagnostics.push({
66527
+ severity: "warn",
66528
+ message: "\"entryPoint\" must be a string"
66529
+ });
66530
+ return;
66531
+ }
66532
+ return resolvePluginPathField({
66533
+ pluginRoot,
66534
+ field: "entryPoint",
66535
+ value: raw,
66536
+ diagnostics
66537
+ });
66538
+ }
66539
+ /**
66540
+ * Parse a plugin's declared hooks (external-command HookEngine channel). Each
66541
+ * entry mirrors {@link HookDef}: event, optional matcher, command (a `./`
66542
+ * relative command is resolved against the plugin root), optional timeout.
66543
+ * Unknown event names are kept (cast): they can never match a HookEngine
66544
+ * trigger, so they are inert rather than harmful.
66545
+ */
66546
+ function readHooks(pluginRoot, raw, diagnostics) {
66547
+ if (raw === void 0) return void 0;
66548
+ if (!Array.isArray(raw)) {
66549
+ diagnostics.push({
66550
+ severity: "warn",
66551
+ message: "\"hooks\" must be an array"
66552
+ });
66553
+ return;
66554
+ }
66555
+ const out = [];
66556
+ for (const entry of raw) {
66557
+ if (!isObject(entry)) {
66558
+ diagnostics.push({
66559
+ severity: "warn",
66560
+ message: "\"hooks\" entries must be objects"
66561
+ });
66562
+ continue;
66563
+ }
66564
+ const event = stringField$1(entry, "event");
66565
+ if (event === void 0) {
66566
+ diagnostics.push({
66567
+ severity: "warn",
66568
+ message: "\"hooks\" entry is missing \"event\""
66569
+ });
66570
+ continue;
66571
+ }
66572
+ let command = stringField$1(entry, "command");
66573
+ if (command === void 0) {
66574
+ diagnostics.push({
66575
+ severity: "warn",
66576
+ message: "\"hooks\" entry is missing \"command\""
66577
+ });
66578
+ continue;
66579
+ }
66580
+ if (command.startsWith("./")) command = path.resolve(pluginRoot, command);
66581
+ const timeout = typeof entry["timeout"] === "number" ? entry["timeout"] : void 0;
66582
+ const hook = {
66583
+ event,
66584
+ command,
66585
+ ...stringField$1(entry, "matcher") !== void 0 ? { matcher: stringField$1(entry, "matcher") } : {},
66586
+ ...timeout !== void 0 ? { timeout } : {}
66587
+ };
66588
+ out.push(hook);
66589
+ }
66590
+ return out.length > 0 ? out : void 0;
66591
+ }
66519
66592
  async function resolveSkillsField(pluginRoot, raw, diagnostics) {
66520
66593
  if (raw === void 0) return [];
66521
66594
  const entries = [];
@@ -76454,6 +76527,63 @@ function buildBackgroundTaskNotificationBody(info, isAgentTask) {
76454
76527
  ].join("\n")}`;
76455
76528
  }
76456
76529
  //#endregion
76530
+ //#region ../../packages/agent-core/src/agent/events.ts
76531
+ /**
76532
+ * In-process subscription bus for {@link AgentEvent}s.
76533
+ *
76534
+ * Mirrors the events the agent already broadcasts to the host via
76535
+ * `rpc.emitEvent`, so third-party extensions running inside the agent process
76536
+ * can subscribe without round-tripping through the host. This is the
76537
+ * read-side companion to {@link import('./index').Agent.emitEvent}.
76538
+ *
76539
+ * Lifecycle: extensions call `subscribe()` when activated and must call
76540
+ * `clear()` (or the returned unsubscribe) when deactivated to avoid leaking
76541
+ * handlers across sessions/restarts.
76542
+ */
76543
+ var EventSubscriptionBus = class {
76544
+ byType = /* @__PURE__ */ new Map();
76545
+ wildcard = /* @__PURE__ */ new Set();
76546
+ /**
76547
+ * Subscribe to a specific event type (or `'*'` for all events).
76548
+ * Returns an unsubscribe function.
76549
+ */
76550
+ subscribe(type, handler) {
76551
+ if (type === "*") {
76552
+ this.wildcard.add(handler);
76553
+ return () => {
76554
+ this.wildcard.delete(handler);
76555
+ };
76556
+ }
76557
+ let set = this.byType.get(type);
76558
+ if (set === void 0) {
76559
+ set = /* @__PURE__ */ new Set();
76560
+ this.byType.set(type, set);
76561
+ }
76562
+ set.add(handler);
76563
+ return () => {
76564
+ set.delete(handler);
76565
+ };
76566
+ }
76567
+ /** Drop every subscriber (used on deactivate / session switch). */
76568
+ clear() {
76569
+ this.byType.clear();
76570
+ this.wildcard.clear();
76571
+ }
76572
+ /**
76573
+ * Deliver an event to matching handlers. A handler that throws is isolated
76574
+ * so a subscriber bug can never break the agent's event loop.
76575
+ */
76576
+ dispatch(event) {
76577
+ const typed = this.byType.get(event.type);
76578
+ if (typed !== void 0) for (const handler of typed) try {
76579
+ handler(event);
76580
+ } catch {}
76581
+ if (this.wildcard.size > 0) for (const handler of this.wildcard) try {
76582
+ handler(event);
76583
+ } catch {}
76584
+ }
76585
+ };
76586
+ //#endregion
76457
76587
  //#region ../../packages/agent-core/src/agent/context/projector.ts
76458
76588
  /** Synthetic error text used when a tool result is missing and must be
76459
76589
  * filled in so the provider accepts the message sequence. */
@@ -80033,6 +80163,39 @@ var HookEngine = class {
80033
80163
  for (const [event, hooks] of this.byEvent.entries()) result[event] = hooks.length;
80034
80164
  return result;
80035
80165
  }
80166
+ /**
80167
+ * Register a hook at runtime (e.g. when a plugin activates). Returns an
80168
+ * unregister function for symmetric removal when the plugin deactivates.
80169
+ * The existing constructor-injected hooks are unaffected; the byEvent map
80170
+ * stays the single source of truth so trigger paths need no changes.
80171
+ */
80172
+ register(hook) {
80173
+ const entries = this.byEvent.get(hook.event) ?? [];
80174
+ entries.push(hook);
80175
+ this.byEvent.set(hook.event, entries);
80176
+ return () => {
80177
+ this.unregister(hook);
80178
+ };
80179
+ }
80180
+ /**
80181
+ * Register several hooks at once (e.g. a plugin's manifest hooks). Returns
80182
+ * a single function that unregisters all of them.
80183
+ */
80184
+ registerAll(hooks) {
80185
+ const unregisters = hooks.map((hook) => this.register(hook));
80186
+ return () => {
80187
+ for (const unregister of unregisters) unregister();
80188
+ };
80189
+ }
80190
+ unregister(hook) {
80191
+ const entries = this.byEvent.get(hook.event);
80192
+ if (entries === void 0) return;
80193
+ const index = entries.lastIndexOf(hook);
80194
+ if (index >= 0) {
80195
+ entries.splice(index, 1);
80196
+ if (entries.length === 0) this.byEvent.delete(hook.event);
80197
+ }
80198
+ }
80036
80199
  trigger(event, args = {}) {
80037
80200
  try {
80038
80201
  return this.triggerInner(event, args).catch(() => []);
@@ -98631,6 +98794,8 @@ var Agent = class {
98631
98794
  mcp;
98632
98795
  hooks;
98633
98796
  log;
98797
+ /** In-process event bus for extensions running inside the agent process. */
98798
+ eventBus;
98634
98799
  blobStore;
98635
98800
  records;
98636
98801
  fullCompaction;
@@ -98674,6 +98839,7 @@ var Agent = class {
98674
98839
  this.subagentHost = options.subagentHost;
98675
98840
  this.mcp = options.mcp;
98676
98841
  this.hooks = options.hookEngine;
98842
+ this.eventBus = new EventSubscriptionBus();
98677
98843
  const embedCacheDir = options.screamHomeDir !== void 0 ? join$1(options.screamHomeDir, "cache", "fastembed") : void 0;
98678
98844
  this.sharedEmbeddingEngine = createFastEmbedEngine(embedCacheDir);
98679
98845
  this.log = options.log ?? log;
@@ -99240,6 +99406,7 @@ var Agent = class {
99240
99406
  }
99241
99407
  emitEvent(event) {
99242
99408
  if (this.records.restoring) return;
99409
+ this.eventBus.dispatch(event);
99243
99410
  this.rpc?.emitEvent?.(event)?.catch(() => {});
99244
99411
  }
99245
99412
  emitStatusUpdated() {
@@ -104275,6 +104442,90 @@ function createRPC() {
104275
104442
  return [leftClient, rightClient];
104276
104443
  }
104277
104444
  //#endregion
104445
+ //#region ../../packages/agent-core/src/plugin/runtime/extension.ts
104446
+ /**
104447
+ * Loads and activates code-entry plugins inside the agent process.
104448
+ *
104449
+ * Responsibilities:
104450
+ * - `discover()` — pick plugins whose manifest declares an `entryPoint`
104451
+ * - `load()` — dynamic-import the entry point (cached per path)
104452
+ * - `activate()` — inject declared manifest hooks into the agent's HookEngine,
104453
+ * then call the plugin's `activate(context)`; tools are registered by the
104454
+ * plugin itself via `context.services.tools.registerUserTool`
104455
+ * - `deactivate()` — symmetric removal (hooks + deactivate hook), isolated so
104456
+ * a failing plugin never breaks the agent
104457
+ *
104458
+ * Activation is deliberately lazy (a `/plugin activate` command or explicit
104459
+ * config opt-in), so merely installing a plugin never executes its code.
104460
+ */
104461
+ var ExtensionRuntime = class {
104462
+ loaded = /* @__PURE__ */ new Map();
104463
+ activations = /* @__PURE__ */ new Map();
104464
+ /** Plugins that declare a code entry point, in installation order. */
104465
+ discover(plugins) {
104466
+ const result = [];
104467
+ for (const plugin of plugins) {
104468
+ if (plugin.manifest?.entryPoint === void 0) continue;
104469
+ result.push({
104470
+ pluginId: plugin.id,
104471
+ entryPoint: plugin.manifest.entryPoint,
104472
+ manifest: plugin.manifest
104473
+ });
104474
+ }
104475
+ return result;
104476
+ }
104477
+ /** Import (and cache) an entry point module. Rejects for non-conforming modules. */
104478
+ async load(entryPoint) {
104479
+ const cached = this.loaded.get(entryPoint);
104480
+ if (cached !== void 0) return cached;
104481
+ const module = normalizeExtensionModule(await import(entryPoint));
104482
+ this.loaded.set(entryPoint, module);
104483
+ return module;
104484
+ }
104485
+ /** Activate a discovered plugin against a live agent. */
104486
+ async activate(agent, extension) {
104487
+ if (this.activations.has(extension.pluginId)) throw new Error(`Plugin "${extension.pluginId}" is already active`);
104488
+ const module = await this.load(extension.entryPoint);
104489
+ let hooksUnregister;
104490
+ if (extension.manifest.hooks !== void 0 && extension.manifest.hooks.length > 0) hooksUnregister = agent.hooks?.registerAll(extension.manifest.hooks);
104491
+ const context = {
104492
+ services: agent.services,
104493
+ events: agent.eventBus,
104494
+ config: extension.manifest.config,
104495
+ pluginId: extension.pluginId
104496
+ };
104497
+ try {
104498
+ await module.activate(context);
104499
+ } catch (error) {
104500
+ hooksUnregister?.();
104501
+ throw error;
104502
+ }
104503
+ this.activations.set(extension.pluginId, {
104504
+ module,
104505
+ hooksUnregister
104506
+ });
104507
+ }
104508
+ /** Deactivate a plugin: remove its hooks and call its deactivate hook. */
104509
+ async deactivate(pluginId) {
104510
+ const activation = this.activations.get(pluginId);
104511
+ if (activation === void 0) return;
104512
+ activation.hooksUnregister?.();
104513
+ this.activations.delete(pluginId);
104514
+ await activation.module.deactivate?.();
104515
+ }
104516
+ isActive(pluginId) {
104517
+ return this.activations.has(pluginId);
104518
+ }
104519
+ activePluginIds() {
104520
+ return [...this.activations.keys()];
104521
+ }
104522
+ };
104523
+ function normalizeExtensionModule(mod) {
104524
+ const candidate = mod?.default ?? mod;
104525
+ if (candidate === void 0 || typeof candidate.activate !== "function") throw new Error("Plugin entry point must export an activate(context) function");
104526
+ return candidate;
104527
+ }
104528
+ //#endregion
104278
104529
  //#region ../../packages/agent-core/src/tools/providers/fetch-cache.ts
104279
104530
  /**
104280
104531
  * Simple in-memory LRU cache for URL fetch results.
@@ -120593,6 +120844,8 @@ var ScreamCore = class {
120593
120844
  subagentModelBindings;
120594
120845
  sessionStore;
120595
120846
  plugins;
120847
+ /** Loads/activates code-entry plugins (manifest `entryPoint`). */
120848
+ extensionRuntime = new ExtensionRuntime();
120596
120849
  pluginsReady;
120597
120850
  pluginsLoadError;
120598
120851
  constructor(rpcClient, options = {}) {
@@ -120892,6 +121145,37 @@ var ScreamCore = class {
120892
121145
  registerTool({ sessionId, ...payload }) {
120893
121146
  return this.sessionApi(sessionId).registerTool(payload);
120894
121147
  }
121148
+ /**
121149
+ * Activate a code plugin (one with a manifest `entryPoint`) on the session's
121150
+ * main agent: injects declared manifest hooks into the agent's HookEngine and
121151
+ * calls the plugin's `activate(context)`.
121152
+ */
121153
+ async activatePlugin({ sessionId, pluginId }) {
121154
+ await this.pluginsReady;
121155
+ const session = this.sessions.get(sessionId);
121156
+ if (session === void 0) throw new ScreamError(ErrorCodes.SESSION_NOT_FOUND, `Session "${sessionId}" was not found`, { details: { sessionId } });
121157
+ const agent = session.agents.get("main");
121158
+ if (agent === void 0) throw new ScreamError(ErrorCodes.AGENT_NOT_FOUND, `Session "${sessionId}" has no main agent`, { details: { sessionId } });
121159
+ const plugin = this.plugins.list().find((record) => record.id === pluginId);
121160
+ if (plugin === void 0) throw new ScreamError(ErrorCodes.PLUGIN_NOT_FOUND, `Plugin "${pluginId}" was not found`, { details: { pluginId } });
121161
+ const [extension] = this.extensionRuntime.discover([plugin]);
121162
+ if (extension === void 0) throw new ScreamError(ErrorCodes.PLUGIN_NOT_FOUND, `Plugin "${pluginId}" has no code entry point`, { details: { pluginId } });
121163
+ await this.extensionRuntime.activate(agent, extension);
121164
+ }
121165
+ /** Deactivate a code plugin (removes its hooks and runs its deactivate). */
121166
+ async deactivatePlugin({ pluginId }) {
121167
+ await this.pluginsReady;
121168
+ await this.extensionRuntime.deactivate(pluginId);
121169
+ }
121170
+ /** Code plugins the runtime can load, with their activation state. */
121171
+ async pluginExtensionStatus() {
121172
+ await this.pluginsReady;
121173
+ return this.extensionRuntime.discover(this.plugins.list()).map((extension) => ({
121174
+ pluginId: extension.pluginId,
121175
+ entryPoint: extension.entryPoint,
121176
+ active: this.extensionRuntime.isActive(extension.pluginId)
121177
+ }));
121178
+ }
120895
121179
  unregisterTool({ sessionId, ...payload }) {
120896
121180
  return this.sessionApi(sessionId).unregisterTool(payload);
120897
121181
  }
@@ -121792,6 +122076,18 @@ var SDKRpcClient = class {
121792
122076
  args: input.args
121793
122077
  });
121794
122078
  }
122079
+ async activatePlugin(input) {
122080
+ return (await this.getRpc()).activatePlugin({
122081
+ sessionId: input.sessionId,
122082
+ pluginId: input.pluginId
122083
+ });
122084
+ }
122085
+ async deactivatePlugin(input) {
122086
+ return (await this.getRpc()).deactivatePlugin({ pluginId: input.pluginId });
122087
+ }
122088
+ async pluginExtensionStatus(input) {
122089
+ return (await this.getRpc()).pluginExtensionStatus({ sessionId: input.sessionId });
122090
+ }
121795
122091
  async undoHistory(input) {
121796
122092
  return (await this.getRpc()).undoHistory({
121797
122093
  sessionId: input.sessionId,
@@ -122261,6 +122557,30 @@ var Session = class {
122261
122557
  });
122262
122558
  }
122263
122559
  /**
122560
+ * Activate a code-entry plugin (manifest `entryPoint`) on the session's main
122561
+ * agent. Lazy and isolated: the plugin's code runs only after this call.
122562
+ */
122563
+ async activatePlugin(pluginId) {
122564
+ this.ensureOpen();
122565
+ await this.rpc.activatePlugin({
122566
+ sessionId: this.id,
122567
+ pluginId
122568
+ });
122569
+ }
122570
+ /** Deactivate a code-entry plugin (removes its hooks, runs its deactivate). */
122571
+ async deactivatePlugin(pluginId) {
122572
+ this.ensureOpen();
122573
+ await this.rpc.deactivatePlugin({
122574
+ sessionId: this.id,
122575
+ pluginId
122576
+ });
122577
+ }
122578
+ /** Code plugins the runtime can load, with their activation state. */
122579
+ async pluginExtensionStatus() {
122580
+ this.ensureOpen();
122581
+ return this.rpc.pluginExtensionStatus({ sessionId: this.id });
122582
+ }
122583
+ /**
122264
122584
  * Remove the last N user-prompt turns from the conversation history.
122265
122585
  * The TUI is responsible for cleaning up the corresponding transcript
122266
122586
  * entries and UI components after this call succeeds.
@@ -123214,16 +123534,20 @@ const BUILTIN_SLASH_COMMANDS = [
123214
123534
  availability: "always"
123215
123535
  },
123216
123536
  {
123217
- name: "skill",
123218
- aliases: [
123219
- "skills",
123220
- "plugin",
123221
- "plugins"
123222
- ],
123223
- description: "registry.skill_desc",
123537
+ name: "plugin",
123538
+ aliases: ["skills", "plugins"],
123539
+ hiddenAliases: ["skill"],
123540
+ description: "registry.plugin_desc",
123224
123541
  priority: 202,
123225
123542
  availability: "always"
123226
123543
  },
123544
+ {
123545
+ name: "extension",
123546
+ aliases: ["extensions"],
123547
+ description: "registry.extension_desc",
123548
+ priority: 203,
123549
+ availability: "always"
123550
+ },
123227
123551
  {
123228
123552
  name: "fork",
123229
123553
  aliases: [],
@@ -123400,7 +123724,7 @@ const BUILTIN_SLASH_COMMANDS = [
123400
123724
  }
123401
123725
  ];
123402
123726
  function findBuiltInSlashCommand(commandName) {
123403
- return BUILTIN_SLASH_COMMANDS.find((command) => command.name === commandName || command.aliases.includes(commandName));
123727
+ return BUILTIN_SLASH_COMMANDS.find((command) => command.name === commandName || command.aliases.includes(commandName) || command.hiddenAliases?.includes(commandName) === true);
123404
123728
  }
123405
123729
  function resolveSlashCommandAvailability(command, args) {
123406
123730
  const availability = command.availability ?? "idle-only";
@@ -129093,7 +129417,7 @@ async function guidedGoalSetup(host) {
129093
129417
  host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
129094
129418
  return;
129095
129419
  }
129096
- const { TextInputDialogComponent } = await import("./text-input-dialog-D8QuZFfe.mjs");
129420
+ const { TextInputDialogComponent } = await import("./text-input-dialog-DVWsE7Gh.mjs");
129097
129421
  const initialDesc = await promptText(host, TextInputDialogComponent, {
129098
129422
  title: t("goal.setup_title_initial"),
129099
129423
  subtitle: t("goal.setup_desc_hint"),
@@ -129114,7 +129438,7 @@ async function guidedGoalSetup(host) {
129114
129438
  await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
129115
129439
  }
129116
129440
  async function showGoalConfigWizard(host, session, objective, replace) {
129117
- const { TextInputDialogComponent } = await import("./text-input-dialog-D8QuZFfe.mjs");
129441
+ const { TextInputDialogComponent } = await import("./text-input-dialog-DVWsE7Gh.mjs");
129118
129442
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
129119
129443
  title: t("goal.wizard_title", { objective }),
129120
129444
  subtitle: t("goal.budget_turns_hint"),
@@ -133713,7 +134037,7 @@ async function findCcConnectResidualPaths(excludePath) {
133713
134037
  existing.push(p);
133714
134038
  } catch {}
133715
134039
  }
133716
- return existing.sort();
134040
+ return existing.toSorted();
133717
134041
  }
133718
134042
  async function handleCcCommand(host) {
133719
134043
  const daemon = resolveDaemonMode();
@@ -133753,7 +134077,7 @@ function runLifecycleAction(host, daemon, action) {
133753
134077
  host.refreshCcStatus();
133754
134078
  } else host.showError(t("cc.start_failed", {
133755
134079
  label,
133756
- output: output || "未知错误"
134080
+ output: output || t("cc.unknown_error")
133757
134081
  }));
133758
134082
  })();
133759
134083
  }
@@ -133763,7 +134087,7 @@ function buildUninstallSummary(daemon, install, residualPaths = []) {
133763
134087
  t("cc.will_clean"),
133764
134088
  t("cc.clean_daemon", { label: daemon.method }),
133765
134089
  t("cc.clean_config", { detail: t("cc.clean_config.detail") }),
133766
- "· 执行 npm uninstall -g cc-connect"
134090
+ t("cc.uninstall_step_npm")
133767
134091
  ];
133768
134092
  if (install.version) lines.push(t("cc.current_version", { version: install.version }));
133769
134093
  if (install.entry) lines.push(t("cc.install_path", { path: install.entry }));
@@ -134710,9 +135034,92 @@ function getPlatforms() {
134710
135034
  type: "wecom",
134711
135035
  setupCmd: "wecom setup --project default",
134712
135036
  note: t("ccconnect.note_wecom")
135037
+ },
135038
+ {
135039
+ name: "QQ 官方 bot",
135040
+ type: "qqbot",
135041
+ setupCmd: "qqbot setup --project default"
135042
+ },
135043
+ {
135044
+ name: "LINE",
135045
+ type: "line",
135046
+ setupCmd: "line setup --project default"
135047
+ },
135048
+ {
135049
+ name: "微博",
135050
+ type: "weibo",
135051
+ setupCmd: "weibo setup --project default"
135052
+ },
135053
+ {
135054
+ name: "WPS 协作",
135055
+ type: "wps-xiezuo",
135056
+ setupCmd: "wps-xiezuo setup --project default"
135057
+ },
135058
+ {
135059
+ name: "Matrix",
135060
+ type: "matrix",
135061
+ setupCmd: "matrix setup --project default",
135062
+ note: t("ccconnect.note_matrix")
135063
+ },
135064
+ {
135065
+ name: "Cisco Webex",
135066
+ type: "webex",
135067
+ setupCmd: "webex setup --project default",
135068
+ note: t("ccconnect.note_webex")
135069
+ },
135070
+ {
135071
+ name: "MAX",
135072
+ type: "max",
135073
+ setupCmd: "max setup --project default",
135074
+ note: t("ccconnect.note_max")
135075
+ },
135076
+ {
135077
+ name: "Google Chat",
135078
+ type: "googlechat",
135079
+ setupCmd: "googlechat setup --project default",
135080
+ note: t("ccconnect.note_googlechat")
135081
+ },
135082
+ {
135083
+ name: "Cloud Web",
135084
+ type: "cloud_web",
135085
+ setupCmd: "cloud_web setup --project default",
135086
+ note: t("ccconnect.note_cloud_web")
135087
+ },
135088
+ {
135089
+ name: "腾讯元宝",
135090
+ type: "yuanbao",
135091
+ setupCmd: "yuanbao setup --project default",
135092
+ note: t("ccconnect.note_yuanbao")
135093
+ },
135094
+ {
135095
+ name: "Tuitui",
135096
+ type: "tuitui",
135097
+ setupCmd: "tuitui setup --project default",
135098
+ note: t("ccconnect.note_tuitui")
135099
+ },
135100
+ {
135101
+ name: "WPS 数字员工",
135102
+ type: "wps-agentspace",
135103
+ setupCmd: "wps-agentspace setup --project default",
135104
+ note: t("ccconnect.note_wps_agentspace")
134713
135105
  }
134714
135106
  ];
134715
135107
  }
135108
+ /**
135109
+ * Platform types introduced by cc-connect v1.4/v1.5. Older cc-connect
135110
+ * binaries do not recognise these, so the post-config notice tells the user
135111
+ * to update cc-connect first.
135112
+ */
135113
+ const NEW_PLATFORM_TYPES = new Set([
135114
+ "matrix",
135115
+ "webex",
135116
+ "max",
135117
+ "googlechat",
135118
+ "cloud_web",
135119
+ "yuanbao",
135120
+ "tuitui",
135121
+ "wps-agentspace"
135122
+ ]);
134716
135123
  const CONFIG_PATH = join(homedir(), ".cc-connect", "config.toml");
134717
135124
  function checkCcConnect() {
134718
135125
  try {
@@ -134727,43 +135134,80 @@ function checkCcConnect() {
134727
135134
  return { installed: false };
134728
135135
  }
134729
135136
  }
135137
+ /** Auto-detect the path to the scream binary, including the stream-json subcommand. */
134730
135138
  function detectScreamPath() {
135139
+ const execBase = process.execPath.toLowerCase();
135140
+ if (execBase.endsWith("/scream") || execBase.endsWith("\\scream") || execBase.endsWith("scream.exe")) return `${quoteShellPath(process.execPath)} stream-json`;
135141
+ if (execBase.includes("node") && process.argv[1]) {
135142
+ const arg1 = process.argv[1];
135143
+ if (arg1.includes("scream-code") || arg1.includes("scream")) return `node ${quoteShellPath(arg1)} stream-json`;
135144
+ }
134731
135145
  try {
134732
135146
  const first = execSync(process.platform === "win32" ? "where scream" : "which scream 2>/dev/null", {
134733
135147
  encoding: "utf-8",
134734
135148
  timeout: 3e3
134735
135149
  }).trim().split(/[\r\n]+/)[0]?.trim() ?? "";
134736
- if (first) return `${first} stream-json`;
135150
+ if (first) return `${quoteShellPath(first)} stream-json`;
134737
135151
  } catch {}
134738
135152
  return "scream stream-json";
134739
135153
  }
134740
- function readConfiguredType() {
134741
- if (!existsSync(CONFIG_PATH)) return void 0;
134742
- try {
134743
- const content = readFileSync(CONFIG_PATH, "utf-8");
134744
- let inPlatforms = false;
134745
- for (const line of content.split("\n")) {
134746
- const trimmed = line.trim();
134747
- if (trimmed === "[[projects.platforms]]") {
134748
- inPlatforms = true;
134749
- continue;
134750
- }
134751
- if (trimmed.startsWith("[[") && trimmed !== "[[projects.platforms]]") {
134752
- inPlatforms = false;
134753
- continue;
134754
- }
134755
- if (inPlatforms) {
134756
- const m = line.match(/^type\s*=\s*"(\S+)"/);
134757
- if (m) return m[1];
134758
- }
135154
+ /**
135155
+ * Double-quote a path for shell parsing. cc-connect runs `cmd` through a
135156
+ * shell, so an unquoted path containing spaces would be split apart.
135157
+ */
135158
+ function quoteShellPath(path) {
135159
+ return `"${path.replaceAll(/"/g, "\\\"")}"`;
135160
+ }
135161
+ /** Parse every configured platform type from config.toml content. */
135162
+ function parseConfiguredTypes(content) {
135163
+ const types = [];
135164
+ let inPlatforms = false;
135165
+ for (const line of content.split("\n")) {
135166
+ const trimmed = line.trim();
135167
+ if (trimmed.startsWith("[[projects.platforms]]")) {
135168
+ inPlatforms = true;
135169
+ continue;
134759
135170
  }
134760
- return;
135171
+ if (trimmed.startsWith("[")) {
135172
+ inPlatforms = false;
135173
+ continue;
135174
+ }
135175
+ if (inPlatforms) {
135176
+ const m = trimmed.match(/^type\s*=\s*["']([^"']+)["']/);
135177
+ if (m?.[1]) types.push(m[1]);
135178
+ }
135179
+ }
135180
+ return types;
135181
+ }
135182
+ function readConfiguredTypes() {
135183
+ if (!existsSync(CONFIG_PATH)) return [];
135184
+ try {
135185
+ return parseConfiguredTypes(readFileSync(CONFIG_PATH, "utf-8"));
134761
135186
  } catch {
134762
- return;
135187
+ return [];
134763
135188
  }
134764
135189
  }
134765
- function escapeSingleQuotes(str) {
134766
- return str.replaceAll("'", "\\'");
135190
+ /** True when config content already has an active (non-comment) platform of this type. */
135191
+ function hasPlatformConfigured(content, type) {
135192
+ const escaped = type.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
135193
+ return new RegExp(`^\\s*type\\s*=\\s*["']${escaped}["']`, "m").test(content);
135194
+ }
135195
+ /** Minimal semver-ish check: "1.5.0" >= "1.5.0". */
135196
+ function isVersionAtLeast(version, min) {
135197
+ if (!version) return false;
135198
+ const parse = (v) => v.split(".").map((n) => Number.parseInt(n, 10) || 0);
135199
+ const [a = 0, b = 0, c = 0] = parse(version);
135200
+ const [x = 0, y = 0, z = 0] = parse(min);
135201
+ return a > x || a === x && (b > y || b === y && c >= z);
135202
+ }
135203
+ /**
135204
+ * Quote a value for TOML. Literal strings ('...') cannot contain a single
135205
+ * quote at all (no escape mechanism), so values with one fall back to a
135206
+ * basic string ("...") where backslash and double-quote are escaped.
135207
+ */
135208
+ function tomlString(value) {
135209
+ if (!value.includes("'")) return `'${value}'`;
135210
+ return `"${value.replaceAll(/\\/g, "\\\\").replaceAll(/"/g, "\\\"")}"`;
134767
135211
  }
134768
135212
  function generateConfig(platform) {
134769
135213
  const dir = dirname$1(CONFIG_PATH);
@@ -134771,11 +135215,15 @@ function generateConfig(platform) {
134771
135215
  const platformBlock = `\n[[projects.platforms]]\ntype = "${platform.type}"\n`;
134772
135216
  if (existsSync(CONFIG_PATH)) {
134773
135217
  const existing = readFileSync(CONFIG_PATH, "utf-8");
134774
- if (existing.includes(`type = "${platform.type}"`)) return;
135218
+ if (hasPlatformConfigured(existing, platform.type)) return;
134775
135219
  writeFileSync(CONFIG_PATH, existing + platformBlock, "utf-8");
134776
135220
  return;
134777
135221
  }
134778
- writeFileSync(CONFIG_PATH, [
135222
+ writeFileSync(CONFIG_PATH, buildConfigContent(platform, detectScreamPath(), process.cwd()), "utf-8");
135223
+ }
135224
+ /** Build the full config.toml content for a fresh setup. */
135225
+ function buildConfigContent(platform, screamCmd, cwd) {
135226
+ return [
134779
135227
  t("ccconnect.config_comment_attachment"),
134780
135228
  "attachment_send = \"on\"",
134781
135229
  "",
@@ -134786,21 +135234,22 @@ function generateConfig(platform) {
134786
135234
  "type = \"claudecode\"",
134787
135235
  "",
134788
135236
  "[projects.agent.options]",
134789
- `cmd = '${escapeSingleQuotes(detectScreamPath())}'`,
134790
- `work_dir = '${escapeSingleQuotes(process.cwd())}'`,
134791
- "mode = \"default\"",
135237
+ `cmd = ${tomlString(screamCmd)}`,
135238
+ `work_dir = ${tomlString(cwd)}`,
135239
+ "# Permission mode: default (confirm each tool) | acceptEdits | plan | auto | bypassPermissions (yolo) | dontAsk",
135240
+ "mode = \"auto\"",
134792
135241
  "",
134793
135242
  "[[projects.platforms]]",
134794
135243
  `type = "${platform.type}"`,
134795
135244
  ""
134796
- ].join("\n"), "utf-8");
135245
+ ].join("\n");
134797
135246
  }
134798
135247
  const SEP = "──".repeat(20);
134799
135248
  /**
134800
135249
  * Build the full notice text shown after platform selection.
134801
135250
  * Common management commands come first; detailed setup steps follow.
134802
135251
  */
134803
- function buildNoticeText(platform, isReconfigure) {
135252
+ function buildNoticeText(platform, isReconfigure, installedVersion) {
134804
135253
  const daemon = getDaemonInstructions(dirname$1(CONFIG_PATH));
134805
135254
  const parts = [];
134806
135255
  if (isReconfigure) {
@@ -134812,6 +135261,10 @@ function buildNoticeText(platform, isReconfigure) {
134812
135261
  parts.push("");
134813
135262
  parts.push(t("ccconnect.config_written", { path: CONFIG_PATH }));
134814
135263
  }
135264
+ if (NEW_PLATFORM_TYPES.has(platform.type) && !isVersionAtLeast(installedVersion, "1.5.0")) {
135265
+ parts.push("");
135266
+ parts.push(t("ccconnect.new_platform_version_hint"));
135267
+ }
134815
135268
  parts.push("");
134816
135269
  parts.push(t("ccconnect.quick_ref"));
134817
135270
  parts.push("");
@@ -134865,13 +135318,14 @@ function buildNoticeText(platform, isReconfigure) {
134865
135318
  return parts.join("\n");
134866
135319
  }
134867
135320
  async function handleChannelCommand(host, _args) {
134868
- if (!checkCcConnect().installed) {
135321
+ const cc = checkCcConnect();
135322
+ if (!cc.installed) {
134869
135323
  host.showNotice(t("ccconnect.not_installed"), t("ccconnect.install_guide"));
134870
135324
  return;
134871
135325
  }
134872
- const configuredType = readConfiguredType();
135326
+ const configuredTypes = readConfiguredTypes();
134873
135327
  const options = getPlatforms().map((p) => {
134874
- const isConfigured = configuredType === p.type;
135328
+ const isConfigured = configuredTypes.includes(p.type);
134875
135329
  return {
134876
135330
  value: p.type,
134877
135331
  label: isConfigured ? t("ccconnect.already_configured", { name: p.name }) : p.name,
@@ -134882,7 +135336,7 @@ async function handleChannelCommand(host, _args) {
134882
135336
  title: t("ccconnect.picker_title"),
134883
135337
  hint: t("ccconnect.picker_hint"),
134884
135338
  options,
134885
- currentValue: configuredType,
135339
+ currentValue: configuredTypes[0],
134886
135340
  colors: host.state.theme.colors,
134887
135341
  onSelect: (value) => {
134888
135342
  host.restoreEditor();
@@ -134891,12 +135345,12 @@ async function handleChannelCommand(host, _args) {
134891
135345
  host.showError(t("error.internal"));
134892
135346
  return;
134893
135347
  }
134894
- if (configuredType === value) {
134895
- host.showNotice(t("ccconnect.reconfigured", { name: platform.name }), buildNoticeText(platform, true));
135348
+ if (configuredTypes.includes(value)) {
135349
+ host.showNotice(t("ccconnect.reconfigured", { name: platform.name }), buildNoticeText(platform, true, cc.version));
134896
135350
  return;
134897
135351
  }
134898
135352
  generateConfig(platform);
134899
- host.showNotice(t("ccconnect.config_done", { name: platform.name }), buildNoticeText(platform, false));
135353
+ host.showNotice(t("ccconnect.config_done", { name: platform.name }), buildNoticeText(platform, false, cc.version));
134900
135354
  },
134901
135355
  onCancel: () => {
134902
135356
  host.restoreEditor();
@@ -135156,13 +135610,13 @@ function disposeChildren(container) {
135156
135610
  const SKILL_DESC_MAX = 60;
135157
135611
  async function handleSkillCommand(host, _args) {
135158
135612
  if (!host.session) {
135159
- host.showError(t("skill.no_session"));
135613
+ host.showError(t("plugin.no_session"));
135160
135614
  return;
135161
135615
  }
135162
135616
  await openSkillCenter(host);
135163
135617
  }
135164
135618
  async function openSkillCenter(host) {
135165
- const loading = new SkillCenterLoadingComponent(host, t("skill.loading"));
135619
+ const loading = new SkillCenterLoadingComponent(host, t("plugin.loading"));
135166
135620
  host.mountEditorReplacement(loading);
135167
135621
  const [skillsResult, pluginsResult, marketplaceResult] = await Promise.allSettled([
135168
135622
  loadActivatableSkills(host),
@@ -135177,12 +135631,12 @@ async function openSkillCenter(host) {
135177
135631
  const options = buildOptions(host, skills, plugins, marketplace);
135178
135632
  if (options.length === 0) {
135179
135633
  host.restoreEditor();
135180
- host.showNotice(t("skill.center_title"), t("skill.no_skills"));
135634
+ host.showNotice(t("plugin.center_title"), t("plugin.no_plugins"));
135181
135635
  return;
135182
135636
  }
135183
135637
  const picker = new ChoicePickerComponent({
135184
- title: t("skill.center_title"),
135185
- hint: t("skill.footer_hint"),
135638
+ title: t("plugin.center_title"),
135639
+ hint: t("plugin.footer_hint"),
135186
135640
  options,
135187
135641
  colors: host.state.theme.colors,
135188
135642
  searchable: true,
@@ -135262,7 +135716,7 @@ function buildOptions(host, skills, plugins, marketplace) {
135262
135716
  if (skills.length > 0) {
135263
135717
  options.push({
135264
135718
  value: "__section__installed",
135265
- label: "── " + t("skill.installed") + " ──"
135719
+ label: "── " + t("plugin.installed") + " ──"
135266
135720
  });
135267
135721
  for (const skill of skills) {
135268
135722
  const actionKeys = {};
@@ -135289,12 +135743,12 @@ function buildOptions(host, skills, plugins, marketplace) {
135289
135743
  if (installable.length > 0) {
135290
135744
  options.push({
135291
135745
  value: "__section__installable",
135292
- label: "── " + t("skill.installable") + " ──"
135746
+ label: "── " + t("plugin.installable") + " ──"
135293
135747
  });
135294
135748
  for (const entry of installable) options.push({
135295
135749
  value: `install:${entry.source}`,
135296
135750
  label: entry.displayName,
135297
- description: entry.description ? `${truncate(entry.description, SKILL_DESC_MAX)} [${t("skill.not_installed")}]` : `[${t("skill.not_installed")}]`,
135751
+ description: entry.description ? `${truncate(entry.description, SKILL_DESC_MAX)} [${t("plugin.not_installed")}]` : `[${t("plugin.not_installed")}]`,
135298
135752
  actionKeys: { i: () => {
135299
135753
  host.restoreEditor();
135300
135754
  installInjectActivate(host, entry.source);
@@ -135321,12 +135775,12 @@ async function handleSelect(host, value, skills, _plugins, _marketplace) {
135321
135775
  async function activateSkillByName(host, name, skills) {
135322
135776
  const session = host.session;
135323
135777
  if (!session) {
135324
- host.showError(t("skill.no_session_activate"));
135778
+ host.showError(t("plugin.no_session_activate"));
135325
135779
  return;
135326
135780
  }
135327
135781
  const skill = skills.find((s) => s.name === name);
135328
135782
  if (!skill) {
135329
- host.showError(t("skill.not_found"));
135783
+ host.showError(t("plugin.not_found"));
135330
135784
  return;
135331
135785
  }
135332
135786
  host.sendSkillActivation(session, skill.name, "");
@@ -135334,20 +135788,20 @@ async function activateSkillByName(host, name, skills) {
135334
135788
  async function installInjectActivate(host, source) {
135335
135789
  const session = host.session;
135336
135790
  if (!session) {
135337
- host.showError(t("skill.no_session_activate"));
135791
+ host.showError(t("plugin.no_session_activate"));
135338
135792
  return;
135339
135793
  }
135340
- const spinner = host.showProgressSpinner(t("skill.installing_package"));
135794
+ const spinner = host.showProgressSpinner(t("plugin.installing_package"));
135341
135795
  try {
135342
135796
  const summary = await session.installPlugin(source);
135343
135797
  await session.injectPlugin(summary.id);
135344
135798
  spinner.stop({
135345
135799
  ok: true,
135346
- label: `"${summary.displayName}" ${t("skill.installed_injected")}`
135800
+ label: `"${summary.displayName}" ${t("plugin.installed_injected")}`
135347
135801
  });
135348
135802
  const pluginSkills = (await session.listSkills()).filter((s) => s.pluginId === summary.id && isUserActivatableSkill(s));
135349
135803
  if (pluginSkills.length === 0) {
135350
- host.showNotice(t("skill.plugin_installed"), `${summary.displayName} ${t("skill.no_manual_skill")}`);
135804
+ host.showNotice(t("plugin.plugin_installed"), `${summary.displayName} ${t("plugin.no_manual_plugin")}`);
135351
135805
  return;
135352
135806
  }
135353
135807
  if (pluginSkills.length === 1) {
@@ -135359,9 +135813,9 @@ async function installInjectActivate(host, source) {
135359
135813
  } catch (error) {
135360
135814
  spinner.stop({
135361
135815
  ok: false,
135362
- label: t("skill.install_failed")
135816
+ label: t("plugin.install_failed")
135363
135817
  });
135364
- host.showError(t("skill.install_failed_msg", { msg: error instanceof Error ? error.message : String(error) }));
135818
+ host.showError(t("plugin.install_failed_msg", { msg: error instanceof Error ? error.message : String(error) }));
135365
135819
  }
135366
135820
  }
135367
135821
  async function pickAndActivateSkill(host, skills, plugins = []) {
@@ -135373,8 +135827,8 @@ async function pickAndActivateSkill(host, skills, plugins = []) {
135373
135827
  description: formatSkillDescription(skill, plugins)
135374
135828
  }));
135375
135829
  const picker = new ChoicePickerComponent({
135376
- title: t("skill.select_activate"),
135377
- hint: t("skill.select_hint"),
135830
+ title: t("plugin.select_activate"),
135831
+ hint: t("plugin.select_hint"),
135378
135832
  options,
135379
135833
  colors: host.state.theme.colors,
135380
135834
  searchable: true,
@@ -135392,7 +135846,7 @@ async function pickAndActivateSkill(host, skills, plugins = []) {
135392
135846
  async function uninstallByPluginId(host, pluginId, plugin) {
135393
135847
  const session = host.session;
135394
135848
  if (!session) {
135395
- host.showError(t("skill.no_session_activate"));
135849
+ host.showError(t("plugin.no_session_activate"));
135396
135850
  return;
135397
135851
  }
135398
135852
  if (plugin === void 0) try {
@@ -135400,24 +135854,24 @@ async function uninstallByPluginId(host, pluginId, plugin) {
135400
135854
  } catch {}
135401
135855
  const label = plugin?.displayName ?? pluginId;
135402
135856
  const skillCount = plugin?.skillCount;
135403
- if (!await confirmUninstall(host, label, skillCount !== void 0 && skillCount > 0 ? t("skill.uninstall_whole_pkg", { count: skillCount }) : t("skill.uninstall_single"))) {
135857
+ if (!await confirmUninstall(host, label, skillCount !== void 0 && skillCount > 0 ? t("plugin.uninstall_whole_pkg", { count: skillCount }) : t("plugin.uninstall_single"))) {
135404
135858
  await openSkillCenter(host);
135405
135859
  return;
135406
135860
  }
135407
- const spinner = host.showProgressSpinner(`${t("skill.uninstalling")} "${label}"…`);
135861
+ const spinner = host.showProgressSpinner(`${t("plugin.uninstalling")} "${label}"…`);
135408
135862
  try {
135409
135863
  await session.removePlugin(pluginId);
135410
135864
  spinner.stop({
135411
135865
  ok: true,
135412
- label: `"${label}" ${t("skill.uninstalled")}`
135866
+ label: `"${label}" ${t("plugin.uninstalled")}`
135413
135867
  });
135414
- host.showNotice(t("skill.plugin_uninstalled"), t("skill.plugin_removed"));
135868
+ host.showNotice(t("plugin.plugin_uninstalled"), t("plugin.plugin_removed"));
135415
135869
  } catch (error) {
135416
135870
  spinner.stop({
135417
135871
  ok: false,
135418
- label: t("skill.uninstall_failed")
135872
+ label: t("plugin.uninstall_failed")
135419
135873
  });
135420
- host.showError(t("skill.uninstall_failed_msg", { msg: error instanceof Error ? error.message : String(error) }));
135874
+ host.showError(t("plugin.uninstall_failed_msg", { msg: error instanceof Error ? error.message : String(error) }));
135421
135875
  } finally {
135422
135876
  await openSkillCenter(host);
135423
135877
  }
@@ -135425,27 +135879,27 @@ async function uninstallByPluginId(host, pluginId, plugin) {
135425
135879
  async function uninstallManualSkill(host, skill) {
135426
135880
  const session = host.session;
135427
135881
  if (!session) {
135428
- host.showError(t("skill.no_session_activate"));
135882
+ host.showError(t("plugin.no_session_activate"));
135429
135883
  return;
135430
135884
  }
135431
- if (!await confirmUninstall(host, skill.name, t("skill.deleting_skill"))) {
135885
+ if (!await confirmUninstall(host, skill.name, t("plugin.deleting_plugin"))) {
135432
135886
  await openSkillCenter(host);
135433
135887
  return;
135434
135888
  }
135435
- const spinner = host.showProgressSpinner(`${t("skill.deleting")} "${skill.name}"…`);
135889
+ const spinner = host.showProgressSpinner(`${t("plugin.deleting")} "${skill.name}"…`);
135436
135890
  try {
135437
135891
  await session.removeSkill(skill.name);
135438
135892
  spinner.stop({
135439
135893
  ok: true,
135440
- label: `"${skill.name}" ${t("skill.deleted")}`
135894
+ label: `"${skill.name}" ${t("plugin.deleted")}`
135441
135895
  });
135442
- host.showNotice(t("skill.skill_deleted"), t("skill.skill_removed"));
135896
+ host.showNotice(t("plugin.plugin_deleted"), t("plugin.skill_removed"));
135443
135897
  } catch (error) {
135444
135898
  spinner.stop({
135445
135899
  ok: false,
135446
- label: t("skill.delete_failed")
135900
+ label: t("plugin.delete_failed")
135447
135901
  });
135448
- host.showError(t("skill.delete_failed_msg", { msg: error instanceof Error ? error.message : String(error) }));
135902
+ host.showError(t("plugin.delete_failed_msg", { msg: error instanceof Error ? error.message : String(error) }));
135449
135903
  } finally {
135450
135904
  await openSkillCenter(host);
135451
135905
  }
@@ -135453,14 +135907,14 @@ async function uninstallManualSkill(host, skill) {
135453
135907
  async function confirmUninstall(host, label, description) {
135454
135908
  return new Promise((resolve) => {
135455
135909
  const picker = new ChoicePickerComponent({
135456
- title: t("skill.confirm_uninstall", { label }),
135457
- hint: t("skill.uninstall_reversible"),
135910
+ title: t("plugin.confirm_uninstall", { label }),
135911
+ hint: t("plugin.uninstall_reversible"),
135458
135912
  options: [{
135459
135913
  value: "no",
135460
135914
  label: t("common.cancel")
135461
135915
  }, {
135462
135916
  value: "yes",
135463
- label: t("skill.uninstall_yes"),
135917
+ label: t("plugin.uninstall_yes"),
135464
135918
  tone: "danger",
135465
135919
  description
135466
135920
  }],
@@ -135479,10 +135933,10 @@ async function confirmUninstall(host, label, description) {
135479
135933
  }
135480
135934
  function formatSkillDescription(skill, plugins = []) {
135481
135935
  const parts = [];
135482
- if (skill.source) parts.push(`${t("skill.source_label")} ${skill.source}`);
135936
+ if (skill.source) parts.push(`${t("plugin.source_label")} ${skill.source}`);
135483
135937
  if (skill.pluginId !== void 0) {
135484
135938
  const label = plugins.find((p) => p.id === skill.pluginId)?.displayName ?? skill.pluginId;
135485
- parts.push(`${t("skill.plugin_label")} ${label}`);
135939
+ parts.push(`${t("plugin.plugin_label")} ${label}`);
135486
135940
  }
135487
135941
  if (skill.description) parts.push(truncate(skill.description, SKILL_DESC_MAX));
135488
135942
  return parts.join(" · ");
@@ -135491,6 +135945,68 @@ function truncate(value, max) {
135491
135945
  return value.length > max ? `${value.slice(0, max)}…` : value;
135492
135946
  }
135493
135947
  //#endregion
135948
+ //#region src/tui/commands/extension.ts
135949
+ /**
135950
+ * /extension activate|deactivate|status [pluginId]
135951
+ *
135952
+ * Activates and deactivates code-entry plugins (manifests that declare an
135953
+ * `entryPoint`) on the session's main agent, or lists them with their
135954
+ * activation state. Activation is lazy and isolated: a failing extension never
135955
+ * breaks the agent.
135956
+ */
135957
+ async function handleExtensionCommand(host, args) {
135958
+ const session = host.session;
135959
+ if (!session) {
135960
+ host.showError(t("extension.no_session"));
135961
+ return;
135962
+ }
135963
+ const [action, ...rest] = args.trim().split(/\s+/);
135964
+ const pluginId = rest.join(" ").trim();
135965
+ switch (action === "" || action === void 0 ? "status" : action) {
135966
+ case "activate":
135967
+ if (!pluginId) {
135968
+ host.showError(t("extension.missing_id", { action: "activate" }));
135969
+ return;
135970
+ }
135971
+ try {
135972
+ await session.activatePlugin(pluginId);
135973
+ host.showStatus(t("extension.activated", { pluginId }));
135974
+ } catch (error) {
135975
+ host.showError(getErrorMessage(error));
135976
+ }
135977
+ return;
135978
+ case "deactivate":
135979
+ if (!pluginId) {
135980
+ host.showError(t("extension.missing_id", { action: "deactivate" }));
135981
+ return;
135982
+ }
135983
+ try {
135984
+ await session.deactivatePlugin(pluginId);
135985
+ host.showStatus(t("extension.deactivated", { pluginId }));
135986
+ } catch (error) {
135987
+ host.showError(getErrorMessage(error));
135988
+ }
135989
+ return;
135990
+ case "status":
135991
+ try {
135992
+ const extensions = await session.pluginExtensionStatus();
135993
+ if (extensions.length === 0) {
135994
+ host.showStatus(t("extension.no_extensions"));
135995
+ return;
135996
+ }
135997
+ for (const ext of extensions) host.showStatus(`${ext.active ? "●" : "○"} ${ext.pluginId}`);
135998
+ } catch (error) {
135999
+ host.showError(getErrorMessage(error));
136000
+ }
136001
+ return;
136002
+ default: host.showError(t("extension.usage"));
136003
+ }
136004
+ }
136005
+ function getErrorMessage(error) {
136006
+ if (error === null || error === void 0) return String(error);
136007
+ return String(error?.message ?? error);
136008
+ }
136009
+ //#endregion
135494
136010
  //#region src/tui/commands/btw.ts
135495
136011
  /**
135496
136012
  * /btw — Fast side question without interrupting the main conversation.
@@ -138505,13 +139021,16 @@ async function handleBuiltInSlashCommand(host, name, args) {
138505
139021
  case "make-skill":
138506
139022
  await handleMakeSkillCommand(host, args);
138507
139023
  return;
138508
- case "skill":
139024
+ case "plugin":
138509
139025
  await handleSkillCommand(host, args);
138510
139026
  return;
139027
+ case "extension":
139028
+ await handleExtensionCommand(host, args);
139029
+ return;
138511
139030
  default:
138512
139031
  host.showError(`Unknown slash command: /${String(name)}`);
138513
139032
  return;
138514
139033
  }
138515
139034
  }
138516
139035
  //#endregion
138517
- export { toTerminalHyperlink as $, parseStreamingArgs as $t, highlightLines as A, CLI_UI_MODE as An, ENABLE_TERMINAL_THEME_REPORTING as At, AssistantMessageComponent as B, flushDiagnosticLogs as Bn, isBusy as Bt, UserMessageComponent as C, saveTuiConfig as Cn, contrastTextHex as Ct, ToolCallComponent as D, getLogDir as Dn, DISABLE_TERMINAL_FOCUS_REPORTING as Dt, toggleEmptySessionHint as E, getInputHistoryFile as En, parseOsc11BackgroundTheme as Et, getSharedSpeedTracker as F, saveCatalogCache as Fn, QUERY_TERMINAL_THEME as Ft, resetBreathingClock as G, ErrorCodes as Gn, handleConnectCommand as Gt, WelcomeComponent as H, resolveGlobalLogPath as Hn, FooterComponent as Ht, SkillActivationComponent as I, ScreamHarness as In, TERMINAL_FOCUS_IN as It, handleExportDebugZipCommand as J, STATUS_BULLET as Jt, clearGoalState as K, SCREAM_ERROR_INFO as Kn, handleLogoutCommand as Kt, ReadGroupComponent as L, MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE as Ln, TERMINAL_FOCUS_OUT as Lt, CachedContainer as M, PRODUCT_NAME as Mn, OSC11_RESPONSE as Mt, ThinkingComponent as N, DEFAULT_CATALOG_URL as Nn, OSC11_RESPONSE_PREFIX as Nt, renderDiffLines as O, detectInstallSource as On, DISABLE_TERMINAL_THEME_REPORTING as Ot, estimateTokens as P, fetchCatalog as Pn, OSC11_RESPONSE_PREFIX_NO_ESC as Pt, handleTitleCommand as Q, isTodoItemShape as Qt, parseReadGroupOutput as R, resolveScreamHome as Rn, TERMINAL_THEME_DARK as Rt, handleRevokeCommand as S, loadTuiConfig as Sn, createThemeStyles as St, isTurnElapsedEnabled as T, getDataDir as Tn, detectTerminalTheme as Tt, BREATHE_CYCLE_MS as U, isScreamError as Un, handleTraceCommand as Ut, AgentGroupComponent as V, log as Vn, isStreaming as Vt, getBreathingFrame as W, isOrphanedToolCallError as Wn, handleSearchCommand as Wt, handleForkCommand as X, argsRecord as Xt, handleExportMdCommand as Y, appendStreamingArgsPreview as Yt, handleInitCommand as Z, formatErrorMessage as Zt, readUpdateCache as _, setExperimentalFlags as _n, showStatusReport as _t, handleSkillCommand as a, EXIT_CONFIRM_WINDOW_MS as an, handleFusionPlanCommand as at, handleCcCommand as b, TuiConfigParseError as bn, createEditorTheme as bt, isPlanExpandable as c, TIP_ROTATION_INTERVAL_MS as cn, handleThemeCommand as ct, handleMemoryCommand as d, getLlmNotSetMessage as dn, showModelPicker as dt, serializeToolResultOutput as en, changeThinkingLevel as et, handleChannelCommand as f, getNoActiveSessionMessage as fn, showPermissionPicker as ft, refreshUpdateCache as g, isExperimentalFlagEnabled as gn, clearInfoPanelState as gt, selectUpdateTarget as h, sortSlashCommands as hn, supportsBalance as ht, buildRoleAdditionalText as i, EMPTY_SESSION_HINT_URL as in, handleEditorCommand as it, langFromPath as j, CLI_USER_AGENT_PRODUCT as jn, OSC11_QUERY as jt, renderDiffLinesClustered as k, CLI_COMMAND_NAME as kn, ENABLE_TERMINAL_FOCUS_REPORTING as kt, MoonLoader as l, getCtrlCHint as ln, handleWolfpackCommand as lt, handleUpdateCommand as m, BUILTIN_SLASH_COMMANDS as mn, refreshProviderBalance as mt, clearEvalPanelState as n, truncateErrorMessage as nn, handleAutoCommand as nt, disposeChildren as o, MAIN_AGENT_ID$1 as on, handleModelCommand as ot, handleMcpCommand as p, buildSkillSlashCommands as pn, showSettingsSelector as pt, refineGoal as q, printableChar as qt, openUrl as r, CHARS_PER_TOKEN as rn, handleCompactCommand as rt, hasDispose as s, SESSION_TIPS as sn, handlePlanCommand as st, dispatchInput as t, stringValue as tn, getModelCycleLevel as tt, formatMemoryMemoForInjection as u, getCtrlDHint as un, handleYoloCommand as ut, appendJsonlLine as v, PIXEL_PULSE_FRAMES as vn, showUsage as vt, isEmptySessionHintDismissed as w, detectShellEnvironment as wn, getColorPalette as wt, getDaemonInstructions as x, TuiLikePreferencesSchema as xn, createMarkdownTheme as xt, readJsonlFile as y, PULSE_WAVE_FRAMES as yn, resolveThemeSync as yt, BackgroundAgentStatusComponent as z, MemoryMemoStore as zn, TERMINAL_THEME_LIGHT as zt };
139036
+ export { handleTitleCommand as $, isTodoItemShape as $t, renderDiffLinesClustered as A, CLI_COMMAND_NAME as An, ENABLE_TERMINAL_FOCUS_REPORTING as At, BackgroundAgentStatusComponent as B, MemoryMemoStore as Bn, TERMINAL_THEME_LIGHT as Bt, handleRevokeCommand as C, loadTuiConfig as Cn, createThemeStyles as Ct, toggleEmptySessionHint as D, getInputHistoryFile as Dn, parseOsc11BackgroundTheme as Dt, isTurnElapsedEnabled as E, getDataDir as En, detectTerminalTheme as Et, estimateTokens as F, fetchCatalog as Fn, OSC11_RESPONSE_PREFIX_NO_ESC as Ft, getBreathingFrame as G, isOrphanedToolCallError as Gn, handleSearchCommand as Gt, AgentGroupComponent as H, log as Hn, isStreaming as Ht, getSharedSpeedTracker as I, saveCatalogCache as In, QUERY_TERMINAL_THEME as It, refineGoal as J, printableChar as Jt, resetBreathingClock as K, ErrorCodes as Kn, handleConnectCommand as Kt, SkillActivationComponent as L, ScreamHarness as Ln, TERMINAL_FOCUS_IN as Lt, langFromPath as M, CLI_USER_AGENT_PRODUCT as Mn, OSC11_QUERY as Mt, CachedContainer as N, PRODUCT_NAME as Nn, OSC11_RESPONSE as Nt, ToolCallComponent as O, getLogDir as On, DISABLE_TERMINAL_FOCUS_REPORTING as Ot, ThinkingComponent as P, DEFAULT_CATALOG_URL as Pn, OSC11_RESPONSE_PREFIX as Pt, handleInitCommand as Q, formatErrorMessage as Qt, ReadGroupComponent as R, MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE as Rn, TERMINAL_FOCUS_OUT as Rt, getDaemonInstructions as S, TuiLikePreferencesSchema as Sn, createMarkdownTheme as St, isEmptySessionHintDismissed as T, detectShellEnvironment as Tn, getColorPalette as Tt, WelcomeComponent as U, resolveGlobalLogPath as Un, FooterComponent as Ut, AssistantMessageComponent as V, flushDiagnosticLogs as Vn, isBusy as Vt, BREATHE_CYCLE_MS as W, isScreamError as Wn, handleTraceCommand as Wt, handleExportMdCommand as X, appendStreamingArgsPreview as Xt, handleExportDebugZipCommand as Y, STATUS_BULLET as Yt, handleForkCommand as Z, argsRecord as Zt, refreshUpdateCache as _, isExperimentalFlagEnabled as _n, clearInfoPanelState as _t, handleExtensionCommand as a, EMPTY_SESSION_HINT_URL as an, handleEditorCommand as at, readJsonlFile as b, PULSE_WAVE_FRAMES as bn, resolveThemeSync as bt, hasDispose as c, SESSION_TIPS as cn, handlePlanCommand as ct, formatMemoryMemoForInjection as d, getCtrlDHint as dn, handleYoloCommand as dt, parseStreamingArgs as en, toTerminalHyperlink as et, handleMemoryCommand as f, getLlmNotSetMessage as fn, showModelPicker as ft, selectUpdateTarget as g, sortSlashCommands as gn, supportsBalance as gt, handleUpdateCommand as h, BUILTIN_SLASH_COMMANDS as hn, refreshProviderBalance as ht, buildRoleAdditionalText as i, CHARS_PER_TOKEN as in, handleCompactCommand as it, highlightLines as j, CLI_UI_MODE as jn, ENABLE_TERMINAL_THEME_REPORTING as jt, renderDiffLines as k, detectInstallSource as kn, DISABLE_TERMINAL_THEME_REPORTING as kt, isPlanExpandable as l, TIP_ROTATION_INTERVAL_MS as ln, handleThemeCommand as lt, handleMcpCommand as m, buildSkillSlashCommands as mn, showSettingsSelector as mt, clearEvalPanelState as n, stringValue as nn, getModelCycleLevel as nt, handleSkillCommand as o, EXIT_CONFIRM_WINDOW_MS as on, handleFusionPlanCommand as ot, handleChannelCommand as p, getNoActiveSessionMessage as pn, showPermissionPicker as pt, clearGoalState as q, SCREAM_ERROR_INFO as qn, handleLogoutCommand as qt, openUrl as r, truncateErrorMessage as rn, handleAutoCommand as rt, disposeChildren as s, MAIN_AGENT_ID$1 as sn, handleModelCommand as st, dispatchInput as t, serializeToolResultOutput as tn, changeThinkingLevel as tt, MoonLoader as u, getCtrlCHint as un, handleWolfpackCommand as ut, readUpdateCache as v, setExperimentalFlags as vn, showStatusReport as vt, UserMessageComponent as w, saveTuiConfig as wn, contrastTextHex as wt, handleCcCommand as x, TuiConfigParseError as xn, createEditorTheme as xt, appendJsonlLine as y, PIXEL_PULSE_FRAMES as yn, showUsage as yt, parseReadGroupOutput as z, resolveScreamHome as zn, TERMINAL_THEME_DARK as zt };