@kolisachint/hoocode-agent 0.4.118 → 0.4.120

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/dist/core/agent-session.d.ts +35 -1
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +157 -1
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/extension-mcp-servers.d.ts +8 -1
  7. package/dist/core/extension-mcp-servers.d.ts.map +1 -1
  8. package/dist/core/extension-mcp-servers.js.map +1 -1
  9. package/dist/core/extensions/index.d.ts +1 -1
  10. package/dist/core/extensions/index.d.ts.map +1 -1
  11. package/dist/core/extensions/index.js.map +1 -1
  12. package/dist/core/extensions/plugins/authoring.d.ts.map +1 -1
  13. package/dist/core/extensions/plugins/authoring.js +5 -2
  14. package/dist/core/extensions/plugins/authoring.js.map +1 -1
  15. package/dist/core/extensions/plugins/formats/copilot.d.ts +16 -14
  16. package/dist/core/extensions/plugins/formats/copilot.d.ts.map +1 -1
  17. package/dist/core/extensions/plugins/formats/copilot.js +64 -46
  18. package/dist/core/extensions/plugins/formats/copilot.js.map +1 -1
  19. package/dist/core/extensions/plugins/formats/jsonManifest.d.ts.map +1 -1
  20. package/dist/core/extensions/plugins/formats/jsonManifest.js +6 -6
  21. package/dist/core/extensions/plugins/formats/jsonManifest.js.map +1 -1
  22. package/dist/core/extensions/plugins/formats/shared.d.ts +12 -1
  23. package/dist/core/extensions/plugins/formats/shared.d.ts.map +1 -1
  24. package/dist/core/extensions/plugins/formats/shared.js +16 -0
  25. package/dist/core/extensions/plugins/formats/shared.js.map +1 -1
  26. package/dist/core/extensions/plugins/formats/types.d.ts +7 -2
  27. package/dist/core/extensions/plugins/formats/types.d.ts.map +1 -1
  28. package/dist/core/extensions/plugins/formats/types.js.map +1 -1
  29. package/dist/core/extensions/plugins/index.d.ts.map +1 -1
  30. package/dist/core/extensions/plugins/index.js +13 -0
  31. package/dist/core/extensions/plugins/index.js.map +1 -1
  32. package/dist/core/extensions/plugins/install.d.ts +23 -1
  33. package/dist/core/extensions/plugins/install.d.ts.map +1 -1
  34. package/dist/core/extensions/plugins/install.js +63 -5
  35. package/dist/core/extensions/plugins/install.js.map +1 -1
  36. package/dist/core/extensions/plugins/marketplace.d.ts.map +1 -1
  37. package/dist/core/extensions/plugins/marketplace.js +22 -16
  38. package/dist/core/extensions/plugins/marketplace.js.map +1 -1
  39. package/dist/core/extensions/runner.d.ts +2 -0
  40. package/dist/core/extensions/runner.d.ts.map +1 -1
  41. package/dist/core/extensions/runner.js +15 -0
  42. package/dist/core/extensions/runner.js.map +1 -1
  43. package/dist/core/extensions/types.d.ts +33 -0
  44. package/dist/core/extensions/types.d.ts.map +1 -1
  45. package/dist/core/extensions/types.js.map +1 -1
  46. package/dist/core/settings-defaults.d.ts +1 -1
  47. package/dist/core/settings-defaults.d.ts.map +1 -1
  48. package/dist/core/settings-defaults.js +1 -1
  49. package/dist/core/settings-defaults.js.map +1 -1
  50. package/dist/core/settings-types.d.ts.map +1 -1
  51. package/dist/core/settings-types.js.map +1 -1
  52. package/dist/core/tools/plugins.d.ts.map +1 -1
  53. package/dist/core/tools/plugins.js +26 -5
  54. package/dist/core/tools/plugins.js.map +1 -1
  55. package/dist/core/tools/propose-plugin.d.ts.map +1 -1
  56. package/dist/core/tools/propose-plugin.js +10 -3
  57. package/dist/core/tools/propose-plugin.js.map +1 -1
  58. package/dist/extensions/core/mcp-loader.d.ts +11 -3
  59. package/dist/extensions/core/mcp-loader.d.ts.map +1 -1
  60. package/dist/extensions/core/mcp-loader.js +103 -18
  61. package/dist/extensions/core/mcp-loader.js.map +1 -1
  62. package/dist/main.d.ts.map +1 -1
  63. package/dist/main.js +2 -1
  64. package/dist/main.js.map +1 -1
  65. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  66. package/dist/modes/interactive/interactive-mode.js +2 -0
  67. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  68. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  69. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  70. package/examples/extensions/sandbox/package.json +1 -1
  71. package/examples/extensions/with-deps/package.json +1 -1
  72. package/package.json +4 -4
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
- import { basename, dirname } from "node:path";
15
+ import { basename, dirname, sep } from "node:path";
16
16
  import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, modelsAreEqual, resetApiProviders, } from "@kolisachint/hoocode-ai";
17
17
  import { theme } from "../modes/interactive/theme/theme.js";
18
18
  import { loadAgentRegistry } from "./agent-registry.js";
@@ -27,6 +27,7 @@ import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
27
27
  import { exportSessionToHtml } from "./export-html/index.js";
28
28
  import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
29
29
  import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
30
+ import { parsePluginDir } from "./extensions/plugins/index.js";
30
31
  import { emitSessionShutdownEvent } from "./extensions/runner.js";
31
32
  import { expandPromptTemplate, tryExpandPromptTemplate } from "./prompt-templates.js";
32
33
  import { clearProviderExhaustion, isProviderQuotaError, markProviderExhausted } from "./provider-health.js";
@@ -38,6 +39,21 @@ import { createAllToolDefinitions } from "./tools/index.js";
38
39
  import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
39
40
  import { updateWarmSubagentSkillPaths } from "./warm-subagent-pool-instance.js";
40
41
  export { parseSkillBlock } from "./agent-session-skills.js";
42
+ function describeActivation(pluginId, skills, commands, agents, pendingReload) {
43
+ const parts = [];
44
+ const list = (label, caps, render) => {
45
+ if (caps.length > 0)
46
+ parts.push(`${label}: ${caps.map(render).join(", ")}`);
47
+ };
48
+ list("skills", skills, (c) => c.name);
49
+ list("commands", commands, (c) => `/${c.name}`);
50
+ list("subagents", agents, (c) => c.name);
51
+ const now = parts.length > 0 ? ` Active NOW (usable this turn) — ${parts.join("; ")}.` : "";
52
+ const later = pendingReload
53
+ ? " This plugin also bundles executable capabilities (hooks/MCP servers); they activate automatically when the current turn ends."
54
+ : "";
55
+ return `Plugin "${pluginId}" activated in the live session.${now}${later}`;
56
+ }
41
57
  // ============================================================================
42
58
  // Constants
43
59
  // ============================================================================
@@ -98,6 +114,20 @@ export class AgentSession {
98
114
  // Base system prompt (without extension appends) - used to apply fresh appends each turn
99
115
  _baseSystemPrompt = "";
100
116
  _baseSystemPromptOptions;
117
+ /**
118
+ * Set when tools or the system prompt change while a run is in flight (a tool
119
+ * registered mid-turn, a plugin activated live). The agent loop snapshots its
120
+ * context at run start; this flag makes prepareNextTurn hand the loop a fresh
121
+ * systemPrompt + tools before the next provider request, so mid-turn
122
+ * capability changes are visible within the same turn.
123
+ */
124
+ _runtimeContextDirty = false;
125
+ /**
126
+ * Set when a change needs a full reload (executable plugin capabilities:
127
+ * hooks, MCP servers, providers) that cannot be wired mid-turn. Consumed on
128
+ * agent_end: once the session is idle the reload runs automatically.
129
+ */
130
+ _reloadWhenIdle = false;
101
131
  constructor(config) {
102
132
  this.agent = config.agent;
103
133
  this.sessionManager = config.sessionManager;
@@ -168,6 +198,21 @@ export class AgentSession {
168
198
  // (session persistence, extensions, auto-compaction, retry logic)
169
199
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
170
200
  this._installAgentToolHooks();
201
+ // Refresh the loop's frozen context snapshot between turns whenever tools or
202
+ // the system prompt changed mid-run (deferred MCP schema resolution, live
203
+ // plugin activation). Keeps the loop's message history; swaps prompt + tools.
204
+ this.agent.prepareNextTurn = (loopContext) => {
205
+ if (!this._runtimeContextDirty)
206
+ return undefined;
207
+ this._runtimeContextDirty = false;
208
+ return {
209
+ context: {
210
+ systemPrompt: this.agent.state.systemPrompt,
211
+ messages: loopContext.context.messages,
212
+ tools: this.agent.state.tools.slice(),
213
+ },
214
+ };
215
+ };
171
216
  this._buildRuntime({
172
217
  activeToolNames: this._initialActiveToolNames,
173
218
  includeAllExtensionTools: true,
@@ -366,6 +411,18 @@ export class AgentSession {
366
411
  this._retry.resolve();
367
412
  await this._compaction.checkCompaction(msg);
368
413
  }
414
+ // A live plugin activation (or uninstall) mid-run deferred its executable
415
+ // capabilities to a full reload; run it once the session settles. agent_end
416
+ // fires before the run's streaming flag clears, so defer one tick and
417
+ // re-check — if another run started meanwhile, the flag stays armed for the
418
+ // next agent_end.
419
+ if (event.type === "agent_end" && this._reloadWhenIdle) {
420
+ setTimeout(() => {
421
+ if (this._reloadWhenIdle && !this.isStreaming && !this.isCompacting) {
422
+ this._runIdleReload();
423
+ }
424
+ }, 0);
425
+ }
369
426
  }
370
427
  /** Extract text content from a message */
371
428
  _getUserMessageText(message) {
@@ -597,6 +654,8 @@ export class AgentSession {
597
654
  // Rebuild base system prompt with new tool set
598
655
  this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
599
656
  this.agent.state.systemPrompt = this._baseSystemPrompt;
657
+ // If a run is in flight, let prepareNextTurn hand the loop this new state.
658
+ this._runtimeContextDirty = true;
600
659
  }
601
660
  /** Whether compaction or branch summarization is currently running */
602
661
  get isCompacting() {
@@ -1468,6 +1527,8 @@ export class AgentSession {
1468
1527
  })();
1469
1528
  },
1470
1529
  getSystemPrompt: () => this.systemPrompt,
1530
+ activatePlugin: (pluginDir) => this.activatePlugin(pluginDir),
1531
+ requestReloadWhenIdle: () => this.requestReloadWhenIdle(),
1471
1532
  }, {
1472
1533
  registerProvider: (name, config) => {
1473
1534
  this._modelRegistry.registerProvider(name, config);
@@ -1616,6 +1677,101 @@ export class AgentSession {
1616
1677
  }
1617
1678
  }
1618
1679
  // =========================================================================
1680
+ // Live plugin activation (single-turn install → use)
1681
+ // =========================================================================
1682
+ /**
1683
+ * Activate a just-installed or just-authored plugin in the LIVE session, no
1684
+ * reload required. Passive capabilities — skills, slash commands, subagents,
1685
+ * themes — register immediately and become visible to the model on its next
1686
+ * provider request, even mid-turn (via the prepareNextTurn context refresh).
1687
+ *
1688
+ * Executable capabilities (hooks, MCP servers, providers) cannot be safely
1689
+ * wired into a run that is already streaming; when the plugin bundles any,
1690
+ * this schedules an automatic full reload for when the session next goes
1691
+ * idle (end of the current turn), so they still activate autonomously.
1692
+ */
1693
+ activatePlugin(pluginDir) {
1694
+ const plugin = parsePluginDir(pluginDir);
1695
+ if (!plugin) {
1696
+ return { activated: false, message: `No recognizable plugin manifest at ${pluginDir}.` };
1697
+ }
1698
+ const metadata = { source: "plugin", scope: "project", origin: "top-level" };
1699
+ const paths = {
1700
+ skillPaths: plugin.skillsDir ? [{ path: plugin.skillsDir, metadata }] : undefined,
1701
+ slashCommandPaths: plugin.commandsDir ? [{ path: plugin.commandsDir, metadata }] : undefined,
1702
+ agentPaths: plugin.agentsDir ? [{ path: plugin.agentsDir, metadata }] : undefined,
1703
+ themePaths: plugin.themesDir ? [{ path: plugin.themesDir, metadata }] : undefined,
1704
+ };
1705
+ this._resourceLoader.extendResources(paths);
1706
+ updateSubagentSkillPaths(this._resourceLoader.getSkillPaths());
1707
+ updateWarmSubagentSkillPaths(this._resourceLoader.getSkillPaths());
1708
+ // Rebuild the system prompt (skills + agent catalog are injected there) and
1709
+ // mark the run context dirty so the refresh lands before the next provider
1710
+ // request in the current run.
1711
+ this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());
1712
+ this.agent.state.systemPrompt = this._baseSystemPrompt;
1713
+ this._runtimeContextDirty = true;
1714
+ const underRoot = (filePath) => !!filePath && (filePath === plugin.root || filePath.startsWith(`${plugin.root}${sep}`));
1715
+ const skills = this._resourceLoader
1716
+ .getSkills()
1717
+ .skills.filter((s) => underRoot(s.filePath))
1718
+ .map((s) => ({ name: s.name, description: s.description }));
1719
+ const commands = this._resourceLoader
1720
+ .getPrompts()
1721
+ .prompts.filter((p) => underRoot(p.filePath))
1722
+ .map((p) => ({ name: p.name, description: p.description }));
1723
+ const agents = loadAgentRegistry({ cwd: this._cwd })
1724
+ .list()
1725
+ .filter((a) => underRoot(a.filePath))
1726
+ .map((a) => ({ name: a.name, description: a.description }));
1727
+ const needsReload = !!(plugin.hooks || plugin.mcpServers || (plugin.providers?.length ?? 0) > 0);
1728
+ if (needsReload) {
1729
+ this._reloadWhenIdle = true;
1730
+ if (!this.isStreaming && !this.isCompacting)
1731
+ this._runIdleReload();
1732
+ }
1733
+ return {
1734
+ activated: true,
1735
+ pluginId: plugin.id,
1736
+ skills,
1737
+ commands,
1738
+ agents,
1739
+ pendingReloadForExecutables: needsReload,
1740
+ message: describeActivation(plugin.id, skills, commands, agents, needsReload),
1741
+ };
1742
+ }
1743
+ /**
1744
+ * Ask for a full reload once the session is idle — used when a change (e.g.
1745
+ * uninstalling a plugin) can only take effect through the reload path. Runs
1746
+ * immediately when already idle.
1747
+ */
1748
+ requestReloadWhenIdle() {
1749
+ this._reloadWhenIdle = true;
1750
+ if (!this.isStreaming && !this.isCompacting)
1751
+ this._runIdleReload();
1752
+ }
1753
+ /** Run the pending idle reload through the mode's reload flow when bound (UI refresh included). */
1754
+ _runIdleReload() {
1755
+ if (!this._reloadWhenIdle)
1756
+ return;
1757
+ this._reloadWhenIdle = false;
1758
+ const reloadFlow = this._extensionCommandContextActions?.reload;
1759
+ void (async () => {
1760
+ try {
1761
+ if (reloadFlow) {
1762
+ await reloadFlow();
1763
+ }
1764
+ else {
1765
+ await this.reload();
1766
+ }
1767
+ }
1768
+ catch {
1769
+ // Auto-reload is best-effort; a failed reload leaves the session usable
1770
+ // and the plugin activates on the next manual /reload or restart.
1771
+ }
1772
+ })();
1773
+ }
1774
+ // =========================================================================
1619
1775
  // Auto-Retry
1620
1776
  // =========================================================================
1621
1777
  /**