@ycodium-ai/agent-fake 0.2.3230 → 0.2.3232

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 +32 -1
  2. package/dist/index.js +264 -127
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -4,7 +4,7 @@ A minimal executor plugin. It starts no CLI and replays a fixed event script:
4
4
 
5
5
  - `startSession` emits `session.started`
6
6
  - `sendTurn` emits `turn.started`, one `content.delta`, then `turn.completed` (the host binds the turn by `turn.started`)
7
- - `config.script` picks the script: `plain` (default), `approval-tool` (two approvals before the reply), `async-question` (on a thread's first turn, the reply plus one non-blocking `user-input.requested` with `responseMode: "message"`), `stream` (a Markdown answer in `config.streamChunks` deltas, default 200, one every `config.streamIntervalMs`, default 30; the UI probe `interaction-table.ts` measures streaming with it), `mcp-send` (real calls through the built-in MCP server the host handed to `startSession`, the same path a real agent takes: a turn whose input is a JSON object is passed as the arguments of one `send_message` call, and a JSON array of `{ "tool": "…", "arguments": { … } }` is called step by step, for example `ycodium_plugin_define`, then `ycodium_plugin_run`, then a tool the mounted plugin contributes; the reply has one line per call with its result or error, and any other input gets the plain reply; see [Playbook mode](#playbook-mode) for per-thread scripts that ignore turn input), `visualize` (emits one in-progress builtin tool row, holds for `config.previewHoldMs` milliseconds so the input preview can paint, then one real `visualize_visualize` call on that same server; the tool row’s result is the MCP confirmation text, and the reply is “The figure is in the card above.”), or `lane` (the UI probe `lane1-journey.ts` script: the user message's `lane:stream|plain|approve|question|attach|hold` picks the turn; `hold` accepts a second `sendTurn` with the in-flight turn id as a steer and folds that text into the reply; `config.holdMs` is how long `hold` waits, default 15000)
7
+ - `config.script` picks the script: `plain` (default), `approval-tool` (two approvals before the reply), `async-question` (on a thread's first turn, the reply plus one non-blocking `user-input.requested` with `responseMode: "message"`), `stream` (a Markdown answer in `config.streamChunks` deltas, default 200, one every `config.streamIntervalMs`, default 30; the UI probe `interaction-table.ts` measures streaming with it), `replay` (recorded events; see [Event replay](#event-replay)), `mcp-send` (real calls through the built-in MCP server the host handed to `startSession`, the same path a real agent takes: a turn whose input is a JSON object is passed as the arguments of one `send_message` call, and a JSON array of `{ "tool": "…", "arguments": { … } }` is called step by step, for example `ycodium_plugin_define`, then `ycodium_plugin_run`, then a tool the mounted plugin contributes; the reply has one line per call with its result or error, and any other input gets the plain reply; see [Playbook mode](#playbook-mode) for per-thread scripts that ignore turn input), `visualize` (emits one in-progress builtin tool row, holds for `config.previewHoldMs` milliseconds so the input preview can paint, then one real `visualize_visualize` call on that same server; the tool row’s result is the MCP confirmation text, and the reply is “The figure is in the card above.”), or `lane` (the UI probe `lane1-journey.ts` script: the user message's `lane:stream|plain|approve|question|attach|hold` picks the turn; `hold` accepts a second `sendTurn` with the in-flight turn id as a steer and folds that text into the reply; `config.holdMs` is how long `hold` waits, default 15000)
8
8
  - `interruptTurn` (or an aborted `AbortSignal`) ends the in-flight turn as `interrupted`
9
9
  - `readThread` returns the bookkeeping the instance recorded
10
10
  - `probe` reports two models, `fake` (default) and `fake-large`
@@ -23,6 +23,37 @@ This package is **not** in the default bundle and **not** a first-party add-grid
23
23
 
24
24
  Develop with `vp run --filter @ycodium-ai/agent-fake typecheck`, `build`, and `test`.
25
25
 
26
+ ## Event replay
27
+
28
+ Set `config.script: "replay"` to replay recorded `ExecutorEvent` output through the fake's
29
+ event emitter. `config.replayEventsPath` must be an absolute path on the executor's machine
30
+ to a JSON array or an adapter golden object with an `events` array. The file is read on each
31
+ turn; an unreadable file, invalid event envelope or malformed JSON fails that turn with a
32
+ `replay:` error. The plugin does not bundle recordings or depend on another executor plugin.
33
+
34
+ ```json
35
+ {
36
+ "script": "replay",
37
+ "replayEventsPath": "/absolute/path/to/recorded-events.json",
38
+ "replayStepMs": 50,
39
+ "replayTaskHoldMs": 4000
40
+ }
41
+ ```
42
+
43
+ `replayStepMs` sets the interval between retained events (default 30 ms).
44
+ `replayTaskHoldMs` adds a hold after every `task.started` (default 3000 ms), so observers can
45
+ read the working state before completion. Both are non-negative integers; zero disables
46
+ that delay. These settings are outside the settings form. Timing is configured, not recorded.
47
+
48
+ Replay keeps items, content, tasks, plans and other turn-local events. Lifecycle events
49
+ (`isExecutorLifecycleEvent` in the plugin API: session, thread and turn lifecycle, plus
50
+ `runtime.error`, which the host turns into a session error) are owned by the fake and never
51
+ replayed, so a recording cannot fail the turn behind the fake's back. See the allowlist in
52
+ [`replayScript.ts`](src/replayScript.ts). Events get the current thread and turn IDs and fresh
53
+ event metadata. Item, request and task IDs are fresh per replay, with tool/task links kept
54
+ consistent. Adapter golden `⟨undefined⟩` fields are omitted. Assistant text is also recorded
55
+ by `readThread`. Interrupting a replay cancels its delays and ends the turn as `interrupted`.
56
+
26
57
  ## Playbook mode
27
58
 
28
59
  Replay recorded conversations or drive multi-thread acceptance tests with
package/dist/index.js CHANGED
@@ -2,6 +2,16 @@
2
2
  import { definePlugin } from "@ycodium-ai/plugin-api";
3
3
 
4
4
  // src/card.ts
5
+ var FAKE_SCRIPTS = [
6
+ "plain",
7
+ "approval-tool",
8
+ "async-question",
9
+ "stream",
10
+ "replay",
11
+ "mcp-send",
12
+ "visualize",
13
+ "lane"
14
+ ];
5
15
  var FAKE_DRIVER_KIND = "fake";
6
16
  var FAKE_ASSISTANT_TEXT = "fake turn complete";
7
17
  var FAKE_CARD = {
@@ -45,18 +55,13 @@ var FAKE_CARD = {
45
55
  properties: {
46
56
  script: {
47
57
  type: "string",
48
- enum: [
49
- "plain",
50
- "approval-tool",
51
- "async-question",
52
- "stream",
53
- "mcp-send",
54
- "visualize",
55
- "lane"
56
- ]
58
+ enum: [...FAKE_SCRIPTS]
57
59
  },
58
60
  streamChunks: { type: "integer", minimum: 1 },
59
61
  streamIntervalMs: { type: "integer", minimum: 0 },
62
+ replayEventsPath: { type: "string" },
63
+ replayStepMs: { type: "integer", minimum: 0 },
64
+ replayTaskHoldMs: { type: "integer", minimum: 0 },
60
65
  // 仅探针与测试故意违约:stop 收完后台轮但不发 session.exited,事件流仍打开;默认关闭,不进设置表单。
61
66
  stopWithoutExit: { type: "boolean" },
62
67
  // lane 的 hold 最多等插话多久;不进设置表单。
@@ -125,6 +130,131 @@ var EventBus = class {
125
130
  }
126
131
  };
127
132
 
133
+ // src/approvalToolScript.ts
134
+ function createApprovalToolScript({
135
+ emit,
136
+ nextId,
137
+ pendingDecisions,
138
+ yieldTick: yieldTick2
139
+ }) {
140
+ const runApprovalToolScript = async (threadId, turnId, signal) => {
141
+ const isAborted = () => signal?.aborted === true;
142
+ const fakeReply = "\u597D";
143
+ emit(
144
+ "content.delta",
145
+ threadId,
146
+ {
147
+ streamKind: "reasoning_text",
148
+ delta: "\u5148\u60F3\u6E05\u695A\u6B65\u9AA4\uFF1A\u7B2C\u4E00\u6B65 ls \u5F53\u524D\u76EE\u5F55\uFF1B\u7B2C\u4E8C\u6B65\u8BFB README.md \u7684\u7B2C\u4E00\u884C\uFF1B\u6700\u540E\u53EA\u56DE\u4E00\u4E2A\u5B57\u3002"
149
+ },
150
+ { turnId }
151
+ );
152
+ await yieldTick2();
153
+ if (isAborted()) return null;
154
+ const approve = async (request) => {
155
+ emit(
156
+ "item.started",
157
+ threadId,
158
+ {
159
+ itemType: request.itemType,
160
+ status: "inProgress",
161
+ title: request.title,
162
+ data: request.args
163
+ },
164
+ { turnId, itemId: request.itemId }
165
+ );
166
+ emit(
167
+ "request.opened",
168
+ threadId,
169
+ {
170
+ requestType: request.requestType,
171
+ toolName: request.toolName,
172
+ detail: request.detail,
173
+ args: request.args
174
+ },
175
+ { turnId, requestId: request.requestId }
176
+ );
177
+ const decision = await new Promise((resolve) => {
178
+ let settled = false;
179
+ const settle = (value) => {
180
+ if (settled) return;
181
+ settled = true;
182
+ signal?.removeEventListener("abort", onAbort);
183
+ pendingDecisions.delete(request.requestId);
184
+ resolve(value);
185
+ };
186
+ const onAbort = () => settle("cancel");
187
+ signal?.addEventListener("abort", onAbort);
188
+ if (isAborted()) {
189
+ settle("cancel");
190
+ return;
191
+ }
192
+ pendingDecisions.set(request.requestId, settle);
193
+ });
194
+ emit(
195
+ "request.resolved",
196
+ threadId,
197
+ {
198
+ requestType: request.requestType,
199
+ decision: decision === "allow" ? "accept" : decision === "deny" ? "decline" : "cancel"
200
+ },
201
+ { turnId, requestId: request.requestId }
202
+ );
203
+ if (decision === "allow") {
204
+ emit(
205
+ "content.delta",
206
+ threadId,
207
+ { streamKind: "command_output", delta: request.output },
208
+ { turnId, itemId: request.itemId }
209
+ );
210
+ }
211
+ emit(
212
+ "item.completed",
213
+ threadId,
214
+ {
215
+ itemType: request.itemType,
216
+ status: decision === "allow" ? "completed" : "declined",
217
+ title: request.title,
218
+ data: { ...request.args, ...decision === "allow" ? { output: request.output } : {} }
219
+ },
220
+ { turnId, itemId: request.itemId }
221
+ );
222
+ return decision;
223
+ };
224
+ const first = await approve({
225
+ itemId: nextId("item"),
226
+ requestId: nextId("req"),
227
+ requestType: "exec_command_approval",
228
+ toolName: "shell",
229
+ detail: "ls",
230
+ args: { command: "ls" },
231
+ itemType: "command_execution",
232
+ title: "ls",
233
+ output: "README.md\n"
234
+ });
235
+ if (first === "cancel") return null;
236
+ if (first === "allow") {
237
+ const second = await approve({
238
+ itemId: nextId("item"),
239
+ requestId: nextId("req"),
240
+ requestType: "file_read_approval",
241
+ toolName: "Read",
242
+ detail: "README.md",
243
+ args: { file_path: "README.md" },
244
+ itemType: "dynamic_tool_call",
245
+ title: "Read README.md",
246
+ output: "# hands-probe-workspace\n"
247
+ });
248
+ if (second === "cancel") return null;
249
+ }
250
+ if (isAborted()) return null;
251
+ emit("content.delta", threadId, { streamKind: "assistant_text", delta: fakeReply }, { turnId });
252
+ await yieldTick2();
253
+ return isAborted() ? null : fakeReply;
254
+ };
255
+ return runApprovalToolScript;
256
+ }
257
+
128
258
  // src/fakeSessionLifecycle.ts
129
259
  var parseStopWithoutExit = (input) => {
130
260
  const envelope = input;
@@ -1239,6 +1369,112 @@ async function runVisualizeTurn(input) {
1239
1369
  return resultText.length > 0 ? REPLY : "visualize failed: empty tool result";
1240
1370
  }
1241
1371
 
1372
+ // src/replayScript.ts
1373
+ import * as NodeFSP2 from "node:fs/promises";
1374
+ import * as NodePath2 from "node:path";
1375
+ import { isExecutorLifecycleEvent } from "@ycodium-ai/plugin-api/executor";
1376
+ var IN_TURN_TYPES = /* @__PURE__ */ new Set([
1377
+ "item.started",
1378
+ "item.updated",
1379
+ "item.completed",
1380
+ "content.delta",
1381
+ "task.started",
1382
+ "task.progress",
1383
+ "task.updated",
1384
+ "task.completed",
1385
+ "turn.plan.updated",
1386
+ "turn.proposed.delta",
1387
+ "turn.proposed.completed",
1388
+ "turn.diff.updated",
1389
+ "request.opened",
1390
+ "request.resolved",
1391
+ "user-input.requested",
1392
+ "user-input.resolved",
1393
+ "hook.started",
1394
+ "hook.progress",
1395
+ "hook.completed",
1396
+ "tool.progress",
1397
+ "tool.summary",
1398
+ "tool.denied",
1399
+ "model.rerouted",
1400
+ "files.persisted",
1401
+ "assistant.attachment.produced",
1402
+ "assistant.artifact.produced",
1403
+ "runtime.warning"
1404
+ ]);
1405
+ var IDENTITY_FIELDS = [
1406
+ ["taskId", "task"],
1407
+ ["agentId", "task"],
1408
+ ["parentAgentId", "task"],
1409
+ ["toolUseId", "item"],
1410
+ ["parentToolUseId", "item"]
1411
+ ];
1412
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1413
+ function parseEvents(text) {
1414
+ const parsed = JSON.parse(
1415
+ text,
1416
+ (_key, value) => value === "\u27E8undefined\u27E9" ? void 0 : value
1417
+ );
1418
+ const events = isRecord3(parsed) ? parsed.events : parsed;
1419
+ if (!Array.isArray(events))
1420
+ throw new Error("expected ExecutorEvent[] or { events: ExecutorEvent[] }");
1421
+ return events.map((event, index) => {
1422
+ if (!isRecord3(event) || !["type", "eventId", "provider", "threadId", "createdAt"].every(
1423
+ (key) => typeof event[key] === "string"
1424
+ ) || !isRecord3(event.payload) || !["turnId", "itemId", "requestId"].every(
1425
+ (key) => event[key] === void 0 || typeof event[key] === "string"
1426
+ ))
1427
+ throw new Error(`invalid ExecutorEvent at index ${index}`);
1428
+ return event;
1429
+ }).filter((event) => !isExecutorLifecycleEvent(event) && IN_TURN_TYPES.has(event.type));
1430
+ }
1431
+ async function runReplayTurn(input) {
1432
+ const config = input.config?.config;
1433
+ const path = config?.replayEventsPath;
1434
+ const duration = (key, fallback) => {
1435
+ const value = config?.[key] ?? fallback;
1436
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
1437
+ throw new Error(`replay: ${key} must be a non-negative integer`);
1438
+ return value;
1439
+ };
1440
+ const stepMs = duration("replayStepMs", 30);
1441
+ const taskHoldMs = duration("replayTaskHoldMs", 3e3);
1442
+ if (typeof path !== "string" || !NodePath2.isAbsolute(path))
1443
+ throw new Error("replay: replayEventsPath must be an absolute path");
1444
+ let events;
1445
+ try {
1446
+ events = parseEvents(await NodeFSP2.readFile(path, "utf8"));
1447
+ } catch (error) {
1448
+ throw new Error(`replay: ${path}: ${error instanceof Error ? error.message : String(error)}`, {
1449
+ cause: error
1450
+ });
1451
+ }
1452
+ const ids = /* @__PURE__ */ new Map();
1453
+ const remap = (kind, id) => {
1454
+ const key = `${kind}:${id}`;
1455
+ if (!ids.has(key)) ids.set(key, input.nextId(kind));
1456
+ return ids.get(key);
1457
+ };
1458
+ let text = "";
1459
+ for (const [index, event] of events.entries()) {
1460
+ if (index > 0) await hold(stepMs, input.signal);
1461
+ if (input.signal.aborted) return null;
1462
+ const payload = { ...event.payload };
1463
+ for (const [key, kind] of IDENTITY_FIELDS) {
1464
+ if (typeof payload[key] === "string") payload[key] = remap(kind, payload[key]);
1465
+ }
1466
+ input.emit(event.type, input.threadId, payload, {
1467
+ turnId: input.turnId,
1468
+ ...event.itemId === void 0 ? {} : { itemId: remap("item", event.itemId) },
1469
+ ...event.requestId === void 0 ? {} : { requestId: remap("request", event.requestId) }
1470
+ });
1471
+ if (event.type === "content.delta" && payload.streamKind === "assistant_text" && typeof payload.delta === "string")
1472
+ text += payload.delta;
1473
+ if (event.type === "task.started") await hold(taskHoldMs, input.signal);
1474
+ }
1475
+ return input.signal.aborted ? null : text;
1476
+ }
1477
+
1242
1478
  // src/fakeInstance.ts
1243
1479
  var yieldTick = () => Promise.resolve();
1244
1480
  var FAKE_ASYNC_QUESTIONS = [
@@ -1265,7 +1501,7 @@ var FAKE_ASYNC_QUESTIONS = [
1265
1501
  function parseScript(input) {
1266
1502
  const envelope = input;
1267
1503
  const script = envelope?.config?.script;
1268
- return script === "approval-tool" || script === "async-question" || script === "stream" || script === "mcp-send" || script === "visualize" || script === "lane" ? script : "plain";
1504
+ return FAKE_SCRIPTS.find((name) => name === script) ?? "plain";
1269
1505
  }
1270
1506
  var parseReplyText = (input) => {
1271
1507
  const envelope = input;
@@ -1370,121 +1606,12 @@ var createFakeInstance = (config, host) => {
1370
1606
  if (server === null) return "mcp-send: no MCP server was handed to this session";
1371
1607
  return runMcpCalls({ fetch: host.fetch, server, calls, signal });
1372
1608
  };
1373
- const runApprovalToolScript = async (threadId, turnId, signal) => {
1374
- const isAborted = () => signal?.aborted === true;
1375
- const fakeReply = "\u597D";
1376
- emit(
1377
- "content.delta",
1378
- threadId,
1379
- {
1380
- streamKind: "reasoning_text",
1381
- delta: "\u5148\u60F3\u6E05\u695A\u6B65\u9AA4\uFF1A\u7B2C\u4E00\u6B65 ls \u5F53\u524D\u76EE\u5F55\uFF1B\u7B2C\u4E8C\u6B65\u8BFB README.md \u7684\u7B2C\u4E00\u884C\uFF1B\u6700\u540E\u53EA\u56DE\u4E00\u4E2A\u5B57\u3002"
1382
- },
1383
- { turnId }
1384
- );
1385
- await yieldTick();
1386
- if (isAborted()) return null;
1387
- const approve = async (request) => {
1388
- emit(
1389
- "item.started",
1390
- threadId,
1391
- {
1392
- itemType: request.itemType,
1393
- status: "inProgress",
1394
- title: request.title,
1395
- data: request.args
1396
- },
1397
- { turnId, itemId: request.itemId }
1398
- );
1399
- emit(
1400
- "request.opened",
1401
- threadId,
1402
- {
1403
- requestType: request.requestType,
1404
- toolName: request.toolName,
1405
- detail: request.detail,
1406
- args: request.args
1407
- },
1408
- { turnId, requestId: request.requestId }
1409
- );
1410
- const decision = await new Promise((resolve) => {
1411
- let settled = false;
1412
- const settle = (value) => {
1413
- if (settled) return;
1414
- settled = true;
1415
- signal?.removeEventListener("abort", onAbort);
1416
- pendingDecisions.delete(request.requestId);
1417
- resolve(value);
1418
- };
1419
- const onAbort = () => settle("cancel");
1420
- signal?.addEventListener("abort", onAbort);
1421
- if (isAborted()) {
1422
- settle("cancel");
1423
- return;
1424
- }
1425
- pendingDecisions.set(request.requestId, settle);
1426
- });
1427
- emit(
1428
- "request.resolved",
1429
- threadId,
1430
- {
1431
- requestType: request.requestType,
1432
- decision: decision === "allow" ? "accept" : decision === "deny" ? "decline" : "cancel"
1433
- },
1434
- { turnId, requestId: request.requestId }
1435
- );
1436
- if (decision === "allow") {
1437
- emit(
1438
- "content.delta",
1439
- threadId,
1440
- { streamKind: "command_output", delta: request.output },
1441
- { turnId, itemId: request.itemId }
1442
- );
1443
- }
1444
- emit(
1445
- "item.completed",
1446
- threadId,
1447
- {
1448
- itemType: request.itemType,
1449
- status: decision === "allow" ? "completed" : "declined",
1450
- title: request.title,
1451
- data: { ...request.args, ...decision === "allow" ? { output: request.output } : {} }
1452
- },
1453
- { turnId, itemId: request.itemId }
1454
- );
1455
- return decision;
1456
- };
1457
- const first = await approve({
1458
- itemId: nextId("item"),
1459
- requestId: nextId("req"),
1460
- requestType: "exec_command_approval",
1461
- toolName: "shell",
1462
- detail: "ls",
1463
- args: { command: "ls" },
1464
- itemType: "command_execution",
1465
- title: "ls",
1466
- output: "README.md\n"
1467
- });
1468
- if (first === "cancel") return null;
1469
- if (first === "allow") {
1470
- const second = await approve({
1471
- itemId: nextId("item"),
1472
- requestId: nextId("req"),
1473
- requestType: "file_read_approval",
1474
- toolName: "Read",
1475
- detail: "README.md",
1476
- args: { file_path: "README.md" },
1477
- itemType: "dynamic_tool_call",
1478
- title: "Read README.md",
1479
- output: "# hands-probe-workspace\n"
1480
- });
1481
- if (second === "cancel") return null;
1482
- }
1483
- if (isAborted()) return null;
1484
- emit("content.delta", threadId, { streamKind: "assistant_text", delta: fakeReply }, { turnId });
1485
- await yieldTick();
1486
- return isAborted() ? null : fakeReply;
1487
- };
1609
+ const runApprovalToolScript = createApprovalToolScript({
1610
+ emit,
1611
+ nextId,
1612
+ pendingDecisions,
1613
+ yieldTick
1614
+ });
1488
1615
  const runScriptedTurn = async (booked, threadId, turnId, signal, input) => {
1489
1616
  const aborted = () => signal.aborted;
1490
1617
  await yieldTick();
@@ -1572,6 +1699,16 @@ var createFakeInstance = (config, host) => {
1572
1699
  ]);
1573
1700
  return;
1574
1701
  }
1702
+ if (script === "replay") {
1703
+ const text = await runReplayTurn({ config, threadId, turnId, signal, emit, nextId });
1704
+ finishTurn(
1705
+ booked,
1706
+ turnId,
1707
+ aborted() || text === null ? "interrupted" : "completed",
1708
+ text === null || text === "" ? [] : [{ kind: "assistant", text }]
1709
+ );
1710
+ return;
1711
+ }
1575
1712
  if (script === "lane") {
1576
1713
  const text = await runLaneTurn({
1577
1714
  threadId,
@@ -1693,7 +1830,7 @@ var createFakeInstance = (config, host) => {
1693
1830
  signal?.removeEventListener("abort", abortTurn);
1694
1831
  if (inflight.get(input.threadId)?.turnId === turnId) inflight.delete(input.threadId);
1695
1832
  };
1696
- const background = script === "approval-tool" || script === "stream" || script === "mcp-send" || script === "visualize" || script === "lane";
1833
+ const background = script === "approval-tool" || script === "stream" || script === "replay" || script === "mcp-send" || script === "visualize" || script === "lane";
1697
1834
  if (background) signal?.removeEventListener("abort", abortTurn);
1698
1835
  booked.turnWork = runFakeTurn(
1699
1836
  run,
@@ -1751,7 +1888,7 @@ var executors = [
1751
1888
  ];
1752
1889
  var index_default = definePlugin({
1753
1890
  id: "agent-fake",
1754
- apiVersion: 23,
1891
+ apiVersion: 24,
1755
1892
  executors,
1756
1893
  activate(ctx) {
1757
1894
  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.3230",
3
+ "version": "0.2.3232",
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.3230",
24
+ "@ycodium-ai/plugin-api": "0.2.3232",
25
25
  "ycodium-executor-testing": "0.0.0"
26
26
  },
27
27
  "ycodium": {