@koda-sl/baker-bridge 0.59.0 → 0.61.0
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 +64 -15
- package/dist/hono/agent-session.d.ts +3 -1
- package/dist/hono/agent-session.d.ts.map +1 -1
- package/dist/hono/agent-session.js +20 -1
- package/dist/hono/agent-session.js.map +1 -1
- package/dist/hono/agent-session.test.js +78 -13
- package/dist/hono/agent-session.test.js.map +1 -1
- package/dist/hono/agent-tasks.d.ts +11 -0
- package/dist/hono/agent-tasks.d.ts.map +1 -1
- package/dist/hono/agent-tasks.js +37 -0
- package/dist/hono/agent-tasks.js.map +1 -1
- package/dist/hono/agent-tasks.test.js +42 -1
- package/dist/hono/agent-tasks.test.js.map +1 -1
- package/dist/hono/agent.d.ts.map +1 -1
- package/dist/hono/agent.js +43 -3
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/agent.test.js +29 -5
- package/dist/hono/agent.test.js.map +1 -1
- package/dist/hono/bridge-job.d.ts +12 -1
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +235 -21
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +300 -1
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/convex.d.ts +4 -1
- package/dist/hono/convex.d.ts.map +1 -1
- package/dist/hono/convex.js +31 -2
- package/dist/hono/convex.js.map +1 -1
- package/dist/hono/convex.test.js +11 -1
- package/dist/hono/convex.test.js.map +1 -1
- package/dist/hono/model-router.d.ts +1 -0
- package/dist/hono/model-router.d.ts.map +1 -1
- package/dist/hono/model-router.js +39 -6
- package/dist/hono/model-router.js.map +1 -1
- package/dist/hono/model-router.test.js +17 -1
- package/dist/hono/model-router.test.js.map +1 -1
- package/dist/hono/relay.d.ts +6 -1
- package/dist/hono/relay.d.ts.map +1 -1
- package/dist/hono/relay.js +11 -3
- package/dist/hono/relay.js.map +1 -1
- package/dist/hono/session-continuity.d.ts +6 -0
- package/dist/hono/session-continuity.d.ts.map +1 -1
- package/dist/hono/session-continuity.integration.test.js +25 -3
- package/dist/hono/session-continuity.integration.test.js.map +1 -1
- package/dist/hono/session-continuity.js +7 -1
- package/dist/hono/session-continuity.js.map +1 -1
- package/package.json +1 -1
|
@@ -184,6 +184,8 @@ function createExecutor(options) {
|
|
|
184
184
|
askUserQuestionTimeoutMs: options.askUserQuestionTimeoutMs,
|
|
185
185
|
stallTimeoutMs: options.stallTimeoutMs,
|
|
186
186
|
stallCheckIntervalMs: options.stallCheckIntervalMs,
|
|
187
|
+
turnEndSettleMs: options.turnEndSettleMs,
|
|
188
|
+
turnEndExitGuardMs: options.turnEndExitGuardMs,
|
|
187
189
|
now: options.now,
|
|
188
190
|
});
|
|
189
191
|
}
|
|
@@ -415,6 +417,31 @@ describe("BridgeJobExecutor", () => {
|
|
|
415
417
|
}),
|
|
416
418
|
]);
|
|
417
419
|
});
|
|
420
|
+
// Without this the only reading we ever record is the one at `result`, taken
|
|
421
|
+
// after compaction already shrank the window — so a compaction that fired far
|
|
422
|
+
// too early leaves no evidence behind.
|
|
423
|
+
it("relays the occupancy that triggered a compaction before showing the spinner", async () => {
|
|
424
|
+
const relay = recordingRelay();
|
|
425
|
+
const executor = createExecutor({
|
|
426
|
+
relay,
|
|
427
|
+
model: "claude-sonnet-5",
|
|
428
|
+
queryResults: [
|
|
429
|
+
scriptedMessages(assistantMessage("assistant_1", "Working.", "session_next", {
|
|
430
|
+
usage: { input_tokens: 4000, cache_read_input_tokens: 150000, cache_creation_input_tokens: 1000 },
|
|
431
|
+
}), {
|
|
432
|
+
type: "system",
|
|
433
|
+
subtype: "status",
|
|
434
|
+
status: "compacting",
|
|
435
|
+
session_id: "session_next",
|
|
436
|
+
}, resultMessage(0.02, "session_next")),
|
|
437
|
+
],
|
|
438
|
+
});
|
|
439
|
+
await executor.execute({ threadId: "thread_cc", turnId: "turn_cc", prompt: "keep going", history: history([]) }, activeControls());
|
|
440
|
+
// Two readings: one at the compaction, one at turn end. 155k against Sonnet
|
|
441
|
+
// 5's real 1M window — a compaction here would be ~6x early.
|
|
442
|
+
const reading = { target: { threadId: "thread_cc", turnId: "turn_cc" }, tokens: 155000, window: 1_000_000 };
|
|
443
|
+
expect(relay.contextUsageUpdates).toEqual([reading, reading]);
|
|
444
|
+
});
|
|
418
445
|
it("relays context occupancy from the last main request, not the turn aggregate", async () => {
|
|
419
446
|
const relay = recordingRelay();
|
|
420
447
|
const executor = createExecutor({
|
|
@@ -687,7 +714,7 @@ describe("BridgeJobExecutor", () => {
|
|
|
687
714
|
queryResults: [scriptedMessages(resultMessage(0, "session_mem"))],
|
|
688
715
|
});
|
|
689
716
|
await executor.execute({ threadId: "thread_mem", turnId: "turn_mem", prompt: "Remember this", history: history([]) }, activeControls());
|
|
690
|
-
expect(queryInputs[0]?.options.settings).
|
|
717
|
+
expect(queryInputs[0]?.options.settings).toMatchObject({
|
|
691
718
|
autoMemoryEnabled: true,
|
|
692
719
|
autoMemoryDirectory: bridgeRuntimeFiles.memoryDir(),
|
|
693
720
|
});
|
|
@@ -700,6 +727,45 @@ describe("BridgeJobExecutor", () => {
|
|
|
700
727
|
queryResults: [scriptedMessages(resultMessage(0, "session_no_mem"))],
|
|
701
728
|
});
|
|
702
729
|
await executor.execute({ threadId: "thread_no_mem", turnId: "turn_no_mem", prompt: "No memory", history: history([]) }, activeControls());
|
|
730
|
+
const settings = (queryInputs[0]?.options.settings ?? {});
|
|
731
|
+
expect(settings).not.toHaveProperty("autoMemoryEnabled");
|
|
732
|
+
expect(settings).not.toHaveProperty("autoMemoryDirectory");
|
|
733
|
+
});
|
|
734
|
+
// Left unset, the CLI's compaction threshold falls through to a remotely-controlled
|
|
735
|
+
// client-data value or experiment gate that can cap it well under the CLI's own
|
|
736
|
+
// ceiling, with no change on our side and no warning to the user.
|
|
737
|
+
it("pins the auto-compact window to the context window it resolved for the model", async () => {
|
|
738
|
+
const queryInputs = [];
|
|
739
|
+
const executor = createExecutor({
|
|
740
|
+
queryInputs,
|
|
741
|
+
model: "claude-sonnet-5",
|
|
742
|
+
queryResults: [scriptedMessages(resultMessage(0, "session_compact_window"))],
|
|
743
|
+
});
|
|
744
|
+
await executor.execute({ threadId: "thread_cw", turnId: "turn_cw", prompt: "Hi", history: history([]) }, activeControls());
|
|
745
|
+
expect(queryInputs[0]?.options.settings).toMatchObject({ autoCompactWindow: 1_000_000 });
|
|
746
|
+
});
|
|
747
|
+
it("leaves the auto-compact window unset for a model whose window it cannot resolve", async () => {
|
|
748
|
+
const queryInputs = [];
|
|
749
|
+
const executor = createExecutor({
|
|
750
|
+
queryInputs,
|
|
751
|
+
model: "claude-unknown",
|
|
752
|
+
queryResults: [scriptedMessages(resultMessage(0, "session_unknown_window"))],
|
|
753
|
+
});
|
|
754
|
+
await executor.execute({ threadId: "thread_uw", turnId: "turn_uw", prompt: "Hi", history: history([]) }, activeControls());
|
|
755
|
+
expect((queryInputs[0]?.options.settings ?? {})).not.toHaveProperty("autoCompactWindow");
|
|
756
|
+
});
|
|
757
|
+
// The SDK forwards `settings` to the CLI whenever the key is present — an empty
|
|
758
|
+
// object still becomes a `--settings {}` flag tier. With nothing to say, say
|
|
759
|
+
// nothing rather than hand the CLI a settings override it has to parse.
|
|
760
|
+
it("omits the settings tier entirely when it has no setting to pin", async () => {
|
|
761
|
+
vi.stubEnv("BAKER_CHAT_ID", "");
|
|
762
|
+
const queryInputs = [];
|
|
763
|
+
const executor = createExecutor({
|
|
764
|
+
queryInputs,
|
|
765
|
+
model: "claude-unknown",
|
|
766
|
+
queryResults: [scriptedMessages(resultMessage(0, "session_no_settings"))],
|
|
767
|
+
});
|
|
768
|
+
await executor.execute({ threadId: "thread_ns", turnId: "turn_ns", prompt: "Hi", history: history([]) }, activeControls());
|
|
703
769
|
expect(queryInputs[0]?.options).not.toHaveProperty("settings");
|
|
704
770
|
});
|
|
705
771
|
it("runs exact publish commands with the prompt passed through verbatim", async () => {
|
|
@@ -1425,6 +1491,9 @@ describe("BridgeJobExecutor stall recovery", () => {
|
|
|
1425
1491
|
});
|
|
1426
1492
|
expect(queryInputs).toHaveLength(2);
|
|
1427
1493
|
expect(queryInputs[1].options.resume).toBe("session_a");
|
|
1494
|
+
// Forked: the stalled CLI can outlive its SIGTERM by a few seconds and
|
|
1495
|
+
// still append to its transcript — the retry must not share its file.
|
|
1496
|
+
expect(queryInputs[1].options.forkSession).toBe(true);
|
|
1428
1497
|
expect(queryInputs[1].prompt).toContain("interrupted by a connection failure");
|
|
1429
1498
|
});
|
|
1430
1499
|
it("catches a retry that hangs before its first message", async () => {
|
|
@@ -1518,5 +1587,235 @@ describe("BridgeJobExecutor stall recovery", () => {
|
|
|
1518
1587
|
expect(outcome).toMatchObject({ kind: "completed" });
|
|
1519
1588
|
expect(queryInputs).toHaveLength(1);
|
|
1520
1589
|
});
|
|
1590
|
+
function assistantToolUses(uuid, sessionId, toolUseIds) {
|
|
1591
|
+
return {
|
|
1592
|
+
type: "assistant",
|
|
1593
|
+
uuid,
|
|
1594
|
+
session_id: sessionId,
|
|
1595
|
+
message: {
|
|
1596
|
+
role: "assistant",
|
|
1597
|
+
content: toolUseIds.map((id) => ({ type: "tool_use", id, name: "Bash", input: {} })),
|
|
1598
|
+
stop_reason: "tool_use",
|
|
1599
|
+
},
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
function toolResultFor(sessionId, toolUseId) {
|
|
1603
|
+
return {
|
|
1604
|
+
type: "user",
|
|
1605
|
+
session_id: sessionId,
|
|
1606
|
+
message: {
|
|
1607
|
+
role: "user",
|
|
1608
|
+
content: [{ type: "tool_result", tool_use_id: toolUseId, content: "ok" }],
|
|
1609
|
+
},
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
it("does not treat silence as a stall while a parallel sibling tool is still executing", async () => {
|
|
1613
|
+
// Regression: an assistant turn issues two tool calls (or a fan-out of
|
|
1614
|
+
// subagents interleaves theirs); the fast one returns while the slow one —
|
|
1615
|
+
// a multi-minute canvas render — is still running. The tool_result used to
|
|
1616
|
+
// arm the detector and the quiet render window was mistaken for a stall,
|
|
1617
|
+
// killing the CLI mid-work.
|
|
1618
|
+
const queryInputs = [];
|
|
1619
|
+
let release;
|
|
1620
|
+
const siblingDone = new Promise((resolveSibling) => {
|
|
1621
|
+
release = resolveSibling;
|
|
1622
|
+
});
|
|
1623
|
+
const query = {
|
|
1624
|
+
close: () => release(),
|
|
1625
|
+
async *[Symbol.asyncIterator]() {
|
|
1626
|
+
yield assistantToolUses("assistant_par", "session_a", ["toolu_fast", "toolu_slow"]);
|
|
1627
|
+
yield toolResultFor("session_a", "toolu_fast");
|
|
1628
|
+
await siblingDone; // the slow sibling, running well past the stall window
|
|
1629
|
+
yield toolResultFor("session_a", "toolu_slow");
|
|
1630
|
+
yield resultMessage(0.01, "session_a");
|
|
1631
|
+
},
|
|
1632
|
+
};
|
|
1633
|
+
setTimeout(release, 80);
|
|
1634
|
+
const executor = createExecutor({
|
|
1635
|
+
queryInputs,
|
|
1636
|
+
stallTimeoutMs: 20,
|
|
1637
|
+
stallCheckIntervalMs: 5,
|
|
1638
|
+
queryResults: [query],
|
|
1639
|
+
});
|
|
1640
|
+
const outcome = await executor.execute({ threadId: "thread_par", turnId: "turn_par", prompt: "Render both" }, activeControls());
|
|
1641
|
+
expect(outcome).toMatchObject({ kind: "completed" });
|
|
1642
|
+
expect(queryInputs).toHaveLength(1);
|
|
1643
|
+
});
|
|
1644
|
+
it("still retries when the stream hangs after the last outstanding tool result", async () => {
|
|
1645
|
+
// The true-positive the detector exists for: every tool has returned, the
|
|
1646
|
+
// model should be talking, and nothing comes.
|
|
1647
|
+
const queryInputs = [];
|
|
1648
|
+
const query = (() => {
|
|
1649
|
+
let release;
|
|
1650
|
+
const closed = new Promise((resolveClosed) => {
|
|
1651
|
+
release = resolveClosed;
|
|
1652
|
+
});
|
|
1653
|
+
return {
|
|
1654
|
+
close: () => release(),
|
|
1655
|
+
async *[Symbol.asyncIterator]() {
|
|
1656
|
+
yield assistantToolUses("assistant_only", "session_a", ["toolu_only"]);
|
|
1657
|
+
yield toolResultFor("session_a", "toolu_only");
|
|
1658
|
+
await closed; // hung upstream request
|
|
1659
|
+
},
|
|
1660
|
+
};
|
|
1661
|
+
})();
|
|
1662
|
+
const executor = createExecutor({
|
|
1663
|
+
queryInputs,
|
|
1664
|
+
stallTimeoutMs: 20,
|
|
1665
|
+
stallCheckIntervalMs: 5,
|
|
1666
|
+
queryResults: [query, scriptedMessages(resultMessage(0.03, "session_a"))],
|
|
1667
|
+
});
|
|
1668
|
+
const outcome = await executor.execute({ threadId: "thread_hang", turnId: "turn_hang", prompt: "Run then hang" }, activeControls());
|
|
1669
|
+
expect(outcome).toMatchObject({ kind: "completed", costUsd: 0.03 });
|
|
1670
|
+
expect(queryInputs).toHaveLength(2);
|
|
1671
|
+
});
|
|
1672
|
+
it("surfaces the control-channel breaker even when the detector also flagged a stall", async () => {
|
|
1673
|
+
// A poisoned control channel is a terminal verdict for the turn — it must
|
|
1674
|
+
// not be rewritten into a stall retry that resumes the poisoned session.
|
|
1675
|
+
const query = {
|
|
1676
|
+
close: () => undefined, // a wedged CLI that ignores the close
|
|
1677
|
+
async *[Symbol.asyncIterator]() {
|
|
1678
|
+
yield controlChannelDenyMessage();
|
|
1679
|
+
// Quiet long enough for the stall detector to fire, then two more denies.
|
|
1680
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 60));
|
|
1681
|
+
yield controlChannelDenyMessage();
|
|
1682
|
+
yield controlChannelDenyMessage();
|
|
1683
|
+
},
|
|
1684
|
+
};
|
|
1685
|
+
const executor = createExecutor({
|
|
1686
|
+
stallTimeoutMs: 20,
|
|
1687
|
+
stallCheckIntervalMs: 5,
|
|
1688
|
+
queryResults: [query],
|
|
1689
|
+
});
|
|
1690
|
+
const outcome = await executor.execute({ threadId: "thread_deny_stall", turnId: "turn_deny_stall", prompt: "baker canvas run x" }, activeControls());
|
|
1691
|
+
expect(outcome).toMatchObject({ kind: "errored" });
|
|
1692
|
+
expect(outcome.errors[0]).toContain("control-channel closed");
|
|
1693
|
+
});
|
|
1694
|
+
});
|
|
1695
|
+
describe("BridgeJobExecutor turn end with background tasks", () => {
|
|
1696
|
+
function taskStarted(taskId, extras) {
|
|
1697
|
+
return {
|
|
1698
|
+
type: "system",
|
|
1699
|
+
subtype: "task_started",
|
|
1700
|
+
task_id: taskId,
|
|
1701
|
+
description: "Build a creative",
|
|
1702
|
+
subagent_type: "creative-producer",
|
|
1703
|
+
session_id: "session_bg",
|
|
1704
|
+
...extras,
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
function taskSettled(taskId) {
|
|
1708
|
+
return {
|
|
1709
|
+
type: "system",
|
|
1710
|
+
subtype: "task_notification",
|
|
1711
|
+
task_id: taskId,
|
|
1712
|
+
status: "completed",
|
|
1713
|
+
summary: "done",
|
|
1714
|
+
session_id: "session_bg",
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
// Mimics the real CLI on a held-open input stream: it emits its messages,
|
|
1718
|
+
// then stays alive until endInput() (stdin EOF) — and once input has ended,
|
|
1719
|
+
// it exits instead of producing further turns.
|
|
1720
|
+
function cliLikeQuery(endInputAt, ...messages) {
|
|
1721
|
+
let release;
|
|
1722
|
+
let inputEnded = false;
|
|
1723
|
+
const ended = new Promise((resolveEnded) => {
|
|
1724
|
+
release = resolveEnded;
|
|
1725
|
+
});
|
|
1726
|
+
let emitted = 0;
|
|
1727
|
+
return {
|
|
1728
|
+
close: () => release(),
|
|
1729
|
+
endInput: () => {
|
|
1730
|
+
if (!inputEnded) {
|
|
1731
|
+
inputEnded = true;
|
|
1732
|
+
endInputAt.count = emitted;
|
|
1733
|
+
release();
|
|
1734
|
+
}
|
|
1735
|
+
},
|
|
1736
|
+
async *[Symbol.asyncIterator]() {
|
|
1737
|
+
for (const message of messages) {
|
|
1738
|
+
if (inputEnded) {
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
await Promise.resolve();
|
|
1742
|
+
emitted += 1;
|
|
1743
|
+
yield message;
|
|
1744
|
+
}
|
|
1745
|
+
await ended;
|
|
1746
|
+
},
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
it("ends input at the first clean result when the turn ran no tasks", async () => {
|
|
1750
|
+
const endInputAt = { count: -1 };
|
|
1751
|
+
const executor = createExecutor({
|
|
1752
|
+
queryResults: [cliLikeQuery(endInputAt, resultMessage(0.01, "session_bg"))],
|
|
1753
|
+
});
|
|
1754
|
+
const outcome = await executor.execute({ threadId: "thread_plain", turnId: "turn_plain", prompt: "Say hi", history: history([]) }, activeControls());
|
|
1755
|
+
expect(outcome).toMatchObject({ kind: "completed", costUsd: 0.01 });
|
|
1756
|
+
expect(endInputAt.count).toBe(1);
|
|
1757
|
+
});
|
|
1758
|
+
it("holds the turn open past a result while a background task is in flight", async () => {
|
|
1759
|
+
// The incident shape: the main turn's result lands while spawned subagents
|
|
1760
|
+
// are still working. Ending input there severs the CLI's tool-permission
|
|
1761
|
+
// control channel and every later tool call across the fan-out is denied.
|
|
1762
|
+
const endInputAt = { count: -1 };
|
|
1763
|
+
const executor = createExecutor({
|
|
1764
|
+
turnEndSettleMs: 20,
|
|
1765
|
+
queryResults: [
|
|
1766
|
+
cliLikeQuery(endInputAt, taskStarted("task_bg"), resultMessage(0.1, "session_bg"), taskSettled("task_bg"), resultMessage(0.2, "session_bg")),
|
|
1767
|
+
],
|
|
1768
|
+
});
|
|
1769
|
+
const outcome = await executor.execute({ threadId: "thread_bg", turnId: "turn_bg", prompt: "Fan out producers", history: history([]) }, activeControls());
|
|
1770
|
+
// All four messages were consumed before input ended: the first result did
|
|
1771
|
+
// NOT close the channel, and the turn completed on the post-task result.
|
|
1772
|
+
expect(outcome).toMatchObject({ kind: "completed", costUsd: 0.2 });
|
|
1773
|
+
expect(endInputAt.count).toBe(4);
|
|
1774
|
+
});
|
|
1775
|
+
it("ends the turn when the last task settles without waking the model into a second result", async () => {
|
|
1776
|
+
// A settle is not guaranteed to produce another result. If only a result
|
|
1777
|
+
// could arm the end, this shape would strand in `for await` until the
|
|
1778
|
+
// 30-minute session watchdog errored a perfectly healthy turn.
|
|
1779
|
+
const endInputAt = { count: -1 };
|
|
1780
|
+
const executor = createExecutor({
|
|
1781
|
+
turnEndSettleMs: 20,
|
|
1782
|
+
queryResults: [
|
|
1783
|
+
cliLikeQuery(endInputAt, taskStarted("task_silent"), resultMessage(0.1, "session_bg"), taskSettled("task_silent")),
|
|
1784
|
+
],
|
|
1785
|
+
});
|
|
1786
|
+
const outcome = await executor.execute({ threadId: "thread_silent", turnId: "turn_silent", prompt: "Fan out one producer", history: history([]) }, activeControls());
|
|
1787
|
+
expect(outcome).toMatchObject({ kind: "completed", costUsd: 0.1 });
|
|
1788
|
+
expect(endInputAt.count).toBe(3);
|
|
1789
|
+
});
|
|
1790
|
+
it("re-arms the settle window when a passive system event trails the result", async () => {
|
|
1791
|
+
// Passive trailing events (files persisted, session state) must not cancel
|
|
1792
|
+
// a scheduled end — only genuine model activity does. A cancel with no
|
|
1793
|
+
// re-arm would strand the turn the same way a silent settle did.
|
|
1794
|
+
const endInputAt = { count: -1 };
|
|
1795
|
+
const executor = createExecutor({
|
|
1796
|
+
turnEndSettleMs: 20,
|
|
1797
|
+
queryResults: [
|
|
1798
|
+
cliLikeQuery(endInputAt, taskStarted("task_bg"), taskSettled("task_bg"), resultMessage(0.1, "session_bg"), {
|
|
1799
|
+
type: "system",
|
|
1800
|
+
subtype: "session_state_changed",
|
|
1801
|
+
session_id: "session_bg",
|
|
1802
|
+
}),
|
|
1803
|
+
],
|
|
1804
|
+
});
|
|
1805
|
+
const outcome = await executor.execute({ threadId: "thread_trail", turnId: "turn_trail", prompt: "Fan out then idle", history: history([]) }, activeControls());
|
|
1806
|
+
expect(outcome).toMatchObject({ kind: "completed", costUsd: 0.1 });
|
|
1807
|
+
expect(endInputAt.count).toBe(4);
|
|
1808
|
+
});
|
|
1809
|
+
it("does not hold the turn open for ambient skip_transcript tasks", async () => {
|
|
1810
|
+
const endInputAt = { count: -1 };
|
|
1811
|
+
const executor = createExecutor({
|
|
1812
|
+
queryResults: [
|
|
1813
|
+
cliLikeQuery(endInputAt, taskStarted("task_ambient", { skip_transcript: true }), resultMessage(0.05, "session_bg")),
|
|
1814
|
+
],
|
|
1815
|
+
});
|
|
1816
|
+
const outcome = await executor.execute({ threadId: "thread_ambient", turnId: "turn_ambient", prompt: "Quick turn", history: history([]) }, activeControls());
|
|
1817
|
+
expect(outcome).toMatchObject({ kind: "completed", costUsd: 0.05 });
|
|
1818
|
+
expect(endInputAt.count).toBe(2);
|
|
1819
|
+
});
|
|
1521
1820
|
});
|
|
1522
1821
|
//# sourceMappingURL=bridge-job.test.js.map
|