@agfpd/iapeer 0.4.45 → 0.4.47
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 +1 -1
- package/src/init/index.ts +1 -1
- package/src/launch/adapters/claude.ts +59 -37
- package/src/launch/invocation.ts +12 -0
- package/src/launch/types.ts +1 -1
- package/src/supervisor/boot.ts +1 -1
- package/src/supervisor/daemon.ts +1 -1
package/package.json
CHANGED
package/src/init/index.ts
CHANGED
|
@@ -130,7 +130,7 @@ export function writeClaudeMcpConfig(cwd: string, personality: string, daemonUrl
|
|
|
130
130
|
* peer has no one to approve it, so the wake stalls. This flag pre-approves the cwd's MCP
|
|
131
131
|
* servers so the dialog never appears (verified live, claude 2.1.183). Set unconditionally.
|
|
132
132
|
* - `"tui": "default"` — pins the CLASSIC renderer so claude never pops its one-time
|
|
133
|
-
* "Try the new fullscreen
|
|
133
|
+
* "Try the new fullscreen-renderer?" upsell modal (a BLOCKING mid-session nag that froze
|
|
134
134
|
* live fleet peers; empirically an explicit tui choice suppresses it — verified live). This
|
|
135
135
|
* is the PREVENTION belt to the supervisor nag-watcher: init keeps the modal from appearing
|
|
136
136
|
* for a new peer at all, the watcher dismisses the class on every peer mid-session. Set ONLY
|
|
@@ -66,16 +66,38 @@ const CLAUDE_BOOT_DIALOG_MARKERS = [
|
|
|
66
66
|
// lowercase "bypass permissions on" (a DISTINCT string), so this marker vanishes —
|
|
67
67
|
// it never falsely traps isInputReady, and a re-launch is dialog-free (verified).
|
|
68
68
|
'Bypass Permissions mode',
|
|
69
|
-
// Project MCP-server approval ("N new MCP servers found in this project", servers
|
|
70
|
-
// pre-checked [✔]) — shown when cwd carries a .mcp.json the config has not yet
|
|
71
|
-
// approved. Suppressed at the source by enableAllProjectMcpServers in
|
|
72
|
-
// <cwd>/.claude/settings.json (init writes it); bootDialogKeys Enter-confirms it as
|
|
73
|
-
// a backstop. Substring matches both "1 new MCP server" and "N new MCP servers".
|
|
74
|
-
'new MCP server',
|
|
75
69
|
] as const
|
|
76
70
|
|
|
71
|
+
// В39 — Project MCP-server approval ("N new MCP servers found in this project", servers pre-checked
|
|
72
|
+
// [✔]). This one is NOT a plain substring marker: agents on this fleet mention "new MCP server" in
|
|
73
|
+
// NORMAL conversation, and a resumed session replays that tail into the viewport → the old substring
|
|
74
|
+
// gated isInputReady forever, the wake failed 'never-became-ready', the message was lost (live
|
|
75
|
+
// incident). Require the dialog's DISTINGUISHING context ("found in this project") too — a
|
|
76
|
+
// conversational mention of the topic phrase does not carry it. Suppressed at the source by
|
|
77
|
+
// enableAllProjectMcpServers (init); Enter-confirmed here as a backstop.
|
|
78
|
+
function mcpApprovalActive(pane: string): boolean {
|
|
79
|
+
return pane.includes('new MCP server') && pane.includes('found in')
|
|
80
|
+
}
|
|
81
|
+
|
|
77
82
|
function anyBootDialog(pane: string): boolean {
|
|
78
|
-
return CLAUDE_BOOT_DIALOG_MARKERS.some(m => pane.includes(m))
|
|
83
|
+
return CLAUDE_BOOT_DIALOG_MARKERS.some(m => pane.includes(m)) || mcpApprovalActive(pane)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// В40 — the fullscreen-renderer nag needles are BUILT from fragments so this SOURCE FILE never contains
|
|
87
|
+
// the verbatim modal text. A nag-watcher reads a peer's live VIEWPORT; if this file's text (a peer
|
|
88
|
+
// editing/reviewing it) carried the contiguous phrase, viewing it would self-trigger the very bug — which
|
|
89
|
+
// is exactly what happened live while this fix was being written. Split literals keep the match working at
|
|
90
|
+
// runtime while making the source inert.
|
|
91
|
+
const NAG_TITLE = 'Try the new fullscreen render' + 'er?'
|
|
92
|
+
const NAG_OPTION_YES = 'Yes, ' + 'try it'
|
|
93
|
+
/** Does the pane carry the fullscreen-renderer nag at all (cheap gate)? */
|
|
94
|
+
function isFullscreenNagText(pane: string): boolean {
|
|
95
|
+
return pane.includes(NAG_TITLE)
|
|
96
|
+
}
|
|
97
|
+
/** Is THIS row the modal's LIVE selected option (cursor `❯` on "1. Yes, try it")? The bottom-most `❯`
|
|
98
|
+
* row of a live modal is this; a mere quote of the modal has the ready composer `❯` below it instead. */
|
|
99
|
+
function isFullscreenNagOptionRow(row: string): boolean {
|
|
100
|
+
return row.includes('❯') && (row.includes(NAG_OPTION_YES) || /❯\s*1\.\s*Yes/.test(row))
|
|
79
101
|
}
|
|
80
102
|
|
|
81
103
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -226,10 +248,10 @@ export const claudeAdapter: RuntimeAdapter = {
|
|
|
226
248
|
pane.includes('trust this folder') ||
|
|
227
249
|
pane.includes('Allow external CLAUDE.md file imports?') ||
|
|
228
250
|
pane.includes('I am using this for local development') ||
|
|
229
|
-
// Project MCP-server approval (servers pre-checked [✔]; Enter confirms, Esc
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
pane
|
|
251
|
+
// Project MCP-server approval (servers pre-checked [✔]; Enter confirms, Esc rejects all).
|
|
252
|
+
// enableAllProjectMcpServers normally suppresses it; this is the backstop. В39: full-signature
|
|
253
|
+
// (topic + "found in") so a conversational mention never triggers a stray Enter.
|
|
254
|
+
mcpApprovalActive(pane) ||
|
|
233
255
|
// First-run theme picker (clean host): the default-highlighted row is always a
|
|
234
256
|
// valid theme (any choice is harmless for a headless peer), so a bare Enter
|
|
235
257
|
// accepts it. Without this the picker blocks the boot forever on a fresh host.
|
|
@@ -246,35 +268,35 @@ export const claudeAdapter: RuntimeAdapter = {
|
|
|
246
268
|
},
|
|
247
269
|
|
|
248
270
|
/**
|
|
249
|
-
* MID-SESSION nag/upsell auto-dismiss (livability — distinct from bootDialogKeys,
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* 2. Not now
|
|
258
|
-
* Enter to confirm · Esc to cancel
|
|
271
|
+
* MID-SESSION nag/upsell auto-dismiss (livability — distinct from bootDialogKeys, which the supervisor
|
|
272
|
+
* drives only until ready). claude pops a ONE-TIME interactive upsell modal AFTER the session is live —
|
|
273
|
+
* the fullscreen-renderer offer — that BLOCKS a headless peer (no human to answer). Its default cursor
|
|
274
|
+
* is on the ACCEPT option, so a bare Enter would enable the alt-screen renderer and change the surface
|
|
275
|
+
* the pane-log model / composer-occupancy / ready-gate all read off. We DECLINE by selecting the "not
|
|
276
|
+
* now" option ('2' then Enter — '2' is emitted verbatim, not a named key). VERIFIED-SAFE on the live
|
|
277
|
+
* fleet ('2'+Enter cleared it on boris/doc; an arrow-step+Enter mis-fired into fullscreen on scriber; Esc/
|
|
278
|
+
* 'n' unverified) — so this is the ONLY accepted sequence.
|
|
259
279
|
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
* fleet: '2'+Enter cleared the modal on boris and doc and returned them to the
|
|
267
|
-
* composer; an arrow-step+Enter MIS-FIRED into "tui":"fullscreen" on scriber, and Esc/
|
|
268
|
-
* 'n' are unverified — so this is the ONLY accepted sequence. Match the FULL signature
|
|
269
|
-
* (title AND the "2. Not now" decline row) so a transcript merely DISCUSSING the
|
|
270
|
-
* fullscreen renderer can never trigger a stray keystroke. Future one-time CC upsells
|
|
271
|
-
* of this class extend the match here (each with its own verified-safe decline key).
|
|
280
|
+
* В40 — the match is POSITION-based, NOT phrase-based: the needles are built from fragments (this file
|
|
281
|
+
* never carries the verbatim modal text, or a peer editing/reviewing it would self-trigger — observed
|
|
282
|
+
* live), and it fires ONLY when the BOTTOM-MOST cursor row is the modal's live option (a mere quote of
|
|
283
|
+
* the modal has the ready composer below it). NB text-matching still cannot fully distinguish a live
|
|
284
|
+
* claude modal from an agent RENDERING the modal glyphs at its viewport bottom — the correct fix is a
|
|
285
|
+
* rendering-level signal (the alt-screen switch), tracked as a HIGH-priority follow-up (В59).
|
|
272
286
|
*/
|
|
273
287
|
nagDismissKeys(pane: string): string[] | null {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
288
|
+
// В40 — a text-only match fired '2'+Enter into a LIVE composer whenever the peer merely displayed the
|
|
289
|
+
// modal text (a code review of this file, a forwarded bug report — or, live-observed, the peer editing
|
|
290
|
+
// THIS adapter). Discriminate by POSITION, not phrases: claude's live modal REPLACES the composer, so
|
|
291
|
+
// the BOTTOM-MOST cursor row (`❯`) sits on the modal's option. A mere quote of the modal has the READY
|
|
292
|
+
// composer `❯` rendered BELOW it as the bottom-most cursor row. So: fire only when the last `❯`-bearing
|
|
293
|
+
// row is the modal's selected option — a displayed quote never is.
|
|
294
|
+
if (!isFullscreenNagText(pane)) return null
|
|
295
|
+
let lastCursorRow = ''
|
|
296
|
+
for (const line of pane.split(/\r?\n/)) if (line.includes('❯')) lastCursorRow = line
|
|
297
|
+
// the live modal's bottom-most cursor sits on option 1 ("Yes, try it"); the ready composer's `❯` does
|
|
298
|
+
// not carry the option text, so a quote (composer below) fails this.
|
|
299
|
+
return isFullscreenNagOptionRow(lastCursorRow) ? ['2', 'Enter'] : null
|
|
278
300
|
},
|
|
279
301
|
|
|
280
302
|
/**
|
package/src/launch/invocation.ts
CHANGED
|
@@ -67,5 +67,17 @@ export function buildLaunchInvocation(
|
|
|
67
67
|
for (const k of Object.keys(childEnv)) {
|
|
68
68
|
if (k.startsWith('CLAUDE_CODE_') || k === 'CLAUDECODE') delete childEnv[k]
|
|
69
69
|
}
|
|
70
|
+
// В59 — pin claude to the CLASSIC (main-screen) renderer. Two load-bearing effects: (1) the whole
|
|
71
|
+
// supervisor model — pane-log serialize-snapshot, composer-occupancy, the ready-gate flip — assumes
|
|
72
|
+
// claude's DEFAULT no-alt-screen relative-cursor rendering (the daemon census). Pinning it makes that
|
|
73
|
+
// assumption UNCONDITIONAL: no future claude feature, and no mis-accepted mid-session upsell, can
|
|
74
|
+
// silently switch to the alt-screen renderer and corrupt the surface the supervisor reads off. This
|
|
75
|
+
// NEUTRALIZES the consequence of the В40 fullscreen-renderer nag even if a stray keystroke accepts it.
|
|
76
|
+
// (2) It disables the very feature that upsell offers, so the modal has nothing to enable — expected to
|
|
77
|
+
// suppress it at the SOURCE (the root-cause complement to the nag-watcher backstop). Set AFTER the
|
|
78
|
+
// CLAUDE_CODE_* strip above (which would otherwise delete it); claude-only — codex/router runtimes
|
|
79
|
+
// ignore CLAUDE_CODE_*. Safe by construction: claude already renders classic by default (peers boot and
|
|
80
|
+
// attach today), so this pins the current behavior, it does not change it.
|
|
81
|
+
if (spec.runtime === 'claude') childEnv.CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN = '1'
|
|
70
82
|
return { argv, env: childEnv }
|
|
71
83
|
}
|
package/src/launch/types.ts
CHANGED
|
@@ -288,7 +288,7 @@ export interface RuntimeAdapter {
|
|
|
288
288
|
/**
|
|
289
289
|
* MID-SESSION nag/upsell auto-dismiss (livability). Distinct from bootDialogKeys:
|
|
290
290
|
* the boot-driver stops at ready, but claude/codex can pop a ONE-TIME interactive
|
|
291
|
-
* upsell modal AFTER the session is live (e.g. "Try the new fullscreen
|
|
291
|
+
* upsell modal AFTER the session is live (e.g. "Try the new fullscreen-renderer?")
|
|
292
292
|
* that BLOCKS the pty waiting for a keypress no headless peer answers — it froze the
|
|
293
293
|
* live fleet until a human cleared it. A persistent supervisor watcher (daemon.ts)
|
|
294
294
|
* runs THIS off the authoritative model for the whole session and writes the keys.
|
package/src/supervisor/boot.ts
CHANGED
|
@@ -44,7 +44,7 @@ export type NagAction = { kind: 'dismiss'; keys: string[]; bytes: Buffer } | { k
|
|
|
44
44
|
/**
|
|
45
45
|
* MID-SESSION nag/upsell step (livability) — the persistent sibling of nextBootAction. Unlike the
|
|
46
46
|
* boot-driver, which STOPS at ready, the daemon loops this for the WHOLE session: a one-time CC upsell
|
|
47
|
-
* modal (e.g. "Try the new fullscreen
|
|
47
|
+
* modal (e.g. "Try the new fullscreen-renderer?") can pop AFTER the session is live and BLOCK the pty on
|
|
48
48
|
* a keypress no headless peer answers. If the adapter recognizes the (FULL-signature) modal it returns
|
|
49
49
|
* the verified-safe DECLINE keys; the caller writes `action.bytes` to the pty (cooldown-guarded so a
|
|
50
50
|
* cleared modal is never double-answered). An adapter with no nagDismissKeys → always 'none'.
|
package/src/supervisor/daemon.ts
CHANGED
|
@@ -599,7 +599,7 @@ export function runSupervisorDaemon(opts: SupervisorDaemonOptions): void {
|
|
|
599
599
|
|
|
600
600
|
// ── mid-session nag-watcher (livability) ────────────────────────────────────────
|
|
601
601
|
// The boot-driver above STOPS at ready. But claude/codex can pop a ONE-TIME upsell modal
|
|
602
|
-
// MID-SESSION (e.g. "Try the new fullscreen
|
|
602
|
+
// MID-SESSION (e.g. "Try the new fullscreen-renderer?", which appears on the return-to-prompt
|
|
603
603
|
// boundary after a tool result) that BLOCKS the pty on a keypress no headless peer answers — it
|
|
604
604
|
// froze live fleet peers (boris, doc) until a human cleared it by hand. A PERSISTENT watcher answers
|
|
605
605
|
// the curated, verified-safe DECLINE for that class off the SAME authoritative @xterm model, for the
|