@phi-code-admin/phi-code 0.86.0 → 0.87.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 (33) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/docs/fork-policy.md +18 -0
  3. package/extensions/phi/agents.ts +7 -4
  4. package/extensions/phi/benchmark.ts +129 -49
  5. package/extensions/phi/browser.ts +10 -37
  6. package/extensions/phi/btw/btw.ts +1 -7
  7. package/extensions/phi/chrome/index.ts +1283 -741
  8. package/extensions/phi/commit.ts +9 -14
  9. package/extensions/phi/goal/index.ts +10 -33
  10. package/extensions/phi/init.ts +37 -47
  11. package/extensions/phi/keys.ts +2 -7
  12. package/extensions/phi/mcp/callback-server.ts +162 -165
  13. package/extensions/phi/mcp/config.ts +122 -136
  14. package/extensions/phi/mcp/errors.ts +18 -23
  15. package/extensions/phi/mcp/index.ts +322 -355
  16. package/extensions/phi/mcp/oauth-provider.ts +289 -289
  17. package/extensions/phi/mcp/server-manager.ts +390 -413
  18. package/extensions/phi/mcp/tool-bridge.ts +381 -415
  19. package/extensions/phi/memory.ts +2 -2
  20. package/extensions/phi/models.ts +27 -26
  21. package/extensions/phi/orchestrator.ts +338 -229
  22. package/extensions/phi/productivity.ts +4 -2
  23. package/extensions/phi/providers/agent-def.ts +1 -1
  24. package/extensions/phi/providers/alibaba.ts +56 -7
  25. package/extensions/phi/providers/context-window.ts +4 -1
  26. package/extensions/phi/providers/live-models.ts +5 -20
  27. package/extensions/phi/providers/orchestrator-helpers.ts +19 -5
  28. package/extensions/phi/providers/phase-machine.ts +220 -0
  29. package/extensions/phi/setup.ts +196 -169
  30. package/extensions/phi/skill-loader.ts +14 -17
  31. package/extensions/phi/smart-router.ts +6 -6
  32. package/extensions/phi/web-search.ts +90 -50
  33. package/package.json +1 -1
@@ -5,13 +5,13 @@
5
5
  * Uses Node.js http module for compatibility with no external dependencies.
6
6
  */
7
7
 
8
- import { createServer, type Server, type IncomingMessage, type ServerResponse } from "http";
8
+ import { createServer, type IncomingMessage, type Server, type ServerResponse } from "http";
9
9
  import { URL } from "url";
10
10
 
11
11
  interface PendingAuth {
12
- resolve: (code: string) => void;
13
- reject: (error: Error) => void;
14
- timeout: ReturnType<typeof setTimeout>;
12
+ resolve: (code: string) => void;
13
+ reject: (error: Error) => void;
14
+ timeout: ReturnType<typeof setTimeout>;
15
15
  }
16
16
 
17
17
  let server: Server | null = null;
@@ -44,78 +44,78 @@ const HTML_ERROR = (error: string) => `<!DOCTYPE html>
44
44
  * Escape HTML entities to prevent XSS attacks.
45
45
  */
46
46
  function escapeHtml(text: string): string {
47
- return text
48
- .replace(/&/g, "&amp;")
49
- .replace(/</g, "&lt;")
50
- .replace(/>/g, "&gt;")
51
- .replace(/"/g, "&quot;")
52
- .replace(/'/g, "&#039;");
47
+ return text
48
+ .replace(/&/g, "&amp;")
49
+ .replace(/</g, "&lt;")
50
+ .replace(/>/g, "&gt;")
51
+ .replace(/"/g, "&quot;")
52
+ .replace(/'/g, "&#039;");
53
53
  }
54
54
 
55
55
  /**
56
56
  * Handle incoming HTTP requests to the callback server.
57
57
  */
58
58
  function handleRequest(req: IncomingMessage, res: ServerResponse): void {
59
- const url = new URL(req.url || "/", `http://${req.headers.host}`);
60
-
61
- // Only handle the callback path
62
- if (url.pathname !== CALLBACK_PATH) {
63
- res.writeHead(404, { "Content-Type": "text/plain" });
64
- res.end("Not found");
65
- return;
66
- }
67
-
68
- const code = url.searchParams.get("code");
69
- const state = url.searchParams.get("state");
70
- const error = url.searchParams.get("error");
71
- const errorDescription = url.searchParams.get("error_description");
72
-
73
- // Enforce state parameter presence for CSRF protection
74
- if (!state) {
75
- const errorMsg = "Missing required state parameter - potential CSRF attack";
76
- res.writeHead(400, { "Content-Type": "text/html" });
77
- res.end(HTML_ERROR(errorMsg));
78
- return;
79
- }
80
-
81
- // Handle OAuth errors
82
- if (error) {
83
- const errorMsg = errorDescription || error;
84
- res.writeHead(200, { "Content-Type": "text/html" });
85
- res.end(HTML_ERROR(errorMsg));
86
- if (pendingAuths.has(state)) {
87
- const pending = pendingAuths.get(state)!;
88
- clearTimeout(pending.timeout);
89
- pendingAuths.delete(state);
90
- setTimeout(() => pending.reject(new Error(errorMsg)), 0);
91
- }
92
- return;
93
- }
94
-
95
- // Require authorization code
96
- if (!code) {
97
- res.writeHead(400, { "Content-Type": "text/html" });
98
- res.end(HTML_ERROR("No authorization code provided"));
99
- return;
100
- }
101
-
102
- // Validate state parameter
103
- if (!pendingAuths.has(state)) {
104
- const errorMsg = "Invalid or expired state parameter - potential CSRF attack";
105
- res.writeHead(400, { "Content-Type": "text/html" });
106
- res.end(HTML_ERROR(errorMsg));
107
- return;
108
- }
109
-
110
- const pending = pendingAuths.get(state)!;
111
-
112
- // Clear timeout and resolve the pending promise
113
- clearTimeout(pending.timeout);
114
- pendingAuths.delete(state);
115
- pending.resolve(code);
116
-
117
- res.writeHead(200, { "Content-Type": "text/html" });
118
- res.end(HTML_SUCCESS);
59
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
60
+
61
+ // Only handle the callback path
62
+ if (url.pathname !== CALLBACK_PATH) {
63
+ res.writeHead(404, { "Content-Type": "text/plain" });
64
+ res.end("Not found");
65
+ return;
66
+ }
67
+
68
+ const code = url.searchParams.get("code");
69
+ const state = url.searchParams.get("state");
70
+ const error = url.searchParams.get("error");
71
+ const errorDescription = url.searchParams.get("error_description");
72
+
73
+ // Enforce state parameter presence for CSRF protection
74
+ if (!state) {
75
+ const errorMsg = "Missing required state parameter - potential CSRF attack";
76
+ res.writeHead(400, { "Content-Type": "text/html" });
77
+ res.end(HTML_ERROR(errorMsg));
78
+ return;
79
+ }
80
+
81
+ // Handle OAuth errors
82
+ if (error) {
83
+ const errorMsg = errorDescription || error;
84
+ res.writeHead(200, { "Content-Type": "text/html" });
85
+ res.end(HTML_ERROR(errorMsg));
86
+ if (pendingAuths.has(state)) {
87
+ const pending = pendingAuths.get(state)!;
88
+ clearTimeout(pending.timeout);
89
+ pendingAuths.delete(state);
90
+ setTimeout(() => pending.reject(new Error(errorMsg)), 0);
91
+ }
92
+ return;
93
+ }
94
+
95
+ // Require authorization code
96
+ if (!code) {
97
+ res.writeHead(400, { "Content-Type": "text/html" });
98
+ res.end(HTML_ERROR("No authorization code provided"));
99
+ return;
100
+ }
101
+
102
+ // Validate state parameter
103
+ if (!pendingAuths.has(state)) {
104
+ const errorMsg = "Invalid or expired state parameter - potential CSRF attack";
105
+ res.writeHead(400, { "Content-Type": "text/html" });
106
+ res.end(HTML_ERROR(errorMsg));
107
+ return;
108
+ }
109
+
110
+ const pending = pendingAuths.get(state)!;
111
+
112
+ // Clear timeout and resolve the pending promise
113
+ clearTimeout(pending.timeout);
114
+ pendingAuths.delete(state);
115
+ pending.resolve(code);
116
+
117
+ res.writeHead(200, { "Content-Type": "text/html" });
118
+ res.end(HTML_SUCCESS);
119
119
  }
120
120
 
121
121
  /**
@@ -126,60 +126,60 @@ function handleRequest(req: IncomingMessage, res: ServerResponse): void {
126
126
  * @returns The actual port the server is listening on
127
127
  */
128
128
  export async function ensureCallbackServer(preferredPort: number = DEFAULT_PORT): Promise<number> {
129
- if (server) {
130
- // Already running, return the tracked actual port
131
- if (actualServerPort !== null) {
132
- return actualServerPort;
133
- }
134
- // Fallback: try to get the port from the server
135
- const address = server.address();
136
- if (address && typeof address === "object" && "port" in address) {
137
- actualServerPort = address.port;
138
- return address.port;
139
- }
140
- // If we still can't determine the port, return the preferred port
141
- // (this shouldn't happen in practice)
142
- return preferredPort;
143
- }
144
-
145
- const maxAttempts = 25; // Try up to 25 ports
146
-
147
- for (let offset = 0; offset < maxAttempts; offset++) {
148
- const candidatePort = preferredPort + offset;
149
- const candidateServer = createServer(handleRequest);
150
-
151
- try {
152
- await new Promise<void>((resolve, reject) => {
153
- candidateServer.once("error", (err: any) => {
154
- reject(err);
155
- });
156
-
157
- // Bind to 127.0.0.1 explicitly (IPv4) to avoid issues with IPv6
158
- candidateServer.listen(candidatePort, "127.0.0.1", () => {
159
- resolve();
160
- });
161
- });
162
-
163
- server = candidateServer;
164
- actualServerPort = candidatePort;
165
- server.unref(); // Don't block process exit
166
- return candidatePort;
167
- } catch (error) {
168
- const nodeError = error as NodeJS.ErrnoException;
169
- await new Promise<void>((resolve) => {
170
- candidateServer.close(() => resolve());
171
- });
172
-
173
- // If not EADDRINUSE, rethrow
174
- if (nodeError.code !== "EADDRINUSE") {
175
- throw error;
176
- }
177
- }
178
- }
179
-
180
- throw new Error(
181
- `OAuth callback port ${preferredPort} is already in use and no free port was found in range ${preferredPort}-${preferredPort + maxAttempts - 1}`
182
- );
129
+ if (server) {
130
+ // Already running, return the tracked actual port
131
+ if (actualServerPort !== null) {
132
+ return actualServerPort;
133
+ }
134
+ // Fallback: try to get the port from the server
135
+ const address = server.address();
136
+ if (address && typeof address === "object" && "port" in address) {
137
+ actualServerPort = address.port;
138
+ return address.port;
139
+ }
140
+ // If we still can't determine the port, return the preferred port
141
+ // (this shouldn't happen in practice)
142
+ return preferredPort;
143
+ }
144
+
145
+ const maxAttempts = 25; // Try up to 25 ports
146
+
147
+ for (let offset = 0; offset < maxAttempts; offset++) {
148
+ const candidatePort = preferredPort + offset;
149
+ const candidateServer = createServer(handleRequest);
150
+
151
+ try {
152
+ await new Promise<void>((resolve, reject) => {
153
+ candidateServer.once("error", (err: any) => {
154
+ reject(err);
155
+ });
156
+
157
+ // Bind to 127.0.0.1 explicitly (IPv4) to avoid issues with IPv6
158
+ candidateServer.listen(candidatePort, "127.0.0.1", () => {
159
+ resolve();
160
+ });
161
+ });
162
+
163
+ server = candidateServer;
164
+ actualServerPort = candidatePort;
165
+ server.unref(); // Don't block process exit
166
+ return candidatePort;
167
+ } catch (error) {
168
+ const nodeError = error as NodeJS.ErrnoException;
169
+ await new Promise<void>((resolve) => {
170
+ candidateServer.close(() => resolve());
171
+ });
172
+
173
+ // If not EADDRINUSE, rethrow
174
+ if (nodeError.code !== "EADDRINUSE") {
175
+ throw error;
176
+ }
177
+ }
178
+ }
179
+
180
+ throw new Error(
181
+ `OAuth callback port ${preferredPort} is already in use and no free port was found in range ${preferredPort}-${preferredPort + maxAttempts - 1}`,
182
+ );
183
183
  }
184
184
 
185
185
  /**
@@ -190,20 +190,17 @@ export async function ensureCallbackServer(preferredPort: number = DEFAULT_PORT)
190
190
  * @param timeoutMs - Timeout in milliseconds (default: 5 minutes)
191
191
  * @returns Promise that resolves with the authorization code
192
192
  */
193
- export function waitForCallback(
194
- oauthState: string,
195
- timeoutMs: number = TIMEOUT_MS
196
- ): Promise<string> {
197
- return new Promise((resolve, reject) => {
198
- const timeout = setTimeout(() => {
199
- if (pendingAuths.has(oauthState)) {
200
- pendingAuths.delete(oauthState);
201
- reject(new Error("OAuth callback timeout - authorization took too long"));
202
- }
203
- }, timeoutMs);
204
-
205
- pendingAuths.set(oauthState, { resolve, reject, timeout });
206
- });
193
+ export function waitForCallback(oauthState: string, timeoutMs: number = TIMEOUT_MS): Promise<string> {
194
+ return new Promise((resolve, reject) => {
195
+ const timeout = setTimeout(() => {
196
+ if (pendingAuths.has(oauthState)) {
197
+ pendingAuths.delete(oauthState);
198
+ reject(new Error("OAuth callback timeout - authorization took too long"));
199
+ }
200
+ }, timeoutMs);
201
+
202
+ pendingAuths.set(oauthState, { resolve, reject, timeout });
203
+ });
207
204
  }
208
205
 
209
206
  /**
@@ -212,51 +209,51 @@ export function waitForCallback(
212
209
  * @param oauthState - The OAuth state to cancel
213
210
  */
214
211
  export function cancelCallback(oauthState: string): void {
215
- const pending = pendingAuths.get(oauthState);
216
- if (pending) {
217
- clearTimeout(pending.timeout);
218
- pendingAuths.delete(oauthState);
219
- pending.reject(new Error("Authorization cancelled"));
220
- }
212
+ const pending = pendingAuths.get(oauthState);
213
+ if (pending) {
214
+ clearTimeout(pending.timeout);
215
+ pendingAuths.delete(oauthState);
216
+ pending.reject(new Error("Authorization cancelled"));
217
+ }
221
218
  }
222
219
 
223
220
  /**
224
221
  * Stop the callback server and reject all pending authorizations.
225
222
  */
226
223
  export async function stopCallbackServer(): Promise<void> {
227
- if (server) {
228
- await new Promise<void>((resolve) => {
229
- server!.close(() => {
230
- resolve();
231
- });
232
- });
233
- server = null;
234
- actualServerPort = null;
235
- }
236
-
237
- // Reject all pending auths (defer to allow any pending operations to complete)
238
- const pendingList = Array.from(pendingAuths.entries());
239
- pendingAuths.clear();
240
- setTimeout(() => {
241
- for (const [, pending] of pendingList) {
242
- clearTimeout(pending.timeout);
243
- pending.reject(new Error("OAuth callback server stopped"));
244
- }
245
- }, 0);
224
+ if (server) {
225
+ await new Promise<void>((resolve) => {
226
+ server!.close(() => {
227
+ resolve();
228
+ });
229
+ });
230
+ server = null;
231
+ actualServerPort = null;
232
+ }
233
+
234
+ // Reject all pending auths (defer to allow any pending operations to complete)
235
+ const pendingList = Array.from(pendingAuths.entries());
236
+ pendingAuths.clear();
237
+ setTimeout(() => {
238
+ for (const [, pending] of pendingList) {
239
+ clearTimeout(pending.timeout);
240
+ pending.reject(new Error("OAuth callback server stopped"));
241
+ }
242
+ }, 0);
246
243
  }
247
244
 
248
245
  /**
249
246
  * Check if the callback server is running.
250
247
  */
251
248
  export function isCallbackServerRunning(): boolean {
252
- return server !== null;
249
+ return server !== null;
253
250
  }
254
251
 
255
252
  /**
256
253
  * Get the number of pending authorizations.
257
254
  */
258
255
  export function getPendingAuthCount(): number {
259
- return pendingAuths.size;
256
+ return pendingAuths.size;
260
257
  }
261
258
 
262
259
  // Export constants for testing/config