@danypops/pi-jittor 0.1.0 → 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.
@@ -0,0 +1,40 @@
1
+ import {
2
+ CONTEXT_HUB_CONTRIBUTION_DEDUP_LIMIT,
3
+ validateContextContribution,
4
+ type ContextSegment,
5
+ } from "@danypops/jittor";
6
+
7
+ /**
8
+ * Merges Jittor's own directly-computed segments (tool ledger, real usage) with whatever
9
+ * segments other extensions contributed on CONTEXT_HUB_CONTRIBUTION_CHANNEL for the current
10
+ * session. Keeps only the latest contribution per producer -- a producer re-emits every turn
11
+ * (mirroring Papyrus's own context-injection.v1 cadence), so an older segment from the same
12
+ * producer is stale, not a second real contributor.
13
+ */
14
+ export class ContextHubCapability {
15
+ private readonly latestByProducer = new Map<string, ContextSegment>();
16
+ private readonly seen = new Set<string>();
17
+
18
+ /** Validates and records one contribution; silently drops a malformed, stale, or duplicate one without retaining its payload or crashing the caller. */
19
+ observe(payload: unknown, now = Date.now()): void {
20
+ try {
21
+ const contribution = validateContextContribution(payload, now);
22
+ const key = `${contribution.producerName}:${contribution.sequence}`;
23
+ if (this.seen.has(key)) return;
24
+ this.seen.add(key);
25
+ if (this.seen.size > CONTEXT_HUB_CONTRIBUTION_DEDUP_LIMIT) this.seen.delete(this.seen.values().next().value!);
26
+ this.latestByProducer.set(contribution.producerName, contribution.segment);
27
+ } catch {
28
+ // Reject malformed or stale cross-extension contributions without retaining payloads.
29
+ }
30
+ }
31
+
32
+ contributedSegments(): ContextSegment[] {
33
+ return [...this.latestByProducer.values()];
34
+ }
35
+
36
+ reset(): void {
37
+ this.latestByProducer.clear();
38
+ this.seen.clear();
39
+ }
40
+ }
@@ -0,0 +1,46 @@
1
+ import type { ContextSegment, ContextSegmentItem } from "@danypops/jittor";
2
+
3
+ export interface ContextReportUsage {
4
+ tokens: number | null;
5
+ contextWindow: number;
6
+ percent: number | null;
7
+ }
8
+
9
+ /** Bounds how many items render per segment -- a report is a scan-at-a-glance summary, not a full dump. */
10
+ const MAX_ITEMS_PER_SEGMENT_LINE = 5;
11
+
12
+ function formatTokens(tokens: number): string {
13
+ return tokens >= 1_000 ? `${(tokens / 1_000).toFixed(1)}k` : String(tokens);
14
+ }
15
+
16
+ function topItems(items: ContextSegmentItem[] | undefined): ContextSegmentItem[] {
17
+ return [...(items ?? [])].sort((left, right) => right.estimatedTokens - left.estimatedTokens).slice(0, MAX_ITEMS_PER_SEGMENT_LINE);
18
+ }
19
+
20
+ /**
21
+ * Plain-text Context Hub report: real provider-reported usage first (never a character-based
22
+ * estimate when real usage is available, matching Papyrus's own real-vs-estimate honesty
23
+ * accounting), then every segment heaviest-first with an explicit confidence tag
24
+ * (exact-tool/exact-cooperative/correlated/audited) so a reader never mistakes one attribution
25
+ * tier's certainty for another's.
26
+ */
27
+ export function buildContextReport(segments: readonly ContextSegment[], usage: ContextReportUsage | undefined): string {
28
+ const lines: string[] = [];
29
+ if (usage?.tokens !== null && usage?.tokens !== undefined) {
30
+ const percent = usage.percent !== null && usage.percent !== undefined ? ` (${usage.percent.toFixed(1)}%)` : "";
31
+ lines.push(`Real usage: ${formatTokens(usage.tokens)} / ${formatTokens(usage.contextWindow)} tokens${percent}`);
32
+ } else {
33
+ lines.push("Real usage: not yet reported -- sizes below are estimates only");
34
+ }
35
+ const sorted = [...segments].sort((left, right) => right.estimatedTokens - left.estimatedTokens);
36
+ if (sorted.length === 0) {
37
+ lines.push("", "(no segments observed yet)");
38
+ return lines.join("\n");
39
+ }
40
+ lines.push("");
41
+ for (const segment of sorted) {
42
+ lines.push(`${segment.label} — ${formatTokens(segment.estimatedTokens)} tok [${segment.confidence}]`);
43
+ for (const item of topItems(segment.items)) lines.push(` ${formatTokens(item.estimatedTokens)} tok ${item.label}`);
44
+ }
45
+ return lines.join("\n");
46
+ }
@@ -1,6 +1,7 @@
1
1
  import { isAbsolute, relative, resolve, sep } from "node:path";
2
2
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
4
+ import { ProgressBar } from "malevich-tui-components";
4
5
  import {
5
6
  FOOTER_BAR_MAX_WIDTH,
6
7
  FOOTER_BAR_MIN_WIDTH,
@@ -119,11 +120,11 @@ function barWidth(width: number): number {
119
120
  return width >= FOOTER_WIDE_TERMINAL_WIDTH ? FOOTER_BAR_MAX_WIDTH : FOOTER_BAR_MIN_WIDTH;
120
121
  }
121
122
 
123
+ // Thin wrapper over Malevich's ProgressBar.format() -- keeps this file's own call sites
124
+ // (fraction-first, width-second, tolerant of null/non-finite) unchanged.
122
125
  function progressBar(fraction: number | null, width: number): string {
123
- if (fraction === null || !Number.isFinite(fraction)) return "░".repeat(width);
124
- const clamped = Math.min(1, Math.max(0, fraction));
125
- const filled = Math.round(width * clamped);
126
- return "█".repeat(filled) + "░".repeat(width - filled);
126
+ const value = fraction === null || !Number.isFinite(fraction) ? 0 : fraction;
127
+ return new ProgressBar({ value, max: 1, width }).format(width);
127
128
  }
128
129
 
129
130
  function fillColor(fraction: number | null): FooterColor {
@@ -1,5 +1,6 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import {
3
+ CONTEXT_HUB_CONTRIBUTION_CHANNEL,
3
4
  FOOTER_COMPACTION_RENDER_INTERVAL_MS,
4
5
  MAX_DYNAMIC_ROUTES,
5
6
  PAPYRUS_CONTEXT_INJECTION_CHANNEL,
@@ -10,6 +11,7 @@ import {
10
11
  validatePapyrusContextInjection,
11
12
  applyTaskFocusEvent,
12
13
  validateTaskFocusEvent,
14
+ toolLedgerSegment,
13
15
  TASK_DOMAINS,
14
16
  TASK_TYPES,
15
17
  USAGE_PERIODS,
@@ -35,6 +37,8 @@ import { showUsagePanel } from "./usage.ts";
35
37
  import { CodexRecoveryCapability, SYSTEM_RECOVERY_RUNTIME, type CodexRecoveryRuntime } from "./capabilities/codex-recovery.ts";
36
38
  import { ProviderResponseTelemetry } from "./capabilities/provider-response-telemetry.ts";
37
39
  import { LocalRunTelemetry } from "./capabilities/local-run-telemetry.ts";
40
+ import { ContextHubCapability } from "./capabilities/context-hub.ts";
41
+ import { buildContextReport } from "./context-report.ts";
38
42
 
39
43
  export { formatFooterStatus } from "./tui.ts";
40
44
  export type { CodexRecoveryRuntime } from "./capabilities/codex-recovery.ts";
@@ -253,6 +257,8 @@ export function registerJittorExtension(
253
257
  const localRunTelemetry = new LocalRunTelemetry();
254
258
  const providerResponseTelemetry = new ProviderResponseTelemetry();
255
259
  const codexRecoveryCapability = new CodexRecoveryCapability(pi, codexRecovery, recoveryRuntime);
260
+ const contextHub = new ContextHubCapability();
261
+ const stopContextHub = pi.events?.on?.(CONTEXT_HUB_CONTRIBUTION_CHANNEL, (payload) => contextHub.observe(payload));
256
262
  const contextObservations = new Set<string>();
257
263
  const stopPapyrusContext = pi.events?.on?.(PAPYRUS_CONTEXT_INJECTION_CHANNEL, (payload) => {
258
264
  try {
@@ -458,6 +464,15 @@ export function registerJittorExtension(
458
464
  },
459
465
  });
460
466
 
467
+ pi.registerCommand("context", {
468
+ description: "Context Hub: real usage plus every segment's estimated size (tool schemas by owning extension, and whatever other extensions contributed), each tagged with how it was attributed",
469
+ handler: async (_args, ctx) => {
470
+ const toolSegment = toolLedgerSegment(pi.getAllTools());
471
+ const segments = [toolSegment, ...contextHub.contributedSegments()];
472
+ ctx.ui.notify(buildContextReport(segments, ctx.getContextUsage()), "info");
473
+ },
474
+ });
475
+
461
476
  pi.registerCommand("usage", {
462
477
  description: "Cumulative token/cost usage graph with hourly/daily/weekly/monthly/quarterly views",
463
478
  handler: async (args, ctx) => {
@@ -502,6 +517,7 @@ export function registerJittorExtension(
502
517
  finishCompactionUi();
503
518
  compactionTelemetry = new CompactionTelemetry();
504
519
  localRunTelemetry.reset();
520
+ contextHub.reset();
505
521
  cancelRecovery(true);
506
522
  providerResponseTelemetry.resetTurn();
507
523
  ctx.ui.setStatus("jittor", undefined);
@@ -642,6 +658,7 @@ export function registerJittorExtension(
642
658
  if (compactionTelemetry.hasOpenCompaction()) await recordMetrics(client, [compactionTelemetry.abort(Date.now(), "session-shutdown")]).catch(() => undefined);
643
659
  stopPapyrusContext?.();
644
660
  stopPapyrusTaskFocus?.();
661
+ stopContextHub?.();
645
662
  cancelRecovery(true);
646
663
  localRunTelemetry.reset();
647
664
  const session_id = ctx.sessionManager.getSessionId();
@@ -1,4 +1,4 @@
1
- import { createRetryingClient, type RetryingClient } from "@danypops/daemon-kit/pi-client";
1
+ import { createRetryingClient, type RetryingClient } from "@danypops/vehicle-client/daemon-client";
2
2
  import { connectJittorClient, type JittorClient, type OperationInputs, type OperationName, type OperationOutputs } from "@danypops/jittor";
3
3
 
4
4
  type JittorConnector = () => Promise<JittorClient>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/pi-jittor",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Pi extension for Jittor: native routing enforcement, footer, settings, usage graphs, and benchmark panels backed by the @danypops/jittor daemon",
5
5
  "type": "module",
6
6
  "keywords": ["pi-package", "llm-router", "token-budget"],
@@ -12,8 +12,9 @@
12
12
  "extensions": ["extension/src/index.ts"]
13
13
  },
14
14
  "dependencies": {
15
- "@danypops/daemon-kit": "^0.4.0",
16
- "@danypops/jittor": "^0.12.0"
15
+ "@danypops/vehicle-client": "^0.1.1",
16
+ "@danypops/jittor": "^0.13.0",
17
+ "malevich-tui-components": "^0.2.0"
17
18
  },
18
19
  "peerDependencies": {
19
20
  "@earendil-works/pi-coding-agent": "*",