@aiwg/cli 2026.8.17 → 2026.8.19

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 (50) hide show
  1. package/bin/aiwg.mjs +24 -2
  2. package/dist/src/a2a/agent-card.js +4 -1
  3. package/dist/src/a2a/client.js +148 -68
  4. package/dist/src/a2a/codecs.js +480 -0
  5. package/dist/src/a2a/events.js +226 -0
  6. package/dist/src/a2a/hitl-driver.js +8 -6
  7. package/dist/src/a2a/hitl.js +2 -1
  8. package/dist/src/a2a/http.js +85 -5
  9. package/dist/src/a2a/protocol.js +136 -0
  10. package/dist/src/a2a/types.js +4 -14
  11. package/dist/src/a2a/webhook.js +101 -4
  12. package/dist/src/artifacts/index-builder.js +63 -12
  13. package/dist/src/artifacts/index-files.js +26 -5
  14. package/dist/src/artifacts/query-engine.js +67 -67
  15. package/dist/src/artifacts/stats.js +6 -2
  16. package/dist/src/artifacts/types.js +1 -1
  17. package/dist/src/audit/operator-decision.js +15 -1
  18. package/dist/src/channel/manager.mjs +89 -17
  19. package/dist/src/cli/handlers/index.js +3 -1
  20. package/dist/src/cli/handlers/installation.js +79 -0
  21. package/dist/src/cli/handlers/refresh.js +6 -2
  22. package/dist/src/cli/handlers/runtime-info.js +9 -1
  23. package/dist/src/cli/handlers/serve.js +107 -4
  24. package/dist/src/cli/handlers/session.js +12 -26
  25. package/dist/src/cli/handlers/use.js +19 -4
  26. package/dist/src/cli/handlers/utilities.js +4 -0
  27. package/dist/src/cli/handlers/version.js +4 -0
  28. package/dist/src/config/user-config-dir.mjs +29 -0
  29. package/dist/src/config/user-config.js +4 -22
  30. package/dist/src/extensions/commands/definitions.js +20 -1
  31. package/dist/src/features/catalog.js +2 -1
  32. package/dist/src/flow/graph-metadata.js +56 -0
  33. package/dist/src/installation/manager-command.mjs +31 -0
  34. package/dist/src/installation/manager.mjs +264 -0
  35. package/dist/src/serve/a2a-terminal-observer.js +28 -5
  36. package/dist/src/serve/dispatch-router.js +32 -4
  37. package/dist/src/serve/executor-registry.js +29 -0
  38. package/dist/src/serve/mission-conductor.js +15 -1
  39. package/dist/src/serve/stack-adapters.js +2 -1
  40. package/dist/src/serve/telemetry.js +5 -1
  41. package/dist/src/smiths/context-pipeline/claude-hook.js +8 -5
  42. package/dist/src/smiths/context-pipeline/line-endings.js +12 -0
  43. package/dist/src/smiths/context-pipeline/managed-hook.js +8 -5
  44. package/dist/src/smiths/context-pipeline/workspace-context.js +3 -1
  45. package/dist/src/update/checker.mjs +16 -15
  46. package/dist/src/update/notifier.mjs +8 -3
  47. package/dist/src/update/service.mjs +51 -5
  48. package/package.json +1 -1
  49. package/tools/agents/deploy-agents.mjs +28 -8
  50. package/tools/agents/providers/base.mjs +5 -3
@@ -7,7 +7,7 @@
7
7
  * @source @src/artifacts/types.ts
8
8
  * @tests @test/unit/artifacts/stats.test.ts
9
9
  */
10
- import { GRAPH_CONFIGS, loadUserGraphConfigs } from './types.js';
10
+ import { GRAPH_CONFIGS, loadGlobalGraphConfigs, loadUserGraphConfigs } from './types.js';
11
11
  import { loadIndexStats, loadGraphIndexFile } from './index-reader.js';
12
12
  import { collectGraphIndexFiles, indexPathFor } from './index-files.js';
13
13
  /** Calculate coverage over the same current file set used by the index builder. */
@@ -30,6 +30,8 @@ async function calculateCoverage(cwd, stats, graphType) {
30
30
  */
31
31
  export async function showStats(cwd, options = {}) {
32
32
  const { graph } = options;
33
+ loadUserGraphConfigs(cwd);
34
+ loadGlobalGraphConfigs();
33
35
  if (graph) {
34
36
  // Single graph mode
35
37
  const stats = loadGraphIndexFile(cwd, 'stats.json', graph);
@@ -42,7 +44,6 @@ export async function showStats(cwd, options = {}) {
42
44
  return;
43
45
  }
44
46
  // No graph specified: show all graphs with defaultBuild=true
45
- loadUserGraphConfigs(cwd);
46
47
  const graphTypes = Object.entries(GRAPH_CONFIGS)
47
48
  .filter(([, config]) => config.defaultBuild)
48
49
  .map(([name]) => name);
@@ -128,6 +129,9 @@ async function renderStats(cwd, stats, options, graphType) {
128
129
  // Dependency graph
129
130
  console.log('Dependency Graph:');
130
131
  console.log(` Total edges: ${stats.graphMetrics.totalEdges}`);
132
+ if (stats.graphMetrics.markdownLinkEdges !== undefined) {
133
+ console.log(` Markdown link edges:${String(stats.graphMetrics.markdownLinkEdges).padStart(3)}`);
134
+ }
131
135
  if (stats.graphMetrics.canonicalEdges !== undefined) {
132
136
  console.log(` Canonical edges: ${stats.graphMetrics.canonicalEdges}`);
133
137
  console.log(` Outgoing declares: ${stats.graphMetrics.outgoingDeclarations}`);
@@ -104,7 +104,7 @@ export const INDEX_VERSION = '1.0.0';
104
104
  * making the serialized index schema incompatible; a mismatch simply forces a
105
105
  * one-time content re-extraction during the next incremental build.
106
106
  */
107
- export const INDEX_EXTRACTOR_VERSION = '2026.07.21.2';
107
+ export const INDEX_EXTRACTOR_VERSION = '2026.08.24.1';
108
108
  /**
109
109
  * Built-in graph definitions
110
110
  */
@@ -22,8 +22,9 @@ export function createDecisionRecord(input, previousHash) {
22
22
  const actor = redact(input.actor);
23
23
  const correlation = redact(input.correlation);
24
24
  const runtime = input.runtime ? redact(input.runtime) : undefined;
25
+ const graph = input.graph ? redact(input.graph) : undefined;
25
26
  const reason = redact(input.reason);
26
- const detected = [...actor.paths, ...correlation.paths, ...(runtime?.paths ?? []), ...reason.paths];
27
+ const detected = [...actor.paths, ...correlation.paths, ...(runtime?.paths ?? []), ...(graph?.paths ?? []), ...reason.paths];
27
28
  const unsigned = {
28
29
  schema_version: OPERATOR_DECISION_SCHEMA,
29
30
  event_id: input.event_id ?? randomUUID(),
@@ -36,6 +37,7 @@ export function createDecisionRecord(input, previousHash) {
36
37
  classification: input.classification,
37
38
  correlation: correlation.value,
38
39
  ...(runtime ? { runtime: runtime.value } : {}),
40
+ ...(graph ? { graph: graph.value } : {}),
39
41
  ...(input.policy_ref ? { policy_ref: input.policy_ref } : {}),
40
42
  redacted_fields: [...new Set([...(input.redacted_fields ?? []), ...detected])].sort(),
41
43
  previous_hash: previousHash,
@@ -74,6 +76,15 @@ export function toOpenTelemetryLog(record) {
74
76
  'aiwg.provider.id': record.correlation.provider_id,
75
77
  'aiwg.sandbox.task_id': record.correlation.sandbox_task_id,
76
78
  'aiwg.prompt.id': record.correlation.prompt_id,
79
+ 'aiwg.graph.id': record.graph?.graph_id,
80
+ 'aiwg.graph.version': record.graph?.graph_version,
81
+ 'aiwg.graph.run_id': record.graph?.run_id,
82
+ 'aiwg.graph.node_id': record.graph?.node_id,
83
+ 'aiwg.graph.node_run_id': record.graph?.node_run_id,
84
+ 'aiwg.graph.edge_id': record.graph?.edge_id,
85
+ 'aiwg.graph.route_name': record.graph?.route_name,
86
+ 'aiwg.graph.checkpoint_id': record.graph?.checkpoint_id,
87
+ 'aiwg.graph.replay_parent_run_id': record.graph?.replay_parent_run_id,
77
88
  }).filter(([, value]) => value !== undefined).map(([key, value]) => ({
78
89
  key,
79
90
  value: { stringValue: String(value) },
@@ -135,6 +146,9 @@ function validateInput(input) {
135
146
  throw new Error('a non-empty operator reason is required');
136
147
  if (!Object.values(input.correlation).some(Boolean))
137
148
  throw new Error('at least one correlation identifier is required');
149
+ if (input.graph && ![input.graph.graph_id, input.graph.graph_version, input.graph.run_id, input.graph.node_id, input.graph.node_run_id].every(value => typeof value === 'string' && value.length > 0)) {
150
+ throw new Error('graph decision context requires graph, run, node, and node-run identity');
151
+ }
138
152
  if (input.timestamp && !Number.isFinite(Date.parse(input.timestamp)))
139
153
  throw new Error('timestamp must be valid ISO time');
140
154
  }
@@ -13,8 +13,17 @@ import fs from 'fs/promises';
13
13
  import { readFileSync, existsSync } from 'fs';
14
14
  import path from 'path';
15
15
  import { fileURLToPath } from 'url';
16
- import { execSync, spawn } from 'child_process';
16
+ import { execFileSync, execSync, spawn } from 'child_process';
17
17
  import os from 'os';
18
+ import { resolveUserConfigDir } from '../config/user-config-dir.mjs';
19
+ import {
20
+ assertCanonicalInstallation,
21
+ createInstallationIdentity,
22
+ inferInstallationMethod,
23
+ inspectInstallation,
24
+ loadInstallationIdentity,
25
+ saveInstallationIdentity,
26
+ } from '../installation/manager.mjs';
18
27
 
19
28
  /**
20
29
  * Run a command with inherited stdio, applying a wall-clock timeout so a
@@ -80,8 +89,6 @@ const __filename = fileURLToPath(import.meta.url);
80
89
  const __dirname = path.dirname(__filename);
81
90
 
82
91
  // Configuration paths
83
- const CONFIG_DIR = path.join(os.homedir(), '.aiwg');
84
- const CONFIG_FILE = path.join(CONFIG_DIR, 'channel.json');
85
92
  const EDGE_INSTALL_PATH = path.join(os.homedir(), '.local', 'share', 'ai-writing-guide');
86
93
  const REPO_URL = 'https://github.com/jmagly/aiwg.git';
87
94
 
@@ -132,22 +139,65 @@ export function getPackageRoot() {
132
139
  * Load channel configuration
133
140
  * @returns {Promise<object>} Channel configuration
134
141
  */
135
- export async function loadConfig() {
142
+ export async function loadConfig(options = {}) {
143
+ const configDir = resolveUserConfigDir(options);
144
+ const configFile = path.join(configDir, 'channel.json');
145
+ let legacy = {};
136
146
  try {
137
- const data = await fs.readFile(CONFIG_FILE, 'utf8');
138
- return { ...DEFAULT_CONFIG, ...JSON.parse(data) };
147
+ const data = await fs.readFile(configFile, 'utf8');
148
+ legacy = JSON.parse(data);
139
149
  } catch {
140
- return { ...DEFAULT_CONFIG };
150
+ // Legacy state is optional. The canonical identity below is authoritative.
141
151
  }
152
+ const actualRoot = options.actualRoot ?? getPackageRoot();
153
+ const identity = loadInstallationIdentity({ ...options, actualRoot, legacyConfig: legacy });
154
+ if (!identity) return { ...DEFAULT_CONFIG, ...legacy };
155
+ return {
156
+ ...DEFAULT_CONFIG,
157
+ ...legacy,
158
+ channel: identity.channel,
159
+ edgePath: identity.edgePath ?? legacy.edgePath ?? EDGE_INSTALL_PATH,
160
+ devMode: identity.runMode === 'development',
161
+ lastUpdateCheck: identity.lastUpdateCheck,
162
+ updateCheckInterval: identity.updateCheckInterval,
163
+ checkOnStartup: identity.checkOnStartup,
164
+ installation: identity,
165
+ };
142
166
  }
143
167
 
144
168
  /**
145
169
  * Save channel configuration
146
170
  * @param {object} config - Configuration to save
147
171
  */
148
- export async function saveConfig(config) {
149
- await fs.mkdir(CONFIG_DIR, { recursive: true });
150
- await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2));
172
+ export async function saveConfig(config, options = {}) {
173
+ const configDir = resolveUserConfigDir(options);
174
+ const configFile = path.join(configDir, 'channel.json');
175
+ await fs.mkdir(configDir, { recursive: true });
176
+ const { installation: _installation, ...legacyConfig } = config;
177
+ await fs.writeFile(configFile, `${JSON.stringify(legacyConfig, null, 2)}\n`);
178
+
179
+ const actualRoot = options.actualRoot ?? getPackageRoot();
180
+ const previous = loadInstallationIdentity({ ...options, actualRoot, legacyConfig: config });
181
+ if (previous) {
182
+ const development = config.devMode === true;
183
+ const root = development && config.edgePath ? config.edgePath : previous.root;
184
+ const method = development ? 'source' : previous.method;
185
+ saveInstallationIdentity(createInstallationIdentity({
186
+ ...options,
187
+ actualRoot,
188
+ root,
189
+ method,
190
+ runMode: development ? 'development' : 'normal',
191
+ channel: config.channel ?? previous.channel,
192
+ edgePath: config.edgePath ?? previous.edgePath,
193
+ managerExecutable: development ? undefined : previous.managerExecutable,
194
+ updateStrategy: development ? 'source-git' : previous.updateStrategy,
195
+ lastUpdateCheck: config.lastUpdateCheck ?? previous.lastUpdateCheck,
196
+ updateCheckInterval: config.updateCheckInterval ?? previous.updateCheckInterval,
197
+ checkOnStartup: config.checkOnStartup ?? previous.checkOnStartup,
198
+ recordedAt: previous.recordedAt,
199
+ }), options);
200
+ }
151
201
  }
152
202
 
153
203
  /**
@@ -178,7 +228,7 @@ export async function getFrameworkRoot() {
178
228
  }
179
229
  }
180
230
 
181
- return getPackageRoot();
231
+ return config.installation?.root ?? getPackageRoot();
182
232
  }
183
233
 
184
234
  /**
@@ -316,12 +366,16 @@ export async function switchToDev(devPath) {
316
366
  */
317
367
  export async function switchToNext() {
318
368
  const config = await loadConfig();
369
+ const status = assertCanonicalInstallation({ actualRoot: getPackageRoot() });
370
+ if (status.identity.method !== 'npm' || !status.identity.managerExecutable) {
371
+ throw new Error('The next channel requires a canonical npm installation with a recorded package-manager executable.');
372
+ }
319
373
 
320
374
  console.log('Switching to next channel (alpha/beta/RC — latest pre-release)...');
321
375
  console.log('');
322
376
 
323
377
  try {
324
- execSync('npm install -g aiwg@next', { stdio: 'inherit' });
378
+ execFileSync(status.identity.managerExecutable, ['install', '--global', 'aiwg@next'], { stdio: 'inherit' });
325
379
  } catch (error) {
326
380
  console.error('Failed to install aiwg@next:', error.message);
327
381
  console.error('Check that npm is available and you have write access to the global prefix.');
@@ -346,12 +400,16 @@ export async function switchToNext() {
346
400
  */
347
401
  export async function switchToNightly() {
348
402
  const config = await loadConfig();
403
+ const status = assertCanonicalInstallation({ actualRoot: getPackageRoot() });
404
+ if (status.identity.method !== 'npm' || !status.identity.managerExecutable) {
405
+ throw new Error('The nightly channel requires a canonical npm installation with a recorded package-manager executable.');
406
+ }
349
407
 
350
408
  console.log('Switching to nightly channel (latest automated snapshot)...');
351
409
  console.log('');
352
410
 
353
411
  try {
354
- execSync('npm install -g aiwg@nightly', { stdio: 'inherit' });
412
+ execFileSync(status.identity.managerExecutable, ['install', '--global', 'aiwg@nightly'], { stdio: 'inherit' });
355
413
  } catch (error) {
356
414
  console.error('Failed to install aiwg@nightly:', error.message);
357
415
  console.error('Check that npm is available and you have write access to the global prefix.');
@@ -376,17 +434,29 @@ export async function switchToNightly() {
376
434
  export async function switchToStable() {
377
435
  const config = await loadConfig();
378
436
 
379
- console.log('Switching to stable channel (npm package)...');
437
+ console.log('Switching to the stable channel...');
380
438
  console.log('');
381
439
 
382
440
  config.channel = 'stable';
383
441
  config.devMode = false;
384
442
  await saveConfig(config);
443
+ const actualRoot = getPackageRoot();
444
+ saveInstallationIdentity(createInstallationIdentity({
445
+ actualRoot,
446
+ root: actualRoot,
447
+ method: inferInstallationMethod(actualRoot),
448
+ runMode: 'normal',
449
+ channel: 'stable',
450
+ edgePath: config.edgePath,
451
+ lastUpdateCheck: config.lastUpdateCheck,
452
+ updateCheckInterval: config.updateCheckInterval,
453
+ checkOnStartup: config.checkOnStartup,
454
+ }));
385
455
 
386
456
  console.log('Switched to stable channel.');
387
- console.log('You are now using the npm-installed package.');
457
+ console.log('You are now using the canonical installed package.');
388
458
  console.log('');
389
- console.log('To update: npm install -g aiwg@latest');
459
+ console.log('To update: aiwg refresh --channel latest');
390
460
  console.log('To switch to edge: aiwg --use-main');
391
461
  }
392
462
 
@@ -415,6 +485,7 @@ function normalizeRepoUrl(repository) {
415
485
  export async function getVersionInfo() {
416
486
  const config = await loadConfig();
417
487
  const packageRoot = getPackageRoot();
488
+ const installation = inspectInstallation({ actualRoot: packageRoot, identity: config.installation });
418
489
 
419
490
  // Read package.json version
420
491
  const packageJsonPath = path.join(packageRoot, 'package.json');
@@ -441,6 +512,7 @@ export async function getVersionInfo() {
441
512
  version,
442
513
  channel,
443
514
  packageRoot,
515
+ installation,
444
516
  devMode: config.devMode || false,
445
517
  // Public-facing URLs — single source of truth is package.json, so user-visible
446
518
  // stamps/links never hardcode the internal build origin. The published package
@@ -480,7 +552,7 @@ export async function updateEdge() {
480
552
  const config = await loadConfig();
481
553
 
482
554
  if (config.channel !== 'edge') {
483
- console.log('Not in edge channel. Use npm install -g aiwg@latest for stable channel.');
555
+ console.log('Not in edge channel. Use `aiwg update` for the canonical installed channel.');
484
556
  return;
485
557
  }
486
558
 
@@ -63,12 +63,13 @@ import { costReportHandler } from './cost-report.js';
63
63
  import { evidenceHandler } from './evidence.js';
64
64
  import { artifactVerifyHandler } from './artifact-verify.js';
65
65
  import { outputModeHandler } from './output-mode.js';
66
+ import { installationHandler } from './installation.js';
66
67
  // Re-export individual handlers
67
68
  export {
68
69
  // Maintenance
69
70
  helpHandler, versionHandler, authHandler, doctorHandler, contextFirewallHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
70
71
  // Framework management
71
- useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler, artifactVerifyHandler, outputModeHandler,
72
+ useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler, artifactVerifyHandler, outputModeHandler, installationHandler,
72
73
  // Project
73
74
  newBundleHandler, quickrefHandler, newProjectHandler, sessionHandler, sessionsHandler,
74
75
  // Workspace
@@ -125,6 +126,7 @@ export const allHandlers = [
125
126
  contextFirewallHandler,
126
127
  updateHandler,
127
128
  refreshHandler,
129
+ installationHandler,
128
130
  regenerateHandler,
129
131
  workspaceContextHandler,
130
132
  // Framework management
@@ -0,0 +1,79 @@
1
+ import { getPackageRoot } from '../../channel/manager.mjs';
2
+ import { adoptInstallation, inspectInstallation, loadInstallationIdentity, switchInstallation, } from '../../installation/manager.mjs';
3
+ function valueAfter(args, flag) {
4
+ const index = args.indexOf(flag);
5
+ return index >= 0 ? args[index + 1] : undefined;
6
+ }
7
+ function display(status, json) {
8
+ if (json) {
9
+ console.log(JSON.stringify(status, null, 2));
10
+ return;
11
+ }
12
+ console.log('\nCanonical AIWG Installation');
13
+ console.log('===========================');
14
+ console.log(`State: ${status.state}`);
15
+ console.log(`Canonical method: ${status.identity?.method ?? '(unrecorded)'}`);
16
+ console.log(`Canonical root: ${status.identity?.root ?? '(unrecorded)'}`);
17
+ console.log(`Manager: ${status.identity?.managerExecutable ?? '(internal)'}`);
18
+ console.log(`Update strategy: ${status.identity?.updateStrategy ?? '(unrecorded)'}`);
19
+ console.log(`Run mode: ${status.identity?.runMode ?? '(unrecorded)'}`);
20
+ console.log(`Release channel: ${status.identity?.channel ?? '(unrecorded)'}`);
21
+ console.log(`Actual method: ${status.actualMethod}`);
22
+ console.log(`Actual root: ${status.actualRoot}`);
23
+ if (status.drift.length > 0) {
24
+ console.log('Drift:');
25
+ for (const item of status.drift)
26
+ console.log(` - ${item}`);
27
+ }
28
+ console.log('');
29
+ }
30
+ export const installationHandler = {
31
+ id: 'installation',
32
+ name: 'Installation',
33
+ description: 'Inspect, adopt, or deliberately switch the canonical global installation',
34
+ category: 'maintenance',
35
+ aliases: [],
36
+ async execute(ctx) {
37
+ const [action = 'show'] = ctx.args;
38
+ const json = ctx.args.includes('--json');
39
+ const actualRoot = getPackageRoot();
40
+ const common = {
41
+ actualRoot,
42
+ configDir: valueAfter(ctx.args, '--config-dir'),
43
+ managerExecutable: valueAfter(ctx.args, '--manager'),
44
+ channel: valueAfter(ctx.args, '--channel'),
45
+ };
46
+ if (action === 'show') {
47
+ const identity = loadInstallationIdentity({ ...common, createIfMissing: true });
48
+ display(inspectInstallation({ ...common, identity, probeManager: true }), json);
49
+ return { exitCode: 0 };
50
+ }
51
+ if (action === 'adopt') {
52
+ const method = valueAfter(ctx.args, '--method');
53
+ const status = adoptInstallation({
54
+ ...common,
55
+ method,
56
+ runMode: valueAfter(ctx.args, '--run-mode'),
57
+ });
58
+ display(status, json);
59
+ return { exitCode: 0 };
60
+ }
61
+ if (action === 'switch') {
62
+ const root = valueAfter(ctx.args, '--root');
63
+ const method = valueAfter(ctx.args, '--method');
64
+ if (!root || !method) {
65
+ return { exitCode: 2, message: 'Usage: aiwg installation switch --root <path> --method <npm|web|source> [--manager <absolute-path>]' };
66
+ }
67
+ const status = switchInstallation({
68
+ ...common,
69
+ root,
70
+ method,
71
+ runMode: valueAfter(ctx.args, '--run-mode'),
72
+ });
73
+ display(status, json);
74
+ return { exitCode: 0 };
75
+ }
76
+ return { exitCode: 2, message: 'Usage: aiwg installation <show|adopt|switch> [options]' };
77
+ },
78
+ };
79
+ //# sourceMappingURL=installation.js.map
@@ -205,6 +205,7 @@ export const refreshHandler = {
205
205
  if (!quiet)
206
206
  ui.info(dryRun ? 'Would refresh remote packages...' : 'Refreshing remote packages...');
207
207
  const deploymentFailures = [];
208
+ let updateFailure = null;
208
209
  if (!dryRun) {
209
210
  try {
210
211
  const refreshed = await refreshAllPackages();
@@ -242,8 +243,10 @@ export const refreshHandler = {
242
243
  ui.success('Package up to date');
243
244
  }
244
245
  else {
245
- if (!quiet)
246
- ui.warn('Update check returned non-zero (may already be current)');
246
+ updateFailure = { exitCode: updateResult.exitCode };
247
+ if (!quiet) {
248
+ ui.warn('Installation update failed; continuing with re-deployment. Run `aiwg installation show` for canonical-install diagnostics.');
249
+ }
247
250
  }
248
251
  }
249
252
  }
@@ -463,6 +466,7 @@ export const refreshHandler = {
463
466
  channel: channel || undefined,
464
467
  staleAgentRemovals,
465
468
  deploymentFailures,
469
+ updateFailure,
466
470
  });
467
471
  console.log(output);
468
472
  }
@@ -10,6 +10,8 @@
10
10
  */
11
11
  import path from 'path';
12
12
  import { AiwgError, EXIT_CODES, handlerResultFromError } from '../errors.js';
13
+ import { getPackageRoot } from '../../channel/manager.mjs';
14
+ import { inspectInstallation } from '../../installation/manager.mjs';
13
15
  function isMissingRuntimeCatalogError(error) {
14
16
  return error instanceof Error && error.message.includes('No catalog found');
15
17
  }
@@ -237,8 +239,9 @@ async function handleRuntimeInfo(args, cwd = process.cwd()) {
237
239
  await discovery.discover();
238
240
  summary = await discovery.getSummary();
239
241
  }
242
+ const installation = inspectInstallation({ actualRoot: getPackageRoot() });
240
243
  if (hasJson) {
241
- console.log(JSON.stringify(summary, null, 2));
244
+ console.log(JSON.stringify({ ...summary, installation }, null, 2));
242
245
  }
243
246
  else {
244
247
  console.log(`\nRuntime Environment Summary`);
@@ -256,6 +259,11 @@ async function handleRuntimeInfo(args, cwd = process.cwd()) {
256
259
  console.log(`\nTotal: ${summary.totalTools} verified tools`);
257
260
  console.log(`\nLast Discovery: ${summary.lastDiscovery}`);
258
261
  console.log(`Catalog: ${summary.catalogPath}`);
262
+ console.log(`\nAIWG Installation:`);
263
+ console.log(` Canonical: ${installation.identity?.method ?? 'unrecorded'} at ${installation.identity?.root ?? '(unrecorded)'}`);
264
+ console.log(` Actual: ${installation.actualMethod} at ${installation.actualRoot}`);
265
+ console.log(` Run mode: ${installation.identity?.runMode ?? '(unrecorded)'}`);
266
+ console.log(` State: ${installation.state}`);
259
267
  // Scheduler backend detection
260
268
  const { execSync } = await import('child_process');
261
269
  let schedulerBackend = 'external trigger required (system cron/systemd/CI)';
@@ -27,8 +27,14 @@ const pushSecretRegistry = new PushSecretRegistry();
27
27
  const webhookIdempotency = new IdempotencyCache();
28
28
  const DEFAULT_PORT = 7337;
29
29
  const DEFAULT_HOST = '127.0.0.1';
30
+ function readA2AProtocolPolicy(value) {
31
+ const policy = value ?? '0.3';
32
+ if (policy === '0.3' || policy === '1.0' || policy === 'auto')
33
+ return policy;
34
+ throw new Error(`AIWG_A2A_PROTOCOL_POLICY must be 0.3, 1.0, or auto (received '${policy}')`);
35
+ }
30
36
  /**
31
- * Parse --port, --bind, --no-open, --read-only flags from args
37
+ * Parse dashboard and A2A negotiation flags from args.
32
38
  */
33
39
  function parseServeArgs(args) {
34
40
  let port = DEFAULT_PORT;
@@ -36,6 +42,9 @@ function parseServeArgs(args) {
36
42
  let open = true;
37
43
  let readOnly = false;
38
44
  let sandbox = null;
45
+ let a2aProtocolPolicy;
46
+ let allowA2AProtocolFallback;
47
+ let allowLegacyExecutorFallback;
39
48
  for (let i = 0; i < args.length; i++) {
40
49
  const arg = args[i];
41
50
  if (arg === '--port' && args[i + 1]) {
@@ -58,8 +67,33 @@ function parseServeArgs(args) {
58
67
  else if (arg === '--read-only') {
59
68
  readOnly = true;
60
69
  }
70
+ else if (arg === '--a2a-protocol' && args[i + 1]) {
71
+ a2aProtocolPolicy = readA2AProtocolPolicy(args[i + 1]);
72
+ i++;
73
+ }
74
+ else if (arg === '--a2a-protocol-fallback') {
75
+ allowA2AProtocolFallback = true;
76
+ }
77
+ else if (arg === '--no-a2a-protocol-fallback') {
78
+ allowA2AProtocolFallback = false;
79
+ }
80
+ else if (arg === '--a2a-legacy-executor-fallback') {
81
+ allowLegacyExecutorFallback = true;
82
+ }
83
+ else if (arg === '--no-a2a-legacy-executor-fallback') {
84
+ allowLegacyExecutorFallback = false;
85
+ }
61
86
  }
62
- return { port, host, open, readOnly, sandbox };
87
+ return {
88
+ port,
89
+ host,
90
+ open,
91
+ readOnly,
92
+ sandbox,
93
+ ...(a2aProtocolPolicy ? { a2aProtocolPolicy } : {}),
94
+ ...(allowA2AProtocolFallback !== undefined ? { allowA2AProtocolFallback } : {}),
95
+ ...(allowLegacyExecutorFallback !== undefined ? { allowLegacyExecutorFallback } : {}),
96
+ };
63
97
  }
64
98
  // ============================================================
65
99
  // WebSocket routing (#851)
@@ -499,6 +533,13 @@ async function setupWebSockets(httpServer, readOnly) {
499
533
  * @see #1277 — moves the integration suite off spawn-based testing
500
534
  */
501
535
  export async function startServer(opts) {
536
+ const configuredA2AProtocolPolicy = opts.a2aProtocolPolicy
537
+ ?? readA2AProtocolPolicy(process.env['AIWG_A2A_PROTOCOL_POLICY']);
538
+ const configuredA2AProtocolFallback = opts.allowA2AProtocolFallback
539
+ ?? process.env['AIWG_A2A_PROTOCOL_FALLBACK'] === 'true';
540
+ const configuredLegacyExecutorFallback = configuredA2AProtocolPolicy !== '1.0'
541
+ && (opts.allowLegacyExecutorFallback
542
+ ?? process.env['AIWG_A2A_LEGACY_EXECUTOR_FALLBACK'] !== 'false');
502
543
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
503
544
  let honoMod;
504
545
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -760,8 +801,32 @@ export async function startServer(opts) {
760
801
  let a2aInstanceId;
761
802
  let dispatchPath = 'v2';
762
803
  let a2aTask = undefined;
804
+ let a2aProtocolVersion;
805
+ let a2aInterface;
806
+ let a2aFallbackReason;
807
+ const a2aProtocolPolicy = configuredA2AProtocolPolicy;
763
808
  try {
764
809
  const result = await routeDispatch(executor, payload, {
810
+ a2aProtocolPolicy,
811
+ allowA2AProtocolFallback: configuredA2AProtocolFallback,
812
+ allowLegacyExecutorFallback: configuredLegacyExecutorFallback,
813
+ onA2AProtocolSelection: (info) => {
814
+ telemetryStore.ingest(createEvent('a2a.protocol.selected', sessionId, {
815
+ selected_version: info.selected,
816
+ policy: info.policy,
817
+ ...(info.interface ? {
818
+ protocol_binding: info.interface.protocolBinding,
819
+ interface_url: info.interface.url,
820
+ } : {}),
821
+ }, missionId));
822
+ },
823
+ onA2AProtocolFallback: (info) => {
824
+ telemetryStore.ingest(createEvent('a2a.protocol.fallback', sessionId, {
825
+ from_version: info.from,
826
+ to_version: info.to,
827
+ reason: info.reason,
828
+ }, missionId));
829
+ },
765
830
  onV1Fallback: (info) => {
766
831
  logServeWarn('dispatch', `v1 fallback for executor ${info.executorId}: ${info.reason}`);
767
832
  telemetryStore.ingest(createEvent('v1.dispatch.fallback', sessionId, info, missionId));
@@ -775,6 +840,9 @@ export async function startServer(opts) {
775
840
  dispatchPath = result.dispatchPath;
776
841
  a2aInstanceId = result.a2aInstanceId;
777
842
  a2aTask = result.task;
843
+ a2aProtocolVersion = result.a2aProtocolVersion;
844
+ a2aInterface = result.a2aInterface;
845
+ a2aFallbackReason = result.a2aFallbackReason;
778
846
  if (result.estimatedStart)
779
847
  estimatedStart = result.estimatedStart;
780
848
  }
@@ -796,12 +864,22 @@ export async function startServer(opts) {
796
864
  return c.json({ error: errorTag, detail: msg }, status);
797
865
  }
798
866
  // 4. Record the mission and emit telemetry
867
+ if (a2aProtocolVersion && a2aInterface) {
868
+ executorRegistry.recordA2AProtocolSelection(executor.executorId, {
869
+ policy: a2aProtocolPolicy,
870
+ selectedVersion: a2aProtocolVersion,
871
+ interface: a2aInterface,
872
+ ...(a2aFallbackReason ? { fallbackReason: a2aFallbackReason } : {}),
873
+ });
874
+ }
799
875
  executorRegistry.assignMission(missionId, executor.executorId);
800
876
  if (dispatchPath === 'v2' && a2aTask && a2aInstanceId) {
801
877
  void observeA2ATerminalState(executorRegistry, executor, missionId, a2aInstanceId, a2aTask, {
802
878
  onError: (err) => {
803
879
  logServeWarn('dispatch', `A2A terminal observer failed for mission ${missionId}: ${err.message ?? String(err)}`);
804
880
  },
881
+ ...(a2aProtocolVersion ? { protocolVersion: a2aProtocolVersion } : {}),
882
+ ...(a2aInterface ? { selectedInterface: a2aInterface } : {}),
805
883
  });
806
884
  }
807
885
  telemetryStore.ingest(createEvent('mission.dispatch', sessionId, {
@@ -809,6 +887,8 @@ export async function startServer(opts) {
809
887
  executorId: executor.executorId,
810
888
  objective: payload.objective,
811
889
  completion: payload.completion,
890
+ ...(a2aProtocolVersion ? { a2a_protocol_version: a2aProtocolVersion } : {}),
891
+ ...(a2aFallbackReason ? { a2a_fallback_reason: a2aFallbackReason } : {}),
812
892
  }, missionId));
813
893
  // 5. Return 202 Accepted
814
894
  const dispatchResp = {
@@ -819,6 +899,14 @@ export async function startServer(opts) {
819
899
  };
820
900
  if (a2aInstanceId)
821
901
  dispatchResp.a2a_instance_id = a2aInstanceId;
902
+ if (a2aProtocolVersion)
903
+ dispatchResp.a2a_protocol_version = a2aProtocolVersion;
904
+ if (a2aInterface) {
905
+ dispatchResp.a2a_protocol_binding = a2aInterface.protocolBinding;
906
+ dispatchResp.a2a_interface_url = a2aInterface.url;
907
+ }
908
+ if (a2aFallbackReason)
909
+ dispatchResp.a2a_fallback_reason = a2aFallbackReason;
822
910
  if (estimatedStart)
823
911
  dispatchResp.estimated_start = estimatedStart;
824
912
  return c.json(dispatchResp, 202);
@@ -947,6 +1035,7 @@ export async function startServer(opts) {
947
1035
  const result = await handleWebhook(configId, bodyBuf, signature, eventId, {
948
1036
  registry: pushSecretRegistry,
949
1037
  idempotency: webhookIdempotency,
1038
+ contentType: c.req.header('content-type') ?? undefined,
950
1039
  route: async (entry, event) => {
951
1040
  // Append to mission recentEvents via a synthesized envelope.
952
1041
  // The 'mission.webhook' event type falls through the registry's
@@ -990,11 +1079,17 @@ export async function startServer(opts) {
990
1079
  if (!p.secret || typeof p.secret !== 'string' || p.secret.length < 16) {
991
1080
  return c.json({ error: 'secret is required and must be ≥16 chars' }, 400);
992
1081
  }
1082
+ if (p.protocolVersion === '1.0' && !p.taskId) {
1083
+ return c.json({ error: 'taskId is required for A2A 1.0 push ownership scope' }, 400);
1084
+ }
993
1085
  pushSecretRegistry.register({
994
1086
  configId: p.configId,
995
1087
  secret: p.secret,
996
1088
  ...(p.missionId ? { missionId: p.missionId } : {}),
997
1089
  ...(p.taskId ? { taskId: p.taskId } : {}),
1090
+ ...(p.contextId ? { contextId: p.contextId } : {}),
1091
+ ...(p.protocolVersion ? { protocolVersion: p.protocolVersion } : {}),
1092
+ ...(p.taskOwner ? { taskOwner: p.taskOwner } : {}),
998
1093
  ...(p.metadata ? { metadata: p.metadata } : {}),
999
1094
  });
1000
1095
  return c.json({ ok: true, configId: p.configId }, 201);
@@ -1749,10 +1844,18 @@ export const serveHandler = {
1749
1844
  category: 'project',
1750
1845
  aliases: [],
1751
1846
  async execute(ctx) {
1752
- const { port, host, open, readOnly } = parseServeArgs(ctx.args);
1847
+ const { port, host, open, readOnly, a2aProtocolPolicy, allowA2AProtocolFallback, allowLegacyExecutorFallback, } = parseServeArgs(ctx.args);
1753
1848
  let server;
1754
1849
  try {
1755
- server = await startServer({ port, host, readOnly, frameworkRoot: ctx.frameworkRoot });
1850
+ server = await startServer({
1851
+ port,
1852
+ host,
1853
+ readOnly,
1854
+ frameworkRoot: ctx.frameworkRoot,
1855
+ ...(a2aProtocolPolicy ? { a2aProtocolPolicy } : {}),
1856
+ ...(allowA2AProtocolFallback !== undefined ? { allowA2AProtocolFallback } : {}),
1857
+ ...(allowLegacyExecutorFallback !== undefined ? { allowLegacyExecutorFallback } : {}),
1858
+ });
1756
1859
  }
1757
1860
  catch (error) {
1758
1861
  const { handlerResultFromError } = await import('../errors.js');