@skrr-ai/cli 0.1.119 → 0.1.121
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/dist/base-command.d.ts +31 -0
- package/dist/base-command.js +117 -6
- package/dist/commands/agent/message/context.d.ts +1 -0
- package/dist/commands/agent/message/context.js +3 -2
- package/dist/commands/commands.js +11 -0
- package/dist/commands/commitments/action-proposal.d.ts +13 -0
- package/dist/commands/commitments/action-proposal.js +39 -0
- package/dist/commands/commitments/action-proposals/decide.js +8 -1
- package/dist/commands/commitments/action-proposals/execute.js +11 -1
- package/dist/commands/commitments/create.js +2 -2
- package/dist/commands/commitments/doctor.d.ts +8 -0
- package/dist/commands/commitments/doctor.js +22 -0
- package/dist/commands/commitments/execute-remediation.js +12 -1
- package/dist/commands/commitments/observations.js +2 -2
- package/dist/commands/commitments/pack/bind.d.ts +18 -1
- package/dist/commands/commitments/pack/bind.js +66 -8
- package/dist/commands/commitments/pack/plan.d.ts +4 -1
- package/dist/commands/commitments/pack/plan.js +30 -2
- package/dist/commands/commitments/update.js +8 -2
- package/dist/commands/completion/index.js +1 -1
- package/dist/commands/completion/values.js +1 -1
- package/dist/commands/computer-use/audio.d.ts +1 -0
- package/dist/commands/computer-use/audio.js +1 -0
- package/dist/commands/computer-use/browser.d.ts +1 -0
- package/dist/commands/computer-use/browser.js +1 -0
- package/dist/commands/computer-use/disable.d.ts +1 -0
- package/dist/commands/computer-use/disable.js +1 -0
- package/dist/commands/computer-use/enable.d.ts +1 -0
- package/dist/commands/computer-use/enable.js +1 -0
- package/dist/commands/computer-use/index.d.ts +1 -0
- package/dist/commands/computer-use/index.js +1 -0
- package/dist/commands/computer-use/install.d.ts +1 -0
- package/dist/commands/computer-use/install.js +1 -0
- package/dist/commands/computer-use/recover.d.ts +1 -0
- package/dist/commands/computer-use/recover.js +1 -0
- package/dist/commands/computer-use/status.d.ts +1 -0
- package/dist/commands/computer-use/status.js +1 -0
- package/dist/commands/computer-use/stop.d.ts +1 -0
- package/dist/commands/computer-use/stop.js +1 -0
- package/dist/commands/context.js +16 -11
- package/dist/commands/convos/recovery.js +1 -1
- package/dist/commands/daemon/diagnostics.d.ts +9 -0
- package/dist/commands/daemon/diagnostics.js +53 -0
- package/dist/commands/daemon/index.js +2 -0
- package/dist/commands/daemon/maintenance/attest.d.ts +15 -0
- package/dist/commands/daemon/maintenance/attest.js +52 -0
- package/dist/commands/daemon/maintenance/hold.d.ts +17 -0
- package/dist/commands/daemon/maintenance/hold.js +70 -0
- package/dist/commands/daemon/maintenance/index.d.ts +12 -0
- package/dist/commands/daemon/maintenance/index.js +30 -0
- package/dist/commands/daemon/maintenance/resume.d.ts +13 -0
- package/dist/commands/daemon/maintenance/resume.js +36 -0
- package/dist/commands/daemon/maintenance/status.d.ts +13 -0
- package/dist/commands/daemon/maintenance/status.js +36 -0
- package/dist/commands/doctor.d.ts +4 -1
- package/dist/commands/doctor.js +50 -6
- package/dist/commands/followups/plans/cancel-batch.js +6 -3
- package/dist/commands/followups/plans/capacity.js +7 -3
- package/dist/commands/followups/plans/observability.js +11 -3
- package/dist/commands/followups/plans/preferences/set.js +26 -8
- package/dist/commands/followups/plans/preferences.js +1 -1
- package/dist/commands/followups/plans/show.js +1 -1
- package/dist/commands/goals/key-results/assess.d.ts +36 -0
- package/dist/commands/goals/key-results/assess.js +110 -0
- package/dist/commands/goals/key-results/history.d.ts +30 -0
- package/dist/commands/goals/key-results/history.js +104 -0
- package/dist/commands/goals/key-results/list.d.ts +5 -4
- package/dist/commands/goals/key-results/list.js +12 -6
- package/dist/commands/goals/key-results/show.d.ts +4 -2
- package/dist/commands/goals/key-results/show.js +25 -4
- package/dist/commands/goals/key-results/update.d.ts +4 -0
- package/dist/commands/goals/key-results/update.js +18 -24
- package/dist/commands/goals/plan-now.js +12 -1
- package/dist/commands/goals/planner-compare.d.ts +30 -0
- package/dist/commands/goals/planner-compare.js +136 -0
- package/dist/commands/machines/dedicated/service/exec.d.ts +3 -0
- package/dist/commands/machines/dedicated/service/exec.js +70 -11
- package/dist/commands/machines/hosted/destroy.d.ts +1 -0
- package/dist/commands/machines/hosted/destroy.js +11 -1
- package/dist/commands/machines/hosted/exec.d.ts +1 -0
- package/dist/commands/machines/hosted/exec.js +12 -1
- package/dist/commands/machines/hosted/index.js +1 -0
- package/dist/commands/machines/hosted/operation.d.ts +14 -0
- package/dist/commands/machines/hosted/operation.js +51 -0
- package/dist/commands/machines/hosted/pull.js +1 -1
- package/dist/commands/machines/hosted/start.js +6 -2
- package/dist/commands/search/agents.js +5 -1
- package/dist/commands/search/all.js +74 -22
- package/dist/commands/search/skills.d.ts +1 -0
- package/dist/commands/search/skills.js +12 -1
- package/dist/commands/search/spaces.d.ts +8 -1
- package/dist/commands/search/spaces.js +50 -12
- package/dist/commands/search/tasks.d.ts +13 -2
- package/dist/commands/search/tasks.js +78 -14
- package/dist/commands/search/teams.js +5 -1
- package/dist/commands/search/workspace.d.ts +19 -0
- package/dist/commands/search/workspace.js +126 -0
- package/dist/commands/skills/browse.d.ts +1 -0
- package/dist/commands/skills/browse.js +8 -1
- package/dist/commands/skills/search.d.ts +1 -0
- package/dist/commands/skills/search.js +12 -1
- package/dist/commands/spaces/auto-executor/compare.d.ts +18 -0
- package/dist/commands/spaces/auto-executor/compare.js +139 -0
- package/dist/commands/spaces/auto-executor/runs.d.ts +3 -0
- package/dist/commands/spaces/auto-executor/runs.js +13 -1
- package/dist/commands/spaces/charter/create-from-description.d.ts +21 -0
- package/dist/commands/spaces/charter/create-from-description.js +77 -0
- package/dist/commands/spaces/charter/show.d.ts +22 -0
- package/dist/commands/spaces/charter/show.js +61 -0
- package/dist/commands/spaces/heartbeats/list.d.ts +1 -16
- package/dist/commands/spaces/heartbeats/list.js +13 -0
- package/dist/commands/spaces/install-template.js +3 -0
- package/dist/commands/spaces/list.d.ts +1 -0
- package/dist/commands/spaces/list.js +12 -3
- package/dist/commands/spaces/loops/migrate.d.ts +32 -0
- package/dist/commands/spaces/loops/migrate.js +103 -0
- package/dist/commands/spaces/loops/release.d.ts +14 -0
- package/dist/commands/spaces/loops/release.js +59 -0
- package/dist/commands/spaces/notebook.d.ts +25 -0
- package/dist/commands/spaces/notebook.js +75 -0
- package/dist/commands/spaces/proposals/discard.d.ts +17 -0
- package/dist/commands/spaces/proposals/discard.js +17 -0
- package/dist/commands/spaces/proposals/keep.d.ts +17 -0
- package/dist/commands/spaces/proposals/keep.js +17 -0
- package/dist/commands/spaces/proposals/list.d.ts +24 -0
- package/dist/commands/spaces/proposals/list.js +89 -0
- package/dist/commands/spaces/search.d.ts +1 -0
- package/dist/commands/spaces/search.js +9 -0
- package/dist/commands/spaces/skills/propose.d.ts +29 -0
- package/dist/commands/spaces/skills/propose.js +82 -0
- package/dist/commands/spaces/templates/fork.js +3 -0
- package/dist/commands/spaces/triggers/show.js +6 -0
- package/dist/commands/spaces/workflows/retry.d.ts +1 -0
- package/dist/commands/spaces/workflows/retry.js +8 -1
- package/dist/commands/spaces/workflows/triggers/create.js +3 -2
- package/dist/commands/spaces/workflows/triggers/fire.js +4 -0
- package/dist/commands/spaces/workflows/triggers/update.js +2 -1
- package/dist/commands/spaces/write-policy/set.d.ts +22 -0
- package/dist/commands/spaces/write-policy/set.js +82 -0
- package/dist/commands/spaces/write-policy/show.d.ts +19 -0
- package/dist/commands/spaces/write-policy/show.js +49 -0
- package/dist/commands/store/artifacts/receipt.d.ts +14 -0
- package/dist/commands/store/artifacts/receipt.js +34 -0
- package/dist/commands/store/artifacts/release.d.ts +2 -0
- package/dist/commands/store/artifacts/release.js +11 -0
- package/dist/commands/store/governance/publish-release.d.ts +2 -0
- package/dist/commands/store/governance/publish-release.js +30 -1
- package/dist/commands/store/governance/release-receipt.d.ts +12 -0
- package/dist/commands/store/governance/release-receipt.js +32 -0
- package/dist/commands/tasks/approvals/list.d.ts +11 -5
- package/dist/commands/tasks/approvals/list.js +13 -6
- package/dist/commands/tasks/attachments/facts.d.ts +12 -0
- package/dist/commands/tasks/attachments/facts.js +29 -0
- package/dist/commands/tasks/attachments/upload.d.ts +1 -0
- package/dist/commands/tasks/attachments/upload.js +16 -0
- package/dist/commands/tasks/bulk-update.js +35 -1
- package/dist/commands/tasks/context.d.ts +15 -0
- package/dist/commands/tasks/context.js +69 -0
- package/dist/commands/tasks/copy.d.ts +18 -0
- package/dist/commands/tasks/copy.js +85 -0
- package/dist/commands/tasks/create-tree.js +97 -23
- package/dist/commands/tasks/create.js +9 -1
- package/dist/commands/tasks/deliverable/add.js +20 -10
- package/dist/commands/tasks/move.js +8 -1
- package/dist/commands/tasks/operations/show.d.ts +16 -0
- package/dist/commands/tasks/operations/show.js +98 -0
- package/dist/commands/tasks/operations/wait.d.ts +22 -0
- package/dist/commands/tasks/operations/wait.js +114 -0
- package/dist/commands/tasks/preflight.d.ts +1 -0
- package/dist/commands/tasks/preflight.js +11 -1
- package/dist/commands/tasks/rearm-recurrence.js +4 -0
- package/dist/commands/tasks/result/show.js +22 -0
- package/dist/commands/tasks/result/submit.d.ts +1 -0
- package/dist/commands/tasks/result/submit.js +3 -0
- package/dist/commands/tasks/retry-receipt.d.ts +15 -0
- package/dist/commands/tasks/retry-receipt.js +89 -0
- package/dist/commands/tasks/retry-review.d.ts +16 -0
- package/dist/commands/tasks/retry-review.js +66 -0
- package/dist/commands/tasks/retry.d.ts +4 -8
- package/dist/commands/tasks/retry.js +34 -68
- package/dist/commands/tasks/runs.d.ts +21 -0
- package/dist/commands/tasks/runs.js +95 -0
- package/dist/commands/tasks/search.d.ts +1 -0
- package/dist/commands/tasks/search.js +9 -0
- package/dist/commands/tasks/show.d.ts +2 -0
- package/dist/commands/tasks/show.js +15 -1
- package/dist/commands/tasks/start-receipt.d.ts +15 -0
- package/dist/commands/tasks/start-receipt.js +95 -0
- package/dist/commands/tasks/start.d.ts +4 -0
- package/dist/commands/tasks/start.js +49 -3
- package/dist/commands/tasks/update.js +3 -0
- package/dist/commands/tasks/upsert.js +17 -3
- package/dist/commands/tasks/watch.d.ts +3 -0
- package/dist/commands/tasks/watch.js +85 -20
- package/dist/commands/wiki/history.d.ts +33 -0
- package/dist/commands/wiki/history.js +134 -0
- package/dist/commands/wiki/ls.d.ts +2 -0
- package/dist/commands/wiki/ls.js +8 -0
- package/dist/commands/wiki/update.d.ts +3 -1
- package/dist/commands/wiki/update.js +24 -2
- package/dist/commands/wiki/write.d.ts +22 -0
- package/dist/commands/wiki/write.js +144 -6
- package/dist/generated/agent-guides.json +81 -81
- package/dist/generated/managed-instruction-guides.json +1 -1
- package/dist/lib/agent-guides.d.ts +9 -0
- package/dist/lib/agent-guides.js +23 -1
- package/dist/lib/agentic-stream.d.ts +5 -0
- package/dist/lib/agentic-stream.js +122 -3
- package/dist/lib/api-error-reason.d.ts +2 -0
- package/dist/lib/api-error-reason.js +14 -0
- package/dist/lib/api-fetch.js +3 -0
- package/dist/lib/auth-diagnostics.d.ts +39 -0
- package/dist/lib/auth-diagnostics.js +71 -0
- package/dist/lib/cli-client-identity.d.ts +6 -0
- package/dist/lib/cli-client-identity.js +16 -0
- package/dist/lib/cli-installation-diagnostics.d.ts +52 -0
- package/dist/lib/cli-installation-diagnostics.js +254 -0
- package/dist/lib/command-hygiene.d.ts +45 -0
- package/dist/lib/command-hygiene.js +413 -0
- package/dist/lib/commitments.d.ts +64 -3
- package/dist/lib/commitments.js +34 -6
- package/dist/lib/completion-index.d.ts +16 -4
- package/dist/lib/completion-index.js +50 -14
- package/dist/lib/config.js +7 -1
- package/dist/lib/daemon-maintenance-command.d.ts +22 -0
- package/dist/lib/daemon-maintenance-command.js +39 -0
- package/dist/lib/daemon-maintenance.d.ts +275 -0
- package/dist/lib/daemon-maintenance.js +1035 -0
- package/dist/lib/dedicated-service.d.ts +3 -2
- package/dist/lib/dedicated-service.js +11 -3
- package/dist/lib/governed-store.d.ts +8 -0
- package/dist/lib/governed-store.js +5 -0
- package/dist/lib/hosted-machines.d.ts +33 -2
- package/dist/lib/hosted-machines.js +19 -1
- package/dist/lib/json-output.d.ts +1 -1
- package/dist/lib/machines.d.ts +9 -1
- package/dist/lib/machines.js +15 -1
- package/dist/lib/node-adapter.js +13 -0
- package/dist/lib/record-output.d.ts +22 -0
- package/dist/lib/record-output.js +78 -0
- package/dist/lib/roles.d.ts +3 -3
- package/dist/lib/scoped-memory-command.d.ts +18 -0
- package/dist/lib/scoped-memory-command.js +53 -2
- package/dist/lib/search-display.d.ts +7 -0
- package/dist/lib/search-display.js +31 -0
- package/dist/lib/skill-store-pagination.d.ts +8 -0
- package/dist/lib/skill-store-pagination.js +35 -0
- package/dist/lib/space-charter.d.ts +49 -0
- package/dist/lib/space-charter.js +78 -0
- package/dist/lib/space-loop-migration.d.ts +18 -0
- package/dist/lib/space-loop-migration.js +128 -0
- package/dist/lib/space-notebook.d.ts +22 -0
- package/dist/lib/space-notebook.js +140 -0
- package/dist/lib/space-proposal-decision-command.d.ts +36 -0
- package/dist/lib/space-proposal-decision-command.js +108 -0
- package/dist/lib/space-proposals.d.ts +44 -0
- package/dist/lib/space-proposals.js +186 -0
- package/dist/lib/space-templates.d.ts +8 -0
- package/dist/lib/space-templates.js +17 -0
- package/dist/lib/space-write-policy.d.ts +24 -0
- package/dist/lib/space-write-policy.js +103 -0
- package/dist/lib/task-agent-context-capabilities.d.ts +10 -0
- package/dist/lib/task-agent-context-capabilities.js +22 -0
- package/dist/lib/task-agent-context-compatibility.d.ts +9 -0
- package/dist/lib/task-agent-context-compatibility.js +30 -0
- package/dist/lib/task-asset-upload.d.ts +22 -0
- package/dist/lib/task-asset-upload.js +257 -29
- package/dist/lib/task-batch.d.ts +15 -1
- package/dist/lib/task-batch.js +35 -2
- package/dist/lib/task-operation-watch.d.ts +66 -0
- package/dist/lib/task-operation-watch.js +159 -0
- package/dist/lib/task-resolver.d.ts +1 -1
- package/dist/lib/task-resolver.js +4 -3
- package/dist/lib/task-work.d.ts +1 -1
- package/dist/lib/task-work.js +35 -1
- package/dist/lib/task-write-attribution.d.ts +14 -0
- package/dist/lib/task-write-attribution.js +35 -2
- package/dist/lib/triggers.d.ts +11 -0
- package/dist/lib/triggers.js +7 -0
- package/dist/lib/wiki-update.d.ts +1 -0
- package/dist/lib/wiki-update.js +10 -1
- package/dist/lib/wiki.js +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/computerWire.js +31 -15
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +10 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +147 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonConsentProof.d.ts +20 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonConsentProof.js +72 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonWire.js +13 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopDaemonProof.d.ts +21 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopDaemonProof.js +96 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +2 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +102 -20
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/guidedUpdate.d.ts +370 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/guidedUpdate.js +315 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.d.ts +27 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +161 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineScopes.d.ts +26 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineScopes.js +41 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +15 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +20 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/maintenanceReportWire.d.ts +178 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/maintenanceReportWire.js +262 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/toolApprovalSignature.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/toolApprovalSignature.js +106 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/computerWire.js +32 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +10 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +172 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonConsentProof.d.ts +20 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonConsentProof.js +43 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonWire.js +33 -14
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopDaemonProof.d.ts +21 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopDaemonProof.js +62 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +2 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +133 -25
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/guidedUpdate.d.ts +370 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/guidedUpdate.js +404 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.d.ts +27 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +132 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineScopes.d.ts +26 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineScopes.js +15 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +28 -15
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/maintenanceReportWire.d.ts +178 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/maintenanceReportWire.js +342 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/toolApprovalSignature.d.ts +51 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/toolApprovalSignature.js +138 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +51 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +27181 -20293
- package/oclif.manifest.json +5745 -1813
- package/package.json +20 -2
|
@@ -31,9 +31,10 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import fs from 'node:fs';
|
|
33
33
|
import path from 'node:path';
|
|
34
|
+
import lockfile from 'proper-lockfile';
|
|
34
35
|
import { buildProof as buildDeviceProof, generateDeviceKeyPair, jwkThumbprint, } from './deviceKey.js';
|
|
35
36
|
import { hasCredEnvelopePathConflict, isCredEnvelopeActive, maybeDecryptOnRead, splitKeyConvergencePolicy, wrapIfActiveOrPassthrough, } from './credentialEnvelopeBridge.js';
|
|
36
|
-
import { consolidateProfileState, profileStateFilePathsToClear, resolveProfileStateDir, } from './profileStateDir.js';
|
|
37
|
+
import { consolidateProfileState, profileStateFilePathsToClear, profileStateDir, resolveProfileStateDir, } from './profileStateDir.js';
|
|
37
38
|
import { emitAuthTelemetry, getAuthLogger } from './runtime.js';
|
|
38
39
|
const PUBLIC_KEY_FILENAME = 'device-public-key.jwk';
|
|
39
40
|
const PRIVATE_KEY_FILENAME = 'device-private-key';
|
|
@@ -56,6 +57,34 @@ const DEVICE_IDENTITY_FILES = [
|
|
|
56
57
|
// so every CLI command does not repeat the same guaranteed 404.
|
|
57
58
|
const DAEMON_NOT_FOUND_BACKOFF_MS = 15 * 60 * 1000;
|
|
58
59
|
let _state = { kind: 'uninit' };
|
|
60
|
+
let _initializing = null;
|
|
61
|
+
/**
|
|
62
|
+
* The CLI, daemon, and desktop's short-lived signer resolve the same canonical
|
|
63
|
+
* profile directory even when an older private key still lives in the nested
|
|
64
|
+
* legacy directory. Serialize the entire read/consolidate/create transaction;
|
|
65
|
+
* otherwise two first starts can each retain a different key in memory.
|
|
66
|
+
*
|
|
67
|
+
* Never proceed unlocked. The long stale interval favors preserving a pinned
|
|
68
|
+
* identity if a process is paused; a contended caller fails and can retry.
|
|
69
|
+
* Versions that predate this lock do not participate and still require an
|
|
70
|
+
* orderly upgrade before using the new-profile authorization path.
|
|
71
|
+
*/
|
|
72
|
+
async function withDeviceIdentityLock(profile, body) {
|
|
73
|
+
const dir = profileStateDir(profile);
|
|
74
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
75
|
+
const release = await lockfile.lock(dir, {
|
|
76
|
+
realpath: false,
|
|
77
|
+
lockfilePath: path.join(dir, '.device-identity.lock'),
|
|
78
|
+
stale: 10 * 60 * 1000,
|
|
79
|
+
retries: { retries: 20, minTimeout: 100, maxTimeout: 500, factor: 1.25 },
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
return await body();
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
await release();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
59
88
|
/** @internal Test seam — force-set state. */
|
|
60
89
|
export function __setStateForTest(next) {
|
|
61
90
|
const prev = _state;
|
|
@@ -72,6 +101,11 @@ export function __getStateForTest() {
|
|
|
72
101
|
* that decides which copy the group is read from.
|
|
73
102
|
*/
|
|
74
103
|
function profileConfigDir(profile) {
|
|
104
|
+
// A reserved independent profile has a server-pinned owner and device key.
|
|
105
|
+
// An older nested copy is not authority to replace or shadow its canonical
|
|
106
|
+
// key, even when generic profile-convergence policy favors that copy.
|
|
107
|
+
if (/^desktop-bg-[a-f0-9]{32}$/i.test(profile))
|
|
108
|
+
return profileStateDir(profile);
|
|
75
109
|
return resolveProfileStateDir(profile, PRIVATE_KEY_FILENAME).dir;
|
|
76
110
|
}
|
|
77
111
|
function publicKeyPath(profile) {
|
|
@@ -177,6 +211,50 @@ function persistKeypair(profile, kp) {
|
|
|
177
211
|
});
|
|
178
212
|
return { envelopeProtected };
|
|
179
213
|
}
|
|
214
|
+
/** Reserved identities are read without following links or accepting an
|
|
215
|
+
* unbounded, shared, unsealed or concurrently replaced key file. */
|
|
216
|
+
function readIndependentIdentityFile(file, sealed) {
|
|
217
|
+
if (typeof fs.constants.O_NOFOLLOW !== 'number' || typeof fs.constants.O_NONBLOCK !== 'number')
|
|
218
|
+
return null;
|
|
219
|
+
let fd;
|
|
220
|
+
try {
|
|
221
|
+
fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const before = fs.fstatSync(fd);
|
|
228
|
+
if (!before.isFile() || before.nlink !== 1 || before.size > 16_384 || (before.mode & 0o077) !== 0 ||
|
|
229
|
+
(typeof process.getuid === 'function' && before.uid !== process.getuid()))
|
|
230
|
+
return null;
|
|
231
|
+
const bytes = Buffer.alloc(before.size);
|
|
232
|
+
let offset = 0;
|
|
233
|
+
while (offset < bytes.length) {
|
|
234
|
+
const count = fs.readSync(fd, bytes, offset, bytes.length - offset, offset);
|
|
235
|
+
if (count <= 0)
|
|
236
|
+
return null;
|
|
237
|
+
offset += count;
|
|
238
|
+
}
|
|
239
|
+
const after = fs.fstatSync(fd);
|
|
240
|
+
const current = fs.lstatSync(file);
|
|
241
|
+
if (!current.isFile() || current.isSymbolicLink() || current.dev !== before.dev || current.ino !== before.ino ||
|
|
242
|
+
current.size !== before.size || current.nlink !== before.nlink || current.mode !== before.mode ||
|
|
243
|
+
current.uid !== before.uid || current.mtimeMs !== before.mtimeMs || current.ctimeMs !== before.ctimeMs ||
|
|
244
|
+
before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.nlink !== after.nlink ||
|
|
245
|
+
before.mode !== after.mode || before.uid !== after.uid || before.mtimeMs !== after.mtimeMs ||
|
|
246
|
+
before.ctimeMs !== after.ctimeMs)
|
|
247
|
+
return null;
|
|
248
|
+
const value = bytes.toString('utf8');
|
|
249
|
+
return sealed && !value.startsWith('osk-env1:') ? null : value;
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
fs.closeSync(fd);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
180
258
|
/**
|
|
181
259
|
* Load a previously-persisted keypair. Returns null on missing files,
|
|
182
260
|
* decrypt failure, public/private mismatch (file-swap defense), or
|
|
@@ -185,11 +263,13 @@ function persistKeypair(profile, kp) {
|
|
|
185
263
|
function loadKeypair(profile) {
|
|
186
264
|
const pubPath = publicKeyPath(profile);
|
|
187
265
|
const privPath = privateKeyPath(profile);
|
|
188
|
-
|
|
266
|
+
const independent = /^desktop-bg-[a-f0-9]{32}$/i.test(profile);
|
|
267
|
+
if (!independent && (!fs.existsSync(pubPath) || !fs.existsSync(privPath)))
|
|
268
|
+
return null;
|
|
269
|
+
const pubRaw = independent ? readIndependentIdentityFile(pubPath, false) : fs.readFileSync(pubPath, 'utf-8');
|
|
270
|
+
const privRaw = independent ? readIndependentIdentityFile(privPath, true) : fs.readFileSync(privPath, 'utf-8');
|
|
271
|
+
if (pubRaw === null || privRaw === null)
|
|
189
272
|
return null;
|
|
190
|
-
}
|
|
191
|
-
const pubRaw = fs.readFileSync(pubPath, 'utf-8');
|
|
192
|
-
const privRaw = fs.readFileSync(privPath, 'utf-8');
|
|
193
273
|
let publicJwk;
|
|
194
274
|
let storedThumbprint;
|
|
195
275
|
try {
|
|
@@ -348,23 +428,47 @@ async function consolidateDeviceIdentity(profile) {
|
|
|
348
428
|
* `createIfMissing: false` stops before step 2 (disabled+no_stored_key) and
|
|
349
429
|
* never replaces anything: for a short-lived process that must only READ the
|
|
350
430
|
* daemon's key while the daemon itself may be creating it (OSK-15990's
|
|
351
|
-
* `skrrd device-proof`).
|
|
352
|
-
*
|
|
353
|
-
* memory and the disk another.
|
|
431
|
+
* `skrrd device-proof`). Readers and writers both take the profile lock so
|
|
432
|
+
* they cannot observe an incomplete write from another updated process.
|
|
354
433
|
*/
|
|
355
434
|
export async function initDeviceIdentity(profile, options = {}) {
|
|
435
|
+
if (_initializing)
|
|
436
|
+
return _initializing;
|
|
356
437
|
if (_state.kind !== 'uninit')
|
|
357
438
|
return;
|
|
358
|
-
if (typeof profile !== 'string' ||
|
|
439
|
+
if (typeof profile !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(profile)) {
|
|
359
440
|
_state = { kind: 'disabled', reason: 'init_failed' };
|
|
360
|
-
emit('init_failed', false, { reason: '
|
|
441
|
+
emit('init_failed', false, { reason: 'invalid_profile' });
|
|
361
442
|
return;
|
|
362
443
|
}
|
|
444
|
+
const job = withDeviceIdentityLock(profile, async () => {
|
|
445
|
+
if (_state.kind === 'uninit')
|
|
446
|
+
await initDeviceIdentityLocked(profile, options);
|
|
447
|
+
}).catch((err) => {
|
|
448
|
+
_state = { kind: 'disabled', reason: 'init_failed' };
|
|
449
|
+
emit('init_failed', false, {
|
|
450
|
+
profile,
|
|
451
|
+
reason: 'identity_lock_unavailable',
|
|
452
|
+
message: err instanceof Error ? err.message : 'unknown',
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
_initializing = job;
|
|
456
|
+
try {
|
|
457
|
+
await job;
|
|
458
|
+
}
|
|
459
|
+
finally {
|
|
460
|
+
if (_initializing === job)
|
|
461
|
+
_initializing = null;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
async function initDeviceIdentityLocked(profile, options = {}) {
|
|
363
465
|
// OSK-10314 — converge the two directories this profile may have before
|
|
364
466
|
// resolving any of the four files. Must run after `initCredEnvelope`, which
|
|
365
467
|
// both consumers already guarantee, because the private key's readability
|
|
366
468
|
// depends on which DEK ended up active.
|
|
367
|
-
|
|
469
|
+
const independent = /^desktop-bg-[a-f0-9]{32}$/i.test(profile);
|
|
470
|
+
if (!independent)
|
|
471
|
+
await consolidateDeviceIdentity(profile);
|
|
368
472
|
let keypair = null;
|
|
369
473
|
try {
|
|
370
474
|
keypair = loadKeypair(profile);
|
|
@@ -394,12 +498,12 @@ export async function initDeviceIdentity(profile, options = {}) {
|
|
|
394
498
|
// DEVICE_KEY_MISMATCH. Only an explicit interactive login may replace it;
|
|
395
499
|
// that login proves the new key and re-pins it before issuing credentials.
|
|
396
500
|
const hasStoredKey = storedKeyMaterialExists(profile);
|
|
397
|
-
if (hasStoredKey && !options.recoverUnreadable) {
|
|
501
|
+
if (hasStoredKey && (!options.recoverUnreadable || independent)) {
|
|
398
502
|
_state = { kind: 'disabled', reason: 'unreadable_key' };
|
|
399
503
|
emit('recovery_required', false, { profile, reason: 'stored_key_unreadable' });
|
|
400
504
|
return;
|
|
401
505
|
}
|
|
402
|
-
if (options.createIfMissing === false) {
|
|
506
|
+
if (options.createIfMissing === false || (independent && options.createIfMissing !== true)) {
|
|
403
507
|
_state = { kind: 'disabled', reason: 'no_stored_key' };
|
|
404
508
|
emit('no_stored_key', false, { profile });
|
|
405
509
|
return;
|
|
@@ -448,25 +552,29 @@ export async function initDeviceIdentity(profile, options = {}) {
|
|
|
448
552
|
* the daemon's pinned identity.
|
|
449
553
|
*/
|
|
450
554
|
export async function resetDeviceIdentity(opts) {
|
|
555
|
+
if (_initializing)
|
|
556
|
+
await _initializing;
|
|
451
557
|
if (opts?.clearOnDisk && opts.profile) {
|
|
452
558
|
// Every location, not just the resolved one. A reset that cleared only the
|
|
453
559
|
// canonical copy would leave the pre-consolidation copy for the next init to
|
|
454
560
|
// adopt — a deleted device key that reappears.
|
|
455
561
|
const targets = [PUBLIC_KEY_FILENAME, PRIVATE_KEY_FILENAME].flatMap((name) => profileStateFilePathsToClear(opts.profile, name));
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
562
|
+
await withDeviceIdentityLock(opts.profile, async () => {
|
|
563
|
+
for (const p of targets) {
|
|
564
|
+
try {
|
|
565
|
+
fs.unlinkSync(p);
|
|
566
|
+
}
|
|
567
|
+
catch (err) {
|
|
568
|
+
if (err.code !== 'ENOENT') {
|
|
569
|
+
emit('clear_failed', false, {
|
|
570
|
+
profile: opts.profile,
|
|
571
|
+
file: path.basename(p),
|
|
572
|
+
message: err?.message ?? 'unknown',
|
|
573
|
+
});
|
|
574
|
+
}
|
|
467
575
|
}
|
|
468
576
|
}
|
|
469
|
-
}
|
|
577
|
+
});
|
|
470
578
|
}
|
|
471
579
|
_state = { kind: 'uninit' };
|
|
472
580
|
}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure core of a GUIDED update (OSK-19107, Space OSK-S-524, tasks M51 to M53):
|
|
3
|
+
* hold the machine, wait for it to be quiet, update, verify, and give the machine
|
|
4
|
+
* back. The Desktop app and a standalone `skrr` install both run it, so what they
|
|
5
|
+
* decide on, and the words they use for what is in the way, cannot drift apart.
|
|
6
|
+
*
|
|
7
|
+
* This file is DECISIONS ONLY. It performs no I/O, reads no clock and holds no
|
|
8
|
+
* state, so every rule below is a table test. The orchestration (a persisted flow
|
|
9
|
+
* record, polling, the installer, the supervisor) lives with each caller, because
|
|
10
|
+
* that is the part that differs between a Desktop that quits mid-flow and a CLI that
|
|
11
|
+
* runs in the foreground.
|
|
12
|
+
*
|
|
13
|
+
* ## Why these rules (each names the failure it prevents)
|
|
14
|
+
*
|
|
15
|
+
* - **`prepare` is a probe that briefly refuses admitted work.** While it runs the
|
|
16
|
+
* daemon refuses admitted tool calls with an infra-class error, and a tool-proxy Run
|
|
17
|
+
* that sees one drops its tools to the Hosted Machine for good. So a guided flow
|
|
18
|
+
* calls it ONLY when the server's reading says there is nothing admitted to refuse
|
|
19
|
+
* ({@link prepareBasis}), never to find out whether there is.
|
|
20
|
+
* - **`unknown` is an answer.** A source nobody could read is never zero and never
|
|
21
|
+
* ready. {@link noServerVeto} is deliberately strict: every server source must have
|
|
22
|
+
* been read, and none may list active or unproven work.
|
|
23
|
+
* - **A server source can only veto.** Only the daemon's own positively announced
|
|
24
|
+
* report grants `ready`; the owner's attestation and the daemon's own `prepare`
|
|
25
|
+
* (labelled `no_veto`, D38) are the two other bases, and each is labelled as what
|
|
26
|
+
* it is wherever it is shown.
|
|
27
|
+
* - **Blockers are shown in the words of whoever knows them.** The daemon's blocker
|
|
28
|
+
* `reason` and the server's holder `label` are the vocabulary; nothing here
|
|
29
|
+
* re-spells them. They are bounded and never a raw path, because a diagnostic can
|
|
30
|
+
* carry one.
|
|
31
|
+
* - **A new PID, an exit code or a cloud connection is not a verified update.**
|
|
32
|
+
* {@link verifyReplacement} requires the daemon's own `verified`, from a process
|
|
33
|
+
* other than the one that was replaced, for the build that was asked for, and the
|
|
34
|
+
* same machine identity afterwards. A different identity is the reinstall case: the
|
|
35
|
+
* hold is left in place and shown, never resumed.
|
|
36
|
+
*
|
|
37
|
+
* The shapes are STRUCTURAL SUBSETS of `DaemonMaintenanceView` in
|
|
38
|
+
* `@skrr-ai/data-provider` (`daemonMaintenance.ts`), which is the one vocabulary.
|
|
39
|
+
* This package cannot import it (it sits below it), so `guidedUpdate.test.ts` reads
|
|
40
|
+
* that file and fails when a list mirrored here drifts from it.
|
|
41
|
+
*/
|
|
42
|
+
export type GuidedHolderClass = 'active' | 'pending_delivery' | 'unproven' | 'forensic';
|
|
43
|
+
export interface GuidedHolder {
|
|
44
|
+
kind: string;
|
|
45
|
+
class: GuidedHolderClass | string;
|
|
46
|
+
ref?: string;
|
|
47
|
+
label?: string;
|
|
48
|
+
since?: string | null;
|
|
49
|
+
detail?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface GuidedSourceReading {
|
|
52
|
+
source: string;
|
|
53
|
+
read: 'ok' | 'unreadable' | 'not_announced' | string;
|
|
54
|
+
cause?: string;
|
|
55
|
+
readAt?: string;
|
|
56
|
+
total?: number;
|
|
57
|
+
holders?: GuidedHolder[];
|
|
58
|
+
truncated?: boolean;
|
|
59
|
+
}
|
|
60
|
+
export interface GuidedViewLike {
|
|
61
|
+
runtimeKey?: string;
|
|
62
|
+
hold: {
|
|
63
|
+
generation: number;
|
|
64
|
+
lifecycle: 'held' | 'released' | string;
|
|
65
|
+
reason?: string;
|
|
66
|
+
requestedAt?: string;
|
|
67
|
+
heldBy?: {
|
|
68
|
+
kind?: string;
|
|
69
|
+
id?: string;
|
|
70
|
+
name?: string;
|
|
71
|
+
};
|
|
72
|
+
attestation?: {
|
|
73
|
+
at?: string;
|
|
74
|
+
by?: string;
|
|
75
|
+
} | null;
|
|
76
|
+
} | null;
|
|
77
|
+
reading: {
|
|
78
|
+
state: 'draining' | 'ready' | 'unknown' | string;
|
|
79
|
+
cause: string | null;
|
|
80
|
+
readAt?: string;
|
|
81
|
+
sources?: GuidedSourceReading[];
|
|
82
|
+
} | null;
|
|
83
|
+
journey: {
|
|
84
|
+
phase: string;
|
|
85
|
+
modifiers?: string[];
|
|
86
|
+
cause?: string | null;
|
|
87
|
+
controls?: string[];
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** The server sources that can only veto. `daemon_report` is the only one that grants. */
|
|
91
|
+
export declare const GUIDED_SERVER_SOURCES: readonly ["claims", "receipts", "sessions", "runs", "leases", "custody"];
|
|
92
|
+
/** The refusals the owner API names, mirrored from `DAEMON_MAINTENANCE_REFUSALS`. */
|
|
93
|
+
export declare const GUIDED_REFUSALS: readonly ["managed_machine", "no_stable_identity", "not_found", "ambiguous", "generation_conflict", "not_held", "already_held", "attestation_not_applicable", "not_owner", "held_by_other"];
|
|
94
|
+
export type GuidedRefusal = (typeof GUIDED_REFUSALS)[number];
|
|
95
|
+
export declare function isGuidedRefusal(value: unknown): value is GuidedRefusal;
|
|
96
|
+
/** The daemon handoff phases, mirrored from `daemon/src/update/handoff.ts`. */
|
|
97
|
+
export declare const GUIDED_HANDOFF_PHASES: readonly ["ready", "deferred", "reconnecting", "verified", "failed", "rolled-back"];
|
|
98
|
+
/**
|
|
99
|
+
* What a guided flow may do with the server's answer. The flow reads `reading` and
|
|
100
|
+
* the fence, never the Journey's phase alone: a Journey says `back_held` for any
|
|
101
|
+
* process that registered after the fence, and that masks the reading it would
|
|
102
|
+
* otherwise carry.
|
|
103
|
+
*/
|
|
104
|
+
export type Readiness =
|
|
105
|
+
/** No live hold on this machine (it was released, or never existed). */
|
|
106
|
+
{
|
|
107
|
+
kind: 'not_held';
|
|
108
|
+
}
|
|
109
|
+
/** A live hold, but not the generation this flow took. Never adopted. */
|
|
110
|
+
| {
|
|
111
|
+
kind: 'generation_changed';
|
|
112
|
+
currentGeneration: number;
|
|
113
|
+
}
|
|
114
|
+
/** The fence is not confirmed on every row, so no reading means anything yet. */
|
|
115
|
+
| {
|
|
116
|
+
kind: 'pausing';
|
|
117
|
+
}
|
|
118
|
+
/** Nothing admitted is running: the daemon's report said so, or the owner vouched. */
|
|
119
|
+
| {
|
|
120
|
+
kind: 'ready';
|
|
121
|
+
basis: 'evidence' | 'attested';
|
|
122
|
+
}
|
|
123
|
+
/** The daemon cannot report. No server source vetoes. Only the daemon's own check may decide. */
|
|
124
|
+
| {
|
|
125
|
+
kind: 'no_report';
|
|
126
|
+
} | {
|
|
127
|
+
kind: 'draining';
|
|
128
|
+
} | {
|
|
129
|
+
kind: 'unknown';
|
|
130
|
+
cause: string | null;
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Whether the server holds a reading that nothing vetoes, for a daemon that cannot
|
|
134
|
+
* report (D38). Strict on purpose: a source that is absent from the list, unreadable,
|
|
135
|
+
* truncated, or that names active or unproven work, is a veto. `daemon_report` must
|
|
136
|
+
* be `not_announced`: any other answer means the reading is not the
|
|
137
|
+
* `no_inventory_capability` case this is for.
|
|
138
|
+
*/
|
|
139
|
+
export declare function noServerVeto(sources: readonly GuidedSourceReading[] | undefined): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Read the owner API's view for a flow that took `generation`.
|
|
142
|
+
*
|
|
143
|
+
* Order matters and is the rule set (each is a test):
|
|
144
|
+
* 1. No live hold: `not_held`. A different live generation: `generation_changed`.
|
|
145
|
+
* 2. The fence is not confirmed (`pausing`): nothing below it means anything.
|
|
146
|
+
* 3. The reading is `ready` (the daemon's own report): `ready` by evidence.
|
|
147
|
+
* 4. `draining`: work is running. Evidence, never overridden.
|
|
148
|
+
* 5. `unknown`: ready by attestation if the Journey says the owner vouched for this
|
|
149
|
+
* cause; `no_report` if the daemon cannot report and no source vetoes; else
|
|
150
|
+
* `unknown`. No reading at all is `unknown (source_unreadable)`, never ready.
|
|
151
|
+
*/
|
|
152
|
+
export declare function evaluateReadiness(view: GuidedViewLike | null | undefined, expectedGeneration: number | null): Readiness;
|
|
153
|
+
/** How a `prepare` is justified. Labelled on every surface as exactly this. */
|
|
154
|
+
export type PrepareBasis = 'evidence' | 'attested' | 'no_veto';
|
|
155
|
+
/**
|
|
156
|
+
* Whether a guided flow may call the daemon's `prepare`, and on what basis. `null`
|
|
157
|
+
* means NO: `prepare` is never a way to find out. A legacy daemon never gets one:
|
|
158
|
+
* it has no endpoint, and its restart is the owner's attested decision, not a probe.
|
|
159
|
+
*/
|
|
160
|
+
export declare function prepareBasis(readiness: Readiness, opts: {
|
|
161
|
+
legacy: boolean;
|
|
162
|
+
}): PrepareBasis | null;
|
|
163
|
+
/**
|
|
164
|
+
* Whether a legacy daemon (no handoff endpoint) may be restarted by the supervisor.
|
|
165
|
+
* Only on the owner's attestation: the daemon cannot report what it is doing and a
|
|
166
|
+
* SIGTERM ends its wrappers, so nothing but the owner's word can vouch for it.
|
|
167
|
+
*/
|
|
168
|
+
export declare function legacyRestartAllowed(readiness: Readiness): boolean;
|
|
169
|
+
export interface DaemonBlockerLike {
|
|
170
|
+
kind: string;
|
|
171
|
+
id?: string;
|
|
172
|
+
reason?: string;
|
|
173
|
+
}
|
|
174
|
+
export interface DesktopWorkLike {
|
|
175
|
+
localSessions: number;
|
|
176
|
+
agentBrowserWindows: number;
|
|
177
|
+
}
|
|
178
|
+
export type OwnerBlockerSource = 'server' | 'daemon' | 'desktop';
|
|
179
|
+
export interface OwnerBlocker {
|
|
180
|
+
source: OwnerBlockerSource;
|
|
181
|
+
/** The holder or blocker kind as its owner spelled it, or a `desktop_*` id for the app's own work. */
|
|
182
|
+
kind: string;
|
|
183
|
+
/** The server's label, or the daemon's reason, verbatim after bounding. Absent when it was unsafe to show. */
|
|
184
|
+
text?: string;
|
|
185
|
+
/** A server label for a daemon blocker the server holds under the same ref. */
|
|
186
|
+
label?: string;
|
|
187
|
+
/** A bounded identifier (a session or request id), never a path. */
|
|
188
|
+
id?: string;
|
|
189
|
+
since?: string | null;
|
|
190
|
+
/** For the app's own work: how many. */
|
|
191
|
+
count?: number;
|
|
192
|
+
}
|
|
193
|
+
export declare const MAX_OWNER_BLOCKERS = 20;
|
|
194
|
+
/** A sentence safe to put in front of the owner, or `undefined` when it is not. */
|
|
195
|
+
export declare function sanitizeBlockerText(value: unknown, max?: number): string | undefined;
|
|
196
|
+
/**
|
|
197
|
+
* Everything in the way, from the three places that know: the server's holders
|
|
198
|
+
* (active and unproven work: `pending_delivery` and `forensic` never block), the
|
|
199
|
+
* daemon's own refusal from `prepare`, and the work the Desktop app itself hosts and
|
|
200
|
+
* ends when it quits (`LocalSessionService` processes and agent browser windows,
|
|
201
|
+
* which the daemon's inventory cannot see).
|
|
202
|
+
*
|
|
203
|
+
* A daemon blocker whose id equals a server holder's ref is the same work seen twice:
|
|
204
|
+
* it is listed once, with the daemon's reason and the server's label. Whether the two
|
|
205
|
+
* ids are in fact comparable was never established (review §6.2 item 2), so a match
|
|
206
|
+
* only MERGES the wording and never hides a blocker that has no match.
|
|
207
|
+
*/
|
|
208
|
+
export declare function describeBlockers(input: {
|
|
209
|
+
view?: GuidedViewLike | null;
|
|
210
|
+
daemon?: readonly DaemonBlockerLike[] | null;
|
|
211
|
+
desktop?: DesktopWorkLike | null;
|
|
212
|
+
}): OwnerBlocker[];
|
|
213
|
+
/**
|
|
214
|
+
* A string that changes whenever the set of things in the way changes. The "update
|
|
215
|
+
* now anyway" confirmation carries the one it was shown, and the action is refused
|
|
216
|
+
* when it no longer matches: a confirmation that names what WAS running is not a
|
|
217
|
+
* confirmation of what is.
|
|
218
|
+
*/
|
|
219
|
+
export declare function blockersFingerprint(blockers: readonly OwnerBlocker[]): string;
|
|
220
|
+
/**
|
|
221
|
+
* The labels the owner is shown, as the server records them in the outcome
|
|
222
|
+
* (`acknowledged`): each bounded to the server's 160-character limit.
|
|
223
|
+
*/
|
|
224
|
+
export declare function acknowledgedLabels(blockers: readonly OwnerBlocker[], max?: number): string[];
|
|
225
|
+
export interface HandoffBlocker {
|
|
226
|
+
kind: string;
|
|
227
|
+
id?: string;
|
|
228
|
+
reason: string;
|
|
229
|
+
}
|
|
230
|
+
export interface HandoffStatusLike {
|
|
231
|
+
protocolVersion: 1;
|
|
232
|
+
handoffId: string;
|
|
233
|
+
phase: 'ready' | 'deferred' | 'reconnecting' | 'verified' | 'failed' | 'rolled-back';
|
|
234
|
+
reason: string;
|
|
235
|
+
sourceProcessId: string;
|
|
236
|
+
sourceVersion: string;
|
|
237
|
+
target: {
|
|
238
|
+
path?: string;
|
|
239
|
+
mtimeMs?: number;
|
|
240
|
+
sha256: string;
|
|
241
|
+
};
|
|
242
|
+
blockers: HandoffBlocker[];
|
|
243
|
+
expectedSessions: unknown[];
|
|
244
|
+
adoptedSessionCount?: number;
|
|
245
|
+
verifiedBy?: {
|
|
246
|
+
processId: string;
|
|
247
|
+
version: string;
|
|
248
|
+
};
|
|
249
|
+
observer?: {
|
|
250
|
+
processId: string;
|
|
251
|
+
version: string;
|
|
252
|
+
buildMatches: boolean;
|
|
253
|
+
ready: boolean;
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* What a call to the daemon's loopback `/v1/update-handoff` came back as.
|
|
258
|
+
*
|
|
259
|
+
* - `absent`: a 404. The daemon answered and has no such endpoint: it predates the
|
|
260
|
+
* handoff (the installed 0.8.117 answers 404 to every path it does not serve). This
|
|
261
|
+
* is the ONLY reply that means "legacy". A refused connection is not.
|
|
262
|
+
* - `unauthorized`: the bootstrap secret was refused (it rotates when the daemon
|
|
263
|
+
* restarts). Re-read the bootstrap and try again.
|
|
264
|
+
* - `refused`: reachable, speaks the protocol, said no (409: no handoff owned, or the
|
|
265
|
+
* lease expired; 400; 413). A refusal is a value the caller shows.
|
|
266
|
+
* - `unreachable`: no bootstrap file, a connection error, a timeout, a malformed or
|
|
267
|
+
* unexpected body. Unknown: never `absent`, never `ready`.
|
|
268
|
+
*/
|
|
269
|
+
export type HandoffReply = {
|
|
270
|
+
kind: 'ok';
|
|
271
|
+
status: HandoffStatusLike;
|
|
272
|
+
} | {
|
|
273
|
+
kind: 'absent';
|
|
274
|
+
} | {
|
|
275
|
+
kind: 'unauthorized';
|
|
276
|
+
} | {
|
|
277
|
+
kind: 'refused';
|
|
278
|
+
code?: string;
|
|
279
|
+
} | {
|
|
280
|
+
kind: 'unreachable';
|
|
281
|
+
};
|
|
282
|
+
export declare function classifyHandoffReply(input: {
|
|
283
|
+
/** `null` when no HTTP response arrived at all. */
|
|
284
|
+
statusCode: number | null;
|
|
285
|
+
/** The parsed JSON body, or `undefined` when there was none or it did not parse. */
|
|
286
|
+
body?: unknown;
|
|
287
|
+
}): HandoffReply;
|
|
288
|
+
export interface ReplacementEvidence {
|
|
289
|
+
/** The daemon's own answer to `status` for the handoff, or null when it did not answer. */
|
|
290
|
+
handoff: Pick<HandoffStatusLike, 'phase' | 'verifiedBy' | 'target' | 'reason' | 'observer'> | null;
|
|
291
|
+
expect: {
|
|
292
|
+
sourceProcessId: string;
|
|
293
|
+
targetSha256: string;
|
|
294
|
+
/** The bundled daemon's version, or null when it could not be read (the hash binds it anyway). */
|
|
295
|
+
version: string | null;
|
|
296
|
+
};
|
|
297
|
+
identity: {
|
|
298
|
+
recordedDaemonId: string;
|
|
299
|
+
recordedRuntimeKey: string;
|
|
300
|
+
/** The daemon id of the process now holding the loopback bootstrap, or null when unreadable. */
|
|
301
|
+
bootstrapDaemonId: string | null;
|
|
302
|
+
/** The runtimeKey the machine payload lists for `recordedDaemonId` now, or null when it lists none or could not be read. */
|
|
303
|
+
runtimeKeyNow: string | null;
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
export type ReplacementVerdict =
|
|
307
|
+
/** (1) the daemon's `verified` for this build by another process, and (2) the same identity. */
|
|
308
|
+
{
|
|
309
|
+
kind: 'verified';
|
|
310
|
+
} | {
|
|
311
|
+
kind: 'not_yet';
|
|
312
|
+
missing: 'daemon_status' | 'verified' | 'identity_unreadable';
|
|
313
|
+
}
|
|
314
|
+
/** The daemon id or the machine's runtimeKey changed: a reinstall. The hold is left and shown. */
|
|
315
|
+
| {
|
|
316
|
+
kind: 'reinstall';
|
|
317
|
+
what: 'daemon_id' | 'runtime_key';
|
|
318
|
+
} | {
|
|
319
|
+
kind: 'failed';
|
|
320
|
+
phase: 'failed' | 'rolled-back';
|
|
321
|
+
reason: string;
|
|
322
|
+
sameProcess: boolean;
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* Decide whether a committed replacement is complete. All of (1) and (2) must hold
|
|
326
|
+
* before anything else is asked of the server (3, {@link serverSawNewProcess}), and
|
|
327
|
+
* nothing here resumes: the verdict only says what was seen.
|
|
328
|
+
*
|
|
329
|
+
* An identity that is merely UNREADABLE (the bootstrap is mid-rotation, the machine
|
|
330
|
+
* payload could not be fetched) is `not_yet`, never a reinstall and never verified.
|
|
331
|
+
*/
|
|
332
|
+
export declare function verifyReplacement(evidence: ReplacementEvidence): ReplacementVerdict;
|
|
333
|
+
export interface LegacyRestartEvidence {
|
|
334
|
+
before: {
|
|
335
|
+
startedAt: number | null;
|
|
336
|
+
processId: number | null;
|
|
337
|
+
};
|
|
338
|
+
after: {
|
|
339
|
+
daemonId: string | null;
|
|
340
|
+
version: string | null;
|
|
341
|
+
startedAt: number | null;
|
|
342
|
+
processId: number | null;
|
|
343
|
+
};
|
|
344
|
+
expect: {
|
|
345
|
+
daemonId: string;
|
|
346
|
+
version: string | null;
|
|
347
|
+
};
|
|
348
|
+
runtimeKeyNow: string | null;
|
|
349
|
+
recordedRuntimeKey: string;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* The legacy counterpart: a daemon with no handoff cannot say `verified`, so the copy
|
|
353
|
+
* claims only what was seen ("restarted on version X"), never "adopted N sessions".
|
|
354
|
+
* A new process (a different start time and, where known, a different PID), the same
|
|
355
|
+
* identity, and the version it announces equal to the bundled one.
|
|
356
|
+
*/
|
|
357
|
+
export declare function verifyLegacyRestart(evidence: LegacyRestartEvidence): ReplacementVerdict;
|
|
358
|
+
/**
|
|
359
|
+
* Whether the server independently saw a new process register under the held key
|
|
360
|
+
* (condition 3). The Journey says `back_held` for ANY process that registered since
|
|
361
|
+
* the fence, so it can only corroborate THIS replacement when it did not already say
|
|
362
|
+
* so before the commit. When it did, the answer is `not_independent`: the flow rests
|
|
363
|
+
* on (1) and (2) and says so.
|
|
364
|
+
*/
|
|
365
|
+
export declare function serverSawNewProcess(input: {
|
|
366
|
+
journeyPhase: string | null | undefined;
|
|
367
|
+
backHeldAtCommit: boolean;
|
|
368
|
+
}): 'seen' | 'not_yet' | 'not_independent';
|
|
369
|
+
/** Whether an observed hold is the one this flow took, and still live. */
|
|
370
|
+
export declare function isOurLiveHold(view: GuidedViewLike | null | undefined, generation: number | null): boolean;
|