hoomanjs 1.11.1 → 1.11.3
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 +1 -1
- package/src/acp/acp-agent.ts +0 -2
- package/src/acp/approvals.ts +6 -7
- package/src/acp/utils/tool-kind.ts +1 -1
- package/src/chat/app.tsx +2 -5
- package/src/chat/approvals.ts +7 -10
- package/src/chat/index.tsx +0 -3
- package/src/cli.ts +2 -6
- package/src/core/approvals/allowed-tools.ts +61 -0
- package/src/core/memory/stm/lazy-session-manager.ts +112 -14
- package/src/daemon/approvals.ts +7 -7
- package/src/daemon/index.ts +15 -5
- package/src/exec/approvals.ts +11 -10
package/package.json
CHANGED
package/src/acp/acp-agent.ts
CHANGED
|
@@ -350,7 +350,6 @@ export class AcpAgent implements AgentContract {
|
|
|
350
350
|
createAcpToolApprovalHook(
|
|
351
351
|
this.#connection,
|
|
352
352
|
sessionId,
|
|
353
|
-
config,
|
|
354
353
|
() =>
|
|
355
354
|
this.#sessions.get(sessionId)?.streamedToolCallIds ??
|
|
356
355
|
EMPTY_STREAMED_TOOL_CALL_IDS,
|
|
@@ -458,7 +457,6 @@ export class AcpAgent implements AgentContract {
|
|
|
458
457
|
createAcpToolApprovalHook(
|
|
459
458
|
this.#connection,
|
|
460
459
|
params.sessionId,
|
|
461
|
-
config,
|
|
462
460
|
() =>
|
|
463
461
|
this.#sessions.get(params.sessionId)?.streamedToolCallIds ??
|
|
464
462
|
EMPTY_STREAMED_TOOL_CALL_IDS,
|
package/src/acp/approvals.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import type { AgentSideConnection } from "@agentclientprotocol/sdk";
|
|
2
2
|
import { BeforeToolCallEvent, type HookCallback } from "@strands-agents/sdk";
|
|
3
|
-
import type { Config } from "../core/config.ts";
|
|
4
3
|
import {
|
|
5
4
|
INTERNAL_ALWAYS_ALLOWED,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "
|
|
5
|
+
allowToolForSession,
|
|
6
|
+
isToolSessionAllowed,
|
|
7
|
+
} from "../core/approvals/allowed-tools.ts";
|
|
8
|
+
import { inferToolKind, toolDisplayTitle } from "./utils/tool-kind.ts";
|
|
9
9
|
import { toolCallLocationsFromInput } from "./utils/tool-locations.ts";
|
|
10
10
|
|
|
11
11
|
export function createAcpToolApprovalHook(
|
|
12
12
|
connection: AgentSideConnection,
|
|
13
13
|
sessionId: string,
|
|
14
|
-
config: Config,
|
|
15
14
|
/** Tool calls already announced via model stream (`tool_call` pending). */
|
|
16
15
|
streamPrimedToolCallIds?: () => ReadonlySet<string>,
|
|
17
16
|
): HookCallback<BeforeToolCallEvent> {
|
|
@@ -53,7 +52,7 @@ export function createAcpToolApprovalHook(
|
|
|
53
52
|
|
|
54
53
|
if (
|
|
55
54
|
INTERNAL_ALWAYS_ALLOWED.has(name) ||
|
|
56
|
-
|
|
55
|
+
isToolSessionAllowed(event.agent, name)
|
|
57
56
|
) {
|
|
58
57
|
await connection.sessionUpdate({
|
|
59
58
|
sessionId,
|
|
@@ -122,7 +121,7 @@ export function createAcpToolApprovalHook(
|
|
|
122
121
|
return;
|
|
123
122
|
}
|
|
124
123
|
if (optionId === "allow_always") {
|
|
125
|
-
|
|
124
|
+
allowToolForSession(event.agent, name);
|
|
126
125
|
await connection.sessionUpdate({
|
|
127
126
|
sessionId,
|
|
128
127
|
update: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ToolKind } from "@agentclientprotocol/sdk";
|
|
2
2
|
import type { Tool } from "@strands-agents/sdk";
|
|
3
|
+
import { INTERNAL_ALWAYS_ALLOWED } from "../../core/approvals/allowed-tools.ts";
|
|
3
4
|
|
|
4
|
-
const INTERNAL_ALWAYS_ALLOWED = new Set(["strands_structured_output"]);
|
|
5
5
|
const KNOWN_TOOL_KINDS = new Map<string, ToolKind>([
|
|
6
6
|
["read_file", "read"],
|
|
7
7
|
["read_multiple_files", "read"],
|
package/src/chat/app.tsx
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
type Agent,
|
|
12
12
|
type AgentStreamEvent,
|
|
13
13
|
} from "@strands-agents/sdk";
|
|
14
|
-
import type { Config } from "../core/config.ts";
|
|
15
14
|
import type { Manager as McpManager } from "../core/mcp/index.ts";
|
|
16
15
|
import type { Registry } from "../core/skills/index.ts";
|
|
17
16
|
import {
|
|
@@ -26,7 +25,6 @@ import type { ApprovalRequest, ChatLine } from "./types.ts";
|
|
|
26
25
|
|
|
27
26
|
type ChatAppProps = {
|
|
28
27
|
agent: Agent;
|
|
29
|
-
config: Config;
|
|
30
28
|
sessionId: string;
|
|
31
29
|
manager: McpManager;
|
|
32
30
|
registry: Registry;
|
|
@@ -69,7 +67,6 @@ function toToolResultText(result: unknown): string {
|
|
|
69
67
|
|
|
70
68
|
export function ChatApp({
|
|
71
69
|
agent,
|
|
72
|
-
config,
|
|
73
70
|
sessionId,
|
|
74
71
|
manager,
|
|
75
72
|
registry,
|
|
@@ -143,13 +140,13 @@ export function ChatApp({
|
|
|
143
140
|
});
|
|
144
141
|
const cleanupHook = agent.addHook(
|
|
145
142
|
BeforeToolCallEvent,
|
|
146
|
-
createChatApprovalHandler(
|
|
143
|
+
createChatApprovalHandler(controller, { yolo }),
|
|
147
144
|
);
|
|
148
145
|
return () => {
|
|
149
146
|
cleanupListener();
|
|
150
147
|
cleanupHook();
|
|
151
148
|
};
|
|
152
|
-
}, [agent,
|
|
149
|
+
}, [agent, yolo]);
|
|
153
150
|
|
|
154
151
|
const appendLine = useCallback((line: ChatLine) => {
|
|
155
152
|
setLines((prev) => [...prev, line]);
|
package/src/chat/approvals.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { BeforeToolCallEvent } from "@strands-agents/sdk";
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
INTERNAL_ALWAYS_ALLOWED,
|
|
4
|
+
allowToolForSession,
|
|
5
|
+
isToolSessionAllowed,
|
|
6
|
+
} from "../core/approvals/allowed-tools.ts";
|
|
3
7
|
import type { ApprovalDecision, ApprovalRequest } from "./types.ts";
|
|
4
|
-
|
|
5
|
-
const INTERNAL_ALWAYS_ALLOWED = new Set(["strands_structured_output"]);
|
|
6
8
|
const INPUT_PREVIEW_LIMIT = 256;
|
|
7
9
|
|
|
8
10
|
function previewInput(input: unknown): string {
|
|
@@ -67,7 +69,6 @@ export class ChatApprovalController {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
export function createChatApprovalHandler(
|
|
70
|
-
config: Config,
|
|
71
72
|
controller: ChatApprovalController,
|
|
72
73
|
options?: { yolo?: boolean },
|
|
73
74
|
): (event: BeforeToolCallEvent) => Promise<void> {
|
|
@@ -78,7 +79,7 @@ export function createChatApprovalHandler(
|
|
|
78
79
|
}
|
|
79
80
|
if (
|
|
80
81
|
INTERNAL_ALWAYS_ALLOWED.has(toolName) ||
|
|
81
|
-
|
|
82
|
+
isToolSessionAllowed(event.agent, toolName)
|
|
82
83
|
) {
|
|
83
84
|
return;
|
|
84
85
|
}
|
|
@@ -88,11 +89,7 @@ export function createChatApprovalHandler(
|
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
90
91
|
if (decision === "always") {
|
|
91
|
-
|
|
92
|
-
config.update({
|
|
93
|
-
tools: { allowed: [...config.tools.allowed, toolName] },
|
|
94
|
-
});
|
|
95
|
-
}
|
|
92
|
+
allowToolForSession(event.agent, toolName);
|
|
96
93
|
return;
|
|
97
94
|
}
|
|
98
95
|
event.cancel = `Tool "${toolName}" was rejected by the user.`;
|
package/src/chat/index.tsx
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { render } from "ink";
|
|
3
3
|
import type { Agent } from "@strands-agents/sdk";
|
|
4
|
-
import type { Config } from "../core/config.ts";
|
|
5
4
|
import type { Manager as McpManager } from "../core/mcp/index.ts";
|
|
6
5
|
import type { Registry } from "../core/skills/index.ts";
|
|
7
6
|
import { ChatApp } from "./app.tsx";
|
|
8
7
|
|
|
9
8
|
type LaunchChatOptions = {
|
|
10
9
|
agent: Agent;
|
|
11
|
-
config: Config;
|
|
12
10
|
manager: McpManager;
|
|
13
11
|
registry: Registry;
|
|
14
12
|
sessionId: string;
|
|
@@ -21,7 +19,6 @@ export async function chat(options: LaunchChatOptions): Promise<void> {
|
|
|
21
19
|
const { waitUntilExit, unmount } = render(
|
|
22
20
|
<ChatApp
|
|
23
21
|
agent={options.agent}
|
|
24
|
-
config={options.config}
|
|
25
22
|
manager={options.manager}
|
|
26
23
|
registry={options.registry}
|
|
27
24
|
sessionId={options.sessionId}
|
package/src/cli.ts
CHANGED
|
@@ -53,13 +53,12 @@ program
|
|
|
53
53
|
async (prompt: string, options: { session?: string; yolo?: boolean }) => {
|
|
54
54
|
const sessionId = options.session?.trim() || crypto.randomUUID();
|
|
55
55
|
const {
|
|
56
|
-
config,
|
|
57
56
|
agent,
|
|
58
57
|
mcp: { manager },
|
|
59
58
|
} = await bootstrap({ sessionId }, true);
|
|
60
59
|
agent.addHook(
|
|
61
60
|
BeforeToolCallEvent,
|
|
62
|
-
createToolApprovalHandler(
|
|
61
|
+
createToolApprovalHandler({ yolo: Boolean(options.yolo) }),
|
|
63
62
|
);
|
|
64
63
|
try {
|
|
65
64
|
await agent.invoke(prompt);
|
|
@@ -84,7 +83,6 @@ program
|
|
|
84
83
|
) => {
|
|
85
84
|
const sessionId = options.session?.trim() || crypto.randomUUID();
|
|
86
85
|
const {
|
|
87
|
-
config,
|
|
88
86
|
agent,
|
|
89
87
|
mcp: { manager },
|
|
90
88
|
registry,
|
|
@@ -93,7 +91,6 @@ program
|
|
|
93
91
|
try {
|
|
94
92
|
await chat({
|
|
95
93
|
agent,
|
|
96
|
-
config,
|
|
97
94
|
manager,
|
|
98
95
|
registry,
|
|
99
96
|
sessionId,
|
|
@@ -129,7 +126,6 @@ program
|
|
|
129
126
|
}) => {
|
|
130
127
|
const session = options.session?.trim();
|
|
131
128
|
const {
|
|
132
|
-
config,
|
|
133
129
|
agent,
|
|
134
130
|
mcp: { manager },
|
|
135
131
|
} = await bootstrap(
|
|
@@ -141,7 +137,7 @@ program
|
|
|
141
137
|
);
|
|
142
138
|
agent.addHook(
|
|
143
139
|
BeforeToolCallEvent,
|
|
144
|
-
createDaemonApprovalHandler(
|
|
140
|
+
createDaemonApprovalHandler(manager, agent, {
|
|
145
141
|
yolo: Boolean(options.yolo),
|
|
146
142
|
}),
|
|
147
143
|
);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
type AppStateLike = {
|
|
2
|
+
get<T = unknown>(key: string): T;
|
|
3
|
+
set(key: string, value: unknown): void;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
type AgentLike = {
|
|
7
|
+
appState: AppStateLike;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const SESSION_ALLOWED_TOOLS_KEY = "allowedTools";
|
|
11
|
+
|
|
12
|
+
export const INTERNAL_ALWAYS_ALLOWED = new Set(["strands_structured_output"]);
|
|
13
|
+
|
|
14
|
+
function normalizeAllowedTools(value: unknown): string[] {
|
|
15
|
+
if (!Array.isArray(value)) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const unique = new Set<string>();
|
|
20
|
+
for (const entry of value) {
|
|
21
|
+
if (typeof entry !== "string") {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const normalized = entry.trim();
|
|
25
|
+
if (!normalized) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
unique.add(normalized);
|
|
29
|
+
}
|
|
30
|
+
return [...unique];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getSessionAllowedTools(agent: AgentLike): string[] {
|
|
34
|
+
const current = normalizeAllowedTools(
|
|
35
|
+
agent.appState.get(SESSION_ALLOWED_TOOLS_KEY),
|
|
36
|
+
);
|
|
37
|
+
const raw = agent.appState.get(SESSION_ALLOWED_TOOLS_KEY);
|
|
38
|
+
if (!Array.isArray(raw) || current.length !== raw.length) {
|
|
39
|
+
agent.appState.set(SESSION_ALLOWED_TOOLS_KEY, current);
|
|
40
|
+
}
|
|
41
|
+
return current;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isToolSessionAllowed(
|
|
45
|
+
agent: AgentLike,
|
|
46
|
+
toolName: string,
|
|
47
|
+
): boolean {
|
|
48
|
+
return getSessionAllowedTools(agent).includes(toolName);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function allowToolForSession(agent: AgentLike, toolName: string): void {
|
|
52
|
+
const normalized = toolName.trim();
|
|
53
|
+
if (!normalized) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const allowed = getSessionAllowedTools(agent);
|
|
57
|
+
if (allowed.includes(normalized)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
agent.appState.set(SESSION_ALLOWED_TOOLS_KEY, [...allowed, normalized]);
|
|
61
|
+
}
|
|
@@ -2,21 +2,26 @@ import {
|
|
|
2
2
|
AfterInvocationEvent,
|
|
3
3
|
BeforeInvocationEvent,
|
|
4
4
|
Message,
|
|
5
|
+
contentBlockFromData,
|
|
5
6
|
type LocalAgent,
|
|
6
7
|
type MessageData,
|
|
7
8
|
type Plugin,
|
|
9
|
+
type SystemPrompt,
|
|
10
|
+
type SystemPromptData,
|
|
8
11
|
type Snapshot,
|
|
9
12
|
type SnapshotLocation,
|
|
10
13
|
type SnapshotStorage,
|
|
11
14
|
} from "@strands-agents/sdk";
|
|
15
|
+
import type { JSONValue } from "@strands-agents/sdk";
|
|
12
16
|
|
|
13
17
|
const DEFAULT_SESSION_ID = "default-session";
|
|
14
18
|
const DEFAULT_APP_STATE_KEY = "sessionId";
|
|
15
19
|
const DEFAULT_SCOPE_ID = "agent";
|
|
16
|
-
const
|
|
20
|
+
const SNAPSHOT_SCHEMA_VERSION = "1.0";
|
|
17
21
|
// `FileStorage` (and any backend that follows its convention) validates ids
|
|
18
22
|
// against `[a-z0-9_-]+`, so coerce anything else (e.g. `919599960600@c.us`).
|
|
19
23
|
const UNSAFE_CHARS = /[^a-z0-9_-]+/g;
|
|
24
|
+
const RUNTIME_OWNED_KEYS = ["userId", "origin"] as const;
|
|
20
25
|
|
|
21
26
|
export type LazySessionManagerConfig = {
|
|
22
27
|
/** Pluggable snapshot backend (e.g. `FileStorage`). */
|
|
@@ -86,27 +91,21 @@ export class LazySessionManager implements Plugin {
|
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
private async restore(agent: LocalAgent): Promise<void> {
|
|
94
|
+
const runtimeState = this.captureRuntimeState(agent);
|
|
89
95
|
const snapshot = await this.storage.loadSnapshot({
|
|
90
96
|
location: this.location(agent),
|
|
91
97
|
});
|
|
92
98
|
agent.messages.length = 0;
|
|
93
|
-
if (!snapshot)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
for (const md of raw as unknown as MessageData[]) {
|
|
97
|
-
agent.messages.push(Message.fromJSON(md));
|
|
99
|
+
if (!snapshot) {
|
|
100
|
+
this.restoreRuntimeState(agent, runtimeState);
|
|
101
|
+
return;
|
|
98
102
|
}
|
|
103
|
+
loadSnapshot(agent, snapshot);
|
|
104
|
+
this.restoreRuntimeState(agent, runtimeState);
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
private async save(agent: LocalAgent): Promise<void> {
|
|
102
|
-
const
|
|
103
|
-
const snapshot: Snapshot = {
|
|
104
|
-
scope: "agent",
|
|
105
|
-
schemaVersion: SCHEMA_VERSION,
|
|
106
|
-
createdAt: new Date().toISOString(),
|
|
107
|
-
data: { messages: messages as unknown as Snapshot["data"]["messages"] },
|
|
108
|
-
appData: {},
|
|
109
|
-
};
|
|
108
|
+
const snapshot = takeSnapshot(agent);
|
|
110
109
|
await this.storage.saveSnapshot({
|
|
111
110
|
location: this.location(agent),
|
|
112
111
|
snapshotId: "latest",
|
|
@@ -114,9 +113,108 @@ export class LazySessionManager implements Plugin {
|
|
|
114
113
|
snapshot,
|
|
115
114
|
});
|
|
116
115
|
}
|
|
116
|
+
|
|
117
|
+
private captureRuntimeState(agent: LocalAgent): Map<string, unknown> {
|
|
118
|
+
const state = new Map<string, unknown>();
|
|
119
|
+
const keys = [this.appStateKey, ...RUNTIME_OWNED_KEYS];
|
|
120
|
+
for (const key of keys) {
|
|
121
|
+
state.set(key, agent.appState.get(key));
|
|
122
|
+
}
|
|
123
|
+
return state;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private restoreRuntimeState(
|
|
127
|
+
agent: LocalAgent,
|
|
128
|
+
runtimeState: Map<string, unknown>,
|
|
129
|
+
): void {
|
|
130
|
+
for (const [key, value] of runtimeState.entries()) {
|
|
131
|
+
if (value === undefined) {
|
|
132
|
+
agent.appState.delete(key);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
agent.appState.set(key, value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
117
138
|
}
|
|
118
139
|
|
|
119
140
|
function sanitize(value: string): string {
|
|
120
141
|
const trimmed = value.trim().toLowerCase().replace(UNSAFE_CHARS, "_");
|
|
121
142
|
return trimmed.length > 0 ? trimmed : DEFAULT_SESSION_ID;
|
|
122
143
|
}
|
|
144
|
+
|
|
145
|
+
function takeSnapshot(agent: LocalAgent): Snapshot {
|
|
146
|
+
const data: Record<string, JSONValue> = {
|
|
147
|
+
messages: agent.messages.map((message) =>
|
|
148
|
+
message.toJSON(),
|
|
149
|
+
) as unknown as JSONValue,
|
|
150
|
+
state: agent.appState.getAll() as JSONValue,
|
|
151
|
+
systemPrompt:
|
|
152
|
+
agent.systemPrompt !== undefined
|
|
153
|
+
? (systemPromptToData(agent.systemPrompt) as JSONValue)
|
|
154
|
+
: null,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
scope: "agent",
|
|
159
|
+
schemaVersion: SNAPSHOT_SCHEMA_VERSION,
|
|
160
|
+
createdAt: new Date().toISOString(),
|
|
161
|
+
data,
|
|
162
|
+
appData: {},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function loadSnapshot(agent: LocalAgent, snapshot: Snapshot): void {
|
|
167
|
+
if (snapshot.scope !== "agent") {
|
|
168
|
+
throw new Error(`Expected snapshot scope 'agent', got '${snapshot.scope}'`);
|
|
169
|
+
}
|
|
170
|
+
if (snapshot.schemaVersion !== SNAPSHOT_SCHEMA_VERSION) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Unsupported snapshot schema version: ${snapshot.schemaVersion}. Current version: ${SNAPSHOT_SCHEMA_VERSION}`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if ("messages" in snapshot.data) {
|
|
177
|
+
const messages = snapshot.data.messages;
|
|
178
|
+
agent.messages.length = 0;
|
|
179
|
+
for (const msgData of messages as unknown as MessageData[]) {
|
|
180
|
+
agent.messages.push(Message.fromJSON(msgData));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if ("state" in snapshot.data) {
|
|
185
|
+
const state = snapshot.data.state;
|
|
186
|
+
const nextState =
|
|
187
|
+
state && typeof state === "object" && !Array.isArray(state)
|
|
188
|
+
? (state as Record<string, JSONValue>)
|
|
189
|
+
: {};
|
|
190
|
+
agent.appState.clear();
|
|
191
|
+
for (const [key, value] of Object.entries(nextState)) {
|
|
192
|
+
agent.appState.set(key, value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if ("systemPrompt" in snapshot.data) {
|
|
197
|
+
const systemPrompt = snapshot.data.systemPrompt;
|
|
198
|
+
if (systemPrompt !== null) {
|
|
199
|
+
agent.systemPrompt = systemPromptFromData(
|
|
200
|
+
systemPrompt as SystemPromptData,
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
delete agent.systemPrompt;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function systemPromptToData(prompt: SystemPrompt): SystemPromptData {
|
|
209
|
+
if (typeof prompt === "string") {
|
|
210
|
+
return prompt;
|
|
211
|
+
}
|
|
212
|
+
return prompt.map((block) => block.toJSON()) as SystemPromptData;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function systemPromptFromData(data: SystemPromptData): SystemPrompt {
|
|
216
|
+
if (typeof data === "string") {
|
|
217
|
+
return data;
|
|
218
|
+
}
|
|
219
|
+
return data.map((block) => contentBlockFromData(block)) as SystemPrompt;
|
|
220
|
+
}
|
package/src/daemon/approvals.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Agent, BeforeToolCallEvent } from "@strands-agents/sdk";
|
|
2
|
-
import type { Config } from "../core/config.ts";
|
|
3
2
|
import type { Manager as McpManager } from "../core/mcp/index.ts";
|
|
4
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
INTERNAL_ALWAYS_ALLOWED,
|
|
5
|
+
allowToolForSession,
|
|
6
|
+
isToolSessionAllowed,
|
|
7
|
+
} from "../core/approvals/allowed-tools.ts";
|
|
5
8
|
|
|
6
9
|
const TOOL_DESCRIPTION_PREVIEW_LIMIT = 50;
|
|
7
10
|
const TOOL_ARGS_PREVIEW_LIMIT = 50;
|
|
@@ -62,7 +65,6 @@ function readOrigin(agent: Agent): ChannelOrigin | null {
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
export function createDaemonApprovalHandler(
|
|
65
|
-
config: Config,
|
|
66
68
|
manager: McpManager,
|
|
67
69
|
agent: Agent,
|
|
68
70
|
options?: { yolo?: boolean },
|
|
@@ -74,7 +76,7 @@ export function createDaemonApprovalHandler(
|
|
|
74
76
|
}
|
|
75
77
|
if (
|
|
76
78
|
INTERNAL_ALWAYS_ALLOWED.has(name) ||
|
|
77
|
-
|
|
79
|
+
isToolSessionAllowed(event.agent, name)
|
|
78
80
|
) {
|
|
79
81
|
return;
|
|
80
82
|
}
|
|
@@ -116,9 +118,7 @@ export function createDaemonApprovalHandler(
|
|
|
116
118
|
return;
|
|
117
119
|
}
|
|
118
120
|
if (behavior === "allow_always") {
|
|
119
|
-
|
|
120
|
-
config.update({ tools: { allowed: [...config.tools.allowed, name] } });
|
|
121
|
-
}
|
|
121
|
+
allowToolForSession(event.agent, name);
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
|
package/src/daemon/index.ts
CHANGED
|
@@ -64,16 +64,26 @@ export async function main(options: RunDaemonOptions): Promise<void> {
|
|
|
64
64
|
|
|
65
65
|
options.agent.appState.set("userId", user);
|
|
66
66
|
options.agent.appState.set("sessionId", session);
|
|
67
|
-
|
|
67
|
+
const origin = {
|
|
68
68
|
server: message.meta.server,
|
|
69
69
|
channel: message.meta.channel,
|
|
70
|
-
source: message.meta.source,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
...(message.meta.source ? { source: message.meta.source } : {}),
|
|
71
|
+
...(message.meta.identity.user
|
|
72
|
+
? { user: message.meta.identity.user }
|
|
73
|
+
: {}),
|
|
74
|
+
...(message.meta.identity.session
|
|
75
|
+
? { session: message.meta.identity.session }
|
|
76
|
+
: {}),
|
|
77
|
+
...(message.meta.identity.thread
|
|
78
|
+
? { thread: message.meta.identity.thread }
|
|
79
|
+
: {}),
|
|
80
|
+
};
|
|
81
|
+
options.agent.appState.set("origin", {
|
|
82
|
+
...origin,
|
|
74
83
|
});
|
|
75
84
|
|
|
76
85
|
try {
|
|
86
|
+
debug(`invoking agent → ${tag} session=${session} user=${user}`);
|
|
77
87
|
await options.agent.invoke(message.prompt);
|
|
78
88
|
debug(`completed → ${tag} session=${session} user=${user}`);
|
|
79
89
|
} catch (error) {
|
package/src/exec/approvals.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
2
|
import { stdin, stdout } from "node:process";
|
|
3
3
|
import { BeforeToolCallEvent } from "@strands-agents/sdk";
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import {
|
|
5
|
+
INTERNAL_ALWAYS_ALLOWED,
|
|
6
|
+
allowToolForSession,
|
|
7
|
+
isToolSessionAllowed,
|
|
8
|
+
} from "../core/approvals/allowed-tools.ts";
|
|
7
9
|
const INPUT_PREVIEW_LIMIT = 1_024;
|
|
8
10
|
|
|
9
11
|
type ApprovalDecision = "allow" | "reject" | "always";
|
|
@@ -57,10 +59,9 @@ async function promptForApproval(
|
|
|
57
59
|
|
|
58
60
|
type BeforeToolCallEventHandler = (event: BeforeToolCallEvent) => Promise<void>;
|
|
59
61
|
|
|
60
|
-
export function createToolApprovalHandler(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
): BeforeToolCallEventHandler {
|
|
62
|
+
export function createToolApprovalHandler(options?: {
|
|
63
|
+
yolo?: boolean;
|
|
64
|
+
}): BeforeToolCallEventHandler {
|
|
64
65
|
return async function onBeforeToolCallEvent(event: BeforeToolCallEvent) {
|
|
65
66
|
const name = event.toolUse.name;
|
|
66
67
|
if (options?.yolo) {
|
|
@@ -68,12 +69,12 @@ export function createToolApprovalHandler(
|
|
|
68
69
|
}
|
|
69
70
|
if (
|
|
70
71
|
INTERNAL_ALWAYS_ALLOWED.has(name) ||
|
|
71
|
-
|
|
72
|
+
isToolSessionAllowed(event.agent, name)
|
|
72
73
|
) {
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
75
76
|
if (!canPromptForApproval()) {
|
|
76
|
-
event.cancel = `Tool "${name}" requires approval, but no interactive terminal is available
|
|
77
|
+
event.cancel = `Tool "${name}" requires approval, but no interactive terminal is available.`;
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
79
80
|
const decision = await promptForApproval(event);
|
|
@@ -81,7 +82,7 @@ export function createToolApprovalHandler(
|
|
|
81
82
|
return;
|
|
82
83
|
}
|
|
83
84
|
if (decision === "always") {
|
|
84
|
-
|
|
85
|
+
allowToolForSession(event.agent, name);
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
87
88
|
event.cancel = `Tool "${name}" was rejected by the user.`;
|