@bivy/bivy 0.20.9-staging.8 → 0.21.0-staging.1
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 +1 -2
- package/dist/agent-instructions.js +14 -4
- package/dist/apps/remote-preview.js +7 -0
- package/dist/apps/service.js +6 -1
- package/dist/apps/styles.css +56 -70
- package/dist/automation-reseal.js +30 -0
- package/dist/config-cli.js +2 -0
- package/dist/controllers/credential-commands.js +3 -27
- package/dist/credentials/api.js +2 -43
- package/dist/credentials/document.js +6 -14
- package/dist/credentials/store.js +5 -6
- package/dist/device-registry.js +27 -0
- package/dist/harness/checkpoint.js +0 -0
- package/dist/node-config.js +4 -3
- package/dist/runtime/model-catalog.js +16 -0
- package/dist/runtime/tool-call-map.js +1 -1
- package/dist/runtime/tool-trace.js +1 -1
- package/dist/server.js +136 -244
- package/dist/session/event-log.js +136 -29
- package/dist/session/inline-image-fetch.js +24 -33
- package/dist/session/message-components.js +429 -0
- package/dist/session/run-terminal.js +9 -7
- package/dist/session/transcript-persistence.js +135 -10
- package/dist/work-instructions.js +50 -0
- package/package.json +4 -4
- package/web/assets/AutomationsView-DA9_ilGJ.js +51 -0
- package/web/assets/ChangesCard-D1_285PU.js +4 -0
- package/web/assets/LibraryView-Cnvd9lkW.js +2 -0
- package/web/assets/{ProviderConnect-DId9XOvG.js → ProviderConnect-B_ZjrMZY.js} +1 -1
- package/web/assets/SessionList-DUdqO_nL.js +1 -0
- package/web/assets/Settings--6U-Td7F.js +10 -0
- package/web/assets/{Sheet-Q2ePGQye.js → Sheet-DCRQrcPM.js} +1 -1
- package/web/assets/{StatusDot-dpbztK53.js → Spinner-CPdRfDcT.js} +1 -1
- package/web/assets/{Terminal-DSZ8YBgi.js → Terminal-DpsRLIV7.js} +1 -1
- package/web/assets/{VoiceSettings-CGxPed9_.js → VoiceSettings-B4ftQ8Xr.js} +1 -1
- package/web/assets/{abnfDiagram-VCTEODGH-P-N-ZNBN.js → abnfDiagram-VCTEODGH-Cdw5yDXk.js} +1 -1
- package/web/assets/arc-BNLueWOY.js +1 -0
- package/web/assets/architecture-7GRP2DOG-CfYziiZA.js +1 -0
- package/web/assets/{architectureDiagram-5GKGNRK7-byCBwDFy.js → architectureDiagram-5GKGNRK7-CX_Knw6D.js} +1 -1
- package/web/assets/{blockDiagram-I7D4REHJ-8Ix3AyxA.js → blockDiagram-I7D4REHJ-CYlwdU3X.js} +1 -1
- package/web/assets/{c4Diagram-7LVT6UL2-DsaczzUF.js → c4Diagram-7LVT6UL2-PA3wEbxG.js} +1 -1
- package/web/assets/channel-DjmBScdY.js +1 -0
- package/web/assets/{chunk-4HAMMTFA-CNU1npt1.js → chunk-4HAMMTFA-DkNFPUyu.js} +1 -1
- package/web/assets/chunk-75Z2AOVW-Cb23_YVq.js +2 -0
- package/web/assets/{chunk-DU6HZSFF-kfSsBAl-.js → chunk-DU6HZSFF-CqaTLnF7.js} +15 -15
- package/web/assets/chunk-F27PBJKO-D0O8iotE.js +1 -0
- package/web/assets/{chunk-GMAD6QVW-DN9x2U-t.js → chunk-GMAD6QVW-Db7P-bVL.js} +1 -1
- package/web/assets/{chunk-GVQU2GXP-kNfCpzk-.js → chunk-GVQU2GXP-C6nQDHF7.js} +1 -1
- package/web/assets/{chunk-IMKFNOWR-RQCVneQm.js → chunk-IMKFNOWR-Byj0E7lr.js} +1 -1
- package/web/assets/{chunk-L3NEJ4N5-DCUakvZ4.js → chunk-L3NEJ4N5-BpH7QJFo.js} +1 -1
- package/web/assets/{chunk-OSK3NFVY-DwoeBdfs.js → chunk-OSK3NFVY-CzeAEhZO.js} +2 -2
- package/web/assets/{chunk-P2QGCYS3-Dr0BtBMh.js → chunk-P2QGCYS3-uL1IB5fR.js} +1 -1
- package/web/assets/{chunk-POPQ4Y6H-9OxfP46b.js → chunk-POPQ4Y6H-CXlNmGZG.js} +1 -1
- package/web/assets/{chunk-PWAF6VOD-CvD7tBZF.js → chunk-PWAF6VOD-BkHQH-na.js} +1 -1
- package/web/assets/{chunk-SHT3W25Y-jYLIsjaC.js → chunk-SHT3W25Y-1ZIHyWA5.js} +1 -1
- package/web/assets/{chunk-SVP7TREG-D2yvpT1y.js → chunk-SVP7TREG-DOkiRFbX.js} +1 -1
- package/web/assets/{chunk-TICWLB2K-V1PKTqXh.js → chunk-TICWLB2K-B3zv0oLo.js} +1 -1
- package/web/assets/chunk-XXDRQBXY-CjF14Z7v.js +1 -0
- package/web/assets/classDiagram-ZZMXUADV-0QtonV64.js +1 -0
- package/web/assets/classDiagram-v2-VYDZK3BY-0QtonV64.js +1 -0
- package/web/assets/colors-Cg5Vh3br.js +1 -0
- package/web/assets/{cose-bilkent-JH36ORCC-BIqBF2fY.js → cose-bilkent-JH36ORCC-CEUD3V3W.js} +1 -1
- package/web/assets/{cynefin-OW5HDTMX-S3-HDgGF.js → cynefin-OW5HDTMX-mKyDjyM_.js} +1 -1
- package/web/assets/cynefinDiagram-5FMLGOSQ-Cwe3TvQ5.js +62 -0
- package/web/assets/{dagre-GXQ25YYZ-DYFiDtUI.js → dagre-GXQ25YYZ-BK_vzPXJ.js} +1 -1
- package/web/assets/{defaultLocale-BFoDCU3G.js → defaultLocale-CNPGWpMu.js} +1 -1
- package/web/assets/{diagram-S7CK7UJ4-B98M2jJY.js → diagram-S7CK7UJ4-CVqyMnLL.js} +1 -1
- package/web/assets/diagram-UQ7AKVKN-XzAb6plC.js +41 -0
- package/web/assets/{diagram-VSXAHHWV-ds4j8snX.js → diagram-VSXAHHWV-Ba5Y2kBC.js} +1 -1
- package/web/assets/diagram-VX7I27RA-DNPLCmCh.js +24 -0
- package/web/assets/diagram-Z3DM3KII-DrAi87vL.js +24 -0
- package/web/assets/dist-kYfyPZP9.js +1 -0
- package/web/assets/{ebnfDiagram-PWID7BFC-BnpYqLy9.js → ebnfDiagram-PWID7BFC-DYYlJEgD.js} +1 -1
- package/web/assets/embed-BXV41wg7.js +201 -0
- package/web/assets/{erDiagram-RLTQ6QDP-BB7HZnkw.js → erDiagram-RLTQ6QDP-B_JY_DQk.js} +1 -1
- package/web/assets/eventmodeling-NTZA5JFV-BtSu2if9.js +1 -0
- package/web/assets/flowDiagram-HODETNUW-CGzQbPxy.js +1 -0
- package/web/assets/ganttDiagram-EL5Y4UJY-tyoaaBb0.js +292 -0
- package/web/assets/{gitGraph-4MIJSDKK-C4LumuQC.js → gitGraph-4MIJSDKK-C2bY1CGX.js} +1 -1
- package/web/assets/gitGraphDiagram-WWUBYQGX-DTlHPC5D.js +106 -0
- package/web/assets/{index-DqS1nQdi.js → index-BCWScGzL.js} +2 -2
- package/web/assets/index-BKSbod42.css +1 -0
- package/web/assets/{info-A6RAGUB7-D5ABcZgz.js → info-A6RAGUB7-sKkbqM_E.js} +1 -1
- package/web/assets/infoDiagram-27XIBGKW-Bvw2AcJr.js +2 -0
- package/web/assets/init-ZlXS9mIS.js +1 -0
- package/web/assets/{ishikawaDiagram-5VMMS53U-DrsbsHXm.js → ishikawaDiagram-5VMMS53U-Kza7MX-1.js} +1 -1
- package/web/assets/{journeyDiagram-3NMN7TZE-B7VhME2y.js → journeyDiagram-3NMN7TZE-DtMmNsof.js} +1 -1
- package/web/assets/{kanban-definition-UXKFOSKX-DH-NTkzb.js → kanban-definition-UXKFOSKX-DG-Uv-OI.js} +1 -1
- package/web/assets/linear-CeYXFQza.js +1 -0
- package/web/assets/{mermaid-parser.core-Dltug786.js → mermaid-parser.core-CVrhbt86.js} +3 -3
- package/web/assets/{mermaid.core--75UrgAP.js → mermaid.core-U3bnccth.js} +6 -6
- package/web/assets/{mindmap-definition-YA3MSWOX-D7pwHRXt.js → mindmap-definition-YA3MSWOX-B1liGoy5.js} +1 -1
- package/web/assets/mount-BSLnTLn1.js +36 -0
- package/web/assets/ordinal-BdBDtYTt.js +1 -0
- package/web/assets/{packet-AYTQ26CC-DpU7rkFQ.js → packet-AYTQ26CC-BmJrNR8t.js} +1 -1
- package/web/assets/{pegDiagram-XKGWAZYB-CyUHaaZw.js → pegDiagram-XKGWAZYB-dbZzxRiv.js} +1 -1
- package/web/assets/{pie-WAS4IAKB-k4r1AOoI.js → pie-WAS4IAKB-H_zYRQZS.js} +1 -1
- package/web/assets/{pieDiagram-E7YTZNPT-9vy-IUVb.js → pieDiagram-E7YTZNPT-D0Q3vzEh.js} +2 -2
- package/web/assets/{quadrantDiagram-AXDQQJYC-C-u0AR03.js → quadrantDiagram-AXDQQJYC-qNA-jyeM.js} +1 -1
- package/web/assets/{radar-RG4KPBEZ-DBX_1r-c.js → radar-RG4KPBEZ-DWs2LjJN.js} +1 -1
- package/web/assets/{railroad-74A4TZTK-C8ed06QJ.js → railroad-74A4TZTK-CHesrAHB.js} +1 -1
- package/web/assets/railroad-abnf-HS5TGJTU-9p3_CI3I.js +1 -0
- package/web/assets/railroad-ebnf-LZEXJU2U-CPUSpbug.js +1 -0
- package/web/assets/railroad-peg-WCYAUIDC-Dfhg5mGh.js +1 -0
- package/web/assets/{railroadDiagram-O6MQD6OU-Df9l6PLc.js → railroadDiagram-O6MQD6OU-C3-f4ErM.js} +1 -1
- package/web/assets/range-EplFIxc7.js +1 -0
- package/web/assets/{requirementDiagram-BXWQKSXE-DMbX29EP.js → requirementDiagram-BXWQKSXE-Duz3-frS.js} +1 -1
- package/web/assets/{sankeyDiagram-P5KCCOFB-DMdKyNDk.js → sankeyDiagram-P5KCCOFB-Dy2AjqHt.js} +1 -1
- package/web/assets/{sequenceDiagram-WJ2MYXX4-BZUKpd2D.js → sequenceDiagram-WJ2MYXX4-Bk4_fn7m.js} +3 -3
- package/web/assets/{settingsRoute-CxmTmim2.js → settingsRoute-CA4CTBbQ.js} +1 -1
- package/web/assets/src-CFHfg5gg.js +1 -0
- package/web/assets/stateDiagram-D77RDMKH-BDlN7MCW.js +1 -0
- package/web/assets/stateDiagram-v2-MP3YSRHH-BzK0RFf3.js +1 -0
- package/web/assets/step-BDqHAzds.js +1 -0
- package/web/assets/{swimlanes-42K2YHIH-DHDsLQTH.js → swimlanes-42K2YHIH-BKDYcVvH.js} +1 -1
- package/web/assets/swimlanesDiagram-VR7AAH4N-BCYnGbyq.js +8 -0
- package/web/assets/time-Dzw-YSCF.js +1 -0
- package/web/assets/{timeline-definition-24CTP7MA-MGKQWkpI.js → timeline-definition-24CTP7MA-65mGVsBy.js} +1 -1
- package/web/assets/{treeView-Q6P3EWNA-C_8iTJWi.js → treeView-Q6P3EWNA-WzobWotE.js} +1 -1
- package/web/assets/treemap-BfjIY-m6.js +1 -0
- package/web/assets/{treemap-WGGIJYW6-DX8MPwKV.js → treemap-WGGIJYW6-DywYNy-e.js} +1 -1
- package/web/assets/useStore-Cjpbp6X5.js +16 -0
- package/web/assets/{vennDiagram-4TSXK5OY-WSZd61f7.js → vennDiagram-4TSXK5OY-D7jOIO0P.js} +1 -1
- package/web/assets/{wardley-WFR3VGLG-Bi4EAVkj.js → wardley-WFR3VGLG-z6JfJlwZ.js} +1 -1
- package/web/assets/{wardleyDiagram-VM6X3IG4-L6mpluSU.js → wardleyDiagram-VM6X3IG4-DuwAEGCR.js} +1 -1
- package/web/assets/{xychartDiagram-S5SC5T6Z-CwBesblt.js → xychartDiagram-S5SC5T6Z-D2i-Q9Q1.js} +3 -3
- package/web/index.html +2 -2
- package/web/sw.js +1 -1
- package/dist/ephemeral-exec.js +0 -108
- package/dist/hosted-custody.js +0 -30
- package/web/assets/AutomationsView-DoDebW9z.js +0 -51
- package/web/assets/ChangesCard-Cm7TsFxw.js +0 -4
- package/web/assets/LibraryView-D_Za_-QQ.js +0 -2
- package/web/assets/Settings-DjfDtitx.js +0 -10
- package/web/assets/arc-Pry-7G1_.js +0 -1
- package/web/assets/architecture-7GRP2DOG-Bx9givzi.js +0 -1
- package/web/assets/array-BifhSqXX.js +0 -1
- package/web/assets/channel-CoFJqi63.js +0 -1
- package/web/assets/chunk-75Z2AOVW-2R8IkZ6K.js +0 -2
- package/web/assets/chunk-F27PBJKO-CnHKeJnk.js +0 -1
- package/web/assets/chunk-XXDRQBXY-DqAYeljN.js +0 -1
- package/web/assets/classDiagram-ZZMXUADV-CoCI6-mr.js +0 -1
- package/web/assets/classDiagram-v2-VYDZK3BY-CoCI6-mr.js +0 -1
- package/web/assets/cynefinDiagram-5FMLGOSQ-AMnKjokI.js +0 -62
- package/web/assets/diagram-UQ7AKVKN-B3rlK3KL.js +0 -41
- package/web/assets/diagram-VX7I27RA-B2SkgQlQ.js +0 -24
- package/web/assets/diagram-Z3DM3KII-6t1yNosN.js +0 -24
- package/web/assets/dist-v5Q1xZ2K.js +0 -1
- package/web/assets/eventmodeling-NTZA5JFV-Cz2DLFjE.js +0 -1
- package/web/assets/flowDiagram-HODETNUW-Dx9iPIiD.js +0 -1
- package/web/assets/ganttDiagram-EL5Y4UJY-CRGarlJz.js +0 -292
- package/web/assets/gitGraphDiagram-WWUBYQGX-BEFQqyEF.js +0 -106
- package/web/assets/index-kWXUrhG8.css +0 -1
- package/web/assets/infoDiagram-27XIBGKW-DM1N7dMj.js +0 -2
- package/web/assets/init-C-OQMol4.js +0 -1
- package/web/assets/line-CpnjiQNF.js +0 -1
- package/web/assets/linear-SzVmGSw4.js +0 -1
- package/web/assets/mount-B0yT9nH2.js +0 -31
- package/web/assets/ordinal-BDEzSJ7C.js +0 -1
- package/web/assets/path-fybaL0A-.js +0 -1
- package/web/assets/railroad-abnf-HS5TGJTU-CH3K-Q4R.js +0 -1
- package/web/assets/railroad-ebnf-LZEXJU2U-By7WQSrj.js +0 -1
- package/web/assets/railroad-peg-WCYAUIDC-J5fbvVLI.js +0 -1
- package/web/assets/src-B6xuSHsQ.js +0 -1
- package/web/assets/stateDiagram-D77RDMKH-A5Z3JkUu.js +0 -1
- package/web/assets/stateDiagram-v2-MP3YSRHH-CbBdq37k.js +0 -1
- package/web/assets/swimlanesDiagram-VR7AAH4N-uxsVNpHd.js +0 -8
- package/web/assets/useStore-DnnAeOPe.js +0 -29
package/README.md
CHANGED
|
@@ -228,8 +228,7 @@ bivy secrets ref github.repo-token op://Bivy/GitHub/repo-token
|
|
|
228
228
|
|
|
229
229
|
**Not every CLI login syncs.** Native agent logins may still be per-machine;
|
|
230
230
|
GitHub App private-key sync is separately opt-in. If you lose every node and
|
|
231
|
-
device able to unwrap a vault, you must sign in to providers again.
|
|
232
|
-
hosted-provisioning custody grants are separate from ordinary encrypted sync.
|
|
231
|
+
device able to unwrap a vault, you must sign in to providers again.
|
|
233
232
|
|
|
234
233
|
[Credential sync and runtime coverage →](docs/credential-sync.md) ·
|
|
235
234
|
[Credentials guide →](docs/credentials-guide.md) ·
|
|
@@ -30,10 +30,20 @@ export const MAX_AGENT_INSTRUCTIONS_BYTES = 16 * 1024;
|
|
|
30
30
|
export const BIVY_AGENT_NOTE = [
|
|
31
31
|
"You are running inside Bivy. The user follows this session in a chat app (web or phone): they cannot see your " +
|
|
32
32
|
"terminal or files you only write to disk. $BIVY_SESSION_ID identifies this session; the `bivy` commands below use it.",
|
|
33
|
-
"- Show a
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
33
|
+
"- Show an IMAGE (a screenshot, or a picture you rendered to a file) inside your reply: write it as markdown, " +
|
|
34
|
+
"``, where you want it to appear. The path must be inside the session " +
|
|
35
|
+
"workspace; a remote `https://` image works too. No tool call needed.",
|
|
36
|
+
"- Show a FILE (a report, a log, data they asked for) inside your reply: put " +
|
|
37
|
+
'`::view{src=relative/path.pdf caption="what it is"}` on its own line, where you want it. Same workspace-only ' +
|
|
38
|
+
"rule. A `.csv`/`.tsv` renders as a table; anything else arrives as a download.",
|
|
39
|
+
"- Show a NUMBER or a CHART inside your reply: fence a spec as ```bivy. A number is " +
|
|
40
|
+
'`{"type":"metric","value":"4 650 kWh","label":"per year"}`; a chart is a plain Vega-Lite spec with its data ' +
|
|
41
|
+
"inline, or pointing at a workspace data file with `\"data\":{\"url\":\"data/use.csv\"}` (.csv/.tsv/.json). " +
|
|
42
|
+
"A chart cannot fetch a remote URL.",
|
|
43
|
+
"- Send a file WITHOUT writing a reply (or from a script): run " +
|
|
44
|
+
'`bivy attach <path> [--caption "short note"]`, or call the `attach_to_chat` tool if you have it.',
|
|
45
|
+
"- Put a published app's preview INSIDE your reply: `::view{app=<app id>}` on its own line. It frames the app " +
|
|
46
|
+
"where you wrote it, and still opens full screen on demand.",
|
|
37
47
|
"- Something with a UI: `bivy app publish <manifest.json>` (a web server's port, a static build, a terminal) or " +
|
|
38
48
|
"`bivy app run -- <command>` (a desktop app) gives the user a live preview; `bivy app shot` screenshots it so you " +
|
|
39
49
|
"can check your work; `bivy app present` tells the user a visible change is ready to look at, and " +
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { WebSocket } from "ws";
|
|
4
4
|
import { AppGateway, previewOriginTemplate } from "./gateway.js";
|
|
5
5
|
import { PreviewStream } from "./preview-stream.js";
|
|
6
|
+
const RECENT_USE_MS = 2 * 60_000;
|
|
6
7
|
/** Automatically discovered preview delivery. The app gateway receives ONLY
|
|
7
8
|
* HTTP streams on a private in-process socket, never a publicly bound port. */
|
|
8
9
|
export class RemotePreview {
|
|
@@ -16,6 +17,7 @@ export class RemotePreview {
|
|
|
16
17
|
relayUrl;
|
|
17
18
|
online = false;
|
|
18
19
|
sockets = new Set();
|
|
20
|
+
lastConnectAt = -Infinity;
|
|
19
21
|
constructor(registry, returnOrigins, signIn, captureNote, badge) {
|
|
20
22
|
this.registry = registry;
|
|
21
23
|
this.returnOrigins = returnOrigins;
|
|
@@ -24,6 +26,10 @@ export class RemotePreview {
|
|
|
24
26
|
this.badge = badge;
|
|
25
27
|
}
|
|
26
28
|
get available() { return this.online && Boolean(this.gateway); }
|
|
29
|
+
/** Someone loaded part of a preview recently. Each request burst opens a
|
|
30
|
+
* stream; an open but silent live-reload socket from a forgotten tab doesn't
|
|
31
|
+
* count, so it can't keep a sleeping machine awake. */
|
|
32
|
+
inUse(now = Date.now()) { return now - this.lastConnectAt < RECENT_USE_MS; }
|
|
27
33
|
ready(origin, relayUrl) {
|
|
28
34
|
this.disconnect();
|
|
29
35
|
if (!origin)
|
|
@@ -44,6 +50,7 @@ export class RemotePreview {
|
|
|
44
50
|
if (!this.available || !this.relayUrl || !/^[a-f0-9]{64}$/.test(ticket) || this.sockets.size >= 64)
|
|
45
51
|
return;
|
|
46
52
|
const gateway = this.gateway;
|
|
53
|
+
this.lastConnectAt = Date.now();
|
|
47
54
|
const ws = new WebSocket(this.relayUrl, { headers: { authorization: `Bearer ${ticket}` }, maxPayload: 64 * 1024, handshakeTimeout: 10_000, perMessageDeflate: false, followRedirects: false });
|
|
48
55
|
this.sockets.add(ws);
|
|
49
56
|
ws.on("error", () => ws.terminate());
|
package/dist/apps/service.js
CHANGED
|
@@ -814,8 +814,13 @@ export class AppService {
|
|
|
814
814
|
return;
|
|
815
815
|
const now = Date.now();
|
|
816
816
|
server.restarts = server.restarts.filter((at) => now - at < RESTART_WINDOW);
|
|
817
|
-
|
|
817
|
+
// A crash loop stays down until the view is opened again, rather than
|
|
818
|
+
// retrying every time the window rolls over.
|
|
819
|
+
if (server.restarts.length >= MAX_RESTARTS) {
|
|
820
|
+
clearInterval(server.timer);
|
|
821
|
+
server.timer = undefined;
|
|
818
822
|
return;
|
|
823
|
+
}
|
|
819
824
|
server.restarts.push(now);
|
|
820
825
|
server.termId = undefined;
|
|
821
826
|
void this.ensureServer(current).catch(() => { });
|
package/dist/apps/styles.css
CHANGED
|
@@ -639,6 +639,29 @@ input, textarea, select { font-family: inherit; font-size: 16px; }
|
|
|
639
639
|
.msg.assistant .mermaid-diagram:first-child { margin-top: 0; }
|
|
640
640
|
.msg.assistant .mermaid-diagram:last-child { margin-bottom: 0; }
|
|
641
641
|
.msg.assistant .mermaid-diagram svg { display: inline-block; max-width: none; height: auto; vertical-align: top; }
|
|
642
|
+
/* Mount point for a component the agent placed in its message (::view{…}, a
|
|
643
|
+
```bivy fence). Layout only: what renders inside is an existing component
|
|
644
|
+
(.msg-attachment, .card), so this must not restyle its shell — it only gives
|
|
645
|
+
it the same vertical rhythm as a paragraph. Empty until the portal mounts,
|
|
646
|
+
and empty forever if a reference never resolves, so it carries no border or
|
|
647
|
+
background of its own that would leave a visible hole. */
|
|
648
|
+
.msg.assistant .md-component { margin: 0.75em 0; }
|
|
649
|
+
.msg.assistant .md-component:first-child { margin-top: 0; }
|
|
650
|
+
.msg.assistant .md-component:last-child { margin-bottom: 0; }
|
|
651
|
+
.msg.assistant .md-component:empty { margin: 0; }
|
|
652
|
+
/* A placed component is a block in the reading column, not a tile in a row: it
|
|
653
|
+
fills the message width so every kind lines up with every other one and with
|
|
654
|
+
the prose around it. The fixed chip width belongs to the composer's wrapping
|
|
655
|
+
attachment list, where several chips share a line. */
|
|
656
|
+
.msg.assistant .md-component > * { width: 100%; }
|
|
657
|
+
/* A single number the agent wants read at a glance. Inherits .card's shell;
|
|
658
|
+
only the value's type scale is its own. */
|
|
659
|
+
.metric-component { text-align: center; }
|
|
660
|
+
.metric-value { font-size: var(--text-3xl); line-height: var(--lh-3xl); font-weight: var(--weight-semibold); }
|
|
661
|
+
/* A table from a CSV or a spec reuses .markdown-table-wrap, so it reads exactly
|
|
662
|
+
like a table the agent wrote as markdown. The caption is the only addition. */
|
|
663
|
+
.table-component { margin: 0; }
|
|
664
|
+
.table-component figcaption { margin-top: var(--space-2); }
|
|
642
665
|
/* Syntax highlighting for fenced code blocks (highlight.js token classes).
|
|
643
666
|
Colors use light-dark() so one rule set tracks the app's light/dark scheme
|
|
644
667
|
(color-scheme is set on :root). A GitHub-flavored palette that sits well on
|
|
@@ -1469,10 +1492,8 @@ button.sheet-action:last-child { border-bottom: 0; }
|
|
|
1469
1492
|
}
|
|
1470
1493
|
/* Password-manager-style provider credential vault. Saved items are the primary
|
|
1471
1494
|
list; the provider catalog appears only inside Add. */
|
|
1472
|
-
/*
|
|
1473
|
-
|
|
1474
|
-
.credential-vault, .machine-profiles { gap: 14px; max-width: 680px; width: 100%; }
|
|
1475
|
-
.credential-vault { overflow-wrap: anywhere; }
|
|
1495
|
+
/* The credential vault: a wide settings panel that reads as a shallow "vault". */
|
|
1496
|
+
.credential-vault { gap: 14px; max-width: 680px; width: 100%; overflow-wrap: anywhere; }
|
|
1476
1497
|
.vault-title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; }
|
|
1477
1498
|
.vault-title-row > div { min-width: 0; }
|
|
1478
1499
|
/* The header action (e.g. "+ Add profile") never shrinks or wraps — the title
|
|
@@ -1496,7 +1517,6 @@ button.sheet-action:last-child { border-bottom: 0; }
|
|
|
1496
1517
|
.vault-detail-grid { display: grid; grid-template-columns: minmax(110px, auto) 1fr; gap: 10px 18px; padding: 14px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface-2); }
|
|
1497
1518
|
.vault-detail-grid strong { min-width: 0; overflow-wrap: anywhere; }
|
|
1498
1519
|
.vault-detail-grid code { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1499
|
-
.vault-custody-note { padding: 12px 14px; border-left: 3px solid var(--accent); border-radius: var(--radius-sm) var(--radius-md) var(--radius-md) var(--radius-sm); background: var(--surface-2); line-height: 1.5; }
|
|
1500
1520
|
.vault-advanced { padding: 10px 0; border-top: 1px solid var(--line); }
|
|
1501
1521
|
.vault-advanced summary { cursor: pointer; color: var(--ink); font-weight: 600; }
|
|
1502
1522
|
.vault-advanced[open] summary { margin-bottom: 10px; }
|
|
@@ -1857,21 +1877,8 @@ button.template-card.is-featured .template-card-icon {
|
|
|
1857
1877
|
}
|
|
1858
1878
|
.automation-row .row-menu-btn { opacity: 0.72; }
|
|
1859
1879
|
.automation-row:hover .row-menu-btn { opacity: 1; }
|
|
1860
|
-
.run-row { align-items: center; }
|
|
1861
|
-
.run-row-open {
|
|
1862
|
-
appearance: none; border: 0; background: transparent; color: inherit; font: inherit;
|
|
1863
|
-
text-align: left; padding: 0; cursor: pointer;
|
|
1864
|
-
}
|
|
1865
|
-
.run-row-open:disabled { cursor: default; }
|
|
1866
|
-
.run-row-open:not(:disabled):hover .automation-row-title strong { color: var(--accent); }
|
|
1867
|
-
.run-row-chevron {
|
|
1868
|
-
display: grid; place-items: center; flex: none; width: 40px; min-height: 40px;
|
|
1869
|
-
border: 0; border-radius: var(--radius-md); padding: 0; background: transparent;
|
|
1870
|
-
color: var(--muted); font: inherit; font-size: 24px; line-height: 1; cursor: pointer;
|
|
1871
|
-
}
|
|
1872
|
-
.run-row-chevron:hover { color: var(--ink); background: var(--surface-2); }
|
|
1873
1880
|
.runs-setup {
|
|
1874
|
-
margin-top:
|
|
1881
|
+
margin-top: var(--space-6); border: 1px solid var(--line); border-radius: var(--radius-md);
|
|
1875
1882
|
background: var(--surface); overflow: hidden;
|
|
1876
1883
|
}
|
|
1877
1884
|
.runs-setup > summary {
|
|
@@ -1886,16 +1893,13 @@ button.template-card.is-featured .template-card-icon {
|
|
|
1886
1893
|
.runs-setup[open] .runs-setup-chevron { transform: rotate(90deg); }
|
|
1887
1894
|
.runs-setup-body { display: flex; flex-direction: column; gap: 12px; padding: 14px; border-top: 1px solid var(--line); }
|
|
1888
1895
|
.runs-setup-body .picker-search { max-width: 100%; box-sizing: border-box; }
|
|
1889
|
-
|
|
1890
|
-
.
|
|
1891
|
-
.
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
.
|
|
1895
|
-
.
|
|
1896
|
-
.incoming-work { margin-top: 22px; }
|
|
1897
|
-
.incoming-work > .autom-section-label { margin-bottom: 8px; }
|
|
1898
|
-
.incoming-work > .settings-form { gap: 14px; }
|
|
1896
|
+
/* Runs feed: grouped like the session list (attention, in progress, by day). */
|
|
1897
|
+
.run-history { margin-top: var(--space-2); }
|
|
1898
|
+
.run-history .session-group-label { margin: 0; padding-inline: 0; }
|
|
1899
|
+
.run-history > .settings-form { gap: var(--space-2); padding: 0 0 var(--space-3); border-bottom: 1px solid var(--line); }
|
|
1900
|
+
.run-history > .settings-form:empty { display: none; }
|
|
1901
|
+
.run-history-failure { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1902
|
+
.automation-history-row:disabled { cursor: default; }
|
|
1899
1903
|
|
|
1900
1904
|
/* A runner is a first-class requirement, not a tiny select hidden under the
|
|
1901
1905
|
prompt. Pairing/key readiness is visible before Save can become disabled. */
|
|
@@ -1911,6 +1915,7 @@ button.template-card.is-featured .template-card-icon {
|
|
|
1911
1915
|
.autom-runner-select-copy span { color: var(--muted); font-size: 12px; line-height: 1.35; }
|
|
1912
1916
|
.autom-runner-select-row .autom-inline-select { flex: none; width: min(42%, 190px); }
|
|
1913
1917
|
.autom-runner-help {
|
|
1918
|
+
display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2);
|
|
1914
1919
|
padding: 10px 12px; border-top: 1px solid var(--line);
|
|
1915
1920
|
color: var(--ink); background: color-mix(in srgb, var(--warn) 9%, var(--surface));
|
|
1916
1921
|
font-size: 12px; line-height: 1.45;
|
|
@@ -1947,14 +1952,6 @@ button.template-card.is-featured .template-card-icon {
|
|
|
1947
1952
|
.run-timeline li span { color: var(--muted); font-size: 12px; }
|
|
1948
1953
|
.run-timeline p { grid-column: 1 / -1; margin: 0; color: var(--muted); font-size: 12px; line-height: 1.4; }
|
|
1949
1954
|
.run-timeline code { grid-column: 1 / -1; overflow-wrap: anywhere; font-size: 11px; }
|
|
1950
|
-
.run-history-filters {
|
|
1951
|
-
display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0;
|
|
1952
|
-
}
|
|
1953
|
-
.run-history-filters .btn { white-space: nowrap; }
|
|
1954
|
-
@media (max-width: 479px) {
|
|
1955
|
-
.run-history-filters { flex-wrap: nowrap; overflow-x: auto; margin-inline: -4px; padding-inline: 4px; scrollbar-width: none; }
|
|
1956
|
-
.run-history-filters::-webkit-scrollbar { display: none; }
|
|
1957
|
-
}
|
|
1958
1955
|
.run-details .run-sheet-failure { margin-top: 14px; border: 1px solid color-mix(in srgb, var(--danger) 35%, var(--line)); border-radius: var(--radius-md); }
|
|
1959
1956
|
.run-details-receipt { margin-top: 18px !important; }
|
|
1960
1957
|
.run-details-state {
|
|
@@ -2139,7 +2136,7 @@ button.template-card.is-featured .template-card-icon {
|
|
|
2139
2136
|
.automation-detail-head .icon { flex-shrink: 0; }
|
|
2140
2137
|
.automation-detail-head svg { width: var(--space-5); height: var(--space-5); }
|
|
2141
2138
|
.automations-view.is-history .automations-view-body { flex: 1; padding: var(--space-5) var(--space-4); }
|
|
2142
|
-
.automation-history-view
|
|
2139
|
+
.automation-history-view { margin-top: 0; }
|
|
2143
2140
|
.automation-history-row {
|
|
2144
2141
|
display: flex; align-items: center; gap: var(--space-3); width: 100%;
|
|
2145
2142
|
padding: var(--space-4) 0; border: 0; border-bottom: 1px solid var(--line);
|
|
@@ -2499,16 +2496,6 @@ a.queue-item:hover { border-color: var(--line-strong); background: var(--surface
|
|
|
2499
2496
|
.automation-row-actions { width: auto; justify-content: flex-end; border-top: 0; padding-top: 0; }
|
|
2500
2497
|
.automation-row-actions .btn { min-height: 40px; }
|
|
2501
2498
|
.automation-row .row-menu-btn { min-width: 40px; min-height: 40px; opacity: 1; }
|
|
2502
|
-
.run-row { align-items: flex-start; flex-wrap: wrap; }
|
|
2503
|
-
.run-row .automation-row-main { flex-basis: 100%; }
|
|
2504
|
-
.run-row .automation-row-actions { width: 100%; padding-top: 6px; }
|
|
2505
|
-
.run-row-chevron { margin-left: auto; }
|
|
2506
|
-
.runs-overview .autom-section-head { align-items: stretch; flex-direction: column; }
|
|
2507
|
-
.runs-overview .autom-section-actions { display: grid; grid-template-columns: 1fr 1fr; width: 100%; }
|
|
2508
|
-
/* Keep two actions balanced when needed, but don't make a lone Refresh button
|
|
2509
|
-
occupy half the screen. */
|
|
2510
|
-
.runs-overview .autom-section-actions:has(> :only-child) { display: flex; justify-content: flex-start; }
|
|
2511
|
-
.runs-overview .autom-section-actions .btn { min-width: 96px; width: auto; }
|
|
2512
2499
|
.queue-head { align-items: flex-start; gap: 10px; }
|
|
2513
2500
|
.queue-head-actions { justify-content: flex-end; gap: 8px; min-width: 0; text-align: right; }
|
|
2514
2501
|
.queue-head-actions .btn.link { overflow-wrap: anywhere; }
|
|
@@ -2609,15 +2596,6 @@ a.queue-item:hover { border-color: var(--line-strong); background: var(--surface
|
|
|
2609
2596
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
2610
2597
|
}
|
|
2611
2598
|
.connect-option:hover { border-color: var(--line-strong); box-shadow: var(--shadow-md); }
|
|
2612
|
-
.connect-option-head { display: flex; align-items: flex-start; gap: 12px; }
|
|
2613
|
-
.connect-option-badge {
|
|
2614
|
-
flex: none; width: 36px; height: 36px; display: grid; place-items: center;
|
|
2615
|
-
border-radius: var(--radius-md); color: var(--accent);
|
|
2616
|
-
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
|
2617
|
-
}
|
|
2618
|
-
.connect-option-copy { min-width: 0; }
|
|
2619
|
-
.connect-option-copy h3 { margin: 1px 0 3px; font-size: 15px; }
|
|
2620
|
-
.connect-option-copy p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.45; }
|
|
2621
2599
|
|
|
2622
2600
|
.connect-command {
|
|
2623
2601
|
display: flex; align-items: center; gap: 8px; padding: 4px 4px 4px 12px;
|
|
@@ -2637,7 +2615,6 @@ a.queue-item:hover { border-color: var(--line-strong); background: var(--surface
|
|
|
2637
2615
|
.connect-option-links { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
|
|
2638
2616
|
.connect-option-link { align-self: flex-start; color: var(--accent); font-size: 12.5px; font-weight: 600; text-decoration: none; }
|
|
2639
2617
|
.connect-option-link:hover { text-decoration: underline; }
|
|
2640
|
-
.connect-option-cta { align-self: flex-start; }
|
|
2641
2618
|
/* A one-time "next step" card above the composer (automations, notifications). */
|
|
2642
2619
|
.next-step { box-sizing: border-box; width: min(100% - 2 * var(--space-3), 800px - 2 * var(--space-3)); margin: 0 auto var(--space-3); display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
|
|
2643
2620
|
.next-step p { margin: var(--space-1) 0 0; color: var(--muted); font-size: var(--text-sm); }
|
|
@@ -3608,21 +3585,13 @@ p.setup-note .btn.link { min-height: 44px; margin-block: calc((1lh - 44px) / 2);
|
|
|
3608
3585
|
|
|
3609
3586
|
/* Compact, thumb-friendly automation list: the row is the editor link. */
|
|
3610
3587
|
.automation-list { gap: var(--space-2); border: 0; }
|
|
3611
|
-
.automation-row
|
|
3588
|
+
.automation-row {
|
|
3612
3589
|
min-height: 0; padding: var(--space-3); border: 1px solid var(--line);
|
|
3613
3590
|
border-radius: var(--radius-lg); background: var(--surface-2);
|
|
3614
3591
|
grid-template-columns: 36px minmax(0, 1fr) auto; gap: var(--space-3);
|
|
3615
3592
|
align-items: center;
|
|
3616
3593
|
}
|
|
3617
|
-
.
|
|
3618
|
-
display: flex; flex-wrap: wrap; align-items: flex-start;
|
|
3619
|
-
gap: var(--space-3); padding: var(--space-4);
|
|
3620
|
-
}
|
|
3621
|
-
.run-row-open { flex: 1 1 100%; min-width: 0; }
|
|
3622
|
-
.run-row .automation-row-main { width: 100%; }
|
|
3623
|
-
.run-row .automation-row-actions { width: 100%; justify-content: flex-start; flex-wrap: wrap; }
|
|
3624
|
-
.run-row .run-row-chevron { margin-left: auto; }
|
|
3625
|
-
.automation-row:not(.run-row):hover { background: var(--surface-3); }
|
|
3594
|
+
.automation-row:hover { background: var(--surface-3); }
|
|
3626
3595
|
.automation-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
3627
3596
|
.automation-row-icon { width: 36px; height: 36px; }
|
|
3628
3597
|
.automation-row-icon svg { width: 18px; height: 18px; }
|
|
@@ -3649,8 +3618,6 @@ p.setup-note .btn.link { min-height: 44px; margin-block: calc((1lh - 44px) / 2);
|
|
|
3649
3618
|
.automation-history-view .autom-section-head { margin-bottom: var(--space-3); }
|
|
3650
3619
|
.automation-history-view .automation-list { gap: var(--space-2); }
|
|
3651
3620
|
.automation-history-view .automation-row { padding: var(--space-3); }
|
|
3652
|
-
.automation-history-view .run-history-filters { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding-bottom: var(--space-1); }
|
|
3653
|
-
.automation-history-view .run-history-filters::-webkit-scrollbar { display: none; }
|
|
3654
3621
|
.automations-view.is-history .automations-view-head-actions > .btn.primary { display: none; }
|
|
3655
3622
|
.automation-history-view .autom-section-head { align-items: flex-start; gap: var(--space-3); }
|
|
3656
3623
|
.automation-history-view .autom-section-head > strong { min-width: 0; line-height: var(--lh-lg); }
|
|
@@ -3666,3 +3633,22 @@ p.setup-note .btn.link { min-height: 44px; margin-block: calc((1lh - 44px) / 2);
|
|
|
3666
3633
|
font-family: var(--font-serif);
|
|
3667
3634
|
font-weight: var(--weight-medium);
|
|
3668
3635
|
}
|
|
3636
|
+
/* A Vega-Lite chart an agent placed in a message. Vega owns the SVG inside
|
|
3637
|
+
.chart-host, so nothing here styles its internals — colors come from the
|
|
3638
|
+
tokens read in vega.ts, which is how the chart tracks light/dark. */
|
|
3639
|
+
.chart-component { margin: 0; }
|
|
3640
|
+
.chart-component .chart-host {
|
|
3641
|
+
width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; padding: 10px;
|
|
3642
|
+
border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface);
|
|
3643
|
+
}
|
|
3644
|
+
.chart-component figcaption { margin-top: var(--space-2); }
|
|
3645
|
+
/* An app preview framed inside a message. Same bordered surface as a chart or
|
|
3646
|
+
a mermaid diagram — all three are a figure in the prose, and should not read
|
|
3647
|
+
as three different things. */
|
|
3648
|
+
.inline-app { margin: 0; }
|
|
3649
|
+
.inline-app-frame {
|
|
3650
|
+
display: block; width: 100%; height: 420px; border: 1px solid var(--line);
|
|
3651
|
+
border-radius: var(--radius-sm); background: var(--surface);
|
|
3652
|
+
}
|
|
3653
|
+
.inline-app figcaption { display: flex; gap: var(--space-2); align-items: baseline; justify-content: space-between; margin-top: var(--space-2); }
|
|
3654
|
+
.inline-app-open { flex: none; }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2026 Petter André Sjulstad
|
|
3
|
+
import { open, seal } from "./e2e.js";
|
|
4
|
+
export async function resealAutomationTemplates(opts) {
|
|
5
|
+
const prefix = `bivy-room-v1:${opts.nodeId}:`;
|
|
6
|
+
const result = { resealed: 0, unreadable: [] };
|
|
7
|
+
for (const template of opts.templates) {
|
|
8
|
+
if (!template.templateCiphertext.startsWith(prefix))
|
|
9
|
+
continue;
|
|
10
|
+
const payload = template.templateCiphertext.slice(prefix.length);
|
|
11
|
+
if (tryOpen(opts.current, payload) !== undefined)
|
|
12
|
+
continue;
|
|
13
|
+
const plaintext = opts.retired.map((key) => tryOpen(key, payload)).find((text) => text !== undefined);
|
|
14
|
+
if (plaintext === undefined) {
|
|
15
|
+
result.unreadable.push(template.id);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
await opts.save(template.id, `${prefix}${seal(opts.current, plaintext)}`);
|
|
19
|
+
result.resealed += 1;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
function tryOpen(key, payload) {
|
|
24
|
+
try {
|
|
25
|
+
return open(key, payload);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/config-cli.js
CHANGED
|
@@ -112,6 +112,7 @@ const ENV_FOR_KEY = {
|
|
|
112
112
|
"sessions.previewBadge": "BIVY_PREVIEW_BADGE",
|
|
113
113
|
"automation.checks": "BIVY_AUTOMATION_CHECKS",
|
|
114
114
|
"automation.checkTimeoutMinutes": "BIVY_AUTOMATION_CHECK_TIMEOUT_MS",
|
|
115
|
+
"automation.slackPrompts": "BIVY_SLACK_PROMPTS",
|
|
115
116
|
};
|
|
116
117
|
const BUILTIN_VALUES = {
|
|
117
118
|
"node.port": 4317,
|
|
@@ -128,6 +129,7 @@ const BUILTIN_VALUES = {
|
|
|
128
129
|
"sessions.previewBadge": true,
|
|
129
130
|
"automation.checks": ["test", "lint", "typecheck"],
|
|
130
131
|
"automation.checkTimeoutMinutes": 10,
|
|
132
|
+
"automation.slackPrompts": false,
|
|
131
133
|
};
|
|
132
134
|
function baseEffectiveValue(config, key) {
|
|
133
135
|
const configured = getConfigValue(config, key);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// provider-list helpers) as composition deps.
|
|
11
11
|
import { testProviderCredential } from "../runtime/credentials.js";
|
|
12
12
|
import { NativeAuthPreviewService } from "../runtime/native-auth-preview.js";
|
|
13
|
-
import { exportAccountApiKeys, exportAccountOAuthCredentials, importAccountOAuthCredentials, exportRecordTombstones, listCredentialRecords, removeProviderCredential, setProviderApiKeyLabeled, setProviderReferenceLabeled, setCredentialSync,
|
|
13
|
+
import { exportAccountApiKeys, exportAccountOAuthCredentials, importAccountOAuthCredentials, exportRecordTombstones, listCredentialRecords, removeProviderCredential, setProviderApiKeyLabeled, setProviderReferenceLabeled, setCredentialSync, getCredentialPresets, setActiveCredentialPreset, setCredentialPresetMapping, } from "../credentials/api.js";
|
|
14
14
|
export function createCredentialCommands(deps) {
|
|
15
15
|
const { credsDir } = deps;
|
|
16
16
|
const nativeAuth = new NativeAuthPreviewService(credsDir);
|
|
@@ -95,8 +95,7 @@ export function createCredentialCommands(deps) {
|
|
|
95
95
|
async "credential.remove"(msg, ctx) {
|
|
96
96
|
try {
|
|
97
97
|
await removeProviderCredential(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""));
|
|
98
|
-
|
|
99
|
-
await deps.pushModelAuthToControlPlane({ allowEmptyHostedEscrow: true });
|
|
98
|
+
await deps.pushModelAuthToControlPlane();
|
|
100
99
|
await deps.refreshSessionAfterAuth();
|
|
101
100
|
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
102
101
|
deps.broadcast({ type: "providers.list", providers: await deps.listProvidersUnified() });
|
|
@@ -112,8 +111,7 @@ export function createCredentialCommands(deps) {
|
|
|
112
111
|
try {
|
|
113
112
|
const sync = msg.sync === "node" ? "node" : "account";
|
|
114
113
|
await setCredentialSync(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""), sync);
|
|
115
|
-
|
|
116
|
-
await deps.pushModelAuthToControlPlane({ allowEmptyHostedEscrow: sync === "node" });
|
|
114
|
+
await deps.pushModelAuthToControlPlane();
|
|
117
115
|
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
118
116
|
ctx.reply({ type: "credential.sync.set.ok", requestId: msg.requestId });
|
|
119
117
|
}
|
|
@@ -123,28 +121,6 @@ export function createCredentialCommands(deps) {
|
|
|
123
121
|
ctx.reply({ type: "credential.sync.set.error", requestId: msg.requestId, error: message });
|
|
124
122
|
}
|
|
125
123
|
},
|
|
126
|
-
async "credential.unattended.set"(msg, ctx) {
|
|
127
|
-
const provider = String(msg.provider ?? "");
|
|
128
|
-
const label = String(msg.label ?? "");
|
|
129
|
-
const previous = (await listCredentialRecords(credsDir)).find((record) => record.provider === provider.trim().toLowerCase() && record.label === label)?.unattended === true;
|
|
130
|
-
try {
|
|
131
|
-
await setCredentialUnattended(credsDir, provider, label, msg.unattended === true);
|
|
132
|
-
// Fail loud: the grant reply must reflect whether the encrypted cloud
|
|
133
|
-
// copy actually reached the control plane. An explicit disable is the
|
|
134
|
-
// one caller allowed to publish an empty escrow snapshot.
|
|
135
|
-
await deps.pushModelAuthToControlPlane({ throwOnFailure: true, allowEmptyHostedEscrow: msg.unattended !== true });
|
|
136
|
-
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
137
|
-
ctx.reply({ type: "credential.unattended.set.ok", requestId: msg.requestId });
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
|
-
// The UI must never claim an encrypted Cloud copy exists when custody
|
|
141
|
-
// publication failed. Restore the prior local grant before rejecting.
|
|
142
|
-
await setCredentialUnattended(credsDir, provider, label, previous).catch(() => { });
|
|
143
|
-
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
144
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
145
|
-
ctx.reply({ type: "credential.unattended.set.error", requestId: msg.requestId, error: message });
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
124
|
// "Test connection": a bounded, non-secret liveness probe for one credential
|
|
149
125
|
// record (see credentials/api.ts testCredential). The reply carries only
|
|
150
126
|
// ok/at/reason — the credential's own token never leaves this handler.
|
package/dist/credentials/api.js
CHANGED
|
@@ -132,18 +132,6 @@ export async function importCredentialRecords(credsDir, records, deletedAt = {})
|
|
|
132
132
|
}
|
|
133
133
|
return createCredentialVault(credsDir).importRecords(list, tombstones);
|
|
134
134
|
}
|
|
135
|
-
/** Apply a hosted custody snapshot as an authoritative filtered set. */
|
|
136
|
-
export async function reconcileHostedCredentialRecords(credsDir, records, previous) {
|
|
137
|
-
const current = Object.values(records ?? {}).filter((record) => Boolean(record) && typeof record === "object" && typeof record.provider === "string" && typeof record.label === "string");
|
|
138
|
-
const currentIds = new Set(current.map((record) => `${record.provider}\u0000${record.label}`));
|
|
139
|
-
const store = createCredentialVault(credsDir);
|
|
140
|
-
for (const prior of previous) {
|
|
141
|
-
if (!currentIds.has(`${prior.provider}\u0000${prior.label}`))
|
|
142
|
-
await store.deleteRecord(prior.provider, prior.label);
|
|
143
|
-
}
|
|
144
|
-
await importCredentialRecords(credsDir, records);
|
|
145
|
-
return current.map((record) => ({ provider: record.provider, label: record.label }));
|
|
146
|
-
}
|
|
147
135
|
/** Export provider revocations for cross-node convergence. */
|
|
148
136
|
export async function exportProviderAuthTombstones(credsDir) {
|
|
149
137
|
return createCredentialVault(credsDir).exportTombstones();
|
|
@@ -225,7 +213,7 @@ export async function listCredentialRecords(credsDir) {
|
|
|
225
213
|
const verification = await vault.readVerification(record.provider, record.label).catch(() => undefined);
|
|
226
214
|
const verified = verification ? { lastVerifiedAt: verification.at, lastVerifiedOk: verification.ok } : {};
|
|
227
215
|
if (source.kind === "reference") {
|
|
228
|
-
return { provider: record.provider, label: record.label, kind: "reference", sync: record.sync, origin: record.origin,
|
|
216
|
+
return { provider: record.provider, label: record.label, kind: "reference", sync: record.sync, origin: record.origin, ref: source.ref, testable: false, ...verified };
|
|
229
217
|
}
|
|
230
218
|
const cred = source.cred;
|
|
231
219
|
const summary = {
|
|
@@ -234,7 +222,6 @@ export async function listCredentialRecords(credsDir) {
|
|
|
234
222
|
kind: cred.type,
|
|
235
223
|
sync: record.sync,
|
|
236
224
|
origin: record.origin,
|
|
237
|
-
unattended: record.unattended === true,
|
|
238
225
|
testable: isTestableProvider(record.provider),
|
|
239
226
|
...verified,
|
|
240
227
|
};
|
|
@@ -253,7 +240,6 @@ async function labeledMeta(credsDir, provider, label) {
|
|
|
253
240
|
return {
|
|
254
241
|
sync: existing?.sync ?? defaultSyncFor("bivy"),
|
|
255
242
|
origin: existing?.origin ?? "bivy",
|
|
256
|
-
...(existing?.unattended === true ? { unattended: true } : {}),
|
|
257
243
|
};
|
|
258
244
|
}
|
|
259
245
|
/** Store an API key under a specific label (multi-account). */
|
|
@@ -404,33 +390,6 @@ export function getCredentialIngestPolicy(credsDir) {
|
|
|
404
390
|
export function setCredentialIngestPolicy(credsDir, policy) {
|
|
405
391
|
setIngestPolicyFile(defaultPresetsPath(credsDir), policy);
|
|
406
392
|
}
|
|
407
|
-
/** Explicitly grant/revoke use by separately escrowed unattended runners. */
|
|
408
|
-
export async function setCredentialUnattended(credsDir, provider, label, unattended) {
|
|
409
|
-
const id = provider.trim().toLowerCase();
|
|
410
|
-
if (!id)
|
|
411
|
-
throw new Error("Provider is required");
|
|
412
|
-
const store = createCredentialVault(credsDir);
|
|
413
|
-
const existing = await store.readRecord(id, label);
|
|
414
|
-
if (!existing)
|
|
415
|
-
throw new Error(`No credential for ${id}:${normalizeLabel(label)}`);
|
|
416
|
-
if (existing.sync !== "account" && unattended)
|
|
417
|
-
throw new Error("A machine-only credential cannot be granted to unattended runners");
|
|
418
|
-
if (existing.source.kind === "reference" && unattended)
|
|
419
|
-
throw new Error("Password-manager references cannot be resolved by unattended runners");
|
|
420
|
-
await store.putRecord({ ...existing, unattended });
|
|
421
|
-
}
|
|
422
|
-
/** Credentials included in the explicit hosted custody snapshot. */
|
|
423
|
-
export async function exportUnattendedRecords(credsDir) {
|
|
424
|
-
const records = await createCredentialVault(credsDir).exportSyncableRecords();
|
|
425
|
-
return Object.fromEntries(Object.entries(records).filter(([, record]) => record.unattended === true && record.source.kind === "stored"));
|
|
426
|
-
}
|
|
427
|
-
/** Monotonic logical revision for the filtered custody projection. */
|
|
428
|
-
export async function unattendedCredentialRevision(credsDir) {
|
|
429
|
-
const store = createCredentialVault(credsDir);
|
|
430
|
-
const records = await store.listRecords();
|
|
431
|
-
const deletedAt = await store.exportRecordTombstones();
|
|
432
|
-
return Math.max(0, ...records.map((record) => Number(record.updatedAt) || 0), ...Object.values(deletedAt).map((stamp) => Number(stamp) || 0));
|
|
433
|
-
}
|
|
434
393
|
/** Set a labeled credential's sync tier — the per-credential opt-out toggle. */
|
|
435
394
|
export async function setCredentialSync(credsDir, provider, label, sync) {
|
|
436
395
|
const id = provider.trim().toLowerCase();
|
|
@@ -445,5 +404,5 @@ export async function setCredentialSync(credsDir, provider, label, sync) {
|
|
|
445
404
|
if (existing.source.kind === "reference" && existing.source.backend === "command" && sync === "account") {
|
|
446
405
|
throw new Error("A cmd:// reference is node-local (it runs a command on this machine) and cannot be synced.");
|
|
447
406
|
}
|
|
448
|
-
await store.putRecord({ ...existing, sync
|
|
407
|
+
await store.putRecord({ ...existing, sync });
|
|
449
408
|
}
|
|
@@ -137,12 +137,11 @@ export function preferIncomingRecord(local, incoming) {
|
|
|
137
137
|
const incomingOauth = oauthOf(incoming);
|
|
138
138
|
if (!localOauth || !incomingOauth)
|
|
139
139
|
return true;
|
|
140
|
-
// Identical token content means this is a metadata-only change (
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
// record content so both merge directions still converge.
|
|
140
|
+
// Identical token content means this is a metadata-only change (a sync-tier
|
|
141
|
+
// move). Token freshness cannot order those, so the newer record write wins —
|
|
142
|
+
// without this, a change made on one machine never reaches its peers. Equal
|
|
143
|
+
// stamps fall back to canonical record content so both merge directions still
|
|
144
|
+
// converge.
|
|
146
145
|
if (canonicalCredential(localOauth) === canonicalCredential(incomingOauth)) {
|
|
147
146
|
const localAt = Number(local.updatedAt) || 0;
|
|
148
147
|
const incomingAt = Number(incoming.updatedAt) || 0;
|
|
@@ -269,14 +268,7 @@ export function mergeDocuments(local, incomingCredentials, incomingDeletedAt = {
|
|
|
269
268
|
continue;
|
|
270
269
|
if (!(key in credentials))
|
|
271
270
|
imported += 1;
|
|
272
|
-
|
|
273
|
-
// v2 wire and plaintext ingest never carry the flag) must not strip an
|
|
274
|
-
// explicit local grant/revoke: agent-side token refreshes flow through this
|
|
275
|
-
// merge, and dropping the flag would make the node's next escrow push
|
|
276
|
-
// silently revoke the encrypted cloud copy the user still relies on.
|
|
277
|
-
credentials[key] = localRecord && incoming.unattended === undefined && localRecord.unattended !== undefined
|
|
278
|
-
? { ...incoming, unattended: localRecord.unattended }
|
|
279
|
-
: incoming;
|
|
271
|
+
credentials[key] = incoming;
|
|
280
272
|
const incomingUpdatedAt = Number(incoming.updatedAt);
|
|
281
273
|
if (!Number.isFinite(deletedAt[key]) || incomingUpdatedAt > deletedAt[key])
|
|
282
274
|
delete deletedAt[key];
|
|
@@ -555,11 +555,11 @@ export class BivyCredentialStore {
|
|
|
555
555
|
// tombstone-newer-wins — see document.ts).
|
|
556
556
|
const incoming = migrateToV3({ v: 2, providers: snapshot, deletedAt });
|
|
557
557
|
// The v2 wire carries no record metadata, so migrateToV3 synthesizes
|
|
558
|
-
// defaults (sync: "account", origin: "bivy"
|
|
559
|
-
//
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
//
|
|
558
|
+
// defaults (sync: "account", origin: "bivy"). Those defaults must not
|
|
559
|
+
// clobber local intent — a machine-only sync tier — when only the token
|
|
560
|
+
// content is newer (e.g. an agent's own TUI refreshed the OAuth token
|
|
561
|
+
// set). Inherit the existing record's metadata; the merge engine still
|
|
562
|
+
// decides freshness.
|
|
563
563
|
for (const [key, record] of Object.entries(incoming.credentials)) {
|
|
564
564
|
const existing = document.credentials[key];
|
|
565
565
|
if (!existing)
|
|
@@ -568,7 +568,6 @@ export class BivyCredentialStore {
|
|
|
568
568
|
...record,
|
|
569
569
|
sync: existing.sync,
|
|
570
570
|
origin: existing.origin,
|
|
571
|
-
...(existing.unattended !== undefined ? { unattended: existing.unattended } : {}),
|
|
572
571
|
};
|
|
573
572
|
}
|
|
574
573
|
const result = mergeDocuments(document, incoming.credentials, incoming.deletedAt);
|
package/dist/device-registry.js
CHANGED
|
@@ -7,6 +7,7 @@ import { generatePairingKeypair, generateRoomKey, generatePairSecret, deriveWrap
|
|
|
7
7
|
import { seal, open } from "./e2e.js";
|
|
8
8
|
const DEFAULT_PAIR_TTL_MS = 5 * 60_000;
|
|
9
9
|
const ROOM_KEY_BYTES = 32;
|
|
10
|
+
const MAX_RETIRED_ROOM_KEYS = 8;
|
|
10
11
|
/**
|
|
11
12
|
* Validate + canonicalize a pre-shared room-key seed. Returns the canonical
|
|
12
13
|
* base64 of a well-formed 32-byte key, or undefined for anything malformed
|
|
@@ -81,6 +82,7 @@ export class PairingStore {
|
|
|
81
82
|
return new PairingStore(filePath, {
|
|
82
83
|
nodeKeypair: parsed.nodeKeypair,
|
|
83
84
|
roomKeyB64: parsed.roomKeyB64,
|
|
85
|
+
...(Array.isArray(parsed.retiredRoomKeysB64) && parsed.retiredRoomKeysB64.length ? { retiredRoomKeysB64: parsed.retiredRoomKeysB64 } : {}),
|
|
84
86
|
devices: Array.isArray(parsed.devices) ? parsed.devices : [],
|
|
85
87
|
});
|
|
86
88
|
}
|
|
@@ -130,6 +132,30 @@ export class PairingStore {
|
|
|
130
132
|
roomKey() {
|
|
131
133
|
return Buffer.from(this.data.roomKeyB64, "base64");
|
|
132
134
|
}
|
|
135
|
+
/** Room keys replaced by earlier rotations that still guard stored data. */
|
|
136
|
+
retiredRoomKeys() {
|
|
137
|
+
return (this.data.retiredRoomKeysB64 ?? []).map((key) => Buffer.from(key, "base64"));
|
|
138
|
+
}
|
|
139
|
+
/** Drop retired room keys once nothing stored is sealed under them. */
|
|
140
|
+
forgetRetiredRoomKeys() {
|
|
141
|
+
if (!this.data.retiredRoomKeysB64)
|
|
142
|
+
return;
|
|
143
|
+
delete this.data.retiredRoomKeysB64;
|
|
144
|
+
this.persist();
|
|
145
|
+
}
|
|
146
|
+
/** Open a payload sealed under the current room key or a retired one. */
|
|
147
|
+
openSealed(payload) {
|
|
148
|
+
let lastError;
|
|
149
|
+
for (const key of [this.roomKey(), ...this.retiredRoomKeys()]) {
|
|
150
|
+
try {
|
|
151
|
+
return open(key, payload);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
lastError = error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
throw lastError;
|
|
158
|
+
}
|
|
133
159
|
/** Issue a single-use, expiring pairing secret to embed in a linking QR. */
|
|
134
160
|
issuePairSecret(ttlMs = DEFAULT_PAIR_TTL_MS) {
|
|
135
161
|
this.sweepSecrets();
|
|
@@ -215,6 +241,7 @@ export class PairingStore {
|
|
|
215
241
|
}
|
|
216
242
|
/** Rotate the room key and re-wrap it for all current devices. */
|
|
217
243
|
rotateRoomKey() {
|
|
244
|
+
this.data.retiredRoomKeysB64 = [this.data.roomKeyB64, ...(this.data.retiredRoomKeysB64 ?? [])].slice(0, MAX_RETIRED_ROOM_KEYS);
|
|
218
245
|
this.data.roomKeyB64 = generateRoomKey().toString("base64");
|
|
219
246
|
this.persist();
|
|
220
247
|
const roomKey = this.roomKey();
|
|
Binary file
|