@intentic/sandbox-contract 1.344.0 → 1.346.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 +27 -9
- package/dist/contracts/accounts.contract.d.ts +24 -80
- package/dist/contracts/accounts.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +247 -8
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +13 -2
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +1570 -33
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +47 -4
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/device.contract.d.ts +244 -1
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.js +5 -1
- package/dist/contracts/device.contract.js.map +1 -1
- package/dist/contracts/endpoints.contract.d.ts +2 -1
- package/dist/contracts/endpoints.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.js +1 -0
- package/dist/contracts/endpoints.contract.js.map +1 -1
- package/dist/contracts/providers.contract.d.ts +0 -1
- package/dist/contracts/providers.contract.d.ts.map +1 -1
- package/dist/contracts/push.contract.d.ts +11 -0
- package/dist/contracts/push.contract.d.ts.map +1 -1
- package/dist/contracts/push.contract.js +10 -1
- package/dist/contracts/push.contract.js.map +1 -1
- package/dist/contracts/runner.contract.d.ts +149 -123
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.d.ts +169 -0
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.js +35 -0
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +2 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +37 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +235 -31
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.d.ts +24 -80
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +11 -0
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +11 -1
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events/agent-events.d.ts +60 -0
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +44 -38
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/agent-transcript.d.ts +727 -0
- package/dist/events/agent-transcript.d.ts.map +1 -0
- package/dist/events/agent-transcript.js +34 -0
- package/dist/events/agent-transcript.js.map +1 -0
- package/dist/events/agent-words.d.ts +5 -0
- package/dist/events/agent-words.d.ts.map +1 -1
- package/dist/events/agent-words.js +9 -0
- package/dist/events/agent-words.js.map +1 -1
- package/dist/events/failure-rows.d.ts +14 -0
- package/dist/events/failure-rows.d.ts.map +1 -0
- package/dist/events/failure-rows.js +109 -0
- package/dist/events/failure-rows.js.map +1 -0
- package/dist/events/requests.d.ts +143 -0
- package/dist/events/requests.d.ts.map +1 -1
- package/dist/events/requests.js +35 -1
- package/dist/events/requests.js.map +1 -1
- package/dist/events/sandbox-notice.d.ts +23 -0
- package/dist/events/sandbox-notice.d.ts.map +1 -1
- package/dist/events/sandbox-notice.js +2 -0
- package/dist/events/sandbox-notice.js.map +1 -1
- package/dist/events/system-events.d.ts +156 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/system-events.js +7 -1
- package/dist/events/system-events.js.map +1 -1
- package/dist/events/transcript.d.ts +20 -720
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +17 -19
- package/dist/events/transcript.js.map +1 -1
- package/dist/ids/conversation-ids.d.ts +2 -0
- package/dist/ids/conversation-ids.d.ts.map +1 -1
- package/dist/ids/conversation-ids.js +2 -0
- package/dist/ids/conversation-ids.js.map +1 -1
- package/dist/ids/row-ids.d.ts +3 -0
- package/dist/ids/row-ids.d.ts.map +1 -0
- package/dist/ids/row-ids.js +11 -0
- package/dist/ids/row-ids.js.map +1 -0
- package/dist/index.d.ts +2296 -199
- package/dist/index.d.ts.map +1 -1
- package/dist/models/agent-lanes.d.ts.map +1 -1
- package/dist/models/agent-lanes.js +6 -7
- package/dist/models/agent-lanes.js.map +1 -1
- package/dist/models/agent-standing.d.ts +6 -19
- package/dist/models/agent-standing.d.ts.map +1 -1
- package/dist/models/agent-standing.js +14 -38
- package/dist/models/agent-standing.js.map +1 -1
- package/dist/models/model-pins.d.ts +3 -0
- package/dist/models/model-pins.d.ts.map +1 -1
- package/dist/models/model-pins.js +2 -0
- package/dist/models/model-pins.js.map +1 -1
- package/dist/models/plan-pools.d.ts +26 -4
- package/dist/models/plan-pools.d.ts.map +1 -1
- package/dist/models/plan-pools.js +159 -40
- package/dist/models/plan-pools.js.map +1 -1
- package/dist/netd/generated/wire.d.ts +5 -0
- package/dist/netd/generated/wire.d.ts.map +1 -1
- package/dist/policy/command-classes.d.ts +1 -1
- package/dist/policy/command-classes.d.ts.map +1 -1
- package/dist/policy/command-classes.js +2 -2
- package/dist/policy/command-classes.js.map +1 -1
- package/dist/policy/keyring-origins.d.ts +27 -0
- package/dist/policy/keyring-origins.d.ts.map +1 -0
- package/dist/policy/keyring-origins.js +213 -0
- package/dist/policy/keyring-origins.js.map +1 -0
- package/dist/policy/keyring-seal.d.ts +9 -0
- package/dist/policy/keyring-seal.d.ts.map +1 -1
- package/dist/policy/keyring-seal.js +18 -6
- package/dist/policy/keyring-seal.js.map +1 -1
- package/dist/policy/safety-policy.d.ts +1 -1
- package/dist/policy/safety-policy.d.ts.map +1 -1
- package/dist/policy/safety-policy.js +1 -1
- package/dist/policy/safety-policy.js.map +1 -1
- package/dist/policy/turned-away.d.ts +1 -0
- package/dist/policy/turned-away.d.ts.map +1 -1
- package/dist/policy/turned-away.js +10 -0
- package/dist/policy/turned-away.js.map +1 -1
- package/dist/protocol/doors/host-protocol.d.ts +1 -0
- package/dist/protocol/doors/host-protocol.d.ts.map +1 -1
- package/dist/protocol/doors/host-protocol.js +1 -0
- package/dist/protocol/doors/host-protocol.js.map +1 -1
- package/dist/protocol/doors/runner-protocol.d.ts +1 -0
- package/dist/protocol/doors/runner-protocol.d.ts.map +1 -1
- package/dist/protocol/doors/runner-protocol.js +1 -0
- package/dist/protocol/doors/runner-protocol.js.map +1 -1
- package/dist/protocol/doors/webext-links.d.ts +1 -0
- package/dist/protocol/doors/webext-links.d.ts.map +1 -1
- package/dist/protocol/doors/webext-links.js +1 -0
- package/dist/protocol/doors/webext-links.js.map +1 -1
- package/dist/protocol/peer-dial.d.ts +2 -0
- package/dist/protocol/peer-dial.d.ts.map +1 -1
- package/dist/protocol/peer-dial.js +62 -29
- package/dist/protocol/peer-dial.js.map +1 -1
- package/dist/protocol/raw/raw-json-routes.d.ts +179 -1
- package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-json-routes.js +5 -0
- package/dist/protocol/raw/raw-json-routes.js.map +1 -1
- package/dist/protocol/raw/raw-routes.d.ts +35 -0
- package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-routes.js +9 -0
- package/dist/protocol/raw/raw-routes.js.map +1 -1
- package/dist/schemas/agent-next.d.ts +21 -0
- package/dist/schemas/agent-next.d.ts.map +1 -0
- package/dist/schemas/agent-next.js +21 -0
- package/dist/schemas/agent-next.js.map +1 -0
- package/dist/schemas/agent-place.d.ts +10 -0
- package/dist/schemas/agent-place.d.ts.map +1 -0
- package/dist/schemas/agent-place.js +15 -0
- package/dist/schemas/agent-place.js.map +1 -0
- package/dist/schemas/agent-search.d.ts +45 -0
- package/dist/schemas/agent-search.d.ts.map +1 -0
- package/dist/schemas/agent-search.js +28 -0
- package/dist/schemas/agent-search.js.map +1 -0
- package/dist/schemas/agent.d.ts +120 -78
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +40 -49
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents-list.d.ts +54 -0
- package/dist/schemas/agents-list.d.ts.map +1 -1
- package/dist/schemas/agents.d.ts +165 -59
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +25 -50
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/attach.d.ts +8 -0
- package/dist/schemas/attach.d.ts.map +1 -0
- package/dist/schemas/attach.js +14 -0
- package/dist/schemas/attach.js.map +1 -0
- package/dist/schemas/command-admission.d.ts +71 -0
- package/dist/schemas/command-admission.d.ts.map +1 -0
- package/dist/schemas/command-admission.js +27 -0
- package/dist/schemas/command-admission.js.map +1 -0
- package/dist/schemas/compose-overlay.d.ts +9 -0
- package/dist/schemas/compose-overlay.d.ts.map +1 -0
- package/dist/schemas/compose-overlay.js +11 -0
- package/dist/schemas/compose-overlay.js.map +1 -0
- package/dist/schemas/composer-drafts.d.ts +169 -0
- package/dist/schemas/composer-drafts.d.ts.map +1 -0
- package/dist/schemas/composer-drafts.js +60 -0
- package/dist/schemas/composer-drafts.js.map +1 -0
- package/dist/schemas/conversation-links.d.ts +44 -0
- package/dist/schemas/conversation-links.d.ts.map +1 -0
- package/dist/schemas/conversation-links.js +35 -0
- package/dist/schemas/conversation-links.js.map +1 -0
- package/dist/schemas/data-protection.d.ts +1 -1
- package/dist/schemas/data-protection.d.ts.map +1 -1
- package/dist/schemas/device-commands.d.ts +63 -0
- package/dist/schemas/device-commands.d.ts.map +1 -0
- package/dist/schemas/device-commands.js +42 -0
- package/dist/schemas/device-commands.js.map +1 -0
- package/dist/schemas/device-folder.d.ts +11 -0
- package/dist/schemas/device-folder.d.ts.map +1 -1
- package/dist/schemas/device-folder.js +1 -0
- package/dist/schemas/device-folder.js.map +1 -1
- package/dist/schemas/devices.d.ts +18 -68
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +8 -45
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/environment.d.ts.map +1 -1
- package/dist/schemas/environment.js.map +1 -1
- package/dist/schemas/hosts.d.ts +29 -0
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +23 -0
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/keyring-native.d.ts +137 -0
- package/dist/schemas/keyring-native.d.ts.map +1 -0
- package/dist/schemas/keyring-native.js +69 -0
- package/dist/schemas/keyring-native.js.map +1 -0
- package/dist/schemas/keyring.d.ts +413 -0
- package/dist/schemas/keyring.d.ts.map +1 -1
- package/dist/schemas/keyring.js +139 -4
- package/dist/schemas/keyring.js.map +1 -1
- package/dist/schemas/phone-passwords.d.ts +186 -0
- package/dist/schemas/phone-passwords.d.ts.map +1 -0
- package/dist/schemas/phone-passwords.js +25 -0
- package/dist/schemas/phone-passwords.js.map +1 -0
- package/dist/schemas/privacy-egress.d.ts +41 -0
- package/dist/schemas/privacy-egress.d.ts.map +1 -0
- package/dist/schemas/privacy-egress.js +30 -0
- package/dist/schemas/privacy-egress.js.map +1 -0
- package/dist/schemas/privacy.d.ts +1 -0
- package/dist/schemas/privacy.d.ts.map +1 -1
- package/dist/schemas/privacy.js +1 -0
- package/dist/schemas/privacy.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +76 -130
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +36 -23
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/providers/provider-oauth.d.ts +24 -82
- package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/providers/provider-oauth.js +1 -6
- package/dist/schemas/providers/provider-oauth.js.map +1 -1
- package/dist/schemas/providers/refusals.d.ts +64 -0
- package/dist/schemas/providers/refusals.d.ts.map +1 -0
- package/dist/schemas/providers/refusals.js +33 -0
- package/dist/schemas/providers/refusals.js.map +1 -0
- package/dist/schemas/providers/usage.d.ts +1 -0
- package/dist/schemas/providers/usage.d.ts.map +1 -1
- package/dist/schemas/providers/usage.js +1 -0
- package/dist/schemas/providers/usage.js.map +1 -1
- package/dist/schemas/push.d.ts +13 -0
- package/dist/schemas/push.d.ts.map +1 -1
- package/dist/schemas/push.js +11 -1
- package/dist/schemas/push.js.map +1 -1
- package/dist/schemas/sandbox-passwords.d.ts +288 -0
- package/dist/schemas/sandbox-passwords.d.ts.map +1 -0
- package/dist/schemas/sandbox-passwords.js +66 -0
- package/dist/schemas/sandbox-passwords.js.map +1 -0
- package/dist/schemas/secrets.d.ts +2 -0
- package/dist/schemas/secrets.d.ts.map +1 -1
- package/dist/schemas/secrets.js +4 -0
- package/dist/schemas/secrets.js.map +1 -1
- package/dist/schemas/sessions.js +1 -1
- package/dist/schemas/sessions.js.map +1 -1
- package/dist/schemas/settings-savings.d.ts +709 -0
- package/dist/schemas/settings-savings.d.ts.map +1 -0
- package/dist/schemas/settings-savings.js +53 -0
- package/dist/schemas/settings-savings.js.map +1 -0
- package/dist/schemas/settings.d.ts +7 -671
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +5 -50
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/system.d.ts +16 -0
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +11 -0
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/terminal.d.ts +5 -0
- package/dist/schemas/terminal.d.ts.map +1 -1
- package/dist/schemas/terminal.js +4 -0
- package/dist/schemas/terminal.js.map +1 -1
- package/dist/schemas/work-assurance.d.ts +24 -0
- package/dist/schemas/work-assurance.d.ts.map +1 -0
- package/dist/schemas/work-assurance.js +23 -0
- package/dist/schemas/work-assurance.js.map +1 -0
- package/dist/schemas/workspace/workspace-tree.d.ts +23 -0
- package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
- package/dist/schemas/workspace/workspace-tree.js +20 -0
- package/dist/schemas/workspace/workspace-tree.js.map +1 -1
- package/dist/state/auth-state.d.ts +4 -0
- package/dist/state/auth-state.d.ts.map +1 -0
- package/dist/state/auth-state.js +37 -0
- package/dist/state/auth-state.js.map +1 -0
- package/dist/state/definition.d.ts +12 -0
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/runtime-state.d.ts +3 -0
- package/dist/state/runtime-state.d.ts.map +1 -1
- package/dist/state/runtime-state.js +1 -0
- package/dist/state/runtime-state.js.map +1 -1
- package/dist/state/sandbox-build.d.ts +20 -0
- package/dist/state/sandbox-build.d.ts.map +1 -0
- package/dist/state/sandbox-build.js +43 -0
- package/dist/state/sandbox-build.js.map +1 -0
- package/dist/state/ways-back.d.ts +4 -0
- package/dist/state/ways-back.d.ts.map +1 -0
- package/dist/state/ways-back.js +8 -0
- package/dist/state/ways-back.js.map +1 -0
- package/dist/text/land-rows.d.ts +14 -0
- package/dist/text/land-rows.d.ts.map +1 -0
- package/dist/text/land-rows.js +86 -0
- package/dist/text/land-rows.js.map +1 -0
- package/dist/text/transcript-fold.d.ts +4 -2
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +21 -187
- package/dist/text/transcript-fold.js.map +1 -1
- package/dist/webext/index.d.ts +1 -0
- package/dist/webext/index.d.ts.map +1 -1
- package/dist/webext/index.js +1 -0
- package/dist/webext/index.js.map +1 -1
- package/package.json +7 -6
- package/src/contracts/agent.contract.ts +13 -1
- package/src/contracts/agents.contract.ts +53 -28
- package/src/contracts/device.contract.ts +16 -0
- package/src/contracts/endpoints.contract.ts +3 -0
- package/src/contracts/push.contract.ts +23 -1
- package/src/contracts/secrets.contract.ts +39 -0
- package/src/contracts/workspace.contract.ts +16 -0
- package/src/events/agent-events.ts +99 -79
- package/src/events/agent-transcript.ts +48 -0
- package/src/events/agent-words.ts +13 -0
- package/src/events/failure-rows.ts +148 -0
- package/src/events/requests.ts +41 -3
- package/src/events/sandbox-notice.ts +14 -2
- package/src/events/system-events.ts +14 -4
- package/src/events/transcript.ts +30 -28
- package/src/ids/conversation-ids.ts +7 -0
- package/src/ids/project-dir.test.ts +2 -2
- package/src/ids/project-dir.ts +4 -4
- package/src/ids/row-ids.ts +20 -0
- package/src/models/agent-lanes.ts +16 -24
- package/src/models/agent-runtimes.ts +2 -2
- package/src/models/agent-standing.ts +38 -95
- package/src/models/model-pins.test.ts +31 -1
- package/src/models/model-pins.ts +17 -0
- package/src/models/plan-pools.test.ts +274 -31
- package/src/models/plan-pools.ts +294 -79
- package/src/netd/generated/netd-wire.json +25 -0
- package/src/netd/generated/wire.ts +1 -1
- package/src/policy/command-classes.ts +2 -2
- package/src/policy/keyring-origins.test.ts +132 -0
- package/src/policy/keyring-origins.ts +287 -0
- package/src/policy/keyring-seal.test.ts +37 -2
- package/src/policy/keyring-seal.ts +33 -7
- package/src/policy/safety-policy.ts +1 -1
- package/src/policy/tests/safety-policy.test.ts +1 -1
- package/src/policy/turned-away.ts +15 -0
- package/src/protocol/doors/host-protocol.ts +3 -0
- package/src/protocol/doors/runner-protocol.ts +4 -1
- package/src/protocol/doors/webext-links.ts +3 -0
- package/src/protocol/peer-dial.test.ts +90 -4
- package/src/protocol/peer-dial.ts +83 -39
- package/src/protocol/raw/raw-json-routes.ts +13 -0
- package/src/protocol/raw/raw-routes.ts +13 -1
- package/src/schemas/agent-next.ts +34 -0
- package/src/schemas/agent-place.ts +22 -0
- package/src/schemas/agent-search.ts +51 -0
- package/src/schemas/agent.ts +63 -90
- package/src/schemas/agents.ts +55 -99
- package/src/schemas/attach.ts +23 -0
- package/src/schemas/command-admission.ts +57 -0
- package/src/schemas/compose-overlay.test.ts +35 -0
- package/src/schemas/compose-overlay.ts +19 -0
- package/src/schemas/composer-drafts.ts +86 -0
- package/src/schemas/conversation-links.ts +62 -0
- package/src/schemas/data-protection.ts +1 -1
- package/src/schemas/device-commands.ts +89 -0
- package/src/schemas/device-folder.ts +9 -0
- package/src/schemas/devices.ts +28 -110
- package/src/schemas/environment.ts +8 -9
- package/src/schemas/hosts.ts +60 -0
- package/src/schemas/keyring-native.test.ts +13 -0
- package/src/schemas/keyring-native.ts +101 -0
- package/src/schemas/keyring.ts +202 -4
- package/src/schemas/machine-presence.test.ts +33 -0
- package/src/schemas/phone-passwords.ts +51 -0
- package/src/schemas/privacy-egress.test.ts +83 -0
- package/src/schemas/privacy-egress.ts +86 -0
- package/src/schemas/privacy.ts +4 -0
- package/src/schemas/project-delivery.ts +1 -1
- package/src/schemas/providers/plan-limits.test.ts +16 -0
- package/src/schemas/providers/plan-limits.ts +55 -49
- package/src/schemas/providers/provider-oauth.ts +1 -13
- package/src/schemas/providers/refusals.ts +62 -0
- package/src/schemas/providers/usage.ts +2 -0
- package/src/schemas/push.ts +19 -3
- package/src/schemas/sandbox-passwords.ts +88 -0
- package/src/schemas/secrets.ts +4 -0
- package/src/schemas/sessions.ts +1 -1
- package/src/schemas/settings-savings.ts +125 -0
- package/src/schemas/settings.ts +10 -116
- package/src/schemas/system.ts +15 -0
- package/src/schemas/terminal.ts +8 -0
- package/src/schemas/work-assurance.ts +36 -0
- package/src/schemas/workspace/workspace-tree.ts +32 -0
- package/src/state/auth-state.ts +89 -0
- package/src/state/runtime-state.ts +10 -4
- package/src/state/sandbox-build.test.ts +95 -0
- package/src/state/sandbox-build.ts +96 -0
- package/src/state/ways-back.test.ts +36 -0
- package/src/state/ways-back.ts +19 -0
- package/src/state/workspace-state.ts +1 -1
- package/src/text/land-rows.ts +111 -0
- package/src/text/transcript-fold.test.ts +105 -9
- package/src/text/transcript-fold.ts +30 -232
- package/src/webext/index.ts +2 -0
- package/dist/events/undelivered.d.ts +0 -6
- package/dist/events/undelivered.d.ts.map +0 -1
- package/dist/events/undelivered.js +0 -9
- package/dist/events/undelivered.js.map +0 -1
- package/dist/state/history-state.d.ts +0 -3
- package/dist/state/history-state.d.ts.map +0 -1
- package/dist/state/history-state.js +0 -137
- package/dist/state/history-state.js.map +0 -1
- package/src/events/undelivered.ts +0 -15
- package/src/state/history-state.ts +0 -270
package/src/schemas/hosts.ts
CHANGED
|
@@ -21,6 +21,22 @@ export const MachineIdSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{7,1
|
|
|
21
21
|
export const derivedMachineId = (card: string): string => `card:${card}`;
|
|
22
22
|
export const isDerivedMachineId = (machineId: string): boolean => machineId.startsWith("card:");
|
|
23
23
|
|
|
24
|
+
// Which computer and which OS install on it is enrolling (`environmentKeyOf`), as the machine agent says it when it
|
|
25
|
+
// redeems a pairing. Both optional, since an agent older than either says nothing.
|
|
26
|
+
export const MachineIdentitySchema = z.object({
|
|
27
|
+
machineId: MachineIdSchema.optional(),
|
|
28
|
+
environment: z.string().min(1).optional(),
|
|
29
|
+
});
|
|
30
|
+
export type MachineIdentity = z.infer<typeof MachineIdentitySchema>;
|
|
31
|
+
|
|
32
|
+
// (2026-10-20) The body of POST /system/hosts/enroll: the redeeming agent's identity, so the enrollment is written with
|
|
33
|
+
// its real machine id instead of the card's derived one and joins that machine's sync row from its first write. Before
|
|
34
|
+
// this the route took no body and the id was learned only at the agent's first connect, which left a freshly paired
|
|
35
|
+
// computer as two machine cards until then. An agent older than it sends none, and the daemon reads one it cannot parse
|
|
36
|
+
// as none. Kept here rather than beside the answer (peer-enrollment.ts), which the browser extension bundles.
|
|
37
|
+
export const PeerEnrollmentRequestSchema = MachineIdentitySchema;
|
|
38
|
+
export type PeerEnrollmentRequest = MachineIdentity;
|
|
39
|
+
|
|
24
40
|
// How many skipped lines an agent sends in its upkeep summary at most; the counts cover the rest.
|
|
25
41
|
export const UPKEEP_SKIPPED_SHOWN = 10;
|
|
26
42
|
|
|
@@ -113,6 +129,11 @@ export type DeviceFacts = z.infer<typeof DeviceFactsSchema>;
|
|
|
113
129
|
// - `keyring`: the keyring* procedures, intentic-keyring on this computer reached through the machine (keyring.ts);
|
|
114
130
|
// advertised only while a keyring is installed, so a sandbox never offers a computer without one as a place to keep or
|
|
115
131
|
// type a secret.
|
|
132
|
+
// - `sleep`: the `sleep` op, a sandbox put to sleep now (`ic sandbox sleep <slug>`): stopped, and started again by
|
|
133
|
+
// itself when somebody opens it; advertised only when the agent's `ic` has `sandbox sleep`.
|
|
134
|
+
// - `reachable-ways-back`: not an op but what a listing means: a sandbox's `rollbackTargets` are only the ways back a
|
|
135
|
+
// rollback can reach (ic 2026-10-19 on). Said by ic itself (`ic features`, golden/ic-features.json); without it the
|
|
136
|
+
// rows are an older ic's, which also listed pruned dev builds no download can bring back (state/ways-back.ts).
|
|
116
137
|
export const DeviceFeatureSchema = z.enum([
|
|
117
138
|
"reshape-later",
|
|
118
139
|
"set-shape",
|
|
@@ -122,6 +143,8 @@ export const DeviceFeatureSchema = z.enum([
|
|
|
122
143
|
"project-delivery",
|
|
123
144
|
"programs",
|
|
124
145
|
"keyring",
|
|
146
|
+
"sleep",
|
|
147
|
+
"reachable-ways-back",
|
|
125
148
|
]);
|
|
126
149
|
export type DeviceFeature = z.infer<typeof DeviceFeatureSchema>;
|
|
127
150
|
export const DEVICE_FEATURE_RESHAPE_LATER: DeviceFeature = "reshape-later";
|
|
@@ -132,6 +155,12 @@ export const DEVICE_FEATURE_BACKGROUND_PREPARE: DeviceFeature = "background-prep
|
|
|
132
155
|
export const DEVICE_FEATURE_PROJECT_DELIVERY: DeviceFeature = "project-delivery";
|
|
133
156
|
export const DEVICE_FEATURE_PROGRAMS: DeviceFeature = "programs";
|
|
134
157
|
export const DEVICE_FEATURE_KEYRING: DeviceFeature = "keyring";
|
|
158
|
+
export const DEVICE_FEATURE_SLEEP: DeviceFeature = "sleep";
|
|
159
|
+
export const DEVICE_FEATURE_REACHABLE_WAYS_BACK: DeviceFeature = "reachable-ways-back";
|
|
160
|
+
// What `ic features --json` answers (_sandbox/ic/src/features.rs, golden/ic-features.json): the optional ops that ic
|
|
161
|
+
// implements, as strings, since a newer ic's word means nothing to an older agent.
|
|
162
|
+
export const IcFeaturesAnswerSchema = z.object({ features: z.array(z.string()) });
|
|
163
|
+
|
|
135
164
|
// The features an agent advertised that this build knows; an unknown one is a newer agent's and means nothing here.
|
|
136
165
|
export const deviceFeatures = (facts: Pick<DeviceFacts, "features"> | undefined): DeviceFeature[] =>
|
|
137
166
|
(facts?.features ?? []).flatMap((feature) => {
|
|
@@ -204,6 +233,34 @@ export const HostEnvironmentSchema = z.object({
|
|
|
204
233
|
});
|
|
205
234
|
export type HostEnvironment = z.infer<typeof HostEnvironmentSchema>;
|
|
206
235
|
|
|
236
|
+
// HOW MUCH OF ONE COMPUTER ANSWERS RIGHT NOW (2026-10-20), read off its environments' own sockets: the one answer
|
|
237
|
+
// /capabilities, the Devices board and a call's routing give about a machine. `online` while its native side answers
|
|
238
|
+
// (the one named after the card, which owns the screen and every tool but `run_command`'s crossing), whatever its
|
|
239
|
+
// distros do, since a distro asleep beside a live Windows is the ordinary state of a PC; `partly` while the native side
|
|
240
|
+
// does not and another environment does (a call naming that side still reaches it); `offline` while nothing does. Before
|
|
241
|
+
// this the card read the native side alone and the board any side, so one PC was offline on one screen and live on the
|
|
242
|
+
// next (2026-10-13), and "partly" was worked out twice more. A list with no native side at all (a machine seen only
|
|
243
|
+
// through a distro's sync) is online while anything of it answers.
|
|
244
|
+
export type MachinePresence =
|
|
245
|
+
| { readonly kind: "online" }
|
|
246
|
+
| { readonly kind: "partly"; readonly awake: readonly string[] }
|
|
247
|
+
| { readonly kind: "offline" };
|
|
248
|
+
|
|
249
|
+
export const machinePresence = (environments: readonly Pick<HostEnvironment, "key" | "online">[]): MachinePresence => {
|
|
250
|
+
const native = environments.find((environment) => environment.key === HOST_NATIVE_ENVIRONMENT);
|
|
251
|
+
const awake = environments
|
|
252
|
+
.filter((environment) => environment.key !== HOST_NATIVE_ENVIRONMENT && environment.online)
|
|
253
|
+
.map((environment) => environment.key)
|
|
254
|
+
.toSorted();
|
|
255
|
+
if (native?.online === true || (native === undefined && awake.length > 0)) {
|
|
256
|
+
return { kind: "online" };
|
|
257
|
+
}
|
|
258
|
+
return awake.length > 0 ? { kind: "partly", awake } : { kind: "offline" };
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// An environment by the name a person and `run_command`'s `in` know it by: a distro by its WSL name, the rest by key.
|
|
262
|
+
export const environmentName = (key: string): string => (key.startsWith("wsl:") ? key.slice("wsl:".length) : key);
|
|
263
|
+
|
|
207
264
|
export const HostSummarySchema = z.object({
|
|
208
265
|
// The capability id, the machine's name, and the prefix of its tools (mcp__<id>__run_command). On a per-connection
|
|
209
266
|
// row (`hostConnections`) it is that connection's key instead, and `card` names the machine.
|
|
@@ -221,6 +278,9 @@ export const HostSummarySchema = z.object({
|
|
|
221
278
|
// Epoch ms of the last held socket; absent means not since this daemon booted (liveness resets on restart).
|
|
222
279
|
lastSeen: z.number().optional(),
|
|
223
280
|
facts: DeviceFactsSchema.optional(),
|
|
281
|
+
// The card's "Manage sandboxes on this device" switch is on: a container verb sent to this machine (create, start,
|
|
282
|
+
// remove…) is let through rather than refused by the machine. Absent from a daemon older than it.
|
|
283
|
+
manages: z.boolean().optional(),
|
|
224
284
|
});
|
|
225
285
|
export type HostSummary = z.infer<typeof HostSummarySchema>;
|
|
226
286
|
export const HostsListSchema = z.object({ hosts: z.array(HostSummarySchema) });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { KEYRING_NAME_RE, KeyringCaptureOutcomeSchema, KeyringRefusalCodeSchema } from "./keyring.js";
|
|
2
|
+
import { KeyringNativeNameSchema, KeyringNativeOutcomeSchema, KeyringNativeRefusalCodeSchema } from "./keyring-native.js";
|
|
3
|
+
|
|
4
|
+
// keyring-native.ts spells three of keyring.ts's schemas again so the browser extension's bundle never reaches keyring.ts
|
|
5
|
+
// (and the zod namespace it imports); these hold the two spellings equal.
|
|
6
|
+
|
|
7
|
+
test("the extension's copies of keyring.ts's names, refusals and outcomes are the same", () => {
|
|
8
|
+
expect(KeyringNativeRefusalCodeSchema.options).toEqual(KeyringRefusalCodeSchema.options);
|
|
9
|
+
expect(KeyringNativeOutcomeSchema.options).toEqual(KeyringCaptureOutcomeSchema.options);
|
|
10
|
+
for (const name of ["GITHUB_COM", "_1PASSWORD", "a", "1BAD", "has-dash", ""]) {
|
|
11
|
+
expect(KeyringNativeNameSchema.safeParse(name).success).toBe(KEYRING_NAME_RE.test(name));
|
|
12
|
+
}
|
|
13
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Named imports rather than the `z` namespace, and nothing from schemas/keyring.ts: this module is bundled into the
|
|
2
|
+
// browser extension, where the namespace keeps zod's locales (~330 kB) that esbuild can otherwise drop
|
|
3
|
+
// (_devices/webext/scripts/size-budget.mjs holds the ceiling). The three schemas it shares with keyring.ts are spelled
|
|
4
|
+
// again below, and keyring-native.test.ts holds them equal.
|
|
5
|
+
import { array, boolean, discriminatedUnion, enum as zEnum, literal, number, object, string, union } from "zod";
|
|
6
|
+
import type * as z from "zod";
|
|
7
|
+
|
|
8
|
+
// What the browser extension and `intentic-keyring native-host` say to each other over Chrome's native messaging: one
|
|
9
|
+
// JSON message each way per request, length-prefixed by the browser, correlated by `id`. The native host is a process
|
|
10
|
+
// the browser starts as the person's user; it seals a captured value to the vault's inbox key before it reaches the
|
|
11
|
+
// keyring's pipe, and asks the keyring (which asks the person, with Windows Hello or polkit) before any value goes back
|
|
12
|
+
// to the page. A site's host, never a full address, is all the extension ever names.
|
|
13
|
+
//
|
|
14
|
+
// `@intentic/sandbox-contract/webext` re-exports it.
|
|
15
|
+
|
|
16
|
+
/** The name the extension connects to (`chrome.runtime.connectNative`), and the native host's manifest declares. */
|
|
17
|
+
export const KEYRING_NATIVE_HOST = "dev.intentic.keyring";
|
|
18
|
+
|
|
19
|
+
const id = string().min(1).max(64);
|
|
20
|
+
const host = string().min(1).max(253);
|
|
21
|
+
|
|
22
|
+
/** keyring.ts's secret name, refusal code and capture outcome, as the extension sees them. */
|
|
23
|
+
export const KeyringNativeNameSchema = string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,127}$/u);
|
|
24
|
+
export const KeyringNativeRefusalCodeSchema = zEnum([
|
|
25
|
+
"no-keyring",
|
|
26
|
+
"locked",
|
|
27
|
+
"declined",
|
|
28
|
+
"unknown",
|
|
29
|
+
"not-shared",
|
|
30
|
+
"bad-grant",
|
|
31
|
+
"expired",
|
|
32
|
+
"replayed",
|
|
33
|
+
"guard",
|
|
34
|
+
"unavailable",
|
|
35
|
+
"busy",
|
|
36
|
+
"pending",
|
|
37
|
+
]);
|
|
38
|
+
export const KeyringNativeOutcomeSchema = zEnum(["saved", "updated", "unchanged", "pending", "skipped"]);
|
|
39
|
+
|
|
40
|
+
export const KeyringNativeRequestSchema = discriminatedUnion("type", [
|
|
41
|
+
// What the extension needs before it shows anything: whether remembering is on for browsers, and the vault's state.
|
|
42
|
+
object({
|
|
43
|
+
type: literal("hello"),
|
|
44
|
+
id,
|
|
45
|
+
browser: string().min(1).max(100).describe("The browser's own name, `Brave`, for the title line."),
|
|
46
|
+
}).strict(),
|
|
47
|
+
// A sign-in, sign-up or password change the page just submitted.
|
|
48
|
+
object({
|
|
49
|
+
type: literal("capture"),
|
|
50
|
+
id,
|
|
51
|
+
browser: string().min(1).max(100),
|
|
52
|
+
host,
|
|
53
|
+
username: string().max(256).optional(),
|
|
54
|
+
password: string().min(1).max(4096),
|
|
55
|
+
title: string().max(200).optional().describe("The site's own name (`og:site_name`, the title), when it has one."),
|
|
56
|
+
failed: boolean().optional().describe("The form came back for the same user: the last value saved for it did not work."),
|
|
57
|
+
}).strict(),
|
|
58
|
+
// What the keyring holds for this site: names, titles and users, never values.
|
|
59
|
+
object({ type: literal("candidates"), id, host }).strict(),
|
|
60
|
+
// One entry's value for the focused field, after the person's yes at the computer.
|
|
61
|
+
object({ type: literal("fill"), id, host, name: KeyringNativeNameSchema }).strict(),
|
|
62
|
+
// Take back a capture made moments ago (the toast's Undo).
|
|
63
|
+
object({ type: literal("undo"), id, name: KeyringNativeNameSchema, at: number().int() }).strict(),
|
|
64
|
+
// Never save for this site (the toast's "Never for this site").
|
|
65
|
+
object({ type: literal("never"), id, host }).strict(),
|
|
66
|
+
]);
|
|
67
|
+
export type KeyringNativeRequest = z.infer<typeof KeyringNativeRequestSchema>;
|
|
68
|
+
|
|
69
|
+
const refused = object({ ok: literal(false), code: KeyringNativeRefusalCodeSchema, refusal: string() });
|
|
70
|
+
|
|
71
|
+
export const KeyringNativeCandidateSchema = object({ name: KeyringNativeNameSchema, title: string(), username: string().optional() });
|
|
72
|
+
export type KeyringNativeCandidate = z.infer<typeof KeyringNativeCandidateSchema>;
|
|
73
|
+
|
|
74
|
+
// A plain union: a refusal shares its `type` with the answer it stands in for, which a discriminated union cannot hold.
|
|
75
|
+
export const KeyringNativeResponseSchema = union([
|
|
76
|
+
object({
|
|
77
|
+
type: literal("hello"),
|
|
78
|
+
id,
|
|
79
|
+
ok: literal(true),
|
|
80
|
+
setUp: boolean(),
|
|
81
|
+
locked: boolean(),
|
|
82
|
+
remember: boolean().describe("Whether the person turned on remembering for browsers."),
|
|
83
|
+
version: string(),
|
|
84
|
+
}),
|
|
85
|
+
object({
|
|
86
|
+
type: literal("capture"),
|
|
87
|
+
id,
|
|
88
|
+
ok: literal(true),
|
|
89
|
+
outcome: KeyringNativeOutcomeSchema,
|
|
90
|
+
name: KeyringNativeNameSchema.optional(),
|
|
91
|
+
title: string().optional(),
|
|
92
|
+
at: number().int(),
|
|
93
|
+
}),
|
|
94
|
+
object({ type: literal("candidates"), id, ok: literal(true), items: array(KeyringNativeCandidateSchema) }),
|
|
95
|
+
object({ type: literal("fill"), id, ok: literal(true), username: string().optional(), password: string() }),
|
|
96
|
+
object({ type: literal("undo"), id, ok: literal(true) }),
|
|
97
|
+
object({ type: literal("never"), id, ok: literal(true) }),
|
|
98
|
+
// Any request refused, under the type it was asked as.
|
|
99
|
+
refused.extend({ type: zEnum(["hello", "capture", "candidates", "fill", "undo", "never"]), id }),
|
|
100
|
+
]);
|
|
101
|
+
export type KeyringNativeResponse = z.infer<typeof KeyringNativeResponseSchema>;
|
package/src/schemas/keyring.ts
CHANGED
|
@@ -82,15 +82,28 @@ export const KeyringSandboxKeysSchema = z.object({
|
|
|
82
82
|
});
|
|
83
83
|
export type KeyringSandboxKeys = z.infer<typeof KeyringSandboxKeysSchema>;
|
|
84
84
|
|
|
85
|
+
// The one-time "let Intentic remember the passwords you type", per source. All off until the person turns them on;
|
|
86
|
+
// each switches off on its own. Imports, an agent's `secrets ask` and adding by hand are deliberate acts and need none.
|
|
87
|
+
export const KeyringRememberSchema = z.object({
|
|
88
|
+
browser: z.boolean().describe("Sign-ins in a browser with the Intentic extension."),
|
|
89
|
+
terminal: z.boolean().describe("ssh, sudo and git prompts on this computer, and git's credential helper."),
|
|
90
|
+
apps: z.boolean().describe("Password fields in programs on this computer, typed once into the keyring's own box."),
|
|
91
|
+
phone: z.boolean().describe("The paired phone's autofill."),
|
|
92
|
+
sandbox: z.boolean().describe("Prompts that hide what is typed, in a sandbox terminal the person types into."),
|
|
93
|
+
});
|
|
94
|
+
export type KeyringRemember = z.infer<typeof KeyringRememberSchema>;
|
|
95
|
+
|
|
85
96
|
export const KeyringStateSchema = z.object({
|
|
86
97
|
version: z.string().describe("The keyring's own release."),
|
|
87
98
|
locked: z.boolean().describe("Whether the vault is locked. Locked, `while-here` entries ask like `each-use` ones."),
|
|
88
99
|
unlockedUntil: z.number().optional().describe("When the current unlock ends, in milliseconds. Absent while locked."),
|
|
89
100
|
unlock: z
|
|
90
|
-
.array(z.enum(["hello", "passphrase"]))
|
|
91
|
-
.describe("How a person unlocks it here: Windows Hello,
|
|
101
|
+
.array(z.enum(["hello", "passphrase", "polkit"]))
|
|
102
|
+
.describe("How a person unlocks it here: Windows Hello, the keyring's passphrase (its recovery key), or polkit's own check on Linux."),
|
|
92
103
|
keys: KeyringDeviceKeysSchema.optional().describe("Absent until the keyring is set up: there is no vault, so no inbox to seal a value to."),
|
|
93
104
|
pendingInbox: z.number().int().nonnegative().describe("Values sent to be kept that wait for the next unlock."),
|
|
105
|
+
pendingCaptures: z.number().int().nonnegative().optional().describe("Credentials seen in use while locked, kept sealed until the next unlock. Absent from a keyring older than 2026-10-19."),
|
|
106
|
+
remember: KeyringRememberSchema.optional().describe("Which sources the person let the keyring remember passwords from, so a collector knows whether to send one. Absent before setup and from an older keyring."),
|
|
94
107
|
});
|
|
95
108
|
export type KeyringState = z.infer<typeof KeyringStateSchema>;
|
|
96
109
|
|
|
@@ -173,9 +186,9 @@ export const SealedBoxSchema = z
|
|
|
173
186
|
export type SealedBox = z.infer<typeof SealedBoxSchema>;
|
|
174
187
|
|
|
175
188
|
export const KeyringRefusalCodeSchema = z
|
|
176
|
-
.enum(["no-keyring", "locked", "declined", "unknown", "not-shared", "bad-grant", "expired", "replayed", "guard", "unavailable", "busy"])
|
|
189
|
+
.enum(["no-keyring", "locked", "declined", "unknown", "not-shared", "bad-grant", "expired", "replayed", "guard", "unavailable", "busy", "pending"])
|
|
177
190
|
.describe(
|
|
178
|
-
"Why: no keyring here; locked with nobody to unlock it; the person said no; no such entry; not shared with this sandbox; a grant that does not verify; one past its time; one already spent; the focused window or field is not one it may go to; its password manager did not answer; another prompt is open.",
|
|
191
|
+
"Why: no keyring here; locked with nobody to unlock it; the person said no; no such entry; not shared with this sandbox; a grant that does not verify; one past its time; one already spent; the focused window or field is not one it may go to; its password manager did not answer; another prompt is open; nobody has answered yet, and the request waits in the Passwords app.",
|
|
179
192
|
);
|
|
180
193
|
export type KeyringRefusalCode = z.infer<typeof KeyringRefusalCodeSchema>;
|
|
181
194
|
|
|
@@ -275,3 +288,188 @@ export const KeyringSyncPairResultSchema = z.discriminatedUnion("ok", [z.object(
|
|
|
275
288
|
export const KeyringSyncOutboxSchema = z.object({ items: z.array(KeyringSyncItemSchema) });
|
|
276
289
|
export const KeyringSyncDeliverSchema = z.object({ items: z.array(KeyringSyncItemSchema).max(64) }).strict();
|
|
277
290
|
export const KeyringSyncDeliverResultSchema = z.object({ accepted: z.number().int().nonnegative().describe("How many it took; the rest were not from a paired keyring or did not open.") });
|
|
291
|
+
|
|
292
|
+
// ---- origins: where a credential is used, and where it was seen ----
|
|
293
|
+
|
|
294
|
+
// One place a credential belongs to. An entry's rules (programs, packages, hosts, askpass matchers) are the keyring's
|
|
295
|
+
// own record of the same thing; policy/keyring-origins.ts maps one onto the other, so a captured credential gets its
|
|
296
|
+
// rules without anyone typing them and the Passwords app can say "Works on github.com, git https://github.com".
|
|
297
|
+
export const KeyringOriginSchema = z.discriminatedUnion("kind", [
|
|
298
|
+
z.object({ kind: z.literal("site"), host: z.string().min(1).max(253).describe("The page's host, `github.com`; never a path.") }).strict(),
|
|
299
|
+
z
|
|
300
|
+
.object({
|
|
301
|
+
kind: z.literal("program"),
|
|
302
|
+
program: KeyringProgramSchema,
|
|
303
|
+
label: z.string().max(200).optional().describe("The program's own name for people, `FortiClient VPN`, when the window says it."),
|
|
304
|
+
})
|
|
305
|
+
.strict(),
|
|
306
|
+
z.object({ kind: z.literal("package"), package: KeyringPackageSchema, label: z.string().max(200).optional() }).strict(),
|
|
307
|
+
z.object({ kind: z.literal("ssh"), target: z.string().min(1).max(300).describe("`user@host`, as ssh's prompt names it.") }).strict(),
|
|
308
|
+
z.object({ kind: z.literal("ssh-key"), path: z.string().min(1).max(1000) }).strict(),
|
|
309
|
+
z.object({ kind: z.literal("sudo"), user: z.string().max(64).optional() }).strict(),
|
|
310
|
+
z.object({ kind: z.literal("git"), url: z.string().min(1).max(500).describe("`https://host`, as git's credential helper or askpass names it.") }).strict(),
|
|
311
|
+
z
|
|
312
|
+
.object({
|
|
313
|
+
kind: z.literal("prompt"),
|
|
314
|
+
text: z.string().min(1).max(300).describe("The prompt's own words, `Password for npm:`."),
|
|
315
|
+
command: z.string().max(200).optional().describe("The command that asked, `npm login`, when known."),
|
|
316
|
+
sandbox: z.string().url().optional().describe("The sandbox whose terminal asked, for a prompt seen there rather than on this computer."),
|
|
317
|
+
})
|
|
318
|
+
.strict(),
|
|
319
|
+
]);
|
|
320
|
+
export type KeyringOrigin = z.infer<typeof KeyringOriginSchema>;
|
|
321
|
+
|
|
322
|
+
// Where a capture came from, for its title line ("saved from Brave") and its record.
|
|
323
|
+
export const KeyringCaptureSourceSchema = z
|
|
324
|
+
.enum(["browser", "askpass", "git", "app", "phone", "terminal", "import", "agent", "manual"])
|
|
325
|
+
.describe(
|
|
326
|
+
"What saw it: the browser extension, an ssh/sudo/git prompt answered through the keyring, git's credential helper, a password field in an app on this computer, the phone's autofill, a sandbox terminal's prompt, an import, an agent's `secrets ask`, or the person adding it by hand.",
|
|
327
|
+
);
|
|
328
|
+
export type KeyringCaptureSource = z.infer<typeof KeyringCaptureSourceSchema>;
|
|
329
|
+
|
|
330
|
+
// A credential seen in use, sent to be kept. The value is sealed to the vault's inbox key for `{ kind: "capture" }`
|
|
331
|
+
// (policy/keyring-seal.ts), so whoever carries it (the native-messaging host, the sandbox for a phone or a terminal)
|
|
332
|
+
// holds ciphertext. Taken in without asking once the person turned remembering on for its source: bringing a value IN
|
|
333
|
+
// reveals nothing, and an update keeps the old value in the entry's history, so a capture can be undone.
|
|
334
|
+
export const KEYRING_CAPTURE_BATCH_MAX = 5000;
|
|
335
|
+
export const KeyringCaptureItemSchema = z
|
|
336
|
+
.object({
|
|
337
|
+
origin: KeyringOriginSchema,
|
|
338
|
+
username: z.string().max(256).optional().describe("The account it signs in as, when the form or prompt said. Not a secret."),
|
|
339
|
+
sealed: SealedBoxSchema,
|
|
340
|
+
source: KeyringCaptureSourceSchema,
|
|
341
|
+
via: z.string().min(1).max(200).describe("Who saw it, in words for the title line: `Brave`, `Pixel 8`, `intentic-dev`. Also bound into the seal."),
|
|
342
|
+
kind: z.enum(["password", "text"]).default("password"),
|
|
343
|
+
title: z.string().max(200).optional().describe("The site's or app's own name, when it said one; the keyring derives one otherwise."),
|
|
344
|
+
at: z.number().int().describe("When it was seen, in milliseconds. Also bound into the seal."),
|
|
345
|
+
failed: z
|
|
346
|
+
.boolean()
|
|
347
|
+
.optional()
|
|
348
|
+
.describe("The last value captured for this origin and user did not work (ssh asked again, git erased it, the form came back): replace it rather than keep it as history."),
|
|
349
|
+
})
|
|
350
|
+
.strict();
|
|
351
|
+
export type KeyringCaptureItem = z.infer<typeof KeyringCaptureItemSchema>;
|
|
352
|
+
|
|
353
|
+
export const KeyringCaptureOutcomeSchema = z
|
|
354
|
+
.enum(["saved", "updated", "unchanged", "pending", "skipped"])
|
|
355
|
+
.describe(
|
|
356
|
+
"A new entry; a new value for an existing one (the old kept in its history); the same value it already had; kept sealed until the next unlock; not kept, because remembering is off for its source or its origin is on the never-save list.",
|
|
357
|
+
);
|
|
358
|
+
export type KeyringCaptureOutcome = z.infer<typeof KeyringCaptureOutcomeSchema>;
|
|
359
|
+
|
|
360
|
+
export const KeyringCaptureResultSchema = z.discriminatedUnion("ok", [
|
|
361
|
+
z.object({
|
|
362
|
+
ok: z.literal(true),
|
|
363
|
+
outcome: KeyringCaptureOutcomeSchema,
|
|
364
|
+
name: KeyringSecretNameSchema.optional().describe("The entry it went to. Absent while pending or skipped."),
|
|
365
|
+
title: z.string().optional().describe("That entry's title, for the 'Saved github.com' notice."),
|
|
366
|
+
}),
|
|
367
|
+
refusal,
|
|
368
|
+
]);
|
|
369
|
+
export type KeyringCaptureResult = z.infer<typeof KeyringCaptureResultSchema>;
|
|
370
|
+
|
|
371
|
+
// ---- an agent's need: asked for by what it is for, not by name ----
|
|
372
|
+
|
|
373
|
+
// A sandbox asking for a credential by where it is to be used. The keyring matches its entries by origin and asks the
|
|
374
|
+
// person (with their OS's own check: Windows Hello, polkit, a phone's biometric signature) whether this sandbox may use
|
|
375
|
+
// the one it found, and on what terms. Nothing about entries that do not match, nor about one the person declines,
|
|
376
|
+
// reaches the sandbox. A match nobody answers waits in the Passwords app's Requests, and the sandbox learns of the yes
|
|
377
|
+
// from its next `shares` read.
|
|
378
|
+
export const KeyringNeedSchema = z
|
|
379
|
+
.object({
|
|
380
|
+
origin: KeyringOriginSchema,
|
|
381
|
+
username: z.string().max(256).optional().describe("The account wanted, when the agent knows it."),
|
|
382
|
+
detail: z.string().min(1).max(300).describe("What it is for, in words for the prompt: `sign in to push the release branch`."),
|
|
383
|
+
conversation: z.string().max(200).optional(),
|
|
384
|
+
purpose: z.enum(["agent", "owner"]).default("agent"),
|
|
385
|
+
typeOnce: z
|
|
386
|
+
.boolean()
|
|
387
|
+
.default(false)
|
|
388
|
+
.describe(
|
|
389
|
+
"Nothing matched on any of the owner's computers: offer the person here the keyring's type-once box. Without it, a keyring with no match answers `unknown` at once, so the sandbox can ask the next computer without a box opening on each.",
|
|
390
|
+
),
|
|
391
|
+
})
|
|
392
|
+
.strict();
|
|
393
|
+
export type KeyringNeed = z.infer<typeof KeyringNeedSchema>;
|
|
394
|
+
|
|
395
|
+
export const KeyringNeedAnswerSchema = z
|
|
396
|
+
.enum(["once", "while-here", "always", "deny"])
|
|
397
|
+
.describe("The person's answer: this one use (an `each-use` share), unasked while unlocked, a copy in the sandbox, or no.");
|
|
398
|
+
export type KeyringNeedAnswer = z.infer<typeof KeyringNeedAnswerSchema>;
|
|
399
|
+
|
|
400
|
+
export const KeyringNeedResultSchema = z.discriminatedUnion("ok", [
|
|
401
|
+
z.object({
|
|
402
|
+
ok: z.literal(true),
|
|
403
|
+
pointer: KeyringPointerSchema.describe("The entry now shared with the asking sandbox, as its next `shares` read will also list it."),
|
|
404
|
+
asked: z.boolean(),
|
|
405
|
+
saved: z.boolean().describe("Nothing matched, and the person typed it once into the keyring's own box: a new entry, kept here and shared."),
|
|
406
|
+
}),
|
|
407
|
+
refusal,
|
|
408
|
+
]);
|
|
409
|
+
export type KeyringNeedResult = z.infer<typeof KeyringNeedResultSchema>;
|
|
410
|
+
|
|
411
|
+
// A need that waits for an answer, as the Passwords app lists it.
|
|
412
|
+
export const KeyringPendingNeedSchema = z.object({
|
|
413
|
+
id: z.string().regex(KEYRING_NONCE_RE),
|
|
414
|
+
sandbox: z.string().url(),
|
|
415
|
+
need: KeyringNeedSchema,
|
|
416
|
+
matches: z.array(KeyringSecretNameSchema).describe("The entries that fit, best first. Empty: none yet, the app offers to type one once."),
|
|
417
|
+
at: z.number().int(),
|
|
418
|
+
});
|
|
419
|
+
export type KeyringPendingNeed = z.infer<typeof KeyringPendingNeedSchema>;
|
|
420
|
+
|
|
421
|
+
// ---- the owner's phone as a place to save and fill ----
|
|
422
|
+
|
|
423
|
+
// A phone paired with the keyring holds two keys made in its Android Keystore: `seal` (X25519, kept in software, which
|
|
424
|
+
// Android's Keystore cannot do natively) that a value released for autofill is sealed to, and `presence` (ECDSA P-256,
|
|
425
|
+
// `setUserAuthenticationRequired`, so it signs only after the person's fingerprint, face or screen lock). A signature by
|
|
426
|
+
// `presence` over a fill request is the person's yes, checked by the keyring against the key it pinned at pairing, the
|
|
427
|
+
// way a Windows Hello signature is. Pairing is carried by a sandbox both are linked to, with both fingerprints shown.
|
|
428
|
+
export const KeyringPhoneKeysSchema = z
|
|
429
|
+
.object({
|
|
430
|
+
device: z.string().min(1).max(128).describe("The phone, by its device card."),
|
|
431
|
+
label: z.string().min(1).max(200).describe("The phone's name for people, `Pixel 8`."),
|
|
432
|
+
seal: KeyringPublicKeySchema,
|
|
433
|
+
presence: z.string().min(1).max(512).describe("The presence key, X.509 SubjectPublicKeyInfo DER, base64."),
|
|
434
|
+
})
|
|
435
|
+
.strict();
|
|
436
|
+
export type KeyringPhoneKeys = z.infer<typeof KeyringPhoneKeysSchema>;
|
|
437
|
+
|
|
438
|
+
export const KeyringPhonePairResultSchema = z.discriminatedUnion("ok", [
|
|
439
|
+
z.object({ ok: z.literal(true), fingerprint: z.string().describe("The keyring's own fingerprint, for the phone to show beside its own.") }),
|
|
440
|
+
refusal,
|
|
441
|
+
]);
|
|
442
|
+
export type KeyringPhonePairResult = z.infer<typeof KeyringPhonePairResultSchema>;
|
|
443
|
+
|
|
444
|
+
export const KeyringPhoneFillSchema = z
|
|
445
|
+
.object({
|
|
446
|
+
v: z.literal(1),
|
|
447
|
+
device: z.string().min(1).max(128),
|
|
448
|
+
origin: KeyringOriginSchema.describe("Where the phone is filling: a `package`, or a `site` for a browser on the phone."),
|
|
449
|
+
username: z.string().max(256).optional(),
|
|
450
|
+
nonce: z.string().regex(KEYRING_NONCE_RE),
|
|
451
|
+
issuedAt: z.number().int(),
|
|
452
|
+
expiresAt: z.number().int(),
|
|
453
|
+
})
|
|
454
|
+
.strict();
|
|
455
|
+
export type KeyringPhoneFill = z.infer<typeof KeyringPhoneFillSchema>;
|
|
456
|
+
|
|
457
|
+
export const SignedKeyringPhoneFillSchema = z
|
|
458
|
+
.object({
|
|
459
|
+
payload: z.string().min(1).max(4096).describe("The fill request's JSON, base64url."),
|
|
460
|
+
sig: z.string().min(1).max(256).describe("The presence key's ECDSA P-256 signature (DER) over the payload, base64url."),
|
|
461
|
+
})
|
|
462
|
+
.strict();
|
|
463
|
+
export type SignedKeyringPhoneFill = z.infer<typeof SignedKeyringPhoneFillSchema>;
|
|
464
|
+
|
|
465
|
+
export const KeyringPhoneFillResultSchema = z.discriminatedUnion("ok", [
|
|
466
|
+
z.object({
|
|
467
|
+
ok: z.literal(true),
|
|
468
|
+
username: z.string().max(256).optional(),
|
|
469
|
+
sealed: SealedBoxSchema.describe("The value, sealed to the phone's `seal` key for `{ kind: \"phone\" }`."),
|
|
470
|
+
name: KeyringSecretNameSchema,
|
|
471
|
+
title: z.string(),
|
|
472
|
+
}),
|
|
473
|
+
refusal,
|
|
474
|
+
]);
|
|
475
|
+
export type KeyringPhoneFillResult = z.infer<typeof KeyringPhoneFillResultSchema>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { environmentName, machinePresence } from "./hosts.js";
|
|
2
|
+
|
|
3
|
+
// One answer to "does this computer answer", read by /capabilities, the Devices board and a call's routing alike
|
|
4
|
+
// (2026-10-13/15: one PC was offline on the card, live on the board, and partly connected by a third reading).
|
|
5
|
+
|
|
6
|
+
const side = (key: string, online: boolean) => ({ key, online });
|
|
7
|
+
|
|
8
|
+
test("a PC whose native side answers is online, whatever its distros do", () => {
|
|
9
|
+
expect(machinePresence([side("native", true), side("wsl:archlinux", false)])).toEqual({ kind: "online" });
|
|
10
|
+
expect(machinePresence([side("native", true), side("wsl:archlinux", true)])).toEqual({ kind: "online" });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("a PC answering only through its distros is partly connected, and names them in order", () => {
|
|
14
|
+
expect(machinePresence([side("native", false), side("wsl:ubuntu", true), side("wsl:archlinux", true)])).toEqual({
|
|
15
|
+
kind: "partly",
|
|
16
|
+
awake: ["wsl:archlinux", "wsl:ubuntu"],
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("a computer nothing of which answers is offline", () => {
|
|
21
|
+
expect(machinePresence([side("native", false), side("wsl:archlinux", false)])).toEqual({ kind: "offline" });
|
|
22
|
+
expect(machinePresence([])).toEqual({ kind: "offline" });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// A machine seen only through a distro's sync has no native side to be missing.
|
|
26
|
+
test("a computer with no native side is online while anything of it answers", () => {
|
|
27
|
+
expect(machinePresence([side("wsl:archlinux", true)])).toEqual({ kind: "online" });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("names a distro the way WSL and `in` do", () => {
|
|
31
|
+
expect(environmentName("wsl:archlinux")).toBe("archlinux");
|
|
32
|
+
expect(environmentName("native")).toBe("native");
|
|
33
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
KeyringCaptureItemSchema,
|
|
4
|
+
KeyringCaptureResultSchema,
|
|
5
|
+
KeyringPhoneFillResultSchema,
|
|
6
|
+
KeyringPhoneKeysSchema,
|
|
7
|
+
KeyringPhonePairResultSchema,
|
|
8
|
+
KeyringPublicKeySchema,
|
|
9
|
+
SignedKeyringPhoneFillSchema,
|
|
10
|
+
} from "./keyring.js";
|
|
11
|
+
|
|
12
|
+
// THE PHONE'S WAY TO THE OWNER'S PASSWORDS (2026-10-19): the Intentic Device app as Android's autofill service, saving
|
|
13
|
+
// what the person signs in with and filling it back, against the keyrings on the owner's computers. The phone door is
|
|
14
|
+
// the sandbox asking and the phone answering (phone-protocol.ts); these go the other way, so they are plain HTTPS
|
|
15
|
+
// requests from the app to its sandbox, authorised by the phone's enrollment token (`Authorization: Bearer <token>`),
|
|
16
|
+
// and the sandbox relays each to the named computer's keyring over that computer's device link. What rides is sealed
|
|
17
|
+
// end to end: a capture to the keyring's inbox key, a fill's answer to the phone's own seal key.
|
|
18
|
+
|
|
19
|
+
export const PHONE_PASSWORDS_PATHS = {
|
|
20
|
+
keyrings: "/system/phones/passwords/keyrings",
|
|
21
|
+
pair: "/system/phones/passwords/pair",
|
|
22
|
+
capture: "/system/phones/passwords/capture",
|
|
23
|
+
fill: "/system/phones/passwords/fill",
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
/** One of the owner's computers whose keyring the phone can save to and fill from. */
|
|
27
|
+
export const PhoneKeyringSchema = z.object({
|
|
28
|
+
device: z.string().min(1).max(128).describe("The computer, by its device card."),
|
|
29
|
+
label: z.string().describe("The computer's name for people."),
|
|
30
|
+
online: z.boolean(),
|
|
31
|
+
locked: z.boolean().optional(),
|
|
32
|
+
inbox: KeyringPublicKeySchema.optional().describe("What a capture is sealed to. Absent until the keyring is set up."),
|
|
33
|
+
remember: z.boolean().describe("Whether the person turned on remembering from the phone on that computer."),
|
|
34
|
+
paired: z.boolean().describe("Whether this phone's keys are pinned there, so it can fill."),
|
|
35
|
+
});
|
|
36
|
+
export type PhoneKeyring = z.infer<typeof PhoneKeyringSchema>;
|
|
37
|
+
|
|
38
|
+
// GET keyrings → the owner's keyrings this sandbox can reach.
|
|
39
|
+
export const PhoneKeyringsResultSchema = z.object({ keyrings: z.array(PhoneKeyringSchema) });
|
|
40
|
+
|
|
41
|
+
// POST pair → the computer's keyring asks its person, showing the phone's fingerprint; the phone shows the keyring's.
|
|
42
|
+
export const PhonePasswordsPairSchema = z.object({ keyring: z.string().min(1).max(128), phone: KeyringPhoneKeysSchema }).strict();
|
|
43
|
+
export const PhonePasswordsPairResultSchema = KeyringPhonePairResultSchema;
|
|
44
|
+
|
|
45
|
+
// POST capture → onSaveRequest's credential, sealed on the phone for `{ kind: "capture", via, at }`.
|
|
46
|
+
export const PhonePasswordsCaptureSchema = z.object({ keyring: z.string().min(1).max(128), item: KeyringCaptureItemSchema }).strict();
|
|
47
|
+
export const PhonePasswordsCaptureResultSchema = KeyringCaptureResultSchema;
|
|
48
|
+
|
|
49
|
+
// POST fill → after the person's fingerprint signed the request with the presence key.
|
|
50
|
+
export const PhonePasswordsFillSchema = z.object({ keyring: z.string().min(1).max(128), fill: SignedKeyringPhoneFillSchema }).strict();
|
|
51
|
+
export const PhonePasswordsFillResultSchema = KeyringPhoneFillResultSchema;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { DEFAULT_PRIVACY_SHIELD, type PrivacyShieldMode, type PrivacyShieldPolicy, withConversationTrust } from "./privacy.js";
|
|
2
|
+
import { isPrivacyRefusal, ownerTrusted, type PrivacyEgress, type PrivacyRoute, shieldVerdict, turnRoute } from "./privacy-egress.js";
|
|
3
|
+
|
|
4
|
+
// One verdict for every place bytes leave for a model, and for the editor's word before a send. Each scenario the shield
|
|
5
|
+
// once got wrong at one of those places is a row here, so a place that reads the verdict cannot get it wrong again.
|
|
6
|
+
|
|
7
|
+
const policy = (mode: PrivacyShieldMode, extra: Partial<PrivacyShieldPolicy> = {}): PrivacyShieldPolicy => ({ ...DEFAULT_PRIVACY_SHIELD, mode, ...extra });
|
|
8
|
+
const egress = (route: PrivacyRoute, extra: Partial<PrivacyEgress> = {}): PrivacyEgress => ({ provider: "cursor", local: false, route, ...extra });
|
|
9
|
+
|
|
10
|
+
const ROUTES: readonly PrivacyRoute[] = ["gateway", "hooks", "sealed", "runtime", "runner", "own-key", "shared-server"];
|
|
11
|
+
|
|
12
|
+
describe(`shieldVerdict`, () => {
|
|
13
|
+
it(`passes everything while the shield is off, and only reads while it watches`, () => {
|
|
14
|
+
for (const route of ROUTES) {
|
|
15
|
+
expect(shieldVerdict(policy(`off`), egress(route))).toEqual({ kind: `pass`, because: `off` });
|
|
16
|
+
}
|
|
17
|
+
expect(ROUTES.map((route) => shieldVerdict(policy(`watch`), egress(route)).kind)).toEqual([
|
|
18
|
+
`watch`,
|
|
19
|
+
`watch`,
|
|
20
|
+
`watch`,
|
|
21
|
+
`pass`,
|
|
22
|
+
`pass`,
|
|
23
|
+
`pass`,
|
|
24
|
+
`pass`,
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it(`masks every seam and refuses every bypass while it is on`, () => {
|
|
29
|
+
expect(ROUTES.map((route) => shieldVerdict(policy(`on`), egress(route)))).toEqual([
|
|
30
|
+
{ kind: `mask` },
|
|
31
|
+
{ kind: `mask` },
|
|
32
|
+
{ kind: `mask` },
|
|
33
|
+
{ kind: `refuse`, route: `runtime` },
|
|
34
|
+
{ kind: `refuse`, route: `runner` },
|
|
35
|
+
{ kind: `refuse`, route: `own-key` },
|
|
36
|
+
{ kind: `refuse`, route: `shared-server` },
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it(`passes a local model, a trusted provider and a granted conversation on every route`, () => {
|
|
41
|
+
const granted = withConversationTrust(policy(`on`), `c-1`, `cursor`, true);
|
|
42
|
+
for (const route of ROUTES) {
|
|
43
|
+
expect(shieldVerdict(policy(`on`), egress(route, { local: true }))).toEqual({ kind: `pass`, because: `local` });
|
|
44
|
+
expect(shieldVerdict(policy(`on`, { trusted: [`cursor`] }), egress(route))).toEqual({ kind: `pass`, because: `trusted` });
|
|
45
|
+
expect(shieldVerdict(granted, egress(route, { conversationId: `c-1` }))).toEqual({ kind: `pass`, because: `granted` });
|
|
46
|
+
// The grant is that conversation's alone, and a request outside any conversation (a helper) has none.
|
|
47
|
+
expect(shieldVerdict(granted, egress(route, { conversationId: `c-2` })).kind).not.toBe(`pass`);
|
|
48
|
+
expect(shieldVerdict(granted, egress(route)).kind).not.toBe(`pass`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// grand-mesa-dsjg (2026-10-07): commit messages on Composer were refused before a word was read, while a turn on the
|
|
53
|
+
// same provider ran. A helper's prompt is sealed, so it is read and masked like a turn's, never refused for its runtime.
|
|
54
|
+
it(`reads a helper's sealed prompt exactly as a hooked turn is read`, () => {
|
|
55
|
+
expect(shieldVerdict(policy(`on`), egress(`sealed`))).toEqual(shieldVerdict(policy(`on`), egress(`hooks`)));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// calm-lichen-5wrn (2026-10-08): opening a Cursor composer warned before anything was sent. A hooked runtime is a seam.
|
|
59
|
+
// deft-shale-590s (2026-10-11): the same conversation on a runner was refused only after the send.
|
|
60
|
+
it(`refuses a turn by its placement, not by its runtime`, () => {
|
|
61
|
+
expect(shieldVerdict(policy(`on`), egress(turnRoute(`hooks`)))).toEqual({ kind: `mask` });
|
|
62
|
+
expect(shieldVerdict(policy(`on`), egress(turnRoute(`hooks`, `omen`)))).toEqual({ kind: `refuse`, route: `runner` });
|
|
63
|
+
expect(shieldVerdict(policy(`on`), egress(turnRoute(`gateway`, `omen`)))).toEqual({ kind: `refuse`, route: `runner` });
|
|
64
|
+
expect(shieldVerdict(policy(`on`), egress(turnRoute(`none`)))).toEqual({ kind: `refuse`, route: `runtime` });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it(`calls only the owner's own choices trusted`, () => {
|
|
68
|
+
expect(ownerTrusted({ kind: `pass`, because: `granted` })).toBe(true);
|
|
69
|
+
expect(ownerTrusted({ kind: `pass`, because: `local` })).toBe(true);
|
|
70
|
+
expect(ownerTrusted({ kind: `pass`, because: `off` })).toBe(false);
|
|
71
|
+
expect(ownerTrusted({ kind: `pass`, because: `unwatched` })).toBe(false);
|
|
72
|
+
expect(ownerTrusted({ kind: `mask` })).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe(`isPrivacyRefusal`, () => {
|
|
77
|
+
it(`knows the shield's own refusals and nothing else`, () => {
|
|
78
|
+
expect(isPrivacyRefusal(`privacy-unshielded`)).toBe(true);
|
|
79
|
+
expect(isPrivacyRefusal(`privacy-instructions`)).toBe(true);
|
|
80
|
+
expect(isPrivacyRefusal(`model-unavailable`)).toBe(false);
|
|
81
|
+
expect(isPrivacyRefusal(undefined)).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
});
|