@pathbit/agentdevkit 0.0.19 → 0.0.21
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/.pbai4devs/scripts/preparar-ferramentas/SCRIPT.mjs +69 -15
- package/.pbai4devs/scripts/preparar-ferramentas/tests/TEST.mjs +77 -1
- package/.pbai4devs/tools/agent-browser/TOOL.json +8 -0
- package/CHANGELOG.md +10 -0
- package/docs/maintainers/maintenance.md +35 -2
- package/docs/specs/README.md +2 -0
- package/docs/specs/automatic-release-tags/SPEC.md +16 -0
- package/docs/specs/tool-release/SPEC.md +19 -0
- package/package.json +1 -1
|
@@ -49,6 +49,20 @@ export function validateToolConfig(name, value) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
+
if (value.optionalExecutables !== undefined) {
|
|
53
|
+
if (download.provider !== 'npm' || !value.optionalExecutables || typeof value.optionalExecutables !== 'object' || Array.isArray(value.optionalExecutables) || !Object.keys(value.optionalExecutables).length) fail(`Mapa de executaveis opcionais invalido: ${name}`);
|
|
54
|
+
for (const [platform, files] of Object.entries(value.optionalExecutables)) {
|
|
55
|
+
if (!value.platforms.includes(platform) || !Array.isArray(files) || !files.length) fail(`Plataforma de executaveis opcionais invalida: ${name}/${platform}`);
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
for (const item of files) {
|
|
58
|
+
if (!item || typeof item !== 'object' || Array.isArray(item) || Object.keys(item).sort().join(',') !== 'file,package'
|
|
59
|
+
|| !/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/.test(item.package || '') || seen.has(item.package)
|
|
60
|
+
|| typeof item.file !== 'string' || !item.file || item.file.includes('\0') || item.file.includes('\\') || item.file.includes(':')
|
|
61
|
+
|| item.file.split('/').some(part => !part || part === '.' || part === '..')) fail(`Executavel opcional deve ser relativo ao pacote: ${name}`);
|
|
62
|
+
seen.add(item.package);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
52
66
|
if (value.scripts?.entry !== 'SCRIPT.mjs' || Object.keys(value.scripts).length !== 1) fail(`Script invalido; use scripts.entry=SCRIPT.mjs: ${name}`);
|
|
53
67
|
if (value.nodeMinimum !== undefined) fail(`nodeMinimum foi substituido pelo requisito comum Node >=24: ${name}`);
|
|
54
68
|
const environmentKeys = new Set();
|
|
@@ -162,7 +176,7 @@ export function binaryInvocation(prefix, name, args = [], binaryName = name.spli
|
|
|
162
176
|
if (typeof bin !== 'string' || !bin || bin.includes('\0')) fail(`Executavel ausente ou invalido: ${name}`);
|
|
163
177
|
const binary = resolve(base, bin); const traversal = relative(base, binary);
|
|
164
178
|
if (isAbsolute(traversal) || traversal === '..' || traversal.startsWith('..' + (process.platform === 'win32' ? '\\' : '/'))) fail('Executavel fora do pacote.');
|
|
165
|
-
const resolved = relative(prefix, realpathSync(binary));
|
|
179
|
+
const resolved = relative(realpathSync(prefix), realpathSync(binary));
|
|
166
180
|
if (isAbsolute(resolved) || resolved === '..' || resolved.startsWith('..' + (process.platform === 'win32' ? '\\' : '/'))) fail('Executavel fora da instalacao local.');
|
|
167
181
|
const header = Buffer.alloc(128); const handle = openSync(binary, 'r');
|
|
168
182
|
try { readSync(handle, header, 0, header.length, 0); } finally { closeSync(handle); }
|
|
@@ -203,25 +217,57 @@ function execute(prefix, entry, args, capture = false, input, environment, runti
|
|
|
203
217
|
return result.status ?? 1;
|
|
204
218
|
}
|
|
205
219
|
|
|
206
|
-
function prepareExecutables(prefix, entry, platform) {
|
|
207
|
-
const files = entry.executables?.[platform];
|
|
208
|
-
|
|
220
|
+
async function prepareExecutables(prefix, entry, platform, pkg, lock, fetchOptional) {
|
|
221
|
+
const files = entry.executables?.[platform] || [];
|
|
222
|
+
const optional = entry.optionalExecutables?.[platform] || [];
|
|
223
|
+
if (!files.length && !optional.length) return;
|
|
209
224
|
const within = (base, file) => { const child = relative(base, file); return !isAbsolute(child) && child !== '..' && !child.startsWith(`..${sep}`); };
|
|
210
225
|
const stage = realpathSync(prefix);
|
|
211
226
|
const packageRoot = realpathSync(join(prefix, 'node_modules', entry.download.package));
|
|
212
227
|
if (!within(stage, packageRoot)) fail(`Pacote fora do staging: ${entry.name}`);
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
const declared = optional.filter(item => Object.hasOwn(pkg.optionalDependencies || {}, item.package));
|
|
229
|
+
const targets = [];
|
|
230
|
+
const collect = (base, file, label) => {
|
|
231
|
+
const target = realpathSync(join(base, file));
|
|
232
|
+
if (!within(base, target)) fail(`Executavel fora do pacote: ${label}`);
|
|
216
233
|
const info = lstatSync(target);
|
|
217
|
-
if (!info.isFile()) fail(`Executavel nao e arquivo regular: ${
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
234
|
+
if (!info.isFile()) fail(`Executavel nao e arquivo regular: ${label}`);
|
|
235
|
+
targets.push({ target, mode: (info.mode & 0o777) | 0o111 });
|
|
236
|
+
};
|
|
237
|
+
// Older releases contain all of their binaries in the main package. Newer
|
|
238
|
+
// releases declare a host package; inspect every legacy file that exists so
|
|
239
|
+
// the upstream launcher cannot prefer an unchecked file over that package.
|
|
240
|
+
for (const file of files) {
|
|
241
|
+
let exists = false;
|
|
242
|
+
try { lstatSync(join(packageRoot, file)); exists = true; } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
243
|
+
if (exists || !declared.length) collect(packageRoot, file, `${entry.name}/${file}`);
|
|
244
|
+
}
|
|
245
|
+
if (declared.length) {
|
|
246
|
+
let installed = 0;
|
|
247
|
+
for (const item of declared) {
|
|
248
|
+
const version = pkg.optionalDependencies[item.package];
|
|
249
|
+
if (!versionPattern.test(version || '')) fail(`Versao de executavel opcional invalida: ${item.package}`);
|
|
250
|
+
const directory = sourcePath(prefix, join('node_modules', item.package));
|
|
251
|
+
try { lstatSync(directory); } catch (error) { if (error.code === 'ENOENT') continue; throw error; }
|
|
252
|
+
const optionalRoot = realpathSync(directory);
|
|
253
|
+
if (!within(realpathSync(join(prefix, 'node_modules')), optionalRoot)) fail(`Pacote opcional fora de node_modules: ${item.package}`);
|
|
254
|
+
const manifest = json(join(optionalRoot, 'package.json'));
|
|
255
|
+
const record = lock.packages?.[`node_modules/${item.package}`];
|
|
256
|
+
const official = await fetchOptional(item.package, version);
|
|
257
|
+
const tarball = new URL(official.dist?.tarball);
|
|
258
|
+
if (official.name !== item.package || official.version !== version || tarball.origin !== registry.slice(0, -1)
|
|
259
|
+
|| !decodeURIComponent(tarball.pathname).startsWith(`/${item.package}/-/`) || tarball.search || tarball.hash
|
|
260
|
+
|| !official.dist?.integrity?.startsWith('sha512-') || manifest.name !== item.package || manifest.version !== version
|
|
261
|
+
|| record?.version !== version || record.integrity !== official.dist.integrity || record.resolved !== official.dist.tarball) fail(`Pacote opcional instalado nao corresponde ao registro oficial: ${item.package}`);
|
|
262
|
+
collect(optionalRoot, item.file, `${item.package}/${item.file}`); installed++;
|
|
263
|
+
}
|
|
264
|
+
if (!installed) fail(`Executavel opcional ausente para ${entry.name}/${platform}; instale as dependencias opcionais.`);
|
|
265
|
+
}
|
|
266
|
+
if (process.platform !== 'win32') for (const { target, mode } of targets) chmodSync(target, mode);
|
|
221
267
|
}
|
|
222
268
|
|
|
223
269
|
export class ToolManager {
|
|
224
|
-
constructor({ root = projectRoot, catalog, fetchRelease, install, npm = npmCli, nodeVersion = process.versions.node, platform = process.platform, arch = process.arch } = {}) {
|
|
270
|
+
constructor({ root = projectRoot, catalog, fetchRelease, fetchOptional, install, npm = npmCli, nodeVersion = process.versions.node, platform = process.platform, arch = process.arch } = {}) {
|
|
225
271
|
this.root = resolve(root); this.home = join(this.root, '.pbai4devs/tools'); this.target = `${platform}-${arch}`;
|
|
226
272
|
this.nodeVersion = nodeVersion; this.npm = npm;
|
|
227
273
|
this.catalog = catalog ?? loadCatalog(this.root);
|
|
@@ -232,11 +278,19 @@ export class ToolManager {
|
|
|
232
278
|
if (!response.ok) fail(`Registro indisponivel: HTTP ${response.status}`);
|
|
233
279
|
return response.json();
|
|
234
280
|
});
|
|
281
|
+
this.fetchOptional = fetchOptional || (async (name, version) => {
|
|
282
|
+
const response = await fetch(`${registry}${encodeURIComponent(name)}/${version}`, { signal: AbortSignal.timeout(20000) });
|
|
283
|
+
if (!response.ok) fail(`Registro indisponivel para ${name}: HTTP ${response.status}`);
|
|
284
|
+
return response.json();
|
|
285
|
+
});
|
|
235
286
|
this.installPackage = install || (async (prefix, packageName, version, entry, runtime) => {
|
|
236
287
|
if (entry.download.provider === 'github-release') return installNative(prefix, entry, this.target);
|
|
237
288
|
const environment = toolEnvironment(entry, this.local(entry.name), runtime);
|
|
238
|
-
|
|
239
|
-
|
|
289
|
+
// npm resolves cwd but not --prefix, yielding invalid lock paths when
|
|
290
|
+
// ancestors are aliases such as macOS /var -> /private/var.
|
|
291
|
+
const npmPrefix = realpathSync(storagePath(this.root, prefix, 'directory'));
|
|
292
|
+
const result = spawnSync(runtime.file, [this.npm(runtime, environment), 'install', '--prefix', npmPrefix, '--cache', join(this.home, '.cache/npm'), '--registry', entry.download.registry, '--ignore-scripts', '--omit=dev', '--no-audit', '--no-fund', '--save-exact', `${packageName}@${version}`], {
|
|
293
|
+
cwd: npmPrefix, encoding: 'utf8', timeout: 180000,
|
|
240
294
|
env: { ...environment, npm_config_engine_strict: 'true', npm_config_update_notifier: 'false', PUPPETEER_SKIP_DOWNLOAD: 'true', PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' }
|
|
241
295
|
});
|
|
242
296
|
if (result.error || result.status !== 0) fail(`npm: ${result.error?.message || result.stderr?.trim() || result.status}`, result.status ?? 1);
|
|
@@ -438,7 +492,7 @@ export class ToolManager {
|
|
|
438
492
|
else {
|
|
439
493
|
const pkg = json(join(stage, 'node_modules', entry.download.package, 'package.json')); const lock = json(join(stage, 'package-lock.json'));
|
|
440
494
|
if (pkg.name !== entry.download.package || pkg.version !== release.version || lock.packages?.[`node_modules/${entry.download.package}`]?.integrity !== release.dist.integrity) fail('Pacote instalado nao corresponde ao registro.');
|
|
441
|
-
prepareExecutables(stage, entry, this.target);
|
|
495
|
+
await prepareExecutables(stage, entry, this.target, pkg, lock, this.fetchOptional);
|
|
442
496
|
}
|
|
443
497
|
execute(stage, entry, entry.probe, true, entry.probeInput, toolEnvironment(entry, local, runtime), runtime);
|
|
444
498
|
this.storage(name); storagePath(this.root, target, 'directory');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { test } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, readdirSync, existsSync, copyFileSync, symlinkSync, unlinkSync, chmodSync, statSync, renameSync } from 'node:fs';
|
|
3
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, readdirSync, existsSync, copyFileSync, symlinkSync, unlinkSync, chmodSync, statSync, renameSync, realpathSync } from 'node:fs';
|
|
4
4
|
import { resolve, join, dirname, delimiter } from 'node:path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
@@ -519,6 +519,26 @@ writeFileSync(join(prefix, 'npm-proof.json'), JSON.stringify({args,path:process.
|
|
|
519
519
|
assert.equal(JSON.parse(readFileSync(join(local, 'CURRENT.json'), 'utf8')).nodePath, process.execPath);
|
|
520
520
|
});
|
|
521
521
|
|
|
522
|
+
test('npm recebe staging canonico quando a raiz usa alias simbolico', async t => {
|
|
523
|
+
const config = tool(); const { root } = fixture(t, { 'test-tool': config });
|
|
524
|
+
const alias = join(root, 'project-alias');
|
|
525
|
+
if (!createSymlink(t, root, alias, 'dir')) return;
|
|
526
|
+
const npm = join(root, 'fake-npm.cjs');
|
|
527
|
+
writeFileSync(npm, `const { mkdirSync, writeFileSync, realpathSync } = require('node:fs');
|
|
528
|
+
const { join } = require('node:path');
|
|
529
|
+
${packageFiles.toString()}
|
|
530
|
+
const prefix = process.argv[process.argv.indexOf('--prefix') + 1];
|
|
531
|
+
if (prefix !== realpathSync(prefix) || process.cwd() !== prefix) process.exit(91);
|
|
532
|
+
packageFiles(prefix, 'test-tool', '1.0.0');
|
|
533
|
+
writeFileSync(join(prefix, 'canonical-proof.json'), JSON.stringify({prefix,cwd:process.cwd()}));
|
|
534
|
+
`);
|
|
535
|
+
const manager = new ToolManager({ root: alias, catalog: { 'test-tool': config }, npm: () => npm, fetchRelease: async () => release('1.0.0') });
|
|
536
|
+
const installed = await manager.install('test-tool');
|
|
537
|
+
const proof = JSON.parse(readFileSync(join(installed.prefix, 'canonical-proof.json'), 'utf8'));
|
|
538
|
+
assert.equal(proof.cwd, proof.prefix); assert.ok(proof.prefix.startsWith(realpathSync(root)));
|
|
539
|
+
assert.equal(manager.check('test-tool').version, '1.0.0');
|
|
540
|
+
});
|
|
541
|
+
|
|
522
542
|
test('setup padrao prepara todas as ferramentas compativeis e confirma cada instalacao', async t => {
|
|
523
543
|
const unsupported = { ...tool('unsupported-tool'), platforms: [process.platform === 'darwin' ? 'linux-x64' : 'darwin-arm64'] };
|
|
524
544
|
const { root, manager, state, local } = fixture(t, { 'test-tool': tool(), 'unsupported-tool': unsupported });
|
|
@@ -599,6 +619,62 @@ test('executables valida plataforma e caminhos relativos sem autorizar comandos
|
|
|
599
619
|
}
|
|
600
620
|
});
|
|
601
621
|
|
|
622
|
+
test('executaveis opcionais validam pacotes e caminhos sem permitir escapes', () => {
|
|
623
|
+
const target = platformKey(); const base = tool();
|
|
624
|
+
const good = { [target]: [{ package: '@owner/cli-host', file: 'bin/probe' }] };
|
|
625
|
+
assert.deepEqual(validateToolConfig('test-tool', { ...base, optionalExecutables: good }).optionalExecutables, good);
|
|
626
|
+
for (const item of [
|
|
627
|
+
{ package: '../outside', file: 'bin/probe' }, { package: '@owner/cli-host', file: '../outside' },
|
|
628
|
+
{ package: '@owner/cli-host', file: '/absolute' }, { package: '@owner/cli-host', file: 'C:\\outside' },
|
|
629
|
+
{ package: '@owner/cli-host', file: 'bin/probe', extra: true }
|
|
630
|
+
]) assert.throws(() => validateToolConfig('test-tool', { ...base, optionalExecutables: { [target]: [item] } }), /opcional|relativo/);
|
|
631
|
+
assert.throws(() => validateToolConfig('test-tool', { ...base, optionalExecutables: { [target]: [good[target][0], good[target][0]] } }), /relativo/);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test('atualizacao aceita layout opcional e preserva versao anterior se dependencia falhar', async t => {
|
|
635
|
+
const targets = process.platform === 'win32' ? [platformKey()] : [platformKey(), 'win32-x64'];
|
|
636
|
+
for (const target of targets) await t.test(target, async subtest => {
|
|
637
|
+
const name = '@owner/cli-host'; const isWindowsTarget = target.startsWith('win32-');
|
|
638
|
+
const config = { ...tool(), platforms: [target], ...(!isWindowsTarget ? { executables: { [target]: ['bin/legacy'] } } : {}), optionalExecutables: { [target]: [{ package: name, file: 'bin/probe' }] } };
|
|
639
|
+
const [platform, arch] = target.split('-');
|
|
640
|
+
const { manager, state, local, root } = fixture(subtest, { 'test-tool': config }, { platform, arch });
|
|
641
|
+
let scenario = 'legacy'; const outside = join(root, 'outside-native'); const probe = join(root, 'probe-executado'); writeFileSync(outside, 'untouched'); chmodSync(outside, 0o644);
|
|
642
|
+
const officialTarball = version => `https://registry.npmjs.org/${name}/-/cli-host-${version}.tgz`;
|
|
643
|
+
manager.fetchOptional = async (pkgName, version) => ({ name: pkgName, version, dist: { tarball: officialTarball(version), integrity: 'sha512-fixture' } });
|
|
644
|
+
manager.installPackage = (prefix, pkgName, version) => {
|
|
645
|
+
packageFiles(prefix, pkgName, version);
|
|
646
|
+
const base = join(prefix, 'node_modules', pkgName); mkdirSync(join(base, 'bin'));
|
|
647
|
+
if (scenario === 'legacy') { writeFileSync(join(base, 'bin/legacy'), 'legacy'); return; }
|
|
648
|
+
const manifestFile = join(base, 'package.json'); const manifest = JSON.parse(readFileSync(manifestFile));
|
|
649
|
+
manifest.optionalDependencies = { [name]: version }; writeFileSync(manifestFile, JSON.stringify(manifest));
|
|
650
|
+
writeFileSync(join(base, 'cli.cjs'), `require('node:fs').writeFileSync(${JSON.stringify(probe)}, 'executado'); console.log(${JSON.stringify(version)});`);
|
|
651
|
+
if (scenario === 'missing') return;
|
|
652
|
+
const optional = join(prefix, 'node_modules', name);
|
|
653
|
+
if (scenario === 'package-link') { mkdirSync(dirname(optional), { recursive: true }); symlinkSync(root, optional, 'dir'); return; }
|
|
654
|
+
mkdirSync(join(optional, 'bin'), { recursive: true });
|
|
655
|
+
writeFileSync(join(optional, 'package.json'), JSON.stringify({ name, version: scenario === 'version' ? '9.9.9' : version }));
|
|
656
|
+
const lockFile = join(prefix, 'package-lock.json'); const lock = JSON.parse(readFileSync(lockFile));
|
|
657
|
+
lock.packages[`node_modules/${name}`] = { version, integrity: scenario === 'integrity' ? 'sha512-other-registry' : 'sha512-fixture', resolved: scenario === 'registry' ? `https://example.invalid/${name}/-/cli-host-${version}.tgz` : officialTarball(version) };
|
|
658
|
+
writeFileSync(lockFile, JSON.stringify(lock));
|
|
659
|
+
if (scenario === 'escape') symlinkSync(outside, join(optional, 'bin/probe'));
|
|
660
|
+
else { writeFileSync(join(optional, 'bin/probe'), 'probe'); chmodSync(join(optional, 'bin/probe'), 0o644); }
|
|
661
|
+
if (scenario === 'legacy-escape') symlinkSync(outside, join(base, 'bin/legacy'));
|
|
662
|
+
};
|
|
663
|
+
await manager.install('test-tool'); const before = readFileSync(join(local, 'CURRENT.json'), 'utf8'); state.version = '2.0.0';
|
|
664
|
+
for (scenario of ['missing', 'version', 'integrity', 'registry', ...(process.platform === 'win32' ? [] : ['escape', 'package-link', ...(!isWindowsTarget ? ['legacy-escape'] : [])])]) {
|
|
665
|
+
await assert.rejects(manager.update('test-tool'), /ausente|corresponde|fora do pacote|nao pode ser um link/);
|
|
666
|
+
assert.equal(readFileSync(join(local, 'CURRENT.json'), 'utf8'), before);
|
|
667
|
+
assert.equal(manager.check('test-tool').version, '1.0.0');
|
|
668
|
+
assert.equal(existsSync(probe), false);
|
|
669
|
+
assert.ok(readdirSync(local).every(entry => !entry.startsWith('.stage-')));
|
|
670
|
+
}
|
|
671
|
+
if (process.platform !== 'win32') assert.equal(statSync(outside).mode & 0o777, 0o644);
|
|
672
|
+
scenario = 'optional'; const updated = await manager.update('test-tool');
|
|
673
|
+
assert.equal(updated.version, '2.0.0'); assert.equal(manager.check('test-tool').version, '2.0.0');
|
|
674
|
+
if (process.platform !== 'win32') assert.equal(statSync(join(updated.prefix, 'node_modules', name, 'bin/probe')).mode & 0o777, 0o755);
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
|
|
602
678
|
test('executables prepara somente binarios da plataforma no staging antes de executar probe real', { skip: process.platform === 'win32' }, async t => {
|
|
603
679
|
const other = process.platform === 'darwin' ? 'linux-x64' : 'darwin-arm64';
|
|
604
680
|
const config = { ...tool(), platforms: [platformKey(), other], executables: { [platformKey()]: ['native/probe with space'], [other]: ['native/unused'] } };
|
|
@@ -24,6 +24,14 @@
|
|
|
24
24
|
"linux-arm64": ["bin/agent-browser-linux-arm64", "bin/agent-browser-linux-musl-arm64"],
|
|
25
25
|
"linux-x64": ["bin/agent-browser-linux-x64", "bin/agent-browser-linux-musl-x64"]
|
|
26
26
|
},
|
|
27
|
+
"optionalExecutables": {
|
|
28
|
+
"darwin-arm64": [{"package": "@agent-browser/cli-darwin-arm64", "file": "bin/agent-browser"}],
|
|
29
|
+
"darwin-x64": [{"package": "@agent-browser/cli-darwin-x64", "file": "bin/agent-browser"}],
|
|
30
|
+
"linux-arm64": [{"package": "@agent-browser/cli-linux-arm64-gnu", "file": "bin/agent-browser"}, {"package": "@agent-browser/cli-linux-arm64-musl", "file": "bin/agent-browser"}],
|
|
31
|
+
"linux-x64": [{"package": "@agent-browser/cli-linux-x64-gnu", "file": "bin/agent-browser"}, {"package": "@agent-browser/cli-linux-x64-musl", "file": "bin/agent-browser"}],
|
|
32
|
+
"win32-arm64": [{"package": "@agent-browser/cli-win32-x64-msvc", "file": "bin/agent-browser.exe"}],
|
|
33
|
+
"win32-x64": [{"package": "@agent-browser/cli-win32-x64-msvc", "file": "bin/agent-browser.exe"}]
|
|
34
|
+
},
|
|
27
35
|
"scripts": {
|
|
28
36
|
"entry": "SCRIPT.mjs"
|
|
29
37
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ description: Alterações verificáveis por versão do AgentDevKit.
|
|
|
4
4
|
|
|
5
5
|
# Changelog
|
|
6
6
|
|
|
7
|
+
## 0.0.21 — 2026-11-13
|
|
8
|
+
|
|
9
|
+
- Criação automática da tag da versão após aprovação da matriz de CI.
|
|
10
|
+
- Publicação acionada pela automação com proteção contra colisões de tags.
|
|
11
|
+
|
|
12
|
+
## 0.0.20 — 2026-11-13
|
|
13
|
+
|
|
14
|
+
- Atualização da ferramenta agent-browser compatível com sua distribuição atual.
|
|
15
|
+
- Orientações para consultar e recuperar versões pelas tags publicadas.
|
|
16
|
+
|
|
7
17
|
## 0.0.19 — 2026-11-13
|
|
8
18
|
|
|
9
19
|
- Pastas dos clientes são ignoradas localmente por padrão, com exceções do projeto.
|
|
@@ -75,8 +75,15 @@ nada. Nunca versione o tarball ou seus relatórios.
|
|
|
75
75
|
|
|
76
76
|
1. Atualize versão e lockfile, changelog, exemplos e escopo da entrega.
|
|
77
77
|
2. Conclua testes, documentação e revisão independente.
|
|
78
|
-
3. Envie o commit à branch principal e
|
|
79
|
-
|
|
78
|
+
3. Envie o commit à branch principal e acompanhe o CI e a release.
|
|
79
|
+
|
|
80
|
+
Após toda a matriz de CI passar, a automação cria a tag `v<versão>` no
|
|
81
|
+
commit verificado e inicia o workflow de release. Um push sem mudança de versão
|
|
82
|
+
publicada não gera outra release. Uma tag existente não é movida: se a versão
|
|
83
|
+
mudou e a tag aponta para outro commit, a automação falha para revisão.
|
|
84
|
+
Versões inferiores às já registradas também não são publicadas automaticamente.
|
|
85
|
+
Se um push cancelar o CI anterior e a versão ainda não tiver tag, o próximo
|
|
86
|
+
CI aprovado da branch principal poderá publicar a versão no seu próprio commit.
|
|
80
87
|
|
|
81
88
|
O [workflow de release][release] localiza o CI aprovado de um push na branch
|
|
82
89
|
principal, confere o commit e baixa seu artifact por ID. Verifica procedência,
|
|
@@ -85,6 +92,29 @@ Também envia o mesmo pacote ao [npm público e GitHub Packages](packages.md),
|
|
|
85
92
|
confere a integridade nos dois destinos e testa versão, instalação, diagnóstico
|
|
86
93
|
e sincronização. O consumo pelo npm é anônimo; pelo GitHub, autenticado.
|
|
87
94
|
|
|
95
|
+
A tag é a origem da publicação: `v0.0.21`, a release `v0.0.21` e os pacotes
|
|
96
|
+
`0.0.21` apontam para a mesma entrega. A execução manual também exige a tag;
|
|
97
|
+
não crie outra tag para uma versão nem mova uma tag já publicada. Publicações
|
|
98
|
+
feitas fora desse fluxo não criam tags automaticamente.
|
|
99
|
+
|
|
100
|
+
Para consultar o código de uma versão sem trocar os arquivos atuais:
|
|
101
|
+
|
|
102
|
+
```console
|
|
103
|
+
git fetch --tags
|
|
104
|
+
git worktree add --detach ../AgentDevKit-v0.0.19 v0.0.19
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Em um projeto atendido, fixe a versão desejada do pacote e revise o plano antes
|
|
108
|
+
de aplicá-la:
|
|
109
|
+
|
|
110
|
+
```console
|
|
111
|
+
npx @pathbit/agentdevkit@0.0.19 update --dry-run --json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
O comando usa aquela versão do kit; não reverte automaticamente o histórico
|
|
115
|
+
Git nem arquivos próprios do projeto. Confira os conflitos e faça backup antes
|
|
116
|
+
de aplicar uma versão anterior.
|
|
117
|
+
|
|
88
118
|
Se o artifact expirou ou um job do CI falhou, use **Re-run all jobs** no CI
|
|
89
119
|
original do push e aguarde todos os jobs. **Re-run failed jobs** pode manter o
|
|
90
120
|
pacote de uma tentativa anterior, que a release não aceita. Depois reexecute
|
|
@@ -102,6 +132,9 @@ Se a release ainda não existe, use **Re-run failed jobs** ou
|
|
|
102
132
|
`gh workflow run release.yml --ref vX.Y.Z` com a tag real. A publicação prioriza
|
|
103
133
|
o secret `NPMJS_TOKEN` do repositório; sem ele, usa OIDC, conforme
|
|
104
134
|
[publicação](packages.md).
|
|
135
|
+
Se a criação da tag ou o acionamento falhar, consulte a execução **Automatic
|
|
136
|
+
release tag**. Uma tag no commit correto pode ser retomada sem movê-la; se ela
|
|
137
|
+
apontar para outro commit, investigue antes de qualquer publicação.
|
|
105
138
|
|
|
106
139
|
Se a release já existe e falta somente o pacote em um registro, use o modo
|
|
107
140
|
`package_only` descrito em [pacote npm](packages.md#publicar-pelo-actions).
|
package/docs/specs/README.md
CHANGED
|
@@ -26,6 +26,8 @@ description: Incrementos verificáveis do AgentDevKit.
|
|
|
26
26
|
- [Compatibilidade com projetos reais][oss]: projetos públicos e exemplos reproduzíveis.
|
|
27
27
|
- [Ignore do projeto](project-ignore/SPEC.md): preservar o arquivo da raiz.
|
|
28
28
|
- [Ignore de clientes](client-ignore/SPEC.md): padrão local e exceções do projeto.
|
|
29
|
+
- [Ferramenta e versões](tool-release/SPEC.md): atualização segura e tags de release.
|
|
30
|
+
- [Tags automáticas](automatic-release-tags/SPEC.md): publicação após CI aprovada.
|
|
29
31
|
|
|
30
32
|
Cada incremento vincula requisitos, critérios, testes e evidências locais.
|
|
31
33
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Criação e publicação seguras de tags de versões verificadas.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Tags automáticas de release
|
|
6
|
+
|
|
7
|
+
## Critérios de aceite
|
|
8
|
+
|
|
9
|
+
- AC-001: A CI cria `v<versão>` no commit da branch principal somente após
|
|
10
|
+
toda a matriz passar e inicia a publicação do mesmo commit.
|
|
11
|
+
- AC-002: Alterações sem nova versão não duplicam publicações; tags existentes
|
|
12
|
+
nunca são movidas e colisões ou regressões de versão impedem a publicação.
|
|
13
|
+
- AC-003: A release e os dois pacotes usam o mesmo artefato e a mesma versão
|
|
14
|
+
da tag; a publicação exige o CI completo aprovado.
|
|
15
|
+
- AC-004: Testes verificam eventos indevidos, falhas, colisões, retomada e
|
|
16
|
+
corridas sem criar tags ou publicar pacotes reais.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Atualização segura do agent-browser e recuperação por versão.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Atualização de ferramenta e versões
|
|
6
|
+
|
|
7
|
+
O agent-browser passou a distribuir executáveis em pacotes opcionais por
|
|
8
|
+
plataforma. A atualização deve funcionar sem perder instalações existentes.
|
|
9
|
+
|
|
10
|
+
## Critérios de aceite
|
|
11
|
+
|
|
12
|
+
- AC-001: O instalador aceita os layouts anterior e atual do agent-browser,
|
|
13
|
+
valida executáveis adequados à plataforma e comprova a versão instalada.
|
|
14
|
+
- AC-002: Caminhos inválidos ou incompletos são recusados antes da troca;
|
|
15
|
+
falhas preservam a instalação anterior e não atravessam links inseguros.
|
|
16
|
+
- AC-003: Cada release e pacote do fluxo aponta para a tag da mesma versão;
|
|
17
|
+
a documentação explica como consultar uma versão e simular seu uso.
|
|
18
|
+
- AC-004: Testes automatizados cobrem layouts e falhas, e a CI verifica Linux,
|
|
19
|
+
Windows e macOS na matriz configurada antes da publicação.
|