@ycodium-ai/agent-fake 0.2.2944 → 0.2.2948
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 +309 -26
- package/package.json +3 -3
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),
|
|
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), `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`
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,9 @@ var FAKE_CARD = {
|
|
|
29
29
|
everydayHarness: false,
|
|
30
30
|
capabilities: {
|
|
31
31
|
sessionModelSwitch: "unsupported",
|
|
32
|
-
runtimeModeSwitch: "next-turn"
|
|
32
|
+
runtimeModeSwitch: "next-turn",
|
|
33
|
+
// lane 脚本会把插话并进正在跑的那一轮;不声明的话界面不给「发送到当前轮次」。
|
|
34
|
+
turnSteering: true
|
|
33
35
|
},
|
|
34
36
|
credentialSources: [],
|
|
35
37
|
diagnostics: [],
|
|
@@ -42,10 +44,20 @@ var FAKE_CARD = {
|
|
|
42
44
|
properties: {
|
|
43
45
|
script: {
|
|
44
46
|
type: "string",
|
|
45
|
-
enum: [
|
|
47
|
+
enum: [
|
|
48
|
+
"plain",
|
|
49
|
+
"approval-tool",
|
|
50
|
+
"async-question",
|
|
51
|
+
"stream",
|
|
52
|
+
"mcp-send",
|
|
53
|
+
"visualize",
|
|
54
|
+
"lane"
|
|
55
|
+
]
|
|
46
56
|
},
|
|
47
57
|
streamChunks: { type: "integer", minimum: 1 },
|
|
48
58
|
streamIntervalMs: { type: "integer", minimum: 0 },
|
|
59
|
+
// lane 的 hold 最多等插话多久;不进设置表单。
|
|
60
|
+
holdMs: { type: "integer", minimum: 0 },
|
|
49
61
|
// stream 脚本循环切片的源文本;性能探针用它换上可控的长回复,不进设置表单。
|
|
50
62
|
streamText: { type: "string" },
|
|
51
63
|
// 额度与用量怎么答(见 fakeUsage.ts);不进设置表单,开发与验收时手改实例配置。
|
|
@@ -74,6 +86,235 @@ var FAKE_CARD = {
|
|
|
74
86
|
// src/fakeInstance.ts
|
|
75
87
|
import * as NodeCrypto from "node:crypto";
|
|
76
88
|
|
|
89
|
+
// src/laneScript.ts
|
|
90
|
+
import * as NodeFS from "node:fs";
|
|
91
|
+
import * as NodePath from "node:path";
|
|
92
|
+
var LANE_STREAM_DONE = "LANE_STREAM_DONE";
|
|
93
|
+
var LANE_PLAIN_DONE = "LANE_PLAIN_DONE";
|
|
94
|
+
var LANE_APPROVAL_DONE = "LANE_APPROVAL_DONE";
|
|
95
|
+
var LANE_APPROVAL_DECLINED = "LANE_APPROVAL_DECLINED";
|
|
96
|
+
var LANE_QUESTION_ASKED = "LANE_QUESTION_ASKED";
|
|
97
|
+
var LANE_QUESTION_TEXT = "Which database should I use?";
|
|
98
|
+
var LANE_ATTACH_DONE = "LANE_ATTACH_DONE";
|
|
99
|
+
var LANE_HOLD_DONE = "LANE_HOLD_DONE";
|
|
100
|
+
var LANE_STEERED_PREFIX = "LANE_STEERED:";
|
|
101
|
+
var LANE_IMAGE_NAME = "lane1.png";
|
|
102
|
+
var LANE_DOCUMENT_NAME = "lane1.pdf";
|
|
103
|
+
var LANE_STREAM_TEXT = [
|
|
104
|
+
"Here is a longer note so the timeline can scroll.",
|
|
105
|
+
"The first paragraph stays on screen while the rest arrives in pieces.",
|
|
106
|
+
"Each piece is short on purpose: the probe sends the next message before this turn ends.",
|
|
107
|
+
"Scroll up after this reply and the position should survive a tab switch."
|
|
108
|
+
].join(" ");
|
|
109
|
+
var LANE_PNG = Buffer.from(
|
|
110
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
|
|
111
|
+
"base64"
|
|
112
|
+
);
|
|
113
|
+
var LANE_PDF = Buffer.from(
|
|
114
|
+
"%PDF-1.1\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n2 0 obj<</Type/Pages/Count 1/Kids[3 0 R]>>endobj\n3 0 obj<</Type/Page/Parent 2 0 R/MediaBox[0 0 200 200]>>endobj\ntrailer<</Root 1 0 R>>\n%%EOF\n"
|
|
115
|
+
);
|
|
116
|
+
function parseLaneKind(input) {
|
|
117
|
+
const match = /lane:(stream|plain|approve|question|attach|hold)\b/.exec(input ?? "");
|
|
118
|
+
const kind = match?.[1];
|
|
119
|
+
return kind === "stream" || kind === "plain" || kind === "approve" || kind === "question" || kind === "attach" || kind === "hold" ? kind : "plain";
|
|
120
|
+
}
|
|
121
|
+
function laneTurnOptions(input, stream) {
|
|
122
|
+
const holdMs = input?.config?.holdMs;
|
|
123
|
+
return {
|
|
124
|
+
streamChunks: stream.chunks,
|
|
125
|
+
streamIntervalMs: stream.intervalMs,
|
|
126
|
+
holdMs: typeof holdMs === "number" && holdMs >= 0 ? holdMs : 15e3
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async function openShellApproval(input) {
|
|
130
|
+
const itemId = input.nextItemId();
|
|
131
|
+
const args = { command: input.detail };
|
|
132
|
+
input.emit(
|
|
133
|
+
"item.started",
|
|
134
|
+
{ itemType: "command_execution", status: "inProgress", title: input.detail, data: args },
|
|
135
|
+
{ itemId }
|
|
136
|
+
);
|
|
137
|
+
input.emit(
|
|
138
|
+
"request.opened",
|
|
139
|
+
{
|
|
140
|
+
requestType: "exec_command_approval",
|
|
141
|
+
toolName: "shell",
|
|
142
|
+
detail: input.detail,
|
|
143
|
+
args
|
|
144
|
+
},
|
|
145
|
+
{ requestId: input.requestId }
|
|
146
|
+
);
|
|
147
|
+
const decision = await new Promise((resolve) => {
|
|
148
|
+
let settled = false;
|
|
149
|
+
const settle = (value) => {
|
|
150
|
+
if (settled) return;
|
|
151
|
+
settled = true;
|
|
152
|
+
input.signal.removeEventListener("abort", onAbort);
|
|
153
|
+
input.pending.delete(input.requestId);
|
|
154
|
+
resolve(value);
|
|
155
|
+
};
|
|
156
|
+
const onAbort = () => settle("cancel");
|
|
157
|
+
input.signal.addEventListener("abort", onAbort);
|
|
158
|
+
if (input.signal.aborted) {
|
|
159
|
+
settle("cancel");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
input.pending.set(input.requestId, settle);
|
|
163
|
+
});
|
|
164
|
+
input.emit(
|
|
165
|
+
"request.resolved",
|
|
166
|
+
{
|
|
167
|
+
requestType: "exec_command_approval",
|
|
168
|
+
decision: decision === "allow" ? "accept" : decision === "deny" ? "decline" : "cancel"
|
|
169
|
+
},
|
|
170
|
+
{ requestId: input.requestId }
|
|
171
|
+
);
|
|
172
|
+
input.emit(
|
|
173
|
+
"item.completed",
|
|
174
|
+
{
|
|
175
|
+
itemType: "command_execution",
|
|
176
|
+
status: decision === "allow" ? "completed" : "declined",
|
|
177
|
+
title: input.detail,
|
|
178
|
+
data: args
|
|
179
|
+
},
|
|
180
|
+
{ itemId }
|
|
181
|
+
);
|
|
182
|
+
return decision;
|
|
183
|
+
}
|
|
184
|
+
var delay = (ms, signal) => new Promise((resolve) => {
|
|
185
|
+
if (signal.aborted || ms <= 0) {
|
|
186
|
+
resolve();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const timer = setTimeout(resolve, ms);
|
|
190
|
+
signal.addEventListener(
|
|
191
|
+
"abort",
|
|
192
|
+
() => {
|
|
193
|
+
clearTimeout(timer);
|
|
194
|
+
resolve();
|
|
195
|
+
},
|
|
196
|
+
{ once: true }
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
var assistant = (emit, delta) => {
|
|
200
|
+
emit("content.delta", { streamKind: "assistant_text", delta });
|
|
201
|
+
};
|
|
202
|
+
function acceptLaneSteer(input) {
|
|
203
|
+
const { running } = input;
|
|
204
|
+
if (running === void 0 || !input.enabled) return null;
|
|
205
|
+
if (input.expectedTurnId !== void 0 && input.expectedTurnId !== running.turnId) return null;
|
|
206
|
+
const text = input.text?.trim() ?? "";
|
|
207
|
+
if (text !== "") {
|
|
208
|
+
const parts = input.steered.get(input.threadId) ?? [];
|
|
209
|
+
parts.push(text);
|
|
210
|
+
input.steered.set(input.threadId, parts);
|
|
211
|
+
}
|
|
212
|
+
return { threadId: input.threadId, turnId: running.turnId };
|
|
213
|
+
}
|
|
214
|
+
async function runLaneTurn(ctx) {
|
|
215
|
+
const kind = parseLaneKind(ctx.input);
|
|
216
|
+
if (ctx.signal.aborted) return null;
|
|
217
|
+
switch (kind) {
|
|
218
|
+
case "stream":
|
|
219
|
+
return streamTurn(ctx);
|
|
220
|
+
case "approve":
|
|
221
|
+
return approveTurn(ctx);
|
|
222
|
+
case "question":
|
|
223
|
+
return questionTurn(ctx);
|
|
224
|
+
case "attach":
|
|
225
|
+
return attachTurn(ctx);
|
|
226
|
+
case "hold":
|
|
227
|
+
return holdTurn(ctx);
|
|
228
|
+
case "plain":
|
|
229
|
+
assistant(ctx.emit, LANE_PLAIN_DONE);
|
|
230
|
+
await delay(0, ctx.signal);
|
|
231
|
+
return ctx.signal.aborted ? null : LANE_PLAIN_DONE;
|
|
232
|
+
default: {
|
|
233
|
+
const unreachable = kind;
|
|
234
|
+
return unreachable;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async function streamTurn(ctx) {
|
|
239
|
+
let text = "";
|
|
240
|
+
const pieces = Math.max(1, ctx.options.streamChunks);
|
|
241
|
+
for (let index = 0; index < pieces; index += 1) {
|
|
242
|
+
if (ctx.signal.aborted) return null;
|
|
243
|
+
const start = index * 24 % LANE_STREAM_TEXT.length;
|
|
244
|
+
const delta = LANE_STREAM_TEXT.slice(start, start + 24) || LANE_STREAM_TEXT.slice(0, 24);
|
|
245
|
+
text += delta;
|
|
246
|
+
assistant(ctx.emit, delta);
|
|
247
|
+
await delay(ctx.options.streamIntervalMs, ctx.signal);
|
|
248
|
+
}
|
|
249
|
+
if (ctx.signal.aborted) return null;
|
|
250
|
+
assistant(ctx.emit, LANE_STREAM_DONE);
|
|
251
|
+
return `${text}${LANE_STREAM_DONE}`;
|
|
252
|
+
}
|
|
253
|
+
async function approveTurn(ctx) {
|
|
254
|
+
const requestId = ctx.nextId("req");
|
|
255
|
+
const decision = await ctx.approve({ requestId, detail: "echo lane1-approve" });
|
|
256
|
+
if (decision === "cancel" || ctx.signal.aborted) return null;
|
|
257
|
+
const text = decision === "allow" ? LANE_APPROVAL_DONE : LANE_APPROVAL_DECLINED;
|
|
258
|
+
assistant(ctx.emit, text);
|
|
259
|
+
await delay(0, ctx.signal);
|
|
260
|
+
return ctx.signal.aborted ? null : text;
|
|
261
|
+
}
|
|
262
|
+
function questionTurn(ctx) {
|
|
263
|
+
if (ctx.signal.aborted) return null;
|
|
264
|
+
assistant(ctx.emit, LANE_QUESTION_ASKED);
|
|
265
|
+
ctx.emit(
|
|
266
|
+
"user-input.requested",
|
|
267
|
+
{
|
|
268
|
+
questions: [
|
|
269
|
+
{
|
|
270
|
+
id: "q0",
|
|
271
|
+
header: LANE_QUESTION_TEXT,
|
|
272
|
+
question: LANE_QUESTION_TEXT,
|
|
273
|
+
options: [
|
|
274
|
+
{ label: "Postgres", description: "" },
|
|
275
|
+
{ label: "SQLite", description: "" }
|
|
276
|
+
],
|
|
277
|
+
allowFreeform: true,
|
|
278
|
+
multiSelect: false
|
|
279
|
+
}
|
|
280
|
+
],
|
|
281
|
+
responseMode: "message"
|
|
282
|
+
},
|
|
283
|
+
{ requestId: ctx.nextId("question") }
|
|
284
|
+
);
|
|
285
|
+
return LANE_QUESTION_ASKED;
|
|
286
|
+
}
|
|
287
|
+
function attachTurn(ctx) {
|
|
288
|
+
if (ctx.signal.aborted) return null;
|
|
289
|
+
NodeFS.mkdirSync(ctx.cwd, { recursive: true });
|
|
290
|
+
const imagePath = NodePath.join(ctx.cwd, LANE_IMAGE_NAME);
|
|
291
|
+
const documentPath = NodePath.join(ctx.cwd, LANE_DOCUMENT_NAME);
|
|
292
|
+
NodeFS.writeFileSync(imagePath, LANE_PNG);
|
|
293
|
+
NodeFS.writeFileSync(documentPath, LANE_PDF);
|
|
294
|
+
ctx.emit("assistant.artifact.produced", {
|
|
295
|
+
artifacts: [
|
|
296
|
+
{ path: imagePath, name: LANE_IMAGE_NAME, mimeType: "image/png" },
|
|
297
|
+
{ path: documentPath, name: LANE_DOCUMENT_NAME, mimeType: "application/pdf" }
|
|
298
|
+
]
|
|
299
|
+
});
|
|
300
|
+
assistant(ctx.emit, LANE_ATTACH_DONE);
|
|
301
|
+
return LANE_ATTACH_DONE;
|
|
302
|
+
}
|
|
303
|
+
async function holdTurn(ctx) {
|
|
304
|
+
assistant(ctx.emit, "holding the turn");
|
|
305
|
+
const deadline = Date.now() + ctx.options.holdMs;
|
|
306
|
+
let steered = "";
|
|
307
|
+
while (!ctx.signal.aborted && Date.now() < deadline) {
|
|
308
|
+
steered = ctx.takeSteered();
|
|
309
|
+
if (steered !== "") break;
|
|
310
|
+
await delay(50, ctx.signal);
|
|
311
|
+
}
|
|
312
|
+
if (ctx.signal.aborted) return null;
|
|
313
|
+
const text = steered === "" ? LANE_HOLD_DONE : `${LANE_HOLD_DONE} ${LANE_STEERED_PREFIX}${steered}`;
|
|
314
|
+
assistant(ctx.emit, text);
|
|
315
|
+
return text;
|
|
316
|
+
}
|
|
317
|
+
|
|
77
318
|
// src/fakeUsage.ts
|
|
78
319
|
var FAKE_USAGE_SCRIPTS = [
|
|
79
320
|
"available",
|
|
@@ -287,6 +528,29 @@ async function runMcpCalls(input) {
|
|
|
287
528
|
return lines.join("\n");
|
|
288
529
|
}
|
|
289
530
|
|
|
531
|
+
// src/streamScript.ts
|
|
532
|
+
var FAKE_STREAM_TEXT = [
|
|
533
|
+
"Here is the plan for the change, step by step.\n\n",
|
|
534
|
+
"1. Read the current implementation and note the hot path.\n",
|
|
535
|
+
"2. Move the measurement out of the commit phase.\n",
|
|
536
|
+
"3. Verify the numbers on a production build.\n\n",
|
|
537
|
+
"```ts\nexport function measure(root: HTMLElement) {\n return root.getBoundingClientRect().height;\n}\n```\n\n",
|
|
538
|
+
"The **important** part is that the timeline only re-renders the streaming row, ",
|
|
539
|
+
"not the whole list, so each delta stays cheap even on a long conversation. "
|
|
540
|
+
].join("");
|
|
541
|
+
var STREAM_CHUNK_CHARS = 12;
|
|
542
|
+
var parseStreamOptions = (input) => {
|
|
543
|
+
const config = input?.config ?? {};
|
|
544
|
+
const customText = typeof config.streamText === "string" && config.streamText !== "" ? config.streamText : null;
|
|
545
|
+
const chunks = typeof config.streamChunks === "number" && config.streamChunks >= 1 ? Math.floor(config.streamChunks) : customText === null ? 200 : Math.ceil(customText.length / STREAM_CHUNK_CHARS);
|
|
546
|
+
const intervalMs = typeof config.streamIntervalMs === "number" && config.streamIntervalMs >= 0 ? config.streamIntervalMs : 30;
|
|
547
|
+
return { chunks, intervalMs, text: customText ?? FAKE_STREAM_TEXT };
|
|
548
|
+
};
|
|
549
|
+
var fakeStreamChunk = (text, index) => {
|
|
550
|
+
const start = index * STREAM_CHUNK_CHARS % text.length;
|
|
551
|
+
return text.slice(start, start + STREAM_CHUNK_CHARS) || text.slice(0, STREAM_CHUNK_CHARS);
|
|
552
|
+
};
|
|
553
|
+
|
|
290
554
|
// src/visualizeScript.ts
|
|
291
555
|
var WIRE_NAME = "mcp__ycodium__visualize_visualize";
|
|
292
556
|
var TITLE = "Monthly sales";
|
|
@@ -427,29 +691,8 @@ var FAKE_ASYNC_QUESTIONS = [
|
|
|
427
691
|
function parseScript(input) {
|
|
428
692
|
const envelope = input;
|
|
429
693
|
const script = envelope?.config?.script;
|
|
430
|
-
return script === "approval-tool" || script === "async-question" || script === "stream" || script === "mcp-send" || script === "visualize" ? script : "plain";
|
|
694
|
+
return script === "approval-tool" || script === "async-question" || script === "stream" || script === "mcp-send" || script === "visualize" || script === "lane" ? script : "plain";
|
|
431
695
|
}
|
|
432
|
-
var FAKE_STREAM_TEXT = [
|
|
433
|
-
"Here is the plan for the change, step by step.\n\n",
|
|
434
|
-
"1. Read the current implementation and note the hot path.\n",
|
|
435
|
-
"2. Move the measurement out of the commit phase.\n",
|
|
436
|
-
"3. Verify the numbers on a production build.\n\n",
|
|
437
|
-
"```ts\nexport function measure(root: HTMLElement) {\n return root.getBoundingClientRect().height;\n}\n```\n\n",
|
|
438
|
-
"The **important** part is that the timeline only re-renders the streaming row, ",
|
|
439
|
-
"not the whole list, so each delta stays cheap even on a long conversation. "
|
|
440
|
-
].join("");
|
|
441
|
-
var STREAM_CHUNK_CHARS = 12;
|
|
442
|
-
var parseStreamOptions = (input) => {
|
|
443
|
-
const config = input?.config ?? {};
|
|
444
|
-
const customText = typeof config.streamText === "string" && config.streamText !== "" ? config.streamText : null;
|
|
445
|
-
const chunks = typeof config.streamChunks === "number" && config.streamChunks >= 1 ? Math.floor(config.streamChunks) : customText === null ? 200 : Math.ceil(customText.length / STREAM_CHUNK_CHARS);
|
|
446
|
-
const intervalMs = typeof config.streamIntervalMs === "number" && config.streamIntervalMs >= 0 ? config.streamIntervalMs : 30;
|
|
447
|
-
return { chunks, intervalMs, text: customText ?? FAKE_STREAM_TEXT };
|
|
448
|
-
};
|
|
449
|
-
var fakeStreamChunk = (text, index) => {
|
|
450
|
-
const start = index * STREAM_CHUNK_CHARS % text.length;
|
|
451
|
-
return text.slice(start, start + STREAM_CHUNK_CHARS) || text.slice(0, STREAM_CHUNK_CHARS);
|
|
452
|
-
};
|
|
453
696
|
var parseReplyText = (input) => {
|
|
454
697
|
const envelope = input;
|
|
455
698
|
const replyText = envelope?.config?.replyText;
|
|
@@ -472,6 +715,8 @@ var createFakeInstance = (config, host) => {
|
|
|
472
715
|
const failStartSession = parseFailStartSession(config);
|
|
473
716
|
const streamOptions = parseStreamOptions(config);
|
|
474
717
|
const previewHoldMs = parsePreviewHoldMs(config);
|
|
718
|
+
const laneOptions = laneTurnOptions(config, streamOptions);
|
|
719
|
+
const steeredInputs = /* @__PURE__ */ new Map();
|
|
475
720
|
const pendingDecisions = /* @__PURE__ */ new Map();
|
|
476
721
|
let seq = 0;
|
|
477
722
|
const inflight = /* @__PURE__ */ new Map();
|
|
@@ -707,6 +952,34 @@ var createFakeInstance = (config, host) => {
|
|
|
707
952
|
]);
|
|
708
953
|
return;
|
|
709
954
|
}
|
|
955
|
+
if (script === "lane") {
|
|
956
|
+
const text = await runLaneTurn({
|
|
957
|
+
threadId,
|
|
958
|
+
turnId,
|
|
959
|
+
cwd: booked.session.cwd ?? host.workspacePath(threadId),
|
|
960
|
+
input,
|
|
961
|
+
signal,
|
|
962
|
+
options: laneOptions,
|
|
963
|
+
emit: (type, payload, extra) => emit(type, threadId, payload, { turnId, ...extra }),
|
|
964
|
+
nextId,
|
|
965
|
+
takeSteered: () => (steeredInputs.get(threadId) ?? []).splice(0).join("\n"),
|
|
966
|
+
approve: (request) => openShellApproval({
|
|
967
|
+
requestId: request.requestId,
|
|
968
|
+
detail: request.detail,
|
|
969
|
+
signal,
|
|
970
|
+
nextItemId: () => nextId("item"),
|
|
971
|
+
pending: pendingDecisions,
|
|
972
|
+
emit: (type, payload, extra) => emit(type, threadId, payload, { turnId, ...extra })
|
|
973
|
+
})
|
|
974
|
+
});
|
|
975
|
+
finishTurn(
|
|
976
|
+
booked,
|
|
977
|
+
turnId,
|
|
978
|
+
signal.aborted || text === null ? "interrupted" : "completed",
|
|
979
|
+
text === null ? [] : [{ kind: "assistant", text }]
|
|
980
|
+
);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
710
983
|
if (script === "approval-tool") {
|
|
711
984
|
const finalText = await runApprovalToolScript(threadId, turnId, signal);
|
|
712
985
|
finishTurn(
|
|
@@ -767,7 +1040,17 @@ var createFakeInstance = (config, host) => {
|
|
|
767
1040
|
sendTurn: async (input, signal) => {
|
|
768
1041
|
if (disposed) throw new Error("instance disposed");
|
|
769
1042
|
const booked = requireThread(input.threadId);
|
|
770
|
-
|
|
1043
|
+
const running = inflight.get(input.threadId);
|
|
1044
|
+
const steered = acceptLaneSteer({
|
|
1045
|
+
running,
|
|
1046
|
+
enabled: script === "lane",
|
|
1047
|
+
expectedTurnId: input.expectedTurnId,
|
|
1048
|
+
text: input.input,
|
|
1049
|
+
threadId: input.threadId,
|
|
1050
|
+
steered: steeredInputs
|
|
1051
|
+
});
|
|
1052
|
+
if (steered !== null) return steered;
|
|
1053
|
+
if (running !== void 0) throw new Error("turn already in flight");
|
|
771
1054
|
const turnId = input.expectedTurnId ?? nextId("turn");
|
|
772
1055
|
const turnAbort = new AbortController();
|
|
773
1056
|
const abortTurn = () => turnAbort.abort();
|
|
@@ -787,7 +1070,7 @@ var createFakeInstance = (config, host) => {
|
|
|
787
1070
|
signal?.removeEventListener("abort", abortTurn);
|
|
788
1071
|
if (inflight.get(input.threadId)?.turnId === turnId) inflight.delete(input.threadId);
|
|
789
1072
|
};
|
|
790
|
-
if (script === "approval-tool" || script === "stream" || script === "mcp-send" || script === "visualize") {
|
|
1073
|
+
if (script === "approval-tool" || script === "stream" || script === "mcp-send" || script === "visualize" || script === "lane") {
|
|
791
1074
|
signal?.removeEventListener("abort", abortTurn);
|
|
792
1075
|
void run().catch((error) => failTurn(booked, turnId, error)).finally(settle);
|
|
793
1076
|
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.2948",
|
|
4
4
|
"description": "Minimal fake executor for plugin authors and host orchestration tests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ycodium",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"@types/node": "24.12.4",
|
|
22
22
|
"esbuild": "0.28.1",
|
|
23
23
|
"vite-plus": "0.2.2",
|
|
24
|
-
"
|
|
25
|
-
"ycodium-
|
|
24
|
+
"ycodium-executor-testing": "0.0.0",
|
|
25
|
+
"@ycodium-ai/plugin-api": "0.2.2948"
|
|
26
26
|
},
|
|
27
27
|
"ycodium": {
|
|
28
28
|
"plugin": "./dist/index.js",
|