@firstpick/pi-package-webui 0.2.7 → 0.2.8

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.
@@ -10,12 +10,12 @@
10
10
  "category": "native-command",
11
11
  "title": "Settings selector/editor",
12
12
  "command": { "name": "settings", "description": "Open settings menu" },
13
- "webStatus": "degraded",
13
+ "webStatus": "implemented",
14
14
  "priority": "P1",
15
15
  "sensitive": false,
16
16
  "guards": ["none"],
17
- "currentBehavior": "Browser dialog covers high-frequency runtime settings only.",
18
- "targetBehavior": "Expose all documented settings with runtime/global/project scopes, source visibility, validation, and reload/restart labels."
17
+ "currentBehavior": "Browser dialog exposes native TUI settings in concise sections with runtime/browser/reload/TUI badges; reload-needed settings can restart the active tab after saving.",
18
+ "targetBehavior": "Keep native TUI setting coverage current; project/global source editing can be added as an advanced enhancement."
19
19
  },
20
20
  {
21
21
  "id": "/model",
package/bin/pi-webui.mjs CHANGED
@@ -42,6 +42,27 @@ const INLINE_IMAGE_MAX_BYTES = 8 * 1024 * 1024;
42
42
  const INLINE_IMAGE_TOTAL_MAX_BYTES = 16 * 1024 * 1024;
43
43
  const RPC_IMAGE_MIME_TYPES = new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
44
44
  const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"];
45
+ const SETTINGS_TRANSPORT_CHOICES = ["sse", "websocket", "websocket-cached", "auto"];
46
+ const SETTINGS_HTTP_IDLE_TIMEOUT_CHOICES = [
47
+ { label: "30 sec", timeoutMs: 30_000 },
48
+ { label: "1 min", timeoutMs: 60_000 },
49
+ { label: "2 min", timeoutMs: 120_000 },
50
+ { label: "5 min", timeoutMs: 300_000 },
51
+ { label: "disabled", timeoutMs: 0 },
52
+ ];
53
+ const SETTINGS_DOUBLE_ESCAPE_ACTIONS = ["tree", "fork", "none"];
54
+ const SETTINGS_TREE_FILTER_MODES = ["default", "no-tools", "user-only", "labeled-only", "all"];
55
+ const SETTINGS_IMAGE_WIDTH_CELLS = [60, 80, 120];
56
+ const SETTINGS_EDITOR_PADDING_X = [0, 1, 2, 3];
57
+ const SETTINGS_AUTOCOMPLETE_MAX_VISIBLE = [3, 5, 7, 10, 15, 20];
58
+ const SETTINGS_RELOAD_RECOMMENDED_KEYS = new Set(["transport", "httpIdleTimeoutMs", "autoResizeImages", "blockImages", "enableSkillCommands"]);
59
+ const SETTINGS_RELOAD_LABELS = new Map([
60
+ ["transport", "Transport"],
61
+ ["httpIdleTimeoutMs", "HTTP idle timeout"],
62
+ ["autoResizeImages", "Auto-resize images"],
63
+ ["blockImages", "Block images"],
64
+ ["enableSkillCommands", "Skill commands"],
65
+ ]);
45
66
  const EVENT_HISTORY_LIMIT = 200;
46
67
  const EXTENSION_UI_BLOCKING_METHODS = new Set(["select", "confirm", "input", "editor"]);
47
68
  const STATUS_RPC_TIMEOUT_MS = 1_800;
@@ -3103,6 +3124,167 @@ async function setSkillConfigData(tab, body) {
3103
3124
  return getMergedSkillConfigData(activeTab);
3104
3125
  }
3105
3126
 
3127
+ function settingsManagerForTab(tab) {
3128
+ return SettingsManager.create(tab?.cwd || options.cwd, agentDir);
3129
+ }
3130
+
3131
+ function nativeSettingsPayload(settingsManager = settingsManagerForTab()) {
3132
+ const settings = {
3133
+ transport: settingsManager.getTransport(),
3134
+ httpIdleTimeoutMs: settingsManager.getHttpIdleTimeoutMs(),
3135
+ autoResizeImages: settingsManager.getImageAutoResize(),
3136
+ blockImages: settingsManager.getBlockImages(),
3137
+ enableSkillCommands: settingsManager.getEnableSkillCommands(),
3138
+ hideThinkingBlock: settingsManager.getHideThinkingBlock(),
3139
+ showImages: settingsManager.getShowImages(),
3140
+ imageWidthCells: settingsManager.getImageWidthCells(),
3141
+ collapseChangelog: settingsManager.getCollapseChangelog(),
3142
+ quietStartup: settingsManager.getQuietStartup(),
3143
+ enableInstallTelemetry: settingsManager.getEnableInstallTelemetry(),
3144
+ doubleEscapeAction: settingsManager.getDoubleEscapeAction(),
3145
+ treeFilterMode: settingsManager.getTreeFilterMode(),
3146
+ showHardwareCursor: settingsManager.getShowHardwareCursor(),
3147
+ editorPaddingX: settingsManager.getEditorPaddingX(),
3148
+ autocompleteMaxVisible: settingsManager.getAutocompleteMaxVisible(),
3149
+ clearOnShrink: settingsManager.getClearOnShrink(),
3150
+ showTerminalProgress: settingsManager.getShowTerminalProgress(),
3151
+ warnings: settingsManager.getWarnings(),
3152
+ };
3153
+ return {
3154
+ settings,
3155
+ options: {
3156
+ thinkingLevels: THINKING_LEVELS,
3157
+ transports: SETTINGS_TRANSPORT_CHOICES,
3158
+ httpIdleTimeouts: SETTINGS_HTTP_IDLE_TIMEOUT_CHOICES,
3159
+ doubleEscapeActions: SETTINGS_DOUBLE_ESCAPE_ACTIONS,
3160
+ treeFilterModes: SETTINGS_TREE_FILTER_MODES,
3161
+ imageWidthCells: SETTINGS_IMAGE_WIDTH_CELLS,
3162
+ editorPaddingX: SETTINGS_EDITOR_PADDING_X,
3163
+ autocompleteMaxVisible: SETTINGS_AUTOCOMPLETE_MAX_VISIBLE,
3164
+ },
3165
+ scope: "global",
3166
+ paths: {
3167
+ global: settingsManager.storage?.globalSettingsPath || path.join(agentDir, "settings.json"),
3168
+ project: settingsManager.storage?.projectSettingsPath || path.join(options.cwd, ".pi", "settings.json"),
3169
+ },
3170
+ };
3171
+ }
3172
+
3173
+ function hasOwnSetting(body, key) {
3174
+ return Object.prototype.hasOwnProperty.call(body || {}, key);
3175
+ }
3176
+
3177
+ function requireBooleanSetting(value, key) {
3178
+ if (typeof value !== "boolean") throw makeHttpError(400, `${key} must be a boolean`);
3179
+ return value;
3180
+ }
3181
+
3182
+ function requireStringChoiceSetting(value, key, choices) {
3183
+ const text = String(value ?? "").trim();
3184
+ if (!choices.includes(text)) throw makeHttpError(400, `${key} must be one of: ${choices.join(", ")}`);
3185
+ return text;
3186
+ }
3187
+
3188
+ function requireNumberChoiceSetting(value, key, choices) {
3189
+ const number = Number(value);
3190
+ if (!Number.isFinite(number) || !choices.includes(number)) throw makeHttpError(400, `${key} must be one of: ${choices.join(", ")}`);
3191
+ return number;
3192
+ }
3193
+
3194
+ function rememberSettingChange(changed, reloadRecommended, key, before, after) {
3195
+ if (before === after) return;
3196
+ changed.push(key);
3197
+ if (SETTINGS_RELOAD_RECOMMENDED_KEYS.has(key)) reloadRecommended.push(SETTINGS_RELOAD_LABELS.get(key) || key);
3198
+ }
3199
+
3200
+ function applyBooleanSetting(body, key, settingsManager, getter, setter, changed, reloadRecommended) {
3201
+ if (!hasOwnSetting(body, key)) return;
3202
+ const next = requireBooleanSetting(body[key], key);
3203
+ const before = getter.call(settingsManager);
3204
+ if (before !== next) setter.call(settingsManager, next);
3205
+ rememberSettingChange(changed, reloadRecommended, key, before, next);
3206
+ }
3207
+
3208
+ function applyStringChoiceSetting(body, key, choices, settingsManager, getter, setter, changed, reloadRecommended) {
3209
+ if (!hasOwnSetting(body, key)) return;
3210
+ const next = requireStringChoiceSetting(body[key], key, choices);
3211
+ const before = getter.call(settingsManager);
3212
+ if (before !== next) setter.call(settingsManager, next);
3213
+ rememberSettingChange(changed, reloadRecommended, key, before, next);
3214
+ }
3215
+
3216
+ function applyNumberChoiceSetting(body, key, choices, settingsManager, getter, setter, changed, reloadRecommended) {
3217
+ if (!hasOwnSetting(body, key)) return;
3218
+ const next = requireNumberChoiceSetting(body[key], key, choices);
3219
+ const before = getter.call(settingsManager);
3220
+ if (before !== next) setter.call(settingsManager, next);
3221
+ rememberSettingChange(changed, reloadRecommended, key, before, next);
3222
+ }
3223
+
3224
+ function applyHttpIdleTimeoutSetting(body, settingsManager, changed, reloadRecommended) {
3225
+ const key = "httpIdleTimeoutMs";
3226
+ if (!hasOwnSetting(body, key)) return;
3227
+ const next = Number(body[key]);
3228
+ if (!Number.isFinite(next) || next < 0) throw makeHttpError(400, `${key} must be a non-negative number`);
3229
+ const normalized = Math.floor(next);
3230
+ const before = settingsManager.getHttpIdleTimeoutMs();
3231
+ if (before !== normalized) settingsManager.setHttpIdleTimeoutMs(normalized);
3232
+ rememberSettingChange(changed, reloadRecommended, key, before, normalized);
3233
+ }
3234
+
3235
+ async function setNativeSettingsData(tab, body) {
3236
+ const submitted = body?.settings && typeof body.settings === "object" ? body.settings : {};
3237
+ const settingsManager = settingsManagerForTab(tab);
3238
+ const changed = [];
3239
+ const reloadRecommended = [];
3240
+
3241
+ applyStringChoiceSetting(submitted, "transport", SETTINGS_TRANSPORT_CHOICES, settingsManager, settingsManager.getTransport, settingsManager.setTransport, changed, reloadRecommended);
3242
+ applyHttpIdleTimeoutSetting(submitted, settingsManager, changed, reloadRecommended);
3243
+ applyBooleanSetting(submitted, "autoResizeImages", settingsManager, settingsManager.getImageAutoResize, settingsManager.setImageAutoResize, changed, reloadRecommended);
3244
+ applyBooleanSetting(submitted, "blockImages", settingsManager, settingsManager.getBlockImages, settingsManager.setBlockImages, changed, reloadRecommended);
3245
+ applyBooleanSetting(submitted, "enableSkillCommands", settingsManager, settingsManager.getEnableSkillCommands, settingsManager.setEnableSkillCommands, changed, reloadRecommended);
3246
+ applyBooleanSetting(submitted, "hideThinkingBlock", settingsManager, settingsManager.getHideThinkingBlock, settingsManager.setHideThinkingBlock, changed, reloadRecommended);
3247
+ applyBooleanSetting(submitted, "showImages", settingsManager, settingsManager.getShowImages, settingsManager.setShowImages, changed, reloadRecommended);
3248
+ applyNumberChoiceSetting(submitted, "imageWidthCells", SETTINGS_IMAGE_WIDTH_CELLS, settingsManager, settingsManager.getImageWidthCells, settingsManager.setImageWidthCells, changed, reloadRecommended);
3249
+ applyBooleanSetting(submitted, "collapseChangelog", settingsManager, settingsManager.getCollapseChangelog, settingsManager.setCollapseChangelog, changed, reloadRecommended);
3250
+ applyBooleanSetting(submitted, "quietStartup", settingsManager, settingsManager.getQuietStartup, settingsManager.setQuietStartup, changed, reloadRecommended);
3251
+ applyBooleanSetting(submitted, "enableInstallTelemetry", settingsManager, settingsManager.getEnableInstallTelemetry, settingsManager.setEnableInstallTelemetry, changed, reloadRecommended);
3252
+ applyStringChoiceSetting(submitted, "doubleEscapeAction", SETTINGS_DOUBLE_ESCAPE_ACTIONS, settingsManager, settingsManager.getDoubleEscapeAction, settingsManager.setDoubleEscapeAction, changed, reloadRecommended);
3253
+ applyStringChoiceSetting(submitted, "treeFilterMode", SETTINGS_TREE_FILTER_MODES, settingsManager, settingsManager.getTreeFilterMode, settingsManager.setTreeFilterMode, changed, reloadRecommended);
3254
+ applyBooleanSetting(submitted, "showHardwareCursor", settingsManager, settingsManager.getShowHardwareCursor, settingsManager.setShowHardwareCursor, changed, reloadRecommended);
3255
+ applyNumberChoiceSetting(submitted, "editorPaddingX", SETTINGS_EDITOR_PADDING_X, settingsManager, settingsManager.getEditorPaddingX, settingsManager.setEditorPaddingX, changed, reloadRecommended);
3256
+ applyNumberChoiceSetting(submitted, "autocompleteMaxVisible", SETTINGS_AUTOCOMPLETE_MAX_VISIBLE, settingsManager, settingsManager.getAutocompleteMaxVisible, settingsManager.setAutocompleteMaxVisible, changed, reloadRecommended);
3257
+ applyBooleanSetting(submitted, "clearOnShrink", settingsManager, settingsManager.getClearOnShrink, settingsManager.setClearOnShrink, changed, reloadRecommended);
3258
+ applyBooleanSetting(submitted, "showTerminalProgress", settingsManager, settingsManager.getShowTerminalProgress, settingsManager.setShowTerminalProgress, changed, reloadRecommended);
3259
+
3260
+ if (submitted.warnings && typeof submitted.warnings === "object" && hasOwnSetting(submitted.warnings, "anthropicExtraUsage")) {
3261
+ const warnings = settingsManager.getWarnings();
3262
+ const before = warnings.anthropicExtraUsage ?? true;
3263
+ const next = requireBooleanSetting(submitted.warnings.anthropicExtraUsage, "warnings.anthropicExtraUsage");
3264
+ if (before !== next) {
3265
+ settingsManager.setWarnings({ ...warnings, anthropicExtraUsage: next });
3266
+ rememberSettingChange(changed, reloadRecommended, "warnings.anthropicExtraUsage", before, next);
3267
+ }
3268
+ }
3269
+
3270
+ await settingsManager.flush();
3271
+ let activeTab = tab;
3272
+ let reloaded = false;
3273
+ const shouldReload = body?.reload === true && reloadRecommended.length > 0;
3274
+ if (shouldReload) {
3275
+ activeTab = await restartTabRpc(tab, "settings");
3276
+ reloaded = true;
3277
+ }
3278
+
3279
+ return {
3280
+ ...nativeSettingsPayload(settingsManagerForTab(activeTab)),
3281
+ changed,
3282
+ reloadRecommended: [...new Set(reloadRecommended)],
3283
+ reloaded,
3284
+ tab: tabMeta(activeTab),
3285
+ };
3286
+ }
3287
+
3106
3288
  async function annotateSkillCommandState(tab, commands) {
3107
3289
  let disabledSkills = new Set();
3108
3290
  try {
@@ -4228,6 +4410,19 @@ const server = createServer(async (req, res) => {
4228
4410
  return;
4229
4411
  }
4230
4412
 
4413
+ if (url.pathname === "/api/settings" && req.method === "GET") {
4414
+ const tab = getRequestedTab(req, url);
4415
+ sendJson(res, 200, { ok: true, data: nativeSettingsPayload(settingsManagerForTab(tab)) });
4416
+ return;
4417
+ }
4418
+
4419
+ if (url.pathname === "/api/settings" && req.method === "POST") {
4420
+ const body = await readJsonBody(req);
4421
+ const tab = getRequestedTab(req, url, body);
4422
+ sendJson(res, 200, { ok: true, data: await setNativeSettingsData(tab, body) });
4423
+ return;
4424
+ }
4425
+
4231
4426
  if (url.pathname === "/api/commands" && req.method === "GET") {
4232
4427
  const tab = getRequestedTab(req, url);
4233
4428
  sendJson(res, 200, { type: "response", command: "get_commands", success: true, data: await getCommandData(tab) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firstpick/pi-package-webui",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Pi Web UI companion package with a local browser UI CLI plus /webui-start and /webui-status commands.",
5
5
  "license": "MIT",
6
6
  "type": "module",