@maudecode/talaria-web 1.18.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_release.json +8 -8
- package/dist/api/auth-raw.js +16 -16
- package/dist/api/auth-raw.js.map +1 -1
- package/dist/api/automation-raw.d.ts +2 -2
- package/dist/api/automation-raw.js +93 -38
- package/dist/api/automation-raw.js.map +1 -1
- package/dist/api/automation-router.d.ts +99 -17
- package/dist/api/automation-router.js +22 -14
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +1433 -37
- package/dist/api/chat-router.js +205 -90
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +6 -2
- package/dist/api/git-router.js +30 -30
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.d.ts +3 -3
- package/dist/api/raw-routes.js +76 -62
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +5 -5
- package/dist/api/router.js +22 -22
- package/dist/api/router.js.map +1 -1
- package/dist/api/session-visibility.d.ts +4 -4
- package/dist/api/session-visibility.js +26 -16
- package/dist/api/session-visibility.js.map +1 -1
- package/dist/api/sessions-router.d.ts +2635 -151
- package/dist/api/sessions-router.js +193 -177
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +450 -11
- package/dist/api/settings-router.js +102 -49
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.d.ts +2 -2
- package/dist/api/sse-routes.js +102 -80
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-raw.d.ts +5 -0
- package/dist/api/tools-raw.js +58 -33
- package/dist/api/tools-raw.js.map +1 -1
- package/dist/api/tools-router.d.ts +963 -109
- package/dist/api/tools-router.js +426 -72
- package/dist/api/tools-router.js.map +1 -1
- package/dist/api/transcribe-stream.d.ts +16 -0
- package/dist/api/transcribe-stream.js +172 -0
- package/dist/api/transcribe-stream.js.map +1 -0
- package/dist/app.d.ts +4 -1
- package/dist/app.js +80 -57
- package/dist/app.js.map +1 -1
- package/dist/auth/crypto.d.ts +3 -0
- package/dist/auth/crypto.js +18 -0
- package/dist/auth/crypto.js.map +1 -1
- package/dist/auth/gate.js +14 -9
- package/dist/auth/gate.js.map +1 -1
- package/dist/auth/passkeys.d.ts +15 -14
- package/dist/auth/passkeys.js +89 -91
- package/dist/auth/passkeys.js.map +1 -1
- package/dist/auth/store.d.ts +17 -7
- package/dist/auth/store.js +93 -52
- package/dist/auth/store.js.map +1 -1
- package/dist/bin/talaria-web.js +12 -81
- package/dist/bin/talaria-web.js.map +1 -1
- package/dist/cli/ctl.js +8 -2
- package/dist/cli/ctl.js.map +1 -1
- package/dist/cli/dotenv.d.ts +5 -0
- package/dist/cli/dotenv.js +14 -2
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/cli/launcher.js +5 -5
- package/dist/cli/launcher.js.map +1 -1
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +90 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/supervise.d.ts +24 -0
- package/dist/cli/supervise.js +147 -6
- package/dist/cli/supervise.js.map +1 -1
- package/dist/config/agent-config.d.ts +37 -11
- package/dist/config/agent-config.js +146 -64
- package/dist/config/agent-config.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +14 -5
- package/dist/config.js.map +1 -1
- package/dist/fs/atomic.d.ts +4 -0
- package/dist/fs/atomic.js +19 -2
- package/dist/fs/atomic.js.map +1 -1
- package/dist/fs/stat.d.ts +7 -0
- package/dist/fs/stat.js +27 -0
- package/dist/fs/stat.js.map +1 -0
- package/dist/http/context.d.ts +28 -9
- package/dist/http/context.js +77 -25
- package/dist/http/context.js.map +1 -1
- package/dist/onboarding.d.ts +2 -2
- package/dist/onboarding.js +10 -8
- package/dist/onboarding.js.map +1 -1
- package/dist/profiles/profiles.d.ts +12 -4
- package/dist/profiles/profiles.js +78 -30
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/projects.d.ts +15 -6
- package/dist/projects.js +55 -47
- package/dist/projects.js.map +1 -1
- package/dist/providers/catalog.d.ts +85 -14
- package/dist/providers/catalog.js +502 -146
- package/dist/providers/catalog.js.map +1 -1
- package/dist/providers/env-file.d.ts +10 -3
- package/dist/providers/env-file.js +24 -75
- package/dist/providers/env-file.js.map +1 -1
- package/dist/providers/runtime-env.d.ts +4 -2
- package/dist/providers/runtime-env.js +9 -6
- package/dist/providers/runtime-env.js.map +1 -1
- package/dist/providers/tables.d.ts +3 -1
- package/dist/providers/tables.js +4 -2
- package/dist/providers/tables.js.map +1 -1
- package/dist/redact.js +56 -23
- package/dist/redact.js.map +1 -1
- package/dist/release.d.ts +7 -0
- package/dist/release.js +19 -3
- package/dist/release.js.map +1 -1
- package/dist/runtime.d.ts +5 -1
- package/dist/runtime.js +303 -96
- package/dist/runtime.js.map +1 -1
- package/dist/server.js +31 -18
- package/dist/server.js.map +1 -1
- package/dist/sessions/anchor.d.ts +11 -4
- package/dist/sessions/anchor.js +33 -37
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-events.d.ts +2 -0
- package/dist/sessions/background-events.js +10 -0
- package/dist/sessions/background-events.js.map +1 -0
- package/dist/sessions/background-tasks.d.ts +49 -23
- package/dist/sessions/background-tasks.js +161 -89
- package/dist/sessions/background-tasks.js.map +1 -1
- package/dist/sessions/background-updates.d.ts +15 -8
- package/dist/sessions/background-updates.js +101 -25
- package/dist/sessions/background-updates.js.map +1 -1
- package/dist/sessions/cli-sessions.d.ts +23 -6
- package/dist/sessions/cli-sessions.js +84 -84
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/completions.d.ts +38 -2
- package/dist/sessions/completions.js +348 -77
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/compress.d.ts +8 -0
- package/dist/sessions/compress.js +2 -2
- package/dist/sessions/compress.js.map +1 -1
- package/dist/sessions/drafts.d.ts +14 -10
- package/dist/sessions/drafts.js +38 -43
- package/dist/sessions/drafts.js.map +1 -1
- package/dist/sessions/export.d.ts +2 -0
- package/dist/sessions/export.js +6 -2
- package/dist/sessions/export.js.map +1 -1
- package/dist/sessions/gateway-sessions.d.ts +86 -0
- package/dist/sessions/gateway-sessions.js +914 -0
- package/dist/sessions/gateway-sessions.js.map +1 -0
- package/dist/sessions/gateway-watcher.d.ts +10 -11
- package/dist/sessions/gateway-watcher.js +25 -21
- package/dist/sessions/gateway-watcher.js.map +1 -1
- package/dist/sessions/goals.d.ts +31 -0
- package/dist/sessions/goals.js +75 -0
- package/dist/sessions/goals.js.map +1 -0
- package/dist/sessions/journal.d.ts +5 -79
- package/dist/sessions/journal.js +15 -606
- package/dist/sessions/journal.js.map +1 -1
- package/dist/sessions/list.d.ts +34 -18
- package/dist/sessions/list.js +161 -147
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +77 -13
- package/dist/sessions/merge.js +315 -65
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/notices.d.ts +22 -0
- package/dist/sessions/notices.js +72 -0
- package/dist/sessions/notices.js.map +1 -0
- package/dist/sessions/pending.d.ts +10 -4
- package/dist/sessions/pending.js +83 -7
- package/dist/sessions/pending.js.map +1 -1
- package/dist/sessions/prefix-index.d.ts +85 -0
- package/dist/sessions/prefix-index.js +243 -0
- package/dist/sessions/prefix-index.js.map +1 -0
- package/dist/sessions/recovery.d.ts +4 -7
- package/dist/sessions/recovery.js +100 -283
- package/dist/sessions/recovery.js.map +1 -1
- package/dist/sessions/relay.d.ts +20 -20
- package/dist/sessions/relay.js +205 -127
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +251 -78
- package/dist/sessions/service.js +1510 -564
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +20 -2
- package/dist/sessions/session.js +33 -5
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/shares.d.ts +7 -6
- package/dist/sessions/shares.js +22 -45
- package/dist/sessions/shares.js.map +1 -1
- package/dist/sessions/source-kind.js +1 -0
- package/dist/sessions/source-kind.js.map +1 -1
- package/dist/sessions/state-db-identity.d.ts +18 -0
- package/dist/sessions/state-db-identity.js +67 -0
- package/dist/sessions/state-db-identity.js.map +1 -0
- package/dist/sessions/state-db-worker.js +24 -5
- package/dist/sessions/state-db-worker.js.map +1 -1
- package/dist/sessions/state-db.d.ts +157 -21
- package/dist/sessions/state-db.js +1145 -204
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/state-saved.d.ts +1 -1
- package/dist/sessions/state-saved.js +35 -11
- package/dist/sessions/state-saved.js.map +1 -1
- package/dist/sessions/storage/backend.d.ts +171 -0
- package/dist/sessions/storage/backend.js +19 -0
- package/dist/sessions/storage/backend.js.map +1 -0
- package/dist/sessions/storage/conformance.d.ts +20 -0
- package/dist/sessions/storage/conformance.js +237 -0
- package/dist/sessions/storage/conformance.js.map +1 -0
- package/dist/sessions/storage/document.d.ts +16 -0
- package/dist/sessions/storage/document.js +44 -0
- package/dist/sessions/storage/document.js.map +1 -0
- package/dist/sessions/storage/json-documents.d.ts +25 -0
- package/dist/sessions/storage/json-documents.js +277 -0
- package/dist/sessions/storage/json-documents.js.map +1 -0
- package/dist/sessions/storage/json-files.d.ts +23 -0
- package/dist/sessions/storage/json-files.js +244 -0
- package/dist/sessions/storage/json-files.js.map +1 -0
- package/dist/sessions/storage/json-installation.d.ts +3 -0
- package/dist/sessions/storage/json-installation.js +31 -0
- package/dist/sessions/storage/json-installation.js.map +1 -0
- package/dist/sessions/storage/json-journal.d.ts +113 -0
- package/dist/sessions/storage/json-journal.js +756 -0
- package/dist/sessions/storage/json-journal.js.map +1 -0
- package/dist/sessions/storage/json-recovery.d.ts +4 -0
- package/dist/sessions/storage/json-recovery.js +195 -0
- package/dist/sessions/storage/json-recovery.js.map +1 -0
- package/dist/sessions/storage/json.d.ts +82 -0
- package/dist/sessions/storage/json.js +350 -0
- package/dist/sessions/storage/json.js.map +1 -0
- package/dist/sessions/storage/row.d.ts +6 -0
- package/dist/sessions/storage/row.js +49 -0
- package/dist/sessions/storage/row.js.map +1 -0
- package/dist/sessions/storage/search-text.d.ts +4 -0
- package/dist/sessions/storage/search-text.js +16 -0
- package/dist/sessions/storage/search-text.js.map +1 -0
- package/dist/sessions/store.d.ts +67 -73
- package/dist/sessions/store.js +341 -607
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +12 -7
- package/dist/sessions/streams.js +58 -15
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/titles.js +1 -1
- package/dist/sessions/titles.js.map +1 -1
- package/dist/sessions/todo.d.ts +5 -0
- package/dist/sessions/todo.js +28 -21
- package/dist/sessions/todo.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +8 -5
- package/dist/sessions/tool-display.js +28 -16
- package/dist/sessions/tool-display.js.map +1 -1
- package/dist/sessions/turn-context.d.ts +10 -1
- package/dist/sessions/turn-context.js +13 -6
- package/dist/sessions/turn-context.js.map +1 -1
- package/dist/sessions/turn.d.ts +110 -22
- package/dist/sessions/turn.js +918 -380
- package/dist/sessions/turn.js.map +1 -1
- package/dist/sessions/wakeup-pause.d.ts +8 -13
- package/dist/sessions/wakeup-pause.js +31 -49
- package/dist/sessions/wakeup-pause.js.map +1 -1
- package/dist/sessions/window.js +13 -12
- package/dist/sessions/window.js.map +1 -1
- package/dist/settings.d.ts +19 -7
- package/dist/settings.js +179 -183
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/client.d.ts +41 -4
- package/dist/sidecar/client.js +192 -42
- package/dist/sidecar/client.js.map +1 -1
- package/dist/sidecar/discover.d.ts +2 -1
- package/dist/sidecar/discover.js +93 -10
- package/dist/sidecar/discover.js.map +1 -1
- package/dist/sidecar/env.d.ts +4 -0
- package/dist/sidecar/env.js +3 -0
- package/dist/sidecar/env.js.map +1 -0
- package/dist/sidecar/fake.d.ts +6 -2
- package/dist/sidecar/fake.js +34 -4
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/spa.d.ts +9 -4
- package/dist/spa.js +36 -12
- package/dist/spa.js.map +1 -1
- package/dist/startup-timing.d.ts +66 -0
- package/dist/startup-timing.js +129 -0
- package/dist/startup-timing.js.map +1 -0
- package/dist/test/env-file.d.ts +5 -0
- package/dist/test/env-file.js +88 -0
- package/dist/test/env-file.js.map +1 -0
- package/dist/test/fs-handles.d.ts +11 -0
- package/dist/test/fs-handles.js +31 -0
- package/dist/test/fs-handles.js.map +1 -0
- package/dist/test/harness.d.ts +1 -0
- package/dist/test/harness.js +1 -1
- package/dist/test/harness.js.map +1 -1
- package/dist/test/passkeys.d.ts +22 -0
- package/dist/test/passkeys.js +62 -0
- package/dist/test/passkeys.js.map +1 -0
- package/dist/test/storage.d.ts +15 -0
- package/dist/test/storage.js +26 -0
- package/dist/test/storage.js.map +1 -0
- package/dist/tools/commands.d.ts +6 -1
- package/dist/tools/commands.js +8 -2
- package/dist/tools/commands.js.map +1 -1
- package/dist/tools/connectors.d.ts +33 -0
- package/dist/tools/connectors.js +121 -0
- package/dist/tools/connectors.js.map +1 -0
- package/dist/tools/crons.d.ts +46 -5
- package/dist/tools/crons.js +100 -26
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/dashboard-plugins.d.ts +34 -5
- package/dist/tools/dashboard-plugins.js +146 -14
- package/dist/tools/dashboard-plugins.js.map +1 -1
- package/dist/tools/extensions.d.ts +5 -4
- package/dist/tools/extensions.js +213 -207
- package/dist/tools/extensions.js.map +1 -1
- package/dist/tools/gateway-restart.d.ts +82 -0
- package/dist/tools/gateway-restart.js +222 -0
- package/dist/tools/gateway-restart.js.map +1 -0
- package/dist/tools/health.d.ts +28 -4
- package/dist/tools/health.js +171 -50
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/hygiene.d.ts +4 -3
- package/dist/tools/hygiene.js +23 -9
- package/dist/tools/hygiene.js.map +1 -1
- package/dist/tools/insights.d.ts +1 -1
- package/dist/tools/insights.js +2 -2
- package/dist/tools/insights.js.map +1 -1
- package/dist/tools/kanban.d.ts +4 -2
- package/dist/tools/kanban.js +28 -19
- package/dist/tools/kanban.js.map +1 -1
- package/dist/tools/mcp-health.js +74 -24
- package/dist/tools/mcp-health.js.map +1 -1
- package/dist/tools/mcp.js +19 -1
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/memory-masks.d.ts +5 -0
- package/dist/tools/memory-masks.js +256 -0
- package/dist/tools/memory-masks.js.map +1 -0
- package/dist/tools/memory.d.ts +29 -3
- package/dist/tools/memory.js +69 -27
- package/dist/tools/memory.js.map +1 -1
- package/dist/tools/plugin-oauth.d.ts +14 -0
- package/dist/tools/plugin-oauth.js +53 -0
- package/dist/tools/plugin-oauth.js.map +1 -0
- package/dist/tools/plugin-settings.d.ts +15 -0
- package/dist/tools/plugin-settings.js +90 -0
- package/dist/tools/plugin-settings.js.map +1 -0
- package/dist/tools/prune-advice.d.ts +50 -0
- package/dist/tools/prune-advice.js +114 -0
- package/dist/tools/prune-advice.js.map +1 -0
- package/dist/tools/skills.d.ts +13 -5
- package/dist/tools/skills.js +53 -19
- package/dist/tools/skills.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +102 -17
- package/dist/tools/update-notifications.js +400 -82
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/update-rollback.d.ts +11 -0
- package/dist/tools/update-rollback.js +28 -0
- package/dist/tools/update-rollback.js.map +1 -0
- package/dist/tools/updates.d.ts +100 -33
- package/dist/tools/updates.js +1085 -345
- package/dist/tools/updates.js.map +1 -1
- package/dist/util.d.ts +2 -0
- package/dist/util.js +5 -0
- package/dist/util.js.map +1 -1
- package/dist/workspace/git.d.ts +16 -1
- package/dist/workspace/git.js +291 -11
- package/dist/workspace/git.js.map +1 -1
- package/dist/workspace/media.d.ts +12 -9
- package/dist/workspace/media.js +98 -38
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/preview.js +4 -14
- package/dist/workspace/preview.js.map +1 -1
- package/dist/workspace/rollback.d.ts +8 -4
- package/dist/workspace/rollback.js +22 -12
- package/dist/workspace/rollback.js.map +1 -1
- package/dist/workspace/workspaces.d.ts +15 -19
- package/dist/workspace/workspaces.js +55 -127
- package/dist/workspace/workspaces.js.map +1 -1
- package/dist/workspace/worktrees.js +170 -32
- package/dist/workspace/worktrees.js.map +1 -1
- package/package.json +6 -4
- package/sidecar/agent_dependency.json +4 -4
- package/sidecar/agent_support.json +29 -0
- package/sidecar/scripts/record-fixtures.py +12 -3
- package/sidecar/scripts/replay_sidecar.py +8 -0
- package/sidecar/scripts/suite_runner.py +458 -0
- package/sidecar/scripts/test.sh +11 -5
- package/sidecar/talaria_sidecar/__init__.py +6 -1
- package/sidecar/talaria_sidecar/__main__.py +13 -4
- package/sidecar/talaria_sidecar/agent_mutation.py +1111 -0
- package/sidecar/talaria_sidecar/agent_services.py +1116 -0
- package/sidecar/talaria_sidecar/context_detail.py +59 -0
- package/sidecar/talaria_sidecar/dependency_sync.py +85 -0
- package/sidecar/talaria_sidecar/home.py +133 -14
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +211 -41
- package/sidecar/talaria_sidecar/methods/chat.py +1378 -200
- package/sidecar/talaria_sidecar/methods/checkpoints.py +29 -4
- package/sidecar/talaria_sidecar/methods/commands.py +483 -13
- package/sidecar/talaria_sidecar/methods/config.py +195 -6
- package/sidecar/talaria_sidecar/methods/connectors.py +375 -0
- package/sidecar/talaria_sidecar/methods/cron.py +374 -36
- package/sidecar/talaria_sidecar/methods/cron_execution.py +284 -0
- package/sidecar/talaria_sidecar/methods/dashboard_plugins.py +301 -0
- package/sidecar/talaria_sidecar/methods/gateway.py +215 -55
- package/sidecar/talaria_sidecar/methods/gateway_sessions.py +418 -0
- package/sidecar/talaria_sidecar/methods/goals.py +81 -41
- package/sidecar/talaria_sidecar/methods/kanban.py +82 -30
- package/sidecar/talaria_sidecar/methods/memory.py +84 -0
- package/sidecar/talaria_sidecar/methods/oauth.py +144 -13
- package/sidecar/talaria_sidecar/methods/plugin_catalog.py +198 -0
- package/sidecar/talaria_sidecar/methods/plugin_oauth.py +234 -0
- package/sidecar/talaria_sidecar/methods/process.py +229 -16
- package/sidecar/talaria_sidecar/methods/profiles.py +48 -9
- package/sidecar/talaria_sidecar/methods/providers.py +196 -26
- package/sidecar/talaria_sidecar/methods/runtime.py +71 -1
- package/sidecar/talaria_sidecar/methods/skills.py +83 -5
- package/sidecar/talaria_sidecar/methods/state_db.py +405 -112
- package/sidecar/talaria_sidecar/methods/stt.py +159 -78
- package/sidecar/talaria_sidecar/methods/text.py +14 -1
- package/sidecar/talaria_sidecar/methods/toolsets.py +51 -0
- package/sidecar/talaria_sidecar/methods/usage.py +113 -14
- package/sidecar/talaria_sidecar/methods/worktree.py +145 -19
- package/sidecar/talaria_sidecar/methods/write_approval.py +181 -0
- package/sidecar/talaria_sidecar/rpc.py +70 -7
- package/sidecar/talaria_sidecar/runtime.py +122 -14
- package/sidecar/talaria_sidecar/speed.py +47 -0
- package/sidecar/talaria_sidecar/startup_timing.py +119 -0
- package/static/dist/FILES.txt +242 -215
- package/static/dist/THIRD_PARTY_LICENSES.md +6 -22
- package/static/dist/assets/AppShell-3isgvoro.js +8 -0
- package/static/dist/assets/Button-f4n6dapf.js +1 -0
- package/static/dist/assets/ChatPage-k7b6xfax.js +4 -0
- package/static/dist/assets/ConnectTargets-juqvt5lm.js +1 -0
- package/static/dist/assets/Dialog-hta5utr3.js +1 -0
- package/static/dist/assets/ExtensionRoute-zrhjn0wm.js +1 -0
- package/static/dist/assets/Field-b6ez48oz.js +1 -0
- package/static/dist/assets/HubRoute-iwvso2tz.js +2 -0
- package/static/dist/assets/LinkCheckContext-ix6zqqkk.js +1 -0
- package/static/dist/assets/Markdown-jdeth7zq.js +2 -0
- package/static/dist/assets/MarkdownRenderer-fbswsgld.js +302 -0
- package/static/dist/assets/MarkdownRenderer-i9o6nr38.css +1 -0
- package/static/dist/assets/OAuthSignIn-bpd8hom3.js +1 -0
- package/static/dist/assets/ResourceSwitch-odi6dy2o.js +1 -0
- package/static/dist/assets/RightPanel-eu6enlo6.js +1 -0
- package/static/dist/assets/Select-kr07928w.js +1 -0
- package/static/dist/assets/SessionListPanel-etb2i54l.js +1 -0
- package/static/dist/assets/SettingsLayout-eu3xxp23.js +1 -0
- package/static/dist/assets/States-icab19yg.js +1 -0
- package/static/dist/assets/TerminalPanel-hngmsr8b.js +16 -0
- package/static/dist/assets/Toaster-nb2uzjw1.js +1 -0
- package/static/dist/assets/_app-ga1moszl.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-na62087t.js +1 -0
- package/static/dist/assets/_app.index-jypgdqoi.js +1 -0
- package/static/dist/assets/_app.insights-kabejdtm.js +1 -0
- package/static/dist/assets/_app.kanban-m19kfcmx.js +1 -0
- package/static/dist/assets/_app.logs-iw4m96sz.js +1 -0
- package/static/dist/assets/_app.memory-jq5frwcg.js +1 -0
- package/static/dist/assets/_app.profiles-kwa3m7rp.js +1 -0
- package/static/dist/assets/_app.session._sessionId-o5lyeipj.js +1 -0
- package/static/dist/assets/_app.settings-ckl8hdll.js +1 -0
- package/static/dist/assets/_app.settings._section-dtdjk98a.js +3 -0
- package/static/dist/assets/_app.skills-n016rihd.js +1 -0
- package/static/dist/assets/_app.tasks-n8zuqsqe.js +1 -0
- package/static/dist/assets/_app.todos-btew5v2s.js +1 -0
- package/static/dist/assets/_app.workspaces-lnfk95ch.js +1 -0
- package/static/dist/assets/abnfDiagram-VCTEODGH-dttheegb.js +1 -0
- package/static/dist/assets/{angular-html-nu6l31w3.js → angular-html-l28bd5fo.js} +1 -1
- package/static/dist/assets/{angular-ts-fbx4f8mm.js → angular-ts-ffzx1vj0.js} +1 -1
- package/static/dist/assets/{apl-bx2xr7dh.js → apl-b7h7mmy4.js} +1 -1
- package/static/dist/assets/{arc-7popfhbk.js → arc-fk1b21lm.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-ic435919.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-8r3glgiq.js +36 -0
- package/static/dist/assets/archive-il7c6053.js +1 -0
- package/static/dist/assets/{astro-g77i97cb.js → astro-k117fy0t.js} +1 -1
- package/static/dist/assets/back-gu3nu279.js +1 -0
- package/static/dist/assets/{blade-boo0u92y.js → blade-npefsmn8.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-jzthp1wa.js → blockDiagram-I7D4REHJ-jenyos6c.js} +4 -4
- package/static/dist/assets/{c-cat0w3sc.js → c-2umg6kbn.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-mwr67mt8.js → c4Diagram-7LVT6UL2-nzlx7yc4.js} +1 -1
- package/static/dist/assets/cancel-bsnfo36u.js +1 -0
- package/static/dist/assets/channel-dsbtk01y.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-gpq1y228.js → chunk-4HAMMTFA-g0cru63b.js} +20 -20
- package/static/dist/assets/{chunk-75Z2AOVW-ktje58c4.js → chunk-75Z2AOVW-7rkswkjw.js} +1 -1
- package/static/dist/assets/chunk-DU6HZSFF-fn73jzdo.js +128 -0
- package/static/dist/assets/{chunk-F27PBJKO-cmev85ti.js → chunk-F27PBJKO-oey8e72m.js} +1 -1
- package/static/dist/assets/{chunk-FOHPRMQF-opuflp73.js → chunk-FOHPRMQF-ipewusgj.js} +8 -8
- package/static/dist/assets/{chunk-GMAD6QVW-htjgt22y.js → chunk-GMAD6QVW-l2a32wiy.js} +6 -6
- package/static/dist/assets/{chunk-GVQU2GXP-qndvoxl7.js → chunk-GVQU2GXP-o9dwzh8h.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-e5bkd5q6.js → chunk-IMKFNOWR-eblwj20b.js} +5 -5
- package/static/dist/assets/{chunk-L3NEJ4N5-mj1155r6.js → chunk-L3NEJ4N5-o2mufspb.js} +1 -1
- package/static/dist/assets/chunk-OSK3NFVY-ijp9pw29.js +10 -0
- package/static/dist/assets/{chunk-P2QGCYS3-f7t54xfm.js → chunk-P2QGCYS3-hs9hqwl4.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-i927v9oa.js → chunk-POPQ4Y6H-bp9rsqez.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-m84lmnyt.js → chunk-PWAF6VOD-brkprov6.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-mi0tlcu9.js → chunk-SHT3W25Y-gmpv0pxz.js} +13 -13
- package/static/dist/assets/{chunk-SVP7TREG-dnau702j.js → chunk-SVP7TREG-oldm08fu.js} +4 -4
- package/static/dist/assets/{chunk-TICWLB2K-edem1gkg.js → chunk-TICWLB2K-ooqilfv7.js} +6 -6
- package/static/dist/assets/{chunk-XXDRQBXY-n40pf9vy.js → chunk-XXDRQBXY-jkmnqu65.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-fvgcv24g.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-fvgcv24g.js +1 -0
- package/static/dist/assets/{cobol-go622aep.js → cobol-ssf9hkh7.js} +1 -1
- package/static/dist/assets/{coffee-jwo1xjhq.js → coffee-lsi9fwae.js} +1 -1
- package/static/dist/assets/copied-bz3gkazh.js +1 -0
- package/static/dist/assets/copy-jo006coq.js +1 -0
- package/static/dist/assets/cose-bilkent-JH36ORCC-layy2r27.js +1 -0
- package/static/dist/assets/{cpp-nre7j0kq.js → cpp-lfrh4y55.js} +1 -1
- package/static/dist/assets/{crystal-lviso2je.js → crystal-efh4pte7.js} +1 -1
- package/static/dist/assets/{css-ywz9j4hb.js → css-i82qdo89.js} +1 -1
- package/static/dist/assets/cynefin-OW5HDTMX-d47op7qs.js +1 -0
- package/static/dist/assets/cynefinDiagram-5FMLGOSQ-doa7594y.js +62 -0
- package/static/dist/assets/cytoscape.esm-fjn2algp.js +321 -0
- package/static/dist/assets/dagre-GXQ25YYZ-os768dp4.js +4 -0
- package/static/dist/assets/dagre-o5jmjz46.js +1 -0
- package/static/dist/assets/{diagram-S7CK7UJ4-l8abxaes.js → diagram-S7CK7UJ4-fyi81nxd.js} +4 -4
- package/static/dist/assets/diagram-UQ7AKVKN-j3exggaq.js +41 -0
- package/static/dist/assets/{diagram-VSXAHHWV-fnky6lvc.js → diagram-VSXAHHWV-omo1en2l.js} +1 -1
- package/static/dist/assets/diagram-VX7I27RA-oro2cyu0.js +24 -0
- package/static/dist/assets/diagram-Z3DM3KII-byt9vdb7.js +24 -0
- package/static/dist/assets/dist-crc46di9.js +7 -0
- package/static/dist/assets/dist-etpoyscq.js +44 -0
- package/static/dist/assets/{dist-c4c2m3kv.js → dist-nb670eu6.js} +1 -1
- package/static/dist/assets/ebnfDiagram-PWID7BFC-ee3haugh.js +1 -0
- package/static/dist/assets/{edge-dvv35byi.js → edge-lrob635n.js} +1 -1
- package/static/dist/assets/{elixir-i2f1ki1g.js → elixir-kdpxeq93.js} +1 -1
- package/static/dist/assets/{elm-cpepyjm4.js → elm-c8irj9uh.js} +1 -1
- package/static/dist/assets/erDiagram-RLTQ6QDP-d1e93yff.js +99 -0
- package/static/dist/assets/{erb-idlyfheg.js → erb-flwsv146.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-i1ihb40c.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-jahfpiod.js +1 -0
- package/static/dist/assets/folder-plus-ivoglq98.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ngbsyp37.js → ganttDiagram-EL5Y4UJY-m7g0jud4.js} +1 -1
- package/static/dist/assets/{git-rebase-d9lpfpbn.js → git-rebase-bli19aoq.js} +1 -1
- package/static/dist/assets/gitGraph-4MIJSDKK-hzdj07yy.js +1 -0
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-eravdti4.js → gitGraphDiagram-WWUBYQGX-dm9agbna.js} +1 -1
- package/static/dist/assets/{glimmer-js-39i0urfb.js → glimmer-js-d4icg38p.js} +1 -1
- package/static/dist/assets/{glimmer-ts-gdtd6n73.js → glimmer-ts-bk75772u.js} +1 -1
- package/static/dist/assets/{glsl-o1djm5pp.js → glsl-lqormp2b.js} +1 -1
- package/static/dist/assets/{graphql-kj6rz55t.js → graphql-hw6s5qzd.js} +1 -1
- package/static/dist/assets/{hack-pe1qqr2i.js → hack-ozrv6l4d.js} +1 -1
- package/static/dist/assets/{haml-cknnbcqs.js → haml-gytfosdt.js} +1 -1
- package/static/dist/assets/{handlebars-hwq64orh.js → handlebars-b6g7jffd.js} +1 -1
- package/static/dist/assets/highlighted-body-KPVGNVTW-mmgz2czu.js +1 -0
- package/static/dist/assets/host-fr6lni1p.js +1 -0
- package/static/dist/assets/{html-derivative-h2i2pyjc.js → html-derivative-f1tqcroo.js} +1 -1
- package/static/dist/assets/{html-ha05xtvt.js → html-on6oj6yj.js} +1 -1
- package/static/dist/assets/{http-cf94zu27.js → http-odhufsl7.js} +1 -1
- package/static/dist/assets/{hurl-lslax568.js → hurl-mu680336.js} +1 -1
- package/static/dist/assets/index-73649w7o.js +10 -0
- package/static/dist/assets/index-lhf1fsdf.css +2 -0
- package/static/dist/assets/info-A6RAGUB7-lll1dqmm.js +1 -0
- package/static/dist/assets/infoDiagram-27XIBGKW-k7esc639.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-i6ypq351.js → ishikawaDiagram-5VMMS53U-lgr6k9wo.js} +6 -6
- package/static/dist/assets/{java-ndjf2o89.js → java-nxbe8uju.js} +1 -1
- package/static/dist/assets/{javascript-coftlwy3.js → javascript-lz3kxcyz.js} +1 -1
- package/static/dist/assets/{jinja-etei9wy7.js → jinja-l5pk84gt.js} +1 -1
- package/static/dist/assets/{jison-hyekgft1.js → jison-jdutdwyy.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-lduf1z95.js → journeyDiagram-3NMN7TZE-ndmbxuqk.js} +1 -1
- package/static/dist/assets/{json-lok0yaho.js → json-dnge4ssf.js} +1 -1
- package/static/dist/assets/{jsx-b64w0nbx.js → jsx-c3lwhkep.js} +1 -1
- package/static/dist/assets/{jsx-runtime-eu92a5pu.js → jsx-runtime-o40ukmjl.js} +1 -1
- package/static/dist/assets/{julia-jxxzz2yn.js → julia-hwd4rrcb.js} +1 -1
- package/static/dist/assets/{just-mc29iw9t.js → just-nka3oxdx.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-e18bu4pg.js → kanban-definition-UXKFOSKX-mjxym0jp.js} +28 -28
- package/static/dist/assets/katex-cbdd1ohw.css +1 -0
- package/static/dist/assets/katex-pbij2oo6.js +257 -0
- package/static/dist/assets/{latex-pafa03ft.js → latex-iqmr7e40.js} +1 -1
- package/static/dist/assets/lazyRouteComponent-cwb6l7xh.js +1 -0
- package/static/dist/assets/{line-meizqsvn.js → line-mtr5k6il.js} +1 -1
- package/static/dist/assets/linear-jlwrvvy6.js +1 -0
- package/static/dist/assets/link-iadw4ghw.js +2 -0
- package/static/dist/assets/{liquid-hz5c2pov.js → liquid-okmk9tbp.js} +1 -1
- package/static/dist/assets/locale-cs-n41hptse.js +5 -0
- package/static/dist/assets/locale-de-k7ht47ew.js +5 -0
- package/static/dist/assets/locale-es-ih078lco.js +5 -0
- package/static/dist/assets/locale-fr-ojabnf41.js +5 -0
- package/static/dist/assets/locale-it-kb96jufy.js +5 -0
- package/static/dist/assets/locale-ja-jpsp3xs1.js +7 -0
- package/static/dist/assets/locale-ko-cpzuy0n7.js +5 -0
- package/static/dist/assets/locale-pl-dbap4leu.js +5 -0
- package/static/dist/assets/locale-pt-nci7w72x.js +5 -0
- package/static/dist/assets/locale-ru-soghe4cs.js +7 -0
- package/static/dist/assets/locale-tr-ks00mu68.js +5 -0
- package/static/dist/assets/locale-vi-ij7twfm6.js +5 -0
- package/static/dist/assets/locale-zh-Hant-feakwr7a.js +5 -0
- package/static/dist/assets/locale-zh-ren0vjyv.js +7 -0
- package/static/dist/assets/login-lkhtzfcl.js +1 -0
- package/static/dist/assets/{lua-leyf8mlb.js → lua-krx4xj5h.js} +1 -1
- package/static/dist/assets/{marko-cn1fzpdv.js → marko-gbvpfehz.js} +1 -1
- package/static/dist/assets/{mdc-huhy88df.js → mdc-c21zlyi5.js} +1 -1
- package/static/dist/assets/mermaid-HWGCJPDP-jx0q5dm2.js +1 -0
- package/static/dist/assets/mermaid-parser.core-k91vdnvi.js +7 -0
- package/static/dist/assets/micromark-factory-space-fnmnpi6s.js +1 -0
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-27b26eok.js → mindmap-definition-YA3MSWOX-ehztfea4.js} +28 -28
- package/static/dist/assets/{nginx-e7lvhsmn.js → nginx-f3d68ed4.js} +1 -1
- package/static/dist/assets/{nim-dun5d8mk.js → nim-dw0jcy70.js} +1 -1
- package/static/dist/assets/onboarding-f67z0a2r.js +1 -0
- package/static/dist/assets/packet-AYTQ26CC-ok2xrblz.js +1 -0
- package/static/dist/assets/pcmTap.worklet-74EKjWAZ.js +1 -0
- package/static/dist/assets/pegDiagram-XKGWAZYB-ec3rpj0m.js +1 -0
- package/static/dist/assets/{perl-bj3oqtet.js → perl-3c0r45c8.js} +1 -1
- package/static/dist/assets/{php-buzp12r0.js → php-rznhidvp.js} +1 -1
- package/static/dist/assets/pie-WAS4IAKB-iwkir23u.js +1 -0
- package/static/dist/assets/pieDiagram-E7YTZNPT-fcpqpekh.js +39 -0
- package/static/dist/assets/{pug-mjad7he9.js → pug-f2le4f3y.js} +1 -1
- package/static/dist/assets/{qml-d1gdp10s.js → qml-e2wbq9cr.js} +1 -1
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-hhue7d8o.js → quadrantDiagram-AXDQQJYC-lqsync43.js} +1 -1
- package/static/dist/assets/queryKeys-ed4n2egc.js +9 -0
- package/static/dist/assets/{r-o3r9c7ao.js → r-epfzuw6q.js} +1 -1
- package/static/dist/assets/radar-RG4KPBEZ-hje4jbk6.js +1 -0
- package/static/dist/assets/railroad-74A4TZTK-gll9n6qi.js +1 -0
- package/static/dist/assets/railroad-abnf-HS5TGJTU-o6h6lpn4.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-juaefj6o.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-cgfhvpah.js +1 -0
- package/static/dist/assets/railroadDiagram-O6MQD6OU-lp3w7awf.js +1 -0
- package/static/dist/assets/{razor-ilc585yr.js → razor-pea1u1d0.js} +1 -1
- package/static/dist/assets/refresh-cw-iccm7a4e.js +1 -0
- package/static/dist/assets/refresh-i9ya9dvl.js +1 -0
- package/static/dist/assets/{regexp-mpjtcvwm.js → regexp-hzte0a5x.js} +1 -1
- package/static/dist/assets/registry-g7w3353u.js +1 -0
- package/static/dist/assets/requirementDiagram-BXWQKSXE-ha8eibcf.js +84 -0
- package/static/dist/assets/rolldown-runtime-bnnw9q4a.js +1 -0
- package/static/dist/assets/{rst-jrkwjdyi.js → rst-m6tugj5o.js} +1 -1
- package/static/dist/assets/{ruby-n4tjcbpz.js → ruby-l19vq3w1.js} +1 -1
- package/static/dist/assets/runtime-nlxpnegr.js +1 -0
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-fqb8xcjf.js → sankeyDiagram-P5KCCOFB-oxf31pr9.js} +1 -1
- package/static/dist/assets/{sas-hjo6y93k.js → sas-ktdxo397.js} +1 -1
- package/static/dist/assets/{scss-ncidjv48.js → scss-kn3oxfwf.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-dlcz6qjz.js → sequenceDiagram-WJ2MYXX4-jiq8lhyo.js} +11 -11
- package/static/dist/assets/settings_section_extensions_title-fadc7o65.js +1 -0
- package/static/dist/assets/share._token-o76ki0ly.js +2 -0
- package/static/dist/assets/{shellscript-m8whjpqd.js → shellscript-l100jtrg.js} +1 -1
- package/static/dist/assets/{shellsession-i5bj6syz.js → shellsession-m5qukwow.js} +1 -1
- package/static/dist/assets/sizeCapture-INFHLROL-dk1io13w.js +1 -0
- package/static/dist/assets/sliders-horizontal-ddcs88av.js +1 -0
- package/static/dist/assets/{soy-hhh36ysq.js → soy-mexlscur.js} +1 -1
- package/static/dist/assets/{sql-j21bzcl6.js → sql-o0ho5suh.js} +1 -1
- package/static/dist/assets/src-h89s0hs4.js +1 -0
- package/static/dist/assets/{stata-bbu2onh1.js → stata-be9ck1q3.js} +1 -1
- package/static/dist/assets/stateDiagram-D77RDMKH-f3fslnon.js +1 -0
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hlt6yphw.js +1 -0
- package/static/dist/assets/{surrealql-l5vczuco.js → surrealql-gk1xkkyy.js} +1 -1
- package/static/dist/assets/{svelte-idn422g6.js → svelte-k6ecjilf.js} +1 -1
- package/static/dist/assets/swimlanes-42K2YHIH-k1hmk7jf.js +1 -0
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-hd5t9tnt.js +8 -0
- package/static/dist/assets/{templ-e22qou3c.js → templ-gf3bftk6.js} +1 -1
- package/static/dist/assets/{tex-deqpc5ee.js → tex-d21fqv1z.js} +1 -1
- package/static/dist/assets/{timeline-definition-24CTP7MA-b1vl2zil.js → timeline-definition-24CTP7MA-n3ncfy9r.js} +8 -8
- package/static/dist/assets/toast-liparz2j.js +56 -0
- package/static/dist/assets/treeView-Q6P3EWNA-l3y2pejt.js +1 -0
- package/static/dist/assets/treemap-WGGIJYW6-jown9axh.js +1 -0
- package/static/dist/assets/{ts-tags-ctnibc27.js → ts-tags-n0g61gr6.js} +1 -1
- package/static/dist/assets/{tsx-i163n32g.js → tsx-cvv7wv6q.js} +1 -1
- package/static/dist/assets/{twig-x0fi7wm8.js → twig-n1f67204.js} +1 -1
- package/static/dist/assets/{typescript-g18c34xz.js → typescript-og7o55cx.js} +1 -1
- package/static/dist/assets/untitled-mj86r4gz.js +1 -0
- package/static/dist/assets/useDebounced-g8r0ggve.js +1 -0
- package/static/dist/assets/useForm-o4uu74mx.js +1 -0
- package/static/dist/assets/{useParams-b4dq6n4q.js → useParams-ftsveu84.js} +1 -1
- package/static/dist/assets/{useSearch-ipfmv3he.js → useSearch-crqueqga.js} +1 -1
- package/static/dist/assets/vennDiagram-4TSXK5OY-dh59uq1c.js +34 -0
- package/static/dist/assets/{vue-mwu5l1bb.js → vue-ej4xpwqw.js} +1 -1
- package/static/dist/assets/{vue-html-nskj3du0.js → vue-html-d48xsjuo.js} +1 -1
- package/static/dist/assets/{vue-vine-cqe77smq.js → vue-vine-pev88ip6.js} +1 -1
- package/static/dist/assets/wardley-WFR3VGLG-e6d74b0j.js +1 -0
- package/static/dist/assets/wardleyDiagram-VM6X3IG4-hnofh78f.js +78 -0
- package/static/dist/assets/with-selector-nu1b5z0v.js +1 -0
- package/static/dist/assets/{xml-gau7gvrv.js → xml-ifxp6gqb.js} +1 -1
- package/static/dist/assets/{xsl-ew09zq8b.js → xsl-fug2cckc.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-e49523ab.js → xychartDiagram-S5SC5T6Z-bpymjelb.js} +6 -6
- package/static/dist/assets/{yaml-ofnrxtvf.js → yaml-fjmafnlo.js} +1 -1
- package/static/dist/extension-sdk.js +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +180 -136
- package/static/dist/assets/AppShell-g3bkgnvt.js +0 -1
- package/static/dist/assets/ChatPage-oo7bdan6.js +0 -25
- package/static/dist/assets/Dialog-g355qxhp.js +0 -1
- package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +0 -1
- package/static/dist/assets/HubRoute-lmkrbw47.js +0 -2
- package/static/dist/assets/Markdown-j4kut4j2.js +0 -598
- package/static/dist/assets/Markdown-lwviulmg.css +0 -1
- package/static/dist/assets/RightPanel-ohlsdzfl.js +0 -1
- package/static/dist/assets/Select-kddq75qc.js +0 -1
- package/static/dist/assets/SessionListPanel-lu3oy0hw.js +0 -1
- package/static/dist/assets/SettingsLayout-o9bv4mgb.js +0 -1
- package/static/dist/assets/States-eq6fe29m.js +0 -1
- package/static/dist/assets/Toaster-pc247u9f.js +0 -1
- package/static/dist/assets/_app-f8n1i0m1.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-gfbzpoou.js +0 -1
- package/static/dist/assets/_app.index-bzzv80hk.js +0 -1
- package/static/dist/assets/_app.insights-dzixbryi.js +0 -1
- package/static/dist/assets/_app.kanban-c6hep2us.js +0 -1
- package/static/dist/assets/_app.logs-i69qds98.js +0 -1
- package/static/dist/assets/_app.memory-kl4vf0gd.js +0 -1
- package/static/dist/assets/_app.profiles-b8zinmyp.js +0 -1
- package/static/dist/assets/_app.session._sessionId-fn1bhde6.js +0 -1
- package/static/dist/assets/_app.settings-cg8plwmf.js +0 -1
- package/static/dist/assets/_app.settings._section-bkwbi9zn.js +0 -3
- package/static/dist/assets/_app.skills-cs47sbvs.js +0 -1
- package/static/dist/assets/_app.tasks-dwvk64tc.js +0 -1
- package/static/dist/assets/_app.todos-c8xjm8p3.js +0 -1
- package/static/dist/assets/_app.workspaces-ehvd1gds.js +0 -1
- package/static/dist/assets/abnfDiagram-VCTEODGH-c8kdixke.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +0 -1
- package/static/dist/assets/architectureDiagram-5GKGNRK7-cku06x1p.js +0 -36
- package/static/dist/assets/back-h7aun7ew.js +0 -1
- package/static/dist/assets/cancel-fhi0vcnu.js +0 -1
- package/static/dist/assets/channel-b2zo73z1.js +0 -1
- package/static/dist/assets/chunk-DU6HZSFF-ofajdixh.js +0 -128
- package/static/dist/assets/chunk-OSK3NFVY-e7gkikcw.js +0 -10
- package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +0 -1
- package/static/dist/assets/copied-hcplv46p.js +0 -1
- package/static/dist/assets/copy-magtofyp.js +0 -1
- package/static/dist/assets/cose-bilkent-JH36ORCC-eclcy6ja.js +0 -1
- package/static/dist/assets/cynefin-OW5HDTMX-siqlulkl.js +0 -1
- package/static/dist/assets/cynefinDiagram-5FMLGOSQ-i648tah9.js +0 -62
- package/static/dist/assets/cytoscape.esm-bqu53u12.js +0 -321
- package/static/dist/assets/dagre-GXQ25YYZ-mwn6uc17.js +0 -4
- package/static/dist/assets/dagre-lkoknbjn.js +0 -1
- package/static/dist/assets/diagram-UQ7AKVKN-mekq7zgq.js +0 -41
- package/static/dist/assets/diagram-VX7I27RA-xe6r5qfw.js +0 -24
- package/static/dist/assets/diagram-Z3DM3KII-nq07dg7k.js +0 -24
- package/static/dist/assets/ebnfDiagram-PWID7BFC-c62ipm3g.js +0 -1
- package/static/dist/assets/endpoints-klbxod4t.js +0 -64
- package/static/dist/assets/erDiagram-RLTQ6QDP-h39kmnzy.js +0 -99
- package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +0 -1
- package/static/dist/assets/gitGraph-4MIJSDKK-fr2i7caa.js +0 -1
- package/static/dist/assets/highlighted-body-KPVGNVTW-jwpop9p8.js +0 -1
- package/static/dist/assets/host-i828xuo7.js +0 -1
- package/static/dist/assets/index-eh8zilnv.css +0 -2
- package/static/dist/assets/index-r648f4o1.js +0 -11
- package/static/dist/assets/info-A6RAGUB7-h11x0dxo.js +0 -1
- package/static/dist/assets/infoDiagram-27XIBGKW-dfh47dw9.js +0 -2
- package/static/dist/assets/lazyRouteComponent-e1y4t2i9.js +0 -1
- package/static/dist/assets/linear-becwlnhk.js +0 -1
- package/static/dist/assets/link-oot2yn73.js +0 -1
- package/static/dist/assets/login-gu28l9ou.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +0 -1
- package/static/dist/assets/mermaid-parser.core-ntne45i9.js +0 -7
- package/static/dist/assets/not-found-ccnnac39.js +0 -1
- package/static/dist/assets/onboarding-lkphzu1s.js +0 -1
- package/static/dist/assets/onboarding_model_label-ftpjdwqk.js +0 -1
- package/static/dist/assets/packet-AYTQ26CC-l6jdka8p.js +0 -1
- package/static/dist/assets/pegDiagram-XKGWAZYB-g1er3bxj.js +0 -1
- package/static/dist/assets/pie-WAS4IAKB-i0ylcrt9.js +0 -1
- package/static/dist/assets/pieDiagram-E7YTZNPT-fbz1e55z.js +0 -39
- package/static/dist/assets/radar-RG4KPBEZ-f9b70aa4.js +0 -1
- package/static/dist/assets/railroad-74A4TZTK-lgsuoeda.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +0 -1
- package/static/dist/assets/railroadDiagram-O6MQD6OU-eqpy9fnz.js +0 -1
- package/static/dist/assets/refresh-abtu93ke.js +0 -1
- package/static/dist/assets/refresh-cw-hlwb02qv.js +0 -1
- package/static/dist/assets/requirementDiagram-BXWQKSXE-ogf6puhi.js +0 -84
- package/static/dist/assets/rolldown-runtime-b9miacsj.js +0 -1
- package/static/dist/assets/root-mbklpuv4.js +0 -1
- package/static/dist/assets/saved-girnabkf.js +0 -1
- package/static/dist/assets/settings_section_extensions_title-h14cwk6f.js +0 -1
- package/static/dist/assets/share._token-kl0r7ido.js +0 -2
- package/static/dist/assets/sizeCapture-INFHLROL-g612l48j.js +0 -1
- package/static/dist/assets/sliders-horizontal-n1zm34qq.js +0 -1
- package/static/dist/assets/src-hjyib4mu.js +0 -1
- package/static/dist/assets/sse-iwku0sjo.js +0 -1
- package/static/dist/assets/stateDiagram-D77RDMKH-fyoznpcs.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +0 -1
- package/static/dist/assets/swimlanes-42K2YHIH-oefldhfy.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +0 -8
- package/static/dist/assets/todoStore-jjr5yr78.js +0 -1
- package/static/dist/assets/treeView-Q6P3EWNA-f7hcci5o.js +0 -1
- package/static/dist/assets/treemap-WGGIJYW6-nl85071a.js +0 -1
- package/static/dist/assets/units-kgxzxfhi.js +0 -1
- package/static/dist/assets/untitled-cikfjntn.js +0 -1
- package/static/dist/assets/useForm-kqoqg0k9.js +0 -1
- package/static/dist/assets/useLocale-o59tbiea.js +0 -1
- package/static/dist/assets/vennDiagram-4TSXK5OY-h5hzbk0n.js +0 -34
- package/static/dist/assets/wardley-WFR3VGLG-jftvpm3e.js +0 -1
- package/static/dist/assets/wardleyDiagram-VM6X3IG4-hedh85ge.js +0 -78
- /package/static/dist/assets/{ChatPage-gmu5c7lf.css → TerminalPanel-gmu5c7lf.css} +0 -0
|
@@ -0,0 +1,1111 @@
|
|
|
1
|
+
"""A lifetime-leased Agent update supervisor, extending dependency_sync's child custody.
|
|
2
|
+
|
|
3
|
+
Agent code is loaded only in the separate worker, before source changes. The native
|
|
4
|
+
checkout lock and gateway journal also fence the backend recovery companion.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import ast
|
|
9
|
+
import contextlib
|
|
10
|
+
import hashlib
|
|
11
|
+
import importlib
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import queue
|
|
16
|
+
import re
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
import threading
|
|
21
|
+
import time
|
|
22
|
+
import uuid
|
|
23
|
+
|
|
24
|
+
from .dependency_sync import _stop_tree
|
|
25
|
+
from .errors import InvalidParams, RpcError
|
|
26
|
+
from . import agent_services as services
|
|
27
|
+
|
|
28
|
+
_SESSIONS = {}
|
|
29
|
+
_SESSIONS_LOCK = threading.Lock()
|
|
30
|
+
_CONSENTS = {}
|
|
31
|
+
CONSENT_SECONDS = 300
|
|
32
|
+
# One lease exchange, including the worker's unwind after a refused drain.
|
|
33
|
+
LEASE_SECONDS = 330
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _close(session):
|
|
37
|
+
with session["close_lock"]:
|
|
38
|
+
if session["closed"]:
|
|
39
|
+
return
|
|
40
|
+
session["closed"] = True
|
|
41
|
+
with contextlib.suppress(OSError):
|
|
42
|
+
session["proc"].stdin.close()
|
|
43
|
+
try:
|
|
44
|
+
session["proc"].wait(timeout=330)
|
|
45
|
+
except subprocess.TimeoutExpired:
|
|
46
|
+
# The native pause journal still owns recovery; never fabricate a successful resume.
|
|
47
|
+
session["proc"].kill()
|
|
48
|
+
session["proc"].wait()
|
|
49
|
+
session["stderr"].close()
|
|
50
|
+
session["proc"].stdout.close()
|
|
51
|
+
session["gateway_lock"].release()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def close_all():
|
|
55
|
+
with _SESSIONS_LOCK:
|
|
56
|
+
sessions = list(_SESSIONS.values())
|
|
57
|
+
_SESSIONS.clear()
|
|
58
|
+
for session in sessions:
|
|
59
|
+
_close(session)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _reap_sessions():
|
|
63
|
+
with _SESSIONS_LOCK:
|
|
64
|
+
dead = [token for token, session in _SESSIONS.items() if session['proc'].poll() is not None]
|
|
65
|
+
sessions = [_SESSIONS.pop(token) for token in dead]
|
|
66
|
+
for session in sessions:
|
|
67
|
+
_close(session)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _git_directory(root):
|
|
71
|
+
"""Locate receipts before importing possibly torn Agent modules."""
|
|
72
|
+
gitdir = root / '.git'
|
|
73
|
+
if gitdir.is_file():
|
|
74
|
+
value = gitdir.read_text().strip()
|
|
75
|
+
if not value.startswith('gitdir: '):
|
|
76
|
+
raise OSError('The Agent checkout git directory is unreadable.')
|
|
77
|
+
gitdir = (root / value[8:]).resolve()
|
|
78
|
+
return gitdir
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _native_source_repair(root):
|
|
82
|
+
marker = _git_directory(root) / 'hermes-update-pull'
|
|
83
|
+
if not marker.exists():
|
|
84
|
+
return
|
|
85
|
+
with marker.open(encoding='utf-8-sig') as handle:
|
|
86
|
+
raw = handle.read(65537)
|
|
87
|
+
fields = dict(line.partition('=')[::2] for line in raw.splitlines())
|
|
88
|
+
if len(raw) > 65536 or any(not re.fullmatch(r'[a-f0-9]{40}', fields.get(key, '')) for key in ('pre', 'target')):
|
|
89
|
+
raise RuntimeError('Native source recovery receipt is malformed; its evidence is retained.')
|
|
90
|
+
executable = fields.get('git', '')
|
|
91
|
+
executable = executable if os.path.isabs(executable) and Path(executable).is_file() else 'git'
|
|
92
|
+
head = subprocess.run([executable, '--no-replace-objects', '-C', str(root), 'rev-parse', 'HEAD'], capture_output=True, text=True, timeout=15, check=True).stdout.strip()
|
|
93
|
+
if head not in {fields['pre'], fields['target']} or not fields.get('pid', '').isdecimal():
|
|
94
|
+
raise RuntimeError('Native source recovery no longer matches the recorded checkout identity; its receipt is retained.')
|
|
95
|
+
# Read the native launch-repair procedure from the immutable baseline, never the damaged tree.
|
|
96
|
+
def constants(path, names):
|
|
97
|
+
found = subprocess.run([executable, '--no-replace-objects', '-C', str(root), 'show', fields['pre'] + ':' + path],
|
|
98
|
+
capture_output=True, text=True, timeout=15, check=True)
|
|
99
|
+
values = {}
|
|
100
|
+
for node in ast.parse(found.stdout).body:
|
|
101
|
+
if isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name) and node.targets[0].id in names:
|
|
102
|
+
values[node.targets[0].id] = ast.literal_eval(node.value)
|
|
103
|
+
if values.keys() != names:
|
|
104
|
+
raise RuntimeError('The recorded Agent baseline has no complete native launch-repair procedure.')
|
|
105
|
+
return values
|
|
106
|
+
template = constants('hermes_cli/_launchers.py', {'_CLOSURE_REPAIR'})['_CLOSURE_REPAIR']
|
|
107
|
+
keys = {'INTERRUPTED_PULL_MARKER', 'RECOVERY_CLOSURE_DIR', 'RECOVERY_CLOSURE_MANIFEST', 'RECOVERY_CLOSURE_INIT', 'RECOVERY_CLOSURE'}
|
|
108
|
+
values = constants('hermes_cli/_early_recovery.py', keys)
|
|
109
|
+
for placeholder, key in (('__MARKER__', 'INTERRUPTED_PULL_MARKER'), ('__CLOSURE_DIR__', 'RECOVERY_CLOSURE_DIR'),
|
|
110
|
+
('__MANIFEST__', 'RECOVERY_CLOSURE_MANIFEST'), ('__INIT__', 'RECOVERY_CLOSURE_INIT'), ('__CLOSURE__', 'RECOVERY_CLOSURE')):
|
|
111
|
+
template = template.replace(placeholder, repr(values[key]))
|
|
112
|
+
template = template.replace('__ROOT__', repr(str(root)))
|
|
113
|
+
namespace = {'os': os, 're': re, 'sys': sys}
|
|
114
|
+
exec(compile(template, '<native recorded launch repair>', 'exec'), namespace)
|
|
115
|
+
try:
|
|
116
|
+
namespace['_hermes_closure_repair']() # Native verification, checkout custody and re-exec remain authoritative.
|
|
117
|
+
except SystemExit:
|
|
118
|
+
if not marker.exists():
|
|
119
|
+
# A proved no-op removes the receipt without changing files; discard the closure imports just as after a restore.
|
|
120
|
+
sys.modules['hermes_cli._early_recovery'].relaunch_after_restore()
|
|
121
|
+
raise
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _receipt_candidates(path, pattern, cache):
|
|
125
|
+
"""Cache discovery, never receipt presence; missing paths watch their nearest existing ancestor."""
|
|
126
|
+
key = (path, pattern)
|
|
127
|
+
if cache is not None:
|
|
128
|
+
ancestor = path
|
|
129
|
+
while True:
|
|
130
|
+
try:
|
|
131
|
+
stat = ancestor.stat()
|
|
132
|
+
break
|
|
133
|
+
except FileNotFoundError:
|
|
134
|
+
if ancestor == ancestor.parent:
|
|
135
|
+
raise
|
|
136
|
+
ancestor = ancestor.parent
|
|
137
|
+
stamp = (ancestor, stat.st_dev, stat.st_ino, stat.st_mode, stat.st_mtime_ns, stat.st_ctime_ns, stat.st_size)
|
|
138
|
+
now = time.monotonic()
|
|
139
|
+
previous = cache.get(key)
|
|
140
|
+
if previous is not None and previous[0] == stamp and 0 <= now - previous[1] < 1:
|
|
141
|
+
return previous[2]
|
|
142
|
+
if pattern is not None:
|
|
143
|
+
try:
|
|
144
|
+
# glob suppresses enumeration errors, which cannot authorize Agent admission.
|
|
145
|
+
with os.scandir(path) as children:
|
|
146
|
+
found = tuple(path / child.name for child in children if Path(child.name).match(pattern))
|
|
147
|
+
except FileNotFoundError:
|
|
148
|
+
found = ()
|
|
149
|
+
else:
|
|
150
|
+
try:
|
|
151
|
+
found = (path,) if any(' refs/talaria/autostash/pending/' in line for line in path.read_text().splitlines()) else ()
|
|
152
|
+
except FileNotFoundError:
|
|
153
|
+
found = ()
|
|
154
|
+
if cache is not None:
|
|
155
|
+
if len(cache) >= 16:
|
|
156
|
+
cache.clear() # Bound retained paths if an installer repeatedly redirects Git metadata.
|
|
157
|
+
cache[key] = (stamp, now, found)
|
|
158
|
+
return found
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _process_presence(pid):
|
|
162
|
+
"""A stdlib rearm trigger only; native identity verification alone decides recovery."""
|
|
163
|
+
if os.name == 'nt' or pid <= 0:
|
|
164
|
+
return 'unknown'
|
|
165
|
+
try:
|
|
166
|
+
os.kill(pid, 0)
|
|
167
|
+
except ProcessLookupError:
|
|
168
|
+
return 'exited'
|
|
169
|
+
except PermissionError:
|
|
170
|
+
pass
|
|
171
|
+
except (OSError, OverflowError):
|
|
172
|
+
return 'unknown'
|
|
173
|
+
return 'present'
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def recovery_receipts(runtime, *, watch=False, discovery_cache=None):
|
|
177
|
+
"""Read only the configured checkout's receipts without importing its possibly torn code."""
|
|
178
|
+
root, home = runtime.agent_dir, runtime.hermes_home
|
|
179
|
+
if root is None:
|
|
180
|
+
return [], None
|
|
181
|
+
root = root.resolve()
|
|
182
|
+
state = os.environ.get('TALARIA_SIDECAR_WEB_STATE_DIR') or os.environ.get('HERMES_WEBUI_STATE_DIR', '').strip() or str(home / 'webui')
|
|
183
|
+
marker = Path(state).expanduser() / 'agent-dependency-sync' / hashlib.sha256(str(root).encode()).hexdigest()
|
|
184
|
+
key = hashlib.sha256(os.path.normcase(str(root)).encode()).hexdigest()[:12]
|
|
185
|
+
native = home / ('.hermes-update-paused-gateways.' + key + '.json')
|
|
186
|
+
gitdir = _git_directory(root)
|
|
187
|
+
paths = [services.recovery_path(root, home), marker, native,
|
|
188
|
+
root / '.update-incomplete', root / '.lazy-refresh-incomplete',
|
|
189
|
+
gitdir / 'hermes-update-pull', root / '.hermes-update-zip-swap']
|
|
190
|
+
paths.extend(_receipt_candidates(home, native.name + '.*.claim', discovery_cache))
|
|
191
|
+
common = gitdir
|
|
192
|
+
try:
|
|
193
|
+
common = (gitdir / (gitdir / 'commondir').read_text().strip()).resolve()
|
|
194
|
+
except FileNotFoundError:
|
|
195
|
+
pass
|
|
196
|
+
paths.extend(_receipt_candidates(common / 'refs/talaria/autostash/pending', '*', discovery_cache))
|
|
197
|
+
packed = common / 'packed-refs'
|
|
198
|
+
paths.extend(_receipt_candidates(packed, None, discovery_cache))
|
|
199
|
+
def stamps(candidates):
|
|
200
|
+
result = []
|
|
201
|
+
for path in sorted(set(candidates)):
|
|
202
|
+
try:
|
|
203
|
+
stat = path.lstat()
|
|
204
|
+
result.append((str(path), stat.st_ino, stat.st_mtime_ns, stat.st_size))
|
|
205
|
+
except FileNotFoundError:
|
|
206
|
+
pass
|
|
207
|
+
return result
|
|
208
|
+
found = stamps(paths)
|
|
209
|
+
if watch and found:
|
|
210
|
+
# A repaired source/index or interpreter selection can make previously blocked debt provable again.
|
|
211
|
+
watched = [gitdir / 'HEAD', gitdir / 'index', root / '.venv/pyvenv.cfg', root / 'venv/pyvenv.cfg',
|
|
212
|
+
root / '.venv/bin/python', root / 'venv/bin/python',
|
|
213
|
+
home / 'installs' / hashlib.sha256(str(root).encode()).hexdigest()[:16] / 'facts.json']
|
|
214
|
+
try:
|
|
215
|
+
head = (gitdir / 'HEAD').read_text().strip()
|
|
216
|
+
if head.startswith('ref: '):
|
|
217
|
+
ref = Path(head[5:])
|
|
218
|
+
if not ref.is_absolute() and '..' not in ref.parts:
|
|
219
|
+
watched.append(common / ref)
|
|
220
|
+
except (OSError, UnicodeError):
|
|
221
|
+
pass
|
|
222
|
+
lifecycle = []
|
|
223
|
+
try:
|
|
224
|
+
body = json.loads(services.recovery_path(root, home).read_text())
|
|
225
|
+
for baseline in body.get('tree', {}).get('baselines', []):
|
|
226
|
+
changed = [*baseline.get('dirty_at_pause', []), *baseline.get('move_paths', [])]
|
|
227
|
+
changed.extend(path for paths in baseline.get('landed', {}).values() for path in paths)
|
|
228
|
+
watched.extend(root / path for path in changed if isinstance(path, str) and not Path(path).is_absolute() and '..' not in Path(path).parts)
|
|
229
|
+
# A retained gateway's exit or successor rearms exhausted recovery without changing any receipt.
|
|
230
|
+
for entry in body.get('entries', []):
|
|
231
|
+
if entry.get('purpose') == 'gateway' and isinstance(entry.get('home'), str):
|
|
232
|
+
watched.append(Path(entry['home']) / 'gateway.pid')
|
|
233
|
+
pids = {**entry.get('retired_processes', {}), **entry.get('processes', {})}
|
|
234
|
+
lifecycle.extend(('process', int(pid), _process_presence(int(pid))) for pid in sorted(pids, key=int))
|
|
235
|
+
except (OSError, ValueError, TypeError, AttributeError):
|
|
236
|
+
pass # The receipt's own stamp still rearms corrected evidence; the worker reports its exact refusal.
|
|
237
|
+
found.extend(stamps(watched))
|
|
238
|
+
found.extend(lifecycle)
|
|
239
|
+
return found, marker
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def recover_retained(runtime, ctx, *, probe=False, complete_revision=None):
|
|
243
|
+
"""One leased continuation. UpdateService owns the bounded retry sequence."""
|
|
244
|
+
receipts, _ = recovery_receipts(runtime, watch=True)
|
|
245
|
+
fingerprint = hashlib.sha256(json.dumps(receipts).encode()).hexdigest() if receipts else ''
|
|
246
|
+
result = {'ok': not receipts, 'owed': bool(receipts), 'fingerprint': fingerprint, 'detail': ''}
|
|
247
|
+
if complete_revision is not None:
|
|
248
|
+
if not isinstance(complete_revision, str) or not re.fullmatch(r'[a-f0-9]{40}', complete_revision):
|
|
249
|
+
raise InvalidParams('A verified loaded revision is required to complete recovery.')
|
|
250
|
+
if not runtime.loaded or runtime.revision != complete_revision or runtime.is_stale():
|
|
251
|
+
return {**result, 'ok': False, 'detail': 'The loaded Agent runtime does not match the recovered revision.'}
|
|
252
|
+
if probe or not receipts:
|
|
253
|
+
return result
|
|
254
|
+
_reap_sessions()
|
|
255
|
+
from .methods.gateway import _ACTION_LOCK
|
|
256
|
+
if not _ACTION_LOCK.acquire(blocking=False):
|
|
257
|
+
return {**result, 'detail': 'A gateway lifecycle action or Agent update still owns recovery.'}
|
|
258
|
+
from .home import _frozen_launch_env
|
|
259
|
+
root = runtime.agent_dir.resolve()
|
|
260
|
+
env = _frozen_launch_env()
|
|
261
|
+
env.update(HERMES_HOME=str(runtime.hermes_home), HERMES_DISABLE_LAZY_INSTALLS='1',
|
|
262
|
+
PYTHONPATH=str(Path(__file__).parents[1]) + os.pathsep + str(root))
|
|
263
|
+
try:
|
|
264
|
+
if complete_revision is None:
|
|
265
|
+
runtime.retire()
|
|
266
|
+
with tempfile.TemporaryFile() as stdout, tempfile.TemporaryFile() as stderr:
|
|
267
|
+
child = subprocess.Popen([sys.executable, '-m', 'talaria_sidecar.agent_mutation', '--recover-current', str(root), complete_revision or ''],
|
|
268
|
+
cwd=root, env=env, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr)
|
|
269
|
+
try:
|
|
270
|
+
deadline = time.monotonic() + 660
|
|
271
|
+
while child.poll() is None:
|
|
272
|
+
ctx.check_cancelled()
|
|
273
|
+
if time.monotonic() >= deadline:
|
|
274
|
+
raise RuntimeError('Agent recovery timed out; durable recovery remains owed.')
|
|
275
|
+
ctx.cancel_event.wait(.05)
|
|
276
|
+
stdout.seek(0)
|
|
277
|
+
reply = json.loads(stdout.read(1024 * 1024))
|
|
278
|
+
if not isinstance(reply, dict) or not isinstance(reply.get('ok'), bool):
|
|
279
|
+
raise ValueError('Invalid Agent recovery result.')
|
|
280
|
+
if not reply['ok']:
|
|
281
|
+
stderr.seek(0, os.SEEK_END)
|
|
282
|
+
stderr.seek(max(0, stderr.tell() - 4000))
|
|
283
|
+
diagnostic = stderr.read().decode('utf-8', errors='replace').strip()
|
|
284
|
+
if diagnostic:
|
|
285
|
+
reply['detail'] = str(reply.get('detail') or '') + ': ' + diagnostic
|
|
286
|
+
finally:
|
|
287
|
+
child.stdin.close()
|
|
288
|
+
try:
|
|
289
|
+
child.wait(timeout=330)
|
|
290
|
+
except subprocess.TimeoutExpired:
|
|
291
|
+
child.kill()
|
|
292
|
+
child.wait()
|
|
293
|
+
remaining, _ = recovery_receipts(runtime, watch=True)
|
|
294
|
+
return {**result, **reply, 'owed': bool(remaining),
|
|
295
|
+
'fingerprint': hashlib.sha256(json.dumps(remaining).encode()).hexdigest() if remaining else ''}
|
|
296
|
+
finally:
|
|
297
|
+
_ACTION_LOCK.release()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _drain_progress(result):
|
|
301
|
+
progress = result.get("progress") if isinstance(result, dict) and result.keys() == {"progress"} else None
|
|
302
|
+
if (not isinstance(progress, dict) or progress.get("phase") != "gateway_drain"
|
|
303
|
+
or not isinstance(progress.get("deadline_seconds"), (int, float)) or isinstance(progress["deadline_seconds"], bool)
|
|
304
|
+
or not 0 <= progress["deadline_seconds"] <= services.GATEWAY_DRAIN_CEILING
|
|
305
|
+
or not isinstance(progress.get("services"), list) or not all(isinstance(name, str) for name in progress["services"])):
|
|
306
|
+
return None
|
|
307
|
+
return {"phase": "gateway_drain", "deadline_seconds": progress["deadline_seconds"], "services": progress["services"]}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _exchange(session, request, ctx):
|
|
311
|
+
with session["lock"]:
|
|
312
|
+
if request is not None:
|
|
313
|
+
session["proc"].stdin.write(json.dumps(request) + "\n")
|
|
314
|
+
session["proc"].stdin.flush()
|
|
315
|
+
deadline = time.monotonic() + LEASE_SECONDS
|
|
316
|
+
while True:
|
|
317
|
+
ctx.check_cancelled()
|
|
318
|
+
if time.monotonic() >= deadline:
|
|
319
|
+
raise RpcError("Agent writer lease timed out; recovery remains recorded.", condition="agent_update_unsafe")
|
|
320
|
+
try:
|
|
321
|
+
line = session["output"].get(timeout=.1)
|
|
322
|
+
except queue.Empty:
|
|
323
|
+
continue
|
|
324
|
+
if line is None:
|
|
325
|
+
raise RpcError("Agent writer lease exited; its pause journal retains recovery.", condition="agent_update_unsafe")
|
|
326
|
+
result = json.loads(line)
|
|
327
|
+
if isinstance(result, dict) and "progress" in result:
|
|
328
|
+
progress = _drain_progress(result)
|
|
329
|
+
if progress is None:
|
|
330
|
+
raise RpcError("Invalid Agent writer lease response.", condition="agent_update_unsafe")
|
|
331
|
+
# The worker's own drain deadline is bounded; unwind keeps the ordinary lease margin.
|
|
332
|
+
deadline = max(deadline, time.monotonic() + progress["deadline_seconds"] + LEASE_SECONDS)
|
|
333
|
+
ctx.emit("progress", progress)
|
|
334
|
+
continue
|
|
335
|
+
if not isinstance(result, dict) or not isinstance(result.get("ok"), bool):
|
|
336
|
+
raise RpcError("Invalid Agent writer lease response.", condition="agent_update_unsafe")
|
|
337
|
+
return {"ok": result["ok"], "skipped": result.get("skipped") is True, "detail": str(result.get("detail") or ""),
|
|
338
|
+
**({"refresh_confirmation": result["refresh_confirmation"]} if "refresh_confirmation" in result else {})}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def call(runtime, ctx, params):
|
|
342
|
+
root = runtime.agent_dir
|
|
343
|
+
expected = params.get("agent_dir")
|
|
344
|
+
if root is None or not isinstance(expected, str) or not expected or Path(expected).resolve() != root.resolve():
|
|
345
|
+
raise InvalidParams("agent_dir must match the sidecar's configured Agent checkout")
|
|
346
|
+
action = params.get("action")
|
|
347
|
+
if action not in {"begin", "git", "sync", "finish"}:
|
|
348
|
+
raise InvalidParams("invalid Agent mutation action")
|
|
349
|
+
if action == "begin":
|
|
350
|
+
_reap_sessions()
|
|
351
|
+
revision = params.get("revision")
|
|
352
|
+
homes = params.get("homes")
|
|
353
|
+
if not isinstance(revision, str) or not re.fullmatch(r"[a-f0-9]{40}", revision):
|
|
354
|
+
raise InvalidParams("an immutable target revision is required")
|
|
355
|
+
if not isinstance(homes, list) or not homes or not all(isinstance(home, str) and Path(home).is_absolute() for home in homes):
|
|
356
|
+
raise InvalidParams("resolved profile homes are required")
|
|
357
|
+
if any(Path(home).resolve().is_relative_to(root.resolve()) for home in homes):
|
|
358
|
+
raise InvalidParams("Agent profile homes must stay outside the source checkout to survive update cleanup")
|
|
359
|
+
intent = params.get('intent', '')
|
|
360
|
+
refresh_token = params.get('refresh_token')
|
|
361
|
+
if not isinstance(intent, str) or len(intent) > 1024:
|
|
362
|
+
raise InvalidParams('invalid update intent')
|
|
363
|
+
binding = None
|
|
364
|
+
with _SESSIONS_LOCK:
|
|
365
|
+
for key, value in list(_CONSENTS.items()):
|
|
366
|
+
if value['expires'] <= time.monotonic():
|
|
367
|
+
_CONSENTS.pop(key)
|
|
368
|
+
if refresh_token is not None:
|
|
369
|
+
consent = _CONSENTS.pop(refresh_token, None) if isinstance(refresh_token, str) else None
|
|
370
|
+
if (consent is None or not intent or consent['intent'] != intent or consent['root'] != root.resolve()
|
|
371
|
+
or consent['revision'] != revision or consent['homes'] != sorted({str(Path(h).resolve()) for h in homes})):
|
|
372
|
+
return {'ok': False, 'skipped': False, 'detail': 'Legacy refresh confirmation expired, changed or was already used. Check for updates and confirm again.'}
|
|
373
|
+
binding = consent['binding']
|
|
374
|
+
if any(s["root"] == root.resolve() for s in _SESSIONS.values()):
|
|
375
|
+
return {"ok": False, "skipped": False, "detail": "Another Agent update owns the writer lease."}
|
|
376
|
+
from .home import _frozen_launch_env
|
|
377
|
+
env = _frozen_launch_env()
|
|
378
|
+
extra_pythonpath = (os.pathsep + env["PYTHONPATH"]) if env.get("PYTHONPATH") else ""
|
|
379
|
+
env.update(HERMES_HOME=str(runtime.hermes_home), HERMES_DISABLE_LAZY_INSTALLS="1",
|
|
380
|
+
PYTHONPATH=str(Path(__file__).parents[1]) + os.pathsep + str(root) + extra_pythonpath)
|
|
381
|
+
stderr = tempfile.TemporaryFile()
|
|
382
|
+
from .methods.gateway import _ACTION_LOCK
|
|
383
|
+
if not _ACTION_LOCK.acquire(blocking=False):
|
|
384
|
+
stderr.close()
|
|
385
|
+
return {"ok": False, "skipped": False, "detail": "A gateway lifecycle action is still running."}
|
|
386
|
+
try:
|
|
387
|
+
proc = subprocess.Popen([sys.executable, "-m", "talaria_sidecar.agent_mutation", str(root), revision, json.dumps(homes), binding or ""],
|
|
388
|
+
cwd=root, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr, text=True)
|
|
389
|
+
except BaseException:
|
|
390
|
+
stderr.close()
|
|
391
|
+
_ACTION_LOCK.release()
|
|
392
|
+
raise
|
|
393
|
+
token = uuid.uuid4().hex
|
|
394
|
+
session = {"intent": intent, "revision": revision, "homes": sorted({str(Path(h).resolve()) for h in homes}), "root": root.resolve(), "proc": proc, "stderr": stderr, "lock": threading.RLock(),
|
|
395
|
+
"close_lock": threading.Lock(), "closed": False, "output": queue.Queue(), "gateway_lock": _ACTION_LOCK}
|
|
396
|
+
_SESSIONS[token] = session
|
|
397
|
+
def read():
|
|
398
|
+
try:
|
|
399
|
+
for line in proc.stdout:
|
|
400
|
+
if len(line) > 16 * 1024 * 1024:
|
|
401
|
+
break
|
|
402
|
+
session["output"].put(line)
|
|
403
|
+
finally:
|
|
404
|
+
session["output"].put(None)
|
|
405
|
+
threading.Thread(target=read, daemon=True).start()
|
|
406
|
+
else:
|
|
407
|
+
token = params.get("token")
|
|
408
|
+
if not isinstance(token, str) or not re.fullmatch(r"[a-f0-9]{32}", token):
|
|
409
|
+
raise InvalidParams("invalid Agent writer lease token")
|
|
410
|
+
with _SESSIONS_LOCK:
|
|
411
|
+
session = _SESSIONS.get(token)
|
|
412
|
+
if session is None or session["root"] != root.resolve():
|
|
413
|
+
raise RpcError("Agent writer lease is unavailable; retry recovery before updating.", condition="agent_update_unsafe")
|
|
414
|
+
if action == "git":
|
|
415
|
+
args = params.get("args")
|
|
416
|
+
if (not isinstance(args, list) or not args or len(args) > 1000
|
|
417
|
+
or not all(isinstance(arg, str) for arg in args) or sum(map(len, args)) > 1024 * 1024
|
|
418
|
+
or args[0] not in {"rev-parse", "ls-tree", "show", "tag", "for-each-ref", "status", "diff-index", "diff", "stash",
|
|
419
|
+
"merge", "apply", "checkout", "clean", "reset", "update-ref", "describe", "log", "merge-base", "rev-list", "ls-files"}
|
|
420
|
+
or "--unsafe-paths" in args):
|
|
421
|
+
raise InvalidParams("invalid leased git command")
|
|
422
|
+
timeout = params.get("timeout_ms")
|
|
423
|
+
if not isinstance(timeout, int) or not 100 <= timeout <= 300000:
|
|
424
|
+
raise InvalidParams("invalid leased git timeout")
|
|
425
|
+
try:
|
|
426
|
+
result = _exchange(session, None if action == "begin" else params, ctx)
|
|
427
|
+
if action == 'begin' and 'refresh_confirmation' in result:
|
|
428
|
+
confirmation = result['refresh_confirmation']
|
|
429
|
+
if not session['intent']:
|
|
430
|
+
return {'ok': False, 'skipped': False, 'detail': 'Legacy dashboard refresh requires an interactive operator confirmation.'}
|
|
431
|
+
nonce = uuid.uuid4().hex
|
|
432
|
+
with _SESSIONS_LOCK:
|
|
433
|
+
if len(_CONSENTS) >= 64:
|
|
434
|
+
_CONSENTS.pop(next(iter(_CONSENTS)))
|
|
435
|
+
_CONSENTS[nonce] = {key: session[key] for key in ('root', 'revision', 'homes', 'intent')}
|
|
436
|
+
_CONSENTS[nonce].update(binding=confirmation['binding'], expires=time.monotonic() + CONSENT_SECONDS)
|
|
437
|
+
result['refresh_confirmation'] = {'token': nonce, 'services': confirmation['services']}
|
|
438
|
+
if action == "begin" and result["ok"]:
|
|
439
|
+
runtime.retire()
|
|
440
|
+
return {**result, "token": token}
|
|
441
|
+
if action != "finish" and result["ok"]:
|
|
442
|
+
return result
|
|
443
|
+
# A failed Git/sync command leaves the lease held so the caller can restore its stash and finish.
|
|
444
|
+
if action in {"git", "sync"}:
|
|
445
|
+
return result
|
|
446
|
+
return result
|
|
447
|
+
except BaseException:
|
|
448
|
+
with _SESSIONS_LOCK:
|
|
449
|
+
_SESSIONS.pop(token, None)
|
|
450
|
+
_close(session)
|
|
451
|
+
raise
|
|
452
|
+
finally:
|
|
453
|
+
if action == "finish" or (action == "begin" and 'result' in locals() and not result["ok"]):
|
|
454
|
+
with _SESSIONS_LOCK:
|
|
455
|
+
_SESSIONS.pop(token, None)
|
|
456
|
+
_close(session)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _owners(root, homes, pause, identity, windows=None):
|
|
460
|
+
homes = {Path(home).resolve() for home in homes}
|
|
461
|
+
if windows is None:
|
|
462
|
+
units, jobs = pause._discover_systemd(), pause._discover_launchd()
|
|
463
|
+
units, jobs, notices = pause._left_running(units, jobs)
|
|
464
|
+
supervised = services.gateway_entries(units + jobs, homes, pause)
|
|
465
|
+
bare, more = pause._discover_bare({pid for e in supervised for pid in (e["pid"], e["service_pid"])})
|
|
466
|
+
if notices or more:
|
|
467
|
+
raise RuntimeError("A gateway cannot be safely quiesced: " + "; ".join(notices + more))
|
|
468
|
+
entries = supervised + bare
|
|
469
|
+
else:
|
|
470
|
+
from hermes_cli.gateway import _capture_gateway_argv
|
|
471
|
+
_, native_services, _, running = windows._discover_windows_gateways()
|
|
472
|
+
by_pid = {int(service.gateway_pid): service for service in native_services}
|
|
473
|
+
entries = [{"pid": int(pid), "home": pause._home_of(int(pid)), "argv": _capture_gateway_argv(int(pid))} for pid in running]
|
|
474
|
+
for entry in entries:
|
|
475
|
+
service = by_pid.get(entry["pid"])
|
|
476
|
+
if service is not None:
|
|
477
|
+
entry.update(kind="windows-service", service=str(service.name), profile=str(service.profile),
|
|
478
|
+
service_pid=int(service.service_pid), service_ct=float(service.service_create_time),
|
|
479
|
+
descendants=list(getattr(service, "descendant_identities", ())))
|
|
480
|
+
if entry["service_pid"] <= 0 or entry["service_ct"] <= 0:
|
|
481
|
+
raise RuntimeError("A Windows gateway service's process identity could not be proved.")
|
|
482
|
+
if any(not entry["argv"] and not entry.get("kind") for entry in entries):
|
|
483
|
+
raise RuntimeError("A Windows gateway's resume command could not be proved.")
|
|
484
|
+
if any(holder["kind"] != "gateway" or holder["pid"] not in {e["pid"] for e in entries} for holder in windows.list_venv_holders()):
|
|
485
|
+
raise RuntimeError("An Agent environment writer cannot be cooperatively quiesced.")
|
|
486
|
+
pids = {entry["pid"] for entry in entries}
|
|
487
|
+
for entry, live in _writer_ledger(root, identity):
|
|
488
|
+
if live is not True:
|
|
489
|
+
raise RuntimeError(f"Agent writer PID {entry.get('pid')} has no proven live identity; source was not changed.")
|
|
490
|
+
if entry.get('purpose') in {'serve', 'dashboard'} and windows is None:
|
|
491
|
+
backend = services.backend_entry(entry, homes, pause, identity)
|
|
492
|
+
existing = next((e for e in entries if e['pid'] == backend['pid']), None)
|
|
493
|
+
if existing is not None and (existing.get('purpose') != backend['purpose'] or existing.get('home') != backend['home']):
|
|
494
|
+
raise RuntimeError(f"Agent writer PID {backend['pid']} has conflicting native ownership identities.")
|
|
495
|
+
if backend['pid'] not in pids:
|
|
496
|
+
if any(e.get('purpose') in {'serve', 'dashboard'} and services._service_key(e) == services._service_key(backend) for e in entries):
|
|
497
|
+
raise RuntimeError('More than one backend claims the same supervised service.')
|
|
498
|
+
entries.append(backend)
|
|
499
|
+
pids.add(backend['pid'])
|
|
500
|
+
elif entry.get("pid") not in pids:
|
|
501
|
+
raise RuntimeError(f"Agent writer PID {entry.get('pid')} has no proven cooperative pause; stop it through its owner before updating.")
|
|
502
|
+
if any(not entry.get("home") or Path(entry["home"]).resolve() not in homes for entry in entries):
|
|
503
|
+
raise RuntimeError("A gateway's owning profile is unknown or outside this Web instance's scope.")
|
|
504
|
+
return entries
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _writer_ledger(root, identity):
|
|
508
|
+
ledger = identity._read_ledger(identity._ledger_path())
|
|
509
|
+
if ledger is None:
|
|
510
|
+
raise RuntimeError("Agent process ownership evidence is unreadable.")
|
|
511
|
+
try:
|
|
512
|
+
raw = identity._ledger_path().read_text(encoding="utf-8-sig")
|
|
513
|
+
except FileNotFoundError:
|
|
514
|
+
raw = "[]"
|
|
515
|
+
if raw.strip():
|
|
516
|
+
complete = json.loads(raw)
|
|
517
|
+
if not isinstance(complete, list) or not all(isinstance(row, dict) for row in complete):
|
|
518
|
+
raise RuntimeError("Agent process ownership evidence is malformed.")
|
|
519
|
+
found = []
|
|
520
|
+
for entry in ledger:
|
|
521
|
+
if entry.get("purpose") in {"gateway", "serve", "dashboard"} and not entry.get("install"):
|
|
522
|
+
raise RuntimeError("Agent process ownership evidence is incomplete.")
|
|
523
|
+
if entry.get("install") != identity.install_id(root) or entry.get("purpose") not in {"gateway", "serve", "dashboard"}:
|
|
524
|
+
continue
|
|
525
|
+
live = identity._pid_alive_matches(entry.get("pid"), entry.get("create_time"))
|
|
526
|
+
if live is not False:
|
|
527
|
+
found.append((entry, live))
|
|
528
|
+
return found
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _worker(root, revision, homes, requests, stopped, send, refresh_binding=None):
|
|
532
|
+
if os.name not in {"posix", "nt"}:
|
|
533
|
+
raise RuntimeError("A cooperative Agent writer lease is unavailable on this platform; source was not changed.")
|
|
534
|
+
sys.path.insert(0, str(root))
|
|
535
|
+
# Keep the installed updater's resume closure loaded across replacement of its source files.
|
|
536
|
+
modules = {name: importlib.import_module("hermes_cli." + name) for name in (
|
|
537
|
+
"update_lock", "update_cmd_posix_pause", "update_pause_record", "update_cmd_pause_gate",
|
|
538
|
+
"update_cmd_windows", "update_custody", "process_identity", "update_cmd_commit")}
|
|
539
|
+
if any(not Path(module.__file__).resolve().is_relative_to(root) for module in modules.values()):
|
|
540
|
+
raise RuntimeError("Agent writer lease imported a different checkout.")
|
|
541
|
+
pause, records, identity = (modules[name] for name in ("update_cmd_posix_pause", "update_pause_record", "process_identity"))
|
|
542
|
+
windows = modules["update_cmd_windows"] if os.name == "nt" else None
|
|
543
|
+
lock = modules["update_lock"].UpdateLock(install_root=root)
|
|
544
|
+
if not lock.acquire():
|
|
545
|
+
raise RuntimeError("Another Agent update owns the native checkout lock.")
|
|
546
|
+
token = None
|
|
547
|
+
initial = None
|
|
548
|
+
initial_tree = None
|
|
549
|
+
handed_off = False
|
|
550
|
+
sync_failed = False
|
|
551
|
+
error = None
|
|
552
|
+
recovery = None
|
|
553
|
+
gateway_deadline = None
|
|
554
|
+
try:
|
|
555
|
+
recovery = services.ServiceRecovery(root, Path(os.environ['HERMES_HOME']), records, identity, pause)
|
|
556
|
+
found = _command(modules["update_custody"].git_argv(["git"], ["rev-parse", "HEAD"]), root, 10, stopped, modules["update_custody"])
|
|
557
|
+
if not found["ok"]:
|
|
558
|
+
raise RuntimeError("The installed Agent revision could not be verified before quiescence.")
|
|
559
|
+
initial = found["detail"]
|
|
560
|
+
initial_tree = records.tree_state(root)
|
|
561
|
+
if initial_tree is None or initial_tree.get("pre_sha") != initial or initial_tree.get("dirty_at_pause") is None:
|
|
562
|
+
raise RuntimeError("The Agent source baseline could not be proved before quiescence.")
|
|
563
|
+
# Recovery must remain readable after a crash on the target tree. Inspect it without importing it.
|
|
564
|
+
found = _command(modules["update_custody"].git_argv(["git"], ["show", f"{revision}:hermes_cli/update_pause_record.py"]), root, 15, stopped, modules["update_custody"])
|
|
565
|
+
if not found["ok"]:
|
|
566
|
+
raise RuntimeError("The target Agent's durable gateway resume protocol could not be read.")
|
|
567
|
+
reader = found["detail"]
|
|
568
|
+
if not all(re.search(r"^def " + name + r"\(", reader, re.M) for name in ("recover", "tree_is_whole", "adopt_orphans")):
|
|
569
|
+
raise RuntimeError("The target Agent lacks the durable gateway resume protocol.")
|
|
570
|
+
found = _command(modules["update_custody"].git_argv(["git"], ["ls-tree", "-r", "--name-only", revision, "--", "hermes_cli"]), root, 15, stopped, modules["update_custody"])
|
|
571
|
+
target_files = set(found["detail"].splitlines()) if found["ok"] else set()
|
|
572
|
+
if not {f"hermes_cli/{name}.py" for name in (*modules, "main")} <= target_files:
|
|
573
|
+
raise RuntimeError("The target Agent is missing its native update-recovery modules.")
|
|
574
|
+
needs_sync = "hermes_cli/venv_sync.py" in target_files
|
|
575
|
+
adopted, claims = records.adopt_orphans()
|
|
576
|
+
bootstrap_repair = False
|
|
577
|
+
try:
|
|
578
|
+
main_module = importlib.import_module("hermes_cli.main")
|
|
579
|
+
if not Path(main_module.__file__).resolve().is_relative_to(root):
|
|
580
|
+
raise RuntimeError("The Agent update loaded another checkout's runtime.")
|
|
581
|
+
except Exception:
|
|
582
|
+
if (not (adopted or recovery.state['entries']) or (initial != revision and needs_sync) or _writer_ledger(root, identity)
|
|
583
|
+
or any(records.identity_is_live({"pid": int(pid), "ct": ct}) for pid, ct in adopted.get("identities", {}).items())):
|
|
584
|
+
raise RuntimeError("The Agent environment cannot import and its writers cannot be proved quiesced; use native recovery first.")
|
|
585
|
+
bootstrap_repair = True
|
|
586
|
+
if any(e.get('legacy_refresh') for e in recovery.state['entries']):
|
|
587
|
+
recovery.resume(_writer_ledger)
|
|
588
|
+
entries = [] if bootstrap_repair else _owners(root, homes, pause, identity, windows)
|
|
589
|
+
recovery_only = ((bootstrap_repair and needs_sync) or bool(adopted and not records.tree_is_whole(adopted, root)[0])
|
|
590
|
+
or bool(recovery.state['entries'] and (recovery.state.get('sync_failed') or not records.tree_is_whole(recovery.state['tree'], root)[0])))
|
|
591
|
+
# Native gateway birth stamps and journal identities use different units.
|
|
592
|
+
# Bracket and repeat both probes, binding them to the same incarnation before recording it.
|
|
593
|
+
from gateway.status import get_process_start_time
|
|
594
|
+
owner_identities = {}
|
|
595
|
+
legacy = []
|
|
596
|
+
for entry in entries:
|
|
597
|
+
services.bind_processes(entry, records)
|
|
598
|
+
canonical = records.identity(entry["pid"])["ct"]
|
|
599
|
+
birth = get_process_start_time(entry["pid"])
|
|
600
|
+
if canonical is None or birth is None:
|
|
601
|
+
raise RuntimeError("Gateway process identity could not be proved.")
|
|
602
|
+
if (records.identity(entry["pid"])["ct"] != canonical
|
|
603
|
+
or get_process_start_time(entry["pid"]) != birth
|
|
604
|
+
or (entry.get("ct") is not None and entry["ct"] != birth)):
|
|
605
|
+
raise RuntimeError("Gateway process identity changed during quiescence; source was not changed.")
|
|
606
|
+
entry["ct"] = birth
|
|
607
|
+
if entry.get('purpose') in {'dashboard', 'serve'}:
|
|
608
|
+
try:
|
|
609
|
+
services.backend_request(entry)
|
|
610
|
+
except services.BackendLifecycleUnsupported:
|
|
611
|
+
services.verify_listener(entry)
|
|
612
|
+
entry['config_stamp'] = services._config_stamp(entry)
|
|
613
|
+
legacy.append(entry)
|
|
614
|
+
services.verify_processes(entry, records)
|
|
615
|
+
if not entry.get('purpose'):
|
|
616
|
+
owner_identities[str(entry["pid"])] = canonical
|
|
617
|
+
owner_identities.update({pid: p['ct'] for pid, p in entry.get('processes', {}).items()})
|
|
618
|
+
binding = hashlib.sha256(json.dumps({'root': str(root), 'revision': revision, 'tree': initial_tree,
|
|
619
|
+
'homes': sorted(map(str, homes)), 'entries': sorted(legacy, key=services._name)},
|
|
620
|
+
sort_keys=True).encode()).hexdigest()
|
|
621
|
+
if refresh_binding and (not legacy or refresh_binding != binding):
|
|
622
|
+
raise RuntimeError('Legacy refresh target or process identity changed. Check for updates and confirm again.')
|
|
623
|
+
if legacy and not refresh_binding:
|
|
624
|
+
send({'ok': False, 'detail': 'Active dashboard work may be interrupted by a graceful refresh.',
|
|
625
|
+
'refresh_confirmation': {'binding': binding, 'services': [services._name(e) for e in legacy]}})
|
|
626
|
+
return
|
|
627
|
+
if legacy and (recovery_only or bootstrap_repair or initial_tree['dirty_at_pause']
|
|
628
|
+
or (root / '.interrupted').exists()):
|
|
629
|
+
raise RuntimeError('The installed Agent source/dependencies are not safe for a legacy refresh; repair remains owed.')
|
|
630
|
+
gateway_entries = [e for e in entries if not e.get('purpose')]
|
|
631
|
+
backend_entries = [e for e in entries if e.get('purpose') in {'serve', 'dashboard'}]
|
|
632
|
+
token = pause._empty_token()
|
|
633
|
+
if recovery.state['entries']:
|
|
634
|
+
token['baselines'] = recovery.state['tree'].get('baselines', [])
|
|
635
|
+
if windows is not None:
|
|
636
|
+
token.pop("platform", None)
|
|
637
|
+
native_services = [e for e in entries if e.get("kind") == "windows-service"]
|
|
638
|
+
token.update(services=[e["service"] for e in native_services], expected_services=[e["service"] for e in native_services], restarted_services=[],
|
|
639
|
+
service_profiles={e["service"]: e["profile"] for e in native_services})
|
|
640
|
+
token.update(posix_units=[], unmapped=[e for e in gateway_entries if not e.get("kind")],
|
|
641
|
+
unmapped_pids=[e["pid"] for e in gateway_entries if not e.get("kind")],
|
|
642
|
+
identities=owner_identities, resume_needed=bool(gateway_entries))
|
|
643
|
+
records.record_pause(token, adopted, claims)
|
|
644
|
+
recovery.state.setdefault('transaction', {'revision': revision, 'homes': sorted(map(str, homes))})
|
|
645
|
+
recovery.save(token)
|
|
646
|
+
if legacy:
|
|
647
|
+
whole, why = records.tree_is_whole(token, root)
|
|
648
|
+
if not whole:
|
|
649
|
+
raise RuntimeError('Legacy refresh requires verified current source/dependencies: ' + why)
|
|
650
|
+
for entry in legacy:
|
|
651
|
+
recovery.prepare([entry], token)
|
|
652
|
+
recovery.refresh(entry, stopped, _writer_ledger)
|
|
653
|
+
# Bootstrap is bounded to the consented incarnations; every successor now uses the normal fence.
|
|
654
|
+
entries = _owners(root, homes, pause, identity, windows)
|
|
655
|
+
for entry in entries:
|
|
656
|
+
services.bind_processes(entry, records)
|
|
657
|
+
if entry.get('purpose') in {'dashboard', 'serve'}:
|
|
658
|
+
services.backend_request(entry)
|
|
659
|
+
gateway_entries = [e for e in entries if not e.get('purpose')]
|
|
660
|
+
backend_entries = [e for e in entries if e.get('purpose') in {'serve', 'dashboard'}]
|
|
661
|
+
recovery.prepare(backend_entries + [{**e, 'purpose': 'gateway'} for e in gateway_entries if e.get('processes')], token)
|
|
662
|
+
records.mark_stop_requested(token, sorted({pid for e in gateway_entries for pid in (e['pid'], e.get('service_pid', e['pid']))}))
|
|
663
|
+
for entry in backend_entries:
|
|
664
|
+
recovery.stop(entry, stopped)
|
|
665
|
+
# Every profile shares the longest native drain; the lease reports it so its RPC keeps custody.
|
|
666
|
+
gateway_deadline = recovery.gateway_deadline(homes)
|
|
667
|
+
if gateway_entries:
|
|
668
|
+
send({'progress': {'phase': 'gateway_drain', 'deadline_seconds': round(max(gateway_deadline - time.monotonic(), 0), 3),
|
|
669
|
+
'services': sorted({services._name(e) for e in gateway_entries})}})
|
|
670
|
+
for entry in gateway_entries:
|
|
671
|
+
if stopped.is_set() or time.monotonic() >= gateway_deadline:
|
|
672
|
+
raise RuntimeError('Gateway drain has not completed; its durable resume obligation is retained.')
|
|
673
|
+
if entry.get('processes'):
|
|
674
|
+
services.verify_processes(entry, records)
|
|
675
|
+
recovery.gateway_requested(entry)
|
|
676
|
+
for pid in {entry['pid'], entry.get('service_pid', entry['pid'])}:
|
|
677
|
+
records.mark_stop_sent(token, pid)
|
|
678
|
+
# mark_stop_sent is best effort upstream; this producer requires a durable request before sending it.
|
|
679
|
+
records.write(token)
|
|
680
|
+
if not pause._ask_to_drain(entry):
|
|
681
|
+
raise RuntimeError("A gateway did not acknowledge cooperative quiescence; source was not changed.")
|
|
682
|
+
recovery.gateway_requested(entry, acknowledged=True)
|
|
683
|
+
waiting = list(gateway_entries)
|
|
684
|
+
while waiting:
|
|
685
|
+
for entry in list(waiting):
|
|
686
|
+
if not pause._alive(entry["pid"], entry["ct"]):
|
|
687
|
+
if entry.get("kind") == "windows-service":
|
|
688
|
+
_, service = windows._win_service(entry["service"])
|
|
689
|
+
if service.status() != "stopped":
|
|
690
|
+
windows._stop_windows_gateway_service(entry["service"], expected_processes=tuple(map(tuple, entry["descendants"])),
|
|
691
|
+
expected_service_identity=(entry["service_pid"], entry["service_ct"]))
|
|
692
|
+
else:
|
|
693
|
+
if entry.get('processes'):
|
|
694
|
+
recovery.settle_gateway(entry, stopped, deadline=gateway_deadline)
|
|
695
|
+
else:
|
|
696
|
+
pause._disarm(services.service_entry(entry))
|
|
697
|
+
waiting.remove(entry)
|
|
698
|
+
if waiting and (stopped.wait(.05) or time.monotonic() >= gateway_deadline):
|
|
699
|
+
raise RuntimeError("Gateway drain has not completed; its durable resume obligation is retained.")
|
|
700
|
+
if (_writer_ledger(root, identity) if bootstrap_repair else _owners(root, homes, pause, identity, windows)):
|
|
701
|
+
raise RuntimeError("A gateway appeared during quiescence; source was not changed.")
|
|
702
|
+
send({"ok": True})
|
|
703
|
+
last_request = time.monotonic()
|
|
704
|
+
while not stopped.is_set():
|
|
705
|
+
try:
|
|
706
|
+
request = requests.get(timeout=.1)
|
|
707
|
+
except queue.Empty:
|
|
708
|
+
if time.monotonic() - last_request > 60:
|
|
709
|
+
raise RuntimeError("The idle Agent writer lease expired; no further commands are admitted.")
|
|
710
|
+
continue
|
|
711
|
+
last_request = time.monotonic()
|
|
712
|
+
if request["action"] == "finish":
|
|
713
|
+
break
|
|
714
|
+
if (_writer_ledger(root, identity) if bootstrap_repair else _owners(root, homes, pause, identity, windows)):
|
|
715
|
+
raise RuntimeError('An Agent writer appeared during the held lease; source/dependency mutation was refused.')
|
|
716
|
+
if request["action"] == "sync":
|
|
717
|
+
argv = [sys.executable, "-m", "hermes_cli.venv_sync", "--project-root", str(root), "--json"]
|
|
718
|
+
if not (root / "hermes_cli/venv_sync.py").is_file():
|
|
719
|
+
sync_failed = needs_sync
|
|
720
|
+
recovery.state['sync_failed'] = needs_sync
|
|
721
|
+
recovery.save(token)
|
|
722
|
+
send({"ok": not needs_sync, "skipped": True, "detail": "The target's dependency repair module is missing." if needs_sync else ""})
|
|
723
|
+
continue
|
|
724
|
+
sync_failed = True
|
|
725
|
+
recovery.state['sync_failed'] = True
|
|
726
|
+
recovery.save(token)
|
|
727
|
+
result = _command(argv, root, 300, stopped, modules["update_custody"])
|
|
728
|
+
try:
|
|
729
|
+
response = json.loads(result["detail"]) if result["ok"] else {}
|
|
730
|
+
result["ok"] = result["ok"] and response.get("ok") is True
|
|
731
|
+
result["detail"] = str(response.get("detail") or result["detail"])
|
|
732
|
+
except ValueError:
|
|
733
|
+
result = {"ok": False, "detail": "Agent dependency sync returned an invalid result."}
|
|
734
|
+
sync_failed = not result["ok"]
|
|
735
|
+
recovery.state['sync_failed'] = sync_failed
|
|
736
|
+
recovery.state['transaction']['dependencies_verified'] = not sync_failed
|
|
737
|
+
recovery.save(token)
|
|
738
|
+
if not bootstrap_repair and not sync_failed and records.tree_is_whole(token, root)[0]:
|
|
739
|
+
recovery_only = False
|
|
740
|
+
else:
|
|
741
|
+
args = request.get("args")
|
|
742
|
+
if not isinstance(args, list) or not args or not all(isinstance(arg, str) for arg in args):
|
|
743
|
+
raise ValueError("invalid git arguments")
|
|
744
|
+
if recovery_only and args[0] in {"stash", "merge", "apply", "checkout", "clean", "reset"}:
|
|
745
|
+
send({"ok": False, "detail": "A prior Agent update requires source/dependency recovery before another checkout mutation."})
|
|
746
|
+
continue
|
|
747
|
+
seconds = min(max(float(request.get("timeout_ms", 10000)) / 1000, .1), 300)
|
|
748
|
+
applying = args[0] == "apply" and "--check" not in args
|
|
749
|
+
paths = _patch_paths(root, args[-1], stopped, modules["update_custody"]) if applying else []
|
|
750
|
+
before = records.tree_state(root) if applying else None
|
|
751
|
+
whole_before = records.tree_is_whole(token, root)[0] if applying else False
|
|
752
|
+
mutating = args[0] in {"merge", "reset", "checkout", "apply", "clean", "stash"} and "--check" not in args
|
|
753
|
+
gate = modules["update_cmd_pause_gate"]._checkout_move(token, revision, paths=paths, revert=True) if mutating else contextlib.nullcontext({})
|
|
754
|
+
if mutating:
|
|
755
|
+
recovery.state['transaction']['dependencies_verified'] = False
|
|
756
|
+
move_marker = None
|
|
757
|
+
if args[0] == 'merge' and '--ff-only' in args or args[0] == 'reset' and '--hard' in args:
|
|
758
|
+
commit = modules['update_cmd_commit']
|
|
759
|
+
current = records.tree_state(root)
|
|
760
|
+
if current is None or current.get('dirty_at_pause'):
|
|
761
|
+
raise RuntimeError('A source move requires the recorded clean tree; local edits remain retained.')
|
|
762
|
+
# The native rollback receipt is immutable before Git can tear any source file.
|
|
763
|
+
move_marker = commit.arm_tree_move(['git'], root, pre=current['pre_sha'], target=revision, stash=None, rollback='branch')
|
|
764
|
+
with gate as step:
|
|
765
|
+
recovery.save(token)
|
|
766
|
+
argv = modules["update_custody"].git_argv(["git"], args)
|
|
767
|
+
result = _command(argv, root, seconds, stopped, modules["update_custody"])
|
|
768
|
+
if result["ok"] and applying:
|
|
769
|
+
step["whole"] = True
|
|
770
|
+
if move_marker is not None and result['ok']:
|
|
771
|
+
installed = records.tree_state(root)
|
|
772
|
+
if installed and installed['pre_sha'] == revision and installed['dirty_at_pause'] == []:
|
|
773
|
+
move_marker.unlink(missing_ok=True)
|
|
774
|
+
if not needs_sync:
|
|
775
|
+
recovery.state['sync_failed'] = False
|
|
776
|
+
recovery.state['transaction']['dependencies_verified'] = True
|
|
777
|
+
else:
|
|
778
|
+
result = {'ok': False, 'detail': 'The source move did not leave a verified whole target; native rollback remains owed.'}
|
|
779
|
+
if result["ok"] and applying and whole_before:
|
|
780
|
+
after = records.tree_state(root)
|
|
781
|
+
if before is None or after is None or before["pre_sha"] != after["pre_sha"]:
|
|
782
|
+
raise RuntimeError("The completed local restore could not be verified; gateway resume remains owed.")
|
|
783
|
+
# Every carried pause verified this HEAD before the atomic restore; retain the intended edits for all of them.
|
|
784
|
+
for baseline in token.get("baselines", []):
|
|
785
|
+
if baseline.get("pre_sha") == after["pre_sha"]:
|
|
786
|
+
baseline.update(after)
|
|
787
|
+
records.write(token)
|
|
788
|
+
recovery.save(token)
|
|
789
|
+
send(result)
|
|
790
|
+
except BaseException as exc:
|
|
791
|
+
error = str(exc)
|
|
792
|
+
finally:
|
|
793
|
+
try:
|
|
794
|
+
# Cancellation or a lost lease pipe ends the drain wait; the retained debt keeps the gateway owed.
|
|
795
|
+
unwind_deadline = time.monotonic() if stopped.is_set() else gateway_deadline
|
|
796
|
+
if token:
|
|
797
|
+
live = any(records.identity_is_live({"pid": int(pid), "ct": ct}) for pid, ct in token.get("identities", {}).items())
|
|
798
|
+
found = _command(modules["update_custody"].git_argv(["git"], ["rev-parse", "HEAD"]), root, 10, threading.Event(), modules["update_custody"])
|
|
799
|
+
if not found["ok"]:
|
|
800
|
+
raise RuntimeError("Gateway resume remains owed because the Agent revision is unverified.")
|
|
801
|
+
final_tree = records.tree_state(root)
|
|
802
|
+
if final_tree is None or final_tree.get("dirty_at_pause") is None:
|
|
803
|
+
raise RuntimeError("Gateway resume remains owed because the source tree is unverified.")
|
|
804
|
+
changed = found["detail"] != initial or final_tree != initial_tree
|
|
805
|
+
if live and not changed and not sync_failed and records.tree_is_whole(token, root)[0]:
|
|
806
|
+
# An untouched gateway must not strand a backend stopped earlier in a partial pause.
|
|
807
|
+
recovery.save(token)
|
|
808
|
+
recovery.resume(_writer_ledger, gateway_deadline=unwind_deadline)
|
|
809
|
+
if live or sync_failed or (changed and needs_sync and not locals().get("response", {}).get("ok")):
|
|
810
|
+
raise RuntimeError("Gateway resume remains owed until writer drain and dependency repair complete.")
|
|
811
|
+
whole, why = records.tree_is_whole(token, root)
|
|
812
|
+
if not whole:
|
|
813
|
+
raise RuntimeError("Gateway resume remains owed: " + why)
|
|
814
|
+
pending = _command(modules['update_custody'].git_argv(['git'], ['for-each-ref', '--format=%(objectname)', 'refs/talaria/autostash/pending/']), root, 10, threading.Event(), modules['update_custody'])
|
|
815
|
+
if not pending['ok'] or pending['detail']:
|
|
816
|
+
raise RuntimeError('Gateway resume remains owed until retained local Agent edits are restored.')
|
|
817
|
+
if token.get("resume_needed"):
|
|
818
|
+
if bootstrap_repair:
|
|
819
|
+
records.write({**token, "resume_needed": True}, owner=records.UNOWNED)
|
|
820
|
+
handed_off = True
|
|
821
|
+
resumed = _resume_current(root, modules["update_custody"])
|
|
822
|
+
if not resumed["ok"]:
|
|
823
|
+
raise RuntimeError(resumed["detail"])
|
|
824
|
+
token["resume_needed"] = False
|
|
825
|
+
else:
|
|
826
|
+
modules["update_cmd_windows"]._resume_windows_gateways_after_update(token)
|
|
827
|
+
if token.get("resume_needed") or token.get("resume_deferred"):
|
|
828
|
+
raise RuntimeError("Gateway resume could not be verified; its obligation is retained.")
|
|
829
|
+
recovery.save(token)
|
|
830
|
+
recovery.resume(_writer_ledger, gateway_deadline=unwind_deadline)
|
|
831
|
+
if token and not handed_off:
|
|
832
|
+
records.discharge(token)
|
|
833
|
+
from .runtime import AgentRuntime
|
|
834
|
+
_, marker = recovery_receipts(AgentRuntime(Path(os.environ['HERMES_HOME']), root))
|
|
835
|
+
if marker and marker.exists():
|
|
836
|
+
recovery.state['transaction']['runtime_revision'] = found['detail']
|
|
837
|
+
recovery.save(token)
|
|
838
|
+
else:
|
|
839
|
+
recovery.state.pop('transaction', None)
|
|
840
|
+
recovery.save()
|
|
841
|
+
except BaseException as exc:
|
|
842
|
+
error = (error + "; " if error else "") + str(exc)
|
|
843
|
+
if token and not handed_off:
|
|
844
|
+
records.write({**token, "resume_needed": True}, owner=records.UNOWNED)
|
|
845
|
+
finally:
|
|
846
|
+
lock.release()
|
|
847
|
+
send({"ok": error is None, "detail": error or ""})
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
def _resume_current(root, custody, stopped=None, complete_revision=None):
|
|
851
|
+
# Resolve target PM code in a fresh stdlib process, after any source/dependency change.
|
|
852
|
+
try:
|
|
853
|
+
launchers = importlib.import_module('hermes_cli._launchers')
|
|
854
|
+
if not Path(launchers.__file__).resolve().is_relative_to(root):
|
|
855
|
+
raise RuntimeError('Recovery loaded another Agent bootstrap selector.')
|
|
856
|
+
bootstrap = launchers.resolve_store_python(root)
|
|
857
|
+
except ModuleNotFoundError as exc:
|
|
858
|
+
if exc.name != 'hermes_cli._launchers':
|
|
859
|
+
raise
|
|
860
|
+
bootstrap = None
|
|
861
|
+
# A copied macOS venv interpreter can be its own _base_executable and later be retired.
|
|
862
|
+
bootstrap = bootstrap or getattr(sys, '_base_executable', None) or sys.executable
|
|
863
|
+
result = _command([str(bootstrap), "-m", "talaria_sidecar.agent_mutation", "--select-resume", str(root), complete_revision or "", sys.executable], root, 660, stopped or threading.Event(), custody, cooperative=True)
|
|
864
|
+
try:
|
|
865
|
+
reply = json.loads(result["detail"]) if result["ok"] else {}
|
|
866
|
+
return {**reply, "ok": result["ok"] and reply.get("ok") is True, "detail": str(reply.get("detail") or result["detail"])}
|
|
867
|
+
except ValueError:
|
|
868
|
+
return {"ok": False, "detail": "The current Agent could not verify its retained gateway resume obligation."}
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
def _recovery_environment(root):
|
|
872
|
+
"""Reuse PM's selector and packaged launch policy without copying its facts/path rules."""
|
|
873
|
+
sys.path.insert(0, str(root))
|
|
874
|
+
environments = importlib.import_module('pm.environments')
|
|
875
|
+
if any(not Path(sys.modules[name].__file__).resolve().is_relative_to(root)
|
|
876
|
+
for name in ('pm', 'pm.environments', 'hermes_constants')):
|
|
877
|
+
raise RuntimeError('Recovery loaded another Agent environment selector.')
|
|
878
|
+
selected = environments.committed_venv(root)
|
|
879
|
+
if selected is None:
|
|
880
|
+
if environments.install_state_dir(root).exists():
|
|
881
|
+
raise RuntimeError('No dependency environment is committed for this Agent install; recovery remains owed.')
|
|
882
|
+
selected = environments.selected_venv(root)
|
|
883
|
+
return environments, selected
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def _select_resume(root, complete_revision, legacy_python):
|
|
887
|
+
if (root / 'pm').exists():
|
|
888
|
+
environments, selected = _recovery_environment(root)
|
|
889
|
+
command = environments.venv_command(root, selected)
|
|
890
|
+
environment = str(selected)
|
|
891
|
+
else:
|
|
892
|
+
python = next((path for venv in ('.venv', 'venv')
|
|
893
|
+
for path in (root / venv / 'bin/python', root / venv / 'Scripts/python.exe') if path.is_file()), Path(legacy_python))
|
|
894
|
+
command, environment = [str(python)], ''
|
|
895
|
+
# Keep bin/python intact: realpath would turn a POSIX venv into its bare tool.
|
|
896
|
+
argv = [*command, '-m', 'talaria_sidecar.agent_mutation', '--resume-leased', str(root), complete_revision or '', environment]
|
|
897
|
+
os.execv(argv[0], argv)
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def _verify_recovery_environment(root, expected):
|
|
901
|
+
from .runtime import _check_site_packages_python
|
|
902
|
+
environments, selected = _recovery_environment(root)
|
|
903
|
+
if str(selected) != expected or environments.venv_python_version(selected) != sys.version_info[:2]:
|
|
904
|
+
raise RuntimeError('Agent recovery dependency environment changed or has an incompatible Python ABI.')
|
|
905
|
+
site = environments.site_packages(selected)
|
|
906
|
+
if not site.is_dir() or site.resolve() not in {Path(entry).resolve() for entry in sys.path if entry}:
|
|
907
|
+
raise RuntimeError('Agent recovery did not enter its selected dependency environment.')
|
|
908
|
+
_check_site_packages_python()
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
def _resume_worker(root, stopped=None, complete_revision=None):
|
|
912
|
+
sys.path.insert(0, str(root))
|
|
913
|
+
from hermes_cli import update_lock, update_pause_record as records, process_identity, update_cmd_posix_pause
|
|
914
|
+
if any(not Path(module.__file__).resolve().is_relative_to(root) for module in (update_lock, records, process_identity, update_cmd_posix_pause)):
|
|
915
|
+
raise RuntimeError("Gateway recovery loaded another Agent checkout.")
|
|
916
|
+
lock = update_lock.UpdateLock(install_root=root)
|
|
917
|
+
if not lock.acquire():
|
|
918
|
+
raise RuntimeError("The gateway recovery child could not join the native checkout lease.")
|
|
919
|
+
try:
|
|
920
|
+
recovery = services.ServiceRecovery(root, Path(os.environ['HERMES_HOME']), records, process_identity, update_cmd_posix_pause)
|
|
921
|
+
adopted = None
|
|
922
|
+
for _, body in records.orphans():
|
|
923
|
+
adopted = records.merge_into(adopted, body['token'])
|
|
924
|
+
tree = records.merge_into(adopted, recovery.state['tree']) if recovery.state['tree'] else adopted or {}
|
|
925
|
+
from .runtime import AgentRuntime
|
|
926
|
+
runtime = AgentRuntime(Path(os.environ['HERMES_HOME']), root)
|
|
927
|
+
receipts, marker = recovery_receipts(runtime)
|
|
928
|
+
custody = importlib.import_module('hermes_cli.update_custody')
|
|
929
|
+
stopped = stopped or threading.Event()
|
|
930
|
+
from hermes_cli._early_recovery import interrupted_pull_marker
|
|
931
|
+
if interrupted_pull_marker(root).exists():
|
|
932
|
+
return {'ok': False, 'detail': 'Native source recovery remains incomplete; its receipt is retained.'}
|
|
933
|
+
if (root / '.hermes-update-zip-swap').exists():
|
|
934
|
+
from hermes_cli._early_recovery import restore_interrupted_pull
|
|
935
|
+
if restore_interrupted_pull(root):
|
|
936
|
+
return _resume_current(root, custody, stopped, complete_revision)
|
|
937
|
+
if (root / '.update-incomplete').exists() or (root / '.lazy-refresh-incomplete').exists():
|
|
938
|
+
return {'ok': False, 'detail': 'Native source recovery remains incomplete; its receipt is preserved.'}
|
|
939
|
+
if receipts and not tree.get('baselines'):
|
|
940
|
+
return {'ok': False, 'detail': 'Recovery has no verified transaction baseline; its receipt is preserved.'}
|
|
941
|
+
whole, why = records.tree_is_whole(tree, root)
|
|
942
|
+
if not whole:
|
|
943
|
+
return {'ok': False, 'detail': 'Source recovery remains owed: ' + why}
|
|
944
|
+
head = _command(custody.git_argv(['git'], ['rev-parse', 'HEAD']), root, 10, stopped, custody)
|
|
945
|
+
if not head['ok'] or not re.fullmatch(r'[a-f0-9]{40}', head['detail']):
|
|
946
|
+
return {'ok': False, 'detail': 'Recovery cannot verify the installed revision.'}
|
|
947
|
+
revision = head['detail']
|
|
948
|
+
if revision not in {r for b in tree.get('baselines', []) for r in [b.get('pre_sha'), *b.get('move_targets', []), *b.get('landed', {})]}:
|
|
949
|
+
return {'ok': False, 'detail': 'Installed revision is outside the retained transaction; recovery was refused.'}
|
|
950
|
+
moved = any(b.get('pre_sha') != revision for b in tree.get('baselines', []))
|
|
951
|
+
transaction = recovery.state.get('transaction') or {}
|
|
952
|
+
if recovery.state.get('sync_failed') or (moved and not transaction.get('dependencies_verified')) or (marker and marker.exists() and not transaction):
|
|
953
|
+
if _writer_ledger(root, process_identity):
|
|
954
|
+
return {'ok': False, 'detail': 'Recorded dependency repair is waiting for the existing writer drain; no additional stop was sent.'}
|
|
955
|
+
sync = root / 'hermes_cli/venv_sync.py'
|
|
956
|
+
if sync.is_file():
|
|
957
|
+
recovery.state['sync_failed'] = True
|
|
958
|
+
recovery.save(tree)
|
|
959
|
+
synced = _command([sys.executable, '-m', 'hermes_cli.venv_sync', '--project-root', str(root), '--json'], root, 300, stopped, custody)
|
|
960
|
+
response = json.loads(synced['detail']) if synced['ok'] else {}
|
|
961
|
+
if response.get('ok') is not True:
|
|
962
|
+
return {'ok': False, 'detail': 'Recorded dependency repair failed: ' + str(response.get('detail') or synced['detail'])}
|
|
963
|
+
recovery.state['sync_failed'] = False
|
|
964
|
+
transaction = recovery.state.setdefault('transaction', {'revision': revision, 'homes': [str(runtime.hermes_home)]})
|
|
965
|
+
transaction['dependencies_verified'] = True
|
|
966
|
+
recovery.save(tree)
|
|
967
|
+
return _resume_current(root, custody, stopped, complete_revision)
|
|
968
|
+
elif recovery.state.get('sync_failed'):
|
|
969
|
+
return {'ok': False, 'detail': 'The recorded dependency repair module is missing.'}
|
|
970
|
+
whole, why = records.tree_is_whole(tree, root)
|
|
971
|
+
if not whole:
|
|
972
|
+
return {'ok': False, 'detail': 'Recorded source/dependency repair remains unverified: ' + why}
|
|
973
|
+
pending = _command(custody.git_argv(['git'], ['for-each-ref', '--format=%(objectname)', 'refs/talaria/autostash/pending/']), root, 10, stopped, custody)
|
|
974
|
+
if not pending['ok']:
|
|
975
|
+
return {'ok': False, 'detail': 'Retained local edits could not be inspected.'}
|
|
976
|
+
if pending['detail']:
|
|
977
|
+
return {'ok': False, 'local_restore': True, 'revision': revision, 'detail': 'Retained local edits require the existing leased restore path.'}
|
|
978
|
+
main_module = importlib.import_module("hermes_cli.main")
|
|
979
|
+
if not Path(main_module.__file__).resolve().is_relative_to(root):
|
|
980
|
+
raise RuntimeError("Gateway recovery loaded another Agent runtime.")
|
|
981
|
+
records.recover(argv=[])
|
|
982
|
+
try:
|
|
983
|
+
recovery.resume(_writer_ledger)
|
|
984
|
+
except services.GatewayDraining as exc:
|
|
985
|
+
return {'ok': False, 'draining': True, 'detail': str(exc)}
|
|
986
|
+
owed = records.record_path().exists() or bool(records._claims(records.record_path())) or bool(recovery.state['entries'])
|
|
987
|
+
runtime_pending = bool((marker and marker.exists()) or (recovery.state.get('transaction') or {}).get('runtime_revision'))
|
|
988
|
+
if not owed and runtime_pending:
|
|
989
|
+
transaction = recovery.state.setdefault('transaction', {'revision': revision, 'homes': [str(runtime.hermes_home)]})
|
|
990
|
+
if complete_revision == revision and transaction.get('runtime_revision') == revision:
|
|
991
|
+
runtime_pending = False
|
|
992
|
+
else:
|
|
993
|
+
transaction['runtime_revision'] = revision
|
|
994
|
+
recovery.save(tree)
|
|
995
|
+
if not owed and not runtime_pending:
|
|
996
|
+
recovery.state.pop('transaction', None)
|
|
997
|
+
recovery.state['sync_failed'] = False
|
|
998
|
+
recovery.save()
|
|
999
|
+
if marker:
|
|
1000
|
+
marker.unlink(missing_ok=True)
|
|
1001
|
+
return {"ok": not owed, "revision": revision, "runtime_pending": runtime_pending, "detail": "Gateway recovery remains durably recorded." if owed else ""}
|
|
1002
|
+
finally:
|
|
1003
|
+
lock.release()
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
def _patch_paths(root, patch, stopped, custody):
|
|
1007
|
+
result = _command(custody.git_argv(["git"], ["apply", "--numstat", "-z", patch]), root, 10, stopped, custody)
|
|
1008
|
+
if not result["ok"]:
|
|
1009
|
+
raise RuntimeError("Local restore paths could not be bound to the native recovery journal.")
|
|
1010
|
+
rows, paths, index = result["detail"].split("\0"), [], 0
|
|
1011
|
+
while index < len(rows) and rows[index]:
|
|
1012
|
+
fields = rows[index].split("\t", 2)
|
|
1013
|
+
if len(fields) != 3:
|
|
1014
|
+
raise RuntimeError("Invalid local restore path evidence.")
|
|
1015
|
+
if fields[2]:
|
|
1016
|
+
paths.append(fields[2])
|
|
1017
|
+
index += 1
|
|
1018
|
+
else:
|
|
1019
|
+
if index + 2 >= len(rows) or not rows[index + 1] or not rows[index + 2]:
|
|
1020
|
+
raise RuntimeError("Invalid renamed local restore path evidence.")
|
|
1021
|
+
paths.extend(rows[index + 1:index + 3])
|
|
1022
|
+
index += 3
|
|
1023
|
+
return paths
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
def _command(argv, root, timeout, stopped, custody, *, cooperative=False):
|
|
1027
|
+
with tempfile.TemporaryFile() as stdout, tempfile.TemporaryFile() as stderr:
|
|
1028
|
+
child = custody.popen(argv, inherit_lock=True, cwd=root, stdin=subprocess.PIPE if cooperative else subprocess.DEVNULL, stdout=stdout, stderr=stderr,
|
|
1029
|
+
start_new_session=os.name != "nt")
|
|
1030
|
+
deadline = time.monotonic() + timeout
|
|
1031
|
+
try:
|
|
1032
|
+
while child.poll() is None:
|
|
1033
|
+
if stopped.wait(.05) or time.monotonic() >= deadline:
|
|
1034
|
+
if cooperative:
|
|
1035
|
+
child.stdin.close()
|
|
1036
|
+
try:
|
|
1037
|
+
child.wait(timeout=330)
|
|
1038
|
+
except subprocess.TimeoutExpired:
|
|
1039
|
+
child.kill() # The service journal still owns unverified resume.
|
|
1040
|
+
child.wait()
|
|
1041
|
+
else:
|
|
1042
|
+
_stop_tree(child)
|
|
1043
|
+
return {"ok": False, "detail": "Agent update command cancelled or timed out."}
|
|
1044
|
+
out = stdout if child.returncode == 0 else stderr
|
|
1045
|
+
out.seek(0, os.SEEK_END)
|
|
1046
|
+
size = out.tell()
|
|
1047
|
+
if size > 16 * 1024 * 1024:
|
|
1048
|
+
return {"ok": False, "detail": "Agent update command output exceeds limit."}
|
|
1049
|
+
out.seek(0)
|
|
1050
|
+
return {"ok": child.returncode == 0, "detail": out.read().decode("utf-8", errors="replace").strip()}
|
|
1051
|
+
finally:
|
|
1052
|
+
if cooperative:
|
|
1053
|
+
child.stdin.close()
|
|
1054
|
+
if child.poll() is None:
|
|
1055
|
+
_stop_tree(child)
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
def main():
|
|
1059
|
+
for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"):
|
|
1060
|
+
os.environ.pop(key, None)
|
|
1061
|
+
if len(sys.argv) > 2 and sys.argv[1] in {'--recover-current', '--resume', '--resume-leased', '--select-resume'}:
|
|
1062
|
+
try:
|
|
1063
|
+
_native_source_repair(Path(sys.argv[2]).resolve())
|
|
1064
|
+
if sys.argv[1] == '--select-resume':
|
|
1065
|
+
_select_resume(Path(sys.argv[2]).resolve(), sys.argv[3], sys.argv[4])
|
|
1066
|
+
except BaseException as exc:
|
|
1067
|
+
print(json.dumps({'ok': False, 'detail': str(exc) if str(exc) != '1' else 'Native recorded source repair could not complete; its receipt remains owed.'}), flush=True)
|
|
1068
|
+
return
|
|
1069
|
+
from .__main__ import _claim_stdout
|
|
1070
|
+
output = _claim_stdout()
|
|
1071
|
+
stopped, requests = threading.Event(), queue.Queue()
|
|
1072
|
+
def read():
|
|
1073
|
+
try:
|
|
1074
|
+
for line in sys.stdin:
|
|
1075
|
+
requests.put(json.loads(line))
|
|
1076
|
+
finally:
|
|
1077
|
+
stopped.set()
|
|
1078
|
+
threading.Thread(target=read, daemon=True).start()
|
|
1079
|
+
def send(result):
|
|
1080
|
+
output.write((json.dumps(result) + "\n").encode())
|
|
1081
|
+
try:
|
|
1082
|
+
if sys.argv[1] == "--recover-current":
|
|
1083
|
+
root = Path(sys.argv[2]).resolve()
|
|
1084
|
+
sys.path.insert(0, str(root))
|
|
1085
|
+
custody = importlib.import_module('hermes_cli.update_custody')
|
|
1086
|
+
update_lock = importlib.import_module('hermes_cli.update_lock')
|
|
1087
|
+
if any(not Path(module.__file__).resolve().is_relative_to(root) for module in (custody, update_lock)):
|
|
1088
|
+
raise RuntimeError('Recovery loaded another Agent checkout.')
|
|
1089
|
+
lock = update_lock.UpdateLock(install_root=root)
|
|
1090
|
+
if not lock.acquire():
|
|
1091
|
+
raise RuntimeError('Another Agent update owns the native checkout lock.')
|
|
1092
|
+
try:
|
|
1093
|
+
send(_resume_current(root, custody, stopped, sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] else None))
|
|
1094
|
+
finally:
|
|
1095
|
+
lock.release()
|
|
1096
|
+
return
|
|
1097
|
+
if sys.argv[1] in {"--resume", "--resume-leased"}:
|
|
1098
|
+
with contextlib.redirect_stdout(sys.stderr):
|
|
1099
|
+
if len(sys.argv) > 4 and sys.argv[4]:
|
|
1100
|
+
_verify_recovery_environment(Path(sys.argv[2]).resolve(), sys.argv[4])
|
|
1101
|
+
reply = _resume_worker(Path(sys.argv[2]).resolve(), stopped if sys.argv[1] == "--resume-leased" else None, sys.argv[3] if len(sys.argv) > 3 and sys.argv[3] else None)
|
|
1102
|
+
send(reply)
|
|
1103
|
+
return
|
|
1104
|
+
with contextlib.redirect_stdout(sys.stderr):
|
|
1105
|
+
_worker(Path(sys.argv[1]).resolve(), sys.argv[2], {Path(h).resolve() for h in json.loads(sys.argv[3])}, requests, stopped, send, sys.argv[4] if len(sys.argv) > 4 else None)
|
|
1106
|
+
except BaseException as exc:
|
|
1107
|
+
send({"ok": False, "detail": str(exc)})
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
if __name__ == "__main__":
|
|
1111
|
+
main()
|