@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
|
@@ -5,14 +5,17 @@ import {
|
|
|
5
5
|
type CognitiveRoutingHistoryOptions,
|
|
6
6
|
type CognitiveRoutingHistoryResult,
|
|
7
7
|
} from "./history.js";
|
|
8
|
+
import { PI_SESSION_MODEL_STATE_ENTRY, parsePiSessionModelStateCommit } from "./pi-session-control.js";
|
|
8
9
|
import type {
|
|
9
10
|
CognitiveRoutingCapabilityState,
|
|
10
11
|
CognitiveRoutingProfileName,
|
|
12
|
+
CognitiveRoutingSessionStart,
|
|
11
13
|
CognitiveRoutingThinkingLevel,
|
|
12
14
|
} from "./types.js";
|
|
13
15
|
|
|
14
16
|
export const COGNITIVE_ROUTING_INTENT_ENTRY = "freeflow-cognitive-routing-intent";
|
|
15
17
|
export const COGNITIVE_ROUTING_CONTROL_ENTRY = "freeflow-cognitive-routing-control";
|
|
18
|
+
export const COGNITIVE_ROUTING_INACTIVE_ENTRY = "freeflow-cognitive-routing-inactive";
|
|
16
19
|
|
|
17
20
|
export type CognitiveRoutingMechanism =
|
|
18
21
|
| "agent-tool"
|
|
@@ -46,6 +49,7 @@ type CognitiveRoutingIntent = {
|
|
|
46
49
|
decisionCorrelationId?: string;
|
|
47
50
|
fromPair?: CognitiveRoutingPair;
|
|
48
51
|
fromProfile?: CognitiveRoutingProfileName;
|
|
52
|
+
fromControl?: "automatic" | "manual";
|
|
49
53
|
profile?: CognitiveRoutingProfileName;
|
|
50
54
|
resumeProfile?: CognitiveRoutingProfileName;
|
|
51
55
|
resumeControl?: "automatic" | "manual";
|
|
@@ -64,7 +68,16 @@ type CognitiveRoutingControlEntry = {
|
|
|
64
68
|
epoch: string;
|
|
65
69
|
};
|
|
66
70
|
|
|
67
|
-
type
|
|
71
|
+
type CognitiveRoutingInactiveEntry = {
|
|
72
|
+
version: 1;
|
|
73
|
+
source: "user";
|
|
74
|
+
reason: string;
|
|
75
|
+
branchId?: string | null;
|
|
76
|
+
pair: CognitiveRoutingPair;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
type CognitiveRoutingPersistedData =
|
|
80
|
+
CognitiveRoutingIntent | CognitiveRoutingControlEntry | CognitiveRoutingInactiveEntry;
|
|
68
81
|
|
|
69
82
|
type ModelStateLease = {
|
|
70
83
|
setState(request: CognitiveRoutingPair & { correlationId: string }): Promise<{ status: string }>;
|
|
@@ -77,6 +90,11 @@ type ModelStateAcquisition =
|
|
|
77
90
|
type ControllerPi = {
|
|
78
91
|
appendEntryDurable(customType: string, data: CognitiveRoutingPersistedData): void;
|
|
79
92
|
acquireModelStateControl(options: { label: string }): Promise<ModelStateAcquisition>;
|
|
93
|
+
recoverPreparedModelState?: (request: {
|
|
94
|
+
fromPair: CognitiveRoutingPair;
|
|
95
|
+
target: CognitiveRoutingPair;
|
|
96
|
+
correlationId: string;
|
|
97
|
+
}) => Promise<{ status: "restored" | "rollback-failed" | "rejected" }>;
|
|
80
98
|
};
|
|
81
99
|
|
|
82
100
|
type ControllerContext = {
|
|
@@ -93,15 +111,24 @@ type ControllerOptions = {
|
|
|
93
111
|
capabilityState: CognitiveRoutingCapabilityState;
|
|
94
112
|
pi: ControllerPi;
|
|
95
113
|
ctx: ControllerContext;
|
|
114
|
+
initialSessionStart?: CognitiveRoutingSessionStart;
|
|
96
115
|
label?: string;
|
|
97
116
|
idFactory?: () => string;
|
|
117
|
+
liveStateAuthoritative?: boolean;
|
|
98
118
|
};
|
|
99
119
|
|
|
100
120
|
export type CognitiveRoutingControlMode = "automatic" | "manual-standard" | "manual-reasoning";
|
|
101
121
|
|
|
122
|
+
export type CognitiveRoutingRuntimeStatus = "active" | "inactive" | "blocked";
|
|
123
|
+
|
|
124
|
+
export type CognitiveRoutingNativeChangeResult =
|
|
125
|
+
{ status: "ignored" } | { status: "inactive"; reason: string } | { status: "blocked"; reason: string };
|
|
126
|
+
|
|
102
127
|
export type CognitiveRoutingControllerState = {
|
|
103
128
|
effective: boolean;
|
|
104
129
|
controlMode: CognitiveRoutingControlMode;
|
|
130
|
+
runtimeStatus?: CognitiveRoutingRuntimeStatus;
|
|
131
|
+
runtimeReason?: string;
|
|
105
132
|
activeProfile?: CognitiveRoutingProfileName;
|
|
106
133
|
epoch?: string;
|
|
107
134
|
returnTarget?: CognitiveRoutingPair;
|
|
@@ -187,18 +214,47 @@ function asPair(value: unknown): CognitiveRoutingPair | undefined {
|
|
|
187
214
|
};
|
|
188
215
|
}
|
|
189
216
|
|
|
217
|
+
function hostPairFromEntry(entry: Record<string, unknown>): CognitiveRoutingPair | undefined {
|
|
218
|
+
if (entry.type === "model_state_change") {
|
|
219
|
+
return asPair({
|
|
220
|
+
provider: entry.provider,
|
|
221
|
+
modelId: entry.modelId,
|
|
222
|
+
thinkingLevel: entry.thinkingLevel,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
if (entry.type !== "custom" || entry.customType !== PI_SESSION_MODEL_STATE_ENTRY) return undefined;
|
|
226
|
+
const commit = parsePiSessionModelStateCommit(entry.data);
|
|
227
|
+
if (!commit || commit.phase !== "committed" || commit.status !== "applied") return undefined;
|
|
228
|
+
return commit.target;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function hostCorrelationIdFromEntry(entry: Record<string, unknown>): string | undefined {
|
|
232
|
+
if (entry.type === "model_state_change") {
|
|
233
|
+
return typeof entry.correlationId === "string" ? entry.correlationId : undefined;
|
|
234
|
+
}
|
|
235
|
+
if (entry.type !== "custom" || entry.customType !== PI_SESSION_MODEL_STATE_ENTRY) return undefined;
|
|
236
|
+
const commit = parsePiSessionModelStateCommit(entry.data);
|
|
237
|
+
return commit?.phase === "committed" && commit.status === "applied" ? commit.correlationId : undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function latestPiSessionCommit(entries: readonly unknown[], correlationId: string) {
|
|
241
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
242
|
+
const entry = entries[index];
|
|
243
|
+
if (!entry || typeof entry !== "object") continue;
|
|
244
|
+
const candidate = entry as Record<string, unknown>;
|
|
245
|
+
if (candidate.type !== "custom" || candidate.customType !== PI_SESSION_MODEL_STATE_ENTRY) continue;
|
|
246
|
+
const commit = parsePiSessionModelStateCommit(candidate.data);
|
|
247
|
+
if (commit?.correlationId === correlationId) return commit;
|
|
248
|
+
}
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
190
252
|
function latestHostPair(context: ControllerContext): CognitiveRoutingPair | undefined {
|
|
191
253
|
const entries = branchEntriesFrom(context);
|
|
192
254
|
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
193
255
|
const entry = entries[index];
|
|
194
256
|
if (!entry || typeof entry !== "object") continue;
|
|
195
|
-
const
|
|
196
|
-
if (candidate.type !== "model_state_change") continue;
|
|
197
|
-
const pair = asPair({
|
|
198
|
-
provider: candidate.provider,
|
|
199
|
-
modelId: candidate.modelId,
|
|
200
|
-
thinkingLevel: candidate.thinkingLevel,
|
|
201
|
-
});
|
|
257
|
+
const pair = hostPairFromEntry(entry as Record<string, unknown>);
|
|
202
258
|
if (pair) return pair;
|
|
203
259
|
}
|
|
204
260
|
return undefined;
|
|
@@ -224,6 +280,22 @@ function profileNameForPair(
|
|
|
224
280
|
return undefined;
|
|
225
281
|
}
|
|
226
282
|
|
|
283
|
+
function pairMatchesProfile(
|
|
284
|
+
capabilityState: CognitiveRoutingCapabilityState,
|
|
285
|
+
pair: CognitiveRoutingPair,
|
|
286
|
+
profileName: CognitiveRoutingProfileName,
|
|
287
|
+
): boolean {
|
|
288
|
+
const profile = capabilityState.resolvedProfiles[profileName];
|
|
289
|
+
return Boolean(
|
|
290
|
+
profile &&
|
|
291
|
+
pairEquals(pair, {
|
|
292
|
+
provider: profile.provider,
|
|
293
|
+
modelId: profile.model,
|
|
294
|
+
thinkingLevel: profile.effectiveThinkingLevel,
|
|
295
|
+
}),
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
227
299
|
function asIntent(value: unknown): CognitiveRoutingIntent | undefined {
|
|
228
300
|
if (!value || typeof value !== "object") return undefined;
|
|
229
301
|
const candidate = value as Record<string, unknown>;
|
|
@@ -252,6 +324,9 @@ function asIntent(value: unknown): CognitiveRoutingIntent | undefined {
|
|
|
252
324
|
: {}),
|
|
253
325
|
...(fromPair ? { fromPair } : {}),
|
|
254
326
|
...(isProfileName(candidate.fromProfile) ? { fromProfile: candidate.fromProfile } : {}),
|
|
327
|
+
...(candidate.fromControl === "automatic" || candidate.fromControl === "manual"
|
|
328
|
+
? { fromControl: candidate.fromControl }
|
|
329
|
+
: {}),
|
|
255
330
|
...(typeof candidate.reason === "string" ? { reason: candidate.reason } : {}),
|
|
256
331
|
...(typeof candidate.branchId === "string" || candidate.branchId === null
|
|
257
332
|
? { branchId: candidate.branchId as string | null }
|
|
@@ -269,6 +344,44 @@ function asIntent(value: unknown): CognitiveRoutingIntent | undefined {
|
|
|
269
344
|
};
|
|
270
345
|
}
|
|
271
346
|
|
|
347
|
+
function asInactiveEntry(value: unknown): CognitiveRoutingInactiveEntry | undefined {
|
|
348
|
+
if (!value || typeof value !== "object") return undefined;
|
|
349
|
+
const candidate = value as Record<string, unknown>;
|
|
350
|
+
const pair = asPair(candidate.pair);
|
|
351
|
+
if (
|
|
352
|
+
candidate.version !== 1 ||
|
|
353
|
+
candidate.source !== "user" ||
|
|
354
|
+
typeof candidate.reason !== "string" ||
|
|
355
|
+
candidate.reason.length === 0 ||
|
|
356
|
+
!pair
|
|
357
|
+
) {
|
|
358
|
+
return undefined;
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
version: 1,
|
|
362
|
+
source: "user",
|
|
363
|
+
reason: candidate.reason,
|
|
364
|
+
...(typeof candidate.branchId === "string" || candidate.branchId === null
|
|
365
|
+
? { branchId: candidate.branchId as string | null }
|
|
366
|
+
: {}),
|
|
367
|
+
pair,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function latestInactiveEntry(
|
|
372
|
+
entries: readonly unknown[],
|
|
373
|
+
): { entry: CognitiveRoutingInactiveEntry; position: number } | undefined {
|
|
374
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
375
|
+
const candidate = entries[index];
|
|
376
|
+
if (!candidate || typeof candidate !== "object") continue;
|
|
377
|
+
const record = candidate as Record<string, unknown>;
|
|
378
|
+
if (record.type !== "custom" || record.customType !== COGNITIVE_ROUTING_INACTIVE_ENTRY) continue;
|
|
379
|
+
const entry = asInactiveEntry(record.data);
|
|
380
|
+
if (entry) return { entry, position: index };
|
|
381
|
+
}
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
|
|
272
385
|
function entriesFrom(context: ControllerContext): readonly unknown[] {
|
|
273
386
|
try {
|
|
274
387
|
const entries = context.sessionManager?.getEntries?.();
|
|
@@ -338,7 +451,14 @@ function latestControlMode(entries: readonly unknown[], intent: CognitiveRouting
|
|
|
338
451
|
}
|
|
339
452
|
if (candidate.customType !== COGNITIVE_ROUTING_INTENT_ENTRY) continue;
|
|
340
453
|
const candidateIntent = asIntent(candidate.data);
|
|
341
|
-
if (!candidateIntent || candidateIntent.
|
|
454
|
+
if (!candidateIntent || candidateIntent.epoch !== intent.epoch) continue;
|
|
455
|
+
if (candidateIntent.phase === "abandoned") {
|
|
456
|
+
if (candidateIntent.fromControl === "manual" && candidateIntent.fromProfile) {
|
|
457
|
+
return `manual-${candidateIntent.fromProfile}` as CognitiveRoutingControlMode;
|
|
458
|
+
}
|
|
459
|
+
if (candidateIntent.fromControl === "automatic") return "automatic";
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
342
462
|
if (candidateIntent.kind === "profile" || candidateIntent.kind === "activation") {
|
|
343
463
|
return controlModeForIntent(candidateIntent);
|
|
344
464
|
}
|
|
@@ -346,14 +466,86 @@ function latestControlMode(entries: readonly unknown[], intent: CognitiveRouting
|
|
|
346
466
|
return "automatic";
|
|
347
467
|
}
|
|
348
468
|
|
|
469
|
+
function intentEntryIndex(entries: readonly unknown[], intent: CognitiveRoutingIntent): number {
|
|
470
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
471
|
+
const entry = entries[index];
|
|
472
|
+
if (!entry || typeof entry !== "object") continue;
|
|
473
|
+
const candidate = entry as Record<string, unknown>;
|
|
474
|
+
if (candidate.type !== "custom" || candidate.customType !== COGNITIVE_ROUTING_INTENT_ENTRY) continue;
|
|
475
|
+
const parsed = asIntent(candidate.data);
|
|
476
|
+
if (parsed?.correlationId === intent.correlationId && parsed.phase === "prepared") return index;
|
|
477
|
+
}
|
|
478
|
+
return entries.length;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function matchesBaseline(candidate: CognitiveRoutingIntent, failed: CognitiveRoutingIntent): boolean {
|
|
482
|
+
if (failed.fromPair && !pairEquals(candidate.target, failed.fromPair)) return false;
|
|
483
|
+
if (failed.fromProfile && candidate.profile !== failed.fromProfile) return false;
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export function resolveControlBeforeIntent(
|
|
488
|
+
entries: readonly unknown[],
|
|
489
|
+
intent: CognitiveRoutingIntent,
|
|
490
|
+
): CognitiveRoutingControlMode | undefined {
|
|
491
|
+
if (intent.fromControl === "automatic") return "automatic";
|
|
492
|
+
if (intent.fromControl === "manual") {
|
|
493
|
+
return intent.fromProfile ? (`manual-${intent.fromProfile}` as CognitiveRoutingControlMode) : undefined;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const priorEntries = entries.slice(0, intentEntryIndex(entries, intent));
|
|
497
|
+
const abandonedCorrelations = new Set<string>();
|
|
498
|
+
for (const entry of priorEntries) {
|
|
499
|
+
if (!entry || typeof entry !== "object") continue;
|
|
500
|
+
const candidate = entry as Record<string, unknown>;
|
|
501
|
+
if (candidate.type !== "custom" || candidate.customType !== COGNITIVE_ROUTING_INTENT_ENTRY) continue;
|
|
502
|
+
const parsed = asIntent(candidate.data);
|
|
503
|
+
if (parsed?.phase === "abandoned") abandonedCorrelations.add(parsed.correlationId);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
let resolved: CognitiveRoutingControlMode | undefined;
|
|
507
|
+
for (const entry of priorEntries) {
|
|
508
|
+
if (!entry || typeof entry !== "object") continue;
|
|
509
|
+
const candidate = entry as Record<string, unknown>;
|
|
510
|
+
if (candidate.type !== "custom") continue;
|
|
511
|
+
if (candidate.customType === COGNITIVE_ROUTING_CONTROL_ENTRY) {
|
|
512
|
+
const data = candidate.data;
|
|
513
|
+
if (
|
|
514
|
+
data &&
|
|
515
|
+
typeof data === "object" &&
|
|
516
|
+
((data as Record<string, unknown>).version === 1 || (data as Record<string, unknown>).version === 2) &&
|
|
517
|
+
(data as Record<string, unknown>).epoch === intent.epoch &&
|
|
518
|
+
(data as Record<string, unknown>).control === "automatic"
|
|
519
|
+
) {
|
|
520
|
+
resolved = "automatic";
|
|
521
|
+
}
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
if (candidate.customType !== COGNITIVE_ROUTING_INTENT_ENTRY) continue;
|
|
525
|
+
const parsed = asIntent(candidate.data);
|
|
526
|
+
if (
|
|
527
|
+
!parsed ||
|
|
528
|
+
parsed.phase !== "prepared" ||
|
|
529
|
+
parsed.epoch !== intent.epoch ||
|
|
530
|
+
abandonedCorrelations.has(parsed.correlationId) ||
|
|
531
|
+
(parsed.kind !== "profile" && parsed.kind !== "activation") ||
|
|
532
|
+
!hasMatchingHostEntry(priorEntries, parsed) ||
|
|
533
|
+
!matchesBaseline(parsed, intent)
|
|
534
|
+
) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
resolved = controlModeForIntent(parsed);
|
|
538
|
+
}
|
|
539
|
+
return resolved;
|
|
540
|
+
}
|
|
541
|
+
|
|
349
542
|
function hasMatchingHostEntry(entries: readonly unknown[], intent: CognitiveRoutingIntent): boolean {
|
|
350
543
|
return entries.some((entry) => {
|
|
351
544
|
if (!entry || typeof entry !== "object") return false;
|
|
352
545
|
const candidate = entry as Record<string, unknown>;
|
|
353
546
|
return (
|
|
354
|
-
candidate
|
|
355
|
-
candidate
|
|
356
|
-
pairEquals(asPair(candidate), intent.target)
|
|
547
|
+
hostCorrelationIdFromEntry(candidate) === intent.correlationId &&
|
|
548
|
+
pairEquals(hostPairFromEntry(candidate), intent.target)
|
|
357
549
|
);
|
|
358
550
|
});
|
|
359
551
|
}
|
|
@@ -362,13 +554,18 @@ export class CognitiveRoutingController {
|
|
|
362
554
|
private readonly capabilityState: CognitiveRoutingCapabilityState;
|
|
363
555
|
private readonly pi: ControllerPi;
|
|
364
556
|
private readonly ctx: ControllerContext;
|
|
557
|
+
private readonly initialSessionStart: CognitiveRoutingSessionStart | undefined;
|
|
365
558
|
private readonly label: string;
|
|
366
559
|
private readonly idFactory: () => string;
|
|
560
|
+
private readonly liveStateAuthoritative: boolean;
|
|
367
561
|
private lease: ModelStateLease | undefined;
|
|
368
562
|
private activeProfile: CognitiveRoutingProfileName | undefined;
|
|
369
563
|
private epoch: string | undefined;
|
|
370
564
|
private returnTarget: CognitiveRoutingPair | undefined;
|
|
371
565
|
private controlMode: CognitiveRoutingControlMode = "automatic";
|
|
566
|
+
private nativeInactiveReason: string | undefined;
|
|
567
|
+
private nativeBlockedReason: string | undefined;
|
|
568
|
+
private transitionInFlight = false;
|
|
372
569
|
private branchPending = false;
|
|
373
570
|
private operation: Promise<unknown> = Promise.resolve();
|
|
374
571
|
|
|
@@ -376,18 +573,32 @@ export class CognitiveRoutingController {
|
|
|
376
573
|
this.capabilityState = options.capabilityState;
|
|
377
574
|
this.pi = options.pi;
|
|
378
575
|
this.ctx = options.ctx;
|
|
576
|
+
this.initialSessionStart = options.initialSessionStart;
|
|
379
577
|
this.label = options.label ?? "Cognitive Routing";
|
|
380
578
|
this.idFactory = options.idFactory ?? randomUUID;
|
|
579
|
+
this.liveStateAuthoritative = options.liveStateAuthoritative === true;
|
|
381
580
|
}
|
|
382
581
|
|
|
383
582
|
state(): CognitiveRoutingControllerState {
|
|
384
|
-
const hostPair = latestHostPair(this.ctx);
|
|
385
|
-
const observedPair = hostPair ?? this.currentPair();
|
|
583
|
+
const hostPair = this.liveStateAuthoritative ? undefined : latestHostPair(this.ctx);
|
|
584
|
+
const observedPair = this.liveStateAuthoritative ? this.currentPair() : (hostPair ?? this.currentPair());
|
|
386
585
|
const observedProfile = this.activeProfile ? profileNameForPair(this.capabilityState, observedPair) : undefined;
|
|
387
|
-
const activeProfile =
|
|
586
|
+
const activeProfile = this.liveStateAuthoritative
|
|
587
|
+
? observedProfile
|
|
588
|
+
: hostPair
|
|
589
|
+
? observedProfile
|
|
590
|
+
: (observedProfile ?? this.activeProfile);
|
|
591
|
+
const effective = this.lease !== undefined && activeProfile !== undefined && !this.branchPending;
|
|
388
592
|
return {
|
|
389
|
-
effective
|
|
593
|
+
effective,
|
|
390
594
|
controlMode: this.controlMode,
|
|
595
|
+
...(this.nativeBlockedReason
|
|
596
|
+
? { runtimeStatus: "blocked" as const, runtimeReason: this.nativeBlockedReason }
|
|
597
|
+
: this.nativeInactiveReason
|
|
598
|
+
? { runtimeStatus: "inactive" as const, runtimeReason: this.nativeInactiveReason }
|
|
599
|
+
: effective
|
|
600
|
+
? { runtimeStatus: "active" as const }
|
|
601
|
+
: {}),
|
|
391
602
|
...(activeProfile ? { activeProfile } : {}),
|
|
392
603
|
...(this.epoch ? { epoch: this.epoch } : {}),
|
|
393
604
|
...(this.returnTarget ? { returnTarget: { ...this.returnTarget } } : {}),
|
|
@@ -449,13 +660,86 @@ export class CognitiveRoutingController {
|
|
|
449
660
|
return this.enqueue(() => this._reconcileBranch(mechanism));
|
|
450
661
|
}
|
|
451
662
|
|
|
452
|
-
|
|
663
|
+
async observeNativeChange(
|
|
664
|
+
reason: string,
|
|
665
|
+
source: "set" | "cycle" | "restore" | undefined = undefined,
|
|
666
|
+
): Promise<CognitiveRoutingNativeChangeResult> {
|
|
667
|
+
if (this.transitionInFlight || source === "restore") return { status: "ignored" };
|
|
668
|
+
return this.enqueue(() => this._observeNativeChange(reason, source));
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
async reconcileLiveState(reason = "Pi model state diverged"): Promise<CognitiveRoutingNativeChangeResult> {
|
|
672
|
+
if (this.transitionInFlight) return { status: "ignored" };
|
|
673
|
+
return this.enqueue(() => this._observeNativeChange(reason));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
private recordNativeInactive(reason: string, pair: CognitiveRoutingPair): CognitiveRoutingNativeChangeResult {
|
|
677
|
+
try {
|
|
678
|
+
this.pi.appendEntryDurable(COGNITIVE_ROUTING_INACTIVE_ENTRY, {
|
|
679
|
+
version: 1,
|
|
680
|
+
source: "user",
|
|
681
|
+
reason,
|
|
682
|
+
branchId: this.currentBranchId(),
|
|
683
|
+
pair,
|
|
684
|
+
});
|
|
685
|
+
} catch {
|
|
686
|
+
this.nativeInactiveReason = undefined;
|
|
687
|
+
this.nativeBlockedReason = "inactive_state_persistence_failed";
|
|
688
|
+
return { status: "blocked", reason: this.nativeBlockedReason };
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
this.nativeInactiveReason = reason;
|
|
692
|
+
this.nativeBlockedReason = undefined;
|
|
693
|
+
return { status: "inactive", reason };
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
private async _observeNativeChange(
|
|
697
|
+
reason: string,
|
|
698
|
+
source: "set" | "cycle" | "restore" | undefined = undefined,
|
|
699
|
+
): Promise<CognitiveRoutingNativeChangeResult> {
|
|
700
|
+
if (!this.lease || !this.activeProfile) return { status: "ignored" };
|
|
701
|
+
|
|
702
|
+
const profile = this.capabilityState.resolvedProfiles[this.activeProfile];
|
|
703
|
+
const current = this.currentPair();
|
|
704
|
+
const expected = profile
|
|
705
|
+
? {
|
|
706
|
+
provider: profile.provider,
|
|
707
|
+
modelId: profile.model,
|
|
708
|
+
thinkingLevel: profile.effectiveThinkingLevel,
|
|
709
|
+
}
|
|
710
|
+
: undefined;
|
|
711
|
+
if (current && expected && pairEquals(current, expected) && source !== "set") return { status: "ignored" };
|
|
712
|
+
const lease = this.lease;
|
|
713
|
+
if (!current) {
|
|
714
|
+
await this.releaseLease(lease);
|
|
715
|
+
this.clearActiveState();
|
|
716
|
+
this.nativeBlockedReason = "native_state_unavailable";
|
|
717
|
+
return { status: "blocked", reason: this.nativeBlockedReason };
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const result = this.recordNativeInactive(reason, current);
|
|
721
|
+
await this.releaseLease(lease);
|
|
722
|
+
this.clearActiveState();
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
private async _activate(
|
|
727
|
+
allowInactive = false,
|
|
728
|
+
requestedProfile?: CognitiveRoutingProfileName,
|
|
729
|
+
requestedControlMode?: CognitiveRoutingControlMode,
|
|
730
|
+
): Promise<CognitiveRoutingActivationResult> {
|
|
453
731
|
if (this.state().effective && this.activeProfile) {
|
|
454
732
|
return { status: "active", profile: this.activeProfile };
|
|
455
733
|
}
|
|
456
734
|
if (!this.capabilityState.effective) {
|
|
457
735
|
return { status: "inactive", reason: "not_effective" };
|
|
458
736
|
}
|
|
737
|
+
if ((this.nativeInactiveReason || this.nativeBlockedReason) && !allowInactive) {
|
|
738
|
+
return {
|
|
739
|
+
status: "inactive",
|
|
740
|
+
reason: this.nativeBlockedReason ? "runtime_blocked" : "native_override",
|
|
741
|
+
};
|
|
742
|
+
}
|
|
459
743
|
|
|
460
744
|
const sessionEntries = entriesFrom(this.ctx);
|
|
461
745
|
const lifecycleIntent = latestIntent(
|
|
@@ -465,7 +749,10 @@ export class CognitiveRoutingController {
|
|
|
465
749
|
if (lifecycleIntent && !hasMatchingHostEntry(sessionEntries, lifecycleIntent)) {
|
|
466
750
|
return { status: "inactive", reason: "pending_intent" };
|
|
467
751
|
}
|
|
468
|
-
if (
|
|
752
|
+
if (
|
|
753
|
+
lifecycleIntent?.kind === "activation" &&
|
|
754
|
+
!(allowInactive && (this.nativeInactiveReason || this.nativeBlockedReason))
|
|
755
|
+
) {
|
|
469
756
|
return { status: "inactive", reason: "recover_required" };
|
|
470
757
|
}
|
|
471
758
|
const preservedReload = lifecycleIntent?.kind === "closing" && lifecycleIntent.resumeProfile !== undefined;
|
|
@@ -478,19 +765,29 @@ export class CognitiveRoutingController {
|
|
|
478
765
|
}
|
|
479
766
|
const epoch = this.epoch ?? this.idFactory();
|
|
480
767
|
const branchId = this.currentBranchId();
|
|
481
|
-
const sessionStart = this.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
768
|
+
const sessionStart = this.initialSessionStart ??
|
|
769
|
+
this.capabilityState.sessionStart ?? {
|
|
770
|
+
control: "automatic" as const,
|
|
771
|
+
profile: "reasoning" as const,
|
|
772
|
+
};
|
|
773
|
+
let profileName: CognitiveRoutingProfileName;
|
|
774
|
+
if (requestedProfile) {
|
|
775
|
+
profileName = requestedProfile;
|
|
776
|
+
} else if (preservedReload) {
|
|
777
|
+
profileName = lifecycleIntent.resumeProfile;
|
|
778
|
+
} else if (sessionStart.control === "automatic") {
|
|
779
|
+
profileName = "reasoning";
|
|
780
|
+
} else {
|
|
781
|
+
profileName = sessionStart.profile;
|
|
782
|
+
}
|
|
488
783
|
const profile = this.capabilityState.resolvedProfiles[profileName];
|
|
489
784
|
if (!profile) {
|
|
490
785
|
return { status: "inactive", reason: preservedReload ? "resume_profile_unavailable" : "profile_unavailable" };
|
|
491
786
|
}
|
|
492
787
|
let controlMode: CognitiveRoutingControlMode;
|
|
493
|
-
if (
|
|
788
|
+
if (requestedControlMode) {
|
|
789
|
+
controlMode = requestedControlMode;
|
|
790
|
+
} else if (preservedReload) {
|
|
494
791
|
controlMode = lifecycleIntent.resumeControl === "manual" ? `manual-${profileName}` : "automatic";
|
|
495
792
|
} else {
|
|
496
793
|
controlMode = sessionStart.control === "manual" ? `manual-${profileName}` : "automatic";
|
|
@@ -547,7 +844,7 @@ export class CognitiveRoutingController {
|
|
|
547
844
|
(candidate) => candidate.kind === "profile" && candidate.epoch === this.epoch,
|
|
548
845
|
)
|
|
549
846
|
: undefined;
|
|
550
|
-
const fromPair =
|
|
847
|
+
const fromPair = this.observedHostPair();
|
|
551
848
|
const fromProfile = this.activeProfile ?? profileNameForPair(this.capabilityState, fromPair);
|
|
552
849
|
const intent = this.prepareIntent({
|
|
553
850
|
kind: "closing",
|
|
@@ -575,7 +872,7 @@ export class CognitiveRoutingController {
|
|
|
575
872
|
|
|
576
873
|
this.clearActiveState(false);
|
|
577
874
|
try {
|
|
578
|
-
const result = await
|
|
875
|
+
const result = await this.setState(lease, { ...returnTarget, correlationId: intent.correlationId });
|
|
579
876
|
if (result.status !== "applied" && result.status !== "unchanged") {
|
|
580
877
|
return { status: "inactive", reason: "restore_rejected" };
|
|
581
878
|
}
|
|
@@ -587,9 +884,133 @@ export class CognitiveRoutingController {
|
|
|
587
884
|
}
|
|
588
885
|
}
|
|
589
886
|
|
|
887
|
+
private async recoverTerminalIntent(
|
|
888
|
+
intent: CognitiveRoutingIntent,
|
|
889
|
+
): Promise<CognitiveRoutingRecoveryResult | undefined> {
|
|
890
|
+
const commit = latestPiSessionCommit(branchEntriesFrom(this.ctx), intent.correlationId);
|
|
891
|
+
if (!commit) return undefined;
|
|
892
|
+
if (commit.phase === "prepared") {
|
|
893
|
+
if (!this.pi.recoverPreparedModelState) {
|
|
894
|
+
return { status: "pending", reason: "prepared_recovery_unsupported" };
|
|
895
|
+
}
|
|
896
|
+
let recovery: { status: "restored" | "rollback-failed" | "rejected" };
|
|
897
|
+
try {
|
|
898
|
+
recovery = await this.pi.recoverPreparedModelState({
|
|
899
|
+
fromPair: commit.fromPair,
|
|
900
|
+
target: commit.target,
|
|
901
|
+
correlationId: commit.correlationId,
|
|
902
|
+
});
|
|
903
|
+
} catch {
|
|
904
|
+
return { status: "pending", reason: "prepared_recovery_failed" };
|
|
905
|
+
}
|
|
906
|
+
if (recovery.status === "rollback-failed") {
|
|
907
|
+
return { status: "pending", reason: "rollback_failed" };
|
|
908
|
+
}
|
|
909
|
+
if (recovery.status !== "restored") {
|
|
910
|
+
return { status: "pending", reason: "prepared_recovery_failed" };
|
|
911
|
+
}
|
|
912
|
+
try {
|
|
913
|
+
this.abandonPreparedIntent(intent, "Pi session transition interrupted.");
|
|
914
|
+
} catch {
|
|
915
|
+
return { status: "pending", reason: "intent_abandon_failed" };
|
|
916
|
+
}
|
|
917
|
+
const recovered = await this._recover();
|
|
918
|
+
return recovered.status === "inactive" && recovered.reason === "no_pending_intent"
|
|
919
|
+
? { status: "inactive", reason: "intent_abandoned" }
|
|
920
|
+
: recovered;
|
|
921
|
+
}
|
|
922
|
+
if (commit.phase !== "aborted") return undefined;
|
|
923
|
+
if (commit.status === "rollback-failed") {
|
|
924
|
+
return { status: "pending", reason: "rollback_failed" };
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
try {
|
|
928
|
+
this.abandonPreparedIntent(intent, "Pi session transition rolled back.");
|
|
929
|
+
} catch {
|
|
930
|
+
return { status: "pending", reason: "intent_abandon_failed" };
|
|
931
|
+
}
|
|
932
|
+
return this._recover();
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
private async recoverTerminalProfileIntent(
|
|
936
|
+
intent: CognitiveRoutingIntent,
|
|
937
|
+
): Promise<CognitiveRoutingRecoveryResult | undefined> {
|
|
938
|
+
const commit = latestPiSessionCommit(branchEntriesFrom(this.ctx), intent.correlationId);
|
|
939
|
+
if (!commit || commit.phase === "committed") return undefined;
|
|
940
|
+
if (commit.phase === "aborted" && commit.status === "rollback-failed") {
|
|
941
|
+
return { status: "pending", reason: "rollback_failed" };
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
const controlMode = resolveControlBeforeIntent(branchEntriesFrom(this.ctx), intent);
|
|
945
|
+
if (
|
|
946
|
+
!controlMode ||
|
|
947
|
+
!intent.fromPair ||
|
|
948
|
+
!intent.fromProfile ||
|
|
949
|
+
!pairMatchesProfile(this.capabilityState, intent.fromPair, intent.fromProfile)
|
|
950
|
+
) {
|
|
951
|
+
return { status: "pending", reason: "baseline_control_unknown" };
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (commit.phase === "prepared") {
|
|
955
|
+
if (!this.pi.recoverPreparedModelState) {
|
|
956
|
+
return { status: "pending", reason: "prepared_recovery_unsupported" };
|
|
957
|
+
}
|
|
958
|
+
let recovery: { status: "restored" | "rollback-failed" | "rejected" };
|
|
959
|
+
try {
|
|
960
|
+
recovery = await this.pi.recoverPreparedModelState({
|
|
961
|
+
fromPair: commit.fromPair,
|
|
962
|
+
target: commit.target,
|
|
963
|
+
correlationId: commit.correlationId,
|
|
964
|
+
});
|
|
965
|
+
} catch {
|
|
966
|
+
return { status: "pending", reason: "prepared_recovery_failed" };
|
|
967
|
+
}
|
|
968
|
+
if (recovery.status === "rollback-failed") {
|
|
969
|
+
return { status: "pending", reason: "rollback_failed" };
|
|
970
|
+
}
|
|
971
|
+
if (recovery.status !== "restored") {
|
|
972
|
+
return { status: "pending", reason: "prepared_recovery_failed" };
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
try {
|
|
977
|
+
this.abandonPreparedIntent(intent, "Pi session transition interrupted or rolled back.");
|
|
978
|
+
} catch {
|
|
979
|
+
return { status: "pending", reason: "intent_abandon_failed" };
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const recovered = await this.activatePrepared({
|
|
983
|
+
kind: "profile",
|
|
984
|
+
controlMode,
|
|
985
|
+
source: "system",
|
|
986
|
+
mechanism: "recovery",
|
|
987
|
+
...(decisionCorrelationForIntent(intent) ? { decisionCorrelationId: decisionCorrelationForIntent(intent) } : {}),
|
|
988
|
+
branchId: this.currentBranchId(),
|
|
989
|
+
epoch: intent.epoch,
|
|
990
|
+
returnTarget: intent.returnTarget,
|
|
991
|
+
target: intent.fromPair,
|
|
992
|
+
profile: intent.fromProfile,
|
|
993
|
+
});
|
|
994
|
+
return recovered.status === "active" ? recovered : { status: "pending", reason: "baseline_recovery_failed" };
|
|
995
|
+
}
|
|
996
|
+
|
|
590
997
|
private async _recover(): Promise<CognitiveRoutingRecoveryResult> {
|
|
591
998
|
const sessionEntries = entriesFrom(this.ctx);
|
|
592
999
|
const branchEntries = branchEntriesFrom(this.ctx);
|
|
1000
|
+
const inactive = latestInactiveEntry(branchEntries);
|
|
1001
|
+
const branchLifecycleIntent = latestIntent(
|
|
1002
|
+
branchEntries,
|
|
1003
|
+
(intent) => intent.kind === "activation" || intent.kind === "closing",
|
|
1004
|
+
);
|
|
1005
|
+
if (
|
|
1006
|
+
inactive &&
|
|
1007
|
+
(!branchLifecycleIntent || intentEntryIndex(branchEntries, branchLifecycleIntent) <= inactive.position)
|
|
1008
|
+
) {
|
|
1009
|
+
this.nativeInactiveReason = inactive.entry.reason;
|
|
1010
|
+
this.nativeBlockedReason = undefined;
|
|
1011
|
+
this.clearActiveState();
|
|
1012
|
+
return { status: "inactive", reason: "native_override" };
|
|
1013
|
+
}
|
|
593
1014
|
const lifecycleIntent = latestIntent(
|
|
594
1015
|
sessionEntries,
|
|
595
1016
|
(intent) => intent.kind === "activation" || intent.kind === "closing",
|
|
@@ -597,6 +1018,10 @@ export class CognitiveRoutingController {
|
|
|
597
1018
|
if (!lifecycleIntent) {
|
|
598
1019
|
return { status: "inactive", reason: "no_pending_intent" };
|
|
599
1020
|
}
|
|
1021
|
+
if (lifecycleIntent.kind === "activation") {
|
|
1022
|
+
const lifecycleTerminal = await this.recoverTerminalIntent(lifecycleIntent);
|
|
1023
|
+
if (lifecycleTerminal) return lifecycleTerminal;
|
|
1024
|
+
}
|
|
600
1025
|
if (lifecycleIntent.kind === "closing") {
|
|
601
1026
|
return hasMatchingHostEntry(sessionEntries, lifecycleIntent)
|
|
602
1027
|
? { status: "inactive", reason: "closed" }
|
|
@@ -618,6 +1043,19 @@ export class CognitiveRoutingController {
|
|
|
618
1043
|
thinkingLevel: currentActivationProfile.effectiveThinkingLevel,
|
|
619
1044
|
};
|
|
620
1045
|
const activationMatched = hasMatchingHostEntry(sessionEntries, lifecycleIntent);
|
|
1046
|
+
if (this.liveStateAuthoritative && activationMatched) {
|
|
1047
|
+
const livePair = this.currentPair();
|
|
1048
|
+
if (!livePair) {
|
|
1049
|
+
this.clearActiveState();
|
|
1050
|
+
this.nativeBlockedReason = "native_state_unavailable";
|
|
1051
|
+
return { status: "inactive", reason: "native_override" };
|
|
1052
|
+
}
|
|
1053
|
+
if (!pairEquals(livePair, activationTarget)) {
|
|
1054
|
+
this.recordNativeInactive("Pi model state diverged during recovery", livePair);
|
|
1055
|
+
this.clearActiveState();
|
|
1056
|
+
return { status: "inactive", reason: "native_override" };
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
621
1059
|
if (activationMatched && !pairEquals(activationTarget, lifecycleIntent.target)) {
|
|
622
1060
|
return { status: "pending", reason: "applied_target_changed" };
|
|
623
1061
|
}
|
|
@@ -627,7 +1065,7 @@ export class CognitiveRoutingController {
|
|
|
627
1065
|
if (!activationMatched) {
|
|
628
1066
|
const retried = await this.activatePrepared({
|
|
629
1067
|
kind: "activation",
|
|
630
|
-
controlMode:
|
|
1068
|
+
controlMode: latestControlMode(branchEntries, lifecycleIntent),
|
|
631
1069
|
source: "system",
|
|
632
1070
|
mechanism: "recovery",
|
|
633
1071
|
branchId: this.currentBranchId(),
|
|
@@ -644,6 +1082,8 @@ export class CognitiveRoutingController {
|
|
|
644
1082
|
(intent) => intent.kind === "profile" && intent.epoch === lifecycleIntent.epoch,
|
|
645
1083
|
);
|
|
646
1084
|
if (profileIntent) {
|
|
1085
|
+
const terminal = await this.recoverTerminalProfileIntent(profileIntent);
|
|
1086
|
+
if (terminal) return terminal;
|
|
647
1087
|
const currentProfile = this.capabilityState.resolvedProfiles[profileIntent.profile ?? "standard"];
|
|
648
1088
|
if (!currentProfile) {
|
|
649
1089
|
return this.abandonIntent(profileIntent, "Prepared profile is unavailable.");
|
|
@@ -678,7 +1118,7 @@ export class CognitiveRoutingController {
|
|
|
678
1118
|
|
|
679
1119
|
return this.activatePrepared({
|
|
680
1120
|
kind: "activation",
|
|
681
|
-
controlMode:
|
|
1121
|
+
controlMode: latestControlMode(branchEntries, lifecycleIntent),
|
|
682
1122
|
source: "system",
|
|
683
1123
|
mechanism: "recovery",
|
|
684
1124
|
branchId: this.currentBranchId(),
|
|
@@ -690,7 +1130,7 @@ export class CognitiveRoutingController {
|
|
|
690
1130
|
}
|
|
691
1131
|
|
|
692
1132
|
private async recoverClosingIntent(intent: CognitiveRoutingIntent): Promise<CognitiveRoutingRecoveryResult> {
|
|
693
|
-
const fromPair =
|
|
1133
|
+
const fromPair = this.observedHostPair();
|
|
694
1134
|
const fromProfile = intent.fromProfile ?? profileNameForPair(this.capabilityState, fromPair);
|
|
695
1135
|
const retryIntent = this.prepareIntent({
|
|
696
1136
|
kind: "closing",
|
|
@@ -726,7 +1166,10 @@ export class CognitiveRoutingController {
|
|
|
726
1166
|
}
|
|
727
1167
|
|
|
728
1168
|
try {
|
|
729
|
-
const result = await
|
|
1169
|
+
const result = await this.setState(acquisition.lease, {
|
|
1170
|
+
...intent.target,
|
|
1171
|
+
correlationId: retryIntent.correlationId,
|
|
1172
|
+
});
|
|
730
1173
|
return result.status === "applied" || result.status === "unchanged"
|
|
731
1174
|
? { status: "inactive", reason: "restored" }
|
|
732
1175
|
: { status: "pending", reason: "closing_recovery_rejected" };
|
|
@@ -737,20 +1180,24 @@ export class CognitiveRoutingController {
|
|
|
737
1180
|
}
|
|
738
1181
|
}
|
|
739
1182
|
|
|
1183
|
+
private abandonPreparedIntent(intent: CognitiveRoutingIntent, reason: string): void {
|
|
1184
|
+
const fromPair = intent.fromPair ?? this.observedHostPair();
|
|
1185
|
+
const fromProfile = intent.fromProfile ?? profileNameForPair(this.capabilityState, fromPair);
|
|
1186
|
+
this.pi.appendEntryDurable(COGNITIVE_ROUTING_INTENT_ENTRY, {
|
|
1187
|
+
...intent,
|
|
1188
|
+
version: 2,
|
|
1189
|
+
phase: "abandoned",
|
|
1190
|
+
source: "system",
|
|
1191
|
+
mechanism: intent.mechanism ?? "recovery",
|
|
1192
|
+
...(fromPair ? { fromPair } : {}),
|
|
1193
|
+
...(fromProfile ? { fromProfile } : {}),
|
|
1194
|
+
reason,
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
|
|
740
1198
|
private async abandonIntent(intent: CognitiveRoutingIntent, reason: string): Promise<CognitiveRoutingRecoveryResult> {
|
|
741
1199
|
try {
|
|
742
|
-
|
|
743
|
-
const fromProfile = intent.fromProfile ?? profileNameForPair(this.capabilityState, fromPair);
|
|
744
|
-
this.pi.appendEntryDurable(COGNITIVE_ROUTING_INTENT_ENTRY, {
|
|
745
|
-
...intent,
|
|
746
|
-
version: 2,
|
|
747
|
-
phase: "abandoned",
|
|
748
|
-
source: "system",
|
|
749
|
-
mechanism: intent.mechanism ?? "recovery",
|
|
750
|
-
...(fromPair ? { fromPair } : {}),
|
|
751
|
-
...(fromProfile ? { fromProfile } : {}),
|
|
752
|
-
reason,
|
|
753
|
-
});
|
|
1200
|
+
this.abandonPreparedIntent(intent, reason);
|
|
754
1201
|
const recovered = await this._recover();
|
|
755
1202
|
return recovered.status === "inactive" && recovered.reason === "no_pending_intent"
|
|
756
1203
|
? { status: "inactive", reason: "intent_abandoned" }
|
|
@@ -853,8 +1300,9 @@ export class CognitiveRoutingController {
|
|
|
853
1300
|
return { status: "inactive", reason: "not_effective" };
|
|
854
1301
|
}
|
|
855
1302
|
|
|
856
|
-
|
|
857
|
-
|
|
1303
|
+
if (!this.state().effective) {
|
|
1304
|
+
return this._activate(true, profile, `manual-${profile}`);
|
|
1305
|
+
}
|
|
858
1306
|
return this._setProfile(profile, {
|
|
859
1307
|
controlMode: `manual-${profile}`,
|
|
860
1308
|
source: "user",
|
|
@@ -902,7 +1350,7 @@ export class CognitiveRoutingController {
|
|
|
902
1350
|
modelId: resolvedProfile.model,
|
|
903
1351
|
thinkingLevel: resolvedProfile.effectiveThinkingLevel,
|
|
904
1352
|
};
|
|
905
|
-
const fromPair =
|
|
1353
|
+
const fromPair = this.observedHostPair();
|
|
906
1354
|
if (!fromPair) return { status: "inactive", reason: "current_state_unavailable" };
|
|
907
1355
|
const fromProfile = this.activeProfile ?? profileNameForPair(this.capabilityState, fromPair);
|
|
908
1356
|
const correlationId = this.idFactory();
|
|
@@ -917,6 +1365,7 @@ export class CognitiveRoutingController {
|
|
|
917
1365
|
...(decisionCorrelationId ? { decisionCorrelationId } : {}),
|
|
918
1366
|
...(fromPair ? { fromPair } : {}),
|
|
919
1367
|
...(fromProfile ? { fromProfile } : {}),
|
|
1368
|
+
fromControl: options.controlMode === "automatic" ? "automatic" : "manual",
|
|
920
1369
|
reason: options.reason,
|
|
921
1370
|
branchId: this.currentBranchId(),
|
|
922
1371
|
epoch: this.epoch,
|
|
@@ -946,8 +1395,27 @@ export class CognitiveRoutingController {
|
|
|
946
1395
|
}
|
|
947
1396
|
|
|
948
1397
|
try {
|
|
949
|
-
const result = await
|
|
1398
|
+
const result = await this.setState(lease, { ...target, correlationId: intent.correlationId });
|
|
950
1399
|
if (result.status !== "applied" && result.status !== "unchanged") {
|
|
1400
|
+
const commit = latestPiSessionCommit(branchEntriesFrom(this.ctx), intent.correlationId);
|
|
1401
|
+
if (result.status === "rejected" && commit?.phase === "aborted" && commit.status === "rollback-failed") {
|
|
1402
|
+
try {
|
|
1403
|
+
this.abandonPreparedIntent(intent, "Pi session rollback failed.");
|
|
1404
|
+
} catch {
|
|
1405
|
+
// Fail closed even when the terminal abandonment cannot be persisted.
|
|
1406
|
+
}
|
|
1407
|
+
await this.releaseLease(lease);
|
|
1408
|
+
this.clearActiveState();
|
|
1409
|
+
return { status: "inactive", reason: "rollback_failed" };
|
|
1410
|
+
}
|
|
1411
|
+
if (result.status === "rejected" && commit?.phase === "aborted" && commit.status === "rolled-back") {
|
|
1412
|
+
try {
|
|
1413
|
+
this.abandonPreparedIntent(intent, "Pi session transition rolled back.");
|
|
1414
|
+
this.branchPending = false;
|
|
1415
|
+
} catch {
|
|
1416
|
+
// Preserve the pending boundary when the terminal abandonment cannot be persisted.
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
951
1419
|
return { status: "inactive", reason: "transition_rejected" };
|
|
952
1420
|
}
|
|
953
1421
|
this.activeProfile = profile;
|
|
@@ -961,8 +1429,27 @@ export class CognitiveRoutingController {
|
|
|
961
1429
|
|
|
962
1430
|
private async _setAutomaticControl(mechanism: CognitiveRoutingMechanism): Promise<CognitiveRoutingAutomaticResult> {
|
|
963
1431
|
if (!this.lease || !this.epoch) {
|
|
964
|
-
return { status: "rejected", reason: "not_active" };
|
|
1432
|
+
if (!this.nativeInactiveReason && !this.nativeBlockedReason) return { status: "rejected", reason: "not_active" };
|
|
1433
|
+
const activation = await this._activate(true, this.profileForCurrentPair(), "automatic");
|
|
1434
|
+
return activation.status === "active"
|
|
1435
|
+
? { status: "automatic" }
|
|
1436
|
+
: { status: "rejected", reason: activation.reason };
|
|
965
1437
|
}
|
|
1438
|
+
const current = this.state();
|
|
1439
|
+
if (!current.activeProfile) {
|
|
1440
|
+
return { status: "rejected", reason: "profile_unknown" };
|
|
1441
|
+
}
|
|
1442
|
+
if (current.activeProfile !== "reasoning") {
|
|
1443
|
+
const transition = await this._setProfile("reasoning", {
|
|
1444
|
+
controlMode: "automatic",
|
|
1445
|
+
source: "user",
|
|
1446
|
+
mechanism,
|
|
1447
|
+
reason: "automatic control selects the Reasoning profile",
|
|
1448
|
+
});
|
|
1449
|
+
if (transition.status !== "active") return { status: "rejected", reason: transition.reason };
|
|
1450
|
+
return { status: "automatic" };
|
|
1451
|
+
}
|
|
1452
|
+
if (this.controlMode === "automatic") return { status: "automatic" };
|
|
966
1453
|
try {
|
|
967
1454
|
this.pi.appendEntryDurable(COGNITIVE_ROUTING_CONTROL_ENTRY, {
|
|
968
1455
|
version: 2,
|
|
@@ -994,6 +1481,15 @@ export class CognitiveRoutingController {
|
|
|
994
1481
|
}
|
|
995
1482
|
}
|
|
996
1483
|
|
|
1484
|
+
private observedHostPair(): CognitiveRoutingPair | undefined {
|
|
1485
|
+
const livePair = this.currentPair();
|
|
1486
|
+
return this.liveStateAuthoritative ? livePair : (latestHostPair(this.ctx) ?? livePair);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
private profileForCurrentPair(): CognitiveRoutingProfileName | undefined {
|
|
1490
|
+
return profileNameForPair(this.capabilityState, this.currentPair());
|
|
1491
|
+
}
|
|
1492
|
+
|
|
997
1493
|
private currentPair(): CognitiveRoutingPair | undefined {
|
|
998
1494
|
const model = this.ctx.model;
|
|
999
1495
|
if (
|
|
@@ -1028,7 +1524,7 @@ export class CognitiveRoutingController {
|
|
|
1028
1524
|
target: CognitiveRoutingPair;
|
|
1029
1525
|
profile: CognitiveRoutingProfileName;
|
|
1030
1526
|
}): Promise<CognitiveRoutingActivationResult> {
|
|
1031
|
-
const fromPair =
|
|
1527
|
+
const fromPair = this.observedHostPair();
|
|
1032
1528
|
if (!fromPair) return { status: "inactive", reason: "current_state_unavailable" };
|
|
1033
1529
|
const fromProfile = profileNameForPair(this.capabilityState, fromPair);
|
|
1034
1530
|
const correlationId = this.idFactory();
|
|
@@ -1069,7 +1565,7 @@ export class CognitiveRoutingController {
|
|
|
1069
1565
|
|
|
1070
1566
|
this.lease = acquisition.lease;
|
|
1071
1567
|
try {
|
|
1072
|
-
const result = await acquisition.lease
|
|
1568
|
+
const result = await this.setState(acquisition.lease, { ...options.target, correlationId: intent.correlationId });
|
|
1073
1569
|
if (result.status !== "applied" && result.status !== "unchanged") {
|
|
1074
1570
|
await this.releaseLease(acquisition.lease);
|
|
1075
1571
|
this.clearActiveState();
|
|
@@ -1079,6 +1575,8 @@ export class CognitiveRoutingController {
|
|
|
1079
1575
|
this.returnTarget = options.returnTarget;
|
|
1080
1576
|
this.activeProfile = options.profile;
|
|
1081
1577
|
this.controlMode = options.controlMode;
|
|
1578
|
+
this.nativeInactiveReason = undefined;
|
|
1579
|
+
this.nativeBlockedReason = undefined;
|
|
1082
1580
|
return { status: "active", profile: options.profile };
|
|
1083
1581
|
} catch {
|
|
1084
1582
|
await this.releaseLease(acquisition.lease);
|
|
@@ -1096,6 +1594,18 @@ export class CognitiveRoutingController {
|
|
|
1096
1594
|
return next;
|
|
1097
1595
|
}
|
|
1098
1596
|
|
|
1597
|
+
private async setState(
|
|
1598
|
+
lease: ModelStateLease,
|
|
1599
|
+
request: CognitiveRoutingPair & { correlationId: string },
|
|
1600
|
+
): Promise<{ status: string }> {
|
|
1601
|
+
this.transitionInFlight = true;
|
|
1602
|
+
try {
|
|
1603
|
+
return await lease.setState(request);
|
|
1604
|
+
} finally {
|
|
1605
|
+
this.transitionInFlight = false;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1099
1609
|
private async releaseLease(lease: ModelStateLease): Promise<void> {
|
|
1100
1610
|
try {
|
|
1101
1611
|
await lease.release();
|