hoomanjs 1.11.3 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hoomanjs",
3
- "version": "1.11.3",
3
+ "version": "1.12.0",
4
4
  "description": "Bun-powered local AI agent CLI with chat, exec, ACP, MCP, and skills support.",
5
5
  "author": {
6
6
  "name": "Vaibhav Pandey",
@@ -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
  }
@@ -5,11 +5,19 @@ import {
5
5
  HOOMAN_CHANNEL_PERMISSION,
6
6
  type ChannelMessage,
7
7
  type ChannelPermissionBehavior,
8
+ type ChannelSubscription,
9
+ type ChannelSubscriptionHandle,
8
10
  } from "./manager.ts";
9
11
 
10
12
  export { Config, Manager };
11
13
  export { HOOMAN_CHANNEL, HOOMAN_CHANNEL_PERMISSION };
12
- export type { ChannelMessage, ChannelPermissionBehavior, NamedMcpTransport };
14
+ export type {
15
+ ChannelMessage,
16
+ ChannelPermissionBehavior,
17
+ ChannelSubscription,
18
+ ChannelSubscriptionHandle,
19
+ NamedMcpTransport,
20
+ };
13
21
  export { createMcpTools } from "./tools.ts";
14
22
 
15
23
  export function createMcpConfig(path: string): Config {
@@ -18,7 +26,8 @@ export function createMcpConfig(path: string): Config {
18
26
 
19
27
  export function createMcpManager(
20
28
  config: Config,
29
+ acp = false,
21
30
  mcpServers: readonly NamedMcpTransport[] = [],
22
31
  ): Manager {
23
- return new Manager(config, mcpServers);
32
+ return new Manager(config, acp, mcpServers);
24
33
  }
@@ -33,6 +33,16 @@ export type ChannelMessage = {
33
33
 
34
34
  export type ChannelPermissionBehavior = "allow_once" | "allow_always" | "deny";
35
35
 
36
+ export type ChannelSubscription = {
37
+ server: string;
38
+ channel: string;
39
+ };
40
+
41
+ export type ChannelSubscriptionHandle = {
42
+ unsubscribe: () => void;
43
+ subscriptions: ChannelSubscription[];
44
+ };
45
+
36
46
  type ChannelPermissionRequest = {
37
47
  requestId: string;
38
48
  tool: string;
@@ -111,7 +121,7 @@ function readSourceValue(value: unknown): string | undefined {
111
121
  */
112
122
  export class Manager {
113
123
  private instances: Map<string, McpClient> | null = null;
114
- private readonly pendingPermissions = new Map<
124
+ private readonly permissions = new Map<
115
125
  string,
116
126
  {
117
127
  resolve: (behavior: ChannelPermissionBehavior) => void;
@@ -122,7 +132,8 @@ export class Manager {
122
132
 
123
133
  public constructor(
124
134
  private readonly config: Config,
125
- private readonly mcpServers: readonly NamedMcpTransport[] = [],
135
+ private readonly acp = false,
136
+ private readonly servers: readonly NamedMcpTransport[] = [],
126
137
  ) {}
127
138
 
128
139
  /** Lazily builds clients from the current in-memory config (reloads file first). */
@@ -138,13 +149,15 @@ export class Manager {
138
149
  * previous clients (stdio subprocesses, HTTP sessions).
139
150
  */
140
151
  public reload(): void {
141
- this.config.reload();
152
+ if (!this.acp) {
153
+ this.config.reload();
154
+ }
142
155
  const previous = this.instances;
143
156
  const next = new Map<string, McpClient>();
144
157
  const transports = [
145
- ...this.config.list(),
146
- // Session-scoped ACP servers intentionally override local config names.
147
- ...this.mcpServers,
158
+ ...(this.acp ? [] : this.config.list()),
159
+ // Session-scoped servers override local config entries on name conflicts.
160
+ ...this.servers,
148
161
  ];
149
162
  for (const { name, transport } of transports) {
150
163
  next.set(
@@ -163,11 +176,11 @@ export class Manager {
163
176
  }
164
177
 
165
178
  public async disconnect(): Promise<void> {
166
- for (const [key, pending] of this.pendingPermissions.entries()) {
179
+ for (const [key, pending] of this.permissions.entries()) {
167
180
  clearTimeout(pending.timer);
168
181
  pending.reject(new Error(`Pending permission "${key}" cancelled.`));
169
182
  }
170
- this.pendingPermissions.clear();
183
+ this.permissions.clear();
171
184
  const toClose = this.instances;
172
185
  this.instances = null;
173
186
  if (!toClose?.size) {
@@ -224,7 +237,7 @@ export class Manager {
224
237
  public async subscribeToChannels(
225
238
  channels: readonly string[],
226
239
  onMessage: (message: ChannelMessage) => void,
227
- ): Promise<() => void> {
240
+ ): Promise<ChannelSubscriptionHandle> {
228
241
  if (this.instances === null) {
229
242
  this.reload();
230
243
  }
@@ -234,10 +247,11 @@ export class Manager {
234
247
  ...new Set(channels.map((c) => c.trim()).filter(Boolean)),
235
248
  ];
236
249
  if (requested.length === 0) {
237
- return () => {};
250
+ return { unsubscribe: () => {}, subscriptions: [] };
238
251
  }
239
252
 
240
253
  const unsubs: Array<() => void> = [];
254
+ const subscriptions: ChannelSubscription[] = [];
241
255
  for (const [server, client] of map.entries()) {
242
256
  await client.connect();
243
257
  const experimental =
@@ -270,11 +284,11 @@ export class Manager {
270
284
  return;
271
285
  }
272
286
  const key = `${server}:${requestId}`;
273
- const pending = this.pendingPermissions.get(key);
287
+ const pending = this.permissions.get(key);
274
288
  if (!pending) {
275
289
  return;
276
290
  }
277
- this.pendingPermissions.delete(key);
291
+ this.permissions.delete(key);
278
292
  clearTimeout(pending.timer);
279
293
  pending.resolve(behavior);
280
294
  };
@@ -289,6 +303,8 @@ export class Manager {
289
303
  continue;
290
304
  }
291
305
 
306
+ subscriptions.push({ server, channel });
307
+
292
308
  const method = `notifications/${channel}`;
293
309
  const schema = z.object({
294
310
  method: z.literal(method),
@@ -327,10 +343,13 @@ export class Manager {
327
343
  }
328
344
  }
329
345
 
330
- return () => {
331
- for (const off of unsubs) {
332
- off();
333
- }
346
+ return {
347
+ subscriptions,
348
+ unsubscribe: () => {
349
+ for (const off of unsubs) {
350
+ off();
351
+ }
352
+ },
334
353
  };
335
354
  }
336
355
 
@@ -374,21 +393,21 @@ export class Manager {
374
393
  throw new Error("requestId is required.");
375
394
  }
376
395
  const key = `${server}:${requestId}`;
377
- if (this.pendingPermissions.has(key)) {
396
+ if (this.permissions.has(key)) {
378
397
  throw new Error(`Permission request "${requestId}" is already pending.`);
379
398
  }
380
399
 
381
400
  const response = new Promise<ChannelPermissionBehavior>(
382
401
  (resolve, reject) => {
383
402
  const timer = setTimeout(() => {
384
- this.pendingPermissions.delete(key);
403
+ this.permissions.delete(key);
385
404
  reject(
386
405
  new Error(
387
406
  `Permission request "${requestId}" timed out after ${timeoutMs}ms.`,
388
407
  ),
389
408
  );
390
409
  }, timeoutMs);
391
- this.pendingPermissions.set(key, { resolve, reject, timer });
410
+ this.permissions.set(key, { resolve, reject, timer });
392
411
  },
393
412
  );
394
413
 
@@ -423,10 +442,10 @@ export class Manager {
423
442
  });
424
443
  return await response;
425
444
  } catch (error) {
426
- const pending = this.pendingPermissions.get(key);
445
+ const pending = this.permissions.get(key);
427
446
  if (pending) {
428
447
  clearTimeout(pending.timer);
429
- this.pendingPermissions.delete(key);
448
+ this.permissions.delete(key);
430
449
  }
431
450
  throw error;
432
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
  }
@@ -3,6 +3,7 @@ import type { Agent } from "@strands-agents/sdk";
3
3
  import { HOOMAN_CHANNEL } from "../core/mcp/index.ts";
4
4
  import type {
5
5
  ChannelMessage,
6
+ ChannelSubscription,
6
7
  Manager as McpManager,
7
8
  } from "../core/mcp/index.ts";
8
9
  import { createQueue } from "./queue.ts";
@@ -42,6 +43,18 @@ function resolveUserId(
42
43
  return `${message.meta.server}:${raw}`;
43
44
  }
44
45
 
46
+ function formatSubscriptions(
47
+ subscriptions: readonly ChannelSubscription[],
48
+ ): string {
49
+ if (subscriptions.length === 0) {
50
+ return "none";
51
+ }
52
+ const servers = [
53
+ ...new Set(subscriptions.map((subscription) => subscription.server)),
54
+ ].sort((left, right) => left.localeCompare(right));
55
+ return `${servers.length} MCP server(s): ${servers.join(", ")}`;
56
+ }
57
+
45
58
  export async function main(options: RunDaemonOptions): Promise<void> {
46
59
  if (!options.channels) {
47
60
  throw new Error("No daemon inputs enabled. Pass --channels.");
@@ -94,7 +107,7 @@ export async function main(options: RunDaemonOptions): Promise<void> {
94
107
  () => unsubscribe(),
95
108
  );
96
109
 
97
- unsubscribe = await options.manager.subscribeToChannels(
110
+ const handle = await options.manager.subscribeToChannels(
98
111
  channels,
99
112
  (message) => {
100
113
  debug(
@@ -103,7 +116,8 @@ export async function main(options: RunDaemonOptions): Promise<void> {
103
116
  void queue.push(message);
104
117
  },
105
118
  );
106
- debug(`subscribed to ${channels.length} channel(s)`);
119
+ unsubscribe = handle.unsubscribe;
120
+ debug(`subscribed → ${formatSubscriptions(handle.subscriptions)}`);
107
121
 
108
122
  try {
109
123
  await stop();