agentic-workflow-manager 9.11.6 → 9.11.7
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/dist/src/commands/hooks/codex.js +41 -1
- package/dist/tests/commands/hooks/codex.test.js +84 -0
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -10,6 +10,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.awmCodexEntry = awmCodexEntry;
|
|
13
|
+
exports.isStaleAwmCodexEntry = isStaleAwmCodexEntry;
|
|
13
14
|
exports.installCodexHook = installCodexHook;
|
|
14
15
|
exports.computeCodexHookStatus = computeCodexHookStatus;
|
|
15
16
|
exports.uninstallCodexHook = uninstallCodexHook;
|
|
@@ -38,6 +39,44 @@ function awmCodexEntry(scriptsDir) {
|
|
|
38
39
|
}],
|
|
39
40
|
};
|
|
40
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Una entrada NUESTRA en `SessionStart` de Codex que ya no es la canonica (#184).
|
|
44
|
+
*
|
|
45
|
+
* Medido en una Mac real: `hooks.json` con dos entradas de AWM cuyos matchers se
|
|
46
|
+
* solapan en startup/clear/compact, o sea el hook corre dos veces por sesion y el
|
|
47
|
+
* contexto se inyecta dos veces. Ninguna de las dos vias existentes la alcanzaba:
|
|
48
|
+
* `isAwmCodexEntry` exige el matcher ACTUAL, e `isDeadAwmHookEntry` compara el
|
|
49
|
+
* basename del ejecutable contra 'session-start' cuando aca el ejecutable es
|
|
50
|
+
* `run-hook.cmd`. Y no es "muerta": el wrapper existe porque Claude Code si lo usa,
|
|
51
|
+
* asi que ninguna prueba de vivencia puede alcanzarla.
|
|
52
|
+
*
|
|
53
|
+
* Se reconoce por PROCEDENCIA y no-canonicidad, en dos formas:
|
|
54
|
+
*
|
|
55
|
+
* - `run-hook.cmd session-start`: el contrato de wrapper que escribe el adapter de
|
|
56
|
+
* Claude (claude.ts) y que este adapter declara no usar. En el archivo de Codex
|
|
57
|
+
* solo puede ser un resto de una version anterior.
|
|
58
|
+
* - nuestro propio script con el matcher viejo: misma ruta exacta que instalamos,
|
|
59
|
+
* pero el matcher cambio, asi que el instalador la ignoraba y AGREGABA otra.
|
|
60
|
+
*
|
|
61
|
+
* Deliberadamente fuera: una instalacion paralela VIVA de otro AWM_HOME, que usa la
|
|
62
|
+
* forma nativa con su propia ruta. `isDeadAwmHookEntry` la preserva a proposito y
|
|
63
|
+
* esta funcion no revierte esa decision.
|
|
64
|
+
*/
|
|
65
|
+
function isStaleAwmCodexEntry(entry, scriptsDir, canonicalMatcher) {
|
|
66
|
+
const candidate = entry;
|
|
67
|
+
if (!Array.isArray(candidate?.hooks))
|
|
68
|
+
return false;
|
|
69
|
+
const ours = path_1.default.join(scriptsDir, 'session-start');
|
|
70
|
+
return candidate.hooks.some((hook) => {
|
|
71
|
+
const command = hook?.command;
|
|
72
|
+
if (typeof command !== 'string' || command.length === 0)
|
|
73
|
+
return false;
|
|
74
|
+
const [executable, ...args] = command.split(' ');
|
|
75
|
+
if (path_1.default.basename(executable) === 'run-hook.cmd' && args.join(' ') === 'session-start')
|
|
76
|
+
return true;
|
|
77
|
+
return executable === ours && candidate.matcher !== canonicalMatcher;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
41
80
|
function isAwmCodexEntry(entry, scriptsDir) {
|
|
42
81
|
return (entry?.matcher === codexMatcher() &&
|
|
43
82
|
Array.isArray(entry?.hooks) &&
|
|
@@ -60,7 +99,8 @@ function installCodexHook(options) {
|
|
|
60
99
|
// Se podan primero las entradas nuestras que apuntan a un script inexistente: son
|
|
61
100
|
// restos de otro AWM_HOME ya borrado, y el agente las intenta ejecutar cada sesion.
|
|
62
101
|
// Una instalacion paralela VIVA no se toca — su script existe. Ver isDeadAwmHookEntry.
|
|
63
|
-
const entries = raw.filter((entry) => !(0, shared_1.isDeadAwmHookEntry)(entry, codexMatcher(), 'session-start', config.scriptsDir)
|
|
102
|
+
const entries = raw.filter((entry) => !(0, shared_1.isDeadAwmHookEntry)(entry, codexMatcher(), 'session-start', config.scriptsDir)
|
|
103
|
+
&& !isStaleAwmCodexEntry(entry, config.scriptsDir, codexMatcher()));
|
|
64
104
|
const matches = entries.filter((entry) => isAwmCodexEntry(entry, config.scriptsDir));
|
|
65
105
|
if (matches.length > 1) {
|
|
66
106
|
throw new Error('multiple AWM SessionStart entries in Codex hooks.json');
|
|
@@ -282,6 +282,90 @@ describe('installHook / computeHookStatus / uninstallHook — Codex adapter', ()
|
|
|
282
282
|
const foreign = entryFor('/tmp/someone-elses/my-own-hook');
|
|
283
283
|
expect(isDeadAwmHookEntry(foreign, 'startup|resume|clear|compact', 'session-start', codexScriptsDir)).toBe(false);
|
|
284
284
|
});
|
|
285
|
+
// #184, medido en una Mac real: `~/.codex/hooks.json` con DOS entradas de AWM,
|
|
286
|
+
// matchers solapados en startup/clear/compact, o sea el hook corre dos veces por
|
|
287
|
+
// sesion y el contexto se inyecta dos veces — tokens duplicados en cada arranque.
|
|
288
|
+
//
|
|
289
|
+
// La entrada vieja es la forma WRAPPER de Claude (`run-hook.cmd session-start` con
|
|
290
|
+
// el matcher de Claude), que este adapter declara en su propio encabezado que no
|
|
291
|
+
// usa. Es invisible para las dos vias que la reconocerian: `isAwmCodexEntry` exige
|
|
292
|
+
// el matcher actual, e `isDeadAwmHookEntry` compara el basename del EJECUTABLE
|
|
293
|
+
// contra 'session-start' — y aca el ejecutable es `run-hook.cmd`. Y no es "muerta":
|
|
294
|
+
// `run-hook.cmd` existe, porque Claude Code si lo usa, asi que ninguna prueba de
|
|
295
|
+
// vivencia puede alcanzarla.
|
|
296
|
+
function legacyClaudeWrapperEntry() {
|
|
297
|
+
return {
|
|
298
|
+
matcher: 'startup|clear|compact',
|
|
299
|
+
hooks: [{ type: 'command', command: `${path_1.default.join(tmpHome, '.awm/hooks/run-hook.cmd')} session-start` }],
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
it('drops the legacy run-hook.cmd wrapper entry, so the hook stops firing twice', () => {
|
|
303
|
+
installCodexFixture({ heartbeat: false });
|
|
304
|
+
// El wrapper EXISTE en disco: es el que usa Claude Code de verdad.
|
|
305
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpHome, '.awm/hooks'), { recursive: true });
|
|
306
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpHome, '.awm/hooks/run-hook.cmd'), '#!/bin/sh\n', { mode: 0o755 });
|
|
307
|
+
const current = JSON.parse(fs_1.default.readFileSync(hooksJson, 'utf-8'));
|
|
308
|
+
current.hooks.SessionStart.unshift(legacyClaudeWrapperEntry());
|
|
309
|
+
fs_1.default.writeFileSync(hooksJson, JSON.stringify(current, null, 2));
|
|
310
|
+
writeRegistry();
|
|
311
|
+
const { installHook } = require('../../../src/commands/hooks/install');
|
|
312
|
+
installHook({ agent: 'codex', registryRoot: tmpRegistry, installMethod: 'copy' });
|
|
313
|
+
const after = JSON.parse(fs_1.default.readFileSync(hooksJson, 'utf-8')).hooks.SessionStart;
|
|
314
|
+
expect(after).toHaveLength(1);
|
|
315
|
+
expect(after[0].matcher).toBe('startup|resume|clear|compact');
|
|
316
|
+
expect(after[0].hooks[0].command).toBe(path_1.default.join(codexScriptsDir, 'session-start'));
|
|
317
|
+
});
|
|
318
|
+
it('drops our own entry when only its matcher drifted, instead of adding a second one', () => {
|
|
319
|
+
installCodexFixture({ heartbeat: false });
|
|
320
|
+
const current = JSON.parse(fs_1.default.readFileSync(hooksJson, 'utf-8'));
|
|
321
|
+
current.hooks.SessionStart.unshift({
|
|
322
|
+
matcher: 'startup|clear|compact',
|
|
323
|
+
hooks: [{ type: 'command', command: path_1.default.join(codexScriptsDir, 'session-start') }],
|
|
324
|
+
});
|
|
325
|
+
fs_1.default.writeFileSync(hooksJson, JSON.stringify(current, null, 2));
|
|
326
|
+
writeRegistry();
|
|
327
|
+
const { installHook } = require('../../../src/commands/hooks/install');
|
|
328
|
+
installHook({ agent: 'codex', registryRoot: tmpRegistry, installMethod: 'copy' });
|
|
329
|
+
const after = JSON.parse(fs_1.default.readFileSync(hooksJson, 'utf-8')).hooks.SessionStart;
|
|
330
|
+
expect(after).toHaveLength(1);
|
|
331
|
+
expect(after[0].matcher).toBe('startup|resume|clear|compact');
|
|
332
|
+
});
|
|
333
|
+
it('leaves a live parallel AWM_HOME alone: cleaning our own leftovers is not breaking someone else', () => {
|
|
334
|
+
installCodexFixture({ heartbeat: false });
|
|
335
|
+
const other = path_1.default.join(tmpHome, 'other-awm/hooks/codex');
|
|
336
|
+
fs_1.default.mkdirSync(other, { recursive: true });
|
|
337
|
+
fs_1.default.writeFileSync(path_1.default.join(other, 'session-start'), '#!/bin/sh\n', { mode: 0o755 });
|
|
338
|
+
const current = JSON.parse(fs_1.default.readFileSync(hooksJson, 'utf-8'));
|
|
339
|
+
current.hooks.SessionStart.unshift(entryFor(path_1.default.join(other, 'session-start')));
|
|
340
|
+
fs_1.default.writeFileSync(hooksJson, JSON.stringify(current, null, 2));
|
|
341
|
+
writeRegistry();
|
|
342
|
+
const { installHook } = require('../../../src/commands/hooks/install');
|
|
343
|
+
installHook({ agent: 'codex', registryRoot: tmpRegistry, installMethod: 'copy' });
|
|
344
|
+
const after = JSON.parse(fs_1.default.readFileSync(hooksJson, 'utf-8')).hooks.SessionStart;
|
|
345
|
+
expect(after).toHaveLength(2);
|
|
346
|
+
});
|
|
347
|
+
// La guarda real: podar por procedencia tiene que distinguir NUESTRA ruta exacta
|
|
348
|
+
// de la de otro AWM_HOME. Con matcher canonico la distincion no se ejercita (nada
|
|
349
|
+
// la poda de todos modos), asi que este caso usa un matcher DERIVADO: si la regla
|
|
350
|
+
// mirara el basename en vez de la ruta completa, le romperia la instalacion a otro.
|
|
351
|
+
it('leaves a live parallel AWM_HOME alone even when ITS matcher drifted', () => {
|
|
352
|
+
installCodexFixture({ heartbeat: false });
|
|
353
|
+
const other = path_1.default.join(tmpHome, 'other-awm/hooks/codex');
|
|
354
|
+
fs_1.default.mkdirSync(other, { recursive: true });
|
|
355
|
+
fs_1.default.writeFileSync(path_1.default.join(other, 'session-start'), '#!/bin/sh\n', { mode: 0o755 });
|
|
356
|
+
const current = JSON.parse(fs_1.default.readFileSync(hooksJson, 'utf-8'));
|
|
357
|
+
current.hooks.SessionStart.unshift({
|
|
358
|
+
matcher: 'startup|clear|compact',
|
|
359
|
+
hooks: [{ type: 'command', command: path_1.default.join(other, 'session-start') }],
|
|
360
|
+
});
|
|
361
|
+
fs_1.default.writeFileSync(hooksJson, JSON.stringify(current, null, 2));
|
|
362
|
+
writeRegistry();
|
|
363
|
+
const { installHook } = require('../../../src/commands/hooks/install');
|
|
364
|
+
installHook({ agent: 'codex', registryRoot: tmpRegistry, installMethod: 'copy' });
|
|
365
|
+
const after = JSON.parse(fs_1.default.readFileSync(hooksJson, 'utf-8')).hooks.SessionStart;
|
|
366
|
+
expect(after).toHaveLength(2);
|
|
367
|
+
expect(after.some((e) => e.hooks[0].command === path_1.default.join(other, 'session-start'))).toBe(true);
|
|
368
|
+
});
|
|
285
369
|
it('install removes the stale entry instead of accumulating a second one', () => {
|
|
286
370
|
installCodexFixture({ heartbeat: false });
|
|
287
371
|
const hooksJson = path_1.default.join(tmpHome, '.codex', 'hooks.json');
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|