create-walle 0.9.21 → 0.9.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -5
- package/package.json +2 -2
- package/template/CLAUDE.md +2 -2
- package/template/LICENSE +1 -1
- package/template/bin/ctm-dev-cleanup.js +24 -3
- package/template/bin/ctm-launch.sh +13 -0
- package/template/bin/dev.sh +156 -18
- package/template/bin/node-bin.sh +84 -0
- package/template/bin/pin-node.sh +51 -0
- package/template/claude-task-manager/api-prompts.js +1203 -182
- package/template/claude-task-manager/api-reviews.js +109 -15
- package/template/claude-task-manager/approval-agent.js +1360 -280
- package/template/claude-task-manager/bin/restart-ctm.sh +64 -23
- package/template/claude-task-manager/bin/storage-migration-supervisor.js +338 -0
- package/template/claude-task-manager/db.js +4417 -295
- package/template/claude-task-manager/docs/app-update-refresh-protocol.md +69 -0
- package/template/claude-task-manager/docs/approval-ai-refinement.md +138 -0
- package/template/claude-task-manager/docs/approval-rescue-loop.md +74 -0
- package/template/claude-task-manager/docs/codex-operational-warning-health.md +107 -0
- package/template/claude-task-manager/docs/codex-resume-state-guard-design.md +17 -12
- package/template/claude-task-manager/docs/codex-terminal-render-controller-handoff.md +311 -0
- package/template/claude-task-manager/docs/coding-agent-hooks-architecture.md +418 -0
- package/template/claude-task-manager/docs/conversation-import-freshness.md +20 -0
- package/template/claude-task-manager/docs/google-workspace-auth-health.md +77 -0
- package/template/claude-task-manager/docs/image-paste-ux.md +13 -0
- package/template/claude-task-manager/docs/ipad-web-preview.md +88 -0
- package/template/claude-task-manager/docs/main-loop-offload-architecture.md +66 -0
- package/template/claude-task-manager/docs/microsoft-dev-tunnel-phone-access-design.md +274 -519
- package/template/claude-task-manager/docs/mobile-live-streaming.md +27 -5
- package/template/claude-task-manager/docs/mobile-remote-submission-lifecycle.md +69 -0
- package/template/claude-task-manager/docs/phone-access-design.md +53 -15
- package/template/claude-task-manager/docs/phone-passkey-identity.md +122 -0
- package/template/claude-task-manager/docs/phone-setup.md +3 -0
- package/template/claude-task-manager/docs/prompt-editing-tree-design.md +25 -1
- package/template/claude-task-manager/docs/remote-desktop-access-design.md +268 -0
- package/template/claude-task-manager/docs/restart-lifecycle-architecture.md +95 -0
- package/template/claude-task-manager/docs/runtime-work-control-plane.md +53 -0
- package/template/claude-task-manager/docs/session-interactive-wait-surfaces.md +38 -0
- package/template/claude-task-manager/docs/session-needs-you-dismissal.md +84 -0
- package/template/claude-task-manager/docs/session-render-state-management-design.md +91 -3
- package/template/claude-task-manager/docs/session-standup-command-center-design.md +25 -1
- package/template/claude-task-manager/docs/session-title-authority.md +32 -0
- package/template/claude-task-manager/docs/session-workspace-binding.md +33 -0
- package/template/claude-task-manager/docs/skill-intent-resolution-design.md +72 -0
- package/template/claude-task-manager/docs/walle-mcp-supervisor-health.md +86 -0
- package/template/claude-task-manager/docs/walle-relay-phone-access-design.md +24 -15
- package/template/claude-task-manager/docs/walle-session-history-hydration.md +114 -0
- package/template/claude-task-manager/docs/walle-session-input-queue.md +104 -0
- package/template/claude-task-manager/docs/walle-session-model-catalog.md +90 -0
- package/template/claude-task-manager/docs/walle-session-model-preferences.md +15 -6
- package/template/claude-task-manager/git-utils.js +897 -27
- package/template/claude-task-manager/lib/agent-capabilities.js +33 -0
- package/template/claude-task-manager/lib/agent-cli-cache.js +37 -7
- package/template/claude-task-manager/lib/agent-hooks-installer.js +26 -2
- package/template/claude-task-manager/lib/agent-presets.js +17 -1
- package/template/claude-task-manager/lib/all-sessions-query.js +108 -0
- package/template/claude-task-manager/lib/approval-ai-refinement.js +488 -0
- package/template/claude-task-manager/lib/approval-self-adapt.js +168 -0
- package/template/claude-task-manager/lib/async-semaphore.js +44 -0
- package/template/claude-task-manager/lib/auth-context.js +5 -0
- package/template/claude-task-manager/lib/auth-rate-limit.js +47 -4
- package/template/claude-task-manager/lib/auth-rules.js +29 -2
- package/template/claude-task-manager/lib/auto-approval-verifier.js +129 -16
- package/template/claude-task-manager/lib/background-llm.js +144 -17
- package/template/claude-task-manager/lib/branch-inventory.js +212 -0
- package/template/claude-task-manager/lib/claude-desktop-sessions.js +15 -3
- package/template/claude-task-manager/lib/coalesce-sync-frames.js +151 -0
- package/template/claude-task-manager/lib/codex-launch-health.js +762 -0
- package/template/claude-task-manager/lib/codex-transcript-pager.js +51 -0
- package/template/claude-task-manager/lib/codex-zst.js +124 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +233 -30
- package/template/claude-task-manager/lib/connection-health.js +232 -0
- package/template/claude-task-manager/lib/conversation-blob-parser.js +42 -0
- package/template/claude-task-manager/lib/conversation-tail-merge.js +89 -26
- package/template/claude-task-manager/lib/ctm-session-context-api.js +39 -10
- package/template/claude-task-manager/lib/cursor-conversation-store.js +354 -0
- package/template/claude-task-manager/lib/db-owner-worker-client.js +315 -0
- package/template/claude-task-manager/lib/document-review.js +141 -6
- package/template/claude-task-manager/lib/escalation-review.js +152 -0
- package/template/claude-task-manager/lib/graceful-shutdown.js +159 -0
- package/template/claude-task-manager/lib/headless-term-service.js +678 -0
- package/template/claude-task-manager/lib/heavy-worker-fallback.js +38 -0
- package/template/claude-task-manager/lib/jsonl-conversation-parser.js +542 -0
- package/template/claude-task-manager/lib/jsonl-range-reader.js +112 -0
- package/template/claude-task-manager/lib/main-db-census.js +216 -0
- package/template/claude-task-manager/lib/message-pagination.js +106 -4
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +750 -26
- package/template/claude-task-manager/lib/mobile-auth-api.js +274 -7
- package/template/claude-task-manager/lib/mobile-auth-store.js +592 -10
- package/template/claude-task-manager/lib/mobile-notification-dispatcher.js +15 -0
- package/template/claude-task-manager/lib/model-overview-brain-fallback.js +311 -0
- package/template/claude-task-manager/lib/model-overview-cache.js +141 -0
- package/template/claude-task-manager/lib/models-health-routing-notice.js +126 -0
- package/template/claude-task-manager/lib/node-pin-guard.js +93 -0
- package/template/claude-task-manager/lib/perf-tracker.js +242 -6
- package/template/claude-task-manager/lib/permission-match.js +76 -0
- package/template/claude-task-manager/lib/permission-sync.js +133 -20
- package/template/claude-task-manager/lib/process-title.js +35 -0
- package/template/claude-task-manager/lib/prompt-executions-query.js +25 -0
- package/template/claude-task-manager/lib/prompt-index-disk-cache.js +44 -0
- package/template/claude-task-manager/lib/prompt-intent.js +132 -0
- package/template/claude-task-manager/lib/provider-user-context.js +34 -0
- package/template/claude-task-manager/lib/read-pool-client.js +313 -0
- package/template/claude-task-manager/lib/readpool-breaker.js +31 -0
- package/template/claude-task-manager/lib/recent-sessions-breaker.js +12 -0
- package/template/claude-task-manager/lib/remote-feedback-client.js +72 -0
- package/template/claude-task-manager/lib/remote-relay-protocol.js +37 -4
- package/template/claude-task-manager/lib/remote-relay-store.js +159 -0
- package/template/claude-task-manager/lib/remote-submission-observer.js +278 -0
- package/template/claude-task-manager/lib/restart-guard.js +109 -0
- package/template/claude-task-manager/lib/restore-interruption-detector.js +439 -0
- package/template/claude-task-manager/lib/restore-policy.js +13 -0
- package/template/claude-task-manager/lib/restore-resume-batch.js +74 -0
- package/template/claude-task-manager/lib/restore-runtime.js +68 -0
- package/template/claude-task-manager/lib/restore-storm.js +34 -0
- package/template/claude-task-manager/lib/resume-cwd.js +36 -0
- package/template/claude-task-manager/lib/resume-preflight.js +313 -0
- package/template/claude-task-manager/lib/runtime-work-registry.js +444 -0
- package/template/claude-task-manager/lib/sanitize-openai-auth.js +31 -0
- package/template/claude-task-manager/lib/scheduler.js +21 -1
- package/template/claude-task-manager/lib/scrollback-snapshot-store.js +159 -0
- package/template/claude-task-manager/lib/serial-task-queue.js +64 -0
- package/template/claude-task-manager/lib/server-listeners.js +239 -0
- package/template/claude-task-manager/lib/session-capture.js +42 -7
- package/template/claude-task-manager/lib/session-content-backfill.js +131 -0
- package/template/claude-task-manager/lib/session-history.js +388 -43
- package/template/claude-task-manager/lib/session-host-manager.js +287 -0
- package/template/claude-task-manager/lib/session-image-refs.js +209 -0
- package/template/claude-task-manager/lib/session-jobs.js +399 -59
- package/template/claude-task-manager/lib/session-prompt-index.js +137 -0
- package/template/claude-task-manager/lib/session-restore.js +53 -0
- package/template/claude-task-manager/lib/session-standup.js +123 -23
- package/template/claude-task-manager/lib/session-state-bus.js +14 -0
- package/template/claude-task-manager/lib/session-stream.js +64 -16
- package/template/claude-task-manager/lib/session-timeline-summary.js +260 -0
- package/template/claude-task-manager/lib/session-token-usage.js +494 -0
- package/template/claude-task-manager/lib/session-workspace-binding.js +356 -0
- package/template/claude-task-manager/lib/setup-network-config.js +9 -0
- package/template/claude-task-manager/lib/size-cap.js +45 -0
- package/template/claude-task-manager/lib/size-cap.test.js +62 -0
- package/template/claude-task-manager/lib/skill-autocomplete.js +180 -1
- package/template/claude-task-manager/lib/skill-intent-resolver.js +304 -0
- package/template/claude-task-manager/lib/sqlite-driver.js +19 -3
- package/template/claude-task-manager/lib/standup-attention.js +7 -3
- package/template/claude-task-manager/lib/status-authority.js +39 -0
- package/template/claude-task-manager/lib/status-hooks.js +4 -0
- package/template/claude-task-manager/lib/storage-migration.js +235 -0
- package/template/claude-task-manager/lib/structured-capture.js +298 -0
- package/template/claude-task-manager/lib/sync-io-census.js +163 -0
- package/template/claude-task-manager/lib/tailscale-setup.js +6 -0
- package/template/claude-task-manager/lib/terminal-activity-evidence.js +33 -0
- package/template/claude-task-manager/lib/terminal-choice.js +364 -0
- package/template/claude-task-manager/lib/terminal-control-sanitize.js +17 -0
- package/template/claude-task-manager/lib/terminal-fingerprint.js +48 -0
- package/template/claude-task-manager/lib/terminal-output-flush.js +84 -0
- package/template/claude-task-manager/lib/timeline-order.js +122 -0
- package/template/claude-task-manager/lib/transcript-store.js +348 -43
- package/template/claude-task-manager/lib/transport-security.js +84 -1
- package/template/claude-task-manager/lib/wait-state.js +184 -0
- package/template/claude-task-manager/lib/walle-client.js +47 -5
- package/template/claude-task-manager/lib/walle-ctm-history.js +564 -4
- package/template/claude-task-manager/lib/walle-external-actions.js +135 -16
- package/template/claude-task-manager/lib/walle-history-hydration.js +46 -0
- package/template/claude-task-manager/lib/walle-native-health.js +403 -0
- package/template/claude-task-manager/lib/walle-repair.js +701 -0
- package/template/claude-task-manager/lib/walle-session-cache.js +109 -0
- package/template/claude-task-manager/lib/walle-session-context.js +57 -21
- package/template/claude-task-manager/lib/walle-session-model-catalog.js +34 -0
- package/template/claude-task-manager/lib/walle-supervisor.js +539 -63
- package/template/claude-task-manager/lib/walle-transcript.js +52 -0
- package/template/claude-task-manager/lib/worktree-active-sync.js +11 -7
- package/template/claude-task-manager/lib/worktree-cwd.js +32 -1
- package/template/claude-task-manager/package.json +1 -1
- package/template/claude-task-manager/prompt-harvest.js +89 -66
- package/template/claude-task-manager/providers/claude-code.js +51 -3
- package/template/claude-task-manager/providers/cursor.js +140 -45
- package/template/claude-task-manager/public/css/reviews.css +551 -61
- package/template/claude-task-manager/public/css/setup.css +191 -0
- package/template/claude-task-manager/public/css/walle-session.css +865 -10
- package/template/claude-task-manager/public/css/walle.css +154 -0
- package/template/claude-task-manager/public/designs/ai-providers-consolidation-v2.html +830 -0
- package/template/claude-task-manager/public/index.html +18516 -2058
- package/template/claude-task-manager/public/ipad.html +363 -0
- package/template/claude-task-manager/public/js/document-review-links.js +301 -0
- package/template/claude-task-manager/public/js/image-normalize.js +69 -36
- package/template/claude-task-manager/public/js/message-renderer.js +1265 -77
- package/template/claude-task-manager/public/js/prompts.js +66 -29
- package/template/claude-task-manager/public/js/reviews.js +901 -133
- package/template/claude-task-manager/public/js/session-activity-utils.js +11 -1
- package/template/claude-task-manager/public/js/session-search-utils.js +94 -10
- package/template/claude-task-manager/public/js/session-status-precedence.js +23 -5
- package/template/claude-task-manager/public/js/setup.js +1273 -176
- package/template/claude-task-manager/public/js/stream-view.js +691 -73
- package/template/claude-task-manager/public/js/terminal-reconciler.js +210 -0
- package/template/claude-task-manager/public/js/walle-session.js +2455 -158
- package/template/claude-task-manager/public/js/walle.js +455 -28
- package/template/claude-task-manager/public/m/app.css +2909 -262
- package/template/claude-task-manager/public/m/app.js +6601 -398
- package/template/claude-task-manager/public/m/claim.html +224 -17
- package/template/claude-task-manager/public/m/index.html +117 -21
- package/template/claude-task-manager/public/m/sw.js +3 -1
- package/template/claude-task-manager/public/manifest.json +2 -2
- package/template/claude-task-manager/public/prompts.html +30 -14
- package/template/claude-task-manager/queue-engine.js +507 -28
- package/template/claude-task-manager/scripts/repair-claude-session-images.js +27 -8
- package/template/claude-task-manager/server.js +14341 -2197
- package/template/claude-task-manager/session-integrity.js +160 -18
- package/template/claude-task-manager/session-search-ranking.js +1 -0
- package/template/claude-task-manager/session-utils.js +25 -5
- package/template/claude-task-manager/workers/approval-blocklist.js +96 -6
- package/template/claude-task-manager/workers/approval-widget-validator.js +14 -8
- package/template/claude-task-manager/workers/conversation-import-worker.js +11 -50
- package/template/claude-task-manager/workers/db-owner-worker.js +386 -0
- package/template/claude-task-manager/workers/harvest-worker.js +9 -55
- package/template/claude-task-manager/workers/headless-term-worker.js +9 -530
- package/template/claude-task-manager/workers/read-pool-worker.js +387 -0
- package/template/claude-task-manager/workers/scrollback-worker.js +11 -72
- package/template/claude-task-manager/workers/session-host-process.js +146 -0
- package/template/claude-task-manager/workers/session-integrity-worker.js +10 -54
- package/template/claude-task-manager/workers/state-detectors/base.js +18 -1
- package/template/claude-task-manager/workers/state-detectors/claude-code.js +182 -9
- package/template/claude-task-manager/workers/state-detectors/codex.js +150 -2
- package/template/claude-task-manager/workers/state-detectors/cursor.js +127 -0
- package/template/claude-task-manager/workers/state-detectors/gemini.js +21 -0
- package/template/claude-task-manager/workers/state-detectors/index.js +29 -0
- package/template/claude-task-manager/workers/state-detectors/opencode.js +103 -0
- package/template/docs/design/markdown-review-pane.md +206 -0
- package/template/docs/designs/2026-05-17-portkey-gateway-provider-ux.md +129 -38
- package/template/docs/designs/2026-05-20-mobile-worktree-finish-command.md +27 -0
- package/template/docs/designs/2026-05-22-ai-configuration-consolidation.md +248 -0
- package/template/docs/designs/ai-configuration-consolidation-mock.html +812 -0
- package/template/docs/private-memory-and-pii-policy.md +69 -0
- package/template/package.json +2 -1
- package/template/scripts/check-private-data.js +201 -0
- package/template/shared/sqlite-owner-guard.js +30 -0
- package/template/shared/sqlite-owner-write-queue.js +225 -0
- package/template/shared/sqlite-storage-policy.js +111 -0
- package/template/shared/sqlite-write-lock.js +428 -0
- package/template/wall-e/agent-runners/claude-code.js +5 -0
- package/template/wall-e/agent.js +166 -22
- package/template/wall-e/api-walle.js +524 -70
- package/template/wall-e/auth/provider-flows.js +11 -1
- package/template/wall-e/bin/walle-mcp-stdio.js +341 -17
- package/template/wall-e/brain.js +1614 -141
- package/template/wall-e/chat/attachment-blocks.js +96 -0
- package/template/wall-e/chat/attachments.js +2 -1
- package/template/wall-e/chat/capability-resolver.js +7 -7
- package/template/wall-e/chat/context-messages.js +28 -0
- package/template/wall-e/chat/conversation-frame.js +630 -0
- package/template/wall-e/chat/provider-messages.js +125 -0
- package/template/wall-e/chat.js +1002 -233
- package/template/wall-e/coding/acceptance-contract.js +170 -0
- package/template/wall-e/coding/acp-adapter.js +1 -1
- package/template/wall-e/coding/agent-catalog.js +3 -0
- package/template/wall-e/coding/artifact-store.js +93 -0
- package/template/wall-e/coding/capability-router.js +120 -0
- package/template/wall-e/coding/coding-run-controller.js +423 -0
- package/template/wall-e/coding/compaction-service.js +157 -12
- package/template/wall-e/coding/frontend-verification.js +258 -0
- package/template/wall-e/coding/lifecycle-hooks.js +75 -0
- package/template/wall-e/coding/local-preview-contract.js +157 -0
- package/template/wall-e/coding/permission-service.js +57 -13
- package/template/wall-e/coding/prompt-bundle.js +19 -1
- package/template/wall-e/coding/prompt-section-registry.js +227 -0
- package/template/wall-e/coding/provider-compat.js +15 -0
- package/template/wall-e/coding/runtime-events.js +224 -0
- package/template/wall-e/coding/runtime-mode.js +3 -0
- package/template/wall-e/coding/side-git-snapshot.js +160 -4
- package/template/wall-e/coding/snapshot-service.js +143 -1
- package/template/wall-e/coding/stream-processor.js +388 -34
- package/template/wall-e/coding/task-tool.js +141 -4
- package/template/wall-e/coding/tool-execution-controller.js +365 -0
- package/template/wall-e/coding/tool-registry.js +43 -5
- package/template/wall-e/coding/user-hooks.js +217 -0
- package/template/wall-e/coding-orchestrator.js +1330 -221
- package/template/wall-e/coding-prompts.js +20 -4
- package/template/wall-e/context/context-builder.js +15 -2
- package/template/wall-e/decision/confidence.js +1 -1
- package/template/wall-e/docs/coding-acceptance-contract.md +41 -0
- package/template/wall-e/docs/external-action-controller.md +26 -6
- package/template/wall-e/docs/telemetry-lifecycle.md +8 -2
- package/template/wall-e/embeddings.js +591 -53
- package/template/wall-e/external-action-controller.js +12 -0
- package/template/wall-e/http/auth.js +1 -0
- package/template/wall-e/http/chat-api.js +46 -11
- package/template/wall-e/http/model-admin.js +836 -34
- package/template/wall-e/lib/boot-profile.js +88 -0
- package/template/wall-e/lib/event-loop-monitor.js +93 -0
- package/template/wall-e/lib/service-health.js +194 -0
- package/template/wall-e/llm/anthropic.js +130 -5
- package/template/wall-e/llm/client.js +266 -63
- package/template/wall-e/llm/default-fallback.js +382 -0
- package/template/wall-e/llm/health.js +19 -0
- package/template/wall-e/llm/message-guard.js +78 -0
- package/template/wall-e/llm/model-catalog.js +252 -1
- package/template/wall-e/llm/openai.js +26 -4
- package/template/wall-e/llm/portkey-sync.js +654 -0
- package/template/wall-e/llm/provider-error.js +30 -2
- package/template/wall-e/llm/registry.js +5 -1
- package/template/wall-e/llm/request-compat.js +67 -0
- package/template/wall-e/loops/backfill.js +79 -23
- package/template/wall-e/loops/brain-optimize.js +67 -0
- package/template/wall-e/loops/ingest.js +25 -10
- package/template/wall-e/loops/question-digest.js +160 -0
- package/template/wall-e/loops/reflect.js +6 -4
- package/template/wall-e/loops/think.js +39 -12
- package/template/wall-e/mcp-server.js +318 -36
- package/template/wall-e/memory/ctm-context-client.js +52 -14
- package/template/wall-e/memory/ctm-operational-context.js +237 -0
- package/template/wall-e/memory/ctm-prompt-executions-client.js +128 -0
- package/template/wall-e/memory/ctm-session-context.js +111 -63
- package/template/wall-e/prompts/coding/deepseek.txt +3 -0
- package/template/wall-e/prompts/coding/gemini.txt +6 -0
- package/template/wall-e/prompts/coding/gpt.txt +6 -0
- package/template/wall-e/prompts/coding/local.txt +7 -0
- package/template/wall-e/runtime/decision-hooks.js +115 -0
- package/template/wall-e/runtime/devbox-gateway.js +82 -8
- package/template/wall-e/runtime/prompt-manifest.js +86 -0
- package/template/wall-e/runtime/tool-executor.js +269 -0
- package/template/wall-e/runtime/tool-result-envelope.js +138 -0
- package/template/wall-e/runtime/transcript-projection.js +60 -0
- package/template/wall-e/runtime/walle-runtime.js +224 -0
- package/template/wall-e/scripts/db-optimize/migrate.js +162 -0
- package/template/wall-e/scripts/db-optimize/recall-eval.js +117 -0
- package/template/wall-e/server.js +15 -0
- package/template/wall-e/session-files.js +9 -0
- package/template/wall-e/skills/_bundled/google-calendar/run.js +1 -1
- package/template/wall-e/skills/_bundled/gws-workspace/run.js +1 -1
- package/template/wall-e/skills/_bundled/slack-mentions/run.js +76 -6
- package/template/wall-e/skills/claude-code-reader.js +7 -3
- package/template/wall-e/skills/script-skill-runner.js +10 -0
- package/template/wall-e/skills/skill-planner.js +38 -0
- package/template/wall-e/tools/builtin-middleware.js +19 -9
- package/template/wall-e/tools/local-tools.js +1428 -16
- package/template/wall-e/tools/permission-checker.js +73 -5
- package/template/wall-e/tools/question-manager.js +117 -7
- package/template/wall-e/training/harvester.js +12 -28
- package/template/wall-e/training/replay.js +25 -80
- package/template/website/index.html +10 -10
- package/template/wall-e/eval/ab-test.js +0 -203
- package/template/wall-e/eval/agent-runner.js +0 -772
- package/template/wall-e/eval/agent-scorer.js +0 -461
- package/template/wall-e/eval/aggregator.js +0 -414
- package/template/wall-e/eval/allowed-test-commands.js +0 -34
- package/template/wall-e/eval/benchmark-generator.js +0 -113
- package/template/wall-e/eval/benchmarks/chat-eval.json +0 -1662
- package/template/wall-e/eval/benchmarks/chat.json +0 -82
- package/template/wall-e/eval/benchmarks/coding-agent-real.json +0 -1
- package/template/wall-e/eval/benchmarks/coding-agent.json +0 -1581
- package/template/wall-e/eval/benchmarks/coding.json +0 -122
- package/template/wall-e/eval/benchmarks/memory-retrieval.json +0 -234
- package/template/wall-e/eval/benchmarks/reasoning.json +0 -82
- package/template/wall-e/eval/benchmarks/swebench-lite-30.json +0 -212
- package/template/wall-e/eval/benchmarks.js +0 -669
- package/template/wall-e/eval/cc-replay.js +0 -719
- package/template/wall-e/eval/chat-eval.js +0 -525
- package/template/wall-e/eval/check-keys.js +0 -15
- package/template/wall-e/eval/check-providers.js +0 -42
- package/template/wall-e/eval/codex-cli-baseline.js +0 -669
- package/template/wall-e/eval/coding-agent-real.js +0 -570
- package/template/wall-e/eval/context-compactor.js +0 -251
- package/template/wall-e/eval/debug-agent003.js +0 -68
- package/template/wall-e/eval/diagnostics.js +0 -216
- package/template/wall-e/eval/eval-orchestrator.js +0 -642
- package/template/wall-e/eval/evaluate.js +0 -202
- package/template/wall-e/eval/evaluator.js +0 -373
- package/template/wall-e/eval/exporter.js +0 -212
- package/template/wall-e/eval/fixtures/express-basic/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-basic/server.js +0 -115
- package/template/wall-e/eval/fixtures/express-basic/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-buggy/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-buggy/server.js +0 -113
- package/template/wall-e/eval/fixtures/express-buggy/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-buggy-items/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-buggy-items/server.js +0 -112
- package/template/wall-e/eval/fixtures/express-buggy-items/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-buggy-search/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-buggy-search/server.js +0 -121
- package/template/wall-e/eval/fixtures/express-buggy-search/test.js +0 -83
- package/template/wall-e/eval/fixtures/express-rename-data/data.js +0 -34
- package/template/wall-e/eval/fixtures/express-rename-data/package.json +0 -9
- package/template/wall-e/eval/fixtures/express-rename-data/server.js +0 -97
- package/template/wall-e/eval/fixtures/express-rename-data/test.js +0 -88
- package/template/wall-e/eval/fixtures/express-xss/package.json +0 -12
- package/template/wall-e/eval/fixtures/express-xss/server.js +0 -90
- package/template/wall-e/eval/fixtures/express-xss/test.js +0 -67
- package/template/wall-e/eval/fixtures/express-xss/views/profile.ejs +0 -9
- package/template/wall-e/eval/fixtures/fullstack-app/config/default.js +0 -9
- package/template/wall-e/eval/fixtures/fullstack-app/config/test.js +0 -13
- package/template/wall-e/eval/fixtures/fullstack-app/package.json +0 -11
- package/template/wall-e/eval/fixtures/fullstack-app/public/css/style.css +0 -137
- package/template/wall-e/eval/fixtures/fullstack-app/public/index.html +0 -46
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/app.js +0 -121
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/auth.js +0 -71
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/items.js +0 -80
- package/template/wall-e/eval/fixtures/fullstack-app/public/js/users.js +0 -46
- package/template/wall-e/eval/fixtures/fullstack-app/public/login.html +0 -45
- package/template/wall-e/eval/fixtures/fullstack-app/public/register.html +0 -38
- package/template/wall-e/eval/fixtures/fullstack-app/scripts/migrate.js +0 -23
- package/template/wall-e/eval/fixtures/fullstack-app/scripts/seed.js +0 -46
- package/template/wall-e/eval/fixtures/fullstack-app/server/db.js +0 -99
- package/template/wall-e/eval/fixtures/fullstack-app/server/index.js +0 -94
- package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/auth.js +0 -19
- package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/logger.js +0 -19
- package/template/wall-e/eval/fixtures/fullstack-app/server/router.js +0 -50
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/auth.js +0 -69
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/health.js +0 -23
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/items.js +0 -88
- package/template/wall-e/eval/fixtures/fullstack-app/server/routes/users.js +0 -75
- package/template/wall-e/eval/fixtures/fullstack-app/server/test.js +0 -198
- package/template/wall-e/eval/fixtures/fullstack-app/server/utils/response.js +0 -34
- package/template/wall-e/eval/fixtures/fullstack-app/server/utils/validate.js +0 -26
- package/template/wall-e/eval/fixtures/fullstack-app/server.js +0 -8
- package/template/wall-e/eval/fixtures/fullstack-app/test.js +0 -12
- package/template/wall-e/eval/fixtures/monorepo-basic/package.json +0 -8
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/data.js +0 -58
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/middleware.js +0 -46
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/package.json +0 -8
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/routes.js +0 -64
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/server.js +0 -56
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/test.js +0 -116
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/commands.js +0 -61
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/index.js +0 -62
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/output.js +0 -43
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/package.json +0 -11
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/test.js +0 -44
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/formatters.js +0 -43
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/index.js +0 -12
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/package.json +0 -5
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/test.js +0 -55
- package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/validators.js +0 -29
- package/template/wall-e/eval/fixtures/monorepo-basic/test.js +0 -46
- package/template/wall-e/eval/fixtures/node-cli/index.js +0 -78
- package/template/wall-e/eval/fixtures/node-cli/package.json +0 -10
- package/template/wall-e/eval/fixtures/node-cli/test.js +0 -57
- package/template/wall-e/eval/fixtures/node-typed/package.json +0 -8
- package/template/wall-e/eval/fixtures/node-typed/src/handlers.js +0 -31
- package/template/wall-e/eval/fixtures/node-typed/src/utils.js +0 -33
- package/template/wall-e/eval/fixtures/node-typed/test.js +0 -36
- package/template/wall-e/eval/fixtures/python-flask/app.py +0 -14
- package/template/wall-e/eval/fixtures/python-flask/requirements.txt +0 -2
- package/template/wall-e/eval/fixtures/python-flask/test_app.py +0 -25
- package/template/wall-e/eval/fixtures/wall-e-subset/brain.js +0 -105
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/aggregator.js +0 -101
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/chat.json +0 -20
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/coding.json +0 -32
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks.js +0 -64
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/package.json +0 -6
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/server.js +0 -31
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/test.js +0 -18
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/utils.js +0 -34
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/runner.js +0 -104
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/scorer.js +0 -73
- package/template/wall-e/eval/fixtures/wall-e-subset/eval/test.js +0 -134
- package/template/wall-e/eval/fixtures/wall-e-subset/llm/client.js +0 -99
- package/template/wall-e/eval/fixtures/wall-e-subset/llm/providers.js +0 -63
- package/template/wall-e/eval/fixtures/wall-e-subset/llm/test.js +0 -70
- package/template/wall-e/eval/fixtures/wall-e-subset/package.json +0 -10
- package/template/wall-e/eval/fixtures/wall-e-subset/test.js +0 -86
- package/template/wall-e/eval/harvester.js +0 -685
- package/template/wall-e/eval/head-to-head.js +0 -388
- package/template/wall-e/eval/humaneval-adapter.js +0 -321
- package/template/wall-e/eval/list-models.js +0 -31
- package/template/wall-e/eval/livecodebench-adapter.js +0 -291
- package/template/wall-e/eval/mail-integration.js +0 -443
- package/template/wall-e/eval/manifest.js +0 -186
- package/template/wall-e/eval/meta-harness/adapters/coding-agent.js +0 -57
- package/template/wall-e/eval/meta-harness/bootstrap-snapshot.js +0 -149
- package/template/wall-e/eval/meta-harness/candidate-store.js +0 -117
- package/template/wall-e/eval/meta-harness/cli.js +0 -86
- package/template/wall-e/eval/meta-harness/domain-spec.js +0 -154
- package/template/wall-e/eval/meta-harness/domains/coding-agent.domain.json +0 -84
- package/template/wall-e/eval/meta-harness/examples/env-bootstrap-candidate.js +0 -29
- package/template/wall-e/eval/meta-harness/experience-store.js +0 -174
- package/template/wall-e/eval/meta-harness/frontier.js +0 -96
- package/template/wall-e/eval/meta-harness/harness-interface.js +0 -90
- package/template/wall-e/eval/meta-harness/leakage-guard.js +0 -80
- package/template/wall-e/eval/meta-harness/optimizer.js +0 -207
- package/template/wall-e/eval/meta-harness/proposer-runner.js +0 -110
- package/template/wall-e/eval/meta-harness/reporting.js +0 -58
- package/template/wall-e/eval/meta-harness/telemetry.js +0 -27
- package/template/wall-e/eval/meta-harness/validation.js +0 -81
- package/template/wall-e/eval/promoter.js +0 -228
- package/template/wall-e/eval/provider-normalizer.js +0 -33
- package/template/wall-e/eval/replay.js +0 -395
- package/template/wall-e/eval/run-agent-benchmarks.js +0 -386
- package/template/wall-e/eval/run-codex-cli-baseline.js +0 -177
- package/template/wall-e/eval/run-coding-agent-real.js +0 -187
- package/template/wall-e/eval/run-eval.js +0 -435
- package/template/wall-e/eval/run-model-comparison.js +0 -142
- package/template/wall-e/eval/session-evaluator.js +0 -187
- package/template/wall-e/eval/session-miner.js +0 -207
- package/template/wall-e/eval/session-retrieval-benchmark.js +0 -150
- package/template/wall-e/eval/session-transcripts.js +0 -509
- package/template/wall-e/eval/shadow.js +0 -161
- package/template/wall-e/eval/swebench-adapter.js +0 -345
- package/template/wall-e/eval/swebench-docker.js +0 -192
- package/template/wall-e/eval/train.py +0 -320
- package/template/wall-e/eval/trainer.js +0 -232
- package/template/wall-e/eval/weekly-eval-loop.js +0 -241
|
@@ -19,6 +19,8 @@ const HOST_READY_TIMEOUT_MS = 45000;
|
|
|
19
19
|
const WATCHDOG_CHECK_INTERVAL_MS = 30000;
|
|
20
20
|
const WATCHDOG_WAKE_DRIFT_MS = 90000;
|
|
21
21
|
const PUBLIC_PROBE_TIMEOUT_MS = 6000;
|
|
22
|
+
const DEVTUNNEL_MAX_EXPIRATION = '30d';
|
|
23
|
+
const DEVTUNNEL_RENEWAL_INTERVAL_MS = 25 * 24 * 60 * 60 * 1000;
|
|
22
24
|
const HOST_HEADER_MODE_PASSTHROUGH = 'passthrough';
|
|
23
25
|
const HOST_HEADER_MODE_DEFAULT = 'default';
|
|
24
26
|
|
|
@@ -68,6 +70,58 @@ function loginProviderLabel(provider) {
|
|
|
68
70
|
return provider === 'github' ? 'GitHub' : 'Microsoft';
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
// Host-event diagnostics: classify devtunnel host output so the Access page can
|
|
74
|
+
// show WHY the phone link dropped (credential expiry vs relay disconnect vs
|
|
75
|
+
// recovery) instead of a generic failure.
|
|
76
|
+
const HOST_EVENT_LIMIT = 30;
|
|
77
|
+
const HOST_EVENT_REPEAT_SUPPRESS_MS = 30 * 1000;
|
|
78
|
+
|
|
79
|
+
function classifyDevTunnelHostEventText(text) {
|
|
80
|
+
const t = String(text || '');
|
|
81
|
+
if (!t.trim()) return null;
|
|
82
|
+
if (/unauthoriz|forbidden|authentication failed|login\s+required|token[^\n]{0,60}(expired|invalid|refresh)/i.test(t)) {
|
|
83
|
+
return 'auth_failure';
|
|
84
|
+
}
|
|
85
|
+
if (/connection\s*lost|session closed/i.test(t)) return 'connection_lost';
|
|
86
|
+
if (/relay restored|reconnected/i.test(t)) return 'reconnected';
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function appendManagedTunnelHostEvent(event, options = {}) {
|
|
91
|
+
if (!event || !event.type) return null;
|
|
92
|
+
try {
|
|
93
|
+
const state = loadManagedTunnelState(options);
|
|
94
|
+
if (!state || !state.tunnel_id) return null;
|
|
95
|
+
const events = Array.isArray(state.host_events) ? state.host_events : [];
|
|
96
|
+
const at = new Date().toISOString();
|
|
97
|
+
const last = events.length ? events[events.length - 1] : null;
|
|
98
|
+
const suppressMs = Number(options.hostEventRepeatSuppressMs || HOST_EVENT_REPEAT_SUPPRESS_MS);
|
|
99
|
+
if (last && last.type === event.type && (Date.parse(at) - (Date.parse(last.at) || 0)) < suppressMs) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const entry = { at, type: String(event.type), detail: String(event.detail || '').slice(0, 240) };
|
|
103
|
+
state.host_events = [...events, entry].slice(-HOST_EVENT_LIMIT);
|
|
104
|
+
writeManagedTunnelState(state, options);
|
|
105
|
+
return entry;
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Only the CTM process that spawned the host sees its pipes; after a CTM restart
|
|
112
|
+
// the re-adopted host runs unobserved until its next (re)start.
|
|
113
|
+
function watchDevTunnelHostOutput(child, options = {}) {
|
|
114
|
+
let carry = '';
|
|
115
|
+
function scan(chunk) {
|
|
116
|
+
const text = carry + String(chunk || '');
|
|
117
|
+
carry = text.slice(-200);
|
|
118
|
+
const type = classifyDevTunnelHostEventText(text);
|
|
119
|
+
if (type) appendManagedTunnelHostEvent({ type, detail: text.trim().slice(-240) }, options);
|
|
120
|
+
}
|
|
121
|
+
if (child && child.stdout) child.stdout.on('data', scan);
|
|
122
|
+
if (child && child.stderr) child.stderr.on('data', scan);
|
|
123
|
+
}
|
|
124
|
+
|
|
71
125
|
function setupDir(options = {}) {
|
|
72
126
|
const configDir = path.resolve(options.configDir || path.join(process.env.HOME || process.cwd(), '.walle', 'data'));
|
|
73
127
|
return path.join(configDir, 'microsoft-dev-tunnel');
|
|
@@ -231,10 +285,32 @@ function devTunnelAccessListArgs(tunnelId, options = {}) {
|
|
|
231
285
|
return ['access', 'list', tunnelId, '-p', String(options.port || 3456), '-j'];
|
|
232
286
|
}
|
|
233
287
|
|
|
288
|
+
function devTunnelUpdateArgs(tunnelId, options = {}) {
|
|
289
|
+
return ['update', tunnelId, '--expiration', String(options.tunnelExpiration || options.tunnel_expiration || DEVTUNNEL_MAX_EXPIRATION)];
|
|
290
|
+
}
|
|
291
|
+
|
|
234
292
|
function devTunnelPrivateAccessResetArgs(tunnelId, options = {}) {
|
|
235
293
|
return ['access', 'reset', tunnelId, '-p', String(options.port || 3456), '-j'];
|
|
236
294
|
}
|
|
237
295
|
|
|
296
|
+
function devTunnelAccessDeleteArgs(tunnelId, index, options = {}) {
|
|
297
|
+
return ['access', 'delete', tunnelId, '-p', String(options.port || 3456), '-i', String(index), '-j'];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function devTunnelAnonymousConnectAccessArgs(tunnelId, options = {}) {
|
|
301
|
+
const expiration = String(options.anonymousAccessExpiration || options.anonymous_access_expiration || DEVTUNNEL_MAX_EXPIRATION);
|
|
302
|
+
return [
|
|
303
|
+
'access',
|
|
304
|
+
'create',
|
|
305
|
+
tunnelId,
|
|
306
|
+
'-p', String(options.port || 3456),
|
|
307
|
+
'--anonymous',
|
|
308
|
+
'--scopes', 'connect',
|
|
309
|
+
'-e', expiration,
|
|
310
|
+
'-j',
|
|
311
|
+
];
|
|
312
|
+
}
|
|
313
|
+
|
|
238
314
|
function caffeinateArgs(options = {}) {
|
|
239
315
|
const parentPid = Number(options.parentPid || process.pid);
|
|
240
316
|
const args = ['-i', '-m'];
|
|
@@ -243,6 +319,42 @@ function caffeinateArgs(options = {}) {
|
|
|
243
319
|
return args;
|
|
244
320
|
}
|
|
245
321
|
|
|
322
|
+
function isoAfter(timestamp, ms) {
|
|
323
|
+
const base = Date.parse(timestamp || '');
|
|
324
|
+
const at = Number.isFinite(base) ? base : Date.now();
|
|
325
|
+
return new Date(at + ms).toISOString();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function ageMs(timestamp) {
|
|
329
|
+
const at = Date.parse(timestamp || '');
|
|
330
|
+
if (!Number.isFinite(at)) return Infinity;
|
|
331
|
+
return Date.now() - at;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function renewalDueFrom(timestamp, options = {}) {
|
|
335
|
+
const intervalMs = Number(options.renewalIntervalMs || options.renewal_interval_ms || DEVTUNNEL_RENEWAL_INTERVAL_MS);
|
|
336
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0) return false;
|
|
337
|
+
return ageMs(timestamp) >= intervalMs;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function microsoftTunnelTunnelRenewalDue(state = {}, options = {}) {
|
|
341
|
+
if (!state || typeof state !== 'object') return true;
|
|
342
|
+
return renewalDueFrom(state.last_tunnel_renewed_at || state.tunnel_renewed_at || state.started_at, options);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function microsoftTunnelAccessRenewalDue(state = {}, options = {}) {
|
|
346
|
+
const mode = normalizeMicrosoftTunnelAccessMode(state.access_mode || state.access?.mode || options.accessMode || options.access_mode);
|
|
347
|
+
if (mode !== 'ctm_authenticated') return false;
|
|
348
|
+
return renewalDueFrom(state.last_access_renewed_at || state.access?.renewed_at || state.access?.created_at, options);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function microsoftTunnelRenewalDue(state = {}, options = {}) {
|
|
352
|
+
return {
|
|
353
|
+
tunnel: microsoftTunnelTunnelRenewalDue(state, options),
|
|
354
|
+
access: microsoftTunnelAccessRenewalDue(state, options),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
246
358
|
async function inspectManagedTunnelProcess(state = {}, options = {}) {
|
|
247
359
|
const pid = Number(state && state.pid);
|
|
248
360
|
const pidRunning = pidIsRunning(pid);
|
|
@@ -482,31 +594,110 @@ async function annotateWatchdogState(partial, options = {}) {
|
|
|
482
594
|
|
|
483
595
|
function managedTunnelAccessSummary(partial = {}, options = {}) {
|
|
484
596
|
const port = Number(options.port || 3456);
|
|
597
|
+
const mode = normalizeMicrosoftTunnelAccessMode(partial.mode || options.accessMode || options.access_mode);
|
|
598
|
+
const anonymousConnect = Object.prototype.hasOwnProperty.call(partial, 'anonymous_connect')
|
|
599
|
+
? !!partial.anonymous_connect
|
|
600
|
+
: mode === 'ctm_authenticated';
|
|
601
|
+
const staleAnonymous = !!partial.stale_anonymous_access
|
|
602
|
+
|| !!partial.has_stale_anonymous_access
|
|
603
|
+
|| (mode === 'private_microsoft' && anonymousConnect && !partial.reset);
|
|
604
|
+
const error = partial.error || '';
|
|
485
605
|
return {
|
|
486
|
-
mode
|
|
487
|
-
|
|
606
|
+
mode,
|
|
607
|
+
status: error ? 'error' : (staleAnonymous ? 'stale_anonymous' : mode),
|
|
608
|
+
anonymous_connect: anonymousConnect,
|
|
609
|
+
stale_anonymous_access: staleAnonymous,
|
|
610
|
+
needs_private_reset: staleAnonymous,
|
|
488
611
|
port,
|
|
489
612
|
checked_at: partial.checked_at || new Date().toISOString(),
|
|
490
613
|
already_configured: !!partial.already_configured,
|
|
614
|
+
created: !!partial.created,
|
|
615
|
+
renewed: !!partial.renewed,
|
|
616
|
+
renewed_at: partial.renewed_at || '',
|
|
617
|
+
expiration: partial.expiration || '',
|
|
618
|
+
next_renewal_at: partial.next_renewal_at || '',
|
|
619
|
+
renewal_due: !!partial.renewal_due,
|
|
491
620
|
reset: !!partial.reset,
|
|
492
621
|
anonymous_removed: !!partial.anonymous_removed,
|
|
493
622
|
list_error: partial.list_error || '',
|
|
494
|
-
error
|
|
623
|
+
error,
|
|
495
624
|
};
|
|
496
625
|
}
|
|
497
626
|
|
|
627
|
+
function normalizeMicrosoftTunnelAccessMode(value) {
|
|
628
|
+
const raw = String(value || '').trim().toLowerCase().replace(/[-\s]+/g, '_');
|
|
629
|
+
if (raw === 'private' || raw === 'microsoft' || raw === 'private_microsoft') return 'private_microsoft';
|
|
630
|
+
if (raw === 'anonymous' || raw === 'public' || raw === 'ctm_auth' || raw === 'ctm_authenticated') return 'ctm_authenticated';
|
|
631
|
+
// Default is app-gated (ctm_authenticated): reachable URL + CTM passkey/device
|
|
632
|
+
// auth. It is the only PWA/WebSocket-compatible mode; private is an opt-in.
|
|
633
|
+
return 'ctm_authenticated';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// SECURITY / REACHABILITY: the Dev Tunnel default is "ctm_authenticated" — the
|
|
637
|
+
// tunnel transport is reachable by URL, and CTM's own app-layer auth (per-origin
|
|
638
|
+
// passkey + revocable device token + step-up, behind an unguessable random tunnel
|
|
639
|
+
// URL) is the gate. This is the ONLY mode compatible with the phone client, which
|
|
640
|
+
// is a PWA that talks to CTM via fetch() + a WebSocket: Dev Tunnels "private" mode
|
|
641
|
+
// enforces auth by redirecting the *initial request* to an interactive
|
|
642
|
+
// Microsoft/GitHub login page, and programmatic XHR/fetch/WebSocket cannot follow
|
|
643
|
+
// that cross-origin redirect — so private silently bricks remote access.
|
|
644
|
+
//
|
|
645
|
+
// "private_microsoft" therefore is an explicit, deliberate opt-in only. It is
|
|
646
|
+
// honored when it comes from a fresh user selection (input.access_mode), the env
|
|
647
|
+
// (CTM_MS_TUNNEL_ACCESS_MODE), or options.allowPrivateAccess — but NOT when a
|
|
648
|
+
// private value arrives only from restored/persisted state. A persisted private
|
|
649
|
+
// value is treated as stale (it predates this policy) and migrated back to the
|
|
650
|
+
// app-gated default, so the phone keeps working across headless restarts.
|
|
651
|
+
function microsoftTunnelPrivateAccessForced(options = {}) {
|
|
652
|
+
if (options.allowPrivateAccess === true) return true;
|
|
653
|
+
const envMode = String(process.env.CTM_MS_TUNNEL_ACCESS_MODE || '').trim();
|
|
654
|
+
return envMode !== '' && normalizeMicrosoftTunnelAccessMode(envMode) === 'private_microsoft';
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function resolveMicrosoftTunnelAccessMode(input = {}, options = {}) {
|
|
658
|
+
const fromInput = normalizeMicrosoftTunnelAccessMode(input.access_mode || input.accessMode);
|
|
659
|
+
const inputIsExplicit = !!(input.access_mode || input.accessMode);
|
|
660
|
+
const requested = inputIsExplicit ? fromInput : normalizeMicrosoftTunnelAccessMode(
|
|
661
|
+
options.accessMode
|
|
662
|
+
|| options.access_mode
|
|
663
|
+
|| process.env.CTM_MS_TUNNEL_ACCESS_MODE
|
|
664
|
+
);
|
|
665
|
+
if (requested === 'private_microsoft') {
|
|
666
|
+
const deliberate = (inputIsExplicit && fromInput === 'private_microsoft')
|
|
667
|
+
|| microsoftTunnelPrivateAccessForced(options);
|
|
668
|
+
if (!deliberate) return 'ctm_authenticated';
|
|
669
|
+
}
|
|
670
|
+
return requested;
|
|
671
|
+
}
|
|
672
|
+
|
|
498
673
|
function persistManagedTunnelAccess(tunnelId, access, options = {}) {
|
|
499
674
|
const state = loadManagedTunnelState(options);
|
|
500
675
|
if (!state || normalizeTunnelId(state.tunnel_id || '') !== normalizeTunnelId(tunnelId || '')) return;
|
|
676
|
+
const renewedAt = access.renewed_at || (access.created ? access.checked_at : '') || '';
|
|
501
677
|
writeManagedTunnelState({
|
|
502
678
|
...state,
|
|
503
679
|
access_mode: access.mode || 'private_microsoft',
|
|
504
680
|
access,
|
|
505
681
|
last_access_check_at: access.checked_at || new Date().toISOString(),
|
|
682
|
+
last_access_renewed_at: renewedAt || state.last_access_renewed_at || '',
|
|
683
|
+
next_access_renewal_at: access.next_renewal_at || (renewedAt ? isoAfter(renewedAt, DEVTUNNEL_RENEWAL_INTERVAL_MS) : state.next_access_renewal_at || ''),
|
|
684
|
+
access_expiration: access.expiration || state.access_expiration || '',
|
|
506
685
|
access_error: access.error || '',
|
|
507
686
|
}, options);
|
|
508
687
|
}
|
|
509
688
|
|
|
689
|
+
function persistManagedTunnelRenewal(tunnelId, renewal, options = {}) {
|
|
690
|
+
const state = loadManagedTunnelState(options);
|
|
691
|
+
if (!state || normalizeTunnelId(state.tunnel_id || '') !== normalizeTunnelId(tunnelId || '')) return;
|
|
692
|
+
writeManagedTunnelState({
|
|
693
|
+
...state,
|
|
694
|
+
last_tunnel_renewed_at: renewal.renewed_at || state.last_tunnel_renewed_at || '',
|
|
695
|
+
next_tunnel_renewal_at: renewal.next_renewal_at || state.next_tunnel_renewal_at || '',
|
|
696
|
+
tunnel_expiration: renewal.expiration || state.tunnel_expiration || DEVTUNNEL_MAX_EXPIRATION,
|
|
697
|
+
tunnel_renewal_error: renewal.error || '',
|
|
698
|
+
}, options);
|
|
699
|
+
}
|
|
700
|
+
|
|
510
701
|
function parseDevTunnelAccessEntries(stdout) {
|
|
511
702
|
const text = String(stdout || '').trim();
|
|
512
703
|
if (!text) return [];
|
|
@@ -544,6 +735,47 @@ function hasAnonymousConnectAccess(stdout) {
|
|
|
544
735
|
return parseDevTunnelAccessEntries(stdout).some(accessEntryAllowsAnonymousConnect);
|
|
545
736
|
}
|
|
546
737
|
|
|
738
|
+
function anonymousConnectAccessIndices(stdout) {
|
|
739
|
+
return parseDevTunnelAccessEntries(stdout)
|
|
740
|
+
.map((entry, index) => (accessEntryAllowsAnonymousConnect(entry) ? index : -1))
|
|
741
|
+
.filter((index) => index >= 0);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
async function inspectMicrosoftTunnelAccess(tunnelId, options = {}) {
|
|
745
|
+
const id = normalizeTunnelId(tunnelId);
|
|
746
|
+
const checkedAt = new Date().toISOString();
|
|
747
|
+
if (!id) {
|
|
748
|
+
return managedTunnelAccessSummary({
|
|
749
|
+
checked_at: checkedAt,
|
|
750
|
+
error: 'Microsoft tunnel id is missing.',
|
|
751
|
+
}, options);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
try {
|
|
755
|
+
const list = await execDevTunnel(devTunnelAccessListArgs(id, options), options);
|
|
756
|
+
const body = list?.stdout || list?.stderr || '';
|
|
757
|
+
const hasAnonymous = hasAnonymousConnectAccess(body);
|
|
758
|
+
const mode = resolveMicrosoftTunnelAccessMode({}, options);
|
|
759
|
+
const access = managedTunnelAccessSummary({
|
|
760
|
+
mode,
|
|
761
|
+
anonymous_connect: hasAnonymous,
|
|
762
|
+
already_configured: mode === 'ctm_authenticated' ? hasAnonymous : !hasAnonymous,
|
|
763
|
+
checked_at: checkedAt,
|
|
764
|
+
}, options);
|
|
765
|
+
persistManagedTunnelAccess(id, access, options);
|
|
766
|
+
return access;
|
|
767
|
+
} catch (err) {
|
|
768
|
+
const message = String(err?.stderr || err?.message || err || 'Could not list Microsoft tunnel access.').slice(0, 800);
|
|
769
|
+
const access = managedTunnelAccessSummary({
|
|
770
|
+
checked_at: checkedAt,
|
|
771
|
+
list_error: message,
|
|
772
|
+
error: message,
|
|
773
|
+
}, options);
|
|
774
|
+
persistManagedTunnelAccess(id, access, options);
|
|
775
|
+
return access;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
547
779
|
function normalizeTunnelId(value) {
|
|
548
780
|
const id = String(value || '').trim().toLowerCase();
|
|
549
781
|
return /^[a-z0-9][a-z0-9-]{2,59}$/.test(id) ? id : '';
|
|
@@ -559,6 +791,14 @@ function parseDevTunnelUserShow(stdout, stderr) {
|
|
|
559
791
|
if (/not\s+(logged|signed)\s+in|no\s+user|login\s+required/i.test(text)) {
|
|
560
792
|
return { signed_in: false, account: null, raw: text.slice(0, 800) };
|
|
561
793
|
}
|
|
794
|
+
if (/(?:github|microsoft|entra|oauth|auth(?:entication)?|token|credential)[^\n]*(?:refresh|expired|invalid|failed|unauthori[sz]ed)|(?:refresh|expired|invalid|failed|unauthori[sz]ed)[^\n]*(?:github|microsoft|entra|oauth|auth(?:entication)?|token|credential)|invalid_grant|interaction_required|AADSTS/i.test(text)) {
|
|
795
|
+
return {
|
|
796
|
+
signed_in: false,
|
|
797
|
+
account: null,
|
|
798
|
+
raw: text.slice(0, 800),
|
|
799
|
+
error: text.slice(0, 800),
|
|
800
|
+
};
|
|
801
|
+
}
|
|
562
802
|
let parsed = null;
|
|
563
803
|
try { parsed = JSON.parse(text); } catch {}
|
|
564
804
|
if (parsed && typeof parsed === 'object') {
|
|
@@ -705,6 +945,60 @@ async function detectLoginStatus(options = {}) {
|
|
|
705
945
|
}
|
|
706
946
|
}
|
|
707
947
|
|
|
948
|
+
async function checkMicrosoftDevTunnelLoginStatus(options = {}) {
|
|
949
|
+
const previous = loadLoginProcessState(options) || {};
|
|
950
|
+
const checkedAt = new Date().toISOString();
|
|
951
|
+
const login = await detectLoginStatus(options);
|
|
952
|
+
const provider = previous.provider || normalizeLoginProvider(options);
|
|
953
|
+
const signedIn = !!login.signed_in;
|
|
954
|
+
const account = login.account || null;
|
|
955
|
+
const nextState = {
|
|
956
|
+
...previous,
|
|
957
|
+
provider,
|
|
958
|
+
provider_label: previous.provider_label || loginProviderLabel(provider),
|
|
959
|
+
installed: login.installed !== false,
|
|
960
|
+
signed_in: signedIn,
|
|
961
|
+
account,
|
|
962
|
+
account_display: account && account.display ? String(account.display) : '',
|
|
963
|
+
login_checked_at: checkedAt,
|
|
964
|
+
login_check_error: signedIn ? '' : String(login.error || '').slice(0, 800),
|
|
965
|
+
};
|
|
966
|
+
if (signedIn) {
|
|
967
|
+
nextState.running = false;
|
|
968
|
+
nextState.finished_at = nextState.finished_at || checkedAt;
|
|
969
|
+
nextState.error = '';
|
|
970
|
+
nextState.error_code = '';
|
|
971
|
+
nextState.verified_at = checkedAt;
|
|
972
|
+
} else if (login.installed === false) {
|
|
973
|
+
nextState.running = false;
|
|
974
|
+
nextState.error_code = 'devtunnel_cli_missing';
|
|
975
|
+
nextState.error = login.error || 'devtunnel CLI is not installed or not on PATH.';
|
|
976
|
+
}
|
|
977
|
+
if (previous.signed_in === true && !signedIn && login.installed !== false) {
|
|
978
|
+
appendManagedTunnelHostEvent({
|
|
979
|
+
type: 'credential_expired',
|
|
980
|
+
detail: nextState.login_check_error || `${loginProviderLabel(provider)} sign-in is no longer valid.`,
|
|
981
|
+
}, options);
|
|
982
|
+
} else if (previous.signed_in === false && signedIn) {
|
|
983
|
+
appendManagedTunnelHostEvent({ type: 'signed_in', detail: nextState.account_display || '' }, options);
|
|
984
|
+
}
|
|
985
|
+
writeLoginProcessState(nextState, options);
|
|
986
|
+
const progress = getMicrosoftDevTunnelProgress(options);
|
|
987
|
+
const setup = options.includeSetup === false
|
|
988
|
+
? null
|
|
989
|
+
: await detectMicrosoftDevTunnelSetup(options).catch(() => null);
|
|
990
|
+
return {
|
|
991
|
+
ok: signedIn,
|
|
992
|
+
signed_in: signedIn,
|
|
993
|
+
account,
|
|
994
|
+
login: progress.login,
|
|
995
|
+
setup,
|
|
996
|
+
microsoft_dev_tunnel: setup,
|
|
997
|
+
progress,
|
|
998
|
+
error: signedIn ? '' : (login.error || 'Dev Tunnels is not signed in yet. Finish the browser sign-in, then check again.'),
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
708
1002
|
async function detectManagedMicrosoftDevTunnel(options = {}) {
|
|
709
1003
|
const state = loadManagedTunnelState(options);
|
|
710
1004
|
if (!state) return { configured: false, running: false };
|
|
@@ -743,7 +1037,15 @@ async function detectManagedMicrosoftDevTunnel(options = {}) {
|
|
|
743
1037
|
access_mode: state.access_mode || (state.access && state.access.mode) || '',
|
|
744
1038
|
access: state.access || null,
|
|
745
1039
|
last_access_check_at: state.last_access_check_at || (state.access && state.access.checked_at) || '',
|
|
1040
|
+
last_access_renewed_at: state.last_access_renewed_at || (state.access && state.access.renewed_at) || '',
|
|
1041
|
+
next_access_renewal_at: state.next_access_renewal_at || (state.access && state.access.next_renewal_at) || '',
|
|
1042
|
+
access_expiration: state.access_expiration || (state.access && state.access.expiration) || '',
|
|
746
1043
|
access_error: state.access_error || (state.access && state.access.error) || '',
|
|
1044
|
+
last_tunnel_renewed_at: state.last_tunnel_renewed_at || '',
|
|
1045
|
+
next_tunnel_renewal_at: state.next_tunnel_renewal_at || '',
|
|
1046
|
+
tunnel_expiration: state.tunnel_expiration || '',
|
|
1047
|
+
tunnel_renewal_error: state.tunnel_renewal_error || '',
|
|
1048
|
+
host_events: Array.isArray(state.host_events) ? state.host_events : [],
|
|
747
1049
|
state_path: managedStatePath(options),
|
|
748
1050
|
};
|
|
749
1051
|
}
|
|
@@ -978,12 +1280,26 @@ async function detectMicrosoftDevTunnelSetup(options = {}) {
|
|
|
978
1280
|
}
|
|
979
1281
|
|
|
980
1282
|
const login = await detectLoginStatus(options);
|
|
981
|
-
|
|
1283
|
+
let managed = await detectManagedMicrosoftDevTunnel(options);
|
|
1284
|
+
if (login.signed_in && managed.tunnel_id) {
|
|
1285
|
+
const access = await inspectMicrosoftTunnelAccess(managed.tunnel_id, {
|
|
1286
|
+
...options,
|
|
1287
|
+
accessMode: managed.access_mode || managed.access?.mode || options.accessMode || options.access_mode,
|
|
1288
|
+
});
|
|
1289
|
+
managed = {
|
|
1290
|
+
...managed,
|
|
1291
|
+
access,
|
|
1292
|
+
access_mode: access.mode,
|
|
1293
|
+
last_access_check_at: access.checked_at,
|
|
1294
|
+
access_error: access.error || '',
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
982
1297
|
return {
|
|
983
1298
|
installed: login.installed !== false,
|
|
984
1299
|
available: login.installed !== false && !!login.signed_in,
|
|
985
1300
|
signed_in: !!login.signed_in,
|
|
986
1301
|
account: login.account || null,
|
|
1302
|
+
login_provider: loginStateProvider(loadLoginProcessState(options) || {}),
|
|
987
1303
|
version,
|
|
988
1304
|
origin: managed.origin || '',
|
|
989
1305
|
mobile_url: managed.mobile_url || '',
|
|
@@ -1129,12 +1445,15 @@ function microsoftDevTunnelCommands(options = {}) {
|
|
|
1129
1445
|
login: command('devtunnel', ['user', 'login', '-g']),
|
|
1130
1446
|
device_login: command('devtunnel', ['user', 'login', '-g', '-d']),
|
|
1131
1447
|
microsoft_device_login: command('devtunnel', ['user', 'login', '-e', '-d']),
|
|
1448
|
+
logout: command('devtunnel', ['user', 'logout']),
|
|
1132
1449
|
access_private_reset: command('devtunnel', devTunnelPrivateAccessResetArgs('TUNNELID', { port })),
|
|
1450
|
+
access_ctm_authenticated: command('devtunnel', devTunnelAnonymousConnectAccessArgs('TUNNELID', { port })),
|
|
1451
|
+
renew_tunnel: command('devtunnel', devTunnelUpdateArgs('TUNNELID', { port })),
|
|
1133
1452
|
host_temporary: command('devtunnel', [
|
|
1134
1453
|
'host',
|
|
1135
1454
|
'-p', String(port),
|
|
1136
1455
|
'--protocol', 'http',
|
|
1137
|
-
'--expiration',
|
|
1456
|
+
'--expiration', DEVTUNNEL_MAX_EXPIRATION,
|
|
1138
1457
|
'--host-header', 'unchanged',
|
|
1139
1458
|
'--origin-header', 'unchanged',
|
|
1140
1459
|
]),
|
|
@@ -1142,7 +1461,7 @@ function microsoftDevTunnelCommands(options = {}) {
|
|
|
1142
1461
|
'host',
|
|
1143
1462
|
'-p', String(port),
|
|
1144
1463
|
'--protocol', 'http',
|
|
1145
|
-
'--expiration',
|
|
1464
|
+
'--expiration', DEVTUNNEL_MAX_EXPIRATION,
|
|
1146
1465
|
]),
|
|
1147
1466
|
};
|
|
1148
1467
|
}
|
|
@@ -1204,12 +1523,215 @@ async function ensurePrivateConnectAccess(tunnelId, options = {}) {
|
|
|
1204
1523
|
}
|
|
1205
1524
|
}
|
|
1206
1525
|
|
|
1526
|
+
async function renewCtmAuthenticatedConnectAccess(tunnelId, accessListBody, options = {}) {
|
|
1527
|
+
const checkedAt = new Date().toISOString();
|
|
1528
|
+
const indices = anonymousConnectAccessIndices(accessListBody);
|
|
1529
|
+
// Delete descending so the CLI's zero-based indices do not shift underneath us.
|
|
1530
|
+
for (const index of indices.slice().sort((a, b) => b - a)) {
|
|
1531
|
+
await execDevTunnel(devTunnelAccessDeleteArgs(tunnelId, index, options), options);
|
|
1532
|
+
}
|
|
1533
|
+
await execDevTunnel(devTunnelAnonymousConnectAccessArgs(tunnelId, options), options);
|
|
1534
|
+
const access = managedTunnelAccessSummary({
|
|
1535
|
+
mode: 'ctm_authenticated',
|
|
1536
|
+
renewed: true,
|
|
1537
|
+
renewed_at: checkedAt,
|
|
1538
|
+
expiration: String(options.anonymousAccessExpiration || options.anonymous_access_expiration || DEVTUNNEL_MAX_EXPIRATION),
|
|
1539
|
+
next_renewal_at: isoAfter(checkedAt, DEVTUNNEL_RENEWAL_INTERVAL_MS),
|
|
1540
|
+
checked_at: checkedAt,
|
|
1541
|
+
}, options);
|
|
1542
|
+
persistManagedTunnelAccess(tunnelId, access, options);
|
|
1543
|
+
return access;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
async function ensureCtmAuthenticatedConnectAccess(tunnelId, options = {}) {
|
|
1547
|
+
const checkedAt = new Date().toISOString();
|
|
1548
|
+
const listArgs = devTunnelAccessListArgs(tunnelId, options);
|
|
1549
|
+
let listError = '';
|
|
1550
|
+
let body = '';
|
|
1551
|
+
try {
|
|
1552
|
+
const list = await execDevTunnel(listArgs, options);
|
|
1553
|
+
body = list?.stdout || list?.stderr || '';
|
|
1554
|
+
} catch (err) {
|
|
1555
|
+
listError = String(err?.stderr || err?.message || err || 'Could not list Microsoft tunnel access.').slice(0, 500);
|
|
1556
|
+
// Listing can fail on older CLI builds or transient service errors. Try to
|
|
1557
|
+
// establish the narrow connect-only rule; CTM still owns application auth.
|
|
1558
|
+
}
|
|
1559
|
+
const hasAnonymous = hasAnonymousConnectAccess(body);
|
|
1560
|
+
if (hasAnonymous) {
|
|
1561
|
+
const state = loadManagedTunnelState(options) || {};
|
|
1562
|
+
const renewalDue = options.renewAccess === true || options.renew_access === true || microsoftTunnelAccessRenewalDue(state, options);
|
|
1563
|
+
if (renewalDue) {
|
|
1564
|
+
return await renewCtmAuthenticatedConnectAccess(tunnelId, body, options);
|
|
1565
|
+
}
|
|
1566
|
+
const access = managedTunnelAccessSummary({
|
|
1567
|
+
mode: 'ctm_authenticated',
|
|
1568
|
+
already_configured: true,
|
|
1569
|
+
renewal_due: false,
|
|
1570
|
+
checked_at: checkedAt,
|
|
1571
|
+
}, options);
|
|
1572
|
+
persistManagedTunnelAccess(tunnelId, access, options);
|
|
1573
|
+
return access;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
try {
|
|
1577
|
+
await execDevTunnel(devTunnelAnonymousConnectAccessArgs(tunnelId, options), options);
|
|
1578
|
+
const access = managedTunnelAccessSummary({
|
|
1579
|
+
mode: 'ctm_authenticated',
|
|
1580
|
+
created: true,
|
|
1581
|
+
renewed_at: checkedAt,
|
|
1582
|
+
expiration: String(options.anonymousAccessExpiration || options.anonymous_access_expiration || DEVTUNNEL_MAX_EXPIRATION),
|
|
1583
|
+
next_renewal_at: isoAfter(checkedAt, DEVTUNNEL_RENEWAL_INTERVAL_MS),
|
|
1584
|
+
list_error: listError,
|
|
1585
|
+
checked_at: checkedAt,
|
|
1586
|
+
}, options);
|
|
1587
|
+
persistManagedTunnelAccess(tunnelId, access, options);
|
|
1588
|
+
return access;
|
|
1589
|
+
} catch (err) {
|
|
1590
|
+
if (ignoreAlreadyExists(err)) {
|
|
1591
|
+
const access = managedTunnelAccessSummary({
|
|
1592
|
+
mode: 'ctm_authenticated',
|
|
1593
|
+
already_configured: true,
|
|
1594
|
+
renewal_due: false,
|
|
1595
|
+
list_error: listError,
|
|
1596
|
+
checked_at: checkedAt,
|
|
1597
|
+
}, options);
|
|
1598
|
+
persistManagedTunnelAccess(tunnelId, access, options);
|
|
1599
|
+
return access;
|
|
1600
|
+
}
|
|
1601
|
+
const message = String(err?.stderr || err?.message || err || 'Could not enable CTM-authenticated Microsoft tunnel access.').slice(0, 800);
|
|
1602
|
+
const access = managedTunnelAccessSummary({
|
|
1603
|
+
mode: 'ctm_authenticated',
|
|
1604
|
+
checked_at: checkedAt,
|
|
1605
|
+
list_error: listError,
|
|
1606
|
+
error: message,
|
|
1607
|
+
}, options);
|
|
1608
|
+
persistManagedTunnelAccess(tunnelId, access, options);
|
|
1609
|
+
throw err;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
async function ensureMicrosoftTunnelConnectAccess(tunnelId, options = {}) {
|
|
1614
|
+
const accessMode = resolveMicrosoftTunnelAccessMode(options.input || {}, options);
|
|
1615
|
+
// Default is the app-gated (ctm_authenticated) anonymous-connect ACE so the
|
|
1616
|
+
// phone PWA + WebSocket can reach CTM; CTM's own passkey/device-token auth is
|
|
1617
|
+
// the gate. Only take the private path when private was a deliberate opt-in
|
|
1618
|
+
// (resolveMicrosoftTunnelAccessMode already migrated stale persisted private
|
|
1619
|
+
// back to ctm_authenticated).
|
|
1620
|
+
if (accessMode === 'private_microsoft') {
|
|
1621
|
+
return ensurePrivateConnectAccess(tunnelId, { ...options, accessMode: 'private_microsoft' });
|
|
1622
|
+
}
|
|
1623
|
+
return ensureCtmAuthenticatedConnectAccess(tunnelId, { ...options, accessMode: 'ctm_authenticated' });
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
async function resetMicrosoftDevTunnelPrivateAccess(input = {}, options = {}) {
|
|
1627
|
+
// This is the explicit, deliberate "switch to private" action, so it carries the
|
|
1628
|
+
// private opt-in flag — otherwise the follow-up detect/inspect would resolve the
|
|
1629
|
+
// mode without it and migrate the just-set private back to the app-gated default.
|
|
1630
|
+
const mergedOptions = {
|
|
1631
|
+
...options,
|
|
1632
|
+
accessMode: 'private_microsoft',
|
|
1633
|
+
allowPrivateAccess: true,
|
|
1634
|
+
input: { ...(options.input || {}), ...(input || {}), access_mode: 'private_microsoft' },
|
|
1635
|
+
};
|
|
1636
|
+
const state = loadManagedTunnelState(mergedOptions) || {};
|
|
1637
|
+
const tunnelId = normalizeTunnelId(input.tunnel_id || input.tunnelId || state.tunnel_id || '');
|
|
1638
|
+
if (!tunnelId) {
|
|
1639
|
+
return {
|
|
1640
|
+
ok: false,
|
|
1641
|
+
error_code: 'microsoft_tunnel_missing',
|
|
1642
|
+
error: 'No managed Microsoft Dev Tunnel exists yet.',
|
|
1643
|
+
setup: await detectMicrosoftDevTunnelSetup(mergedOptions).catch(() => null),
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
const login = await detectLoginStatus(mergedOptions);
|
|
1647
|
+
if (login.installed === false || !login.signed_in) {
|
|
1648
|
+
return {
|
|
1649
|
+
ok: false,
|
|
1650
|
+
error_code: login.installed === false ? 'devtunnel_cli_missing' : 'microsoft_tunnel_sign_in_required',
|
|
1651
|
+
error: login.installed === false
|
|
1652
|
+
? 'Microsoft devtunnel CLI is not installed or not on PATH.'
|
|
1653
|
+
: 'Sign in to Microsoft Dev Tunnels on this Mac before resetting tunnel access.',
|
|
1654
|
+
setup: await detectMicrosoftDevTunnelSetup(mergedOptions).catch(() => null),
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
try {
|
|
1658
|
+
const access = await ensurePrivateConnectAccess(tunnelId, mergedOptions);
|
|
1659
|
+
const managed = await detectManagedMicrosoftDevTunnel(mergedOptions);
|
|
1660
|
+
return {
|
|
1661
|
+
ok: true,
|
|
1662
|
+
tunnel_id: tunnelId,
|
|
1663
|
+
access,
|
|
1664
|
+
managed_tunnel: { ...managed, access },
|
|
1665
|
+
setup: await detectMicrosoftDevTunnelSetup(mergedOptions),
|
|
1666
|
+
};
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
return {
|
|
1669
|
+
ok: false,
|
|
1670
|
+
error_code: 'microsoft_tunnel_private_reset_failed',
|
|
1671
|
+
error: String(err?.stderr || err?.message || err || 'Could not reset Microsoft tunnel access to private.').slice(0, 800),
|
|
1672
|
+
setup: await detectMicrosoftDevTunnelSetup(mergedOptions).catch(() => null),
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
async function renewPersistentTunnelExpiration(tunnelId, options = {}) {
|
|
1678
|
+
const checkedAt = new Date().toISOString();
|
|
1679
|
+
try {
|
|
1680
|
+
await execDevTunnel(devTunnelUpdateArgs(tunnelId, options), options);
|
|
1681
|
+
const renewal = {
|
|
1682
|
+
ok: true,
|
|
1683
|
+
renewed: true,
|
|
1684
|
+
renewed_at: checkedAt,
|
|
1685
|
+
expiration: String(options.tunnelExpiration || options.tunnel_expiration || DEVTUNNEL_MAX_EXPIRATION),
|
|
1686
|
+
next_renewal_at: isoAfter(checkedAt, DEVTUNNEL_RENEWAL_INTERVAL_MS),
|
|
1687
|
+
error: '',
|
|
1688
|
+
};
|
|
1689
|
+
persistManagedTunnelRenewal(tunnelId, renewal, options);
|
|
1690
|
+
return renewal;
|
|
1691
|
+
} catch (err) {
|
|
1692
|
+
const renewal = {
|
|
1693
|
+
ok: false,
|
|
1694
|
+
renewed: false,
|
|
1695
|
+
checked_at: checkedAt,
|
|
1696
|
+
expiration: String(options.tunnelExpiration || options.tunnel_expiration || DEVTUNNEL_MAX_EXPIRATION),
|
|
1697
|
+
error: String(err?.stderr || err?.message || err || 'Could not renew Microsoft tunnel expiration.').slice(0, 800),
|
|
1698
|
+
};
|
|
1699
|
+
persistManagedTunnelRenewal(tunnelId, renewal, options);
|
|
1700
|
+
return renewal;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
function createdTunnelRenewal(options = {}) {
|
|
1705
|
+
const checkedAt = new Date().toISOString();
|
|
1706
|
+
return {
|
|
1707
|
+
ok: true,
|
|
1708
|
+
created: true,
|
|
1709
|
+
renewed: true,
|
|
1710
|
+
renewed_at: checkedAt,
|
|
1711
|
+
expiration: String(options.tunnelExpiration || options.tunnel_expiration || DEVTUNNEL_MAX_EXPIRATION),
|
|
1712
|
+
next_renewal_at: isoAfter(checkedAt, DEVTUNNEL_RENEWAL_INTERVAL_MS),
|
|
1713
|
+
error: '',
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1207
1717
|
async function ensurePersistentTunnel(tunnelId, options = {}) {
|
|
1718
|
+
const previousState = loadManagedTunnelState(options) || {};
|
|
1719
|
+
let created = false;
|
|
1208
1720
|
try {
|
|
1209
|
-
await execDevTunnel(['create', tunnelId, '--expiration',
|
|
1721
|
+
await execDevTunnel(['create', tunnelId, '--expiration', String(options.tunnelExpiration || options.tunnel_expiration || DEVTUNNEL_MAX_EXPIRATION)], options);
|
|
1722
|
+
created = true;
|
|
1210
1723
|
} catch (err) {
|
|
1211
1724
|
if (!ignoreAlreadyExists(err)) throw err;
|
|
1212
1725
|
}
|
|
1726
|
+
const renewalDue = options.renewTunnel === true || options.renew_tunnel === true || microsoftTunnelTunnelRenewalDue(previousState, options);
|
|
1727
|
+
const tunnelRenewal = created ? createdTunnelRenewal(options) : (renewalDue ? await renewPersistentTunnelExpiration(tunnelId, options) : {
|
|
1728
|
+
ok: true,
|
|
1729
|
+
renewed: false,
|
|
1730
|
+
expiration: previousState.tunnel_expiration || String(options.tunnelExpiration || options.tunnel_expiration || DEVTUNNEL_MAX_EXPIRATION),
|
|
1731
|
+
renewed_at: previousState.last_tunnel_renewed_at || '',
|
|
1732
|
+
next_renewal_at: previousState.next_tunnel_renewal_at || '',
|
|
1733
|
+
error: previousState.tunnel_renewal_error || '',
|
|
1734
|
+
});
|
|
1213
1735
|
try {
|
|
1214
1736
|
await execDevTunnel(['port', 'create', tunnelId, '-p', String(options.port || 3456), '--protocol', 'http'], options);
|
|
1215
1737
|
} catch (err) {
|
|
@@ -1221,8 +1743,22 @@ async function ensurePersistentTunnel(tunnelId, options = {}) {
|
|
|
1221
1743
|
}
|
|
1222
1744
|
}
|
|
1223
1745
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1746
|
+
// Fault-isolate access configuration from tunnel exposure. A transient
|
|
1747
|
+
// `devtunnel access` failure must NOT abort hosting the tunnel — CTM's app-layer
|
|
1748
|
+
// auth still gates every request, and a missing ACE at worst costs a one-time
|
|
1749
|
+
// sign-in, never a full outage. Surface the error in the access summary instead.
|
|
1750
|
+
let access;
|
|
1751
|
+
try {
|
|
1752
|
+
const accessRenewalDue = options.renewAccess === true || options.renew_access === true || microsoftTunnelAccessRenewalDue(previousState, options);
|
|
1753
|
+
access = await ensureMicrosoftTunnelConnectAccess(tunnelId, { ...options, renewAccess: accessRenewalDue });
|
|
1754
|
+
} catch (err) {
|
|
1755
|
+
access = managedTunnelAccessSummary({
|
|
1756
|
+
mode: resolveMicrosoftTunnelAccessMode(options.input || {}, options),
|
|
1757
|
+
checked_at: new Date().toISOString(),
|
|
1758
|
+
error: String(err?.stderr || err?.message || err || 'tunnel access configuration failed').slice(0, 800),
|
|
1759
|
+
}, options);
|
|
1760
|
+
}
|
|
1761
|
+
return { access, tunnel_renewal: tunnelRenewal };
|
|
1226
1762
|
}
|
|
1227
1763
|
|
|
1228
1764
|
function waitForHostReady(child, logStreams, options = {}) {
|
|
@@ -1309,6 +1845,16 @@ async function startMicrosoftDevTunnelHostAttempt(tunnelId, options = {}) {
|
|
|
1309
1845
|
throw hostErr;
|
|
1310
1846
|
}
|
|
1311
1847
|
const runCommand = command('devtunnel', args).display;
|
|
1848
|
+
const tunnelRenewal = options.tunnelRenewal || options.tunnel_renewal || {};
|
|
1849
|
+
const lastTunnelRenewedAt = tunnelRenewal.renewed_at || '';
|
|
1850
|
+
const accessRenewedAt = options.access && (options.access.renewed_at || (options.access.created ? options.access.checked_at : '')) || '';
|
|
1851
|
+
const previousEvents = (() => {
|
|
1852
|
+
try {
|
|
1853
|
+
const prev = loadManagedTunnelState(options);
|
|
1854
|
+
return prev && Array.isArray(prev.host_events) ? prev.host_events : [];
|
|
1855
|
+
} catch { return []; }
|
|
1856
|
+
})();
|
|
1857
|
+
const startedAtIso = new Date().toISOString();
|
|
1312
1858
|
const state = {
|
|
1313
1859
|
pid: child && child.pid ? child.pid : null,
|
|
1314
1860
|
tunnel_id: tunnelId,
|
|
@@ -1323,16 +1869,25 @@ async function startMicrosoftDevTunnelHostAttempt(tunnelId, options = {}) {
|
|
|
1323
1869
|
header_passthrough: hostHeaderMode === HOST_HEADER_MODE_PASSTHROUGH,
|
|
1324
1870
|
header_passthrough_fallback: !!options.headerPassthroughFallback,
|
|
1325
1871
|
header_passthrough_fallback_reason: options.headerPassthroughFallbackReason || '',
|
|
1326
|
-
started_at:
|
|
1872
|
+
started_at: startedAtIso,
|
|
1327
1873
|
stopped_at: '',
|
|
1328
1874
|
last_restore_failed_at: '',
|
|
1329
1875
|
restore_error: '',
|
|
1876
|
+
host_events: [...previousEvents, { at: startedAtIso, type: 'host_started', detail: urls.origin || '' }].slice(-HOST_EVENT_LIMIT),
|
|
1330
1877
|
access_mode: options.access && options.access.mode || '',
|
|
1331
1878
|
access: options.access || null,
|
|
1332
1879
|
last_access_check_at: options.access && options.access.checked_at || '',
|
|
1880
|
+
last_access_renewed_at: accessRenewedAt,
|
|
1881
|
+
next_access_renewal_at: options.access && options.access.next_renewal_at || '',
|
|
1882
|
+
access_expiration: options.access && options.access.expiration || '',
|
|
1333
1883
|
access_error: options.access && options.access.error || '',
|
|
1884
|
+
last_tunnel_renewed_at: lastTunnelRenewedAt,
|
|
1885
|
+
next_tunnel_renewal_at: tunnelRenewal.next_renewal_at || '',
|
|
1886
|
+
tunnel_expiration: tunnelRenewal.expiration || '',
|
|
1887
|
+
tunnel_renewal_error: tunnelRenewal.error || '',
|
|
1334
1888
|
};
|
|
1335
1889
|
writeManagedTunnelState(state, options);
|
|
1890
|
+
watchDevTunnelHostOutput(child, options);
|
|
1336
1891
|
return { configured: true, running: !!state.pid, started: true, ...state, state_path: managedStatePath(options) };
|
|
1337
1892
|
}
|
|
1338
1893
|
|
|
@@ -1366,9 +1921,9 @@ async function applyMicrosoftDevTunnelSetup(input = {}, options = {}) {
|
|
|
1366
1921
|
const previous = loadManagedTunnelState(options) || {};
|
|
1367
1922
|
const previousProcess = previous.pid ? await inspectManagedTunnelProcess(previous, options) : null;
|
|
1368
1923
|
if (previousProcess && managedTunnelProcessUsable(previousProcess, previous) && previous.origin) {
|
|
1369
|
-
let
|
|
1924
|
+
let persistent = null;
|
|
1370
1925
|
try {
|
|
1371
|
-
|
|
1926
|
+
persistent = await ensurePersistentTunnel(previous.tunnel_id, { ...options, input });
|
|
1372
1927
|
} catch (err) {
|
|
1373
1928
|
return {
|
|
1374
1929
|
ok: false,
|
|
@@ -1377,6 +1932,7 @@ async function applyMicrosoftDevTunnelSetup(input = {}, options = {}) {
|
|
|
1377
1932
|
setup: detected,
|
|
1378
1933
|
};
|
|
1379
1934
|
}
|
|
1935
|
+
const access = persistent && persistent.access;
|
|
1380
1936
|
const managed = await detectManagedMicrosoftDevTunnel(options);
|
|
1381
1937
|
return {
|
|
1382
1938
|
ok: true,
|
|
@@ -1385,7 +1941,14 @@ async function applyMicrosoftDevTunnelSetup(input = {}, options = {}) {
|
|
|
1385
1941
|
origin: managed.origin,
|
|
1386
1942
|
mobile_url: managed.mobile_url,
|
|
1387
1943
|
inspect_url: managed.inspect_url,
|
|
1388
|
-
managed_tunnel: {
|
|
1944
|
+
managed_tunnel: {
|
|
1945
|
+
...managed,
|
|
1946
|
+
access,
|
|
1947
|
+
access_mode: access.mode,
|
|
1948
|
+
last_access_check_at: access.checked_at,
|
|
1949
|
+
access_error: access.error || '',
|
|
1950
|
+
tunnel_renewal: persistent.tunnel_renewal,
|
|
1951
|
+
},
|
|
1389
1952
|
traffic: getTrafficInspection(),
|
|
1390
1953
|
};
|
|
1391
1954
|
}
|
|
@@ -1396,8 +1959,12 @@ async function applyMicrosoftDevTunnelSetup(input = {}, options = {}) {
|
|
|
1396
1959
|
const requestedTunnelId = normalizeTunnelId(input.tunnel_id || input.tunnelId || previous.tunnel_id || '');
|
|
1397
1960
|
const tunnelId = requestedTunnelId || createTunnelId();
|
|
1398
1961
|
try {
|
|
1399
|
-
const persistent = await ensurePersistentTunnel(tunnelId, options);
|
|
1400
|
-
const managed = await startMicrosoftDevTunnelHost(tunnelId, {
|
|
1962
|
+
const persistent = await ensurePersistentTunnel(tunnelId, { ...options, input });
|
|
1963
|
+
const managed = await startMicrosoftDevTunnelHost(tunnelId, {
|
|
1964
|
+
...options,
|
|
1965
|
+
access: persistent.access,
|
|
1966
|
+
tunnelRenewal: persistent.tunnel_renewal,
|
|
1967
|
+
});
|
|
1401
1968
|
return {
|
|
1402
1969
|
ok: true,
|
|
1403
1970
|
reused: false,
|
|
@@ -1428,16 +1995,31 @@ async function restoreMicrosoftDevTunnelHost(options = {}) {
|
|
|
1428
1995
|
if (managed.running && options.forceRestart === true) {
|
|
1429
1996
|
killProcessTree(state.pid, options);
|
|
1430
1997
|
} else if (managedTunnelProcessUsable(managed, state)) {
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1998
|
+
const due = microsoftTunnelRenewalDue(state, options);
|
|
1999
|
+
let persistent = null;
|
|
2000
|
+
if (options.repairAccess === true || options.renewTunnel === true || options.renew_tunnel === true || options.renewAccess === true || options.renew_access === true || due.tunnel || due.access) {
|
|
2001
|
+
persistent = await ensurePersistentTunnel(state.tunnel_id, {
|
|
2002
|
+
...options,
|
|
2003
|
+
accessMode: state.access_mode || state.access?.mode || options.accessMode || options.access_mode,
|
|
2004
|
+
renewTunnel: options.renewTunnel === true || options.renew_tunnel === true || due.tunnel,
|
|
2005
|
+
renewAccess: options.renewAccess === true || options.renew_access === true || due.access,
|
|
2006
|
+
});
|
|
1434
2007
|
}
|
|
1435
2008
|
const latest = await detectManagedMicrosoftDevTunnel(options);
|
|
1436
2009
|
return {
|
|
1437
2010
|
ok: true,
|
|
1438
2011
|
restored: false,
|
|
1439
2012
|
already_running: true,
|
|
1440
|
-
managed_tunnel:
|
|
2013
|
+
managed_tunnel: persistent && persistent.access
|
|
2014
|
+
? {
|
|
2015
|
+
...latest,
|
|
2016
|
+
access: persistent.access,
|
|
2017
|
+
access_mode: persistent.access.mode,
|
|
2018
|
+
last_access_check_at: persistent.access.checked_at,
|
|
2019
|
+
access_error: persistent.access.error || '',
|
|
2020
|
+
tunnel_renewal: persistent.tunnel_renewal,
|
|
2021
|
+
}
|
|
2022
|
+
: latest,
|
|
1441
2023
|
};
|
|
1442
2024
|
}
|
|
1443
2025
|
if (managed.running && !managedTunnelProcessUsable(managed, state)) {
|
|
@@ -1475,8 +2057,15 @@ async function restoreMicrosoftDevTunnelHost(options = {}) {
|
|
|
1475
2057
|
}
|
|
1476
2058
|
|
|
1477
2059
|
try {
|
|
1478
|
-
const persistent = await ensurePersistentTunnel(tunnelId,
|
|
1479
|
-
|
|
2060
|
+
const persistent = await ensurePersistentTunnel(tunnelId, {
|
|
2061
|
+
...options,
|
|
2062
|
+
accessMode: state.access_mode || state.access?.mode || options.accessMode || options.access_mode,
|
|
2063
|
+
});
|
|
2064
|
+
const restored = await startMicrosoftDevTunnelHost(tunnelId, {
|
|
2065
|
+
...options,
|
|
2066
|
+
access: persistent.access,
|
|
2067
|
+
tunnelRenewal: persistent.tunnel_renewal,
|
|
2068
|
+
});
|
|
1480
2069
|
return { ok: true, restored: true, managed_tunnel: restored };
|
|
1481
2070
|
} catch (err) {
|
|
1482
2071
|
const nextState = {
|
|
@@ -1521,11 +2110,25 @@ async function checkMicrosoftDevTunnelAvailability(options = {}) {
|
|
|
1521
2110
|
await annotateWatchdogState({ last_watchdog_check_at: checkedAt, watchdog_error: '' }, options);
|
|
1522
2111
|
if (managedTunnelProcessUsable(before, state)) {
|
|
1523
2112
|
let managed = before;
|
|
1524
|
-
|
|
2113
|
+
const due = microsoftTunnelRenewalDue(state, options);
|
|
2114
|
+
if (options.repairAccess === true || options.renewTunnel === true || options.renew_tunnel === true || options.renewAccess === true || options.renew_access === true || due.tunnel || due.access) {
|
|
1525
2115
|
try {
|
|
1526
|
-
const
|
|
2116
|
+
const persistent = await ensurePersistentTunnel(state.tunnel_id, {
|
|
2117
|
+
...options,
|
|
2118
|
+
accessMode: state.access_mode || state.access?.mode || options.accessMode || options.access_mode,
|
|
2119
|
+
renewTunnel: options.renewTunnel === true || options.renew_tunnel === true || due.tunnel,
|
|
2120
|
+
renewAccess: options.renewAccess === true || options.renew_access === true || due.access,
|
|
2121
|
+
});
|
|
1527
2122
|
const latest = await detectManagedMicrosoftDevTunnel(options);
|
|
1528
|
-
|
|
2123
|
+
const access = persistent.access;
|
|
2124
|
+
managed = {
|
|
2125
|
+
...latest,
|
|
2126
|
+
access,
|
|
2127
|
+
access_mode: access.mode,
|
|
2128
|
+
last_access_check_at: access.checked_at,
|
|
2129
|
+
access_error: access.error || '',
|
|
2130
|
+
tunnel_renewal: persistent.tunnel_renewal,
|
|
2131
|
+
};
|
|
1529
2132
|
} catch (err) {
|
|
1530
2133
|
const error = String(err?.stderr || err?.message || err || 'Microsoft tunnel access repair failed.').slice(0, 800);
|
|
1531
2134
|
await annotateWatchdogState({ last_watchdog_check_at: checkedAt, watchdog_error: error, access_error: error }, options);
|
|
@@ -1557,6 +2160,20 @@ async function checkMicrosoftDevTunnelAvailability(options = {}) {
|
|
|
1557
2160
|
};
|
|
1558
2161
|
}
|
|
1559
2162
|
if (probe.blocked_by_tunnel_auth) {
|
|
2163
|
+
if (managed.access?.mode === 'ctm_authenticated') {
|
|
2164
|
+
const error = probe.message || 'Microsoft Dev Tunnels is still requiring provider sign-in even though CTM-authenticated tunnel access is configured.';
|
|
2165
|
+
await annotateWatchdogState({ last_watchdog_check_at: checkedAt, watchdog_error: error }, options);
|
|
2166
|
+
return {
|
|
2167
|
+
ok: false,
|
|
2168
|
+
checked: true,
|
|
2169
|
+
recovered: false,
|
|
2170
|
+
reason: 'unexpected_tunnel_auth_gate',
|
|
2171
|
+
error,
|
|
2172
|
+
public_probe: probe,
|
|
2173
|
+
keep_awake: await getMicrosoftDevTunnelKeepAwakeStatus(options),
|
|
2174
|
+
managed_tunnel: managed,
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
1560
2177
|
await annotateWatchdogState({ last_watchdog_check_at: checkedAt, watchdog_error: '' }, options);
|
|
1561
2178
|
return {
|
|
1562
2179
|
ok: true,
|
|
@@ -1704,6 +2321,90 @@ function loginStateIsReusable(state, provider, options = {}) {
|
|
|
1704
2321
|
return now - started < Number(options.loginReuseMs || LOGIN_REUSE_MS);
|
|
1705
2322
|
}
|
|
1706
2323
|
|
|
2324
|
+
async function logoutMicrosoftDevTunnelUser(options = {}) {
|
|
2325
|
+
const previous = loadLoginProcessState(options);
|
|
2326
|
+
if (previous && previous.running && pidIsRunning(previous.pid)) {
|
|
2327
|
+
stopLoginProcessState(previous, options);
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
const execFile = options.execFile || defaultExecFile;
|
|
2331
|
+
const args = ['user', 'logout'];
|
|
2332
|
+
// Keep the provider the user last signed in with so the next sign-in (and the
|
|
2333
|
+
// one-click Reconnect action) defaults to the same account type.
|
|
2334
|
+
const previousProvider = loginStateProvider(previous || {});
|
|
2335
|
+
const baseState = {
|
|
2336
|
+
...(previous || {}),
|
|
2337
|
+
running: false,
|
|
2338
|
+
pid: null,
|
|
2339
|
+
provider: previousProvider,
|
|
2340
|
+
provider_label: loginProviderLabel(previousProvider),
|
|
2341
|
+
command: command('devtunnel', args).display,
|
|
2342
|
+
login_url: '',
|
|
2343
|
+
device_code: '',
|
|
2344
|
+
error: '',
|
|
2345
|
+
error_code: '',
|
|
2346
|
+
signed_out_at: new Date().toISOString(),
|
|
2347
|
+
};
|
|
2348
|
+
try {
|
|
2349
|
+
const result = await execFile('devtunnel', args, {
|
|
2350
|
+
encoding: 'utf8',
|
|
2351
|
+
timeout: options.commandTimeoutMs || 15000,
|
|
2352
|
+
maxBuffer: 512 * 1024,
|
|
2353
|
+
});
|
|
2354
|
+
const stoppedTunnel = options.stopTunnel === false ? null : await stopMicrosoftDevTunnel(options).catch((err) => ({
|
|
2355
|
+
ok: false,
|
|
2356
|
+
error: String(err?.message || err || 'Could not stop existing Microsoft tunnel').slice(0, 800),
|
|
2357
|
+
}));
|
|
2358
|
+
const nextState = {
|
|
2359
|
+
...baseState,
|
|
2360
|
+
stdout: String(result?.stdout || '').trim().slice(0, 800),
|
|
2361
|
+
stderr: String(result?.stderr || '').trim().slice(0, 800),
|
|
2362
|
+
};
|
|
2363
|
+
writeLoginProcessState(nextState, options);
|
|
2364
|
+
return {
|
|
2365
|
+
ok: true,
|
|
2366
|
+
already_signed_out: false,
|
|
2367
|
+
stopped_tunnel: stoppedTunnel,
|
|
2368
|
+
logout: { ...nextState, state_path: loginStatePath(options) },
|
|
2369
|
+
setup: await detectMicrosoftDevTunnelSetup(options),
|
|
2370
|
+
progress: getMicrosoftDevTunnelProgress(options),
|
|
2371
|
+
};
|
|
2372
|
+
} catch (err) {
|
|
2373
|
+
const text = String(`${err?.stdout || ''}\n${err?.stderr || ''}\n${err?.message || ''}`);
|
|
2374
|
+
const alreadySignedOut = /not\s+(?:logged|signed)\s+in|no\s+user|login\s+required/i.test(text);
|
|
2375
|
+
const nextState = {
|
|
2376
|
+
...baseState,
|
|
2377
|
+
already_signed_out: alreadySignedOut,
|
|
2378
|
+
error_code: alreadySignedOut ? '' : (err?.code === 'ENOENT' ? 'devtunnel_cli_missing' : 'devtunnel_logout_failed'),
|
|
2379
|
+
error: alreadySignedOut ? '' : text.trim().slice(0, 800),
|
|
2380
|
+
};
|
|
2381
|
+
writeLoginProcessState(nextState, options);
|
|
2382
|
+
if (alreadySignedOut) {
|
|
2383
|
+
const stoppedTunnel = options.stopTunnel === false ? null : await stopMicrosoftDevTunnel(options).catch((stopErr) => ({
|
|
2384
|
+
ok: false,
|
|
2385
|
+
error: String(stopErr?.message || stopErr || 'Could not stop existing Microsoft tunnel').slice(0, 800),
|
|
2386
|
+
}));
|
|
2387
|
+
return {
|
|
2388
|
+
ok: true,
|
|
2389
|
+
already_signed_out: true,
|
|
2390
|
+
stopped_tunnel: stoppedTunnel,
|
|
2391
|
+
logout: { ...nextState, state_path: loginStatePath(options) },
|
|
2392
|
+
setup: await detectMicrosoftDevTunnelSetup(options),
|
|
2393
|
+
progress: getMicrosoftDevTunnelProgress(options),
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
return {
|
|
2397
|
+
ok: false,
|
|
2398
|
+
already_signed_out: false,
|
|
2399
|
+
error_code: nextState.error_code,
|
|
2400
|
+
error: nextState.error || 'Microsoft Dev Tunnels sign-out failed.',
|
|
2401
|
+
logout: { ...nextState, state_path: loginStatePath(options) },
|
|
2402
|
+
setup: await detectMicrosoftDevTunnelSetup(options).catch(() => null),
|
|
2403
|
+
progress: getMicrosoftDevTunnelProgress(options),
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
|
|
1707
2408
|
function startMicrosoftDevTunnelLogin(input = {}, options = {}) {
|
|
1708
2409
|
const fsImpl = options.fs || fs;
|
|
1709
2410
|
fsImpl.mkdirSync(setupDir(options), { recursive: true, mode: 0o700 });
|
|
@@ -1711,9 +2412,15 @@ function startMicrosoftDevTunnelLogin(input = {}, options = {}) {
|
|
|
1711
2412
|
fsImpl.mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
1712
2413
|
const logPath = path.join(logDir, 'devtunnel-login.log');
|
|
1713
2414
|
const errorLogPath = path.join(logDir, 'devtunnel-login.err');
|
|
1714
|
-
|
|
2415
|
+
// No explicit provider (e.g. the one-click Reconnect health action) → stick to
|
|
2416
|
+
// the last-used provider instead of silently switching account types.
|
|
2417
|
+
const requestedProvider = String(input.provider || '').trim() || (input.github === true ? 'github' : '');
|
|
2418
|
+
const provider = requestedProvider
|
|
2419
|
+
? normalizeLoginProvider({ provider: requestedProvider })
|
|
2420
|
+
: loginStateProvider(loadLoginProcessState(options) || {});
|
|
2421
|
+
const forceNew = input.force_new === true || input.forceNew === true || input.regenerate === true;
|
|
1715
2422
|
const previous = loadLoginProcessState(options);
|
|
1716
|
-
if (loginStateIsReusable(previous, provider, options)) {
|
|
2423
|
+
if (!forceNew && loginStateIsReusable(previous, provider, options)) {
|
|
1717
2424
|
const login = processProgressFromState(previous, options);
|
|
1718
2425
|
return {
|
|
1719
2426
|
ok: true,
|
|
@@ -1777,6 +2484,11 @@ function startMicrosoftDevTunnelLogin(input = {}, options = {}) {
|
|
|
1777
2484
|
update({ running: false, exit_code: code, signal: signal || '', finished_at: new Date().toISOString() });
|
|
1778
2485
|
try { if (out) out.end(); } catch {}
|
|
1779
2486
|
try { if (err) err.end(); } catch {}
|
|
2487
|
+
if (code === 0) {
|
|
2488
|
+
checkMicrosoftDevTunnelLoginStatus({ ...options, includeSetup: false }).catch((loginErr) => {
|
|
2489
|
+
update({ login_check_error: String(loginErr?.message || loginErr || 'Could not verify Dev Tunnels sign-in').slice(0, 800) });
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
1780
2492
|
});
|
|
1781
2493
|
return {
|
|
1782
2494
|
ok: true,
|
|
@@ -1786,8 +2498,11 @@ function startMicrosoftDevTunnelLogin(input = {}, options = {}) {
|
|
|
1786
2498
|
}
|
|
1787
2499
|
|
|
1788
2500
|
module.exports = {
|
|
2501
|
+
appendManagedTunnelHostEvent,
|
|
1789
2502
|
applyMicrosoftDevTunnelSetup,
|
|
1790
2503
|
checkMicrosoftDevTunnelAvailability,
|
|
2504
|
+
checkMicrosoftDevTunnelLoginStatus,
|
|
2505
|
+
classifyDevTunnelHostEventText,
|
|
1791
2506
|
clearMicrosoftDevTunnelTraffic,
|
|
1792
2507
|
command,
|
|
1793
2508
|
detectManagedMicrosoftDevTunnel,
|
|
@@ -1798,15 +2513,23 @@ module.exports = {
|
|
|
1798
2513
|
getMicrosoftDevTunnelProgress,
|
|
1799
2514
|
getTrafficInspection,
|
|
1800
2515
|
installMicrosoftDevTunnelCli,
|
|
2516
|
+
inspectMicrosoftTunnelAccess,
|
|
1801
2517
|
inspectKeepAwakeProcess,
|
|
1802
2518
|
startMicrosoftDevTunnelInstall,
|
|
1803
2519
|
inspectManagedTunnelProcess,
|
|
1804
2520
|
loadKeepAwakeState,
|
|
1805
2521
|
loadManagedTunnelState,
|
|
1806
2522
|
loadLoginProcessState,
|
|
2523
|
+
loginProviderArgs,
|
|
2524
|
+
loginProviderLabel,
|
|
2525
|
+
loginStateProvider,
|
|
2526
|
+
logoutMicrosoftDevTunnelUser,
|
|
1807
2527
|
managedTunnelDesiredRunning,
|
|
1808
2528
|
microsoftDevTunnelCommands,
|
|
2529
|
+
microsoftTunnelPrivateAccessForced,
|
|
2530
|
+
normalizeMicrosoftTunnelAccessMode,
|
|
1809
2531
|
normalizeTunnelId,
|
|
2532
|
+
resolveMicrosoftTunnelAccessMode,
|
|
1810
2533
|
parseDevTunnelAccessEntries,
|
|
1811
2534
|
parseDevTunnelUrls,
|
|
1812
2535
|
parseDevTunnelLoginHints,
|
|
@@ -1815,6 +2538,7 @@ module.exports = {
|
|
|
1815
2538
|
pidIsRunning,
|
|
1816
2539
|
probeMicrosoftDevTunnelPublicAccess,
|
|
1817
2540
|
recordMicrosoftDevTunnelTraffic,
|
|
2541
|
+
resetMicrosoftDevTunnelPrivateAccess,
|
|
1818
2542
|
restoreMicrosoftDevTunnelHost,
|
|
1819
2543
|
sanitizeRequestPath,
|
|
1820
2544
|
setMicrosoftDevTunnelKeepAwake,
|