@aiwg/cli 2026.7.20 → 2026.7.23
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 +18 -7
- package/dist/src/api/index.d.ts +2 -0
- package/dist/src/api/index.js +2 -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 +1265 -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/config/aiwg-config.js +12 -0
- package/dist/src/config/cli.js +16 -3
- package/dist/src/extensions/commands/definitions.js +29 -0
- package/dist/src/extensions/manifest.js +29 -0
- package/dist/src/security/threat-assessment-config.js +296 -0
- package/dist/src/sessions/adapters/claude.js +385 -0
- package/dist/src/sessions/adapters/codex.js +548 -0
- package/dist/src/sessions/adapters/copilot.js +226 -0
- package/dist/src/sessions/adapters/cursor.js +528 -0
- package/dist/src/sessions/adapters/factory.js +386 -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/batch-contracts.js +121 -0
- package/dist/src/sessions/batch-import.js +265 -0
- package/dist/src/sessions/candidates.js +210 -0
- package/dist/src/sessions/contracts.js +337 -0
- package/dist/src/sessions/discovery.js +51 -0
- package/dist/src/sessions/fixtures.js +12 -0
- package/dist/src/sessions/import-lease.js +152 -0
- package/dist/src/sessions/importer.js +464 -0
- package/dist/src/sessions/index.js +31 -0
- package/dist/src/sessions/knowledge-shard.js +61 -0
- package/dist/src/sessions/optional-backends.js +238 -0
- package/dist/src/sessions/origin.js +117 -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 +1892 -0
- package/dist/src/sessions/timeline.js +148 -0
- package/dist/src/sessions/workspace-discovery.js +319 -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,337 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export const SESSION_CONTRACT_VERSION = '1.0.0';
|
|
4
|
+
export const SESSION_PROVIDER_IDS = [
|
|
5
|
+
'claude', 'codex', 'copilot', 'cursor', 'factory', 'hermes',
|
|
6
|
+
'opencode', 'openclaw', 'openhuman', 'warp', 'devin-desktop', 'generic',
|
|
7
|
+
];
|
|
8
|
+
export const SessionProviderIdSchema = z.enum(SESSION_PROVIDER_IDS);
|
|
9
|
+
export const SESSION_PROVIDER_ALIASES = Object.freeze({
|
|
10
|
+
windsurf: 'devin-desktop',
|
|
11
|
+
});
|
|
12
|
+
const CompatibleSessionProviderIdSchema = z.preprocess((value) => typeof value === 'string'
|
|
13
|
+
? (SESSION_PROVIDER_ALIASES[value] ?? value)
|
|
14
|
+
: value, SessionProviderIdSchema);
|
|
15
|
+
export const CapabilityDispositionSchema = z.enum([
|
|
16
|
+
'implemented', 'manual-only', 'degraded', 'unsupported',
|
|
17
|
+
]);
|
|
18
|
+
export const OperationalStateSchema = z.enum([
|
|
19
|
+
'available', 'unavailable', 'inaccessible', 'version-unknown',
|
|
20
|
+
'schema-unsupported', 'degraded',
|
|
21
|
+
]);
|
|
22
|
+
export const ConsistencyStateSchema = z.enum([
|
|
23
|
+
'provisional', 'consistent-snapshot', 'complete',
|
|
24
|
+
]);
|
|
25
|
+
export const SessionErrorCodeSchema = z.enum([
|
|
26
|
+
'UNKNOWN_PROVIDER', 'UNKNOWN_SCHEMA_MAJOR', 'SOURCE_NOT_AUTHORIZED',
|
|
27
|
+
'SOURCE_OUTSIDE_ALLOWED_ROOT', 'SOURCE_SYMLINK', 'SOURCE_NOT_REGULAR_FILE',
|
|
28
|
+
'RESOURCE_LIMIT_EXCEEDED', 'NETWORK_NOT_AUTHORIZED', 'SCHEMA_DRIFT',
|
|
29
|
+
'OPERATION_NOT_AUTHORIZED',
|
|
30
|
+
'IMPORT_CONFLICT', 'IMPORT_INTERRUPTED', 'MALFORMED_SOURCE',
|
|
31
|
+
'DUPLICATE_NATIVE_ID', 'AMBIGUOUS_TIMESTAMP', 'TRUNCATED_SOURCE',
|
|
32
|
+
'UNSUPPORTED_OPERATION',
|
|
33
|
+
'INVALID_SEARCH_QUERY', 'INVALID_ARGUMENT',
|
|
34
|
+
]);
|
|
35
|
+
const VersionSchema = z.string().regex(/^\d+\.\d+\.\d+(?:[-+].+)?$/);
|
|
36
|
+
const DigestSchema = z.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
37
|
+
const NativeExtensionsSchema = z.record(z.unknown()).superRefine((value, context) => {
|
|
38
|
+
for (const key of Object.keys(value)) {
|
|
39
|
+
if (!key.startsWith('native.')) {
|
|
40
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: `native extension must use native.<provider>: ${key}` });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
export const SessionSourceSchema = z.object({
|
|
45
|
+
contractVersion: z.literal(SESSION_CONTRACT_VERSION),
|
|
46
|
+
sourceId: z.string().min(1),
|
|
47
|
+
provider: CompatibleSessionProviderIdSchema,
|
|
48
|
+
providerProfile: z.string().min(1),
|
|
49
|
+
locatorClass: z.string().min(1),
|
|
50
|
+
redactedLocator: z.string().min(1),
|
|
51
|
+
adapterVersion: VersionSchema,
|
|
52
|
+
sourceSchemaVersion: VersionSchema,
|
|
53
|
+
disposition: CapabilityDispositionSchema,
|
|
54
|
+
operationalState: OperationalStateSchema,
|
|
55
|
+
consistency: ConsistencyStateSchema,
|
|
56
|
+
authorizedAt: z.string().datetime({ offset: true }),
|
|
57
|
+
extensions: NativeExtensionsSchema.default({}),
|
|
58
|
+
});
|
|
59
|
+
export const SessionEventSchema = z.object({
|
|
60
|
+
contractVersion: z.literal(SESSION_CONTRACT_VERSION),
|
|
61
|
+
eventId: z.string().min(1),
|
|
62
|
+
sessionId: z.string().min(1),
|
|
63
|
+
sourceId: z.string().min(1),
|
|
64
|
+
importRunId: z.string().min(1),
|
|
65
|
+
nativeId: z.string().min(1).nullable(),
|
|
66
|
+
sequence: z.number().int().nonnegative(),
|
|
67
|
+
kind: z.string().min(1),
|
|
68
|
+
role: z.string().min(1).nullable(),
|
|
69
|
+
participant: z.string().min(1).nullable().default(null),
|
|
70
|
+
toolName: z.string().min(1).nullable().default(null),
|
|
71
|
+
toolCallId: z.string().min(1).nullable().default(null),
|
|
72
|
+
model: z.string().min(1).nullable().default(null),
|
|
73
|
+
entities: z.array(z.string().min(1)).default([]),
|
|
74
|
+
extractionState: z.string().min(1).nullable().default(null),
|
|
75
|
+
occurredAt: z.string().datetime({ offset: true }).nullable(),
|
|
76
|
+
activityBoundary: z.enum(['pause', 'resume', 'continuation', 'end']).nullable().default(null),
|
|
77
|
+
activityBoundaryBasis: z.string().min(1).nullable().default(null),
|
|
78
|
+
activityBoundaryConfidence: z.enum(['low', 'medium', 'high']).nullable().default(null),
|
|
79
|
+
origin: z.enum([
|
|
80
|
+
'user-authored', 'assistant-generated', 'provider-bootstrap',
|
|
81
|
+
'workspace-instruction', 'tool-control', 'unknown',
|
|
82
|
+
]).default('unknown'),
|
|
83
|
+
originRule: z.string().min(1).default('legacy:unknown'),
|
|
84
|
+
originClassifierVersion: VersionSchema.default('1.0.0'),
|
|
85
|
+
searchableText: z.string(),
|
|
86
|
+
digest: DigestSchema,
|
|
87
|
+
rawReference: z.object({
|
|
88
|
+
locatorClass: z.string().min(1),
|
|
89
|
+
offset: z.number().int().nonnegative().optional(),
|
|
90
|
+
sequence: z.number().int().nonnegative().optional(),
|
|
91
|
+
}),
|
|
92
|
+
adapterVersion: VersionSchema,
|
|
93
|
+
consistency: ConsistencyStateSchema,
|
|
94
|
+
sensitivity: z.object({
|
|
95
|
+
classification: z.enum(['none', 'sensitive']),
|
|
96
|
+
classes: z.array(z.string().min(1)),
|
|
97
|
+
}),
|
|
98
|
+
opaque: z.boolean().default(false),
|
|
99
|
+
extensions: NativeExtensionsSchema.default({}),
|
|
100
|
+
});
|
|
101
|
+
export const SessionSchema = z.object({
|
|
102
|
+
contractVersion: z.literal(SESSION_CONTRACT_VERSION),
|
|
103
|
+
sessionId: z.string().min(1),
|
|
104
|
+
sourceId: z.string().min(1),
|
|
105
|
+
provider: CompatibleSessionProviderIdSchema,
|
|
106
|
+
nativeSessionId: z.string().min(1),
|
|
107
|
+
workspaceId: z.string().min(1),
|
|
108
|
+
startedAt: z.string().datetime({ offset: true }).nullable(),
|
|
109
|
+
updatedAt: z.string().datetime({ offset: true }).nullable(),
|
|
110
|
+
consistency: ConsistencyStateSchema,
|
|
111
|
+
lifecycle: z.enum([
|
|
112
|
+
'active', 'inactive', 'paused', 'complete', 'interrupted', 'archived',
|
|
113
|
+
'unknown', 'tombstoned',
|
|
114
|
+
]),
|
|
115
|
+
intent: z.object({
|
|
116
|
+
status: z.enum(['selected', 'absent', 'unknown']),
|
|
117
|
+
eventId: z.string().min(1).nullable(),
|
|
118
|
+
sequence: z.number().int().nonnegative().nullable(),
|
|
119
|
+
title: z.string().nullable(),
|
|
120
|
+
summary: z.string().nullable(),
|
|
121
|
+
}).default({
|
|
122
|
+
status: 'unknown',
|
|
123
|
+
eventId: null,
|
|
124
|
+
sequence: null,
|
|
125
|
+
title: null,
|
|
126
|
+
summary: null,
|
|
127
|
+
}),
|
|
128
|
+
sourceDigest: DigestSchema,
|
|
129
|
+
extensions: NativeExtensionsSchema.default({}),
|
|
130
|
+
});
|
|
131
|
+
export const ImportCheckpointSchema = z.object({
|
|
132
|
+
cursor: z.string(),
|
|
133
|
+
recordsRead: z.number().int().nonnegative(),
|
|
134
|
+
bytesRead: z.number().int().nonnegative(),
|
|
135
|
+
checkpointVersion: z.literal('2').optional(),
|
|
136
|
+
positionKind: z.enum(['record-index', 'byte-offset', 'provider-native']).optional(),
|
|
137
|
+
sourceGeneration: z.string().min(1).optional(),
|
|
138
|
+
locatorClass: z.string().min(1).optional(),
|
|
139
|
+
adapterVersion: VersionSchema.optional(),
|
|
140
|
+
sourceSchemaVersion: VersionSchema.optional(),
|
|
141
|
+
policyVersion: VersionSchema.optional(),
|
|
142
|
+
continuity: z.enum([
|
|
143
|
+
'new-generation',
|
|
144
|
+
'validated-append',
|
|
145
|
+
'unchanged-replay',
|
|
146
|
+
'unverified',
|
|
147
|
+
]).optional(),
|
|
148
|
+
sourceSize: z.number().int().nonnegative().optional(),
|
|
149
|
+
sourceMtimeMs: z.number().nonnegative().optional(),
|
|
150
|
+
sourceFileIdentity: z.string().min(1).optional(),
|
|
151
|
+
prefixDigest: DigestSchema.optional(),
|
|
152
|
+
});
|
|
153
|
+
export const ImportRunSchema = z.object({
|
|
154
|
+
contractVersion: z.literal(SESSION_CONTRACT_VERSION),
|
|
155
|
+
importRunId: z.string().min(1),
|
|
156
|
+
sourceId: z.string().min(1),
|
|
157
|
+
parserVersion: VersionSchema,
|
|
158
|
+
policyVersion: VersionSchema,
|
|
159
|
+
sourceSchemaVersion: VersionSchema,
|
|
160
|
+
consistency: ConsistencyStateSchema,
|
|
161
|
+
status: z.enum(['running', 'committed', 'rolled-back', 'failed']),
|
|
162
|
+
checkpoint: ImportCheckpointSchema,
|
|
163
|
+
startedAt: z.string().datetime({ offset: true }),
|
|
164
|
+
completedAt: z.string().datetime({ offset: true }).nullable(),
|
|
165
|
+
errorCode: SessionErrorCodeSchema.nullable(),
|
|
166
|
+
});
|
|
167
|
+
export const ProvenanceEdgeSchema = z.object({
|
|
168
|
+
contractVersion: z.literal(SESSION_CONTRACT_VERSION),
|
|
169
|
+
edgeId: z.string().min(1),
|
|
170
|
+
relation: z.enum([
|
|
171
|
+
'acquired-from', 'normalized-from', 'derived-from', 'promoted-to',
|
|
172
|
+
'supersedes', 'invalidates',
|
|
173
|
+
]),
|
|
174
|
+
fromId: z.string().min(1),
|
|
175
|
+
toId: z.string().min(1),
|
|
176
|
+
importRunId: z.string().min(1),
|
|
177
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
178
|
+
});
|
|
179
|
+
export const IntelligenceCandidateTypeSchema = z.enum([
|
|
180
|
+
'decision', 'requirement', 'constraint', 'preference', 'task', 'discovery',
|
|
181
|
+
'fix', 'failed-approach', 'procedure', 'risk', 'contradiction', 'question',
|
|
182
|
+
'entity', 'relationship',
|
|
183
|
+
]);
|
|
184
|
+
export const CandidateSecurityWarningSchema = z.enum([
|
|
185
|
+
'instruction-like', 'structure-breaking', 'control-character', 'bidi-control',
|
|
186
|
+
'unicode-confusable', 'active-content', 'secret-bearing',
|
|
187
|
+
]);
|
|
188
|
+
export const CandidateSecuritySchema = z.object({
|
|
189
|
+
disposition: z.enum(['clear', 'suspicious']),
|
|
190
|
+
warnings: z.array(CandidateSecurityWarningSchema),
|
|
191
|
+
requiresAcknowledgement: z.boolean(),
|
|
192
|
+
acknowledged: z.boolean(),
|
|
193
|
+
policyVersion: VersionSchema,
|
|
194
|
+
}).strict();
|
|
195
|
+
export const IntelligenceCandidateSchema = z.object({
|
|
196
|
+
contractVersion: z.literal(SESSION_CONTRACT_VERSION),
|
|
197
|
+
candidateId: z.string().min(1),
|
|
198
|
+
version: z.number().int().positive(),
|
|
199
|
+
type: IntelligenceCandidateTypeSchema,
|
|
200
|
+
assertion: z.string().min(1),
|
|
201
|
+
subject: z.string().min(1).nullable(),
|
|
202
|
+
predicate: z.string().min(1).nullable(),
|
|
203
|
+
object: z.string().min(1).nullable(),
|
|
204
|
+
evidence: z.array(z.object({
|
|
205
|
+
eventId: z.string().min(1),
|
|
206
|
+
start: z.number().int().nonnegative(),
|
|
207
|
+
end: z.number().int().positive(),
|
|
208
|
+
quoteDigest: DigestSchema,
|
|
209
|
+
quote: z.string().min(1).optional(),
|
|
210
|
+
}).strict()).min(1),
|
|
211
|
+
confidence: z.number().min(0).max(1),
|
|
212
|
+
temporalScope: z.string().min(1),
|
|
213
|
+
projectScope: z.string().min(1),
|
|
214
|
+
extractionMethod: z.string().min(1),
|
|
215
|
+
extractionVersion: VersionSchema,
|
|
216
|
+
extractionPolicyVersion: VersionSchema,
|
|
217
|
+
model: z.string().min(1).nullable(),
|
|
218
|
+
sensitivity: z.enum(['none', 'sensitive']),
|
|
219
|
+
security: CandidateSecuritySchema.default({
|
|
220
|
+
disposition: 'clear',
|
|
221
|
+
warnings: [],
|
|
222
|
+
requiresAcknowledgement: false,
|
|
223
|
+
acknowledged: false,
|
|
224
|
+
policyVersion: '1.0.0',
|
|
225
|
+
}),
|
|
226
|
+
reviewState: z.enum([
|
|
227
|
+
'pending', 'accepted', 'rejected', 'deferred', 'promoted', 'superseded',
|
|
228
|
+
]),
|
|
229
|
+
conflictsWith: z.array(z.string().min(1)).default([]),
|
|
230
|
+
supersedes: z.array(z.string().min(1)).default([]),
|
|
231
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
232
|
+
}).strict().superRefine((candidate, context) => {
|
|
233
|
+
if (candidate.type === 'relationship'
|
|
234
|
+
&& (!candidate.subject || !candidate.predicate || !candidate.object)) {
|
|
235
|
+
context.addIssue({
|
|
236
|
+
code: z.ZodIssueCode.custom,
|
|
237
|
+
message: 'relationship candidates require subject, predicate, and object',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
export const CandidateReviewReceiptSchema = z.object({
|
|
242
|
+
contractVersion: z.literal(SESSION_CONTRACT_VERSION),
|
|
243
|
+
receiptId: z.string().min(1),
|
|
244
|
+
candidateId: z.string().min(1),
|
|
245
|
+
candidateVersion: z.number().int().positive(),
|
|
246
|
+
fromState: z.enum([
|
|
247
|
+
'pending', 'accepted', 'rejected', 'deferred', 'promoted', 'superseded',
|
|
248
|
+
]),
|
|
249
|
+
toState: z.enum([
|
|
250
|
+
'pending', 'accepted', 'rejected', 'deferred', 'promoted', 'superseded',
|
|
251
|
+
]),
|
|
252
|
+
reviewer: z.string().min(1),
|
|
253
|
+
reason: z.string().min(1),
|
|
254
|
+
securityWarnings: z.array(CandidateSecurityWarningSchema).default([]),
|
|
255
|
+
securityAcknowledged: z.boolean().default(false),
|
|
256
|
+
occurredAt: z.string().datetime({ offset: true }),
|
|
257
|
+
}).strict();
|
|
258
|
+
export const PromotionReceiptSchema = z.object({
|
|
259
|
+
contractVersion: z.literal(SESSION_CONTRACT_VERSION),
|
|
260
|
+
receiptId: z.string().min(1),
|
|
261
|
+
operationId: z.string().min(1),
|
|
262
|
+
candidateId: z.string().min(1),
|
|
263
|
+
candidateVersion: z.number().int().positive(),
|
|
264
|
+
consumer: z.string().min(1),
|
|
265
|
+
destinationRef: z.string().min(1),
|
|
266
|
+
reviewer: z.string().min(1),
|
|
267
|
+
approvedAt: z.string().datetime({ offset: true }),
|
|
268
|
+
evidenceEventIds: z.array(z.string().min(1)).min(1),
|
|
269
|
+
conflictsWith: z.array(z.string().min(1)),
|
|
270
|
+
supersedes: z.array(z.string().min(1)),
|
|
271
|
+
beforeHash: DigestSchema.nullable(),
|
|
272
|
+
afterHash: DigestSchema,
|
|
273
|
+
dryRun: z.boolean(),
|
|
274
|
+
duplicate: z.boolean(),
|
|
275
|
+
}).strict();
|
|
276
|
+
export const DeletionReceiptSchema = z.object({
|
|
277
|
+
contractVersion: z.literal(SESSION_CONTRACT_VERSION),
|
|
278
|
+
receiptId: z.string().min(1),
|
|
279
|
+
operationId: z.string().min(1),
|
|
280
|
+
scopeClass: z.string().min(1),
|
|
281
|
+
counts: z.record(z.number().int().nonnegative()),
|
|
282
|
+
survivingDependentIds: z.array(z.string().min(1)),
|
|
283
|
+
actorClass: z.string().min(1),
|
|
284
|
+
reasonCode: z.string().min(1),
|
|
285
|
+
orphanCounts: z.record(z.number().int().nonnegative()),
|
|
286
|
+
outcome: z.enum(['preview', 'committed', 'failed']),
|
|
287
|
+
occurredAt: z.string().datetime({ offset: true }),
|
|
288
|
+
}).strict();
|
|
289
|
+
export const PromotionDependencyDecisionSchema = z.object({
|
|
290
|
+
dependentId: z.string().min(1),
|
|
291
|
+
action: z.enum([
|
|
292
|
+
'revoke', 'supersede', 'retain', 'origin_unavailable', 'delete', 'abort',
|
|
293
|
+
]),
|
|
294
|
+
basis: z.string().min(1),
|
|
295
|
+
}).strict();
|
|
296
|
+
export function assertSessionProviderId(value) {
|
|
297
|
+
const canonical = SESSION_PROVIDER_ALIASES[value] ?? value;
|
|
298
|
+
const parsed = SessionProviderIdSchema.safeParse(canonical);
|
|
299
|
+
if (!parsed.success)
|
|
300
|
+
throw new SessionContractError('UNKNOWN_PROVIDER', `unknown session provider: ${value}`);
|
|
301
|
+
return parsed.data;
|
|
302
|
+
}
|
|
303
|
+
export function assertSupportedSchemaMajor(version, supportedMajor = 1) {
|
|
304
|
+
const match = /^(\d+)\./.exec(version);
|
|
305
|
+
if (!match || Number(match[1]) !== supportedMajor) {
|
|
306
|
+
throw new SessionContractError('UNKNOWN_SCHEMA_MAJOR', `unsupported session schema major: ${version}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
export function stableSessionId(provider, sourceId, nativeSessionId) {
|
|
310
|
+
const canonical = assertSessionProviderId(provider);
|
|
311
|
+
// Preserve the legacy identity seed through the published compatibility
|
|
312
|
+
// window so existing Windsurf catalogs do not duplicate normalized rows.
|
|
313
|
+
const identitySeed = canonical === 'devin-desktop' ? 'windsurf' : canonical;
|
|
314
|
+
return stableId('session', identitySeed, sourceId, nativeSessionId);
|
|
315
|
+
}
|
|
316
|
+
export function stableEventId(provider, sourceId, record, digest) {
|
|
317
|
+
const canonical = assertSessionProviderId(provider);
|
|
318
|
+
const identityScheme = 'event-v2-native-scope';
|
|
319
|
+
return record.nativeEventId
|
|
320
|
+
? stableId('event', identityScheme, canonical, sourceId, record.nativeSessionId, record.nativeEventId, record.sequence)
|
|
321
|
+
: stableId('event', identityScheme, canonical, sourceId, record.nativeSessionId, record.sequence, record.kind, digest);
|
|
322
|
+
}
|
|
323
|
+
export function sha256(value) {
|
|
324
|
+
return `sha256:${createHash('sha256').update(value).digest('hex')}`;
|
|
325
|
+
}
|
|
326
|
+
function stableId(prefix, ...parts) {
|
|
327
|
+
return `${prefix}_${createHash('sha256').update(parts.join('\0')).digest('hex')}`;
|
|
328
|
+
}
|
|
329
|
+
export class SessionContractError extends Error {
|
|
330
|
+
code;
|
|
331
|
+
constructor(code, message) {
|
|
332
|
+
super(message);
|
|
333
|
+
this.code = code;
|
|
334
|
+
this.name = 'SessionContractError';
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=contracts.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { SessionContractError, assertSessionProviderId, } from './contracts.js';
|
|
2
|
+
export class SessionSourceAdapterRegistry {
|
|
3
|
+
adapters = new Map();
|
|
4
|
+
register(adapter) {
|
|
5
|
+
const provider = assertSessionProviderId(adapter.provider);
|
|
6
|
+
if (this.adapters.has(provider))
|
|
7
|
+
throw new Error(`session adapter already registered: ${provider}`);
|
|
8
|
+
this.adapters.set(provider, adapter);
|
|
9
|
+
}
|
|
10
|
+
get(providerInput) {
|
|
11
|
+
const provider = assertSessionProviderId(providerInput);
|
|
12
|
+
const adapter = this.adapters.get(provider);
|
|
13
|
+
if (!adapter) {
|
|
14
|
+
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', `no session adapter is registered for ${provider}`);
|
|
15
|
+
}
|
|
16
|
+
return adapter;
|
|
17
|
+
}
|
|
18
|
+
assertOperation(providerInput, operation) {
|
|
19
|
+
const adapter = this.get(providerInput);
|
|
20
|
+
if (!adapter.supportedOperations.includes(operation)) {
|
|
21
|
+
throw new SessionContractError('UNSUPPORTED_OPERATION', `${operation} is unsupported for provider ${adapter.provider}`);
|
|
22
|
+
}
|
|
23
|
+
return adapter;
|
|
24
|
+
}
|
|
25
|
+
async *discover(providerInput, scope) {
|
|
26
|
+
const adapter = this.assertOperation(providerInput, 'discover');
|
|
27
|
+
if (scope.allowedRoots.length === 0 && (scope.authorizedAccounts?.length ?? 0) === 0) {
|
|
28
|
+
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'discovery requires at least one explicitly authorized root or account');
|
|
29
|
+
}
|
|
30
|
+
yield* adapter.discover({
|
|
31
|
+
...scope,
|
|
32
|
+
allowedRoots: [...scope.allowedRoots],
|
|
33
|
+
authorizedAccounts: scope.authorizedAccounts ? [...scope.authorizedAccounts] : undefined,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
report(providerInput, input) {
|
|
37
|
+
const adapter = this.get(providerInput);
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
provider: adapter.provider,
|
|
40
|
+
classification: adapter.disposition,
|
|
41
|
+
supportedOperations: Object.freeze([...adapter.supportedOperations]),
|
|
42
|
+
acquisitionModes: Object.freeze([...adapter.acquisitionModes]),
|
|
43
|
+
...input,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function redactSourceLocator(locator) {
|
|
48
|
+
const leaf = locator.replaceAll('\\', '/').split('/').filter(Boolean).at(-1) ?? '<source>';
|
|
49
|
+
return `<session-source>/${leaf.replace(/[^A-Za-z0-9._-]/g, '_')}`;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function defineSessionAdapterFixture(fixture) {
|
|
2
|
+
if (fixture.synthetic !== true)
|
|
3
|
+
throw new Error('session fixtures must be synthetic or separately redacted');
|
|
4
|
+
if (fixture.disposition === 'implemented' && fixture.records.length === 0) {
|
|
5
|
+
throw new Error('implemented adapters require at least one synthetic fixture record');
|
|
6
|
+
}
|
|
7
|
+
if (fixture.disposition !== 'implemented' && !fixture.unsupportedReason) {
|
|
8
|
+
throw new Error(`${fixture.disposition} fixtures require an explicit reason`);
|
|
9
|
+
}
|
|
10
|
+
return Object.freeze({ ...fixture, records: Object.freeze([...fixture.records]) });
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=fixtures.js.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, rm, stat, writeFile, } from 'node:fs/promises';
|
|
2
|
+
import { hostname } from 'node:os';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
const DEFAULTS = {
|
|
5
|
+
waitMs: 5_000,
|
|
6
|
+
pollMs: 50,
|
|
7
|
+
heartbeatMs: 1_000,
|
|
8
|
+
staleMs: 30_000,
|
|
9
|
+
};
|
|
10
|
+
export class ImportLeaseContentionError extends Error {
|
|
11
|
+
owner;
|
|
12
|
+
waitMs;
|
|
13
|
+
code = 'IMPORT_LOCKED';
|
|
14
|
+
constructor(owner, waitMs) {
|
|
15
|
+
const identity = owner
|
|
16
|
+
? `run ${owner.runId} (pid ${owner.pid} on ${owner.host}, heartbeat ${owner.heartbeatAt})`
|
|
17
|
+
: 'an unreadable owner';
|
|
18
|
+
super(`session import lease is held by ${identity}; waited ${waitMs}ms. `
|
|
19
|
+
+ 'Wait for that run to finish, or remove the lease only after confirming the owner is no longer active.');
|
|
20
|
+
this.owner = owner;
|
|
21
|
+
this.waitMs = waitMs;
|
|
22
|
+
this.name = 'ImportLeaseContentionError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function importLeasePath(databasePath) {
|
|
26
|
+
return `${databasePath}.import.lock`;
|
|
27
|
+
}
|
|
28
|
+
export async function acquireImportLease(databasePath, runId, options = {}) {
|
|
29
|
+
const waitMs = options.waitMs ?? DEFAULTS.waitMs;
|
|
30
|
+
const pollMs = options.pollMs ?? DEFAULTS.pollMs;
|
|
31
|
+
const heartbeatMs = options.heartbeatMs ?? DEFAULTS.heartbeatMs;
|
|
32
|
+
const staleMs = options.staleMs ?? DEFAULTS.staleMs;
|
|
33
|
+
const now = options.now ?? (() => new Date());
|
|
34
|
+
const processAlive = options.processAlive ?? defaultProcessAlive;
|
|
35
|
+
const lockPath = importLeasePath(databasePath);
|
|
36
|
+
const ownerPath = `${lockPath}/owner.json`;
|
|
37
|
+
const deadline = Date.now() + waitMs;
|
|
38
|
+
await mkdir(dirname(databasePath), { recursive: true, mode: 0o700 });
|
|
39
|
+
while (true) {
|
|
40
|
+
try {
|
|
41
|
+
await mkdir(lockPath, { mode: 0o700 });
|
|
42
|
+
const observed = now().toISOString();
|
|
43
|
+
const owner = {
|
|
44
|
+
contractVersion: '1.0.0',
|
|
45
|
+
runId,
|
|
46
|
+
pid: process.pid,
|
|
47
|
+
host: hostname(),
|
|
48
|
+
startedAt: observed,
|
|
49
|
+
heartbeatAt: observed,
|
|
50
|
+
};
|
|
51
|
+
await writeOwner(ownerPath, owner);
|
|
52
|
+
const timer = setInterval(() => {
|
|
53
|
+
owner.heartbeatAt = now().toISOString();
|
|
54
|
+
void writeOwner(ownerPath, owner).catch(() => undefined);
|
|
55
|
+
}, heartbeatMs);
|
|
56
|
+
timer.unref();
|
|
57
|
+
let released = false;
|
|
58
|
+
return {
|
|
59
|
+
owner,
|
|
60
|
+
lockPath,
|
|
61
|
+
async release() {
|
|
62
|
+
if (released)
|
|
63
|
+
return;
|
|
64
|
+
released = true;
|
|
65
|
+
clearInterval(timer);
|
|
66
|
+
const current = await readOwner(ownerPath);
|
|
67
|
+
if (current?.runId === owner.runId) {
|
|
68
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (!isNodeError(error) || error.code !== 'EEXIST')
|
|
75
|
+
throw error;
|
|
76
|
+
const existing = await readOwner(ownerPath);
|
|
77
|
+
if (await staleLease(lockPath, existing, staleMs, processAlive)) {
|
|
78
|
+
const confirmed = await readOwner(ownerPath);
|
|
79
|
+
if (sameLease(existing, confirmed)
|
|
80
|
+
&& await staleLease(lockPath, confirmed, staleMs, processAlive)) {
|
|
81
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (Date.now() >= deadline) {
|
|
86
|
+
throw new ImportLeaseContentionError(existing, waitMs);
|
|
87
|
+
}
|
|
88
|
+
await delay(Math.min(pollMs, Math.max(1, deadline - Date.now())));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function writeOwner(path, owner) {
|
|
93
|
+
const temporary = `${path}.tmp-${process.pid}`;
|
|
94
|
+
await writeFile(temporary, `${JSON.stringify(owner)}\n`, { mode: 0o600 });
|
|
95
|
+
await rename(temporary, path);
|
|
96
|
+
}
|
|
97
|
+
async function readOwner(path) {
|
|
98
|
+
try {
|
|
99
|
+
const value = JSON.parse(await readFile(path, 'utf8'));
|
|
100
|
+
if (value.contractVersion !== '1.0.0'
|
|
101
|
+
|| typeof value.runId !== 'string'
|
|
102
|
+
|| typeof value.pid !== 'number'
|
|
103
|
+
|| typeof value.host !== 'string'
|
|
104
|
+
|| typeof value.startedAt !== 'string'
|
|
105
|
+
|| typeof value.heartbeatAt !== 'string')
|
|
106
|
+
return null;
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function staleLease(lockPath, owner, staleMs, processAlive) {
|
|
114
|
+
if (!owner || owner.host !== hostname())
|
|
115
|
+
return false;
|
|
116
|
+
if (processAlive(owner.pid))
|
|
117
|
+
return false;
|
|
118
|
+
const heartbeat = owner ? Date.parse(owner.heartbeatAt) : Number.NaN;
|
|
119
|
+
if (Number.isFinite(heartbeat))
|
|
120
|
+
return Date.now() - heartbeat > staleMs;
|
|
121
|
+
try {
|
|
122
|
+
return Date.now() - (await stat(lockPath)).mtimeMs > staleMs;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function sameLease(first, second) {
|
|
129
|
+
if (!first || !second)
|
|
130
|
+
return first === second;
|
|
131
|
+
return first.runId === second.runId
|
|
132
|
+
&& first.pid === second.pid
|
|
133
|
+
&& first.host === second.host
|
|
134
|
+
&& first.startedAt === second.startedAt
|
|
135
|
+
&& first.heartbeatAt === second.heartbeatAt;
|
|
136
|
+
}
|
|
137
|
+
function defaultProcessAlive(pid) {
|
|
138
|
+
try {
|
|
139
|
+
process.kill(pid, 0);
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
return isNodeError(error) && error.code === 'EPERM';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function isNodeError(error) {
|
|
147
|
+
return error instanceof Error && 'code' in error;
|
|
148
|
+
}
|
|
149
|
+
function delay(ms) {
|
|
150
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=import-lease.js.map
|