natureco-cli 2.23.28 → 2.23.29

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/src/utils/api.js CHANGED
@@ -317,8 +317,6 @@ function encodeToolResult(toolResult) {
317
317
  content = toolResult;
318
318
  } else if (toolResult.output) {
319
319
  content = toolResult.output;
320
- /* DEAD CODE */ } else if (toolResult.data) {
321
- content = JSON.stringify(toolResult.data); /* DEAD CODE */
322
320
  } else if (toolResult.success !== undefined) {
323
321
  // Handle { success: true/false, output/error: ... } format
324
322
  content = toolResult.success ? (toolResult.output || JSON.stringify(toolResult)) : (toolResult.error || 'Unknown error');
@@ -429,9 +427,7 @@ function formatToolsForAnthropic() {
429
427
  */
430
428
  async function sendMessageOpenAICompatible(providerConfig, messages, tools) {
431
429
  const baseUrl = providerConfig.url.replace(/\/+$/, '');
432
- const endpoint = baseUrl.includes('api.natureco.me')
433
- ? 'https://api.natureco.me/api/v1/chat/completions'
434
- : `${baseUrl}/chat/completions`;
430
+ const endpoint = `${baseUrl}/chat/completions`;
435
431
  const requestBody = {
436
432
  model: providerConfig.model,
437
433
  messages: messages,
@@ -859,16 +855,8 @@ async function getBots(apiKey) {
859
855
  }
860
856
  } catch (e) {
861
857
  debugLog('[getBots] NatureCo API error:', e.message);
858
+ return { bots: [], error: e.message };
862
859
  }
863
- // Fallback — API'den bot gelmezse universal provider döndür
864
- return {
865
- bots: [{
866
- id: 'universal-provider',
867
- name: 'Universal Provider (NatureCo)',
868
- ai_provider: 'natureco',
869
- model: 'natureco-default',
870
- }]
871
- };
872
860
  }
873
861
 
874
862
  // Diğer provider'lar — universal provider döndür
@@ -889,9 +877,6 @@ async function getBots(apiKey) {
889
877
  // ── Streaming Support ────────────────────────────────────────────────────────────
890
878
 
891
879
  async function streamProviderCompletion(providerConfig, messages, tools) {
892
- if (providerConfig.url.includes('api.natureco.me')) {
893
- return sendMessageOpenAICompatible(providerConfig, messages, tools);
894
- }
895
880
  if (providerConfig.isAnthropic) {
896
881
  return streamAnthropicCompletion(providerConfig, messages);
897
882
  }
@@ -900,9 +885,7 @@ async function streamProviderCompletion(providerConfig, messages, tools) {
900
885
 
901
886
  async function streamOpenAICompletion(providerConfig, messages, tools) {
902
887
  const baseUrl = providerConfig.url.replace(/\/+$/, '');
903
- const endpoint = baseUrl.includes('api.natureco.me')
904
- ? 'https://api.natureco.me/api/v1/chat/completions'
905
- : `${baseUrl}/chat/completions`;
888
+ const endpoint = `${baseUrl}/chat/completions`;
906
889
 
907
890
  const requestBody = {
908
891
  model: providerConfig.model,
@@ -0,0 +1,297 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const net = require('net');
5
+ const chalk = require('chalk');
6
+ const inquirer = require('./inquirer-wrapper');
7
+ const { NatureCoError } = require('./errors');
8
+
9
+ const APPROVALS_FILE = path.join(os.homedir(), '.natureco', 'exec-approvals.json');
10
+ const APPROVALS_SOCKET_PATH = path.join(os.homedir(), '.natureco', 'exec-approvals.sock');
11
+ const DEFAULT_TIMEOUT_MS = 1800000; // 30 min
12
+
13
+ class ExecApprovalError extends NatureCoError {
14
+ constructor(message, options = {}) {
15
+ super(message, options);
16
+ this.command = options.command || null;
17
+ }
18
+ }
19
+
20
+ // -- Data types --
21
+
22
+ /**
23
+ * @typedef {'deny'|'allowlist'|'full'} ExecSecurity
24
+ * @typedef {'off'|'on-miss'|'always'} ExecAsk
25
+ * @typedef {'deny'|'allowlist'|'ask'|'auto'|'full'} ExecMode
26
+ * @typedef {{ id?: string, pattern: string, argPattern?: string, source?: string, lastUsedAt?: string, lastUsedCommand?: string }} AllowlistEntry
27
+ * @typedef {{ version: 1, defaults?: { security?: ExecSecurity, ask?: ExecAsk }, agents?: Record<string, { security?: ExecSecurity, ask?: ExecAsk, allowlist?: AllowlistEntry[] }> }} ApprovalsFile
28
+ * @typedef {'allow-once'|'allow-always'|'deny'} ApprovalDecision
29
+ */
30
+
31
+ function getApprovalsPath() {
32
+ return APPROVALS_FILE;
33
+ }
34
+
35
+ function loadApprovals() {
36
+ if (!fs.existsSync(APPROVALS_FILE)) {
37
+ return { version: 1, defaults: { security: 'full', ask: 'off' }, agents: {} };
38
+ }
39
+ try {
40
+ return JSON.parse(fs.readFileSync(APPROVALS_FILE, 'utf8'));
41
+ } catch {
42
+ return { version: 1, defaults: { security: 'full', ask: 'off' }, agents: {} };
43
+ }
44
+ }
45
+
46
+ function saveApprovals(data) {
47
+ const dir = path.dirname(APPROVALS_FILE);
48
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
49
+ fs.writeFileSync(APPROVALS_FILE, JSON.stringify(data, null, 2), 'utf8');
50
+ }
51
+
52
+ function resolveEffectivePolicy(agentId) {
53
+ const file = loadApprovals();
54
+ const defaults = file.defaults || { security: 'full', ask: 'off' };
55
+ if (!agentId || !file.agents?.[agentId]) {
56
+ return { security: defaults.security || 'full', ask: defaults.ask || 'off', allowlist: [] };
57
+ }
58
+ const agent = file.agents[agentId];
59
+ return {
60
+ security: agent.security || defaults.security || 'full',
61
+ ask: agent.ask || defaults.ask || 'off',
62
+ allowlist: agent.allowlist || [],
63
+ };
64
+ }
65
+
66
+ function resolveMode(security, ask) {
67
+ if (security === 'deny') return 'deny';
68
+ if (security === 'allowlist' && ask === 'always') return 'ask';
69
+ if (security === 'allowlist') return 'allowlist';
70
+ if (security === 'full') return 'full';
71
+ return 'full';
72
+ }
73
+
74
+ function matchAllowlist(entries, command) {
75
+ if (!entries || !command) return null;
76
+ for (const entry of entries) {
77
+ try {
78
+ const pattern = new RegExp(entry.pattern, 'i');
79
+ if (pattern.test(command)) {
80
+ if (entry.argPattern) {
81
+ const argRe = new RegExp(entry.argPattern, 'i');
82
+ const args = command.split(/\s+/).slice(1).join(' ');
83
+ if (!argRe.test(args)) continue;
84
+ }
85
+ return entry;
86
+ }
87
+ } catch {}
88
+ }
89
+ return null;
90
+ }
91
+
92
+ function requiresApproval({ command, agentId, security, ask }) {
93
+ const policy = resolveEffectivePolicy(agentId);
94
+ const mode = resolveMode(security || policy.security, ask || policy.ask);
95
+
96
+ if (mode === 'deny') return { required: true, reason: 'deny' };
97
+ if (mode === 'full') return { required: false, reason: 'full' };
98
+
99
+ // Check allowlist
100
+ const match = matchAllowlist(policy.allowlist, command);
101
+ if (match) return { required: false, reason: 'allowlist', entry: match };
102
+
103
+ if (mode === 'allowlist') return { required: true, reason: 'not-in-allowlist' };
104
+ if (mode === 'ask') return { required: true, reason: 'ask' };
105
+
106
+ return { required: true, reason: 'unknown' };
107
+ }
108
+
109
+ // Built-in safe commands that never need approval
110
+ const SAFE_COMMANDS = new Set([
111
+ 'ls', 'cat', 'head', 'tail', 'echo', 'pwd', 'date', 'whoami',
112
+ 'node -e', 'node -v', 'npm -v', 'git status', 'git diff', 'git log',
113
+ ]);
114
+
115
+ function isSafeCommand(command) {
116
+ if (SAFE_COMMANDS.has(command.trim())) return true;
117
+ for (const safe of SAFE_COMMANDS) {
118
+ if (command.trim().startsWith(safe)) return true;
119
+ }
120
+ return false;
121
+ }
122
+
123
+ // Known dangerous patterns that should always warn
124
+ const DANGEROUS_PATTERNS = [
125
+ /^rm\s+-rf\s+\/\s*$/,
126
+ /^mkfs/,
127
+ /^dd\s+if=.*\s+of=\/dev/,
128
+ /^:\(\)\s*\{.*:\(\)\s*;\s*\};/,
129
+ /^chmod\s+-R\s+777\s+\//,
130
+ /^chown\s+-R/,
131
+ /^>\/dev\/sda/,
132
+ /^\|.*sh$/,
133
+ /^curl.*\|.*sh$/,
134
+ /^wget.*\|.*sh$/,
135
+ ];
136
+
137
+ function isDangerousCommand(command) {
138
+ for (const pattern of DANGEROUS_PATTERNS) {
139
+ if (pattern.test(command.trim())) return true;
140
+ }
141
+ return false;
142
+ }
143
+
144
+ async function requestUserApproval(command, options = {}) {
145
+ const { timeoutMs = DEFAULT_TIMEOUT_MS, agentId } = options;
146
+
147
+ console.log('');
148
+ console.log(chalk.yellow(' ⚠️ Command requires approval'));
149
+ console.log(chalk.gray(' ─'.repeat(30)));
150
+ console.log(chalk.white(' ') + command);
151
+ console.log(chalk.gray(' ─'.repeat(30)));
152
+
153
+ const choices = [
154
+ { value: 'allow-once', name: 'Allow once' },
155
+ { value: 'allow-always', name: 'Always allow this command' },
156
+ { value: 'deny', name: 'Deny' },
157
+ ];
158
+
159
+ // Add edit option if command is dangerous
160
+ if (options.isDangerous) {
161
+ choices.push({ value: 'edit', name: 'Edit command' });
162
+ }
163
+
164
+ process.stdin.resume();
165
+ const { decision } = await inquirer.prompt([{
166
+ type: 'list',
167
+ name: 'decision',
168
+ message: 'What would you like to do?',
169
+ choices,
170
+ }]);
171
+
172
+ if (decision === 'edit') {
173
+ const { edited } = await inquirer.prompt([{
174
+ type: 'input',
175
+ name: 'edited',
176
+ message: 'Edit command:',
177
+ default: command,
178
+ }]);
179
+ return { decision: 'allow-once', command: edited };
180
+ }
181
+
182
+ if (decision === 'allow-always') {
183
+ addAllowlistEntry(agentId, command);
184
+ }
185
+
186
+ return { decision, command };
187
+ }
188
+
189
+ function addAllowlistEntry(agentId, command) {
190
+ const file = loadApprovals();
191
+ if (!file.agents) file.agents = {};
192
+ if (!file.agents[agentId]) file.agents[agentId] = { allowlist: [] };
193
+
194
+ // Escape special regex chars in the command for pattern matching
195
+ const escaped = command.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
196
+ const entry = {
197
+ id: `auto-${Date.now()}`,
198
+ pattern: `^${escaped}$`,
199
+ source: 'allow-always',
200
+ lastUsedAt: new Date().toISOString(),
201
+ lastUsedCommand: command,
202
+ };
203
+
204
+ file.agents[agentId].allowlist.push(entry);
205
+ saveApprovals(file);
206
+ }
207
+
208
+ function setSecurityPolicy(agentId, options = {}) {
209
+ const file = loadApprovals();
210
+ if (!file.agents) file.agents = {};
211
+ if (!file.agents[agentId]) file.agents[agentId] = {};
212
+
213
+ if (options.security) file.agents[agentId].security = options.security;
214
+ if (options.ask !== undefined) file.agents[agentId].ask = options.ask;
215
+
216
+ saveApprovals(file);
217
+ }
218
+
219
+ async function checkCommand(command, options = {}) {
220
+ const { agentId = 'default' } = options;
221
+
222
+ // Empty command
223
+ if (!command || !command.trim()) {
224
+ return { allowed: false, reason: 'empty' };
225
+ }
226
+
227
+ // Check if safe
228
+ if (isSafeCommand(command)) {
229
+ return { allowed: true, reason: 'safe-command' };
230
+ }
231
+
232
+ // Check if dangerous
233
+ const dangerous = isDangerousCommand(command);
234
+ const policy = resolveEffectivePolicy(agentId);
235
+ const mode = resolveMode(policy.security, policy.ask);
236
+
237
+ if (mode === 'deny') {
238
+ return { allowed: false, reason: 'denied-by-policy', policy };
239
+ }
240
+
241
+ // Check allowlist
242
+ const match = matchAllowlist(policy.allowlist, command);
243
+ if (match) {
244
+ return { allowed: true, reason: 'allowlist', entry: match };
245
+ }
246
+
247
+ if (mode === 'allowlist') {
248
+ return { allowed: false, reason: 'not-in-allowlist', policy };
249
+ }
250
+
251
+ if (mode === 'ask') {
252
+ const result = await requestUserApproval(command, { ...options, isDangerous: dangerous });
253
+ return {
254
+ allowed: result.decision === 'allow-once' || result.decision === 'allow-always',
255
+ reason: result.decision,
256
+ editedCommand: result.command,
257
+ };
258
+ }
259
+
260
+ // Full mode - always allow
261
+ return { allowed: true, reason: 'full-mode' };
262
+ }
263
+
264
+ function listAllowlist(agentId) {
265
+ const policy = resolveEffectivePolicy(agentId);
266
+ return policy.allowlist || [];
267
+ }
268
+
269
+ function removeAllowlistEntry(agentId, entryId) {
270
+ const file = loadApprovals();
271
+ if (!file.agents?.[agentId]?.allowlist) return false;
272
+ const before = file.agents[agentId].allowlist.length;
273
+ file.agents[agentId].allowlist = file.agents[agentId].allowlist.filter(e => e.id !== entryId);
274
+ saveApprovals(file);
275
+ return file.agents[agentId].allowlist.length < before;
276
+ }
277
+
278
+ module.exports = {
279
+ ExecApprovalError,
280
+ loadApprovals,
281
+ saveApprovals,
282
+ resolveEffectivePolicy,
283
+ resolveMode,
284
+ matchAllowlist,
285
+ requiresApproval,
286
+ isSafeCommand,
287
+ isDangerousCommand,
288
+ requestUserApproval,
289
+ addAllowlistEntry,
290
+ setSecurityPolicy,
291
+ checkCommand,
292
+ listAllowlist,
293
+ removeAllowlistEntry,
294
+ getApprovalsPath,
295
+ DANGEROUS_PATTERNS,
296
+ SAFE_COMMANDS,
297
+ };
@@ -1,66 +1,223 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
-
5
- const BACKGROUND_TASKS_FILE = path.join(os.homedir(), '.natureco', 'background-tasks.json');
6
-
7
- function ensureBackgroundDir() {
8
- const dir = path.dirname(BACKGROUND_TASKS_FILE);
9
- if (!fs.existsSync(dir)) {
10
- fs.mkdirSync(dir, { recursive: true });
11
- }
12
- }
13
-
14
- function loadBackgroundTasks() {
15
- ensureBackgroundDir();
16
- if (!fs.existsSync(BACKGROUND_TASKS_FILE)) {
17
- return [];
18
- }
19
- try {
20
- const data = fs.readFileSync(BACKGROUND_TASKS_FILE, 'utf-8');
21
- return JSON.parse(data);
22
- } catch {
23
- return [];
24
- }
25
- }
26
-
27
- function saveBackgroundTasks(tasks) {
28
- ensureBackgroundDir();
29
- fs.writeFileSync(BACKGROUND_TASKS_FILE, JSON.stringify(tasks, null, 2), 'utf-8');
30
- }
31
-
32
- function addBackgroundTask(task) {
33
- const tasks = loadBackgroundTasks();
34
- const id = Date.now().toString(36) + Math.random().toString(36).slice(2);
35
- const newTask = {
36
- id,
37
- ...task,
38
- status: 'pending',
39
- createdAt: new Date().toISOString(),
40
- };
41
- tasks.push(newTask);
42
- saveBackgroundTasks(tasks);
43
- return newTask;
44
- }
45
-
46
- function updateBackgroundTask(id, updates) {
47
- const tasks = loadBackgroundTasks();
48
- const index = tasks.findIndex(t => t.id === id);
49
- if (index === -1) return false;
50
- tasks[index] = { ...tasks[index], ...updates };
51
- saveBackgroundTasks(tasks);
52
- return true;
53
- }
54
-
55
- function getBackgroundTask(id) {
56
- const tasks = loadBackgroundTasks();
57
- return tasks.find(t => t.id === id);
58
- }
59
-
60
- module.exports = {
61
- loadBackgroundTasks,
62
- saveBackgroundTasks,
63
- addBackgroundTask,
64
- updateBackgroundTask,
65
- getBackgroundTask,
66
- };
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const TASKS_FILE = path.join(os.homedir(), '.natureco', 'tasks.json');
6
+ const TASK_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
7
+ const STALE_QUEUED_MS = 10 * 60 * 1000;
8
+ const STALE_RUNNING_MS = 30 * 60 * 1000;
9
+ const LOST_GRACE_MS = 5 * 60 * 1000;
10
+
11
+ const TASK_STATUSES = ['queued', 'running', 'succeeded', 'failed', 'timed_out', 'cancelled', 'lost'];
12
+ const TERMINAL_STATUSES = ['succeeded', 'failed', 'timed_out', 'cancelled', 'lost'];
13
+ const RUNTIME_TYPES = ['cli', 'cron', 'subagent', 'acp'];
14
+ const NOTIFY_POLICIES = ['done_only', 'state_changes', 'silent'];
15
+
16
+ function ensureDir() {
17
+ const dir = path.dirname(TASKS_FILE);
18
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
19
+ }
20
+
21
+ function loadTasks() {
22
+ ensureDir();
23
+ if (!fs.existsSync(TASKS_FILE)) return [];
24
+ try {
25
+ return JSON.parse(fs.readFileSync(TASKS_FILE, 'utf-8'));
26
+ } catch {
27
+ return [];
28
+ }
29
+ }
30
+
31
+ function saveTasks(tasks) {
32
+ ensureDir();
33
+ fs.writeFileSync(TASKS_FILE, JSON.stringify(tasks, null, 2), 'utf-8');
34
+ }
35
+
36
+ function generateId() {
37
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
38
+ }
39
+
40
+ function nowISO() {
41
+ return new Date().toISOString();
42
+ }
43
+
44
+ function createTask(data) {
45
+ const tasks = loadTasks();
46
+ const task = {
47
+ id: generateId(),
48
+ status: 'queued',
49
+ runtime: data.runtime || 'cli',
50
+ message: data.message || '',
51
+ botName: data.botName || 'default',
52
+ childSessionKey: data.childSessionKey || null,
53
+ requesterSessionKey: data.requesterSessionKey || null,
54
+ runId: data.runId || null,
55
+ notifyPolicy: data.notifyPolicy || 'done_only',
56
+ createdAt: nowISO(),
57
+ startedAt: null,
58
+ endedAt: null,
59
+ error: null,
60
+ result: null,
61
+ cleanupAfter: null,
62
+ metadata: data.metadata || {},
63
+ };
64
+ tasks.push(task);
65
+ saveTasks(tasks);
66
+ return task;
67
+ }
68
+
69
+ function updateTask(id, updates) {
70
+ const tasks = loadTasks();
71
+ const idx = tasks.findIndex(t => t.id === id);
72
+ if (idx === -1) return null;
73
+
74
+ const now = nowISO();
75
+ if (updates.status === 'running' && !tasks[idx].startedAt) {
76
+ updates.startedAt = now;
77
+ }
78
+ if (updates.status && TERMINAL_STATUSES.includes(updates.status) && !tasks[idx].endedAt) {
79
+ updates.endedAt = now;
80
+ updates.cleanupAfter = new Date(Date.now() + TASK_RETENTION_MS).toISOString();
81
+ }
82
+
83
+ tasks[idx] = { ...tasks[idx], ...updates };
84
+ saveTasks(tasks);
85
+ return tasks[idx];
86
+ }
87
+
88
+ function getTask(id) {
89
+ const tasks = loadTasks();
90
+ return tasks.find(t => t.id === id) || null;
91
+ }
92
+
93
+ function getTasksBySession(sessionKey) {
94
+ const tasks = loadTasks();
95
+ return tasks.filter(t => t.requesterSessionKey === sessionKey || t.childSessionKey === sessionKey);
96
+ }
97
+
98
+ function cancelTask(id) {
99
+ const task = getTask(id);
100
+ if (!task) return null;
101
+ if (TERMINAL_STATUSES.includes(task.status)) return task;
102
+ return updateTask(id, { status: 'cancelled' });
103
+ }
104
+
105
+ function listTasks(options = {}) {
106
+ let tasks = loadTasks();
107
+
108
+ if (options.runtime) {
109
+ tasks = tasks.filter(t => t.runtime === options.runtime);
110
+ }
111
+ if (options.status) {
112
+ tasks = tasks.filter(t => t.status === options.status);
113
+ }
114
+ if (options.limit) {
115
+ tasks = tasks.slice(0, options.limit);
116
+ }
117
+
118
+ return tasks.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
119
+ }
120
+
121
+ function auditTasks() {
122
+ const tasks = loadTasks();
123
+ const now = Date.now();
124
+ const findings = [];
125
+
126
+ tasks.forEach(t => {
127
+ const createdAt = new Date(t.createdAt).getTime();
128
+
129
+ if (t.status === 'queued' && (now - createdAt) > STALE_QUEUED_MS) {
130
+ findings.push({ id: t.id, severity: 'warn', code: 'stale_queued', message: `${Math.round((now - createdAt) / 1000)}s boyunca kuyrukta bekliyor` });
131
+ }
132
+
133
+ if (t.status === 'running' && t.startedAt) {
134
+ const startedAt = new Date(t.startedAt).getTime();
135
+ if ((now - startedAt) > STALE_RUNNING_MS) {
136
+ findings.push({ id: t.id, severity: 'error', code: 'stale_running', message: `${Math.round((now - startedAt) / 1000)}s boyunca çalışıyor` });
137
+ }
138
+ }
139
+
140
+ if (t.status === 'lost') {
141
+ const sev = t.cleanupAfter && new Date(t.cleanupAfter).getTime() > now ? 'warn' : 'error';
142
+ findings.push({ id: t.id, severity: sev, code: 'lost', message: `Runtime backing kayboldu` });
143
+ }
144
+
145
+ if (TERMINAL_STATUSES.includes(t.status) && !t.cleanupAfter) {
146
+ findings.push({ id: t.id, severity: 'warn', code: 'missing_cleanup', message: 'Terminal task has no cleanup timestamp' });
147
+ }
148
+
149
+ if (t.endedAt && t.startedAt && new Date(t.endedAt) < new Date(t.startedAt)) {
150
+ findings.push({ id: t.id, severity: 'warn', code: 'inconsistent_timestamps', message: 'Bitiş zamanı başlangıçtan önce' });
151
+ }
152
+ });
153
+
154
+ return findings;
155
+ }
156
+
157
+ function maintenanceTasks(dryRun = true) {
158
+ const tasks = loadTasks();
159
+ const now = Date.now();
160
+ const stats = { pruned: 0, reconciled: 0, cleaned: 0 };
161
+
162
+ const remaining = tasks.filter(t => {
163
+ if (t.cleanupAfter && new Date(t.cleanupAfter).getTime() < now) {
164
+ if (!dryRun) stats.pruned++;
165
+ return dryRun;
166
+ }
167
+
168
+ if (t.status === 'queued' && (now - new Date(t.createdAt).getTime()) > LOST_GRACE_MS) {
169
+ if (!dryRun) {
170
+ t.status = 'lost';
171
+ t.endedAt = nowISO();
172
+ t.cleanupAfter = new Date(now + TASK_RETENTION_MS).toISOString();
173
+ }
174
+ stats.reconciled++;
175
+ }
176
+
177
+ if (t.status === 'running' && t.startedAt && (now - new Date(t.startedAt).getTime()) > (STALE_RUNNING_MS + LOST_GRACE_MS)) {
178
+ if (!dryRun) {
179
+ t.status = 'lost';
180
+ t.endedAt = nowISO();
181
+ t.cleanupAfter = new Date(now + TASK_RETENTION_MS).toISOString();
182
+ }
183
+ stats.reconciled++;
184
+ }
185
+
186
+ if (TERMINAL_STATUSES.includes(t.status) && !t.cleanupAfter) {
187
+ if (!dryRun) {
188
+ t.cleanupAfter = new Date(now + TASK_RETENTION_MS).toISOString();
189
+ }
190
+ stats.cleaned++;
191
+ }
192
+
193
+ return true;
194
+ });
195
+
196
+ if (!dryRun) saveTasks(remaining);
197
+ return { ...stats, remaining: dryRun ? remaining.length : remaining.length };
198
+ }
199
+
200
+ function getTaskSummary() {
201
+ const tasks = loadTasks();
202
+ const active = tasks.filter(t => t.status === 'queued' || t.status === 'running').length;
203
+ const failures = tasks.filter(t => ['failed', 'timed_out', 'lost'].includes(t.status)).length;
204
+ const byRuntime = {};
205
+ RUNTIME_TYPES.forEach(r => { byRuntime[r] = tasks.filter(t => t.runtime === r).length; });
206
+ return { active, failures, byRuntime, total: tasks.length };
207
+ }
208
+
209
+ module.exports = {
210
+ createTask,
211
+ updateTask,
212
+ getTask,
213
+ getTasksBySession,
214
+ cancelTask,
215
+ listTasks,
216
+ auditTasks,
217
+ maintenanceTasks,
218
+ getTaskSummary,
219
+ TASK_STATUSES,
220
+ TERMINAL_STATUSES,
221
+ RUNTIME_TYPES,
222
+ NOTIFY_POLICIES,
223
+ };
@@ -0,0 +1,21 @@
1
+ let _makeWASocket, _useMultiFileAuthState, _DisconnectReason, _fetchLatestBaileysVersion, _Browsers;
2
+
3
+ function loadBaileys() {
4
+ if (!_makeWASocket) {
5
+ const baileys = require('@whiskeysockets/baileys');
6
+ _makeWASocket = baileys.default;
7
+ _useMultiFileAuthState = baileys.useMultiFileAuthState;
8
+ _DisconnectReason = baileys.DisconnectReason;
9
+ _fetchLatestBaileysVersion = baileys.fetchLatestBaileysVersion;
10
+ _Browsers = baileys.Browsers;
11
+ }
12
+ return {
13
+ makeWASocket: _makeWASocket,
14
+ useMultiFileAuthState: _useMultiFileAuthState,
15
+ DisconnectReason: _DisconnectReason,
16
+ fetchLatestBaileysVersion: _fetchLatestBaileysVersion,
17
+ Browsers: _Browsers,
18
+ };
19
+ }
20
+
21
+ module.exports = { loadBaileys };