@sayknow-cli/coding-agent 0.4.6 → 0.5.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/CHANGELOG.md +112 -0
- package/dist/types/capability/index.d.ts +10 -20
- package/dist/types/capability/types.d.ts +3 -0
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/cli.d.ts +9 -1
- package/dist/types/commands/daemon.d.ts +2 -2
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/commands/sdk.d.ts +20 -2
- package/dist/types/config/keybindings.d.ts +34 -4
- package/dist/types/config/model-profile-contract.d.ts +40 -0
- package/dist/types/config/model-profiles.d.ts +2 -3
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +156 -10
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/edit/modes/patch.d.ts +13 -0
- package/dist/types/edit/path-mutation-lock.d.ts +15 -0
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/eval/py/runner-artifact.d.ts +8 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/extensibility/extensions/types.d.ts +43 -2
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +33 -2
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +22 -9
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/queue-pane.d.ts +8 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/tool-status-header.d.ts +4 -2
- package/dist/types/modes/components/welcome.d.ts +5 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +13 -2
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
- package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
- package/dist/types/modes/types.d.ts +6 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/runtime/memory-domain.d.ts +9 -0
- package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
- package/dist/types/runtime/memory-guard.d.ts +39 -0
- package/dist/types/runtime/memory-limit.d.ts +11 -0
- package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
- package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +1 -1
- package/dist/types/runtime-mcp/manager.d.ts +9 -1
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
- package/dist/types/runtime-mcp/redaction.d.ts +2 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/acp/final-text.d.ts +19 -0
- package/dist/types/sdk/acp/mcp.d.ts +23 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +13 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +9 -1
- package/dist/types/sdk/bus/config-commands.d.ts +2 -2
- package/dist/types/sdk/bus/config.d.ts +2 -0
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +52 -4
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/notification-service.d.ts +34 -0
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +106 -0
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +33 -9
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +286 -12
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
- package/dist/types/sdk/client/discovery.d.ts +2 -0
- package/dist/types/sdk/client/liveness.d.ts +7 -0
- package/dist/types/sdk/host/control/operations.d.ts +3 -2
- package/dist/types/sdk/host/host.d.ts +3 -1
- package/dist/types/sdk/host/query/handlers.d.ts +14 -0
- package/dist/types/sdk/index.d.ts +2 -0
- package/dist/types/sdk/lifecycle-session.d.ts +10 -1
- package/dist/types/sdk/prompt-status.d.ts +88 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +7 -0
- package/dist/types/sdk/startup-capability.d.ts +9 -0
- package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
- package/dist/types/sdk/transport/index.d.ts +12 -0
- package/dist/types/sdk/transport/relay.d.ts +43 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
- package/dist/types/sdk/transport/socket.d.ts +7 -0
- package/dist/types/sdk/transport/stdio.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +83 -24
- package/dist/types/session/btw-contract.d.ts +16 -0
- package/dist/types/session/fallback-chain-controller.d.ts +18 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +70 -10
- package/dist/types/session/internal/managed-session-storage.d.ts +34 -12
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +89 -18
- package/dist/types/session/session-storage.d.ts +31 -0
- package/dist/types/session/streaming-output.d.ts +60 -5
- package/dist/types/setup/credential-import.d.ts +5 -1
- package/dist/types/setup/provider-onboarding.d.ts +2 -0
- package/dist/types/skc-runtime/deep-interview-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/skc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/linux-proc.d.ts +22 -14
- package/dist/types/skc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +112 -1
- package/dist/types/skc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/skc-runtime/state-runtime.d.ts +3 -1
- package/dist/types/skc-runtime/state-writer.d.ts +5 -1
- package/dist/types/skc-runtime/team-launch.d.ts +1 -1
- package/dist/types/skc-runtime/team-runtime.d.ts +9 -2
- package/dist/types/skc-runtime/team-store.d.ts +12 -0
- package/dist/types/skc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +7 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +6 -5
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +28 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +43 -5
- package/dist/types/skill-state/workflow-hud.d.ts +3 -0
- package/dist/types/ssh/utils.d.ts +1 -0
- package/dist/types/task/discovery.d.ts +3 -1
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/provider-retry-status.d.ts +13 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +133 -0
- package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +22 -9
- package/dist/types/tools/image-gen.d.ts +63 -1
- package/dist/types/tools/index.d.ts +7 -1
- package/dist/types/tools/output-meta.d.ts +21 -1
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -1
- package/dist/types/tools/resource-gc.d.ts +23 -0
- package/dist/types/tools/skill-discovery.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +7 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/dist/types/utils/shell-snapshot.d.ts +12 -0
- package/dist/types/web/insane/bridge.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +17 -0
- package/package.json +8 -12
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/compile-args.ts +0 -1
- package/scripts/dogfood-repository-binding.ts +209 -0
- package/scripts/generate-docs-index.ts +4 -1
- package/scripts/generate-sdk-operation-inventory.ts +9 -0
- package/scripts/run-test-manifest.ts +6 -4
- package/scripts/verify-skc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/capability/index.ts +58 -42
- package/src/capability/types.ts +3 -0
- package/src/cli/args.ts +16 -100
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/mcp-cli.ts +4 -21
- package/src/cli/notify-cli.ts +6 -38
- package/src/cli/read-cli.ts +2 -1
- package/src/cli/setup-cli.ts +10 -4
- package/src/cli.ts +133 -11
- package/src/commands/daemon.ts +22 -5
- package/src/commands/deep-interview.ts +26 -1
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/sdk.ts +134 -17
- package/src/commands/team.ts +1 -1
- package/src/commit/agentic/index.ts +3 -3
- package/src/commit/map-reduce/index.ts +2 -2
- package/src/config/keybindings.ts +158 -25
- package/src/config/model-profile-contract.ts +174 -0
- package/src/config/model-profiles.ts +51 -10
- package/src/config/model-registry.ts +24 -4
- package/src/config/model-resolver.ts +2 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +157 -10
- package/src/config/settings.ts +254 -108
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/skc/skills/deep-interview/SKILL.md +147 -24
- package/src/defaults/skc/skills/ralplan/SKILL.md +91 -34
- package/src/defaults/skc/skills/team/SKILL.md +1 -1
- package/src/defaults/skc/skills/ultragoal/SKILL.md +68 -17
- package/src/discovery/ssh.ts +12 -1
- package/src/edit/index.ts +3 -6
- package/src/edit/modes/patch.ts +38 -1
- package/src/edit/modes/replace.ts +42 -0
- package/src/edit/path-mutation-lock.ts +112 -0
- package/src/eval/js/tool-bridge.ts +1 -1
- package/src/eval/py/executor.ts +37 -13
- package/src/eval/py/kernel.ts +1 -21
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/runner-artifact.ts +84 -0
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +42 -4
- package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
- package/src/extensibility/shared-events.ts +5 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +2 -1
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/internal-urls/docs-index.generated.ts +17 -16
- package/src/internal-urls/local-protocol.ts +87 -25
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +120 -42
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +52 -0
- package/src/modes/acp/acp-agent.ts +647 -83
- package/src/modes/acp/acp-event-mapper.ts +106 -12
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/action-registry.ts +1 -1
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/btw-panel.ts +82 -16
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +61 -4
- package/src/modes/components/notifications-settings-editor.ts +1 -1
- package/src/modes/components/oauth-selector.ts +42 -12
- package/src/modes/components/plan-preview-overlay.ts +1 -1
- package/src/modes/components/queue-pane.ts +55 -9
- package/src/modes/components/queued-message-selector.ts +53 -10
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
- package/src/modes/components/sayknow-pet-widget.ts +27 -8
- package/src/modes/components/session-observer-overlay.ts +187 -32
- package/src/modes/components/settings-selector.ts +60 -18
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/components/tool-status-header.ts +26 -8
- package/src/modes/components/welcome.ts +50 -23
- package/src/modes/controllers/btw-controller.ts +119 -36
- package/src/modes/controllers/command-controller.ts +101 -54
- package/src/modes/controllers/event-controller.ts +119 -27
- package/src/modes/controllers/extension-ui-controller.ts +23 -12
- package/src/modes/controllers/input-controller.ts +100 -8
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +4 -1
- package/src/modes/interactive-mode.ts +201 -60
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/prompt-action-autocomplete.ts +32 -13
- package/src/modes/prompt-suggestion-controller.ts +96 -0
- package/src/modes/runtime-init.ts +20 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +3 -3
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
- package/src/modes/types.ts +6 -0
- package/src/modes/utils/hotkeys-markdown.ts +64 -36
- package/src/modes/utils/ui-helpers.ts +99 -4
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/architect.md +17 -3
- package/src/prompts/agents/critic.md +17 -2
- package/src/prompts/agents/executor.md +1 -1
- package/src/prompts/agents/planner.md +8 -1
- package/src/prompts/system/btw-user.md +3 -8
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +6 -3
- package/src/prompts/tools/cron.md +3 -1
- package/src/prompts/tools/read.md +4 -2
- package/src/prompts/tools/skill-discovery.md +1 -0
- package/src/runtime/memory-domain.ts +66 -0
- package/src/runtime/memory-guard-contract.ts +73 -0
- package/src/runtime/memory-guard.ts +292 -0
- package/src/runtime/memory-limit.ts +50 -0
- package/src/runtime-mcp/client.ts +41 -55
- package/src/runtime-mcp/content-limits.ts +71 -0
- package/src/runtime-mcp/discoverable-tool-metadata.ts +1 -1
- package/src/runtime-mcp/json-rpc.ts +28 -11
- package/src/runtime-mcp/manager.ts +77 -59
- package/src/runtime-mcp/oauth-flow.ts +0 -72
- package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
- package/src/runtime-mcp/redaction.ts +30 -0
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/runtime-mcp/transports/http.ts +63 -22
- package/src/sdk/acp/adapter.ts +8 -1
- package/src/sdk/acp/final-text.ts +40 -0
- package/src/sdk/acp/mcp.ts +26 -0
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/identity.ts +22 -12
- package/src/sdk/broker/lifecycle.ts +293 -43
- package/src/sdk/broker/session-index.ts +11 -1
- package/src/sdk/bus/chat-daemon-cli.ts +2 -2
- package/src/sdk/bus/chat-daemon-control.ts +22 -5
- package/src/sdk/bus/config-commands.ts +2 -2
- package/src/sdk/bus/config.ts +10 -2
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/conversation-store.ts +40 -6
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/index.ts +793 -121
- package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
- package/src/sdk/bus/lifecycle-commands.ts +63 -10
- package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
- package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
- package/src/sdk/bus/notification-orchestration.ts +12 -0
- package/src/sdk/bus/notification-service.ts +87 -25
- package/src/sdk/bus/operator-runtime.ts +31 -5
- package/src/sdk/bus/prompt-reconciliation.ts +240 -0
- package/src/sdk/bus/rate-limit-pool.ts +8 -0
- package/src/sdk/bus/recent-activity.ts +90 -26
- package/src/sdk/bus/reconciliation-store.ts +224 -0
- package/src/sdk/bus/telegram-cli.ts +6 -13
- package/src/sdk/bus/telegram-daemon-cli.ts +8 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +34 -9
- package/src/sdk/bus/telegram-daemon-control.ts +161 -40
- package/src/sdk/bus/telegram-daemon.ts +3649 -593
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/bus/topic-registry.ts +451 -12
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/client/discovery.ts +19 -3
- package/src/sdk/client/liveness.ts +49 -0
- package/src/sdk/host/control/dispatch.ts +8 -2
- package/src/sdk/host/control/operations.ts +3 -2
- package/src/sdk/host/host.ts +27 -9
- package/src/sdk/host/query/handlers.ts +125 -5
- package/src/sdk/index.ts +12 -0
- package/src/sdk/lifecycle-session.ts +19 -3
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/prompt-status.ts +97 -0
- package/src/sdk/protocol/operation-inventory.generated.json +145 -0
- package/src/sdk/protocol/operation-registry.ts +24 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +69 -8
- package/src/sdk/startup-capability.ts +15 -1
- package/src/sdk/transport/auth-preface.ts +82 -0
- package/src/sdk/transport/index.ts +18 -0
- package/src/sdk/transport/relay.ts +252 -0
- package/src/sdk/transport/serve-cli.ts +105 -0
- package/src/sdk/transport/socket.ts +197 -0
- package/src/sdk/transport/stdio.ts +17 -0
- package/src/session/agent-session.ts +1112 -314
- package/src/session/blob-store.ts +25 -7
- package/src/session/btw-contract.ts +59 -0
- package/src/session/fallback-chain-controller.ts +47 -8
- package/src/session/internal/managed-session-scope.ts +1426 -259
- package/src/session/internal/managed-session-storage.ts +414 -238
- package/src/session/internal/native-publish-outcome.ts +271 -0
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +2079 -469
- package/src/session/session-storage.ts +151 -11
- package/src/session/streaming-output.ts +262 -43
- package/src/setup/credential-import.ts +28 -3
- package/src/setup/provider-onboarding.ts +49 -5
- package/src/setup/provider-presets.json +2 -1
- package/src/skc-runtime/deep-interview-runtime.ts +303 -36
- package/src/skc-runtime/deep-interview-stage.ts +1016 -0
- package/src/skc-runtime/deep-interview-state.ts +8 -5
- package/src/skc-runtime/gc-render.ts +1 -0
- package/src/skc-runtime/gc-runtime.ts +11 -1
- package/src/skc-runtime/linux-proc.ts +70 -36
- package/src/skc-runtime/managed-owner-supervisor.ts +83 -4
- package/src/skc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/skc-runtime/ralplan-runtime.ts +1057 -75
- package/src/skc-runtime/repository-binding.ts +267 -0
- package/src/skc-runtime/session-state-sidecar.ts +25 -9
- package/src/skc-runtime/state-runtime.ts +50 -12
- package/src/skc-runtime/state-writer.ts +68 -56
- package/src/skc-runtime/team-launch.ts +34 -5
- package/src/skc-runtime/team-runtime.ts +41 -1
- package/src/skc-runtime/team-store.ts +32 -0
- package/src/skc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/skc-runtime/team-workers.ts +1 -0
- package/src/skc-runtime/tmux-common.ts +13 -2
- package/src/skc-runtime/tmux-owner-isolation.ts +20 -1
- package/src/skc-runtime/tmux-sessions.ts +45 -6
- package/src/skc-runtime/ultragoal-guard.ts +93 -15
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +123 -0
- package/src/skc-runtime/ultragoal-runtime.ts +469 -38
- package/src/skc-runtime/workflow-manifest.generated.json +195 -5
- package/src/skc-runtime/workflow-manifest.ts +67 -3
- package/src/skill-state/workflow-hud.ts +25 -2
- package/src/skill-state/workflow-mutation-guard.ts +104 -12
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/ssh/utils.ts +14 -0
- package/src/task/discovery.ts +8 -2
- package/src/task/executor.ts +165 -47
- package/src/task/index.ts +123 -15
- package/src/task/provider-retry-status.ts +88 -0
- package/src/task/receipt.ts +3 -0
- package/src/task/render.ts +45 -26
- package/src/task/skc-command.ts +2 -2
- package/src/task/types.ts +55 -10
- package/src/task/ultragoal-redteam-activation.ts +75 -0
- package/src/tools/ask.ts +1 -1
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/bash-allowed-prefixes.ts +45 -1
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/browser/screenshot-format.ts +6 -1
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +127 -154
- package/src/tools/image-gen.ts +476 -23
- package/src/tools/index.ts +7 -1
- package/src/tools/output-meta.ts +176 -9
- package/src/tools/path-utils.ts +5 -1
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1544 -310
- package/src/tools/resource-gc.ts +665 -125
- package/src/tools/skill-discovery.ts +39 -3
- package/src/tools/sqlite-reader.ts +63 -5
- package/src/tools/subagent-render.ts +74 -16
- package/src/tools/subagent.ts +2 -0
- package/src/tools/tool-result.ts +14 -1
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/pasted-image-path.ts +15 -6
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/shell-snapshot.ts +173 -63
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/bridge.ts +5 -1
- package/src/web/insane/url-guard.ts +140 -27
- package/src/web/scrapers/docs-rs.ts +30 -5
- package/src/web/scrapers/types.ts +19 -49
- package/src/web/scrapers/utils.ts +12 -26
- package/src/web/search/providers/codex.ts +1 -1
- package/src/workflow/workflow-intent-diff.ts +4 -1
|
@@ -156,6 +156,18 @@ export function createAliasTable(): AliasTable {
|
|
|
156
156
|
},
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
|
+
/** Copy labels for display and annotate one valid recommended option only. */
|
|
160
|
+
function withRecommendedOptionLabel(options: readonly string[], recommendedIndex: unknown): string[] {
|
|
161
|
+
if (
|
|
162
|
+
typeof recommendedIndex !== "number" ||
|
|
163
|
+
!Number.isFinite(recommendedIndex) ||
|
|
164
|
+
!Number.isInteger(recommendedIndex) ||
|
|
165
|
+
recommendedIndex < 0 ||
|
|
166
|
+
recommendedIndex >= options.length
|
|
167
|
+
)
|
|
168
|
+
return [...options];
|
|
169
|
+
return options.map((label, index) => (index === recommendedIndex ? `${label} (Recommended)` : label));
|
|
170
|
+
}
|
|
159
171
|
|
|
160
172
|
/** Render an `action_needed` payload into a Telegram message. */
|
|
161
173
|
export function buildActionMessage(action: {
|
|
@@ -163,6 +175,7 @@ export function buildActionMessage(action: {
|
|
|
163
175
|
id: string;
|
|
164
176
|
question?: string;
|
|
165
177
|
options?: string[];
|
|
178
|
+
recommendedIndex?: unknown;
|
|
166
179
|
controls?: readonly TelegramActionControl[];
|
|
167
180
|
summary?: string;
|
|
168
181
|
}): RenderedMessage {
|
|
@@ -172,9 +185,10 @@ export function buildActionMessage(action: {
|
|
|
172
185
|
}
|
|
173
186
|
const text = `❓ ${bold(action.question ?? "Question")}`;
|
|
174
187
|
const options = action.options ?? [];
|
|
188
|
+
const displayOptions = withRecommendedOptionLabel(options, action.recommendedIndex);
|
|
175
189
|
const controls = (action.controls ?? []).filter(control => control.enabled);
|
|
176
190
|
if (options.length === 0 && controls.length === 0) return { text: `${text}\n\n(reply with text)` };
|
|
177
|
-
const body = options.length ? `${text}\n\n${numberedOptionList(
|
|
191
|
+
const body = options.length ? `${text}\n\n${numberedOptionList(displayOptions)}` : text;
|
|
178
192
|
const inline_keyboard = [
|
|
179
193
|
...(options.length ? buildCompactChoiceGrid(options, i => encodeCallbackData(action.id, i)) : []),
|
|
180
194
|
...controls.map(control => [
|
|
@@ -189,6 +203,7 @@ export function buildActionMarkdown(action: {
|
|
|
189
203
|
kind: "ask" | "idle";
|
|
190
204
|
question?: string;
|
|
191
205
|
options?: string[];
|
|
206
|
+
recommendedIndex?: unknown;
|
|
192
207
|
summary?: string;
|
|
193
208
|
}): string {
|
|
194
209
|
if (action.kind === "idle") {
|
|
@@ -196,8 +211,9 @@ export function buildActionMarkdown(action: {
|
|
|
196
211
|
}
|
|
197
212
|
const heading = `❓ **${action.question ?? "Question"}**`;
|
|
198
213
|
const options = action.options ?? [];
|
|
214
|
+
const displayOptions = withRecommendedOptionLabel(options, action.recommendedIndex);
|
|
199
215
|
if (options.length === 0) return `${heading}\n\n(reply with text)`;
|
|
200
|
-
const list =
|
|
216
|
+
const list = displayOptions.map((label, i) => `${i + 1}. ${label.replace(/^\s*\d+[.)]\s+/, "")}`).join("\n");
|
|
201
217
|
return `${heading}\n\n${list}`;
|
|
202
218
|
}
|
|
203
219
|
|
|
@@ -362,6 +378,7 @@ export async function runTelegramReferenceClient(opts: TelegramReferenceOptions)
|
|
|
362
378
|
id?: string;
|
|
363
379
|
question?: string;
|
|
364
380
|
options?: string[];
|
|
381
|
+
recommendedIndex?: unknown;
|
|
365
382
|
controls?: TelegramActionControl[];
|
|
366
383
|
summary?: string;
|
|
367
384
|
reason?: string;
|
|
@@ -374,6 +391,7 @@ export async function runTelegramReferenceClient(opts: TelegramReferenceOptions)
|
|
|
374
391
|
id: msg.id,
|
|
375
392
|
question: msg.question,
|
|
376
393
|
options: msg.options,
|
|
394
|
+
recommendedIndex: msg.recommendedIndex,
|
|
377
395
|
controls: msg.controls,
|
|
378
396
|
summary: msg.summary,
|
|
379
397
|
});
|
|
@@ -39,8 +39,22 @@ export interface TopicRecord {
|
|
|
39
39
|
replaySeq?: number;
|
|
40
40
|
/** Serialized authority epoch; a late create may commit only in its starting epoch. */
|
|
41
41
|
authorityEpoch?: number;
|
|
42
|
+
/** Immutable authority epoch held when this remote topic create began. */
|
|
43
|
+
creationLeaseEpoch?: number;
|
|
42
44
|
/** An uncertain delete fences future creation and inbound routing. */
|
|
43
45
|
authorityState?: "active" | "delete_pending";
|
|
46
|
+
/** Telegram chat and endpoint authority last proven to use this topic. */
|
|
47
|
+
chatId?: string;
|
|
48
|
+
/** Canonical endpoint tuple (URL + token) that currently holds the lease. */
|
|
49
|
+
endpointKey?: string;
|
|
50
|
+
/** Authenticated endpoint authority digest, excluding transport presentation. */
|
|
51
|
+
endpointDigest?: string;
|
|
52
|
+
/** SDK event generation associated with the current endpoint lease. */
|
|
53
|
+
endpointGeneration?: number;
|
|
54
|
+
/** Monotonic authenticated endpoint handoffs; legacy bindings begin at zero. */
|
|
55
|
+
endpointIncarnation?: number;
|
|
56
|
+
/** True when persisted binding fields were present but malformed; recovery must fail closed. */
|
|
57
|
+
bindingMalformed?: true;
|
|
44
58
|
}
|
|
45
59
|
|
|
46
60
|
/** Serialisable shape persisted to disk. */
|
|
@@ -49,6 +63,69 @@ export interface TopicRegistryState {
|
|
|
49
63
|
topics: Record<string, TopicRecord>;
|
|
50
64
|
/** Durable deletion epochs retained after a definite delete. */
|
|
51
65
|
fences?: Record<string, number>;
|
|
66
|
+
/** Closed transport endpoint leases; unchanged endpoint discovery remains fenced across restart. */
|
|
67
|
+
closedEndpoints?: Record<string, TopicEndpointBinding>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Authenticated runtime binding for a durable topic lease. */
|
|
71
|
+
export interface TopicEndpointBinding {
|
|
72
|
+
chatId: string;
|
|
73
|
+
endpointKey: string;
|
|
74
|
+
endpointDigest: string;
|
|
75
|
+
endpointGeneration?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Discriminated durable endpoint authority for identity-less replay admission. */
|
|
79
|
+
export type TopicEndpointAuthority =
|
|
80
|
+
| { state: "none" }
|
|
81
|
+
| { state: "unique"; sessionId: string }
|
|
82
|
+
| { state: "ambiguous" };
|
|
83
|
+
|
|
84
|
+
/** Conditional rollback token for a delete fence publication. */
|
|
85
|
+
export interface TopicDeleteAuthoritySnapshot {
|
|
86
|
+
sessionId: string;
|
|
87
|
+
topicId?: string;
|
|
88
|
+
authorityEpoch?: number;
|
|
89
|
+
authorityState?: TopicRecord["authorityState"];
|
|
90
|
+
fenceEpoch?: number;
|
|
91
|
+
/** Exact fenced record, retained to restore an in-memory tombstone after a failed clear publication. */
|
|
92
|
+
record?: TopicRecord;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isValidBindingString(value: unknown): value is string {
|
|
96
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isValidBindingGeneration(value: unknown): value is number {
|
|
100
|
+
return Number.isSafeInteger(value) && (value as number) >= 0;
|
|
101
|
+
}
|
|
102
|
+
function hasAnyBinding(record: TopicRecord): boolean {
|
|
103
|
+
return (
|
|
104
|
+
record.chatId !== undefined ||
|
|
105
|
+
record.endpointKey !== undefined ||
|
|
106
|
+
record.endpointDigest !== undefined ||
|
|
107
|
+
record.endpointGeneration !== undefined ||
|
|
108
|
+
record.endpointIncarnation !== undefined
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function hasCompleteBinding(record: TopicRecord): boolean {
|
|
113
|
+
return (
|
|
114
|
+
isValidBindingString(record.chatId) &&
|
|
115
|
+
isValidBindingString(record.endpointKey) &&
|
|
116
|
+
isValidBindingString(record.endpointDigest) &&
|
|
117
|
+
(record.endpointGeneration === undefined || isValidBindingGeneration(record.endpointGeneration)) &&
|
|
118
|
+
(record.endpointIncarnation === undefined || isValidBindingGeneration(record.endpointIncarnation))
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function hasValidBinding(binding: TopicEndpointBinding): boolean {
|
|
123
|
+
return (
|
|
124
|
+
isValidBindingString(binding.chatId) &&
|
|
125
|
+
isValidBindingString(binding.endpointKey) &&
|
|
126
|
+
isValidBindingString(binding.endpointDigest) &&
|
|
127
|
+
(binding.endpointGeneration === undefined || isValidBindingGeneration(binding.endpointGeneration))
|
|
128
|
+
);
|
|
52
129
|
}
|
|
53
130
|
|
|
54
131
|
function isValidTopicId(value: unknown): value is string {
|
|
@@ -74,6 +151,12 @@ export class TopicRegistry {
|
|
|
74
151
|
readonly #ambiguousTopicIds = new Set<string>();
|
|
75
152
|
/** In-flight create promises, keyed by session, to dedupe concurrent creates. */
|
|
76
153
|
private readonly inflight = new Map<string, Promise<TopicRecord>>();
|
|
154
|
+
/** Newly-created records being durably published; never routable until committed. */
|
|
155
|
+
private readonly staged = new Map<string, TopicRecord>();
|
|
156
|
+
/** Socket-specific provenance for transient endpoint claims. */
|
|
157
|
+
private readonly transientClaimants = new Map<string, object | undefined>();
|
|
158
|
+
/** Endpoint claims registered before a remote topic create can publish a record. */
|
|
159
|
+
private readonly creatingBindings = new Map<string, TopicEndpointBinding>();
|
|
77
160
|
/** Monotonic authority epochs, including deletion fences for absent records. */
|
|
78
161
|
private readonly epochs = new Map<string, number>();
|
|
79
162
|
|
|
@@ -82,6 +165,15 @@ export class TopicRegistry {
|
|
|
82
165
|
this.load(state);
|
|
83
166
|
}
|
|
84
167
|
|
|
168
|
+
/** Replace all runtime state after a successfully persisted staged publication. */
|
|
169
|
+
replace(state: TopicRegistryState): void {
|
|
170
|
+
this.topics.clear();
|
|
171
|
+
this.byTopic.clear();
|
|
172
|
+
this.#ambiguousTopicIds.clear();
|
|
173
|
+
this.epochs.clear();
|
|
174
|
+
this.load(state);
|
|
175
|
+
}
|
|
176
|
+
|
|
85
177
|
/** Merge serialized state and normalize authority fields from older releases. */
|
|
86
178
|
load(state: TopicRegistryState): void {
|
|
87
179
|
for (const [sessionId, epoch] of Object.entries(state.fences ?? {})) {
|
|
@@ -94,9 +186,10 @@ export class TopicRegistry {
|
|
|
94
186
|
raw.nameOwner === "user" &&
|
|
95
187
|
typeof raw.name === "string" &&
|
|
96
188
|
raw.name.trim().length > 0 &&
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
189
|
+
(raw.userNameUpdateId === undefined ||
|
|
190
|
+
(typeof raw.userNameUpdateId === "number" &&
|
|
191
|
+
Number.isSafeInteger(raw.userNameUpdateId) &&
|
|
192
|
+
raw.userNameUpdateId >= 0));
|
|
100
193
|
const hasValidReplayCursor =
|
|
101
194
|
typeof raw.replayGeneration === "number" &&
|
|
102
195
|
Number.isSafeInteger(raw.replayGeneration) &&
|
|
@@ -104,6 +197,19 @@ export class TopicRegistry {
|
|
|
104
197
|
typeof raw.replaySeq === "number" &&
|
|
105
198
|
Number.isSafeInteger(raw.replaySeq) &&
|
|
106
199
|
raw.replaySeq >= 0;
|
|
200
|
+
const legacyUnbound = !hasAnyBinding(raw);
|
|
201
|
+
const bindingMalformed = raw.bindingMalformed === true || (!legacyUnbound && !hasCompleteBinding(raw));
|
|
202
|
+
const candidateAuthorityEpoch = raw.authorityEpoch;
|
|
203
|
+
const rawAuthorityEpoch: number =
|
|
204
|
+
typeof candidateAuthorityEpoch === "number" &&
|
|
205
|
+
Number.isSafeInteger(candidateAuthorityEpoch) &&
|
|
206
|
+
candidateAuthorityEpoch >= 0
|
|
207
|
+
? candidateAuthorityEpoch
|
|
208
|
+
: 0;
|
|
209
|
+
// A fence is the durable authority source. A mixed snapshot can contain an
|
|
210
|
+
// older active record alongside a newer fence; never rebuild its inbound route.
|
|
211
|
+
const fenceEpoch = this.epochs.get(sessionId) ?? 0;
|
|
212
|
+
const fenceSupersedesRecord = fenceEpoch > rawAuthorityEpoch;
|
|
107
213
|
const record: TopicRecord = {
|
|
108
214
|
topicId: raw.topicId,
|
|
109
215
|
identitySent: raw.identitySent === true,
|
|
@@ -114,15 +220,33 @@ export class TopicRegistry {
|
|
|
114
220
|
: {}),
|
|
115
221
|
...(hasValidUserAuthority ? { nameOwner: "user" as const } : {}),
|
|
116
222
|
...(hasValidUserAuthority && raw.nameReconcilePending === true ? { nameReconcilePending: true } : {}),
|
|
117
|
-
...(hasValidUserAuthority
|
|
223
|
+
...(hasValidUserAuthority && typeof raw.userNameUpdateId === "number"
|
|
224
|
+
? { userNameUpdateId: raw.userNameUpdateId }
|
|
225
|
+
: {}),
|
|
118
226
|
...(typeof raw.identityKey === "string" ? { identityKey: raw.identityKey } : {}),
|
|
227
|
+
...(typeof raw.creationLeaseEpoch === "number" &&
|
|
228
|
+
Number.isSafeInteger(raw.creationLeaseEpoch) &&
|
|
229
|
+
raw.creationLeaseEpoch >= 0
|
|
230
|
+
? { creationLeaseEpoch: raw.creationLeaseEpoch }
|
|
231
|
+
: {}),
|
|
119
232
|
...(hasValidReplayCursor ? { replayGeneration: raw.replayGeneration, replaySeq: raw.replaySeq } : {}),
|
|
120
|
-
|
|
121
|
-
|
|
233
|
+
authorityEpoch: Math.max(rawAuthorityEpoch, fenceEpoch),
|
|
234
|
+
...(raw.authorityState === "delete_pending" || fenceSupersedesRecord
|
|
235
|
+
? { authorityState: "delete_pending" as const }
|
|
236
|
+
: {}),
|
|
237
|
+
...(isValidBindingString(raw.chatId) ? { chatId: raw.chatId } : {}),
|
|
238
|
+
...(isValidBindingString(raw.endpointKey) ? { endpointKey: raw.endpointKey } : {}),
|
|
239
|
+
...(isValidBindingString(raw.endpointDigest) ? { endpointDigest: raw.endpointDigest } : {}),
|
|
240
|
+
...(isValidBindingGeneration(raw.endpointGeneration) ? { endpointGeneration: raw.endpointGeneration } : {}),
|
|
241
|
+
...(isValidBindingGeneration(raw.endpointIncarnation)
|
|
242
|
+
? { endpointIncarnation: raw.endpointIncarnation }
|
|
122
243
|
: {}),
|
|
123
|
-
...(
|
|
244
|
+
...(bindingMalformed ? { bindingMalformed: true as const } : {}),
|
|
124
245
|
};
|
|
125
|
-
this.epochs.set(sessionId, Math.max(
|
|
246
|
+
this.epochs.set(sessionId, Math.max(fenceEpoch, record.authorityEpoch ?? 0));
|
|
247
|
+
// Pre-generation-17 records have no endpoint authority. Retire them locally:
|
|
248
|
+
// their unknown remote topic must neither be rebound nor deleted cross-chat.
|
|
249
|
+
if (legacyUnbound) continue;
|
|
126
250
|
|
|
127
251
|
this.topics.set(sessionId, record);
|
|
128
252
|
}
|
|
@@ -135,7 +259,7 @@ export class TopicRegistry {
|
|
|
135
259
|
const activeByTopic = new Map<string, string>();
|
|
136
260
|
|
|
137
261
|
for (const [sessionId, record] of this.topics) {
|
|
138
|
-
if (record.authorityState === "delete_pending") {
|
|
262
|
+
if (record.authorityState === "delete_pending" || record.bindingMalformed) {
|
|
139
263
|
this.#ambiguousTopicIds.add(record.topicId);
|
|
140
264
|
continue;
|
|
141
265
|
}
|
|
@@ -161,11 +285,164 @@ export class TopicRegistry {
|
|
|
161
285
|
return [...this.topics.keys()];
|
|
162
286
|
}
|
|
163
287
|
|
|
288
|
+
/** Persisted remote deletes that must be reconciled before recovery can proceed. */
|
|
289
|
+
deletePendingSessionIds(): string[] {
|
|
290
|
+
return [...this.topics].flatMap(([sessionId, record]) =>
|
|
291
|
+
record.authorityState === "delete_pending" ? [sessionId] : [],
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
164
295
|
/** The existing topic record for a session, if any. */
|
|
165
296
|
get(sessionId: string): TopicRecord | undefined {
|
|
166
297
|
return this.topics.get(sessionId);
|
|
167
298
|
}
|
|
168
299
|
|
|
300
|
+
/** Current immutable authority epoch for a creation lease. */
|
|
301
|
+
authorityEpoch(sessionId: string): number {
|
|
302
|
+
return Math.max(this.epochs.get(sessionId) ?? 0, this.topics.get(sessionId)?.authorityEpoch ?? 0);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Whether this session has an active, unambiguous topic authority. */
|
|
306
|
+
isActiveUnambiguous(sessionId: string): boolean {
|
|
307
|
+
const record = this.topics.get(sessionId);
|
|
308
|
+
return record?.authorityState !== "delete_pending" && this.byTopic.get(record?.topicId ?? "") === sessionId;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Resolve endpoint authority for identity-less replay. An endpoint may bootstrap
|
|
313
|
+
* only when no committed, staged, or pre-create claim can own it; malformed
|
|
314
|
+
* partial bindings and deletion fences deliberately fail closed.
|
|
315
|
+
*/
|
|
316
|
+
endpointAuthority(binding: TopicEndpointBinding, excludedTransientClaimant?: object): TopicEndpointAuthority {
|
|
317
|
+
if (!hasValidBinding(binding)) return { state: "ambiguous" };
|
|
318
|
+
const canClaim = (record: Pick<TopicRecord, "chatId" | "endpointKey" | "endpointDigest">): boolean =>
|
|
319
|
+
(record.chatId === undefined || record.chatId === binding.chatId) &&
|
|
320
|
+
(record.endpointKey === undefined || record.endpointKey === binding.endpointKey) &&
|
|
321
|
+
(record.endpointDigest === undefined || record.endpointDigest === binding.endpointDigest);
|
|
322
|
+
const committed = [...this.topics].filter(([, record]) => canClaim(record));
|
|
323
|
+
const excludesClaimant = (sessionId: string): boolean =>
|
|
324
|
+
excludedTransientClaimant !== undefined &&
|
|
325
|
+
this.transientClaimants.get(sessionId) === excludedTransientClaimant;
|
|
326
|
+
const staged = [...this.staged].filter(([sessionId, record]) => canClaim(record) && !excludesClaimant(sessionId));
|
|
327
|
+
const creating = [...this.creatingBindings].filter(
|
|
328
|
+
([sessionId, claim]) => canClaim(claim) && !excludesClaimant(sessionId),
|
|
329
|
+
);
|
|
330
|
+
if (committed.length === 0 && staged.length === 0 && creating.length === 0) return { state: "none" };
|
|
331
|
+
if (committed.length !== 1 || staged.length !== 0 || creating.length !== 0) return { state: "ambiguous" };
|
|
332
|
+
const [sessionId, record] = committed[0]!;
|
|
333
|
+
return record.chatId === binding.chatId &&
|
|
334
|
+
record.endpointKey === binding.endpointKey &&
|
|
335
|
+
record.endpointDigest === binding.endpointDigest &&
|
|
336
|
+
this.isActiveUnambiguous(sessionId) &&
|
|
337
|
+
!record.bindingMalformed
|
|
338
|
+
? { state: "unique", sessionId }
|
|
339
|
+
: { state: "ambiguous" };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** Resolve a uniquely bound logical owner for callers that only need the owner. */
|
|
343
|
+
uniqueSessionForEndpoint(binding: TopicEndpointBinding): string | undefined {
|
|
344
|
+
const authority = this.endpointAuthority(binding);
|
|
345
|
+
return authority.state === "unique" ? authority.sessionId : undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** Whether this exact session owns the complete durable endpoint binding. */
|
|
349
|
+
matchesEndpoint(sessionId: string, binding: TopicEndpointBinding): boolean {
|
|
350
|
+
const record = this.topics.get(sessionId);
|
|
351
|
+
return (
|
|
352
|
+
this.isActiveUnambiguous(sessionId) &&
|
|
353
|
+
!record?.bindingMalformed &&
|
|
354
|
+
record?.chatId === binding.chatId &&
|
|
355
|
+
record.endpointKey === binding.endpointKey &&
|
|
356
|
+
record.endpointDigest === binding.endpointDigest
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Rebind an existing topic to an authenticated successor endpoint. The exact
|
|
362
|
+
* logical session id is proved by replay before this method is called. A
|
|
363
|
+
* rotated credential may replace only an inactive incumbent; concurrent
|
|
364
|
+
* incumbents, cross-chat records, malformed evidence, collisions, and delete
|
|
365
|
+
* fences remain fail-closed.
|
|
366
|
+
*/
|
|
367
|
+
bindEndpoint(
|
|
368
|
+
sessionId: string,
|
|
369
|
+
binding: TopicEndpointBinding,
|
|
370
|
+
activeEndpointKeys: ReadonlySet<string> = new Set(),
|
|
371
|
+
allowEndpointRotation = false,
|
|
372
|
+
): "bound" | "unchanged" | "rejected" {
|
|
373
|
+
const record = this.topics.get(sessionId);
|
|
374
|
+
if (
|
|
375
|
+
!record ||
|
|
376
|
+
record.authorityState === "delete_pending" ||
|
|
377
|
+
record.bindingMalformed ||
|
|
378
|
+
!hasValidBinding(binding) ||
|
|
379
|
+
!this.isActiveUnambiguous(sessionId)
|
|
380
|
+
)
|
|
381
|
+
return "rejected";
|
|
382
|
+
if (hasAnyBinding(record) && !hasCompleteBinding(record)) return "rejected";
|
|
383
|
+
// A topic id without chat affinity may belong to any prior paired chat.
|
|
384
|
+
// Do not bind it to the current chat merely because a resumed endpoint
|
|
385
|
+
// authenticated its logical session id.
|
|
386
|
+
if (record.chatId === undefined || record.chatId !== binding.chatId) return "rejected";
|
|
387
|
+
|
|
388
|
+
const sameEndpoint =
|
|
389
|
+
record.endpointKey === binding.endpointKey && record.endpointDigest === binding.endpointDigest;
|
|
390
|
+
if (
|
|
391
|
+
sameEndpoint &&
|
|
392
|
+
record.endpointGeneration !== undefined &&
|
|
393
|
+
binding.endpointGeneration !== undefined &&
|
|
394
|
+
binding.endpointGeneration < record.endpointGeneration
|
|
395
|
+
)
|
|
396
|
+
return "rejected";
|
|
397
|
+
if (
|
|
398
|
+
!sameEndpoint &&
|
|
399
|
+
hasAnyBinding(record) &&
|
|
400
|
+
(!allowEndpointRotation || (record.endpointKey !== undefined && activeEndpointKeys.has(record.endpointKey)))
|
|
401
|
+
)
|
|
402
|
+
return "rejected";
|
|
403
|
+
|
|
404
|
+
const changed =
|
|
405
|
+
record.chatId !== binding.chatId ||
|
|
406
|
+
record.endpointKey !== binding.endpointKey ||
|
|
407
|
+
record.endpointDigest !== binding.endpointDigest ||
|
|
408
|
+
record.endpointGeneration !== binding.endpointGeneration;
|
|
409
|
+
if (!changed) return "unchanged";
|
|
410
|
+
record.chatId = binding.chatId;
|
|
411
|
+
record.endpointKey = binding.endpointKey;
|
|
412
|
+
record.endpointDigest = binding.endpointDigest;
|
|
413
|
+
record.endpointGeneration = binding.endpointGeneration;
|
|
414
|
+
if (!sameEndpoint) record.endpointIncarnation = (record.endpointIncarnation ?? 0) + 1;
|
|
415
|
+
return "bound";
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Undo a failed durable endpoint migration without disturbing concurrent metadata writers. */
|
|
419
|
+
restoreEndpointBinding(
|
|
420
|
+
sessionId: string,
|
|
421
|
+
expected: TopicEndpointBinding,
|
|
422
|
+
previous: Pick<
|
|
423
|
+
TopicRecord,
|
|
424
|
+
"chatId" | "endpointKey" | "endpointDigest" | "endpointGeneration" | "endpointIncarnation"
|
|
425
|
+
>,
|
|
426
|
+
): boolean {
|
|
427
|
+
const record = this.topics.get(sessionId);
|
|
428
|
+
if (
|
|
429
|
+
!record ||
|
|
430
|
+
record.chatId !== expected.chatId ||
|
|
431
|
+
record.endpointKey !== expected.endpointKey ||
|
|
432
|
+
record.endpointDigest !== expected.endpointDigest ||
|
|
433
|
+
record.endpointGeneration !== expected.endpointGeneration
|
|
434
|
+
)
|
|
435
|
+
return false;
|
|
436
|
+
record.chatId = previous.chatId;
|
|
437
|
+
record.endpointKey = previous.endpointKey;
|
|
438
|
+
record.endpointDigest = previous.endpointDigest;
|
|
439
|
+
if (previous.endpointGeneration === undefined) delete record.endpointGeneration;
|
|
440
|
+
else record.endpointGeneration = previous.endpointGeneration;
|
|
441
|
+
if (previous.endpointIncarnation === undefined) delete record.endpointIncarnation;
|
|
442
|
+
else record.endpointIncarnation = previous.endpointIncarnation;
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
|
|
169
446
|
/**
|
|
170
447
|
* Return the existing active topic for `sessionId`, or create one via
|
|
171
448
|
* `create` (called only on first use).
|
|
@@ -175,13 +452,22 @@ export class TopicRegistry {
|
|
|
175
452
|
create: () => Promise<unknown>,
|
|
176
453
|
now: () => number = Date.now,
|
|
177
454
|
name?: string,
|
|
455
|
+
binding?: TopicEndpointBinding,
|
|
456
|
+
commit?: () => Promise<void>,
|
|
457
|
+
transientClaimant?: object,
|
|
178
458
|
): Promise<TopicRecord> {
|
|
179
459
|
const existing = this.topics.get(sessionId);
|
|
180
460
|
if (existing?.authorityState === "delete_pending") throw new Error("topic authority is deletion-fenced");
|
|
461
|
+
if (existing?.bindingMalformed) throw new Error("topic authority binding is quarantined");
|
|
181
462
|
if (existing) return existing;
|
|
182
463
|
const pending = this.inflight.get(sessionId);
|
|
183
464
|
if (pending) return pending;
|
|
184
465
|
const epoch = this.epochs.get(sessionId) ?? 0;
|
|
466
|
+
// Publish the compatible endpoint claim before invoking `create`: the callback
|
|
467
|
+
// may immediately begin a remote create and identity-less recovery must never
|
|
468
|
+
// observe a false absence during that await.
|
|
469
|
+
if (binding) this.creatingBindings.set(sessionId, binding);
|
|
470
|
+
this.transientClaimants.set(sessionId, transientClaimant);
|
|
185
471
|
const promise = (async () => {
|
|
186
472
|
const topicId = await create();
|
|
187
473
|
if (!isValidTopicId(topicId)) throw new Error("createForumTopic: invalid message_thread_id");
|
|
@@ -192,10 +478,39 @@ export class TopicRegistry {
|
|
|
192
478
|
identitySent: false,
|
|
193
479
|
createdAt: now(),
|
|
194
480
|
authorityEpoch: revoked ? (this.epochs.get(sessionId) ?? 0) : epoch,
|
|
481
|
+
creationLeaseEpoch: epoch,
|
|
482
|
+
...(binding
|
|
483
|
+
? {
|
|
484
|
+
chatId: binding.chatId,
|
|
485
|
+
endpointKey: binding.endpointKey,
|
|
486
|
+
endpointDigest: binding.endpointDigest,
|
|
487
|
+
endpointIncarnation: 0,
|
|
488
|
+
...(binding.endpointGeneration === undefined
|
|
489
|
+
? {}
|
|
490
|
+
: { endpointGeneration: binding.endpointGeneration }),
|
|
491
|
+
}
|
|
492
|
+
: {}),
|
|
195
493
|
...(revoked ? { authorityState: "delete_pending" as const } : {}),
|
|
196
494
|
};
|
|
495
|
+
if (revoked) {
|
|
496
|
+
this.topics.set(sessionId, record);
|
|
497
|
+
throw new Error("topic authority was revoked during creation");
|
|
498
|
+
}
|
|
499
|
+
this.staged.set(sessionId, record);
|
|
500
|
+
try {
|
|
501
|
+
await commit?.();
|
|
502
|
+
} catch (error) {
|
|
503
|
+
this.staged.delete(sessionId);
|
|
504
|
+
throw error;
|
|
505
|
+
}
|
|
506
|
+
this.staged.delete(sessionId);
|
|
507
|
+
if ((this.epochs.get(sessionId) ?? 0) !== epoch) {
|
|
508
|
+
record.authorityEpoch = this.epochs.get(sessionId) ?? 0;
|
|
509
|
+
record.authorityState = "delete_pending";
|
|
510
|
+
this.topics.set(sessionId, record);
|
|
511
|
+
throw new Error("topic authority was revoked during creation");
|
|
512
|
+
}
|
|
197
513
|
this.topics.set(sessionId, record);
|
|
198
|
-
if (revoked) throw new Error("topic authority was revoked during creation");
|
|
199
514
|
if (this.#ambiguousTopicIds.has(topicId)) return record;
|
|
200
515
|
if (this.byTopic.has(topicId)) {
|
|
201
516
|
this.byTopic.delete(topicId);
|
|
@@ -210,6 +525,8 @@ export class TopicRegistry {
|
|
|
210
525
|
return await promise;
|
|
211
526
|
} finally {
|
|
212
527
|
this.inflight.delete(sessionId);
|
|
528
|
+
this.creatingBindings.delete(sessionId);
|
|
529
|
+
this.transientClaimants.delete(sessionId);
|
|
213
530
|
}
|
|
214
531
|
}
|
|
215
532
|
|
|
@@ -323,6 +640,67 @@ export class TopicRegistry {
|
|
|
323
640
|
record.nameReconcilePending = false;
|
|
324
641
|
}
|
|
325
642
|
|
|
643
|
+
/** Capture only authority fields that a failed delete publication may restore. */
|
|
644
|
+
captureDeleteAuthority(sessionId: string): TopicDeleteAuthoritySnapshot {
|
|
645
|
+
const record = this.topics.get(sessionId);
|
|
646
|
+
return {
|
|
647
|
+
sessionId,
|
|
648
|
+
topicId: record?.topicId,
|
|
649
|
+
authorityEpoch: record?.authorityEpoch,
|
|
650
|
+
authorityState: record?.authorityState,
|
|
651
|
+
fenceEpoch: this.epochs.get(sessionId),
|
|
652
|
+
...(record ? { record: { ...record } } : {}),
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** Restore a failed delete fence only while its exact authority mutation remains current. */
|
|
657
|
+
restoreDeleteAuthority(snapshot: TopicDeleteAuthoritySnapshot): boolean {
|
|
658
|
+
const record = this.topics.get(snapshot.sessionId);
|
|
659
|
+
const deleteEpoch = Math.max(snapshot.fenceEpoch ?? 0, snapshot.authorityEpoch ?? 0) + 1;
|
|
660
|
+
if (this.epochs.get(snapshot.sessionId) !== deleteEpoch) return false;
|
|
661
|
+
if (snapshot.topicId === undefined) {
|
|
662
|
+
if (record) return false;
|
|
663
|
+
} else if (
|
|
664
|
+
!record ||
|
|
665
|
+
record.topicId !== snapshot.topicId ||
|
|
666
|
+
record.authorityState !== "delete_pending" ||
|
|
667
|
+
record.authorityEpoch !== deleteEpoch
|
|
668
|
+
) {
|
|
669
|
+
return false;
|
|
670
|
+
} else {
|
|
671
|
+
record.authorityEpoch = snapshot.authorityEpoch;
|
|
672
|
+
record.authorityState = snapshot.authorityState;
|
|
673
|
+
this.rebuildInboundRoutes();
|
|
674
|
+
}
|
|
675
|
+
if (snapshot.fenceEpoch === undefined) this.epochs.delete(snapshot.sessionId);
|
|
676
|
+
else this.epochs.set(snapshot.sessionId, snapshot.fenceEpoch);
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/** Restore the exact delete fence after a failed compensation publication. */
|
|
681
|
+
restoreDeleteFence(snapshot: TopicDeleteAuthoritySnapshot): boolean {
|
|
682
|
+
const record = this.topics.get(snapshot.sessionId);
|
|
683
|
+
const deleteEpoch = Math.max(snapshot.fenceEpoch ?? 0, snapshot.authorityEpoch ?? 0) + 1;
|
|
684
|
+
if (snapshot.topicId === undefined) {
|
|
685
|
+
if (record) return false;
|
|
686
|
+
} else if (!record) {
|
|
687
|
+
if (!snapshot.record) return false;
|
|
688
|
+
this.topics.set(snapshot.sessionId, {
|
|
689
|
+
...snapshot.record,
|
|
690
|
+
authorityEpoch: deleteEpoch,
|
|
691
|
+
authorityState: "delete_pending",
|
|
692
|
+
});
|
|
693
|
+
} else if (record.topicId !== snapshot.topicId) {
|
|
694
|
+
return false;
|
|
695
|
+
} else {
|
|
696
|
+
record.authorityEpoch = deleteEpoch;
|
|
697
|
+
record.authorityState = "delete_pending";
|
|
698
|
+
if (this.byTopic.get(record.topicId) === snapshot.sessionId) this.byTopic.delete(record.topicId);
|
|
699
|
+
}
|
|
700
|
+
this.epochs.set(snapshot.sessionId, deleteEpoch);
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
|
|
326
704
|
/** Fence new work before the remote delete starts, including an absent in-flight create. */
|
|
327
705
|
beginDelete(sessionId: string): TopicRecord | undefined {
|
|
328
706
|
const record = this.topics.get(sessionId);
|
|
@@ -335,6 +713,67 @@ export class TopicRegistry {
|
|
|
335
713
|
return record;
|
|
336
714
|
}
|
|
337
715
|
|
|
716
|
+
/** Retain an accepted create as deletion-fenced before remote compensation can begin. */
|
|
717
|
+
fenceAcceptedCreate(
|
|
718
|
+
sessionId: string,
|
|
719
|
+
topicId: string,
|
|
720
|
+
now: () => number = Date.now,
|
|
721
|
+
name?: string,
|
|
722
|
+
binding?: TopicEndpointBinding,
|
|
723
|
+
): TopicRecord {
|
|
724
|
+
const epoch = Math.max(this.epochs.get(sessionId) ?? 0, this.topics.get(sessionId)?.authorityEpoch ?? 0);
|
|
725
|
+
const record: TopicRecord = {
|
|
726
|
+
topicId,
|
|
727
|
+
name,
|
|
728
|
+
identitySent: false,
|
|
729
|
+
createdAt: now(),
|
|
730
|
+
authorityEpoch: epoch,
|
|
731
|
+
authorityState: "delete_pending",
|
|
732
|
+
...(binding
|
|
733
|
+
? {
|
|
734
|
+
chatId: binding.chatId,
|
|
735
|
+
endpointKey: binding.endpointKey,
|
|
736
|
+
endpointDigest: binding.endpointDigest,
|
|
737
|
+
endpointIncarnation: 0,
|
|
738
|
+
...(binding.endpointGeneration === undefined
|
|
739
|
+
? {}
|
|
740
|
+
: { endpointGeneration: binding.endpointGeneration }),
|
|
741
|
+
}
|
|
742
|
+
: {}),
|
|
743
|
+
};
|
|
744
|
+
this.topics.set(sessionId, record);
|
|
745
|
+
if (this.byTopic.get(topicId) === sessionId) this.byTopic.delete(topicId);
|
|
746
|
+
this.#ambiguousTopicIds.add(topicId);
|
|
747
|
+
return record;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/** Fence an accepted create only when its exact creator lease still owns the record. */
|
|
751
|
+
fenceAcceptedCreateForLease(
|
|
752
|
+
sessionId: string,
|
|
753
|
+
topicId: string,
|
|
754
|
+
creationLeaseEpoch: number,
|
|
755
|
+
now: () => number = Date.now,
|
|
756
|
+
name?: string,
|
|
757
|
+
binding?: TopicEndpointBinding,
|
|
758
|
+
): TopicRecord | undefined {
|
|
759
|
+
const record = this.topics.get(sessionId);
|
|
760
|
+
const matchesBinding =
|
|
761
|
+
record?.chatId === binding?.chatId &&
|
|
762
|
+
record?.endpointKey === binding?.endpointKey &&
|
|
763
|
+
record?.endpointDigest === binding?.endpointDigest &&
|
|
764
|
+
record?.endpointGeneration === binding?.endpointGeneration;
|
|
765
|
+
if (
|
|
766
|
+
record
|
|
767
|
+
? record.topicId !== topicId || record.creationLeaseEpoch !== creationLeaseEpoch || !matchesBinding
|
|
768
|
+
: (this.epochs.get(sessionId) ?? 0) !== creationLeaseEpoch
|
|
769
|
+
)
|
|
770
|
+
return undefined;
|
|
771
|
+
this.beginDelete(sessionId);
|
|
772
|
+
const fenced = this.fenceAcceptedCreate(sessionId, topicId, now, name, binding);
|
|
773
|
+
fenced.creationLeaseEpoch = creationLeaseEpoch;
|
|
774
|
+
return fenced;
|
|
775
|
+
}
|
|
776
|
+
|
|
338
777
|
/** Wait for a revoked create to settle before admitting a later lifecycle epoch. */
|
|
339
778
|
async awaitInflight(sessionId: string): Promise<void> {
|
|
340
779
|
await this.inflight.get(sessionId)?.catch(() => undefined);
|
|
@@ -357,8 +796,8 @@ export class TopicRegistry {
|
|
|
357
796
|
return this.topics.delete(sessionId);
|
|
358
797
|
}
|
|
359
798
|
|
|
360
|
-
/** Serialise
|
|
799
|
+
/** Serialise active records plus unpublished staged creates for atomic commit. */
|
|
361
800
|
serialize(): TopicRegistryState {
|
|
362
|
-
return { topics: Object.fromEntries(this.topics), fences: Object.fromEntries(this.epochs) };
|
|
801
|
+
return { topics: Object.fromEntries([...this.topics, ...this.staged]), fences: Object.fromEntries(this.epochs) };
|
|
363
802
|
}
|
|
364
803
|
}
|
|
@@ -40,6 +40,7 @@ class SdkSessionCliError extends Error {
|
|
|
40
40
|
readonly code: string,
|
|
41
41
|
message: string,
|
|
42
42
|
readonly exitCode: 1 | 2,
|
|
43
|
+
readonly details?: unknown,
|
|
43
44
|
) {
|
|
44
45
|
super(message);
|
|
45
46
|
}
|
|
@@ -261,7 +262,12 @@ export async function runSdkSessionCli(
|
|
|
261
262
|
error instanceof SdkSessionCliError
|
|
262
263
|
? error
|
|
263
264
|
: error instanceof SdkClientError
|
|
264
|
-
? new SdkSessionCliError(
|
|
265
|
+
? new SdkSessionCliError(
|
|
266
|
+
error.code,
|
|
267
|
+
error.message,
|
|
268
|
+
1,
|
|
269
|
+
(error.details as { details?: unknown } | undefined)?.details,
|
|
270
|
+
)
|
|
265
271
|
: error instanceof SdkDiscoveryError
|
|
266
272
|
? new SdkSessionCliError(error.code, error.message, 1)
|
|
267
273
|
: new SdkSessionCliError(
|
|
@@ -269,7 +275,14 @@ export async function runSdkSessionCli(
|
|
|
269
275
|
error instanceof Error ? error.message : "SDK operation failed.",
|
|
270
276
|
1,
|
|
271
277
|
);
|
|
272
|
-
writeOutput({
|
|
278
|
+
writeOutput({
|
|
279
|
+
ok: false,
|
|
280
|
+
error: {
|
|
281
|
+
code: cliError.code,
|
|
282
|
+
message: cliError.message,
|
|
283
|
+
...(cliError.details ? { details: cliError.details } : {}),
|
|
284
|
+
},
|
|
285
|
+
});
|
|
273
286
|
setExitCode(cliError.exitCode);
|
|
274
287
|
}
|
|
275
288
|
}
|