hoomanjs 1.0.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/.github/screenshot.png +0 -0
- package/.github/workflows/build-publish.yml +49 -0
- package/LICENSE +21 -0
- package/README.md +399 -0
- package/docker-compose.yml +13 -0
- package/package.json +78 -0
- package/src/acp/acp-agent.ts +803 -0
- package/src/acp/approvals.ts +147 -0
- package/src/acp/index.ts +1 -0
- package/src/acp/meta/system-prompt.ts +44 -0
- package/src/acp/meta/user-id.ts +44 -0
- package/src/acp/prompt-invoke.ts +149 -0
- package/src/acp/sessions/config-options.ts +56 -0
- package/src/acp/sessions/replay.ts +131 -0
- package/src/acp/sessions/store.ts +158 -0
- package/src/acp/sessions/title.ts +22 -0
- package/src/acp/utils/paths.ts +5 -0
- package/src/acp/utils/tool-kind.ts +38 -0
- package/src/acp/utils/tool-locations.ts +46 -0
- package/src/acp/utils/tool-result-content.ts +27 -0
- package/src/chat/app.tsx +428 -0
- package/src/chat/approvals.ts +96 -0
- package/src/chat/components/ApprovalPrompt.tsx +25 -0
- package/src/chat/components/ChatMessage.tsx +47 -0
- package/src/chat/components/Composer.tsx +39 -0
- package/src/chat/components/EmptyChatBanner.tsx +26 -0
- package/src/chat/components/ReasoningStrip.tsx +30 -0
- package/src/chat/components/Spinner.tsx +34 -0
- package/src/chat/components/StatusBar.tsx +65 -0
- package/src/chat/components/ThinkingStatus.tsx +128 -0
- package/src/chat/components/ToolEvent.tsx +34 -0
- package/src/chat/components/Transcript.tsx +34 -0
- package/src/chat/components/ascii-logo.ts +11 -0
- package/src/chat/components/shared.ts +70 -0
- package/src/chat/index.tsx +42 -0
- package/src/chat/types.ts +21 -0
- package/src/cli.ts +146 -0
- package/src/configure/app.tsx +911 -0
- package/src/configure/components/BusyScreen.tsx +22 -0
- package/src/configure/components/HomeScreen.tsx +43 -0
- package/src/configure/components/MenuScreen.tsx +44 -0
- package/src/configure/components/PromptForm.tsx +40 -0
- package/src/configure/components/SelectMenuItem.tsx +30 -0
- package/src/configure/index.tsx +43 -0
- package/src/configure/open-in-editor.ts +133 -0
- package/src/configure/types.ts +45 -0
- package/src/configure/utils.ts +113 -0
- package/src/core/agent/index.ts +76 -0
- package/src/core/config.ts +157 -0
- package/src/core/index.ts +54 -0
- package/src/core/mcp/config.ts +80 -0
- package/src/core/mcp/index.ts +13 -0
- package/src/core/mcp/manager.ts +109 -0
- package/src/core/mcp/prefixed-mcp-tool.ts +45 -0
- package/src/core/mcp/tools.ts +92 -0
- package/src/core/mcp/types.ts +37 -0
- package/src/core/memory/index.ts +17 -0
- package/src/core/memory/ltm/embed.ts +67 -0
- package/src/core/memory/ltm/index.ts +18 -0
- package/src/core/memory/ltm/store.ts +376 -0
- package/src/core/memory/ltm/tools.ts +146 -0
- package/src/core/memory/ltm/types.ts +111 -0
- package/src/core/memory/ltm/utils.ts +218 -0
- package/src/core/memory/stm/index.ts +17 -0
- package/src/core/models/anthropic.ts +53 -0
- package/src/core/models/bedrock.ts +54 -0
- package/src/core/models/google.ts +51 -0
- package/src/core/models/index.ts +16 -0
- package/src/core/models/ollama/index.ts +13 -0
- package/src/core/models/ollama/strands-ollama.ts +439 -0
- package/src/core/models/openai.ts +12 -0
- package/src/core/prompts/index.ts +23 -0
- package/src/core/prompts/skills.ts +66 -0
- package/src/core/prompts/static/fetch.md +33 -0
- package/src/core/prompts/static/filesystem.md +38 -0
- package/src/core/prompts/static/identity.md +22 -0
- package/src/core/prompts/static/ltm.md +39 -0
- package/src/core/prompts/static/memory.md +39 -0
- package/src/core/prompts/static/shell.md +34 -0
- package/src/core/prompts/static/skills.md +19 -0
- package/src/core/prompts/static/thinking.md +27 -0
- package/src/core/prompts/system.ts +109 -0
- package/src/core/skills/index.ts +2 -0
- package/src/core/skills/registry.ts +239 -0
- package/src/core/skills/tools.ts +80 -0
- package/src/core/toolkit.ts +13 -0
- package/src/core/tools/fetch.ts +288 -0
- package/src/core/tools/filesystem.ts +747 -0
- package/src/core/tools/index.ts +5 -0
- package/src/core/tools/shell.ts +426 -0
- package/src/core/tools/thinking.ts +184 -0
- package/src/core/tools/time.ts +121 -0
- package/src/core/utils/cwd-context.ts +11 -0
- package/src/core/utils/paths.ts +28 -0
- package/src/exec/approvals.ts +85 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { tool } from "@strands-agents/sdk";
|
|
2
|
+
import type { JSONValue } from "@strands-agents/sdk";
|
|
3
|
+
import { DateTime, IANAZone } from "luxon";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
type TimeInfo = {
|
|
7
|
+
timezone: string;
|
|
8
|
+
datetime: string;
|
|
9
|
+
day_of_week: string;
|
|
10
|
+
is_dst: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function toJsonValue(value: unknown): JSONValue {
|
|
14
|
+
return JSON.parse(JSON.stringify(value)) as JSONValue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getLocalTimezone(): string {
|
|
18
|
+
const zone = DateTime.local().zoneName;
|
|
19
|
+
return IANAZone.isValidZone(zone) ? zone : "UTC";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveTimezone(timezone?: string): string {
|
|
23
|
+
const zone = timezone?.trim() || getLocalTimezone();
|
|
24
|
+
if (!IANAZone.isValidZone(zone)) {
|
|
25
|
+
throw new Error(`Invalid timezone: ${zone}`);
|
|
26
|
+
}
|
|
27
|
+
return zone;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toTimeInfo(dt: DateTime): TimeInfo {
|
|
31
|
+
return {
|
|
32
|
+
timezone: dt.zoneName ?? "UTC",
|
|
33
|
+
datetime: dt.toISO({ suppressMilliseconds: true }) ?? dt.toISO() ?? "",
|
|
34
|
+
day_of_week: dt.weekdayLong ?? "",
|
|
35
|
+
is_dst: dt.isInDST,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseSourceTime(sourceTimezone: string, time: string): DateTime {
|
|
40
|
+
const sourceNow = DateTime.now().setZone(sourceTimezone);
|
|
41
|
+
const parsed = DateTime.fromFormat(time.trim(), "HH:mm", {
|
|
42
|
+
zone: sourceTimezone,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!parsed.isValid) {
|
|
46
|
+
throw new Error("Invalid time format. Expected HH:MM [24-hour format]");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return sourceNow.set({
|
|
50
|
+
hour: parsed.hour,
|
|
51
|
+
minute: parsed.minute,
|
|
52
|
+
second: 0,
|
|
53
|
+
millisecond: 0,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function formatOffsetDifference(source: DateTime, target: DateTime): string {
|
|
58
|
+
const diffHours = (target.offset - source.offset) / 60;
|
|
59
|
+
const formatted = Number.isInteger(diffHours)
|
|
60
|
+
? diffHours.toFixed(1)
|
|
61
|
+
: diffHours.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
62
|
+
return `${diffHours >= 0 ? "+" : ""}${formatted}h`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createTimeTools() {
|
|
66
|
+
const localTimezone = getLocalTimezone();
|
|
67
|
+
|
|
68
|
+
return [
|
|
69
|
+
tool({
|
|
70
|
+
name: "get_current_time",
|
|
71
|
+
description:
|
|
72
|
+
"Get the current time in a specific timezone. Defaults to the local timezone when omitted.",
|
|
73
|
+
inputSchema: z.object({
|
|
74
|
+
timezone: z
|
|
75
|
+
.string()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe(
|
|
78
|
+
`IANA timezone name (e.g. 'America/New_York', 'Europe/London'). Defaults to '${localTimezone}'.`,
|
|
79
|
+
),
|
|
80
|
+
}),
|
|
81
|
+
callback: async (input) => {
|
|
82
|
+
const timezone = resolveTimezone(input.timezone);
|
|
83
|
+
const current = DateTime.now().setZone(timezone);
|
|
84
|
+
|
|
85
|
+
return toJsonValue(toTimeInfo(current));
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
tool({
|
|
89
|
+
name: "convert_time",
|
|
90
|
+
description:
|
|
91
|
+
"Convert a time between timezones. Defaults omitted timezones to the local timezone.",
|
|
92
|
+
inputSchema: z.object({
|
|
93
|
+
source_timezone: z
|
|
94
|
+
.string()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe(
|
|
97
|
+
`Source IANA timezone name. Defaults to '${localTimezone}'.`,
|
|
98
|
+
),
|
|
99
|
+
time: z.string().describe("Time to convert in 24-hour format (HH:MM)."),
|
|
100
|
+
target_timezone: z
|
|
101
|
+
.string()
|
|
102
|
+
.optional()
|
|
103
|
+
.describe(
|
|
104
|
+
`Target IANA timezone name. Defaults to '${localTimezone}'.`,
|
|
105
|
+
),
|
|
106
|
+
}),
|
|
107
|
+
callback: async (input) => {
|
|
108
|
+
const sourceTimezone = resolveTimezone(input.source_timezone);
|
|
109
|
+
const targetTimezone = resolveTimezone(input.target_timezone);
|
|
110
|
+
const source = parseSourceTime(sourceTimezone, input.time);
|
|
111
|
+
const target = source.setZone(targetTimezone);
|
|
112
|
+
|
|
113
|
+
return toJsonValue({
|
|
114
|
+
source: toTimeInfo(source),
|
|
115
|
+
target: toTimeInfo(target),
|
|
116
|
+
time_difference: formatOffsetDifference(source, target),
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
}),
|
|
120
|
+
];
|
|
121
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
const storage = new AsyncLocalStorage<string>();
|
|
4
|
+
|
|
5
|
+
export function runWithCwd<T>(cwd: string, fn: () => Promise<T>): Promise<T> {
|
|
6
|
+
return storage.run(cwd, fn);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getCwd(): string {
|
|
10
|
+
return storage.getStore() ?? process.cwd();
|
|
11
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
const HOME_FOLDER_NAME = ".hooman";
|
|
5
|
+
|
|
6
|
+
export const basePath = () => {
|
|
7
|
+
return join(homedir(), HOME_FOLDER_NAME);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const configJsonPath = () => {
|
|
11
|
+
return join(basePath(), "config.json");
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const instructionsMdPath = () => {
|
|
15
|
+
return join(basePath(), "instructions.md");
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const mcpJsonPath = () => {
|
|
19
|
+
return join(basePath(), "mcp.json");
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const sessionsPath = () => {
|
|
23
|
+
return join(basePath(), "sessions");
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const skillsPath = () => {
|
|
27
|
+
return join(basePath(), "skills");
|
|
28
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { stdin, stdout } from "node:process";
|
|
3
|
+
import { BeforeToolCallEvent } from "@strands-agents/sdk";
|
|
4
|
+
import type { Config } from "../core/config.ts";
|
|
5
|
+
|
|
6
|
+
const INTERNAL_ALWAYS_ALLOWED = new Set(["strands_structured_output"]);
|
|
7
|
+
const INPUT_PREVIEW_LIMIT = 1_024;
|
|
8
|
+
|
|
9
|
+
type ApprovalDecision = "allow" | "reject" | "always";
|
|
10
|
+
|
|
11
|
+
function inputPreview(input: unknown): string {
|
|
12
|
+
try {
|
|
13
|
+
const text = JSON.stringify(input, null, 2) ?? "null";
|
|
14
|
+
return text.length > INPUT_PREVIEW_LIMIT
|
|
15
|
+
? `${text.slice(0, INPUT_PREVIEW_LIMIT)}\n... (truncated)`
|
|
16
|
+
: text;
|
|
17
|
+
} catch {
|
|
18
|
+
return String(input);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function canPromptForApproval(): boolean {
|
|
23
|
+
return Boolean(stdin.isTTY && stdout.isTTY);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function promptForApproval(
|
|
27
|
+
event: BeforeToolCallEvent,
|
|
28
|
+
): Promise<ApprovalDecision> {
|
|
29
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
30
|
+
const description = event.tool?.description?.trim();
|
|
31
|
+
const preview = inputPreview(event.toolUse.input);
|
|
32
|
+
try {
|
|
33
|
+
stdout.write(`\nTool approval required\n`);
|
|
34
|
+
stdout.write(`Tool: ${event.toolUse.name}\n`);
|
|
35
|
+
if (description) {
|
|
36
|
+
stdout.write(`Description: ${description}\n`);
|
|
37
|
+
}
|
|
38
|
+
stdout.write(`Input:\n${preview}\n`);
|
|
39
|
+
stdout.write(`Options: [a]llow once, [r]eject, [A]lways allow\n`);
|
|
40
|
+
while (true) {
|
|
41
|
+
const answer = (await rl.question("> ")).trim();
|
|
42
|
+
if (answer === "a" || answer === "allow" || answer === "") {
|
|
43
|
+
return "allow";
|
|
44
|
+
}
|
|
45
|
+
if (answer === "r" || answer === "reject") {
|
|
46
|
+
return "reject";
|
|
47
|
+
}
|
|
48
|
+
if (answer === "A" || answer === "always") {
|
|
49
|
+
return "always";
|
|
50
|
+
}
|
|
51
|
+
stdout.write("Enter a, r, or A.\n");
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
rl.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type BeforeToolCallEventHandler = (event: BeforeToolCallEvent) => Promise<void>;
|
|
59
|
+
|
|
60
|
+
export function createToolApprovalHandler(
|
|
61
|
+
config: Config,
|
|
62
|
+
): BeforeToolCallEventHandler {
|
|
63
|
+
return async function onBeforeToolCallEvent(event: BeforeToolCallEvent) {
|
|
64
|
+
const name = event.toolUse.name;
|
|
65
|
+
if (
|
|
66
|
+
INTERNAL_ALWAYS_ALLOWED.has(name) ||
|
|
67
|
+
config.tools.allowed.includes(name)
|
|
68
|
+
) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!canPromptForApproval()) {
|
|
72
|
+
event.cancel = `Tool "${name}" requires approval, but no interactive terminal is available. Add it to config.tools.allowed to always allow it.`;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const decision = await promptForApproval(event);
|
|
76
|
+
if (decision === "allow") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (decision === "always") {
|
|
80
|
+
config.update({ tools: { allowed: [...config.tools.allowed, name] } });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
event.cancel = `Tool "${name}" was rejected by the user.`;
|
|
84
|
+
};
|
|
85
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"types": ["bun"],
|
|
11
|
+
|
|
12
|
+
// Bundler mode
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
"verbatimModuleSyntax": true,
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
|
|
18
|
+
// Best practices
|
|
19
|
+
"strict": true,
|
|
20
|
+
"skipLibCheck": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
"noUncheckedIndexedAccess": true,
|
|
23
|
+
"noImplicitOverride": true,
|
|
24
|
+
|
|
25
|
+
// Some stricter flags (disabled by default)
|
|
26
|
+
"noUnusedLocals": false,
|
|
27
|
+
"noUnusedParameters": false,
|
|
28
|
+
"noPropertyAccessFromIndexSignature": false
|
|
29
|
+
}
|
|
30
|
+
}
|