@agfpd/iapeer 0.2.40 → 0.2.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer",
3
- "version": "0.2.40",
3
+ "version": "0.2.41",
4
4
  "description": "Foundation core for the iapeer multi-agent ecosystem: identity, registry, storage, codec.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/init/index.ts CHANGED
@@ -172,6 +172,41 @@ export function writeCodexMcpConfig(daemonUrl: string, options: StorageOptions =
172
172
  return { path, added: true }
173
173
  }
174
174
 
175
+ /**
176
+ * Idempotently ensure codex's startup update-check DIALOG is disabled host-wide:
177
+ * `check_for_update_on_startup = false` as a TOP-LEVEL key in config.toml.
178
+ *
179
+ * Why (incident 11.06, codex-linus): the interactive "✨ Update available! …
180
+ * Press enter to continue" dialog is a BOOT GATE — a daemon-woken codex session
181
+ * sits on it until a human attaches. The wake reported never-became-ready after
182
+ * 4+ minutes and a notifier alert of the EMERGENCY contour (mining farm) was
183
+ * lost; the session came alive only from a manual attach. Warm-on-demand peers
184
+ * must reach ready hands-free — codex updates stay operator-driven (`codex
185
+ * update` / brew), never a boot-time question to a headless session.
186
+ *
187
+ * Mechanics: the key is a documented top-level config.toml option (present in
188
+ * the binary's own config schema; suppression PROVEN LIVE 11.06 on codex 0.138
189
+ * with the 0.139 update pending — control boot showed the dialog, the keyed
190
+ * boot went straight past it). A TOML top-level key must precede the first
191
+ * table header, so the key is PREPENDED — appending would land it inside the
192
+ * last [section]. An EXISTING `check_for_update_on_startup` of either value is
193
+ * respected (never override an explicit operator choice).
194
+ */
195
+ export function ensureCodexUpdateCheckDisabled(
196
+ options: StorageOptions = {},
197
+ ): { path: string; changed: boolean } {
198
+ const path = codexConfigPath(options)
199
+ let existing = ''
200
+ try {
201
+ existing = readFileSync(path, 'utf8')
202
+ } catch {
203
+ /* no config yet → create it */
204
+ }
205
+ if (/^\s*check_for_update_on_startup\s*=/m.test(existing)) return { path, changed: false }
206
+ writeFileAtomic(path, `check_for_update_on_startup = false\n${existing}`, 0o644)
207
+ return { path, changed: true }
208
+ }
209
+
175
210
  // ─────────────────────────────────────────────────────────────────────────────
176
211
  // Local doctrine template (<cwd>/.iapeer/IAPEER.md — personality/role; human fills)
177
212
  // ─────────────────────────────────────────────────────────────────────────────
@@ -304,6 +339,9 @@ export async function initPeer(opts: InitPeerOptions): Promise<InitPeerResult> {
304
339
  let codexMcpConfigPath: string | undefined
305
340
  if (provisioned.runtime === 'codex' || hasCodexMarker(provisioned.cwd)) {
306
341
  codexMcpConfigPath = writeCodexMcpConfig(daemonUrl, { env }).path
342
+ // Boot-gate hygiene (incident 11.06): a codex peer must never sit on the
343
+ // interactive update dialog at wake — disable the startup check host-wide.
344
+ ensureCodexUpdateCheckDisabled({ env })
307
345
  }
308
346
 
309
347
  const doctrine = ensureDoctrineTemplate(provisioned.cwd)
@@ -9,6 +9,7 @@ import {
9
9
  CODEX_BEARER_ENV_VAR,
10
10
  IAPEER_MCP_SERVER_NAME,
11
11
  codexConfigPath,
12
+ ensureCodexUpdateCheckDisabled,
12
13
  ensureDoctrineTemplate,
13
14
  initPeer,
14
15
  resolveDaemonMcpUrl,
@@ -169,3 +170,44 @@ describe('initPeer orchestration', () => {
169
170
  expect(readFileSync(join(cwd, '.iapeer', 'IAPEER.md'), 'utf8')).toBe('custom doctrine')
170
171
  })
171
172
  })
173
+
174
+ describe('ensureCodexUpdateCheckDisabled (boot-gate hygiene — incident 11.06 codex-linus)', () => {
175
+ test('PREPENDS the top-level key BEFORE the first [section] (TOML top-level scope)', () => {
176
+ const codexHome = mkdtempSync(join(tmpdir(), 'iapeer-codexhome3-'))
177
+ try {
178
+ const env: NodeJS.ProcessEnv = { ...process.env, CODEX_HOME: codexHome }
179
+ writeFileSync(codexConfigPath({ env }), 'model = "gpt-5.5"\n\n[projects."/x"]\ntrust_level = "trusted"\n')
180
+ const { changed } = ensureCodexUpdateCheckDisabled({ env })
181
+ expect(changed).toBe(true)
182
+ const toml = readFileSync(codexConfigPath({ env }), 'utf8')
183
+ expect(toml.startsWith('check_for_update_on_startup = false\n')).toBe(true)
184
+ expect(toml).toContain('[projects."/x"]') // existing config preserved
185
+ // idempotent: second run is a no-op
186
+ expect(ensureCodexUpdateCheckDisabled({ env }).changed).toBe(false)
187
+ expect((toml.match(/check_for_update_on_startup/g) ?? []).length).toBe(1)
188
+ } finally {
189
+ rmSync(codexHome, { recursive: true, force: true })
190
+ }
191
+ })
192
+ test('respects an EXPLICIT existing value (never overrides the operator)', () => {
193
+ const codexHome = mkdtempSync(join(tmpdir(), 'iapeer-codexhome4-'))
194
+ try {
195
+ const env: NodeJS.ProcessEnv = { ...process.env, CODEX_HOME: codexHome }
196
+ writeFileSync(codexConfigPath({ env }), 'check_for_update_on_startup = true\n')
197
+ expect(ensureCodexUpdateCheckDisabled({ env }).changed).toBe(false)
198
+ expect(readFileSync(codexConfigPath({ env }), 'utf8')).toContain('check_for_update_on_startup = true')
199
+ } finally {
200
+ rmSync(codexHome, { recursive: true, force: true })
201
+ }
202
+ })
203
+ test('absent config → created with just the key', () => {
204
+ const codexHome = mkdtempSync(join(tmpdir(), 'iapeer-codexhome5-'))
205
+ try {
206
+ const env: NodeJS.ProcessEnv = { ...process.env, CODEX_HOME: codexHome }
207
+ expect(ensureCodexUpdateCheckDisabled({ env }).changed).toBe(true)
208
+ expect(readFileSync(codexConfigPath({ env }), 'utf8')).toBe('check_for_update_on_startup = false\n')
209
+ } finally {
210
+ rmSync(codexHome, { recursive: true, force: true })
211
+ }
212
+ })
213
+ })