@aiwg/cli 2026.7.20 → 2026.7.21
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 +4 -4
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/browser-export.js +7 -0
- package/dist/src/artifacts/citation-parser.js +96 -35
- package/dist/src/artifacts/index-builder.js +54 -17
- package/dist/src/artifacts/state-transfer.js +27 -0
- package/dist/src/artifacts/stats.js +8 -0
- package/dist/src/cli/cli-extension-loader.js +73 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/sessions.js +966 -0
- package/dist/src/cli/handlers/skill-lint.js +49 -45
- package/dist/src/cli/handlers/use.js +143 -60
- package/dist/src/cli/handlers/utilities.js +22 -8
- package/dist/src/cli/skill-usage.js +146 -24
- package/dist/src/extensions/commands/definitions.js +29 -0
- package/dist/src/extensions/manifest.js +29 -0
- package/dist/src/sessions/adapters/claude.js +357 -0
- package/dist/src/sessions/adapters/codex.js +521 -0
- package/dist/src/sessions/adapters/copilot.js +226 -0
- package/dist/src/sessions/adapters/cursor.js +372 -0
- package/dist/src/sessions/adapters/factory.js +345 -0
- package/dist/src/sessions/adapters/generic.js +225 -0
- package/dist/src/sessions/adapters/hermes.js +341 -0
- package/dist/src/sessions/adapters/openclaw.js +381 -0
- package/dist/src/sessions/adapters/opencode.js +454 -0
- package/dist/src/sessions/adapters/openhuman.js +315 -0
- package/dist/src/sessions/adapters/warp.js +160 -0
- package/dist/src/sessions/adapters/windsurf.js +212 -0
- package/dist/src/sessions/candidates.js +210 -0
- package/dist/src/sessions/contracts.js +310 -0
- package/dist/src/sessions/discovery.js +51 -0
- package/dist/src/sessions/fixtures.js +12 -0
- package/dist/src/sessions/importer.js +315 -0
- package/dist/src/sessions/index.js +25 -0
- package/dist/src/sessions/knowledge-shard.js +61 -0
- package/dist/src/sessions/optional-backends.js +238 -0
- package/dist/src/sessions/policy.js +192 -0
- package/dist/src/sessions/ports.js +2 -0
- package/dist/src/sessions/promotion.js +367 -0
- package/dist/src/sessions/readers.js +176 -0
- package/dist/src/sessions/repository.js +1551 -0
- package/dist/src/skills/adapters/agent-skills.js +59 -0
- package/dist/src/skills/adapters/local.js +19 -1
- package/dist/src/skills/agent-skills.js +249 -0
- package/dist/src/skills/cli.js +463 -7
- package/dist/src/skills/deployer.js +554 -0
- package/dist/src/skills/doctor.js +105 -0
- package/dist/src/skills/exporter.js +382 -0
- package/dist/src/skills/importer.js +921 -0
- package/dist/src/skills/registry.js +19 -0
- package/dist/src/skills/validator.js +323 -0
- package/package.json +2 -2
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
3
|
+
import { readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
|
|
4
|
+
export const OPENHUMAN_ADAPTER_VERSION = '1.0.0';
|
|
5
|
+
export const OPENHUMAN_SOURCE_SCHEMA_VERSION = '1.0.0';
|
|
6
|
+
const MetadataSchema = z.object({
|
|
7
|
+
provider: z.string().optional(),
|
|
8
|
+
model: z.string().optional(),
|
|
9
|
+
profile: z.string().optional(),
|
|
10
|
+
nestedAgentId: z.string().optional(),
|
|
11
|
+
parentAgentId: z.string().optional(),
|
|
12
|
+
}).passthrough();
|
|
13
|
+
const AttachmentSchema = z.object({
|
|
14
|
+
id: z.string().min(1),
|
|
15
|
+
name: z.string().optional(),
|
|
16
|
+
mime: z.string().optional(),
|
|
17
|
+
uri: z.string().optional(),
|
|
18
|
+
state: z.enum(['present', 'expired']),
|
|
19
|
+
expiredAt: z.union([z.string(), z.number()]).optional(),
|
|
20
|
+
}).passthrough();
|
|
21
|
+
const RawSchema = z.object({
|
|
22
|
+
schemaVersion: z.union([z.string(), z.number()]),
|
|
23
|
+
recordType: z.literal('session_raw'),
|
|
24
|
+
session_id: z.string().min(1),
|
|
25
|
+
event_id: z.string().min(1),
|
|
26
|
+
thread_id: z.string().min(1),
|
|
27
|
+
request_id: z.string().optional(),
|
|
28
|
+
parent_event_id: z.string().nullable().optional(),
|
|
29
|
+
type: z.string().min(1),
|
|
30
|
+
role: z.string().optional(),
|
|
31
|
+
content: z.unknown().optional(),
|
|
32
|
+
timestamp: z.union([z.string(), z.number()]).optional(),
|
|
33
|
+
metadata: MetadataSchema.optional(),
|
|
34
|
+
input_tokens: z.number().optional(),
|
|
35
|
+
cached_input_tokens: z.number().optional(),
|
|
36
|
+
output_tokens: z.number().optional(),
|
|
37
|
+
charged_amount_usd: z.number().optional(),
|
|
38
|
+
compacted: z.boolean().optional(),
|
|
39
|
+
interrupted: z.boolean().optional(),
|
|
40
|
+
tool: z.record(z.unknown()).optional(),
|
|
41
|
+
attachment: AttachmentSchema.optional(),
|
|
42
|
+
active: z.boolean().optional(),
|
|
43
|
+
raw_transcript_deleted_at: z.union([z.string(), z.number()]).nullable().optional(),
|
|
44
|
+
}).passthrough();
|
|
45
|
+
const ThreadStateSchema = z.object({
|
|
46
|
+
schemaVersion: z.union([z.string(), z.number()]),
|
|
47
|
+
recordType: z.literal('thread_state'),
|
|
48
|
+
thread_id: z.string().min(1),
|
|
49
|
+
title: z.string().optional(),
|
|
50
|
+
state: z.string().optional(),
|
|
51
|
+
deleted_at: z.union([z.string(), z.number()]).nullable().optional(),
|
|
52
|
+
}).passthrough();
|
|
53
|
+
const TurnStateSchema = z.object({
|
|
54
|
+
schemaVersion: z.union([z.string(), z.number()]),
|
|
55
|
+
recordType: z.literal('turn_state'),
|
|
56
|
+
thread_id: z.string().min(1),
|
|
57
|
+
request_id: z.string().min(1),
|
|
58
|
+
state: z.string().min(1),
|
|
59
|
+
interrupted: z.boolean().optional(),
|
|
60
|
+
completed_at: z.union([z.string(), z.number()]).nullable().optional(),
|
|
61
|
+
}).passthrough();
|
|
62
|
+
const RecordSchema = z.discriminatedUnion('recordType', [
|
|
63
|
+
RawSchema, ThreadStateSchema, TurnStateSchema,
|
|
64
|
+
]);
|
|
65
|
+
export class OpenHumanSessionAdapter {
|
|
66
|
+
limits;
|
|
67
|
+
provider = 'openhuman';
|
|
68
|
+
adapterVersion = OPENHUMAN_ADAPTER_VERSION;
|
|
69
|
+
disposition = 'implemented';
|
|
70
|
+
supportedOperations = ['inspect', 'stream'];
|
|
71
|
+
acquisitionModes = ['jsonl'];
|
|
72
|
+
constructor(limits) {
|
|
73
|
+
this.limits = limits;
|
|
74
|
+
}
|
|
75
|
+
async *discover(_scope) {
|
|
76
|
+
// session_raw and enrichment bundles require explicit selection.
|
|
77
|
+
}
|
|
78
|
+
async inspect(source) {
|
|
79
|
+
const parsed = await this.readSource(source);
|
|
80
|
+
return {
|
|
81
|
+
sourceSchemaVersion: parsed.schemaVersion,
|
|
82
|
+
consistency: parsed.consistency,
|
|
83
|
+
operationalState: 'available',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async *stream(source, cursor) {
|
|
87
|
+
if (source.locatorClass !== 'openhuman-session-raw-jsonl'
|
|
88
|
+
&& source.locatorClass !== 'openhuman-enriched-jsonl') {
|
|
89
|
+
throw new SessionContractError('UNSUPPORTED_OPERATION', 'unsupported OpenHuman source class');
|
|
90
|
+
}
|
|
91
|
+
const start = parseCursor(cursor?.value);
|
|
92
|
+
const input = await streamBoundedJsonLines({
|
|
93
|
+
selectedPath: source.locator,
|
|
94
|
+
allowedRoots: source.authorizedScope.allowedRoots,
|
|
95
|
+
}, { consistency: 'provisional', limits: this.limits });
|
|
96
|
+
const threads = new Map();
|
|
97
|
+
const turns = new Map();
|
|
98
|
+
const maxJoinStates = Math.min(this.limits?.maxRecords ?? 1_000_000, 10_000);
|
|
99
|
+
let schemaVersion = null;
|
|
100
|
+
let outputIndex = 0;
|
|
101
|
+
let sawRaw = false;
|
|
102
|
+
for await (const line of input) {
|
|
103
|
+
const parsed = RecordSchema.safeParse(line.value);
|
|
104
|
+
if (!parsed.success) {
|
|
105
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman transcript record is malformed');
|
|
106
|
+
}
|
|
107
|
+
const value = parsed.data;
|
|
108
|
+
const currentVersion = version(value.schemaVersion);
|
|
109
|
+
if (schemaVersion && schemaVersion !== currentVersion) {
|
|
110
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenHuman schema versions');
|
|
111
|
+
}
|
|
112
|
+
schemaVersion = currentVersion;
|
|
113
|
+
assertSupportedSchemaMajor(schemaVersion);
|
|
114
|
+
if (value.recordType === 'thread_state') {
|
|
115
|
+
threads.set(value.thread_id, value);
|
|
116
|
+
}
|
|
117
|
+
else if (value.recordType === 'turn_state') {
|
|
118
|
+
turns.set(`${value.thread_id}\0${value.request_id}`, value);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
sawRaw = true;
|
|
122
|
+
const record = normalize(value, line, source.locatorClass, threads.get(value.thread_id), value.request_id ? turns.get(`${value.thread_id}\0${value.request_id}`) : undefined);
|
|
123
|
+
if (outputIndex++ >= start)
|
|
124
|
+
yield record;
|
|
125
|
+
}
|
|
126
|
+
if (threads.size + turns.size > maxJoinStates) {
|
|
127
|
+
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'OpenHuman enrichment join state exceeds the bounded streaming limit');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!sawRaw) {
|
|
131
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman source contains no session_raw records');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async readSource(source) {
|
|
135
|
+
if (source.locatorClass !== 'openhuman-session-raw-jsonl'
|
|
136
|
+
&& source.locatorClass !== 'openhuman-enriched-jsonl') {
|
|
137
|
+
throw new SessionContractError('UNSUPPORTED_OPERATION', 'unsupported OpenHuman source class');
|
|
138
|
+
}
|
|
139
|
+
const result = await readBoundedJsonLines({
|
|
140
|
+
selectedPath: source.locator,
|
|
141
|
+
allowedRoots: source.authorizedScope.allowedRoots,
|
|
142
|
+
}, { consistency: 'provisional', limits: this.limits });
|
|
143
|
+
if (result.records.length === 0) {
|
|
144
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman transcript source is empty');
|
|
145
|
+
}
|
|
146
|
+
const parsed = result.records.map(({ value, ...line }) => {
|
|
147
|
+
const record = RecordSchema.safeParse(value);
|
|
148
|
+
if (!record.success)
|
|
149
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman transcript record is malformed');
|
|
150
|
+
return { value: record.data, ...line };
|
|
151
|
+
});
|
|
152
|
+
const versions = new Set(parsed.map(({ value }) => version(value.schemaVersion)));
|
|
153
|
+
if (versions.size !== 1)
|
|
154
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenHuman schema versions');
|
|
155
|
+
assertSupportedSchemaMajor([...versions][0]);
|
|
156
|
+
const raw = parsed.filter((item) => item.value.recordType === 'session_raw');
|
|
157
|
+
if (raw.length === 0) {
|
|
158
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'OpenHuman source contains no session_raw records');
|
|
159
|
+
}
|
|
160
|
+
const threads = new Map();
|
|
161
|
+
const turns = new Map();
|
|
162
|
+
for (const item of parsed) {
|
|
163
|
+
if (item.value.recordType === 'thread_state')
|
|
164
|
+
threads.set(item.value.thread_id, item.value);
|
|
165
|
+
if (item.value.recordType === 'turn_state') {
|
|
166
|
+
turns.set(`${item.value.thread_id}\0${item.value.request_id}`, item.value);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const sessions = new Map();
|
|
170
|
+
for (const item of raw) {
|
|
171
|
+
const values = sessions.get(item.value.session_id) ?? [];
|
|
172
|
+
values.push(item.value);
|
|
173
|
+
sessions.set(item.value.session_id, values);
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
schemaVersion: OPENHUMAN_SOURCE_SCHEMA_VERSION,
|
|
177
|
+
consistency: result.incompleteTail || [...sessions.values()].some((events) => events.at(-1)?.active !== false && events.at(-1)?.type !== 'session_end')
|
|
178
|
+
? 'provisional' : 'complete',
|
|
179
|
+
records: raw.map(({ value, ...line }) => normalize(value, line, source.locatorClass, threads.get(value.thread_id), value.request_id ? turns.get(`${value.thread_id}\0${value.request_id}`) : undefined)),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function normalize(value, line, locatorClass, thread, turn) {
|
|
184
|
+
const lifecycle = value.raw_transcript_deleted_at
|
|
185
|
+
? 'deleted'
|
|
186
|
+
: value.active === false || value.type === 'session_end' ? 'complete' : 'active';
|
|
187
|
+
return {
|
|
188
|
+
nativeSessionId: value.session_id,
|
|
189
|
+
nativeEventId: value.event_id,
|
|
190
|
+
sequence: line.sequence,
|
|
191
|
+
kind: recordKind(value),
|
|
192
|
+
role: value.role,
|
|
193
|
+
occurredAt: timestamp(value.timestamp),
|
|
194
|
+
text: extractText(value.content, value.tool),
|
|
195
|
+
rawReference: { locatorClass, offset: line.byteOffset },
|
|
196
|
+
extensions: {
|
|
197
|
+
lifecycle,
|
|
198
|
+
relationship: {
|
|
199
|
+
threadId: value.thread_id,
|
|
200
|
+
requestId: value.request_id,
|
|
201
|
+
parentEventId: value.parent_event_id,
|
|
202
|
+
},
|
|
203
|
+
repeatedMetadata: value.metadata,
|
|
204
|
+
usage: {
|
|
205
|
+
inputTokens: value.input_tokens,
|
|
206
|
+
cachedInputTokens: value.cached_input_tokens,
|
|
207
|
+
outputTokens: value.output_tokens,
|
|
208
|
+
chargedAmountUsd: value.charged_amount_usd,
|
|
209
|
+
},
|
|
210
|
+
compaction: { compacted: value.compacted === true },
|
|
211
|
+
interruption: {
|
|
212
|
+
interrupted: value.interrupted === true || turn?.interrupted === true,
|
|
213
|
+
turnState: turn?.state,
|
|
214
|
+
turnCompletedAt: timestamp(turn?.completed_at),
|
|
215
|
+
},
|
|
216
|
+
tool: value.tool,
|
|
217
|
+
attachment: value.attachment ? {
|
|
218
|
+
id: value.attachment.id,
|
|
219
|
+
name: value.attachment.name,
|
|
220
|
+
mime: value.attachment.mime,
|
|
221
|
+
state: value.attachment.state,
|
|
222
|
+
uriPresent: Boolean(value.attachment.uri),
|
|
223
|
+
expiredAt: timestamp(value.attachment.expiredAt),
|
|
224
|
+
} : undefined,
|
|
225
|
+
thread: thread ? {
|
|
226
|
+
title: thread.title,
|
|
227
|
+
state: thread.state,
|
|
228
|
+
deletedAt: timestamp(thread.deleted_at),
|
|
229
|
+
} : undefined,
|
|
230
|
+
deletion: {
|
|
231
|
+
threadDeletedAt: timestamp(thread?.deleted_at),
|
|
232
|
+
rawTranscriptDeletedAt: timestamp(value.raw_transcript_deleted_at),
|
|
233
|
+
threadDeletionDoesNotImplyRawDeletion: true,
|
|
234
|
+
aiwgDeletionDoesNotDeleteProviderData: true,
|
|
235
|
+
},
|
|
236
|
+
opaqueContent: typeof value.content !== 'string',
|
|
237
|
+
provenance: {
|
|
238
|
+
acquisition: locatorClass,
|
|
239
|
+
schema: version(value.schemaVersion),
|
|
240
|
+
rawTranscriptAuthoritative: true,
|
|
241
|
+
enrichmentJoined: Boolean(thread || turn),
|
|
242
|
+
},
|
|
243
|
+
unknownFields: unknownFields(value, RAW_KEYS),
|
|
244
|
+
threadUnknownFields: thread ? unknownFields(thread, THREAD_KEYS) : {},
|
|
245
|
+
turnUnknownFields: turn ? unknownFields(turn, TURN_KEYS) : {},
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function recordKind(value) {
|
|
250
|
+
if (value.attachment)
|
|
251
|
+
return 'attachment';
|
|
252
|
+
if (value.tool) {
|
|
253
|
+
const status = String(value.tool.status ?? '');
|
|
254
|
+
return ['failed', 'error', 'completed'].includes(status) ? 'tool-result' : 'tool-call';
|
|
255
|
+
}
|
|
256
|
+
if (value.compacted)
|
|
257
|
+
return 'compaction';
|
|
258
|
+
if (value.interrupted)
|
|
259
|
+
return 'interruption';
|
|
260
|
+
if (value.type === 'message')
|
|
261
|
+
return 'message';
|
|
262
|
+
return `openhuman.${value.type}`;
|
|
263
|
+
}
|
|
264
|
+
function extractText(content, tool) {
|
|
265
|
+
if (typeof content === 'string')
|
|
266
|
+
return content;
|
|
267
|
+
if (tool && typeof tool.output === 'string')
|
|
268
|
+
return tool.output;
|
|
269
|
+
if (Array.isArray(content)) {
|
|
270
|
+
return content.map((item) => {
|
|
271
|
+
const record = item && typeof item === 'object' ? item : {};
|
|
272
|
+
return typeof record.text === 'string' ? record.text : '';
|
|
273
|
+
}).filter(Boolean).join('\n');
|
|
274
|
+
}
|
|
275
|
+
return '';
|
|
276
|
+
}
|
|
277
|
+
function version(value) {
|
|
278
|
+
if (typeof value === 'number')
|
|
279
|
+
return `${value}.0.0`;
|
|
280
|
+
return /^\d+$/.test(value) ? `${value}.0.0` : value;
|
|
281
|
+
}
|
|
282
|
+
function timestamp(value) {
|
|
283
|
+
if (typeof value === 'string') {
|
|
284
|
+
const date = new Date(value);
|
|
285
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
286
|
+
}
|
|
287
|
+
if (typeof value !== 'number')
|
|
288
|
+
return undefined;
|
|
289
|
+
const date = new Date(value < 10_000_000_000 ? value * 1_000 : value);
|
|
290
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
291
|
+
}
|
|
292
|
+
function unknownFields(value, keys) {
|
|
293
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => !keys.has(key)).sort(([a], [b]) => a.localeCompare(b)));
|
|
294
|
+
}
|
|
295
|
+
function parseCursor(value) {
|
|
296
|
+
if (!value)
|
|
297
|
+
return 0;
|
|
298
|
+
if (!/^\d+$/.test(value))
|
|
299
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'invalid OpenHuman cursor');
|
|
300
|
+
return Number(value);
|
|
301
|
+
}
|
|
302
|
+
const RAW_KEYS = new Set([
|
|
303
|
+
'schemaVersion', 'recordType', 'session_id', 'event_id', 'thread_id', 'request_id',
|
|
304
|
+
'parent_event_id', 'type', 'role', 'content', 'timestamp', 'metadata', 'input_tokens',
|
|
305
|
+
'cached_input_tokens', 'output_tokens', 'charged_amount_usd', 'compacted',
|
|
306
|
+
'interrupted', 'tool', 'attachment', 'active', 'raw_transcript_deleted_at',
|
|
307
|
+
]);
|
|
308
|
+
const THREAD_KEYS = new Set([
|
|
309
|
+
'schemaVersion', 'recordType', 'thread_id', 'title', 'state', 'deleted_at',
|
|
310
|
+
]);
|
|
311
|
+
const TURN_KEYS = new Set([
|
|
312
|
+
'schemaVersion', 'recordType', 'thread_id', 'request_id', 'state', 'interrupted',
|
|
313
|
+
'completed_at',
|
|
314
|
+
]);
|
|
315
|
+
//# sourceMappingURL=openhuman.js.map
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
4
|
+
import { readBoundedText } from '../readers.js';
|
|
5
|
+
export const WARP_ADAPTER_VERSION = '1.0.0';
|
|
6
|
+
export const WARP_MARKDOWN_SCHEMA_VERSION = '1.0.0';
|
|
7
|
+
export class WarpSessionAdapter {
|
|
8
|
+
limits;
|
|
9
|
+
provider = 'warp';
|
|
10
|
+
adapterVersion = WARP_ADAPTER_VERSION;
|
|
11
|
+
disposition = 'manual-only';
|
|
12
|
+
supportedOperations = ['inspect', 'stream'];
|
|
13
|
+
acquisitionModes = ['manual-export'];
|
|
14
|
+
constructor(limits) {
|
|
15
|
+
this.limits = limits;
|
|
16
|
+
}
|
|
17
|
+
async *discover(_scope) {
|
|
18
|
+
// Deliberately no internal store discovery. Users select a Markdown export.
|
|
19
|
+
}
|
|
20
|
+
async inspect(source) {
|
|
21
|
+
if (source.locatorClass === 'warp-internal-store'
|
|
22
|
+
|| source.locatorClass === 'warp-sqlite'
|
|
23
|
+
|| source.locatorClass === 'warp-protobuf') {
|
|
24
|
+
throw new SessionContractError('UNSUPPORTED_OPERATION', 'Warp internal store discovery is unsupported; use /export-to-file and select the Markdown export');
|
|
25
|
+
}
|
|
26
|
+
const parsed = await this.readSource(source);
|
|
27
|
+
return {
|
|
28
|
+
sourceSchemaVersion: parsed.schemaVersion,
|
|
29
|
+
consistency: 'complete',
|
|
30
|
+
operationalState: 'available',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async *stream(source, cursor) {
|
|
34
|
+
const parsed = await this.readSource(source);
|
|
35
|
+
const start = parseCursor(cursor?.value);
|
|
36
|
+
for (const record of parsed.records.slice(start))
|
|
37
|
+
yield record;
|
|
38
|
+
}
|
|
39
|
+
async readSource(source) {
|
|
40
|
+
if (source.locatorClass !== 'warp-markdown-export') {
|
|
41
|
+
throw new SessionContractError('UNSUPPORTED_OPERATION', 'Warp supports only an explicitly selected Markdown conversation export');
|
|
42
|
+
}
|
|
43
|
+
const { value } = await readBoundedText({
|
|
44
|
+
selectedPath: source.locator,
|
|
45
|
+
allowedRoots: source.authorizedScope.allowedRoots,
|
|
46
|
+
}, this.limits);
|
|
47
|
+
const schemaVersion = declaredSchema(value);
|
|
48
|
+
assertSupportedSchemaMajor(schemaVersion);
|
|
49
|
+
const blocks = parseBlocks(value);
|
|
50
|
+
if (blocks.length === 0) {
|
|
51
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Warp Markdown export contains no recognized user or assistant conversation blocks');
|
|
52
|
+
}
|
|
53
|
+
const stableSessionId = `manual:${createHash('sha256').update(value).digest('hex').slice(0, 32)}`;
|
|
54
|
+
const lifecycleEvidence = /<!--\s*warp-lifecycle:\s*complete\s*-->/i.test(value)
|
|
55
|
+
? 'completed-at-import' : 'unknown-at-import';
|
|
56
|
+
const title = /^#\s+(.+)$/m.exec(value)?.[1]?.trim();
|
|
57
|
+
return {
|
|
58
|
+
schemaVersion: WARP_MARKDOWN_SCHEMA_VERSION,
|
|
59
|
+
records: blocks.map((block, sequence) => ({
|
|
60
|
+
nativeSessionId: stableSessionId,
|
|
61
|
+
nativeEventId: undefined,
|
|
62
|
+
sequence,
|
|
63
|
+
kind: 'message',
|
|
64
|
+
role: block.role,
|
|
65
|
+
occurredAt: undefined,
|
|
66
|
+
text: block.text,
|
|
67
|
+
rawReference: { locatorClass: source.locatorClass, sequence },
|
|
68
|
+
extensions: {
|
|
69
|
+
lifecycle: lifecycleEvidence,
|
|
70
|
+
manualImport: true,
|
|
71
|
+
title,
|
|
72
|
+
heading: block.heading,
|
|
73
|
+
identity: {
|
|
74
|
+
nativeSessionIdKnown: false,
|
|
75
|
+
nativeEventIdKnown: false,
|
|
76
|
+
derivedSessionIdentity: true,
|
|
77
|
+
},
|
|
78
|
+
lossReport: {
|
|
79
|
+
lossless: false,
|
|
80
|
+
sourceFormat: 'markdown',
|
|
81
|
+
unknownFields: [
|
|
82
|
+
'nativeSessionId',
|
|
83
|
+
'nativeEventId',
|
|
84
|
+
'timestamp',
|
|
85
|
+
'model',
|
|
86
|
+
'toolStructure',
|
|
87
|
+
'attachmentStructure',
|
|
88
|
+
'tokenUsage',
|
|
89
|
+
'cost',
|
|
90
|
+
'workspace',
|
|
91
|
+
'lineage',
|
|
92
|
+
],
|
|
93
|
+
roleInference: 'heading-based',
|
|
94
|
+
lifecycleEvidence,
|
|
95
|
+
},
|
|
96
|
+
provenance: {
|
|
97
|
+
acquisition: 'user-selected-markdown-export',
|
|
98
|
+
schema: schemaVersion,
|
|
99
|
+
exportCommand: '/export-to-file',
|
|
100
|
+
internalStoreInspected: false,
|
|
101
|
+
originalFilename: basename(source.locator),
|
|
102
|
+
},
|
|
103
|
+
deletion: {
|
|
104
|
+
aiwgDeletionDoesNotDeleteWarpConversation: true,
|
|
105
|
+
exportDeletionDoesNotDeleteWarpConversation: true,
|
|
106
|
+
providerDeletionStateUnknown: true,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
})),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function declaredSchema(value) {
|
|
114
|
+
const match = /<!--\s*warp-conversation-export:\s*([^\s]+)\s*-->/i.exec(value);
|
|
115
|
+
if (!match)
|
|
116
|
+
return WARP_MARKDOWN_SCHEMA_VERSION;
|
|
117
|
+
const version = match[1];
|
|
118
|
+
return /^\d+$/.test(version) ? `${version}.0.0` : version;
|
|
119
|
+
}
|
|
120
|
+
function parseBlocks(value) {
|
|
121
|
+
const lines = value.replace(/\r\n/g, '\n').split('\n');
|
|
122
|
+
const blocks = [];
|
|
123
|
+
let current;
|
|
124
|
+
const flush = () => {
|
|
125
|
+
if (!current)
|
|
126
|
+
return;
|
|
127
|
+
const text = current.lines.join('\n').trim();
|
|
128
|
+
if (text)
|
|
129
|
+
blocks.push({ role: current.role, heading: current.heading, text });
|
|
130
|
+
};
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
const heading = /^#{2,6}\s+(.+?)\s*$/.exec(line);
|
|
133
|
+
const role = heading ? headingRole(heading[1]) : undefined;
|
|
134
|
+
if (role) {
|
|
135
|
+
flush();
|
|
136
|
+
current = { role, heading: heading[1].trim(), lines: [] };
|
|
137
|
+
}
|
|
138
|
+
else if (current && !/^<!--\s*warp-(?:conversation-export|lifecycle):/i.test(line)) {
|
|
139
|
+
current.lines.push(line);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
flush();
|
|
143
|
+
return blocks;
|
|
144
|
+
}
|
|
145
|
+
function headingRole(heading) {
|
|
146
|
+
const normalized = heading.trim().toLowerCase().replace(/[::]\s*$/, '');
|
|
147
|
+
if (/^(user|you|human|prompt|query)(?:\s+\d+)?$/.test(normalized))
|
|
148
|
+
return 'user';
|
|
149
|
+
if (/^(warp|assistant|agent|response|answer)(?:\s+\d+)?$/.test(normalized))
|
|
150
|
+
return 'assistant';
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
function parseCursor(value) {
|
|
154
|
+
if (!value)
|
|
155
|
+
return 0;
|
|
156
|
+
if (!/^\d+$/.test(value))
|
|
157
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Warp cursor');
|
|
158
|
+
return Number(value);
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=warp.js.map
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
4
|
+
import { readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
|
|
5
|
+
export const DEVIN_DESKTOP_ADAPTER_VERSION = '1.1.0';
|
|
6
|
+
export const DEVIN_DESKTOP_TRANSCRIPT_SCHEMA_VERSION = '1.0.0';
|
|
7
|
+
/** @deprecated Compatibility export. */
|
|
8
|
+
export const WINDSURF_ADAPTER_VERSION = DEVIN_DESKTOP_ADAPTER_VERSION;
|
|
9
|
+
/** @deprecated Compatibility export. */
|
|
10
|
+
export const WINDSURF_TRANSCRIPT_SCHEMA_VERSION = DEVIN_DESKTOP_TRANSCRIPT_SCHEMA_VERSION;
|
|
11
|
+
const StepSchema = z.object({
|
|
12
|
+
schema_version: z.string().optional(),
|
|
13
|
+
type: z.string().min(1),
|
|
14
|
+
status: z.string().min(1),
|
|
15
|
+
trajectory_id: z.string().min(1).optional(),
|
|
16
|
+
execution_id: z.string().min(1).optional(),
|
|
17
|
+
event_id: z.string().min(1).optional(),
|
|
18
|
+
timestamp: z.string().optional(),
|
|
19
|
+
model_name: z.string().optional(),
|
|
20
|
+
model: z.union([z.string(), z.record(z.unknown())]).optional(),
|
|
21
|
+
sensitive_content_warning: z.union([z.boolean(), z.string(), z.record(z.unknown())]).optional(),
|
|
22
|
+
user_input: z.record(z.unknown()).optional(),
|
|
23
|
+
planner_response: z.record(z.unknown()).optional(),
|
|
24
|
+
code_action: z.record(z.unknown()).optional(),
|
|
25
|
+
tool_info: z.record(z.unknown()).optional(),
|
|
26
|
+
}).passthrough();
|
|
27
|
+
export class DevinDesktopSessionAdapter {
|
|
28
|
+
limits;
|
|
29
|
+
provider = 'devin-desktop';
|
|
30
|
+
adapterVersion = DEVIN_DESKTOP_ADAPTER_VERSION;
|
|
31
|
+
disposition = 'implemented';
|
|
32
|
+
supportedOperations = ['inspect', 'stream'];
|
|
33
|
+
acquisitionModes = ['hook', 'jsonl'];
|
|
34
|
+
constructor(limits) {
|
|
35
|
+
this.limits = limits;
|
|
36
|
+
}
|
|
37
|
+
async *discover(_scope) {
|
|
38
|
+
// Deliberately empty: hook enablement and transcript selection are explicit user actions.
|
|
39
|
+
}
|
|
40
|
+
async inspect(source) {
|
|
41
|
+
const parsed = await this.readSource(source);
|
|
42
|
+
return {
|
|
43
|
+
sourceSchemaVersion: parsed.schemaVersion,
|
|
44
|
+
consistency: 'provisional',
|
|
45
|
+
operationalState: 'available',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async *stream(source, cursor) {
|
|
49
|
+
if (!isCurrentHookLocator(source.locatorClass)) {
|
|
50
|
+
// Preserve the precise unsupported-operation diagnostics.
|
|
51
|
+
await this.readSource(source);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const start = parseCursor(cursor?.value);
|
|
55
|
+
const input = await streamBoundedJsonLines({
|
|
56
|
+
selectedPath: source.locator,
|
|
57
|
+
allowedRoots: source.authorizedScope.allowedRoots,
|
|
58
|
+
}, { consistency: 'provisional', limits: this.limits });
|
|
59
|
+
let schemaVersion = null;
|
|
60
|
+
let nativeSessionId = null;
|
|
61
|
+
let seen = 0;
|
|
62
|
+
for await (const line of input) {
|
|
63
|
+
const parsed = StepSchema.safeParse(line.value);
|
|
64
|
+
if (!parsed.success) {
|
|
65
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Windsurf transcript step is malformed');
|
|
66
|
+
}
|
|
67
|
+
const step = parsed.data;
|
|
68
|
+
const currentVersion = step.schema_version ?? DEVIN_DESKTOP_TRANSCRIPT_SCHEMA_VERSION;
|
|
69
|
+
if (schemaVersion && schemaVersion !== currentVersion) {
|
|
70
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Windsurf transcript schemas');
|
|
71
|
+
}
|
|
72
|
+
schemaVersion = currentVersion;
|
|
73
|
+
assertSupportedSchemaMajor(schemaVersion);
|
|
74
|
+
const currentSession = step.trajectory_id
|
|
75
|
+
?? nativeSessionId
|
|
76
|
+
?? basename(source.locator, '.jsonl');
|
|
77
|
+
if (step.trajectory_id && nativeSessionId && step.trajectory_id !== nativeSessionId) {
|
|
78
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Windsurf trajectory identities');
|
|
79
|
+
}
|
|
80
|
+
nativeSessionId = currentSession;
|
|
81
|
+
if (seen++ < start)
|
|
82
|
+
continue;
|
|
83
|
+
yield normalizeStep(step, currentSession, line.sequence, line.byteOffset, canonicalLocatorClass(source.locatorClass), schemaVersion);
|
|
84
|
+
}
|
|
85
|
+
if (seen === 0) {
|
|
86
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Windsurf transcript is empty or malformed');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async readSource(source) {
|
|
90
|
+
if (isLegacyLocator(source.locatorClass)) {
|
|
91
|
+
throw new SessionContractError('UNSUPPORTED_OPERATION', 'legacy Devin Desktop/Windsurf protobuf stores are unsupported; enable post_cascade_response_with_transcript and select its JSONL output');
|
|
92
|
+
}
|
|
93
|
+
if (!isCurrentHookLocator(source.locatorClass)) {
|
|
94
|
+
throw new SessionContractError('UNSUPPORTED_OPERATION', 'Devin Desktop (Windsurf compatibility) requires an explicitly selected post_cascade_response_with_transcript JSONL file');
|
|
95
|
+
}
|
|
96
|
+
const input = await readBoundedJsonLines({
|
|
97
|
+
selectedPath: source.locator,
|
|
98
|
+
allowedRoots: source.authorizedScope.allowedRoots,
|
|
99
|
+
}, { consistency: 'provisional', limits: this.limits });
|
|
100
|
+
if (input.records.length === 0) {
|
|
101
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Windsurf transcript is empty or malformed');
|
|
102
|
+
}
|
|
103
|
+
const steps = input.records.map((record) => {
|
|
104
|
+
const parsed = StepSchema.safeParse(record.value);
|
|
105
|
+
if (!parsed.success) {
|
|
106
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Windsurf transcript step is malformed');
|
|
107
|
+
}
|
|
108
|
+
return { ...record, value: parsed.data };
|
|
109
|
+
});
|
|
110
|
+
const versions = new Set(steps.map(({ value }) => value.schema_version ?? DEVIN_DESKTOP_TRANSCRIPT_SCHEMA_VERSION));
|
|
111
|
+
if (versions.size !== 1) {
|
|
112
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Windsurf transcript schemas');
|
|
113
|
+
}
|
|
114
|
+
const schemaVersion = [...versions][0];
|
|
115
|
+
assertSupportedSchemaMajor(schemaVersion);
|
|
116
|
+
const trajectoryIds = new Set(steps.flatMap(({ value }) => value.trajectory_id ? [value.trajectory_id] : []));
|
|
117
|
+
if (trajectoryIds.size > 1) {
|
|
118
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'mixed Windsurf trajectory identities');
|
|
119
|
+
}
|
|
120
|
+
const nativeSessionId = [...trajectoryIds][0] ?? basename(source.locator, '.jsonl');
|
|
121
|
+
return {
|
|
122
|
+
schemaVersion,
|
|
123
|
+
records: steps.map(({ value, sequence, byteOffset }) => normalizeStep(value, nativeSessionId, sequence, byteOffset, canonicalLocatorClass(source.locatorClass), schemaVersion)),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** @deprecated Use DevinDesktopSessionAdapter; retained through the alias window. */
|
|
128
|
+
export class WindsurfSessionAdapter extends DevinDesktopSessionAdapter {
|
|
129
|
+
}
|
|
130
|
+
function isCurrentHookLocator(value) {
|
|
131
|
+
return value === 'devin-desktop-cascade-hook-jsonl'
|
|
132
|
+
|| value === 'windsurf-cascade-hook-jsonl';
|
|
133
|
+
}
|
|
134
|
+
function isLegacyLocator(value) {
|
|
135
|
+
return value === 'devin-desktop-legacy-protobuf'
|
|
136
|
+
|| value === 'windsurf-legacy-protobuf';
|
|
137
|
+
}
|
|
138
|
+
function canonicalLocatorClass(value) {
|
|
139
|
+
return value.startsWith('windsurf-')
|
|
140
|
+
? `devin-desktop-${value.slice('windsurf-'.length)}`
|
|
141
|
+
: value;
|
|
142
|
+
}
|
|
143
|
+
function normalizeStep(step, nativeSessionId, sequence, offset, locatorClass, schemaVersion) {
|
|
144
|
+
const text = stepText(step);
|
|
145
|
+
return {
|
|
146
|
+
nativeSessionId,
|
|
147
|
+
nativeEventId: step.event_id ?? (step.execution_id
|
|
148
|
+
? `${step.execution_id}:${sequence}` : `${nativeSessionId}:${sequence}`),
|
|
149
|
+
sequence,
|
|
150
|
+
kind: `devin-desktop.${step.type}`,
|
|
151
|
+
role: step.type === 'user_input' ? 'user'
|
|
152
|
+
: step.type === 'planner_response' ? 'assistant' : 'tool',
|
|
153
|
+
participant: step.type === 'user_input' ? 'user'
|
|
154
|
+
: step.type === 'planner_response' ? 'assistant' : 'tool',
|
|
155
|
+
model: step.model_name ?? (typeof step.model === 'string' ? step.model : undefined),
|
|
156
|
+
occurredAt: timestamp(step.timestamp),
|
|
157
|
+
text,
|
|
158
|
+
rawReference: { locatorClass, offset, sequence },
|
|
159
|
+
extensions: {
|
|
160
|
+
status: step.status,
|
|
161
|
+
trajectoryId: step.trajectory_id ?? nativeSessionId,
|
|
162
|
+
executionId: step.execution_id,
|
|
163
|
+
model: step.model_name ?? step.model,
|
|
164
|
+
sensitiveContentWarning: step.sensitive_content_warning ?? true,
|
|
165
|
+
nativeStep: step,
|
|
166
|
+
provenance: {
|
|
167
|
+
acquisition: 'user-enabled-post_cascade_response_with_transcript',
|
|
168
|
+
product: 'Devin Desktop',
|
|
169
|
+
providerId: 'devin-desktop',
|
|
170
|
+
compatibilityProviderId: 'windsurf',
|
|
171
|
+
captureBoundary: 'completed-cascade-response',
|
|
172
|
+
schema: schemaVersion,
|
|
173
|
+
optInRequired: true,
|
|
174
|
+
hookConfiguredByAiWG: false,
|
|
175
|
+
credentialsInspected: false,
|
|
176
|
+
environmentSecretsInspected: false,
|
|
177
|
+
liveTokenCapture: false,
|
|
178
|
+
completeHistoricalCapture: false,
|
|
179
|
+
providerRetention: { maximumFiles: 100, evictionOrder: 'oldest-mtime' },
|
|
180
|
+
},
|
|
181
|
+
deletion: {
|
|
182
|
+
aiwgDeletionDoesNotDeleteWindsurfTranscript: true,
|
|
183
|
+
providerConversationDeletionStateUnknown: true,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function stepText(step) {
|
|
189
|
+
const candidate = step.type === 'user_input'
|
|
190
|
+
? step.user_input?.user_response
|
|
191
|
+
: step.type === 'planner_response'
|
|
192
|
+
? step.planner_response?.response
|
|
193
|
+
: step.code_action?.new_content ?? step.tool_info?.response;
|
|
194
|
+
return typeof candidate === 'string' ? candidate : '';
|
|
195
|
+
}
|
|
196
|
+
function timestamp(value) {
|
|
197
|
+
if (!value)
|
|
198
|
+
return undefined;
|
|
199
|
+
const date = new Date(value);
|
|
200
|
+
if (Number.isNaN(date.getTime())) {
|
|
201
|
+
throw new SessionContractError('AMBIGUOUS_TIMESTAMP', 'invalid Windsurf timestamp');
|
|
202
|
+
}
|
|
203
|
+
return date.toISOString();
|
|
204
|
+
}
|
|
205
|
+
function parseCursor(value) {
|
|
206
|
+
if (!value)
|
|
207
|
+
return 0;
|
|
208
|
+
if (!/^\d+$/.test(value))
|
|
209
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Windsurf cursor');
|
|
210
|
+
return Number(value);
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=windsurf.js.map
|