@frockbot/plugin-shell 0.3.1 → 0.3.2
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 +32 -29
- package/src/backend-applets.test.ts +581 -0
- package/src/backend-applets.ts +959 -0
- package/src/backend-authoring.test.ts +61 -19
- package/src/backend-authoring.ts +52 -26
- package/src/backend-composition.ts +64 -0
- package/src/backend-computer.test.ts +128 -0
- package/src/backend-computer.ts +81 -0
- package/src/backend-configuration.test.ts +8 -1
- package/src/backend-iframe-ui.test.ts +29 -12
- package/src/backend-isolate.ts +31 -5
- package/src/backend-package-catalog.test.ts +13 -8
- package/src/backend-package-catalog.ts +8 -6
- package/src/backend-recovery-integration.test.ts +23 -0
- package/src/backend.ts +508 -5
- package/src/client/AppletCanvas.vue +679 -0
- package/src/client/FrockBotApp.vue +169 -15
- package/src/client/PackageEntryTrigger.vue +77 -0
- package/src/client/PackageIframeHost.vue +148 -47
- package/src/client/PackageIframeSettings.vue +8 -6
- package/src/client/PackageSurfacePage.vue +39 -0
- package/src/client/applets-client.test.ts +204 -0
- package/src/client/applets-client.ts +139 -0
- package/src/client/applets-state.ts +64 -0
- package/src/client/index.test.ts +13 -7
- package/src/client/index.ts +308 -1
- package/src/client/package-iframe-entries.test.ts +122 -0
- package/src/client/package-iframe-entries.ts +112 -0
- package/src/client/package-iframe-host-message.test.ts +3 -3
- package/src/client/package-iframe-host-message.ts +3 -3
- package/src/client/styles.css +107 -1
- package/src/composition-views.ts +31 -6
- package/src/shared.ts +52 -0
package/src/client/styles.css
CHANGED
|
@@ -49,6 +49,15 @@
|
|
|
49
49
|
--panel-current: var(--panel-surface-width);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/*
|
|
53
|
+
* An Applet needs room a summary column does not have, and how much is the
|
|
54
|
+
* User's: `--applet-panel-width` is the width they last dragged, written by
|
|
55
|
+
* the component and remembered per browser.
|
|
56
|
+
*/
|
|
57
|
+
.app-shell.panel-open.panel-applet {
|
|
58
|
+
--panel-current: var(--applet-panel-width, 480px);
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
/* Sidebar */
|
|
53
62
|
|
|
54
63
|
.sidebar {
|
|
@@ -577,6 +586,44 @@
|
|
|
577
586
|
* message being composed, not a separate control.
|
|
578
587
|
*/
|
|
579
588
|
|
|
589
|
+
/*
|
|
590
|
+
* The focused Applet, on a phone, while its panel is closed.
|
|
591
|
+
*
|
|
592
|
+
* It sits above the composer rather than inside it: the composer's row is the
|
|
593
|
+
* message and the send control, and squeezing a third thing into it is what
|
|
594
|
+
* makes a phone composer unusable.
|
|
595
|
+
*/
|
|
596
|
+
.applet-chip {
|
|
597
|
+
position: absolute;
|
|
598
|
+
bottom: calc(100% + 8px);
|
|
599
|
+
left: 0;
|
|
600
|
+
display: inline-flex;
|
|
601
|
+
max-width: 100%;
|
|
602
|
+
height: var(--frock-control-md);
|
|
603
|
+
align-items: center;
|
|
604
|
+
gap: 6px;
|
|
605
|
+
padding: 0 10px;
|
|
606
|
+
border: 1px solid var(--frock-border-strong);
|
|
607
|
+
border-radius: 999px;
|
|
608
|
+
color: var(--frock-text);
|
|
609
|
+
background: var(--frock-surface-raised);
|
|
610
|
+
box-shadow: var(--frock-shadow-control);
|
|
611
|
+
font-size: var(--frock-text-sm);
|
|
612
|
+
cursor: pointer;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.applet-chip-name {
|
|
616
|
+
overflow: hidden;
|
|
617
|
+
white-space: nowrap;
|
|
618
|
+
text-overflow: ellipsis;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.applet-chip-action {
|
|
622
|
+
flex: 0 0 auto;
|
|
623
|
+
color: var(--frock-action-primary);
|
|
624
|
+
font-weight: 600;
|
|
625
|
+
}
|
|
626
|
+
|
|
580
627
|
.composer-body {
|
|
581
628
|
display: flex;
|
|
582
629
|
width: 100%;
|
|
@@ -702,6 +749,50 @@
|
|
|
702
749
|
flex-direction: column;
|
|
703
750
|
}
|
|
704
751
|
|
|
752
|
+
.panel-applet .right-panel-content {
|
|
753
|
+
width: var(--applet-panel-width, 480px);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/*
|
|
757
|
+
* The Applet panel's drag edge. It is 8px of grab area over a 1px seam, it
|
|
758
|
+
* takes focus, and the arrow keys resize by the same amounts the drag does, so
|
|
759
|
+
* the width is not a mouse-only setting.
|
|
760
|
+
*/
|
|
761
|
+
.applet-panel-handle {
|
|
762
|
+
position: absolute;
|
|
763
|
+
z-index: 16;
|
|
764
|
+
top: 0;
|
|
765
|
+
bottom: 0;
|
|
766
|
+
left: -4px;
|
|
767
|
+
width: 8px;
|
|
768
|
+
border: 0;
|
|
769
|
+
padding: 0;
|
|
770
|
+
background: transparent;
|
|
771
|
+
cursor: col-resize;
|
|
772
|
+
touch-action: none;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.applet-panel-handle::after {
|
|
776
|
+
position: absolute;
|
|
777
|
+
top: 0;
|
|
778
|
+
bottom: 0;
|
|
779
|
+
left: 3px;
|
|
780
|
+
width: 2px;
|
|
781
|
+
background: transparent;
|
|
782
|
+
content: "";
|
|
783
|
+
transition: background-color var(--frock-motion-fast);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.applet-panel-handle:hover::after,
|
|
787
|
+
.applet-panel-handle:focus-visible::after {
|
|
788
|
+
background: var(--frock-action-primary);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.applet-panel-handle:focus-visible {
|
|
792
|
+
outline: 2px solid var(--frock-border-focus);
|
|
793
|
+
outline-offset: -2px;
|
|
794
|
+
}
|
|
795
|
+
|
|
705
796
|
.right-panel-header {
|
|
706
797
|
display: flex;
|
|
707
798
|
height: var(--frock-titlebar-height);
|
|
@@ -788,6 +879,11 @@
|
|
|
788
879
|
width: var(--panel-surface-width);
|
|
789
880
|
}
|
|
790
881
|
|
|
882
|
+
.app-shell.panel-applet .right-panel {
|
|
883
|
+
width: var(--applet-panel-width, 480px);
|
|
884
|
+
max-width: 100%;
|
|
885
|
+
}
|
|
886
|
+
|
|
791
887
|
/*
|
|
792
888
|
* A parked panel is hidden as well as off-canvas: it is not something to
|
|
793
889
|
* tab into, read out, or hit-test, and `visibility` is the one property that
|
|
@@ -894,15 +990,25 @@
|
|
|
894
990
|
|
|
895
991
|
/* The right panel is a drawer too, and on a phone it takes the whole width. */
|
|
896
992
|
.right-panel,
|
|
897
|
-
.app-shell.panel-surface .right-panel
|
|
993
|
+
.app-shell.panel-surface .right-panel,
|
|
994
|
+
.app-shell.panel-applet .right-panel {
|
|
898
995
|
width: 100%;
|
|
899
996
|
}
|
|
900
997
|
|
|
901
998
|
.right-panel-content,
|
|
999
|
+
.panel-applet .right-panel-content,
|
|
902
1000
|
.panel-surface-view {
|
|
903
1001
|
width: 100%;
|
|
904
1002
|
}
|
|
905
1003
|
|
|
1004
|
+
/*
|
|
1005
|
+
* A focused Applet on a phone is a full-height sheet, not a wider column:
|
|
1006
|
+
* there is one column, so the canvas is the whole of it while it is open.
|
|
1007
|
+
*/
|
|
1008
|
+
.app-shell.panel-open.panel-applet {
|
|
1009
|
+
--applet-panel-width: 100%;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
906
1012
|
/* There is no window chrome to drag and no traffic lights to clear. */
|
|
907
1013
|
.brand {
|
|
908
1014
|
padding-left: 0;
|
package/src/composition-views.ts
CHANGED
|
@@ -39,7 +39,26 @@ export type CompositionMemberManifestReaderV1 = (
|
|
|
39
39
|
member: CompositionMemberV1,
|
|
40
40
|
) => Promise<FrockBotManifest | undefined>;
|
|
41
41
|
|
|
42
|
-
/**
|
|
42
|
+
/** What the shell attributes an iframe page to, by where its code came from. */
|
|
43
|
+
function iframeProvenanceV1(
|
|
44
|
+
member: CompositionMemberV1,
|
|
45
|
+
): PackageIframeCompositionV1["contributions"][number]["provenance"] {
|
|
46
|
+
if (member.provenance.kind === "bot") return "Bot-authored";
|
|
47
|
+
if (member.provenance.kind === "first-party") return "FrockBot";
|
|
48
|
+
return "User-installed";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Project iframe Contributions from every artifact-backed member.
|
|
53
|
+
*
|
|
54
|
+
* The test is the artifact, not the provenance. "Every Contribution kind is
|
|
55
|
+
* resolved from the manifest and an artifact, never from a switch over Package
|
|
56
|
+
* identity" — a first-party Package that ships as an artifact-backed member
|
|
57
|
+
* (ADR 0022 decision 8) has a manifest the Bot object holds and pages the
|
|
58
|
+
* anonymous origin can serve, so there is nothing left for a provenance test
|
|
59
|
+
* to decide. A first-party member with no artifact is in-process code whose
|
|
60
|
+
* client Contribution is a compiled module, and it has no iframe page to show.
|
|
61
|
+
*/
|
|
43
62
|
export async function projectPackageIframeCompositionV1(input: {
|
|
44
63
|
botId: string;
|
|
45
64
|
generation: CompositionGenerationV1;
|
|
@@ -47,7 +66,7 @@ export async function projectPackageIframeCompositionV1(input: {
|
|
|
47
66
|
}): Promise<PackageIframeCompositionV1> {
|
|
48
67
|
const contributions: PackageIframeCompositionV1["contributions"] = [];
|
|
49
68
|
for (const member of input.generation.members) {
|
|
50
|
-
if (member.
|
|
69
|
+
if (member.artifact === undefined) continue;
|
|
51
70
|
const manifest = await input.readMemberManifest(member);
|
|
52
71
|
if (!manifest) continue;
|
|
53
72
|
const client = manifest.contributions.client;
|
|
@@ -55,10 +74,16 @@ export async function projectPackageIframeCompositionV1(input: {
|
|
|
55
74
|
contributions.push({
|
|
56
75
|
packageId: member.packageId,
|
|
57
76
|
displayName: manifest.displayName,
|
|
58
|
-
provenance:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
provenance: iframeProvenanceV1(member),
|
|
78
|
+
pages: client.pages.map((page) => ({
|
|
79
|
+
id: page.id,
|
|
80
|
+
artifact: { ...page.artifact },
|
|
81
|
+
mounts: page.mounts.map((mount) => ({ ...mount })),
|
|
82
|
+
})),
|
|
83
|
+
entries: (client.entries ?? []).map((entry) => ({
|
|
84
|
+
...entry,
|
|
85
|
+
opens: { ...entry.opens },
|
|
86
|
+
})),
|
|
62
87
|
declaredTools: (manifest.tools ?? []).map((tool) => tool.name),
|
|
63
88
|
});
|
|
64
89
|
}
|
package/src/shared.ts
CHANGED
|
@@ -14,6 +14,9 @@ import type {
|
|
|
14
14
|
CatalogIndexEntryV1,
|
|
15
15
|
} from "@frockbot/catalog-core";
|
|
16
16
|
import type {
|
|
17
|
+
AppletBuildViewV1,
|
|
18
|
+
AppletSourceViewV1,
|
|
19
|
+
AppletSummaryV1,
|
|
17
20
|
PackageIframeCatalogV1,
|
|
18
21
|
PackageIframeContributionViewV1,
|
|
19
22
|
SendToUserPayloadV1,
|
|
@@ -193,6 +196,39 @@ export interface FrockBotWebData {
|
|
|
193
196
|
tasks: TaskViewV1[];
|
|
194
197
|
/** Sandboxed pages in the selected Bot's active fail-closed Composition. */
|
|
195
198
|
packageUi?: PackageIframeCatalogV1;
|
|
199
|
+
/**
|
|
200
|
+
* The Applets this User owns. Account-wide, like every other Package-shaped
|
|
201
|
+
* capability: an Applet belongs to the User, and every Bot they own sees it.
|
|
202
|
+
*/
|
|
203
|
+
applets: AppletSummaryV1[];
|
|
204
|
+
/**
|
|
205
|
+
* The Session's focused Applet id, as the Bot Durable Object recorded it.
|
|
206
|
+
* `null` is a value — the Session deliberately has no Applet in the canvas —
|
|
207
|
+
* and `undefined` means it has not been read yet.
|
|
208
|
+
*/
|
|
209
|
+
focusedAppletId?: string | null;
|
|
210
|
+
/** The focused Applet joined with the list. Derived; never written to. */
|
|
211
|
+
readonly focusedApplet?: AppletSummaryV1;
|
|
212
|
+
/** Where the focused Applet's live UI is, and the credential to open it. */
|
|
213
|
+
appletViewer?: {
|
|
214
|
+
appletId: string;
|
|
215
|
+
token: string;
|
|
216
|
+
expiresAt: string;
|
|
217
|
+
socketUrl: string;
|
|
218
|
+
uiUrl: string;
|
|
219
|
+
generationId: string;
|
|
220
|
+
};
|
|
221
|
+
/** The focused Applet's source, for the canvas's building state. */
|
|
222
|
+
appletSource?: AppletSourceViewV1;
|
|
223
|
+
/** The outcome the Bot last recorded for `applet check` or `applet build`. */
|
|
224
|
+
appletBuild?: AppletBuildViewV1;
|
|
225
|
+
/**
|
|
226
|
+
* How the canvas's own read went. `loading` is the first read of a focused
|
|
227
|
+
* Applet, `failed` is a read the User can retry, and `ready` says the canvas
|
|
228
|
+
* has everything it needs to draw either of its states.
|
|
229
|
+
*/
|
|
230
|
+
appletCanvas: "idle" | "loading" | "ready" | "failed";
|
|
231
|
+
appletCanvasError?: string;
|
|
196
232
|
/**
|
|
197
233
|
* The User's MCP servers: state, tool count, last handshake, instructions,
|
|
198
234
|
* failure, and the durable refusal ledger. Absent until it is loaded, and
|
|
@@ -247,6 +283,22 @@ export interface FrockBotWebData {
|
|
|
247
283
|
/** Refreshes {@link FrockBotWebData.tasks} for the active Bot. */
|
|
248
284
|
loadTasks(): Promise<void>;
|
|
249
285
|
loadPackageUi(): Promise<void>;
|
|
286
|
+
/** Refreshes {@link FrockBotWebData.applets} for the signed-in User. */
|
|
287
|
+
loadApplets(): Promise<void>;
|
|
288
|
+
/** Refreshes the Session's focused Applet for the active Bot. */
|
|
289
|
+
loadFocusedApplet(): Promise<void>;
|
|
290
|
+
/**
|
|
291
|
+
* Focuses one Applet for this Session, or clears the focus. The backend is
|
|
292
|
+
* the authority: what the canvas then shows is the focus that was recorded,
|
|
293
|
+
* never the one the click asked for.
|
|
294
|
+
*/
|
|
295
|
+
setFocusedApplet(appletId: string | null): Promise<void>;
|
|
296
|
+
/**
|
|
297
|
+
* Re-reads what the canvas draws for the focused Applet: its source, the
|
|
298
|
+
* last recorded check or build outcome, and a viewer credential once a
|
|
299
|
+
* generation is active.
|
|
300
|
+
*/
|
|
301
|
+
refreshAppletCanvas(): Promise<void>;
|
|
250
302
|
callPackageUiTool(
|
|
251
303
|
contribution: PackageIframeContributionViewV1,
|
|
252
304
|
name: string,
|