@phi-code-admin/phi-code 0.86.0 → 0.88.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/docs/adr/0001-phase-contract.md +57 -0
  3. package/docs/adr/0002-independent-review.md +47 -0
  4. package/docs/fork-policy.md +18 -0
  5. package/extensions/phi/agents.ts +7 -4
  6. package/extensions/phi/benchmark.ts +129 -49
  7. package/extensions/phi/browser.ts +10 -37
  8. package/extensions/phi/btw/btw.ts +1 -7
  9. package/extensions/phi/chrome/index.ts +1283 -741
  10. package/extensions/phi/commit.ts +9 -14
  11. package/extensions/phi/goal/index.ts +10 -33
  12. package/extensions/phi/init.ts +37 -47
  13. package/extensions/phi/keys.ts +2 -7
  14. package/extensions/phi/mcp/callback-server.ts +162 -165
  15. package/extensions/phi/mcp/config.ts +122 -136
  16. package/extensions/phi/mcp/errors.ts +18 -23
  17. package/extensions/phi/mcp/index.ts +322 -355
  18. package/extensions/phi/mcp/oauth-provider.ts +289 -289
  19. package/extensions/phi/mcp/server-manager.ts +390 -413
  20. package/extensions/phi/mcp/tool-bridge.ts +381 -415
  21. package/extensions/phi/memory.ts +2 -2
  22. package/extensions/phi/models.ts +27 -26
  23. package/extensions/phi/orchestrator.ts +451 -237
  24. package/extensions/phi/productivity.ts +4 -2
  25. package/extensions/phi/providers/agent-def.ts +1 -1
  26. package/extensions/phi/providers/alibaba.ts +56 -7
  27. package/extensions/phi/providers/context-window.ts +4 -1
  28. package/extensions/phi/providers/live-models.ts +5 -20
  29. package/extensions/phi/providers/orchestrator-helpers.ts +31 -5
  30. package/extensions/phi/providers/phase-machine.ts +283 -0
  31. package/extensions/phi/setup.ts +196 -169
  32. package/extensions/phi/skill-loader.ts +14 -17
  33. package/extensions/phi/smart-router.ts +6 -6
  34. package/extensions/phi/web-search.ts +90 -50
  35. package/package.json +1 -1
@@ -18,104 +18,104 @@ import { McpError } from "./errors.js";
18
18
  // ─── Zod Schemas ──────────────────────────────────────────────────────────────
19
19
 
20
20
  const AuthConfigSchema = z.object({
21
- /** Auth type. Currently only "oauth" is supported. Default: "oauth". */
22
- type: z.enum(["oauth"]).default("oauth"),
23
- /**
24
- * Callback URL for the OAuth redirect.
25
- * Default: auto-detected local callback server.
26
- */
27
- redirectUrl: z.string().optional(),
28
- /**
29
- * Optional scope to request during authorization.
30
- */
31
- scope: z.string().optional(),
32
- /**
33
- * Pre-registered client_id (skip dynamic client registration).
34
- */
35
- clientId: z.string().optional(),
36
- /**
37
- * Pre-registered client_secret.
38
- */
39
- clientSecret: z.string().optional(),
21
+ /** Auth type. Currently only "oauth" is supported. Default: "oauth". */
22
+ type: z.enum(["oauth"]).default("oauth"),
23
+ /**
24
+ * Callback URL for the OAuth redirect.
25
+ * Default: auto-detected local callback server.
26
+ */
27
+ redirectUrl: z.string().optional(),
28
+ /**
29
+ * Optional scope to request during authorization.
30
+ */
31
+ scope: z.string().optional(),
32
+ /**
33
+ * Pre-registered client_id (skip dynamic client registration).
34
+ */
35
+ clientId: z.string().optional(),
36
+ /**
37
+ * Pre-registered client_secret.
38
+ */
39
+ clientSecret: z.string().optional(),
40
40
  });
41
41
 
42
42
  const ServerConfigSchema = z
43
- .object({
44
- /** Executable to spawn (e.g. "npx", "node", "uvx"). Required for stdio. */
45
- command: z.string().optional(),
46
- /** Arguments passed to command. */
47
- args: z.array(z.string()).default([]),
48
- /**
49
- * Extra environment variables passed to the child process as literals.
50
- * These merge with process.env; project env overrides parent env.
51
- * No ${VAR} interpolation — set vars in your shell environment instead.
52
- */
53
- env: z.record(z.string()).optional(),
54
- /** Transport protocol. Default: "stdio". */
55
- transport: z.enum(["stdio", "streamable-http", "sse"]).default("stdio"),
56
- /**
57
- * URL for streamable-http or sse transports.
58
- * Must be a valid URL (e.g. "https://my-mcp-server.example.com/mcp").
59
- */
60
- url: z.string().url().optional(),
61
- /**
62
- * Static HTTP headers to include with every request (streamable-http / sse only).
63
- * Useful for API-key-based auth (e.g. { "Authorization": "Bearer <key>" }).
64
- * For OAuth2, use the "auth" field instead.
65
- */
66
- headers: z.record(z.string()).optional(),
67
- /**
68
- * OAuth2 configuration for servers that require authorization.
69
- * When set, the transport will use the SDK's OAuth flow (discovery,
70
- * dynamic client registration, PKCE, token refresh).
71
- * Only applies to streamable-http and sse transports.
72
- */
73
- auth: AuthConfigSchema.optional(),
74
- /**
75
- * "eager" — start at session_start.
76
- * "lazy" — start manually via /mcp:start command.
77
- */
78
- lifecycle: z.enum(["eager", "lazy"]).default("lazy"),
79
- /** Per-request timeout in ms. Overrides global setting. Default: 30000. */
80
- requestTimeoutMs: z.number().positive().optional(),
81
- /**
82
- * Opt-in heartbeat interval (ping) in ms.
83
- * Only useful for long-lived connections where you want proactive liveness checks.
84
- * Default: disabled.
85
- */
86
- healthCheckIntervalMs: z.number().positive().optional(),
87
- })
88
- .refine(
89
- (cfg) => {
90
- if (cfg.transport === "stdio") return cfg.command !== undefined;
91
- return cfg.url !== undefined;
92
- },
93
- (cfg) => ({
94
- message:
95
- cfg.transport === "stdio"
96
- ? `"command" is required for stdio transport`
97
- : `"url" is required for ${cfg.transport} transport`,
98
- }),
99
- );
43
+ .object({
44
+ /** Executable to spawn (e.g. "npx", "node", "uvx"). Required for stdio. */
45
+ command: z.string().optional(),
46
+ /** Arguments passed to command. */
47
+ args: z.array(z.string()).default([]),
48
+ /**
49
+ * Extra environment variables passed to the child process as literals.
50
+ * These merge with process.env; project env overrides parent env.
51
+ * No ${VAR} interpolation — set vars in your shell environment instead.
52
+ */
53
+ env: z.record(z.string()).optional(),
54
+ /** Transport protocol. Default: "stdio". */
55
+ transport: z.enum(["stdio", "streamable-http", "sse"]).default("stdio"),
56
+ /**
57
+ * URL for streamable-http or sse transports.
58
+ * Must be a valid URL (e.g. "https://my-mcp-server.example.com/mcp").
59
+ */
60
+ url: z.string().url().optional(),
61
+ /**
62
+ * Static HTTP headers to include with every request (streamable-http / sse only).
63
+ * Useful for API-key-based auth (e.g. { "Authorization": "Bearer <key>" }).
64
+ * For OAuth2, use the "auth" field instead.
65
+ */
66
+ headers: z.record(z.string()).optional(),
67
+ /**
68
+ * OAuth2 configuration for servers that require authorization.
69
+ * When set, the transport will use the SDK's OAuth flow (discovery,
70
+ * dynamic client registration, PKCE, token refresh).
71
+ * Only applies to streamable-http and sse transports.
72
+ */
73
+ auth: AuthConfigSchema.optional(),
74
+ /**
75
+ * "eager" — start at session_start.
76
+ * "lazy" — start manually via /mcp:start command.
77
+ */
78
+ lifecycle: z.enum(["eager", "lazy"]).default("lazy"),
79
+ /** Per-request timeout in ms. Overrides global setting. Default: 30000. */
80
+ requestTimeoutMs: z.number().positive().optional(),
81
+ /**
82
+ * Opt-in heartbeat interval (ping) in ms.
83
+ * Only useful for long-lived connections where you want proactive liveness checks.
84
+ * Default: disabled.
85
+ */
86
+ healthCheckIntervalMs: z.number().positive().optional(),
87
+ })
88
+ .refine(
89
+ (cfg) => {
90
+ if (cfg.transport === "stdio") return cfg.command !== undefined;
91
+ return cfg.url !== undefined;
92
+ },
93
+ (cfg) => ({
94
+ message:
95
+ cfg.transport === "stdio"
96
+ ? `"command" is required for stdio transport`
97
+ : `"url" is required for ${cfg.transport} transport`,
98
+ }),
99
+ );
100
100
 
101
101
  const SettingsSchema = z.object({
102
- /**
103
- * Prefix used in Pi tool names: <prefix>_<server>_<tool>.
104
- * Must match [a-zA-Z0-9_]. Default: "mcp".
105
- */
106
- toolPrefix: z
107
- .string()
108
- .regex(/^[a-zA-Z0-9_]+$/, "toolPrefix must match [a-zA-Z0-9_]")
109
- .default("mcp"),
110
- /** Default per-request timeout in ms for all servers. Default: 30000. */
111
- requestTimeoutMs: z.number().positive().default(30000),
112
- /** Maximum retry attempts when a server fails to connect. Default: 5. */
113
- maxRetries: z.number().int().min(0).max(10).default(5),
102
+ /**
103
+ * Prefix used in Pi tool names: <prefix>_<server>_<tool>.
104
+ * Must match [a-zA-Z0-9_]. Default: "mcp".
105
+ */
106
+ toolPrefix: z
107
+ .string()
108
+ .regex(/^[a-zA-Z0-9_]+$/, "toolPrefix must match [a-zA-Z0-9_]")
109
+ .default("mcp"),
110
+ /** Default per-request timeout in ms for all servers. Default: 30000. */
111
+ requestTimeoutMs: z.number().positive().default(30000),
112
+ /** Maximum retry attempts when a server fails to connect. Default: 5. */
113
+ maxRetries: z.number().int().min(0).max(10).default(5),
114
114
  });
115
115
 
116
116
  const McpConfigSchema = z.object({
117
- settings: SettingsSchema.default({}),
118
- mcpServers: z.record(ServerConfigSchema).default({}),
117
+ settings: SettingsSchema.default({}),
118
+ mcpServers: z.record(ServerConfigSchema).default({}),
119
119
  });
120
120
 
121
121
  // ─── Public Types ─────────────────────────────────────────────────────────────
@@ -128,41 +128,32 @@ export type McpConfig = z.output<typeof McpConfigSchema>;
128
128
  // ─── Loader ───────────────────────────────────────────────────────────────────
129
129
 
130
130
  async function readJsonFile(path: string): Promise<unknown | null> {
131
- try {
132
- const text = await readFile(path, "utf8");
133
- return JSON.parse(text) as unknown;
134
- } catch (err) {
135
- // ENOENT → file doesn't exist, silently skip
136
- if ((err as NodeJS.ErrnoException).code === "ENOENT") return null;
137
- throw err;
138
- }
131
+ try {
132
+ const text = await readFile(path, "utf8");
133
+ return JSON.parse(text) as unknown;
134
+ } catch (err) {
135
+ // ENOENT → file doesn't exist, silently skip
136
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") return null;
137
+ throw err;
138
+ }
139
139
  }
140
140
 
141
141
  function parseConfig(raw: unknown, sourcePath: string): McpConfig {
142
- const result = McpConfigSchema.safeParse(raw);
143
- if (!result.success) {
144
- const issues = result.error.issues
145
- .map((i) => ` ${i.path.join(".")}: ${i.message}`)
146
- .join("\n");
147
- throw new McpError(
148
- `Invalid mcp.json at ${sourcePath}:\n${issues}`,
149
- "<config>",
150
- "config",
151
- );
152
- }
153
- return result.data;
142
+ const result = McpConfigSchema.safeParse(raw);
143
+ if (!result.success) {
144
+ const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
145
+ throw new McpError(`Invalid mcp.json at ${sourcePath}:\n${issues}`, "<config>", "config");
146
+ }
147
+ return result.data;
154
148
  }
155
149
 
156
- function mergeConfigs(
157
- globalCfg: McpConfig,
158
- projectCfg: McpConfig,
159
- ): McpConfig {
160
- return {
161
- // Shallow spread: project settings override global settings per key
162
- settings: { ...globalCfg.settings, ...projectCfg.settings },
163
- // Per-server override: project server entry completely replaces global entry with same name
164
- mcpServers: { ...globalCfg.mcpServers, ...projectCfg.mcpServers },
165
- };
150
+ function mergeConfigs(globalCfg: McpConfig, projectCfg: McpConfig): McpConfig {
151
+ return {
152
+ // Shallow spread: project settings override global settings per key
153
+ settings: { ...globalCfg.settings, ...projectCfg.settings },
154
+ // Per-server override: project server entry completely replaces global entry with same name
155
+ mcpServers: { ...globalCfg.mcpServers, ...projectCfg.mcpServers },
156
+ };
166
157
  }
167
158
 
168
159
  /**
@@ -171,25 +162,20 @@ function mergeConfigs(
171
162
  * Returns a fully validated, merged config.
172
163
  */
173
164
  export async function loadConfig(cwd: string): Promise<McpConfig> {
174
- const globalPath = join(getAgentDir(), "mcp.json");
175
- const projectPath = join(cwd, ".phi", "mcp.json");
165
+ const globalPath = join(getAgentDir(), "mcp.json");
166
+ const projectPath = join(cwd, ".phi", "mcp.json");
176
167
 
177
- const [globalRaw, projectRaw] = await Promise.all([
178
- readJsonFile(globalPath),
179
- readJsonFile(projectPath),
180
- ]);
168
+ const [globalRaw, projectRaw] = await Promise.all([readJsonFile(globalPath), readJsonFile(projectPath)]);
181
169
 
182
- // If neither file exists, return an empty valid config
183
- if (globalRaw === null && projectRaw === null) {
184
- return McpConfigSchema.parse({});
185
- }
170
+ // If neither file exists, return an empty valid config
171
+ if (globalRaw === null && projectRaw === null) {
172
+ return McpConfigSchema.parse({});
173
+ }
186
174
 
187
- const globalCfg = globalRaw !== null
188
- ? parseConfig(globalRaw, globalPath)
189
- : McpConfigSchema.parse({});
175
+ const globalCfg = globalRaw !== null ? parseConfig(globalRaw, globalPath) : McpConfigSchema.parse({});
190
176
 
191
- if (projectRaw === null) return globalCfg;
177
+ if (projectRaw === null) return globalCfg;
192
178
 
193
- const projectCfg = parseConfig(projectRaw, projectPath);
194
- return mergeConfigs(globalCfg, projectCfg);
179
+ const projectCfg = parseConfig(projectRaw, projectPath);
180
+ return mergeConfigs(globalCfg, projectCfg);
195
181
  }
@@ -5,31 +5,26 @@
5
5
  */
6
6
 
7
7
  export type McpErrorCode =
8
- | "config" // Configuration loading or validation failed
9
- | "connection" // Transport/connection failed
10
- | "protocol" // JSON-RPC protocol violation (server error response, timeout, etc.)
11
- | "tool"; // Tool execution error (isError: true from server)
8
+ | "config" // Configuration loading or validation failed
9
+ | "connection" // Transport/connection failed
10
+ | "protocol" // JSON-RPC protocol violation (server error response, timeout, etc.)
11
+ | "tool"; // Tool execution error (isError: true from server)
12
12
 
13
13
  export class McpError extends Error {
14
- public readonly server: string;
15
- public readonly code: McpErrorCode;
16
- public readonly cause: unknown;
14
+ public readonly server: string;
15
+ public readonly code: McpErrorCode;
16
+ public readonly cause: unknown;
17
17
 
18
- constructor(
19
- message: string,
20
- server: string,
21
- code: McpErrorCode,
22
- cause?: unknown,
23
- ) {
24
- super(message);
25
- this.name = "McpError";
26
- this.server = server;
27
- this.code = code;
28
- this.cause = cause;
29
- }
18
+ constructor(message: string, server: string, code: McpErrorCode, cause?: unknown) {
19
+ super(message);
20
+ this.name = "McpError";
21
+ this.server = server;
22
+ this.code = code;
23
+ this.cause = cause;
24
+ }
30
25
 
31
- /** Short user-facing message suitable for ctx.ui.notify() */
32
- get userMessage(): string {
33
- return `[${this.server}] ${this.message}`;
34
- }
26
+ /** Short user-facing message suitable for ctx.ui.notify() */
27
+ get userMessage(): string {
28
+ return `[${this.server}] ${this.message}`;
29
+ }
35
30
  }