@ycodium-ai/agent-fake 0.2.3122 → 0.2.3130
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 +13 -1
- package/dist/index.js +47 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -94,13 +94,25 @@ A `wait` polls `tools/list` on that MCP server every 250 ms for the tool name or
|
|
|
94
94
|
tool items. A `waitFor`, such as
|
|
95
95
|
`{ "waitFor": { "tool": "message_inbox_peek", "path": "items", "atLeast": 2 } }`, calls that tool
|
|
96
96
|
with its optional `arguments` (none by default; placeholders are not resolved there) every 250 ms on one session until the value at the dot `path` of its result is
|
|
97
|
-
an array at least `atLeast` long, or a number at least `atLeast`;
|
|
97
|
+
an array at least `atLeast` long, or a number at least `atLeast`; any other value found there (for
|
|
98
|
+
example one entry picked by a selector such as `items[title=<encodeURIComponent value>]`) counts
|
|
99
|
+
as 1, and a tool error counts as not yet. Dots inside a selector's brackets do not split the path.
|
|
100
|
+
An optional `id` keeps the result of the ready poll so later calls can reference it with
|
|
101
|
+
`{{step:<id>.<path>}}`; it is not added to the history that `{{step:*…}}` searches.
|
|
98
102
|
Point it only at read-only tools, since every poll is a real call. It also defaults to 30000 ms,
|
|
99
103
|
replies `mcp-send waitFor <tool> <path> >= <atLeast>: ready` or `… failed: timeout`, emits no tool
|
|
100
104
|
items and continues. Calls reply `mcp-send <tool>: <JSON result>` or `mcp-send <tool> failed: <error>` and
|
|
101
105
|
emit paired `item.started` / `item.completed` events carrying the prefixed tool name, resolved
|
|
102
106
|
input, raw result and status. Abort stops before the next step and ends the turn as `interrupted`.
|
|
103
107
|
|
|
108
|
+
A batch may start with `{ "expectInput": "<text>" }`: it runs only on a turn whose input contains
|
|
109
|
+
that text. Each turn runs the earliest batch that has not run yet and either matches the turn's
|
|
110
|
+
input or, without `expectInput`, is the earliest batch left; so batches keyed by their wake-up text
|
|
111
|
+
can run out of order, and a playbook without `expectInput` still runs batch by batch. A turn that
|
|
112
|
+
matches nothing falls back to the plain `mcp-send` handling below. Recorded replays key each batch
|
|
113
|
+
by the message that started that turn, so wake-ups arriving in a different order still reach the
|
|
114
|
+
right batch. `expectInput` is valid only as a batch's first step.
|
|
115
|
+
|
|
104
116
|
Threads with no matching line, or with all batches consumed, use the original `mcp-send`
|
|
105
117
|
input handling: JSON requests call tools and ordinary text gets `replyText`. Omitting
|
|
106
118
|
`playbookPath` keeps the existing scripts and output unchanged.
|
package/dist/index.js
CHANGED
|
@@ -666,6 +666,8 @@ var parseTimeout = (value) => {
|
|
|
666
666
|
};
|
|
667
667
|
function parseStep(value) {
|
|
668
668
|
if (!isRecord2(value)) return null;
|
|
669
|
+
if (Object.hasOwn(value, "expectInput"))
|
|
670
|
+
return onlyKeys(value, ["expectInput"]) && isName(value.expectInput) ? { expectInput: value.expectInput } : null;
|
|
669
671
|
if (Object.hasOwn(value, "wait")) {
|
|
670
672
|
const wait = value.wait;
|
|
671
673
|
if (!onlyKeys(value, ["wait"]) || !isRecord2(wait) || !isName(wait.tool)) return null;
|
|
@@ -683,7 +685,7 @@ function parseStep(value) {
|
|
|
683
685
|
if (Object.hasOwn(value, "waitFor")) {
|
|
684
686
|
const wait = value.waitFor;
|
|
685
687
|
if (!onlyKeys(value, ["waitFor"]) || !isRecord2(wait) || !isName(wait.tool)) return null;
|
|
686
|
-
if (!onlyKeys(wait, ["server", "tool", "arguments", "path", "atLeast", "timeoutMs"]) || !isName(wait.path) || wait.arguments !== void 0 && !isRecord2(wait.arguments) || wait.server !== void 0 && !isName(wait.server))
|
|
688
|
+
if (!onlyKeys(wait, ["id", "server", "tool", "arguments", "path", "atLeast", "timeoutMs"]) || !isName(wait.path) || wait.id !== void 0 && !isName(wait.id) || wait.arguments !== void 0 && !isRecord2(wait.arguments) || wait.server !== void 0 && !isName(wait.server))
|
|
687
689
|
return null;
|
|
688
690
|
if (typeof wait.atLeast !== "number" || !Number.isInteger(wait.atLeast) || wait.atLeast < 0)
|
|
689
691
|
return null;
|
|
@@ -694,6 +696,7 @@ function parseStep(value) {
|
|
|
694
696
|
path: wait.path,
|
|
695
697
|
atLeast: wait.atLeast,
|
|
696
698
|
timeoutMs,
|
|
699
|
+
...wait.id === void 0 ? {} : { id: wait.id },
|
|
697
700
|
...wait.server === void 0 ? {} : { server: wait.server },
|
|
698
701
|
...wait.arguments === void 0 ? {} : { arguments: wait.arguments }
|
|
699
702
|
}
|
|
@@ -732,7 +735,7 @@ function parsePlaybook(text) {
|
|
|
732
735
|
const steps = [];
|
|
733
736
|
for (const entry of batch) {
|
|
734
737
|
const step = parseStep(entry);
|
|
735
|
-
if (step === null)
|
|
738
|
+
if (step === null || "expectInput" in step && steps.length > 0)
|
|
736
739
|
return { ok: false, reason: `threads[${index}].turns[${turn}] has an invalid step` };
|
|
737
740
|
steps.push(step);
|
|
738
741
|
}
|
|
@@ -753,9 +756,10 @@ function parsePlaybook(text) {
|
|
|
753
756
|
}
|
|
754
757
|
var UnresolvedPlaceholder = class extends Error {
|
|
755
758
|
};
|
|
759
|
+
var splitPath = (path) => path.split(/\.(?![^\[]*\])/u);
|
|
756
760
|
function placeholderValue(raw, threadId, results, history) {
|
|
757
761
|
if (raw === "{{thread}}") return threadId;
|
|
758
|
-
const [id, ...path] = raw.slice(7, -2)
|
|
762
|
+
const [id, ...path] = splitPath(raw.slice(7, -2));
|
|
759
763
|
const unresolved = () => new UnresolvedPlaceholder(`unresolved placeholder ${raw}`);
|
|
760
764
|
const nthAt = path.findIndex((part) => {
|
|
761
765
|
const selector = parseSelector(part);
|
|
@@ -855,6 +859,17 @@ function stepResult(result) {
|
|
|
855
859
|
return first.text;
|
|
856
860
|
}
|
|
857
861
|
}
|
|
862
|
+
function pickBatch(line, consumed, input) {
|
|
863
|
+
let earliest = true;
|
|
864
|
+
for (const [index, batch] of line.turns.entries()) {
|
|
865
|
+
if (consumed.has(index)) continue;
|
|
866
|
+
const first = batch[0];
|
|
867
|
+
const expected = first !== void 0 && "expectInput" in first ? first.expectInput : void 0;
|
|
868
|
+
if (expected === void 0 ? earliest : input.includes(expected)) return index;
|
|
869
|
+
earliest = false;
|
|
870
|
+
}
|
|
871
|
+
return void 0;
|
|
872
|
+
}
|
|
858
873
|
async function poll(timeoutMs, input, ready) {
|
|
859
874
|
const deadline = Date.now() + timeoutMs;
|
|
860
875
|
const controller = new AbortController();
|
|
@@ -885,18 +900,25 @@ var waitForTool = (step, input, clients) => poll(step.wait.timeoutMs, input, asy
|
|
|
885
900
|
(tool) => isRecord2(tool) && (tool.name === step.wait.tool || tool.name === `mcp__${step.wait.server ?? "ycodium"}__${step.wait.tool}`)
|
|
886
901
|
);
|
|
887
902
|
});
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
903
|
+
async function waitForCount(step, input, clients) {
|
|
904
|
+
let ready;
|
|
905
|
+
const ok = await poll(step.waitFor.timeoutMs, input, async (signal) => {
|
|
906
|
+
const called = await clients.request(
|
|
907
|
+
step.waitFor.server,
|
|
908
|
+
"tools/call",
|
|
909
|
+
{ name: step.waitFor.tool, arguments: step.waitFor.arguments ?? {} },
|
|
910
|
+
signal
|
|
911
|
+
);
|
|
912
|
+
if (isRecord2(called) && called.isError === true) return false;
|
|
913
|
+
const result = stepResult(called);
|
|
914
|
+
const value = valueAt(result, splitPath(step.waitFor.path));
|
|
915
|
+
const count = Array.isArray(value) ? value.length : typeof value === "number" ? value : value === void 0 || value === null ? 0 : 1;
|
|
916
|
+
if (count < step.waitFor.atLeast) return false;
|
|
917
|
+
ready = { result };
|
|
918
|
+
return true;
|
|
919
|
+
});
|
|
920
|
+
return ok ? ready : void 0;
|
|
921
|
+
}
|
|
900
922
|
async function runCall(step, playback, input, clients) {
|
|
901
923
|
let args = step.arguments ?? {};
|
|
902
924
|
let failure;
|
|
@@ -981,15 +1003,16 @@ function createPlaybookRunner(input) {
|
|
|
981
1003
|
if (index >= 0) claimed.add(index);
|
|
982
1004
|
playback = {
|
|
983
1005
|
line: parsed.threads[index] ?? null,
|
|
984
|
-
|
|
1006
|
+
consumed: /* @__PURE__ */ new Set(),
|
|
985
1007
|
results: /* @__PURE__ */ new Map(),
|
|
986
1008
|
history: []
|
|
987
1009
|
};
|
|
988
1010
|
sessions.set(turn.threadId, playback);
|
|
989
1011
|
}
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
1012
|
+
const picked = playback.line === null ? void 0 : pickBatch(playback.line, playback.consumed, turn.input ?? "");
|
|
1013
|
+
const batch = picked === void 0 ? void 0 : playback.line?.turns[picked];
|
|
1014
|
+
if (picked === void 0 || batch === void 0) return null;
|
|
1015
|
+
playback.consumed.add(picked);
|
|
993
1016
|
let opened = clients.get(turn.threadId);
|
|
994
1017
|
if (opened === void 0) {
|
|
995
1018
|
opened = createPlaybookMcp({ fetch: turn.fetch, servers: turn.mcpServers });
|
|
@@ -1004,6 +1027,7 @@ function createPlaybookRunner(input) {
|
|
|
1004
1027
|
const lines = [];
|
|
1005
1028
|
for (const step of batch) {
|
|
1006
1029
|
if (turn.signal.aborted) break;
|
|
1030
|
+
if ("expectInput" in step) continue;
|
|
1007
1031
|
if ("wait" in step) {
|
|
1008
1032
|
const missing = opened.missing(step.wait.server);
|
|
1009
1033
|
if (step.wait.server !== void 0 && missing !== void 0) {
|
|
@@ -1021,7 +1045,8 @@ function createPlaybookRunner(input) {
|
|
|
1021
1045
|
}
|
|
1022
1046
|
const ready = await waitForCount(step, safeTurn, opened);
|
|
1023
1047
|
if (turn.signal.aborted) break;
|
|
1024
|
-
const { tool, path: path2, atLeast } = step.waitFor;
|
|
1048
|
+
const { id, tool, path: path2, atLeast } = step.waitFor;
|
|
1049
|
+
if (ready !== void 0 && id !== void 0) playback.results.set(id, ready.result);
|
|
1025
1050
|
lines.push(
|
|
1026
1051
|
`mcp-send waitFor ${tool} ${path2} >= ${String(atLeast)}${ready ? ": ready" : " failed: timeout"}`
|
|
1027
1052
|
);
|
|
@@ -1391,6 +1416,7 @@ var createFakeInstance = (config, host) => {
|
|
|
1391
1416
|
threadId,
|
|
1392
1417
|
turnId,
|
|
1393
1418
|
signal,
|
|
1419
|
+
...input === void 0 ? {} : { input },
|
|
1394
1420
|
fetch: host.fetch,
|
|
1395
1421
|
mcpServers: booked.mcpServers,
|
|
1396
1422
|
emit,
|
|
@@ -1652,7 +1678,7 @@ var executors = [
|
|
|
1652
1678
|
];
|
|
1653
1679
|
var index_default = definePlugin({
|
|
1654
1680
|
id: "agent-fake",
|
|
1655
|
-
apiVersion:
|
|
1681
|
+
apiVersion: 14,
|
|
1656
1682
|
executors,
|
|
1657
1683
|
activate(ctx) {
|
|
1658
1684
|
ctx.log.info("fake executor ready");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ycodium-ai/agent-fake",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3130",
|
|
4
4
|
"description": "Minimal fake executor for plugin authors and host orchestration tests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ycodium",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"@types/node": "24.12.4",
|
|
22
22
|
"esbuild": "0.28.1",
|
|
23
23
|
"vite-plus": "0.2.2",
|
|
24
|
-
"@ycodium-ai/plugin-api": "0.2.
|
|
24
|
+
"@ycodium-ai/plugin-api": "0.2.3130",
|
|
25
25
|
"ycodium-executor-testing": "0.0.0"
|
|
26
26
|
},
|
|
27
27
|
"ycodium": {
|