@aiwg/cli 2026.10.10 → 2026.10.12

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 (156) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +78 -14
  2. package/agentic/code/providers/model-catalog.v1.json +62 -12
  3. package/dist/src/artifacts/binding-target-identity.js +51 -0
  4. package/dist/src/artifacts/checksum-manifest.js +12 -0
  5. package/dist/src/artifacts/index-builder.js +11 -13
  6. package/dist/src/artifacts/query-engine.js +5 -11
  7. package/dist/src/cli/handlers/doctor-help.js +32 -0
  8. package/dist/src/cli/handlers/gates.js +19 -18
  9. package/dist/src/cli/handlers/models-help.js +7 -1
  10. package/dist/src/cli/handlers/models.js +3 -3
  11. package/dist/src/cli/handlers/script-runner.js +8 -5
  12. package/dist/src/cli/handlers/sessions-intake.js +104 -0
  13. package/dist/src/cli/handlers/sessions-tail.js +172 -0
  14. package/dist/src/cli/handlers/sessions.js +287 -31
  15. package/dist/src/cli/handlers/skill-lint.js +10 -3
  16. package/dist/src/cli/handlers/steward-format.js +100 -0
  17. package/dist/src/cli/handlers/steward-models.js +167 -0
  18. package/dist/src/cli/handlers/steward.js +14 -248
  19. package/dist/src/cli/handlers/use-bundle-counts.js +61 -0
  20. package/dist/src/cli/handlers/use.js +18 -28
  21. package/dist/src/cli/handlers/utilities.js +1 -24
  22. package/dist/src/config/aiwg-config.js +7 -0
  23. package/dist/src/config/authorization-cli.js +89 -0
  24. package/dist/src/config/cli-help.js +1 -0
  25. package/dist/src/config/cli.js +10 -1
  26. package/dist/src/config/worker-context.js +337 -0
  27. package/dist/src/decision/context-pruning.js +3 -2
  28. package/dist/src/decision/ensemble/contract.js +15 -4
  29. package/dist/src/decision/ensemble-study/artifacts.js +5 -0
  30. package/dist/src/decision/ensemble-study/score.js +4 -3
  31. package/dist/src/decision/feature-export.js +3 -9
  32. package/dist/src/decision/heldout/collector.js +2 -2
  33. package/dist/src/decision/issue-triage/runtime.js +3 -9
  34. package/dist/src/decision/qualification/gates.js +2 -1
  35. package/dist/src/decision/qualification/release.js +2 -3
  36. package/dist/src/decision/routing/report.js +2 -0
  37. package/dist/src/decision/sdlc-screening.js +3 -5
  38. package/dist/src/decision/sensitivity/comparative.js +5 -3
  39. package/dist/src/gates/driver.js +6 -77
  40. package/dist/src/gates/evaluate.js +4 -11
  41. package/dist/src/gates/floors.js +4 -4
  42. package/dist/src/gates/index.js +3 -3
  43. package/dist/src/gates/lattice.js +26 -0
  44. package/dist/src/gates/registry.js +2 -1
  45. package/dist/src/gates/report.js +19 -7
  46. package/dist/src/issues/audit-cli.js +120 -0
  47. package/dist/src/issues/audit-contract.js +64 -0
  48. package/dist/src/issues/audit-local.js +82 -0
  49. package/dist/src/issues/audit.js +220 -0
  50. package/dist/src/lint/rule-activation.js +15 -1
  51. package/dist/src/mcp/tools/worker-invoke.mjs +108 -10
  52. package/dist/src/mission-protocol/canonical-codecs.js +270 -0
  53. package/dist/src/mission-protocol/codecs.js +22 -550
  54. package/dist/src/mission-protocol/health-trace-codecs.js +281 -0
  55. package/dist/src/mission-protocol/health-trace-types.js +43 -0
  56. package/dist/src/mission-protocol/types.js +2 -42
  57. package/dist/src/mission-protocol/worker-contract-json.js +10 -0
  58. package/dist/src/mission-protocol/worker-review-gate.js +100 -0
  59. package/dist/src/mission-protocol/worker-review-types.js +2 -0
  60. package/dist/src/models/catalog-policy.js +280 -0
  61. package/dist/src/models/index.js +1 -1
  62. package/dist/src/models/model-capabilities.v1.json +78 -14
  63. package/dist/src/models/model-catalog.v1.json +62 -12
  64. package/dist/src/models/policy-resources.js +28 -0
  65. package/dist/src/models/provider-policy.js +10 -539
  66. package/dist/src/models/resolver.js +26 -8
  67. package/dist/src/models/router.js +15 -2
  68. package/dist/src/models/tiers.js +13 -0
  69. package/dist/src/models/types.js +0 -6
  70. package/dist/src/models/worker-ceilings.js +59 -0
  71. package/dist/src/models/worker-invocation-policy.js +243 -0
  72. package/dist/src/models/worker-plan.js +16 -20
  73. package/dist/src/models/worker-review-lineage.js +55 -0
  74. package/dist/src/models/worker-selection-policy.js +1 -1
  75. package/dist/src/models/wrapper-route.js +6 -3
  76. package/dist/src/policy/authorization.js +30 -1
  77. package/dist/src/policy/hitl-config.js +63 -0
  78. package/dist/src/policy/hitl-receipt.js +57 -0
  79. package/dist/src/policy/hitl-types.js +4 -0
  80. package/dist/src/policy/hitl-validation.js +39 -0
  81. package/dist/src/policy/hitl.js +117 -0
  82. package/dist/src/process/aiwg-probe-command.d.mts +15 -0
  83. package/dist/src/process/aiwg-probe-command.mjs +37 -0
  84. package/dist/src/rules/activation.d.mts +1 -1
  85. package/dist/src/rules/activation.mjs +0 -2
  86. package/dist/src/rules/gate-coverage.js +51 -0
  87. package/dist/src/rules/gate-references.js +16 -0
  88. package/dist/src/rules/rule-frontmatter.schema.json +7 -1
  89. package/dist/src/serve/worker-command-request.js +21 -4
  90. package/dist/src/serve/worker-invocation-preparation.js +3 -1
  91. package/dist/src/serve/worker-invocation.js +21 -10
  92. package/dist/src/sessions/adapters/deepseek-harness.js +1 -0
  93. package/dist/src/sessions/adapters/grok-build-native.js +269 -0
  94. package/dist/src/sessions/adapters/grok-build.js +57 -15
  95. package/dist/src/sessions/adapters/grokbot.js +7 -0
  96. package/dist/src/sessions/adapters/hermes-state-db.js +252 -0
  97. package/dist/src/sessions/adapters/hermes.js +42 -3
  98. package/dist/src/sessions/adapters/warp.js +1 -1
  99. package/dist/src/sessions/batch-import.js +44 -6
  100. package/dist/src/sessions/contracts.js +20 -1
  101. package/dist/src/sessions/coverage-preflight.js +7 -1
  102. package/dist/src/sessions/discovery-capability.js +38 -0
  103. package/dist/src/sessions/fortemi-export-mapping.js +33 -3
  104. package/dist/src/sessions/host-catalog.js +157 -0
  105. package/dist/src/sessions/image-blobs.js +141 -0
  106. package/dist/src/sessions/importer.js +9 -232
  107. package/dist/src/sessions/index.js +6 -0
  108. package/dist/src/sessions/intake-dir.js +71 -0
  109. package/dist/src/sessions/normalization.js +251 -0
  110. package/dist/src/sessions/refresh.js +25 -2
  111. package/dist/src/sessions/repository.js +164 -22
  112. package/dist/src/sessions/tail.js +91 -0
  113. package/dist/src/sessions/workspace-discovery.js +88 -9
  114. package/dist/src/skills/agent-skill-sidecar.js +28 -0
  115. package/dist/src/skills/agent-skills.js +4 -23
  116. package/dist/src/skills/claude-surface-projection.js +25 -0
  117. package/dist/src/skills/deployer.js +8 -288
  118. package/dist/src/skills/deployment-policy.js +114 -0
  119. package/dist/src/skills/deployment-projection.js +152 -0
  120. package/dist/src/skills/deployment-types.js +20 -0
  121. package/dist/src/skills/exporter.js +8 -31
  122. package/dist/src/skills/provider-extensions.js +41 -0
  123. package/dist/src/skills/run.js +6 -0
  124. package/dist/src/skills/types.js +0 -8
  125. package/dist/src/skills/validator-resources.js +68 -0
  126. package/dist/src/skills/validator.js +6 -55
  127. package/dist/src/storage/cli-migrate-options.js +125 -0
  128. package/dist/src/storage/cli-migrate.js +111 -0
  129. package/dist/src/storage/cli.js +4 -279
  130. package/dist/src/storage/migration-adapter.js +79 -0
  131. package/dist/src/storage/migration-copy.js +108 -0
  132. package/dist/src/storage/migration-protocol.js +14 -197
  133. package/dist/src/storage/migration-records.js +186 -0
  134. package/dist/src/storage/migration-types.js +12 -0
  135. package/dist/src/tracker/forge-http.js +2 -1
  136. package/dist/src/tracker/issue-pages.js +110 -0
  137. package/package.json +7 -1
  138. package/schemas/mission-v1.schema.json +335 -0
  139. package/schemas/models/model-catalog.v1.schema.json +19 -1
  140. package/schemas/models/model-config-compatibility.v1.schema.json +3 -2
  141. package/schemas/models/model-policy.v1.schema.json +1 -1
  142. package/tools/agents/providers/provider-paths-staging.mjs +35 -0
  143. package/tools/agents/providers/rule-binding-targets.mjs +146 -0
  144. package/tools/agents/providers/rule-delivery.mjs +7 -126
  145. package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
  146. package/tools/ralph-external/lib/worker-context.mjs +237 -0
  147. package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
  148. package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
  149. package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
  150. package/tools/ralph-external/lib/worker-isolation.mjs +17 -29
  151. package/tools/ralph-external/lib/worker-launch-cli.mjs +3 -1
  152. package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
  153. package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
  154. package/tools/ralph-external/lib/worker-termination.mjs +82 -0
  155. package/tools/ralph-external/session-launcher.mjs +51 -96
  156. package/vscode-extension/schemas/aiwg.config.v1.json +177 -0
@@ -0,0 +1,269 @@
1
+ // Native Grok Build session-store reader (synthetic schema v1; real stores never inspected).
2
+ import { opendir, readFile } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { z } from 'zod';
5
+ import { integer, number } from '../../schema/zod-validation.mjs';
6
+ import { schemaIssueDetail, schemaRejectionDetail, SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
7
+ import { matchSessionWorkspace } from '../workspace-matching.js';
8
+ import { authorizeSourceFile } from '../policy.js';
9
+ import { streamBoundedJsonLines, } from '../readers.js';
10
+ export const GROK_BUILD_NATIVE_SCHEMA_VERSION = '1.0.0';
11
+ export const GROK_BUILD_NATIVE_SCHEMA_MAJOR = 1;
12
+ export const GROK_BUILD_NATIVE_LOCATOR_CLASS = 'grok-build-native-chat-history';
13
+ export const GROK_BUILD_NATIVE_SESSION_FILENAME = 'chat_history.jsonl';
14
+ export const GROK_BUILD_ACTIVE_SESSIONS_SCHEMA_VERSION = 1;
15
+ export const GROK_BUILD_ACTIVE_FRESHNESS_MS = 120_000;
16
+ export const GROK_BUILD_NATIVE_DISCOVERY_MAX_FILES = 10_000;
17
+ const ACTIVE_SESSIONS_MAX_BYTES = 1024 * 1024;
18
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
19
+ // Synthetic native record shape (v1): one JSON object per line. Real
20
+ // chat_history.jsonl schemas are unqualified (real stores are never read),
21
+ // so any record outside this shape fails closed with SCHEMA_DRIFT.
22
+ const NativeRecordSchema = z.looseObject({
23
+ schemaVersion: z.union([z.string(), z.number()]),
24
+ sessionId: z.string().regex(UUID),
25
+ seq: integer().nonnegative(),
26
+ role: z.enum(['user', 'assistant', 'tool']),
27
+ content: z.string(),
28
+ timestamp: number().optional(),
29
+ toolName: z.string().min(1).optional(),
30
+ toolCallId: z.string().optional(),
31
+ id: z.string().min(1).optional(),
32
+ });
33
+ const ActiveSessionsSchema = z.looseObject({
34
+ schemaVersion: z.union([z.string(), z.number()]),
35
+ sessions: z.array(z.looseObject({
36
+ sessionId: z.string().regex(UUID),
37
+ cwd: z.string().optional(),
38
+ lastHeartbeatAt: number().optional(),
39
+ })),
40
+ });
41
+ /** Decode a url-encoded cwd directory name exactly once; null means skip. */
42
+ export function decodeGrokBuildWorkspaceDir(name) {
43
+ if (!name || name.includes('/') || name.includes('\\'))
44
+ return null;
45
+ let decoded;
46
+ try {
47
+ decoded = decodeURIComponent(name);
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ if (!decoded.startsWith('/') || decoded.includes('\0'))
53
+ return null;
54
+ return decoded;
55
+ }
56
+ export function grokBuildNativeSessionId(locator) {
57
+ const sessionId = dirname(locator).split('/').pop()?.split('\\').pop() ?? '';
58
+ return UUID.test(sessionId) ? sessionId.toLowerCase() : null;
59
+ }
60
+ /** Enumerate documented `<root>/<url-encoded cwd>/<id>/chat_history.jsonl` paths. */
61
+ export async function* discoverGrokBuildNativeChatHistories(root, maxFiles = GROK_BUILD_NATIVE_DISCOVERY_MAX_FILES) {
62
+ const resolved = resolve(root);
63
+ let workspaces;
64
+ try {
65
+ workspaces = await opendir(resolved);
66
+ }
67
+ catch {
68
+ return;
69
+ }
70
+ let count = 0;
71
+ for await (const workspace of workspaces) {
72
+ if (!workspace.isDirectory())
73
+ continue;
74
+ const decoded = decodeGrokBuildWorkspaceDir(workspace.name);
75
+ if (!decoded)
76
+ continue;
77
+ const workspacePath = resolve(resolved, workspace.name);
78
+ if (workspacePath !== resolved && !workspacePath.startsWith(`${resolved}/`))
79
+ continue;
80
+ let sessions;
81
+ try {
82
+ sessions = await opendir(workspacePath);
83
+ }
84
+ catch {
85
+ continue;
86
+ }
87
+ for await (const session of sessions) {
88
+ if (!session.isDirectory() || !UUID.test(session.name))
89
+ continue;
90
+ const sessionPath = resolve(workspacePath, session.name);
91
+ if (!sessionPath.startsWith(`${workspacePath}/`))
92
+ continue;
93
+ let entries;
94
+ try {
95
+ entries = await opendir(sessionPath);
96
+ }
97
+ catch {
98
+ continue;
99
+ }
100
+ for await (const entry of entries) {
101
+ if (!entry.isFile() || entry.name !== GROK_BUILD_NATIVE_SESSION_FILENAME)
102
+ continue;
103
+ if (++count > maxFiles) {
104
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Grok Build native session discovery exceeded the authorized file limit', { limit: 'maxFiles', configured: maxFiles, observed: count });
105
+ }
106
+ yield {
107
+ provider: 'grok-build',
108
+ locator: resolve(sessionPath, entry.name),
109
+ locatorClass: GROK_BUILD_NATIVE_LOCATOR_CLASS,
110
+ nativeSessionId: session.name.toLowerCase(),
111
+ };
112
+ }
113
+ }
114
+ }
115
+ }
116
+ /** Match a native locator against the authorized workspace via its cwd directory. */
117
+ export async function grokBuildSourceWorkspaceMatch(locator, workspacePath) {
118
+ const segments = locator.replace(/\\/g, '/').split('/');
119
+ const encoded = segments.at(-3);
120
+ if (!encoded)
121
+ return null;
122
+ const decoded = decodeGrokBuildWorkspaceDir(encoded);
123
+ if (!decoded)
124
+ return null;
125
+ return matchSessionWorkspace(decoded, workspacePath);
126
+ }
127
+ export async function readGrokBuildNativeChatHistory(source, limits) {
128
+ const sessionId = grokBuildNativeSessionId(source.locator);
129
+ if (source.locatorClass !== GROK_BUILD_NATIVE_LOCATOR_CLASS || !sessionId) {
130
+ throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Grok Build native source must be an explicitly authorized chat_history.jsonl path');
131
+ }
132
+ await authorizeSourceFile({
133
+ selectedPath: source.locator,
134
+ allowedRoots: source.authorizedScope.allowedRoots,
135
+ });
136
+ const input = await streamBoundedJsonLines({
137
+ selectedPath: source.locator,
138
+ allowedRoots: source.authorizedScope.allowedRoots,
139
+ }, { consistency: 'provisional', limits });
140
+ const records = [];
141
+ let lastSeq = -1;
142
+ for await (const line of input) {
143
+ const parsed = NativeRecordSchema.safeParse(line.value);
144
+ if (!parsed.success) {
145
+ throw new SessionContractError('SCHEMA_DRIFT', 'unrecognized Grok Build native chat_history.jsonl record', schemaRejectionDetail(parsed.error));
146
+ }
147
+ assertSupportedSchemaMajor(declaredSchema(parsed.data.schemaVersion), GROK_BUILD_NATIVE_SCHEMA_MAJOR);
148
+ if (parsed.data.sessionId.toLowerCase() !== sessionId) {
149
+ throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build native record session does not match its session directory', schemaIssueDetail('sessionId'));
150
+ }
151
+ if (parsed.data.seq <= lastSeq) {
152
+ throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build native chat history sequence is not strictly increasing', schemaIssueDetail('seq'));
153
+ }
154
+ lastSeq = parsed.data.seq;
155
+ if (parsed.data.role === 'tool' && !parsed.data.toolName) {
156
+ throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build native tool record is missing its tool name', schemaIssueDetail('toolName'));
157
+ }
158
+ records.push({ record: parsed.data, line });
159
+ }
160
+ if (records.length === 0) {
161
+ throw new SessionContractError('MALFORMED_SOURCE', 'Grok Build native chat history is empty', schemaIssueDetail('<root>'));
162
+ }
163
+ return { records, incompleteTail: input.incompleteTail };
164
+ }
165
+ export function toGrokBuildNativeRecord(sessionId, entry) {
166
+ const { record, line } = entry;
167
+ const occurredAt = timestamp(record.timestamp);
168
+ const nativeEventId = record.id ?? `${sessionId}:${record.seq}`;
169
+ const common = {
170
+ lifecycle: 'unknown-at-import',
171
+ provenance: {
172
+ product: 'grok-build',
173
+ acquisition: GROK_BUILD_NATIVE_LOCATOR_CLASS,
174
+ sourceSchema: GROK_BUILD_NATIVE_SCHEMA_VERSION,
175
+ nativeStoreInspected: true,
176
+ },
177
+ identity: { nativeSessionIdKnown: true, nativeEventIdKnown: true },
178
+ };
179
+ if (record.role === 'tool') {
180
+ return {
181
+ nativeSessionId: sessionId,
182
+ nativeEventId,
183
+ sequence: line.sequence * 1_000,
184
+ kind: 'tool-call',
185
+ role: 'tool',
186
+ toolName: record.toolName,
187
+ toolCallId: record.toolCallId,
188
+ occurredAt,
189
+ text: record.content,
190
+ rawReference: { locatorClass: GROK_BUILD_NATIVE_LOCATOR_CLASS, sequence: line.sequence },
191
+ extensions: common,
192
+ };
193
+ }
194
+ return {
195
+ nativeSessionId: sessionId,
196
+ nativeEventId,
197
+ sequence: line.sequence * 1_000,
198
+ kind: 'message',
199
+ role: record.role,
200
+ occurredAt,
201
+ text: record.content,
202
+ rawReference: { locatorClass: GROK_BUILD_NATIVE_LOCATOR_CLASS, sequence: line.sequence },
203
+ extensions: common,
204
+ };
205
+ }
206
+ /** Read and validate active_sessions.json (read-only, bounded); throws on misuse. */
207
+ export async function readGrokBuildActiveRegistry(selectedPath, allowedRoots) {
208
+ const authorized = await authorizeSourceFile({
209
+ selectedPath, allowedRoots, maxBytes: ACTIVE_SESSIONS_MAX_BYTES,
210
+ });
211
+ const raw = await readFile(authorized.canonicalPath, 'utf8');
212
+ let value;
213
+ try {
214
+ value = JSON.parse(raw);
215
+ }
216
+ catch {
217
+ throw new SessionContractError('SCHEMA_DRIFT', 'Grok Build active_sessions.json is not valid JSON', schemaIssueDetail('<root>'));
218
+ }
219
+ const parsed = ActiveSessionsSchema.safeParse(value);
220
+ if (!parsed.success) {
221
+ throw new SessionContractError('SCHEMA_DRIFT', 'unrecognized Grok Build active_sessions.json shape', schemaRejectionDetail(parsed.error));
222
+ }
223
+ assertSupportedSchemaMajor(declaredSchema(parsed.data.schemaVersion), GROK_BUILD_ACTIVE_SESSIONS_SCHEMA_VERSION);
224
+ return parsed.data;
225
+ }
226
+ /** Provider-reported activity only; never a verified running process. */
227
+ export function grokBuildLiveState(registry, sessionId, nowMs = Date.now()) {
228
+ if (!registry)
229
+ return { state: 'unknown', basis: 'none', reasonCode: 'NO_ACTIVE_SESSIONS_GRANT' };
230
+ const entry = registry.sessions.find((item) => item.sessionId.toLowerCase() === sessionId.toLowerCase());
231
+ if (!entry)
232
+ return { state: 'unknown', basis: 'grok-active-sessions', reasonCode: 'SESSION_NOT_LISTED' };
233
+ if (entry.lastHeartbeatAt === undefined) {
234
+ return { state: 'unknown', basis: 'grok-active-sessions', reasonCode: 'NO_HEARTBEAT' };
235
+ }
236
+ const heartbeatMs = entry.lastHeartbeatAt < 10_000_000_000
237
+ ? entry.lastHeartbeatAt * 1_000
238
+ : entry.lastHeartbeatAt;
239
+ if (!Number.isFinite(heartbeatMs)) {
240
+ return { state: 'unknown', basis: 'grok-active-sessions', reasonCode: 'INVALID_HEARTBEAT' };
241
+ }
242
+ if (heartbeatMs > nowMs) {
243
+ return { state: 'unknown', basis: 'grok-active-sessions', reasonCode: 'FUTURE_HEARTBEAT' };
244
+ }
245
+ if (nowMs - heartbeatMs > GROK_BUILD_ACTIVE_FRESHNESS_MS) {
246
+ return { state: 'unknown', basis: 'grok-active-sessions', reasonCode: 'STALE_HEARTBEAT' };
247
+ }
248
+ return { state: 'active', basis: 'grok-active-sessions', reasonCode: 'FRESH_HEARTBEAT' };
249
+ }
250
+ export function parseGrokBuildNativeCursor(value) {
251
+ if (!value)
252
+ return 0;
253
+ if (!/^\d+$/.test(value)) {
254
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Build native cursor', schemaIssueDetail('cursor'));
255
+ }
256
+ return Number(value);
257
+ }
258
+ function declaredSchema(value) {
259
+ if (typeof value === 'number')
260
+ return `${value}.0.0`;
261
+ return /^\d+$/.test(String(value)) ? `${value}.0.0` : String(value);
262
+ }
263
+ function timestamp(value) {
264
+ if (value === undefined || value === null)
265
+ return undefined;
266
+ const date = new Date(value < 10_000_000_000 ? value * 1_000 : value);
267
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
268
+ }
269
+ //# sourceMappingURL=grok-build-native.js.map
@@ -1,31 +1,49 @@
1
1
  import { basename, extname } from 'node:path';
2
2
  import { SessionContractError, schemaIssueDetail, } from '../contracts.js';
3
3
  import { readBoundedText } from '../readers.js';
4
+ import { discoverGrokBuildNativeChatHistories, GROK_BUILD_NATIVE_DISCOVERY_MAX_FILES, GROK_BUILD_NATIVE_LOCATOR_CLASS, GROK_BUILD_NATIVE_SCHEMA_VERSION, grokBuildNativeSessionId, parseGrokBuildNativeCursor, readGrokBuildNativeChatHistory, toGrokBuildNativeRecord, } from './grok-build-native.js';
4
5
  export const GROK_BUILD_ADAPTER_VERSION = '1.0.0';
5
6
  export const GROK_BUILD_CLI_EXPORT_SCHEMA_VERSION = '1.0.0';
6
7
  export const GROK_BUILD_CLI_EXPORT_LOCATOR_CLASS = 'grok-build-cli-markdown-export';
7
8
  /**
8
- * Imports the documented `grok export <session-id> <file>` surface.
9
+ * Imports the documented `grok export <session-id> <file>` surface plus the
10
+ * native `chat_history.jsonl` store under an explicitly authorized
11
+ * sessions root (`--grok-root`).
9
12
  *
10
- * The public export contract is intentionally lossy. Direct reads from
11
- * $GROK_HOME/sessions are prohibited until a released native schema has been
12
- * qualified independently.
13
+ * The public export contract is intentionally lossy. Native reads use the
14
+ * synthetic v1 schema in grok-build-native.ts (real stores were never
15
+ * inspected); anything outside it fails closed. AIWG never runs
16
+ * `grok export` itself: export remains an operator-selected fallback.
13
17
  */
14
18
  export class GrokBuildSessionAdapter {
15
19
  limits;
20
+ maxFiles;
16
21
  provider = 'grok-build';
17
22
  adapterVersion = GROK_BUILD_ADAPTER_VERSION;
18
23
  disposition = 'implemented';
19
- supportedOperations = ['inspect', 'stream'];
20
- acquisitionModes = ['manual-export'];
21
- constructor(limits) {
24
+ supportedOperations = ['discover', 'inspect', 'stream'];
25
+ acquisitionModes = ['manual-export', 'jsonl'];
26
+ constructor(limits, maxFiles = GROK_BUILD_NATIVE_DISCOVERY_MAX_FILES) {
22
27
  this.limits = limits;
28
+ this.maxFiles = maxFiles;
23
29
  }
24
- async *discover(_scope) {
25
- throw new SessionContractError('UNSUPPORTED_OPERATION', 'Grok Build native-store discovery is disabled. Run `grok sessions list`, then '
26
- + '`grok export <session-id> <session-id>.md` and import that authorized export.');
30
+ async *discover(scope) {
31
+ if (scope.allowedRoots.length === 0) {
32
+ throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Grok Build native discovery requires an explicitly authorized sessions root; pass --grok-root.');
33
+ }
34
+ for (const root of [...scope.allowedRoots].sort()) {
35
+ yield* discoverGrokBuildNativeChatHistories(root, this.maxFiles);
36
+ }
27
37
  }
28
38
  async inspect(source) {
39
+ if (source.locatorClass === GROK_BUILD_NATIVE_LOCATOR_CLASS) {
40
+ const native = await readGrokBuildNativeChatHistory(source, this.limits);
41
+ return {
42
+ sourceSchemaVersion: GROK_BUILD_NATIVE_SCHEMA_VERSION,
43
+ consistency: native.incompleteTail ? 'provisional' : 'complete',
44
+ operationalState: 'available',
45
+ };
46
+ }
29
47
  await this.readSource(source);
30
48
  return {
31
49
  sourceSchemaVersion: GROK_BUILD_CLI_EXPORT_SCHEMA_VERSION,
@@ -34,13 +52,33 @@ export class GrokBuildSessionAdapter {
34
52
  };
35
53
  }
36
54
  async *stream(source, cursor) {
55
+ if (source.locatorClass === GROK_BUILD_NATIVE_LOCATOR_CLASS) {
56
+ const sessionId = grokBuildNativeSessionId(source.locator);
57
+ if (!sessionId) {
58
+ throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Grok Build native source must be an explicitly authorized chat_history.jsonl path');
59
+ }
60
+ const native = await readGrokBuildNativeChatHistory(source, this.limits);
61
+ const start = parseGrokBuildNativeCursor(cursor?.value);
62
+ let outputIndex = 0;
63
+ for (const entry of native.records) {
64
+ const position = outputIndex;
65
+ outputIndex += 1;
66
+ if (position < start)
67
+ continue;
68
+ const record = toGrokBuildNativeRecord(sessionId, entry);
69
+ record.sourceCursor = String(outputIndex);
70
+ yield record;
71
+ }
72
+ return;
73
+ }
37
74
  const parsed = await this.readSource(source);
38
75
  const start = parseCursor(cursor?.value);
76
+ let outputIndex = 0;
39
77
  for (const [sequence, block] of parsed.blocks.entries()) {
40
- if (sequence < start)
41
- continue;
42
78
  if (block.heading === 'Tools') {
43
- for (const tool of parseTools(block.text, sequence))
79
+ for (const tool of parseTools(block.text, sequence)) {
80
+ if (outputIndex++ < start)
81
+ continue;
44
82
  yield {
45
83
  nativeSessionId: parsed.sessionId,
46
84
  nativeEventId: undefined,
@@ -53,8 +91,11 @@ export class GrokBuildSessionAdapter {
53
91
  rawReference: { locatorClass: source.locatorClass, sequence },
54
92
  extensions: provenance(source, parsed.sessionId, 'Tools'),
55
93
  };
94
+ }
56
95
  continue;
57
96
  }
97
+ if (outputIndex++ < start)
98
+ continue;
58
99
  yield {
59
100
  nativeSessionId: parsed.sessionId,
60
101
  nativeEventId: undefined,
@@ -70,8 +111,9 @@ export class GrokBuildSessionAdapter {
70
111
  }
71
112
  async readSource(source) {
72
113
  if (source.locatorClass !== GROK_BUILD_CLI_EXPORT_LOCATOR_CLASS) {
73
- throw new SessionContractError('UNSUPPORTED_OPERATION', 'Grok Build accepts only a documented `grok export` Markdown transcript; '
74
- + 'native session files, Grok Bot exports, and Cursor stores are not supported.');
114
+ throw new SessionContractError('UNSUPPORTED_OPERATION', 'Grok Build accepts only a documented `grok export` Markdown transcript or an '
115
+ + `explicitly authorized \`${GROK_BUILD_NATIVE_LOCATOR_CLASS}\` source; `
116
+ + 'Grok Bot exports and Cursor stores are not supported.');
75
117
  }
76
118
  const filename = basename(source.locator, extname(source.locator));
77
119
  if (extname(source.locator).toLowerCase() !== '.md' || !UUID.test(filename)) {
@@ -152,6 +152,13 @@ export class GrokbotSessionAdapter {
152
152
  * rename-swap), not appended to. See the class-level doc comment.
153
153
  */
154
154
  sourceContinuity = 'snapshot';
155
+ /**
156
+ * Account scope (#3232): conversations carry no workspace identity, so
157
+ * every workspace authorizing the same `--grokbot-root` would otherwise
158
+ * import the same conversations. The importer routes account-scoped
159
+ * sources to the host catalog once; workspace catalogs keep a reference.
160
+ */
161
+ scopeClass = 'account';
155
162
  interchange;
156
163
  constructor(limits, maxFiles = 10_000) {
157
164
  this.limits = limits;
@@ -0,0 +1,252 @@
1
+ // Hermes state.db snapshot reader: read-only backup, schema-gated projection (synthetic physical schema v1).
2
+ import { chmod, lstat, mkdir, realpath, rm, stat, writeFile } from 'node:fs/promises';
3
+ import { join, resolve } from 'node:path';
4
+ import { sha256, SessionContractError } from '../contracts.js';
5
+ import { requireFeaturePackage } from '../../features/runtime.js';
6
+ import { matchSessionWorkspace } from '../workspace-matching.js';
7
+ export const HERMES_STATE_DB_FILENAME = 'state.db';
8
+ export const HERMES_NATIVE_PHYSICAL_SCHEMA = 'hermes-synthetic-v1';
9
+ export const HERMES_SNAPSHOT_LOCATOR_CLASS = 'hermes-consistent-snapshot-jsonl';
10
+ const STAGING_DIR_MODE = 0o700;
11
+ const FILE_MODE = 0o600;
12
+ const MAX_SOURCE_BYTES = 1024 * 1024 * 1024;
13
+ const MAX_SESSIONS = 1_000_000;
14
+ /** Owned staging beneath the workspace session store; never inside the provider tree. */
15
+ export function hermesSnapshotStagingDir(workspacePath) {
16
+ return join(resolve(workspacePath), '.aiwg', 'sessions', 'staging', 'hermes');
17
+ }
18
+ export function hermesSnapshotProjectionPath(stagingDir, canonicalRoot) {
19
+ return join(stagingDir, `state-${sha256(canonicalRoot).slice(7, 23)}.snapshot.jsonl`);
20
+ }
21
+ function loadSqlite() {
22
+ try {
23
+ return requireFeaturePackage('better-sqlite3');
24
+ }
25
+ catch {
26
+ throw new SessionContractError('UNSUPPORTED_OPERATION', 'Hermes native discovery requires the optional sqlite backend; '
27
+ + 'run `aiwg features install sqlite`, then rediscover with --hermes-root.');
28
+ }
29
+ }
30
+ function sqliteUnavailable(error, what) {
31
+ if (error instanceof SessionContractError)
32
+ throw error;
33
+ const code = typeof error.code === 'string'
34
+ ? error.code
35
+ : 'SQLITE_ERROR';
36
+ return new SessionContractError('UNSUPPORTED_OPERATION', `Hermes snapshot is unavailable while ${what} (sqlite ${code}); `
37
+ + 'start Hermes so a consistent backup can be taken without writing to its directory, '
38
+ + 'or import a `hermes sessions export` file instead.');
39
+ }
40
+ /**
41
+ * WAL reads need the provider's shared-memory index, and any first read
42
+ * would create it as a provider-tree write. A WAL database without its
43
+ * sidecars present therefore refuses BEFORE any SQLite contact — fail closed
44
+ * rather than writing sidecars or reopening writable. (Hermes runs WAL mode;
45
+ * the export fallback covers a stopped provider.) When the index pre-exists,
46
+ * the read-only open reuses it without creating files; its volatile
47
+ * read-marks may advance through normal WAL read concurrency — the same as
48
+ * any Hermes reader — but tree membership, sizes, and database content never
49
+ * change.
50
+ */
51
+ async function assertReadableWithoutSidecarWrites(live) {
52
+ const shm = await lstat(`${live}-shm`).then((item) => item.isFile()).catch(() => false);
53
+ if (!shm) {
54
+ throw new SessionContractError('UNSUPPORTED_OPERATION', 'Hermes snapshot needs the provider WAL sidecars (state.db-shm); '
55
+ + 'start Hermes so a consistent backup can be taken without writing to its directory, '
56
+ + 'or import a `hermes sessions export` file instead.');
57
+ }
58
+ }
59
+ async function authorizeLiveDb(liveDbPath, allowedRoots) {
60
+ if (allowedRoots.length === 0) {
61
+ throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Hermes discovery requires --hermes-root.');
62
+ }
63
+ const selected = resolve(liveDbPath);
64
+ const link = await lstat(selected).catch(() => null);
65
+ if (!link) {
66
+ throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Hermes state.db is not available under --hermes-root.');
67
+ }
68
+ if (link.isSymbolicLink()) {
69
+ throw new SessionContractError('SOURCE_SYMLINK', 'Hermes state.db cannot be a symlink.');
70
+ }
71
+ const canonical = await realpath(selected).catch(() => null);
72
+ if (!canonical) {
73
+ throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Hermes state.db is not available under --hermes-root.');
74
+ }
75
+ const canonicalRoots = await Promise.all(allowedRoots.map(async (root) => resolve(root)));
76
+ const inside = canonicalRoots.some((root) => canonical === root || canonical.startsWith(`${root}/`));
77
+ if (!inside) {
78
+ throw new SessionContractError('SOURCE_OUTSIDE_ALLOWED_ROOT', 'Hermes state.db escapes the authorized roots.');
79
+ }
80
+ const details = await stat(canonical);
81
+ if (!details.isFile()) {
82
+ throw new SessionContractError('SOURCE_NOT_REGULAR_FILE', 'Hermes state.db must be a regular file.');
83
+ }
84
+ if (details.size > MAX_SOURCE_BYTES) {
85
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Hermes state.db exceeds the snapshot byte limit', {
86
+ limit: 'maxBytes', configured: MAX_SOURCE_BYTES, observed: details.size,
87
+ });
88
+ }
89
+ return canonical;
90
+ }
91
+ const REQUIRED_SESSION_COLUMNS = new Set([
92
+ 'id', 'source', 'user_id', 'model', 'model_config', 'parent_session_id',
93
+ 'compressed_from_session_id', 'started_at', 'ended_at', 'end_reason',
94
+ 'archived', 'inactive', 'cwd', 'git_branch', 'routing_key', 'input_tokens',
95
+ 'output_tokens', 'cache_read_tokens', 'cache_write_tokens', 'reasoning_tokens',
96
+ 'estimated_cost_usd', 'actual_cost_usd', 'billing_provider',
97
+ 'export_deleted_at', 'provider_deleted_at',
98
+ ]);
99
+ const REQUIRED_MESSAGE_COLUMNS = new Set([
100
+ 'session_id', 'seq', 'id', 'role', 'content', 'tool_call_id', 'tool_calls',
101
+ 'tool_name', 'timestamp', 'token_count', 'finish_reason', 'reasoning',
102
+ 'reasoning_content', 'reasoning_details', 'codex_reasoning_items',
103
+ ]);
104
+ function assertPhysicalSchema(backup) {
105
+ const tables = new Set(backup.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((row) => String(row.name)));
106
+ if (!tables.has('meta') || !tables.has('sessions') || !tables.has('messages')) {
107
+ throw new SessionContractError('SCHEMA_DRIFT', 'unrecognized Hermes state.db physical schema', { issues: [{ path: 'tables', code: 'custom' }] });
108
+ }
109
+ const marker = backup.prepare('SELECT value FROM meta WHERE key = ?').get('native_schema');
110
+ if (!marker || marker.value !== HERMES_NATIVE_PHYSICAL_SCHEMA) {
111
+ throw new SessionContractError('SCHEMA_DRIFT', 'unsupported Hermes state.db physical schema version', { issues: [{ path: 'meta.native_schema', code: 'custom' }] });
112
+ }
113
+ for (const [table, required] of [['sessions', REQUIRED_SESSION_COLUMNS], ['messages', REQUIRED_MESSAGE_COLUMNS]]) {
114
+ const columns = new Set(backup.prepare(`PRAGMA table_info(${table})`).all().map((row) => String(row.name)));
115
+ for (const column of required) {
116
+ if (!columns.has(column)) {
117
+ throw new SessionContractError('SCHEMA_DRIFT', `Hermes state.db table ${table} is missing column ${column}`, { issues: [{ path: `${table}.${column}`, code: 'custom' }] });
118
+ }
119
+ }
120
+ }
121
+ }
122
+ function parseJsonColumn(value, path) {
123
+ if (value === null || value === undefined)
124
+ return undefined;
125
+ if (typeof value !== 'string')
126
+ return value;
127
+ try {
128
+ return JSON.parse(value);
129
+ }
130
+ catch {
131
+ throw new SessionContractError('SCHEMA_DRIFT', `Hermes state.db column ${path} is not valid JSON`, { issues: [{ path, code: 'invalid_json' }] });
132
+ }
133
+ }
134
+ function optional(value) {
135
+ return value === null || value === undefined ? undefined : value;
136
+ }
137
+ /**
138
+ * Take a consistent backup of the live database (never opened for writing)
139
+ * and project workspace-matching sessions to snapshot JSONL. Returns null
140
+ * when no session matches the authorized workspace.
141
+ */
142
+ export async function snapshotHermesStateDb(options) {
143
+ const canonicalRoot = await realpath(resolve(options.hermesRoot)).catch(() => resolve(options.hermesRoot));
144
+ const live = await authorizeLiveDb(join(options.hermesRoot, HERMES_STATE_DB_FILENAME), options.allowedRoots);
145
+ await assertReadableWithoutSidecarWrites(live);
146
+ const Database = loadSqlite();
147
+ await mkdir(options.stagingDir, { recursive: true, mode: STAGING_DIR_MODE });
148
+ const staging = await realpath(options.stagingDir);
149
+ const backupPath = join(staging, `.backup-${process.pid}-${Date.now()}.db`);
150
+ const projectionPath = hermesSnapshotProjectionPath(staging, canonicalRoot);
151
+ let source;
152
+ let backup;
153
+ try {
154
+ source = new Database(live, { readonly: true, fileMustExist: true });
155
+ await source.backup(backupPath);
156
+ }
157
+ catch (error) {
158
+ throw sqliteUnavailable(error, 'opening the provider database for backup');
159
+ }
160
+ finally {
161
+ source?.close();
162
+ }
163
+ try {
164
+ backup = new Database(backupPath, { readonly: true, fileMustExist: true });
165
+ assertPhysicalSchema(backup);
166
+ const sessions = backup.prepare('SELECT * FROM sessions ORDER BY started_at ASC, id ASC').all();
167
+ const lines = [];
168
+ let matched = 0;
169
+ let excluded = 0;
170
+ let bytes = 0;
171
+ for (const row of sessions) {
172
+ if (matched + excluded >= MAX_SESSIONS) {
173
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Hermes snapshot exceeds the session limit', {
174
+ limit: 'maxSessions', configured: MAX_SESSIONS, observed: matched + excluded + 1,
175
+ });
176
+ }
177
+ const cwd = typeof row.cwd === 'string' && row.cwd.length > 0 ? row.cwd : null;
178
+ const workspaceMatch = cwd ? await matchSessionWorkspace(cwd, options.workspaceId) : null;
179
+ if (!workspaceMatch) {
180
+ excluded += 1;
181
+ continue;
182
+ }
183
+ const messages = backup.prepare('SELECT * FROM messages WHERE session_id = ? ORDER BY seq ASC').all(String(row.id));
184
+ const record = {
185
+ schemaVersion: 23,
186
+ id: String(row.id),
187
+ source: String(row.source ?? 'cli'),
188
+ user_id: optional(row.user_id),
189
+ model: optional(row.model),
190
+ model_config: parseJsonColumn(row.model_config, 'sessions.model_config'),
191
+ parent_session_id: optional(row.parent_session_id),
192
+ compressed_from_session_id: optional(row.compressed_from_session_id),
193
+ started_at: row.started_at,
194
+ ended_at: optional(row.ended_at),
195
+ end_reason: optional(row.end_reason),
196
+ archived: row.archived,
197
+ inactive: row.inactive,
198
+ cwd,
199
+ git_branch: optional(row.git_branch),
200
+ routing_key: optional(row.routing_key),
201
+ input_tokens: optional(row.input_tokens),
202
+ output_tokens: optional(row.output_tokens),
203
+ cache_read_tokens: optional(row.cache_read_tokens),
204
+ cache_write_tokens: optional(row.cache_write_tokens),
205
+ reasoning_tokens: optional(row.reasoning_tokens),
206
+ estimated_cost_usd: optional(row.estimated_cost_usd),
207
+ actual_cost_usd: optional(row.actual_cost_usd),
208
+ billing_provider: optional(row.billing_provider),
209
+ messages: messages.map((message) => ({
210
+ id: message.id,
211
+ role: String(message.role),
212
+ content: parseJsonColumn(message.content, 'messages.content'),
213
+ tool_call_id: optional(message.tool_call_id),
214
+ tool_calls: parseJsonColumn(message.tool_calls, 'messages.tool_calls'),
215
+ tool_name: optional(message.tool_name),
216
+ timestamp: optional(message.timestamp),
217
+ token_count: optional(message.token_count),
218
+ finish_reason: optional(message.finish_reason),
219
+ reasoning: optional(message.reasoning),
220
+ reasoning_content: optional(message.reasoning_content),
221
+ reasoning_details: parseJsonColumn(message.reasoning_details, 'messages.reasoning_details'),
222
+ codex_reasoning_items: parseJsonColumn(message.codex_reasoning_items, 'messages.codex_reasoning_items'),
223
+ })),
224
+ export_deleted_at: optional(row.export_deleted_at),
225
+ provider_deleted_at: optional(row.provider_deleted_at),
226
+ snapshotConsistency: 'sqlite-backup',
227
+ };
228
+ const line = `${JSON.stringify(record)}\n`;
229
+ bytes += Buffer.byteLength(line);
230
+ if (bytes > MAX_SOURCE_BYTES) {
231
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Hermes snapshot exceeds the projection byte limit', {
232
+ limit: 'maxBytes', configured: MAX_SOURCE_BYTES, observed: bytes,
233
+ });
234
+ }
235
+ lines.push(line);
236
+ matched += 1;
237
+ }
238
+ if (matched === 0)
239
+ return null;
240
+ await writeFile(projectionPath, lines.join(''), { mode: FILE_MODE });
241
+ await chmod(projectionPath, FILE_MODE);
242
+ return { projectionPath, authorizedRoot: staging, matched, excluded };
243
+ }
244
+ catch (error) {
245
+ throw sqliteUnavailable(error, 'reading the snapshot');
246
+ }
247
+ finally {
248
+ backup?.close();
249
+ await rm(backupPath, { force: true });
250
+ }
251
+ }
252
+ //# sourceMappingURL=hermes-state-db.js.map