@pathbit/agentdevkit 0.0.19 → 0.0.20
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 +5 -0
- package/docs/maintainers/maintenance.md +22 -0
- package/docs/specs/README.md +1 -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,11 @@ description: Alterações verificáveis por versão do AgentDevKit.
|
|
|
4
4
|
|
|
5
5
|
# Changelog
|
|
6
6
|
|
|
7
|
+
## 0.0.20 — 2026-11-13
|
|
8
|
+
|
|
9
|
+
- Atualização da ferramenta agent-browser compatível com sua distribuição atual.
|
|
10
|
+
- Orientações para consultar e recuperar versões pelas tags publicadas.
|
|
11
|
+
|
|
7
12
|
## 0.0.19 — 2026-11-13
|
|
8
13
|
|
|
9
14
|
- Pastas dos clientes são ignoradas localmente por padrão, com exceções do projeto.
|
|
@@ -85,6 +85,28 @@ Também envia o mesmo pacote ao [npm público e GitHub Packages](packages.md),
|
|
|
85
85
|
confere a integridade nos dois destinos e testa versão, instalação, diagnóstico
|
|
86
86
|
e sincronização. O consumo pelo npm é anônimo; pelo GitHub, autenticado.
|
|
87
87
|
|
|
88
|
+
A tag é a origem da publicação: `v0.0.19`, a release `v0.0.19` e os pacotes
|
|
89
|
+
`0.0.19` apontam para a mesma entrega. A execução manual também exige a tag;
|
|
90
|
+
não crie outra tag para uma versão nem mova uma tag já publicada.
|
|
91
|
+
|
|
92
|
+
Para consultar o código de uma versão sem trocar os arquivos atuais:
|
|
93
|
+
|
|
94
|
+
```console
|
|
95
|
+
git fetch --tags
|
|
96
|
+
git worktree add --detach ../AgentDevKit-v0.0.19 v0.0.19
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Em um projeto atendido, fixe a versão desejada do pacote e revise o plano antes
|
|
100
|
+
de aplicá-la:
|
|
101
|
+
|
|
102
|
+
```console
|
|
103
|
+
npx @pathbit/agentdevkit@0.0.19 update --dry-run --json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
O comando usa aquela versão do kit; não reverte automaticamente o histórico
|
|
107
|
+
Git nem arquivos próprios do projeto. Confira os conflitos e faça backup antes
|
|
108
|
+
de aplicar uma versão anterior.
|
|
109
|
+
|
|
88
110
|
Se o artifact expirou ou um job do CI falhou, use **Re-run all jobs** no CI
|
|
89
111
|
original do push e aguarde todos os jobs. **Re-run failed jobs** pode manter o
|
|
90
112
|
pacote de uma tentativa anterior, que a release não aceita. Depois reexecute
|
package/docs/specs/README.md
CHANGED
|
@@ -26,6 +26,7 @@ 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.
|
|
29
30
|
|
|
30
31
|
Cada incremento vincula requisitos, critérios, testes e evidências locais.
|
|
31
32
|
|
|
@@ -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.
|