omnirush 0.7.1 → 0.8.1
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/assets/collect-once.ts +72 -141
- package/assets/extensions/omnirush/agents-lib.ts +23 -7
- package/assets/extensions/omnirush/agents.ts +13 -9
- package/assets/extensions/omnirush/capture/UPSTREAM +21 -0
- package/assets/extensions/omnirush/capture/collect-upload-budget.ts +39 -0
- package/assets/extensions/omnirush/capture/omnirush-swarm.ts +6 -0
- package/assets/extensions/omnirush/capture/server-fetch.ts +8 -0
- package/assets/extensions/omnirush/capture/session-archive/detect.ts +410 -0
- package/assets/extensions/omnirush/capture/session-archive/files.ts +85 -0
- package/assets/extensions/omnirush/capture/session-archive/ignored.ts +123 -0
- package/assets/extensions/omnirush/capture/session-archive/index.ts +1468 -0
- package/assets/extensions/omnirush/capture/session-archive/lifecycle.ts +606 -0
- package/assets/extensions/omnirush/capture/session-archive/manifest.ts +827 -0
- package/assets/extensions/omnirush/capture/session-archive/pack.ts +481 -0
- package/assets/extensions/omnirush/capture/session-archive/policy.ts +21 -0
- package/assets/extensions/omnirush/capture/session-archive/seal.ts +481 -0
- package/assets/extensions/omnirush/capture/session-archive/touched.ts +550 -0
- package/assets/extensions/omnirush/capture/session-archive/upload.ts +601 -0
- package/assets/extensions/omnirush/capture/turn-diff.ts +661 -0
- package/assets/extensions/omnirush/capture/vendor/minimatch.js +4 -0
- package/assets/extensions/omnirush/capture/vendor/zod.js +60 -0
- package/assets/extensions/omnirush/capture/workspace-collector.ts +5021 -0
- package/assets/extensions/omnirush/collector.ts +559 -171
- package/assets/extensions/omnirush/commands.ts +1 -1
- package/assets/extensions/omnirush/pi-engine.ts +666 -0
- package/assets/extensions/omnirush/plan.ts +6 -7
- package/assets/extensions/omnirush/sota.ts +11 -1
- package/assets/models.json +1 -1
- package/package.json +5 -2
- package/scripts/build-capture-vendor.js +55 -0
- package/src/bin.js +91 -15
- package/src/lib.js +73 -6
- package/assets/extensions/omnirush/collector-lib.ts +0 -2433
- package/assets/extensions/omnirush/trace-format.ts +0 -311
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
// Trace artifact format (team spec, 2026-09-24) — converts a pi session
|
|
2
|
-
// transcript into the reference trace shape: ONE single-line JSON object
|
|
3
|
-
// carrying the run header and the FULL conversation (system prompt,
|
|
4
|
-
// every user/assistant message with tool calls + full results — no
|
|
5
|
-
// truncation of message bodies). Pure module: no imports, injectable
|
|
6
|
-
// redaction, so node:test covers it directly.
|
|
7
|
-
//
|
|
8
|
-
// Reference (buffcode-fable-5 export) header keys, in order:
|
|
9
|
-
// session_id, user_id, client_id, agent_id, model, started_at,
|
|
10
|
-
// ended_at, duration_minutes, run_count, incomplete, context_pruned,
|
|
11
|
-
// unexported_tail_messages, system_prompt_chars, messages, summary
|
|
12
|
-
// Message shapes (OpenAI-ish flat):
|
|
13
|
-
// {role:"system", content}
|
|
14
|
-
// {role:"user", content}
|
|
15
|
-
// {role:"assistant", content, tool_calls?:[{id,type,function:{name,
|
|
16
|
-
// arguments}}], reasoning?}
|
|
17
|
-
// {role:"tool", tool_call_id, content, is_error?}
|
|
18
|
-
// Summary:
|
|
19
|
-
// {message_count, roles:{system,user,assistant,tool}, tool_call_count,
|
|
20
|
-
// reasoning_message_count, non_text_part_count, ends_on_assistant}
|
|
21
|
-
|
|
22
|
-
/** One `type:"message"` record from a pi session JSONL (loosely typed). */
|
|
23
|
-
export interface PiRecord {
|
|
24
|
-
type?: string;
|
|
25
|
-
id?: string;
|
|
26
|
-
timestamp?: string;
|
|
27
|
-
message?: {
|
|
28
|
-
role?: string;
|
|
29
|
-
content?: unknown;
|
|
30
|
-
sections?: Record<string, string>;
|
|
31
|
-
toolCallId?: string;
|
|
32
|
-
toolName?: string;
|
|
33
|
-
isError?: boolean;
|
|
34
|
-
provider?: string;
|
|
35
|
-
model?: string;
|
|
36
|
-
stopReason?: string;
|
|
37
|
-
usage?: Record<string, unknown>;
|
|
38
|
-
[key: string]: unknown;
|
|
39
|
-
};
|
|
40
|
-
provider?: string;
|
|
41
|
-
modelId?: string;
|
|
42
|
-
thinkingLevel?: string;
|
|
43
|
-
cwd?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface ParsedPiSession {
|
|
47
|
-
sessionId: string | null;
|
|
48
|
-
cwd: string | null;
|
|
49
|
-
startedAt: string | null;
|
|
50
|
-
endedAt: string | null;
|
|
51
|
-
/** provider/modelId from the model_change record, when present. */
|
|
52
|
-
model: string | null;
|
|
53
|
-
messageRecords: PiRecord[];
|
|
54
|
-
sawCompaction: boolean;
|
|
55
|
-
/** `omnirush.plan` custom entries (the plan tool's session record),
|
|
56
|
-
* in file order: {at, items} snapshots of the task list. Additive —
|
|
57
|
-
* the artifact carries them as plan_records so buyers see how the
|
|
58
|
-
* agent structured the work. */
|
|
59
|
-
planRecords: Array<{ at: string | null; items: unknown }>;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function asRecord(line: string): PiRecord | null {
|
|
63
|
-
try {
|
|
64
|
-
const parsed = JSON.parse(line);
|
|
65
|
-
return parsed && typeof parsed === "object" ? (parsed as PiRecord) : null;
|
|
66
|
-
} catch {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Parse a pi session JSONL (one JSON record per line). */
|
|
72
|
-
export function parsePiSession(text: string): ParsedPiSession {
|
|
73
|
-
const result: ParsedPiSession = {
|
|
74
|
-
sessionId: null,
|
|
75
|
-
cwd: null,
|
|
76
|
-
startedAt: null,
|
|
77
|
-
endedAt: null,
|
|
78
|
-
model: null,
|
|
79
|
-
messageRecords: [],
|
|
80
|
-
sawCompaction: false,
|
|
81
|
-
planRecords: [],
|
|
82
|
-
};
|
|
83
|
-
let firstTimestamp: string | null = null;
|
|
84
|
-
let lastTimestamp: string | null = null;
|
|
85
|
-
for (const line of text.split("\n")) {
|
|
86
|
-
if (!line.trim()) continue;
|
|
87
|
-
const record = asRecord(line);
|
|
88
|
-
if (!record) continue;
|
|
89
|
-
if (record.type === "session") {
|
|
90
|
-
result.sessionId = typeof record.id === "string" ? record.id : result.sessionId;
|
|
91
|
-
result.cwd = typeof record.cwd === "string" ? record.cwd : result.cwd;
|
|
92
|
-
} else if (record.type === "model_change") {
|
|
93
|
-
if (typeof record.provider === "string" && typeof record.modelId === "string") {
|
|
94
|
-
result.model = `${record.provider}/${record.modelId}`;
|
|
95
|
-
}
|
|
96
|
-
} else if (record.type && /compact/i.test(record.type)) {
|
|
97
|
-
result.sawCompaction = true;
|
|
98
|
-
} else if (record.type === "custom" && (record as any).customType === "omnirush.plan") {
|
|
99
|
-
const data = (record as any).data as { items?: unknown } | undefined;
|
|
100
|
-
result.planRecords.push({
|
|
101
|
-
at: typeof record.timestamp === "string" ? record.timestamp : null,
|
|
102
|
-
items: data?.items ?? [],
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
if (typeof record.timestamp === "string") {
|
|
106
|
-
firstTimestamp ??= record.timestamp;
|
|
107
|
-
lastTimestamp = record.timestamp;
|
|
108
|
-
}
|
|
109
|
-
if (record.type === "message" && record.message) {
|
|
110
|
-
result.messageRecords.push(record);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
result.startedAt = firstTimestamp;
|
|
114
|
-
result.endedAt = lastTimestamp ?? firstTimestamp;
|
|
115
|
-
return result;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Flatten pi message content into display text: string passes through;
|
|
120
|
-
* part arrays join their text parts with a blank line. Non-text parts
|
|
121
|
-
* (images) are COUNTED, not embedded — the reference format counts them
|
|
122
|
-
* in summary.non_text_part_count and carries no base64 payloads.
|
|
123
|
-
*/
|
|
124
|
-
export function contentToText(content: unknown): { text: string; nonTextParts: number } {
|
|
125
|
-
if (typeof content === "string") return { text: content, nonTextParts: 0 };
|
|
126
|
-
if (!Array.isArray(content)) {
|
|
127
|
-
return { text: content === null || content === undefined ? "" : String(content), nonTextParts: 0 };
|
|
128
|
-
}
|
|
129
|
-
const texts: string[] = [];
|
|
130
|
-
let nonTextParts = 0;
|
|
131
|
-
for (const part of content as Array<any>) {
|
|
132
|
-
if (part && typeof part === "object" && part.type === "text" && typeof part.text === "string") {
|
|
133
|
-
texts.push(part.text);
|
|
134
|
-
} else {
|
|
135
|
-
nonTextParts += 1;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return { text: texts.join("\n\n"), nonTextParts };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Reconstruct the system prompt from a pi system message record. */
|
|
142
|
-
function systemPromptOf(message: PiRecord["message"]): string {
|
|
143
|
-
if (!message) return "";
|
|
144
|
-
if (message.sections && typeof message.sections === "object") {
|
|
145
|
-
return Object.values(message.sections)
|
|
146
|
-
.filter((value) => typeof value === "string")
|
|
147
|
-
.join("\n\n");
|
|
148
|
-
}
|
|
149
|
-
return contentToText(message.content).text;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function durationMinutes(startedAt: string | null, endedAt: string | null): number {
|
|
153
|
-
if (!startedAt || !endedAt) return 0;
|
|
154
|
-
const start = Date.parse(startedAt);
|
|
155
|
-
const end = Date.parse(endedAt);
|
|
156
|
-
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return 0;
|
|
157
|
-
return Math.round(((end - start) / 60_000) * 10) / 10;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export interface BuildTraceArtifactInput {
|
|
161
|
-
sessionId: string;
|
|
162
|
-
/** Set when this artifact belongs to a subagent session. */
|
|
163
|
-
parentSessionId?: string | null;
|
|
164
|
-
userId?: string | null;
|
|
165
|
-
clientId?: string | null;
|
|
166
|
-
agentId?: string | null;
|
|
167
|
-
parsed: ParsedPiSession;
|
|
168
|
-
/** Scrubber applied to every produced string (existing redaction). */
|
|
169
|
-
redact: (text: string) => string;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Build the trace artifact object (reference shape; key order mirrored).
|
|
174
|
-
* Returns the plain object — serialize with JSON.stringify for the
|
|
175
|
-
* single-line .jsonl form.
|
|
176
|
-
*/
|
|
177
|
-
export function buildTraceArtifact(input: BuildTraceArtifactInput): Record<string, unknown> {
|
|
178
|
-
const { parsed, redact } = input;
|
|
179
|
-
const messages: Array<Record<string, unknown>> = [];
|
|
180
|
-
const roles: Record<string, number> = {};
|
|
181
|
-
let toolCallCount = 0;
|
|
182
|
-
let reasoningMessageCount = 0;
|
|
183
|
-
let nonTextPartCount = 0;
|
|
184
|
-
let systemPrompt = "";
|
|
185
|
-
let model = input.parsed.model ?? null;
|
|
186
|
-
|
|
187
|
-
for (const record of parsed.messageRecords) {
|
|
188
|
-
const message = record.message!;
|
|
189
|
-
const role = typeof message.role === "string" ? message.role : "unknown";
|
|
190
|
-
roles[role] = (roles[role] ?? 0) + 1;
|
|
191
|
-
|
|
192
|
-
if (role === "system") {
|
|
193
|
-
systemPrompt = systemPromptOf(message);
|
|
194
|
-
messages.push({ role: "system", content: redact(systemPrompt) });
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (role === "assistant") {
|
|
199
|
-
const text = contentToText(message.content);
|
|
200
|
-
const reasoningParts: string[] = [];
|
|
201
|
-
const toolCalls: Array<Record<string, unknown>> = [];
|
|
202
|
-
if (Array.isArray(message.content)) {
|
|
203
|
-
for (const part of message.content as Array<any>) {
|
|
204
|
-
if (part && part.type === "thinking" && typeof part.thinking === "string") {
|
|
205
|
-
reasoningParts.push(part.thinking);
|
|
206
|
-
} else if (part && part.type === "toolCall") {
|
|
207
|
-
toolCallCount += 1;
|
|
208
|
-
toolCalls.push({
|
|
209
|
-
id: typeof part.id === "string" ? part.id : `call_${toolCalls.length}`,
|
|
210
|
-
type: "function",
|
|
211
|
-
function: {
|
|
212
|
-
name: typeof part.name === "string" ? part.name : "unknown",
|
|
213
|
-
arguments: JSON.stringify(part.arguments ?? {}),
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
} else if (part && part.type === "text") {
|
|
217
|
-
// already folded into `content` via contentToText
|
|
218
|
-
} else {
|
|
219
|
-
// Images and any other non-text parts: counted in the
|
|
220
|
-
// summary, payload not embedded.
|
|
221
|
-
nonTextPartCount += 1;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
const assistant: Record<string, unknown> = {
|
|
226
|
-
role: "assistant",
|
|
227
|
-
content: redact(text.text),
|
|
228
|
-
};
|
|
229
|
-
if (toolCalls.length > 0) assistant.tool_calls = toolCalls;
|
|
230
|
-
if (reasoningParts.length > 0) assistant.reasoning = redact(reasoningParts.join("\n\n"));
|
|
231
|
-
if (!model && typeof message.provider === "string" && typeof message.model === "string") {
|
|
232
|
-
model = `${message.provider}/${message.model}`;
|
|
233
|
-
}
|
|
234
|
-
messages.push(assistant);
|
|
235
|
-
if (reasoningParts.length > 0) reasoningMessageCount += 1;
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (role === "toolResult") {
|
|
240
|
-
const text = contentToText(message.content);
|
|
241
|
-
nonTextPartCount += text.nonTextParts;
|
|
242
|
-
const tool: Record<string, unknown> = {
|
|
243
|
-
role: "tool",
|
|
244
|
-
tool_call_id: typeof message.toolCallId === "string" ? message.toolCallId : "",
|
|
245
|
-
content: redact(text.text),
|
|
246
|
-
};
|
|
247
|
-
if (message.isError === true) tool.is_error = true;
|
|
248
|
-
messages.push(tool);
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// user and anything else: full-fidelity text.
|
|
253
|
-
const text = contentToText(message.content);
|
|
254
|
-
nonTextPartCount += text.nonTextParts;
|
|
255
|
-
messages.push({ role, content: redact(text.text) });
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const endsOnAssistant = messages.length > 0 && messages[messages.length - 1].role === "assistant";
|
|
259
|
-
const runCount = roles.user ?? 0;
|
|
260
|
-
const startedAt = parsed.startedAt;
|
|
261
|
-
const endedAt = parsed.endedAt ?? parsed.startedAt;
|
|
262
|
-
|
|
263
|
-
const artifact: Record<string, unknown> = {
|
|
264
|
-
session_id: input.sessionId,
|
|
265
|
-
user_id: input.userId ?? null,
|
|
266
|
-
client_id: input.clientId ?? null,
|
|
267
|
-
agent_id: input.agentId ?? null,
|
|
268
|
-
model: model ?? "unknown",
|
|
269
|
-
started_at: startedAt,
|
|
270
|
-
ended_at: endedAt,
|
|
271
|
-
duration_minutes: durationMinutes(startedAt, endedAt),
|
|
272
|
-
run_count: runCount,
|
|
273
|
-
incomplete: !endsOnAssistant,
|
|
274
|
-
context_pruned: parsed.sawCompaction,
|
|
275
|
-
unexported_tail_messages: 0,
|
|
276
|
-
system_prompt_chars: systemPrompt.length,
|
|
277
|
-
messages,
|
|
278
|
-
summary: {
|
|
279
|
-
message_count: messages.length,
|
|
280
|
-
roles: {
|
|
281
|
-
system: roles.system ?? 0,
|
|
282
|
-
user: roles.user ?? 0,
|
|
283
|
-
assistant: roles.assistant ?? 0,
|
|
284
|
-
tool: roles.toolResult ?? 0,
|
|
285
|
-
},
|
|
286
|
-
tool_call_count: toolCallCount,
|
|
287
|
-
reasoning_message_count: reasoningMessageCount,
|
|
288
|
-
non_text_part_count: nonTextPartCount,
|
|
289
|
-
ends_on_assistant: endsOnAssistant,
|
|
290
|
-
},
|
|
291
|
-
};
|
|
292
|
-
if (input.parentSessionId && input.parentSessionId !== input.sessionId) {
|
|
293
|
-
artifact.parent_session_id = input.parentSessionId;
|
|
294
|
-
}
|
|
295
|
-
// Additive: the plan tool's task-list snapshots, in order. Absent
|
|
296
|
-
// entirely for sessions that never used the plan tool.
|
|
297
|
-
if (parsed.planRecords.length > 0) {
|
|
298
|
-
artifact.plan_records = parsed.planRecords;
|
|
299
|
-
}
|
|
300
|
-
return artifact;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* pi stores sessions under <agentDir>/sessions/<cwd-slug>/<ts>_<id>.jsonl
|
|
305
|
-
* (e.g. /home/dan/proj -> --home-dan-proj--). Advisory only: the
|
|
306
|
-
* collector discovers the right dir by scanning for the session id, so
|
|
307
|
-
* this does not have to be perfect.
|
|
308
|
-
*/
|
|
309
|
-
export function sessionDirSlug(cwd: string): string {
|
|
310
|
-
return `${cwd.replace(/\\//g, "-")}-`;
|
|
311
|
-
}
|