hoomanjs 1.11.4 → 1.12.1

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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <div align="center">
2
2
  <h1>Hooman</h1>
3
3
  <p>
4
- Hooman is a Bun-powered local AI agent CLI built with TypeScript, <a href="https://www.npmjs.com/package/@strands-agents/sdk">Strands Agents SDK</a>, and <a href="https://github.com/vadimdemedes/ink">Ink</a>.
4
+ Hooman is a hackable, Bun-powered AI agent toolkit for local workflows. It is built with TypeScript, <a href="https://www.npmjs.com/package/@strands-agents/sdk">Strands Agents SDK</a>, and <a href="https://github.com/vadimdemedes/ink">Ink</a>.
5
5
  </p>
6
6
  <p>
7
7
  <a href="https://bun.com"><img src="https://img.shields.io/badge/runtime-Bun-f9f1e1?logo=bun&logoColor=000000" alt="Bun" /></a>
@@ -16,12 +16,12 @@
16
16
  </p>
17
17
  </div>
18
18
 
19
- It gives you:
19
+ It gives you a practical toolkit to build and run agent workflows:
20
20
 
21
21
  - a one-shot `exec` command for single prompts
22
- - a stateful `chat` interface for interactive sessions
23
- - a `daemon` command for processing MCP channel notifications in background
24
- - an Ink-powered `configure` workflow for editing app config, `instructions.md`, MCP servers, and installed skills
22
+ - a stateful `chat` interface for iterative sessions
23
+ - a `daemon` command for channel-driven MCP automation
24
+ - an Ink-powered `configure` workflow for app config, prompts, MCP servers, and installed skills
25
25
  - an `acp` command for running Hooman as an Agent Client Protocol (ACP) agent over stdio
26
26
 
27
27
  ## Features
@@ -32,6 +32,7 @@ It gives you:
32
32
  - MCP server `instructions` support: server-provided instructions are appended to the agent system prompt
33
33
  - MCP channel notification support through `hooman daemon --channels`
34
34
  - Skill discovery / install / removal through the integrated configure flow
35
+ - Toolkit-oriented architecture with configurable tools, prompts, memory, and transports
35
36
  - Interactive terminal UI for chat and configuration
36
37
 
37
38
  ## Requirements
@@ -157,18 +158,20 @@ hooman daemon --channels --yolo
157
158
 
158
159
  ### Feature Flags
159
160
 
160
- Runtime tools and prompt sections are controlled from `config.json` under `features`:
161
+ Runtime tools and prompt sections are controlled from `config.json` under `tools`:
161
162
 
162
- - `features.fetch.enabled`
163
- - `features.filesystem.enabled`
164
- - `features.shell.enabled`
165
- - `features.ltm.enabled`
166
- - `features.wiki.enabled`
163
+ - `tools.fetch.enabled`
164
+ - `tools.filesystem.enabled`
165
+ - `tools.shell.enabled`
166
+ - `tools.ltm.enabled`
167
+ - `tools.wiki.enabled`
168
+ - `tools.mcp.enabled` (enables MCP management tools + prefixed MCP server tools/instructions)
169
+ - `tools.skills.enabled` (enables skills management tools + skills prompt sections)
167
170
 
168
171
  Both `ltm` and `wiki` include dedicated Chroma settings under:
169
172
 
170
- - `features.ltm.chroma` (default collection: `memory`)
171
- - `features.wiki.chroma` (default collection: `wiki`)
173
+ - `tools.ltm.chroma` (default collection: `memory`)
174
+ - `tools.wiki.chroma` (default collection: `wiki`)
172
175
 
173
176
  ### `hooman configure`
174
177
 
@@ -211,7 +214,7 @@ Hooman stores its data in:
211
214
 
212
215
  Important files and folders:
213
216
 
214
- - `config.json` - app name, LLM provider/model, tool approvals, feature flags, LTM/wiki settings, compaction
217
+ - `config.json` - app name, LLM provider/model, tool flags, LTM/wiki settings, compaction
215
218
  - `instructions.md` - system instructions used to build the agent prompt
216
219
  - `mcp.json` - MCP server definitions
217
220
  - `skills/` - installed skills
@@ -231,9 +234,6 @@ This is the shape managed by `hooman configure`:
231
234
  "params": {}
232
235
  },
233
236
  "tools": {
234
- "allowed": []
235
- },
236
- "features": {
237
237
  "fetch": {
238
238
  "enabled": true
239
239
  },
@@ -260,6 +260,12 @@ This is the shape managed by `hooman configure`:
260
260
  "wiki": "wiki"
261
261
  }
262
262
  }
263
+ },
264
+ "mcp": {
265
+ "enabled": false
266
+ },
267
+ "skills": {
268
+ "enabled": false
263
269
  }
264
270
  },
265
271
  "compaction": {
@@ -269,6 +275,8 @@ This is the shape managed by `hooman configure`:
269
275
  }
270
276
  ```
271
277
 
278
+ Tool approvals are session-scoped and are not persisted in `config.json`.
279
+
272
280
  Supported `llm.provider` values:
273
281
 
274
282
  - `ollama`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hoomanjs",
3
- "version": "1.11.4",
4
- "description": "Bun-powered local AI agent CLI with chat, exec, ACP, MCP, and skills support.",
3
+ "version": "1.12.1",
4
+ "description": "Hackable Bun-powered AI agent toolkit for building local CLI, ACP, MCP, and channel-driven workflows.",
5
5
  "author": {
6
6
  "name": "Vaibhav Pandey",
7
7
  "email": "contact@vaibhavpandey.com"
@@ -337,8 +337,10 @@ export class AcpAgent implements AgentContract {
337
337
  {
338
338
  userId: bootstrapUserId,
339
339
  sessionId,
340
- mcpServers,
341
- ...(clientSystemPrompt ? { systemPrompt: clientSystemPrompt } : {}),
340
+ acp: {
341
+ mcpServers,
342
+ ...(clientSystemPrompt ? { systemPrompt: clientSystemPrompt } : {}),
343
+ },
342
344
  },
343
345
  false,
344
346
  );
@@ -433,8 +435,10 @@ export class AcpAgent implements AgentContract {
433
435
  {
434
436
  userId: bootstrapUserId,
435
437
  sessionId: params.sessionId,
436
- mcpServers,
437
- ...(clientSystemPrompt ? { systemPrompt: clientSystemPrompt } : {}),
438
+ acp: {
439
+ mcpServers,
440
+ ...(clientSystemPrompt ? { systemPrompt: clientSystemPrompt } : {}),
441
+ },
438
442
  },
439
443
  false,
440
444
  );
@@ -63,8 +63,8 @@ export function applySessionConfigOption(
63
63
  if (params.configId === HOOMAN_LTM_CONFIG_ID) {
64
64
  const chroma = config.ltm.chroma;
65
65
  config.update({
66
- features: {
67
- ...config.features,
66
+ tools: {
67
+ ...config.tools,
68
68
  ltm: {
69
69
  enabled: value === "on",
70
70
  chroma: {
@@ -79,8 +79,8 @@ export function applySessionConfigOption(
79
79
 
80
80
  const chroma = config.wiki.chroma;
81
81
  config.update({
82
- features: {
83
- ...config.features,
82
+ tools: {
83
+ ...config.tools,
84
84
  wiki: {
85
85
  enabled: value === "on",
86
86
  chroma: {
@@ -132,7 +132,6 @@ export function ConfigureApp({
132
132
  name: config.name,
133
133
  llm: config.llm,
134
134
  tools: config.tools,
135
- features: config.features,
136
135
  compaction: config.compaction,
137
136
  }) satisfies ConfigData,
138
137
  [config, revision],
@@ -433,79 +432,98 @@ export function ConfigureApp({
433
432
  }),
434
433
  },
435
434
  {
436
- label: `Allowed tools • ${truncate(compactJson(configData.tools.allowed))}`,
437
- value: () =>
438
- promptValue({
439
- title: "Update allowed list",
440
- label: "Allowed",
441
- initialValue: compactJson(configData.tools.allowed),
442
- placeholder: '["tool_a","tool_b"]',
443
- onSubmit: async (value) => {
444
- const allowed = parseStringArray(value, "Allowed");
445
- updateConfig({ tools: { allowed } }, "Updated allowed list.");
446
- setPrompt(null);
447
- },
448
- }),
449
- },
450
- {
451
- label: `Fetch feature • ${configData.features.fetch.enabled ? "Enabled" : "Disabled"}`,
435
+ label: `Fetch tool • ${configData.tools.fetch.enabled ? "Enabled" : "Disabled"}`,
452
436
  value: () => {
453
437
  updateConfig(
454
438
  {
455
- features: {
456
- ...config.features,
439
+ tools: {
440
+ ...config.tools,
457
441
  fetch: {
458
- enabled: !configData.features.fetch.enabled,
442
+ enabled: !configData.tools.fetch.enabled,
459
443
  },
460
444
  },
461
445
  },
462
- `Fetch feature ${configData.features.fetch.enabled ? "disabled" : "enabled"}.`,
446
+ `Fetch tool ${configData.tools.fetch.enabled ? "disabled" : "enabled"}.`,
463
447
  );
464
448
  setScreen({ kind: "config" });
465
449
  },
466
450
  },
467
451
  {
468
- label: `Filesystem feature • ${configData.features.filesystem.enabled ? "Enabled" : "Disabled"}`,
452
+ label: `Filesystem tool • ${configData.tools.filesystem.enabled ? "Enabled" : "Disabled"}`,
469
453
  value: () => {
470
454
  updateConfig(
471
455
  {
472
- features: {
473
- ...config.features,
456
+ tools: {
457
+ ...config.tools,
474
458
  filesystem: {
475
- enabled: !configData.features.filesystem.enabled,
459
+ enabled: !configData.tools.filesystem.enabled,
476
460
  },
477
461
  },
478
462
  },
479
- `Filesystem feature ${configData.features.filesystem.enabled ? "disabled" : "enabled"}.`,
463
+ `Filesystem tool ${configData.tools.filesystem.enabled ? "disabled" : "enabled"}.`,
480
464
  );
481
465
  setScreen({ kind: "config" });
482
466
  },
483
467
  },
484
468
  {
485
- label: `Shell feature • ${configData.features.shell.enabled ? "Enabled" : "Disabled"}`,
469
+ label: `Shell tool • ${configData.tools.shell.enabled ? "Enabled" : "Disabled"}`,
486
470
  value: () => {
487
471
  updateConfig(
488
472
  {
489
- features: {
490
- ...config.features,
473
+ tools: {
474
+ ...config.tools,
491
475
  shell: {
492
- enabled: !configData.features.shell.enabled,
476
+ enabled: !configData.tools.shell.enabled,
493
477
  },
494
478
  },
495
479
  },
496
- `Shell feature ${configData.features.shell.enabled ? "disabled" : "enabled"}.`,
480
+ `Shell tool ${configData.tools.shell.enabled ? "disabled" : "enabled"}.`,
497
481
  );
498
482
  setScreen({ kind: "config" });
499
483
  },
500
484
  },
501
485
  {
502
- label: `Long-term memory • ${configData.features.ltm.enabled ? "Enabled" : "Disabled"} • ${configData.features.ltm.chroma.collection.memory}`,
486
+ label: `Long-term memory • ${configData.tools.ltm.enabled ? "Enabled" : "Disabled"} • ${configData.tools.ltm.chroma.collection.memory}`,
503
487
  value: () => setScreen({ kind: "config-ltm" }),
504
488
  },
505
489
  {
506
- label: `Wiki feature • ${configData.features.wiki.enabled ? "Enabled" : "Disabled"} • ${configData.features.wiki.chroma.collection.wiki}`,
490
+ label: `Wiki tool • ${configData.tools.wiki.enabled ? "Enabled" : "Disabled"} • ${configData.tools.wiki.chroma.collection.wiki}`,
507
491
  value: () => setScreen({ kind: "config-wiki" }),
508
492
  },
493
+ {
494
+ label: `MCP tools • ${configData.tools.mcp.enabled ? "Enabled" : "Disabled"}`,
495
+ value: () => {
496
+ updateConfig(
497
+ {
498
+ tools: {
499
+ ...config.tools,
500
+ mcp: {
501
+ enabled: !configData.tools.mcp.enabled,
502
+ },
503
+ },
504
+ },
505
+ `MCP tools ${configData.tools.mcp.enabled ? "disabled" : "enabled"}.`,
506
+ );
507
+ setScreen({ kind: "config" });
508
+ },
509
+ },
510
+ {
511
+ label: `Skills tools • ${configData.tools.skills.enabled ? "Enabled" : "Disabled"}`,
512
+ value: () => {
513
+ updateConfig(
514
+ {
515
+ tools: {
516
+ ...config.tools,
517
+ skills: {
518
+ enabled: !configData.tools.skills.enabled,
519
+ },
520
+ },
521
+ },
522
+ `Skills tools ${configData.tools.skills.enabled ? "disabled" : "enabled"}.`,
523
+ );
524
+ setScreen({ kind: "config" });
525
+ },
526
+ },
509
527
  {
510
528
  label: `Compaction ratio • ${configData.compaction.ratio}`,
511
529
  value: () =>
@@ -604,30 +622,30 @@ export function ConfigureApp({
604
622
  const renderLtmConfigMenu = () => {
605
623
  const items: MenuItem[] = [
606
624
  {
607
- label: `Enabled • ${configData.features.ltm.enabled ? "On" : "Off"}`,
625
+ label: `Enabled • ${configData.tools.ltm.enabled ? "On" : "Off"}`,
608
626
  value: () => {
609
627
  updateConfig(
610
628
  {
611
- features: {
612
- ...config.features,
629
+ tools: {
630
+ ...config.tools,
613
631
  ltm: {
614
- ...config.features.ltm,
615
- enabled: !configData.features.ltm.enabled,
632
+ ...config.tools.ltm,
633
+ enabled: !configData.tools.ltm.enabled,
616
634
  },
617
635
  },
618
636
  },
619
- `Long-term memory ${configData.features.ltm.enabled ? "disabled" : "enabled"}.`,
637
+ `Long-term memory ${configData.tools.ltm.enabled ? "disabled" : "enabled"}.`,
620
638
  );
621
639
  setScreen({ kind: "config-ltm" });
622
640
  },
623
641
  },
624
642
  {
625
- label: `Chroma URL • ${configData.features.ltm.chroma.url}`,
643
+ label: `Chroma URL • ${configData.tools.ltm.chroma.url}`,
626
644
  value: () =>
627
645
  promptValue({
628
646
  title: "Update LTM Chroma URL",
629
647
  label: "URL",
630
- initialValue: configData.features.ltm.chroma.url,
648
+ initialValue: configData.tools.ltm.chroma.url,
631
649
  onSubmit: async (value) => {
632
650
  const url = value.trim();
633
651
  if (!url) {
@@ -635,12 +653,12 @@ export function ConfigureApp({
635
653
  }
636
654
  updateConfig(
637
655
  {
638
- features: {
639
- ...config.features,
656
+ tools: {
657
+ ...config.tools,
640
658
  ltm: {
641
- ...config.features.ltm,
659
+ ...config.tools.ltm,
642
660
  chroma: {
643
- ...config.features.ltm.chroma,
661
+ ...config.tools.ltm.chroma,
644
662
  url,
645
663
  },
646
664
  },
@@ -653,12 +671,12 @@ export function ConfigureApp({
653
671
  }),
654
672
  },
655
673
  {
656
- label: `Chroma collection • ${configData.features.ltm.chroma.collection.memory}`,
674
+ label: `Chroma collection • ${configData.tools.ltm.chroma.collection.memory}`,
657
675
  value: () =>
658
676
  promptValue({
659
677
  title: "Update LTM Chroma collection",
660
678
  label: "Collection name",
661
- initialValue: configData.features.ltm.chroma.collection.memory,
679
+ initialValue: configData.tools.ltm.chroma.collection.memory,
662
680
  onSubmit: async (value) => {
663
681
  const memory = value.trim();
664
682
  if (!memory) {
@@ -666,12 +684,12 @@ export function ConfigureApp({
666
684
  }
667
685
  updateConfig(
668
686
  {
669
- features: {
670
- ...config.features,
687
+ tools: {
688
+ ...config.tools,
671
689
  ltm: {
672
- ...config.features.ltm,
690
+ ...config.tools.ltm,
673
691
  chroma: {
674
- ...config.features.ltm.chroma,
692
+ ...config.tools.ltm.chroma,
675
693
  collection: { memory },
676
694
  },
677
695
  },
@@ -701,30 +719,30 @@ export function ConfigureApp({
701
719
  const renderWikiConfigMenu = () => {
702
720
  const items: MenuItem[] = [
703
721
  {
704
- label: `Enabled • ${configData.features.wiki.enabled ? "On" : "Off"}`,
722
+ label: `Enabled • ${configData.tools.wiki.enabled ? "On" : "Off"}`,
705
723
  value: () => {
706
724
  updateConfig(
707
725
  {
708
- features: {
709
- ...config.features,
726
+ tools: {
727
+ ...config.tools,
710
728
  wiki: {
711
- ...config.features.wiki,
712
- enabled: !configData.features.wiki.enabled,
729
+ ...config.tools.wiki,
730
+ enabled: !configData.tools.wiki.enabled,
713
731
  },
714
732
  },
715
733
  },
716
- `Wiki feature ${configData.features.wiki.enabled ? "disabled" : "enabled"}.`,
734
+ `Wiki tool ${configData.tools.wiki.enabled ? "disabled" : "enabled"}.`,
717
735
  );
718
736
  setScreen({ kind: "config-wiki" });
719
737
  },
720
738
  },
721
739
  {
722
- label: `Chroma URL • ${configData.features.wiki.chroma.url}`,
740
+ label: `Chroma URL • ${configData.tools.wiki.chroma.url}`,
723
741
  value: () =>
724
742
  promptValue({
725
743
  title: "Update Wiki Chroma URL",
726
744
  label: "URL",
727
- initialValue: configData.features.wiki.chroma.url,
745
+ initialValue: configData.tools.wiki.chroma.url,
728
746
  onSubmit: async (value) => {
729
747
  const url = value.trim();
730
748
  if (!url) {
@@ -732,12 +750,12 @@ export function ConfigureApp({
732
750
  }
733
751
  updateConfig(
734
752
  {
735
- features: {
736
- ...config.features,
753
+ tools: {
754
+ ...config.tools,
737
755
  wiki: {
738
- ...config.features.wiki,
756
+ ...config.tools.wiki,
739
757
  chroma: {
740
- ...config.features.wiki.chroma,
758
+ ...config.tools.wiki.chroma,
741
759
  url,
742
760
  },
743
761
  },
@@ -750,12 +768,12 @@ export function ConfigureApp({
750
768
  }),
751
769
  },
752
770
  {
753
- label: `Chroma collection • ${configData.features.wiki.chroma.collection.wiki}`,
771
+ label: `Chroma collection • ${configData.tools.wiki.chroma.collection.wiki}`,
754
772
  value: () =>
755
773
  promptValue({
756
774
  title: "Update Wiki Chroma collection",
757
775
  label: "Collection name",
758
- initialValue: configData.features.wiki.chroma.collection.wiki,
776
+ initialValue: configData.tools.wiki.chroma.collection.wiki,
759
777
  onSubmit: async (value) => {
760
778
  const wiki = value.trim();
761
779
  if (!wiki) {
@@ -763,12 +781,12 @@ export function ConfigureApp({
763
781
  }
764
782
  updateConfig(
765
783
  {
766
- features: {
767
- ...config.features,
784
+ tools: {
785
+ ...config.tools,
768
786
  wiki: {
769
- ...config.features.wiki,
787
+ ...config.tools.wiki,
770
788
  chroma: {
771
- ...config.features.wiki.chroma,
789
+ ...config.tools.wiki.chroma,
772
790
  collection: { wiki },
773
791
  },
774
792
  },
@@ -789,7 +807,7 @@ export function ConfigureApp({
789
807
  return (
790
808
  <MenuScreen
791
809
  title="Wiki"
792
- description="Configure wiki features and Chroma-backed wiki search."
810
+ description="Configure wiki tool and Chroma-backed wiki search."
793
811
  items={items}
794
812
  />
795
813
  );
@@ -1,7 +1,11 @@
1
1
  import { Agent } from "@strands-agents/sdk";
2
2
  import type { Config } from "../config.ts";
3
3
  import { modelProviders } from "../models";
4
- import { type Config as McpConfig, type Manager as McpManager } from "../mcp";
4
+ import {
5
+ createMcpTools,
6
+ type Config as McpConfig,
7
+ type Manager as McpManager,
8
+ } from "../mcp";
5
9
  import type { System as SystemPrompt } from "../prompts";
6
10
  import { skills as createSkillsPrompt } from "../prompts";
7
11
  import {
@@ -9,7 +13,7 @@ import {
9
13
  createLongTermMemoryStore,
10
14
  createLongTermMemoryTools,
11
15
  } from "../memory";
12
- import type { Registry } from "../skills";
16
+ import { createSkillsTools, type Registry } from "../skills";
13
17
  import {
14
18
  createFetchTools,
15
19
  createFilesystemTools,
@@ -37,13 +41,19 @@ export async function create(
37
41
  const userId = meta.userId ?? sessionId;
38
42
  const llm = await modelProviders[config.llm.provider]!();
39
43
  const stm = createShortTermMemory(sessionId);
40
- const ltm = config.features.ltm.enabled
44
+ const ltm = config.tools.ltm.enabled
41
45
  ? createLongTermMemoryStore(config)
42
46
  : null;
43
- const skills = await createSkillsPrompt(registry);
44
- const tools = await mcp.manager.listPrefixedTools();
45
- const append = await mcp.manager.listServerInstructions();
46
- const prompt = [system.content, meta.systemPrompt, ...append, skills.content]
47
+ const skills = config.tools.skills.enabled
48
+ ? (await createSkillsPrompt(registry)).content
49
+ : "";
50
+ const tools = config.tools.mcp.enabled
51
+ ? await mcp.manager.listPrefixedTools()
52
+ : [];
53
+ const append = config.tools.mcp.enabled
54
+ ? await mcp.manager.listServerInstructions()
55
+ : [];
56
+ const prompt = [system.content, meta.systemPrompt, ...append, skills]
47
57
  .filter((x) => !!x)
48
58
  .join(SECTION_BREAK);
49
59
  return new Agent({
@@ -56,11 +66,13 @@ export async function create(
56
66
  },
57
67
  tools: [
58
68
  ...createTimeTools(),
59
- ...(config.features.fetch.enabled ? createFetchTools() : []),
69
+ ...(config.tools.fetch.enabled ? createFetchTools() : []),
60
70
  ...(ltm ? createLongTermMemoryTools(ltm) : []),
61
- ...(config.features.filesystem.enabled ? createFilesystemTools() : []),
62
- ...(config.features.shell.enabled ? createShellTools() : []),
63
- ...(config.features.wiki.enabled ? createWikiTools(config) : []),
71
+ ...(config.tools.filesystem.enabled ? createFilesystemTools() : []),
72
+ ...(config.tools.shell.enabled ? createShellTools() : []),
73
+ ...(config.tools.wiki.enabled ? createWikiTools(config) : []),
74
+ ...(config.tools.mcp.enabled ? createMcpTools(mcp.config) : []),
75
+ ...(config.tools.skills.enabled ? createSkillsTools(registry) : []),
64
76
  ...createThinkingTools(),
65
77
  ...tools,
66
78
  ],
@@ -67,49 +67,45 @@ const WikiPartialSchema = z.object({
67
67
  chroma: WikiChromaPartialSchema.optional(),
68
68
  });
69
69
 
70
- const FeatureTogglePartialSchema = z.object({
70
+ const ToolTogglePartialSchema = z.object({
71
71
  enabled: z.boolean().optional(),
72
72
  });
73
73
 
74
- const FeaturesPartialSchema = z.object({
75
- fetch: FeatureTogglePartialSchema.optional(),
76
- filesystem: FeatureTogglePartialSchema.optional(),
77
- shell: FeatureTogglePartialSchema.optional(),
74
+ const ToolsPartialSchema = z.object({
75
+ fetch: ToolTogglePartialSchema.optional(),
76
+ filesystem: ToolTogglePartialSchema.optional(),
77
+ shell: ToolTogglePartialSchema.optional(),
78
78
  ltm: LtmPartialSchema.optional(),
79
79
  wiki: WikiPartialSchema.optional(),
80
- });
81
-
82
- const ToolsPartialSchema = z.object({
83
- allowed: z.array(z.string().min(1)).default([]),
80
+ mcp: ToolTogglePartialSchema.optional(),
81
+ skills: ToolTogglePartialSchema.optional(),
84
82
  });
85
83
 
86
84
  const ConfigSchema = z
87
85
  .object({
88
86
  name: z.string().min(1),
89
87
  llm: LlmSchema,
90
- tools: ToolsPartialSchema.default({ allowed: [] }),
91
- features: FeaturesPartialSchema.nullish(),
88
+ tools: ToolsPartialSchema.nullish(),
92
89
  compaction: CompactionPartialSchema.nullish().transform((c) => ({
93
90
  ratio: c?.ratio ?? DEFAULT_COMPACTION.ratio,
94
91
  keep: c?.keep ?? DEFAULT_COMPACTION.keep,
95
92
  })),
96
93
  })
97
94
  .transform((input) => {
98
- const ltm = input.features?.ltm;
99
- const wiki = input.features?.wiki;
95
+ const ltm = input.tools?.ltm;
96
+ const wiki = input.tools?.wiki;
100
97
  return {
101
98
  name: input.name,
102
99
  llm: input.llm,
103
- tools: input.tools,
104
- features: {
100
+ tools: {
105
101
  fetch: {
106
- enabled: input.features?.fetch?.enabled ?? true,
102
+ enabled: input.tools?.fetch?.enabled ?? true,
107
103
  },
108
104
  filesystem: {
109
- enabled: input.features?.filesystem?.enabled ?? true,
105
+ enabled: input.tools?.filesystem?.enabled ?? true,
110
106
  },
111
107
  shell: {
112
- enabled: input.features?.shell?.enabled ?? true,
108
+ enabled: input.tools?.shell?.enabled ?? true,
113
109
  },
114
110
  ltm: {
115
111
  enabled: ltm?.enabled ?? false,
@@ -133,6 +129,12 @@ const ConfigSchema = z
133
129
  },
134
130
  },
135
131
  },
132
+ mcp: {
133
+ enabled: input.tools?.mcp?.enabled ?? false,
134
+ },
135
+ skills: {
136
+ enabled: input.tools?.skills?.enabled ?? false,
137
+ },
136
138
  },
137
139
  compaction: input.compaction,
138
140
  };
@@ -141,10 +143,9 @@ const ConfigSchema = z
141
143
  export type ConfigData = z.infer<typeof ConfigSchema>;
142
144
  export type LlmConfig = z.infer<typeof LlmSchema>;
143
145
  export type CompactionConfig = ConfigData["compaction"];
144
- export type LtmConfig = ConfigData["features"]["ltm"];
145
- export type WikiConfig = ConfigData["features"]["wiki"];
146
+ export type LtmConfig = ConfigData["tools"]["ltm"];
147
+ export type WikiConfig = ConfigData["tools"]["wiki"];
146
148
  export type ToolsConfig = ConfigData["tools"];
147
- export type FeaturesConfig = ConfigData["features"];
148
149
 
149
150
  const defaultConfigData = (): ConfigData => ({
150
151
  name: "Hooman",
@@ -154,9 +155,6 @@ const defaultConfigData = (): ConfigData => ({
154
155
  params: {},
155
156
  },
156
157
  tools: {
157
- allowed: [],
158
- },
159
- features: {
160
158
  fetch: {
161
159
  enabled: true,
162
160
  },
@@ -180,6 +178,12 @@ const defaultConfigData = (): ConfigData => ({
180
178
  collection: { wiki: "wiki" },
181
179
  },
182
180
  },
181
+ mcp: {
182
+ enabled: false,
183
+ },
184
+ skills: {
185
+ enabled: false,
186
+ },
183
187
  },
184
188
  compaction: {
185
189
  ratio: 0.75,
@@ -207,43 +211,38 @@ export class Config {
207
211
  get tools(): ToolsConfig {
208
212
  return {
209
213
  ...this.data.tools,
210
- allowed: [...this.data.tools.allowed],
211
- };
212
- }
213
-
214
- get compaction(): CompactionConfig {
215
- return this.data.compaction;
216
- }
217
-
218
- get features(): FeaturesConfig {
219
- return {
220
- ...this.data.features,
221
- fetch: { ...this.data.features.fetch },
222
- filesystem: { ...this.data.features.filesystem },
223
- shell: { ...this.data.features.shell },
214
+ fetch: { ...this.data.tools.fetch },
215
+ filesystem: { ...this.data.tools.filesystem },
216
+ shell: { ...this.data.tools.shell },
224
217
  ltm: {
225
- ...this.data.features.ltm,
218
+ ...this.data.tools.ltm,
226
219
  chroma: {
227
- ...this.data.features.ltm.chroma,
228
- collection: { ...this.data.features.ltm.chroma.collection },
220
+ ...this.data.tools.ltm.chroma,
221
+ collection: { ...this.data.tools.ltm.chroma.collection },
229
222
  },
230
223
  },
231
224
  wiki: {
232
- ...this.data.features.wiki,
225
+ ...this.data.tools.wiki,
233
226
  chroma: {
234
- ...this.data.features.wiki.chroma,
235
- collection: { ...this.data.features.wiki.chroma.collection },
227
+ ...this.data.tools.wiki.chroma,
228
+ collection: { ...this.data.tools.wiki.chroma.collection },
236
229
  },
237
230
  },
231
+ mcp: { ...this.data.tools.mcp },
232
+ skills: { ...this.data.tools.skills },
238
233
  };
239
234
  }
240
235
 
236
+ get compaction(): CompactionConfig {
237
+ return this.data.compaction;
238
+ }
239
+
241
240
  get ltm(): LtmConfig {
242
- return this.features.ltm;
241
+ return this.tools.ltm;
243
242
  }
244
243
 
245
244
  get wiki(): WikiConfig {
246
- return this.features.wiki;
245
+ return this.tools.wiki;
247
246
  }
248
247
 
249
248
  private readJson(): unknown {
package/src/core/index.ts CHANGED
@@ -18,13 +18,19 @@ import {
18
18
  mcpJsonPath,
19
19
  } from "./utils/paths.ts";
20
20
 
21
+ export type BootstrapMeta = {
22
+ userId?: string;
23
+ sessionId?: string;
24
+ acp?: AcpMeta;
25
+ };
26
+
27
+ export type AcpMeta = {
28
+ systemPrompt?: string;
29
+ mcpServers?: NamedMcpTransport[];
30
+ };
31
+
21
32
  export async function bootstrap(
22
- meta: {
23
- userId?: string;
24
- sessionId?: string;
25
- systemPrompt?: string;
26
- mcpServers?: NamedMcpTransport[];
27
- },
33
+ meta: BootstrapMeta,
28
34
  print: boolean = false,
29
35
  ): Promise<{
30
36
  config: Config;
@@ -34,14 +40,18 @@ export async function bootstrap(
34
40
  }> {
35
41
  const config = new Config(configJsonPath());
36
42
  const mcpConfig = createMcpConfig(mcpJsonPath());
37
- const mcpManager = createMcpManager(mcpConfig, meta.mcpServers ?? []);
43
+ const mcpManager = createMcpManager(
44
+ mcpConfig,
45
+ meta.acp !== undefined,
46
+ meta.acp?.mcpServers ?? [],
47
+ );
38
48
  const mcp = { config: mcpConfig, manager: mcpManager };
39
49
  const registry = createSkillsRegistry(basePath());
40
50
  const system = await createSystemPrompt(instructionsMdPath(), config);
41
51
  const agent = await createAgent(config, system, registry, mcp, print, {
42
52
  userId: meta?.userId ?? meta?.sessionId,
43
53
  sessionId: meta?.sessionId,
44
- systemPrompt: meta?.systemPrompt,
54
+ systemPrompt: meta?.acp?.systemPrompt,
45
55
  });
46
56
  return { config, agent, mcp, registry };
47
57
  }
@@ -26,7 +26,8 @@ export function createMcpConfig(path: string): Config {
26
26
 
27
27
  export function createMcpManager(
28
28
  config: Config,
29
+ acp = false,
29
30
  mcpServers: readonly NamedMcpTransport[] = [],
30
31
  ): Manager {
31
- return new Manager(config, mcpServers);
32
+ return new Manager(config, acp, mcpServers);
32
33
  }
@@ -121,7 +121,7 @@ function readSourceValue(value: unknown): string | undefined {
121
121
  */
122
122
  export class Manager {
123
123
  private instances: Map<string, McpClient> | null = null;
124
- private readonly pendingPermissions = new Map<
124
+ private readonly permissions = new Map<
125
125
  string,
126
126
  {
127
127
  resolve: (behavior: ChannelPermissionBehavior) => void;
@@ -132,7 +132,8 @@ export class Manager {
132
132
 
133
133
  public constructor(
134
134
  private readonly config: Config,
135
- private readonly mcpServers: readonly NamedMcpTransport[] = [],
135
+ private readonly acp = false,
136
+ private readonly servers: readonly NamedMcpTransport[] = [],
136
137
  ) {}
137
138
 
138
139
  /** Lazily builds clients from the current in-memory config (reloads file first). */
@@ -148,13 +149,15 @@ export class Manager {
148
149
  * previous clients (stdio subprocesses, HTTP sessions).
149
150
  */
150
151
  public reload(): void {
151
- this.config.reload();
152
+ if (!this.acp) {
153
+ this.config.reload();
154
+ }
152
155
  const previous = this.instances;
153
156
  const next = new Map<string, McpClient>();
154
157
  const transports = [
155
- ...this.config.list(),
156
- // Session-scoped ACP servers intentionally override local config names.
157
- ...this.mcpServers,
158
+ ...(this.acp ? [] : this.config.list()),
159
+ // Session-scoped servers override local config entries on name conflicts.
160
+ ...this.servers,
158
161
  ];
159
162
  for (const { name, transport } of transports) {
160
163
  next.set(
@@ -173,11 +176,11 @@ export class Manager {
173
176
  }
174
177
 
175
178
  public async disconnect(): Promise<void> {
176
- for (const [key, pending] of this.pendingPermissions.entries()) {
179
+ for (const [key, pending] of this.permissions.entries()) {
177
180
  clearTimeout(pending.timer);
178
181
  pending.reject(new Error(`Pending permission "${key}" cancelled.`));
179
182
  }
180
- this.pendingPermissions.clear();
183
+ this.permissions.clear();
181
184
  const toClose = this.instances;
182
185
  this.instances = null;
183
186
  if (!toClose?.size) {
@@ -281,11 +284,11 @@ export class Manager {
281
284
  return;
282
285
  }
283
286
  const key = `${server}:${requestId}`;
284
- const pending = this.pendingPermissions.get(key);
287
+ const pending = this.permissions.get(key);
285
288
  if (!pending) {
286
289
  return;
287
290
  }
288
- this.pendingPermissions.delete(key);
291
+ this.permissions.delete(key);
289
292
  clearTimeout(pending.timer);
290
293
  pending.resolve(behavior);
291
294
  };
@@ -390,21 +393,21 @@ export class Manager {
390
393
  throw new Error("requestId is required.");
391
394
  }
392
395
  const key = `${server}:${requestId}`;
393
- if (this.pendingPermissions.has(key)) {
396
+ if (this.permissions.has(key)) {
394
397
  throw new Error(`Permission request "${requestId}" is already pending.`);
395
398
  }
396
399
 
397
400
  const response = new Promise<ChannelPermissionBehavior>(
398
401
  (resolve, reject) => {
399
402
  const timer = setTimeout(() => {
400
- this.pendingPermissions.delete(key);
403
+ this.permissions.delete(key);
401
404
  reject(
402
405
  new Error(
403
406
  `Permission request "${requestId}" timed out after ${timeoutMs}ms.`,
404
407
  ),
405
408
  );
406
409
  }, timeoutMs);
407
- this.pendingPermissions.set(key, { resolve, reject, timer });
410
+ this.permissions.set(key, { resolve, reject, timer });
408
411
  },
409
412
  );
410
413
 
@@ -439,10 +442,10 @@ export class Manager {
439
442
  });
440
443
  return await response;
441
444
  } catch (error) {
442
- const pending = this.pendingPermissions.get(key);
445
+ const pending = this.permissions.get(key);
443
446
  if (pending) {
444
447
  clearTimeout(pending.timer);
445
- this.pendingPermissions.delete(key);
448
+ this.permissions.delete(key);
446
449
  }
447
450
  throw error;
448
451
  }
@@ -36,15 +36,17 @@ export class System {
36
36
  return STATIC_PROMPT_FILES.filter((file) => {
37
37
  switch (file) {
38
38
  case "ltm.md":
39
- return this.config.features.ltm.enabled;
39
+ return this.config.tools.ltm.enabled;
40
40
  case "fetch.md":
41
- return this.config.features.fetch.enabled;
41
+ return this.config.tools.fetch.enabled;
42
42
  case "filesystem.md":
43
- return this.config.features.filesystem.enabled;
43
+ return this.config.tools.filesystem.enabled;
44
44
  case "shell.md":
45
- return this.config.features.shell.enabled;
45
+ return this.config.tools.shell.enabled;
46
46
  case "wiki.md":
47
- return this.config.features.wiki.enabled;
47
+ return this.config.tools.wiki.enabled;
48
+ case "skills.md":
49
+ return this.config.tools.skills.enabled;
48
50
  case "thinking.md":
49
51
  default:
50
52
  return true;
@@ -96,8 +98,8 @@ export class System {
96
98
  return {
97
99
  name: this.config.name,
98
100
  llm: this.config.llm,
99
- ltm: this.config.features.ltm,
100
- wiki: this.config.features.wiki,
101
+ ltm: this.config.tools.ltm,
102
+ wiki: this.config.tools.wiki,
101
103
  compaction: this.config.compaction,
102
104
  };
103
105
  }
@@ -353,14 +353,14 @@ function searchMetadata(page: PageRecord): Record<string, string | number> {
353
353
  export function createWikiTools(config: Config) {
354
354
  const root = wikiRoot();
355
355
  const client = new ChromaClient({
356
- ...chromaClientArgsFromUrl(config.features.wiki.chroma.url),
356
+ ...chromaClientArgsFromUrl(config.tools.wiki.chroma.url),
357
357
  });
358
358
  let collectionPromise: Promise<Collection> | null = null;
359
359
 
360
360
  const collection = async (): Promise<Collection> => {
361
361
  if (!collectionPromise) {
362
362
  collectionPromise = client.getOrCreateCollection({
363
- name: config.features.wiki.chroma.collection.wiki,
363
+ name: config.tools.wiki.chroma.collection.wiki,
364
364
  embeddingFunction: new HFEmbedding(),
365
365
  });
366
366
  }