xshat-lite 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -0
- package/bin/xshat.mjs +36 -0
- package/package.json +60 -0
- package/src/config/default.mjs +620 -0
- package/src/index.mjs +2865 -0
- package/src/modules/agent-executor.mjs +1184 -0
- package/src/modules/agent-helpers.mjs +132 -0
- package/src/modules/agent-loop.mjs +181 -0
- package/src/modules/agent.mjs +152 -0
- package/src/modules/api-chat.mjs +212 -0
- package/src/modules/browser.mjs +1384 -0
- package/src/modules/chat-helpers.mjs +275 -0
- package/src/modules/chat.mjs +589 -0
- package/src/modules/docs.mjs +88 -0
- package/src/modules/local-intent-router.mjs +280 -0
- package/src/modules/logger.mjs +111 -0
- package/src/modules/multi-agent.mjs +183 -0
- package/src/modules/navigation-recovery.mjs +35 -0
- package/src/modules/provider-debug.mjs +98 -0
- package/src/modules/provider-pool.mjs +66 -0
- package/src/modules/provider-runtime.mjs +26 -0
- package/src/modules/provider-strategies.mjs +102 -0
- package/src/modules/response-parser.mjs +511 -0
- package/src/modules/response-pipeline.mjs +211 -0
- package/src/modules/runtime-maintenance.mjs +195 -0
- package/src/modules/session-browser.mjs +180 -0
- package/src/modules/session.mjs +370 -0
- package/src/modules/settings-menu.mjs +367 -0
- package/src/modules/task-runner.mjs +511 -0
- package/src/modules/task-store.mjs +262 -0
- package/src/modules/ui.mjs +2204 -0
- package/src/utils/config.mjs +257 -0
- package/src/utils/key-input.mjs +86 -0
- package/src/utils/storage.mjs +55 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import config from '../utils/config.mjs';
|
|
4
|
+
import {
|
|
5
|
+
readJsonSafe,
|
|
6
|
+
resolveSiblingPath,
|
|
7
|
+
writeJsonAtomic
|
|
8
|
+
} from '../utils/storage.mjs';
|
|
9
|
+
|
|
10
|
+
function nowIso(clock = () => new Date()) {
|
|
11
|
+
return clock().toISOString();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function summarizeText(text, maxChars = 120) {
|
|
15
|
+
const value = String(text ?? '').replace(/\s+/g, ' ').trim();
|
|
16
|
+
if (value.length <= maxChars) {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
return `${value.slice(0, Math.max(0, maxChars - 1))}…`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class TaskStore {
|
|
23
|
+
constructor({
|
|
24
|
+
dir = config.tasks.dir,
|
|
25
|
+
maxHistory = config.tasks.maxHistory,
|
|
26
|
+
maxMessages = config.tasks.maxMessages,
|
|
27
|
+
fsModule = fs,
|
|
28
|
+
clock = () => new Date()
|
|
29
|
+
} = {}) {
|
|
30
|
+
this.dir = dir;
|
|
31
|
+
this.maxHistory = maxHistory;
|
|
32
|
+
this.maxMessages = maxMessages;
|
|
33
|
+
this.fs = fsModule;
|
|
34
|
+
this.clock = clock;
|
|
35
|
+
this.indexPath = resolveSiblingPath(this.dir, '_index.json');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async init() {
|
|
39
|
+
await this.fs.mkdir(this.dir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
taskPath(id) {
|
|
43
|
+
return resolve(this.dir, `${id}.json`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
toMeta(task) {
|
|
47
|
+
if (!task) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
id: task.id,
|
|
53
|
+
title: task.title,
|
|
54
|
+
goal: summarizeText(task.goal, 80),
|
|
55
|
+
provider: task.provider,
|
|
56
|
+
mode: task.mode,
|
|
57
|
+
intervalMs: task.intervalMs ?? null,
|
|
58
|
+
status: task.status,
|
|
59
|
+
createdAt: task.createdAt,
|
|
60
|
+
updatedAt: task.updatedAt,
|
|
61
|
+
lastRunAt: task.lastRunAt,
|
|
62
|
+
lastCompletedAt: task.lastCompletedAt || null,
|
|
63
|
+
nextRunAt: task.nextRunAt,
|
|
64
|
+
runCount: task.runCount || 0,
|
|
65
|
+
retryCount: task.retryCount || 0,
|
|
66
|
+
retryLimit: task.retryLimit || 0,
|
|
67
|
+
stopRequested: Boolean(task.stopRequested),
|
|
68
|
+
handoffUrl: task.handoffUrl || null,
|
|
69
|
+
lastError: task.lastError
|
|
70
|
+
? summarizeText(task.lastError, 100)
|
|
71
|
+
: null,
|
|
72
|
+
lastResult: task.lastResult
|
|
73
|
+
? summarizeText(task.lastResult, 100)
|
|
74
|
+
: null
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async loadIndex() {
|
|
79
|
+
const index = await readJsonSafe(this.indexPath, []);
|
|
80
|
+
return Array.isArray(index) ? index : [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async saveIndex(entries = []) {
|
|
84
|
+
const normalized = entries
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
|
|
87
|
+
await writeJsonAtomic(this.indexPath, normalized);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async upsertIndexEntry(task) {
|
|
91
|
+
const meta = this.toMeta(task);
|
|
92
|
+
if (!meta?.id) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const current = await this.loadIndex();
|
|
97
|
+
const filtered = current.filter((item) => item?.id !== meta.id);
|
|
98
|
+
filtered.push(meta);
|
|
99
|
+
await this.saveIndex(filtered);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async removeIndexEntry(id) {
|
|
103
|
+
const current = await this.loadIndex();
|
|
104
|
+
await this.saveIndex(current.filter((item) => item?.id !== id));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
create({
|
|
108
|
+
title,
|
|
109
|
+
goal,
|
|
110
|
+
schedule = null,
|
|
111
|
+
provider = null,
|
|
112
|
+
mode = 'once',
|
|
113
|
+
intervalMs = null,
|
|
114
|
+
retryLimit = 0,
|
|
115
|
+
retryDelayMs = null
|
|
116
|
+
}) {
|
|
117
|
+
const timestamp = Date.now();
|
|
118
|
+
const createdAt = nowIso(this.clock);
|
|
119
|
+
return {
|
|
120
|
+
id: `task_${timestamp}`,
|
|
121
|
+
title: String(title || goal || '未命名任务').trim().slice(0, 80),
|
|
122
|
+
goal: String(goal || '').trim(),
|
|
123
|
+
provider,
|
|
124
|
+
mode,
|
|
125
|
+
schedule,
|
|
126
|
+
intervalMs:
|
|
127
|
+
intervalMs === null || intervalMs === undefined
|
|
128
|
+
? null
|
|
129
|
+
: Math.max(0, Number(intervalMs) || 0),
|
|
130
|
+
retryLimit: Math.max(0, Number(retryLimit) || 0),
|
|
131
|
+
retryDelayMs:
|
|
132
|
+
retryDelayMs === null || retryDelayMs === undefined
|
|
133
|
+
? null
|
|
134
|
+
: Math.max(0, Number(retryDelayMs) || 0),
|
|
135
|
+
status: 'idle',
|
|
136
|
+
createdAt,
|
|
137
|
+
updatedAt: createdAt,
|
|
138
|
+
lastRunAt: null,
|
|
139
|
+
lastCompletedAt: null,
|
|
140
|
+
nextRunAt: null,
|
|
141
|
+
lastError: null,
|
|
142
|
+
lastResult: null,
|
|
143
|
+
handoffUrl: null,
|
|
144
|
+
stopRequested: false,
|
|
145
|
+
runCount: 0,
|
|
146
|
+
retryCount: 0,
|
|
147
|
+
history: [],
|
|
148
|
+
messages: []
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async save(task) {
|
|
153
|
+
const nextTask = {
|
|
154
|
+
...task,
|
|
155
|
+
updatedAt: nowIso(this.clock),
|
|
156
|
+
history: Array.isArray(task.history)
|
|
157
|
+
? task.history.slice(-this.maxHistory)
|
|
158
|
+
: [],
|
|
159
|
+
messages: Array.isArray(task.messages)
|
|
160
|
+
? task.messages.slice(-this.maxMessages)
|
|
161
|
+
: []
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
await writeJsonAtomic(this.taskPath(nextTask.id), nextTask);
|
|
165
|
+
await this.upsertIndexEntry(nextTask);
|
|
166
|
+
return nextTask;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async load(id) {
|
|
170
|
+
try {
|
|
171
|
+
const raw = await this.fs.readFile(this.taskPath(id), 'utf-8');
|
|
172
|
+
return JSON.parse(raw);
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async list() {
|
|
179
|
+
try {
|
|
180
|
+
const indexed = await this.loadIndex();
|
|
181
|
+
if (indexed.length > 0) {
|
|
182
|
+
return indexed;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const files = await this.fs.readdir(this.dir);
|
|
186
|
+
const tasks = await Promise.all(
|
|
187
|
+
files
|
|
188
|
+
.filter((file) => file.endsWith('.json') && file !== '_index.json')
|
|
189
|
+
.map(async (file) => {
|
|
190
|
+
try {
|
|
191
|
+
const raw = await this.fs.readFile(resolve(this.dir, file), 'utf-8');
|
|
192
|
+
const task = JSON.parse(raw);
|
|
193
|
+
return this.toMeta(task);
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const normalized = tasks
|
|
201
|
+
.filter(Boolean)
|
|
202
|
+
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
|
|
203
|
+
await this.saveIndex(normalized);
|
|
204
|
+
return normalized;
|
|
205
|
+
} catch {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async update(id, updater) {
|
|
211
|
+
const task = await this.load(id);
|
|
212
|
+
if (!task) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const nextTask =
|
|
217
|
+
typeof updater === 'function'
|
|
218
|
+
? updater(task) || task
|
|
219
|
+
: { ...task, ...(updater || {}) };
|
|
220
|
+
|
|
221
|
+
return this.save(nextTask);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async appendHistory(id, event) {
|
|
225
|
+
return this.update(id, (task) => ({
|
|
226
|
+
...task,
|
|
227
|
+
history: [
|
|
228
|
+
...(Array.isArray(task.history) ? task.history : []),
|
|
229
|
+
{
|
|
230
|
+
time: nowIso(this.clock),
|
|
231
|
+
...event
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async appendMessage(id, role, content) {
|
|
238
|
+
return this.update(id, (task) => ({
|
|
239
|
+
...task,
|
|
240
|
+
messages: [
|
|
241
|
+
...(Array.isArray(task.messages) ? task.messages : []),
|
|
242
|
+
{
|
|
243
|
+
role,
|
|
244
|
+
content: String(content ?? ''),
|
|
245
|
+
time: nowIso(this.clock)
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async delete(id) {
|
|
252
|
+
try {
|
|
253
|
+
await this.fs.rm(this.taskPath(id), { force: true });
|
|
254
|
+
await this.removeIndexEntry(id);
|
|
255
|
+
return true;
|
|
256
|
+
} catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export default new TaskStore();
|