@frockbot/plugin-shell 0.3.11 → 0.3.13
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/package.json +35 -33
- package/src/agent.test.ts +78 -0
- package/src/agent.ts +130 -2
- package/src/backend-configuration.test.ts +26 -26
- package/src/backend-recovery-integration.test.ts +10 -10
- package/src/backend-runner.ts +19 -2
- package/src/backend.ts +85 -18
- package/src/client/AppletCanvas.vue +19 -6
- package/src/client/FrockBotApp.vue +405 -75
- package/src/client/activity-trail.test.ts +205 -0
- package/src/client/activity-trail.ts +227 -0
- package/src/client/applets-client.test.ts +62 -0
- package/src/client/applets-client.ts +19 -0
- package/src/client/index.test.ts +128 -21
- package/src/client/index.ts +359 -114
- package/src/client/model-presentation.test.ts +3 -3
- package/src/client/no-bot-model-label.test.ts +7 -7
- package/src/client/skill-invocation.test.ts +34 -0
- package/src/client/skill-invocation.ts +22 -0
- package/src/client/styles.css +69 -19
- package/src/client/transcript-cache.test.ts +125 -0
- package/src/client/transcript-cache.ts +190 -0
- package/src/compaction-scheduler.test.ts +96 -0
- package/src/compaction-scheduler.ts +108 -0
- package/src/compaction-transcript.test.ts +174 -0
- package/src/compaction.test.ts +596 -0
- package/src/compaction.ts +539 -0
- package/src/focus.test.ts +222 -0
- package/src/focus.ts +93 -0
- package/src/history.ts +86 -8
- package/src/legacy-frock-model-id.test.ts +148 -0
- package/src/notification-id.ts +0 -0
- package/src/run-failure-copy.test.ts +150 -0
- package/src/run-failure-copy.ts +110 -0
- package/src/run-protocol.test.ts +50 -7
- package/src/run-protocol.ts +152 -43
- package/src/settings-links.test.ts +8 -2
- package/src/settings-links.ts +11 -2
- package/src/shared.ts +36 -0
- package/tsconfig.json +1 -2
- package/src/client/activity-ring.test.ts +0 -89
- package/src/client/activity-ring.ts +0 -94
package/src/client/index.test.ts
CHANGED
|
@@ -572,7 +572,11 @@ describe("Bot selection", () => {
|
|
|
572
572
|
await Promise.all([botLoad, olderLoad]);
|
|
573
573
|
|
|
574
574
|
expect(provided.value.userSettings?.profile.name).toBe("Newer");
|
|
575
|
-
|
|
575
|
+
// The surface says what failed and what it was for; the thrown text
|
|
576
|
+
// ("Bot settings unavailable") stays in the console.
|
|
577
|
+
expect(provided.value.settingsError).toBe(
|
|
578
|
+
"Couldn't load this Bot's settings.",
|
|
579
|
+
);
|
|
576
580
|
});
|
|
577
581
|
|
|
578
582
|
test("says a deployment has no Catalog instead of relaying the gateway's sentence", async () => {
|
|
@@ -608,12 +612,14 @@ describe("Bot selection", () => {
|
|
|
608
612
|
"No plugins are published for this deployment yet.",
|
|
609
613
|
);
|
|
610
614
|
|
|
611
|
-
// A genuine fault
|
|
615
|
+
// A genuine fault is labelled as one — and says what failed rather than
|
|
616
|
+
// what the storage layer called it. "R2 is down" is for the console.
|
|
612
617
|
failure = new Error("R2 is down");
|
|
613
618
|
await provided.value.loadPackageCatalog();
|
|
614
619
|
expect(provided.value.settingsError).toBe(
|
|
615
|
-
"
|
|
620
|
+
"Couldn't load the plugin catalog.",
|
|
616
621
|
);
|
|
622
|
+
expect(provided.value.settingsError).not.toContain("R2");
|
|
617
623
|
});
|
|
618
624
|
|
|
619
625
|
test("commits a catalog without overwriting newer User settings", async () => {
|
|
@@ -1258,14 +1264,20 @@ describe("detached Turn projection", () => {
|
|
|
1258
1264
|
],
|
|
1259
1265
|
);
|
|
1260
1266
|
|
|
1267
|
+
// The failure is a notice under the reply, never a bubble that reads as
|
|
1268
|
+
// the Bot saying the provider's own words.
|
|
1261
1269
|
expect(messages).toMatchObject([
|
|
1262
1270
|
{ role: "user", status: "completed" },
|
|
1263
1271
|
{
|
|
1264
1272
|
role: "assistant",
|
|
1265
|
-
text: "
|
|
1273
|
+
text: "",
|
|
1274
|
+
notice: "This Bot couldn't finish its reply. Try again.",
|
|
1266
1275
|
status: "error",
|
|
1267
1276
|
},
|
|
1268
1277
|
]);
|
|
1278
|
+
expect(JSON.stringify(messages)).not.toContain(
|
|
1279
|
+
"Provider reconciliation is required",
|
|
1280
|
+
);
|
|
1269
1281
|
});
|
|
1270
1282
|
});
|
|
1271
1283
|
|
|
@@ -1514,16 +1526,95 @@ describe("active durable Turn projection", () => {
|
|
|
1514
1526
|
],
|
|
1515
1527
|
);
|
|
1516
1528
|
|
|
1517
|
-
|
|
1529
|
+
// One message per send, in the order they were sent, and nothing merged:
|
|
1530
|
+
// the last line is the Turn's own, and it carries no send at all.
|
|
1531
|
+
expect(
|
|
1532
|
+
state.messages.slice(1).map((message) => [message.id, message.sends]),
|
|
1533
|
+
).toEqual([
|
|
1534
|
+
[
|
|
1535
|
+
"run-send:send:0",
|
|
1536
|
+
[{ kind: "payload", payload: { type: "text", text: "On it." } }],
|
|
1537
|
+
],
|
|
1538
|
+
[
|
|
1539
|
+
"run-send:send:1",
|
|
1540
|
+
[
|
|
1541
|
+
{
|
|
1542
|
+
kind: "payload",
|
|
1543
|
+
payload: {
|
|
1544
|
+
type: "widget",
|
|
1545
|
+
widget: { prompt: "Which day?", options: ["Tue"] },
|
|
1546
|
+
},
|
|
1547
|
+
},
|
|
1548
|
+
],
|
|
1549
|
+
],
|
|
1550
|
+
["run-send:send:2", [{ kind: "unsupported" }]],
|
|
1551
|
+
["run-send:assistant", []],
|
|
1552
|
+
]);
|
|
1553
|
+
});
|
|
1554
|
+
|
|
1555
|
+
test("a second send is a new bubble, and never rewrites the first", () => {
|
|
1556
|
+
const state: Pick<
|
|
1557
|
+
FrockBotWebData,
|
|
1558
|
+
"messages" | "activeRunId" | "activeRun"
|
|
1559
|
+
> = { messages: [] };
|
|
1560
|
+
const run = (
|
|
1561
|
+
events: ClientRun["events"],
|
|
1562
|
+
status: ClientRun["status"] = "running",
|
|
1563
|
+
): ClientRun => ({
|
|
1564
|
+
runId: "run-stack",
|
|
1565
|
+
input: "Book it",
|
|
1566
|
+
admittedAt: "2026-09-04T00:00:00.000Z",
|
|
1567
|
+
status,
|
|
1568
|
+
events,
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
// The acknowledgement arrives first, on its own.
|
|
1572
|
+
projectDurableRuns(state, [], [
|
|
1573
|
+
run([
|
|
1574
|
+
{ type: "send/to-user", payload: { type: "text", text: "On it." } },
|
|
1575
|
+
]),
|
|
1576
|
+
] as ClientRun[]);
|
|
1577
|
+
const acknowledgement = state.messages[1];
|
|
1578
|
+
expect(acknowledgement?.sends).toEqual([
|
|
1518
1579
|
{ kind: "payload", payload: { type: "text", text: "On it." } },
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1580
|
+
]);
|
|
1581
|
+
|
|
1582
|
+
// The result follows in the same Turn. It appends: the first bubble is
|
|
1583
|
+
// still the first bubble, still saying what it said.
|
|
1584
|
+
projectDurableRuns(state, [], [
|
|
1585
|
+
run(
|
|
1586
|
+
[
|
|
1587
|
+
{
|
|
1588
|
+
type: "send/to-user",
|
|
1589
|
+
payload: { type: "text", text: "On it." },
|
|
1590
|
+
},
|
|
1591
|
+
{ type: "send/to-user", payload: { type: "text", text: "Booked." } },
|
|
1592
|
+
],
|
|
1593
|
+
"completed",
|
|
1594
|
+
),
|
|
1595
|
+
] as ClientRun[]);
|
|
1596
|
+
|
|
1597
|
+
expect(
|
|
1598
|
+
state.messages
|
|
1599
|
+
.filter((message) => message.sends.length > 0)
|
|
1600
|
+
.map((message) => [message.id, message.sends]),
|
|
1601
|
+
).toEqual([
|
|
1602
|
+
[
|
|
1603
|
+
"run-stack:send:0",
|
|
1604
|
+
[{ kind: "payload", payload: { type: "text", text: "On it." } }],
|
|
1605
|
+
],
|
|
1606
|
+
[
|
|
1607
|
+
"run-stack:send:1",
|
|
1608
|
+
[{ kind: "payload", payload: { type: "text", text: "Booked." } }],
|
|
1609
|
+
],
|
|
1610
|
+
]);
|
|
1611
|
+
// The Turn's lines stay together and in order behind the prompt that
|
|
1612
|
+
// started it, so a reload draws the thread that was watched being written.
|
|
1613
|
+
expect(state.messages.map((message) => message.id)).toEqual([
|
|
1614
|
+
"run-stack:user",
|
|
1615
|
+
"run-stack:send:0",
|
|
1616
|
+
"run-stack:send:1",
|
|
1617
|
+
"run-stack:assistant",
|
|
1527
1618
|
]);
|
|
1528
1619
|
});
|
|
1529
1620
|
|
|
@@ -1549,8 +1640,8 @@ describe("active durable Turn projection", () => {
|
|
|
1549
1640
|
],
|
|
1550
1641
|
);
|
|
1551
1642
|
|
|
1552
|
-
expect(state.messages[1]?.text).toBe("");
|
|
1553
1643
|
expect(state.messages[1]?.sends).toHaveLength(1);
|
|
1644
|
+
expect(state.messages[2]?.text).toBe("");
|
|
1554
1645
|
});
|
|
1555
1646
|
|
|
1556
1647
|
test("a Turn that sent nothing still draws the model's text", () => {
|
|
@@ -1700,8 +1791,8 @@ describe("active durable Turn projection", () => {
|
|
|
1700
1791
|
},
|
|
1701
1792
|
],
|
|
1702
1793
|
);
|
|
1703
|
-
expect(state.messages[1]).toMatchObject({ text: "", status: "streaming" });
|
|
1704
1794
|
expect(state.messages[1]?.sends).toHaveLength(1);
|
|
1795
|
+
expect(state.messages[2]).toMatchObject({ text: "", status: "streaming" });
|
|
1705
1796
|
});
|
|
1706
1797
|
|
|
1707
1798
|
test("projects reconciliation-required recovery state", () => {
|
|
@@ -1726,6 +1817,9 @@ describe("active durable Turn projection", () => {
|
|
|
1726
1817
|
},
|
|
1727
1818
|
],
|
|
1728
1819
|
);
|
|
1820
|
+
// One sentence of the product's own, and the raw provider text nowhere on
|
|
1821
|
+
// screen — the banner, the bubble and the notice all read the same way
|
|
1822
|
+
// whatever the provider called the failure.
|
|
1729
1823
|
expect(reconciliation.activeRun).toEqual({
|
|
1730
1824
|
runId: "run-reconciliation",
|
|
1731
1825
|
status: "reconciliation-required",
|
|
@@ -1733,9 +1827,14 @@ describe("active durable Turn projection", () => {
|
|
|
1733
1827
|
canResume: true,
|
|
1734
1828
|
});
|
|
1735
1829
|
expect(reconciliation.messages[1]).toMatchObject({
|
|
1736
|
-
|
|
1830
|
+
role: "assistant",
|
|
1831
|
+
text: "",
|
|
1832
|
+
notice: "This reply stopped partway. Try again to continue it.",
|
|
1737
1833
|
status: "reconciliation-required",
|
|
1738
1834
|
});
|
|
1835
|
+
expect(JSON.stringify(reconciliation.messages)).not.toContain(
|
|
1836
|
+
"Provider result needs confirmation",
|
|
1837
|
+
);
|
|
1739
1838
|
});
|
|
1740
1839
|
|
|
1741
1840
|
test("keeps busy state until the durable run becomes terminal", () => {
|
|
@@ -3195,11 +3294,14 @@ describe("a message sent while a Turn is running", () => {
|
|
|
3195
3294
|
expect(state.messages.every((message) => !message.pending)).toBe(true);
|
|
3196
3295
|
expect(state.activeRunId).toBe("run-2");
|
|
3197
3296
|
expect(state.runningRunId).toBe("run-2");
|
|
3198
|
-
// The superseded Turn
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3297
|
+
// The superseded Turn is left unlabelled: the message that replaced it is
|
|
3298
|
+
// right underneath, and it says why the Turn ends there better than a
|
|
3299
|
+
// notice of ours would.
|
|
3300
|
+
expect(state.messages[1]).toMatchObject({ status: "aborted" });
|
|
3301
|
+
expect(state.messages[1]?.notice).toBeUndefined();
|
|
3302
|
+
expect(state.messages.some((message) => message.notice !== undefined)).toBe(
|
|
3303
|
+
false,
|
|
3304
|
+
);
|
|
3203
3305
|
});
|
|
3204
3306
|
|
|
3205
3307
|
test("a reload reconstructs the greyed state from durable runs alone", () => {
|
|
@@ -3234,6 +3336,11 @@ describe("a message sent while a Turn is running", () => {
|
|
|
3234
3336
|
text: "second",
|
|
3235
3337
|
pending: true,
|
|
3236
3338
|
});
|
|
3339
|
+
// The durable record still says why the Turn ended; the transcript does
|
|
3340
|
+
// not repeat it at the reader.
|
|
3341
|
+
expect(
|
|
3342
|
+
reloaded.messages.some((message) => message.notice !== undefined),
|
|
3343
|
+
).toBe(false);
|
|
3237
3344
|
expect(reloaded.runningRunId).toBeUndefined();
|
|
3238
3345
|
expect(reloaded.activeRunId).toBe("run-2");
|
|
3239
3346
|
});
|