@pi-ohm/subagents 0.6.4-dev.22132543465.1.e4e3071 → 0.6.4-dev.22169815567.1.cdde4e8
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 +120 -14
- package/package.json +3 -2
- package/src/extension.test.ts +22 -0
- package/src/extension.ts +68 -0
- package/src/runtime/backend.test.ts +501 -1
- package/src/runtime/backend.ts +592 -26
- package/src/runtime/events.test.ts +101 -0
- package/src/runtime/events.ts +198 -0
- package/src/runtime/live-ui.test.ts +272 -0
- package/src/runtime/live-ui.ts +273 -0
- package/src/runtime/tasks.test.ts +184 -0
- package/src/runtime/tasks.ts +378 -8
- package/src/runtime/ui.test.ts +125 -5
- package/src/runtime/ui.ts +165 -14
- package/src/tools/primary.test.ts +121 -4
- package/src/tools/primary.ts +248 -29
- package/src/tools/task.test.ts +934 -4
- package/src/tools/task.ts +1183 -33
|
@@ -3,11 +3,18 @@ import test from "node:test";
|
|
|
3
3
|
import type { OhmRuntimeConfig, OhmSubagentBackend } from "@pi-ohm/config";
|
|
4
4
|
import { Result } from "better-result";
|
|
5
5
|
import type { OhmSubagentDefinition } from "../catalog";
|
|
6
|
+
import { SubagentRuntimeError } from "../errors";
|
|
6
7
|
import {
|
|
8
|
+
applyPiSdkSessionEvent,
|
|
7
9
|
createDefaultTaskExecutionBackend,
|
|
10
|
+
createPiSdkStreamCaptureState,
|
|
11
|
+
finalizePiSdkStreamCapture,
|
|
8
12
|
PiCliTaskExecutionBackend,
|
|
13
|
+
PiSdkTaskExecutionBackend,
|
|
9
14
|
ScaffoldTaskExecutionBackend,
|
|
10
15
|
type PiCliRunner,
|
|
16
|
+
type PiSdkRunner,
|
|
17
|
+
type TaskExecutionBackend,
|
|
11
18
|
} from "./backend";
|
|
12
19
|
|
|
13
20
|
function defineTest(name: string, run: () => void | Promise<void>): void {
|
|
@@ -197,7 +204,11 @@ defineTest(
|
|
|
197
204
|
assert.equal(prompts.length, 1);
|
|
198
205
|
assert.match(prompts[0] ?? "", /You are the Finder subagent in Pi OHM/);
|
|
199
206
|
assert.equal(result.value.output, "finder online");
|
|
200
|
-
assert.match(result.value.summary, /Finder:
|
|
207
|
+
assert.match(result.value.summary, /Finder: Auth flow scan/);
|
|
208
|
+
assert.equal(result.value.runtime, "pi-cli");
|
|
209
|
+
assert.equal(result.value.route, "interactive-shell");
|
|
210
|
+
assert.equal(result.value.provider, "unavailable");
|
|
211
|
+
assert.equal(result.value.model, "unavailable");
|
|
201
212
|
},
|
|
202
213
|
);
|
|
203
214
|
|
|
@@ -238,6 +249,442 @@ defineTest(
|
|
|
238
249
|
},
|
|
239
250
|
);
|
|
240
251
|
|
|
252
|
+
defineTest(
|
|
253
|
+
"PiCliTaskExecutionBackend strips backend/provider/model metadata lines from nested output",
|
|
254
|
+
async () => {
|
|
255
|
+
const runner: PiCliRunner = async () => ({
|
|
256
|
+
exitCode: 0,
|
|
257
|
+
stdout: [
|
|
258
|
+
"backend: pi-coding-agent",
|
|
259
|
+
"provider: openai-codex",
|
|
260
|
+
"model: gpt-5.3-codex",
|
|
261
|
+
"",
|
|
262
|
+
"actual line one",
|
|
263
|
+
"actual line two",
|
|
264
|
+
].join("\n"),
|
|
265
|
+
stderr: "",
|
|
266
|
+
timedOut: false,
|
|
267
|
+
aborted: false,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const backend = new PiCliTaskExecutionBackend(runner, 1_000);
|
|
271
|
+
const result = await backend.executeStart({
|
|
272
|
+
taskId: "task_9",
|
|
273
|
+
subagent: subagentFixture,
|
|
274
|
+
description: "Auth flow scan",
|
|
275
|
+
prompt: "Find auth validation path and refresh flow",
|
|
276
|
+
cwd: "/tmp/project",
|
|
277
|
+
config: makeConfig("interactive-shell"),
|
|
278
|
+
signal: undefined,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
assert.equal(Result.isOk(result), true);
|
|
282
|
+
if (Result.isError(result)) {
|
|
283
|
+
assert.fail("Expected sanitized output success");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
assert.doesNotMatch(result.value.output, /^backend:/m);
|
|
287
|
+
assert.doesNotMatch(result.value.output, /^provider:/m);
|
|
288
|
+
assert.doesNotMatch(result.value.output, /^model:/m);
|
|
289
|
+
assert.match(result.value.output, /actual line one/);
|
|
290
|
+
assert.match(result.value.output, /actual line two/);
|
|
291
|
+
assert.equal(result.value.provider, "openai-codex");
|
|
292
|
+
assert.equal(result.value.model, "gpt-5.3-codex");
|
|
293
|
+
assert.equal(result.value.runtime, "pi-coding-agent");
|
|
294
|
+
assert.equal(result.value.route, "interactive-shell");
|
|
295
|
+
},
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
defineTest("PiSdkTaskExecutionBackend executes sdk runner for interactive-sdk", async () => {
|
|
299
|
+
const prompts: string[] = [];
|
|
300
|
+
|
|
301
|
+
const runner: PiSdkRunner = async (input) => {
|
|
302
|
+
prompts.push(input.prompt);
|
|
303
|
+
return {
|
|
304
|
+
output: "sdk online",
|
|
305
|
+
events: [],
|
|
306
|
+
provider: "sdk-provider",
|
|
307
|
+
model: "sdk-model",
|
|
308
|
+
runtime: "pi-sdk",
|
|
309
|
+
timedOut: false,
|
|
310
|
+
aborted: false,
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const backend = new PiSdkTaskExecutionBackend(runner, 1_000);
|
|
315
|
+
const result = await backend.executeStart({
|
|
316
|
+
taskId: "task_sdk_1",
|
|
317
|
+
subagent: subagentFixture,
|
|
318
|
+
description: "Auth flow scan",
|
|
319
|
+
prompt: "Trace auth validation path",
|
|
320
|
+
cwd: "/tmp/project",
|
|
321
|
+
config: makeConfig("interactive-sdk"),
|
|
322
|
+
signal: undefined,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
assert.equal(Result.isOk(result), true);
|
|
326
|
+
if (Result.isError(result)) {
|
|
327
|
+
assert.fail("Expected interactive-sdk backend execution to succeed");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
assert.equal(prompts.length, 1);
|
|
331
|
+
assert.match(prompts[0] ?? "", /You are the Finder subagent in Pi OHM/);
|
|
332
|
+
assert.equal(result.value.output, "sdk online");
|
|
333
|
+
assert.equal(result.value.provider, "sdk-provider");
|
|
334
|
+
assert.equal(result.value.model, "sdk-model");
|
|
335
|
+
assert.equal(result.value.runtime, "pi-sdk");
|
|
336
|
+
assert.equal(result.value.route, "interactive-sdk");
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
defineTest("Pi SDK stream capture records tool lifecycle and assistant deltas", () => {
|
|
340
|
+
const capture = createPiSdkStreamCaptureState();
|
|
341
|
+
|
|
342
|
+
applyPiSdkSessionEvent(capture, {
|
|
343
|
+
type: "tool_execution_start",
|
|
344
|
+
toolCallId: "tool_1",
|
|
345
|
+
toolName: "read",
|
|
346
|
+
args: { path: "src/index.ts" },
|
|
347
|
+
});
|
|
348
|
+
applyPiSdkSessionEvent(capture, {
|
|
349
|
+
type: "tool_execution_update",
|
|
350
|
+
toolCallId: "tool_1",
|
|
351
|
+
toolName: "read",
|
|
352
|
+
partialResult: { progress: "50%" },
|
|
353
|
+
});
|
|
354
|
+
applyPiSdkSessionEvent(capture, {
|
|
355
|
+
type: "tool_execution_end",
|
|
356
|
+
toolCallId: "tool_1",
|
|
357
|
+
toolName: "read",
|
|
358
|
+
result: { ok: true },
|
|
359
|
+
isError: false,
|
|
360
|
+
});
|
|
361
|
+
applyPiSdkSessionEvent(capture, {
|
|
362
|
+
type: "message_update",
|
|
363
|
+
assistantMessageEvent: { type: "text_delta", delta: "Found auth flow." },
|
|
364
|
+
});
|
|
365
|
+
applyPiSdkSessionEvent(capture, {
|
|
366
|
+
type: "agent_end",
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const finalized = finalizePiSdkStreamCapture(capture);
|
|
370
|
+
assert.equal(finalized.sawAgentEnd, true);
|
|
371
|
+
assert.equal(finalized.capturedEventCount, 5);
|
|
372
|
+
assert.match(finalized.output, /tool_call: read start/);
|
|
373
|
+
assert.match(finalized.output, /tool_call: read update/);
|
|
374
|
+
assert.match(finalized.output, /tool_call: read end success/);
|
|
375
|
+
assert.match(finalized.output, /Found auth flow\./);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
defineTest("Pi SDK stream capture ignores unsupported events", () => {
|
|
379
|
+
const capture = createPiSdkStreamCaptureState();
|
|
380
|
+
|
|
381
|
+
applyPiSdkSessionEvent(capture, { type: "turn_start", turnIndex: 1 });
|
|
382
|
+
applyPiSdkSessionEvent(capture, {
|
|
383
|
+
type: "message_update",
|
|
384
|
+
assistantMessageEvent: { type: "thinking_delta", delta: "..." },
|
|
385
|
+
});
|
|
386
|
+
applyPiSdkSessionEvent(capture, {
|
|
387
|
+
type: "tool_execution_start",
|
|
388
|
+
toolCallId: "tool_1",
|
|
389
|
+
toolName: "bash",
|
|
390
|
+
args: undefined,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const finalized = finalizePiSdkStreamCapture(capture);
|
|
394
|
+
assert.equal(finalized.capturedEventCount, 1);
|
|
395
|
+
assert.equal(finalized.sawAgentEnd, false);
|
|
396
|
+
assert.match(finalized.output, /tool_call: bash start/);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
defineTest("PiSdkTaskExecutionBackend maps timeout/abort/execution failures", async () => {
|
|
400
|
+
const timeoutBackend = new PiSdkTaskExecutionBackend(async () => ({
|
|
401
|
+
output: "",
|
|
402
|
+
events: [],
|
|
403
|
+
timedOut: true,
|
|
404
|
+
aborted: false,
|
|
405
|
+
}));
|
|
406
|
+
|
|
407
|
+
const timedOut = await timeoutBackend.executeStart({
|
|
408
|
+
taskId: "task_sdk_timeout",
|
|
409
|
+
subagent: subagentFixture,
|
|
410
|
+
description: "timeout",
|
|
411
|
+
prompt: "timeout",
|
|
412
|
+
cwd: "/tmp/project",
|
|
413
|
+
config: makeConfig("interactive-sdk"),
|
|
414
|
+
signal: undefined,
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
assert.equal(Result.isError(timedOut), true);
|
|
418
|
+
if (Result.isOk(timedOut)) {
|
|
419
|
+
assert.fail("Expected timeout error");
|
|
420
|
+
}
|
|
421
|
+
assert.equal(timedOut.error.code, "task_backend_timeout");
|
|
422
|
+
|
|
423
|
+
const abortedBackend = new PiSdkTaskExecutionBackend(async () => ({
|
|
424
|
+
output: "",
|
|
425
|
+
events: [],
|
|
426
|
+
timedOut: false,
|
|
427
|
+
aborted: true,
|
|
428
|
+
}));
|
|
429
|
+
|
|
430
|
+
const aborted = await abortedBackend.executeStart({
|
|
431
|
+
taskId: "task_sdk_abort",
|
|
432
|
+
subagent: subagentFixture,
|
|
433
|
+
description: "abort",
|
|
434
|
+
prompt: "abort",
|
|
435
|
+
cwd: "/tmp/project",
|
|
436
|
+
config: makeConfig("interactive-sdk"),
|
|
437
|
+
signal: undefined,
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
assert.equal(Result.isError(aborted), true);
|
|
441
|
+
if (Result.isOk(aborted)) {
|
|
442
|
+
assert.fail("Expected aborted error");
|
|
443
|
+
}
|
|
444
|
+
assert.equal(aborted.error.code, "task_aborted");
|
|
445
|
+
|
|
446
|
+
const failedBackend = new PiSdkTaskExecutionBackend(async () => ({
|
|
447
|
+
output: "",
|
|
448
|
+
events: [],
|
|
449
|
+
timedOut: false,
|
|
450
|
+
aborted: false,
|
|
451
|
+
error: "sdk failed",
|
|
452
|
+
}));
|
|
453
|
+
|
|
454
|
+
const failed = await failedBackend.executeSend({
|
|
455
|
+
taskId: "task_sdk_fail",
|
|
456
|
+
subagent: subagentFixture,
|
|
457
|
+
description: "failure",
|
|
458
|
+
initialPrompt: "initial",
|
|
459
|
+
followUpPrompts: [],
|
|
460
|
+
prompt: "follow-up",
|
|
461
|
+
cwd: "/tmp/project",
|
|
462
|
+
config: makeConfig("interactive-sdk"),
|
|
463
|
+
signal: undefined,
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
assert.equal(Result.isError(failed), true);
|
|
467
|
+
if (Result.isOk(failed)) {
|
|
468
|
+
assert.fail("Expected execution failure");
|
|
469
|
+
}
|
|
470
|
+
assert.equal(failed.error.code, "task_backend_execution_failed");
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
defineTest("PiCliTaskExecutionBackend delegates to sdk backend when configured", async () => {
|
|
474
|
+
let cliCalled = false;
|
|
475
|
+
|
|
476
|
+
const cliRunner: PiCliRunner = async () => {
|
|
477
|
+
cliCalled = true;
|
|
478
|
+
return {
|
|
479
|
+
exitCode: 0,
|
|
480
|
+
stdout: "cli output",
|
|
481
|
+
stderr: "",
|
|
482
|
+
timedOut: false,
|
|
483
|
+
aborted: false,
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const sdkBackend: TaskExecutionBackend = {
|
|
488
|
+
id: "interactive-sdk",
|
|
489
|
+
async executeStart() {
|
|
490
|
+
return Result.ok({
|
|
491
|
+
summary: "Finder: via sdk",
|
|
492
|
+
output: "sdk output",
|
|
493
|
+
provider: "sdk-provider",
|
|
494
|
+
model: "sdk-model",
|
|
495
|
+
runtime: "pi-sdk",
|
|
496
|
+
route: "interactive-sdk",
|
|
497
|
+
});
|
|
498
|
+
},
|
|
499
|
+
async executeSend() {
|
|
500
|
+
return Result.ok({
|
|
501
|
+
summary: "Finder follow-up: via sdk",
|
|
502
|
+
output: "sdk follow-up output",
|
|
503
|
+
provider: "sdk-provider",
|
|
504
|
+
model: "sdk-model",
|
|
505
|
+
runtime: "pi-sdk",
|
|
506
|
+
route: "interactive-sdk",
|
|
507
|
+
});
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const backend = new PiCliTaskExecutionBackend(cliRunner, 1_000, sdkBackend);
|
|
512
|
+
const result = await backend.executeStart({
|
|
513
|
+
taskId: "task_sdk_2",
|
|
514
|
+
subagent: subagentFixture,
|
|
515
|
+
description: "Auth flow scan",
|
|
516
|
+
prompt: "Trace auth validation path",
|
|
517
|
+
cwd: "/tmp/project",
|
|
518
|
+
config: makeConfig("interactive-sdk"),
|
|
519
|
+
signal: undefined,
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
assert.equal(cliCalled, false);
|
|
523
|
+
assert.equal(Result.isOk(result), true);
|
|
524
|
+
if (Result.isError(result)) {
|
|
525
|
+
assert.fail("Expected sdk delegation to succeed");
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
assert.equal(result.value.output, "sdk output");
|
|
529
|
+
assert.equal(result.value.route, "interactive-sdk");
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
defineTest(
|
|
533
|
+
"PiCliTaskExecutionBackend falls back to cli when sdk fails and fallback is enabled",
|
|
534
|
+
async () => {
|
|
535
|
+
const previous = process.env.OHM_SUBAGENTS_SDK_FALLBACK_TO_CLI;
|
|
536
|
+
process.env.OHM_SUBAGENTS_SDK_FALLBACK_TO_CLI = "true";
|
|
537
|
+
|
|
538
|
+
try {
|
|
539
|
+
let cliCalls = 0;
|
|
540
|
+
const cliRunner: PiCliRunner = async () => {
|
|
541
|
+
cliCalls += 1;
|
|
542
|
+
return {
|
|
543
|
+
exitCode: 0,
|
|
544
|
+
stdout: "cli fallback output",
|
|
545
|
+
stderr: "",
|
|
546
|
+
timedOut: false,
|
|
547
|
+
aborted: false,
|
|
548
|
+
};
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
const sdkBackend: TaskExecutionBackend = {
|
|
552
|
+
id: "interactive-sdk",
|
|
553
|
+
async executeStart() {
|
|
554
|
+
return Result.err(
|
|
555
|
+
new SubagentRuntimeError({
|
|
556
|
+
code: "task_backend_execution_failed",
|
|
557
|
+
stage: "execute_start",
|
|
558
|
+
message: "sdk bootstrap failed",
|
|
559
|
+
}),
|
|
560
|
+
);
|
|
561
|
+
},
|
|
562
|
+
async executeSend() {
|
|
563
|
+
return Result.err(
|
|
564
|
+
new SubagentRuntimeError({
|
|
565
|
+
code: "task_backend_execution_failed",
|
|
566
|
+
stage: "execute_send",
|
|
567
|
+
message: "sdk bootstrap failed",
|
|
568
|
+
}),
|
|
569
|
+
);
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const backend = new PiCliTaskExecutionBackend(cliRunner, 1_000, sdkBackend);
|
|
574
|
+
const result = await backend.executeStart({
|
|
575
|
+
taskId: "task_sdk_fallback",
|
|
576
|
+
subagent: subagentFixture,
|
|
577
|
+
description: "Fallback run",
|
|
578
|
+
prompt: "Use cli fallback",
|
|
579
|
+
cwd: "/tmp/project",
|
|
580
|
+
config: makeConfig("interactive-sdk"),
|
|
581
|
+
signal: undefined,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
assert.equal(Result.isOk(result), true);
|
|
585
|
+
if (Result.isError(result)) {
|
|
586
|
+
assert.fail("Expected sdk->cli fallback success");
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
assert.equal(cliCalls, 1);
|
|
590
|
+
assert.equal(result.value.output, "cli fallback output");
|
|
591
|
+
assert.equal(result.value.route, "interactive-shell");
|
|
592
|
+
} finally {
|
|
593
|
+
if (previous === undefined) {
|
|
594
|
+
delete process.env.OHM_SUBAGENTS_SDK_FALLBACK_TO_CLI;
|
|
595
|
+
} else {
|
|
596
|
+
process.env.OHM_SUBAGENTS_SDK_FALLBACK_TO_CLI = previous;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
defineTest(
|
|
603
|
+
"PiCliTaskExecutionBackend keeps sdk error taxonomy when fallback is disabled",
|
|
604
|
+
async () => {
|
|
605
|
+
const previous = process.env.OHM_SUBAGENTS_SDK_FALLBACK_TO_CLI;
|
|
606
|
+
delete process.env.OHM_SUBAGENTS_SDK_FALLBACK_TO_CLI;
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
let cliCalls = 0;
|
|
610
|
+
const cliRunner: PiCliRunner = async () => {
|
|
611
|
+
cliCalls += 1;
|
|
612
|
+
return {
|
|
613
|
+
exitCode: 0,
|
|
614
|
+
stdout: "cli output",
|
|
615
|
+
stderr: "",
|
|
616
|
+
timedOut: false,
|
|
617
|
+
aborted: false,
|
|
618
|
+
};
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const sdkBackend: TaskExecutionBackend = {
|
|
622
|
+
id: "interactive-sdk",
|
|
623
|
+
async executeStart() {
|
|
624
|
+
return Result.err(
|
|
625
|
+
new SubagentRuntimeError({
|
|
626
|
+
code: "task_backend_timeout",
|
|
627
|
+
stage: "execute_start",
|
|
628
|
+
message: "sdk timed out",
|
|
629
|
+
}),
|
|
630
|
+
);
|
|
631
|
+
},
|
|
632
|
+
async executeSend() {
|
|
633
|
+
return Result.err(
|
|
634
|
+
new SubagentRuntimeError({
|
|
635
|
+
code: "task_aborted",
|
|
636
|
+
stage: "execute_send",
|
|
637
|
+
message: "sdk aborted",
|
|
638
|
+
}),
|
|
639
|
+
);
|
|
640
|
+
},
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
const backend = new PiCliTaskExecutionBackend(cliRunner, 1_000, sdkBackend);
|
|
644
|
+
const started = await backend.executeStart({
|
|
645
|
+
taskId: "task_sdk_no_fallback",
|
|
646
|
+
subagent: subagentFixture,
|
|
647
|
+
description: "No fallback start",
|
|
648
|
+
prompt: "start",
|
|
649
|
+
cwd: "/tmp/project",
|
|
650
|
+
config: makeConfig("interactive-sdk"),
|
|
651
|
+
signal: undefined,
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
assert.equal(Result.isError(started), true);
|
|
655
|
+
if (Result.isOk(started)) {
|
|
656
|
+
assert.fail("Expected sdk start failure without fallback");
|
|
657
|
+
}
|
|
658
|
+
assert.equal(started.error.code, "task_backend_timeout");
|
|
659
|
+
|
|
660
|
+
const sent = await backend.executeSend({
|
|
661
|
+
taskId: "task_sdk_no_fallback",
|
|
662
|
+
subagent: subagentFixture,
|
|
663
|
+
description: "No fallback send",
|
|
664
|
+
initialPrompt: "initial",
|
|
665
|
+
followUpPrompts: [],
|
|
666
|
+
prompt: "send",
|
|
667
|
+
cwd: "/tmp/project",
|
|
668
|
+
config: makeConfig("interactive-sdk"),
|
|
669
|
+
signal: undefined,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
assert.equal(Result.isError(sent), true);
|
|
673
|
+
if (Result.isOk(sent)) {
|
|
674
|
+
assert.fail("Expected sdk send failure without fallback");
|
|
675
|
+
}
|
|
676
|
+
assert.equal(sent.error.code, "task_aborted");
|
|
677
|
+
assert.equal(cliCalls, 0);
|
|
678
|
+
} finally {
|
|
679
|
+
if (previous === undefined) {
|
|
680
|
+
delete process.env.OHM_SUBAGENTS_SDK_FALLBACK_TO_CLI;
|
|
681
|
+
} else {
|
|
682
|
+
process.env.OHM_SUBAGENTS_SDK_FALLBACK_TO_CLI = previous;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
);
|
|
687
|
+
|
|
241
688
|
defineTest("PiCliTaskExecutionBackend reports timeout and backend execution failures", async () => {
|
|
242
689
|
const timeoutRunner: PiCliRunner = async () => ({
|
|
243
690
|
exitCode: 124,
|
|
@@ -290,6 +737,58 @@ defineTest("PiCliTaskExecutionBackend reports timeout and backend execution fail
|
|
|
290
737
|
assert.equal(failed.error.code, "task_backend_execution_failed");
|
|
291
738
|
});
|
|
292
739
|
|
|
740
|
+
defineTest("PiSdkTaskExecutionBackend maps send timeout and abort", async () => {
|
|
741
|
+
const timeoutBackend = new PiSdkTaskExecutionBackend(async () => ({
|
|
742
|
+
output: "",
|
|
743
|
+
events: [],
|
|
744
|
+
timedOut: true,
|
|
745
|
+
aborted: false,
|
|
746
|
+
}));
|
|
747
|
+
|
|
748
|
+
const timedOut = await timeoutBackend.executeSend({
|
|
749
|
+
taskId: "task_sdk_send_timeout",
|
|
750
|
+
subagent: subagentFixture,
|
|
751
|
+
description: "send timeout",
|
|
752
|
+
initialPrompt: "initial",
|
|
753
|
+
followUpPrompts: [],
|
|
754
|
+
prompt: "follow-up",
|
|
755
|
+
cwd: "/tmp/project",
|
|
756
|
+
config: makeConfig("interactive-sdk"),
|
|
757
|
+
signal: undefined,
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
assert.equal(Result.isError(timedOut), true);
|
|
761
|
+
if (Result.isOk(timedOut)) {
|
|
762
|
+
assert.fail("Expected send timeout error");
|
|
763
|
+
}
|
|
764
|
+
assert.equal(timedOut.error.code, "task_backend_timeout");
|
|
765
|
+
|
|
766
|
+
const abortedBackend = new PiSdkTaskExecutionBackend(async () => ({
|
|
767
|
+
output: "",
|
|
768
|
+
events: [],
|
|
769
|
+
timedOut: false,
|
|
770
|
+
aborted: true,
|
|
771
|
+
}));
|
|
772
|
+
|
|
773
|
+
const aborted = await abortedBackend.executeSend({
|
|
774
|
+
taskId: "task_sdk_send_abort",
|
|
775
|
+
subagent: subagentFixture,
|
|
776
|
+
description: "send abort",
|
|
777
|
+
initialPrompt: "initial",
|
|
778
|
+
followUpPrompts: [],
|
|
779
|
+
prompt: "follow-up",
|
|
780
|
+
cwd: "/tmp/project",
|
|
781
|
+
config: makeConfig("interactive-sdk"),
|
|
782
|
+
signal: undefined,
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
assert.equal(Result.isError(aborted), true);
|
|
786
|
+
if (Result.isOk(aborted)) {
|
|
787
|
+
assert.fail("Expected send aborted error");
|
|
788
|
+
}
|
|
789
|
+
assert.equal(aborted.error.code, "task_aborted");
|
|
790
|
+
});
|
|
791
|
+
|
|
293
792
|
defineTest("PiCliTaskExecutionBackend resolves backend IDs from runtime config", () => {
|
|
294
793
|
const backend = new PiCliTaskExecutionBackend(async () => ({
|
|
295
794
|
exitCode: 0,
|
|
@@ -300,6 +799,7 @@ defineTest("PiCliTaskExecutionBackend resolves backend IDs from runtime config",
|
|
|
300
799
|
}));
|
|
301
800
|
|
|
302
801
|
assert.equal(backend.resolveBackendId(makeConfig("interactive-shell")), "interactive-shell");
|
|
802
|
+
assert.equal(backend.resolveBackendId(makeConfig("interactive-sdk")), "interactive-sdk");
|
|
303
803
|
assert.equal(backend.resolveBackendId(makeConfig("none")), "scaffold");
|
|
304
804
|
assert.equal(backend.resolveBackendId(makeConfig("custom-plugin")), "custom-plugin");
|
|
305
805
|
});
|