Merge remote-tracking branch 'upstream/main'

This commit is contained in:
github-actions[bot]
2026-06-26 18:25:24 +00:00
28 changed files with 95 additions and 40 deletions

42
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,42 @@
# Contributing
This is a curated teaching repository. Each chapter (`sNN_topic/`) builds one idea on top of the last, and the code is deliberately minimal. The simplifications are intentional, so the lesson stays legible. Contributions are welcome when they serve that goal.
## Before opening a PR
1. Tie it to a specific issue. Say what is wrong and why your change is the right fix. One PR addresses one issue.
2. Keep teaching code minimal. Do not add production hardening, defensive parsing, error handling layers, abstractions, or a test framework unless the chapter is specifically about that. A simplification that looks like a bug is often deliberate, so ask in an issue first.
3. Keep the three languages in sync. Any change to a chapter's `code.py` or a README must be reflected in all three language READMEs in that chapter folder, with the code blocks identical across them.
4. Target the current course files (`sNN_topic/`), not legacy mirrors.
5. Disclose AI assistance. Using an agent is fine. Say so, and make sure you have read and stand behind the change.
## What gets closed without detailed review
To keep the review queue useful, we close:
1. Bulk PRs generated by an agent and opened across many unrelated repositories at once. We cannot responsibly review code the author has not engaged with.
2. Changes that expand a chapter's scope or structure against the teaching design.
3. PRs not tied to an issue, or that restate a deliberate simplification as a bug.
Focused, clearly explained contributions from people who have actually worked with the material are always welcome.
# 贡献指南
这是一个**教学仓库**。每一章(`sNN_topic/`)在上一章的基础上只讲清一个点,代码刻意保持最简。简化是有意为之,为的是让讲解保持清晰。欢迎服务于这个目标的贡献。
## 提 PR 之前
1. 对应一个具体 issue。说清问题是什么、为什么你的改法是对的。一个 PR 只解决一个问题。
2. 教学代码保持最简。不要加生产级的防御解析、错误处理层、抽象或测试框架,除非这一章本身就在讲这些。某处"像 bug"的简化往往是刻意的,先在 issue 里问一声。
3. 三语保持同步。对某章 `code.py` 或 README 的改动,必须同步到该章目录下三种语言的 README,且其中代码块完全一致。
4. 改现行课程文件(`sNN_topic/`),不要改旧的镜像目录。
5. 声明 AI 协助。用 agent 没问题,但请说明,并确保你读过、也认可这份改动。
## 哪些会被直接关闭(不做详细评审)
1. 在大量互不相关的仓库里一次性批量提交、由 agent 生成的 PR。作者自己都没参与的代码,我们无法负责任地评审。
2. 让某章范围或结构超出教学设计的改动。
3. 没有对应 issue、或把刻意的简化当成 bug 来"修"的 PR。
真正用过这份材料、聚焦且讲清理由的贡献,我们始终欢迎。

View File

@@ -409,13 +409,13 @@ learn-claude-code/
Skill & LSP 対応、Windows 対応、GLM / MiniMax / DeepSeek 等のオープンモデルに接続可能。インストールしてすぐ使える。
GitHub: **[shareAI-lab/Kode-cli](https://github.com/shareAI-lab/Kode-cli)**
GitHub: **[shareAI-lab/Kode-CLI](https://github.com/shareAI-lab/Kode-CLI)**
### Kode Agent SDK -- アプリにエージェント機能を埋め込む
公式 Claude Code Agent SDK は内部で完全な CLI プロセスと通信する -- 同時ユーザーごとに独立のターミナルプロセスが必要。Kode SDK は独立ライブラリでユーザーごとのプロセスオーバーヘッドがなく、バックエンド、ブラウザ拡張、組み込みデバイス等に埋め込み可能。
GitHub: **[shareAI-lab/Kode-agent-sdk](https://github.com/shareAI-lab/Kode-agent-sdk)**
GitHub: **[shareAI-lab/kode-agent-sdk](https://github.com/shareAI-lab/kode-agent-sdk)**
---

View File

@@ -410,13 +410,13 @@ learn-claude-code/
支持 Skill & LSP, 适配 Windows, 可接 GLM / MiniMax / DeepSeek 等开放模型。装完即用。
GitHub: **[shareAI-lab/Kode-cli](https://github.com/shareAI-lab/Kode-cli)**
GitHub: **[shareAI-lab/Kode-CLI](https://github.com/shareAI-lab/Kode-CLI)**
### Kode Agent SDK -- 把 Agent 能力嵌入你的应用
官方 Claude Code Agent SDK 底层与完整 CLI 进程通信 -- 每个并发用户 = 一个终端进程。Kode SDK 是独立库, 无 per-user 进程开销, 可嵌入后端、浏览器插件、嵌入式设备等任意运行时。
GitHub: **[shareAI-lab/Kode-agent-sdk](https://github.com/shareAI-lab/Kode-agent-sdk)**
GitHub: **[shareAI-lab/kode-agent-sdk](https://github.com/shareAI-lab/kode-agent-sdk)**
---

View File

@@ -415,7 +415,7 @@ After 20 lessons, you understand harness engineering from the inside out. Two pa
Skill and LSP support, Windows compatible, works with GLM / MiniMax / DeepSeek and other open models. Install and go.
GitHub: **[shareAI-lab/Kode-Agent](https://github.com/shareAI-lab/Kode-Agent)**
GitHub: **[shareAI-lab/Kode-CLI](https://github.com/shareAI-lab/Kode-CLI)**
### Kode Agent SDK -- Embed Agent Capabilities in Your Application

View File

@@ -260,7 +260,7 @@ def _safe_path(p: str) -> Path:
def _run_bash(command: str) -> str:
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot"]
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
return "Error: Dangerous command blocked"
try:

View File

@@ -301,7 +301,7 @@ def _safe_path(p: str) -> Path:
def _run_bash(command: str) -> str:
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot"]
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
return "Error: Dangerous command blocked"
try:

View File

@@ -389,7 +389,7 @@ def _safe_path(p: str) -> Path:
def _run_bash(command: str) -> str:
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot"]
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
return "Error: Dangerous command blocked"
try:

View File

@@ -1,6 +1,6 @@
# s01: The Agent Loop
`[ s01 ] s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`
`[ s01 ] > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`
> *"One loop & Bash is all you need"* -- one tool + one loop = an agent.
>

View File

@@ -1,6 +1,6 @@
# s02: Tool Use
`s01 > [ s02 ] s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`
`s01 > [ s02 ] > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`
> *"Adding a tool means adding one handler"* -- the loop stays the same; new tools register into the dispatch map.
>

View File

@@ -1,6 +1,6 @@
# s03: TodoWrite
`s01 > s02 > [ s03 ] s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`
`s01 > s02 > [ s03 ] > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`
> *"An agent without a plan drifts"* -- list the steps first, then execute.
>

View File

@@ -1,6 +1,6 @@
# s04: Subagents
`s01 > s02 > s03 > [ s04 ] s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`
`s01 > s02 > s03 > [ s04 ] > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`
> *"Break big tasks down; each subtask gets a clean context"* -- subagents use independent messages[], keeping the main conversation clean.
>

View File

@@ -1,6 +1,6 @@
# s05: Skills
`s01 > s02 > s03 > s04 > [ s05 ] s06 | s07 > s08 > s09 > s10 > s11 > s12`
`s01 > s02 > s03 > s04 > [ s05 ] > s06 | s07 > s08 > s09 > s10 > s11 > s12`
> *"Load knowledge when you need it, not upfront"* -- inject via tool_result, not the system prompt.
>

View File

@@ -1,6 +1,6 @@
# s07: Task System
`s01 > s02 > s03 > s04 > s05 > s06 | [ s07 ] s08 > s09 > s10 > s11 > s12`
`s01 > s02 > s03 > s04 > s05 > s06 | [ s07 ] > s08 > s09 > s10 > s11 > s12`
> *"Break big goals into small tasks, order them, persist to disk"* -- a file-based task graph with dependencies, laying the foundation for multi-agent collaboration.
>

View File

@@ -1,6 +1,6 @@
# s08: Background Tasks
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > [ s08 ] s09 > s10 > s11 > s12`
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > [ s08 ] > s09 > s10 > s11 > s12`
> *"Run slow operations in the background; the agent keeps thinking"* -- daemon threads run commands, inject notifications on completion.
>

View File

@@ -1,6 +1,6 @@
# s09: Agent Teams
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > [ s09 ] s10 > s11 > s12`
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > [ s09 ] > s10 > s11 > s12`
> *"When the task is too big for one, delegate to teammates"* -- persistent teammates + async mailboxes.
>

View File

@@ -1,6 +1,6 @@
# s10: Team Protocols
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > [ s10 ] s11 > s12`
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > [ s10 ] > s11 > s12`
> *"Teammates need shared communication rules"* -- one request-response pattern drives all negotiation.
>

View File

@@ -1,6 +1,6 @@
# s11: Autonomous Agents
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > [ s11 ] s12`
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > [ s11 ] > s12`
> *"Teammates scan the board and claim tasks themselves"* -- no need for the lead to assign each one.
>

View File

@@ -373,4 +373,6 @@ if __name__ == "__main__":
for block in history[-1]["content"]:
if getattr(block, "type", None) == "text":
print(block.text)
elif isinstance(block, dict) and block.get("type") == "text":
print(block.get("text", ""))
print()

View File

@@ -475,4 +475,6 @@ if __name__ == "__main__":
for block in history[-1]["content"]:
if getattr(block, "type", None) == "text":
print(block.text)
elif isinstance(block, dict) and block.get("type") == "text":
print(block.get("text", ""))
print()

View File

@@ -973,6 +973,8 @@ if __name__ == "__main__":
for block in history[-1]["content"]:
if getattr(block, "type", None) == "text":
print(block.text)
elif isinstance(block, dict) and block.get("type") == "text":
print(block.get("text", ""))
# Announce once when every teammate has finished and its output drained.
if active_teammates:

View File

@@ -868,6 +868,8 @@ if __name__ == "__main__":
for block in history[-1]["content"]:
if getattr(block, "type", None) == "text":
print(block.text)
elif isinstance(block, dict) and block.get("type") == "text":
print(block.get("text", ""))
# Check inbox → route protocol + inject into history
inbox_msgs = consume_lead_inbox(route_protocol=True)

View File

@@ -42,13 +42,13 @@ After completing a task, the teammate doesn't exit. It enters the IDLE phase —
IDLE_POLL_INTERVAL = 5 # seconds
IDLE_TIMEOUT = 60 # seconds
def idle_poll(agent_name, messages, name, role) -> str:
def idle_poll(name, messages, role) -> str:
"""Return 'work', 'shutdown', or 'timeout'."""
for _ in range(IDLE_TIMEOUT // IDLE_POLL_INTERVAL):
time.sleep(IDLE_POLL_INTERVAL)
# ① Check inbox (priority)
inbox = BUS.read_inbox(agent_name)
inbox = BUS.read_inbox(name)
if inbox:
# shutdown_request handled immediately
for msg in inbox:
@@ -63,7 +63,7 @@ def idle_poll(agent_name, messages, name, role) -> str:
unclaimed = scan_unclaimed_tasks()
if unclaimed:
task = unclaimed[0]
result = claim_task(task["id"], agent_name)
result = claim_task(task["id"], name)
if "Claimed" in result:
messages.append(...)
return "work"
@@ -126,7 +126,7 @@ while True:
break # WORK phase ends
# IDLE phase
idle_result = idle_poll(name, messages, name, role)
idle_result = idle_poll(name, messages, role)
if idle_result == "shutdown":
break
if idle_result == "timeout":
@@ -268,4 +268,4 @@ Teaching version's `idle_poll()` merges CC's four mechanisms into one polling fu
</details>
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
<!-- translation-sync: zh@v2, en@v2, ja@v2 -->

View File

@@ -42,13 +42,13 @@ S16 の教学版 MessageBus とプロトコルツールを踏襲。本章の追
IDLE_POLL_INTERVAL = 5 # seconds
IDLE_TIMEOUT = 60 # seconds
def idle_poll(agent_name, messages, name, role) -> str:
def idle_poll(name, messages, role) -> str:
"""Return 'work', 'shutdown', or 'timeout'."""
for _ in range(IDLE_TIMEOUT // IDLE_POLL_INTERVAL):
time.sleep(IDLE_POLL_INTERVAL)
# ① 受信箱確認(優先)
inbox = BUS.read_inbox(agent_name)
inbox = BUS.read_inbox(name)
if inbox:
# shutdown_request は即座に処理
for msg in inbox:
@@ -63,7 +63,7 @@ def idle_poll(agent_name, messages, name, role) -> str:
unclaimed = scan_unclaimed_tasks()
if unclaimed:
task = unclaimed[0]
result = claim_task(task["id"], agent_name)
result = claim_task(task["id"], name)
if "Claimed" in result:
messages.append(...)
return "work"
@@ -126,7 +126,7 @@ while True:
break # WORK フェーズ終了
# IDLE フェーズ
idle_result = idle_poll(name, messages, name, role)
idle_result = idle_poll(name, messages, role)
if idle_result == "shutdown":
break
if idle_result == "timeout":
@@ -268,4 +268,4 @@ s18 Worktree Isolation → 各タスクに専用の作業ディレクトリ、
</details>
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
<!-- translation-sync: zh@v2, en@v2, ja@v2 -->

View File

@@ -42,13 +42,13 @@ s16 的队友能通信、能握手关机。但每个队友等 Lead 分配任务
IDLE_POLL_INTERVAL = 5 # seconds
IDLE_TIMEOUT = 60 # seconds
def idle_poll(agent_name, messages, name, role) -> str:
def idle_poll(name, messages, role) -> str:
"""Return 'work', 'shutdown', or 'timeout'."""
for _ in range(IDLE_TIMEOUT // IDLE_POLL_INTERVAL):
time.sleep(IDLE_POLL_INTERVAL)
# ① 检查收件箱(优先)
inbox = BUS.read_inbox(agent_name)
inbox = BUS.read_inbox(name)
if inbox:
# shutdown_request 立即处理
for msg in inbox:
@@ -63,7 +63,7 @@ def idle_poll(agent_name, messages, name, role) -> str:
unclaimed = scan_unclaimed_tasks()
if unclaimed:
task = unclaimed[0]
result = claim_task(task["id"], agent_name)
result = claim_task(task["id"], name)
if "Claimed" in result:
messages.append(...)
return "work"
@@ -126,7 +126,7 @@ while True:
break # WORK 阶段结束
# IDLE phase
idle_result = idle_poll(name, messages, name, role)
idle_result = idle_poll(name, messages, role)
if idle_result == "shutdown":
break
if idle_result == "timeout":
@@ -268,4 +268,4 @@ s18 Worktree Isolation → 每个任务有自己的工作目录,互不干扰
</details>
<!-- translation-sync: zh@v1, en@v1, ja@v1 -->
<!-- translation-sync: zh@v2, en@v2, ja@v2 -->

View File

@@ -301,14 +301,13 @@ def scan_unclaimed_tasks() -> list[dict]:
return unclaimed
def idle_poll(agent_name: str, messages: list,
name: str, role: str) -> str:
def idle_poll(name: str, messages: list, role: str) -> str:
"""Poll for 60s. Return 'work', 'shutdown', or 'timeout'."""
for _ in range(IDLE_TIMEOUT // IDLE_POLL_INTERVAL):
time.sleep(IDLE_POLL_INTERVAL)
# Check inbox — dispatch protocol messages first
inbox = BUS.read_inbox(agent_name)
inbox = BUS.read_inbox(name)
if inbox:
# Check for shutdown_request
for msg in inbox:
@@ -331,7 +330,7 @@ def idle_poll(agent_name: str, messages: list,
unclaimed = scan_unclaimed_tasks()
if unclaimed:
task = unclaimed[0]
result = claim_task(task["id"], agent_name)
result = claim_task(task["id"], name)
if "Claimed" in result:
messages.append({"role": "user",
"content": f"<auto-claimed>Task {task['id']}: "
@@ -498,7 +497,7 @@ def spawn_teammate_thread(name: str, role: str, prompt: str) -> str:
break
# IDLE phase (s17 new)
idle_result = idle_poll(name, messages, name, role)
idle_result = idle_poll(name, messages, role)
if idle_result == "shutdown":
break
if idle_result == "timeout":
@@ -800,6 +799,8 @@ if __name__ == "__main__":
for block in history[-1]["content"]:
if getattr(block, "type", None) == "text":
print(block.text)
elif isinstance(block, dict) and block.get("type") == "text":
print(block.get("text", ""))
# Consume lead inbox: route protocol + inject into history
inbox = consume_lead_inbox(route_protocol=True)

View File

@@ -984,6 +984,8 @@ if __name__ == "__main__":
for block in history[-1]["content"]:
if getattr(block, "type", None) == "text":
print(block.text)
elif isinstance(block, dict) and block.get("type") == "text":
print(block.get("text", ""))
# Consume lead inbox: route protocol + inject into history
inbox = consume_lead_inbox(route_protocol=True)

View File

@@ -1013,6 +1013,8 @@ if __name__ == "__main__":
for block in history[-1]["content"]:
if getattr(block, "type", None) == "text":
print(block.text)
elif isinstance(block, dict) and block.get("type") == "text":
print(block.get("text", ""))
inbox = consume_lead_inbox(route_protocol=True)
if inbox:

View File

@@ -2063,8 +2063,8 @@ def print_turn_assistants(messages: list, turn_start: int):
if msg.get("role") != "assistant":
continue
for block in msg.get("content", []):
if getattr(block, "type", None) == "text":
terminal_print(block.text)
if block_type(block) == "text":
terminal_print(block["text"] if isinstance(block, dict) else block.text)
def cron_autorun_loop(history: list, context: dict):