toolcraft 0.0.2 → 0.0.4

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.
Files changed (85) hide show
  1. package/README.md +461 -58
  2. package/dist/cli.compile-check.js +1 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +768 -40
  5. package/dist/human-in-loop/approval-tasks.d.ts +31 -0
  6. package/dist/human-in-loop/approval-tasks.js +201 -0
  7. package/dist/human-in-loop/approvals-commands.d.ts +11 -0
  8. package/dist/human-in-loop/approvals-commands.js +191 -0
  9. package/dist/human-in-loop/config.d.ts +11 -0
  10. package/dist/human-in-loop/config.js +21 -0
  11. package/dist/human-in-loop/default-provider.d.ts +2 -0
  12. package/dist/human-in-loop/default-provider.js +26 -0
  13. package/dist/human-in-loop/gate.d.ts +4 -0
  14. package/dist/human-in-loop/gate.js +57 -0
  15. package/dist/human-in-loop/index.d.ts +7 -0
  16. package/dist/human-in-loop/index.js +4 -0
  17. package/dist/human-in-loop/runner.d.ts +3 -0
  18. package/dist/human-in-loop/runner.js +196 -0
  19. package/dist/human-in-loop/spawn.d.ts +3 -0
  20. package/dist/human-in-loop/spawn.js +16 -0
  21. package/dist/human-in-loop/state-machine.d.ts +4 -0
  22. package/dist/human-in-loop/state-machine.js +10 -0
  23. package/dist/human-in-loop/types.d.ts +41 -0
  24. package/dist/human-in-loop/types.js +13 -0
  25. package/dist/index.compile-check.js +24 -0
  26. package/dist/index.d.ts +32 -13
  27. package/dist/index.js +82 -17
  28. package/dist/json-schema-converter.d.ts +21 -0
  29. package/dist/json-schema-converter.js +432 -0
  30. package/dist/mcp-proxy.d.ts +8 -0
  31. package/dist/mcp-proxy.js +383 -0
  32. package/dist/mcp.compile-check.js +1 -0
  33. package/dist/mcp.d.ts +2 -0
  34. package/dist/mcp.js +103 -11
  35. package/dist/sdk.compile-check.js +77 -0
  36. package/dist/sdk.d.ts +14 -5
  37. package/dist/sdk.js +57 -6
  38. package/dist/user-error.d.ts +3 -0
  39. package/dist/user-error.js +6 -0
  40. package/node_modules/@poe-code/agent-human-in-loop/README.md +42 -0
  41. package/node_modules/@poe-code/agent-human-in-loop/dist/index.d.ts +5 -0
  42. package/node_modules/@poe-code/agent-human-in-loop/dist/index.js +3 -0
  43. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.d.ts +2 -0
  44. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.js +11 -0
  45. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.d.ts +4 -0
  46. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +40 -0
  47. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.d.ts +6 -0
  48. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +33 -0
  49. package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.d.ts +4 -0
  50. package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.js +4 -0
  51. package/node_modules/@poe-code/agent-human-in-loop/dist/types.d.ts +14 -0
  52. package/node_modules/@poe-code/agent-human-in-loop/dist/types.js +1 -0
  53. package/node_modules/@poe-code/agent-human-in-loop/package.json +25 -0
  54. package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +6 -0
  55. package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +175 -0
  56. package/node_modules/@poe-code/agent-mcp-config/dist/configs.d.ts +22 -0
  57. package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +74 -0
  58. package/node_modules/@poe-code/agent-mcp-config/dist/index.d.ts +3 -0
  59. package/node_modules/@poe-code/agent-mcp-config/dist/index.js +2 -0
  60. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +31 -0
  61. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +87 -0
  62. package/node_modules/@poe-code/agent-mcp-config/dist/types.d.ts +25 -0
  63. package/node_modules/@poe-code/agent-mcp-config/dist/types.js +1 -0
  64. package/node_modules/@poe-code/agent-mcp-config/package.json +25 -0
  65. package/node_modules/@poe-code/task-list/README.md +114 -0
  66. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.d.ts +2 -0
  67. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +466 -0
  68. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +8 -0
  69. package/node_modules/@poe-code/task-list/dist/backends/utils.js +58 -0
  70. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.d.ts +2 -0
  71. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +444 -0
  72. package/node_modules/@poe-code/task-list/dist/index.d.ts +4 -0
  73. package/node_modules/@poe-code/task-list/dist/index.js +4 -0
  74. package/node_modules/@poe-code/task-list/dist/open.d.ts +3 -0
  75. package/node_modules/@poe-code/task-list/dist/open.js +34 -0
  76. package/node_modules/@poe-code/task-list/dist/schema/store.schema.json +32 -0
  77. package/node_modules/@poe-code/task-list/dist/schema/task.schema.json +33 -0
  78. package/node_modules/@poe-code/task-list/dist/state-machine.d.ts +16 -0
  79. package/node_modules/@poe-code/task-list/dist/state-machine.js +67 -0
  80. package/node_modules/@poe-code/task-list/dist/state.d.ts +29 -0
  81. package/node_modules/@poe-code/task-list/dist/state.js +61 -0
  82. package/node_modules/@poe-code/task-list/dist/types.d.ts +116 -0
  83. package/node_modules/@poe-code/task-list/dist/types.js +37 -0
  84. package/node_modules/@poe-code/task-list/package.json +26 -0
  85. 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,3 @@
1
+ import type { HumanInLoopRuntimeOptions } from "./types.js";
2
+ export declare function spawnApprovalRunner(approvalId: string, runtimeOptions: HumanInLoopRuntimeOptions, spawnFn?: typeof import("node:child_process").spawn): void;
3
+ export default spawnApprovalRunner;
@@ -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
- }): Command<TServices, TParamsSchema, TSecrets, TResult> & TypedCommandMetadata<TName, TParamsSchema, TResult, TOwnScope>;
157
- export declare function defineGroup<TServices extends object = EmptyServices, TName extends string = string, TChildren extends readonly unknown[] = readonly CommandNode<TServices>[], TOwnScope extends ScopeInput = undefined>(config: Omit<GroupConfig<TServices>, "name" | "children" | "scope"> & {
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
- }): Group<TServices> & TypedGroupMetadata<TServices, TName, TChildren, TOwnScope>;
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 scope = resolveGroupScope(internal.scope, inherited.scope);
302
- const secrets = mergeSecrets(inherited.secrets, internal.secrets);
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
- secrets,
328
- requires,
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 {};