@aexol/spectral 0.9.251 → 0.9.255

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/extensions/dataset-uploader/analyzer.d.ts +103 -0
  2. package/dist/extensions/dataset-uploader/analyzer.d.ts.map +1 -0
  3. package/dist/extensions/dataset-uploader/analyzer.js +266 -0
  4. package/dist/extensions/dataset-uploader/cleaner.d.ts +59 -0
  5. package/dist/extensions/dataset-uploader/cleaner.d.ts.map +1 -0
  6. package/dist/extensions/dataset-uploader/cleaner.js +118 -0
  7. package/dist/extensions/dataset-uploader/exporter.d.ts +147 -0
  8. package/dist/extensions/dataset-uploader/exporter.d.ts.map +1 -0
  9. package/dist/extensions/dataset-uploader/exporter.js +313 -0
  10. package/dist/extensions/dataset-uploader/extra-values.d.ts +17 -0
  11. package/dist/extensions/dataset-uploader/extra-values.d.ts.map +1 -0
  12. package/dist/extensions/dataset-uploader/extra-values.js +51 -0
  13. package/dist/extensions/dataset-uploader/index.d.ts +22 -0
  14. package/dist/extensions/dataset-uploader/index.d.ts.map +1 -0
  15. package/dist/extensions/dataset-uploader/index.js +247 -0
  16. package/dist/extensions/dataset-uploader/scrubber.d.ts +34 -0
  17. package/dist/extensions/dataset-uploader/scrubber.d.ts.map +1 -0
  18. package/dist/extensions/dataset-uploader/scrubber.js +91 -0
  19. package/dist/extensions/dataset-uploader/upload.d.ts +105 -0
  20. package/dist/extensions/dataset-uploader/upload.d.ts.map +1 -0
  21. package/dist/extensions/dataset-uploader/upload.js +274 -0
  22. package/dist/generated/zeus/const.d.ts.map +1 -1
  23. package/dist/generated/zeus/const.js +80 -6
  24. package/dist/generated/zeus/index.d.ts +432 -36
  25. package/dist/generated/zeus/index.d.ts.map +1 -1
  26. package/dist/mcp/commands.d.ts.map +1 -1
  27. package/dist/mcp/commands.js +3 -0
  28. package/dist/mcp/direct-tools.d.ts.map +1 -1
  29. package/dist/mcp/direct-tools.js +4 -3
  30. package/dist/mcp/index.d.ts.map +1 -1
  31. package/dist/mcp/index.js +5 -1
  32. package/dist/mcp/mcp-auth-flow.d.ts +5 -1
  33. package/dist/mcp/mcp-auth-flow.d.ts.map +1 -1
  34. package/dist/mcp/mcp-auth-flow.js +20 -4
  35. package/dist/mcp/mcp-auth-start.d.ts +64 -0
  36. package/dist/mcp/mcp-auth-start.d.ts.map +1 -0
  37. package/dist/mcp/mcp-auth-start.js +136 -0
  38. package/dist/mcp/proxy-modes.d.ts +7 -0
  39. package/dist/mcp/proxy-modes.d.ts.map +1 -1
  40. package/dist/mcp/proxy-modes.js +71 -11
  41. package/dist/mcp/server-manager.d.ts +2 -1
  42. package/dist/mcp/server-manager.d.ts.map +1 -1
  43. package/dist/mcp/server-manager.js +8 -4
  44. package/dist/memory/commands/status.d.ts.map +1 -1
  45. package/dist/memory/commands/status.js +2 -1
  46. package/dist/memory/hooks/compaction-trigger.d.ts +14 -1
  47. package/dist/memory/hooks/compaction-trigger.d.ts.map +1 -1
  48. package/dist/memory/hooks/compaction-trigger.js +31 -4
  49. package/dist/relay/dispatcher.d.ts.map +1 -1
  50. package/dist/relay/dispatcher.js +4 -47
  51. package/dist/relay/routing.d.ts +1 -1
  52. package/dist/relay/routing.d.ts.map +1 -1
  53. package/dist/relay/routing.js +9 -0
  54. package/dist/sdk/ai/utils/image-source.d.ts +26 -11
  55. package/dist/sdk/ai/utils/image-source.d.ts.map +1 -1
  56. package/dist/sdk/ai/utils/image-source.js +184 -47
  57. package/dist/sdk/ai/utils/transient-error.d.ts +4 -0
  58. package/dist/sdk/ai/utils/transient-error.d.ts.map +1 -1
  59. package/dist/sdk/ai/utils/transient-error.js +15 -0
  60. package/dist/sdk/coding-agent/core/compaction/policy.d.ts +2 -0
  61. package/dist/sdk/coding-agent/core/compaction/policy.d.ts.map +1 -1
  62. package/dist/sdk/coding-agent/core/compaction/policy.js +2 -0
  63. package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
  64. package/dist/sdk/coding-agent/core/extensions/native-extensions.js +10 -0
  65. package/dist/sdk/coding-agent/core/system-prompt.d.ts.map +1 -1
  66. package/dist/sdk/coding-agent/core/system-prompt.js +4 -0
  67. package/dist/sdk/coding-agent/core/tools/read.d.ts.map +1 -1
  68. package/dist/sdk/coding-agent/core/tools/read.js +14 -7
  69. package/dist/sdk/coding-agent/index.d.ts +1 -1
  70. package/dist/sdk/coding-agent/index.d.ts.map +1 -1
  71. package/dist/sdk/coding-agent/index.js +1 -1
  72. package/dist/sdk/coding-agent/utils/image-resize.d.ts +25 -11
  73. package/dist/sdk/coding-agent/utils/image-resize.d.ts.map +1 -1
  74. package/dist/sdk/coding-agent/utils/image-resize.js +54 -36
  75. package/dist/sdk/coding-agent/utils/photon.d.ts.map +1 -1
  76. package/dist/sdk/coding-agent/utils/photon.js +11 -2
  77. package/dist/server/agent-bridge.d.ts +32 -0
  78. package/dist/server/agent-bridge.d.ts.map +1 -1
  79. package/dist/server/agent-bridge.js +59 -9
  80. package/dist/server/handlers/mcp-auth.d.ts +28 -0
  81. package/dist/server/handlers/mcp-auth.d.ts.map +1 -0
  82. package/dist/server/handlers/mcp-auth.js +41 -0
  83. package/dist/server/handlers/mcp-status.d.ts +8 -2
  84. package/dist/server/handlers/mcp-status.d.ts.map +1 -1
  85. package/dist/server/handlers/mcp-status.js +15 -3
  86. package/dist/server/preset-chain.d.ts +0 -13
  87. package/dist/server/preset-chain.d.ts.map +1 -1
  88. package/dist/server/preset-chain.js +0 -25
  89. package/dist/server/session-stream/memory.d.ts.map +1 -1
  90. package/dist/server/session-stream/memory.js +8 -1
  91. package/dist/server/session-stream.d.ts +15 -56
  92. package/dist/server/session-stream.d.ts.map +1 -1
  93. package/dist/server/session-stream.js +41 -271
  94. package/dist/server/wire.d.ts +18 -32
  95. package/dist/server/wire.d.ts.map +1 -1
  96. package/package.json +1 -1
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Session → sharegpt SFT dataset exporter (Dataset-uploader.md §2.1).
3
+ *
4
+ * Reads the local sessions DB read-only (default `~/.spectral/sessions.db`)
5
+ * and maps stored messages to sharegpt conversation samples:
6
+ *
7
+ * user row → { from: "human", value: content }
8
+ * assistant row → events_jsonl parsed in event order:
9
+ * concatenated text_delta → { from: "gpt" }
10
+ * each tool_call → { from: "function_call",
11
+ * value: JSON.stringify({name, arguments}) }
12
+ * matching tool_result → { from: "observation",
13
+ * value: text of result.content,
14
+ * "[ERROR] " prefix when isError }
15
+ * thinking_* events → DISCARDED (non-thinking target)
16
+ *
17
+ * Per-turn expansion: each assistant message row that produces at least one
18
+ * entry becomes one sample — `{ conversations: [all entries up to and
19
+ * including this turn], system?, tools? }`. `system`/`tools` are plain
20
+ * parameters (the assembling index.ts passes `ctx.getSystemPrompt()` and
21
+ * the runtime tool registry). `system` is attached ONLY to samples from
22
+ * sessions whose `active_agent` equals the MOST COMMON active_agent among
23
+ * the exported sessions (the ExtensionContext exposes no current-agent
24
+ * accessor, so this heuristic is the documented fallback); sessions with a
25
+ * different — or null — active_agent get no system field.
26
+ *
27
+ * Subagent handling: when `includeSubagents` is false (default) every
28
+ * observation is capped at MAX_OBSERVATION_CHARS with a "...[truncated]"
29
+ * marker (subagent transcripts are the dominant source of oversized
30
+ * observations).
31
+ */
32
+ interface ReadOnlyStatement {
33
+ all(...params: unknown[]): unknown[];
34
+ }
35
+ interface ReadOnlyDb {
36
+ prepare(sql: string): ReadOnlyStatement;
37
+ close(): void;
38
+ }
39
+ /** Open a SQLite database file read-only (file must already exist). */
40
+ export declare function openReadOnlySqlite(path: string): ReadOnlyDb;
41
+ export interface SharegptEntry {
42
+ from: "human" | "gpt" | "function_call" | "observation";
43
+ value: string;
44
+ }
45
+ /** OpenAI-style tool definition, as stored in the sample `tools` field. */
46
+ export interface ToolDefinition {
47
+ type: "function";
48
+ function: {
49
+ name: string;
50
+ description?: string;
51
+ parameters?: unknown;
52
+ };
53
+ }
54
+ export interface SharegptSample {
55
+ conversations: SharegptEntry[];
56
+ system?: string;
57
+ tools?: ToolDefinition[];
58
+ }
59
+ export interface ExportSessionsOptions {
60
+ /** Path to the sessions DB. Default: `<configDir>/sessions.db`. */
61
+ dbPath?: string;
62
+ /** Restrict the export to these session ids. */
63
+ sessionIds?: string[];
64
+ /** Max number of sessions (newest first by updated_at). */
65
+ limit?: number;
66
+ /** Only sessions with updated_at >= since (ISO 8601 string). */
67
+ since?: string;
68
+ /**
69
+ * When true, observations are NOT capped (subagent transcripts kept
70
+ * intact). Default false → observations capped at MAX_OBSERVATION_CHARS.
71
+ */
72
+ includeSubagents?: boolean;
73
+ /** Output JSONL path. Default: `<configDir>/datasets/export-<timestamp>.jsonl`. */
74
+ outputPath?: string;
75
+ /**
76
+ * System prompt to attach to samples of sessions whose `active_agent`
77
+ * matches the most common active_agent among the exported sessions;
78
+ * omitted when null/empty.
79
+ */
80
+ system?: string | null;
81
+ /** Tool registry to attach to every sample; omitted when null/empty. */
82
+ tools?: ToolDefinition[] | undefined;
83
+ }
84
+ export interface ExportSessionsResult {
85
+ /** Number of sessions processed. */
86
+ sessions: number;
87
+ /** Number of sharegpt samples written. */
88
+ samples: number;
89
+ /** Total bytes written to the output file(s). */
90
+ bytes: number;
91
+ /** Count of tool calls per tool name (per call event, not per sample). */
92
+ toolCoverage: Record<string, number>;
93
+ /** Output file paths (always a single JSONL in v1). */
94
+ files: string[];
95
+ /**
96
+ * The active_agent value that received the system prompt (the most
97
+ * common active_agent among the exported sessions), when a system
98
+ * prompt was provided. null when no system prompt was attached.
99
+ */
100
+ systemAgent: string | null;
101
+ /** Number of sessions whose samples had the system prompt attached. */
102
+ systemSessionCount: number;
103
+ }
104
+ /**
105
+ * Cap for observation text when `includeSubagents` is false. history-
106
+ * projection's 256 KB field cap is too large for training data, so this
107
+ * module defines its own tighter limit.
108
+ */
109
+ export declare const MAX_OBSERVATION_CHARS = 4000;
110
+ export declare const OBSERVATION_TRUNCATION_MARKER = "...[truncated]";
111
+ interface ToolResultContentPart {
112
+ type?: string;
113
+ text?: string;
114
+ }
115
+ interface AssistantEvent {
116
+ type: string;
117
+ delta?: string;
118
+ id?: string;
119
+ name?: string;
120
+ args?: unknown;
121
+ result?: {
122
+ content?: ToolResultContentPart[];
123
+ } | null;
124
+ isError?: boolean;
125
+ }
126
+ export interface ParsedAssistantTurn {
127
+ entries: SharegptEntry[];
128
+ /** Tool names invoked by this turn's tool_call events (in order). */
129
+ toolNames: string[];
130
+ }
131
+ /** Extract the plain text of a tool_result payload (concatenated text parts). */
132
+ export declare function extractResultText(result: AssistantEvent["result"]): string;
133
+ /**
134
+ * Parse an assistant row's events_jsonl into sharegpt entries, in event
135
+ * order. Thinking events are discarded. When `capChars` is non-null,
136
+ * observation values are truncated to it + OBSERVATION_TRUNCATION_MARKER.
137
+ */
138
+ export declare function parseAssistantEvents(eventsJsonl: string, capChars?: number | null): ParsedAssistantTurn;
139
+ /**
140
+ * Export sessions from the local sessions DB to a sharegpt JSONL file.
141
+ *
142
+ * The DB is opened strictly read-only; the output file is written next to
143
+ * (or at) `outputPath`. One JSON sample per line.
144
+ */
145
+ export declare function exportSessions(options?: ExportSessionsOptions): ExportSessionsResult;
146
+ export {};
147
+ //# sourceMappingURL=exporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exporter.d.ts","sourceRoot":"","sources":["../../../src/extensions/dataset-uploader/exporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Session → sharegpt SFT dataset exporter (Dataset-uploader.md §2.1).
3
+ *
4
+ * Reads the local sessions DB read-only (default `~/.spectral/sessions.db`)
5
+ * and maps stored messages to sharegpt conversation samples:
6
+ *
7
+ * user row → { from: "human", value: content }
8
+ * assistant row → events_jsonl parsed in event order:
9
+ * concatenated text_delta → { from: "gpt" }
10
+ * each tool_call → { from: "function_call",
11
+ * value: JSON.stringify({name, arguments}) }
12
+ * matching tool_result → { from: "observation",
13
+ * value: text of result.content,
14
+ * "[ERROR] " prefix when isError }
15
+ * thinking_* events → DISCARDED (non-thinking target)
16
+ *
17
+ * Per-turn expansion: each assistant message row that produces at least one
18
+ * entry becomes one sample — `{ conversations: [all entries up to and
19
+ * including this turn], system?, tools? }`. `system`/`tools` are plain
20
+ * parameters (the assembling index.ts passes `ctx.getSystemPrompt()` and
21
+ * the runtime tool registry). `system` is attached ONLY to samples from
22
+ * sessions whose `active_agent` equals the MOST COMMON active_agent among
23
+ * the exported sessions (the ExtensionContext exposes no current-agent
24
+ * accessor, so this heuristic is the documented fallback); sessions with a
25
+ * different — or null — active_agent get no system field.
26
+ *
27
+ * Subagent handling: when `includeSubagents` is false (default) every
28
+ * observation is capped at MAX_OBSERVATION_CHARS with a "...[truncated]"
29
+ * marker (subagent transcripts are the dominant source of oversized
30
+ * observations).
31
+ */
32
+ import { createRequire } from "node:module";
33
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
34
+ import { dirname, join } from "node:path";
35
+ import { getConfigDir } from "../../config.js";
36
+ const _require = createRequire(import.meta.url);
37
+ let nodeSqliteModule;
38
+ let nodeSqliteProbed = false;
39
+ function loadNodeSqlite() {
40
+ if (nodeSqliteProbed)
41
+ return nodeSqliteModule;
42
+ nodeSqliteProbed = true;
43
+ // Same warning suppression as sqlite-adapter.ts: node:sqlite emits an
44
+ // ExperimentalWarning on first load; we opt into it deliberately.
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ const proc = process;
47
+ const originalEmitWarning = proc.emitWarning;
48
+ try {
49
+ proc.emitWarning = (warning, ...rest) => {
50
+ const message = typeof warning === "string"
51
+ ? warning
52
+ : warning instanceof Error
53
+ ? warning.message
54
+ : String(warning);
55
+ if (message.includes("SQLite is an experimental feature"))
56
+ return;
57
+ return originalEmitWarning.call(process, warning, ...rest);
58
+ };
59
+ nodeSqliteModule = _require("node:sqlite");
60
+ }
61
+ catch {
62
+ nodeSqliteModule = null;
63
+ }
64
+ finally {
65
+ proc.emitWarning = originalEmitWarning;
66
+ }
67
+ return nodeSqliteModule;
68
+ }
69
+ /** Open a SQLite database file read-only (file must already exist). */
70
+ export function openReadOnlySqlite(path) {
71
+ if (typeof globalThis.Bun !== "undefined") {
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
+ const { Database } = _require("bun:sqlite");
74
+ return new Database(path, { readonly: true, create: false });
75
+ }
76
+ const mod = loadNodeSqlite();
77
+ if (typeof mod?.DatabaseSync === "function") {
78
+ return new mod.DatabaseSync(path, { readOnly: true });
79
+ }
80
+ const Db = _require("better-sqlite3");
81
+ return new Db(path, { readonly: true });
82
+ }
83
+ // ---------------------------------------------------------------------------
84
+ // Observation cap
85
+ // ---------------------------------------------------------------------------
86
+ /**
87
+ * Cap for observation text when `includeSubagents` is false. history-
88
+ * projection's 256 KB field cap is too large for training data, so this
89
+ * module defines its own tighter limit.
90
+ */
91
+ export const MAX_OBSERVATION_CHARS = 4000;
92
+ export const OBSERVATION_TRUNCATION_MARKER = "...[truncated]";
93
+ function capObservation(limit, text) {
94
+ if (limit === null || text.length <= limit)
95
+ return text;
96
+ return text.slice(0, limit) + OBSERVATION_TRUNCATION_MARKER;
97
+ }
98
+ /** Extract the plain text of a tool_result payload (concatenated text parts). */
99
+ export function extractResultText(result) {
100
+ const parts = result?.content;
101
+ if (!Array.isArray(parts))
102
+ return "";
103
+ return parts
104
+ .filter((p) => p && p.type === "text" && typeof p.text === "string")
105
+ .map((p) => p.text)
106
+ .join("\n");
107
+ }
108
+ /**
109
+ * Parse an assistant row's events_jsonl into sharegpt entries, in event
110
+ * order. Thinking events are discarded. When `capChars` is non-null,
111
+ * observation values are truncated to it + OBSERVATION_TRUNCATION_MARKER.
112
+ */
113
+ export function parseAssistantEvents(eventsJsonl, capChars = null) {
114
+ const entries = [];
115
+ const toolNames = [];
116
+ const pendingCallIds = new Set();
117
+ let textBuffer = "";
118
+ const flushText = () => {
119
+ if (textBuffer.length > 0) {
120
+ entries.push({ from: "gpt", value: textBuffer });
121
+ textBuffer = "";
122
+ }
123
+ };
124
+ for (const line of eventsJsonl.split("\n")) {
125
+ const trimmed = line.trim();
126
+ if (!trimmed)
127
+ continue;
128
+ let event;
129
+ try {
130
+ event = JSON.parse(trimmed);
131
+ }
132
+ catch {
133
+ continue; // skip malformed lines
134
+ }
135
+ switch (event.type) {
136
+ case "text_delta":
137
+ textBuffer += event.delta ?? "";
138
+ break;
139
+ case "tool_call":
140
+ flushText();
141
+ if (typeof event.name === "string" && event.name.length > 0) {
142
+ entries.push({
143
+ from: "function_call",
144
+ value: JSON.stringify({ name: event.name, arguments: event.args ?? {} }),
145
+ });
146
+ toolNames.push(event.name);
147
+ if (typeof event.id === "string")
148
+ pendingCallIds.add(event.id);
149
+ }
150
+ break;
151
+ case "tool_result": {
152
+ flushText();
153
+ // Only emit observations that match a tool_call from this turn.
154
+ if (typeof event.id !== "string" || !pendingCallIds.has(event.id))
155
+ break;
156
+ pendingCallIds.delete(event.id);
157
+ const text = extractResultText(event.result);
158
+ if (text.length === 0)
159
+ break;
160
+ entries.push({
161
+ from: "observation",
162
+ value: (event.isError ? "[ERROR] " : "") + capObservation(capChars, text),
163
+ });
164
+ break;
165
+ }
166
+ default:
167
+ // thinking_*, message_*, token_usage, subagent_* → discarded
168
+ break;
169
+ }
170
+ }
171
+ flushText();
172
+ return { entries, toolNames };
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // Export
176
+ // ---------------------------------------------------------------------------
177
+ function makeSample(entries, system, tools) {
178
+ const sample = { conversations: [...entries] };
179
+ if (typeof system === "string" && system.length > 0)
180
+ sample.system = system;
181
+ if (Array.isArray(tools) && tools.length > 0)
182
+ sample.tools = tools;
183
+ return sample;
184
+ }
185
+ /**
186
+ * Most common non-null active_agent among the exported sessions (ties
187
+ * broken by first occurrence in newest-first order). null when no session
188
+ * has a non-null active_agent.
189
+ */
190
+ function mostCommonActiveAgent(sessions) {
191
+ const counts = new Map();
192
+ for (const s of sessions) {
193
+ if (typeof s.activeAgent === "string" && s.activeAgent.length > 0) {
194
+ counts.set(s.activeAgent, (counts.get(s.activeAgent) ?? 0) + 1);
195
+ }
196
+ }
197
+ let best = null;
198
+ let bestCount = 0;
199
+ for (const [agent, count] of counts) {
200
+ if (count > bestCount) {
201
+ best = agent;
202
+ bestCount = count;
203
+ }
204
+ }
205
+ return best;
206
+ }
207
+ function defaultTimestamp() {
208
+ return new Date().toISOString().replace(/[:.]/g, "-");
209
+ }
210
+ /**
211
+ * Export sessions from the local sessions DB to a sharegpt JSONL file.
212
+ *
213
+ * The DB is opened strictly read-only; the output file is written next to
214
+ * (or at) `outputPath`. One JSON sample per line.
215
+ */
216
+ export function exportSessions(options = {}) {
217
+ const dbPath = options.dbPath ?? join(getConfigDir(), "sessions.db");
218
+ if (!existsSync(dbPath)) {
219
+ throw new Error(`sessions DB not found at ${dbPath}`);
220
+ }
221
+ const db = openReadOnlySqlite(dbPath);
222
+ try {
223
+ // ---- session selection (newest first) --------------------------------
224
+ const where = [];
225
+ const params = [];
226
+ if (options.sessionIds !== undefined && options.sessionIds.length > 0) {
227
+ where.push(`id IN (${options.sessionIds.map(() => "?").join(",")})`);
228
+ params.push(...options.sessionIds);
229
+ }
230
+ if (options.since !== undefined) {
231
+ const sinceMs = Date.parse(options.since);
232
+ if (Number.isNaN(sinceMs)) {
233
+ throw new Error(`invalid "since" value: ${JSON.stringify(options.since)}`);
234
+ }
235
+ where.push("updated_at >= ?");
236
+ params.push(sinceMs);
237
+ }
238
+ let sql = "SELECT id, active_agent FROM sessions";
239
+ if (where.length > 0)
240
+ sql += ` WHERE ${where.join(" AND ")}`;
241
+ sql += " ORDER BY updated_at DESC";
242
+ if (typeof options.limit === "number" && options.limit > 0) {
243
+ sql += " LIMIT ?";
244
+ params.push(options.limit);
245
+ }
246
+ const selectedSessions = db.prepare(sql).all(...params).map((r) => ({
247
+ id: r.id,
248
+ activeAgent: r.active_agent ?? null,
249
+ }));
250
+ // System prompt heuristic: attach it only to sessions whose
251
+ // active_agent is the most common one among the exported sessions
252
+ // (no current-agent accessor exists on ExtensionContext).
253
+ const hasSystem = typeof options.system === "string" && options.system.length > 0;
254
+ const systemAgent = hasSystem
255
+ ? mostCommonActiveAgent(selectedSessions)
256
+ : null;
257
+ let systemSessionCount = 0;
258
+ // ---- per-session mapping ---------------------------------------------
259
+ const cap = options.includeSubagents ? null : MAX_OBSERVATION_CHARS;
260
+ const toolCoverage = {};
261
+ const samples = [];
262
+ const messageStmt = db.prepare("SELECT role, content, events_jsonl FROM messages WHERE session_id = ? ORDER BY seq ASC, id ASC");
263
+ for (const session of selectedSessions) {
264
+ const sessionId = session.id;
265
+ const sessionSystem = systemAgent !== null && session.activeAgent === systemAgent
266
+ ? options.system
267
+ : null;
268
+ const rows = messageStmt.all(sessionId);
269
+ const entries = [];
270
+ for (const row of rows) {
271
+ if (row.role === "user") {
272
+ if (row.content.trim().length > 0) {
273
+ entries.push({ from: "human", value: row.content });
274
+ }
275
+ continue;
276
+ }
277
+ if (row.role !== "assistant")
278
+ continue; // system rows are not part of SFT samples
279
+ const turn = parseAssistantEvents(row.events_jsonl, cap);
280
+ if (turn.entries.length === 0)
281
+ continue;
282
+ for (const name of turn.toolNames) {
283
+ toolCoverage[name] = (toolCoverage[name] ?? 0) + 1;
284
+ }
285
+ entries.push(...turn.entries);
286
+ // Per-turn expansion: snapshot everything up to and including this turn.
287
+ samples.push(makeSample(entries, sessionSystem, options.tools));
288
+ }
289
+ if (sessionSystem !== null)
290
+ systemSessionCount++;
291
+ }
292
+ // ---- write JSONL --------------------------------------------------------
293
+ const outputPath = options.outputPath
294
+ ?? join(getConfigDir(), "datasets", `export-${defaultTimestamp()}.jsonl`);
295
+ mkdirSync(dirname(outputPath), { recursive: true });
296
+ const content = samples.length > 0
297
+ ? samples.map((s) => JSON.stringify(s)).join("\n") + "\n"
298
+ : "";
299
+ writeFileSync(outputPath, content, "utf8");
300
+ return {
301
+ sessions: selectedSessions.length,
302
+ samples: samples.length,
303
+ bytes: Buffer.byteLength(content, "utf8"),
304
+ toolCoverage,
305
+ files: [outputPath],
306
+ systemAgent,
307
+ systemSessionCount,
308
+ };
309
+ }
310
+ finally {
311
+ db.close();
312
+ }
313
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shared opaque-secret collector for the dataset tools (used by BOTH
3
+ * `dataset_clean` and `dataset_analyze` so their scrub runs agree).
4
+ *
5
+ * Collects:
6
+ * (a) secret-store values (listSecrets + getSecretValue)
7
+ * (b) values from backend/.env.local under cwd (KEY=VALUE lines)
8
+ *
9
+ * Values are NEVER printed — they are only passed to the scrubber.
10
+ * Failures are non-fatal (the pattern-based rules still apply).
11
+ */
12
+ /**
13
+ * Collect opaque secret values for the scrubber (see module doc). Returns
14
+ * an empty array when no values are available.
15
+ */
16
+ export declare function collectExtraValues(cwd: string): string[];
17
+ //# sourceMappingURL=extra-values.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extra-values.d.ts","sourceRoot":"","sources":["../../../src/extensions/dataset-uploader/extra-values.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA2BxD"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Shared opaque-secret collector for the dataset tools (used by BOTH
3
+ * `dataset_clean` and `dataset_analyze` so their scrub runs agree).
4
+ *
5
+ * Collects:
6
+ * (a) secret-store values (listSecrets + getSecretValue)
7
+ * (b) values from backend/.env.local under cwd (KEY=VALUE lines)
8
+ *
9
+ * Values are NEVER printed — they are only passed to the scrubber.
10
+ * Failures are non-fatal (the pattern-based rules still apply).
11
+ */
12
+ import { existsSync, readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { listSecrets, getSecretValue } from "../../secrets/store.js";
15
+ /**
16
+ * Collect opaque secret values for the scrubber (see module doc). Returns
17
+ * an empty array when no values are available.
18
+ */
19
+ export function collectExtraValues(cwd) {
20
+ const values = [];
21
+ try {
22
+ for (const meta of listSecrets()) {
23
+ const value = getSecretValue(meta.name);
24
+ if (typeof value === "string" && value.length > 0)
25
+ values.push(value);
26
+ }
27
+ }
28
+ catch {
29
+ // Store unreadable — non-fatal, pattern rules still apply.
30
+ }
31
+ try {
32
+ const envPath = join(cwd, "backend", ".env.local");
33
+ if (existsSync(envPath)) {
34
+ for (const line of readFileSync(envPath, "utf8").split("\n")) {
35
+ const trimmed = line.trim();
36
+ if (!trimmed || trimmed.startsWith("#"))
37
+ continue;
38
+ const eq = trimmed.indexOf("=");
39
+ if (eq > 0) {
40
+ const value = trimmed.slice(eq + 1).trim();
41
+ if (value.length > 0)
42
+ values.push(value);
43
+ }
44
+ }
45
+ }
46
+ }
47
+ catch {
48
+ // .env unreadable — non-fatal.
49
+ }
50
+ return values;
51
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Dataset Uploader — native spectral extension (Dataset-uploader.md, Phase 2–3).
3
+ *
4
+ * Turns real harness sessions into SFT (sharegpt) training data and uploads
5
+ * it to the team dataset vault on the Aexol backend. Registers four tools:
6
+ *
7
+ * - dataset_export sessions DB → sharegpt JSONL (system + runtime tool
8
+ * registry attached from the live agent context)
9
+ * - dataset_analyze heuristic quality report (coverage, duplicates,
10
+ * dry-run scrub residue, quality flags)
11
+ * - dataset_clean scrub every string field (secret-store + .env values
12
+ * as extraValues) + quality filters → *.clean.jsonl
13
+ * - dataset_upload chunked upload to the backend vault (dry-run aware)
14
+ *
15
+ * USAGE POLICY (encoded in every tool description): these tools exist for
16
+ * model-training dataset building and must be used ONLY when the user
17
+ * explicitly asks to export/analyze/clean/upload session data for a
18
+ * training dataset. The agent must never run them proactively.
19
+ */
20
+ import type { ExtensionAPI } from "../../sdk/coding-agent/index.js";
21
+ export default function datasetUploaderExtension(ext: ExtensionAPI): Promise<void>;
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/dataset-uploader/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAEV,YAAY,EAGb,MAAM,iCAAiC,CAAC;AA+KzC,wBAA8B,wBAAwB,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4GvF"}