seedcode-cli 7.1.0__py3-none-any.whl → 8.1.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.
- seedcode/__init__.py +1 -1
- seedcode/app.py +77 -27
- seedcode/branding.py +5 -4
- seedcode/codemode_state.py +1 -1
- seedcode/commands/agent.py +8 -8
- seedcode/commands/assist.py +35 -30
- seedcode/commands/chat.py +2 -2
- seedcode/commands/codemode.py +3 -3
- seedcode/commands/desktop.py +8 -8
- seedcode/commands/mode.py +23 -20
- seedcode/commands/palette.py +5 -5
- seedcode/commands/provider.py +281 -18
- seedcode/commands/status.py +8 -7
- seedcode/computer/permissions.py +4 -4
- seedcode/core/chat.py +88 -11
- seedcode/core/models.py +275 -11
- seedcode/core/modes.py +126 -0
- seedcode/core/providers/__init__.py +96 -12
- seedcode/core/providers/custom.py +159 -0
- seedcode/core/providers/failover.py +102 -0
- seedcode/core/providers/health.py +223 -0
- seedcode/core/providers/ollama_start.py +135 -0
- seedcode/core/session.py +164 -9
- seedcode/core/tasks.py +30 -6
- seedcode/tools/desktop.py +2 -2
- seedcode/ui/__init__.py +1 -1
- seedcode/ui/badges.py +9 -2
- seedcode/ui/codemode_header.py +6 -2
- seedcode/ui/dashboard.py +107 -215
- seedcode/ui/reference.py +1 -1
- seedcode/ui/session_view.py +1 -0
- seedcode/ui/statusbar.py +4 -2
- seedcode/ui/tasks.py +2 -2
- {seedcode_cli-7.1.0.dist-info → seedcode_cli-8.1.0.dist-info}/METADATA +90 -58
- {seedcode_cli-7.1.0.dist-info → seedcode_cli-8.1.0.dist-info}/RECORD +38 -33
- seedcode_cli-8.1.0.dist-info/licenses/LICENSE +90 -0
- seedcode_cli-7.1.0.dist-info/licenses/LICENSE +0 -21
- {seedcode_cli-7.1.0.dist-info → seedcode_cli-8.1.0.dist-info}/WHEEL +0 -0
- {seedcode_cli-7.1.0.dist-info → seedcode_cli-8.1.0.dist-info}/entry_points.txt +0 -0
seedcode/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ Plant ideas. Grow code.
|
|
|
6
6
|
# Single source of truth for the application version. Every consumer reads it:
|
|
7
7
|
# the PyPI build (via [tool.hatch.version]), the CLI --version flag, the
|
|
8
8
|
# Windows installer metadata, the GitHub release tag, and the WinGet manifest.
|
|
9
|
-
__version__ = "
|
|
9
|
+
__version__ = "8.1.0"
|
|
10
10
|
__author__ = "Al Shahriar Sowan"
|
|
11
11
|
__publisher__ = "Eagox Studio"
|
|
12
12
|
# Current author/company links, surfaced by /about and the README credits.
|
seedcode/app.py
CHANGED
|
@@ -32,7 +32,12 @@ from .core.agent import AgentEngine, strip_tool_blocks
|
|
|
32
32
|
from .core.chat import ChatEngine, ChatError
|
|
33
33
|
from .core.lifecycle import LifecycleError, lifecycle
|
|
34
34
|
from .core.models import AppConfig
|
|
35
|
-
from .core.providers import
|
|
35
|
+
from .core.providers import (
|
|
36
|
+
PROVIDERS,
|
|
37
|
+
provider_label,
|
|
38
|
+
provider_ready,
|
|
39
|
+
sync_custom_providers,
|
|
40
|
+
)
|
|
36
41
|
from .core.providers.freemodel import AUTO_MODEL
|
|
37
42
|
from .memory import HistoryStore
|
|
38
43
|
from .tools import PermissionManager, PermissionMode
|
|
@@ -145,23 +150,28 @@ def _key_status(config: AppConfig) -> str:
|
|
|
145
150
|
|
|
146
151
|
|
|
147
152
|
def _mode_status(config: AppConfig) -> str:
|
|
148
|
-
"""Menu status for the Code Mode item (ON only when really active).
|
|
153
|
+
"""Menu status for the Code Mode item (ON only when really active).
|
|
154
|
+
|
|
155
|
+
Code Mode has its own workspace session, so its status must reflect that
|
|
156
|
+
session alone. Reading ``agent_mode`` here made the item read ON whenever
|
|
157
|
+
Agent Mode was on, even though no Code Mode workspace was active — a live
|
|
158
|
+
status that did not match the live state.
|
|
159
|
+
"""
|
|
149
160
|
try:
|
|
150
161
|
from .codemode_state import codemode_state
|
|
151
162
|
|
|
152
|
-
if codemode_state().enabled
|
|
153
|
-
return "ON"
|
|
163
|
+
return "ON" if codemode_state().enabled else "OFF"
|
|
154
164
|
except Exception:
|
|
155
|
-
|
|
156
|
-
return "ON" if config.agent_mode else "OFF"
|
|
165
|
+
return "OFF"
|
|
157
166
|
|
|
158
167
|
|
|
159
168
|
def _main_menu(config: AppConfig):
|
|
160
169
|
"""The interactive main menu; returns an action id or None (exit).
|
|
161
170
|
|
|
162
|
-
v6.2.5 reference layout: the
|
|
171
|
+
v6.2.5 reference layout: the mode/setup actions carry Ctrl+1..Ctrl+5
|
|
163
172
|
shortcuts that execute the real handlers (no decorative items), followed
|
|
164
|
-
by the chat/setup actions kept from earlier releases.
|
|
173
|
+
by the chat/setup actions kept from earlier releases. There are exactly
|
|
174
|
+
three modes; Assist Mode no longer exists as a menu entry.
|
|
165
175
|
"""
|
|
166
176
|
provider = PROVIDERS.get(config.provider)
|
|
167
177
|
badge = badge_for_status(provider.status if provider is not None else "")
|
|
@@ -169,10 +179,9 @@ def _main_menu(config: AppConfig):
|
|
|
169
179
|
[
|
|
170
180
|
MenuItem("Code Mode", "codemode", status=_mode_status(config), shortcut="1"),
|
|
171
181
|
MenuItem("Agent Mode", "agent", shortcut="2"),
|
|
172
|
-
MenuItem("
|
|
173
|
-
MenuItem("
|
|
174
|
-
MenuItem("
|
|
175
|
-
MenuItem("Exit", "exit", shortcut="6"),
|
|
182
|
+
MenuItem("Project Memory", "memory", shortcut="3"),
|
|
183
|
+
MenuItem("Settings", "settings", shortcut="4"),
|
|
184
|
+
MenuItem("Exit", "exit", shortcut="5"),
|
|
176
185
|
MenuItem("Start Chat", "chat", status=_model_status(config), badge=badge),
|
|
177
186
|
MenuItem("Provider", "provider", status=_provider_status(config)),
|
|
178
187
|
MenuItem("API Key", "apikey", status=_key_status(config)),
|
|
@@ -310,8 +319,16 @@ class _TaskPresenter:
|
|
|
310
319
|
return
|
|
311
320
|
if kind == "error":
|
|
312
321
|
self.ui.dim(f" ✖ {detail.splitlines()[0][:120]}")
|
|
322
|
+
elif kind == "switching_provider":
|
|
323
|
+
# A provider request failed and a healthy fallback took over; the
|
|
324
|
+
# task continues rather than restarting (v7.2.5).
|
|
325
|
+
self.ui.warning(f" Switching provider: {detail}")
|
|
326
|
+
if flow is not None:
|
|
327
|
+
flow.set_activity("Switching provider")
|
|
328
|
+
elif kind == "retry":
|
|
329
|
+
self.ui.dim(f" {detail}")
|
|
313
330
|
elif kind == "limit":
|
|
314
|
-
self.ui.warning(f"
|
|
331
|
+
self.ui.warning(f"Agent Mode stopped: {detail}")
|
|
315
332
|
elif kind == "call" and flow is None:
|
|
316
333
|
# With a task view on screen the step list already shows the tool
|
|
317
334
|
# work; without one, narrate it (the plain/legacy experience).
|
|
@@ -384,10 +401,23 @@ class _TaskPresenter:
|
|
|
384
401
|
self.ui.warning(detail)
|
|
385
402
|
elif kind == "task_failed":
|
|
386
403
|
self.ui.error(detail)
|
|
404
|
+
elif kind == "task_skipped":
|
|
405
|
+
# A skipped task is resolved, never verified work (v7.2.5).
|
|
406
|
+
self.ui.dim(f" – {detail}")
|
|
387
407
|
elif kind == "final_verify":
|
|
388
408
|
self.ui.dim(" final verification…")
|
|
389
409
|
if flow is not None:
|
|
390
410
|
flow.set_activity("Verifying the project (tests / build)")
|
|
411
|
+
elif kind == "reconnect":
|
|
412
|
+
# The connection dropped; the session is reconnecting and the task
|
|
413
|
+
# is preserved rather than failed (v7.2.5).
|
|
414
|
+
self.ui.warning(f" {detail}")
|
|
415
|
+
if flow is not None:
|
|
416
|
+
flow.set_activity("Reconnecting to the provider")
|
|
417
|
+
elif kind == "reconnected":
|
|
418
|
+
self.ui.dim(f" {detail}")
|
|
419
|
+
elif kind == "network_lost":
|
|
420
|
+
self.ui.warning(f"Network: {detail}")
|
|
391
421
|
elif kind in ("retry", "continue"):
|
|
392
422
|
self.ui.dim(f" {detail}")
|
|
393
423
|
if flow is not None and session is not None:
|
|
@@ -471,7 +501,7 @@ def agent_presenter(agent: AgentEngine) -> _TaskPresenter | None:
|
|
|
471
501
|
|
|
472
502
|
|
|
473
503
|
def _task_mode_label(config: AppConfig) -> str:
|
|
474
|
-
"""The mode named in a task header (Code Mode
|
|
504
|
+
"""The mode named in a task header (Chat / Code Mode / Agent Mode)."""
|
|
475
505
|
try:
|
|
476
506
|
from .codemode_state import codemode_state
|
|
477
507
|
|
|
@@ -479,7 +509,9 @@ def _task_mode_label(config: AppConfig) -> str:
|
|
|
479
509
|
return "Code Mode"
|
|
480
510
|
except Exception:
|
|
481
511
|
pass
|
|
482
|
-
|
|
512
|
+
from .core.modes import active_mode, mode_title
|
|
513
|
+
|
|
514
|
+
return mode_title(active_mode(config))
|
|
483
515
|
|
|
484
516
|
|
|
485
517
|
def _save_codemode_summary(agent: AgentEngine, text: str, outcome: str) -> None:
|
|
@@ -586,7 +618,13 @@ def _handle_codemode(
|
|
|
586
618
|
if flow is not None:
|
|
587
619
|
flow.set_state("paused")
|
|
588
620
|
flow.stop()
|
|
589
|
-
|
|
621
|
+
# Surface the real reason (a network pause says so) but keep the
|
|
622
|
+
# actionable instruction identical for every pause.
|
|
623
|
+
reason = (session.reason or "").strip()
|
|
624
|
+
if reason and "connection" in reason.lower():
|
|
625
|
+
ui.warning(f"{reason}")
|
|
626
|
+
else:
|
|
627
|
+
ui.warning("Session paused — state saved. Send /resume to continue.")
|
|
590
628
|
session_mod.set_current_session(session)
|
|
591
629
|
return
|
|
592
630
|
|
|
@@ -620,6 +658,10 @@ def _report_session_summary(ui: UI, session) -> None:
|
|
|
620
658
|
graph = session.graph
|
|
621
659
|
done, total = graph.progress()
|
|
622
660
|
ui.success(f"Project completed — {done}/{total} tasks verified")
|
|
661
|
+
skipped = graph.skipped_count()
|
|
662
|
+
if skipped:
|
|
663
|
+
# A skipped task is resolved, never counted as verified work.
|
|
664
|
+
ui.dim(f" • Skipped: {skipped} task(s) not needed")
|
|
623
665
|
evidence = session.session_evidence
|
|
624
666
|
if evidence.tests:
|
|
625
667
|
# What matters is the final state: the last test run passed.
|
|
@@ -645,7 +687,7 @@ def _handle_agent(
|
|
|
645
687
|
text: str,
|
|
646
688
|
presenter: _TaskPresenter | None = None,
|
|
647
689
|
) -> None:
|
|
648
|
-
"""Run one full
|
|
690
|
+
"""Run one full Agent/Code turn (tool loop) and render the final answer.
|
|
649
691
|
|
|
650
692
|
The turn is shown as a compact live task flow whose step states follow real
|
|
651
693
|
engine activity (see :mod:`seedcode.ui.tasks`) — never a fake progress bar.
|
|
@@ -730,7 +772,7 @@ def _handle_agent(
|
|
|
730
772
|
def _make_agent(
|
|
731
773
|
ui: UI, config: AppConfig, presenter: "_TaskPresenter | None" = None
|
|
732
774
|
) -> AgentEngine:
|
|
733
|
-
"""Build an
|
|
775
|
+
"""Build an Agent Mode engine bound to the CWD and configured permissions."""
|
|
734
776
|
# Lazy import (matching _make_desktop_session): the optional, platform-
|
|
735
777
|
# specific computer package stays out of app.py's top-level import graph.
|
|
736
778
|
from .computer import is_available
|
|
@@ -782,7 +824,7 @@ def _make_desktop_session(ui: UI):
|
|
|
782
824
|
|
|
783
825
|
The session is bound to the process-wide
|
|
784
826
|
:class:`~seedcode.computer.SessionPermissionManager` so a permission the
|
|
785
|
-
user granted once at ``/
|
|
827
|
+
user granted once at ``/agent on`` keeps holding even though this gate is
|
|
786
828
|
rebuilt whenever the permission level changes.
|
|
787
829
|
"""
|
|
788
830
|
from .computer import DesktopGrant, DesktopSession, session_permissions
|
|
@@ -830,14 +872,14 @@ def _chat_loop(
|
|
|
830
872
|
) -> None:
|
|
831
873
|
"""Interactive chat until /exit (returns to the main menu).
|
|
832
874
|
|
|
833
|
-
Only an explicit exit leaves here: a finished task (Code Mode,
|
|
834
|
-
Mode
|
|
875
|
+
Only an explicit exit leaves here: a finished task (Code Mode, Agent
|
|
876
|
+
Mode or a plain chat turn) returns to the prompt with
|
|
835
877
|
``Ready for next task.`` and the session keeps running.
|
|
836
878
|
"""
|
|
837
879
|
ctx = CommandContext(ui=ui, config=config, engine=engine)
|
|
838
880
|
ui.dim(INPUT_HINT)
|
|
839
881
|
|
|
840
|
-
# The
|
|
882
|
+
# The Agent engine is built lazily on the first agent-mode turn and
|
|
841
883
|
# rebuilt when the permission or desktop mode changes (its system
|
|
842
884
|
# prompt and permission gates reflect both). The presenter that owns the
|
|
843
885
|
# live step view lives for the whole chat session, so each turn can bind
|
|
@@ -1007,6 +1049,9 @@ def run(ui: UI) -> None:
|
|
|
1007
1049
|
interactive menu remains available via /exit for provider/model/settings.
|
|
1008
1050
|
"""
|
|
1009
1051
|
config = load_config()
|
|
1052
|
+
# Register the saved custom providers before anything resolves a provider
|
|
1053
|
+
# id (v7.2.5): the registry must reflect config.json from the first turn.
|
|
1054
|
+
sync_custom_providers(config)
|
|
1010
1055
|
set_active_theme(config.theme)
|
|
1011
1056
|
ui.apply_theme(config.theme)
|
|
1012
1057
|
ui.banner(config)
|
|
@@ -1017,8 +1062,15 @@ def run(ui: UI) -> None:
|
|
|
1017
1062
|
config.is_configured(),
|
|
1018
1063
|
)
|
|
1019
1064
|
|
|
1065
|
+
def presenter_on_event(kind: str, detail: str) -> None:
|
|
1066
|
+
"""Surface provider retry/failover on the plain chat prompt (v7.2.5)."""
|
|
1067
|
+
if kind == "switching_provider":
|
|
1068
|
+
ui.warning(f"Switching provider: {detail}")
|
|
1069
|
+
elif kind == "retry":
|
|
1070
|
+
ui.dim(f" {detail}")
|
|
1071
|
+
|
|
1020
1072
|
active_backend = config.provider
|
|
1021
|
-
engine = ChatEngine(config)
|
|
1073
|
+
engine = ChatEngine(config, on_event=presenter_on_event)
|
|
1022
1074
|
history = HistoryStore(provider_id=active_backend)
|
|
1023
1075
|
chat_session = _make_chat_session(ui)
|
|
1024
1076
|
|
|
@@ -1031,7 +1083,7 @@ def run(ui: UI) -> None:
|
|
|
1031
1083
|
if config.provider != active_backend:
|
|
1032
1084
|
# Guided setup switched providers: rebuild the backend state.
|
|
1033
1085
|
active_backend = config.provider
|
|
1034
|
-
engine = ChatEngine(config)
|
|
1086
|
+
engine = ChatEngine(config, on_event=presenter_on_event)
|
|
1035
1087
|
history = HistoryStore(provider_id=active_backend)
|
|
1036
1088
|
_chat_loop(ui, config, engine, history, chat_session)
|
|
1037
1089
|
else:
|
|
@@ -1048,7 +1100,7 @@ def run(ui: UI) -> None:
|
|
|
1048
1100
|
# backend/context and the new provider's own history store.
|
|
1049
1101
|
_log.info("backend switched: %s -> %s", active_backend, config.provider)
|
|
1050
1102
|
active_backend = config.provider
|
|
1051
|
-
engine = ChatEngine(config)
|
|
1103
|
+
engine = ChatEngine(config, on_event=presenter_on_event)
|
|
1052
1104
|
history = HistoryStore(provider_id=active_backend)
|
|
1053
1105
|
|
|
1054
1106
|
try:
|
|
@@ -1074,8 +1126,6 @@ def run(ui: UI) -> None:
|
|
|
1074
1126
|
_dispatch_command(ui, config, engine, "/codemode on")
|
|
1075
1127
|
elif choice == "agent":
|
|
1076
1128
|
_dispatch_command(ui, config, engine, "/agent on")
|
|
1077
|
-
elif choice == "assist":
|
|
1078
|
-
_dispatch_command(ui, config, engine, "/assist on")
|
|
1079
1129
|
elif choice == "memory":
|
|
1080
1130
|
_dispatch_command(ui, config, engine, "/codemode status")
|
|
1081
1131
|
elif choice == "settings":
|
seedcode/branding.py
CHANGED
|
@@ -8,10 +8,11 @@ the exact palette so every surface renders identical art:
|
|
|
8
8
|
bitmaps, the canonical ``assets/logo.png`` and the preview render
|
|
9
9
|
* the installer wizard and the Windows taskbar/Explorer surfaces
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
terminal
|
|
11
|
+
v7.2.5 note: this module stays a packaging/desktop asset. The terminal UI
|
|
12
|
+
renders its own branding from :data:`seedcode.ui.dashboard.LOGO_LINES` (the
|
|
13
|
+
exact ANSI wordmark, with a text fallback on narrow or non-Unicode consoles),
|
|
14
|
+
so no terminal surface imports this module and the pixel-art mark never costs
|
|
15
|
+
anything at startup.
|
|
15
16
|
|
|
16
17
|
Stdlib only (struct/zlib): safe to import from anywhere in the package,
|
|
17
18
|
costs nothing at startup, and keeps the brand byte-reproducible.
|
seedcode/codemode_state.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Code Mode state (v6.2.0): workspace-aware coding agent session state.
|
|
2
2
|
|
|
3
|
-
Code Mode is
|
|
3
|
+
Code Mode is the workspace-aware coding mode for the current project:
|
|
4
4
|
|
|
5
5
|
* the CWD at ``/codemode on`` becomes the *workspace* root;
|
|
6
6
|
* ``.seedcode/`` project memory is ensured and the index refreshed
|
seedcode/commands/agent.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Agent Mode commands: /agent and /permission (v8.1.0).
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/agent is the primary route into Agent Mode — the single general-purpose
|
|
4
|
+
execution mode that the retired Assist Mode folded into. /assist and
|
|
5
|
+
/desktop remain accepted aliases so an existing habit keeps working.
|
|
4
6
|
/permission keeps its dedicated interactive picker; /index and /tools stay
|
|
5
7
|
as inspection commands.
|
|
6
8
|
"""
|
|
@@ -15,7 +17,7 @@ from . import CommandContext, CommandResult, command, show_session_bar
|
|
|
15
17
|
from .assist import disable_assist, enable_assist
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
@command("agent", "
|
|
20
|
+
@command("agent", "Select Agent Mode (general-purpose execution). Usage: /agent [on|off]")
|
|
19
21
|
def _agent(ctx: CommandContext, arg: str) -> CommandResult:
|
|
20
22
|
raw = arg.strip().lower()
|
|
21
23
|
if raw in ("on", "off"):
|
|
@@ -27,8 +29,6 @@ def _agent(ctx: CommandContext, arg: str) -> CommandResult:
|
|
|
27
29
|
ctx.ui.dim("Expected: /agent on|off")
|
|
28
30
|
return CommandResult()
|
|
29
31
|
|
|
30
|
-
# Agent Mode was merged into Assist Mode — route there transparently.
|
|
31
|
-
ctx.ui.dim("(/agent is now Assist Mode)")
|
|
32
32
|
if enable:
|
|
33
33
|
enable_assist(ctx.ui, ctx.config)
|
|
34
34
|
else:
|
|
@@ -39,7 +39,7 @@ def _agent(ctx: CommandContext, arg: str) -> CommandResult:
|
|
|
39
39
|
|
|
40
40
|
@command(
|
|
41
41
|
"permission",
|
|
42
|
-
"Show or set the
|
|
42
|
+
"Show or set the tool permission level",
|
|
43
43
|
aliases=("perm", "permissions"),
|
|
44
44
|
)
|
|
45
45
|
def _permission(ctx: CommandContext, arg: str) -> CommandResult:
|
|
@@ -85,7 +85,7 @@ def _index(ctx: CommandContext, arg: str) -> CommandResult:
|
|
|
85
85
|
return CommandResult()
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
@command("tools", "List the tools available
|
|
88
|
+
@command("tools", "List the tools available to the agent modes")
|
|
89
89
|
def _tools(ctx: CommandContext, arg: str) -> CommandResult:
|
|
90
90
|
from ..ui.layout import columns_grid
|
|
91
91
|
|
|
@@ -95,6 +95,6 @@ def _tools(ctx: CommandContext, arg: str) -> CommandResult:
|
|
|
95
95
|
kind = "changes files/system" if tool.mutates else "read-only"
|
|
96
96
|
rows.append((name, f"{tool.description} ({kind})"))
|
|
97
97
|
ctx.ui.panel(
|
|
98
|
-
columns_grid(rows, ("seed.primary", "seed.text")), title="
|
|
98
|
+
columns_grid(rows, ("seed.primary", "seed.text")), title="Agent Tools"
|
|
99
99
|
)
|
|
100
100
|
return CommandResult()
|
seedcode/commands/assist.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Agent Mode: unified AI + tool/computer execution (v8.1.0).
|
|
2
2
|
|
|
3
|
-
/
|
|
4
|
-
git, browser, keyboard, mouse, windows, vision, OCR,
|
|
5
|
-
automation).
|
|
6
|
-
/
|
|
3
|
+
/agent on — selects Agent Mode: the full capability set (AI, filesystem,
|
|
4
|
+
terminal, git, browser, keyboard, mouse, windows, vision, OCR,
|
|
5
|
+
desktop automation).
|
|
6
|
+
/agent off — back to plain Chat Mode.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Agent Mode is the single general-purpose execution mode. The retired Assist
|
|
9
|
+
Mode is this mode under its old name: ``/assist`` and ``/desktop`` still work
|
|
10
|
+
and route here, but they never create a fourth mode. Code Mode is the
|
|
11
|
+
workspace-aware coding agent (see :mod:`seedcode.commands.codemode`).
|
|
10
12
|
"""
|
|
11
13
|
|
|
12
14
|
from __future__ import annotations
|
|
@@ -18,7 +20,7 @@ from ..config import save_config
|
|
|
18
20
|
from ..tools import TOOL_REGISTRY, PermissionMode
|
|
19
21
|
from . import CommandContext, CommandResult, command, show_session_bar
|
|
20
22
|
|
|
21
|
-
# The
|
|
23
|
+
# The Agent Mode capability set, in display order. Desktop-engine rows are
|
|
22
24
|
# marked so they can be dimmed when the Computer Engine is unavailable.
|
|
23
25
|
_CAPABILITIES: tuple[tuple[str, str, bool], ...] = (
|
|
24
26
|
("AI", "Chat, reasoning, and code generation", False),
|
|
@@ -35,7 +37,7 @@ _CAPABILITIES: tuple[tuple[str, str, bool], ...] = (
|
|
|
35
37
|
)
|
|
36
38
|
|
|
37
39
|
|
|
38
|
-
@command("assist", "
|
|
40
|
+
@command("assist", "Legacy alias for Agent Mode. Usage: /assist [on|off]")
|
|
39
41
|
def _assist(ctx: CommandContext, arg: str) -> CommandResult:
|
|
40
42
|
raw = arg.strip().lower()
|
|
41
43
|
if raw in ("on", "off"):
|
|
@@ -46,7 +48,7 @@ def _assist(ctx: CommandContext, arg: str) -> CommandResult:
|
|
|
46
48
|
return CommandResult()
|
|
47
49
|
else:
|
|
48
50
|
ctx.ui.warning("[Command Error] Invalid syntax.")
|
|
49
|
-
ctx.ui.dim("Expected: /assist on|off")
|
|
51
|
+
ctx.ui.dim("Expected: /agent on|off (or /assist on|off)")
|
|
50
52
|
return CommandResult()
|
|
51
53
|
|
|
52
54
|
if enable:
|
|
@@ -58,7 +60,7 @@ def _assist(ctx: CommandContext, arg: str) -> CommandResult:
|
|
|
58
60
|
|
|
59
61
|
|
|
60
62
|
def capability_table(desktop_ok: bool) -> Table:
|
|
61
|
-
"""The
|
|
63
|
+
"""The Agent Mode capability list (✓ rows; unavailable rows dim).
|
|
62
64
|
|
|
63
65
|
OCR is probed independently of the rest of the desktop stack: it needs a
|
|
64
66
|
native engine the Python packages do not supply, so marking it available
|
|
@@ -91,12 +93,13 @@ def _ocr_available() -> bool:
|
|
|
91
93
|
|
|
92
94
|
|
|
93
95
|
def enable_assist(ui, config) -> None:
|
|
94
|
-
"""
|
|
95
|
-
config.
|
|
96
|
+
"""Select Agent Mode: the full capability set in one switch."""
|
|
97
|
+
config.mode = "agent"
|
|
96
98
|
|
|
97
|
-
# Desktop capabilities require the Computer Engine. When available,
|
|
98
|
-
# runs at the ``desktop`` level so the AI can drive the computer;
|
|
99
|
-
# it stays at ``workspace`` (AI + filesystem + terminal + git
|
|
99
|
+
# Desktop capabilities require the Computer Engine. When available, Agent
|
|
100
|
+
# Mode runs at the ``desktop`` level so the AI can drive the computer;
|
|
101
|
+
# otherwise it stays at ``workspace`` (AI + filesystem + terminal + git
|
|
102
|
+
# still work).
|
|
100
103
|
desktop_ok, desktop_reason = is_available()
|
|
101
104
|
config.permission_mode = (
|
|
102
105
|
PermissionMode.DESKTOP.value_str if desktop_ok
|
|
@@ -104,9 +107,9 @@ def enable_assist(ui, config) -> None:
|
|
|
104
107
|
)
|
|
105
108
|
save_config(config)
|
|
106
109
|
|
|
107
|
-
ui.success("
|
|
110
|
+
ui.success("Agent Mode ON")
|
|
108
111
|
ui.blank()
|
|
109
|
-
ui.panel(capability_table(desktop_ok), title="
|
|
112
|
+
ui.panel(capability_table(desktop_ok), title="Agent Mode")
|
|
110
113
|
if not desktop_ok:
|
|
111
114
|
ui.dim(f"Desktop capabilities unavailable: {desktop_reason}")
|
|
112
115
|
ui.blank()
|
|
@@ -143,14 +146,14 @@ def request_session_permissions(ui) -> bool:
|
|
|
143
146
|
f" • {CATEGORY_LABELS.get(c, c)}" for c in SESSION_GRANT_CATEGORIES
|
|
144
147
|
)
|
|
145
148
|
description = (
|
|
146
|
-
"Grant these for this session so
|
|
149
|
+
"Grant these for this session so Agent Mode doesn't interrupt every step:\n"
|
|
147
150
|
f"{wanted}\n"
|
|
148
151
|
"Sensitive actions (registry writes, passwords, system power, deletions, "
|
|
149
152
|
"purchases) will still ask each time."
|
|
150
153
|
)
|
|
151
154
|
|
|
152
155
|
try:
|
|
153
|
-
answer = ui.confirm_desktop("
|
|
156
|
+
answer = ui.confirm_desktop("Agent Mode session permissions", description)
|
|
154
157
|
except Exception:
|
|
155
158
|
# No interactive prompt available (headless/non-TTY): leave the
|
|
156
159
|
# per-action flow in place rather than silently granting anything.
|
|
@@ -161,25 +164,27 @@ def request_session_permissions(ui) -> bool:
|
|
|
161
164
|
session.request(SESSION_GRANT_CATEGORIES, allow=True)
|
|
162
165
|
ui.dim("Desktop control granted for this session — you won't be asked again.")
|
|
163
166
|
else:
|
|
164
|
-
ui.dim("Declined:
|
|
167
|
+
ui.dim("Declined: Agent Mode will ask before each desktop action.")
|
|
165
168
|
return allowed
|
|
166
169
|
|
|
167
170
|
|
|
168
171
|
def disable_assist(ui, config) -> None:
|
|
169
|
-
"""
|
|
172
|
+
"""Leave Agent Mode: back to plain Chat."""
|
|
170
173
|
from ..computer.permissions import session_permissions
|
|
171
174
|
|
|
172
|
-
config.
|
|
175
|
+
config.mode = "chat"
|
|
173
176
|
# Drop back to the safe editing level (removes desktop capability).
|
|
174
177
|
config.permission_mode = PermissionMode.WORKSPACE.value_str
|
|
175
178
|
save_config(config)
|
|
176
|
-
# Forget the session-wide desktop grant: turning
|
|
179
|
+
# Forget the session-wide desktop grant: turning Agent Mode back on asks again.
|
|
177
180
|
session_permissions().reset()
|
|
178
|
-
ui.success("
|
|
181
|
+
ui.success("Agent Mode OFF — back to plain chat")
|
|
179
182
|
|
|
180
183
|
|
|
181
184
|
def _show_status(ctx: CommandContext) -> None:
|
|
182
|
-
"""Show current
|
|
185
|
+
"""Show current Agent Mode status."""
|
|
186
|
+
from ..core.modes import active_mode, mode_title
|
|
187
|
+
|
|
183
188
|
on = ctx.config.agent_mode
|
|
184
189
|
desktop_ok, desktop_reason = is_available()
|
|
185
190
|
|
|
@@ -188,8 +193,8 @@ def _show_status(ctx: CommandContext) -> None:
|
|
|
188
193
|
table.add_column()
|
|
189
194
|
|
|
190
195
|
state = "[seed.accent]ON[/seed.accent]" if on else "[seed.dim]OFF[/seed.dim]"
|
|
191
|
-
table.add_row("
|
|
192
|
-
table.add_row("Mode",
|
|
196
|
+
table.add_row("Agent Mode", state)
|
|
197
|
+
table.add_row("Mode", mode_title(active_mode(ctx.config)))
|
|
193
198
|
if not desktop_ok:
|
|
194
199
|
table.add_row("Desktop", f"Unavailable: {desktop_reason}")
|
|
195
200
|
table.add_row("Permission", ctx.config.permission_mode.replace("_", " ").title())
|
|
@@ -198,6 +203,6 @@ def _show_status(ctx: CommandContext) -> None:
|
|
|
198
203
|
desktop_tools = [n for n, t in TOOL_REGISTRY.items() if t.group == "desktop"]
|
|
199
204
|
table.add_row("Available tools", f"{len(core_tools)} core, {len(desktop_tools)} desktop")
|
|
200
205
|
|
|
201
|
-
ctx.ui.panel(table, title="
|
|
206
|
+
ctx.ui.panel(table, title="Agent Mode")
|
|
202
207
|
ctx.ui.blank()
|
|
203
|
-
ctx.ui.dim("Toggle with: /
|
|
208
|
+
ctx.ui.dim("Toggle with: /agent on | /agent off (alias: /assist)")
|
seedcode/commands/chat.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""/chat — explicit Chat Mode (plain conversation, no tools).
|
|
2
2
|
|
|
3
3
|
/chat — show the current mode.
|
|
4
|
-
/chat on — leave
|
|
4
|
+
/chat on — leave Code/Agent Mode and return to plain Chat: the AI answers
|
|
5
5
|
with text only and never touches the filesystem or terminal.
|
|
6
6
|
|
|
7
7
|
Chat Mode is the default runtime mode; this command exists so a user can
|
|
@@ -19,7 +19,7 @@ def _chat(ctx: CommandContext, arg: str) -> CommandResult:
|
|
|
19
19
|
raw = arg.strip().lower()
|
|
20
20
|
if raw in ("", "status"):
|
|
21
21
|
ctx.ui.info(f"Mode: {mode_label(ctx.config)}")
|
|
22
|
-
ctx.ui.dim("Switch with: /chat on · /
|
|
22
|
+
ctx.ui.dim("Switch with: /chat on · /agent on · /codemode on (or /mode chat|code|agent)")
|
|
23
23
|
return CommandResult()
|
|
24
24
|
|
|
25
25
|
if raw != "on":
|
seedcode/commands/codemode.py
CHANGED
|
@@ -57,13 +57,13 @@ def _enable_codemode(ctx: CommandContext) -> None:
|
|
|
57
57
|
state = codemode_state()
|
|
58
58
|
workspace = state.workspace # already set? keep it (re-enable same project)
|
|
59
59
|
|
|
60
|
-
# Agent
|
|
60
|
+
# Agent Mode is the engine Code Mode sharpens; turn it on when off.
|
|
61
61
|
if not config.agent_mode:
|
|
62
62
|
from .assist import enable_assist
|
|
63
63
|
|
|
64
64
|
enable_assist(ctx.ui, config)
|
|
65
|
-
#
|
|
66
|
-
# *coding* workflow, so keep the editing level (workspace) unless the user
|
|
65
|
+
# Agent Mode prefers the desktop level when the engine exists; Code Mode is
|
|
66
|
+
# a *coding* workflow, so keep the editing level (workspace) unless the user
|
|
67
67
|
# had already chosen something stronger.
|
|
68
68
|
if PermissionMode.parse(config.permission_mode) == PermissionMode.DESKTOP:
|
|
69
69
|
config.permission_mode = PermissionMode.WORKSPACE.value_str
|
seedcode/commands/desktop.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"""Desktop-related commands: /desktop (legacy →
|
|
1
|
+
"""Desktop-related commands: /desktop (legacy → Agent Mode), /computer,
|
|
2
2
|
/screenshot, /windows.
|
|
3
3
|
|
|
4
|
-
The old Desktop Mode was merged into
|
|
4
|
+
The old Desktop Mode was merged into Agent Mode — /desktop now routes
|
|
5
5
|
there. /computer shows engine status; /screenshot and /windows work
|
|
6
|
-
immediately (no
|
|
6
|
+
immediately (no Agent Mode turn needed) so the user can sanity-check the
|
|
7
7
|
engine by hand.
|
|
8
8
|
"""
|
|
9
9
|
|
|
@@ -16,19 +16,19 @@ from . import CommandContext, CommandResult, command, show_session_bar
|
|
|
16
16
|
from .assist import disable_assist, enable_assist
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
@command("desktop", "Legacy alias for
|
|
19
|
+
@command("desktop", "Legacy alias for Agent Mode. Usage: /desktop [on|off]")
|
|
20
20
|
def _desktop(ctx: CommandContext, arg: str) -> CommandResult:
|
|
21
21
|
raw = arg.strip().lower()
|
|
22
22
|
if raw in ("on", "off"):
|
|
23
23
|
enable = raw == "on"
|
|
24
24
|
elif not raw:
|
|
25
|
-
enable = not ctx.config.agent_mode # bare /desktop toggles
|
|
25
|
+
enable = not ctx.config.agent_mode # bare /desktop toggles Agent Mode
|
|
26
26
|
else:
|
|
27
27
|
ctx.ui.warning("Usage: /desktop [on|off]")
|
|
28
28
|
return CommandResult()
|
|
29
29
|
|
|
30
|
-
# Desktop Mode was merged into
|
|
31
|
-
ctx.ui.dim("(/desktop is now
|
|
30
|
+
# Desktop Mode was merged into Agent Mode — route there transparently.
|
|
31
|
+
ctx.ui.dim("(/desktop is now Agent Mode)")
|
|
32
32
|
if enable:
|
|
33
33
|
enable_assist(ctx.ui, ctx.config)
|
|
34
34
|
else:
|
|
@@ -44,7 +44,7 @@ def _computer(ctx: CommandContext, arg: str) -> CommandResult:
|
|
|
44
44
|
table = Table.grid(padding=(0, 3))
|
|
45
45
|
table.add_column(style="seed.dim", justify="right", no_wrap=True)
|
|
46
46
|
table.add_column(style="seed.text")
|
|
47
|
-
table.add_row("Mode", "
|
|
47
|
+
table.add_row("Mode", "Agent" if ctx.config.agent_mode else "Chat (/agent on)")
|
|
48
48
|
table.add_row("Engine", reason if not ok else "Available")
|
|
49
49
|
missing = missing_packages()
|
|
50
50
|
if missing:
|
seedcode/commands/mode.py
CHANGED
|
@@ -1,52 +1,55 @@
|
|
|
1
|
-
"""/mode —
|
|
1
|
+
"""/mode — the generic switcher for the three canonical modes (v8.1.0).
|
|
2
2
|
|
|
3
3
|
/mode — show the current mode.
|
|
4
4
|
/mode chat — plain conversation (no tools).
|
|
5
|
-
/mode
|
|
6
|
-
/mode
|
|
7
|
-
/mode agent — legacy alias of assist.
|
|
5
|
+
/mode code — the workspace coding agent (Code Mode).
|
|
6
|
+
/mode agent — general-purpose execution (Agent Mode).
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
``assist`` is accepted as an input alias and resolves to Agent Mode — the
|
|
9
|
+
retired Assist Mode is Agent Mode now, so no fourth mode can be selected.
|
|
10
|
+
|
|
11
|
+
The individual commands (/chat, /codemode, /agent) remain the direct routes;
|
|
10
12
|
this command is the single entry point that understands all of them.
|
|
11
13
|
"""
|
|
12
14
|
|
|
13
15
|
from __future__ import annotations
|
|
14
16
|
|
|
17
|
+
from ..core.modes import MODE_DESCRIPTIONS, Mode, active_mode, mode_title, parse_mode
|
|
15
18
|
from . import CommandContext, CommandResult, command, show_session_bar
|
|
16
19
|
from .status import mode_label
|
|
17
20
|
|
|
18
21
|
|
|
19
|
-
@command("mode", "Show or switch the runtime mode. Usage: /mode [chat|
|
|
22
|
+
@command("mode", "Show or switch the runtime mode. Usage: /mode [chat|code|agent]")
|
|
20
23
|
def _mode(ctx: CommandContext, arg: str) -> CommandResult:
|
|
21
24
|
raw = arg.strip().lower()
|
|
22
25
|
|
|
23
26
|
if not raw:
|
|
24
27
|
ctx.ui.info(f"Mode: {mode_label(ctx.config)}")
|
|
25
|
-
ctx.ui.dim("Switch with: /mode chat|
|
|
28
|
+
ctx.ui.dim("Switch with: /mode chat|code|agent")
|
|
26
29
|
return CommandResult()
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
mode = parse_mode(raw, default=Mode.CHAT)
|
|
32
|
+
if raw not in ("chat", "code", "agent", "assist", "codemode"):
|
|
33
|
+
ctx.ui.warning("[Command Error] Invalid syntax.")
|
|
34
|
+
ctx.ui.dim("Expected: /mode chat|code|agent")
|
|
35
|
+
return CommandResult()
|
|
36
|
+
|
|
37
|
+
if raw == "assist":
|
|
38
|
+
ctx.ui.dim("(Assist Mode is Agent Mode now — selecting Agent Mode)")
|
|
39
|
+
|
|
40
|
+
if mode is Mode.CHAT:
|
|
29
41
|
from .chat import _enter_chat
|
|
30
42
|
|
|
31
43
|
_enter_chat(ctx)
|
|
32
|
-
elif
|
|
33
|
-
from .assist import enable_assist
|
|
34
|
-
|
|
35
|
-
enable_assist(ctx.ui, ctx.config)
|
|
36
|
-
elif raw == "code":
|
|
44
|
+
elif mode is Mode.CODE:
|
|
37
45
|
from .codemode import _enable_codemode
|
|
38
46
|
|
|
39
47
|
_enable_codemode(ctx)
|
|
40
|
-
|
|
41
|
-
# Agent Mode merged into Assist Mode; keep the name working.
|
|
48
|
+
else: # Mode.AGENT
|
|
42
49
|
from .assist import enable_assist
|
|
43
50
|
|
|
44
51
|
enable_assist(ctx.ui, ctx.config)
|
|
45
|
-
ctx.ui.dim("(Agent Mode is Assist Mode now — same engine, same tools)")
|
|
46
|
-
else:
|
|
47
|
-
ctx.ui.warning("[Command Error] Invalid syntax.")
|
|
48
|
-
ctx.ui.dim("Expected: /mode chat|assist|code|agent")
|
|
49
|
-
return CommandResult()
|
|
50
52
|
|
|
53
|
+
ctx.ui.dim(f"Mode: {mode_title(active_mode(ctx.config))} — {MODE_DESCRIPTIONS[mode]}")
|
|
51
54
|
show_session_bar(ctx.ui, ctx.config)
|
|
52
55
|
return CommandResult()
|