@topolo/cli 0.13.19 → 0.13.22

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.
Files changed (142) hide show
  1. package/agent-packs/README.md +421 -1
  2. package/agent-packs/docs/selected-artifacts.md +47 -0
  3. package/agent-packs/documentation/cloud-task-state.md +56 -0
  4. package/agent-packs/domain-contexts/README.md +25 -0
  5. package/agent-packs/domain-contexts/cicd.json +8 -0
  6. package/agent-packs/gui/feed-adapter.js +4 -0
  7. package/agent-packs/gui/index.html +9 -0
  8. package/agent-packs/gui/studio.css +14 -0
  9. package/agent-packs/gui/studio.js +121 -0
  10. package/agent-packs/gui/studio_server.py +355 -0
  11. package/agent-packs/roles/cicd/CLAUDE.md +24 -0
  12. package/agent-packs/specializations/cicd--cicd.json +5 -0
  13. package/agent-packs/specializations/cicd--pipeline-engineering.json +5 -0
  14. package/agent-packs/specializations/cicd--promotion-deployment.json +5 -0
  15. package/agent-packs/specializations/cicd--queue-recovery.json +5 -0
  16. package/agent-packs/specializations/cicd--release-verification.json +5 -0
  17. package/agent-packs/specializations/cicd--runner-capacity.json +5 -0
  18. package/agent-packs/specializations/intakes--engineering-02.json +5 -0
  19. package/agent-packs/specializations/intakes--engineering-03.json +5 -0
  20. package/agent-packs/specializations/intakes--engineering.json +5 -0
  21. package/agent-packs/specializations/intakes--operator-01.json +5 -0
  22. package/agent-packs/specializations/intakes--operator-02.json +5 -0
  23. package/agent-packs/specializations/operator-skills--audio-and-narration-specialist.json +5 -0
  24. package/agent-packs/specializations/operator-skills--brand-and-copywriting-specialist.json +5 -0
  25. package/agent-packs/specializations/operator-skills--capability-research-specialist.json +5 -0
  26. package/agent-packs/specializations/operator-skills--data-analysis-and-visualization.json +5 -0
  27. package/agent-packs/specializations/operator-skills--financial-evidence-specialist.json +5 -0
  28. package/agent-packs/specializations/operator-skills--operating-template-specialist.json +5 -0
  29. package/agent-packs/specializations/operator-skills--operator-skill-library.json +5 -0
  30. package/agent-packs/specializations/operator-skills--operator-skills.json +5 -0
  31. package/agent-packs/specializations/operator-skills--operator-workflow-evaluation.json +5 -0
  32. package/agent-packs/specializations/operator-skills--privacy-and-agreement-review.json +5 -0
  33. package/agent-packs/specializations/operator-skills--storytelling-specialist.json +5 -0
  34. package/agent-packs/specializations/operator-skills--ux-and-accessibility-specialist.json +5 -0
  35. package/agent-packs/specializations/operator-skills--video-production-specialist.json +5 -0
  36. package/agent-packs/specializations/operator-skills--visual-design-specialist.json +5 -0
  37. package/agent-packs/specializations/operator-skills--workflow-integration-specialist.json +5 -0
  38. package/agent-packs/specializations/operators--access-and-privacy.json +5 -0
  39. package/agent-packs/specializations/operators--accounting-evidence-review.json +5 -0
  40. package/agent-packs/specializations/operators--agreement-operations.json +5 -0
  41. package/agent-packs/specializations/operators--books-operator.json +5 -0
  42. package/agent-packs/specializations/operators--brand-operator.json +5 -0
  43. package/agent-packs/specializations/operators--commerce-operations.json +5 -0
  44. package/agent-packs/specializations/operators--communications-operator.json +5 -0
  45. package/agent-packs/specializations/operators--content-and-assets-operator.json +5 -0
  46. package/agent-packs/specializations/operators--customer-operations.json +5 -0
  47. package/agent-packs/specializations/operators--design-operator.json +5 -0
  48. package/agent-packs/specializations/operators--design-production-02.json +5 -0
  49. package/agent-packs/specializations/operators--design-production-03.json +5 -0
  50. package/agent-packs/specializations/operators--design-production-04.json +5 -0
  51. package/agent-packs/specializations/operators--device-and-feed-operator.json +5 -0
  52. package/agent-packs/specializations/operators--director-operator.json +5 -0
  53. package/agent-packs/specializations/operators--experience-review.json +5 -0
  54. package/agent-packs/specializations/operators--growth-operations.json +5 -0
  55. package/agent-packs/specializations/operators--learn-operator.json +5 -0
  56. package/agent-packs/specializations/operators--localization-operator.json +5 -0
  57. package/agent-packs/specializations/operators--operators.json +5 -0
  58. package/agent-packs/specializations/operators--pay-operator.json +5 -0
  59. package/agent-packs/specializations/operators--research-and-intelligence.json +5 -0
  60. package/agent-packs/specializations/operators--roadmap-operator.json +5 -0
  61. package/agent-packs/specializations/operators--social-content-operator.json +5 -0
  62. package/agent-packs/specializations/operators--talent-operations.json +5 -0
  63. package/agent-packs/specializations/operators--web-operator.json +5 -0
  64. package/agent-packs/specializations/operators--workflow-operator.json +5 -0
  65. package/agent-packs/specializations/specialists--adversarial-qa.json +5 -0
  66. package/agent-packs/specializations/specialists--backlog-dispatch.json +5 -0
  67. package/agent-packs/specializations/specialists--build-01.json +5 -0
  68. package/agent-packs/specializations/specialists--build-02.json +5 -0
  69. package/agent-packs/specializations/specialists--build-03.json +5 -0
  70. package/agent-packs/specializations/specialists--developer-environment-steward.json +5 -0
  71. package/agent-packs/specializations/specialists--documentation-knowledge-steward.json +5 -0
  72. package/agent-packs/specializations/specialists--execution-readiness.json +5 -0
  73. package/agent-packs/specializations/specialists--incident-triage.json +5 -0
  74. package/agent-packs/specializations/specialists--platform-package-steward.json +5 -0
  75. package/agent-packs/specializations/specialists--product-direction-01.json +5 -0
  76. package/agent-packs/specializations/specialists--product-direction-02.json +5 -0
  77. package/agent-packs/specializations/specialists--release-recovery.json +5 -0
  78. package/agent-packs/specializations/specialists--release-watch.json +5 -0
  79. package/agent-packs/specializations/specialists--roadmap-planning-01.json +5 -0
  80. package/agent-packs/specializations/specialists--roadmap-planning-02.json +5 -0
  81. package/agent-packs/specializations/specialists--security-permissions-review.json +5 -0
  82. package/agent-packs/specializations/specialists--system-architecture-01.json +5 -0
  83. package/agent-packs/specializations/specialists--system-architecture-02.json +5 -0
  84. package/agent-packs/specializations/specialists--translation.json +5 -0
  85. package/agent-packs/specializations/specialists--ux-consistency-review.json +5 -0
  86. package/agent-packs/specializations/specialists--workflow-improvement.json +5 -0
  87. package/agent-packs/specializations/specialists--worktree-retirement.json +5 -0
  88. package/agent-packs/src/topolo_agent_pack/cicd.py +101 -0
  89. package/agent-packs/src/topolo_agent_pack/claude.py +22 -7
  90. package/agent-packs/src/topolo_agent_pack/claude_activation.py +157 -0
  91. package/agent-packs/src/topolo_agent_pack/claude_completion_recovery.py +216 -0
  92. package/agent-packs/src/topolo_agent_pack/claude_desktop.py +181 -0
  93. package/agent-packs/src/topolo_agent_pack/claude_dispatch.py +240 -0
  94. package/agent-packs/src/topolo_agent_pack/claude_prompt.py +74 -0
  95. package/agent-packs/src/topolo_agent_pack/claude_runner.py +207 -0
  96. package/agent-packs/src/topolo_agent_pack/claude_service.py +397 -0
  97. package/agent-packs/src/topolo_agent_pack/cli.py +181 -16
  98. package/agent-packs/src/topolo_agent_pack/closed_ledger_fence.py +118 -0
  99. package/agent-packs/src/topolo_agent_pack/cloud_attach.py +128 -0
  100. package/agent-packs/src/topolo_agent_pack/cloud_cutover.py +134 -0
  101. package/agent-packs/src/topolo_agent_pack/cloud_delta.py +162 -0
  102. package/agent-packs/src/topolo_agent_pack/cloud_migrate.py +142 -0
  103. package/agent-packs/src/topolo_agent_pack/cloud_recovery.py +42 -0
  104. package/agent-packs/src/topolo_agent_pack/cloud_work.py +126 -0
  105. package/agent-packs/src/topolo_agent_pack/coordinator_activity.py +27 -0
  106. package/agent-packs/src/topolo_agent_pack/coordinator_context.py +91 -0
  107. package/agent-packs/src/topolo_agent_pack/coordinator_refresh.py +116 -0
  108. package/agent-packs/src/topolo_agent_pack/desktop_acceptance.py +45 -0
  109. package/agent-packs/src/topolo_agent_pack/desktop_launcher.py +114 -0
  110. package/agent-packs/src/topolo_agent_pack/domain_context.py +38 -0
  111. package/agent-packs/src/topolo_agent_pack/execution.py +103 -6
  112. package/agent-packs/src/topolo_agent_pack/frontdoor.py +23 -0
  113. package/agent-packs/src/topolo_agent_pack/linked_task_context.py +80 -0
  114. package/agent-packs/src/topolo_agent_pack/live.py +285 -18
  115. package/agent-packs/src/topolo_agent_pack/native.py +160 -4
  116. package/agent-packs/src/topolo_agent_pack/review_context.py +33 -0
  117. package/agent-packs/src/topolo_agent_pack/role_catalog.py +137 -0
  118. package/agent-packs/src/topolo_agent_pack/role_context.py +63 -0
  119. package/agent-packs/src/topolo_agent_pack/role_refresh.py +50 -0
  120. package/agent-packs/src/topolo_agent_pack/topolo.py +7 -1
  121. package/agent-packs/src/topolo_agent_pack/work.py +322 -0
  122. package/agent-packs/src/topolo_agent_pack/work_adoption.py +122 -0
  123. package/agent-packs/src/topolo_agent_pack/work_artifacts.py +84 -0
  124. package/agent-packs/src/topolo_agent_pack/work_context.py +77 -0
  125. package/agent-packs/src/topolo_agent_pack/work_gui.py +51 -0
  126. package/agent-packs/src/topolo_agent_pack/work_native.py +414 -0
  127. package/agent-packs/src/topolo_agent_pack/work_wait.py +151 -0
  128. package/agent-packs/templates/topolo-coordinators/AGENTS.md +7 -0
  129. package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/AGENTS.md +6 -0
  130. package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/config.json +4 -0
  131. package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/AGENTS.md +3 -0
  132. package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/config.json +4 -0
  133. package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/AGENTS.md +3 -0
  134. package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/config.json +4 -0
  135. package/agent-packs/templates/topolo-coordinators/pack.json +56 -0
  136. package/dist/chunk-NYDIAYVY.js +144 -0
  137. package/dist/{claude-pack-backend-YEVPAOID.js → chunk-UY5LEU5C.js} +1659 -21
  138. package/dist/claude-desktop-profile-JCQKSY3K.js +13 -0
  139. package/dist/claude-pack-activation-Q25MCEPU.js +506 -0
  140. package/dist/claude-pack-backend-HUTKGIDS.js +90 -0
  141. package/dist/index.js +152 -73
  142. package/package.json +5 -5
@@ -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}