@agfpd/iapeer 0.2.69 → 0.2.70

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.69",
3
+ "version": "0.2.70",
4
4
  "description": "Foundation core for the iapeer multi-agent ecosystem: identity, registry, storage, codec.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -107,6 +107,46 @@ describe('buildLaunchInvocation — differential vs the verbatim pre-extraction
107
107
  })
108
108
  })
109
109
 
110
+ // Silent-loss guard — the claude-hosting canary caught a warm-deliver false-fail: a launch INITIATED
111
+ // from inside a claude session leaked CLAUDE_CODE_SESSION_ID + CLAUDE_CODE_CHILD_SESSION into the
112
+ // child, which then wrote no own transcript → newestActivityMtime null → landed-confirm blind while
113
+ // the message landed. buildLaunchInvocation must strip the claude-code-internal namespace so a
114
+ // launched claude is always a FRESH top-level session, regardless of initiator env.
115
+ describe('buildLaunchInvocation — strips the claude-code-internal session namespace', () => {
116
+ const DIRTY: LaunchAdapterConfig & { env: NodeJS.ProcessEnv } = {
117
+ claudeBin: '/bin/claude',
118
+ codexBin: '/bin/codex',
119
+ env: {
120
+ PATH: '/test/path',
121
+ BASE_VAR: 'base',
122
+ CLAUDE_CODE_SESSION_ID: 'spawner-session-uuid', // the corruptor (pins the child to the spawner's session)
123
+ CLAUDE_CODE_CHILD_SESSION: '1', // the corruptor (marks a child sub-session → suppresses own transcript)
124
+ CLAUDECODE: '1',
125
+ CLAUDE_CODE_ENTRYPOINT: 'cli',
126
+ CLAUDE_CODE_EXECPATH: '/v/2.1.177',
127
+ CLAUDE_EFFORT: 'xhigh', // NOT in the claude-code namespace → must survive (scoped strip, not a wipe)
128
+ },
129
+ }
130
+ test('claude launch: the whole CLAUDE_CODE_* namespace + CLAUDECODE is removed', () => {
131
+ const { env } = buildLaunchInvocation(spec({ runtime: 'claude', identity: 'claude-p' }), claudeAdapter, DIRTY)
132
+ expect(env.CLAUDE_CODE_SESSION_ID).toBeUndefined()
133
+ expect(env.CLAUDE_CODE_CHILD_SESSION).toBeUndefined()
134
+ expect(env.CLAUDECODE).toBeUndefined()
135
+ expect(env.CLAUDE_CODE_ENTRYPOINT).toBeUndefined()
136
+ expect(env.CLAUDE_CODE_EXECPATH).toBeUndefined()
137
+ // scoped: non-namespace vars (incl. a CLAUDE_-prefixed-but-not-CLAUDE_CODE_ one) survive
138
+ expect(env.BASE_VAR).toBe('base')
139
+ expect(env.CLAUDE_EFFORT).toBe('xhigh')
140
+ expect(env.PEER_IDENTITY).toBe('claude-p')
141
+ })
142
+ test('codex launch: same strip applies (harmless — codex ignores CLAUDE_*) and the bearer stub stays', () => {
143
+ const { env } = buildLaunchInvocation(spec({ runtime: 'codex', identity: 'codex-p' }), codexAdapter, DIRTY)
144
+ expect(env.CLAUDE_CODE_SESSION_ID).toBeUndefined()
145
+ expect(env.CLAUDECODE).toBeUndefined()
146
+ expect(env[CODEX_BEARER_ENV_VAR]).toBe(CODEX_DUMMY_BEARER)
147
+ })
148
+ })
149
+
110
150
  // Independent contract anchors — expectations come from the CONTRACT, not from the function output.
111
151
  describe('buildLaunchInvocation — identity ABI + system-prompt swap (contract values)', () => {
112
152
  test('codex carries the non-secret IAPEER_BEARER stub; claude does NOT (identity in .mcp.json)', () => {
@@ -52,5 +52,20 @@ export function buildLaunchInvocation(
52
52
  PEER_IDENTITY: spec.identity,
53
53
  PATH: env.PATH ?? `${homedir()}/.bun/bin:${homedir()}/.local/bin:/opt/homebrew/bin:/usr/bin:/bin`,
54
54
  }
55
+ // Strip the claude-code-internal session namespace from the inherited env. When a launch is
56
+ // INITIATED from inside a running claude session (an operator CLI, or a claude peer shelling out),
57
+ // process.env carries that session's CLAUDE_CODE_SESSION_ID + CLAUDE_CODE_CHILD_SESSION. Inherited
58
+ // by a newly-launched claude, they make it believe it is a CHILD sub-session of the initiator → it
59
+ // writes NO independent ~/.claude/projects transcript → newestActivityMtime(cwd) is permanently
60
+ // null → the warm-deliver landed-confirm false-fails WHILE the message actually lands (a silent-
61
+ // loss, the class the contract forbids). Stripping the whole namespace makes a launched claude
62
+ // always start as a FRESH top-level session (it re-establishes its own vars), independent of who
63
+ // initiated the launch — the env-composition analogue of the supervisor's TMUX strip. Caught live
64
+ // by the claude-hosting canary. Harmless for codex (no CLAUDE_* usage). NOTE: a launch driven by
65
+ // the always-on daemon already has a clean env (no claude session) — this hardens the CLI / peer-
66
+ // initiated paths and removes the silent-loss class regardless of initiator.
67
+ for (const k of Object.keys(childEnv)) {
68
+ if (k.startsWith('CLAUDE_CODE_') || k === 'CLAUDECODE') delete childEnv[k]
69
+ }
55
70
  return { argv, env: childEnv }
56
71
  }