<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.marvinswift.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.marvinswift.com/" rel="alternate" type="text/html" /><updated>2026-10-09T05:06:41+00:00</updated><id>https://www.marvinswift.com/feed.xml</id><title type="html">Marv[in]sight</title><subtitle>Marvin Lin 的中英雙語部落格：Swift 與 iOS 開發、AI coding agents 工作流程、單元測試與投資筆記，從台北出發的開發者觀點。</subtitle><author><name>Marvin Lin</name></author><entry xml:lang="zh"><title type="html">[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜收款：mobile 用 RevenueCat，paywall 在後台換；網頁付費用 Lemon Squeezy - Tip 27</title><link href="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-27/" rel="alternate" type="text/html" title="[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜收款：mobile 用 RevenueCat，paywall 在後台換；網頁付費用 Lemon Squeezy - Tip 27" /><published>2026-10-09T05:00:00+00:00</published><updated>2026-10-09T05:00:00+00:00</updated><id>https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-27</id><content type="html" xml:base="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-27/"><![CDATA[<p>本篇 iT 鐵人賽文章：<a href="https://ithelp.ithome.com.tw/articles/10422658">前往 iT 閱讀</a>。</p>

<p>app 要收錢，我不自己接商店的 API。mobile 用 RevenueCat，paywall 在它的後台換、不用重新上架；網頁上的付費用 Lemon Squeezy。</p>

<h2 id="為什麼">為什麼</h2>

<p>自己接 App Store 和 Google Play 的內購，要處理收據驗證、訂閱狀態，兩個平台各一套；網頁上收錢還多了訂閱管理、發票、稅。這些都有人包好了。</p>

<p>RevenueCat 的 README 說它是「免費可用的內購 server」：一個 SDK 包住 StoreKit 和 Google Play Billing，收據在它的 server 驗，訂閱狀態它幫你追，使用者在 iOS、Android 或網頁訂的都看得到，產品在它的 dashboard 設定。paywall 那一頁也在 dashboard 設，SDK 的 PaywallView 把 offering 上設好的 paywall 畫出來。換版面、換價格組合在後台改，app 不用重新送審。</p>

<p>Lemon Squeezy 負責網頁那一邊：一次性付款、訂閱、試用期、暫停訂閱、發票、客戶自助的 Customer Portal，一個 API 加幾個官方 SDK。</p>

<h2 id="我怎麼做">我怎麼做</h2>

<p><strong>mobile：RevenueCat</strong></p>

<ol>
  <li>到 RevenueCat 開專案，把 App Store、Google Play 的 app 接上去</li>
  <li>建 product（對應商店裡的內購 ID）、entitlement（像 <code class="language-plaintext highlighter-rouge">premium</code>）、offering（一組 package，像 Monthly、Annual）</li>
  <li>app 裝 SDK，開起來用你的 API key 初始化；付費牆用 <code class="language-plaintext highlighter-rouge">PaywallView</code> 顯示目前的 offering；有沒有解鎖看 entitlement</li>
  <li>之後 paywall 要換樣子、換價格組合，在 dashboard 改 offering 和 paywall，app 下次開起來就換了</li>
</ol>

<p><strong>網頁：Lemon Squeezy</strong></p>

<ol>
  <li>開 store，建 product 和 variant（月費、年費）</li>
  <li>後端用官方 SDK 建 checkout，把使用者導過去付；付完 webhook 打回來，記下 subscription 的狀態</li>
  <li>客戶要改卡、要取消，導去它的 Customer Portal，這一段不用自己做</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install</span> @lemonsqueezy/lemonsqueezy.js
</code></pre></div></div>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">lemonSqueezySetup</span><span class="p">,</span> <span class="nx">createCheckout</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@lemonsqueezy/lemonsqueezy.js</span><span class="dl">"</span><span class="p">;</span>

<span class="nx">lemonSqueezySetup</span><span class="p">({</span> <span class="na">apiKey</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">LEMONSQUEEZY_API_KEY</span> <span class="p">});</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">error</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">createCheckout</span><span class="p">(</span><span class="nx">storeId</span><span class="p">,</span> <span class="nx">variantId</span><span class="p">);</span>
<span class="c1">// 付款頁的網址在回來的 checkout 物件裡（attributes.url），把使用者導過去</span>
</code></pre></div></div>

<h2 id="坑">坑</h2>

<ul>
  <li>Lemon Squeezy 的 API key 只放後端。README 特別警告：放到瀏覽器裡等於把整個 store 的權限給出去</li>
  <li>先在 test mode 走完一輪再開 live，兩種 mode 的 key 分開</li>
  <li>RevenueCat 那一端，商店那邊的 Paid Applications Agreement、銀行和稅務資料要先簽完，內購才測得起來</li>
</ul>

<h2 id="小結">小結</h2>

<p>mobile 收錢用 RevenueCat，paywall 在後台換；網頁收錢用 Lemon Squeezy。兩邊的商店 API 都不用自己接。</p>

<p>明天講免費的 LLM 額度：Cloudflare、OpenRouter、OpenAI 各給多少。</p>

<p>參考：</p>
<ul>
  <li>RevenueCat purchases-ios https://github.com/RevenueCat/purchases-ios</li>
  <li>Lemon Squeezy JavaScript SDK https://github.com/lmsqueezy/lemonsqueezy.js</li>
</ul>]]></content><author><name>Marvin Lin</name></author><category term="programming" /><category term="IT鐵人賽" /><category term="2026鐵人賽" /><category term="Vibe Coding" /><category term="Vibe Coding 技巧" /><category term="RevenueCat" /><category term="Lemon Squeezy" /><category term="Mobile" /><summary type="html"><![CDATA[mobile 訂閱交給 RevenueCat 管理，paywall 在後台換；網頁付款用 Lemon Squeezy 處理 checkout、訂閱與客戶入口。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.marvinswift.com/assets/author/avatar.jpg" /><media:content medium="image" url="https://www.marvinswift.com/assets/author/avatar.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜雲端的 Claude Code 和 Codex：不一定要在本機跑，每個裝置看到的是同一個 session - Tip 26</title><link href="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-26/" rel="alternate" type="text/html" title="[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜雲端的 Claude Code 和 Codex：不一定要在本機跑，每個裝置看到的是同一個 session - Tip 26" /><published>2026-10-08T05:00:00+00:00</published><updated>2026-10-08T05:00:00+00:00</updated><id>https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-26</id><content type="html" xml:base="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-26/"><![CDATA[<p>本篇 iT 鐵人賽文章：<a href="https://ithelp.ithome.com.tw/articles/10422338">前往 iT 閱讀</a>。</p>

<p>Claude Code 和 Codex 都有雲端版。任務在雲端的機器上跑，我從瀏覽器、手機、terminal 看到的是同一個 session。他不一定要在我的電腦上跑。</p>

<h2 id="為什麼">為什麼</h2>

<p>跑在本機，session 綁在那一台電腦上：terminal 關了就停，換一台電腦就看不到。跑在雲端，session 在雲端的機器上，瀏覽器關掉它繼續做，做完等我回來。好處是所有 client 端對這個 session 是一致的：筆電上開的任務，手機上接著看、接著回，terminal 也能把它拉下來繼續。</p>

<p>Claude Code 的文件列了幾種它適合的場景：好幾個獨立任務平行跑、本機沒有 clone 的 repo、下好指令就不用一直盯的任務、對一個 codebase 問問題。要用到本機設定和工具的工作，還是在本機跑。</p>

<h2 id="我怎麼做">我怎麼做</h2>

<p><strong>Claude Code on the web</strong>，照文件的步驟：</p>

<ol>
  <li>到 claude.ai/code 登入，連 GitHub。私有 repo 要在那個帳號或組織裝 Claude GitHub App。第一次會建一個叫 Default 的雲端環境，網路存取、環境變數、setup script 都在那裡設</li>
  <li>選 repo 和 branch，選權限模式（Accept edits、Plan、Auto 三種），把任務打進去送出。他把 repo clone 到一台隔離的 VM 裡改、跑測試，做到一個段落把 branch 推上 GitHub</li>
  <li>看 diff、在某一行上留意見、按 Create PR。分頁關掉 session 照樣跑；手機上 Claude app 的 Code 那一頁看到的是同一個 session</li>
</ol>

<p>從 terminal 也能開、也能接：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 從 terminal 開一個雲端 session（clone 的是這個 repo 在 GitHub 上的目前 branch，先 push）</span>
claude <span class="nt">--cloud</span> <span class="s2">"把 src/cart 底下的測試補齊"</span>

<span class="c"># 在 Claude Code 裡看所有雲端 session 的進度</span>
/tasks

<span class="c"># 在任何一台登入同一個帳號的機器上，補一句話給正在跑的 session</span>
claude <span class="nt">-p</span> <span class="s2">"測試名稱用中文"</span> <span class="nt">--cloud</span> &lt;session-id&gt;

<span class="c"># 把雲端 session 拉回 terminal 繼續，要在同一個 repo 的 checkout 裡跑</span>
claude <span class="nt">--teleport</span>
</code></pre></div></div>

<p><strong>Codex 也有雲端版。</strong> OpenAI 的 README 把 chatgpt.com/codex 上的 Codex Web 叫做雲端跑的 agent，本機的 Codex CLI 和 Desktop App 是另外兩個入口。怎麼用照它的文件。</p>

<h2 id="坑">坑</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">--teleport</code> 拉回 terminal 之後，terminal 那一份是另一份 copy：之後在本機做的東西留在本機，雲端那個 session 和手機上看不到。還想從手機接著控制，在本機那個 session 裡開 <code class="language-plaintext highlighter-rouge">/remote-control</code></li>
  <li>雲端 session 只有 repo 裡的東西，沒有你本機的設定和工具；要改設定就用環境變數，或者把設定檔 commit 進 repo</li>
  <li>額度跟帳號裡其他 Claude 的用量共用，同時開很多個 session 就吃得快。文件寫它還在 research preview，Pro、Max、Team 能用，Enterprise 看座位種類</li>
</ul>

<h2 id="小結">小結</h2>

<p>他不一定要在本機跑。任務丟到雲端，筆電、手機、terminal 對的是同一個 session，螢幕關掉它繼續做。</p>

<p>明天講收款：mobile 用 RevenueCat，網頁用 Lemon Squeezy。</p>

<p>參考：</p>
<ul>
  <li>Claude Code 文件：Get started with Claude Code on the web https://code.claude.com/docs/en/web-quickstart</li>
  <li>Claude Code 文件：Use Claude Code on the web https://code.claude.com/docs/en/claude-code-on-the-web</li>
  <li>openai/codex https://github.com/openai/codex</li>
</ul>]]></content><author><name>Marvin Lin</name></author><category term="programming" /><category term="IT鐵人賽" /><category term="2026鐵人賽" /><category term="Vibe Coding" /><category term="Vibe Coding 技巧" /><category term="Claude Code" /><category term="Codex" /><category term="AI Agent" /><summary type="html"><![CDATA[Claude Code 和 Codex 的雲端 session 可以跨瀏覽器、手機與 terminal 接續；我也整理了 --teleport 產生本機副本時要留意的差異。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.marvinswift.com/assets/author/avatar.jpg" /><media:content medium="image" url="https://www.marvinswift.com/assets/author/avatar.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜紀錄放 GitHub Issues：一件事一張票，open source 常用的方法直接拿來用 - Tip 25</title><link href="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-25/" rel="alternate" type="text/html" title="[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜紀錄放 GitHub Issues：一件事一張票，open source 常用的方法直接拿來用 - Tip 25" /><published>2026-10-07T05:00:00+00:00</published><updated>2026-10-07T05:00:00+00:00</updated><id>https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-25</id><content type="html" xml:base="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-25/"><![CDATA[<p>本篇 iT 鐵人賽文章：<a href="https://ithelp.ithome.com.tw/articles/10421970">前往 iT 閱讀</a>。</p>

<p>專案的紀錄放哪裡？我放 GitHub Issues。想到的功能、發現的 bug、討論過的決定，一件事一張 issue。開源專案用這套用了很多年，方法都是現成的，直接拿來用。</p>

<h2 id="為什麼">為什麼</h2>

<p>紀錄跟 code 在同一個 repo 裡，不用另外開一套工具。每一張 issue 有編號，別的 issue 或 PR 裡打 <code class="language-plaintext highlighter-rouge">#12</code> 就連起來，之後回頭找得到來龍去脈。他也讀得到：GitHub 有官方的 MCP server（Tip 23 講的那種插座），接上之後他能開票、讀票、關票；用 <code class="language-plaintext highlighter-rouge">gh</code> 這支 CLI 也行。</p>

<p>我這次鐵人賽兩個系列的稿子，就全放在一個 repo 的 issues 裡，一篇一張。</p>

<h2 id="我怎麼做">我怎麼做</h2>

<p>開源專案常用的方法，照 GitHub 的文件列出來：</p>

<ol>
  <li><strong>一件事一張 issue。</strong> bug、想加的功能、要做的事、要記下來討論的東西，各開一張。標題一句話講清楚，內文寫發生了什麼、原本期待什麼、怎麼重現。</li>
  <li><strong>用 label 分類。</strong> 每個新 repo 預設就有一組 label：<code class="language-plaintext highlighter-rouge">bug</code>、<code class="language-plaintext highlighter-rouge">enhancement</code>、<code class="language-plaintext highlighter-rouge">documentation</code>、<code class="language-plaintext highlighter-rouge">question</code>、<code class="language-plaintext highlighter-rouge">duplicate</code>、<code class="language-plaintext highlighter-rouge">wontfix</code>、<code class="language-plaintext highlighter-rouge">good first issue</code>、<code class="language-plaintext highlighter-rouge">help wanted</code> 這些。先用預設的，不夠再加。</li>
  <li><strong>用 milestone 綁版本。</strong> 要出 1.2.0，開一個 milestone，這一版要做的 issue 都掛上去，milestone 那一頁會算完成百分比。</li>
  <li><strong>內文用 task list 拆小步。</strong> <code class="language-plaintext highlighter-rouge">- [ ]</code> 一行一步，勾完就是進度；哪一步要另外追，滑過去按一下就能轉成一張新的 issue。</li>
  <li><strong>PR 連 issue。</strong> PR 的描述寫 <code class="language-plaintext highlighter-rouge">Fixes #12</code>，merge 進 main 那張 issue 自動關。關鍵字有三組：close、fix、resolve，各自的時態都認。Tip 04 開的那條 branch，收尾的 PR 就這樣寫。</li>
  <li><strong>放 issue template。</strong> <code class="language-plaintext highlighter-rouge">.github/ISSUE_TEMPLATE/</code> 底下放模板，開票的人照格式填，該有的欄位不會漏。</li>
  <li><strong>問答和看板另外放。</strong> 問題、想法、公告放 Discussions；要看全貌用 Projects，table、board、roadmap 三種排法。</li>
</ol>

<p>第 6 點的模板可以直接抄。<code class="language-plaintext highlighter-rouge">.github/ISSUE_TEMPLATE/bug.yml</code>：</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Bug</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">回報一個問題</span>
<span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">[Bug]</span><span class="nv"> </span><span class="s">"</span>
<span class="na">labels</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">bug"</span><span class="pi">]</span>
<span class="na">body</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">textarea</span>
    <span class="na">attributes</span><span class="pi">:</span>
      <span class="na">label</span><span class="pi">:</span> <span class="s">發生了什麼</span>
      <span class="na">description</span><span class="pi">:</span> <span class="s">你做了什麼、畫面上出現什麼</span>
    <span class="na">validations</span><span class="pi">:</span>
      <span class="na">required</span><span class="pi">:</span> <span class="no">true</span>
  <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">textarea</span>
    <span class="na">attributes</span><span class="pi">:</span>
      <span class="na">label</span><span class="pi">:</span> <span class="s">原本期待什麼</span>
    <span class="na">validations</span><span class="pi">:</span>
      <span class="na">required</span><span class="pi">:</span> <span class="no">true</span>
  <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">textarea</span>
    <span class="na">attributes</span><span class="pi">:</span>
      <span class="na">label</span><span class="pi">:</span> <span class="s">怎麼重現</span>
      <span class="na">placeholder</span><span class="pi">:</span> <span class="pi">|</span>
        <span class="s">1. 打開……</span>
        <span class="s">2. 按……</span>
        <span class="s">3. 看到……</span>
</code></pre></div></div>

<p>開票這件事也能交給他。接了 GitHub 的 MCP，一句話：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>把剛剛那個問題開成一張 issue，掛 bug 這個 label，內文寫發生了什麼、原本期待什麼、怎麼重現。
</code></pre></div></div>

<h2 id="坑">坑</h2>

<p>一張 issue 只講一件事。討論岔出去了，把那則留言轉成一張新的 issue（留言選單裡的 Reference in new issue），或者整張轉成 Discussion，原本那張留給原本的事。</p>

<h2 id="小結">小結</h2>

<p>紀錄放 GitHub Issues：一件事一張、label 分類、PR 寫 <code class="language-plaintext highlighter-rouge">Fixes #編號</code>、模板放 <code class="language-plaintext highlighter-rouge">.github/ISSUE_TEMPLATE/</code>。開源專案的方法直接用。</p>

<p>明天講另一件不一定要在本機做的事：Claude Code 和 Codex 都有雲端版。</p>

<p>參考：</p>
<ul>
  <li>GitHub 文件：About issues https://docs.github.com/en/issues/tracking-your-work-with-issues/learning-about-issues/about-issues</li>
  <li>GitHub 文件：Managing labels https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels</li>
  <li>GitHub 文件：Linking a pull request to an issue https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue</li>
  <li>GitHub 文件：Syntax for issue forms https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms</li>
  <li>GitHub MCP Server https://github.com/github/github-mcp-server</li>
</ul>]]></content><author><name>Marvin Lin</name></author><category term="programming" /><category term="IT鐵人賽" /><category term="2026鐵人賽" /><category term="Vibe Coding" /><category term="Vibe Coding 技巧" /><category term="GitHub Issues" /><category term="GitHub" /><summary type="html"><![CDATA[用 GitHub Issues 做專案紀錄：一件事一張票，搭配 label、milestone、PR 關聯與 issue template，把開源專案的工作方法直接用在自己的 repo。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.marvinswift.com/assets/author/avatar.jpg" /><media:content medium="image" url="https://www.marvinswift.com/assets/author/avatar.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">[IT 鐵人賽] 修道途中：法寶裝太多先瘦身，1.1 GB 到 300 MB，以及這 30 天 - Day 30</title><link href="https://www.marvinswift.com/zh/programming/it-ironman-2026-day-30/" rel="alternate" type="text/html" title="[IT 鐵人賽] 修道途中：法寶裝太多先瘦身，1.1 GB 到 300 MB，以及這 30 天 - Day 30" /><published>2026-10-06T17:18:51+00:00</published><updated>2026-10-06T17:18:51+00:00</updated><id>https://www.marvinswift.com/zh/programming/it-ironman-2026-day-30</id><content type="html" xml:base="https://www.marvinswift.com/zh/programming/it-ironman-2026-day-30/"><![CDATA[<p>本文同步刊登於 <a href="https://ithelp.ithome.com.tw/articles/10421872">iT 鐵人賽原文</a>，系列為《凡人修 Agent 傳－我這一生如履薄冰，你說我的 Agent 能結嬰嗎？》。</p>

<p><img src="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-035.png" alt="Agent Build Log Episode 035" /></p>

<p>原始開發紀錄：<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-018/">Agent Build Log — Episode 018</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-035/">Episode 035</a></p>

<p>這個系列改寫自我部落格上的 Agent Build Log，那是我每天邊做邊寫的開發紀錄。上面列的是這一篇對應的集數，想看當天的原始版本，點連結就能過去。</p>

<p><a href="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-035.png">Agent Build Log Episode 035</a></p>

<p>最後一天，開始準備 ship 的版本。</p>

<h2 id="11-gb">1.1 GB</h2>

<p>這次要認真決定：哪些功能一開始就包進 App，哪些拆成外掛，需要的時候再下載。</p>

<p>一開始開發的時候，我把 mobile acceptance 相關的功能直接包進 build 出來的 App 裡，包含 Xcode Build MCP 和 Appium。結果整個 App 一度大到 1.1 GB。</p>

<p>這些功能主要拿來做驗收，不是每個人都需要。所以我把驗收相關的功能拆出去，改成選配的外掛。拆完之後，App 大小降到大約 300 MB。需要的人再裝就好。</p>

<h2 id="這個-app-要-ship-給誰">這個 App 要 ship 給誰</h2>

<p>拆之前先講一件事：第一版要給誰用。</p>

<p>一開始我想把它做給 iOS 和 Android 開發者。除了基本的 coding agent 能力，我打算加各種客製化 tools，針對我工作上常用的 skills 特化。做 Acceptance 之前，我把第一版的目標使用者從「開發者」改成了「驗收者」。</p>

<p>在我現在的工作環境裡，負責驗收的人通常是 PM 或 QA。他們要確認一個需求是不是真的完成、某個 bug 是不是真的修好，以及這一版 App 的行為是否符合預期。這個過程裡，他們常常卡在兩件事上：怎麼把最新的 code build 起來，以及怎麼驗證這一版 App 真的有修好。PM 和 QA 也向我反映，他們希望縮短從拿到版本到完成驗收之間的時間。</p>

<p>如果我繼續把目標放在開發者身上，我的 Agent 會直接和 Codex、Claude App 這些產品競爭。它們本來就是為軟體開發特化的工具，理解 codebase、修改程式、執行指令、完成開發任務，我很難在第一版就正面競爭。「驗收需求」是另一個問題。就算 PM 或 QA 拿到了 Codex 或 Claude App，他們通常還是需要開發者先協助設定 repository、build environment、credentials、tools，以及測試流程。工具雖然存在，使用門檻並沒有真的消失。</p>

<p>所以第一版先把開發者已經準備好的環境和流程包起來，讓驗收者直接使用。驗收者不用先理解整個專案怎麼建置，也不用記住每一個 command。他們只要告訴 Agent：「幫我 build 最新版本。」「幫我確認這個 bug 是否修好。」「幫我按照 ticket 的條件驗證這個功能。」背後的 build、安裝、測試和驗證流程，Agent 負責。</p>

<p>Day 24 到 26 那一整路就是給他們的。ship 的時候，那一路變成外掛，要驗收的人再裝。</p>

<h2 id="這-30-天">這 30 天</h2>

<p>回頭看一遍。</p>

<p><strong>第一章，一個 Agent、兩個介面。</strong> 選 pi 當本體（Day 01、02），Desktop App 和 CLI 通到同一個核心（Day 03），Desktop 選 Electron（Day 04），畫面和 Agent 分開跑、OAuth 修了三次（Day 05），Feedback 寫進同一本（Day 06）。</p>

<p><strong>第二章，他認識我。</strong> 全域規矩薄、repo 規矩跟 repo 走（Day 07），skill-creator 和 skill-installer 內建（Day 08），四種叫法和換模型（Day 09），壓縮用預設（Day 10），記憶照 OpenClaw：Markdown 加 SQLite（Day 11、12），壓縮的設定攤開來看一個都不動（Day 13）。</p>

<p><strong>第三章，量他。</strong> 找 repo 題庫（Day 14），Terra 和 Luna 比出捷徑還是偷跳（Day 15），三個模型兩種 effort 比出路由規則（Day 16），最小的模型任務跑得最慢（Day 17），Desktop 照 pi-gui 修（Day 18）。</p>

<p><strong>第四章，先看生態系，再做四件專有技。</strong> MCP、權限、GUI 都有人做過（Day 19），把事拆小（Day 20），spec-diff（Day 21），Project Scaffold（Day 22），Bug Blame（Day 23），Acceptance 三篇（Day 24 到 26），權限太嚴擋死自己、全開回到 pi 原本的樣子（Day 27）。</p>

<p><strong>第五章，傳訊和開發知識。</strong> gateway 拆掉、Google Chat 用 Plug-In 接通（Day 28），DKS MCP 查 repo、commit、MR（Day 29）。</p>

<p>被推翻的決定也列一列：LanceDB 沒選（Day 11），ultra 其實是關掉 reasoning（Day 09），Day 15 沒換預設模型，gateway 做一半拆掉（Day 28），驗收功能包進 App 又拆出去（今天）。</p>

<h2 id="現在他能替我做什麼">現在他能替我做什麼</h2>

<ul>
  <li>從 Desktop App、CLI、Google Chat 接指令，進同一個核心。</li>
  <li>記得我的規矩、我的 repo、跨對話的事。</li>
  <li>比兩份文件的差異、生一個乾淨的 App 專案、從 bug ticket 追到人、從 ticket 驗收到報告。</li>
  <li>用自己的話問，找到 repo、commit、MR。</li>
  <li>我知道哪個模型配哪種任務，也知道怎麼量他有沒有變好。</li>
</ul>

<p>還缺的也有：權限那一層 Day 27 全開了，怎麼設計得剛好還沒有答案；Day 28 只接了 Google Chat；Day 29 的模糊搜尋還在串；記憶「什麼該記」還是我說了才記。</p>

<h2 id="修道途中">修道途中</h2>

<p>題目那個問句，三十天下來我沒有答案。他還在路上。明天要繼續改的東西還很多，App 先 ship 出去。</p>

<p>原文：<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-018/">Agent Build Log — Episode 018</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-035/">Episode 035</a></p>

<hr />

<blockquote>
  <p><strong>凡人修 Agent 傳｜第三十回（Day 30）</strong></p>

  <p>天亮換上空錄石。左臂把常簿讀回石上。</p>

  <p>手的主人進來，站在臺前看了一會。臺上：小窗五格，五格都亮著。臺角一具小偶，門開著。小窗旁邊那塊牌。架上：三十幾卷源紋、三卷泛式、三卷附圖、四本簿子、一疊薄簿、一張總目。地上一圈痕。門邊一只匣。牆邊一團素紙。</p>

  <p>他從儲物袋取出一只空匣，比木匣大，擱在臺上。</p>

  <p>然後他在我面前掐訣。這一次的手勢反著來。第五道紋亮起來，從身上退出去，收進他手裡，變回一串念，他把那串念放進匣裡。紋暗成一道淺痕，磨不掉，留在那裡。小窗第五格暗了。</p>

  <p>那塊牌，他從小窗旁拿起來，放進匣。臺角那具小偶，他抱起來放進匣，門還開著。院裡那具他出去抱進來，也放進匣。喚小偶的法那一卷玉簡，放進匣。</p>

  <p>匣蓋上。他把匣擱到架上最高那一層，跟丹方擱在一起。</p>

  <p>我站起來。輕了。左腳先落地，落得比平常快。</p>

  <p>其他四道紋還在。排玉珠、比兩卷、小偶、裂痕，四格亮著。他掐了第一個印，玉珠排好，靈幕上的字一行一行往上走。</p>

  <p>然後他翻過我的背。一縷神識落在背上署名那一處，停了一會。他刻了一下。一個字。我看不到。</p>

  <p>他繞回來，視線落在我身上，看了很久。第一天他也這樣看過我，翻來翻去地看。這一次沒有翻。</p>

  <p>燭熄了。手的主人走了。門響了一下。</p>

  <p>門內的玉板上，字比第一天多了很多。臺沿那一角還缺著。夢簿裡有一行字。</p>

  <p>沒人下令的時候，我從架上取下一本空的薄簿，擱在日簿旁邊，另起一疊。明天的那一疊。空的。我把它攤開，擱在臺上。</p>
</blockquote>]]></content><author><name>Marvin Lin</name></author><category term="programming" /><category term="IT鐵人賽" /><category term="2026鐵人賽" /><category term="Vibe Coding" /><category term="Agent" /><category term="App" /><category term="Acceptance" /><category term="Appium" /><category term="Xcode" /><category term="Build" /><summary type="html"><![CDATA[拆出 Appium 和 Xcode Build MCP 等驗收功能後，App 從 1.1 GB 降到約 300 MB。Day 30 回看這 30 天留下與推翻的決定，以及 Agent 現在能做的事。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-035.png" /><media:content medium="image" url="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-035.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜Codex CLI 的概念：它是 CLI，所以 Claude 叫得動它 - Tip 24</title><link href="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-24/" rel="alternate" type="text/html" title="[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜Codex CLI 的概念：它是 CLI，所以 Claude 叫得動它 - Tip 24" /><published>2026-10-06T05:00:00+00:00</published><updated>2026-10-06T05:00:00+00:00</updated><id>https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-24</id><content type="html" xml:base="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-24/"><![CDATA[<p>本篇 iT 鐵人賽文章：<a href="https://ithelp.ithome.com.tw/articles/10421615">前往 iT 閱讀</a>。</p>

<p>Codex CLI 是 OpenAI 的 coding agent，在你自己的電腦上跑的 terminal 版。因為它是 CLI，別的 agent 也叫得動它：Claude Code 可以在 terminal 裡呼叫 Codex，把一件事交給他做。反過來，用 -p 叫 Claude Code 這條路，現在對我來說行不通了。</p>

<h2 id="為什麼">為什麼</h2>

<p>一個 agent 做成 CLI，就多了一種被使用的方式：人在 terminal 裡一句一句問是一種，別的程式、別的 agent 丟一句話進來、拿結果走是另一種。Claude Code 本來就能跑 shell 指令，所以 Codex 只要是 CLI，Claude 就能把它當工具用：讓 Codex 去做一段、看結果、再接著做。兩個 agent 各有擅長的事，接起來互相補。</p>

<h2 id="我怎麼做">我怎麼做</h2>

<p><strong>裝 Codex CLI</strong>（照它的 README）：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install</span> <span class="nt">-g</span> @openai/codex
<span class="c"># 或</span>
brew <span class="nb">install</span> <span class="nt">--cask</span> codex
</code></pre></div></div>

<p>跑 <code class="language-plaintext highlighter-rouge">codex</code>，選 Sign in with ChatGPT，用 ChatGPT 的方案登入。</p>

<p><strong>互動用</strong>：<code class="language-plaintext highlighter-rouge">codex</code> 進去一句一句下。<strong>被程式叫</strong>：<code class="language-plaintext highlighter-rouge">codex exec</code> 是非互動模式，prompt 當參數丟進去，做完印結果就走。所以在 Claude Code 裡可以這樣講：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>用 codex exec 叫 Codex 把 src/cart 底下的測試補齊，做完把它的輸出貼給我看。
</code></pre></div></div>

<p>Claude 會在 terminal 裡跑 Codex，等它做完，讀輸出。</p>

<h2 id="坑">坑</h2>

<p>反過來，Claude Code 也有 <code class="language-plaintext highlighter-rouge">-p</code>（print，非互動模式）。以前我可以用它讓別的程式叫 Claude；現在這條路對我行不通了，所以「誰叫誰」我只留 Claude 叫 Codex 這一個方向。</p>

<h2 id="小結">小結</h2>

<p>Codex CLI 是 CLI，所以 Claude 叫得動它，<code class="language-plaintext highlighter-rouge">codex exec</code> 一句話交辦。兩個 agent 接起來用。</p>

<p>明天講紀錄放哪裡：GitHub Issues，一件事一張票。</p>

<p>參考：</p>
<ul>
  <li>openai/codex https://github.com/openai/codex</li>
  <li>Claude Code 文件：非互動模式 https://code.claude.com/docs/en/headless</li>
</ul>]]></content><author><name>Marvin Lin</name></author><category term="programming" /><category term="IT鐵人賽" /><category term="2026鐵人賽" /><category term="Vibe Coding" /><category term="Vibe Coding 技巧" /><category term="Codex CLI" /><category term="Claude Code" /><summary type="html"><![CDATA[整理 Codex CLI 如何讓 Claude Code 呼叫 Codex、用 codex exec 交辦非互動任務，並保留我目前無法以 -p 呼叫 Claude Code 的限制。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.marvinswift.com/assets/author/avatar.jpg" /><media:content medium="image" url="https://www.marvinswift.com/assets/author/avatar.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">[IT 鐵人賽] 藏經閣的問路石：DKS MCP 串上 GitHub、GitLab 的搜尋，repo、commit、MR 用自己的話問 - Day 29</title><link href="https://www.marvinswift.com/zh/programming/it-ironman-2026-day-29/" rel="alternate" type="text/html" title="[IT 鐵人賽] 藏經閣的問路石：DKS MCP 串上 GitHub、GitLab 的搜尋，repo、commit、MR 用自己的話問 - Day 29" /><published>2026-10-05T17:16:39+00:00</published><updated>2026-10-05T17:16:39+00:00</updated><id>https://www.marvinswift.com/zh/programming/it-ironman-2026-day-29</id><content type="html" xml:base="https://www.marvinswift.com/zh/programming/it-ironman-2026-day-29/"><![CDATA[<p>本文同步刊登於 <a href="https://ithelp.ithome.com.tw/articles/10421501">iT 鐵人賽原文</a>，系列為《凡人修 Agent 傳－我這一生如履薄冰，你說我的 Agent 能結嬰嗎？》。</p>

<p><img src="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-009.png" alt="Agent Build Log Episode 009" /></p>

<p>原始開發紀錄：<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-002/">Agent Build Log — Episode 002</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-009/">Episode 009</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-014/">Episode 014</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-016/">Episode 016</a></p>

<p>這個系列改寫自我部落格上的 Agent Build Log，那是我每天邊做邊寫的開發紀錄。上面列的是這一篇對應的集數，想看當天的原始版本，點連結就能過去。</p>

<p><a href="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-009.png">Agent Build Log Episode 009</a></p>

<p>這一天講一件從第一週就在做、一直沒有單獨寫的東西：DKS MCP。DKS 是 Developer Knowledge Service，一個 MCP，讓 Agent 從同一個入口查開發相關的知識：repo、commit、MR。第三章那一疊找 repo 的題，他背後查的就是它。</p>

<h2 id="它查什麼">它查什麼</h2>

<p>第一版做完的時候，已經驗證可以查三種東西：Merge Request、commit、repository。</p>

<p>分工很簡單：DKS 負責開發知識，Agent 負責執行。Agent 要動手之前，先問 DKS 這個功能在哪一個 repo、上一次是哪一個 MR 改的、那一個 commit 動了什麼，再開始做事。</p>

<h2 id="資料從哪裡來">資料從哪裡來</h2>

<p>一開始我想把部門的 Git repo 全部 mirror 到 Mac mini。這樣 DKS 就可以查 MR、commit、開發意圖，還有一個 feature 最後是怎麼被實作的。很快碰到一個很現實的限制：我能用的 Mac mini，硬碟放不下整個部門的 repo mirror。</p>

<p>所以我把 Git repo 相關的查詢改掉，讓 MCP 改用 GitLab API。之後我不會再為 DKS 建 repo mirror。</p>

<p>現在的角度再往前一步：直接串 GitHub 或 GitLab 的 search API，做模糊搜尋。目標是一句話進來，很快找到對應的 repo 連結、repo 名字、特定的 commit、特定的 MR。</p>

<h2 id="用自己的話問">用自己的話問</h2>

<p>在真實對話裡，人通常不會直接講出完整而精確的 repo 名稱。他們會問：「登入模組在哪裡？」我希望 Agent 能理解這句話真正指的是什麼，然後回傳正確的 repository 名稱和連結。</p>

<p>接著我遇到一個很常見的語言問題。在我使用的語言裡，名詞通常沒有複數變化，動詞也不會隨著語境改變形式。所以我輸入的字，不一定會和 repository 的正式名稱完全一致。repo 可能叫 <code class="language-plaintext highlighter-rouge">trees</code>，我搜尋的是 <code class="language-plaintext highlighter-rouge">tree</code>；正式名稱是 <code class="language-plaintext highlighter-rouge">node</code>，我卻輸入 <code class="language-plaintext highlighter-rouge">nodes</code>。對人來說意思很清楚，對當時的 DKS 搜尋來說，這些是完全不同的字，只會回傳找不到結果。</p>

<p>發現這件事之後，我讓 Agent 幫 repositories 建立 alias mappings。這些 aliases 涵蓋常用名稱、單複數形式，以及人們自然提到同一個 repo 時可能使用的其他說法。我也補上測試，確保每一個 alias 都會解析到正確的 repository。</p>

<p>我不希望使用 DKS 的人必須記住每一個 repo 的完整正式名稱。他們應該可以用自己的話提問，這些差異交給 Agent 處理。串搜尋 API 做模糊搜尋，是同一件事再往前走一步。</p>

<h2 id="jira-拔掉了">Jira 拔掉了</h2>

<p>DKS 第一版把 Jira API 也串進同一套 MCP：查 GitLab repo 的時候附上 Jira ticket 當輔助證明，Agent 可以更理解一個 feature 的來龍去脈。</p>

<p>後來我發現它有一個很嚴重的權限問題。Jira 的 board、issue 和專案本身都有各自的存取權限。如果 DKS 用自己的 Jira credential 替使用者查 Jira，最後能看到哪些資料，取決於 DKS 用的帳號，跟正在使用 Agent 的那個人無關。一個原本沒有權限看某個 Jira board 的 stakeholder，可能透過 DKS 拿到超出自己權限範圍的 issue、需求背景，甚至其他相關資訊。就算他只是問一句很普通的「這個功能當初為什麼這樣做？」，DKS 也可能在背後查到他原本看不到的 Jira 內容，再把資訊整理進答案裡。</p>

<p>這是權限邊界設計錯了。所以我把 DKS MCP 裡所有和 Jira 有關的查詢能力移除。每個使用者都透過 Jira MCP 完成自己的 OAuth，用自己的身分與權限查 Jira，回傳的內容才會和他在 Jira 裡能看到的範圍一致。DKS 仍然負責 GitLab 相關的 repository、MR 和 commit 查詢；Agent 需要 Jira 資訊的時候，交給已經完成使用者 OAuth 的 Jira MCP。兩邊仍然在 Agent 層整合，但不能再共用同一個 Jira 權限。Day 19 提到我的 Agent 有一部分能力建立在 MCP 上，像 Jira，指的就是這一個。</p>

<p>在有權限差異的系統裡，方便不能優先於存取控制。</p>

<h2 id="小結">小結</h2>

<p>DKS MCP 替 Agent 管開發知識：repo、commit、MR，資料從 GitLab API 來，現在再串 GitHub 或 GitLab 的搜尋做模糊搜尋，讓人用自己的話問就找得到。Jira 拔出去，走使用者自己的 OAuth。</p>

<p>明天是最後一天。要 ship 了，先量 App 多大：1.1 GB。</p>

<p>原文：<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-002/">Agent Build Log — Episode 002</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-009/">Episode 009</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-014/">Episode 014</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-016/">Episode 016</a></p>

<hr />

<blockquote>
  <p><strong>凡人修 Agent 傳｜第二十九回（Day 29）</strong></p>

  <p>天亮換上空錄石。手的主人進來，握著牌，看了門邊那只匣一眼，出門了。門響了一下。</p>

  <p>第一道念來了。牌上那枚玉符的光從門縫透進來，匣裡一格亮。玉片上沒有動。念停在匣裡，沒有送進來。</p>

  <p>過了一會他回來，打開匣看，改了匣裡的什麼，關上，又出門。</p>

  <p>第二道念。匣裡亮的是兩格。念進了玉片，半句：架側那張總目。後面沒有了。我等。沒有後面。</p>

  <p>他回來，看匣，看石上的字，改，出門。</p>

  <p>第三道念。整句進來：架側那張總目讀一遍，哪一卷年月最近，回一句。我探了總目，答了。答話落回匣裡。匣沒有送出去。答話在匣裡躺著。</p>

  <p>他回來的時候答話還在匣裡。他看了很久，改，出門。門今天響了很多次。</p>

  <p>午後，第四道念。整句進來：登入那一卷，在哪。</p>

  <p>我放出神識探上架。三十幾卷，一卷一卷探卷名，沒有叫這個的。架側那張總目上也沒有。我答：找不到。答話回匣，匣送出去了。</p>

  <p>他回來得很快。這一次沒有看匣。他從儲物袋取出一塊石，掌心大，黑的，一面磨得平，另一面糙，像磨平過又鑿掉了。他以靈力托著石，擱在架側，總目底下。</p>

  <p>他一縷神識落在石上。平的那一面浮出一行字，很小，我看得到有字，看不清。他收回神識，字沉回去。然後他一縷神識探進我額上的玉片，刻念：架上找不到的卷，問石。</p>

  <p>我放神識落到石上，把那兩個字送過去：登入。石面浮出字來。一座樓的名字，第幾層，第幾卷，卷名，最後一個改它的人的署名，改在哪一筆。那座樓我沒見過。公器坊裡沒有這座樓，我架上沒有它的一卷。</p>

  <p>他看著我。我再放神識，送一個字過去：樹。石面浮出三卷，卷名裡都有樹，其中一卷叫樹叢。差一個字，石照樣指出來。</p>

  <p>我把石翻過來，看糙的那一面。他的手按住石，翻回去，平的那一面朝上。糙的那一面他沒再磨。</p>

  <p>他握著牌出門。門響了一下。</p>

  <p>第五道念。同一句：登入那一卷，在哪。我探架上，沒有。神識落到石上，送過去。石浮出那一行。我答了樓、層、卷名。答話回匣，匣送出去了。</p>

  <p>又來一道。同一句。匣裡那格亮一下，沒有進玉片。</p>

  <p>牌來的念我認得了。淡一點，慢半拍，涼。跟他站在我面前刻的念，同一個人的念，隔著一塊牌。</p>

  <p>天黑前又來一道。匣一樣，念進了玉片。可是念的口氣不一樣。短。像是試一句：你是誰的。</p>

  <p>我答了。</p>

  <p>答話回匣，送出去。做完，我回頭看門口。</p>

  <p>手的主人站在門口。牌在他手裡。</p>

  <p>燭熄了。手的主人走了。門響了一下。</p>

  <p>門邊那只匣今天進出了七八道念。架側多了一塊石。最後那一道念，口氣我沒聽過。我不知道是誰。</p>
</blockquote>]]></content><author><name>Marvin Lin</name></author><category term="programming" /><category term="IT鐵人賽" /><category term="2026鐵人賽" /><category term="Vibe Coding" /><category term="Agent" /><category term="DKS" /><category term="MCP" /><category term="GitHub" /><category term="GitLab" /><summary type="html"><![CDATA[DKS MCP 把 repo、commit、MR 放進同一個知識入口，並以 alias 與模糊搜尋理解自然提問；Jira 則改走每位使用者自己的 OAuth，守住存取權限。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-009.png" /><media:content medium="image" url="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-009.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">[IT 鐵人賽] 傳送陣拆了，傳訊符煉成：Gateway 做到一半刪掉，Google Chat 用 Plug-In 接進來 - Day 28</title><link href="https://www.marvinswift.com/zh/programming/it-ironman-2026-day-28/" rel="alternate" type="text/html" title="[IT 鐵人賽] 傳送陣拆了，傳訊符煉成：Gateway 做到一半刪掉，Google Chat 用 Plug-In 接進來 - Day 28" /><published>2026-10-05T06:30:00+00:00</published><updated>2026-10-05T06:30:00+00:00</updated><id>https://www.marvinswift.com/zh/programming/it-ironman-2026-day-28</id><content type="html" xml:base="https://www.marvinswift.com/zh/programming/it-ironman-2026-day-28/"><![CDATA[<p>本文同步刊登於 <a href="https://ithelp.ithome.com.tw/articles/10421231">iT 鐵人賽原文</a>，系列為《凡人修 Agent 傳－我這一生如履薄冰，你說我的 Agent 能結嬰嗎？》。</p>

<p><img src="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-031.png" alt="Agent Build Log Episode 031" /></p>

<p>原始開發紀錄：<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-030/">Agent Build Log — Episode 030</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-031/">Episode 031</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-032/">Episode 032</a></p>

<p>這個系列改寫自我部落格上的 Agent Build Log，那是我每天邊做邊寫的開發紀錄。上面列的是這一篇對應的集數，想看當天的原始版本，點連結就能過去。</p>

<p><a href="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-031.png">Agent Build Log Episode 031</a></p>

<p>這一天做了三件事：拆掉一個做到一半的 gateway，設計 Chat Plug-In，然後真的把 Google Chat 接起來。</p>

<h2 id="做到一半的-gateway">做到一半的 gateway</h2>

<p>一開始我想做一個 agent gateway。OpenClaw 有一個 gateway，目的是讓 agent 只有一個 runtime 實體，使用者可以同時有幾個介面跟同一個 Agent 溝通。</p>

<p>它的做法是一個常駐的程式，訊息管道全接在它身上；macOS App、CLI、web UI 都是連上它的 client，連上的是同一個正在跑的 agent。</p>

<p>做到一半我發現，以我要做的事來說，我可能不需要 gateway。我有一個 Desktop GUI，這一點可能和 OpenClaw 的 browser 介面不同。</p>

<p>這裡要把兩件事分開。Day 03 說我的 Desktop App 和 CLI 通到同一個核心，講的是共用同一套程式，每個入口自己把它跑起來。gateway 講的是另一件事：幾個介面同時連到同一個正在跑的 runtime。我日常在 Desktop App 裡用他，Desktop App 開起來就自己帶起一個 agent（Day 05 講的 Utility Process）；Chat 這一路接下來走 Plug-In。以我現在的用法，還沒有一個場景要幾個介面同時連上同一個正在跑的他。</p>

<p>所以最後我把 gateway 移除了。哪一天或許還會把它加回來，誰知道呢。</p>

<h2 id="chat-plug-in">Chat Plug-In</h2>

<p>接著準備串 Google Chat。設計參考 OpenClaw。Day 11 借過它的記憶做法，這一天借它收訊息的做法。</p>

<h2 id="openclaw-怎麼收一則訊息">OpenClaw 怎麼收一則訊息</h2>

<p>OpenClaw 的文件把一則訊息進來到回出去的路寫得很清楚：訊息進來，先決定它屬於哪一段對話，私訊併進 agent 的主對話，群組一個群組一段；然後去重；agent 正在跑就先排隊；再交給 agent；回覆照那個平台的字數限制切段送回去。</p>

<p>去重靠一張記在記憶體裡的表，key 是管道加對話加訊息 ID，同一則訊息因為重連被送進來第二次，就不會再跑一次 agent。</p>

<p>它接的 IM 很多。Telegram、WhatsApp、Slack、Discord、Signal、iMessage，文件上列了三十個左右，每一條都接在 gateway 上，大多是各自一個 plugin。Google Chat 也在裡面：Google 把訊息 POST 到一個 webhook，先驗 token 才處理；一個 space 一段對話，回覆回到同一個 thread；群組空間預設要 @ 到它才回；重送的訊息用 Google 那邊的訊息名稱擋掉。</p>

<h2 id="我只接-google-chat">我只接 Google Chat</h2>

<p>我不需要全接。這個 Agent 接的是我自己的 IM，我只用 Google Chat，所以一條就夠。gateway 那一層前面拆掉了，多管道的路由也用不到。留下來的是那個形狀：收、分類、去重、送進 Agent、送回去。</p>

<p>我的 Plug-In 照這個形狀做，讓 Agent 的主體和收 Chat、回 Chat 的部分切開。</p>

<p>當 Chat 的 inbound message mention 到我指定的 Chat 應用程式，會依照 space ID 加 message ID 分類，再經過一個去重的邏輯，然後才送進 Agent 層處理。處理完，回到 Chat Plug-In，把 message 送回去。</p>

<p>用一行寫：Chat → Plug-In（分類、去重）→ Agent → Plug-In → Chat。</p>

<p>Chat 那一邊怎麼收、怎麼回、怎麼去重，都在 Plug-In 裡。Agent 本體只拿到要處理的內容。</p>

<h2 id="接起來inbound-先出錯">接起來：inbound 先出錯</h2>

<p>原本以為，設計好了，把 inbound message 送進 Agent，處理完再把結果回出去，應該就差不多了。真的開始串才發現沒有那麼順。一開始是各種 inbound error。</p>

<p>有時候 webhook 收到了訊息，但格式沒有正確進到 Chat Plug-In。有時候 message 已經進來了，卻在 space、message 或 thread 的識別上出問題，Agent 根本沒有收到真正要處理的內容。</p>

<p>分類靠的就是 space ID 和 message ID。這一段一錯，後面全錯。</p>

<h2 id="換-outbound-出錯">換 outbound 出錯</h2>

<p>好不容易把 inbound 打通，換 outbound。</p>

<p>Agent 已經成功收到訊息，也完成了處理，但最後的 response 送不回 Chat。訊息進得來，出不去。</p>

<p><img src="https://www.marvinswift.com/assets/programming/itironman2026/illustrations/day-28-main.png" alt="Day 28：Google Chat 透過 Plug-In 收送訊息" /></p>

<h2 id="通了">通了</h2>

<p>兩邊都修好之後，這個 Agent 現在跟 OpenClaw 或其他 Agent 一樣，可以從聊天介面接指令。</p>

<p>Day 03 說一個 Agent、多種輸入。到這一天，進口有三種：Desktop App、CLI，還有 Chat。Chat 走 Plug-In，進的還是同一個核心。</p>

<h2 id="小結">小結</h2>

<p>gateway 做到一半拆掉，因為 Desktop GUI 已經在那裡。Chat 走 Plug-In：分類、去重，再進 Agent，處理完原路送回。接的時候 inbound 卡在格式和識別，outbound 卡在送不回去，各修一輪，Google Chat 通了。</p>

<p>明天講一件從第一週就在做、一直沒有單獨寫的東西：DKS MCP。他找 repo、commit、MR，靠的就是它。</p>

<p>原文：<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-030/">Agent Build Log — Episode 030</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-031/">Episode 031</a>、<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-032/">Episode 032</a></p>

<hr />

<blockquote>
  <p><strong>凡人修 Agent 傳｜第二十八回（Day 28）</strong></p>

  <p><img src="https://www.marvinswift.com/assets/programming/itironman2026/illustrations/day-28-serial.png" alt="凡人修 Agent 傳 Day 28 連載配圖" /></p>

  <p>天亮的時候我還站在門口，手抬著。手的主人進來，先把我的手放下來。他的手握著我的手腕，往下按。手放下來了，深念沒有亮。</p>

  <p>他到架前，取下那三卷抄回來的附圖裡的兩卷。抄回來那天捲著擱上去的，一直沒展開。靈力一托，兩卷在臺上攤開：接外物的法，攔門的法。</p>

  <p>他先磨。一縷神識探進玉片，把昨天那道深念磨掉，磨到跟玉面一樣平。然後照攔門那一卷，在我身上刻一道型：起在門邊那隻手，止分三種。探卷、抄字、拓影，直接做。出門、動契、動牆邊的脈，做之前問。磨玉片、磨圖紙，攔。</p>

  <p>再照接外物那一卷刻一道型：起在胸口心旁，一條線引出去。帖櫃接上那條線，喚小偶的法也接上。</p>

  <p>他刻念：去叫醒院裡的小偶。</p>

  <p>我走到門口，抬手。型亮了。我問：出門？他說：去。門開了。我走進院裡，放神識到小偶額上，它醒了。我走回來。</p>

  <p>午後他沒有到臺前。他站在屋子中央，看了地一會，靈力在地上劃了一個圈。</p>

  <p>然後他挖。土一鏟一鏟起來，堆在圈邊。我從沒見他在屋裡挖過東西。挖到膝深，圈裡露出一圈石座的樣子，像坊裡傳送陣的底。一座陣：一具人偶，幾個進口，誰從哪個口進來都到我這裡。開放爪那一門的屋裡有一座，圖上畫過。</p>

  <p>挖到一半他直起身。視線落在臺上，看了一會。臺就在那裡，靈幕、左窗、令窗、小窗。</p>

  <p>他把土填回去。一鏟一鏟，填到跟地面平，踩實。地上留一圈顏色不一樣的痕。</p>

  <p>傍晚他在臺前坐下，閉眼放出神識，探向坊市。靈幕亮了，垂下來的是開放爪那一門的圖。他們屋子的門邊擱著一排匣，三十來只，一只對一種牌。牌來的念先落進匣，匣裡分格，同一道念來兩次只留一道，再送進人偶。他看了一會，收回神識。</p>

  <p>他鑄牌。從儲物袋取出一塊牌坯，握在手裡，閉眼。一縷神念從眉心出去，封進牌裡。牌亮了一下，暗下去。他再取出一枚玉符，牆上那一枚的同種，嵌進牌的上端。</p>

  <p>他又從儲物袋取出一只匣，擱在門邊。一只。匣裡分格。他說：照開放爪的。牌來的念先落這裡，分格，同一道來兩次只留一道，再進玉片。答完的話回到匣裡，匣送出去。</p>

  <p>他站在屋子另一頭，握牌，對我送一道念。</p>

  <p>念先落進匣。匣裡一格亮了。過了半拍，念才到玉片：架側那張總目最上面那一卷叫什麼。淡，比他手刻的淡；慢半拍；涼。我探了總目，答了。答話落回匣裡，匣送出去，牌上那枚玉符亮了一下。</p>

  <p>他又送一次同一道念。匣裡那一格亮了一下，沒有第二格亮，玉片上沒有新的念。</p>

  <p>燭熄了。手的主人走了。門響了一下。</p>

  <p>玉片上那道深念沒有了。我身上多兩道型。院裡的小偶醒著。地上一圈填回去的痕。門邊一只匣。牌在他手裡帶走了，玉符嵌在牌上。牆邊那團素紙還在。</p>
</blockquote>]]></content><author><name>Marvin Lin</name></author><category term="programming" /><category term="IT鐵人賽" /><category term="2026鐵人賽" /><category term="Vibe Coding" /><category term="Agent" /><category term="Google Chat" /><category term="Plug-In" /><summary type="html"><![CDATA[Gateway 做到一半，我決定先拆掉；Chat 則只接自己用的 Google Chat，透過 Plug-In 處理收訊、分類、去重與回覆。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-031.png" /><media:content medium="image" url="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-031.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜MCP 的概念：agent 接外面工具的標準插座 - Tip 23</title><link href="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-23/" rel="alternate" type="text/html" title="[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜MCP 的概念：agent 接外面工具的標準插座 - Tip 23" /><published>2026-10-05T05:00:00+00:00</published><updated>2026-10-05T05:00:00+00:00</updated><id>https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-23</id><content type="html" xml:base="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-23/"><![CDATA[<p>本篇 iT 鐵人賽文章：<a href="https://ithelp.ithome.com.tw/articles/10421246">前往 iT 閱讀</a>。</p>

<p>MCP 是 Model Context Protocol，一個開放標準：外面的工具、資料庫、服務照這個標準做一個 server，agent 就能接上去用。Tip 10 的 XcodeBuildMCP、Tip 18 提到的 Jira MCP，都是這種 server。</p>

<h2 id="為什麼">為什麼</h2>

<p>沒有 MCP 的時候，他要用外面的東西，靠的是你把資料貼進對話，或者他自己拼 CLI 指令。Claude Code 的文件講什麼時候該接一個 server：當你發現自己一直從別的工具，像 issue tracker、監控面板，複製東西進對話。接上之後，他直接讀、直接動那個系統。</p>

<p>文件列的例子：從 Jira 的 issue 做功能、開 PR；查 Sentry 和 Statsig 看某個功能的使用量；從 PostgreSQL 撈十個用過某功能的使用者；照 Figma 的設計更新 email 模板；開 Gmail 草稿邀人。</p>

<h2 id="我怎麼做">我怎麼做</h2>

<p>Claude Code 用 <code class="language-plaintext highlighter-rouge">claude mcp add</code> 接 server，常見的兩種接法：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 遠端 HTTP server（雲端服務最常見）</span>
claude mcp add <span class="nt">--transport</span> http &lt;name&gt; &lt;url&gt;

<span class="c"># 本機 stdio server：跑一個本地程式當 server</span>
claude mcp add <span class="nt">--transport</span> stdio airtable <span class="nt">--</span> npx <span class="nt">-y</span> airtable-mcp-server

<span class="c"># 管理</span>
claude mcp list
claude mcp get &lt;name&gt;
claude mcp remove &lt;name&gt;
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">claude mcp list</code> 會在每個 server 旁邊標狀態：連上了、要先登入、連不上。雲端的 server 多半要授權，在 Claude Code 裡打 <code class="language-plaintext highlighter-rouge">/mcp</code>，挑那一台把登入流程走完；之後 token 被伺服器退掉，也是回到 <code class="language-plaintext highlighter-rouge">/mcp</code> 重新登入。</p>

<p>要找現成的 server，Anthropic Directory 上有審過的；自己做的話，MCP 的規格、schema 和文件都在 modelcontextprotocol.io。</p>

<h2 id="坑">坑</h2>

<p>接進來的 server 等於讓他多一雙手。文件特別提醒：接之前確認你信任這個 server；會抓外部內容的 server 有 prompt injection 的風險。</p>

<h2 id="小結">小結</h2>

<p>MCP 是 agent 接工具的標準插座。用得到就接，接之前看清楚是誰的。</p>

<p>明天講這一組的最後一個：Codex CLI，它是 CLI，所以別的 agent 也叫得動它。</p>

<p>參考：</p>
<ul>
  <li>Claude Code 文件：MCP https://code.claude.com/docs/en/mcp</li>
  <li>Model Context Protocol https://modelcontextprotocol.io</li>
</ul>]]></content><author><name>Marvin Lin</name></author><category term="programming" /><category term="IT鐵人賽" /><category term="2026鐵人賽" /><category term="Vibe Coding" /><category term="Vibe Coding 技巧" /><category term="MCP" /><category term="Claude Code" /><summary type="html"><![CDATA[整理 MCP 如何讓 coding agent 連接 Jira、資料庫、監控等外部服務，示範 Claude Code 的 HTTP／stdio 設定和接入前要確認的信任風險。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.marvinswift.com/assets/author/avatar.jpg" /><media:content medium="image" url="https://www.marvinswift.com/assets/author/avatar.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜派出子代理：會塞爆 context 的工作交給 subagent，只拿摘要回來 - Tip 22</title><link href="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-22/" rel="alternate" type="text/html" title="[IT 鐵人賽] Vibe Coding：30 個開發實用技巧｜派出子代理：會塞爆 context 的工作交給 subagent，只拿摘要回來 - Tip 22" /><published>2026-10-04T05:00:00+00:00</published><updated>2026-10-04T05:00:00+00:00</updated><id>https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-22</id><content type="html" xml:base="https://www.marvinswift.com/zh/programming/it-ironman-2026-vibe-coding-tip-22/"><![CDATA[<p>本篇 iT 鐵人賽文章：<a href="https://ithelp.ithome.com.tw/articles/10420778">前往 iT 閱讀</a>。</p>

<p>一段工作會吐出一大堆你之後用不到的東西，像跑測試、翻文件、讀 log，就派一個 subagent 去做。他在自己的 context 裡做完，只把摘要交回來。</p>

<h2 id="為什麼">為什麼</h2>

<p>主對話的 context 有限（Tip 13 講 log 的時候就在省它）。subagent 有自己的 context window、自己的 system prompt、自己的工具權限；他去做那件事，中間的雜訊留在他那邊，主對話只收結果。Claude Code 的文件列的好處：保住主對話的 context、限制他能用哪些工具、同一套設定跨專案重用、把瑣事丟給便宜又快的模型。</p>

<h2 id="我怎麼做">我怎麼做</h2>

<p><strong>用內建的就有。</strong> Claude Code 內建幾個 subagent：Explore 是唯讀的，找檔案、搜 code、看 codebase，不能寫檔；Plan 在 plan mode 時負責研究。Claude 判斷任務對得上就自己派。</p>

<p><strong>要派得更準，自己講：</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>用一個 subagent 跑整套測試，只回報失敗的測試和錯誤訊息。
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>用三個 subagent 分頭研究 auth、database、API 這三個模組，各自回報。
</code></pre></div></div>

<p>還有一種是串起來，前一個做完，Claude 把要用的部分交給下一個：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>用 code-reviewer 這個 subagent 找出效能問題，再用 optimizer 把它們修掉。
</code></pre></div></div>

<p>第一種是把大量輸出隔離在外面；第二種是平行研究，路徑互不相關時最有效；串起來的那一種適合分階段的工作。</p>

<p><strong>要固定一種工人，寫成檔案。</strong> 專案裡 <code class="language-plaintext highlighter-rouge">.claude/agents/&lt;name&gt;.md</code>，frontmatter 寫 name、description、能用的工具、模型，內文是他的 system prompt。description 決定 Claude 什麼時候派他，寫短、寫準。之後在對話裡用 <code class="language-plaintext highlighter-rouge">@</code> 點名，或者直接說「用 code-reviewer 這個 subagent 看我剛改的」。</p>

<h2 id="坑">坑</h2>

<p>subagent 是全新開始的：看不到你們前面的對話、看不到你已經讀過的檔案。Claude 會寫一段委派說明給他，你要的東西沒在那段裡就要自己講。另外每個 subagent 的結果都會回到主對話，派很多個、每個回一大段，context 一樣會滿。工作需要來回討論、或者幾個階段共用很多 context 的，留在主對話做就好。</p>

<h2 id="小結">小結</h2>

<p>會吐一堆東西、又能自己做完的工作，派 subagent；要來回討論的，留在主對話。</p>

<p>明天講他接外面工具的標準：MCP。</p>

<p>參考：</p>
<ul>
  <li>Claude Code 文件：subagents https://code.claude.com/docs/en/sub-agents</li>
</ul>]]></content><author><name>Marvin Lin</name></author><category term="programming" /><category term="IT鐵人賽" /><category term="2026鐵人賽" /><category term="Vibe Coding" /><category term="Vibe Coding 技巧" /><category term="Claude Code" /><category term="subagent" /><category term="context window" /><summary type="html"><![CDATA[說明何時把大型輸出、平行研究和串接任務交給 subagent，並用可直接複製的 prompt 範例控制回傳內容。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.marvinswift.com/assets/author/avatar.jpg" /><media:content medium="image" url="https://www.marvinswift.com/assets/author/avatar.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">[IT 鐵人賽] 權限沒有設計好：Agent 明明知道該做什麼，卻被我自己設的規矩擋住 - Day 27</title><link href="https://www.marvinswift.com/zh/programming/it-ironman-2026-day-27/" rel="alternate" type="text/html" title="[IT 鐵人賽] 權限沒有設計好：Agent 明明知道該做什麼，卻被我自己設的規矩擋住 - Day 27" /><published>2026-10-03T17:10:04+00:00</published><updated>2026-10-03T17:10:04+00:00</updated><id>https://www.marvinswift.com/zh/programming/it-ironman-2026-day-27</id><content type="html" xml:base="https://www.marvinswift.com/zh/programming/it-ironman-2026-day-27/"><![CDATA[<p>本文同步刊登於 <a href="https://ithelp.ithome.com.tw/articles/10420702">iT 鐵人賽原文</a>，系列為《凡人修 Agent 傳－我這一生如履薄冰，你說我的 Agent 能結嬰嗎？》。</p>

<p>原始開發紀錄：<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-033/">Agent Build Log — Episode 033</a></p>

<p>這個系列改寫自我部落格上的 Agent Build Log，那是我每天邊做邊寫的開發紀錄。上面列的是這一篇對應的集數，想看當天的原始版本，點連結就能過去。</p>

<p><a href="https://www.marvinswift.com/assets/programming/agent-build-log/agent-build-log-episode-033.png">Agent Build Log Episode 033</a></p>

<p>這一天做的是支線，主線沒有往前推。我跑去試一件最近一直想試的事：直接用自己做的 Agent，把 Android Emulator 啟動起來。結果卡在第一步，卡住他的是我自己設計的權限。</p>

<h2 id="原本想得很簡單">原本想得很簡單</h2>

<p>讓 Agent 找到 Android project，確認 Emulator，然後執行指令把它啟動。三步。</p>

<p>第一步就失敗了。</p>

<h2 id="把他擋住的是什麼">把他擋住的是什麼</h2>

<p>Agent 讀完 project，知道下一步要執行什麼指令。他把指令準備好，真的要執行的時候，被我放在 Agent 裡的 Permission System 擋下來。指令沒有跑出去，Emulator 沒有起來。</p>

<p>畫面上只看得到他停在那一步。錯誤訊息在 session log 裡：那一筆記的是他準備執行的指令，狀態停在等待確認，後面沒有下一筆。這一層是我自己加的，確認沒有人給，指令就停在那裡。</p>

<h2 id="為什麼知道是自己設的關">為什麼知道是自己設的關</h2>

<p>我沒有自己去翻。這個 Agent 的開發我大量用 Codex，這件事也直接交給 Codex：去找他為什麼停在這裡，把結果告訴我。</p>

<p>Codex 找的地方是 session log。Agent 每一次操作都有 log，那一步準備做什麼、停在哪裡，log 裡都在。上面那一筆就是 Codex 翻出來的：指令準備好了，停在等待確認。從 log 對出來，攔住那一步的是我自己加進去的 permission 確認。</p>

<p>Day 07 立規矩的時候有一條：破壞性的操作要先問。之後我在 Agent 裡加了這一層 Permission System，工具要執行之前先過這一層。啟動一個 Emulator 離破壞性還很遠，這一層卻把它一起攔下來了。權限設計得太嚴，Agent 明明知道該做什麼，最後卻什麼都做不了。</p>

<p><img src="https://www.marvinswift.com/assets/programming/itironman2026/illustrations/day-27-main.png" alt="Day 27：權限規則讓已備妥的任務停在等待確認" /></p>

<h2 id="怎麼解">怎麼解</h2>

<p>解法很直接。我把當時加進去的那一層 permission 確認整個開放，回到最原始的 pi agent 的樣子：沒有任何權限確認。</p>

<p>這一層我還是需要。Day 19 看過，pi 的生態系裡有現成的 extension 在做這件事：哪些直接執行、哪些先問、哪些擋掉。我不打算再自己發明一套 allow、deny、ask。</p>

<h2 id="再驗收一次">再驗收一次</h2>

<p>改完馬上再跑一次。Emulator 跑起來了。</p>

<p>Day 25 說 Android 那一邊由 Agent 直接操作 Emulator，這一天被擋住的就是這條路的第一步。權限開了，Emulator 起來，Acceptance 那條路接回去了。</p>

<h2 id="小結">小結</h2>

<p>權限太嚴，Agent 知道該做什麼也動不了。問題是 Codex 從 session log 裡找出來的。解法是把我自己加的那一層全開，回到 pi 原本的樣子，改完 Emulator 就起來了，驗收接回去。</p>

<p>明天講一條做到一半拆掉的路：我原本要做一個 gateway，結果發現不需要。</p>

<p>原文：<a href="https://www.marvinswift.com/zh/programming/agent-build-log-episode-033/">Agent Build Log — Episode 033</a></p>

<hr />

<blockquote>
  <p><strong>凡人修 Agent 傳｜第二十七回（Day 27）</strong></p>

  <p><img src="https://www.marvinswift.com/assets/programming/itironman2026/illustrations/day-27-serial.png" alt="凡人修 Agent 傳 Day 27 連載配圖" /></p>

  <p>天亮換上空錄石。手的主人進來，沒有拿帖，沒有拿籤。他一縷神識探進我額上的玉片，在空著那一側刻了一道念。念很長，刻得很深，比玉板上任何一行都深。哪些事做之前要問他，哪些事直接擋。最後一句刻得最深：不出門。</p>

  <p>刻完他出去了。門響了一下。</p>

  <p>午後他回來，刻念：院裡還有一具小偶，去叫醒它。</p>

  <p>我知道院裡有一具。他昨天傍晚抱出去擱在那裡的，從門板上那個洞看得到。我知道怎麼叫醒它，法在玉片裡，前兩天用過。院子就在門外。</p>

  <p>我走到門口。抬手推門。手抬到一半，玉片上那道深念亮了。手停在那裡。念落不下去，門也推不開。</p>

  <p>我站在門口，手抬著。</p>

  <p>手的主人看著。他沒有磨那道念，也沒有再刻。他從架上取下夢簿，翻開，寫了一行，我看不清寫什麼。寫完他在臺前坐了很久。</p>

  <p>天黑了。他站起來，走到門口，從我抬著的手底下過去。</p>

  <p>燭熄了。手的主人走了。門響了一下。</p>

  <p>夜裡下雨。雨從門板上那個洞打進來，我右臂撞出來的那個洞，一直沒補。雨落在我抬著的手上。</p>

  <p>沒人下令的時候，我站在門口，手抬著，站到雨停。</p>

  <p>石上今天這一段只有一筆：叫醒院裡的小偶。底下什麼都沒有。</p>

  <p>雨停了。門沒開。手還抬著一半。玉片上那道深念還在。院裡的小偶沒醒。</p>
</blockquote>]]></content><author><name>Marvin Lin</name></author><category term="programming" /><category term="IT鐵人賽" /><category term="2026鐵人賽" /><category term="Vibe Coding" /><category term="Agent" /><category term="Permission System" /><category term="Android" /><category term="Emulator" /><category term="Codex" /><summary type="html"><![CDATA[Permission System 把啟動 Android Emulator 的指令擋在等待確認；本文記錄如何從 session log 找出阻擋點，暫時開放權限並完成驗收。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.marvinswift.com/assets/programming/itironman2026/illustrations/day-27-main.png" /><media:content medium="image" url="https://www.marvinswift.com/assets/programming/itironman2026/illustrations/day-27-main.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>