@aiwg/cli 2026.10.1 → 2026.10.3
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/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3091 -24
- package/bin/aiwg.mjs +86 -21
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/channel/manager.mjs +59 -10
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/decision.js +210 -4
- package/dist/src/cli/handlers/diagnose.js +9 -2
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/init.js +245 -30
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +69 -6
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +16 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/runtime-info.js +24 -9
- package/dist/src/cli/handlers/script-runner.js +16 -6
- package/dist/src/cli/handlers/session.js +396 -62
- package/dist/src/cli/handlers/sessions.js +146 -40
- package/dist/src/cli/handlers/setup-manifest.js +439 -39
- package/dist/src/cli/handlers/setup.js +139 -39
- package/dist/src/cli/handlers/subcommands.js +3 -1
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/use.js +13 -0
- package/dist/src/cli/handlers/utilities.js +106 -23
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/config/aiwg-config.js +268 -4
- package/dist/src/config/cli.js +146 -19
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/user-config.js +36 -10
- package/dist/src/decision/adapters/jev.js +75 -27
- package/dist/src/decision/driver.js +3 -1
- package/dist/src/decision/entry.js +38 -6
- package/dist/src/decision/heldout/collector.js +10 -1
- package/dist/src/decision/index.js +1 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/ledger.js +839 -0
- package/dist/src/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -0
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +3 -1
- package/dist/src/installation/manager-command.mjs +1 -1
- package/dist/src/installation/manager.mjs +17 -2
- package/dist/src/lint/runner.js +2 -2
- package/dist/src/mcp/adapters/codex-runtime.js +360 -56
- package/dist/src/mcp/cli.mjs +587 -77
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.mjs +16 -26
- package/dist/src/mcp/helpers.mjs +106 -2
- package/dist/src/mcp/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +264 -57
- package/dist/src/mcp/registry.mjs +198 -45
- package/dist/src/mcp/server.mjs +403 -20
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +21 -3
- package/dist/src/mcp/tools/orchestration.mjs +38 -0
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +639 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +70 -13
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3091 -24
- package/dist/src/models/model-discovery.js +862 -63
- package/dist/src/models/provider-policy.js +625 -11
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +259 -0
- package/dist/src/models/worker-direct-readiness.js +529 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +131 -0
- package/dist/src/models/worker-installation.mjs +879 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.mjs +387 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/grok-build-headless.js +43 -21
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/serve/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +9 -0
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +538 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +45 -0
- package/dist/src/sessions/adapters/generic.js +37 -3
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +423 -24
- package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
- package/dist/src/sessions/adapters/muse.js +68 -29
- package/dist/src/sessions/adapters/pi.js +1 -0
- package/dist/src/sessions/batch-contracts.js +13 -1
- package/dist/src/sessions/batch-import.js +23 -42
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +35 -0
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/importer.js +24 -8
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/repository.js +54 -13
- package/dist/src/sessions/workspace-discovery.js +99 -37
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +18 -1
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
- package/tools/_resolve-impl.mjs +39 -0
- package/tools/agents/deploy-agents.mjs +15 -2
- package/tools/agents/providers/base.mjs +49 -32
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +6 -3
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/providers/deepseek-harness-transport.mjs +358 -0
- package/tools/providers/omp-transport.mjs +192 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- package/vscode-extension/schemas/aiwg.config.v1.json +1953 -0
|
@@ -1,49 +1,448 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Grok Bot session adapter — manual-export
|
|
3
|
-
* for
|
|
2
|
+
* Grok Bot session adapter — manual-export interchange plus an evidence-gated
|
|
3
|
+
* native locator for the Grok Bot desktop app's local transcript cache.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Native discovery (#2970) is Linux-only and requires an explicitly
|
|
6
|
+
* authorized `sand-client-persistence` root (`--grokbot-root`); there is no
|
|
7
|
+
* default path and nothing is guessed for macOS/Windows, where the layout is
|
|
8
|
+
* unverified. The store is one JSON document per key, named `<base32
|
|
9
|
+
* key>.blob` (RFC 4648 section 6, lowercase, unpadded). Discovery decodes
|
|
10
|
+
* ONLY filenames to classify them; it never opens a file whose decoded key
|
|
11
|
+
* is outside the allowlist below, and it never recurses or follows symlinks.
|
|
7
12
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
13
|
+
* Allowlisted keys (see grokbot-locator.ts):
|
|
14
|
+
* - `...transcript.replicas.<conversation-uuid>` — one conversation; the
|
|
15
|
+
* only key class ever selected as an importable source.
|
|
16
|
+
* - `...roster.last-roster` — account conversation index; never opened by
|
|
17
|
+
* this adapter (no session content), kept out of discover() results.
|
|
18
|
+
* Every other key (composer-drafts, send-journal, experiments, ui-layout,
|
|
19
|
+
* sand-secrets, box-secrets, cookies, local/session storage, etc.) is never
|
|
20
|
+
* decoded content, never opened, and `~/.grokbot` (daemon credentials) is
|
|
21
|
+
* never referenced.
|
|
22
|
+
*
|
|
23
|
+
* Grok Bot conversations carry no workspace/cwd field — there is no native
|
|
24
|
+
* evidence linking a conversation to a project directory. Following the
|
|
25
|
+
* established precedent for other account-scoped, non-workspace-scoped
|
|
26
|
+
* native locators (`pi`, `deepseek-harness`: no per-source workspace
|
|
27
|
+
* filtering in workspace-discovery.ts), every authorized conversation under
|
|
28
|
+
* `--grokbot-root` surfaces to whichever workspace runs discovery; this
|
|
29
|
+
* adapter never guesses a workspace link and `inspect()` never sets
|
|
30
|
+
* `declaredWorkspaceId` for native sources.
|
|
31
|
+
*
|
|
32
|
+
* The replica is a bounded client-side cache (~200 entries) that can evict
|
|
33
|
+
* its oldest entries as new ones arrive, so a re-read is not append-only —
|
|
34
|
+
* the file may shrink, and Grok Bot may rewrite it via a rename-swap that
|
|
35
|
+
* changes its inode. This adapter declares `sourceContinuity: 'snapshot'`
|
|
36
|
+
* (contracts.ts), which tells the shared importer to skip its generic
|
|
37
|
+
* size-shrink / file-identity / prefix-rewrite checks (those assume
|
|
38
|
+
* append-only growth) for this source. Idempotent, non-duplicating re-import
|
|
39
|
+
* across the sliding window then relies on two things instead: (1)
|
|
40
|
+
* `workspace-discovery.ts` assigns a conversation-id–derived,
|
|
41
|
+
* content-independent `sourceId` for native sources (mirroring the existing
|
|
42
|
+
* OMP convention), so the importer's checkpoint continuity survives content
|
|
43
|
+
* changes; (2) `stream()` resumes from the native, monotonic `seq` carried
|
|
44
|
+
* by each entry (not a position/byte offset, which the sliding window would
|
|
45
|
+
* invalidate) and tags every record's `sourceCursor` as `native:<seq>` — an
|
|
46
|
+
* opaque provider-native resume token the importer stores verbatim
|
|
47
|
+
* (`positionKind: 'provider-native'`), never reinterpreted as a byte offset.
|
|
48
|
+
* Combined with the catalog's identity-keyed `INSERT OR IGNORE`, re-importing
|
|
49
|
+
* after the window has slid can only add genuinely new entries, never
|
|
50
|
+
* duplicate ones.
|
|
10
51
|
*
|
|
11
52
|
* @see docs/providers/grokbot-sessions.md
|
|
12
|
-
* @issue #215
|
|
53
|
+
* @issue #215, #2970
|
|
13
54
|
*/
|
|
14
|
-
import {
|
|
55
|
+
import { opendir } from 'node:fs/promises';
|
|
56
|
+
import { resolve } from 'node:path';
|
|
57
|
+
import { z } from 'zod';
|
|
58
|
+
import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
|
|
59
|
+
import { readBoundedJson } from '../readers.js';
|
|
15
60
|
import { GenericSessionInterchangeAdapter, } from './generic.js';
|
|
61
|
+
import { classifyGrokbotBlobName, grokbotConversationIdFromLocator, } from './grokbot-locator.js';
|
|
16
62
|
export const GROKBOT_ADAPTER_VERSION = '1.0.0';
|
|
17
63
|
export const GROKBOT_EXPORT_SCHEMA_VERSION = '1.0.0';
|
|
64
|
+
export const GROKBOT_NATIVE_SCHEMA_VERSION = '1.0.0';
|
|
65
|
+
export const GROKBOT_NATIVE_SCHEMA_MAJOR = 1;
|
|
18
66
|
export const GROKBOT_LOCATOR_CLASS = 'manual-export';
|
|
67
|
+
export const GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS = 'grokbot-native-transcript-replica';
|
|
68
|
+
const GrokbotEntryBaseSchema = z.object({
|
|
69
|
+
kind: z.string().min(1),
|
|
70
|
+
id: z.string().min(1),
|
|
71
|
+
timestampMs: z.number().int().nonnegative(),
|
|
72
|
+
requestId: z.string().optional(),
|
|
73
|
+
seq: z.number().int().nonnegative(),
|
|
74
|
+
}).passthrough();
|
|
75
|
+
const SendMessageEntrySchema = GrokbotEntryBaseSchema.extend({
|
|
76
|
+
kind: z.literal('send-message'),
|
|
77
|
+
respondedValue: z.unknown().optional(),
|
|
78
|
+
secretProvided: z.boolean().optional(),
|
|
79
|
+
message: z.discriminatedUnion('type', [
|
|
80
|
+
z.object({ type: z.literal('text'), content: z.string() }).passthrough(),
|
|
81
|
+
z.object({
|
|
82
|
+
type: z.literal('widget'),
|
|
83
|
+
widget: z.object({ prompt: z.string() }).passthrough(),
|
|
84
|
+
}).passthrough(),
|
|
85
|
+
z.object({
|
|
86
|
+
type: z.literal('email-draft'),
|
|
87
|
+
draft: z.object({ subject: z.string().optional(), body: z.string() }).passthrough(),
|
|
88
|
+
}).passthrough(),
|
|
89
|
+
z.object({
|
|
90
|
+
type: z.literal('slack-draft'),
|
|
91
|
+
draft: z.object({ target: z.string().optional(), body: z.string() }).passthrough(),
|
|
92
|
+
}).passthrough(),
|
|
93
|
+
z.object({
|
|
94
|
+
type: z.literal('attachment'),
|
|
95
|
+
url: z.string(),
|
|
96
|
+
file_name: z.string(),
|
|
97
|
+
}).passthrough(),
|
|
98
|
+
z.object({
|
|
99
|
+
type: z.literal('user-form'),
|
|
100
|
+
formRequest: z.object({ title: z.string() }).passthrough(),
|
|
101
|
+
}).passthrough(),
|
|
102
|
+
z.object({
|
|
103
|
+
type: z.literal('secret-request'),
|
|
104
|
+
secretRequest: z.object({ label: z.string() }).passthrough(),
|
|
105
|
+
}).passthrough(),
|
|
106
|
+
]),
|
|
107
|
+
});
|
|
108
|
+
const MessageEntrySchema = GrokbotEntryBaseSchema.extend({
|
|
109
|
+
kind: z.literal('message'),
|
|
110
|
+
role: z.enum(['user', 'assistant']),
|
|
111
|
+
content: z.string(),
|
|
112
|
+
fromAgent: z.object({ id: z.string().optional(), name: z.string().optional() })
|
|
113
|
+
.passthrough().optional(),
|
|
114
|
+
});
|
|
115
|
+
const UserAttachmentEntrySchema = GrokbotEntryBaseSchema.extend({
|
|
116
|
+
kind: z.literal('user-attachment'),
|
|
117
|
+
file_name: z.string(),
|
|
118
|
+
file_path: z.string().optional(),
|
|
119
|
+
width: z.number().optional(),
|
|
120
|
+
height: z.number().optional(),
|
|
121
|
+
byteSize: z.number().optional(),
|
|
122
|
+
});
|
|
123
|
+
const EventEntrySchema = GrokbotEntryBaseSchema.extend({
|
|
124
|
+
kind: z.literal('event'),
|
|
125
|
+
event: z.record(z.string(), z.unknown()),
|
|
126
|
+
});
|
|
127
|
+
const NoticeEntrySchema = GrokbotEntryBaseSchema.extend({
|
|
128
|
+
kind: z.literal('notice'),
|
|
129
|
+
text: z.string(),
|
|
130
|
+
});
|
|
131
|
+
const GrokbotReplicaValueSchema = z.object({
|
|
132
|
+
entries: z.array(GrokbotEntryBaseSchema),
|
|
133
|
+
epochHint: z.string().optional(),
|
|
134
|
+
acceptedSequenceHint: z.number().int().nonnegative().optional(),
|
|
135
|
+
persistedAt: z.number().int().nonnegative().optional(),
|
|
136
|
+
precedingTimestampMs: z.number().int().nonnegative().optional(),
|
|
137
|
+
}).passthrough();
|
|
138
|
+
const GrokbotReplicaDocumentSchema = z.object({
|
|
139
|
+
schemaVersion: z.number().int().nonnegative(),
|
|
140
|
+
value: GrokbotReplicaValueSchema,
|
|
141
|
+
}).passthrough();
|
|
19
142
|
export class GrokbotSessionAdapter {
|
|
143
|
+
limits;
|
|
144
|
+
maxFiles;
|
|
20
145
|
provider = 'grokbot';
|
|
21
146
|
adapterVersion = GROKBOT_ADAPTER_VERSION;
|
|
22
|
-
disposition = '
|
|
23
|
-
supportedOperations = ['inspect', 'stream'];
|
|
24
|
-
acquisitionModes = ['manual-export'];
|
|
147
|
+
disposition = 'implemented';
|
|
148
|
+
supportedOperations = ['discover', 'inspect', 'stream'];
|
|
149
|
+
acquisitionModes = ['manual-export', 'jsonl'];
|
|
150
|
+
/**
|
|
151
|
+
* The replica is rewritten wholesale (sliding window, possible
|
|
152
|
+
* rename-swap), not appended to. See the class-level doc comment.
|
|
153
|
+
*/
|
|
154
|
+
sourceContinuity = 'snapshot';
|
|
25
155
|
interchange;
|
|
26
|
-
constructor(limits) {
|
|
156
|
+
constructor(limits, maxFiles = 10_000) {
|
|
157
|
+
this.limits = limits;
|
|
158
|
+
this.maxFiles = maxFiles;
|
|
27
159
|
this.interchange = new GenericSessionInterchangeAdapter(limits);
|
|
28
160
|
}
|
|
29
|
-
async *discover(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
161
|
+
async *discover(scope) {
|
|
162
|
+
if (scope.allowedRoots.length === 0) {
|
|
163
|
+
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Grok Bot native discovery requires an explicitly authorized sand-client-persistence root; '
|
|
164
|
+
+ 'pass --grokbot-root. There is no default root and nothing is guessed for macOS or Windows.');
|
|
165
|
+
}
|
|
166
|
+
let count = 0;
|
|
167
|
+
for (const root of [...scope.allowedRoots].sort()) {
|
|
168
|
+
let directory;
|
|
169
|
+
try {
|
|
170
|
+
directory = await opendir(resolve(root));
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
for await (const dirent of directory) {
|
|
176
|
+
// Never recurse, never follow symlinks: only plain files directly
|
|
177
|
+
// under the authorized root are even filename-classified.
|
|
178
|
+
if (!dirent.isFile())
|
|
179
|
+
continue;
|
|
180
|
+
const classification = classifyGrokbotBlobName(dirent.name);
|
|
181
|
+
if (classification?.kind !== 'transcript-replica')
|
|
182
|
+
continue;
|
|
183
|
+
if (++count > this.maxFiles) {
|
|
184
|
+
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Grok Bot native discovery exceeded the authorized file limit');
|
|
185
|
+
}
|
|
186
|
+
yield {
|
|
187
|
+
provider: 'grokbot',
|
|
188
|
+
locator: resolve(root, dirent.name),
|
|
189
|
+
locatorClass: GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
33
193
|
}
|
|
34
194
|
async inspect(source) {
|
|
35
|
-
|
|
36
|
-
|
|
195
|
+
if (source.locatorClass === GROKBOT_LOCATOR_CLASS) {
|
|
196
|
+
return this.interchange.inspect(source);
|
|
197
|
+
}
|
|
198
|
+
this.assertNativeReplica(source);
|
|
199
|
+
const native = await this.readReplica(source);
|
|
200
|
+
return {
|
|
201
|
+
sourceSchemaVersion: GROKBOT_NATIVE_SCHEMA_VERSION,
|
|
202
|
+
consistency: native.truncated ? 'provisional' : 'complete',
|
|
203
|
+
operationalState: 'available',
|
|
204
|
+
// No native evidence links a conversation to a workspace/cwd; never
|
|
205
|
+
// fabricate one.
|
|
206
|
+
};
|
|
37
207
|
}
|
|
38
208
|
async *stream(source, cursor) {
|
|
39
|
-
|
|
40
|
-
|
|
209
|
+
if (source.locatorClass === GROKBOT_LOCATOR_CLASS) {
|
|
210
|
+
yield* this.interchange.stream(source, cursor);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
this.assertNativeReplica(source);
|
|
214
|
+
const native = await this.readReplica(source);
|
|
215
|
+
const conversationId = grokbotConversationIdFromLocator(source.locator);
|
|
216
|
+
if (!conversationId) {
|
|
217
|
+
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Grok Bot native locator does not decode to an authorized transcript-replica key');
|
|
218
|
+
}
|
|
219
|
+
const lastSeq = parseNativeCursor(cursor?.value);
|
|
220
|
+
const seen = new Set();
|
|
221
|
+
for (const entry of native.entries) {
|
|
222
|
+
if (seen.has(entry.seq)) {
|
|
223
|
+
throw new SessionContractError('DUPLICATE_NATIVE_ID', 'Grok Bot transcript replica contains a duplicate entry sequence');
|
|
224
|
+
}
|
|
225
|
+
seen.add(entry.seq);
|
|
226
|
+
if (entry.seq <= lastSeq)
|
|
227
|
+
continue;
|
|
228
|
+
const mapped = mapGrokbotEntry(entry);
|
|
229
|
+
if (!mapped)
|
|
230
|
+
continue; // Unrecognized entry kind: skipped, never fabricated.
|
|
231
|
+
yield {
|
|
232
|
+
nativeSessionId: conversationId,
|
|
233
|
+
nativeEventId: mapped.nativeEventId,
|
|
234
|
+
sequence: mapped.sequence,
|
|
235
|
+
kind: mapped.kind,
|
|
236
|
+
role: mapped.role,
|
|
237
|
+
participant: mapped.participant,
|
|
238
|
+
text: mapped.text,
|
|
239
|
+
occurredAt: mapped.occurredAt,
|
|
240
|
+
// Opaque provider-native resume token: the importer stores this
|
|
241
|
+
// verbatim (positionKind: 'provider-native') and never reinterprets
|
|
242
|
+
// it as a byte offset. It encodes the last imported native `seq`,
|
|
243
|
+
// which is monotonic and stable across the bounded cache evicting
|
|
244
|
+
// old entries. See the class-level doc comment for the full
|
|
245
|
+
// rationale.
|
|
246
|
+
sourceCursor: `native:${entry.seq}`,
|
|
247
|
+
rawReference: { locatorClass: GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS, sequence: entry.seq },
|
|
248
|
+
extensions: mapped.extensions,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
assertNativeReplica(source) {
|
|
253
|
+
if (source.locatorClass !== GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS) {
|
|
254
|
+
throw new SessionContractError('UNSUPPORTED_OPERATION', `Grok Bot supports only locatorClass "${GROKBOT_LOCATOR_CLASS}" or `
|
|
255
|
+
+ `"${GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS}" (got "${source.locatorClass}"). `
|
|
256
|
+
+ 'Do not reuse Cursor or other provider locators.');
|
|
257
|
+
}
|
|
258
|
+
// Enforce the filename allowlist before any read, not only in discover():
|
|
259
|
+
// a caller can hand inspect()/stream() any path with the native class.
|
|
260
|
+
if (!grokbotConversationIdFromLocator(source.locator)) {
|
|
261
|
+
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Grok Bot native locator does not decode to an authorized transcript-replica key');
|
|
262
|
+
}
|
|
41
263
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
264
|
+
async readReplica(source) {
|
|
265
|
+
const { value } = await readBoundedJson({
|
|
266
|
+
selectedPath: source.locator,
|
|
267
|
+
allowedRoots: source.authorizedScope.allowedRoots,
|
|
268
|
+
}, this.limits);
|
|
269
|
+
const parsed = GrokbotReplicaDocumentSchema.safeParse(value);
|
|
270
|
+
if (!parsed.success) {
|
|
271
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot transcript replica is malformed');
|
|
46
272
|
}
|
|
273
|
+
assertSupportedSchemaMajor(`${parsed.data.schemaVersion}.0.0`, GROKBOT_NATIVE_SCHEMA_MAJOR);
|
|
274
|
+
const { entries, precedingTimestampMs } = parsed.data.value;
|
|
275
|
+
// The resume cursor is the last yielded seq, so stream in seq order even
|
|
276
|
+
// if the cache stores entries out of order.
|
|
277
|
+
entries.sort((left, right) => left.seq - right.seq);
|
|
278
|
+
// A window that no longer starts at seq 1 has evicted history, whether or
|
|
279
|
+
// not the optional precedingTimestampMs hint is present.
|
|
280
|
+
const truncated = entries.length > 0
|
|
281
|
+
&& (entries[0].seq > 1 || (precedingTimestampMs ?? 0) > 0);
|
|
282
|
+
return { entries, truncated };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function parseNativeCursor(value) {
|
|
286
|
+
if (!value)
|
|
287
|
+
return 0;
|
|
288
|
+
const raw = value.startsWith('native:') ? value.slice('native:'.length) : value;
|
|
289
|
+
if (!/^\d+$/.test(raw))
|
|
290
|
+
throw new SessionContractError('SCHEMA_DRIFT', 'invalid Grok Bot native cursor');
|
|
291
|
+
return Number(raw);
|
|
292
|
+
}
|
|
293
|
+
function mapGrokbotEntry(entry) {
|
|
294
|
+
const occurredAt = new Date(entry.timestampMs).toISOString();
|
|
295
|
+
switch (entry.kind) {
|
|
296
|
+
case 'send-message':
|
|
297
|
+
return mapSendMessage(entry, occurredAt);
|
|
298
|
+
case 'message':
|
|
299
|
+
return mapMessage(entry, occurredAt);
|
|
300
|
+
case 'user-attachment':
|
|
301
|
+
return mapUserAttachment(entry, occurredAt);
|
|
302
|
+
case 'event':
|
|
303
|
+
return mapEvent(entry, occurredAt);
|
|
304
|
+
case 'notice':
|
|
305
|
+
return mapNotice(entry, occurredAt);
|
|
306
|
+
default:
|
|
307
|
+
// Unrecognized entry kind: safely skipped, never fabricated into a record.
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function mapSendMessage(raw, occurredAt) {
|
|
312
|
+
const parsed = SendMessageEntrySchema.safeParse(raw);
|
|
313
|
+
if (!parsed.success) {
|
|
314
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot send-message entry is malformed');
|
|
315
|
+
}
|
|
316
|
+
const entry = parsed.data;
|
|
317
|
+
const { message } = entry;
|
|
318
|
+
let text;
|
|
319
|
+
switch (message.type) {
|
|
320
|
+
case 'text':
|
|
321
|
+
text = message.content;
|
|
322
|
+
break;
|
|
323
|
+
case 'widget':
|
|
324
|
+
text = `[widget] ${message.widget.prompt} -> ${stringOrEmpty(entry.respondedValue)}`;
|
|
325
|
+
break;
|
|
326
|
+
case 'email-draft':
|
|
327
|
+
text = `[email-draft] ${message.draft.subject ?? ''}\n${message.draft.body}`;
|
|
328
|
+
break;
|
|
329
|
+
case 'slack-draft':
|
|
330
|
+
text = `[slack-draft] ${message.draft.target ?? ''}\n${message.draft.body}`;
|
|
331
|
+
break;
|
|
332
|
+
case 'attachment':
|
|
333
|
+
text = `[attachment] ${message.file_name} (${message.url})`;
|
|
334
|
+
break;
|
|
335
|
+
case 'user-form':
|
|
336
|
+
text = `[user-form] ${message.formRequest.title}`;
|
|
337
|
+
break;
|
|
338
|
+
case 'secret-request':
|
|
339
|
+
text = `[secret-request] ${message.secretRequest.label} (value not exported)`;
|
|
340
|
+
break;
|
|
341
|
+
default:
|
|
342
|
+
text = '';
|
|
47
343
|
}
|
|
344
|
+
return {
|
|
345
|
+
nativeEventId: entry.id,
|
|
346
|
+
sequence: entry.seq,
|
|
347
|
+
kind: `grokbot.send-message.${message.type}`,
|
|
348
|
+
role: 'assistant',
|
|
349
|
+
text,
|
|
350
|
+
occurredAt,
|
|
351
|
+
extensions: {
|
|
352
|
+
nativeKind: 'send-message',
|
|
353
|
+
messageType: message.type,
|
|
354
|
+
secretProvided: entry.secretProvided,
|
|
355
|
+
provenance: { acquisition: GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS },
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function mapMessage(raw, occurredAt) {
|
|
360
|
+
const parsed = MessageEntrySchema.safeParse(raw);
|
|
361
|
+
if (!parsed.success) {
|
|
362
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot message entry is malformed');
|
|
363
|
+
}
|
|
364
|
+
const entry = parsed.data;
|
|
365
|
+
return {
|
|
366
|
+
nativeEventId: entry.id,
|
|
367
|
+
sequence: entry.seq,
|
|
368
|
+
kind: 'grokbot.message',
|
|
369
|
+
role: entry.role,
|
|
370
|
+
participant: entry.fromAgent?.name ?? entry.fromAgent?.id,
|
|
371
|
+
text: entry.content,
|
|
372
|
+
occurredAt,
|
|
373
|
+
extensions: {
|
|
374
|
+
nativeKind: 'message',
|
|
375
|
+
fromAgent: entry.fromAgent,
|
|
376
|
+
provenance: { acquisition: GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS },
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function mapUserAttachment(raw, occurredAt) {
|
|
381
|
+
const parsed = UserAttachmentEntrySchema.safeParse(raw);
|
|
382
|
+
if (!parsed.success) {
|
|
383
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot user-attachment entry is malformed');
|
|
384
|
+
}
|
|
385
|
+
const entry = parsed.data;
|
|
386
|
+
return {
|
|
387
|
+
nativeEventId: entry.id,
|
|
388
|
+
sequence: entry.seq,
|
|
389
|
+
kind: 'grokbot.user-attachment',
|
|
390
|
+
role: 'user',
|
|
391
|
+
text: `[user-attachment] ${entry.file_name} `
|
|
392
|
+
+ `(${entry.width ?? '?'}x${entry.height ?? '?'}, ${entry.byteSize ?? '?'} bytes)`,
|
|
393
|
+
occurredAt,
|
|
394
|
+
extensions: {
|
|
395
|
+
nativeKind: 'user-attachment',
|
|
396
|
+
fileName: entry.file_name,
|
|
397
|
+
filePath: entry.file_path,
|
|
398
|
+
width: entry.width,
|
|
399
|
+
height: entry.height,
|
|
400
|
+
byteSize: entry.byteSize,
|
|
401
|
+
provenance: { acquisition: GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS },
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function mapEvent(raw, occurredAt) {
|
|
406
|
+
const parsed = EventEntrySchema.safeParse(raw);
|
|
407
|
+
if (!parsed.success) {
|
|
408
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot event entry is malformed');
|
|
409
|
+
}
|
|
410
|
+
const entry = parsed.data;
|
|
411
|
+
const label = stringOrEmpty(entry.event.kind) || stringOrEmpty(entry.event.type) || 'event';
|
|
412
|
+
return {
|
|
413
|
+
nativeEventId: entry.id,
|
|
414
|
+
sequence: entry.seq,
|
|
415
|
+
kind: 'grokbot.event',
|
|
416
|
+
role: 'system',
|
|
417
|
+
text: `[event] ${label}`,
|
|
418
|
+
occurredAt,
|
|
419
|
+
extensions: {
|
|
420
|
+
nativeKind: 'event',
|
|
421
|
+
event: entry.event,
|
|
422
|
+
provenance: { acquisition: GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS },
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function mapNotice(raw, occurredAt) {
|
|
427
|
+
const parsed = NoticeEntrySchema.safeParse(raw);
|
|
428
|
+
if (!parsed.success) {
|
|
429
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Grok Bot notice entry is malformed');
|
|
430
|
+
}
|
|
431
|
+
const entry = parsed.data;
|
|
432
|
+
return {
|
|
433
|
+
nativeEventId: entry.id,
|
|
434
|
+
sequence: entry.seq,
|
|
435
|
+
kind: 'grokbot.notice',
|
|
436
|
+
role: 'system',
|
|
437
|
+
text: entry.text,
|
|
438
|
+
occurredAt,
|
|
439
|
+
extensions: {
|
|
440
|
+
nativeKind: 'notice',
|
|
441
|
+
provenance: { acquisition: GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS },
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function stringOrEmpty(value) {
|
|
446
|
+
return typeof value === 'string' ? value : '';
|
|
48
447
|
}
|
|
49
448
|
//# sourceMappingURL=grokbot.js.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { open } from 'node:fs/promises';
|
|
2
|
+
import { SessionContractError } from '../contracts.js';
|
|
3
|
+
import { authorizeSourceFile } from '../policy.js';
|
|
4
|
+
// Discovery inspects metadata, not an entire import. Bound allocation before
|
|
5
|
+
// assembling a line (readline would buffer an arbitrarily large single line).
|
|
6
|
+
export const MUSE_WORKSPACE_SCAN_LIMITS = Object.freeze({
|
|
7
|
+
maxRecords: 10_000,
|
|
8
|
+
maxRecordBytes: 8 * 1024 * 1024,
|
|
9
|
+
maxTotalBytes: 32 * 1024 * 1024,
|
|
10
|
+
maxNestingDepth: 64,
|
|
11
|
+
});
|
|
12
|
+
export async function* museWorkspaceScanLines(authorization, limits, progress) {
|
|
13
|
+
const allowed = await authorizeSourceFile(authorization);
|
|
14
|
+
const handle = await open(allowed.canonicalPath, 'r');
|
|
15
|
+
try {
|
|
16
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
17
|
+
let position = 0;
|
|
18
|
+
let parts = [];
|
|
19
|
+
let length = 0;
|
|
20
|
+
while (position < allowed.size) {
|
|
21
|
+
const remaining = limits.maxTotalBytes - progress.bytesInspected;
|
|
22
|
+
if (remaining <= 0)
|
|
23
|
+
throw scanLimit();
|
|
24
|
+
const { bytesRead } = await handle.read(buffer, 0, Math.min(buffer.length, allowed.size - position, remaining), position);
|
|
25
|
+
if (bytesRead === 0) {
|
|
26
|
+
throw new SessionContractError('TRUNCATED_SOURCE', 'Muse source changed during inspection');
|
|
27
|
+
}
|
|
28
|
+
position += bytesRead;
|
|
29
|
+
let start = 0;
|
|
30
|
+
while (start < bytesRead) {
|
|
31
|
+
const newline = buffer.indexOf(10, start);
|
|
32
|
+
const end = newline >= 0 && newline < bytesRead ? newline + 1 : bytesRead;
|
|
33
|
+
const segment = buffer.subarray(start, end);
|
|
34
|
+
if (length + segment.length > limits.maxRecordBytes)
|
|
35
|
+
throw scanLimit();
|
|
36
|
+
parts.push(Buffer.from(segment));
|
|
37
|
+
length += segment.length;
|
|
38
|
+
progress.bytesInspected += segment.length;
|
|
39
|
+
start = end;
|
|
40
|
+
if (newline >= 0 && newline < bytesRead) {
|
|
41
|
+
const line = Buffer.concat(parts, length).toString('utf8');
|
|
42
|
+
parts = [];
|
|
43
|
+
length = 0;
|
|
44
|
+
if (line.trim() !== '') {
|
|
45
|
+
if (progress.recordsInspected >= limits.maxRecords)
|
|
46
|
+
throw scanLimit();
|
|
47
|
+
const value = parseMuseScanJson(line, limits.maxNestingDepth);
|
|
48
|
+
progress.recordsInspected += 1;
|
|
49
|
+
yield value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (length > 0) {
|
|
55
|
+
const line = Buffer.concat(parts, length).toString('utf8');
|
|
56
|
+
if (line.trim() !== '') {
|
|
57
|
+
if (progress.recordsInspected >= limits.maxRecords)
|
|
58
|
+
throw scanLimit();
|
|
59
|
+
let value;
|
|
60
|
+
try {
|
|
61
|
+
value = parseMuseScanJson(line, limits.maxNestingDepth);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error instanceof SessionContractError && error.code === 'MALFORMED_SOURCE') {
|
|
65
|
+
throw new SessionContractError('TRUNCATED_SOURCE', 'Muse source has an incomplete JSONL tail');
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
progress.recordsInspected += 1;
|
|
70
|
+
yield value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const after = await handle.stat();
|
|
74
|
+
if (after.size !== allowed.size || after.mtimeMs !== allowed.mtimeMs) {
|
|
75
|
+
throw new SessionContractError('TRUNCATED_SOURCE', 'Muse source changed during inspection');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
await handle.close();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function parseMuseScanJson(line, maxDepth) {
|
|
83
|
+
let value;
|
|
84
|
+
try {
|
|
85
|
+
value = JSON.parse(line);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
throw new SessionContractError('MALFORMED_SOURCE', 'Muse inspection encountered malformed JSON');
|
|
89
|
+
}
|
|
90
|
+
// Check depth iteratively, including decoded retained children, before zod
|
|
91
|
+
// validation. Deep input must produce a limit diagnostic, not stack overflow.
|
|
92
|
+
const pending = [{ value, depth: 0 }];
|
|
93
|
+
while (pending.length > 0) {
|
|
94
|
+
const current = pending.pop();
|
|
95
|
+
if (current.depth > maxDepth)
|
|
96
|
+
throw scanLimit();
|
|
97
|
+
if (current.value !== null && typeof current.value === 'object') {
|
|
98
|
+
for (const child of Object.values(current.value)) {
|
|
99
|
+
pending.push({ value: child, depth: current.depth + 1 });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
function scanLimit() {
|
|
106
|
+
return new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'bounded Muse workspace inspection limit exceeded');
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=muse-workspace-scan.js.map
|