@ycodium-ai/agent-fake 0.2.2938 → 0.2.2944
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 +1 -1
- package/dist/index.js +145 -21
- 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),
|
|
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), or `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.”)
|
|
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`
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ var FAKE_CARD = {
|
|
|
42
42
|
properties: {
|
|
43
43
|
script: {
|
|
44
44
|
type: "string",
|
|
45
|
-
enum: ["plain", "approval-tool", "async-question", "stream", "mcp-send"]
|
|
45
|
+
enum: ["plain", "approval-tool", "async-question", "stream", "mcp-send", "visualize"]
|
|
46
46
|
},
|
|
47
47
|
streamChunks: { type: "integer", minimum: 1 },
|
|
48
48
|
streamIntervalMs: { type: "integer", minimum: 0 },
|
|
@@ -250,6 +250,124 @@ async function callMcpTool(input) {
|
|
|
250
250
|
);
|
|
251
251
|
return called.message?.result;
|
|
252
252
|
}
|
|
253
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
254
|
+
function parseMcpSendInput(input) {
|
|
255
|
+
let parsed;
|
|
256
|
+
try {
|
|
257
|
+
parsed = JSON.parse(input ?? "");
|
|
258
|
+
} catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
if (isRecord(parsed)) return [{ tool: "send_message", arguments: parsed }];
|
|
262
|
+
if (!Array.isArray(parsed) || parsed.length === 0) return null;
|
|
263
|
+
const calls = [];
|
|
264
|
+
for (const entry of parsed) {
|
|
265
|
+
if (!isRecord(entry) || typeof entry.tool !== "string" || entry.tool.length === 0) return null;
|
|
266
|
+
const args = entry.arguments ?? {};
|
|
267
|
+
if (!isRecord(args)) return null;
|
|
268
|
+
calls.push({ tool: entry.tool, arguments: args });
|
|
269
|
+
}
|
|
270
|
+
return calls;
|
|
271
|
+
}
|
|
272
|
+
async function runMcpCalls(input) {
|
|
273
|
+
const lines = [];
|
|
274
|
+
for (const call of input.calls) {
|
|
275
|
+
try {
|
|
276
|
+
const result = await callMcpTool({
|
|
277
|
+
fetch: input.fetch,
|
|
278
|
+
server: input.server,
|
|
279
|
+
name: call.tool,
|
|
280
|
+
arguments: call.arguments
|
|
281
|
+
});
|
|
282
|
+
lines.push(`mcp-send ${call.tool}: ${JSON.stringify(result)}`);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
lines.push(`mcp-send ${call.tool} failed: ${String(error)}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return lines.join("\n");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/visualizeScript.ts
|
|
291
|
+
var WIRE_NAME = "mcp__ycodium__visualize_visualize";
|
|
292
|
+
var TITLE = "Monthly sales";
|
|
293
|
+
var FRAGMENT = "<section><h1>Monthly sales</h1><p>Jan 12 \xB7 Feb 18 \xB7 Mar 15</p></section>";
|
|
294
|
+
var REPLY = "The figure is in the card above.";
|
|
295
|
+
function parsePreviewHoldMs(input) {
|
|
296
|
+
const config = input?.config;
|
|
297
|
+
const raw = config?.previewHoldMs;
|
|
298
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0) return 0;
|
|
299
|
+
return Math.min(3e4, Math.floor(raw));
|
|
300
|
+
}
|
|
301
|
+
function confirmationText(result) {
|
|
302
|
+
const content = result?.content;
|
|
303
|
+
if (!Array.isArray(content)) return "";
|
|
304
|
+
const chunks = [];
|
|
305
|
+
for (const entry of content) {
|
|
306
|
+
const text = entry?.text;
|
|
307
|
+
if (typeof text === "string" && text.length > 0) chunks.push(text);
|
|
308
|
+
}
|
|
309
|
+
return chunks.join("\n");
|
|
310
|
+
}
|
|
311
|
+
function hold(ms, signal) {
|
|
312
|
+
if (ms === 0 || signal.aborted) return Promise.resolve();
|
|
313
|
+
return new Promise((resolve) => {
|
|
314
|
+
let timer;
|
|
315
|
+
const finish = () => {
|
|
316
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
317
|
+
signal.removeEventListener("abort", finish);
|
|
318
|
+
resolve();
|
|
319
|
+
};
|
|
320
|
+
timer = setTimeout(finish, ms);
|
|
321
|
+
signal.addEventListener("abort", finish, { once: true });
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
async function runVisualizeTurn(input) {
|
|
325
|
+
const args = JSON.stringify({ action: "create", title: TITLE, fragment: FRAGMENT });
|
|
326
|
+
const tool = (status, result) => ({
|
|
327
|
+
itemType: "dynamic_tool_call",
|
|
328
|
+
status,
|
|
329
|
+
title: WIRE_NAME,
|
|
330
|
+
data: {
|
|
331
|
+
toolName: WIRE_NAME,
|
|
332
|
+
argumentText: args,
|
|
333
|
+
...result === void 0 ? {} : { result }
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
input.emit("item.started", input.threadId, tool("inProgress"), {
|
|
337
|
+
turnId: input.turnId,
|
|
338
|
+
itemId: input.itemId
|
|
339
|
+
});
|
|
340
|
+
await hold(input.holdMs, input.signal);
|
|
341
|
+
if (input.signal.aborted) return null;
|
|
342
|
+
const server = builtinMcpServer(input.mcpServers);
|
|
343
|
+
if (server === null) {
|
|
344
|
+
const failure = "visualize: no MCP server was handed to this session";
|
|
345
|
+
input.emit("item.completed", input.threadId, tool("completed", failure), {
|
|
346
|
+
turnId: input.turnId,
|
|
347
|
+
itemId: input.itemId
|
|
348
|
+
});
|
|
349
|
+
return failure;
|
|
350
|
+
}
|
|
351
|
+
let resultText;
|
|
352
|
+
try {
|
|
353
|
+
resultText = confirmationText(
|
|
354
|
+
await callMcpTool({
|
|
355
|
+
fetch: input.fetch,
|
|
356
|
+
server,
|
|
357
|
+
name: "visualize_visualize",
|
|
358
|
+
arguments: { action: "create", title: TITLE, fragment: FRAGMENT }
|
|
359
|
+
})
|
|
360
|
+
);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
resultText = `visualize failed: ${String(error)}`;
|
|
363
|
+
}
|
|
364
|
+
if (input.signal.aborted) return null;
|
|
365
|
+
input.emit("item.completed", input.threadId, tool("completed", resultText), {
|
|
366
|
+
turnId: input.turnId,
|
|
367
|
+
itemId: input.itemId
|
|
368
|
+
});
|
|
369
|
+
return resultText.length > 0 ? REPLY : "visualize failed: empty tool result";
|
|
370
|
+
}
|
|
253
371
|
|
|
254
372
|
// src/fakeInstance.ts
|
|
255
373
|
var yieldTick = () => Promise.resolve();
|
|
@@ -309,7 +427,7 @@ var FAKE_ASYNC_QUESTIONS = [
|
|
|
309
427
|
function parseScript(input) {
|
|
310
428
|
const envelope = input;
|
|
311
429
|
const script = envelope?.config?.script;
|
|
312
|
-
return script === "approval-tool" || script === "async-question" || script === "stream" || script === "mcp-send" ? script : "plain";
|
|
430
|
+
return script === "approval-tool" || script === "async-question" || script === "stream" || script === "mcp-send" || script === "visualize" ? script : "plain";
|
|
313
431
|
}
|
|
314
432
|
var FAKE_STREAM_TEXT = [
|
|
315
433
|
"Here is the plan for the change, step by step.\n\n",
|
|
@@ -353,6 +471,7 @@ var createFakeInstance = (config, host) => {
|
|
|
353
471
|
const replyText = parseReplyText(config);
|
|
354
472
|
const failStartSession = parseFailStartSession(config);
|
|
355
473
|
const streamOptions = parseStreamOptions(config);
|
|
474
|
+
const previewHoldMs = parsePreviewHoldMs(config);
|
|
356
475
|
const pendingDecisions = /* @__PURE__ */ new Map();
|
|
357
476
|
let seq = 0;
|
|
358
477
|
const inflight = /* @__PURE__ */ new Map();
|
|
@@ -397,26 +516,11 @@ var createFakeInstance = (config, host) => {
|
|
|
397
516
|
}
|
|
398
517
|
};
|
|
399
518
|
const runMcpSendScript = async (booked, input) => {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
args = JSON.parse(input ?? "");
|
|
403
|
-
} catch {
|
|
404
|
-
return replyText;
|
|
405
|
-
}
|
|
406
|
-
if (typeof args !== "object" || args === null || Array.isArray(args)) return replyText;
|
|
519
|
+
const calls = parseMcpSendInput(input);
|
|
520
|
+
if (calls === null) return replyText;
|
|
407
521
|
const server = builtinMcpServer(booked.mcpServers);
|
|
408
522
|
if (server === null) return "mcp-send: no MCP server was handed to this session";
|
|
409
|
-
|
|
410
|
-
const result = await callMcpTool({
|
|
411
|
-
fetch: host.fetch,
|
|
412
|
-
server,
|
|
413
|
-
name: "send_message",
|
|
414
|
-
arguments: args
|
|
415
|
-
});
|
|
416
|
-
return `mcp-send: ${JSON.stringify(result)}`;
|
|
417
|
-
} catch (error) {
|
|
418
|
-
return `mcp-send failed: ${String(error)}`;
|
|
419
|
-
}
|
|
523
|
+
return runMcpCalls({ fetch: host.fetch, server, calls });
|
|
420
524
|
};
|
|
421
525
|
const runApprovalToolScript = async (threadId, turnId, signal) => {
|
|
422
526
|
const isAborted = () => signal?.aborted === true;
|
|
@@ -560,6 +664,26 @@ var createFakeInstance = (config, host) => {
|
|
|
560
664
|
]);
|
|
561
665
|
return;
|
|
562
666
|
}
|
|
667
|
+
if (script === "visualize") {
|
|
668
|
+
const text = await runVisualizeTurn({
|
|
669
|
+
threadId,
|
|
670
|
+
turnId,
|
|
671
|
+
itemId: nextId("item"),
|
|
672
|
+
holdMs: previewHoldMs,
|
|
673
|
+
signal,
|
|
674
|
+
fetch: host.fetch,
|
|
675
|
+
mcpServers: booked.mcpServers,
|
|
676
|
+
emit
|
|
677
|
+
});
|
|
678
|
+
if (text === null || aborted()) {
|
|
679
|
+
finishTurn(booked, turnId, "interrupted", []);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
emit("content.delta", threadId, { streamKind: "assistant_text", delta: text }, { turnId });
|
|
683
|
+
await yieldTick();
|
|
684
|
+
finishTurn(booked, turnId, "completed", [{ kind: "assistant", text }]);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
563
687
|
if (script === "mcp-send") {
|
|
564
688
|
const text = await runMcpSendScript(booked, input);
|
|
565
689
|
emit("content.delta", threadId, { streamKind: "assistant_text", delta: text }, { turnId });
|
|
@@ -663,7 +787,7 @@ var createFakeInstance = (config, host) => {
|
|
|
663
787
|
signal?.removeEventListener("abort", abortTurn);
|
|
664
788
|
if (inflight.get(input.threadId)?.turnId === turnId) inflight.delete(input.threadId);
|
|
665
789
|
};
|
|
666
|
-
if (script === "approval-tool" || script === "stream" || script === "mcp-send") {
|
|
790
|
+
if (script === "approval-tool" || script === "stream" || script === "mcp-send" || script === "visualize") {
|
|
667
791
|
signal?.removeEventListener("abort", abortTurn);
|
|
668
792
|
void run().catch((error) => failTurn(booked, turnId, error)).finally(settle);
|
|
669
793
|
return result;
|
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.2944",
|
|
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.2944",
|
|
25
25
|
"ycodium-executor-testing": "0.0.0"
|
|
26
26
|
},
|
|
27
27
|
"ycodium": {
|