@cuna_labs/cli 0.1.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/LICENSE +201 -0
- package/NOTICE +9 -0
- package/README.md +169 -0
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/dist/api/capability-evidence.d.ts +19 -0
- package/dist/api/capability-evidence.d.ts.map +1 -0
- package/dist/api/capability-evidence.js +38 -0
- package/dist/api/capability-evidence.js.map +1 -0
- package/dist/api/client.d.ts +167 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +937 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/contracts.d.ts +345 -0
- package/dist/api/contracts.d.ts.map +1 -0
- package/dist/api/contracts.js +976 -0
- package/dist/api/contracts.js.map +1 -0
- package/dist/api/execution-workspaces.d.ts +17 -0
- package/dist/api/execution-workspaces.d.ts.map +1 -0
- package/dist/api/execution-workspaces.js +46 -0
- package/dist/api/execution-workspaces.js.map +1 -0
- package/dist/api/http.d.ts +76 -0
- package/dist/api/http.d.ts.map +1 -0
- package/dist/api/http.js +838 -0
- package/dist/api/http.js.map +1 -0
- package/dist/api/managed-executions.d.ts +37 -0
- package/dist/api/managed-executions.d.ts.map +1 -0
- package/dist/api/managed-executions.js +80 -0
- package/dist/api/managed-executions.js.map +1 -0
- package/dist/api/observer-v2-schema.d.ts +206 -0
- package/dist/api/observer-v2-schema.d.ts.map +1 -0
- package/dist/api/observer-v2-schema.js +264 -0
- package/dist/api/observer-v2-schema.js.map +1 -0
- package/dist/api/observer-v2.d.ts +33 -0
- package/dist/api/observer-v2.d.ts.map +1 -0
- package/dist/api/observer-v2.js +60 -0
- package/dist/api/observer-v2.js.map +1 -0
- package/dist/api/owner-observe-grants-v2-schema.d.ts +678 -0
- package/dist/api/owner-observe-grants-v2-schema.d.ts.map +1 -0
- package/dist/api/owner-observe-grants-v2-schema.js +887 -0
- package/dist/api/owner-observe-grants-v2-schema.js.map +1 -0
- package/dist/api/owner-observe-grants-v2.d.ts +268 -0
- package/dist/api/owner-observe-grants-v2.d.ts.map +1 -0
- package/dist/api/owner-observe-grants-v2.js +604 -0
- package/dist/api/owner-observe-grants-v2.js.map +1 -0
- package/dist/api/provider-v2-schema.d.ts +293 -0
- package/dist/api/provider-v2-schema.d.ts.map +1 -0
- package/dist/api/provider-v2-schema.js +381 -0
- package/dist/api/provider-v2-schema.js.map +1 -0
- package/dist/api/provider-v2.d.ts +55 -0
- package/dist/api/provider-v2.d.ts.map +1 -0
- package/dist/api/provider-v2.js +54 -0
- package/dist/api/provider-v2.js.map +1 -0
- package/dist/api/remote-workspace.d.ts +29 -0
- package/dist/api/remote-workspace.d.ts.map +1 -0
- package/dist/api/remote-workspace.js +62 -0
- package/dist/api/remote-workspace.js.map +1 -0
- package/dist/api/supervisor-live-update.d.ts +248 -0
- package/dist/api/supervisor-live-update.d.ts.map +1 -0
- package/dist/api/supervisor-live-update.js +434 -0
- package/dist/api/supervisor-live-update.js.map +1 -0
- package/dist/api/terminal-reason.d.ts +5 -0
- package/dist/api/terminal-reason.d.ts.map +1 -0
- package/dist/api/terminal-reason.js +12 -0
- package/dist/api/terminal-reason.js.map +1 -0
- package/dist/auth/browser-handoff.d.ts +59 -0
- package/dist/auth/browser-handoff.d.ts.map +1 -0
- package/dist/auth/browser-handoff.js +33 -0
- package/dist/auth/browser-handoff.js.map +1 -0
- package/dist/auth/browser.d.ts +11 -0
- package/dist/auth/browser.d.ts.map +1 -0
- package/dist/auth/browser.js +47 -0
- package/dist/auth/browser.js.map +1 -0
- package/dist/auth/human-client.d.ts +39 -0
- package/dist/auth/human-client.d.ts.map +1 -0
- package/dist/auth/human-client.js +66 -0
- package/dist/auth/human-client.js.map +1 -0
- package/dist/auth/human-contracts.d.ts +52 -0
- package/dist/auth/human-contracts.d.ts.map +1 -0
- package/dist/auth/human-contracts.js +229 -0
- package/dist/auth/human-contracts.js.map +1 -0
- package/dist/auth/human-session.d.ts +48 -0
- package/dist/auth/human-session.d.ts.map +1 -0
- package/dist/auth/human-session.js +902 -0
- package/dist/auth/human-session.js.map +1 -0
- package/dist/auth/onboarding-state.d.ts +31 -0
- package/dist/auth/onboarding-state.d.ts.map +1 -0
- package/dist/auth/onboarding-state.js +52 -0
- package/dist/auth/onboarding-state.js.map +1 -0
- package/dist/auth/pkce.d.ts +15 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +19 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/bin/cuna.d.ts +3 -0
- package/dist/bin/cuna.d.ts.map +1 -0
- package/dist/bin/cuna.js +4 -0
- package/dist/bin/cuna.js.map +1 -0
- package/dist/build-identity.d.ts +20 -0
- package/dist/build-identity.d.ts.map +1 -0
- package/dist/build-identity.js +77 -0
- package/dist/build-identity.js.map +1 -0
- package/dist/cli/command-help.d.ts +9 -0
- package/dist/cli/command-help.d.ts.map +1 -0
- package/dist/cli/command-help.js +324 -0
- package/dist/cli/command-help.js.map +1 -0
- package/dist/cli/help.d.ts +40 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +256 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/output.d.ts +28 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +95 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/parser.d.ts +65 -0
- package/dist/cli/parser.d.ts.map +1 -0
- package/dist/cli/parser.js +294 -0
- package/dist/cli/parser.js.map +1 -0
- package/dist/cli/process-entrypoint.d.ts +18 -0
- package/dist/cli/process-entrypoint.d.ts.map +1 -0
- package/dist/cli/process-entrypoint.js +26 -0
- package/dist/cli/process-entrypoint.js.map +1 -0
- package/dist/cli/run.d.ts +114 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +1969 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/commands/commands.d.ts +74 -0
- package/dist/commands/commands.d.ts.map +1 -0
- package/dist/commands/commands.js +2814 -0
- package/dist/commands/commands.js.map +1 -0
- package/dist/config/config.d.ts +86 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +323 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/infra-contract-witness.d.ts +29 -0
- package/dist/config/infra-contract-witness.d.ts.map +1 -0
- package/dist/config/infra-contract-witness.js +30 -0
- package/dist/config/infra-contract-witness.js.map +1 -0
- package/dist/core/browser-url.d.ts +2 -0
- package/dist/core/browser-url.d.ts.map +1 -0
- package/dist/core/browser-url.js +20 -0
- package/dist/core/browser-url.js.map +1 -0
- package/dist/core/deployed-wire-compatibility.d.ts +15 -0
- package/dist/core/deployed-wire-compatibility.d.ts.map +1 -0
- package/dist/core/deployed-wire-compatibility.js +15 -0
- package/dist/core/deployed-wire-compatibility.js.map +1 -0
- package/dist/core/errors.d.ts +59 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +82 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/exit-codes.d.ts +68 -0
- package/dist/core/exit-codes.d.ts.map +1 -0
- package/dist/core/exit-codes.js +84 -0
- package/dist/core/exit-codes.js.map +1 -0
- package/dist/core/instant.d.ts +60 -0
- package/dist/core/instant.d.ts.map +1 -0
- package/dist/core/instant.js +90 -0
- package/dist/core/instant.js.map +1 -0
- package/dist/core/namespace.d.ts +171 -0
- package/dist/core/namespace.d.ts.map +1 -0
- package/dist/core/namespace.js +227 -0
- package/dist/core/namespace.js.map +1 -0
- package/dist/core/observation-budget.d.ts +185 -0
- package/dist/core/observation-budget.d.ts.map +1 -0
- package/dist/core/observation-budget.js +209 -0
- package/dist/core/observation-budget.js.map +1 -0
- package/dist/core/product-web.d.ts +81 -0
- package/dist/core/product-web.d.ts.map +1 -0
- package/dist/core/product-web.js +87 -0
- package/dist/core/product-web.js.map +1 -0
- package/dist/core/validation.d.ts +90 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +199 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/credentials/contracts.d.ts +82 -0
- package/dist/credentials/contracts.d.ts.map +1 -0
- package/dist/credentials/contracts.js +2 -0
- package/dist/credentials/contracts.js.map +1 -0
- package/dist/credentials/errors.d.ts +19 -0
- package/dist/credentials/errors.d.ts.map +1 -0
- package/dist/credentials/errors.js +16 -0
- package/dist/credentials/errors.js.map +1 -0
- package/dist/credentials/index.d.ts +6 -0
- package/dist/credentials/index.d.ts.map +1 -0
- package/dist/credentials/index.js +6 -0
- package/dist/credentials/index.js.map +1 -0
- package/dist/credentials/local-session.d.ts +101 -0
- package/dist/credentials/local-session.d.ts.map +1 -0
- package/dist/credentials/local-session.js +839 -0
- package/dist/credentials/local-session.js.map +1 -0
- package/dist/credentials/secret-material.d.ts +17 -0
- package/dist/credentials/secret-material.d.ts.map +1 -0
- package/dist/credentials/secret-material.js +49 -0
- package/dist/credentials/secret-material.js.map +1 -0
- package/dist/credentials/vault.d.ts +41 -0
- package/dist/credentials/vault.d.ts.map +1 -0
- package/dist/credentials/vault.js +719 -0
- package/dist/credentials/vault.js.map +1 -0
- package/dist/daemon/endpoint.d.ts +29 -0
- package/dist/daemon/endpoint.d.ts.map +1 -0
- package/dist/daemon/endpoint.js +66 -0
- package/dist/daemon/endpoint.js.map +1 -0
- package/dist/daemon/journal.d.ts +62 -0
- package/dist/daemon/journal.d.ts.map +1 -0
- package/dist/daemon/journal.js +299 -0
- package/dist/daemon/journal.js.map +1 -0
- package/dist/daemon/lease.d.ts +25 -0
- package/dist/daemon/lease.d.ts.map +1 -0
- package/dist/daemon/lease.js +94 -0
- package/dist/daemon/lease.js.map +1 -0
- package/dist/daemon/lifecycle.d.ts +14 -0
- package/dist/daemon/lifecycle.d.ts.map +1 -0
- package/dist/daemon/lifecycle.js +29 -0
- package/dist/daemon/lifecycle.js.map +1 -0
- package/dist/daemon/protocol.d.ts +65 -0
- package/dist/daemon/protocol.d.ts.map +1 -0
- package/dist/daemon/protocol.js +194 -0
- package/dist/daemon/protocol.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +29 -0
- package/dist/daemon/supervisor.d.ts.map +1 -0
- package/dist/daemon/supervisor.js +87 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/daemon/views.d.ts +31 -0
- package/dist/daemon/views.d.ts.map +1 -0
- package/dist/daemon/views.js +86 -0
- package/dist/daemon/views.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/journey/api-effects.d.ts +59 -0
- package/dist/journey/api-effects.d.ts.map +1 -0
- package/dist/journey/api-effects.js +386 -0
- package/dist/journey/api-effects.js.map +1 -0
- package/dist/journey/derived-identity.d.ts +91 -0
- package/dist/journey/derived-identity.d.ts.map +1 -0
- package/dist/journey/derived-identity.js +123 -0
- package/dist/journey/derived-identity.js.map +1 -0
- package/dist/journey/index.d.ts +6 -0
- package/dist/journey/index.d.ts.map +1 -0
- package/dist/journey/index.js +6 -0
- package/dist/journey/index.js.map +1 -0
- package/dist/journey/intent.d.ts +36 -0
- package/dist/journey/intent.d.ts.map +1 -0
- package/dist/journey/intent.js +191 -0
- package/dist/journey/intent.js.map +1 -0
- package/dist/journey/orchestrator.d.ts +140 -0
- package/dist/journey/orchestrator.d.ts.map +1 -0
- package/dist/journey/orchestrator.js +415 -0
- package/dist/journey/orchestrator.js.map +1 -0
- package/dist/journey/provider-launch-intent.d.ts +12 -0
- package/dist/journey/provider-launch-intent.d.ts.map +1 -0
- package/dist/journey/provider-launch-intent.js +40 -0
- package/dist/journey/provider-launch-intent.js.map +1 -0
- package/dist/journey/remote-workspace.d.ts +34 -0
- package/dist/journey/remote-workspace.d.ts.map +1 -0
- package/dist/journey/remote-workspace.js +91 -0
- package/dist/journey/remote-workspace.js.map +1 -0
- package/dist/journey/root-entry.d.ts +56 -0
- package/dist/journey/root-entry.d.ts.map +1 -0
- package/dist/journey/root-entry.js +19 -0
- package/dist/journey/root-entry.js.map +1 -0
- package/dist/journey/selection.d.ts +165 -0
- package/dist/journey/selection.d.ts.map +1 -0
- package/dist/journey/selection.js +565 -0
- package/dist/journey/selection.js.map +1 -0
- package/dist/journey/session-failure.d.ts +4 -0
- package/dist/journey/session-failure.d.ts.map +1 -0
- package/dist/journey/session-failure.js +28 -0
- package/dist/journey/session-failure.js.map +1 -0
- package/dist/journey/workspace-effects.d.ts +30 -0
- package/dist/journey/workspace-effects.d.ts.map +1 -0
- package/dist/journey/workspace-effects.js +348 -0
- package/dist/journey/workspace-effects.js.map +1 -0
- package/dist/local-actions/broker.d.ts +32 -0
- package/dist/local-actions/broker.d.ts.map +1 -0
- package/dist/local-actions/broker.js +400 -0
- package/dist/local-actions/broker.js.map +1 -0
- package/dist/local-actions/browser-action.d.ts +78 -0
- package/dist/local-actions/browser-action.d.ts.map +1 -0
- package/dist/local-actions/browser-action.js +307 -0
- package/dist/local-actions/browser-action.js.map +1 -0
- package/dist/local-actions/callback-relay.d.ts +45 -0
- package/dist/local-actions/callback-relay.d.ts.map +1 -0
- package/dist/local-actions/callback-relay.js +265 -0
- package/dist/local-actions/callback-relay.js.map +1 -0
- package/dist/local-actions/clipboard.d.ts +3 -0
- package/dist/local-actions/clipboard.d.ts.map +1 -0
- package/dist/local-actions/clipboard.js +18 -0
- package/dist/local-actions/clipboard.js.map +1 -0
- package/dist/local-actions/contracts.d.ts +63 -0
- package/dist/local-actions/contracts.d.ts.map +1 -0
- package/dist/local-actions/contracts.js +18 -0
- package/dist/local-actions/contracts.js.map +1 -0
- package/dist/local-actions/desktop-presentation.d.ts +141 -0
- package/dist/local-actions/desktop-presentation.d.ts.map +1 -0
- package/dist/local-actions/desktop-presentation.js +253 -0
- package/dist/local-actions/desktop-presentation.js.map +1 -0
- package/dist/local-actions/device-selection.d.ts +43 -0
- package/dist/local-actions/device-selection.d.ts.map +1 -0
- package/dist/local-actions/device-selection.js +154 -0
- package/dist/local-actions/device-selection.js.map +1 -0
- package/dist/local-actions/executor.d.ts +49 -0
- package/dist/local-actions/executor.d.ts.map +1 -0
- package/dist/local-actions/executor.js +329 -0
- package/dist/local-actions/executor.js.map +1 -0
- package/dist/local-actions/file-transfers.d.ts +141 -0
- package/dist/local-actions/file-transfers.d.ts.map +1 -0
- package/dist/local-actions/file-transfers.js +446 -0
- package/dist/local-actions/file-transfers.js.map +1 -0
- package/dist/local-actions/foreground-notifications.d.ts +45 -0
- package/dist/local-actions/foreground-notifications.d.ts.map +1 -0
- package/dist/local-actions/foreground-notifications.js +163 -0
- package/dist/local-actions/foreground-notifications.js.map +1 -0
- package/dist/local-actions/git-signing.d.ts +39 -0
- package/dist/local-actions/git-signing.d.ts.map +1 -0
- package/dist/local-actions/git-signing.js +135 -0
- package/dist/local-actions/git-signing.js.map +1 -0
- package/dist/local-actions/index.d.ts +19 -0
- package/dist/local-actions/index.d.ts.map +1 -0
- package/dist/local-actions/index.js +19 -0
- package/dist/local-actions/index.js.map +1 -0
- package/dist/local-actions/local-service-actions.d.ts +87 -0
- package/dist/local-actions/local-service-actions.d.ts.map +1 -0
- package/dist/local-actions/local-service-actions.js +347 -0
- package/dist/local-actions/local-service-actions.js.map +1 -0
- package/dist/local-actions/loopback.d.ts +5 -0
- package/dist/local-actions/loopback.d.ts.map +1 -0
- package/dist/local-actions/loopback.js +15 -0
- package/dist/local-actions/loopback.js.map +1 -0
- package/dist/local-actions/policy-store.d.ts +7 -0
- package/dist/local-actions/policy-store.d.ts.map +1 -0
- package/dist/local-actions/policy-store.js +70 -0
- package/dist/local-actions/policy-store.js.map +1 -0
- package/dist/local-actions/policy.d.ts +21 -0
- package/dist/local-actions/policy.d.ts.map +1 -0
- package/dist/local-actions/policy.js +43 -0
- package/dist/local-actions/policy.js.map +1 -0
- package/dist/local-actions/private-port-forward.d.ts +36 -0
- package/dist/local-actions/private-port-forward.d.ts.map +1 -0
- package/dist/local-actions/private-port-forward.js +154 -0
- package/dist/local-actions/private-port-forward.js.map +1 -0
- package/dist/local-actions/provider-mcp-config.d.ts +101 -0
- package/dist/local-actions/provider-mcp-config.d.ts.map +1 -0
- package/dist/local-actions/provider-mcp-config.js +166 -0
- package/dist/local-actions/provider-mcp-config.js.map +1 -0
- package/dist/local-actions/providers.d.ts +12 -0
- package/dist/local-actions/providers.d.ts.map +1 -0
- package/dist/local-actions/providers.js +42 -0
- package/dist/local-actions/providers.js.map +1 -0
- package/dist/local-actions/schemas.d.ts +4 -0
- package/dist/local-actions/schemas.d.ts.map +1 -0
- package/dist/local-actions/schemas.js +314 -0
- package/dist/local-actions/schemas.js.map +1 -0
- package/dist/local-actions/sensitive-consent.d.ts +32 -0
- package/dist/local-actions/sensitive-consent.d.ts.map +1 -0
- package/dist/local-actions/sensitive-consent.js +47 -0
- package/dist/local-actions/sensitive-consent.js.map +1 -0
- package/dist/machines/command-launch.d.ts +17 -0
- package/dist/machines/command-launch.d.ts.map +1 -0
- package/dist/machines/command-launch.js +52 -0
- package/dist/machines/command-launch.js.map +1 -0
- package/dist/machines/execution-receipt.d.ts +18 -0
- package/dist/machines/execution-receipt.d.ts.map +1 -0
- package/dist/machines/execution-receipt.js +87 -0
- package/dist/machines/execution-receipt.js.map +1 -0
- package/dist/machines/executions-screen.d.ts +6 -0
- package/dist/machines/executions-screen.d.ts.map +1 -0
- package/dist/machines/executions-screen.js +410 -0
- package/dist/machines/executions-screen.js.map +1 -0
- package/dist/machines/explorer.d.ts +75 -0
- package/dist/machines/explorer.d.ts.map +1 -0
- package/dist/machines/explorer.js +1726 -0
- package/dist/machines/explorer.js.map +1 -0
- package/dist/machines/live-supervisor-update.d.ts +325 -0
- package/dist/machines/live-supervisor-update.d.ts.map +1 -0
- package/dist/machines/live-supervisor-update.js +513 -0
- package/dist/machines/live-supervisor-update.js.map +1 -0
- package/dist/machines/machine-first.d.ts +123 -0
- package/dist/machines/machine-first.d.ts.map +1 -0
- package/dist/machines/machine-first.js +119 -0
- package/dist/machines/machine-first.js.map +1 -0
- package/dist/machines/opencode-supervisor.d.ts +51 -0
- package/dist/machines/opencode-supervisor.d.ts.map +1 -0
- package/dist/machines/opencode-supervisor.js +124 -0
- package/dist/machines/opencode-supervisor.js.map +1 -0
- package/dist/machines/pagination.d.ts +4 -0
- package/dist/machines/pagination.d.ts.map +1 -0
- package/dist/machines/pagination.js +7 -0
- package/dist/machines/pagination.js.map +1 -0
- package/dist/machines/provider-availability.d.ts +42 -0
- package/dist/machines/provider-availability.d.ts.map +1 -0
- package/dist/machines/provider-availability.js +82 -0
- package/dist/machines/provider-availability.js.map +1 -0
- package/dist/machines/provider-screen.d.ts +12 -0
- package/dist/machines/provider-screen.d.ts.map +1 -0
- package/dist/machines/provider-screen.js +129 -0
- package/dist/machines/provider-screen.js.map +1 -0
- package/dist/machines/session-actionability.d.ts +86 -0
- package/dist/machines/session-actionability.d.ts.map +1 -0
- package/dist/machines/session-actionability.js +125 -0
- package/dist/machines/session-actionability.js.map +1 -0
- package/dist/machines/session-visibility.d.ts +139 -0
- package/dist/machines/session-visibility.d.ts.map +1 -0
- package/dist/machines/session-visibility.js +109 -0
- package/dist/machines/session-visibility.js.map +1 -0
- package/dist/platform/adapter.d.ts +62 -0
- package/dist/platform/adapter.d.ts.map +1 -0
- package/dist/platform/adapter.js +240 -0
- package/dist/platform/adapter.js.map +1 -0
- package/dist/platform/support.d.ts +12 -0
- package/dist/platform/support.d.ts.map +1 -0
- package/dist/platform/support.js +25 -0
- package/dist/platform/support.js.map +1 -0
- package/dist/pty/contracts.d.ts +45 -0
- package/dist/pty/contracts.d.ts.map +1 -0
- package/dist/pty/contracts.js +2 -0
- package/dist/pty/contracts.js.map +1 -0
- package/dist/pty/evidence-gate.d.ts +14 -0
- package/dist/pty/evidence-gate.d.ts.map +1 -0
- package/dist/pty/evidence-gate.js +35 -0
- package/dist/pty/evidence-gate.js.map +1 -0
- package/dist/pty/index.d.ts +5 -0
- package/dist/pty/index.d.ts.map +1 -0
- package/dist/pty/index.js +5 -0
- package/dist/pty/index.js.map +1 -0
- package/dist/pty/node-host-terminal.d.ts +14 -0
- package/dist/pty/node-host-terminal.d.ts.map +1 -0
- package/dist/pty/node-host-terminal.js +173 -0
- package/dist/pty/node-host-terminal.js.map +1 -0
- package/dist/pty/node-process.d.ts +26 -0
- package/dist/pty/node-process.d.ts.map +1 -0
- package/dist/pty/node-process.js +102 -0
- package/dist/pty/node-process.js.map +1 -0
- package/dist/runtime/api-terminal-control-plane.d.ts +7 -0
- package/dist/runtime/api-terminal-control-plane.d.ts.map +1 -0
- package/dist/runtime/api-terminal-control-plane.js +95 -0
- package/dist/runtime/api-terminal-control-plane.js.map +1 -0
- package/dist/runtime/backoff.d.ts +17 -0
- package/dist/runtime/backoff.d.ts.map +1 -0
- package/dist/runtime/backoff.js +61 -0
- package/dist/runtime/backoff.js.map +1 -0
- package/dist/runtime/boundary.d.ts +171 -0
- package/dist/runtime/boundary.d.ts.map +1 -0
- package/dist/runtime/boundary.js +1858 -0
- package/dist/runtime/boundary.js.map +1 -0
- package/dist/runtime/capability-gate.d.ts +25 -0
- package/dist/runtime/capability-gate.d.ts.map +1 -0
- package/dist/runtime/capability-gate.js +77 -0
- package/dist/runtime/capability-gate.js.map +1 -0
- package/dist/runtime/contracts.d.ts +25 -0
- package/dist/runtime/contracts.d.ts.map +1 -0
- package/dist/runtime/contracts.js +66 -0
- package/dist/runtime/contracts.js.map +1 -0
- package/dist/runtime/errors.d.ts +20 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +22 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +9 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/node-foreground-session.d.ts +50 -0
- package/dist/runtime/node-foreground-session.d.ts.map +1 -0
- package/dist/runtime/node-foreground-session.js +431 -0
- package/dist/runtime/node-foreground-session.js.map +1 -0
- package/dist/runtime/node-websocket-connector.d.ts +6 -0
- package/dist/runtime/node-websocket-connector.d.ts.map +1 -0
- package/dist/runtime/node-websocket-connector.js +344 -0
- package/dist/runtime/node-websocket-connector.js.map +1 -0
- package/dist/runtime/observer-screen.d.ts +6 -0
- package/dist/runtime/observer-screen.d.ts.map +1 -0
- package/dist/runtime/observer-screen.js +144 -0
- package/dist/runtime/observer-screen.js.map +1 -0
- package/dist/runtime/owner-grant-operations.d.ts +136 -0
- package/dist/runtime/owner-grant-operations.d.ts.map +1 -0
- package/dist/runtime/owner-grant-operations.js +216 -0
- package/dist/runtime/owner-grant-operations.js.map +1 -0
- package/dist/runtime/owner-grants-screen.d.ts +40 -0
- package/dist/runtime/owner-grants-screen.d.ts.map +1 -0
- package/dist/runtime/owner-grants-screen.js +987 -0
- package/dist/runtime/owner-grants-screen.js.map +1 -0
- package/dist/runtime/terminal-transport.d.ts +85 -0
- package/dist/runtime/terminal-transport.d.ts.map +1 -0
- package/dist/runtime/terminal-transport.js +152 -0
- package/dist/runtime/terminal-transport.js.map +1 -0
- package/dist/sync/continuous-sync-supervisor.d.ts +107 -0
- package/dist/sync/continuous-sync-supervisor.d.ts.map +1 -0
- package/dist/sync/continuous-sync-supervisor.js +1117 -0
- package/dist/sync/continuous-sync-supervisor.js.map +1 -0
- package/dist/sync/index.d.ts +9 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +9 -0
- package/dist/sync/index.js.map +1 -0
- package/dist/sync/journal.d.ts +57 -0
- package/dist/sync/journal.d.ts.map +1 -0
- package/dist/sync/journal.js +685 -0
- package/dist/sync/journal.js.map +1 -0
- package/dist/sync/revisions.d.ts +118 -0
- package/dist/sync/revisions.d.ts.map +1 -0
- package/dist/sync/revisions.js +364 -0
- package/dist/sync/revisions.js.map +1 -0
- package/dist/sync/supervisor.d.ts +107 -0
- package/dist/sync/supervisor.d.ts.map +1 -0
- package/dist/sync/supervisor.js +246 -0
- package/dist/sync/supervisor.js.map +1 -0
- package/dist/sync/workspace-sync-client.d.ts +18 -0
- package/dist/sync/workspace-sync-client.d.ts.map +1 -0
- package/dist/sync/workspace-sync-client.js +150 -0
- package/dist/sync/workspace-sync-client.js.map +1 -0
- package/dist/sync/workspace-sync-coordinator.d.ts +76 -0
- package/dist/sync/workspace-sync-coordinator.d.ts.map +1 -0
- package/dist/sync/workspace-sync-coordinator.js +621 -0
- package/dist/sync/workspace-sync-coordinator.js.map +1 -0
- package/dist/sync/workspace-sync-product-service.d.ts +79 -0
- package/dist/sync/workspace-sync-product-service.d.ts.map +1 -0
- package/dist/sync/workspace-sync-product-service.js +398 -0
- package/dist/sync/workspace-sync-product-service.js.map +1 -0
- package/dist/sync/workspace-sync-protocol.d.ts +145 -0
- package/dist/sync/workspace-sync-protocol.d.ts.map +1 -0
- package/dist/sync/workspace-sync-protocol.js +347 -0
- package/dist/sync/workspace-sync-protocol.js.map +1 -0
- package/dist/terminal/appbar.d.ts +40 -0
- package/dist/terminal/appbar.d.ts.map +1 -0
- package/dist/terminal/appbar.js +55 -0
- package/dist/terminal/appbar.js.map +1 -0
- package/dist/terminal/cell-width.d.ts +3 -0
- package/dist/terminal/cell-width.d.ts.map +1 -0
- package/dist/terminal/cell-width.js +53 -0
- package/dist/terminal/cell-width.js.map +1 -0
- package/dist/terminal/codec.d.ts +107 -0
- package/dist/terminal/codec.d.ts.map +1 -0
- package/dist/terminal/codec.js +538 -0
- package/dist/terminal/codec.js.map +1 -0
- package/dist/terminal/foreground.d.ts +135 -0
- package/dist/terminal/foreground.d.ts.map +1 -0
- package/dist/terminal/foreground.js +2097 -0
- package/dist/terminal/foreground.js.map +1 -0
- package/dist/terminal/generated/terminal-wire-v1.d.ts +26 -0
- package/dist/terminal/generated/terminal-wire-v1.d.ts.map +1 -0
- package/dist/terminal/generated/terminal-wire-v1.js +27 -0
- package/dist/terminal/generated/terminal-wire-v1.js.map +1 -0
- package/dist/terminal/mode.d.ts +33 -0
- package/dist/terminal/mode.d.ts.map +1 -0
- package/dist/terminal/mode.js +99 -0
- package/dist/terminal/mode.js.map +1 -0
- package/dist/terminal/passthrough.d.ts +47 -0
- package/dist/terminal/passthrough.d.ts.map +1 -0
- package/dist/terminal/passthrough.js +666 -0
- package/dist/terminal/passthrough.js.map +1 -0
- package/dist/terminal/resume.d.ts +58 -0
- package/dist/terminal/resume.d.ts.map +1 -0
- package/dist/terminal/resume.js +185 -0
- package/dist/terminal/resume.js.map +1 -0
- package/dist/terminal/viewport.d.ts +93 -0
- package/dist/terminal/viewport.d.ts.map +1 -0
- package/dist/terminal/viewport.js +266 -0
- package/dist/terminal/viewport.js.map +1 -0
- package/dist/terminal/workbench.d.ts +32 -0
- package/dist/terminal/workbench.d.ts.map +1 -0
- package/dist/terminal/workbench.js +316 -0
- package/dist/terminal/workbench.js.map +1 -0
- package/dist/terminal/xterm-vte.d.ts +49 -0
- package/dist/terminal/xterm-vte.d.ts.map +1 -0
- package/dist/terminal/xterm-vte.js +602 -0
- package/dist/terminal/xterm-vte.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/workspace/binding-path.d.ts +17 -0
- package/dist/workspace/binding-path.d.ts.map +1 -0
- package/dist/workspace/binding-path.js +91 -0
- package/dist/workspace/binding-path.js.map +1 -0
- package/dist/workspace/binding-store.d.ts +107 -0
- package/dist/workspace/binding-store.d.ts.map +1 -0
- package/dist/workspace/binding-store.js +783 -0
- package/dist/workspace/binding-store.js.map +1 -0
- package/dist/workspace/binding.d.ts +35 -0
- package/dist/workspace/binding.d.ts.map +1 -0
- package/dist/workspace/binding.js +70 -0
- package/dist/workspace/binding.js.map +1 -0
- package/dist/workspace/errors.d.ts +4 -0
- package/dist/workspace/errors.d.ts.map +1 -0
- package/dist/workspace/errors.js +19 -0
- package/dist/workspace/errors.js.map +1 -0
- package/dist/workspace/exclusion.d.ts +21 -0
- package/dist/workspace/exclusion.d.ts.map +1 -0
- package/dist/workspace/exclusion.js +243 -0
- package/dist/workspace/exclusion.js.map +1 -0
- package/dist/workspace/index.d.ts +8 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +8 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/lifecycle.d.ts +56 -0
- package/dist/workspace/lifecycle.d.ts.map +1 -0
- package/dist/workspace/lifecycle.js +94 -0
- package/dist/workspace/lifecycle.js.map +1 -0
- package/dist/workspace/manifest.d.ts +47 -0
- package/dist/workspace/manifest.d.ts.map +1 -0
- package/dist/workspace/manifest.js +243 -0
- package/dist/workspace/manifest.js.map +1 -0
- package/dist/workspace/paths.d.ts +14 -0
- package/dist/workspace/paths.d.ts.map +1 -0
- package/dist/workspace/paths.js +67 -0
- package/dist/workspace/paths.js.map +1 -0
- package/dist/workspace/schema.d.ts +15 -0
- package/dist/workspace/schema.d.ts.map +1 -0
- package/dist/workspace/schema.js +33 -0
- package/dist/workspace/schema.js.map +1 -0
- package/dist/workspace/selection-screen.d.ts +4 -0
- package/dist/workspace/selection-screen.d.ts.map +1 -0
- package/dist/workspace/selection-screen.js +216 -0
- package/dist/workspace/selection-screen.js.map +1 -0
- package/dist/workspace/selection-service.d.ts +16 -0
- package/dist/workspace/selection-service.d.ts.map +1 -0
- package/dist/workspace/selection-service.js +65 -0
- package/dist/workspace/selection-service.js.map +1 -0
- package/node_modules/@xterm/headless/README.md +31 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.js +2 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.js.map +1 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.mjs +46 -0
- package/node_modules/@xterm/headless/lib-headless/xterm-headless.mjs.map +7 -0
- package/node_modules/@xterm/headless/package.json +29 -0
- package/node_modules/@xterm/headless/typings/xterm-headless.d.ts +1385 -0
- package/package.json +75 -0
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createNodeForegroundTerminalHost } from '../pty/node-host-terminal.js';
|
|
3
|
+
import { sanitizeHumanTerminalOutput } from '../cli/output.js';
|
|
4
|
+
import { describeGrantState, OwnerGrantError, classifyTransportFailure, audienceRefusalCanResend } from '../api/owner-observe-grants-v2.js';
|
|
5
|
+
import { audienceFactIsOlder, audienceFactSameRun } from './owner-grant-operations.js';
|
|
6
|
+
const DURATIONS = [['1', '1 hour', 3600000], ['2', '8 hours', 28800000], ['3', '24 hours', 86400000]];
|
|
7
|
+
const CLOSE = 'Esc / Ctrl+C closes this local view. Observation is read-only: it never grants keyboard control.';
|
|
8
|
+
/**
|
|
9
|
+
* CUNA-COL-016-R4: state the real history scope BEFORE the key that grants.
|
|
10
|
+
*
|
|
11
|
+
* Plain language only. Every line is a property of the shipped producer rather
|
|
12
|
+
* than a promise, and the proof stays here instead of on the terminal: the
|
|
13
|
+
* supervisor's `publish_public_audience` documents itself as publishing "future
|
|
14
|
+
* provider output"; `PublicScreenView.reset` starts a blank model and
|
|
15
|
+
* `PublicAudience.publish` returns `starting_sequence: 0`, after which each poll
|
|
16
|
+
* encodes `snapshot()` -- a full redraw of the CURRENTLY VISIBLE screen. So no
|
|
17
|
+
* scrollback and no earlier output is replayed, but the screen still visible on
|
|
18
|
+
* attach is transmitted whole. Migration 0183's
|
|
19
|
+
* `resolve_collab_v2_observer_admission` admits nobody unless the session's
|
|
20
|
+
* latest audience result is `public`, so a grant and a live publication are
|
|
21
|
+
* separate facts and stay separate keypresses here. Which stream and generation
|
|
22
|
+
* a member joins is decided at attach time, which is why the lines below promise
|
|
23
|
+
* a boundary rather than a moment; the moment is named on the receipt the
|
|
24
|
+
* publication itself returns.
|
|
25
|
+
*/
|
|
26
|
+
const DISCLOSURE = [
|
|
27
|
+
'What the member will see:',
|
|
28
|
+
" Only what happens from the moment they start watching - a live view of this session's screen.",
|
|
29
|
+
' Nothing from before that: earlier output and scrollback are never sent to them.',
|
|
30
|
+
' Whatever is still on the screen when they start watching is visible to them, even if it is from earlier.',
|
|
31
|
+
' Watching also requires this session to be shared live. That is a separate decision: press s on the session list.',
|
|
32
|
+
' They can never type, resize or send a signal.',
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* What starting a live share actually exposes, stated before the key that does it.
|
|
36
|
+
*
|
|
37
|
+
* Every line is a property of the shipped producer.
|
|
38
|
+
* `terminal-public-audience.py`'s `PublicAudience.publish` resets to a blank
|
|
39
|
+
* public view and answers `starting_sequence: 0`, after which each poll encodes
|
|
40
|
+
* `snapshot()` -- a full redraw of the CURRENTLY VISIBLE screen -- so no
|
|
41
|
+
* scrollback and no earlier output is replayed, while whatever is still on the
|
|
42
|
+
* screen is transmitted whole. CUNA-COL-016 is explicit that screen inspection
|
|
43
|
+
* cannot detect an authentication prompt and that bytes already delivered cannot
|
|
44
|
+
* be recalled; both are said here rather than implied.
|
|
45
|
+
*/
|
|
46
|
+
const SHARE_DISCLOSURE = [
|
|
47
|
+
' Everyone you have granted observation of this session can watch it from the moment this starts.',
|
|
48
|
+
' They see whatever is on the screen now, and everything printed from now on.',
|
|
49
|
+
' Nothing from before is replayed to them: no scrollback, no earlier output.',
|
|
50
|
+
' Anything a program prints while this is on is part of that - a token, a login code, a file it opens.',
|
|
51
|
+
' Cuna cannot detect that and will not hide it. Stopping later ends what comes next; it cannot recall what was already sent.',
|
|
52
|
+
' They can never type, resize or send a signal.',
|
|
53
|
+
];
|
|
54
|
+
export async function runOwnerGrantsScreen(api, sessions, store, identity, options = {}) {
|
|
55
|
+
const host = options.host ?? createNodeForegroundTerminalHost(), now = options.now ?? Date.now, lease = await host.acquire('rich'), abort = new AbortController(), encoder = new TextEncoder();
|
|
56
|
+
let stopped = false, busy = false, phase = 'sessions', index = 0, items = [], listing, members = [], membersAfter = null, membersNext = null, session, member, grant, failure, hostWrites = Promise.resolve();
|
|
57
|
+
// Every operation whose outcome is unknown HERE. The record is on disk before
|
|
58
|
+
// the request leaves, so an exit, a crash or a lost answer cannot lose the
|
|
59
|
+
// operation identity a replay needs. An unresolved operation blocks every NEW
|
|
60
|
+
// change of the SAME authority, so it is never overwritten -- and only that
|
|
61
|
+
// authority, because a grant and a publication are separate things.
|
|
62
|
+
let unresolved = [], cursor = 0, refusal;
|
|
63
|
+
// Operations whose exact request Cuna has already answered, so resending it
|
|
64
|
+
// again cannot settle anything. In memory only: a new process has not seen that
|
|
65
|
+
// refusal and must not pretend it has.
|
|
66
|
+
const unresendable = new Set();
|
|
67
|
+
// The operation a settled answer just closed. It is kept for the OUTCOME
|
|
68
|
+
// screen only: once the record is gone this string is the last thing that can
|
|
69
|
+
// tie what the owner is reading to what they asked Cuna to do.
|
|
70
|
+
let settled;
|
|
71
|
+
// The last sharing decision Cuna confirmed IN THIS PROCESS, and the session a
|
|
72
|
+
// pending sharing decision is about. A confirmed decision describes the moment
|
|
73
|
+
// Cuna confirmed it; it is never rendered as what the session is doing later.
|
|
74
|
+
let audience, audienceTarget, audienceFrom = 'sessions';
|
|
75
|
+
// The last answer the SESSION gave to the question "what are you sharing".
|
|
76
|
+
// It is an observation at the moment it was answered -- the strongest statement
|
|
77
|
+
// anything here can make -- and it is never merged with the history half beside
|
|
78
|
+
// it, nor promoted to a claim about a later moment.
|
|
79
|
+
let audienceState, audienceAsked;
|
|
80
|
+
// What this computer had already recorded about the run a reading describes.
|
|
81
|
+
// `stale` means the answer is from earlier in the run than a fact recorded
|
|
82
|
+
// here, so it must not be shown as this session's state; `otherRun` means the
|
|
83
|
+
// recorded fact is about a different run and could not order this answer at
|
|
84
|
+
// all. Both are reported rather than resolved by guessing.
|
|
85
|
+
let audienceStale, audienceOtherRun, factNote;
|
|
86
|
+
// Which surface the failure on screen came from, so a recovery offered for a
|
|
87
|
+
// grant refusal is not offered for a sharing refusal that reads alike.
|
|
88
|
+
let failureSubject = 'grant';
|
|
89
|
+
// Set while `grant` holds a receipt Cuna REPLAYED from storage rather than a
|
|
90
|
+
// reading of current authority. Never presented as the grant's state.
|
|
91
|
+
let replayed;
|
|
92
|
+
let forgetTarget, forgotten;
|
|
93
|
+
let finish;
|
|
94
|
+
const done = new Promise(resolve => { finish = resolve; });
|
|
95
|
+
const write = (text) => { hostWrites = hostWrites.then(async () => { if (!stopped)
|
|
96
|
+
await host.write(encoder.encode(text)); }).catch(() => { stop(); }); return hostWrites; };
|
|
97
|
+
const stop = () => { if (stopped)
|
|
98
|
+
return; stopped = true; abort.abort(); finish(); };
|
|
99
|
+
const screen = (title, lines, footer) => write('\x1b[H\x1b[2J' + sanitizeHumanTerminalOutput([title, '', ...lines, '', footer, CLOSE].join('\n')).replaceAll('\n', '\r\n'));
|
|
100
|
+
const mark = (i) => index === i ? '>' : ' ';
|
|
101
|
+
const short = (id) => id.slice(0, 8);
|
|
102
|
+
/** What one operation was, in the words the owner chose it with. */
|
|
103
|
+
const noun = (op) => op.kind === 'create' ? 'grant' : op.kind === 'revoke' ? 'revocation' : op.action === 'publish' ? 'start of live sharing' : 'stop of live sharing';
|
|
104
|
+
const sessionLine = (s, i) => `${mark(i)} ${s.machineName} / ${s.name} ${s.agent} ${s.state}${s.projectId === undefined ? ' (Project not named by server)' : ''} ${short(s.id)}`;
|
|
105
|
+
const memberLine = (m, i) => `${mark(i)} ${m.recipient_email ?? '(no email on record)'} ${short(m.principal_id)} membership rev ${m.membership_revision}`;
|
|
106
|
+
const authorityOf = (op) => op.kind === 'audience' ? 'audience' : 'grant';
|
|
107
|
+
const pendingIn = (authority) => unresolved.filter(op => authorityOf(op) === authority);
|
|
108
|
+
const banner = () => {
|
|
109
|
+
if (!unresolved.length)
|
|
110
|
+
return [];
|
|
111
|
+
const grants = pendingIn('grant').length, shares = pendingIn('audience').length;
|
|
112
|
+
return ['', `${unresolved.length} earlier change${unresolved.length === 1 ? ' has' : 's have'} an unknown outcome. Press p to return to ${unresolved.length === 1 ? 'it' : 'them'}.`,
|
|
113
|
+
...(grants ? ['No new grant or revocation can start until it is resolved or its local record is forgotten.'] : []),
|
|
114
|
+
...(shares ? ['No new start of live sharing can begin until it is resolved or its local record is forgotten. Stopping, asking what the session is sharing, and granting or revoking observation are all still available.'] : [])];
|
|
115
|
+
};
|
|
116
|
+
/** Say what this listing could not see. Silence here would claim a completeness the reads do not have. */
|
|
117
|
+
const limits = () => {
|
|
118
|
+
if (!listing)
|
|
119
|
+
return [];
|
|
120
|
+
const parts = [];
|
|
121
|
+
if (listing.omittedMachines > 0)
|
|
122
|
+
parts.push(`${listing.omittedMachines} further Machine${listing.omittedMachines === 1 ? ' was' : 's were'} not read`);
|
|
123
|
+
if (listing.machinesWithMoreSessions > 0)
|
|
124
|
+
parts.push(`${listing.machinesWithMoreSessions} Machine${listing.machinesWithMoreSessions === 1 ? ' has' : 's have'} more AgentSessions than one page`);
|
|
125
|
+
return parts.length ? ['', `This list is incomplete: ${parts.join(', and ')}. Sessions not shown here cannot be shared from this screen.`] : [];
|
|
126
|
+
};
|
|
127
|
+
const paintSessions = () => {
|
|
128
|
+
phase = 'sessions';
|
|
129
|
+
return screen('Share a session - read-only observation', [...(items.length ? items.map(sessionLine) : ['No AgentSessions in this Project were found on your Machines.']), ...limits(),
|
|
130
|
+
...(items.length ? ['', 'Granting a member access and sharing the session live are two separate decisions. Cuna needs both before anyone can watch.'] : []), ...banner()], items.length ? 'Enter grants a member access; s starts sharing this session live; e stops it; c asks what it is sharing now; Up/Down selects; r refreshes.' : 'r refreshes.');
|
|
131
|
+
};
|
|
132
|
+
const paintMembers = () => { phase = 'members'; return screen(`Choose the member who may observe ${session.name}`, [...(members.length ? members.map(memberLine) : ['No other members hold observer membership in this Project. Invite one in the web app first; an invitation alone grants no observation.']), '', 'Membership lets a member be granted observation; it grants nothing by itself.', ...banner()], 'Up/Down selects; Enter chooses; n next page; r refreshes; b back.'); };
|
|
133
|
+
const paintDuration = () => { phase = 'duration'; return screen(`Grant ${member.recipient_email ?? short(member.principal_id)} read-only observation of ${session.name}`, [...DURATIONS.map(([key, label]) => ` ${key} ${label}`), '', ...DISCLOSURE, ...banner()], 'Press 1, 2 or 3 to create the grant; b back.'); };
|
|
134
|
+
const paintGrant = () => {
|
|
135
|
+
phase = 'grant';
|
|
136
|
+
const g = grant, d = describeGrantState(g, now()), stored = replayed;
|
|
137
|
+
return screen(`Observation grant ${short(g.grant_id)} - ${stored ? 'stored answer, not current authority' : d.label}`, [`Session ${g.agent_session_id}`, `Member ${g.subject_principal_id}`, `State ${g.state}${g.revocation_state === null ? '' : ` / ${g.revocation_state}`} revision ${g.revision}`, `Expires ${new Date(g.expires_at).toISOString()}`, '',
|
|
138
|
+
...(stored
|
|
139
|
+
? [`This is the receipt Cuna stored when operation ${stored.operationId} was first answered, replayed unchanged. It cannot show a revocation made since, so it is not evidence that this grant is still ${g.state}.`,
|
|
140
|
+
...(stored.reason === undefined ? [] : [`Reading the current state did not answer: ${stored.reason}`]),
|
|
141
|
+
'Press i to read the grant\'s current state.']
|
|
142
|
+
: [d.detail]),
|
|
143
|
+
// A grant on its own shows nobody anything, and this is the screen where
|
|
144
|
+
// that is asked. Neither key claims to know how the session is shared now.
|
|
145
|
+
'', 'Watching also needs this session to be shared live, which is a separate decision from this grant.',
|
|
146
|
+
...banner()], `${stored ? 'i reads the current state' : d.final ? 'i inspects again' : g.state === 'active' ? 'x revokes; i inspects the current state' : 'i inspects again (no scheduled polling)'}; s starts sharing this session live; e stops it; c asks what it is sharing now; b back to sessions.`);
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* The two sharing decisions, each asked before it is made.
|
|
150
|
+
*
|
|
151
|
+
* Publication is the authority that lets a granted member see anything, so it
|
|
152
|
+
* is never a side effect of granting: the owner reaches these screens by
|
|
153
|
+
* pressing s or e and confirms with y.
|
|
154
|
+
*/
|
|
155
|
+
const paintConfirmShare = () => {
|
|
156
|
+
phase = 'confirm-share';
|
|
157
|
+
const target = audienceTarget;
|
|
158
|
+
return screen("Share this session's screen live?", [`Session ${target.label}`, '', ...SHARE_DISCLOSURE, '',
|
|
159
|
+
'It gives nobody keyboard control, and it creates no grant: a member still needs one, and Cuna still applies its own checks before letting anyone watch.'], 'Press y to start sharing; any other key cancels and sends nothing.');
|
|
160
|
+
};
|
|
161
|
+
const paintConfirmStop = () => {
|
|
162
|
+
phase = 'confirm-stop';
|
|
163
|
+
const target = audienceTarget;
|
|
164
|
+
return screen('Stop sharing this session live?', [`Session ${target.label}`, '',
|
|
165
|
+
' Anyone watching stops receiving this session the moment Cuna confirms it.',
|
|
166
|
+
' What they have already seen cannot be recalled.',
|
|
167
|
+
' Observation grants are left exactly as they are: this revokes nothing and expires nothing.',
|
|
168
|
+
' If the session is already private, this changes nothing.'], 'Press y to stop sharing; any other key cancels and sends nothing.');
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* The receipt screen for a confirmed sharing decision.
|
|
172
|
+
*
|
|
173
|
+
* CUNA-COL-016-R4 asks for the starting point of what a watcher receives. The
|
|
174
|
+
* publication receipt is the only place it exists: the producer answers with a
|
|
175
|
+
* generation exactly one past the one it was issued against and
|
|
176
|
+
* `first_sequence: '1'`, meaning a new public stream whose first frame is a
|
|
177
|
+
* redraw of the screen as it is now. It is rendered as a plain share number and
|
|
178
|
+
* a plain sentence; the stream and request identifiers stay off the terminal
|
|
179
|
+
* because no decision here depends on them.
|
|
180
|
+
*/
|
|
181
|
+
const paintAudience = () => {
|
|
182
|
+
phase = 'shared';
|
|
183
|
+
const a = audience, target = audienceTarget;
|
|
184
|
+
// An answer that a later confirmed decision has already replaced never reaches
|
|
185
|
+
// this screen: `setAudience` refuses it at the adapter, so there is no path by
|
|
186
|
+
// which a superseded receipt can be painted as the session's state.
|
|
187
|
+
return screen(a.state === 'public' ? 'Live sharing started - Cuna confirmed it' : 'Live sharing stopped - Cuna confirmed it', [`Session ${target?.label ?? a.agentSessionId}`,
|
|
188
|
+
...(a.state === 'public'
|
|
189
|
+
? [`Share #${a.generation} of this run of the session`, '',
|
|
190
|
+
'Watching starts here: the first thing anyone sees is the screen as it is now. Nothing printed before this moment is sent to them.',
|
|
191
|
+
'Everyone who holds an active observation grant on this session can watch it, if Cuna\'s own checks also allow it. Sharing grants no keyboard, resize or signal.']
|
|
192
|
+
: ['',
|
|
193
|
+
'Cuna confirmed with the session itself that its output is fenced: nothing more is sent to anyone who was watching.',
|
|
194
|
+
'What was already sent cannot be recalled. Observation grants are untouched - stopping the live share revokes nothing.']),
|
|
195
|
+
...banner()], a.state === 'public' ? 'e stops sharing; c asks what the session is sharing now; b back to sessions.' : 's starts sharing again; c asks what the session is sharing now; b back to sessions.');
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* What one earlier change is RECORDED to have done, said next to -- never
|
|
199
|
+
* merged into -- what the session is doing now.
|
|
200
|
+
*
|
|
201
|
+
* The distinction is the whole repair: a receipt Cuna stored describes the
|
|
202
|
+
* moment that request was answered, and an owner reading it as the present is
|
|
203
|
+
* exactly how a session stays shared while its owner believes it is private.
|
|
204
|
+
*/
|
|
205
|
+
const historyLines = (operation, asked) => {
|
|
206
|
+
// `unknown` carries no action of its own -- there is no request to read one
|
|
207
|
+
// from -- so the noun comes from the durable record the owner is asking about.
|
|
208
|
+
const named = `the earlier ${asked === 'publish' ? 'start of live sharing' : 'stop of live sharing'} (operation ${operation.operationId})`;
|
|
209
|
+
if (operation.status === 'unknown')
|
|
210
|
+
return [`Cuna has no record of ${named} at all. It never reached the session, so it changed nothing.`];
|
|
211
|
+
const opened = `${named[0].toUpperCase()}${named.slice(1)}`;
|
|
212
|
+
if (operation.status !== 'recorded')
|
|
213
|
+
return operation.status === 'pending'
|
|
214
|
+
? [`${opened} is still inside the few seconds Cuna allows it, with no answer recorded yet. Ask again in a moment.`]
|
|
215
|
+
: [`${opened} ran out of time with no answer recorded.`,
|
|
216
|
+
'Whether it took effect is unknown, and it will stay unknown: Cuna will not raise that exact request with the session again.',
|
|
217
|
+
'What the session is doing now is the line above, and it is not an answer about that request.'];
|
|
218
|
+
if (operation.outcome.kind === 'refused')
|
|
219
|
+
return [
|
|
220
|
+
`Cuna has a recorded answer for ${named}: the session refused it, so that request did not change what it asked to change.`,
|
|
221
|
+
'That is what that one request did. It is not what the session is doing now.'
|
|
222
|
+
];
|
|
223
|
+
return [
|
|
224
|
+
`Cuna has a recorded answer for ${named}: the session ${operation.outcome.state === 'public' ? 'started sharing at share' : 'stopped sharing at share'} #${operation.outcome.generation}.`,
|
|
225
|
+
'That is what that one request did. It is not what the session is doing now.'
|
|
226
|
+
];
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* The answer to "what is this session sharing", as the session itself gave it.
|
|
230
|
+
*
|
|
231
|
+
* Asking is a query: `consume_collab_v2_session_audience_response` returns
|
|
232
|
+
* before it retires an attachment or closes an endpoint when the action is
|
|
233
|
+
* `observe`, so nothing here starts a share, stops one, creates a grant or
|
|
234
|
+
* disconnects anybody -- and that is stated rather than assumed.
|
|
235
|
+
*/
|
|
236
|
+
/** The one sentence every reading has to carry. A reading is answered at a
|
|
237
|
+
* moment; nothing about that moment binds the next one, and this computer can
|
|
238
|
+
* only compare it against changes that were made HERE. */
|
|
239
|
+
const AS_OF = [
|
|
240
|
+
'That is what the session answered when Cuna asked, not a promise about now: any window signed in to this account can change it at any moment.',
|
|
241
|
+
'Cuna is asked again every time you press c. This computer can only compare an answer against sharing changes made on this computer.',
|
|
242
|
+
];
|
|
243
|
+
const factLine = (fact) => `${fact.kind === 'transition' ? 'A sharing change confirmed on this computer' : 'An answer read on this computer'}: ${fact.state === 'public' ? `sharing live at share #${fact.generation}` : fact.generation === '0' ? 'never shared live on that run' : `not sharing live, last share #${fact.generation}`}.`;
|
|
244
|
+
const paintAudienceState = () => {
|
|
245
|
+
phase = 'state';
|
|
246
|
+
const s = audienceState, target = audienceTarget, current = s.current, stale = audienceStale;
|
|
247
|
+
const answered = current.state === 'public'
|
|
248
|
+
? [`Share #${current.generation} of this run of the session`, '',
|
|
249
|
+
'Cuna put the question to the session itself, and the session answered that it was sharing live.',
|
|
250
|
+
'Everyone holding an active observation grant on this session could watch it from that moment, if Cuna\'s own checks also allowed it.']
|
|
251
|
+
: [...(current.generation === '0' ? ['', 'The session answered that this run has never been shared live.'] : [`Last share #${current.generation} of this run of the session`, '',
|
|
252
|
+
'Cuna put the question to the session itself, and the session answered that it was not sharing live.']),
|
|
253
|
+
'Nothing new was being sent to anyone who had been watching. What was already sent cannot be recalled.'];
|
|
254
|
+
return screen(stale !== undefined
|
|
255
|
+
? 'Cuna answered about an earlier moment than this computer already knows about'
|
|
256
|
+
: current.state === 'public' ? 'When Cuna asked, this session was sharing live' : 'When Cuna asked, this session was not sharing live', [`Session ${target?.label ?? current.agentSessionId}`,
|
|
257
|
+
...(stale !== undefined
|
|
258
|
+
// Painting this as the session's state is the exact failure this route
|
|
259
|
+
// exists to end, so it is described and set aside instead.
|
|
260
|
+
? ['', `Cuna's answer describes ${current.state === 'public' ? `sharing live at share #${current.generation}` : `not sharing live at share #${current.generation}`}, which comes earlier in this run than what this computer has already recorded.`,
|
|
261
|
+
factLine(stale),
|
|
262
|
+
'So that answer is not shown as this session\'s state. Press c to ask again.']
|
|
263
|
+
: answered),
|
|
264
|
+
...(audienceOtherRun === undefined ? [] : ['',
|
|
265
|
+
'This computer also holds a sharing record for a different run of this session, so it could not be used to order this answer.',
|
|
266
|
+
factLine(audienceOtherRun)]),
|
|
267
|
+
'', ...AS_OF,
|
|
268
|
+
'Asking changed nothing: it started no share, stopped none, created no grant and disconnected nobody.',
|
|
269
|
+
...(factNote === undefined ? [] : ['', factNote]),
|
|
270
|
+
...(s.operation === undefined || audienceAsked === undefined ? [] : ['', ...historyLines(s.operation, audienceAsked),
|
|
271
|
+
// The record is gone by the time this renders, so this is the last link
|
|
272
|
+
// between the answer above and the thing the owner asked Cuna to do.
|
|
273
|
+
...(settled === undefined ? [] : [`Its local record was dropped: Cuna answered operation ${settled.operationId}, so nothing here is left to finish.`])]),
|
|
274
|
+
...banner()], `${stale !== undefined ? 's starts sharing; e stops sharing' : current.state === 'public' ? 'e stops sharing' : 's starts sharing'}; c asks again; b back to sessions.`);
|
|
275
|
+
};
|
|
276
|
+
/** True when rereading the Project members is the actual recovery for this refusal. */
|
|
277
|
+
const rereadsMembers = (f) => failureSubject === 'grant' && !grant && session !== undefined && (f.kind === 'stale_revision' || f.kind === 'grant_unavailable');
|
|
278
|
+
const paintFailure = () => {
|
|
279
|
+
phase = 'failure';
|
|
280
|
+
const f = failure;
|
|
281
|
+
return screen(`${failureSubject === 'reading' ? 'Not read' : 'Not done'} - ${f.kind.replaceAll('_', ' ')}`, [f.message,
|
|
282
|
+
// A question that failed leaves everything as it was, including every
|
|
283
|
+
// earlier change it might have been asked about.
|
|
284
|
+
...(failureSubject === 'reading' ? ['', 'Asking changed nothing, and this answer is about the question only: it says nothing about any earlier start or stop.'] : []),
|
|
285
|
+
// `settled` is set only when a MUTATION was refused with its effect known,
|
|
286
|
+
// so this is the one place the no-effect claim is proved by the answer. A
|
|
287
|
+
// read reaching this screen changed nothing by construction and needs no
|
|
288
|
+
// such line; asserting one for every failure claimed more than was known.
|
|
289
|
+
...(settled === undefined ? [] : ['',
|
|
290
|
+
'This attempt applied nothing.',
|
|
291
|
+
// The record is gone by the time this renders, so this is the last link
|
|
292
|
+
// between the sentence above and the thing the owner asked Cuna to do.
|
|
293
|
+
`Operation ${settled.operationId} (${noun(settled)}) is settled: Cuna answered it, so its local record was dropped.`]),
|
|
294
|
+
...banner()], rereadsMembers(f) ? 'r rereads the members; b back.' : grant ? 'i inspects the grant; b back.' : 'b back to sessions.');
|
|
295
|
+
};
|
|
296
|
+
/**
|
|
297
|
+
* The recovery screen, reached on relaunch as well as in-session.
|
|
298
|
+
*
|
|
299
|
+
* It states what is known and what is not, and does not claim the CLI can
|
|
300
|
+
* determine a create's outcome: the canonical contract has no owner-side
|
|
301
|
+
* listing of a session's grants, so an unanswered create can only be replayed
|
|
302
|
+
* under its own operation identity or checked in the web app. A revoke is
|
|
303
|
+
* different -- the grant ID is known, so inspecting it answers the question.
|
|
304
|
+
*/
|
|
305
|
+
const paintUnresolved = () => {
|
|
306
|
+
phase = 'unresolved';
|
|
307
|
+
// A resend the server SETTLES empties this list, and the reason it settled on
|
|
308
|
+
// is then the only explanation that exists -- the durable record it would
|
|
309
|
+
// otherwise have been read from has just been destroyed. Falling through to
|
|
310
|
+
// the session list here rendered nothing at all, which is what the owner saw
|
|
311
|
+
// on a 404 or a 409 replay.
|
|
312
|
+
if (!unresolved.length)
|
|
313
|
+
return failure ? paintFailure() : paintSessions();
|
|
314
|
+
cursor = Math.min(cursor, unresolved.length - 1);
|
|
315
|
+
const row = unresolved[cursor];
|
|
316
|
+
const lines = unresolved.map((op, i) => `${cursor === i ? '>' : ' '} ${op.kind === 'create' ? 'grant ' : op.kind === 'revoke' ? 'revoke ' : op.action === 'publish' ? 'share ' : 'unshare'} operation ${op.operationId}`);
|
|
317
|
+
lines.push('', row.kind === 'create'
|
|
318
|
+
? `Grant for member ${row.subjectPrincipalId} on session ${row.agentSessionId}, membership revision ${row.expectedMembershipRevision}, expiring ${new Date(row.expiresAtMs).toISOString()}.`
|
|
319
|
+
: row.kind === 'revoke'
|
|
320
|
+
? `Revocation of grant ${row.grantId} (member ${row.subjectPrincipalId}, session ${row.agentSessionId}) against revision ${row.expectedRevision}.`
|
|
321
|
+
: `${row.action === 'publish' ? 'Start' : 'Stop'} of live sharing on session ${row.agentSessionId}.`);
|
|
322
|
+
// Cuna has closed the door on resending this exact request: the stored request
|
|
323
|
+
// has expired, or Cuna has already recorded an answer for it. Offering `r`
|
|
324
|
+
// would be offering an action that cannot settle anything -- and the standing
|
|
325
|
+
// description must stop promising it, or the screen contradicts itself one
|
|
326
|
+
// line later.
|
|
327
|
+
const resendable = !unresendable.has(row.operationId);
|
|
328
|
+
lines.push('', row.kind === 'create'
|
|
329
|
+
? "Cuna never confirmed this. The grant may or may not exist, and this CLI cannot tell: the collaboration contract has no owner-side listing of a session's grants. Resending reuses this exact operation ID, so it cannot create a second grant. The web app is the other way to find out."
|
|
330
|
+
: row.kind === 'revoke'
|
|
331
|
+
? 'Cuna never confirmed this. Inspecting the grant answers it directly, because the grant ID is known. Resending reuses this exact operation ID and the same expected revision.'
|
|
332
|
+
// Resending is not one option among several here: the session refuses a
|
|
333
|
+
// fresh request issued against a sharing state it has already moved past,
|
|
334
|
+
// so this exact identity is the only thing that can ever settle this -- for
|
|
335
|
+
// as long as Cuna will still raise it.
|
|
336
|
+
: `Cuna never confirmed this. Press c to ask the session what it is sharing now and what this exact request is recorded to have done; that is the question this record exists around, and asking changes nothing.${resendable ? ' Resending instead reuses this exact operation ID, which asks the session about the same request rather than starting a second one.' : ' Cuna will not raise this request with the session again, so resending cannot settle it now.'}${row.action === 'publish' ? ' If what you need is for nobody to be watching, press e: stopping never depends on this answer.' : ''}`);
|
|
337
|
+
if (!resendable)
|
|
338
|
+
lines.push('', 'What that operation did stays unknown, and it will stay unknown. Asking what the session is sharing now still answers what the session is doing; forgetting the record is the only other thing left here, and it changes nothing at Cuna.');
|
|
339
|
+
if (failure)
|
|
340
|
+
lines.push('', `Last reason: ${failure.message}`);
|
|
341
|
+
if (refusal)
|
|
342
|
+
lines.push('', refusal);
|
|
343
|
+
return screen(`Unresolved change${unresolved.length === 1 ? '' : 's'} - outcome unknown`, lines, `Up/Down selects; ${resendable ? 'r resends this exact operation; ' : ''}${row.kind === 'revoke' ? 'i inspects the grant; ' : row.kind === 'audience' ? `c asks what the session is sharing now${row.action === 'publish' ? '; e stops live sharing now' : ''}; ` : ''}d forgets the local record only; b back to sessions.`);
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* Deleting the record is irreversible and revokes nothing, so it is asked
|
|
347
|
+
* before it is done and named after it is done.
|
|
348
|
+
*/
|
|
349
|
+
const paintConfirmForget = () => {
|
|
350
|
+
phase = 'confirm-forget';
|
|
351
|
+
const op = forgetTarget;
|
|
352
|
+
return screen('Forget this local record?', [`${op.kind === 'create' ? 'Grant' : op.kind === 'revoke' ? 'Revocation' : op.action === 'publish' ? 'Start of live sharing' : 'Stop of live sharing'} operation ${op.operationId}`, '',
|
|
353
|
+
'This deletes the local record only. Cuna is not contacted and nothing is revoked.',
|
|
354
|
+
// Reaching Cuna is not the same as taking effect, and the outcome here is
|
|
355
|
+
// unknown by definition. Every branch stays conditional on the effect, not
|
|
356
|
+
// on delivery, and none states that an authority exists.
|
|
357
|
+
op.kind === 'create'
|
|
358
|
+
? "The outcome stays unknown. If that request did take effect, a grant exists and the member may be able to observe this session - and after this, no screen in this CLI can name or replay that operation, because the collaboration contract has no owner-side listing of a session's grants. The web app becomes the only way to find out."
|
|
359
|
+
: op.kind === 'revoke'
|
|
360
|
+
? `The outcome stays unknown. If that request did take effect, the revocation applied. Grant ${op.grantId} stays readable by ID either way, so its state is still reachable; the operation identity is not.`
|
|
361
|
+
: op.action === 'publish'
|
|
362
|
+
? 'The outcome of that request stays unknown, and after this no screen here can name or replay it. Asking what the session is sharing now does not need it and answers what the session is doing; stopping the sharing does not need it either and is the only way to be sure nobody is watching.'
|
|
363
|
+
: 'The outcome of that request stays unknown, and after this no screen here can name or replay it. Asking what the session is sharing now does not need it. Asking Cuna to stop sharing again is a separate decision that does not need it either.',
|
|
364
|
+
'', 'This cannot be undone.'], 'Press y to forget it; any other key keeps it.');
|
|
365
|
+
};
|
|
366
|
+
const paintForgotten = () => {
|
|
367
|
+
phase = 'forgotten';
|
|
368
|
+
const op = forgotten;
|
|
369
|
+
return screen('Local record forgotten - nothing was revoked', [`${op.kind === 'create' ? 'Grant' : op.kind === 'revoke' ? 'Revocation' : op.action === 'publish' ? 'Start of live sharing' : 'Stop of live sharing'} operation ${op.operationId}`, '',
|
|
370
|
+
'Cuna was not contacted, so its side is exactly as it was. Whatever that operation did or did not do still stands, unchanged.',
|
|
371
|
+
op.kind === 'create'
|
|
372
|
+
? 'This CLI can no longer name or replay it. Check the web app if you need to know whether a grant exists.'
|
|
373
|
+
: op.kind === 'revoke'
|
|
374
|
+
? `Grant ${op.grantId} can still be read: cuna share --project ${identity.project} --grant ${op.grantId}`
|
|
375
|
+
: 'This CLI can no longer replay it. Asking what the session is sharing now, and stopping live sharing, are both still available and depend on nothing that was just deleted.',
|
|
376
|
+
...banner()], unresolved.length ? 'p returns to the remaining unresolved changes; b back to sessions.' : 'b back to sessions.');
|
|
377
|
+
};
|
|
378
|
+
/**
|
|
379
|
+
* Run a local-state operation and keep its failure local.
|
|
380
|
+
*
|
|
381
|
+
* These never touch the network, so classifying them with the transport
|
|
382
|
+
* classifier reported "Cuna could not be reached for this read", which names
|
|
383
|
+
* the wrong boundary and hides the file the person has to fix.
|
|
384
|
+
*/
|
|
385
|
+
const localState = async (what, work) => {
|
|
386
|
+
try {
|
|
387
|
+
return await work();
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
if (error instanceof OwnerGrantError || (error instanceof Error && error.name === 'AbortError'))
|
|
391
|
+
throw error;
|
|
392
|
+
throw new OwnerGrantError('unavailable', `Cuna was not contacted. This CLI could not ${what}: ${error instanceof Error ? error.message : String(error)}`, error);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
const run = async (label, work) => {
|
|
396
|
+
if (busy || stopped)
|
|
397
|
+
return;
|
|
398
|
+
busy = true;
|
|
399
|
+
const previous = phase;
|
|
400
|
+
phase = 'working';
|
|
401
|
+
settled = undefined;
|
|
402
|
+
await screen(label, ['Waiting for Cuna...'], '');
|
|
403
|
+
try {
|
|
404
|
+
await work();
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
if (stopped)
|
|
408
|
+
return;
|
|
409
|
+
failure = error instanceof OwnerGrantError ? error : classifyTransportFailure(error, false);
|
|
410
|
+
if (failure.effectUnknown || previous === 'unresolved') {
|
|
411
|
+
cursor = 0;
|
|
412
|
+
await paintUnresolved();
|
|
413
|
+
}
|
|
414
|
+
else
|
|
415
|
+
await paintFailure();
|
|
416
|
+
}
|
|
417
|
+
finally {
|
|
418
|
+
busy = false;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
/**
|
|
422
|
+
* Drop a resolved record.
|
|
423
|
+
*
|
|
424
|
+
* Failing to drop it over-reports, which is the safe direction, and it must
|
|
425
|
+
* never replace the answer that made this call: this runs inside the catch
|
|
426
|
+
* that carries a server refusal.
|
|
427
|
+
*/
|
|
428
|
+
const forget = async (operationId) => {
|
|
429
|
+
try {
|
|
430
|
+
await store.settle(operationId);
|
|
431
|
+
unresolved = unresolved.filter(op => op.operationId !== operationId);
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
refusal = `Cuna answered this operation, but its local record could not be removed: ${error instanceof Error ? error.message : String(error)}. It is still listed as unresolved here.`;
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
/**
|
|
438
|
+
* Send one mutation under an already-durable identity.
|
|
439
|
+
*
|
|
440
|
+
* On success, and on any refusal the server issued WITHOUT acting, the record
|
|
441
|
+
* is dropped. When the effect is undetermined the record stays exactly as it
|
|
442
|
+
* was, which is what makes the next process able to finish this operation.
|
|
443
|
+
*/
|
|
444
|
+
const settleWith = async (op, call, accept) => {
|
|
445
|
+
const identity = { operationId: op.operationId, kind: op.kind, ...(op.action === undefined ? {} : { action: op.action }) };
|
|
446
|
+
let value;
|
|
447
|
+
try {
|
|
448
|
+
value = await call();
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
if (!(error instanceof OwnerGrantError) || !error.effectUnknown) {
|
|
452
|
+
settled = identity;
|
|
453
|
+
await forget(op.operationId);
|
|
454
|
+
}
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
await forget(op.operationId);
|
|
458
|
+
failure = undefined;
|
|
459
|
+
settled = identity;
|
|
460
|
+
await accept(value);
|
|
461
|
+
};
|
|
462
|
+
const acceptGrant = async (value) => { grant = value; replayed = undefined; refusal = undefined; await paintGrant(); };
|
|
463
|
+
/**
|
|
464
|
+
* Write down what Cuna confirmed, so a second `cuna share` on this computer
|
|
465
|
+
* cannot later paint an older answer as this session's state.
|
|
466
|
+
*
|
|
467
|
+
* A store that will not record it is reported rather than swallowed: the
|
|
468
|
+
* consequence is that another window may show something stale, which the owner
|
|
469
|
+
* has to be able to know.
|
|
470
|
+
*/
|
|
471
|
+
const rememberAudience = async (fact) => {
|
|
472
|
+
try {
|
|
473
|
+
await store.recordAudienceFact(fact);
|
|
474
|
+
factNote = undefined;
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
factNote = `Cuna answered, but this computer could not record it where other windows would see it: ${error instanceof Error ? error.message : String(error)}. Another window of this account may still show an older answer as current.`;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
const acceptAudience = async (value) => {
|
|
481
|
+
audience = value;
|
|
482
|
+
refusal = undefined;
|
|
483
|
+
await rememberAudience({ kind: 'transition', agentSessionId: value.agentSessionId, sessionIncarnation: value.sessionIncarnation, processEpoch: value.processEpoch, state: value.state, generation: value.generation });
|
|
484
|
+
await paintAudience();
|
|
485
|
+
};
|
|
486
|
+
const reserve = async (intent) => {
|
|
487
|
+
let reserved;
|
|
488
|
+
try {
|
|
489
|
+
reserved = await store.reserve(intent);
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
throw new OwnerGrantError('unavailable', 'Cuna could not record this change locally, so nothing was sent. Fix the local state directory and try again.', error);
|
|
493
|
+
}
|
|
494
|
+
unresolved = [reserved, ...unresolved];
|
|
495
|
+
return reserved;
|
|
496
|
+
};
|
|
497
|
+
/**
|
|
498
|
+
* Re-read the durable scope immediately before reserving.
|
|
499
|
+
*
|
|
500
|
+
* `unresolved` is this process's view, loaded at start. A second `cuna share`
|
|
501
|
+
* on the same Project writes to the same scope directory, so the in-memory
|
|
502
|
+
* guard alone lets two processes each start a mutation. This closes all but
|
|
503
|
+
* the window between this read and the reserve below; there is no cross-process
|
|
504
|
+
* lock, so it narrows the race rather than removing it.
|
|
505
|
+
*/
|
|
506
|
+
const cleared = async (action, authority) => {
|
|
507
|
+
unresolved = [...await localState('read its record of unresolved changes', () => store.list(abort.signal))];
|
|
508
|
+
const blocking = pendingIn(authority);
|
|
509
|
+
if (!blocking.length)
|
|
510
|
+
return true;
|
|
511
|
+
cursor = Math.max(0, unresolved.indexOf(blocking[0]));
|
|
512
|
+
refusal = `Cannot ${action} while an earlier change has an unknown outcome. Resend it, inspect it, or forget its local record first.`;
|
|
513
|
+
await paintUnresolved();
|
|
514
|
+
return false;
|
|
515
|
+
};
|
|
516
|
+
const readSessions = async () => { listing = await sessions.list(abort.signal); items = listing.items.filter(s => s.projectId === undefined || s.projectId === identity.project); };
|
|
517
|
+
const loadSessions = () => run('Finding your AgentSessions', async () => { failureSubject = 'grant'; await readSessions(); index = 0; failure = undefined; refusal = undefined; await paintSessions(); });
|
|
518
|
+
const loadMembers = (after) => run('Reading Project members', async () => { failureSubject = 'grant'; const page = await api.listMembers(after, abort.signal); members = page.items.filter(m => m.principal_id !== identity.owner); membersAfter = after; membersNext = page.next_after_principal_id; index = 0; await paintMembers(); });
|
|
519
|
+
const create = (input) => run('Creating the observation grant', async () => { failureSubject = 'grant'; if (!await cleared('create a grant', 'grant'))
|
|
520
|
+
return; const op = await reserve({ kind: 'create', operationId: randomUUID(), ...input }); await settleWith(op, () => api.create(input, op.operationId, abort.signal), acceptGrant); });
|
|
521
|
+
const revoke = (g) => run('Requesting revocation', async () => { failureSubject = 'grant'; if (!await cleared('revoke', 'grant'))
|
|
522
|
+
return; const op = await reserve({ kind: 'revoke', operationId: randomUUID(), grantId: g.grant_id, agentSessionId: g.agent_session_id, subjectPrincipalId: g.subject_principal_id, expectedRevision: g.revision }); await settleWith(op, () => api.revoke(g, op.operationId, abort.signal), acceptGrant); });
|
|
523
|
+
/** The session one sharing decision is about, named the way the owner saw it if this process has seen it listed. */
|
|
524
|
+
const audienceTargetFor = (agentSessionId, incarnation) => {
|
|
525
|
+
const known = items.find(s => s.id === agentSessionId);
|
|
526
|
+
return { id: agentSessionId, label: known ? `${known.machineName} / ${known.name}` : agentSessionId, ...(incarnation === undefined ? {} : { incarnation }) };
|
|
527
|
+
};
|
|
528
|
+
/**
|
|
529
|
+
* Start or stop live sharing of one session.
|
|
530
|
+
*
|
|
531
|
+
* Stopping is deliberately NOT gated on an unresolved change. Every other
|
|
532
|
+
* mutation here is blocked while one exists, because starting a second could
|
|
533
|
+
* create a second authority. This one only ever removes access, and an owner
|
|
534
|
+
* who cannot tell whether a session is live is exactly the owner who most needs
|
|
535
|
+
* to be able to make it private. It still takes its own operation identity and
|
|
536
|
+
* never touches, replaces or replays the unresolved record.
|
|
537
|
+
*/
|
|
538
|
+
const changeAudience = (action, target) => run(action === 'publish' ? 'Starting live sharing' : 'Stopping live sharing', async () => {
|
|
539
|
+
failureSubject = 'audience';
|
|
540
|
+
audienceTarget = target;
|
|
541
|
+
if (action === 'publish') {
|
|
542
|
+
if (!await cleared('start live sharing', 'audience'))
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
else
|
|
546
|
+
unresolved = [...await localState('read its record of unresolved changes', () => store.list(abort.signal))];
|
|
547
|
+
const op = await reserve({ kind: 'audience', operationId: randomUUID(), action, agentSessionId: target.id });
|
|
548
|
+
await settleWith(op, () => api.setAudience({ agentSessionId: target.id, action, ...(target.incarnation === undefined ? {} : { sessionIncarnation: target.incarnation }) }, op.operationId, abort.signal), acceptAudience);
|
|
549
|
+
});
|
|
550
|
+
/**
|
|
551
|
+
* Reconcile the receipt a CREATE replay returned.
|
|
552
|
+
*
|
|
553
|
+
* `create_collab_v2_session_observe_grant` answers a replayed operation ID with
|
|
554
|
+
* `return old.result` -- the receipt as it stood when the original request was
|
|
555
|
+
* first answered. Migration 0190 gave the revoke replay a freshly built receipt
|
|
556
|
+
* but left this one a stored snapshot, and it always says `active`, so showing
|
|
557
|
+
* it as the grant's state would report authority that may have been revoked
|
|
558
|
+
* since. Read the current state instead; if that read does not answer, keep
|
|
559
|
+
* the replayed receipt and label it for exactly what it is.
|
|
560
|
+
*/
|
|
561
|
+
const reconcileReplay = async (operationId) => {
|
|
562
|
+
const stored = grant;
|
|
563
|
+
replayed = { operationId };
|
|
564
|
+
try {
|
|
565
|
+
grant = await api.inspect({ grant_id: stored.grant_id, agent_session_id: stored.agent_session_id, subject_principal_id: stored.subject_principal_id, revision: stored.revision }, abort.signal);
|
|
566
|
+
replayed = undefined;
|
|
567
|
+
refusal = undefined;
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
grant = stored;
|
|
571
|
+
replayed = { operationId, reason: (error instanceof OwnerGrantError ? error : classifyTransportFailure(error, false, 'inspect')).message };
|
|
572
|
+
}
|
|
573
|
+
await paintGrant();
|
|
574
|
+
};
|
|
575
|
+
/**
|
|
576
|
+
* Ask the session what it is sharing, optionally about one unfinished change.
|
|
577
|
+
*
|
|
578
|
+
* It takes NO durable record and reserves nothing. A reading is a query with no
|
|
579
|
+
* effect to recover, and its stored request keeps a twenty-second deadline, so
|
|
580
|
+
* a fresh identity every time is the only thing that can work. It is also never
|
|
581
|
+
* gated on an unresolved change: an owner who cannot tell whether a session is
|
|
582
|
+
* shared is exactly the owner who most needs to be able to ask.
|
|
583
|
+
*
|
|
584
|
+
* An answer about the named change SETTLES its record when one exists --
|
|
585
|
+
* `recorded` because the answer is durable, `unknown` because Cuna never issued
|
|
586
|
+
* the request at all. `expired_unrecorded` settles nothing: the effect stays
|
|
587
|
+
* unknown, and only the owner removes that record. It does establish that
|
|
588
|
+
* resending can no longer settle it, which is recorded the same way a refusal
|
|
589
|
+
* that closed the door is.
|
|
590
|
+
*/
|
|
591
|
+
const askAudience = (target, about) => run('Asking what this session is sharing now', async () => {
|
|
592
|
+
failureSubject = 'reading';
|
|
593
|
+
audienceTarget = target;
|
|
594
|
+
audienceAsked = about?.action;
|
|
595
|
+
audienceStale = undefined;
|
|
596
|
+
audienceOtherRun = undefined;
|
|
597
|
+
factNote = undefined;
|
|
598
|
+
const answer = await api.readAudience({ agentSessionId: target.id,
|
|
599
|
+
...(target.incarnation === undefined ? {} : { sessionIncarnation: target.incarnation }),
|
|
600
|
+
...(about === undefined ? {} : { reconcile: { operationId: about.operationId, action: about.action } }) }, randomUUID(), abort.signal);
|
|
601
|
+
// The history half is about one named operation and is independent of every
|
|
602
|
+
// ordering question below, so it settles its record either way.
|
|
603
|
+
if (about !== undefined && answer.operation !== undefined) {
|
|
604
|
+
if (answer.operation.status === 'recorded' || answer.operation.status === 'unknown') {
|
|
605
|
+
settled = { operationId: about.operationId, kind: 'audience', action: about.action };
|
|
606
|
+
await forget(about.operationId);
|
|
607
|
+
}
|
|
608
|
+
else if (answer.operation.status === 'expired_unrecorded')
|
|
609
|
+
unresendable.add(about.operationId);
|
|
610
|
+
}
|
|
611
|
+
// Order the answer against what this computer recorded, read AFTER the answer
|
|
612
|
+
// arrived: another process may have confirmed a change while this question was
|
|
613
|
+
// in flight, and that is exactly the case a floor held in this closure missed.
|
|
614
|
+
const current = answer.current;
|
|
615
|
+
let held = null;
|
|
616
|
+
try {
|
|
617
|
+
held = await store.readAudienceFact(current.agentSessionId);
|
|
618
|
+
}
|
|
619
|
+
catch (error) {
|
|
620
|
+
factNote = `This computer holds a sharing record for this session that could not be read: ${error instanceof Error ? error.message : String(error)}. It was not used to order this answer.`;
|
|
621
|
+
}
|
|
622
|
+
if (held !== null && !audienceFactSameRun(held, current))
|
|
623
|
+
audienceOtherRun = held;
|
|
624
|
+
else if (held !== null && audienceFactIsOlder(current, held))
|
|
625
|
+
audienceStale = held;
|
|
626
|
+
// An answer this computer already knows to be out of date must never become
|
|
627
|
+
// the newest thing it knows.
|
|
628
|
+
if (audienceStale === undefined)
|
|
629
|
+
await rememberAudience({ kind: 'reading', agentSessionId: current.agentSessionId, sessionIncarnation: current.sessionIncarnation, processEpoch: current.processEpoch, state: current.state, generation: current.generation });
|
|
630
|
+
audienceState = answer;
|
|
631
|
+
failure = undefined;
|
|
632
|
+
refusal = undefined;
|
|
633
|
+
await paintAudienceState();
|
|
634
|
+
});
|
|
635
|
+
/** Replay an operation the durable record already owns. It is never re-reserved and never re-identified. */
|
|
636
|
+
const resend = (op) => run(op.kind === 'create' ? 'Resending the same grant request' : op.kind === 'revoke' ? 'Resending the same revocation' : op.action === 'publish' ? 'Resending the same request to share' : 'Resending the same request to stop sharing', async () => {
|
|
637
|
+
failureSubject = op.kind === 'audience' ? 'audience' : 'grant';
|
|
638
|
+
if (op.kind === 'audience') {
|
|
639
|
+
audienceTarget = audienceTargetFor(op.agentSessionId);
|
|
640
|
+
try {
|
|
641
|
+
await settleWith(op, () => api.setAudience({ agentSessionId: op.agentSessionId, action: op.action, replay: true }, op.operationId, abort.signal), acceptAudience);
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
// Record the one thing this refusal does establish: whether this request can
|
|
645
|
+
// still be sent at all. The outcome of the operation stays unknown.
|
|
646
|
+
if (error instanceof OwnerGrantError && !audienceRefusalCanResend(error.details?.reason))
|
|
647
|
+
unresendable.add(op.operationId);
|
|
648
|
+
throw error;
|
|
649
|
+
}
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (op.kind === 'create') {
|
|
653
|
+
await settleWith(op, () => api.create({ agentSessionId: op.agentSessionId, subjectPrincipalId: op.subjectPrincipalId, expectedMembershipRevision: op.expectedMembershipRevision, expiresAtMs: op.expiresAtMs }, op.operationId, abort.signal), async (value) => { grant = value; await reconcileReplay(op.operationId); });
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
await settleWith(op, () => api.revoke({ grant_id: op.grantId, agent_session_id: op.agentSessionId, subject_principal_id: op.subjectPrincipalId, revision: op.expectedRevision }, op.operationId, abort.signal), acceptGrant);
|
|
657
|
+
});
|
|
658
|
+
const inspect = (reference, settling) => run('Inspecting the grant', async () => {
|
|
659
|
+
failureSubject = 'grant';
|
|
660
|
+
grant = await api.inspect(grant?.grant_id === reference.grant_id ? grant : reference, abort.signal);
|
|
661
|
+
// A trustworthy receipt answers the question the record existed to ask,
|
|
662
|
+
// whichever way it went: the revocation either applied or never did.
|
|
663
|
+
if (settling !== undefined)
|
|
664
|
+
await forget(settling);
|
|
665
|
+
failure = undefined;
|
|
666
|
+
refusal = undefined;
|
|
667
|
+
replayed = undefined;
|
|
668
|
+
await paintGrant();
|
|
669
|
+
});
|
|
670
|
+
/** One unresolved operation blocks every NEW change of the SAME authority, so it can never be silently replaced. */
|
|
671
|
+
const blocked = (action, authority) => {
|
|
672
|
+
const blocking = pendingIn(authority);
|
|
673
|
+
if (!blocking.length)
|
|
674
|
+
return false;
|
|
675
|
+
refusal = `Cannot ${action} while an earlier change has an unknown outcome. Resend it, inspect it, or forget its local record first.`;
|
|
676
|
+
cursor = Math.max(0, unresolved.indexOf(blocking[0]));
|
|
677
|
+
void paintUnresolved();
|
|
678
|
+
return true;
|
|
679
|
+
};
|
|
680
|
+
const toUnresolved = (key) => { if (key !== 'p' || !unresolved.length)
|
|
681
|
+
return false; refusal = undefined; cursor = 0; void paintUnresolved(); return true; };
|
|
682
|
+
const remove = host.onInput(bytes => {
|
|
683
|
+
if (stopped)
|
|
684
|
+
return;
|
|
685
|
+
const key = new TextDecoder().decode(bytes);
|
|
686
|
+
if (key === '\x03' || key === '\x1b') {
|
|
687
|
+
stop();
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
if (busy)
|
|
691
|
+
return;
|
|
692
|
+
const up = () => { index = Math.max(0, index - 1); }, down = (n) => { index = Math.min(Math.max(0, n - 1), index + 1); };
|
|
693
|
+
if (phase === 'sessions') {
|
|
694
|
+
if (key === '\x1b[A') {
|
|
695
|
+
up();
|
|
696
|
+
void paintSessions();
|
|
697
|
+
}
|
|
698
|
+
else if (key === '\x1b[B') {
|
|
699
|
+
down(items.length);
|
|
700
|
+
void paintSessions();
|
|
701
|
+
}
|
|
702
|
+
else if (key === '\r' && items[index]) {
|
|
703
|
+
session = items[index];
|
|
704
|
+
void loadMembers(null);
|
|
705
|
+
}
|
|
706
|
+
// Publication is asked for on the session the cursor names, and confirmed
|
|
707
|
+
// before anything is sent.
|
|
708
|
+
else if (key === 's' && items[index]) {
|
|
709
|
+
if (blocked('start live sharing', 'audience'))
|
|
710
|
+
return;
|
|
711
|
+
audienceFrom = 'sessions';
|
|
712
|
+
audienceTarget = audienceTargetFor(items[index].id);
|
|
713
|
+
void paintConfirmShare();
|
|
714
|
+
}
|
|
715
|
+
else if (key === 'e' && items[index]) {
|
|
716
|
+
audienceFrom = 'sessions';
|
|
717
|
+
audienceTarget = audienceTargetFor(items[index].id);
|
|
718
|
+
void paintConfirmStop();
|
|
719
|
+
}
|
|
720
|
+
// Asking is never gated: it changes nothing, and an owner who cannot tell
|
|
721
|
+
// whether a session is shared is the one who most needs to be able to ask.
|
|
722
|
+
else if (key === 'c' && items[index]) {
|
|
723
|
+
audienceFrom = 'sessions';
|
|
724
|
+
void askAudience(audienceTargetFor(items[index].id));
|
|
725
|
+
}
|
|
726
|
+
else if (key === 'r')
|
|
727
|
+
void loadSessions();
|
|
728
|
+
else
|
|
729
|
+
toUnresolved(key);
|
|
730
|
+
}
|
|
731
|
+
else if (phase === 'members') {
|
|
732
|
+
if (key === '\x1b[A') {
|
|
733
|
+
up();
|
|
734
|
+
void paintMembers();
|
|
735
|
+
}
|
|
736
|
+
else if (key === '\x1b[B') {
|
|
737
|
+
down(members.length);
|
|
738
|
+
void paintMembers();
|
|
739
|
+
}
|
|
740
|
+
else if (key === '\r' && members[index]) {
|
|
741
|
+
member = members[index];
|
|
742
|
+
void paintDuration();
|
|
743
|
+
}
|
|
744
|
+
else if (key === 'n' && membersNext)
|
|
745
|
+
void loadMembers(membersNext);
|
|
746
|
+
else if (key === 'r')
|
|
747
|
+
void loadMembers(membersAfter);
|
|
748
|
+
else if (key === 'b')
|
|
749
|
+
void paintSessions();
|
|
750
|
+
else
|
|
751
|
+
toUnresolved(key);
|
|
752
|
+
}
|
|
753
|
+
else if (phase === 'duration') {
|
|
754
|
+
const choice = DURATIONS.find(([k]) => k === key);
|
|
755
|
+
if (choice) {
|
|
756
|
+
if (blocked('create a grant', 'grant'))
|
|
757
|
+
return;
|
|
758
|
+
void create({ agentSessionId: session.id, subjectPrincipalId: member.principal_id, expectedMembershipRevision: member.membership_revision, expiresAtMs: now() + choice[2] });
|
|
759
|
+
}
|
|
760
|
+
else if (key === 'b')
|
|
761
|
+
void paintMembers();
|
|
762
|
+
else
|
|
763
|
+
toUnresolved(key);
|
|
764
|
+
}
|
|
765
|
+
else if (phase === 'grant') {
|
|
766
|
+
if (key === 'i' && grant)
|
|
767
|
+
void inspect(grant);
|
|
768
|
+
// A stored replay is not a reading of current authority, so it may not be
|
|
769
|
+
// the revision a revocation is aimed at. `i` first.
|
|
770
|
+
else if (key === 'x' && grant && !replayed && grant.state === 'active' && !describeGrantState(grant, now()).final) {
|
|
771
|
+
if (blocked('revoke', 'grant'))
|
|
772
|
+
return;
|
|
773
|
+
void revoke(grant);
|
|
774
|
+
}
|
|
775
|
+
// The grant names its session and the exact run of it, so a sharing receipt
|
|
776
|
+
// about a different run can be refused rather than believed.
|
|
777
|
+
else if (key === 's' && grant) {
|
|
778
|
+
if (blocked('start live sharing', 'audience'))
|
|
779
|
+
return;
|
|
780
|
+
audienceFrom = 'grant';
|
|
781
|
+
audienceTarget = audienceTargetFor(grant.agent_session_id, grant.session_incarnation);
|
|
782
|
+
void paintConfirmShare();
|
|
783
|
+
}
|
|
784
|
+
else if (key === 'e' && grant) {
|
|
785
|
+
audienceFrom = 'grant';
|
|
786
|
+
audienceTarget = audienceTargetFor(grant.agent_session_id, grant.session_incarnation);
|
|
787
|
+
void paintConfirmStop();
|
|
788
|
+
}
|
|
789
|
+
else if (key === 'c' && grant) {
|
|
790
|
+
audienceFrom = 'grant';
|
|
791
|
+
void askAudience(audienceTargetFor(grant.agent_session_id, grant.session_incarnation));
|
|
792
|
+
}
|
|
793
|
+
else if (key === 'b') {
|
|
794
|
+
grant = undefined;
|
|
795
|
+
failure = undefined;
|
|
796
|
+
replayed = undefined;
|
|
797
|
+
void loadSessions();
|
|
798
|
+
}
|
|
799
|
+
else
|
|
800
|
+
toUnresolved(key);
|
|
801
|
+
}
|
|
802
|
+
else if (phase === 'confirm-share' || phase === 'confirm-stop') {
|
|
803
|
+
const action = phase === 'confirm-share' ? 'publish' : 'private', target = audienceTarget;
|
|
804
|
+
if (key === 'y')
|
|
805
|
+
void changeAudience(action, target);
|
|
806
|
+
// Cancelling returns to the screen the decision was reached from, so the
|
|
807
|
+
// receipt a person was reading is not lost by declining the next step.
|
|
808
|
+
else if (audienceFrom === 'shared' && audience)
|
|
809
|
+
void paintAudience();
|
|
810
|
+
else if (audienceFrom === 'state' && audienceState)
|
|
811
|
+
void paintAudienceState();
|
|
812
|
+
else {
|
|
813
|
+
audienceTarget = undefined;
|
|
814
|
+
void (audienceFrom === 'grant' && grant ? paintGrant() : items.length ? paintSessions() : loadSessions());
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
else if (phase === 'shared') {
|
|
818
|
+
if (key === 's' && audienceTarget) {
|
|
819
|
+
if (blocked('start live sharing', 'audience'))
|
|
820
|
+
return;
|
|
821
|
+
audienceFrom = 'shared';
|
|
822
|
+
void paintConfirmShare();
|
|
823
|
+
}
|
|
824
|
+
else if (key === 'e' && audienceTarget) {
|
|
825
|
+
audienceFrom = 'shared';
|
|
826
|
+
void paintConfirmStop();
|
|
827
|
+
}
|
|
828
|
+
else if (key === 'c' && audienceTarget) {
|
|
829
|
+
audienceFrom = 'shared';
|
|
830
|
+
void askAudience(audienceTarget);
|
|
831
|
+
}
|
|
832
|
+
else if (key === 'b') {
|
|
833
|
+
audience = undefined;
|
|
834
|
+
audienceTarget = undefined;
|
|
835
|
+
void (items.length ? paintSessions() : loadSessions());
|
|
836
|
+
}
|
|
837
|
+
else
|
|
838
|
+
toUnresolved(key);
|
|
839
|
+
}
|
|
840
|
+
// What the session itself answered. A fresh decision from here is issued
|
|
841
|
+
// against the generation this reading recorded, which is what makes starting
|
|
842
|
+
// or stopping possible again after an acknowledgement was lost.
|
|
843
|
+
else if (phase === 'state') {
|
|
844
|
+
if (key === 's' && audienceTarget) {
|
|
845
|
+
if (blocked('start live sharing', 'audience'))
|
|
846
|
+
return;
|
|
847
|
+
audienceFrom = 'state';
|
|
848
|
+
void paintConfirmShare();
|
|
849
|
+
}
|
|
850
|
+
else if (key === 'e' && audienceTarget) {
|
|
851
|
+
audienceFrom = 'state';
|
|
852
|
+
void paintConfirmStop();
|
|
853
|
+
}
|
|
854
|
+
else if (key === 'c' && audienceTarget) {
|
|
855
|
+
audienceFrom = 'state';
|
|
856
|
+
void askAudience(audienceTarget);
|
|
857
|
+
}
|
|
858
|
+
else if (key === 'b') {
|
|
859
|
+
audienceState = undefined;
|
|
860
|
+
audienceAsked = undefined;
|
|
861
|
+
audienceTarget = undefined;
|
|
862
|
+
void (items.length ? paintSessions() : loadSessions());
|
|
863
|
+
}
|
|
864
|
+
else
|
|
865
|
+
toUnresolved(key);
|
|
866
|
+
}
|
|
867
|
+
else if (phase === 'failure') {
|
|
868
|
+
if (key === 'r' && failure && rereadsMembers(failure))
|
|
869
|
+
void loadMembers(null);
|
|
870
|
+
else if (key === 'i' && grant)
|
|
871
|
+
void inspect(grant);
|
|
872
|
+
else if (key === 'b') {
|
|
873
|
+
failure = undefined;
|
|
874
|
+
grant = undefined;
|
|
875
|
+
replayed = undefined;
|
|
876
|
+
void loadSessions();
|
|
877
|
+
}
|
|
878
|
+
else
|
|
879
|
+
toUnresolved(key);
|
|
880
|
+
}
|
|
881
|
+
else if (phase === 'unresolved') {
|
|
882
|
+
const row = unresolved[cursor];
|
|
883
|
+
if (key === '\x1b[A') {
|
|
884
|
+
cursor = Math.max(0, cursor - 1);
|
|
885
|
+
void paintUnresolved();
|
|
886
|
+
}
|
|
887
|
+
else if (key === '\x1b[B') {
|
|
888
|
+
cursor = Math.min(Math.max(0, unresolved.length - 1), cursor + 1);
|
|
889
|
+
void paintUnresolved();
|
|
890
|
+
}
|
|
891
|
+
else if (key === 'r' && row && !unresendable.has(row.operationId)) {
|
|
892
|
+
refusal = undefined;
|
|
893
|
+
void resend(row);
|
|
894
|
+
}
|
|
895
|
+
else if (key === 'i' && row?.kind === 'revoke') {
|
|
896
|
+
refusal = undefined;
|
|
897
|
+
void inspect({ grant_id: row.grantId }, row.operationId);
|
|
898
|
+
}
|
|
899
|
+
// The safe direction out of an unconfirmed publication, reachable without
|
|
900
|
+
// first settling it: it removes access and starts its own operation.
|
|
901
|
+
else if (key === 'e' && row?.kind === 'audience' && row.action === 'publish') {
|
|
902
|
+
refusal = undefined;
|
|
903
|
+
audienceFrom = 'sessions';
|
|
904
|
+
audienceTarget = audienceTargetFor(row.agentSessionId);
|
|
905
|
+
void paintConfirmStop();
|
|
906
|
+
}
|
|
907
|
+
// The question this record exists around, asked about this exact operation.
|
|
908
|
+
// It is the one action here that can settle the record without a resend.
|
|
909
|
+
else if (key === 'c' && row?.kind === 'audience') {
|
|
910
|
+
refusal = undefined;
|
|
911
|
+
audienceFrom = 'state';
|
|
912
|
+
void askAudience(audienceTargetFor(row.agentSessionId), row);
|
|
913
|
+
}
|
|
914
|
+
else if (key === 'd' && row) {
|
|
915
|
+
forgetTarget = row;
|
|
916
|
+
void paintConfirmForget();
|
|
917
|
+
}
|
|
918
|
+
else if (key === 'b') {
|
|
919
|
+
refusal = undefined;
|
|
920
|
+
void (items.length ? paintSessions() : loadSessions());
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
else if (phase === 'confirm-forget') {
|
|
924
|
+
const op = forgetTarget;
|
|
925
|
+
if (key === 'y')
|
|
926
|
+
void run('Forgetting the local record', async () => {
|
|
927
|
+
await forget(op.operationId);
|
|
928
|
+
forgetTarget = undefined;
|
|
929
|
+
refusal = undefined;
|
|
930
|
+
failure = undefined;
|
|
931
|
+
if (unresolved.some(o => o.operationId === op.operationId))
|
|
932
|
+
await paintUnresolved();
|
|
933
|
+
else {
|
|
934
|
+
forgotten = op;
|
|
935
|
+
await paintForgotten();
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
else {
|
|
939
|
+
forgetTarget = undefined;
|
|
940
|
+
void paintUnresolved();
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
else if (phase === 'forgotten') {
|
|
944
|
+
if (key === 'b') {
|
|
945
|
+
forgotten = undefined;
|
|
946
|
+
void (items.length ? paintSessions() : loadSessions());
|
|
947
|
+
}
|
|
948
|
+
else
|
|
949
|
+
toUnresolved(key);
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
options.signal?.addEventListener('abort', stop, { once: true });
|
|
953
|
+
try {
|
|
954
|
+
if (options.signal?.aborted)
|
|
955
|
+
stop();
|
|
956
|
+
else
|
|
957
|
+
await run('Reading local state', async () => {
|
|
958
|
+
// Recover operation identity from an earlier process before anything else.
|
|
959
|
+
unresolved = [...await localState('read its record of unresolved changes', () => store.list(abort.signal))];
|
|
960
|
+
await readSessions();
|
|
961
|
+
// `--grant` names one explicit subject the owner came here for, and reaching
|
|
962
|
+
// it is how a grant is revoked. An unresolved SHARING change must not hide
|
|
963
|
+
// it: they are separate authorities, the record outlives every retry, and
|
|
964
|
+
// revoking only ever removes access. Recovery is not lost either -- the
|
|
965
|
+
// banner on that screen names it and `p` opens it.
|
|
966
|
+
if (options.initialGrantId) {
|
|
967
|
+
grant = await api.inspect({ grant_id: options.initialGrantId }, abort.signal);
|
|
968
|
+
await paintGrant();
|
|
969
|
+
}
|
|
970
|
+
else if (unresolved.length) {
|
|
971
|
+
cursor = 0;
|
|
972
|
+
await paintUnresolved();
|
|
973
|
+
}
|
|
974
|
+
else
|
|
975
|
+
await paintSessions();
|
|
976
|
+
});
|
|
977
|
+
await done;
|
|
978
|
+
}
|
|
979
|
+
finally {
|
|
980
|
+
stop();
|
|
981
|
+
remove();
|
|
982
|
+
options.signal?.removeEventListener('abort', stop);
|
|
983
|
+
await hostWrites;
|
|
984
|
+
await lease.restore();
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
//# sourceMappingURL=owner-grants-screen.js.map
|