@blackbelt-technology/pi-agent-dashboard 0.5.0 → 0.5.2
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/AGENTS.md +26 -5
- package/README.md +49 -7
- package/docs/architecture.md +129 -1
- package/package.json +15 -15
- package/packages/extension/package.json +11 -3
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +1 -1
- package/packages/extension/src/__tests__/bridge-slash-command-routing.test.ts +362 -0
- package/packages/extension/src/__tests__/command-handler.test.ts +78 -8
- package/packages/extension/src/__tests__/enrich-model-metadata.test.ts +1 -1
- package/packages/extension/src/__tests__/extension-slash-command-detection.test.ts +107 -0
- package/packages/extension/src/__tests__/no-tui-multiselect-arm-regression.test.ts +1 -1
- package/packages/extension/src/__tests__/prompt-expander.test.ts +110 -1
- package/packages/extension/src/__tests__/provider-register-reload.test.ts +74 -0
- package/packages/extension/src/__tests__/retry-tracker.test.ts +147 -0
- package/packages/extension/src/__tests__/server-launcher-launch.test.ts +78 -0
- package/packages/extension/src/__tests__/session-sync.test.ts +72 -0
- package/packages/extension/src/__tests__/usage-limit-orderer.test.ts +105 -0
- package/packages/extension/src/ask-user-tool.ts +1 -1
- package/packages/extension/src/bridge-context.ts +68 -4
- package/packages/extension/src/bridge.ts +79 -11
- package/packages/extension/src/command-handler.ts +95 -15
- package/packages/extension/src/flow-event-wiring.ts +1 -1
- package/packages/extension/src/multiselect-list.ts +1 -1
- package/packages/extension/src/pi-env.d.ts +16 -9
- package/packages/extension/src/prompt-expander.ts +74 -63
- package/packages/extension/src/provider-register.ts +16 -9
- package/packages/extension/src/retry-tracker.ts +123 -0
- package/packages/extension/src/server-launcher.ts +31 -70
- package/packages/extension/src/session-sync.ts +10 -1
- package/packages/extension/src/slash-dispatch.ts +123 -0
- package/packages/extension/src/usage-limit-orderer.ts +76 -0
- package/packages/server/bin/pi-dashboard.mjs +84 -0
- package/packages/server/package.json +8 -7
- package/packages/server/scripts/fix-pty-permissions.cjs +52 -0
- package/packages/server/src/__tests__/changelog-fs.test.ts +171 -0
- package/packages/server/src/__tests__/changelog-parser.test.ts +220 -0
- package/packages/server/src/__tests__/changelog-remote.test.ts +193 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +16 -4
- package/packages/server/src/__tests__/directory-service-openspec-enabled.test.ts +187 -0
- package/packages/server/src/__tests__/directory-service-refresh-force.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service-specs-mtime.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service-toctou.test.ts +1 -1
- package/packages/server/src/__tests__/directory-service.test.ts +2 -2
- package/packages/server/src/__tests__/dispatch-extension-command-router.test.ts +178 -0
- package/packages/server/src/__tests__/e2e/model-proxy-google-flash.test.ts +184 -0
- package/packages/server/src/__tests__/event-wiring-providers-list.test.ts +68 -1
- package/packages/server/src/__tests__/fixtures/pi-changelog-slice.md +180 -0
- package/packages/server/src/__tests__/fork-empty-session-preflight.test.ts +268 -0
- package/packages/server/src/__tests__/headless-pid-registry.test.ts +316 -0
- package/packages/server/src/__tests__/is-pi-process.test.ts +1 -1
- package/packages/server/src/__tests__/keeper-manager.test.ts +298 -0
- package/packages/server/src/__tests__/legacy-pi-cleanup.test.ts +149 -0
- package/packages/server/src/__tests__/model-proxy-api-key-routes.test.ts +277 -0
- package/packages/server/src/__tests__/model-proxy-auth-gate.test.ts +263 -0
- package/packages/server/src/__tests__/model-proxy-multi-user.test.ts +169 -0
- package/packages/server/src/__tests__/model-proxy-routes.test.ts +286 -0
- package/packages/server/src/__tests__/model-proxy-second-port.test.ts +116 -0
- package/packages/server/src/__tests__/openspec-connect-snapshot.test.ts +64 -8
- package/packages/server/src/__tests__/openspec-group-broadcast.test.ts +97 -0
- package/packages/server/src/__tests__/openspec-group-join.test.ts +80 -0
- package/packages/server/src/__tests__/openspec-group-routes.test.ts +370 -0
- package/packages/server/src/__tests__/openspec-group-store.test.ts +496 -0
- package/packages/server/src/__tests__/package-manager-wrapper-resolve.test.ts +4 -4
- package/packages/server/src/__tests__/package-routes.test.ts +1 -1
- package/packages/server/src/__tests__/pending-fork-registry.test.ts +48 -24
- package/packages/server/src/__tests__/pi-ai-shape.test.ts +147 -0
- package/packages/server/src/__tests__/pi-changelog-integration.test.ts +165 -0
- package/packages/server/src/__tests__/pi-changelog-routes.test.ts +409 -0
- package/packages/server/src/__tests__/pi-core-checker.test.ts +155 -13
- package/packages/server/src/__tests__/pi-core-updater-managed-path.test.ts +62 -3
- package/packages/server/src/__tests__/pi-core-updater.test.ts +1 -1
- package/packages/server/src/__tests__/pi-dashboard-bin-wrapper.test.ts +84 -0
- package/packages/server/src/__tests__/pi-dev-version-check.test.ts +184 -0
- package/packages/server/src/__tests__/pi-version-skew.test.ts +4 -4
- package/packages/server/src/__tests__/process-manager-keeper-spawn.test.ts +206 -0
- package/packages/server/src/__tests__/provider-auth-routes.test.ts +12 -4
- package/packages/server/src/__tests__/provider-catalogue-cache.test.ts +13 -23
- package/packages/server/src/__tests__/provider-routes-recursion-guard.test.ts +131 -0
- package/packages/server/src/__tests__/recommended-routes.test.ts +3 -3
- package/packages/server/src/__tests__/spawn-correlation-token-integration.test.ts +91 -0
- package/packages/server/src/__tests__/spawn-register-watchdog.test.ts +84 -0
- package/packages/server/src/__tests__/spawn-token.test.ts +57 -0
- package/packages/server/src/__tests__/tunnel-watchdog.test.ts +139 -0
- package/packages/server/src/auth-plugin.ts +3 -0
- package/packages/server/src/bootstrap-state.ts +10 -0
- package/packages/server/src/browser-gateway.ts +27 -10
- package/packages/server/src/browser-handlers/handler-context.ts +9 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +128 -19
- package/packages/server/src/changelog-fs.ts +167 -0
- package/packages/server/src/changelog-parser.ts +321 -0
- package/packages/server/src/changelog-remote.ts +134 -0
- package/packages/server/src/cli.ts +62 -82
- package/packages/server/src/config-api.ts +14 -2
- package/packages/server/src/directory-service.ts +106 -4
- package/packages/server/src/event-wiring.ts +90 -6
- package/packages/server/src/headless-pid-registry.ts +344 -37
- package/packages/server/src/legacy-pi-cleanup.ts +151 -0
- package/packages/server/src/model-proxy/__tests__/api-key-store.test.ts +142 -0
- package/packages/server/src/model-proxy/__tests__/auth-json-contention.test.ts +98 -0
- package/packages/server/src/model-proxy/__tests__/concurrency.test.ts +107 -0
- package/packages/server/src/model-proxy/__tests__/failed-auth-backoff.test.ts +46 -0
- package/packages/server/src/model-proxy/__tests__/recursion-guard.test.ts +61 -0
- package/packages/server/src/model-proxy/__tests__/streamer.test.ts +139 -0
- package/packages/server/src/model-proxy/api-key-store.ts +87 -0
- package/packages/server/src/model-proxy/auth-gate.ts +116 -0
- package/packages/server/src/model-proxy/concurrency.ts +76 -0
- package/packages/server/src/model-proxy/convert/UPSTREAM.md +13 -0
- package/packages/server/src/model-proxy/convert/__tests__/anthropic-in.test.ts +137 -0
- package/packages/server/src/model-proxy/convert/__tests__/anthropic-out.test.ts +183 -0
- package/packages/server/src/model-proxy/convert/__tests__/openai-in.test.ts +134 -0
- package/packages/server/src/model-proxy/convert/__tests__/openai-out.test.ts +166 -0
- package/packages/server/src/model-proxy/convert/anthropic-in.ts +129 -0
- package/packages/server/src/model-proxy/convert/anthropic-out.ts +173 -0
- package/packages/server/src/model-proxy/convert/index.ts +8 -0
- package/packages/server/src/model-proxy/convert/openai-in.ts +119 -0
- package/packages/server/src/model-proxy/convert/openai-out.ts +151 -0
- package/packages/server/src/model-proxy/convert/types.ts +70 -0
- package/packages/server/src/model-proxy/failed-auth-backoff.ts +45 -0
- package/packages/server/src/model-proxy/internal-auth-storage.ts +146 -0
- package/packages/server/src/model-proxy/internal-registry.ts +157 -0
- package/packages/server/src/model-proxy/recursion-guard.ts +72 -0
- package/packages/server/src/model-proxy/registry-singleton.ts +109 -0
- package/packages/server/src/model-proxy/request-log.ts +53 -0
- package/packages/server/src/model-proxy/streamer.ts +59 -0
- package/packages/server/src/openspec-group-store.ts +490 -0
- package/packages/server/src/pending-client-correlations.ts +73 -0
- package/packages/server/src/pending-fork-registry.ts +24 -12
- package/packages/server/src/pi-core-checker.ts +77 -17
- package/packages/server/src/pi-core-updater.ts +16 -6
- package/packages/server/src/pi-dev-version-check.ts +145 -0
- package/packages/server/src/pi-gateway.ts +4 -0
- package/packages/server/src/pi-version-skew.ts +12 -4
- package/packages/server/src/process-manager.ts +182 -11
- package/packages/server/src/provider-auth-storage.ts +29 -47
- package/packages/server/src/provider-catalogue-cache.ts +24 -18
- package/packages/server/src/restart-helper.ts +17 -16
- package/packages/server/src/routes/bootstrap-routes.ts +37 -0
- package/packages/server/src/routes/jj-routes.ts +3 -0
- package/packages/server/src/routes/model-proxy-api-key-routes.ts +168 -0
- package/packages/server/src/routes/model-proxy-refresh-routes.ts +24 -0
- package/packages/server/src/routes/model-proxy-routes.ts +330 -0
- package/packages/server/src/routes/openspec-group-routes.ts +231 -0
- package/packages/server/src/routes/pi-changelog-routes.ts +194 -0
- package/packages/server/src/routes/pi-core-routes.ts +1 -1
- package/packages/server/src/routes/provider-auth-routes.ts +8 -1
- package/packages/server/src/routes/provider-routes.ts +28 -5
- package/packages/server/src/routes/system-routes.ts +44 -2
- package/packages/server/src/rpc-keeper/__tests__/fixtures/mock-pi-shim.sh +9 -0
- package/packages/server/src/rpc-keeper/__tests__/fixtures/mock-pi.cjs +50 -0
- package/packages/server/src/rpc-keeper/__tests__/keeper.test.ts +371 -0
- package/packages/server/src/rpc-keeper/dispatch-router.ts +85 -0
- package/packages/server/src/rpc-keeper/keeper-manager.ts +364 -0
- package/packages/server/src/rpc-keeper/keeper.cjs +313 -0
- package/packages/server/src/server.ts +254 -60
- package/packages/server/src/session-api.ts +63 -4
- package/packages/server/src/session-discovery.ts +1 -1
- package/packages/server/src/session-file-reader.ts +1 -1
- package/packages/server/src/spawn-register-watchdog.ts +62 -7
- package/packages/server/src/spawn-token.ts +20 -0
- package/packages/server/src/tunnel-watchdog.ts +230 -0
- package/packages/server/src/tunnel.ts +5 -1
- package/packages/shared/package.json +1 -1
- package/packages/shared/src/__tests__/binary-lookup-resolveJiti.test.ts +228 -0
- package/packages/shared/src/__tests__/bootstrap/__snapshots__/cube.test.ts.snap +24 -17
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/a-electron.test.ts.snap +5 -4
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/b-npm-global.test.ts.snap +6 -5
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/c-dev-monorepo.test.ts.snap +1 -0
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/e-stale-partial.test.ts.snap +5 -4
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/f-cwd-variants.test.ts.snap +2 -1
- package/packages/shared/src/__tests__/bootstrap/families/__snapshots__/g-windows-specifics.test.ts.snap +5 -3
- package/packages/shared/src/__tests__/bootstrap/fixtures/dev-monorepo.ts +1 -1
- package/packages/shared/src/__tests__/changelog-types.test.ts +78 -0
- package/packages/shared/src/__tests__/config-openspec.test.ts +74 -0
- package/packages/shared/src/__tests__/model-proxy-config.test.ts +146 -0
- package/packages/shared/src/__tests__/no-raw-node-import.test.ts +7 -5
- package/packages/shared/src/__tests__/node-spawn-jiti-contract.test.ts +56 -20
- package/packages/shared/src/__tests__/node-spawn.test.ts +51 -0
- package/packages/shared/src/__tests__/openspec-groups-types.test.ts +135 -0
- package/packages/shared/src/__tests__/publish-workflow-contract.test.ts +96 -0
- package/packages/shared/src/__tests__/recommended-extensions.test.ts +11 -3
- package/packages/shared/src/__tests__/server-launcher.test.ts +227 -0
- package/packages/shared/src/__tests__/tool-registry-definitions.test.ts +1 -1
- package/packages/shared/src/bootstrap-install.ts +1 -1
- package/packages/shared/src/browser-protocol.ts +70 -0
- package/packages/shared/src/changelog-types.ts +111 -0
- package/packages/shared/src/config.ts +172 -2
- package/packages/shared/src/dashboard-plugin/manifest-types.ts +16 -1
- package/packages/shared/src/dashboard-plugin/slot-props.ts +8 -0
- package/packages/shared/src/dashboard-plugin/slot-types.ts +57 -0
- package/packages/shared/src/platform/binary-lookup.ts +204 -0
- package/packages/shared/src/platform/node-spawn.ts +71 -26
- package/packages/shared/src/protocol.ts +27 -1
- package/packages/shared/src/recommended-extensions.ts +18 -0
- package/packages/shared/src/rest-api.ts +219 -1
- package/packages/shared/src/server-launcher.ts +277 -0
- package/packages/shared/src/skill-block-parser.ts +1 -1
- package/packages/shared/src/tool-registry/__tests__/pi-ai-registration.test.ts +124 -0
- package/packages/shared/src/tool-registry/definitions.ts +15 -3
- package/packages/shared/src/types.ts +62 -0
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +0 -53
- package/packages/shared/src/resolve-jiti.ts +0 -102
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST route for `GET /api/pi-core/changelog`.
|
|
3
|
+
*
|
|
4
|
+
* Returns the parsed `CHANGELOG.md` for a core package filtered to
|
|
5
|
+
* a `(from, to]` half-open version range, plus a derived `hasBreaking`
|
|
6
|
+
* flag and a public GitHub URL for the full changelog.
|
|
7
|
+
*
|
|
8
|
+
* Gated identically to other `/api/pi-core/*` routes via the shared
|
|
9
|
+
* bootstrap-status gate.
|
|
10
|
+
*
|
|
11
|
+
* See change: pi-update-whats-new-panel.
|
|
12
|
+
*/
|
|
13
|
+
import type { FastifyInstance } from "fastify";
|
|
14
|
+
import type {
|
|
15
|
+
ChangelogResponse,
|
|
16
|
+
} from "@blackbelt-technology/pi-dashboard-shared/changelog-types.js";
|
|
17
|
+
import { CORE_PACKAGE_NAMES } from "../pi-core-checker.js";
|
|
18
|
+
import { parseVersion, compareVersions } from "../pi-version-skew.js";
|
|
19
|
+
import {
|
|
20
|
+
findChangelogPath,
|
|
21
|
+
readPackageJson,
|
|
22
|
+
deriveChangelogUrl,
|
|
23
|
+
} from "../changelog-fs.js";
|
|
24
|
+
import {
|
|
25
|
+
parseChangelog,
|
|
26
|
+
readAndParseChangelog,
|
|
27
|
+
getCachedRemoteChangelog,
|
|
28
|
+
setRemoteChangelog,
|
|
29
|
+
refreshRemoteChangelogTtl,
|
|
30
|
+
} from "../changelog-parser.js";
|
|
31
|
+
import {
|
|
32
|
+
deriveChangelogRawUrl,
|
|
33
|
+
fetchRemoteChangelog,
|
|
34
|
+
} from "../changelog-remote.js";
|
|
35
|
+
import type { ChangelogRelease } from "@blackbelt-technology/pi-dashboard-shared/changelog-types.js";
|
|
36
|
+
import type { BootstrapStateStore } from "../bootstrap-state.js";
|
|
37
|
+
|
|
38
|
+
export interface PiChangelogRouteDeps {
|
|
39
|
+
/** When provided, route returns 503 unless bootstrap status is "ready". */
|
|
40
|
+
bootstrapState?: BootstrapStateStore;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface QueryShape {
|
|
44
|
+
pkg?: string;
|
|
45
|
+
from?: string;
|
|
46
|
+
to?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function registerPiChangelogRoutes(
|
|
50
|
+
fastify: FastifyInstance,
|
|
51
|
+
deps: PiChangelogRouteDeps,
|
|
52
|
+
): void {
|
|
53
|
+
const { bootstrapState } = deps;
|
|
54
|
+
|
|
55
|
+
const bootstrapGate = async (
|
|
56
|
+
_req: unknown,
|
|
57
|
+
reply: { code: (n: number) => { send: (body: unknown) => unknown } },
|
|
58
|
+
): Promise<unknown> => {
|
|
59
|
+
if (!bootstrapState) return undefined;
|
|
60
|
+
const status = bootstrapState.get().status;
|
|
61
|
+
if (status === "ready") return undefined;
|
|
62
|
+
return reply.code(503).send({
|
|
63
|
+
success: false,
|
|
64
|
+
error: `pi not yet installed (bootstrap status: ${status})`,
|
|
65
|
+
bootstrap: status,
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
fastify.get<{ Querystring: QueryShape }>(
|
|
70
|
+
"/api/pi-core/changelog",
|
|
71
|
+
{ preHandler: bootstrapGate as any },
|
|
72
|
+
async (request, reply) => {
|
|
73
|
+
const pkg = (request.query.pkg ?? "").trim();
|
|
74
|
+
const from = (request.query.from ?? "").trim();
|
|
75
|
+
const to = (request.query.to ?? "").trim();
|
|
76
|
+
|
|
77
|
+
// Validate `pkg` against the core whitelist BEFORE touching
|
|
78
|
+
// the filesystem — prevents arbitrary path reads via crafted
|
|
79
|
+
// input.
|
|
80
|
+
if (!pkg || !CORE_PACKAGE_NAMES.includes(pkg)) {
|
|
81
|
+
return reply.code(400).send({
|
|
82
|
+
success: false,
|
|
83
|
+
error: `pkg must be one of: ${CORE_PACKAGE_NAMES.join(", ")}`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Validate version range using the existing parseVersion helper
|
|
88
|
+
// from pi-version-skew. Both endpoints are required.
|
|
89
|
+
if (!from || !to) {
|
|
90
|
+
return reply.code(400).send({
|
|
91
|
+
success: false,
|
|
92
|
+
error: "from and to query params are required",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (!parseVersion(from) || !parseVersion(to)) {
|
|
96
|
+
return reply.code(400).send({
|
|
97
|
+
success: false,
|
|
98
|
+
error: "from and to must be parseable semver versions",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const located = findChangelogPath(pkg);
|
|
103
|
+
|
|
104
|
+
// Spec: package not installed / CHANGELOG missing → 200 with empty body.
|
|
105
|
+
if (!located) {
|
|
106
|
+
const body: ChangelogResponse = {
|
|
107
|
+
pkg,
|
|
108
|
+
from,
|
|
109
|
+
to,
|
|
110
|
+
releases: [],
|
|
111
|
+
hasBreaking: false,
|
|
112
|
+
changelogUrl: null,
|
|
113
|
+
parsedAt: new Date().toISOString(),
|
|
114
|
+
};
|
|
115
|
+
return body;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Read package.json once for both URLs (raw for the parser,
|
|
119
|
+
// human for the dialog footer link).
|
|
120
|
+
const pkgJson = readPackageJson(located.packageDir);
|
|
121
|
+
const rawUrl = pkgJson ? deriveChangelogRawUrl(pkgJson.repository) : null;
|
|
122
|
+
const changelogUrl = pkgJson ? deriveChangelogUrl(pkgJson.repository) : null;
|
|
123
|
+
|
|
124
|
+
// Try remote first — the upstream CHANGELOG describes versions
|
|
125
|
+
// newer than the locally-installed tarball. Fall back to local
|
|
126
|
+
// on failure / offline / non-GitHub repo. See change:
|
|
127
|
+
// read-changelog-from-github.
|
|
128
|
+
let allReleases: ChangelogRelease[] | undefined;
|
|
129
|
+
let usedRemote = false;
|
|
130
|
+
|
|
131
|
+
if (rawUrl) {
|
|
132
|
+
const cached = getCachedRemoteChangelog(pkg);
|
|
133
|
+
if (cached && !cached.expired) {
|
|
134
|
+
// Within TTL — reuse cached result, no fetch.
|
|
135
|
+
allReleases = cached.releases;
|
|
136
|
+
usedRemote = true;
|
|
137
|
+
} else {
|
|
138
|
+
const fetchResult = await fetchRemoteChangelog(rawUrl, {
|
|
139
|
+
etag: cached?.etag ?? null,
|
|
140
|
+
});
|
|
141
|
+
if (fetchResult?.status === "ok") {
|
|
142
|
+
const parsed = parseChangelog(fetchResult.text);
|
|
143
|
+
setRemoteChangelog(pkg, parsed, fetchResult.etag);
|
|
144
|
+
allReleases = parsed;
|
|
145
|
+
usedRemote = true;
|
|
146
|
+
} else if (fetchResult?.status === "not-modified" && cached) {
|
|
147
|
+
refreshRemoteChangelogTtl(pkg);
|
|
148
|
+
allReleases = cached.releases;
|
|
149
|
+
usedRemote = true;
|
|
150
|
+
}
|
|
151
|
+
// null result (network error / offline / non-2xx): fall through
|
|
152
|
+
// to local read below.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!usedRemote) {
|
|
157
|
+
try {
|
|
158
|
+
allReleases = readAndParseChangelog(pkg, located.changelogPath);
|
|
159
|
+
} catch (err: any) {
|
|
160
|
+
request.log.warn(
|
|
161
|
+
{ err: err?.message },
|
|
162
|
+
"[pi-changelog-routes] local read/parse failed; returning empty",
|
|
163
|
+
);
|
|
164
|
+
allReleases = [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Filter to (from, to]. Unparseable release versions are
|
|
169
|
+
// dropped — conservative.
|
|
170
|
+
const filtered = (allReleases ?? []).filter((r) => {
|
|
171
|
+
const rv = parseVersion(r.version);
|
|
172
|
+
if (!rv) return false;
|
|
173
|
+
return compareVersions(r.version, from) > 0 &&
|
|
174
|
+
compareVersions(r.version, to) <= 0;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const hasBreaking = filtered.some((r) => r.breaking.length > 0);
|
|
178
|
+
|
|
179
|
+
const body: ChangelogResponse = {
|
|
180
|
+
pkg,
|
|
181
|
+
from,
|
|
182
|
+
to,
|
|
183
|
+
releases: filtered,
|
|
184
|
+
hasBreaking,
|
|
185
|
+
changelogUrl,
|
|
186
|
+
parsedAt: new Date().toISOString(),
|
|
187
|
+
};
|
|
188
|
+
return body;
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Re-export for tests that need to bypass the cache.
|
|
194
|
+
export { parseChangelog };
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* POST /api/pi-core/update { packages?: string[] }
|
|
6
6
|
*
|
|
7
7
|
* Complements /api/packages/* (extension management): this endpoint covers
|
|
8
|
-
* globally-installed pi CLI packages like @
|
|
8
|
+
* globally-installed pi CLI packages like @earendil-works/pi-coding-agent,
|
|
9
9
|
* pi-dashboard itself, pi-model-proxy, etc.
|
|
10
10
|
*/
|
|
11
11
|
import type { FastifyInstance } from "fastify";
|
|
@@ -22,6 +22,7 @@ import { getLatestCatalogue } from "../provider-catalogue-cache.js";
|
|
|
22
22
|
import { startCallbackServer } from "../oauth-callback-server.js";
|
|
23
23
|
import type { PiGateway } from "../pi-gateway.js";
|
|
24
24
|
import type { BrowserGateway } from "../browser-gateway.js";
|
|
25
|
+
import { refreshModelRegistry } from "../model-proxy/registry-singleton.js";
|
|
25
26
|
|
|
26
27
|
// ── In-memory flow store (short-lived PKCE + device code state) ──────────────
|
|
27
28
|
|
|
@@ -88,8 +89,14 @@ export function registerProviderAuthRoutes(
|
|
|
88
89
|
const { piGateway, browserGateway } = deps;
|
|
89
90
|
|
|
90
91
|
function notifyBridges() {
|
|
92
|
+
// Tell every bridge to reload auth.json + refresh its model registry.
|
|
93
|
+
// Each bridge will then push a fresh per-session models_list (and
|
|
94
|
+
// providers_list); browsers pick those up via the existing per-session
|
|
95
|
+
// broadcast and update modelsMap / catalogue cache without needing a
|
|
96
|
+
// global wipe. See change: simplify-model-selection-channels.
|
|
91
97
|
piGateway.broadcast({ type: "credentials_updated" });
|
|
92
|
-
|
|
98
|
+
// Eager-refresh model proxy registry so /v1/models reflects the change.
|
|
99
|
+
refreshModelRegistry().catch(() => {});
|
|
93
100
|
}
|
|
94
101
|
|
|
95
102
|
// List OAuth providers
|
|
@@ -9,6 +9,9 @@ import type { NetworkGuard } from "./route-deps.js";
|
|
|
9
9
|
import type { PiGateway } from "../pi-gateway.js";
|
|
10
10
|
import type { BrowserGateway } from "../browser-gateway.js";
|
|
11
11
|
import { probeProvider, resolveProbeApiKey, type ProbeApi } from "../provider-probe.js";
|
|
12
|
+
import { refreshModelRegistry } from "../model-proxy/registry-singleton.js";
|
|
13
|
+
import { isSelfPointing, collectDashboardOrigins } from "../model-proxy/recursion-guard.js";
|
|
14
|
+
import { getTunnelUrl } from "../tunnel.js";
|
|
12
15
|
|
|
13
16
|
const REDACTED = "***";
|
|
14
17
|
const CONFIG_PATH = join(homedir(), ".pi", "agent", "providers.json");
|
|
@@ -47,7 +50,7 @@ function redactProviders(
|
|
|
47
50
|
return redacted;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
export function registerProviderRoutes(fastify: FastifyInstance, deps: { networkGuard: NetworkGuard; piGateway?: PiGateway; browserGateway?: BrowserGateway }): void {
|
|
53
|
+
export function registerProviderRoutes(fastify: FastifyInstance, deps: { networkGuard: NetworkGuard; piGateway?: PiGateway; browserGateway?: BrowserGateway; port?: number }): void {
|
|
51
54
|
const { networkGuard, piGateway } = deps;
|
|
52
55
|
fastify.get(
|
|
53
56
|
"/api/providers",
|
|
@@ -68,6 +71,23 @@ export function registerProviderRoutes(fastify: FastifyInstance, deps: { network
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
const incoming = body.providers as Record<string, ProviderEntry>;
|
|
74
|
+
|
|
75
|
+
// Recursion guard: reject providers pointing back at the dashboard
|
|
76
|
+
const dashboardPort = deps.port ?? 8000;
|
|
77
|
+
const tunnelUrl = getTunnelUrl();
|
|
78
|
+
const tunnelHostname = tunnelUrl ? new URL(tunnelUrl).hostname : undefined;
|
|
79
|
+
const origins = collectDashboardOrigins(dashboardPort, { tunnelHostname });
|
|
80
|
+
for (const [name, entry] of Object.entries(incoming)) {
|
|
81
|
+
if (entry.baseUrl && isSelfPointing(entry.baseUrl, origins)) {
|
|
82
|
+
return reply.code(400).send({
|
|
83
|
+
success: false,
|
|
84
|
+
code: "RECURSIVE_PROXY",
|
|
85
|
+
message: `Provider "${name}" baseUrl points back at this dashboard`,
|
|
86
|
+
offendingBaseUrl: entry.baseUrl,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
71
91
|
const existing = readProvidersRaw();
|
|
72
92
|
|
|
73
93
|
// Merge: preserve redacted apiKey values from existing file
|
|
@@ -98,13 +118,16 @@ export function registerProviderRoutes(fastify: FastifyInstance, deps: { network
|
|
|
98
118
|
mkdirSync(dir, { recursive: true });
|
|
99
119
|
writeFileSync(CONFIG_PATH, JSON.stringify(fileData, null, 2) + "\n", "utf-8");
|
|
100
120
|
|
|
101
|
-
// Broadcast credentials_updated so
|
|
121
|
+
// Broadcast credentials_updated so each bridge re-reads providers.json
|
|
122
|
+
// and pushes a fresh per-session models_list. Browsers receive those
|
|
123
|
+
// pushes via the existing per-session broadcast — no global wipe.
|
|
124
|
+
// See change: simplify-model-selection-channels.
|
|
102
125
|
if (piGateway) {
|
|
103
126
|
piGateway.broadcast({ type: "credentials_updated" });
|
|
104
127
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
128
|
+
|
|
129
|
+
// Eager-refresh model proxy registry so /v1/models reflects the change.
|
|
130
|
+
refreshModelRegistry().catch(() => {});
|
|
108
131
|
|
|
109
132
|
return { success: true };
|
|
110
133
|
},
|
|
@@ -13,7 +13,9 @@ import type { NetworkGuard } from "./route-deps.js";
|
|
|
13
13
|
import { detectEditors, EDITORS } from "../editor-registry.js";
|
|
14
14
|
import { detectCodeServerBinary, resetDetectionCache } from "../editor-detection.js";
|
|
15
15
|
import { readConfigRedacted, writeConfigPartial } from "../config-api.js";
|
|
16
|
-
import { createTunnel, deleteTunnel, getTunnelStatus } from "../tunnel.js";
|
|
16
|
+
import { createTunnel, deleteTunnel, getTunnelStatus, getTunnelUrl } from "../tunnel.js";
|
|
17
|
+
import { getModelProxyStatus } from "../model-proxy/registry-singleton.js";
|
|
18
|
+
import { startTunnelWatchdog, stopTunnelWatchdog } from "../tunnel-watchdog.js";
|
|
17
19
|
import { spawnRestart } from "../restart-helper.js";
|
|
18
20
|
import { spawn } from "@blackbelt-technology/pi-dashboard-shared/platform/exec.js";
|
|
19
21
|
import path from "node:path";
|
|
@@ -152,6 +154,29 @@ export function registerSystemRoutes(
|
|
|
152
154
|
if (partial.openspec !== undefined && directoryService) {
|
|
153
155
|
directoryService.reconfigurePolling(reloaded.openspec);
|
|
154
156
|
}
|
|
157
|
+
// Live-reload tunnel watchdog when its config changes (no restart needed).
|
|
158
|
+
// We always restart the watchdog when partial.tunnel is present and a
|
|
159
|
+
// tunnel is currently active — covers both watchdog flag changes and
|
|
160
|
+
// numeric tweaks. Cheap operation: stop + start with new config.
|
|
161
|
+
if (partial.tunnel !== undefined) {
|
|
162
|
+
config.tunnelWatchdog = reloaded.tunnel.watchdog;
|
|
163
|
+
if (getTunnelUrl()) {
|
|
164
|
+
stopTunnelWatchdog();
|
|
165
|
+
const wd = reloaded.tunnel.watchdog;
|
|
166
|
+
if (wd?.enabled !== false) {
|
|
167
|
+
startTunnelWatchdog(
|
|
168
|
+
{
|
|
169
|
+
getUrl: getTunnelUrl,
|
|
170
|
+
recycle: async () => {
|
|
171
|
+
await deleteTunnel(config.port);
|
|
172
|
+
return await createTunnel(config.port, config.tunnelReservedToken);
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
wd,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
155
180
|
|
|
156
181
|
return { success: true, restartRequired: result.restartRequired };
|
|
157
182
|
},
|
|
@@ -167,13 +192,29 @@ export function registerSystemRoutes(
|
|
|
167
192
|
if (status.status === "active") return { ok: true, url: status.url };
|
|
168
193
|
if (status.status === "unavailable") return { ok: false, error: "zrok not installed" };
|
|
169
194
|
const url = await createTunnel(config.port, config.tunnelReservedToken);
|
|
170
|
-
if (url)
|
|
195
|
+
if (url) {
|
|
196
|
+
const wd = config.tunnelWatchdog;
|
|
197
|
+
if (wd?.enabled !== false) {
|
|
198
|
+
startTunnelWatchdog(
|
|
199
|
+
{
|
|
200
|
+
getUrl: getTunnelUrl,
|
|
201
|
+
recycle: async () => {
|
|
202
|
+
await deleteTunnel(config.port);
|
|
203
|
+
return await createTunnel(config.port, config.tunnelReservedToken);
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
wd,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return { ok: true, url };
|
|
210
|
+
}
|
|
171
211
|
return { ok: false, error: "Failed to create tunnel" };
|
|
172
212
|
});
|
|
173
213
|
|
|
174
214
|
fastify.post("/api/tunnel-disconnect", async () => {
|
|
175
215
|
// Pass port so orphan zrok processes bound to this endpoint are also
|
|
176
216
|
// swept (not just the one we tracked via pid-file).
|
|
217
|
+
stopTunnelWatchdog();
|
|
177
218
|
await deleteTunnel(config.port);
|
|
178
219
|
return { ok: true };
|
|
179
220
|
});
|
|
@@ -206,6 +247,7 @@ export function registerSystemRoutes(
|
|
|
206
247
|
},
|
|
207
248
|
agents: agentMetrics,
|
|
208
249
|
plugins: getPluginStatusStore().listAll(),
|
|
250
|
+
proxy: getModelProxyStatus(),
|
|
209
251
|
};
|
|
210
252
|
});
|
|
211
253
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PATH-shim used in keeper.test.ts to make `pi --mode rpc` invoke our
|
|
3
|
+
# mock-pi.cjs instead of the real pi binary.
|
|
4
|
+
#
|
|
5
|
+
# The keeper spawns `pi --mode rpc` from PATH; we prepend the dir
|
|
6
|
+
# containing this script (named `pi`) to PATH so this shim wins.
|
|
7
|
+
# The path to mock-pi.cjs is passed via env var so the shim can be
|
|
8
|
+
# placed anywhere without copying mock-pi.cjs alongside it.
|
|
9
|
+
exec node "${MOCK_PI_CJS_PATH:?MOCK_PI_CJS_PATH env var required}" "$@"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Mock pi for keeper integration tests.
|
|
4
|
+
*
|
|
5
|
+
* Reads JSON-line input from stdin and appends each line to the file at
|
|
6
|
+
* `process.env.MOCK_PI_LOG`. Exits 0 on stdin EOF.
|
|
7
|
+
*
|
|
8
|
+
* Behavior modes (via env):
|
|
9
|
+
* MOCK_PI_MODE=normal (default) — read until EOF, log lines, exit 0
|
|
10
|
+
* MOCK_PI_MODE=crash — exit non-zero immediately (tests
|
|
11
|
+
* keeper crash-detection window)
|
|
12
|
+
*
|
|
13
|
+
* CommonJS-pure, only Node built-ins.
|
|
14
|
+
*/
|
|
15
|
+
"use strict";
|
|
16
|
+
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
|
|
19
|
+
const mode = process.env.MOCK_PI_MODE || "normal";
|
|
20
|
+
const logPath = process.env.MOCK_PI_LOG;
|
|
21
|
+
|
|
22
|
+
if (mode === "crash") {
|
|
23
|
+
process.stderr.write("[mock-pi] crash mode: exiting 1 immediately\n");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!logPath) {
|
|
28
|
+
process.stderr.write("[mock-pi] FATAL: MOCK_PI_LOG env var required\n");
|
|
29
|
+
process.exit(2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let buf = "";
|
|
33
|
+
process.stdin.setEncoding("utf8");
|
|
34
|
+
process.stdin.on("data", (chunk) => {
|
|
35
|
+
buf += chunk;
|
|
36
|
+
let nl;
|
|
37
|
+
// eslint-disable-next-line no-cond-assign
|
|
38
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
39
|
+
const line = buf.slice(0, nl);
|
|
40
|
+
buf = buf.slice(nl + 1);
|
|
41
|
+
fs.appendFileSync(logPath, line + "\n");
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
process.stdin.on("end", () => {
|
|
45
|
+
if (buf.length > 0) {
|
|
46
|
+
fs.appendFileSync(logPath, buf + "\n");
|
|
47
|
+
}
|
|
48
|
+
process.exit(0);
|
|
49
|
+
});
|
|
50
|
+
process.stdin.on("error", () => process.exit(0));
|