@topolo/cli 0.13.17 → 0.13.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -4
- package/agent-packs/README.md +469 -1
- package/agent-packs/coverage_changed.py +1 -1
- package/agent-packs/docs/selected-artifacts.md +47 -0
- package/agent-packs/documentation/cloud-task-state.md +56 -0
- package/agent-packs/domain-contexts/README.md +25 -0
- package/agent-packs/domain-contexts/cicd.json +8 -0
- package/agent-packs/gui/feed-adapter.js +4 -0
- package/agent-packs/gui/index.html +9 -0
- package/agent-packs/gui/studio.css +14 -0
- package/agent-packs/gui/studio.js +121 -0
- package/agent-packs/gui/studio_server.py +355 -0
- package/agent-packs/roles/cicd/CLAUDE.md +24 -0
- package/agent-packs/specializations/cicd--cicd.json +5 -0
- package/agent-packs/specializations/cicd--pipeline-engineering.json +5 -0
- package/agent-packs/specializations/cicd--promotion-deployment.json +5 -0
- package/agent-packs/specializations/cicd--queue-recovery.json +5 -0
- package/agent-packs/specializations/cicd--release-verification.json +5 -0
- package/agent-packs/specializations/cicd--runner-capacity.json +5 -0
- package/agent-packs/specializations/intakes--engineering-02.json +5 -0
- package/agent-packs/specializations/intakes--engineering-03.json +5 -0
- package/agent-packs/specializations/intakes--engineering.json +5 -0
- package/agent-packs/specializations/intakes--operator-01.json +5 -0
- package/agent-packs/specializations/intakes--operator-02.json +5 -0
- package/agent-packs/specializations/operator-skills--audio-and-narration-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--brand-and-copywriting-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--capability-research-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--data-analysis-and-visualization.json +5 -0
- package/agent-packs/specializations/operator-skills--financial-evidence-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--operating-template-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--operator-skill-library.json +5 -0
- package/agent-packs/specializations/operator-skills--operator-skills.json +5 -0
- package/agent-packs/specializations/operator-skills--operator-workflow-evaluation.json +5 -0
- package/agent-packs/specializations/operator-skills--privacy-and-agreement-review.json +5 -0
- package/agent-packs/specializations/operator-skills--storytelling-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--ux-and-accessibility-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--video-production-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--visual-design-specialist.json +5 -0
- package/agent-packs/specializations/operator-skills--workflow-integration-specialist.json +5 -0
- package/agent-packs/specializations/operators--access-and-privacy.json +5 -0
- package/agent-packs/specializations/operators--accounting-evidence-review.json +5 -0
- package/agent-packs/specializations/operators--agreement-operations.json +5 -0
- package/agent-packs/specializations/operators--books-operator.json +5 -0
- package/agent-packs/specializations/operators--brand-operator.json +5 -0
- package/agent-packs/specializations/operators--commerce-operations.json +5 -0
- package/agent-packs/specializations/operators--communications-operator.json +5 -0
- package/agent-packs/specializations/operators--content-and-assets-operator.json +5 -0
- package/agent-packs/specializations/operators--customer-operations.json +5 -0
- package/agent-packs/specializations/operators--design-operator.json +5 -0
- package/agent-packs/specializations/operators--design-production-02.json +5 -0
- package/agent-packs/specializations/operators--design-production-03.json +5 -0
- package/agent-packs/specializations/operators--design-production-04.json +5 -0
- package/agent-packs/specializations/operators--device-and-feed-operator.json +5 -0
- package/agent-packs/specializations/operators--director-operator.json +5 -0
- package/agent-packs/specializations/operators--experience-review.json +5 -0
- package/agent-packs/specializations/operators--growth-operations.json +5 -0
- package/agent-packs/specializations/operators--learn-operator.json +5 -0
- package/agent-packs/specializations/operators--localization-operator.json +5 -0
- package/agent-packs/specializations/operators--operators.json +5 -0
- package/agent-packs/specializations/operators--pay-operator.json +5 -0
- package/agent-packs/specializations/operators--research-and-intelligence.json +5 -0
- package/agent-packs/specializations/operators--roadmap-operator.json +5 -0
- package/agent-packs/specializations/operators--social-content-operator.json +5 -0
- package/agent-packs/specializations/operators--talent-operations.json +5 -0
- package/agent-packs/specializations/operators--web-operator.json +5 -0
- package/agent-packs/specializations/operators--workflow-operator.json +5 -0
- package/agent-packs/specializations/specialists--adversarial-qa.json +5 -0
- package/agent-packs/specializations/specialists--backlog-dispatch.json +5 -0
- package/agent-packs/specializations/specialists--build-01.json +5 -0
- package/agent-packs/specializations/specialists--build-02.json +5 -0
- package/agent-packs/specializations/specialists--build-03.json +5 -0
- package/agent-packs/specializations/specialists--developer-environment-steward.json +5 -0
- package/agent-packs/specializations/specialists--documentation-knowledge-steward.json +5 -0
- package/agent-packs/specializations/specialists--execution-readiness.json +5 -0
- package/agent-packs/specializations/specialists--incident-triage.json +5 -0
- package/agent-packs/specializations/specialists--platform-package-steward.json +5 -0
- package/agent-packs/specializations/specialists--product-direction-01.json +5 -0
- package/agent-packs/specializations/specialists--product-direction-02.json +5 -0
- package/agent-packs/specializations/specialists--release-recovery.json +5 -0
- package/agent-packs/specializations/specialists--release-watch.json +5 -0
- package/agent-packs/specializations/specialists--roadmap-planning-01.json +5 -0
- package/agent-packs/specializations/specialists--roadmap-planning-02.json +5 -0
- package/agent-packs/specializations/specialists--security-permissions-review.json +5 -0
- package/agent-packs/specializations/specialists--system-architecture-01.json +5 -0
- package/agent-packs/specializations/specialists--system-architecture-02.json +5 -0
- package/agent-packs/specializations/specialists--translation.json +5 -0
- package/agent-packs/specializations/specialists--ux-consistency-review.json +5 -0
- package/agent-packs/specializations/specialists--workflow-improvement.json +5 -0
- package/agent-packs/specializations/specialists--worktree-retirement.json +5 -0
- package/agent-packs/src/topolo_agent_pack/cicd.py +101 -0
- package/agent-packs/src/topolo_agent_pack/claude.py +22 -7
- package/agent-packs/src/topolo_agent_pack/claude_activation.py +157 -0
- package/agent-packs/src/topolo_agent_pack/claude_completion_recovery.py +216 -0
- package/agent-packs/src/topolo_agent_pack/claude_desktop.py +181 -0
- package/agent-packs/src/topolo_agent_pack/claude_dispatch.py +240 -0
- package/agent-packs/src/topolo_agent_pack/claude_prompt.py +74 -0
- package/agent-packs/src/topolo_agent_pack/claude_runner.py +207 -0
- package/agent-packs/src/topolo_agent_pack/claude_service.py +397 -0
- package/agent-packs/src/topolo_agent_pack/cli.py +188 -18
- package/agent-packs/src/topolo_agent_pack/closed_ledger_fence.py +118 -0
- package/agent-packs/src/topolo_agent_pack/cloud_attach.py +128 -0
- package/agent-packs/src/topolo_agent_pack/cloud_cutover.py +134 -0
- package/agent-packs/src/topolo_agent_pack/cloud_delta.py +162 -0
- package/agent-packs/src/topolo_agent_pack/cloud_migrate.py +142 -0
- package/agent-packs/src/topolo_agent_pack/cloud_recovery.py +42 -0
- package/agent-packs/src/topolo_agent_pack/cloud_work.py +126 -0
- package/agent-packs/src/topolo_agent_pack/coordinator_activity.py +27 -0
- package/agent-packs/src/topolo_agent_pack/coordinator_context.py +91 -0
- package/agent-packs/src/topolo_agent_pack/coordinator_refresh.py +116 -0
- package/agent-packs/src/topolo_agent_pack/desktop_acceptance.py +45 -0
- package/agent-packs/src/topolo_agent_pack/desktop_launcher.py +114 -0
- package/agent-packs/src/topolo_agent_pack/domain_context.py +38 -0
- package/agent-packs/src/topolo_agent_pack/execution.py +104 -7
- package/agent-packs/src/topolo_agent_pack/fresh.py +1 -1
- package/agent-packs/src/topolo_agent_pack/frontdoor.py +23 -0
- package/agent-packs/src/topolo_agent_pack/linked_task_context.py +80 -0
- package/agent-packs/src/topolo_agent_pack/live.py +285 -18
- package/agent-packs/src/topolo_agent_pack/native.py +160 -4
- package/agent-packs/src/topolo_agent_pack/policy.py +8 -0
- package/agent-packs/src/topolo_agent_pack/review_context.py +33 -0
- package/agent-packs/src/topolo_agent_pack/role_catalog.py +137 -0
- package/agent-packs/src/topolo_agent_pack/role_context.py +63 -0
- package/agent-packs/src/topolo_agent_pack/role_refresh.py +50 -0
- package/agent-packs/src/topolo_agent_pack/task_models.py +11 -7
- package/agent-packs/src/topolo_agent_pack/throughput.py +149 -0
- package/agent-packs/src/topolo_agent_pack/topolo.py +7 -1
- package/agent-packs/src/topolo_agent_pack/work.py +322 -0
- package/agent-packs/src/topolo_agent_pack/work_adoption.py +122 -0
- package/agent-packs/src/topolo_agent_pack/work_artifacts.py +84 -0
- package/agent-packs/src/topolo_agent_pack/work_context.py +77 -0
- package/agent-packs/src/topolo_agent_pack/work_gui.py +51 -0
- package/agent-packs/src/topolo_agent_pack/work_native.py +414 -0
- package/agent-packs/src/topolo_agent_pack/work_wait.py +151 -0
- package/agent-packs/templates/topolo-coordinators/AGENTS.md +7 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/AGENTS.md +6 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/config.json +4 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/AGENTS.md +3 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/config.json +4 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/AGENTS.md +3 -0
- package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/config.json +4 -0
- package/agent-packs/templates/topolo-coordinators/pack.json +56 -0
- package/dist/chunk-NYDIAYVY.js +144 -0
- package/dist/{claude-pack-backend-YEVPAOID.js → chunk-UY5LEU5C.js} +1659 -21
- package/dist/claude-desktop-profile-JCQKSY3K.js +13 -0
- package/dist/claude-pack-activation-Q25MCEPU.js +506 -0
- package/dist/claude-pack-backend-HUTKGIDS.js +90 -0
- package/dist/index.js +408 -164
- package/package.json +8 -6
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Human-authorized diagnostic continuation of one immutable cloud claim.
|
|
2
|
+
|
|
3
|
+
Never reconstruct authority from a chat, local source path or worker assertion.
|
|
4
|
+
The original failed jobs, source custody and uncertain effects remain held.
|
|
5
|
+
"""
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import pathlib
|
|
9
|
+
import tempfile
|
|
10
|
+
import uuid
|
|
11
|
+
from .storage import canonical
|
|
12
|
+
from .work import require, text
|
|
13
|
+
|
|
14
|
+
FIELDS={'requestId','objective','channel','canonicalClaimId','continuationOwnerTaskId',
|
|
15
|
+
'role','acceptance','checkpointSHA256','checkpointRef','authorizationRef','continuationRevision','parentCheckpointSHA256'}
|
|
16
|
+
SCOPE='read-only-checkpoint-diagnostic'
|
|
17
|
+
|
|
18
|
+
def digest(value):
|
|
19
|
+
return hashlib.sha256(canonical(value)).hexdigest()
|
|
20
|
+
|
|
21
|
+
def request_digest(request):
|
|
22
|
+
return digest(request)
|
|
23
|
+
|
|
24
|
+
def validate(request):
|
|
25
|
+
require(isinstance(request,dict) and set(request)==FIELDS,'Exact retained adoption fields required')
|
|
26
|
+
for key in ['requestId','continuationOwnerTaskId']:
|
|
27
|
+
try:valid=str(uuid.UUID(request[key]))==request[key]
|
|
28
|
+
except (TypeError,ValueError,AttributeError):valid=False
|
|
29
|
+
require(valid,'Adoption requires exact task identities')
|
|
30
|
+
require(type(request['continuationRevision']) is int and request['continuationRevision']>0,'Exact parent revision required')
|
|
31
|
+
require(isinstance(request['parentCheckpointSHA256'],str) and len(request['parentCheckpointSHA256'])==64 and all(c in '0123456789abcdef' for c in request['parentCheckpointSHA256']),'Exact parent checkpoint heads required')
|
|
32
|
+
require(request['requestId']!=request['continuationOwnerTaskId'],'Continuation must retain its originating delivery task')
|
|
33
|
+
require(request['channel']=='engineering','Retained engineering channel required')
|
|
34
|
+
require(all(text(request[k],12000 if k=='acceptance' else 16000 if k=='objective' else 240)
|
|
35
|
+
for k in ['objective','canonicalClaimId','role','acceptance']),'Adoption requires objective, claim, registered role and string acceptance')
|
|
36
|
+
require(isinstance(request['checkpointSHA256'],str) and len(request['checkpointSHA256'])==64
|
|
37
|
+
and all(c in '0123456789abcdef' for c in request['checkpointSHA256']),'Exact canonical checkpoint digest required')
|
|
38
|
+
for key in ['checkpointRef','authorizationRef']:
|
|
39
|
+
ref=request[key]
|
|
40
|
+
require(isinstance(ref,dict) and set(ref)=={'appId','workspaceId','scope','key','etag','size','sha256'}
|
|
41
|
+
and ref['appId']=='app_topolo_bytes' and ref['scope']=='org'
|
|
42
|
+
and all(text(ref[k],1000) for k in ['workspaceId','key','etag','sha256'])
|
|
43
|
+
and type(ref['size']) is int and 0<ref['size']<=131072,'Exact bounded cloud evidence reference required')
|
|
44
|
+
|
|
45
|
+
def read_evidence(store,ref):
|
|
46
|
+
from .topolo import read_artifact
|
|
47
|
+
require(getattr(store,'cloud',False),'Retained continuation requires activated cloud authority; no local history fallback')
|
|
48
|
+
authority=json.loads((store.root/'context-authority.json').read_text())
|
|
49
|
+
require(authority.get('organizationId')==store.binding['organizationId']
|
|
50
|
+
and authority.get('scope')==ref['scope'] and authority.get('workspaceId')==ref['workspaceId'],
|
|
51
|
+
'Selected claim evidence belongs to another cloud authority')
|
|
52
|
+
with tempfile.TemporaryDirectory(prefix='topolo-selected-claim-') as tmp:
|
|
53
|
+
path=pathlib.Path(tmp)/'selected.json'
|
|
54
|
+
read_artifact(store.client,{'input':{'bytesScope':ref['scope'],'bytesWorkspaceId':ref['workspaceId']}},ref['key'],ref,path)
|
|
55
|
+
return json.loads(path.read_text())
|
|
56
|
+
|
|
57
|
+
def plan(request,retained,claim,roles):
|
|
58
|
+
validate(request)
|
|
59
|
+
require(request['role'] in roles,'Exact registered specialist role required')
|
|
60
|
+
require(retained['claimId']==request['canonicalClaimId']==claim.get('claimId'),'Claim identity differs')
|
|
61
|
+
require(text(retained.get('ownerThreadId'),200) and retained['ownerThreadId']==claim.get('ownerThreadId'),'Recorded original controller differs')
|
|
62
|
+
require(digest(claim)==request['checkpointSHA256'],'Canonical checkpoint changed')
|
|
63
|
+
require(any(word in str(claim.get('state','')).lower() for word in ['failed','blocked','held','paused','unknown']),
|
|
64
|
+
'Recorded claim is active or unqualified; preserve current custody')
|
|
65
|
+
require(not any(claim.get(key) for key in ['lease','activeLease','ownerLease','leaseId','ownerLeaseId','leaseExpiresAt','ownerLeaseExpiresAt','activeWorkerId','activeRunId']),
|
|
66
|
+
'Canonical claim has a live or unresolved owner lease')
|
|
67
|
+
return {'format':'topolo-retained-continuation-plan-v2','canonicalClaimId':request['canonicalClaimId'],
|
|
68
|
+
'checkpointSHA256':digest(claim),'retainedSHA256':digest(retained),
|
|
69
|
+
'fromOwnerThreadId':claim['ownerThreadId'],'toRole':request['role'],
|
|
70
|
+
'continuationOwnerTaskId':request['continuationOwnerTaskId'],'acceptance':request['acceptance'],
|
|
71
|
+
'requestSHA256':request_digest(request),'canonicalSource':request['checkpointRef'],
|
|
72
|
+
'authorizationRef':request['authorizationRef'],'checkpoint':claim,'scope':SCOPE,
|
|
73
|
+
'preserveUnknownEffects':True,'sourceOwnerRetained':True,'releaseGatesRetained':True}
|
|
74
|
+
|
|
75
|
+
def verify_authority(request,prepared,authorization,actor,consent,installation):
|
|
76
|
+
consent=consent or {}
|
|
77
|
+
required={'format','principalId','organizationId','canonicalClaimId','checkpointSHA256','sourceHead',
|
|
78
|
+
'continuationOwnerTaskId','continuationRevision','parentCheckpointSHA256','scope','instruction','instructionSHA256'}
|
|
79
|
+
require(isinstance(authorization,dict) and set(authorization)==required
|
|
80
|
+
and authorization['format']=='topolo-human-retained-continuation-v1'
|
|
81
|
+
and authorization['scope']==prepared['scope']==SCOPE,'Exact human diagnostic authorization required')
|
|
82
|
+
require(actor.get('user',{}).get('role')=='owner' and actor.get('user',{}).get('id')==authorization['principalId']==consent.get('principalId')
|
|
83
|
+
and actor.get('organization',{}).get('id')==authorization['organizationId']=='org_topolo_platform'
|
|
84
|
+
and consent.get('approved') is True and consent.get('contextKey')=='org:org_topolo_platform',
|
|
85
|
+
'Current authenticated owner and installation consent differ')
|
|
86
|
+
require(all(authorization[k]==request[k] for k in ['canonicalClaimId','checkpointSHA256','continuationOwnerTaskId','continuationRevision','parentCheckpointSHA256'])
|
|
87
|
+
and text(authorization['instruction']) and hashlib.sha256(authorization['instruction'].encode()).hexdigest()==authorization['instructionSHA256'],
|
|
88
|
+
'Human instruction does not bind this exact claim continuation')
|
|
89
|
+
head=prepared['checkpoint'].get('head') or prepared['checkpoint'].get('sourceCommit')
|
|
90
|
+
require(isinstance(head,str) and len(head)==40 and all(c in '0123456789abcdef' for c in head)
|
|
91
|
+
and authorization['sourceHead']==head,'Exact source head differs from human authorization')
|
|
92
|
+
return {'authority':'current-authenticated-human-instruction','principalId':authorization['principalId'],
|
|
93
|
+
'organizationId':authorization['organizationId'],'installationId':installation,
|
|
94
|
+
'authorizationSHA256':digest(authorization),'consentSHA256':digest(consent),
|
|
95
|
+
'planSHA256':digest(prepared),'scope':SCOPE,'originalWorkerAcceptance':False}
|
|
96
|
+
|
|
97
|
+
def parent_heads(parent,workers):
|
|
98
|
+
return {'result':parent.get('result',{}).get('checkpoint'),
|
|
99
|
+
'workers':[{'id':w['id'],'phase':w['phase'],'checkpoint':w.get('checkpoint')} for w in sorted(workers,key=lambda w:w['id'])]}
|
|
100
|
+
|
|
101
|
+
def verify_parent(parent,workers,request,root,client,installation):
|
|
102
|
+
"""Only closed portable checkpoints permit a foreign installation transfer."""
|
|
103
|
+
from .work_artifacts import read_checkpoint
|
|
104
|
+
require(parent['state'] in {'blocked','needs_input'} and parent['revision']==request['continuationRevision'],
|
|
105
|
+
'Parent is active, uncertain or revision changed; preserve custody')
|
|
106
|
+
require(workers and digest(parent_heads(parent,workers))==request['parentCheckpointSHA256'],
|
|
107
|
+
'Parent cloud checkpoint heads differ')
|
|
108
|
+
for worker in workers:
|
|
109
|
+
require(worker['state']=='archived' and worker.get('nativeArchived') is True
|
|
110
|
+
and text(worker.get('nativeId'),200) and text(worker.get('turnId'),200)
|
|
111
|
+
and isinstance(worker.get('checkpoint',{}).get('artifact'),dict),
|
|
112
|
+
'Parent native worker has unresolved or nonportable custody')
|
|
113
|
+
payload=read_checkpoint(root,worker['checkpoint'],parent['id'],worker['id'],client=client)
|
|
114
|
+
native=payload['nativeReceipt']
|
|
115
|
+
require(payload['phase']==worker['phase'] and native['threadId']==worker['nativeId']
|
|
116
|
+
and native['turnId']==worker['turnId'] and native['status'] in {'completed','failed','cancelled'},
|
|
117
|
+
'Parent terminal checkpoint differs')
|
|
118
|
+
require(parent.get('result',{}).get('checkpoint') in [w['checkpoint'] for w in workers],
|
|
119
|
+
'Parent result does not bind a verified terminal checkpoint')
|
|
120
|
+
return {'fromInstallationId':parent.get('ownerInstallationId'),'toInstallationId':installation,
|
|
121
|
+
'revision':parent['revision'],'checkpointHeadsSHA256':request['parentCheckpointSHA256'],
|
|
122
|
+
'scope':SCOPE,'taskStatePreserved':parent['state']}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Portable task results and declared evidence; never exports native reasoning/history."""
|
|
2
|
+
import hashlib,json,pathlib,tempfile,mimetypes
|
|
3
|
+
from .storage import atomic_json,read_json,canonical,PackError,safe_id
|
|
4
|
+
from .work import require
|
|
5
|
+
|
|
6
|
+
def client_default():
|
|
7
|
+
from .topolo import Client
|
|
8
|
+
return Client('production','org:org_topolo_platform')
|
|
9
|
+
|
|
10
|
+
def publish(root,task_id,label,data,content_type='application/json',client=None):
|
|
11
|
+
safe_id(task_id)
|
|
12
|
+
root=pathlib.Path(root);authority=read_json(root/'context-authority.json',{})
|
|
13
|
+
require(authority.get('format')=='topolo-task-context-authority-v1' and authority.get('organizationId')=='org_topolo_platform' and authority.get('scope')=='org' and authority.get('workspaceId'),'Cloud artifact authority is missing')
|
|
14
|
+
require(isinstance(data,bytes) and 0<len(data)<=100*1024*1024 and all(c.isalnum() or c in '-_.' for c in label),'Invalid declared artifact')
|
|
15
|
+
digest=hashlib.sha256(data).hexdigest();directory=root/'artifact-uploads'/task_id;directory.mkdir(parents=True,exist_ok=True,mode=0o700)
|
|
16
|
+
name=label+'-'+digest[:16];file=directory/name;receipt=directory/(name+'.receipt.json');prior=read_json(receipt,{})
|
|
17
|
+
client=client or client_default()
|
|
18
|
+
if prior:
|
|
19
|
+
require(prior.get('sha256')==digest and prior.get('state') in {'uploaded','verified'},'Artifact upload is uncertain; reconcile before retrying')
|
|
20
|
+
key=prior['key']
|
|
21
|
+
else:
|
|
22
|
+
require(not file.exists(),'Artifact source exists without an upload receipt; reconcile first')
|
|
23
|
+
file.write_bytes(data);file.chmod(0o600);atomic_json(receipt,{'state':'prepared','sha256':digest})
|
|
24
|
+
result=client.action('app_topolo_bytes.files.upload_url.create',{'key':'task-results/'+task_id+'/'+name,'type':content_type,'size':len(data),'scope':authority['scope']},authority['workspaceId'],write=True,upload=file)
|
|
25
|
+
key=result.get('key') or result.get('session',{}).get('key');require(isinstance(key,str) and key,'Artifact upload key is unconfirmed; reconcile before retrying')
|
|
26
|
+
atomic_json(receipt,{'state':'uploaded','sha256':digest,'key':key})
|
|
27
|
+
from .topolo import read_artifact
|
|
28
|
+
ref=read_artifact(client,{'input':{'bytesScope':authority['scope'],'bytesWorkspaceId':authority['workspaceId']}},key)
|
|
29
|
+
require(ref['sha256']==digest,'Cloud artifact differs from declared output')
|
|
30
|
+
atomic_json(receipt,{'state':'verified','sha256':digest,'key':key,'artifact':ref});return ref
|
|
31
|
+
|
|
32
|
+
def declared_files(worker,values):
|
|
33
|
+
require(isinstance(values,list) and len(values)<=16,'Declare at most sixteen evidence files')
|
|
34
|
+
cwd=pathlib.Path(worker['cwd']).resolve();paths=[]
|
|
35
|
+
for value in values:
|
|
36
|
+
require(isinstance(value,str),'Evidence path must be a string')
|
|
37
|
+
path=pathlib.Path(value)
|
|
38
|
+
require(path.is_absolute() and '..' not in path.parts and path.is_file() and not path.is_symlink() and path.resolve().is_relative_to(cwd) and not any(p.is_symlink() for p in path.parents if p!=cwd.parent),'Evidence must be a regular file in this worker workspace')
|
|
39
|
+
require(0<path.stat().st_size<=100*1024*1024,'Declared evidence exceeds upload boundary');paths.append(path)
|
|
40
|
+
return paths
|
|
41
|
+
|
|
42
|
+
def checkpoint(root,task,worker,actual,client=None):
|
|
43
|
+
text='\n'.join(i.get('text','') for i in actual.get('items',[]) if i.get('type')=='agentMessage' and i.get('phase')!='commentary')
|
|
44
|
+
require(len(text.encode())<=100000,'Worker output exceeds bounded checkpoint')
|
|
45
|
+
normalized=None;error=None
|
|
46
|
+
if worker['phase'] in {'execute','review'} and actual['status']=='completed':
|
|
47
|
+
try:
|
|
48
|
+
raw=text.strip()
|
|
49
|
+
if raw.startswith('```'):raw=raw.split('\n',1)[1].rsplit('```',1)[0]
|
|
50
|
+
output=json.loads(raw)
|
|
51
|
+
if worker['phase']=='execute':
|
|
52
|
+
require(set(output)=={'summary','artifacts','references'} and isinstance(output['summary'],str) and isinstance(output['references'],list) and len(output['references'])<=32 and all(isinstance(r,str) and len(r)<=2000 for r in output['references']),'Invalid portable worker result contract')
|
|
53
|
+
paths=declared_files(worker,output['artifacts'])
|
|
54
|
+
else:
|
|
55
|
+
require(set(output)=={'verdict','summary','evidence'} and output['verdict'] in {'completed','needs_input','blocked'} and isinstance(output['summary'],str),'Invalid independent review contract')
|
|
56
|
+
paths=declared_files(worker,output['evidence']);require(output['verdict']!='completed' or bool(paths),'Completion requires inspected evidence')
|
|
57
|
+
except (PackError,ValueError,TypeError,KeyError) as exc:error=str(exc)
|
|
58
|
+
else:
|
|
59
|
+
refs=[{'title':path.name,'artifact':publish(root,task['id'],worker['id']+'-evidence-'+str(n),path.read_bytes(),mimetypes.guess_type(path.name)[0] or 'application/octet-stream',client)} for n,path in enumerate(paths)]
|
|
60
|
+
normalized={**output,('artifacts' if worker['phase']=='execute' else 'evidence'):refs}
|
|
61
|
+
payload={'format':'topolo-task-checkpoint-v1','taskId':task['id'],'workerId':worker['id'],'phase':worker['phase'],'nativeReceipt':{'threadId':worker['nativeId'],'turnId':actual['id'],'status':actual['status'],'toolItems':sum(i.get('type') not in {'agentMessage','userMessage','reasoning','plan'} for i in actual.get('items',[]))},'resolution':worker['resolution'],'roleSource':worker.get('roleSource'),'contextRef':task.get('contextRef'),'output':canonical(normalized).decode() if normalized is not None else text,'outputError':error,'nativeHistoryIncluded':False}
|
|
62
|
+
if actual.get('nativeProof'):
|
|
63
|
+
payload['nativeReceipt']['producer']=actual['nativeProof']
|
|
64
|
+
data=canonical(payload);ref=publish(root,task['id'],worker['id']+'-checkpoint',data,client=client)
|
|
65
|
+
return {'artifact':ref,'sha256':ref['sha256']},payload,normalized
|
|
66
|
+
|
|
67
|
+
def read_checkpoint(root,proof,task_id,worker_id=None,client=None):
|
|
68
|
+
from .topolo import read_artifact
|
|
69
|
+
ref=proof['artifact'];require(proof['sha256']==ref['sha256'] and 0<ref['size']<=200000,'Checkpoint digest or size binding differs');client=client or client_default()
|
|
70
|
+
with tempfile.TemporaryDirectory(prefix='topolo-one-checkpoint-') as tmp:
|
|
71
|
+
file=pathlib.Path(tmp)/'checkpoint.json';read_artifact(client,{'input':{'bytesScope':ref['scope'],'bytesWorkspaceId':ref['workspaceId']}},ref['key'],ref,file);payload=json.loads(file.read_text())
|
|
72
|
+
require(set(payload)=={'format','taskId','workerId','phase','nativeReceipt','resolution','roleSource','contextRef','output','outputError','nativeHistoryIncluded'} and payload.get('format')=='topolo-task-checkpoint-v1' and payload.get('taskId')==task_id and (worker_id is None or payload.get('workerId')==worker_id) and payload.get('nativeHistoryIncluded') is False,'Cloud checkpoint identity differs')
|
|
73
|
+
return payload
|
|
74
|
+
|
|
75
|
+
def materialize_result(root,task,cwd,client=None):
|
|
76
|
+
from .topolo import read_artifact
|
|
77
|
+
result=task.get('result',{});payload=read_checkpoint(root,result['checkpoint'],task['id'],result['workerId'],client)
|
|
78
|
+
require(payload['phase']=='execute' and payload['outputError'] is None,'Worker checkpoint is not a qualified result')
|
|
79
|
+
output=json.loads(payload['output']);require(output=={k:result[k] for k in ['summary','artifacts','references']},'Task result differs from the immutable worker checkpoint')
|
|
80
|
+
target=pathlib.Path(cwd)/'context';target.mkdir(exist_ok=True);atomic_json(target/'worker-result.json',payload)
|
|
81
|
+
evidence=target/'evidence';evidence.mkdir(exist_ok=True);mapping=[];client=client or client_default()
|
|
82
|
+
for n,row in enumerate(result.get('artifacts',[])):
|
|
83
|
+
ref=row['artifact'];path=evidence/str(n);read_artifact(client,{'input':{'bytesScope':ref['scope'],'bytesWorkspaceId':ref['workspaceId']}},ref['key'],ref,path);mapping.append({'title':row['title'],'path':str(path),'artifact':ref})
|
|
84
|
+
atomic_json(target/'result-source.json',{'checkpoint':result['checkpoint'],'evidence':mapping})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Pinned immutable Docs task context, fetched at assignment time."""
|
|
2
|
+
import hashlib
|
|
3
|
+
import pathlib
|
|
4
|
+
import json,tempfile
|
|
5
|
+
from .storage import read_json,atomic_json,PackError
|
|
6
|
+
from .work import require
|
|
7
|
+
|
|
8
|
+
def materialize(root,task,cwd,client_factory=None):
|
|
9
|
+
binding=pathlib.Path(root)/'context-refs'/(task['id']+'.json')
|
|
10
|
+
if not binding.exists() and not task.get('contextRef'):
|
|
11
|
+
require((cwd/'context/continuity.md').is_file(),'Task continuity context is missing; bind the existing cloud task packet before dispatch')
|
|
12
|
+
return
|
|
13
|
+
require(not binding.is_symlink(),'Task context binding cannot be a symlink')
|
|
14
|
+
ref=task.get('contextRef') or read_json(binding)
|
|
15
|
+
if client_factory is None:
|
|
16
|
+
from .topolo import Client
|
|
17
|
+
client_factory=lambda:Client('production','org:org_topolo_platform')
|
|
18
|
+
if set(ref)=={'taskId','artifact'}:
|
|
19
|
+
require(ref['taskId']==task['id'],'Cloud task context identity differs')
|
|
20
|
+
from .topolo import read_artifact
|
|
21
|
+
artifact=ref['artifact']
|
|
22
|
+
with tempfile.TemporaryDirectory(prefix='topolo-one-task-context-') as tmp:
|
|
23
|
+
path=pathlib.Path(tmp)/'task.json'
|
|
24
|
+
read_artifact(client_factory(),{'input':{'bytesScope':artifact['scope'],'bytesWorkspaceId':artifact['workspaceId']}},artifact['key'],artifact,path)
|
|
25
|
+
packet=json.loads(path.read_text())
|
|
26
|
+
require(set(packet)=={'format','taskId','markdown'} and packet['format']=='topolo-task-context-v1' and packet['taskId']==task['id'] and isinstance(packet['markdown'],str) and len(packet['markdown'])<=100000,'Unqualified individual task context')
|
|
27
|
+
target=cwd/'context';target.mkdir(exist_ok=True,mode=0o700)
|
|
28
|
+
(target/'continuity.md').write_text(packet['markdown'])
|
|
29
|
+
atomic_json(target/'continuity-source.json',ref)
|
|
30
|
+
return
|
|
31
|
+
require(set(ref)=={'taskId','workspaceId','documentId','versionId','bodySHA256'},'Invalid pinned task context')
|
|
32
|
+
require(ref['taskId']==task['id'],'Cloud task context identity differs')
|
|
33
|
+
result=client_factory().action('app_topolo_docs.documents.get',{'documentId':ref['documentId']},ref['workspaceId'])
|
|
34
|
+
doc=result['document'];versions=[v for v in result['versions'] if v['id']==ref['versionId']]
|
|
35
|
+
require(doc['id']==ref['documentId'] and doc['workspaceId']==ref['workspaceId'] and doc['organizationId']=='org_topolo_platform' and len(versions)==1,'Pinned cloud context is unavailable in the owning organization')
|
|
36
|
+
body=versions[0]['bodyMarkdown'];require(hashlib.sha256(body.encode()).hexdigest()==ref['bodySHA256'],'Pinned cloud task context changed')
|
|
37
|
+
start='<!-- topolo-task-context:'+task['id']+' -->';end='<!-- /topolo-task-context -->'
|
|
38
|
+
require(body.count(start)==1,'Cloud document has no unique context for this task')
|
|
39
|
+
section=body.split(start,1)[1];require(end in section,'Cloud task context section is incomplete');section=section.split(end,1)[0]
|
|
40
|
+
target=cwd/'context';target.mkdir(exist_ok=True,mode=0o700)
|
|
41
|
+
(target/'continuity.md').write_text('# Verified immutable Topolo Docs task context\n\nSource: '+ref['documentId']+' / '+ref['versionId']+'\n\n'+section)
|
|
42
|
+
atomic_json(target/'continuity-source.json',ref)
|
|
43
|
+
|
|
44
|
+
def ensure(root,task,client=None):
|
|
45
|
+
"""Publish one new request packet before native work; never replay an uncertain upload."""
|
|
46
|
+
root=pathlib.Path(root);binding=root/'context-refs'/(task['id']+'.json')
|
|
47
|
+
if task.get('contextRef'):return task['contextRef']
|
|
48
|
+
if binding.exists():return read_json(binding)
|
|
49
|
+
authority=read_json(root/'context-authority.json',{})
|
|
50
|
+
require(set(authority)=={'format','organizationId','workspaceId','scope'} and authority['format']=='topolo-task-context-authority-v1' and authority['organizationId']=='org_topolo_platform' and authority['scope']=='org' and isinstance(authority['workspaceId'],str) and authority['workspaceId'],'Task cloud context authority is missing; no local-context fallback')
|
|
51
|
+
if client is None:
|
|
52
|
+
from .topolo import Client
|
|
53
|
+
client=Client('production','org:org_topolo_platform')
|
|
54
|
+
from .topolo import read_artifact
|
|
55
|
+
directory=root/'context-uploads';directory.mkdir(exist_ok=True,mode=0o700)
|
|
56
|
+
receipt=directory/(task['id']+'.receipt.json');file=directory/(task['id']+'.context.json')
|
|
57
|
+
packet={'format':'topolo-task-context-v1','taskId':task['id'],'markdown':'# Current human request\n\nTask: '+task['id']+'\nChannel: '+task['channel']+'\n\n'+task['objective']+'\n\n## Custody and evidence\nThis packet records the human request, not proof that existing work is unowned, resumed, released or complete. Discover only the relevant canonical Topolo Docs/Bytes references before execution. Preserve existing task/claim/intent identities, owners, pauses and UNKNOWN effects. Report missing context or authority without duplicating work. Current task phase and subsequent human responses are in TASK.json. No task history or portfolio export is required for this packet.\n'}
|
|
58
|
+
source=json.dumps(packet,sort_keys=True,separators=(',',':')).encode();digest=hashlib.sha256(source).hexdigest()
|
|
59
|
+
prior=read_json(receipt,{})
|
|
60
|
+
if prior:
|
|
61
|
+
require(prior.get('sourceSHA256')==digest,'Task context input changed; reconcile the existing upload')
|
|
62
|
+
require(prior.get('state') in {'uploaded','verified'},'Task context upload is uncertain; reconcile its receipt before retrying')
|
|
63
|
+
key=prior['key']
|
|
64
|
+
else:
|
|
65
|
+
require(not file.exists(),'Task context source exists without a receipt; reconcile before uploading')
|
|
66
|
+
file.write_bytes(source);file.chmod(0o600)
|
|
67
|
+
atomic_json(receipt,{'state':'prepared','taskId':task['id'],'sourceSHA256':digest})
|
|
68
|
+
result=client.action('app_topolo_bytes.files.upload_url.create',{'key':'task-context/'+file.name,'type':'application/json','size':len(source),'scope':authority['scope']},authority['workspaceId'],write=True,upload=file)
|
|
69
|
+
key=result.get('key') or result.get('session',{}).get('key')
|
|
70
|
+
require(isinstance(key,str) and key,'Task context upload returned no persisted key; reconcile before retrying')
|
|
71
|
+
atomic_json(receipt,{'state':'uploaded','taskId':task['id'],'sourceSHA256':digest,'key':key})
|
|
72
|
+
artifact=read_artifact(client,{'input':{'bytesScope':authority['scope'],'bytesWorkspaceId':authority['workspaceId']}},key)
|
|
73
|
+
require(artifact['sha256']==digest,'Persisted task context differs; no native work started')
|
|
74
|
+
ref={'taskId':task['id'],'artifact':artifact}
|
|
75
|
+
atomic_json(receipt,{'state':'verified','taskId':task['id'],'sourceSHA256':digest,'key':key,'artifact':artifact})
|
|
76
|
+
atomic_json(binding,ref)
|
|
77
|
+
return ref
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Closed loopback GUI adapter to the token-authenticated owning connection."""
|
|
2
|
+
import json
|
|
3
|
+
import socket
|
|
4
|
+
from .storage import Library,canonical,read_json,PackError
|
|
5
|
+
from . import live
|
|
6
|
+
from .work import Store
|
|
7
|
+
|
|
8
|
+
class Client:
|
|
9
|
+
def __init__(self,root):
|
|
10
|
+
self.lib=Library(root)
|
|
11
|
+
def snapshot(self):
|
|
12
|
+
try:
|
|
13
|
+
result=live.request(self.lib,'work-list',{})
|
|
14
|
+
if 'events' not in result:result['events']=[event for task in result['tasks'] for event in live.request(self.lib,'work-read',{'taskId':task['id']})['events']]
|
|
15
|
+
result['coordinatorObservations']=self.observe_coordinators()
|
|
16
|
+
if read_json(self.lib.root/'work/config.json',{}).get('frontdoorDispatch') is not True:
|
|
17
|
+
states=read_json(self.lib.root/'work/coordinator-observations.json',{}).get('states',{})
|
|
18
|
+
active=sorted({event['owner'] for event in result['coordinatorObservations'] if states.get(event['threadId'])=='active'})
|
|
19
|
+
if active:result['activityNotice']=', '.join(active)+' is executing outside the task ledger. Recorded milestones update here; continuous specialist progress awaits the pending backend activation.'
|
|
20
|
+
status=live.request(self.lib,'status',{})
|
|
21
|
+
if status.get('workStartupError'):result['activityNotice']='Automatic task resumption is blocked: '+status['workStartupError']
|
|
22
|
+
result['connection']='live';return result
|
|
23
|
+
except (PackError,OSError):
|
|
24
|
+
from .cloud_work import open_store
|
|
25
|
+
store=open_store(self.lib.root)
|
|
26
|
+
result=store.snapshot() if getattr(store,'cloud',False) else {'tasks':store.list(),'workers':store.workers(),'events':store.events(),'retainedCount':len(store.retained())}
|
|
27
|
+
return {**result,'connection':'offline','notice':'Native execution is disconnected. Recorded checkpoints are shown; no task is being retried.'}
|
|
28
|
+
def observe_coordinators(self):
|
|
29
|
+
from .coordinator_activity import record
|
|
30
|
+
status=live.request(self.lib,'status',{})
|
|
31
|
+
if status.get('activityError'):
|
|
32
|
+
raise PackError('Native activity recording failed: '+status['activityError'])
|
|
33
|
+
return record(self.lib.root,status)
|
|
34
|
+
def submit(self,request):
|
|
35
|
+
return live.request(self.lib,'work-submit',request)
|
|
36
|
+
def respond(self,request):
|
|
37
|
+
return live.request(self.lib,'work-respond',request)
|
|
38
|
+
def read(self,task_id):
|
|
39
|
+
return live.request(self.lib,'work-read',{'taskId':task_id})
|
|
40
|
+
def events(self):
|
|
41
|
+
record=read_json(self.lib.root/'live-bridge.json',{})
|
|
42
|
+
if not record:
|
|
43
|
+
raise PackError('Native event connection unavailable')
|
|
44
|
+
with socket.socket(socket.AF_UNIX,socket.SOCK_STREAM) as connection:
|
|
45
|
+
connection.settimeout(3);connection.connect(record['socket']);connection.sendall(canonical({'token':record['token'],'operation':'work-watch','params':{}})+b'\n');connection.settimeout(None)
|
|
46
|
+
with connection.makefile('rb') as stream:
|
|
47
|
+
for line in stream:
|
|
48
|
+
value=json.loads(line)
|
|
49
|
+
if 'error' in value:
|
|
50
|
+
raise PackError(value['error'])
|
|
51
|
+
yield value
|