@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,7 +1,7 @@
|
|
|
1
1
|
import { execFile as execFileCallback } from 'node:child_process';
|
|
2
2
|
import { constants as fsConstants } from 'node:fs';
|
|
3
|
-
import { lstat, open, realpath, writeFile } from 'node:fs/promises';
|
|
4
|
-
import { dirname, isAbsolute, join, parse, resolve } from 'node:path';
|
|
3
|
+
import { access, lstat, open, realpath, stat, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { delimiter, dirname, isAbsolute, join, parse, resolve } from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { resolveGrokHomeResult } from '../providers/grok-build-paths.js';
|
|
7
7
|
import { SessionContractError } from './contracts.js';
|
|
@@ -115,10 +115,10 @@ async function runGrok(options, args) {
|
|
|
115
115
|
if (!home.ok)
|
|
116
116
|
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', home.message);
|
|
117
117
|
await rejectSymlinkComponents(home.path);
|
|
118
|
-
|
|
119
|
-
if (binary !== 'grok' && !isAbsolute(binary)) {
|
|
118
|
+
if (options.binary !== undefined && !isAbsolute(options.binary)) {
|
|
120
119
|
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Grok Build binary override must be absolute');
|
|
121
120
|
}
|
|
121
|
+
const binary = options.binary ?? await defaultGrokBinary(env, home.path);
|
|
122
122
|
const runner = options.runner ?? execFile;
|
|
123
123
|
try {
|
|
124
124
|
const result = await runner(binary, ['--no-auto-update', ...args], {
|
|
@@ -136,7 +136,69 @@ async function runGrok(options, args) {
|
|
|
136
136
|
if (error.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
|
|
137
137
|
throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Grok Build CLI output exceeds 2 MB');
|
|
138
138
|
}
|
|
139
|
-
throw
|
|
139
|
+
throw grokFailure(error, binary);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Map a failed `grok` launch to an actionable error (#2968). stderr is never
|
|
144
|
+
* echoed: Grok Build diagnostics can carry session content.
|
|
145
|
+
*/
|
|
146
|
+
function grokFailure(error, binary) {
|
|
147
|
+
const name = binary === 'grok' ? '`grok`' : `\`${binary}\``;
|
|
148
|
+
if (error.code === 'ENOENT') {
|
|
149
|
+
return new SessionContractError('UNSUPPORTED_OPERATION', `Grok Build CLI not found: ${name} is not on PATH and no $GROK_HOME/bin/grok was found; `
|
|
150
|
+
+ 'pass --grok-bin <absolute path> or add the Grok Build bin directory to PATH.');
|
|
151
|
+
}
|
|
152
|
+
if (error.code === 'EACCES') {
|
|
153
|
+
return new SessionContractError('UNSUPPORTED_OPERATION', `Grok Build CLI ${name} is not executable; check its permissions or pass --grok-bin <absolute path>.`);
|
|
154
|
+
}
|
|
155
|
+
if (error.killed || error.signal) {
|
|
156
|
+
return new SessionContractError('UNSUPPORTED_OPERATION', `Grok Build CLI ${name} timed out after ${TIMEOUT_MS / 1000}s; check that it is authenticated `
|
|
157
|
+
+ 'and does not prompt for input.');
|
|
158
|
+
}
|
|
159
|
+
if (typeof error.code === 'number') {
|
|
160
|
+
return new SessionContractError('UNSUPPORTED_OPERATION', `Grok Build CLI ${name} exited with code ${error.code}; run \`grok sessions list\` in the selected `
|
|
161
|
+
+ 'workspace to check authentication and installation.');
|
|
162
|
+
}
|
|
163
|
+
return new SessionContractError('UNSUPPORTED_OPERATION', 'Grok Build CLI failed; check the local `grok` installation, authentication, and selected workspace.');
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Resolve `grok` from PATH, else the installer's `$GROK_HOME/bin/grok` (#2968).
|
|
167
|
+
* The installer links `bin/grok` to a versioned binary beside it, so that one
|
|
168
|
+
* link is followed only when it resolves to a regular file inside `bin`.
|
|
169
|
+
*/
|
|
170
|
+
async function defaultGrokBinary(env, grokHome) {
|
|
171
|
+
for (const directory of (env.PATH ?? '').split(delimiter)) {
|
|
172
|
+
if (!directory || !isAbsolute(directory))
|
|
173
|
+
continue;
|
|
174
|
+
if (await isExecutableFile(join(directory, 'grok')))
|
|
175
|
+
return 'grok';
|
|
176
|
+
}
|
|
177
|
+
const binDirectory = join(grokHome, 'bin');
|
|
178
|
+
try {
|
|
179
|
+
await rejectSymlinkComponents(binDirectory);
|
|
180
|
+
const canonicalBin = await realpath(binDirectory);
|
|
181
|
+
const target = await realpath(join(binDirectory, 'grok'));
|
|
182
|
+
if (dirname(target) !== canonicalBin || !(await isExecutableFile(target)))
|
|
183
|
+
return 'grok';
|
|
184
|
+
return target;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
if (error instanceof SessionContractError)
|
|
188
|
+
throw error;
|
|
189
|
+
return 'grok';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function isExecutableFile(path) {
|
|
193
|
+
try {
|
|
194
|
+
const item = await stat(path);
|
|
195
|
+
if (!item.isFile())
|
|
196
|
+
return false;
|
|
197
|
+
await access(path, fsConstants.X_OK);
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return false;
|
|
140
202
|
}
|
|
141
203
|
}
|
|
142
204
|
function parseSessionIds(stdout, mode) {
|
|
@@ -65,6 +65,7 @@ export class IncrementalSessionImporter {
|
|
|
65
65
|
policyVersion: request.policyVersion,
|
|
66
66
|
continuity: continuity
|
|
67
67
|
? (!previous || previous.positionKind === 'byte-offset'
|
|
68
|
+
|| previous.positionKind === 'provider-native'
|
|
68
69
|
? continuity.outcome
|
|
69
70
|
: 'unverified')
|
|
70
71
|
: (previous ? 'unverified' : 'new-generation'),
|
|
@@ -152,6 +153,14 @@ export class IncrementalSessionImporter {
|
|
|
152
153
|
checkpoint.cursor = record.sourceCursor;
|
|
153
154
|
checkpoint.bytesRead = Number(record.sourceCursor.slice(5));
|
|
154
155
|
}
|
|
156
|
+
else if (record.sourceCursor?.startsWith('native:')) {
|
|
157
|
+
// An opaque provider-native resume token (e.g. a monotonic native
|
|
158
|
+
// sequence) — never a byte offset. Stored verbatim; `bytesRead`
|
|
159
|
+
// still accumulates so resource limits remain meaningful.
|
|
160
|
+
checkpoint.positionKind = 'provider-native';
|
|
161
|
+
checkpoint.cursor = record.sourceCursor;
|
|
162
|
+
checkpoint.bytesRead += bytes;
|
|
163
|
+
}
|
|
155
164
|
else {
|
|
156
165
|
checkpoint.bytesRead += bytes;
|
|
157
166
|
checkpoint.cursor = String(checkpoint.recordsRead);
|
|
@@ -435,9 +444,15 @@ async function sourceContinuity(request, previous) {
|
|
|
435
444
|
selectedPath: request.selectedSource.locator,
|
|
436
445
|
allowedRoots: request.selectedSource.authorizedScope.allowedRoots,
|
|
437
446
|
};
|
|
447
|
+
// Snapshot sources are rewritten wholesale on every save (no stable
|
|
448
|
+
// append-only byte region, and the file identity may legitimately change
|
|
449
|
+
// via rename-swap): skip the shrink, file-identity, and prefix-rewrite
|
|
450
|
+
// throws below. Idempotency instead rests on the adapter's own monotonic
|
|
451
|
+
// native cursor plus the catalog's identity-keyed inserts.
|
|
452
|
+
const isSnapshot = request.adapter.sourceContinuity === 'snapshot';
|
|
438
453
|
const skipPrefixBytes = await request.adapter.mutablePrefixBytes?.(request.selectedSource) ?? 0;
|
|
439
454
|
const metadata = await fingerprintSourcePrefix(authorization, 0);
|
|
440
|
-
if (previous?.sourceSize !== undefined) {
|
|
455
|
+
if (!isSnapshot && previous?.sourceSize !== undefined) {
|
|
441
456
|
if (metadata.size < previous.sourceSize) {
|
|
442
457
|
throw new SessionContractError('SCHEMA_DRIFT', 'session source was truncated before its durable checkpoint');
|
|
443
458
|
}
|
|
@@ -451,17 +466,18 @@ async function sourceContinuity(request, previous) {
|
|
|
451
466
|
}
|
|
452
467
|
}
|
|
453
468
|
const current = await fingerprintSourcePrefix(authorization, metadata.size, skipPrefixBytes);
|
|
454
|
-
const generation = previous?.sourceGeneration ??
|
|
455
|
-
sourceGenerationDigest(request)
|
|
456
|
-
metadata.fileIdentity
|
|
457
|
-
].join('\0'));
|
|
469
|
+
const generation = previous?.sourceGeneration ?? (isSnapshot
|
|
470
|
+
? sourceGenerationDigest(request)
|
|
471
|
+
: sha256([sourceGenerationDigest(request), metadata.fileIdentity].join('\0')));
|
|
458
472
|
return {
|
|
459
473
|
sourceGeneration: generation,
|
|
460
474
|
outcome: !previous
|
|
461
475
|
? 'new-generation'
|
|
462
|
-
:
|
|
463
|
-
|
|
464
|
-
|
|
476
|
+
: isSnapshot
|
|
477
|
+
? (current.digest === previous.prefixDigest ? 'unchanged-replay' : 'validated-append')
|
|
478
|
+
: metadata.size === previous.sourceSize
|
|
479
|
+
&& current.digest === previous.prefixDigest
|
|
480
|
+
? 'unchanged-replay' : 'validated-append',
|
|
465
481
|
size: metadata.size,
|
|
466
482
|
mtimeMs: metadata.mtimeMs,
|
|
467
483
|
fileIdentity: metadata.fileIdentity,
|
|
@@ -42,4 +42,6 @@ export * from './adapters/warp.js';
|
|
|
42
42
|
export * from './adapters/windsurf.js';
|
|
43
43
|
export * from './adapters/muse.js';
|
|
44
44
|
export * from './adapters/omp.js';
|
|
45
|
+
export * from './adapter-registry.js';
|
|
46
|
+
export * from './coverage-preflight.js';
|
|
45
47
|
//# sourceMappingURL=index.js.map
|
|
@@ -5,6 +5,11 @@ import { redactSessionText, sanitizeNativeExtensions } from './policy.js';
|
|
|
5
5
|
import { deriveSessionAnalytics, } from './analytics.js';
|
|
6
6
|
const POLICY_PROVIDER_MIGRATION = 'policy-provider-identity:v2';
|
|
7
7
|
const EVENT_ORIGIN_INTENT_MIGRATION = 'event-origin-intent:v1';
|
|
8
|
+
const VISIBLE_SESSION_PREDICATE = `s.lifecycle != 'tombstoned' AND EXISTS (
|
|
9
|
+
SELECT 1 FROM session_events e
|
|
10
|
+
JOIN import_runs r ON r.import_run_id=e.import_run_id
|
|
11
|
+
WHERE e.session_id=s.session_id AND r.status='committed'
|
|
12
|
+
)`;
|
|
8
13
|
export class SessionRepository {
|
|
9
14
|
db;
|
|
10
15
|
constructor(path = ':memory:') {
|
|
@@ -620,16 +625,56 @@ export class SessionRepository {
|
|
|
620
625
|
}
|
|
621
626
|
getSession(id, workspaceId) {
|
|
622
627
|
const row = this.db.prepare(`SELECT s.data FROM sessions s
|
|
623
|
-
WHERE s.session_id=? AND
|
|
624
|
-
${workspaceId ? 'AND s.workspace_id=?' : ''}
|
|
625
|
-
|
|
626
|
-
SELECT 1 FROM session_events e
|
|
627
|
-
JOIN import_runs r ON r.import_run_id=e.import_run_id
|
|
628
|
-
WHERE e.session_id=s.session_id AND r.status='committed'
|
|
629
|
-
)`)
|
|
630
|
-
.get(...(workspaceId ? [id, 'tombstoned', workspaceId] : [id, 'tombstoned']));
|
|
628
|
+
WHERE s.session_id=? AND ${VISIBLE_SESSION_PREDICATE}
|
|
629
|
+
${workspaceId ? 'AND s.workspace_id=?' : ''}`)
|
|
630
|
+
.get(...(workspaceId ? [id, workspaceId] : [id]));
|
|
631
631
|
return row ? JSON.parse(String(row.data)) : null;
|
|
632
632
|
}
|
|
633
|
+
findSessionsByReference(reference, workspaceId) {
|
|
634
|
+
const internal = this.getSession(reference, workspaceId);
|
|
635
|
+
if (internal)
|
|
636
|
+
return [internal];
|
|
637
|
+
const query = (predicate) => this.db.prepare(`SELECT s.data FROM sessions s
|
|
638
|
+
WHERE ${workspaceId ? 's.workspace_id=? AND' : ''} ${VISIBLE_SESSION_PREDICATE} AND (${predicate})
|
|
639
|
+
ORDER BY s.session_id`).all(...(workspaceId ? [workspaceId, reference] : [reference]))
|
|
640
|
+
.map((row) => JSON.parse(String(row.data)));
|
|
641
|
+
const exact = query('s.native_session_id=?');
|
|
642
|
+
if (exact.length === 1)
|
|
643
|
+
return exact;
|
|
644
|
+
if (exact.length > 1) {
|
|
645
|
+
// Artifact IDs are redacted by native-extension sanitization. The
|
|
646
|
+
// adapter's boolean subagent flag survives. Only prefer a parent when
|
|
647
|
+
// every other match is a proven Claude subagent in the same workspace.
|
|
648
|
+
if (exact.every((session) => session.provider === 'claude'
|
|
649
|
+
&& session.workspaceId === exact[0].workspaceId)) {
|
|
650
|
+
const classified = exact.map((session) => {
|
|
651
|
+
const flags = this.db.prepare(`SELECT
|
|
652
|
+
MAX(json_extract(e.data, '$.extensions."native.claude".transcriptFamily.subagent')=0) AS parent,
|
|
653
|
+
MAX(json_extract(e.data, '$.extensions."native.claude".transcriptFamily.subagent')=1) AS subagent
|
|
654
|
+
FROM session_events e JOIN import_runs r ON r.import_run_id=e.import_run_id
|
|
655
|
+
WHERE e.session_id=? AND r.status='committed'`).get(session.sessionId);
|
|
656
|
+
return { session, parent: flags.parent === 1, subagent: flags.subagent === 1 };
|
|
657
|
+
});
|
|
658
|
+
const parents = classified.filter((item) => item.parent && !item.subagent);
|
|
659
|
+
if (parents.length === 1 && classified.every((item) => item === parents[0]
|
|
660
|
+
|| (item.subagent && !item.parent)))
|
|
661
|
+
return [parents[0].session];
|
|
662
|
+
}
|
|
663
|
+
return exact;
|
|
664
|
+
}
|
|
665
|
+
// substr treats %, _ and other characters literally rather than as LIKE patterns.
|
|
666
|
+
return this.db.prepare(`SELECT s.data FROM sessions s
|
|
667
|
+
WHERE ${workspaceId ? 's.workspace_id=? AND' : ''} ${VISIBLE_SESSION_PREDICATE}
|
|
668
|
+
AND (substr(s.native_session_id, 1, length(?))=?
|
|
669
|
+
OR substr(s.session_id, 1, length(?))=?)
|
|
670
|
+
ORDER BY s.session_id`).all(...(workspaceId ? [workspaceId, reference, reference, reference, reference]
|
|
671
|
+
: [reference, reference, reference, reference]))
|
|
672
|
+
.map((row) => JSON.parse(String(row.data)));
|
|
673
|
+
}
|
|
674
|
+
listWorkspaceSessionCounts() {
|
|
675
|
+
return this.db.prepare(`SELECT s.workspace_id, COUNT(*) AS count FROM sessions s
|
|
676
|
+
WHERE ${VISIBLE_SESSION_PREDICATE} GROUP BY s.workspace_id ORDER BY s.workspace_id`).all().map((row) => ({ workspaceId: String(row.workspace_id), count: Number(row.count) }));
|
|
677
|
+
}
|
|
633
678
|
listSources() {
|
|
634
679
|
return this.db.prepare('SELECT data FROM session_sources ORDER BY provider, source_id').all().map((row) => JSON.parse(String(row.data)));
|
|
635
680
|
}
|
|
@@ -670,11 +715,7 @@ export class SessionRepository {
|
|
|
670
715
|
: coverage;
|
|
671
716
|
}
|
|
672
717
|
listSessions(options) {
|
|
673
|
-
const where = [
|
|
674
|
-
SELECT 1 FROM session_events e
|
|
675
|
-
JOIN import_runs r ON r.import_run_id=e.import_run_id
|
|
676
|
-
WHERE e.session_id=s.session_id AND r.status='committed'
|
|
677
|
-
)`];
|
|
718
|
+
const where = [VISIBLE_SESSION_PREDICATE];
|
|
678
719
|
const scopeDigest = sessionListScopeDigest(options);
|
|
679
720
|
const cursor = decodeSessionListCursor(options.cursor);
|
|
680
721
|
if (cursor && cursor.scopeDigest !== scopeDigest) {
|
|
@@ -3,46 +3,53 @@ import { access, mkdir, readFile, realpath, rename, stat, writeFile, } from 'nod
|
|
|
3
3
|
import { homedir, userInfo } from 'node:os';
|
|
4
4
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { createInterface } from 'node:readline';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { FactorySessionAdapter } from './adapters/factory.js';
|
|
10
|
-
import { MuseSessionAdapter, museNativeSourceMatchesWorkspace } from './adapters/muse.js';
|
|
11
|
-
import { OmpSessionAdapter, readOmpSessionHeader } from './adapters/omp.js';
|
|
6
|
+
import { inspectMuseNativeWorkspace } from './adapters/muse.js';
|
|
7
|
+
import { readOmpSessionHeader } from './adapters/omp.js';
|
|
8
|
+
import { grokbotConversationIdFromLocator } from './adapters/grokbot-locator.js';
|
|
12
9
|
import { resolveOmpPaths } from '../providers/omp-paths.mjs';
|
|
13
|
-
import {
|
|
14
|
-
import { DeepSeekHarnessSessionAdapter } from './adapters/deepseek-harness.js';
|
|
15
|
-
import { SESSION_PROVIDER_IDS, sha256, } from './contracts.js';
|
|
10
|
+
import { SESSION_PROVIDER_IDS, SessionCoveragePreflightSchema, SessionContractError, sha256, } from './contracts.js';
|
|
16
11
|
import { redactSourceLocator } from './discovery.js';
|
|
12
|
+
import { createSessionAdapterRegistry } from './adapter-registry.js';
|
|
13
|
+
import { coveragePreflight, expectedSessionProviders } from './coverage-preflight.js';
|
|
17
14
|
import { fingerprintSourceFile } from './readers.js';
|
|
18
15
|
export const DISCOVERY_MANIFEST_VERSION = '1.0.0';
|
|
19
16
|
const MANUAL_EXPORT_PROVIDERS = new Set([
|
|
20
|
-
'copilot', 'hermes', 'opencode', 'openclaw', 'openhuman', '
|
|
17
|
+
'copilot', 'hermes', 'opencode', 'openclaw', 'openhuman', 'grok-build',
|
|
21
18
|
'warp', 'devin-desktop', 'generic',
|
|
22
19
|
]);
|
|
23
20
|
export async function discoverWorkspaceHistories(options) {
|
|
21
|
+
const registry = options.registry ?? createSessionAdapterRegistry();
|
|
22
|
+
const expectedProviders = expectedSessionProviders(options.expectedProviders ?? SESSION_PROVIDER_IDS);
|
|
23
|
+
const supportedProviders = new Set(registry.list().map((adapter) => adapter.provider));
|
|
24
24
|
const workspacePath = await canonicalPath(options.workspace);
|
|
25
25
|
const workspaceId = workspacePath;
|
|
26
26
|
const providerHomes = providerHomeCandidates(options.providerHome, options.operatorHome);
|
|
27
27
|
const keyWithLeadingDash = workspaceKey(workspacePath, true);
|
|
28
28
|
const keyWithoutLeadingDash = workspaceKey(workspacePath, false);
|
|
29
29
|
const discoverable = [
|
|
30
|
-
{ provider: 'deepseek-harness',
|
|
30
|
+
{ provider: 'deepseek-harness', roots: options.dshRoot
|
|
31
31
|
? [resolve(options.dshRoot)] : options.providerHome
|
|
32
32
|
? [join(resolve(options.providerHome), '.dsh', 'sessions')] : [] },
|
|
33
|
-
{ provider: 'omp',
|
|
33
|
+
{ provider: 'omp', roots: options.ompRoot
|
|
34
34
|
? [resolve(options.ompRoot)] : options.providerHome
|
|
35
35
|
? [resolveOmpPaths({ home: resolve(options.providerHome), cwd: workspacePath }).sessionsDir] : [] },
|
|
36
|
-
{ provider: 'muse',
|
|
36
|
+
{ provider: 'muse', roots: options.museRoot
|
|
37
37
|
? [resolve(options.museRoot)] : [] },
|
|
38
|
+
// Grok Bot conversations carry no workspace/cwd field, so unlike
|
|
39
|
+
// claude/cursor/factory there is no per-workspace default path to derive
|
|
40
|
+
// from providerHome. Native discovery requires an explicit
|
|
41
|
+
// --grokbot-root (same shared-root authorization model as
|
|
42
|
+
// codex/omp/deepseek-harness/muse below), and every authorized
|
|
43
|
+
// conversation surfaces to whichever workspace runs discovery (same
|
|
44
|
+
// no-filtering behavior already used for `pi`/`deepseek-harness`).
|
|
45
|
+
{ provider: 'grokbot', roots: options.grokbotRoot
|
|
46
|
+
? [resolve(options.grokbotRoot)] : [] },
|
|
38
47
|
{
|
|
39
48
|
provider: 'claude',
|
|
40
|
-
adapter: new ClaudeSessionAdapter(),
|
|
41
49
|
roots: providerHomes.map((providerHome) => join(providerHome, '.claude', 'projects', keyWithLeadingDash)),
|
|
42
50
|
},
|
|
43
51
|
{
|
|
44
52
|
provider: 'codex',
|
|
45
|
-
adapter: new CodexSessionAdapter(),
|
|
46
53
|
roots: options.codexRoot
|
|
47
54
|
? [resolve(options.codexRoot)]
|
|
48
55
|
: options.providerHome
|
|
@@ -51,31 +58,39 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
51
58
|
},
|
|
52
59
|
{
|
|
53
60
|
provider: 'cursor',
|
|
54
|
-
adapter: new CursorSessionAdapter(),
|
|
55
61
|
roots: providerHomes.map((providerHome) => join(providerHome, '.cursor', 'projects', keyWithoutLeadingDash, 'agent-transcripts')),
|
|
56
62
|
},
|
|
57
63
|
{
|
|
58
64
|
provider: 'factory',
|
|
59
|
-
adapter: new FactorySessionAdapter(),
|
|
60
65
|
roots: providerHomes.flatMap((providerHome) => [
|
|
61
66
|
join(providerHome, '.factory', 'projects', keyWithLeadingDash),
|
|
62
67
|
join(providerHome, '.factory', 'sessions', keyWithLeadingDash),
|
|
63
68
|
]),
|
|
64
69
|
},
|
|
65
70
|
{
|
|
71
|
+
// Pi stores every working directory's sessions in its own
|
|
72
|
+
// subdirectory of the sessions root, named `--<path>--` where
|
|
73
|
+
// `<path>` has its leading separator removed and `/`, `\`, and `:`
|
|
74
|
+
// replaced with `-` (https://pi.dev/docs/latest/session-format,
|
|
75
|
+
// "File Location", verified 2026-10-12) — the same transform
|
|
76
|
+
// `workspaceKey(path, false)` already applies for Cursor below.
|
|
77
|
+
// Scanning the whole sessions root (as before) would import every
|
|
78
|
+
// other workspace's history into this one's catalog (#2656).
|
|
66
79
|
provider: 'pi',
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
? resolve(process.env.PI_CODING_AGENT_SESSION_DIR)
|
|
71
|
-
: join(resolve(options.providerHome), '.pi', 'agent', 'sessions')]
|
|
72
|
-
: [],
|
|
80
|
+
roots: (process.env.PI_CODING_AGENT_SESSION_DIR
|
|
81
|
+
? [resolve(process.env.PI_CODING_AGENT_SESSION_DIR)]
|
|
82
|
+
: providerHomes.map((providerHome) => join(providerHome, '.pi', 'agent', 'sessions'))).map((sessionsRoot) => join(sessionsRoot, `--${keyWithoutLeadingDash}--`)),
|
|
73
83
|
},
|
|
74
84
|
];
|
|
75
85
|
const reports = new Map();
|
|
76
86
|
const candidates = [];
|
|
77
87
|
const seen = new Set();
|
|
78
88
|
for (const entry of discoverable) {
|
|
89
|
+
if (!expectedProviders.includes(entry.provider) || !supportedProviders.has(entry.provider))
|
|
90
|
+
continue;
|
|
91
|
+
const adapter = registry.get(entry.provider);
|
|
92
|
+
if (adapter.disposition === 'unsupported' || !adapter.supportedOperations.includes('discover'))
|
|
93
|
+
continue;
|
|
79
94
|
const availableRoots = [];
|
|
80
95
|
for (const root of entry.roots) {
|
|
81
96
|
if (await pathExists(root))
|
|
@@ -83,12 +98,13 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
83
98
|
}
|
|
84
99
|
if (availableRoots.length === 0) {
|
|
85
100
|
const codexNeedsAuthorization = (entry.provider === 'codex' || entry.provider === 'omp'
|
|
86
|
-
|| entry.provider === 'deepseek-harness' || entry.provider === 'muse'
|
|
101
|
+
|| entry.provider === 'deepseek-harness' || entry.provider === 'muse'
|
|
102
|
+
|| entry.provider === 'grokbot')
|
|
87
103
|
&& entry.roots.length === 0;
|
|
88
104
|
reports.set(entry.provider, providerReport(entry.provider, codexNeedsAuthorization ? 'export-required' : 'unavailable', codexNeedsAuthorization ? 'manual-export' : 'discoverable', [], codexNeedsAuthorization
|
|
89
105
|
? 'SHARED_ROOT_AUTHORIZATION_REQUIRED'
|
|
90
106
|
: 'PROVIDER_ROOT_UNAVAILABLE', codexNeedsAuthorization
|
|
91
|
-
? `Pass --${entry.provider}-root with an explicitly authorized sessions root.`
|
|
107
|
+
? `Pass --${entry.provider === 'deepseek-harness' ? 'dsh' : entry.provider}-root with an explicitly authorized sessions root.`
|
|
92
108
|
: `No authorized ${entry.provider} workspace history root was found.`));
|
|
93
109
|
continue;
|
|
94
110
|
}
|
|
@@ -97,17 +113,25 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
97
113
|
allowedRoots: availableRoots,
|
|
98
114
|
};
|
|
99
115
|
const providerSources = [];
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (entry.provider === '
|
|
103
|
-
|
|
104
|
-
continue;
|
|
105
|
-
if (entry.provider === 'muse'
|
|
106
|
-
&& !await museNativeSourceMatchesWorkspace({
|
|
116
|
+
const museSourceInspections = [];
|
|
117
|
+
for await (const descriptor of adapter.discover(scope)) {
|
|
118
|
+
if (entry.provider === 'muse') {
|
|
119
|
+
const inspection = await inspectMuseNativeWorkspace({
|
|
107
120
|
...descriptor,
|
|
108
121
|
sourceId: 'discovery',
|
|
109
122
|
authorizedScope: scope,
|
|
110
|
-
}, workspacePath)
|
|
123
|
+
}, workspacePath);
|
|
124
|
+
museSourceInspections.push({
|
|
125
|
+
sourceKey: sha256(['muse-workspace-inspection-v1', descriptor.locator].join('\0')),
|
|
126
|
+
redactedLocator: redactSourceLocator(descriptor.locator),
|
|
127
|
+
...inspection,
|
|
128
|
+
});
|
|
129
|
+
if (inspection.outcome !== 'matched')
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const locator = await canonicalPath(descriptor.locator);
|
|
133
|
+
if (entry.provider === 'codex'
|
|
134
|
+
&& !await codexSourceMatchesWorkspace(locator, workspacePath))
|
|
111
135
|
continue;
|
|
112
136
|
const ompHeader = entry.provider === 'omp' ? await readOmpSessionHeader({ ...descriptor, sourceId: 'discovery', authorizedScope: scope }) : undefined;
|
|
113
137
|
if (ompHeader && resolve(ompHeader.cwd) !== workspacePath)
|
|
@@ -127,17 +151,40 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
127
151
|
const source = sourceFromDescriptor(descriptor, locator, authorizedRoot, details, fingerprint.digest);
|
|
128
152
|
if (ompHeader)
|
|
129
153
|
source.sourceId = sha256(['omp-native-source-v1', workspaceId, ompHeader.id].join('\0'));
|
|
154
|
+
if (entry.provider === 'grokbot') {
|
|
155
|
+
// The replica is rewritten wholesale as its sliding window evicts
|
|
156
|
+
// old entries, so a content-digest sourceId (the default above)
|
|
157
|
+
// would churn every re-discovery and break checkpoint continuity.
|
|
158
|
+
// Key it on the conversation id instead, same rationale as the OMP
|
|
159
|
+
// override above.
|
|
160
|
+
const conversationId = grokbotConversationIdFromLocator(locator);
|
|
161
|
+
if (conversationId) {
|
|
162
|
+
source.sourceId = sha256(['grokbot-native-source-v1', workspaceId, conversationId].join('\0'));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
130
165
|
providerSources.push(source);
|
|
131
166
|
candidates.push(source);
|
|
132
167
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
: null
|
|
168
|
+
const incompleteMuseInspection = museSourceInspections.some((inspection) => inspection.outcome === 'inconclusive');
|
|
169
|
+
const report = providerReport(entry.provider, incompleteMuseInspection ? 'not-checked' : 'checked', 'discoverable', providerSources, incompleteMuseInspection ? 'MUSE_SOURCE_INSPECTION_INCOMPLETE'
|
|
170
|
+
: providerSources.length === 0 ? 'NO_WORKSPACE_SOURCES' : null, incompleteMuseInspection
|
|
171
|
+
? 'Review museSourceInspections; authorize a supported Muse export for unresolved sources before claiming workspace coverage.'
|
|
172
|
+
: providerSources.length === 0
|
|
173
|
+
? `The ${entry.provider} root was checked, but no source matched the authorized workspace.`
|
|
174
|
+
: null);
|
|
175
|
+
if (entry.provider === 'muse') {
|
|
176
|
+
report.museSourceInspections = museSourceInspections.sort((a, b) => a.sourceKey.localeCompare(b.sourceKey));
|
|
177
|
+
}
|
|
178
|
+
reports.set(entry.provider, report);
|
|
136
179
|
}
|
|
137
|
-
for (const provider of
|
|
180
|
+
for (const provider of expectedProviders) {
|
|
138
181
|
if (reports.has(provider))
|
|
139
182
|
continue;
|
|
140
183
|
const manual = MANUAL_EXPORT_PROVIDERS.has(provider);
|
|
184
|
+
if (!supportedProviders.has(provider)) {
|
|
185
|
+
reports.set(provider, providerReport(provider, 'not-checked', 'unsupported', [], 'MISSING_ADAPTER', 'Deliberately select a compatible runtime or an authorized supported export.'));
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
141
188
|
const grokBuild = provider === 'grok-build';
|
|
142
189
|
reports.set(provider, providerReport(provider, manual ? 'export-required' : 'not-checked', manual ? 'manual-export' : 'unsupported', [], grokBuild ? 'CLI_EXPORT_REQUIRED'
|
|
143
190
|
: manual ? 'EXPLICIT_EXPORT_REQUIRED' : 'PROVIDER_NOT_CHECKED', grokBuild
|
|
@@ -148,9 +195,14 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
148
195
|
}
|
|
149
196
|
const sources = candidates.sort(compareSources);
|
|
150
197
|
const providers = [...reports.values()].sort((a, b) => a.provider.localeCompare(b.provider));
|
|
198
|
+
const preflight = coveragePreflight({
|
|
199
|
+
expectedProviders, expectedProviderBasis: options.expectedProviders ? 'explicit' : 'declared-default',
|
|
200
|
+
registry, runtime: options.runtime, reports: providers,
|
|
201
|
+
});
|
|
151
202
|
const identity = {
|
|
152
203
|
schemaVersion: DISCOVERY_MANIFEST_VERSION,
|
|
153
204
|
workspaceId,
|
|
205
|
+
preflight,
|
|
154
206
|
sources: sources.map((source) => ({
|
|
155
207
|
sourceId: source.sourceId,
|
|
156
208
|
provider: source.provider,
|
|
@@ -164,6 +216,7 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
164
216
|
provider: provider.provider,
|
|
165
217
|
status: provider.status,
|
|
166
218
|
reasonCode: provider.reasonCode,
|
|
219
|
+
...(provider.museSourceInspections ? { museSourceInspections: provider.museSourceInspections } : {}),
|
|
167
220
|
})),
|
|
168
221
|
};
|
|
169
222
|
return {
|
|
@@ -172,6 +225,7 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
172
225
|
createdAt: options.createdAt ?? new Date().toISOString(),
|
|
173
226
|
workspaceId,
|
|
174
227
|
workspacePath,
|
|
228
|
+
preflight,
|
|
175
229
|
providers,
|
|
176
230
|
sources,
|
|
177
231
|
};
|
|
@@ -194,6 +248,11 @@ export async function readDiscoveryManifest(path) {
|
|
|
194
248
|
|| !Array.isArray(value.sources)) {
|
|
195
249
|
throw new Error('session discovery manifest is malformed or unsupported');
|
|
196
250
|
}
|
|
251
|
+
if (value.preflight) {
|
|
252
|
+
const parsed = SessionCoveragePreflightSchema.safeParse(value.preflight);
|
|
253
|
+
if (!parsed.success)
|
|
254
|
+
throw new SessionContractError('INVALID_ARGUMENT', 'invalid discovery coverage preflight');
|
|
255
|
+
}
|
|
197
256
|
const expected = await rediscoverManifestIdentity(value);
|
|
198
257
|
if (expected !== value.manifestId) {
|
|
199
258
|
throw new Error('session discovery manifest identity does not match its contents');
|
|
@@ -207,6 +266,7 @@ export function publicDiscoveryManifest(manifest) {
|
|
|
207
266
|
createdAt: manifest.createdAt,
|
|
208
267
|
workspaceId: manifest.workspaceId,
|
|
209
268
|
workspacePath: manifest.workspacePath,
|
|
269
|
+
preflight: manifest.preflight,
|
|
210
270
|
providers: manifest.providers,
|
|
211
271
|
sources: manifest.sources.map(({ locator: _locator, authorizedRoot: _root, ...source }) => source),
|
|
212
272
|
totals: {
|
|
@@ -250,6 +310,7 @@ async function rediscoverManifestIdentity(manifest) {
|
|
|
250
310
|
const identity = {
|
|
251
311
|
schemaVersion: manifest.schemaVersion,
|
|
252
312
|
workspaceId: manifest.workspaceId,
|
|
313
|
+
...(manifest.preflight ? { preflight: manifest.preflight } : {}),
|
|
253
314
|
sources: manifest.sources.map((source) => ({
|
|
254
315
|
sourceId: source.sourceId,
|
|
255
316
|
provider: source.provider,
|
|
@@ -263,6 +324,7 @@ async function rediscoverManifestIdentity(manifest) {
|
|
|
263
324
|
provider: provider.provider,
|
|
264
325
|
status: provider.status,
|
|
265
326
|
reasonCode: provider.reasonCode,
|
|
327
|
+
...(provider.museSourceInspections ? { museSourceInspections: provider.museSourceInspections } : {}),
|
|
266
328
|
})),
|
|
267
329
|
};
|
|
268
330
|
return sha256(JSON.stringify(identity));
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* @see https://clawhub.ai
|
|
12
12
|
* @see https://docs.openclaw.ai/tools/clawhub
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
14
|
+
import { boundedExecFileSync } from '../../process/bounded-child-process.mjs';
|
|
15
|
+
import { cliCommand } from '../../process/cli-command.mjs';
|
|
15
16
|
import fs from 'fs';
|
|
16
17
|
import path from 'path';
|
|
17
18
|
import { getProviderDefinition, resolveProviderPathValue, } from '../../providers/provider-definitions.js';
|
|
@@ -47,7 +48,8 @@ async function clawHubFetch(endpoint) {
|
|
|
47
48
|
*/
|
|
48
49
|
function runClawHub(args) {
|
|
49
50
|
try {
|
|
50
|
-
|
|
51
|
+
const [file, argv] = cliCommand('clawhub', args);
|
|
52
|
+
return boundedExecFileSync(file, argv, 'quick', {
|
|
51
53
|
encoding: 'utf-8',
|
|
52
54
|
timeout: 30000,
|
|
53
55
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -65,7 +67,7 @@ export class ClawHubAdapter {
|
|
|
65
67
|
if (this.available !== null)
|
|
66
68
|
return this.available;
|
|
67
69
|
// Check CLI
|
|
68
|
-
const cliAvailable = runClawHub('--version
|
|
70
|
+
const cliAvailable = runClawHub(['--version']) !== null;
|
|
69
71
|
if (cliAvailable) {
|
|
70
72
|
this.available = true;
|
|
71
73
|
return true;
|
|
@@ -89,7 +91,7 @@ export class ClawHubAdapter {
|
|
|
89
91
|
}));
|
|
90
92
|
}
|
|
91
93
|
// Try CLI
|
|
92
|
-
const cliOutput = runClawHub(
|
|
94
|
+
const cliOutput = runClawHub(['search', query, '--json']);
|
|
93
95
|
if (cliOutput) {
|
|
94
96
|
try {
|
|
95
97
|
const parsed = JSON.parse(cliOutput);
|
|
@@ -127,7 +129,7 @@ export class ClawHubAdapter {
|
|
|
127
129
|
};
|
|
128
130
|
}
|
|
129
131
|
// Try CLI
|
|
130
|
-
const cliOutput = runClawHub(
|
|
132
|
+
const cliOutput = runClawHub(['info', name, '--json']);
|
|
131
133
|
if (cliOutput) {
|
|
132
134
|
try {
|
|
133
135
|
const parsed = JSON.parse(cliOutput);
|
|
@@ -162,7 +164,7 @@ export class ClawHubAdapter {
|
|
|
162
164
|
}));
|
|
163
165
|
}
|
|
164
166
|
// Try CLI
|
|
165
|
-
const cliOutput = runClawHub('list --json
|
|
167
|
+
const cliOutput = runClawHub(['list', '--json']);
|
|
166
168
|
if (cliOutput) {
|
|
167
169
|
try {
|
|
168
170
|
const parsed = JSON.parse(cliOutput);
|
|
@@ -195,7 +197,7 @@ export class ClawHubAdapter {
|
|
|
195
197
|
targetDir = resolveSkillFallbackPath(target, options.projectDir, name);
|
|
196
198
|
}
|
|
197
199
|
// Try CLI install (downloads to a temp location, then we copy)
|
|
198
|
-
const cliResult = runClawHub(
|
|
200
|
+
const cliResult = runClawHub(['install', name, '--output', targetDir]);
|
|
199
201
|
if (cliResult !== null)
|
|
200
202
|
return;
|
|
201
203
|
// Try API download
|
|
@@ -240,7 +242,7 @@ export class ClawHubAdapter {
|
|
|
240
242
|
}
|
|
241
243
|
}
|
|
242
244
|
// Try CLI publish
|
|
243
|
-
const cliResult = runClawHub(
|
|
245
|
+
const cliResult = runClawHub(['publish', packageDir]);
|
|
244
246
|
if (cliResult !== null)
|
|
245
247
|
return;
|
|
246
248
|
throw new Error('ClawHub publish requires the clawhub CLI. ' +
|