@gavin-hu/my-pi-agent 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -11
- package/README.zh-CN.md +13 -8
- package/docs/extensions/README.md +1 -1
- package/docs/extensions/board.md +286 -123
- package/docs/extensions/bytefly.md +381 -223
- package/docs/extensions/file-browse.md +14 -7
- package/docs/extensions/job.md +1 -2
- package/docs/extensions/plan.md +56 -11
- package/docs/extensions/session-bus.md +23 -10
- package/docs/extensions/team.md +45 -17
- package/docs/extensions/wechat.md +2 -1
- package/docs/extensions/worktree.md +30 -15
- package/extensions/board/commands.ts +23 -5
- package/extensions/board/format.ts +107 -9
- package/extensions/board/join.ts +1 -1
- package/extensions/board/page.ts +170 -94
- package/extensions/board/result.ts +9 -0
- package/extensions/board/schema.ts +22 -7
- package/extensions/board/seat.ts +9 -10
- package/extensions/board/snapshot.ts +1 -0
- package/extensions/board/tools.ts +186 -21
- package/extensions/board/transport/router.ts +27 -108
- package/extensions/board/transport/server.ts +18 -29
- package/extensions/board/types.ts +67 -0
- package/extensions/board/view/client.ts +23 -13
- package/extensions/board/view/dialog.ts +43 -0
- package/extensions/board/view/keys.ts +28 -24
- package/extensions/board/view/live.ts +5 -171
- package/extensions/board/view/names.ts +91 -0
- package/extensions/board/view/page.ts +48 -88
- package/extensions/board/view/render.ts +213 -190
- package/extensions/board/view/styles.ts +65 -48
- package/extensions/board/view/team.ts +63 -41
- package/extensions/bytefly/footer/budget.ts +6 -5
- package/extensions/bytefly/footer/collect/entries.ts +12 -4
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -17
- package/extensions/bytefly/footer/config.ts +19 -8
- package/extensions/bytefly/footer/price/price.ts +30 -8
- package/extensions/bytefly/footer/register.ts +5 -5
- package/extensions/bytefly/footer/render/format.ts +18 -0
- package/extensions/bytefly/footer/render/lines.ts +23 -7
- package/extensions/bytefly/footer/render/segments.ts +117 -31
- package/extensions/bytefly/footer/types.ts +29 -5
- package/extensions/bytefly/footer/unpriced.ts +42 -17
- package/extensions/bytefly/panel/commands.ts +28 -1
- package/extensions/bytefly/panel/filter.ts +68 -0
- package/extensions/bytefly/panel/format.ts +16 -15
- package/extensions/bytefly/panel/join.ts +56 -13
- package/extensions/bytefly/panel/prices.ts +43 -0
- package/extensions/bytefly/panel/settings.ts +72 -23
- package/extensions/bytefly/panel/tui/entries.ts +26 -8
- package/extensions/bytefly/panel/tui/frame.ts +130 -93
- package/extensions/bytefly/panel/tui/keys.ts +352 -0
- package/extensions/bytefly/panel/tui/list-view.ts +25 -46
- package/extensions/bytefly/panel/tui/panes.ts +76 -0
- package/extensions/bytefly/panel/tui/section-view.ts +126 -0
- package/extensions/bytefly/panel/tui/settings-section.ts +54 -125
- package/extensions/bytefly/panel/tui/settings-view.ts +30 -131
- package/extensions/bytefly/panel/tui/side-pane.ts +224 -0
- package/extensions/bytefly/panel/tui.ts +306 -259
- package/extensions/check/commands.ts +2 -1
- package/extensions/file-browse/files/classify.ts +136 -0
- package/extensions/file-browse/files/files.ts +8 -547
- package/extensions/file-browse/files/index.ts +90 -0
- package/extensions/file-browse/files/list.ts +261 -0
- package/extensions/file-browse/files/tree.ts +101 -0
- package/extensions/file-browse/transport/router.ts +0 -2
- package/extensions/file-browse/view/emit.ts +51 -0
- package/extensions/file-browse/view/html.ts +6 -7
- package/extensions/file-browse/view/markdown.ts +11 -13
- package/extensions/file-browse/view/raw-html.ts +18 -13
- package/extensions/job/runtime.ts +4 -4
- package/extensions/plan/feedback.ts +40 -16
- package/extensions/plan/format.ts +10 -3
- package/extensions/plan/index.ts +17 -1
- package/extensions/plan/plan-files.ts +88 -0
- package/extensions/plan/plans.ts +19 -23
- package/extensions/plan/policy.ts +10 -5
- package/extensions/plan/runtime.ts +31 -2
- package/extensions/plan/todos.ts +51 -3
- package/extensions/plan/tools/exit.ts +10 -1
- package/extensions/plan/tools/todo-seed.ts +4 -2
- package/extensions/plan/tools/write.ts +12 -1
- package/extensions/plan/types.ts +15 -0
- package/extensions/plan/web-blocks.ts +89 -0
- package/extensions/plan/web-chrome.ts +3 -5
- package/extensions/plan/web-client.ts +179 -330
- package/extensions/plan/web-collab.ts +3 -3
- package/extensions/plan/web-comments.ts +18 -8
- package/extensions/plan/web-composer.ts +80 -0
- package/extensions/plan/web-draft.ts +40 -0
- package/extensions/plan/web-keys.ts +134 -0
- package/extensions/plan/web-outline.ts +192 -0
- package/extensions/plan/web-page.ts +4 -1
- package/extensions/plan/web-plan.ts +150 -0
- package/extensions/plan/web-router.ts +0 -1
- package/extensions/plan/web-settle.ts +194 -0
- package/extensions/rewind/commands/review.ts +2 -1
- package/extensions/session-bus/rows.ts +7 -3
- package/extensions/session-bus/runtime.ts +100 -38
- package/extensions/session-bus/store.ts +14 -2
- package/extensions/session-bus/tui.ts +1 -1
- package/extensions/session-bus/types.ts +10 -1
- package/extensions/subagent/commands.ts +2 -1
- package/extensions/team/commands.ts +41 -8
- package/extensions/team/format.ts +14 -0
- package/extensions/team/index.ts +2 -1
- package/extensions/team/tools.ts +30 -9
- package/extensions/team/types.ts +17 -0
- package/extensions/todo/index.ts +4 -1
- package/extensions/todo/nudge.ts +8 -2
- package/extensions/wechat/bridge.ts +43 -172
- package/extensions/wechat/intake.ts +245 -0
- package/extensions/worktree/commands.ts +24 -17
- package/extensions/worktree/exit.ts +12 -6
- package/extensions/worktree/git.ts +22 -14
- package/extensions/worktree/guard.ts +15 -285
- package/extensions/worktree/handoff.ts +10 -2
- package/extensions/worktree/index.ts +2 -1
- package/extensions/worktree/lifecycle.ts +2 -0
- package/extensions/worktree/registry.ts +20 -11
- package/extensions/worktree/root-tools.ts +13 -3
- package/extensions/worktree/runtime.ts +8 -2
- package/extensions/worktree/screen.ts +15 -8
- package/extensions/worktree/shell.ts +303 -0
- package/extensions/worktree/state.ts +1 -1
- package/extensions/worktree/status.ts +42 -9
- package/extensions/worktree/tools.ts +19 -13
- package/lib/README.md +6 -5
- package/lib/board/card.ts +192 -3
- package/lib/board/links.ts +26 -2
- package/lib/board/page-pointer.ts +86 -32
- package/lib/board/round-text.ts +11 -2
- package/lib/board/round.ts +25 -20
- package/lib/board/rules.ts +144 -11
- package/lib/board/store.ts +239 -52
- package/lib/board/view.ts +94 -10
- package/lib/format.ts +22 -0
- package/lib/http/serve.ts +3 -0
- package/lib/http/sink.ts +34 -9
- package/lib/plan-entry.ts +11 -0
- package/lib/policy.ts +14 -1
- package/lib/session-roster.ts +74 -3
- package/lib/spawn.ts +37 -3
- package/lib/stray-fields.ts +45 -0
- package/lib/tool-names.ts +13 -0
- package/lib/tui/glyphs.ts +2 -3
- package/lib/web/README.md +52 -2
- package/lib/web/client-runtime.ts +123 -0
- package/lib/web/index.ts +4 -2
- package/lib/web/markup.ts +88 -1
- package/lib/web/names.ts +42 -0
- package/package.json +1 -1
- package/extensions/board/view/actions.ts +0 -306
- package/extensions/board/writes.ts +0 -99
- package/extensions/bytefly/panel/tui/extensions-view.ts +0 -40
- package/extensions/bytefly/panel/tui/skills-view.ts +0 -50
- package/extensions/job/process.ts +0 -35
|
@@ -127,23 +127,24 @@ const SECTION_LABELS: Record<PanelSection, string> = {
|
|
|
127
127
|
settings: "Settings",
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
+
/** One section's name, as the tab row prints it. */
|
|
131
|
+
export function sectionLabel(section: PanelSection): string {
|
|
132
|
+
return SECTION_LABELS[section];
|
|
133
|
+
}
|
|
134
|
+
|
|
130
135
|
/**
|
|
131
|
-
* The
|
|
132
|
-
*
|
|
136
|
+
* The header's attention line: the worst thing the screen knows, then how many
|
|
137
|
+
* changes a reload would apply. `all clear` is said out loud rather than left
|
|
138
|
+
* blank, so the header never reads as "nothing loaded".
|
|
133
139
|
*/
|
|
134
|
-
export function
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
export function settingsSummarySegments(scope: PanelScope, count: number): SummarySegment[] {
|
|
143
|
-
return [
|
|
144
|
-
{ text: scope === "global" ? "Global" : "Project", color: "accent" },
|
|
145
|
-
{ text: `${count} settings`, color: "meta" },
|
|
146
|
-
];
|
|
140
|
+
export function attentionSegments(counts: PanelCounts, dirty: boolean): SummarySegment[] {
|
|
141
|
+
const segments: SummarySegment[] = [];
|
|
142
|
+
if (counts.missing > 0) segments.push({ text: `${counts.missing} missing`, color: "bad" });
|
|
143
|
+
else if (counts.degraded > 0) segments.push({ text: `${counts.degraded} degraded`, color: "warn" });
|
|
144
|
+
else if (counts.off > 0) segments.push({ text: `${counts.off} off`, color: "faint" });
|
|
145
|
+
else segments.push({ text: "all clear", color: "ok" });
|
|
146
|
+
if (dirty) segments.push({ text: "unsaved changes", color: "warn" });
|
|
147
|
+
return segments;
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
/**
|
|
@@ -31,6 +31,17 @@ export function extensionName(path: string): string {
|
|
|
31
31
|
return basename(dirname(path));
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* The name a resource is called by on screen: the directory for an entry file
|
|
36
|
+
* (`…/worktree/index.ts`, `…/pdf/SKILL.md`), and the file's own stem otherwise, so
|
|
37
|
+
* a flat `extensions/auto.ts` is `auto` rather than the directory every flat file
|
|
38
|
+
* shares.
|
|
39
|
+
*/
|
|
40
|
+
export function resourceName(path: string): string {
|
|
41
|
+
const stem = basename(path).replace(/\.(ts|js|mjs|cjs|md)$/u, "");
|
|
42
|
+
return stem === "index" || stem === "SKILL" ? basename(dirname(path)) : stem;
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
/** Row key for a report that has no resolved extension item. */
|
|
35
46
|
function reportKey(name: string): string {
|
|
36
47
|
return `report:${name}`;
|
|
@@ -46,13 +57,24 @@ export interface PanelJoinOptions {
|
|
|
46
57
|
agentDir: string;
|
|
47
58
|
/** Narrow to one extension directory or report name, or `undefined` for the full list. */
|
|
48
59
|
filter?: string;
|
|
60
|
+
/** Keep only the rows this accepts; a group's count follows what survives. */
|
|
61
|
+
where?: (row: PanelRow) => boolean;
|
|
49
62
|
/** Which resource kind the list is, for labels and grouping. */
|
|
50
63
|
kind?: ResourceKind;
|
|
51
64
|
}
|
|
52
65
|
|
|
66
|
+
/**
|
|
67
|
+
* The rows one group list produces, group headings included. The screen measures
|
|
68
|
+
* a section's height from this rather than from a built model, so a section it is
|
|
69
|
+
* not showing costs no `SKILL.md` read.
|
|
70
|
+
*/
|
|
71
|
+
export function groupedEntryCount(items: readonly ResourceItem[], agentDir: string, kind: ResourceKind): number {
|
|
72
|
+
return buildEntries([...items], agentDir, kind).length;
|
|
73
|
+
}
|
|
74
|
+
|
|
53
75
|
/** Build the merged model for the current items and reports. */
|
|
54
76
|
export function buildPanelModel(options: PanelJoinOptions): PanelModel {
|
|
55
|
-
const { items, reports, agentDir, filter, kind = "extensions" } = options;
|
|
77
|
+
const { items, reports, agentDir, filter, where, kind = "extensions" } = options;
|
|
56
78
|
const byName = new Map(reports.map((report) => [report.extension, report]));
|
|
57
79
|
|
|
58
80
|
// A directory name may claim a report only when exactly one unique resource
|
|
@@ -60,7 +82,7 @@ export function buildPanelModel(options: PanelJoinOptions): PanelModel {
|
|
|
60
82
|
// the report and it stays available to the trailing `Not resolved` group.
|
|
61
83
|
const pathsByName = new Map<string, Set<string>>();
|
|
62
84
|
for (const item of items) {
|
|
63
|
-
const name =
|
|
85
|
+
const name = resourceName(item.path);
|
|
64
86
|
const paths = pathsByName.get(name) ?? new Set<string>();
|
|
65
87
|
paths.add(resourceKey(item));
|
|
66
88
|
pathsByName.set(name, paths);
|
|
@@ -83,12 +105,23 @@ export function buildPanelModel(options: PanelJoinOptions): PanelModel {
|
|
|
83
105
|
const report = unambiguous(name) ? byName.get(name) : undefined;
|
|
84
106
|
if (report) claimed.add(name);
|
|
85
107
|
if (filter !== undefined && name !== filter) continue;
|
|
86
|
-
|
|
108
|
+
// The row is named for the resource, not for its file: a name is only unique
|
|
109
|
+
// enough to be a name when one resource uses it, which is the same rule that
|
|
110
|
+
// lets a directory claim a report. Otherwise the picker's `folder/file.ts`
|
|
111
|
+
// stays, because two rows would otherwise read alike.
|
|
112
|
+
const label = unambiguous(name) ? resourceName(item.path) : entry.label;
|
|
113
|
+
const row: PanelRow = { key: resourceKey(item), item, report, label, group: current?.key ?? "" };
|
|
114
|
+
if (where !== undefined && !where(row)) continue;
|
|
115
|
+
rows.push(row);
|
|
87
116
|
if (current) current.count += 1;
|
|
88
117
|
}
|
|
89
118
|
|
|
90
119
|
const extras = reports.filter(
|
|
91
|
-
(report) =>
|
|
120
|
+
(report) =>
|
|
121
|
+
!claimed.has(report.extension) &&
|
|
122
|
+
(filter === undefined || report.extension === filter) &&
|
|
123
|
+
(where === undefined ||
|
|
124
|
+
where({ key: reportKey(report.extension), report, label: report.extension, group: REPORT_ONLY_GROUP })),
|
|
92
125
|
);
|
|
93
126
|
if (extras.length > 0) {
|
|
94
127
|
const group: PanelGroup = { key: REPORT_ONLY_GROUP, label: REPORT_ONLY_LABEL, count: extras.length };
|
|
@@ -111,14 +144,24 @@ export function buildSkillModel(options: {
|
|
|
111
144
|
items: readonly ResourceItem[];
|
|
112
145
|
index: ReadonlyMap<string, SkillInfo>;
|
|
113
146
|
agentDir: string;
|
|
147
|
+
where?: (row: PanelRow) => boolean;
|
|
114
148
|
}): PanelModel {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
149
|
+
// The filter runs after the join: a skill's name and its `SKILL.md` facts are
|
|
150
|
+
// what a reader searches and what decides whether it needs attention.
|
|
151
|
+
const model = buildPanelModel({
|
|
152
|
+
items: options.items,
|
|
153
|
+
reports: [],
|
|
154
|
+
agentDir: options.agentDir,
|
|
155
|
+
kind: "skills",
|
|
156
|
+
});
|
|
157
|
+
const joined = model.rows.map((row) => {
|
|
158
|
+
const skill = row.item ? options.index.get(row.item.path) : undefined;
|
|
159
|
+
if (skill === undefined) return row;
|
|
160
|
+
return { ...row, label: skill.name ?? row.label, skill };
|
|
161
|
+
});
|
|
162
|
+
const rows = options.where === undefined ? joined : joined.filter(options.where);
|
|
163
|
+
const groups = model.groups
|
|
164
|
+
.map((group) => ({ ...group, count: rows.filter((row) => row.group === group.key).length }))
|
|
165
|
+
.filter((group) => group.count > 0);
|
|
166
|
+
return { groups, rows };
|
|
124
167
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/bytefly prices` report: which models this session could not price.
|
|
3
|
+
*
|
|
4
|
+
* A record nothing priced contributes no cost, so the footer's total is smaller than
|
|
5
|
+
* the work done and the reason is a *model*, not a record count. This is the answer to
|
|
6
|
+
* the question the footer can only hint at: the keys, in the `price.rates` vocabulary,
|
|
7
|
+
* and the one line saying what to do with them.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here quotes a price: a rate is the user's to state, and inventing one is the
|
|
10
|
+
* rate table this command exists instead of.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { sanitizeLine } from "../../../lib/format.ts";
|
|
14
|
+
import type { PricedUsage } from "../footer/types.ts";
|
|
15
|
+
import { formatCost } from "../footer/render/format.ts";
|
|
16
|
+
import { recordsText } from "../footer/unpriced.ts";
|
|
17
|
+
import type { Currency } from "../footer/price/types.ts";
|
|
18
|
+
|
|
19
|
+
/** How many keys the report prints before it counts the rest. */
|
|
20
|
+
export const REPORTED_MODELS = 8;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The report as the lines a reader gets.
|
|
24
|
+
*
|
|
25
|
+
* The headline says what the total is and how much of the session it leaves out; one
|
|
26
|
+
* line per key says what to add; the last line says where to add it. Pure: the caller
|
|
27
|
+
* has already priced the session.
|
|
28
|
+
*/
|
|
29
|
+
export function pricesReport(priced: PricedUsage, currency: Currency): string[] {
|
|
30
|
+
const models = priced.unpricedModels;
|
|
31
|
+
if (models.length === 0) return ["bytefly: every record in this session has a rate — nothing to add."];
|
|
32
|
+
const count = models.reduce((total, model) => total + model.records, 0);
|
|
33
|
+
const headline =
|
|
34
|
+
`bytefly: ${formatCost(priced.cost, currency)} priced · ` +
|
|
35
|
+
`${recordsText(count)} in ${models.length === 1 ? "1 model" : `${models.length} models`} ${count === 1 ? "has" : "have"} no rate`;
|
|
36
|
+
const keys = models
|
|
37
|
+
.slice(0, REPORTED_MODELS)
|
|
38
|
+
.map((model) => ` ${sanitizeLine(model.key)} · ${recordsText(model.records)}`);
|
|
39
|
+
const rest = models.length - keys.length;
|
|
40
|
+
if (rest > 0) keys.push(` +${rest} more`);
|
|
41
|
+
keys.push(' add these under "price.rates" in bytefly.json — a key may be `*`, or a prefix like `gpt-*`.');
|
|
42
|
+
return [headline, ...keys];
|
|
43
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* The catalogue is the shortlist a session changes, not the whole document: every
|
|
10
10
|
* row is one a workflow differs on, and each states its own rule so a value the
|
|
11
11
|
* screen accepts is a value the loader keeps. The catalogue order is the screen's
|
|
12
|
-
* order, and it is capped so the section fits a 24-row terminal (`
|
|
12
|
+
* order, and it is capped so the section fits a 24-row terminal (`settings.test.ts`
|
|
13
13
|
* holds the ceiling); a new row has to displace one. Everything else stays
|
|
14
14
|
* `bytefly.json` — a list (`doctor.skip`), a table (`price.rates`), a schedule
|
|
15
15
|
* (`price.peak`), and the tuning numbers and labels this screen deliberately does
|
|
@@ -28,9 +28,11 @@ import { configSection, configValueAt } from "../../../lib/config.ts";
|
|
|
28
28
|
import { DEFAULT_CONFIG as DEFAULT_DOCTOR_CONFIG, normalizeConfig as normalizeDoctorConfig } from "../doctor/config.ts";
|
|
29
29
|
import {
|
|
30
30
|
CURRENCY_DEFAULTS,
|
|
31
|
+
CURRENCY_SYMBOLS,
|
|
31
32
|
DEFAULT_BYTEFLY_CONFIG,
|
|
32
33
|
DEFAULT_CURRENCY,
|
|
33
34
|
MAX_BUDGET,
|
|
35
|
+
MAX_DECIMALS,
|
|
34
36
|
normalizeByteflyConfig,
|
|
35
37
|
} from "../footer/price/config.ts";
|
|
36
38
|
import type { CurrencyCode } from "../footer/price/types.ts";
|
|
@@ -46,6 +48,13 @@ type SettingKind = "boolean" | "enum" | "text";
|
|
|
46
48
|
/** Where the effective value came from. */
|
|
47
49
|
type SettingOrigin = "default" | "global" | "project" | "inherited";
|
|
48
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The heading a row sits under. The screen draws one heading per group, so twelve
|
|
53
|
+
* rows read as four short lists: what a session starts with, what it tells you
|
|
54
|
+
* about, what it keeps, and what it costs.
|
|
55
|
+
*/
|
|
56
|
+
export type SettingGroup = "Startup" | "Notifications" | "Session" | "Price";
|
|
57
|
+
|
|
49
58
|
/** One curated value a row can take. */
|
|
50
59
|
interface SettingOption {
|
|
51
60
|
value: string;
|
|
@@ -80,6 +89,8 @@ interface SettingLeaf {
|
|
|
80
89
|
export interface SettingDescriptor {
|
|
81
90
|
/** Stable identity, also the focus key. */
|
|
82
91
|
id: string;
|
|
92
|
+
/** The heading this row is listed under. */
|
|
93
|
+
group: SettingGroup;
|
|
83
94
|
label: string;
|
|
84
95
|
description: string;
|
|
85
96
|
source: SettingSource;
|
|
@@ -152,8 +163,10 @@ function amountRule(min: number, max: number): TextRule {
|
|
|
152
163
|
export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
153
164
|
{
|
|
154
165
|
id: "byteflyUi",
|
|
155
|
-
|
|
156
|
-
|
|
166
|
+
group: "Startup",
|
|
167
|
+
label: "Startup screen",
|
|
168
|
+
description:
|
|
169
|
+
"Show bytefly's header in place of Pi's own, so a session opens on the versions, the help keys, and the update marker.",
|
|
157
170
|
source: "app",
|
|
158
171
|
kind: "boolean",
|
|
159
172
|
fallback: "false",
|
|
@@ -162,9 +175,10 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
162
175
|
},
|
|
163
176
|
{
|
|
164
177
|
id: "theme",
|
|
178
|
+
group: "Startup",
|
|
165
179
|
label: "Theme",
|
|
166
180
|
description:
|
|
167
|
-
"
|
|
181
|
+
"The palette every bytefly surface is drawn with; `system` follows Pi's own colors. Takes effect after reload.",
|
|
168
182
|
source: "app",
|
|
169
183
|
kind: "enum",
|
|
170
184
|
fallback: "system",
|
|
@@ -173,8 +187,9 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
173
187
|
},
|
|
174
188
|
{
|
|
175
189
|
id: "notify.enabled",
|
|
190
|
+
group: "Notifications",
|
|
176
191
|
label: "Desktop notifications",
|
|
177
|
-
description: "
|
|
192
|
+
description: "The master switch for OS notifications; with it off, none of the four below is raised.",
|
|
178
193
|
source: "bytefly",
|
|
179
194
|
kind: "boolean",
|
|
180
195
|
fallback: "false",
|
|
@@ -184,8 +199,9 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
184
199
|
},
|
|
185
200
|
{
|
|
186
201
|
id: "notify.turnFinished",
|
|
187
|
-
|
|
188
|
-
|
|
202
|
+
group: "Notifications",
|
|
203
|
+
label: "When a turn ends",
|
|
204
|
+
description: "Raise a notification once the agent has finished a reply.",
|
|
189
205
|
source: "bytefly",
|
|
190
206
|
kind: "boolean",
|
|
191
207
|
fallback: "true",
|
|
@@ -195,8 +211,9 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
195
211
|
},
|
|
196
212
|
{
|
|
197
213
|
id: "notify.needsInput",
|
|
198
|
-
|
|
199
|
-
|
|
214
|
+
group: "Notifications",
|
|
215
|
+
label: "When the agent needs you",
|
|
216
|
+
description: "Raise a notification when a question or a permission prompt is waiting.",
|
|
200
217
|
source: "bytefly",
|
|
201
218
|
kind: "boolean",
|
|
202
219
|
fallback: "true",
|
|
@@ -206,8 +223,9 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
206
223
|
},
|
|
207
224
|
{
|
|
208
225
|
id: "notify.jobFinished",
|
|
209
|
-
|
|
210
|
-
|
|
226
|
+
group: "Notifications",
|
|
227
|
+
label: "When a job ends",
|
|
228
|
+
description: "Raise a notification when a background job or a watcher settles.",
|
|
211
229
|
source: "bytefly",
|
|
212
230
|
kind: "boolean",
|
|
213
231
|
fallback: "true",
|
|
@@ -217,8 +235,9 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
217
235
|
},
|
|
218
236
|
{
|
|
219
237
|
id: "notify.matchFound",
|
|
220
|
-
|
|
221
|
-
|
|
238
|
+
group: "Notifications",
|
|
239
|
+
label: "When a watcher matches",
|
|
240
|
+
description: "Raise a notification when a watched pattern appears, once the agent is idle.",
|
|
222
241
|
source: "bytefly",
|
|
223
242
|
kind: "boolean",
|
|
224
243
|
fallback: "true",
|
|
@@ -228,8 +247,9 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
228
247
|
},
|
|
229
248
|
{
|
|
230
249
|
id: "job.wakeOnFinish",
|
|
231
|
-
|
|
232
|
-
|
|
250
|
+
group: "Session",
|
|
251
|
+
label: "Resume after a job",
|
|
252
|
+
description: "Wake the agent once a background job finishes, so it can read the output.",
|
|
233
253
|
source: "bytefly",
|
|
234
254
|
kind: "boolean",
|
|
235
255
|
fallback: "false",
|
|
@@ -239,8 +259,9 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
239
259
|
},
|
|
240
260
|
{
|
|
241
261
|
id: "rewind.autoSnapshots",
|
|
242
|
-
|
|
243
|
-
|
|
262
|
+
group: "Session",
|
|
263
|
+
label: "Snapshot before a turn",
|
|
264
|
+
description: "Keep one rewind point per prompt, so `/rewind` can take you back.",
|
|
244
265
|
source: "bytefly",
|
|
245
266
|
kind: "boolean",
|
|
246
267
|
fallback: "true",
|
|
@@ -250,8 +271,9 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
250
271
|
},
|
|
251
272
|
{
|
|
252
273
|
id: "doctor.proactive",
|
|
253
|
-
|
|
254
|
-
|
|
274
|
+
group: "Session",
|
|
275
|
+
label: "Health check at startup",
|
|
276
|
+
description: "Probe what the extensions need before the first turn and say what is missing.",
|
|
255
277
|
source: "bytefly",
|
|
256
278
|
kind: "boolean",
|
|
257
279
|
fallback: "true",
|
|
@@ -261,9 +283,9 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
261
283
|
},
|
|
262
284
|
{
|
|
263
285
|
id: "price.currency.code",
|
|
286
|
+
group: "Price",
|
|
264
287
|
label: "Currency",
|
|
265
|
-
description:
|
|
266
|
-
"CNY prices DeepSeek from its yuan rates; USD from its published dollars, falling back to Pi's recorded cost.",
|
|
288
|
+
description: "The currency the footer prices a session in, at that currency's published rates.",
|
|
267
289
|
source: "bytefly",
|
|
268
290
|
kind: "enum",
|
|
269
291
|
fallback: DEFAULT_CURRENCY.code,
|
|
@@ -283,8 +305,9 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
|
|
|
283
305
|
},
|
|
284
306
|
{
|
|
285
307
|
id: "price.budget",
|
|
308
|
+
group: "Price",
|
|
286
309
|
label: "Session budget",
|
|
287
|
-
description: "
|
|
310
|
+
description: "Where the footer's `spent/limit` warns: one notice and one notification at the limit; 0 is off.",
|
|
288
311
|
source: "bytefly",
|
|
289
312
|
kind: "text",
|
|
290
313
|
fallback: "0",
|
|
@@ -329,6 +352,31 @@ function themeOptions(themes: readonly string[]): readonly SettingOption[] {
|
|
|
329
352
|
return names.map((name) => ({ value: name, label: name }));
|
|
330
353
|
}
|
|
331
354
|
|
|
355
|
+
/** The currency and precision a session prices in, through the price reader. */
|
|
356
|
+
function currencyOf(snapshot: SettingsSnapshot, scope: WriteScope): { code: CurrencyCode; decimals: number } {
|
|
357
|
+
const reader = SECTION_READERS.price;
|
|
358
|
+
const globalRaw = configSection(snapshot.global, "price");
|
|
359
|
+
const projectRaw = scope === "project" ? configSection(snapshot.project, "price") : undefined;
|
|
360
|
+
const code = reader?.(globalRaw, projectRaw, ["currency", "code"]);
|
|
361
|
+
const decimals = reader?.(globalRaw, projectRaw, ["currency", "decimals"]);
|
|
362
|
+
return {
|
|
363
|
+
code: typeof code === "string" && code in CURRENCY_SYMBOLS ? (code as CurrencyCode) : DEFAULT_CURRENCY.code,
|
|
364
|
+
decimals: typeof decimals === "number" ? Math.min(decimals, MAX_DECIMALS) : DEFAULT_CURRENCY.decimals,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* The budget's display value: `Off` for zero, otherwise the amount in the currency
|
|
370
|
+
* the session prices in. A reader thinks in `¥5`, not in `5`, and in `Off` rather
|
|
371
|
+
* than in `0`.
|
|
372
|
+
*/
|
|
373
|
+
function budgetLabel(raw: string, snapshot: SettingsSnapshot, scope: WriteScope): string {
|
|
374
|
+
const amount = Number(raw);
|
|
375
|
+
if (!Number.isFinite(amount) || amount <= 0) return "Off";
|
|
376
|
+
const { code, decimals } = currencyOf(snapshot, scope);
|
|
377
|
+
return `${CURRENCY_SYMBOLS[code]}${amount.toFixed(decimals)}`;
|
|
378
|
+
}
|
|
379
|
+
|
|
332
380
|
/** The display label for `raw`, or the raw value when it is a valid custom one. */
|
|
333
381
|
function labelFor(row: SettingDescriptor, raw: string): string {
|
|
334
382
|
const option = row.options.find((entry) => entry.value === raw);
|
|
@@ -428,7 +476,8 @@ export function settingViews(snapshot: SettingsSnapshot, scope: WriteScope): Set
|
|
|
428
476
|
const options = row.id === "theme" ? themeOptions(snapshot.themes) : row.options;
|
|
429
477
|
const { value, origin } = row.source === "app" ? appValue(snapshot, row) : byteflyValue(snapshot, row, scope);
|
|
430
478
|
const view = { ...row, options, value };
|
|
431
|
-
|
|
479
|
+
const valueLabel = row.id === "price.budget" ? budgetLabel(value, snapshot, scope) : labelFor(view, value);
|
|
480
|
+
return { ...view, valueLabel, origin };
|
|
432
481
|
});
|
|
433
482
|
}
|
|
434
483
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The panel's entry model: one flat list of
|
|
2
|
+
* The panel's entry model: one flat list of display entries per section, made of
|
|
3
|
+
* group headings interleaved with the selectable rows under them.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Every section groups. A resource list groups by source — where an extension came
|
|
6
|
+
* from is something its row cannot say — and Settings groups by what the
|
|
7
|
+
* preference does, so twelve rows read as four short lists instead of one long
|
|
8
|
+
* one. Both are built from a model or a set of views, so the component only has to
|
|
9
|
+
* keep one cursor per section.
|
|
8
10
|
*
|
|
9
11
|
* Pure: the list is rebuilt from a model or a set of views, and navigation is
|
|
10
12
|
* index arithmetic.
|
|
@@ -76,9 +78,25 @@ export function entriesOf(model: PanelModel): PanelEntry[] {
|
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
/**
|
|
79
|
-
* The Settings entries: one
|
|
80
|
-
*
|
|
81
|
+
* The Settings entries: one heading per group, in catalogue order, then its rows.
|
|
82
|
+
* The group key names the heading so a filter cannot collide a setting id with it.
|
|
81
83
|
*/
|
|
82
84
|
export function settingEntries(views: readonly SettingView[]): PanelEntry[] {
|
|
83
|
-
|
|
85
|
+
const entries: PanelEntry[] = [];
|
|
86
|
+
for (const [group, settings] of groupSettings(views)) {
|
|
87
|
+
entries.push({ kind: "group", key: `setting:${group}`, label: group, count: settings.length });
|
|
88
|
+
for (const setting of settings) entries.push({ kind: "setting", setting });
|
|
89
|
+
}
|
|
90
|
+
return entries;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The views by their group, in catalogue order of first appearance. */
|
|
94
|
+
function groupSettings(views: readonly SettingView[]): Map<string, SettingView[]> {
|
|
95
|
+
const groups = new Map<string, SettingView[]>();
|
|
96
|
+
for (const view of views) {
|
|
97
|
+
const list = groups.get(view.group) ?? [];
|
|
98
|
+
list.push(view);
|
|
99
|
+
groups.set(view.group, list);
|
|
100
|
+
}
|
|
101
|
+
return groups;
|
|
84
102
|
}
|