@hyperdreamer/pi-webui 1.11.0-beta.4 → 1.11.0-beta.5
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 +15 -0
- package/dist/cli.js +265 -32
- package/dist/cli.js.map +1 -1
- package/dist/client/assets/{CodeViewer-Vvy_gSv8.js → CodeViewer-BJQFgK3S.js} +1 -1
- package/dist/client/assets/{UnifiedDiffViewer-OAmwWrcV.js → UnifiedDiffViewer-DfJJrNLr.js} +1 -1
- package/dist/client/assets/{index-DycHqr3e.js → index-CsVTzPPD.js} +551 -405
- package/dist/client/index.html +1 -1
- package/dist/server/sessions/modelTierRegistry.js +14 -0
- package/dist/server/sessions/modelTierRegistry.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +479 -16
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionModelPolicy.js +118 -0
- package/dist/server/sessions/sessionModelPolicy.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +98 -3
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/skills/optionalSkillInstall.js +69 -0
- package/dist/server/skills/optionalSkillInstall.js.map +1 -0
- package/dist/server/skills/optionalSkillInstaller.js +148 -0
- package/dist/server/skills/optionalSkillInstaller.js.map +1 -0
- package/dist/shared/apiTypes.d.ts +34 -0
- package/dist/shared/apiTypes.js +1 -0
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +3 -0
- package/dist/shared/capabilities.js.map +1 -1
- package/dist/shared/federatedRoutes.js +2 -0
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/docs/config.md +10 -0
- package/optional-skills/deterministic-subagent-driven-development/SKILL.md +224 -0
- package/optional-skills/deterministic-subagent-driven-development/pi-webui-skill.json +28 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/final-reviewer.md +132 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/implementer.md +101 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/re-reviewer.md +60 -0
- package/optional-skills/deterministic-subagent-driven-development/prompts/task-reviewer.md +80 -0
- package/optional-skills/deterministic-subagent-driven-development/references/capability-contract.md +174 -0
- package/optional-skills/deterministic-subagent-driven-development/references/plan-contract.md +268 -0
- package/optional-skills/deterministic-subagent-driven-development/references/state-machine.md +177 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/manifest.mjs +258 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/plan-policy.mjs +341 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/prompt-renderer.mjs +290 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/state-machine.mjs +1263 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/lib/state-store.mjs +532 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/sdd-state +3 -0
- package/optional-skills/deterministic-subagent-driven-development/scripts/sdd-state.mjs +348 -0
- package/optional-skills/deterministic-writing-plans/SKILL.md +232 -0
- package/optional-skills/deterministic-writing-plans/references/grammar.md +84 -0
- package/optional-skills/deterministic-writing-plans/templates/plan-skeleton.md +143 -0
- package/package.json +6 -3
|
@@ -27,6 +27,7 @@ import { SessionNotificationStore, } from "./sessionNotificationStore.js";
|
|
|
27
27
|
import { plainTextTheme } from "./plainTextTheme.js";
|
|
28
28
|
import { SessionUnreadStore } from "./sessionUnreadStore.js";
|
|
29
29
|
import { createModelTierRegistry, isModelTier, runtimeThinkingLevels } from "./modelTierRegistry.js";
|
|
30
|
+
import { inspectSessionModelPolicy, planSessionModelPolicyUpdate, serializeSessionModelPolicy, SESSION_MODEL_POLICY_CUSTOM_TYPE, } from "./sessionModelPolicy.js";
|
|
30
31
|
const noopLogger = { info() { } };
|
|
31
32
|
const DEFAULT_UNREAD_PUBLICATION_RETRY_MS = 1_000;
|
|
32
33
|
const MAX_UNREAD_PUBLICATION_RETRY_MS = 30_000;
|
|
@@ -291,6 +292,43 @@ export class PiSessionService {
|
|
|
291
292
|
this.pendingSessionOpens = new Map();
|
|
292
293
|
this.activities = new Map();
|
|
293
294
|
this.generationMetrics = new WeakMap();
|
|
295
|
+
this.modelPolicyInspections = new WeakMap();
|
|
296
|
+
this.modelPolicyLadderValidations = new WeakMap();
|
|
297
|
+
/**
|
|
298
|
+
* Reason the newest persisted policy entry is unusable. Recomputed on every
|
|
299
|
+
* inspection refresh, so repairing the entry clears it.
|
|
300
|
+
*/
|
|
301
|
+
this.modelPolicyEntryReasons = new WeakMap();
|
|
302
|
+
/**
|
|
303
|
+
* `MODEL_POLICY_BLOCKED` runtime state from an application whose restoration
|
|
304
|
+
* could not be proven. Deliberately *not* cleared by inspection refresh: only
|
|
305
|
+
* an explicit successful policy application clears it, because the runtime
|
|
306
|
+
* tuple — not the persisted entry — is what became ambiguous.
|
|
307
|
+
*
|
|
308
|
+
* Keyed by session *id* rather than by the runtime session object so the block
|
|
309
|
+
* is daemon-owned: it survives a runtime rebind, a reload, and a close/reopen
|
|
310
|
+
* of the same session, none of which prove anything about the ambiguous tuple.
|
|
311
|
+
*/
|
|
312
|
+
this.modelPolicyRuntimeBlocks = new Map();
|
|
313
|
+
/**
|
|
314
|
+
* Sessions inside the apply-and-persist window of a policy transition. During
|
|
315
|
+
* that window the runtime tuple is transient (model set, thinking not yet, or
|
|
316
|
+
* persistence not yet confirmed), so no prompt may reach Pi.
|
|
317
|
+
*/
|
|
318
|
+
this.modelPolicyMutationCounts = new Map();
|
|
319
|
+
/**
|
|
320
|
+
* Tuple last *confirmed* by the policy runtime adapter, captured when the
|
|
321
|
+
* outermost policy mutation opens and dropped when it closes.
|
|
322
|
+
*
|
|
323
|
+
* A status may be published from inside the transition window (the runtime
|
|
324
|
+
* subscription and the heartbeat both reach `publishStatus`), where the live
|
|
325
|
+
* runtime tuple is mid-apply: the model is set but the thinking level is not,
|
|
326
|
+
* so reading it would report a pair that was never requested and never
|
|
327
|
+
* persisted. `ClientSessionModelPolicyStatus.resolved` promises the last
|
|
328
|
+
* confirmed tuple, so the window reports this instead of live state. Keyed by
|
|
329
|
+
* session id to match the mutation counter it is scoped by.
|
|
330
|
+
*/
|
|
331
|
+
this.modelPolicyConfirmedSelections = new Map();
|
|
294
332
|
/** Runtime-identity gate held while Pi may await abandoned-branch summarization. */
|
|
295
333
|
this.treeNavigations = new WeakSet();
|
|
296
334
|
/** Counts async operations that may append an entry before they settle. */
|
|
@@ -496,6 +534,9 @@ export class PiSessionService {
|
|
|
496
534
|
this.pendingSessionOpens.clear();
|
|
497
535
|
this.activities.clear();
|
|
498
536
|
this.compactionPromptQueues.clear();
|
|
537
|
+
this.modelPolicyRuntimeBlocks.clear();
|
|
538
|
+
this.modelPolicyMutationCounts.clear();
|
|
539
|
+
this.modelPolicyConfirmedSelections.clear();
|
|
499
540
|
this.authLossWarnings.clear();
|
|
500
541
|
this.subsessionParents.clear();
|
|
501
542
|
this.subsessionChildren.clear();
|
|
@@ -544,14 +585,16 @@ export class PiSessionService {
|
|
|
544
585
|
.map((session) => mergeSessionMetadata(session, pinnedPathSet));
|
|
545
586
|
return [...unarchivedSessions, ...archivedSessions];
|
|
546
587
|
}
|
|
547
|
-
async start(cwd, options
|
|
548
|
-
|
|
588
|
+
async start(cwd, options) {
|
|
589
|
+
const modelPolicy = options?.modelPolicy;
|
|
590
|
+
return this.startSession(cwd, { initializeModelPolicy: modelPolicy ?? true });
|
|
549
591
|
}
|
|
550
592
|
async startSession(cwd, options) {
|
|
551
593
|
const active = await this.create(this.sessionManager.create(cwd, options.parentSession === undefined ? undefined : { parentSession: options.parentSession }), cwd, {
|
|
552
594
|
...(options.initialModel === undefined ? {} : { initialModel: options.initialModel }),
|
|
553
595
|
...(options.initialThinkingLevel === undefined ? {} : { initialThinkingLevel: options.initialThinkingLevel }),
|
|
554
596
|
...(options.creationProvenance === undefined ? {} : { creationProvenance: options.creationProvenance }),
|
|
597
|
+
...(options.initializeModelPolicy === undefined ? {} : { initializeModelPolicy: options.initializeModelPolicy }),
|
|
555
598
|
});
|
|
556
599
|
const { session } = active.runtime;
|
|
557
600
|
const created = {
|
|
@@ -583,7 +626,7 @@ export class PiSessionService {
|
|
|
583
626
|
const decision = await this.spawnTargets.resolveSpawnTarget(input.spawningCwd, input.cwd);
|
|
584
627
|
if (!decision.allowed)
|
|
585
628
|
throw spawnTargetError(decision);
|
|
586
|
-
const created = await this.
|
|
629
|
+
const created = await this.startSession(decision.cwd, input.model === undefined ? {} : { initialModel: input.model });
|
|
587
630
|
await this.prompt(created.id, input.prompt);
|
|
588
631
|
this.logger.info({ spawningCwd: input.spawningCwd, sessionId: created.id, cwd: decision.cwd, promptLength: input.prompt.length }, "spawn_session started a new session");
|
|
589
632
|
return { sessionId: created.id, cwd: decision.cwd };
|
|
@@ -977,6 +1020,41 @@ export class PiSessionService {
|
|
|
977
1020
|
async status(ref) {
|
|
978
1021
|
return this.statusFromSession(await this.getOrOpen(ref));
|
|
979
1022
|
}
|
|
1023
|
+
async modelPolicy(ref) {
|
|
1024
|
+
const session = await this.getOrOpen(ref);
|
|
1025
|
+
const inspection = this.inspectAndCacheSessionModelPolicy(session);
|
|
1026
|
+
return {
|
|
1027
|
+
contractVersion: 1,
|
|
1028
|
+
...(inspection.kind === "invalid" ? {} : { policy: inspection.policy }),
|
|
1029
|
+
session: this.statusFromSession(session),
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* The only interface that changes a session's Exact/Tiered policy. Applies one
|
|
1034
|
+
* complete transition atomically and publishes a single confirmed status; a
|
|
1035
|
+
* corrupt newest entry is repaired only by the explicit update supplied here,
|
|
1036
|
+
* never executed as a silent fallback. A transition that fails after the plan
|
|
1037
|
+
* is resolved publishes one error/blocked status before the route error, so
|
|
1038
|
+
* other clients learn the session refused the change.
|
|
1039
|
+
*/
|
|
1040
|
+
async setModelPolicy(ref, update) {
|
|
1041
|
+
const action = "change the session model policy";
|
|
1042
|
+
await this.assertWritable(ref);
|
|
1043
|
+
const session = await this.getOrOpen(ref);
|
|
1044
|
+
this.assertModelPolicyMutationIdle(session, action);
|
|
1045
|
+
const inspection = this.inspectAndCacheSessionModelPolicy(session);
|
|
1046
|
+
// A malformed newest entry contributes only a starting exact branch for the
|
|
1047
|
+
// new explicit entry; it is never treated as an executable current policy.
|
|
1048
|
+
const current = inspection.kind === "invalid" ? inspection.fallback : inspection.policy;
|
|
1049
|
+
const policy = await this.applySessionModelPolicy(session, current, update, {
|
|
1050
|
+
action,
|
|
1051
|
+
onTransitionFailure: (reason) => { this.publishModelPolicyTransitionFailure(session, reason); },
|
|
1052
|
+
});
|
|
1053
|
+
const resolved = this.exactSelectionFromSession(session);
|
|
1054
|
+
this.publishActivity(session, `model policy: ${policy.mode}`, "idle", policy.mode === "tiered" ? policy.tier : resolved.model.id);
|
|
1055
|
+
this.publishStatus(session);
|
|
1056
|
+
return this.modelPolicy(ref);
|
|
1057
|
+
}
|
|
980
1058
|
async systemPrompt(ref) {
|
|
981
1059
|
const systemPrompt = (await this.getOrOpen(ref)).state.systemPrompt;
|
|
982
1060
|
return systemPrompt === undefined ? {} : { systemPrompt };
|
|
@@ -1011,9 +1089,9 @@ export class PiSessionService {
|
|
|
1011
1089
|
async setModel(ref, provider, modelId) {
|
|
1012
1090
|
await this.assertWritable(ref);
|
|
1013
1091
|
const session = await this.getOrOpen(ref);
|
|
1014
|
-
this.
|
|
1092
|
+
const currentPolicy = this.assertExactModelPolicyMutationAllowed(session, "change models");
|
|
1015
1093
|
await session.modelRuntime.refresh({ allowNetwork: false });
|
|
1016
|
-
this.
|
|
1094
|
+
this.assertModelPolicyMutationIdle(session, "change models");
|
|
1017
1095
|
const candidates = session.scopedModels.length > 0
|
|
1018
1096
|
? session.scopedModels.map((scoped) => scoped.model)
|
|
1019
1097
|
: session.modelRuntime.getAvailableSnapshot();
|
|
@@ -1021,7 +1099,7 @@ export class PiSessionService {
|
|
|
1021
1099
|
?? session.modelRuntime.getModel(provider, modelId);
|
|
1022
1100
|
if (model === undefined)
|
|
1023
1101
|
throw new Error(`Model not found: ${provider}/${modelId}`);
|
|
1024
|
-
await this.
|
|
1102
|
+
await this.runExactModelPolicyMutation(session, "change models", currentPolicy, () => session.setModel(model));
|
|
1025
1103
|
this.publishActivity(session, `model: ${model.id}`, "idle", model.provider);
|
|
1026
1104
|
this.publishStatus(session);
|
|
1027
1105
|
return this.statusFromSession(session);
|
|
@@ -1029,9 +1107,14 @@ export class PiSessionService {
|
|
|
1029
1107
|
async cycleModel(ref, direction) {
|
|
1030
1108
|
await this.assertWritable(ref);
|
|
1031
1109
|
const session = await this.getOrOpen(ref);
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
|
|
1110
|
+
const currentPolicy = this.assertExactModelPolicyMutationAllowed(session, "change models");
|
|
1111
|
+
const result = await this.runExactModelPolicyMutation(session, "change models", currentPolicy, async () => {
|
|
1112
|
+
const cycled = await session.cycleModel(direction);
|
|
1113
|
+
if (cycled === undefined) {
|
|
1114
|
+
throw new Error(session.scopedModels.length > 0 ? "Only one model in scope" : "Only one model available");
|
|
1115
|
+
}
|
|
1116
|
+
return cycled;
|
|
1117
|
+
});
|
|
1035
1118
|
this.publishActivity(session, `model: ${result.model.id}`, "idle", result.model.provider);
|
|
1036
1119
|
this.publishStatus(session);
|
|
1037
1120
|
return this.statusFromSession(session);
|
|
@@ -1043,14 +1126,17 @@ export class PiSessionService {
|
|
|
1043
1126
|
async setThinkingLevel(ref, level) {
|
|
1044
1127
|
await this.assertWritable(ref);
|
|
1045
1128
|
const session = await this.getOrOpen(ref);
|
|
1046
|
-
this.
|
|
1129
|
+
const currentPolicy = this.assertExactModelPolicyMutationAllowed(session, "change the thinking level");
|
|
1047
1130
|
// pi owns the valid set; validate against the session's live levels rather
|
|
1048
1131
|
// than a hardcoded union so this stays correct if pi changes the set.
|
|
1049
1132
|
const available = session.getAvailableThinkingLevels();
|
|
1050
1133
|
const match = available.find((candidate) => candidate === level);
|
|
1051
1134
|
if (match === undefined)
|
|
1052
1135
|
throw new Error(`Invalid thinking level: ${level}`);
|
|
1053
|
-
|
|
1136
|
+
await this.runExactModelPolicyMutation(session, "change the thinking level", currentPolicy, () => {
|
|
1137
|
+
session.setThinkingLevel(match);
|
|
1138
|
+
return Promise.resolve();
|
|
1139
|
+
});
|
|
1054
1140
|
this.publishActivity(session, `thinking: ${session.thinkingLevel}`, "idle");
|
|
1055
1141
|
this.publishStatus(session);
|
|
1056
1142
|
return this.statusFromSession(session);
|
|
@@ -1058,10 +1144,13 @@ export class PiSessionService {
|
|
|
1058
1144
|
async cycleThinkingLevel(ref) {
|
|
1059
1145
|
await this.assertWritable(ref);
|
|
1060
1146
|
const session = await this.getOrOpen(ref);
|
|
1061
|
-
this.
|
|
1062
|
-
const level =
|
|
1063
|
-
|
|
1064
|
-
|
|
1147
|
+
const currentPolicy = this.assertExactModelPolicyMutationAllowed(session, "change the thinking level");
|
|
1148
|
+
const level = await this.runExactModelPolicyMutation(session, "change the thinking level", currentPolicy, () => {
|
|
1149
|
+
const cycled = session.cycleThinkingLevel();
|
|
1150
|
+
if (cycled === undefined)
|
|
1151
|
+
throw new Error("Current model does not support thinking");
|
|
1152
|
+
return Promise.resolve(cycled);
|
|
1153
|
+
});
|
|
1065
1154
|
this.publishActivity(session, `thinking: ${level}`, "idle");
|
|
1066
1155
|
this.publishStatus(session);
|
|
1067
1156
|
return this.statusFromSession(session);
|
|
@@ -1093,6 +1182,7 @@ export class PiSessionService {
|
|
|
1093
1182
|
await this.assertWritable(ref);
|
|
1094
1183
|
const session = await this.getOrOpen(ref);
|
|
1095
1184
|
this.assertTreeNavigationInactive(session, "send a prompt");
|
|
1185
|
+
this.assertPromptModelPolicyAllowed(session);
|
|
1096
1186
|
this.maybeGenerateSessionName(session, promptText);
|
|
1097
1187
|
const isQueued = session.isStreaming || session.isCompacting;
|
|
1098
1188
|
const behavior = isQueued ? requestedBehavior ?? "followUp" : undefined;
|
|
@@ -1105,9 +1195,31 @@ export class PiSessionService {
|
|
|
1105
1195
|
this.enqueuePromptDuringCompaction(session, promptText, behavior ?? "followUp", images, echoUserMessage);
|
|
1106
1196
|
return;
|
|
1107
1197
|
}
|
|
1198
|
+
if (this.isModelPolicyMutationActive(session)) {
|
|
1199
|
+
this.retainPromptDuringModelPolicyMutation(session, promptText, behavior ?? "followUp", images, echoUserMessage);
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1108
1202
|
void this.submitPrompt(session, promptText, behavior, images, echoUserMessage);
|
|
1109
1203
|
}
|
|
1110
1204
|
submitPrompt(session, text, behavior, images = [], echoUserMessage = true) {
|
|
1205
|
+
// A policy transition may have opened while this prompt waited in the
|
|
1206
|
+
// compaction queue. The runtime tuple is transient until the transition
|
|
1207
|
+
// persists, so retain the input instead of reaching Pi with a partial pair.
|
|
1208
|
+
if (this.isModelPolicyMutationActive(session)) {
|
|
1209
|
+
this.retainPromptDuringModelPolicyMutation(session, text, behavior ?? "followUp", images, echoUserMessage, "front");
|
|
1210
|
+
return Promise.resolve();
|
|
1211
|
+
}
|
|
1212
|
+
// Re-check before any activity publication or provider call: a prompt held
|
|
1213
|
+
// in the compaction queue may have waited while the policy entry changed.
|
|
1214
|
+
try {
|
|
1215
|
+
this.assertPromptModelPolicyAllowed(session);
|
|
1216
|
+
}
|
|
1217
|
+
catch (error) {
|
|
1218
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1219
|
+
this.publishActivity(session, "error", "error", message);
|
|
1220
|
+
this.events.publish(session.sessionId, { type: "session.error", message });
|
|
1221
|
+
return Promise.resolve();
|
|
1222
|
+
}
|
|
1111
1223
|
this.publishActivity(session, behavior === "steer" ? "steering queued" : behavior === "followUp" ? "message queued" : "prompt accepted", "active");
|
|
1112
1224
|
if (behavior === undefined && echoUserMessage)
|
|
1113
1225
|
this.events.publish(session.sessionId, { type: "message.append", message: userMessage(text, images) });
|
|
@@ -1127,6 +1239,23 @@ export class PiSessionService {
|
|
|
1127
1239
|
this.publishActivity(session, "message queued during compaction", "active");
|
|
1128
1240
|
this.publishStatus(session);
|
|
1129
1241
|
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Hold prompt input for the duration of a policy transition, reusing the
|
|
1244
|
+
* existing queue so the post-mutation drain submits it once the confirmed
|
|
1245
|
+
* model/thinking pair is persisted. A prompt taken back out of the queue is
|
|
1246
|
+
* re-retained at the front so relative order is preserved.
|
|
1247
|
+
*/
|
|
1248
|
+
retainPromptDuringModelPolicyMutation(session, text, kind, images = [], echoUserMessage = true, position = "back") {
|
|
1249
|
+
const queue = this.compactionPromptQueues.get(session.sessionId) ?? [];
|
|
1250
|
+
const queued = { kind, text, ...(images.length > 0 ? { images } : {}), ...(echoUserMessage ? {} : { echoUserMessage: false }) };
|
|
1251
|
+
if (position === "front")
|
|
1252
|
+
queue.unshift(queued);
|
|
1253
|
+
else
|
|
1254
|
+
queue.push(queued);
|
|
1255
|
+
this.compactionPromptQueues.set(session.sessionId, queue);
|
|
1256
|
+
this.publishActivity(session, "message queued during model policy change", "active");
|
|
1257
|
+
this.publishStatus(session);
|
|
1258
|
+
}
|
|
1130
1259
|
async saveAttachments(ref, attachments, folder) {
|
|
1131
1260
|
const parsed = parsePromptAttachments(attachments, { enforceInlineSizeLimit: false, allowFileAttachments: true });
|
|
1132
1261
|
if (parsed.length === 0)
|
|
@@ -1969,6 +2098,7 @@ export class PiSessionService {
|
|
|
1969
2098
|
if (notificationGeneration !== undefined)
|
|
1970
2099
|
this.notificationGenerationBySession.set(runtime.session, notificationGeneration);
|
|
1971
2100
|
try {
|
|
2101
|
+
this.inspectAndCacheSessionModelPolicy(runtime.session);
|
|
1972
2102
|
if (options.creationProvenance === "tracked-subsession") {
|
|
1973
2103
|
await this.publishUnreadMutations(this.unreadStore.excludeSession(runtime.session.sessionId, canonicalizeStoredCwd(runtime.session.sessionManager.getCwd())));
|
|
1974
2104
|
}
|
|
@@ -1987,6 +2117,7 @@ export class PiSessionService {
|
|
|
1987
2117
|
candidateGeneration = this.notificationStore.beginReplacement(priorGeneration, notificationIdentityForSession(session));
|
|
1988
2118
|
this.notificationGenerationBySession.set(session, candidateGeneration);
|
|
1989
2119
|
}
|
|
2120
|
+
this.inspectAndCacheSessionModelPolicy(session);
|
|
1990
2121
|
this.bindRuntime(active, session);
|
|
1991
2122
|
boundSession = session;
|
|
1992
2123
|
await this.bindSessionExtensions(session, candidateGeneration);
|
|
@@ -2005,6 +2136,9 @@ export class PiSessionService {
|
|
|
2005
2136
|
}
|
|
2006
2137
|
});
|
|
2007
2138
|
this.active.set(runtime.session.sessionId, active);
|
|
2139
|
+
if (options.initializeModelPolicy !== undefined) {
|
|
2140
|
+
await this.initializeSessionModelPolicy(runtime.session, options.initializeModelPolicy);
|
|
2141
|
+
}
|
|
2008
2142
|
if (notificationOwnership === "replacement" && notificationGeneration !== undefined) {
|
|
2009
2143
|
this.publishNotificationMutations(this.notificationStore.commitReplacement(notificationGeneration));
|
|
2010
2144
|
notificationOwnership = "external";
|
|
@@ -2295,7 +2429,7 @@ export class PiSessionService {
|
|
|
2295
2429
|
if (active === undefined)
|
|
2296
2430
|
return;
|
|
2297
2431
|
const { session } = active.runtime;
|
|
2298
|
-
if (session.isCompacting) {
|
|
2432
|
+
if (session.isCompacting || this.isModelPolicyMutationActive(session)) {
|
|
2299
2433
|
this.scheduleCompactionQueueDrain(sessionId, 100);
|
|
2300
2434
|
return;
|
|
2301
2435
|
}
|
|
@@ -2637,6 +2771,334 @@ export class PiSessionService {
|
|
|
2637
2771
|
this.events.publish(session.sessionId, { type: "activity.update", activity });
|
|
2638
2772
|
this.events.publishGlobal({ type: "activity.update", activity });
|
|
2639
2773
|
}
|
|
2774
|
+
exactSelectionFromSession(session) {
|
|
2775
|
+
const model = session.model;
|
|
2776
|
+
if (model === undefined
|
|
2777
|
+
|| typeof model.provider !== "string"
|
|
2778
|
+
|| model.provider.trim() === ""
|
|
2779
|
+
|| typeof model.id !== "string"
|
|
2780
|
+
|| model.id.trim() === "") {
|
|
2781
|
+
throw new Error("Session model policy requires a resolved runtime model provider and id");
|
|
2782
|
+
}
|
|
2783
|
+
if (typeof session.thinkingLevel !== "string" || session.thinkingLevel.trim() === "") {
|
|
2784
|
+
throw new Error("Session model policy requires a resolved runtime thinking level");
|
|
2785
|
+
}
|
|
2786
|
+
return {
|
|
2787
|
+
model: { provider: model.provider, id: model.id },
|
|
2788
|
+
thinkingLevel: session.thinkingLevel,
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
policyEntries(session) {
|
|
2792
|
+
return session.sessionManager.getEntries?.() ?? session.sessionManager.getBranch();
|
|
2793
|
+
}
|
|
2794
|
+
inspectAndCacheSessionModelPolicy(session) {
|
|
2795
|
+
const inspection = inspectSessionModelPolicy(this.policyEntries(session), this.exactSelectionFromSession(session));
|
|
2796
|
+
const ladderValidation = this.modelTierRegistry.validate();
|
|
2797
|
+
this.modelPolicyInspections.set(session, inspection);
|
|
2798
|
+
this.modelPolicyLadderValidations.set(session, ladderValidation);
|
|
2799
|
+
if (inspection.kind === "invalid")
|
|
2800
|
+
this.modelPolicyEntryReasons.set(session, inspection.reason);
|
|
2801
|
+
else
|
|
2802
|
+
this.modelPolicyEntryReasons.delete(session);
|
|
2803
|
+
return inspection;
|
|
2804
|
+
}
|
|
2805
|
+
/** Effective block: an unproven runtime restoration outranks an entry defect. */
|
|
2806
|
+
modelPolicyBlockedReason(session) {
|
|
2807
|
+
return this.modelPolicyRuntimeBlocks.get(session.sessionId) ?? this.modelPolicyEntryReasons.get(session);
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* Root-session policy initialization. `true` records the runtime's own tuple;
|
|
2811
|
+
* an update applies the requested transition. Both complete before the caller
|
|
2812
|
+
* of `start()` sees the session, so no prompt can precede the policy.
|
|
2813
|
+
*/
|
|
2814
|
+
async initializeSessionModelPolicy(session, initializer) {
|
|
2815
|
+
const current = { mode: "exact", exact: this.exactSelectionFromSession(session) };
|
|
2816
|
+
if (initializer === true) {
|
|
2817
|
+
this.appendSessionModelPolicy(session, current);
|
|
2818
|
+
this.inspectAndCacheSessionModelPolicy(session);
|
|
2819
|
+
return;
|
|
2820
|
+
}
|
|
2821
|
+
// Root creation reports failure through its own abort/dispose cleanup rather
|
|
2822
|
+
// than publishing a failed transition for a session no client has seen.
|
|
2823
|
+
await this.applySessionModelPolicy(session, current, initializer, { action: "initialize the session model policy" });
|
|
2824
|
+
}
|
|
2825
|
+
/**
|
|
2826
|
+
* The single policy transition path. Validates the complete target before any
|
|
2827
|
+
* setter, re-checks that no conflicting work started during that async
|
|
2828
|
+
* validation, applies model then thinking under the serialized entry-mutation
|
|
2829
|
+
* seam, verifies the effective pair, and only then persists and caches.
|
|
2830
|
+
*/
|
|
2831
|
+
async applySessionModelPolicy(session, current, update, options) {
|
|
2832
|
+
const plan = planSessionModelPolicyUpdate(current, update, (tier) => {
|
|
2833
|
+
const resolved = this.modelTierRegistry.resolve(tier);
|
|
2834
|
+
return { model: { provider: resolved.model.provider, id: resolved.model.id }, thinkingLevel: resolved.thinkingLevel };
|
|
2835
|
+
});
|
|
2836
|
+
// Validation happens before the mutation opens, so a rejected target leaves
|
|
2837
|
+
// the runtime and the persisted policy untouched.
|
|
2838
|
+
const target = await this.resolveAvailableExactSelection(session, plan.target);
|
|
2839
|
+
// Refresh/validation awaited above, so a prompt or another mutation may have
|
|
2840
|
+
// started meanwhile. Re-check here — synchronously adjacent to the mutation
|
|
2841
|
+
// open, and *outside* it so the mutation is never its own conflict — to close
|
|
2842
|
+
// the await-to-setter race without touching tree-navigation behavior.
|
|
2843
|
+
this.assertModelPolicyMutationIdle(session, options.action);
|
|
2844
|
+
return this.runSessionModelPolicyMutation(session, "change session model policy", async () => {
|
|
2845
|
+
const previous = this.exactSelectionFromSession(session);
|
|
2846
|
+
try {
|
|
2847
|
+
await this.applyExactSelection(session, target);
|
|
2848
|
+
this.appendSessionModelPolicy(session, plan.policy);
|
|
2849
|
+
}
|
|
2850
|
+
catch (error) {
|
|
2851
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2852
|
+
if (!await this.restoreExactSelection(session, previous)) {
|
|
2853
|
+
this.modelPolicyRuntimeBlocks.set(session.sessionId, `MODEL_POLICY_BLOCKED: ${reason}; the previous model and thinking pair could not be restored`);
|
|
2854
|
+
}
|
|
2855
|
+
this.inspectAndCacheSessionModelPolicy(session);
|
|
2856
|
+
options.onTransitionFailure?.(reason);
|
|
2857
|
+
throw error;
|
|
2858
|
+
}
|
|
2859
|
+
this.modelPolicyRuntimeBlocks.delete(session.sessionId);
|
|
2860
|
+
this.inspectAndCacheSessionModelPolicy(session);
|
|
2861
|
+
return plan.policy;
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Serialized policy-transition seam. Adds the policy-mutation marker on top of
|
|
2866
|
+
* the shared entry-mutation lifecycle so prompt submission can tell a transient
|
|
2867
|
+
* policy window apart from ordinary session work, and drains any prompt input
|
|
2868
|
+
* retained during that window once the tuple is settled again.
|
|
2869
|
+
*/
|
|
2870
|
+
async runSessionModelPolicyMutation(session, action, operation) {
|
|
2871
|
+
// Captured before the first setter runs, so this is the confirmed tuple the
|
|
2872
|
+
// window reports. Only the outermost mutation records it: a nested mutation
|
|
2873
|
+
// opens on an already-transient tuple, which would not be confirmed.
|
|
2874
|
+
if (!this.isModelPolicyMutationActive(session))
|
|
2875
|
+
this.recordConfirmedExactSelection(session);
|
|
2876
|
+
this.modelPolicyMutationCounts.set(session.sessionId, (this.modelPolicyMutationCounts.get(session.sessionId) ?? 0) + 1);
|
|
2877
|
+
try {
|
|
2878
|
+
return await this.runSessionEntryMutation(session, action, operation);
|
|
2879
|
+
}
|
|
2880
|
+
finally {
|
|
2881
|
+
decrementMapCount(this.modelPolicyMutationCounts, session.sessionId);
|
|
2882
|
+
if (!this.isModelPolicyMutationActive(session)) {
|
|
2883
|
+
// The tuple is settled again (applied, restored, or blocked), so live
|
|
2884
|
+
// runtime state is authoritative once more.
|
|
2885
|
+
this.modelPolicyConfirmedSelections.delete(session.sessionId);
|
|
2886
|
+
this.scheduleCompactionQueueDrain(session.sessionId);
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* Remember the pre-transition tuple for the status projection. A session whose
|
|
2892
|
+
* runtime has no resolved model has no confirmed tuple to report; that case is
|
|
2893
|
+
* left to the existing `exactSelectionFromSession` failure rather than
|
|
2894
|
+
* substituting anything.
|
|
2895
|
+
*/
|
|
2896
|
+
recordConfirmedExactSelection(session) {
|
|
2897
|
+
try {
|
|
2898
|
+
this.modelPolicyConfirmedSelections.set(session.sessionId, this.exactSelectionFromSession(session));
|
|
2899
|
+
}
|
|
2900
|
+
catch {
|
|
2901
|
+
this.modelPolicyConfirmedSelections.delete(session.sessionId);
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
isModelPolicyMutationActive(session) {
|
|
2905
|
+
return (this.modelPolicyMutationCounts.get(session.sessionId) ?? 0) > 0;
|
|
2906
|
+
}
|
|
2907
|
+
/**
|
|
2908
|
+
* Tell other clients a policy transition failed. Called after the failure state
|
|
2909
|
+
* (including any `MODEL_POLICY_BLOCKED`) is recorded and before the route error
|
|
2910
|
+
* propagates, so the published status already carries the blocked reason.
|
|
2911
|
+
*/
|
|
2912
|
+
publishModelPolicyTransitionFailure(session, reason) {
|
|
2913
|
+
this.publishActivity(session, "model policy change failed", "error", reason);
|
|
2914
|
+
this.publishStatus(session);
|
|
2915
|
+
}
|
|
2916
|
+
/**
|
|
2917
|
+
* Reject a policy mutation that could race Pi's own entry writes. Tree
|
|
2918
|
+
* navigation is reported first because it is the more specific state.
|
|
2919
|
+
*/
|
|
2920
|
+
assertModelPolicyMutationIdle(session, action) {
|
|
2921
|
+
this.assertTreeNavigationInactive(session, action);
|
|
2922
|
+
if (this.hasActiveWork(session))
|
|
2923
|
+
throw new Error(`Stop current session activity before you ${action}`);
|
|
2924
|
+
}
|
|
2925
|
+
/**
|
|
2926
|
+
* Gate for the direct Exact model/thinking routes. They act only in Exact
|
|
2927
|
+
* mode: while Tiered is active the ladder owns the runtime tuple, and while
|
|
2928
|
+
* the runtime is blocked no tuple change may be recorded as authoritative.
|
|
2929
|
+
*/
|
|
2930
|
+
assertExactModelPolicyMutationAllowed(session, action) {
|
|
2931
|
+
this.assertModelPolicyMutationIdle(session, action);
|
|
2932
|
+
const inspection = this.inspectAndCacheSessionModelPolicy(session);
|
|
2933
|
+
const runtimeBlock = this.modelPolicyRuntimeBlocks.get(session.sessionId);
|
|
2934
|
+
if (runtimeBlock !== undefined)
|
|
2935
|
+
throw new Error(`Cannot ${action}: ${runtimeBlock}`);
|
|
2936
|
+
if (inspection.kind === "invalid") {
|
|
2937
|
+
throw new Error(`Cannot ${action}: the session model policy entry is invalid (${inspection.reason}). Repair the policy first.`);
|
|
2938
|
+
}
|
|
2939
|
+
if (inspection.policy.mode === "tiered") {
|
|
2940
|
+
throw new Error(`Cannot ${action} directly while the session model policy is Tiered. Change the model policy instead.`);
|
|
2941
|
+
}
|
|
2942
|
+
return inspection.policy;
|
|
2943
|
+
}
|
|
2944
|
+
/**
|
|
2945
|
+
* Run one Exact route operation and record Pi's *confirmed* resulting pair as
|
|
2946
|
+
* the new authoritative policy inside the same serialized mutation. Pi's own
|
|
2947
|
+
* model-selection clamp is therefore persisted as-is rather than replaced by
|
|
2948
|
+
* an invented target thinking level.
|
|
2949
|
+
*/
|
|
2950
|
+
async runExactModelPolicyMutation(session, action, currentPolicy, operation) {
|
|
2951
|
+
let transitionFailure;
|
|
2952
|
+
try {
|
|
2953
|
+
return await this.runSessionModelPolicyMutation(session, action, async () => {
|
|
2954
|
+
const previous = this.exactSelectionFromSession(session);
|
|
2955
|
+
const result = await operation();
|
|
2956
|
+
const policy = {
|
|
2957
|
+
mode: "exact",
|
|
2958
|
+
exact: this.exactSelectionFromSession(session),
|
|
2959
|
+
...(currentPolicy.tier === undefined ? {} : { tier: currentPolicy.tier }),
|
|
2960
|
+
};
|
|
2961
|
+
try {
|
|
2962
|
+
this.appendSessionModelPolicy(session, policy);
|
|
2963
|
+
}
|
|
2964
|
+
catch (error) {
|
|
2965
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2966
|
+
if (!await this.restoreExactSelection(session, previous)) {
|
|
2967
|
+
this.modelPolicyRuntimeBlocks.set(session.sessionId, `MODEL_POLICY_BLOCKED: ${reason}; the previous model and thinking pair could not be restored`);
|
|
2968
|
+
}
|
|
2969
|
+
this.inspectAndCacheSessionModelPolicy(session);
|
|
2970
|
+
// Only a failure that already moved the runtime tuple is announced; an
|
|
2971
|
+
// operation precondition ("Only one model available") keeps its plain
|
|
2972
|
+
// route error and publishes nothing.
|
|
2973
|
+
transitionFailure = reason;
|
|
2974
|
+
throw error;
|
|
2975
|
+
}
|
|
2976
|
+
this.inspectAndCacheSessionModelPolicy(session);
|
|
2977
|
+
return result;
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
catch (error) {
|
|
2981
|
+
// Published after the mutation closed so the status reports the settled
|
|
2982
|
+
// (blocked or restored) state rather than "updating session".
|
|
2983
|
+
if (transitionFailure !== undefined)
|
|
2984
|
+
this.publishModelPolicyTransitionFailure(session, transitionFailure);
|
|
2985
|
+
throw error;
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
/**
|
|
2989
|
+
* Refuse to reach a provider while the authoritative policy is unusable. This
|
|
2990
|
+
* covers a malformed newest entry and an unproven restoration; it deliberately
|
|
2991
|
+
* does no tier remapping.
|
|
2992
|
+
*/
|
|
2993
|
+
assertPromptModelPolicyAllowed(session) {
|
|
2994
|
+
const inspection = this.inspectAndCacheSessionModelPolicy(session);
|
|
2995
|
+
const runtimeBlock = this.modelPolicyRuntimeBlocks.get(session.sessionId);
|
|
2996
|
+
if (runtimeBlock !== undefined)
|
|
2997
|
+
throw new Error(`Cannot send a prompt: ${runtimeBlock}`);
|
|
2998
|
+
if (inspection.kind === "invalid") {
|
|
2999
|
+
throw new Error(`Cannot send a prompt: the session model policy entry is invalid (${inspection.reason}). Repair the policy first.`);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
/**
|
|
3003
|
+
* Bind an exact selection to a currently available runtime model and prove the
|
|
3004
|
+
* target thinking level is supported by that *incoming* model. Pi's
|
|
3005
|
+
* `setThinkingLevel` clamps silently, so this check must precede every setter.
|
|
3006
|
+
*/
|
|
3007
|
+
async resolveAvailableExactSelection(session, selection) {
|
|
3008
|
+
await session.modelRuntime.refresh({ allowNetwork: false });
|
|
3009
|
+
const candidates = session.scopedModels.length > 0
|
|
3010
|
+
? session.scopedModels.map((scoped) => scoped.model)
|
|
3011
|
+
: session.modelRuntime.getAvailableSnapshot();
|
|
3012
|
+
const model = candidates.find((candidate) => candidate.provider === selection.model.provider && candidate.id === selection.model.id);
|
|
3013
|
+
const described = `${selection.model.provider}/${selection.model.id}`;
|
|
3014
|
+
if (model === undefined)
|
|
3015
|
+
throw new Error(`Model not found: ${described}`);
|
|
3016
|
+
if (!isKnownThinkingLevel(selection.thinkingLevel)) {
|
|
3017
|
+
throw new Error(`Unknown thinking level ${selection.thinkingLevel} for ${described}`);
|
|
3018
|
+
}
|
|
3019
|
+
if (!runtimeThinkingLevels(model).includes(selection.thinkingLevel)) {
|
|
3020
|
+
throw new Error(`Thinking level ${selection.thinkingLevel} is unsupported by ${described}`);
|
|
3021
|
+
}
|
|
3022
|
+
return { model, selection: { model: { provider: model.provider, id: model.id }, thinkingLevel: selection.thinkingLevel } };
|
|
3023
|
+
}
|
|
3024
|
+
/**
|
|
3025
|
+
* Model first, then thinking: `setThinkingLevel` clamps against the currently
|
|
3026
|
+
* selected model, so the reverse order can discard a level the incoming model
|
|
3027
|
+
* supports. The effective pair is verified afterwards; a clamped substitute is
|
|
3028
|
+
* a failed transition, never a success.
|
|
3029
|
+
*/
|
|
3030
|
+
async applyExactSelection(session, target) {
|
|
3031
|
+
const level = target.selection.thinkingLevel;
|
|
3032
|
+
if (!isKnownThinkingLevel(level))
|
|
3033
|
+
throw new Error(`Unknown thinking level ${level}`);
|
|
3034
|
+
await session.setModel(target.model);
|
|
3035
|
+
session.setThinkingLevel(level);
|
|
3036
|
+
const effective = this.exactSelectionFromSession(session);
|
|
3037
|
+
if (effective.model.provider !== target.selection.model.provider
|
|
3038
|
+
|| effective.model.id !== target.selection.model.id
|
|
3039
|
+
|| effective.thinkingLevel !== level) {
|
|
3040
|
+
throw new Error(`Session model policy could not activate ${target.selection.model.provider}/${target.selection.model.id} at thinking level ${level}`
|
|
3041
|
+
+ `; the runtime reports ${effective.model.provider}/${effective.model.id} at thinking level ${effective.thinkingLevel}`);
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
/**
|
|
3045
|
+
* Put the previous exact pair back in the same model-then-thinking order.
|
|
3046
|
+
* Returns whether the previous pair is *proven* active again; a false result
|
|
3047
|
+
* means the runtime tuple is ambiguous and the session must refuse prompts.
|
|
3048
|
+
*/
|
|
3049
|
+
async restoreExactSelection(session, previous) {
|
|
3050
|
+
try {
|
|
3051
|
+
const current = this.exactSelectionFromSession(session);
|
|
3052
|
+
if (current.model.provider === previous.model.provider
|
|
3053
|
+
&& current.model.id === previous.model.id
|
|
3054
|
+
&& current.thinkingLevel === previous.thinkingLevel) {
|
|
3055
|
+
return true;
|
|
3056
|
+
}
|
|
3057
|
+
const target = await this.resolveAvailableExactSelection(session, previous);
|
|
3058
|
+
await this.applyExactSelection(session, target);
|
|
3059
|
+
return true;
|
|
3060
|
+
}
|
|
3061
|
+
catch {
|
|
3062
|
+
return false;
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
appendSessionModelPolicy(session, policy) {
|
|
3066
|
+
if (session.sessionManager.appendCustomEntry === undefined) {
|
|
3067
|
+
throw new Error("Cannot persist the session model policy: session persistence is unavailable");
|
|
3068
|
+
}
|
|
3069
|
+
session.sessionManager.appendCustomEntry(SESSION_MODEL_POLICY_CUSTOM_TYPE, serializeSessionModelPolicy(policy));
|
|
3070
|
+
}
|
|
3071
|
+
modelPolicyStatusFromSession(session) {
|
|
3072
|
+
const inspection = this.modelPolicyInspections.get(session);
|
|
3073
|
+
if (inspection === undefined)
|
|
3074
|
+
throw new Error("Session model policy inspection is unavailable");
|
|
3075
|
+
const ladderValidation = this.modelPolicyLadderValidations.get(session);
|
|
3076
|
+
if (ladderValidation === undefined)
|
|
3077
|
+
throw new Error("Session model tier validation is unavailable");
|
|
3078
|
+
// Inside a transition the live tuple is half-applied, so report the tuple
|
|
3079
|
+
// this session last confirmed. `mode` already comes from the unchanged
|
|
3080
|
+
// entry, keeping the published pair internally consistent.
|
|
3081
|
+
const confirmed = this.isModelPolicyMutationActive(session)
|
|
3082
|
+
? this.modelPolicyConfirmedSelections.get(session.sessionId)
|
|
3083
|
+
: undefined;
|
|
3084
|
+
const resolved = confirmed ?? this.exactSelectionFromSession(session);
|
|
3085
|
+
const blockedReason = this.modelPolicyBlockedReason(session);
|
|
3086
|
+
if (inspection.kind === "invalid") {
|
|
3087
|
+
return {
|
|
3088
|
+
mode: "exact",
|
|
3089
|
+
resolved,
|
|
3090
|
+
ladderValid: ladderValidation.valid,
|
|
3091
|
+
...(blockedReason === undefined ? {} : { blockedReason }),
|
|
3092
|
+
};
|
|
3093
|
+
}
|
|
3094
|
+
return {
|
|
3095
|
+
mode: inspection.policy.mode,
|
|
3096
|
+
...(inspection.policy.tier === undefined ? {} : { tier: inspection.policy.tier }),
|
|
3097
|
+
resolved,
|
|
3098
|
+
ladderValid: ladderValidation.valid,
|
|
3099
|
+
...(blockedReason === undefined ? {} : { blockedReason }),
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
2640
3102
|
statusFromSession(session) {
|
|
2641
3103
|
const stats = session.getSessionStats();
|
|
2642
3104
|
const model = session.model === undefined ? undefined : modelToClientModel(session.model);
|
|
@@ -2648,6 +3110,7 @@ export class PiSessionService {
|
|
|
2648
3110
|
persisted: sessionFileExists(session.sessionFile),
|
|
2649
3111
|
...(model === undefined ? {} : { model }),
|
|
2650
3112
|
thinkingLevel: session.thinkingLevel,
|
|
3113
|
+
modelPolicy: this.modelPolicyStatusFromSession(session),
|
|
2651
3114
|
isStreaming: session.isStreaming,
|
|
2652
3115
|
isCompacting: session.isCompacting,
|
|
2653
3116
|
isBashRunning: session.isBashRunning,
|