auramaxx 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +26 -0
- package/README.md +77 -0
- package/apps/desktop-electron/main.js +428 -0
- package/bin/auramaxx.js +1063 -0
- package/docs/ADAPTERS.md +466 -0
- package/docs/AGENT_SETUP.md +159 -0
- package/docs/API.md +127 -0
- package/docs/APPS.md +199 -0
- package/docs/ARCHITECTURE.md +235 -0
- package/docs/AUTH.md +318 -0
- package/docs/BEST-PRACTICES.md +82 -0
- package/docs/CLI.md +141 -0
- package/docs/DESKTOP_ELECTRON.md +26 -0
- package/docs/DEVELOPING-APPS.md +453 -0
- package/docs/MCP.md +122 -0
- package/docs/PACKAGING_POLICY.md +19 -0
- package/docs/PERMISSION.md +137 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/README.md +50 -0
- package/docs/SKILLS.md +132 -0
- package/docs/TROUBLESHOOTING.md +376 -0
- package/docs/WORKSPACE.md +673 -0
- package/docs/agent-auth.md +14 -0
- package/docs/api/authentication.md +79 -0
- package/docs/api/secrets/api-keys.md +28 -0
- package/docs/api/secrets/credentials.md +80 -0
- package/docs/api/secrets/sharing.md +48 -0
- package/docs/api/system.md +41 -0
- package/docs/api/wallets/apps-strategies.md +66 -0
- package/docs/api/wallets/core.md +46 -0
- package/docs/api/wallets/data-portfolio.md +42 -0
- package/docs/aura-file.md +48 -0
- package/docs/core-concepts/FEATURES.md +114 -0
- package/docs/credentials.md +120 -0
- package/docs/external/HOW_TO_AURAMAXX/GETTING_SECRETS.md +33 -0
- package/docs/external/HOW_TO_AURAMAXX/README.md +45 -0
- package/docs/external/getting-started.md +10 -0
- package/docs/external/overview.md +19 -0
- package/docs/external/persona-paths.md +7 -0
- package/docs/external/share-secret.md +76 -0
- package/docs/external/why-aura.md +7 -0
- package/docs/security.md +227 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
- package/docs/wallet/AI.md +508 -0
- package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
- package/docs/wallet/README.md +47 -0
- package/docs/wallet/STRATEGY.md +89 -0
- package/next.config.ts +28 -0
- package/package.json +167 -0
- package/postcss.config.mjs +8 -0
- package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
- package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
- package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
- package/prisma/migrations/20260222090000_update_admin_ttl_default/migration.sql +10 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/actions.ts +474 -0
- package/server/cli/commands/api.ts +220 -0
- package/server/cli/commands/apikey.ts +277 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/auth.ts +464 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/diary.ts +274 -0
- package/server/cli/commands/doctor.ts +1247 -0
- package/server/cli/commands/env.ts +476 -0
- package/server/cli/commands/experimental.ts +69 -0
- package/server/cli/commands/init.ts +798 -0
- package/server/cli/commands/lock.ts +157 -0
- package/server/cli/commands/mcp.ts +285 -0
- package/server/cli/commands/quickhack.ts +86 -0
- package/server/cli/commands/release-check.ts +231 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/service.ts +320 -0
- package/server/cli/commands/shell-hook.ts +512 -0
- package/server/cli/commands/skill.ts +216 -0
- package/server/cli/commands/start.ts +139 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +36 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +50 -0
- package/server/cli/commands/vault.ts +1323 -0
- package/server/cli/commands/wallet.ts +209 -0
- package/server/cli/index.ts +280 -0
- package/server/cli/lib/approval-poll.ts +94 -0
- package/server/cli/lib/aura-parser.ts +64 -0
- package/server/cli/lib/credential-create.ts +74 -0
- package/server/cli/lib/credential-resolve.ts +280 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/escalation.ts +57 -0
- package/server/cli/lib/http.ts +91 -0
- package/server/cli/lib/init-steps.ts +76 -0
- package/server/cli/lib/local-agent-trust.ts +45 -0
- package/server/cli/lib/lock-unlock-helper.ts +71 -0
- package/server/cli/lib/process.ts +162 -0
- package/server/cli/lib/prompt.ts +294 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +579 -0
- package/server/cli/transport-client.ts +50 -0
- package/server/cron/index.ts +137 -0
- package/server/cron/job.ts +31 -0
- package/server/cron/jobs/balance-sync.ts +436 -0
- package/server/cron/jobs/incoming-scan.ts +506 -0
- package/server/cron/jobs/native-price.ts +70 -0
- package/server/cron/jobs/orphan-cleanup.ts +40 -0
- package/server/cron/jobs/strategy-runner.ts +175 -0
- package/server/cron/scheduler.ts +125 -0
- package/server/index.ts +420 -0
- package/server/lib/adapters/factory.ts +119 -0
- package/server/lib/adapters/index.ts +19 -0
- package/server/lib/adapters/router.ts +297 -0
- package/server/lib/adapters/telegram.ts +645 -0
- package/server/lib/adapters/types.ts +89 -0
- package/server/lib/adapters/webhook.ts +95 -0
- package/server/lib/address.ts +49 -0
- package/server/lib/agent-auth/contracts.ts +1194 -0
- package/server/lib/agent-profiles.ts +419 -0
- package/server/lib/ai.ts +285 -0
- package/server/lib/api-registry/contracts.ts +86 -0
- package/server/lib/api-registry/validation.ts +172 -0
- package/server/lib/apikey-migration.ts +258 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/approval-link.ts +27 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/auto-execute.ts +160 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +1048 -0
- package/server/lib/config.ts +408 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +111 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +608 -0
- package/server/lib/credential-scope.ts +102 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +533 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +422 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +58 -0
- package/server/lib/defaults.ts +386 -0
- package/server/lib/dex/index.ts +80 -0
- package/server/lib/dex/relay.ts +235 -0
- package/server/lib/dex/types.ts +59 -0
- package/server/lib/dex/uniswap.ts +370 -0
- package/server/lib/diary.ts +34 -0
- package/server/lib/dont-ask-again-policy.ts +41 -0
- package/server/lib/e2e-agent/artifacts.ts +36 -0
- package/server/lib/e2e-agent/contracts.ts +112 -0
- package/server/lib/e2e-agent/validation.ts +135 -0
- package/server/lib/encrypt.ts +114 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +217 -0
- package/server/lib/feature-flags.ts +93 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/human-action-summary.ts +80 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +340 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +230 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +71 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +299 -0
- package/server/lib/pino.ts +24 -0
- package/server/lib/policy-preview.ts +138 -0
- package/server/lib/price.ts +338 -0
- package/server/lib/prices.ts +34 -0
- package/server/lib/project-scope.ts +297 -0
- package/server/lib/resolve-action.ts +328 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/secret-gist-share.ts +296 -0
- package/server/lib/sessions.ts +634 -0
- package/server/lib/socket-path.ts +56 -0
- package/server/lib/solana/connection.ts +26 -0
- package/server/lib/solana/jupiter.ts +128 -0
- package/server/lib/solana/transfer.ts +108 -0
- package/server/lib/solana/wallet.ts +136 -0
- package/server/lib/strategy/emits.ts +21 -0
- package/server/lib/strategy/engine.ts +1305 -0
- package/server/lib/strategy/executor.ts +115 -0
- package/server/lib/strategy/hook-context.ts +159 -0
- package/server/lib/strategy/hooks.ts +990 -0
- package/server/lib/strategy/index.ts +28 -0
- package/server/lib/strategy/installer.ts +305 -0
- package/server/lib/strategy/loader.ts +256 -0
- package/server/lib/strategy/message.ts +237 -0
- package/server/lib/strategy/repository.ts +218 -0
- package/server/lib/strategy/session-logger.ts +693 -0
- package/server/lib/strategy/sources.ts +288 -0
- package/server/lib/strategy/state.ts +189 -0
- package/server/lib/strategy/templates.ts +403 -0
- package/server/lib/strategy/tick.ts +404 -0
- package/server/lib/strategy/types.ts +230 -0
- package/server/lib/swap.ts +3 -0
- package/server/lib/temp.ts +86 -0
- package/server/lib/token-metadata.ts +86 -0
- package/server/lib/token-safety.ts +200 -0
- package/server/lib/token-search.ts +444 -0
- package/server/lib/totp.ts +194 -0
- package/server/lib/transactions.ts +123 -0
- package/server/lib/transport.ts +84 -0
- package/server/lib/txhistory/decoder.ts +262 -0
- package/server/lib/txhistory/enricher.ts +652 -0
- package/server/lib/txhistory/index.ts +391 -0
- package/server/lib/txhistory/signatures.ts +59 -0
- package/server/lib/update-check.ts +35 -0
- package/server/lib/verified-summary.ts +414 -0
- package/server/lib/view-registry.ts +80 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +1589 -0
- package/server/mcp/tools.ts +276 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +539 -0
- package/server/routes/adapters.ts +711 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +343 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +406 -0
- package/server/routes/backup.ts +404 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +380 -0
- package/server/routes/credential-vaults.ts +159 -0
- package/server/routes/credentials.ts +1782 -0
- package/server/routes/dashboard.ts +97 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/flags.ts +11 -0
- package/server/routes/fund.ts +225 -0
- package/server/routes/heartbeat.ts +375 -0
- package/server/routes/import.ts +364 -0
- package/server/routes/launch.ts +665 -0
- package/server/routes/lock.ts +54 -0
- package/server/routes/logs.ts +68 -0
- package/server/routes/nuke.ts +111 -0
- package/server/routes/passkey-credentials.ts +99 -0
- package/server/routes/passkey.ts +366 -0
- package/server/routes/portfolio.ts +217 -0
- package/server/routes/price.ts +63 -0
- package/server/routes/resolve.ts +31 -0
- package/server/routes/security.ts +45 -0
- package/server/routes/send-evm.ts +241 -0
- package/server/routes/send-solana.ts +281 -0
- package/server/routes/send.ts +178 -0
- package/server/routes/setup.ts +210 -0
- package/server/routes/strategy.ts +894 -0
- package/server/routes/swap-evm.ts +352 -0
- package/server/routes/swap-solana.ts +176 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +467 -0
- package/server/routes/views.ts +41 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +709 -0
- package/server/types.ts +146 -0
- package/shared/credential-field-schema.ts +248 -0
- package/skills/auramaxx/HEARTBEAT.md +78 -0
- package/skills/auramaxx/SKILL.md +745 -0
- package/skills/auramaxx/docs/AGENT_SETUP.md +155 -0
- package/skills/auramaxx/docs/API.md +127 -0
- package/skills/auramaxx/docs/AUTH.md +318 -0
- package/skills/auramaxx/docs/CLI.md +130 -0
- package/skills/auramaxx/docs/MCP.md +122 -0
- package/skills/auramaxx/docs/TROUBLESHOOTING.md +357 -0
- package/skills/auramaxx/docs/WORKSPACE.md +673 -0
- package/skills/auramaxx/docs/security.md +227 -0
- package/skills/task-lifecycle/SKILL.md +378 -0
- package/src/app/api/[...doc]/page.tsx +36 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +132 -0
- package/src/app/api/apps/manifests/route.ts +16 -0
- package/src/app/api/apps/static/[...path]/route.ts +57 -0
- package/src/app/api/docs/plain/route.ts +74 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +290 -0
- package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
- package/src/app/api/workspace/[id]/apps/route.ts +81 -0
- package/src/app/api/workspace/[id]/export/route.ts +67 -0
- package/src/app/api/workspace/[id]/route.ts +168 -0
- package/src/app/api/workspace/auth.ts +40 -0
- package/src/app/api/workspace/config/route.ts +121 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app-legacy-do-not-use/page.tsx +2245 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/approve/[actionId]/page.tsx +409 -0
- package/src/app/docs/DocsPageContent.tsx +269 -0
- package/src/app/docs/[...doc]/page.tsx +41 -0
- package/src/app/docs/page.tsx +38 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +819 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +102 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +39 -0
- package/src/app/page.tsx +1964 -0
- package/src/app/privacy/page.tsx +63 -0
- package/src/app/providers.tsx +87 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/app/terms/page.tsx +80 -0
- package/src/components/ChainSelector.tsx +44 -0
- package/src/components/HumanActionBar.tsx +697 -0
- package/src/components/NotificationDrawer.tsx +387 -0
- package/src/components/PasskeyEnrollmentPrompt.tsx +235 -0
- package/src/components/apps/AgentKeysApp.tsx +490 -0
- package/src/components/apps/App.tsx +153 -0
- package/src/components/apps/AppGrid.tsx +15 -0
- package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
- package/src/components/apps/DraggableApp.tsx +562 -0
- package/src/components/apps/IFrameApp.tsx +73 -0
- package/src/components/apps/LogsApp.tsx +360 -0
- package/src/components/apps/SendApp.tsx +394 -0
- package/src/components/apps/SetupWizardApp.tsx +1004 -0
- package/src/components/apps/SystemDefaultsApp.tsx +845 -0
- package/src/components/apps/ThirdPartyApp.tsx +428 -0
- package/src/components/apps/TokenApp.tsx +319 -0
- package/src/components/apps/TransactionsApp.tsx +438 -0
- package/src/components/apps/WalletDetailApp.tsx +1505 -0
- package/src/components/apps/index.ts +13 -0
- package/src/components/design-system/Button.tsx +88 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +147 -0
- package/src/components/design-system/ConfirmationModal.tsx +107 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/DownloadButton.tsx +149 -0
- package/src/components/design-system/Drawer.tsx +133 -0
- package/src/components/design-system/FilterDropdown.tsx +183 -0
- package/src/components/design-system/ItemPicker.tsx +157 -0
- package/src/components/design-system/Modal.tsx +296 -0
- package/src/components/design-system/Popover.tsx +142 -0
- package/src/components/design-system/TextInput.tsx +85 -0
- package/src/components/design-system/Toggle.tsx +65 -0
- package/src/components/design-system/TyvekCollapsibleSection.tsx +55 -0
- package/src/components/design-system/index.ts +14 -0
- package/src/components/docs/ClientSideMarkdown.tsx +51 -0
- package/src/components/docs/DocsSearchBar.tsx +118 -0
- package/src/components/docs/DocsThemeToggle.tsx +38 -0
- package/src/components/docs/PersistentDocGroup.tsx +91 -0
- package/src/components/docs/ShareUrlButton.tsx +33 -0
- package/src/components/docs/SidebarScrollMemory.tsx +56 -0
- package/src/components/health/CredentialHealthDashboard.tsx +214 -0
- package/src/components/icons/ChainIcons.tsx +72 -0
- package/src/components/layout/AppStoreDrawer.tsx +369 -0
- package/src/components/layout/ContentArea.tsx +21 -0
- package/src/components/layout/CreateViewModal.tsx +88 -0
- package/src/components/layout/LeftRail.tsx +114 -0
- package/src/components/layout/TabBar.tsx +284 -0
- package/src/components/layout/WalletSidebar.tsx +1030 -0
- package/src/components/layout/index.ts +6 -0
- package/src/components/marketing/AuraMaxxSpecOverlay.tsx +653 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1272 -0
- package/src/components/vault/AuditConsole.tsx +600 -0
- package/src/components/vault/CredentialDetail.tsx +625 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +583 -0
- package/src/components/vault/CredentialForm.tsx +1484 -0
- package/src/components/vault/CredentialList.tsx +265 -0
- package/src/components/vault/CredentialRow.tsx +130 -0
- package/src/components/vault/CredentialShareModal.tsx +273 -0
- package/src/components/vault/CredentialVault.tsx +1662 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/DocsConsole.tsx +113 -0
- package/src/components/vault/ImportCredentialsModal.tsx +578 -0
- package/src/components/vault/LargeTypeModal.tsx +88 -0
- package/src/components/vault/PasswordGenerator.tsx +232 -0
- package/src/components/vault/TOTPDisplay.tsx +108 -0
- package/src/components/vault/TotpSetupPanel.tsx +198 -0
- package/src/components/vault/VaultSidebar.tsx +881 -0
- package/src/components/vault/credentialFormName.ts +91 -0
- package/src/components/vault/hooks/useVaultKeyboardShortcuts.ts +69 -0
- package/src/components/vault/types.ts +56 -0
- package/src/context/AuthContext.tsx +365 -0
- package/src/context/PriceContext.tsx +113 -0
- package/src/context/ThemeContext.tsx +164 -0
- package/src/context/WebSocketContext.tsx +269 -0
- package/src/context/WorkspaceContext.tsx +668 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useAgentActions.ts +552 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/hooks/useTheme.ts +156 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api-docs.ts +154 -0
- package/src/lib/api.ts +474 -0
- package/src/lib/app-loader.ts +148 -0
- package/src/lib/app-registry.ts +178 -0
- package/src/lib/app-sdk.ts +157 -0
- package/src/lib/audit-console-adapter.ts +151 -0
- package/src/lib/auth-client.ts +75 -0
- package/src/lib/config.ts +74 -0
- package/src/lib/credential-field-schema.ts +11 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +544 -0
- package/src/lib/events.ts +363 -0
- package/src/lib/pino.ts +24 -0
- package/src/lib/theme-handlers.ts +168 -0
- package/src/lib/theme.ts +351 -0
- package/src/lib/tokenData.ts +378 -0
- package/src/lib/totp-import.ts +57 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/view-registry.ts +57 -0
- package/src/lib/websocket-server.ts +302 -0
- package/src/lib/websocket-setup.ts +79 -0
- package/src/lib/wordlist.ts +2050 -0
- package/src/lib/workspace-handlers.ts +285 -0
- package/start.sh +170 -0
- package/tailwind.config.ts +99 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Logger
|
|
3
|
+
* ==============
|
|
4
|
+
* Writes human-readable markdown files per agent session.
|
|
5
|
+
* Chat sessions: one file per conversation (idle-timeout grouped).
|
|
6
|
+
* Tick sessions: one file per app per day.
|
|
7
|
+
* Crash recovery on startup, log rotation, daily summaries.
|
|
8
|
+
*
|
|
9
|
+
* All writes are async (appendFile) and never block the caller.
|
|
10
|
+
* Errors in logging are caught and logged via pino — never thrown to callers.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { mkdir, appendFile, readFile, writeFile, readdir, rm, stat } from 'fs/promises';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
import { homedir } from 'os';
|
|
16
|
+
import { randomBytes } from 'crypto';
|
|
17
|
+
import { log } from '../pino';
|
|
18
|
+
import { getErrorMessage } from '../error';
|
|
19
|
+
|
|
20
|
+
// ─── Types ──────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export type SessionStatus = 'in_progress' | 'completed' | 'error' | 'timeout' | 'crashed';
|
|
23
|
+
export type AdapterSource = 'dashboard' | 'telegram' | 'webhook' | 'cli' | 'system' | 'unknown';
|
|
24
|
+
|
|
25
|
+
interface SessionMeta {
|
|
26
|
+
sessionId: string;
|
|
27
|
+
filePath: string;
|
|
28
|
+
appId: string;
|
|
29
|
+
adapter: AdapterSource;
|
|
30
|
+
startedAt: number;
|
|
31
|
+
messageCount: number;
|
|
32
|
+
toolCallCount: number;
|
|
33
|
+
totalTokens: number;
|
|
34
|
+
errorCount: number;
|
|
35
|
+
lastModel?: string;
|
|
36
|
+
lastProvider?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface TickSessionMeta {
|
|
40
|
+
sessionId: string;
|
|
41
|
+
filePath: string;
|
|
42
|
+
appId: string;
|
|
43
|
+
startedAt: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface ToolCallEntry {
|
|
47
|
+
name: string;
|
|
48
|
+
input: Record<string, unknown>;
|
|
49
|
+
result: string;
|
|
50
|
+
durationMs: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Config ─────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/** Idle timeout: new file after this gap (ms) */
|
|
56
|
+
const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
57
|
+
|
|
58
|
+
/** Default log retention in days */
|
|
59
|
+
const DEFAULT_RETENTION_DAYS = 30;
|
|
60
|
+
|
|
61
|
+
// ─── State ──────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/** Active chat sessions by sessionId */
|
|
64
|
+
const activeSessions = new Map<string, SessionMeta>();
|
|
65
|
+
|
|
66
|
+
/** Last activity per app → for idle-timeout grouping */
|
|
67
|
+
const lastActivity = new Map<string, { sessionId: string; timestamp: number }>();
|
|
68
|
+
|
|
69
|
+
/** Active tick sessions by sessionId */
|
|
70
|
+
const activeTickSessions = new Map<string, TickSessionMeta>();
|
|
71
|
+
|
|
72
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function shouldSkip(): boolean {
|
|
75
|
+
return (process.env.NODE_ENV === 'test' || !!process.env.VITEST);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getDataDir(): string {
|
|
79
|
+
return process.env.WALLET_DATA_DIR || join(homedir(), '.auramaxx');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getSessionsDir(): string {
|
|
83
|
+
return join(getDataDir(), 'logs', 'sessions');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function dateDir(date?: Date): string {
|
|
87
|
+
const d = date || new Date();
|
|
88
|
+
return d.toISOString().slice(0, 10); // YYYY-MM-DD
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function timeStamp(date?: Date): string {
|
|
92
|
+
const d = date || new Date();
|
|
93
|
+
return d.toISOString().slice(11, 19).replace(/:/g, '-'); // HH-MM-SS
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function timeDisplay(date?: Date): string {
|
|
97
|
+
const d = date || new Date();
|
|
98
|
+
return d.toISOString().slice(11, 19); // HH:MM:SS
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function formatDuration(ms: number): string {
|
|
102
|
+
if (ms < 1000) return `${ms}ms`;
|
|
103
|
+
const s = Math.floor(ms / 1000);
|
|
104
|
+
if (s < 60) return `${s}s`;
|
|
105
|
+
const m = Math.floor(s / 60);
|
|
106
|
+
const rs = s % 60;
|
|
107
|
+
return `${m}m ${rs}s`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function generateId(): string {
|
|
111
|
+
return 's_' + randomBytes(6).toString('hex');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function ensureDir(dirPath: string): Promise<void> {
|
|
115
|
+
await mkdir(dirPath, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function safeAppend(filePath: string, content: string): Promise<void> {
|
|
119
|
+
try {
|
|
120
|
+
await ensureDir(join(filePath, '..'));
|
|
121
|
+
await appendFile(filePath, content, 'utf-8');
|
|
122
|
+
} catch (err) {
|
|
123
|
+
log.warn({ err, filePath }, 'session log write failed');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function safeWrite(filePath: string, content: string): Promise<void> {
|
|
128
|
+
try {
|
|
129
|
+
await ensureDir(join(filePath, '..'));
|
|
130
|
+
await writeFile(filePath, content, 'utf-8');
|
|
131
|
+
} catch (err) {
|
|
132
|
+
log.warn({ err, filePath }, 'session log write failed');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── Chat Sessions ──────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Start or resume a chat session for a app.
|
|
140
|
+
* Returns a sessionId to pass to subsequent log calls.
|
|
141
|
+
*/
|
|
142
|
+
export function startChatSession(
|
|
143
|
+
appId: string,
|
|
144
|
+
adapter: AdapterSource | string,
|
|
145
|
+
message: string,
|
|
146
|
+
): string {
|
|
147
|
+
if (shouldSkip()) return 'skip';
|
|
148
|
+
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
const adapterSource = (adapter || 'unknown') as AdapterSource;
|
|
151
|
+
|
|
152
|
+
// Check if there's a recent session for this app (within idle timeout)
|
|
153
|
+
const last = lastActivity.get(appId);
|
|
154
|
+
if (last) {
|
|
155
|
+
const existing = activeSessions.get(last.sessionId);
|
|
156
|
+
if (existing) {
|
|
157
|
+
if ((now - last.timestamp) < IDLE_TIMEOUT_MS) {
|
|
158
|
+
// Resume existing session — append new message
|
|
159
|
+
existing.messageCount++;
|
|
160
|
+
last.timestamp = now;
|
|
161
|
+
const msgNum = existing.messageCount;
|
|
162
|
+
|
|
163
|
+
const content = `\n---\n\n## Message ${msgNum} — ${timeDisplay()}\n**User:** ${message.slice(0, 500)}${message.length > 500 ? '...' : ''}\n`;
|
|
164
|
+
safeAppend(existing.filePath, content);
|
|
165
|
+
|
|
166
|
+
return existing.sessionId;
|
|
167
|
+
}
|
|
168
|
+
// Idle timeout expired — close the old session before creating a new one
|
|
169
|
+
endChatSession(last.sessionId, 'completed');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Create new session
|
|
174
|
+
const sessionId = generateId();
|
|
175
|
+
const dd = dateDir();
|
|
176
|
+
const ts = timeStamp();
|
|
177
|
+
const safeApp = appId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
178
|
+
const fileName = `${ts}_${adapterSource}_${safeApp}.md`;
|
|
179
|
+
const filePath = join(getSessionsDir(), dd, fileName);
|
|
180
|
+
|
|
181
|
+
const meta: SessionMeta = {
|
|
182
|
+
sessionId,
|
|
183
|
+
filePath,
|
|
184
|
+
appId,
|
|
185
|
+
adapter: adapterSource,
|
|
186
|
+
startedAt: now,
|
|
187
|
+
messageCount: 1,
|
|
188
|
+
toolCallCount: 0,
|
|
189
|
+
totalTokens: 0,
|
|
190
|
+
errorCount: 0,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
activeSessions.set(sessionId, meta);
|
|
194
|
+
lastActivity.set(appId, { sessionId, timestamp: now });
|
|
195
|
+
|
|
196
|
+
// Write file header + first message
|
|
197
|
+
const header = buildFrontmatter(meta, 'in_progress');
|
|
198
|
+
const title = `# ${adapterSource} > ${appId}\n`;
|
|
199
|
+
const firstMsg = `\n## Message 1 — ${timeDisplay()}\n**User:** ${message.slice(0, 500)}${message.length > 500 ? '...' : ''}\n`;
|
|
200
|
+
|
|
201
|
+
safeWrite(filePath, header + '\n' + title + firstMsg);
|
|
202
|
+
|
|
203
|
+
return sessionId;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Metadata passed to logReply from the AI provider */
|
|
207
|
+
interface ReplyMeta {
|
|
208
|
+
model?: string;
|
|
209
|
+
tokens?: number;
|
|
210
|
+
durationMs?: number;
|
|
211
|
+
provider?: string;
|
|
212
|
+
costUsd?: number;
|
|
213
|
+
inputTokens?: number;
|
|
214
|
+
outputTokens?: number;
|
|
215
|
+
cacheReadTokens?: number;
|
|
216
|
+
/** Tool call count from providers that don't report individual calls (e.g. CLI) */
|
|
217
|
+
toolCallCount?: number;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Log the AI reply for a chat session message.
|
|
222
|
+
*/
|
|
223
|
+
export function logReply(
|
|
224
|
+
sessionId: string,
|
|
225
|
+
reply: string | null,
|
|
226
|
+
meta?: ReplyMeta,
|
|
227
|
+
): void {
|
|
228
|
+
if (shouldSkip() || sessionId === 'skip') return;
|
|
229
|
+
|
|
230
|
+
const session = activeSessions.get(sessionId);
|
|
231
|
+
if (!session) return;
|
|
232
|
+
|
|
233
|
+
const model = meta?.model || 'unknown';
|
|
234
|
+
const tokens = meta?.tokens || 0;
|
|
235
|
+
const durationMs = meta?.durationMs || 0;
|
|
236
|
+
const provider = meta?.provider || 'unknown';
|
|
237
|
+
|
|
238
|
+
session.totalTokens += tokens;
|
|
239
|
+
session.lastModel = model;
|
|
240
|
+
session.lastProvider = provider;
|
|
241
|
+
|
|
242
|
+
// For providers that don't report individual tool calls (e.g. CLI),
|
|
243
|
+
// update the count from the aggregate reported by _meta.toolCallCount
|
|
244
|
+
if (meta?.toolCallCount && meta.toolCallCount > session.toolCallCount) {
|
|
245
|
+
session.toolCallCount = meta.toolCallCount;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Update last activity
|
|
249
|
+
const last = lastActivity.get(session.appId);
|
|
250
|
+
if (last && last.sessionId === sessionId) {
|
|
251
|
+
last.timestamp = Date.now();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Build rich timing line with all available metadata
|
|
255
|
+
const parts: string[] = [
|
|
256
|
+
`**Response time:** ${durationMs.toLocaleString()}ms`,
|
|
257
|
+
`**Model:** ${model}`,
|
|
258
|
+
`**Provider:** ${provider}`,
|
|
259
|
+
];
|
|
260
|
+
if (meta?.inputTokens || meta?.outputTokens) {
|
|
261
|
+
parts.push(`**Tokens:** ${(meta.inputTokens || 0).toLocaleString()} in / ${(meta.outputTokens || 0).toLocaleString()} out`);
|
|
262
|
+
if (meta.cacheReadTokens) parts.push(`**Cache:** ${meta.cacheReadTokens.toLocaleString()}`);
|
|
263
|
+
} else if (tokens) {
|
|
264
|
+
parts.push(`**Tokens:** ${tokens.toLocaleString()}`);
|
|
265
|
+
}
|
|
266
|
+
if (meta?.costUsd) parts.push(`**Cost:** $${meta.costUsd.toFixed(4)}`);
|
|
267
|
+
const timing = parts.join(' | ');
|
|
268
|
+
|
|
269
|
+
const replyBlock = reply
|
|
270
|
+
? `\n### Reply\n${reply.slice(0, 2000)}${reply.length > 2000 ? '\n...(truncated)' : ''}\n`
|
|
271
|
+
: '\n### Reply\n_(no reply)_\n';
|
|
272
|
+
|
|
273
|
+
safeAppend(session.filePath, timing + '\n' + replyBlock);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Log a tool call within a chat session.
|
|
278
|
+
*/
|
|
279
|
+
export function logToolCall(
|
|
280
|
+
sessionId: string,
|
|
281
|
+
entry: ToolCallEntry,
|
|
282
|
+
): void {
|
|
283
|
+
if (shouldSkip() || sessionId === 'skip') return;
|
|
284
|
+
|
|
285
|
+
const session = activeSessions.get(sessionId);
|
|
286
|
+
if (!session) return;
|
|
287
|
+
|
|
288
|
+
session.toolCallCount++;
|
|
289
|
+
const num = session.toolCallCount;
|
|
290
|
+
|
|
291
|
+
// If this is the first tool call for the current message, write header
|
|
292
|
+
if (num === 1 || (session as any)._lastToolMsg !== session.messageCount) {
|
|
293
|
+
safeAppend(session.filePath, '\n### Tool Calls\n| # | Tool | Input | Duration |\n|---|------|-------|----------|\n');
|
|
294
|
+
(session as any)._lastToolMsg = session.messageCount;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const inputStr = typeof entry.input === 'object'
|
|
298
|
+
? `${entry.input.method || '?'} ${entry.input.endpoint || JSON.stringify(entry.input).slice(0, 60)}`
|
|
299
|
+
: String(entry.input).slice(0, 60);
|
|
300
|
+
|
|
301
|
+
safeAppend(session.filePath, `| ${num} | ${entry.name} | ${inputStr} | ${entry.durationMs}ms |\n`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Log an error in a chat session.
|
|
306
|
+
*/
|
|
307
|
+
export function logError(
|
|
308
|
+
sessionId: string,
|
|
309
|
+
error: unknown,
|
|
310
|
+
): void {
|
|
311
|
+
if (shouldSkip() || sessionId === 'skip') return;
|
|
312
|
+
|
|
313
|
+
const session = activeSessions.get(sessionId);
|
|
314
|
+
if (!session) return;
|
|
315
|
+
|
|
316
|
+
session.errorCount++;
|
|
317
|
+
const errMsg = getErrorMessage(error);
|
|
318
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
319
|
+
|
|
320
|
+
const content = `\n### Error\n\`\`\`\n${errMsg}${stack ? '\n' + stack : ''}\n\`\`\`\n`;
|
|
321
|
+
safeAppend(session.filePath, content);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* End a chat session with a final status.
|
|
326
|
+
*/
|
|
327
|
+
export async function endChatSession(
|
|
328
|
+
sessionId: string,
|
|
329
|
+
status: SessionStatus = 'completed',
|
|
330
|
+
): Promise<void> {
|
|
331
|
+
if (shouldSkip() || sessionId === 'skip') return;
|
|
332
|
+
|
|
333
|
+
const session = activeSessions.get(sessionId);
|
|
334
|
+
if (!session) return;
|
|
335
|
+
|
|
336
|
+
// Rewrite frontmatter with final status
|
|
337
|
+
await rewriteSessionStatus(session.filePath, session, status);
|
|
338
|
+
|
|
339
|
+
// Cleanup
|
|
340
|
+
activeSessions.delete(sessionId);
|
|
341
|
+
const last = lastActivity.get(session.appId);
|
|
342
|
+
if (last && last.sessionId === sessionId) {
|
|
343
|
+
lastActivity.delete(session.appId);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ─── Tick Sessions ──────────────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Start a tick session for a app. Appends to the daily tick file.
|
|
351
|
+
*/
|
|
352
|
+
export function startTickSession(appId: string): string {
|
|
353
|
+
if (shouldSkip()) return 'skip';
|
|
354
|
+
|
|
355
|
+
const sessionId = generateId();
|
|
356
|
+
const dd = dateDir();
|
|
357
|
+
const safeApp = appId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
358
|
+
const fileName = `ticks_${safeApp}.md`;
|
|
359
|
+
const filePath = join(getSessionsDir(), dd, fileName);
|
|
360
|
+
|
|
361
|
+
const meta: TickSessionMeta = {
|
|
362
|
+
sessionId,
|
|
363
|
+
filePath,
|
|
364
|
+
appId,
|
|
365
|
+
startedAt: Date.now(),
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
activeTickSessions.set(sessionId, meta);
|
|
369
|
+
|
|
370
|
+
// Check if file needs a header (first tick of the day)
|
|
371
|
+
const header = `\n## Tick — ${timeDisplay()} [${sessionId}]\n**Status:** in_progress\n`;
|
|
372
|
+
safeAppend(filePath, header);
|
|
373
|
+
|
|
374
|
+
return sessionId;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Log an event within a tick session.
|
|
379
|
+
*/
|
|
380
|
+
export function logTickEvent(
|
|
381
|
+
sessionId: string,
|
|
382
|
+
phase: string,
|
|
383
|
+
details: string,
|
|
384
|
+
): void {
|
|
385
|
+
if (shouldSkip() || sessionId === 'skip') return;
|
|
386
|
+
|
|
387
|
+
const tick = activeTickSessions.get(sessionId);
|
|
388
|
+
if (!tick) return;
|
|
389
|
+
|
|
390
|
+
safeAppend(tick.filePath, `**${phase}:** ${details}\n`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* End a tick session with status and duration.
|
|
395
|
+
*/
|
|
396
|
+
export function endTickSession(
|
|
397
|
+
sessionId: string,
|
|
398
|
+
status: SessionStatus,
|
|
399
|
+
durationMs: number,
|
|
400
|
+
): void {
|
|
401
|
+
if (shouldSkip() || sessionId === 'skip') return;
|
|
402
|
+
|
|
403
|
+
const tick = activeTickSessions.get(sessionId);
|
|
404
|
+
if (!tick) return;
|
|
405
|
+
|
|
406
|
+
const statusIcon = status === 'completed' ? '' : status === 'error' ? ' ERROR' : ` ${status.toUpperCase()}`;
|
|
407
|
+
safeAppend(tick.filePath, `**Duration:** ${formatDuration(durationMs)} | **Status:** ${status}${statusIcon}\n\n---\n`);
|
|
408
|
+
|
|
409
|
+
activeTickSessions.delete(sessionId);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ─── Frontmatter ────────────────────────────────────────────────────
|
|
413
|
+
|
|
414
|
+
function buildFrontmatter(session: SessionMeta, status: SessionStatus): string {
|
|
415
|
+
return `---
|
|
416
|
+
session_id: ${session.sessionId}
|
|
417
|
+
adapter: ${session.adapter}
|
|
418
|
+
app: ${session.appId}
|
|
419
|
+
started: ${new Date(session.startedAt).toISOString()}
|
|
420
|
+
status: ${status}
|
|
421
|
+
messages: ${session.messageCount}
|
|
422
|
+
tool_calls: ${session.toolCallCount}
|
|
423
|
+
tokens: ${session.totalTokens}
|
|
424
|
+
model: ${session.lastModel || 'unknown'}
|
|
425
|
+
provider: ${session.lastProvider || 'unknown'}
|
|
426
|
+
errors: ${session.errorCount}
|
|
427
|
+
---
|
|
428
|
+
`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function rewriteSessionStatus(
|
|
432
|
+
filePath: string,
|
|
433
|
+
session: SessionMeta,
|
|
434
|
+
status: SessionStatus,
|
|
435
|
+
): Promise<void> {
|
|
436
|
+
try {
|
|
437
|
+
const content = await readFile(filePath, 'utf-8');
|
|
438
|
+
const endOfFrontmatter = content.indexOf('---', 4); // Skip first ---
|
|
439
|
+
if (endOfFrontmatter === -1) return;
|
|
440
|
+
|
|
441
|
+
const endedAt = new Date();
|
|
442
|
+
const durationMs = endedAt.getTime() - session.startedAt;
|
|
443
|
+
|
|
444
|
+
const newFrontmatter = `---
|
|
445
|
+
session_id: ${session.sessionId}
|
|
446
|
+
adapter: ${session.adapter}
|
|
447
|
+
app: ${session.appId}
|
|
448
|
+
started: ${new Date(session.startedAt).toISOString()}
|
|
449
|
+
ended: ${endedAt.toISOString()}
|
|
450
|
+
duration: ${formatDuration(durationMs)}
|
|
451
|
+
status: ${status}
|
|
452
|
+
messages: ${session.messageCount}
|
|
453
|
+
tool_calls: ${session.toolCallCount}
|
|
454
|
+
tokens: ${session.totalTokens}
|
|
455
|
+
model: ${session.lastModel || 'unknown'}
|
|
456
|
+
provider: ${session.lastProvider || 'unknown'}
|
|
457
|
+
errors: ${session.errorCount}
|
|
458
|
+
---`;
|
|
459
|
+
|
|
460
|
+
const rest = content.slice(endOfFrontmatter + 3);
|
|
461
|
+
await writeFile(filePath, newFrontmatter + rest, 'utf-8');
|
|
462
|
+
} catch (err) {
|
|
463
|
+
log.warn({ err, filePath }, 'failed to rewrite session frontmatter');
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ─── Crash Recovery ─────────────────────────────────────────────────
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Scan recent session files for `status: in_progress` and mark them as crashed.
|
|
471
|
+
* Called on server startup.
|
|
472
|
+
*/
|
|
473
|
+
export async function recoverCrashedSessions(): Promise<void> {
|
|
474
|
+
if (shouldSkip()) return;
|
|
475
|
+
|
|
476
|
+
const sessionsDir = getSessionsDir();
|
|
477
|
+
const now = new Date();
|
|
478
|
+
const today = dateDir(now);
|
|
479
|
+
const yesterday = dateDir(new Date(now.getTime() - 86_400_000));
|
|
480
|
+
|
|
481
|
+
for (const dd of [today, yesterday]) {
|
|
482
|
+
const dayDir = join(sessionsDir, dd);
|
|
483
|
+
let files: string[];
|
|
484
|
+
try {
|
|
485
|
+
files = await readdir(dayDir);
|
|
486
|
+
} catch {
|
|
487
|
+
continue; // Directory doesn't exist
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
for (const file of files) {
|
|
491
|
+
if (!file.endsWith('.md') || file.startsWith('_') || file.startsWith('ticks_')) continue;
|
|
492
|
+
|
|
493
|
+
const filePath = join(dayDir, file);
|
|
494
|
+
try {
|
|
495
|
+
const content = await readFile(filePath, 'utf-8');
|
|
496
|
+
if (!content.includes('status: in_progress')) continue;
|
|
497
|
+
|
|
498
|
+
// Mark as crashed
|
|
499
|
+
const updated = content.replace(
|
|
500
|
+
'status: in_progress',
|
|
501
|
+
'status: crashed',
|
|
502
|
+
);
|
|
503
|
+
const crashNote = `\n## CRASH DETECTED\nThis session was in_progress when the server stopped unexpectedly.\nServer restarted at: ${now.toISOString()}\n`;
|
|
504
|
+
await writeFile(filePath, updated + crashNote, 'utf-8');
|
|
505
|
+
log.info({ file }, 'recovered crashed session');
|
|
506
|
+
} catch (err) {
|
|
507
|
+
log.warn({ err, file }, 'failed to recover crashed session');
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Mark all currently active sessions with the given status.
|
|
515
|
+
* Called during graceful shutdown.
|
|
516
|
+
*/
|
|
517
|
+
export async function endAllActiveSessions(status: SessionStatus = 'completed'): Promise<void> {
|
|
518
|
+
if (shouldSkip()) return;
|
|
519
|
+
|
|
520
|
+
const promises: Promise<void>[] = [];
|
|
521
|
+
for (const [sessionId] of activeSessions) {
|
|
522
|
+
promises.push(endChatSession(sessionId, status));
|
|
523
|
+
}
|
|
524
|
+
for (const [sessionId] of activeTickSessions) {
|
|
525
|
+
endTickSession(sessionId, status, Date.now() - (activeTickSessions.get(sessionId)?.startedAt || Date.now()));
|
|
526
|
+
}
|
|
527
|
+
await Promise.all(promises);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Mark all active sessions as crashed.
|
|
532
|
+
* Called from uncaughtException handler.
|
|
533
|
+
*/
|
|
534
|
+
export async function markAllSessionsCrashed(): Promise<void> {
|
|
535
|
+
if (shouldSkip()) return;
|
|
536
|
+
|
|
537
|
+
const promises: Promise<void>[] = [];
|
|
538
|
+
for (const [sessionId] of activeSessions) {
|
|
539
|
+
promises.push(endChatSession(sessionId, 'crashed'));
|
|
540
|
+
}
|
|
541
|
+
for (const [sessionId] of activeTickSessions) {
|
|
542
|
+
endTickSession(sessionId, 'crashed', Date.now() - (activeTickSessions.get(sessionId)?.startedAt || Date.now()));
|
|
543
|
+
}
|
|
544
|
+
await Promise.all(promises);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ─── Log Rotation ───────────────────────────────────────────────────
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Delete session log directories older than retentionDays.
|
|
551
|
+
*/
|
|
552
|
+
export async function cleanupOldLogs(retentionDays: number = DEFAULT_RETENTION_DAYS): Promise<void> {
|
|
553
|
+
if (shouldSkip()) return;
|
|
554
|
+
|
|
555
|
+
const sessionsDir = getSessionsDir();
|
|
556
|
+
let dirs: string[];
|
|
557
|
+
try {
|
|
558
|
+
dirs = await readdir(sessionsDir);
|
|
559
|
+
} catch {
|
|
560
|
+
return; // No sessions dir yet
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const cutoff = new Date();
|
|
564
|
+
cutoff.setDate(cutoff.getDate() - retentionDays);
|
|
565
|
+
const cutoffStr = dateDir(cutoff);
|
|
566
|
+
|
|
567
|
+
for (const dir of dirs) {
|
|
568
|
+
// Only process YYYY-MM-DD directories
|
|
569
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(dir)) continue;
|
|
570
|
+
if (dir >= cutoffStr) continue;
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
await rm(join(sessionsDir, dir), { recursive: true });
|
|
574
|
+
log.info({ dir }, 'cleaned up old session logs');
|
|
575
|
+
} catch (err) {
|
|
576
|
+
log.warn({ err, dir }, 'failed to clean up old session logs');
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// ─── Daily Summary ──────────────────────────────────────────────────
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Generate a daily summary markdown file from today's session files.
|
|
585
|
+
*/
|
|
586
|
+
export async function generateDailySummary(date?: string): Promise<void> {
|
|
587
|
+
if (shouldSkip()) return;
|
|
588
|
+
|
|
589
|
+
const dd = date || dateDir();
|
|
590
|
+
const dayDir = join(getSessionsDir(), dd);
|
|
591
|
+
let files: string[];
|
|
592
|
+
try {
|
|
593
|
+
files = await readdir(dayDir);
|
|
594
|
+
} catch {
|
|
595
|
+
return; // No sessions for this day
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
let chatSessions = 0;
|
|
599
|
+
let tickInvocations = 0;
|
|
600
|
+
let totalTokens = 0;
|
|
601
|
+
let completedCount = 0;
|
|
602
|
+
let errorCount = 0;
|
|
603
|
+
let crashCount = 0;
|
|
604
|
+
let timeoutCount = 0;
|
|
605
|
+
const sessionRows: string[] = [];
|
|
606
|
+
|
|
607
|
+
for (const file of files) {
|
|
608
|
+
if (!file.endsWith('.md') || file.startsWith('_')) continue;
|
|
609
|
+
|
|
610
|
+
const filePath = join(dayDir, file);
|
|
611
|
+
try {
|
|
612
|
+
const content = await readFile(filePath, 'utf-8');
|
|
613
|
+
|
|
614
|
+
if (file.startsWith('ticks_')) {
|
|
615
|
+
// Count tick entries
|
|
616
|
+
const tickMatches = content.match(/^## Tick —/gm);
|
|
617
|
+
tickInvocations += tickMatches ? tickMatches.length : 0;
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Parse chat session frontmatter
|
|
622
|
+
chatSessions++;
|
|
623
|
+
const statusMatch = content.match(/^status:\s*(.+)$/m);
|
|
624
|
+
const status = statusMatch ? statusMatch[1].trim() : 'unknown';
|
|
625
|
+
const adapterMatch = content.match(/^adapter:\s*(.+)$/m);
|
|
626
|
+
const adapter = adapterMatch ? adapterMatch[1].trim() : '?';
|
|
627
|
+
const appMatch = content.match(/^app:\s*(.+)$/m);
|
|
628
|
+
const app = appMatch ? appMatch[1].trim() : '?';
|
|
629
|
+
const msgMatch = content.match(/^messages:\s*(\d+)$/m);
|
|
630
|
+
const messages = msgMatch ? parseInt(msgMatch[1]) : 0;
|
|
631
|
+
const tokenMatch = content.match(/^tokens:\s*(\d+)$/m);
|
|
632
|
+
const tokens = tokenMatch ? parseInt(tokenMatch[1]) : 0;
|
|
633
|
+
const startMatch = content.match(/^started:\s*(.+)$/m);
|
|
634
|
+
const startTime = startMatch ? startMatch[1].trim().slice(11, 16) : '?';
|
|
635
|
+
|
|
636
|
+
totalTokens += tokens;
|
|
637
|
+
if (status === 'completed') completedCount++;
|
|
638
|
+
else if (status === 'error') errorCount++;
|
|
639
|
+
else if (status === 'crashed') crashCount++;
|
|
640
|
+
else if (status === 'timeout') timeoutCount++;
|
|
641
|
+
|
|
642
|
+
sessionRows.push(`| ${startTime} | ${adapter} | ${app} | ${messages} | ${status} | ${tokens} |`);
|
|
643
|
+
} catch {
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const total = chatSessions + tickInvocations;
|
|
649
|
+
const successRate = total > 0 ? ((completedCount / (chatSessions || 1)) * 100).toFixed(1) : '0.0';
|
|
650
|
+
|
|
651
|
+
const summary = `# Daily Summary — ${dd}
|
|
652
|
+
|
|
653
|
+
| Metric | Value |
|
|
654
|
+
|--------|-------|
|
|
655
|
+
| Total sessions | ${total} |
|
|
656
|
+
| Chat sessions | ${chatSessions} |
|
|
657
|
+
| Tick invocations | ${tickInvocations} |
|
|
658
|
+
| Success rate | ${successRate}% |
|
|
659
|
+
| Timeouts | ${timeoutCount} |
|
|
660
|
+
| Errors | ${errorCount} |
|
|
661
|
+
| Crashes | ${crashCount} |
|
|
662
|
+
| Total tokens | ${totalTokens.toLocaleString()} |
|
|
663
|
+
|
|
664
|
+
## Sessions
|
|
665
|
+
| Time | Adapter | App | Messages | Status | Tokens |
|
|
666
|
+
|------|---------|--------|----------|--------|--------|
|
|
667
|
+
${sessionRows.join('\n') || '_(none)_'}
|
|
668
|
+
|
|
669
|
+
## Errors & Crashes
|
|
670
|
+
${errorCount + crashCount > 0 ? `${errorCount} error(s), ${crashCount} crash(es) — see individual session files for details.` : '_None today._'}
|
|
671
|
+
`;
|
|
672
|
+
|
|
673
|
+
await safeWrite(join(dayDir, '_daily-summary.md'), summary);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// ─── Test Helpers ───────────────────────────────────────────────────
|
|
677
|
+
|
|
678
|
+
/** Reset all in-memory state (for tests) */
|
|
679
|
+
export function __resetSessionLogger(): void {
|
|
680
|
+
activeSessions.clear();
|
|
681
|
+
lastActivity.clear();
|
|
682
|
+
activeTickSessions.clear();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/** Get active session count (for tests) */
|
|
686
|
+
export function __getActiveSessionCount(): number {
|
|
687
|
+
return activeSessions.size;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/** Get active tick session count (for tests) */
|
|
691
|
+
export function __getActiveTickSessionCount(): number {
|
|
692
|
+
return activeTickSessions.size;
|
|
693
|
+
}
|