yycode 0.3.2__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 (131) hide show
  1. agent/__init__.py +33 -0
  2. agent/acp/__init__.py +2 -0
  3. agent/acp/approval_adapter.py +134 -0
  4. agent/acp/content_adapter.py +45 -0
  5. agent/acp/jsonrpc.py +92 -0
  6. agent/acp/server.py +197 -0
  7. agent/acp/session_manager.py +193 -0
  8. agent/acp/update_adapter.py +192 -0
  9. agent/app_paths.py +25 -0
  10. agent/approval.py +169 -0
  11. agent/cancellation.py +52 -0
  12. agent/change_snapshot.py +186 -0
  13. agent/context_compressor.py +116 -0
  14. agent/graph.py +137 -0
  15. agent/llm_retry.py +434 -0
  16. agent/logger.py +97 -0
  17. agent/lsp/__init__.py +13 -0
  18. agent/lsp/client.py +151 -0
  19. agent/lsp/manager.py +234 -0
  20. agent/lsp/types.py +119 -0
  21. agent/message_context_manager.py +322 -0
  22. agent/message_format.py +105 -0
  23. agent/nodes/llm_node.py +58 -0
  24. agent/nodes/state.py +12 -0
  25. agent/nodes/task_guard_node.py +50 -0
  26. agent/nodes/tools_node.py +70 -0
  27. agent/plan_snapshot.py +70 -0
  28. agent/providers/__init__.py +13 -0
  29. agent/providers/anthropic_provider.py +268 -0
  30. agent/providers/base.py +52 -0
  31. agent/providers/openai_provider.py +279 -0
  32. agent/providers/text_tool_calls.py +118 -0
  33. agent/runtime/approval_service.py +184 -0
  34. agent/runtime/context.py +43 -0
  35. agent/runtime/tool_events.py +368 -0
  36. agent/runtime/tool_executor.py +208 -0
  37. agent/runtime/tool_output.py +261 -0
  38. agent/runtime/tool_registry.py +91 -0
  39. agent/runtime/tool_scheduler.py +35 -0
  40. agent/runtime/workflow_guard.py +217 -0
  41. agent/runtime/workspace.py +5 -0
  42. agent/runtime/workspace_tools.py +22 -0
  43. agent/session.py +787 -0
  44. agent/session_replay.py +95 -0
  45. agent/session_store.py +186 -0
  46. agent/skills.py +254 -0
  47. agent/streaming.py +248 -0
  48. agent/subagent.py +634 -0
  49. agent/task_memory.py +340 -0
  50. agent/todo_manager.py +304 -0
  51. agent/tool_retry.py +106 -0
  52. agent/tui/__init__.py +14 -0
  53. agent/tui/app.py +1325 -0
  54. agent/tui/approval.py +53 -0
  55. agent/tui/commands/__init__.py +6 -0
  56. agent/tui/commands/base.py +48 -0
  57. agent/tui/commands/clear.py +37 -0
  58. agent/tui/commands/help.py +27 -0
  59. agent/tui/commands/registry.py +94 -0
  60. agent/tui/help_content.py +108 -0
  61. agent/tui/renderers.py +1961 -0
  62. agent/tui/runner.py +439 -0
  63. agent/tui/state.py +653 -0
  64. main.py +465 -0
  65. tools/__init__.py +50 -0
  66. tools/apply_patch.py +305 -0
  67. tools/bash.py +76 -0
  68. tools/diff_utils.py +139 -0
  69. tools/edit_file.py +40 -0
  70. tools/git_diff.py +72 -0
  71. tools/git_show.py +65 -0
  72. tools/grep.py +149 -0
  73. tools/list_files.py +90 -0
  74. tools/list_skills.py +24 -0
  75. tools/load_skill.py +30 -0
  76. tools/lsp_definition.py +27 -0
  77. tools/lsp_diagnostics.py +32 -0
  78. tools/lsp_document_symbols.py +23 -0
  79. tools/lsp_hover.py +29 -0
  80. tools/lsp_references.py +37 -0
  81. tools/lsp_utils.py +38 -0
  82. tools/lsp_workspace_symbols.py +23 -0
  83. tools/read_file.py +61 -0
  84. tools/read_many_files.py +50 -0
  85. tools/safety.py +50 -0
  86. tools/subagent.py +57 -0
  87. tools/todo.py +89 -0
  88. tools/verify.py +107 -0
  89. tools/web_search.py +250 -0
  90. tools/workspace.py +36 -0
  91. tools/workspace_state.py +60 -0
  92. tools/write_file.py +88 -0
  93. utils/__init__.py +5 -0
  94. utils/retry.py +13 -0
  95. yycode-0.3.2.data/data/skills/code_review.md +61 -0
  96. yycode-0.3.2.data/data/skills/code_workflow.md +404 -0
  97. yycode-0.3.2.data/data/skills/drawio/SKILL.md +636 -0
  98. yycode-0.3.2.data/data/skills/drawio/agents/openai.yaml +19 -0
  99. yycode-0.3.2.data/data/skills/drawio/assets/demo-erd.drawio +84 -0
  100. yycode-0.3.2.data/data/skills/drawio/assets/demo-layered-cn.drawio +91 -0
  101. yycode-0.3.2.data/data/skills/drawio/assets/demo-layered-cn.png +0 -0
  102. yycode-0.3.2.data/data/skills/drawio/assets/demo-layered.drawio +112 -0
  103. yycode-0.3.2.data/data/skills/drawio/assets/demo-layered.png +0 -0
  104. yycode-0.3.2.data/data/skills/drawio/assets/demo-ml.drawio +90 -0
  105. yycode-0.3.2.data/data/skills/drawio/assets/demo-ring-cn.drawio +68 -0
  106. yycode-0.3.2.data/data/skills/drawio/assets/demo-ring-cn.png +0 -0
  107. yycode-0.3.2.data/data/skills/drawio/assets/demo-ring.drawio +86 -0
  108. yycode-0.3.2.data/data/skills/drawio/assets/demo-ring.png +0 -0
  109. yycode-0.3.2.data/data/skills/drawio/assets/demo-sequence.drawio +116 -0
  110. yycode-0.3.2.data/data/skills/drawio/assets/demo-star-cn.drawio +66 -0
  111. yycode-0.3.2.data/data/skills/drawio/assets/demo-star-cn.png +0 -0
  112. yycode-0.3.2.data/data/skills/drawio/assets/demo-star.drawio +79 -0
  113. yycode-0.3.2.data/data/skills/drawio/assets/demo-star.png +0 -0
  114. yycode-0.3.2.data/data/skills/drawio/assets/demo-uml-class.drawio +64 -0
  115. yycode-0.3.2.data/data/skills/drawio/assets/microservices-example.drawio +173 -0
  116. yycode-0.3.2.data/data/skills/drawio/assets/microservices-example.png +0 -0
  117. yycode-0.3.2.data/data/skills/drawio/assets/workflow-cn.drawio +120 -0
  118. yycode-0.3.2.data/data/skills/drawio/assets/workflow-cn.png +0 -0
  119. yycode-0.3.2.data/data/skills/drawio/assets/workflow.drawio +120 -0
  120. yycode-0.3.2.data/data/skills/drawio/assets/workflow.png +0 -0
  121. yycode-0.3.2.data/data/skills/drawio/docs/index.html +469 -0
  122. yycode-0.3.2.data/data/skills/drawio/docs/zh.html +456 -0
  123. yycode-0.3.2.data/data/skills/drawio/references/style-extraction.md +254 -0
  124. yycode-0.3.2.data/data/skills/drawio/styles/schema.json +112 -0
  125. yycode-0.3.2.data/data/skills/plan.md +115 -0
  126. yycode-0.3.2.data/data/skills/ppt/SKILL.md +254 -0
  127. yycode-0.3.2.dist-info/METADATA +12 -0
  128. yycode-0.3.2.dist-info/RECORD +131 -0
  129. yycode-0.3.2.dist-info/WHEEL +5 -0
  130. yycode-0.3.2.dist-info/entry_points.txt +2 -0
  131. yycode-0.3.2.dist-info/top_level.txt +4 -0
agent/tui/approval.py ADDED
@@ -0,0 +1,53 @@
1
+ """Approval adapter for the terminal UI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ from typing import Optional
7
+
8
+ from agent.approval import ApprovalDecision, ApprovalRequest
9
+
10
+
11
+ def approval_id_for_request(request: ApprovalRequest) -> str:
12
+ """Build the approval identifier used by stream events and the TUI."""
13
+ target = request.path or request.command
14
+ return "|".join([request.action, request.tool_name, target])
15
+
16
+
17
+ class TuiApprovalAdapter:
18
+ """Bridge runtime approval callbacks to TUI-driven user decisions."""
19
+
20
+ def __init__(self) -> None:
21
+ self._pending: dict[str, asyncio.Future[ApprovalDecision]] = {}
22
+
23
+ async def callback(self, request: ApprovalRequest) -> bool:
24
+ """Wait for the TUI to approve or deny the request."""
25
+ return (await self.decide(request)).approved
26
+
27
+ async def decide(self, request: ApprovalRequest) -> ApprovalDecision:
28
+ """Wait for the TUI to return an approval decision."""
29
+ approval_id = approval_id_for_request(request)
30
+ future: asyncio.Future[ApprovalDecision] = asyncio.get_running_loop().create_future()
31
+ self._pending[approval_id] = future
32
+ try:
33
+ return await future
34
+ finally:
35
+ self._pending.pop(approval_id, None)
36
+
37
+ def resolve(self, approval_id: str, approved: bool) -> bool:
38
+ """Resolve one pending approval request."""
39
+ future: Optional[asyncio.Future[ApprovalDecision]] = self._pending.get(approval_id)
40
+ if future is None or future.done():
41
+ return False
42
+ future.set_result(ApprovalDecision("approved" if approved else "denied"))
43
+ return True
44
+
45
+ def cancel_pending(self) -> int:
46
+ """Cancel all pending approvals and return how many were resolved."""
47
+ count = 0
48
+ for future in list(self._pending.values()):
49
+ if future.done():
50
+ continue
51
+ future.set_result(ApprovalDecision("cancelled"))
52
+ count += 1
53
+ return count
@@ -0,0 +1,6 @@
1
+ """Built-in TUI commands."""
2
+
3
+ from .base import CommandResult, TuiCommand
4
+ from .registry import CommandRegistry, ParsedCommand, discover_commands
5
+
6
+ __all__ = ["CommandRegistry", "CommandResult", "ParsedCommand", "TuiCommand", "discover_commands"]
@@ -0,0 +1,48 @@
1
+ """Base types for TUI-only commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING, Literal, Protocol
7
+
8
+ if TYPE_CHECKING: # pragma: no cover
9
+ from agent.tui.runner import AgentTuiRunner
10
+
11
+ from .registry import CommandRegistry
12
+
13
+
14
+ Severity = Literal["information", "warning", "error"]
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class CommandContext:
19
+ """Execution dependencies passed to TUI commands."""
20
+
21
+ runner: "AgentTuiRunner"
22
+ registry: "CommandRegistry"
23
+ confirmed: bool = False
24
+ raw_text: str = ""
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class CommandResult:
29
+ """Result displayed in the TUI after a command executes."""
30
+
31
+ title: str
32
+ content: str = ""
33
+ severity: Severity = "information"
34
+ status: str = "completed"
35
+ clear_input: bool = True
36
+
37
+
38
+ class TuiCommand(Protocol):
39
+ """Protocol implemented by TUI-only commands."""
40
+
41
+ name: str
42
+ description: str
43
+ usage: str
44
+ aliases: tuple[str, ...]
45
+ destructive: bool
46
+
47
+ async def execute(self, ctx: CommandContext, args: str) -> CommandResult:
48
+ """Execute the command without sending it to the LLM."""
@@ -0,0 +1,37 @@
1
+ """Clear session history command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from .base import CommandContext, CommandResult
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class ClearCommand:
12
+ name: str = "clear"
13
+ description: str = "Clear current session message history and TUI timeline"
14
+ usage: str = ":clear"
15
+ aliases: tuple[str, ...] = ()
16
+ destructive: bool = True
17
+
18
+ async def execute(self, ctx: CommandContext, args: str) -> CommandResult:
19
+ if args.strip():
20
+ return CommandResult(
21
+ title="Usage: :clear",
22
+ content="The :clear command does not accept arguments.",
23
+ severity="warning",
24
+ status="warning",
25
+ )
26
+ if not ctx.confirmed:
27
+ return CommandResult(
28
+ title="Confirm clear",
29
+ content="Clear session history? Type :clear! to confirm.",
30
+ severity="warning",
31
+ status="waiting_for_confirmation",
32
+ )
33
+ await ctx.runner.clear_session_history()
34
+ return CommandResult(title="Session cleared", content="Session history cleared.")
35
+
36
+
37
+ COMMAND = ClearCommand()
@@ -0,0 +1,27 @@
1
+ """Help command for TUI commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from agent.tui.help_content import render_help_page
8
+
9
+ from .base import CommandContext, CommandResult
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class HelpCommand:
14
+ name: str = "help"
15
+ description: str = "List available TUI commands"
16
+ usage: str = ":help [command]"
17
+ aliases: tuple[str, ...] = ("?",)
18
+ destructive: bool = False
19
+
20
+ async def execute(self, ctx: CommandContext, args: str) -> CommandResult:
21
+ return CommandResult(
22
+ title="yycode Help",
23
+ content=render_help_page(ctx.registry.list_commands()),
24
+ )
25
+
26
+
27
+ COMMAND = HelpCommand()
@@ -0,0 +1,94 @@
1
+ """Registry and auto-discovery for TUI-only commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import pkgutil
7
+ from collections.abc import Iterable
8
+ from dataclasses import dataclass
9
+
10
+ from .base import TuiCommand
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class ParsedCommand:
15
+ """Parsed command line."""
16
+
17
+ command: TuiCommand
18
+ args: str = ""
19
+ confirmed: bool = False
20
+ raw_text: str = ""
21
+
22
+
23
+ class CommandRegistry:
24
+ """Lookup table for TUI commands."""
25
+
26
+ def __init__(self, commands: Iterable[TuiCommand] = ()) -> None:
27
+ self._commands: dict[str, TuiCommand] = {}
28
+ self._aliases: dict[str, TuiCommand] = {}
29
+ for command in commands:
30
+ self.register(command)
31
+
32
+ def register(self, command: TuiCommand) -> None:
33
+ """Register one command by its lowercase name and aliases."""
34
+ name = command.name.strip().lower()
35
+ if not name:
36
+ raise ValueError("Command name cannot be empty")
37
+ if name in self._commands or name in self._aliases:
38
+ raise ValueError(f"Duplicate TUI command name: {name}")
39
+ self._commands[name] = command
40
+ for alias in getattr(command, "aliases", ()) or ():
41
+ normalized = alias.strip().lower()
42
+ if not normalized:
43
+ continue
44
+ if normalized in self._commands or normalized in self._aliases:
45
+ raise ValueError(f"Duplicate TUI command alias: {normalized}")
46
+ self._aliases[normalized] = command
47
+
48
+ def get(self, name: str) -> TuiCommand | None:
49
+ """Return a command by name or alias, if present."""
50
+ normalized = name.strip().lower().lstrip(":").rstrip("!")
51
+ return self._commands.get(normalized) or self._aliases.get(normalized)
52
+
53
+ def list_commands(self) -> list[TuiCommand]:
54
+ """Return commands sorted by name."""
55
+ return [self._commands[name] for name in sorted(self._commands)]
56
+
57
+ def matching(self, token: str, limit: int = 8) -> list[TuiCommand]:
58
+ """Return commands matching a completion token."""
59
+ normalized = token.strip().lower().lstrip(":")
60
+ commands = self.list_commands()
61
+ rows = [command for command in commands if command.name.startswith(normalized)]
62
+ if normalized and not rows:
63
+ rows = [command for command in commands if normalized in command.name]
64
+ return rows[:limit]
65
+
66
+ def parse(self, text: str) -> ParsedCommand | None:
67
+ """Parse a command line such as ':clear!' or ':help clear'."""
68
+ stripped = text.strip()
69
+ if not stripped.startswith(":"):
70
+ return None
71
+ command_text = stripped[1:].strip()
72
+ if not command_text:
73
+ return None
74
+ name, _, args = command_text.partition(" ")
75
+ confirmed = name.endswith("!")
76
+ name = name.rstrip("!")
77
+ command = self.get(name)
78
+ if command is None:
79
+ return None
80
+ return ParsedCommand(command=command, args=args.strip(), confirmed=confirmed, raw_text=stripped)
81
+
82
+
83
+ def discover_commands(package_name: str = "agent.tui.commands") -> CommandRegistry:
84
+ """Discover COMMAND instances from command modules in this package."""
85
+ package = importlib.import_module(package_name)
86
+ registry = CommandRegistry()
87
+ for module_info in pkgutil.iter_modules(package.__path__):
88
+ if module_info.name.startswith("_") or module_info.name in {"base", "registry"}:
89
+ continue
90
+ module = importlib.import_module(f"{package_name}.{module_info.name}")
91
+ command = getattr(module, "COMMAND", None)
92
+ if command is not None:
93
+ registry.register(command)
94
+ return registry
@@ -0,0 +1,108 @@
1
+ """Single-page help content for the TUI :help command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+
7
+ from agent.tui.commands.base import TuiCommand
8
+
9
+
10
+ TUI_SHORTCUTS = [
11
+ ("Ctrl+Enter / Ctrl+J", "Submit current input"),
12
+ ("Ctrl+C", "Cancel current task / interrupt"),
13
+ ("Ctrl+Shift+C", "Copy timeline"),
14
+ ("Ctrl+L", "Open plain-text timeline"),
15
+ ("Ctrl+Q", "Quit"),
16
+ ("Ctrl+T", "Open task plan panel"),
17
+ ("Ctrl+D", "Open changed files panel"),
18
+ ("Ctrl+M", "Open message token manager"),
19
+ ]
20
+
21
+ COMPLETION_SHORTCUTS = [
22
+ ("/", "Open skill completion"),
23
+ ("@", "Open subagent role completion"),
24
+ (":", "Open TUI command completion"),
25
+ ("Up/Down or Ctrl+P/Ctrl+N", "Select completion item"),
26
+ ("Tab/Enter", "Accept selected completion"),
27
+ ("Esc", "Close completion or focus input"),
28
+ ]
29
+
30
+ TIMELINE_SHORTCUTS = [
31
+ ("Up/Down", "Scroll timeline by line"),
32
+ ("PageUp/PageDown", "Scroll timeline by page"),
33
+ ("Home/End", "Jump to timeline top/bottom"),
34
+ ]
35
+
36
+ APPROVAL_SHORTCUTS = [
37
+ ("Y / Enter", "Approve current request"),
38
+ ("N / Esc", "Deny current request"),
39
+ ]
40
+
41
+ STARTUP_ARGUMENTS = [
42
+ ("WORKDIR", "Workspace directory. Defaults to the current directory."),
43
+ ("-d, --debug", "Enable debug logging to console."),
44
+ ("--log-file", "Write logs to agent_debug.log."),
45
+ ("-a, --auto", "Auto-approve risky actions where supported."),
46
+ ("--silent", "Compatibility alias for --auto."),
47
+ ("-r, --resume ID", "Resume messages from a persisted session id in the same workspace."),
48
+ ("-s, --sessions", "List persisted sessions for WORKDIR and exit."),
49
+ ("--list-sessions", "Compatibility alias for --sessions."),
50
+ ("-t, --temp", "Temporary session; do not save messages."),
51
+ ("--no-persist", "Compatibility alias for --temp."),
52
+ ("-x, --delete ID", "Delete a persisted session id for WORKDIR and exit."),
53
+ ]
54
+
55
+
56
+ def render_help_page(commands: Iterable[TuiCommand]) -> str:
57
+ """Render the complete single-page TUI help text."""
58
+ sections = [
59
+ "yycode Help",
60
+ "",
61
+ "TUI Commands",
62
+ *_render_commands(commands),
63
+ "",
64
+ "Keyboard Shortcuts",
65
+ *_render_rows(TUI_SHORTCUTS),
66
+ "",
67
+ "Input Completion",
68
+ *_render_rows(COMPLETION_SHORTCUTS),
69
+ "",
70
+ "Timeline Navigation",
71
+ *_render_rows(TIMELINE_SHORTCUTS),
72
+ "",
73
+ "Approval",
74
+ *_render_rows(APPROVAL_SHORTCUTS),
75
+ "",
76
+ "Subagents",
77
+ " Use @role followed by a focused task. Type @ to complete a role.",
78
+ " Example: @tester verify the command system behavior",
79
+ " Example: @architect /plan design a safer cleanup flow",
80
+ "",
81
+ "Skills",
82
+ " Use /skill-name followed by the task instruction. Type / to complete a skill.",
83
+ " Example: /plan design a command registry",
84
+ " Example: /drawio-skill create an architecture diagram",
85
+ "",
86
+ "Startup Arguments",
87
+ " yycode [WORKDIR] [options]",
88
+ *_render_rows(STARTUP_ARGUMENTS),
89
+ "",
90
+ "More",
91
+ " Run `yycode --help` for raw CLI help.",
92
+ ]
93
+ return "\n".join(sections)
94
+
95
+
96
+ def _render_commands(commands: Iterable[TuiCommand]) -> list[str]:
97
+ lines: list[str] = []
98
+ for command in sorted(commands, key=lambda item: item.name):
99
+ usage = command.usage or f":{command.name}"
100
+ description = command.description.rstrip(".")
101
+ lines.append(f" {usage:<18} {description}.")
102
+ if command.name == "clear":
103
+ lines.append(" :clear! Confirm clearing current session history.")
104
+ return lines or [" No TUI commands registered."]
105
+
106
+
107
+ def _render_rows(rows: Iterable[tuple[str, str]]) -> list[str]:
108
+ return [f" {key:<24} {description}" for key, description in rows]