@aiwg/cli 2026.8.10 → 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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
const MANAGED_MARKER_RE = /(?:^|\n)(?:#|<!--)\s*aiwg:managed\s+v?([^\s]+)\s+([^\s>]+)(?:\s*-->)?/;
|
|
4
5
|
/** Normalize provider-specific deployed filenames to the source agent id. */
|
|
@@ -12,6 +13,31 @@ export function parseManagedArtifactMarker(content) {
|
|
|
12
13
|
const match = MANAGED_MARKER_RE.exec(content);
|
|
13
14
|
return match ? { version: match[1], source: match[2] } : null;
|
|
14
15
|
}
|
|
16
|
+
/** Extract the developer-instruction body from a canonical Markdown agent. */
|
|
17
|
+
export function extractAgentInstructionBody(content) {
|
|
18
|
+
if (!content.startsWith('---'))
|
|
19
|
+
return content.trim();
|
|
20
|
+
const withoutFrontmatter = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
|
|
21
|
+
return withoutFrontmatter.trim();
|
|
22
|
+
}
|
|
23
|
+
function instructionHash(content) {
|
|
24
|
+
return createHash('sha256').update(content).digest('hex');
|
|
25
|
+
}
|
|
26
|
+
function extractDeployedInstructions(filename, content) {
|
|
27
|
+
if (!filename.toLowerCase().endsWith('.toml')) {
|
|
28
|
+
return extractAgentInstructionBody(content);
|
|
29
|
+
}
|
|
30
|
+
const match = content.match(/^developer_instructions\s*=\s*(.+)$/m);
|
|
31
|
+
if (!match)
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
const value = JSON.parse(match[1]);
|
|
35
|
+
return typeof value === 'string' ? value.trim() : null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
15
41
|
async function collectAgentSources(frameworkRoot, rootDir, inventory, allMarkdownFiles) {
|
|
16
42
|
let entries;
|
|
17
43
|
try {
|
|
@@ -31,8 +57,12 @@ async function collectAgentSources(frameworkRoot, rootDir, inventory, allMarkdow
|
|
|
31
57
|
if (!allMarkdownFiles && path.basename(rootDir) !== 'agents')
|
|
32
58
|
return;
|
|
33
59
|
let stat;
|
|
60
|
+
let content;
|
|
34
61
|
try {
|
|
35
|
-
stat = await
|
|
62
|
+
[stat, content] = await Promise.all([
|
|
63
|
+
fs.stat(absolute),
|
|
64
|
+
fs.readFile(absolute, 'utf8'),
|
|
65
|
+
]);
|
|
36
66
|
}
|
|
37
67
|
catch {
|
|
38
68
|
return;
|
|
@@ -42,6 +72,7 @@ async function collectAgentSources(frameworkRoot, rootDir, inventory, allMarkdow
|
|
|
42
72
|
sources.push({
|
|
43
73
|
path: path.relative(frameworkRoot, absolute),
|
|
44
74
|
size: stat.size,
|
|
75
|
+
instructionHash: instructionHash(extractAgentInstructionBody(content)),
|
|
45
76
|
});
|
|
46
77
|
inventory.set(name, sources);
|
|
47
78
|
}));
|
|
@@ -67,6 +98,11 @@ export function diagnoseOversizedAgent(filename, content, inventory, ceilingByte
|
|
|
67
98
|
const packaged = inventory.get(normalizeAgentArtifactName(filename)) ?? [];
|
|
68
99
|
if (packaged.some((source) => source.size > ceilingBytes))
|
|
69
100
|
return 'current-package';
|
|
101
|
+
const deployedInstructions = extractDeployedInstructions(filename, content);
|
|
102
|
+
if (deployedInstructions !== null
|
|
103
|
+
&& packaged.some((source) => source.instructionHash === instructionHash(deployedInstructions))) {
|
|
104
|
+
return 'current-package';
|
|
105
|
+
}
|
|
70
106
|
const marker = parseManagedArtifactMarker(content);
|
|
71
107
|
if (marker?.source === 'bundled')
|
|
72
108
|
return 'stale-deployment';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { GRAPH_CONFIGS, getProjectIndexRoot, loadGlobalGraphConfigs } from "./types.js";
|
|
5
6
|
import { buildAiwgFortemiIndexExport, } from "./browser-export.js";
|
|
6
7
|
import { loadGraphIndexFile } from "./index-reader.js";
|
|
@@ -25,7 +26,7 @@ function findPackageRoot(startDir) {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
function prebuiltDir(graph) {
|
|
28
|
-
const moduleDir = path.dirname(
|
|
29
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
29
30
|
const packageRoot = findPackageRoot(moduleDir);
|
|
30
31
|
if (!packageRoot)
|
|
31
32
|
return null;
|
|
@@ -215,17 +216,31 @@ export function getFortemiCorePrebuiltStatus(graph = "framework") {
|
|
|
215
216
|
reason: manifestReadReason,
|
|
216
217
|
};
|
|
217
218
|
}
|
|
219
|
+
const prebuiltManifest = manifest;
|
|
218
220
|
const exportExists = fs.existsSync(exportPath);
|
|
219
221
|
let reason = null;
|
|
220
222
|
if (exportExists) {
|
|
221
223
|
try {
|
|
222
224
|
const exportText = fs.readFileSync(exportPath, "utf-8");
|
|
223
225
|
const exported = JSON.parse(exportText);
|
|
224
|
-
if (
|
|
226
|
+
if (prebuiltManifest.schema_version !== "aiwg.fortemi.prebuilt.v1" ||
|
|
227
|
+
prebuiltManifest.backend !== "fortemi-core" ||
|
|
228
|
+
prebuiltManifest.graph !== graph ||
|
|
229
|
+
prebuiltManifest.export_path !== "aiwg-fortemi-index-v2.json" ||
|
|
230
|
+
prebuiltManifest.export_schema_version !== "aiwg.fortemi.index.export.v2") {
|
|
231
|
+
reason = "prebuilt manifest is incompatible with the requested graph";
|
|
232
|
+
}
|
|
233
|
+
else if (sha256(exportText) !== prebuiltManifest.export_checksum) {
|
|
225
234
|
reason = "prebuilt export checksum does not match manifest";
|
|
226
235
|
}
|
|
227
|
-
else if (exported.schema_version !==
|
|
228
|
-
reason = `prebuilt export schema '${exported.schema_version}' does not match manifest '${
|
|
236
|
+
else if (exported.schema_version !== prebuiltManifest.export_schema_version) {
|
|
237
|
+
reason = `prebuilt export schema '${exported.schema_version}' does not match manifest '${prebuiltManifest.export_schema_version}'`;
|
|
238
|
+
}
|
|
239
|
+
else if (exported.source?.graph !== graph) {
|
|
240
|
+
reason = `prebuilt export graph '${exported.source?.graph ?? "unknown"}' does not match requested graph '${graph}'`;
|
|
241
|
+
}
|
|
242
|
+
else if (!Array.isArray(exported.items) || exported.items.length !== prebuiltManifest.item_count) {
|
|
243
|
+
reason = "prebuilt export item count does not match manifest";
|
|
229
244
|
}
|
|
230
245
|
}
|
|
231
246
|
catch (err) {
|
|
@@ -241,10 +256,10 @@ export function getFortemiCorePrebuiltStatus(graph = "framework") {
|
|
|
241
256
|
exportPath,
|
|
242
257
|
built: exportExists,
|
|
243
258
|
stale: !exportExists || reason !== null,
|
|
244
|
-
itemCount:
|
|
245
|
-
exportChecksum:
|
|
246
|
-
generatedAt:
|
|
247
|
-
sourceIndexBuiltAt:
|
|
259
|
+
itemCount: prebuiltManifest.item_count,
|
|
260
|
+
exportChecksum: prebuiltManifest.export_checksum,
|
|
261
|
+
generatedAt: prebuiltManifest.generated_at,
|
|
262
|
+
sourceIndexBuiltAt: prebuiltManifest.source_index_built_at,
|
|
248
263
|
reason: !exportExists ? "prebuilt manifest exists but export file is missing" : reason,
|
|
249
264
|
};
|
|
250
265
|
}
|
|
@@ -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
|
-
|
|
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);
|
|
@@ -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();
|
|
@@ -259,13 +269,30 @@ export const refreshHandler = {
|
|
|
259
269
|
ui.dim(' No installed frameworks or addons to re-deploy');
|
|
260
270
|
}
|
|
261
271
|
for (const fw of frameworks) {
|
|
262
|
-
|
|
263
|
-
|
|
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
|
+
});
|
|
264
290
|
if (useResult.exitCode === 0) {
|
|
265
291
|
if (!quiet)
|
|
266
292
|
ui.success(`Deployed: ${fw}`);
|
|
267
293
|
}
|
|
268
294
|
else {
|
|
295
|
+
deploymentFailures.push(fw);
|
|
269
296
|
if (!quiet)
|
|
270
297
|
ui.warn(`Deploy issue: ${fw} (exit ${useResult.exitCode})`);
|
|
271
298
|
}
|
|
@@ -281,11 +308,10 @@ export const refreshHandler = {
|
|
|
281
308
|
}
|
|
282
309
|
}
|
|
283
310
|
// Step 4.25: Report planned project-local deploys (#1035).
|
|
284
|
-
// The
|
|
285
|
-
// this block surfaces
|
|
286
|
-
// 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.
|
|
287
313
|
try {
|
|
288
|
-
const plDiscovery = await discoverProjectLocalBundles(
|
|
314
|
+
const plDiscovery = await discoverProjectLocalBundles(ctx.cwd);
|
|
289
315
|
const plCount = plDiscovery.bundles.length;
|
|
290
316
|
if (plCount > 0) {
|
|
291
317
|
if (dryRun) {
|
|
@@ -312,11 +338,12 @@ export const refreshHandler = {
|
|
|
312
338
|
if (!quiet)
|
|
313
339
|
ui.info('Checking for stale deployments...');
|
|
314
340
|
let staleAgentRemovals = [];
|
|
315
|
-
if (!dryRun) {
|
|
341
|
+
if (!dryRun && deploymentFailures.length === 0) {
|
|
316
342
|
try {
|
|
317
343
|
staleAgentRemovals = await pruneStaleManagedAgentFiles({
|
|
318
344
|
projectRoot: ctx.cwd,
|
|
319
345
|
frameworkRoot,
|
|
346
|
+
provider: detectedProvider,
|
|
320
347
|
});
|
|
321
348
|
if (staleAgentRemovals.length > 0 && !quiet) {
|
|
322
349
|
const total = staleAgentRemovals.reduce((sum, item) => sum + item.paths.length, 0);
|
|
@@ -435,10 +462,17 @@ export const refreshHandler = {
|
|
|
435
462
|
skipUpdate,
|
|
436
463
|
channel: channel || undefined,
|
|
437
464
|
staleAgentRemovals,
|
|
465
|
+
deploymentFailures,
|
|
438
466
|
});
|
|
439
467
|
console.log(output);
|
|
440
468
|
}
|
|
441
|
-
|
|
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 };
|
|
442
476
|
},
|
|
443
477
|
};
|
|
444
478
|
//# sourceMappingURL=refresh.js.map
|
|
@@ -1673,6 +1673,20 @@ async function ensurePostDeployPhases(opts) {
|
|
|
1673
1673
|
// The shared verifier reports the index failure with stable remediation.
|
|
1674
1674
|
}
|
|
1675
1675
|
}
|
|
1676
|
+
// The Fortemi export is the default discovery backend. Rebuilding only the
|
|
1677
|
+
// source graph makes any existing export stale, so every successful use
|
|
1678
|
+
// must leave the shared cache synchronized (#142/#2103). This also
|
|
1679
|
+
// materializes a fresh-install cache without requiring a manual index sync.
|
|
1680
|
+
try {
|
|
1681
|
+
const { getFortemiCoreSyncStatus, syncFortemiCoreIndex, } = await import('../../artifacts/fortemi-core-sync.js');
|
|
1682
|
+
const status = getFortemiCoreSyncStatus(opts.frameworkRoot, 'framework');
|
|
1683
|
+
if (!status.built || status.stale) {
|
|
1684
|
+
syncFortemiCoreIndex(opts.frameworkRoot, { graph: 'framework' });
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
catch {
|
|
1688
|
+
// The shared verifier reports discovery failures with stable remediation.
|
|
1689
|
+
}
|
|
1676
1690
|
if (opts.args.includes('--no-context-files'))
|
|
1677
1691
|
return;
|
|
1678
1692
|
const paths = getProviderPaths(opts.provider);
|
|
@@ -3152,6 +3166,8 @@ export class UseHandler {
|
|
|
3152
3166
|
// project (#1217). The output index location is XDG-shared
|
|
3153
3167
|
// regardless of build cwd.
|
|
3154
3168
|
await buildIndex(aiwgRootForIndex, { graph: 'framework', explicit: false });
|
|
3169
|
+
const { syncFortemiCoreIndex } = await import('../../artifacts/fortemi-core-sync.js');
|
|
3170
|
+
syncFortemiCoreIndex(aiwgRootForIndex, { graph: 'framework' });
|
|
3155
3171
|
console.log = origLog;
|
|
3156
3172
|
const indexElapsedSec = ((Date.now() - indexStart) / 1000).toFixed(1);
|
|
3157
3173
|
ui.success(`Capability index ready (${indexElapsedSec}s).`);
|
package/package.json
CHANGED
|
@@ -180,8 +180,11 @@ const MANIFEST_FILENAME = '.aiwg-manifest.json';
|
|
|
180
180
|
*
|
|
181
181
|
* Idempotent — skips if either form of the marker is already present.
|
|
182
182
|
*/
|
|
183
|
-
export function addManagedMarker(content, version, source) {
|
|
183
|
+
export function addManagedMarker(content, version, source, style = 'markdown') {
|
|
184
184
|
if (MANAGED_MARKER_RE.test(content)) return content;
|
|
185
|
+
if (style === 'line-comment') {
|
|
186
|
+
return `# aiwg:managed v${version} ${source}\n${content}`;
|
|
187
|
+
}
|
|
185
188
|
// Frontmatter present → inject as YAML comment after the opening `---\n`.
|
|
186
189
|
if (content.startsWith('---\n')) {
|
|
187
190
|
return content.replace(
|
|
@@ -680,9 +683,18 @@ export function deployFiles(files, destDir, opts, transformFn) {
|
|
|
680
683
|
transformedContent = injectPlatformInContent(transformedContent, platformName);
|
|
681
684
|
}
|
|
682
685
|
|
|
683
|
-
// Add managed marker for
|
|
686
|
+
// Add managed marker for provider artifacts (#749). TOML accepts `#`
|
|
687
|
+
// comments, which lets doctor attribute transformed Codex agents even
|
|
688
|
+
// though their YAML frontmatter is removed during serialization.
|
|
684
689
|
if (base.endsWith('.md') || base.endsWith('.mdc')) {
|
|
685
690
|
transformedContent = addManagedMarker(transformedContent, deployVersion, deploySource);
|
|
691
|
+
} else if (base.endsWith('.toml')) {
|
|
692
|
+
transformedContent = addManagedMarker(
|
|
693
|
+
transformedContent,
|
|
694
|
+
deployVersion,
|
|
695
|
+
deploySource,
|
|
696
|
+
'line-comment',
|
|
697
|
+
);
|
|
686
698
|
}
|
|
687
699
|
|
|
688
700
|
// Compute content hash for sidecar comparison
|