mixdog 1.0.12 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -182
- package/package.json +4 -2
- package/scripts/build-shipped-history.mjs +131 -0
- package/scripts/latest-guard.mjs +63 -0
- package/scripts/lib/run-node-tests.mjs +330 -32
- package/scripts/lib/test-failure-records.mjs +5 -0
- package/scripts/lib/test-failure-summary.mjs +14 -3
- package/scripts/lib/test-spawn-ledger.mjs +19 -0
- package/scripts/merge-mac-update-feed.mjs +107 -0
- package/scripts/mobile-release.mjs +210 -0
- package/scripts/post-release-check.mjs +353 -0
- package/scripts/prune-embedding-runtime.mjs +9 -5
- package/scripts/publish-asset-release.mjs +43 -0
- package/scripts/release-guard.mjs +63 -0
- package/scripts/test-direct.mjs +16 -1
- package/scripts/test.mjs +29 -7
- package/src/agents/advisor/AGENT.md +38 -0
- package/src/agents/advisor/agent.json +6 -0
- package/src/agents/reviewer/AGENT.md +5 -2
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +383 -0
- package/src/defaults/skills/computer-use/SKILL.md +1 -1
- package/src/defaults/skills/goal-management/SKILL.md +11 -5
- package/src/defaults/skills/setup/SKILL.md +5 -8
- package/src/defaults/skills/setup/references/actions.md +62 -31
- package/src/defaults/skills/setup/references/surfaces.md +12 -10
- package/src/lib/keychain-cjs.cjs +582 -17
- package/src/lib/rules-builder.cjs +1 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.d.mts +1 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +44 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +9 -1
- package/src/runtime/agent/orchestrator/config-storage.mjs +32 -4
- package/src/runtime/agent/orchestrator/config.mjs +120 -21
- package/src/runtime/agent/orchestrator/context/instruction-files.mjs +130 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +77 -26
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +44 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +388 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +92 -109
- package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +107 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +51 -9
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/lib/open-login-url.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +254 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +218 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +239 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +233 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-login.mjs +247 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +225 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +542 -0
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +18 -10
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +51 -11
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +7 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +37 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +12 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +99 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +4 -8
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +3 -7
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +70 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +42 -3
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +19 -35
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +7 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +0 -17
- package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +0 -3
- package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +0 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +16 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +18 -4
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -1
- package/src/runtime/agent/orchestrator/runtime-core/session-route-policy.mjs +16 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +2 -2
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +7 -7
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -1
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +28 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-call-ids.mjs +90 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +19 -11
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +24 -11
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +2 -6
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +63 -3
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/turn-effort.mjs +18 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +11 -8
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +27 -12
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +245 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +13 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +49 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -7
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +19 -20
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +67 -19
- package/src/runtime/agent/orchestrator/tools/builtin/file-version-test-helpers.mjs +11 -0
- package/src/runtime/agent/orchestrator/tools/builtin/file-version.mjs +13 -0
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +28 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +8 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +25 -12
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +73 -1
- package/src/runtime/agent/orchestrator/tools/builtin/raw-content-publication.mjs +17 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +136 -76
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +15 -17
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +11 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +30 -16
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +17 -6
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +23 -5
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +93 -43
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +4 -11
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-validation.mjs +16 -19
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +32 -6
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +9 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +14 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +58 -8
- package/src/runtime/agent/orchestrator/tools/patch/content-guard.mjs +47 -0
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +155 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-proofs.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-snapshots.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +19 -16
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +13 -44
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/post-patch-excerpts.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +47 -7
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +72 -8
- package/src/runtime/agent/orchestrator/tools/patch/section-coalesce.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/patch/sequence/run.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +16 -4
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +35 -14
- package/src/runtime/agent/orchestrator/tools/patch/wave.mjs +42 -4
- package/src/runtime/agent/orchestrator/tools/shell-codepage.mjs +296 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +124 -81
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +75 -24
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +19 -4
- package/src/runtime/channels/lib/config.mjs +3 -8
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +7 -3
- package/src/{tui → runtime/channels}/lib/voice-setup.mjs +38 -22
- package/src/runtime/channels/lib/webhook/request-handler.mjs +42 -8
- package/src/runtime/computer-bridge/actions.mjs +2 -0
- package/src/runtime/computer-bridge/client.mjs +25 -1
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +84 -0
- package/src/runtime/computer-bridge/linux-readiness.d.mts +52 -0
- package/src/runtime/computer-bridge/linux-readiness.mjs +411 -0
- package/src/runtime/computer-bridge/test-linux-display.mjs +5 -0
- package/src/runtime/effort-judge/compact-tokenizer.mjs +13 -8
- package/src/runtime/effort-judge/judge-client.mjs +12 -3
- package/src/runtime/effort-judge/judge-input.mjs +5 -2
- package/src/runtime/effort-judge/model-install.mjs +9 -3
- package/src/runtime/effort-judge/model-manifest.json +8 -8
- package/src/runtime/effort-judge/previous-human-request.mjs +162 -0
- package/src/runtime/effort-judge/verified-context-input.mjs +10 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +12 -0
- package/src/runtime/memory/lib/embedding-worker.mjs +9 -3
- package/src/runtime/memory/lib/http-router/session-routes.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +3 -2
- package/src/runtime/memory/lib/memory-embed.mjs +5 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +3 -0
- package/src/runtime/shared/agent-route-config.mjs +50 -2
- package/src/runtime/shared/bounded-file-read.mjs +166 -0
- package/src/runtime/shared/builtin-grandfathering.mjs +37 -0
- package/src/runtime/shared/config.mjs +74 -6
- package/src/runtime/shared/defaults-separation.mjs +263 -0
- package/src/runtime/shared/developer-options.mjs +7 -0
- package/src/runtime/shared/instruction-files-mode.mjs +9 -0
- package/src/runtime/shared/llm/anthropic-effort.mjs +4 -0
- package/src/runtime/shared/llm/model-catalog.mjs +25 -1
- package/src/runtime/shared/llm/openai-daybreak.mjs +79 -0
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +105 -1
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +2 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +72 -5
- package/src/runtime/shared/onnx-runtime-support.mjs +6 -0
- package/src/runtime/shared/orchestration.mjs +14 -6
- package/src/runtime/shared/pristine-execution-contract.json +9 -0
- package/src/runtime/shared/pristine-execution.mjs +1 -1
- package/src/runtime/shared/projects.mjs +65 -48
- package/src/runtime/shared/provider-accounts.mjs +49 -6
- package/src/runtime/shared/quota-resume-store.mjs +255 -0
- package/src/runtime/shared/resource-admission.mjs +10 -3
- package/src/runtime/shared/schedule-model-ref.mjs +11 -0
- package/src/runtime/shared/session-approval-wait.mjs +35 -0
- package/src/runtime/shared/shell-display.mjs +4 -2
- package/src/runtime/shared/shipped-definitions.mjs +275 -0
- package/src/runtime/shared/tool-card-model.golden.json +3 -3
- package/src/runtime/shared/tool-result-summary.mjs +86 -15
- package/src/runtime/shared/transcript-metadata.mjs +2 -0
- package/src/runtime/shared/turn-review-reply.mjs +2 -0
- package/src/runtime/shared/turn-snapshot.mjs +65 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +208 -27
- package/src/session-runtime/boot/apis.mjs +24 -0
- package/src/session-runtime/boot/config.mjs +4 -0
- package/src/session-runtime/boot/facade.mjs +4 -1
- package/src/session-runtime/boot/infrastructure.mjs +21 -2
- package/src/session-runtime/boot/session.mjs +3 -0
- package/src/session-runtime/channel-config-api.mjs +5 -0
- package/src/session-runtime/config-lifecycle/config-writers.mjs +3 -0
- package/src/session-runtime/config-lifecycle.mjs +31 -6
- package/src/session-runtime/goal-facade-api.mjs +4 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +28 -6
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +3 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -1
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +6 -0
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +14 -2
- package/src/session-runtime/media-api.mjs +32 -22
- package/src/session-runtime/model-recency.mjs +17 -0
- package/src/session-runtime/model-route/route-tuning.mjs +3 -5
- package/src/session-runtime/model-route/set-route.mjs +49 -21
- package/src/session-runtime/model-route-api.mjs +2 -1
- package/src/session-runtime/notification-bus.mjs +31 -6
- package/src/session-runtime/provider-auth/login.mjs +16 -10
- package/src/session-runtime/provider-auth/setup.mjs +22 -2
- package/src/session-runtime/provider-models/catalog-load.mjs +3 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +26 -17
- package/src/session-runtime/provider-readiness.mjs +4 -0
- package/src/session-runtime/quick-model-rows/model-meta.mjs +26 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +40 -14
- package/src/session-runtime/resource-api.mjs +8 -1
- package/src/session-runtime/runtime-core.mjs +4 -0
- package/src/session-runtime/runtime-facade.mjs +25 -1
- package/src/session-runtime/runtime-review-api.mjs +12 -1
- package/src/session-runtime/schedule-session-run.mjs +3 -1
- package/src/session-runtime/services/agent-tool/helpers.mjs +4 -0
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +63 -27
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +15 -3
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +11 -3
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +2 -0
- package/src/session-runtime/services/agent-tool/worker-index/approval-wait.mjs +97 -0
- package/src/session-runtime/services/agent-tool/worker-index/mutation-batch.mjs +4 -2
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -2
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +3 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +58 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +69 -15
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +2 -0
- package/src/session-runtime/services/channel-admin.mjs +91 -15
- package/src/session-runtime/services/provider-admin.mjs +336 -80
- package/src/session-runtime/services/seeds.mjs +80 -152
- package/src/session-runtime/services/usage-dashboard-model.mjs +35 -0
- package/src/session-runtime/services/usage-dashboard.mjs +7 -0
- package/src/session-runtime/session-address.mjs +125 -0
- package/src/session-runtime/session-create.mjs +57 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +23 -0
- package/src/session-runtime/settings-compaction-api.mjs +13 -0
- package/src/session-runtime/setup-tool/executor.mjs +97 -40
- package/src/session-runtime/setup-tool/extended-actions.mjs +64 -5
- package/src/session-runtime/setup-tool/settings-contract.mjs +44 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -7
- package/src/session-runtime/tool-policy-refresh.mjs +3 -0
- package/src/session-runtime/tool-surface.mjs +16 -9
- package/src/session-runtime/turn/session-ops.mjs +3 -0
- package/src/session-runtime/turn/share-ops.mjs +4 -1
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +6 -0
- package/src/session-runtime/usage-stats-api.mjs +15 -0
- package/src/session-runtime/webhook-session-run.mjs +3 -1
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +31 -5
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +4 -0
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +27 -7
- package/src/standalone/daemon-boot-coordinator.mjs +10 -0
- package/src/standalone/daemon-drain-policy.mjs +9 -0
- package/src/standalone/daemon-stored-session-views.mjs +7 -8
- package/src/standalone/daemon.mjs +21 -5
- package/src/standalone/local-session-runtime.mjs +11 -0
- package/src/standalone/session-client.mjs +52 -7
- package/src/standalone/session-protocol.mjs +9 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +2 -0
- package/src/standalone/session-service/entries/lifecycle.mjs +2 -0
- package/src/standalone/session-service/entries/session-loads.mjs +9 -0
- package/src/standalone/session-service/projection/revisions.mjs +28 -4
- package/src/standalone/session-service/projection.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +140 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +2 -2
- package/src/standalone/session-service/session-calls.mjs +10 -2
- package/src/standalone/session-service/stored-reader.mjs +26 -1
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-transport/routes.mjs +2 -0
- package/src/tui/app/doctor.mjs +7 -11
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +3 -5
- package/src/tui/app/model-options.mjs +13 -1
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +10 -4
- package/src/tui/app/provider-setup/oauth-actions.mjs +5 -0
- package/src/tui/app/settings-picker/settings-rows.mjs +4 -6
- package/src/tui/app/settings-picker/settings-toggles/store-toggles.mjs +1 -1
- package/src/tui/app/slash-dispatch/route-commands.mjs +3 -2
- package/src/tui/components/ContextPanel.jsx +5 -1
- package/src/tui/components/Spinner.jsx +34 -319
- package/src/tui/components/SpinnerByline.jsx +256 -0
- package/src/tui/components/use-spinner-progress.mjs +113 -0
- package/src/tui/dist/index.mjs +15421 -14283
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +13 -1
- package/src/tui/session/agent-job-feed/notification-router.mjs +3 -0
- package/src/tui/session/agent-job-feed/pending-resume.mjs +14 -1
- package/src/tui/session/agent-job-feed.mjs +4 -0
- package/src/tui/session/context-state.mjs +15 -1
- package/src/tui/session/initial-state.mjs +6 -0
- package/src/tui/session/live-share/owner.mjs +17 -0
- package/src/tui/session/oauth-flows.mjs +2 -0
- package/src/tui/session/quota-resume.mjs +263 -0
- package/src/tui/session/session-api/intake/abort.mjs +39 -35
- package/src/tui/session/session-api/intake/submission.mjs +13 -21
- package/src/tui/session/session-api/intake.mjs +3 -3
- package/src/tui/session/session-api/integrations.mjs +13 -9
- package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
- package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -2
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +4 -0
- package/src/tui/session/session-api/lifecycle.mjs +71 -2
- package/src/tui/session/session-api/media.mjs +2 -2
- package/src/tui/session/session-api/quota-resume.mjs +21 -0
- package/src/tui/session/session-api/routes.mjs +4 -2
- package/src/tui/session/session-api/settings.mjs +2 -0
- package/src/tui/session/session-api.mjs +2 -0
- package/src/tui/session/session-flow/auto-clear.mjs +14 -6
- package/src/tui/session/session-flow/drain.mjs +52 -5
- package/src/tui/session/session-flow/queue/take-entries.mjs +2 -3
- package/src/tui/session/session-flow/queue.mjs +3 -0
- package/src/tui/session/session-flow/steering.mjs +11 -0
- package/src/tui/session/session-flow.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +7 -2
- package/src/tui/session/tool-card-results/grouped-result.mjs +11 -2
- package/src/tui/session/transcript-intake.mjs +1 -0
- package/src/tui/session/transcript-spill/spill-dir.mjs +22 -6
- package/src/tui/session/turn.mjs +40 -3
- package/src/tui/spinner-meta.mjs +26 -10
- package/src/vendor/statusline/bin/statusline-route.mjs +3 -2
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// The mobile (Android APK + iOS TestFlight) side of Deploy.
|
|
2
|
+
//
|
|
3
|
+
// plan: whether this deploy ships a mobile release and under which version.
|
|
4
|
+
// A release is due when anything the app binaries are built from changed
|
|
5
|
+
// since the newest mobile-v* tag (input `auto`), always (`force`), or never
|
|
6
|
+
// (`skip`). The version is the next patch (or minor) of that tag. The tag
|
|
7
|
+
// itself is created by the Android release, so a failed mobile release
|
|
8
|
+
// leaves no tag and the next deploy plans the same version again.
|
|
9
|
+
// CLI: MOBILE_MODE=auto|force|skip MOBILE_BUMP=patch|minor node scripts/mobile-release.mjs plan
|
|
10
|
+
// appends mobile_release / mobile_version / mobile_tag to $GITHUB_OUTPUT.
|
|
11
|
+
//
|
|
12
|
+
// link: the Android release the relay's /app page links — the newest
|
|
13
|
+
// published (non-draft) mobile-v* release whose APK actually downloads, or
|
|
14
|
+
// the releases list when there is none. `--require=<tag>` instead fails
|
|
15
|
+
// unless that exact release is the one that resolves (post-publish check).
|
|
16
|
+
// CLI: GITHUB_REPOSITORY=o/r GH_TOKEN=… node scripts/mobile-release.mjs link [--require=mobile-vX.Y.Z]
|
|
17
|
+
// appends url / tag to $GITHUB_OUTPUT.
|
|
18
|
+
import { appendFileSync } from 'node:fs';
|
|
19
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
20
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
21
|
+
import { pathToFileURL } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const TAG_RE = /^mobile-v(\d+)\.(\d+)\.(\d+)$/;
|
|
24
|
+
|
|
25
|
+
// Everything the APK and the iOS build are made from. The app bundles one
|
|
26
|
+
// desktop module (apps/mobile/src/main.ts imports shared/native-app.ts, which
|
|
27
|
+
// imports nothing). Relay changes are not listed: the relay deploys on its
|
|
28
|
+
// own with every release and the app binary does not embed it.
|
|
29
|
+
export const MOBILE_RELEASE_PATHS = [
|
|
30
|
+
'apps/mobile',
|
|
31
|
+
':(exclude,glob)apps/mobile/**/*.md',
|
|
32
|
+
'apps/desktop/src/shared/native-app.ts',
|
|
33
|
+
'.github/workflows/mobile-android.yml',
|
|
34
|
+
'.github/workflows/mobile-ios.yml',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export function parseMobileTag(tag) {
|
|
38
|
+
const match = TAG_RE.exec(String(tag ?? '').trim());
|
|
39
|
+
return match ? match.slice(1).map(Number) : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function compareMobileTags(left, right) {
|
|
43
|
+
const a = parseMobileTag(left);
|
|
44
|
+
const b = parseMobileTag(right);
|
|
45
|
+
for (let index = 0; index < 3; index += 1) {
|
|
46
|
+
if (a[index] !== b[index]) return a[index] - b[index];
|
|
47
|
+
}
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function latestMobileTag(tags) {
|
|
52
|
+
const valid = tags.map((tag) => String(tag).trim()).filter((tag) => parseMobileTag(tag));
|
|
53
|
+
return valid.sort(compareMobileTags).at(-1) ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function nextMobileVersion(tag, bump = 'patch') {
|
|
57
|
+
const parts = parseMobileTag(tag);
|
|
58
|
+
if (!parts) throw new Error(`invalid mobile release tag: ${tag}`);
|
|
59
|
+
const [major, minor, patch] = parts;
|
|
60
|
+
if (bump === 'minor') return `${major}.${minor + 1}.0`;
|
|
61
|
+
if (bump === 'patch') return `${major}.${minor}.${patch + 1}`;
|
|
62
|
+
throw new Error(`invalid mobile version bump: ${bump}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** `changedSince(tag)` answers whether a mobile path changed between tag and HEAD. */
|
|
66
|
+
export function planMobileRelease({ mode = 'auto', bump = 'patch', tags, changedSince }) {
|
|
67
|
+
if (!['auto', 'force', 'skip'].includes(mode)) throw new Error(`invalid mobile mode: ${mode}`);
|
|
68
|
+
if (!['patch', 'minor'].includes(bump)) throw new Error(`invalid mobile version bump: ${bump}`);
|
|
69
|
+
const base = latestMobileTag(tags);
|
|
70
|
+
const none = (reason) => ({ release: false, version: '', tag: '', base: base ?? '', reason });
|
|
71
|
+
if (mode === 'skip') return none('skipped by the mobile input');
|
|
72
|
+
// Versions continue from the newest tag; without one there is nothing to
|
|
73
|
+
// continue from, and guessing could overwrite a published release.
|
|
74
|
+
if (!base) return none('no mobile-v* tag yet: push the first one by hand');
|
|
75
|
+
const changed = changedSince(base);
|
|
76
|
+
if (mode === 'auto' && !changed) return none(`no mobile source changed since ${base}`);
|
|
77
|
+
const version = nextMobileVersion(base, bump);
|
|
78
|
+
return {
|
|
79
|
+
release: true,
|
|
80
|
+
version,
|
|
81
|
+
tag: `mobile-v${version}`,
|
|
82
|
+
base,
|
|
83
|
+
reason: changed ? `mobile sources changed since ${base}` : 'forced by the mobile input',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function mobilePathsChangedSince(tag, { cwd } = {}) {
|
|
88
|
+
const result = spawnSync('git', ['diff', '--quiet', `${tag}..HEAD`, '--', ...MOBILE_RELEASE_PATHS], { cwd });
|
|
89
|
+
if (result.status === 0) return false;
|
|
90
|
+
if (result.status === 1) return true;
|
|
91
|
+
throw new Error(`git diff failed for ${tag}: ${result.stderr?.toString() || result.status}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Published mobile releases that carry an APK, newest version first. */
|
|
95
|
+
export function androidReleaseCandidates(releases) {
|
|
96
|
+
return releases
|
|
97
|
+
.filter((release) => !release.draft && parseMobileTag(release.tag_name))
|
|
98
|
+
.map((release) => ({
|
|
99
|
+
tag: release.tag_name,
|
|
100
|
+
url: release.html_url,
|
|
101
|
+
apkUrl: release.assets?.find((asset) => /\.apk$/i.test(asset.name || ''))?.browser_download_url,
|
|
102
|
+
}))
|
|
103
|
+
.filter((release) => release.url && release.apkUrl)
|
|
104
|
+
.sort((left, right) => compareMobileTags(right.tag, left.tag));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function listReleases(repository, { token, fetchImpl }) {
|
|
108
|
+
const releases = [];
|
|
109
|
+
for (let page = 1; ; page += 1) {
|
|
110
|
+
const response = await fetchImpl(`https://api.github.com/repos/${repository}/releases?per_page=100&page=${page}`, {
|
|
111
|
+
headers: {
|
|
112
|
+
Accept: 'application/vnd.github+json',
|
|
113
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) throw new Error(`listing releases failed: HTTP ${response.status}`);
|
|
117
|
+
const batch = await response.json();
|
|
118
|
+
releases.push(...batch);
|
|
119
|
+
if (batch.length < 100) return releases;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function downloadable(url, { fetchImpl, attempts, delayMs }) {
|
|
124
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
125
|
+
try {
|
|
126
|
+
const response = await fetchImpl(url, { method: 'HEAD', redirect: 'follow' });
|
|
127
|
+
if (response.ok) return true;
|
|
128
|
+
} catch {
|
|
129
|
+
/* retried below */
|
|
130
|
+
}
|
|
131
|
+
if (attempt < attempts) await delay(delayMs);
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function resolveAndroidLink({
|
|
137
|
+
repository,
|
|
138
|
+
token = '',
|
|
139
|
+
requiredTag = '',
|
|
140
|
+
fetchImpl = fetch,
|
|
141
|
+
attempts = 3,
|
|
142
|
+
delayMs = 5000,
|
|
143
|
+
}) {
|
|
144
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository || '')) {
|
|
145
|
+
throw new Error(`invalid repository: ${repository}`);
|
|
146
|
+
}
|
|
147
|
+
if (requiredTag && !parseMobileTag(requiredTag)) throw new Error(`invalid mobile release tag: ${requiredTag}`);
|
|
148
|
+
const candidates = androidReleaseCandidates(await listReleases(repository, { token, fetchImpl }));
|
|
149
|
+
for (const candidate of requiredTag ? candidates.filter((item) => item.tag === requiredTag) : candidates) {
|
|
150
|
+
if (await downloadable(candidate.apkUrl, { fetchImpl, attempts, delayMs })) {
|
|
151
|
+
return { url: candidate.url, tag: candidate.tag };
|
|
152
|
+
}
|
|
153
|
+
if (!requiredTag) console.warn(`::warning::${candidate.tag} APK is not downloadable; trying an older release.`);
|
|
154
|
+
}
|
|
155
|
+
if (requiredTag) throw new Error(`${requiredTag} is not a published release with a downloadable APK`);
|
|
156
|
+
return { url: `https://github.com/${repository}/releases`, tag: '' };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function writeOutputs(values) {
|
|
160
|
+
if (process.env.GITHUB_OUTPUT) {
|
|
161
|
+
appendFileSync(
|
|
162
|
+
process.env.GITHUB_OUTPUT,
|
|
163
|
+
Object.entries(values)
|
|
164
|
+
.map(([key, value]) => `${key}=${value}\n`)
|
|
165
|
+
.join('')
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
console.log(JSON.stringify(values, null, 2));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function main([command, ...args]) {
|
|
172
|
+
if (command === 'plan') {
|
|
173
|
+
const tags = execFileSync('git', ['tag', '--list', 'mobile-v*'], { encoding: 'utf8' }).split(/\r?\n/);
|
|
174
|
+
const plan = planMobileRelease({
|
|
175
|
+
mode: process.env.MOBILE_MODE || 'auto',
|
|
176
|
+
bump: process.env.MOBILE_BUMP || 'patch',
|
|
177
|
+
tags,
|
|
178
|
+
changedSince: (tag) => mobilePathsChangedSince(tag),
|
|
179
|
+
});
|
|
180
|
+
writeOutputs({
|
|
181
|
+
mobile_release: String(plan.release),
|
|
182
|
+
mobile_version: plan.version,
|
|
183
|
+
mobile_tag: plan.tag,
|
|
184
|
+
mobile_base: plan.base,
|
|
185
|
+
mobile_reason: plan.reason,
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (command === 'link') {
|
|
190
|
+
const requiredTag = args.find((arg) => arg.startsWith('--require='))?.slice('--require='.length) || '';
|
|
191
|
+
writeOutputs(
|
|
192
|
+
await resolveAndroidLink({
|
|
193
|
+
repository: process.env.GITHUB_REPOSITORY,
|
|
194
|
+
token: process.env.GH_TOKEN || '',
|
|
195
|
+
requiredTag,
|
|
196
|
+
// Right after publishing, the asset CDN can lag a few seconds.
|
|
197
|
+
attempts: requiredTag ? 6 : 3,
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
throw new Error('usage: node scripts/mobile-release.mjs plan | link [--require=mobile-vX.Y.Z]');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
|
|
206
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
207
|
+
console.error(`::error::${error.message}`);
|
|
208
|
+
process.exitCode = 1;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Final job of Deploy: verify the published app release end to end.
|
|
3
|
+
//
|
|
4
|
+
// - GitHub Latest is the new app tag
|
|
5
|
+
// - the release asset set matches the asset contract (release.yml
|
|
6
|
+
// "Verify complete hidden release"), and every url/sha512 listed in
|
|
7
|
+
// latest.yml and latest-mac.yml matches the uploaded assets
|
|
8
|
+
// - mixdog@<version> is resolvable on npm (retried while it propagates)
|
|
9
|
+
// - production relay /app answers 200 and links an existing, published
|
|
10
|
+
// mobile-v* release
|
|
11
|
+
// - the effort-judge manifest release exists, is published, and is not Latest
|
|
12
|
+
//
|
|
13
|
+
// Env: GITHUB_REPOSITORY, RELEASE_TAG (vX.Y.Z), RELAY_DOMAIN, GH_TOKEN.
|
|
14
|
+
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
16
|
+
import { appendFileSync, readFileSync } from 'node:fs';
|
|
17
|
+
import { pathToFileURL } from 'node:url';
|
|
18
|
+
import { parseMacFeed } from './merge-mac-update-feed.mjs';
|
|
19
|
+
import { APP_TAG_RE, currentLatestTag, gh, isHttp404 } from './release-guard.mjs';
|
|
20
|
+
|
|
21
|
+
const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
|
+
|
|
23
|
+
// Keep in step with the per-platform `requirements` in
|
|
24
|
+
// .github/workflows/release.yml (a unit test compares both). The Linux update
|
|
25
|
+
// feeds (latest-linux*.yml) are not parsed here, only required to exist.
|
|
26
|
+
export const ASSET_CONTRACT = Object.freeze([
|
|
27
|
+
'latest-linux-arm64.yml',
|
|
28
|
+
'latest-linux.yml',
|
|
29
|
+
'latest-mac.yml',
|
|
30
|
+
'latest.yml',
|
|
31
|
+
'mixdog-desktop-linux-amd64.deb',
|
|
32
|
+
'mixdog-desktop-linux-arm64.AppImage',
|
|
33
|
+
'mixdog-desktop-linux-arm64.deb',
|
|
34
|
+
'mixdog-desktop-linux-x86_64.AppImage',
|
|
35
|
+
'mixdog-desktop-mac-arm64.dmg',
|
|
36
|
+
'mixdog-desktop-mac-arm64.zip',
|
|
37
|
+
'mixdog-desktop-mac-arm64.zip.blockmap',
|
|
38
|
+
'mixdog-desktop-mac-x64.dmg',
|
|
39
|
+
'mixdog-desktop-mac-x64.zip',
|
|
40
|
+
'mixdog-desktop-mac-x64.zip.blockmap',
|
|
41
|
+
'mixdog-desktop-win-x64.exe',
|
|
42
|
+
'mixdog-desktop-win-x64.exe.blockmap',
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const MAC_ZIPS = ['mixdog-desktop-mac-arm64.zip', 'mixdog-desktop-mac-x64.zip'];
|
|
46
|
+
|
|
47
|
+
export function checkLatest(tag, latestTag) {
|
|
48
|
+
return latestTag === tag ? [] : [`GitHub Latest is ${latestTag ?? 'none'}, expected ${tag}`];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function checkAssetSet(assets) {
|
|
52
|
+
const problems = [];
|
|
53
|
+
const names = assets.map((asset) => asset.name);
|
|
54
|
+
for (const name of ASSET_CONTRACT) {
|
|
55
|
+
if (!names.includes(name)) problems.push(`missing release asset ${name}`);
|
|
56
|
+
}
|
|
57
|
+
for (const name of names) {
|
|
58
|
+
if (!ASSET_CONTRACT.includes(name)) problems.push(`unexpected release asset ${name}`);
|
|
59
|
+
}
|
|
60
|
+
if (new Set(names).size !== names.length) problems.push('duplicate release asset names');
|
|
61
|
+
for (const asset of assets) {
|
|
62
|
+
if (asset.state !== 'uploaded' || !(asset.size > 0)) problems.push(`incomplete release asset ${asset.name}`);
|
|
63
|
+
}
|
|
64
|
+
return problems;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check one electron-builder feed against the uploaded assets: version, that
|
|
69
|
+
* every listed file is an uploaded asset of the listed size, and that its
|
|
70
|
+
* sha512 is the hash of the uploaded bytes. `hashAsset(asset)` returns the
|
|
71
|
+
* base64 sha512 of an asset's content.
|
|
72
|
+
*/
|
|
73
|
+
export async function checkFeed({ text, label, version, assets, hashAsset, requiredFiles = [] }) {
|
|
74
|
+
let feed;
|
|
75
|
+
try {
|
|
76
|
+
feed = parseMacFeed(text, label);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return [error.message];
|
|
79
|
+
}
|
|
80
|
+
const problems = [];
|
|
81
|
+
if (feed.version !== version) {
|
|
82
|
+
problems.push(`${label}: version ${feed.version}, expected ${version}`);
|
|
83
|
+
}
|
|
84
|
+
const field = (file, key) =>
|
|
85
|
+
file.lines.map((line) => new RegExp(`^ +(?:- )?${key}: (\\S+)$`).exec(line)?.[1]).find(Boolean);
|
|
86
|
+
const byUrl = new Map(feed.files.map((file) => [file.url, file]));
|
|
87
|
+
for (const name of requiredFiles) {
|
|
88
|
+
if (!byUrl.has(name)) problems.push(`${label}: files[] does not list ${name}`);
|
|
89
|
+
}
|
|
90
|
+
const hashes = new Map();
|
|
91
|
+
for (const file of feed.files) {
|
|
92
|
+
const asset = assets.find((candidate) => candidate.name === file.url);
|
|
93
|
+
if (!asset) {
|
|
94
|
+
problems.push(`${label}: ${file.url} is not an uploaded asset`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const size = field(file, 'size');
|
|
98
|
+
if (size === undefined || Number(size) !== asset.size) {
|
|
99
|
+
problems.push(`${label}: ${file.url} size ${size} != uploaded ${asset.size}`);
|
|
100
|
+
}
|
|
101
|
+
const sha512 = field(file, 'sha512');
|
|
102
|
+
const actual = await hashAsset(asset);
|
|
103
|
+
hashes.set(file.url, actual);
|
|
104
|
+
if (sha512 !== actual) problems.push(`${label}: ${file.url} sha512 does not match the uploaded asset`);
|
|
105
|
+
}
|
|
106
|
+
// Top-level path/sha512 is what pre-files[] clients read; it must agree too.
|
|
107
|
+
const topPath = /^path: (\S+)$/.exec(feed.top.path)?.[1];
|
|
108
|
+
const topSha = /^sha512: (\S+)$/.exec(feed.top.sha512)?.[1];
|
|
109
|
+
if (!byUrl.has(topPath)) problems.push(`${label}: path ${topPath} is not listed in files[]`);
|
|
110
|
+
else if (hashes.has(topPath) && hashes.get(topPath) !== topSha) {
|
|
111
|
+
problems.push(`${label}: top-level sha512 does not match ${topPath}`);
|
|
112
|
+
}
|
|
113
|
+
return problems;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function waitForNpm({
|
|
117
|
+
name,
|
|
118
|
+
version,
|
|
119
|
+
fetchFn = fetch,
|
|
120
|
+
sleep = defaultSleep,
|
|
121
|
+
budgetMs = 10 * 60 * 1000,
|
|
122
|
+
firstDelayMs = 5000,
|
|
123
|
+
maxDelayMs = 60_000,
|
|
124
|
+
}) {
|
|
125
|
+
let waited = 0;
|
|
126
|
+
let delay = firstDelayMs;
|
|
127
|
+
let last = 'not queried';
|
|
128
|
+
for (;;) {
|
|
129
|
+
try {
|
|
130
|
+
const response = await fetchFn(`https://registry.npmjs.org/${name}/${version}`);
|
|
131
|
+
if (response.ok && (await response.json()).version === version) return [];
|
|
132
|
+
last = `HTTP ${response.status}`;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
last = error.message;
|
|
135
|
+
}
|
|
136
|
+
if (waited >= budgetMs)
|
|
137
|
+
return [`${name}@${version} not resolvable on npm after ${Math.round(waited / 1000)}s (${last})`];
|
|
138
|
+
const step = Math.min(delay, budgetMs - waited);
|
|
139
|
+
await sleep(step);
|
|
140
|
+
waited += step;
|
|
141
|
+
delay = Math.min(delay * 2, maxDelayMs);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const ANDROID_LINK_RE =
|
|
146
|
+
/https:\/\/github\.com\/[^"'\s<>]+\/releases\/(?:tag|download)\/(mobile-v\d+\.\d+\.\d+)[^"'\s<>]*/;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* `getRelease(tag)` returns the release object or null when it does not exist.
|
|
150
|
+
* `mobileVersion` is the Android version this deploy tried to ship (empty for
|
|
151
|
+
* none). When that release failed, /app keeps linking the previous good
|
|
152
|
+
* mobile-v* release (or the releases list); that is a warning, not an error.
|
|
153
|
+
* A link to a missing or draft release stays an error.
|
|
154
|
+
* Returns { problems, warnings }.
|
|
155
|
+
*/
|
|
156
|
+
export async function checkRelayApp({ domain, fetchFn = fetch, getRelease, mobileVersion = '' }) {
|
|
157
|
+
const page = await fetchFn(`https://${domain}/app`);
|
|
158
|
+
if (page.status !== 200)
|
|
159
|
+
return { problems: [`https://${domain}/app answered ${page.status}, expected 200`], warnings: [] };
|
|
160
|
+
const match = ANDROID_LINK_RE.exec(await page.text());
|
|
161
|
+
const wanted = mobileVersion ? `mobile-v${mobileVersion}` : '';
|
|
162
|
+
if (!match) {
|
|
163
|
+
return wanted
|
|
164
|
+
? {
|
|
165
|
+
problems: [],
|
|
166
|
+
warnings: [`/app links no mobile-v* release; ${wanted} was not shipped (previous good release kept)`],
|
|
167
|
+
}
|
|
168
|
+
: { problems: [`https://${domain}/app has no link to a mobile-v* release`], warnings: [] };
|
|
169
|
+
}
|
|
170
|
+
const [url, tag] = match;
|
|
171
|
+
const release = getRelease(tag);
|
|
172
|
+
if (!release) return { problems: [`/app links ${tag}, which is not an existing release`], warnings: [] };
|
|
173
|
+
if (release.draft) return { problems: [`/app links ${tag}, which is still a draft`], warnings: [] };
|
|
174
|
+
const resolved = await fetchFn(url, { redirect: 'follow' });
|
|
175
|
+
if (!resolved.ok) return { problems: [`/app Android link ${url} answered ${resolved.status}`], warnings: [] };
|
|
176
|
+
const warnings =
|
|
177
|
+
wanted && tag !== wanted
|
|
178
|
+
? [
|
|
179
|
+
`/app links ${tag} instead of ${wanted}; the Android release failed or is not yet published (previous good release kept)`,
|
|
180
|
+
]
|
|
181
|
+
: [];
|
|
182
|
+
return { problems: [], warnings };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function checkEffortJudge({ manifest, latestTag, getRelease }) {
|
|
186
|
+
const tag = manifest.release;
|
|
187
|
+
const release = getRelease(tag);
|
|
188
|
+
if (!release) return [`effort-judge release ${tag} does not exist`];
|
|
189
|
+
const problems = [];
|
|
190
|
+
if (release.draft) problems.push(`effort-judge release ${tag} is a draft`);
|
|
191
|
+
if (latestTag === tag) problems.push(`effort-judge release ${tag} is GitHub Latest`);
|
|
192
|
+
for (const [name, file] of Object.entries(manifest.files)) {
|
|
193
|
+
const asset = (release.assets ?? []).find((candidate) => candidate.name === name);
|
|
194
|
+
if (!asset) problems.push(`effort-judge release ${tag} lacks ${name}`);
|
|
195
|
+
else if (asset.size !== file.size)
|
|
196
|
+
problems.push(`effort-judge ${name} size ${asset.size} != manifest ${file.size}`);
|
|
197
|
+
}
|
|
198
|
+
return problems;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** The release for `tag`, or null only on an explicit HTTP 404. Any other failure throws. */
|
|
202
|
+
export function getReleaseByTag(repo, tag, run = gh) {
|
|
203
|
+
try {
|
|
204
|
+
return JSON.parse(run(['api', `repos/${repo}/releases/tags/${tag}`]));
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (isHttp404(error)) return null;
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Was the app release published? A missing release (`fetchRelease` returns
|
|
213
|
+
* null) or a draft means "not published" and the checks are skipped. Any
|
|
214
|
+
* other failure (auth, transport, 5xx) is retried with backoff and then
|
|
215
|
+
* rethrown, so an API outage fails the job instead of silently skipping
|
|
216
|
+
* verification of a release that may be public.
|
|
217
|
+
*/
|
|
218
|
+
export async function detectPublished({
|
|
219
|
+
fetchRelease,
|
|
220
|
+
sleep = defaultSleep,
|
|
221
|
+
attempts = 5,
|
|
222
|
+
firstDelayMs = 2000,
|
|
223
|
+
}) {
|
|
224
|
+
let delay = firstDelayMs;
|
|
225
|
+
for (let attempt = 1; ; attempt++) {
|
|
226
|
+
try {
|
|
227
|
+
const release = fetchRelease();
|
|
228
|
+
return Boolean(release) && release.draft === false;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (attempt >= attempts) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
`could not determine whether the release is published after ${attempts} attempts: ${error.message}`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
await sleep(delay);
|
|
236
|
+
delay *= 2;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function sha512Base64(url) {
|
|
242
|
+
const response = await fetch(url, { redirect: 'follow' });
|
|
243
|
+
if (!response.ok) throw new Error(`download ${url} answered ${response.status}`);
|
|
244
|
+
const hash = createHash('sha512');
|
|
245
|
+
for await (const chunk of response.body) hash.update(chunk);
|
|
246
|
+
return hash.digest('base64');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function main() {
|
|
250
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
251
|
+
const tag = process.env.RELEASE_TAG;
|
|
252
|
+
const domain = process.env.RELAY_DOMAIN;
|
|
253
|
+
if (!repo || !APP_TAG_RE.test(tag ?? '') || !domain) {
|
|
254
|
+
throw new Error('GITHUB_REPOSITORY, RELEASE_TAG (vX.Y.Z) and RELAY_DOMAIN are required');
|
|
255
|
+
}
|
|
256
|
+
const version = tag.slice(1);
|
|
257
|
+
const getRelease = (releaseTag) => getReleaseByTag(repo, releaseTag);
|
|
258
|
+
const release = getRelease(tag);
|
|
259
|
+
const assets = release?.assets ?? [];
|
|
260
|
+
const latestTag = currentLatestTag(repo);
|
|
261
|
+
const hashed = new Map();
|
|
262
|
+
const hashAsset = (asset) => {
|
|
263
|
+
if (!hashed.has(asset.name)) hashed.set(asset.name, sha512Base64(asset.browser_download_url));
|
|
264
|
+
return hashed.get(asset.name);
|
|
265
|
+
};
|
|
266
|
+
const feedText = async (name) => {
|
|
267
|
+
const asset = assets.find((candidate) => candidate.name === name);
|
|
268
|
+
if (!asset) return null;
|
|
269
|
+
const response = await fetch(asset.browser_download_url, { redirect: 'follow' });
|
|
270
|
+
return response.ok ? response.text() : null;
|
|
271
|
+
};
|
|
272
|
+
const feed = async (name, requiredFiles) => {
|
|
273
|
+
const text = await feedText(name);
|
|
274
|
+
if (text === null) return [`${name} could not be downloaded from ${tag}`];
|
|
275
|
+
return checkFeed({ text, label: name, version, assets, hashAsset, requiredFiles });
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const sections = [
|
|
279
|
+
[
|
|
280
|
+
'GitHub Latest',
|
|
281
|
+
async () => (release && !release.draft ? checkLatest(tag, latestTag) : [`${tag} is not a published release`]),
|
|
282
|
+
],
|
|
283
|
+
['Release assets', async () => checkAssetSet(assets)],
|
|
284
|
+
['latest.yml', () => feed('latest.yml', ['mixdog-desktop-win-x64.exe'])],
|
|
285
|
+
['latest-mac.yml', () => feed('latest-mac.yml', MAC_ZIPS)],
|
|
286
|
+
['Relay /app', () => checkRelayApp({ domain, getRelease, mobileVersion: process.env.MOBILE_VERSION ?? '' })],
|
|
287
|
+
[
|
|
288
|
+
'effort-judge release',
|
|
289
|
+
async () =>
|
|
290
|
+
checkEffortJudge({
|
|
291
|
+
manifest: JSON.parse(readFileSync('src/runtime/effort-judge/model-manifest.json', 'utf8')),
|
|
292
|
+
latestTag,
|
|
293
|
+
getRelease,
|
|
294
|
+
}),
|
|
295
|
+
],
|
|
296
|
+
['npm', () => waitForNpm({ name: 'mixdog', version })],
|
|
297
|
+
];
|
|
298
|
+
const results = [];
|
|
299
|
+
for (const [name, run] of sections) {
|
|
300
|
+
try {
|
|
301
|
+
const outcome = await run();
|
|
302
|
+
results.push(
|
|
303
|
+
Array.isArray(outcome)
|
|
304
|
+
? { name, problems: outcome, warnings: [] }
|
|
305
|
+
: { name, problems: outcome.problems, warnings: outcome.warnings }
|
|
306
|
+
);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
results.push({ name, problems: [`check failed: ${error.message}`], warnings: [] });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const lines = [`## Post-release check for ${tag}`, ''];
|
|
313
|
+
for (const { name, problems, warnings } of results) {
|
|
314
|
+
let status = 'ok';
|
|
315
|
+
if (problems.length) status = 'FAIL';
|
|
316
|
+
else if (warnings.length) status = 'WARN';
|
|
317
|
+
lines.push(`- ${status} ${name}`);
|
|
318
|
+
for (const problem of problems) lines.push(` - ${problem}`);
|
|
319
|
+
for (const warning of warnings) {
|
|
320
|
+
lines.push(` - warning: ${warning}`);
|
|
321
|
+
console.log(`::warning title=Post-release ${name}::${warning}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const text = `${lines.join('\n')}\n`;
|
|
325
|
+
console.log(text);
|
|
326
|
+
if (process.env.GITHUB_STEP_SUMMARY) appendFileSync(process.env.GITHUB_STEP_SUMMARY, text);
|
|
327
|
+
return results.some(({ problems }) => problems.length) ? 1 : 0;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// `detect-published`: write published=true|false to GITHUB_OUTPUT. Exits 1
|
|
331
|
+
// (job fails) when the GitHub API cannot give a definite answer.
|
|
332
|
+
async function detectPublishedMain() {
|
|
333
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
334
|
+
const tag = process.env.RELEASE_TAG;
|
|
335
|
+
if (!repo || !APP_TAG_RE.test(tag ?? '')) {
|
|
336
|
+
throw new Error('GITHUB_REPOSITORY and RELEASE_TAG (vX.Y.Z) are required');
|
|
337
|
+
}
|
|
338
|
+
const published = await detectPublished({ fetchRelease: () => getReleaseByTag(repo, tag) });
|
|
339
|
+
if (process.env.GITHUB_OUTPUT) appendFileSync(process.env.GITHUB_OUTPUT, `published=${published}\n`);
|
|
340
|
+
const result = process.env.RELEASE_RESULT ?? 'unknown';
|
|
341
|
+
if (published) console.log(`${tag} is published (release job: ${result}); running the post-release checks.`);
|
|
342
|
+
else console.log(`::notice title=Post-release checks skipped::${tag} was not published (release job: ${result}).`);
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
|
|
347
|
+
try {
|
|
348
|
+
process.exit(process.argv[2] === 'detect-published' ? await detectPublishedMain() : await main());
|
|
349
|
+
} catch (error) {
|
|
350
|
+
console.log(`::error title=Post-release check::${error.message}`);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { access, readdir, rm } from 'node:fs/promises';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
import { onnxRuntimeSupported } from '../src/runtime/shared/onnx-runtime-support.mjs';
|
|
6
7
|
|
|
7
8
|
const SUPPORTED_TARGETS = new Map([
|
|
8
9
|
['win32', new Set(['x64', 'arm64'])],
|
|
@@ -83,8 +84,9 @@ export async function pruneEmbeddingRuntime(packageRoot, options = {}) {
|
|
|
83
84
|
).find(Boolean);
|
|
84
85
|
if (!ortRoot) throw new Error('Embedding runtime is incomplete: onnxruntime-node is unavailable');
|
|
85
86
|
|
|
86
|
-
const
|
|
87
|
-
|
|
87
|
+
const onnx = onnxRuntimeSupported(target.platform, target.arch);
|
|
88
|
+
const targetBinaryDir = onnx ? join(ortRoot, 'bin', 'napi-v6', target.platform, target.arch) : null;
|
|
89
|
+
if (onnx && !(await exists(join(targetBinaryDir, 'onnxruntime_binding.node')))) {
|
|
88
90
|
throw new Error(`Embedding runtime is incomplete: missing ${target.key} ONNX binding`);
|
|
89
91
|
}
|
|
90
92
|
|
|
@@ -100,10 +102,11 @@ export async function pruneEmbeddingRuntime(packageRoot, options = {}) {
|
|
|
100
102
|
// bin/napi-v6/${process.platform}/${process.arch}/onnxruntime_binding.node.
|
|
101
103
|
// Keep one native payload and remove every foreign OS/architecture.
|
|
102
104
|
const napiRoot = join(ortRoot, 'bin', 'napi-v6');
|
|
103
|
-
|
|
104
|
-
await removeChildrenExcept(
|
|
105
|
+
// Targets without an ONNX binding keep no native payload at all.
|
|
106
|
+
await removeChildrenExcept(napiRoot, new Set(onnx ? [target.platform] : []));
|
|
107
|
+
if (onnx) await removeChildrenExcept(join(napiRoot, target.platform), new Set([target.arch]));
|
|
105
108
|
const removedTargetFiles = [];
|
|
106
|
-
if (target.platform === 'win32') {
|
|
109
|
+
if (onnx && target.platform === 'win32') {
|
|
107
110
|
// The fixed production embedding profile uses CPU inference. Keep
|
|
108
111
|
// DirectML.dll for explicit device overrides, but remove the optional DXC
|
|
109
112
|
// shader compiler payload verified unnecessary by packaged-runtime warmup.
|
|
@@ -132,6 +135,7 @@ export async function pruneEmbeddingRuntime(packageRoot, options = {}) {
|
|
|
132
135
|
...target,
|
|
133
136
|
transformerRoot,
|
|
134
137
|
ortRoot,
|
|
138
|
+
onnx,
|
|
135
139
|
targetBinaryDir,
|
|
136
140
|
removedTargetFiles,
|
|
137
141
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Create or publish a NON-app release (effort-judge, voice, graph, spawn,
|
|
3
|
+
// mobile, ...) without ever moving GitHub's "Latest" pointer, which belongs to
|
|
4
|
+
// the newest app release (vX.Y.Z) alone.
|
|
5
|
+
//
|
|
6
|
+
// publish-asset-release.mjs create <tag> [gh release create options/files...]
|
|
7
|
+
// publish-asset-release.mjs publish <tag> # draft -> published
|
|
8
|
+
//
|
|
9
|
+
// Always passes --latest=false, and fails if Latest changed across the call.
|
|
10
|
+
|
|
11
|
+
import { pathToFileURL } from 'node:url';
|
|
12
|
+
import { APP_TAG_RE, currentLatestTag, gh } from './release-guard.mjs';
|
|
13
|
+
|
|
14
|
+
export function buildArgs(action, tag, extra = [], repo) {
|
|
15
|
+
if (APP_TAG_RE.test(tag)) throw new Error(`${tag} is an app release tag; asset releases must not use it`);
|
|
16
|
+
if (extra.some((arg) => /^--latest(=|$)/.test(arg))) {
|
|
17
|
+
throw new Error('--latest is set by this script; do not pass it');
|
|
18
|
+
}
|
|
19
|
+
const scope = repo ? ['--repo', repo] : [];
|
|
20
|
+
if (action === 'create') return ['release', 'create', tag, ...extra, ...scope, '--latest=false'];
|
|
21
|
+
if (action === 'publish') return ['release', 'edit', tag, ...scope, '--draft=false', '--latest=false'];
|
|
22
|
+
throw new Error(`unknown action ${action} (expected create|publish)`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function publishAssetRelease({ action, tag, extra = [], repo, run = gh }) {
|
|
26
|
+
const args = buildArgs(action, tag, extra, repo);
|
|
27
|
+
const before = currentLatestTag(repo, run);
|
|
28
|
+
run(args);
|
|
29
|
+
const after = currentLatestTag(repo, run);
|
|
30
|
+
if (after !== before) {
|
|
31
|
+
throw new Error(`Latest moved from ${before ?? 'none'} to ${after ?? 'none'} while ${action} ${tag}`);
|
|
32
|
+
}
|
|
33
|
+
return before;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
|
|
37
|
+
const [action, tag, ...extra] = process.argv.slice(2);
|
|
38
|
+
if (!action || !tag) {
|
|
39
|
+
throw new Error('usage: publish-asset-release.mjs <create|publish> <tag> [gh release create args...]');
|
|
40
|
+
}
|
|
41
|
+
const latest = publishAssetRelease({ action, tag, extra, repo: process.env.GITHUB_REPOSITORY });
|
|
42
|
+
console.log(`${action} ${tag}: Latest unchanged (${latest ?? 'none'}).`);
|
|
43
|
+
}
|