@spinabot/brigade 1.0.1 → 1.1.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/convex/_generated/api.d.ts +85 -0
- package/convex/_generated/api.js +23 -0
- package/convex/_generated/dataModel.d.ts +60 -0
- package/convex/_generated/server.d.ts +143 -0
- package/convex/_generated/server.js +93 -0
- package/convex/admin.d.ts +57 -0
- package/convex/admin.ts +315 -0
- package/convex/auth.d.ts +159 -0
- package/convex/auth.ts +217 -0
- package/convex/blobs.d.ts +38 -0
- package/convex/blobs.ts +115 -0
- package/convex/channels.d.ts +150 -0
- package/convex/channels.ts +455 -0
- package/convex/config.d.ts +67 -0
- package/convex/config.ts +168 -0
- package/convex/cron.d.ts +237 -0
- package/convex/cron.ts +199 -0
- package/convex/execApprovals.d.ts +31 -0
- package/convex/execApprovals.ts +58 -0
- package/convex/extensions.d.ts +30 -0
- package/convex/extensions.ts +51 -0
- package/convex/health.d.ts +18 -0
- package/convex/health.ts +69 -0
- package/convex/instance.d.ts +34 -0
- package/convex/instance.ts +82 -0
- package/convex/logs.d.ts +178 -0
- package/convex/logs.ts +253 -0
- package/convex/memory.d.ts +354 -0
- package/convex/memory.ts +536 -0
- package/convex/messages.d.ts +124 -0
- package/convex/messages.ts +347 -0
- package/convex/org.d.ts +75 -0
- package/convex/org.ts +99 -0
- package/convex/schema.d.ts +1130 -0
- package/convex/schema.ts +847 -0
- package/convex/sessions.d.ts +100 -0
- package/convex/sessions.ts +105 -0
- package/convex/skills.d.ts +73 -0
- package/convex/skills.ts +102 -0
- package/convex/subagents.d.ts +214 -0
- package/convex/subagents.ts +99 -0
- package/convex/tsconfig.json +23 -0
- package/convex/whatsappAuth.d.ts +52 -0
- package/convex/whatsappAuth.ts +151 -0
- package/convex/workspace.d.ts +49 -0
- package/convex/workspace.ts +106 -0
- package/dist/buildstamp.json +1 -1
- package/dist/cli/commands/convex-cmd.d.ts +27 -0
- package/dist/cli/commands/convex-cmd.d.ts.map +1 -0
- package/dist/cli/commands/convex-cmd.js +162 -0
- package/dist/cli/commands/convex-cmd.js.map +1 -0
- package/dist/cli/program/build-program.d.ts.map +1 -1
- package/dist/cli/program/build-program.js +64 -0
- package/dist/cli/program/build-program.js.map +1 -1
- package/dist/config/paths.d.ts +3 -0
- package/dist/config/paths.d.ts.map +1 -1
- package/dist/config/paths.js +39 -0
- package/dist/config/paths.js.map +1 -1
- package/package.json +7 -1
- package/scripts/convex-dev.mjs +321 -0
- package/scripts/convex-push.mjs +69 -0
- package/scripts/install-convex.mjs +123 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare const getCoord: import("convex/server").RegisteredQuery<"public", {
|
|
2
|
+
instanceId: string;
|
|
3
|
+
}, Promise<{
|
|
4
|
+
_id: import("convex/values").GenericId<"gatewayCoord">;
|
|
5
|
+
_creationTime: number;
|
|
6
|
+
pid?: number | undefined;
|
|
7
|
+
pidAliveAt?: number | undefined;
|
|
8
|
+
heartbeatTs?: number | undefined;
|
|
9
|
+
heartbeatPid?: number | undefined;
|
|
10
|
+
heartbeatUptimeMs?: number | undefined;
|
|
11
|
+
lockPid?: number | undefined;
|
|
12
|
+
lockPort?: number | undefined;
|
|
13
|
+
lockCreatedAt?: string | undefined;
|
|
14
|
+
lockLeaseUntil?: number | undefined;
|
|
15
|
+
instanceId: string;
|
|
16
|
+
updatedAt: number;
|
|
17
|
+
} | null>>;
|
|
18
|
+
export declare const writePid: import("convex/server").RegisteredMutation<"public", {
|
|
19
|
+
pid: number;
|
|
20
|
+
instanceId: string;
|
|
21
|
+
}, Promise<void>>;
|
|
22
|
+
export declare const clearPid: import("convex/server").RegisteredMutation<"public", {
|
|
23
|
+
instanceId: string;
|
|
24
|
+
}, Promise<void>>;
|
|
25
|
+
export declare const writeHeartbeat: import("convex/server").RegisteredMutation<"public", {
|
|
26
|
+
ts: number;
|
|
27
|
+
pid: number;
|
|
28
|
+
uptimeMs: number;
|
|
29
|
+
instanceId: string;
|
|
30
|
+
}, Promise<void>>;
|
|
31
|
+
export declare const clearHeartbeat: import("convex/server").RegisteredMutation<"public", {
|
|
32
|
+
instanceId: string;
|
|
33
|
+
}, Promise<void>>;
|
|
34
|
+
//# sourceMappingURL=instance.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// convex/instance.ts — gatewayCoord (heartbeat/pid; the lock stays LOCAL — fs.open("wx") has no Convex equivalent)
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
import { mutation, query } from "./_generated/server.js";
|
|
4
|
+
|
|
5
|
+
export const getCoord = query({
|
|
6
|
+
args: { instanceId: v.string() },
|
|
7
|
+
handler: async (ctx, args) => {
|
|
8
|
+
return ctx.db
|
|
9
|
+
.query("gatewayCoord")
|
|
10
|
+
.withIndex("by_instance", (q) => q.eq("instanceId", args.instanceId))
|
|
11
|
+
.first();
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const writePid = mutation({
|
|
16
|
+
args: { instanceId: v.string(), pid: v.number() },
|
|
17
|
+
handler: async (ctx, args) => {
|
|
18
|
+
const existing = await ctx.db
|
|
19
|
+
.query("gatewayCoord")
|
|
20
|
+
.withIndex("by_instance", (q) => q.eq("instanceId", args.instanceId))
|
|
21
|
+
.first();
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const payload = {
|
|
24
|
+
instanceId: args.instanceId,
|
|
25
|
+
pid: args.pid,
|
|
26
|
+
pidAliveAt: now,
|
|
27
|
+
updatedAt: now,
|
|
28
|
+
};
|
|
29
|
+
if (existing) await ctx.db.patch(existing._id, payload);
|
|
30
|
+
else await ctx.db.insert("gatewayCoord", payload);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const clearPid = mutation({
|
|
35
|
+
args: { instanceId: v.string() },
|
|
36
|
+
handler: async (ctx, args) => {
|
|
37
|
+
const existing = await ctx.db
|
|
38
|
+
.query("gatewayCoord")
|
|
39
|
+
.withIndex("by_instance", (q) => q.eq("instanceId", args.instanceId))
|
|
40
|
+
.first();
|
|
41
|
+
if (existing) {
|
|
42
|
+
await ctx.db.patch(existing._id, { pid: undefined, pidAliveAt: undefined, updatedAt: Date.now() });
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const writeHeartbeat = mutation({
|
|
48
|
+
args: { instanceId: v.string(), ts: v.number(), pid: v.number(), uptimeMs: v.number() },
|
|
49
|
+
handler: async (ctx, args) => {
|
|
50
|
+
const existing = await ctx.db
|
|
51
|
+
.query("gatewayCoord")
|
|
52
|
+
.withIndex("by_instance", (q) => q.eq("instanceId", args.instanceId))
|
|
53
|
+
.first();
|
|
54
|
+
const payload = {
|
|
55
|
+
instanceId: args.instanceId,
|
|
56
|
+
heartbeatTs: args.ts,
|
|
57
|
+
heartbeatPid: args.pid,
|
|
58
|
+
heartbeatUptimeMs: args.uptimeMs,
|
|
59
|
+
updatedAt: Date.now(),
|
|
60
|
+
};
|
|
61
|
+
if (existing) await ctx.db.patch(existing._id, payload);
|
|
62
|
+
else await ctx.db.insert("gatewayCoord", payload);
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export const clearHeartbeat = mutation({
|
|
67
|
+
args: { instanceId: v.string() },
|
|
68
|
+
handler: async (ctx, args) => {
|
|
69
|
+
const existing = await ctx.db
|
|
70
|
+
.query("gatewayCoord")
|
|
71
|
+
.withIndex("by_instance", (q) => q.eq("instanceId", args.instanceId))
|
|
72
|
+
.first();
|
|
73
|
+
if (existing) {
|
|
74
|
+
await ctx.db.patch(existing._id, {
|
|
75
|
+
heartbeatTs: undefined,
|
|
76
|
+
heartbeatPid: undefined,
|
|
77
|
+
heartbeatUptimeMs: undefined,
|
|
78
|
+
updatedAt: Date.now(),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
});
|
package/convex/logs.d.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
export declare const appendSessionEvent: import("convex/server").RegisteredMutation<"public", {
|
|
2
|
+
toolName?: string | undefined;
|
|
3
|
+
content?: ArrayBuffer | undefined;
|
|
4
|
+
args?: ArrayBuffer | undefined;
|
|
5
|
+
aborted?: boolean | undefined;
|
|
6
|
+
inner?: string | undefined;
|
|
7
|
+
delta?: string | undefined;
|
|
8
|
+
role?: string | undefined;
|
|
9
|
+
stopReason?: string | undefined;
|
|
10
|
+
errorMessage?: string | undefined;
|
|
11
|
+
toolCallId?: string | undefined;
|
|
12
|
+
isError?: boolean | undefined;
|
|
13
|
+
result?: ArrayBuffer | undefined;
|
|
14
|
+
attempt?: number | undefined;
|
|
15
|
+
maxAttempts?: number | undefined;
|
|
16
|
+
delayMs?: number | undefined;
|
|
17
|
+
success?: boolean | undefined;
|
|
18
|
+
finalError?: string | undefined;
|
|
19
|
+
willRetry?: boolean | undefined;
|
|
20
|
+
messageCount?: number | undefined;
|
|
21
|
+
type: string;
|
|
22
|
+
agentId: string;
|
|
23
|
+
sessionKey: string;
|
|
24
|
+
ts: string;
|
|
25
|
+
day: string;
|
|
26
|
+
ownerId: string;
|
|
27
|
+
}, Promise<void>>;
|
|
28
|
+
export declare const readSessionEventTail: import("convex/server").RegisteredQuery<"public", {
|
|
29
|
+
limit?: number | undefined;
|
|
30
|
+
day?: string | undefined;
|
|
31
|
+
ownerId: string;
|
|
32
|
+
}, Promise<{
|
|
33
|
+
_id: import("convex/values").GenericId<"sessionEvents">;
|
|
34
|
+
_creationTime: number;
|
|
35
|
+
toolName?: string | undefined;
|
|
36
|
+
content?: ArrayBuffer | undefined;
|
|
37
|
+
args?: ArrayBuffer | undefined;
|
|
38
|
+
aborted?: boolean | undefined;
|
|
39
|
+
inner?: string | undefined;
|
|
40
|
+
delta?: string | undefined;
|
|
41
|
+
role?: string | undefined;
|
|
42
|
+
stopReason?: string | undefined;
|
|
43
|
+
errorMessage?: string | undefined;
|
|
44
|
+
toolCallId?: string | undefined;
|
|
45
|
+
isError?: boolean | undefined;
|
|
46
|
+
result?: ArrayBuffer | undefined;
|
|
47
|
+
attempt?: number | undefined;
|
|
48
|
+
maxAttempts?: number | undefined;
|
|
49
|
+
delayMs?: number | undefined;
|
|
50
|
+
success?: boolean | undefined;
|
|
51
|
+
finalError?: string | undefined;
|
|
52
|
+
willRetry?: boolean | undefined;
|
|
53
|
+
messageCount?: number | undefined;
|
|
54
|
+
type: string;
|
|
55
|
+
agentId: string;
|
|
56
|
+
sessionKey: string;
|
|
57
|
+
ts: string;
|
|
58
|
+
day: string;
|
|
59
|
+
ownerId: string;
|
|
60
|
+
}[]>>;
|
|
61
|
+
export declare const findLastError: import("convex/server").RegisteredQuery<"public", {
|
|
62
|
+
limit?: number | undefined;
|
|
63
|
+
ownerId: string;
|
|
64
|
+
}, Promise<{
|
|
65
|
+
_id: import("convex/values").GenericId<"sessionEvents">;
|
|
66
|
+
_creationTime: number;
|
|
67
|
+
toolName?: string | undefined;
|
|
68
|
+
content?: ArrayBuffer | undefined;
|
|
69
|
+
args?: ArrayBuffer | undefined;
|
|
70
|
+
aborted?: boolean | undefined;
|
|
71
|
+
inner?: string | undefined;
|
|
72
|
+
delta?: string | undefined;
|
|
73
|
+
role?: string | undefined;
|
|
74
|
+
stopReason?: string | undefined;
|
|
75
|
+
errorMessage?: string | undefined;
|
|
76
|
+
toolCallId?: string | undefined;
|
|
77
|
+
isError?: boolean | undefined;
|
|
78
|
+
result?: ArrayBuffer | undefined;
|
|
79
|
+
attempt?: number | undefined;
|
|
80
|
+
maxAttempts?: number | undefined;
|
|
81
|
+
delayMs?: number | undefined;
|
|
82
|
+
success?: boolean | undefined;
|
|
83
|
+
finalError?: string | undefined;
|
|
84
|
+
willRetry?: boolean | undefined;
|
|
85
|
+
messageCount?: number | undefined;
|
|
86
|
+
type: string;
|
|
87
|
+
agentId: string;
|
|
88
|
+
sessionKey: string;
|
|
89
|
+
ts: string;
|
|
90
|
+
day: string;
|
|
91
|
+
ownerId: string;
|
|
92
|
+
} | null>>;
|
|
93
|
+
export declare const appendSubsystemRecord: import("convex/server").RegisteredMutation<"public", {
|
|
94
|
+
fields?: any;
|
|
95
|
+
message: string;
|
|
96
|
+
time: string;
|
|
97
|
+
level: "error" | "trace" | "debug" | "info" | "warn" | "fatal";
|
|
98
|
+
subsystem: string;
|
|
99
|
+
day: string;
|
|
100
|
+
ownerId: string;
|
|
101
|
+
}, Promise<void>>;
|
|
102
|
+
export declare const readSubsystemRecords: import("convex/server").RegisteredQuery<"public", {
|
|
103
|
+
level?: "error" | "trace" | "debug" | "info" | "warn" | "fatal" | undefined;
|
|
104
|
+
subsystem?: string | undefined;
|
|
105
|
+
limit?: number | undefined;
|
|
106
|
+
day?: string | undefined;
|
|
107
|
+
ownerId: string;
|
|
108
|
+
}, Promise<{
|
|
109
|
+
_id: import("convex/values").GenericId<"subsystemLog">;
|
|
110
|
+
_creationTime: number;
|
|
111
|
+
fields?: any;
|
|
112
|
+
message: string;
|
|
113
|
+
time: string;
|
|
114
|
+
level: string;
|
|
115
|
+
subsystem: string;
|
|
116
|
+
day: string;
|
|
117
|
+
ownerId: string;
|
|
118
|
+
}[]>>;
|
|
119
|
+
export declare const pruneSubsystemLogs: import("convex/server").RegisteredMutation<"public", {
|
|
120
|
+
ownerId: string;
|
|
121
|
+
olderThanMs: number;
|
|
122
|
+
}, Promise<{
|
|
123
|
+
removed: number;
|
|
124
|
+
}>>;
|
|
125
|
+
export declare const appendConfigAudit: import("convex/server").RegisteredMutation<"public", {
|
|
126
|
+
pid?: number | undefined;
|
|
127
|
+
sha256: string;
|
|
128
|
+
ts: string;
|
|
129
|
+
bytes: number;
|
|
130
|
+
instanceId: string;
|
|
131
|
+
}, Promise<{
|
|
132
|
+
pid?: number | undefined;
|
|
133
|
+
prevHash?: string | undefined;
|
|
134
|
+
instanceId: string;
|
|
135
|
+
ts: string;
|
|
136
|
+
sha256: string;
|
|
137
|
+
bytes: number;
|
|
138
|
+
seq: number;
|
|
139
|
+
lineHash: string;
|
|
140
|
+
}>>;
|
|
141
|
+
export declare const listConfigAudit: import("convex/server").RegisteredQuery<"public", {
|
|
142
|
+
limit?: number | undefined;
|
|
143
|
+
instanceId: string;
|
|
144
|
+
}, Promise<{
|
|
145
|
+
_id: import("convex/values").GenericId<"brigadeConfigAudit">;
|
|
146
|
+
_creationTime: number;
|
|
147
|
+
pid?: number | undefined;
|
|
148
|
+
prevHash?: string | undefined;
|
|
149
|
+
sha256: string;
|
|
150
|
+
ts: string;
|
|
151
|
+
bytes: number;
|
|
152
|
+
instanceId: string;
|
|
153
|
+
lineHash: string;
|
|
154
|
+
seq: number;
|
|
155
|
+
}[]>>;
|
|
156
|
+
export declare const writeConfigHealth: import("convex/server").RegisteredMutation<"public", {
|
|
157
|
+
sha256: string;
|
|
158
|
+
mtimeMs: number;
|
|
159
|
+
ts: string;
|
|
160
|
+
pid: number;
|
|
161
|
+
bytes: number;
|
|
162
|
+
ownerId: string;
|
|
163
|
+
configPath: string;
|
|
164
|
+
}, Promise<void>>;
|
|
165
|
+
export declare const readConfigHealth: import("convex/server").RegisteredQuery<"public", {
|
|
166
|
+
ownerId: string;
|
|
167
|
+
}, Promise<{
|
|
168
|
+
_id: import("convex/values").GenericId<"configHealth">;
|
|
169
|
+
_creationTime: number;
|
|
170
|
+
sha256: string;
|
|
171
|
+
mtimeMs: number;
|
|
172
|
+
ts: string;
|
|
173
|
+
pid: number;
|
|
174
|
+
bytes: number;
|
|
175
|
+
ownerId: string;
|
|
176
|
+
configPath: string;
|
|
177
|
+
} | null>>;
|
|
178
|
+
//# sourceMappingURL=logs.d.ts.map
|
package/convex/logs.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// convex/logs.ts — sessionEvents + subsystemLog + brigadeConfigAudit + configHealth
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
import { mutation, query } from "./_generated/server.js";
|
|
4
|
+
|
|
5
|
+
const Level = v.union(
|
|
6
|
+
v.literal("trace"),
|
|
7
|
+
v.literal("debug"),
|
|
8
|
+
v.literal("info"),
|
|
9
|
+
v.literal("warn"),
|
|
10
|
+
v.literal("error"),
|
|
11
|
+
v.literal("fatal"),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// sessionEvents (Pi session events)
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
export const appendSessionEvent = mutation({
|
|
19
|
+
args: {
|
|
20
|
+
ts: v.string(),
|
|
21
|
+
day: v.string(),
|
|
22
|
+
ownerId: v.string(),
|
|
23
|
+
agentId: v.string(),
|
|
24
|
+
sessionKey: v.string(),
|
|
25
|
+
type: v.string(),
|
|
26
|
+
inner: v.optional(v.string()),
|
|
27
|
+
delta: v.optional(v.string()),
|
|
28
|
+
toolCallId: v.optional(v.string()),
|
|
29
|
+
toolName: v.optional(v.string()),
|
|
30
|
+
args: v.optional(v.bytes()),
|
|
31
|
+
result: v.optional(v.bytes()),
|
|
32
|
+
isError: v.optional(v.boolean()),
|
|
33
|
+
role: v.optional(v.string()),
|
|
34
|
+
content: v.optional(v.bytes()),
|
|
35
|
+
stopReason: v.optional(v.string()),
|
|
36
|
+
errorMessage: v.optional(v.string()),
|
|
37
|
+
attempt: v.optional(v.number()),
|
|
38
|
+
maxAttempts: v.optional(v.number()),
|
|
39
|
+
delayMs: v.optional(v.number()),
|
|
40
|
+
aborted: v.optional(v.boolean()),
|
|
41
|
+
willRetry: v.optional(v.boolean()),
|
|
42
|
+
messageCount: v.optional(v.number()),
|
|
43
|
+
success: v.optional(v.boolean()),
|
|
44
|
+
finalError: v.optional(v.string()),
|
|
45
|
+
},
|
|
46
|
+
handler: async (ctx, args) => {
|
|
47
|
+
await ctx.db.insert("sessionEvents", args);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const readSessionEventTail = query({
|
|
52
|
+
args: { ownerId: v.string(), day: v.optional(v.string()), limit: v.optional(v.number()) },
|
|
53
|
+
handler: async (ctx, args) => {
|
|
54
|
+
const day = args.day ?? new Date().toISOString().slice(0, 10);
|
|
55
|
+
const limit = args.limit && args.limit > 0 ? args.limit : 200;
|
|
56
|
+
// Take the most-recent `limit` (desc) but RETURN them chronological
|
|
57
|
+
// (oldest-first) to match the disk reader (event-logger.readSession-
|
|
58
|
+
// EventTail walks the file tail in append order). A consumer replaying
|
|
59
|
+
// the tail (e.g. convex→filesystem migrate) would otherwise reverse it.
|
|
60
|
+
const rows = await ctx.db
|
|
61
|
+
.query("sessionEvents")
|
|
62
|
+
.withIndex("by_owner_day", (q) => q.eq("ownerId", args.ownerId).eq("day", day))
|
|
63
|
+
.order("desc")
|
|
64
|
+
.take(limit);
|
|
65
|
+
return rows.reverse();
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const findLastError = query({
|
|
70
|
+
args: { ownerId: v.string(), limit: v.optional(v.number()) },
|
|
71
|
+
handler: async (ctx, args) => {
|
|
72
|
+
// Mirror the disk `getLastLoggedError` scan: the most recent error is not
|
|
73
|
+
// always a tool error (isError) — it can be an auto_retry_start with an
|
|
74
|
+
// errorMessage or an aborted compaction_end. Scan today's events
|
|
75
|
+
// newest-first and return the first error-shaped row. Falls back to the
|
|
76
|
+
// isError index for older days when today has none.
|
|
77
|
+
const day = new Date().toISOString().slice(0, 10);
|
|
78
|
+
const scan = args.limit && args.limit > 0 ? args.limit : 400;
|
|
79
|
+
const recent = await ctx.db
|
|
80
|
+
.query("sessionEvents")
|
|
81
|
+
.withIndex("by_owner_day", (q) => q.eq("ownerId", args.ownerId).eq("day", day))
|
|
82
|
+
.order("desc")
|
|
83
|
+
.take(scan);
|
|
84
|
+
for (const row of recent) {
|
|
85
|
+
if (row.type === "tool_execution_end" && row.isError === true) return row;
|
|
86
|
+
if (row.type === "auto_retry_start" && typeof row.errorMessage === "string") return row;
|
|
87
|
+
if (row.type === "compaction_end" && row.aborted === true && typeof row.errorMessage === "string") {
|
|
88
|
+
return row;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Nothing today — fall back to the newest tool error across all days.
|
|
92
|
+
return ctx.db
|
|
93
|
+
.query("sessionEvents")
|
|
94
|
+
.withIndex("by_owner_error", (q) => q.eq("ownerId", args.ownerId).eq("isError", true))
|
|
95
|
+
.order("desc")
|
|
96
|
+
.first();
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// subsystemLog
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
104
|
+
export const appendSubsystemRecord = mutation({
|
|
105
|
+
args: {
|
|
106
|
+
time: v.string(),
|
|
107
|
+
day: v.string(),
|
|
108
|
+
ownerId: v.string(),
|
|
109
|
+
level: Level,
|
|
110
|
+
subsystem: v.string(),
|
|
111
|
+
message: v.string(),
|
|
112
|
+
fields: v.optional(v.any()),
|
|
113
|
+
},
|
|
114
|
+
handler: async (ctx, args) => {
|
|
115
|
+
await ctx.db.insert("subsystemLog", args);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export const readSubsystemRecords = query({
|
|
120
|
+
args: {
|
|
121
|
+
ownerId: v.string(),
|
|
122
|
+
day: v.optional(v.string()),
|
|
123
|
+
level: v.optional(Level),
|
|
124
|
+
subsystem: v.optional(v.string()),
|
|
125
|
+
limit: v.optional(v.number()),
|
|
126
|
+
},
|
|
127
|
+
handler: async (ctx, args) => {
|
|
128
|
+
const limit = args.limit && args.limit > 0 ? args.limit : 200;
|
|
129
|
+
const day = args.day ?? new Date().toISOString().slice(0, 10);
|
|
130
|
+
let rows = await ctx.db
|
|
131
|
+
.query("subsystemLog")
|
|
132
|
+
.withIndex("by_owner_day", (q) => q.eq("ownerId", args.ownerId).eq("day", day))
|
|
133
|
+
.order("desc")
|
|
134
|
+
.take(limit * 4);
|
|
135
|
+
if (args.level) rows = rows.filter((r) => r.level === args.level);
|
|
136
|
+
if (args.subsystem) rows = rows.filter((r) => r.subsystem === args.subsystem);
|
|
137
|
+
return rows.slice(0, limit);
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export const pruneSubsystemLogs = mutation({
|
|
142
|
+
args: { ownerId: v.string(), olderThanMs: v.number() },
|
|
143
|
+
handler: async (ctx, args) => {
|
|
144
|
+
const cutoff = new Date(Date.now() - args.olderThanMs).toISOString();
|
|
145
|
+
// Prune ONE bounded page of expired rows; the client loops until
|
|
146
|
+
// {removed:0}. `.collect()` of the whole (unbounded, high-volume)
|
|
147
|
+
// subsystemLog blows the 16 MiB read + 8k-delete caps once it's large —
|
|
148
|
+
// which is exactly when prune runs. Expired rows sort FIRST on
|
|
149
|
+
// by_owner_day (oldest day first), so each page makes progress until
|
|
150
|
+
// only kept rows remain. Page size is small + count-bounded (log rows
|
|
151
|
+
// are tiny) to stay under the read cap. Lossless.
|
|
152
|
+
const rows = await ctx.db
|
|
153
|
+
.query("subsystemLog")
|
|
154
|
+
.withIndex("by_owner_day", (q) => q.eq("ownerId", args.ownerId))
|
|
155
|
+
.order("asc")
|
|
156
|
+
.take(500);
|
|
157
|
+
let removed = 0;
|
|
158
|
+
for (const r of rows) {
|
|
159
|
+
if (r.time < cutoff) {
|
|
160
|
+
await ctx.db.delete(r._id);
|
|
161
|
+
removed += 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { removed };
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// brigadeConfigAudit (hash-chained)
|
|
170
|
+
// ============================================================================
|
|
171
|
+
|
|
172
|
+
export const appendConfigAudit = mutation({
|
|
173
|
+
args: {
|
|
174
|
+
instanceId: v.string(),
|
|
175
|
+
ts: v.string(),
|
|
176
|
+
sha256: v.string(),
|
|
177
|
+
bytes: v.number(),
|
|
178
|
+
pid: v.optional(v.number()),
|
|
179
|
+
},
|
|
180
|
+
handler: async (ctx, args) => {
|
|
181
|
+
const tail = await ctx.db
|
|
182
|
+
.query("brigadeConfigAudit")
|
|
183
|
+
.withIndex("by_instance_seq", (q) => q.eq("instanceId", args.instanceId))
|
|
184
|
+
.order("desc")
|
|
185
|
+
.first();
|
|
186
|
+
const seq = (tail?.seq ?? 0) + 1;
|
|
187
|
+
const prevHash = tail?.lineHash;
|
|
188
|
+
const lineHashInput = `${args.instanceId}|${args.ts}|${args.sha256}|${args.bytes}|${seq}|${prevHash ?? ""}`;
|
|
189
|
+
const enc = new TextEncoder().encode(lineHashInput);
|
|
190
|
+
const buf = await crypto.subtle.digest("SHA-256", enc);
|
|
191
|
+
const lineHash = Array.from(new Uint8Array(buf))
|
|
192
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
193
|
+
.join("");
|
|
194
|
+
const payload = {
|
|
195
|
+
instanceId: args.instanceId,
|
|
196
|
+
ts: args.ts,
|
|
197
|
+
sha256: args.sha256,
|
|
198
|
+
bytes: args.bytes,
|
|
199
|
+
seq,
|
|
200
|
+
lineHash,
|
|
201
|
+
...(prevHash !== undefined ? { prevHash } : {}),
|
|
202
|
+
...(args.pid !== undefined ? { pid: args.pid } : {}),
|
|
203
|
+
};
|
|
204
|
+
await ctx.db.insert("brigadeConfigAudit", payload);
|
|
205
|
+
return payload;
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
export const listConfigAudit = query({
|
|
210
|
+
args: { instanceId: v.string(), limit: v.optional(v.number()) },
|
|
211
|
+
handler: async (ctx, args) => {
|
|
212
|
+
const limit = args.limit && args.limit > 0 ? args.limit : 100;
|
|
213
|
+
return ctx.db
|
|
214
|
+
.query("brigadeConfigAudit")
|
|
215
|
+
.withIndex("by_instance_seq", (q) => q.eq("instanceId", args.instanceId))
|
|
216
|
+
.order("asc")
|
|
217
|
+
.take(limit);
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// ============================================================================
|
|
222
|
+
// configHealth
|
|
223
|
+
// ============================================================================
|
|
224
|
+
|
|
225
|
+
export const writeConfigHealth = mutation({
|
|
226
|
+
args: {
|
|
227
|
+
ownerId: v.string(),
|
|
228
|
+
ts: v.string(),
|
|
229
|
+
configPath: v.string(),
|
|
230
|
+
bytes: v.number(),
|
|
231
|
+
sha256: v.string(),
|
|
232
|
+
mtimeMs: v.number(),
|
|
233
|
+
pid: v.number(),
|
|
234
|
+
},
|
|
235
|
+
handler: async (ctx, args) => {
|
|
236
|
+
const existing = await ctx.db
|
|
237
|
+
.query("configHealth")
|
|
238
|
+
.withIndex("by_owner", (q) => q.eq("ownerId", args.ownerId))
|
|
239
|
+
.first();
|
|
240
|
+
if (existing) await ctx.db.replace(existing._id, args);
|
|
241
|
+
else await ctx.db.insert("configHealth", args);
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
export const readConfigHealth = query({
|
|
246
|
+
args: { ownerId: v.string() },
|
|
247
|
+
handler: async (ctx, args) => {
|
|
248
|
+
return ctx.db
|
|
249
|
+
.query("configHealth")
|
|
250
|
+
.withIndex("by_owner", (q) => q.eq("ownerId", args.ownerId))
|
|
251
|
+
.first();
|
|
252
|
+
},
|
|
253
|
+
});
|