@topolo/cli 0.13.6 → 0.13.8

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.
@@ -327,6 +327,9 @@ def parser():
327
327
  q.add_argument('operation',choices=['list','details','respond']);q.add_argument('key',nargs='?')
328
328
  q.add_argument('--intake');q.add_argument('--fingerprint');q.add_argument('--response')
329
329
  q.add_argument('--human-decision-recorded',action='store_true')
330
+ q=sub.add_parser('smoke',help='Start or read one closed, bound native acceptance case; never arbitrary prompts')
331
+ q.add_argument('operation',choices=['start','read']);q.add_argument('identity')
332
+ q.add_argument('--case',choices=sorted(live.SMOKE_CASES));q.add_argument('--operation-id')
330
333
  for command in ['connect','remote-context','remember','artifact','artifact-get','remote-recover']:
331
334
  q=sub.add_parser(command,help='Use public Topolo Agent continuity and Bytes artifact contracts')
332
335
  q.add_argument('name');q.add_argument('--environment',choices=['development','staging','production'],required=True)
@@ -380,7 +383,17 @@ def main(argv=None):
380
383
  emit(result)
381
384
  return 0
382
385
  with lib.lock():
383
- if args.command=='approvals':
386
+ if args.command=='smoke':
387
+ if args.operation=='start':
388
+ if not args.case or not args.operation_id:
389
+ raise PackError('Exact smoke case and operation ID required')
390
+ params={'key':args.identity,'caseId':args.case,'operationId':live.OwnedSmoke.operation_id(args.operation_id)}
391
+ else:
392
+ if args.case or args.operation_id:
393
+ raise PackError('Smoke read accepts only its existing operation ID')
394
+ params={'operationId':live.OwnedSmoke.operation_id(args.identity)}
395
+ result=live.request(lib,'smoke-'+args.operation,params)
396
+ elif args.command=='approvals':
384
397
  if args.operation!='list' and (not args.key or not args.intake or not args.fingerprint):
385
398
  raise PackError('Exact request key, --intake and --fingerprint required')
386
399
  params={'key':args.key,'intakeThreadId':args.intake,'fingerprint':args.fingerprint}
@@ -23,11 +23,240 @@ import time
23
23
  import urllib.request
24
24
  import urllib.parse
25
25
  import uuid
26
+ import datetime
26
27
 
27
28
  from .storage import Library,PackError,atomic_json,canonical,read_json
28
29
  from .execution import ExecutionGate, load_gate, prepare_runtime, qualify_protocol
29
30
  from .approvals import ApprovalRouter
30
31
 
32
+ SMOKE_PLAN = '6504d4e44c2456ae3e761de6c2e57b086b0e49229a7ee6a1ac782684ad4e40c6'
33
+ SMOKE_CASES = {'stg-context-memory-bytes': ('staging', 6), 'stg-peer-ack': ('staging', 6),
34
+ 'prod-role-context-ack': ('production', 140)}
35
+
36
+ class OwnedSmoke:
37
+ """One finite native verification operation. No caller text, paths or RPC.
38
+
39
+ Both native hosts use this store. A durable exclusive reservation covers
40
+ budget checks and dispatch; an uncertain send retains it across restart.
41
+ Binding/fixture references are supplied by the effect owner, never invented.
42
+ """
43
+ def __init__(self, lib, harness):
44
+ if harness not in {'codex', 'claude'}:
45
+ raise PackError('Unknown smoke harness')
46
+ self.lib=lib;self.harness=harness;self.root=lib.root/'smoke-operations'
47
+ self.root.mkdir(mode=0o700,exist_ok=True)
48
+ self.owned(self.root,directory=True)
49
+
50
+ @staticmethod
51
+ def owned(path,directory=False):
52
+ stat=path.lstat()
53
+ if path.is_symlink() or stat.st_uid!=os.getuid() or stat.st_mode&0o022 or (not directory and not path.is_file()):
54
+ raise PackError('Unowned smoke record')
55
+
56
+ @staticmethod
57
+ def operation_id(value):
58
+ try: valid=isinstance(value,str) and str(uuid.UUID(value))==value
59
+ except ValueError: valid=False
60
+ if not valid:
61
+ raise PackError('Exact smoke UUID required')
62
+ return value
63
+
64
+ def path(self,operation):
65
+ return self.root/(self.operation_id(operation)+'.json')
66
+
67
+ def read(self,operation):
68
+ path=self.path(operation);self.owned(path)
69
+ if path.stat().st_size>1048576:
70
+ raise PackError('Smoke record exceeds bounded transport')
71
+ record=read_json(path)
72
+ if record.get('operationId')!=operation or record.get('harness')!=self.harness or record.get('planSHA256')!=SMOKE_PLAN:
73
+ raise PackError('Smoke operation custody differs')
74
+ return record
75
+
76
+ def binding(self,key,case):
77
+ if case not in SMOKE_CASES or not isinstance(key,str) or not re.fullmatch(r'[a-z0-9-]+/[a-z0-9-]+/[a-z0-9-]+',key):
78
+ raise PackError('Finite owned smoke case and role required')
79
+ path=self.lib.root/'smoke-bindings'/(self.harness+'.json');self.owned(path.parent,directory=True);self.owned(path)
80
+ if path.stat().st_size>131072:
81
+ raise PackError('Smoke binding exceeds bounded inventory')
82
+ binding=read_json(path);now=time.time()
83
+ if (set(binding)!={'format','planSHA256','planId','issuedAt','expiresAt','roles'} or
84
+ binding['format']!='topolo-owned-native-smoke-v1' or binding['planSHA256']!=SMOKE_PLAN or
85
+ not isinstance(binding['issuedAt'],(int,float)) or not isinstance(binding['expiresAt'],(int,float)) or
86
+ not binding['issuedAt']<=now<binding['expiresAt']<=binding['issuedAt']+900):
87
+ raise PackError('Current bounded smoke binding required')
88
+ self.operation_id(binding['planId'])
89
+ rows=binding['roles']
90
+ if (not isinstance(rows,list) or not 1<=len(rows)<=70 or
91
+ len({r['key'] for r in rows})!=len(rows) or len({r['nativeId'] for r in rows})!=len(rows)):
92
+ raise PackError('Unique complete smoke roster required')
93
+ matches=[r for r in rows if r['key']==key]
94
+ if len(matches)!=1:
95
+ raise PackError('Smoke role is outside the owned binding')
96
+ row=matches[0]
97
+ fields={'key','nativeId','cwd','title','environment','contextKey','principalId','organizationId','instanceId',
98
+ 'logicalId','agentId','agentVersionId','agentWorkspaceId','bytesWorkspaceId',
99
+ 'contextMarkerId','contextMarkerHash','artifactMemoryId','artifactHash','fixtureExpiresAt'}
100
+ if set(row)!=fields or row['environment']!=SMOKE_CASES[case][0]:
101
+ raise PackError('Exact smoke descriptor or environment differs')
102
+ for field in fields-{'key','cwd','title','environment','contextMarkerHash','artifactHash','fixtureExpiresAt'}:
103
+ if not isinstance(row[field],str) or not re.fullmatch(r'[A-Za-z0-9_:/.-]{1,160}',row[field]):
104
+ raise PackError('Invalid smoke identity reference')
105
+ if (not pathlib.Path(row['cwd']).is_absolute() or not isinstance(row['title'],str) or not 1<=len(row['title'])<=200 or
106
+ any(not re.fullmatch(r'[a-f0-9]{64}',row[f]) for f in ['contextMarkerHash','artifactHash']) or
107
+ row['logicalId']!='/'.join(key.split('/')[1:]) or row['contextKey']!='org:'+row['organizationId']):
108
+ raise PackError('Smoke role/context references differ')
109
+ if case.startswith('stg-') and not now<row['fixtureExpiresAt']<=binding['expiresAt']:
110
+ raise PackError('Bounded staging fixture expiry required')
111
+ from . import topolo
112
+ pack=key.split('/')[0]
113
+ connection=read_json(topolo.binding_path(self.lib,pack,row['environment'],row['contextKey']))
114
+ role=[r for r in connection['input']['roles'] if r['logicalId']==row['logicalId']]
115
+ expected={'instanceId':row['instanceId'],'bytesWorkspaceId':row['bytesWorkspaceId']}
116
+ if (connection['identity']!={'userId':row['principalId'],'organizationId':row['organizationId']} or
117
+ connection['agentWorkspaceId']!=row['agentWorkspaceId'] or
118
+ any(connection['input'].get(k)!=v for k,v in expected.items()) or len(role)!=1 or
119
+ any(role[0].get(k)!=row[k] for k in ['agentId','agentVersionId'])):
120
+ raise PackError('Smoke hosted connection differs')
121
+ local=pathlib.Path(row['cwd'])/'topolo-connection.json';self.owned(local)
122
+ descriptor=read_json(local)
123
+ if any(descriptor.get(k)!=row[k] for k in ['environment','contextKey','instanceId','agentWorkspaceId','bytesWorkspaceId']):
124
+ raise PackError('Current native hosted descriptor differs')
125
+ return binding,row,hashlib.sha256(canonical(binding)).hexdigest()
126
+
127
+ def prepare(self,key,case,operation,native_identity):
128
+ path=self.path(operation)
129
+ if path.exists():
130
+ raise PackError('Smoke operation already exists; read without replay')
131
+ lock=self.root/'.reservation'
132
+ try:fd=os.open(lock,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
133
+ except FileExistsError: raise PackError('Native smoke reservation remains; reconcile without replay')
134
+ with os.fdopen(fd,'wb') as stream:stream.write(canonical({'operationId':operation,'harness':self.harness}));stream.flush();os.fsync(stream.fileno())
135
+ try:
136
+ binding,row,digest=self.binding(key,case)
137
+ history=[]
138
+ for previous in self.root.glob('*.json'):
139
+ self.owned(previous);history.append(read_json(previous))
140
+ active=[r for r in history if r.get('state') not in {'completed','preflight-refused','native-refused'}]
141
+ if active:
142
+ raise PackError('Unsettled native smoke operation remains; no replay')
143
+ used=[r for r in history if r.get('planSHA256')==SMOKE_PLAN and r.get('caseId')==case and r.get('state')!='preflight-refused']
144
+ same_role=lambda r:r.get('harness')==self.harness and (r.get('key')==key or
145
+ (case.startswith('stg-') and r.get('key','').split('/')[1:2]==key.split('/')[1:2]))
146
+ if len(used)>=SMOKE_CASES[case][1] or any(same_role(r) for r in used):
147
+ raise PackError('Finite smoke case budget or role already consumed')
148
+ authority=self.authority(key,row)
149
+ if self.binding(key,case)[2]!=digest:
150
+ raise PackError('Smoke binding changed during qualification')
151
+ record={'operationId':operation,'harness':self.harness,'planSHA256':SMOKE_PLAN,'planId':binding['planId'],
152
+ 'caseId':case,'key':key,'nativeIdentity':native_identity,'bindingHash':digest,'row':row,
153
+ 'bindingExpiresAt':binding['expiresAt'],'authority':authority,
154
+ 'dispatchExpiresAt':min(binding['expiresAt'],row['fixtureExpiresAt']) if case.startswith('stg-') else binding['expiresAt'],
155
+ 'state':'prepared','atUTC':datetime.datetime.now(datetime.timezone.utc).isoformat(),
156
+ 'nativeId':None,'hostedUsageVerified':False,'operationEffectVerified':False}
157
+ context={};prefix={}
158
+ for member in binding['roles']:
159
+ charter=pathlib.Path(member['cwd'])/('AGENTS.md' if self.harness=='codex' else 'CLAUDE.md');self.owned(charter)
160
+ for file in pathlib.Path(member['cwd']).rglob('*.md'):
161
+ self.owned(file);context[str(file)]=hashlib.sha256(file.read_bytes()).hexdigest()
162
+ if self.harness=='codex':
163
+ runtime=pathlib.Path(self.lib.active()['runtime'])
164
+ files=[p for folder in ['sessions','archived_sessions'] for p in (runtime/'home'/folder).rglob('*.jsonl')]
165
+ else:
166
+ state=read_json(self.lib.root/'claude/state.json')
167
+ ids={r['nativeId'] for r in binding['roles']}
168
+ files=[pathlib.Path(a['transcript']) for a in state['agents'] if a.get('desktopSessionId') in ids]
169
+ for file in files:
170
+ self.owned(file);raw=file.read_bytes();prefix[str(file)]={'bytes':len(raw),'sha256':hashlib.sha256(raw).hexdigest()}
171
+ record.update(contextFiles=context,contextRoots=[r['cwd'] for r in binding['roles']],transcriptPrefixes=prefix)
172
+ atomic_json(path,record)
173
+ refs={k:row[k] for k in ['principalId','organizationId','instanceId','logicalId','agentId','agentVersionId',
174
+ 'agentWorkspaceId','bytesWorkspaceId','contextMarkerId','contextMarkerHash','artifactMemoryId','artifactHash','fixtureExpiresAt']}
175
+ task=('Perform the explicit expiring STG test-memory/Bytes roundtrip only; test provenance is this operation marker. '
176
+ if case=='stg-context-memory-bytes' else 'Read and acknowledge only the bound peer test-memory/Bytes references. '
177
+ if case=='stg-peer-ack' else 'Read only your hosted role/context and the bound verification memory/Bytes artifact. ')
178
+ text=('Topolo finite native acceptance case '+case+'. Operation '+operation+'. '+task+
179
+ 'Use the published Topolo Agent and Bytes tools in '+row['environment']+' / '+row['contextKey']+
180
+ ', for exactly '+key+'. Retrieve actual hosted context and the referenced Bytes content; local or CLI-proxy evidence is insufficient. '+
181
+ 'Exact references: '+canonical(refs).decode()+'. No business work, spawn, additional messages, permission changes or unrelated writes. '+
182
+ 'If any reference/tool is unavailable report the exact blocker. Return one concise JSON ACK with operationId, logicalId, agentVersionId, '+
183
+ 'contextMarkerHash, artifactHash and status; target at most 256 output tokens.')
184
+ return {'record':record,'text':text,'roster':[{k:r[k] for k in ['key','nativeId','cwd','title']} for r in binding['roles']]}
185
+ except BaseException:
186
+ lock.unlink();raise
187
+
188
+ def authority(self,key,row):
189
+ """Fresh public authority, with local consent re-read after blocking I/O."""
190
+ from . import topolo,permissions
191
+ pack=key.split('/')[0]
192
+ manifest,_=self.lib.load(pack);consent=permissions.load(self.lib,pack,manifest['agents'])
193
+ if not consent or consent.get('profile')!='full-access' or consent.get('contextKey')!=row['contextKey']:
194
+ raise PackError('Smoke needs the existing exact full-access consent')
195
+ original=canonical([manifest,consent])
196
+ client=topolo.Client(row['environment'],row['contextKey'])
197
+ if topolo.identity(client)!={'userId':row['principalId'],'organizationId':row['organizationId']}:
198
+ raise PackError('Fresh smoke principal differs')
199
+ ceiling,proof=topolo.organization_execution_policy(client,consent,manifest['agents'],row['agentWorkspaceId'])
200
+ permissions.authorize_native_scope(proof)
201
+ current,_=self.lib.load(pack)
202
+ if canonical([current,permissions.load(self.lib,pack,current['agents'])])!=original:
203
+ raise PackError('Smoke consent or manifest changed during authority qualification')
204
+ return {'localHash':hashlib.sha256(original).hexdigest(),'policyHash':hashlib.sha256(canonical([ceiling,proof])).hexdigest()}
205
+
206
+ def arm(self,operation,native_identity):
207
+ """Admit only the original prepared operation; never reset its budget."""
208
+ record=self.read(operation)
209
+ lock=self.root/'.reservation';self.owned(lock)
210
+ if (record['state']!='prepared' or record['nativeIdentity']!=native_identity or
211
+ read_json(lock)!={'operationId':operation,'harness':self.harness}):
212
+ raise PackError('Original prepared smoke reservation required; no replay')
213
+ binding,row,digest=self.binding(record['key'],record['caseId'])
214
+ if digest!=record['bindingHash'] or row!=record['row']:
215
+ raise PackError('Original smoke binding changed before dispatch')
216
+ if self.authority(record['key'],row)!=record['authority']:
217
+ raise PackError('Original smoke authority changed before dispatch')
218
+ # The public qualification can block. Re-read local binding/expiry,
219
+ # descriptors and original context after it, before the durable arm.
220
+ if self.binding(record['key'],record['caseId'])[2]!=record['bindingHash']:
221
+ raise PackError('Original smoke binding changed during final qualification')
222
+ self.verify(operation)
223
+ self.mark(operation,'start-may-have-effects')
224
+ return {'operationId':operation,'nativeIdentity':native_identity,'bindingHash':digest,
225
+ 'expiresAt':record['dispatchExpiresAt'],'state':'start-may-have-effects'}
226
+
227
+ def mark(self,operation,state,**evidence):
228
+ record=self.read(operation)
229
+ allowed={'prepared':{'prepared','resume-may-have-effects','start-may-have-effects','preflight-refused'},
230
+ 'resume-may-have-effects':{'start-may-have-effects','uncertain'},
231
+ 'start-may-have-effects':{'native-delivered','native-refused','uncertain'},
232
+ 'native-delivered':{'completed','uncertain'}}
233
+ if state not in allowed.get(record['state'],set()):
234
+ raise PackError('Smoke phase cannot replay or regress')
235
+ if set(evidence)-{'nativeId','nativeStatus','ack','historyPreserved','historyCount','historyHash'} or len(canonical(evidence))>8192:
236
+ raise PackError('Bounded native smoke evidence required')
237
+ record.update(state=state,**evidence);atomic_json(self.path(operation),record)
238
+ if state in {'native-delivered','native-refused','preflight-refused'}:
239
+ lock=self.root/'.reservation';self.owned(lock)
240
+ if read_json(lock).get('operationId')!=operation:
241
+ raise PackError('Native smoke reservation custody differs')
242
+ lock.unlink()
243
+ return record
244
+
245
+ def verify(self,operation):
246
+ record=self.read(operation)
247
+ if {str(p) for root in record['contextRoots'] for p in pathlib.Path(root).rglob('*.md')}!=set(record['contextFiles']):
248
+ raise PackError('Native smoke context inventory changed')
249
+ for filename,digest in record['contextFiles'].items():
250
+ path=pathlib.Path(filename);self.owned(path)
251
+ if hashlib.sha256(path.read_bytes()).hexdigest()!=digest:
252
+ raise PackError('Native smoke context changed')
253
+ for filename,before in record['transcriptPrefixes'].items():
254
+ path=pathlib.Path(filename);self.owned(path)
255
+ with path.open('rb') as stream: prefix=stream.read(before['bytes'])
256
+ if hashlib.sha256(prefix).hexdigest()!=before['sha256']:
257
+ raise PackError('Native smoke original transcript prefix changed')
258
+ return {'contextPreserved':True,'originalTranscriptPrefixesPreserved':True}
259
+
31
260
  def permission_settings(agent, current, consent):
32
261
  from .policy import settings
33
262
  wanted=settings(agent,'codex',consent)['nativeSettings']
@@ -46,7 +275,7 @@ def request(lib,operation,params=None):
46
275
  record=read_json(lib.root/'live-bridge.json',{})
47
276
  if not record: raise PackError('This desktop profile has no live bridge. Open it through topolo-agent-pack first.')
48
277
  with socket.socket(socket.AF_UNIX,socket.SOCK_STREAM) as connection:
49
- connection.settimeout(3 if operation=='status' else 60 if operation in {'permissions-configure','owned-roster-read'} else 20)
278
+ connection.settimeout(3 if operation=='status' else 60 if operation in {'permissions-configure','owned-roster-read','smoke-start'} else 20)
50
279
  try: connection.connect(record['socket'])
51
280
  except OSError as exc: raise PackError('Managed live bridge is unavailable: '+str(exc)) from exc
52
281
  connection.sendall(canonical({'token':record['token'],'operation':operation,**({'params':params} if params is not None else {})})+b'\n')
@@ -85,6 +314,7 @@ class BackendProxy:
85
314
  self.permission_configuration=False
86
315
  self.permission_batch=None
87
316
  self._permission_configuration_inputs=None
317
+ self.smoke_busy=False
88
318
 
89
319
  async def start_child(self):
90
320
  active=self.lib.active()
@@ -152,6 +382,9 @@ class BackendProxy:
152
382
  if self.output_task: await self.output_task
153
383
 
154
384
  async def forward(self,value):
385
+ if self.smoke_busy and value.get('method') in {'thread/resume','turn/start'}:
386
+ sys.stdout.buffer.write(canonical({'id':value.get('id'),'error':{'code':-32000,
387
+ 'message':'Owned native smoke dispatch is being qualified'}})+b'\n');sys.stdout.buffer.flush();return
155
388
  if self.permission_configuration and value.get('method') in {'thread/resume','turn/start'}:
156
389
  sys.stdout.buffer.write(canonical({'id':value.get('id'),'error':{'code':-32000,'message':'Owned permission configuration in progress'}})+b'\n')
157
390
  sys.stdout.buffer.flush();return
@@ -183,7 +416,7 @@ class BackendProxy:
183
416
  'params':{'includeHidden':True,**({'cursor':cursor} if cursor else {})}})+b'\n')
184
417
  await self.child.stdin.drain()
185
418
 
186
- async def call_native(self,method,params):
419
+ async def call_native(self,method,params,before_dispatch=None):
187
420
  identity='topolo-relay-'+uuid.uuid4().hex
188
421
  request={'id':identity,'method':method,'params':params}
189
422
  if method in {'thread/resume','turn/start'}:
@@ -196,6 +429,7 @@ class BackendProxy:
196
429
  raise PackError('No owned permission configuration snapshot; native settings were not sent')
197
430
  self.configuration_current(*self._permission_configuration_inputs)
198
431
  request=self.execution.prepare(request)
432
+ if before_dispatch is not None: before_dispatch()
199
433
  future=asyncio.get_running_loop().create_future();self.native_calls[identity]=future;self.pending.add(identity)
200
434
  try:
201
435
  self.child.stdin.write(canonical(request)+b'\n');await self.child.stdin.drain()
@@ -207,6 +441,111 @@ class BackendProxy:
207
441
  raise PackError('Native intake relay refused; original request remains pending')
208
442
  return reply['result']
209
443
 
444
+ def smoke_current(self,roles,resuming=None):
445
+ if (not self.execution or self.permission_configuration or self.quiesced or not self.initialized or self.closed or
446
+ not self.child or self.child.returncode is not None or self.active_turns or self.pending or self.notifications or
447
+ (self.approvals and self.approvals.pending)):
448
+ raise PackError('Smoke requires an idle current configured native connection')
449
+ current=self.existing_runtime_intent()['roles']
450
+ effective=[{k:r.get(k) for k in original} for original in current for r in [self.execution.roles.get(original['threadId'],{})]]
451
+ if canonical(current)!=canonical(roles) or canonical(current)!=canonical(effective):
452
+ raise PackError('Smoke execution/source roster changed')
453
+ verified=set(self.execution.roles)
454
+ if resuming is not None:
455
+ if list(self.execution.pending.values())!=[resuming]:
456
+ raise PackError('Owned smoke resume custody differs')
457
+ verified.discard(resuming)
458
+ if self.execution.verified!=verified:
459
+ raise PackError('Smoke requires all owned native permission/settings ACKs on this connection')
460
+ for role in roles:
461
+ spec=role['settings']
462
+ if (spec['sandbox_mode']!='danger-full-access' or spec['approval_policy']!='never' or
463
+ not role.get('installationConsent') or spec['model_reasoning_effort'] not in self.execution.catalog.get(spec['model'],set())):
464
+ raise PackError('Smoke native full-access policy or catalog is not qualified')
465
+
466
+ async def owned_smoke(self,operation,params):
467
+ wanted={'operationId'} if operation=='smoke-read' else {'operationId','key','caseId'}
468
+ if operation not in {'smoke-start','smoke-read'} or set(params)!=wanted:
469
+ raise PackError('Only closed smoke start/read fields are supported')
470
+ store=OwnedSmoke(self.lib,'codex');op=store.operation_id(params['operationId'])
471
+ if operation=='smoke-read':
472
+ record=store.read(op)
473
+ if record['state']!='native-delivered': return self.smoke_result(record)
474
+ if self.smoke_busy or not self.execution or not self.child or self.quiesced:
475
+ raise PackError('Existing native smoke connection unavailable; no replay')
476
+ identity=hashlib.sha256(canonical([self.runtime,self.child.pid,self.protocol])).hexdigest()
477
+ if record['nativeIdentity']!=identity: return {**self.smoke_result(record),'connectionCurrent':False}
478
+ row=record['row'];role=self.execution.roles.get(row['nativeId'])
479
+ if not role or role['key']!=record['key'] or role['cwd']!=row['cwd']:
480
+ raise PackError('Native smoke read ownership differs')
481
+ native=await self.call_native('thread/read',{'threadId':row['nativeId'],'includeTurns':True})
482
+ thread=native['thread'];turns=thread.get('turns',[])
483
+ matches=[t for t in turns if t.get('id')==record['nativeId']]
484
+ if thread['id']!=row['nativeId'] or thread['cwd']!=row['cwd'] or len(matches)!=1:
485
+ raise PackError('Exact native smoke turn is unavailable')
486
+ turn=matches[0]
487
+ if turn.get('status') not in {'completed','failed','interrupted'}: return self.smoke_result(record)
488
+ prefix=turns[:record['historyCount']]
489
+ preserved=hashlib.sha256(canonical(prefix)).hexdigest()==record['historyHash']
490
+ texts=[i.get('text','') for i in turn.get('items',[]) if i.get('type')=='agentMessage']
491
+ ack=None
492
+ if texts and len(texts[-1])<=2048:
493
+ try: ack=json.loads(texts[-1])
494
+ except ValueError: pass
495
+ expected={'operationId':op,'logicalId':row['logicalId'],'agentVersionId':row['agentVersionId'],
496
+ 'contextMarkerHash':row['contextMarkerHash'],'artifactHash':row['artifactHash']}
497
+ valid=(preserved and isinstance(ack,dict) and set(ack)==set(expected)|{'status'} and
498
+ all(ack[k]==v for k,v in expected.items()) and ack['status'] in {'ok','blocked'} and turn.get('status')=='completed')
499
+ store.verify(op)
500
+ if not valid:
501
+ record=store.mark(op,'uncertain',nativeStatus=turn.get('status'),historyPreserved=preserved)
502
+ else: record=store.mark(op,'completed',nativeStatus=turn.get('status'),ack=ack,historyPreserved=True)
503
+ return self.smoke_result(record)
504
+ if self.smoke_busy:
505
+ raise PackError('Native smoke dispatch already reserved')
506
+ self.smoke_busy=True;prepared=False
507
+ try:
508
+ roles=self.existing_runtime_intent()['roles'];self.smoke_current(roles)
509
+ self.execution.set_catalog(await self.read_catalog());self.smoke_current(roles)
510
+ binding,row,_=store.binding(params['key'],params['caseId'])
511
+ if {(r['key'],r['nativeId'],r['cwd']) for r in binding['roles']}!={(r['key'],r['threadId'],r['cwd']) for r in roles}:
512
+ raise PackError('Smoke binding must cover the complete existing roster')
513
+ identity=hashlib.sha256(canonical([self.runtime,self.child.pid,self.protocol])).hexdigest()
514
+ plan=await asyncio.to_thread(store.prepare,params['key'],params['caseId'],op,identity);prepared=True
515
+ self.smoke_current(roles)
516
+ native=await self.call_native('thread/read',{'threadId':row['nativeId'],'includeTurns':True})
517
+ thread=native['thread'];history=thread.get('turns',[])
518
+ if (thread.get('id')!=row['nativeId'] or thread.get('cwd')!=row['cwd'] or thread.get('name')!=row['title'] or
519
+ thread.get('status',{}).get('type') not in {'idle','notLoaded'} or not isinstance(history,list)):
520
+ raise PackError('Native smoke descriptor is not the exact idle role')
521
+ record=store.read(op);record.update(historyCount=len(history),historyHash=hashlib.sha256(canonical(history)).hexdigest())
522
+ atomic_json(store.path(op),record)
523
+ def guard(resuming=None):
524
+ self.smoke_current(roles,resuming)
525
+ store.verify(op)
526
+ if store.binding(params['key'],params['caseId'])[2]!=record['bindingHash']:
527
+ raise PackError('Smoke binding changed before native dispatch')
528
+ guard();store.mark(op,'resume-may-have-effects')
529
+ await self.call_native('thread/resume',{'threadId':row['nativeId']},before_dispatch=lambda:guard(row['nativeId']))
530
+ guard();store.mark(op,'start-may-have-effects')
531
+ result=await self.call_native('turn/start',{'threadId':row['nativeId'],
532
+ 'input':[{'type':'text','text':plan['text'],'text_elements':[]}]},before_dispatch=guard)
533
+ turn=result.get('turn',{})
534
+ if not isinstance(turn.get('id'),str) or not turn['id']:
535
+ raise PackError('Native smoke turn identity missing')
536
+ return self.smoke_result(store.mark(op,'native-delivered',nativeId=turn['id']))
537
+ except BaseException:
538
+ if prepared:
539
+ phase=store.read(op)['state']
540
+ store.mark(op,'preflight-refused' if phase=='prepared' else 'uncertain')
541
+ raise
542
+ finally:self.smoke_busy=False
543
+
544
+ @staticmethod
545
+ def smoke_result(record):
546
+ return {k:record[k] for k in ['operationId','harness','caseId','key','state','nativeId',
547
+ 'hostedUsageVerified','operationEffectVerified', 'nativeStatus','historyPreserved','ack'] if k in record}
548
+
210
549
  async def qualify_organization(self,thread_id):
211
550
  role=self.execution.roles.get(thread_id) if self.execution else None
212
551
  if role and not self.permission_configuration and (pathlib.Path(self.runtime)/'permissions-operation.json').exists():
@@ -473,6 +812,8 @@ class BackendProxy:
473
812
  if self.quiesced: raise PackError('Use the desktop reconnect action to resume a quiesced bridge')
474
813
  elif op=='owned-roster-read':
475
814
  result=await self.owned_roster_read(message.get('params') or {})
815
+ elif op in {'smoke-start','smoke-read'}:
816
+ result=await self.owned_smoke(op,message.get('params') or {})
476
817
  elif op=='permissions-configure':
477
818
  result=await self.configure_permissions((message.get('params') or {})['pack'])
478
819
  elif op in {'approval-list','approval-details','approval-respond'}:
@@ -498,7 +839,7 @@ class BackendProxy:
498
839
  self.child.stdin.write(canonical(reply)+b'\n');await self.child.stdin.drain();self.approvals.settled(reply)
499
840
  result={'state':'native-response-forwarded','operationEffectVerified':False}
500
841
  elif op!='status': raise PackError('Unknown live bridge operation')
501
- if op not in {'approval-list','approval-details','approval-respond','permissions-configure','owned-roster-read'}:result=self.status()
842
+ if op not in {'approval-list','approval-details','approval-respond','permissions-configure','owned-roster-read','smoke-start','smoke-read'}:result=self.status()
502
843
  except Exception as exc: result={'error':str(exc)}
503
844
  writer.write(canonical(result)+b'\n');await writer.drain();writer.close();await writer.wait_closed()
504
845
 
@@ -270,7 +270,8 @@ async function nativeClaudeApprovalControl(manager, descriptor, token, net, fs,
270
270
  if (Object.keys(request).sort().join(",") !== "identity,operation,pid,token" || request.token !== token || request.pid !== descriptor.pid || request.identity !== descriptor.identity)
271
271
  throw new Error("Native approval control authentication or process identity differs.");
272
272
  const op = request.operation;
273
- if (!op || !["list", "details", "respond"].includes(op.kind) || Object.keys(op).some((k) => !["kind", "key", "intake", "fingerprint", "decision", "humanDecisionRecorded"].includes(k)))
273
+ const fields = op?.kind === "smoke-start" ? ["kind", "key", "caseId", "operationId"] : op?.kind === "smoke-read" ? ["kind", "operationId"] : ["kind", "key", "intake", "fingerprint", "decision", "humanDecisionRecorded"];
274
+ if (!op || !["list", "details", "respond", "smoke-start", "smoke-read"].includes(op.kind) || Object.keys(op).some((k) => !fields.includes(k)))
274
275
  throw new Error("Unsupported native control operation.");
275
276
  const result = await invoke(op);
276
277
  const reply = JSON.stringify({ result }) + "\n";
@@ -315,6 +316,136 @@ async function nativeClaudeApprovalControl(manager, descriptor, token, net, fs,
315
316
  manager[key] = control;
316
317
  return { state: "native-approval-control-installed", ...descriptor };
317
318
  }
319
+ async function nativeClaudeOwnedSmokeOperation(manager, members, operation, helper, fingerprint, nativeIdentity, qualifyNative = () => {
320
+ }) {
321
+ const uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
322
+ const fields = operation?.kind === "smoke-start" ? ["kind", "key", "caseId", "operationId"] : ["kind", "operationId"];
323
+ if (!operation || !["smoke-start", "smoke-read"].includes(operation.kind) || Object.keys(operation).sort().join() !== fields.sort().join() || !uuid.test(operation.operationId) || operation.kind === "smoke-start" && (!/^[a-z0-9-]+\/[a-z0-9-]+\/[a-z0-9-]+$/.test(operation.key) || !["stg-context-memory-bytes", "stg-peer-ack", "prod-role-context-ack"].includes(operation.caseId)))
324
+ throw new Error("Exact closed native smoke operation required.");
325
+ if (!members.length || members.length > 70 || new Set(members.map((m) => m.key)).size !== members.length || new Set(members.map((m) => m.sessionId)).size !== members.length) throw new Error("Unique native smoke roster required.");
326
+ const owner = /* @__PURE__ */ Symbol.for("topolo.agent-pack.owned-smoke.v1");
327
+ const rosterIdentity = fingerprint(members);
328
+ const summary = (record) => Object.fromEntries([
329
+ "operationId",
330
+ "harness",
331
+ "caseId",
332
+ "key",
333
+ "state",
334
+ "nativeId",
335
+ "hostedUsageVerified",
336
+ "operationEffectVerified",
337
+ "nativeStatus",
338
+ "historyPreserved",
339
+ "ack"
340
+ ].filter((k) => k in record).map((k) => [k, record[k]]));
341
+ const check = (member2, session) => {
342
+ if (!session || session.sessionId !== member2.sessionId || session.cwd !== member2.cwd || session.title !== member2.title || session.isArchived || session.backend?.kind !== "local" || session.backend.remoteTarget || session.worktreePath || session.worktreeLazy || session.spawnedFrom || session.isRunning || session.starting || session.pendingToolPermissions?.length || manager.hasLosableWork(member2.sessionId) || session.model !== member2.model || session.effort !== member2.effort || session.permissionMode !== member2.permissionMode || member2.permissionMode !== "bypassPermissions" || !member2.permissionConsent || !manager.getAvailableCodeModels().includes(member2.model)) throw new Error("Exact idle native smoke ownership/settings unavailable.");
343
+ };
344
+ const current = () => {
345
+ qualifyNative();
346
+ if (manager[/* @__PURE__ */ Symbol.for("topolo.agent-pack.native-operation.v1")] || fingerprint(members) !== rosterIdentity)
347
+ throw new Error("Unresolved native configuration or changed smoke roster remains.");
348
+ for (const member2 of members) check(member2, manager.getSession(member2.sessionId));
349
+ };
350
+ const ready = async () => {
351
+ current();
352
+ for (const member2 of members) {
353
+ const descriptor = await manager.getSessionWhenLoaded(member2.sessionId, "topolo-owned-smoke", { warnOnMiss: false });
354
+ check(member2, descriptor);
355
+ check(member2, manager.getSession(member2.sessionId));
356
+ }
357
+ current();
358
+ };
359
+ if (operation.kind === "smoke-read") {
360
+ const record = await helper("read", { operation: operation.operationId });
361
+ if (record.state !== "native-delivered") return summary(record);
362
+ if (record.nativeIdentity !== nativeIdentity) return { ...summary(record), connectionCurrent: false };
363
+ const member2 = members.find((m) => m.key === record.key && m.sessionId === record.row.nativeId && m.cwd === record.row.cwd);
364
+ if (!member2) throw new Error("Native smoke read custody differs.");
365
+ const session = await manager.getSession(member2.sessionId);
366
+ if (session?.isRunning || session?.starting || manager[owner]) return summary(record);
367
+ check(member2, session);
368
+ const history = await manager.getTranscript(member2.sessionId);
369
+ if (!Array.isArray(history)) throw new Error("Qualified native smoke transcript API unavailable.");
370
+ const same = fingerprint(history.slice(0, record.historyCount)) === record.historyHash;
371
+ const markers = history.map((m, index) => m.uuid === record.operationId ? index : -1).filter((i) => i >= 0);
372
+ const marker = markers[0] ?? -1;
373
+ const user = markers.length === 1 && marker === record.historyCount && history[marker]?.type === "user" ? marker : -1;
374
+ const nextUser = history.findIndex((m, index) => index > user && m.type === "user");
375
+ const assistants = user < 0 ? [] : history.slice(user + 1, nextUser < 0 ? history.length : nextUser).filter((m) => m.type === "assistant");
376
+ const text = assistants.at(-1)?.message?.content?.filter((b) => b.type === "text").map((b) => b.text).join("");
377
+ let ack;
378
+ if (typeof text === "string" && text.length <= 2048) {
379
+ try {
380
+ ack = JSON.parse(text);
381
+ } catch {
382
+ }
383
+ }
384
+ const expected = {
385
+ operationId: record.operationId,
386
+ logicalId: record.row.logicalId,
387
+ agentVersionId: record.row.agentVersionId,
388
+ contextMarkerHash: record.row.contextMarkerHash,
389
+ artifactHash: record.row.artifactHash
390
+ };
391
+ const valid = same && Number.isSafeInteger(record.historyCount) && record.historyCount >= 0 && ack && typeof ack === "object" && !Array.isArray(ack) && Object.keys(ack).sort().join() === [...Object.keys(expected), "status"].sort().join() && Object.entries(expected).every(([k, v]) => ack[k] === v) && ["ok", "blocked"].includes(ack.status);
392
+ await helper("verify", { operation: operation.operationId });
393
+ return summary(await helper("mark", {
394
+ operation: operation.operationId,
395
+ state: "uncertain",
396
+ nativeStatus: "terminal-evidence-unavailable",
397
+ historyPreserved: same,
398
+ ...valid ? { ack } : {}
399
+ }));
400
+ }
401
+ if (manager[owner]) throw new Error("Native smoke dispatch already reserved.");
402
+ const member = members.find((m) => m.key === operation.key);
403
+ if (!member) throw new Error("Smoke role is not natively owned.");
404
+ manager[owner] = operation.operationId;
405
+ let prepared = false;
406
+ try {
407
+ await ready();
408
+ const plan = await helper("prepare", {
409
+ key: operation.key,
410
+ case: operation.caseId,
411
+ operation: operation.operationId,
412
+ native_identity: nativeIdentity
413
+ });
414
+ prepared = true;
415
+ const actual = plan.roster.map((r) => [r.key, r.nativeId, r.cwd, r.title]).sort();
416
+ const wanted = members.map((m) => [m.key, m.sessionId, m.cwd, m.title]).sort();
417
+ if (JSON.stringify(actual) !== JSON.stringify(wanted)) throw new Error("Smoke binding must match the complete owned native roster.");
418
+ await ready();
419
+ await helper("verify", { operation: operation.operationId });
420
+ const history = await manager.getTranscript(member.sessionId);
421
+ if (!Array.isArray(history)) throw new Error("Qualified native smoke transcript API unavailable.");
422
+ await helper("mark", { operation: operation.operationId, state: "prepared", historyCount: history.length, historyHash: fingerprint(history) });
423
+ await ready();
424
+ const admission = await helper("arm", { operation: operation.operationId, native_identity: nativeIdentity });
425
+ current();
426
+ if (admission.operationId !== operation.operationId || admission.nativeIdentity !== nativeIdentity || admission.state !== "start-may-have-effects" || admission.bindingHash !== plan.record.bindingHash || !/^[a-f0-9]{64}$/.test(admission.bindingHash) || !Number.isFinite(admission.expiresAt) || admission.expiresAt !== plan.record.dispatchExpiresAt || Date.now() >= admission.expiresAt * 1e3)
427
+ throw new Error("Original smoke final dispatch admission expired or differs.");
428
+ const ack = await manager.sendMessage(
429
+ member.sessionId,
430
+ plan.text,
431
+ void 0,
432
+ { messageUuid: operation.operationId, initiator: "agent", origin: { kind: "task-notification" } }
433
+ );
434
+ return summary(await helper("mark", {
435
+ operation: operation.operationId,
436
+ state: ack?.delivered === true ? "native-delivered" : ack?.delivered === false ? "native-refused" : "uncertain",
437
+ nativeId: operation.operationId
438
+ }));
439
+ } catch (error) {
440
+ if (prepared) {
441
+ const record = await helper("read", { operation: operation.operationId });
442
+ await helper("mark", { operation: operation.operationId, state: record.state === "prepared" ? "preflight-refused" : "uncertain" });
443
+ }
444
+ throw error;
445
+ } finally {
446
+ delete manager[owner];
447
+ }
448
+ }
318
449
  async function nativeClaudeApprovalOperation(manager, routes, operation, fingerprint, checkpoint, history = []) {
319
450
  const symbol = /* @__PURE__ */ Symbol.for("topolo.agent-pack.approvals.v1");
320
451
  if (!routes.length || routes.length > 100 || new Set(routes.map((r) => r.sessionId)).size !== routes.length || new Set(routes.map((r) => r.key)).size !== routes.length) throw new Error("Unique owned approval roster required.");
@@ -496,13 +627,13 @@ async function nativeClaudeApprovalOperation(manager, routes, operation, fingerp
496
627
 
497
628
  // src/lib/claude-pack-backend.ts
498
629
  var CLAUDE_NATIVE_QUALIFIER = {
499
- version: "2.31226.1",
500
- manager: "index.chunk-BCHP6ll9.js",
501
- sidebar: "index.chunk-4-bSRKfE.js",
630
+ version: "2.37337.1",
631
+ manager: "index.chunk-BolytlRP.js",
632
+ sidebar: "index.chunk-BoQoqlAs.js",
502
633
  hashes: {
503
- "index.chunk-BCHP6ll9.js": "ec7c7cda3dd597a3cce034cc6eaea52e6e2b10d1b24f0eb9aee3964638afadf2",
504
- "index.chunk-4-bSRKfE.js": "594213a92b61bb64a3d61695d2370b624981af91f359c18832debb42a7814d86",
505
- "index.chunk-Cyt2WTko.js": "29738ec09f4bf44d511caae628b0f786e35956848e6a18c3159c9928d6ac1ad9"
634
+ "index.chunk-BolytlRP.js": "e387edfb06a34a59d99b54c04365f6e32d0108e8710124b4d1a628eea726d030",
635
+ "index.chunk-BoQoqlAs.js": "5c07a7e836d98dcd6afcfc2a62619e71d173a136ecb9af847716ea377b58b1df",
636
+ "index.chunk-BWk6pVUt.js": "f7d74e113f34761a675816fe2abf65b3ddc190327ee7623d9c822f7b59cceaca"
506
637
  }
507
638
  };
508
639
  function validateInspectorEndpoint(endpoint) {
@@ -615,6 +746,7 @@ function prepareClaudeNativePlan(store, pack, policyPath, mode = "configure") {
615
746
  contextInventories,
616
747
  approvalRoutes,
617
748
  installationConsent: policy.installationConsent,
749
+ libraryRoot: root,
618
750
  sourceRevision: policy.sourceRevision,
619
751
  mode,
620
752
  activeBefore: active
@@ -630,6 +762,9 @@ function qualifyPermissionConsent(policy, key, records, full) {
630
762
  function nativePackExpression(pid, plan, journal, apply) {
631
763
  if (!Number.isSafeInteger(pid) || pid < 1) throw new Error("Explicit Claude main-process PID required.");
632
764
  const qualifier = CLAUDE_NATIVE_QUALIFIER;
765
+ const here = dirname(fileURLToPath(import.meta.url));
766
+ const smokeSource = [resolve(here, "../../agent-packs/src"), resolve(here, "../agent-packs/src")].find(existsSync);
767
+ if (!smokeSource) throw new Error("Bundled owned smoke source unavailable.");
633
768
  return `(async () => {
634
769
  const expected = ${JSON.stringify({ pid, qualifier, journal, apply })};
635
770
  if (process.pid !== expected.pid || typeof process.getBuiltinModule !== 'function') throw new Error('Claude process identity or Node runtime differs.');
@@ -659,14 +794,24 @@ function nativePackExpression(pid, plan, journal, apply) {
659
794
  throw new Error('Context or transcript changed during configuration: ' + file);
660
795
  };
661
796
  const result = await (${nativeClaudePackOperation.toString()})(manager,
662
- value => transport.U(value, transport.V),
797
+ value => transport.P(value, transport.M),
663
798
  { members: ${JSON.stringify(plan.members)}, outgoing: ${JSON.stringify(plan.outgoing)},
664
799
  operationId: ${JSON.stringify(plan.operationId)}, mode: ${JSON.stringify(plan.recovery ? "recover" : plan.mode)},
665
800
  noEffects: ${JSON.stringify(plan.noEffects)},
666
801
  deadline: Date.now() + 45000 }, expected.apply, checkpoint, verifyContext);
667
802
  ${plan.approvalRoutes.length ? `const routes = ${JSON.stringify(plan.approvalRoutes)};
668
803
  const fingerprint = value => crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');
669
- const invoke = op => (${nativeClaudeApprovalOperation.toString()})(manager, routes, op, fingerprint,
804
+ const qualifyNative = () => {
805
+ if (process.pid !== expected.pid || load('electron').app.getVersion() !== expected.qualifier.version) throw new Error('Native process changed.');
806
+ for (const [file, hash] of Object.entries(expected.qualifier.hashes))
807
+ if (crypto.createHash('sha256').update(fs.readFileSync(base + file)).digest('hex') !== hash) throw new Error('Native module changed.');
808
+ };
809
+ const smokeIdentity = fingerprint({pid: expected.pid, qualifier: expected.qualifier, routes});
810
+ const invoke = op => ['smoke-start', 'smoke-read'].includes(op.kind) ?
811
+ (${nativeClaudeOwnedSmokeOperation.toString()})(manager, ${JSON.stringify(plan.members)}, op,
812
+ (action, payload) => (${runClaudeSmokeHelper.toString()})(${JSON.stringify(plan.libraryRoot)}, action, payload,
813
+ ${JSON.stringify(realpathSync(smokeSource))}, load('node:child_process').spawn), fingerprint, smokeIdentity, qualifyNative) :
814
+ (${nativeClaudeApprovalOperation.toString()})(manager, routes, op, fingerprint,
670
815
  row => checkpoint({kind: 'approval', ...row}),
671
816
  fs.readFileSync(expected.journal, 'utf8').split('\\n').filter(Boolean).map(line => JSON.parse(line)));
672
817
  if (expected.apply) {
@@ -676,11 +821,6 @@ function nativePackExpression(pid, plan, journal, apply) {
676
821
  const stat = fs.lstatSync(control.secretPath);
677
822
  if (!stat.isFile() || stat.isSymbolicLink() || stat.uid !== process.getuid() || (stat.mode & 63)) throw new Error('Unowned native control secret.');
678
823
  const secret = JSON.parse(fs.readFileSync(control.secretPath, 'utf8'));
679
- const qualifyNative = () => {
680
- if (process.pid !== expected.pid || load('electron').app.getVersion() !== expected.qualifier.version) throw new Error('Native process changed.');
681
- for (const [file, hash] of Object.entries(expected.qualifier.hashes))
682
- if (crypto.createHash('sha256').update(fs.readFileSync(base + file)).digest('hex') !== hash) throw new Error('Native module changed.');
683
- };
684
824
  result.approvalRouting.control = await (${nativeClaudeApprovalControl.toString()})(manager,
685
825
  {socket: control.socket, pid: expected.pid, identity: fingerprint(routes)}, secret.token,
686
826
  load('node:net'), fs, invoke, qualifyNative, checkpoint);
@@ -935,6 +1075,47 @@ async function configureClaudePack(options, evaluate = evaluateClaudeMain, quali
935
1075
  await release();
936
1076
  }
937
1077
  }
1078
+ function runClaudeSmokeHelper(root, action, payload, source, launch = spawn) {
1079
+ if (!["prepare", "arm", "mark", "read", "verify"].includes(action) || typeof root !== "string" || typeof source !== "string")
1080
+ return Promise.reject(new Error("Closed owned smoke helper required."));
1081
+ const script = 'import sys,json\nsys.path.insert(0,sys.argv[3])\nfrom topolo_agent_pack.storage import Library\nfrom topolo_agent_pack.live import OwnedSmoke\ns=OwnedSmoke(Library(sys.argv[1]),"claude")\np=json.loads(sys.stdin.read(16384))\na=sys.argv[2]\nr=s.prepare(**p) if a=="prepare" else s.arm(**p) if a=="arm" else s.mark(**p) if a=="mark" else s.verify(p["operation"]) if a=="verify" else s.read(p["operation"])\nprint(json.dumps(r))';
1082
+ return new Promise((done, fail) => {
1083
+ const child = launch("python3", ["-I", "-B", "-c", script, root, action, source], { stdio: ["pipe", "pipe", "pipe"] });
1084
+ let output = "";
1085
+ let settled = false;
1086
+ const finish = (error, value) => {
1087
+ if (settled) return;
1088
+ settled = true;
1089
+ clearTimeout(timer);
1090
+ error ? fail(error) : done(value);
1091
+ };
1092
+ const timer = setTimeout(() => {
1093
+ child.kill("SIGTERM");
1094
+ finish(new Error("Owned smoke helper timed out; reconcile without replay."));
1095
+ }, 6e4);
1096
+ child.stdout.setEncoding("utf8");
1097
+ child.stderr.resume();
1098
+ child.stdout.on("data", (data) => {
1099
+ output += data;
1100
+ if (Buffer.byteLength(output) > 1048576) {
1101
+ child.kill("SIGTERM");
1102
+ finish(new Error("Owned smoke evidence exceeds bounded transport."));
1103
+ }
1104
+ });
1105
+ child.on("error", () => finish(new Error("Bundled owned smoke helper unavailable.")));
1106
+ child.on("close", (code) => {
1107
+ if (settled) return;
1108
+ if (code !== 0) return finish(new Error("Owned smoke qualification refused; preserve its journal."));
1109
+ try {
1110
+ finish(void 0, JSON.parse(output));
1111
+ } catch {
1112
+ finish(new Error("Invalid owned smoke helper result."));
1113
+ }
1114
+ });
1115
+ child.stdin.on("error", () => finish(new Error("Owned smoke helper input failed; reconcile without replay.")));
1116
+ child.stdin.end(JSON.stringify(payload));
1117
+ });
1118
+ }
938
1119
  function requestClaudeApprovalControl(connection, operation, token, connect = createConnection) {
939
1120
  const parent = lstatSync(dirname(connection.socket));
940
1121
  const socket = lstatSync(connection.socket);
@@ -951,7 +1132,7 @@ function requestClaudeApprovalControl(connection, operation, token, connect = cr
951
1132
  error ? fail(error) : done(result);
952
1133
  };
953
1134
  client.setEncoding("utf8");
954
- client.setTimeout(1e4, () => finish(new Error("Native approval response timed out; reconcile without replay.")));
1135
+ client.setTimeout(operation.kind === "smoke-start" ? 6e4 : 1e4, () => finish(new Error("Native approval response timed out; reconcile without replay.")));
955
1136
  client.on("connect", () => client.write(JSON.stringify({ token, pid: connection.pid, identity: connection.identity, operation }) + "\n"));
956
1137
  client.on("data", (chunk) => {
957
1138
  reply += chunk;
@@ -985,6 +1166,15 @@ async function operateClaudeApprovals(options, request = requestClaudeApprovalCo
985
1166
  if (!secretPath.startsWith(realpathSync(privateDirectory) + sep) || !stat.isFile() || stat.isSymbolicLink() || stat.uid !== process.getuid?.() || stat.mode & 63) throw new Error("Unowned native control secret.");
986
1167
  const secret = JSON.parse(readFileSync(secretPath, "utf8"));
987
1168
  if (!/^[a-f0-9]{64}$/.test(secret.token) || !/^[a-f0-9]{64}$/.test(connection.identity)) throw new Error("Native control identity is invalid.");
1169
+ if (options.kind === "smoke-start" || options.kind === "smoke-read") return request(
1170
+ connection,
1171
+ {
1172
+ kind: options.kind,
1173
+ operationId: options.operationId,
1174
+ ...options.kind === "smoke-start" ? { key: options.key, caseId: options.caseId } : {}
1175
+ },
1176
+ secret.token
1177
+ );
988
1178
  return request(connection, {
989
1179
  kind: options.kind,
990
1180
  key: options.key,
@@ -994,6 +1184,12 @@ async function operateClaudeApprovals(options, request = requestClaudeApprovalCo
994
1184
  humanDecisionRecorded: options.humanDecisionRecorded
995
1185
  }, secret.token);
996
1186
  }
1187
+ async function operateClaudeSmoke(options, request = requestClaudeApprovalControl) {
1188
+ const fields = options.kind === "smoke-start" ? ["store", "kind", "operationId", "key", "caseId"] : ["store", "kind", "operationId"];
1189
+ if (!["smoke-start", "smoke-read"].includes(options.kind) || Object.keys(options).some((k) => !fields.includes(k)) || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(options.operationId) || options.kind === "smoke-start" && (!/^[a-z0-9-]+\/[a-z0-9-]+\/[a-z0-9-]+$/.test(options.key ?? "") || !["stg-context-memory-bytes", "stg-peer-ack", "prod-role-context-ack"].includes(options.caseId ?? "")))
1190
+ throw new Error("Exact closed native smoke fields required.");
1191
+ return operateClaudeApprovals(options, request);
1192
+ }
997
1193
  export {
998
1194
  CLAUDE_NATIVE_QUALIFIER,
999
1195
  configureClaudePack,
@@ -1001,8 +1197,10 @@ export {
1001
1197
  lockClaudeLibrary,
1002
1198
  nativePackExpression,
1003
1199
  operateClaudeApprovals,
1200
+ operateClaudeSmoke,
1004
1201
  prepareClaudeNativePlan,
1005
1202
  requestClaudeApprovalControl,
1203
+ runClaudeSmokeHelper,
1006
1204
  snapshotClaudeMarkdown,
1007
1205
  validateInspectorEndpoint,
1008
1206
  verifyClaudeOrganizationPolicy
package/dist/index.js CHANGED
@@ -8,9 +8,43 @@ import { fileURLToPath } from "url";
8
8
  import { homedir } from "os";
9
9
  function registerAgentPacksCommand(program2) {
10
10
  const packs = program2.command("agent-packs").description("Install and operate Topolo Agent Packs in supported local hosts").option("--store <path>", "Portable pack library (default ~/AgentPacks)");
11
+ packs.command("smoke").description("Start or read one closed, bound native acceptance case").argument("<operation>", "start or read").argument("<identity>", "Owned role key for start, existing operation UUID for read").option("--case <id>", "Finite compiled acceptance case").option("--operation-id <id>", "Unique native operation UUID for start").option("--harness <name>", "codex or claude", "codex").allowExcessArguments(false).action(async (operation, identity, options) => {
12
+ const uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
13
+ if (!["start", "read"].includes(operation) || !["codex", "claude"].includes(options.harness) || (operation === "start" ? !/^[a-z0-9-]+\/[a-z0-9-]+\/[a-z0-9-]+$/.test(identity) || !["stg-context-memory-bytes", "stg-peer-ack", "prod-role-context-ack"].includes(options.case ?? "") || !uuid.test(options.operationId ?? "") : !uuid.test(identity) || options.case !== void 0 || options.operationId !== void 0))
14
+ throw new Error("Exact closed smoke operation, role/case and UUID required.");
15
+ const store = packs.opts().store ?? resolve(homedir(), "AgentPacks");
16
+ if (options.harness === "claude") {
17
+ const { operateClaudeSmoke } = await import("./claude-pack-backend-3YJNKMYD.js");
18
+ console.log(JSON.stringify(await operateClaudeSmoke({
19
+ store,
20
+ kind: "smoke-" + operation,
21
+ operationId: operation === "start" ? options.operationId : identity,
22
+ ...operation === "start" ? { key: identity, caseId: options.case } : {}
23
+ }), null, 2));
24
+ return;
25
+ }
26
+ const here = dirname(fileURLToPath(import.meta.url));
27
+ const adapter = [resolve(here, "../agent-packs/topolo-agent-pack"), resolve(here, "../../agent-packs/topolo-agent-pack")].find(existsSync);
28
+ if (!adapter) throw new Error("Installed bundled pack adapter unavailable.");
29
+ const result = spawnSync(
30
+ "python3",
31
+ [
32
+ adapter,
33
+ "--store",
34
+ store,
35
+ "smoke",
36
+ operation,
37
+ identity,
38
+ ...operation === "start" ? ["--case", options.case, "--operation-id", options.operationId] : []
39
+ ],
40
+ { stdio: "inherit", env: { ...process.env, PYTHONDONTWRITEBYTECODE: "1", TOPOLO_AGENT_PACK_PUBLIC_CLI: "1" } }
41
+ );
42
+ if (result.error) throw new Error("Python 3.11 or later is required for local packs.");
43
+ if (result.status !== 0) process.exitCode = result.status ?? 2;
44
+ });
11
45
  packs.command("configure").description("Qualify and configure an adopted Claude roster through its live native backend").argument("<pack>").option("--harness <name>", "Host adapter", "claude").requiredOption("--policy <path>", "Reviewed execution policy JSON").requiredOption("--endpoint <url>", "Explicit loopback main-process inspector endpoint, enabled separately with authorization").requiredOption("--pid <pid>", "Expected Claude main-process PID").option("--apply", "Apply native groups, pins, models, effort and permission modes; otherwise preflight only").action(async (pack, options) => {
12
46
  if (options.harness !== "claude") throw new Error("Native configure is currently qualified only for Claude Desktop.");
13
- const { configureClaudePack } = await import("./claude-pack-backend-AWCWJJA5.js");
47
+ const { configureClaudePack } = await import("./claude-pack-backend-3YJNKMYD.js");
14
48
  const result = await configureClaudePack({
15
49
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
16
50
  pack,
@@ -62,7 +96,7 @@ function registerAgentPacksCommand(program2) {
62
96
  throw new Error("Only a one-request once/deny response is supported.");
63
97
  decision = response.decision;
64
98
  }
65
- const { operateClaudeApprovals } = await import("./claude-pack-backend-AWCWJJA5.js");
99
+ const { operateClaudeApprovals } = await import("./claude-pack-backend-3YJNKMYD.js");
66
100
  console.log(JSON.stringify(await operateClaudeApprovals({
67
101
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
68
102
  kind: args[0],
@@ -77,7 +111,7 @@ function registerAgentPacksCommand(program2) {
77
111
  if (options.policy || options.endpoint || options.pid || options.apply) {
78
112
  if (options.harness !== "claude" || !options.policy || !options.endpoint || !options.pid || args.length !== 1)
79
113
  throw new Error("Native switch/recovery requires one Claude pack, --policy, --endpoint and --pid.");
80
- const { configureClaudePack } = await import("./claude-pack-backend-AWCWJJA5.js");
114
+ const { configureClaudePack } = await import("./claude-pack-backend-3YJNKMYD.js");
81
115
  const result2 = await configureClaudePack({
82
116
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
83
117
  pack: args[0],
@@ -124,7 +158,7 @@ import { platform as platform2 } from "os";
124
158
  import { createInterface } from "readline/promises";
125
159
  import { stdin as input } from "process";
126
160
 
127
- // node_modules/.pnpm/@topolo+sdk@0.23.19/node_modules/@topolo/sdk/dist/index.js
161
+ // node_modules/.pnpm/@topolo+sdk@0.23.20/node_modules/@topolo/sdk/dist/index.js
128
162
  function record(value) {
129
163
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
130
164
  }
@@ -3734,10 +3768,11 @@ async function pollUntilApproved(params) {
3734
3768
  throw new Error("Device code expired. Run `topolo auth login` again.");
3735
3769
  }
3736
3770
 
3737
- // node_modules/.pnpm/@topolo+sdk@0.23.19/node_modules/@topolo/sdk/dist/node/credential-store.js
3738
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, lstat, readdir as readdir2, rename as rename2, link } from "fs/promises";
3771
+ // node_modules/.pnpm/@topolo+sdk@0.23.20/node_modules/@topolo/sdk/dist/node/credential-store.js
3772
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, lstat, readdir as readdir2 } from "fs/promises";
3739
3773
  import { existsSync as existsSync2, mkdirSync, lstatSync, readdirSync, readFileSync as readFileSync2, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync, rmdirSync, linkSync } from "fs";
3740
3774
  import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
3775
+ import { constants, fstatSync, readSync } from "fs";
3741
3776
  import { homedir as homedir2, platform } from "os";
3742
3777
  import { dirname as dirname2, join as join3 } from "path";
3743
3778
  import { createHash, randomUUID } from "crypto";
@@ -4603,6 +4638,7 @@ async function resolveCredential(environment = selectedEnvironment()) {
4603
4638
  var REFRESH_SKEW_SECONDS = 60;
4604
4639
  var CONFIG_LOCK_RETRY_MS = 25;
4605
4640
  var CONFIG_LOCK_TIMEOUT_MS = 5e3;
4641
+ var CONFIG_LOCK_DEAD_OWNER_MIN_AGE_MS = 2e3;
4606
4642
  var ConfigOwnershipError = class extends TopoloSdkError2 {
4607
4643
  constructor() {
4608
4644
  super("state_conflict", "Credential configuration ownership cannot be verified. Retain the unresolved operation instead of starting another renewal.");
@@ -4617,32 +4653,145 @@ function lockEntryExists(path) {
4617
4653
  throw new ConfigOwnershipError();
4618
4654
  }
4619
4655
  }
4656
+ function lockIdentity(entry) {
4657
+ return [entry.dev, entry.ino, entry.uid, entry.mode, entry.nlink, entry.size, entry.mtimeMs, entry.ctimeMs];
4658
+ }
4659
+ function privateLockDirectory(path) {
4660
+ const entry = lstatSync(path);
4661
+ if (typeof process.getuid !== "function" || !entry.isDirectory() || entry.isSymbolicLink() || entry.uid !== process.getuid() || (entry.mode & 511) !== 448) throw new ConfigOwnershipError();
4662
+ return entry;
4663
+ }
4664
+ function readPrivateLockMarker(path) {
4665
+ const before = lstatSync(path);
4666
+ if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.size > 512 || before.uid !== process.getuid?.() || (before.mode & 511) !== 384) throw new ConfigOwnershipError();
4667
+ const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
4668
+ try {
4669
+ if (JSON.stringify(lockIdentity(fstatSync(fd))) !== JSON.stringify(lockIdentity(before))) throw new ConfigOwnershipError();
4670
+ const bytes = Buffer.alloc(513);
4671
+ const count = readSync(fd, bytes, 0, bytes.length, 0);
4672
+ if (count !== before.size || count > 512 || JSON.stringify(lockIdentity(fstatSync(fd))) !== JSON.stringify(lockIdentity(before)) || JSON.stringify(lockIdentity(lstatSync(path))) !== JSON.stringify(lockIdentity(before))) throw new ConfigOwnershipError();
4673
+ return bytes.subarray(0, count).toString("utf8");
4674
+ } finally {
4675
+ closeSync(fd);
4676
+ }
4677
+ }
4678
+ var OWNER_MARKER_NAME = /^owner-[a-f0-9-]{36}\.json$/;
4679
+ function readOwnerMarker(lockPath, name) {
4680
+ const bytes = readPrivateLockMarker(join3(lockPath, name));
4681
+ const owner = JSON.parse(bytes);
4682
+ if (owner.version !== 1 || Object.keys(owner).sort().join(",") !== "identity,pid,version" || name !== "owner-" + owner.identity + ".json" || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) throw new ConfigOwnershipError();
4683
+ return { bytes, pid: owner.pid };
4684
+ }
4685
+ function writePrivateLockRecord(path, bytes) {
4686
+ const fd = openSync(path, "wx", 384);
4687
+ try {
4688
+ writeFileSync(fd, bytes);
4689
+ fsyncSync(fd);
4690
+ } finally {
4691
+ closeSync(fd);
4692
+ }
4693
+ }
4694
+ function deadOwnerMarker(lockPath, ignoreOwnFence = false) {
4695
+ try {
4696
+ if (!ignoreOwnFence && lockEntryExists(lockPath + ".reconcile")) return null;
4697
+ const directory = privateLockDirectory(lockPath);
4698
+ const entries = readdirSync(lockPath);
4699
+ if (entries.length !== 1 || !OWNER_MARKER_NAME.test(entries[0])) return null;
4700
+ const name = entries[0];
4701
+ const { bytes, pid } = readOwnerMarker(lockPath, name);
4702
+ if (Date.now() - lstatSync(join3(lockPath, name)).mtimeMs < CONFIG_LOCK_DEAD_OWNER_MIN_AGE_MS) return null;
4703
+ try {
4704
+ process.kill(pid, 0);
4705
+ return null;
4706
+ } catch (error) {
4707
+ if (error.code !== "ESRCH") return null;
4708
+ }
4709
+ return { directory, name, bytes, pid };
4710
+ } catch {
4711
+ return null;
4712
+ }
4713
+ }
4714
+ function reclaimDeadOwnerLock(lockPath, markerName, marker) {
4715
+ const observed = deadOwnerMarker(lockPath);
4716
+ if (!observed) return false;
4717
+ const fence = lockPath + ".reconcile";
4718
+ try {
4719
+ mkdirSync(fence, { mode: 448 });
4720
+ } catch {
4721
+ return false;
4722
+ }
4723
+ const fenceMarker = join3(fence, markerName);
4724
+ const quarantine = join3(dirname2(lockPath), ".credential-reclaim-" + randomUUID2());
4725
+ let captured;
4726
+ let reclaimed = false;
4727
+ try {
4728
+ writePrivateLockRecord(fenceMarker, marker);
4729
+ const current = deadOwnerMarker(lockPath, true);
4730
+ if (!current || current.name !== observed.name || current.bytes !== observed.bytes || current.directory.dev !== observed.directory.dev || current.directory.ino !== observed.directory.ino) return false;
4731
+ mkdirSync(quarantine, { mode: 448 });
4732
+ writePrivateLockRecord(join3(quarantine, "receipt.json"), JSON.stringify({
4733
+ schemaVersion: 1,
4734
+ disposition: "DEAD_OWNER_RECLAIMED",
4735
+ lockPath,
4736
+ deadOwnerMarker: current.name,
4737
+ deadOwnerPid: current.pid,
4738
+ deadOwnerEvidence: "ESRCH",
4739
+ reclaimedByPid: process.pid,
4740
+ reclaimedAt: (/* @__PURE__ */ new Date()).toISOString(),
4741
+ configCredentialOrOAuthEffectsByThisReclaim: "none"
4742
+ }, null, 2));
4743
+ captured = join3(quarantine, current.name);
4744
+ renameSync(join3(lockPath, current.name), captured);
4745
+ const directory = privateLockDirectory(lockPath);
4746
+ if (directory.dev !== observed.directory.dev || directory.ino !== observed.directory.ino || readdirSync(lockPath).length) throw new ConfigOwnershipError();
4747
+ writePrivateLockRecord(join3(lockPath, markerName), marker);
4748
+ reclaimed = true;
4749
+ } catch {
4750
+ if (captured && !reclaimed) {
4751
+ try {
4752
+ linkSync(captured, join3(lockPath, observed.name));
4753
+ } catch {
4754
+ }
4755
+ }
4756
+ } finally {
4757
+ try {
4758
+ if (readPrivateLockMarker(fenceMarker) !== marker || readdirSync(fence).length !== 1) throw new ConfigOwnershipError();
4759
+ unlinkSync(fenceMarker);
4760
+ rmdirSync(fence);
4761
+ } catch {
4762
+ reclaimed = false;
4763
+ }
4764
+ }
4765
+ return reclaimed;
4766
+ }
4767
+ var RELEASE_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
4620
4768
  async function withConfigWriteLock(operation) {
4621
4769
  const lockPath = `${configPath()}.lock`;
4622
4770
  await mkdir2(dirname2(lockPath), { recursive: true });
4623
4771
  const startedAt = performance.now();
4772
+ const identity = randomUUID2();
4773
+ const markerName = "owner-" + identity + ".json";
4774
+ const markerPath = join3(lockPath, markerName);
4775
+ const marker = JSON.stringify({ version: 1, identity, pid: process.pid });
4624
4776
  while (true) {
4625
4777
  try {
4626
4778
  if (lockEntryExists(lockPath + ".reconcile")) throw Object.assign(new Error(), { code: "EEXIST" });
4627
- await mkdir2(lockPath, { mode: 448 });
4779
+ mkdirSync(lockPath, { mode: 448 });
4628
4780
  if (lockEntryExists(lockPath + ".reconcile")) {
4629
4781
  throw new ConfigOwnershipError();
4630
4782
  }
4783
+ writeFileSync(markerPath, marker, { flag: "wx", mode: 384 });
4631
4784
  break;
4632
4785
  } catch (error) {
4633
4786
  if (error.code !== "EEXIST") throw error;
4787
+ if (reclaimDeadOwnerLock(lockPath, markerName, marker)) break;
4634
4788
  if (performance.now() - startedAt >= CONFIG_LOCK_TIMEOUT_MS) {
4635
4789
  throw new TopoloSdkError2("state_conflict", "Credential configuration is still held or unresolved. This waiter did not dispatch a renewal.");
4636
4790
  }
4637
4791
  await new Promise((resolve22) => setTimeout(resolve22, CONFIG_LOCK_RETRY_MS));
4638
4792
  }
4639
4793
  }
4640
- const directory = await lstat(lockPath);
4641
- const identity = randomUUID2();
4642
- const markerName = "owner-" + identity + ".json";
4643
- const markerPath = join3(lockPath, markerName);
4644
- const marker = JSON.stringify({ version: 1, identity, pid: process.pid });
4645
- await writeFile2(markerPath, marker, { flag: "wx", mode: 384 });
4794
+ const directory = lstatSync(lockPath);
4646
4795
  const assertDirectory = async () => {
4647
4796
  const current = await lstat(lockPath);
4648
4797
  if (lockEntryExists(lockPath + ".reconcile") || !current.isDirectory() || current.isSymbolicLink() || current.dev !== directory.dev || current.ino !== directory.ino)
@@ -4666,10 +4815,14 @@ async function withConfigWriteLock(operation) {
4666
4815
  throw new ConfigOwnershipError();
4667
4816
  };
4668
4817
  const assertOwnedSync = () => {
4669
- assertDirectorySync();
4670
- const entries = readdirSync(lockPath);
4671
- const entry = lstatSync(markerPath);
4672
- if (entries.length !== 1 || entries[0] !== markerName || !entry.isFile() || entry.isSymbolicLink() || readFileSync2(markerPath, "utf8") !== marker) throw new ConfigOwnershipError();
4818
+ try {
4819
+ assertDirectorySync();
4820
+ const entries = readdirSync(lockPath);
4821
+ const entry = lstatSync(markerPath);
4822
+ if (entries.length !== 1 || entries[0] !== markerName || !entry.isFile() || entry.isSymbolicLink() || readFileSync2(markerPath, "utf8") !== marker) throw new ConfigOwnershipError();
4823
+ } catch {
4824
+ throw new ConfigOwnershipError();
4825
+ }
4673
4826
  };
4674
4827
  const persist = async (update) => {
4675
4828
  const path = configPath();
@@ -4698,14 +4851,14 @@ async function withConfigWriteLock(operation) {
4698
4851
  if (temporaryOwned) unlinkSync(temporary);
4699
4852
  }
4700
4853
  };
4701
- const release = async () => {
4702
- await assertOwned();
4854
+ const release = () => {
4855
+ assertOwnedSync();
4703
4856
  const quarantine = join3(dirname2(lockPath), ".credential-release-" + randomUUID2());
4704
- await mkdir2(quarantine, { mode: 448 });
4857
+ mkdirSync(quarantine, { mode: 448 });
4705
4858
  const captured = join3(quarantine, markerName);
4706
4859
  try {
4707
- await assertOwned();
4708
- await rename2(markerPath, captured);
4860
+ assertOwnedSync();
4861
+ renameSync(markerPath, captured);
4709
4862
  assertDirectorySync();
4710
4863
  const entry = lstatSync(captured);
4711
4864
  if (!entry.isFile() || entry.isSymbolicLink() || readFileSync2(captured, "utf8") !== marker || readdirSync(lockPath).length) throw new ConfigOwnershipError();
@@ -4717,20 +4870,40 @@ async function withConfigWriteLock(operation) {
4717
4870
  } catch {
4718
4871
  if (existsSync2(captured)) {
4719
4872
  try {
4720
- await link(captured, markerPath);
4873
+ linkSync(captured, markerPath);
4721
4874
  } catch {
4722
4875
  }
4723
4876
  }
4724
4877
  throw new ConfigOwnershipError();
4725
4878
  }
4726
4879
  };
4880
+ let held = true;
4881
+ const releaseOnce = () => {
4882
+ if (!held) return;
4883
+ held = false;
4884
+ process.off("exit", onExit);
4885
+ for (const signal of RELEASE_SIGNALS) process.off(signal, onSignal);
4886
+ release();
4887
+ };
4888
+ const onExit = () => {
4889
+ try {
4890
+ releaseOnce();
4891
+ } catch {
4892
+ }
4893
+ };
4894
+ const onSignal = (signal) => {
4895
+ onExit();
4896
+ if (process.listenerCount(signal) === 0) process.kill(process.pid, signal);
4897
+ };
4898
+ process.on("exit", onExit);
4899
+ for (const signal of RELEASE_SIGNALS) process.on(signal, onSignal);
4727
4900
  try {
4728
4901
  await assertOwned();
4729
4902
  const result = await operation(assertOwned, persist);
4730
4903
  await assertOwned();
4731
4904
  return result;
4732
4905
  } finally {
4733
- await release();
4906
+ releaseOnce();
4734
4907
  }
4735
4908
  }
4736
4909
  function renewalFailure(error) {
@@ -6623,6 +6796,7 @@ async function readGooseRegistrations(path) {
6623
6796
  continue;
6624
6797
  }
6625
6798
  if (inExtensions && /^\S/.test(rawLine) && rawLine.trim()) {
6799
+ if (current?.command) registrations.push(current);
6626
6800
  inExtensions = false;
6627
6801
  inTopolo = false;
6628
6802
  current = void 0;
@@ -6630,6 +6804,7 @@ async function readGooseRegistrations(path) {
6630
6804
  if (!inExtensions) continue;
6631
6805
  const extension = rawLine.match(/^ ([A-Za-z0-9_-]+):\s*$/);
6632
6806
  if (extension) {
6807
+ if (current?.command) registrations.push(current);
6633
6808
  inTopolo = true;
6634
6809
  current = isPublicRegistrationName(extension[1]) ? { name: extension[1], command: "", args: [], env: {} } : void 0;
6635
6810
  continue;
@@ -6807,7 +6982,7 @@ function message(error) {
6807
6982
 
6808
6983
  // src/commands/agent-config.ts
6809
6984
  import { existsSync as existsSync5 } from "fs";
6810
- import { mkdir as mkdir3, readFile as readFile5, rename as rename3, writeFile as writeFile3 } from "fs/promises";
6985
+ import { mkdir as mkdir3, readFile as readFile5, rename as rename2, writeFile as writeFile3 } from "fs/promises";
6811
6986
  import { dirname as dirname5 } from "path";
6812
6987
  async function installAgentSkill(agent, source, homeDir, projectDir) {
6813
6988
  const path = agentSkillPath(agent, homeDir, projectDir);
@@ -6843,6 +7018,7 @@ async function unregisterAgentConfig(agent, homeDir, name, projectDir) {
6843
7018
  function registrationEnvironment(agent, registration) {
6844
7019
  return {
6845
7020
  TOPOLO_AGENT_NAME: AGENT_NAME_FOR[agent],
7021
+ ...registration.configPath ? { TOPOLO_CONFIG_PATH: registration.configPath } : {},
6846
7022
  ...registration.installationId ? { TOPOLO_AGENT_INSTALLATION_ID: registration.installationId } : {},
6847
7023
  ...registration.context ? { TOPOLO_CONTEXT: registration.context, TOPOLO_ENV: registration.environment } : {}
6848
7024
  };
@@ -6974,7 +7150,7 @@ async function atomicWrite(path, contents) {
6974
7150
  await mkdir3(dirname5(path), { recursive: true });
6975
7151
  const temporaryPath = path + ".topolo-" + process.pid + ".tmp";
6976
7152
  await writeFile3(temporaryPath, contents, { encoding: "utf8", mode: 384 });
6977
- await rename3(temporaryPath, path);
7153
+ await rename2(temporaryPath, path);
6978
7154
  }
6979
7155
  function withoutMcpServerSections(text2, name) {
6980
7156
  const section = "[mcp_servers." + name + "]";
@@ -7038,7 +7214,14 @@ function resolveMcpRegistration(options) {
7038
7214
  if (installationId && /[\r\n]/.test(installationId)) {
7039
7215
  throw new Error("Installation id must be a single-line identifier.");
7040
7216
  }
7041
- return { name, ...context ? { context } : {}, environment, ...installationId ? { installationId } : {} };
7217
+ const configPath2 = process.env.TOPOLO_CONFIG_PATH;
7218
+ return {
7219
+ name,
7220
+ ...context ? { context } : {},
7221
+ environment,
7222
+ ...installationId ? { installationId } : {},
7223
+ ...configPath2 ? { configPath: configPath2 } : {}
7224
+ };
7042
7225
  }
7043
7226
  async function installForAgent(agent, sourceDir, mcp, opts) {
7044
7227
  const source = await readFile6(join7(sourceDir, "topolo", "SKILL.md"), "utf8");
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
3
  "packageManager": "pnpm@10.17.1",
4
- "version": "0.13.6",
5
- "topoloPublishedVersion": "0.13.6",
4
+ "version": "0.13.8",
5
+ "topoloPublishedVersion": "0.13.8",
6
6
  "description": "Public command-line interface for Topolo customer organizations, developers, and agents.",
7
7
  "license": "UNLICENSED",
8
8
  "author": "Topolo",
@@ -31,8 +31,8 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@modelcontextprotocol/sdk": "^1.0.0",
34
- "@topolo/mcp": "0.14.24",
35
- "@topolo/sdk": "0.23.19",
34
+ "@topolo/mcp": "0.14.25",
35
+ "@topolo/sdk": "0.23.20",
36
36
  "commander": "^12.1.0",
37
37
  "undici": "6.29.0"
38
38
  },