heyio 1.2.4 → 1.4.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/dist/api/server.js +289 -12
- package/dist/config.js +6 -0
- package/dist/copilot/agents.js +100 -5
- package/dist/copilot/ceremonies.js +12 -2
- package/dist/copilot/io-scheduler.js +9 -1
- package/dist/copilot/orchestrator.js +4 -0
- package/dist/copilot/scheduler.js +7 -2
- package/dist/copilot/skills.js +138 -6
- package/dist/copilot/squad-tools.js +102 -0
- package/dist/copilot/system-message.js +2 -1
- package/dist/copilot/token-tracker.js +89 -0
- package/dist/copilot/tools.js +27 -5
- package/dist/copilot/trigger-schedule.js +31 -0
- package/dist/paths.js +1 -0
- package/dist/store/agent-events.js +19 -0
- package/dist/store/audit-log.js +71 -0
- package/dist/store/conversations.js +150 -0
- package/dist/store/db.js +111 -0
- package/dist/store/schedules.js +9 -1
- package/dist/store/squad-colors.js +23 -0
- package/dist/store/squads.js +6 -1
- package/dist/store/tasks.js +43 -0
- package/dist/store/token-usage.js +94 -0
- package/dist/wiki/backlinks.js +51 -0
- package/dist/wiki/fs.js +63 -1
- package/dist/wiki/search.js +13 -2
- package/package.json +1 -1
- package/web-dist/assets/AuditLogView-DqxVzjd_.js +6 -0
- package/web-dist/assets/ChatView-BBopM_A3.js +1 -0
- package/web-dist/assets/FeedView-Bo4p1stx.js +6 -0
- package/web-dist/assets/HistoryView-ChTuQvXr.js +1 -0
- package/web-dist/assets/LoginView-AnOP3Mau.js +1 -0
- package/web-dist/assets/McpView-DPcihjuB.js +1 -0
- package/web-dist/assets/SchedulesView-B2o3vMm-.js +6 -0
- package/web-dist/assets/SettingsView-rtMUmH43.js +1 -0
- package/web-dist/assets/SkillsView-D_NHLk7C.js +15 -0
- package/web-dist/assets/SquadDetailView-BKXLWvwn.js +26 -0
- package/web-dist/assets/SquadHealthView-CVJiAgVW.js +11 -0
- package/web-dist/assets/SquadsView-fammrB7r.js +6 -0
- package/web-dist/assets/UsageView-Cy5Mbprb.js +16 -0
- package/web-dist/assets/WikiView-B5TOMnOg.js +36 -0
- package/web-dist/assets/arrow-left-CGMB1w_A.js +6 -0
- package/web-dist/assets/git-branch-C_Hu39uh.js +6 -0
- package/web-dist/assets/index-CQ_szaoT.css +1 -0
- package/web-dist/assets/index-CiZnRvN4.js +253 -0
- package/web-dist/assets/{plus-Cvp1w2CO.js → plus-DIBAaEMT.js} +1 -1
- package/web-dist/assets/{x-O3fBd1Cr.js → save-Chqlu7QA.js} +2 -7
- package/web-dist/assets/search-Cl8HcIsG.js +6 -0
- package/web-dist/assets/squad-colors-B8B_Y-lz.js +1 -0
- package/web-dist/assets/{trash-2-Cr3vrmL5.js → trash-2-CQSzbVIr.js} +1 -1
- package/web-dist/assets/triangle-alert-C1OjMvP5.js +6 -0
- package/web-dist/assets/x-DThJHYFm.js +6 -0
- package/web-dist/favicon.svg +9 -3
- package/web-dist/index.html +2 -2
- package/web-dist/logo.svg +10 -0
- package/web-dist/assets/ChatView-mZaaw3pd.js +0 -11
- package/web-dist/assets/FeedView-BHacQwXQ.js +0 -6
- package/web-dist/assets/LoginView-B6aSD9II.js +0 -1
- package/web-dist/assets/MarkdownContent.vue_vue_type_script_setup_true_lang-CEo_ckIb.js +0 -56
- package/web-dist/assets/McpView-BAVRUHIE.js +0 -1
- package/web-dist/assets/SchedulesView-dOd1SQiP.js +0 -1
- package/web-dist/assets/SettingsView-CCDeEsVg.js +0 -1
- package/web-dist/assets/SkillsView-gCfQ35FQ.js +0 -1
- package/web-dist/assets/SquadDetailView-CQhFfZTc.js +0 -21
- package/web-dist/assets/SquadsView-CZFxtOao.js +0 -6
- package/web-dist/assets/WikiView-B0cuUFfm.js +0 -26
- package/web-dist/assets/api-DdW5uOZf.js +0 -1
- package/web-dist/assets/index-BQdXxKfc.js +0 -138
- package/web-dist/assets/index-BbSJ0cfF.css +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getDb } from "./db.js";
|
|
3
|
+
function toMessage(row) {
|
|
4
|
+
return {
|
|
5
|
+
id: row.id,
|
|
6
|
+
conversationId: row.conversation_id,
|
|
7
|
+
role: row.role,
|
|
8
|
+
content: row.content,
|
|
9
|
+
source: row.source,
|
|
10
|
+
createdAt: row.created_at,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function toSummary(row) {
|
|
14
|
+
return {
|
|
15
|
+
id: row.id,
|
|
16
|
+
preview: row.preview,
|
|
17
|
+
messageCount: row.message_count,
|
|
18
|
+
startedAt: row.started_at,
|
|
19
|
+
updatedAt: row.updated_at,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function saveMessage(conversationId, role, content, source) {
|
|
23
|
+
const db = getDb();
|
|
24
|
+
const id = randomUUID();
|
|
25
|
+
db.prepare("INSERT INTO conversation_messages (id, conversation_id, role, content, source) VALUES (?, ?, ?, ?, ?)").run(id, conversationId, role, content, source);
|
|
26
|
+
const row = db
|
|
27
|
+
.prepare("SELECT * FROM conversation_messages WHERE id = ?")
|
|
28
|
+
.get(id);
|
|
29
|
+
return toMessage(row);
|
|
30
|
+
}
|
|
31
|
+
export function getConversation(conversationId) {
|
|
32
|
+
const db = getDb();
|
|
33
|
+
const rows = db
|
|
34
|
+
.prepare("SELECT * FROM conversation_messages WHERE conversation_id = ? ORDER BY created_at ASC")
|
|
35
|
+
.all(conversationId);
|
|
36
|
+
return rows.map(toMessage);
|
|
37
|
+
}
|
|
38
|
+
export function listConversations(opts = {}) {
|
|
39
|
+
const db = getDb();
|
|
40
|
+
const limit = opts.limit ?? 50;
|
|
41
|
+
const offset = opts.offset ?? 0;
|
|
42
|
+
let where = "WHERE 1=1";
|
|
43
|
+
const params = [];
|
|
44
|
+
if (opts.from) {
|
|
45
|
+
where += " AND started_at >= ?";
|
|
46
|
+
params.push(opts.from);
|
|
47
|
+
}
|
|
48
|
+
if (opts.to) {
|
|
49
|
+
where += " AND updated_at <= ?";
|
|
50
|
+
params.push(opts.to);
|
|
51
|
+
}
|
|
52
|
+
const countRow = db
|
|
53
|
+
.prepare(`SELECT COUNT(*) as cnt FROM (
|
|
54
|
+
SELECT conversation_id as id,
|
|
55
|
+
MIN(created_at) as started_at,
|
|
56
|
+
MAX(created_at) as updated_at
|
|
57
|
+
FROM conversation_messages
|
|
58
|
+
GROUP BY conversation_id
|
|
59
|
+
) ${where}`)
|
|
60
|
+
.get(...params);
|
|
61
|
+
const total = countRow.cnt;
|
|
62
|
+
const rows = db
|
|
63
|
+
.prepare(`SELECT
|
|
64
|
+
sub.id,
|
|
65
|
+
sub.started_at,
|
|
66
|
+
sub.updated_at,
|
|
67
|
+
sub.message_count,
|
|
68
|
+
first_msg.content as preview
|
|
69
|
+
FROM (
|
|
70
|
+
SELECT conversation_id as id,
|
|
71
|
+
MIN(created_at) as started_at,
|
|
72
|
+
MAX(created_at) as updated_at,
|
|
73
|
+
COUNT(*) as message_count
|
|
74
|
+
FROM conversation_messages
|
|
75
|
+
GROUP BY conversation_id
|
|
76
|
+
) sub
|
|
77
|
+
JOIN conversation_messages first_msg
|
|
78
|
+
ON first_msg.conversation_id = sub.id
|
|
79
|
+
AND first_msg.role = 'user'
|
|
80
|
+
AND first_msg.created_at = (
|
|
81
|
+
SELECT MIN(created_at) FROM conversation_messages
|
|
82
|
+
WHERE conversation_id = sub.id AND role = 'user'
|
|
83
|
+
)
|
|
84
|
+
${where}
|
|
85
|
+
ORDER BY sub.updated_at DESC
|
|
86
|
+
LIMIT ? OFFSET ?`)
|
|
87
|
+
.all(...params, limit, offset);
|
|
88
|
+
return { items: rows.map(toSummary), total };
|
|
89
|
+
}
|
|
90
|
+
export function searchConversations(query, opts = {}) {
|
|
91
|
+
const db = getDb();
|
|
92
|
+
const limit = opts.limit ?? 50;
|
|
93
|
+
const offset = opts.offset ?? 0;
|
|
94
|
+
let dateWhere = "";
|
|
95
|
+
const dateParams = [];
|
|
96
|
+
if (opts.from) {
|
|
97
|
+
dateWhere += " AND cm.created_at >= ?";
|
|
98
|
+
dateParams.push(opts.from);
|
|
99
|
+
}
|
|
100
|
+
if (opts.to) {
|
|
101
|
+
dateWhere += " AND cm.created_at <= ?";
|
|
102
|
+
dateParams.push(opts.to);
|
|
103
|
+
}
|
|
104
|
+
// Find distinct conversation IDs matching FTS query
|
|
105
|
+
const matchingIds = db
|
|
106
|
+
.prepare(`SELECT DISTINCT cm.conversation_id
|
|
107
|
+
FROM conversation_messages cm
|
|
108
|
+
JOIN conversation_messages_fts fts ON fts.rowid = cm.rowid
|
|
109
|
+
WHERE conversation_messages_fts MATCH ?${dateWhere}
|
|
110
|
+
LIMIT 500`)
|
|
111
|
+
.all(query, ...dateParams);
|
|
112
|
+
if (matchingIds.length === 0) {
|
|
113
|
+
return { items: [], total: 0 };
|
|
114
|
+
}
|
|
115
|
+
const idList = matchingIds.map((r) => r.conversation_id);
|
|
116
|
+
const placeholders = idList.map(() => "?").join(",");
|
|
117
|
+
const total = idList.length;
|
|
118
|
+
const rows = db
|
|
119
|
+
.prepare(`SELECT
|
|
120
|
+
sub.id,
|
|
121
|
+
sub.started_at,
|
|
122
|
+
sub.updated_at,
|
|
123
|
+
sub.message_count,
|
|
124
|
+
first_msg.content as preview
|
|
125
|
+
FROM (
|
|
126
|
+
SELECT conversation_id as id,
|
|
127
|
+
MIN(created_at) as started_at,
|
|
128
|
+
MAX(created_at) as updated_at,
|
|
129
|
+
COUNT(*) as message_count
|
|
130
|
+
FROM conversation_messages
|
|
131
|
+
WHERE conversation_id IN (${placeholders})
|
|
132
|
+
GROUP BY conversation_id
|
|
133
|
+
) sub
|
|
134
|
+
JOIN conversation_messages first_msg
|
|
135
|
+
ON first_msg.conversation_id = sub.id
|
|
136
|
+
AND first_msg.role = 'user'
|
|
137
|
+
AND first_msg.created_at = (
|
|
138
|
+
SELECT MIN(created_at) FROM conversation_messages
|
|
139
|
+
WHERE conversation_id = sub.id AND role = 'user'
|
|
140
|
+
)
|
|
141
|
+
ORDER BY sub.updated_at DESC
|
|
142
|
+
LIMIT ? OFFSET ?`)
|
|
143
|
+
.all(...idList, limit, offset);
|
|
144
|
+
return { items: rows.map(toSummary), total };
|
|
145
|
+
}
|
|
146
|
+
export function deleteConversation(conversationId) {
|
|
147
|
+
const db = getDb();
|
|
148
|
+
db.prepare("DELETE FROM conversation_messages WHERE conversation_id = ?").run(conversationId);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=conversations.js.map
|
package/dist/store/db.js
CHANGED
|
@@ -2,6 +2,7 @@ import Database from "better-sqlite3";
|
|
|
2
2
|
import { existsSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { PATHS } from "../paths.js";
|
|
5
|
+
import { pickSquadColor } from "./squad-colors.js";
|
|
5
6
|
let db;
|
|
6
7
|
export function getDb() {
|
|
7
8
|
if (db)
|
|
@@ -128,6 +129,116 @@ function runMigrations(db) {
|
|
|
128
129
|
}
|
|
129
130
|
setSchemaVersion(db, 2);
|
|
130
131
|
}
|
|
132
|
+
if (version < 3) {
|
|
133
|
+
db.exec(`
|
|
134
|
+
CREATE TABLE IF NOT EXISTS agent_events (
|
|
135
|
+
id TEXT PRIMARY KEY,
|
|
136
|
+
task_id TEXT NOT NULL,
|
|
137
|
+
type TEXT NOT NULL,
|
|
138
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
139
|
+
payload TEXT NOT NULL DEFAULT '{}',
|
|
140
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_task_id ON agent_events (task_id);
|
|
144
|
+
`);
|
|
145
|
+
setSchemaVersion(db, 3);
|
|
146
|
+
}
|
|
147
|
+
if (version < 4) {
|
|
148
|
+
db.exec(`
|
|
149
|
+
ALTER TABLE squads ADD COLUMN color TEXT;
|
|
150
|
+
`);
|
|
151
|
+
const squads = db
|
|
152
|
+
.prepare("SELECT id FROM squads WHERE color IS NULL ORDER BY created_at")
|
|
153
|
+
.all();
|
|
154
|
+
const update = db.prepare("UPDATE squads SET color = ? WHERE id = ?");
|
|
155
|
+
const usedColors = [];
|
|
156
|
+
for (let i = 0; i < squads.length; i++) {
|
|
157
|
+
const color = pickSquadColor(usedColors);
|
|
158
|
+
usedColors.push(color);
|
|
159
|
+
update.run(color, squads[i].id);
|
|
160
|
+
}
|
|
161
|
+
setSchemaVersion(db, 4);
|
|
162
|
+
}
|
|
163
|
+
if (version < 5) {
|
|
164
|
+
db.exec(`
|
|
165
|
+
CREATE TABLE IF NOT EXISTS conversation_messages (
|
|
166
|
+
id TEXT PRIMARY KEY,
|
|
167
|
+
conversation_id TEXT NOT NULL,
|
|
168
|
+
role TEXT NOT NULL CHECK(role IN ('user', 'assistant')),
|
|
169
|
+
content TEXT NOT NULL,
|
|
170
|
+
source TEXT NOT NULL DEFAULT 'web',
|
|
171
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
CREATE INDEX IF NOT EXISTS idx_conv_messages_conversation_id
|
|
175
|
+
ON conversation_messages(conversation_id);
|
|
176
|
+
|
|
177
|
+
CREATE INDEX IF NOT EXISTS idx_conv_messages_created_at
|
|
178
|
+
ON conversation_messages(created_at);
|
|
179
|
+
|
|
180
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS conversation_messages_fts
|
|
181
|
+
USING fts5(content, content=conversation_messages, content_rowid=rowid);
|
|
182
|
+
|
|
183
|
+
CREATE TRIGGER IF NOT EXISTS conv_messages_fts_insert
|
|
184
|
+
AFTER INSERT ON conversation_messages BEGIN
|
|
185
|
+
INSERT INTO conversation_messages_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
186
|
+
END;
|
|
187
|
+
|
|
188
|
+
CREATE TRIGGER IF NOT EXISTS conv_messages_fts_update
|
|
189
|
+
AFTER UPDATE ON conversation_messages BEGIN
|
|
190
|
+
INSERT INTO conversation_messages_fts(conversation_messages_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
191
|
+
INSERT INTO conversation_messages_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
192
|
+
END;
|
|
193
|
+
|
|
194
|
+
CREATE TRIGGER IF NOT EXISTS conv_messages_fts_delete
|
|
195
|
+
AFTER DELETE ON conversation_messages BEGIN
|
|
196
|
+
INSERT INTO conversation_messages_fts(conversation_messages_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
197
|
+
END;
|
|
198
|
+
`);
|
|
199
|
+
setSchemaVersion(db, 5);
|
|
200
|
+
}
|
|
201
|
+
if (version < 6) {
|
|
202
|
+
db.exec(`
|
|
203
|
+
CREATE TABLE IF NOT EXISTS audit_log (
|
|
204
|
+
id TEXT PRIMARY KEY,
|
|
205
|
+
squad_id TEXT REFERENCES squads(id) ON DELETE SET NULL,
|
|
206
|
+
agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
207
|
+
task_id TEXT,
|
|
208
|
+
action_type TEXT NOT NULL,
|
|
209
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
210
|
+
payload TEXT NOT NULL DEFAULT '{}',
|
|
211
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_squad_id ON audit_log (squad_id);
|
|
215
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_agent_id ON audit_log (agent_id);
|
|
216
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_action_type ON audit_log (action_type);
|
|
217
|
+
CREATE INDEX IF NOT EXISTS idx_audit_log_created_at ON audit_log (created_at);
|
|
218
|
+
`);
|
|
219
|
+
setSchemaVersion(db, 6);
|
|
220
|
+
}
|
|
221
|
+
if (version < 7) {
|
|
222
|
+
db.exec(`
|
|
223
|
+
CREATE TABLE IF NOT EXISTS token_usage (
|
|
224
|
+
id TEXT PRIMARY KEY,
|
|
225
|
+
squad_id TEXT REFERENCES squads(id) ON DELETE CASCADE,
|
|
226
|
+
agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
227
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
228
|
+
model TEXT NOT NULL,
|
|
229
|
+
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
230
|
+
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
231
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
232
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
CREATE INDEX IF NOT EXISTS idx_token_usage_squad ON token_usage(squad_id);
|
|
236
|
+
CREATE INDEX IF NOT EXISTS idx_token_usage_agent ON token_usage(agent_id);
|
|
237
|
+
CREATE INDEX IF NOT EXISTS idx_token_usage_task ON token_usage(task_id);
|
|
238
|
+
CREATE INDEX IF NOT EXISTS idx_token_usage_created ON token_usage(created_at);
|
|
239
|
+
`);
|
|
240
|
+
setSchemaVersion(db, 7);
|
|
241
|
+
}
|
|
131
242
|
}
|
|
132
243
|
function getSchemaVersion(db) {
|
|
133
244
|
const row = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
|
package/dist/store/schedules.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getDb } from "./db.js";
|
|
3
3
|
export function createSchedule(input) {
|
|
4
|
+
const squadId = input.squad_id.trim();
|
|
5
|
+
if (!squadId) {
|
|
6
|
+
throw new Error("squad_id is required");
|
|
7
|
+
}
|
|
4
8
|
const db = getDb();
|
|
5
9
|
const id = randomUUID();
|
|
6
10
|
db.prepare(`INSERT INTO schedules (id, type, squad_id, cron, agenda, prompt)
|
|
7
|
-
VALUES (?, ?, ?, ?, ?, ?)`).run(id, input.type,
|
|
11
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(id, input.type, squadId, input.cron, input.agenda ?? "", input.prompt ?? "");
|
|
12
|
+
return db.prepare("SELECT * FROM schedules WHERE id = ?").get(id);
|
|
13
|
+
}
|
|
14
|
+
export function getSchedule(id) {
|
|
15
|
+
const db = getDb();
|
|
8
16
|
return db.prepare("SELECT * FROM schedules WHERE id = ?").get(id);
|
|
9
17
|
}
|
|
10
18
|
export function listSchedules(type) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Palette complementary to the site's brand gradient (magenta #E43A9C → violet #F041FF)
|
|
2
|
+
// Avoiding direct brand colors; using analogous/complementary hues that look cohesive
|
|
3
|
+
export const SQUAD_COLOR_PALETTE = [
|
|
4
|
+
"#06b6d4", // cyan (complementary to magenta)
|
|
5
|
+
"#14b8a6", // teal
|
|
6
|
+
"#f59e0b", // amber (warm contrast)
|
|
7
|
+
"#8b5cf6", // violet (analogous)
|
|
8
|
+
"#3b82f6", // blue
|
|
9
|
+
"#10b981", // emerald
|
|
10
|
+
"#f43f5e", // rose (analogous warm)
|
|
11
|
+
"#6366f1", // indigo
|
|
12
|
+
];
|
|
13
|
+
const DEFAULT_SQUAD_COLOR = "#3b82f6";
|
|
14
|
+
export function pickSquadColor(usedColors, random = Math.random) {
|
|
15
|
+
const used = new Set(usedColors.filter(Boolean).map((c) => c.toLowerCase()));
|
|
16
|
+
const available = SQUAD_COLOR_PALETTE.filter((c) => !used.has(c.toLowerCase()));
|
|
17
|
+
const source = available.length > 0 ? available : SQUAD_COLOR_PALETTE;
|
|
18
|
+
if (source.length === 0)
|
|
19
|
+
return DEFAULT_SQUAD_COLOR;
|
|
20
|
+
const index = Math.floor(random() * source.length);
|
|
21
|
+
return source[index] ?? DEFAULT_SQUAD_COLOR;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=squad-colors.js.map
|
package/dist/store/squads.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getDb } from "./db.js";
|
|
3
|
+
import { pickSquadColor } from "./squad-colors.js";
|
|
3
4
|
export function createSquad(name, universe, repoUrl) {
|
|
4
5
|
const db = getDb();
|
|
5
6
|
const id = randomUUID();
|
|
6
7
|
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
7
|
-
|
|
8
|
+
const usedColors = db
|
|
9
|
+
.prepare("SELECT color FROM squads WHERE color IS NOT NULL")
|
|
10
|
+
.all();
|
|
11
|
+
const color = pickSquadColor(usedColors.map((row) => row.color));
|
|
12
|
+
db.prepare("INSERT INTO squads (id, name, slug, universe, color, repo_url) VALUES (?, ?, ?, ?, ?, ?)").run(id, name, slug, universe, color, repoUrl ?? null);
|
|
8
13
|
return db.prepare("SELECT * FROM squads WHERE id = ?").get(id);
|
|
9
14
|
}
|
|
10
15
|
export function getSquad(id) {
|
package/dist/store/tasks.js
CHANGED
|
@@ -32,4 +32,47 @@ export function getActiveTasksForInstance(instanceId) {
|
|
|
32
32
|
.prepare("SELECT * FROM tasks WHERE instance_id = ? AND status NOT IN ('done', 'failed') ORDER BY created_at")
|
|
33
33
|
.all(instanceId);
|
|
34
34
|
}
|
|
35
|
+
/** Squads with active tasks and no update within this window are flagged as stalled. */
|
|
36
|
+
const STALL_THRESHOLD_MS = 60 * 60 * 1000; // 60 minutes
|
|
37
|
+
export function getSquadTaskMetrics(squadId) {
|
|
38
|
+
const db = getDb();
|
|
39
|
+
const row = db
|
|
40
|
+
.prepare(`SELECT
|
|
41
|
+
COUNT(*) AS total,
|
|
42
|
+
SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) AS completed,
|
|
43
|
+
SUM(CASE WHEN status = 'done' AND updated_at >= datetime('now', '-7 days') THEN 1 ELSE 0 END) AS completed_recent,
|
|
44
|
+
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending,
|
|
45
|
+
SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) AS in_progress,
|
|
46
|
+
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
47
|
+
AVG(CASE WHEN status = 'done'
|
|
48
|
+
THEN (julianday(updated_at) - julianday(created_at)) * 1440
|
|
49
|
+
ELSE NULL END) AS avg_cycle_minutes,
|
|
50
|
+
MAX(CASE WHEN status IN ('pending', 'in_progress') THEN updated_at ELSE NULL END) AS last_active_update
|
|
51
|
+
FROM tasks WHERE squad_id = ?`)
|
|
52
|
+
.get(squadId);
|
|
53
|
+
// A squad is considered stalled if it has active tasks but none have been
|
|
54
|
+
// updated within STALL_THRESHOLD_MS.
|
|
55
|
+
const hasActiveTasks = row.pending + row.in_progress > 0;
|
|
56
|
+
const isStalled = hasActiveTasks &&
|
|
57
|
+
row.last_active_update !== null &&
|
|
58
|
+
new Date(row.last_active_update + "Z").getTime() <
|
|
59
|
+
Date.now() - STALL_THRESHOLD_MS;
|
|
60
|
+
const recentTasks = db
|
|
61
|
+
.prepare("SELECT * FROM tasks WHERE squad_id = ? ORDER BY updated_at DESC LIMIT 5")
|
|
62
|
+
.all(squadId);
|
|
63
|
+
return {
|
|
64
|
+
squadId,
|
|
65
|
+
tasksTotal: row.total ?? 0,
|
|
66
|
+
tasksCompleted: row.completed ?? 0,
|
|
67
|
+
tasksCompletedRecent: row.completed_recent ?? 0,
|
|
68
|
+
tasksPending: row.pending ?? 0,
|
|
69
|
+
tasksInProgress: row.in_progress ?? 0,
|
|
70
|
+
tasksFailed: row.failed ?? 0,
|
|
71
|
+
avgCycleTimeMinutes: row.avg_cycle_minutes != null
|
|
72
|
+
? Math.round(row.avg_cycle_minutes * 10) / 10
|
|
73
|
+
: null,
|
|
74
|
+
isStalled,
|
|
75
|
+
recentTasks,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
35
78
|
//# sourceMappingURL=tasks.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getDb } from "./db.js";
|
|
3
|
+
export function recordTokenUsage(params) {
|
|
4
|
+
const db = getDb();
|
|
5
|
+
const id = randomUUID();
|
|
6
|
+
db.prepare(`INSERT INTO token_usage (id, squad_id, agent_id, task_id, model, input_tokens, output_tokens, cost_usd)
|
|
7
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(id, params.squadId ?? null, params.agentId ?? null, params.taskId ?? null, params.model, params.inputTokens, params.outputTokens, params.costUsd);
|
|
8
|
+
return db.prepare("SELECT * FROM token_usage WHERE id = ?").get(id);
|
|
9
|
+
}
|
|
10
|
+
export function getTokenUsageSummary(opts) {
|
|
11
|
+
const db = getDb();
|
|
12
|
+
let sql = `SELECT
|
|
13
|
+
COUNT(*) as total_records,
|
|
14
|
+
COALESCE(SUM(input_tokens), 0) as total_input_tokens,
|
|
15
|
+
COALESCE(SUM(output_tokens), 0) as total_output_tokens,
|
|
16
|
+
COALESCE(SUM(input_tokens + output_tokens), 0) as total_tokens,
|
|
17
|
+
COALESCE(SUM(cost_usd), 0) as total_cost_usd
|
|
18
|
+
FROM token_usage WHERE 1=1`;
|
|
19
|
+
const params = [];
|
|
20
|
+
if (opts?.since) {
|
|
21
|
+
sql += " AND created_at >= ?";
|
|
22
|
+
params.push(opts.since);
|
|
23
|
+
}
|
|
24
|
+
return db.prepare(sql).get(...params);
|
|
25
|
+
}
|
|
26
|
+
export function getTokenUsageBySquad(opts) {
|
|
27
|
+
const db = getDb();
|
|
28
|
+
let sql = `SELECT
|
|
29
|
+
s.id,
|
|
30
|
+
s.name,
|
|
31
|
+
COALESCE(SUM(t.input_tokens), 0) as total_input_tokens,
|
|
32
|
+
COALESCE(SUM(t.output_tokens), 0) as total_output_tokens,
|
|
33
|
+
COALESCE(SUM(t.input_tokens + t.output_tokens), 0) as total_tokens,
|
|
34
|
+
COALESCE(SUM(t.cost_usd), 0) as total_cost_usd,
|
|
35
|
+
COUNT(t.id) as record_count
|
|
36
|
+
FROM squads s
|
|
37
|
+
LEFT JOIN token_usage t ON t.squad_id = s.id`;
|
|
38
|
+
const params = [];
|
|
39
|
+
if (opts?.since) {
|
|
40
|
+
sql += " AND t.created_at >= ?";
|
|
41
|
+
params.push(opts.since);
|
|
42
|
+
}
|
|
43
|
+
sql += " GROUP BY s.id ORDER BY total_tokens DESC";
|
|
44
|
+
return db.prepare(sql).all(...params);
|
|
45
|
+
}
|
|
46
|
+
export function getTokenUsageByAgent(opts) {
|
|
47
|
+
const db = getDb();
|
|
48
|
+
let sql = `SELECT
|
|
49
|
+
a.id,
|
|
50
|
+
a.character_name as name,
|
|
51
|
+
COALESCE(SUM(t.input_tokens), 0) as total_input_tokens,
|
|
52
|
+
COALESCE(SUM(t.output_tokens), 0) as total_output_tokens,
|
|
53
|
+
COALESCE(SUM(t.input_tokens + t.output_tokens), 0) as total_tokens,
|
|
54
|
+
COALESCE(SUM(t.cost_usd), 0) as total_cost_usd,
|
|
55
|
+
COUNT(t.id) as record_count
|
|
56
|
+
FROM agents a
|
|
57
|
+
LEFT JOIN token_usage t ON t.agent_id = a.id`;
|
|
58
|
+
const params = [];
|
|
59
|
+
const conditions = [];
|
|
60
|
+
if (opts?.squadId) {
|
|
61
|
+
conditions.push("a.squad_id = ?");
|
|
62
|
+
params.push(opts.squadId);
|
|
63
|
+
}
|
|
64
|
+
if (opts?.since) {
|
|
65
|
+
conditions.push("(t.created_at >= ? OR t.created_at IS NULL)");
|
|
66
|
+
params.push(opts.since);
|
|
67
|
+
}
|
|
68
|
+
if (conditions.length > 0) {
|
|
69
|
+
sql += " WHERE " + conditions.join(" AND ");
|
|
70
|
+
}
|
|
71
|
+
sql += " GROUP BY a.id ORDER BY total_tokens DESC";
|
|
72
|
+
return db.prepare(sql).all(...params);
|
|
73
|
+
}
|
|
74
|
+
export function getDailyTokenUsage(days = 30) {
|
|
75
|
+
const db = getDb();
|
|
76
|
+
const sql = `SELECT
|
|
77
|
+
date(created_at) as date,
|
|
78
|
+
COALESCE(SUM(input_tokens), 0) as total_input_tokens,
|
|
79
|
+
COALESCE(SUM(output_tokens), 0) as total_output_tokens,
|
|
80
|
+
COALESCE(SUM(input_tokens + output_tokens), 0) as total_tokens,
|
|
81
|
+
COALESCE(SUM(cost_usd), 0) as total_cost_usd
|
|
82
|
+
FROM token_usage
|
|
83
|
+
WHERE created_at >= date('now', '-' || ? || ' days')
|
|
84
|
+
GROUP BY date(created_at)
|
|
85
|
+
ORDER BY date ASC`;
|
|
86
|
+
return db.prepare(sql).all(days);
|
|
87
|
+
}
|
|
88
|
+
export function getTokenUsageForTask(taskId) {
|
|
89
|
+
const db = getDb();
|
|
90
|
+
return db
|
|
91
|
+
.prepare("SELECT * FROM token_usage WHERE task_id = ? ORDER BY created_at ASC")
|
|
92
|
+
.all(taskId);
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=token-usage.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, dirname, resolve } from "node:path";
|
|
3
|
+
import { PATHS } from "../paths.js";
|
|
4
|
+
import { listPages } from "./fs.js";
|
|
5
|
+
export async function getBacklinks(targetPath) {
|
|
6
|
+
if (!existsSync(PATHS.wikiPages))
|
|
7
|
+
return [];
|
|
8
|
+
const pages = await listPages();
|
|
9
|
+
const backlinks = [];
|
|
10
|
+
// Normalize the target path (ensure .md extension)
|
|
11
|
+
const normalizedTarget = targetPath.endsWith(".md") ? targetPath : `${targetPath}.md`;
|
|
12
|
+
for (const pagePath of pages) {
|
|
13
|
+
if (pagePath === normalizedTarget)
|
|
14
|
+
continue; // Skip self-references
|
|
15
|
+
const fullPath = join(PATHS.wikiPages, pagePath);
|
|
16
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
17
|
+
if (pageLinksTo(content, pagePath, normalizedTarget)) {
|
|
18
|
+
backlinks.push(pagePath);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return backlinks;
|
|
22
|
+
}
|
|
23
|
+
function pageLinksTo(content, fromPage, targetPage) {
|
|
24
|
+
const fromDir = dirname(fromPage);
|
|
25
|
+
const targetWithoutExt = targetPage.replace(/\.md$/, "");
|
|
26
|
+
// Check standard markdown links: [text](url)
|
|
27
|
+
const markdownLinkRegex = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
28
|
+
let match;
|
|
29
|
+
while ((match = markdownLinkRegex.exec(content)) !== null) {
|
|
30
|
+
const linkHref = match[2].split("#")[0].trim(); // Strip anchors and whitespace
|
|
31
|
+
if (!linkHref || linkHref.startsWith("http://") || linkHref.startsWith("https://"))
|
|
32
|
+
continue;
|
|
33
|
+
const resolvedLink = resolve("/", fromDir, linkHref).slice(1); // Resolve relative path, strip leading /
|
|
34
|
+
const resolvedWithoutExt = resolvedLink.endsWith(".md") ? resolvedLink.slice(0, -3) : resolvedLink;
|
|
35
|
+
if (resolvedWithoutExt === targetWithoutExt)
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
// Check wiki-style links: [[page]] or [[page|display text]]
|
|
39
|
+
// Wiki-style links are resolved from the wiki root (absolute), not relative to the current page
|
|
40
|
+
const wikiLinkRegex = /\[\[([^\]]+)\]\]/g;
|
|
41
|
+
while ((match = wikiLinkRegex.exec(content)) !== null) {
|
|
42
|
+
const linkTarget = match[1].split("|")[0].trim(); // Handle [[page|display text]]
|
|
43
|
+
if (!linkTarget)
|
|
44
|
+
continue;
|
|
45
|
+
const resolvedWithoutExt = linkTarget.endsWith(".md") ? linkTarget.slice(0, -3) : linkTarget;
|
|
46
|
+
if (resolvedWithoutExt === targetWithoutExt)
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=backlinks.js.map
|
package/dist/wiki/fs.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, cpSync } from "node:fs";
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
3
|
import { readdirSync } from "node:fs";
|
|
4
4
|
import { PATHS } from "../paths.js";
|
|
5
|
+
function safeJoin(base, userPath) {
|
|
6
|
+
// Strip path traversal by removing any '..' and absolute path components
|
|
7
|
+
const sanitized = userPath
|
|
8
|
+
.split(/[/\\]/)
|
|
9
|
+
.filter((segment) => segment !== ".." && segment !== ".")
|
|
10
|
+
.join("/");
|
|
11
|
+
const resolved = join(base, sanitized);
|
|
12
|
+
if (!resolved.startsWith(base)) {
|
|
13
|
+
throw new Error("Invalid path: traversal outside base directory is not allowed");
|
|
14
|
+
}
|
|
15
|
+
return resolved;
|
|
16
|
+
}
|
|
5
17
|
export async function readPage(path) {
|
|
6
18
|
const fullPath = join(PATHS.wikiPages, path);
|
|
7
19
|
if (!existsSync(fullPath)) {
|
|
@@ -43,4 +55,54 @@ export async function listPages(dir) {
|
|
|
43
55
|
walk(root, "");
|
|
44
56
|
return results;
|
|
45
57
|
}
|
|
58
|
+
export async function listTemplates() {
|
|
59
|
+
const root = PATHS.wikiSquadTemplates;
|
|
60
|
+
if (!existsSync(root))
|
|
61
|
+
return [];
|
|
62
|
+
const results = [];
|
|
63
|
+
const walk = (current, prefix) => {
|
|
64
|
+
const entries = readdirSync(current, { withFileTypes: true });
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
67
|
+
if (entry.isDirectory()) {
|
|
68
|
+
walk(join(current, entry.name), rel);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
results.push(rel);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
walk(root, "");
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
export async function readTemplate(path) {
|
|
79
|
+
const fullPath = safeJoin(PATHS.wikiSquadTemplates, path);
|
|
80
|
+
if (!existsSync(fullPath)) {
|
|
81
|
+
throw new Error(`Template not found: ${path}`);
|
|
82
|
+
}
|
|
83
|
+
return readFileSync(fullPath, "utf-8");
|
|
84
|
+
}
|
|
85
|
+
export async function writeTemplate(path, content) {
|
|
86
|
+
const fullPath = safeJoin(PATHS.wikiSquadTemplates, path);
|
|
87
|
+
const dir = dirname(fullPath);
|
|
88
|
+
if (!existsSync(dir))
|
|
89
|
+
mkdirSync(dir, { recursive: true });
|
|
90
|
+
writeFileSync(fullPath, content);
|
|
91
|
+
}
|
|
92
|
+
export async function deleteTemplate(path) {
|
|
93
|
+
const fullPath = safeJoin(PATHS.wikiSquadTemplates, path);
|
|
94
|
+
if (!existsSync(fullPath)) {
|
|
95
|
+
throw new Error(`Template not found: ${path}`);
|
|
96
|
+
}
|
|
97
|
+
rmSync(fullPath);
|
|
98
|
+
}
|
|
99
|
+
export async function copySquadTemplates(slug) {
|
|
100
|
+
const templateDir = PATHS.wikiSquadTemplates;
|
|
101
|
+
if (!existsSync(templateDir))
|
|
102
|
+
return;
|
|
103
|
+
const destDir = join(PATHS.wikiPages, "squads", slug);
|
|
104
|
+
if (!existsSync(destDir))
|
|
105
|
+
mkdirSync(destDir, { recursive: true });
|
|
106
|
+
cpSync(templateDir, destDir, { recursive: true, force: false, errorOnExist: false });
|
|
107
|
+
}
|
|
46
108
|
//# sourceMappingURL=fs.js.map
|
package/dist/wiki/search.js
CHANGED
|
@@ -2,7 +2,18 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { PATHS } from "../paths.js";
|
|
4
4
|
export async function searchPages(query) {
|
|
5
|
-
|
|
5
|
+
return searchInDirectory(PATHS.wikiPages, query);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Search wiki pages within a specific subfolder (e.g., "squads/my-squad").
|
|
9
|
+
* Results have paths relative to the subfolder.
|
|
10
|
+
*/
|
|
11
|
+
export async function searchSquadPages(query, subdir) {
|
|
12
|
+
const root = join(PATHS.wikiPages, subdir);
|
|
13
|
+
return searchInDirectory(root, query);
|
|
14
|
+
}
|
|
15
|
+
function searchInDirectory(root, query) {
|
|
16
|
+
if (!existsSync(root))
|
|
6
17
|
return [];
|
|
7
18
|
const results = [];
|
|
8
19
|
const lower = query.toLowerCase();
|
|
@@ -30,7 +41,7 @@ export async function searchPages(query) {
|
|
|
30
41
|
}
|
|
31
42
|
}
|
|
32
43
|
};
|
|
33
|
-
walk(
|
|
44
|
+
walk(root, "");
|
|
34
45
|
return results;
|
|
35
46
|
}
|
|
36
47
|
//# sourceMappingURL=search.js.map
|
package/package.json
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{q as W,t as X,x as Z,z as a,o as d,l as o,s as A,K as N,a as V,r as S,J as l,Y as f,T,F as h,E as w,V as F,n as b,D as n,h as L,k as _,v as ee}from"./index-CiZnRvN4.js";/**
|
|
2
|
+
* @license lucide-vue-next v0.474.0 - ISC
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the ISC license.
|
|
5
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/const te=W("FilterIcon",[["polygon",{points:"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3",key:"1yg77f"}]]),oe={class:"p-6"},se={class:"flex items-center justify-between mb-6"},ae={class:"text-2xl font-bold flex items-center gap-2"},de={class:"text-sm text-muted-foreground"},le={class:"border border-border rounded-lg p-4 mb-6 space-y-3"},re={class:"flex items-center gap-2 text-sm font-medium text-muted-foreground mb-1"},ne={class:"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3"},ue=["value"],ie=["value"],ce=["value"],pe={key:0,class:"text-muted-foreground"},ve={key:1,class:"text-center py-12 text-muted-foreground"},me={key:2,class:"space-y-1"},ge=["onClick"],xe={class:"flex-1 min-w-0"},fe={class:"flex flex-wrap items-center gap-2 mb-1"},be={key:0,class:"text-xs bg-secondary text-secondary-foreground px-2 py-0.5 rounded-full"},_e={key:1,class:"text-xs bg-secondary text-secondary-foreground px-2 py-0.5 rounded-full"},ye={class:"text-sm truncate"},ke={class:"text-xs text-muted-foreground mt-0.5"},he={class:"text-xs text-muted-foreground mt-1 shrink-0"},we={key:0,class:"border-t border-border px-4 py-3"},qe={key:0,class:"text-xs text-muted-foreground mb-2"},Ce={class:"font-mono"},Pe={class:"text-xs bg-muted rounded p-3 overflow-x-auto whitespace-pre-wrap break-words"},Ae={key:3,class:"flex items-center justify-between mt-4"},Ne=["disabled"],Se={class:"text-xs text-muted-foreground"},Te=["disabled"],u=50,Me=X({__name:"AuditLogView",setup(Ve){const q=n([]),c=n(0),C=n(!0),y=n(null),P=n([]),k=n([]),i=n(""),p=n(""),v=n(""),m=n(""),g=n(""),r=n(0),M=["message_received","task_delegated","task_completed","task_failed","shell_command","squad_created","squad_meeting"],I={message_received:"bg-blue-500/20 text-blue-400",task_delegated:"bg-purple-500/20 text-purple-400",task_completed:"bg-green-500/20 text-green-400",task_failed:"bg-red-500/20 text-red-400",shell_command:"bg-yellow-500/20 text-yellow-400",squad_created:"bg-indigo-500/20 text-indigo-400",squad_meeting:"bg-teal-500/20 text-teal-400"};function O(s){return I[s]??"bg-secondary text-secondary-foreground"}async function x(){C.value=!0;try{const s=new URLSearchParams;i.value&&s.set("squad_id",i.value),p.value&&s.set("agent_id",p.value),v.value&&s.set("action_type",v.value),m.value&&s.set("from",m.value),g.value&&s.set("to",g.value),s.set("limit",String(u)),s.set("offset",String(r.value));const t=await L(`/audit-log?${s.toString()}`);q.value=t.entries,c.value=t.total}finally{C.value=!1}}async function U(){const s=await L("/squads");P.value=s.squads,k.value=s.agents}function D(){r.value=0,x()}function $(){i.value="",p.value="",v.value="",m.value="",g.value="",r.value=0,x()}function E(){r.value>0&&(r.value=Math.max(0,r.value-u),x())}function B(){r.value+u<c.value&&(r.value=r.value+u,x())}function J(s){y.value=y.value===s?null:s}function R(s){try{return JSON.stringify(JSON.parse(s.payload),null,2)}catch{return s.payload}}function Y(s){var t;return s?((t=P.value.find(e=>e.id===s))==null?void 0:t.name)??s.slice(0,8):""}function j(s){if(!s)return"";const t=k.value.find(e=>e.id===s);return t?`${t.character_name} (${t.role_title})`:s.slice(0,8)}const z=_(()=>r.value+u<c.value),G=_(()=>r.value>0),K=_(()=>Math.floor(r.value/u)+1),H=_(()=>Math.max(1,Math.ceil(c.value/u))),Q=_(()=>i.value?k.value.filter(s=>s.squad_id===i.value):k.value);return Z(async()=>{await Promise.all([x(),U()])}),(s,t)=>(a(),d("div",oe,[o("div",se,[o("h1",ae,[A(N(V),{class:"w-6 h-6"}),t[6]||(t[6]=S(" Audit Log ",-1))]),o("span",de,l(c.value)+" entries",1)]),o("div",le,[o("div",re,[A(N(te),{class:"w-4 h-4"}),t[7]||(t[7]=S(" Filters ",-1))]),o("div",ne,[f(o("select",{"onUpdate:modelValue":t[0]||(t[0]=e=>i.value=e),class:"px-2 py-1.5 text-sm rounded-md border border-border bg-background",onChange:t[1]||(t[1]=e=>p.value="")},[t[8]||(t[8]=o("option",{value:""},"All squads",-1)),(a(!0),d(h,null,w(P.value,e=>(a(),d("option",{key:e.id,value:e.id},l(e.name),9,ue))),128))],544),[[T,i.value]]),f(o("select",{"onUpdate:modelValue":t[2]||(t[2]=e=>p.value=e),class:"px-2 py-1.5 text-sm rounded-md border border-border bg-background"},[t[9]||(t[9]=o("option",{value:""},"All agents",-1)),(a(!0),d(h,null,w(Q.value,e=>(a(),d("option",{key:e.id,value:e.id},l(e.character_name),9,ie))),128))],512),[[T,p.value]]),f(o("select",{"onUpdate:modelValue":t[3]||(t[3]=e=>v.value=e),class:"px-2 py-1.5 text-sm rounded-md border border-border bg-background"},[t[10]||(t[10]=o("option",{value:""},"All action types",-1)),(a(),d(h,null,w(M,e=>o("option",{key:e,value:e},l(e),9,ce)),64))],512),[[T,v.value]]),f(o("input",{"onUpdate:modelValue":t[4]||(t[4]=e=>m.value=e),type:"datetime-local",placeholder:"From",class:"px-2 py-1.5 text-sm rounded-md border border-border bg-background"},null,512),[[F,m.value]]),f(o("input",{"onUpdate:modelValue":t[5]||(t[5]=e=>g.value=e),type:"datetime-local",placeholder:"To",class:"px-2 py-1.5 text-sm rounded-md border border-border bg-background"},null,512),[[F,g.value]])]),o("div",{class:"flex gap-2"},[o("button",{onClick:D,class:"px-3 py-1.5 text-xs rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"}," Apply "),o("button",{onClick:$,class:"px-3 py-1.5 text-xs rounded-md border border-border text-muted-foreground hover:text-foreground transition-colors"}," Reset ")])]),C.value?(a(),d("div",pe,"Loading...")):q.value.length===0?(a(),d("div",ve,[A(N(V),{class:"w-12 h-12 mx-auto mb-3 opacity-50"}),t[11]||(t[11]=o("p",null,"No audit log entries found.",-1))])):(a(),d("div",me,[(a(!0),d(h,null,w(q.value,e=>(a(),d("div",{key:e.id,class:"border border-border rounded-lg overflow-hidden"},[o("div",{onClick:Fe=>J(e.id),class:"flex items-start gap-3 px-4 py-3 cursor-pointer hover:bg-muted/50 transition-colors"},[o("div",xe,[o("div",fe,[o("span",{class:ee(["text-xs px-2 py-0.5 rounded-full font-mono",O(e.action_type)])},l(e.action_type),3),e.squad_id?(a(),d("span",be,l(Y(e.squad_id)),1)):b("",!0),e.agent_id?(a(),d("span",_e,l(j(e.agent_id)),1)):b("",!0)]),o("p",ye,l(e.summary),1),o("p",ke,l(e.created_at),1)]),o("span",he,l(y.value===e.id?"▲":"▼"),1)],8,ge),y.value===e.id?(a(),d("div",we,[e.task_id?(a(),d("div",qe,[t[12]||(t[12]=S(" Task ID: ",-1)),o("code",Ce,l(e.task_id),1)])):b("",!0),o("pre",Pe,l(R(e)),1)])):b("",!0)]))),128))])),c.value>u?(a(),d("div",Ae,[o("button",{disabled:!G.value,onClick:E,class:"px-3 py-1.5 text-xs rounded-md border border-border text-muted-foreground disabled:opacity-40 hover:text-foreground transition-colors"}," ← Previous ",8,Ne),o("span",Se," Page "+l(K.value)+" of "+l(H.value),1),o("button",{disabled:!z.value,onClick:B,class:"px-3 py-1.5 text-xs rounded-md border border-border text-muted-foreground disabled:opacity-40 hover:text-foreground transition-colors"}," Next → ",8,Te)])):b("",!0)]))}});export{Me as default};
|