@aiwg/cli 2026.9.15 → 2026.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agentic/code/providers/capability-matrix.yaml +42 -0
- package/agentic/code/providers/model-capabilities.v1.json +119 -34
- package/agentic/code/providers/model-catalog.v1.json +259 -56
- package/dist/src/cli/handlers/sessions.js +159 -45
- package/dist/src/cli/handlers/use.js +20 -1
- package/dist/src/cli/scope-resolver.js +13 -0
- package/dist/src/cli/services/deployment-verification.js +33 -2
- package/dist/src/config/aiwg-config.js +39 -0
- package/dist/src/config/user-registry.js +9 -0
- package/dist/src/extensions/manifest.js +1 -0
- package/dist/src/models/model-capabilities.v1.json +119 -34
- package/dist/src/models/model-catalog.v1.json +259 -56
- package/dist/src/models/model-discovery.js +7 -0
- package/dist/src/models/provider-policy.js +1 -1
- package/dist/src/plugin/skill-command-translator.js +1 -0
- package/dist/src/providers/capability-matrix.yaml +42 -0
- package/dist/src/providers/grokbot-paths.js +103 -0
- package/dist/src/providers/provider-definitions.js +85 -1
- package/dist/src/providers/provider-inventory.js +1 -0
- package/dist/src/sessions/adapters/grokbot.js +49 -0
- package/dist/src/sessions/adapters/hermes.js +17 -9
- package/dist/src/sessions/adapters/opencode.js +12 -1
- package/dist/src/sessions/contracts.js +1 -1
- package/dist/src/sessions/export-plan.js +130 -4
- package/dist/src/sessions/fortemi-export-mapping.js +10 -6
- package/dist/src/sessions/fortemi-shard-blobs.js +78 -0
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/workspace-discovery.js +1 -1
- package/dist/src/skills/deployer.js +17 -0
- package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
- package/package.json +1 -1
- package/tools/agents/deploy-agents.mjs +3 -2
- package/tools/agents/providers/grokbot.mjs +285 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, writeFileSync, } from 'node:fs';
|
|
1
|
+
import { existsSync, linkSync, mkdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { dirname, isAbsolute, join, resolve, } from 'node:path';
|
|
3
|
+
import { basename, dirname, isAbsolute, join, resolve, } from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, DEEPSEEK_HARNESS_ADAPTER_VERSION, DeepSeekHarnessSessionAdapter, OmpSessionAdapter, OMP_ADAPTER_VERSION, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, buildSessionExportPlan, reverifySessionExportPlan, buildSessionAiwgFortemiIndexExport, recoverAiwgFortemiIndexFromFullV1Shard, SESSION_EXPORT_PLAN_SCHEMA_VERSION, } from '../../sessions/index.js';
|
|
5
|
+
import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, GROKBOT_ADAPTER_VERSION, GrokbotSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, DEEPSEEK_HARNESS_ADAPTER_VERSION, DeepSeekHarnessSessionAdapter, OmpSessionAdapter, OMP_ADAPTER_VERSION, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, buildSessionExportPlan, reverifySessionExportPlan, buildSessionAiwgFortemiIndexExport, recoverAiwgFortemiIndexFromFullV1Shard, embedAttachmentsInFullV1Shard, recoverEmbeddedAttachments, SESSION_EXPORT_PLAN_SCHEMA_VERSION, } from '../../sessions/index.js';
|
|
6
6
|
const JSON_CONTRACT_VERSION = '1.0.0';
|
|
7
7
|
async function createLineMemoryPromotionDestination(projectRoot, manifestPath) {
|
|
8
8
|
const modulePath = resolve(dirname(manifestPath), 'commands', 'line-memory.mjs');
|
|
@@ -70,6 +70,9 @@ Options:
|
|
|
70
70
|
--workspace <id>, --tag <tag>, --limit <n>, --cursor <n>
|
|
71
71
|
--page-size <n> Extraction scan page size (default 250, maximum 500)
|
|
72
72
|
--max-documents <n> Explicit extraction safety limit; returns a partial receipt
|
|
73
|
+
--include-bytes Embed registered-output bytes in session export shards
|
|
74
|
+
--max-attachment-bytes <n> Maximum bytes per embedded output (default 67108864)
|
|
75
|
+
--force Permit replacing an existing export or recovery output
|
|
73
76
|
|
|
74
77
|
Search filters:
|
|
75
78
|
--date-from <rfc3339>, --date-to <rfc3339>
|
|
@@ -653,7 +656,7 @@ async function importSource(ctx, args) {
|
|
|
653
656
|
if (provider !== 'generic' && provider !== 'claude' && provider !== 'codex'
|
|
654
657
|
&& provider !== 'copilot' && provider !== 'cursor' && provider !== 'factory'
|
|
655
658
|
&& provider !== 'hermes' && provider !== 'opencode' && provider !== 'openclaw'
|
|
656
|
-
&& provider !== 'openhuman' && provider !== 'pi' && provider !== 'omp' && provider !== 'deepseek-harness' && provider !== 'warp' && provider !== 'devin-desktop') {
|
|
659
|
+
&& provider !== 'openhuman' && provider !== 'grokbot' && provider !== 'pi' && provider !== 'omp' && provider !== 'deepseek-harness' && provider !== 'warp' && provider !== 'devin-desktop') {
|
|
657
660
|
throw new CliError('UNSUPPORTED_OPERATION', `session import is not implemented for ${provider}`, EXIT.unsupported);
|
|
658
661
|
}
|
|
659
662
|
const sourceId = requiredValue(args, '--source-id');
|
|
@@ -669,6 +672,7 @@ async function importSource(ctx, args) {
|
|
|
669
672
|
const isOpenCode = provider === 'opencode';
|
|
670
673
|
const isOpenClaw = provider === 'openclaw';
|
|
671
674
|
const isOpenHuman = provider === 'openhuman';
|
|
675
|
+
const isGrokbot = provider === 'grokbot';
|
|
672
676
|
const isPi = provider === 'pi';
|
|
673
677
|
const isOmp = provider === 'omp';
|
|
674
678
|
const isDsh = provider === 'deepseek-harness';
|
|
@@ -692,13 +696,15 @@ async function importSource(ctx, args) {
|
|
|
692
696
|
? new OpenClawSessionAdapter()
|
|
693
697
|
: isOpenHuman
|
|
694
698
|
? new OpenHumanSessionAdapter()
|
|
695
|
-
:
|
|
696
|
-
? new
|
|
697
|
-
:
|
|
698
|
-
? new
|
|
699
|
-
:
|
|
700
|
-
? new
|
|
701
|
-
:
|
|
699
|
+
: isGrokbot
|
|
700
|
+
? new GrokbotSessionAdapter()
|
|
701
|
+
: isPi
|
|
702
|
+
? new PiSessionAdapter()
|
|
703
|
+
: isWarp
|
|
704
|
+
? new WarpSessionAdapter()
|
|
705
|
+
: isDevinDesktop
|
|
706
|
+
? new DevinDesktopSessionAdapter()
|
|
707
|
+
: new GenericSessionInterchangeAdapter();
|
|
702
708
|
const locatorClass = isDsh ? 'deepseek-harness-session-v2-jsonl' : isOmp ? 'omp-session-v3-jsonl' : isClaude
|
|
703
709
|
? claudeLocatorClass(input)
|
|
704
710
|
: isCodex
|
|
@@ -717,13 +723,15 @@ async function importSource(ctx, args) {
|
|
|
717
723
|
? 'openclaw-consistent-snapshot-jsonl'
|
|
718
724
|
: isOpenHuman
|
|
719
725
|
? 'openhuman-enriched-jsonl'
|
|
720
|
-
:
|
|
721
|
-
? '
|
|
722
|
-
:
|
|
723
|
-
? '
|
|
724
|
-
:
|
|
725
|
-
? '
|
|
726
|
-
:
|
|
726
|
+
: isGrokbot
|
|
727
|
+
? 'manual-export'
|
|
728
|
+
: isPi
|
|
729
|
+
? 'pi-session-v3-jsonl'
|
|
730
|
+
: isWarp
|
|
731
|
+
? 'warp-markdown-export'
|
|
732
|
+
: isDevinDesktop
|
|
733
|
+
? 'devin-desktop-cascade-hook-jsonl'
|
|
734
|
+
: 'manual-export';
|
|
727
735
|
const selectedSource = {
|
|
728
736
|
provider, locator: input, locatorClass, sourceId,
|
|
729
737
|
authorizedScope: { workspaceId, allowedRoots: [dirname(input)] },
|
|
@@ -749,11 +757,13 @@ async function importSource(ctx, args) {
|
|
|
749
757
|
? 'schema-16-event-v3-consistent-snapshot'
|
|
750
758
|
: isOpenHuman
|
|
751
759
|
? 'schema-1-session-raw-enriched'
|
|
752
|
-
:
|
|
753
|
-
? 'manual-
|
|
754
|
-
:
|
|
755
|
-
? '
|
|
756
|
-
:
|
|
760
|
+
: isGrokbot
|
|
761
|
+
? 'manual-interchange'
|
|
762
|
+
: isWarp
|
|
763
|
+
? 'manual-lossy-markdown-export'
|
|
764
|
+
: isDevinDesktop
|
|
765
|
+
? 'opt-in-cascade-transcript-hook'
|
|
766
|
+
: 'manual-interchange',
|
|
757
767
|
locatorClass, redactedLocator: redactSourceLocator(input),
|
|
758
768
|
adapterVersion: isDsh ? DEEPSEEK_HARNESS_ADAPTER_VERSION : isOmp ? OMP_ADAPTER_VERSION : isClaude
|
|
759
769
|
? CLAUDE_ADAPTER_VERSION
|
|
@@ -773,13 +783,15 @@ async function importSource(ctx, args) {
|
|
|
773
783
|
? OPENCLAW_ADAPTER_VERSION
|
|
774
784
|
: isOpenHuman
|
|
775
785
|
? OPENHUMAN_ADAPTER_VERSION
|
|
776
|
-
:
|
|
777
|
-
?
|
|
778
|
-
:
|
|
779
|
-
?
|
|
780
|
-
:
|
|
786
|
+
: isGrokbot
|
|
787
|
+
? GROKBOT_ADAPTER_VERSION
|
|
788
|
+
: isWarp
|
|
789
|
+
? WARP_ADAPTER_VERSION
|
|
790
|
+
: isDevinDesktop
|
|
791
|
+
? DEVIN_DESKTOP_ADAPTER_VERSION
|
|
792
|
+
: GENERIC_ADAPTER_VERSION,
|
|
781
793
|
sourceSchemaVersion: probe.sourceSchemaVersion,
|
|
782
|
-
disposition: isWarp
|
|
794
|
+
disposition: isWarp || isGrokbot
|
|
783
795
|
? 'manual-only'
|
|
784
796
|
: isClaude || isCodex || isCopilot || isCursor || isFactory || isHermes
|
|
785
797
|
|| isOpenCode || isOpenClaw || isOpenHuman || isDevinDesktop || isOmp || isDsh || isPi
|
|
@@ -804,14 +816,16 @@ async function importSource(ctx, args) {
|
|
|
804
816
|
? { 'native.openclaw': {} }
|
|
805
817
|
: isOpenHuman
|
|
806
818
|
? { 'native.openhuman': {} }
|
|
807
|
-
:
|
|
808
|
-
? { 'native.
|
|
809
|
-
:
|
|
810
|
-
? { 'native.
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
819
|
+
: isGrokbot
|
|
820
|
+
? { 'native.grokbot': {} }
|
|
821
|
+
: isWarp
|
|
822
|
+
? { 'native.warp': {} }
|
|
823
|
+
: isDevinDesktop
|
|
824
|
+
? { 'native.devin-desktop': {
|
|
825
|
+
product: 'Devin Desktop',
|
|
826
|
+
compatibilityProviderId: 'windsurf',
|
|
827
|
+
} }
|
|
828
|
+
: { 'native.generic': {} },
|
|
815
829
|
});
|
|
816
830
|
if (isDryRun(ctx, args)) {
|
|
817
831
|
return preview('import', { source, wouldInspect: true, wouldPersist: false });
|
|
@@ -883,6 +897,14 @@ async function exportBuild(ctx, args, repository, command) {
|
|
|
883
897
|
const planPath = resolve(ctx.cwd, requiredValue(args, '--plan'));
|
|
884
898
|
const outDirectory = resolve(ctx.cwd, requiredValue(args, '--out'));
|
|
885
899
|
const shardName = args.values.get('--shard-name') ?? 'evidence.shard';
|
|
900
|
+
if (basename(shardName) !== shardName) {
|
|
901
|
+
throw new CliError('INVALID_ARGUMENT', '--shard-name must be a filename, not a path', EXIT.usage);
|
|
902
|
+
}
|
|
903
|
+
const finalPath = join(outDirectory, shardName);
|
|
904
|
+
const receiptPath = `${finalPath}.receipt.json`;
|
|
905
|
+
if (!args.flags.has('--force') && (existsSync(finalPath) || existsSync(receiptPath))) {
|
|
906
|
+
throw new CliError('OUTPUT_EXISTS', `refusing to overwrite an existing shard or receipt: ${finalPath} (use --force to replace)`, EXIT.usage);
|
|
907
|
+
}
|
|
886
908
|
let plan;
|
|
887
909
|
try {
|
|
888
910
|
plan = JSON.parse(readFileSync(planPath, 'utf8'));
|
|
@@ -895,8 +917,13 @@ async function exportBuild(ctx, args, repository, command) {
|
|
|
895
917
|
}
|
|
896
918
|
// Recheck source changes before writing (#2564 acceptance criteria):
|
|
897
919
|
// re-select from the live repository and reject on any digest drift.
|
|
898
|
-
const { sessions, eventsBySessionId } = reverifySessionExportPlan(repository, plan);
|
|
899
|
-
const
|
|
920
|
+
const { sessions, eventsBySessionId, catalogTagsBySessionId, outputBytesByRegistrationId, attachmentBytesByEventId, } = reverifySessionExportPlan(repository, plan, { projectRoot: ctx.cwd });
|
|
921
|
+
const includeBytes = args.flags.has('--include-bytes');
|
|
922
|
+
const outputs = plan.outputs.map((output) => ({
|
|
923
|
+
...output,
|
|
924
|
+
bytesEmbedded: includeBytes && outputBytesByRegistrationId.has(output.registrationId),
|
|
925
|
+
}));
|
|
926
|
+
const index = buildSessionAiwgFortemiIndexExport(plan.workspaceId, sessions, eventsBySessionId, outputs, catalogTagsBySessionId);
|
|
900
927
|
// aiwgFortemiIndexToKnowledgeShardWithReport targets the declared
|
|
901
928
|
// full-v1/2.0.0 archive contract and validates losslessness -- unlike the
|
|
902
929
|
// plain aiwgFortemiIndexToKnowledgeShard (core-v1), which silently embeds
|
|
@@ -910,16 +937,64 @@ async function exportBuild(ctx, args, repository, command) {
|
|
|
910
937
|
if (!result.success || !result.archive) {
|
|
911
938
|
throw new CliError('MALFORMED_SOURCE', `full-v1 shard conversion failed: ${JSON.stringify(result.losses)}`, EXIT.contract);
|
|
912
939
|
}
|
|
913
|
-
|
|
940
|
+
let bytes = result.archive;
|
|
941
|
+
let embeddedAttachmentCount = 0;
|
|
942
|
+
let embeddedAttachmentBytes = 0;
|
|
943
|
+
if (includeBytes) {
|
|
944
|
+
const maximum = Number(args.values.get('--max-attachment-bytes') ?? 64 * 1024 * 1024);
|
|
945
|
+
if (!Number.isSafeInteger(maximum) || maximum <= 0) {
|
|
946
|
+
throw new CliError('INVALID_ARGUMENT', '--max-attachment-bytes must be a positive integer', EXIT.usage);
|
|
947
|
+
}
|
|
948
|
+
const attachments = plan.outputs.map((output) => {
|
|
949
|
+
const content = outputBytesByRegistrationId.get(output.registrationId);
|
|
950
|
+
if (content.length > maximum) {
|
|
951
|
+
throw new CliError('SOURCE_TOO_LARGE', `registered output exceeds --max-attachment-bytes: ${output.outputLocator}`, EXIT.contract);
|
|
952
|
+
}
|
|
953
|
+
return {
|
|
954
|
+
recordId: `output_${output.registrationId}`,
|
|
955
|
+
attachmentId: randomUUID(),
|
|
956
|
+
filename: basename(output.outputLocator),
|
|
957
|
+
mimeType: output.mediaType,
|
|
958
|
+
bytes: content,
|
|
959
|
+
};
|
|
960
|
+
});
|
|
961
|
+
for (const attachment of plan.attachments ?? []) {
|
|
962
|
+
const content = attachmentBytesByEventId.get(attachment.eventId);
|
|
963
|
+
if (content.length > maximum) {
|
|
964
|
+
throw new CliError('SOURCE_TOO_LARGE', `session attachment exceeds --max-attachment-bytes: ${attachment.filename}`, EXIT.contract);
|
|
965
|
+
}
|
|
966
|
+
attachments.push({
|
|
967
|
+
recordId: attachment.eventId,
|
|
968
|
+
attachmentId: randomUUID(),
|
|
969
|
+
filename: basename(attachment.filename),
|
|
970
|
+
mimeType: attachment.mediaType,
|
|
971
|
+
bytes: content,
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
embeddedAttachmentCount = attachments.length;
|
|
975
|
+
embeddedAttachmentBytes = attachments.reduce((sum, attachment) => sum + attachment.bytes.length, 0);
|
|
976
|
+
bytes = await embedAttachmentsInFullV1Shard(bytes, attachments);
|
|
977
|
+
}
|
|
914
978
|
mkdirSync(outDirectory, { recursive: true, mode: 0o700 });
|
|
915
|
-
const finalPath = join(outDirectory, shardName);
|
|
916
979
|
// Interrupted writes must never be mistaken for a completed export: write
|
|
917
980
|
// to a sibling temp file, then rename (atomic on the same filesystem), and
|
|
918
981
|
// only then write the receipt -- the receipt's existence is the completion
|
|
919
982
|
// signal, not the shard file's.
|
|
920
983
|
const tempPath = join(outDirectory, `.${shardName}.${randomUUID()}.tmp`);
|
|
921
984
|
writeFileSync(tempPath, bytes, { mode: 0o600 });
|
|
922
|
-
|
|
985
|
+
if (args.flags.has('--force'))
|
|
986
|
+
renameSync(tempPath, finalPath);
|
|
987
|
+
else {
|
|
988
|
+
try {
|
|
989
|
+
linkSync(tempPath, finalPath);
|
|
990
|
+
unlinkSync(tempPath);
|
|
991
|
+
}
|
|
992
|
+
catch (error) {
|
|
993
|
+
if (existsSync(tempPath))
|
|
994
|
+
unlinkSync(tempPath);
|
|
995
|
+
throw new CliError('OUTPUT_EXISTS', `refusing to overwrite an existing shard: ${finalPath}`, EXIT.usage, error);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
923
998
|
const readBack = readFileSync(finalPath);
|
|
924
999
|
const shardDigest = sha256(readBack);
|
|
925
1000
|
if (readBack.length !== bytes.length) {
|
|
@@ -941,9 +1016,14 @@ async function exportBuild(ctx, args, repository, command) {
|
|
|
941
1016
|
shardFile: shardName,
|
|
942
1017
|
shardDigest,
|
|
943
1018
|
shardBytes: readBack.length,
|
|
1019
|
+
embeddedAttachmentCount,
|
|
1020
|
+
embeddedAttachmentBytes,
|
|
944
1021
|
};
|
|
945
|
-
writeFileSync(
|
|
946
|
-
|
|
1022
|
+
writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`, {
|
|
1023
|
+
mode: 0o600,
|
|
1024
|
+
flag: args.flags.has('--force') ? 'w' : 'wx',
|
|
1025
|
+
});
|
|
1026
|
+
return ok(command, { shard: finalPath, receipt: receiptPath, ...receipt });
|
|
947
1027
|
}
|
|
948
1028
|
async function exportVerify(ctx, args, command) {
|
|
949
1029
|
const input = resolve(ctx.cwd, requiredValue(args, '--input'));
|
|
@@ -955,8 +1035,10 @@ async function exportVerify(ctx, args, command) {
|
|
|
955
1035
|
throw new CliError('SOURCE_NOT_AUTHORIZED', `shard file is not readable: ${input}`, EXIT.usage);
|
|
956
1036
|
}
|
|
957
1037
|
let recovered;
|
|
1038
|
+
let embeddedAttachments;
|
|
958
1039
|
try {
|
|
959
1040
|
recovered = recoverAiwgFortemiIndexFromFullV1Shard(bytes);
|
|
1041
|
+
embeddedAttachments = recoverEmbeddedAttachments(bytes);
|
|
960
1042
|
}
|
|
961
1043
|
catch (error) {
|
|
962
1044
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -982,6 +1064,7 @@ async function exportVerify(ctx, args, command) {
|
|
|
982
1064
|
shardBytes: bytes.length,
|
|
983
1065
|
receiptChecked: existsSync(receiptPath),
|
|
984
1066
|
receiptMatches,
|
|
1067
|
+
embeddedAttachmentCount: embeddedAttachments.length,
|
|
985
1068
|
});
|
|
986
1069
|
}
|
|
987
1070
|
async function exportUnpack(ctx, args, command) {
|
|
@@ -1002,17 +1085,34 @@ async function exportUnpack(ctx, args, command) {
|
|
|
1002
1085
|
const message = error instanceof Error ? error.message : String(error);
|
|
1003
1086
|
throw new CliError('MALFORMED_SOURCE', `shard failed validation: ${message}`, EXIT.contract);
|
|
1004
1087
|
}
|
|
1088
|
+
const attachments = recoverEmbeddedAttachments(bytes);
|
|
1005
1089
|
mkdirSync(outDirectory, { recursive: true, mode: 0o700 });
|
|
1006
1090
|
const finalPath = join(outDirectory, 'index.json');
|
|
1091
|
+
if (!args.flags.has('--force') && existsSync(finalPath)) {
|
|
1092
|
+
throw new CliError('OUTPUT_EXISTS', `refusing to overwrite recovered output: ${finalPath}`, EXIT.usage);
|
|
1093
|
+
}
|
|
1094
|
+
const attachmentPaths = attachments.map((attachment) => join(outDirectory, 'attachments', attachment.recordId, basename(attachment.filename)));
|
|
1095
|
+
if (!args.flags.has('--force')) {
|
|
1096
|
+
const conflict = attachmentPaths.find((attachmentPath) => existsSync(attachmentPath));
|
|
1097
|
+
if (conflict)
|
|
1098
|
+
throw new CliError('OUTPUT_EXISTS', `refusing to overwrite recovered attachment: ${conflict}`, EXIT.usage);
|
|
1099
|
+
}
|
|
1007
1100
|
const tempPath = join(outDirectory, `.index.json.${randomUUID()}.tmp`);
|
|
1008
1101
|
writeFileSync(tempPath, `${JSON.stringify(recovered, null, 2)}\n`, { mode: 0o600 });
|
|
1009
1102
|
renameSync(tempPath, finalPath);
|
|
1103
|
+
for (const [index, attachment] of attachments.entries()) {
|
|
1104
|
+
const attachmentDirectory = join(outDirectory, 'attachments', attachment.recordId);
|
|
1105
|
+
mkdirSync(attachmentDirectory, { recursive: true, mode: 0o700 });
|
|
1106
|
+
writeFileSync(attachmentPaths[index], attachment.bytes, { mode: 0o600 });
|
|
1107
|
+
}
|
|
1010
1108
|
return ok(command, {
|
|
1011
1109
|
index: finalPath,
|
|
1012
1110
|
schemaVersion: recovered.schema_version,
|
|
1013
1111
|
itemCount: recovered.items.length,
|
|
1014
1112
|
sessionRecordCount: recovered.items.filter((item) => item.type === 'aiwg.session').length,
|
|
1015
1113
|
eventRecordCount: recovered.items.filter((item) => item.type === 'aiwg.session-event').length,
|
|
1114
|
+
recoveredAttachmentCount: attachments.length,
|
|
1115
|
+
recoveredAttachmentBytes: attachments.reduce((sum, attachment) => sum + attachment.bytes.length, 0),
|
|
1016
1116
|
});
|
|
1017
1117
|
}
|
|
1018
1118
|
async function discoverWorkspace(ctx, args) {
|
|
@@ -1261,6 +1361,20 @@ function providerDisposition(provider) {
|
|
|
1261
1361
|
},
|
|
1262
1362
|
};
|
|
1263
1363
|
}
|
|
1364
|
+
if (provider === 'grokbot') {
|
|
1365
|
+
return {
|
|
1366
|
+
provider, disposition: 'manual-only', operationalState: 'available',
|
|
1367
|
+
supportedOperations: ['inspect', 'stream'],
|
|
1368
|
+
acquisitionModes: ['manual-export'],
|
|
1369
|
+
reasonCode: 'MANUAL_SOURCE_SELECTION_REQUIRED',
|
|
1370
|
+
remediation: 'Select an authorized AIWG session interchange export; Grok Bot auto-discover is unsupported until a native locator exists.',
|
|
1371
|
+
evidence: {
|
|
1372
|
+
adapterVersion: GROKBOT_ADAPTER_VERSION,
|
|
1373
|
+
verifiedAt: '2026-09-15',
|
|
1374
|
+
documentation: 'docs/providers/grokbot-sessions.md',
|
|
1375
|
+
},
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1264
1378
|
if (provider === 'warp') {
|
|
1265
1379
|
return {
|
|
1266
1380
|
provider, disposition: 'manual-only', operationalState: 'available',
|
|
@@ -1373,7 +1487,7 @@ function parseArgs(argv) {
|
|
|
1373
1487
|
'--inactivity-threshold',
|
|
1374
1488
|
'--control-events',
|
|
1375
1489
|
'--session', '--status', '--actor', '--group-by',
|
|
1376
|
-
'--out', '--plan', '--input', '--shard-name',
|
|
1490
|
+
'--out', '--plan', '--input', '--shard-name', '--max-attachment-bytes',
|
|
1377
1491
|
]);
|
|
1378
1492
|
let command;
|
|
1379
1493
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -3102,6 +3102,7 @@ export class UseHandler {
|
|
|
3102
3102
|
version: manifest.version ?? (await getVersionInfo()).version,
|
|
3103
3103
|
source: 'bundled',
|
|
3104
3104
|
manifestHash: await hashManifest(manifestPath),
|
|
3105
|
+
asPrimary: Boolean(explicitAddonProvider),
|
|
3105
3106
|
});
|
|
3106
3107
|
await writeAiwgConfig(projectDir, updated);
|
|
3107
3108
|
config = updated;
|
|
@@ -3224,6 +3225,10 @@ export class UseHandler {
|
|
|
3224
3225
|
}
|
|
3225
3226
|
if (scope === 'user' && verbose) {
|
|
3226
3227
|
ui.dim(` --scope user: deploy targets mirror to home-rooted paths per ADR-4 §2`);
|
|
3228
|
+
if (process.env.AIWG_USER_REGISTRY_PATH?.trim()) {
|
|
3229
|
+
ui.warn('AIWG_USER_REGISTRY_PATH is set (test override active); user registry is not writing to default ~/.aiwg/installed.json'
|
|
3230
|
+
+ ` (active: ${process.env.AIWG_USER_REGISTRY_PATH})`);
|
|
3231
|
+
}
|
|
3227
3232
|
}
|
|
3228
3233
|
const filteredArgs = deployArgs.filter(a => a !== '--no-utils' && a !== '--no-project-local' && a !== '--ci-hooks-enabled' && a !== '--force' && a !== '--skip-conflicts' && a !== '--no-harness-agents');
|
|
3229
3234
|
const deployFilteredArgs = removeFlagWithOptionalValue(filteredArgs, '--harness-agents');
|
|
@@ -3324,6 +3329,15 @@ export class UseHandler {
|
|
|
3324
3329
|
message: `--scope user not supported for provider '${provider}' — see docs/customization/user-scope-deployment.md for the supported list`,
|
|
3325
3330
|
};
|
|
3326
3331
|
}
|
|
3332
|
+
if (provider === 'grokbot') {
|
|
3333
|
+
const { isGrokbotUserScopeConfigured, grokbotMissingRootRemediation } = await import('../../providers/grokbot-paths.js');
|
|
3334
|
+
if (!isGrokbotUserScopeConfigured()) {
|
|
3335
|
+
return {
|
|
3336
|
+
exitCode: 1,
|
|
3337
|
+
message: grokbotMissingRootRemediation(),
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3327
3341
|
}
|
|
3328
3342
|
// Pre-deployment collision check (skip in dry-run — nothing is written)
|
|
3329
3343
|
if (!dryRun) {
|
|
@@ -3853,7 +3867,12 @@ export class UseHandler {
|
|
|
3853
3867
|
commands: counts.commands,
|
|
3854
3868
|
skills: counts.skills,
|
|
3855
3869
|
rules: counts.rules,
|
|
3856
|
-
}, {
|
|
3870
|
+
}, {
|
|
3871
|
+
version: versionInfo.version,
|
|
3872
|
+
source: 'bundled',
|
|
3873
|
+
manifestHash: mHash,
|
|
3874
|
+
asPrimary: Boolean(explicitProvider),
|
|
3875
|
+
});
|
|
3857
3876
|
await writeAiwgConfig(projectDir, updatedConfig);
|
|
3858
3877
|
}
|
|
3859
3878
|
catch {
|
|
@@ -13,6 +13,7 @@ import { resolveOmpPaths } from '../providers/omp-paths.mjs';
|
|
|
13
13
|
import { homedir } from 'node:os';
|
|
14
14
|
import * as path from 'node:path';
|
|
15
15
|
import { resolveHermesHome, resolveHermesHomePath } from '../providers/hermes-home.js';
|
|
16
|
+
import { resolveGrokbotSkillsDir } from '../providers/grokbot-paths.js';
|
|
16
17
|
export const hermesHome = resolveHermesHome;
|
|
17
18
|
function piAgentDir() {
|
|
18
19
|
const configured = process.env.PI_CODING_AGENT_DIR;
|
|
@@ -216,6 +217,18 @@ export const USER_SCOPE_PATHS = {
|
|
|
216
217
|
rules: path.join(homedir(), '.openhuman', '.aiwg', 'rules'),
|
|
217
218
|
behaviors: '',
|
|
218
219
|
},
|
|
220
|
+
get grokbot() {
|
|
221
|
+
// Fail-closed: skills path is empty until AIWG_GROKBOT_SKILLS_DIR is set.
|
|
222
|
+
// Never invent ~/.grokbot. See adr-grokbot-provider-target / #205/#207.
|
|
223
|
+
const skills = resolveGrokbotSkillsDir() || '';
|
|
224
|
+
return {
|
|
225
|
+
agents: '',
|
|
226
|
+
skills,
|
|
227
|
+
commands: '',
|
|
228
|
+
rules: '',
|
|
229
|
+
behaviors: '',
|
|
230
|
+
};
|
|
231
|
+
},
|
|
219
232
|
factory: {
|
|
220
233
|
// #1164 — Verified against Factory docs (docs.factory.ai/cli/configuration/skills).
|
|
221
234
|
// Skills primary user-scope path is ~/.factory/skills/, NOT the
|
|
@@ -6,6 +6,7 @@ import { readUserRegistry } from '../../config/user-registry.js';
|
|
|
6
6
|
import { inspectInstallation } from '../../installation/manager.mjs';
|
|
7
7
|
import { getPackageRoot } from '../../channel/manager.mjs';
|
|
8
8
|
import { getProviderDefinition, normalizeProviderDefinitionId, resolveProviderPathValue, } from '../../providers/provider-definitions.js';
|
|
9
|
+
import { resolveGrokbotSkillsDir } from '../../providers/grokbot-paths.js';
|
|
9
10
|
import { diagnoseIntegratedProviderTransformationReceipt } from '../../providers/transformation-receipt-integration.js';
|
|
10
11
|
import { diagnoseWorkspaceContext, providerContextContract, } from '../../smiths/context-pipeline/workspace-context.js';
|
|
11
12
|
import { USER_SCOPE_PATHS } from '../scope-resolver.js';
|
|
@@ -31,6 +32,11 @@ const RESTART_NOTICES = {
|
|
|
31
32
|
action: 'Reload the Cursor workspace so it reloads agents and rules.',
|
|
32
33
|
reason: 'Cursor reads workspace agents and rules when the workspace opens.',
|
|
33
34
|
},
|
|
35
|
+
grokbot: {
|
|
36
|
+
policy: 'restart-required',
|
|
37
|
+
action: 'Start a new Grok Bot agent chat (or re-read skills) so deployed AIWG context and skills are visible.',
|
|
38
|
+
reason: 'Grok Bot skill/context reload behavior is not yet verified; AIWG does not claim live refresh.',
|
|
39
|
+
},
|
|
34
40
|
factory: {
|
|
35
41
|
policy: 'restart-required',
|
|
36
42
|
action: 'Restart the Factory droid runtime so it reloads deployed droids.',
|
|
@@ -362,7 +368,16 @@ export async function verifyProviderDeployment(options) {
|
|
|
362
368
|
const deploymentRoot = options.outputRoot ?? options.projectRoot;
|
|
363
369
|
const artifactPaths = options.scope === 'user'
|
|
364
370
|
? USER_SCOPE_PATHS[normalized] ?? definition.paths.artifacts
|
|
365
|
-
: definition.paths.artifacts;
|
|
371
|
+
: { ...definition.paths.artifacts };
|
|
372
|
+
// Grok Bot: project-scope definition keeps skills null (fail-closed sentinel).
|
|
373
|
+
// When the operator configured AIWG_GROKBOT_SKILLS_DIR, count that absolute
|
|
374
|
+
// root so verification matches the deployer (#210 PUW).
|
|
375
|
+
if (normalized === 'grokbot') {
|
|
376
|
+
const configuredSkills = resolveGrokbotSkillsDir();
|
|
377
|
+
if (configuredSkills) {
|
|
378
|
+
artifactPaths.skills = configuredSkills;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
366
381
|
const writtenSince = options.invocationStartedAt
|
|
367
382
|
? Date.parse(options.invocationStartedAt)
|
|
368
383
|
: Number.NaN;
|
|
@@ -402,7 +417,23 @@ export async function verifyProviderDeployment(options) {
|
|
|
402
417
|
counts.skills += kernelCount;
|
|
403
418
|
const artifactTotal = Object.values(counts).reduce((sum, value) => sum + value, 0);
|
|
404
419
|
if (artifactTotal === 0) {
|
|
405
|
-
|
|
420
|
+
// Bridge-only providers (e.g. grokbot project scope without skills env)
|
|
421
|
+
// intentionally have null native artifact dirs and land AGENTS.md instead.
|
|
422
|
+
const bridgeName = definition.paths.configFile || definition.paths.contextFiles?.contextFile;
|
|
423
|
+
const bridgePath = bridgeName ? path.join(options.projectRoot, bridgeName) : '';
|
|
424
|
+
const bridgePresent = bridgePath ? await exists(bridgePath) : false;
|
|
425
|
+
const expectsNoNativeArtifacts = (!definition.paths.artifacts.agents
|
|
426
|
+
&& !definition.paths.artifacts.commands
|
|
427
|
+
&& !definition.paths.artifacts.skills
|
|
428
|
+
&& !definition.paths.artifacts.rules
|
|
429
|
+
&& !definition.paths.artifacts.behaviors
|
|
430
|
+
&& !definition.paths.kernelSkills);
|
|
431
|
+
if (expectsNoNativeArtifacts && bridgePresent) {
|
|
432
|
+
findings.push(finding(normalized, 'provider-bridge-only', 'info', `No native artifact directories for ${normalized}; discover-first bridge ${bridgeName} is present.`, 'Set AIWG_GROKBOT_SKILLS_DIR (absolute) before --scope user / --global when native skill copies are required.', { bridgePath, counts }));
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
findings.push(finding(normalized, 'provider-artifacts-missing', 'blocking', `No deployed provider or kernel artifacts were found for ${normalized}.`, `Re-run aiwg use ${options.requestedBundles[0] ?? 'all'} --provider ${normalized}.`, { kernelPath, kernelCount, counts }));
|
|
436
|
+
}
|
|
406
437
|
}
|
|
407
438
|
}
|
|
408
439
|
findings.push(...await collectRegistryFindings(options, normalized, counts));
|
|
@@ -172,6 +172,7 @@ export function resolveDelivery(delivery) {
|
|
|
172
172
|
* less aggressive at small fan-outs (10 is a safe middle ground)
|
|
173
173
|
* - hermes: MCP sidecar; rate-limit depends on upstream provider, operator
|
|
174
174
|
* should tune. Conservative 10 default.
|
|
175
|
+
* - grokbot: desktop multi-agent; conservative 4 until native evidence.
|
|
175
176
|
* - unknown: conservative 4 default.
|
|
176
177
|
*/
|
|
177
178
|
export const PROVIDER_PARALLELISM_DEFAULTS = {
|
|
@@ -186,6 +187,13 @@ export const PROVIDER_PARALLELISM_DEFAULTS = {
|
|
|
186
187
|
windsurf: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
187
188
|
openclaw: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
188
189
|
hermes: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
|
|
190
|
+
// Desktop multi-agent; conservative until native concurrency evidence exists.
|
|
191
|
+
grokbot: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
|
|
192
|
+
// Conservative defaults for remaining stable/experimental harnesses (#249).
|
|
193
|
+
openhuman: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
|
|
194
|
+
omp: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
|
|
195
|
+
pi: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
|
|
196
|
+
'deepseek-harness': { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
|
|
189
197
|
};
|
|
190
198
|
const UNKNOWN_PROVIDER_PARALLELISM = {
|
|
191
199
|
max_parallel_subagents: 4,
|
|
@@ -928,6 +936,35 @@ export async function writeAiwgConfig(projectDir, config) {
|
|
|
928
936
|
}
|
|
929
937
|
}
|
|
930
938
|
}
|
|
939
|
+
/**
|
|
940
|
+
* Ensure `provider` appears in `config.providers` without wiping others (#247).
|
|
941
|
+
* First entry is the primary provider for parallelism/doctor labels.
|
|
942
|
+
* When `asPrimary` is true (explicit `--provider` deploy), move the provider to the front.
|
|
943
|
+
*/
|
|
944
|
+
export function ensureProviderListed(config, provider, opts = {}) {
|
|
945
|
+
const normalized = provider.trim();
|
|
946
|
+
if (!normalized)
|
|
947
|
+
return config;
|
|
948
|
+
const providers = Array.isArray(config.providers) ? [...config.providers] : [];
|
|
949
|
+
const idx = providers.indexOf(normalized);
|
|
950
|
+
if (opts.asPrimary) {
|
|
951
|
+
if (idx === 0) {
|
|
952
|
+
config.providers = providers;
|
|
953
|
+
return config;
|
|
954
|
+
}
|
|
955
|
+
if (idx > 0)
|
|
956
|
+
providers.splice(idx, 1);
|
|
957
|
+
providers.unshift(normalized);
|
|
958
|
+
}
|
|
959
|
+
else if (idx < 0) {
|
|
960
|
+
providers.push(normalized);
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
return config;
|
|
964
|
+
}
|
|
965
|
+
config.providers = providers;
|
|
966
|
+
return config;
|
|
967
|
+
}
|
|
931
968
|
/**
|
|
932
969
|
* Update the `installed` record for a framework after a successful deployment.
|
|
933
970
|
* Returns the updated config (does not write to disk — caller must call writeAiwgConfig).
|
|
@@ -975,6 +1012,8 @@ export function updateInstalled(config, name, provider, counts, opts) {
|
|
|
975
1012
|
delete existing.deployedArtifactHashes;
|
|
976
1013
|
}
|
|
977
1014
|
config.installed[name] = existing;
|
|
1015
|
+
// Keep providers[] aligned with deployedTo so doctor/parallelism see the right primary (#247).
|
|
1016
|
+
ensureProviderListed(config, provider, { asPrimary: opts.asPrimary === true });
|
|
978
1017
|
return config;
|
|
979
1018
|
}
|
|
980
1019
|
/**
|
|
@@ -25,6 +25,15 @@ import { userScopeConfigPath } from '../cli/scope-resolver.js';
|
|
|
25
25
|
* never set this — it exists so the test suite can avoid clobbering the real
|
|
26
26
|
* `~/.aiwg/installed.json` on the developer's machine.
|
|
27
27
|
*/
|
|
28
|
+
/** True when AIWG_USER_REGISTRY_PATH redirects away from ~/.aiwg/installed.json. */
|
|
29
|
+
export function isUserRegistryPathOverrideActive() {
|
|
30
|
+
return Boolean(process.env.AIWG_USER_REGISTRY_PATH?.trim());
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Operator-facing warning for a leftover test override (#246).
|
|
34
|
+
* Docs mark AIWG_USER_REGISTRY_PATH as test-only; doctor/status/use must surface it.
|
|
35
|
+
*/
|
|
36
|
+
export const USER_REGISTRY_PATH_OVERRIDE_WARN = 'AIWG_USER_REGISTRY_PATH is set (test override active); user registry is not writing to default ~/.aiwg/installed.json';
|
|
28
37
|
export function userRegistryPath() {
|
|
29
38
|
const override = process.env.AIWG_USER_REGISTRY_PATH;
|
|
30
39
|
if (override)
|