@topolo/cli 0.13.19 → 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/agent-packs/README.md +421 -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 +181 -16
- 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 +103 -6
- 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/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/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 +89 -10
- package/package.json +4 -4
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Supported native coordinator conversation and typed broker-tool boundary.
|
|
2
|
+
|
|
3
|
+
The native prompt_id is the deduplication identity. No transcript, chat export,
|
|
4
|
+
or locally generated substitute is consulted when it is missing.
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import pathlib
|
|
8
|
+
import sys
|
|
9
|
+
import uuid
|
|
10
|
+
|
|
11
|
+
from .storage import Library, PackError, read_json
|
|
12
|
+
from .work import require
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def submit(root, event, send=None):
|
|
16
|
+
require(isinstance(event, dict), 'Native Claude hook input must be an object')
|
|
17
|
+
lib = Library(root)
|
|
18
|
+
binding = read_json(lib.root / 'work/claude-frontdoors.json')
|
|
19
|
+
require(binding.get('format') == 'topolo-claude-frontdoors-v1',
|
|
20
|
+
'Claude front-door binding is not installed')
|
|
21
|
+
require(event.get('hook_event_name') in {'UserPromptSubmit', 'PreToolUse'},
|
|
22
|
+
'Unsupported Claude front-door hook')
|
|
23
|
+
rows = [r for r in binding['roles'] if r['sessionId'] == event.get('session_id')
|
|
24
|
+
and r['cwd'] == event.get('cwd')]
|
|
25
|
+
require(len(rows) == 1, 'Native Claude session is not the bound front door')
|
|
26
|
+
role = rows[0]
|
|
27
|
+
if event['hook_event_name'] == 'PreToolUse':
|
|
28
|
+
from .coordinator_context import tool_allowed
|
|
29
|
+
try:allowed=tool_allowed(lib.root,role,event)
|
|
30
|
+
except (PackError,ValueError,TypeError,OSError):allowed=False
|
|
31
|
+
return {'hookSpecificOutput': {'hookEventName': 'PreToolUse',
|
|
32
|
+
'permissionDecision': 'allow' if allowed else 'deny', 'permissionDecisionReason':
|
|
33
|
+
'Use only the exact broker schemas in the current Topolo coordinator context. For continuation: read the existing task, then Write {requestId: dispatchRequestId, taskId, revision: latest task.revision, intent: continue/request_changes/hold, note} to context/coordinator-request.json and run the installed respond --input command. Do not use {taskId,message}, create a replacement task, or perform engineering effects.'}}
|
|
34
|
+
identity = event.get('prompt_id')
|
|
35
|
+
try:
|
|
36
|
+
valid = isinstance(identity, str) and str(uuid.UUID(identity)) == identity
|
|
37
|
+
except ValueError:
|
|
38
|
+
valid = False
|
|
39
|
+
require(valid, 'Native Claude prompt identity is unavailable; no task was submitted')
|
|
40
|
+
require(event.get('permission_mode') == 'bypassPermissions',
|
|
41
|
+
'Claude front-door startup permissions differ from the installation')
|
|
42
|
+
require(isinstance(event.get('prompt'),str) and 1<=len(event['prompt'])<=16000,'Bounded native human input required')
|
|
43
|
+
if send is None:
|
|
44
|
+
from .live import request as live_request
|
|
45
|
+
send = lambda operation, params: live_request(lib, operation, params)
|
|
46
|
+
status = send('status', {})
|
|
47
|
+
require(status.get('harness') == 'claude' and status.get('cloudDispatchReady') is True
|
|
48
|
+
and status.get('desktopPid') == binding.get('desktopPid'),
|
|
49
|
+
'Claude cloud dispatch is not active; the coordinator will not execute the request itself')
|
|
50
|
+
from .coordinator_context import prepare
|
|
51
|
+
selected=prepare(lib.root,role,event,send('work-list',{}))
|
|
52
|
+
return {'hookSpecificOutput': {'hookEventName': 'UserPromptSubmit',
|
|
53
|
+
'additionalContext':'Topolo coordinator context (the original human prompt is unchanged; no task was submitted):\n'+json.dumps(selected,sort_keys=True,separators=(',',':'))}}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def main(root, stream=None):
|
|
58
|
+
try:
|
|
59
|
+
source = (stream or sys.stdin).read(100001)
|
|
60
|
+
require(len(source) <= 100000, 'Claude hook input exceeds its boundary')
|
|
61
|
+
event = json.loads(source)
|
|
62
|
+
if isinstance(event, dict) and event.get('hook_event_name') == 'UserPromptSubmit':
|
|
63
|
+
from .claude_service import ensure_running, refresh_desktop_owner
|
|
64
|
+
lib = Library(root)
|
|
65
|
+
refresh_desktop_owner(lib)
|
|
66
|
+
ensure_running(lib)
|
|
67
|
+
result = submit(pathlib.Path(root), event)
|
|
68
|
+
print(json.dumps(result))
|
|
69
|
+
return 0
|
|
70
|
+
except Exception as exc:
|
|
71
|
+
# Nonzero hook exit blocks direct execution on a failed handoff. Never
|
|
72
|
+
# print the incoming human prompt or a transcript in a diagnostic.
|
|
73
|
+
print('Topolo handoff unavailable: ' + str(exc), file=sys.stderr)
|
|
74
|
+
return 2
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Single-use supported Claude Code worker, without a terminal or saved history.
|
|
2
|
+
|
|
3
|
+
The Desktop front doors remain separate. This adapter retires a process, not a
|
|
4
|
+
Desktop chat, and never represents a CLI process exit as native chat archival.
|
|
5
|
+
"""
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import pathlib
|
|
10
|
+
import shlex
|
|
11
|
+
import signal
|
|
12
|
+
import sys
|
|
13
|
+
import uuid
|
|
14
|
+
|
|
15
|
+
from .claude import find_binary
|
|
16
|
+
from .storage import atomic_json, read_json, PackError
|
|
17
|
+
from .work import require
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def public_progress(text):
|
|
21
|
+
if not text.strip() or '```' in text:
|
|
22
|
+
return False
|
|
23
|
+
try:
|
|
24
|
+
json.loads(text)
|
|
25
|
+
except ValueError:
|
|
26
|
+
return True
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def hook(directory, event):
|
|
31
|
+
directory = pathlib.Path(directory)
|
|
32
|
+
gate = read_json(directory / 'gate.json')
|
|
33
|
+
require(event.get('session_id') == gate['sessionId'], 'Worker hook session differs')
|
|
34
|
+
kind = event.get('hook_event_name')
|
|
35
|
+
if kind == 'PreToolUse':
|
|
36
|
+
allowed = gate['phase'] == 'assigned' and gate.get('allowTools') is True and event.get('permission_mode') == 'bypassPermissions'
|
|
37
|
+
allowed = allowed and event.get('effort', {}).get('level') == gate['effort']
|
|
38
|
+
allowed = allowed and event.get('tool_name') not in {'Agent', 'Task', 'Skill', 'EnterWorktree', 'ExitWorktree'}
|
|
39
|
+
return {'hookSpecificOutput': {'hookEventName': kind,
|
|
40
|
+
'permissionDecision': 'allow' if allowed else 'deny',
|
|
41
|
+
'permissionDecisionReason': 'Topolo bounded worker phase and native effort gate'}}
|
|
42
|
+
require(kind in {'UserPromptSubmit', 'Stop'}, 'Unsupported worker hook')
|
|
43
|
+
with (directory / 'native-settings.jsonl').open('a') as stream:
|
|
44
|
+
stream.write(json.dumps({k: event.get(k) for k in
|
|
45
|
+
['session_id', 'prompt_id', 'hook_event_name', 'permission_mode', 'effort']}) + '\n')
|
|
46
|
+
return {}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def hook_main(directory):
|
|
50
|
+
try:
|
|
51
|
+
raw = sys.stdin.read(100001)
|
|
52
|
+
require(len(raw) <= 100000, 'Worker hook input exceeds its boundary')
|
|
53
|
+
print(json.dumps(hook(directory, json.loads(raw))))
|
|
54
|
+
return 0
|
|
55
|
+
except Exception as exc:
|
|
56
|
+
print('Topolo worker gate refused: ' + str(exc), file=sys.stderr)
|
|
57
|
+
return 2
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Session:
|
|
61
|
+
def __init__(self, cwd, worker_id, model, effort, progress=None):
|
|
62
|
+
require(str(uuid.UUID(worker_id)) == worker_id, 'Exact worker UUID required')
|
|
63
|
+
self.cwd = pathlib.Path(cwd)
|
|
64
|
+
self.id = worker_id
|
|
65
|
+
self.model = model
|
|
66
|
+
self.effort = effort
|
|
67
|
+
self.progress = progress
|
|
68
|
+
self.directory = self.cwd / '.topolo-worker'
|
|
69
|
+
self.directory.mkdir(mode=0o700)
|
|
70
|
+
self.process = None
|
|
71
|
+
self.pending = None
|
|
72
|
+
self.results = {}
|
|
73
|
+
self.init = None
|
|
74
|
+
self.closed = False
|
|
75
|
+
self.assigned = False
|
|
76
|
+
self.allow_tools = True
|
|
77
|
+
self.expected_email = None
|
|
78
|
+
self.auth_config_directory = None
|
|
79
|
+
self.tool_path = None
|
|
80
|
+
|
|
81
|
+
async def start(self):
|
|
82
|
+
binary = find_binary()
|
|
83
|
+
require(binary is not None, 'Signed-in Claude Code runtime is required')
|
|
84
|
+
gate = {'sessionId': self.id, 'model': self.model, 'effort': self.effort, 'phase': 'readiness'}
|
|
85
|
+
atomic_json(self.directory / 'gate.json', gate)
|
|
86
|
+
adapter = pathlib.Path(__file__).resolve().parents[2] / 'topolo-agent-pack'
|
|
87
|
+
require(adapter.is_file(), 'Installed bundled worker-hook entrypoint is unavailable')
|
|
88
|
+
command = shlex.join([sys.executable, str(adapter), 'work', 'claude-worker-hook',
|
|
89
|
+
'--harness', 'claude', '--input', str(self.directory)]) + ' || exit 2'
|
|
90
|
+
settings = {'autoMemoryEnabled': False, 'hooks': {
|
|
91
|
+
event: [{'hooks': [{'type': 'command', 'command': command, 'timeout': 15}]}]
|
|
92
|
+
for event in ['UserPromptSubmit', 'Stop', 'PreToolUse']}}
|
|
93
|
+
atomic_json(self.directory / 'settings.json', settings)
|
|
94
|
+
env = dict(os.environ)
|
|
95
|
+
# Use the explicitly bound native profile's supported subscription login;
|
|
96
|
+
# never copy a token or silently select default-profile/API-key billing.
|
|
97
|
+
require(not any(env.get(k) for k in ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN',
|
|
98
|
+
'CLAUDE_CODE_OAUTH_TOKEN', 'ANTHROPIC_BASE_URL']),
|
|
99
|
+
'Explicit provider or credential overrides are outside this subscription worker binding')
|
|
100
|
+
require(self.auth_config_directory is not None, 'An explicitly bound Claude authentication profile is required')
|
|
101
|
+
env['CLAUDE_CONFIG_DIR'] = self.auth_config_directory
|
|
102
|
+
env.pop('CLAUDECODE', None)
|
|
103
|
+
env['PYTHONPATH'] = str(pathlib.Path(__file__).resolve().parent.parent)
|
|
104
|
+
if self.tool_path is not None:
|
|
105
|
+
env['PATH'] = self.tool_path
|
|
106
|
+
auth = await asyncio.create_subprocess_exec(binary, 'auth', 'status', '--json', env=env,
|
|
107
|
+
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.DEVNULL)
|
|
108
|
+
try:
|
|
109
|
+
stdout, _ = await asyncio.wait_for(auth.communicate(), 15)
|
|
110
|
+
finally:
|
|
111
|
+
if auth.returncode is None:
|
|
112
|
+
auth.kill()
|
|
113
|
+
await auth.wait()
|
|
114
|
+
account = json.loads(stdout)
|
|
115
|
+
require(auth.returncode == 0 and account.get('loggedIn') is True and account.get('authMethod') == 'claude.ai'
|
|
116
|
+
and self.expected_email is not None and account.get('email') == self.expected_email,
|
|
117
|
+
'The signed-in Claude subscription account differs from the installation')
|
|
118
|
+
stderr = (self.directory / 'stderr.log').open('wb')
|
|
119
|
+
try:
|
|
120
|
+
self.process = await asyncio.create_subprocess_exec(binary, '--print',
|
|
121
|
+
'--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose',
|
|
122
|
+
'--replay-user-messages', '--session-id', self.id, '--no-session-persistence',
|
|
123
|
+
'--model', self.model, '--effort', self.effort, '--permission-mode', 'bypassPermissions',
|
|
124
|
+
'--setting-sources', '', '--settings', str(self.directory / 'settings.json'),
|
|
125
|
+
'--strict-mcp-config', '--mcp-config', '{"mcpServers":{}}',
|
|
126
|
+
'--append-system-prompt-file', str(self.cwd / 'CLAUDE.md'), cwd=self.cwd, env=env,
|
|
127
|
+
stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=stderr,
|
|
128
|
+
limit=2 * 1024 * 1024, start_new_session=True)
|
|
129
|
+
finally:
|
|
130
|
+
stderr.close()
|
|
131
|
+
ready = 'TOPOLO_WORKER_READY:' + self.id
|
|
132
|
+
result = await self.exchange(self.id, 'Readiness only. Do not use tools or perform work. Reply exactly ' + ready)
|
|
133
|
+
require(result['output'].strip() == ready and result['toolItems'] == 0,
|
|
134
|
+
'The exact tool-free readiness probe did not complete')
|
|
135
|
+
proofs = [json.loads(line) for line in (self.directory / 'native-settings.jsonl').read_text().splitlines()]
|
|
136
|
+
require(any(p['hook_event_name'] == 'UserPromptSubmit' and p['permission_mode'] == 'bypassPermissions' for p in proofs)
|
|
137
|
+
and any(p['hook_event_name'] == 'Stop' and isinstance(p['effort'], dict)
|
|
138
|
+
and p['effort'].get('level') == self.effort for p in proofs),
|
|
139
|
+
'Actual native permission and effort acknowledgement is unavailable')
|
|
140
|
+
return {'model': self.model, 'effort': self.effort, 'sessionId': self.id,
|
|
141
|
+
'processId': self.process.pid, 'boundary': 'native init, readiness result and Stop hook'}
|
|
142
|
+
|
|
143
|
+
async def assign(self, message_id, prompt):
|
|
144
|
+
require(not self.assigned and self.pending is None, 'This worker already has an assignment; reconcile without resend')
|
|
145
|
+
self.assigned = True
|
|
146
|
+
atomic_json(self.directory / 'gate.json', {'sessionId': self.id, 'model': self.model,
|
|
147
|
+
'effort': self.effort, 'phase': 'assigned', 'allowTools': self.allow_tools})
|
|
148
|
+
return await self.exchange(message_id, prompt)
|
|
149
|
+
|
|
150
|
+
async def exchange(self, message_id, prompt):
|
|
151
|
+
require(self.process is not None and not self.closed and self.pending is None,
|
|
152
|
+
'Native worker is not ready for one exact input')
|
|
153
|
+
self.pending = message_id
|
|
154
|
+
# A create-only marker precedes stdin delivery. Lost acknowledgements
|
|
155
|
+
# retain the UUID; neither startup nor exchange retries it.
|
|
156
|
+
with (self.directory / (message_id + '.send.json')).open('x') as stream:
|
|
157
|
+
json.dump({'sessionId': self.id, 'messageId': message_id}, stream)
|
|
158
|
+
stream.flush()
|
|
159
|
+
os.fsync(stream.fileno())
|
|
160
|
+
value = {'type': 'user', 'session_id': self.id, 'uuid': message_id,
|
|
161
|
+
'message': {'role': 'user', 'content': prompt}}
|
|
162
|
+
self.process.stdin.write((json.dumps(value) + '\n').encode())
|
|
163
|
+
await self.process.stdin.drain()
|
|
164
|
+
echoed = False
|
|
165
|
+
tools = 0
|
|
166
|
+
async with asyncio.timeout(1800):
|
|
167
|
+
while line := await self.process.stdout.readline():
|
|
168
|
+
row = json.loads(line)
|
|
169
|
+
require(row.get('session_id', self.id) == self.id, 'Native event belongs to another session')
|
|
170
|
+
if row.get('type') == 'system' and row.get('subtype') == 'init':
|
|
171
|
+
require(self.init is None and row.get('model') == self.model
|
|
172
|
+
and row.get('permissionMode') == 'bypassPermissions', 'Native worker startup tuple differs')
|
|
173
|
+
self.init = row
|
|
174
|
+
if row.get('type') == 'user' and row.get('uuid') == message_id:
|
|
175
|
+
require(not echoed, 'Native input acknowledgement is duplicated')
|
|
176
|
+
echoed = True
|
|
177
|
+
if row.get('type') == 'assistant':
|
|
178
|
+
content = row.get('message', {}).get('content', [])
|
|
179
|
+
tools += sum(item.get('type') == 'tool_use' for item in content)
|
|
180
|
+
if self.assigned and self.progress:
|
|
181
|
+
text = '\n'.join(item['text'] for item in content if item.get('type') == 'text')
|
|
182
|
+
if public_progress(text):
|
|
183
|
+
await self.progress(row['uuid'], text[:1600])
|
|
184
|
+
if row.get('type') == 'result':
|
|
185
|
+
require(self.init is not None and echoed and row.get('is_error') is False
|
|
186
|
+
and row.get('subtype') == 'success' and row.get('terminal_reason') == 'completed'
|
|
187
|
+
and row.get('stop_reason') == 'end_turn' and not row.get('permission_denials')
|
|
188
|
+
and set(row.get('modelUsage', {})) == {self.model}
|
|
189
|
+
and row.get('queued_turn_count', 0) == 0 and isinstance(row.get('result'), str),
|
|
190
|
+
'Native worker did not prove a successful selected-model turn: ' + str(row.get('result', ''))[:300])
|
|
191
|
+
receipt = {'id': message_id, 'status': 'completed', 'output': row['result'],
|
|
192
|
+
'toolItems': tools, 'nativeSessionId': self.id, 'nativeResultId': row['uuid']}
|
|
193
|
+
self.results[message_id] = receipt
|
|
194
|
+
self.pending = None
|
|
195
|
+
return receipt
|
|
196
|
+
raise PackError('Native worker ended without its exact terminal receipt; no resend')
|
|
197
|
+
|
|
198
|
+
async def close(self):
|
|
199
|
+
if self.process is not None and self.process.returncode is None:
|
|
200
|
+
self.process.stdin.close()
|
|
201
|
+
try:
|
|
202
|
+
await asyncio.wait_for(self.process.wait(), 8)
|
|
203
|
+
except TimeoutError:
|
|
204
|
+
os.killpg(self.process.pid, signal.SIGTERM)
|
|
205
|
+
await asyncio.wait_for(self.process.wait(), 8)
|
|
206
|
+
self.closed = True
|
|
207
|
+
return {'state': 'process-retired', 'sessionId': self.id, 'desktopChatArchived': False}
|