@aiwg/cli 2026.9.12 → 2026.9.16

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 (36) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +42 -0
  2. package/agentic/code/providers/model-capabilities.v1.json +119 -34
  3. package/agentic/code/providers/model-catalog.v1.json +259 -56
  4. package/dist/src/cli/handlers/sessions.js +348 -42
  5. package/dist/src/cli/handlers/use.js +20 -1
  6. package/dist/src/cli/scope-resolver.js +13 -0
  7. package/dist/src/cli/services/deployment-verification.js +33 -2
  8. package/dist/src/config/aiwg-config.js +39 -0
  9. package/dist/src/config/user-registry.js +9 -0
  10. package/dist/src/extensions/manifest.js +1 -0
  11. package/dist/src/models/model-capabilities.v1.json +119 -34
  12. package/dist/src/models/model-catalog.v1.json +259 -56
  13. package/dist/src/models/model-discovery.js +7 -0
  14. package/dist/src/models/provider-policy.js +1 -1
  15. package/dist/src/plugin/skill-command-translator.js +1 -0
  16. package/dist/src/providers/capability-matrix.yaml +42 -0
  17. package/dist/src/providers/grokbot-paths.js +103 -0
  18. package/dist/src/providers/provider-definitions.js +85 -1
  19. package/dist/src/providers/provider-inventory.js +1 -0
  20. package/dist/src/sessions/adapters/claude.js +127 -3
  21. package/dist/src/sessions/adapters/grokbot.js +49 -0
  22. package/dist/src/sessions/adapters/hermes.js +17 -9
  23. package/dist/src/sessions/adapters/opencode.js +12 -1
  24. package/dist/src/sessions/contracts.js +1 -1
  25. package/dist/src/sessions/export-plan.js +243 -0
  26. package/dist/src/sessions/fortemi-export-mapping.js +300 -0
  27. package/dist/src/sessions/fortemi-shard-blobs.js +78 -0
  28. package/dist/src/sessions/fortemi-shard-recovery.js +151 -0
  29. package/dist/src/sessions/index.js +6 -0
  30. package/dist/src/sessions/output-lineage.js +43 -0
  31. package/dist/src/sessions/workspace-discovery.js +1 -1
  32. package/dist/src/skills/deployer.js +17 -0
  33. package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
  34. package/package.json +1 -1
  35. package/tools/agents/deploy-agents.mjs +3 -2
  36. package/tools/agents/providers/grokbot.mjs +285 -0
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { homedir } from 'os';
3
3
  import { join } from 'path';
4
4
  import { resolveHermesHomePath } from './hermes-home.js';
5
+ import { resolveGrokbotSkillsDir } from './grokbot-paths.js';
5
6
  import { getProviderCapabilities, } from './capability-matrix.js';
6
7
  const ArtifactPathsSchema = z.object({
7
8
  agents: z.string().nullable(),
@@ -19,6 +20,7 @@ const ProviderDefinitionSchema = z.object({
19
20
  'cursor',
20
21
  'deepseek-harness',
21
22
  'factory',
23
+ 'grokbot',
22
24
  'hermes',
23
25
  'opencode',
24
26
  'openclaw',
@@ -140,6 +142,7 @@ export const PROVIDER_IDS = [
140
142
  'cursor',
141
143
  'deepseek-harness',
142
144
  'factory',
145
+ 'grokbot',
143
146
  'hermes',
144
147
  'opencode',
145
148
  'openclaw',
@@ -204,6 +207,17 @@ const CONTEXT_CONTRACTS = {
204
207
  bootstrapTargets: ['AGENTS.md'], maxContextBytes: null, recommendedMaxLines: 150, nestedContext: true, support: 'supported',
205
208
  verification: { method: 'official Factory AGENTS.md discovery hierarchy', source: 'https://docs.factory.ai/cli/configuration/agents-md', lastVerified: VERIFIED_ON },
206
209
  },
210
+ grokbot: {
211
+ startupFiles: ['AGENTS.md'],
212
+ precedence: ['provider/system', 'project AGENTS.md when the host exposes it', 'explicit aiwg discover/show'],
213
+ loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
214
+ bootstrapTargets: ['AGENTS.md'], maxContextBytes: null, recommendedMaxLines: null, nestedContext: false, support: 'degraded',
215
+ verification: {
216
+ method: 'AIWG discover-first adapter contract; native Grok Bot startup/include path not yet verified — explicit-read guidance only',
217
+ source: 'docs/architecture/adr-grokbot-provider-target.md',
218
+ lastVerified: '2026-09-15',
219
+ },
220
+ },
207
221
  hermes: {
208
222
  startupFiles: ['.hermes.md', 'AGENTS.md'], precedence: ['provider/system', '.hermes.md', 'AGENTS.md'],
209
223
  loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
@@ -556,6 +570,68 @@ const BUILT_IN_SEEDS = [
556
570
  },
557
571
  matrixRef: 'factory',
558
572
  },
573
+ {
574
+ id: 'grokbot',
575
+ displayName: 'Grok Bot',
576
+ aliases: [],
577
+ // #210: promoted stable — Linux PUW + security + migration + maintainer Linux-only waiver 2026-09-16.
578
+ status: 'stable',
579
+ builtIn: true,
580
+ surfaces: {
581
+ primary: 'grokbot',
582
+ compatibility: [],
583
+ precedence: ['AGENTS.md', 'AIWG_GROKBOT_SKILLS_DIR when configured'],
584
+ related: [],
585
+ },
586
+ // Do not treat xAI API keys or generic GROK_* model env as Grok Bot evidence.
587
+ detection: { env: [], process: [], capabilityId: 'grokbot' },
588
+ paths: {
589
+ deployTarget: 'mixed',
590
+ artifacts: {
591
+ agents: null,
592
+ commands: null,
593
+ // Native skill copies only when AIWG_GROKBOT_SKILLS_DIR is set at deploy time.
594
+ skills: null,
595
+ rules: null,
596
+ behaviors: null,
597
+ },
598
+ kernelSkills: null,
599
+ contextDiscovery: {
600
+ agents: '.agents/agents',
601
+ skills: null,
602
+ rules: null,
603
+ behaviors: null,
604
+ },
605
+ configFile: 'AGENTS.md',
606
+ contextFiles: { aiwgMd: true, agentsMd: true, claudeMdHook: false, hookFile: null, contextFile: 'AGENTS.md' },
607
+ },
608
+ smithPaths: {
609
+ agents: null,
610
+ commands: null,
611
+ skills: null,
612
+ rules: null,
613
+ fileExtension: '.md',
614
+ configFile: 'AGENTS.md',
615
+ aggregated: false,
616
+ },
617
+ // Skills root is env-gated (AIWG_GROKBOT_SKILLS_DIR). skillsBaseDir is a
618
+ // documentation sentinel only — deployer resolves via resolveGrokbotSkillsDir
619
+ // and never joins ~/grokbot-skills.
620
+ skillNamespace: {
621
+ deploymentGroup: 'deep-recursion',
622
+ pathType: 'home-dir',
623
+ skillsBaseDir: 'AIWG_GROKBOT_SKILLS_DIR',
624
+ subdirLayout: true,
625
+ },
626
+ adapters: {
627
+ agentFormat: 'agents-md',
628
+ hookBridge: null,
629
+ mcpInjection: null,
630
+ contextAggregation: 'agents-md',
631
+ ruleFormat: 'agents-md-section',
632
+ },
633
+ matrixRef: 'grokbot',
634
+ },
559
635
  {
560
636
  id: 'hermes',
561
637
  aliases: [],
@@ -1137,10 +1213,18 @@ export function getProviderArtifactPathStrings(provider) {
1137
1213
  const definition = getProviderDefinition(provider);
1138
1214
  if (!definition)
1139
1215
  return undefined;
1216
+ const normalized = normalizeProviderDefinitionId(provider);
1217
+ // Grok Bot skills are env-gated (AIWG_GROKBOT_SKILLS_DIR). Resolve dynamically so
1218
+ // deploy counting / user-scope same-path inventory can see the configured root
1219
+ // without inventing ~/.grokbot (#210).
1220
+ let skills = expandProviderHomePath(definition.paths.artifacts.skills);
1221
+ if (normalized === 'grokbot' && !skills) {
1222
+ skills = resolveGrokbotSkillsDir() ?? '';
1223
+ }
1140
1224
  return {
1141
1225
  agents: expandProviderHomePath(definition.paths.artifacts.agents),
1142
1226
  commands: expandProviderHomePath(definition.paths.artifacts.commands),
1143
- skills: expandProviderHomePath(definition.paths.artifacts.skills),
1227
+ skills,
1144
1228
  rules: expandProviderHomePath(definition.paths.artifacts.rules),
1145
1229
  behaviors: expandProviderHomePath(definition.paths.artifacts.behaviors),
1146
1230
  };
@@ -12,6 +12,7 @@ const PROVIDER_EXECUTABLES = {
12
12
  cursor: ['cursor'],
13
13
  'deepseek-harness': ['dsh'],
14
14
  factory: ['droid'],
15
+ // Grok Bot has no verified CLI executable name; omit rather than invent.
15
16
  hermes: ['hermes'],
16
17
  opencode: ['opencode'],
17
18
  openclaw: ['openclaw'],
@@ -3,9 +3,12 @@ import { basename, extname, resolve } from 'node:path';
3
3
  import { z } from 'zod';
4
4
  import { SessionContractError, assertSupportedSchemaMajor, sha256, } from '../contracts.js';
5
5
  import { redactSourceLocator } from '../discovery.js';
6
- import { readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
7
- export const CLAUDE_ADAPTER_VERSION = '1.0.0';
6
+ import { readBoundedJson, readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
7
+ export const CLAUDE_ADAPTER_VERSION = '1.1.0';
8
8
  export const CLAUDE_TRANSCRIPT_SCHEMA_VERSION = '1.0.0';
9
+ /** AIWG's own understanding of the layout it supports; Claude web/account
10
+ * exports carry no in-band schema version to read instead. */
11
+ export const CLAUDE_WEB_EXPORT_SCHEMA_VERSION = '1.0.0';
9
12
  const ClaudeRecordSchema = z.object({
10
13
  type: z.string().min(1),
11
14
  subtype: z.string().optional(),
@@ -40,6 +43,43 @@ const ClaudeHookSchema = z.object({
40
43
  schemaVersion: z.string().optional(),
41
44
  timestamp: z.string().datetime({ offset: true }).optional(),
42
45
  }).passthrough();
46
+ /**
47
+ * Claude.ai web/account data export (`conversations.json`): a JSON array of
48
+ * conversations, each carrying its own `chat_messages`. This is a distinct
49
+ * source format from local JSONL transcripts/hooks -- renaming the file does
50
+ * not make it one (#2565). Unrecognized fields are captured via unknownFields
51
+ * rather than dropped, and content the export marks as an attachment/file is
52
+ * recorded as a reference (bytes are not embedded in this export format).
53
+ */
54
+ const ClaudeWebExportAttachmentSchema = z.object({
55
+ file_name: z.string().optional(),
56
+ file_type: z.string().optional(),
57
+ file_size: z.number().optional(),
58
+ extracted_content: z.string().optional(),
59
+ }).passthrough();
60
+ const ClaudeWebExportContentBlockSchema = z.object({
61
+ type: z.string().optional(),
62
+ text: z.string().optional(),
63
+ }).passthrough();
64
+ const ClaudeWebExportMessageSchema = z.object({
65
+ uuid: z.string().min(1),
66
+ text: z.string().optional(),
67
+ sender: z.enum(['human', 'assistant']),
68
+ created_at: z.string().optional(),
69
+ updated_at: z.string().optional(),
70
+ content: z.array(ClaudeWebExportContentBlockSchema).optional(),
71
+ attachments: z.array(ClaudeWebExportAttachmentSchema).optional(),
72
+ files: z.array(ClaudeWebExportAttachmentSchema).optional(),
73
+ }).passthrough();
74
+ const ClaudeWebExportConversationSchema = z.object({
75
+ uuid: z.string().min(1),
76
+ name: z.string().optional(),
77
+ created_at: z.string().optional(),
78
+ updated_at: z.string().optional(),
79
+ account: z.object({ uuid: z.string().optional() }).passthrough().optional(),
80
+ chat_messages: z.array(ClaudeWebExportMessageSchema),
81
+ }).passthrough();
82
+ const ClaudeWebExportSchema = z.array(ClaudeWebExportConversationSchema);
43
83
  export class ClaudeSessionAdapter {
44
84
  limits;
45
85
  discoveryLimits;
@@ -47,7 +87,7 @@ export class ClaudeSessionAdapter {
47
87
  adapterVersion = CLAUDE_ADAPTER_VERSION;
48
88
  disposition = 'implemented';
49
89
  supportedOperations = ['discover', 'inspect', 'stream'];
50
- acquisitionModes = ['jsonl', 'hook'];
90
+ acquisitionModes = ['jsonl', 'hook', 'manual-export'];
51
91
  constructor(limits, discoveryLimits = { maxDepth: 8, maxFiles: 10_000 }) {
52
92
  this.limits = limits;
53
93
  this.discoveryLimits = discoveryLimits;
@@ -80,6 +120,13 @@ export class ClaudeSessionAdapter {
80
120
  };
81
121
  }
82
122
  async *stream(source, cursor) {
123
+ if (source.locatorClass === 'claude-web-export-json') {
124
+ const parsed = await this.readWebExportSource(source);
125
+ const start = parseRecordCursor(cursor?.value);
126
+ for (const record of parsed.records.slice(start))
127
+ yield record;
128
+ return;
129
+ }
83
130
  const start = parseRecordCursor(cursor?.value);
84
131
  const input = await streamBoundedJsonLines({
85
132
  selectedPath: source.locator,
@@ -112,6 +159,9 @@ export class ClaudeSessionAdapter {
112
159
  }
113
160
  }
114
161
  async readSource(source) {
162
+ if (source.locatorClass === 'claude-web-export-json') {
163
+ return this.readWebExportSource(source);
164
+ }
115
165
  const result = await readBoundedJsonLines({
116
166
  selectedPath: source.locator,
117
167
  allowedRoots: source.authorizedScope.allowedRoots,
@@ -132,7 +182,81 @@ export class ClaudeSessionAdapter {
132
182
  consistency: normalized.complete ? 'complete' : 'provisional',
133
183
  };
134
184
  }
185
+ async readWebExportSource(source) {
186
+ const { value } = await readBoundedJson({ selectedPath: source.locator, allowedRoots: source.authorizedScope.allowedRoots }, this.limits);
187
+ const parsed = ClaudeWebExportSchema.safeParse(value);
188
+ if (!parsed.success) {
189
+ throw new SessionContractError('MALFORMED_SOURCE', 'Claude web/account export is not a recognized conversations.json layout');
190
+ }
191
+ if (parsed.data.length === 0) {
192
+ throw new SessionContractError('MALFORMED_SOURCE', 'Claude web/account export is empty');
193
+ }
194
+ return {
195
+ records: normalizeWebExport(parsed.data),
196
+ schemaVersion: CLAUDE_WEB_EXPORT_SCHEMA_VERSION,
197
+ // The export is a completed snapshot at request time, not a live tail.
198
+ consistency: 'complete',
199
+ };
200
+ }
135
201
  }
202
+ function normalizeWebExport(conversations) {
203
+ const output = [];
204
+ const seenConversationIds = new Set();
205
+ for (const conversation of conversations) {
206
+ if (seenConversationIds.has(conversation.uuid)) {
207
+ throw new SessionContractError('DUPLICATE_NATIVE_ID', `Claude web export declares the same conversation uuid twice: ${conversation.uuid}`);
208
+ }
209
+ seenConversationIds.add(conversation.uuid);
210
+ const seenMessageIds = new Set();
211
+ for (const [index, message] of conversation.chat_messages.entries()) {
212
+ if (seenMessageIds.has(message.uuid)) {
213
+ throw new SessionContractError('DUPLICATE_NATIVE_ID', `Claude web export conversation ${conversation.uuid} declares the same message uuid twice: ${message.uuid}`);
214
+ }
215
+ seenMessageIds.add(message.uuid);
216
+ output.push(webExportProviderRecord(conversation, message, index));
217
+ }
218
+ }
219
+ return output;
220
+ }
221
+ function webExportProviderRecord(conversation, message, sequence) {
222
+ const text = message.text && message.text.length > 0
223
+ ? message.text
224
+ : (message.content ?? []).map((block) => block.text ?? '').filter(Boolean).join('\n');
225
+ const attachments = [...(message.attachments ?? []), ...(message.files ?? [])];
226
+ return {
227
+ nativeSessionId: conversation.uuid,
228
+ nativeEventId: message.uuid,
229
+ sequence,
230
+ kind: 'message',
231
+ role: message.sender,
232
+ participant: message.sender,
233
+ occurredAt: message.created_at,
234
+ text,
235
+ rawReference: { locatorClass: 'claude-web-export-json', sequence },
236
+ extensions: {
237
+ conversationName: conversation.name,
238
+ conversationCreatedAt: conversation.created_at,
239
+ conversationUpdatedAt: conversation.updated_at,
240
+ provenance: { acquisition: 'claude-web-export-json', schema: CLAUDE_WEB_EXPORT_SCHEMA_VERSION },
241
+ // Web/account exports reference attachments/files by metadata only; the
242
+ // export format does not embed original bytes, so this is a coverage
243
+ // note (#2565 acceptance criteria), not a loss AIWG introduced.
244
+ ...(attachments.length > 0 ? {
245
+ attachmentReferences: attachments.map((attachment) => ({
246
+ fileName: attachment.file_name,
247
+ fileType: attachment.file_type,
248
+ fileSize: attachment.file_size,
249
+ extractedContentAvailable: typeof attachment.extracted_content === 'string',
250
+ })),
251
+ metadataLoss: ['attachment bytes unavailable in web/account export format'],
252
+ } : {}),
253
+ unknownFields: unknownFields(message, WEB_EXPORT_MESSAGE_KEYS),
254
+ },
255
+ };
256
+ }
257
+ const WEB_EXPORT_MESSAGE_KEYS = new Set([
258
+ 'uuid', 'text', 'sender', 'created_at', 'updated_at', 'content', 'attachments', 'files',
259
+ ]);
136
260
  function normalizeTranscript(records, locator) {
137
261
  const filenameSessionId = basename(locator, extname(locator));
138
262
  const output = [];
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Grok Bot session adapter — manual-export only until product evidence exists
3
+ * for a native session filesystem or API locator.
4
+ *
5
+ * Auto-discover is unsupported and must never scrape invented ~/.grokbot,
6
+ * Cursor session paths, or other unverified roots.
7
+ *
8
+ * Authorized imports reuse the AIWG generic session interchange format so
9
+ * operators can supply an explicit export without inventing a Grok schema.
10
+ *
11
+ * @see docs/providers/grokbot-sessions.md
12
+ * @issue #215
13
+ */
14
+ import { SessionContractError, } from '../contracts.js';
15
+ import { GenericSessionInterchangeAdapter, } from './generic.js';
16
+ export const GROKBOT_ADAPTER_VERSION = '1.0.0';
17
+ export const GROKBOT_EXPORT_SCHEMA_VERSION = '1.0.0';
18
+ export const GROKBOT_LOCATOR_CLASS = 'manual-export';
19
+ export class GrokbotSessionAdapter {
20
+ provider = 'grokbot';
21
+ adapterVersion = GROKBOT_ADAPTER_VERSION;
22
+ disposition = 'manual-only';
23
+ supportedOperations = ['inspect', 'stream'];
24
+ acquisitionModes = ['manual-export'];
25
+ interchange;
26
+ constructor(limits) {
27
+ this.interchange = new GenericSessionInterchangeAdapter(limits);
28
+ }
29
+ async *discover(_scope) {
30
+ throw new SessionContractError('UNSUPPORTED_OPERATION', 'Grok Bot session auto-discover is unsupported until a verified native locator exists; '
31
+ + 'select an authorized manual export explicitly. AIWG does not scrape ~/.grokbot, '
32
+ + '~/grokbot-skills, or Cursor session paths.');
33
+ }
34
+ async inspect(source) {
35
+ this.assertManualExport(source);
36
+ return this.interchange.inspect(source);
37
+ }
38
+ async *stream(source, cursor) {
39
+ this.assertManualExport(source);
40
+ yield* this.interchange.stream(source, cursor);
41
+ }
42
+ assertManualExport(source) {
43
+ if (source.locatorClass !== GROKBOT_LOCATOR_CLASS) {
44
+ throw new SessionContractError('UNSUPPORTED_OPERATION', `Grok Bot supports only locatorClass "${GROKBOT_LOCATOR_CLASS}" until native session evidence exists `
45
+ + `(got "${source.locatorClass}"). Do not reuse Cursor session locators.`);
46
+ }
47
+ }
48
+ }
49
+ //# sourceMappingURL=grokbot.js.map
@@ -4,23 +4,31 @@ import { readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
4
4
  export const HERMES_ADAPTER_VERSION = '1.0.0';
5
5
  export const HERMES_EXPORT_SCHEMA_VERSION = '1.0.0';
6
6
  export const HERMES_NATIVE_SCHEMA_VERSION = '23.0.0';
7
+ const sqliteBoolean = z.union([z.boolean(), z.literal(0), z.literal(1)])
8
+ .transform((value) => Boolean(value));
9
+ function nullableOptional(schema) {
10
+ return z.preprocess((value) => value === null ? undefined : value, schema.optional());
11
+ }
7
12
  const MessageSchema = z.object({
8
13
  id: z.union([z.string(), z.number()]),
9
14
  role: z.string().min(1),
10
15
  content: z.unknown().optional(),
11
- tool_call_id: z.string().optional(),
16
+ tool_call_id: nullableOptional(z.string()),
12
17
  tool_calls: z.unknown().optional(),
13
- tool_name: z.string().optional(),
18
+ tool_name: nullableOptional(z.string()),
14
19
  timestamp: z.number().optional(),
15
- token_count: z.number().optional(),
16
- finish_reason: z.string().optional(),
17
- reasoning: z.string().optional(),
18
- reasoning_content: z.string().optional(),
20
+ token_count: nullableOptional(z.number()),
21
+ finish_reason: nullableOptional(z.string()),
22
+ reasoning: nullableOptional(z.string()),
23
+ reasoning_content: nullableOptional(z.string()),
19
24
  reasoning_details: z.unknown().optional(),
20
25
  codex_reasoning_items: z.unknown().optional(),
21
26
  }).passthrough();
22
27
  const ExportSchema = z.object({
23
- schemaVersion: z.union([z.string(), z.number()]),
28
+ // Hermes CLI exports currently omit a schema marker. They use the same
29
+ // schema-23 record shape that the adapter already validates; explicit
30
+ // markers still pass through the major-version gate below.
31
+ schemaVersion: z.union([z.string(), z.number()]).default(HERMES_NATIVE_SCHEMA_VERSION),
24
32
  id: z.string().min(1),
25
33
  source: z.string().min(1),
26
34
  user_id: z.string().nullable().optional(),
@@ -31,8 +39,8 @@ const ExportSchema = z.object({
31
39
  started_at: z.number(),
32
40
  ended_at: z.number().nullable().optional(),
33
41
  end_reason: z.string().nullable().optional(),
34
- archived: z.boolean().optional(),
35
- inactive: z.boolean().optional(),
42
+ archived: sqliteBoolean.optional(),
43
+ inactive: sqliteBoolean.optional(),
36
44
  cwd: z.string().nullable().optional(),
37
45
  git_branch: z.string().nullable().optional(),
38
46
  routing_key: z.string().nullable().optional(),
@@ -343,7 +343,18 @@ function normalizeMessage(message, sequence, locatorClass, common) {
343
343
  toolCallId: part.callID,
344
344
  toolState: part.state,
345
345
  attachment: part.type === 'file'
346
- ? { mime: part.mime, filename: part.filename, urlPresent: Boolean(part.url) }
346
+ ? {
347
+ mime: part.mime,
348
+ filename: part.filename,
349
+ displayName: part.filename,
350
+ urlPresent: Boolean(part.url),
351
+ // Preserve only locally recoverable byte locators. Remote URLs
352
+ // remain presence-only metadata and are never fetched by export.
353
+ dataBytes: part.url?.startsWith('data:')
354
+ ? [...Buffer.from(part.url.slice(part.url.indexOf(',') + 1), part.url.includes(';base64,') ? 'base64' : 'utf8')]
355
+ : undefined,
356
+ fileUrl: part.url?.startsWith('file:') ? part.url : undefined,
357
+ }
347
358
  : undefined,
348
359
  opaqueContent: !['text', 'reasoning', 'tool', 'file'].includes(part.type),
349
360
  unknownFields: unknownFields(part, PART_KEYS),
@@ -3,7 +3,7 @@ import { z } from 'zod';
3
3
  export const SESSION_CONTRACT_VERSION = '1.0.0';
4
4
  export const SESSION_PROVIDER_IDS = [
5
5
  'claude', 'codex', 'copilot', 'cursor', 'factory', 'hermes',
6
- 'opencode', 'openclaw', 'openhuman', 'pi', 'omp', 'deepseek-harness', 'warp', 'devin-desktop', 'generic',
6
+ 'opencode', 'openclaw', 'openhuman', 'grokbot', 'pi', 'omp', 'deepseek-harness', 'warp', 'devin-desktop', 'generic',
7
7
  ];
8
8
  export const SessionProviderIdSchema = z.enum(SESSION_PROVIDER_IDS);
9
9
  export const SESSION_PROVIDER_ALIASES = Object.freeze({