@hassangameryt/freeflow 0.6.0 → 0.7.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +3 -2
- package/.codex-plugin/plugin.json +5 -6
- package/.cursor-plugin/plugin.json +25 -0
- package/README.md +66 -56
- package/capabilities/cognitive-routing/SKILL.md +288 -130
- package/com.github.copilot/hooks/hooks.json +11 -0
- package/command-surface.json +0 -24
- package/gemini-extension.json +5 -0
- package/hooks/adapters/claude-session-start.mjs +16 -0
- package/hooks/adapters/codex-session-start.mjs +16 -0
- package/hooks/adapters/copilot-session-start.mjs +13 -0
- package/hooks/adapters/cursor-session-start.mjs +13 -0
- package/hooks/adapters/gemini-session-start.mjs +15 -0
- package/hooks/claude/hooks.json +18 -0
- package/hooks/codex/hooks.json +18 -0
- package/hooks/cursor/hooks.json +9 -0
- package/hooks/freeflow-runtime-context.mjs +3 -650
- package/hooks/hooks.json +4 -30
- package/hooks/shared/runtime-context.mjs +210 -0
- package/hooks/tests/check-host-adapters.test.mjs +82 -0
- package/hooks/tests/check-runtime-context-hook.sh +140 -320
- package/package.json +20 -4
- package/pi-extension/dist/cognitive-routing/commands.js +3 -3
- package/pi-extension/dist/cognitive-routing/config.js +16 -20
- package/pi-extension/dist/cognitive-routing/controller.js +478 -48
- package/pi-extension/dist/cognitive-routing/history.js +12 -0
- package/pi-extension/dist/cognitive-routing/host.js +74 -12
- package/pi-extension/dist/cognitive-routing/pi-routing-session.js +414 -0
- package/pi-extension/dist/cognitive-routing/pi-session-control.js +320 -0
- package/pi-extension/dist/cognitive-routing/tool.js +1 -0
- package/pi-extension/dist/cognitive-routing/types.js +1 -1
- package/pi-extension/dist/index.js +236 -219
- package/pi-extension/dist/runtime/runtime-context.js +126 -286
- package/pi-extension/dist/settings/settings-ui.js +52 -336
- package/pi-extension/src/cognitive-routing/commands.ts +15 -5
- package/pi-extension/src/cognitive-routing/config.ts +16 -20
- package/pi-extension/src/cognitive-routing/controller.ts +561 -51
- package/pi-extension/src/cognitive-routing/history.ts +12 -0
- package/pi-extension/src/cognitive-routing/host.ts +110 -16
- package/pi-extension/src/cognitive-routing/pi-routing-session.ts +538 -0
- package/pi-extension/src/cognitive-routing/pi-session-control.ts +444 -0
- package/pi-extension/src/cognitive-routing/tool.ts +1 -0
- package/pi-extension/src/cognitive-routing/types.ts +1 -1
- package/pi-extension/src/index.ts +249 -239
- package/pi-extension/src/runtime/runtime-context.ts +129 -305
- package/pi-extension/src/settings/settings-ui.ts +53 -371
- package/pi-extension/tests/cognitive-routing/commands.test.js +1 -1
- package/pi-extension/tests/cognitive-routing/controller.test.js +69 -40
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +128 -57
- package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +434 -0
- package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +174 -0
- package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +91 -0
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +47 -4
- package/pi-extension/tests/integration/mode-free-core.test.js +218 -0
- package/pi-extension/tests/integration/pi-extension.test.js +430 -2478
- package/pi-extension/tests/integration/prompt-architecture.test.js +124 -79
- package/pi-extension/tests/integration/unaffected-regressions.test.js +411 -0
- package/plugin.json +33 -0
- package/runtime/prompts/cognitive-routing.md +5 -4
- package/runtime/prompts/conversation-history.md +1 -1
- package/runtime/prompts/core.md +77 -6
- package/runtime/prompts/interaction-contract.md +6 -2
- package/skills/action-selection/SKILL.md +93 -44
- package/skills/action-selection/references/trajectory-stalls.md +71 -0
- package/skills/bypass/SKILL.md +109 -39
- package/skills/commit-work/SKILL.md +94 -55
- package/skills/commit-work/references/git-state-edges.md +94 -0
- package/skills/commit-work/references/simple-push.md +92 -0
- package/skills/commit-work/references/staging-decisions.md +79 -46
- package/skills/decision-gate/SKILL.md +80 -22
- package/skills/design-for-depth/SKILL.md +208 -43
- package/skills/design-for-depth/references/design-pressure-signals.md +108 -240
- package/skills/design-for-depth/references/interface-design-loop.md +77 -97
- package/skills/design-for-depth/references/module-and-dependency-design.md +147 -0
- package/skills/design-for-depth/references/software-design-philosophy.md +127 -173
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +136 -0
- package/skills/diagnose-failure/SKILL.md +198 -52
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +19 -5
- package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +13 -5
- package/skills/discuss/SKILL.md +118 -76
- package/skills/discuss/references/discussion-continuity.md +84 -32
- package/skills/evaluate-skill/SKILL.md +166 -52
- package/skills/evaluate-skill/references/evaluation-definition-schema.md +2 -2
- package/skills/evaluate-skill/references/evaluation-design.md +56 -30
- package/skills/evaluate-skill/references/execution-and-evidence.md +1 -1
- package/skills/evaluate-skill/references/review-and-revision.md +62 -21
- package/skills/execute-work/SKILL.md +98 -59
- package/skills/execute-work/references/code-practices.md +4 -4
- package/skills/execute-work/references/execute-work-edges.md +42 -22
- package/skills/review-artifact/SKILL.md +132 -71
- package/skills/review-artifact/references/adjudicate-artifact-review.md +101 -0
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +52 -41
- package/skills/review-work/SKILL.md +115 -72
- package/skills/review-work/references/adjudicate-work-review.md +97 -0
- package/skills/review-work/references/independent-work-reviewer-contract.md +40 -35
- package/skills/review-work/references/security-risk-lens.md +17 -15
- package/skills/setup-freeflow/SKILL.md +14 -17
- package/skills/setup-freeflow/references/activation-contract.md +24 -30
- package/skills/setup-freeflow/references/host-setup.md +6 -7
- package/skills/simplify-code/SKILL.md +97 -39
- package/skills/simplify-code/references/simplification-patterns.md +1 -1
- package/skills/tdd/SKILL.md +108 -41
- package/skills/tdd/references/test-design.md +1 -1
- package/skills/track-work/SKILL.md +133 -300
- package/skills/track-work/references/working-record-format.md +515 -0
- package/skills/track-work/scripts/lib/document.mjs +713 -0
- package/skills/track-work/scripts/lib/format.mjs +166 -0
- package/skills/track-work/scripts/lib/store.mjs +233 -0
- package/skills/track-work/scripts/lib/transitions.mjs +790 -0
- package/skills/track-work/scripts/lib/views.mjs +27 -430
- package/skills/track-work/scripts/working-record.mjs +292 -98
- package/skills/verify-work/SKILL.md +46 -20
- package/skills/verify-work/references/browser-runtime-evidence.md +1 -1
- package/skills/verify-work/references/integration-evidence.md +1 -7
- package/skills/verify-work/references/performance-evidence.md +1 -1
- package/skills/workflow/SKILL.md +110 -80
- package/skills/workflow/references/domain-skill-composition.md +24 -18
- package/skills/write-skill/SKILL.md +218 -62
- package/skills/write-skill/references/activation-boundaries.md +63 -21
- package/skills/write-skill/references/agent-first-instructions.md +61 -27
- package/skills/write-skill/references/progressive-disclosure.md +91 -41
- package/skills/write-skill/references/skill-dependency-graphs.md +190 -0
- package/runtime/prompts/skills.md +0 -65
- package/skills/mode-contract/SKILL.md +0 -104
- package/skills/track-work/scripts/lib/codec.mjs +0 -570
- package/skills/track-work/scripts/lib/commands.mjs +0 -568
- package/skills/track-work/scripts/lib/contract.mjs +0 -303
- package/skills/track-work/scripts/lib/model.mjs +0 -506
- package/skills/track-work/scripts/lib/operations.mjs +0 -965
- package/skills/track-work/scripts/lib/record-store.mjs +0 -264
- package/skills/track-work/scripts/lib/result.mjs +0 -37
- package/skills/track-work/scripts/lib/rewrites.mjs +0 -740
- package/skills/track-work/scripts/lib/schema.mjs +0 -277
- package/skills/track-work/scripts/lib/source-inventory.mjs +0 -162
- package/skills/track-work/scripts/lib/workspace.mjs +0 -134
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { readCognitiveRoutingHistory } from "./history.js";
|
|
3
|
+
import { PI_SESSION_MODEL_STATE_ENTRY, parsePiSessionModelStateCommit } from "./pi-session-control.js";
|
|
3
4
|
export const COGNITIVE_ROUTING_INTENT_ENTRY = "freeflow-cognitive-routing-intent";
|
|
4
5
|
export const COGNITIVE_ROUTING_CONTROL_ENTRY = "freeflow-cognitive-routing-control";
|
|
6
|
+
export const COGNITIVE_ROUTING_INACTIVE_ENTRY = "freeflow-cognitive-routing-inactive";
|
|
5
7
|
function isProfileName(value) {
|
|
6
8
|
return value === "standard" || value === "reasoning";
|
|
7
9
|
}
|
|
@@ -42,18 +44,44 @@ function asPair(value) {
|
|
|
42
44
|
thinkingLevel: candidate.thinkingLevel,
|
|
43
45
|
};
|
|
44
46
|
}
|
|
47
|
+
function hostPairFromEntry(entry) {
|
|
48
|
+
if (entry.type === "model_state_change") {
|
|
49
|
+
return asPair({
|
|
50
|
+
provider: entry.provider,
|
|
51
|
+
modelId: entry.modelId,
|
|
52
|
+
thinkingLevel: entry.thinkingLevel,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (entry.type !== "custom" || entry.customType !== PI_SESSION_MODEL_STATE_ENTRY) return undefined;
|
|
56
|
+
const commit = parsePiSessionModelStateCommit(entry.data);
|
|
57
|
+
if (!commit || commit.phase !== "committed" || commit.status !== "applied") return undefined;
|
|
58
|
+
return commit.target;
|
|
59
|
+
}
|
|
60
|
+
function hostCorrelationIdFromEntry(entry) {
|
|
61
|
+
if (entry.type === "model_state_change") {
|
|
62
|
+
return typeof entry.correlationId === "string" ? entry.correlationId : undefined;
|
|
63
|
+
}
|
|
64
|
+
if (entry.type !== "custom" || entry.customType !== PI_SESSION_MODEL_STATE_ENTRY) return undefined;
|
|
65
|
+
const commit = parsePiSessionModelStateCommit(entry.data);
|
|
66
|
+
return commit?.phase === "committed" && commit.status === "applied" ? commit.correlationId : undefined;
|
|
67
|
+
}
|
|
68
|
+
function latestPiSessionCommit(entries, correlationId) {
|
|
69
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
70
|
+
const entry = entries[index];
|
|
71
|
+
if (!entry || typeof entry !== "object") continue;
|
|
72
|
+
const candidate = entry;
|
|
73
|
+
if (candidate.type !== "custom" || candidate.customType !== PI_SESSION_MODEL_STATE_ENTRY) continue;
|
|
74
|
+
const commit = parsePiSessionModelStateCommit(candidate.data);
|
|
75
|
+
if (commit?.correlationId === correlationId) return commit;
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
45
79
|
function latestHostPair(context) {
|
|
46
80
|
const entries = branchEntriesFrom(context);
|
|
47
81
|
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
48
82
|
const entry = entries[index];
|
|
49
83
|
if (!entry || typeof entry !== "object") continue;
|
|
50
|
-
const
|
|
51
|
-
if (candidate.type !== "model_state_change") continue;
|
|
52
|
-
const pair = asPair({
|
|
53
|
-
provider: candidate.provider,
|
|
54
|
-
modelId: candidate.modelId,
|
|
55
|
-
thinkingLevel: candidate.thinkingLevel,
|
|
56
|
-
});
|
|
84
|
+
const pair = hostPairFromEntry(entry);
|
|
57
85
|
if (pair) return pair;
|
|
58
86
|
}
|
|
59
87
|
return undefined;
|
|
@@ -74,6 +102,17 @@ function profileNameForPair(capabilityState, pair) {
|
|
|
74
102
|
}
|
|
75
103
|
return undefined;
|
|
76
104
|
}
|
|
105
|
+
function pairMatchesProfile(capabilityState, pair, profileName) {
|
|
106
|
+
const profile = capabilityState.resolvedProfiles[profileName];
|
|
107
|
+
return Boolean(
|
|
108
|
+
profile &&
|
|
109
|
+
pairEquals(pair, {
|
|
110
|
+
provider: profile.provider,
|
|
111
|
+
modelId: profile.model,
|
|
112
|
+
thinkingLevel: profile.effectiveThinkingLevel,
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
77
116
|
function asIntent(value) {
|
|
78
117
|
if (!value || typeof value !== "object") return undefined;
|
|
79
118
|
const candidate = value;
|
|
@@ -102,6 +141,9 @@ function asIntent(value) {
|
|
|
102
141
|
: {}),
|
|
103
142
|
...(fromPair ? { fromPair } : {}),
|
|
104
143
|
...(isProfileName(candidate.fromProfile) ? { fromProfile: candidate.fromProfile } : {}),
|
|
144
|
+
...(candidate.fromControl === "automatic" || candidate.fromControl === "manual"
|
|
145
|
+
? { fromControl: candidate.fromControl }
|
|
146
|
+
: {}),
|
|
105
147
|
...(typeof candidate.reason === "string" ? { reason: candidate.reason } : {}),
|
|
106
148
|
...(typeof candidate.branchId === "string" || candidate.branchId === null ? { branchId: candidate.branchId } : {}),
|
|
107
149
|
epoch: candidate.epoch,
|
|
@@ -116,6 +158,38 @@ function asIntent(value) {
|
|
|
116
158
|
returnTarget,
|
|
117
159
|
};
|
|
118
160
|
}
|
|
161
|
+
function asInactiveEntry(value) {
|
|
162
|
+
if (!value || typeof value !== "object") return undefined;
|
|
163
|
+
const candidate = value;
|
|
164
|
+
const pair = asPair(candidate.pair);
|
|
165
|
+
if (
|
|
166
|
+
candidate.version !== 1 ||
|
|
167
|
+
candidate.source !== "user" ||
|
|
168
|
+
typeof candidate.reason !== "string" ||
|
|
169
|
+
candidate.reason.length === 0 ||
|
|
170
|
+
!pair
|
|
171
|
+
) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
version: 1,
|
|
176
|
+
source: "user",
|
|
177
|
+
reason: candidate.reason,
|
|
178
|
+
...(typeof candidate.branchId === "string" || candidate.branchId === null ? { branchId: candidate.branchId } : {}),
|
|
179
|
+
pair,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function latestInactiveEntry(entries) {
|
|
183
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
184
|
+
const candidate = entries[index];
|
|
185
|
+
if (!candidate || typeof candidate !== "object") continue;
|
|
186
|
+
const record = candidate;
|
|
187
|
+
if (record.type !== "custom" || record.customType !== COGNITIVE_ROUTING_INACTIVE_ENTRY) continue;
|
|
188
|
+
const entry = asInactiveEntry(record.data);
|
|
189
|
+
if (entry) return { entry, position: index };
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
119
193
|
function entriesFrom(context) {
|
|
120
194
|
try {
|
|
121
195
|
const entries = context.sessionManager?.getEntries?.();
|
|
@@ -177,21 +251,92 @@ function latestControlMode(entries, intent) {
|
|
|
177
251
|
}
|
|
178
252
|
if (candidate.customType !== COGNITIVE_ROUTING_INTENT_ENTRY) continue;
|
|
179
253
|
const candidateIntent = asIntent(candidate.data);
|
|
180
|
-
if (!candidateIntent || candidateIntent.
|
|
254
|
+
if (!candidateIntent || candidateIntent.epoch !== intent.epoch) continue;
|
|
255
|
+
if (candidateIntent.phase === "abandoned") {
|
|
256
|
+
if (candidateIntent.fromControl === "manual" && candidateIntent.fromProfile) {
|
|
257
|
+
return `manual-${candidateIntent.fromProfile}`;
|
|
258
|
+
}
|
|
259
|
+
if (candidateIntent.fromControl === "automatic") return "automatic";
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
181
262
|
if (candidateIntent.kind === "profile" || candidateIntent.kind === "activation") {
|
|
182
263
|
return controlModeForIntent(candidateIntent);
|
|
183
264
|
}
|
|
184
265
|
}
|
|
185
266
|
return "automatic";
|
|
186
267
|
}
|
|
268
|
+
function intentEntryIndex(entries, intent) {
|
|
269
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
270
|
+
const entry = entries[index];
|
|
271
|
+
if (!entry || typeof entry !== "object") continue;
|
|
272
|
+
const candidate = entry;
|
|
273
|
+
if (candidate.type !== "custom" || candidate.customType !== COGNITIVE_ROUTING_INTENT_ENTRY) continue;
|
|
274
|
+
const parsed = asIntent(candidate.data);
|
|
275
|
+
if (parsed?.correlationId === intent.correlationId && parsed.phase === "prepared") return index;
|
|
276
|
+
}
|
|
277
|
+
return entries.length;
|
|
278
|
+
}
|
|
279
|
+
function matchesBaseline(candidate, failed) {
|
|
280
|
+
if (failed.fromPair && !pairEquals(candidate.target, failed.fromPair)) return false;
|
|
281
|
+
if (failed.fromProfile && candidate.profile !== failed.fromProfile) return false;
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
export function resolveControlBeforeIntent(entries, intent) {
|
|
285
|
+
if (intent.fromControl === "automatic") return "automatic";
|
|
286
|
+
if (intent.fromControl === "manual") {
|
|
287
|
+
return intent.fromProfile ? `manual-${intent.fromProfile}` : undefined;
|
|
288
|
+
}
|
|
289
|
+
const priorEntries = entries.slice(0, intentEntryIndex(entries, intent));
|
|
290
|
+
const abandonedCorrelations = new Set();
|
|
291
|
+
for (const entry of priorEntries) {
|
|
292
|
+
if (!entry || typeof entry !== "object") continue;
|
|
293
|
+
const candidate = entry;
|
|
294
|
+
if (candidate.type !== "custom" || candidate.customType !== COGNITIVE_ROUTING_INTENT_ENTRY) continue;
|
|
295
|
+
const parsed = asIntent(candidate.data);
|
|
296
|
+
if (parsed?.phase === "abandoned") abandonedCorrelations.add(parsed.correlationId);
|
|
297
|
+
}
|
|
298
|
+
let resolved;
|
|
299
|
+
for (const entry of priorEntries) {
|
|
300
|
+
if (!entry || typeof entry !== "object") continue;
|
|
301
|
+
const candidate = entry;
|
|
302
|
+
if (candidate.type !== "custom") continue;
|
|
303
|
+
if (candidate.customType === COGNITIVE_ROUTING_CONTROL_ENTRY) {
|
|
304
|
+
const data = candidate.data;
|
|
305
|
+
if (
|
|
306
|
+
data &&
|
|
307
|
+
typeof data === "object" &&
|
|
308
|
+
(data.version === 1 || data.version === 2) &&
|
|
309
|
+
data.epoch === intent.epoch &&
|
|
310
|
+
data.control === "automatic"
|
|
311
|
+
) {
|
|
312
|
+
resolved = "automatic";
|
|
313
|
+
}
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (candidate.customType !== COGNITIVE_ROUTING_INTENT_ENTRY) continue;
|
|
317
|
+
const parsed = asIntent(candidate.data);
|
|
318
|
+
if (
|
|
319
|
+
!parsed ||
|
|
320
|
+
parsed.phase !== "prepared" ||
|
|
321
|
+
parsed.epoch !== intent.epoch ||
|
|
322
|
+
abandonedCorrelations.has(parsed.correlationId) ||
|
|
323
|
+
(parsed.kind !== "profile" && parsed.kind !== "activation") ||
|
|
324
|
+
!hasMatchingHostEntry(priorEntries, parsed) ||
|
|
325
|
+
!matchesBaseline(parsed, intent)
|
|
326
|
+
) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
resolved = controlModeForIntent(parsed);
|
|
330
|
+
}
|
|
331
|
+
return resolved;
|
|
332
|
+
}
|
|
187
333
|
function hasMatchingHostEntry(entries, intent) {
|
|
188
334
|
return entries.some((entry) => {
|
|
189
335
|
if (!entry || typeof entry !== "object") return false;
|
|
190
336
|
const candidate = entry;
|
|
191
337
|
return (
|
|
192
|
-
candidate
|
|
193
|
-
candidate
|
|
194
|
-
pairEquals(asPair(candidate), intent.target)
|
|
338
|
+
hostCorrelationIdFromEntry(candidate) === intent.correlationId &&
|
|
339
|
+
pairEquals(hostPairFromEntry(candidate), intent.target)
|
|
195
340
|
);
|
|
196
341
|
});
|
|
197
342
|
}
|
|
@@ -199,30 +344,49 @@ export class CognitiveRoutingController {
|
|
|
199
344
|
capabilityState;
|
|
200
345
|
pi;
|
|
201
346
|
ctx;
|
|
347
|
+
initialSessionStart;
|
|
202
348
|
label;
|
|
203
349
|
idFactory;
|
|
350
|
+
liveStateAuthoritative;
|
|
204
351
|
lease;
|
|
205
352
|
activeProfile;
|
|
206
353
|
epoch;
|
|
207
354
|
returnTarget;
|
|
208
355
|
controlMode = "automatic";
|
|
356
|
+
nativeInactiveReason;
|
|
357
|
+
nativeBlockedReason;
|
|
358
|
+
transitionInFlight = false;
|
|
209
359
|
branchPending = false;
|
|
210
360
|
operation = Promise.resolve();
|
|
211
361
|
constructor(options) {
|
|
212
362
|
this.capabilityState = options.capabilityState;
|
|
213
363
|
this.pi = options.pi;
|
|
214
364
|
this.ctx = options.ctx;
|
|
365
|
+
this.initialSessionStart = options.initialSessionStart;
|
|
215
366
|
this.label = options.label ?? "Cognitive Routing";
|
|
216
367
|
this.idFactory = options.idFactory ?? randomUUID;
|
|
368
|
+
this.liveStateAuthoritative = options.liveStateAuthoritative === true;
|
|
217
369
|
}
|
|
218
370
|
state() {
|
|
219
|
-
const hostPair = latestHostPair(this.ctx);
|
|
220
|
-
const observedPair = hostPair ?? this.currentPair();
|
|
371
|
+
const hostPair = this.liveStateAuthoritative ? undefined : latestHostPair(this.ctx);
|
|
372
|
+
const observedPair = this.liveStateAuthoritative ? this.currentPair() : (hostPair ?? this.currentPair());
|
|
221
373
|
const observedProfile = this.activeProfile ? profileNameForPair(this.capabilityState, observedPair) : undefined;
|
|
222
|
-
const activeProfile =
|
|
374
|
+
const activeProfile = this.liveStateAuthoritative
|
|
375
|
+
? observedProfile
|
|
376
|
+
: hostPair
|
|
377
|
+
? observedProfile
|
|
378
|
+
: (observedProfile ?? this.activeProfile);
|
|
379
|
+
const effective = this.lease !== undefined && activeProfile !== undefined && !this.branchPending;
|
|
223
380
|
return {
|
|
224
|
-
effective
|
|
381
|
+
effective,
|
|
225
382
|
controlMode: this.controlMode,
|
|
383
|
+
...(this.nativeBlockedReason
|
|
384
|
+
? { runtimeStatus: "blocked", runtimeReason: this.nativeBlockedReason }
|
|
385
|
+
: this.nativeInactiveReason
|
|
386
|
+
? { runtimeStatus: "inactive", runtimeReason: this.nativeInactiveReason }
|
|
387
|
+
: effective
|
|
388
|
+
? { runtimeStatus: "active" }
|
|
389
|
+
: {}),
|
|
226
390
|
...(activeProfile ? { activeProfile } : {}),
|
|
227
391
|
...(this.epoch ? { epoch: this.epoch } : {}),
|
|
228
392
|
...(this.returnTarget ? { returnTarget: { ...this.returnTarget } } : {}),
|
|
@@ -273,13 +437,69 @@ export class CognitiveRoutingController {
|
|
|
273
437
|
async reconcileBranch(mechanism = "session-tree") {
|
|
274
438
|
return this.enqueue(() => this._reconcileBranch(mechanism));
|
|
275
439
|
}
|
|
276
|
-
async
|
|
440
|
+
async observeNativeChange(reason, source = undefined) {
|
|
441
|
+
if (this.transitionInFlight || source === "restore") return { status: "ignored" };
|
|
442
|
+
return this.enqueue(() => this._observeNativeChange(reason, source));
|
|
443
|
+
}
|
|
444
|
+
async reconcileLiveState(reason = "Pi model state diverged") {
|
|
445
|
+
if (this.transitionInFlight) return { status: "ignored" };
|
|
446
|
+
return this.enqueue(() => this._observeNativeChange(reason));
|
|
447
|
+
}
|
|
448
|
+
recordNativeInactive(reason, pair) {
|
|
449
|
+
try {
|
|
450
|
+
this.pi.appendEntryDurable(COGNITIVE_ROUTING_INACTIVE_ENTRY, {
|
|
451
|
+
version: 1,
|
|
452
|
+
source: "user",
|
|
453
|
+
reason,
|
|
454
|
+
branchId: this.currentBranchId(),
|
|
455
|
+
pair,
|
|
456
|
+
});
|
|
457
|
+
} catch {
|
|
458
|
+
this.nativeInactiveReason = undefined;
|
|
459
|
+
this.nativeBlockedReason = "inactive_state_persistence_failed";
|
|
460
|
+
return { status: "blocked", reason: this.nativeBlockedReason };
|
|
461
|
+
}
|
|
462
|
+
this.nativeInactiveReason = reason;
|
|
463
|
+
this.nativeBlockedReason = undefined;
|
|
464
|
+
return { status: "inactive", reason };
|
|
465
|
+
}
|
|
466
|
+
async _observeNativeChange(reason, source = undefined) {
|
|
467
|
+
if (!this.lease || !this.activeProfile) return { status: "ignored" };
|
|
468
|
+
const profile = this.capabilityState.resolvedProfiles[this.activeProfile];
|
|
469
|
+
const current = this.currentPair();
|
|
470
|
+
const expected = profile
|
|
471
|
+
? {
|
|
472
|
+
provider: profile.provider,
|
|
473
|
+
modelId: profile.model,
|
|
474
|
+
thinkingLevel: profile.effectiveThinkingLevel,
|
|
475
|
+
}
|
|
476
|
+
: undefined;
|
|
477
|
+
if (current && expected && pairEquals(current, expected) && source !== "set") return { status: "ignored" };
|
|
478
|
+
const lease = this.lease;
|
|
479
|
+
if (!current) {
|
|
480
|
+
await this.releaseLease(lease);
|
|
481
|
+
this.clearActiveState();
|
|
482
|
+
this.nativeBlockedReason = "native_state_unavailable";
|
|
483
|
+
return { status: "blocked", reason: this.nativeBlockedReason };
|
|
484
|
+
}
|
|
485
|
+
const result = this.recordNativeInactive(reason, current);
|
|
486
|
+
await this.releaseLease(lease);
|
|
487
|
+
this.clearActiveState();
|
|
488
|
+
return result;
|
|
489
|
+
}
|
|
490
|
+
async _activate(allowInactive = false, requestedProfile, requestedControlMode) {
|
|
277
491
|
if (this.state().effective && this.activeProfile) {
|
|
278
492
|
return { status: "active", profile: this.activeProfile };
|
|
279
493
|
}
|
|
280
494
|
if (!this.capabilityState.effective) {
|
|
281
495
|
return { status: "inactive", reason: "not_effective" };
|
|
282
496
|
}
|
|
497
|
+
if ((this.nativeInactiveReason || this.nativeBlockedReason) && !allowInactive) {
|
|
498
|
+
return {
|
|
499
|
+
status: "inactive",
|
|
500
|
+
reason: this.nativeBlockedReason ? "runtime_blocked" : "native_override",
|
|
501
|
+
};
|
|
502
|
+
}
|
|
283
503
|
const sessionEntries = entriesFrom(this.ctx);
|
|
284
504
|
const lifecycleIntent = latestIntent(
|
|
285
505
|
sessionEntries,
|
|
@@ -288,7 +508,10 @@ export class CognitiveRoutingController {
|
|
|
288
508
|
if (lifecycleIntent && !hasMatchingHostEntry(sessionEntries, lifecycleIntent)) {
|
|
289
509
|
return { status: "inactive", reason: "pending_intent" };
|
|
290
510
|
}
|
|
291
|
-
if (
|
|
511
|
+
if (
|
|
512
|
+
lifecycleIntent?.kind === "activation" &&
|
|
513
|
+
!(allowInactive && (this.nativeInactiveReason || this.nativeBlockedReason))
|
|
514
|
+
) {
|
|
292
515
|
return { status: "inactive", reason: "recover_required" };
|
|
293
516
|
}
|
|
294
517
|
const preservedReload = lifecycleIntent?.kind === "closing" && lifecycleIntent.resumeProfile !== undefined;
|
|
@@ -301,17 +524,29 @@ export class CognitiveRoutingController {
|
|
|
301
524
|
}
|
|
302
525
|
const epoch = this.epoch ?? this.idFactory();
|
|
303
526
|
const branchId = this.currentBranchId();
|
|
304
|
-
const sessionStart = this.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
527
|
+
const sessionStart = this.initialSessionStart ??
|
|
528
|
+
this.capabilityState.sessionStart ?? {
|
|
529
|
+
control: "automatic",
|
|
530
|
+
profile: "reasoning",
|
|
531
|
+
};
|
|
532
|
+
let profileName;
|
|
533
|
+
if (requestedProfile) {
|
|
534
|
+
profileName = requestedProfile;
|
|
535
|
+
} else if (preservedReload) {
|
|
536
|
+
profileName = lifecycleIntent.resumeProfile;
|
|
537
|
+
} else if (sessionStart.control === "automatic") {
|
|
538
|
+
profileName = "reasoning";
|
|
539
|
+
} else {
|
|
540
|
+
profileName = sessionStart.profile;
|
|
541
|
+
}
|
|
309
542
|
const profile = this.capabilityState.resolvedProfiles[profileName];
|
|
310
543
|
if (!profile) {
|
|
311
544
|
return { status: "inactive", reason: preservedReload ? "resume_profile_unavailable" : "profile_unavailable" };
|
|
312
545
|
}
|
|
313
546
|
let controlMode;
|
|
314
|
-
if (
|
|
547
|
+
if (requestedControlMode) {
|
|
548
|
+
controlMode = requestedControlMode;
|
|
549
|
+
} else if (preservedReload) {
|
|
315
550
|
controlMode = lifecycleIntent.resumeControl === "manual" ? `manual-${profileName}` : "automatic";
|
|
316
551
|
} else {
|
|
317
552
|
controlMode = sessionStart.control === "manual" ? `manual-${profileName}` : "automatic";
|
|
@@ -365,7 +600,7 @@ export class CognitiveRoutingController {
|
|
|
365
600
|
(candidate) => candidate.kind === "profile" && candidate.epoch === this.epoch,
|
|
366
601
|
)
|
|
367
602
|
: undefined;
|
|
368
|
-
const fromPair =
|
|
603
|
+
const fromPair = this.observedHostPair();
|
|
369
604
|
const fromProfile = this.activeProfile ?? profileNameForPair(this.capabilityState, fromPair);
|
|
370
605
|
const intent = this.prepareIntent({
|
|
371
606
|
kind: "closing",
|
|
@@ -392,7 +627,7 @@ export class CognitiveRoutingController {
|
|
|
392
627
|
}
|
|
393
628
|
this.clearActiveState(false);
|
|
394
629
|
try {
|
|
395
|
-
const result = await
|
|
630
|
+
const result = await this.setState(lease, { ...returnTarget, correlationId: intent.correlationId });
|
|
396
631
|
if (result.status !== "applied" && result.status !== "unchanged") {
|
|
397
632
|
return { status: "inactive", reason: "restore_rejected" };
|
|
398
633
|
}
|
|
@@ -403,9 +638,122 @@ export class CognitiveRoutingController {
|
|
|
403
638
|
await this.releaseLease(lease);
|
|
404
639
|
}
|
|
405
640
|
}
|
|
641
|
+
async recoverTerminalIntent(intent) {
|
|
642
|
+
const commit = latestPiSessionCommit(branchEntriesFrom(this.ctx), intent.correlationId);
|
|
643
|
+
if (!commit) return undefined;
|
|
644
|
+
if (commit.phase === "prepared") {
|
|
645
|
+
if (!this.pi.recoverPreparedModelState) {
|
|
646
|
+
return { status: "pending", reason: "prepared_recovery_unsupported" };
|
|
647
|
+
}
|
|
648
|
+
let recovery;
|
|
649
|
+
try {
|
|
650
|
+
recovery = await this.pi.recoverPreparedModelState({
|
|
651
|
+
fromPair: commit.fromPair,
|
|
652
|
+
target: commit.target,
|
|
653
|
+
correlationId: commit.correlationId,
|
|
654
|
+
});
|
|
655
|
+
} catch {
|
|
656
|
+
return { status: "pending", reason: "prepared_recovery_failed" };
|
|
657
|
+
}
|
|
658
|
+
if (recovery.status === "rollback-failed") {
|
|
659
|
+
return { status: "pending", reason: "rollback_failed" };
|
|
660
|
+
}
|
|
661
|
+
if (recovery.status !== "restored") {
|
|
662
|
+
return { status: "pending", reason: "prepared_recovery_failed" };
|
|
663
|
+
}
|
|
664
|
+
try {
|
|
665
|
+
this.abandonPreparedIntent(intent, "Pi session transition interrupted.");
|
|
666
|
+
} catch {
|
|
667
|
+
return { status: "pending", reason: "intent_abandon_failed" };
|
|
668
|
+
}
|
|
669
|
+
const recovered = await this._recover();
|
|
670
|
+
return recovered.status === "inactive" && recovered.reason === "no_pending_intent"
|
|
671
|
+
? { status: "inactive", reason: "intent_abandoned" }
|
|
672
|
+
: recovered;
|
|
673
|
+
}
|
|
674
|
+
if (commit.phase !== "aborted") return undefined;
|
|
675
|
+
if (commit.status === "rollback-failed") {
|
|
676
|
+
return { status: "pending", reason: "rollback_failed" };
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
this.abandonPreparedIntent(intent, "Pi session transition rolled back.");
|
|
680
|
+
} catch {
|
|
681
|
+
return { status: "pending", reason: "intent_abandon_failed" };
|
|
682
|
+
}
|
|
683
|
+
return this._recover();
|
|
684
|
+
}
|
|
685
|
+
async recoverTerminalProfileIntent(intent) {
|
|
686
|
+
const commit = latestPiSessionCommit(branchEntriesFrom(this.ctx), intent.correlationId);
|
|
687
|
+
if (!commit || commit.phase === "committed") return undefined;
|
|
688
|
+
if (commit.phase === "aborted" && commit.status === "rollback-failed") {
|
|
689
|
+
return { status: "pending", reason: "rollback_failed" };
|
|
690
|
+
}
|
|
691
|
+
const controlMode = resolveControlBeforeIntent(branchEntriesFrom(this.ctx), intent);
|
|
692
|
+
if (
|
|
693
|
+
!controlMode ||
|
|
694
|
+
!intent.fromPair ||
|
|
695
|
+
!intent.fromProfile ||
|
|
696
|
+
!pairMatchesProfile(this.capabilityState, intent.fromPair, intent.fromProfile)
|
|
697
|
+
) {
|
|
698
|
+
return { status: "pending", reason: "baseline_control_unknown" };
|
|
699
|
+
}
|
|
700
|
+
if (commit.phase === "prepared") {
|
|
701
|
+
if (!this.pi.recoverPreparedModelState) {
|
|
702
|
+
return { status: "pending", reason: "prepared_recovery_unsupported" };
|
|
703
|
+
}
|
|
704
|
+
let recovery;
|
|
705
|
+
try {
|
|
706
|
+
recovery = await this.pi.recoverPreparedModelState({
|
|
707
|
+
fromPair: commit.fromPair,
|
|
708
|
+
target: commit.target,
|
|
709
|
+
correlationId: commit.correlationId,
|
|
710
|
+
});
|
|
711
|
+
} catch {
|
|
712
|
+
return { status: "pending", reason: "prepared_recovery_failed" };
|
|
713
|
+
}
|
|
714
|
+
if (recovery.status === "rollback-failed") {
|
|
715
|
+
return { status: "pending", reason: "rollback_failed" };
|
|
716
|
+
}
|
|
717
|
+
if (recovery.status !== "restored") {
|
|
718
|
+
return { status: "pending", reason: "prepared_recovery_failed" };
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
try {
|
|
722
|
+
this.abandonPreparedIntent(intent, "Pi session transition interrupted or rolled back.");
|
|
723
|
+
} catch {
|
|
724
|
+
return { status: "pending", reason: "intent_abandon_failed" };
|
|
725
|
+
}
|
|
726
|
+
const recovered = await this.activatePrepared({
|
|
727
|
+
kind: "profile",
|
|
728
|
+
controlMode,
|
|
729
|
+
source: "system",
|
|
730
|
+
mechanism: "recovery",
|
|
731
|
+
...(decisionCorrelationForIntent(intent) ? { decisionCorrelationId: decisionCorrelationForIntent(intent) } : {}),
|
|
732
|
+
branchId: this.currentBranchId(),
|
|
733
|
+
epoch: intent.epoch,
|
|
734
|
+
returnTarget: intent.returnTarget,
|
|
735
|
+
target: intent.fromPair,
|
|
736
|
+
profile: intent.fromProfile,
|
|
737
|
+
});
|
|
738
|
+
return recovered.status === "active" ? recovered : { status: "pending", reason: "baseline_recovery_failed" };
|
|
739
|
+
}
|
|
406
740
|
async _recover() {
|
|
407
741
|
const sessionEntries = entriesFrom(this.ctx);
|
|
408
742
|
const branchEntries = branchEntriesFrom(this.ctx);
|
|
743
|
+
const inactive = latestInactiveEntry(branchEntries);
|
|
744
|
+
const branchLifecycleIntent = latestIntent(
|
|
745
|
+
branchEntries,
|
|
746
|
+
(intent) => intent.kind === "activation" || intent.kind === "closing",
|
|
747
|
+
);
|
|
748
|
+
if (
|
|
749
|
+
inactive &&
|
|
750
|
+
(!branchLifecycleIntent || intentEntryIndex(branchEntries, branchLifecycleIntent) <= inactive.position)
|
|
751
|
+
) {
|
|
752
|
+
this.nativeInactiveReason = inactive.entry.reason;
|
|
753
|
+
this.nativeBlockedReason = undefined;
|
|
754
|
+
this.clearActiveState();
|
|
755
|
+
return { status: "inactive", reason: "native_override" };
|
|
756
|
+
}
|
|
409
757
|
const lifecycleIntent = latestIntent(
|
|
410
758
|
sessionEntries,
|
|
411
759
|
(intent) => intent.kind === "activation" || intent.kind === "closing",
|
|
@@ -413,6 +761,10 @@ export class CognitiveRoutingController {
|
|
|
413
761
|
if (!lifecycleIntent) {
|
|
414
762
|
return { status: "inactive", reason: "no_pending_intent" };
|
|
415
763
|
}
|
|
764
|
+
if (lifecycleIntent.kind === "activation") {
|
|
765
|
+
const lifecycleTerminal = await this.recoverTerminalIntent(lifecycleIntent);
|
|
766
|
+
if (lifecycleTerminal) return lifecycleTerminal;
|
|
767
|
+
}
|
|
416
768
|
if (lifecycleIntent.kind === "closing") {
|
|
417
769
|
return hasMatchingHostEntry(sessionEntries, lifecycleIntent)
|
|
418
770
|
? { status: "inactive", reason: "closed" }
|
|
@@ -433,6 +785,19 @@ export class CognitiveRoutingController {
|
|
|
433
785
|
thinkingLevel: currentActivationProfile.effectiveThinkingLevel,
|
|
434
786
|
};
|
|
435
787
|
const activationMatched = hasMatchingHostEntry(sessionEntries, lifecycleIntent);
|
|
788
|
+
if (this.liveStateAuthoritative && activationMatched) {
|
|
789
|
+
const livePair = this.currentPair();
|
|
790
|
+
if (!livePair) {
|
|
791
|
+
this.clearActiveState();
|
|
792
|
+
this.nativeBlockedReason = "native_state_unavailable";
|
|
793
|
+
return { status: "inactive", reason: "native_override" };
|
|
794
|
+
}
|
|
795
|
+
if (!pairEquals(livePair, activationTarget)) {
|
|
796
|
+
this.recordNativeInactive("Pi model state diverged during recovery", livePair);
|
|
797
|
+
this.clearActiveState();
|
|
798
|
+
return { status: "inactive", reason: "native_override" };
|
|
799
|
+
}
|
|
800
|
+
}
|
|
436
801
|
if (activationMatched && !pairEquals(activationTarget, lifecycleIntent.target)) {
|
|
437
802
|
return { status: "pending", reason: "applied_target_changed" };
|
|
438
803
|
}
|
|
@@ -442,7 +807,7 @@ export class CognitiveRoutingController {
|
|
|
442
807
|
if (!activationMatched) {
|
|
443
808
|
const retried = await this.activatePrepared({
|
|
444
809
|
kind: "activation",
|
|
445
|
-
controlMode:
|
|
810
|
+
controlMode: latestControlMode(branchEntries, lifecycleIntent),
|
|
446
811
|
source: "system",
|
|
447
812
|
mechanism: "recovery",
|
|
448
813
|
branchId: this.currentBranchId(),
|
|
@@ -458,6 +823,8 @@ export class CognitiveRoutingController {
|
|
|
458
823
|
(intent) => intent.kind === "profile" && intent.epoch === lifecycleIntent.epoch,
|
|
459
824
|
);
|
|
460
825
|
if (profileIntent) {
|
|
826
|
+
const terminal = await this.recoverTerminalProfileIntent(profileIntent);
|
|
827
|
+
if (terminal) return terminal;
|
|
461
828
|
const currentProfile = this.capabilityState.resolvedProfiles[profileIntent.profile ?? "standard"];
|
|
462
829
|
if (!currentProfile) {
|
|
463
830
|
return this.abandonIntent(profileIntent, "Prepared profile is unavailable.");
|
|
@@ -491,7 +858,7 @@ export class CognitiveRoutingController {
|
|
|
491
858
|
}
|
|
492
859
|
return this.activatePrepared({
|
|
493
860
|
kind: "activation",
|
|
494
|
-
controlMode:
|
|
861
|
+
controlMode: latestControlMode(branchEntries, lifecycleIntent),
|
|
495
862
|
source: "system",
|
|
496
863
|
mechanism: "recovery",
|
|
497
864
|
branchId: this.currentBranchId(),
|
|
@@ -502,7 +869,7 @@ export class CognitiveRoutingController {
|
|
|
502
869
|
});
|
|
503
870
|
}
|
|
504
871
|
async recoverClosingIntent(intent) {
|
|
505
|
-
const fromPair =
|
|
872
|
+
const fromPair = this.observedHostPair();
|
|
506
873
|
const fromProfile = intent.fromProfile ?? profileNameForPair(this.capabilityState, fromPair);
|
|
507
874
|
const retryIntent = this.prepareIntent({
|
|
508
875
|
kind: "closing",
|
|
@@ -536,7 +903,10 @@ export class CognitiveRoutingController {
|
|
|
536
903
|
return { status: "pending", reason: `closing_recovery_acquire_${acquisition.status}` };
|
|
537
904
|
}
|
|
538
905
|
try {
|
|
539
|
-
const result = await
|
|
906
|
+
const result = await this.setState(acquisition.lease, {
|
|
907
|
+
...intent.target,
|
|
908
|
+
correlationId: retryIntent.correlationId,
|
|
909
|
+
});
|
|
540
910
|
return result.status === "applied" || result.status === "unchanged"
|
|
541
911
|
? { status: "inactive", reason: "restored" }
|
|
542
912
|
: { status: "pending", reason: "closing_recovery_rejected" };
|
|
@@ -546,20 +916,23 @@ export class CognitiveRoutingController {
|
|
|
546
916
|
await this.releaseLease(acquisition.lease);
|
|
547
917
|
}
|
|
548
918
|
}
|
|
919
|
+
abandonPreparedIntent(intent, reason) {
|
|
920
|
+
const fromPair = intent.fromPair ?? this.observedHostPair();
|
|
921
|
+
const fromProfile = intent.fromProfile ?? profileNameForPair(this.capabilityState, fromPair);
|
|
922
|
+
this.pi.appendEntryDurable(COGNITIVE_ROUTING_INTENT_ENTRY, {
|
|
923
|
+
...intent,
|
|
924
|
+
version: 2,
|
|
925
|
+
phase: "abandoned",
|
|
926
|
+
source: "system",
|
|
927
|
+
mechanism: intent.mechanism ?? "recovery",
|
|
928
|
+
...(fromPair ? { fromPair } : {}),
|
|
929
|
+
...(fromProfile ? { fromProfile } : {}),
|
|
930
|
+
reason,
|
|
931
|
+
});
|
|
932
|
+
}
|
|
549
933
|
async abandonIntent(intent, reason) {
|
|
550
934
|
try {
|
|
551
|
-
|
|
552
|
-
const fromProfile = intent.fromProfile ?? profileNameForPair(this.capabilityState, fromPair);
|
|
553
|
-
this.pi.appendEntryDurable(COGNITIVE_ROUTING_INTENT_ENTRY, {
|
|
554
|
-
...intent,
|
|
555
|
-
version: 2,
|
|
556
|
-
phase: "abandoned",
|
|
557
|
-
source: "system",
|
|
558
|
-
mechanism: intent.mechanism ?? "recovery",
|
|
559
|
-
...(fromPair ? { fromPair } : {}),
|
|
560
|
-
...(fromProfile ? { fromProfile } : {}),
|
|
561
|
-
reason,
|
|
562
|
-
});
|
|
935
|
+
this.abandonPreparedIntent(intent, reason);
|
|
563
936
|
const recovered = await this._recover();
|
|
564
937
|
return recovered.status === "inactive" && recovered.reason === "no_pending_intent"
|
|
565
938
|
? { status: "inactive", reason: "intent_abandoned" }
|
|
@@ -639,8 +1012,9 @@ export class CognitiveRoutingController {
|
|
|
639
1012
|
if (!this.capabilityState.effective) {
|
|
640
1013
|
return { status: "inactive", reason: "not_effective" };
|
|
641
1014
|
}
|
|
642
|
-
|
|
643
|
-
|
|
1015
|
+
if (!this.state().effective) {
|
|
1016
|
+
return this._activate(true, profile, `manual-${profile}`);
|
|
1017
|
+
}
|
|
644
1018
|
return this._setProfile(profile, {
|
|
645
1019
|
controlMode: `manual-${profile}`,
|
|
646
1020
|
source: "user",
|
|
@@ -671,7 +1045,7 @@ export class CognitiveRoutingController {
|
|
|
671
1045
|
modelId: resolvedProfile.model,
|
|
672
1046
|
thinkingLevel: resolvedProfile.effectiveThinkingLevel,
|
|
673
1047
|
};
|
|
674
|
-
const fromPair =
|
|
1048
|
+
const fromPair = this.observedHostPair();
|
|
675
1049
|
if (!fromPair) return { status: "inactive", reason: "current_state_unavailable" };
|
|
676
1050
|
const fromProfile = this.activeProfile ?? profileNameForPair(this.capabilityState, fromPair);
|
|
677
1051
|
const correlationId = this.idFactory();
|
|
@@ -686,6 +1060,7 @@ export class CognitiveRoutingController {
|
|
|
686
1060
|
...(decisionCorrelationId ? { decisionCorrelationId } : {}),
|
|
687
1061
|
...(fromPair ? { fromPair } : {}),
|
|
688
1062
|
...(fromProfile ? { fromProfile } : {}),
|
|
1063
|
+
fromControl: options.controlMode === "automatic" ? "automatic" : "manual",
|
|
689
1064
|
reason: options.reason,
|
|
690
1065
|
branchId: this.currentBranchId(),
|
|
691
1066
|
epoch: this.epoch,
|
|
@@ -714,8 +1089,27 @@ export class CognitiveRoutingController {
|
|
|
714
1089
|
return { status: "inactive", reason: "prepare_failed" };
|
|
715
1090
|
}
|
|
716
1091
|
try {
|
|
717
|
-
const result = await
|
|
1092
|
+
const result = await this.setState(lease, { ...target, correlationId: intent.correlationId });
|
|
718
1093
|
if (result.status !== "applied" && result.status !== "unchanged") {
|
|
1094
|
+
const commit = latestPiSessionCommit(branchEntriesFrom(this.ctx), intent.correlationId);
|
|
1095
|
+
if (result.status === "rejected" && commit?.phase === "aborted" && commit.status === "rollback-failed") {
|
|
1096
|
+
try {
|
|
1097
|
+
this.abandonPreparedIntent(intent, "Pi session rollback failed.");
|
|
1098
|
+
} catch {
|
|
1099
|
+
// Fail closed even when the terminal abandonment cannot be persisted.
|
|
1100
|
+
}
|
|
1101
|
+
await this.releaseLease(lease);
|
|
1102
|
+
this.clearActiveState();
|
|
1103
|
+
return { status: "inactive", reason: "rollback_failed" };
|
|
1104
|
+
}
|
|
1105
|
+
if (result.status === "rejected" && commit?.phase === "aborted" && commit.status === "rolled-back") {
|
|
1106
|
+
try {
|
|
1107
|
+
this.abandonPreparedIntent(intent, "Pi session transition rolled back.");
|
|
1108
|
+
this.branchPending = false;
|
|
1109
|
+
} catch {
|
|
1110
|
+
// Preserve the pending boundary when the terminal abandonment cannot be persisted.
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
719
1113
|
return { status: "inactive", reason: "transition_rejected" };
|
|
720
1114
|
}
|
|
721
1115
|
this.activeProfile = profile;
|
|
@@ -728,8 +1122,27 @@ export class CognitiveRoutingController {
|
|
|
728
1122
|
}
|
|
729
1123
|
async _setAutomaticControl(mechanism) {
|
|
730
1124
|
if (!this.lease || !this.epoch) {
|
|
731
|
-
return { status: "rejected", reason: "not_active" };
|
|
1125
|
+
if (!this.nativeInactiveReason && !this.nativeBlockedReason) return { status: "rejected", reason: "not_active" };
|
|
1126
|
+
const activation = await this._activate(true, this.profileForCurrentPair(), "automatic");
|
|
1127
|
+
return activation.status === "active"
|
|
1128
|
+
? { status: "automatic" }
|
|
1129
|
+
: { status: "rejected", reason: activation.reason };
|
|
1130
|
+
}
|
|
1131
|
+
const current = this.state();
|
|
1132
|
+
if (!current.activeProfile) {
|
|
1133
|
+
return { status: "rejected", reason: "profile_unknown" };
|
|
732
1134
|
}
|
|
1135
|
+
if (current.activeProfile !== "reasoning") {
|
|
1136
|
+
const transition = await this._setProfile("reasoning", {
|
|
1137
|
+
controlMode: "automatic",
|
|
1138
|
+
source: "user",
|
|
1139
|
+
mechanism,
|
|
1140
|
+
reason: "automatic control selects the Reasoning profile",
|
|
1141
|
+
});
|
|
1142
|
+
if (transition.status !== "active") return { status: "rejected", reason: transition.reason };
|
|
1143
|
+
return { status: "automatic" };
|
|
1144
|
+
}
|
|
1145
|
+
if (this.controlMode === "automatic") return { status: "automatic" };
|
|
733
1146
|
try {
|
|
734
1147
|
this.pi.appendEntryDurable(COGNITIVE_ROUTING_CONTROL_ENTRY, {
|
|
735
1148
|
version: 2,
|
|
@@ -757,6 +1170,13 @@ export class CognitiveRoutingController {
|
|
|
757
1170
|
return undefined;
|
|
758
1171
|
}
|
|
759
1172
|
}
|
|
1173
|
+
observedHostPair() {
|
|
1174
|
+
const livePair = this.currentPair();
|
|
1175
|
+
return this.liveStateAuthoritative ? livePair : (latestHostPair(this.ctx) ?? livePair);
|
|
1176
|
+
}
|
|
1177
|
+
profileForCurrentPair() {
|
|
1178
|
+
return profileNameForPair(this.capabilityState, this.currentPair());
|
|
1179
|
+
}
|
|
760
1180
|
currentPair() {
|
|
761
1181
|
const model = this.ctx.model;
|
|
762
1182
|
if (
|
|
@@ -777,7 +1197,7 @@ export class CognitiveRoutingController {
|
|
|
777
1197
|
return { version: 2, phase: "prepared", ...input };
|
|
778
1198
|
}
|
|
779
1199
|
async activatePrepared(options) {
|
|
780
|
-
const fromPair =
|
|
1200
|
+
const fromPair = this.observedHostPair();
|
|
781
1201
|
if (!fromPair) return { status: "inactive", reason: "current_state_unavailable" };
|
|
782
1202
|
const fromProfile = profileNameForPair(this.capabilityState, fromPair);
|
|
783
1203
|
const correlationId = this.idFactory();
|
|
@@ -816,7 +1236,7 @@ export class CognitiveRoutingController {
|
|
|
816
1236
|
}
|
|
817
1237
|
this.lease = acquisition.lease;
|
|
818
1238
|
try {
|
|
819
|
-
const result = await acquisition.lease
|
|
1239
|
+
const result = await this.setState(acquisition.lease, { ...options.target, correlationId: intent.correlationId });
|
|
820
1240
|
if (result.status !== "applied" && result.status !== "unchanged") {
|
|
821
1241
|
await this.releaseLease(acquisition.lease);
|
|
822
1242
|
this.clearActiveState();
|
|
@@ -826,6 +1246,8 @@ export class CognitiveRoutingController {
|
|
|
826
1246
|
this.returnTarget = options.returnTarget;
|
|
827
1247
|
this.activeProfile = options.profile;
|
|
828
1248
|
this.controlMode = options.controlMode;
|
|
1249
|
+
this.nativeInactiveReason = undefined;
|
|
1250
|
+
this.nativeBlockedReason = undefined;
|
|
829
1251
|
return { status: "active", profile: options.profile };
|
|
830
1252
|
} catch {
|
|
831
1253
|
await this.releaseLease(acquisition.lease);
|
|
@@ -841,6 +1263,14 @@ export class CognitiveRoutingController {
|
|
|
841
1263
|
);
|
|
842
1264
|
return next;
|
|
843
1265
|
}
|
|
1266
|
+
async setState(lease, request) {
|
|
1267
|
+
this.transitionInFlight = true;
|
|
1268
|
+
try {
|
|
1269
|
+
return await lease.setState(request);
|
|
1270
|
+
} finally {
|
|
1271
|
+
this.transitionInFlight = false;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
844
1274
|
async releaseLease(lease) {
|
|
845
1275
|
try {
|
|
846
1276
|
await lease.release();
|