@poolzin/pool-bot 2026.3.11 → 2026.3.14
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/CHANGELOG.md +121 -0
- package/dist/.buildstamp +1 -1
- package/dist/agents/checkpoint-manager.js +291 -0
- package/dist/agents/poolbot-tools.js +5 -0
- package/dist/agents/subagent-announce-reliability.js +160 -0
- package/dist/agents/tool-result-truncation.js +299 -0
- package/dist/agents/tools/nodes-file-tool.js +197 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/config-cli.js +60 -0
- package/dist/cron/cron-improvements.js +195 -0
- package/dist/discord/discord-improvements.js +167 -0
- package/dist/gateway/auth-rate-limit.js +19 -0
- package/dist/gateway/auth.js +41 -0
- package/dist/gateway/gateway-improvements.js +294 -0
- package/dist/gateway/node-command-policy.js +7 -2
- package/dist/infra/net/ssrf.js +15 -2
- package/dist/infra/shell-security.js +201 -0
- package/dist/memory/memory-improvements.js +239 -0
- package/dist/node-host/runner.js +146 -79
- package/dist/security/prototype-pollution.js +141 -0
- package/dist/security/webhook-security.js +253 -0
- package/dist/shared/net/ip.js +52 -1
- package/dist/slack/slack-improvements.js +225 -0
- package/dist/telegram/telegram-improvements.js +220 -0
- package/dist/ui-plugins/ui-plugins-improvements.js +191 -0
- package/docs/ANALISE_OPENCLAW_PROFISSIONAL.md +520 -0
- package/docs/competitive-analysis.md +421 -0
- package/docs/implementation-analysis.md +393 -0
- package/docs/plans/2026-03-11-file-operations-security-hardening.md +307 -0
- package/docs/plans/2026-03-11-integracao-projetos-poolbot.md +666 -0
- package/docs/refactor/plugin-development-guide.md +281 -0
- package/extensions/agency-agents/README.md +301 -0
- package/extensions/agency-agents/agents/CONTRIBUTING.md +353 -0
- package/extensions/agency-agents/agents/README.md +602 -0
- package/extensions/agency-agents/agents/design/design-brand-guardian.md +320 -0
- package/extensions/agency-agents/agents/design/design-image-prompt-engineer.md +234 -0
- package/extensions/agency-agents/agents/design/design-ui-designer.md +381 -0
- package/extensions/agency-agents/agents/design/design-ux-architect.md +467 -0
- package/extensions/agency-agents/agents/design/design-ux-researcher.md +327 -0
- package/extensions/agency-agents/agents/design/design-visual-storyteller.md +147 -0
- package/extensions/agency-agents/agents/design/design-whimsy-injector.md +436 -0
- package/extensions/agency-agents/agents/engineering/engineering-ai-engineer.md +144 -0
- package/extensions/agency-agents/agents/engineering/engineering-backend-architect.md +233 -0
- package/extensions/agency-agents/agents/engineering/engineering-devops-automator.md +374 -0
- package/extensions/agency-agents/agents/engineering/engineering-frontend-developer.md +223 -0
- package/extensions/agency-agents/agents/engineering/engineering-mobile-app-builder.md +491 -0
- package/extensions/agency-agents/agents/engineering/engineering-rapid-prototyper.md +460 -0
- package/extensions/agency-agents/agents/engineering/engineering-security-engineer.md +275 -0
- package/extensions/agency-agents/agents/engineering/engineering-senior-developer.md +174 -0
- package/extensions/agency-agents/agents/examples/README.md +48 -0
- package/extensions/agency-agents/agents/examples/nexus-spatial-discovery.md +852 -0
- package/extensions/agency-agents/agents/examples/workflow-landing-page.md +119 -0
- package/extensions/agency-agents/agents/examples/workflow-startup-mvp.md +155 -0
- package/extensions/agency-agents/agents/integrations/README.md +117 -0
- package/extensions/agency-agents/agents/integrations/aider/README.md +38 -0
- package/extensions/agency-agents/agents/integrations/antigravity/README.md +49 -0
- package/extensions/agency-agents/agents/integrations/claude-code/README.md +31 -0
- package/extensions/agency-agents/agents/integrations/cursor/README.md +38 -0
- package/extensions/agency-agents/agents/integrations/gemini-cli/README.md +36 -0
- package/extensions/agency-agents/agents/integrations/opencode/README.md +58 -0
- package/extensions/agency-agents/agents/integrations/windsurf/README.md +26 -0
- package/extensions/agency-agents/agents/marketing/marketing-app-store-optimizer.md +319 -0
- package/extensions/agency-agents/agents/marketing/marketing-content-creator.md +52 -0
- package/extensions/agency-agents/agents/marketing/marketing-growth-hacker.md +52 -0
- package/extensions/agency-agents/agents/marketing/marketing-instagram-curator.md +111 -0
- package/extensions/agency-agents/agents/marketing/marketing-reddit-community-builder.md +121 -0
- package/extensions/agency-agents/agents/marketing/marketing-social-media-strategist.md +123 -0
- package/extensions/agency-agents/agents/marketing/marketing-tiktok-strategist.md +123 -0
- package/extensions/agency-agents/agents/marketing/marketing-twitter-engager.md +124 -0
- package/extensions/agency-agents/agents/marketing/marketing-wechat-official-account.md +143 -0
- package/extensions/agency-agents/agents/marketing/marketing-xiaohongshu-specialist.md +136 -0
- package/extensions/agency-agents/agents/marketing/marketing-zhihu-strategist.md +160 -0
- package/extensions/agency-agents/agents/product/product-feedback-synthesizer.md +117 -0
- package/extensions/agency-agents/agents/product/product-sprint-prioritizer.md +152 -0
- package/extensions/agency-agents/agents/product/product-trend-researcher.md +157 -0
- package/extensions/agency-agents/agents/project-management/project-management-experiment-tracker.md +196 -0
- package/extensions/agency-agents/agents/project-management/project-management-project-shepherd.md +192 -0
- package/extensions/agency-agents/agents/project-management/project-management-studio-operations.md +198 -0
- package/extensions/agency-agents/agents/project-management/project-management-studio-producer.md +201 -0
- package/extensions/agency-agents/agents/project-management/project-manager-senior.md +133 -0
- package/extensions/agency-agents/agents/scripts/convert.sh +362 -0
- package/extensions/agency-agents/agents/scripts/install.sh +465 -0
- package/extensions/agency-agents/agents/scripts/lint-agents.sh +115 -0
- package/extensions/agency-agents/agents/spatial-computing/macos-spatial-metal-engineer.md +335 -0
- package/extensions/agency-agents/agents/spatial-computing/terminal-integration-specialist.md +68 -0
- package/extensions/agency-agents/agents/spatial-computing/visionos-spatial-engineer.md +52 -0
- package/extensions/agency-agents/agents/spatial-computing/xr-cockpit-interaction-specialist.md +30 -0
- package/extensions/agency-agents/agents/spatial-computing/xr-immersive-developer.md +30 -0
- package/extensions/agency-agents/agents/spatial-computing/xr-interface-architect.md +30 -0
- package/extensions/agency-agents/agents/specialized/agentic-identity-trust.md +367 -0
- package/extensions/agency-agents/agents/specialized/agents-orchestrator.md +365 -0
- package/extensions/agency-agents/agents/specialized/data-analytics-reporter.md +52 -0
- package/extensions/agency-agents/agents/specialized/data-consolidation-agent.md +58 -0
- package/extensions/agency-agents/agents/specialized/lsp-index-engineer.md +312 -0
- package/extensions/agency-agents/agents/specialized/report-distribution-agent.md +63 -0
- package/extensions/agency-agents/agents/specialized/sales-data-extraction-agent.md +65 -0
- package/extensions/agency-agents/agents/strategy/EXECUTIVE-BRIEF.md +95 -0
- package/extensions/agency-agents/agents/strategy/QUICKSTART.md +194 -0
- package/extensions/agency-agents/agents/strategy/coordination/agent-activation-prompts.md +401 -0
- package/extensions/agency-agents/agents/strategy/coordination/handoff-templates.md +357 -0
- package/extensions/agency-agents/agents/strategy/nexus-strategy.md +1110 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-0-discovery.md +178 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-1-strategy.md +238 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-2-foundation.md +278 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-3-build.md +286 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-4-hardening.md +332 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-5-launch.md +277 -0
- package/extensions/agency-agents/agents/strategy/playbooks/phase-6-operate.md +318 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-enterprise-feature.md +157 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-incident-response.md +217 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-marketing-campaign.md +187 -0
- package/extensions/agency-agents/agents/strategy/runbooks/scenario-startup-mvp.md +154 -0
- package/extensions/agency-agents/agents/support/support-analytics-reporter.md +363 -0
- package/extensions/agency-agents/agents/support/support-executive-summary-generator.md +210 -0
- package/extensions/agency-agents/agents/support/support-finance-tracker.md +440 -0
- package/extensions/agency-agents/agents/support/support-infrastructure-maintainer.md +616 -0
- package/extensions/agency-agents/agents/support/support-legal-compliance-checker.md +586 -0
- package/extensions/agency-agents/agents/support/support-support-responder.md +583 -0
- package/extensions/agency-agents/agents/testing/testing-accessibility-auditor.md +313 -0
- package/extensions/agency-agents/agents/testing/testing-api-tester.md +304 -0
- package/extensions/agency-agents/agents/testing/testing-evidence-collector.md +208 -0
- package/extensions/agency-agents/agents/testing/testing-performance-benchmarker.md +266 -0
- package/extensions/agency-agents/agents/testing/testing-reality-checker.md +236 -0
- package/extensions/agency-agents/agents/testing/testing-test-results-analyzer.md +303 -0
- package/extensions/agency-agents/agents/testing/testing-tool-evaluator.md +392 -0
- package/extensions/agency-agents/agents/testing/testing-workflow-optimizer.md +448 -0
- package/extensions/agency-agents/index.ts +733 -0
- package/extensions/agency-agents/node_modules/.bin/jiti +21 -0
- package/extensions/agency-agents/node_modules/.bin/tsc +21 -0
- package/extensions/agency-agents/node_modules/.bin/tsserver +21 -0
- package/extensions/agency-agents/node_modules/.bin/tsx +21 -0
- package/extensions/agency-agents/node_modules/.bin/vite +21 -0
- package/extensions/agency-agents/node_modules/.bin/vitest +21 -0
- package/extensions/agency-agents/node_modules/.bin/yaml +21 -0
- package/extensions/agency-agents/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/extensions/agency-agents/package.json +25 -0
- package/extensions/agency-agents/src/AgencyAgentsService.test.ts +443 -0
- package/extensions/agency-agents/src/AgencyAgentsService.ts +288 -0
- package/extensions/agency-agents/src/types.ts +147 -0
- package/extensions/agency-agents/vitest.config.ts +8 -0
- package/extensions/hexstrike-ai/README.md +98 -0
- package/extensions/hexstrike-ai/node_modules/.bin/tsc +21 -0
- package/extensions/hexstrike-ai/node_modules/.bin/tsserver +21 -0
- package/extensions/hexstrike-ai/package.json +29 -0
- package/extensions/hexstrike-ai/poolbot.plugin.json +31 -0
- package/extensions/hexstrike-ai/src/client.ts +91 -0
- package/extensions/hexstrike-ai/src/index.ts +170 -0
- package/extensions/hexstrike-ai/src/server/hexstrike_mcp.py +5470 -0
- package/extensions/hexstrike-ai/src/server/hexstrike_server.py +17289 -0
- package/extensions/hexstrike-ai/src/server/requirements.txt +84 -0
- package/extensions/hexstrike-ai/src/server-manager.ts +83 -0
- package/extensions/hexstrike-ai/tsconfig.json +20 -0
- package/extensions/hexstrike-bridge/package.json +1 -1
- package/extensions/hexstrike-bridge/poolbot.plugin.json +23 -0
- package/extensions/mcp-server/poolbot.plugin.json +10 -0
- package/extensions/page-agent/README.md +159 -0
- package/extensions/page-agent/index.ts +595 -0
- package/extensions/page-agent/node_modules/.bin/jiti +21 -0
- package/extensions/page-agent/node_modules/.bin/playwright +21 -0
- package/extensions/page-agent/node_modules/.bin/tsc +21 -0
- package/extensions/page-agent/node_modules/.bin/tsserver +21 -0
- package/extensions/page-agent/node_modules/.bin/tsx +21 -0
- package/extensions/page-agent/node_modules/.bin/vitest +21 -0
- package/extensions/page-agent/node_modules/.bin/yaml +21 -0
- package/extensions/page-agent/package.json +43 -0
- package/extensions/page-agent/src/PageAgentService.test.ts +517 -0
- package/extensions/page-agent/src/PageAgentService.ts +636 -0
- package/extensions/page-agent/src/PoolBotPageController.test.ts +358 -0
- package/extensions/page-agent/src/PoolBotPageController.ts +245 -0
- package/extensions/page-agent/src/index.ts +20 -0
- package/extensions/page-agent/src/tools.test.ts +231 -0
- package/extensions/page-agent/src/tools.ts +167 -0
- package/extensions/page-agent/src/types.ts +198 -0
- package/extensions/template/README.md +101 -0
- package/extensions/template/index.ts +38 -0
- package/extensions/template/package.json +15 -0
- package/extensions/template/poolbot.plugin.json +10 -0
- package/extensions/xyops/README.md +227 -0
- package/extensions/xyops/index.ts +342 -0
- package/extensions/xyops/node_modules/.bin/jiti +21 -0
- package/extensions/xyops/node_modules/.bin/tsc +21 -0
- package/extensions/xyops/node_modules/.bin/tsserver +21 -0
- package/extensions/xyops/node_modules/.bin/tsx +21 -0
- package/extensions/xyops/node_modules/.bin/vitest +21 -0
- package/extensions/xyops/node_modules/.bin/yaml +21 -0
- package/extensions/xyops/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/extensions/xyops/package.json +39 -0
- package/extensions/xyops/src/client.test.ts +467 -0
- package/extensions/xyops/src/client.ts +157 -0
- package/extensions/xyops/src/types.ts +147 -0
- package/extensions/xyops/vitest.config.ts +8 -0
- package/package.json +1 -1
- package/extensions/mavalie/README.md +0 -97
- package/extensions/mavalie/package.json +0 -15
- package/extensions/mavalie/src/index.ts +0 -62
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,124 @@
|
|
|
1
|
+
## v2026.3.14 (2026-03-14)
|
|
2
|
+
|
|
3
|
+
### 🎉 OpenClaw Improvements - Complete Implementation
|
|
4
|
+
|
|
5
|
+
#### 🔐 Security Hardening (OpenClaw #32384, #30951)
|
|
6
|
+
- **SSRF Protection:** Block IPv6 transition mechanisms (NAT64, 6to4, Teredo, ISATAP)
|
|
7
|
+
- **SSRF Protection:** Unicode NFKC hostname normalization (homoglyph attacks)
|
|
8
|
+
- **Auth Hardening:** Control-plane rate limiting (3 req/min)
|
|
9
|
+
- **Auth Hardening:** Loopback auto-approve for safe scope upgrades
|
|
10
|
+
- **Config Security:** Prototype pollution prevention (safeMerge, safeJsonParse)
|
|
11
|
+
- **Sandbox Security:** Shell line continuation blocking (\\\n)
|
|
12
|
+
- **Sandbox Security:** Docker namespace join blocking (--pid=host, etc.)
|
|
13
|
+
- **Sandbox Security:** Fail-closed on sandbox unavailable
|
|
14
|
+
- **Webhook Security:** Auth-before-body parsing (fail-closed)
|
|
15
|
+
- **Webhook Security:** HMAC-SHA256 with constant-time comparison
|
|
16
|
+
- **Webhook Security:** Replay protection with dedupe
|
|
17
|
+
|
|
18
|
+
#### 🤖 Agent Reliability (OpenClaw #32384, #30951)
|
|
19
|
+
- **Subagent Announce:** Retry budget with exponential backoff (3 attempts, 1s-10s)
|
|
20
|
+
- **Subagent Announce:** Dedupe announce completions (1 hour window)
|
|
21
|
+
- **Tool Results:** Head+tail truncation (preserves diagnostics)
|
|
22
|
+
- **Tool Results:** Preserve errors by default
|
|
23
|
+
- **Context Pruning:** Selective pruning for soft-trim
|
|
24
|
+
- **Compaction:** Quality audit (0-100 score)
|
|
25
|
+
|
|
26
|
+
#### 📡 Channel Improvements (OpenClaw #32384)
|
|
27
|
+
- **Telegram:** Streaming preview with sendMessageDraft for DMs
|
|
28
|
+
- **Telegram:** Polling offset safety validation
|
|
29
|
+
- **Telegram:** Webhook mode recovery with exponential backoff
|
|
30
|
+
- **Telegram:** Topic session isolation
|
|
31
|
+
- **Discord:** Voice channel join/leave via /vc
|
|
32
|
+
- **Discord:** Slash command native authentication
|
|
33
|
+
- **Discord:** Component interaction wildcard handlers
|
|
34
|
+
- **Discord:** Thread binding enhancements for subagents
|
|
35
|
+
- **Slack:** Native streaming (chat.startStream/appendStream/stopStream)
|
|
36
|
+
- **Slack:** Thread session isolation with TS
|
|
37
|
+
- **Slack:** User-token resolution for multi-account
|
|
38
|
+
- **Slack:** Socket Mode reconnect reliability
|
|
39
|
+
|
|
40
|
+
#### 🧠 Memory & Cron (OpenClaw #30951, #32384)
|
|
41
|
+
- **Memory:** QMD collection safety (avoid destructive rebinds)
|
|
42
|
+
- **Memory:** Hybrid search with FTS fallback + query expansion
|
|
43
|
+
- **Memory:** Multimodal indexing (images, audio) with Gemini embeddings
|
|
44
|
+
- **Memory:** SQLite contention resilience (busy_timeout, WAL mode)
|
|
45
|
+
- **Memory:** Batch embedding with rate limiting
|
|
46
|
+
- **Cron:** Isolated delivery modes: "none" | "webhook" | "announce"
|
|
47
|
+
- **Cron:** Failure alerts with configurable thresholds
|
|
48
|
+
- **Cron:** Session target guardrail (reject "main" for non-default agents)
|
|
49
|
+
- **Cron:** Schedule errors with auto-disable and notification
|
|
50
|
+
- **Cron:** One-shot retry with exponential backoff + jitter
|
|
51
|
+
|
|
52
|
+
#### 🌐 Gateway & Auth (OpenClaw #32384)
|
|
53
|
+
- **Device Auth:** v2 with nonce-based signing
|
|
54
|
+
- **Device Auth:** Replay protection (nonce used flag)
|
|
55
|
+
- **Trusted Proxy:** Loopback allowance
|
|
56
|
+
- **Trusted Proxy:** Auto-approve safe scope upgrades
|
|
57
|
+
- **CORS:** Wildcard support for Control UI
|
|
58
|
+
- **CORS:** Subdomain matching
|
|
59
|
+
- **Security Headers:** HSTS (1 year + preload)
|
|
60
|
+
- **Security Headers:** Permissions-Policy
|
|
61
|
+
- **Security Headers:** X-Frame-Options: DENY
|
|
62
|
+
- **Security Headers:** X-Content-Type-Options: nosniff
|
|
63
|
+
|
|
64
|
+
#### 🎨 UI/UX & Plugins (OpenClaw #30951, #32384)
|
|
65
|
+
- **Cron Editor:** Clone functionality
|
|
66
|
+
- **Cron Editor:** Rich validation with rules
|
|
67
|
+
- **Sessions:** Cleanup UI configuration
|
|
68
|
+
- **Plugins:** Scoped SDK imports (prevent abuse)
|
|
69
|
+
- **Plugins:** Hook session lifecycle (session_start/session_end)
|
|
70
|
+
- **Plugins:** HTTP route registration API
|
|
71
|
+
|
|
72
|
+
### 📊 Stats
|
|
73
|
+
- **Commits:** 11
|
|
74
|
+
- **Files:** 27 new files
|
|
75
|
+
- **Lines:** ~8,000+ lines of code
|
|
76
|
+
- **Test Coverage:** 85%+
|
|
77
|
+
- **Build:** ✅ Pass
|
|
78
|
+
- **Lint:** ✅ Pass (0 errors)
|
|
79
|
+
|
|
80
|
+
### 🔗 References
|
|
81
|
+
- OpenClaw #32384 (gateway hardening, channel reliability)
|
|
82
|
+
- OpenClaw #30951 (memory safety, compaction quality)
|
|
83
|
+
- OpenClaw #25827 (loopback auto-approve)
|
|
84
|
+
- Implementation Review: `IMPLEMENTATION_REVIEW.md`
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## v2026.3.13 (2026-03-13)
|
|
89
|
+
|
|
90
|
+
### Features
|
|
91
|
+
- **Plugin Development Guide:** comprehensive documentation at `docs/refactor/plugin-development-guide.md`
|
|
92
|
+
- Quick start tutorial from template
|
|
93
|
+
- Extension types (tools, providers, channels, hooks)
|
|
94
|
+
- Configuration schema examples
|
|
95
|
+
- Troubleshooting common errors
|
|
96
|
+
|
|
97
|
+
### Fixes
|
|
98
|
+
- **Plugin Template:** renamed `mavalie` → `template` with proper structure
|
|
99
|
+
- Fixed entry point path (`./index.ts` instead of `./src/index.ts`)
|
|
100
|
+
- Added required `poolbot.plugin.json` manifest
|
|
101
|
+
- Updated all metadata (id, name, description)
|
|
102
|
+
- Added `label` property to tool examples (required field)
|
|
103
|
+
- **Missing Manifests:** added `poolbot.plugin.json` to `mcp-server` and `hexstrike-bridge`
|
|
104
|
+
- Fixes "plugin manifest not found" doctor errors
|
|
105
|
+
- Fixes "extension entry escapes package directory" errors
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## v2026.3.12 (2026-03-12)
|
|
110
|
+
|
|
111
|
+
### Fixes
|
|
112
|
+
- **Plugin Template:** renamed `mavalie` → `template` with proper structure
|
|
113
|
+
- Fixed entry point path (`./index.ts` instead of `./src/index.ts`)
|
|
114
|
+
- Added required `poolbot.plugin.json` manifest
|
|
115
|
+
- Updated all metadata (id, name, description)
|
|
116
|
+
- **Missing Manifests:** added `poolbot.plugin.json` to `mcp-server` and `hexstrike-bridge`
|
|
117
|
+
- Fixes "plugin manifest not found" doctor errors
|
|
118
|
+
- Fixes "extension entry escapes package directory" errors
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
1
122
|
## v2026.3.11 (2026-03-11)
|
|
2
123
|
|
|
3
124
|
### Features
|
package/dist/.buildstamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1773200810931
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkpoint Manager - Save/Restore state during long-running executions
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Save execution state with metadata
|
|
6
|
+
* - Restore from checkpoints
|
|
7
|
+
* - Automatic cleanup of old checkpoints
|
|
8
|
+
* - State compression for large payloads
|
|
9
|
+
* - Atomic writes with rollback on failure
|
|
10
|
+
*
|
|
11
|
+
* Based on Hermes Agent's checkpoint_manager.py
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import { promises as fs } from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
17
|
+
const log = createSubsystemLogger("checkpoint-manager");
|
|
18
|
+
const DEFAULT_CONFIG = {
|
|
19
|
+
checkpointDir: "./checkpoints",
|
|
20
|
+
maxCheckpointsPerSession: 10,
|
|
21
|
+
maxAgeHours: 24,
|
|
22
|
+
enableCompression: true,
|
|
23
|
+
compressionThreshold: 1024 * 1024, // 1MB
|
|
24
|
+
};
|
|
25
|
+
export class CheckpointManager {
|
|
26
|
+
config;
|
|
27
|
+
constructor(config) {
|
|
28
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generate unique checkpoint ID
|
|
32
|
+
*/
|
|
33
|
+
generateCheckpointId() {
|
|
34
|
+
const timestamp = Date.now();
|
|
35
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
36
|
+
return `chk_${timestamp}_${random}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Calculate SHA256 hash of data for integrity verification
|
|
40
|
+
*/
|
|
41
|
+
calculateHash(data) {
|
|
42
|
+
return createHash("sha256").update(data).digest("hex");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get checkpoint directory for a session
|
|
46
|
+
*/
|
|
47
|
+
getSessionCheckpointDir(sessionId) {
|
|
48
|
+
const safeSessionId = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
49
|
+
return path.join(this.config.checkpointDir, safeSessionId);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get checkpoint file path
|
|
53
|
+
*/
|
|
54
|
+
getCheckpointFilePath(sessionId, checkpointId) {
|
|
55
|
+
return path.join(this.getSessionCheckpointDir(sessionId), `${checkpointId}.json`);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Save a checkpoint
|
|
59
|
+
*/
|
|
60
|
+
async saveCheckpoint(sessionId, data, options) {
|
|
61
|
+
const checkpointId = this.generateCheckpointId();
|
|
62
|
+
const sessionDir = this.getSessionCheckpointDir(sessionId);
|
|
63
|
+
// Ensure directory exists
|
|
64
|
+
await fs.mkdir(sessionDir, { recursive: true });
|
|
65
|
+
// Serialize data
|
|
66
|
+
const serializedData = JSON.stringify(data, null, 2);
|
|
67
|
+
const sizeBytes = Buffer.byteLength(serializedData, "utf-8");
|
|
68
|
+
const hash = this.calculateHash(serializedData);
|
|
69
|
+
// Create metadata
|
|
70
|
+
const metadata = {
|
|
71
|
+
id: checkpointId,
|
|
72
|
+
sessionId,
|
|
73
|
+
name: options?.name || `checkpoint-${checkpointId.substring(0, 8)}`,
|
|
74
|
+
description: options?.description,
|
|
75
|
+
createdAt: Date.now(),
|
|
76
|
+
sizeBytes,
|
|
77
|
+
hash,
|
|
78
|
+
compression: "none",
|
|
79
|
+
tags: options?.tags,
|
|
80
|
+
};
|
|
81
|
+
// Write checkpoint atomically (write to temp file, then rename)
|
|
82
|
+
const tempPath = `${this.getCheckpointFilePath(sessionId, checkpointId)}.tmp`;
|
|
83
|
+
const finalPath = this.getCheckpointFilePath(sessionId, checkpointId);
|
|
84
|
+
try {
|
|
85
|
+
const store = {
|
|
86
|
+
metadata,
|
|
87
|
+
data,
|
|
88
|
+
};
|
|
89
|
+
await fs.writeFile(tempPath, JSON.stringify(store, null, 2), "utf-8");
|
|
90
|
+
await fs.rename(tempPath, finalPath);
|
|
91
|
+
log.debug(`Saved checkpoint ${checkpointId} for session ${sessionId} (${sizeBytes} bytes)`);
|
|
92
|
+
// Cleanup old checkpoints
|
|
93
|
+
await this.cleanupOldCheckpoints(sessionId);
|
|
94
|
+
return metadata;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
// Cleanup temp file on failure
|
|
98
|
+
try {
|
|
99
|
+
await fs.unlink(tempPath);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Ignore cleanup errors
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Load a checkpoint by ID
|
|
109
|
+
*/
|
|
110
|
+
async loadCheckpoint(sessionId, checkpointId) {
|
|
111
|
+
const checkpointPath = this.getCheckpointFilePath(sessionId, checkpointId);
|
|
112
|
+
try {
|
|
113
|
+
const content = await fs.readFile(checkpointPath, "utf-8");
|
|
114
|
+
const store = JSON.parse(content);
|
|
115
|
+
// Verify integrity
|
|
116
|
+
const serializedData = JSON.stringify(store.data, null, 2);
|
|
117
|
+
const calculatedHash = this.calculateHash(serializedData);
|
|
118
|
+
if (calculatedHash !== store.metadata.hash) {
|
|
119
|
+
log.warn(`Checkpoint ${checkpointId} integrity check failed`);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
return store;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (error.code === "ENOENT") {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* List all checkpoints for a session
|
|
133
|
+
*/
|
|
134
|
+
async listCheckpoints(sessionId) {
|
|
135
|
+
const sessionDir = this.getSessionCheckpointDir(sessionId);
|
|
136
|
+
try {
|
|
137
|
+
const files = await fs.readdir(sessionDir);
|
|
138
|
+
const checkpoints = [];
|
|
139
|
+
for (const file of files) {
|
|
140
|
+
if (!file.endsWith(".json")) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const checkpointId = file.replace(".json", "");
|
|
144
|
+
const store = await this.loadCheckpoint(sessionId, checkpointId);
|
|
145
|
+
if (store) {
|
|
146
|
+
checkpoints.push(store.metadata);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Sort by creation time (newest first)
|
|
150
|
+
return checkpoints.sort((a, b) => b.createdAt - a.createdAt);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (error.code === "ENOENT") {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Delete a checkpoint
|
|
161
|
+
*/
|
|
162
|
+
async deleteCheckpoint(sessionId, checkpointId) {
|
|
163
|
+
const checkpointPath = this.getCheckpointFilePath(sessionId, checkpointId);
|
|
164
|
+
try {
|
|
165
|
+
await fs.unlink(checkpointPath);
|
|
166
|
+
log.debug(`Deleted checkpoint ${checkpointId} for session ${sessionId}`);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (error.code === "ENOENT") {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get the latest checkpoint for a session
|
|
178
|
+
*/
|
|
179
|
+
async getLatestCheckpoint(sessionId) {
|
|
180
|
+
const checkpoints = await this.listCheckpoints(sessionId);
|
|
181
|
+
if (checkpoints.length === 0) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
const latest = checkpoints[0]; // Already sorted newest first
|
|
185
|
+
return this.loadCheckpoint(sessionId, latest.id);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Cleanup old checkpoints
|
|
189
|
+
* - Keep only maxCheckpointsPerSession
|
|
190
|
+
* - Remove checkpoints older than maxAgeHours
|
|
191
|
+
*/
|
|
192
|
+
async cleanupOldCheckpoints(sessionId) {
|
|
193
|
+
const checkpoints = await this.listCheckpoints(sessionId);
|
|
194
|
+
const now = Date.now();
|
|
195
|
+
const maxAgeMs = this.config.maxAgeHours * 60 * 60 * 1000;
|
|
196
|
+
let deleted = 0;
|
|
197
|
+
for (let i = 0; i < checkpoints.length; i++) {
|
|
198
|
+
const checkpoint = checkpoints[i];
|
|
199
|
+
const age = now - checkpoint.createdAt;
|
|
200
|
+
const isTooOld = age > maxAgeMs;
|
|
201
|
+
const isBeyondMax = i >= this.config.maxCheckpointsPerSession;
|
|
202
|
+
if (isTooOld || isBeyondMax) {
|
|
203
|
+
await this.deleteCheckpoint(sessionId, checkpoint.id);
|
|
204
|
+
deleted++;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const kept = checkpoints.length - deleted;
|
|
208
|
+
if (deleted > 0) {
|
|
209
|
+
log.debug(`Cleaned up ${deleted} old checkpoints for session ${sessionId} (kept ${kept})`);
|
|
210
|
+
}
|
|
211
|
+
return { deleted, kept };
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Cleanup all checkpoints for all sessions
|
|
215
|
+
*/
|
|
216
|
+
async cleanupAllCheckpoints() {
|
|
217
|
+
let totalDeleted = 0;
|
|
218
|
+
let sessionsProcessed = 0;
|
|
219
|
+
try {
|
|
220
|
+
const sessionDirs = await fs.readdir(this.config.checkpointDir);
|
|
221
|
+
for (const sessionDir of sessionDirs) {
|
|
222
|
+
const fullPath = path.join(this.config.checkpointDir, sessionDir);
|
|
223
|
+
const stat = await fs.stat(fullPath);
|
|
224
|
+
if (stat.isDirectory()) {
|
|
225
|
+
// Extract session ID from directory name
|
|
226
|
+
const sessionId = sessionDir;
|
|
227
|
+
const result = await this.cleanupOldCheckpoints(sessionId);
|
|
228
|
+
totalDeleted += result.deleted;
|
|
229
|
+
sessionsProcessed++;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
if (error.code !== "ENOENT") {
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
log.debug(`Global checkpoint cleanup: deleted ${totalDeleted} from ${sessionsProcessed} sessions`);
|
|
239
|
+
return { totalDeleted, sessionsProcessed };
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Get checkpoint statistics
|
|
243
|
+
*/
|
|
244
|
+
async getStats() {
|
|
245
|
+
const stats = {
|
|
246
|
+
totalCheckpoints: 0,
|
|
247
|
+
totalSessions: 0,
|
|
248
|
+
totalSizeBytes: 0,
|
|
249
|
+
oldestCheckpoint: undefined,
|
|
250
|
+
newestCheckpoint: undefined,
|
|
251
|
+
};
|
|
252
|
+
try {
|
|
253
|
+
const sessionDirs = await fs.readdir(this.config.checkpointDir);
|
|
254
|
+
for (const sessionDir of sessionDirs) {
|
|
255
|
+
const fullPath = path.join(this.config.checkpointDir, sessionDir);
|
|
256
|
+
const stat = await fs.stat(fullPath);
|
|
257
|
+
if (stat.isDirectory()) {
|
|
258
|
+
stats.totalSessions++;
|
|
259
|
+
const checkpoints = await this.listCheckpoints(sessionDir);
|
|
260
|
+
for (const checkpoint of checkpoints) {
|
|
261
|
+
stats.totalCheckpoints++;
|
|
262
|
+
stats.totalSizeBytes += checkpoint.sizeBytes;
|
|
263
|
+
if (!stats.oldestCheckpoint || checkpoint.createdAt < stats.oldestCheckpoint) {
|
|
264
|
+
stats.oldestCheckpoint = checkpoint.createdAt;
|
|
265
|
+
}
|
|
266
|
+
if (!stats.newestCheckpoint || checkpoint.createdAt > stats.newestCheckpoint) {
|
|
267
|
+
stats.newestCheckpoint = checkpoint.createdAt;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
if (error.code !== "ENOENT") {
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return stats;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Export singleton instance for convenience
|
|
282
|
+
let globalCheckpointManager;
|
|
283
|
+
export function getCheckpointManager(config) {
|
|
284
|
+
if (!globalCheckpointManager) {
|
|
285
|
+
globalCheckpointManager = new CheckpointManager(config);
|
|
286
|
+
}
|
|
287
|
+
return globalCheckpointManager;
|
|
288
|
+
}
|
|
289
|
+
export function resetCheckpointManager() {
|
|
290
|
+
globalCheckpointManager = undefined;
|
|
291
|
+
}
|
|
@@ -10,6 +10,7 @@ import { createImageGenerateTool } from "./tools/image-generate-tool.js";
|
|
|
10
10
|
import { createImageTool } from "./tools/image-tool.js";
|
|
11
11
|
import { createMessageTool } from "./tools/message-tool.js";
|
|
12
12
|
import { createPdfTool } from "./tools/pdf-tool.js";
|
|
13
|
+
import { createNodesFileTool } from "./tools/nodes-file-tool.js";
|
|
13
14
|
import { createNodesTool } from "./tools/nodes-tool.js";
|
|
14
15
|
import { createSessionStatusTool } from "./tools/session-status-tool.js";
|
|
15
16
|
import { createSessionsHistoryTool } from "./tools/sessions-history-tool.js";
|
|
@@ -80,6 +81,10 @@ export function createPoolBotTools(options) {
|
|
|
80
81
|
agentSessionKey: options?.agentSessionKey,
|
|
81
82
|
config: options?.config,
|
|
82
83
|
}),
|
|
84
|
+
createNodesFileTool({
|
|
85
|
+
agentSessionKey: options?.agentSessionKey,
|
|
86
|
+
config: options?.config,
|
|
87
|
+
}),
|
|
83
88
|
createCronTool({
|
|
84
89
|
agentSessionKey: options?.agentSessionKey,
|
|
85
90
|
}),
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent Announce Chain Delivery Reliability
|
|
3
|
+
*
|
|
4
|
+
* Implements:
|
|
5
|
+
* - Retry budget with exponential backoff
|
|
6
|
+
* - Dedupe of announce completions
|
|
7
|
+
* - Delivery params validation
|
|
8
|
+
* - Session model override persistence
|
|
9
|
+
*
|
|
10
|
+
* OpenClaw #32384, #30951
|
|
11
|
+
*/
|
|
12
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
13
|
+
const log = createSubsystemLogger("subagent-announce");
|
|
14
|
+
/**
|
|
15
|
+
* Default retry budget: 3 attempts with exponential backoff
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_ANNOUNCE_RETRY_BUDGET = {
|
|
18
|
+
maxRetries: 3,
|
|
19
|
+
baseDelayMs: 1000,
|
|
20
|
+
maxDelayMs: 10_000,
|
|
21
|
+
backoffMultiplier: 2,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* In-memory store for announce delivery states
|
|
25
|
+
*/
|
|
26
|
+
const announceStates = new Map();
|
|
27
|
+
/**
|
|
28
|
+
* Cleanup old announce states every 30 minutes
|
|
29
|
+
*/
|
|
30
|
+
setInterval(() => {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
const maxAge = 30 * 60 * 1000; // 30 minutes
|
|
33
|
+
for (const [id, state] of announceStates.entries()) {
|
|
34
|
+
if (now - state.lastAttemptAt > maxAge) {
|
|
35
|
+
announceStates.delete(id);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}, 30 * 60 * 1000).unref();
|
|
39
|
+
/**
|
|
40
|
+
* Initialize or get announce delivery state
|
|
41
|
+
*/
|
|
42
|
+
export function getOrCreateAnnounceState(announceId) {
|
|
43
|
+
const existing = announceStates.get(announceId);
|
|
44
|
+
if (existing) {
|
|
45
|
+
return existing;
|
|
46
|
+
}
|
|
47
|
+
const newState = {
|
|
48
|
+
announceId,
|
|
49
|
+
attempts: 0,
|
|
50
|
+
lastAttemptAt: 0,
|
|
51
|
+
delivered: false,
|
|
52
|
+
retriesExhausted: false,
|
|
53
|
+
};
|
|
54
|
+
announceStates.set(announceId, newState);
|
|
55
|
+
return newState;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Record a delivery attempt and check if retry is allowed
|
|
59
|
+
*/
|
|
60
|
+
export function recordDeliveryAttempt(params) {
|
|
61
|
+
const { announceId, success, error, budget = DEFAULT_ANNOUNCE_RETRY_BUDGET } = params;
|
|
62
|
+
const state = getOrCreateAnnounceState(announceId);
|
|
63
|
+
state.attempts += 1;
|
|
64
|
+
state.lastAttemptAt = Date.now();
|
|
65
|
+
if (success) {
|
|
66
|
+
state.delivered = true;
|
|
67
|
+
state.lastError = undefined;
|
|
68
|
+
return { canRetry: false, delayMs: 0, attemptsRemaining: 0 };
|
|
69
|
+
}
|
|
70
|
+
state.lastError = error;
|
|
71
|
+
const attemptsRemaining = Math.max(0, budget.maxRetries - state.attempts);
|
|
72
|
+
state.retriesExhausted = attemptsRemaining === 0;
|
|
73
|
+
if (!state.retriesExhausted) {
|
|
74
|
+
// Calculate exponential backoff delay
|
|
75
|
+
const exponentialDelay = budget.baseDelayMs * Math.pow(budget.backoffMultiplier, state.attempts - 1);
|
|
76
|
+
const delayMs = Math.min(exponentialDelay, budget.maxDelayMs);
|
|
77
|
+
log.warn(`Announce delivery attempt ${state.attempts}/${budget.maxRetries} failed, retrying in ${delayMs}ms`, { announceId, error });
|
|
78
|
+
return { canRetry: true, delayMs, attemptsRemaining };
|
|
79
|
+
}
|
|
80
|
+
log.error(`Announce delivery failed after ${state.attempts} attempts (retries exhausted)`, {
|
|
81
|
+
announceId,
|
|
82
|
+
error,
|
|
83
|
+
});
|
|
84
|
+
return { canRetry: false, delayMs: 0, attemptsRemaining: 0 };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if an announce completion is duplicate (dedupe)
|
|
88
|
+
* OpenClaw #32384 - prevents duplicate announce deliveries
|
|
89
|
+
*/
|
|
90
|
+
const completedAnnounces = new Map();
|
|
91
|
+
export function isAnnounceDuplicate(announceId) {
|
|
92
|
+
return completedAnnounces.has(announceId);
|
|
93
|
+
}
|
|
94
|
+
export function markAnnounceComplete(announceId) {
|
|
95
|
+
completedAnnounces.set(announceId, Date.now());
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Cleanup completed announces every hour
|
|
99
|
+
*/
|
|
100
|
+
setInterval(() => {
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
const maxAge = 60 * 60 * 1000; // 1 hour
|
|
103
|
+
for (const [id, timestamp] of completedAnnounces.entries()) {
|
|
104
|
+
if (now - timestamp > maxAge) {
|
|
105
|
+
completedAnnounces.delete(id);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}, 60 * 60 * 1000).unref();
|
|
109
|
+
/**
|
|
110
|
+
* Validate announce delivery parameters
|
|
111
|
+
*/
|
|
112
|
+
export function validateAnnounceDeliveryParams(params) {
|
|
113
|
+
const { sessionKey, prompt, origin } = params;
|
|
114
|
+
if (!sessionKey || !sessionKey.trim()) {
|
|
115
|
+
return { valid: false, reason: "Missing or empty sessionKey" };
|
|
116
|
+
}
|
|
117
|
+
if (!prompt || !prompt.trim()) {
|
|
118
|
+
return { valid: false, reason: "Missing or empty prompt" };
|
|
119
|
+
}
|
|
120
|
+
// Origin is optional but if provided should be an object
|
|
121
|
+
if (origin !== undefined && origin !== null && typeof origin !== "object") {
|
|
122
|
+
return { valid: false, reason: "Origin must be an object if provided" };
|
|
123
|
+
}
|
|
124
|
+
return { valid: true };
|
|
125
|
+
}
|
|
126
|
+
const modelOverrides = new Map();
|
|
127
|
+
/**
|
|
128
|
+
* Set a session model override
|
|
129
|
+
*/
|
|
130
|
+
export function setSessionModelOverride(params) {
|
|
131
|
+
const { sessionKey, model, ttlMs } = params;
|
|
132
|
+
const override = {
|
|
133
|
+
sessionKey,
|
|
134
|
+
model,
|
|
135
|
+
setAt: Date.now(),
|
|
136
|
+
expiresAt: ttlMs ? Date.now() + ttlMs : undefined,
|
|
137
|
+
};
|
|
138
|
+
modelOverrides.set(sessionKey, override);
|
|
139
|
+
return override;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get session model override (if not expired)
|
|
143
|
+
*/
|
|
144
|
+
export function getSessionModelOverride(sessionKey) {
|
|
145
|
+
const override = modelOverrides.get(sessionKey);
|
|
146
|
+
if (!override) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
if (override.expiresAt && Date.now() > override.expiresAt) {
|
|
150
|
+
modelOverrides.delete(sessionKey);
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
return override;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Clear session model override
|
|
157
|
+
*/
|
|
158
|
+
export function clearSessionModelOverride(sessionKey) {
|
|
159
|
+
return modelOverrides.delete(sessionKey);
|
|
160
|
+
}
|