@aiwg/cli 2026.8.8 → 2026.8.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 (70) hide show
  1. package/README.md +23 -8
  2. package/THIRD_PARTY_NOTICES.md +35 -0
  3. package/agentic/code/providers/capability-matrix.yaml +3 -3
  4. package/bin/aiwg.mjs +125 -0
  5. package/dist/src/agents/packaged-agent-inventory.js +37 -1
  6. package/dist/src/artifacts/backends/graphology-backend.js +4 -3
  7. package/dist/src/artifacts/backends/sqlite-backend.js +4 -5
  8. package/dist/src/artifacts/cli.js +2 -2
  9. package/dist/src/artifacts/corpus-tools/cli.js +27 -0
  10. package/dist/src/artifacts/corpus-tools/profile-embed.js +3 -2
  11. package/dist/src/artifacts/corpus-tools/retrieval-lab.js +356 -0
  12. package/dist/src/artifacts/discover-facets.js +2 -2
  13. package/dist/src/artifacts/embedding-index.js +9 -8
  14. package/dist/src/artifacts/fortemi-core-sync.js +23 -8
  15. package/dist/src/artifacts/graph-backend.js +2 -2
  16. package/dist/src/artifacts/query-engine.js +21 -7
  17. package/dist/src/artifacts/repair.js +47 -0
  18. package/dist/src/artifacts/types.js +3 -3
  19. package/dist/src/cli/command-log.js +2 -2
  20. package/dist/src/cli/handlers/artifacts.js +50 -1
  21. package/dist/src/cli/handlers/cost-report.js +71 -0
  22. package/dist/src/cli/handlers/evidence.js +78 -0
  23. package/dist/src/cli/handlers/help.js +9 -0
  24. package/dist/src/cli/handlers/index.js +8 -3
  25. package/dist/src/cli/handlers/local-executor.js +4 -3
  26. package/dist/src/cli/handlers/refresh.js +63 -17
  27. package/dist/src/cli/handlers/regenerate.js +3 -3
  28. package/dist/src/cli/handlers/serve.js +15 -36
  29. package/dist/src/cli/handlers/setup-manifest.js +8 -1
  30. package/dist/src/cli/handlers/use.js +272 -70
  31. package/dist/src/cli/handlers/utilities.js +149 -0
  32. package/dist/src/cli/handlers/workspace.js +10 -0
  33. package/dist/src/cli/help-generator.js +2 -1
  34. package/dist/src/cli/router.js +4 -1
  35. package/dist/src/cli/services/deployment-verification.js +596 -0
  36. package/dist/src/cli/skill-usage.js +2 -2
  37. package/dist/src/cli/workflow-orchestrator.js +1 -1
  38. package/dist/src/cli/workspace-signals.js +2 -2
  39. package/dist/src/config/aiwg-config.js +54 -27
  40. package/dist/src/config/cli.js +3 -3
  41. package/dist/src/config/project-artifacts-health.js +2 -0
  42. package/dist/src/config/project-artifacts-health.mjs +123 -0
  43. package/dist/src/config/project-artifacts-runtime.mjs +16 -0
  44. package/dist/src/config/project-artifacts.js +2 -1
  45. package/dist/src/cost/fleet-report.js +329 -0
  46. package/dist/src/evidence/bundle.js +256 -0
  47. package/dist/src/extensions/commands/definitions.js +77 -25
  48. package/dist/src/extensions/deployment-registration.js +6 -4
  49. package/dist/src/features/catalog.js +26 -0
  50. package/dist/src/features/cli.js +1 -3
  51. package/dist/src/features/runtime.js +17 -1
  52. package/dist/src/issues/cli.js +91 -7
  53. package/dist/src/mcp/server.mjs +1 -1
  54. package/dist/src/ops/registry.js +2 -2
  55. package/dist/src/policy/authorization.js +2 -2
  56. package/dist/src/providers/capability-matrix.yaml +3 -3
  57. package/dist/src/providers/provider-definitions.js +7 -5
  58. package/dist/src/providers/provider-definitions.mjs +1 -1
  59. package/dist/src/serve/pty-bridge.js +2 -8
  60. package/dist/src/serve/screen-reader.js +3 -6
  61. package/dist/src/smiths/context-pipeline/aiwg-md.js +2 -2
  62. package/dist/src/smiths/context-pipeline/finalization.js +18 -5
  63. package/dist/src/smiths/context-pipeline/generator.js +2 -2
  64. package/dist/src/smiths/context-pipeline/workspace-context.js +16 -17
  65. package/package.json +2 -1
  66. package/tools/agents/deploy-agents.mjs +10 -11
  67. package/tools/agents/providers/base.mjs +61 -7
  68. package/tools/agents/providers/openclaw.mjs +5 -2
  69. package/tools/agents/providers/windsurf.mjs +13 -24
  70. package/tools/skills/deploy-skills-codex.mjs +21 -5
@@ -4,7 +4,7 @@ import { createHash } from 'crypto';
4
4
  import path from 'path';
5
5
  import os from 'os';
6
6
  import { readAiwgConfig } from '../config/aiwg-config.js';
7
- import { PROJECT_AIWG_LOCATION_FILE, projectAiwgPath } from '../config/project-artifacts.js';
7
+ import { PROJECT_AIWG_LOCATION_FILE, projectAiwgPath, projectControlPath, } from '../config/project-artifacts.js';
8
8
  const DEFAULT_MAX_BYTES = 1_048_576;
9
9
  const DEFAULT_REPORT_LIMIT = 20;
10
10
  export async function maybeAppendCommandLog(input) {
@@ -157,7 +157,7 @@ async function findProjectRoot(startDir) {
157
157
  while (current !== path.dirname(current)) {
158
158
  if (existsSync(path.join(current, '.aiwg')) ||
159
159
  existsSync(path.join(current, PROJECT_AIWG_LOCATION_FILE)) ||
160
- existsSync(projectAiwgPath(current, 'aiwg.config'))) {
160
+ existsSync(projectControlPath(current, 'aiwg.config'))) {
161
161
  return current;
162
162
  }
163
163
  current = path.dirname(current);
@@ -1,12 +1,17 @@
1
1
  import { getProjectDir } from '../../config/aiwg-config.js';
2
2
  import { moveProjectArtifacts } from '../../artifacts/move.js';
3
+ import { repairProjectArtifacts } from '../../artifacts/repair.js';
4
+ import { resolveProjectAiwgDir } from '../../config/project-artifacts.js';
3
5
  function usage() {
4
6
  return [
5
7
  'aiwg artifacts — Manage the project AIWG artifact root',
6
8
  '',
7
9
  'Usage:',
10
+ ' aiwg artifacts path [--json]',
8
11
  ' aiwg artifacts move --to <path> [--from <path>] [--dry-run] [--no-reindex] [--no-sync]',
9
12
  ' aiwg artifacts attach --to <existing-path> [--dry-run] [--no-reindex] [--no-sync]',
13
+ ' aiwg artifacts repair --dry-run',
14
+ ' aiwg artifacts repair --apply',
10
15
  '',
11
16
  'Notes:',
12
17
  ' move relocates a local artifact root; attach adopts an existing populated root.',
@@ -32,9 +37,53 @@ export const artifactsHandler = {
32
37
  if (action === 'help' || ctx.args.includes('--help') || ctx.args.includes('-h')) {
33
38
  return { exitCode: 0, message: usage() };
34
39
  }
35
- if (action !== 'move' && action !== 'attach') {
40
+ if (action !== 'path' && action !== 'move' && action !== 'attach' && action !== 'repair') {
36
41
  return { exitCode: 1, message: `Unknown artifacts action: ${action}\n\n${usage()}` };
37
42
  }
43
+ if (action === 'path') {
44
+ const projectDir = getProjectDir(ctx, ctx.args);
45
+ const artifactRoot = resolveProjectAiwgDir(projectDir);
46
+ if (ctx.args.includes('--json')) {
47
+ return {
48
+ exitCode: 0,
49
+ message: JSON.stringify({
50
+ schema: 'aiwg.artifacts.path.v1',
51
+ project_root: projectDir,
52
+ artifact_root: artifactRoot,
53
+ }, null, 2),
54
+ rawOutput: true,
55
+ };
56
+ }
57
+ return { exitCode: 0, message: artifactRoot, rawOutput: true };
58
+ }
59
+ if (action === 'repair') {
60
+ try {
61
+ const applied = ctx.args.includes('--apply');
62
+ const result = await repairProjectArtifacts({
63
+ projectDir: getProjectDir(ctx, ctx.args),
64
+ apply: applied,
65
+ });
66
+ return {
67
+ exitCode: 0,
68
+ message: [
69
+ `${applied ? 'Repaired' : 'Artifact repair dry run for'} ${result.before.classification}`,
70
+ ` Local control plane: ${result.before.local_control_root}`,
71
+ ` External corpus: ${result.before.artifact_root}`,
72
+ ` Copy locally: ${result.copied.length ? result.copied.join(', ') : 'none'}`,
73
+ ` Remove local identical corpus copies: ${result.removed.length ? result.removed.join(', ') : 'none'}`,
74
+ ` Result: ${result.after.classification}`,
75
+ applied ? '' : 'No files changed. Re-run with --apply after reviewing this plan.',
76
+ ].filter(Boolean).join('\n'),
77
+ };
78
+ }
79
+ catch (error) {
80
+ return {
81
+ exitCode: 1,
82
+ error: error instanceof Error ? error : new Error(String(error)),
83
+ message: `Artifact repair failed: ${error instanceof Error ? error.message : String(error)}`,
84
+ };
85
+ }
86
+ }
38
87
  const to = valueAfter(ctx.args, '--to');
39
88
  if (!to) {
40
89
  return { exitCode: 1, message: `Error: --to <path> is required.\n\n${usage()}` };
@@ -0,0 +1,71 @@
1
+ /** OpenRouter fleet cost-report CLI handler. @issue #1187 */
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { FleetConfigMissingError, formatFleetSpendReport, generateFleetSpendReport, } from '../../cost/fleet-report.js';
5
+ function option(args, name) {
6
+ const index = args.indexOf(name);
7
+ const value = index >= 0 ? args[index + 1] : undefined;
8
+ return value && !value.startsWith('-') ? value : undefined;
9
+ }
10
+ function usage() {
11
+ return [
12
+ 'Usage: aiwg cost-report (--fleet | --key <key_ref>) [--source openrouter] [--config <fleet.yaml>] [--json]',
13
+ '',
14
+ 'Reads bot-to-key references from ~/.config/aiwg/fleet.yaml and credentials from',
15
+ '~/.config/aiwg/keys/<key_ref> or AIWG_OPENROUTER_KEY_<KEY_REF>.',
16
+ '',
17
+ 'AIWG observes and correlates spend; OpenRouter enforces all key limits and caps.',
18
+ ].join('\n');
19
+ }
20
+ export const costReportHandler = {
21
+ id: 'cost-report',
22
+ name: 'Cost Report',
23
+ description: 'Observe OpenRouter fleet spend and correlate it with local AIWG activity',
24
+ category: 'utility',
25
+ aliases: [],
26
+ async execute(ctx) {
27
+ if (ctx.args.includes('--help') || ctx.args.includes('-h'))
28
+ return { exitCode: 0, message: usage() };
29
+ const keyRef = option(ctx.args, '--key');
30
+ if (ctx.args.includes('--fleet') && keyRef)
31
+ return { exitCode: 2, message: 'Choose either --fleet or --key, not both.' };
32
+ if (!ctx.args.includes('--fleet') && !keyRef) {
33
+ return { exitCode: 2, message: `Select --fleet or --key <key_ref>.\n\n${usage()}` };
34
+ }
35
+ const source = option(ctx.args, '--source') ?? 'openrouter';
36
+ if (source !== 'openrouter')
37
+ return { exitCode: 2, message: `Unsupported source '${source}'. Only openrouter is supported.` };
38
+ const configuredPath = option(ctx.args, '--config');
39
+ const configPath = configuredPath
40
+ ? path.resolve(ctx.cwd, configuredPath)
41
+ : path.join(os.homedir(), '.config', 'aiwg', 'fleet.yaml');
42
+ try {
43
+ const capValue = option(ctx.args, '--monthly-cap');
44
+ const cap = capValue === undefined ? 0 : Number(capValue);
45
+ if (!Number.isFinite(cap) || cap < 0)
46
+ return { exitCode: 2, message: '--monthly-cap must be a non-negative number.' };
47
+ const report = await generateFleetSpendReport({
48
+ cwd: ctx.cwd,
49
+ configPath,
50
+ signal: ctx.signal,
51
+ ...(keyRef ? {
52
+ fleet: [{
53
+ bot: option(ctx.args, '--bot') ?? keyRef,
54
+ machine: option(ctx.args, '--machine') ?? os.hostname(),
55
+ key_ref: keyRef,
56
+ monthly_cap: cap,
57
+ }],
58
+ } : {}),
59
+ });
60
+ return {
61
+ exitCode: report.bots.some(bot => bot.error) ? 1 : 0,
62
+ message: ctx.args.includes('--json') ? JSON.stringify(report, null, 2) : formatFleetSpendReport(report),
63
+ };
64
+ }
65
+ catch (error) {
66
+ const message = error instanceof FleetConfigMissingError ? error.message : `Fleet cost report failed: ${error instanceof Error ? error.message : String(error)}`;
67
+ return { exitCode: 1, message };
68
+ }
69
+ },
70
+ };
71
+ //# sourceMappingURL=cost-report.js.map
@@ -0,0 +1,78 @@
1
+ /** Evidence bundle export and verification CLI. @issue #2039 */
2
+ import path from 'node:path';
3
+ import { createEvidenceBundle, verifyEvidenceBundle } from '../../evidence/bundle.js';
4
+ function values(args, flag) {
5
+ const result = [];
6
+ for (let index = 0; index < args.length; index++)
7
+ if (args[index] === flag && args[index + 1])
8
+ result.push(args[++index]);
9
+ return result;
10
+ }
11
+ function value(args, flag) { return values(args, flag)[0]; }
12
+ function versions(items) {
13
+ return Object.fromEntries(items.map(item => {
14
+ const index = item.indexOf('=');
15
+ if (index < 1 || index === item.length - 1)
16
+ throw new Error(`version '${item}' must use name=value`);
17
+ return [item.slice(0, index), item.slice(index + 1)];
18
+ }));
19
+ }
20
+ function usage() {
21
+ return [
22
+ 'Usage:',
23
+ ' aiwg evidence export --output <dir> [--activity-export <json>] [--report <file>] [--source <file>]',
24
+ ' [--eval-config <file>] [--provenance <file>] [--model-version name=value] [--tool-version name=value]',
25
+ ' [--check-only --not-run <reason>] [--json]',
26
+ ' aiwg evidence verify <bundle> [--expected-root <sha256>] [--json]',
27
+ ].join('\n');
28
+ }
29
+ export const evidenceHandler = {
30
+ id: 'evidence', name: 'Evidence', description: 'Export and verify portable evaluation evidence bundles', category: 'utility', aliases: [],
31
+ async execute(ctx) {
32
+ const [subcommand] = ctx.args;
33
+ if (!subcommand || subcommand === '--help' || subcommand === '-h')
34
+ return { exitCode: 0, message: usage() };
35
+ try {
36
+ if (subcommand === 'verify') {
37
+ const bundle = ctx.args[1];
38
+ if (!bundle || bundle.startsWith('-'))
39
+ return { exitCode: 2, message: usage() };
40
+ const expectedRoot = value(ctx.args, '--expected-root');
41
+ if (expectedRoot && !/^[0-9a-f]{64}$/i.test(expectedRoot))
42
+ return { exitCode: 2, message: '--expected-root must be a SHA-256 hex digest' };
43
+ const result = await verifyEvidenceBundle(path.resolve(ctx.cwd, bundle), expectedRoot?.toLowerCase());
44
+ return { exitCode: result.valid ? 0 : 1, message: ctx.args.includes('--json') ? JSON.stringify(result, null, 2) : [
45
+ `Evidence bundle: ${result.valid ? 'VALID' : 'INVALID'} (${result.status})`,
46
+ ...result.errors.map(error => `ERROR: ${error}`), ...result.warnings.map(warning => `WARN: ${warning}`),
47
+ ].join('\n') };
48
+ }
49
+ if (subcommand !== 'export')
50
+ return { exitCode: 2, message: usage() };
51
+ const output = value(ctx.args, '--output');
52
+ if (!output)
53
+ return { exitCode: 2, message: '--output is required.\n\n' + usage() };
54
+ const checkOnly = ctx.args.includes('--check-only');
55
+ const notRunReason = value(ctx.args, '--not-run');
56
+ if (checkOnly !== Boolean(notRunReason))
57
+ return { exitCode: 2, message: '--check-only and --not-run <reason> must be used together' };
58
+ const inputs = [
59
+ ...values(ctx.args, '--activity-export').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'activity-export' })),
60
+ ...values(ctx.args, '--report').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'report' })),
61
+ ...values(ctx.args, '--source').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'source' })),
62
+ ...values(ctx.args, '--eval-config').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'eval-config' })),
63
+ ...values(ctx.args, '--provenance').map(file => ({ file: path.resolve(ctx.cwd, file), role: 'provenance' })),
64
+ ];
65
+ const manifest = await createEvidenceBundle({
66
+ output: path.resolve(ctx.cwd, output), inputs,
67
+ modelVersions: versions(values(ctx.args, '--model-version')),
68
+ toolVersions: { aiwg: process.env.npm_package_version ?? 'unknown', node: process.version, ...versions(values(ctx.args, '--tool-version')) },
69
+ checkOnly, notRunReason,
70
+ });
71
+ return { exitCode: 0, message: ctx.args.includes('--json') ? JSON.stringify(manifest, null, 2) : `Evidence bundle ${manifest.status}: ${path.resolve(ctx.cwd, output)}\nVerifier root: ${manifest.verifier.root}` };
72
+ }
73
+ catch (error) {
74
+ return { exitCode: 1, message: `Evidence command failed: ${error instanceof Error ? error.message : String(error)}` };
75
+ }
76
+ },
77
+ };
78
+ //# sourceMappingURL=evidence.js.map
@@ -95,6 +95,15 @@ function displayHelp() {
95
95
  ]);
96
96
  helpGroup('VALIDATION', [
97
97
  ['validate-metadata [path]', 'Validate AIWG component metadata (defaults to agentic/code)'],
98
+ ['context-firewall [scan]', 'Audit provider context, trust, drift, poisoning signals, and budget'],
99
+ ['context-firewall baseline', 'Plan or explicitly write the reviewed context baseline'],
100
+ ]);
101
+ helpGroup('METRICS', [
102
+ ['cost-report --fleet', 'Observe OpenRouter per-bot MTD spend and correlate local activity'],
103
+ ]);
104
+ helpGroup('EVIDENCE', [
105
+ ['evidence export --output <dir>', 'Package portable activity, report, source, eval, and provenance evidence'],
106
+ ['evidence verify <bundle>', 'Verify every member hash and the bundle checkpoint'],
98
107
  ]);
99
108
  helpGroup('SCAFFOLDING', [
100
109
  ['new-bundle <name>', 'Create project-local bundle (--type extension|addon|framework|plugin|provider, --starter skill|rule|agent|minimal, --dry-run)'],
@@ -16,7 +16,7 @@ import { versionHandler } from './version.js';
16
16
  import { authHandler } from './auth.js';
17
17
  import { useHandler } from './use.js';
18
18
  import { statusHandler, wizardHandler, migrateWorkspaceHandler, rollbackWorkspaceHandler, workspaceHandlers, } from './workspace.js';
19
- import { prefillCardsHandler, contributeStartHandler, validateMetadataHandler, doctorHandler, updateHandler, utilityHandlers, } from './utilities.js';
19
+ import { prefillCardsHandler, contributeStartHandler, validateMetadataHandler, doctorHandler, contextFirewallHandler, updateHandler, utilityHandlers, } from './utilities.js';
20
20
  import { skillLintHandler } from './skill-lint.js';
21
21
  import { addAgentHandler, addCommandHandler, addSkillHandler, addBehaviorHandler, addTemplateHandler, scaffoldAddonHandler, scaffoldExtensionHandler, scaffoldFrameworkHandler, scaffoldingHandlers, } from './scaffolding.js';
22
22
  import { behaviorHandler, daemonInitHandler, daemonHandlers, } from './daemon.js';
@@ -59,12 +59,14 @@ import { skillUsageHandler } from './skill-usage.js';
59
59
  import { modelsHandler } from './models.js';
60
60
  import { versionsHandler } from './resource-versions.js';
61
61
  import { jobHandler } from './job.js';
62
+ import { costReportHandler } from './cost-report.js';
63
+ import { evidenceHandler } from './evidence.js';
62
64
  // Re-export individual handlers
63
65
  export {
64
66
  // Maintenance
65
- helpHandler, versionHandler, authHandler, doctorHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
67
+ helpHandler, versionHandler, authHandler, doctorHandler, contextFirewallHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
66
68
  // Framework management
67
- useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler,
69
+ useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler,
68
70
  // Project
69
71
  newBundleHandler, quickrefHandler, newProjectHandler, sessionHandler, sessionsHandler,
70
72
  // Workspace
@@ -118,6 +120,7 @@ export const allHandlers = [
118
120
  versionHandler,
119
121
  authHandler,
120
122
  doctorHandler,
123
+ contextFirewallHandler,
121
124
  updateHandler,
122
125
  refreshHandler,
123
126
  regenerateHandler,
@@ -143,6 +146,8 @@ export const allHandlers = [
143
146
  issueAuditHandler,
144
147
  runHandler,
145
148
  jobHandler,
149
+ costReportHandler,
150
+ evidenceHandler,
146
151
  // Workspace management
147
152
  ...workspaceHandlers,
148
153
  // Subcommand handlers (MCP, catalog, index, skills)
@@ -19,6 +19,7 @@
19
19
  import path from 'path';
20
20
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
21
21
  import { randomUUID } from 'crypto';
22
+ import { pathToFileURL } from 'node:url';
22
23
  import { projectAiwgPath } from '../../config/project-artifacts.js';
23
24
  const DEFAULT_PORT = 8200;
24
25
  const DEFAULT_BIND = '127.0.0.1';
@@ -131,7 +132,7 @@ export const localExecutorServeHandler = {
131
132
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
133
  let DaemonSupervisor;
133
134
  try {
134
- const shimMod = await (new Function('m', 'return import(m)'))(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'executor-shim.mjs'));
135
+ const shimMod = await import(pathToFileURL(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'executor-shim.mjs')).href);
135
136
  ExecutorShim = shimMod.ExecutorShim;
136
137
  startExecutorServer = shimMod.startExecutorServer;
137
138
  }
@@ -142,7 +143,7 @@ export const localExecutorServeHandler = {
142
143
  };
143
144
  }
144
145
  try {
145
- const dsMod = await (new Function('m', 'return import(m)'))(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'daemon-supervisor.mjs'));
146
+ const dsMod = await import(pathToFileURL(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'daemon-supervisor.mjs')).href);
146
147
  DaemonSupervisor = dsMod.DaemonSupervisor;
147
148
  }
148
149
  catch (err) {
@@ -155,7 +156,7 @@ export const localExecutorServeHandler = {
155
156
  // requires it. Try loading from ralph-external/orchestrator.mjs.
156
157
  let agentSupervisorInstance = null;
157
158
  try {
158
- const orchMod = await (new Function('m', 'return import(m)'))(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'orchestrator.mjs'));
159
+ const orchMod = await import(pathToFileURL(path.join(ctx.frameworkRoot, 'tools', 'ralph-external', 'orchestrator.mjs')).href);
159
160
  const OrchClass = orchMod.Orchestrator ?? orchMod.default;
160
161
  if (OrchClass) {
161
162
  const orch = new OrchClass({ maxConcurrent: opts.maxConcurrency });
@@ -15,6 +15,7 @@
15
15
  import { promises as fs } from 'fs';
16
16
  import path from 'path';
17
17
  import { createScriptRunner } from './script-runner.js';
18
+ import { createUseHandler } from './use.js';
18
19
  import { getFrameworkRoot } from '../../channel/manager.mjs';
19
20
  import { refreshAllPackages } from '../../packages/registry.js';
20
21
  import { resolveActiveProvider } from '../provider-resolution.js';
@@ -97,7 +98,14 @@ export async function pruneStaleManagedAgentFiles(options) {
97
98
  continue;
98
99
  const artifactName = normalizeAgentArtifactName(entry.name);
99
100
  const missingFromCurrentPackage = !desired.has(artifactName);
100
- const fromOlderPackage = currentVersion !== null && isOlderManagedVersion(marker.version, currentVersion);
101
+ // Addons have independent manifest versions. Comparing their managed
102
+ // marker to the top-level package version makes a successful refresh
103
+ // delete freshly restored addon agents. Version-based cleanup remains
104
+ // valid for other provider trees that were not refreshed, while the
105
+ // active provider removes only artifacts absent from current sources.
106
+ const fromOlderPackage = provider !== options.provider
107
+ && currentVersion !== null
108
+ && isOlderManagedVersion(marker.version, currentVersion);
101
109
  if (!missingFromCurrentPackage && !fromOlderPackage)
102
110
  continue;
103
111
  const relFile = path.relative(options.projectRoot, file);
@@ -149,7 +157,7 @@ export function collectModelDeployArgs(args) {
149
157
  export const refreshHandler = {
150
158
  id: 'refresh',
151
159
  name: 'Refresh',
152
- description: 'Refresh AIWG to latest version and re-deploy all frameworks',
160
+ description: 'Refresh AIWG to latest version and re-deploy installed frameworks',
153
161
  category: 'maintenance',
154
162
  aliases: ['--refresh', 'sync', '--sync'],
155
163
  async execute(ctx) {
@@ -163,6 +171,7 @@ export const refreshHandler = {
163
171
  const modelDeployArgs = collectModelDeployArgs(ctx.args);
164
172
  const frameworkRoot = await getFrameworkRoot();
165
173
  const runner = createScriptRunner(frameworkRoot);
174
+ const activeUseHandler = createUseHandler();
166
175
  if (!quiet) {
167
176
  ui.blank();
168
177
  // Deprecation notice when invoked as 'sync'
@@ -195,6 +204,7 @@ export const refreshHandler = {
195
204
  // Step 2.5: Refresh remote packages (always, unless --packages-only skips npm)
196
205
  if (!quiet)
197
206
  ui.info(dryRun ? 'Would refresh remote packages...' : 'Refreshing remote packages...');
207
+ const deploymentFailures = [];
198
208
  if (!dryRun) {
199
209
  try {
200
210
  const refreshed = await refreshAllPackages();
@@ -241,39 +251,67 @@ export const refreshHandler = {
241
251
  if (!quiet)
242
252
  ui.dim(' Skipping package update (--skip-update)');
243
253
  }
244
- // Step 4: Re-deploy frameworks
245
- const frameworks = frameworksArg ? frameworksArg.split(',') : undefined;
254
+ // Step 4: Re-deploy frameworks. Both the default form and --all mean
255
+ // "all installed", never the `aiwg use all` expansion meta-target. This
256
+ // preserves the operator's selected footprint and removal symmetry.
257
+ const refreshConfig = await readAiwgConfig(ctx.cwd);
258
+ const installedFrameworks = Object.keys(refreshConfig?.installed ?? {});
259
+ const requestedFrameworks = frameworksArg
260
+ ? frameworksArg.split(',').map(item => item.trim()).filter(Boolean)
261
+ : [];
262
+ const frameworks = !frameworksArg || requestedFrameworks.includes('all')
263
+ ? installedFrameworks
264
+ : requestedFrameworks;
246
265
  if (!quiet)
247
266
  ui.info(dryRun ? 'Would re-deploy frameworks...' : 'Re-deploying frameworks...');
248
267
  if (!dryRun) {
249
- const deployTarget = frameworks || ['all'];
250
- for (const fw of deployTarget) {
251
- const providerArgs = ['--provider', detectedProvider, ...modelDeployArgs];
252
- const useResult = await runner.run('tools/cli/deploy.mjs', [fw, ...providerArgs], { capture: quiet });
268
+ if (frameworks.length === 0 && !quiet) {
269
+ ui.dim(' No installed frameworks or addons to re-deploy');
270
+ }
271
+ for (const fw of frameworks) {
272
+ // Invoke the active installation's handler directly. The historical
273
+ // deploy.mjs bridge shells out to the first `aiwg` on PATH, which can
274
+ // be a different version/root and therefore cannot safely refresh
275
+ // addons installed by this package (#143/#2102).
276
+ const useResult = await activeUseHandler.execute({
277
+ ...ctx,
278
+ cwd: ctx.cwd,
279
+ frameworkRoot,
280
+ args: [
281
+ fw,
282
+ '--provider', detectedProvider,
283
+ '--target', ctx.cwd,
284
+ '--yes',
285
+ '--json',
286
+ ...modelDeployArgs,
287
+ ],
288
+ rawArgs: ['use', fw],
289
+ });
253
290
  if (useResult.exitCode === 0) {
254
291
  if (!quiet)
255
292
  ui.success(`Deployed: ${fw}`);
256
293
  }
257
294
  else {
295
+ deploymentFailures.push(fw);
258
296
  if (!quiet)
259
297
  ui.warn(`Deploy issue: ${fw} (exit ${useResult.exitCode})`);
260
298
  }
261
299
  }
262
300
  }
263
301
  else {
264
- const targets = frameworks || ['all installed frameworks'];
265
302
  if (!quiet) {
266
- for (const fw of targets) {
303
+ if (frameworks.length === 0)
304
+ ui.dim(' No installed frameworks or addons');
305
+ for (const fw of frameworks) {
267
306
  ui.dim(` Would re-deploy: ${fw}`);
268
307
  }
269
308
  }
270
309
  }
271
310
  // Step 4.25: Report planned project-local deploys (#1035).
272
- // The actual deploy is performed by `aiwg use` underneath via deploy.mjs;
273
- // this block surfaces what *would* happen during dry-run and what was
274
- // covered during a real refresh.
311
+ // The active use handler performs the actual project-local deploy during
312
+ // framework refresh; this block surfaces dry-run and completion details.
275
313
  try {
276
- const plDiscovery = await discoverProjectLocalBundles(process.cwd());
314
+ const plDiscovery = await discoverProjectLocalBundles(ctx.cwd);
277
315
  const plCount = plDiscovery.bundles.length;
278
316
  if (plCount > 0) {
279
317
  if (dryRun) {
@@ -300,11 +338,12 @@ export const refreshHandler = {
300
338
  if (!quiet)
301
339
  ui.info('Checking for stale deployments...');
302
340
  let staleAgentRemovals = [];
303
- if (!dryRun) {
341
+ if (!dryRun && deploymentFailures.length === 0) {
304
342
  try {
305
343
  staleAgentRemovals = await pruneStaleManagedAgentFiles({
306
344
  projectRoot: ctx.cwd,
307
345
  frameworkRoot,
346
+ provider: detectedProvider,
308
347
  });
309
348
  if (staleAgentRemovals.length > 0 && !quiet) {
310
349
  const total = staleAgentRemovals.reduce((sum, item) => sum + item.paths.length, 0);
@@ -419,14 +458,21 @@ export const refreshHandler = {
419
458
  const output = JSON.stringify({
420
459
  status: dryRun ? 'dry-run' : 'refreshed',
421
460
  provider: detectedProvider,
422
- frameworks: frameworks || ['all'],
461
+ frameworks,
423
462
  skipUpdate,
424
463
  channel: channel || undefined,
425
464
  staleAgentRemovals,
465
+ deploymentFailures,
426
466
  });
427
467
  console.log(output);
428
468
  }
429
- return { exitCode: dryRun ? 0 : 0 };
469
+ if (deploymentFailures.length > 0) {
470
+ return {
471
+ exitCode: 1,
472
+ message: `Failed to re-deploy installed bundle(s): ${deploymentFailures.join(', ')}`,
473
+ };
474
+ }
475
+ return { exitCode: 0 };
430
476
  },
431
477
  };
432
478
  //# sourceMappingURL=refresh.js.map
@@ -25,7 +25,7 @@ import * as ui from '../ui.js';
25
25
  import { generate as generateContextFiles, discoverDeployedArtifacts, shouldEmitContextFiles, buildNormalizedAiwgMd, writeNormalizedAiwgMd, injectLegacyContext, migrateWorkspaceContext, extractExistingProjectContext, } from '../../smiths/context-pipeline/index.js';
26
26
  import { resolveActiveProvider } from '../provider-resolution.js';
27
27
  import { getProviderContextDiscoveryPathStrings } from '../../providers/provider-definitions.js';
28
- import { projectAiwgPath } from '../../config/project-artifacts.js';
28
+ import { projectControlPath } from '../../config/project-artifacts.js';
29
29
  import { selectRegenerateBranch } from '../regenerate-selector.js';
30
30
  async function handleRegenerate(args, cwd) {
31
31
  if (args.includes('--help') || args.includes('-h')) {
@@ -198,7 +198,7 @@ async function handleRegenerate(args, cwd) {
198
198
  if (legacy) {
199
199
  if (skipWorkspaceMd)
200
200
  console.log(' Note: --no-workspace-md is implicit in legacy mode.');
201
- const normalizedPath = projectAiwgPath(target, 'AIWG.md');
201
+ const normalizedPath = projectControlPath(target, 'AIWG.md');
202
202
  let existing = '';
203
203
  try {
204
204
  existing = await fs.readFile(normalizedPath, 'utf8');
@@ -246,7 +246,7 @@ async function handleRegenerate(args, cwd) {
246
246
  console.log(` Would regenerate:`);
247
247
  if (!skipWorkspaceMd)
248
248
  console.log(` - ${path.join(target, 'WORKSPACE.md')} (managed graph; operator section preserved)`);
249
- console.log(` - ${projectAiwgPath(target, 'AIWG.md')}`);
249
+ console.log(` - ${projectControlPath(target, 'AIWG.md')}`);
250
250
  if (!skipAiwgMd)
251
251
  console.log(` - ${aiwgMd}`);
252
252
  if (provider === 'claude') {
@@ -10,7 +10,6 @@
10
10
  */
11
11
  import path from 'path';
12
12
  import { existsSync, readFileSync } from 'fs';
13
- import { spawnSync } from 'child_process';
14
13
  import { createPtyWsHandler, registry as ptyRegistry } from '../../serve/pty-bridge.js';
15
14
  import { telemetryStore, createEvent } from '../../serve/telemetry.js';
16
15
  import { sandboxRegistry, normalizeSandboxEvent, } from '../../serve/sandbox-registry.js';
@@ -21,6 +20,7 @@ import { executorRegistry, validateRegisterPayload, validateDispatchPayload, val
21
20
  import { handleWebhook, IdempotencyCache, PushSecretRegistry, } from '../../a2a/webhook.js';
22
21
  import { AiwgError, EXIT_CODES } from '../errors.js';
23
22
  import { projectAiwgPath } from '../../config/project-artifacts.js';
23
+ import { loadFeaturePackage } from '../../features/runtime.js';
24
24
  // A2A push-notification state — module-scoped so the test harness can
25
25
  // monkey-patch them in if needed. One process serves one set of secrets.
26
26
  const pushSecretRegistry = new PushSecretRegistry();
@@ -190,11 +190,10 @@ async function setupWebSockets(httpServer, readOnly) {
190
190
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
191
  let wsMod;
192
192
  try {
193
- // @ts-expect-error ws lacks bundled types; we use the runtime constructor only
194
- wsMod = await import('ws');
193
+ wsMod = await loadFeaturePackage('ws');
195
194
  }
196
195
  catch {
197
- console.warn('[serve] ws package not available WebSocket routes disabled. Install with: npm install ws');
196
+ console.warn('[serve] WebSocket routes disabledrun `aiwg features install webserver` to enable them.');
198
197
  return;
199
198
  }
200
199
  // ws ships as CJS; ESM import may wrap in .default
@@ -510,40 +509,19 @@ export async function startServer(opts) {
510
509
  // import path raises ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING (#1277).
511
510
  // hono is an optionalDependency; tsc may not find its types under
512
511
  // `npm ci --omit=optional` (e.g. metadata-validation workflow). The
513
- // try/catch + auto-install fallback below handles that at runtime.
512
+ // try/catch below emits the managed feature-install route at runtime.
514
513
  // @ts-ignore — optional dep; may not be installed at typecheck time
515
- honoMod = await import('hono');
514
+ honoMod = await loadFeaturePackage('hono');
516
515
  // @ts-ignore — optional dep; may not be installed at typecheck time
517
- nodeMod = await import('@hono/node-server');
516
+ nodeMod = await loadFeaturePackage('@hono/node-server');
518
517
  }
519
518
  catch {
520
- // Auto-install optional serve dependencies on first use
521
- console.log('Installing serve dependencies (hono, @hono/node-server, ws)...');
522
- const result = spawnSync('npm', ['install', '--save-optional', 'hono', '@hono/node-server', 'ws'], { stdio: 'inherit' });
523
- if (result.status !== 0) {
524
- throw new AiwgError({
525
- code: 'ERR_SERVE_DEPS_INSTALL_FAILED',
526
- message: 'Failed to install serve dependencies (hono, @hono/node-server, ws)',
527
- hint: 'Install manually: npm install hono @hono/node-server ws',
528
- exitCode: EXIT_CODES.GENERAL,
529
- });
530
- }
531
- // Retry imports after install
532
- try {
533
- // @ts-ignore — optional dep; may not be installed at typecheck time
534
- honoMod = await import('hono');
535
- // @ts-ignore — optional dep; may not be installed at typecheck time
536
- nodeMod = await import('@hono/node-server');
537
- }
538
- catch (err) {
539
- throw new AiwgError({
540
- code: 'ERR_SERVE_DEPS_LOAD_FAILED',
541
- message: 'Serve dependencies installed but could not be loaded',
542
- hint: 'Try: npm install hono @hono/node-server ws',
543
- exitCode: EXIT_CODES.GENERAL,
544
- cause: err,
545
- });
546
- }
519
+ throw new AiwgError({
520
+ code: 'ERR_SERVE_DEPS_MISSING',
521
+ message: 'The optional webserver feature is not available',
522
+ hint: 'Run `aiwg features install webserver`, then retry `aiwg serve`.',
523
+ exitCode: EXIT_CODES.GENERAL,
524
+ });
547
525
  }
548
526
  const { Hono } = honoMod;
549
527
  const { serve } = nodeMod;
@@ -1692,7 +1670,8 @@ export async function startServer(opts) {
1692
1670
  const webDistDir = path.join(opts.frameworkRoot, 'apps', 'web', 'dist');
1693
1671
  if (existsSync(webDistDir)) {
1694
1672
  try {
1695
- const { serveStatic } = await (new Function('m', 'return import(m)'))('@hono/node-server/serve-static');
1673
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1674
+ const { serveStatic } = await loadFeaturePackage('@hono/node-server/serve-static');
1696
1675
  app.use('/*', serveStatic({ root: webDistDir }));
1697
1676
  }
1698
1677
  catch {
@@ -1788,7 +1767,7 @@ export const serveHandler = {
1788
1767
  if (open) {
1789
1768
  try {
1790
1769
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1791
- const openMod = await (new Function('m', 'return import(m)'))('open');
1770
+ const openMod = await loadFeaturePackage('open');
1792
1771
  const openBrowser = openMod.default ?? openMod;
1793
1772
  await openBrowser(url);
1794
1773
  }