@frockbot/plugin-shell 0.3.4 → 0.3.6

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.
@@ -516,7 +516,7 @@ describe("Bot selection", () => {
516
516
  const requestCount = requests.length;
517
517
  await expect(
518
518
  provided.value.callPackageUiTool(contribution, "package_author", {}),
519
- ).rejects.toThrow("did not declare");
519
+ ).rejects.toThrow("isn't allowed to use");
520
520
  expect(requests).toHaveLength(requestCount);
521
521
  });
522
522
 
@@ -571,6 +571,47 @@ describe("Bot selection", () => {
571
571
  expect(provided.value.settingsError).toBe("Bot settings unavailable");
572
572
  });
573
573
 
574
+ test("says a deployment has no Catalog instead of relaying the gateway's sentence", async () => {
575
+ // F4: the Plugins/Catalog surface rendered `catalog generation was not
576
+ // found` beside "No Catalog entry matches that search", for a User who had
577
+ // searched nothing on a deployment that had published nothing.
578
+ let provided: Ref<FrockBotWebData> | undefined;
579
+ let failure = new Error("catalog generation was not found");
580
+ await shellClientPlugin({
581
+ transport: {
582
+ turn: () => Promise.resolve({ runId: "run", text: "", events: [] }),
583
+ readConfiguration: () => Promise.reject(new Error("unused")),
584
+ hostedRequest: (path) => {
585
+ expect(path).toBe("/catalog/v1/index");
586
+ return Promise.reject(failure);
587
+ },
588
+ },
589
+ slot: () => () => {},
590
+ inject: () => {
591
+ throw new Error("unexpected client provider injection");
592
+ },
593
+ provide: (_key, value) => {
594
+ provided = value as Ref<FrockBotWebData>;
595
+ return () => {};
596
+ },
597
+ });
598
+ if (!provided) throw new Error("shell data was not provided");
599
+
600
+ await provided.value.loadPackageCatalog();
601
+ expect(provided.value.packageCatalog).toEqual([]);
602
+ expect(provided.value.packageCatalogGeneration).toBeUndefined();
603
+ expect(provided.value.settingsError).toBe(
604
+ "No plugins are published for this deployment yet.",
605
+ );
606
+
607
+ // A genuine fault still reaches the operator, labelled as one.
608
+ failure = new Error("R2 is down");
609
+ await provided.value.loadPackageCatalog();
610
+ expect(provided.value.settingsError).toBe(
611
+ "Plugins could not be loaded: R2 is down",
612
+ );
613
+ });
614
+
574
615
  test("commits a catalog without overwriting newer User settings", async () => {
575
616
  let provided: Ref<FrockBotWebData> | undefined;
576
617
  const catalogManifest = Promise.withResolvers<unknown>();
@@ -648,7 +689,7 @@ describe("Bot selection", () => {
648
689
  },
649
690
  accountValues: undefined,
650
691
  platformModel: undefined,
651
- expectedLabel: "Model name · Model provider · Bot override",
692
+ expectedLabel: "Model name · Model provider · this Bot only",
652
693
  expectedProjection: "bot",
653
694
  ready: true,
654
695
  },
@@ -683,7 +724,7 @@ describe("Bot selection", () => {
683
724
  botValues: {},
684
725
  accountValues: undefined,
685
726
  platformModel: undefined,
686
- expectedLabel: "Model unavailable",
727
+ expectedLabel: "No model available — set one up in Models",
687
728
  expectedProjection: "none",
688
729
  ready: false,
689
730
  },
@@ -900,7 +941,7 @@ describe("Bot selection", () => {
900
941
 
901
942
  expect(provided.value.modelReady).toBe(false);
902
943
  expect(provided.value.modelLabel).toBe(
903
- 'Connection "model-connection" is disabled; enable or reconnect it',
944
+ "That account needs reconnecting before this Bot can reply.",
904
945
  );
905
946
  });
906
947
 
@@ -1217,7 +1258,7 @@ describe("detached Turn projection", () => {
1217
1258
  { role: "user", status: "completed" },
1218
1259
  {
1219
1260
  role: "assistant",
1220
- text: "Provider reconciliation is required",
1261
+ text: "This Bot couldn't finish its reply. Try again.",
1221
1262
  status: "error",
1222
1263
  },
1223
1264
  ]);
@@ -1482,6 +1523,55 @@ describe("active durable Turn projection", () => {
1482
1523
  ]);
1483
1524
  });
1484
1525
 
1526
+ test("a Turn that sent something draws the send, not the model's own text", () => {
1527
+ const state: Pick<
1528
+ FrockBotWebData,
1529
+ "messages" | "activeRunId" | "activeRun"
1530
+ > = { messages: [] };
1531
+
1532
+ projectDurableRuns(
1533
+ state,
1534
+ [],
1535
+ [
1536
+ {
1537
+ runId: "run-once",
1538
+ input: "ping",
1539
+ status: "completed",
1540
+ responseText: "pong",
1541
+ events: [
1542
+ { type: "send/to-user", payload: { type: "text", text: "pong" } },
1543
+ ],
1544
+ },
1545
+ ],
1546
+ );
1547
+
1548
+ expect(state.messages[1]?.text).toBe("");
1549
+ expect(state.messages[1]?.sends).toHaveLength(1);
1550
+ });
1551
+
1552
+ test("a Turn that sent nothing still draws the model's text", () => {
1553
+ const state: Pick<
1554
+ FrockBotWebData,
1555
+ "messages" | "activeRunId" | "activeRun"
1556
+ > = { messages: [] };
1557
+
1558
+ projectDurableRuns(
1559
+ state,
1560
+ [],
1561
+ [
1562
+ {
1563
+ runId: "run-plain",
1564
+ input: "ping",
1565
+ status: "completed",
1566
+ responseText: "pong",
1567
+ events: [],
1568
+ },
1569
+ ],
1570
+ );
1571
+
1572
+ expect(state.messages[1]?.text).toBe("pong");
1573
+ });
1574
+
1485
1575
  test("streams running text without a banner and clears busy state", () => {
1486
1576
  const state: Pick<
1487
1577
  FrockBotWebData,
@@ -1562,11 +1652,11 @@ describe("active durable Turn projection", () => {
1562
1652
  expect(reconciliation.activeRun).toEqual({
1563
1653
  runId: "run-reconciliation",
1564
1654
  status: "reconciliation-required",
1565
- message: "Provider result needs confirmation",
1655
+ message: "Something went wrong mid-reply. Try again to pick it up.",
1566
1656
  canResume: true,
1567
1657
  });
1568
1658
  expect(reconciliation.messages[1]).toMatchObject({
1569
- text: "Provider result needs confirmation",
1659
+ text: "This reply stopped partway. Try again to continue it.",
1570
1660
  status: "reconciliation-required",
1571
1661
  });
1572
1662
  });
@@ -1845,16 +1935,19 @@ describe("hosted Stop", () => {
1845
1935
  await provided.value.stopRun();
1846
1936
  expect(provided.value.activeRun).toMatchObject({
1847
1937
  status: "reconciliation-required",
1848
- message:
1849
- "Stop accepted; reconciling the provider outcome before cancelling.",
1850
- canResume: false,
1938
+ message: "Stopping…",
1939
+ // A parked Turn is offered the resolve control, Stop included: hiding
1940
+ // it there hid it in exactly the case Stop creates.
1941
+ canResume: true,
1851
1942
  });
1852
1943
 
1853
1944
  await provided.value.stopRun();
1854
1945
  expect(provided.value.activeRun).toBeUndefined();
1855
1946
  expect(provided.value.activeRunId).toBeUndefined();
1947
+ // The Turn keeps whatever it had already said; the notice is the line
1948
+ // that says why it ends where it does.
1856
1949
  expect(provided.value.messages[1]).toMatchObject({
1857
- text: "Stopped by an authenticated Stop command.",
1950
+ notice: "You stopped this.",
1858
1951
  status: "aborted",
1859
1952
  });
1860
1953
 
@@ -1911,12 +2004,15 @@ describe("hosted Stop", () => {
1911
2004
 
1912
2005
  await provided.value.stopRun();
1913
2006
 
1914
- expect(lookups).toBe(1);
2007
+ // Two, and deliberately: a client that finds itself holding an active run
2008
+ // it did not start now observes it (a reload mid-Turn, a second tab), and
2009
+ // Stop then observes the settlement it asked for.
2010
+ expect(lookups).toBe(2);
1915
2011
  expect(provided.value.activeRun).toBeUndefined();
1916
2012
  expect(provided.value.activeRunId).toBeUndefined();
1917
2013
  expect(provided.value.messages.at(-1)).toMatchObject({
1918
2014
  runId: "run-1",
1919
- text: "Stopped by an authenticated Stop command.",
2015
+ notice: "You stopped this.",
1920
2016
  status: "aborted",
1921
2017
  });
1922
2018
  });
@@ -2506,7 +2602,7 @@ describe("Connection operation reconciliation", () => {
2506
2602
  ).rejects.toThrow("Connection state update failed");
2507
2603
  await expect(
2508
2604
  provided.value.disconnectConnection("connection-1"),
2509
- ).rejects.toThrow("Connection revocation failed");
2605
+ ).rejects.toThrow("Couldn't disconnect that account.");
2510
2606
  expect(commands).toEqual([
2511
2607
  "connection/update-label",
2512
2608
  "connection/set-enabled",
@@ -2897,7 +2993,7 @@ describe("a message sent while a Turn is running", () => {
2897
2993
  // The superseded Turn keeps the quiet treatment a stopped one gets.
2898
2994
  expect(state.messages[1]).toMatchObject({
2899
2995
  status: "aborted",
2900
- text: "Interrupted by your next message.",
2996
+ notice: "Interrupted by your next message.",
2901
2997
  });
2902
2998
  });
2903
2999