@sangyaahub/pi-marg 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,17 @@
1
1
  import { Type } from "typebox";
2
2
 
3
3
  import {
4
+ FALLBACK_LEVELS,
4
5
  MODEL_STATE_TYPE,
5
6
  emptyModelRouteState,
7
+ executeAutomaticFailover,
6
8
  executeModelRoute,
9
+ failedModelSelector,
10
+ findTerminalUsageLimitFailure,
11
+ formatFailoverRecoveryMessage,
12
+ hasConfiguredFallback,
13
+ modelFailureKey,
14
+ modelToolUsageLimitFailure,
7
15
  restoreModelRouteState,
8
16
  type ModelLike,
9
17
  type ModelRouteParams,
@@ -26,22 +34,123 @@ function availableModels(ctx: any): ModelLike[] {
26
34
 
27
35
  export default function modelRouter(pi: any) {
28
36
  let state = emptyModelRouteState();
29
- pi.on("session_start", (_event: unknown, ctx: any) => {
37
+ let recoveryToken: symbol | undefined;
38
+ let sessionGeneration = 0;
39
+ let pendingModelFailure: { selector: string; key: string } | undefined;
40
+ let pendingToolFailure: { selector: string; key: string } | undefined;
41
+ const restore = (_event: unknown, ctx: any) => {
42
+ sessionGeneration += 1;
43
+ recoveryToken = undefined;
44
+ pendingModelFailure = undefined;
45
+ pendingToolFailure = undefined;
30
46
  state = restoreModelRouteState(ctx.sessionManager.getBranch());
47
+ };
48
+
49
+ pi.on("session_start", restore);
50
+ pi.on("session_tree", restore);
51
+
52
+ const recoverFromUsageLimit = async (failure: { selector: string; key: string }, ctx: any) => {
53
+ if (recoveryToken) return;
54
+ if (!hasConfiguredFallback(state)) return;
55
+ const generation = sessionGeneration;
56
+ const token = Symbol("pi-marg-recovery");
57
+ recoveryToken = token;
58
+ try {
59
+ const execution = await executeAutomaticFailover(state, failure.selector, {
60
+ runtimeName: "Pi",
61
+ models: availableModels(ctx),
62
+ hasExternalDevin: hasExternalDevin(pi),
63
+ activate: (model) => pi.setModel(model),
64
+ persist: (nextState) => {
65
+ if (sessionGeneration !== generation) throw new Error("session changed during automatic recovery");
66
+ pi.appendEntry(MODEL_STATE_TYPE, nextState);
67
+ },
68
+ }, failure.key);
69
+ if (sessionGeneration !== generation) {
70
+ ctx.ui?.notify?.(
71
+ "PiMarg stopped an in-flight recovery because the session changed. Verify the active model before continuing.",
72
+ "warning",
73
+ );
74
+ return;
75
+ }
76
+ if (execution.ignoredDuplicate) return;
77
+ state = execution.state;
78
+ if (!execution.switched) {
79
+ if (!execution.stateChanged) return;
80
+ ctx.ui?.notify?.(
81
+ "PiMarg could not resume automatically because no configured backup model remains. Re-run the model chooser after restoring provider access.",
82
+ "error",
83
+ );
84
+ if (execution.persistenceError) {
85
+ ctx.ui?.notify?.(
86
+ `PiMarg updated recovery state only in memory because the session ledger could not be saved: ${execution.persistenceError}`,
87
+ "error",
88
+ );
89
+ }
90
+ return;
91
+ }
92
+
93
+ const title = execution.switched.level === "high" ? "High" : "Low";
94
+ ctx.ui?.notify?.(
95
+ execution.reusedCurrent
96
+ ? `PiMarg kept the ${title} backup ${execution.switched.selection.selector} active after ${failure.selector} reached its limit and is resuming safely.`
97
+ : `PiMarg switched ${failure.selector} to the ${title} backup ${execution.switched.selection.selector} and is resuming automatically.`,
98
+ "warning",
99
+ );
100
+ if (execution.persistenceError) {
101
+ ctx.ui?.notify?.(
102
+ `The model switched, but PiMarg could not save the recovery state to the session ledger: ${execution.persistenceError}`,
103
+ "error",
104
+ );
105
+ }
106
+ pi.sendUserMessage(
107
+ formatFailoverRecoveryMessage(
108
+ state,
109
+ failure.selector,
110
+ execution.switched.level,
111
+ execution.switched.selection.selector,
112
+ execution.reusedCurrent,
113
+ ),
114
+ { deliverAs: "followUp" },
115
+ );
116
+ } finally {
117
+ if (recoveryToken === token) recoveryToken = undefined;
118
+ }
119
+ };
120
+
121
+ pi.on("agent_end", (event: any, ctx: any) => {
122
+ const failed = findTerminalUsageLimitFailure(event?.messages ?? []);
123
+ pendingModelFailure = failed
124
+ ? { selector: failedModelSelector(failed, ctx.model), key: modelFailureKey(failed) }
125
+ : undefined;
126
+ });
127
+
128
+ pi.on("tool_execution_end", (event: any) => {
129
+ pendingToolFailure = modelToolUsageLimitFailure(event) ?? pendingToolFailure;
130
+ });
131
+
132
+ pi.on("agent_settled", async (_event: unknown, ctx: any) => {
133
+ const failure = pendingModelFailure ?? pendingToolFailure;
134
+ pendingModelFailure = undefined;
135
+ pendingToolFailure = undefined;
136
+ if (!failure) return;
137
+ await recoverFromUsageLimit(failure, ctx);
31
138
  });
32
139
 
33
140
  pi.registerTool({
34
141
  name: "auto_model_route",
35
142
  label: "PiMarg Model Router",
36
- description: "Discover, select, and activate live A/B/C/D model choices while enforcing A different from C and B different from D.",
143
+ description: "Discover, select, and activate live A/B/C/D models plus distinct high/low automatic usage-limit backups.",
37
144
  parameters: Type.Object({
38
145
  action: Type.Union([
39
146
  Type.Literal("catalog"),
40
147
  Type.Literal("select"),
148
+ Type.Literal("select_fallback"),
41
149
  Type.Literal("activate"),
42
150
  Type.Literal("status"),
43
151
  ]),
44
152
  stage: Type.Optional(Type.Union([Type.Literal("A"), Type.Literal("B"), Type.Literal("C"), Type.Literal("D")])),
153
+ fallback: Type.Optional(Type.Union(FALLBACK_LEVELS.map((level) => Type.Literal(level)))),
45
154
  target: Type.Optional(Type.String()),
46
155
  workType: Type.Optional(Type.Number()),
47
156
  provider: Type.Optional(Type.String()),
@@ -1,12 +1,12 @@
1
1
  ---
2
- description: List or change the active Auto Mode A/B/C/D model selections from Pi's live authenticated catalog.
2
+ description: List or change A/B/C/D models and the high/low automatic recovery pair from Pi's live authenticated catalog.
3
3
  argument-hint: [status | choose]
4
4
  ---
5
5
 
6
6
  Load `auto-mode-router` and read its model-routing reference.
7
7
 
8
- - With no argument or `status`, call `auto_model_route` with `action: status`, then show the saved A/B/C/D selectors and separation checks.
9
- - With `choose`, call `auto_model_route` with `action: catalog` and the active work type. Present only required stages and exact available selectors. Save each answer with `action: select`.
8
+ - With no argument or `status`, call `auto_model_route` with `action: status`, then show the saved A/B/C/D selectors, separation checks, high/low backups, active fallback, and last failover.
9
+ - With `choose`, call `auto_model_route` with `action: catalog` and the active work type. Present required stages and exact available selectors. Save each stage with `action: select`, then save two distinct runtime models with `action: select_fallback`, first `fallback: high`, then `fallback: low`.
10
10
  - Do not type or persist a model ID that the live catalog did not return.
11
11
  - Treat any model returned by Pi's registry as a normal in-session route. Treat Devin or another external coding-agent session as a capability-gated external route; creating one requires `post-web` approval.
12
12
  - C/D are the authoritative independent review selectors. Any detected advisor or watcher is supplemental and must not weaken the A≠C or B≠D checks.
@@ -7,4 +7,4 @@ blocking: true
7
7
  autoloadSkills: [auto-mode-router]
8
8
  ---
9
9
 
10
- Complete only the intake and availability checks from `auto-mode-router`. Ask in the required order: work context, work type, skill mode, live A/B/C/D selections, then add-ons. Call `auto_runtime_status` before recommending runtime capabilities. Return the proposed `[AUTO-MODE]` state. Do not plan, implement, or change project files.
10
+ Complete only the intake and availability checks from `auto-mode-router`. Ask in the required order: work context, work type, skill mode, live A/B/C/D selections, high and low runtime backups, then add-ons. Call `auto_runtime_status` before recommending runtime capabilities. Return the proposed `[AUTO-MODE]` state. Do not plan, implement, or change project files.
@@ -1,12 +1,12 @@
1
1
  ---
2
- description: List or change the active Auto Mode A/B/C/D model selections from OMP's live authenticated catalog.
2
+ description: List or change A/B/C/D models and the high/low automatic recovery pair from OMP's live authenticated catalog.
3
3
  argument-hint: [status | choose]
4
4
  ---
5
5
 
6
6
  Load `auto-mode-router` and read its model-routing reference.
7
7
 
8
- - With no argument or `status`, call `auto_model_route` with `action: status`, then show the saved A/B/C/D selectors and separation checks.
9
- - With `choose`, call `auto_model_route` with `action: catalog` and the active work type. Present only required stages and exact available selectors. Save each answer with `action: select`.
8
+ - With no argument or `status`, call `auto_model_route` with `action: status`, then show the saved A/B/C/D selectors, separation checks, high/low backups, active fallback, and last failover.
9
+ - With `choose`, call `auto_model_route` with `action: catalog` and the active work type. Present required stages and exact available selectors. Save each stage with `action: select`, then save two distinct runtime models with `action: select_fallback`, first `fallback: high`, then `fallback: low`.
10
10
  - Do not type or persist a model ID that the live catalog did not return.
11
11
  - Distinguish native `devin/swe-*` OMP models from the optional external `devin/mcp-session` route. Only creating the external session requires `post-web` approval.
12
12
  - C/D are the authoritative independent review selectors. A configured OMP advisor is supplemental and must not weaken the A≠C or B≠D checks.