@herbertgao/pi-extensions 2026.8.7 → 2026.8.8
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/README.md +7 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +63 -15
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +78 -16
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +13 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +17 -6
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +4 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +154 -96
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +3 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +7 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +3 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +137 -96
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +44 -32
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +56 -34
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +50 -4
- package/node_modules/@narumitw/pi-btw/package.json +2 -2
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
- package/node_modules/pi-lens/CHANGELOG.md +90 -0
- package/node_modules/pi-lens/README.md +106 -0
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +97 -6
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +16 -6
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +14 -0
- package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +22 -1
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
- package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +8 -10
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +10 -10
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +10 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +19 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +35 -13
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +8 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +110 -23
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +117 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +16 -14
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +8 -9
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +22 -0
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +5 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +7 -2
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +27 -2
- package/node_modules/pi-lens/dist/clients/lsp/client.js +144 -5
- package/node_modules/pi-lens/dist/clients/lsp/index.js +151 -6
- package/node_modules/pi-lens/dist/clients/module-report.js +14 -1
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +18 -9
- package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +7 -2
- package/node_modules/pi-lens/dist/clients/read-guard.js +88 -19
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
- package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +44 -0
- package/node_modules/pi-lens/dist/clients/runtime-session.js +41 -0
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +47 -2
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
- package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
- package/node_modules/pi-lens/dist/index.js +1224 -393
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +10 -3
- package/node_modules/pi-lens/package.json +18 -5
- package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
- package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
- package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
- package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
- package/node_modules/pi-web-access/CHANGELOG.md +14 -0
- package/node_modules/pi-web-access/README.md +6 -5
- package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
- package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
- package/node_modules/pi-web-access/extract.ts +12 -2
- package/node_modules/pi-web-access/firecrawl.ts +18 -2
- package/node_modules/pi-web-access/github-extract.ts +65 -36
- package/node_modules/pi-web-access/index.ts +19 -4
- package/node_modules/pi-web-access/openai-search.ts +17 -6
- package/node_modules/pi-web-access/package.json +1 -1
- package/node_modules/pi-web-access/pdf-extract.ts +18 -5
- package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
- package/package.json +9 -9
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One home for "this event arrived on a ctx the SDK already invalidated"
|
|
3
|
+
* (#1925).
|
|
4
|
+
*
|
|
5
|
+
* pi invalidates a captured extension ctx when the session is replaced —
|
|
6
|
+
* `ctx.newSession()`, `ctx.fork()`, `ctx.switchSession()`, `ctx.reload()`.
|
|
7
|
+
* Every accessor on that ctx then throws from the SDK's `assertActive()`
|
|
8
|
+
* (`core/extensions/loader.js` in the installed
|
|
9
|
+
* `@earendil-works/pi-coding-agent`). An event already queued when the swap
|
|
10
|
+
* happens still reaches pi-lens, carrying the dead ctx, and the first
|
|
11
|
+
* unguarded `ctx.signal` / `ctx.ui` / `ctx.cwd` read throws.
|
|
12
|
+
*
|
|
13
|
+
* That throw does not escape into pi's loop. `ExtensionRunner.emit` wraps every
|
|
14
|
+
* handler call in a try/catch and routes the error to `emitError`
|
|
15
|
+
* (`core/extensions/runner.js:586-606` in the installed SDK), so the throw
|
|
16
|
+
* surfaces as an extension error report against pi-lens. The report names the
|
|
17
|
+
* wrong cause. It reads as a pi-lens handler bug when the real event is a
|
|
18
|
+
* benign race with a session swap, it says nothing about which handler keeps
|
|
19
|
+
* losing, and it counts nothing. This wrapper converts that noisy misattributed
|
|
20
|
+
* error into a counted, bounded, attributable skip.
|
|
21
|
+
*
|
|
22
|
+
* #1924 fixed that for `agent_settled` with an inline try/catch. #1925 found
|
|
23
|
+
* four more handlers with the same shape. Five inline copies of one policy is
|
|
24
|
+
* the parallel-state defect AGENTS.md names, so the policy lives here instead.
|
|
25
|
+
* `agent_settled` is a consumer of this path, not a special case.
|
|
26
|
+
*
|
|
27
|
+
* ## What this module actually covers
|
|
28
|
+
*
|
|
29
|
+
* Not every `pi.on` registration in `index.ts` goes through here, and the doc
|
|
30
|
+
* used to read as if they all did. Seven do today:
|
|
31
|
+
*
|
|
32
|
+
* - {@link wrapSessionEventHandler} — `tool_result`, `turn_start`,
|
|
33
|
+
* `agent_end`, `turn_end`, `agent_settled` (#1925) and `session_start`
|
|
34
|
+
* (#1929). All six return nothing, so a skipped event resolving to
|
|
35
|
+
* `undefined` is exactly their own early-exit value.
|
|
36
|
+
* - {@link wrapSessionEventHandlerWithResult} — `context` (#1929), which must
|
|
37
|
+
* hand the host back a message list on the live path.
|
|
38
|
+
*
|
|
39
|
+
* Five registrations stay unwrapped on purpose: `resources_discover`,
|
|
40
|
+
* `session_before_fork`, `tool_call`, `session_shutdown`, and `message_end`.
|
|
41
|
+
* `tests/clients/session-event-guard-sweep.test.ts` is the source of truth for
|
|
42
|
+
* that split. It scans every registration in `index.ts` and reds unless the
|
|
43
|
+
* handler is wrapped or carries a written reason, so this paragraph can go
|
|
44
|
+
* stale but the contract cannot.
|
|
45
|
+
*
|
|
46
|
+
* The wrapper does three things and nothing else:
|
|
47
|
+
*
|
|
48
|
+
* 1. **Probes once, before dispatch.** {@link probeCtxActive} answers `false`
|
|
49
|
+
* only when an `assertActive()`-wrapped accessor threw the SDK's own stale
|
|
50
|
+
* message. `undefined` (no ctx, unexpected shape, unrecognised throw) is
|
|
51
|
+
* inconclusive and always dispatches — never guess a session dead.
|
|
52
|
+
* 2. **Still classifies a stale throw that races in mid-handler.** The probe
|
|
53
|
+
* is a point-in-time read; the swap can land between the probe and the
|
|
54
|
+
* handler's own first ctx read, and for an async handler it can land at any
|
|
55
|
+
* await. Both the synchronous throw and the rejected promise are
|
|
56
|
+
* classified by {@link isStaleExtensionCtxError}.
|
|
57
|
+
* 3. **Makes the skip visible.** One bounded record and one ledger kind for
|
|
58
|
+
* the whole class, keyed by the event name so aggregation still answers
|
|
59
|
+
* WHICH handler is being skipped. A silent guard and a guard that never
|
|
60
|
+
* fires read identically from a log, which is exactly how vacuous guards
|
|
61
|
+
* survive review.
|
|
62
|
+
*
|
|
63
|
+
* Anything that is NOT the SDK's stale-ctx error propagates unchanged. The
|
|
64
|
+
* wrapper narrows nothing else: a handler bug must stay as loud as it was.
|
|
65
|
+
*/
|
|
66
|
+
import { emitBounded } from "./bounded-telemetry.js";
|
|
67
|
+
import { probeCtxActive } from "./session-lifecycle.js";
|
|
68
|
+
/**
|
|
69
|
+
* The pi SDK invalidates a captured `pi`/command ctx after a session
|
|
70
|
+
* replacement or reload; every later `pi.*` call then throws with this
|
|
71
|
+
* signature. Matched by MESSAGE — not by `===` against a captured instance —
|
|
72
|
+
* so a fire-and-forget task that races a session swap can recognise the benign
|
|
73
|
+
* stale-ctx throw and degrade to a no-op. Substring-matched on the stable
|
|
74
|
+
* "stale after session replacement or reload" phrase so it survives incidental
|
|
75
|
+
* wording changes around it.
|
|
76
|
+
*
|
|
77
|
+
* Deliberately a NARROWER match than {@link probeCtxActive}'s (which accepts
|
|
78
|
+
* the shorter "stale after session replacement", the message
|
|
79
|
+
* `ExtensionRunner.invalidate()` uses for its own probe path). Widening this
|
|
80
|
+
* one is a behavior change: it decides which throws get swallowed.
|
|
81
|
+
*/
|
|
82
|
+
export function isStaleExtensionCtxError(err) {
|
|
83
|
+
return (err instanceof Error &&
|
|
84
|
+
err.message.includes("stale after session replacement or reload"));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Record one skipped session event. Bounded per event name: the ledger counts
|
|
88
|
+
* every occurrence exactly, and only the first per event name this session
|
|
89
|
+
* also writes the detailed `latency.log` row, so a replaced session whose
|
|
90
|
+
* queue drains a hundred stale events cannot storm the log.
|
|
91
|
+
*/
|
|
92
|
+
function recordStaleSkip(eventName, detectedAt) {
|
|
93
|
+
emitBounded("session_event_stale_ctx_skip", eventName, {
|
|
94
|
+
durationMs: 0,
|
|
95
|
+
metadata: { event: eventName, detectedAt },
|
|
96
|
+
}, {
|
|
97
|
+
ledgerKind: "extension-ctx-stale",
|
|
98
|
+
risingEdgePer: "identity",
|
|
99
|
+
reason: `${eventName} skipped: extension ctx is stale (${detectedAt})`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function isThenable(value) {
|
|
103
|
+
return (typeof value?.then === "function");
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The one policy. Both public wrappers are this function with a different
|
|
107
|
+
* stale-path value, so the probe point, the classification, and the record can
|
|
108
|
+
* never drift apart between a void handler and a value-returning one.
|
|
109
|
+
*/
|
|
110
|
+
function guardSessionEvent(eventName, handler, onStaleResult, options) {
|
|
111
|
+
const skip = (event, detectedAt) => {
|
|
112
|
+
recordStaleSkip(eventName, detectedAt);
|
|
113
|
+
try {
|
|
114
|
+
options.dbg?.(`${eventName} skipped: extension ctx is stale after session replacement or reload`);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// A debug sink must never decide whether an event is handled.
|
|
118
|
+
}
|
|
119
|
+
return onStaleResult(event);
|
|
120
|
+
};
|
|
121
|
+
return (event, ctx) => {
|
|
122
|
+
// `false` is the only confirmed verdict. `undefined` means the probe
|
|
123
|
+
// could not tell, and an inconclusive probe must dispatch.
|
|
124
|
+
if (probeCtxActive(ctx) === false)
|
|
125
|
+
// SAFETY: `R` is the handler's declared return type, which is either a
|
|
126
|
+
// plain value or a promise of one. `skip` yields `Awaited<R>`, the
|
|
127
|
+
// settled form. TypeScript cannot see that a host awaiting an `R` is
|
|
128
|
+
// equally satisfied by the settled value, but every `pi.on` caller
|
|
129
|
+
// either awaits the result or ignores it, so both forms behave the
|
|
130
|
+
// same at the call site. The same reasoning covers the two casts
|
|
131
|
+
// below.
|
|
132
|
+
return skip(event, "pre-dispatch");
|
|
133
|
+
try {
|
|
134
|
+
const result = handler(event, ctx);
|
|
135
|
+
if (isThenable(result)) {
|
|
136
|
+
// Recover the rejection in place. The host awaits the same promise
|
|
137
|
+
// it would have awaited anyway; it just resolves instead.
|
|
138
|
+
// SAFETY: the recovered promise settles to `Awaited<R>`, which the
|
|
139
|
+
// host consumes exactly as it would an `R` — see the note above.
|
|
140
|
+
return Promise.resolve(result).catch((err) => {
|
|
141
|
+
if (isStaleExtensionCtxError(err))
|
|
142
|
+
return skip(event, "mid-handler");
|
|
143
|
+
throw err;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (isStaleExtensionCtxError(err))
|
|
150
|
+
// SAFETY: a synchronous handler's `R` is already its settled form,
|
|
151
|
+
// so `Awaited<R>` and `R` coincide here — see the note above.
|
|
152
|
+
return skip(event, "mid-handler");
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Wrap one `pi.on` handler so a stale ctx becomes an observable no-op instead
|
|
159
|
+
* of a throw into the host.
|
|
160
|
+
*
|
|
161
|
+
* The returned function keeps the handler's own signature and its return
|
|
162
|
+
* value, so a wrapped registration is a drop-in for the bare one. A skipped
|
|
163
|
+
* event resolves to `undefined`, which is what every handler wrapped this way
|
|
164
|
+
* already returns on its early-exit paths.
|
|
165
|
+
*
|
|
166
|
+
* Use {@link wrapSessionEventHandlerWithResult} when `undefined` is NOT the
|
|
167
|
+
* handler's own no-op value.
|
|
168
|
+
*/
|
|
169
|
+
export function wrapSessionEventHandler(eventName, handler, options = {}) {
|
|
170
|
+
return guardSessionEvent(eventName, handler, () => undefined, options);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* {@link wrapSessionEventHandler} for a handler whose return value the host
|
|
174
|
+
* consumes (#1929).
|
|
175
|
+
*
|
|
176
|
+
* The live path returns exactly what the handler returned. The stale path
|
|
177
|
+
* returns `onStaleResult(event)` instead of assuming `undefined`, so a handler
|
|
178
|
+
* whose no-op value is something else does not get one silently substituted.
|
|
179
|
+
* Probe, classification, and the bounded record are identical to the void
|
|
180
|
+
* wrapper's — they are the same function underneath.
|
|
181
|
+
*/
|
|
182
|
+
export function wrapSessionEventHandlerWithResult(eventName, handler, options) {
|
|
183
|
+
return guardSessionEvent(eventName, handler, options.onStaleResult, options);
|
|
184
|
+
}
|
|
@@ -391,11 +391,15 @@ function classifyEntry(entry, fullPath, cfg, probeCache, skipCounters) {
|
|
|
391
391
|
return { recurseInto: fullPath };
|
|
392
392
|
}
|
|
393
393
|
if (entry.isFile()) {
|
|
394
|
-
|
|
395
|
-
|
|
394
|
+
// #1974: extension gate before isIgnored — isIgnored recompiles minimatch
|
|
395
|
+
// patterns per ancestor dir per call, so a large ignored-but-not-dir-
|
|
396
|
+
// prunable pile (e.g. wal/*.log) should never pay that cost for files
|
|
397
|
+
// the extension gate would have dropped anyway.
|
|
396
398
|
const ext = path.extname(entry.name).toLowerCase();
|
|
397
399
|
if (!extensions.has(ext))
|
|
398
400
|
return {};
|
|
401
|
+
if (ignoreMatcher.isIgnored(fullPath, false))
|
|
402
|
+
return {};
|
|
399
403
|
// Skip if this is a build artifact or generated/codegen output.
|
|
400
404
|
// #1107: counted separately (both are observability-only — neither
|
|
401
405
|
// changes what gets skipped) so an invisible coverage hole (a real
|
|
@@ -178,9 +178,12 @@ function makeSourceCountVisitor(state, ignoreMatcher, limit, maxEntries) {
|
|
|
178
178
|
}
|
|
179
179
|
return recurse ? "recurse" : "skip";
|
|
180
180
|
}
|
|
181
|
+
// #1974: extension-pattern gate before isIgnored — isIgnored recompiles
|
|
182
|
+
// minimatch patterns per ancestor dir per call, so it should only run for
|
|
183
|
+
// entries the cheap regex already flags as source.
|
|
181
184
|
if (entry.isFile() &&
|
|
182
|
-
|
|
183
|
-
|
|
185
|
+
SOURCE_FILE_PATTERN.test(entry.name) &&
|
|
186
|
+
!ignoreMatcher.isIgnored(fullPath, false)) {
|
|
184
187
|
state.count += 1;
|
|
185
188
|
if (state.count > limit)
|
|
186
189
|
return "stop";
|