@aiwg/cli 2026.8.17 → 2026.8.18

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 (37) 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/audit/operator-decision.js +15 -1
  13. package/dist/src/channel/manager.mjs +89 -17
  14. package/dist/src/cli/handlers/index.js +3 -1
  15. package/dist/src/cli/handlers/installation.js +79 -0
  16. package/dist/src/cli/handlers/refresh.js +4 -2
  17. package/dist/src/cli/handlers/runtime-info.js +9 -1
  18. package/dist/src/cli/handlers/serve.js +107 -4
  19. package/dist/src/cli/handlers/session.js +12 -26
  20. package/dist/src/cli/handlers/utilities.js +4 -0
  21. package/dist/src/cli/handlers/version.js +4 -0
  22. package/dist/src/config/user-config-dir.mjs +29 -0
  23. package/dist/src/config/user-config.js +4 -22
  24. package/dist/src/extensions/commands/definitions.js +20 -1
  25. package/dist/src/features/catalog.js +2 -1
  26. package/dist/src/flow/graph-metadata.js +56 -0
  27. package/dist/src/installation/manager.mjs +243 -0
  28. package/dist/src/serve/a2a-terminal-observer.js +28 -5
  29. package/dist/src/serve/dispatch-router.js +32 -4
  30. package/dist/src/serve/executor-registry.js +29 -0
  31. package/dist/src/serve/mission-conductor.js +15 -1
  32. package/dist/src/serve/stack-adapters.js +2 -1
  33. package/dist/src/serve/telemetry.js +5 -1
  34. package/dist/src/update/checker.mjs +16 -15
  35. package/dist/src/update/notifier.mjs +8 -3
  36. package/dist/src/update/service.mjs +49 -5
  37. package/package.json +1 -1
@@ -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');
@@ -21,6 +21,7 @@ import { spawnSync } from 'child_process';
21
21
  import { ensureRuntimeHome, writeProfileConfig, launchWithProfile } from '../../mcp/adapters/codex-runtime.js';
22
22
  import { getFrameworkRoot } from '../../channel/manager.mjs';
23
23
  import { forceUpdateCheck } from '../../update/checker.mjs';
24
+ import { updateInstallation } from '../../update/service.mjs';
24
25
  import { readAiwgConfig, getDeploymentSummary, VALID_PROVIDERS, } from '../../config/aiwg-config.js';
25
26
  import { getProviderConfig, isSpawnableProvider, PROVIDER_CONFIGS, } from '../agent-spawn.js';
26
27
  import { useHandler as useFrameworkHandler } from './use.js';
@@ -84,8 +85,11 @@ async function checkAndUpdateVersion(noRepair) {
84
85
  debug('cli:session:update', 'forceUpdateCheck failed', err);
85
86
  if (!noRepair) {
86
87
  console.log(' Version check failed — attempting sync...');
87
- const r = spawnSync('npm', ['install', '-g', 'aiwg@latest'], { stdio: 'inherit' });
88
- if (r.status !== 0) {
88
+ try {
89
+ await updateInstallation();
90
+ }
91
+ catch (updateError) {
92
+ debug('cli:session:update', 'canonical installation update failed', updateError);
89
93
  console.warn(' WARN Could not update aiwg — continuing with current version.');
90
94
  return false;
91
95
  }
@@ -103,10 +107,11 @@ function runDoctor(_frameworkRoot, cwd) {
103
107
  }
104
108
  /**
105
109
  * Attempt to repair a failed doctor result.
106
- * Strategy: `aiwg sync` first; if that fails, offer full reinstall.
110
+ * Strategy: `aiwg sync`; package repair remains bound to the canonical update
111
+ * strategy and never falls through to an arbitrary npm on PATH.
107
112
  * Returns true if repair succeeded (doctor now passes).
108
113
  */
109
- function repairInstallation(frameworkRoot, cwd, provider, installedFrameworks) {
114
+ function repairInstallation(frameworkRoot, cwd, provider) {
110
115
  // Strategy 1: sync (update + redeploy)
111
116
  console.log('\n Attempting auto-repair via `aiwg sync`...');
112
117
  const syncResult = spawnSync(process.execPath, [process.argv[1], 'sync'], { stdio: 'inherit', cwd });
@@ -118,29 +123,13 @@ function repairInstallation(frameworkRoot, cwd, provider, installedFrameworks) {
118
123
  return true;
119
124
  }
120
125
  }
121
- // Strategy 2: full reinstall
122
- console.log('\n Sync did not fully resolve the issue. Attempting full reinstall...');
123
- const reinstallResult = spawnSync('npm', ['install', '-g', 'aiwg@latest'], { stdio: 'inherit' });
124
- if (reinstallResult.status === 0 && installedFrameworks.length > 0) {
125
- // Redeploy all installed frameworks for this provider
126
- console.log(`\n Redeploying frameworks to ${provider}...`);
127
- for (const fw of installedFrameworks) {
128
- spawnSync(process.execPath, [process.argv[1], 'use', fw, '--provider', provider], { stdio: 'inherit', cwd });
129
- }
130
- // Final doctor check
131
- const finalOk = runDoctor(frameworkRoot, cwd);
132
- if (finalOk) {
133
- console.log(' OK Full reinstall + redeploy succeeded.');
134
- return true;
135
- }
136
- }
137
126
  // Could not auto-repair
138
127
  console.log(`
139
128
  ✗ Auto-repair could not resolve all issues.
140
129
 
141
130
  Manual options:
142
- aiwg sync sync and redeploy
143
- npm install -g aiwg@latest — reinstall package
131
+ aiwg installation show inspect canonical install drift
132
+ aiwg refresh — update and redeploy canonically
144
133
  aiwg use all --provider ${provider.padEnd(10)} — redeploy all frameworks
145
134
 
146
135
  Report this issue:
@@ -291,10 +280,7 @@ export const sessionHandler = {
291
280
  console.log('\n Running health checks...');
292
281
  const doctorOk = runDoctor(frameworkRoot, cwd);
293
282
  if (!doctorOk && !noRepair) {
294
- // Read installed frameworks before attempting repair (needed for redeploy)
295
- const config = await readAiwgConfig(cwd);
296
- const installedFrameworks = Object.keys(config?.installed ?? {});
297
- const repaired = repairInstallation(frameworkRoot, cwd, provider, installedFrameworks);
283
+ const repaired = repairInstallation(frameworkRoot, cwd, provider);
298
284
  if (!repaired) {
299
285
  // Repair failed — still continue (user was already informed)
300
286
  }
@@ -777,6 +777,10 @@ export const updateHandler = {
777
777
  console.log(`${update.message}\n`);
778
778
  }
779
779
  catch (error) {
780
+ if (error.code === 'AIWG_INSTALLATION_DRIFT'
781
+ || error.code === 'AIWG_INSTALLATION_INVALID') {
782
+ return { exitCode: 78, message: error instanceof Error ? error.message : String(error) };
783
+ }
780
784
  console.error(`Warning: Update check failed: ${error instanceof Error ? error.message : String(error)}`);
781
785
  console.log('Continuing with re-deployment...\n');
782
786
  }
@@ -53,6 +53,7 @@ function collectFingerprint(versionInfo) {
53
53
  logFile: loggerInfo.logFile,
54
54
  },
55
55
  invocation_id: loggerInfo.provenance.invocation_id,
56
+ installation: versionInfo.installation,
56
57
  };
57
58
  if (versionInfo.gitHash) {
58
59
  fp.git = {
@@ -115,6 +116,9 @@ async function displayVersion(opts) {
115
116
  ui.dim(` path: ${fp.packageRoot}`);
116
117
  }
117
118
  ui.dim(` channel: ${fp.channel}`);
119
+ ui.dim(` install: ${fp.installation.identity?.method ?? 'unrecorded'} (${fp.installation.state})`);
120
+ ui.dim(` canonical: ${fp.installation.identity?.root ?? '(unrecorded)'}`);
121
+ ui.dim(` actual: ${fp.installation.actualRoot}`);
118
122
  ui.dim(` node: ${fp.node}`);
119
123
  ui.dim(` platform: ${fp.platform.os} ${fp.platform.arch} (${fp.platform.release})`);
120
124
  ui.dim(` tty: stdin=${fp.tty.stdin} stdout=${fp.tty.stdout} stderr=${fp.tty.stderr}`);
@@ -0,0 +1,29 @@
1
+ import { existsSync } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ /**
6
+ * Resolve AIWG's global, provider-neutral user configuration directory.
7
+ *
8
+ * Contract: explicit override > AIWG_CONFIG > existing ~/.aiwg > existing
9
+ * ~/.config/aiwg > ~/.aiwg. Keeping this in a dependency-free ESM module lets
10
+ * the launcher, channel manager, updater, and TypeScript config API share the
11
+ * exact same resolution rules.
12
+ */
13
+ export function resolveUserConfigDir(options = {}) {
14
+ if (options.configDir) return path.resolve(options.configDir);
15
+ const env = options.env ?? process.env;
16
+ if (env.AIWG_CONFIG) return path.resolve(env.AIWG_CONFIG);
17
+
18
+ const home = options.homeDir ?? os.homedir();
19
+ const legacy = path.join(home, '.aiwg');
20
+ const xdg = path.join(home, '.config', 'aiwg');
21
+ const pathExists = options.exists ?? existsSync;
22
+ if (pathExists(legacy)) return legacy;
23
+ if (pathExists(xdg)) return xdg;
24
+ return legacy;
25
+ }
26
+
27
+ export function userConfigFile(name, options = {}) {
28
+ return path.join(resolveUserConfigDir(options), name);
29
+ }
@@ -16,9 +16,9 @@
16
16
  * @implements #545
17
17
  */
18
18
  import { readFile, writeFile, mkdir, access } from 'fs/promises';
19
- import { resolve } from 'path';
20
- import { homedir } from 'os';
21
19
  import { existsSync } from 'fs';
20
+ import { resolve } from 'path';
21
+ import { resolveUserConfigDir } from './user-config-dir.mjs';
22
22
  /**
23
23
  * Known config files in the user config directory
24
24
  */
@@ -28,6 +28,7 @@ export const KNOWN_CONFIG_FILES = [
28
28
  { filename: 'ops.json', description: 'Ops workspace registry' },
29
29
  { filename: 'mcp-servers.json', description: 'MCP server registry (single source of truth)' },
30
30
  { filename: 'packages.yaml', description: 'Installed remote packages (aiwg install)' },
31
+ { filename: 'installation.json', description: 'Canonical global installation identity' },
31
32
  ];
32
33
  /**
33
34
  * Default user config values
@@ -60,26 +61,7 @@ export const DEFAULT_USER_CONFIG = {
60
61
  * 4. ~/.aiwg (default if neither exists)
61
62
  */
62
63
  export function resolveConfigDir(overridePath) {
63
- // 1. Explicit override from env or CLI flag
64
- const envOverride = process.env.AIWG_CONFIG;
65
- if (overridePath) {
66
- return resolve(overridePath);
67
- }
68
- if (envOverride) {
69
- return resolve(envOverride);
70
- }
71
- // 2. Check primary path: ~/.aiwg
72
- const primaryPath = resolve(homedir(), '.aiwg');
73
- if (existsSync(primaryPath)) {
74
- return primaryPath;
75
- }
76
- // 3. Check fallback path: ~/.config/aiwg
77
- const fallbackPath = resolve(homedir(), '.config/aiwg');
78
- if (existsSync(fallbackPath)) {
79
- return fallbackPath;
80
- }
81
- // 4. Default to primary if neither exists
82
- return primaryPath;
64
+ return resolveUserConfigDir({ configDir: overridePath });
83
65
  }
84
66
  /**
85
67
  * User-level configuration manager
@@ -164,6 +164,24 @@ export const updateCommand = {
164
164
  },
165
165
  },
166
166
  };
167
+ export const installationCommand = {
168
+ id: 'installation',
169
+ type: 'command',
170
+ name: 'Installation Identity',
171
+ description: 'Inspect, adopt, or deliberately switch the canonical global AIWG installation',
172
+ version: '1.0.0',
173
+ capabilities: ['cli', 'installation', 'update', 'diagnostics', 'recovery'],
174
+ keywords: ['installation', 'canonical', 'adopt', 'switch', 'package-manager', 'drift'],
175
+ category: 'maintenance',
176
+ platforms: { claude: 'full', generic: 'full' },
177
+ deployment: { pathTemplate: '.{platform}/commands/{id}.md', core: true },
178
+ metadata: {
179
+ type: 'command',
180
+ template: 'utility',
181
+ argumentHint: '<show|adopt|switch> [--root <path>] [--method <npm|web|source>] [--manager <absolute-path>] [--json]',
182
+ allowedTools: ['Read', 'Write'],
183
+ },
184
+ };
167
185
  // Renamed from `refreshCommand` as part of #694 (avoid collision with git sync
168
186
  // semantics) and re-linked to `refreshHandler` in #919. Users who type
169
187
  // `aiwg sync` still reach this handler via its 'sync' alias and see a
@@ -912,7 +930,7 @@ export const serveCommand = {
912
930
  triggerPhrases: ['serve dashboard', 'start server', 'open dashboard', 'aiwg serve'],
913
931
  commandHint: {
914
932
  template: 'utility',
915
- argumentHint: '[--port <n>] [--bind <host>] [--no-open] [--read-only]',
933
+ argumentHint: '[--port <n>] [--bind <host>] [--no-open] [--read-only] [--a2a-protocol <0.3|1.0|auto>] [--a2a-protocol-fallback] [--no-a2a-legacy-executor-fallback]',
916
934
  allowedTools: ['Bash'],
917
935
  },
918
936
  },
@@ -3569,6 +3587,7 @@ export const commandDefinitions = [
3569
3587
  doctorCommand,
3570
3588
  contextFirewallCommand,
3571
3589
  updateCommand,
3590
+ installationCommand,
3572
3591
  refreshCommand,
3573
3592
  regenerateCommand,
3574
3593
  workspaceContextCommand,
@@ -72,7 +72,7 @@ export const FEATURE_CATALOG = [
72
72
  },
73
73
  {
74
74
  name: 'graph',
75
- description: 'Graphology backend for rich artifact traversal and graph operators',
75
+ description: 'Graphology backend for artifact-index traversal only; not Flow graph execution',
76
76
  packages: ['graphology', 'graphology-operators', 'graphology-traversal'],
77
77
  packageSpecs: {
78
78
  graphology: '0.26.0',
@@ -82,6 +82,7 @@ export const FEATURE_CATALOG = [
82
82
  enables: [
83
83
  'index.graphBackend: graphology',
84
84
  'in-memory attributed graph traversal and operator workflows',
85
+ 'artifact graph data operations (use graph-pattern addon for Flow execution graphs)',
85
86
  ],
86
87
  cost: '~2 MB — pure JS, no native deps',
87
88
  },
@@ -0,0 +1,56 @@
1
+ /** A2A/telemetry metadata key reserved for the optional Flow graph profile. */
2
+ export const AIWG_GRAPH_METADATA_KEY = 'aiwg.flow.graph';
3
+ function nonEmpty(value) {
4
+ return typeof value === 'string' && value.length > 0;
5
+ }
6
+ export function isGraphExecutionMetadata(value) {
7
+ if (!value || typeof value !== 'object' || Array.isArray(value))
8
+ return false;
9
+ const item = value;
10
+ return item.schemaVersion === 'graph.flow.aiwg.io/v1'
11
+ && nonEmpty(item.graphId)
12
+ && nonEmpty(item.graphVersion)
13
+ && nonEmpty(item.runId)
14
+ && nonEmpty(item.nodeId)
15
+ && nonEmpty(item.nodeRunId);
16
+ }
17
+ export function graphMetadataRecord(value) {
18
+ return { [AIWG_GRAPH_METADATA_KEY]: value };
19
+ }
20
+ export function extractGraphMetadata(metadata) {
21
+ const value = metadata?.[AIWG_GRAPH_METADATA_KEY];
22
+ return isGraphExecutionMetadata(value) ? value : undefined;
23
+ }
24
+ /**
25
+ * Project graph metadata to an explicitly named audience. Public projection
26
+ * removes execution identifiers and decision evidence. Cockpit receives only
27
+ * the declared route-decision summary; arbitrary user/task context is never a
28
+ * member of this contract and is therefore dropped by construction.
29
+ */
30
+ export function projectGraphMetadata(value, audience) {
31
+ if (!isGraphExecutionMetadata(value))
32
+ throw new Error('Invalid graph execution metadata.');
33
+ if (audience === 'internal')
34
+ return structuredClone(value);
35
+ const common = {
36
+ schemaVersion: value.schemaVersion,
37
+ graphVersion: value.graphVersion,
38
+ nodeId: value.nodeId,
39
+ ...(value.edgeId ? { edgeId: value.edgeId } : {}),
40
+ ...(value.routeName ? { routeName: value.routeName } : {}),
41
+ ...(value.runtimeBinding ? { runtimeBinding: value.runtimeBinding } : {}),
42
+ ...(value.nodeState ? { nodeState: value.nodeState } : {}),
43
+ };
44
+ if (audience === 'public')
45
+ return common;
46
+ return {
47
+ ...common,
48
+ graphId: value.graphId,
49
+ runId: value.runId,
50
+ nodeRunId: value.nodeRunId,
51
+ ...(value.checkpointId ? { checkpointId: value.checkpointId } : {}),
52
+ ...(value.routeReason ? { routeReason: value.routeReason } : {}),
53
+ ...(value.routeEvidence === undefined ? {} : { routeEvidence: structuredClone(value.routeEvidence) }),
54
+ };
55
+ }
56
+ //# sourceMappingURL=graph-metadata.js.map