toolcraft 0.0.93 → 0.0.95
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 +74 -4
- package/composition.json +1 -1
- package/dist/agent-defs.d.ts +1 -0
- package/dist/agent-defs.js +1 -0
- package/dist/agent-human-in-loop.d.ts +1 -0
- package/dist/agent-human-in-loop.js +1 -0
- package/dist/agent-mcp-config.d.ts +1 -0
- package/dist/agent-mcp-config.js +1 -0
- package/dist/auth-store.d.ts +1 -0
- package/dist/auth-store.js +1 -0
- package/dist/cli.d.ts +4 -1
- package/dist/cli.js +24 -65
- package/dist/composition.json +1 -1
- package/dist/config-mutations.d.ts +1 -0
- package/dist/config-mutations.js +1 -0
- package/dist/frontmatter.d.ts +1 -0
- package/dist/frontmatter.js +1 -0
- package/dist/human-in-loop/gate.d.ts +5 -1
- package/dist/human-in-loop/gate.js +10 -8
- package/dist/human-in-loop/runner.js +1 -28
- package/dist/mcp.d.ts +4 -1
- package/dist/mcp.js +8 -51
- package/dist/process-runner.d.ts +1 -0
- package/dist/process-runner.js +1 -0
- package/dist/runtime/io.d.ts +5 -0
- package/dist/runtime/io.js +50 -0
- package/dist/sdk.d.ts +12 -8
- package/dist/sdk.js +7 -52
- package/dist/task-list.d.ts +1 -0
- package/dist/task-list.js +1 -0
- package/dist/testing/fakes.d.ts +20 -0
- package/dist/testing/fakes.js +83 -0
- package/dist/testing/fixtures.d.ts +8 -0
- package/dist/testing/fixtures.js +248 -0
- package/dist/testing/harness.d.ts +76 -0
- package/dist/testing/harness.js +391 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +3 -0
- package/dist/testing/memory-fs.d.ts +11 -0
- package/dist/testing/memory-fs.js +61 -0
- package/dist/testing/parity.d.ts +25 -0
- package/dist/testing/parity.js +384 -0
- package/dist/testing/render-capture.d.ts +6 -0
- package/dist/testing/render-capture.js +54 -0
- package/dist/tiny-mcp-client.d.ts +1 -0
- package/dist/tiny-mcp-client.js +1 -0
- package/node_modules/@poe-code/agent-defs/package.json +2 -0
- package/node_modules/@poe-code/agent-human-in-loop/README.md +12 -2
- package/node_modules/@poe-code/agent-human-in-loop/package.json +2 -0
- package/node_modules/@poe-code/agent-mcp-config/README.md +7 -7
- package/node_modules/@poe-code/agent-mcp-config/package.json +2 -0
- package/node_modules/@poe-code/config-mutations/README.md +8 -8
- package/node_modules/@poe-code/config-mutations/package.json +2 -0
- package/node_modules/@poe-code/frontmatter/README.md +2 -2
- package/node_modules/@poe-code/frontmatter/package.json +2 -0
- package/node_modules/@poe-code/process-runner/README.md +1 -1
- package/node_modules/@poe-code/process-runner/package.json +2 -0
- package/node_modules/@poe-code/task-list/README.md +15 -3
- package/node_modules/@poe-code/task-list/package.json +2 -0
- package/node_modules/auth-store/README.md +15 -0
- package/node_modules/auth-store/package.json +2 -0
- package/node_modules/tiny-mcp-client/README.md +36 -36
- package/node_modules/tiny-mcp-client/package.json +2 -0
- package/node_modules/toolcraft-design/README.md +8 -4
- package/node_modules/toolcraft-design/dist/dashboard/terminal.js +48 -6
- package/node_modules/toolcraft-design/dist/explorer/actions.d.ts +4 -0
- package/node_modules/toolcraft-design/dist/explorer/actions.js +1 -0
- package/node_modules/toolcraft-design/dist/explorer/events.d.ts +4 -0
- package/node_modules/toolcraft-design/dist/explorer/reducer.js +52 -4
- package/node_modules/toolcraft-design/dist/explorer/render/list.js +47 -20
- package/node_modules/toolcraft-design/dist/explorer/render/modal.js +13 -1
- package/node_modules/toolcraft-design/dist/explorer/render/test-fixtures.js +1 -1
- package/node_modules/toolcraft-design/dist/explorer/runtime.js +3 -0
- package/node_modules/toolcraft-design/dist/explorer/state.d.ts +9 -0
- package/node_modules/toolcraft-design/package.json +5 -0
- package/package.json +42 -2
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { McpClient, McpError, ERROR_INVALID_PARAMS, createSdkTestPair } from "tiny-mcp-client";
|
|
3
|
+
import { runCLI } from "../cli.js";
|
|
4
|
+
import { UserError } from "../index.js";
|
|
5
|
+
import { createMCPServer } from "../mcp.js";
|
|
6
|
+
import { filterSchemaForScope } from "../schema-scope.js";
|
|
7
|
+
import { createSDK } from "../sdk.js";
|
|
8
|
+
class SurfaceScopeError extends Error {
|
|
9
|
+
constructor(surface, path) {
|
|
10
|
+
super(`Command "${path.join(" ")}" is filtered out of the ${surface} surface.`);
|
|
11
|
+
this.name = "SurfaceScopeError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function splitWords(value) {
|
|
15
|
+
const words = [];
|
|
16
|
+
let current = "";
|
|
17
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
18
|
+
const char = value[index] ?? "";
|
|
19
|
+
const lower = char.toLowerCase();
|
|
20
|
+
const upper = char.toUpperCase();
|
|
21
|
+
const isSeparator = char === "-" || char === "_" || char === " " || char === ".";
|
|
22
|
+
if (isSeparator) {
|
|
23
|
+
if (current.length > 0) {
|
|
24
|
+
words.push(current.toLowerCase());
|
|
25
|
+
current = "";
|
|
26
|
+
}
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const isUppercase = char !== lower && char === upper;
|
|
30
|
+
const previous = value[index - 1];
|
|
31
|
+
const next = value[index + 1];
|
|
32
|
+
const previousIsLowercase = previous !== undefined &&
|
|
33
|
+
previous === previous.toLowerCase() &&
|
|
34
|
+
previous !== previous.toUpperCase();
|
|
35
|
+
const nextIsLowercase = next !== undefined && next === next.toLowerCase() && next !== next.toUpperCase();
|
|
36
|
+
if (isUppercase && current.length > 0 && (previousIsLowercase || nextIsLowercase)) {
|
|
37
|
+
words.push(current.toLowerCase());
|
|
38
|
+
current = char;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
current += char;
|
|
42
|
+
}
|
|
43
|
+
if (current.length > 0) {
|
|
44
|
+
words.push(current.toLowerCase());
|
|
45
|
+
}
|
|
46
|
+
return words;
|
|
47
|
+
}
|
|
48
|
+
function formatName(value, casing) {
|
|
49
|
+
const words = splitWords(value);
|
|
50
|
+
if (casing === "camel") {
|
|
51
|
+
return words
|
|
52
|
+
.map((word, index) => index === 0 ? word : `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`)
|
|
53
|
+
.join("");
|
|
54
|
+
}
|
|
55
|
+
return words.join(casing === "kebab" ? "-" : "_");
|
|
56
|
+
}
|
|
57
|
+
function unwrapOptional(schema) {
|
|
58
|
+
return schema.kind === "optional" ? unwrapOptional(schema.inner) : schema;
|
|
59
|
+
}
|
|
60
|
+
function mapUnknownMCPObject(value) {
|
|
61
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [formatName(key, "snake"), child]));
|
|
62
|
+
}
|
|
63
|
+
function filterParams(schema, params, surface) {
|
|
64
|
+
const filtered = filterSchemaForScope(schema, surface);
|
|
65
|
+
if (filtered?.kind !== "object") {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
return Object.fromEntries(Object.entries(filtered.shape).flatMap(([key, childSchema]) => {
|
|
69
|
+
const value = params[key];
|
|
70
|
+
if (value === undefined) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const unwrapped = unwrapOptional(childSchema);
|
|
74
|
+
if (unwrapped.kind === "object" &&
|
|
75
|
+
typeof value === "object" &&
|
|
76
|
+
value !== null &&
|
|
77
|
+
!Array.isArray(value)) {
|
|
78
|
+
return [[key, filterParams(unwrapped, value, surface)]];
|
|
79
|
+
}
|
|
80
|
+
return [[key, value]];
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
function mapMCPValue(schema, value) {
|
|
84
|
+
const unwrapped = unwrapOptional(schema);
|
|
85
|
+
if (value === null || value === undefined) {
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
switch (unwrapped.kind) {
|
|
89
|
+
case "array":
|
|
90
|
+
return Array.isArray(value) ? value.map((item) => mapMCPValue(unwrapped.item, item)) : value;
|
|
91
|
+
case "object": {
|
|
92
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => {
|
|
96
|
+
const childSchema = unwrapped.shape[key];
|
|
97
|
+
return [
|
|
98
|
+
formatName(key, "snake"),
|
|
99
|
+
childSchema === undefined ? child : mapMCPValue(childSchema, child)
|
|
100
|
+
];
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
case "record":
|
|
104
|
+
return typeof value === "object" && !Array.isArray(value)
|
|
105
|
+
? Object.fromEntries(Object.entries(value).map(([key, child]) => [key, mapMCPValue(unwrapped.value, child)]))
|
|
106
|
+
: value;
|
|
107
|
+
case "oneOf": {
|
|
108
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
const objectValue = value;
|
|
112
|
+
const discriminator = objectValue[unwrapped.discriminator];
|
|
113
|
+
const branch = typeof discriminator === "string" ? unwrapped.branches[discriminator] : undefined;
|
|
114
|
+
return branch === undefined
|
|
115
|
+
? mapUnknownMCPObject(objectValue)
|
|
116
|
+
: mapMCPValue(branch, objectValue);
|
|
117
|
+
}
|
|
118
|
+
case "union": {
|
|
119
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
const objectValue = value;
|
|
123
|
+
const branch = unwrapped.branches.find((candidate) => Object.keys(candidate.shape).every((key) => candidate.shape[key]?.kind === "optional" ||
|
|
124
|
+
Object.prototype.hasOwnProperty.call(objectValue, key)));
|
|
125
|
+
return branch === undefined
|
|
126
|
+
? mapUnknownMCPObject(objectValue)
|
|
127
|
+
: mapMCPValue(branch, objectValue);
|
|
128
|
+
}
|
|
129
|
+
case "boolean":
|
|
130
|
+
case "enum":
|
|
131
|
+
case "json":
|
|
132
|
+
case "number":
|
|
133
|
+
case "string":
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function success(value) {
|
|
138
|
+
return { ok: true, value, error: undefined };
|
|
139
|
+
}
|
|
140
|
+
function failure(error) {
|
|
141
|
+
return { ok: false, value: undefined, error };
|
|
142
|
+
}
|
|
143
|
+
async function runSDK(root, resolved, params, options) {
|
|
144
|
+
if (!resolved.command.scope.includes("sdk")) {
|
|
145
|
+
return failure(new SurfaceScopeError("sdk", resolved.path));
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
let member = createSDK(root, options);
|
|
149
|
+
for (const segment of resolved.path) {
|
|
150
|
+
member = member[formatName(segment, "camel")];
|
|
151
|
+
}
|
|
152
|
+
if (typeof member !== "function") {
|
|
153
|
+
throw new Error(`SDK command "${resolved.path.join(".")}" is not callable.`);
|
|
154
|
+
}
|
|
155
|
+
return success(await member(filterParams(resolved.command.params, params, "sdk")));
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
return failure(error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function parseMcpValue(result, reference) {
|
|
162
|
+
if (result.structuredContent !== undefined) {
|
|
163
|
+
return result.structuredContent;
|
|
164
|
+
}
|
|
165
|
+
const text = result.content
|
|
166
|
+
.filter((item) => item.type === "text" && item.text !== undefined)
|
|
167
|
+
.map((item) => item.text)
|
|
168
|
+
.join("\n");
|
|
169
|
+
if (text.length === 0) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
if (reference?.ok === true && isDeepStrictEqual(text, reference.value)) {
|
|
173
|
+
return text;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
return JSON.parse(text);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return text;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function runMCP(root, resolved, params, options, reference) {
|
|
183
|
+
if (!resolved.command.scope.includes("mcp")) {
|
|
184
|
+
return failure(new SurfaceScopeError("mcp", resolved.path));
|
|
185
|
+
}
|
|
186
|
+
const server = createMCPServer(root, {
|
|
187
|
+
...options,
|
|
188
|
+
name: `${root.name}-parity`,
|
|
189
|
+
version: "0.0.0",
|
|
190
|
+
casing: "snake"
|
|
191
|
+
});
|
|
192
|
+
const client = new McpClient({
|
|
193
|
+
clientInfo: { name: "toolcraft-parity", version: "0.0.0" }
|
|
194
|
+
});
|
|
195
|
+
const pair = await createSdkTestPair(server, () => client);
|
|
196
|
+
try {
|
|
197
|
+
const toolName = [root.name, ...resolved.path]
|
|
198
|
+
.map((segment) => formatName(segment, "snake"))
|
|
199
|
+
.join("__");
|
|
200
|
+
const argumentsValue = mapMCPValue(resolved.command.params, filterParams(resolved.command.params, params, "mcp"));
|
|
201
|
+
const result = await client.callTool({ name: toolName, arguments: argumentsValue });
|
|
202
|
+
return success(parseMcpValue(result, reference));
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
if (error instanceof McpError && error.code === ERROR_INVALID_PARAMS) {
|
|
206
|
+
return failure(new UserError(error.message));
|
|
207
|
+
}
|
|
208
|
+
return failure(error);
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
await pair.cleanup();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function valueAtPath(value, path) {
|
|
215
|
+
return path.reduce((current, segment) => typeof current === "object" && current !== null
|
|
216
|
+
? current[segment]
|
|
217
|
+
: undefined, value);
|
|
218
|
+
}
|
|
219
|
+
function appendCLIValue(argv, flag, value) {
|
|
220
|
+
if (typeof value === "boolean") {
|
|
221
|
+
argv.push(value ? flag : `--no-${flag.slice(2)}`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (Array.isArray(value)) {
|
|
225
|
+
argv.push(flag, ...value.map((item) => String(item)));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
argv.push(flag, typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
229
|
+
}
|
|
230
|
+
function appendCLIFlags(argv, schema, params, positionals, sourcePath = [], flagPath = []) {
|
|
231
|
+
for (const [key, rawSchema] of Object.entries(schema.shape)) {
|
|
232
|
+
const nextSourcePath = [...sourcePath, key];
|
|
233
|
+
const nextFlagPath = [...flagPath, formatName(key, "kebab")];
|
|
234
|
+
const displayPath = nextSourcePath.join(".");
|
|
235
|
+
const value = params[key];
|
|
236
|
+
if (value === undefined || positionals.has(displayPath)) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const childSchema = unwrapOptional(rawSchema);
|
|
240
|
+
if (childSchema.kind === "object" &&
|
|
241
|
+
typeof value === "object" &&
|
|
242
|
+
value !== null &&
|
|
243
|
+
!Array.isArray(value)) {
|
|
244
|
+
appendCLIFlags(argv, childSchema, value, positionals, nextSourcePath, nextFlagPath);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (childSchema.kind === "record" &&
|
|
248
|
+
typeof value === "object" &&
|
|
249
|
+
value !== null &&
|
|
250
|
+
!Array.isArray(value)) {
|
|
251
|
+
for (const [recordKey, recordValue] of Object.entries(value)) {
|
|
252
|
+
appendCLIValue(argv, `--${[...nextFlagPath, recordKey].join(".")}`, recordValue);
|
|
253
|
+
}
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
appendCLIValue(argv, `--${nextFlagPath.join(".")}`, value);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function buildCLIArgv(root, resolved, params) {
|
|
260
|
+
const visibleParams = filterParams(resolved.command.params, params, "cli");
|
|
261
|
+
const argv = ["node", root.name, ...resolved.path.map((segment) => formatName(segment, "kebab"))];
|
|
262
|
+
for (const positional of resolved.command.positional) {
|
|
263
|
+
const value = valueAtPath(visibleParams, positional.split("."));
|
|
264
|
+
if (Array.isArray(value)) {
|
|
265
|
+
argv.push(...value.map((item) => String(item)));
|
|
266
|
+
}
|
|
267
|
+
else if (value !== undefined) {
|
|
268
|
+
argv.push(String(value));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
appendCLIFlags(argv, filterSchemaForScope(resolved.command.params, "cli"), visibleParams, new Set(resolved.command.positional));
|
|
272
|
+
argv.push("--output", "json", "--debug");
|
|
273
|
+
return argv;
|
|
274
|
+
}
|
|
275
|
+
function parseCLIValue(entries, reference) {
|
|
276
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
277
|
+
const entry = entries[index];
|
|
278
|
+
if (entry === undefined) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
const parsed = JSON.parse(entry);
|
|
283
|
+
if (reference?.ok !== true || isDeepStrictEqual(parsed, reference.value)) {
|
|
284
|
+
return parsed;
|
|
285
|
+
}
|
|
286
|
+
if (typeof parsed === "object" &&
|
|
287
|
+
parsed !== null &&
|
|
288
|
+
!Array.isArray(parsed) &&
|
|
289
|
+
Object.keys(parsed).length === 1 &&
|
|
290
|
+
Object.prototype.hasOwnProperty.call(parsed, "result") &&
|
|
291
|
+
isDeepStrictEqual(parsed.result, reference.value)) {
|
|
292
|
+
return reference.value;
|
|
293
|
+
}
|
|
294
|
+
if ((reference.value === null || reference.value === undefined) &&
|
|
295
|
+
isDeepStrictEqual(parsed, { ok: true })) {
|
|
296
|
+
return reference.value;
|
|
297
|
+
}
|
|
298
|
+
return parsed;
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return entries.at(-1);
|
|
305
|
+
}
|
|
306
|
+
function parseCLIError(entries) {
|
|
307
|
+
const message = entries.at(-1) ?? "CLI command failed.";
|
|
308
|
+
const lines = message.split("\n");
|
|
309
|
+
const lastLine = lines.at(-1) ?? "";
|
|
310
|
+
if (lastLine.startsWith("Run ") && lastLine.endsWith(" --help for usage.")) {
|
|
311
|
+
lines.pop();
|
|
312
|
+
}
|
|
313
|
+
return new UserError(lines.join("\n"));
|
|
314
|
+
}
|
|
315
|
+
async function runCLISurface(root, resolved, params, options, reference) {
|
|
316
|
+
if (!resolved.command.scope.includes("cli")) {
|
|
317
|
+
return failure(new SurfaceScopeError("cli", resolved.path));
|
|
318
|
+
}
|
|
319
|
+
const entries = [];
|
|
320
|
+
const previousExitCode = process.exitCode;
|
|
321
|
+
process.exitCode = undefined;
|
|
322
|
+
try {
|
|
323
|
+
await runCLI(root, {
|
|
324
|
+
...options,
|
|
325
|
+
argv: buildCLIArgv(root, resolved, params),
|
|
326
|
+
controls: { debug: true, output: true },
|
|
327
|
+
outputEmitter: (entry) => entries.push(entry)
|
|
328
|
+
});
|
|
329
|
+
if (process.exitCode !== undefined && process.exitCode !== 0) {
|
|
330
|
+
return failure(parseCLIError(entries));
|
|
331
|
+
}
|
|
332
|
+
return success(parseCLIValue(entries, reference));
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
return failure(error);
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
process.exitCode = previousExitCode;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function errorIdentity(error) {
|
|
342
|
+
if (error instanceof Error) {
|
|
343
|
+
return { name: error.constructor.name, message: error.message };
|
|
344
|
+
}
|
|
345
|
+
return { name: typeof error, message: String(error) };
|
|
346
|
+
}
|
|
347
|
+
function outcomesAgree(left, right) {
|
|
348
|
+
if (left.ok !== right.ok) {
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
if (left.ok) {
|
|
352
|
+
return isDeepStrictEqual(left.value, right.value);
|
|
353
|
+
}
|
|
354
|
+
return isDeepStrictEqual(errorIdentity(left.error), errorIdentity(right.error));
|
|
355
|
+
}
|
|
356
|
+
function describeOutcome(surface, outcome) {
|
|
357
|
+
if (outcome.ok) {
|
|
358
|
+
return `${surface}: ok ${JSON.stringify(outcome.value)}`;
|
|
359
|
+
}
|
|
360
|
+
const error = errorIdentity(outcome.error);
|
|
361
|
+
return `${surface}: error ${error.name}: ${error.message}`;
|
|
362
|
+
}
|
|
363
|
+
export async function runParity(root, resolved, params, options) {
|
|
364
|
+
const sdk = await runSDK(root, resolved, params, options);
|
|
365
|
+
const mcp = await runMCP(root, resolved, params, options, sdk);
|
|
366
|
+
const cli = await runCLISurface(root, resolved, params, options, sdk);
|
|
367
|
+
const agree = outcomesAgree(sdk, mcp) && outcomesAgree(sdk, cli);
|
|
368
|
+
return {
|
|
369
|
+
sdk,
|
|
370
|
+
mcp,
|
|
371
|
+
cli,
|
|
372
|
+
agree,
|
|
373
|
+
...(agree
|
|
374
|
+
? {}
|
|
375
|
+
: {
|
|
376
|
+
diff: [
|
|
377
|
+
`Surface outcomes differ for "${resolved.path.join(" ")}".`,
|
|
378
|
+
describeOutcome("sdk", sdk),
|
|
379
|
+
describeOutcome("mcp", mcp),
|
|
380
|
+
describeOutcome("cli", cli)
|
|
381
|
+
].join("\n")
|
|
382
|
+
})
|
|
383
|
+
};
|
|
384
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createLogger, light, renderTable, stripAnsi, withOutputFormat } from "toolcraft-design";
|
|
2
|
+
const captureWidth = 80;
|
|
3
|
+
function withoutColors(theme) {
|
|
4
|
+
return {
|
|
5
|
+
header: (text) => stripAnsi(theme.header(text)),
|
|
6
|
+
divider: (text) => stripAnsi(theme.divider(text)),
|
|
7
|
+
prompt: (text) => stripAnsi(theme.prompt(text)),
|
|
8
|
+
number: (text) => stripAnsi(theme.number(text)),
|
|
9
|
+
intro: (text) => stripAnsi(theme.intro(text)),
|
|
10
|
+
resolvedSymbol: stripAnsi(theme.resolvedSymbol),
|
|
11
|
+
errorSymbol: stripAnsi(theme.errorSymbol),
|
|
12
|
+
accent: (text) => stripAnsi(theme.accent(text)),
|
|
13
|
+
muted: (text) => stripAnsi(theme.muted(text)),
|
|
14
|
+
success: (text) => stripAnsi(theme.success(text)),
|
|
15
|
+
warning: (text) => stripAnsi(theme.warning(text)),
|
|
16
|
+
error: (text) => stripAnsi(theme.error(text)),
|
|
17
|
+
info: (text) => stripAnsi(theme.info(text)),
|
|
18
|
+
badge: (text) => stripAnsi(theme.badge(text)),
|
|
19
|
+
styles: {
|
|
20
|
+
accent: { ...theme.styles.accent },
|
|
21
|
+
muted: { ...theme.styles.muted },
|
|
22
|
+
success: { ...theme.styles.success },
|
|
23
|
+
warning: { ...theme.styles.warning },
|
|
24
|
+
error: { ...theme.styles.error },
|
|
25
|
+
info: { ...theme.styles.info }
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function createCaptureTheme() {
|
|
30
|
+
return {
|
|
31
|
+
...withoutColors(light),
|
|
32
|
+
intro: (text) => ` Poe - ${text} `
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function createRenderCapture() {
|
|
36
|
+
const output = [];
|
|
37
|
+
const captureTheme = createCaptureTheme();
|
|
38
|
+
const emit = (message) => {
|
|
39
|
+
output.push(stripAnsi(message));
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
primitives: {
|
|
43
|
+
logger: createLogger(emit),
|
|
44
|
+
renderTable: (options) => withOutputFormat("terminal", () => stripAnsi(renderTable({
|
|
45
|
+
...options,
|
|
46
|
+
theme: withoutColors(options.theme),
|
|
47
|
+
maxWidth: captureWidth
|
|
48
|
+
}))),
|
|
49
|
+
getTheme: () => captureTheme,
|
|
50
|
+
note: (message, title) => emit(title === undefined ? message : `${title}\n${message}`)
|
|
51
|
+
},
|
|
52
|
+
output: () => output.join("\n")
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "tiny-mcp-client";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "tiny-mcp-client";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# @poe-code/agent-human-in-loop
|
|
2
2
|
|
|
3
3
|
`@poe-code/agent-human-in-loop` is a UI-only package for asking a human "approve this?" before an agent proceeds. The UI is providerized, sync vs async is the caller's choice because the API returns a Promise you can await immediately or hold and resolve later, and the package is approval-only: decline returns a declined outcome and can optionally capture a reason.
|
|
4
4
|
|
|
@@ -21,7 +21,13 @@ declare function mockProvider(
|
|
|
21
21
|
- `osascriptProvider({ title?, binary? })` — macOS native dialog via `display dialog`. Mac only.
|
|
22
22
|
- `mockProvider(answer | thunk)` — fixed or scripted answers for tests.
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## Configuration Options
|
|
25
|
+
|
|
26
|
+
- `osascriptProvider({ title?, binary? })`: optional dialog title and `osascript` binary path.
|
|
27
|
+
- `mockProvider(answer)`: fixed approval result or callback for tests.
|
|
28
|
+
- `requestApproval({ message, approveLabel?, declineLabel?, declineInputPrompt?, provider })`: approval prompt payload and provider.
|
|
29
|
+
|
|
30
|
+
## Environment Variables
|
|
25
31
|
|
|
26
32
|
None in v1.
|
|
27
33
|
|
|
@@ -29,6 +35,10 @@ None in v1.
|
|
|
29
35
|
|
|
30
36
|
Messages and prompts are passed verbatim through the dialog; the provider escapes `"` and `\` for AppleScript string literals. Do not pass user-supplied AppleScript fragments expecting them to execute.
|
|
31
37
|
|
|
38
|
+
## Manual QA
|
|
39
|
+
|
|
40
|
+
Use [QA.md](QA.md) for package-level approval-flow checks after behavior changes.
|
|
41
|
+
|
|
32
42
|
## Example
|
|
33
43
|
|
|
34
44
|
```ts
|
|
@@ -36,13 +36,13 @@ console.log(supportedAgents);
|
|
|
36
36
|
|
|
37
37
|
`ApplyOptions` controls how mutations are applied:
|
|
38
38
|
|
|
39
|
-
| Option
|
|
40
|
-
|
|
|
41
|
-
| `fs`
|
|
42
|
-
| `homeDir`
|
|
43
|
-
| `platform`
|
|
44
|
-
| `dryRun`
|
|
45
|
-
| `observers` | `MutationObservers`
|
|
39
|
+
| Option | Type | Description |
|
|
40
|
+
| ----------- | -------------------------------- | --------------------------------------------------------- |
|
|
41
|
+
| `fs` | `FileSystem` | Filesystem adapter from `@poe-code/config-mutations`. |
|
|
42
|
+
| `homeDir` | `string` | Home directory used to resolve `~` in agent config paths. |
|
|
43
|
+
| `platform` | `"darwin" \| "linux" \| "win32"` | Selects platform-specific config paths. |
|
|
44
|
+
| `dryRun` | `boolean` | Computes mutations without writing files. |
|
|
45
|
+
| `observers` | `MutationObservers` | Receives mutation lifecycle events. |
|
|
46
46
|
|
|
47
47
|
The package's built-in agent config table declares `configFile`, `configKey`,
|
|
48
48
|
`format`, `shape`, and optional `mcpOutputFormat` per supported agent.
|
|
@@ -38,14 +38,14 @@ await runMutations(
|
|
|
38
38
|
|
|
39
39
|
`MutationContext` controls execution:
|
|
40
40
|
|
|
41
|
-
| Option
|
|
42
|
-
|
|
|
43
|
-
| `fs`
|
|
44
|
-
| `homeDir`
|
|
45
|
-
| `dryRun`
|
|
46
|
-
| `observers`
|
|
47
|
-
| `templates`
|
|
48
|
-
| `pathMapper` | `PathMapper`
|
|
41
|
+
| Option | Type | Description |
|
|
42
|
+
| ------------ | ------------------- | ------------------------------------------------------- |
|
|
43
|
+
| `fs` | `FileSystem` | Required filesystem adapter. |
|
|
44
|
+
| `homeDir` | `string` | Required home directory for `~` expansion. |
|
|
45
|
+
| `dryRun` | `boolean` | Reports changes without writing them. |
|
|
46
|
+
| `observers` | `MutationObservers` | Receives mutation lifecycle events. |
|
|
47
|
+
| `templates` | `TemplateLoader` | Loads templates referenced by template mutations. |
|
|
48
|
+
| `pathMapper` | `PathMapper` | Redirects target directories for isolated config flows. |
|
|
49
49
|
|
|
50
50
|
Mutation builders also expose per-mutation options such as target path, format,
|
|
51
51
|
label, force removal, backup behavior, template id, and transform callbacks.
|
|
@@ -26,10 +26,10 @@ Duplicate mapping keys use YAML's last-wins behavior by default, matching the
|
|
|
26
26
|
legacy frontmatter readers. Callers that need strict YAML mappings can pass
|
|
27
27
|
`{ uniqueKeys: true }` to `parseFrontmatter` or `parseFrontmatterDocument`.
|
|
28
28
|
|
|
29
|
-
## Environment
|
|
29
|
+
## Environment Variables
|
|
30
30
|
|
|
31
31
|
This package reads no environment variables.
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## Configuration Options
|
|
34
34
|
|
|
35
35
|
This package has no configuration options.
|
|
@@ -32,7 +32,7 @@ Low-level process execution abstraction. Single interface for launching processe
|
|
|
32
32
|
- `extraArgs`: additional runtime arguments.
|
|
33
33
|
- `containerName`: optional container name prefix.
|
|
34
34
|
|
|
35
|
-
## Environment
|
|
35
|
+
## Environment Variables
|
|
36
36
|
|
|
37
37
|
This package exposes no environment variables.
|
|
38
38
|
|
|
@@ -46,7 +46,7 @@ interface EventDef<TState extends string = string> {
|
|
|
46
46
|
|
|
47
47
|
Pass a custom machine with `openTaskList({ stateMachine })`. If omitted, the package uses `defaultStateMachine`, whose event names are `plan`, `start`, `complete`, and `archive`.
|
|
48
48
|
|
|
49
|
-
## Options
|
|
49
|
+
## Configuration Options
|
|
50
50
|
|
|
51
51
|
| Option | Type | Default | Behavior |
|
|
52
52
|
| ----------------- | ---------------------------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
@@ -59,7 +59,7 @@ Pass a custom machine with `openTaskList({ stateMachine })`. If omitted, the pac
|
|
|
59
59
|
| `fs` | `TaskListFs` | `node:fs/promises` adapter | Injectable filesystem, primarily for tests. |
|
|
60
60
|
| `stateMachine` | `StateMachineDef` | `defaultStateMachine` | Overrides the task lifecycle used by `create`, `fire`, `canFire`, and `events`. |
|
|
61
61
|
|
|
62
|
-
##
|
|
62
|
+
## Environment Variables
|
|
63
63
|
|
|
64
64
|
| Env var | Behavior |
|
|
65
65
|
| --------- | --------------------------------------------------------------- |
|
|
@@ -231,7 +231,7 @@ interface SyncGhProjectReport extends VerifyGhProjectReport {
|
|
|
231
231
|
}
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
-
The CLI exposes
|
|
234
|
+
The root CLI exposes verification, provisioning, and task mutation commands:
|
|
235
235
|
|
|
236
236
|
```sh
|
|
237
237
|
poe-code tasks verify <list> --workflow ./WORKFLOW.md --repo octo-org/octo-repo --project octo-org/7 --states queued,agent-running,human-review,done,failed,archived --json
|
|
@@ -249,6 +249,18 @@ poe-code tasks sync <list> --workflow ./WORKFLOW.md --repo octo-org/octo-repo --
|
|
|
249
249
|
| `--json` | `verify`, `sync` | Prints the report as JSON. |
|
|
250
250
|
| `--yes` | `sync` | Confirms non-interactive provisioning. |
|
|
251
251
|
|
|
252
|
+
Additional task backend commands:
|
|
253
|
+
|
|
254
|
+
| Command | Purpose |
|
|
255
|
+
| --------------------------------------- | ----------------------------------------------------- |
|
|
256
|
+
| `poe-code tasks move` | Move tasks between workflow-configured backends. |
|
|
257
|
+
| `poe-code tasks import` | Import Markdown task files into a configured backend. |
|
|
258
|
+
| `poe-code tasks get <id>` | Read one task. |
|
|
259
|
+
| `poe-code tasks set <id>` | Update one task. |
|
|
260
|
+
| `poe-code tasks set-state <id> <state>` | Set one task's state. |
|
|
261
|
+
| `poe-code tasks next <id>` | Advance one task to the next workflow state. |
|
|
262
|
+
| `poe-code tasks comment <id>` | Add a task comment when the backend supports it. |
|
|
263
|
+
|
|
252
264
|
The `Status` field name and option names are matched case-sensitively. The field must be named `Status`, and required option names must match exactly. For example, `status` is treated as a missing field, and `Done` is treated as missing when the required state is `done`.
|
|
253
265
|
|
|
254
266
|
In v1, if sync creates a new GitHub Project v2 board, the CLI prints the new project number and does not rewrite `WORKFLOW.md`. The operator must update `WORKFLOW.md` by hand with the printed `<owner>/<number>`.
|