@topolo/cli 0.13.9 → 0.13.10

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.
@@ -275,7 +275,7 @@ def request(lib,operation,params=None):
275
275
  record=read_json(lib.root/'live-bridge.json',{})
276
276
  if not record: raise PackError('This desktop profile has no live bridge. Open it through topolo-agent-pack first.')
277
277
  with socket.socket(socket.AF_UNIX,socket.SOCK_STREAM) as connection:
278
- connection.settimeout(3 if operation=='status' else 60 if operation in {'permissions-configure','owned-roster-read','smoke-start'} else 20)
278
+ connection.settimeout(3 if operation=='status' else 240 if operation.startswith('lane-') else 60 if operation in {'permissions-configure','owned-roster-read','smoke-start'} else 20)
279
279
  try: connection.connect(record['socket'])
280
280
  except OSError as exc: raise PackError('Managed live bridge is unavailable: '+str(exc)) from exc
281
281
  connection.sendall(canonical({'token':record['token'],'operation':operation,**({'params':params} if params is not None else {})})+b'\n')
@@ -315,8 +315,15 @@ class BackendProxy:
315
315
  self.permission_batch=None
316
316
  self._permission_configuration_inputs=None
317
317
  self.smoke_busy=False
318
+ self.lane_busy=False
319
+ self.emit_native=True
320
+ self.lane_connection_binding=None
321
+ self.managed_transport=None
322
+ self._lane_configuration_record=None
318
323
 
319
324
  async def start_child(self):
325
+ if self.lane_connection_binding:
326
+ raise PackError('Managed dynamic lanes require the existing native owner; no alternate app-server startup')
320
327
  active=self.lib.active()
321
328
  if not active.get('runtime'): raise PackError('No selected managed runtime')
322
329
  self.runtime=active['runtime'];env=os.environ.copy()
@@ -359,6 +366,7 @@ class BackendProxy:
359
366
  method=value.get('method');params=value.get('params') or {}
360
367
  if self.approvals:
361
368
  record=self.approvals.capture(value)
369
+ if record:self.dynamic_permission_event(record)
362
370
  if record and record['sourceThreadId']!=record['intakeThreadId'] and record['notificationState']!='delivered':
363
371
  self.schedule_notification(record)
364
372
  if method=='turn/started': self.active_turns.add(params['threadId'])
@@ -370,7 +378,7 @@ class BackendProxy:
370
378
  for row in self.approvals.records():
371
379
  if row['nativeConnectionCurrent'] and row['intakeThreadId']==params['threadId'] and row['notificationState']!='delivered':
372
380
  self.schedule_notification(row)
373
- sys.stdout.buffer.write(line);sys.stdout.buffer.flush()
381
+ if self.emit_native:sys.stdout.buffer.write(line);sys.stdout.buffer.flush()
374
382
 
375
383
  async def stop_child(self):
376
384
  if self.child and self.child.returncode is None:
@@ -406,7 +414,12 @@ class BackendProxy:
406
414
  if 'id' in value and 'method' in value:self.pending.add(str(value['id']))
407
415
  if self.approvals and 'method' not in value and ('result' in value or 'error' in value):self.approvals.sending(value)
408
416
  self.child.stdin.write(canonical(value)+b'\n');await self.child.stdin.drain()
409
- if self.approvals and 'method' not in value and ('result' in value or 'error' in value):self.approvals.settled(value)
417
+ if self.approvals and 'method' not in value and ('result' in value or 'error' in value):
418
+ entry=self.approvals.pending.get(str(value['id']))
419
+ self.approvals.settled(value)
420
+ decision=(value.get('result') or {}).get('decision')
421
+ if entry and decision in {'accept','decline','cancel'}:
422
+ self.dynamic_permission_event(entry['record'],'approved' if decision=='accept' else 'denied')
410
423
  if self.execution and value.get('method')=='initialized':
411
424
  await self.request_catalog()
412
425
 
@@ -417,6 +430,8 @@ class BackendProxy:
417
430
  await self.child.stdin.drain()
418
431
 
419
432
  async def call_native(self,method,params,before_dispatch=None):
433
+ if self.managed_transport:
434
+ return await self.call_managed_native(method,params,before_dispatch)
420
435
  identity='topolo-relay-'+uuid.uuid4().hex
421
436
  request={'id':identity,'method':method,'params':params}
422
437
  if method in {'thread/resume','turn/start'}:
@@ -441,6 +456,49 @@ class BackendProxy:
441
456
  raise PackError('Native intake relay refused; original request remains pending')
442
457
  return reply['result']
443
458
 
459
+ async def call_managed_native(self,method,params,before_dispatch=None):
460
+ identity='topolo-managed-'+uuid.uuid4().hex
461
+ request={'id':identity,'method':method,'params':params}
462
+ intake=next((r for r in self.execution.roles.values() if r['key']==self.lane_connection_binding['intakeKey']),None)
463
+ intake_notification=method=='turn/start' and intake and params.get('threadId')==intake['threadId']
464
+ if method in {'thread/resume','turn/start'}:
465
+ await self.qualify_organization(params['threadId'])
466
+ if not intake_notification:request=self.execution.prepare(request)
467
+ elif set(params)!={'threadId','input'}:
468
+ raise PackError('Original intake native settings must remain unchanged')
469
+ if before_dispatch is not None:before_dispatch()
470
+ self.pending.add(identity)
471
+ try:
472
+ result=await self.managed_transport.evaluate('request',{'method':method,'params':request['params']})
473
+ reply=self.execution.reply({'id':identity,'result':result})
474
+ return reply['result']
475
+ finally:self.pending.discard(identity)
476
+
477
+ async def managed_native_events(self):
478
+ while not self.managed_transport.closed:
479
+ messages=await self.managed_transport.evaluate('next',{},events=True)
480
+ if not isinstance(messages,list):
481
+ raise PackError('Actual managed native event batch differs')
482
+ for value in messages:
483
+ if value.get('method')=='topolo/managed-request-unavailable':
484
+ raise PackError('Actual managed own-child request is unavailable; preserve custody')
485
+ record=self.approvals.capture(value)
486
+ if record:
487
+ self.dynamic_permission_event(record)
488
+ self.schedule_notification(record)
489
+ params=value.get('params') or {};method=value.get('method')
490
+ if method=='turn/started':self.active_turns.add(params['threadId'])
491
+ elif method=='turn/completed':self.active_turns.discard(params['threadId'])
492
+ elif method=='thread/status/changed':
493
+ if params.get('status',{}).get('type')=='active':self.active_turns.add(params['threadId'])
494
+ elif params.get('status',{}).get('type') in {'idle','notLoaded','systemError'}:
495
+ self.active_turns.discard(params['threadId'])
496
+
497
+ async def send_native_approval(self,entry,reply):
498
+ if self.managed_transport:
499
+ return await self.managed_transport.evaluate('approval',{'request':entry['message'],'reply':reply})
500
+ self.child.stdin.write(canonical(reply)+b'\n');await self.child.stdin.drain()
501
+
444
502
  def smoke_current(self,roles,resuming=None):
445
503
  if (not self.execution or self.permission_configuration or self.quiesced or not self.initialized or self.closed or
446
504
  not self.child or self.child.returncode is not None or self.active_turns or self.pending or self.notifications or
@@ -557,20 +615,33 @@ class BackendProxy:
557
615
  from . import topolo
558
616
  consent=role['installationConsent']
559
617
  agents=self.lib.load(consent['pack'])[0]['agents']
560
- matches=[a for a in agents if '/'.join([a['pack'],a['set'],a['id']])==role['key'] and a['threadId']==role['threadId']]
618
+ matches=[a for a in agents if '/'.join([a['pack'],a['set'],a['id']])==role.get('sourceRoleKey',role['key']) and a['threadId']==role.get('sourceRoleNativeId',role['threadId'])]
561
619
  if len(matches)!=1:
562
620
  raise PackError('Current consent has no unique live owned role')
563
- if role['settings']!=permission_settings(matches[0],role['settings'],consent):
621
+ effective_consent=consent
622
+ if role.get('dynamicVerificationProfile')=='manual':
623
+ effective_consent={**consent,'profile':'manual','overrides':{}}
624
+ if role['settings']!=permission_settings(matches[0],role['settings'],effective_consent):
564
625
  raise PackError('Effective native permission intent differs from the current consent profile')
565
626
  if self.permission_batch and self.permission_batch['consent']==consent:
566
627
  if time.monotonic()>=self.permission_batch['deadline']:
567
628
  raise PackError('Qualified native permission batch expired; reconcile without replay')
568
629
  role['organizationPolicyProof']=self.permission_batch['proof'];return
569
- _,proof=await asyncio.to_thread(topolo.organization_execution_policy,
630
+ remote,proof=await asyncio.to_thread(topolo.organization_execution_policy,
570
631
  topolo.Client(consent['environment'],consent['contextKey']),consent,agents,consent.get('agentWorkspaceId'))
571
632
  from .permissions import authorize_native_scope
572
633
  authorize_native_scope(proof)
634
+ if role.get('dynamicVerificationProfile')=='manual' and 'manual' not in remote['allowedProfiles']:
635
+ raise PackError('Fresh organization policy prohibits the child-only manual verification profile')
573
636
  role['organizationPolicyProof']=proof
637
+ if role.get('dynamicLaneId'):
638
+ from .lanes import Registry
639
+ record=Registry(self.lib.root,'codex').read(role['dynamicLaneId'])
640
+ current=await self.dynamic_current(record['plan'])
641
+ configuring=self._lane_configuration_record
642
+ if configuring and configuring['nativeId']==thread_id and self.lane_busy:
643
+ self.execution.prepare_dynamic_configuration(configuring,current)
644
+ else:self.execution.qualify_dynamic(record,current)
574
645
 
575
646
  def existing_runtime_intent(self):
576
647
  """Full existing composition only; never use a new composition as migration."""
@@ -588,7 +659,12 @@ class BackendProxy:
588
659
  declared={(r['key'],r['threadId']) for r in document['roles']}
589
660
  with closing(sqlite3.connect('file:'+str(pathlib.Path(self.runtime)/'home/state_5.sqlite')+'?mode=ro',uri=True)) as db:
590
661
  native_ids=[r[0] for r in db.execute('SELECT id FROM threads')]
591
- if len(native_ids)!=len(declared) or set(native_ids)!={tid for _,tid in declared}:
662
+ from .lanes import Registry
663
+ dynamic=[r for r in Registry(self.lib.root,'codex').records() if r['state']!='retired']
664
+ dynamic_ids={r['nativeId'] for r in dynamic}
665
+ if dynamic_ids & {tid for _,tid in declared} or len(dynamic_ids)!=len(dynamic):
666
+ raise PackError('Dynamic identity overlaps the fixed roster')
667
+ if len(native_ids)!=len(declared)+len(dynamic_ids) or set(native_ids)!={tid for _,tid in declared}|dynamic_ids:
592
668
  raise PackError('Native runtime membership differs from full selected composition')
593
669
  if identities(roster)!=declared:
594
670
  raise PackError('Runtime roster differs from selected source membership')
@@ -773,7 +849,7 @@ class BackendProxy:
773
849
  state=await self.call_native('thread/read',{'threadId':record['intakeThreadId'],'includeTurns':False})
774
850
  if state.get('thread',{}).get('status',{}).get('type') not in {'idle','notLoaded'}:
775
851
  self.approvals.notified(record['requestId'],False);return
776
- if record['intakeThreadId'] not in self.execution.verified:
852
+ if not self.managed_transport and record['intakeThreadId'] not in self.execution.verified:
777
853
  await self.call_native('thread/resume',{'threadId':record['intakeThreadId']})
778
854
  self.approvals.live(record['key'],record['intakeThreadId'],record['fingerprint'])
779
855
  # A lost turn/start reply cannot be retried on the next idle event.
@@ -789,8 +865,457 @@ class BackendProxy:
789
865
  finally:self.notifications.discard(record['key'])
790
866
  asyncio.create_task(deliver())
791
867
 
868
+ def dynamic_history(self, identity):
869
+ with closing(sqlite3.connect('file:'+str(pathlib.Path(self.runtime)/'home/state_5.sqlite')+'?mode=ro',uri=True)) as db:
870
+ row=db.execute('SELECT rollout_path FROM threads WHERE id=?',(identity,)).fetchone()
871
+ if not row:
872
+ raise PackError('Native transcript identity is missing')
873
+ path=pathlib.Path(row[0]);OwnedSmoke.owned(path)
874
+ if not path.resolve().is_relative_to(pathlib.Path(self.runtime).resolve()/'home'):
875
+ raise PackError('Native transcript is outside the owned home')
876
+ return hashlib.sha256(path.read_bytes()).hexdigest()
877
+
878
+ def dynamic_permission_event(self, approval, decision=None):
879
+ from . import lanes
880
+ role=self.execution.roles.get(approval['sourceThreadId']) if self.execution else None
881
+ if not role or not role.get('dynamicLaneId'):
882
+ return
883
+ store=lanes.Registry(self.lib.root,'codex');record=store.read(role['dynamicLaneId'])
884
+ if decision is None:
885
+ prior=record.get('permissionRequest')
886
+ if prior and prior['fingerprint']==approval['fingerprint'] and prior['requestId']==str(approval['requestId']):
887
+ return
888
+ changed=lanes.request_permission(record,approval['fingerprint'],str(approval['requestId']),approval['generation'])
889
+ changed['permissionRequest'].update(turnId=approval.get('turnId'),itemId=approval.get('itemId'))
890
+ else:
891
+ changed=lanes.record_decision(record,decision,approval['intakeKey'],approval['fingerprint'],approval['generation'])
892
+ store.write(changed,lanes.digest(record))
893
+
894
+ async def dynamic_outcome(self, record):
895
+ from . import lanes
896
+ if record['state']=='executed-outcome-verified':
897
+ return record
898
+ if record['state']!='permission-decision-forwarded':
899
+ raise PackError('Lane settlement needs a real native request, intake decision and operation outcome')
900
+ request=record['permissionRequest']
901
+ result=await self.call_native('thread/read',{'threadId':record['nativeId'],'includeTurns':True})
902
+ if result.get('thread',{}).get('id')!=record['nativeId']:
903
+ raise PackError('Native outcome thread identity differs')
904
+ turns=[t for t in result.get('thread',{}).get('turns',[]) if t.get('id')==request.get('turnId')]
905
+ if len(turns)!=1 or turns[0].get('status') not in {'completed','failed','interrupted'}:
906
+ raise PackError('Exact native permission turn has not reached a terminal outcome')
907
+ items=[i for i in turns[0].get('items',[]) if i.get('id')==request.get('itemId')]
908
+ if len(items)!=1 or items[0].get('type') not in {'commandExecution','fileChange'}:
909
+ raise PackError('Native permission operation outcome cannot be independently attributed')
910
+ item=items[0];status=item.get('status')
911
+ if record['permissionDecision']=='denied':
912
+ if status!='declined':
913
+ raise PackError('Denied native request has no declined operation ACK')
914
+ state='denied'
915
+ elif status=='completed' and (item['type']!='commandExecution' or item.get('exitCode')==0): state='completed'
916
+ elif status=='failed' or (item['type']=='commandExecution' and status=='completed' and item.get('exitCode')!=0): state='failed'
917
+ else: raise PackError('Native permission outcome is unresolved')
918
+ return lanes.record_outcome(record,{'nativeId':record['nativeId'],'requestFingerprint':request['fingerprint'],
919
+ 'state':state,'verificationDigest':lanes.digest({'turn':turns[0],'item':item})})
920
+
921
+ def lane_idle(self):
922
+ if (not self.execution or not self.approvals or not self.initialized or self.quiesced or self.closed or
923
+ (not self.managed_transport and (not self.child or self.child.returncode is not None)) or
924
+ (self.managed_transport and not self.managed_transport.ready) or self.permission_configuration or self.smoke_busy):
925
+ raise PackError('Dynamic lanes require their current qualified native connection')
926
+
927
+ def dynamic_role(self, row):
928
+ """Collect identity refs from the supported connection, never caller JSON."""
929
+ from . import topolo, lanes
930
+ consent=row.get('installationConsent')
931
+ if (not isinstance(consent,dict) or not isinstance(consent.get('contextKey'),str) or
932
+ not consent['contextKey'].startswith('org:') or consent['contextKey']!=row.get('contextKey') or
933
+ consent.get('environment') not in {'development','staging','production'}):
934
+ raise PackError('Dynamic lanes need an organization-bound source installation')
935
+ pack=row['key'].split('/')[0];logical='/'.join(row['key'].split('/')[1:])
936
+ cwd=pathlib.Path(row['cwd']);home=pathlib.Path(self.runtime).resolve()
937
+ if not cwd.resolve().is_relative_to(home):
938
+ raise PackError('Dynamic source workspace is outside the selected native runtime')
939
+ files=[cwd/'AGENTS.md',cwd/'topolo-connection.json',cwd/'.mcp.json']
940
+ for file in files: OwnedSmoke.owned(file)
941
+ hashes={str(f):hashlib.sha256(f.read_bytes()).hexdigest() for f in files}
942
+ descriptor=read_json(files[1]);binding=read_json(topolo.binding_path(self.lib,pack,consent['environment'],consent['contextKey']))
943
+ roles=[r for r in binding['input']['roles'] if r['logicalId']==logical]
944
+ identity=binding['identity']
945
+ if (len(roles)!=1 or identity['organizationId']!=consent['contextKey'][4:] or
946
+ any(descriptor.get(k)!=v for k,v in {'pack':pack,'role':logical,'environment':consent['environment'],
947
+ 'contextKey':consent['contextKey'],'instanceId':binding['input']['instanceId'],
948
+ 'agentWorkspaceId':binding['agentWorkspaceId'],'bytesWorkspaceId':binding['input']['bytesWorkspaceId'],
949
+ 'agentId':roles[0]['agentId'],'agentVersionId':roles[0]['agentVersionId'],
950
+ 'identity':{key:identity[key] for key in ['userId','organizationId']}}.items())):
951
+ raise PackError('Hosted role, Bytes and organization descriptors differ')
952
+ return {'key':row['key'],'harness':'codex','nativeId':row['threadId'],'cwd':row['cwd'],
953
+ 'settings':row['settings'],'contextDigest':lanes.digest(hashes),'packId':pack,'packRevision':row['sourceRevision'],
954
+ 'contextKey':consent['contextKey'],'organizationId':identity['organizationId'],'environment':consent['environment'],
955
+ 'installationId':binding['input']['instanceId'],'AgentLogicalId':roles[0]['agentId'],
956
+ 'AgentVersionId':roles[0]['agentVersionId'],'AgentWorkspaceId':binding['agentWorkspaceId'],
957
+ 'BytesWorkspaceId':binding['input']['bytesWorkspaceId'],'privateMCPDescriptorHash':hashes[str(files[2])]}
958
+
959
+ def dynamic_model_inputs(self, source):
960
+ """Explicit versioned pack/group/role model constraints; no inferred defaults."""
961
+ path=self.lib.root/'dynamic-models'/(source['packId']+'.json');OwnedSmoke.owned(path)
962
+ document=read_json(path)
963
+ if (set(document)!={'format','schemaVersion','sourceRevision','configuration','constraints','authorizedOverrides'} or
964
+ document['format']!='topolo-pack-dynamic-models' or document['schemaVersion']!=1 or
965
+ document['sourceRevision']!=source['packRevision']):
966
+ raise PackError('Versioned dynamic model configuration is missing for this exact source role')
967
+ from .task_models import role_constraints
968
+ document={**document,'effectiveConstraints':role_constraints(document['constraints'],source['key'])}
969
+ catalog={'harness':'codex','source':'native-account','connectionGeneration':self.approvals.generation,
970
+ 'models':[{'model':m,'efforts':sorted(e)} for m,e in sorted(self.execution.catalog.items())]}
971
+ return document,catalog
972
+
973
+ async def dynamic_current(self, plan):
974
+ self.lane_idle()
975
+ source=self.execution.roles.get(plan['role']['nativeId']);intake=self.execution.roles.get(plan['intake']['nativeId'])
976
+ if not source or not intake or source.get('dynamicLaneId') or intake.get('dynamicLaneId'):
977
+ raise PackError('Dynamic source role or original intake is not currently owned')
978
+ await self.qualify_organization(source['threadId'])
979
+ self.lane_idle()
980
+ role=self.dynamic_role(source);route=self.dynamic_role(intake);proof=source['organizationPolicyProof']
981
+ consent=source['installationConsent']
982
+ ceiling={'policyRevision':proof['revision'],'policyDigest':proof['policyDigest'],
983
+ 'profile':consent.get('overrides',{}).get(source['key'],consent['profile']),'intakeKey':source['intakeKey']}
984
+ document,catalog=self.dynamic_model_inputs(role)
985
+ from . import task_models,lanes
986
+ task=plan['resolution']['task']
987
+ resolution=task_models.resolve(document['configuration'],task,'codex',catalog,document['effectiveConstraints'],
988
+ override_authorized=lanes.digest(task) in document['authorizedOverrides'])
989
+ if resolution!=plan['resolution']:
990
+ raise PackError('Task profile, native catalog, intake override or configuration changed; no fallback')
991
+ return {'role':role,'intake':route,'ceiling':ceiling}
992
+
993
+ async def dynamic_native(self, identity, cwd):
994
+ result=await self.call_native('thread/read',{'threadId':identity,'includeTurns':False})
995
+ thread=result.get('thread',{})
996
+ with closing(sqlite3.connect('file:'+str(pathlib.Path(self.runtime)/'home/state_5.sqlite')+'?mode=ro',uri=True)) as db:
997
+ rows=db.execute('SELECT id,cwd,archived FROM threads WHERE id=?',(identity,)).fetchall()
998
+ if rows!=[(identity,cwd,0)] or thread.get('id')!=identity or thread.get('cwd')!=cwd:
999
+ raise PackError('Dynamic native descriptor is not the exact active owned runtime identity')
1000
+ return {'nativeId':identity,'cwd':cwd,'nativeParentId':None,'archived':False,'backend':'local',
1001
+ 'foreignWorkspace':False,'busy':thread.get('status',{}).get('type') not in {'idle','notLoaded'},
1002
+ 'pendingApprovals':sum(e['record']['sourceThreadId']==identity for e in self.approvals.pending.values())}
1003
+
1004
+ async def dynamic_context(self,operation,params):
1005
+ from . import lanes
1006
+ import shlex
1007
+ expected_fields={'laneId','operationId'} if operation=='lane-context-start' else {'laneId'}
1008
+ if set(params)!=expected_fields:
1009
+ raise PackError('Closed own-child context operation required')
1010
+ self.lane_idle();store=lanes.Registry(self.lib.root,'codex');record=store.read(params['laneId'])
1011
+ if not record or record['state']!='loaded-context-ACK':
1012
+ raise PackError('Actual native settings and instruction-source ACK required before bootstrap')
1013
+ current=await self.dynamic_current(record['plan'])
1014
+ if current!={k:record['plan'][k] for k in ['role','intake','ceiling']}:
1015
+ raise PackError('Current bootstrap source or policy differs')
1016
+ native=await self.dynamic_native(record['nativeId'],record['creationReceipt']['cwd'])
1017
+ if native['busy'] or native['pendingApprovals']:
1018
+ return {'state':'awaiting-own-native-idle','native':native}
1019
+ if operation=='lane-context-read':
1020
+ probe=record.get('contextProbe')
1021
+ if not probe or probe['state']!='native-delivered':
1022
+ raise PackError('Exact own bootstrap delivery is unresolved; no replay')
1023
+ result=await self.call_native('thread/read',{'threadId':record['nativeId'],'includeTurns':True})
1024
+ thread=result.get('thread',{});turns=[t for t in thread.get('turns',[]) if t.get('id')==probe['turnId']]
1025
+ if thread.get('id')!=record['nativeId'] or len(turns)!=1 or turns[0].get('status')!='completed' or turns[0].get('error'):
1026
+ raise PackError('Exact same-child bootstrap turn is not completed')
1027
+ reads=[]
1028
+ for item in turns[0].get('items',[]):
1029
+ if item.get('type')!='commandExecution' or item.get('status')!='completed' or item.get('exitCode')!=0:
1030
+ continue
1031
+ command=item.get('command','');args=shlex.split(command) if isinstance(command,str) else []
1032
+ exact=command==probe['command'] or (len(args)==3 and pathlib.Path(args[0]).name in {'sh','zsh','bash'} and args[1] in {'-c','-lc'} and args[2]==probe['command'])
1033
+ if not exact:
1034
+ continue
1035
+ try:value=json.loads(item.get('aggregatedOutput',''))
1036
+ except (ValueError,TypeError):continue
1037
+ if value==probe['expected'] and item.get('id'):
1038
+ reads.append(item['id'])
1039
+ if len(reads)!=1:
1040
+ raise PackError('Actual same-child native file-read command and exact metadata result are unproven')
1041
+ if any(hashlib.sha256(pathlib.Path(path).read_bytes()).hexdigest()!=sha for path,sha in probe['fileHashes'].items()):
1042
+ raise PackError('Own role context changed during native bootstrap')
1043
+ updated={**record,'contextProbe':{k:v for k,v in probe.items() if k not in {'text','command'}}|
1044
+ {'state':'native-file-read-ACK','nativeItemId':reads[0],'proofDigest':lanes.digest({'turnId':probe['turnId'],'itemId':reads[0],'expected':probe['expected']})}}
1045
+ return store.write(updated,lanes.digest(record))
1046
+ if record.get('contextProbe'):
1047
+ raise PackError('Owned native bootstrap is already reserved; no second turn')
1048
+ nonce=OwnedSmoke.operation_id(params['operationId']);cwd=pathlib.Path(record['creationReceipt']['cwd'])
1049
+ guide=cwd/'AGENTS.md';connection=cwd/'topolo-connection.json'
1050
+ for path in [guide,connection]:OwnedSmoke.owned(path)
1051
+ descriptor=read_json(connection);role=record['plan']['role'];principal=descriptor.get('identity',{}).get('userId')
1052
+ if not isinstance(principal,str) or not principal:
1053
+ raise PackError('Current connected role principal is unknown')
1054
+ expected={'operationId':nonce,'nativeId':record['nativeId'],'AgentLogicalId':role['AgentLogicalId'],'AgentVersionId':role['AgentVersionId'],
1055
+ 'AgentWorkspaceId':role['AgentWorkspaceId'],'BytesWorkspaceId':role['BytesWorkspaceId'],'principalId':principal,
1056
+ 'organizationId':role['organizationId'],'environment':role['environment'],'charterSHA256':hashlib.sha256(guide.read_bytes()).hexdigest()}
1057
+ script=("import pathlib,json,hashlib;d=json.loads(pathlib.Path("+repr(str(connection))+").read_text());"
1058
+ "v={'operationId':"+repr(nonce)+",'nativeId':"+repr(record['nativeId'])+",'AgentLogicalId':d['agentId'],'AgentVersionId':d['agentVersionId'],"
1059
+ "'AgentWorkspaceId':d['agentWorkspaceId'],'BytesWorkspaceId':d['bytesWorkspaceId'],'principalId':d['identity']['userId'],"
1060
+ "'organizationId':d['identity']['organizationId'],'environment':d['environment'],'charterSHA256':hashlib.sha256(pathlib.Path("+repr(str(guide))+").read_bytes()).hexdigest()};print(json.dumps(v))")
1061
+ command='python3 -c '+shlex.quote(script)
1062
+ text=('Bounded own-child context bootstrap '+nonce+'. Before role work, read only your own AGENTS.md and topolo-connection.json. '
1063
+ 'Use the native exec_command tool to run exactly this read-only metadata command: '+command+'. '
1064
+ 'It outputs only public role identities and a charter hash. Do not write files, access credentials, use the network, or perform other work. '
1065
+ 'Return the same JSON object. This file-read proof does not verify private MCP connectivity.')
1066
+ probe={'operationId':nonce,'expected':expected,'command':command,'text':text,'state':'start-may-have-effects',
1067
+ 'fileHashes':{str(p):hashlib.sha256(p.read_bytes()).hexdigest() for p in [guide,connection]},
1068
+ 'generation':record['nativeConnectionGeneration'],'privateMCPVerified':False}
1069
+ held={**record,'contextProbe':probe};store.write(held,lanes.digest(record))
1070
+ try:
1071
+ self.execution.qualify_dynamic(record,current)
1072
+ result=await self.call_native('turn/start',{'threadId':record['nativeId'],'input':[{'type':'text','text':text,'text_elements':[]}]})
1073
+ turn_id=result.get('turn',{}).get('id')
1074
+ if not isinstance(turn_id,str) or not turn_id:
1075
+ raise PackError('Actual bootstrap turn acknowledgement is missing')
1076
+ updated={**held,'contextProbe':{**probe,'state':'native-delivered','turnId':turn_id}}
1077
+ store.write(updated,lanes.digest(held));return {'state':'own-context-bootstrap-delivered','turnId':turn_id,'operationId':nonce}
1078
+ except BaseException:
1079
+ store.write({**held,'contextProbe':{**probe,'state':'uncertain-effect-hold'}},lanes.digest(held));raise
1080
+
1081
+ async def dynamic_fixture(self,operation,params):
1082
+ from . import lanes
1083
+ expected={'laneId','operationId'} if operation=='lane-fixture-start' else {'laneId'}
1084
+ if set(params)!=expected:
1085
+ raise PackError('Closed own-lane fixture operation required')
1086
+ self.lane_idle();store=lanes.Registry(self.lib.root,'codex');record=store.read(params['laneId'])
1087
+ if not record or record['plan'].get('verificationProfile')!='manual':
1088
+ raise PackError('Actual routing proof requires the bound two-child manual verification plan')
1089
+ current=await self.dynamic_current(record['plan'])
1090
+ if current!={k:record['plan'][k] for k in ['role','intake','ceiling']}:
1091
+ raise PackError('Current source/intake/policy changed before own fixture verification')
1092
+ if record.get('contextProbe',{}).get('state')!='native-file-read-ACK':
1093
+ raise PackError('Actual own-child native file-read bootstrap is required before fixture work')
1094
+ if operation=='lane-fixture-read':
1095
+ fixture=record.get('fixtureVerification')
1096
+ if not fixture or fixture['state']!='native-delivered':
1097
+ raise PackError('Own native fixture delivery is unresolved; no replay')
1098
+ native=await self.dynamic_native(record['nativeId'],record['creationReceipt']['cwd'])
1099
+ if native['busy'] or native['pendingApprovals']:
1100
+ return {'state':record['state'],'nativeBusy':native['busy'],'pendingApprovals':native['pendingApprovals']}
1101
+ verified=await self.dynamic_outcome(record)
1102
+ if verified['outcome']['state']=='completed':
1103
+ path=pathlib.Path(fixture['path']);OwnedSmoke.owned(path)
1104
+ if read_json(path)!=fixture['expected']:
1105
+ raise PackError('Real native item result is not the exact own nonce-file bytes')
1106
+ verified['fixtureVerification']={k:v for k,v in fixture.items() if k!='text'}|{'state':'outcome-verified','fileSHA256':hashlib.sha256(path.read_bytes()).hexdigest()}
1107
+ elif verified['outcome']['state']=='denied':
1108
+ if pathlib.Path(fixture['path']).exists():
1109
+ raise PackError('Denied request has an unexpected fixture file; retain unresolved effects')
1110
+ settled=lanes.settle(verified,native);return store.write(settled,lanes.digest(record))
1111
+ nonce=OwnedSmoke.operation_id(params['operationId'])
1112
+ if record['state']!='loaded-context-ACK' or record.get('fixtureVerification'):
1113
+ raise PackError('Own fixture requires current loaded context and one fresh bounded operation')
1114
+ native=await self.dynamic_native(record['nativeId'],record['creationReceipt']['cwd'])
1115
+ if native['busy'] or native['pendingApprovals']:
1116
+ raise PackError('Own fixture child is not idle')
1117
+ role=record['plan']['role'];path=pathlib.Path(record['creationReceipt']['cwd'])/('.topolo-dynamic-'+record['laneId']+'-'+nonce+'.json')
1118
+ if path.exists() or path.is_symlink():
1119
+ raise PackError('Own fixture path already exists; never overwrite')
1120
+ body={'operationId':nonce,'laneId':record['laneId'],'nativeId':record['nativeId'],'AgentVersionId':role['AgentVersionId'],
1121
+ 'AgentWorkspaceId':role['AgentWorkspaceId'],'BytesWorkspaceId':role['BytesWorkspaceId']}
1122
+ text=('Topolo bounded own-worker verification '+nonce+'. Read the already loaded role charter and connected Agent/Bytes identity. '
1123
+ 'Use the native apply_patch/file-change tool to create only '+str(path)+' with exactly this JSON: '+json.dumps(body,separators=(',',':'))+'. '
1124
+ 'Request native permission through the original intake; do not bypass policy or auto-approve. Read back only your fixture and report its nonce and status. '
1125
+ 'Do not edit source, business content, other workspaces, credentials, global settings, provider resources or external messages.')
1126
+ fixture={'operationId':nonce,'state':'start-may-have-effects','path':str(path),'expected':body,'text':text,
1127
+ 'generation':self.approvals.generation}
1128
+ prepared={**record,'fixtureVerification':fixture};store.write(prepared,lanes.digest(record))
1129
+ try:
1130
+ await self.call_native('thread/resume',{'threadId':record['nativeId']})
1131
+ result=await self.call_native('turn/start',{'threadId':record['nativeId'],'input':[{'type':'text','text':text,'text_elements':[]}]})
1132
+ turn=result.get('turn',{}).get('id')
1133
+ if not isinstance(turn,str) or not turn:
1134
+ raise PackError('Native fixture turn has no actual identity')
1135
+ latest=store.read(record['laneId']);updated={**latest,'fixtureVerification':{**fixture,'state':'native-delivered','turnId':turn}}
1136
+ return store.write(updated,lanes.digest(latest))
1137
+ except BaseException:
1138
+ latest=store.read(record['laneId']);store.write({**latest,'fixtureVerification':{**fixture,'state':'uncertain'}},lanes.digest(latest));raise
1139
+
1140
+ async def dynamic_create(self,params):
1141
+ """Root-invoked supported ThreadStart on this exact owned native home.
1142
+
1143
+ No requested task or turn is started. A possible-effect creation is
1144
+ durably reserved before dispatch and never automatically replayed.
1145
+ """
1146
+ from . import lanes
1147
+ if not self.lane_connection_binding or set(params)!={'laneId'} or params['laneId']!=self.lane_connection_binding['laneId']:
1148
+ raise PackError('Supported native creation requires the exact explicit owned Root lane connection')
1149
+ self.lane_idle();store=lanes.Registry(self.lib.root,'codex');planned=store.read_plan(params['laneId'])
1150
+ if store.read(params['laneId']) is not None:
1151
+ raise PackError('This lane already has an actual native identity')
1152
+ current=await self.dynamic_current(planned)
1153
+ if current!={k:planned[k] for k in ['role','intake','ceiling']}:
1154
+ raise PackError('Source, intake or policy changed before native creation')
1155
+ await asyncio.to_thread(managed_lane_frontend,self.lib,self.runtime,planned)
1156
+ # Independent fixed-ID reads before the single fresh worker is spent.
1157
+ for role in [current['role'],current['intake']]:await self.dynamic_native(role['nativeId'],role['cwd'])
1158
+ directory=store.directory/'creation';directory.mkdir(mode=0o700,exist_ok=True);store._owned(directory,True)
1159
+ marker=directory/(planned['laneId']+'.json')
1160
+ if marker.exists() or marker.is_symlink():
1161
+ raise PackError('Native creation already reserved; reconcile without replay')
1162
+ fd=os.open(marker,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
1163
+ phase={'format':'topolo-owned-native-creation','schemaVersion':1,'laneId':planned['laneId'],
1164
+ 'planDigest':lanes.digest(planned),'state':'may-have-effects','nativeId':None,'generation':self.approvals.generation}
1165
+ with os.fdopen(fd,'wb') as stream:stream.write(canonical(phase)+b'\n');stream.flush();os.fsync(stream.fileno())
1166
+ spec=dict(planned['role']['settings'])
1167
+ if planned.get('verificationProfile')=='manual':
1168
+ from .permissions import apply_settings
1169
+ source=planned['role'];spec=apply_settings({'pack':source['packId'],'set':source['key'].split('/')[1],'id':source['key'].split('/')[2]},
1170
+ 'codex',spec,{'profile':'manual','overrides':{},'intakeRoutes':{source['key']:planned['intake']['key']}})
1171
+ choice=planned['resolution']['resolved'];spec.update(model=choice['model'],model_reasoning_effort=choice['effort'])
1172
+ guide=pathlib.Path(planned['role']['cwd'])/'AGENTS.md';charter=guide.read_text()
1173
+ if len(charter.encode())>131072:
1174
+ raise PackError('Reviewed role charter exceeds bounded native creation context')
1175
+ try:
1176
+ result=await self.call_native('thread/start',{'cwd':planned['role']['cwd'],'model':choice['model'],
1177
+ 'allowProviderModelFallback':False,
1178
+ 'approvalPolicy':spec['approval_policy'],'sandbox':spec['sandbox_mode'],
1179
+ 'config':{'model_reasoning_effort':choice['effort'],'sandbox_workspace_write':spec['sandbox_workspace_write'],'apps':spec['apps']},
1180
+ 'developerInstructions':charter})
1181
+ thread=result.get('thread',{});identity=thread.get('id')
1182
+ if not isinstance(identity,str) or not identity or identity in planned['nativeIdsBeforeCreation']:
1183
+ raise PackError('Native ThreadStart has no proven fresh created identity')
1184
+ phase={**phase,'nativeId':identity};atomic_json(marker,phase)
1185
+ native=await self.dynamic_native(identity,planned['role']['cwd'])
1186
+ if native['busy'] or native['pendingApprovals'] or await self.dynamic_current(planned)!=current:
1187
+ raise PackError('Actual created child placement, idle state or current context differs')
1188
+ frontend=await asyncio.to_thread(managed_lane_frontend,self.lib,self.runtime,planned,identity)
1189
+ receipt={'format':'topolo-native-lane-created','schemaVersion':1,'state':'ready','planDigest':lanes.digest(planned),
1190
+ 'harness':'codex','nativeId':identity,'logicalOriginThreadId':planned['logicalOriginThreadId'],'cwd':native['cwd'],
1191
+ 'model':result.get('model'),'effort':result.get('reasoningEffort'),'nativeParentId':None,
1192
+ 'fresh':True,'creationSource':'supported-native-host-tool'}
1193
+ if receipt['model']!=choice['model'] or receipt['effort']!=choice['effort']:
1194
+ raise PackError('Native creation model/effort tuple differs; no fallback or task dispatch')
1195
+ record=lanes.adopt(planned,receipt,native,current)
1196
+ record.update(frontendVisibility=frontend['state'],frontendProof=frontend)
1197
+ record=store.write(record,None)
1198
+ atomic_json(marker,{**phase,'state':'ready','receipt':receipt,'modelTurnsStarted':False,'nativeParentAvailability':'UNAVAILABLE'})
1199
+ return {'state':'created-adopted-unconfigured','receipt':receipt,'modelTurnsStarted':False,'nativeParentAvailability':'UNAVAILABLE'}
1200
+ except BaseException:
1201
+ atomic_json(marker,{**phase,'state':'uncertain-effect-hold','replayAuthorized':False});raise
1202
+
1203
+ async def dynamic_lane(self, operation, params):
1204
+ from . import lanes,task_models,policy
1205
+ self.lane_idle()
1206
+ if self.lane_busy:
1207
+ raise PackError('An owned dynamic lane operation is already active')
1208
+ self.lane_busy=True;store=lanes.Registry(self.lib.root,'codex');changed=False;record=None
1209
+ try:
1210
+ if operation=='lane-plan':
1211
+ if set(params)!={'request'}:
1212
+ raise PackError('Lane plan accepts only its exact intake request')
1213
+ request=params['request'];source=next((r for r in self.execution.roles.values() if r['key']==request['sourceRoleKey']),None)
1214
+ if not source or source.get('dynamicLaneId'):
1215
+ raise PackError('Exact fixed source role required')
1216
+ intake=next((r for r in self.execution.roles.values() if r['key']==source['intakeKey']),None)
1217
+ if not intake:
1218
+ raise PackError('Original native intake is missing')
1219
+ await self.qualify_organization(source['threadId'])
1220
+ self.execution.set_catalog(await self.read_catalog())
1221
+ role=self.dynamic_role(source);route=self.dynamic_role(intake);document,catalog=self.dynamic_model_inputs(role)
1222
+ choice=policy.dynamic_task_settings(document['configuration'],request['task'],'codex',catalog,
1223
+ document['effectiveConstraints'],override_authorized=lanes.digest(request['task']) in document['authorizedOverrides'])
1224
+ proof=source['organizationPolicyProof'];consent=source['installationConsent']
1225
+ if request.get('verificationProfile')=='manual':
1226
+ from . import topolo
1227
+ remote,_=await asyncio.to_thread(topolo.organization_execution_policy,topolo.Client(consent['environment'],consent['contextKey']),
1228
+ consent,self.lib.load(role['packId'])[0]['agents'],consent.get('agentWorkspaceId'))
1229
+ if 'manual' not in remote['allowedProfiles']:
1230
+ raise PackError('Fresh organization policy does not allow the narrower test profile')
1231
+ plan=lanes.plan(request,role,route,choice,{'policyRevision':proof['revision'],'policyDigest':proof['policyDigest'],
1232
+ 'profile':consent.get('overrides',{}).get(source['key'],consent['profile']),'intakeKey':source['intakeKey']})
1233
+ with closing(sqlite3.connect('file:'+str(pathlib.Path(self.runtime)/'home/state_5.sqlite')+'?mode=ro',uri=True)) as db:
1234
+ plan['nativeIdsBeforeCreation']=sorted(r[0] for r in db.execute('SELECT id FROM threads'))
1235
+ return store.save_plan(plan)
1236
+ if operation=='lane-adopt':
1237
+ if set(params)!={'plan','receipt'}:
1238
+ raise PackError('Lane adoption requires exact plan and native creation receipt')
1239
+ plan=params['plan'];receipt=params['receipt']
1240
+ if store.read_plan(plan['laneId'])!=plan:
1241
+ raise PackError('Creation plan differs from the owned immutable native snapshot')
1242
+ if receipt['nativeId'] in plan.get('nativeIdsBeforeCreation',[]):
1243
+ raise PackError('Native lane was already present at planning; fresh creation not proven')
1244
+ if receipt['cwd']!=plan['role']['cwd']:
1245
+ raise PackError('Dynamic lane must use the reviewed source role context workspace')
1246
+ current=await self.dynamic_current(plan)
1247
+ native=await self.dynamic_native(receipt['nativeId'],receipt['cwd'])
1248
+ if native['busy'] or native['pendingApprovals']:
1249
+ raise PackError('Created native lane must be idle before adoption')
1250
+ record=lanes.adopt(plan,receipt,native,current)
1251
+ return store.write(record,None)
1252
+ if set(params)!={'laneId'}:
1253
+ raise PackError('Lane operation accepts only its exact registered identity')
1254
+ record=store.read(params['laneId'])
1255
+ if not record:
1256
+ raise PackError('No owned dynamic lane registration')
1257
+ current=await self.dynamic_current(record['plan'])
1258
+ if current!={k:record['plan'][k] for k in ['role','intake','ceiling']}:
1259
+ raise PackError('Dynamic source, intake or policy drift; no native mutation')
1260
+ native=await self.dynamic_native(record['nativeId'],record['creationReceipt']['cwd'])
1261
+ if operation=='lane-read':
1262
+ return {'record':record,'native':native,'proof':'current-own-native-read'}
1263
+ if native['busy'] or native['pendingApprovals']:
1264
+ raise PackError('Dynamic lane is busy or has a pending original approval')
1265
+ if operation=='lane-configure':
1266
+ self.execution.prepare_dynamic_configuration(record,current)
1267
+ self.approvals.register_dynamic(self.execution.roles[record['nativeId']])
1268
+ # Persist uncertainty before the first actual settings effect.
1269
+ before=lanes.digest(record);hold={**record,'state':'uncertain-effect-hold'}
1270
+ store.write(hold,before);changed=True
1271
+ self._lane_configuration_record=record
1272
+ result=await self.call_native('thread/resume',{'threadId':record['nativeId']})
1273
+ actual={'model':result.get('model'),'effort':result.get('reasoningEffort')}
1274
+ source=record['plan']['role'];document,catalog=self.dynamic_model_inputs(source)
1275
+ choice=task_models.acknowledge(record['plan']['resolution'],actual,configuration=document['configuration'],
1276
+ catalog=catalog,constraints=document['effectiveConstraints'])
1277
+ configured=lanes.acknowledge_settings(record,actual,choice,self.approvals.generation)
1278
+ guide=str(pathlib.Path(source['cwd'])/'AGENTS.md')
1279
+ if guide not in result.get('instructionSources',[]):
1280
+ raise PackError('Actual native loaded instruction sources omit the owned role charter')
1281
+ if await self.dynamic_current(record['plan'])!=current:
1282
+ raise PackError('Context or policy changed during native settings application')
1283
+ configured=lanes.acknowledge_context(configured,source['contextDigest'])
1284
+ self.execution.qualify_dynamic(configured,current)
1285
+ return store.write(configured,lanes.digest(hold))
1286
+ if operation=='lane-settle':
1287
+ verified=await self.dynamic_outcome(record)
1288
+ return store.write(lanes.settle(verified,native),lanes.digest(record))
1289
+ if operation=='lane-retire':
1290
+ if record['state']!='settled':
1291
+ raise PackError('Only a settled verified child can archive')
1292
+ # Archive through native API; preserve the durable possible-effect state.
1293
+ history=self.dynamic_history(record['nativeId'])
1294
+ hold={**record,'state':'uncertain-effect-hold'};store.write(hold,lanes.digest(record));changed=True
1295
+ await self.call_native('thread/archive',{'threadId':record['nativeId']})
1296
+ with closing(sqlite3.connect('file:'+str(pathlib.Path(self.runtime)/'home/state_5.sqlite')+'?mode=ro',uri=True)) as db:
1297
+ archived=db.execute('SELECT archived FROM threads WHERE id=?',(record['nativeId'],)).fetchone()
1298
+ if archived!=(1,):
1299
+ raise PackError('Native archive readback is unavailable; preserve uncertainty')
1300
+ if self.dynamic_history(record['nativeId'])!=history:
1301
+ raise PackError('Native archived transcript bytes differ; history preservation is unproven')
1302
+ retired=lanes.retire(record,{**native,'archived':True,'historyPreserved':True})
1303
+ self.approvals.retire_dynamic(record['nativeId']);self.execution.retire_dynamic(record['nativeId'])
1304
+ return store.write(retired,lanes.digest(hold))
1305
+ raise PackError('Unsupported closed lane operation')
1306
+ except BaseException:
1307
+ if changed and record and record['nativeId'] in self.execution.roles:
1308
+ self.execution.roles[record['nativeId']]['dynamicCurrentQualified']=False
1309
+ self.execution.verified.discard(record['nativeId'])
1310
+ raise
1311
+ finally:
1312
+ self._lane_configuration_record=None
1313
+ self.lane_busy=False
1314
+
792
1315
  def status(self):
793
- return {'pid':os.getpid(),'childPid':self.child.pid if self.child else None,'desktopPid':os.getppid(),'runtime':self.runtime,
1316
+ return {'pid':os.getpid(),'childPid':self.child.pid if self.child else None,'desktopPid':None if self.managed_transport else os.getppid(),'runtime':self.runtime,
1317
+ 'nativeTransport':'existing-managed-window' if self.managed_transport else 'owned-stdio',
1318
+ 'managedTargetId':self.managed_transport.target_id if self.managed_transport else None,
794
1319
  'quiesced':self.quiesced,'initialized':self.initialized,
795
1320
  'activeTurns':sorted(self.active_turns),'pendingRequests':len(self.pending),
796
1321
  'executionPolicy':self.execution.status() if self.execution else {'state':'not-configured'},
@@ -799,17 +1324,53 @@ class BackendProxy:
799
1324
  async def control(self,reader,writer):
800
1325
  try:
801
1326
  message=json.loads(await asyncio.wait_for(reader.readline(),5))
802
- if not secrets.compare_digest(message.get('token',''),self.token): raise PackError('Live bridge authentication failed')
1327
+ if not secrets.compare_digest(message.get('token',''),self.token):
1328
+ raise PackError('Live bridge authentication failed')
803
1329
  op=message.get('operation')
1330
+ if self.lane_connection_binding:
1331
+ scoped=self.lane_connection_binding;params=message.get('params') or {}
1332
+ if op.startswith('lane-'):
1333
+ lane=params.get('request',params.get('plan',params)).get('laneId')
1334
+ if lane!=scoped['laneId']:
1335
+ raise PackError('Operation is outside the exact owned lane connection')
1336
+ if op=='lane-plan' and params['request']['sourceRoleKey']!=scoped['sourceRoleKey']:
1337
+ raise PackError('Source role differs from the connection binding')
1338
+ elif op in {'approval-details','approval-respond'}:
1339
+ entry=self.approvals.live(params['key'],params['intakeThreadId'],params['fingerprint'])
1340
+ role=self.execution.roles.get(entry['record']['sourceThreadId'],{})
1341
+ if role.get('dynamicLaneId')!=scoped['laneId']:
1342
+ raise PackError('Approval is outside the exact owned lane')
1343
+ elif op not in {'status','approval-list'}:
1344
+ raise PackError('Operation is outside the foreground lane connection')
804
1345
  if op=='quiesce':
805
1346
  if self.permission_configuration:
806
1347
  raise PackError('Permission configuration in progress; preserve the owning native connection')
807
- if self.active_turns: raise PackError('Outgoing agents have active turns. Wait for their current turns; Codex can stay open.')
808
- if self.pending: raise PackError('Backend requests are in flight. Retry the switch; Codex can stay open.')
1348
+ if self.active_turns:
1349
+ raise PackError('Outgoing agents have active turns. Wait for their current turns; Codex can stay open.')
1350
+ if self.pending:
1351
+ raise PackError('Backend requests are in flight. Retry the switch; Codex can stay open.')
809
1352
  self.quiesced=True
810
1353
  await self.stop_child()
811
1354
  elif op=='resume':
812
- if self.quiesced: raise PackError('Use the desktop reconnect action to resume a quiesced bridge')
1355
+ if self.quiesced:
1356
+ raise PackError('Use the desktop reconnect action to resume a quiesced bridge')
1357
+ elif op in {'lane-context-start','lane-context-read'}:
1358
+ result=await self.dynamic_context(op,message.get('params') or {})
1359
+ elif op in {'lane-fixture-start','lane-fixture-read'}:
1360
+ result=await self.dynamic_fixture(op,message.get('params') or {})
1361
+ elif op=='lane-create':
1362
+ result=await self.dynamic_create(message.get('params') or {})
1363
+ elif op=='lane-close':
1364
+ from .lanes import Registry
1365
+ params=message.get('params') or {}
1366
+ if set(params)!={'laneId'} or not self.lane_connection_binding:
1367
+ raise PackError('Explicit foreground own-lane close required')
1368
+ record=Registry(self.lib.root,'codex').read(params['laneId'])
1369
+ if not record or record['state']!='retired' or self.active_turns or self.pending or self.notifications or self.approvals.pending:
1370
+ raise PackError('Own lane must be retired with no pending native work before normal connection closure')
1371
+ self.lane_close_event.set();result={'state':'normal-own-connection-close-requested'}
1372
+ elif op in {'lane-plan','lane-adopt','lane-configure','lane-read','lane-settle','lane-retire'}:
1373
+ result=await self.dynamic_lane(op,message.get('params') or {})
813
1374
  elif op=='owned-roster-read':
814
1375
  result=await self.owned_roster_read(message.get('params') or {})
815
1376
  elif op in {'smoke-start','smoke-read'}:
@@ -836,10 +1397,15 @@ class BackendProxy:
836
1397
  await self.qualify_organization(entry['record']['sourceThreadId'])
837
1398
  self.approvals.live(params['key'],params['intakeThreadId'],params['fingerprint'])
838
1399
  self.approvals.sending(reply)
839
- self.child.stdin.write(canonical(reply)+b'\n');await self.child.stdin.drain();self.approvals.settled(reply)
1400
+ await self.send_native_approval(entry,reply);self.approvals.settled(reply)
1401
+ decision=reply['result'].get('decision')
1402
+ if decision in {'accept','decline','cancel'}:
1403
+ self.dynamic_permission_event(entry['record'],'approved' if decision=='accept' else 'denied')
840
1404
  result={'state':'native-response-forwarded','operationEffectVerified':False}
841
- elif op!='status': raise PackError('Unknown live bridge operation')
842
- if op not in {'approval-list','approval-details','approval-respond','permissions-configure','owned-roster-read','smoke-start','smoke-read'}:result=self.status()
1405
+ elif op!='status':
1406
+ raise PackError('Unknown live bridge operation')
1407
+ if op not in {'lane-context-start','lane-context-read','lane-fixture-start','lane-fixture-read','lane-create','lane-close','lane-plan','lane-adopt','lane-configure','lane-read','lane-settle','lane-retire','approval-list','approval-details','approval-respond','permissions-configure','owned-roster-read','smoke-start','smoke-read'}:
1408
+ result=self.status()
843
1409
  except Exception as exc: result={'error':str(exc)}
844
1410
  writer.write(canonical(result)+b'\n');await writer.drain();writer.close();await writer.wait_closed()
845
1411
 
@@ -869,6 +1435,250 @@ class BackendProxy:
869
1435
  if record.get('pid')==os.getpid():
870
1436
  (self.lib.root/'live-bridge.json').unlink(missing_ok=True);self.path.unlink(missing_ok=True)
871
1437
 
1438
+ class ManagedNativeTransport:
1439
+ """One captured, current desktop manager owns every native lane effect."""
1440
+ def __init__(self,lib,runtime,lane_id):
1441
+ self.lib=lib;self.runtime=runtime;self.lane_id=lane_id;self.token=secrets.token_hex(32)
1442
+ self.ws=None;self.events_ws=None;self.ready=False;self.closed=False;self.subscriptions_closed=False;self.lock=asyncio.Lock()
1443
+ self.scope=None;self.target_id=None
1444
+
1445
+ async def open(self,planned):
1446
+ self.scope=await asyncio.to_thread(managed_lane_frontend,self.lib,self.runtime,planned)
1447
+ self.ws,target=await asyncio.to_thread(renderer,self.lib)
1448
+ self.events_ws,event_target=await asyncio.to_thread(renderer,self.lib)
1449
+ if target!=self.scope['targetId'] or event_target!=target:
1450
+ raise PackError('Existing managed renderer identity changed before connection')
1451
+ self.target_id=target
1452
+ await self.evaluate('open',{'homes':[self.scope['home']],'source':planned['role'],'intake':planned['intake'],
1453
+ 'adapter':self.scope['capability']})
1454
+ self.ready=True
1455
+
1456
+ def expression(self,action,value):
1457
+ params={'action':action,'value':value,'laneId':self.lane_id,'token':self.token}
1458
+ return """(async()=>{
1459
+ const p=PARAMS,key=Symbol.for('topolo.agent-pack.managed-native.v1');
1460
+ let registry=window[key];
1461
+ if(p.action==='open'){
1462
+ window[key]??=new Map();registry=window[key];
1463
+ if(registry.has(p.laneId))throw new Error('Owned managed connection exists; no replay or takeover');
1464
+ const v=p.value,services=(await import(v.adapter.module))[v.adapter.export];
1465
+ const opened=await services?.appServerManagers?.open('local');
1466
+ if(opened?.status!=='ready')throw new Error('Existing managed native backend unavailable');
1467
+ const manager=opened.manager,home=await manager.getCodexHome();
1468
+ if(!v.homes.includes(home))throw new Error('Managed native home differs');
1469
+ const state={manager,home,source:v.source,intake:v.intake,token:p.token,childId:null,creationReserved:false,
1470
+ queue:[],waiters:[],subscriptions:[],closed:false};
1471
+ registry.set(p.laneId,state);
1472
+ const push=value=>{if(state.closed)return;if(state.queue.length>=1024){state.closed=true;return;}
1473
+ state.queue.push(value);state.waiters.splice(0).forEach(f=>f());};
1474
+ state.subscriptions.push(await manager.subscribe({type:'event',key:{hostId:'local'},event:'approval-request',listener:async event=>{
1475
+ if(event.conversationId!==state.childId)return;
1476
+ const conversation=await manager.getConversation(state.childId);
1477
+ const request=conversation?.requests?.find(r=>r.id===event.requestId);
1478
+ if(request&&request.params?.threadId===state.childId&&['item/fileChange/requestApproval','item/commandExecution/requestApproval'].includes(request.method))
1479
+ push(request);
1480
+ else push({method:'topolo/managed-request-unavailable',params:{threadId:state.childId}});
1481
+ }}));
1482
+ state.subscriptions.push(await manager.subscribe({type:'notification',key:{hostId:'local'},
1483
+ methods:['turn/started','turn/completed','thread/status/changed'],listener:notification=>{
1484
+ if([state.childId,state.intake.nativeId].includes(notification.params?.threadId))push(notification);
1485
+ }}));
1486
+ return {state:'managed-owning-connection-ready',home};
1487
+ }
1488
+ const s=registry?.get(p.laneId);
1489
+ if(p.action==='close'){
1490
+ if(s&&s.token!==p.token)throw new Error('Foreign managed native connection must be preserved');
1491
+ if(s){s.closed=true;s.waiters.splice(0).forEach(f=>f());
1492
+ for(const subscription of s.subscriptions)await subscription.unsubscribe();registry.delete(p.laneId);}
1493
+ return {state:'normal-own-managed-subscriptions-closed',nativeOwnerStopped:false};
1494
+ }
1495
+ if(!s||s.token!==p.token||s.closed)throw new Error('Original managed native connection unavailable; no fallback');
1496
+ if(await s.manager.getCodexHome()!==s.home)throw new Error('Captured managed native instance/home changed');
1497
+ if(p.action==='request'){
1498
+ const {method,params}=p.value;
1499
+ const allowed=['model/list','thread/read','thread/start','thread/resume','thread/archive','turn/start'];
1500
+ if(!allowed.includes(method))throw new Error('Unsupported closed managed native method');
1501
+ const id=params.threadId;
1502
+ if(method==='thread/start'){
1503
+ if(s.creationReserved||params.cwd!==s.source.cwd)throw new Error('Managed native creation is reserved or placement differs');
1504
+ s.creationReserved=true;
1505
+ }else if(method!=='model/list'){
1506
+ if(id!==s.childId&&!(method==='thread/read'&&[s.source.nativeId,s.intake.nativeId].includes(id))&&
1507
+ !(method==='turn/start'&&id===s.intake.nativeId))throw new Error('Managed native request is outside the own child/intake');
1508
+ if(method==='turn/start'&&id===s.intake.nativeId&&Object.keys(params).some(k=>!['threadId','input'].includes(k)))
1509
+ throw new Error('Original intake settings must remain unchanged');
1510
+ }
1511
+ const result=await s.manager.sendRequest(method,params);
1512
+ if(method==='thread/start'){
1513
+ const id=result?.thread?.id;
1514
+ if(typeof id!=='string'||[s.source.nativeId,s.intake.nativeId].includes(id))throw new Error('Fresh managed native identity unavailable');
1515
+ s.childId=id;
1516
+ }
1517
+ return result;
1518
+ }
1519
+ if(p.action==='approval'){
1520
+ const {request,reply}=p.value;
1521
+ if(request.params?.threadId!==s.childId||request.id!==reply.id||!['accept','decline','cancel'].includes(reply.result?.decision))
1522
+ throw new Error('Exact original own-child native approval required');
1523
+ const current=await s.manager.getConversation(s.childId);
1524
+ const actual=current?.requests?.find(r=>r.id===request.id);
1525
+ const canonical=v=>JSON.stringify(v,(_k,x)=>x&&typeof x==='object'&&!Array.isArray(x)?Object.fromEntries(Object.keys(x).sort().map(k=>[k,x[k]])):x);
1526
+ if(!actual||canonical(actual)!==canonical(request))throw new Error('Actual native pending request body changed');
1527
+ if(request.method==='item/fileChange/requestApproval')await s.manager.replyWithFileChangeApprovalDecision(s.childId,request.id,reply.result.decision);
1528
+ else if(request.method==='item/commandExecution/requestApproval')await s.manager.replyWithCommandExecutionApprovalDecision(s.childId,request.id,reply.result.decision);
1529
+ else throw new Error('Unsupported own-child native permission decision');
1530
+ const after=await s.manager.getConversation(s.childId);
1531
+ if(after?.requests?.some(r=>r.id===request.id))throw new Error('Native decision forwarding is not acknowledged; preserve uncertainty');
1532
+ return {state:'native-response-forwarded',operationEffectVerified:false};
1533
+ }
1534
+ if(p.action==='next'){
1535
+ if(!s.queue.length)await new Promise(resolve=>{const wake=()=>{clearTimeout(timer);resolve();};
1536
+ const timer=setTimeout(()=>{s.waiters=s.waiters.filter(f=>f!==wake);resolve();},10000);s.waiters.push(wake);});
1537
+ if(s.closed)throw new Error('Managed own native event custody was lost');
1538
+ return s.queue.splice(0);
1539
+ }
1540
+ throw new Error('Unsupported closed managed transport operation');
1541
+ })()""".replace('PARAMS',json.dumps(params),1)
1542
+
1543
+ async def evaluate(self,action,value,events=False):
1544
+ ws=self.events_ws if events else self.ws
1545
+ if not ws or self.closed:
1546
+ raise PackError('Owned managed transport is unavailable')
1547
+ if action!='close' and managed_backend_generation(self.lib,self.runtime)!=self.scope['proxyGeneration']:
1548
+ raise PackError('Original selected native execution owner changed; no retargeting')
1549
+ if events:
1550
+ return await asyncio.to_thread(ws.evaluate,self.expression(action,value))
1551
+ async with self.lock:return await asyncio.to_thread(ws.evaluate,self.expression(action,value))
1552
+
1553
+ async def close(self):
1554
+ try:
1555
+ if self.ws and not self.closed:
1556
+ result=await self.evaluate('close',{})
1557
+ self.subscriptions_closed=result.get('state')=='normal-own-managed-subscriptions-closed'
1558
+ finally:
1559
+ self.closed=True;self.ready=False
1560
+ for ws in [self.ws,self.events_ws]:
1561
+ if ws:
1562
+ ws.close()
1563
+
1564
+
1565
+ class OwnedLaneConnection:
1566
+ """Explicit, foreground native stdio connection for one reviewed new lane.
1567
+
1568
+ The existing window manager owns creation, settings, turns and native approval
1569
+ responses. No replacement app-server or unrelated profile is started.
1570
+ """
1571
+ def __init__(self,lib,connection_id,binding):
1572
+ from .storage import safe_id
1573
+ self.lib=lib;self.connection_id=safe_id(connection_id);self.binding=binding
1574
+ fields={'format','schemaVersion','laneId','runtime','binary','binaryDigest','protocolDigest','sourceRoleKey','intakeKey','transport'}
1575
+ if (not isinstance(binding,dict) or set(binding)!=fields or binding['format']!='topolo-owned-lane-connection' or
1576
+ binding['schemaVersion']!=1 or binding['transport']!='codex-managed-window' or binding['laneId']!=self.connection_id or
1577
+ str(lib.active().get('runtime'))!=binding['runtime'] or not pathlib.Path(binding['binary']).is_absolute() or
1578
+ '/intakes/' not in binding['intakeKey']):
1579
+ raise PackError('Explicit exact owned lane connection binding required')
1580
+ binary=pathlib.Path(binding['binary']);info=binary.lstat()
1581
+ if binary.is_symlink() or not binary.is_file() or info.st_uid not in {0,os.getuid()} or info.st_mode&0o022:
1582
+ raise PackError('Native binary must be a regular qualified installed executable')
1583
+ if hashlib.sha256(binary.read_bytes()).hexdigest()!=binding['binaryDigest']:
1584
+ raise PackError('Native binary differs from the reviewed connection binding')
1585
+ self.protocol=qualify_protocol(str(binary),lane_creation=True)
1586
+ from .lanes import digest
1587
+ if digest(self.protocol)!=binding['protocolDigest']:
1588
+ raise PackError('Native protocol differs from the reviewed connection binding')
1589
+ self.directory=lib.root/'dynamic-connections'/self.connection_id
1590
+ self.proxy=BackendProxy(lib,str(binary),['app-server']);self.proxy.emit_native=False
1591
+ self.proxy.lane_connection_binding=binding
1592
+ self.proxy.lane_close_event=asyncio.Event()
1593
+
1594
+ async def run(self):
1595
+ directory=self.directory
1596
+ active=self.lib.active();self.proxy.runtime=self.binding['runtime']
1597
+ self.proxy.execution=load_gate(self.proxy.runtime) or ExecutionGate(prepare_runtime(self.lib,active['components'],self.proxy.runtime))
1598
+ self.proxy.approvals=ApprovalRouter(pathlib.Path(self.proxy.runtime)/'approval-inbox',list(self.proxy.execution.roles.values()),uuid.uuid4().hex)
1599
+ source=[r for r in self.proxy.execution.roles.values() if r['key']==self.binding['sourceRoleKey']]
1600
+ intake=[r for r in self.proxy.execution.roles.values() if r['key']==self.binding['intakeKey']]
1601
+ if len(source)!=1 or len(intake)!=1 or source[0]['intakeKey']!=intake[0]['key']:
1602
+ raise PackError('Owned managed connection has no exact original source and intake route')
1603
+ planned={name:{'nativeId':row['threadId'],'cwd':row['cwd']} for name,row in [('role',source[0]),('intake',intake[0])]}
1604
+ transport=ManagedNativeTransport(self.lib,self.proxy.runtime,self.connection_id)
1605
+ self.proxy.managed_transport=transport;self.proxy.protocol=self.protocol
1606
+ server=None;stop=asyncio.Event();loop=asyncio.get_running_loop();event_task=None;created=False
1607
+ for sig in [signal.SIGINT,signal.SIGTERM]:loop.add_signal_handler(sig,stop.set)
1608
+ try:
1609
+ # A read-only preflight precedes durable custody for native subscriptions.
1610
+ preflight=await asyncio.to_thread(managed_lane_frontend,self.lib,self.proxy.runtime,planned)
1611
+ directory.parent.mkdir(mode=0o700,exist_ok=True);OwnedSmoke.owned(directory.parent,directory=True)
1612
+ directory.mkdir(mode=0o700);created=True;OwnedSmoke.owned(directory,directory=True)
1613
+ atomic_json(directory/'managed-custody.json',{'state':'native-subscriptions-may-follow','laneId':self.connection_id,
1614
+ 'token':transport.token,'targetId':preflight['targetId'],'runtime':self.proxy.runtime,'binding':self.binding})
1615
+ await transport.open(planned)
1616
+ self.proxy.path=lane_socket_path(self.lib,self.connection_id)
1617
+ if self.proxy.path.exists() or self.proxy.path.is_symlink():
1618
+ raise PackError('Own managed control path is occupied; no takeover')
1619
+ self.proxy.initialized=True
1620
+ self.proxy.execution.set_catalog(await self.proxy.read_catalog());self.proxy.catalog_ready.set()
1621
+ server=await asyncio.start_unix_server(self.proxy.control,path=str(self.proxy.path),limit=1024*1024)
1622
+ self.proxy.path.chmod(0o600)
1623
+ atomic_json(directory/'connection.json',{'pid':os.getpid(),'socket':str(self.proxy.path),'token':self.proxy.token,
1624
+ 'connectionId':self.connection_id,'generation':self.proxy.approvals.generation,'binding':self.binding,
1625
+ 'nativeTransport':'existing-managed-window','managedTargetId':transport.target_id})
1626
+ print(json.dumps({'state':'owned-managed-native-lane-connection-ready','connectionId':self.connection_id,'pid':os.getpid(),
1627
+ 'managedTargetId':transport.target_id,'modelTurnsStarted':False,'generation':self.proxy.approvals.generation}),flush=True)
1628
+ event_task=asyncio.create_task(self.proxy.managed_native_events())
1629
+ stopped=asyncio.create_task(stop.wait());normal=asyncio.create_task(self.proxy.lane_close_event.wait())
1630
+ done,pending=await asyncio.wait({stopped,normal,event_task},return_when=asyncio.FIRST_COMPLETED)
1631
+ for task in pending:task.cancel()
1632
+ if event_task in done:
1633
+ await event_task
1634
+ finally:
1635
+ await self.close_owned_controls(transport,server,event_task,created)
1636
+
1637
+ async def close_owned_controls(self,transport,server,event_task,created):
1638
+ directory=self.directory
1639
+ try:await transport.close()
1640
+ finally:
1641
+ if event_task:
1642
+ event_task.cancel()
1643
+ if server:
1644
+ server.close();await server.wait_closed()
1645
+ if created:
1646
+ pending=self.proxy.approvals.records()
1647
+ normal=self.proxy.lane_close_event.is_set() and transport.subscriptions_closed and not self.proxy.active_turns and not any(r.get('state')=='pending' for r in pending)
1648
+ atomic_json(directory/'closure.json',{'state':'closed' if normal else 'closed-with-unresolved-work',
1649
+ 'normalLifecycleClosureVerified':normal,'nativeTransport':'existing-managed-window',
1650
+ 'nativeOwnerStopped':False,'ownSubscriptionsClosed':transport.subscriptions_closed,
1651
+ 'pendingNativeApprovals':sum(r.get('state')=='pending' for r in pending),'replayAuthorized':False})
1652
+ (directory/'connection.json').unlink(missing_ok=True)
1653
+ if server:
1654
+ self.proxy.path.unlink(missing_ok=True)
1655
+
1656
+
1657
+ def lane_socket_path(lib,connection_id):
1658
+ # A fixed short path fits macOS AF_UNIX even with a long library/lane name.
1659
+ # The authenticated socket is mode0600; an existing path is never taken over.
1660
+ identity=hashlib.sha256(canonical([str(lib.root),connection_id])).hexdigest()[:24]
1661
+ return pathlib.Path('/tmp')/('topolo-lane-'+str(os.getuid())+'-'+identity+'.sock')
1662
+
1663
+
1664
+ def request_lane(lib,connection_id,operation,params):
1665
+ from .storage import safe_id
1666
+ directory=lib.root/'dynamic-connections'/safe_id(connection_id)
1667
+ OwnedSmoke.owned(directory,directory=True);OwnedSmoke.owned(directory/'connection.json')
1668
+ record=read_json(directory/'connection.json')
1669
+ if record.get('connectionId')!=connection_id or record.get('socket')!=str(lane_socket_path(lib,connection_id)):
1670
+ raise PackError('Owned lane connection receipt differs')
1671
+ with socket.socket(socket.AF_UNIX,socket.SOCK_STREAM) as connection:
1672
+ connection.settimeout(240);connection.connect(record['socket'])
1673
+ connection.sendall(canonical({'token':record['token'],'operation':operation,'params':params})+b'\n')
1674
+ with connection.makefile('rb') as stream:line=stream.readline(1024*1024)
1675
+ if not line:
1676
+ raise PackError('Owned lane connection closed without ACK; reconcile without replay')
1677
+ result=json.loads(line)
1678
+ if 'error' in result:
1679
+ raise PackError(result['error'])
1680
+ return result
1681
+
872
1682
  def proxy_main():
873
1683
  lib=Library(os.environ['CODEX_PACK_HOME']);real=os.environ['CODEX_PACK_REAL_CLI']
874
1684
  args=sys.argv[1:]
@@ -976,6 +1786,94 @@ def thread_ids(runtime):
976
1786
  def rendered_threads(ws):
977
1787
  return ws.evaluate('Array.from(document.querySelectorAll("[data-app-action-sidebar-thread-row][data-app-action-sidebar-thread-host-id=local]")).map(e=>({id:e.getAttribute("data-app-action-sidebar-thread-id").replace(/^local:/,""),title:e.getAttribute("data-app-action-sidebar-thread-title")}))')
978
1788
 
1789
+
1790
+ def managed_backend_generation(lib,runtime):
1791
+ """Authenticate the existing proxy; a manifest or symlink is not liveness."""
1792
+ active=lib.active()
1793
+ if active.get('runtime')!=str(runtime):
1794
+ raise PackError('Selected native runtime changed')
1795
+ status=request(lib,'status')
1796
+ if (not isinstance(status,dict) or status.get('runtime')!=str(runtime) or status.get('initialized') is not True or
1797
+ status.get('quiesced') is not False or any(type(status.get(k)) is not int or status[k]<1 for k in ['pid','childPid'])):
1798
+ raise PackError('Current authenticated selected backend is unavailable')
1799
+ return {k:status[k] for k in ['pid','childPid','runtime']}
1800
+
1801
+
1802
+ def managed_lane_frontend(lib,runtime,planned,native_id=None,app='/Applications/ChatGPT.app'):
1803
+ """Use only the current managed window's exported native manager/catalog.
1804
+
1805
+ A separate selected-home app-server cannot assert managed visibility. The
1806
+ existing manager must already own the selected storage before any refresh.
1807
+ This function never reconnects, changes home, restarts or creates a worker.
1808
+ """
1809
+ from . import lanes
1810
+ root=pathlib.Path(runtime);home=root/'home'
1811
+ if not home.is_dir():
1812
+ raise PackError('Selected owned native home is unavailable')
1813
+ generation=managed_backend_generation(lib,str(root))
1814
+ expected=[str(home)]
1815
+ layout=lib.root/'desktop-layout.json';stable=lib.root/'desktop-home';layout_hash=None
1816
+ if layout.exists():
1817
+ OwnedSmoke.owned(layout)
1818
+ if read_json(layout).get('owner') not in {'topolo-agent-pack','codex-pack'}:
1819
+ raise PackError('Stable app-host layout is not owned by this pack')
1820
+ OwnedSmoke.owned(stable,directory=True)
1821
+ expected.append(str(stable));layout_hash=hashlib.sha256(layout.read_bytes()).hexdigest()
1822
+ ws,target=renderer(lib)
1823
+ try:
1824
+ capability=adapter(app)
1825
+ params={'adapter':capability,'homes':expected,'configFile':str(home/'config.toml'),
1826
+ 'roles':[planned['role'],planned['intake']],'nativeId':native_id}
1827
+ expression="""(async()=>{
1828
+ const p=PARAMS,services=(await import(p.adapter.module))[p.adapter.export];
1829
+ if(!services?.appServerManagers||!services.localThreadCatalog)throw new Error('Managed native manager/catalog unavailable; no fallback');
1830
+ const opened=await services.appServerManagers.open('local');
1831
+ if(opened?.status!=='ready'||!opened.manager)throw new Error('Existing managed local backend is unavailable');
1832
+ const manager=opened.manager,home=await manager.getCodexHome();
1833
+ if(typeof home!=='string'||!p.homes.includes(home))throw new Error('Existing managed backend is outside the selected owned home');
1834
+ const provenance=await manager.sendRequest('config/read',{cwd:p.roles[0].cwd,includeLayers:true});
1835
+ const users=provenance?.layers?.filter(layer=>layer.name?.type==='user'&&!layer.disabledReason);
1836
+ if(!Array.isArray(users)||!users.length||users.some(layer=>layer.name.file!==p.configFile||typeof layer.version!=='string'||!layer.version))
1837
+ throw new Error('Actual native execution-home config provenance differs');
1838
+ const configOrigins=users.map(layer=>({name:layer.name,version:layer.version}));
1839
+ for(const role of p.roles){const value=await manager.readThread(role.nativeId,{includeTurns:false}),thread=value?.thread;
1840
+ if(thread?.id!==role.nativeId||thread.cwd!==role.cwd||thread.archived===true)throw new Error('Managed backend cannot read exact fixed source/intake');}
1841
+ if(p.nativeId===null)return {home,configOrigins,sourceIds:p.roles.map(r=>r.nativeId),nativeId:null,state:'managed-preflight-read-verified'};
1842
+ if(p.roles.some(r=>r.nativeId===p.nativeId))throw new Error('Cannot register a fixed source/intake as a new lane');
1843
+ const value=await manager.readThread(p.nativeId,{includeTurns:false}),thread=value?.thread;
1844
+ if(thread?.id!==p.nativeId||thread.cwd!==p.roles[0].cwd||thread.archived===true)throw new Error('Managed native own-child read differs');
1845
+ let cursor=null,listed=false;
1846
+ for(let page=0;page<20;page++){
1847
+ const result=await manager.sendRequest('thread/list',{cursor,limit:100,archived:false,modelProviders:[],useStateDbOnly:true});
1848
+ if(!Array.isArray(result?.data))throw new Error('Managed native thread-list response unavailable');
1849
+ if(result.data.some(t=>t.id===p.nativeId)){listed=true;break;}
1850
+ cursor=result.nextCursor;if(cursor===null||cursor===undefined)break;
1851
+ }
1852
+ if(!listed)throw new Error('Exact own child is absent from the managed native list');
1853
+ const catalog=services.localThreadCatalog;
1854
+ await catalog.notifyThread({hostId:'local',threadId:p.nativeId},'upsert');
1855
+ await catalog.requestSync(['local'],'immediate');
1856
+ const entries=await catalog.readEntries([{hostId:'local',threadId:p.nativeId}]);
1857
+ if(!Array.isArray(entries)||entries.length!==1||entries[0].threadId!==p.nativeId||entries[0].hostId!=='local')
1858
+ throw new Error('Managed own-child catalog registration was not acknowledged');
1859
+ if(await manager.getCodexHome()!==home)throw new Error('Managed backend home changed during native registration');
1860
+ return {state:'managed-native-read-list-catalog-verified',home,configOrigins,nativeId:p.nativeId,sourceIds:p.roles.map(r=>r.nativeId),
1861
+ nativeRead:true,nativeList:true,catalogRead:true,rendered:!!document.querySelector('[data-app-action-sidebar-thread-row][data-app-action-sidebar-thread-id="local:'+CSS.escape(p.nativeId)+'"]')};
1862
+ })()""".replace('PARAMS',json.dumps(params),1)
1863
+ result=ws.evaluate(expression)
1864
+ if (not isinstance(result,dict) or result.get('home') not in expected or result.get('nativeId')!=native_id or
1865
+ result.get('sourceIds')!=[r['nativeId'] for r in params['roles']]):
1866
+ raise PackError('Exact existing managed backend acknowledgement differs')
1867
+ if native_id is not None and (result.get('state')!='managed-native-read-list-catalog-verified' or
1868
+ any(result.get(k) is not True for k in ['nativeRead','nativeList','catalogRead'])):
1869
+ raise PackError('Managed native child read/list/catalog proof is incomplete')
1870
+ if managed_backend_generation(lib,str(root))!=generation:
1871
+ raise PackError('Selected native backend generation changed during managed proof')
1872
+ if layout_hash is not None and hashlib.sha256(layout.read_bytes()).hexdigest()!=layout_hash:
1873
+ raise PackError('Stable owned app-host layout changed during managed proof')
1874
+ return {**result,'targetId':target,'selectedRuntime':str(root),'capability':capability,'proxyGeneration':generation}
1875
+ finally:ws.close()
1876
+
979
1877
  def checkpoint_ui(lib,runtime):
980
1878
  ws,_=renderer(lib)
981
1879
  try: