@herbertgao/pi-extensions 2026.9.13 → 2026.9.15
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/README.md +3 -3
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +4 -4
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +4 -4
- package/node_modules/@herbertgao/resume-from/package.json +2 -2
- package/node_modules/@herbertgao/sol-pi/package.json +5 -5
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +2 -0
- package/node_modules/pi-jev-auto-mode/CHANGELOG.md +8 -0
- package/node_modules/pi-jev-auto-mode/README.md +7 -0
- package/node_modules/pi-jev-auto-mode/package.json +1 -1
- package/node_modules/pi-jev-auto-mode/src/extension.ts +21 -2
- package/node_modules/pi-jev-auto-mode/src/records.ts +68 -2
- package/node_modules/pi-jev-auto-mode/src/settings.ts +22 -0
- package/node_modules/pi-jev-auto-mode/src/ui.ts +10 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +26 -0
- package/node_modules/pi-mcp-adapter/README.md +39 -9
- package/node_modules/pi-mcp-adapter/cli.js +33 -22
- package/node_modules/pi-mcp-adapter/commands.ts +3 -3
- package/node_modules/pi-mcp-adapter/config.ts +9 -1
- package/node_modules/pi-mcp-adapter/dist/config.js +9 -1
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +1 -2
- package/node_modules/pi-mcp-adapter/dist/jev-client.js +59 -32
- package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +1 -1
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +33 -4
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +148 -28
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1 -2
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +7 -1
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/index.ts +51 -38
- package/node_modules/pi-mcp-adapter/jev-client.ts +58 -31
- package/node_modules/pi-mcp-adapter/jev-contracts.ts +2 -2
- package/node_modules/pi-mcp-adapter/jev-key-store.ts +144 -31
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-code.ts +6 -6
- package/node_modules/pi-mcp-adapter/package.json +5 -5
- package/node_modules/pi-mcp-adapter/semantic-search.ts +3 -3
- package/node_modules/pi-mcp-adapter/types.ts +7 -1
- package/node_modules/pi-multi-account/CHANGELOG.md +12 -0
- package/node_modules/pi-multi-account/README.md +21 -6
- package/node_modules/pi-multi-account/index.ts +295 -41
- package/node_modules/pi-multi-account/package.json +3 -3
- package/node_modules/pi-typesafe/dist/backends.d.ts +10 -0
- package/node_modules/pi-typesafe/dist/backends.js +26 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +1 -1
- package/node_modules/pi-typesafe/dist/client.js +10 -6
- package/node_modules/pi-typesafe/dist/errors.d.ts +10 -2
- package/node_modules/pi-typesafe/dist/errors.js +28 -7
- package/node_modules/pi-typesafe/dist/extension.js +2 -1
- package/node_modules/pi-typesafe/package.json +4 -4
- package/node_modules/pi-web-access/CHANGELOG.md +22 -0
- package/node_modules/pi-web-access/README.md +21 -8
- package/node_modules/pi-web-access/baizhi.ts +124 -0
- package/node_modules/pi-web-access/crawl4ai.ts +2 -10
- package/node_modules/pi-web-access/curator-page.ts +3 -1
- package/node_modules/pi-web-access/curator-server.ts +1 -0
- package/node_modules/pi-web-access/dist/index.js +540 -220
- package/node_modules/pi-web-access/firecrawl.ts +2 -10
- package/node_modules/pi-web-access/gemini-search.ts +5 -10
- package/node_modules/pi-web-access/index.ts +134 -23
- package/node_modules/pi-web-access/package.json +5 -1
- package/node_modules/pi-web-access/storage.ts +1 -0
- package/node_modules/pi-web-access/tool-activation.ts +134 -0
- package/node_modules/pi-web-access/utils.ts +9 -1
- package/package.json +14 -14
|
@@ -1,20 +1,44 @@
|
|
|
1
1
|
import { createSecureKeyringStore, type SecureKeyringStore } from "./secure-keyring.ts";
|
|
2
2
|
|
|
3
|
+
/** The Jev decisions endpoint used when `SYSTEMONE_ENDPOINT` is not set. */
|
|
4
|
+
export const JEV_DEFAULT_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
|
|
5
|
+
const SYSTEMONE_ENDPOINT_ENV = "SYSTEMONE_ENDPOINT";
|
|
6
|
+
const SYSTEMONE_API_KEY_ENV = "SYSTEMONE_API_KEY";
|
|
7
|
+
/** TypeSafe-issued key: honored only for the default endpoint and never sent anywhere else. */
|
|
8
|
+
const LEGACY_TYPESAFE_API_KEY_ENV = "TYPESAFE_API_KEY";
|
|
9
|
+
/** Origin recorded in version 1 credential records. */
|
|
3
10
|
export const TYPESAFE_API_ORIGIN = "https://api.typesafe.ai";
|
|
4
|
-
|
|
11
|
+
/** Version 1 credential account, still read for the default endpoint. */
|
|
5
12
|
export const JEV_KEYRING_ACCOUNT = "typesafe@sha256(https://api.typesafe.ai)";
|
|
13
|
+
export const JEV_KEYRING_SERVICE = "pi-mcp-adapter.service-key";
|
|
14
|
+
|
|
15
|
+
const MAX_ENDPOINT_LENGTH = 512;
|
|
16
|
+
/** The path `@typesafe-ai/sdk` appends to whatever base URL it is given. */
|
|
17
|
+
export const JEV_SDK_PATH = "/v1/systemone";
|
|
18
|
+
|
|
19
|
+
export interface ResolvedJevEndpoint {
|
|
20
|
+
/** Canonical absolute URL sent to the provider, for example `https://opencode.ai/zen/v1/systemone`. */
|
|
21
|
+
readonly href: string;
|
|
22
|
+
/** URL origin, used as the SDK base URL. */
|
|
23
|
+
readonly origin: string;
|
|
24
|
+
/** URL path, rewritten onto the SDK's fixed path by the pinned fetch. */
|
|
25
|
+
readonly path: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type JevEndpointResolution =
|
|
29
|
+
| { status: "resolved"; source: "environment" | "default"; endpoint: ResolvedJevEndpoint }
|
|
30
|
+
| { status: "unavailable"; message: string };
|
|
6
31
|
|
|
7
32
|
interface StoredJevKey {
|
|
8
|
-
version:
|
|
9
|
-
|
|
10
|
-
origin: typeof TYPESAFE_API_ORIGIN;
|
|
33
|
+
version: 2;
|
|
34
|
+
endpoint: string;
|
|
11
35
|
apiKey: string;
|
|
12
36
|
}
|
|
13
37
|
|
|
14
38
|
export class JevCredentialStoreError extends Error {
|
|
15
39
|
readonly code = "JEV_CREDENTIAL_STORE_UNAVAILABLE";
|
|
16
40
|
constructor(readonly operation: "read" | "write" | "remove", cause: unknown) {
|
|
17
|
-
super(`
|
|
41
|
+
super(`Jev API key secure credential store unavailable during ${operation}. Configure or unlock the OS credential store and retry.`, { cause });
|
|
18
42
|
this.name = "JevCredentialStoreError";
|
|
19
43
|
}
|
|
20
44
|
}
|
|
@@ -26,54 +50,143 @@ export type JevCredentialResolution =
|
|
|
26
50
|
|
|
27
51
|
function validateApiKey(value: unknown): string {
|
|
28
52
|
if (typeof value !== "string" || value.trim().length === 0 || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
29
|
-
throw new Error("
|
|
53
|
+
throw new Error("Jev API key must be a non-empty string without control characters");
|
|
30
54
|
}
|
|
31
55
|
return value;
|
|
32
56
|
}
|
|
33
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Constrains the operator-supplied endpoint to an absolute HTTPS URL without credentials, query, or fragment, so
|
|
60
|
+
* the pinned fetch can match it exactly and a stray value cannot redirect requests or smuggle a query string.
|
|
61
|
+
*/
|
|
62
|
+
function parseEndpoint(raw: string, label: string): ResolvedJevEndpoint {
|
|
63
|
+
if (typeof raw !== "string" || raw.trim().length === 0) throw new Error(`${label} must be a non-empty URL`);
|
|
64
|
+
if (raw.length > MAX_ENDPOINT_LENGTH) throw new Error(`${label} must be at most ${MAX_ENDPOINT_LENGTH} characters`);
|
|
65
|
+
if (/[\u0000-\u001f\u007f\s]/.test(raw)) throw new Error(`${label} must not contain whitespace or control characters`);
|
|
66
|
+
let url: URL;
|
|
67
|
+
try { url = new URL(raw); }
|
|
68
|
+
catch { throw new Error(`${label} must be an absolute URL`); }
|
|
69
|
+
if (url.protocol !== "https:") throw new Error(`${label} must use https`);
|
|
70
|
+
if (url.username !== "" || url.password !== "") throw new Error(`${label} must not embed credentials`);
|
|
71
|
+
if (url.search !== "") throw new Error(`${label} must not include a query string`);
|
|
72
|
+
if (url.hash !== "") throw new Error(`${label} must not include a fragment`);
|
|
73
|
+
if (url.pathname === "" || url.pathname === "/") throw new Error(`${label} must include a path such as ${JEV_SDK_PATH}`);
|
|
74
|
+
return { href: `${url.origin}${url.pathname}`, origin: url.origin, path: url.pathname };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolves the Jev endpoint. An invalid `SYSTEMONE_ENDPOINT` resolves to `unavailable` instead of silently falling
|
|
79
|
+
* back to the default: sending an operator's judgment payload to a provider they did not ask for would be a data leak.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveJevEndpoint(env: NodeJS.ProcessEnv = process.env): JevEndpointResolution {
|
|
82
|
+
if (Object.hasOwn(env, SYSTEMONE_ENDPOINT_ENV)) {
|
|
83
|
+
try { return { status: "resolved", source: "environment", endpoint: parseEndpoint(env[SYSTEMONE_ENDPOINT_ENV] as string, SYSTEMONE_ENDPOINT_ENV) }; }
|
|
84
|
+
catch (error) { return { status: "unavailable", message: error instanceof Error ? error.message : `${SYSTEMONE_ENDPOINT_ENV} is invalid.` }; }
|
|
85
|
+
}
|
|
86
|
+
return { status: "resolved", source: "default", endpoint: parseEndpoint(JEV_DEFAULT_ENDPOINT, "the default Jev endpoint") };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Keyring account for an endpoint, so keys for different providers coexist instead of overwriting each other. */
|
|
90
|
+
export function jevKeyringAccount(endpoint: ResolvedJevEndpoint): string {
|
|
91
|
+
return `systemone@sha256(${endpoint.href})`;
|
|
92
|
+
}
|
|
93
|
+
|
|
34
94
|
function store(): SecureKeyringStore {
|
|
35
95
|
return createSecureKeyringStore(JEV_KEYRING_SERVICE);
|
|
36
96
|
}
|
|
37
97
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
98
|
+
function requireEndpoint(endpoint: ResolvedJevEndpoint | undefined): ResolvedJevEndpoint {
|
|
99
|
+
if (endpoint) return endpoint;
|
|
100
|
+
const resolution = resolveJevEndpoint();
|
|
101
|
+
if (resolution.status === "unavailable") throw new Error(resolution.message);
|
|
102
|
+
return resolution.endpoint;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Version 1 records were written before the endpoint was configurable, so they are accepted only for the default
|
|
107
|
+
* endpoint.
|
|
108
|
+
*/
|
|
109
|
+
function parseStoredKey(payload: string, endpoint: ResolvedJevEndpoint): string {
|
|
110
|
+
let value: unknown;
|
|
111
|
+
try { value = JSON.parse(payload); }
|
|
112
|
+
catch { throw new Error("invalid record"); }
|
|
113
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid record");
|
|
114
|
+
const record = value as Record<string, unknown>;
|
|
115
|
+
const keys = Object.keys(record);
|
|
116
|
+
if (record.version === 2) {
|
|
117
|
+
if (keys.length !== 3 || !["version", "endpoint", "apiKey"].every(key => Object.hasOwn(record, key))) throw new Error("invalid record fields");
|
|
118
|
+
if (record.endpoint !== endpoint.href) throw new Error("invalid or mismatched record");
|
|
119
|
+
return validateApiKey(record.apiKey);
|
|
120
|
+
}
|
|
121
|
+
if (record.version === 1) {
|
|
48
122
|
if (keys.length !== 4 || !["version", "provider", "origin", "apiKey"].every(key => Object.hasOwn(record, key))) throw new Error("invalid record fields");
|
|
49
|
-
if (record.
|
|
123
|
+
if (record.provider !== "typesafe" || record.origin !== TYPESAFE_API_ORIGIN) throw new Error("invalid or mismatched record");
|
|
124
|
+
if (endpoint.href !== JEV_DEFAULT_ENDPOINT) throw new Error("invalid or mismatched record");
|
|
50
125
|
return validateApiKey(record.apiKey);
|
|
126
|
+
}
|
|
127
|
+
throw new Error("invalid record version");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function readStoredKey(secretStore: SecureKeyringStore, endpoint: ResolvedJevEndpoint): string | undefined {
|
|
131
|
+
try {
|
|
132
|
+
let payload = secretStore.read(jevKeyringAccount(endpoint));
|
|
133
|
+
if (payload === undefined && endpoint.href === JEV_DEFAULT_ENDPOINT) payload = secretStore.read(JEV_KEYRING_ACCOUNT);
|
|
134
|
+
return payload === undefined ? undefined : parseStoredKey(payload, endpoint);
|
|
51
135
|
} catch (error) {
|
|
52
136
|
throw new JevCredentialStoreError("read", error);
|
|
53
137
|
}
|
|
54
138
|
}
|
|
55
139
|
|
|
56
|
-
export function resolveJevCredential(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
140
|
+
export function resolveJevCredential(
|
|
141
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
142
|
+
endpoint?: ResolvedJevEndpoint,
|
|
143
|
+
secretStore: SecureKeyringStore = store(),
|
|
144
|
+
): JevCredentialResolution {
|
|
145
|
+
let target: ResolvedJevEndpoint;
|
|
146
|
+
if (endpoint) target = endpoint;
|
|
147
|
+
else {
|
|
148
|
+
const resolution = resolveJevEndpoint(env);
|
|
149
|
+
if (resolution.status === "unavailable") return resolution;
|
|
150
|
+
target = resolution.endpoint;
|
|
60
151
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
152
|
+
if (Object.hasOwn(env, SYSTEMONE_API_KEY_ENV)) {
|
|
153
|
+
try { return { status: "present", source: "environment", apiKey: validateApiKey(env[SYSTEMONE_API_KEY_ENV]) }; }
|
|
154
|
+
catch { return { status: "unavailable", message: `${SYSTEMONE_API_KEY_ENV} is present but invalid.` }; }
|
|
155
|
+
}
|
|
156
|
+
const legacySet = Object.hasOwn(env, LEGACY_TYPESAFE_API_KEY_ENV);
|
|
157
|
+
if (legacySet && target.href === JEV_DEFAULT_ENDPOINT) {
|
|
158
|
+
try { return { status: "present", source: "environment", apiKey: validateApiKey(env[LEGACY_TYPESAFE_API_KEY_ENV]) }; }
|
|
159
|
+
catch { return { status: "unavailable", message: `${LEGACY_TYPESAFE_API_KEY_ENV} is present but invalid.` }; }
|
|
160
|
+
}
|
|
161
|
+
let stored: string | undefined;
|
|
162
|
+
try { stored = readStoredKey(secretStore, target); }
|
|
163
|
+
catch (error) {
|
|
65
164
|
if (!(error instanceof JevCredentialStoreError)) throw error;
|
|
66
165
|
return { status: "unavailable", message: error.message };
|
|
67
166
|
}
|
|
167
|
+
if (stored !== undefined) return { status: "present", source: "keyring", apiKey: stored };
|
|
168
|
+
// A TypeSafe-issued credential is never sent to another endpoint, but it must not mask a credential stored for
|
|
169
|
+
// this endpoint either, so it is only reported once nothing for this endpoint resolves.
|
|
170
|
+
if (legacySet) {
|
|
171
|
+
return {
|
|
172
|
+
status: "unavailable",
|
|
173
|
+
message: `${LEGACY_TYPESAFE_API_KEY_ENV} is a TypeSafe credential and is not sent to ${target.href}; set ${SYSTEMONE_API_KEY_ENV} for that endpoint.`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return { status: "missing" };
|
|
68
177
|
}
|
|
69
178
|
|
|
70
|
-
export function saveJevApiKey(apiKey: string, secretStore: SecureKeyringStore = store()): void {
|
|
71
|
-
const
|
|
72
|
-
|
|
179
|
+
export function saveJevApiKey(apiKey: string, endpoint?: ResolvedJevEndpoint, secretStore: SecureKeyringStore = store()): void {
|
|
180
|
+
const target = requireEndpoint(endpoint);
|
|
181
|
+
const record: StoredJevKey = { version: 2, endpoint: target.href, apiKey: validateApiKey(apiKey) };
|
|
182
|
+
try { secretStore.write(jevKeyringAccount(target), JSON.stringify(record)); }
|
|
73
183
|
catch (error) { throw new JevCredentialStoreError("write", error); }
|
|
74
184
|
}
|
|
75
185
|
|
|
76
|
-
export function removeJevApiKey(secretStore: SecureKeyringStore = store()): void {
|
|
77
|
-
|
|
78
|
-
|
|
186
|
+
export function removeJevApiKey(endpoint?: ResolvedJevEndpoint, secretStore: SecureKeyringStore = store()): void {
|
|
187
|
+
const target = requireEndpoint(endpoint);
|
|
188
|
+
try {
|
|
189
|
+
secretStore.remove(jevKeyringAccount(target));
|
|
190
|
+
if (target.href === JEV_DEFAULT_ENDPOINT) secretStore.remove(JEV_KEYRING_ACCOUNT);
|
|
191
|
+
} catch (error) { throw new JevCredentialStoreError("remove", error); }
|
|
79
192
|
}
|
|
@@ -213,7 +213,7 @@ export async function runMcpScript(
|
|
|
213
213
|
let evaluationAttempts = 0;
|
|
214
214
|
let evaluationBytes = 0;
|
|
215
215
|
let evaluationTokensRemaining = jevSettings.maxEvaluationTokensPerScript;
|
|
216
|
-
const tokenBudgetExhausted = (): JevEvaluationEnvelope => ({ ok: false, error: { code: "budget_exhausted", message: "
|
|
216
|
+
const tokenBudgetExhausted = (): JevEvaluationEnvelope => ({ ok: false, error: { code: "budget_exhausted", message: "Jev evaluation token budget exhausted." } });
|
|
217
217
|
const chargeEvaluationTokens = (envelope: JevEvaluationEnvelope): JevEvaluationEnvelope => {
|
|
218
218
|
if (!envelope.ok) return envelope;
|
|
219
219
|
const used = envelope.data.usage.inputTokens + envelope.data.usage.outputTokens;
|
|
@@ -226,16 +226,16 @@ export async function runMcpScript(
|
|
|
226
226
|
};
|
|
227
227
|
const admitEvaluation = (input: unknown): JevEvaluationEnvelope | undefined => {
|
|
228
228
|
if (++evaluationAttempts > jevSettings.maxEvaluationsPerScript) {
|
|
229
|
-
return { ok: false, error: { code: "budget_exhausted", message: "
|
|
229
|
+
return { ok: false, error: { code: "budget_exhausted", message: "Jev evaluation count budget exhausted." } };
|
|
230
230
|
}
|
|
231
231
|
if (evaluationTokensRemaining === 0) return tokenBudgetExhausted();
|
|
232
232
|
let serialized: string | undefined;
|
|
233
233
|
try { serialized = JSON.stringify(input); }
|
|
234
|
-
catch { return { ok: false, error: { code: "invalid_request", message: "Invalid
|
|
235
|
-
if (serialized === undefined) return { ok: false, error: { code: "invalid_request", message: "Invalid
|
|
234
|
+
catch { return { ok: false, error: { code: "invalid_request", message: "Invalid Jev evaluation request." } }; }
|
|
235
|
+
if (serialized === undefined) return { ok: false, error: { code: "invalid_request", message: "Invalid Jev evaluation request." } };
|
|
236
236
|
const bytes = Buffer.byteLength(serialized, "utf8");
|
|
237
237
|
if (bytes > jevSettings.maxEvaluationBytesPerScript - evaluationBytes) {
|
|
238
|
-
return { ok: false, error: { code: "budget_exhausted", message: "
|
|
238
|
+
return { ok: false, error: { code: "budget_exhausted", message: "Jev evaluation byte budget exhausted." } };
|
|
239
239
|
}
|
|
240
240
|
evaluationBytes += bytes;
|
|
241
241
|
return undefined;
|
|
@@ -257,7 +257,7 @@ export async function runMcpScript(
|
|
|
257
257
|
envelope = chargeEvaluationTokens(envelope);
|
|
258
258
|
throwIfAborted(callSignal);
|
|
259
259
|
if (!reserveIntermediateBytes(JSON.stringify(envelope))) {
|
|
260
|
-
envelope = { ok: false, error: { code: "budget_exhausted", message: "
|
|
260
|
+
envelope = { ok: false, error: { code: "budget_exhausted", message: "Jev evaluation exceeds the remaining mcpScript intermediate transfer budget (16 MiB per script)." } };
|
|
261
261
|
}
|
|
262
262
|
calls[index] = envelope.ok
|
|
263
263
|
? {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mcp-adapter",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.37.0",
|
|
4
4
|
"description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
"zod": "^3.25.0 || ^4.0.0"
|
|
186
186
|
},
|
|
187
187
|
"peerDependencies": {
|
|
188
|
-
"@earendil-works/pi-ai": "^0.84.1 || ^0.85.0 || ^0.86.0",
|
|
188
|
+
"@earendil-works/pi-ai": "^0.84.1 || ^0.85.0 || ^0.86.0 || ^0.87.0",
|
|
189
189
|
"@earendil-works/pi-tui": "*",
|
|
190
190
|
"typebox": "*",
|
|
191
191
|
"zod": "^3.25.0 || ^4.0.0"
|
|
@@ -202,9 +202,9 @@
|
|
|
202
202
|
}
|
|
203
203
|
},
|
|
204
204
|
"devDependencies": {
|
|
205
|
-
"@earendil-works/pi-ai": "0.
|
|
206
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
207
|
-
"@earendil-works/pi-tui": "0.
|
|
205
|
+
"@earendil-works/pi-ai": "0.87.0",
|
|
206
|
+
"@earendil-works/pi-coding-agent": "0.87.0",
|
|
207
|
+
"@earendil-works/pi-tui": "0.87.0",
|
|
208
208
|
"@modelcontextprotocol/conformance": "0.1.16",
|
|
209
209
|
"@types/bun": "^1.0.0",
|
|
210
210
|
"@types/cross-spawn": "6.0.6",
|
|
@@ -114,10 +114,10 @@ export async function semanticSearch(
|
|
|
114
114
|
try {
|
|
115
115
|
settings = resolveSemanticJevSettings(state);
|
|
116
116
|
} catch {
|
|
117
|
-
return { ok: false, error: { code: "invalid_request", message: "Invalid
|
|
117
|
+
return { ok: false, error: { code: "invalid_request", message: "Invalid Jev semantic search settings." } };
|
|
118
118
|
}
|
|
119
119
|
if (!settings.semanticSearch) {
|
|
120
|
-
return { ok: false, error: { code: "disabled", message: "
|
|
120
|
+
return { ok: false, error: { code: "disabled", message: "Jev semantic search is disabled." } };
|
|
121
121
|
}
|
|
122
122
|
if (settings.allowedServers.length === 0) {
|
|
123
123
|
return { ok: false, error: { code: "data_policy_denied", message: "Semantic search is enabled, but settings.jev.allowedServers is empty. Run /mcp jev setup or allow specific MCP servers." } };
|
|
@@ -166,7 +166,7 @@ export async function semanticSearch(
|
|
|
166
166
|
}
|
|
167
167
|
const answer = envelope.data.answers.match;
|
|
168
168
|
if (!answer || answer.type !== "choice") {
|
|
169
|
-
return { ok: false, error: { code: "invalid_response", message: "
|
|
169
|
+
return { ok: false, error: { code: "invalid_response", message: "Jev returned an invalid semantic search response." } };
|
|
170
170
|
}
|
|
171
171
|
const ranked = candidates
|
|
172
172
|
.map(candidate => ({ candidate, probability: answer.probabilities[candidate.id] ?? 0 }))
|
|
@@ -589,6 +589,8 @@ export type { JevAnswer, JevErrorCode, JevEvaluateInput, JevEvaluationData, JevE
|
|
|
589
589
|
|
|
590
590
|
export interface McpSettings {
|
|
591
591
|
toolPrefix?: ToolPrefix;
|
|
592
|
+
/** Allow agents to persist remote MCP endpoints with the install action. Defaults to true. */
|
|
593
|
+
allowInstall?: boolean;
|
|
592
594
|
/** Show the plug prefix in MCP status and connection text (default: true). Set to false to disable it. */
|
|
593
595
|
showStatusIcon?: boolean;
|
|
594
596
|
/** Footer status verbosity: full details, compact connected/enabled count, or no footer status. Defaults to full. */
|
|
@@ -603,6 +605,8 @@ export interface McpSettings {
|
|
|
603
605
|
agentPluginPaths?: string[];
|
|
604
606
|
idleTimeout?: number; // minutes, default 10, 0 to disable
|
|
605
607
|
requestTimeoutMs?: number; // milliseconds, overrides the SDK request timeout when > 0
|
|
608
|
+
/** Defer lazy runtime startup even when persisted metadata is missing or invalid. Defaults to false. */
|
|
609
|
+
deferWithMissingMetadata?: boolean;
|
|
606
610
|
directTools?: boolean | "search";
|
|
607
611
|
/** Register per-server mcp__<server> namespace proxies. Defaults to true. */
|
|
608
612
|
namespaceProxyTools?: boolean;
|
|
@@ -620,7 +624,9 @@ export interface McpSettings {
|
|
|
620
624
|
warnOnLargeDirectTools?: boolean;
|
|
621
625
|
/** Register the trusted MCP-only JavaScript scripting tool. Defaults to true; set false to hide it. */
|
|
622
626
|
scriptMode?: boolean;
|
|
623
|
-
/**
|
|
627
|
+
/** Expose MCP resources as tools (default: true). Set to false to disable globally across all servers. */
|
|
628
|
+
exposeResources?: boolean;
|
|
629
|
+
/** Optional Jev (System One) integrations. A valid key enables semantic search; script evaluation remains disabled by default. */
|
|
624
630
|
jev?: false | {
|
|
625
631
|
semanticSearch?: boolean;
|
|
626
632
|
scriptEvaluation?: boolean;
|
|
@@ -4,6 +4,18 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
## [1.23.2] — 2026-09-24
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Sessions hosted by pi-web's session daemon no longer deactivate after the first one. The in-process root-activation lease (designed for terminal Pi's `/reload` and in-process children) misclassified every later pi-web session as a subagent child, leaving it in `subagent-child-passive` mode with failover, preflight and auto-continue silently disabled. When `PI_WEB_SESSION=1` is present, each session now activates as an independent root; any other multi-session SDK host can opt in with the host-neutral `PI_MULTI_ACCOUNT_INDEPENDENT_ROOTS=1`. Genuine pi-subagents children still run in a runner process marked `PI_SUBAGENT_CHILD=1` and remain passive.
|
|
12
|
+
- In-process independent roots and same-session rehydrates now share a process-scoped canonical child proxy and publication lifetime. A sibling can exit in either order without restoring real OAuth into child-facing `auth.json` or orphaning `models.json` loopback routes; only the final root closes the listener and restores auth. A root whose proxy is disabled still retires public numbered aliases before another root publishes placeholders, and keeps that listener alive through handoff. The surviving root handles newly discovered slots, while explicit `PI_SUBAGENT_CHILD` remains passive.
|
|
13
|
+
|
|
14
|
+
## [1.23.1] — 2026-09-23
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Restore `pi-ai` as an installed runtime dependency for the OAuth/catalog bridge (#75). Pi-managed npm, bun and pnpm extension installs deliberately omit peer dependencies; v1.23.0 made pi-ai peer-only, so a fresh install could not log into a numbered subscription account. Native provider transports remain bound to the running Pi host, not this bridge copy. The package check now rejects future peer-only regressions.
|
|
7
19
|
|
|
8
20
|
## [1.23.0] — 2026-09-23
|
|
9
21
|
|
|
@@ -27,11 +27,12 @@ pi install npm:pi-multi-account
|
|
|
27
27
|
Restart Pi or run `/reload` after installation.
|
|
28
28
|
|
|
29
29
|
Requires Node 22+ and Agent Pi / pi-ai **>=0.85.1, <0.88.0** (CI covers 0.85.1, 0.86.1 and 0.87.1).
|
|
30
|
-
Pi
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
Pi's host package is a peer. `pi-ai` is a runtime dependency for the OAuth/catalog bridge:
|
|
31
|
+
Pi-managed installs intentionally do not install extension peer dependencies, but subscription
|
|
32
|
+
login must be able to locate pi-ai on disk. Provider transports still use Pi's **host-bound**
|
|
33
|
+
imports, not that filesystem-resolved bridge copy, so a stale nested pi-ai cannot remove tools or
|
|
34
|
+
system instructions. Standalone SDK callers must pair their context with the installed pi-ai
|
|
35
|
+
version. If the OAuth bridge is unavailable, API-key account discovery still works.
|
|
35
36
|
|
|
36
37
|
Kimi OAuth spares remain available in `/login`, but are not written to `models.json`: without a
|
|
37
38
|
Kimi child OAuth proxy those aliases would be resolvable but unauthenticated. Only real API-key
|
|
@@ -178,7 +179,21 @@ Background attempts share the same cooldown and invalidation state as foreground
|
|
|
178
179
|
|
|
179
180
|
### pi-subagents and delegation-broker compatibility
|
|
180
181
|
|
|
181
|
-
`pi-subagents` marks native child processes with `PI_SUBAGENT_CHILD=1` and owns their explicit model plus `fallbackModels` chain.
|
|
182
|
+
`pi-subagents` marks native child processes with `PI_SUBAGENT_CHILD=1` and owns their explicit model plus `fallbackModels` chain. Under terminal Pi, a distinct concurrent in-process child also stays passive while a root activation is live. A replacement with the **same nonempty session ID** reacquires that root's failover ownership without closing its child proxy. The root lease is released at final session shutdown, so `/reload` and `/new` do not permanently lose routing ownership. In passive children this extension stays loaded only for provider/account registration, OAuth request shaping, and catalog support. It deliberately does **not** restore the interactive process's remembered model, persist the child's model as a user preference, switch models, queue work, or auto-continue after errors. The original provider error is returned unchanged so the parent runner can advance its verified fallback chain without a second router competing for model identity.
|
|
183
|
+
|
|
184
|
+
### Multi-session hosts (pi-web and other in-process SDK hosts)
|
|
185
|
+
|
|
186
|
+
Some hosts run many independent sessions in a single long-lived process — pi-web's session daemon, or in-process SDK hosts such as Enso (one session per chat thread). Terminal Pi's in-process root-activation lease would incorrectly demote every session but the first to passive in those hosts.
|
|
187
|
+
|
|
188
|
+
- Under pi-web (`PI_WEB_SESSION=1`, set by the daemon itself) every session activates as an independent root automatically.
|
|
189
|
+
- Any other multi-session host opts in by exporting `PI_MULTI_ACCOUNT_INDEPENDENT_ROOTS=1` in the host process. Set this only when each session truly owns its model; sessions that share one model identity must keep the default single-root behavior.
|
|
190
|
+
- Genuine `pi-subagents` children are unaffected either way: they run in a runner process marked `PI_SUBAGENT_CHILD=1`, which always takes precedence and stays passive.
|
|
191
|
+
|
|
192
|
+
Hosts should not set `PI_WEB_SESSION` themselves — other extensions read it to detect pi-web specifically.
|
|
193
|
+
|
|
194
|
+
In one process, independent roots share one canonical loopback proxy and its child-facing auth/models publication while any root has the child proxy enabled. Closing one root cannot expose its sibling's real OAuth or remove the route its bare children use; the final live root closes the listener and restores the original auth. A rehydrated session with the same ID replaces the old instance's failover ownership rather than counting as a second root. Another process holding the canonical port remains the publisher; a foreign listener is never allowed to overwrite its shared files.
|
|
195
|
+
|
|
196
|
+
Roots with `childProxy: false` still participate in the shared lifetime if another root publishes placeholders. Any public numbered alias they registered earlier is moved to loopback **before** the placeholder appears; they keep the canonical listener alive if the publisher exits first, and only the final root restores auth. With no other publishing root, disabling `childProxy` retains the usual direct route. Hosts should still keep one consistent setting across concurrent roots.
|
|
182
197
|
|
|
183
198
|
[`pi-delegation-broker`](https://github.com/Sarrius/pi-delegation-broker) is an optional companion for splitting work among isolated child agents, with task budgets, reports and verification. Each extension works independently and depends on Agent Pi, not on the other extension. Together, Multi Account manages your interactive account rotation while the broker delegates through Pi's registered providers and models. Neither installs or initializes the other.
|
|
184
199
|
|