@danypops/pi-jittor 0.6.0 → 0.8.0
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/extension/src/index.ts +136 -154
- package/extension/src/observability/footer.ts +32 -1
- package/package.json +2 -2
package/extension/src/index.ts
CHANGED
|
@@ -52,7 +52,12 @@ import {
|
|
|
52
52
|
import { ContextGrowthCapability } from "./observability/context-growth.ts";
|
|
53
53
|
import { ContextHubCapability } from "./observability/context-hub.ts";
|
|
54
54
|
import { showContextView } from "./observability/context-view.ts";
|
|
55
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
type CompactionProgress,
|
|
57
|
+
type IntegratedFooterState,
|
|
58
|
+
installIntegratedFooter,
|
|
59
|
+
type RouterFooterInfo,
|
|
60
|
+
} from "./observability/footer.ts";
|
|
56
61
|
import { LocalRunTelemetry } from "./observability/model-run.ts";
|
|
57
62
|
import { captureProviderContextSnapshot } from "./observability/provider-context-snapshot.ts";
|
|
58
63
|
import { ProviderResponseTelemetry } from "./observability/provider-response.ts";
|
|
@@ -159,21 +164,6 @@ async function refreshFooter(client: JittorExtensionClient, state: IntegratedFoo
|
|
|
159
164
|
state.requestRender?.();
|
|
160
165
|
}
|
|
161
166
|
|
|
162
|
-
function delay(milliseconds: number, signal?: AbortSignal): Promise<void> {
|
|
163
|
-
if (milliseconds <= 0) return Promise.resolve();
|
|
164
|
-
return new Promise((resolve, reject) => {
|
|
165
|
-
const timer = setTimeout(resolve, milliseconds);
|
|
166
|
-
signal?.addEventListener(
|
|
167
|
-
"abort",
|
|
168
|
-
() => {
|
|
169
|
-
clearTimeout(timer);
|
|
170
|
-
reject(new Error("Jittor throttle cancelled"));
|
|
171
|
-
},
|
|
172
|
-
{ once: true },
|
|
173
|
-
);
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
167
|
function routeModelAvailable(ctx: ExtensionContext, route: Route): boolean {
|
|
178
168
|
return ctx.modelRegistry.getAvailable().some((model) => model.provider === route.provider && model.id === route.model);
|
|
179
169
|
}
|
|
@@ -313,38 +303,6 @@ async function syncCurrentRoute(
|
|
|
313
303
|
});
|
|
314
304
|
}
|
|
315
305
|
|
|
316
|
-
function halt(ctx: ExtensionContext, reason: string): false {
|
|
317
|
-
ctx.ui.notify(`${reason}. ${RECOVERY_GUIDANCE}.`, "warning");
|
|
318
|
-
ctx.abort();
|
|
319
|
-
return false;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async function applyDecision(
|
|
323
|
-
pi: ExtensionAPI,
|
|
324
|
-
client: JittorExtensionClient,
|
|
325
|
-
ctx: ExtensionContext,
|
|
326
|
-
decision: PolicyDecision,
|
|
327
|
-
allowResync = true,
|
|
328
|
-
): Promise<boolean> {
|
|
329
|
-
if (decision.action === "halt") return halt(ctx, `Jittor blocked this provider request: ${decision.reason}`);
|
|
330
|
-
if (decision.action === "throttle") await delay(decision.delayMs ?? 0, ctx.signal);
|
|
331
|
-
if (!decision.route || (await applyRoute(pi, ctx, decision.route))) return true;
|
|
332
|
-
if (allowResync) {
|
|
333
|
-
await syncAvailableRoutes(pi, client, ctx);
|
|
334
|
-
return applyDecision(
|
|
335
|
-
pi,
|
|
336
|
-
client,
|
|
337
|
-
ctx,
|
|
338
|
-
(await client.call("router.decide", { session_id: ctx.sessionManager.getSessionId() })) as PolicyDecision,
|
|
339
|
-
false,
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
return halt(
|
|
343
|
-
ctx,
|
|
344
|
-
`Jittor could not apply any authenticated Pi route after ${decision.route.provider}/${decision.route.model} became unavailable`,
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
306
|
let assistantUsageRunSequence = 0;
|
|
349
307
|
|
|
350
308
|
/**
|
|
@@ -498,54 +456,61 @@ export function registerJittorExtension(
|
|
|
498
456
|
const contextFingerprinter = new HmacContextFingerprinter(contextFingerprintKey);
|
|
499
457
|
let contextCaptureSequence = 0;
|
|
500
458
|
pi.on("before_provider_request", (event, ctx) => {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
activeEntryIds: Set<string>;
|
|
507
|
-
branchEntryIds: Set<string>;
|
|
508
|
-
}
|
|
509
|
-
| undefined;
|
|
459
|
+
// Pi awaits this hook immediately before opening the provider request. Snapshotting a large
|
|
460
|
+
// payload here used to add its full CPU cost to TTFT even though persistence was detached.
|
|
461
|
+
// Defer the entire observational path to a later macrotask; the provider payload is immutable
|
|
462
|
+
// after serialization in Pi's request path, and a missed snapshot is preferable to delaying it.
|
|
463
|
+
const timer = setTimeout(() => {
|
|
510
464
|
try {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
465
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
466
|
+
let history:
|
|
467
|
+
| {
|
|
468
|
+
roots: SessionTreeNodeLike[];
|
|
469
|
+
activeEntryIds: Set<string>;
|
|
470
|
+
branchEntryIds: Set<string>;
|
|
471
|
+
}
|
|
472
|
+
| undefined;
|
|
473
|
+
try {
|
|
474
|
+
history = {
|
|
475
|
+
roots: ctx.sessionManager.getTree() as SessionTreeNodeLike[],
|
|
476
|
+
activeEntryIds: new Set((ctx.sessionManager.buildContextEntries() as SessionEntryLike[]).map((entry) => entry.id)),
|
|
477
|
+
branchEntryIds: new Set((ctx.sessionManager.getBranch() as SessionEntryLike[]).map((entry) => entry.id)),
|
|
478
|
+
};
|
|
479
|
+
} catch {
|
|
480
|
+
// Older/custom SessionManager implementations may not expose tree projections.
|
|
481
|
+
}
|
|
482
|
+
const captureInput = {
|
|
483
|
+
payload: event.payload,
|
|
484
|
+
captureId: `${++contextCaptureSequence}`,
|
|
485
|
+
sessionId,
|
|
486
|
+
provider: ctx.model?.provider ?? "unknown",
|
|
487
|
+
model: ctx.model?.id ?? "unknown",
|
|
488
|
+
capturedAt: Date.now(),
|
|
489
|
+
fingerprinter: contextFingerprinter,
|
|
515
490
|
};
|
|
491
|
+
let snapshot: ContextSnapshot;
|
|
492
|
+
try {
|
|
493
|
+
snapshot = captureProviderContextSnapshot({ ...captureInput, ...(history ? { history } : {}) });
|
|
494
|
+
} catch {
|
|
495
|
+
// A custom SessionManager tree shape must not suppress the real request-payload snapshot.
|
|
496
|
+
snapshot = captureProviderContextSnapshot(captureInput);
|
|
497
|
+
}
|
|
498
|
+
// Observation must never alter or abort the provider request. Both local writes are detached;
|
|
499
|
+
// they receive only bounded token sizes and keyed fingerprints.
|
|
500
|
+
const requestTokens = snapshot.segments
|
|
501
|
+
.filter((segment) => segment.requestPosition !== null)
|
|
502
|
+
.reduce((sum, segment) => sum + segment.tokens, 0);
|
|
503
|
+
const compactionMetrics = compactionTelemetry.observeContextSnapshot(requestTokens, "structural-estimate", snapshot.capturedAt, {
|
|
504
|
+
provider: snapshot.provider,
|
|
505
|
+
model: snapshot.model,
|
|
506
|
+
});
|
|
507
|
+
void client.call("context.snapshot", snapshot).catch(() => undefined);
|
|
508
|
+
if (compactionMetrics.length > 0) void recordMetrics(client, compactionMetrics).catch(() => undefined);
|
|
516
509
|
} catch {
|
|
517
|
-
//
|
|
518
|
-
}
|
|
519
|
-
const captureInput = {
|
|
520
|
-
payload: event.payload,
|
|
521
|
-
captureId: `${++contextCaptureSequence}`,
|
|
522
|
-
sessionId,
|
|
523
|
-
provider: ctx.model?.provider ?? "unknown",
|
|
524
|
-
model: ctx.model?.id ?? "unknown",
|
|
525
|
-
capturedAt: Date.now(),
|
|
526
|
-
fingerprinter: contextFingerprinter,
|
|
527
|
-
};
|
|
528
|
-
let snapshot: ContextSnapshot;
|
|
529
|
-
try {
|
|
530
|
-
snapshot = captureProviderContextSnapshot({ ...captureInput, ...(history ? { history } : {}) });
|
|
531
|
-
} catch {
|
|
532
|
-
// A custom SessionManager tree shape must not suppress the real request-payload snapshot.
|
|
533
|
-
snapshot = captureProviderContextSnapshot(captureInput);
|
|
510
|
+
// Snapshot collection is strictly failure-isolated from provider delivery.
|
|
534
511
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
const requestTokens = snapshot.segments
|
|
538
|
-
.filter((segment) => segment.requestPosition !== null)
|
|
539
|
-
.reduce((sum, segment) => sum + segment.tokens, 0);
|
|
540
|
-
const compactionMetrics = compactionTelemetry.observeContextSnapshot(requestTokens, "structural-estimate", snapshot.capturedAt, {
|
|
541
|
-
provider: snapshot.provider,
|
|
542
|
-
model: snapshot.model,
|
|
543
|
-
});
|
|
544
|
-
void client.call("context.snapshot", snapshot).catch(() => undefined);
|
|
545
|
-
if (compactionMetrics.length > 0) void recordMetrics(client, compactionMetrics).catch(() => undefined);
|
|
546
|
-
} catch {
|
|
547
|
-
// Snapshot collection is strictly failure-isolated from provider delivery.
|
|
548
|
-
}
|
|
512
|
+
}, 0);
|
|
513
|
+
timer.unref?.();
|
|
549
514
|
});
|
|
550
515
|
const stopContextHub = pi.events?.on?.(CONTEXT_HUB_CONTRIBUTION_CHANNEL, (payload) => contextHub.observe(payload));
|
|
551
516
|
// Cached from the most recent before_agent_start observation: Pi's own base system prompt is
|
|
@@ -623,7 +588,13 @@ export function registerJittorExtension(
|
|
|
623
588
|
else footerState.requestRender?.();
|
|
624
589
|
};
|
|
625
590
|
const showFooter = (ctx: ExtensionContext): void => {
|
|
626
|
-
if (enforcement.isFooterEnabled())
|
|
591
|
+
if (enforcement.isFooterEnabled())
|
|
592
|
+
installIntegratedFooter(
|
|
593
|
+
ctx,
|
|
594
|
+
footerState,
|
|
595
|
+
() => pi.getThinkingLevel(),
|
|
596
|
+
(): RouterFooterInfo => ({ autoMode: autoMode.getAutoMode() }),
|
|
597
|
+
);
|
|
627
598
|
else ctx.ui.setFooter(undefined);
|
|
628
599
|
};
|
|
629
600
|
const disable = async (ctx: ExtensionContext): Promise<void> => {
|
|
@@ -991,25 +962,58 @@ export function registerJittorExtension(
|
|
|
991
962
|
]).catch(() => undefined);
|
|
992
963
|
});
|
|
993
964
|
|
|
994
|
-
|
|
965
|
+
let ambientRouting: Promise<void> | null = null;
|
|
966
|
+
const scheduleAmbientRouting = (ctx: ExtensionContext): void => {
|
|
967
|
+
if (ambientRouting || !ctx.isIdle()) return;
|
|
968
|
+
ambientRouting = (async () => {
|
|
969
|
+
try {
|
|
970
|
+
await syncCurrentRoute(pi, client, ctx);
|
|
971
|
+
await syncAvailableRoutes(pi, client, ctx);
|
|
972
|
+
if (!ctx.isIdle()) return;
|
|
973
|
+
let budgetActed = false;
|
|
974
|
+
if (enforcement.isEnabled()) {
|
|
975
|
+
const decision = (await client.call("router.decide", {
|
|
976
|
+
session_id: ctx.sessionManager.getSessionId(),
|
|
977
|
+
})) as PolicyDecision;
|
|
978
|
+
if (!ctx.isIdle()) return;
|
|
979
|
+
budgetActed = decision.action !== "continue";
|
|
980
|
+
if (decision.action === "halt") {
|
|
981
|
+
ctx.ui.notify(`Jittor recommends pausing future requests: ${decision.reason}.`, "warning");
|
|
982
|
+
} else if (decision.route && !(await applyRoute(pi, ctx, decision.route)) && ctx.isIdle()) {
|
|
983
|
+
// Catalogs can change while an ambient decision is in flight. Refresh and try one
|
|
984
|
+
// newly-computed route; failure simply leaves the current model in place.
|
|
985
|
+
await syncAvailableRoutes(pi, client, ctx);
|
|
986
|
+
const refreshed = (await client.call("router.decide", {
|
|
987
|
+
session_id: ctx.sessionManager.getSessionId(),
|
|
988
|
+
})) as PolicyDecision;
|
|
989
|
+
if (ctx.isIdle() && refreshed.route) await applyRoute(pi, ctx, refreshed.route);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (!budgetActed && ctx.isIdle()) await runAutoModeTurn(ctx);
|
|
993
|
+
} catch {
|
|
994
|
+
// Routing is opportunistic: daemon latency or failure must never enter Pi's request path.
|
|
995
|
+
} finally {
|
|
996
|
+
ambientRouting = null;
|
|
997
|
+
}
|
|
998
|
+
})();
|
|
999
|
+
};
|
|
1000
|
+
|
|
1001
|
+
pi.on("agent_settled", (_event, ctx) => {
|
|
995
1002
|
if (footerState.compaction) {
|
|
996
1003
|
finishCompactionUi();
|
|
997
1004
|
if (compactionTelemetry.hasOpenCompaction())
|
|
998
|
-
|
|
1005
|
+
void recordMetrics(client, [compactionTelemetry.abort(Date.now(), "agent-settled-without-completion")]).catch(() => undefined);
|
|
999
1006
|
}
|
|
1000
1007
|
scheduleCodexRecovery(ctx);
|
|
1008
|
+
scheduleAmbientRouting(ctx);
|
|
1001
1009
|
if (!enforcement.isFooterEnabled()) return;
|
|
1002
|
-
|
|
1003
|
-
await syncCurrentRoute(pi, client, ctx);
|
|
1004
|
-
await syncAvailableRoutes(pi, client, ctx);
|
|
1005
|
-
await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
|
|
1006
|
-
} catch {
|
|
1010
|
+
void refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => {
|
|
1007
1011
|
footerState.providerBudget = null;
|
|
1008
1012
|
footerState.requestRender?.();
|
|
1009
|
-
}
|
|
1013
|
+
});
|
|
1010
1014
|
});
|
|
1011
1015
|
|
|
1012
|
-
pi.on("input",
|
|
1016
|
+
pi.on("input", (event) => {
|
|
1013
1017
|
if (event.source !== "extension") {
|
|
1014
1018
|
cancelRecovery(true);
|
|
1015
1019
|
// Auto mode's own effort classifier reads this at the next turn_start -- captured
|
|
@@ -1017,57 +1021,34 @@ export function registerJittorExtension(
|
|
|
1017
1021
|
// separate, independently-toggled feature from budget enforcement.
|
|
1018
1022
|
pendingUserText = event.text;
|
|
1019
1023
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
if (next.action === "halt") {
|
|
1024
|
-
ctx.ui.notify(`Jittor blocked input: ${next.reason}. ${RECOVERY_GUIDANCE}.`, "warning");
|
|
1025
|
-
return { action: "handled" as const };
|
|
1026
|
-
}
|
|
1027
|
-
return { action: "continue" as const };
|
|
1028
|
-
} catch {
|
|
1029
|
-
ctx.ui.notify(`Jittor could not verify budget telemetry, so fail-closed enforcement blocked input. ${RECOVERY_GUIDANCE}.`, "error");
|
|
1030
|
-
return { action: "handled" as const };
|
|
1031
|
-
}
|
|
1024
|
+
// Routing is advisory and ambient. The submitted message always enters Pi immediately;
|
|
1025
|
+
// daemon decisions computed while idle can optimize a later turn.
|
|
1026
|
+
return { action: "continue" as const };
|
|
1032
1027
|
});
|
|
1033
1028
|
|
|
1034
|
-
pi.on("model_select",
|
|
1035
|
-
|
|
1029
|
+
pi.on("model_select", (event, ctx) => {
|
|
1030
|
+
void syncCurrentRoute(pi, client, ctx, event.model)
|
|
1036
1031
|
.then(() => syncAvailableRoutes(pi, client, ctx))
|
|
1032
|
+
.then(() =>
|
|
1033
|
+
enforcement.isFooterEnabled()
|
|
1034
|
+
? refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined)
|
|
1035
|
+
: undefined,
|
|
1036
|
+
)
|
|
1037
1037
|
.catch(() => undefined);
|
|
1038
|
-
if (enforcement.isFooterEnabled()) await refreshFooter(client, footerState, ctx.sessionManager.getSessionId()).catch(() => undefined);
|
|
1039
1038
|
});
|
|
1040
1039
|
|
|
1041
|
-
pi.on("thinking_level_select",
|
|
1042
|
-
|
|
1040
|
+
pi.on("thinking_level_select", (event, ctx) => {
|
|
1041
|
+
void syncCurrentRoute(pi, client, ctx, ctx.model, event.level).catch(() => undefined);
|
|
1043
1042
|
});
|
|
1044
1043
|
|
|
1045
|
-
pi.on("turn_start",
|
|
1044
|
+
pi.on("turn_start", (event, ctx) => {
|
|
1046
1045
|
currentSessionId = ctx.sessionManager.getSessionId();
|
|
1047
1046
|
compactionTelemetry.observeTurn();
|
|
1048
1047
|
codexRecoveryCapability.resetTurn();
|
|
1049
1048
|
providerResponseTelemetry.resetTurn();
|
|
1050
1049
|
localRunTelemetry.beginTurn(event.timestamp);
|
|
1051
|
-
//
|
|
1052
|
-
//
|
|
1053
|
-
// so the two decision sources never fight over the model mid-turn.
|
|
1054
|
-
let budgetActedThisTurn = false;
|
|
1055
|
-
if (enforcement.isEnabled()) {
|
|
1056
|
-
try {
|
|
1057
|
-
await syncCurrentRoute(pi, client, ctx);
|
|
1058
|
-
await syncAvailableRoutes(pi, client, ctx);
|
|
1059
|
-
const budgetDecision = (await client.call("router.decide", {
|
|
1060
|
-
session_id: ctx.sessionManager.getSessionId(),
|
|
1061
|
-
})) as PolicyDecision;
|
|
1062
|
-
budgetActedThisTurn = budgetDecision.action !== "continue";
|
|
1063
|
-
await applyDecision(pi, client, ctx, budgetDecision);
|
|
1064
|
-
await refreshFooter(client, footerState, ctx.sessionManager.getSessionId());
|
|
1065
|
-
} catch {
|
|
1066
|
-
halt(ctx, "Jittor could not verify or apply a safe route");
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
if (!budgetActedThisTurn) await runAutoModeTurn(ctx).catch(() => undefined);
|
|
1050
|
+
// Deliberately no daemon or routing work here: Pi awaits turn_start before provider delivery.
|
|
1051
|
+
// The previous agent_settled event computes and applies recommendations for later turns.
|
|
1071
1052
|
});
|
|
1072
1053
|
|
|
1073
1054
|
pi.on("message_update", async (event) => {
|
|
@@ -1084,35 +1065,35 @@ export function registerJittorExtension(
|
|
|
1084
1065
|
if (currentTurnToolNames.length < EFFORT_CLASSIFICATION_MAX_TOOL_NAMES) currentTurnToolNames.push(event.toolName);
|
|
1085
1066
|
});
|
|
1086
1067
|
|
|
1087
|
-
pi.on("after_provider_response",
|
|
1068
|
+
pi.on("after_provider_response", (event, ctx) => {
|
|
1088
1069
|
localRunTelemetry.onProviderResponse();
|
|
1089
1070
|
if (ctx.model?.provider === "openai-codex") codexRecoveryCapability.notifyResponse(event.status, event.headers);
|
|
1090
1071
|
const notifySchemaDrift = (message: string) => {
|
|
1091
1072
|
if (enforcement.isEnabled()) ctx.ui.notify(`Jittor detected ${message}. ${RECOVERY_GUIDANCE}.`, "error");
|
|
1092
1073
|
};
|
|
1093
|
-
|
|
1094
|
-
|
|
1074
|
+
// Pi fires this before consuming the provider stream. Never put telemetry or footer RPCs
|
|
1075
|
+
// between the HTTP response and the first streamed token.
|
|
1076
|
+
void providerResponseTelemetry
|
|
1077
|
+
.handleProviderResponse(client, ctx.model?.provider, event.status, event.headers, notifySchemaDrift)
|
|
1078
|
+
.catch(() => undefined);
|
|
1095
1079
|
});
|
|
1096
1080
|
|
|
1097
|
-
pi.on("turn_end",
|
|
1081
|
+
pi.on("turn_end", (event, ctx) => {
|
|
1098
1082
|
const tokens = ctx.getContextUsage()?.tokens;
|
|
1099
1083
|
if (typeof tokens === "number" && Number.isFinite(tokens)) contextGrowth.observe(++contextGrowthTurn, tokens);
|
|
1100
1084
|
const metrics = localRunTelemetry.completeTurn(event.message, pi.getThinkingLevel());
|
|
1101
|
-
|
|
1085
|
+
void recordMetrics(client, metrics).catch(() => undefined);
|
|
1102
1086
|
priorTurnToolNames = currentTurnToolNames;
|
|
1103
1087
|
currentTurnToolNames = [];
|
|
1104
1088
|
});
|
|
1105
1089
|
|
|
1106
|
-
pi.on("message_end",
|
|
1090
|
+
pi.on("message_end", (event, ctx) => {
|
|
1107
1091
|
if (event.message.role === "assistant") {
|
|
1108
1092
|
if (event.message.provider === "openai-codex")
|
|
1109
1093
|
codexRecoveryCapability.notifyMessageEnd(event.message.stopReason, event.message.errorMessage);
|
|
1110
|
-
|
|
1111
|
-
client,
|
|
1112
|
-
|
|
1113
|
-
event.message.stopReason,
|
|
1114
|
-
event.message.errorMessage,
|
|
1115
|
-
);
|
|
1094
|
+
void providerResponseTelemetry
|
|
1095
|
+
.handleMessageEnd(client, event.message.provider, event.message.stopReason, event.message.errorMessage)
|
|
1096
|
+
.catch(() => undefined);
|
|
1116
1097
|
}
|
|
1117
1098
|
const metrics = assistantUsageMetrics(
|
|
1118
1099
|
event.message,
|
|
@@ -1132,9 +1113,10 @@ export function registerJittorExtension(
|
|
|
1132
1113
|
cacheRead: amount("cache-read-tokens"),
|
|
1133
1114
|
cacheWrite: amount("cache-write-tokens"),
|
|
1134
1115
|
});
|
|
1135
|
-
|
|
1116
|
+
void recordMetrics(client, metrics).catch(() => undefined);
|
|
1136
1117
|
}
|
|
1137
|
-
|
|
1118
|
+
// Footer refresh is consolidated in agent_settled; finalized-message accounting must not
|
|
1119
|
+
// hold back streamed lifecycle delivery or the next queued prompt.
|
|
1138
1120
|
});
|
|
1139
1121
|
|
|
1140
1122
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
@@ -69,6 +69,11 @@ export type ProviderBudget =
|
|
|
69
69
|
valueText: string;
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
+
/** Compact, always-visible state of Jittor's effort-based Auto mode -- the newer, effort-driven router, distinct from the existing budget-pressure route already reflected by the model/budget segments. */
|
|
73
|
+
export interface RouterFooterInfo {
|
|
74
|
+
autoMode: "off" | "suggest" | "auto-switch";
|
|
75
|
+
}
|
|
76
|
+
|
|
72
77
|
export interface CompactionProgress {
|
|
73
78
|
startedAt: number;
|
|
74
79
|
initialFraction: number;
|
|
@@ -238,6 +243,19 @@ function budgetSegment(
|
|
|
238
243
|
return `${budget.label} ${bar} ${value}${reset ? ` · ${reset}` : ""}${staleText}`;
|
|
239
244
|
}
|
|
240
245
|
|
|
246
|
+
/**
|
|
247
|
+
* "off" is dim (nothing happening); "suggest" is plain/unstyled -- it evaluates and may prompt,
|
|
248
|
+
* but only ever acts on explicit confirmation; "auto-switch" alone gets the eye-catching accent
|
|
249
|
+
* color, since it is the only state that can change the active model without asking first --
|
|
250
|
+
* the same distinction that already gates entering it behind an explicit settings confirmation.
|
|
251
|
+
*/
|
|
252
|
+
function routerSegment(info: RouterFooterInfo | undefined, theme: FooterTheme): string | undefined {
|
|
253
|
+
if (!info) return undefined;
|
|
254
|
+
if (info.autoMode === "off") return `auto ${theme.fg("dim", info.autoMode)}`;
|
|
255
|
+
if (info.autoMode === "auto-switch") return `auto ${theme.fg("accent", info.autoMode)}`;
|
|
256
|
+
return `auto ${info.autoMode}`;
|
|
257
|
+
}
|
|
258
|
+
|
|
241
259
|
function usageSegment(context: FooterContext): string {
|
|
242
260
|
const totals = usageTotals(context);
|
|
243
261
|
const parts: string[] = [];
|
|
@@ -295,6 +313,7 @@ export function renderFooterLines(
|
|
|
295
313
|
width: number,
|
|
296
314
|
now = Date.now(),
|
|
297
315
|
compaction?: CompactionProgress,
|
|
316
|
+
router?: RouterFooterInfo,
|
|
298
317
|
): string[] {
|
|
299
318
|
const safeWidth = Math.max(1, width);
|
|
300
319
|
const repository = repositorySegment(context, footerData, theme);
|
|
@@ -306,6 +325,7 @@ export function renderFooterLines(
|
|
|
306
325
|
const minimalContext = minimalContextSegment(context, theme, safeWidth, now, compaction);
|
|
307
326
|
const fullBudget = budgetSegment(providerBudget, theme, safeWidth, false, now);
|
|
308
327
|
const compactBudget = budgetSegment(providerBudget, theme, safeWidth, true, now);
|
|
328
|
+
const routerInfo = routerSegment(router, theme);
|
|
309
329
|
const statuses = [...footerData.getExtensionStatuses().entries()]
|
|
310
330
|
.filter(([key]) => key !== "jittor")
|
|
311
331
|
.sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))
|
|
@@ -313,8 +333,11 @@ export function renderFooterLines(
|
|
|
313
333
|
.join(" ");
|
|
314
334
|
|
|
315
335
|
const candidates = [
|
|
336
|
+
joinSegments([repository, model.full, usage, fullContext, fullBudget, routerInfo, statuses]),
|
|
337
|
+
joinSegments([repository, model.full, usage, fullContext, fullBudget, routerInfo]),
|
|
316
338
|
joinSegments([repository, model.full, usage, fullContext, fullBudget, statuses]),
|
|
317
339
|
joinSegments([repository, model.full, usage, fullContext, fullBudget]),
|
|
340
|
+
joinSegments([model.full, usage, compactContext, compactBudget, routerInfo, statuses]),
|
|
318
341
|
joinSegments([model.full, usage, compactContext, compactBudget, statuses]),
|
|
319
342
|
joinSegments([model.full, usage, compactContext, compactBudget]),
|
|
320
343
|
joinSegments([model.full, compactUsage, compactContext, compactBudget]),
|
|
@@ -332,7 +355,14 @@ export interface IntegratedFooterState {
|
|
|
332
355
|
requestRender?: () => void;
|
|
333
356
|
}
|
|
334
357
|
|
|
335
|
-
export function installIntegratedFooter(
|
|
358
|
+
export function installIntegratedFooter(
|
|
359
|
+
ctx: ExtensionContext,
|
|
360
|
+
state: IntegratedFooterState,
|
|
361
|
+
getThinkingLevel: () => string,
|
|
362
|
+
// Called fresh every render, the same as getThinkingLevel -- Auto mode's setting can change at
|
|
363
|
+
// any time via /jittor settings, so the footer must never cache a stale snapshot of it.
|
|
364
|
+
getRouterInfo: () => RouterFooterInfo | undefined = () => undefined,
|
|
365
|
+
): void {
|
|
336
366
|
ctx.ui.setStatus("jittor", undefined);
|
|
337
367
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
338
368
|
state.requestRender = () => tui.requestRender();
|
|
@@ -349,6 +379,7 @@ export function installIntegratedFooter(ctx: ExtensionContext, state: Integrated
|
|
|
349
379
|
width,
|
|
350
380
|
Date.now(),
|
|
351
381
|
state.compaction,
|
|
382
|
+
getRouterInfo(),
|
|
352
383
|
);
|
|
353
384
|
},
|
|
354
385
|
dispose() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-jittor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Pi extension for Jittor token and context observability with optimization controls backed by the @danypops/jittor daemon",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@danypops/pi-extension-harness": "^0.6.2",
|
|
29
|
-
"@danypops/pi-process-harness": "^0.
|
|
29
|
+
"@danypops/pi-process-harness": "^0.3.2",
|
|
30
30
|
"@danypops/pi-tui-harness": "^0.0.2",
|
|
31
31
|
"@earendil-works/pi-ai": "*",
|
|
32
32
|
"bun-types": "latest"
|