tinywebui 0.1.3 → 0.1.5
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 +20 -13
- package/bin/tinywebui.js +69 -63
- package/package.json +25 -8
- package/public/app.js +40 -14
- package/public/chat/composer.js +1 -1
- package/public/chat/sidebar.js +8 -5
- package/public/index.html +88 -49
- package/public/panels/admin.js +40 -1
- package/public/panels/model-editor.js +336 -0
- package/public/panels/panels.js +37 -12
- package/public/panels/settings-home.js +125 -0
- package/public/panels/settings.js +124 -36
- package/public/shared/attachment_note.js +7 -1
- package/public/shared/events.js +1 -0
- package/public/shared/features.js +1 -0
- package/public/shared/origins.js +1 -0
- package/public/shared/usage.js +16 -1
- package/public/styles.css +83 -20
- package/src/access/auth.js +155 -155
- package/src/access/auth_gate.js +39 -38
- package/src/access/email.js +17 -0
- package/src/access/policy.js +123 -138
- package/src/audit.js +1 -1
- package/src/automations/automation.js +135 -123
- package/src/automations/scheduler.js +138 -127
- package/src/chat/compact.js +149 -156
- package/src/chat/llm.js +380 -382
- package/src/chat/runs.js +162 -151
- package/src/chat/tool_executor.js +229 -218
- package/src/cli.js +535 -433
- package/src/config/approval.js +35 -29
- package/src/config/config.js +546 -451
- package/src/config/data_dir.js +24 -0
- package/src/config/models.js +191 -115
- package/src/config/refs.js +32 -0
- package/src/config/secrets.js +153 -0
- package/src/files/documents.js +57 -68
- package/src/files/images.js +30 -31
- package/src/harness/agent.js +60 -60
- package/src/harness/attribution.js +66 -58
- package/src/harness/checkpoint.js +67 -62
- package/src/harness/history.js +28 -23
- package/src/harness/prompt.js +40 -57
- package/src/harness/provider.js +284 -283
- package/src/harness/types.js +1 -0
- package/src/http.js +98 -66
- package/src/log.js +11 -12
- package/src/mcp.js +398 -374
- package/src/retrieval/embedding.js +228 -240
- package/src/retrieval/retrieval.js +182 -173
- package/src/routes/admin.js +178 -138
- package/src/routes/auth.js +184 -100
- package/src/routes/automations.js +99 -82
- package/src/routes/chats.js +240 -218
- package/src/routes/documents.js +130 -120
- package/src/routes/library.js +43 -46
- package/src/routes/mcp.js +49 -49
- package/src/routes/settings.js +127 -122
- package/src/routes/types.js +1 -0
- package/src/server.js +428 -392
- package/src/store/automations.js +106 -106
- package/src/store/chats.js +217 -242
- package/src/store/documents.js +181 -185
- package/src/store/embeddings.js +52 -54
- package/src/store/index.js +95 -49
- package/src/store/messages.js +55 -80
- package/src/store/schema.js +128 -107
- package/src/store/scope.js +18 -17
- package/src/store/search.js +25 -27
- package/src/store/turns.js +53 -53
- package/src/store/usage.js +343 -269
- package/src/store/users.js +162 -87
- package/src/store/wire.js +50 -41
- package/src/tools/ask_tool.js +68 -68
- package/src/tools/chat_search_tool.js +38 -44
- package/src/tools/context_tool.js +131 -138
- package/src/tools/document_tool.js +73 -78
- package/src/tools/mcp_tool.js +274 -232
- package/src/tools/task_tool.js +53 -50
- package/src/tools/types.js +1 -0
- package/public/panels/connectors.js +0 -184
- package/src/set-password.js +0 -43
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { nextSchedule, runMessage } from './automation.js';
|
|
2
2
|
import { ORIGIN } from '../../public/shared/origins.js';
|
|
3
|
-
|
|
4
3
|
/**
|
|
5
4
|
* Runs automations: on their cron schedule, or on demand. A run needs its
|
|
6
5
|
* chat idle; a manual trigger for a busy chat waits in a per-chat queue and
|
|
@@ -11,137 +10,149 @@ import { ORIGIN } from '../../public/shared/origins.js';
|
|
|
11
10
|
* `app` supplies the store, runs, and modelFor/toolsFor/ownerFeatures.
|
|
12
11
|
*/
|
|
13
12
|
export function createScheduler(app) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
const { store, runs } = app;
|
|
14
|
+
const pendingManual = new Map();
|
|
15
|
+
let timer = null;
|
|
16
|
+
let stopped = false;
|
|
17
|
+
function launch(automation, runId, trigger = 'schedule') {
|
|
18
|
+
// Nobody is at the keyboard to be turned away, so a disabled or pending
|
|
19
|
+
// owner is checked here: their schedules stop the moment their access does.
|
|
20
|
+
if (automation.userId && (store.users.get(automation.userId)?.status !== 'approved' || !app.ownerFeatures(automation.userId).has('automations'))) {
|
|
21
|
+
store.automations.updateRun(runId, { status: 'skipped', finishedAt: Date.now(), error: 'Owner account is not active.' });
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const chat = store.chats.get(automation.chatId, automation.userId);
|
|
25
|
+
if (!chat) {
|
|
26
|
+
store.automations.updateRun(runId, { status: 'failed', finishedAt: Date.now(), error: 'Target chat no longer exists.' });
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (runs.isRunning(chat.id))
|
|
30
|
+
return false;
|
|
31
|
+
// A snapshot, not a reference: the card still reads right after the
|
|
32
|
+
// automation is renamed or deleted.
|
|
33
|
+
const origin = {
|
|
34
|
+
type: ORIGIN.AUTOMATION, automationId: automation.id, runId, trigger,
|
|
35
|
+
name: automation.name, cron: automation.cron, timezone: automation.timezone, prompt: automation.prompt
|
|
36
|
+
};
|
|
37
|
+
const firstSeq = store.messages.add(chat.id, { role: 'user', content: runMessage(automation), origin });
|
|
38
|
+
store.automations.updateRun(runId, { status: 'running', startedAt: Date.now() });
|
|
39
|
+
const role = automation.userId ? store.users.get(automation.userId)?.role : null;
|
|
40
|
+
runs.start({
|
|
41
|
+
chat,
|
|
42
|
+
model: app.modelFor(automation.userId, role),
|
|
43
|
+
tools: app.toolsFor(app.ownerFeatures(automation.userId)),
|
|
44
|
+
historyFromSeq: firstSeq,
|
|
45
|
+
unattended: true,
|
|
46
|
+
onFinish: ({ ok, error, result }) => {
|
|
47
|
+
store.automations.updateRun(runId, {
|
|
48
|
+
status: ok ? 'completed' : 'failed', finishedAt: Date.now(),
|
|
49
|
+
result: String(result || '').slice(0, 200000), error: error ? String(error).slice(0, 1000) : null
|
|
50
|
+
});
|
|
51
|
+
arm();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return true;
|
|
25
55
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
56
|
+
async function trigger(automation, userId) {
|
|
57
|
+
const owned = store.automations.get(automation.id, userId);
|
|
58
|
+
if (!owned)
|
|
59
|
+
throw new Error('no such automation');
|
|
60
|
+
const runId = store.automations.addRun(owned.id, Date.now(), 'queued', 'manual');
|
|
61
|
+
if (runs.isRunning(owned.chatId)) {
|
|
62
|
+
const queue = pendingManual.get(owned.chatId) || [];
|
|
63
|
+
queue.push({ automationId: owned.id, userId, runId });
|
|
64
|
+
pendingManual.set(owned.chatId, queue);
|
|
65
|
+
return { runId, status: 'queued', chatId: owned.chatId };
|
|
66
|
+
}
|
|
67
|
+
launch(owned, runId, 'manual');
|
|
68
|
+
return { runId, status: 'running', chatId: owned.chatId };
|
|
30
69
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function trigger(automation, userId) {
|
|
59
|
-
const owned = store.automations.get(automation.id, userId);
|
|
60
|
-
if (!owned) throw new Error('no such automation');
|
|
61
|
-
const runId = store.automations.addRun(owned.id, Date.now(), 'queued', 'manual');
|
|
62
|
-
if (runs.isRunning(owned.chatId)) {
|
|
63
|
-
const queue = pendingManual.get(owned.chatId) || [];
|
|
64
|
-
queue.push({ automationId: owned.id, userId, runId });
|
|
65
|
-
pendingManual.set(owned.chatId, queue);
|
|
66
|
-
return { runId, status: 'queued', chatId: owned.chatId };
|
|
70
|
+
function drain(chatId) {
|
|
71
|
+
if (runs.isRunning(chatId))
|
|
72
|
+
return;
|
|
73
|
+
const queue = pendingManual.get(chatId);
|
|
74
|
+
if (!queue?.length)
|
|
75
|
+
return;
|
|
76
|
+
const next = queue.shift();
|
|
77
|
+
if (!queue.length)
|
|
78
|
+
pendingManual.delete(chatId);
|
|
79
|
+
const automation = store.automations.get(next.automationId, next.userId);
|
|
80
|
+
if (!automation) {
|
|
81
|
+
store.automations.updateRun(next.runId, { status: 'failed', finishedAt: Date.now(), error: 'Automation no longer exists.' });
|
|
82
|
+
queueMicrotask(() => drain(chatId));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!launch(automation, next.runId, 'manual')) {
|
|
86
|
+
const remaining = pendingManual.get(chatId) || [];
|
|
87
|
+
remaining.unshift(next);
|
|
88
|
+
pendingManual.set(chatId, remaining);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (queue.length)
|
|
92
|
+
pendingManual.set(chatId, queue);
|
|
67
93
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
function refresh() {
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
for (const automation of store.automations.listAll()) {
|
|
97
|
+
if (!automation.enabled)
|
|
98
|
+
continue;
|
|
99
|
+
// Recompute stale timestamps on startup or after changes without replaying
|
|
100
|
+
// occurrences missed while the process was down.
|
|
101
|
+
if (!automation.nextRunAt || automation.nextRunAt <= now) {
|
|
102
|
+
try {
|
|
103
|
+
store.automations.update(automation.id, { nextRunAt: nextSchedule(automation.cron, automation.timezone, now) }, automation.userId);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
store.automations.update(automation.id, { enabled: false, lastStatus: `invalid schedule: ${err.message}` }, automation.userId);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
83
110
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
111
|
+
function processDue() {
|
|
112
|
+
if (stopped)
|
|
113
|
+
return;
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
for (const automation of store.automations.due(now)) {
|
|
116
|
+
const scheduledAt = automation.nextRunAt; // due() only returns automations with one
|
|
117
|
+
const runId = store.automations.addRun(automation.id, scheduledAt, 'queued');
|
|
118
|
+
let nextRunAt;
|
|
119
|
+
try {
|
|
120
|
+
nextRunAt = nextSchedule(automation.cron, automation.timezone, scheduledAt);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
store.automations.update(automation.id, { enabled: false, lastStatus: `invalid schedule: ${err.message}`, nextRunAt: null }, automation.userId);
|
|
124
|
+
store.automations.updateRun(runId, { status: 'failed', finishedAt: now, error: err.message });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
store.automations.update(automation.id, { nextRunAt }, automation.userId);
|
|
128
|
+
if (runs.isRunning(automation.chatId)) {
|
|
129
|
+
store.automations.updateRun(runId, { status: 'skipped', finishedAt: now, error: 'Target chat was already running.' });
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
launch(automation, runId);
|
|
133
|
+
}
|
|
134
|
+
arm();
|
|
89
135
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
136
|
+
/** (Re)sets the timer for the next due automation. Call after any change. */
|
|
137
|
+
function arm() {
|
|
138
|
+
if (stopped)
|
|
139
|
+
return;
|
|
140
|
+
if (timer)
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
refresh();
|
|
143
|
+
const next = store.automations.nextDue();
|
|
144
|
+
if (next == null) {
|
|
145
|
+
timer = null;
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
timer = setTimeout(processDue, Math.max(25, Math.min(2_147_000_000, next - Date.now())));
|
|
149
|
+
timer.unref?.();
|
|
103
150
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const now = Date.now();
|
|
109
|
-
for (const automation of store.automations.due(now)) {
|
|
110
|
-
const scheduledAt = automation.nextRunAt;
|
|
111
|
-
const runId = store.automations.addRun(automation.id, scheduledAt, 'queued');
|
|
112
|
-
let nextRunAt;
|
|
113
|
-
try { nextRunAt = nextSchedule(automation.cron, automation.timezone, scheduledAt); }
|
|
114
|
-
catch (err) {
|
|
115
|
-
store.automations.update(automation.id, { enabled: false, lastStatus: `invalid schedule: ${err.message}`, nextRunAt: null }, automation.userId);
|
|
116
|
-
store.automations.updateRun(runId, { status: 'failed', finishedAt: now, error: err.message });
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
store.automations.update(automation.id, { nextRunAt }, automation.userId);
|
|
120
|
-
if (runs.isRunning(automation.chatId)) {
|
|
121
|
-
store.automations.updateRun(runId, { status: 'skipped', finishedAt: now, error: 'Target chat was already running.' });
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
launch(automation, runId);
|
|
151
|
+
function stop() {
|
|
152
|
+
stopped = true;
|
|
153
|
+
if (timer)
|
|
154
|
+
clearTimeout(timer);
|
|
125
155
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
/** (Re)sets the timer for the next due automation. Call after any change. */
|
|
130
|
-
function arm() {
|
|
131
|
-
if (stopped) return;
|
|
132
|
-
if (timer) clearTimeout(timer);
|
|
133
|
-
refresh();
|
|
134
|
-
const next = store.automations.nextDue();
|
|
135
|
-
if (next == null) { timer = null; return; }
|
|
136
|
-
timer = setTimeout(processDue, Math.max(25, Math.min(2_147_000_000, next - Date.now())));
|
|
137
|
-
timer.unref?.();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function stop() {
|
|
141
|
-
stopped = true;
|
|
142
|
-
if (timer) clearTimeout(timer);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
store.automations.recoverRuns();
|
|
146
|
-
return { arm, trigger, drain, stop };
|
|
156
|
+
store.automations.recoverRuns();
|
|
157
|
+
return { arm, trigger, drain, stop };
|
|
147
158
|
}
|