cct-cli 0.7.9.0__py3-none-any.whl
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.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT — pipeline.py: the AI Execution Pipeline (v0.7.7 spec section 6/7/8).
|
|
3
|
+
|
|
4
|
+
Large tasks automatically follow the spec's workflow, and the workflow
|
|
5
|
+
is VISIBLE to the user the whole way:
|
|
6
|
+
|
|
7
|
+
Step 1 Planner Mode -> analyze request, break into milestones,
|
|
8
|
+
generate roadmap + task graph + todos,
|
|
9
|
+
estimate complexity
|
|
10
|
+
(auto switch)
|
|
11
|
+
Step 2 Research Mode -> search documentation, read APIs, compare
|
|
12
|
+
technologies, deep reasoning
|
|
13
|
+
(auto switch)
|
|
14
|
+
Step 3 Build Mode -> create files, install packages, generate
|
|
15
|
+
code, run tests, fix issues, verify output
|
|
16
|
+
(auto switch)
|
|
17
|
+
Step 4 Notebook Mode -> report, flowchart, architecture diagram,
|
|
18
|
+
performance summary, final explanation
|
|
19
|
+
|
|
20
|
+
Implementation notes (honest scope):
|
|
21
|
+
* Stages are real, sequential passes over the configured AI provider
|
|
22
|
+
(the same single-provider architecture agent.py's multi-agent team
|
|
23
|
+
uses — see its "Multi-agent collaboration" section). Planner and
|
|
24
|
+
Tester are dedicated prompt passes; Implementation reuses
|
|
25
|
+
agent.run_agent()'s real tool-executing loop so files/plots/
|
|
26
|
+
installs genuinely happen; the Debugger pass reuses agent._debug_pass
|
|
27
|
+
(real ast scan + sandbox run).
|
|
28
|
+
* The live todo list (spec section 7) drives the stage UI: the plan
|
|
29
|
+
becomes todos with statuses Pending / Researching / Coding /
|
|
30
|
+
Testing / Completed / Skipped / Failed / Currently Running. Status
|
|
31
|
+
transitions are published through the on_todo callback and the
|
|
32
|
+
eventbus so the Todo panel and the task graph can both react.
|
|
33
|
+
* "Running" is the current stage's highlight (spec section 8) — the
|
|
34
|
+
graph text below is rendered by callers; this module just tracks
|
|
35
|
+
which stage is current and what happened at each.
|
|
36
|
+
* Complexity estimation is a heuristic (word count + plan length),
|
|
37
|
+
labeled as such in the report — not a model call.
|
|
38
|
+
|
|
39
|
+
Checkpoints (v0.7.8.1): after every completed stage the run state
|
|
40
|
+
(plan, todos, steps, research brief, stage) is persisted to
|
|
41
|
+
~/.cct_pipeline_checkpoint.json. When a stage fails — say the provider
|
|
42
|
+
chain is exhausted mid-run — the caller can run again with
|
|
43
|
+
resume=True: matching runs skip the completed stages and continue
|
|
44
|
+
from the failed one instead of restarting from the planner. The
|
|
45
|
+
checkpoint is cleared once a run reaches 'completed'. This is the
|
|
46
|
+
long-task "continue from checkpoint" + provider-failover-resume
|
|
47
|
+
behavior, done with real persisted state, not a fiction.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
print("This is a library file and is not meant to be run directly.")
|
|
52
|
+
import sys
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
|
|
55
|
+
import json
|
|
56
|
+
import os
|
|
57
|
+
import re
|
|
58
|
+
import time
|
|
59
|
+
|
|
60
|
+
from . import aicore
|
|
61
|
+
from . import agent
|
|
62
|
+
from . import eventbus
|
|
63
|
+
from . import sound
|
|
64
|
+
|
|
65
|
+
# ------------------------------------------------------------------ stages --
|
|
66
|
+
PIPELINE_STAGES = [
|
|
67
|
+
"planner", # Step 1 — Plan
|
|
68
|
+
"research", # Step 2 — Research
|
|
69
|
+
"implementation", # Step 3 — Build
|
|
70
|
+
"testing", # Step 3b — Test & fix
|
|
71
|
+
"verification", # Step 3c — Verify
|
|
72
|
+
"documentation", # Step 4 — Notebook report
|
|
73
|
+
"completed",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
_STAGE_ORDER = [
|
|
77
|
+
"planner",
|
|
78
|
+
"research",
|
|
79
|
+
"implementation",
|
|
80
|
+
"testing",
|
|
81
|
+
"verification",
|
|
82
|
+
"documentation",
|
|
83
|
+
"completed",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
_CHECKPOINT_FILE = os.path.join(os.path.expanduser("~"), ".cct_pipeline_checkpoint.json")
|
|
87
|
+
|
|
88
|
+
STAGE_LABELS = {
|
|
89
|
+
"planner": "\U0001f9ed Planner",
|
|
90
|
+
"research": "\U0001f50d Research",
|
|
91
|
+
"implementation": "\U0001f528 Implementation",
|
|
92
|
+
"testing": "\u2699 Testing",
|
|
93
|
+
"verification": "\u2705 Verification",
|
|
94
|
+
"documentation": "\U0001f4d8 Documentation",
|
|
95
|
+
"completed": "\U0001f389 Completed",
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# Live-todo status vocabulary (spec section 7) — mirrors the statuses
|
|
99
|
+
# the Todo panel renders.
|
|
100
|
+
TODO_PENDING = "pending"
|
|
101
|
+
TODO_RUNNING = "running" # current stage in flight
|
|
102
|
+
TODO_RESEARCHING = "researching"
|
|
103
|
+
TODO_CODING = "coding"
|
|
104
|
+
TODO_TESTING = "testing"
|
|
105
|
+
TODO_COMPLETED = "completed"
|
|
106
|
+
TODO_SKIPPED = "skipped"
|
|
107
|
+
TODO_FAILED = "failed"
|
|
108
|
+
|
|
109
|
+
_ANY_STAGE = re.compile(r"\b(planner|planning|research|implementation|build|testing|verify|verification|documentation|docs)\b", re.I)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _classify_stage(chunk_text):
|
|
113
|
+
"""Very cheap keyword stage classifier for the tool-step -> stage
|
|
114
|
+
mapping during Implementation (see _PipelineRun._advance_todos)."""
|
|
115
|
+
t = (chunk_text or "").lower()
|
|
116
|
+
if re.search(r"\b(test|pytest|assert|sandbox)\b", t):
|
|
117
|
+
return "testing"
|
|
118
|
+
if re.search(r"\b(search|research|docs|documentation)\b", t):
|
|
119
|
+
return "research"
|
|
120
|
+
if re.search(r"\b(solve|plot|simulate|calculate|write_file|create_folder)\b", t):
|
|
121
|
+
return "coding"
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _classify_intent(text):
|
|
126
|
+
"""Best-effort complexity + stage-intent estimate for the report
|
|
127
|
+
(spec section 6's "estimate complexity"). Heuristic, labeled as
|
|
128
|
+
such in the report text."""
|
|
129
|
+
words = len(re.findall(r"\w+", text or ""))
|
|
130
|
+
if words < 20:
|
|
131
|
+
return "simple"
|
|
132
|
+
if words < 60:
|
|
133
|
+
return "moderate"
|
|
134
|
+
return "complex"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ---------------------------------------------------------------- graph --
|
|
138
|
+
def task_graph(current_stage=None):
|
|
139
|
+
"""The execution graph (spec section 8): planner -> research ->
|
|
140
|
+
implementation -> testing -> verification -> documentation ->
|
|
141
|
+
completed, with the current stage highlighted. Returns a list of
|
|
142
|
+
(stage_key, label, is_current) tuples so callers can render it
|
|
143
|
+
with their own theming."""
|
|
144
|
+
out = []
|
|
145
|
+
for stage in PIPELINE_STAGES:
|
|
146
|
+
out.append((stage, STAGE_LABELS[stage], stage == current_stage))
|
|
147
|
+
return out
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def render_task_graph(current_stage, term_width=78):
|
|
151
|
+
"""Classic-terminal render of the task graph, current stage
|
|
152
|
+
highlighted with a marker."""
|
|
153
|
+
from . import theme
|
|
154
|
+
lines = []
|
|
155
|
+
for i, (stage, label, current) in enumerate(task_graph(current_stage)):
|
|
156
|
+
arrow = "\u2193"
|
|
157
|
+
if stage == "planner":
|
|
158
|
+
prefix = ""
|
|
159
|
+
else:
|
|
160
|
+
prefix = " "
|
|
161
|
+
indent = " " * 2
|
|
162
|
+
if current:
|
|
163
|
+
lines.append(f"{indent}{theme.cyan(arrow if stage != 'planner' else ' ')} "
|
|
164
|
+
f"{theme.cyan('>', bold=True)} {theme.cyan(label, bold=True)} {theme.orange('\u25cf CURRENT', bold=True)}")
|
|
165
|
+
else:
|
|
166
|
+
lines.append(f"{indent}{theme.faint(arrow if stage != 'planner' else ' ')} {theme.faint(label)}")
|
|
167
|
+
return lines
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# ---------------------------------------------------------- checkpoints --
|
|
171
|
+
def save_checkpoint(run) -> bool:
|
|
172
|
+
"""Persist a pipeline run's state at its current stage so a later
|
|
173
|
+
run with the same user_text can resume from where it stopped."""
|
|
174
|
+
try:
|
|
175
|
+
payload = {
|
|
176
|
+
"user_text": run.user_text,
|
|
177
|
+
"mode": run.mode,
|
|
178
|
+
"last_completed": run._last_completed,
|
|
179
|
+
"plan": run.plan,
|
|
180
|
+
"todos": run.todos,
|
|
181
|
+
"steps": run.steps,
|
|
182
|
+
"final_text": run.final_text,
|
|
183
|
+
"research_brief": getattr(run, "research_brief", ""),
|
|
184
|
+
"started": run.started,
|
|
185
|
+
}
|
|
186
|
+
with open(_CHECKPOINT_FILE, "w", encoding="utf-8") as f:
|
|
187
|
+
json.dump(payload, f, indent=2)
|
|
188
|
+
return True
|
|
189
|
+
except Exception:
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def load_checkpoint() -> dict | None:
|
|
194
|
+
try:
|
|
195
|
+
if os.path.exists(_CHECKPOINT_FILE):
|
|
196
|
+
with open(_CHECKPOINT_FILE, "r", encoding="utf-8") as f:
|
|
197
|
+
data = json.load(f)
|
|
198
|
+
if isinstance(data, dict) and "last_completed" in data:
|
|
199
|
+
return data
|
|
200
|
+
except Exception:
|
|
201
|
+
pass
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def clear_checkpoint():
|
|
206
|
+
try:
|
|
207
|
+
if os.path.exists(_CHECKPOINT_FILE):
|
|
208
|
+
os.remove(_CHECKPOINT_FILE)
|
|
209
|
+
except Exception:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def has_checkpoint(user_text) -> bool:
|
|
214
|
+
cp = load_checkpoint()
|
|
215
|
+
return bool(cp and cp.get("user_text") == user_text)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# ------------------------------------------------------------------ run --
|
|
219
|
+
class PipelineRun:
|
|
220
|
+
"""One full pipeline execution. Callbacks (all optional, all plain
|
|
221
|
+
Python — safe from any thread):
|
|
222
|
+
on_note(text) — a one-line status note (stage entered)
|
|
223
|
+
on_stage(stage_key) — stage transition happened
|
|
224
|
+
on_todo(todo_dict) — a todo's status changed
|
|
225
|
+
on_message(text) — free-form text to surface (report lines)
|
|
226
|
+
on_event(ev_dict) — raw pipeline event (for logs/dashboards)
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
def __init__(self, user_text, mode="agent", on_note=None, on_stage=None,
|
|
230
|
+
on_todo=None, on_message=None, on_event=None, permission_callback=None,
|
|
231
|
+
should_cancel=None):
|
|
232
|
+
self.user_text = user_text
|
|
233
|
+
self.mode = mode
|
|
234
|
+
self.on_note = on_note or (lambda t: None)
|
|
235
|
+
self.on_stage = on_stage or (lambda s: None)
|
|
236
|
+
self.on_todo = on_todo or (lambda t: None)
|
|
237
|
+
self.on_message = on_message or (lambda t: None)
|
|
238
|
+
self.on_event = on_event or (lambda e: None)
|
|
239
|
+
self.permission_callback = permission_callback
|
|
240
|
+
# v0.7.8.1 interrupt: zero-arg callable checked before each stage
|
|
241
|
+
# and threaded into the implementation stage's agent loop.
|
|
242
|
+
self.should_cancel = should_cancel
|
|
243
|
+
self.stage = None
|
|
244
|
+
self.todos = [] # [{id, text, status}]
|
|
245
|
+
self.steps = [] # agent tool steps
|
|
246
|
+
self.plan = [] # planner step list
|
|
247
|
+
self.report_lines = [] # documentation stage output
|
|
248
|
+
self.final_text = ""
|
|
249
|
+
self.started = time.time()
|
|
250
|
+
self.duration = 0.0
|
|
251
|
+
self.failed_stage = None
|
|
252
|
+
self._todo_seq = 0
|
|
253
|
+
self._last_completed = None # last stage that finished cleanly
|
|
254
|
+
|
|
255
|
+
# ------------------------------------------------------------ helpers --
|
|
256
|
+
def _cancelled(self):
|
|
257
|
+
"""v0.7.8.1 interrupt: True once the caller's cancel callback
|
|
258
|
+
fires (the UI worker marks its worker cancelled on Stop/Esc)."""
|
|
259
|
+
try:
|
|
260
|
+
return bool(self.should_cancel and self.should_cancel())
|
|
261
|
+
except Exception:
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
def _restore_checkpoint(self):
|
|
265
|
+
"""Load the saved checkpoint for THIS user_text and fast-forward
|
|
266
|
+
the run state to just after the last completed stage. Returns
|
|
267
|
+
True when a usable checkpoint was found."""
|
|
268
|
+
cp = load_checkpoint()
|
|
269
|
+
if not cp or cp.get("user_text") != self.user_text:
|
|
270
|
+
return False
|
|
271
|
+
self.plan = cp.get("plan") or []
|
|
272
|
+
self.todos = cp.get("todos") or []
|
|
273
|
+
self.steps = cp.get("steps") or []
|
|
274
|
+
self.final_text = cp.get("final_text") or ""
|
|
275
|
+
self.research_brief = cp.get("research_brief", "")
|
|
276
|
+
self.started = cp.get("started") or time.time()
|
|
277
|
+
self._last_completed = cp.get("last_completed") or None
|
|
278
|
+
self._todo_seq = max((t.get("id", 0) for t in self.todos), default=0)
|
|
279
|
+
for t in self.todos:
|
|
280
|
+
self.on_todo(t) # re-sync live todo UIs to restored statuses
|
|
281
|
+
return True
|
|
282
|
+
def _emit(self, kind, message, **extra):
|
|
283
|
+
ev = {"kind": kind, "stage": self.stage, "message": message}
|
|
284
|
+
ev.update(extra)
|
|
285
|
+
self.on_event(ev)
|
|
286
|
+
if kind in ("note", "stage", "error", "done"):
|
|
287
|
+
self.on_note(message)
|
|
288
|
+
elif kind in ("report", "summary", "graph"):
|
|
289
|
+
self.on_message(message)
|
|
290
|
+
|
|
291
|
+
def _enter(self, stage):
|
|
292
|
+
self.stage = stage
|
|
293
|
+
self.on_stage(stage)
|
|
294
|
+
eventbus.bus.publish(eventbus.PIPELINE_STAGE, stage=stage,
|
|
295
|
+
detail=STAGE_LABELS.get(stage, stage))
|
|
296
|
+
if stage == "testing":
|
|
297
|
+
for t in self.todos:
|
|
298
|
+
if t["status"] not in (TODO_COMPLETED, TODO_SKIPPED, TODO_FAILED):
|
|
299
|
+
t["status"] = TODO_TESTING
|
|
300
|
+
self.on_todo(t)
|
|
301
|
+
elif stage == "research":
|
|
302
|
+
for t in self.todos:
|
|
303
|
+
if t["status"] not in (TODO_COMPLETED, TODO_SKIPPED, TODO_FAILED):
|
|
304
|
+
t["status"] = TODO_RESEARCHING
|
|
305
|
+
self.on_todo(t)
|
|
306
|
+
elif stage == "implementation":
|
|
307
|
+
for t in self.todos:
|
|
308
|
+
if t["status"] not in (TODO_COMPLETED, TODO_SKIPPED, TODO_FAILED):
|
|
309
|
+
t["status"] = TODO_RUNNING
|
|
310
|
+
self.on_todo(t)
|
|
311
|
+
|
|
312
|
+
def _add_todo(self, text):
|
|
313
|
+
self._todo_seq += 1
|
|
314
|
+
todo = {"id": self._todo_seq, "text": text, "status": TODO_PENDING}
|
|
315
|
+
self.todos.append(todo)
|
|
316
|
+
self.on_todo(todo)
|
|
317
|
+
return todo
|
|
318
|
+
|
|
319
|
+
def _advance_todos(self, step_text):
|
|
320
|
+
"""Best-effort mapping of executed tool steps onto the plan's
|
|
321
|
+
todos during Implementation: each tool step completes the first
|
|
322
|
+
not-yet-done todo. Documented as an approximation, not a
|
|
323
|
+
semantic match."""
|
|
324
|
+
if not self.todos:
|
|
325
|
+
return
|
|
326
|
+
for t in self.todos:
|
|
327
|
+
if t["status"] not in (TODO_COMPLETED, TODO_SKIPPED, TODO_FAILED):
|
|
328
|
+
t["status"] = TODO_COMPLETED
|
|
329
|
+
self.on_todo(t)
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
# ------------------------------------------------------------ stages --
|
|
333
|
+
def _stage_planner(self):
|
|
334
|
+
"""Step 1 — Planner Mode: analyze, break into milestones,
|
|
335
|
+
roadmap, todos, complexity estimate."""
|
|
336
|
+
self._emit("note", "Step 1/6 \u2014 Planner: analyzing the request and breaking it into milestones...")
|
|
337
|
+
system_prompt = agent.AGENT_SYSTEM_PROMPT if self.mode == "agent" else agent.AI_SYSTEM_PROMPT
|
|
338
|
+
plan = agent._plan_steps(self.user_text, system_prompt)
|
|
339
|
+
self.plan = plan
|
|
340
|
+
for step in plan:
|
|
341
|
+
self._add_todo(step)
|
|
342
|
+
if not self.plan:
|
|
343
|
+
self._add_todo("Work out and answer the request directly.")
|
|
344
|
+
complexity = _classify_intent(self.user_text)
|
|
345
|
+
self._emit("note",
|
|
346
|
+
f"Planner produced {len(self.plan)} milestone(s) \u00b7 "
|
|
347
|
+
f"complexity estimate: {complexity} (heuristic)")
|
|
348
|
+
|
|
349
|
+
def _stage_research(self):
|
|
350
|
+
"""Step 2 — Research Mode: web brief when the request has a
|
|
351
|
+
recency/lookup flavor, folded into the Implementation context."""
|
|
352
|
+
self._emit("note", "Step 2/6 \u2014 Research: searching documentation and comparing approaches...")
|
|
353
|
+
brief = agent._research_pass(self.user_text, agent.AGENT_SYSTEM_PROMPT)
|
|
354
|
+
self.research_brief = brief
|
|
355
|
+
if brief:
|
|
356
|
+
self._emit("note", "Research brief gathered (live web search) \u2014 folding into the build.")
|
|
357
|
+
else:
|
|
358
|
+
self._emit("note", "No live research needed for this request (or search unreachable).")
|
|
359
|
+
|
|
360
|
+
def _stage_implementation(self):
|
|
361
|
+
"""Step 3 — Build Mode: real tool execution (files, installs,
|
|
362
|
+
plots) via agent.run_agent."""
|
|
363
|
+
self._emit("note", "Step 3/6 \u2014 Implementation: creating files, installing packages, generating code...")
|
|
364
|
+
plan_note = ("\n\nPIPELINE PLAN (follow it step by step, completing each "
|
|
365
|
+
"milestone in order):\n" +
|
|
366
|
+
"\n".join(f"{i+1}. {p}" for i, p in enumerate(self.plan)))
|
|
367
|
+
if getattr(self, "research_brief", ""):
|
|
368
|
+
plan_note += "\n\n" + self.research_brief
|
|
369
|
+
|
|
370
|
+
def _on_step(name, args):
|
|
371
|
+
args = args or {}
|
|
372
|
+
target = (args.get("path") or args.get("command") or "").strip()
|
|
373
|
+
label = f"{name}" + (f" {target}" if target else "")
|
|
374
|
+
self._emit("note", f"\u2699 executing: {label}")
|
|
375
|
+
self._advance_todos(label)
|
|
376
|
+
|
|
377
|
+
final_text, steps, _meta = agent.run_agent(
|
|
378
|
+
self.user_text + plan_note, max_steps=agent.MAX_STEPS,
|
|
379
|
+
verbose=False, mode=self.mode,
|
|
380
|
+
permission_callback=self.permission_callback,
|
|
381
|
+
on_step=_on_step, should_cancel=self._cancelled)
|
|
382
|
+
self.steps = steps
|
|
383
|
+
self.final_text = final_text
|
|
384
|
+
changes = agent.summarize_file_changes(steps)
|
|
385
|
+
if changes:
|
|
386
|
+
self._emit("report", changes)
|
|
387
|
+
self._emit("note", f"Implementation finished \u2014 {len(steps)} tool step(s) run.")
|
|
388
|
+
|
|
389
|
+
def _stage_testing(self):
|
|
390
|
+
"""Step 3b — Debugger/Tester pass: static scan + sandbox run of
|
|
391
|
+
any code blocks the implementation produced."""
|
|
392
|
+
self._emit("note", "Step 4/6 \u2014 Testing: statically scanning and sandbox-testing generated code...")
|
|
393
|
+
final_text, note = agent._debug_pass(self.final_text)
|
|
394
|
+
if note:
|
|
395
|
+
risk = note.get("risk", "none")
|
|
396
|
+
self._emit("note",
|
|
397
|
+
f"Debugger scan: {risk} risk "
|
|
398
|
+
+ (f"\u2014 {note.get('sandbox_test', 'not sandboxed')}" if risk != "high" else
|
|
399
|
+
"\u2014 high-risk code not auto-executed"))
|
|
400
|
+
if risk == "high":
|
|
401
|
+
for t in self.todos:
|
|
402
|
+
if t["status"] == TODO_TESTING:
|
|
403
|
+
pass
|
|
404
|
+
if note.get("findings"):
|
|
405
|
+
details = "\n".join(f" {f}" for f in note["findings"][:5])
|
|
406
|
+
self._emit("report", "Debugger findings:\n" + details)
|
|
407
|
+
self.final_text = final_text
|
|
408
|
+
|
|
409
|
+
def _stage_verification(self):
|
|
410
|
+
"""Step 3c — Tester pass: spot-check the final answer."""
|
|
411
|
+
self._emit("note", "Step 5/6 \u2014 Verification: checking the result against the request...")
|
|
412
|
+
ok, note = agent._verify_answer(self.user_text, self.plan, self.final_text,
|
|
413
|
+
agent.AGENT_SYSTEM_PROMPT)
|
|
414
|
+
if not ok and note:
|
|
415
|
+
self._emit("note", f"Tester flagged this for review: {note}")
|
|
416
|
+
self.final_text = self.final_text + f"\n\n\u26a0 Tester flagged this for review: {note}"
|
|
417
|
+
else:
|
|
418
|
+
self._emit("note", "Verification passed \u2014 the result is internally consistent.")
|
|
419
|
+
|
|
420
|
+
def _stage_documentation(self):
|
|
421
|
+
"""Step 4 — Notebook Mode: the final report card (stages,
|
|
422
|
+
graph, todos, summary)."""
|
|
423
|
+
self._emit("note", "Step 6/6 \u2014 Documentation: composing the final report...")
|
|
424
|
+
self._emit("graph", "Pipeline task graph (current stage highlighted):")
|
|
425
|
+
graph_lines = render_task_graph("completed")
|
|
426
|
+
self._emit("report", "\n".join(graph_lines))
|
|
427
|
+
done = sum(1 for t in self.todos if t["status"] == TODO_COMPLETED)
|
|
428
|
+
skipped = sum(1 for t in self.todos if t["status"] == TODO_SKIPPED)
|
|
429
|
+
failed = sum(1 for t in self.todos if t["status"] == TODO_FAILED)
|
|
430
|
+
self.duration = round(time.time() - self.started, 1)
|
|
431
|
+
card = [
|
|
432
|
+
"\u2705 Pipeline complete",
|
|
433
|
+
"",
|
|
434
|
+
f"Milestones: {len(self.plan)} \u00b7 completed {done}"
|
|
435
|
+
+ (f" \u00b7 skipped {skipped}" if skipped else "")
|
|
436
|
+
+ (f" \u00b7 failed {failed}" if failed else ""),
|
|
437
|
+
f"Tool steps executed: {len(self.steps)}",
|
|
438
|
+
f"Duration: {self.duration}s",
|
|
439
|
+
f"Complexity estimate: {_classify_intent(self.user_text)} (heuristic)",
|
|
440
|
+
]
|
|
441
|
+
self._emit("summary", "\n".join(card))
|
|
442
|
+
eventbus.bus.publish(eventbus.PIPELINE_COMPLETED, steps=len(self.steps),
|
|
443
|
+
milestones=len(self.plan))
|
|
444
|
+
|
|
445
|
+
# --------------------------------------------------------------- run --
|
|
446
|
+
def run(self, resume=False):
|
|
447
|
+
"""Execute the pipeline. Stages run in order; after each one
|
|
448
|
+
completes the state is checkpointed (so a failed run can be
|
|
449
|
+
resumed). With `resume=True`, a matching checkpoint is loaded
|
|
450
|
+
and the completed stages are skipped — execution continues
|
|
451
|
+
from the last-completed stage. The checkpoint is cleared when
|
|
452
|
+
the run reaches 'completed'."""
|
|
453
|
+
sound.play("notify")
|
|
454
|
+
try:
|
|
455
|
+
resumed = False
|
|
456
|
+
if resume:
|
|
457
|
+
resumed = self._restore_checkpoint()
|
|
458
|
+
if resumed:
|
|
459
|
+
at = STAGE_LABELS.get(self._last_completed, self._last_completed or "start")
|
|
460
|
+
self._emit("note",
|
|
461
|
+
f"\u267b Resuming from checkpoint \u2014 "
|
|
462
|
+
f"skipping completed stages (up to '{at}').")
|
|
463
|
+
else:
|
|
464
|
+
self._emit("note", "No matching checkpoint found \u2014 starting fresh.")
|
|
465
|
+
else:
|
|
466
|
+
clear_checkpoint() # stale checkpoint from a previous crash
|
|
467
|
+
skip_until = self._last_completed if resumed else None
|
|
468
|
+
stages = [
|
|
469
|
+
("planner", self._stage_planner),
|
|
470
|
+
("research", self._stage_research),
|
|
471
|
+
("implementation", self._stage_implementation),
|
|
472
|
+
("testing", self._stage_testing),
|
|
473
|
+
("verification", self._stage_verification),
|
|
474
|
+
("documentation", self._stage_documentation),
|
|
475
|
+
]
|
|
476
|
+
skipping = False
|
|
477
|
+
for key, fn in stages:
|
|
478
|
+
if skip_until and not skipping:
|
|
479
|
+
skipping = (key == skip_until)
|
|
480
|
+
continue # completed in the previous run — skip it too
|
|
481
|
+
if self._cancelled():
|
|
482
|
+
self._emit("note",
|
|
483
|
+
"\u23f9 Interrupted \u2014 completed work is preserved.")
|
|
484
|
+
return self.final_text
|
|
485
|
+
self._enter(key)
|
|
486
|
+
fn()
|
|
487
|
+
self._last_completed = key
|
|
488
|
+
save_checkpoint(self)
|
|
489
|
+
self._enter("completed")
|
|
490
|
+
sound.play("success")
|
|
491
|
+
clear_checkpoint()
|
|
492
|
+
except Exception as e:
|
|
493
|
+
self.failed_stage = self.stage
|
|
494
|
+
self._emit("error", f"Pipeline failed at {STAGE_LABELS.get(self.stage, self.stage)}: {e}")
|
|
495
|
+
save_checkpoint(self)
|
|
496
|
+
sound.play("error")
|
|
497
|
+
return self.final_text
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def run_pipeline(user_text, **callbacks):
|
|
501
|
+
"""Module-level convenience: builds a PipelineRun, runs it, returns
|
|
502
|
+
it (callers read .final_text / .todos / .plan / .duration)."""
|
|
503
|
+
run = PipelineRun(user_text, **callbacks)
|
|
504
|
+
run.run()
|
|
505
|
+
return run
|