@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.
Files changed (99) hide show
  1. package/README.md +7 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +63 -15
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +78 -16
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +13 -0
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +17 -6
  8. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  9. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +4 -4
  10. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +154 -96
  11. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +3 -0
  12. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +10 -2
  13. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +7 -5
  14. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/tool-schema.md +11 -5
  15. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +3 -0
  16. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  17. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +137 -96
  18. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +44 -32
  19. package/node_modules/@juicesharp/rpiv-ask-user-question/state/selectors/projections.ts +22 -15
  20. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state-reducer.ts +11 -1
  21. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/response-envelope.ts +11 -0
  22. package/node_modules/@juicesharp/rpiv-ask-user-question/tool/types.ts +11 -0
  23. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/multi-select-view.ts +31 -17
  24. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/preview/preview-block-renderer.ts +15 -6
  25. package/node_modules/@juicesharp/rpiv-ask-user-question/view/components/wrapping-select.ts +15 -6
  26. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +56 -34
  27. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +50 -4
  28. package/node_modules/@narumitw/pi-btw/package.json +2 -2
  29. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +44 -1856
  30. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  31. package/node_modules/@pi-plugins/fast-mode/package.json +4 -6
  32. package/node_modules/pi-lens/CHANGELOG.md +90 -0
  33. package/node_modules/pi-lens/README.md +106 -0
  34. package/node_modules/pi-lens/dist/clients/bash-file-access.js +97 -6
  35. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +16 -6
  36. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +14 -0
  37. package/node_modules/pi-lens/dist/clients/cache-observability.js +353 -76
  38. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +22 -1
  39. package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +155 -0
  40. package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +8 -2
  41. package/node_modules/pi-lens/dist/clients/deps/typebox.js +8 -3
  42. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +9 -6
  43. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +9 -7
  44. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +8 -6
  45. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +8 -10
  46. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +10 -10
  47. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +20 -5
  48. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +44 -11
  49. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +10 -15
  50. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +19 -11
  51. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +35 -13
  52. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +8 -8
  53. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +110 -23
  54. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +12 -7
  55. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +117 -1
  56. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +16 -14
  57. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +8 -9
  58. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +22 -0
  59. package/node_modules/pi-lens/dist/clients/jscpd-client.js +5 -2
  60. package/node_modules/pi-lens/dist/clients/language-profile.js +7 -2
  61. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +27 -2
  62. package/node_modules/pi-lens/dist/clients/lsp/client.js +144 -5
  63. package/node_modules/pi-lens/dist/clients/lsp/index.js +151 -6
  64. package/node_modules/pi-lens/dist/clients/module-report.js +14 -1
  65. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +114 -23
  66. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +18 -9
  67. package/node_modules/pi-lens/dist/clients/project-report.js +62 -27
  68. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +7 -2
  69. package/node_modules/pi-lens/dist/clients/read-guard.js +88 -19
  70. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +194 -31
  71. package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +21 -0
  72. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +6 -3
  73. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +44 -0
  74. package/node_modules/pi-lens/dist/clients/runtime-session.js +41 -0
  75. package/node_modules/pi-lens/dist/clients/runtime-turn.js +47 -2
  76. package/node_modules/pi-lens/dist/clients/session-event-guard.js +184 -0
  77. package/node_modules/pi-lens/dist/clients/source-filter.js +6 -2
  78. package/node_modules/pi-lens/dist/clients/startup-scan.js +5 -2
  79. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +1 -0
  80. package/node_modules/pi-lens/dist/index.js +1224 -393
  81. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +10 -3
  82. package/node_modules/pi-lens/package.json +18 -5
  83. package/node_modules/pi-lens/scripts/install-selftest.mjs +99 -11
  84. package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +75 -0
  85. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +285 -0
  86. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +233 -0
  87. package/node_modules/pi-web-access/CHANGELOG.md +14 -0
  88. package/node_modules/pi-web-access/README.md +6 -5
  89. package/node_modules/pi-web-access/chrome-cookies.ts +86 -15
  90. package/node_modules/pi-web-access/data-uri-sanitize.ts +406 -0
  91. package/node_modules/pi-web-access/extract.ts +12 -2
  92. package/node_modules/pi-web-access/firecrawl.ts +18 -2
  93. package/node_modules/pi-web-access/github-extract.ts +65 -36
  94. package/node_modules/pi-web-access/index.ts +19 -4
  95. package/node_modules/pi-web-access/openai-search.ts +17 -6
  96. package/node_modules/pi-web-access/package.json +1 -1
  97. package/node_modules/pi-web-access/pdf-extract.ts +18 -5
  98. package/node_modules/pi-web-access/ssrf-protection.ts +12 -2
  99. 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
- if (ignoreMatcher.isIgnored(fullPath, false))
395
- return {};
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
- !ignoreMatcher.isIgnored(fullPath, false) &&
183
- SOURCE_FILE_PATTERN.test(entry.name)) {
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";
@@ -50,6 +50,7 @@ export function logTreeSitterCacheStats(options) {
50
50
  totalBytes: options.stats.totalBytes,
51
51
  totalLines: options.stats.totalLines,
52
52
  },
53
+ ...(options.astGrep ? { astGrep: options.astGrep } : {}),
53
54
  },
54
55
  });
55
56
  }