toolcraft 0.0.2 → 0.0.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/README.md +458 -58
- package/dist/cli.compile-check.js +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +768 -40
- package/dist/human-in-loop/approval-tasks.d.ts +31 -0
- package/dist/human-in-loop/approval-tasks.js +201 -0
- package/dist/human-in-loop/approvals-commands.d.ts +11 -0
- package/dist/human-in-loop/approvals-commands.js +191 -0
- package/dist/human-in-loop/config.d.ts +11 -0
- package/dist/human-in-loop/config.js +21 -0
- package/dist/human-in-loop/default-provider.d.ts +2 -0
- package/dist/human-in-loop/default-provider.js +26 -0
- package/dist/human-in-loop/gate.d.ts +4 -0
- package/dist/human-in-loop/gate.js +57 -0
- package/dist/human-in-loop/index.d.ts +7 -0
- package/dist/human-in-loop/index.js +4 -0
- package/dist/human-in-loop/runner.d.ts +3 -0
- package/dist/human-in-loop/runner.js +196 -0
- package/dist/human-in-loop/spawn.d.ts +3 -0
- package/dist/human-in-loop/spawn.js +16 -0
- package/dist/human-in-loop/state-machine.d.ts +4 -0
- package/dist/human-in-loop/state-machine.js +10 -0
- package/dist/human-in-loop/types.d.ts +41 -0
- package/dist/human-in-loop/types.js +13 -0
- package/dist/index.compile-check.js +24 -0
- package/dist/index.d.ts +32 -13
- package/dist/index.js +82 -17
- package/dist/json-schema-converter.d.ts +21 -0
- package/dist/json-schema-converter.js +432 -0
- package/dist/mcp-proxy.d.ts +8 -0
- package/dist/mcp-proxy.js +383 -0
- package/dist/mcp.compile-check.js +1 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +103 -11
- package/dist/sdk.compile-check.js +77 -0
- package/dist/sdk.d.ts +14 -5
- package/dist/sdk.js +57 -6
- package/dist/user-error.d.ts +3 -0
- package/dist/user-error.js +6 -0
- package/node_modules/@poe-code/agent-human-in-loop/README.md +42 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/index.d.ts +5 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/index.js +3 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.d.ts +2 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.js +11 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.d.ts +4 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +40 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.d.ts +6 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +33 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.d.ts +4 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.js +4 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/types.d.ts +14 -0
- package/node_modules/@poe-code/agent-human-in-loop/dist/types.js +1 -0
- package/node_modules/@poe-code/agent-human-in-loop/package.json +25 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +6 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +175 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/configs.d.ts +22 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +74 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/index.d.ts +3 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/index.js +2 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +31 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +87 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/types.d.ts +25 -0
- package/node_modules/@poe-code/agent-mcp-config/dist/types.js +1 -0
- package/node_modules/@poe-code/agent-mcp-config/package.json +25 -0
- package/node_modules/@poe-code/task-list/README.md +114 -0
- package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +466 -0
- package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +8 -0
- package/node_modules/@poe-code/task-list/dist/backends/utils.js +58 -0
- package/node_modules/@poe-code/task-list/dist/backends/yaml-file.d.ts +2 -0
- package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +444 -0
- package/node_modules/@poe-code/task-list/dist/index.d.ts +4 -0
- package/node_modules/@poe-code/task-list/dist/index.js +4 -0
- package/node_modules/@poe-code/task-list/dist/open.d.ts +3 -0
- package/node_modules/@poe-code/task-list/dist/open.js +34 -0
- package/node_modules/@poe-code/task-list/dist/schema/store.schema.json +32 -0
- package/node_modules/@poe-code/task-list/dist/schema/task.schema.json +33 -0
- package/node_modules/@poe-code/task-list/dist/state-machine.d.ts +16 -0
- package/node_modules/@poe-code/task-list/dist/state-machine.js +67 -0
- package/node_modules/@poe-code/task-list/dist/state.d.ts +29 -0
- package/node_modules/@poe-code/task-list/dist/state.js +61 -0
- package/node_modules/@poe-code/task-list/dist/types.d.ts +116 -0
- package/node_modules/@poe-code/task-list/dist/types.js +37 -0
- package/node_modules/@poe-code/task-list/package.json +26 -0
- package/package.json +22 -7
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { OpenTaskListOptions, TaskList, Tasks } from "@poe-code/task-list";
|
|
2
|
+
import type { HumanInLoopPending, HumanInLoopRuntimeOptions } from "./types.js";
|
|
3
|
+
export interface ApprovalPayload {
|
|
4
|
+
approvalId?: string;
|
|
5
|
+
commandPath: string;
|
|
6
|
+
params: Record<string, unknown>;
|
|
7
|
+
message: string;
|
|
8
|
+
declineInputPrompt?: string | null;
|
|
9
|
+
enqueuedAt?: string;
|
|
10
|
+
pid?: number | null;
|
|
11
|
+
result?: unknown;
|
|
12
|
+
error?: unknown;
|
|
13
|
+
}
|
|
14
|
+
export declare function ensureApprovalList(runtimeOptions: HumanInLoopRuntimeOptions | undefined, deps?: {
|
|
15
|
+
openTaskList?: (options: OpenTaskListOptions) => Promise<TaskList>;
|
|
16
|
+
}): Promise<{
|
|
17
|
+
taskList: TaskList;
|
|
18
|
+
listName: string;
|
|
19
|
+
tasks: Tasks;
|
|
20
|
+
}>;
|
|
21
|
+
export declare function enqueueApproval(ctx: {
|
|
22
|
+
tasks: Tasks;
|
|
23
|
+
payload: ApprovalPayload;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
approvalId: string;
|
|
26
|
+
pending: HumanInLoopPending;
|
|
27
|
+
}>;
|
|
28
|
+
export declare function loadApproval(ctx: {
|
|
29
|
+
tasks: Tasks;
|
|
30
|
+
approvalId: string;
|
|
31
|
+
}): Promise<ApprovalPayload | undefined>;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { TaskAlreadyExistsError, TaskNotFoundError, openTaskList } from "@poe-code/task-list";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { UserError } from "../user-error.js";
|
|
4
|
+
import { approvalStateMachine } from "./state-machine.js";
|
|
5
|
+
const DEFAULT_LIST_NAME = "approvals";
|
|
6
|
+
const openedTaskListsByRuntime = new WeakMap();
|
|
7
|
+
const validatedListsByRuntime = new WeakMap();
|
|
8
|
+
export async function ensureApprovalList(runtimeOptions, deps = {}) {
|
|
9
|
+
if (runtimeOptions?.taskList === undefined) {
|
|
10
|
+
throw new UserError("humanInLoop.taskList required for async-mode commands");
|
|
11
|
+
}
|
|
12
|
+
const listName = runtimeOptions.listName ?? DEFAULT_LIST_NAME;
|
|
13
|
+
const taskList = await resolveTaskList(runtimeOptions, runtimeOptions.taskList, deps.openTaskList ?? openTaskList);
|
|
14
|
+
const tasks = taskList.list(listName);
|
|
15
|
+
if (!isListValidated(runtimeOptions, listName)) {
|
|
16
|
+
if (!isApprovalStateMachine(tasks.stateMachine)) {
|
|
17
|
+
throw new UserError("approvals task-list configured with a different state machine; pass approvalStateMachine when opening the list");
|
|
18
|
+
}
|
|
19
|
+
cacheValidatedList(runtimeOptions, listName);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
taskList,
|
|
23
|
+
listName,
|
|
24
|
+
tasks,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export async function enqueueApproval(ctx) {
|
|
28
|
+
const enqueuedAt = new Date().toISOString();
|
|
29
|
+
const approval = createApprovalRecord(ctx.payload, enqueuedAt);
|
|
30
|
+
try {
|
|
31
|
+
await createApprovalTask(ctx.tasks, approval);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (!(error instanceof TaskAlreadyExistsError)) {
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
const retryApproval = createApprovalRecord(ctx.payload, enqueuedAt);
|
|
38
|
+
await createApprovalTask(ctx.tasks, retryApproval);
|
|
39
|
+
return retryApproval;
|
|
40
|
+
}
|
|
41
|
+
return approval;
|
|
42
|
+
}
|
|
43
|
+
export async function loadApproval(ctx) {
|
|
44
|
+
try {
|
|
45
|
+
const task = await ctx.tasks.get(ctx.approvalId);
|
|
46
|
+
return approvalPayloadFromTask(task);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof TaskNotFoundError) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function resolveTaskList(runtimeOptions, taskList, openTaskListFn) {
|
|
56
|
+
if (!isTaskListConfig(taskList)) {
|
|
57
|
+
return taskList;
|
|
58
|
+
}
|
|
59
|
+
const cachedTaskList = openedTaskListsByRuntime.get(runtimeOptions);
|
|
60
|
+
if (cachedTaskList !== undefined) {
|
|
61
|
+
return cachedTaskList;
|
|
62
|
+
}
|
|
63
|
+
const openedTaskList = openTaskListFn({
|
|
64
|
+
create: true,
|
|
65
|
+
type: taskList.format,
|
|
66
|
+
path: taskList.dir,
|
|
67
|
+
stateMachine: approvalStateMachine,
|
|
68
|
+
});
|
|
69
|
+
openedTaskListsByRuntime.set(runtimeOptions, openedTaskList);
|
|
70
|
+
return openedTaskList;
|
|
71
|
+
}
|
|
72
|
+
function cacheValidatedList(runtimeOptions, listName) {
|
|
73
|
+
const validatedLists = validatedListsByRuntime.get(runtimeOptions);
|
|
74
|
+
if (validatedLists === undefined) {
|
|
75
|
+
validatedListsByRuntime.set(runtimeOptions, new Set([listName]));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
validatedLists.add(listName);
|
|
79
|
+
}
|
|
80
|
+
function isListValidated(runtimeOptions, listName) {
|
|
81
|
+
return validatedListsByRuntime.get(runtimeOptions)?.has(listName) ?? false;
|
|
82
|
+
}
|
|
83
|
+
function createApprovalRecord(payload, enqueuedAt) {
|
|
84
|
+
const approvalId = `${enqueuedAt.slice(0, 19).replaceAll(":", "-")}-${randomBytes(3).toString("hex")}`;
|
|
85
|
+
return {
|
|
86
|
+
approvalId,
|
|
87
|
+
name: `${payload.commandPath} (${enqueuedAt})`,
|
|
88
|
+
metadata: {
|
|
89
|
+
schemaVersion: 1,
|
|
90
|
+
approvalId,
|
|
91
|
+
commandPath: payload.commandPath,
|
|
92
|
+
params: payload.params,
|
|
93
|
+
message: payload.message,
|
|
94
|
+
declineInputPrompt: payload.declineInputPrompt ?? null,
|
|
95
|
+
enqueuedAt,
|
|
96
|
+
pid: null,
|
|
97
|
+
result: null,
|
|
98
|
+
error: null,
|
|
99
|
+
},
|
|
100
|
+
pending: {
|
|
101
|
+
status: "pending-approval",
|
|
102
|
+
approvalId,
|
|
103
|
+
message: payload.message,
|
|
104
|
+
enqueuedAt,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function createApprovalTask(tasks, approval) {
|
|
109
|
+
await tasks.create({
|
|
110
|
+
id: approval.approvalId,
|
|
111
|
+
name: approval.name,
|
|
112
|
+
metadata: approval.metadata,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function isApprovalStateMachine(stateMachine) {
|
|
116
|
+
if (stateMachine === approvalStateMachine) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return isDeepEqualStateMachine(stateMachine, approvalStateMachine);
|
|
120
|
+
}
|
|
121
|
+
function isTaskListConfig(taskList) {
|
|
122
|
+
return taskList !== undefined && "dir" in taskList;
|
|
123
|
+
}
|
|
124
|
+
function isDeepEqualStateMachine(left, right) {
|
|
125
|
+
if (!areEqualStrings(left.states, right.states)) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const leftEventNames = Object.keys(left.events);
|
|
129
|
+
const rightEventNames = Object.keys(right.events);
|
|
130
|
+
if (!areEqualStrings(leftEventNames, rightEventNames)) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
for (const eventName of leftEventNames) {
|
|
134
|
+
const leftEvent = left.events[eventName];
|
|
135
|
+
const rightEvent = right.events[eventName];
|
|
136
|
+
if (leftEvent === undefined || rightEvent === undefined) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (leftEvent.to !== rightEvent.to) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (!areEqualEventFrom(leftEvent.from, rightEvent.from)) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
function areEqualEventFrom(left, right) {
|
|
149
|
+
if (left === "*" || right === "*") {
|
|
150
|
+
return left === right;
|
|
151
|
+
}
|
|
152
|
+
return areEqualStrings(left, right);
|
|
153
|
+
}
|
|
154
|
+
function areEqualStrings(left, right) {
|
|
155
|
+
if (left.length !== right.length) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
159
|
+
if (left[index] !== right[index]) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
function approvalPayloadFromTask(task) {
|
|
166
|
+
const metadata = task.metadata;
|
|
167
|
+
if (typeof metadata !== "object" || metadata === null) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
if (metadata.schemaVersion !== 1) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
if (typeof metadata.approvalId !== "string") {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
if (typeof metadata.commandPath !== "string") {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
if (typeof metadata.message !== "string") {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
if (typeof metadata.enqueuedAt !== "string") {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
if (typeof metadata.params !== "object" || metadata.params === null) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
approvalId: metadata.approvalId,
|
|
190
|
+
commandPath: metadata.commandPath,
|
|
191
|
+
params: metadata.params,
|
|
192
|
+
message: metadata.message,
|
|
193
|
+
declineInputPrompt: typeof metadata.declineInputPrompt === "string" || metadata.declineInputPrompt === null
|
|
194
|
+
? metadata.declineInputPrompt
|
|
195
|
+
: undefined,
|
|
196
|
+
enqueuedAt: metadata.enqueuedAt,
|
|
197
|
+
pid: typeof metadata.pid === "number" || metadata.pid === null ? metadata.pid : undefined,
|
|
198
|
+
result: metadata.result,
|
|
199
|
+
error: metadata.error,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CommandNode, Group } from "../index.js";
|
|
2
|
+
import type { HumanInLoopRuntimeOptions } from "./types.js";
|
|
3
|
+
interface ApprovalBuiltInServices {
|
|
4
|
+
runtimeOptions: HumanInLoopRuntimeOptions;
|
|
5
|
+
root: CommandNode<any>;
|
|
6
|
+
}
|
|
7
|
+
export declare const approvalsGroup: Group<ApprovalBuiltInServices> & {
|
|
8
|
+
readonly __agentKitGroupTypeInfo: import("../index.js").GroupTypeInfo<ApprovalBuiltInServices, string, readonly CommandNode<ApprovalBuiltInServices>[], undefined, undefined>;
|
|
9
|
+
};
|
|
10
|
+
export declare function mergeApprovalsGroup<TServices extends object>(root: Group<TServices>): Group<TServices>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { S } from "toolcraft-schema";
|
|
2
|
+
import { UserError, defineCommand, defineGroup } from "../index.js";
|
|
3
|
+
import { ensureApprovalList } from "./approval-tasks.js";
|
|
4
|
+
import { runApproval } from "./runner.js";
|
|
5
|
+
const approvalsGroupSymbol = Symbol("toolcraft.humanInLoop.approvalsBuiltIn");
|
|
6
|
+
const listScope = ["cli", "mcp", "sdk"];
|
|
7
|
+
const runScope = ["cli"];
|
|
8
|
+
const listParams = S.Object({
|
|
9
|
+
state: S.Optional(S.String()),
|
|
10
|
+
});
|
|
11
|
+
const showParams = S.Object({
|
|
12
|
+
approvalId: S.String(),
|
|
13
|
+
});
|
|
14
|
+
export const approvalsGroup = markApprovalsBuiltIn(defineGroup({
|
|
15
|
+
name: "approvals",
|
|
16
|
+
description: "Inspect and execute queued approvals.",
|
|
17
|
+
children: [
|
|
18
|
+
defineCommand({
|
|
19
|
+
name: "list",
|
|
20
|
+
description: "List queued approvals.",
|
|
21
|
+
scope: listScope,
|
|
22
|
+
params: listParams,
|
|
23
|
+
handler: async ({ params, runtimeOptions }) => {
|
|
24
|
+
const { tasks } = await ensureApprovalList(runtimeOptions);
|
|
25
|
+
return loadApprovals(tasks, params.state);
|
|
26
|
+
},
|
|
27
|
+
render: {
|
|
28
|
+
rich: (result, primitives) => renderApprovalList(result, primitives),
|
|
29
|
+
markdown: (result) => renderApprovalListMarkdown(result),
|
|
30
|
+
json: (result) => result,
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
defineCommand({
|
|
34
|
+
name: "show",
|
|
35
|
+
description: "Show one approval.",
|
|
36
|
+
scope: listScope,
|
|
37
|
+
params: showParams,
|
|
38
|
+
handler: async ({ params, runtimeOptions }) => {
|
|
39
|
+
const { tasks } = await ensureApprovalList(runtimeOptions);
|
|
40
|
+
return tasks.get(params.approvalId);
|
|
41
|
+
},
|
|
42
|
+
render: {
|
|
43
|
+
rich: (result, primitives) => renderApprovalDetails(result, primitives),
|
|
44
|
+
markdown: (result) => renderApprovalDetailsMarkdown(result),
|
|
45
|
+
json: (result) => result,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
defineCommand({
|
|
49
|
+
name: "run",
|
|
50
|
+
description: "Run one queued approval.",
|
|
51
|
+
scope: runScope,
|
|
52
|
+
params: showParams,
|
|
53
|
+
handler: async ({ params, runtimeOptions, root }) => runApproval(params.approvalId, runtimeOptions, root),
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
56
|
+
}));
|
|
57
|
+
export function mergeApprovalsGroup(root) {
|
|
58
|
+
const existing = root.children.find((child) => child.name === approvalsGroup.name);
|
|
59
|
+
if (existing !== undefined) {
|
|
60
|
+
if (isApprovalsBuiltIn(existing)) {
|
|
61
|
+
return root;
|
|
62
|
+
}
|
|
63
|
+
throw new UserError("Error: 'approvals' is reserved for human-in-loop built-ins");
|
|
64
|
+
}
|
|
65
|
+
root.children = [...root.children, approvalsGroup];
|
|
66
|
+
return root;
|
|
67
|
+
}
|
|
68
|
+
function markApprovalsBuiltIn(group) {
|
|
69
|
+
Object.defineProperty(group, approvalsGroupSymbol, {
|
|
70
|
+
configurable: false,
|
|
71
|
+
enumerable: false,
|
|
72
|
+
value: true,
|
|
73
|
+
writable: false,
|
|
74
|
+
});
|
|
75
|
+
return group;
|
|
76
|
+
}
|
|
77
|
+
function isApprovalsBuiltIn(node) {
|
|
78
|
+
return node.kind === "group" && node[approvalsGroupSymbol] === true;
|
|
79
|
+
}
|
|
80
|
+
async function loadApprovals(tasks, stateFilter) {
|
|
81
|
+
const states = splitStateFilter(stateFilter);
|
|
82
|
+
if (states.length === 0) {
|
|
83
|
+
return tasks.all();
|
|
84
|
+
}
|
|
85
|
+
const seenIds = new Set();
|
|
86
|
+
const approvals = [];
|
|
87
|
+
for (const state of states) {
|
|
88
|
+
const matching = await tasks.all({
|
|
89
|
+
state,
|
|
90
|
+
});
|
|
91
|
+
for (const task of matching) {
|
|
92
|
+
if (seenIds.has(task.qualifiedId)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
seenIds.add(task.qualifiedId);
|
|
96
|
+
approvals.push(task);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return approvals;
|
|
100
|
+
}
|
|
101
|
+
function splitStateFilter(stateFilter) {
|
|
102
|
+
if (stateFilter === undefined) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
const states = [];
|
|
107
|
+
for (const value of stateFilter.split(",")) {
|
|
108
|
+
const trimmed = value.trim();
|
|
109
|
+
if (trimmed.length === 0 || seen.has(trimmed)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
seen.add(trimmed);
|
|
113
|
+
states.push(trimmed);
|
|
114
|
+
}
|
|
115
|
+
return states;
|
|
116
|
+
}
|
|
117
|
+
function renderApprovalList(result, { logger, renderTable, getTheme }) {
|
|
118
|
+
if (result.length === 0) {
|
|
119
|
+
logger.message("No approvals found.");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
logger.message(renderTable({
|
|
123
|
+
theme: getTheme(),
|
|
124
|
+
columns: [
|
|
125
|
+
{ name: "id", title: "ID", alignment: "left", maxLen: 24 },
|
|
126
|
+
{ name: "state", title: "State", alignment: "left", maxLen: 18 },
|
|
127
|
+
{ name: "name", title: "Name", alignment: "left", maxLen: 60 },
|
|
128
|
+
],
|
|
129
|
+
rows: result.map((task) => ({
|
|
130
|
+
id: task.id,
|
|
131
|
+
state: task.state,
|
|
132
|
+
name: task.name,
|
|
133
|
+
})),
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
function renderApprovalListMarkdown(result) {
|
|
137
|
+
if (result.length === 0) {
|
|
138
|
+
return "No approvals found.";
|
|
139
|
+
}
|
|
140
|
+
const lines = ["| ID | State | Name |", "| :--- | :--- | :--- |"];
|
|
141
|
+
for (const task of result) {
|
|
142
|
+
lines.push(`| ${escapeMarkdownCell(task.id)} | ${escapeMarkdownCell(task.state)} | ${escapeMarkdownCell(task.name)} |`);
|
|
143
|
+
}
|
|
144
|
+
return lines.join("\n");
|
|
145
|
+
}
|
|
146
|
+
function renderApprovalDetails(result, { logger, renderTable, getTheme }) {
|
|
147
|
+
logger.message(renderTable({
|
|
148
|
+
theme: getTheme(),
|
|
149
|
+
columns: [
|
|
150
|
+
{ name: "key", title: "Key", alignment: "left", maxLen: 18 },
|
|
151
|
+
{ name: "value", title: "Value", alignment: "left", maxLen: 80 },
|
|
152
|
+
],
|
|
153
|
+
rows: Object.entries(taskToRecord(result)).map(([key, value]) => ({
|
|
154
|
+
key,
|
|
155
|
+
value: stringifyValue(value),
|
|
156
|
+
})),
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
function renderApprovalDetailsMarkdown(result) {
|
|
160
|
+
return Object.entries(taskToRecord(result))
|
|
161
|
+
.map(([key, value]) => `- ${key}: ${stringifyValue(value)}`)
|
|
162
|
+
.join("\n");
|
|
163
|
+
}
|
|
164
|
+
function taskToRecord(task) {
|
|
165
|
+
return {
|
|
166
|
+
list: task.list,
|
|
167
|
+
id: task.id,
|
|
168
|
+
qualifiedId: task.qualifiedId,
|
|
169
|
+
name: task.name,
|
|
170
|
+
state: task.state,
|
|
171
|
+
description: task.description,
|
|
172
|
+
metadata: task.metadata,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function stringifyValue(value) {
|
|
176
|
+
if (value === undefined) {
|
|
177
|
+
return "";
|
|
178
|
+
}
|
|
179
|
+
if (typeof value === "string") {
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
return JSON.stringify(value) ?? String(value);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return String(value);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function escapeMarkdownCell(value) {
|
|
190
|
+
return value.replaceAll("|", "\\|");
|
|
191
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ObjectSchema } from "toolcraft-schema";
|
|
2
|
+
import type { HumanInLoopConfig } from "./types.js";
|
|
3
|
+
type HumanInLoopValue<TParamsSchema extends ObjectSchema<any>> = HumanInLoopConfig<TParamsSchema> | null | undefined;
|
|
4
|
+
export declare function validateHumanInLoopOnDefine<TParamsSchema extends ObjectSchema<any>>(config: {
|
|
5
|
+
name: string;
|
|
6
|
+
confirm?: boolean;
|
|
7
|
+
children?: readonly unknown[];
|
|
8
|
+
humanInLoop?: HumanInLoopValue<TParamsSchema>;
|
|
9
|
+
}): void;
|
|
10
|
+
export declare function mergeHumanInLoopFromGroup<TGroupParamsSchema extends ObjectSchema<any>, TChildParamsSchema extends ObjectSchema<any>>(groupHumanInLoop: HumanInLoopValue<TGroupParamsSchema>, childHumanInLoop: HumanInLoopValue<TChildParamsSchema>): HumanInLoopValue<TChildParamsSchema>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function validateHumanInLoopOnDefine(config) {
|
|
2
|
+
const label = Array.isArray(config.children) ? "group" : "command";
|
|
3
|
+
if (config.confirm === true && config.humanInLoop !== undefined && config.humanInLoop !== null) {
|
|
4
|
+
throw new Error(`${label} '${config.name}': use either confirm or humanInLoop, not both`);
|
|
5
|
+
}
|
|
6
|
+
if (config.humanInLoop === undefined || config.humanInLoop === null) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (config.humanInLoop.mode !== "sync" && config.humanInLoop.mode !== "async") {
|
|
10
|
+
throw new Error(`${label} '${config.name}': humanInLoop.mode must be "sync" or "async"`);
|
|
11
|
+
}
|
|
12
|
+
if (typeof config.humanInLoop.message !== "function") {
|
|
13
|
+
throw new Error(`${label} '${config.name}': humanInLoop.message must be a function`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function mergeHumanInLoopFromGroup(groupHumanInLoop, childHumanInLoop) {
|
|
17
|
+
if (childHumanInLoop !== undefined) {
|
|
18
|
+
return childHumanInLoop;
|
|
19
|
+
}
|
|
20
|
+
return groupHumanInLoop;
|
|
21
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { osascriptProvider } from "@poe-code/agent-human-in-loop";
|
|
3
|
+
import { UserError } from "../user-error.js";
|
|
4
|
+
function noProviderConfigured() {
|
|
5
|
+
return {
|
|
6
|
+
id: "noProviderConfigured",
|
|
7
|
+
async requestApproval() {
|
|
8
|
+
throw new UserError("no human-in-loop provider configured for this platform — pass humanInLoop.provider to the runtime");
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function createDefaultProviderFactory() {
|
|
13
|
+
let provider;
|
|
14
|
+
return () => {
|
|
15
|
+
if (provider !== undefined) {
|
|
16
|
+
return provider;
|
|
17
|
+
}
|
|
18
|
+
provider =
|
|
19
|
+
process.platform === "darwin" ? osascriptProvider({ title: "Approval needed" }) : noProviderConfigured();
|
|
20
|
+
return provider;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const getDefaultProvider = createDefaultProviderFactory();
|
|
24
|
+
export function defaultProviderForPlatform() {
|
|
25
|
+
return getDefaultProvider();
|
|
26
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Command, HandlerContext } from "../index.js";
|
|
2
|
+
import type { HumanInLoopPending, HumanInLoopProvider, HumanInLoopRuntimeOptions } from "./types.js";
|
|
3
|
+
export declare function resolveProvider(runtimeOptions: HumanInLoopRuntimeOptions | undefined): HumanInLoopProvider;
|
|
4
|
+
export declare function invokeWithHumanInLoop<T>(node: Command<any, any, any, T>, ctx: HandlerContext<any, any, any>, runtimeOptions: HumanInLoopRuntimeOptions | undefined, commandPath: string): Promise<T | HumanInLoopPending>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { enqueueApproval, ensureApprovalList } from "./approval-tasks.js";
|
|
2
|
+
import { defaultProviderForPlatform } from "./default-provider.js";
|
|
3
|
+
import { spawnApprovalRunner } from "./spawn.js";
|
|
4
|
+
import { ApprovalDeclinedError } from "./types.js";
|
|
5
|
+
const providersByRuntime = new WeakMap();
|
|
6
|
+
let providerWithoutRuntime;
|
|
7
|
+
export function resolveProvider(runtimeOptions) {
|
|
8
|
+
if (runtimeOptions?.provider !== undefined) {
|
|
9
|
+
return runtimeOptions.provider;
|
|
10
|
+
}
|
|
11
|
+
if (runtimeOptions === undefined) {
|
|
12
|
+
providerWithoutRuntime ??= defaultProviderForPlatform();
|
|
13
|
+
return providerWithoutRuntime;
|
|
14
|
+
}
|
|
15
|
+
const cachedProvider = providersByRuntime.get(runtimeOptions);
|
|
16
|
+
if (cachedProvider !== undefined) {
|
|
17
|
+
return cachedProvider;
|
|
18
|
+
}
|
|
19
|
+
const provider = defaultProviderForPlatform();
|
|
20
|
+
providersByRuntime.set(runtimeOptions, provider);
|
|
21
|
+
return provider;
|
|
22
|
+
}
|
|
23
|
+
export async function invokeWithHumanInLoop(node, ctx, runtimeOptions, commandPath) {
|
|
24
|
+
if (!node.humanInLoop) {
|
|
25
|
+
return node.handler(ctx);
|
|
26
|
+
}
|
|
27
|
+
const message = node.humanInLoop.message({
|
|
28
|
+
params: ctx.params,
|
|
29
|
+
commandPath,
|
|
30
|
+
});
|
|
31
|
+
if (node.humanInLoop.mode === "async") {
|
|
32
|
+
const { tasks } = await ensureApprovalList(runtimeOptions);
|
|
33
|
+
const { approvalId, pending } = await enqueueApproval({
|
|
34
|
+
tasks,
|
|
35
|
+
payload: {
|
|
36
|
+
commandPath,
|
|
37
|
+
params: ctx.params,
|
|
38
|
+
message,
|
|
39
|
+
declineInputPrompt: node.humanInLoop.declineInputPrompt,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
spawnApprovalRunner(approvalId, runtimeOptions);
|
|
43
|
+
return pending;
|
|
44
|
+
}
|
|
45
|
+
const provider = resolveProvider(runtimeOptions);
|
|
46
|
+
const result = await provider.requestApproval({
|
|
47
|
+
message,
|
|
48
|
+
declineInputPrompt: node.humanInLoop.declineInputPrompt,
|
|
49
|
+
});
|
|
50
|
+
if (result.outcome === "declined") {
|
|
51
|
+
throw new ApprovalDeclinedError({
|
|
52
|
+
reason: result.reason,
|
|
53
|
+
commandPath,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return node.handler(ctx);
|
|
57
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { defaultProviderForPlatform } from "./default-provider.js";
|
|
2
|
+
export { invokeWithHumanInLoop } from "./gate.js";
|
|
3
|
+
export { approvalStateMachine } from "./state-machine.js";
|
|
4
|
+
export { ApprovalDeclinedError } from "./types.js";
|
|
5
|
+
export type { ApprovalEvent, ApprovalState } from "./state-machine.js";
|
|
6
|
+
export type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntimeOptions } from "./types.js";
|
|
7
|
+
export type { HumanInLoopProvider } from "@poe-code/agent-human-in-loop";
|