@ycodium-ai/agent-fake 0.2.3377 → 0.2.3389

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 (3) hide show
  1. package/README.md +20 -7
  2. package/dist/index.js +42 -8
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -61,14 +61,25 @@ Replay recorded conversations or drive multi-thread acceptance tests with
61
61
  machine. Use an absolute path. This option is outside the settings form and applies only to
62
62
  `mcp-send`. Each turn runs the next batch, ignoring its input, including host wake-ups and follow-ups.
63
63
 
64
- A playbook instance accepts a steer, as a real provider does: a second `sendTurn` carrying the
65
- running turn's id returns that turn, and when the current batch finishes the next batch runs inside
66
- the same turn, one steer at a time in arrival order (each still subject to `expectInput`). A steer
67
- the playbook has no batch for is logged as `playbook has no step for a steered input` and noted in
68
- the reply. A steer whose target turn has already ended, or is not the running turn, is refused with
69
- an error carrying `code: "provider-rejected"`, so the host puts the message back in the queue. A plain
64
+ A playbook instance accepts a steer only where the playbook says so: while the batch the thread is
65
+ running ends with `awaitSteer`. Then a second `sendTurn` carrying the running turn's id returns that
66
+ turn, and when the current batch finishes the next batch runs inside the same turn, one steer at a
67
+ time in arrival order (each still subject to `expectInput`). A playbook derived from a recording has
68
+ `awaitSteer` exactly where the recorded agent took a steer, so a steer elsewhere would shift every
69
+ later batch by one; it is refused instead, and the host starts it as its own turn, as in the
70
+ recording. A steer the playbook has no batch for is logged as `playbook has no step for a steered
71
+ input` and noted in the reply. A steer the running batch does not await, or whose target turn has
72
+ already ended or is not the running turn, is refused with an error carrying
73
+ `code: "provider-rejected"`, so the host puts the message back in the queue. A plain
70
74
  `mcp-send` instance without a playbook still refuses a second turn while one is running.
71
75
 
76
+ `{ "awaitSteer": { "timeoutMs": 60000, "continueOnTimeout": true } }` treats a steer that never
77
+ arrives as already folded into the turn: on timeout it runs the next batch inside the same turn
78
+ instead of ending it. Derived playbooks set it on member-machine sessions, where a steer can reach
79
+ the host before the member's turn starts and be merged into that turn's input. The next batch then
80
+ relies on its own `waitFor` steps; a steer that arrives later is refused like any other and starts
81
+ a turn with no matching batch.
82
+
72
83
  This example assigns one script by thread ID and another by title:
73
84
 
74
85
  ```json
@@ -132,7 +143,9 @@ A `wait` polls `tools/list` on that MCP server every 250 ms for the tool name or
132
143
  `mcp-send wait <tool>: ready` or `mcp-send wait <tool> failed: timeout`, then continues, with no
133
144
  tool items. A `waitFor`, such as
134
145
  `{ "waitFor": { "tool": "message_inbox_peek", "path": "items", "atLeast": 2 } }`, calls that tool
135
- 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
146
+ with its optional `arguments` (none by default; `{{thread}}` and `{{step:…}}` placeholders are
147
+ resolved as for calls, and an unresolvable one replies `mcp-send <tool> failed: <error>` without
148
+ polling) every 250 ms on one session until the value at the dot `path` of its result is
136
149
  an array at least `atLeast` long, or a number at least `atLeast`; any other value found there (for
137
150
  example one entry picked by a selector such as `items[title=<encodeURIComponent value>]`) counts
138
151
  as 1, and a tool error counts as not yet. Dots inside a selector's brackets do not split the path.
package/dist/index.js CHANGED
@@ -897,10 +897,12 @@ function parseStep(value) {
897
897
  return onlyKeys(value, ["expectInput"]) && isName(value.expectInput) ? { expectInput: value.expectInput } : null;
898
898
  if (Object.hasOwn(value, "awaitSteer")) {
899
899
  const wait = value.awaitSteer;
900
- if (!onlyKeys(value, ["awaitSteer"]) || !isRecord2(wait) || !onlyKeys(wait, ["timeoutMs"]))
900
+ if (!onlyKeys(value, ["awaitSteer"]) || !isRecord2(wait) || !onlyKeys(wait, ["timeoutMs", "continueOnTimeout"]) || Object.hasOwn(wait, "continueOnTimeout") && wait.continueOnTimeout !== true)
901
901
  return null;
902
902
  const timeoutMs = parseTimeout(wait.timeoutMs);
903
- return timeoutMs === null ? null : { awaitSteer: { timeoutMs } };
903
+ if (timeoutMs === null) return null;
904
+ const onTimeout = wait.continueOnTimeout === true ? { continueOnTimeout: true } : {};
905
+ return { awaitSteer: { timeoutMs, ...onTimeout } };
904
906
  }
905
907
  if (Object.hasOwn(value, "wait")) {
906
908
  const wait = value.wait;
@@ -1135,13 +1137,13 @@ var waitForTool = (step, input, clients) => poll(step.wait.timeoutMs, input, asy
1135
1137
  (tool) => isRecord2(tool) && (tool.name === step.wait.tool || tool.name === `mcp__${step.wait.server ?? "ycodium"}__${step.wait.tool}`)
1136
1138
  );
1137
1139
  });
1138
- async function waitForCount(step, input, clients) {
1140
+ async function waitForCount(step, args, input, clients) {
1139
1141
  let ready;
1140
1142
  const ok = await poll(step.waitFor.timeoutMs, input, async (signal) => {
1141
1143
  const called = await clients.request(
1142
1144
  step.waitFor.server,
1143
1145
  "tools/call",
1144
- { name: step.waitFor.tool, arguments: step.waitFor.arguments ?? {} },
1146
+ { name: step.waitFor.tool, arguments: args },
1145
1147
  signal
1146
1148
  );
1147
1149
  if (isRecord2(called) && called.isError === true) return false;
@@ -1207,6 +1209,16 @@ function createPlaybookRunner(input) {
1207
1209
  const clients = /* @__PURE__ */ new Map();
1208
1210
  let loaded;
1209
1211
  return {
1212
+ /**
1213
+ * 这个会话正在跑的那一批以 awaitSteer 结尾时才收插话:剧本照录像在收到插话的地方放 awaitSteer,
1214
+ * 别处的插话录像里是另起一轮的,收了就会把后面的批次往前错一格。
1215
+ */
1216
+ expectsSteer: (threadId) => {
1217
+ const playback = sessions.get(threadId);
1218
+ const batch = playback?.current === void 0 ? void 0 : playback.line?.turns[playback.current];
1219
+ const last = batch?.at(-1);
1220
+ return last !== void 0 && "awaitSteer" in last;
1221
+ },
1210
1222
  startSession: (session) => {
1211
1223
  titles.set(session.threadId, session.title);
1212
1224
  },
@@ -1248,6 +1260,7 @@ function createPlaybookRunner(input) {
1248
1260
  const batch = picked === void 0 ? void 0 : playback.line?.turns[picked];
1249
1261
  if (picked === void 0 || batch === void 0) return null;
1250
1262
  playback.consumed.add(picked);
1263
+ playback.current = picked;
1251
1264
  let opened = clients.get(turn.threadId);
1252
1265
  if (opened === void 0) {
1253
1266
  opened = createPlaybookMcp({ fetch: turn.fetch, servers: turn.mcpServers });
@@ -1260,13 +1273,21 @@ function createPlaybookRunner(input) {
1260
1273
  }
1261
1274
  };
1262
1275
  const lines = [];
1263
- for (const step of batch) {
1276
+ const queue = [...batch];
1277
+ for (let step = queue.shift(); step !== void 0; step = queue.shift()) {
1264
1278
  if (turn.signal.aborted) break;
1265
1279
  if ("expectInput" in step) continue;
1266
1280
  if ("awaitSteer" in step) {
1267
1281
  const ready = await turn.awaitSteer?.(step.awaitSteer.timeoutMs, turn.signal) ?? false;
1268
1282
  if (turn.signal.aborted) break;
1269
1283
  lines.push(`mcp-send awaitSteer${ready ? ": ready" : " failed: timeout"}`);
1284
+ const after = (playback.current ?? picked) + 1;
1285
+ const following = playback.line?.turns[after];
1286
+ if (!ready && step.awaitSteer.continueOnTimeout === true && following !== void 0 && !playback.consumed.has(after)) {
1287
+ playback.consumed.add(after);
1288
+ playback.current = after;
1289
+ queue.push(...following);
1290
+ }
1270
1291
  } else if ("wait" in step) {
1271
1292
  const missing = opened.missing(step.wait.server);
1272
1293
  if (step.wait.server !== void 0 && missing !== void 0) {
@@ -1282,7 +1303,19 @@ function createPlaybookRunner(input) {
1282
1303
  lines.push(`mcp-send ${step.waitFor.tool} failed: ${missing}`);
1283
1304
  continue;
1284
1305
  }
1285
- const ready = await waitForCount(step, safeTurn, opened);
1306
+ let args;
1307
+ try {
1308
+ args = resolveArguments(
1309
+ step.waitFor.arguments ?? {},
1310
+ turn.threadId,
1311
+ playback.results,
1312
+ playback.history
1313
+ );
1314
+ } catch (error) {
1315
+ lines.push(`mcp-send ${step.waitFor.tool} failed: ${String(error)}`);
1316
+ continue;
1317
+ }
1318
+ const ready = await waitForCount(step, args, safeTurn, opened);
1286
1319
  if (turn.signal.aborted) break;
1287
1320
  const { id, tool, path: path2, atLeast } = step.waitFor;
1288
1321
  if (ready !== void 0 && id !== void 0) playback.results.set(id, ready.result);
@@ -1863,7 +1896,8 @@ ${more ?? "playbook: steered input not handled"}`;
1863
1896
  const steerable = script === "lane" || playbook !== void 0;
1864
1897
  const steered = acceptLaneSteer({
1865
1898
  running,
1866
- enabled: steerable,
1899
+ // 剧本只在正在跑的那一批写明 awaitSteer 时收插话;其余照下面「目标轮已收口」一样拒,宿主另起一轮。
1900
+ enabled: script === "lane" || (playbook?.expectsSteer(input.threadId) ?? false),
1867
1901
  expectedTurnId: input.expectedTurnId,
1868
1902
  text: input.input,
1869
1903
  threadId: input.threadId,
@@ -1956,7 +1990,7 @@ var executors = [
1956
1990
  ];
1957
1991
  var index_default = definePlugin({
1958
1992
  id: "agent-fake",
1959
- apiVersion: 36,
1993
+ apiVersion: 37,
1960
1994
  executors,
1961
1995
  activate(ctx) {
1962
1996
  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.3377",
3
+ "version": "0.2.3389",
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.3377",
24
+ "@ycodium-ai/plugin-api": "0.2.3389",
25
25
  "ycodium-executor-testing": "0.0.0"
26
26
  },
27
27
  "ycodium": {