@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
|
@@ -7,6 +7,8 @@ reviewed role. Changing that intent requires rebuilding the selected policy.
|
|
|
7
7
|
import copy
|
|
8
8
|
import hashlib
|
|
9
9
|
import json
|
|
10
|
+
import os
|
|
11
|
+
import datetime
|
|
10
12
|
import pathlib
|
|
11
13
|
import sqlite3
|
|
12
14
|
import subprocess
|
|
@@ -16,6 +18,26 @@ from contextlib import closing
|
|
|
16
18
|
from . import policy, permissions
|
|
17
19
|
from .storage import PackError, atomic_json, component_key, read_json
|
|
18
20
|
|
|
21
|
+
# Native desktop transport/tool configuration is independent of role execution
|
|
22
|
+
# policy. Keep this list explicit; unknown overrides still fail closed.
|
|
23
|
+
DESKTOP_TRANSPORT_CONFIG_KEYS = frozenset('''
|
|
24
|
+
apps.connector_openai_pages.tools
|
|
25
|
+
features.apps_mcp_path_override features.async_compaction
|
|
26
|
+
features.code_mode_interrupt features.code_mode_tool_description_first
|
|
27
|
+
features.code_mode_tool_search features.collaboration_modes
|
|
28
|
+
features.compaction_image_budget features.concurrent_reasoning_summaries
|
|
29
|
+
features.deferred_tool_world_state features.enable_mcp_apps
|
|
30
|
+
features.guardian_approval features.guardian_reuse_parent_compaction
|
|
31
|
+
features.guardianv2 features.guardianv2_decisions_comparison
|
|
32
|
+
features.image_detail_original features.image_generation features.image_resize_notice
|
|
33
|
+
features.incremental_tools features.instant_interrupt features.item_ids
|
|
34
|
+
features.mcp_oauth_refresh_coordination features.realtime_conversation
|
|
35
|
+
features.recommended_plugins features.request_rule features.unified_image_budget
|
|
36
|
+
features.workspace_dependencies mcp_servers.artifact_session
|
|
37
|
+
mcp_servers.cua_repl mcp_servers.node_repl
|
|
38
|
+
plugins.codex-app-tools@openai-bundled.mcp_servers.codex_app.enabled_tools
|
|
39
|
+
'''.split())
|
|
40
|
+
|
|
19
41
|
|
|
20
42
|
def prepare_runtime(lib, components, runtime):
|
|
21
43
|
"""Validate existing role intent without installing a policy or changing history."""
|
|
@@ -30,7 +52,15 @@ def prepare_runtime(lib, components, runtime):
|
|
|
30
52
|
component['key'] in keys or component['threadId'] in identities):
|
|
31
53
|
raise PackError('Execution policy has no unique owned role: ' + component['key'])
|
|
32
54
|
keys.add(component['key']); identities.add(component['threadId'])
|
|
33
|
-
|
|
55
|
+
# Checkpoints contain only the active selection. Installation consent is
|
|
56
|
+
# owned by the original complete pack, not that history snapshot.
|
|
57
|
+
consent = None
|
|
58
|
+
if hasattr(lib, 'root'):
|
|
59
|
+
policy_manifest, _ = lib.load(matches[0]['pack'])
|
|
60
|
+
original = [a for a in policy_manifest['agents'] if component_key(a) == component['key']]
|
|
61
|
+
if len(original) != 1:
|
|
62
|
+
raise PackError('Checkpoint role has no unique original policy identity')
|
|
63
|
+
consent = permissions.load(lib, matches[0]['pack'], policy_manifest['agents'])
|
|
34
64
|
rows.append({'key': component['key'], 'threadId': component['threadId'],
|
|
35
65
|
'sourceRevision': revision, 'cwd': cwds[component['threadId']],
|
|
36
66
|
'intakeKey': consent['intakeRoutes'][component['key']] if consent else None,
|
|
@@ -54,7 +84,7 @@ class ExecutionGate:
|
|
|
54
84
|
def __init__(self, document):
|
|
55
85
|
if document.get('format') != 'topolo-codex-runtime-execution' or document.get('schemaVersion') != 1:
|
|
56
86
|
raise PackError('Unqualified runtime execution policy')
|
|
57
|
-
self.roles = {}; self.pending = {}; self.verified = set(); self.catalog = {}
|
|
87
|
+
self.roles = {}; self.pending = {}; self.verified = set(); self.catalog = {}; self.desktop_home = None
|
|
58
88
|
for role in document['roles']:
|
|
59
89
|
tid = role['threadId']; spec = role['settings']
|
|
60
90
|
if tid in self.roles or not pathlib.Path(role['cwd']).is_absolute():
|
|
@@ -142,6 +172,26 @@ class ExecutionGate:
|
|
|
142
172
|
return {'type': 'workspaceWrite', 'networkAccess': False, 'writableRoots': [role['cwd']],
|
|
143
173
|
'excludeSlashTmp': True, 'excludeTmpdirEnvVar': True}
|
|
144
174
|
|
|
175
|
+
def qualify_roots(self, role, roots):
|
|
176
|
+
if not isinstance(roots, list) or len(roots)>2 or any(not isinstance(p,str) for p in roots) or len(set(roots))!=len(roots):
|
|
177
|
+
raise PackError('Frontend workspace roots have an unsupported shape')
|
|
178
|
+
if not roots:return
|
|
179
|
+
if roots[0]!=role['cwd']:
|
|
180
|
+
raise PackError('Frontend workspace roots differ from the reviewed role')
|
|
181
|
+
if len(roots)==1:return
|
|
182
|
+
if self.desktop_home is None:
|
|
183
|
+
raise PackError('Owned desktop visualization root is unavailable')
|
|
184
|
+
path=pathlib.Path(roots[1]);base=pathlib.Path(self.desktop_home)/'visualizations'
|
|
185
|
+
try:
|
|
186
|
+
relative=path.relative_to(base)
|
|
187
|
+
year,month,day,tid=relative.parts
|
|
188
|
+
if tid!=role['threadId'] or len(year)!=4 or len(month)!=2 or len(day)!=2:
|
|
189
|
+
raise ValueError()
|
|
190
|
+
datetime.date(int(year),int(month),int(day))
|
|
191
|
+
if not path.is_absolute() or path.resolve()!=path or not path.is_dir() or path.stat().st_uid!=os.getuid():
|
|
192
|
+
raise ValueError()
|
|
193
|
+
except (ValueError,OSError):raise PackError('Frontend visualization root is not the owned thread directory')
|
|
194
|
+
|
|
145
195
|
def prepare(self, message):
|
|
146
196
|
value = copy.deepcopy(message); method = value.get('method'); params = value.get('params') or {}
|
|
147
197
|
tid = params.get('threadId'); role = self.roles.get(tid)
|
|
@@ -158,11 +208,47 @@ class ExecutionGate:
|
|
|
158
208
|
raise PackError('Dynamic lane needs actual loaded role context before task execution')
|
|
159
209
|
if spec['model_reasoning_effort'] not in self.catalog.get(spec['model'], set()):
|
|
160
210
|
raise PackError('Role model/effort is absent from the native account catalog; no fallback selected')
|
|
161
|
-
|
|
162
|
-
|
|
211
|
+
collaboration = params.get('collaborationMode')
|
|
212
|
+
if collaboration is not None:
|
|
213
|
+
settings = collaboration.get('settings') if isinstance(collaboration, dict) else None
|
|
214
|
+
if (method != 'turn/start' or not isinstance(collaboration, dict) or
|
|
215
|
+
set(collaboration) != {'mode', 'settings'} or collaboration['mode'] != 'default' or
|
|
216
|
+
not isinstance(settings, dict) or set(settings) - {'model', 'reasoning_effort', 'developer_instructions'} or
|
|
217
|
+
settings.get('model') != spec['model'] or
|
|
218
|
+
settings.get('reasoning_effort') not in (None, spec['model_reasoning_effort']) or
|
|
219
|
+
(settings.get('developer_instructions') is not None and
|
|
220
|
+
(not isinstance(settings['developer_instructions'], str) or
|
|
221
|
+
len(settings['developer_instructions'].encode()) > 64000))):
|
|
222
|
+
raise PackError('Frontend collaboration mode conflicts with the reviewed role policy')
|
|
223
|
+
# Native collaboration settings take precedence over top-level model
|
|
224
|
+
# and effort. Bind them to the same reviewed tuple before forwarding.
|
|
225
|
+
collaboration['settings']['reasoning_effort'] = spec['model_reasoning_effort']
|
|
226
|
+
# Desktop supplies its generic Default-mode prompt here. It is not
|
|
227
|
+
# reviewed role context: discard it rather than accepting any extra
|
|
228
|
+
# frontend developer authority. This is independent of app builds.
|
|
229
|
+
collaboration['settings']['developer_instructions'] = None
|
|
230
|
+
# An empty environment selection explicitly disables environment access;
|
|
231
|
+
# the desktop sends it on ordinary local turns. Nonempty selections
|
|
232
|
+
# still require independently reviewed environment identity and roots.
|
|
233
|
+
environments = params.get('environments')
|
|
234
|
+
if environments not in (None, []):
|
|
235
|
+
if not isinstance(environments,list) or len(environments)!=1 or not isinstance(environments[0],dict):
|
|
236
|
+
raise PackError('Frontend environment selection has an unsupported shape')
|
|
237
|
+
environment=environments[0]
|
|
238
|
+
if (set(environment)-{'environmentId','cwd','runtimeWorkspaceRoots'} or
|
|
239
|
+
environment.get('environmentId')!='local' or environment.get('cwd')!=role['cwd']):
|
|
240
|
+
raise PackError('Frontend environment selection differs from the reviewed local role')
|
|
241
|
+
if environment.get('runtimeWorkspaceRoots') is not None:self.qualify_roots(role,environment['runtimeWorkspaceRoots'])
|
|
242
|
+
roots = params.get('runtimeWorkspaceRoots')
|
|
243
|
+
if roots is not None:self.qualify_roots(role,roots)
|
|
244
|
+
if environments and roots is not None and environments[0].get('runtimeWorkspaceRoots') not in (None,roots):
|
|
245
|
+
raise PackError('Frontend top-level and local environment roots conflict')
|
|
246
|
+
forbidden = [key for key in ['permissions', 'path', 'history'] if params.get(key) is not None]
|
|
247
|
+
if forbidden:
|
|
248
|
+
raise PackError('Frontend supplied unreviewed permission, identity or execution overrides: ' + ','.join(forbidden))
|
|
163
249
|
wanted = {'cwd': role['cwd'], 'model': spec['model'], 'approvalPolicy': spec['approval_policy'],
|
|
164
250
|
'approvalsReviewer': spec['approvals_reviewer']}
|
|
165
|
-
if role.get('dynamicLaneId') and method=='thread/resume':wanted['allowProviderModelFallback']=False
|
|
251
|
+
if (role.get('dynamicLaneId') or role.get('taskWorkerId')) and method=='thread/resume':wanted['allowProviderModelFallback']=False
|
|
166
252
|
if method == 'thread/resume':
|
|
167
253
|
wanted['sandbox'] = spec['sandbox_mode']
|
|
168
254
|
wanted['config'] = {'model_reasoning_effort': spec['model_reasoning_effort'],
|
|
@@ -173,6 +259,13 @@ class ExecutionGate:
|
|
|
173
259
|
raise PackError('Role needs a verified native resume before starting a turn')
|
|
174
260
|
wanted.update(effort=spec['model_reasoning_effort'], sandboxPolicy=self.sandbox(role))
|
|
175
261
|
for key, expected in wanted.items():
|
|
262
|
+
if key=='config' and isinstance(params.get(key),dict):
|
|
263
|
+
supplied=params[key]
|
|
264
|
+
if (set(supplied)-set(expected)-DESKTOP_TRANSPORT_CONFIG_KEYS or
|
|
265
|
+
any(supplied[k]!=expected[k] for k in set(supplied)&set(expected))):
|
|
266
|
+
raise PackError('Frontend config conflicts with the reviewed role policy')
|
|
267
|
+
params[key]={**copy.deepcopy(supplied),**copy.deepcopy(expected)}
|
|
268
|
+
continue
|
|
176
269
|
if params.get(key) is not None and params[key] != expected:
|
|
177
270
|
raise PackError('Frontend ' + key + ' conflicts with the reviewed role policy')
|
|
178
271
|
params[key] = copy.deepcopy(expected)
|
|
@@ -207,7 +300,11 @@ class ExecutionGate:
|
|
|
207
300
|
|
|
208
301
|
def load_gate(runtime):
|
|
209
302
|
path = pathlib.Path(runtime) / 'execution-policy.json'
|
|
210
|
-
|
|
303
|
+
if not path.exists():return None
|
|
304
|
+
gate=ExecutionGate(read_json(path))
|
|
305
|
+
runtime=pathlib.Path(runtime).resolve()
|
|
306
|
+
if runtime.parent.name=='runtimes':gate.desktop_home=runtime.parent.parent/'desktop-home'
|
|
307
|
+
return gate
|
|
211
308
|
|
|
212
309
|
|
|
213
310
|
def qualify_protocol(binary, run=subprocess.run, *, lane_creation=False):
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Legacy explicit native-message request helpers.
|
|
2
|
+
|
|
3
|
+
Conversational coordinators use coordinator_context instead: a human status or
|
|
4
|
+
follow-up must never call this conversion or replace native input with a receipt.
|
|
5
|
+
These helpers retain the original deterministic identity for explicit callers.
|
|
6
|
+
"""
|
|
7
|
+
import copy,uuid
|
|
8
|
+
from .work import require
|
|
9
|
+
|
|
10
|
+
def request(value,channel):
|
|
11
|
+
params=value['params'];identity=params.get('clientUserMessageId')
|
|
12
|
+
require(isinstance(identity,str) and 1<=len(identity)<=200,'Native human message identity is missing; use the work GUI so retries cannot duplicate a task')
|
|
13
|
+
inputs=params.get('input')
|
|
14
|
+
require(isinstance(inputs,list) and inputs and all(isinstance(x,dict) and x.get('type')=='text' and isinstance(x.get('text'),str) for x in inputs),'This front door currently requires text; submit attachment-backed work through its bound task packet')
|
|
15
|
+
objective='\n'.join(x['text'] for x in inputs)
|
|
16
|
+
require(objective.strip(),'Empty task request')
|
|
17
|
+
task_id=str(uuid.uuid5(uuid.NAMESPACE_URL,'topolo-frontdoor:'+params['threadId']+':'+identity))
|
|
18
|
+
return {'requestId':task_id,'objective':objective,'channel':channel,'canonicalClaimId':None}
|
|
19
|
+
|
|
20
|
+
def acknowledgement(value,task):
|
|
21
|
+
result=copy.deepcopy(value)
|
|
22
|
+
result['params']['input']=[{'type':'text','text':'Topolo front-door queue receipt. The human request has already been durably recorded as task '+task['id']+'. Its current state is '+task['state']+'. A separate temporary specialist owns routing and execution. This turn is acknowledgement only. Do not read files, use tools, run commands, contact anyone, inspect servers or carry out any previous assignment. Reply briefly that the request is recorded and progress is in the work GUI: http://127.0.0.1:8769/ . Do not claim the work is complete.'}]
|
|
23
|
+
return result
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Explicit same-authority task citations, not portfolio or recursive history."""
|
|
2
|
+
import asyncio,copy,hashlib,json,pathlib,re
|
|
3
|
+
from .storage import atomic_json,canonical,PackError
|
|
4
|
+
from .work import require
|
|
5
|
+
|
|
6
|
+
UUID=re.compile(r'(?<![A-Za-z0-9])[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?![A-Za-z0-9])',re.I)
|
|
7
|
+
MAX_TASKS=4
|
|
8
|
+
MAX_EVIDENCE=8
|
|
9
|
+
MAX_FILE=512*1024
|
|
10
|
+
MAX_BYTES=2*1024*1024
|
|
11
|
+
|
|
12
|
+
def references(task):
|
|
13
|
+
text=task['objective']+'\n'+'\n'.join(r['note'] for r in task.get('humanResponses',[]))
|
|
14
|
+
require(len(text)<=100000,'Linked-task citation input exceeds the bounded reader')
|
|
15
|
+
ids=list(dict.fromkeys(x.lower() for x in UUID.findall(text) if x.lower()!=task['id']))
|
|
16
|
+
require(len(ids)<=MAX_TASKS,'Too many explicit linked task citations; select at most four')
|
|
17
|
+
return ids
|
|
18
|
+
|
|
19
|
+
def legacy_reference(ref):
|
|
20
|
+
return isinstance(ref,dict) and set(ref)=={'path','sha256'} and isinstance(ref['path'],str) and 1<=len(ref['path'])<=4096 and pathlib.PurePosixPath(ref['path']).is_absolute() and isinstance(ref['sha256'],str) and re.fullmatch('[0-9a-f]{64}',ref['sha256']) is not None
|
|
21
|
+
|
|
22
|
+
def portable(store,linked,target,budget):
|
|
23
|
+
"""Use only immutable references read from the selected canonical task."""
|
|
24
|
+
from .work_context import materialize
|
|
25
|
+
from .work_artifacts import read_checkpoint
|
|
26
|
+
from .topolo import read_artifact
|
|
27
|
+
context=linked.get('contextRef')
|
|
28
|
+
if context:
|
|
29
|
+
require(isinstance(context,dict),'Selected linked context reference must be an object')
|
|
30
|
+
if 'artifact' in context:require(isinstance(context['artifact'],dict) and type(context['artifact'].get('size')) is int and 0<context['artifact']['size']<=200000,'Selected linked context artifact exceeds the bounded reader')
|
|
31
|
+
materialize(store.root,linked,target,lambda:store.client)
|
|
32
|
+
continuity=target/'context/continuity.md';require(continuity.is_file() and continuity.stat().st_size<=128000,'Selected linked task context exceeds the model context bound')
|
|
33
|
+
checkpoints=[];unavailable=[]
|
|
34
|
+
result=linked.get('result') or {};review=linked.get('verification') or {}
|
|
35
|
+
for label,proof,worker in [('execution',result.get('checkpoint'),result.get('workerId')),('review',review.get('nativeCheckpoint'),None)]:
|
|
36
|
+
if not proof:continue
|
|
37
|
+
if legacy_reference(proof):
|
|
38
|
+
unavailable.append({'kind':label+'-checkpoint','originReference':copy.deepcopy(proof),'reason':'Legacy local-only reference; bytes are not portable or read here. Canonical cloud summary remains recorded evidence, not a reverified checkpoint.'});continue
|
|
39
|
+
payload=read_checkpoint(store.root,proof,linked['id'],worker,store.client)
|
|
40
|
+
expected={k:result[k] for k in ['summary','artifacts','references']} if label=='execution' else {k:review[k] for k in ['verdict','summary','evidence']}
|
|
41
|
+
require(payload['phase']==('execute' if label=='execution' else 'review') and payload['outputError'] is None and json.loads(payload['output'])==expected,'Linked task result differs from its immutable checkpoint')
|
|
42
|
+
path=target/(label+'-checkpoint.json');atomic_json(path,payload)
|
|
43
|
+
checkpoints.append({'phase':label,'path':str(path),'proof':proof,'sha256':hashlib.sha256(path.read_bytes()).hexdigest()})
|
|
44
|
+
evidence=[]
|
|
45
|
+
declared=result.get('artifacts',[])+review.get('evidence',[])
|
|
46
|
+
require(len(declared)<=32,'Too many selected linked task evidence references')
|
|
47
|
+
for row in declared:
|
|
48
|
+
if legacy_reference(row):
|
|
49
|
+
unavailable.append({'kind':'declared-evidence','originReference':copy.deepcopy(row),'reason':'Legacy local-only evidence; do not read the origin path or claim its bytes verified.'});continue
|
|
50
|
+
require(isinstance(row,dict) and isinstance(row.get('artifact'),dict),'Linked task evidence must use a portable cloud artifact reference')
|
|
51
|
+
ref=row['artifact'];require(type(ref.get('size')) is int and 0<ref['size']<=MAX_FILE,'Linked task evidence exceeds the bounded file reader')
|
|
52
|
+
require(budget['files']<MAX_EVIDENCE and budget['bytes']+ref['size']<=MAX_BYTES,'Linked task evidence exceeds the selected context budget')
|
|
53
|
+
path=target/('evidence-'+str(len(evidence)))
|
|
54
|
+
read_artifact(store.client,{'input':{'bytesScope':ref['scope'],'bytesWorkspaceId':ref['workspaceId']}},ref['key'],ref,path)
|
|
55
|
+
data=path.read_bytes();require(len(data)==ref['size'] and hashlib.sha256(data).hexdigest()==ref['sha256'],'Linked task evidence digest differs')
|
|
56
|
+
budget['files']+=1;budget['bytes']+=len(data);evidence.append({'title':row.get('title','Selected task evidence'),'path':str(path),'artifact':copy.deepcopy(ref)})
|
|
57
|
+
return {'contextMaterialized':bool(context),'checkpoints':checkpoints,'evidence':evidence,'unavailableEvidence':unavailable,'recordedEvidenceOnly':bool(unavailable)}
|
|
58
|
+
|
|
59
|
+
async def materialize(store,task,cwd,get_task):
|
|
60
|
+
ids=references(task)
|
|
61
|
+
if not ids:return None
|
|
62
|
+
require(getattr(store,'cloud',False) is True,'Linked tasks require the current cloud task authority')
|
|
63
|
+
cwd=pathlib.Path(cwd);require(cwd.resolve()==cwd and cwd.is_dir(),'Owned linked-task workspace required')
|
|
64
|
+
base=cwd/'context';require(not base.is_symlink(),'Linked-task context cannot be a symlink');base.mkdir(exist_ok=True,mode=0o700)
|
|
65
|
+
target=base/'linked-tasks';require(not target.exists() and not target.is_symlink(),'Linked task context already exists; do not overwrite another dispatch')
|
|
66
|
+
target.mkdir(mode=0o700);rows=[];missing=[];budget={'files':0,'bytes':0}
|
|
67
|
+
for tid in ids:
|
|
68
|
+
try:linked=await get_task(tid)
|
|
69
|
+
except PackError as error:
|
|
70
|
+
if str(error)!='Unknown task':raise
|
|
71
|
+
missing.append(tid);continue
|
|
72
|
+
require(linked.get('id')==tid,'Linked cloud task identity differs')
|
|
73
|
+
selected={k:copy.deepcopy(linked[k]) for k in ['id','revision','state','phase','attempt','channel','objective','summary','ownerInstallationId','route','contextRef','result','verification','checkpoints'] if k in linked}
|
|
74
|
+
require(len(canonical(selected))<=128000,'Linked task selected record exceeds the context bound')
|
|
75
|
+
folder=target/tid;folder.mkdir(mode=0o700);atomic_json(folder/'task.json',selected)
|
|
76
|
+
proof=await asyncio.to_thread(portable,store,linked,folder,budget)
|
|
77
|
+
rows.append({'taskId':tid,'revision':linked['revision'],'state':linked['state'],'recordPath':str(folder/'task.json'),**proof})
|
|
78
|
+
index={'format':'topolo-selected-linked-tasks-v1','taskId':task['id'],'authority':{k:store.binding[k] for k in ['organizationId','workspaceId','documentId','datasetId']},'selection':'Explicit UUID citations in the current objective and human responses only; no recursive history','tasks':rows,'unavailableTaskIds':missing,'evidenceBoundary':'Canonical cloud summaries/verification are recorded evidence. Only listed portable checkpoint/artifact files have verified bytes; unavailableEvidence preserves legacy references without reading origin paths or implying byte verification. Compare recorded findings honestly and disclose omitted legacy proof. Preserve owners, holds and UNKNOWN; this grants no adoption or replay authority. Do not substitute an unrelated first retained page for the cited findings.'}
|
|
79
|
+
atomic_json(base/'linked-tasks.json',index)
|
|
80
|
+
return index
|