@aiwg/cli 2026.10.9 → 2026.10.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +275 -17
  2. package/bin/aiwg.mjs +8 -0
  3. package/dist/src/cli/handlers/mc-admission.js +39 -0
  4. package/dist/src/cli/handlers/mc.js +2 -31
  5. package/dist/src/cli/handlers/models-help.js +52 -0
  6. package/dist/src/cli/handlers/models.js +11 -81
  7. package/dist/src/cli/handlers/sessions.js +198 -15
  8. package/dist/src/cli/handlers/use-addon-profile.js +72 -0
  9. package/dist/src/cli/handlers/use.js +203 -234
  10. package/dist/src/cli/ui.js +1 -0
  11. package/dist/src/config/aiwg-config.js +38 -184
  12. package/dist/src/config/cli-help.js +54 -0
  13. package/dist/src/config/cli.js +5 -69
  14. package/dist/src/config/config-normalization.js +10 -0
  15. package/dist/src/config/worker-activation-gate.js +26 -0
  16. package/dist/src/config/worker-adoption-cli.js +102 -0
  17. package/dist/src/config/worker-adoption.js +368 -0
  18. package/dist/src/config/worker-config-digest.js +14 -0
  19. package/dist/src/config/worker-context.js +337 -0
  20. package/dist/src/config/worker-group-templates.js +150 -0
  21. package/dist/src/config/worker-guidance-warning.js +28 -0
  22. package/dist/src/config/worker-owner-lock.js +152 -0
  23. package/dist/src/config/worker-parallelism.js +154 -0
  24. package/dist/src/config/worker-write.js +75 -0
  25. package/dist/src/decision/telemetry/exporter.js +2 -1
  26. package/dist/src/decision/telemetry/index.js +1 -0
  27. package/dist/src/decision/telemetry/mapping.js +43 -0
  28. package/dist/src/decision/telemetry/otlp-http.js +5 -2
  29. package/dist/src/decision/telemetry/redaction.js +6 -1
  30. package/dist/src/decision/telemetry/types.js +3 -0
  31. package/dist/src/decision/telemetry/worker.js +69 -0
  32. package/dist/src/lint/rule-activation.js +15 -7
  33. package/dist/src/lint/runner.js +2 -2
  34. package/dist/src/mcp/server.mjs +2 -11
  35. package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
  36. package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
  37. package/dist/src/mcp/tools/worker-invoke.mjs +114 -16
  38. package/dist/src/mission-protocol/codecs.js +8 -38
  39. package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
  40. package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
  41. package/dist/src/models/conductor-data-policy.d.mts +8 -0
  42. package/dist/src/models/conductor-data-policy.mjs +190 -0
  43. package/dist/src/models/config-loader.js +19 -3
  44. package/dist/src/models/config-writer.js +27 -0
  45. package/dist/src/models/model-capabilities.v1.json +275 -17
  46. package/dist/src/models/provider-policy.js +14 -155
  47. package/dist/src/models/resolver.js +2 -293
  48. package/dist/src/models/worker-artifact-binding.mjs +12 -0
  49. package/dist/src/models/worker-direct-readiness-policy.js +155 -0
  50. package/dist/src/models/worker-direct-readiness.js +9 -218
  51. package/dist/src/models/worker-health-readiness.js +105 -26
  52. package/dist/src/models/worker-installation.mjs +12 -14
  53. package/dist/src/models/worker-member-references.js +64 -0
  54. package/dist/src/models/worker-plan.js +317 -0
  55. package/dist/src/models/worker-selection-policy.js +217 -0
  56. package/dist/src/models/wrapper-deployment.js +1 -1
  57. package/dist/src/policy/authorization.js +29 -1
  58. package/dist/src/process/aiwg-probe-command.d.mts +15 -0
  59. package/dist/src/process/aiwg-probe-command.mjs +37 -0
  60. package/dist/src/serve/shared-host-admission-checks.js +116 -0
  61. package/dist/src/serve/shared-host-scheduler.js +25 -107
  62. package/dist/src/serve/shared-host-starvation.js +73 -0
  63. package/dist/src/serve/worker-artifacts.d.mts +4 -0
  64. package/dist/src/serve/worker-artifacts.mjs +56 -0
  65. package/dist/src/serve/worker-command-request.js +68 -0
  66. package/dist/src/serve/worker-invocation-paths.js +144 -0
  67. package/dist/src/serve/worker-invocation-preparation.js +169 -0
  68. package/dist/src/serve/worker-invocation.js +24 -291
  69. package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
  70. package/dist/src/serve/worker-stdin-frame.mjs +6 -6
  71. package/dist/src/sessions/adapters/grokbot.js +7 -0
  72. package/dist/src/sessions/batch-import.js +34 -6
  73. package/dist/src/sessions/contracts.js +19 -0
  74. package/dist/src/sessions/coverage-preflight.js +7 -1
  75. package/dist/src/sessions/fortemi-export-mapping.js +33 -3
  76. package/dist/src/sessions/host-catalog.js +157 -0
  77. package/dist/src/sessions/image-blobs.js +141 -0
  78. package/dist/src/sessions/importer.js +23 -3
  79. package/dist/src/sessions/index.js +2 -0
  80. package/dist/src/sessions/refresh.js +25 -2
  81. package/dist/src/sessions/repository.js +164 -22
  82. package/dist/src/sessions/workspace-discovery.js +5 -2
  83. package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
  84. package/package.json +15 -1
  85. package/schemas/mission-v1.schema.json +20 -19
  86. package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
  87. package/tools/agents/providers/claude-post-deploy.mjs +35 -0
  88. package/tools/agents/providers/claude.mjs +3 -30
  89. package/tools/agents/providers/codex-post-deploy.mjs +13 -0
  90. package/tools/agents/providers/codex.mjs +2 -8
  91. package/tools/agents/providers/conductor-data-guard.mjs +61 -0
  92. package/tools/agents/providers/rule-delivery.mjs +3 -16
  93. package/tools/agents/providers/rule-presentation.mjs +38 -0
  94. package/tools/plugin/package-plugins.mjs +1 -1
  95. package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
  96. package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
  97. package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
  98. package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
  99. package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
  100. package/tools/ralph-external/lib/session-attempt.mjs +85 -0
  101. package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
  102. package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
  103. package/tools/ralph-external/lib/worker-context.mjs +237 -0
  104. package/tools/ralph-external/lib/worker-final.mjs +143 -0
  105. package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
  106. package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
  107. package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
  108. package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
  109. package/tools/ralph-external/lib/worker-isolation.mjs +19 -73
  110. package/tools/ralph-external/lib/worker-launch-cli.mjs +49 -0
  111. package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
  112. package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
  113. package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
  114. package/tools/ralph-external/lib/worker-termination.mjs +82 -0
  115. package/tools/ralph-external/session-launcher.mjs +124 -313
  116. package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
@@ -12,6 +12,7 @@ import { trackTemporaryDirectory } from '../../utils/temporary-directory.js';
12
12
  * @issue #33
13
13
  */
14
14
  import fs from 'fs/promises';
15
+ import { existsSync, rmSync, rmdirSync } from 'fs';
15
16
  import path from 'path';
16
17
  import os from 'os';
17
18
  import { pathToFileURL } from 'node:url';
@@ -27,11 +28,12 @@ import { registerDeployedExtensions } from '../../extensions/deployment-registra
27
28
  import { loadCliCommandsContribution, registerCliCommands, registerHooks, } from '../cli-extension-loader.js';
28
29
  import { translateSkillsToCommands, providerNeedsCommands } from '../../plugin/skill-command-translator.js';
29
30
  import * as ui from '../ui.js';
30
- import { readAiwgConfig, writeAiwgConfig, updateInstalled, hashManifest, emptyConfig, getProjectDir } from '../../config/aiwg-config.js';
31
- import { appendGitignore } from '../../config/gitignore.js';
31
+ import { readAiwgConfig, writeAiwgConfig, updateInstalled, hashManifest, emptyConfig, getProjectDir, getConfigPath } from '../../config/aiwg-config.js';
32
+ import { AIWG_RUNTIME_PATTERNS, appendGitignore } from '../../config/gitignore.js';
32
33
  import { getLogger } from '../log.js';
33
34
  import { installCockpit } from './cockpit.js';
34
35
  import { initHandler } from './init.js';
36
+ import { selectAddonProfile } from './use-addon-profile.js';
35
37
  import { checkCollisions, formatCollisionReport, hasBlockingCollisions, } from '../../smiths/skillsmith/collision-detector.js';
36
38
  import { discoverProjectLocalBundles, } from '../../extensions/project-local-discovery.js';
37
39
  import { PROJECT_LOCAL_TYPE_TO_DIR } from '../../extensions/project-local-paths.js';
@@ -499,11 +501,19 @@ function getProviderKernelSkillsPath(provider) {
499
501
  const PROVIDER_GENERATED_GITIGNORE_PATTERNS = {
500
502
  codex: ['.codex/', '.agents/'],
501
503
  };
502
- async function ensureProviderGeneratedDirsIgnored(projectRoot, provider, opts) {
504
+ /**
505
+ * Patterns `aiwg use` ensures in the project's .gitignore on every deploy:
506
+ * the AIWG runtime subdirs doctor expects, plus any provider-generated dirs.
507
+ * Append-only via appendGitignore — operator content is never rewritten (#3635).
508
+ */
509
+ export function gitignorePatternsForDeploy(provider) {
510
+ return [...AIWG_RUNTIME_PATTERNS, ...(PROVIDER_GENERATED_GITIGNORE_PATTERNS[provider] ?? [])];
511
+ }
512
+ export async function ensureProviderGeneratedDirsIgnored(projectRoot, provider, opts) {
503
513
  if (opts.dryRun)
504
514
  return;
505
- const patterns = PROVIDER_GENERATED_GITIGNORE_PATTERNS[provider];
506
- if (!patterns || patterns.length === 0)
515
+ const patterns = gitignorePatternsForDeploy(provider);
516
+ if (patterns.length === 0)
507
517
  return;
508
518
  try {
509
519
  const result = await appendGitignore(projectRoot, patterns);
@@ -2687,7 +2697,7 @@ export class UseHandler {
2687
2697
  }
2688
2698
  let coreResult;
2689
2699
  try {
2690
- coreResult = await this.executeCore({ ...ctx, args: coreArgs });
2700
+ coreResult = await this.executeCore({ ...ctx, args: coreArgs }, providers);
2691
2701
  }
2692
2702
  catch (error) {
2693
2703
  coreResult = { exitCode: 1, message: error instanceof Error ? error.message : String(error) };
@@ -2822,7 +2832,7 @@ export class UseHandler {
2822
2832
  message: [coreResult.message, rendered, ...(previewActions ? [...previewActions].sort() : [])].filter(Boolean).join('\n'),
2823
2833
  };
2824
2834
  }
2825
- async executeCore(ctx) {
2835
+ async executeCore(ctx, resolvedProviders) {
2826
2836
  const explicitTarget = firstUsePositional(ctx.args);
2827
2837
  if (ctx.args.includes('--workspace-signals')) {
2828
2838
  const signalArgs = ctx.args.filter((a) => a !== '--workspace-signals');
@@ -2989,6 +2999,13 @@ export class UseHandler {
2989
2999
  || remainingArgs.includes('--yes')
2990
3000
  || remainingArgs.includes('-y')
2991
3001
  || remainingArgs.includes('--non-interactive');
3002
+ // An auto-created config is removed again if the target turns out to be unknown,
3003
+ // so a mistyped `aiwg use <name>` leaves the project untouched.
3004
+ let autoCreatedConfig = null;
3005
+ if (!config && !_isDryRun) {
3006
+ const configPath = getConfigPath(projectDir);
3007
+ autoCreatedConfig = { path: configPath, controlDirExisted: existsSync(path.dirname(configPath)) };
3008
+ }
2992
3009
  if (!config) {
2993
3010
  if (_providersValue) {
2994
3011
  // --providers shorthand: write config without wizard
@@ -3306,6 +3323,15 @@ export class UseHandler {
3306
3323
  message: totalFailed > 0 ? `${totalFailed} project-local deploy(s) failed` : '',
3307
3324
  };
3308
3325
  }
3326
+ if (autoCreatedConfig) {
3327
+ rmSync(autoCreatedConfig.path, { force: true });
3328
+ if (!autoCreatedConfig.controlDirExisted) {
3329
+ try {
3330
+ rmdirSync(path.dirname(autoCreatedConfig.path));
3331
+ }
3332
+ catch { /* not empty or already gone */ }
3333
+ }
3334
+ }
3309
3335
  return {
3310
3336
  exitCode: 1,
3311
3337
  message: `Error: Unknown target '${framework}'\nFrameworks: ${VALID_FRAMEWORKS.join(', ')}\n\n'all' deploys the kernel surface (kernel skills, kernel and core rules, behaviors).\nFor a single addon, run 'aiwg list' to see available addons.\nFor extensions (sys, net, it, sec, stream, dev), see $AIWG_ROOT/agentic/code/extensions/.\nFor project-local artifacts, run 'aiwg list --project-local'.\nRun 'aiwg help' for usage information.`,
@@ -3316,7 +3342,8 @@ export class UseHandler {
3316
3342
  if (isAddon || isExtension) {
3317
3343
  const providerIdx = remainingArgs.findIndex(a => a === '--provider' || a === '--platform');
3318
3344
  const explicitAddonProvider = providerIdx >= 0 && remainingArgs[providerIdx + 1] ? remainingArgs[providerIdx + 1] : null;
3319
- const provider = resolveBuiltInProviderForUse(explicitAddonProvider ?? (config?.providers?.[0] ?? 'claude')).provider;
3345
+ const providers = resolvedProviders ?? [...new Set(configuredGlobalProviders(remainingArgs, config)
3346
+ .map(provider => resolveBuiltInProviderForUse(provider).provider))];
3320
3347
  const targetIdx = remainingArgs.findIndex(a => a === '--target');
3321
3348
  const target = targetIdx >= 0 && remainingArgs[targetIdx + 1] ? remainingArgs[targetIdx + 1] : process.cwd();
3322
3349
  const dryRunAddon = remainingArgs.includes('--dry-run');
@@ -3329,264 +3356,206 @@ export class UseHandler {
3329
3356
  catch (error) {
3330
3357
  return { exitCode: 1, message: `Error: ${error instanceof Error ? error.message : String(error)}` };
3331
3358
  }
3332
- if (addonScope === 'user' && !USER_SCOPE_PATHS[provider]) {
3333
- return {
3334
- exitCode: 1,
3335
- message: `--scope user not supported for provider '${provider}' — see docs/customization/user-scope-deployment.md for the supported list`,
3336
- };
3337
- }
3338
- const runner = createUseScriptRunner(frameworkRoot);
3339
- const addonBaseArgs = ['--deploy-commands', '--deploy-skills', '--deploy-rules'];
3340
- if (remainingArgs.includes('--allow-over-budget'))
3341
- addonBaseArgs.push('--allow-over-budget');
3342
- if (addonScope === 'user')
3343
- addonBaseArgs.push('--scope', 'user');
3344
- // An explicitly selected upstream addon must be self-contained in the
3345
- // project. Unlike a full framework deploy, its standard skills cannot be
3346
- // left index-only: the user asked to install this specific bundle and
3347
- // its supporting scripts must travel with the skill directory.
3348
- addonBaseArgs.push('--copy-all');
3349
- addonBaseArgs.push(...modelDeployArgs);
3350
- if (provider)
3351
- addonBaseArgs.push('--provider', provider);
3352
- if (target)
3353
- addonBaseArgs.push('--target', target);
3354
- // Forward --copy-all (#1219) so addon-only deploys also honor it.
3355
- if ((remainingArgs.includes('--copy-all') || remainingArgs.includes('--copy-standard-skills'))
3356
- && !addonBaseArgs.includes('--copy-all')) {
3357
- addonBaseArgs.push('--copy-all');
3358
- }
3359
- if (dryRunAddon)
3360
- addonBaseArgs.push('--dry-run');
3361
- if (remainingArgs.includes('--force'))
3362
- addonBaseArgs.push('--force');
3363
3359
  const kind = isExtension ? 'extension' : 'addon';
3364
- const activationOrder = isAddon
3365
- ? await resolveRequiredAddonActivationOrder(frameworkRoot, framework)
3366
- : [framework];
3367
- const requiredAddons = activationOrder.slice(0, -1);
3368
- for (const dependency of requiredAddons) {
3360
+ const addonSource = isExtension
3361
+ ? extensionPath(frameworkRoot, framework)
3362
+ : addonPath(frameworkRoot, framework);
3363
+ // Deploy, reconcile, and record exactly the provider set verification checks.
3364
+ for (const provider of providers) {
3365
+ if (addonScope === 'user' && !USER_SCOPE_PATHS[provider]) {
3366
+ return {
3367
+ exitCode: 1,
3368
+ message: `--scope user not supported for provider '${provider}' — see docs/customization/user-scope-deployment.md for the supported list`,
3369
+ };
3370
+ }
3371
+ const runner = createUseScriptRunner(frameworkRoot);
3372
+ const addonBaseArgs = ['--deploy-commands', '--deploy-skills', '--deploy-rules'];
3373
+ if (remainingArgs.includes('--allow-over-budget'))
3374
+ addonBaseArgs.push('--allow-over-budget');
3375
+ if (addonScope === 'user')
3376
+ addonBaseArgs.push('--scope', 'user');
3377
+ // An explicitly selected upstream addon must be self-contained in the
3378
+ // project. Unlike a full framework deploy, its standard skills cannot be
3379
+ // left index-only: the user asked to install this specific bundle and
3380
+ // its supporting scripts must travel with the skill directory.
3381
+ addonBaseArgs.push('--copy-all');
3382
+ addonBaseArgs.push(...modelDeployArgs);
3383
+ if (provider)
3384
+ addonBaseArgs.push('--provider', provider);
3385
+ if (target)
3386
+ addonBaseArgs.push('--target', target);
3387
+ // Forward --copy-all (#1219) so addon-only deploys also honor it.
3388
+ if ((remainingArgs.includes('--copy-all') || remainingArgs.includes('--copy-standard-skills'))
3389
+ && !addonBaseArgs.includes('--copy-all')) {
3390
+ addonBaseArgs.push('--copy-all');
3391
+ }
3392
+ if (dryRunAddon)
3393
+ addonBaseArgs.push('--dry-run');
3394
+ if (remainingArgs.includes('--force'))
3395
+ addonBaseArgs.push('--force');
3396
+ const activationOrder = isAddon
3397
+ ? await resolveRequiredAddonActivationOrder(frameworkRoot, framework)
3398
+ : [framework];
3399
+ const requiredAddons = activationOrder.slice(0, -1);
3400
+ for (const dependency of requiredAddons) {
3401
+ ui.blank();
3402
+ ui.header(` Deploying required ${dependency} addon...`);
3403
+ const dependencySource = addonPath(frameworkRoot, dependency);
3404
+ const dependencyResult = await runner.run('tools/agents/deploy-agents.mjs', [
3405
+ '--quiet', '--source', dependencySource,
3406
+ ...addonBaseArgs,
3407
+ ], { capture: true });
3408
+ if (dependencyResult.exitCode !== 0) {
3409
+ return {
3410
+ ...dependencyResult,
3411
+ message: dependencyResult.message
3412
+ || `Failed to deploy required addon '${dependency}'`,
3413
+ };
3414
+ }
3415
+ if (!dryRunAddon) {
3416
+ try {
3417
+ await reconcileDeployedSkillAssets(dependencySource, target, provider, { strictReferences: false, force: remainingArgs.includes('--force') });
3418
+ }
3419
+ catch (error) {
3420
+ return {
3421
+ exitCode: 1,
3422
+ message: `Required addon '${dependency}' skill asset deployment failed: ${error instanceof Error ? error.message : String(error)}`,
3423
+ };
3424
+ }
3425
+ }
3426
+ try {
3427
+ await registerSourceCliCommands({
3428
+ source: dependencySource,
3429
+ target,
3430
+ provider,
3431
+ dryRun: dryRunAddon,
3432
+ fallbackDescription: `${dependency} addon commands`,
3433
+ });
3434
+ }
3435
+ catch (error) {
3436
+ return {
3437
+ exitCode: 1,
3438
+ message: `Failed to register required addon '${dependency}' CLI commands: ${error instanceof Error ? error.message : String(error)}`,
3439
+ };
3440
+ }
3441
+ ui.success(dryRunAddon
3442
+ ? `Required ${dependency} addon activation previewed`
3443
+ : `Required ${dependency} addon deployed`);
3444
+ }
3369
3445
  ui.blank();
3370
- ui.header(` Deploying required ${dependency} addon...`);
3371
- const dependencySource = addonPath(frameworkRoot, dependency);
3372
- const dependencyResult = await runner.run('tools/agents/deploy-agents.mjs', [
3373
- '--quiet', '--source', dependencySource,
3446
+ ui.header(` Deploying ${framework} ${kind}...`);
3447
+ const addonResult = await runner.run('tools/agents/deploy-agents.mjs', [
3448
+ '--quiet', '--source', addonSource,
3374
3449
  ...addonBaseArgs,
3375
3450
  ], { capture: true });
3376
- if (dependencyResult.exitCode !== 0) {
3377
- return {
3378
- ...dependencyResult,
3379
- message: dependencyResult.message
3380
- || `Failed to deploy required addon '${dependency}'`,
3381
- };
3451
+ if (addonResult.exitCode !== 0) {
3452
+ return addonResult;
3382
3453
  }
3383
3454
  if (!dryRunAddon) {
3384
3455
  try {
3385
- await reconcileDeployedSkillAssets(dependencySource, target, provider, { strictReferences: false, force: remainingArgs.includes('--force') });
3456
+ await reconcileDeployedSkillAssets(addonSource, target, provider, { strictReferences: false, force: remainingArgs.includes('--force') });
3386
3457
  }
3387
3458
  catch (error) {
3388
3459
  return {
3389
3460
  exitCode: 1,
3390
- message: `Required addon '${dependency}' skill asset deployment failed: ${error instanceof Error ? error.message : String(error)}`,
3461
+ message: `${kind} '${framework}' skill asset deployment failed: ${error instanceof Error ? error.message : String(error)}`,
3391
3462
  };
3392
3463
  }
3393
3464
  }
3465
+ if (!dryRunAddon && provider === 'omp' && (detectScope(remainingArgs) === 'user' || remainingArgs.includes('--user'))) {
3466
+ try {
3467
+ await deployOmpUserSource({ frameworkRoot, source: addonSource, target, bundle: framework, copyAll: true });
3468
+ }
3469
+ catch (error) {
3470
+ return { exitCode: 1, message: `OMP user deployment failed: ${error instanceof Error ? error.message : String(error)}` };
3471
+ }
3472
+ }
3473
+ // Register only artifacts actually written by a confirmed deployment.
3474
+ if (!dryRunAddon) {
3475
+ try {
3476
+ const registry = getRegistry();
3477
+ const paths = getProviderPaths(provider);
3478
+ await registerDeployedExtensions(registry, {
3479
+ agentsPath: paths.agents,
3480
+ skillsPath: paths.skills,
3481
+ commandsPath: paths.commands,
3482
+ rulesPath: paths.rules,
3483
+ behaviorsPath: paths.behaviors,
3484
+ provider,
3485
+ cwd: target,
3486
+ });
3487
+ ui.success('Extension registration complete');
3488
+ }
3489
+ catch (error) {
3490
+ ui.warn(`Failed to register extensions: ${error instanceof Error ? error.message : String(error)}`);
3491
+ }
3492
+ }
3493
+ // Register CLI commands if addon declares them
3394
3494
  try {
3395
3495
  await registerSourceCliCommands({
3396
- source: dependencySource,
3496
+ source: addonSource,
3397
3497
  target,
3398
3498
  provider,
3399
3499
  dryRun: dryRunAddon,
3400
- fallbackDescription: `${dependency} addon commands`,
3500
+ fallbackDescription: `${framework} addon commands`,
3401
3501
  });
3402
3502
  }
3403
3503
  catch (error) {
3404
3504
  return {
3405
3505
  exitCode: 1,
3406
- message: `Failed to register required addon '${dependency}' CLI commands: ${error instanceof Error ? error.message : String(error)}`,
3407
- };
3408
- }
3409
- ui.success(dryRunAddon
3410
- ? `Required ${dependency} addon activation previewed`
3411
- : `Required ${dependency} addon deployed`);
3412
- }
3413
- ui.blank();
3414
- ui.header(` Deploying ${framework} ${kind}...`);
3415
- const addonSource = isExtension
3416
- ? extensionPath(frameworkRoot, framework)
3417
- : addonPath(frameworkRoot, framework);
3418
- const addonResult = await runner.run('tools/agents/deploy-agents.mjs', [
3419
- '--quiet', '--source', addonSource,
3420
- ...addonBaseArgs,
3421
- ], { capture: true });
3422
- if (addonResult.exitCode !== 0) {
3423
- return addonResult;
3424
- }
3425
- if (!dryRunAddon) {
3426
- try {
3427
- await reconcileDeployedSkillAssets(addonSource, target, provider, { strictReferences: false, force: remainingArgs.includes('--force') });
3428
- }
3429
- catch (error) {
3430
- return {
3431
- exitCode: 1,
3432
- message: `${kind} '${framework}' skill asset deployment failed: ${error instanceof Error ? error.message : String(error)}`,
3506
+ message: `Failed to register CLI commands: ${error instanceof Error ? error.message : String(error)}`,
3433
3507
  };
3434
3508
  }
3435
- }
3436
- if (!dryRunAddon && provider === 'omp' && (detectScope(remainingArgs) === 'user' || remainingArgs.includes('--user'))) {
3437
- try {
3438
- await deployOmpUserSource({ frameworkRoot, source: addonSource, target, bundle: framework, copyAll: true });
3439
- }
3440
- catch (error) {
3441
- return { exitCode: 1, message: `OMP user deployment failed: ${error instanceof Error ? error.message : String(error)}` };
3442
- }
3443
- }
3444
- // Register only artifacts actually written by a confirmed deployment.
3445
- if (!dryRunAddon) {
3446
- try {
3447
- const registry = getRegistry();
3448
- const paths = getProviderPaths(provider);
3449
- await registerDeployedExtensions(registry, {
3450
- agentsPath: paths.agents,
3451
- skillsPath: paths.skills,
3452
- commandsPath: paths.commands,
3453
- rulesPath: paths.rules,
3454
- behaviorsPath: paths.behaviors,
3455
- provider,
3456
- cwd: target,
3457
- });
3458
- ui.success('Extension registration complete');
3459
- }
3460
- catch (error) {
3461
- ui.warn(`Failed to register extensions: ${error instanceof Error ? error.message : String(error)}`);
3462
- }
3463
- }
3464
- // Register CLI commands if addon declares them
3465
- try {
3466
- await registerSourceCliCommands({
3467
- source: addonSource,
3468
- target,
3469
- provider,
3470
- dryRun: dryRunAddon,
3471
- fallbackDescription: `${framework} addon commands`,
3472
- });
3473
- }
3474
- catch (error) {
3475
- return {
3476
- exitCode: 1,
3477
- message: `Failed to register CLI commands: ${error instanceof Error ? error.message : String(error)}`,
3478
- };
3479
- }
3480
- if (!dryRunAddon && (provider === 'openclaw' || (provider === 'grokbot' && addonScope === 'user'))) {
3481
- try {
3482
- await recordNativeAddonDeploy(addonSource, target, provider, framework);
3483
- }
3484
- catch (error) {
3485
- return { exitCode: 1, message: `Addon user registry update failed: ${error instanceof Error ? error.message : String(error)}` };
3486
- }
3487
- }
3488
- // Persist the same lifecycle record frameworks and project-local bundles
3489
- // receive so status, refresh, doctor, and remove can account for this
3490
- // upstream addon and every provider artifact it actually deployed.
3491
- if (!dryRunAddon && config) {
3492
- try {
3493
- const manifestPath = path.join(addonSource, 'manifest.json');
3494
- const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
3495
- // Provider rule aggregation may rename many source rules into one
3496
- // managed index. Record this addon's contributed artifact counts,
3497
- // matching framework registry semantics, rather than trying to
3498
- // attribute shared aggregate filenames after deployment.
3499
- const counts = await countBundleSourceArtifacts(addonSource);
3500
- const updated = updateInstalled(config, framework, provider, counts, {
3501
- version: manifest.version ?? (await getVersionInfo()).version,
3502
- source: 'bundled',
3503
- manifestHash: await hashManifest(manifestPath),
3504
- asPrimary: Boolean(explicitAddonProvider),
3505
- });
3506
- await writeAiwgConfig(projectDir, updated);
3507
- config = updated;
3508
- }
3509
- catch (error) {
3510
- ui.warn(`Addon registry update failed for '${framework}': ${error instanceof Error ? error.message : String(error)}`);
3511
- }
3512
- }
3513
- // Profile picker for addons with memory topology and multiple templates
3514
- try {
3515
- const profileManifestPath = path.join(addonSource, 'manifest.json');
3516
- const profileManifestContent = await fs.readFile(profileManifestPath, 'utf-8');
3517
- const profileManifest = JSON.parse(profileManifestContent);
3518
- const topology = profileManifest.memory?.topology;
3519
- const templates = profileManifest.templates;
3520
- if (topology && templates && templates.length > 1) {
3521
- const profileIdx = remainingArgs.findIndex(a => a === '--profile');
3522
- let selectedProfile;
3523
- if (profileIdx >= 0 && remainingArgs[profileIdx + 1]) {
3524
- // Explicit --profile flag
3525
- selectedProfile = remainingArgs[profileIdx + 1];
3526
- const templateFile = templates.find((t) => t.replace('.md', '') === selectedProfile || t === selectedProfile);
3527
- if (!templateFile) {
3528
- ui.warn(`Unknown profile "${selectedProfile}". Available: ${templates.map((t) => t.replace('.md', '')).join(', ')}`);
3529
- selectedProfile = undefined;
3530
- }
3509
+ if (!dryRunAddon && (provider === 'openclaw' || (provider === 'grokbot' && addonScope === 'user'))) {
3510
+ try {
3511
+ await recordNativeAddonDeploy(addonSource, target, provider, framework);
3531
3512
  }
3532
- else if (_isBulkIntent || !process.stdin.isTTY) {
3533
- // Bulk/automation or non-TTY: silently pick 'generic' default.
3534
- // The profile picker is annoying during `aiwg use all`.
3535
- selectedProfile = 'generic';
3513
+ catch (error) {
3514
+ return { exitCode: 1, message: `Addon user registry update failed: ${error instanceof Error ? error.message : String(error)}` };
3536
3515
  }
3537
- else if (process.stdin.isTTY) {
3538
- // Interactive profile selection via the shared `listSelect` helper
3539
- // (POC for spike #926). One call renders the option list, handles
3540
- // number-or-name matching, threads `ctx.signal` for Ctrl-C
3541
- // cancellation, and resolves to the fallback on timeout or empty
3542
- // input. The hand-rolled parse-and-branch that used to live here
3543
- // is now a one-liner.
3544
- const { createPromptInterface, listSelect } = await import('../prompt-utils.js');
3545
- ui.blank();
3546
- ui.header(' Select a topology profile:');
3547
- const templateNames = templates.map((t) => t.replace('.md', ''));
3548
- const options = templateNames.map((name) => ({
3549
- label: name === 'generic' ? `${name} (default)` : name,
3550
- value: name,
3551
- }));
3552
- const rl = createPromptInterface();
3553
- try {
3554
- selectedProfile = await listSelect(rl, ' Enter number or name [generic]: ', options, 'generic', ctx.signal);
3555
- }
3556
- finally {
3557
- rl.close();
3558
- }
3516
+ }
3517
+ // Persist the same lifecycle record frameworks and project-local bundles
3518
+ // receive so status, refresh, doctor, and remove can account for this
3519
+ // upstream addon and every provider artifact it actually deployed.
3520
+ if (!dryRunAddon && config) {
3521
+ try {
3522
+ const manifestPath = path.join(addonSource, 'manifest.json');
3523
+ const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
3524
+ // Provider rule aggregation may rename many source rules into one
3525
+ // managed index. Record this addon's contributed artifact counts,
3526
+ // matching framework registry semantics, rather than trying to
3527
+ // attribute shared aggregate filenames after deployment.
3528
+ const counts = await countBundleSourceArtifacts(addonSource);
3529
+ const updated = updateInstalled(config, framework, provider, counts, {
3530
+ version: manifest.version ?? (await getVersionInfo()).version,
3531
+ source: 'bundled',
3532
+ manifestHash: await hashManifest(manifestPath),
3533
+ asPrimary: Boolean(explicitAddonProvider),
3534
+ });
3535
+ await writeAiwgConfig(projectDir, updated);
3536
+ config = updated;
3559
3537
  }
3560
- // Write profile config to project namespace
3561
- if (selectedProfile && !dryRunAddon) {
3562
- const namespace = topology.namespace || `.aiwg/${framework}`;
3563
- const configDir = path.join(target, namespace);
3564
- await fs.mkdir(configDir, { recursive: true });
3565
- const profileConfig = {
3566
- profile: selectedProfile,
3567
- pageTemplate: `templates/${selectedProfile}.md`,
3568
- selectedAt: new Date().toISOString(),
3569
- };
3570
- await fs.writeFile(path.join(configDir, 'config.json'), JSON.stringify(profileConfig, null, 2) + '\n');
3571
- ui.success(`Profile "${selectedProfile}" selected → ${namespace}/config.json`);
3538
+ catch (error) {
3539
+ ui.warn(`Addon registry update failed for '${framework}': ${error instanceof Error ? error.message : String(error)}`);
3572
3540
  }
3573
3541
  }
3542
+ if (framework === 'aiwg-utils' && !['pi', 'omp'].includes(provider) && !remainingArgs.includes('--dry-run')) {
3543
+ const wrapperValidation = await validateDeployedModelWrappers({
3544
+ provider: normalizeProviderDefinitionId(provider) ?? provider,
3545
+ target,
3546
+ frameworkRoot,
3547
+ modelDeployArgs,
3548
+ filtered: remainingArgs.includes('--filter') || remainingArgs.includes('--filter-role'),
3549
+ verbose: remainingArgs.includes('--verbose') || remainingArgs.includes('-v'),
3550
+ });
3551
+ if (wrapperValidation)
3552
+ return wrapperValidation;
3553
+ }
3574
3554
  }
3575
- catch {
3576
- // Profile selection is optional — don't fail deployment
3577
- }
3578
- if (framework === 'aiwg-utils' && !['pi', 'omp'].includes(provider) && !remainingArgs.includes('--dry-run')) {
3579
- const wrapperValidation = await validateDeployedModelWrappers({
3580
- provider: normalizeProviderDefinitionId(provider) ?? provider,
3581
- target,
3582
- frameworkRoot,
3583
- modelDeployArgs,
3584
- filtered: remainingArgs.includes('--filter') || remainingArgs.includes('--filter-role'),
3585
- verbose: remainingArgs.includes('--verbose') || remainingArgs.includes('-v'),
3586
- });
3587
- if (wrapperValidation)
3588
- return wrapperValidation;
3589
- }
3555
+ await selectAddonProfile({
3556
+ addonSource, framework, target, args: remainingArgs,
3557
+ dryRunAddon, bulkIntent: _isBulkIntent, signal: ctx.signal,
3558
+ });
3590
3559
  ui.blank();
3591
3560
  ui.success(dryRunAddon
3592
3561
  ? `${framework} addon activation preview complete`
@@ -44,6 +44,7 @@ export function success(msg) {
44
44
  console.log(isTTY ? chalk.hex(SUCCESS_HEX)(` ✓ ${msg}`) : ` OK ${msg}`);
45
45
  }
46
46
  export function error(msg) {
47
+ msg = msg.trimStart();
47
48
  console.error(isTTY ? chalk.red(` ✗ ${msg}`) : ` ERROR ${msg}`);
48
49
  }
49
50
  export function warn(msg) {