@quandev104/pi-style 0.1.1

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 (69) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +193 -0
  4. package/dist/extensions/pi-style.js +7897 -0
  5. package/dist/extensions/pi-style.js.map +1 -0
  6. package/extension-src/pi-style/app/command-service.ts +244 -0
  7. package/extension-src/pi-style/app/commands.ts +12 -0
  8. package/extension-src/pi-style/app/config-storage.ts +98 -0
  9. package/extension-src/pi-style/app/doctor.ts +53 -0
  10. package/extension-src/pi-style/app/index.ts +322 -0
  11. package/extension-src/pi-style/app/providers.ts +268 -0
  12. package/extension-src/pi-style/app/render-scheduler.ts +48 -0
  13. package/extension-src/pi-style/app/runtime.ts +404 -0
  14. package/extension-src/pi-style/app/snapshot.ts +15 -0
  15. package/extension-src/pi-style/domain/capabilities.ts +34 -0
  16. package/extension-src/pi-style/domain/config-authorization.ts +20 -0
  17. package/extension-src/pi-style/domain/config-diagnostics.ts +20 -0
  18. package/extension-src/pi-style/domain/config-diff.ts +30 -0
  19. package/extension-src/pi-style/domain/config-migrations.ts +52 -0
  20. package/extension-src/pi-style/domain/config-normalization.ts +567 -0
  21. package/extension-src/pi-style/domain/config-presets.ts +51 -0
  22. package/extension-src/pi-style/domain/config-types.ts +115 -0
  23. package/extension-src/pi-style/domain/providers.ts +37 -0
  24. package/extension-src/pi-style/domain/status-presets.ts +60 -0
  25. package/extension-src/pi-style/domain/status-renderer.ts +142 -0
  26. package/extension-src/pi-style/domain/status.ts +430 -0
  27. package/extension-src/pi-style/domain/theme.ts +232 -0
  28. package/extension-src/pi-style/features/.gitkeep +0 -0
  29. package/extension-src/pi-style/features/editor/index.ts +304 -0
  30. package/extension-src/pi-style/features/messages/boxed-block.ts +74 -0
  31. package/extension-src/pi-style/features/messages/index.ts +145 -0
  32. package/extension-src/pi-style/features/messages/special-blocks.ts +281 -0
  33. package/extension-src/pi-style/features/startup/index.ts +564 -0
  34. package/extension-src/pi-style/features/startup/logo.ts +151 -0
  35. package/extension-src/pi-style/features/status-line/index.ts +319 -0
  36. package/extension-src/pi-style/features/tools/boxed/bash.ts +347 -0
  37. package/extension-src/pi-style/features/tools/boxed/edit.ts +113 -0
  38. package/extension-src/pi-style/features/tools/boxed/fallback.ts +67 -0
  39. package/extension-src/pi-style/features/tools/boxed/find.ts +65 -0
  40. package/extension-src/pi-style/features/tools/boxed/grep.ts +108 -0
  41. package/extension-src/pi-style/features/tools/boxed/index.ts +64 -0
  42. package/extension-src/pi-style/features/tools/boxed/ls.ts +65 -0
  43. package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +190 -0
  44. package/extension-src/pi-style/features/tools/boxed/read.ts +204 -0
  45. package/extension-src/pi-style/features/tools/boxed/session-config.ts +45 -0
  46. package/extension-src/pi-style/features/tools/boxed/shared.ts +157 -0
  47. package/extension-src/pi-style/features/tools/boxed/write.ts +89 -0
  48. package/extension-src/pi-style/features/tools/index.ts +480 -0
  49. package/extension-src/pi-style/pi/commands.ts +17 -0
  50. package/extension-src/pi-style/pi/compatibility-coordinator.ts +195 -0
  51. package/extension-src/pi-style/pi/compatibility-probe.ts +645 -0
  52. package/extension-src/pi-style/pi/compatibility-registry.ts +329 -0
  53. package/extension-src/pi-style/pi/config-host.ts +52 -0
  54. package/extension-src/pi-style/pi/config-session.ts +105 -0
  55. package/extension-src/pi-style/pi/index.ts +77 -0
  56. package/extension-src/pi-style/pi/operational-state.ts +29 -0
  57. package/extension-src/pi-style/pi/session-coordinator.ts +187 -0
  58. package/extension-src/pi-style/pi/session-usage.ts +62 -0
  59. package/extension-src/pi-style/pi/startup-resources.ts +70 -0
  60. package/extension-src/pi-style/shared/.gitkeep +0 -0
  61. package/extension-src/pi-style/shared/ansi.ts +386 -0
  62. package/extension-src/pi-style/shared/box.ts +805 -0
  63. package/extension-src/pi-style/shared/disposable-store.ts +28 -0
  64. package/extension-src/pi-style/shared/elapsed.ts +88 -0
  65. package/extension-src/pi-style/shared/render-budget.ts +367 -0
  66. package/extension-src/pi-style/shared/split-diff.ts +692 -0
  67. package/extension-src/pi-style/shared/theme-extras.ts +292 -0
  68. package/package.json +68 -0
  69. package/themes/.gitkeep +0 -0
@@ -0,0 +1,645 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import {
5
+ AssistantMessageComponent,
6
+ BranchSummaryMessageComponent,
7
+ CompactionSummaryMessageComponent,
8
+ CustomMessageComponent,
9
+ SkillInvocationMessageComponent,
10
+ ToolExecutionComponent,
11
+ UserMessageComponent,
12
+ } from "@earendil-works/pi-coding-agent";
13
+ import { decorateMessageRender, type MessageDecorationSnapshot } from "../features/messages/index.js";
14
+ import { renderSpecialMessageBlock, type SpecialBlockSubtype } from "../features/messages/special-blocks.js";
15
+ import { createToolDecorationOwner } from "../features/tools/index.js";
16
+ import {
17
+ type CompatibilityRecord,
18
+ currentGeneration,
19
+ installDelegatingPatch,
20
+ nextGeneration,
21
+ } from "./compatibility-registry.js";
22
+
23
+ const PI_VERSION_RANGE = ">=0.83.0 <0.84.0";
24
+ const TRUSTED_PI_VERSION = "0.83.0";
25
+ const reportStates = new WeakMap<
26
+ object,
27
+ { records: CompatibilityRecord[]; toolOwner: ReturnType<typeof createToolDecorationOwner> | undefined }
28
+ >();
29
+ // These fingerprints are coupled to the installed 0.83.0 package source. A matching
30
+ // name/arity/hash is evidence of the shipped native method, not a cryptographic proof:
31
+ // code loaded before this module could spoof the same function source. We therefore
32
+ // fail closed on every unrecorded Pi build and never use module-load capture as trust.
33
+ export const TRUSTED_NATIVE_FINGERPRINTS: Readonly<Record<string, string>> = Object.freeze({
34
+ "native-user-message:render": "b442a17c",
35
+ "native-assistant-message:render": "2a39243f",
36
+ "native-compaction-message:updateDisplay": "f8c44e78",
37
+ "native-branch-message:updateDisplay": "415d57b7",
38
+ "native-skill-message:updateDisplay": "48099ea6",
39
+ "native-custom-message:rebuild": "76ae2e3a",
40
+ "tool-call-renderer:getCallRenderer": "951ea0e0",
41
+ "tool-result-renderer:getResultRenderer": "8a25cd71",
42
+ });
43
+
44
+ export const CERTIFICATION_TABLE = Object.freeze({
45
+ "0.83.0": Object.freeze({
46
+ "native-user-message:render": Object.freeze({
47
+ feature: "messages",
48
+ subtype: "native-user-message",
49
+ target: UserMessageComponent.prototype,
50
+ method: "render",
51
+ writable: true,
52
+ configurable: true,
53
+ name: "render",
54
+ arity: 1,
55
+ fingerprint: TRUSTED_NATIVE_FINGERPRINTS["native-user-message:render"],
56
+ adapterId: "message-prefix-osc133-v1",
57
+ status: "certified" as const,
58
+ }),
59
+ "native-assistant-message:render": Object.freeze({
60
+ feature: "messages",
61
+ subtype: "native-assistant-message",
62
+ target: AssistantMessageComponent.prototype,
63
+ method: "render",
64
+ writable: true,
65
+ configurable: true,
66
+ name: "render",
67
+ arity: 1,
68
+ fingerprint: TRUSTED_NATIVE_FINGERPRINTS["native-assistant-message:render"],
69
+ adapterId: "message-prefix-osc133-v1",
70
+ status: "certified" as const,
71
+ }),
72
+ "tool-call-renderer:getCallRenderer": Object.freeze({
73
+ feature: "tools",
74
+ subtype: "tool-call-renderer",
75
+ target: ToolExecutionComponent.prototype,
76
+ method: "getCallRenderer",
77
+ writable: true,
78
+ configurable: true,
79
+ name: "getCallRenderer",
80
+ arity: 0,
81
+ fingerprint: TRUSTED_NATIVE_FINGERPRINTS["tool-call-renderer:getCallRenderer"],
82
+ adapterId: "tool-renderer-component-v1",
83
+ status: "certified" as const,
84
+ }),
85
+ "tool-result-renderer:getResultRenderer": Object.freeze({
86
+ feature: "tools",
87
+ subtype: "tool-result-renderer",
88
+ target: ToolExecutionComponent.prototype,
89
+ method: "getResultRenderer",
90
+ writable: true,
91
+ configurable: true,
92
+ name: "getResultRenderer",
93
+ arity: 0,
94
+ fingerprint: TRUSTED_NATIVE_FINGERPRINTS["tool-result-renderer:getResultRenderer"],
95
+ adapterId: "tool-renderer-component-v1",
96
+ status: "certified" as const,
97
+ }),
98
+ "native-compaction-message:updateDisplay": Object.freeze({
99
+ feature: "messages",
100
+ subtype: "native-compaction-message",
101
+ target: CompactionSummaryMessageComponent.prototype,
102
+ method: "updateDisplay",
103
+ writable: true,
104
+ configurable: true,
105
+ adapterId: "message-block-boxed-v1",
106
+ status: "certified" as const,
107
+ }),
108
+ "native-branch-message:updateDisplay": Object.freeze({
109
+ feature: "messages",
110
+ subtype: "native-branch-message",
111
+ target: BranchSummaryMessageComponent.prototype,
112
+ method: "updateDisplay",
113
+ writable: true,
114
+ configurable: true,
115
+ adapterId: "message-block-boxed-v1",
116
+ status: "certified" as const,
117
+ }),
118
+ "native-skill-message:updateDisplay": Object.freeze({
119
+ feature: "messages",
120
+ subtype: "native-skill-message",
121
+ target: SkillInvocationMessageComponent.prototype,
122
+ method: "updateDisplay",
123
+ writable: true,
124
+ configurable: true,
125
+ adapterId: "message-block-boxed-v1",
126
+ status: "certified" as const,
127
+ }),
128
+ "native-custom-message:rebuild": Object.freeze({
129
+ feature: "messages",
130
+ subtype: "native-custom-message",
131
+ target: CustomMessageComponent.prototype,
132
+ method: "rebuild",
133
+ writable: true,
134
+ configurable: true,
135
+ adapterId: "message-block-boxed-v1",
136
+ status: "certified" as const,
137
+ }),
138
+ }),
139
+ });
140
+
141
+ export interface CompatibilityDescriptorSnapshot {
142
+ readonly kind: "data" | "accessor";
143
+ readonly value?: unknown;
144
+ readonly get?: unknown;
145
+ readonly set?: unknown;
146
+ readonly writable?: boolean;
147
+ readonly enumerable: boolean;
148
+ readonly configurable: boolean;
149
+ }
150
+
151
+ export interface CompatibilityRecordSnapshot {
152
+ readonly feature: CompatibilityRecord["feature"];
153
+ readonly subtype: CompatibilityRecord["subtype"];
154
+ readonly method: PropertyKey;
155
+ readonly shape: string;
156
+ readonly piVersion: string;
157
+ readonly versionRange: string;
158
+ readonly generation: number;
159
+ readonly disposed: boolean;
160
+ readonly diagnostic: string | undefined;
161
+ }
162
+
163
+ export interface CompatibilityProbeReport {
164
+ attemptedVersion: string;
165
+ matchedCertifiedVersion: string | undefined;
166
+ certificationTable: typeof CERTIFICATION_TABLE;
167
+ piVersion: string;
168
+ versionRange: string;
169
+ generation: number;
170
+ recordSnapshots: readonly CompatibilityRecordSnapshot[];
171
+ unsupported: ReadonlyArray<{ subtype: string; method: PropertyKey; reason: string }>;
172
+ delegationMarkers: readonly string[];
173
+ certification: readonly {
174
+ readonly version: string;
175
+ readonly attemptedVersion: string;
176
+ readonly matchedCertifiedVersion: string | undefined;
177
+ readonly expected: unknown;
178
+ readonly actualPreinstall: CompatibilityDescriptorSnapshot | undefined;
179
+ readonly feature: CompatibilityRecord["feature"];
180
+ readonly subtype: CompatibilityRecord["subtype"];
181
+ readonly target: object;
182
+ readonly method: string;
183
+ readonly descriptor: CompatibilityDescriptorSnapshot | undefined;
184
+ readonly name: string | undefined;
185
+ readonly arity: number | undefined;
186
+ readonly fingerprint: string | undefined;
187
+ readonly adapterId: string | undefined;
188
+ readonly status: "certified" | "native-fallback";
189
+ readonly fallbackReason?: string;
190
+ }[];
191
+ getRuntimeDiagnostics: () => ReadonlyMap<string, number>;
192
+ getFinalDiagnostics: () => Readonly<import("../features/tools/index.js").ToolDiagnosticArchive> | undefined;
193
+ getActiveToolRecordCount: () => number;
194
+ disposeOwner: () => void;
195
+ }
196
+
197
+ export interface TargetSpec {
198
+ feature: CompatibilityRecord["feature"];
199
+ subtype: CompatibilityRecord["subtype"];
200
+ target: object;
201
+ method: string;
202
+ adapterId: string | undefined;
203
+ status: "certified" | "native-fallback";
204
+ fallbackReason?: string;
205
+ }
206
+
207
+ export function fingerprint(value: unknown): string | undefined {
208
+ if (typeof value !== "function") return undefined;
209
+ let hash = 2166136261;
210
+ for (const character of Function.prototype.toString.call(value)) {
211
+ hash ^= character.charCodeAt(0);
212
+ hash = Math.imul(hash, 16777619) >>> 0;
213
+ }
214
+ return hash.toString(16).padStart(8, "0");
215
+ }
216
+
217
+ function trustedNativeIdentity(spec: TargetSpec, piVersion: string | undefined): unknown {
218
+ if (piVersion !== TRUSTED_PI_VERSION) return undefined;
219
+ const descriptor = Object.getOwnPropertyDescriptor(spec.target, spec.method);
220
+ const value = descriptor?.value;
221
+ const key = `${spec.subtype}:${spec.method}`;
222
+ if (
223
+ descriptor?.writable !== true ||
224
+ descriptor.configurable !== true ||
225
+ typeof value !== "function" ||
226
+ value.name !== spec.method ||
227
+ value.length !== (spec.method === "render" ? 1 : 0) ||
228
+ fingerprint(value) !== TRUSTED_NATIVE_FINGERPRINTS[key]
229
+ )
230
+ return undefined;
231
+ return value;
232
+ }
233
+
234
+ export const targetSpecs: readonly TargetSpec[] = [
235
+ {
236
+ feature: "messages",
237
+ subtype: "native-user-message",
238
+ target: UserMessageComponent.prototype,
239
+ method: "render",
240
+ adapterId: "message-prefix-osc133-v1",
241
+ status: "certified",
242
+ },
243
+ {
244
+ feature: "messages",
245
+ subtype: "native-assistant-message",
246
+ target: AssistantMessageComponent.prototype,
247
+ method: "render",
248
+ adapterId: "message-prefix-osc133-v1",
249
+ status: "certified",
250
+ },
251
+ {
252
+ feature: "messages",
253
+ subtype: "native-compaction-message",
254
+ target: CompactionSummaryMessageComponent.prototype,
255
+ method: "updateDisplay",
256
+ adapterId: "message-block-boxed-v1",
257
+ status: "certified",
258
+ },
259
+ {
260
+ feature: "messages",
261
+ subtype: "native-branch-message",
262
+ target: BranchSummaryMessageComponent.prototype,
263
+ method: "updateDisplay",
264
+ adapterId: "message-block-boxed-v1",
265
+ status: "certified",
266
+ },
267
+ {
268
+ feature: "messages",
269
+ subtype: "native-skill-message",
270
+ target: SkillInvocationMessageComponent.prototype,
271
+ method: "updateDisplay",
272
+ adapterId: "message-block-boxed-v1",
273
+ status: "certified",
274
+ },
275
+ {
276
+ feature: "messages",
277
+ subtype: "native-custom-message",
278
+ target: CustomMessageComponent.prototype,
279
+ method: "rebuild",
280
+ adapterId: "message-block-boxed-v1",
281
+ status: "certified",
282
+ },
283
+ {
284
+ feature: "tools",
285
+ subtype: "tool-call-renderer",
286
+ target: ToolExecutionComponent.prototype,
287
+ method: "getCallRenderer",
288
+ adapterId: "tool-renderer-component-v1",
289
+ status: "certified",
290
+ },
291
+ {
292
+ feature: "tools",
293
+ subtype: "tool-result-renderer",
294
+ target: ToolExecutionComponent.prototype,
295
+ method: "getResultRenderer",
296
+ adapterId: "tool-renderer-component-v1",
297
+ status: "certified",
298
+ },
299
+ ];
300
+
301
+ export interface PiVersionResolution {
302
+ resolvePackageEntry?: (packageName: string) => string;
303
+ readFile?: (path: string) => string;
304
+ }
305
+
306
+ export function detectPiVersion(resolution: PiVersionResolution = {}): {
307
+ version: string | undefined;
308
+ diagnostic?: string;
309
+ } {
310
+ const resolvePackageEntry =
311
+ resolution.resolvePackageEntry ??
312
+ ((name: string) => {
313
+ try {
314
+ return fileURLToPath(new URL(import.meta.resolve(name)));
315
+ } catch (error) {
316
+ throw new Error(
317
+ `public package entry resolution failed: ${error instanceof Error ? error.message : String(error)}`,
318
+ );
319
+ }
320
+ });
321
+ const readFile = resolution.readFile ?? ((path: string) => readFileSync(path, "utf8"));
322
+ try {
323
+ const entry = resolvePackageEntry("@earendil-works/pi-coding-agent");
324
+ let directory = dirname(entry);
325
+ for (;;) {
326
+ const packagePath = join(directory, "package.json");
327
+ try {
328
+ const packageJson = JSON.parse(readFile(packagePath)) as { name?: string; version?: string };
329
+ if (packageJson.name === "@earendil-works/pi-coding-agent" && typeof packageJson.version === "string")
330
+ return { version: packageJson.version };
331
+ } catch {
332
+ // Walk upward only; package exports may place the entry below the package root.
333
+ }
334
+ const parent = dirname(directory);
335
+ if (parent === directory) break;
336
+ directory = parent;
337
+ }
338
+ return { version: undefined, diagnostic: "Pi package version was not found" };
339
+ } catch (error) {
340
+ return {
341
+ version: undefined,
342
+ diagnostic: `Pi package version detection failed: ${error instanceof Error ? error.message : String(error)}`,
343
+ };
344
+ }
345
+ }
346
+
347
+ function descriptorSnapshot(descriptor: PropertyDescriptor | undefined): CompatibilityDescriptorSnapshot | undefined {
348
+ if (!descriptor) return undefined;
349
+ return Object.freeze(
350
+ "value" in descriptor
351
+ ? {
352
+ kind: "data",
353
+ value: descriptor.value,
354
+ writable: descriptor.writable === true,
355
+ enumerable: descriptor.enumerable === true,
356
+ configurable: descriptor.configurable === true,
357
+ }
358
+ : {
359
+ kind: "accessor",
360
+ get: descriptor.get,
361
+ set: descriptor.set,
362
+ enumerable: descriptor.enumerable === true,
363
+ configurable: descriptor.configurable === true,
364
+ },
365
+ );
366
+ }
367
+
368
+ function certificationRecord(
369
+ spec: TargetSpec,
370
+ attemptedVersion: string | undefined,
371
+ evidence = Object.getOwnPropertyDescriptor(spec.target, spec.method),
372
+ ) {
373
+ const descriptor = evidence;
374
+ const value = descriptor?.value;
375
+ return {
376
+ version: attemptedVersion ?? "unknown",
377
+ attemptedVersion: attemptedVersion ?? "unknown",
378
+ matchedCertifiedVersion: attemptedVersion === TRUSTED_PI_VERSION ? TRUSTED_PI_VERSION : undefined,
379
+ expected: undefined,
380
+ actualPreinstall: descriptorSnapshot(descriptor),
381
+ feature: spec.feature,
382
+ subtype: spec.subtype,
383
+ target: spec.target,
384
+ method: spec.method,
385
+ descriptor: descriptorSnapshot(descriptor),
386
+ name: typeof value === "function" ? value.name : undefined,
387
+ arity: typeof value === "function" ? value.length : undefined,
388
+ fingerprint: fingerprint(value),
389
+ adapterId: spec.adapterId,
390
+ status: spec.status,
391
+ ...(spec.fallbackReason ? { fallbackReason: spec.fallbackReason } : {}),
392
+ };
393
+ }
394
+
395
+ function versionInRange(version: string | undefined): boolean {
396
+ return version === TRUSTED_PI_VERSION;
397
+ }
398
+
399
+ function shape(target: object, method: string): boolean {
400
+ const descriptor = Object.getOwnPropertyDescriptor(target, method);
401
+ return typeof descriptor?.value === "function" && descriptor.writable === true && descriptor.configurable === true;
402
+ }
403
+
404
+ export interface CompatibilityProbeOptions {
405
+ markers?: Set<string>;
406
+ config?: Readonly<{
407
+ messages: { enabled: boolean; userPrefix: boolean; assistantPrefix: boolean; specialBlocks: boolean };
408
+ tools: { enabled: boolean; style: string; maxCollapsedLines: number; maxExpandedLines: number; dimOutput: boolean };
409
+ preset: string;
410
+ }>;
411
+ toolSnapshot?: Readonly<{ callMarker?: string; resultMarker?: string; style?: "marker" | "compact-box" }>;
412
+ messageSnapshot?: MessageDecorationSnapshot;
413
+ }
414
+
415
+ function isSpecialBlock(spec: TargetSpec): boolean {
416
+ return spec.feature === "messages" && spec.status === "certified" && spec.adapterId === "message-block-boxed-v1";
417
+ }
418
+
419
+ function createFallbackRecord(
420
+ spec: TargetSpec,
421
+ piVersion: string | undefined,
422
+ generation: number,
423
+ reason: string,
424
+ ): CompatibilityRecord {
425
+ return {
426
+ feature: spec.feature,
427
+ subtype: spec.subtype,
428
+ target: spec.target,
429
+ owner: spec.target,
430
+ method: spec.method,
431
+ originalIdentity: Reflect.get(spec.target, spec.method),
432
+ piVersion: piVersion ?? "unknown",
433
+ versionRange: PI_VERSION_RANGE,
434
+ shape: "unsupported",
435
+ diagnostic: reason,
436
+ generation,
437
+ disposed: true,
438
+ disposer: () => {},
439
+ };
440
+ }
441
+
442
+ function probeDiagnostic(spec: TargetSpec, piVersion: string | undefined, identity: unknown): string {
443
+ if (!versionInRange(piVersion)) return "Pi version is unknown or outside the recorded 0.83.0 support build";
444
+ if (!shape(spec.target, spec.method)) return "target method shape is not an own writable/configurable function";
445
+ if (identity === undefined) return "recorded 0.83.0 native fingerprint, name, or arity did not match";
446
+ return "exact native identity verified; certified guarded decoration enabled";
447
+ }
448
+
449
+ function surfaceDisabled(spec: TargetSpec, config: CompatibilityProbeOptions["config"]): boolean {
450
+ if (!config) return false;
451
+ if (spec.feature === "tools") return !config.tools.enabled;
452
+ if (!config.messages.enabled) return true;
453
+ if (spec.subtype === "native-user-message") return !config.messages.userPrefix;
454
+ if (spec.subtype === "native-assistant-message") return !config.messages.assistantPrefix;
455
+ if (isSpecialBlock(spec)) return !config.messages.specialBlocks;
456
+ return true;
457
+ }
458
+
459
+ function probeSpec(options: {
460
+ spec: TargetSpec;
461
+ piVersion: string | undefined;
462
+ generation: number;
463
+ markers: Set<string>;
464
+ config?: CompatibilityProbeOptions["config"];
465
+ messageSnapshot: MessageDecorationSnapshot | undefined;
466
+ toolOwner: ReturnType<typeof createToolDecorationOwner> | undefined;
467
+ }) {
468
+ const { spec, piVersion, generation, markers, config, toolOwner, messageSnapshot } = options;
469
+ const identity = trustedNativeIdentity(spec, piVersion);
470
+ const diagnostic = probeDiagnostic(spec, piVersion, identity);
471
+ const disabled = surfaceDisabled(spec, config);
472
+ if (disabled) {
473
+ const reason = "native fallback: surface disabled by normalized configuration";
474
+ return { record: createFallbackRecord(spec, piVersion, generation, reason), reason, fallback: true };
475
+ }
476
+ const result = installDelegatingPatch({
477
+ feature: spec.feature,
478
+ subtype: spec.subtype,
479
+ target: spec.target,
480
+ method: spec.method,
481
+ piVersion: piVersion ?? "unknown",
482
+ versionRange: PI_VERSION_RANGE,
483
+ shape: identity !== undefined && versionInRange(piVersion) && shape(spec.target, spec.method),
484
+ generation,
485
+ expectedIdentity: identity,
486
+ hasExpectedIdentity: true,
487
+ diagnostic,
488
+ delegate: (original, target, args) => {
489
+ markers.add(`${spec.subtype}:delegated`);
490
+ if (spec.feature === "tools")
491
+ return (
492
+ toolOwner?.decorateToolRendererSelection(
493
+ spec.subtype as "tool-call-renderer" | "tool-result-renderer",
494
+ original,
495
+ target,
496
+ args,
497
+ ) ?? Reflect.apply(original as (...values: unknown[]) => unknown, target, args)
498
+ );
499
+ if (spec.subtype === "native-user-message" || spec.subtype === "native-assistant-message")
500
+ return decorateMessageRender(
501
+ spec.subtype as "native-user-message" | "native-assistant-message",
502
+ original,
503
+ target,
504
+ args,
505
+ messageSnapshot,
506
+ );
507
+ return renderSpecialMessageBlock(spec.subtype as SpecialBlockSubtype, original, target, args);
508
+ },
509
+ });
510
+ return {
511
+ record: result.record,
512
+ reason: result.reason ?? result.record.diagnostic ?? "skipped",
513
+ fallback: result.status === "skipped",
514
+ };
515
+ }
516
+
517
+ export function probePiCompatibility(
518
+ piVersion: string | undefined,
519
+ options: Set<string> | CompatibilityProbeOptions = new Set(),
520
+ ): CompatibilityProbeReport {
521
+ const markers = options instanceof Set ? options : (options.markers ?? new Set<string>());
522
+ const generation = nextGeneration();
523
+ const toolSpecs = targetSpecs.filter((spec) => spec.feature === "tools");
524
+ let toolOwner: ReturnType<typeof createToolDecorationOwner> | undefined;
525
+ if (
526
+ toolSpecs.some((spec) => trustedNativeIdentity(spec, piVersion) !== undefined) &&
527
+ (options instanceof Set || options.config?.tools.enabled !== false)
528
+ ) {
529
+ toolOwner = createToolDecorationOwner(options instanceof Set ? {} : options.toolSnapshot);
530
+ }
531
+ const evidence = Object.freeze(
532
+ targetSpecs.map((spec) =>
533
+ Object.freeze({
534
+ subtype: spec.subtype,
535
+ method: spec.method,
536
+ target: spec.target,
537
+ descriptor: Object.getOwnPropertyDescriptor(spec.target, spec.method),
538
+ value: Object.getOwnPropertyDescriptor(spec.target, spec.method)?.value,
539
+ }),
540
+ ),
541
+ );
542
+ const evidenceByKey = new Map(evidence.map((item) => [`${item.subtype}:${String(item.method)}`, item]));
543
+ const records: CompatibilityRecord[] = [];
544
+ const unsupported: Array<{ subtype: string; method: PropertyKey; reason: string }> = [];
545
+ const certification: NonNullable<CompatibilityProbeReport["certification"]>[number][] = [];
546
+ for (const spec of targetSpecs) {
547
+ const captured = evidenceByKey.get(`${spec.subtype}:${String(spec.method)}`);
548
+ const preinstallDescriptor = captured?.descriptor;
549
+ const result = probeSpec({
550
+ messageSnapshot: options instanceof Set ? undefined : options.messageSnapshot,
551
+ spec,
552
+ piVersion,
553
+ generation,
554
+ markers,
555
+ config: options instanceof Set ? undefined : options.config,
556
+ toolOwner,
557
+ });
558
+ records.push(result.record);
559
+ const certificate = certificationRecord(spec, piVersion, preinstallDescriptor);
560
+ certification.push({
561
+ ...certificate,
562
+ attemptedVersion: piVersion ?? "unknown",
563
+ matchedCertifiedVersion: piVersion === TRUSTED_PI_VERSION ? TRUSTED_PI_VERSION : undefined,
564
+ expected:
565
+ CERTIFICATION_TABLE[TRUSTED_PI_VERSION][
566
+ `${spec.subtype}:${spec.method}` as keyof (typeof CERTIFICATION_TABLE)["0.83.0"]
567
+ ],
568
+ actualPreinstall: descriptorSnapshot(preinstallDescriptor),
569
+ status: result.fallback ? "native-fallback" : "certified",
570
+ ...(result.fallback ? { fallbackReason: result.reason } : {}),
571
+ });
572
+ if (result.fallback) unsupported.push({ subtype: spec.subtype, method: spec.method, reason: result.reason });
573
+ }
574
+ const report: CompatibilityProbeReport = {
575
+ attemptedVersion: piVersion ?? "unknown",
576
+ matchedCertifiedVersion: piVersion === TRUSTED_PI_VERSION ? TRUSTED_PI_VERSION : undefined,
577
+ certificationTable: CERTIFICATION_TABLE,
578
+ piVersion: piVersion ?? "unknown",
579
+ versionRange: PI_VERSION_RANGE,
580
+ generation: currentGeneration(),
581
+ recordSnapshots: Object.freeze(
582
+ records.map((record) =>
583
+ Object.freeze({
584
+ feature: record.feature,
585
+ subtype: record.subtype,
586
+ method: record.method,
587
+ shape: record.shape,
588
+ piVersion: record.piVersion,
589
+ versionRange: record.versionRange,
590
+ generation: record.generation,
591
+ disposed: record.disposed,
592
+ diagnostic: record.diagnostic,
593
+ }),
594
+ ),
595
+ ),
596
+ unsupported: Object.freeze(unsupported.map((item) => Object.freeze({ ...item }))),
597
+ delegationMarkers: Object.freeze([...markers]),
598
+ certification: Object.freeze(
599
+ certification.map((item) =>
600
+ Object.freeze({
601
+ ...item,
602
+ actualPreinstall: item.actualPreinstall,
603
+ }),
604
+ ),
605
+ ),
606
+ getRuntimeDiagnostics: () => toolOwner?.getDiagnostics() ?? new Map(),
607
+ getFinalDiagnostics: () => toolOwner?.getFinalArchive(),
608
+ getActiveToolRecordCount: () => toolOwner?.getActiveRecordCount() ?? 0,
609
+ disposeOwner: () => {
610
+ toolOwner?.dispose();
611
+ },
612
+ };
613
+ Object.freeze(report);
614
+ reportStates.set(report, { records, toolOwner });
615
+ return report;
616
+ }
617
+
618
+ export type CompatibilityCleanupResult = Readonly<{
619
+ complete: boolean;
620
+ retryablePrototypeRecords: number;
621
+ retryableToolRecords: number;
622
+ finalDiagnostics?: Readonly<import("../features/tools/index.js").ToolDiagnosticArchive>;
623
+ }>;
624
+
625
+ export function disposePiCompatibilityProbe(report: CompatibilityProbeReport): CompatibilityCleanupResult {
626
+ const state = reportStates.get(report);
627
+ if (!state) return { complete: true, retryablePrototypeRecords: 0, retryableToolRecords: 0 };
628
+ for (const record of state.records) record.disposer();
629
+ report.disposeOwner();
630
+ const retryablePrototypeRecords = state.records.filter((record) => !record.disposed).length;
631
+ const finalDiagnostics = report.getFinalDiagnostics();
632
+ const retryableToolRecords = report.getActiveToolRecordCount();
633
+ const toolOwnerWasCreated = report.recordSnapshots.some(
634
+ (record) => record.feature === "tools" && record.shape === "installed",
635
+ );
636
+ return {
637
+ complete:
638
+ retryablePrototypeRecords === 0 &&
639
+ retryableToolRecords === 0 &&
640
+ (!toolOwnerWasCreated || finalDiagnostics !== undefined),
641
+ retryablePrototypeRecords,
642
+ retryableToolRecords,
643
+ ...(finalDiagnostics ? { finalDiagnostics } : {}),
644
+ };
645
+ }