@@ -948,6 +948,7 @@ class ModularBridgeState {
948948 * replay it after a UI refresh mid-download.
949949 */
950950 private activePulls = new Map < string , EngineProgress > ( )
951+ private cancelingPulls = new Map < string , string > ( )
951952 /**
952953 * Authoritative remote-engine facts keyed by {@link remoteOpKey}
953954 * (`${nodeId}:${engineType}`). Populated by the supervisor from each
@@ -960,27 +961,6 @@ class ModularBridgeState {
960961 * evicted.
961962 */
962963 private remoteEngineFacts = new Map < string , RemoteEngineFacts > ( )
963- /**
964- * The model of an in-flight remote pull keyed by {@link remoteOpKey}. The
965- * backend's `engine:remote-progress` frames carry **no** `model` field
966- * (`nvpair-engine-manager/remote.go` `remoteProgress`), so we stamp the model
967- * we dispatched with here and backfill it onto every incoming frame — that
968- * keeps the optimistic entry, the streamed percent updates, and the terminal
969- * clear all on one {@link engineProgressKey}. Cleared by
970- * {@link finishRemoteModelPull}.
971- */
972- private remotePullModels = new Map < string , string > ( )
973- /**
974- * The model of an in-flight local pull keyed by `engineType`. The backend's
975- * `engine:pull-progress` frames (the local counterpart of
976- * `engine:remote-progress`) carry **no** `model` field
977- * (`nvpair-engine-manager/executor.go` `emitPullProgress`), so we stamp the
978- * model we dispatched here and backfill it onto every incoming frame — one
979- * active local pull per engine, matching the backend's engine-scoped
980- * progress hub. Set by {@link beginModelPull}, cleared by
981- * {@link finishModelPull}.
982- */
983- private localPullModels = new Map < EngineType , string > ( )
984964 /**
985965 * The authoritative set of live inbound invites awaiting the local user's PIN
986966 * entry, keyed by `inviteId`. The cluster-manager pushes `cluster:invite-received`
@@ -1492,9 +1472,6 @@ class ModularBridgeState {
14921472 model,
14931473 status : 'pulling'
14941474 }
1495- // Remember the model so the model-less `engine:remote-progress` frames can
1496- // be re-associated with this exact entry (see remotePullModels).
1497- this . remotePullModels . set ( this . remoteOpKey ( nodeId , engineType ) , model )
14981475 this . activePulls . set ( engineProgressKey ( progress ) , progress )
14991476 emitBridgePush ( 'engines:progress-changed' , progress )
15001477 }
@@ -1515,8 +1492,8 @@ class ModularBridgeState {
15151492 * is the only signal that removes the spinner.
15161493 */
15171494 finishRemoteModelPull ( nodeId : string , engineType : EngineType , model : string ) : void {
1518- this . remotePullModels . delete ( this . remoteOpKey ( nodeId , engineType ) )
15191495 const key = engineProgressKey ( { nodeId, engineType, operation : 'pull' , model } )
1496+ this . cancelingPulls . delete ( key )
15201497 if ( ! this . activePulls . delete ( key ) ) return
15211498 emitBridgePush ( 'engines:progress-cleared' , { key } )
15221499 }
@@ -1531,28 +1508,22 @@ class ModularBridgeState {
15311508 const op = stringValue ( obj . op )
15321509 const operation = op === 'pull' || op === 'pull_model' ? 'pull' : 'install'
15331510 const stage = stringValue ( obj . stage ) || 'working'
1534- // `engine:remote-progress` carries no `model`, so for a pull we backfill
1535- // the model captured at dispatch — otherwise the frame would emit under a
1536- // different key than the optimistic entry and never update/clear it.
1537- const model =
1538- stringValue ( obj . model ) ||
1539- ( operation === 'pull'
1540- ? ( this . remotePullModels . get ( this . remoteOpKey ( nodeId , engineType ) ) ?? '' )
1541- : '' )
1511+ // The peer stamps the model on every pull frame it relays
1512+ // (`nvpair-engine-manager/remote.go` `remoteProgressFn`), which is what
1513+ // keys a frame to its optimistic entry. An install carries none, and
1514+ // needs none: its progress is tracked per engine.
1515+ const model = stringValue ( obj . model )
15421516
15431517 if ( stage === 'done' || stage === 'already-installed' || stage === 'failed' ) {
15441518 const progressKey =
15451519 operation === 'pull' && model
15461520 ? engineProgressKey ( { nodeId, engineType, operation : 'pull' , model } )
15471521 : `${ nodeId } :${ engineType } :${ operation } `
15481522 emitBridgePush ( 'engines:progress-cleared' , { key : progressKey } )
1549- if ( operation === 'pull' ) {
1550- this . remotePullModels . delete ( this . remoteOpKey ( nodeId , engineType ) )
1551- if ( model ) {
1552- this . activePulls . delete (
1553- engineProgressKey ( { nodeId, engineType, operation : 'pull' , model } )
1554- )
1555- }
1523+ if ( operation === 'pull' && model ) {
1524+ this . activePulls . delete (
1525+ engineProgressKey ( { nodeId, engineType, operation : 'pull' , model } )
1526+ )
15561527 }
15571528 if ( stage === 'already-installed' || stage === 'failed' ) {
15581529 this . clearRemoteEngineOp ( nodeId , engineType )
@@ -1590,7 +1561,7 @@ class ModularBridgeState {
15901561 nodeId,
15911562 nodeName : node ?. name ?? nodeId ,
15921563 operation,
1593- status : stage ,
1564+ status : this . cancelingPulls . has ( pullKey ) ? 'canceling' : stage ,
15941565 percent,
15951566 model : model || undefined
15961567 }
@@ -1820,7 +1791,6 @@ class ModularBridgeState {
18201791 private dropRemoteEngineFacts ( nodeId : string ) : void {
18211792 for ( const engine of PROXY_ENGINES ) {
18221793 this . remoteEngineFacts . delete ( this . remoteOpKey ( nodeId , engine ) )
1823- this . remotePullModels . delete ( this . remoteOpKey ( nodeId , engine ) )
18241794 }
18251795 }
18261796
@@ -2180,9 +2150,6 @@ class ModularBridgeState {
21802150 model,
21812151 status : 'pulling'
21822152 }
2183- // Remember the model so the model-less `engine:pull-progress` frames can
2184- // be re-associated with this exact entry (see localPullModels).
2185- this . localPullModels . set ( engineType , model )
21862153 this . activePulls . set ( engineProgressKey ( progress ) , progress )
21872154 emitBridgePush ( 'engines:progress-changed' , progress )
21882155 }
@@ -2191,12 +2158,13 @@ class ModularBridgeState {
21912158 * Project a `nvpair-engine-manager` `engine:pull-progress` for the local node
21922159 * — the local counterpart of `engine:remote-progress`: live
21932160 * download progress for a model pull driven via `engine:action`
2194- * `pull_model`. The frame carries `{ engine, op, stage, percent, message }`
2195- * and **no** `model`, so we backfill the model captured at dispatch and
2196- * refresh the optimistic entry in place. Clearing stays with the awaited pull
2197- * ({@link finishModelPull}), so this only advances percent/stage: it ignores
2198- * the terminal error sentinel (`percent: -1`) and the non-download frames'
2199- * `percent: 0` so the rendered bar never jumps backwards.
2161+ * `pull_model`. The frame carries `{ engine, model, op, stage, percent,
2162+ * message }` and its `model` identifies which optimistic entry to refresh —
2163+ * the backend queues concurrent pulls per engine, so the engine alone does
2164+ * not. Clearing stays with the awaited pull ({@link finishModelPull}), so
2165+ * this only advances percent/stage: it ignores the terminal error sentinel
2166+ * (`percent: -1`) and the non-download frames' `percent: 0` so the rendered
2167+ * bar never jumps backwards.
22002168 */
22012169 applyLocalEngineProgress ( params : JsonValue | undefined ) : void {
22022170 const nodeId = this . selfId
@@ -2206,7 +2174,7 @@ class ModularBridgeState {
22062174 const engineType = engineTypeFromManagerName ( stringValue ( obj . engine ) )
22072175 if ( ! engineType ) return
22082176
2209- const model = this . localPullModels . get ( engineType )
2177+ const model = stringValue ( obj . model )
22102178 if ( ! model ) return
22112179 const key = engineProgressKey ( { nodeId, engineType, operation : 'pull' , model } )
22122180 const existing = this . activePulls . get ( key )
@@ -2215,7 +2183,9 @@ class ModularBridgeState {
22152183 const percent = numberValue ( obj . percent )
22162184 const progress : EngineProgress = {
22172185 ...existing ,
2218- status : stringValue ( obj . stage ) || existing . status ,
2186+ status : this . cancelingPulls . has ( key )
2187+ ? 'canceling'
2188+ : stringValue ( obj . stage ) || existing . status ,
22192189 percent : mergePullProgressPercent ( percent , existing . percent )
22202190 }
22212191 this . activePulls . set ( key , progress )
@@ -2235,12 +2205,31 @@ class ModularBridgeState {
22352205 finishModelPull ( engineType : EngineType , model : string ) : void {
22362206 const nodeId = this . selfId
22372207 if ( ! nodeId ) return
2238- this . localPullModels . delete ( engineType )
22392208 const key = engineProgressKey ( { nodeId, engineType, operation : 'pull' , model } )
2209+ this . cancelingPulls . delete ( key )
22402210 if ( ! this . activePulls . delete ( key ) ) return
22412211 emitBridgePush ( 'engines:progress-cleared' , { key } )
22422212 }
22432213
2214+ setModelPullCanceling (
2215+ engineType : EngineType ,
2216+ model : string ,
2217+ canceling : boolean ,
2218+ nodeId = this . selfId
2219+ ) : boolean {
2220+ if ( ! nodeId ) return false
2221+ const key = engineProgressKey ( { nodeId, engineType, operation : 'pull' , model } )
2222+ const existing = this . activePulls . get ( key )
2223+ if ( ! existing || ( canceling && this . cancelingPulls . has ( key ) ) ) return false
2224+ const status = canceling ? 'canceling' : ( this . cancelingPulls . get ( key ) ?? existing . status )
2225+ if ( canceling ) this . cancelingPulls . set ( key , existing . status )
2226+ else this . cancelingPulls . delete ( key )
2227+ const progress = { ...existing , status }
2228+ this . activePulls . set ( key , progress )
2229+ emitBridgePush ( 'engines:progress-changed' , progress )
2230+ return true
2231+ }
2232+
22442233 getLogs ( ) : LogEntry [ ] {
22452234 return [ ...this . logs ]
22462235 }
0 commit comments