toolcraft 0.0.167 → 0.0.169
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/composition.json +2 -2
- package/dist/composition.json +2 -2
- package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +48 -28
- package/node_modules/@poe-code/process-runner/dist/types.d.ts +4 -1
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/toolcraft-design/dist/explorer/filter.js +14 -7
- package/node_modules/toolcraft-design/dist/prompts/interactive/core.js +85 -0
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
package/composition.json
CHANGED
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "toolcraft",
|
|
116
|
-
"version": "0.0.
|
|
116
|
+
"version": "0.0.169",
|
|
117
117
|
"license": "MIT"
|
|
118
118
|
},
|
|
119
119
|
{
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"name": "toolcraft-schema",
|
|
126
|
-
"version": "0.0.
|
|
126
|
+
"version": "0.0.169",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
package/dist/composition.json
CHANGED
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "toolcraft",
|
|
116
|
-
"version": "0.0.
|
|
116
|
+
"version": "0.0.169",
|
|
117
117
|
"license": "MIT"
|
|
118
118
|
},
|
|
119
119
|
{
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"name": "toolcraft-schema",
|
|
126
|
-
"version": "0.0.
|
|
126
|
+
"version": "0.0.169",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
@@ -3,6 +3,7 @@ import { mkdtempSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { readFile, realpath, writeFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
6
7
|
import { buildDockerEnvArgs, buildDockerRunArgs } from "./args.js";
|
|
7
8
|
import { readDockerBuildContextFiles } from "./build-context.js";
|
|
8
9
|
import { buildContextArgs, detectContext } from "./context.js";
|
|
@@ -316,17 +317,24 @@ async function runAndRead(runner, spec) {
|
|
|
316
317
|
}
|
|
317
318
|
return output;
|
|
318
319
|
}
|
|
319
|
-
async function
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
320
|
+
async function runJobRead(runner, spec) {
|
|
321
|
+
spec.signal?.throwIfAborted();
|
|
322
|
+
const controller = new AbortController();
|
|
323
|
+
const signal = spec.signal
|
|
324
|
+
? AbortSignal.any([spec.signal, controller.signal])
|
|
325
|
+
: controller.signal;
|
|
326
|
+
const handle = runner.exec({ ...spec, signal });
|
|
327
|
+
const reads = [handle.result, readStreamBytes(handle.stdout), readStream(handle.stderr)];
|
|
328
|
+
try {
|
|
329
|
+
const [result, stdout, stderr] = await Promise.all(reads);
|
|
330
|
+
signal.throwIfAborted();
|
|
331
|
+
return { exitCode: result.exitCode, stdout, stderr };
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
controller.abort();
|
|
335
|
+
await Promise.allSettled(reads);
|
|
336
|
+
throw error;
|
|
328
337
|
}
|
|
329
|
-
return output;
|
|
330
338
|
}
|
|
331
339
|
async function runOrThrow(runner, spec) {
|
|
332
340
|
await runAndRead(runner, spec);
|
|
@@ -498,15 +506,15 @@ function createContainerJob(containerId, runner, engine, context, detachedJobCon
|
|
|
498
506
|
envId: containerId,
|
|
499
507
|
tool: detachedJobContext?.tool ?? "docker",
|
|
500
508
|
argv: detachedJobContext?.argv ?? ["attach", containerId],
|
|
501
|
-
async status() {
|
|
509
|
+
async status(opts) {
|
|
502
510
|
if (detachedJobContext !== null) {
|
|
503
|
-
const detached = await readDetachedState(containerId, jobId, runner, engine, context);
|
|
511
|
+
const detached = await readDetachedState(containerId, jobId, runner, engine, context, opts?.signal);
|
|
504
512
|
if (detached.kind === "unreachable") {
|
|
505
513
|
return "lost";
|
|
506
514
|
}
|
|
507
515
|
return detached.kind === "exited" ? "exited" : "running";
|
|
508
516
|
}
|
|
509
|
-
const
|
|
517
|
+
const result = await runJobRead(runner, {
|
|
510
518
|
command: engine,
|
|
511
519
|
args: [
|
|
512
520
|
...buildContextArgs(engine, context),
|
|
@@ -516,14 +524,13 @@ function createContainerJob(containerId, runner, engine, context, detachedJobCon
|
|
|
516
524
|
containerId
|
|
517
525
|
],
|
|
518
526
|
stdout: "pipe",
|
|
519
|
-
stderr: "pipe"
|
|
527
|
+
stderr: "pipe",
|
|
528
|
+
signal: opts?.signal
|
|
520
529
|
});
|
|
521
|
-
const stdout = await readStream(handle.stdout);
|
|
522
|
-
const result = await handle.result;
|
|
523
530
|
if (result.exitCode !== 0) {
|
|
524
531
|
return "lost";
|
|
525
532
|
}
|
|
526
|
-
return stdout.trim() === "exited" ? "exited" : "running";
|
|
533
|
+
return result.stdout.toString("utf8").trim() === "exited" ? "exited" : "running";
|
|
527
534
|
},
|
|
528
535
|
async *stream(opts) {
|
|
529
536
|
const logFile = shellQuote(`/tmp/poe-jobs/${jobId}.log`);
|
|
@@ -533,8 +540,9 @@ function createContainerJob(containerId, runner, engine, context, detachedJobCon
|
|
|
533
540
|
let byteOffset = opts?.sinceByte ?? 0;
|
|
534
541
|
let pendingBytes = Buffer.alloc(0);
|
|
535
542
|
let pendingByteOffset = byteOffset;
|
|
543
|
+
let finalRead = false;
|
|
536
544
|
while (true) {
|
|
537
|
-
const
|
|
545
|
+
const spec = {
|
|
538
546
|
command: engine,
|
|
539
547
|
args: [
|
|
540
548
|
...buildContextArgs(engine, context),
|
|
@@ -545,8 +553,13 @@ function createContainerJob(containerId, runner, engine, context, detachedJobCon
|
|
|
545
553
|
`test -f ${logFile}${sinceCondition} && tail -c +${byteOffset + 1} ${logFile} || true`
|
|
546
554
|
],
|
|
547
555
|
stdout: "pipe",
|
|
548
|
-
stderr: "pipe"
|
|
549
|
-
|
|
556
|
+
stderr: "pipe",
|
|
557
|
+
signal: opts?.signal
|
|
558
|
+
};
|
|
559
|
+
const { stdout, stderr, exitCode } = await runJobRead(runner, spec);
|
|
560
|
+
if (exitCode !== 0) {
|
|
561
|
+
throw new Error(`Command failed with exit code ${exitCode}: ${spec.command} ${(spec.args ?? []).join(" ")}${stderr ? `\n${stderr}` : ""}`);
|
|
562
|
+
}
|
|
550
563
|
if (stdout.byteLength > 0) {
|
|
551
564
|
const combined = pendingBytes.byteLength === 0
|
|
552
565
|
? stdout
|
|
@@ -560,10 +573,18 @@ function createContainerJob(containerId, runner, engine, context, detachedJobCon
|
|
|
560
573
|
pendingByteOffset += completeLength;
|
|
561
574
|
}
|
|
562
575
|
}
|
|
563
|
-
if (opts?.follow !== true ||
|
|
576
|
+
if (opts?.follow !== true || finalRead) {
|
|
564
577
|
return;
|
|
565
578
|
}
|
|
566
|
-
|
|
579
|
+
const status = await this.status({ signal: opts?.signal });
|
|
580
|
+
if (status === "exited" && detachedJobContext !== null) {
|
|
581
|
+
finalRead = true;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (status !== "running") {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
await sleep(250, undefined, { signal: opts?.signal });
|
|
567
588
|
}
|
|
568
589
|
},
|
|
569
590
|
async wait() {
|
|
@@ -660,9 +681,9 @@ function utf8SequenceLength(byte) {
|
|
|
660
681
|
}
|
|
661
682
|
return 0;
|
|
662
683
|
}
|
|
663
|
-
async function readDetachedState(containerId, jobId, runner, engine, context) {
|
|
684
|
+
async function readDetachedState(containerId, jobId, runner, engine, context, signal) {
|
|
664
685
|
const exitFile = shellQuote(`/tmp/poe-jobs/${jobId}.exit`);
|
|
665
|
-
const
|
|
686
|
+
const result = await runJobRead(runner, {
|
|
666
687
|
command: engine,
|
|
667
688
|
args: [
|
|
668
689
|
...buildContextArgs(engine, context),
|
|
@@ -673,14 +694,13 @@ async function readDetachedState(containerId, jobId, runner, engine, context) {
|
|
|
673
694
|
`test -f ${exitFile} && cat ${exitFile} || true`
|
|
674
695
|
],
|
|
675
696
|
stdout: "pipe",
|
|
676
|
-
stderr: "pipe"
|
|
697
|
+
stderr: "pipe",
|
|
698
|
+
signal
|
|
677
699
|
});
|
|
678
|
-
const stdout = await readStream(handle.stdout);
|
|
679
|
-
const result = await handle.result;
|
|
680
700
|
if (result.exitCode !== 0) {
|
|
681
701
|
return { kind: "unreachable" };
|
|
682
702
|
}
|
|
683
|
-
const text = stdout.trim();
|
|
703
|
+
const text = result.stdout.toString("utf8").trim();
|
|
684
704
|
if (text.length === 0) {
|
|
685
705
|
return { kind: "running" };
|
|
686
706
|
}
|
|
@@ -127,11 +127,14 @@ export interface JobHandle {
|
|
|
127
127
|
readonly envId: string;
|
|
128
128
|
readonly tool: string;
|
|
129
129
|
readonly argv: string[];
|
|
130
|
-
status(
|
|
130
|
+
status(opts?: {
|
|
131
|
+
signal?: AbortSignal;
|
|
132
|
+
}): Promise<JobStatus>;
|
|
131
133
|
stream(opts?: {
|
|
132
134
|
sinceByte?: number;
|
|
133
135
|
since?: Date;
|
|
134
136
|
follow?: boolean;
|
|
137
|
+
signal?: AbortSignal;
|
|
135
138
|
}): AsyncIterable<LogChunk>;
|
|
136
139
|
wait(): Promise<{
|
|
137
140
|
exitCode: number;
|
|
@@ -28,17 +28,24 @@ function stripAnsi(value) {
|
|
|
28
28
|
return value.replace(/\u001b\[[0-9;]*m/g, "");
|
|
29
29
|
}
|
|
30
30
|
function matchSubsequence(query, text) {
|
|
31
|
+
const queryCharacters = Array.from(query);
|
|
32
|
+
const textCharacters = Array.from(text);
|
|
31
33
|
let previousStates = [];
|
|
32
|
-
for (let queryIndex = 0; queryIndex <
|
|
34
|
+
for (let queryIndex = 0; queryIndex < queryCharacters.length; queryIndex += 1) {
|
|
33
35
|
const states = [];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
let offset = 0;
|
|
37
|
+
for (let textIndex = 0; textIndex < textCharacters.length; textIndex += 1) {
|
|
38
|
+
const character = textCharacters[textIndex];
|
|
39
|
+
const position = offset;
|
|
40
|
+
offset += character.length;
|
|
41
|
+
if (character !== queryCharacters[queryIndex]) {
|
|
36
42
|
continue;
|
|
37
43
|
}
|
|
44
|
+
const positions = character.length === 2 ? [position, position + 1] : [position];
|
|
38
45
|
if (queryIndex === 0) {
|
|
39
46
|
states[textIndex] = {
|
|
40
|
-
score: characterScore(
|
|
41
|
-
positions
|
|
47
|
+
score: characterScore(textCharacters, textIndex, undefined) + Math.max(0, EARLY_MATCH_BONUS - position),
|
|
48
|
+
positions
|
|
42
49
|
};
|
|
43
50
|
continue;
|
|
44
51
|
}
|
|
@@ -48,8 +55,8 @@ function matchSubsequence(query, text) {
|
|
|
48
55
|
continue;
|
|
49
56
|
}
|
|
50
57
|
const next = {
|
|
51
|
-
score: previous.score + characterScore(
|
|
52
|
-
positions: [...previous.positions,
|
|
58
|
+
score: previous.score + characterScore(textCharacters, textIndex, previousIndex),
|
|
59
|
+
positions: [...previous.positions, ...positions]
|
|
53
60
|
};
|
|
54
61
|
if (isBetterMatch(next, states[textIndex])) {
|
|
55
62
|
states[textIndex] = next;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import * as readline from "node:readline";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
4
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3
5
|
import { graphemes } from "../../dashboard/terminal-width.js";
|
|
4
6
|
import { CANCEL } from "./cancel-symbol.js";
|
|
5
7
|
import { mapKey } from "./keys.js";
|
|
6
8
|
import { wrapFrame } from "./wrap.js";
|
|
9
|
+
const pendingCarriageReturns = new WeakMap();
|
|
7
10
|
const cursor = {
|
|
8
11
|
hide: "\x1b[?25l",
|
|
9
12
|
show: "\x1b[?25h",
|
|
@@ -128,6 +131,88 @@ export class Prompt extends EventEmitter {
|
|
|
128
131
|
this.state = "cancel";
|
|
129
132
|
return Promise.resolve(CANCEL);
|
|
130
133
|
}
|
|
134
|
+
const input = this.input;
|
|
135
|
+
if (input instanceof Readable) {
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
const decoder = new StringDecoder("utf8");
|
|
138
|
+
let line = "";
|
|
139
|
+
let settled = false;
|
|
140
|
+
const settle = (value, remainder) => {
|
|
141
|
+
if (settled)
|
|
142
|
+
return;
|
|
143
|
+
settled = true;
|
|
144
|
+
input.removeListener("data", onData);
|
|
145
|
+
input.removeListener("end", onEnd);
|
|
146
|
+
input.removeListener("close", onCancel);
|
|
147
|
+
input.removeListener("error", settle);
|
|
148
|
+
this.signal?.removeEventListener("abort", onCancel);
|
|
149
|
+
input.pause();
|
|
150
|
+
if (remainder?.length)
|
|
151
|
+
input.unshift(remainder, input.readableEncoding ?? undefined);
|
|
152
|
+
if (value instanceof Error)
|
|
153
|
+
reject(value);
|
|
154
|
+
else
|
|
155
|
+
resolve(value);
|
|
156
|
+
};
|
|
157
|
+
const onCancel = () => {
|
|
158
|
+
this.state = "cancel";
|
|
159
|
+
settle(CANCEL);
|
|
160
|
+
};
|
|
161
|
+
const onEnd = () => settle(line + decoder.end());
|
|
162
|
+
const onData = (chunk) => {
|
|
163
|
+
if (chunk.length === 0)
|
|
164
|
+
return;
|
|
165
|
+
if (settled) {
|
|
166
|
+
if (!input.destroyed)
|
|
167
|
+
input.unshift(chunk, input.readableEncoding ?? undefined);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
let offset = 0;
|
|
171
|
+
const previousCarriageReturn = pendingCarriageReturns.get(input);
|
|
172
|
+
if (previousCarriageReturn !== undefined) {
|
|
173
|
+
pendingCarriageReturns.delete(input);
|
|
174
|
+
const isLineFeed = typeof chunk === "string" ? chunk[0] === "\n" : chunk[0] === 10;
|
|
175
|
+
if (isLineFeed && Date.now() - previousCarriageReturn <= 100)
|
|
176
|
+
offset = 1;
|
|
177
|
+
}
|
|
178
|
+
for (; offset < chunk.length; offset += 1) {
|
|
179
|
+
const character = typeof chunk === "string" ? chunk[offset] : decoder.write(chunk.subarray(offset, offset + 1));
|
|
180
|
+
if (character.endsWith("\r") || character.endsWith("\n")) {
|
|
181
|
+
line += character.slice(0, -1);
|
|
182
|
+
offset += 1;
|
|
183
|
+
if (character.endsWith("\r")) {
|
|
184
|
+
if (offset === chunk.length) {
|
|
185
|
+
pendingCarriageReturns.set(input, Date.now());
|
|
186
|
+
}
|
|
187
|
+
else if (typeof chunk === "string" ? chunk[offset] === "\n" : chunk[offset] === 10) {
|
|
188
|
+
offset += 1;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
settle(line, chunk.slice(offset));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
line += character;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
input.once("end", onEnd);
|
|
198
|
+
input.once("close", onCancel);
|
|
199
|
+
input.once("error", settle);
|
|
200
|
+
this.signal?.addEventListener("abort", onCancel, { once: true });
|
|
201
|
+
if (this.signal?.aborted)
|
|
202
|
+
onCancel();
|
|
203
|
+
else if (input.readableEnded)
|
|
204
|
+
onEnd();
|
|
205
|
+
else if (input.destroyed)
|
|
206
|
+
onCancel();
|
|
207
|
+
else if (!settled) {
|
|
208
|
+
input.on("data", onData);
|
|
209
|
+
if (!settled)
|
|
210
|
+
input.resume();
|
|
211
|
+
if (settled)
|
|
212
|
+
input.pause();
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
131
216
|
return new Promise((resolve, reject) => {
|
|
132
217
|
let rl = null;
|
|
133
218
|
let settled = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.169",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
"yaml"
|
|
159
159
|
],
|
|
160
160
|
"optionalDependencies": {
|
|
161
|
-
"toolcraft-schema": "0.0.
|
|
161
|
+
"toolcraft-schema": "0.0.169",
|
|
162
162
|
"toolcraft-design": "*",
|
|
163
163
|
"@poe-code/frontmatter": "*",
|
|
164
164
|
"@poe-code/agent-mcp-config": "*",
|