hoomanjs 1.14.0 → 1.16.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.
Files changed (37) hide show
  1. package/package.json +1 -1
  2. package/src/acp/utils/tool-kind.ts +15 -0
  3. package/src/cli.ts +1 -0
  4. package/src/configure/app.tsx +75 -0
  5. package/src/configure/types.ts +1 -0
  6. package/src/core/agent/index.ts +38 -15
  7. package/src/core/agents/definitions.ts +46 -0
  8. package/src/core/agents/index.ts +15 -0
  9. package/src/core/agents/registry.ts +108 -0
  10. package/src/core/agents/runner.ts +375 -0
  11. package/src/core/agents/tools.ts +100 -0
  12. package/src/core/approvals/allowed-tools.ts +29 -4
  13. package/src/core/config.ts +54 -0
  14. package/src/core/index.ts +6 -1
  15. package/src/core/prompts/agents/plan.md +35 -0
  16. package/src/core/prompts/agents/research.md +32 -0
  17. package/src/core/prompts/environment.ts +62 -0
  18. package/src/core/prompts/harness/behaviour.md +28 -0
  19. package/src/core/prompts/harness/communication.md +31 -0
  20. package/src/core/prompts/harness/engineering.md +29 -0
  21. package/src/core/prompts/harness/execution.md +29 -0
  22. package/src/core/prompts/harness/guardrails.md +28 -0
  23. package/src/core/prompts/index.ts +7 -3
  24. package/src/core/prompts/static/daemon.md +22 -0
  25. package/src/core/prompts/static/environment.md +15 -0
  26. package/src/core/prompts/static/filesystem.md +2 -2
  27. package/src/core/prompts/static/ltm.md +6 -6
  28. package/src/core/prompts/static/shell.md +2 -2
  29. package/src/core/prompts/static/skills.md +1 -1
  30. package/src/core/prompts/static/sleep.md +20 -0
  31. package/src/core/prompts/static/subagents.md +28 -0
  32. package/src/core/prompts/static/thinking.md +2 -2
  33. package/src/core/prompts/static/todo.md +3 -3
  34. package/src/core/prompts/static/wiki.md +1 -1
  35. package/src/core/prompts/system.ts +53 -1
  36. package/src/core/tools/index.ts +1 -0
  37. package/src/core/tools/sleep.ts +51 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hoomanjs",
3
- "version": "1.14.0",
3
+ "version": "1.16.0",
4
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",
@@ -14,6 +14,7 @@ const KNOWN_TOOL_KINDS = new Map<string, ToolKind>([
14
14
  ["search_files", "search"],
15
15
  ["get_file_info", "read"],
16
16
  ["shell", "execute"],
17
+ ["sleep", "other"],
17
18
  ["fetch", "fetch"],
18
19
  ["wiki_list_files", "read"],
19
20
  ["wiki_read_file", "read"],
@@ -22,9 +23,23 @@ const KNOWN_TOOL_KINDS = new Map<string, ToolKind>([
22
23
  ["wiki_stats", "read"],
23
24
  ["wiki_search", "search"],
24
25
  ["think", "think"],
26
+ ["run_agents", "other"],
25
27
  ["update_todos", "other"],
26
28
  ["get_current_time", "other"],
27
29
  ["convert_time", "other"],
30
+ ["list_skills", "read"],
31
+ ["search_skills", "search"],
32
+ ["install_skill", "edit"],
33
+ ["delete_skill", "edit"],
34
+ ["store_memory", "edit"],
35
+ ["search_memory", "search"],
36
+ ["update_memory", "edit"],
37
+ ["archive_memory", "edit"],
38
+ ["list_mcp_servers", "read"],
39
+ ["get_mcp_server", "read"],
40
+ ["add_mcp_server", "edit"],
41
+ ["update_mcp_server", "edit"],
42
+ ["delete_mcp_server", "edit"],
28
43
  ]);
29
44
 
30
45
  export { INTERNAL_ALWAYS_ALLOWED };
package/src/cli.ts CHANGED
@@ -132,6 +132,7 @@ program
132
132
  {
133
133
  sessionId: session,
134
134
  userId: session,
135
+ mode: "daemon",
135
136
  },
136
137
  true,
137
138
  );
@@ -46,6 +46,14 @@ import {
46
46
  truncate,
47
47
  } from "./utils.ts";
48
48
 
49
+ const PROMPT_LABELS: Record<keyof ConfigData["prompts"], string> = {
50
+ behaviour: "Behaviour",
51
+ communication: "Communication",
52
+ execution: "Execution",
53
+ engineering: "Engineering",
54
+ guardrails: "Guardrails",
55
+ };
56
+
49
57
  export function ConfigureApp({
50
58
  config,
51
59
  mcpConfig,
@@ -131,6 +139,7 @@ export function ConfigureApp({
131
139
  ({
132
140
  name: config.name,
133
141
  llm: config.llm,
142
+ prompts: config.prompts,
134
143
  tools: config.tools,
135
144
  compaction: config.compaction,
136
145
  }) satisfies ConfigData,
@@ -369,6 +378,10 @@ export function ConfigureApp({
369
378
  };
370
379
 
371
380
  const renderConfigMenu = () => {
381
+ const enabledPrompts = Object.values(configData.prompts).filter(
382
+ Boolean,
383
+ ).length;
384
+ const totalPrompts = Object.keys(configData.prompts).length;
372
385
  const items: MenuItem[] = [
373
386
  {
374
387
  label: `Name • ${configData.name}`,
@@ -431,6 +444,10 @@ export function ConfigureApp({
431
444
  },
432
445
  }),
433
446
  },
447
+ {
448
+ label: `Prompts • ${enabledPrompts}/${totalPrompts} enabled`,
449
+ value: () => setScreen({ kind: "config-prompts" }),
450
+ },
434
451
  {
435
452
  label: `Todo tool • ${configData.tools.todo.enabled ? "Enabled" : "Disabled"}`,
436
453
  value: () => {
@@ -499,6 +516,23 @@ export function ConfigureApp({
499
516
  setScreen({ kind: "config" });
500
517
  },
501
518
  },
519
+ {
520
+ label: `Sleep tool • ${configData.tools.sleep.enabled ? "Enabled" : "Disabled"}`,
521
+ value: () => {
522
+ updateConfig(
523
+ {
524
+ tools: {
525
+ ...config.tools,
526
+ sleep: {
527
+ enabled: !configData.tools.sleep.enabled,
528
+ },
529
+ },
530
+ },
531
+ `Sleep tool ${configData.tools.sleep.enabled ? "disabled" : "enabled"}.`,
532
+ );
533
+ setScreen({ kind: "config" });
534
+ },
535
+ },
502
536
  {
503
537
  label: `Long-term memory • ${configData.tools.ltm.enabled ? "Enabled" : "Disabled"} • ${configData.tools.ltm.chroma.collection.memory}`,
504
538
  value: () => setScreen({ kind: "config-ltm" }),
@@ -636,6 +670,45 @@ export function ConfigureApp({
636
670
  );
637
671
  };
638
672
 
673
+ const renderPromptsConfigMenu = () => {
674
+ const promptKeys = Object.keys(
675
+ PROMPT_LABELS,
676
+ ) as (keyof ConfigData["prompts"])[];
677
+ const items: MenuItem[] = [
678
+ ...promptKeys.map((key) => {
679
+ const enabled = configData.prompts[key];
680
+ const label = PROMPT_LABELS[key];
681
+ return {
682
+ label: `${label} • ${enabled ? "Enabled" : "Disabled"}`,
683
+ value: () => {
684
+ updateConfig(
685
+ {
686
+ prompts: {
687
+ ...config.prompts,
688
+ [key]: !enabled,
689
+ },
690
+ },
691
+ `${label} prompt ${enabled ? "disabled" : "enabled"}.`,
692
+ );
693
+ setScreen({ kind: "config-prompts" });
694
+ },
695
+ };
696
+ }),
697
+ {
698
+ label: "Back",
699
+ value: () => setScreen({ kind: "config" }),
700
+ },
701
+ ];
702
+
703
+ return (
704
+ <MenuScreen
705
+ title="Prompts"
706
+ description="Choose which bundled harness prompt sections are included in future sessions."
707
+ items={items}
708
+ />
709
+ );
710
+ };
711
+
639
712
  const renderLtmConfigMenu = () => {
640
713
  const items: MenuItem[] = [
641
714
  {
@@ -1096,6 +1169,8 @@ export function ConfigureApp({
1096
1169
  return renderConfigMenu();
1097
1170
  case "config-provider":
1098
1171
  return renderProviderMenu();
1172
+ case "config-prompts":
1173
+ return renderPromptsConfigMenu();
1099
1174
  case "config-ltm":
1100
1175
  return renderLtmConfigMenu();
1101
1176
  case "config-wiki":
@@ -13,6 +13,7 @@ export type Screen =
13
13
  | { kind: "home" }
14
14
  | { kind: "config" }
15
15
  | { kind: "config-provider" }
16
+ | { kind: "config-prompts" }
16
17
  | { kind: "config-ltm" }
17
18
  | { kind: "config-wiki" }
18
19
  | { kind: "mcp" }
@@ -1,4 +1,5 @@
1
1
  import { Agent, BeforeInvocationEvent } from "@strands-agents/sdk";
2
+ import type { Tool } from "@strands-agents/sdk";
2
3
  import type { Config } from "../config.ts";
3
4
  import { modelProviders } from "../models";
4
5
  import {
@@ -14,10 +15,15 @@ import {
14
15
  createLongTermMemoryTools,
15
16
  } from "../memory";
16
17
  import { createSkillsTools, type Registry } from "../skills";
18
+ import {
19
+ createRunAgentsTools,
20
+ loadBuiltInAgentDefinitions,
21
+ } from "../agents/index.ts";
17
22
  import {
18
23
  createTodoTools,
19
24
  createFetchTools,
20
25
  createFilesystemTools,
26
+ createSleepTools,
21
27
  createShellTools,
22
28
  createThinkingTools,
23
29
  createTimeTools,
@@ -49,7 +55,7 @@ export async function create(
49
55
  const skills = config.tools.skills.enabled
50
56
  ? (await createSkillsPrompt(registry)).content
51
57
  : "";
52
- const tools = config.tools.mcp.enabled
58
+ const prefixed = config.tools.mcp.enabled
53
59
  ? await mcp.manager.listPrefixedTools()
54
60
  : [];
55
61
  const append = config.tools.mcp.enabled
@@ -58,27 +64,44 @@ export async function create(
58
64
  const prompt = [system.content, meta.systemPrompt, ...append, skills]
59
65
  .filter((x) => !!x)
60
66
  .join(SECTION_BREAK);
67
+ const model = llm.create(config.llm.model, config.llm.params);
68
+ const tools: Tool[] = [
69
+ ...createTimeTools(),
70
+ ...(config.tools.sleep.enabled ? createSleepTools() : []),
71
+ ...(config.tools.todo.enabled ? createTodoTools() : []),
72
+ ...(config.tools.fetch.enabled ? createFetchTools() : []),
73
+ ...(ltm ? createLongTermMemoryTools(ltm) : []),
74
+ ...(config.tools.filesystem.enabled ? createFilesystemTools() : []),
75
+ ...(config.tools.shell.enabled ? createShellTools() : []),
76
+ ...(config.tools.wiki.enabled ? createWikiTools(config) : []),
77
+ ...(config.tools.mcp.enabled ? createMcpTools(mcp.config) : []),
78
+ ...(config.tools.skills.enabled ? createSkillsTools(registry) : []),
79
+ ...createThinkingTools(),
80
+ ...prefixed,
81
+ ];
82
+ if (config.tools.agents.enabled) {
83
+ const definitions = loadBuiltInAgentDefinitions(config, {
84
+ knownTools: tools.map((entry) => entry.name),
85
+ });
86
+ tools.push(
87
+ ...createRunAgentsTools({
88
+ parent: config.name,
89
+ definitions,
90
+ tools,
91
+ createModel: () => llm.create(config.llm.model, config.llm.params),
92
+ defaultConcurrency: config.tools.agents.concurrency,
93
+ }),
94
+ );
95
+ }
61
96
  const agent = new Agent({
62
97
  name: config.name,
63
98
  systemPrompt: prompt,
64
- model: llm.create(config.llm.model, config.llm.params),
99
+ model,
65
100
  appState: {
66
101
  ...(userId ? { userId } : {}),
67
102
  ...(sessionId ? { sessionId } : {}),
68
103
  },
69
- tools: [
70
- ...createTimeTools(),
71
- ...(config.tools.todo.enabled ? createTodoTools() : []),
72
- ...(config.tools.fetch.enabled ? createFetchTools() : []),
73
- ...(ltm ? createLongTermMemoryTools(ltm) : []),
74
- ...(config.tools.filesystem.enabled ? createFilesystemTools() : []),
75
- ...(config.tools.shell.enabled ? createShellTools() : []),
76
- ...(config.tools.wiki.enabled ? createWikiTools(config) : []),
77
- ...(config.tools.mcp.enabled ? createMcpTools(mcp.config) : []),
78
- ...(config.tools.skills.enabled ? createSkillsTools(registry) : []),
79
- ...createThinkingTools(),
80
- ...tools,
81
- ],
104
+ tools,
82
105
  printer: print,
83
106
  ...stm,
84
107
  });
@@ -0,0 +1,46 @@
1
+ export const BUILTIN_AGENT_KINDS = ["research", "plan"] as const;
2
+
3
+ export type AgentKind = (typeof BUILTIN_AGENT_KINDS)[number];
4
+
5
+ export type AgentConfig = {
6
+ id: AgentKind;
7
+ instructions: string;
8
+ description: string;
9
+ tools: readonly string[];
10
+ };
11
+
12
+ export type AgentDefinition = AgentConfig & {
13
+ instructionsText: string;
14
+ };
15
+
16
+ export const BUILTIN_AGENT_CONFIGS: readonly AgentConfig[] = [
17
+ {
18
+ id: "research",
19
+ instructions: "research.md",
20
+ description: "Investigates sources and context before the parent acts.",
21
+ tools: [
22
+ "read_file",
23
+ "read_multiple_files",
24
+ "list_directory",
25
+ "directory_tree",
26
+ "search_files",
27
+ "get_file_info",
28
+ "fetch",
29
+ "think",
30
+ ],
31
+ },
32
+ {
33
+ id: "plan",
34
+ instructions: "plan.md",
35
+ description: "Produces plans, tradeoffs, risks, and validation steps.",
36
+ tools: [
37
+ "read_file",
38
+ "read_multiple_files",
39
+ "list_directory",
40
+ "directory_tree",
41
+ "search_files",
42
+ "get_file_info",
43
+ "think",
44
+ ],
45
+ },
46
+ ];
@@ -0,0 +1,15 @@
1
+ export {
2
+ BUILTIN_AGENT_CONFIGS,
3
+ BUILTIN_AGENT_KINDS,
4
+ type AgentConfig,
5
+ type AgentDefinition,
6
+ type AgentKind,
7
+ } from "./definitions.ts";
8
+ export { loadBuiltInAgentDefinitions } from "./registry.ts";
9
+ export {
10
+ runAgentJobs,
11
+ type AgentJob,
12
+ type AgentJobResult,
13
+ type RunAgentJobsResult,
14
+ } from "./runner.ts";
15
+ export { RUN_AGENTS_TOOL_NAME, createRunAgentsTools } from "./tools.ts";
@@ -0,0 +1,108 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { compile } from "handlebars";
5
+ import type { Config } from "../config.ts";
6
+ import { getEnvironmentPromptContext } from "../prompts/environment.ts";
7
+ import {
8
+ BUILTIN_AGENT_CONFIGS,
9
+ type AgentConfig,
10
+ type AgentDefinition,
11
+ } from "./definitions.ts";
12
+
13
+ function promptsDir(): string {
14
+ return join(dirname(fileURLToPath(import.meta.url)), "../prompts/agents");
15
+ }
16
+
17
+ function validateConfigs(configs: readonly AgentConfig[]): void {
18
+ const seen = new Set<string>();
19
+ for (const config of configs) {
20
+ if (!config.id.trim()) {
21
+ throw new Error("Agent config id cannot be empty.");
22
+ }
23
+ if (seen.has(config.id)) {
24
+ throw new Error(`Duplicate agent config id: '${config.id}'.`);
25
+ }
26
+ seen.add(config.id);
27
+ if (!config.instructions.trim()) {
28
+ throw new Error(
29
+ `Agent '${config.id}' instructions file cannot be empty.`,
30
+ );
31
+ }
32
+ if (!config.description.trim()) {
33
+ throw new Error(`Agent '${config.id}' description cannot be empty.`);
34
+ }
35
+ if (!Array.isArray(config.tools) || config.tools.length === 0) {
36
+ throw new Error(`Agent '${config.id}' must declare at least one tool.`);
37
+ }
38
+ for (const toolName of config.tools) {
39
+ if (!toolName.trim()) {
40
+ throw new Error(`Agent '${config.id}' has an empty tool name.`);
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ function assertKnownTools(
47
+ definitions: readonly AgentDefinition[],
48
+ knownTools: readonly string[],
49
+ ): void {
50
+ const known = new Set(knownTools);
51
+ for (const definition of definitions) {
52
+ for (const toolName of definition.tools) {
53
+ if (!known.has(toolName)) {
54
+ throw new Error(
55
+ `Agent '${definition.id}' references unknown tool '${toolName}'.`,
56
+ );
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ function context(config: Config): Record<string, unknown> {
63
+ return {
64
+ name: config.name,
65
+ llm: config.llm,
66
+ environment: getEnvironmentPromptContext(),
67
+ ltm: config.tools.ltm,
68
+ wiki: config.tools.wiki,
69
+ compaction: config.compaction,
70
+ };
71
+ }
72
+
73
+ export function loadBuiltInAgentDefinitions(
74
+ config: Config,
75
+ options?: { knownTools?: readonly string[] },
76
+ ): AgentDefinition[] {
77
+ validateConfigs(BUILTIN_AGENT_CONFIGS);
78
+ const dir = promptsDir();
79
+ const definitions = BUILTIN_AGENT_CONFIGS.map((entry) => {
80
+ const fullPath = join(dir, entry.instructions);
81
+ if (!existsSync(fullPath)) {
82
+ throw new Error(
83
+ `Agent '${entry.id}' instructions file not found: ${entry.instructions}`,
84
+ );
85
+ }
86
+ const raw = readFileSync(fullPath, "utf8").trim();
87
+ if (!raw) {
88
+ throw new Error(
89
+ `Agent '${entry.id}' instructions file is empty: ${entry.instructions}`,
90
+ );
91
+ }
92
+ const template = compile(raw);
93
+ const instructionsText = template(context(config)).trim();
94
+ if (!instructionsText) {
95
+ throw new Error(
96
+ `Agent '${entry.id}' instructions rendered to empty content.`,
97
+ );
98
+ }
99
+ return {
100
+ ...entry,
101
+ instructionsText,
102
+ };
103
+ });
104
+ if (options?.knownTools) {
105
+ assertKnownTools(definitions, options.knownTools);
106
+ }
107
+ return definitions;
108
+ }