hoomanjs 1.17.4 → 1.18.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 +3 -1
- package/src/acp/approvals.ts +1 -1
- package/src/acp/utils/tool-kind.ts +1 -1
- package/src/chat/app.tsx +57 -36
- package/src/chat/approvals.ts +1 -1
- package/src/chat/components/TodoPanel.tsx +1 -1
- package/src/chat/components/ToolEvent.tsx +7 -0
- package/src/chat/components/ToolEventFileResult.tsx +116 -0
- package/src/chat/components/TranscriptViewport.tsx +170 -0
- package/src/chat/components/file-tool-diff/file-tool-result.ts +155 -0
- package/src/chat/components/prompt-input/usePromptInputController.ts +5 -0
- package/src/chat/mouse.ts +17 -0
- package/src/chat/types.ts +3 -0
- package/src/core/agent/index.ts +1 -1
- package/src/core/agents/registry.ts +12 -14
- package/src/core/prompts/system.ts +13 -0
- package/src/core/state/file-tool-display.ts +70 -0
- package/src/core/state/thought-process.ts +49 -0
- package/src/core/state/todos.ts +84 -0
- package/src/core/tools/filesystem.ts +369 -26
- package/src/core/tools/thinking.ts +5 -40
- package/src/core/tools/todo.ts +6 -79
- package/src/daemon/approvals.ts +1 -1
- package/src/exec/approvals.ts +1 -1
- /package/src/core/{approvals/allowed-tools.ts → state/tool-approvals.ts} +0 -0
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import { getPromptView, type PromptView } from "./render.ts";
|
|
35
35
|
import { getCwd } from "../../../core/utils/cwd-context.ts";
|
|
36
36
|
import { saveClipboardImageAsAttachment } from "./clipboard-image.ts";
|
|
37
|
+
import { isMouseInput } from "../../mouse.ts";
|
|
37
38
|
|
|
38
39
|
export type PromptSubmission = {
|
|
39
40
|
text: string;
|
|
@@ -355,6 +356,10 @@ export function usePromptInputController({
|
|
|
355
356
|
|
|
356
357
|
useInput(
|
|
357
358
|
(input, key) => {
|
|
359
|
+
if (isMouseInput(input)) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
358
363
|
const state = getState();
|
|
359
364
|
|
|
360
365
|
if (key.return) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { parseMouseEvents } from "ink-use-mouse";
|
|
2
|
+
|
|
3
|
+
const STRIPPED_SGR_MOUSE_RE = /\[?<\d+;\d+;\d+[mM]/;
|
|
4
|
+
|
|
5
|
+
export const MOUSE_REPORTING_ENABLE =
|
|
6
|
+
"\x1b[?1003l\x1b[?1002l\x1b[?1000h\x1b[?1006h";
|
|
7
|
+
|
|
8
|
+
export const MOUSE_REPORTING_DISABLE =
|
|
9
|
+
"\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l";
|
|
10
|
+
|
|
11
|
+
export function isMouseInput(input: string): boolean {
|
|
12
|
+
return (
|
|
13
|
+
parseMouseEvents(input).length > 0 || STRIPPED_SGR_MOUSE_RE.test(input)
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { parseMouseEvents };
|
package/src/chat/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { FileToolDisplay } from "../core/state/file-tool-display.ts";
|
|
2
|
+
|
|
1
3
|
export type ChatRole = "user" | "assistant" | "tool" | "system";
|
|
2
4
|
|
|
3
5
|
export interface ChatLine {
|
|
@@ -9,6 +11,7 @@ export interface ChatLine {
|
|
|
9
11
|
toolName?: string;
|
|
10
12
|
phase?: "running" | "done";
|
|
11
13
|
resultContent?: string;
|
|
14
|
+
fileToolDisplay?: FileToolDisplay;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export type ApprovalDecision = "allow" | "reject" | "always";
|
package/src/core/agent/index.ts
CHANGED
|
@@ -32,8 +32,8 @@ function validateConfigs(configs: readonly AgentConfig[]): void {
|
|
|
32
32
|
if (!config.description.trim()) {
|
|
33
33
|
throw new Error(`Agent '${config.id}' description cannot be empty.`);
|
|
34
34
|
}
|
|
35
|
-
if (!Array.isArray(config.tools)
|
|
36
|
-
throw new Error(`Agent '${config.id}' must
|
|
35
|
+
if (!Array.isArray(config.tools)) {
|
|
36
|
+
throw new Error(`Agent '${config.id}' tools must be an array.`);
|
|
37
37
|
}
|
|
38
38
|
for (const toolName of config.tools) {
|
|
39
39
|
if (!toolName.trim()) {
|
|
@@ -43,20 +43,18 @@ function validateConfigs(configs: readonly AgentConfig[]): void {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function
|
|
46
|
+
function filterKnownTools(
|
|
47
47
|
definitions: readonly AgentDefinition[],
|
|
48
48
|
knownTools: readonly string[],
|
|
49
|
-
):
|
|
49
|
+
): AgentDefinition[] {
|
|
50
50
|
const known = new Set(knownTools);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
}
|
|
51
|
+
return definitions.map((definition) => {
|
|
52
|
+
const tools = definition.tools.filter((toolName) => known.has(toolName));
|
|
53
|
+
return {
|
|
54
|
+
...definition,
|
|
55
|
+
tools,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
function context(config: Config): Record<string, unknown> {
|
|
@@ -102,7 +100,7 @@ export function loadBuiltInAgentDefinitions(
|
|
|
102
100
|
};
|
|
103
101
|
});
|
|
104
102
|
if (options?.knownTools) {
|
|
105
|
-
|
|
103
|
+
return filterKnownTools(definitions, options.knownTools);
|
|
106
104
|
}
|
|
107
105
|
return definitions;
|
|
108
106
|
}
|
|
@@ -34,6 +34,7 @@ const HARNESS_PROMPT_FILES = [
|
|
|
34
34
|
export type SystemMode = "default" | "daemon" | "acp";
|
|
35
35
|
|
|
36
36
|
const SECTION_BREAK = "\n\n---\n\n";
|
|
37
|
+
const EXTRA_CWD_INSTRUCTIONS = "AGENTS.md";
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* Loads `prompts/static/*.md` from the package, then `instructions.md` from disk,
|
|
@@ -122,12 +123,21 @@ export class System {
|
|
|
122
123
|
return parts.join("\n\n");
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
private readCwdAgentsInstructions(): string {
|
|
127
|
+
const path = join(process.cwd(), EXTRA_CWD_INSTRUCTIONS);
|
|
128
|
+
if (!existsSync(path)) {
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
return readFileSync(path, "utf8").trim();
|
|
132
|
+
}
|
|
133
|
+
|
|
125
134
|
private readRawText(): string {
|
|
126
135
|
const instructions = existsSync(this.path)
|
|
127
136
|
? readFileSync(this.path, "utf8").trim()
|
|
128
137
|
: "";
|
|
129
138
|
const bundled = this.readBundledStaticPrompts();
|
|
130
139
|
const harness = this.readBundledHarnessPrompts();
|
|
140
|
+
const extra = this.readCwdAgentsInstructions();
|
|
131
141
|
|
|
132
142
|
const blocks: string[] = [];
|
|
133
143
|
if (bundled.length > 0) {
|
|
@@ -139,6 +149,9 @@ export class System {
|
|
|
139
149
|
if (instructions.length > 0) {
|
|
140
150
|
blocks.push(instructions);
|
|
141
151
|
}
|
|
152
|
+
if (extra.length > 0) {
|
|
153
|
+
blocks.push(extra);
|
|
154
|
+
}
|
|
142
155
|
if (blocks.length === 0) {
|
|
143
156
|
return "";
|
|
144
157
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { JSONValue } from "@strands-agents/sdk";
|
|
2
|
+
|
|
3
|
+
const FILE_TOOL_DISPLAY_STATE_KEY = "fileToolDisplay";
|
|
4
|
+
|
|
5
|
+
export type StructuredPatchHunk = {
|
|
6
|
+
oldStart: number;
|
|
7
|
+
oldLines: number;
|
|
8
|
+
newStart: number;
|
|
9
|
+
newLines: number;
|
|
10
|
+
lines: string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type FileToolDisplay = {
|
|
14
|
+
previews?: string[];
|
|
15
|
+
structuredPatch?: StructuredPatchHunk[];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type DisplayStateStore = {
|
|
19
|
+
get(key: string): JSONValue | undefined;
|
|
20
|
+
set(key: string, value: unknown): void;
|
|
21
|
+
delete(key: string): void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function readDisplayState(
|
|
25
|
+
appState: DisplayStateStore,
|
|
26
|
+
): Record<string, FileToolDisplay> {
|
|
27
|
+
const value = appState.get(FILE_TOOL_DISPLAY_STATE_KEY);
|
|
28
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
29
|
+
? (value as Record<string, FileToolDisplay>)
|
|
30
|
+
: {};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function setFileToolDisplay(
|
|
34
|
+
appState: DisplayStateStore,
|
|
35
|
+
toolUseId: string | undefined,
|
|
36
|
+
display: FileToolDisplay,
|
|
37
|
+
): void {
|
|
38
|
+
if (!toolUseId) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
appState.set(FILE_TOOL_DISPLAY_STATE_KEY, {
|
|
43
|
+
...readDisplayState(appState),
|
|
44
|
+
[toolUseId]: display,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function takeFileToolDisplay(
|
|
49
|
+
appState: DisplayStateStore,
|
|
50
|
+
toolUseId: string | null,
|
|
51
|
+
): FileToolDisplay | undefined {
|
|
52
|
+
if (!toolUseId) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const state = readDisplayState(appState);
|
|
57
|
+
const display = state[toolUseId];
|
|
58
|
+
if (!display) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { [toolUseId]: _removed, ...nextState } = state;
|
|
63
|
+
if (Object.keys(nextState).length === 0) {
|
|
64
|
+
appState.delete(FILE_TOOL_DISPLAY_STATE_KEY);
|
|
65
|
+
} else {
|
|
66
|
+
appState.set(FILE_TOOL_DISPLAY_STATE_KEY, nextState);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return display;
|
|
70
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ToolContext } from "@strands-agents/sdk";
|
|
2
|
+
|
|
3
|
+
const THINKING_STATE_KEY = "thinking.sequential";
|
|
4
|
+
|
|
5
|
+
export type ThoughtEntry = {
|
|
6
|
+
thought: string;
|
|
7
|
+
thoughtNumber: number;
|
|
8
|
+
totalThoughts: number;
|
|
9
|
+
nextThoughtNeeded: boolean;
|
|
10
|
+
isRevision: boolean;
|
|
11
|
+
revisesThought: number | null;
|
|
12
|
+
branchFromThought: number | null;
|
|
13
|
+
branchId: string | null;
|
|
14
|
+
needsMoreThoughts: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ThinkingState = {
|
|
18
|
+
history: ThoughtEntry[];
|
|
19
|
+
branches: string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function isThinkingState(value: unknown): value is ThinkingState {
|
|
23
|
+
return (
|
|
24
|
+
value !== null &&
|
|
25
|
+
typeof value === "object" &&
|
|
26
|
+
!Array.isArray(value) &&
|
|
27
|
+
"history" in value &&
|
|
28
|
+
"branches" in value &&
|
|
29
|
+
Array.isArray(value.history) &&
|
|
30
|
+
Array.isArray(value.branches)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function readThinkingState(context: ToolContext): ThinkingState {
|
|
35
|
+
const current = context.agent.appState.get(THINKING_STATE_KEY);
|
|
36
|
+
|
|
37
|
+
if (isThinkingState(current)) {
|
|
38
|
+
return current;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { history: [], branches: [] };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function writeThinkingState(
|
|
45
|
+
context: ToolContext,
|
|
46
|
+
state: ThinkingState,
|
|
47
|
+
): void {
|
|
48
|
+
context.agent.appState.set(THINKING_STATE_KEY, state);
|
|
49
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const TODO_ITEMS_STATE_KEY = "todo.items";
|
|
4
|
+
export const TODO_VISIBLE_STATE_KEY = "todo.visible";
|
|
5
|
+
|
|
6
|
+
export const TodoStatusSchema = z.enum(["pending", "in_progress", "completed"]);
|
|
7
|
+
|
|
8
|
+
export const TodoItemSchema = z.object({
|
|
9
|
+
content: z.string().trim().min(1),
|
|
10
|
+
status: TodoStatusSchema,
|
|
11
|
+
activeForm: z.string().trim().min(1),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export type TodoStatus = z.infer<typeof TodoStatusSchema>;
|
|
15
|
+
export type TodoItem = z.infer<typeof TodoItemSchema>;
|
|
16
|
+
|
|
17
|
+
type AppStateLike = {
|
|
18
|
+
get<T = unknown>(key: string): T;
|
|
19
|
+
set(key: string, value: unknown): void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type AgentLike = {
|
|
23
|
+
appState: AppStateLike;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type TodoViewState = {
|
|
27
|
+
visible: boolean;
|
|
28
|
+
todos: TodoItem[];
|
|
29
|
+
total: number;
|
|
30
|
+
pending: number;
|
|
31
|
+
ongoing: number;
|
|
32
|
+
completed: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function normalizeTodoItems(value: unknown): TodoItem[] {
|
|
36
|
+
if (!Array.isArray(value)) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
const normalized: TodoItem[] = [];
|
|
40
|
+
for (const item of value) {
|
|
41
|
+
const parsed = TodoItemSchema.safeParse(item);
|
|
42
|
+
if (parsed.success) {
|
|
43
|
+
normalized.push(parsed.data);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return normalized;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function summarizeTodos(
|
|
50
|
+
todos: TodoItem[],
|
|
51
|
+
): Omit<TodoViewState, "visible" | "todos"> {
|
|
52
|
+
const pending = todos.filter((todo) => todo.status === "pending").length;
|
|
53
|
+
const ongoing = todos.filter((todo) => todo.status === "in_progress").length;
|
|
54
|
+
const completed = todos.filter((todo) => todo.status === "completed").length;
|
|
55
|
+
return {
|
|
56
|
+
total: todos.length,
|
|
57
|
+
pending,
|
|
58
|
+
ongoing,
|
|
59
|
+
completed,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getTodoViewState(agent: AgentLike): TodoViewState {
|
|
64
|
+
const todos = normalizeTodoItems(agent.appState.get(TODO_ITEMS_STATE_KEY));
|
|
65
|
+
const rawVisible = agent.appState.get(TODO_VISIBLE_STATE_KEY);
|
|
66
|
+
const visible =
|
|
67
|
+
typeof rawVisible === "boolean" ? rawVisible : todos.length > 0;
|
|
68
|
+
const summary = summarizeTodos(todos);
|
|
69
|
+
return {
|
|
70
|
+
visible,
|
|
71
|
+
todos,
|
|
72
|
+
...summary,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function setTodoState(agent: AgentLike, todos: TodoItem[]): void {
|
|
77
|
+
agent.appState.set(TODO_ITEMS_STATE_KEY, todos);
|
|
78
|
+
agent.appState.set(TODO_VISIBLE_STATE_KEY, todos.length > 0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function clearTodoState(agent: AgentLike): void {
|
|
82
|
+
agent.appState.set(TODO_ITEMS_STATE_KEY, []);
|
|
83
|
+
agent.appState.set(TODO_VISIBLE_STATE_KEY, false);
|
|
84
|
+
}
|