@aiwg/cli 2026.10.9 → 2026.10.10

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 (91) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +197 -3
  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/use-addon-profile.js +72 -0
  8. package/dist/src/cli/handlers/use.js +191 -230
  9. package/dist/src/cli/ui.js +1 -0
  10. package/dist/src/config/aiwg-config.js +38 -184
  11. package/dist/src/config/cli-help.js +54 -0
  12. package/dist/src/config/cli.js +5 -69
  13. package/dist/src/config/config-normalization.js +10 -0
  14. package/dist/src/config/worker-activation-gate.js +26 -0
  15. package/dist/src/config/worker-adoption-cli.js +102 -0
  16. package/dist/src/config/worker-adoption.js +368 -0
  17. package/dist/src/config/worker-config-digest.js +14 -0
  18. package/dist/src/config/worker-group-templates.js +150 -0
  19. package/dist/src/config/worker-guidance-warning.js +28 -0
  20. package/dist/src/config/worker-owner-lock.js +152 -0
  21. package/dist/src/config/worker-parallelism.js +154 -0
  22. package/dist/src/config/worker-write.js +75 -0
  23. package/dist/src/decision/telemetry/exporter.js +2 -1
  24. package/dist/src/decision/telemetry/index.js +1 -0
  25. package/dist/src/decision/telemetry/mapping.js +43 -0
  26. package/dist/src/decision/telemetry/otlp-http.js +5 -2
  27. package/dist/src/decision/telemetry/redaction.js +6 -1
  28. package/dist/src/decision/telemetry/types.js +3 -0
  29. package/dist/src/decision/telemetry/worker.js +69 -0
  30. package/dist/src/lint/rule-activation.js +15 -7
  31. package/dist/src/lint/runner.js +2 -2
  32. package/dist/src/mcp/server.mjs +2 -11
  33. package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
  34. package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
  35. package/dist/src/mcp/tools/worker-invoke.mjs +6 -6
  36. package/dist/src/mission-protocol/codecs.js +8 -38
  37. package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
  38. package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
  39. package/dist/src/models/conductor-data-policy.d.mts +8 -0
  40. package/dist/src/models/conductor-data-policy.mjs +190 -0
  41. package/dist/src/models/config-loader.js +19 -3
  42. package/dist/src/models/config-writer.js +27 -0
  43. package/dist/src/models/model-capabilities.v1.json +197 -3
  44. package/dist/src/models/provider-policy.js +7 -155
  45. package/dist/src/models/resolver.js +2 -293
  46. package/dist/src/models/worker-artifact-binding.mjs +12 -0
  47. package/dist/src/models/worker-direct-readiness-policy.js +155 -0
  48. package/dist/src/models/worker-direct-readiness.js +9 -218
  49. package/dist/src/models/worker-health-readiness.js +105 -26
  50. package/dist/src/models/worker-installation.mjs +12 -14
  51. package/dist/src/models/worker-member-references.js +64 -0
  52. package/dist/src/models/worker-plan.js +317 -0
  53. package/dist/src/models/worker-selection-policy.js +217 -0
  54. package/dist/src/models/wrapper-deployment.js +1 -1
  55. package/dist/src/serve/shared-host-admission-checks.js +116 -0
  56. package/dist/src/serve/shared-host-scheduler.js +25 -107
  57. package/dist/src/serve/shared-host-starvation.js +73 -0
  58. package/dist/src/serve/worker-artifacts.d.mts +4 -0
  59. package/dist/src/serve/worker-artifacts.mjs +56 -0
  60. package/dist/src/serve/worker-command-request.js +68 -0
  61. package/dist/src/serve/worker-invocation-paths.js +144 -0
  62. package/dist/src/serve/worker-invocation-preparation.js +169 -0
  63. package/dist/src/serve/worker-invocation.js +24 -291
  64. package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
  65. package/dist/src/serve/worker-stdin-frame.mjs +6 -6
  66. package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
  67. package/package.json +9 -1
  68. package/schemas/mission-v1.schema.json +20 -19
  69. package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
  70. package/tools/agents/providers/claude-post-deploy.mjs +35 -0
  71. package/tools/agents/providers/claude.mjs +3 -30
  72. package/tools/agents/providers/codex-post-deploy.mjs +13 -0
  73. package/tools/agents/providers/codex.mjs +2 -8
  74. package/tools/agents/providers/conductor-data-guard.mjs +61 -0
  75. package/tools/agents/providers/rule-delivery.mjs +3 -16
  76. package/tools/agents/providers/rule-presentation.mjs +38 -0
  77. package/tools/plugin/package-plugins.mjs +1 -1
  78. package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
  79. package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
  80. package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
  81. package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
  82. package/tools/ralph-external/lib/session-attempt.mjs +85 -0
  83. package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
  84. package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
  85. package/tools/ralph-external/lib/worker-final.mjs +143 -0
  86. package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
  87. package/tools/ralph-external/lib/worker-isolation.mjs +2 -44
  88. package/tools/ralph-external/lib/worker-launch-cli.mjs +47 -0
  89. package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
  90. package/tools/ralph-external/session-launcher.mjs +73 -217
  91. 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 { readAiwgConfig, writeAiwgConfig, updateInstalled, hashManifest, emptyConfig, getProjectDir, getConfigPath } from '../../config/aiwg-config.js';
31
32
  import { 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';
@@ -2687,7 +2689,7 @@ export class UseHandler {
2687
2689
  }
2688
2690
  let coreResult;
2689
2691
  try {
2690
- coreResult = await this.executeCore({ ...ctx, args: coreArgs });
2692
+ coreResult = await this.executeCore({ ...ctx, args: coreArgs }, providers);
2691
2693
  }
2692
2694
  catch (error) {
2693
2695
  coreResult = { exitCode: 1, message: error instanceof Error ? error.message : String(error) };
@@ -2822,7 +2824,7 @@ export class UseHandler {
2822
2824
  message: [coreResult.message, rendered, ...(previewActions ? [...previewActions].sort() : [])].filter(Boolean).join('\n'),
2823
2825
  };
2824
2826
  }
2825
- async executeCore(ctx) {
2827
+ async executeCore(ctx, resolvedProviders) {
2826
2828
  const explicitTarget = firstUsePositional(ctx.args);
2827
2829
  if (ctx.args.includes('--workspace-signals')) {
2828
2830
  const signalArgs = ctx.args.filter((a) => a !== '--workspace-signals');
@@ -2989,6 +2991,13 @@ export class UseHandler {
2989
2991
  || remainingArgs.includes('--yes')
2990
2992
  || remainingArgs.includes('-y')
2991
2993
  || remainingArgs.includes('--non-interactive');
2994
+ // An auto-created config is removed again if the target turns out to be unknown,
2995
+ // so a mistyped `aiwg use <name>` leaves the project untouched.
2996
+ let autoCreatedConfig = null;
2997
+ if (!config && !_isDryRun) {
2998
+ const configPath = getConfigPath(projectDir);
2999
+ autoCreatedConfig = { path: configPath, controlDirExisted: existsSync(path.dirname(configPath)) };
3000
+ }
2992
3001
  if (!config) {
2993
3002
  if (_providersValue) {
2994
3003
  // --providers shorthand: write config without wizard
@@ -3306,6 +3315,15 @@ export class UseHandler {
3306
3315
  message: totalFailed > 0 ? `${totalFailed} project-local deploy(s) failed` : '',
3307
3316
  };
3308
3317
  }
3318
+ if (autoCreatedConfig) {
3319
+ rmSync(autoCreatedConfig.path, { force: true });
3320
+ if (!autoCreatedConfig.controlDirExisted) {
3321
+ try {
3322
+ rmdirSync(path.dirname(autoCreatedConfig.path));
3323
+ }
3324
+ catch { /* not empty or already gone */ }
3325
+ }
3326
+ }
3309
3327
  return {
3310
3328
  exitCode: 1,
3311
3329
  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 +3334,8 @@ export class UseHandler {
3316
3334
  if (isAddon || isExtension) {
3317
3335
  const providerIdx = remainingArgs.findIndex(a => a === '--provider' || a === '--platform');
3318
3336
  const explicitAddonProvider = providerIdx >= 0 && remainingArgs[providerIdx + 1] ? remainingArgs[providerIdx + 1] : null;
3319
- const provider = resolveBuiltInProviderForUse(explicitAddonProvider ?? (config?.providers?.[0] ?? 'claude')).provider;
3337
+ const providers = resolvedProviders ?? [...new Set(configuredGlobalProviders(remainingArgs, config)
3338
+ .map(provider => resolveBuiltInProviderForUse(provider).provider))];
3320
3339
  const targetIdx = remainingArgs.findIndex(a => a === '--target');
3321
3340
  const target = targetIdx >= 0 && remainingArgs[targetIdx + 1] ? remainingArgs[targetIdx + 1] : process.cwd();
3322
3341
  const dryRunAddon = remainingArgs.includes('--dry-run');
@@ -3329,264 +3348,206 @@ export class UseHandler {
3329
3348
  catch (error) {
3330
3349
  return { exitCode: 1, message: `Error: ${error instanceof Error ? error.message : String(error)}` };
3331
3350
  }
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
3351
  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) {
3352
+ const addonSource = isExtension
3353
+ ? extensionPath(frameworkRoot, framework)
3354
+ : addonPath(frameworkRoot, framework);
3355
+ // Deploy, reconcile, and record exactly the provider set verification checks.
3356
+ for (const provider of providers) {
3357
+ if (addonScope === 'user' && !USER_SCOPE_PATHS[provider]) {
3358
+ return {
3359
+ exitCode: 1,
3360
+ message: `--scope user not supported for provider '${provider}' — see docs/customization/user-scope-deployment.md for the supported list`,
3361
+ };
3362
+ }
3363
+ const runner = createUseScriptRunner(frameworkRoot);
3364
+ const addonBaseArgs = ['--deploy-commands', '--deploy-skills', '--deploy-rules'];
3365
+ if (remainingArgs.includes('--allow-over-budget'))
3366
+ addonBaseArgs.push('--allow-over-budget');
3367
+ if (addonScope === 'user')
3368
+ addonBaseArgs.push('--scope', 'user');
3369
+ // An explicitly selected upstream addon must be self-contained in the
3370
+ // project. Unlike a full framework deploy, its standard skills cannot be
3371
+ // left index-only: the user asked to install this specific bundle and
3372
+ // its supporting scripts must travel with the skill directory.
3373
+ addonBaseArgs.push('--copy-all');
3374
+ addonBaseArgs.push(...modelDeployArgs);
3375
+ if (provider)
3376
+ addonBaseArgs.push('--provider', provider);
3377
+ if (target)
3378
+ addonBaseArgs.push('--target', target);
3379
+ // Forward --copy-all (#1219) so addon-only deploys also honor it.
3380
+ if ((remainingArgs.includes('--copy-all') || remainingArgs.includes('--copy-standard-skills'))
3381
+ && !addonBaseArgs.includes('--copy-all')) {
3382
+ addonBaseArgs.push('--copy-all');
3383
+ }
3384
+ if (dryRunAddon)
3385
+ addonBaseArgs.push('--dry-run');
3386
+ if (remainingArgs.includes('--force'))
3387
+ addonBaseArgs.push('--force');
3388
+ const activationOrder = isAddon
3389
+ ? await resolveRequiredAddonActivationOrder(frameworkRoot, framework)
3390
+ : [framework];
3391
+ const requiredAddons = activationOrder.slice(0, -1);
3392
+ for (const dependency of requiredAddons) {
3393
+ ui.blank();
3394
+ ui.header(` Deploying required ${dependency} addon...`);
3395
+ const dependencySource = addonPath(frameworkRoot, dependency);
3396
+ const dependencyResult = await runner.run('tools/agents/deploy-agents.mjs', [
3397
+ '--quiet', '--source', dependencySource,
3398
+ ...addonBaseArgs,
3399
+ ], { capture: true });
3400
+ if (dependencyResult.exitCode !== 0) {
3401
+ return {
3402
+ ...dependencyResult,
3403
+ message: dependencyResult.message
3404
+ || `Failed to deploy required addon '${dependency}'`,
3405
+ };
3406
+ }
3407
+ if (!dryRunAddon) {
3408
+ try {
3409
+ await reconcileDeployedSkillAssets(dependencySource, target, provider, { strictReferences: false, force: remainingArgs.includes('--force') });
3410
+ }
3411
+ catch (error) {
3412
+ return {
3413
+ exitCode: 1,
3414
+ message: `Required addon '${dependency}' skill asset deployment failed: ${error instanceof Error ? error.message : String(error)}`,
3415
+ };
3416
+ }
3417
+ }
3418
+ try {
3419
+ await registerSourceCliCommands({
3420
+ source: dependencySource,
3421
+ target,
3422
+ provider,
3423
+ dryRun: dryRunAddon,
3424
+ fallbackDescription: `${dependency} addon commands`,
3425
+ });
3426
+ }
3427
+ catch (error) {
3428
+ return {
3429
+ exitCode: 1,
3430
+ message: `Failed to register required addon '${dependency}' CLI commands: ${error instanceof Error ? error.message : String(error)}`,
3431
+ };
3432
+ }
3433
+ ui.success(dryRunAddon
3434
+ ? `Required ${dependency} addon activation previewed`
3435
+ : `Required ${dependency} addon deployed`);
3436
+ }
3369
3437
  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,
3438
+ ui.header(` Deploying ${framework} ${kind}...`);
3439
+ const addonResult = await runner.run('tools/agents/deploy-agents.mjs', [
3440
+ '--quiet', '--source', addonSource,
3374
3441
  ...addonBaseArgs,
3375
3442
  ], { capture: true });
3376
- if (dependencyResult.exitCode !== 0) {
3377
- return {
3378
- ...dependencyResult,
3379
- message: dependencyResult.message
3380
- || `Failed to deploy required addon '${dependency}'`,
3381
- };
3443
+ if (addonResult.exitCode !== 0) {
3444
+ return addonResult;
3382
3445
  }
3383
3446
  if (!dryRunAddon) {
3384
3447
  try {
3385
- await reconcileDeployedSkillAssets(dependencySource, target, provider, { strictReferences: false, force: remainingArgs.includes('--force') });
3448
+ await reconcileDeployedSkillAssets(addonSource, target, provider, { strictReferences: false, force: remainingArgs.includes('--force') });
3386
3449
  }
3387
3450
  catch (error) {
3388
3451
  return {
3389
3452
  exitCode: 1,
3390
- message: `Required addon '${dependency}' skill asset deployment failed: ${error instanceof Error ? error.message : String(error)}`,
3453
+ message: `${kind} '${framework}' skill asset deployment failed: ${error instanceof Error ? error.message : String(error)}`,
3391
3454
  };
3392
3455
  }
3393
3456
  }
3457
+ if (!dryRunAddon && provider === 'omp' && (detectScope(remainingArgs) === 'user' || remainingArgs.includes('--user'))) {
3458
+ try {
3459
+ await deployOmpUserSource({ frameworkRoot, source: addonSource, target, bundle: framework, copyAll: true });
3460
+ }
3461
+ catch (error) {
3462
+ return { exitCode: 1, message: `OMP user deployment failed: ${error instanceof Error ? error.message : String(error)}` };
3463
+ }
3464
+ }
3465
+ // Register only artifacts actually written by a confirmed deployment.
3466
+ if (!dryRunAddon) {
3467
+ try {
3468
+ const registry = getRegistry();
3469
+ const paths = getProviderPaths(provider);
3470
+ await registerDeployedExtensions(registry, {
3471
+ agentsPath: paths.agents,
3472
+ skillsPath: paths.skills,
3473
+ commandsPath: paths.commands,
3474
+ rulesPath: paths.rules,
3475
+ behaviorsPath: paths.behaviors,
3476
+ provider,
3477
+ cwd: target,
3478
+ });
3479
+ ui.success('Extension registration complete');
3480
+ }
3481
+ catch (error) {
3482
+ ui.warn(`Failed to register extensions: ${error instanceof Error ? error.message : String(error)}`);
3483
+ }
3484
+ }
3485
+ // Register CLI commands if addon declares them
3394
3486
  try {
3395
3487
  await registerSourceCliCommands({
3396
- source: dependencySource,
3488
+ source: addonSource,
3397
3489
  target,
3398
3490
  provider,
3399
3491
  dryRun: dryRunAddon,
3400
- fallbackDescription: `${dependency} addon commands`,
3492
+ fallbackDescription: `${framework} addon commands`,
3401
3493
  });
3402
3494
  }
3403
3495
  catch (error) {
3404
3496
  return {
3405
3497
  exitCode: 1,
3406
- message: `Failed to register required addon '${dependency}' CLI commands: ${error instanceof Error ? error.message : String(error)}`,
3498
+ message: `Failed to register CLI commands: ${error instanceof Error ? error.message : String(error)}`,
3407
3499
  };
3408
3500
  }
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)}`,
3433
- };
3434
- }
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
- }
3501
+ if (!dryRunAddon && (provider === 'openclaw' || (provider === 'grokbot' && addonScope === 'user'))) {
3502
+ try {
3503
+ await recordNativeAddonDeploy(addonSource, target, provider, framework);
3531
3504
  }
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';
3505
+ catch (error) {
3506
+ return { exitCode: 1, message: `Addon user registry update failed: ${error instanceof Error ? error.message : String(error)}` };
3536
3507
  }
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
- }
3508
+ }
3509
+ // Persist the same lifecycle record frameworks and project-local bundles
3510
+ // receive so status, refresh, doctor, and remove can account for this
3511
+ // upstream addon and every provider artifact it actually deployed.
3512
+ if (!dryRunAddon && config) {
3513
+ try {
3514
+ const manifestPath = path.join(addonSource, 'manifest.json');
3515
+ const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
3516
+ // Provider rule aggregation may rename many source rules into one
3517
+ // managed index. Record this addon's contributed artifact counts,
3518
+ // matching framework registry semantics, rather than trying to
3519
+ // attribute shared aggregate filenames after deployment.
3520
+ const counts = await countBundleSourceArtifacts(addonSource);
3521
+ const updated = updateInstalled(config, framework, provider, counts, {
3522
+ version: manifest.version ?? (await getVersionInfo()).version,
3523
+ source: 'bundled',
3524
+ manifestHash: await hashManifest(manifestPath),
3525
+ asPrimary: Boolean(explicitAddonProvider),
3526
+ });
3527
+ await writeAiwgConfig(projectDir, updated);
3528
+ config = updated;
3559
3529
  }
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`);
3530
+ catch (error) {
3531
+ ui.warn(`Addon registry update failed for '${framework}': ${error instanceof Error ? error.message : String(error)}`);
3572
3532
  }
3573
3533
  }
3534
+ if (framework === 'aiwg-utils' && !['pi', 'omp'].includes(provider) && !remainingArgs.includes('--dry-run')) {
3535
+ const wrapperValidation = await validateDeployedModelWrappers({
3536
+ provider: normalizeProviderDefinitionId(provider) ?? provider,
3537
+ target,
3538
+ frameworkRoot,
3539
+ modelDeployArgs,
3540
+ filtered: remainingArgs.includes('--filter') || remainingArgs.includes('--filter-role'),
3541
+ verbose: remainingArgs.includes('--verbose') || remainingArgs.includes('-v'),
3542
+ });
3543
+ if (wrapperValidation)
3544
+ return wrapperValidation;
3545
+ }
3574
3546
  }
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
- }
3547
+ await selectAddonProfile({
3548
+ addonSource, framework, target, args: remainingArgs,
3549
+ dryRunAddon, bulkIntent: _isBulkIntent, signal: ctx.signal,
3550
+ });
3590
3551
  ui.blank();
3591
3552
  ui.success(dryRunAddon
3592
3553
  ? `${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) {