@ouro.bot/cli 0.1.0-alpha.62 → 0.1.0-alpha.636
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -23
- package/RepairGuide.ouro/agent.json +5 -0
- package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
- package/RepairGuide.ouro/psyche/SOUL.md +55 -0
- package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
- package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
- package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
- package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
- package/changelog.json +4070 -13
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +144 -0
- package/dist/arc/episodes.js +118 -0
- package/dist/arc/intentions.js +134 -0
- package/dist/arc/json-store.js +117 -0
- package/dist/arc/obligations.js +266 -0
- package/dist/arc/packets.js +194 -0
- package/dist/arc/presence.js +185 -0
- package/dist/arc/task-lifecycle.js +57 -0
- package/dist/heart/active-work.js +831 -43
- package/dist/heart/agent-entry.js +69 -3
- package/dist/heart/attachments/image-normalize.js +194 -0
- package/dist/heart/attachments/materialize.js +97 -0
- package/dist/heart/attachments/originals.js +88 -0
- package/dist/heart/attachments/render.js +29 -0
- package/dist/heart/attachments/sources/bluebubbles.js +156 -0
- package/dist/heart/attachments/sources/cli-local-file.js +78 -0
- package/dist/heart/attachments/sources/index.js +16 -0
- package/dist/heart/attachments/store.js +103 -0
- package/dist/heart/attachments/types.js +93 -0
- package/dist/heart/auth/auth-flow.js +479 -0
- package/dist/heart/awaiting/await-alert.js +146 -0
- package/dist/heart/awaiting/await-expiry.js +108 -0
- package/dist/heart/awaiting/await-loader.js +91 -0
- package/dist/heart/awaiting/await-parser.js +141 -0
- package/dist/heart/awaiting/await-runtime-state.js +100 -0
- package/dist/heart/awaiting/await-scheduler.js +377 -0
- package/dist/heart/background-operations.js +281 -0
- package/dist/heart/bridges/manager.js +137 -17
- package/dist/heart/bridges/store.js +14 -2
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +135 -0
- package/dist/heart/config-registry.js +322 -0
- package/dist/heart/config.js +114 -119
- package/dist/heart/core.js +914 -248
- package/dist/heart/cross-chat-delivery.js +3 -18
- package/dist/heart/daemon/agent-config-check.js +419 -0
- package/dist/heart/daemon/agent-discovery.js +102 -3
- package/dist/heart/daemon/agent-service.js +522 -0
- package/dist/heart/daemon/agentic-repair.js +547 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/boot-sync-probe.js +197 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +776 -0
- package/dist/heart/daemon/cli-desk.js +322 -0
- package/dist/heart/daemon/cli-exec.js +7468 -0
- package/dist/heart/daemon/cli-help.js +505 -0
- package/dist/heart/daemon/cli-parse.js +1554 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +763 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/connect-bay.js +323 -0
- package/dist/heart/daemon/daemon-cli.js +29 -1700
- package/dist/heart/daemon/daemon-entry.js +485 -2
- package/dist/heart/daemon/daemon-health.js +176 -0
- package/dist/heart/daemon/daemon-rollup.js +57 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +905 -71
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +873 -0
- package/dist/heart/daemon/health-monitor.js +122 -1
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
- package/dist/heart/daemon/http-health-probe.js +80 -0
- package/dist/heart/daemon/human-command-screens.js +234 -0
- package/dist/heart/daemon/human-readiness.js +114 -0
- package/dist/heart/daemon/inner-status.js +89 -0
- package/dist/heart/daemon/interactive-repair.js +394 -0
- package/dist/heart/daemon/launchd.js +37 -8
- package/dist/heart/daemon/log-tailer.js +79 -10
- package/dist/heart/daemon/logs-prune.js +110 -0
- package/dist/heart/daemon/mcp-canary.js +297 -0
- package/dist/heart/daemon/migrate-to-desk.js +848 -0
- package/dist/heart/daemon/os-cron-deps.js +135 -0
- package/dist/heart/daemon/os-cron.js +14 -12
- package/dist/heart/daemon/ouro-bot-entry.js +4 -2
- package/dist/heart/daemon/ouro-entry.js +3 -1
- package/dist/heart/daemon/plugin-cli.js +432 -0
- package/dist/heart/daemon/process-manager.js +463 -34
- package/dist/heart/daemon/provider-discovery.js +137 -0
- package/dist/heart/daemon/provider-ping-progress.js +83 -0
- package/dist/heart/daemon/pulse.js +475 -0
- package/dist/heart/daemon/readiness-repair.js +365 -0
- package/dist/heart/daemon/run-hooks.js +2 -0
- package/dist/heart/daemon/runtime-logging.js +11 -3
- package/dist/heart/daemon/runtime-metadata.js +2 -30
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +493 -38
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +22 -9
- package/dist/heart/daemon/socket-client.js +158 -11
- package/dist/heart/daemon/stale-bundle-prune.js +96 -0
- package/dist/heart/daemon/startup-tui.js +330 -0
- package/dist/heart/daemon/task-scheduler.js +117 -39
- package/dist/heart/daemon/terminal-ui.js +499 -0
- package/dist/heart/daemon/thoughts.js +229 -17
- package/dist/heart/daemon/up-progress.js +366 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/delegation.js +1 -4
- package/dist/heart/habits/habit-migration.js +189 -0
- package/dist/heart/habits/habit-parser.js +140 -0
- package/dist/heart/habits/habit-runtime-state.js +100 -0
- package/dist/heart/habits/habit-scheduler.js +372 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +32 -56
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
- package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
- package/dist/heart/{daemon → hatch}/specialist-tools.js +37 -14
- package/dist/heart/identity.js +168 -57
- package/dist/heart/kept-notes.js +357 -0
- package/dist/heart/kicks.js +1 -1
- package/dist/heart/machine-identity.js +161 -0
- package/dist/heart/mail-import-discovery.js +353 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +66 -0
- package/dist/heart/mailbox/mailbox-http-response.js +7 -0
- package/dist/heart/mailbox/mailbox-http-routes.js +246 -0
- package/dist/heart/mailbox/mailbox-http-static.js +103 -0
- package/dist/heart/mailbox/mailbox-http-transport.js +116 -0
- package/dist/heart/mailbox/mailbox-http.js +99 -0
- package/dist/heart/mailbox/mailbox-read.js +31 -0
- package/dist/heart/mailbox/mailbox-types.js +27 -0
- package/dist/heart/mailbox/mailbox-view.js +197 -0
- package/dist/heart/mailbox/readers/agent-machine.js +418 -0
- package/dist/heart/mailbox/readers/continuity-readers.js +319 -0
- package/dist/heart/mailbox/readers/mail.js +375 -0
- package/dist/heart/mailbox/readers/runtime-readers.js +756 -0
- package/dist/heart/mailbox/readers/sessions.js +232 -0
- package/dist/heart/mailbox/readers/shared.js +111 -0
- package/dist/heart/mcp/mcp-server.js +656 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +19 -0
- package/dist/heart/orientation-frame.js +217 -0
- package/dist/heart/platform.js +81 -0
- package/dist/heart/provider-attempt.js +134 -0
- package/dist/heart/provider-binding-resolver.js +272 -0
- package/dist/heart/provider-credentials.js +425 -0
- package/dist/heart/provider-failover.js +301 -0
- package/dist/heart/provider-models.js +81 -0
- package/dist/heart/provider-ping.js +262 -0
- package/dist/heart/provider-readiness-cache.js +40 -0
- package/dist/heart/provider-visibility.js +188 -0
- package/dist/heart/providers/anthropic-token.js +131 -0
- package/dist/heart/providers/anthropic.js +139 -52
- package/dist/heart/providers/azure.js +23 -11
- package/dist/heart/providers/error-classification.js +127 -0
- package/dist/heart/providers/github-copilot.js +145 -0
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +26 -8
- package/dist/heart/providers/openai-codex.js +55 -40
- package/dist/heart/runtime-capability-check.js +170 -0
- package/dist/heart/runtime-credentials.js +367 -0
- package/dist/heart/runtime-cwd.js +87 -0
- package/dist/heart/sense-truth.js +13 -4
- package/dist/heart/session-activity.js +48 -24
- package/dist/heart/session-events.js +1163 -0
- package/dist/heart/session-playback-cli-main.js +5 -0
- package/dist/heart/session-playback-cli.js +36 -0
- package/dist/heart/session-playback.js +231 -0
- package/dist/heart/session-stats-cli-main.js +5 -0
- package/dist/heart/session-stats.js +182 -0
- package/dist/heart/session-transcript.js +133 -0
- package/dist/heart/start-of-turn-packet.js +345 -0
- package/dist/heart/streaming.js +44 -27
- package/dist/heart/structured-output.js +196 -0
- package/dist/heart/sync-classification.js +176 -0
- package/dist/heart/sync.js +449 -0
- package/dist/heart/target-resolution.js +9 -5
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -0
- package/dist/heart/timeouts.js +101 -0
- package/dist/heart/tool-activity-callbacks.js +59 -0
- package/dist/heart/tool-description.js +143 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +389 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +426 -0
- package/dist/heart/versioning/ouro-version-manager.js +295 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
- package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
- package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
- package/dist/mailbox-ui/assets/index-9-AxCxuB.js +61 -0
- package/dist/mailbox-ui/assets/index-CWzt267f.css +1 -0
- package/dist/mailbox-ui/index.html +15 -0
- package/dist/mailroom/attention.js +167 -0
- package/dist/mailroom/autonomy.js +209 -0
- package/dist/mailroom/blob-store.js +715 -0
- package/dist/mailroom/body-cache.js +61 -0
- package/dist/mailroom/core.js +788 -0
- package/dist/mailroom/entry.js +160 -0
- package/dist/mailroom/file-store.js +568 -0
- package/dist/mailroom/mbox-import.js +393 -0
- package/dist/mailroom/migration.js +164 -0
- package/dist/mailroom/outbound.js +380 -0
- package/dist/mailroom/policy.js +263 -0
- package/dist/mailroom/reader.js +233 -0
- package/dist/mailroom/search-cache.js +334 -0
- package/dist/mailroom/search-relevance.js +319 -0
- package/dist/mailroom/smtp-ingress.js +176 -0
- package/dist/mailroom/source-state.js +176 -0
- package/dist/mailroom/thread.js +109 -0
- package/dist/mailroom/travel-extract.js +89 -0
- package/dist/mind/bundle-manifest.js +14 -1
- package/dist/mind/context.js +251 -101
- package/dist/mind/desk-section.js +310 -0
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +68 -76
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/friends/channel.js +39 -0
- package/dist/mind/friends/resolver.js +54 -2
- package/dist/mind/friends/store-file.js +48 -4
- package/dist/mind/friends/types.js +2 -2
- package/dist/mind/journal-index.js +162 -0
- package/dist/mind/note-search.js +268 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +6 -1
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +1058 -146
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/scrutiny.js +173 -0
- package/dist/nerves/cli-logging.js +7 -1
- package/dist/nerves/coverage/audit-rules.js +15 -6
- package/dist/nerves/coverage/audit.js +28 -2
- package/dist/nerves/coverage/cli.js +1 -1
- package/dist/nerves/coverage/contract.js +5 -5
- package/dist/nerves/coverage/file-completeness.js +139 -5
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +224 -4
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/redact.js +79 -0
- package/dist/nerves/review/cli-main.js +5 -0
- package/dist/nerves/review/cli.js +156 -0
- package/dist/nerves/review/core.js +152 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +15 -56
- package/dist/repertoire/ado-semantic.js +16 -10
- package/dist/repertoire/api-client.js +97 -0
- package/dist/repertoire/bitwarden-store.js +997 -0
- package/dist/repertoire/bundle-templates.js +72 -0
- package/dist/repertoire/bw-installer.js +180 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +331 -0
- package/dist/repertoire/coding/feedback.js +197 -30
- package/dist/repertoire/coding/manager.js +163 -10
- package/dist/repertoire/coding/spawner.js +55 -9
- package/dist/repertoire/coding/tools.js +177 -7
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- package/dist/repertoire/credential-access.js +178 -0
- package/dist/repertoire/desk/classifier.js +362 -0
- package/dist/repertoire/duffel-client.js +185 -0
- package/dist/repertoire/github-client.js +14 -55
- package/dist/repertoire/graph-client.js +11 -52
- package/dist/repertoire/guardrails.js +385 -0
- package/dist/repertoire/mcp-client.js +295 -0
- package/dist/repertoire/mcp-manager.js +403 -0
- package/dist/repertoire/mcp-tools.js +83 -0
- package/dist/repertoire/plugin-mcp.js +175 -0
- package/dist/repertoire/plugins.js +253 -0
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/skills.js +48 -4
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-attachments.js +317 -0
- package/dist/repertoire/tools-awaiting.js +372 -0
- package/dist/repertoire/tools-base.js +57 -1082
- package/dist/repertoire/tools-bluebubbles.js +2 -0
- package/dist/repertoire/tools-bridge.js +144 -0
- package/dist/repertoire/tools-bundle.js +993 -0
- package/dist/repertoire/tools-config.js +186 -0
- package/dist/repertoire/tools-continuity.js +252 -0
- package/dist/repertoire/tools-credential.js +383 -0
- package/dist/repertoire/tools-files.js +344 -0
- package/dist/repertoire/tools-flight.js +227 -0
- package/dist/repertoire/tools-flow.js +119 -0
- package/dist/repertoire/tools-github.js +3 -8
- package/dist/repertoire/tools-mail.js +1975 -0
- package/dist/repertoire/tools-notes.js +438 -0
- package/dist/repertoire/tools-obligations.js +143 -0
- package/dist/repertoire/tools-orientation.js +31 -0
- package/dist/repertoire/tools-record.js +464 -0
- package/dist/repertoire/tools-runtime.js +150 -0
- package/dist/repertoire/tools-session.js +756 -0
- package/dist/repertoire/tools-shell.js +120 -0
- package/dist/repertoire/tools-stripe.js +182 -0
- package/dist/repertoire/tools-surface.js +316 -0
- package/dist/repertoire/tools-teams.js +12 -39
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-trip.js +982 -0
- package/dist/repertoire/tools-user-profile.js +146 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools-voice.js +145 -0
- package/dist/repertoire/tools.js +215 -103
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/user-profile.js +131 -0
- package/dist/repertoire/vault-setup.js +246 -0
- package/dist/repertoire/vault-unlock.js +594 -0
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/attention-queue.js +116 -0
- package/dist/senses/await-turn-message.js +58 -0
- package/dist/senses/bluebubbles/active-turns.js +216 -0
- package/dist/senses/bluebubbles/attachment-cache.js +53 -0
- package/dist/senses/bluebubbles/attachment-download.js +137 -0
- package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +219 -18
- package/dist/senses/bluebubbles/entry.js +77 -0
- package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
- package/dist/senses/bluebubbles/index.js +2599 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +3 -3
- package/dist/senses/bluebubbles/processed-log.js +133 -0
- package/dist/senses/bluebubbles/replay.js +137 -0
- package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
- package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
- package/dist/senses/bluebubbles-meta-guard.js +40 -0
- package/dist/senses/cli/bracketed-paste.js +82 -0
- package/dist/senses/cli/image-paste.js +287 -0
- package/dist/senses/cli/image-ref-navigation.js +75 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/input-keys.js +174 -0
- package/dist/senses/cli/kill-ring.js +86 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +607 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +60 -0
- package/dist/senses/cli/streaming-markdown.js +526 -0
- package/dist/senses/cli/tool-display.js +85 -0
- package/dist/senses/cli/tool-render.js +85 -0
- package/dist/senses/cli/tui-store.js +240 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +60 -8
- package/dist/senses/cli-layout.js +100 -0
- package/dist/senses/cli.js +517 -204
- package/dist/senses/commands.js +66 -3
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +254 -22
- package/dist/senses/inner-dialog.js +488 -39
- package/dist/senses/mail-entry.js +66 -0
- package/dist/senses/mail.js +379 -0
- package/dist/senses/pipeline.js +666 -181
- package/dist/senses/proactive-content-guard.js +51 -0
- package/dist/senses/shared-turn.js +393 -0
- package/dist/senses/surface-tool.js +70 -0
- package/dist/senses/teams-entry.js +60 -8
- package/dist/senses/teams.js +388 -98
- package/dist/senses/trust-gate.js +100 -5
- package/dist/senses/voice/audio-playback.js +237 -0
- package/dist/senses/voice/audio-routing.js +119 -0
- package/dist/senses/voice/elevenlabs.js +202 -0
- package/dist/senses/voice/floor-control.js +431 -0
- package/dist/senses/voice/floor-controller.js +115 -0
- package/dist/senses/voice/golden-path.js +116 -0
- package/dist/senses/voice/index.js +29 -0
- package/dist/senses/voice/meeting.js +113 -0
- package/dist/senses/voice/outbound.js +190 -0
- package/dist/senses/voice/phone.js +33 -0
- package/dist/senses/voice/playback.js +139 -0
- package/dist/senses/voice/realtime-eval.js +496 -0
- package/dist/senses/voice/realtime-trace.js +531 -0
- package/dist/senses/voice/transcript.js +70 -0
- package/dist/senses/voice/turn.js +191 -0
- package/dist/senses/voice/twilio-phone-runtime.js +807 -0
- package/dist/senses/voice/twilio-phone.js +5079 -0
- package/dist/senses/voice/types.js +2 -0
- package/dist/senses/voice/whisper.js +161 -0
- package/dist/senses/voice-entry.js +81 -0
- package/dist/senses/voice-realtime-eval-command.js +99 -0
- package/dist/senses/voice-realtime-eval-entry.js +21 -0
- package/dist/senses/voice-twilio-entry.js +87 -0
- package/dist/trips/core.js +138 -0
- package/dist/trips/store.js +265 -0
- package/dist/util/frontmatter.js +53 -0
- package/package.json +42 -8
- package/skills/agent-commerce.md +106 -0
- package/skills/browser-navigation.md +117 -0
- package/skills/commerce-setup-guide.md +116 -0
- package/skills/commerce-setup.md +84 -0
- package/skills/configure-dev-tools.md +99 -0
- package/skills/travel-planning.md +138 -0
- package/dist/heart/daemon/auth-flow.js +0 -351
- package/dist/heart/daemon/ouro-path-installer.js +0 -178
- package/dist/heart/safe-workspace.js +0 -228
- package/dist/heart/session-recall.js +0 -116
- package/dist/mind/associative-recall.js +0 -209
- package/dist/repertoire/tasks/board.js +0 -134
- package/dist/repertoire/tasks/index.js +0 -224
- package/dist/repertoire/tasks/lifecycle.js +0 -80
- package/dist/repertoire/tasks/middleware.js +0 -65
- package/dist/repertoire/tasks/parser.js +0 -173
- package/dist/repertoire/tasks/scanner.js +0 -132
- package/dist/repertoire/tasks/transitions.js +0 -144
- package/dist/senses/bluebubbles-entry.js +0 -13
- package/dist/senses/bluebubbles.js +0 -1177
- package/dist/senses/debug-activity.js +0 -148
- package/subagents/README.md +0 -7
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
- /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
- /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
- /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
- /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
- /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
|
@@ -0,0 +1,982 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tripToolDefinitions = void 0;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const types_1 = require("../mind/friends/types");
|
|
6
|
+
const runtime_1 = require("../nerves/runtime");
|
|
7
|
+
const store_1 = require("../trips/store");
|
|
8
|
+
const core_1 = require("../trips/core");
|
|
9
|
+
const identity_1 = require("../heart/identity");
|
|
10
|
+
function trustAllowsTripAccess(ctx) {
|
|
11
|
+
const trustLevel = ctx?.context?.friend?.trustLevel;
|
|
12
|
+
return trustLevel === undefined || (0, types_1.isTrustedLevel)(trustLevel);
|
|
13
|
+
}
|
|
14
|
+
function isRecord(value) {
|
|
15
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function parseJsonArg(raw, label) {
|
|
18
|
+
if (typeof raw !== "string")
|
|
19
|
+
throw new Error(`${label} must be a JSON string`);
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(raw);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw new Error(`${label} is not valid JSON: ${error instanceof Error ? error.message : /* v8 ignore next -- JSON.parse only throws SyntaxError */ String(error)}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function validateTripRecord(value) {
|
|
28
|
+
if (!isRecord(value))
|
|
29
|
+
throw new Error("record must be a TripRecord object");
|
|
30
|
+
// Minimal structural validation — the agent is constructing the value but
|
|
31
|
+
// we still guard against the obvious shape mistakes that would break decrypt.
|
|
32
|
+
for (const field of ["tripId", "agentId", "ownerEmail", "name", "status", "createdAt", "updatedAt"]) {
|
|
33
|
+
if (typeof value[field] !== "string" || value[field].length === 0) {
|
|
34
|
+
throw new Error(`record.${field} must be a non-empty string`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!Array.isArray(value.travellers))
|
|
38
|
+
throw new Error("record.travellers must be an array");
|
|
39
|
+
if (!Array.isArray(value.legs))
|
|
40
|
+
throw new Error("record.legs must be an array");
|
|
41
|
+
for (const leg of value.legs) {
|
|
42
|
+
if (!isRecord(leg))
|
|
43
|
+
throw new Error("each leg must be an object");
|
|
44
|
+
if (typeof leg.legId !== "string" || leg.legId.length === 0)
|
|
45
|
+
throw new Error("each leg requires a legId");
|
|
46
|
+
if (typeof leg.kind !== "string")
|
|
47
|
+
throw new Error("each leg requires a kind");
|
|
48
|
+
if (typeof leg.status !== "string")
|
|
49
|
+
throw new Error("each leg requires a status");
|
|
50
|
+
if (!Array.isArray(leg.evidence))
|
|
51
|
+
throw new Error(`leg ${leg.legId} requires an evidence array`);
|
|
52
|
+
for (const ev of leg.evidence) {
|
|
53
|
+
if (!isRecord(ev))
|
|
54
|
+
throw new Error(`leg ${leg.legId}: each evidence entry must be an object`);
|
|
55
|
+
if (typeof ev.messageId !== "string" || ev.messageId.length === 0)
|
|
56
|
+
throw new Error(`leg ${leg.legId}: evidence.messageId must be a non-empty string`);
|
|
57
|
+
if (typeof ev.discoveryMethod !== "string" || ev.discoveryMethod.length === 0)
|
|
58
|
+
throw new Error(`leg ${leg.legId}: evidence.discoveryMethod must be a non-empty string`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
function validateTripEvidence(value) {
|
|
64
|
+
if (!isRecord(value))
|
|
65
|
+
throw new Error("evidence must be a TripEvidence object");
|
|
66
|
+
for (const field of ["messageId", "reason", "recordedAt", "discoveryMethod"]) {
|
|
67
|
+
if (typeof value[field] !== "string" || value[field].length === 0) {
|
|
68
|
+
throw new Error(`evidence.${field} must be a non-empty string`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
function renderTripSummary(trip) {
|
|
74
|
+
const dateRange = trip.startDate && trip.endDate
|
|
75
|
+
? `${trip.startDate} → ${trip.endDate}`
|
|
76
|
+
: trip.startDate ?? trip.endDate ?? "(no dates)";
|
|
77
|
+
const lines = [
|
|
78
|
+
`- ${trip.tripId} :: "${trip.name}" [${trip.status}; ${dateRange}; legs: ${trip.legs.length}]`,
|
|
79
|
+
` travellers: ${trip.travellers.map((p) => p.name).join(", ") || "(none)"}`,
|
|
80
|
+
];
|
|
81
|
+
if (trip.notes)
|
|
82
|
+
lines.push(` notes: ${trip.notes}`);
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
}
|
|
85
|
+
function compact(parts) {
|
|
86
|
+
return parts
|
|
87
|
+
.map((part) => part?.trim())
|
|
88
|
+
.filter((part) => !!part)
|
|
89
|
+
.join(" ");
|
|
90
|
+
}
|
|
91
|
+
const TRIP_PREVIEW_TOKEN_TTL_MS = 15 * 60 * 1000;
|
|
92
|
+
const pendingTripMutationPreviews = new Map();
|
|
93
|
+
function stableJson(value) {
|
|
94
|
+
if (value === undefined)
|
|
95
|
+
return "undefined";
|
|
96
|
+
if (Array.isArray(value))
|
|
97
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
98
|
+
if (isRecord(value)) {
|
|
99
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
100
|
+
}
|
|
101
|
+
return JSON.stringify(value);
|
|
102
|
+
}
|
|
103
|
+
function previewDigest(value) {
|
|
104
|
+
return (0, node_crypto_1.createHash)("sha256").update(stableJson(value)).digest("hex");
|
|
105
|
+
}
|
|
106
|
+
function pruneExpiredPreviews(nowMs = Date.now()) {
|
|
107
|
+
for (const [token, preview] of pendingTripMutationPreviews.entries()) {
|
|
108
|
+
if (nowMs - preview.createdAtMs > TRIP_PREVIEW_TOKEN_TTL_MS) {
|
|
109
|
+
pendingTripMutationPreviews.delete(token);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function rememberPreview(input) {
|
|
114
|
+
pruneExpiredPreviews();
|
|
115
|
+
const token = `trip_preview_${(0, node_crypto_1.randomUUID)()}`;
|
|
116
|
+
pendingTripMutationPreviews.set(token, { ...input, createdAtMs: Date.now() });
|
|
117
|
+
return token;
|
|
118
|
+
}
|
|
119
|
+
function consumePreview(input) {
|
|
120
|
+
const token = typeof input.token === "string" ? input.token.trim() : "";
|
|
121
|
+
if (!token) {
|
|
122
|
+
const guidance = `previewToken is required. Call ${input.previewToolName} first, inspect the diff, then retry with its previewToken.`;
|
|
123
|
+
return `${guidance}\nAttempted change:\n${input.attemptSummary}`;
|
|
124
|
+
}
|
|
125
|
+
pruneExpiredPreviews();
|
|
126
|
+
const preview = pendingTripMutationPreviews.get(token);
|
|
127
|
+
if (!preview)
|
|
128
|
+
return `previewToken is invalid or expired. Call ${input.previewToolName} again.`;
|
|
129
|
+
const matches = preview.kind === input.kind
|
|
130
|
+
&& preview.agentName === input.agentName
|
|
131
|
+
&& preview.tripId === input.tripId
|
|
132
|
+
&& preview.legId === input.legId
|
|
133
|
+
&& preview.digest === input.digest;
|
|
134
|
+
if (!matches)
|
|
135
|
+
return `previewToken does not match the current trip state or requested change. Call ${input.previewToolName} again.`;
|
|
136
|
+
pendingTripMutationPreviews.delete(token);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
function formatPreviewValue(value) {
|
|
140
|
+
return value === undefined ? "(unset)" : stableJson(value);
|
|
141
|
+
}
|
|
142
|
+
function previewAttemptSummary(renderedPreview) {
|
|
143
|
+
return renderedPreview.replace(/^previewToken: .*\n/, "");
|
|
144
|
+
}
|
|
145
|
+
function changedRecordKeys(before, after, omit = new Set()) {
|
|
146
|
+
const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
147
|
+
return [...keys]
|
|
148
|
+
.filter((key) => !omit.has(key))
|
|
149
|
+
.filter((key) => stableJson(before[key]) !== stableJson(after[key]))
|
|
150
|
+
.sort();
|
|
151
|
+
}
|
|
152
|
+
function legPreviewLabel(trip, leg) {
|
|
153
|
+
const entry = tripLegCalendarEntry(trip, leg);
|
|
154
|
+
return `${entry.title} [${entry.kind}; ${entry.status}; ${calendarEntryRange(entry)}; leg=${entry.legId}]`;
|
|
155
|
+
}
|
|
156
|
+
function updateLegPreviewDigest(input) {
|
|
157
|
+
return previewDigest({
|
|
158
|
+
kind: "update_leg",
|
|
159
|
+
agentName: input.agentName,
|
|
160
|
+
tripId: input.trip.tripId,
|
|
161
|
+
tripUpdatedAt: input.trip.updatedAt,
|
|
162
|
+
leg: input.leg,
|
|
163
|
+
updates: input.updates,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function removeLegPreviewDigest(input) {
|
|
167
|
+
return previewDigest({
|
|
168
|
+
kind: "remove_leg",
|
|
169
|
+
agentName: input.agentName,
|
|
170
|
+
trip: input.trip,
|
|
171
|
+
leg: input.leg,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function replaceTripPreviewDigest(input) {
|
|
175
|
+
return previewDigest({
|
|
176
|
+
kind: "replace_trip",
|
|
177
|
+
agentName: input.agentName,
|
|
178
|
+
currentTrip: input.currentTrip,
|
|
179
|
+
replacementTrip: input.replacementTrip,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function renderUpdateLegPreview(input) {
|
|
183
|
+
const legRecord = input.leg;
|
|
184
|
+
const lines = [
|
|
185
|
+
`previewToken: ${input.token}`,
|
|
186
|
+
`trip: ${input.trip.name} (${input.trip.tripId})`,
|
|
187
|
+
`leg: ${legPreviewLabel(input.trip, input.leg)}`,
|
|
188
|
+
"changes:",
|
|
189
|
+
];
|
|
190
|
+
for (const key of Object.keys(input.updates).sort()) {
|
|
191
|
+
lines.push(`- ${key}: ${formatPreviewValue(legRecord[key])} -> ${formatPreviewValue(input.updates[key])}`);
|
|
192
|
+
}
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
195
|
+
function renderRemoveLegPreview(input) {
|
|
196
|
+
return [
|
|
197
|
+
`previewToken: ${input.token}`,
|
|
198
|
+
`trip: ${input.trip.name} (${input.trip.tripId})`,
|
|
199
|
+
`will remove: ${legPreviewLabel(input.trip, input.leg)}`,
|
|
200
|
+
`trip legs: ${input.trip.legs.length} -> ${input.trip.legs.length - 1}`,
|
|
201
|
+
`evidence on removed leg: ${input.leg.evidence.length}`,
|
|
202
|
+
"removed leg (JSON):",
|
|
203
|
+
JSON.stringify(input.leg, null, 2),
|
|
204
|
+
].join("\n");
|
|
205
|
+
}
|
|
206
|
+
function renderReplaceTripPreview(input) {
|
|
207
|
+
const currentLegs = new Map(input.currentTrip.legs.map((leg) => [leg.legId, leg]));
|
|
208
|
+
const replacementLegs = new Map(input.replacementTrip.legs.map((leg) => [leg.legId, leg]));
|
|
209
|
+
const currentLegIds = new Set(currentLegs.keys());
|
|
210
|
+
const replacementLegIds = new Set(replacementLegs.keys());
|
|
211
|
+
const removed = [...currentLegIds].filter((id) => !replacementLegIds.has(id)).sort();
|
|
212
|
+
const added = [...replacementLegIds].filter((id) => !currentLegIds.has(id)).sort();
|
|
213
|
+
const kept = [...replacementLegIds].filter((id) => currentLegIds.has(id)).sort();
|
|
214
|
+
const lines = [
|
|
215
|
+
`previewToken: ${input.token}`,
|
|
216
|
+
`replace trip: ${input.currentTrip.name} (${input.currentTrip.tripId})`,
|
|
217
|
+
`legs: ${input.currentTrip.legs.length} -> ${input.replacementTrip.legs.length}`,
|
|
218
|
+
"top-level changes:",
|
|
219
|
+
];
|
|
220
|
+
const topLevelChanges = changedRecordKeys(input.currentTrip, input.replacementTrip, new Set(["legs"]));
|
|
221
|
+
if (topLevelChanges.length === 0) {
|
|
222
|
+
lines.push("- (none)");
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
for (const key of topLevelChanges) {
|
|
226
|
+
const before = input.currentTrip;
|
|
227
|
+
const after = input.replacementTrip;
|
|
228
|
+
lines.push(`- ${key}: ${formatPreviewValue(before[key])} -> ${formatPreviewValue(after[key])}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
lines.push("leg changes:");
|
|
232
|
+
let legChangeCount = 0;
|
|
233
|
+
for (const legId of removed) {
|
|
234
|
+
const leg = currentLegs.get(legId);
|
|
235
|
+
lines.push(`- removed ${legId}: ${legPreviewLabel(input.currentTrip, leg)}`);
|
|
236
|
+
legChangeCount += 1;
|
|
237
|
+
}
|
|
238
|
+
for (const legId of added) {
|
|
239
|
+
const leg = replacementLegs.get(legId);
|
|
240
|
+
lines.push(`- added ${legId}: ${legPreviewLabel(input.replacementTrip, leg)}`);
|
|
241
|
+
legChangeCount += 1;
|
|
242
|
+
}
|
|
243
|
+
for (const legId of kept) {
|
|
244
|
+
const before = currentLegs.get(legId);
|
|
245
|
+
const after = replacementLegs.get(legId);
|
|
246
|
+
const fieldChanges = changedRecordKeys(before, after);
|
|
247
|
+
for (const field of fieldChanges) {
|
|
248
|
+
lines.push(`- changed ${legId}.${field}: ${formatPreviewValue(before[field])} -> ${formatPreviewValue(after[field])}`);
|
|
249
|
+
legChangeCount += 1;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (legChangeCount === 0)
|
|
253
|
+
lines.push("- (none)");
|
|
254
|
+
return lines.join("\n");
|
|
255
|
+
}
|
|
256
|
+
function routeLabel(origin, destination) {
|
|
257
|
+
if (origin && destination)
|
|
258
|
+
return `${origin} -> ${destination}`;
|
|
259
|
+
return origin ?? destination;
|
|
260
|
+
}
|
|
261
|
+
function tripLegCalendarEntry(trip, leg) {
|
|
262
|
+
switch (leg.kind) {
|
|
263
|
+
case "lodging":
|
|
264
|
+
return {
|
|
265
|
+
tripId: trip.tripId,
|
|
266
|
+
tripName: trip.name,
|
|
267
|
+
legId: leg.legId,
|
|
268
|
+
kind: leg.kind,
|
|
269
|
+
status: leg.status,
|
|
270
|
+
start: leg.checkInDate,
|
|
271
|
+
end: leg.checkOutDate,
|
|
272
|
+
title: leg.vendor ?? "lodging",
|
|
273
|
+
where: leg.city,
|
|
274
|
+
evidenceIds: leg.evidence.map((entry) => entry.messageId),
|
|
275
|
+
};
|
|
276
|
+
case "flight": {
|
|
277
|
+
const route = routeLabel(leg.origin, leg.destination);
|
|
278
|
+
return {
|
|
279
|
+
tripId: trip.tripId,
|
|
280
|
+
tripName: trip.name,
|
|
281
|
+
legId: leg.legId,
|
|
282
|
+
kind: leg.kind,
|
|
283
|
+
status: leg.status,
|
|
284
|
+
start: leg.departureAt,
|
|
285
|
+
end: leg.arrivalAt,
|
|
286
|
+
title: compact([leg.vendor ?? "flight", leg.flightNumber, route]),
|
|
287
|
+
where: route,
|
|
288
|
+
evidenceIds: leg.evidence.map((entry) => entry.messageId),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
case "train": {
|
|
292
|
+
const route = routeLabel(leg.originStation, leg.destinationStation);
|
|
293
|
+
return {
|
|
294
|
+
tripId: trip.tripId,
|
|
295
|
+
tripName: trip.name,
|
|
296
|
+
legId: leg.legId,
|
|
297
|
+
kind: leg.kind,
|
|
298
|
+
status: leg.status,
|
|
299
|
+
start: leg.departureAt,
|
|
300
|
+
end: leg.arrivalAt,
|
|
301
|
+
title: compact([leg.vendor ?? "train", leg.trainNumber, route]),
|
|
302
|
+
where: route,
|
|
303
|
+
evidenceIds: leg.evidence.map((entry) => entry.messageId),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
case "ground-transport": {
|
|
307
|
+
const route = routeLabel(leg.origin, leg.destination);
|
|
308
|
+
return {
|
|
309
|
+
tripId: trip.tripId,
|
|
310
|
+
tripName: trip.name,
|
|
311
|
+
legId: leg.legId,
|
|
312
|
+
kind: leg.kind,
|
|
313
|
+
status: leg.status,
|
|
314
|
+
start: leg.departureAt,
|
|
315
|
+
end: leg.arrivalAt,
|
|
316
|
+
title: compact([leg.operator ?? leg.vendor ?? "ground transport", route]),
|
|
317
|
+
where: route,
|
|
318
|
+
evidenceIds: leg.evidence.map((entry) => entry.messageId),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
case "rental-car": {
|
|
322
|
+
const route = routeLabel(leg.pickupLocation, leg.dropoffLocation);
|
|
323
|
+
return {
|
|
324
|
+
tripId: trip.tripId,
|
|
325
|
+
tripName: trip.name,
|
|
326
|
+
legId: leg.legId,
|
|
327
|
+
kind: leg.kind,
|
|
328
|
+
status: leg.status,
|
|
329
|
+
start: leg.pickupAt,
|
|
330
|
+
end: leg.dropoffAt,
|
|
331
|
+
title: compact([leg.rentalVendor ?? leg.vendor ?? "rental car", route]),
|
|
332
|
+
where: route,
|
|
333
|
+
evidenceIds: leg.evidence.map((entry) => entry.messageId),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
case "ferry": {
|
|
337
|
+
const route = routeLabel(leg.originPort, leg.destinationPort);
|
|
338
|
+
return {
|
|
339
|
+
tripId: trip.tripId,
|
|
340
|
+
tripName: trip.name,
|
|
341
|
+
legId: leg.legId,
|
|
342
|
+
kind: leg.kind,
|
|
343
|
+
status: leg.status,
|
|
344
|
+
start: leg.departureAt,
|
|
345
|
+
end: leg.arrivalAt,
|
|
346
|
+
title: compact([leg.operator ?? leg.vendor ?? "ferry", route]),
|
|
347
|
+
where: route,
|
|
348
|
+
evidenceIds: leg.evidence.map((entry) => entry.messageId),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
case "event": {
|
|
352
|
+
const where = [leg.venue, leg.city].filter(Boolean).join(", ") || undefined;
|
|
353
|
+
return {
|
|
354
|
+
tripId: trip.tripId,
|
|
355
|
+
tripName: trip.name,
|
|
356
|
+
legId: leg.legId,
|
|
357
|
+
kind: leg.kind,
|
|
358
|
+
status: leg.status,
|
|
359
|
+
start: leg.startsAt,
|
|
360
|
+
end: leg.endsAt,
|
|
361
|
+
title: leg.vendor ?? "event",
|
|
362
|
+
where,
|
|
363
|
+
evidenceIds: leg.evidence.map((entry) => entry.messageId),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function calendarEntryRange(entry) {
|
|
369
|
+
if (entry.start && entry.end && entry.start !== entry.end)
|
|
370
|
+
return `${entry.start} -> ${entry.end}`;
|
|
371
|
+
return entry.start ?? entry.end ?? "(undated)";
|
|
372
|
+
}
|
|
373
|
+
function renderTripCalendar(trips, includeUndated) {
|
|
374
|
+
const entries = trips
|
|
375
|
+
.flatMap((trip) => trip.legs.map((leg) => tripLegCalendarEntry(trip, leg)))
|
|
376
|
+
.filter((entry) => includeUndated || entry.start || entry.end)
|
|
377
|
+
.sort((left, right) => {
|
|
378
|
+
const leftKey = left.start ?? left.end ?? "9999-99-99T99:99:99.999Z";
|
|
379
|
+
const rightKey = right.start ?? right.end ?? "9999-99-99T99:99:99.999Z";
|
|
380
|
+
return leftKey.localeCompare(rightKey) || left.tripName.localeCompare(right.tripName) || left.legId.localeCompare(right.legId);
|
|
381
|
+
});
|
|
382
|
+
if (entries.length === 0)
|
|
383
|
+
return includeUndated ? "no calendar entries on the trip ledger yet." : "no dated calendar entries on the trip ledger yet.";
|
|
384
|
+
const noun = entries.length === 1 ? "entry" : "entries";
|
|
385
|
+
const lines = [`${entries.length} trip calendar ${noun}:`];
|
|
386
|
+
for (const entry of entries) {
|
|
387
|
+
lines.push(`- ${calendarEntryRange(entry)} | ${entry.kind} | ${entry.status} | ${entry.title}`);
|
|
388
|
+
lines.push(` trip: ${entry.tripName} (${entry.tripId}); leg: ${entry.legId}`);
|
|
389
|
+
if (entry.where)
|
|
390
|
+
lines.push(` where: ${entry.where}`);
|
|
391
|
+
if (entry.evidenceIds.length > 0)
|
|
392
|
+
lines.push(` evidence: ${entry.evidenceIds.join(", ")}`);
|
|
393
|
+
}
|
|
394
|
+
return lines.join("\n");
|
|
395
|
+
}
|
|
396
|
+
exports.tripToolDefinitions = [
|
|
397
|
+
{
|
|
398
|
+
tool: {
|
|
399
|
+
type: "function",
|
|
400
|
+
function: {
|
|
401
|
+
name: "trip_ensure_ledger",
|
|
402
|
+
description: "Idempotently ensure this agent has a trip ledger keypair. Safe to call multiple times. Required once before any other trip_ tool.",
|
|
403
|
+
parameters: { type: "object", properties: {} },
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
handler: async (_args, ctx) => {
|
|
407
|
+
if (!trustAllowsTripAccess(ctx))
|
|
408
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
409
|
+
const result = (0, store_1.ensureAgentTripLedger)({ agentName: (0, identity_1.getAgentName)() });
|
|
410
|
+
const verb = result.added ? "created" : "already present";
|
|
411
|
+
return `trip ledger ${verb}: ledgerId=${result.ledger.ledgerId}, keyId=${result.ledger.keyId}, createdAt=${result.ledger.createdAt}`;
|
|
412
|
+
},
|
|
413
|
+
summaryKeys: [],
|
|
414
|
+
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "creates trip ledger state" },
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
tool: {
|
|
418
|
+
type: "function",
|
|
419
|
+
function: {
|
|
420
|
+
name: "trip_status",
|
|
421
|
+
description: "List the agent's trip ids in sorted order. Cheap overview before opening individual trips.",
|
|
422
|
+
parameters: { type: "object", properties: {} },
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
handler: async (_args, ctx) => {
|
|
426
|
+
if (!trustAllowsTripAccess(ctx))
|
|
427
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
428
|
+
const tripIds = (0, store_1.listTripIds)((0, identity_1.getAgentName)());
|
|
429
|
+
if (tripIds.length === 0)
|
|
430
|
+
return "no trips on the ledger yet.";
|
|
431
|
+
return `${tripIds.length} trip(s):\n${tripIds.map((id) => `- ${id}`).join("\n")}`;
|
|
432
|
+
},
|
|
433
|
+
summaryKeys: [],
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
tool: {
|
|
437
|
+
type: "function",
|
|
438
|
+
function: {
|
|
439
|
+
name: "trip_get",
|
|
440
|
+
description: "Read one trip record by id. Returns a structured summary plus the raw JSON for further reasoning.",
|
|
441
|
+
parameters: {
|
|
442
|
+
type: "object",
|
|
443
|
+
properties: {
|
|
444
|
+
tripId: { type: "string", description: "Canonical trip id (trip_<slug>_<fingerprint>)." },
|
|
445
|
+
},
|
|
446
|
+
required: ["tripId"],
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
handler: async (args, ctx) => {
|
|
451
|
+
if (!trustAllowsTripAccess(ctx))
|
|
452
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
453
|
+
const tripId = args.tripId;
|
|
454
|
+
if (typeof tripId !== "string" || tripId.length === 0)
|
|
455
|
+
return "tripId is required.";
|
|
456
|
+
try {
|
|
457
|
+
const trip = (0, store_1.readTripRecord)((0, identity_1.getAgentName)(), tripId);
|
|
458
|
+
return [
|
|
459
|
+
renderTripSummary(trip),
|
|
460
|
+
"",
|
|
461
|
+
"raw record (JSON):",
|
|
462
|
+
JSON.stringify(trip, null, 2),
|
|
463
|
+
].join("\n");
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
if (error instanceof store_1.TripNotFoundError)
|
|
467
|
+
return error.message;
|
|
468
|
+
throw error;
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
summaryKeys: ["tripId"],
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
tool: {
|
|
475
|
+
type: "function",
|
|
476
|
+
function: {
|
|
477
|
+
name: "trip_replace_preview",
|
|
478
|
+
description: "Preview replacing an existing trip record before calling trip_upsert. Returns a short-lived previewToken that trip_upsert requires when the record already exists.",
|
|
479
|
+
parameters: {
|
|
480
|
+
type: "object",
|
|
481
|
+
properties: {
|
|
482
|
+
record: { type: "string", description: "Full replacement TripRecord JSON, same shape accepted by trip_upsert." },
|
|
483
|
+
},
|
|
484
|
+
required: ["record"],
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
handler: async (args, ctx) => {
|
|
489
|
+
if (!trustAllowsTripAccess(ctx))
|
|
490
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
491
|
+
try {
|
|
492
|
+
const parsed = parseJsonArg(args.record, "record");
|
|
493
|
+
const replacementTrip = validateTripRecord(parsed);
|
|
494
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
495
|
+
const currentTrip = (0, store_1.readTripRecord)(agentName, replacementTrip.tripId);
|
|
496
|
+
const digest = replaceTripPreviewDigest({ agentName, currentTrip, replacementTrip });
|
|
497
|
+
const token = rememberPreview({ kind: "replace_trip", agentName, tripId: replacementTrip.tripId, digest });
|
|
498
|
+
return renderReplaceTripPreview({ currentTrip, replacementTrip, token });
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
if (error instanceof store_1.TripNotFoundError)
|
|
502
|
+
return "no existing trip record found; new trip creation does not require trip_replace_preview.";
|
|
503
|
+
return `replace preview failed: ${error instanceof Error ? error.message : /* v8 ignore next -- non-Error throw is unreachable from validateTripRecord/parseJsonArg */ String(error)}`;
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
summaryKeys: [],
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
tool: {
|
|
510
|
+
type: "function",
|
|
511
|
+
function: {
|
|
512
|
+
name: "trip_upsert",
|
|
513
|
+
description: "Create or replace a TripRecord. Pass the full record as a JSON string in `record`. Every leg requires a legId and an evidence array (each evidence entry requires messageId + discoveryMethod). Replacing an existing record requires trip_replace_preview first and its previewToken. Returns the persisted tripId.",
|
|
514
|
+
parameters: {
|
|
515
|
+
type: "object",
|
|
516
|
+
properties: {
|
|
517
|
+
record: { type: "string", description: "Full TripRecord JSON. Must include tripId, agentId, ownerEmail, name, status, travellers[], legs[], createdAt, updatedAt." },
|
|
518
|
+
writeReason: { type: "string", description: "Required when replacing an existing trip record. One-line source or reason that makes the whole-record replacement correct." },
|
|
519
|
+
previewToken: { type: "string", description: "Required when replacing an existing trip record. Get it from trip_replace_preview after inspecting the replacement diff." },
|
|
520
|
+
},
|
|
521
|
+
required: ["record"],
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
handler: async (args, ctx) => {
|
|
526
|
+
if (!trustAllowsTripAccess(ctx))
|
|
527
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
528
|
+
try {
|
|
529
|
+
const parsed = parseJsonArg(args.record, "record");
|
|
530
|
+
const trip = validateTripRecord(parsed);
|
|
531
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
532
|
+
(0, store_1.ensureAgentTripLedger)({ agentName });
|
|
533
|
+
let replacesExisting = false;
|
|
534
|
+
let currentTrip;
|
|
535
|
+
try {
|
|
536
|
+
currentTrip = (0, store_1.readTripRecord)(agentName, trip.tripId);
|
|
537
|
+
replacesExisting = true;
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
if (!(error instanceof store_1.TripNotFoundError))
|
|
541
|
+
throw error;
|
|
542
|
+
}
|
|
543
|
+
const writeReason = typeof args.writeReason === "string" ? args.writeReason.trim() : "";
|
|
544
|
+
if (replacesExisting && writeReason.length === 0) {
|
|
545
|
+
return "writeReason is required when replacing an existing trip record.";
|
|
546
|
+
}
|
|
547
|
+
if (replacesExisting && currentTrip) {
|
|
548
|
+
const digest = replaceTripPreviewDigest({ agentName, currentTrip, replacementTrip: trip });
|
|
549
|
+
const previewError = consumePreview({
|
|
550
|
+
token: args.previewToken,
|
|
551
|
+
kind: "replace_trip",
|
|
552
|
+
agentName,
|
|
553
|
+
tripId: trip.tripId,
|
|
554
|
+
digest,
|
|
555
|
+
previewToolName: "trip_replace_preview",
|
|
556
|
+
attemptSummary: previewAttemptSummary(renderReplaceTripPreview({ currentTrip, replacementTrip: trip, token: "" })),
|
|
557
|
+
});
|
|
558
|
+
if (previewError)
|
|
559
|
+
return previewError;
|
|
560
|
+
}
|
|
561
|
+
(0, store_1.upsertTripRecord)(agentName, trip);
|
|
562
|
+
if (replacesExisting) {
|
|
563
|
+
(0, runtime_1.emitNervesEvent)({
|
|
564
|
+
component: "trips",
|
|
565
|
+
event: "trips.record_replaced",
|
|
566
|
+
message: "trip record replaced with write reason",
|
|
567
|
+
meta: { agentId: agentName, tripId: trip.tripId, legCount: trip.legs.length, status: trip.status, writeReason: writeReason.slice(0, 240) },
|
|
568
|
+
});
|
|
569
|
+
return `trip replaced: ${trip.tripId} (${trip.legs.length} leg(s), status=${trip.status}). reason: ${writeReason}`;
|
|
570
|
+
}
|
|
571
|
+
return `trip upserted: ${trip.tripId} (${trip.legs.length} leg(s), status=${trip.status})`;
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
return `upsert failed: ${error instanceof Error ? error.message : /* v8 ignore next -- non-Error throw is unreachable from validateTripRecord/parseJsonArg */ String(error)}`;
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
summaryKeys: [],
|
|
578
|
+
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "creates or replaces trip records" },
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
tool: {
|
|
582
|
+
type: "function",
|
|
583
|
+
function: {
|
|
584
|
+
name: "trip_attach_evidence",
|
|
585
|
+
description: "Append a TripEvidence record to a specific leg's evidence array. Pass tripId, legId, and the evidence as a JSON string. Useful when extracting a fact from a single mail message and attaching it to an existing leg without re-uploading the whole record.",
|
|
586
|
+
parameters: {
|
|
587
|
+
type: "object",
|
|
588
|
+
properties: {
|
|
589
|
+
tripId: { type: "string", description: "Canonical trip id." },
|
|
590
|
+
legId: { type: "string", description: "Leg id within the trip." },
|
|
591
|
+
evidence: { type: "string", description: "TripEvidence JSON: { messageId, reason, recordedAt, discoveryMethod, excerpt? }." },
|
|
592
|
+
},
|
|
593
|
+
required: ["tripId", "legId", "evidence"],
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
handler: async (args, ctx) => {
|
|
598
|
+
if (!trustAllowsTripAccess(ctx))
|
|
599
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
600
|
+
const tripId = args.tripId;
|
|
601
|
+
const legId = args.legId;
|
|
602
|
+
if (typeof tripId !== "string" || tripId.length === 0)
|
|
603
|
+
return "tripId is required.";
|
|
604
|
+
if (typeof legId !== "string" || legId.length === 0)
|
|
605
|
+
return "legId is required.";
|
|
606
|
+
try {
|
|
607
|
+
const evidence = validateTripEvidence(parseJsonArg(args.evidence, "evidence"));
|
|
608
|
+
const trip = (0, store_1.readTripRecord)((0, identity_1.getAgentName)(), tripId);
|
|
609
|
+
const legIndex = trip.legs.findIndex((leg) => leg.legId === legId);
|
|
610
|
+
if (legIndex === -1)
|
|
611
|
+
return `leg ${legId} not found in trip ${tripId}.`;
|
|
612
|
+
const leg = trip.legs[legIndex];
|
|
613
|
+
const updatedLeg = {
|
|
614
|
+
...leg,
|
|
615
|
+
evidence: [...leg.evidence, evidence],
|
|
616
|
+
updatedAt: evidence.recordedAt,
|
|
617
|
+
};
|
|
618
|
+
const updated = {
|
|
619
|
+
...trip,
|
|
620
|
+
legs: [...trip.legs.slice(0, legIndex), updatedLeg, ...trip.legs.slice(legIndex + 1)],
|
|
621
|
+
updatedAt: evidence.recordedAt,
|
|
622
|
+
};
|
|
623
|
+
(0, store_1.upsertTripRecord)((0, identity_1.getAgentName)(), updated);
|
|
624
|
+
(0, runtime_1.emitNervesEvent)({
|
|
625
|
+
component: "trips",
|
|
626
|
+
event: "trips.evidence_attached",
|
|
627
|
+
message: "trip evidence attached to leg",
|
|
628
|
+
meta: { agentId: (0, identity_1.getAgentName)(), tripId, legId, discoveryMethod: evidence.discoveryMethod, messageId: evidence.messageId },
|
|
629
|
+
});
|
|
630
|
+
return `evidence attached to leg ${legId} in ${tripId}; leg now carries ${updatedLeg.evidence.length} evidence entries.`;
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
if (error instanceof store_1.TripNotFoundError)
|
|
634
|
+
return error.message;
|
|
635
|
+
return `attach failed: ${error instanceof Error ? error.message : /* v8 ignore next -- non-Error throw is unreachable from validateTripEvidence/parseJsonArg/store */ String(error)}`;
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
summaryKeys: ["tripId", "legId"],
|
|
639
|
+
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "appends trip evidence" },
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
tool: {
|
|
643
|
+
type: "function",
|
|
644
|
+
function: {
|
|
645
|
+
name: "trip_update_leg_preview",
|
|
646
|
+
description: "Preview a trip_update_leg mutation before committing it. Returns a diff plus a short-lived previewToken that trip_update_leg requires.",
|
|
647
|
+
parameters: {
|
|
648
|
+
type: "object",
|
|
649
|
+
properties: {
|
|
650
|
+
tripId: { type: "string", description: "Canonical trip id." },
|
|
651
|
+
legId: { type: "string", description: "Leg id within the trip." },
|
|
652
|
+
updates: { type: "string", description: "JSON object of leg fields to update. Cannot include `legId` or `kind`." },
|
|
653
|
+
},
|
|
654
|
+
required: ["tripId", "legId", "updates"],
|
|
655
|
+
},
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
handler: async (args, ctx) => {
|
|
659
|
+
if (!trustAllowsTripAccess(ctx))
|
|
660
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
661
|
+
const tripId = args.tripId;
|
|
662
|
+
const legId = args.legId;
|
|
663
|
+
if (typeof tripId !== "string" || tripId.length === 0)
|
|
664
|
+
return "tripId is required.";
|
|
665
|
+
if (typeof legId !== "string" || legId.length === 0)
|
|
666
|
+
return "legId is required.";
|
|
667
|
+
try {
|
|
668
|
+
const updates = parseJsonArg(args.updates, "updates");
|
|
669
|
+
if (!isRecord(updates))
|
|
670
|
+
return "updates must be a JSON object.";
|
|
671
|
+
if ("legId" in updates)
|
|
672
|
+
return "updates cannot change legId; create a new leg instead.";
|
|
673
|
+
if ("kind" in updates)
|
|
674
|
+
return "updates cannot change kind; create a new leg instead.";
|
|
675
|
+
if (Object.keys(updates).length === 0)
|
|
676
|
+
return "updates cannot be empty — pass at least one field.";
|
|
677
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
678
|
+
const trip = (0, store_1.readTripRecord)(agentName, tripId);
|
|
679
|
+
const leg = trip.legs.find((candidate) => candidate.legId === legId);
|
|
680
|
+
if (!leg)
|
|
681
|
+
return `leg ${legId} not found in trip ${tripId}.`;
|
|
682
|
+
const digest = updateLegPreviewDigest({ agentName, trip, leg, updates });
|
|
683
|
+
const token = rememberPreview({ kind: "update_leg", agentName, tripId, legId, digest });
|
|
684
|
+
return renderUpdateLegPreview({ trip, leg, updates, token });
|
|
685
|
+
}
|
|
686
|
+
catch (error) {
|
|
687
|
+
if (error instanceof store_1.TripNotFoundError)
|
|
688
|
+
return error.message;
|
|
689
|
+
return `update preview failed: ${error instanceof Error ? error.message : /* v8 ignore next -- non-Error throw is unreachable from parseJsonArg/store */ String(error)}`;
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
summaryKeys: ["tripId", "legId"],
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
tool: {
|
|
696
|
+
type: "function",
|
|
697
|
+
function: {
|
|
698
|
+
name: "trip_update_leg",
|
|
699
|
+
description: "Update specific fields of an existing leg in a trip. Pass tripId, legId, and a JSON object of field updates (e.g. {status:\"cancelled\", confirmationCode:\"PNR123\"}). Existing evidence is preserved unless explicitly overwritten. Use this instead of trip_upsert when you only need to change one leg without re-emitting the whole record. The leg's `kind` cannot be changed (changing kind means a new leg). Requires trip_update_leg_preview first and its previewToken.",
|
|
700
|
+
parameters: {
|
|
701
|
+
type: "object",
|
|
702
|
+
properties: {
|
|
703
|
+
tripId: { type: "string", description: "Canonical trip id." },
|
|
704
|
+
legId: { type: "string", description: "Leg id within the trip." },
|
|
705
|
+
updates: { type: "string", description: "JSON object of leg fields to update. Cannot include `legId` or `kind`. Common fields: status, confirmationCode, vendor, amount, checkInDate, checkOutDate, departureTime, arrivalTime, etc." },
|
|
706
|
+
updatedAt: { type: "string", description: "ISO timestamp for the update. Used both for the leg's updatedAt and the trip's updatedAt." },
|
|
707
|
+
updateReason: { type: "string", description: "One-line source or reason that makes this update correct. Required so semantic trip writes remain auditable." },
|
|
708
|
+
previewToken: { type: "string", description: "Required. Get it from trip_update_leg_preview after inspecting the diff." },
|
|
709
|
+
},
|
|
710
|
+
required: ["tripId", "legId", "updates", "updatedAt", "updateReason", "previewToken"],
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
},
|
|
714
|
+
handler: async (args, ctx) => {
|
|
715
|
+
if (!trustAllowsTripAccess(ctx))
|
|
716
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
717
|
+
const tripId = args.tripId;
|
|
718
|
+
const legId = args.legId;
|
|
719
|
+
const updatedAt = args.updatedAt;
|
|
720
|
+
const updateReason = typeof args.updateReason === "string" ? args.updateReason.trim() : "";
|
|
721
|
+
if (typeof tripId !== "string" || tripId.length === 0)
|
|
722
|
+
return "tripId is required.";
|
|
723
|
+
if (typeof legId !== "string" || legId.length === 0)
|
|
724
|
+
return "legId is required.";
|
|
725
|
+
if (typeof updatedAt !== "string" || updatedAt.length === 0)
|
|
726
|
+
return "updatedAt is required.";
|
|
727
|
+
if (updateReason.length === 0)
|
|
728
|
+
return "updateReason is required.";
|
|
729
|
+
try {
|
|
730
|
+
const updates = parseJsonArg(args.updates, "updates");
|
|
731
|
+
if (!isRecord(updates))
|
|
732
|
+
return "updates must be a JSON object.";
|
|
733
|
+
// Reject identity-changing fields — those would silently break referential integrity.
|
|
734
|
+
if ("legId" in updates)
|
|
735
|
+
return "updates cannot change legId; create a new leg instead.";
|
|
736
|
+
if ("kind" in updates)
|
|
737
|
+
return "updates cannot change kind; create a new leg instead.";
|
|
738
|
+
if (Object.keys(updates).length === 0)
|
|
739
|
+
return "updates cannot be empty — pass at least one field.";
|
|
740
|
+
const trip = (0, store_1.readTripRecord)((0, identity_1.getAgentName)(), tripId);
|
|
741
|
+
const legIndex = trip.legs.findIndex((leg) => leg.legId === legId);
|
|
742
|
+
if (legIndex === -1)
|
|
743
|
+
return `leg ${legId} not found in trip ${tripId}.`;
|
|
744
|
+
const leg = trip.legs[legIndex];
|
|
745
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
746
|
+
const digest = updateLegPreviewDigest({ agentName, trip, leg, updates });
|
|
747
|
+
const previewError = consumePreview({
|
|
748
|
+
token: args.previewToken,
|
|
749
|
+
kind: "update_leg",
|
|
750
|
+
agentName,
|
|
751
|
+
tripId,
|
|
752
|
+
legId,
|
|
753
|
+
digest,
|
|
754
|
+
previewToolName: "trip_update_leg_preview",
|
|
755
|
+
attemptSummary: previewAttemptSummary(renderUpdateLegPreview({ trip, leg, updates, token: "" })),
|
|
756
|
+
});
|
|
757
|
+
if (previewError)
|
|
758
|
+
return previewError;
|
|
759
|
+
const updatedLeg = {
|
|
760
|
+
...leg,
|
|
761
|
+
...updates,
|
|
762
|
+
legId: leg.legId,
|
|
763
|
+
kind: leg.kind,
|
|
764
|
+
updatedAt,
|
|
765
|
+
};
|
|
766
|
+
const updated = {
|
|
767
|
+
...trip,
|
|
768
|
+
legs: [...trip.legs.slice(0, legIndex), updatedLeg, ...trip.legs.slice(legIndex + 1)],
|
|
769
|
+
updatedAt,
|
|
770
|
+
};
|
|
771
|
+
(0, store_1.upsertTripRecord)(agentName, updated);
|
|
772
|
+
(0, runtime_1.emitNervesEvent)({
|
|
773
|
+
component: "trips",
|
|
774
|
+
event: "trips.leg_updated",
|
|
775
|
+
message: "trip leg fields updated",
|
|
776
|
+
meta: { agentId: agentName, tripId, legId, fields: Object.keys(updates), updateReason: updateReason.slice(0, 240) },
|
|
777
|
+
});
|
|
778
|
+
const fieldList = Object.keys(updates).join(", ");
|
|
779
|
+
return `leg ${legId} updated in ${tripId}: ${fieldList}. reason: ${updateReason}`;
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
if (error instanceof store_1.TripNotFoundError)
|
|
783
|
+
return error.message;
|
|
784
|
+
return `update failed: ${error instanceof Error ? error.message : /* v8 ignore next -- non-Error throw is unreachable from parseJsonArg/store */ String(error)}`;
|
|
785
|
+
}
|
|
786
|
+
},
|
|
787
|
+
summaryKeys: ["tripId", "legId"],
|
|
788
|
+
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "updates trip leg fields" },
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
tool: {
|
|
792
|
+
type: "function",
|
|
793
|
+
function: {
|
|
794
|
+
name: "trip_remove_leg_preview",
|
|
795
|
+
description: "Preview removing a leg from a trip before committing it. Returns the leg summary plus a short-lived previewToken that trip_remove_leg requires.",
|
|
796
|
+
parameters: {
|
|
797
|
+
type: "object",
|
|
798
|
+
properties: {
|
|
799
|
+
tripId: { type: "string", description: "Canonical trip id." },
|
|
800
|
+
legId: { type: "string", description: "Leg id within the trip to drop." },
|
|
801
|
+
},
|
|
802
|
+
required: ["tripId", "legId"],
|
|
803
|
+
},
|
|
804
|
+
},
|
|
805
|
+
},
|
|
806
|
+
handler: async (args, ctx) => {
|
|
807
|
+
if (!trustAllowsTripAccess(ctx))
|
|
808
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
809
|
+
const tripId = args.tripId;
|
|
810
|
+
const legId = args.legId;
|
|
811
|
+
if (typeof tripId !== "string" || tripId.length === 0)
|
|
812
|
+
return "tripId is required.";
|
|
813
|
+
if (typeof legId !== "string" || legId.length === 0)
|
|
814
|
+
return "legId is required.";
|
|
815
|
+
try {
|
|
816
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
817
|
+
const trip = (0, store_1.readTripRecord)(agentName, tripId);
|
|
818
|
+
const leg = trip.legs.find((candidate) => candidate.legId === legId);
|
|
819
|
+
if (!leg)
|
|
820
|
+
return `leg ${legId} not found in trip ${tripId}.`;
|
|
821
|
+
const digest = removeLegPreviewDigest({ agentName, trip, leg });
|
|
822
|
+
const token = rememberPreview({ kind: "remove_leg", agentName, tripId, legId, digest });
|
|
823
|
+
return renderRemoveLegPreview({ trip, leg, token });
|
|
824
|
+
}
|
|
825
|
+
catch (error) {
|
|
826
|
+
if (error instanceof store_1.TripNotFoundError)
|
|
827
|
+
return error.message;
|
|
828
|
+
return `remove preview failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
summaryKeys: ["tripId", "legId"],
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
tool: {
|
|
835
|
+
type: "function",
|
|
836
|
+
function: {
|
|
837
|
+
name: "trip_remove_leg",
|
|
838
|
+
description: "Remove a leg from a trip. Use when a leg was added by mistake or the booking was cancelled. Updates the trip's updatedAt. Rejects when the leg id is unknown so accidental no-op removals are visible. Requires trip_remove_leg_preview first and its previewToken.",
|
|
839
|
+
parameters: {
|
|
840
|
+
type: "object",
|
|
841
|
+
properties: {
|
|
842
|
+
tripId: { type: "string", description: "Canonical trip id." },
|
|
843
|
+
legId: { type: "string", description: "Leg id within the trip to drop." },
|
|
844
|
+
updatedAt: { type: "string", description: "ISO timestamp for the trip's updatedAt." },
|
|
845
|
+
reason: { type: "string", description: "Why the leg is being removed. Logged in nerves for audit." },
|
|
846
|
+
previewToken: { type: "string", description: "Required. Get it from trip_remove_leg_preview after inspecting the leg removal preview." },
|
|
847
|
+
},
|
|
848
|
+
required: ["tripId", "legId", "updatedAt", "reason", "previewToken"],
|
|
849
|
+
},
|
|
850
|
+
},
|
|
851
|
+
},
|
|
852
|
+
handler: async (args, ctx) => {
|
|
853
|
+
if (!trustAllowsTripAccess(ctx))
|
|
854
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
855
|
+
const tripId = args.tripId;
|
|
856
|
+
const legId = args.legId;
|
|
857
|
+
const updatedAt = args.updatedAt;
|
|
858
|
+
const reason = typeof args.reason === "string" ? args.reason.trim() : "";
|
|
859
|
+
if (typeof tripId !== "string" || tripId.length === 0)
|
|
860
|
+
return "tripId is required.";
|
|
861
|
+
if (typeof legId !== "string" || legId.length === 0)
|
|
862
|
+
return "legId is required.";
|
|
863
|
+
if (typeof updatedAt !== "string" || updatedAt.length === 0)
|
|
864
|
+
return "updatedAt is required.";
|
|
865
|
+
if (reason.length === 0)
|
|
866
|
+
return "reason is required.";
|
|
867
|
+
try {
|
|
868
|
+
const trip = (0, store_1.readTripRecord)((0, identity_1.getAgentName)(), tripId);
|
|
869
|
+
const legIndex = trip.legs.findIndex((leg) => leg.legId === legId);
|
|
870
|
+
if (legIndex === -1)
|
|
871
|
+
return `leg ${legId} not found in trip ${tripId}.`;
|
|
872
|
+
const droppedLeg = trip.legs[legIndex];
|
|
873
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
874
|
+
const digest = removeLegPreviewDigest({ agentName, trip, leg: droppedLeg });
|
|
875
|
+
const previewError = consumePreview({
|
|
876
|
+
token: args.previewToken,
|
|
877
|
+
kind: "remove_leg",
|
|
878
|
+
agentName,
|
|
879
|
+
tripId,
|
|
880
|
+
legId,
|
|
881
|
+
digest,
|
|
882
|
+
previewToolName: "trip_remove_leg_preview",
|
|
883
|
+
attemptSummary: previewAttemptSummary(renderRemoveLegPreview({ trip, leg: droppedLeg, token: "" })),
|
|
884
|
+
});
|
|
885
|
+
if (previewError)
|
|
886
|
+
return previewError;
|
|
887
|
+
const updated = {
|
|
888
|
+
...trip,
|
|
889
|
+
legs: [...trip.legs.slice(0, legIndex), ...trip.legs.slice(legIndex + 1)],
|
|
890
|
+
updatedAt,
|
|
891
|
+
};
|
|
892
|
+
(0, store_1.upsertTripRecord)(agentName, updated);
|
|
893
|
+
(0, runtime_1.emitNervesEvent)({
|
|
894
|
+
component: "trips",
|
|
895
|
+
event: "trips.leg_removed",
|
|
896
|
+
message: "trip leg removed from ledger",
|
|
897
|
+
meta: {
|
|
898
|
+
agentId: agentName,
|
|
899
|
+
tripId,
|
|
900
|
+
legId,
|
|
901
|
+
kind: droppedLeg.kind,
|
|
902
|
+
reason,
|
|
903
|
+
},
|
|
904
|
+
});
|
|
905
|
+
/* v8 ignore next -- pluralization branch: tests don't exhaustively cover both 1-leg and N-leg removal outcomes @preserve */
|
|
906
|
+
return `leg ${legId} removed from ${tripId}. trip now has ${updated.legs.length} leg${updated.legs.length === 1 ? "" : "s"}.`;
|
|
907
|
+
} /* v8 ignore start -- error-classification branches: TripNotFoundError vs unexpected store failure; the latter is covered by trip-store unit tests rather than tool-level fixtures @preserve */
|
|
908
|
+
catch (error) {
|
|
909
|
+
if (error instanceof store_1.TripNotFoundError)
|
|
910
|
+
return error.message;
|
|
911
|
+
return `remove failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
912
|
+
} /* v8 ignore stop */
|
|
913
|
+
},
|
|
914
|
+
summaryKeys: ["tripId", "legId", "reason"],
|
|
915
|
+
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "removes trip legs" },
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
tool: {
|
|
919
|
+
type: "function",
|
|
920
|
+
function: {
|
|
921
|
+
name: "trip_calendar",
|
|
922
|
+
description: "Render a chronological calendar/agenda projection from the trip ledger. Use this before answering current itinerary, travel gap, or what-changed questions; friend notes and old handoffs may be stale. Also use this after extracting mail-backed trip facts so the agent can track dates across lodging, travel, events, and local transport.",
|
|
923
|
+
parameters: {
|
|
924
|
+
type: "object",
|
|
925
|
+
properties: {
|
|
926
|
+
tripId: { type: "string", description: "Optional canonical trip id. Omit to render all trips on the ledger." },
|
|
927
|
+
includeUndated: { type: "string", enum: ["true", "false"], description: "Set true to include legs that have no start/end dates yet. Defaults to false." },
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
},
|
|
931
|
+
},
|
|
932
|
+
handler: async (args, ctx) => {
|
|
933
|
+
if (!trustAllowsTripAccess(ctx))
|
|
934
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
935
|
+
const includeUndated = args.includeUndated === "true";
|
|
936
|
+
const tripId = typeof args.tripId === "string" ? args.tripId.trim() : "";
|
|
937
|
+
try {
|
|
938
|
+
const trips = tripId
|
|
939
|
+
? [(0, store_1.readTripRecord)((0, identity_1.getAgentName)(), tripId)]
|
|
940
|
+
: (0, store_1.listTripIds)((0, identity_1.getAgentName)()).map((id) => (0, store_1.readTripRecord)((0, identity_1.getAgentName)(), id));
|
|
941
|
+
if (trips.length === 0)
|
|
942
|
+
return "no trips on the ledger yet.";
|
|
943
|
+
return renderTripCalendar(trips, includeUndated);
|
|
944
|
+
}
|
|
945
|
+
catch (error) {
|
|
946
|
+
if (error instanceof store_1.TripNotFoundError)
|
|
947
|
+
return error.message;
|
|
948
|
+
throw error;
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
summaryKeys: ["tripId"],
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
tool: {
|
|
955
|
+
type: "function",
|
|
956
|
+
function: {
|
|
957
|
+
name: "trip_new_id",
|
|
958
|
+
description: "Compute a deterministic trip id from agentId + name + createdAt. Useful before constructing a new TripRecord so the id is stable and reproducible.",
|
|
959
|
+
parameters: {
|
|
960
|
+
type: "object",
|
|
961
|
+
properties: {
|
|
962
|
+
name: { type: "string", description: "Human-friendly trip name (e.g. \"Europe summer 2026\")." },
|
|
963
|
+
createdAt: { type: "string", description: "ISO timestamp the trip was first conceived. Pass `now` if just creating it." },
|
|
964
|
+
},
|
|
965
|
+
required: ["name", "createdAt"],
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
},
|
|
969
|
+
handler: async (args, ctx) => {
|
|
970
|
+
if (!trustAllowsTripAccess(ctx))
|
|
971
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
972
|
+
const name = args.name;
|
|
973
|
+
const createdAt = args.createdAt;
|
|
974
|
+
if (typeof name !== "string" || name.length === 0)
|
|
975
|
+
return "name is required.";
|
|
976
|
+
if (typeof createdAt !== "string" || createdAt.length === 0)
|
|
977
|
+
return "createdAt is required.";
|
|
978
|
+
return (0, core_1.newTripId)((0, identity_1.getAgentName)(), name, createdAt);
|
|
979
|
+
},
|
|
980
|
+
summaryKeys: ["name"],
|
|
981
|
+
},
|
|
982
|
+
];
|