@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.
- package/bin/aiwg.mjs +24 -2
- package/dist/src/a2a/agent-card.js +4 -1
- package/dist/src/a2a/client.js +148 -68
- package/dist/src/a2a/codecs.js +480 -0
- package/dist/src/a2a/events.js +226 -0
- package/dist/src/a2a/hitl-driver.js +8 -6
- package/dist/src/a2a/hitl.js +2 -1
- package/dist/src/a2a/http.js +85 -5
- package/dist/src/a2a/protocol.js +136 -0
- package/dist/src/a2a/types.js +4 -14
- package/dist/src/a2a/webhook.js +101 -4
- package/dist/src/audit/operator-decision.js +15 -1
- package/dist/src/channel/manager.mjs +89 -17
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/installation.js +79 -0
- package/dist/src/cli/handlers/refresh.js +4 -2
- package/dist/src/cli/handlers/runtime-info.js +9 -1
- package/dist/src/cli/handlers/serve.js +107 -4
- package/dist/src/cli/handlers/session.js +12 -26
- package/dist/src/cli/handlers/utilities.js +4 -0
- package/dist/src/cli/handlers/version.js +4 -0
- package/dist/src/config/user-config-dir.mjs +29 -0
- package/dist/src/config/user-config.js +4 -22
- package/dist/src/extensions/commands/definitions.js +20 -1
- package/dist/src/features/catalog.js +2 -1
- package/dist/src/flow/graph-metadata.js +56 -0
- package/dist/src/installation/manager.mjs +243 -0
- package/dist/src/serve/a2a-terminal-observer.js +28 -5
- package/dist/src/serve/dispatch-router.js +32 -4
- package/dist/src/serve/executor-registry.js +29 -0
- package/dist/src/serve/mission-conductor.js +15 -1
- package/dist/src/serve/stack-adapters.js +2 -1
- package/dist/src/serve/telemetry.js +5 -1
- package/dist/src/update/checker.mjs +16 -15
- package/dist/src/update/notifier.mjs +8 -3
- package/dist/src/update/service.mjs +49 -5
- 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
|
|
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 {
|
|
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({
|
|
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
|
-
|
|
88
|
-
|
|
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
|
|
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
|
|
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
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|