@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.
- package/CHANGELOG.md +104 -0
- package/docs/adr/0001-phase-contract.md +57 -0
- package/docs/adr/0002-independent-review.md +47 -0
- package/docs/fork-policy.md +18 -0
- package/extensions/phi/agents.ts +7 -4
- package/extensions/phi/benchmark.ts +129 -49
- package/extensions/phi/browser.ts +10 -37
- package/extensions/phi/btw/btw.ts +1 -7
- package/extensions/phi/chrome/index.ts +1283 -741
- package/extensions/phi/commit.ts +9 -14
- package/extensions/phi/goal/index.ts +10 -33
- package/extensions/phi/init.ts +37 -47
- package/extensions/phi/keys.ts +2 -7
- package/extensions/phi/mcp/callback-server.ts +162 -165
- package/extensions/phi/mcp/config.ts +122 -136
- package/extensions/phi/mcp/errors.ts +18 -23
- package/extensions/phi/mcp/index.ts +322 -355
- package/extensions/phi/mcp/oauth-provider.ts +289 -289
- package/extensions/phi/mcp/server-manager.ts +390 -413
- package/extensions/phi/mcp/tool-bridge.ts +381 -415
- package/extensions/phi/memory.ts +2 -2
- package/extensions/phi/models.ts +27 -26
- package/extensions/phi/orchestrator.ts +451 -237
- package/extensions/phi/productivity.ts +4 -2
- package/extensions/phi/providers/agent-def.ts +1 -1
- package/extensions/phi/providers/alibaba.ts +56 -7
- package/extensions/phi/providers/context-window.ts +4 -1
- package/extensions/phi/providers/live-models.ts +5 -20
- package/extensions/phi/providers/orchestrator-helpers.ts +31 -5
- package/extensions/phi/providers/phase-machine.ts +283 -0
- package/extensions/phi/setup.ts +196 -169
- package/extensions/phi/skill-loader.ts +14 -17
- package/extensions/phi/smart-router.ts +6 -6
- package/extensions/phi/web-search.ts +90 -50
- 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
|
|
8
|
+
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "http";
|
|
9
9
|
import { URL } from "url";
|
|
10
10
|
|
|
11
11
|
interface PendingAuth {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
return text
|
|
48
|
+
.replace(/&/g, "&")
|
|
49
|
+
.replace(/</g, "<")
|
|
50
|
+
.replace(/>/g, ">")
|
|
51
|
+
.replace(/"/g, """)
|
|
52
|
+
.replace(/'/g, "'");
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
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
|
-
|
|
256
|
+
return pendingAuths.size;
|
|
260
257
|
}
|
|
261
258
|
|
|
262
259
|
// Export constants for testing/config
|