@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.
@@ -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
- if (!files || process.platform === 'win32') return;
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 targets = files.map(file => {
214
- const target = realpathSync(join(packageRoot, file));
215
- if (!within(packageRoot, target)) fail(`Executavel fora do pacote: ${entry.name}/${file}`);
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: ${entry.name}/${file}`);
218
- return { target, mode: (info.mode & 0o777) | 0o111 };
219
- });
220
- for (const { target, mode } of targets) chmodSync(target, mode);
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
- const result = spawnSync(runtime.file, [this.npm(runtime, environment), 'install', '--prefix', prefix, '--cache', join(this.home, '.cache/npm'), '--registry', entry.download.registry, '--ignore-scripts', '--omit=dev', '--no-audit', '--no-fund', '--save-exact', `${packageName}@${version}`], {
239
- cwd: prefix, encoding: 'utf8', timeout: 180000,
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
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pathbit/agentdevkit",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "Shared skills, specifications and workflows for AI coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",