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