agent-trellis 0.12.0 → 0.14.0

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.
package/dist/probes/pi.js CHANGED
@@ -5,16 +5,26 @@
5
5
  * and `dist/core/resource-loader.js` (`loadContextFileFromDir(resolvedAgentDir)`),
6
6
  * not by assumption. See design.md D5 (openspec/changes/trellis-doctor-p0).
7
7
  *
8
- * pi has no native MCP client (docs/research.md) — nothing to read or
9
- * probe, so `mcpServers` is always empty here. That's real information
10
- * (this is why P4 has to write a runtime bridge extension instead of
11
- * generating config), not an omission.
8
+ * pi >= 1.1.0 has a native MCP client (`~/.pi/agent/mcp.json`), which the
9
+ * adapter manages (trellis-pi-native-mcp-migration); the probe still
10
+ * reports `mcpServers: []` because server health is the runtime's job,
11
+ * not the probe's. `probePiVersion` is split out so the adapter's
12
+ * native-vs-bridge version gate shares exactly this exec and tests can
13
+ * inject a seam.
12
14
  */
13
15
  import { execFileSync } from "node:child_process";
14
16
  import { existsSync } from "node:fs";
15
17
  import { homedir } from "node:os";
16
18
  import { join } from "node:path";
17
19
  import { pathRef, scanSkillRoot } from "../lib/probeCommon.js";
20
+ export function probePiVersion() {
21
+ try {
22
+ return execFileSync("pi", ["--version"], { encoding: "utf-8", timeout: 5_000 }).trim();
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
18
28
  /** Priority order pi itself checks (`loadContextFileFromDir`), first match wins. */
19
29
  const INSTRUCTIONS_CANDIDATES = ["AGENTS.override.md", "AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"];
20
30
  export async function probe(homeDir = homedir()) {
@@ -25,13 +35,8 @@ export async function probe(homeDir = homedir()) {
25
35
  if (!present) {
26
36
  return { agent: "pi", present: false, skillRoots: [], mcpServers: [], diagnostics: [] };
27
37
  }
28
- let version;
29
- try {
30
- version = execFileSync("pi", ["--version"], { encoding: "utf-8", timeout: 5_000 }).trim();
31
- }
32
- catch {
33
- // config exists without the binary on PATH — unusual, still probeable
34
- }
38
+ // config can exist without the binary on PATH — unusual, still probeable
39
+ const version = probePiVersion();
35
40
  const skillRoot = scanSkillRoot(skillsPath);
36
41
  let instructionsFile;
37
42
  for (const candidate of INSTRUCTIONS_CANDIDATES) {
@@ -291,6 +291,28 @@ that advertises `--prompt`, `--resume`, and `--output-format`. A desktop-only
291
291
  profile can still receive managed configuration; Trellis never launches the
292
292
  desktop application's private helper or bundled runtime path.
293
293
 
294
+ ## pi native MCP (pi ≥ 1.1.0)
295
+
296
+ pi 1.1.0 added a native MCP client, and Trellis delivers to it the same way
297
+ as every other JSON-config agent: `trellis mcp sync` merges Trellis-managed
298
+ entries into `~/.pi/agent/mcp.json` — the single `trellis` edge entry in
299
+ gateway mode, plus a bare `{url}` entry for each agent-owned OAuth server.
300
+ Servers you added yourself (anything Trellis never wrote) are preserved
301
+ untouched, and the legacy bridge extension (`~/.pi/agent/extensions/
302
+ trellis-mcp-bridge.js`) is removed in the same run, after the config write.
303
+
304
+ Two pi-specific behaviors to know:
305
+
306
+ - pi does not watch `mcp.json`: after a sync that changed it, restart pi or
307
+ run `/reload` in an open session (sync prints this hint).
308
+ - Agent-owned OAuth servers (e.g. `figma`) are authorized by pi itself:
309
+ run `pi mcp login <name>` once; the grant lives in pi's own token store
310
+ (`~/.pi/agent/mcp-auth.json`), and `trellis.mcp.status` reports them as
311
+ `agent-managed`.
312
+
313
+ On pi older than 1.1.0 nothing changes: the bridge extension remains the
314
+ delivery mechanism and `mcp.json` is never written.
315
+
294
316
  ## Trellis Runtime Skill
295
317
 
296
318
  `trellis-runtime` is the built-in operating guide that Trellis installs into
@@ -907,6 +929,15 @@ An Agent can call the read-only `trellis.mcp.status` Runtime tool to explain
907
929
  this partial state to the user. It reports unavailable or authorization-
908
930
  required servers and gives the next safe action, such as `trellis mcp auth
909
931
  figma`, without exposing tokens or opening a browser from the Agent process.
932
+ Each connected upstream also carries a structured `toolCount`, and an `oauth`
933
+ section reports credential health with explicit ownership: Trellis-owned
934
+ OAuth servers (which connect through the gateway on a Trellis-store token)
935
+ report `valid` / `no-token` / `expired-renewable` / `needs-auth` /
936
+ `unreadable` with `expiresAt` when known; agent-owned servers report
937
+ `agent-managed`, since their authorization lives in the Agent's own client
938
+ and is never inferred from Trellis's store. The classification is read-only
939
+ and shared with `trellis mcp renew` and `trellis doctor`, so the three can
940
+ never disagree.
910
941
 
911
942
  This command only applies to `http`/`sse` servers. stdio servers get
912
943
  their credentials from `env`/`env_aliases` as described above; running
@@ -49,11 +49,30 @@ so a cancelled grant cannot overwrite a concurrent refresh.
49
49
  The gateway can silently refresh its own hosted credentials; it never opens a
50
50
  browser. Agents with native connections continue using their own flows.
51
51
 
52
+ ## Browser callback feedback
53
+
54
+ The local callback page shows a compact Trellis/service label, one status message,
55
+ one short instruction and a close action, with Chinese or English copy based on
56
+ the browser's preferred language. Receiving an
57
+ authorization response is an intermediate step: validation, token exchange and
58
+ credential storage still determine the final result shown in Trellis or the
59
+ terminal. Cancellation, provider errors and incomplete responses have separate
60
+ messages and guidance for starting again.
61
+
62
+ The page uses no external assets and does not display authorization parameters
63
+ or raw provider descriptions. Responses disable caching and referrer disclosure;
64
+ when JavaScript is available, the page also removes the query and fragment from
65
+ the current browser history entry. The close action offers manual-close guidance
66
+ when a browser blocks it, and manual guidance remains visible without JavaScript.
67
+
52
68
  ## Verification
53
69
 
54
70
  Run core tests and typecheck, GUI sidecar tests/typecheck and GUI smoke tests.
55
71
  `npm run test:oauth-browser --workspace @trellis/gui` additionally uses installed
56
- Google Chrome headlessly with a local authorization-server fixture. It exercises
57
- real browser CORS, PKCE callback, progress, cancellation, retry and owner
58
- confirmation without contacting a real provider or using personal credentials.
72
+ Google Chrome headlessly with a local authorization-server fixture. Set
73
+ `TRELLIS_OAUTH_BROWSER_EXECUTABLE` to use a compatible Chromium executable instead.
74
+ It exercises real browser CORS, PKCE callback, progress, cancellation, retry,
75
+ owner confirmation and callback feedback without contacting a real provider or
76
+ using personal credentials. Set `TRELLIS_OAUTH_SCREENSHOTS` to an output directory
77
+ to save callback desktop/mobile previews during that run.
59
78
  Desktop packaging and installed-app acceptance happen after these checks.
@@ -0,0 +1,89 @@
1
+ # Scheduled OAuth renewal — `trellis mcp renew`
2
+
3
+ The gateway/runtime edge already refreshes OAuth tokens silently before
4
+ every upstream call. `trellis mcp renew` moves that renewal *earlier*: run
5
+ on a schedule, it refreshes whatever is expiring and reports — with a
6
+ non-zero exit code — anything that needs a human, before an Agent session
7
+ discovers it as a 401.
8
+
9
+ ```
10
+ trellis mcp renew # refresh what's expiring, report health
11
+ trellis mcp renew --dry-run # report only; no token-endpoint call
12
+ trellis mcp renew --json # machine-readable (no credential material)
13
+ ```
14
+
15
+ Scope is canonical OAuth servers with an explicit `owner: trellis` —
16
+ classification is explicit (mcp-auth-routing-policy), and ownership decides
17
+ whose problem the credential is (trellis-desktop-oauth-ownership): an
18
+ agent-owned server (the default, e.g. scalar `auth: oauth`) is authorized by
19
+ the Agent's own client, never reads Trellis's token store, and is neither
20
+ renewed nor reported here — for pi ≥ 1.1.0 that client-side authorization is
21
+ `pi mcp login <name>`, stored in pi's own `~/.pi/agent/mcp-auth.json`. The command never opens a browser and never
22
+ performs an initial grant; that remains `trellis mcp auth <name>`
23
+ (mcp-gateway-hosting design.md D9). Exit code: `0` when every Trellis-owned
24
+ server is valid or refreshed, `1` when any needs `trellis mcp auth`, has
25
+ never been authorized, or has an unreadable credential file.
26
+
27
+ `trellis doctor` reports the same per-server health read-only (no network,
28
+ no writes) and surfaces an `oauth-expired` finding — contributing to a
29
+ non-zero doctor exit — for credentials that are expired or absent.
30
+
31
+ ## launchd setup (macOS)
32
+
33
+ Trellis has no daemon; periodic renewal belongs to the platform
34
+ scheduler. Save this as `~/Library/LaunchAgents/com.trellis.mcp-renew.plist`,
35
+ adjusting the `trellis` path (`which trellis`):
36
+
37
+ ```xml
38
+ <?xml version="1.0" encoding="UTF-8"?>
39
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
40
+ <plist version="1.0">
41
+ <dict>
42
+ <key>Label</key>
43
+ <string>com.trellis.mcp-renew</string>
44
+ <key>ProgramArguments</key>
45
+ <array>
46
+ <string>/usr/local/bin/trellis</string>
47
+ <string>mcp</string>
48
+ <string>renew</string>
49
+ <string>--json</string>
50
+ </array>
51
+ <key>StartInterval</key>
52
+ <integer>21600</integer>
53
+ <key>ThrottleInterval</key>
54
+ <integer>600</integer>
55
+ <key>StandardOutPath</key>
56
+ <string>/tmp/trellis-mcp-renew.log</string>
57
+ <key>StandardErrorPath</key>
58
+ <string>/tmp/trellis-mcp-renew.log</string>
59
+ </dict>
60
+ </plist>
61
+ ```
62
+
63
+ Then:
64
+
65
+ ```
66
+ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.trellis.mcp-renew.plist
67
+ launchctl kickstart gui/$(id -u)/com.trellis.mcp-renew # run once now
68
+ launchctl bootout gui/$(id -u)/com.trellis.mcp-renew # uninstall
69
+ ```
70
+
71
+ Why these knobs:
72
+
73
+ - **`ThrottleInterval`** (design.md D3) caps how close together runs may
74
+ be. A machine waking from sleep with a launchd backlog runs once, not N
75
+ times — refresh-endpoint hammering is the failure mode that gets refresh
76
+ tokens revoked by rate-limiting providers, which is worse than the
77
+ disease this job exists to prevent.
78
+ - **`StartInterval` of 6h** is comfortably inside typical refresh-token
79
+ lifetimes (days to months) while keeping token-endpoint traffic near
80
+ zero: a valid token is a no-op *read*, so most runs make no network
81
+ calls at all.
82
+ - **Overlapping runs are safe**: refresh is serialized by the per-server
83
+ lock (`src/lib/oauth/lock.ts`). A second concurrent run waits, then
84
+ reads the token the first one just wrote.
85
+ - **`--json` to a log** gives you a parseable trail. A `1` exit means
86
+ "something needs `trellis mcp auth <name>`" — the JSON names which.
87
+
88
+ Check on demand: `trellis doctor` shows the same per-server credential
89
+ health.
package/docs/research.md CHANGED
@@ -236,10 +236,13 @@ Reverse-engineered directly from the installed binary
236
236
  - pi **natively discovers** `AGENTS.override.md`, `AGENTS.md`, `CLAUDE.md`
237
237
  (in that priority order) — instructions and `SKILL.md`-format skills need
238
238
  no adapter work at all.
239
- - pi has **no native MCP client**. The only `mcpServers` string in its bundle
240
- belongs to the Google Gen AI SDK's Vertex tool-schema translator, which
241
- explicitly throws `"mcpServers parameter is not supported"` — unrelated to
242
- MCP proper.
239
+ - pi **gained a native MCP client in 1.1.0** (`pi mcp add/list/login`,
240
+ `~/.pi/agent/mcp.json`, stdio + streamable HTTP, `${VAR}` expansion in
241
+ `env`/`headers`, OAuth into its own `mcp-auth.json` store) — Trellis delivers
242
+ to it natively (trellis-pi-native-mcp-migration). Before 1.1.0 the only
243
+ `mcpServers` string in its bundle belonged to the Google Gen AI SDK's Vertex
244
+ tool-schema translator, which explicitly throws `"mcpServers parameter is
245
+ not supported"` — that era is why the bridge extension below exists.
243
246
  - pi's capability surface is **extensions**: JS modules loaded via
244
247
  `--extension`/discovery, with a real API (`createExtension`,
245
248
  `registerTool`). Giving pi access to the same MCP servers every other agent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-trellis",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "A unified Code Agent Runtime for Skills, MCP, Memory, and shared instructions across Claude Code, Codex, Kiro, pi, Kimi Code, and ZCode.",
5
5
  "license": "MIT",
6
6
  "author": "Paul Leo",