instar 1.3.722 → 1.3.724
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +4 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/SessionManager.d.ts +60 -4
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +87 -9
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/StuckInputSentinel.d.ts +83 -8
- package/dist/core/StuckInputSentinel.d.ts.map +1 -1
- package/dist/core/StuckInputSentinel.js +210 -10
- package/dist/core/StuckInputSentinel.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +31 -0
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +56 -18
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/monitoring/SessionReaper.d.ts +9 -0
- package/dist/monitoring/SessionReaper.d.ts.map +1 -1
- package/dist/monitoring/SessionReaper.js +10 -2
- package/dist/monitoring/SessionReaper.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +3 -3
- package/upgrades/1.3.724.md +130 -0
- package/upgrades/side-effects/closeout-lease-carveout-and-firstparty-bootstrap.md +148 -0
- package/upgrades/side-effects/p05-filesinfo-ghosttext.md +188 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Side-Effects Review — Slack files.info self-verify fix + F2 ghost-text exclusion in StuckInputSentinel
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `p05-filesinfo-ghosttext`
|
|
4
|
+
**Date:** `2026-07-02`
|
|
5
|
+
**Author:** Echo (autonomous, roadmap 0.5)
|
|
6
|
+
**Second-pass reviewer:** self, second pass over the final diff (Tier 1)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Two code halves of roadmap item 0.5, both grounded in 2026-07-02 live evidence:
|
|
11
|
+
|
|
12
|
+
1. **Slack files.info self-verify** (`src/messaging/slack/SlackAdapter.ts`):
|
|
13
|
+
the startup self-verify probed `files.info` with the malformed synthetic id
|
|
14
|
+
`F000SELFTEST`; Slack rejects that id at server-side ARGUMENT VALIDATION
|
|
15
|
+
(`invalid_arguments`) before lookup, and the old classifier counted that as
|
|
16
|
+
an unexpected FAILURE — a healthy adapter red-flagged itself at every boot.
|
|
17
|
+
Fix: (a) probe with a WELL-FORMED synthetic id (`F0000000000`) so a healthy
|
|
18
|
+
workspace answers `file_not_found`; (b) extract the classification into an
|
|
19
|
+
exported pure function `classifyFilesInfoSelfTest()` that treats
|
|
20
|
+
`invalid_arguments` as what it proves (endpoint + auth + transport all
|
|
21
|
+
answered) while `missing_scope` and unknown errors remain failures; (c) use
|
|
22
|
+
`SlackApiError.slackError` (the structured code) when available instead of
|
|
23
|
+
substring-matching the whole message.
|
|
24
|
+
|
|
25
|
+
2. **F2 ghost-text exclusion** (`src/core/StuckInputSentinel.ts` +
|
|
26
|
+
`src/core/SessionManager.ts`): the sentinel treated Claude Code's DIM
|
|
27
|
+
model-generated composer suggestion (SGR 2, `ESC[0;2m`) as stuck real input
|
|
28
|
+
and fired 4 Enter presses at it (live stuck-input-events.jsonl). Fix: before
|
|
29
|
+
any keypress on the generic `❯`-prompt path, re-capture the pane WITH ANSI
|
|
30
|
+
escapes (new `SessionManager.captureOutputAnsi`, `capture-pane -e`) and
|
|
31
|
+
classify the stuck text's presentation: `real` → recover as before; `ghost`
|
|
32
|
+
(entirely dim) → never press keys, sticky-exhaust until the text changes;
|
|
33
|
+
`inconclusive` (capture failed / frames raced / mixed styling) → LOG-ONLY
|
|
34
|
+
this tick, re-assess next tick. One observability event per stuck text
|
|
35
|
+
(`ghost-text-skip` / `ghost-check-inconclusive`, outcome `skipped`).
|
|
36
|
+
|
|
37
|
+
## Decision-point inventory
|
|
38
|
+
|
|
39
|
+
- **Added** (`StuckInputSentinel.evaluateSession`): the ghost-text gate — a new
|
|
40
|
+
refusal branch between "fire-eligible" and "fire keypress" on the generic
|
|
41
|
+
prompt path only. The codex marker path is exempt by construction (it only
|
|
42
|
+
fires at the exact text we ourselves injected). Escalation (tier C) is
|
|
43
|
+
unreachable for ghost text: attempts only accrue through this gate.
|
|
44
|
+
- **Added** (`classifyPromptTextPresentation` + `parseAnsiDimLines`): pure
|
|
45
|
+
classification logic. Only the SGR dim attribute (2 / 22 / reset) is the
|
|
46
|
+
ghost tell; extended-color params (`38;5;n`, `38;2;r;g;b`, colon forms) are
|
|
47
|
+
skipped so a truecolor component `2` is never misread as dim. The ANSI
|
|
48
|
+
frame's own prompt extraction must reproduce the plain frame's text EXACTLY,
|
|
49
|
+
or the verdict is `inconclusive` — the styling verdict is always about the
|
|
50
|
+
same characters the detection saw.
|
|
51
|
+
- **Modified** (`SlackAdapter._selfVerify` check 2): outcome classification
|
|
52
|
+
moved to `classifyFilesInfoSelfTest()`; `invalid_arguments` reclassified
|
|
53
|
+
fail → pass. `missing_scope` and unknown errors keep failing.
|
|
54
|
+
|
|
55
|
+
## FAIL-DIRECTION (the load-bearing property)
|
|
56
|
+
|
|
57
|
+
**The sentinel change fails toward LOG-ONLY, never toward Enter.** Every
|
|
58
|
+
uncertain path — ANSI capture unsupported, capture returned null, capture
|
|
59
|
+
threw, the two frames raced (text mismatch), unparseable escapes, mixed
|
|
60
|
+
styling — resolves to `inconclusive`, which withholds the keypress for that
|
|
61
|
+
tick and logs one event. `inconclusive` is deliberately NOT sticky: a raced
|
|
62
|
+
capture self-heals next tick, so a genuinely stuck message is recovered at most
|
|
63
|
+
a few ticks late; a persistent failure keeps failing toward not pressing.
|
|
64
|
+
Pressing Enter at model-fabricated text is the unsafe direction; a delayed
|
|
65
|
+
recovery is the safe cost. The invariant encoded: the sentinel never
|
|
66
|
+
auto-submits text the user (or an authorized injector) did not actually type.
|
|
67
|
+
|
|
68
|
+
## Roll-up across the seven review dimensions
|
|
69
|
+
|
|
70
|
+
1. **Over-block**: the ghost gate could suppress recovery of REAL stuck input
|
|
71
|
+
if the classifier misfires. Mitigations: color is deliberately NOT a tell
|
|
72
|
+
(only SGR 2); a plain no-SGR frame classifies `real` (legacy behavior
|
|
73
|
+
preserved — proven by the pre-existing suite passing with the default stub);
|
|
74
|
+
`inconclusive` is transient, re-assessed every fire-eligible tick; truecolor
|
|
75
|
+
and 256-color params are param-skipped. Both sides tested.
|
|
76
|
+
2. **Under-block**: ghost text that Claude Code someday renders at NORMAL
|
|
77
|
+
intensity would not be caught — but then it is visually indistinguishable
|
|
78
|
+
from typed text and no capture-based tell exists; the dim attribute is the
|
|
79
|
+
entire live-evidenced signature. The codex-marker exemption cannot leak: it
|
|
80
|
+
requires a marker WE injected.
|
|
81
|
+
3. **Silent failure**: none added. Every skip logs exactly one
|
|
82
|
+
`stuck-input-events.jsonl` row (bounded: one per stuck text, not per tick).
|
|
83
|
+
`captureOutputAnsi` returns null on failure exactly like `captureOutput`.
|
|
84
|
+
4. **Authority creep**: none. The sentinel LOSES authority (a new refusal
|
|
85
|
+
branch); nothing gains a new write path. The Slack change alters only a
|
|
86
|
+
boot-time diagnostic verdict, no message flow.
|
|
87
|
+
5. **Fleet blast radius**: both changes are always-on but strictly narrowing.
|
|
88
|
+
Slack: only installs with a Slack adapter + `files:read` scope run check 2;
|
|
89
|
+
the reclassification can only flip a false-red to green. Sentinel: one extra
|
|
90
|
+
`tmux capture-pane -e` per fire-eligible tick per stuck session (rare;
|
|
91
|
+
bounded by the same tick cadence that already captures panes).
|
|
92
|
+
6. **Rollback**: single-commit revert. No config keys, no migrations, no state
|
|
93
|
+
format changes. The new JSONL `action` values are additive to an existing
|
|
94
|
+
log consumers already treat as free-form.
|
|
95
|
+
7. **Observability**: new `ghost-text-skip` / `ghost-check-inconclusive`
|
|
96
|
+
events in `stuck-input-events.jsonl`; Slack self-verify detail strings name
|
|
97
|
+
the exact outcome (`argument validation` vs `file_not_found`).
|
|
98
|
+
|
|
99
|
+
## 3. Level-of-abstraction fit
|
|
100
|
+
|
|
101
|
+
Right layer, both halves. The ghost gate lives INSIDE the sentinel's own
|
|
102
|
+
fire path (the only place a keypress originates), not as a parallel filter in
|
|
103
|
+
front of it — the same layer that already hosts the activity-indicator refusal
|
|
104
|
+
and the codex-marker strategy choice. The ANSI capture primitive lives in
|
|
105
|
+
`SessionManager` beside `captureOutput` (its styled twin) rather than being
|
|
106
|
+
re-implemented in the sentinel, so future consumers that need rendering (not
|
|
107
|
+
just bytes) reuse it. The Slack classification is a pure exported function at
|
|
108
|
+
adapter level — the self-verify is a boot diagnostic owned by the adapter; no
|
|
109
|
+
higher-layer gate exists (or is needed) for a boot-time health verdict.
|
|
110
|
+
|
|
111
|
+
## 4. Signal vs authority compliance
|
|
112
|
+
|
|
113
|
+
**Checkbox: No — this change has no NEW block/allow surface over agent or user
|
|
114
|
+
information flow.** The ghost gate is deterministic (brittle-by-nature) logic,
|
|
115
|
+
but the only "authority" it holds is the power to make the sentinel DO NOTHING
|
|
116
|
+
— it withholds the sentinel's own recovery keypress; it cannot block a message,
|
|
117
|
+
gate information, or constrain the user or agent. The dangerous authority
|
|
118
|
+
(pressing keys into a live session) is what the sentinel already held; this
|
|
119
|
+
change narrows it, fail-safe toward inaction, exactly like the pre-existing
|
|
120
|
+
activity-indicator refusal at the same layer. The Slack half changes a
|
|
121
|
+
boot-diagnostic verdict string; no flow authority at all.
|
|
122
|
+
|
|
123
|
+
## 5. Interactions
|
|
124
|
+
|
|
125
|
+
- **Shadowing:** the gate runs AFTER stuck-detection and the min-ticks window
|
|
126
|
+
and BEFORE `fireStuckInputRecovery` — it cannot shadow detection, escalation
|
|
127
|
+
handling (which runs earlier in the flow but later in the attempt ladder), or
|
|
128
|
+
the verifyInjection race-window skip. Nothing runs after it that expects a
|
|
129
|
+
keypress to have happened (the event log records `skipped` honestly).
|
|
130
|
+
- **Double-fire:** none. The gate only ever REMOVES a fire. The one-event-per-
|
|
131
|
+
stuck-text latch (`ghostSkipLogged`) prevents log double-fire.
|
|
132
|
+
- **Races:** the plain and ANSI captures are two separate tmux calls; a pane
|
|
133
|
+
repaint between them is detected by exact text-match and resolves to
|
|
134
|
+
`inconclusive` (no press) — the race is handled by construction, not by
|
|
135
|
+
timing assumptions.
|
|
136
|
+
- **Feedback loops:** none; the gate writes no state the detector reads.
|
|
137
|
+
- **Escalation interaction:** tier-C escalation is unreachable for ghost text —
|
|
138
|
+
attempts only accrue through the gate, so four `real` verdicts must precede
|
|
139
|
+
any escalation request.
|
|
140
|
+
|
|
141
|
+
## 6. External surfaces
|
|
142
|
+
|
|
143
|
+
- **Other agents / install base:** behavior narrows only (fewer keypresses;
|
|
144
|
+
a false-red boot check goes green). No API, route, config, or template
|
|
145
|
+
changes. No new dependency.
|
|
146
|
+
- **External systems:** the Slack probe now sends `F0000000000` instead of
|
|
147
|
+
`F000SELFTEST` to `files.info` — same call count, same scope, still a
|
|
148
|
+
synthetic id that cannot match a real file.
|
|
149
|
+
- **Persistent state:** two additive `action` values in the existing
|
|
150
|
+
`stuck-input-events.jsonl` free-form log. No schema, no migration.
|
|
151
|
+
- **Timing/runtime conditions:** tmux `capture-pane -e` support (present in
|
|
152
|
+
every tmux version instar supports; a failure degrades to `inconclusive`,
|
|
153
|
+
i.e. log-only).
|
|
154
|
+
- **Operator surface (Mobile-Complete Operator Actions):** no operator-facing
|
|
155
|
+
actions added or touched.
|
|
156
|
+
|
|
157
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
158
|
+
|
|
159
|
+
**Machine-local BY DESIGN, both halves.** The sentinel observes and recovers
|
|
160
|
+
tmux panes that exist only on THIS machine's disk/terminal — the pane, the
|
|
161
|
+
capture, and the keypress are all physically local, like the rest of the
|
|
162
|
+
stuck-input/watchdog family. The Slack self-verify diagnoses THIS machine's
|
|
163
|
+
adapter boot. Neither emits user-facing notices (the sentinel's events are
|
|
164
|
+
housekeeping JSONL; the self-verify writes to the boot log), neither holds
|
|
165
|
+
durable state that could strand on topic transfer, and neither generates URLs.
|
|
166
|
+
|
|
167
|
+
## 8. Rollback cost
|
|
168
|
+
|
|
169
|
+
Single-commit revert, shipped as the next patch. No config keys, no
|
|
170
|
+
migrations, no persistent-state cleanup, no agent state repair. During a
|
|
171
|
+
rollback window the only regression users could see is the OLD behavior
|
|
172
|
+
returning (false-red Slack check line; watchdog pressing Enter at ghost text).
|
|
173
|
+
|
|
174
|
+
## Test coverage
|
|
175
|
+
|
|
176
|
+
- `tests/unit/StuckInputSentinel-ghost-text.test.ts` (21 tests): the exact live
|
|
177
|
+
F2 frame refused across 10 ticks; genuine input still recovered; null-capture
|
|
178
|
+
/ throwing-capture / raced-frame / mixed-styling all fail toward not
|
|
179
|
+
pressing; truecolor + 256-color params not misread; SGR 22 cancels dim;
|
|
180
|
+
cross-line dim state; wrapped-line ghost; sticky-ghost reset on text change;
|
|
181
|
+
codex path ungated; exactly one observability event.
|
|
182
|
+
- `tests/unit/slack-files-info-selfverify.test.ts` (12 tests): classifier both
|
|
183
|
+
sides (ok / file_not_found / invalid_arguments pass; missing_scope /
|
|
184
|
+
invalid_auth / unknown fail); probe id shape; `_selfVerify` wiring-level test
|
|
185
|
+
proving the live regression goes green and failures still fail.
|
|
186
|
+
- `tests/unit/StuckInputSentinel.test.ts`: pre-existing suite green with the
|
|
187
|
+
gate in place (default ANSI stub = plain frame = `real`), proving legacy
|
|
188
|
+
recovery behavior is preserved.
|