toolcraft 0.0.121 → 0.0.123

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 (44) hide show
  1. package/README.md +23 -16
  2. package/composition.json +2 -2
  3. package/dist/cli.compile-check.js +4 -1
  4. package/dist/cli.d.ts +3 -2
  5. package/dist/cli.js +10 -14
  6. package/dist/composition.json +2 -2
  7. package/dist/human-in-loop/approval-tasks.d.ts +5 -2
  8. package/dist/human-in-loop/approvals-commands.d.ts +2 -2
  9. package/dist/human-in-loop/approvals-commands.js +10 -6
  10. package/dist/human-in-loop/gate.d.ts +3 -3
  11. package/dist/human-in-loop/gate.js +1 -21
  12. package/dist/human-in-loop/index.d.ts +4 -1
  13. package/dist/human-in-loop/index.js +2 -0
  14. package/dist/human-in-loop/runner.d.ts +1 -1
  15. package/dist/human-in-loop/runner.js +1 -2
  16. package/dist/human-in-loop/runtime-options.d.ts +19 -0
  17. package/dist/human-in-loop/runtime-options.js +1 -0
  18. package/dist/human-in-loop/runtime.d.ts +2 -0
  19. package/dist/human-in-loop/runtime.js +14 -0
  20. package/dist/human-in-loop/spawn.d.ts +2 -2
  21. package/dist/human-in-loop/types.d.ts +9 -14
  22. package/dist/human-in-loop/wiring.d.ts +11 -0
  23. package/dist/human-in-loop/wiring.js +36 -0
  24. package/dist/index.d.ts +2 -2
  25. package/dist/mcp.compile-check.js +4 -1
  26. package/dist/mcp.d.ts +2 -2
  27. package/dist/mcp.js +16 -10
  28. package/dist/sdk.compile-check.js +4 -1
  29. package/dist/sdk.d.ts +2 -2
  30. package/dist/sdk.js +11 -8
  31. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +1 -2
  32. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +1 -4
  33. package/node_modules/@poe-code/task-list/dist/schema/ids.d.ts +2 -0
  34. package/node_modules/@poe-code/task-list/dist/schema/ids.js +6 -0
  35. package/node_modules/tiny-http-mcp-server/dist/cli.js +5 -1
  36. package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
  37. package/node_modules/toolcraft-schema/package.json +1 -1
  38. package/package.json +2 -10
  39. package/dist/agent-human-in-loop.d.ts +0 -1
  40. package/dist/agent-human-in-loop.js +0 -1
  41. package/dist/task-list.d.ts +0 -1
  42. package/dist/task-list.js +0 -1
  43. package/node_modules/@poe-code/task-list/dist/schema/store.schema.json +0 -32
  44. package/node_modules/@poe-code/task-list/dist/schema/task.schema.json +0 -33
package/README.md CHANGED
@@ -425,22 +425,24 @@ Modes:
425
425
  - `sync` — handler waits for approval before running.
426
426
  - `async` — toolcraft enqueues the command, returns a pending marker, and runs it in a fresh process when an operator approves via the reserved `approvals` group.
427
427
 
428
- Wire the same `humanInLoop` options into every entrypoint:
428
+ The runtime never ships with the core entrypoints — it loads only through the `toolcraft/human-in-loop` export, and providers are surfaced from the same export. Wire the same runtime into every entrypoint:
429
429
 
430
430
  ```ts
431
- const humanInLoop = {
432
- provider: slackApprovalProvider({ channel: "#deploys", client }),
431
+ import { createHumanInLoop, osascriptProvider } from "toolcraft/human-in-loop";
432
+
433
+ const humanInLoop = createHumanInLoop({
434
+ provider: osascriptProvider({ title: "Approval needed" }), // required
433
435
  taskList: { dir: ".toolcraft/approvals.yaml", format: "yaml-file" as const }
434
- };
436
+ });
435
437
 
436
- await runCLI(root, { humanInLoop });
437
- createMCPServer(root, { name: "mytool", version: "0.1.0", humanInLoop });
438
- const sdk = createSDK(root, { humanInLoop });
438
+ await runCLI(root, { humanInLoop, approvals: true });
439
+ createMCPServer(root, { name: "mytool", version: "0.1.0", humanInLoop, approvals: true });
440
+ const sdk = createSDK(root, { humanInLoop, approvals: true });
439
441
  ```
440
442
 
441
- If `provider` is omitted, toolcraft picks a default lazily on first use: `osascriptProvider` on macOS; otherwise a stub that throws `UserError("no human-in-loop provider configured for this platform")`.
443
+ `provider` is required there is no implicit platform default. `defaultProviderForPlatform()` (osascript on macOS, a throwing stub elsewhere) is exported for callers that want that behavior explicitly. A command that declares `humanInLoop` config while no runtime is wired fails at startup, as does `approvals: true` without a runtime.
442
444
 
443
- A built-in `approvals` group is auto-merged into every root:
445
+ With `approvals: true`, the built-in `approvals` group is merged into the root:
444
446
 
445
447
  - `approvals list` — list pending tasks (CLI, MCP, SDK).
446
448
  - `approvals show --approval-id <id>` — show one task.
@@ -448,7 +450,7 @@ A built-in `approvals` group is auto-merged into every root:
448
450
 
449
451
  The name `approvals` is reserved. Defining your own `approvals` group fails at startup.
450
452
 
451
- The async runner re-execs your binary (`process.execPath` + `process.argv[1]` by default; override via `humanInLoop.binPath`). Re-exec calls the same toolcraft entrypoint with the same `humanInLoop` options — do not branch on `argv` before calling `runCLI`/`runMCP`/`createSDK`.
453
+ The async runner re-execs your binary (`process.execPath` + `process.argv[1]` by default; override via the `binPath` option of `createHumanInLoop`). Re-exec calls the same toolcraft entrypoint with the same wired runtime — do not branch on `argv` before calling `runCLI`/`runMCP`/`createSDK`.
452
454
 
453
455
  Async results must be JSON-serializable; non-serializable returns mark the approval as failed instead of being persisted.
454
456
 
@@ -459,7 +461,7 @@ import type {
459
461
  ApprovalRequest,
460
462
  ApprovalResult,
461
463
  HumanInLoopProvider
462
- } from "@poe-code/agent-human-in-loop";
464
+ } from "toolcraft/human-in-loop";
463
465
 
464
466
  export function slackApprovalProvider(opts: {
465
467
  channel: string;
@@ -545,7 +547,7 @@ esbuild records dependency paths relative to the build layout. A standalone inst
545
547
 
546
548
  ## Configuration Options
547
549
 
548
- Toolcraft configuration is code-first. Use `defineCommand(config)` and `defineGroup(config)` for the command tree, then pass runtime options to `runCLI`, `createSDK`, `createMCPServer`, or `runMCP`. MCP proxy schemas are cached under `.toolcraft/mcp`, and optional human-in-loop state is configured with `HumanInLoopRuntimeOptions`.
550
+ Toolcraft configuration is code-first. Use `defineCommand(config)` and `defineGroup(config)` for the command tree, then pass runtime options to `runCLI`, `createSDK`, `createMCPServer`, or `runMCP`. MCP proxy schemas are cached under `.toolcraft/mcp`, and human-in-loop approvals are wired explicitly with `createHumanInLoop` from `toolcraft/human-in-loop`.
549
551
 
550
552
  ## API reference
551
553
 
@@ -589,7 +591,8 @@ additional long CLI flags. For example, `rawResponse` normally maps to `--raw-re
589
591
  - `presets?: boolean` — enables `--preset <path>` for loading parameter defaults from JSON files.
590
592
  - `controls?: { debug?, logLevel?, output?, verbose?, yes? }` — enables generated global CLI controls. Set `output: true` to expose `--output <rich|md|markdown|json>`.
591
593
  - `apiVersion?: string` — for `requires.apiVersion`.
592
- - `humanInLoop?: HumanInLoopRuntimeOptions`
594
+ - `humanInLoop?: HumanInLoopRuntime` — from `createHumanInLoop` (`toolcraft/human-in-loop`); required for commands with `humanInLoop` config.
595
+ - `approvals?: boolean` — merge the built-in `approvals` group; requires `humanInLoop`.
593
596
  - `errorReports?: boolean | { dir?: string }`
594
597
  - `projectRoot?: string` — root used for MCP proxy cache files (`.toolcraft/mcp/*.json`).
595
598
 
@@ -609,17 +612,21 @@ additional long CLI flags. For example, `rawResponse` normally maps to `--raw-re
609
612
  - `omitRootToolNamePrefix?: boolean` — defaults to `false`. Set to `true` to omit the root group name from single-root MCP tool names (`bot__create`).
610
613
  - `casing?: "snake" | "camel"` — affects MCP input-schema property names, output-schema property names, and structured result keys. Tool names always stay `__`-joined snake_case.
611
614
 
612
- ### `HumanInLoopRuntimeOptions`
615
+ ### `createHumanInLoop(options)` (from `toolcraft/human-in-loop`)
613
616
 
614
617
  ```ts
618
+ import { createHumanInLoop, defaultProviderForPlatform, osascriptProvider } from "toolcraft/human-in-loop";
619
+
615
620
  type HumanInLoopRuntimeOptions = {
616
- provider?: HumanInLoopProvider;
621
+ provider: HumanInLoopProvider; // required
617
622
  taskList?: TaskList | { dir: string; format: "markdown-dir" | "yaml-file" };
618
623
  listName?: string; // defaults to "approvals"
619
624
  binPath?: { execPath: string; entryArgs: readonly string[] };
620
625
  };
621
626
  ```
622
627
 
628
+ Returns the `HumanInLoopRuntime` accepted by `runCLI` / `createMCPServer` / `createSDK`.
629
+
623
630
  ### Handler context
624
631
 
625
632
  - `params` — inferred from the command `params` schema.
@@ -635,7 +642,7 @@ type HumanInLoopRuntimeOptions = {
635
642
  - `defineCommand`, `defineGroup`
636
643
  - `S`, `toJsonSchema`, type helpers — re-exported from `toolcraft-schema`
637
644
  - `UserError`, `ApprovalDeclinedError`, `HttpError` and the HTTP error subclasses.
638
- - Type exports: `Command`, `Group`, `Scope`, `HandlerContext`, `HumanInLoopConfig`, `HumanInLoopPending`, `HumanInLoopRuntimeOptions`, schema types from `toolcraft-schema`.
645
+ - Type exports: `Command`, `Group`, `Scope`, `HandlerContext`, `HumanInLoopConfig`, `HumanInLoopPending`, `HumanInLoopRuntime`, schema types from `toolcraft-schema`.
639
646
 
640
647
  Subpath imports:
641
648
 
package/composition.json CHANGED
@@ -103,7 +103,7 @@
103
103
  },
104
104
  {
105
105
  "name": "toolcraft",
106
- "version": "0.0.121",
106
+ "version": "0.0.123",
107
107
  "license": "MIT"
108
108
  },
109
109
  {
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft-schema",
116
- "version": "0.0.121",
116
+ "version": "0.0.123",
117
117
  "license": "MIT"
118
118
  },
119
119
  {
@@ -27,7 +27,10 @@ const ignoredOptions = {
27
27
  logger: (event) => {
28
28
  event.level;
29
29
  },
30
- humanInLoop: {},
30
+ humanInLoop: {
31
+ invoke: async (node, ctx) => node.handler(ctx),
32
+ mergeApprovalsGroup: (root) => root,
33
+ },
31
34
  version: "1.0.0",
32
35
  };
33
36
  const ignoredServiceOptions = {
package/dist/cli.d.ts CHANGED
@@ -2,7 +2,7 @@ import "./node-require-shim.js";
2
2
  import { configureTheme } from "toolcraft-design";
3
3
  import type { Command, Group, HandlerFs, LogLevel, RenderPrimitives, RuntimeLoggerInput } from "./index.js";
4
4
  import { type ErrorReportsOption } from "./error-report.js";
5
- import type { HumanInLoopRuntimeOptions } from "./human-in-loop/types.js";
5
+ import type { HumanInLoopRuntime } from "./human-in-loop/types.js";
6
6
  export { renderErrorReport } from "./error-report.js";
7
7
  export type { ErrorReportRenderContext, ErrorReportRenderResult } from "./error-report.js";
8
8
  export { configureTheme };
@@ -34,7 +34,7 @@ export interface RunCLIOptions<TServices extends object = Record<string, unknown
34
34
  env?: Record<string, string>;
35
35
  fetch?: typeof globalThis.fetch;
36
36
  fs?: HandlerFs;
37
- humanInLoop?: HumanInLoopRuntimeOptions;
37
+ humanInLoop?: HumanInLoopRuntime;
38
38
  logLevel?: LogLevel;
39
39
  logger?: RuntimeLoggerInput;
40
40
  outputEmitter?: (entry: string) => void;
@@ -92,6 +92,7 @@ export interface CLICommandTreeSnapshotOptions {
92
92
  argv?: readonly string[];
93
93
  casing?: Casing;
94
94
  controls?: CLIControls;
95
+ humanInLoop?: HumanInLoopRuntime;
95
96
  presets?: boolean;
96
97
  version?: string;
97
98
  }
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { cancel, configureTheme, confirm, createLogger, formatCommandList, forma
7
7
  import { ApprovalDeclinedError, ToolcraftBugError, UserError, assertCommandRequirements, getCommandSourcePath, hasMcpProxyConfig, resolveCommandSecrets } from "./index.js";
8
8
  import { hasOwnErrorCode } from "./error-codes.js";
9
9
  import { writeErrorReport } from "./error-report.js";
10
+ import { assertHumanInLoopWired, mergeApprovalsRoot } from "./human-in-loop/wiring.js";
10
11
  import { getExpectedNumberDescription, isValidNumberSchemaValue } from "./number-schema.js";
11
12
  import { findEntrypointPackageMetadata } from "./package-metadata.js";
12
13
  import { redactHttpBody, redactHttpHeaderValue } from "./redaction.js";
@@ -24,8 +25,6 @@ configureTheme({ brand: "blue", label: "Toolcraft" });
24
25
  export { configureTheme };
25
26
  const NULL_OPTION_VALUE = Symbol("toolcraft.cli.null");
26
27
  const optionalModulePaths = {
27
- approvals: "./human-in-loop/approvals-commands.js",
28
- humanInLoop: "./human-in-loop/gate.js",
29
28
  mcpProxy: "./mcp-proxy.js"
30
29
  };
31
30
  function importOptionalModule(specifier) {
@@ -42,9 +41,7 @@ function importOptionalModule(specifier) {
42
41
  export async function createCLICommandTreeSnapshot(roots, options = {}) {
43
42
  const argv = [...(options.argv ?? ["node", "toolcraft"])];
44
43
  const normalizedRoot = normalizeRoots(roots, argv);
45
- const root = options.approvals === true
46
- ? (await importOptionalModule(optionalModulePaths.approvals)).mergeApprovalsGroup(normalizedRoot)
47
- : normalizedRoot;
44
+ const root = mergeApprovalsRoot(normalizedRoot, options);
48
45
  const controls = resolveCLIControls(options.controls);
49
46
  const presetsEnabled = options.presets === true;
50
47
  const globalLongOptionFlags = getGlobalLongOptionFlags(presetsEnabled, options.version !== undefined, controls);
@@ -3028,7 +3025,7 @@ function getResolvedFlags(command) {
3028
3025
  const flags = command.optsWithGlobals();
3029
3026
  return flags;
3030
3027
  }
3031
- async function executeCommand(state, services, requirementOptions, runtimeFetch, runtimeOptions, runtimeEnv, runtimeFs, outputEmitter, outputFormats, promptStreams, diagnosticsOptions, onErrorReportContext) {
3028
+ async function executeCommand(state, services, requirementOptions, runtimeFetch, humanInLoop, runtimeEnv, runtimeFs, outputEmitter, outputFormats, promptStreams, diagnosticsOptions, onErrorReportContext) {
3032
3029
  const logger = createLogger(outputEmitter);
3033
3030
  const optionValues = state.actionCommand.optsWithGlobals();
3034
3031
  const resolvedFlags = optionValues;
@@ -3162,8 +3159,8 @@ async function executeCommand(state, services, requirementOptions, runtimeFetch,
3162
3159
  throw new UserError("Operation cancelled.");
3163
3160
  }
3164
3161
  }
3165
- const result = state.command.humanInLoop
3166
- ? await (await importOptionalModule(optionalModulePaths.humanInLoop)).invokeWithHumanInLoop(state.command, context, runtimeOptions, state.commandPath)
3162
+ const result = state.command.humanInLoop && humanInLoop !== undefined
3163
+ ? await humanInLoop.invoke(state.command, context, state.commandPath)
3167
3164
  : await state.command.handler(context);
3168
3165
  if (output === "rich" && runtime.isFixture) {
3169
3166
  writeRichHeader(`${state.command.name} (fixture)`);
@@ -3729,20 +3726,19 @@ export async function runCLI(roots, options = {}) {
3729
3726
  let errorReportContext;
3730
3727
  try {
3731
3728
  const normalizedRoot = normalizeRoots(roots, argv);
3732
- const root = options.approvals === true
3733
- ? (await importOptionalModule(optionalModulePaths.approvals)).mergeApprovalsGroup(normalizedRoot)
3734
- : normalizedRoot;
3729
+ const root = mergeApprovalsRoot(normalizedRoot, options);
3730
+ assertHumanInLoopWired(root, options.humanInLoop);
3735
3731
  if (hasMcpProxyConfig(root)) {
3736
3732
  await (await importOptionalModule(optionalModulePaths.mcpProxy)).resolveMcpProxies(root, { projectRoot: options.projectRoot });
3737
3733
  }
3738
3734
  const casing = options.casing ?? "kebab";
3739
3735
  const services = (options.services ?? {});
3740
- const runtimeOptions = options.humanInLoop ?? {};
3736
+ const humanInLoop = options.humanInLoop;
3741
3737
  const runtimeFetch = options.fetch ?? globalThis.fetch;
3742
3738
  version = options.version ?? findEntrypointPackageMetadata(argv[1])?.version;
3743
3739
  const servicesWithBuiltIns = {
3744
3740
  ...services,
3745
- runtimeOptions,
3741
+ humanInLoop,
3746
3742
  root
3747
3743
  };
3748
3744
  const requirementOptions = {
@@ -3777,7 +3773,7 @@ export async function runCLI(roots, options = {}) {
3777
3773
  const execute = async (state) => {
3778
3774
  lastActionCommand = state.actionCommand;
3779
3775
  resolvedCommandPath = formatCliCommandPath(state.commandPath);
3780
- await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeFetch, runtimeOptions, options.env, options.fs, options.outputEmitter, controls.outputFormats, {
3776
+ await executeCommand(state, servicesWithBuiltIns, requirementOptions, runtimeFetch, humanInLoop, options.env, options.fs, options.outputEmitter, controls.outputFormats, {
3781
3777
  input: options.promptInput,
3782
3778
  output: options.promptOutput
3783
3779
  }, {
@@ -103,7 +103,7 @@
103
103
  },
104
104
  {
105
105
  "name": "toolcraft",
106
- "version": "0.0.121",
106
+ "version": "0.0.123",
107
107
  "license": "MIT"
108
108
  },
109
109
  {
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "name": "toolcraft-schema",
116
- "version": "0.0.121",
116
+ "version": "0.0.123",
117
117
  "license": "MIT"
118
118
  },
119
119
  {
@@ -1,5 +1,7 @@
1
1
  import type { OpenTaskListOptions, TaskList, Tasks } from "@poe-code/task-list";
2
- import type { HumanInLoopPending, HumanInLoopRuntimeOptions } from "./types.js";
2
+ import type { HumanInLoopPending } from "./types.js";
3
+ import type { HumanInLoopRuntimeOptions } from "./runtime-options.js";
4
+ type ApprovalListOptions = Pick<HumanInLoopRuntimeOptions, "taskList" | "listName">;
3
5
  import { type ApprovalPlanValue } from "./plan-hash.js";
4
6
  export interface ApprovalPayload {
5
7
  approvalId?: string;
@@ -14,7 +16,7 @@ export interface ApprovalPayload {
14
16
  result?: unknown;
15
17
  error?: unknown;
16
18
  }
17
- export declare function ensureApprovalList(runtimeOptions: HumanInLoopRuntimeOptions | undefined, deps?: {
19
+ export declare function ensureApprovalList(runtimeOptions: ApprovalListOptions | undefined, deps?: {
18
20
  create?: boolean;
19
21
  openTaskList?: (options: OpenTaskListOptions) => Promise<TaskList>;
20
22
  }): Promise<{
@@ -33,3 +35,4 @@ export declare function loadApproval(ctx: {
33
35
  tasks: Tasks;
34
36
  approvalId: string;
35
37
  }): Promise<ApprovalPayload | undefined>;
38
+ export {};
@@ -1,7 +1,7 @@
1
1
  import type { CommandNode, Group } from "../index.js";
2
- import type { HumanInLoopRuntimeOptions } from "./types.js";
2
+ import type { HumanInLoopRuntimeInstance } from "./runtime-options.js";
3
3
  interface ApprovalBuiltInServices {
4
- runtimeOptions: HumanInLoopRuntimeOptions;
4
+ humanInLoop: HumanInLoopRuntimeInstance;
5
5
  root: CommandNode<any>;
6
6
  }
7
7
  export declare const approvalsGroup: Group<ApprovalBuiltInServices> & {
@@ -25,9 +25,11 @@ export const approvalsGroup = markApprovalsBuiltIn(defineGroup({
25
25
  description: "List queued approvals.",
26
26
  scope: listScope,
27
27
  params: listParams,
28
- handler: async ({ params, runtimeOptions }) => {
28
+ handler: async ({ params, humanInLoop }) => {
29
29
  try {
30
- const { tasks } = await ensureApprovalList(runtimeOptions, { create: false });
30
+ const { tasks } = await ensureApprovalList(humanInLoop.runtimeOptions, {
31
+ create: false
32
+ });
31
33
  return loadApprovals(tasks, params.state);
32
34
  }
33
35
  catch (error) {
@@ -48,9 +50,11 @@ export const approvalsGroup = markApprovalsBuiltIn(defineGroup({
48
50
  description: "Show one approval.",
49
51
  scope: listScope,
50
52
  params: showParams,
51
- handler: async ({ params, runtimeOptions }) => {
53
+ handler: async ({ params, humanInLoop }) => {
52
54
  try {
53
- const { tasks } = await ensureApprovalList(runtimeOptions, { create: false });
55
+ const { tasks } = await ensureApprovalList(humanInLoop.runtimeOptions, {
56
+ create: false
57
+ });
54
58
  return tasks.get(params.approvalId);
55
59
  }
56
60
  catch (error) {
@@ -71,8 +75,8 @@ export const approvalsGroup = markApprovalsBuiltIn(defineGroup({
71
75
  description: "Run one queued approval.",
72
76
  scope: runScope,
73
77
  params: runParams,
74
- handler: async ({ params, runtimeOptions, root }) => {
75
- return runApproval(params.approvalId, runtimeOptions, root);
78
+ handler: async ({ params, humanInLoop, root }) => {
79
+ return runApproval(params.approvalId, humanInLoop.runtimeOptions, root);
76
80
  },
77
81
  render: {
78
82
  rich: (result, primitives) => {
@@ -1,8 +1,8 @@
1
1
  import type { Command, HandlerContext } from "../index.js";
2
2
  import { enqueueApproval } from "./approval-tasks.js";
3
- import type { HumanInLoopPending, HumanInLoopProvider, HumanInLoopRuntimeOptions } from "./types.js";
4
- export declare function resolveProvider(runtimeOptions: HumanInLoopRuntimeOptions | undefined): HumanInLoopProvider;
5
- export declare function invokeWithHumanInLoop<T>(node: Command<any, any, any, T>, ctx: HandlerContext<any, any, any>, runtimeOptions: HumanInLoopRuntimeOptions | undefined, commandPath: string, options?: {
3
+ import type { HumanInLoopPending } from "./types.js";
4
+ import type { HumanInLoopRuntimeOptions } from "./runtime-options.js";
5
+ export declare function invokeWithHumanInLoop<T>(node: Command<any, any, any, T>, ctx: HandlerContext<any, any, any>, runtimeOptions: HumanInLoopRuntimeOptions, commandPath: string, options?: {
6
6
  enqueueApproval?: typeof enqueueApproval;
7
7
  spawnRunner?: boolean;
8
8
  }): Promise<T | HumanInLoopPending>;
@@ -1,26 +1,7 @@
1
1
  import { enqueueApproval, ensureApprovalList } from "./approval-tasks.js";
2
- import { defaultProviderForPlatform } from "./default-provider.js";
3
2
  import { spawnApprovalRunner } from "./spawn.js";
4
3
  import { ApprovalDeclinedError } from "./types.js";
5
4
  import { assertApprovalPlanHash, createApprovalPlan, formatApprovalMessage } from "./plan-hash.js";
6
- const providersByRuntime = new WeakMap();
7
- let providerWithoutRuntime;
8
- export function resolveProvider(runtimeOptions) {
9
- if (runtimeOptions?.provider !== undefined) {
10
- return runtimeOptions.provider;
11
- }
12
- if (runtimeOptions === undefined) {
13
- providerWithoutRuntime ??= defaultProviderForPlatform();
14
- return providerWithoutRuntime;
15
- }
16
- const cachedProvider = providersByRuntime.get(runtimeOptions);
17
- if (cachedProvider !== undefined) {
18
- return cachedProvider;
19
- }
20
- const provider = defaultProviderForPlatform();
21
- providersByRuntime.set(runtimeOptions, provider);
22
- return provider;
23
- }
24
5
  export async function invokeWithHumanInLoop(node, ctx, runtimeOptions, commandPath, options = {}) {
25
6
  if (!node.humanInLoop) {
26
7
  return node.handler(ctx);
@@ -54,8 +35,7 @@ export async function invokeWithHumanInLoop(node, ctx, runtimeOptions, commandPa
54
35
  }
55
36
  return pending;
56
37
  }
57
- const provider = resolveProvider(runtimeOptions);
58
- const result = await provider.requestApproval({
38
+ const result = await runtimeOptions.provider.requestApproval({
59
39
  message,
60
40
  declineInputPrompt: node.humanInLoop.declineInputPrompt
61
41
  });
@@ -1,7 +1,10 @@
1
+ export { createHumanInLoop } from "./runtime.js";
1
2
  export { defaultProviderForPlatform } from "./default-provider.js";
3
+ export { osascriptProvider } from "@poe-code/agent-human-in-loop";
2
4
  export { invokeWithHumanInLoop } from "./gate.js";
3
5
  export { approvalStateMachine } from "./state-machine.js";
4
6
  export { ApprovalDeclinedError } from "./types.js";
5
7
  export type { ApprovalEvent, ApprovalState } from "./state-machine.js";
6
- export type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntimeOptions } from "./types.js";
8
+ export type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntime } from "./types.js";
9
+ export type { HumanInLoopRuntimeInstance, HumanInLoopRuntimeOptions } from "./runtime-options.js";
7
10
  export type { HumanInLoopProvider } from "@poe-code/agent-human-in-loop";
@@ -1,4 +1,6 @@
1
+ export { createHumanInLoop } from "./runtime.js";
1
2
  export { defaultProviderForPlatform } from "./default-provider.js";
3
+ export { osascriptProvider } from "@poe-code/agent-human-in-loop";
2
4
  export { invokeWithHumanInLoop } from "./gate.js";
3
5
  export { approvalStateMachine } from "./state-machine.js";
4
6
  export { ApprovalDeclinedError } from "./types.js";
@@ -1,3 +1,3 @@
1
1
  import type { CommandNode } from "../index.js";
2
- import type { HumanInLoopRuntimeOptions } from "./types.js";
2
+ import type { HumanInLoopRuntimeOptions } from "./runtime-options.js";
3
3
  export declare function runApproval(approvalId: string, runtimeOptions: HumanInLoopRuntimeOptions, root: CommandNode<any>): Promise<void>;
@@ -2,7 +2,6 @@ import { InvalidTransitionError } from "@poe-code/task-list";
2
2
  import { UserError, resolveCommandSecrets } from "../index.js";
3
3
  import { createEnv, createFs } from "../runtime/io.js";
4
4
  import { ensureApprovalList } from "./approval-tasks.js";
5
- import { resolveProvider } from "./gate.js";
6
5
  import { createRuntimeLogger } from "../runtime-logging.js";
7
6
  import { assertApprovalPlanHash, createApprovalPlan, formatApprovalMessage, isApprovalPlanValue } from "./plan-hash.js";
8
7
  const MAX_AVAILABLE_COMMAND_PATHS = 20;
@@ -13,7 +12,7 @@ export async function runApproval(approvalId, runtimeOptions, root) {
13
12
  return;
14
13
  }
15
14
  const approval = readApprovalPayload(task);
16
- const provider = resolveProvider(runtimeOptions);
15
+ const provider = runtimeOptions.provider;
17
16
  try {
18
17
  await tasks.fire(approvalId, "claim", {
19
18
  metadataPatch: {
@@ -0,0 +1,19 @@
1
+ import type { HumanInLoopProvider } from "@poe-code/agent-human-in-loop";
2
+ import type { TaskList } from "@poe-code/task-list";
3
+ import type { HumanInLoopRuntime } from "./types.js";
4
+ /** The concrete runtime built by `createHumanInLoop` — carries its options for the approvals built-ins. */
5
+ export interface HumanInLoopRuntimeInstance extends HumanInLoopRuntime {
6
+ readonly runtimeOptions: HumanInLoopRuntimeOptions;
7
+ }
8
+ export interface HumanInLoopRuntimeOptions {
9
+ provider: HumanInLoopProvider;
10
+ taskList?: TaskList | {
11
+ dir: string;
12
+ format: "markdown-dir" | "yaml-file";
13
+ };
14
+ listName?: string;
15
+ binPath?: {
16
+ execPath: string;
17
+ entryArgs: readonly string[];
18
+ };
19
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { HumanInLoopRuntimeInstance, HumanInLoopRuntimeOptions } from "./runtime-options.js";
2
+ export declare function createHumanInLoop(options: HumanInLoopRuntimeOptions): HumanInLoopRuntimeInstance;
@@ -0,0 +1,14 @@
1
+ import { UserError } from "../user-error.js";
2
+ import { mergeApprovalsGroup } from "./approvals-commands.js";
3
+ import { invokeWithHumanInLoop } from "./gate.js";
4
+ export function createHumanInLoop(options) {
5
+ if (options?.provider === undefined) {
6
+ throw new UserError('createHumanInLoop requires a provider — import one from "toolcraft/human-in-loop" (e.g. osascriptProvider) or pass your own');
7
+ }
8
+ const runtimeOptions = { ...options };
9
+ return {
10
+ runtimeOptions,
11
+ invoke: (node, ctx, commandPath) => invokeWithHumanInLoop(node, ctx, runtimeOptions, commandPath),
12
+ mergeApprovalsGroup
13
+ };
14
+ }
@@ -1,3 +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;
1
+ import type { HumanInLoopRuntimeOptions } from "./runtime-options.js";
2
+ export declare function spawnApprovalRunner(approvalId: string, runtimeOptions: Pick<HumanInLoopRuntimeOptions, "binPath">, spawnFn?: typeof import("node:child_process").spawn): void;
3
3
  export default spawnApprovalRunner;
@@ -1,6 +1,5 @@
1
- import type { HumanInLoopProvider } from "@poe-code/agent-human-in-loop";
2
- import type { TaskList } from "@poe-code/task-list";
3
1
  import type { ObjectSchema, Static } from "toolcraft-schema";
2
+ import type { Command, Group, HandlerContext } from "../index.js";
4
3
  import { UserError } from "../user-error.js";
5
4
  export interface HumanInLoopConfig<TParamsSchema extends ObjectSchema<any>> {
6
5
  mode: "sync" | "async";
@@ -14,17 +13,14 @@ export interface HumanInLoopConfig<TParamsSchema extends ObjectSchema<any>> {
14
13
  }) => unknown | Promise<unknown>;
15
14
  declineInputPrompt?: string;
16
15
  }
17
- export interface HumanInLoopRuntimeOptions {
18
- provider?: HumanInLoopProvider;
19
- taskList?: TaskList | {
20
- dir: string;
21
- format: "markdown-dir" | "yaml-file";
22
- };
23
- listName?: string;
24
- binPath?: {
25
- execPath: string;
26
- entryArgs: readonly string[];
27
- };
16
+ /**
17
+ * The wired human-in-loop runtime. Core entrypoints only know this interface;
18
+ * the implementation ships behind the `toolcraft/human-in-loop` export and is
19
+ * created with `createHumanInLoop({ provider, ... })`.
20
+ */
21
+ export interface HumanInLoopRuntime {
22
+ invoke<T>(node: Command<any, any, any, T>, ctx: HandlerContext<any, any, any>, commandPath: string): Promise<T | HumanInLoopPending>;
23
+ mergeApprovalsGroup<TServices extends object>(root: Group<TServices>): Group<TServices>;
28
24
  }
29
25
  export interface HumanInLoopPending {
30
26
  status: "pending-approval";
@@ -43,4 +39,3 @@ export declare class ApprovalDeclinedError extends UserError {
43
39
  approvalId?: string;
44
40
  });
45
41
  }
46
- export type { HumanInLoopProvider } from "@poe-code/agent-human-in-loop";
@@ -0,0 +1,11 @@
1
+ import type { CommandNode, Group } from "../index.js";
2
+ import type { HumanInLoopRuntime } from "./types.js";
3
+ /**
4
+ * Human-in-loop config on a command is only allowed when a runtime is wired.
5
+ * Called by every entrypoint (CLI, MCP, SDK) after the root is normalized.
6
+ */
7
+ export declare function assertHumanInLoopWired(root: CommandNode<any>, humanInLoop: HumanInLoopRuntime | undefined): void;
8
+ export declare function mergeApprovalsRoot<TServices extends object>(root: Group<TServices>, options: {
9
+ approvals?: boolean;
10
+ humanInLoop?: HumanInLoopRuntime;
11
+ }): Group<TServices>;
@@ -0,0 +1,36 @@
1
+ import { UserError } from "../user-error.js";
2
+ const WIRING_HINT = 'pass { humanInLoop: createHumanInLoop({ provider, ... }) } from "toolcraft/human-in-loop"';
3
+ /**
4
+ * Human-in-loop config on a command is only allowed when a runtime is wired.
5
+ * Called by every entrypoint (CLI, MCP, SDK) after the root is normalized.
6
+ */
7
+ export function assertHumanInLoopWired(root, humanInLoop) {
8
+ if (humanInLoop !== undefined) {
9
+ return;
10
+ }
11
+ const commandPath = findHumanInLoopCommandPath(root, []);
12
+ if (commandPath !== undefined) {
13
+ throw new UserError(`command '${commandPath}' declares humanInLoop but no runtime is wired — ${WIRING_HINT}`);
14
+ }
15
+ }
16
+ export function mergeApprovalsRoot(root, options) {
17
+ if (options.approvals !== true) {
18
+ return root;
19
+ }
20
+ if (options.humanInLoop === undefined) {
21
+ throw new UserError(`approvals: true requires a wired humanInLoop runtime — ${WIRING_HINT}`);
22
+ }
23
+ return options.humanInLoop.mergeApprovalsGroup(root);
24
+ }
25
+ function findHumanInLoopCommandPath(node, path) {
26
+ if (node.kind === "command") {
27
+ return node.humanInLoop ? path.join(".") || node.name : undefined;
28
+ }
29
+ for (const child of node.children) {
30
+ const found = findHumanInLoopCommandPath(child, [...path, child.name]);
31
+ if (found !== undefined) {
32
+ return found;
33
+ }
34
+ }
35
+ return undefined;
36
+ }
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { McpServerConfig } from "@poe-code/agent-mcp-config";
2
2
  import type { AnySchema, ObjectSchema, Static } from "toolcraft-schema";
3
3
  import type { LoggerOutput, RenderTableOptions, ThemePalette } from "toolcraft-design";
4
4
  import { ApprovalDeclinedError } from "./human-in-loop/types.js";
5
- import type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntimeOptions } from "./human-in-loop/types.js";
5
+ import type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntime } from "./human-in-loop/types.js";
6
6
  import { ToolcraftBugError, UserError } from "./user-error.js";
7
7
  import type { RuntimeLogger } from "./runtime-logging.js";
8
8
  import type { StreamStatusEvent, ToolcraftStream } from "./stream.js";
@@ -235,4 +235,4 @@ export type { FileChangeRendererOptions, FileChangeResult } from "./file-change-
235
235
  export type { FileChange, FileChangeDisplayMode, FileChangeKind } from "toolcraft-design";
236
236
  export type { DiagnosticLogEvent, LogLevel, RuntimeLogger, RuntimeLoggerInput } from "./runtime-logging.js";
237
237
  export type { AnySchema, ArraySchema, BooleanSchema, CliMissingParameterChoice, CliMissingParameterContext, CliMissingParameterResolution, CliOutputMode, CliSchemaOptions, EnumSchema, JsonSchema, JsonSchemaDocument, JsonSchemaDocumentOptions, JsonValue, JsonValueSchema, NumberSchema, ObjectSchema, OneOfSchema, OptionalSchema, RecordSchema, SchemaBase, Static, StringSchema, UnionSchema, ValidationIssue, ValidationResult } from "./schema.js";
238
- export type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntimeOptions };
238
+ export type { HumanInLoopConfig, HumanInLoopPending, HumanInLoopRuntime };
@@ -21,7 +21,10 @@ const ignoredOptions = {
21
21
  fetch: globalThis.fetch,
22
22
  tools: ["usage"],
23
23
  casing: "snake",
24
- humanInLoop: {},
24
+ humanInLoop: {
25
+ invoke: async (node, ctx) => node.handler(ctx),
26
+ mergeApprovalsGroup: (root) => root,
27
+ },
25
28
  };
26
29
  const ignoredServer = createMCPServer(ignoredRoot, ignoredOptions);
27
30
  const ignoredServerArray = createMCPServer([ignoredRoot], ignoredOptions);
package/dist/mcp.d.ts CHANGED
@@ -2,7 +2,7 @@ import "./node-require-shim.js";
2
2
  import { type SDKTransport, type Server as TinyServer } from "tiny-stdio-mcp-server";
3
3
  import type { Group, HandlerFs, LogLevel, RuntimeLoggerInput } from "./index.js";
4
4
  import { type ErrorReportsOption } from "./error-report.js";
5
- import { type HumanInLoopRuntimeOptions } from "./human-in-loop/index.js";
5
+ import { type HumanInLoopRuntime } from "./human-in-loop/types.js";
6
6
  type Casing = "snake" | "camel";
7
7
  type CmdkitServer = Omit<TinyServer, "connect"> & {
8
8
  connect(transport: SDKTransport): Promise<void>;
@@ -21,7 +21,7 @@ export interface RunMCPOptions<TServices extends object = Record<string, unknown
21
21
  fs?: HandlerFs;
22
22
  name: string;
23
23
  version?: string;
24
- humanInLoop?: HumanInLoopRuntimeOptions;
24
+ humanInLoop?: HumanInLoopRuntime;
25
25
  projectRoot?: string;
26
26
  logLevel?: LogLevel;
27
27
  logger?: RuntimeLoggerInput;
package/dist/mcp.js CHANGED
@@ -4,8 +4,8 @@ import { toJsonSchema } from "toolcraft-schema";
4
4
  import { createHttpErrorEnvelope, isHttpErrorLike } from "./api-error-summary.js";
5
5
  import { ToolcraftBugError, UserError, assertCommandRequirements, resolveCommandSecrets } from "./index.js";
6
6
  import { writeErrorReport } from "./error-report.js";
7
- import { mergeApprovalsGroup } from "./human-in-loop/approvals-commands.js";
8
- import { ApprovalDeclinedError, invokeWithHumanInLoop } from "./human-in-loop/index.js";
7
+ import { ApprovalDeclinedError } from "./human-in-loop/types.js";
8
+ import { assertHumanInLoopWired, mergeApprovalsRoot } from "./human-in-loop/wiring.js";
9
9
  import { hasMcpProxyGroups, resolveMcpProxies } from "./mcp-proxy.js";
10
10
  import { getExpectedNumberDescription, isValidNumberSchemaValue } from "./number-schema.js";
11
11
  import { findEntrypointPackageMetadata } from "./package-metadata.js";
@@ -717,7 +717,7 @@ function toToolError(error, reportPath) {
717
717
  function createResolvedMCPServer(root, options, runtime = {}) {
718
718
  const casing = options.casing ?? "snake";
719
719
  const services = (options.services ?? {});
720
- const runtimeOptions = options.humanInLoop ?? {};
720
+ const humanInLoop = options.humanInLoop;
721
721
  const runtimeFetch = options.fetch ?? globalThis.fetch;
722
722
  const diagnostics = createRuntimeLogger({
723
723
  level: options.logLevel,
@@ -764,7 +764,7 @@ function createResolvedMCPServer(root, options, runtime = {}) {
764
764
  const baseContext = {
765
765
  ...services,
766
766
  ...requestServices,
767
- runtimeOptions,
767
+ humanInLoop,
768
768
  root,
769
769
  secrets,
770
770
  fetch: runtimeFetch,
@@ -863,7 +863,7 @@ function createResolvedMCPServer(root, options, runtime = {}) {
863
863
  const baseContext = {
864
864
  ...services,
865
865
  ...requestServices,
866
- runtimeOptions,
866
+ humanInLoop,
867
867
  root,
868
868
  secrets,
869
869
  fetch: runtimeFetch,
@@ -879,10 +879,13 @@ function createResolvedMCPServer(root, options, runtime = {}) {
879
879
  env: options.env
880
880
  });
881
881
  params = validateToolArguments(tool.command.params, argumentsValue, casing);
882
- const result = await invokeWithHumanInLoop(tool.command, {
882
+ const handlerContext = {
883
883
  ...baseContext,
884
884
  params
885
- }, runtimeOptions, tool.commandPath);
885
+ };
886
+ const result = humanInLoop === undefined
887
+ ? await tool.command.handler(handlerContext)
888
+ : await humanInLoop.invoke(tool.command, handlerContext, tool.commandPath);
886
889
  if (isHumanInLoopPending(result)) {
887
890
  return renderPendingApproval(result);
888
891
  }
@@ -960,7 +963,8 @@ function createDeferredMCPServer(root, options) {
960
963
  }
961
964
  export function createMCPServer(roots, options) {
962
965
  const normalizedRoot = normalizeRoots(roots);
963
- const root = options.approvals === true ? mergeApprovalsGroup(normalizedRoot) : normalizedRoot;
966
+ const root = mergeApprovalsRoot(normalizedRoot, options);
967
+ assertHumanInLoopWired(root, options.humanInLoop);
964
968
  if (!hasMcpProxyGroups(root)) {
965
969
  return createResolvedMCPServer(root, options);
966
970
  }
@@ -969,14 +973,16 @@ export function createMCPServer(roots, options) {
969
973
  /** @internal */
970
974
  export async function createMCPServerForTransport(roots, options, runtime) {
971
975
  const normalizedRoot = normalizeRoots(roots);
972
- const root = options.approvals === true ? mergeApprovalsGroup(normalizedRoot) : normalizedRoot;
976
+ const root = mergeApprovalsRoot(normalizedRoot, options);
977
+ assertHumanInLoopWired(root, options.humanInLoop);
973
978
  await resolveMcpProxies(root, { projectRoot: options.projectRoot });
974
979
  createResolvedMCPServer(root, options, runtime);
975
980
  }
976
981
  export async function runMCP(roots, options) {
977
982
  enableSourceMaps();
978
983
  const normalizedRoot = normalizeRoots(roots);
979
- const root = options.approvals === true ? mergeApprovalsGroup(normalizedRoot) : normalizedRoot;
984
+ const root = mergeApprovalsRoot(normalizedRoot, options);
985
+ assertHumanInLoopWired(root, options.humanInLoop);
980
986
  await resolveMcpProxies(root, { projectRoot: options.projectRoot });
981
987
  const server = createResolvedMCPServer(root, options);
982
988
  await server.listen();
@@ -95,7 +95,10 @@ const ignoredOptions = {
95
95
  services: {
96
96
  logger: console,
97
97
  },
98
- humanInLoop: {},
98
+ humanInLoop: {
99
+ invoke: async (node, ctx) => node.handler(ctx),
100
+ mergeApprovalsGroup: (root) => root,
101
+ },
99
102
  };
100
103
  const ignoredSdk = createSDK(ignoredRoot, ignoredOptions);
101
104
  const ignoredResult = ignoredSdk.poeCode.generate.text({
package/dist/sdk.d.ts CHANGED
@@ -2,7 +2,7 @@ import "./node-require-shim.js";
2
2
  import type { ObjectSchema, Static } from "toolcraft-schema";
3
3
  import type { Group, HandlerFs, LogLevel, RuntimeLoggerInput, Scope } from "./index.js";
4
4
  import { type ErrorReportsOption } from "./error-report.js";
5
- import type { HumanInLoopPending, HumanInLoopRuntimeOptions } from "./human-in-loop/index.js";
5
+ import type { HumanInLoopPending, HumanInLoopRuntime } from "./human-in-loop/types.js";
6
6
  import { type ValidationError } from "./validation-errors.js";
7
7
  import type { StreamConsumerOptions, ToolcraftStream } from "./stream.js";
8
8
  type ScopeInput = readonly Scope[] | undefined;
@@ -70,7 +70,7 @@ export interface CreateSDKOptions<TServices extends object = Record<string, unkn
70
70
  fs?: HandlerFs;
71
71
  services?: TServices;
72
72
  casing?: "camel";
73
- humanInLoop?: HumanInLoopRuntimeOptions;
73
+ humanInLoop?: HumanInLoopRuntime;
74
74
  apiVersion?: string;
75
75
  projectRoot?: string;
76
76
  errorReports?: ErrorReportsOption;
package/dist/sdk.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import "./node-require-shim.js";
2
2
  import { ToolcraftBugError, UserError, assertCommandRequirements, resolveCommandSecrets } from "./index.js";
3
3
  import { writeErrorReport } from "./error-report.js";
4
- import { mergeApprovalsGroup } from "./human-in-loop/approvals-commands.js";
5
- import { invokeWithHumanInLoop } from "./human-in-loop/index.js";
4
+ import { assertHumanInLoopWired, mergeApprovalsRoot } from "./human-in-loop/wiring.js";
6
5
  import { hasMcpProxyGroups, resolveMcpProxies } from "./mcp-proxy.js";
7
6
  import { getExpectedNumberDescription, isValidNumberSchemaValue } from "./number-schema.js";
8
7
  import { filterSchemaForScope } from "./schema-scope.js";
@@ -310,7 +309,8 @@ function defineMember(target, key, value) {
310
309
  }
311
310
  export function createSDK(root, options = {}) {
312
311
  enableSourceMaps();
313
- const mergedRoot = options.approvals === true ? mergeApprovalsGroup(root) : root;
312
+ const mergedRoot = mergeApprovalsRoot(root, options);
313
+ assertHumanInLoopWired(mergedRoot, options.humanInLoop);
314
314
  if (!hasMcpProxyGroups(mergedRoot)) {
315
315
  return createResolvedSDK(mergedRoot, options);
316
316
  }
@@ -318,7 +318,7 @@ export function createSDK(root, options = {}) {
318
318
  }
319
319
  function createResolvedSDK(root, options = {}) {
320
320
  const services = options.services ?? {};
321
- const runtimeOptions = options.humanInLoop ?? {};
321
+ const humanInLoop = options.humanInLoop;
322
322
  const runtimeFetch = options.fetch ?? globalThis.fetch;
323
323
  const diagnostics = createRuntimeLogger({
324
324
  level: options.logLevel,
@@ -344,7 +344,7 @@ function createResolvedSDK(root, options = {}) {
344
344
  secrets = resolveCommandSecrets(node, options.env);
345
345
  const baseContext = {
346
346
  ...services,
347
- runtimeOptions,
347
+ humanInLoop,
348
348
  root,
349
349
  secrets,
350
350
  fetch: runtimeFetch,
@@ -395,7 +395,7 @@ function createResolvedSDK(root, options = {}) {
395
395
  secrets = resolveCommandSecrets(node, options.env);
396
396
  const baseContext = {
397
397
  ...services,
398
- runtimeOptions,
398
+ humanInLoop,
399
399
  root,
400
400
  secrets,
401
401
  fetch: runtimeFetch,
@@ -415,10 +415,13 @@ function createResolvedSDK(root, options = {}) {
415
415
  throw new ToolcraftBugError(`command "${node.name}" must define an object params schema for SDK.`);
416
416
  }
417
417
  validatedParams = validateSDKArguments(paramsSchema, params);
418
- return await invokeWithHumanInLoop(node, {
418
+ const handlerContext = {
419
419
  ...baseContext,
420
420
  params: validatedParams
421
- }, runtimeOptions, commandPath);
421
+ };
422
+ return humanInLoop === undefined
423
+ ? await node.handler(handlerContext)
424
+ : await humanInLoop.invoke(node, handlerContext, commandPath);
422
425
  }
423
426
  catch (error) {
424
427
  await writeErrorReport({
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { parseDocument, stringify } from "yaml";
3
- import taskSchema from "../schema/task.schema.json" with { type: "json" };
3
+ import { TASK_SCHEMA_ID } from "../schema/ids.js";
4
4
  import { eventsFromState, findEvent } from "../state-machine.js";
5
5
  import { resolveStateMachine } from "../state.js";
6
6
  import { AnchorNotFoundError, InvalidTransitionError, MalformedTaskError, OrderMismatchError, TaskAlreadyExistsError, TaskNotFoundError } from "../types.js";
@@ -9,7 +9,6 @@ const ARCHIVE_DIRECTORY_NAME = "archive";
9
9
  const MARKDOWN_EXTENSION = ".md";
10
10
  const TASK_KIND = "task";
11
11
  const TASK_VERSION = 1;
12
- const TASK_SCHEMA_ID = taskSchema.$id;
13
12
  const MIN_PREFIX_WIDTH = 2;
14
13
  const RESERVED_FRONTMATTER_KEYS = new Set([
15
14
  "$schema",
@@ -1,16 +1,13 @@
1
1
  import path from "node:path";
2
2
  import { isMap, parseDocument } from "yaml";
3
- import storeSchema from "../schema/store.schema.json" with { type: "json" };
4
- import taskSchema from "../schema/task.schema.json" with { type: "json" };
3
+ import { STORE_SCHEMA_ID, TASK_SCHEMA_ID } from "../schema/ids.js";
5
4
  import { eventsFromState, findEvent } from "../state-machine.js";
6
5
  import { resolveStateMachine } from "../state.js";
7
6
  import { AnchorNotFoundError, InvalidTransitionError, MalformedTaskError, OrderMismatchError, TaskAlreadyExistsError, TaskNotFoundError } from "../types.js";
8
7
  import { applyOrder, isTrimmedPrintableIdentifier, isRecord, rejectSymbolicLinkComponents, sortStrings, statIfExists, validateTaskId, validateTaskName, withFileLock, writeAtomically } from "./utils.js";
9
8
  const STORE_KIND = "task-store";
10
- const STORE_SCHEMA_ID = storeSchema.$id;
11
9
  const STORE_VERSION = 1;
12
10
  const TASK_KIND = "task";
13
- const TASK_SCHEMA_ID = taskSchema.$id;
14
11
  const TASK_VERSION = 1;
15
12
  const RESERVED_TASK_KEYS = new Set([
16
13
  "$schema",
@@ -0,0 +1,2 @@
1
+ export declare const STORE_SCHEMA_ID = "https://poe-platform.github.io/poe-code/schemas/task-list/store.schema.json";
2
+ export declare const TASK_SCHEMA_ID = "https://poe-platform.github.io/poe-code/schemas/task-list/task.schema.json";
@@ -0,0 +1,6 @@
1
+ // Kept in sync with the sibling *.schema.json files by ids.test.ts. Inlined as
2
+ // constants so every artifact surface (tsc dist, esbuild bundles) works without
3
+ // runtime file lookups, and without JSON import attributes — a syntax error
4
+ // before Node 18.20 despite the >=18.18 engines floor (#517).
5
+ export const STORE_SCHEMA_ID = "https://poe-platform.github.io/poe-code/schemas/task-list/store.schema.json";
6
+ export const TASK_SCHEMA_ID = "https://poe-platform.github.io/poe-code/schemas/task-list/task.schema.json";
@@ -1,10 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync } from "node:fs";
3
+ import { createRequire } from "node:module";
3
4
  import { parseArgs } from "node:util";
4
5
  import { pathToFileURL } from "node:url";
5
6
  import { createHttpServer } from "./http-server.js";
6
7
  import { loadOAuthVerifier } from "./load-oauth-verifier.js";
7
- import packageJson from "../package.json" with { type: "json" };
8
+ // createRequire instead of JSON import attributes: `with { type: "json" }` is a
9
+ // syntax error before Node 18.20, and engines declares >=18.18 (#517).
10
+ const require = createRequire(import.meta.url);
11
+ const packageJson = require("../package.json");
8
12
  function readPackageInfo() {
9
13
  return {
10
14
  name: packageJson.name,
@@ -8,7 +8,7 @@
8
8
  },
9
9
  {
10
10
  "name": "toolcraft-schema",
11
- "version": "0.0.121",
11
+ "version": "0.0.123",
12
12
  "license": "MIT"
13
13
  }
14
14
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft-schema",
3
- "version": "0.0.121",
3
+ "version": "0.0.123",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.121",
3
+ "version": "0.0.123",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,10 +22,6 @@
22
22
  "types": "./dist/agent-defs.d.ts",
23
23
  "import": "./dist/agent-defs.js"
24
24
  },
25
- "./agent-human-in-loop": {
26
- "types": "./dist/agent-human-in-loop.d.ts",
27
- "import": "./dist/agent-human-in-loop.js"
28
- },
29
25
  "./agent-mcp-config": {
30
26
  "types": "./dist/agent-mcp-config.d.ts",
31
27
  "import": "./dist/agent-mcp-config.js"
@@ -94,10 +90,6 @@
94
90
  "types": "./dist/testing/index.d.ts",
95
91
  "import": "./dist/testing/index.js"
96
92
  },
97
- "./task-list": {
98
- "types": "./dist/task-list.d.ts",
99
- "import": "./dist/task-list.js"
100
- },
101
93
  "./tiny-mcp-client": {
102
94
  "types": "./dist/tiny-mcp-client.d.ts",
103
95
  "import": "./dist/tiny-mcp-client.js"
@@ -160,7 +152,7 @@
160
152
  "yaml"
161
153
  ],
162
154
  "optionalDependencies": {
163
- "toolcraft-schema": "0.0.121",
155
+ "toolcraft-schema": "0.0.123",
164
156
  "toolcraft-design": "*",
165
157
  "@poe-code/frontmatter": "*",
166
158
  "@poe-code/agent-mcp-config": "*",
@@ -1 +0,0 @@
1
- export * from "@poe-code/agent-human-in-loop";
@@ -1 +0,0 @@
1
- export * from "@poe-code/agent-human-in-loop";
@@ -1 +0,0 @@
1
- export * from "@poe-code/task-list";
package/dist/task-list.js DELETED
@@ -1 +0,0 @@
1
- export * from "@poe-code/task-list";
@@ -1,32 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://poe-platform.github.io/poe-code/schemas/task-list/store.schema.json",
4
- "title": "Task Store",
5
- "description": "YAML multi-list task store.",
6
- "type": "object",
7
- "properties": {
8
- "$schema": {
9
- "type": "string",
10
- "const": "https://poe-platform.github.io/poe-code/schemas/task-list/store.schema.json"
11
- },
12
- "kind": {
13
- "type": "string",
14
- "const": "task-store"
15
- },
16
- "version": {
17
- "type": "integer",
18
- "const": 1
19
- },
20
- "lists": {
21
- "type": "object",
22
- "additionalProperties": {
23
- "type": "object",
24
- "additionalProperties": {
25
- "$ref": "./task.schema.json"
26
- }
27
- }
28
- }
29
- },
30
- "required": ["$schema", "kind", "version", "lists"],
31
- "additionalProperties": false
32
- }
@@ -1,33 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://poe-platform.github.io/poe-code/schemas/task-list/task.schema.json",
4
- "title": "Task",
5
- "description": "Persisted task payload used by task-list backends.",
6
- "type": "object",
7
- "properties": {
8
- "$schema": {
9
- "type": "string",
10
- "const": "https://poe-platform.github.io/poe-code/schemas/task-list/task.schema.json"
11
- },
12
- "kind": {
13
- "type": "string",
14
- "const": "task"
15
- },
16
- "version": {
17
- "type": "integer",
18
- "const": 1
19
- },
20
- "name": {
21
- "type": "string",
22
- "minLength": 1
23
- },
24
- "state": {
25
- "type": "string"
26
- },
27
- "description": {
28
- "type": "string"
29
- }
30
- },
31
- "required": ["name", "state"],
32
- "additionalProperties": true
33
- }