@poolzin/pool-bot 2026.3.9 → 2026.3.10

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.
Files changed (125) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +147 -69
  3. package/dist/.buildstamp +1 -1
  4. package/dist/agents/error-classifier.js +26 -77
  5. package/dist/agents/skills/security.js +1 -7
  6. package/dist/build-info.json +3 -3
  7. package/dist/cli/cron-cli/register.cron-dashboard.js +339 -0
  8. package/dist/cli/cron-cli/register.js +2 -0
  9. package/dist/cli/errors.js +187 -0
  10. package/dist/cli/program/command-registry.js +13 -0
  11. package/dist/cli/program/register.maintenance.js +21 -0
  12. package/dist/cli/program/register.subclis.js +9 -0
  13. package/dist/cli/swarm-cli/register.js +8 -0
  14. package/dist/cli/swarm-cli/register.swarm-status.js +488 -0
  15. package/dist/cli/telemetry-cli/register.js +10 -0
  16. package/dist/cli/telemetry-cli/register.telemetry-alerts.js +176 -0
  17. package/dist/cli/telemetry-cli/register.telemetry-metrics.js +323 -0
  18. package/dist/cli/telemetry-cli/register.telemetry-status.js +179 -0
  19. package/dist/commands/doctor-checks.js +498 -0
  20. package/dist/context-engine/index.js +1 -1
  21. package/dist/context-engine/legacy.js +1 -3
  22. package/dist/context-engine/summarizing.js +5 -8
  23. package/dist/cron/service/timer.js +18 -0
  24. package/dist/gateway/protocol/index.js +5 -2
  25. package/dist/gateway/protocol/schema/error-codes.js +1 -0
  26. package/dist/gateway/protocol/schema/swarm.js +80 -0
  27. package/dist/gateway/protocol/schema.js +1 -0
  28. package/dist/gateway/server-close.js +4 -0
  29. package/dist/gateway/server-constants.js +1 -0
  30. package/dist/gateway/server-cron.js +29 -0
  31. package/dist/gateway/server-maintenance.js +35 -2
  32. package/dist/gateway/server-methods/swarm.js +58 -0
  33. package/dist/gateway/server-methods/telemetry.js +71 -0
  34. package/dist/gateway/server-methods-list.js +8 -0
  35. package/dist/gateway/server-methods.js +9 -2
  36. package/dist/gateway/server.impl.js +33 -16
  37. package/dist/infra/abort-pattern.js +4 -4
  38. package/dist/infra/retry.js +3 -1
  39. package/dist/skills/commands.js +7 -25
  40. package/dist/skills/index.js +14 -17
  41. package/dist/skills/parser.js +12 -27
  42. package/dist/skills/registry.js +3 -6
  43. package/dist/skills/security.js +2 -8
  44. package/dist/swarm/service.js +247 -0
  45. package/dist/telemetry/alert-engine.js +258 -0
  46. package/dist/telemetry/cron-instrumentation.js +49 -0
  47. package/dist/telemetry/gateway-instrumentation.js +80 -0
  48. package/dist/telemetry/instrumentation.js +66 -0
  49. package/dist/telemetry/service.js +345 -0
  50. package/dist/tui/components/assistant-message.js +6 -2
  51. package/dist/tui/components/hyperlink-markdown.js +32 -0
  52. package/dist/tui/components/searchable-select-list.js +12 -1
  53. package/dist/tui/components/user-message.js +6 -2
  54. package/dist/tui/index.js +22 -6
  55. package/dist/tui/theme/theme-detection.js +226 -0
  56. package/dist/tui/tui-command-handlers.js +20 -0
  57. package/dist/tui/tui-formatters.js +4 -3
  58. package/dist/tui/utils/ctrl-c-handler.js +67 -0
  59. package/dist/tui/utils/osc8-hyperlinks.js +208 -0
  60. package/dist/tui/utils/safe-stop.js +180 -0
  61. package/dist/tui/utils/session-key-utils.js +81 -0
  62. package/dist/tui/utils/text-sanitization.js +284 -0
  63. package/dist/utils/lru-cache.js +116 -0
  64. package/dist/utils/performance.js +199 -0
  65. package/dist/utils/retry.js +240 -0
  66. package/docs/MELHORIAS_IMPLEMENTADAS.md +228 -0
  67. package/docs/MELHORIAS_PROFISSIONAIS.md +282 -0
  68. package/docs/PLANO_ACAO_TUI.md +357 -0
  69. package/docs/PROGRESSO_TUI.md +66 -0
  70. package/docs/RELATORIO_FINAL.md +217 -0
  71. package/docs/diagnostico-shell-completion.md +265 -0
  72. package/docs/features/advanced-memory.md +585 -0
  73. package/docs/features/discord-components-v2.md +277 -0
  74. package/docs/features/swarm.md +100 -0
  75. package/docs/features/telemetry.md +284 -0
  76. package/docs/integrations/INTEGRATION_PLAN.md +665 -345
  77. package/docs/models/provider-infrastructure.md +400 -0
  78. package/docs/security/exec-approvals.md +294 -0
  79. package/extensions/bluebubbles/package.json +1 -1
  80. package/extensions/copilot-proxy/package.json +1 -1
  81. package/extensions/diagnostics-otel/package.json +1 -1
  82. package/extensions/discord/package.json +1 -1
  83. package/extensions/feishu/package.json +1 -1
  84. package/extensions/google-antigravity-auth/package.json +1 -1
  85. package/extensions/google-gemini-cli-auth/package.json +1 -1
  86. package/extensions/googlechat/package.json +1 -1
  87. package/extensions/hexstrike-bridge/README.md +119 -0
  88. package/extensions/hexstrike-bridge/index.test.ts +247 -0
  89. package/extensions/hexstrike-bridge/index.ts +487 -0
  90. package/extensions/hexstrike-bridge/package.json +17 -0
  91. package/extensions/imessage/package.json +1 -1
  92. package/extensions/irc/package.json +1 -1
  93. package/extensions/line/package.json +1 -1
  94. package/extensions/llm-task/package.json +1 -1
  95. package/extensions/lobster/package.json +1 -1
  96. package/extensions/matrix/CHANGELOG.md +5 -0
  97. package/extensions/matrix/package.json +1 -1
  98. package/extensions/mattermost/package.json +1 -1
  99. package/extensions/mcp-server/index.ts +14 -0
  100. package/extensions/mcp-server/package.json +11 -0
  101. package/extensions/mcp-server/src/service.ts +540 -0
  102. package/extensions/memory-core/package.json +1 -1
  103. package/extensions/memory-lancedb/package.json +1 -1
  104. package/extensions/minimax-portal-auth/package.json +1 -1
  105. package/extensions/msteams/CHANGELOG.md +5 -0
  106. package/extensions/msteams/package.json +1 -1
  107. package/extensions/nextcloud-talk/package.json +1 -1
  108. package/extensions/nostr/CHANGELOG.md +5 -0
  109. package/extensions/nostr/package.json +1 -1
  110. package/extensions/open-prose/package.json +1 -1
  111. package/extensions/openai-codex-auth/package.json +1 -1
  112. package/extensions/signal/package.json +1 -1
  113. package/extensions/slack/package.json +1 -1
  114. package/extensions/telegram/package.json +1 -1
  115. package/extensions/tlon/package.json +1 -1
  116. package/extensions/twitch/CHANGELOG.md +5 -0
  117. package/extensions/twitch/package.json +1 -1
  118. package/extensions/voice-call/CHANGELOG.md +5 -0
  119. package/extensions/voice-call/package.json +1 -1
  120. package/extensions/whatsapp/package.json +1 -1
  121. package/extensions/zalo/CHANGELOG.md +5 -0
  122. package/extensions/zalo/package.json +1 -1
  123. package/extensions/zalouser/CHANGELOG.md +5 -0
  124. package/extensions/zalouser/package.json +1 -1
  125. package/package.json +8 -1
@@ -0,0 +1,258 @@
1
+ import { createSubsystemLogger } from "../logging/subsystem.js";
2
+ const log = createSubsystemLogger("telemetry:alerts");
3
+ export class AlertEngine {
4
+ rules = new Map();
5
+ states = new Map();
6
+ onAlertCallback;
7
+ constructor(rules = []) {
8
+ for (const rule of rules) {
9
+ this.addRule(rule);
10
+ }
11
+ }
12
+ addRule(rule) {
13
+ this.rules.set(rule.id, rule);
14
+ this.states.set(rule.id, { consecutiveCount: 0 });
15
+ log.debug(`Added alert rule: ${rule.id} (${rule.name})`);
16
+ }
17
+ removeRule(ruleId) {
18
+ this.states.delete(ruleId);
19
+ return this.rules.delete(ruleId);
20
+ }
21
+ getRules() {
22
+ return Array.from(this.rules.values());
23
+ }
24
+ onAlert(callback) {
25
+ this.onAlertCallback = callback;
26
+ }
27
+ evaluate(snapshot) {
28
+ const alerts = [];
29
+ const now = Date.now();
30
+ for (const rule of this.rules.values()) {
31
+ if (!rule.enabled)
32
+ continue;
33
+ const alert = this.evaluateRule(rule, snapshot, now);
34
+ if (alert) {
35
+ alerts.push(alert);
36
+ this.onAlertCallback?.(alert);
37
+ }
38
+ }
39
+ return alerts;
40
+ }
41
+ evaluateRule(rule, snapshot, now) {
42
+ const state = this.states.get(rule.id);
43
+ if (!state)
44
+ return null;
45
+ // Find matching metrics
46
+ const matchingMetrics = snapshot.metrics.filter((m) => {
47
+ if (m.name !== rule.threshold.metric)
48
+ return false;
49
+ if (rule.attributes) {
50
+ for (const [key, value] of Object.entries(rule.attributes)) {
51
+ if (m.attributes?.[key] !== value)
52
+ return false;
53
+ }
54
+ }
55
+ return true;
56
+ });
57
+ if (matchingMetrics.length === 0) {
58
+ // Reset state if no matching metrics
59
+ state.consecutiveCount = 0;
60
+ state.triggeredAt = undefined;
61
+ return null;
62
+ }
63
+ // Check if any metric violates threshold
64
+ let violated = false;
65
+ let violatingMetric = null;
66
+ for (const metric of matchingMetrics) {
67
+ if (this.checkThreshold(metric.value, rule.threshold)) {
68
+ violated = true;
69
+ violatingMetric = metric;
70
+ break;
71
+ }
72
+ }
73
+ if (!violated) {
74
+ // Reset state if threshold no longer violated
75
+ state.consecutiveCount = 0;
76
+ state.triggeredAt = undefined;
77
+ return null;
78
+ }
79
+ // Track consecutive violations
80
+ state.consecutiveCount++;
81
+ if (!state.triggeredAt) {
82
+ state.triggeredAt = now;
83
+ }
84
+ // Check duration requirement
85
+ const durationMs = rule.threshold.durationMs ?? 0;
86
+ if (now - state.triggeredAt < durationMs) {
87
+ return null; // Haven't met duration requirement yet
88
+ }
89
+ // Check cooldown
90
+ const cooldownMs = rule.threshold.cooldownMs ?? 60000;
91
+ if (state.lastAlertAt && now - state.lastAlertAt < cooldownMs) {
92
+ return null; // Still in cooldown
93
+ }
94
+ // Trigger alert
95
+ state.lastAlertAt = now;
96
+ return {
97
+ id: `${rule.id}-${now}`,
98
+ ruleId: rule.id,
99
+ name: rule.name,
100
+ severity: rule.severity,
101
+ metric: rule.threshold.metric,
102
+ value: violatingMetric.value,
103
+ threshold: rule.threshold.value,
104
+ operator: rule.threshold.operator,
105
+ timestamp: now,
106
+ message: this.buildAlertMessage(rule, violatingMetric.value),
107
+ attributes: violatingMetric.attributes,
108
+ };
109
+ }
110
+ checkThreshold(value, threshold) {
111
+ switch (threshold.operator) {
112
+ case "gt":
113
+ return value > threshold.value;
114
+ case "gte":
115
+ return value >= threshold.value;
116
+ case "lt":
117
+ return value < threshold.value;
118
+ case "lte":
119
+ return value <= threshold.value;
120
+ case "eq":
121
+ return value === threshold.value;
122
+ default:
123
+ return false;
124
+ }
125
+ }
126
+ buildAlertMessage(rule, value) {
127
+ const opMap = {
128
+ gt: ">",
129
+ gte: ">=",
130
+ lt: "<",
131
+ lte: "<=",
132
+ eq: "=",
133
+ };
134
+ return `${rule.name}: ${rule.threshold.metric} = ${value} ${opMap[rule.threshold.operator]} ${rule.threshold.value}`;
135
+ }
136
+ }
137
+ // Predefined alert rules for common Pool Bot scenarios
138
+ export const defaultAlertRules = [
139
+ // Cron Job Alerts
140
+ {
141
+ id: "cron-high-failure-rate",
142
+ name: "High Cron Job Failure Rate",
143
+ description: "Triggers when cron job failures exceed 3 in 5 minutes",
144
+ enabled: true,
145
+ severity: "warning",
146
+ threshold: {
147
+ metric: "poolbot.cron.job_failures",
148
+ operator: "gt",
149
+ value: 3,
150
+ durationMs: 300000, // 5 minutes
151
+ cooldownMs: 600000, // 10 minutes
152
+ },
153
+ },
154
+ {
155
+ id: "cron-slow-jobs",
156
+ name: "Slow Cron Job Execution",
157
+ description: "Triggers when cron job duration exceeds 60 seconds",
158
+ enabled: true,
159
+ severity: "info",
160
+ threshold: {
161
+ metric: "poolbot.cron.job_duration_ms",
162
+ operator: "gt",
163
+ value: 60000,
164
+ durationMs: 60000, // 1 minute
165
+ cooldownMs: 300000, // 5 minutes
166
+ },
167
+ },
168
+ {
169
+ id: "cron-job-stuck",
170
+ name: "Cron Job Possibly Stuck",
171
+ description: "Triggers when cron job duration exceeds 5 minutes",
172
+ enabled: true,
173
+ severity: "critical",
174
+ threshold: {
175
+ metric: "poolbot.cron.job_duration_ms",
176
+ operator: "gt",
177
+ value: 300000,
178
+ durationMs: 60000,
179
+ cooldownMs: 300000,
180
+ },
181
+ },
182
+ // Security Scan Alerts (HexStrike)
183
+ {
184
+ id: "security-scan-failures",
185
+ name: "Security Scan Failures",
186
+ description: "Triggers when security scans fail",
187
+ enabled: true,
188
+ severity: "warning",
189
+ threshold: {
190
+ metric: "poolbot.security.scan_failures",
191
+ operator: "gt",
192
+ value: 2,
193
+ durationMs: 300000,
194
+ cooldownMs: 600000,
195
+ },
196
+ },
197
+ {
198
+ id: "security-high-latency",
199
+ name: "HexStrike API High Latency",
200
+ description: "Triggers when HexStrike API latency exceeds 5 seconds",
201
+ enabled: true,
202
+ severity: "info",
203
+ threshold: {
204
+ metric: "poolbot.security.api_latency_ms",
205
+ operator: "gt",
206
+ value: 5000,
207
+ durationMs: 120000,
208
+ cooldownMs: 300000,
209
+ },
210
+ },
211
+ // Swarm Alerts
212
+ {
213
+ id: "swarm-high-failure-rate",
214
+ name: "Swarm Task Failure Rate",
215
+ description: "Triggers when swarm tasks fail frequently",
216
+ enabled: true,
217
+ severity: "warning",
218
+ threshold: {
219
+ metric: "poolbot.swarm.task_failures",
220
+ operator: "gt",
221
+ value: 5,
222
+ durationMs: 600000,
223
+ cooldownMs: 600000,
224
+ },
225
+ },
226
+ // Gateway Health Alerts
227
+ {
228
+ id: "gateway-high-memory",
229
+ name: "High Gateway Memory Usage",
230
+ description: "Triggers when gateway memory usage exceeds 512MB",
231
+ enabled: true,
232
+ severity: "critical",
233
+ threshold: {
234
+ metric: "poolbot.gateway.memory_usage_bytes",
235
+ operator: "gt",
236
+ value: 536870912, // 512MB
237
+ durationMs: 120000,
238
+ cooldownMs: 300000,
239
+ },
240
+ },
241
+ {
242
+ id: "gateway-high-cpu",
243
+ name: "High Gateway CPU Usage",
244
+ description: "Triggers when gateway CPU usage exceeds 80%",
245
+ enabled: true,
246
+ severity: "warning",
247
+ threshold: {
248
+ metric: "poolbot.gateway.cpu_usage_percent",
249
+ operator: "gt",
250
+ value: 80,
251
+ durationMs: 180000,
252
+ cooldownMs: 300000,
253
+ },
254
+ },
255
+ ];
256
+ export function createAlertEngine(customRules) {
257
+ return new AlertEngine([...defaultAlertRules, ...(customRules ?? [])]);
258
+ }
@@ -0,0 +1,49 @@
1
+ import { recordCounter, recordGauge, recordHistogram, withInstrumentation, } from "../telemetry/instrumentation.js";
2
+ export function instrumentCronOperation(operation, fn, attributes) {
3
+ return withInstrumentation({
4
+ operation,
5
+ component: "cron",
6
+ attributes,
7
+ }, fn);
8
+ }
9
+ export function recordCronJobExecution(metrics) {
10
+ recordCounter("poolbot.cron.jobs_executed", 1, {
11
+ job_id: metrics.jobId,
12
+ job_name: metrics.jobName,
13
+ schedule: metrics.schedule,
14
+ status: metrics.success ? "success" : "failure",
15
+ });
16
+ recordHistogram("poolbot.cron.job_duration_ms", metrics.durationMs, {
17
+ job_id: metrics.jobId,
18
+ job_name: metrics.jobName,
19
+ });
20
+ if (!metrics.success) {
21
+ recordCounter("poolbot.cron.job_failures", 1, {
22
+ job_id: metrics.jobId,
23
+ job_name: metrics.jobName,
24
+ error_type: metrics.errorType ?? "unknown",
25
+ });
26
+ }
27
+ }
28
+ export function recordCronJobScheduled(jobId, jobName, schedule) {
29
+ recordCounter("poolbot.cron.jobs_scheduled", 1, {
30
+ job_id: jobId,
31
+ job_name: jobName,
32
+ schedule,
33
+ });
34
+ }
35
+ export function recordCronJobCancelled(jobId, jobName) {
36
+ recordCounter("poolbot.cron.jobs_cancelled", 1, {
37
+ job_id: jobId,
38
+ job_name: jobName,
39
+ });
40
+ }
41
+ export function updateActiveCronJobsGauge(count) {
42
+ recordGauge("poolbot.cron.active_jobs", count);
43
+ }
44
+ export function recordCronQueueDepth(depth) {
45
+ recordGauge("poolbot.cron.queue_depth", depth);
46
+ }
47
+ export function recordCronOverdueJobs(count) {
48
+ recordGauge("poolbot.cron.overdue_jobs", count);
49
+ }
@@ -0,0 +1,80 @@
1
+ import { recordCounter, recordHistogram, withInstrumentation, } from "../telemetry/instrumentation.js";
2
+ export function instrumentGatewayRequest(operation, fn, attributes) {
3
+ return withInstrumentation({
4
+ operation,
5
+ component: "gateway",
6
+ attributes,
7
+ }, fn);
8
+ }
9
+ export function recordGatewayRequest(metrics) {
10
+ recordCounter("poolbot.gateway.requests", 1, {
11
+ method: metrics.method,
12
+ path: metrics.path,
13
+ status_code: String(metrics.statusCode),
14
+ });
15
+ recordHistogram("poolbot.gateway.request_duration_ms", metrics.durationMs, {
16
+ method: metrics.method,
17
+ path: metrics.path,
18
+ });
19
+ if (metrics.statusCode >= 500) {
20
+ recordCounter("poolbot.gateway.server_errors", 1, {
21
+ path: metrics.path,
22
+ });
23
+ }
24
+ else if (metrics.statusCode >= 400) {
25
+ recordCounter("poolbot.gateway.client_errors", 1, {
26
+ path: metrics.path,
27
+ status_code: String(metrics.statusCode),
28
+ });
29
+ }
30
+ }
31
+ export function createTelemetryMiddleware() {
32
+ return (req, res, next) => {
33
+ const startTime = Date.now();
34
+ const path = req.url ?? "/unknown";
35
+ const method = req.method ?? "UNKNOWN";
36
+ recordCounter("poolbot.gateway.active_requests", 1, {
37
+ method,
38
+ path,
39
+ });
40
+ const originalEnd = res.end.bind(res);
41
+ res.end = function (...args) {
42
+ const duration = Date.now() - startTime;
43
+ recordCounter("poolbot.gateway.active_requests", -1, {
44
+ method,
45
+ path,
46
+ });
47
+ recordGatewayRequest({
48
+ method,
49
+ path,
50
+ statusCode: res.statusCode,
51
+ durationMs: duration,
52
+ userAgent: req.headers["user-agent"],
53
+ });
54
+ return originalEnd(...args);
55
+ };
56
+ next();
57
+ };
58
+ }
59
+ export function instrumentWebSocketConnection(connectionId) {
60
+ recordCounter("poolbot.gateway.ws.connections", 1, {
61
+ connection_id: connectionId,
62
+ });
63
+ }
64
+ export function recordWebSocketMessage(connectionId, messageType, sizeBytes) {
65
+ recordCounter("poolbot.gateway.ws.messages", 1, {
66
+ connection_id: connectionId,
67
+ message_type: messageType,
68
+ });
69
+ recordHistogram("poolbot.gateway.ws.message_size_bytes", sizeBytes, {
70
+ message_type: messageType,
71
+ });
72
+ }
73
+ export function recordWebSocketDisconnection(connectionId, durationMs) {
74
+ recordCounter("poolbot.gateway.ws.disconnections", 1, {
75
+ connection_id: connectionId,
76
+ });
77
+ recordHistogram("poolbot.gateway.ws.connection_duration_ms", durationMs, {
78
+ connection_id: connectionId,
79
+ });
80
+ }
@@ -0,0 +1,66 @@
1
+ import { getGlobalTelemetryService } from "../telemetry/service.js";
2
+ export function withInstrumentation(options, fn) {
3
+ const telemetry = getGlobalTelemetryService();
4
+ if (!telemetry?.isEnabled()) {
5
+ return Promise.resolve(fn());
6
+ }
7
+ const spanName = `${options.component}.${options.operation}`;
8
+ const attributes = {
9
+ component: options.component,
10
+ operation: options.operation,
11
+ ...options.attributes,
12
+ };
13
+ telemetry.recordCounter(`poolbot.${options.component}.operations`, 1, {
14
+ operation: options.operation,
15
+ });
16
+ const startTime = Date.now();
17
+ return telemetry.withSpan(spanName, async () => {
18
+ try {
19
+ const result = await fn();
20
+ const duration = Date.now() - startTime;
21
+ telemetry.recordHistogram(`poolbot.${options.component}.duration_ms`, duration, {
22
+ operation: options.operation,
23
+ });
24
+ telemetry.recordCounter(`poolbot.${options.component}.success`, 1, {
25
+ operation: options.operation,
26
+ });
27
+ return result;
28
+ }
29
+ catch (error) {
30
+ const duration = Date.now() - startTime;
31
+ telemetry.recordHistogram(`poolbot.${options.component}.duration_ms`, duration, {
32
+ operation: options.operation,
33
+ error: "true",
34
+ });
35
+ telemetry.recordCounter(`poolbot.${options.component}.errors`, 1, {
36
+ operation: options.operation,
37
+ error_type: error instanceof Error ? error.name : "unknown",
38
+ });
39
+ throw error;
40
+ }
41
+ }, attributes);
42
+ }
43
+ export function recordMetric(name, value, attributes) {
44
+ const telemetry = getGlobalTelemetryService();
45
+ if (!telemetry?.isEnabled())
46
+ return;
47
+ telemetry.recordHistogram(name, value, attributes);
48
+ }
49
+ export function recordCounter(name, value = 1, attributes) {
50
+ const telemetry = getGlobalTelemetryService();
51
+ if (!telemetry?.isEnabled())
52
+ return;
53
+ telemetry.recordCounter(name, value, attributes);
54
+ }
55
+ export function recordGauge(name, value, attributes) {
56
+ const telemetry = getGlobalTelemetryService();
57
+ if (!telemetry?.isEnabled())
58
+ return;
59
+ telemetry.recordGauge(name, value, attributes);
60
+ }
61
+ export function recordHistogram(name, value, attributes) {
62
+ const telemetry = getGlobalTelemetryService();
63
+ if (!telemetry?.isEnabled())
64
+ return;
65
+ telemetry.recordHistogram(name, value, attributes);
66
+ }