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.
Files changed (39) hide show
  1. seedcode/__init__.py +1 -1
  2. seedcode/app.py +77 -27
  3. seedcode/branding.py +5 -4
  4. seedcode/codemode_state.py +1 -1
  5. seedcode/commands/agent.py +8 -8
  6. seedcode/commands/assist.py +35 -30
  7. seedcode/commands/chat.py +2 -2
  8. seedcode/commands/codemode.py +3 -3
  9. seedcode/commands/desktop.py +8 -8
  10. seedcode/commands/mode.py +23 -20
  11. seedcode/commands/palette.py +5 -5
  12. seedcode/commands/provider.py +281 -18
  13. seedcode/commands/status.py +8 -7
  14. seedcode/computer/permissions.py +4 -4
  15. seedcode/core/chat.py +88 -11
  16. seedcode/core/models.py +275 -11
  17. seedcode/core/modes.py +126 -0
  18. seedcode/core/providers/__init__.py +96 -12
  19. seedcode/core/providers/custom.py +159 -0
  20. seedcode/core/providers/failover.py +102 -0
  21. seedcode/core/providers/health.py +223 -0
  22. seedcode/core/providers/ollama_start.py +135 -0
  23. seedcode/core/session.py +164 -9
  24. seedcode/core/tasks.py +30 -6
  25. seedcode/tools/desktop.py +2 -2
  26. seedcode/ui/__init__.py +1 -1
  27. seedcode/ui/badges.py +9 -2
  28. seedcode/ui/codemode_header.py +6 -2
  29. seedcode/ui/dashboard.py +107 -215
  30. seedcode/ui/reference.py +1 -1
  31. seedcode/ui/session_view.py +1 -0
  32. seedcode/ui/statusbar.py +4 -2
  33. seedcode/ui/tasks.py +2 -2
  34. {seedcode_cli-7.1.0.dist-info → seedcode_cli-8.1.0.dist-info}/METADATA +90 -58
  35. {seedcode_cli-7.1.0.dist-info → seedcode_cli-8.1.0.dist-info}/RECORD +38 -33
  36. seedcode_cli-8.1.0.dist-info/licenses/LICENSE +90 -0
  37. seedcode_cli-7.1.0.dist-info/licenses/LICENSE +0 -21
  38. {seedcode_cli-7.1.0.dist-info → seedcode_cli-8.1.0.dist-info}/WHEEL +0 -0
  39. {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__ = "7.1.0"
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 PROVIDERS, provider_label, provider_ready
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
- pass
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 six mode/setup actions carry Ctrl+1..Ctrl+6
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("Assist Mode", "assist", shortcut="3"),
173
- MenuItem("Project Memory", "memory", shortcut="4"),
174
- MenuItem("Settings", "settings", shortcut="5"),
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"Assist stopped: {detail}")
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 sharpens Assist 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
- return "Assist Mode"
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
- ui.warning("Session paused — state saved. Send /resume to continue.")
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 Assist/Code turn (tool loop) and render the final answer.
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 Assist engine bound to the CWD and the configured permissions."""
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 ``/assist on`` keeps holding even though this gate is
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, Assist
834
- Mode, Agent Mode or a plain chat turn) returns to the prompt with
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 Assist engine is built lazily on the first assist-mode turn and
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
- v6.2.5 note: the interactive CLI no longer renders any ASCII art. The mark
12
- is a packaging/desktop asset only (:mod:`seedcode.ui.dashboard` renders a
13
- borderless text header with no logo), so this module is not imported by the
14
- terminal UI at all.
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.
@@ -1,6 +1,6 @@
1
1
  """Code Mode state (v6.2.0): workspace-aware coding agent session state.
2
2
 
3
- Code Mode is Assist Mode sharpened for the current project:
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
@@ -1,6 +1,8 @@
1
- """Legacy commands: /agent and /permission.
1
+ """Agent Mode commands: /agent and /permission (v8.1.0).
2
2
 
3
- The old Agent Mode was merged into Assist Mode — /agent now routes there.
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", "Legacy alias for Assist Mode. Usage: /agent [on|off]")
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 Assist permission mode",
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 in Assist Mode")
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="Assist Tools"
98
+ columns_grid(rows, ("seed.primary", "seed.text")), title="Agent Tools"
99
99
  )
100
100
  return CommandResult()
@@ -1,12 +1,14 @@
1
- """Assist Mode: unified AI + computer control.
1
+ """Agent Mode: unified AI + tool/computer execution (v8.1.0).
2
2
 
3
- /assist on — enables the full capability set (AI, filesystem, terminal,
4
- git, browser, keyboard, mouse, windows, vision, OCR, desktop
5
- automation).
6
- /assist off — back to plain chat.
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
- Assist Mode is the ONLY automation mode Seed Code exposes. The old Agent
9
- and Desktop modes were merged into it; their commands now route here.
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 Assist capability set, in display order. Desktop-engine rows are
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", "Enable/disable Assist Mode (unified AI + computer control)")
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 Assist capability list (✓ rows; unavailable rows dim).
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
- """Enable Assist Mode: the full capability set in one switch."""
95
- config.agent_mode = True
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, Assist
98
- # runs at the ``desktop`` level so the AI can drive the computer; otherwise
99
- # it stays at ``workspace`` (AI + filesystem + terminal + git still work).
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("Assist Mode ON")
110
+ ui.success("Agent Mode ON")
108
111
  ui.blank()
109
- ui.panel(capability_table(desktop_ok), title="Assist Mode")
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 Assist doesn't interrupt every step:\n"
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("Assist Mode session permissions", description)
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: Assist will ask before each desktop action.")
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
- """Disable Assist Mode: back to plain chat."""
172
+ """Leave Agent Mode: back to plain Chat."""
170
173
  from ..computer.permissions import session_permissions
171
174
 
172
- config.agent_mode = False
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 Assist back on asks again.
179
+ # Forget the session-wide desktop grant: turning Agent Mode back on asks again.
177
180
  session_permissions().reset()
178
- ui.success("Assist Mode OFF — back to plain chat")
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 Assist Mode status."""
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("Assist Mode", state)
192
- table.add_row("Mode", "Assist" if on else "Chat")
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="Assist Mode")
206
+ ctx.ui.panel(table, title="Agent Mode")
202
207
  ctx.ui.blank()
203
- ctx.ui.dim("Toggle with: /assist on | /assist off")
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 Assist/Code Mode and return to plain Chat: the AI answers
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 · /assist on · /codemode on (or /mode chat|assist|code)")
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":
@@ -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 mode is the engine Code Mode sharpens; turn it on when off.
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
- # Assist prefers the desktop level when the engine exists; Code Mode is a
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
@@ -1,9 +1,9 @@
1
- """Desktop-related commands: /desktop (legacy → Assist), /computer,
1
+ """Desktop-related commands: /desktop (legacy → Agent Mode), /computer,
2
2
  /screenshot, /windows.
3
3
 
4
- The old Desktop Mode was merged into Assist Mode — /desktop now routes
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 Assist turn needed) so the user can sanity-check the
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 Assist Mode. Usage: /desktop [on|off]")
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 Assist
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 Assist Mode — route there transparently.
31
- ctx.ui.dim("(/desktop is now Assist Mode)")
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", "Assist" if ctx.config.agent_mode else "Chat (/assist on)")
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 — one generic switcher for the runtime modes.
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 assist — the unified AI + tools mode (filesystem, terminal, git, ...).
6
- /mode code — Assist sharpened for the current workspace (Code 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
- The individual commands (/chat, /assist, /codemode) remain the direct routes;
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|assist|code|agent]")
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|assist|code|agent")
28
+ ctx.ui.dim("Switch with: /mode chat|code|agent")
26
29
  return CommandResult()
27
30
 
28
- if raw == "chat":
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 raw == "assist":
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
- elif raw == "agent":
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()