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,196 @@
|
|
|
1
|
+
import { access, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { UserError, resolveCommandSecrets } from "../index.js";
|
|
3
|
+
import { ensureApprovalList } from "./approval-tasks.js";
|
|
4
|
+
import { resolveProvider } from "./gate.js";
|
|
5
|
+
export async function runApproval(approvalId, runtimeOptions, root) {
|
|
6
|
+
const { tasks } = await ensureApprovalList(runtimeOptions);
|
|
7
|
+
const task = await tasks.get(approvalId);
|
|
8
|
+
if (task.state !== "pending") {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const approval = readApprovalPayload(task);
|
|
12
|
+
const provider = resolveProvider(runtimeOptions);
|
|
13
|
+
try {
|
|
14
|
+
const approvalResult = await provider.requestApproval({
|
|
15
|
+
message: approval.message,
|
|
16
|
+
declineInputPrompt: approval.declineInputPrompt ?? undefined,
|
|
17
|
+
});
|
|
18
|
+
if (approvalResult.outcome === "declined") {
|
|
19
|
+
await tasks.fire(approvalId, "decline", {
|
|
20
|
+
metadataPatch: {
|
|
21
|
+
error: {
|
|
22
|
+
reason: approvalResult.reason,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
await failPendingApproval(tasks, approvalId, error);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
await tasks.fire(approvalId, "start", {
|
|
34
|
+
metadataPatch: {
|
|
35
|
+
pid: process.pid,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
const command = findCommand(root, approval.commandPath);
|
|
40
|
+
const ctx = createHandlerContext(command, approval.params);
|
|
41
|
+
const result = await command.handler(ctx);
|
|
42
|
+
const serializedResult = serializeJsonResult(result);
|
|
43
|
+
if (!serializedResult.ok) {
|
|
44
|
+
await tasks.fire(approvalId, "fail", {
|
|
45
|
+
metadataPatch: {
|
|
46
|
+
error: {
|
|
47
|
+
message: "result not JSON-serializable",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
await tasks.fire(approvalId, "succeed", {
|
|
54
|
+
metadataPatch: {
|
|
55
|
+
result: serializedResult.value,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
await tasks.fire(approvalId, "fail", {
|
|
61
|
+
metadataPatch: {
|
|
62
|
+
error: errorMetadataFromUnknown(error),
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function readApprovalPayload(task) {
|
|
68
|
+
const metadata = task.metadata;
|
|
69
|
+
if (typeof metadata !== "object" || metadata === null) {
|
|
70
|
+
throw new UserError(`Malformed approval metadata for "${task.qualifiedId}".`);
|
|
71
|
+
}
|
|
72
|
+
if (metadata.schemaVersion !== 1) {
|
|
73
|
+
throw new UserError(`Malformed approval metadata for "${task.qualifiedId}".`);
|
|
74
|
+
}
|
|
75
|
+
if (typeof metadata.commandPath !== "string" ||
|
|
76
|
+
typeof metadata.message !== "string" ||
|
|
77
|
+
typeof metadata.params !== "object" ||
|
|
78
|
+
metadata.params === null) {
|
|
79
|
+
throw new UserError(`Malformed approval metadata for "${task.qualifiedId}".`);
|
|
80
|
+
}
|
|
81
|
+
const declineInputPrompt = typeof metadata.declineInputPrompt === "string" || metadata.declineInputPrompt === null
|
|
82
|
+
? metadata.declineInputPrompt
|
|
83
|
+
: undefined;
|
|
84
|
+
return {
|
|
85
|
+
approvalId: typeof metadata.approvalId === "string" ? metadata.approvalId : undefined,
|
|
86
|
+
commandPath: metadata.commandPath,
|
|
87
|
+
params: metadata.params,
|
|
88
|
+
message: metadata.message,
|
|
89
|
+
declineInputPrompt,
|
|
90
|
+
enqueuedAt: typeof metadata.enqueuedAt === "string" ? metadata.enqueuedAt : undefined,
|
|
91
|
+
pid: typeof metadata.pid === "number" || metadata.pid === null ? metadata.pid : undefined,
|
|
92
|
+
result: metadata.result,
|
|
93
|
+
error: metadata.error,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function findCommand(root, commandPath) {
|
|
97
|
+
const pathSegments = commandPath.split(".").filter((segment) => segment.length > 0);
|
|
98
|
+
if (pathSegments.length === 0) {
|
|
99
|
+
throw new UserError(`Unknown approval command path "${commandPath}".`);
|
|
100
|
+
}
|
|
101
|
+
let current = root;
|
|
102
|
+
for (const segment of pathSegments) {
|
|
103
|
+
if (current.kind !== "group") {
|
|
104
|
+
throw new UserError(`Unknown approval command path "${commandPath}".`);
|
|
105
|
+
}
|
|
106
|
+
const next = current.children.find((child) => child.name === segment);
|
|
107
|
+
if (next === undefined) {
|
|
108
|
+
throw new UserError(`Unknown approval command path "${commandPath}".`);
|
|
109
|
+
}
|
|
110
|
+
current = next;
|
|
111
|
+
}
|
|
112
|
+
if (current.kind !== "command") {
|
|
113
|
+
throw new UserError(`Unknown approval command path "${commandPath}".`);
|
|
114
|
+
}
|
|
115
|
+
return current;
|
|
116
|
+
}
|
|
117
|
+
function createHandlerContext(command, params) {
|
|
118
|
+
return {
|
|
119
|
+
params,
|
|
120
|
+
secrets: resolveCommandSecrets(command),
|
|
121
|
+
fetch: globalThis.fetch,
|
|
122
|
+
fs: createFs(),
|
|
123
|
+
env: createEnv(),
|
|
124
|
+
progress() {
|
|
125
|
+
return undefined;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function createFs() {
|
|
130
|
+
return {
|
|
131
|
+
readFile: async (path, encoding = "utf8") => readFile(path, { encoding }),
|
|
132
|
+
writeFile: async (path, contents) => {
|
|
133
|
+
await writeFile(path, contents);
|
|
134
|
+
},
|
|
135
|
+
exists: async (path) => {
|
|
136
|
+
try {
|
|
137
|
+
await access(path);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function createEnv(values = process.env) {
|
|
147
|
+
return {
|
|
148
|
+
get(key) {
|
|
149
|
+
return values[key];
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function serializeJsonResult(value) {
|
|
154
|
+
try {
|
|
155
|
+
const serialized = JSON.stringify(value);
|
|
156
|
+
if (serialized === undefined) {
|
|
157
|
+
return {
|
|
158
|
+
ok: false,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
ok: true,
|
|
163
|
+
value: JSON.parse(serialized),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function errorMetadataFromUnknown(error) {
|
|
173
|
+
if (error instanceof Error) {
|
|
174
|
+
return {
|
|
175
|
+
name: error.name,
|
|
176
|
+
message: error.message,
|
|
177
|
+
stack: error.stack,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
name: "Error",
|
|
182
|
+
message: String(error),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
async function failPendingApproval(tasks, approvalId, error) {
|
|
186
|
+
await tasks.fire(approvalId, "start", {
|
|
187
|
+
metadataPatch: {
|
|
188
|
+
pid: process.pid,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
await tasks.fire(approvalId, "fail", {
|
|
192
|
+
metadataPatch: {
|
|
193
|
+
error: errorMetadataFromUnknown(error),
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
export function spawnApprovalRunner(approvalId, runtimeOptions, spawnFn) {
|
|
3
|
+
const { execPath, entryArgs } = runtimeOptions.binPath ?? {
|
|
4
|
+
execPath: process.execPath,
|
|
5
|
+
entryArgs: [process.argv[1]],
|
|
6
|
+
};
|
|
7
|
+
const fn = spawnFn ?? spawn;
|
|
8
|
+
const child = fn(execPath, [...entryArgs, "approvals", "run", approvalId], {
|
|
9
|
+
detached: true,
|
|
10
|
+
stdio: "ignore",
|
|
11
|
+
env: process.env,
|
|
12
|
+
cwd: process.cwd(),
|
|
13
|
+
});
|
|
14
|
+
child.unref();
|
|
15
|
+
}
|
|
16
|
+
export default spawnApprovalRunner;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { StateMachineDef } from "@poe-code/task-list";
|
|
2
|
+
export type ApprovalState = "pending" | "approved-running" | "approved-done" | "approved-failed" | "declined";
|
|
3
|
+
export type ApprovalEvent = "start" | "succeed" | "fail" | "decline";
|
|
4
|
+
export declare const approvalStateMachine: StateMachineDef<ApprovalState, ApprovalEvent>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const approvalStateMachine = {
|
|
2
|
+
initial: "pending",
|
|
3
|
+
states: ["pending", "approved-running", "approved-done", "approved-failed", "declined"],
|
|
4
|
+
events: {
|
|
5
|
+
start: { from: ["pending"], to: "approved-running" },
|
|
6
|
+
succeed: { from: ["approved-running"], to: "approved-done" },
|
|
7
|
+
fail: { from: ["approved-running"], to: "approved-failed" },
|
|
8
|
+
decline: { from: ["pending"], to: "declined" },
|
|
9
|
+
},
|
|
10
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { HumanInLoopProvider } from "@poe-code/agent-human-in-loop";
|
|
2
|
+
import type { TaskList } from "@poe-code/task-list";
|
|
3
|
+
import type { ObjectSchema, Static } from "toolcraft-schema";
|
|
4
|
+
import { UserError } from "../user-error.js";
|
|
5
|
+
export interface HumanInLoopConfig<TParamsSchema extends ObjectSchema<any>> {
|
|
6
|
+
mode: "sync" | "async";
|
|
7
|
+
message: (ctx: {
|
|
8
|
+
params: Static<TParamsSchema>;
|
|
9
|
+
commandPath: string;
|
|
10
|
+
}) => string;
|
|
11
|
+
declineInputPrompt?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface HumanInLoopRuntimeOptions {
|
|
14
|
+
provider?: HumanInLoopProvider;
|
|
15
|
+
taskList?: TaskList | {
|
|
16
|
+
dir: string;
|
|
17
|
+
format: "markdown-dir" | "yaml-file";
|
|
18
|
+
};
|
|
19
|
+
listName?: string;
|
|
20
|
+
binPath?: {
|
|
21
|
+
execPath: string;
|
|
22
|
+
entryArgs: readonly string[];
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export interface HumanInLoopPending {
|
|
26
|
+
status: "pending-approval";
|
|
27
|
+
approvalId: string;
|
|
28
|
+
message: string;
|
|
29
|
+
enqueuedAt: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class ApprovalDeclinedError extends UserError {
|
|
32
|
+
readonly reason?: string;
|
|
33
|
+
readonly approvalId?: string;
|
|
34
|
+
readonly commandPath: string;
|
|
35
|
+
constructor(options: {
|
|
36
|
+
commandPath: string;
|
|
37
|
+
reason?: string;
|
|
38
|
+
approvalId?: string;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export type { HumanInLoopProvider } from "@poe-code/agent-human-in-loop";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { UserError } from "../user-error.js";
|
|
2
|
+
export class ApprovalDeclinedError extends UserError {
|
|
3
|
+
reason;
|
|
4
|
+
approvalId;
|
|
5
|
+
commandPath;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
super(options.reason === undefined ? "Declined." : `Declined: ${options.reason}`);
|
|
8
|
+
this.name = "ApprovalDeclinedError";
|
|
9
|
+
this.reason = options.reason;
|
|
10
|
+
this.approvalId = options.approvalId;
|
|
11
|
+
this.commandPath = options.commandPath;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -38,6 +38,30 @@ const ignoredGroup = defineGroup({
|
|
|
38
38
|
children: [ignoredCommand],
|
|
39
39
|
default: ignoredCommand,
|
|
40
40
|
});
|
|
41
|
+
const ignoredMcpGroup = defineGroup({
|
|
42
|
+
name: "mcp-root",
|
|
43
|
+
mcp: {
|
|
44
|
+
transport: "stdio",
|
|
45
|
+
command: "server",
|
|
46
|
+
},
|
|
47
|
+
children: [],
|
|
48
|
+
});
|
|
49
|
+
const ignoredToolsGroup = defineGroup({
|
|
50
|
+
name: "tools-root",
|
|
51
|
+
tools: ["usage"],
|
|
52
|
+
children: [],
|
|
53
|
+
});
|
|
54
|
+
const ignoredRenameGroup = defineGroup({
|
|
55
|
+
name: "rename-root",
|
|
56
|
+
rename: {
|
|
57
|
+
usage: "bot.create",
|
|
58
|
+
},
|
|
59
|
+
children: [],
|
|
60
|
+
});
|
|
61
|
+
const ignoredPlainGroup = defineGroup({
|
|
62
|
+
name: "plain-root",
|
|
63
|
+
children: [],
|
|
64
|
+
});
|
|
41
65
|
const ignoredStringSchema = S.String();
|
|
42
66
|
const ignoredNumberSchema = S.Number();
|
|
43
67
|
const ignoredBooleanSchema = S.Boolean();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import type { McpServerConfig } from "@poe-code/agent-mcp-config";
|
|
1
2
|
import type { ObjectSchema, Static } from "toolcraft-schema";
|
|
2
3
|
import type { LoggerOutput, RenderTableOptions, ThemePalette } from "@poe-code/design-system";
|
|
4
|
+
import { ApprovalDeclinedError } from "./human-in-loop/types.js";
|
|
5
|
+
import type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntimeOptions } from "./human-in-loop/types.js";
|
|
6
|
+
import { UserError } from "./user-error.js";
|
|
3
7
|
type ScopeValue = "cli" | "mcp" | "sdk";
|
|
4
8
|
type AnyObjectSchema = ObjectSchema<Record<string, never>>;
|
|
5
9
|
type EmptyServices = Record<string, never>;
|
|
6
10
|
type ScopeInput = readonly Scope[] | undefined;
|
|
11
|
+
type HumanInLoopMode = "sync" | "async";
|
|
12
|
+
type HumanInLoopModeInput = HumanInLoopMode | null | undefined;
|
|
7
13
|
export type Scope = ScopeValue;
|
|
14
|
+
type ResolveOwnHumanInLoopMode<TValue> = TValue extends {
|
|
15
|
+
mode: infer TMode extends HumanInLoopMode;
|
|
16
|
+
} ? TMode : TValue extends null ? null : undefined;
|
|
8
17
|
export interface SecretDefinition {
|
|
9
18
|
env: string;
|
|
10
19
|
description?: string;
|
|
@@ -83,6 +92,7 @@ export interface CommandConfig<TServices extends object, TParamsSchema extends O
|
|
|
83
92
|
secrets?: TSecrets;
|
|
84
93
|
scope?: Scope[];
|
|
85
94
|
confirm?: boolean;
|
|
95
|
+
humanInLoop?: HumanInLoopConfig<TParamsSchema> | null;
|
|
86
96
|
requires?: Requires<CommandCheckContext<TParamsSchema, TSecrets, TServices>>;
|
|
87
97
|
handler: (ctx: HandlerContext<TParamsSchema, TSecrets, TServices>) => Promise<TResult>;
|
|
88
98
|
render?: Renderers<TResult>;
|
|
@@ -97,6 +107,7 @@ export interface Command<TServices extends object = EmptyServices, TParamsSchema
|
|
|
97
107
|
secrets: SecretDeclarations;
|
|
98
108
|
scope: Scope[];
|
|
99
109
|
confirm: boolean;
|
|
110
|
+
humanInLoop?: HumanInLoopConfig<TParamsSchema> | null;
|
|
100
111
|
requires?: Requires<any>;
|
|
101
112
|
handler: (ctx: HandlerContext<TParamsSchema, TSecrets, TServices>) => Promise<TResult>;
|
|
102
113
|
render?: Renderers<TResult>;
|
|
@@ -105,8 +116,12 @@ export interface GroupConfig<TServices extends object> {
|
|
|
105
116
|
name: string;
|
|
106
117
|
description?: string;
|
|
107
118
|
aliases?: string[];
|
|
119
|
+
mcp?: McpServerConfig;
|
|
108
120
|
scope?: Scope[];
|
|
121
|
+
humanInLoop?: HumanInLoopConfig<AnyObjectSchema> | null;
|
|
109
122
|
secrets?: SecretDeclarations;
|
|
123
|
+
tools?: string[];
|
|
124
|
+
rename?: Record<string, string>;
|
|
110
125
|
requires?: Requires<GroupCheckContext<TServices>>;
|
|
111
126
|
children: Array<CommandNode<TServices>>;
|
|
112
127
|
default?: Command<TServices, any, any, any>;
|
|
@@ -117,32 +132,32 @@ export interface Group<TServices extends object = EmptyServices> {
|
|
|
117
132
|
description?: string;
|
|
118
133
|
aliases: string[];
|
|
119
134
|
scope?: Scope[];
|
|
135
|
+
humanInLoop?: HumanInLoopConfig<AnyObjectSchema> | null;
|
|
120
136
|
secrets: SecretDeclarations;
|
|
121
137
|
requires?: Requires<any>;
|
|
122
138
|
children: Array<CommandNode<TServices>>;
|
|
123
139
|
default?: Command<TServices, any, any, any>;
|
|
124
140
|
}
|
|
125
141
|
export type CommandNode<TServices extends object = EmptyServices> = Command<TServices, any, any, any> | Group<TServices>;
|
|
126
|
-
export interface CommandTypeInfo<TName extends string = string, TParamsSchema extends ObjectSchema<any> = AnyObjectSchema, TResult = unknown, TOwnScope extends ScopeInput = ScopeInput> {
|
|
142
|
+
export interface CommandTypeInfo<TName extends string = string, TParamsSchema extends ObjectSchema<any> = AnyObjectSchema, TResult = unknown, TOwnScope extends ScopeInput = ScopeInput, TOwnHumanInLoopMode extends HumanInLoopModeInput = undefined> {
|
|
127
143
|
name: TName;
|
|
128
144
|
params: TParamsSchema;
|
|
129
145
|
result: TResult;
|
|
130
146
|
ownScope: TOwnScope;
|
|
147
|
+
ownHumanInLoopMode: TOwnHumanInLoopMode;
|
|
131
148
|
}
|
|
132
|
-
export interface GroupTypeInfo<TServices extends object = EmptyServices, TName extends string = string, TChildren extends readonly unknown[] = readonly CommandNode<TServices>[], TOwnScope extends ScopeInput = ScopeInput> {
|
|
149
|
+
export interface GroupTypeInfo<TServices extends object = EmptyServices, TName extends string = string, TChildren extends readonly unknown[] = readonly CommandNode<TServices>[], TOwnScope extends ScopeInput = ScopeInput, TOwnHumanInLoopMode extends HumanInLoopModeInput = undefined> {
|
|
133
150
|
name: TName;
|
|
134
151
|
children: TChildren;
|
|
135
152
|
ownScope: TOwnScope;
|
|
153
|
+
ownHumanInLoopMode: TOwnHumanInLoopMode;
|
|
136
154
|
}
|
|
137
|
-
type TypedCommandMetadata<TName extends string, TParamsSchema extends ObjectSchema<any>, TResult, TOwnScope extends ScopeInput> = {
|
|
138
|
-
readonly __agentKitCommandTypeInfo: CommandTypeInfo<TName, TParamsSchema, TResult, TOwnScope>;
|
|
155
|
+
type TypedCommandMetadata<TName extends string, TParamsSchema extends ObjectSchema<any>, TResult, TOwnScope extends ScopeInput, TOwnHumanInLoopMode extends HumanInLoopModeInput> = {
|
|
156
|
+
readonly __agentKitCommandTypeInfo: CommandTypeInfo<TName, TParamsSchema, TResult, TOwnScope, TOwnHumanInLoopMode>;
|
|
139
157
|
};
|
|
140
|
-
type TypedGroupMetadata<TServices extends object, TName extends string, TChildren extends readonly unknown[], TOwnScope extends ScopeInput> = {
|
|
141
|
-
readonly __agentKitGroupTypeInfo: GroupTypeInfo<TServices, TName, TChildren, TOwnScope>;
|
|
158
|
+
type TypedGroupMetadata<TServices extends object, TName extends string, TChildren extends readonly unknown[], TOwnScope extends ScopeInput, TOwnHumanInLoopMode extends HumanInLoopModeInput> = {
|
|
159
|
+
readonly __agentKitGroupTypeInfo: GroupTypeInfo<TServices, TName, TChildren, TOwnScope, TOwnHumanInLoopMode>;
|
|
142
160
|
};
|
|
143
|
-
export declare class UserError extends Error {
|
|
144
|
-
constructor(message: string);
|
|
145
|
-
}
|
|
146
161
|
export interface CommandRequirementOptions {
|
|
147
162
|
apiVersion?: string;
|
|
148
163
|
authEnvVar?: string;
|
|
@@ -150,15 +165,19 @@ export interface CommandRequirementOptions {
|
|
|
150
165
|
}
|
|
151
166
|
export declare function resolveCommandSecrets(command: Command<any, any, any, any>, env?: Record<string, string | undefined>): Record<string, string | undefined>;
|
|
152
167
|
export declare function assertCommandRequirements(command: Command<any, any, any, any>, context: GroupCheckContext<any>, options?: CommandRequirementOptions): Promise<void>;
|
|
153
|
-
export declare function defineCommand<TServices extends object = EmptyServices, TName extends string = string, TParamsSchema extends ObjectSchema<any> = AnyObjectSchema, TSecrets extends SecretDeclarations | undefined = undefined, TResult = unknown, TOwnScope extends ScopeInput = undefined>(config: Omit<CommandConfig<TServices, TParamsSchema, TSecrets, TResult>, "name" | "scope"> & {
|
|
168
|
+
export declare function defineCommand<TServices extends object = EmptyServices, TName extends string = string, TParamsSchema extends ObjectSchema<any> = AnyObjectSchema, TSecrets extends SecretDeclarations | undefined = undefined, TResult = unknown, TOwnScope extends ScopeInput = undefined, TOwnHumanInLoop extends HumanInLoopConfig<TParamsSchema> | null | undefined = undefined>(config: Omit<CommandConfig<TServices, TParamsSchema, TSecrets, TResult>, "name" | "scope" | "humanInLoop"> & {
|
|
154
169
|
name: TName;
|
|
155
170
|
scope?: TOwnScope;
|
|
156
|
-
|
|
157
|
-
|
|
171
|
+
humanInLoop?: TOwnHumanInLoop;
|
|
172
|
+
}): Command<TServices, TParamsSchema, TSecrets, TResult> & TypedCommandMetadata<TName, TParamsSchema, TResult, TOwnScope, ResolveOwnHumanInLoopMode<TOwnHumanInLoop>>;
|
|
173
|
+
export declare function defineGroup<TServices extends object = EmptyServices, TName extends string = string, TChildren extends readonly unknown[] = readonly CommandNode<TServices>[], TOwnScope extends ScopeInput = undefined, TOwnHumanInLoop extends HumanInLoopConfig<AnyObjectSchema> | null | undefined = undefined>(config: Omit<GroupConfig<TServices>, "name" | "children" | "scope" | "humanInLoop"> & {
|
|
158
174
|
name: TName;
|
|
159
175
|
children: TChildren & readonly CommandNode<TServices>[];
|
|
160
176
|
scope?: TOwnScope;
|
|
161
|
-
|
|
177
|
+
humanInLoop?: TOwnHumanInLoop;
|
|
178
|
+
}): Group<TServices> & TypedGroupMetadata<TServices, TName, TChildren, TOwnScope, ResolveOwnHumanInLoopMode<TOwnHumanInLoop>>;
|
|
162
179
|
export declare function getCommandSourcePath(command: Command<any, any, any, any>): string | undefined;
|
|
163
180
|
export { S, toJsonSchema } from "toolcraft-schema";
|
|
181
|
+
export { ApprovalDeclinedError, UserError };
|
|
164
182
|
export type { AnySchema, ArraySchema, BooleanSchema, EnumSchema, JsonSchema, NumberSchema, ObjectSchema, OptionalSchema, Static, StringSchema } from "toolcraft-schema";
|
|
183
|
+
export type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntimeOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { ApprovalDeclinedError } from "./human-in-loop/types.js";
|
|
3
|
+
import { mergeHumanInLoopFromGroup, validateHumanInLoopOnDefine } from "./human-in-loop/config.js";
|
|
4
|
+
import { UserError } from "./user-error.js";
|
|
2
5
|
const commandConfigSymbol = Symbol("toolcraft.command.config");
|
|
3
6
|
const groupConfigSymbol = Symbol("toolcraft.group.config");
|
|
4
7
|
const commandSourcePathSymbol = Symbol("toolcraft.command.sourcePath");
|
|
5
|
-
export class UserError extends Error {
|
|
6
|
-
constructor(message) {
|
|
7
|
-
super(message);
|
|
8
|
-
this.name = "UserError";
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
8
|
function cloneScope(scope) {
|
|
12
9
|
return scope === undefined ? undefined : [...scope];
|
|
13
10
|
}
|
|
@@ -34,6 +31,52 @@ function cloneRequires(requires) {
|
|
|
34
31
|
check: requires.check,
|
|
35
32
|
};
|
|
36
33
|
}
|
|
34
|
+
function cloneStringArray(values) {
|
|
35
|
+
return values === undefined ? undefined : [...values];
|
|
36
|
+
}
|
|
37
|
+
function cloneStringRecord(values) {
|
|
38
|
+
return values === undefined ? undefined : { ...values };
|
|
39
|
+
}
|
|
40
|
+
function cloneMcpServerConfig(config) {
|
|
41
|
+
if (config === undefined) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (config.transport === "stdio") {
|
|
45
|
+
return {
|
|
46
|
+
transport: "stdio",
|
|
47
|
+
command: config.command,
|
|
48
|
+
args: cloneStringArray(config.args),
|
|
49
|
+
env: cloneStringRecord(config.env),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
transport: "http",
|
|
54
|
+
url: config.url,
|
|
55
|
+
headers: cloneStringRecord(config.headers),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function cloneRenameMap(rename) {
|
|
59
|
+
return rename === undefined ? undefined : { ...rename };
|
|
60
|
+
}
|
|
61
|
+
function validateRenameMap(rename) {
|
|
62
|
+
if (rename === undefined) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const seenTargets = new Map();
|
|
66
|
+
for (const [upstreamName, targetPath] of Object.entries(rename)) {
|
|
67
|
+
if (targetPath.length === 0) {
|
|
68
|
+
throw new UserError(`Invalid rename target for upstream tool "${upstreamName}": path cannot be empty.`);
|
|
69
|
+
}
|
|
70
|
+
if (targetPath.split(".").some((segment) => segment.length === 0)) {
|
|
71
|
+
throw new UserError(`Invalid rename target for upstream tool "${upstreamName}": "${targetPath}" contains an empty segment.`);
|
|
72
|
+
}
|
|
73
|
+
const existingUpstreamName = seenTargets.get(targetPath);
|
|
74
|
+
if (existingUpstreamName !== undefined) {
|
|
75
|
+
throw new UserError(`Duplicate rename target "${targetPath}" for upstream tools "${existingUpstreamName}" and "${upstreamName}".`);
|
|
76
|
+
}
|
|
77
|
+
seenTargets.set(targetPath, upstreamName);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
37
80
|
function parseStackPath(candidate) {
|
|
38
81
|
if (candidate.startsWith("file://")) {
|
|
39
82
|
try {
|
|
@@ -224,6 +267,7 @@ function createBaseCommand(config) {
|
|
|
224
267
|
secrets: cloneSecrets(config.secrets),
|
|
225
268
|
scope: resolveCommandScope(config.scope, undefined),
|
|
226
269
|
confirm: config.confirm ?? false,
|
|
270
|
+
humanInLoop: config.humanInLoop,
|
|
227
271
|
requires: cloneRequires(config.requires),
|
|
228
272
|
handler: config.handler,
|
|
229
273
|
render: config.render,
|
|
@@ -231,6 +275,7 @@ function createBaseCommand(config) {
|
|
|
231
275
|
Object.defineProperty(command, commandConfigSymbol, {
|
|
232
276
|
value: {
|
|
233
277
|
scope: cloneScope(config.scope),
|
|
278
|
+
humanInLoop: config.humanInLoop,
|
|
234
279
|
secrets: cloneSecrets(config.secrets),
|
|
235
280
|
requires: cloneRequires(config.requires),
|
|
236
281
|
sourcePath: inferCommandSourcePath(),
|
|
@@ -245,6 +290,7 @@ function createBaseGroup(config) {
|
|
|
245
290
|
description: config.description,
|
|
246
291
|
aliases: [...(config.aliases ?? [])],
|
|
247
292
|
scope: resolveGroupScope(config.scope, undefined),
|
|
293
|
+
humanInLoop: config.humanInLoop,
|
|
248
294
|
secrets: cloneSecrets(config.secrets),
|
|
249
295
|
requires: cloneRequires(config.requires),
|
|
250
296
|
children: [],
|
|
@@ -252,8 +298,12 @@ function createBaseGroup(config) {
|
|
|
252
298
|
};
|
|
253
299
|
Object.defineProperty(group, groupConfigSymbol, {
|
|
254
300
|
value: {
|
|
301
|
+
mcp: cloneMcpServerConfig(config.mcp),
|
|
255
302
|
scope: cloneScope(config.scope),
|
|
303
|
+
humanInLoop: config.humanInLoop,
|
|
256
304
|
secrets: cloneSecrets(config.secrets),
|
|
305
|
+
tools: cloneStringArray(config.tools),
|
|
306
|
+
rename: cloneRenameMap(config.rename),
|
|
257
307
|
requires: cloneRequires(config.requires),
|
|
258
308
|
children: [...config.children],
|
|
259
309
|
default: config.default,
|
|
@@ -279,6 +329,7 @@ function materializeCommand(command, inherited) {
|
|
|
279
329
|
secrets: mergeSecrets(inherited.secrets, internal.secrets),
|
|
280
330
|
scope: resolveCommandScope(internal.scope, inherited.scope),
|
|
281
331
|
confirm: command.confirm,
|
|
332
|
+
humanInLoop: mergeHumanInLoopFromGroup(inherited.humanInLoop, internal.humanInLoop),
|
|
282
333
|
requires: mergeRequires(inherited.requires, internal.requires),
|
|
283
334
|
handler: command.handler,
|
|
284
335
|
render: command.render,
|
|
@@ -286,6 +337,7 @@ function materializeCommand(command, inherited) {
|
|
|
286
337
|
Object.defineProperty(materialized, commandConfigSymbol, {
|
|
287
338
|
value: {
|
|
288
339
|
scope: cloneScope(internal.scope),
|
|
340
|
+
humanInLoop: internal.humanInLoop,
|
|
289
341
|
secrets: cloneSecrets(internal.secrets),
|
|
290
342
|
requires: cloneRequires(internal.requires),
|
|
291
343
|
sourcePath: internal.sourcePath,
|
|
@@ -296,16 +348,18 @@ function materializeCommand(command, inherited) {
|
|
|
296
348
|
});
|
|
297
349
|
return materialized;
|
|
298
350
|
}
|
|
351
|
+
function mergeInheritedMetadata(group, inherited) {
|
|
352
|
+
return {
|
|
353
|
+
scope: resolveGroupScope(group.scope, inherited.scope),
|
|
354
|
+
humanInLoop: mergeHumanInLoopFromGroup(inherited.humanInLoop, group.humanInLoop),
|
|
355
|
+
secrets: mergeSecrets(inherited.secrets, group.secrets),
|
|
356
|
+
requires: mergeRequires(inherited.requires, group.requires),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
299
359
|
function materializeGroup(group, inherited) {
|
|
300
360
|
const internal = getInternalGroupConfig(group);
|
|
301
|
-
const
|
|
302
|
-
const
|
|
303
|
-
const requires = mergeRequires(inherited.requires, internal.requires);
|
|
304
|
-
const materializedChildren = internal.children.map((child) => materializeNode(child, {
|
|
305
|
-
scope,
|
|
306
|
-
secrets,
|
|
307
|
-
requires,
|
|
308
|
-
}));
|
|
361
|
+
const mergedInherited = mergeInheritedMetadata(internal, inherited);
|
|
362
|
+
const materializedChildren = internal.children.map((child) => materializeNode(child, mergedInherited));
|
|
309
363
|
let defaultChild;
|
|
310
364
|
if (internal.default !== undefined) {
|
|
311
365
|
const defaultIndex = internal.children.indexOf(internal.default);
|
|
@@ -323,16 +377,21 @@ function materializeGroup(group, inherited) {
|
|
|
323
377
|
name: group.name,
|
|
324
378
|
description: group.description,
|
|
325
379
|
aliases: [...group.aliases],
|
|
326
|
-
scope,
|
|
327
|
-
|
|
328
|
-
|
|
380
|
+
scope: mergedInherited.scope,
|
|
381
|
+
humanInLoop: mergedInherited.humanInLoop,
|
|
382
|
+
secrets: mergedInherited.secrets,
|
|
383
|
+
requires: mergedInherited.requires,
|
|
329
384
|
children: materializedChildren,
|
|
330
385
|
default: defaultChild,
|
|
331
386
|
};
|
|
332
387
|
Object.defineProperty(materialized, groupConfigSymbol, {
|
|
333
388
|
value: {
|
|
389
|
+
mcp: cloneMcpServerConfig(internal.mcp),
|
|
334
390
|
scope: cloneScope(internal.scope),
|
|
391
|
+
humanInLoop: internal.humanInLoop,
|
|
335
392
|
secrets: cloneSecrets(internal.secrets),
|
|
393
|
+
tools: cloneStringArray(internal.tools),
|
|
394
|
+
rename: cloneRenameMap(internal.rename),
|
|
336
395
|
requires: cloneRequires(internal.requires),
|
|
337
396
|
children: [...internal.children],
|
|
338
397
|
default: internal.default,
|
|
@@ -347,15 +406,20 @@ function materializeNode(node, inherited) {
|
|
|
347
406
|
return materializeGroup(node, inherited);
|
|
348
407
|
}
|
|
349
408
|
export function defineCommand(config) {
|
|
409
|
+
validateHumanInLoopOnDefine(config);
|
|
350
410
|
return materializeCommand(createBaseCommand(config), {
|
|
351
411
|
scope: undefined,
|
|
412
|
+
humanInLoop: undefined,
|
|
352
413
|
secrets: {},
|
|
353
414
|
requires: undefined,
|
|
354
415
|
});
|
|
355
416
|
}
|
|
356
417
|
export function defineGroup(config) {
|
|
418
|
+
validateRenameMap(config.rename);
|
|
419
|
+
validateHumanInLoopOnDefine(config);
|
|
357
420
|
return materializeGroup(createBaseGroup(config), {
|
|
358
421
|
scope: undefined,
|
|
422
|
+
humanInLoop: undefined,
|
|
359
423
|
secrets: {},
|
|
360
424
|
requires: undefined,
|
|
361
425
|
});
|
|
@@ -364,3 +428,4 @@ export function getCommandSourcePath(command) {
|
|
|
364
428
|
return command[commandSourcePathSymbol];
|
|
365
429
|
}
|
|
366
430
|
export { S, toJsonSchema } from "toolcraft-schema";
|
|
431
|
+
export { ApprovalDeclinedError, UserError };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AnySchema } from "toolcraft-schema";
|
|
2
|
+
type JsonSchemaType = "string" | "number" | "integer" | "boolean" | "array" | "object" | "null";
|
|
3
|
+
export interface JsonSchema {
|
|
4
|
+
$defs?: Record<string, JsonSchema>;
|
|
5
|
+
$ref?: string;
|
|
6
|
+
additionalProperties?: boolean | JsonSchema;
|
|
7
|
+
anyOf?: readonly JsonSchema[];
|
|
8
|
+
const?: unknown;
|
|
9
|
+
default?: unknown;
|
|
10
|
+
description?: string;
|
|
11
|
+
enum?: readonly unknown[];
|
|
12
|
+
items?: JsonSchema;
|
|
13
|
+
nullable?: boolean;
|
|
14
|
+
oneOf?: readonly JsonSchema[];
|
|
15
|
+
pattern?: string;
|
|
16
|
+
properties?: Record<string, JsonSchema>;
|
|
17
|
+
required?: readonly string[];
|
|
18
|
+
type?: JsonSchemaType | readonly JsonSchemaType[];
|
|
19
|
+
}
|
|
20
|
+
export declare function convertJsonSchema(schema: JsonSchema): AnySchema;
|
|
21
|
+
export {};
|