ocwatch 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -3
- package/package.json +4 -4
- package/src/client/dist/assets/GraphView-BZV40eAE.css +1 -0
- package/src/client/dist/assets/GraphView-KWCCGYb2.js +9 -0
- package/src/client/dist/assets/graph-Cw_XSlvx.js +7 -0
- package/src/client/dist/assets/index-CbgYG3pJ.js +23 -0
- package/src/client/dist/assets/index-CgDCc8Mm.css +1 -0
- package/src/client/dist/assets/motion-CGUGF2CN.js +9 -0
- package/src/client/dist/index.html +4 -2
- package/src/server/__tests__/helpers/testDb.ts +220 -0
- package/src/server/index.ts +27 -27
- package/src/server/logic/activityLogic.ts +260 -0
- package/src/server/logic/index.ts +2 -0
- package/src/server/logic/sessionLogic.ts +107 -0
- package/src/server/routes/parts.ts +9 -7
- package/src/server/routes/poll.ts +32 -46
- package/src/server/routes/projects.ts +10 -27
- package/src/server/routes/sessions.ts +159 -68
- package/src/server/routes/sse.ts +10 -4
- package/src/server/services/parsing.ts +211 -0
- package/src/server/services/pollService.ts +400 -116
- package/src/server/services/recentSessions.ts +14 -0
- package/src/server/services/sessionContext.ts +97 -0
- package/src/server/services/sessionService.ts +97 -193
- package/src/server/services/sessionTree.ts +92 -0
- package/src/server/storage/db.ts +63 -0
- package/src/server/storage/index.ts +28 -0
- package/src/server/storage/queries.ts +528 -0
- package/src/server/utils/projectResolver.ts +9 -3
- package/src/server/utils/sessionStatus.ts +5 -89
- package/src/server/validation.ts +2 -4
- package/src/server/watcher.ts +225 -82
- package/src/shared/constants.ts +8 -3
- package/src/shared/index.ts +3 -0
- package/src/shared/types/index.ts +48 -53
- package/src/shared/utils/activityUtils.ts +3 -2
- package/src/client/dist/assets/index-BIu7r5_5.css +0 -1
- package/src/client/dist/assets/index-BYMVif3u.js +0 -50
- package/src/server/storage/messageParser.ts +0 -169
- package/src/server/storage/partParser.ts +0 -532
- package/src/server/storage/sessionParser.ts +0 -180
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared parsing functions for converting database rows to domain types.
|
|
3
|
+
*
|
|
4
|
+
* This module is the SINGLE SOURCE OF TRUTH for DB row → domain object mapping.
|
|
5
|
+
* Both pollService and sessionService import from here — never duplicate these functions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
SessionMetadata,
|
|
10
|
+
MessageMeta,
|
|
11
|
+
PartMeta,
|
|
12
|
+
} from "../../shared/types";
|
|
13
|
+
import type { DbSessionRow, DbMessageRow, DbPartRow } from "../storage/queries";
|
|
14
|
+
import { isPendingToolCall } from "../logic";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// JSON shapes (internal to this module)
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
interface MessageJSON {
|
|
21
|
+
role?: string;
|
|
22
|
+
agent?: string;
|
|
23
|
+
mode?: string;
|
|
24
|
+
modelID?: string;
|
|
25
|
+
providerID?: string;
|
|
26
|
+
model?: {
|
|
27
|
+
modelID?: string;
|
|
28
|
+
providerID?: string;
|
|
29
|
+
};
|
|
30
|
+
parentID?: string;
|
|
31
|
+
cost?: number;
|
|
32
|
+
tokens?: {
|
|
33
|
+
input?: number;
|
|
34
|
+
output?: number;
|
|
35
|
+
};
|
|
36
|
+
finish?: string;
|
|
37
|
+
time?: {
|
|
38
|
+
created?: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface PartStateJSON {
|
|
43
|
+
status?: string;
|
|
44
|
+
input?: Record<string, unknown>;
|
|
45
|
+
output?: string;
|
|
46
|
+
error?: string;
|
|
47
|
+
title?: string;
|
|
48
|
+
time?: {
|
|
49
|
+
start?: number;
|
|
50
|
+
end?: number;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface PartJSON {
|
|
55
|
+
type?: string;
|
|
56
|
+
callID?: string;
|
|
57
|
+
tool?: string;
|
|
58
|
+
state?: string | PartStateJSON;
|
|
59
|
+
text?: string;
|
|
60
|
+
title?: string;
|
|
61
|
+
reason?: string;
|
|
62
|
+
files?: unknown;
|
|
63
|
+
input?: Record<string, unknown>;
|
|
64
|
+
time?: {
|
|
65
|
+
start?: number;
|
|
66
|
+
end?: number;
|
|
67
|
+
};
|
|
68
|
+
snapshot?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Helpers
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
export function parseJsonData<T>(raw: unknown): T | null {
|
|
76
|
+
if (typeof raw === "string") {
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(raw) as T;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (raw && typeof raw === "object") {
|
|
85
|
+
return raw as T;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function toStringOrUndefined(value: unknown): string | undefined {
|
|
92
|
+
return typeof value === "string" ? value : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function toDate(value: unknown): Date | undefined {
|
|
96
|
+
return typeof value === "number" ? new Date(value) : undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function toStringArrayOrUndefined(value: unknown): string[] | undefined {
|
|
100
|
+
if (!Array.isArray(value)) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const items = value.filter((item): item is string => typeof item === "string");
|
|
104
|
+
return items.length > 0 ? items : undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Row → Domain converters
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
export function toSessionMetadata(row: DbSessionRow): SessionMetadata {
|
|
112
|
+
return {
|
|
113
|
+
id: row.id,
|
|
114
|
+
projectID: row.projectID,
|
|
115
|
+
directory: row.directory,
|
|
116
|
+
title: row.title,
|
|
117
|
+
parentID: row.parentID ?? undefined,
|
|
118
|
+
createdAt: new Date(row.timeCreated),
|
|
119
|
+
updatedAt: new Date(row.timeUpdated),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function toMessageMeta(row: DbMessageRow): MessageMeta {
|
|
124
|
+
const json = parseJsonData<MessageJSON>(row.data) ?? {};
|
|
125
|
+
const tokenInput = json.tokens?.input;
|
|
126
|
+
const tokenOutput = json.tokens?.output;
|
|
127
|
+
const hasTokens = typeof tokenInput === "number" || typeof tokenOutput === "number";
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
id: row.id,
|
|
131
|
+
sessionID: row.sessionID,
|
|
132
|
+
role: json.role ?? row.role ?? "unknown",
|
|
133
|
+
agent: toStringOrUndefined(json.agent),
|
|
134
|
+
mode: toStringOrUndefined(json.mode),
|
|
135
|
+
modelID: toStringOrUndefined(json.modelID) ?? toStringOrUndefined(json.model?.modelID),
|
|
136
|
+
providerID: toStringOrUndefined(json.providerID) ?? toStringOrUndefined(json.model?.providerID),
|
|
137
|
+
parentID: toStringOrUndefined(json.parentID),
|
|
138
|
+
tokens: hasTokens ? (tokenInput ?? 0) + (tokenOutput ?? 0) : undefined,
|
|
139
|
+
cost: typeof json.cost === "number" ? json.cost : undefined,
|
|
140
|
+
createdAt: new Date(json.time?.created ?? row.timeCreated),
|
|
141
|
+
finish: toStringOrUndefined(json.finish),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function toPartMeta(row: DbPartRow): PartMeta {
|
|
146
|
+
const json = parseJsonData<PartJSON>(row.data) ?? {};
|
|
147
|
+
|
|
148
|
+
const stateObject = (typeof json.state === "object" && json.state !== null)
|
|
149
|
+
? json.state as PartStateJSON
|
|
150
|
+
: undefined;
|
|
151
|
+
|
|
152
|
+
const state = toStringOrUndefined(json.state) ?? toStringOrUndefined(stateObject?.status) ?? row.state ?? undefined;
|
|
153
|
+
|
|
154
|
+
const nestedInput = (stateObject?.input && typeof stateObject.input === "object")
|
|
155
|
+
? stateObject.input as Record<string, unknown>
|
|
156
|
+
: undefined;
|
|
157
|
+
const rootInput = (json.input && typeof json.input === "object")
|
|
158
|
+
? json.input as Record<string, unknown>
|
|
159
|
+
: undefined;
|
|
160
|
+
const input = nestedInput ?? rootInput;
|
|
161
|
+
|
|
162
|
+
const title = toStringOrUndefined(stateObject?.title) ?? toStringOrUndefined(json.title);
|
|
163
|
+
|
|
164
|
+
const time = (json.time && typeof json.time === "object") ? json.time : stateObject?.time;
|
|
165
|
+
const startedAt = toDate(time?.start);
|
|
166
|
+
const completedAt = toDate(time?.end);
|
|
167
|
+
|
|
168
|
+
const errorRaw = toStringOrUndefined(stateObject?.error) ?? toStringOrUndefined(stateObject?.output);
|
|
169
|
+
const error = (state === "error" || state === "failed") && errorRaw
|
|
170
|
+
? errorRaw.slice(0, 500)
|
|
171
|
+
: undefined;
|
|
172
|
+
|
|
173
|
+
const reason = json.reason;
|
|
174
|
+
const stepFinishReason = reason === "stop" || reason === "tool-calls" ? reason : undefined;
|
|
175
|
+
const type = toStringOrUndefined(json.type) ?? row.type ?? "unknown";
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
id: row.id,
|
|
179
|
+
sessionID: row.sessionID,
|
|
180
|
+
messageID: row.messageID,
|
|
181
|
+
type,
|
|
182
|
+
callID: toStringOrUndefined(json.callID),
|
|
183
|
+
tool: toStringOrUndefined(json.tool) ?? row.tool ?? undefined,
|
|
184
|
+
state,
|
|
185
|
+
input,
|
|
186
|
+
title,
|
|
187
|
+
error,
|
|
188
|
+
startedAt,
|
|
189
|
+
completedAt,
|
|
190
|
+
stepSnapshot: toStringOrUndefined(json.snapshot),
|
|
191
|
+
stepFinishReason,
|
|
192
|
+
reasoningText: type === "reasoning" ? toStringOrUndefined(json.text) : undefined,
|
|
193
|
+
patchFiles: toStringArrayOrUndefined(json.files),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Common query helpers (used by both pollService and sessionService)
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
export function getLatestAssistantMessage(messages: MessageMeta[]): MessageMeta | undefined {
|
|
202
|
+
return messages
|
|
203
|
+
.filter((message) => message.role === "assistant")
|
|
204
|
+
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())[0];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function getMostRecentPendingPart(parts: PartMeta[]): PartMeta | undefined {
|
|
208
|
+
return parts
|
|
209
|
+
.filter((part) => isPendingToolCall(part))
|
|
210
|
+
.sort((a, b) => (b.startedAt?.getTime() || 0) - (a.startedAt?.getTime() || 0))[0];
|
|
211
|
+
}
|