@ycodium-ai/agent-fake 0.2.3158 → 0.2.3180

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 (2) hide show
  1. package/dist/index.js +132 -59
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -57,6 +57,8 @@ var FAKE_CARD = {
57
57
  },
58
58
  streamChunks: { type: "integer", minimum: 1 },
59
59
  streamIntervalMs: { type: "integer", minimum: 0 },
60
+ // 仅探针与测试故意违约:stop 收完后台轮但不发 session.exited,事件流仍打开;默认关闭,不进设置表单。
61
+ stopWithoutExit: { type: "boolean" },
60
62
  // lane 的 hold 最多等插话多久;不进设置表单。
61
63
  holdMs: { type: "integer", minimum: 0 },
62
64
  // stream 脚本循环切片的源文本;性能探针用它换上可控的长回复,不进设置表单。
@@ -89,6 +91,90 @@ var FAKE_CARD = {
89
91
  // src/fakeInstance.ts
90
92
  import * as NodeCrypto from "node:crypto";
91
93
 
94
+ // src/fakeEventBus.ts
95
+ var EventBus = class {
96
+ #queued = [];
97
+ #waiters = [];
98
+ #closed = false;
99
+ push(event) {
100
+ const waiter = this.#waiters.shift();
101
+ if (waiter) {
102
+ waiter({ done: false, value: event });
103
+ return;
104
+ }
105
+ this.#queued.push(event);
106
+ }
107
+ close() {
108
+ if (this.#closed) return;
109
+ this.#closed = true;
110
+ while (this.#waiters.length > 0) {
111
+ this.#waiters.shift()?.({ done: true, value: void 0 });
112
+ }
113
+ }
114
+ [Symbol.asyncIterator]() {
115
+ return {
116
+ next: () => {
117
+ const queued = this.#queued.shift();
118
+ if (queued !== void 0) return Promise.resolve({ done: false, value: queued });
119
+ if (this.#closed) return Promise.resolve({ done: true, value: void 0 });
120
+ return new Promise((resolve) => {
121
+ this.#waiters.push(resolve);
122
+ });
123
+ }
124
+ };
125
+ }
126
+ };
127
+
128
+ // src/fakeSessionLifecycle.ts
129
+ var parseStopWithoutExit = (input) => {
130
+ const envelope = input;
131
+ return envelope?.config?.stopWithoutExit === true;
132
+ };
133
+ function runFakeTurn(run, settle, fail) {
134
+ const work = run();
135
+ return (fail === void 0 ? work : work.catch(fail)).finally(settle);
136
+ }
137
+ function createFakeSessionLifecycle(input) {
138
+ let disposal;
139
+ const closeBooked = (threadId, booked) => {
140
+ if (booked.closing !== void 0) return booked.closing;
141
+ const closing = Promise.withResolvers();
142
+ booked.closing = closing.promise;
143
+ void (async () => {
144
+ input.abort(threadId);
145
+ const [cleanup] = await Promise.allSettled([
146
+ (async () => input.stopPlaybook(threadId))(),
147
+ booked.turnWork?.catch(() => void 0)
148
+ ]);
149
+ if (cleanup.status === "rejected") throw cleanup.reason;
150
+ if (input.threads.get(threadId) !== booked) return;
151
+ input.exited(booked.session);
152
+ input.threads.delete(threadId);
153
+ input.forget(threadId);
154
+ })().then(closing.resolve, closing.reject);
155
+ return closing.promise;
156
+ };
157
+ return {
158
+ closeBooked,
159
+ dispose: () => {
160
+ if (disposal !== void 0) return disposal;
161
+ const disposing = Promise.withResolvers();
162
+ disposal = disposing.promise;
163
+ void (async () => {
164
+ const results = await Promise.allSettled(
165
+ [...input.threads].map(([threadId, booked]) => closeBooked(threadId, booked))
166
+ );
167
+ for (const result of results) {
168
+ if (result.status === "rejected") throw result.reason;
169
+ }
170
+ await input.disposePlaybook();
171
+ input.closeEvents();
172
+ })().then(disposing.resolve, disposing.reject);
173
+ return disposal;
174
+ }
175
+ };
176
+ }
177
+
92
178
  // src/laneScript.ts
93
179
  import * as NodeFS from "node:fs";
94
180
  import * as NodePath from "node:path";
@@ -563,12 +649,14 @@ function parseMcpSendInput(input) {
563
649
  async function runMcpCalls(input) {
564
650
  const lines = [];
565
651
  for (const call of input.calls) {
652
+ if (input.signal?.aborted === true) break;
566
653
  try {
567
654
  const result = await callMcpTool({
568
655
  fetch: input.fetch,
569
656
  server: input.server,
570
657
  name: call.tool,
571
- arguments: call.arguments
658
+ arguments: call.arguments,
659
+ ...input.signal === void 0 ? {} : { signal: input.signal }
572
660
  });
573
661
  lines.push(`mcp-send ${call.tool}: ${JSON.stringify(result)}`);
574
662
  } catch (error) {
@@ -1153,38 +1241,6 @@ async function runVisualizeTurn(input) {
1153
1241
 
1154
1242
  // src/fakeInstance.ts
1155
1243
  var yieldTick = () => Promise.resolve();
1156
- var EventBus = class {
1157
- #queued = [];
1158
- #waiters = [];
1159
- #closed = false;
1160
- push(event) {
1161
- const waiter = this.#waiters.shift();
1162
- if (waiter) {
1163
- waiter({ done: false, value: event });
1164
- return;
1165
- }
1166
- this.#queued.push(event);
1167
- }
1168
- close() {
1169
- if (this.#closed) return;
1170
- this.#closed = true;
1171
- while (this.#waiters.length > 0) {
1172
- this.#waiters.shift()?.({ done: true, value: void 0 });
1173
- }
1174
- }
1175
- [Symbol.asyncIterator]() {
1176
- return {
1177
- next: () => {
1178
- const queued = this.#queued.shift();
1179
- if (queued !== void 0) return Promise.resolve({ done: false, value: queued });
1180
- if (this.#closed) return Promise.resolve({ done: true, value: void 0 });
1181
- return new Promise((resolve) => {
1182
- this.#waiters.push(resolve);
1183
- });
1184
- }
1185
- };
1186
- }
1187
- };
1188
1244
  var FAKE_ASYNC_QUESTIONS = [
1189
1245
  {
1190
1246
  id: "q0",
@@ -1232,6 +1288,7 @@ var createFakeInstance = (config, host) => {
1232
1288
  const playbook = script === "mcp-send" ? createPlaybookRunner(config) : void 0;
1233
1289
  const replyText = parseReplyText(config);
1234
1290
  const failStartSession = parseFailStartSession(config);
1291
+ const stopWithoutExit = parseStopWithoutExit(config);
1235
1292
  const streamOptions = parseStreamOptions(config);
1236
1293
  const previewHoldMs = parsePreviewHoldMs(config);
1237
1294
  const laneOptions = laneTurnOptions(config, streamOptions);
@@ -1253,9 +1310,32 @@ var createFakeInstance = (config, host) => {
1253
1310
  payload,
1254
1311
  ...extra?.turnId === void 0 ? {} : { turnId: extra.turnId },
1255
1312
  ...extra?.itemId === void 0 ? {} : { itemId: extra.itemId },
1256
- ...extra?.requestId === void 0 ? {} : { requestId: extra.requestId }
1313
+ ...extra?.requestId === void 0 ? {} : { requestId: extra.requestId },
1314
+ ...extra?.originBindingGeneration === void 0 ? {} : { originBindingGeneration: extra.originBindingGeneration },
1315
+ ...extra?.originResumeCursor === void 0 ? {} : { originResumeCursor: extra.originResumeCursor }
1257
1316
  });
1258
1317
  };
1318
+ const lifecycle = createFakeSessionLifecycle({
1319
+ threads,
1320
+ abort: (threadId) => inflight.get(threadId)?.abort(),
1321
+ stopPlaybook: (threadId) => playbook?.stopSession(threadId),
1322
+ // 代次与续接游标取被关闭的那份 session,不在发送时重查当前会话。
1323
+ exited: (session) => {
1324
+ if (stopWithoutExit) return;
1325
+ emit(
1326
+ "session.exited",
1327
+ session.threadId,
1328
+ { reason: "stopped" },
1329
+ {
1330
+ ...session.bindingGeneration === void 0 ? {} : { originBindingGeneration: session.bindingGeneration },
1331
+ ...session.resumeCursor === void 0 ? {} : { originResumeCursor: session.resumeCursor }
1332
+ }
1333
+ );
1334
+ },
1335
+ forget: (threadId) => steeredInputs.delete(threadId),
1336
+ disposePlaybook: () => playbook?.dispose(),
1337
+ closeEvents: () => events.close()
1338
+ });
1259
1339
  const requireThread = (threadId) => {
1260
1340
  const booked = threads.get(threadId);
1261
1341
  if (!booked) throw new Error(`unknown thread ${threadId}`);
@@ -1283,12 +1363,12 @@ var createFakeInstance = (config, host) => {
1283
1363
  inflight.delete(booked.session.threadId);
1284
1364
  }
1285
1365
  };
1286
- const runMcpSendScript = async (booked, input) => {
1366
+ const runMcpSendScript = async (booked, input, signal) => {
1287
1367
  const calls = parseMcpSendInput(input);
1288
1368
  if (calls === null) return replyText;
1289
1369
  const server = builtinMcpServer(booked.mcpServers);
1290
1370
  if (server === null) return "mcp-send: no MCP server was handed to this session";
1291
- return runMcpCalls({ fetch: host.fetch, server, calls });
1371
+ return runMcpCalls({ fetch: host.fetch, server, calls, signal });
1292
1372
  };
1293
1373
  const runApprovalToolScript = async (threadId, turnId, signal) => {
1294
1374
  const isAborted = () => signal?.aborted === true;
@@ -1470,7 +1550,7 @@ var createFakeInstance = (config, host) => {
1470
1550
  return;
1471
1551
  }
1472
1552
  if (script === "mcp-send") {
1473
- const text = playbookText ?? await runMcpSendScript(booked, input);
1553
+ const text = playbookText ?? await runMcpSendScript(booked, input, signal);
1474
1554
  emit("content.delta", threadId, { streamKind: "assistant_text", delta: text }, { turnId });
1475
1555
  await yieldTick();
1476
1556
  finishTurn(booked, turnId, aborted() ? "interrupted" : "completed", [
@@ -1552,6 +1632,7 @@ var createFakeInstance = (config, host) => {
1552
1632
  startSession: async (input, signal) => {
1553
1633
  if (disposed) throw new Error("instance disposed");
1554
1634
  if (signal?.aborted) throw new Error("aborted");
1635
+ if (threads.get(input.threadId)?.closing !== void 0) throw new Error("session closing");
1555
1636
  if (failStartSession !== void 0) throw new Error(failStartSession);
1556
1637
  const cwd = input.cwd ?? host.workspacePath(input.threadId);
1557
1638
  if (cwd.trim() === "") throw new Error("workspace path missing");
@@ -1581,6 +1662,7 @@ var createFakeInstance = (config, host) => {
1581
1662
  sendTurn: async (input, signal) => {
1582
1663
  if (disposed) throw new Error("instance disposed");
1583
1664
  const booked = requireThread(input.threadId);
1665
+ if (booked.closing !== void 0) throw new Error("session closing");
1584
1666
  const running = inflight.get(input.threadId);
1585
1667
  const steered = acceptLaneSteer({
1586
1668
  running,
@@ -1611,17 +1693,15 @@ var createFakeInstance = (config, host) => {
1611
1693
  signal?.removeEventListener("abort", abortTurn);
1612
1694
  if (inflight.get(input.threadId)?.turnId === turnId) inflight.delete(input.threadId);
1613
1695
  };
1614
- if (script === "approval-tool" || script === "stream" || script === "mcp-send" || script === "visualize" || script === "lane") {
1615
- signal?.removeEventListener("abort", abortTurn);
1616
- void run().catch((error) => failTurn(booked, turnId, error)).finally(settle);
1617
- return result;
1618
- }
1619
- try {
1620
- await run();
1621
- return result;
1622
- } finally {
1623
- settle();
1624
- }
1696
+ const background = script === "approval-tool" || script === "stream" || script === "mcp-send" || script === "visualize" || script === "lane";
1697
+ if (background) signal?.removeEventListener("abort", abortTurn);
1698
+ booked.turnWork = runFakeTurn(
1699
+ run,
1700
+ settle,
1701
+ background ? (error) => failTurn(booked, turnId, error) : void 0
1702
+ );
1703
+ if (!background) await booked.turnWork;
1704
+ return result;
1625
1705
  },
1626
1706
  interruptTurn: async (threadId, turnId) => {
1627
1707
  const running = inflight.get(threadId);
@@ -1642,9 +1722,8 @@ var createFakeInstance = (config, host) => {
1642
1722
  host.log.info("respondToUserInput ignored");
1643
1723
  },
1644
1724
  stopSession: async (threadId) => {
1645
- inflight.get(threadId)?.abort();
1646
- threads.delete(threadId);
1647
- await playbook?.stopSession(threadId);
1725
+ const booked = threads.get(threadId);
1726
+ if (booked !== void 0) await lifecycle.closeBooked(threadId, booked);
1648
1727
  },
1649
1728
  readThread: async (threadId) => {
1650
1729
  const booked = requireThread(threadId);
@@ -1654,13 +1733,7 @@ var createFakeInstance = (config, host) => {
1654
1733
  usage: createFakeUsage({ instanceId: host.instanceId, script: parseUsageScript(config) }),
1655
1734
  dispose: async () => {
1656
1735
  disposed = true;
1657
- for (const running of inflight.values()) running.abort();
1658
- inflight.clear();
1659
- for (const settle of pendingDecisions.values()) settle("cancel");
1660
- pendingDecisions.clear();
1661
- threads.clear();
1662
- await playbook?.dispose();
1663
- events.close();
1736
+ await lifecycle.dispose();
1664
1737
  }
1665
1738
  };
1666
1739
  return instance;
@@ -1678,7 +1751,7 @@ var executors = [
1678
1751
  ];
1679
1752
  var index_default = definePlugin({
1680
1753
  id: "agent-fake",
1681
- apiVersion: 15,
1754
+ apiVersion: 16,
1682
1755
  executors,
1683
1756
  activate(ctx) {
1684
1757
  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.3158",
3
+ "version": "0.2.3180",
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.3158",
24
+ "@ycodium-ai/plugin-api": "0.2.3180",
25
25
  "ycodium-executor-testing": "0.0.0"
26
26
  },
27
27
  "ycodium": {