@piagent/platform 1.43.9 → 1.44.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/CHANGELOG.md +45 -0
- package/docs/command-reference-vietnamese.md +8 -2
- package/docs/company-dev-commands.md +2 -0
- package/docs/distribution-standard.md +6 -6
- package/docs/harness-conformance.md +14 -0
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +3 -0
- package/docs/release-install-policy.md +4 -2
- package/docs/team-onboarding.md +2 -2
- package/docs-site/architecture.html +1 -1
- package/docs-site/assets/search-en.json +1 -1
- package/docs-site/assets/search-vi.json +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/ecosystem.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/ecosystem.html +1 -1
- package/docs-site/en/index.html +15 -15
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/update.html +3 -3
- package/docs-site/en/whats-new.html +79 -45
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +15 -15
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/releases.json +108 -0
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/update.html +3 -3
- package/docs-site/whats-new.html +79 -45
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +4 -1
- package/packages/piagent-core/extensions/piagent-guard.ts +3 -0
- package/packages/piagent-core/managed/helper-roles.mjs +24 -6
- package/packages/piagent-core/managed/pi-host.mjs +60 -1
- package/packages/piagent-core/managed/resource-loader.mjs +3 -2
- package/packages/piagent-core/managed/risk-lanes.mjs +38 -0
- package/packages/piagent-core/managed/session.mjs +6 -5
- package/packages/piagent-core/managed/workflow.mjs +24 -4
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/resources/plugin-skills.mjs +33 -4
- package/packages/piagent-core/runtime/terminal/terminal-style.d.mts +7 -0
- package/packages/piagent-core/runtime/terminal/terminal-style.mjs +99 -0
- package/packages/piagent-core/themes/piagent-contrast.json +78 -0
- package/packages/piagent-core/themes/piagent-focus.json +78 -0
- package/packages/piagent-core/themes/piagent-ocean.json +78 -0
- package/packages/piagent-core/themes/piagent-paper.json +78 -0
- package/packages/piagent-webui/gateway/pi-host.ts +13 -1
- package/scripts/company-terminal.mjs +6 -2
- package/scripts/install-global.sh +13 -1
- package/scripts/pi-host.mjs +109 -0
- package/scripts/piagent-cli.mjs +17 -6
- package/scripts/piagent-dashboard.mjs +2 -2
- package/scripts/runtime-advisory-exceptions.json +40 -0
- package/scripts/setup.sh +19 -1
- package/scripts/skill-sources.mjs +188 -0
- package/scripts/uninstall-global.sh +18 -1
- package/scripts/update-global.mjs +13 -5
|
@@ -25,8 +25,9 @@ import { memberTurn } from './continuation.mjs';
|
|
|
25
25
|
import { workflowPolicy, repositoryChecks, sessionPlanTool, currentPlan, workflowPrompt, RunProcess, completionGate, pendingBaseline, pendingPaths, pendingHead, processEditTools } from './workflow.mjs';
|
|
26
26
|
import { workflowContextTool, workflowContextExtension } from './workflow-context.mjs';
|
|
27
27
|
import { beforeDelegate } from './objections.mjs';
|
|
28
|
-
import { HELPER_CALLS, HELPER_ROLES, helperRoles, helperPrompt, webPrompt, delegateDescription } from './helper-roles.mjs'; import { delegateParameters, delegateArgs, SPEC_PROMPT } from './helper-settlement.mjs';
|
|
28
|
+
import { HELPER_CALLS, HELPER_ROLES, helperRoles, helperPrompt, webPrompt, delegateDescription, mainWebLookups, limitedWebTools } from './helper-roles.mjs'; import { delegateParameters, delegateArgs, SPEC_PROMPT } from './helper-settlement.mjs';
|
|
29
29
|
import { loadAgentResources } from './agent-skills.mjs';
|
|
30
|
+
import { loadPiagentThemes } from '../runtime/terminal/terminal-style.mjs';
|
|
30
31
|
import { restorePermission, recordBypass, permissionSetter } from './permission.mjs';
|
|
31
32
|
import { commandTools, servicesPrompt } from './command-tools.mjs'; import { attachMemory, memoryToolsFor, openRound, closeRound, prepareMemoryContext, trackMemoryLessons } from './memory.mjs';
|
|
32
33
|
|
|
@@ -113,9 +114,9 @@ export class ManagedSession {
|
|
|
113
114
|
self.wrapStreams(self.modelRuntime, 'main');
|
|
114
115
|
const customTools = processEditTools(self, self.boundary.tools(api));
|
|
115
116
|
customTools.push(fetchOriginTool(self));
|
|
116
|
-
// With a research helper the web is its work
|
|
117
|
-
// are read on its cheaper model, not in the main agent's context.
|
|
117
|
+
// With a research helper the web is its work (pages read on its cheaper model), beyond the Harness's few quick lookups.
|
|
118
118
|
if (!helperRoles(manifest).includes('research')) customTools.push(...self.webTools(self.modelRuntime, 'main'));
|
|
119
|
+
else if (mainWebLookups(manifest)) customTools.push(...limitedWebTools(self.webTools(self.modelRuntime, 'main'), () => self.run, mainWebLookups(manifest)));
|
|
119
120
|
// Commands with network, and with Docker when this machine has an engine.
|
|
120
121
|
customTools.push(...commandTools(self));
|
|
121
122
|
// Every helper role passes the tool's schema: Pi fixes a tool's schema
|
|
@@ -134,8 +135,8 @@ export class ManagedSession {
|
|
|
134
135
|
const agentsFiles = projectInstructions(self.cwd, self.boundary.repositoryTop);
|
|
135
136
|
self.checks = repositoryChecks(agentsFiles, self.cwd, self.boundary.repositoryTop);
|
|
136
137
|
// The Harness workflow (possibly changed on a later enrollment) adds its process to the prompt.
|
|
137
|
-
const loader = managedResourceLoader(api, { systemPrompt: () => BASE_PROMPT + referencePrompt(self.boundary.userHome) + TEST_PROMPT + servicesPrompt(self.boundary) + webPrompt(helperRoles(manifest)) + helperPrompt(helperRoles(self.manifest)) + workflowPrompt(workflowPolicy(self.manifest), self.checks), agentsFiles,
|
|
138
|
-
skills: self.resources.skills, prompts: self.resources.prompts, ...await inlineExtensions(api, sdk, self.cwd,
|
|
138
|
+
const loader = managedResourceLoader(api, { systemPrompt: () => BASE_PROMPT + referencePrompt(self.boundary.userHome) + TEST_PROMPT + servicesPrompt(self.boundary) + webPrompt(helperRoles(manifest), mainWebLookups(manifest)) + helperPrompt(helperRoles(self.manifest)) + workflowPrompt(workflowPolicy(self.manifest), self.checks), agentsFiles,
|
|
139
|
+
skills: self.resources.skills, prompts: self.resources.prompts, themes: terminalExtensions.length ? await loadPiagentThemes(sdk) : [], ...await inlineExtensions(api, sdk, self.cwd,
|
|
139
140
|
[...terminalExtensions, workflowContextExtension(self.cwd, () => self.session.sessionManager)]) });
|
|
140
141
|
const settings = api.SettingsManager.inMemory({ retry: { enabled: false, provider: { maxRetries: 0 } }, cacheWarming: 'off', enableInstallTelemetry: false, enableAnalytics: false, enableSkillCommands: true });
|
|
141
142
|
const manager = sessionManager ?? api.SessionManager.inMemory(self.cwd); attachMemory(self, manager, agentDir, g => verifyGrant(g, self.manifest, 'main')); self.studioLink = new StudioLink({ origin: self.origin, agentDir, notify: event => self.notify(event), ui: () => self.session?.extensionRunner?.getUIContext?.() });
|
|
@@ -5,6 +5,7 @@ import { failureCode } from '../runtime/managed-failure.mjs';
|
|
|
5
5
|
import { TsDiagnostics } from './ts-diagnostics.mjs';
|
|
6
6
|
import { checkpointsOf } from './checkpoints.mjs';
|
|
7
7
|
import { discoverDeliveryContext } from './delivery-context.mjs';
|
|
8
|
+
import { LANE_LIMITS, patchStats, changeLane, lanePolicy } from './risk-lanes.mjs';
|
|
8
9
|
|
|
9
10
|
// The process layer of the company harness: a plan for multi-step work, the
|
|
10
11
|
// repository's checks after a code change, an independent review of the patch.
|
|
@@ -17,7 +18,7 @@ export function workflowPolicy(manifest) {
|
|
|
17
18
|
const mode = value => MODES.includes(value) ? value : 'suggest';
|
|
18
19
|
const loops = Number.isInteger(w.max_fix_loops) && w.max_fix_loops >= 0 && w.max_fix_loops <= 3 ? w.max_fix_loops : 2;
|
|
19
20
|
// A review needs a review helper; without one in the Harness it is off.
|
|
20
|
-
return { plan: mode(w.plan), verify: mode(w.verify), review: config.review ? mode(w.review) : 'off', maxFixLoops: loops };
|
|
21
|
+
return { plan: mode(w.plan), verify: mode(w.verify), review: config.review ? mode(w.review) : 'off', maxFixLoops: loops, lanes: w.lanes !== false };
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
// Checks come from the repository: a "## Checks" section in AGENTS.md (code
|
|
@@ -150,6 +151,10 @@ export function workflowPrompt(policy, checks) {
|
|
|
150
151
|
}
|
|
151
152
|
if (policy.review !== 'off') lines.push(policy.review === 'require' ? '- Review: the harness reviews the final patch with an independent reviewer; blocking findings come back to you to fix, or to answer when a finding is wrong: the reviewer then judges your answer, and a disagreement that remains goes to the member.'
|
|
152
153
|
: '- Review: before you finish a code change, ask the review helper (delegate role "review") to review the patch, then fix blocking findings.');
|
|
154
|
+
if (policy.lanes && (policy.verify !== 'off' || policy.review !== 'off')) {
|
|
155
|
+
const L = LANE_LIMITS;
|
|
156
|
+
lines.push(`- Size: the process follows the size of your change. A small change (at most ${L.tinyFiles} files and ${L.tinyLines} changed lines, no migration, auth, security, CI, dependency or AGENTS.md file) ends after its checks, without a review: do not ask for one. A risky change (a complex request, one of those files, more than ${L.riskyFiles} files or ${L.riskyLines} lines) must pass the checks${policy.review !== 'off' ? ' and the harness review' : ''} even where they are only suggested.`);
|
|
157
|
+
}
|
|
153
158
|
return lines.length ? `\n\nProcess for code changes (company Harness):\n${lines.join('\n')}\n${process.platform === 'darwin' ? 'End-to-end and UI checks run in bash with the project\'s Playwright (@playwright/test; add it with npm when the project lacks it) and the browsers installed on this Mac, against a server the test or start_process starts; while a local proxy listens they need run_with_network. When Playwright\'s browsers are missing, ask with run_on_host to run `npx playwright install chromium`.' : 'End-to-end tests that start a local server and a browser run through run_with_network, which the user approves.'} If a check cannot run because the machine lacks something (a runtime such as a JDK, Docker, a database or another service, the network), do not stop there: request the approval that makes it run (run_with_network, run_with_docker, start_docker, or run_on_host to install what is missing) or ask the member with ask_user; never install or start anything without that approval.\nA command stops after 10 minutes and anything left running in the background stops when the turn ends: run the checks that cover your change first and a long suite in parts, not in the background.${process.platform === 'darwin' ? ' A dev server, watcher or local service you need across turns runs with start_process (read it with process_output, stop it with stop_process).' : ''}` : '';
|
|
154
159
|
}
|
|
155
160
|
|
|
@@ -241,7 +246,9 @@ export class RunProcess {
|
|
|
241
246
|
// claims verify marked failed, and where the harness last sent the
|
|
242
247
|
// agent back (to read its answer and see whether the code moved).
|
|
243
248
|
objections: [], disputes: [], verifyFails: [], claimsAsked: false, claimsAt: null, claimsRechecked: false, claimsDone: false,
|
|
244
|
-
sentAt: null, sentDigest: null, rejudged: false, reviewSettled: false, objectionsAsked: false
|
|
249
|
+
sentAt: null, sentDigest: null, rejudged: false, reviewSettled: false, objectionsAsked: false,
|
|
250
|
+
// The patch's risk lane (workflow.lanes) and the main agent's own web lookups.
|
|
251
|
+
lane: null, laneStats: null, laneDigest: null, webLookups: 0 });
|
|
245
252
|
}
|
|
246
253
|
// Plan "require" on a complex task that has no plan yet.
|
|
247
254
|
planMissing(plan) {
|
|
@@ -299,7 +306,18 @@ export class RunProcess {
|
|
|
299
306
|
// A finding, a failed claim or an objection to a brief is the agent's to fix
|
|
300
307
|
// or answer; the same disagreement a second time goes to the member.
|
|
301
308
|
export async function completionGate(managed, run, signal) {
|
|
302
|
-
const session = managed.session,
|
|
309
|
+
const session = managed.session, configured = run.policy;
|
|
310
|
+
let policy = configured;
|
|
311
|
+
// The lane of the patch as it is now, read again only when it changed.
|
|
312
|
+
const laneOf = async digest => {
|
|
313
|
+
if (!configured.lanes) return configured;
|
|
314
|
+
if (run.laneDigest === null || run.laneDigest !== digest) {
|
|
315
|
+
let stats = null;
|
|
316
|
+
try { stats = patchStats(await managed.patchSnapshot?.()); } catch { /* unreadable: standard */ }
|
|
317
|
+
run.lane = changeLane(stats, run); run.laneStats = stats ? { files: stats.files, lines: stats.lines } : null; run.laneDigest = digest;
|
|
318
|
+
}
|
|
319
|
+
return lanePolicy(configured, run.lane);
|
|
320
|
+
};
|
|
303
321
|
const digestOf = async () => { try { return await (managed.changeDigest ?? managed.patchDigest).call(managed); } catch { return null; } };
|
|
304
322
|
const reviewDigestOf = async () => { try { return await managed.patchDigest(); } catch { return null; } };
|
|
305
323
|
const stopped = () => signal?.aborted || ['aborted', 'error'].includes(session.messages.findLast(m => m.role === 'assistant')?.stopReason);
|
|
@@ -316,6 +334,7 @@ export async function completionGate(managed, run, signal) {
|
|
|
316
334
|
// A check that passed still counts when the turn was cut off after it.
|
|
317
335
|
if (run.changed) run.verified = run.verifiedOn(digest);
|
|
318
336
|
run.docsOnly = run.changed && onlyNonCode(managed.ownPaths);
|
|
337
|
+
policy = run.changed ? await laneOf(digest) : configured;
|
|
319
338
|
if (stopped()) { interrupted = run.changed; break; }
|
|
320
339
|
if (run.changed) {
|
|
321
340
|
const reviewDigest = managed.review ? await reviewDigestOf() : null;
|
|
@@ -424,7 +443,8 @@ export async function completionGate(managed, run, signal) {
|
|
|
424
443
|
run.planSkipped ? 'code was changed before the required plan existed' : null, planOpen ? `plan: ${planOpen} step(s) not completed` : null] : []),
|
|
425
444
|
run.disputes.length ? `${run.disputes.length} disagreement(s) between the main agent and a subagent go to the member` : null].filter(Boolean);
|
|
426
445
|
await session.sendCustomMessage({ customType: 'agent-watch-process', content: `Process status for this turn: ${run.changed ? 'code changed' : 'no code changed'}; ${words.join('; ') || 'no process step configured'}.`, display: true,
|
|
427
|
-
details: { phase: 'final', outcome: run.outcome, ...status, ...(run.reviewUnavailable ? { reviewUnavailable: run.reviewUnavailableReason } : {}), policy: { verify: policy.verify, review: policy.review }
|
|
446
|
+
details: { phase: 'final', outcome: run.outcome, ...status, ...(run.reviewUnavailable ? { reviewUnavailable: run.reviewUnavailableReason } : {}), policy: { verify: policy.verify, review: policy.review },
|
|
447
|
+
...(configured.lanes && run.lane ? { lane: run.lane, ...(run.laneStats ? { size: run.laneStats } : {}) } : {}) } }, { triggerTurn: false });
|
|
428
448
|
}
|
|
429
449
|
|
|
430
450
|
// The run's process sees each edit and command: plan guard before, check
|
|
@@ -2,7 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
|
-
// Skills and commands a member installed as Claude or Codex plugins
|
|
5
|
+
// Skills and commands a member installed as Claude or Codex plugins, or added
|
|
6
|
+
// to Piagent as a skill source
|
|
6
7
|
// (superpowers, a team's own marketplace…). Each agent keeps them in its plugin
|
|
7
8
|
// cache, not in ~/.claude/skills or ~/.codex/skills, so they are found here:
|
|
8
9
|
// the plugins the agent records as installed, the newest version of each, and
|
|
@@ -119,9 +120,37 @@ function codexPlugins(home) {
|
|
|
119
120
|
return found.filter(Boolean);
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
//
|
|
123
|
-
//
|
|
123
|
+
// Skill repositories the member added with `piagent skills add` (for example
|
|
124
|
+
// mattpocock/skills), each kept at the commit it was added at under
|
|
125
|
+
// ~/.piagent/skill-sources and listed in ~/.piagent/skill-sources.json
|
|
126
|
+
// (scripts/skill-sources.mjs). A repository that ships a Claude or Codex
|
|
127
|
+
// plugin manifest is read through it, so only the skills it publishes load.
|
|
128
|
+
export function skillSourcesRoot(home = os.homedir()) {
|
|
129
|
+
return path.join(home, '.piagent', 'skill-sources');
|
|
130
|
+
}
|
|
131
|
+
export function skillSourcesIndex(home = os.homedir()) {
|
|
132
|
+
return path.join(home, '.piagent', 'skill-sources.json');
|
|
133
|
+
}
|
|
134
|
+
function piagentSources(home) {
|
|
135
|
+
const sources = readJson(skillSourcesIndex(home))?.sources;
|
|
136
|
+
if (!Array.isArray(sources)) return [];
|
|
137
|
+
const root = folder(skillSourcesRoot(home));
|
|
138
|
+
if (!root) return [];
|
|
139
|
+
return sources.filter((entry) => entry && typeof entry.path === 'string' && entry.enabled !== false).map((entry) => {
|
|
140
|
+
const dir = folder(path.resolve(root, entry.path));
|
|
141
|
+
return dir && inside(dir, root) ? skillSourcePlugin(dir, entry.name || entry.repo) : null;
|
|
142
|
+
}).filter(Boolean);
|
|
143
|
+
}
|
|
144
|
+
// One source's copy read as a plugin: through its Claude or Codex manifest when it has one.
|
|
145
|
+
export function skillSourcePlugin(dir, name) {
|
|
146
|
+
const manifestDir = fs.existsSync(path.join(dir, '.claude-plugin', 'plugin.json')) ? '.claude-plugin' : '.codex-plugin';
|
|
147
|
+
return plugin(dir, 'piagent', name, manifestDir);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// The member's plugins, Claude's first, then Codex's and the skill sources
|
|
151
|
+
// added to Piagent, at most MAX_PLUGINS. A plugin that cannot be read is left
|
|
152
|
+
// out; it never keeps a conversation from opening.
|
|
124
153
|
export function memberPlugins({ home = os.homedir(), projects = [] } = {}) {
|
|
125
154
|
const read = (load) => { try { return load(); } catch { return []; } };
|
|
126
|
-
return [...read(() => claudePlugins(home, projects)), ...read(() => codexPlugins(home))].slice(0, MAX_PLUGINS);
|
|
155
|
+
return [...read(() => claudePlugins(home, projects)), ...read(() => codexPlugins(home)), ...read(() => piagentSources(home))].slice(0, MAX_PLUGINS);
|
|
127
156
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const THEMES_DIR: string;
|
|
2
|
+
export function piagentThemeFiles(dir?: string): string[];
|
|
3
|
+
export function loadPiagentThemes(sdkRoot: string, dir?: string): Promise<unknown[]>;
|
|
4
|
+
export function readStyle(home?: string): string | null;
|
|
5
|
+
export function writeStyle(name: string, home?: string): void;
|
|
6
|
+
export function styleChoices(ui: unknown): { name: string; label: string }[];
|
|
7
|
+
export function registerStyleCommand(pi: unknown, options?: { remember?: boolean; home?: string }): void;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// The Terminal's look, chosen by the member: /style lists Pi's own themes
|
|
2
|
+
// (system, dark, light) and Piagent's (packages/piagent-core/themes), shows
|
|
3
|
+
// the one picked at once and keeps it. Personal Pi keeps it in its own
|
|
4
|
+
// settings (`theme`), as /settings → Theme does. The company Terminal runs
|
|
5
|
+
// with settings in memory, so its choice is kept in ~/.pi beside the other
|
|
6
|
+
// Terminal state and put back when a conversation opens.
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
11
|
+
|
|
12
|
+
export const THEMES_DIR = fileURLToPath(new URL("../../themes", import.meta.url));
|
|
13
|
+
const STYLE_FILE = "piagent-terminal-style.json";
|
|
14
|
+
// The dashboard's own runtime UI draws its own page: no Terminal theme there.
|
|
15
|
+
const GATEWAY_UI = Symbol.for("piagent.webui.gateway-runtime-ui.v1");
|
|
16
|
+
|
|
17
|
+
// What each style is for, in the member's words; Pi's own are listed first.
|
|
18
|
+
const DESCRIPTIONS = {
|
|
19
|
+
system: "theo màu của terminal (mặc định của Pi)",
|
|
20
|
+
dark: "nền tối của Pi",
|
|
21
|
+
light: "nền sáng của Pi",
|
|
22
|
+
"piagent-ocean": "nền tối, xanh biển dịu mắt cho phiên dài",
|
|
23
|
+
"piagent-paper": "nền sáng, tông giấy ấm",
|
|
24
|
+
"piagent-contrast": "tương phản cao, cho phòng sáng hoặc máy chiếu",
|
|
25
|
+
"piagent-focus": "gần như đơn sắc, một màu nhấn hổ phách",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function piagentThemeFiles(dir = THEMES_DIR) {
|
|
29
|
+
try { return fs.readdirSync(dir).filter((name) => name.endsWith(".json")).sort().map((name) => path.join(dir, name)); }
|
|
30
|
+
catch { return []; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Piagent's themes loaded with the host's own loader, for a runtime that
|
|
34
|
+
// supplies its own resources (the company Terminal). One that does not load
|
|
35
|
+
// on this host is left out.
|
|
36
|
+
export async function loadPiagentThemes(sdkRoot, dir = THEMES_DIR) {
|
|
37
|
+
let loader;
|
|
38
|
+
try { loader = await import(pathToFileURL(path.join(sdkRoot, "dist", "modes", "interactive", "theme", "theme.js")).href); }
|
|
39
|
+
catch { return []; }
|
|
40
|
+
const themes = [];
|
|
41
|
+
for (const file of piagentThemeFiles(dir)) {
|
|
42
|
+
try { themes.push(loader.loadThemeFromPath(file)); } catch { /* another host's schema */ }
|
|
43
|
+
}
|
|
44
|
+
return themes;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const styleFile = (home) => path.join(home ?? os.homedir(), ".pi", STYLE_FILE);
|
|
48
|
+
export function readStyle(home) {
|
|
49
|
+
try { const name = JSON.parse(fs.readFileSync(styleFile(home), "utf8"))?.theme; return typeof name === "string" && name ? name : null; }
|
|
50
|
+
catch { return null; }
|
|
51
|
+
}
|
|
52
|
+
export function writeStyle(name, home) {
|
|
53
|
+
const file = styleFile(home);
|
|
54
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
55
|
+
fs.writeFileSync(file, `${JSON.stringify({ theme: name })}\n`, { mode: 0o600 });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The styles to offer: Pi's built-ins and every loaded theme, each once.
|
|
59
|
+
export function styleChoices(ui) {
|
|
60
|
+
const names = [...new Set(["system", "dark", "light", ...(ui?.getAllThemes?.() ?? []).map((item) => item?.name).filter(Boolean)])];
|
|
61
|
+
return names.map((name) => ({ name, label: DESCRIPTIONS[name] ? `${name} — ${DESCRIPTIONS[name]}` : name }));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// remember: true where Pi does not keep the setting itself (company Terminal).
|
|
65
|
+
export function registerStyleCommand(pi, { remember = false, home } = {}) {
|
|
66
|
+
const apply = (ctx, name) => {
|
|
67
|
+
const result = ctx.ui.setTheme(name);
|
|
68
|
+
if (!result?.success) return result?.error ?? "unknown";
|
|
69
|
+
if (remember) writeStyle(name, home);
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
if (remember) {
|
|
73
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
74
|
+
if (!ctx.hasUI || ctx.ui?.[GATEWAY_UI]) return;
|
|
75
|
+
const saved = readStyle(home);
|
|
76
|
+
if (saved && ctx.ui.theme?.name !== saved) ctx.ui.setTheme?.(saved);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
pi.registerCommand("style", {
|
|
80
|
+
description: "Chọn giao diện (theme) của Terminal: /style để chọn trong danh sách, /style <tên> để đổi ngay",
|
|
81
|
+
getArgumentCompletions: (prefix) => {
|
|
82
|
+
const typed = String(prefix ?? "").trim();
|
|
83
|
+
return Object.keys(DESCRIPTIONS).filter((name) => name !== typed && name.startsWith(typed)).map((value) => ({ value, label: value }));
|
|
84
|
+
},
|
|
85
|
+
handler: async (args, ctx) => {
|
|
86
|
+
if (!ctx.hasUI || ctx.ui?.[GATEWAY_UI] || typeof ctx.ui?.setTheme !== "function") { ctx.ui?.notify?.("/style chỉ dùng trong Terminal.", "info"); return; }
|
|
87
|
+
const choices = styleChoices(ctx.ui), current = ctx.ui.theme?.name;
|
|
88
|
+
let name = String(args ?? "").trim();
|
|
89
|
+
if (!name) {
|
|
90
|
+
const picked = await ctx.ui.select(`Giao diện Terminal (đang: ${current ?? "system"})`, choices.map((item) => item.label));
|
|
91
|
+
if (!picked) return;
|
|
92
|
+
name = choices.find((item) => item.label === picked)?.name ?? picked;
|
|
93
|
+
}
|
|
94
|
+
if (!choices.some((item) => item.name === name)) { ctx.ui.notify(`Không có giao diện "${name}". Có: ${choices.map((item) => item.name).join(", ")}.`, "warning"); return; }
|
|
95
|
+
const error = apply(ctx, name);
|
|
96
|
+
ctx.ui.notify(error ? `Không đổi được giao diện: ${error}` : `Đã đổi giao diện sang ${name}.`, error ? "error" : "info");
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://pi.dev/schemas/theme.schema.json",
|
|
3
|
+
"name": "piagent-contrast",
|
|
4
|
+
"appearance": "dark",
|
|
5
|
+
"vars": {
|
|
6
|
+
"text": "okhsl(0 0% 98%)",
|
|
7
|
+
"muted": "okhsl(230 8% 80%)",
|
|
8
|
+
"violet": "okhsl(295 80% 75%)",
|
|
9
|
+
"blue": "okhsl(220 85% 75%)",
|
|
10
|
+
"green": "okhsl(150 85% 72%)",
|
|
11
|
+
"red": "okhsl(20 95% 70%)",
|
|
12
|
+
"yellow": "okhsl(85 100% 75%)",
|
|
13
|
+
"blueBg": "okhsl(230 60% 20%)"
|
|
14
|
+
},
|
|
15
|
+
"colors": {
|
|
16
|
+
"accent": "violet",
|
|
17
|
+
"border": "okhsl(231 66% 71%)",
|
|
18
|
+
"borderAccent": "okhsl(295 61% 70%)",
|
|
19
|
+
"borderMuted": "okhsl(229 9% 59%)",
|
|
20
|
+
"success": "green",
|
|
21
|
+
"error": "red",
|
|
22
|
+
"warning": "yellow",
|
|
23
|
+
"muted": "muted",
|
|
24
|
+
"dim": "okhsl(229 9% 62%)",
|
|
25
|
+
"text": "text",
|
|
26
|
+
"thinkingText": "okhsl(226 8% 71%)",
|
|
27
|
+
"selectedBg": "blueBg",
|
|
28
|
+
"scrollbarTrack": "okhsl(237 8% 39%)",
|
|
29
|
+
"scrollbarThumb": "okhsl(232 8% 71%)",
|
|
30
|
+
"searchMatchBg": "okhsl(53 59% 30%)",
|
|
31
|
+
"searchMatchText": "muted",
|
|
32
|
+
"userMessageBg": "blueBg",
|
|
33
|
+
"userMessageText": "text",
|
|
34
|
+
"customMessageBg": "okhsl(295 48% 30%)",
|
|
35
|
+
"customMessageText": "muted",
|
|
36
|
+
"customMessageLabel": "violet",
|
|
37
|
+
"toolPendingBg": "okhsl(229 6% 30%)",
|
|
38
|
+
"toolSuccessBg": "okhsl(158 53% 31%)",
|
|
39
|
+
"toolErrorBg": "okhsl(19 62% 31%)",
|
|
40
|
+
"toolTitle": "text",
|
|
41
|
+
"toolOutput": "muted",
|
|
42
|
+
"mdHeading": "yellow",
|
|
43
|
+
"mdLink": "blue",
|
|
44
|
+
"mdLinkUrl": "muted",
|
|
45
|
+
"mdCode": "violet",
|
|
46
|
+
"mdCodeBlock": "green",
|
|
47
|
+
"mdCodeBlockBorder": "muted",
|
|
48
|
+
"mdQuote": "muted",
|
|
49
|
+
"mdQuoteBorder": "muted",
|
|
50
|
+
"mdHr": "muted",
|
|
51
|
+
"mdListBullet": "violet",
|
|
52
|
+
"toolDiffAdded": "green",
|
|
53
|
+
"toolDiffRemoved": "red",
|
|
54
|
+
"toolDiffContext": "muted",
|
|
55
|
+
"syntaxComment": "muted",
|
|
56
|
+
"syntaxKeyword": "blue",
|
|
57
|
+
"syntaxFunction": "yellow",
|
|
58
|
+
"syntaxVariable": "okhsl(202 67% 73%)",
|
|
59
|
+
"syntaxString": "okhsl(52 77% 73%)",
|
|
60
|
+
"syntaxNumber": "green",
|
|
61
|
+
"syntaxType": "violet",
|
|
62
|
+
"syntaxOperator": "muted",
|
|
63
|
+
"syntaxPunctuation": "muted",
|
|
64
|
+
"thinkingOff": "okhsl(229 9% 55%)",
|
|
65
|
+
"thinkingMinimal": "okhsl(232 23% 58%)",
|
|
66
|
+
"thinkingLow": "okhsl(232 52% 60%)",
|
|
67
|
+
"thinkingMedium": "okhsl(263 68% 62%)",
|
|
68
|
+
"thinkingHigh": "okhsl(295 84% 65%)",
|
|
69
|
+
"thinkingXhigh": "okhsl(337 93% 67%)",
|
|
70
|
+
"thinkingMax": "okhsl(20 100% 69%)",
|
|
71
|
+
"bashMode": "okhsl(159 74% 71%)"
|
|
72
|
+
},
|
|
73
|
+
"export": {
|
|
74
|
+
"pageBg": "okhsl(262 16% 22%)",
|
|
75
|
+
"cardBg": "okhsl(264 15% 25%)",
|
|
76
|
+
"infoBg": "okhsl(53 59% 30%)"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://pi.dev/schemas/theme.schema.json",
|
|
3
|
+
"name": "piagent-focus",
|
|
4
|
+
"appearance": "dark",
|
|
5
|
+
"vars": {
|
|
6
|
+
"text": "okhsl(60 4% 88%)",
|
|
7
|
+
"muted": "okhsl(60 3% 64%)",
|
|
8
|
+
"violet": "okhsl(70 80% 70%)",
|
|
9
|
+
"blue": "okhsl(60 10% 78%)",
|
|
10
|
+
"green": "okhsl(140 30% 66%)",
|
|
11
|
+
"red": "okhsl(25 60% 66%)",
|
|
12
|
+
"yellow": "okhsl(70 80% 70%)",
|
|
13
|
+
"blueBg": "okhsl(60 6% 22%)"
|
|
14
|
+
},
|
|
15
|
+
"colors": {
|
|
16
|
+
"accent": "violet",
|
|
17
|
+
"border": "okhsl(231 14% 65%)",
|
|
18
|
+
"borderAccent": "okhsl(295 13% 64%)",
|
|
19
|
+
"borderMuted": "okhsl(229 2% 53%)",
|
|
20
|
+
"success": "green",
|
|
21
|
+
"error": "red",
|
|
22
|
+
"warning": "yellow",
|
|
23
|
+
"muted": "muted",
|
|
24
|
+
"dim": "okhsl(229 2% 56%)",
|
|
25
|
+
"text": "text",
|
|
26
|
+
"thinkingText": "okhsl(226 2% 65%)",
|
|
27
|
+
"selectedBg": "blueBg",
|
|
28
|
+
"scrollbarTrack": "okhsl(237 2% 33%)",
|
|
29
|
+
"scrollbarThumb": "okhsl(232 2% 65%)",
|
|
30
|
+
"searchMatchBg": "okhsl(53 13% 24%)",
|
|
31
|
+
"searchMatchText": "muted",
|
|
32
|
+
"userMessageBg": "blueBg",
|
|
33
|
+
"userMessageText": "text",
|
|
34
|
+
"customMessageBg": "okhsl(295 10% 24%)",
|
|
35
|
+
"customMessageText": "muted",
|
|
36
|
+
"customMessageLabel": "violet",
|
|
37
|
+
"toolPendingBg": "okhsl(229 1% 24%)",
|
|
38
|
+
"toolSuccessBg": "okhsl(158 12% 25%)",
|
|
39
|
+
"toolErrorBg": "okhsl(19 14% 25%)",
|
|
40
|
+
"toolTitle": "text",
|
|
41
|
+
"toolOutput": "muted",
|
|
42
|
+
"mdHeading": "yellow",
|
|
43
|
+
"mdLink": "blue",
|
|
44
|
+
"mdLinkUrl": "muted",
|
|
45
|
+
"mdCode": "violet",
|
|
46
|
+
"mdCodeBlock": "green",
|
|
47
|
+
"mdCodeBlockBorder": "muted",
|
|
48
|
+
"mdQuote": "muted",
|
|
49
|
+
"mdQuoteBorder": "muted",
|
|
50
|
+
"mdHr": "muted",
|
|
51
|
+
"mdListBullet": "violet",
|
|
52
|
+
"toolDiffAdded": "green",
|
|
53
|
+
"toolDiffRemoved": "red",
|
|
54
|
+
"toolDiffContext": "muted",
|
|
55
|
+
"syntaxComment": "muted",
|
|
56
|
+
"syntaxKeyword": "blue",
|
|
57
|
+
"syntaxFunction": "yellow",
|
|
58
|
+
"syntaxVariable": "okhsl(202 14% 67%)",
|
|
59
|
+
"syntaxString": "okhsl(52 17% 67%)",
|
|
60
|
+
"syntaxNumber": "green",
|
|
61
|
+
"syntaxType": "violet",
|
|
62
|
+
"syntaxOperator": "muted",
|
|
63
|
+
"syntaxPunctuation": "muted",
|
|
64
|
+
"thinkingOff": "okhsl(229 2% 49%)",
|
|
65
|
+
"thinkingMinimal": "okhsl(232 5% 52%)",
|
|
66
|
+
"thinkingLow": "okhsl(232 11% 54%)",
|
|
67
|
+
"thinkingMedium": "okhsl(263 15% 56%)",
|
|
68
|
+
"thinkingHigh": "okhsl(295 18% 59%)",
|
|
69
|
+
"thinkingXhigh": "okhsl(337 20% 61%)",
|
|
70
|
+
"thinkingMax": "okhsl(20 25% 63%)",
|
|
71
|
+
"bashMode": "okhsl(159 16% 65%)"
|
|
72
|
+
},
|
|
73
|
+
"export": {
|
|
74
|
+
"pageBg": "okhsl(262 4% 16%)",
|
|
75
|
+
"cardBg": "okhsl(264 3% 19%)",
|
|
76
|
+
"infoBg": "okhsl(53 13% 24%)"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://pi.dev/schemas/theme.schema.json",
|
|
3
|
+
"name": "piagent-ocean",
|
|
4
|
+
"appearance": "dark",
|
|
5
|
+
"vars": {
|
|
6
|
+
"text": "okhsl(200 8% 90%)",
|
|
7
|
+
"muted": "okhsl(200 10% 68%)",
|
|
8
|
+
"violet": "okhsl(185 70% 68%)",
|
|
9
|
+
"blue": "okhsl(215 70% 70%)",
|
|
10
|
+
"green": "okhsl(150 60% 68%)",
|
|
11
|
+
"red": "okhsl(15 75% 68%)",
|
|
12
|
+
"yellow": "okhsl(75 85% 70%)",
|
|
13
|
+
"blueBg": "okhsl(205 45% 22%)"
|
|
14
|
+
},
|
|
15
|
+
"colors": {
|
|
16
|
+
"accent": "violet",
|
|
17
|
+
"border": "okhsl(191 51% 65%)",
|
|
18
|
+
"borderAccent": "okhsl(255 48% 64%)",
|
|
19
|
+
"borderMuted": "okhsl(189 7% 53%)",
|
|
20
|
+
"success": "green",
|
|
21
|
+
"error": "red",
|
|
22
|
+
"warning": "yellow",
|
|
23
|
+
"muted": "muted",
|
|
24
|
+
"dim": "okhsl(189 7% 56%)",
|
|
25
|
+
"text": "text",
|
|
26
|
+
"thinkingText": "okhsl(186 6% 65%)",
|
|
27
|
+
"selectedBg": "blueBg",
|
|
28
|
+
"scrollbarTrack": "okhsl(197 6% 33%)",
|
|
29
|
+
"scrollbarThumb": "okhsl(192 6% 65%)",
|
|
30
|
+
"searchMatchBg": "okhsl(13 46% 24%)",
|
|
31
|
+
"searchMatchText": "muted",
|
|
32
|
+
"userMessageBg": "blueBg",
|
|
33
|
+
"userMessageText": "text",
|
|
34
|
+
"customMessageBg": "okhsl(255 38% 24%)",
|
|
35
|
+
"customMessageText": "muted",
|
|
36
|
+
"customMessageLabel": "violet",
|
|
37
|
+
"toolPendingBg": "okhsl(189 4% 24%)",
|
|
38
|
+
"toolSuccessBg": "okhsl(118 41% 25%)",
|
|
39
|
+
"toolErrorBg": "okhsl(339 49% 25%)",
|
|
40
|
+
"toolTitle": "text",
|
|
41
|
+
"toolOutput": "muted",
|
|
42
|
+
"mdHeading": "yellow",
|
|
43
|
+
"mdLink": "blue",
|
|
44
|
+
"mdLinkUrl": "muted",
|
|
45
|
+
"mdCode": "violet",
|
|
46
|
+
"mdCodeBlock": "green",
|
|
47
|
+
"mdCodeBlockBorder": "muted",
|
|
48
|
+
"mdQuote": "muted",
|
|
49
|
+
"mdQuoteBorder": "muted",
|
|
50
|
+
"mdHr": "muted",
|
|
51
|
+
"mdListBullet": "violet",
|
|
52
|
+
"toolDiffAdded": "green",
|
|
53
|
+
"toolDiffRemoved": "red",
|
|
54
|
+
"toolDiffContext": "muted",
|
|
55
|
+
"syntaxComment": "muted",
|
|
56
|
+
"syntaxKeyword": "blue",
|
|
57
|
+
"syntaxFunction": "yellow",
|
|
58
|
+
"syntaxVariable": "okhsl(162 52% 67%)",
|
|
59
|
+
"syntaxString": "okhsl(12 60% 67%)",
|
|
60
|
+
"syntaxNumber": "green",
|
|
61
|
+
"syntaxType": "violet",
|
|
62
|
+
"syntaxOperator": "muted",
|
|
63
|
+
"syntaxPunctuation": "muted",
|
|
64
|
+
"thinkingOff": "okhsl(189 7% 49%)",
|
|
65
|
+
"thinkingMinimal": "okhsl(192 18% 52%)",
|
|
66
|
+
"thinkingLow": "okhsl(192 40% 54%)",
|
|
67
|
+
"thinkingMedium": "okhsl(223 53% 56%)",
|
|
68
|
+
"thinkingHigh": "okhsl(255 66% 59%)",
|
|
69
|
+
"thinkingXhigh": "okhsl(297 73% 61%)",
|
|
70
|
+
"thinkingMax": "okhsl(340 89% 63%)",
|
|
71
|
+
"bashMode": "okhsl(119 58% 65%)"
|
|
72
|
+
},
|
|
73
|
+
"export": {
|
|
74
|
+
"pageBg": "okhsl(222 13% 16%)",
|
|
75
|
+
"cardBg": "okhsl(224 12% 19%)",
|
|
76
|
+
"infoBg": "okhsl(13 46% 24%)"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://pi.dev/schemas/theme.schema.json",
|
|
3
|
+
"name": "piagent-paper",
|
|
4
|
+
"appearance": "light",
|
|
5
|
+
"vars": {
|
|
6
|
+
"text": "okhsl(40 10% 22%)",
|
|
7
|
+
"muted": "okhsl(40 8% 44%)",
|
|
8
|
+
"violet": "okhsl(330 55% 42%)",
|
|
9
|
+
"blue": "okhsl(225 65% 42%)",
|
|
10
|
+
"green": "okhsl(150 70% 36%)",
|
|
11
|
+
"red": "okhsl(25 85% 44%)",
|
|
12
|
+
"yellow": "okhsl(70 95% 40%)",
|
|
13
|
+
"blueBg": "okhsl(50 30% 90%)"
|
|
14
|
+
},
|
|
15
|
+
"colors": {
|
|
16
|
+
"accent": "violet",
|
|
17
|
+
"border": "okhsl(231 60% 55%)",
|
|
18
|
+
"borderAccent": "okhsl(295 53% 55%)",
|
|
19
|
+
"borderMuted": "okhsl(235 6% 66%)",
|
|
20
|
+
"success": "green",
|
|
21
|
+
"error": "red",
|
|
22
|
+
"warning": "yellow",
|
|
23
|
+
"muted": "muted",
|
|
24
|
+
"dim": "okhsl(229 6% 59%)",
|
|
25
|
+
"text": "text",
|
|
26
|
+
"thinkingText": "okhsl(234 7% 55%)",
|
|
27
|
+
"selectedBg": "blueBg",
|
|
28
|
+
"scrollbarTrack": "okhsl(248 3% 90%)",
|
|
29
|
+
"scrollbarThumb": "okhsl(226 6% 65%)",
|
|
30
|
+
"searchMatchBg": "okhsl(56 20% 91%)",
|
|
31
|
+
"searchMatchText": "muted",
|
|
32
|
+
"userMessageBg": "blueBg",
|
|
33
|
+
"userMessageText": "text",
|
|
34
|
+
"customMessageBg": "okhsl(295 22% 91%)",
|
|
35
|
+
"customMessageText": "muted",
|
|
36
|
+
"customMessageLabel": "violet",
|
|
37
|
+
"toolPendingBg": "okhsl(248 3% 91%)",
|
|
38
|
+
"toolSuccessBg": "okhsl(156 19% 91%)",
|
|
39
|
+
"toolErrorBg": "okhsl(24 21% 91%)",
|
|
40
|
+
"toolTitle": "text",
|
|
41
|
+
"toolOutput": "muted",
|
|
42
|
+
"mdHeading": "yellow",
|
|
43
|
+
"mdLink": "blue",
|
|
44
|
+
"mdLinkUrl": "muted",
|
|
45
|
+
"mdCode": "violet",
|
|
46
|
+
"mdCodeBlock": "green",
|
|
47
|
+
"mdCodeBlockBorder": "muted",
|
|
48
|
+
"mdQuote": "muted",
|
|
49
|
+
"mdQuoteBorder": "muted",
|
|
50
|
+
"mdHr": "muted",
|
|
51
|
+
"mdListBullet": "violet",
|
|
52
|
+
"toolDiffAdded": "green",
|
|
53
|
+
"toolDiffRemoved": "red",
|
|
54
|
+
"toolDiffContext": "muted",
|
|
55
|
+
"syntaxComment": "muted",
|
|
56
|
+
"syntaxKeyword": "blue",
|
|
57
|
+
"syntaxFunction": "yellow",
|
|
58
|
+
"syntaxVariable": "okhsl(203 66% 46%)",
|
|
59
|
+
"syntaxString": "okhsl(52 76% 46%)",
|
|
60
|
+
"syntaxNumber": "green",
|
|
61
|
+
"syntaxType": "violet",
|
|
62
|
+
"syntaxOperator": "muted",
|
|
63
|
+
"syntaxPunctuation": "muted",
|
|
64
|
+
"thinkingOff": "okhsl(223 4% 80%)",
|
|
65
|
+
"thinkingMinimal": "okhsl(229 13% 78%)",
|
|
66
|
+
"thinkingLow": "okhsl(232 30% 76%)",
|
|
67
|
+
"thinkingMedium": "okhsl(264 43% 74%)",
|
|
68
|
+
"thinkingHigh": "okhsl(295 56% 72%)",
|
|
69
|
+
"thinkingXhigh": "okhsl(337 67% 70%)",
|
|
70
|
+
"thinkingMax": "okhsl(20 88% 68%)",
|
|
71
|
+
"bashMode": "okhsl(159 67% 55%)"
|
|
72
|
+
},
|
|
73
|
+
"export": {
|
|
74
|
+
"pageBg": "okhsl(17 3% 94%)",
|
|
75
|
+
"cardBg": "okhsl(17 4% 97%)",
|
|
76
|
+
"infoBg": "okhsl(56 20% 91%)"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -4,6 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { privateHostRoot } from "../../piagent-core/managed/pi-host.mjs";
|
|
7
8
|
|
|
8
9
|
const require = createRequire(import.meta.url);
|
|
9
10
|
|
|
@@ -73,8 +74,19 @@ export function installedPiHostRoot(): string {
|
|
|
73
74
|
throw new Error("pi-host-unavailable");
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
// The host of the pinned release: the installed one when it is that release,
|
|
78
|
+
// else Piagent's private copy, kept beside a member's own newer Pi
|
|
79
|
+
// (packages/piagent-core/managed/pi-host.mjs).
|
|
80
|
+
export function pinnedPiHostRoot(expectedVersion: string): string {
|
|
81
|
+
const own = privateHostRoot(expectedVersion) as string | null;
|
|
82
|
+
let root: string;
|
|
83
|
+
try { root = installedPiHostRoot(); } catch (error) { if (own) return own; throw error; }
|
|
84
|
+
try { if ((JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as { version?: string }).version === expectedVersion) return root; } catch { /* unreadable: the private copy, if any */ }
|
|
85
|
+
return own ?? root;
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
export async function loadPinnedPiHost(expectedVersion: string): Promise<any> {
|
|
77
|
-
const root =
|
|
89
|
+
const root = pinnedPiHostRoot(expectedVersion);
|
|
78
90
|
const actual = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as { version?: string };
|
|
79
91
|
if (actual.version !== expectedVersion) throw new Error("pi-host-version-mismatch");
|
|
80
92
|
return await import(pathToFileURL(path.join(root, "dist", "index.js")).href);
|