flavor-code 1.4.3 → 1.4.4-beta.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/README.md CHANGED
@@ -33,7 +33,7 @@ Flavor Code connects to OpenAI, Anthropic, or compatible services and works with
33
33
  | --- | --- | --- |
34
34
  | 🖥️ | **One runtime, three entry points** | CLI, Electron, and VS Code share model configuration, sessions, and tooling |
35
35
  | 🧭 | **Controlled progress on complex tasks** | Task plans, sub-agents, steering, follow-ups, `/loop`, `/goal`, and conflict-safe parallel execution (tasks owning overlapping files run serially) |
36
- | 🏝️ | **Flavor Island local control** | Host apps steer a running session over a token-authenticated local IPC channel (Windows named pipes / Unix sockets): abort, steering, follow-ups, and window focus; model duration, token usage, task summaries, and deliverables are reported via hook events |
36
+ | 🏝️ | **Flavor Island local control** | Host apps steer a running session over a token-authenticated local IPC channel (Windows named pipes / Unix sockets): abort, steering, follow-ups, and window focus; model duration, token usage, session titles, task summaries, and deliverables are reported via hook events |
37
37
  | ⏪ | **Traceable, resumable results** | Full timeline, checkpoints, rewind, traces, diffs, and failure audits |
38
38
  | 🧱 | **Crash-consistent execution** | Fsync-backed event journal, durable steering queue, savepoints, and no automatic replay of non-idempotent tools |
39
39
  | 🧠 | **Local long-term context** | Memory, Skills, plugins, and project guides stored on your machine |
@@ -325,7 +325,7 @@ A Skill is a `SKILL.md` with YAML frontmatter, placed in `.flavor/skills/<name>/
325
325
 
326
326
  Plugins live in `.flavor/plugins/` and can register commands, tools, hooks, Skill roots, and model adapters. Official plugins can be installed with the plugin manager: `npx --yes @flavor-code/plugin-manager`. `additionalContext` returned by `SessionStart` and `UserPromptSubmit` hooks is added to the current task context, enabling reliable project-level engineering policy injection. Plugin loads record a content fingerprint plus declared capabilities. Worker/vm isolation is available through the embedding API's `pluginSandbox: true` option; the compatibility default remains in-process because bundled and existing plugins use Node.js APIs that the isolated runtime does not yet mediate.
327
327
 
328
- When the `flavor-island` plugin is loaded, Flavor also starts a Flavor Island local control channel: a loopback-only IPC service (Windows named pipe, or Unix socket on macOS/Linux) secured by a random token. A host app (such as the Flavor Island desktop) can use it to abort, steer, or send follow-ups to a running session, and desktop hosts can also bring their window into focus. The channel's endpoint, token, and capability list are exposed to the host plugin via hook event context (`islandControlEndpoint`/`islandControlToken`/`islandControlCapabilities`); model-call duration and token usage, plus the final task summary and deliverables, are reported through hook events so the host can show live status and a result overview.
328
+ When the `flavor-island` plugin is loaded, Flavor also starts a Flavor Island local control channel: a loopback-only IPC service (Windows named pipe, or Unix socket on macOS/Linux) secured by a random token. A host app (such as the Flavor Island desktop) can use it to abort, steer, or send follow-ups to a running session, and desktop hosts can also bring their window into focus. The channel's endpoint, token, and capability list are exposed to the host plugin via hook event context (`islandControlEndpoint`/`islandControlToken`/`islandControlCapabilities`); model-call duration and token usage, the current session title (desktop custom task name, falling back to the session preview), plus the final task summary and deliverables, are reported through hook events so the host can show live status and a result overview.
329
329
 
330
330
  > [!WARNING]
331
331
  > The default in-process plugin runtime grants full Node.js access. Only install and enable plugins you trust. Sandboxing reduces ambient access but does not make untrusted instructions safe, and plugins that import Node.js built-ins will not load with `pluginSandbox: true` yet.
package/README.zh-CN.md CHANGED
@@ -33,7 +33,7 @@ Flavor Code 接入 OpenAI、Anthropic 或兼容服务,在受控工作区内使
33
33
  | --- | --- | --- |
34
34
  | 🖥️ | **一个运行时,三个入口** | CLI、Electron 与 VS Code 共享模型配置、会话和工具能力 |
35
35
  | 🧭 | **复杂任务可控推进** | 任务计划、子 Agent、steering、follow-up、`/loop`、`/goal`,并行任务自动避免写冲突(拥有重叠文件的任务串行执行) |
36
- | 🏝️ | **Flavor Island 本地控制** | 宿主应用通过 token 认证的本机 IPC(Windows named pipe / Unix socket)控制运行中的会话:中止、steering、follow-up 与窗口聚焦;模型耗时、token 用量、任务摘要和交付物随 Hook 事件上报 |
36
+ | 🏝️ | **Flavor Island 本地控制** | 宿主应用通过 token 认证的本机 IPC(Windows named pipe / Unix socket)控制运行中的会话:中止、steering、follow-up 与窗口聚焦;模型耗时、token 用量、会话标题、任务摘要和交付物随 Hook 事件上报 |
37
37
  | ⏪ | **结果可追溯、可恢复** | 完整时间线、checkpoint、rewind、trace、Diff 和失败审计 |
38
38
  | 🧱 | **崩溃一致执行** | fsync 事件日志、持久 steering 队列、savepoint,非幂等工具不自动重放 |
39
39
  | 🧠 | **本地长期上下文** | 记忆、Skill、插件和项目指南均保存在本机 |
@@ -325,7 +325,7 @@ Skill 是带有 YAML 头信息的 `SKILL.md`,放在 `.flavor/skills/<name>/`
325
325
 
326
326
  插件放在 `.flavor/plugins/`,可以注册命令、工具、Hook、Skill 根目录和模型适配器。官方插件可通过插件管理器安装:`npx --yes @flavor-code/plugin-manager`。`SessionStart` 与 `UserPromptSubmit` Hook 返回的 `additionalContext` 会进入当前任务上下文,可用于注入项目级工程规则。插件加载会记录内容指纹与声明的能力。可通过嵌入 API 的 `pluginSandbox: true` 启用 Worker/vm 隔离;由于内置插件和已有插件依赖沙箱尚未代理的 Node.js API,当前兼容默认值仍为进程内运行。
327
327
 
328
- 加载 `flavor-island` 插件时,Flavor 会额外启动一个 Flavor Island 本地控制通道:这是一个只监听本机回环的 IPC 服务(Windows 使用 named pipe,macOS/Linux 使用 Unix socket),通过随机 token 认证。宿主应用(如 Flavor Island 桌面端)可以借此对运行中的会话执行中止、steering、follow-up 等操作,桌面端还支持把窗口带到前台(focus)。通道的地址、token 与能力列表会通过 Hook 事件上下文(`islandControlEndpoint`/`islandControlToken`/`islandControlCapabilities`)提供给宿主插件,模型调用的耗时与 token 用量、任务结束时的摘要和交付文件也会随 Hook 事件上报,方便宿主展示运行状态与结果概览。
328
+ 加载 `flavor-island` 插件时,Flavor 会额外启动一个 Flavor Island 本地控制通道:这是一个只监听本机回环的 IPC 服务(Windows 使用 named pipe,macOS/Linux 使用 Unix socket),通过随机 token 认证。宿主应用(如 Flavor Island 桌面端)可以借此对运行中的会话执行中止、steering、follow-up 等操作,桌面端还支持把窗口带到前台(focus)。通道的地址、token 与能力列表会通过 Hook 事件上下文(`islandControlEndpoint`/`islandControlToken`/`islandControlCapabilities`)提供给宿主插件,模型调用的耗时与 token 用量、当前会话标题(桌面端自定义任务名,缺省时回退到会话 preview 摘要)、任务结束时的摘要和交付文件也会随 Hook 事件上报,方便宿主展示运行状态与结果概览。
329
329
 
330
330
  > [!WARNING]
331
331
  > 默认的进程内插件运行时拥有完整 Node.js 权限,只安装和启用你信任的插件。沙箱会降低环境权限,但不能让不可信指令自动变安全;目前导入 Node.js 内置模块的插件在 `pluginSandbox: true` 下仍无法加载。
@@ -34,7 +34,7 @@ import {
34
34
  createGlobTool,
35
35
  createProductionRuntime,
36
36
  isDestructiveTool
37
- } from "./chunk-QACFKPZP.js";
37
+ } from "./chunk-U4XTQ5PU.js";
38
38
  import {
39
39
  getGraphemeSegmenter
40
40
  } from "./chunk-TIEOVQW6.js";
@@ -3427,7 +3427,12 @@ function TerminalLayout({
3427
3427
  [completed, budget]
3428
3428
  );
3429
3429
  const memoryReviewRows = memoryReviews.length === 0 ? 0 : 5;
3430
- const fixedBottomRows = memoryReviewRows + (pendingPrompts.length === 0 ? 0 : 1) + imageAttachments.length + (clipboardNotice === void 0 ? 0 : 1) + (activeSession ? 1 : 0) + 2;
3430
+ const focusedOpen = approval !== void 0 || questions !== void 0 && questions.length > 0 || queueOpen;
3431
+ const focusedRows = focusedOpen ? Math.min(
3432
+ focusedPanelNaturalRows({ approval, approvalExpanded, questions, questionIndex, queueOpen, pendingPrompts, columns, rows }),
3433
+ Math.min(Math.max(4, rows - 3), Math.max(6, Math.floor(rows / 2)))
3434
+ ) : 0;
3435
+ const fixedBottomRows = focusedRows + memoryReviewRows + (pendingPrompts.length === 0 ? 0 : 1) + imageAttachments.length + (clipboardNotice === void 0 ? 0 : 1) + (activeSession ? 1 : 0) + 2;
3431
3436
  const taskPanelRows = taskPanelViewportRows(rows, fixedBottomRows, activeTaskBlocks.length > 0);
3432
3437
  const availableBottomRows = Math.max(1, rows - taskPanelRows - 1);
3433
3438
  const bottomMaxRows = Math.min(availableBottomRows, Math.max(Math.floor(rows / 2), fixedBottomRows + 1));
@@ -3451,28 +3456,8 @@ function TerminalLayout({
3451
3456
  } else {
3452
3457
  footerHint = `Enter send \xB7 \u2191\u2193/Ctrl/Cmd+R history \xB7 ${outputShortcut} ${expandedOutput ? "collapse all output" : "expand all output"} \xB7 Ctrl+C exit`;
3453
3458
  }
3454
- if (approval !== void 0 || questions !== void 0 && questions.length > 0 || queueOpen) {
3455
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3456
- FocusedCliPanel,
3457
- {
3458
- rows,
3459
- columns,
3460
- input,
3461
- promptCursor,
3462
- ...approval === void 0 ? {} : { approval },
3463
- approvalExpanded,
3464
- ...questions === void 0 ? {} : { questions },
3465
- questionIndex,
3466
- customQuestionActive,
3467
- pendingPrompts,
3468
- queueSelection,
3469
- ...onQueueSelectionChange === void 0 ? {} : { onQueueSelectionChange },
3470
- ...focusScrollRef === void 0 ? {} : { focusScrollRef }
3471
- }
3472
- );
3473
- }
3474
3459
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { height: rows, width: "100%", flexDirection: "column", overflow: "hidden", children: [
3475
- showWelcome ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { flexGrow: 1, flexShrink: 1, flexDirection: "column", overflow: "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WelcomeCard, { model, ...serviceName === void 0 ? {} : { serviceName }, workspaceName, ...updateTo === void 0 ? {} : { updateTo }, columns, availableRows: Math.max(0, rows - taskPanelRows - fixedBottomRows - 1) }) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ScrollBox_default, { ...scrollRef === void 0 ? {} : { ref: scrollRef }, flexGrow: 1, flexDirection: "column", stickyScroll: true, children: [
3460
+ showWelcome && !focusedOpen ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Box_default, { flexGrow: 1, flexShrink: 1, flexDirection: "column", overflow: "hidden", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WelcomeCard, { model, ...serviceName === void 0 ? {} : { serviceName }, workspaceName, ...updateTo === void 0 ? {} : { updateTo }, columns, availableRows: Math.max(0, rows - taskPanelRows - fixedBottomRows - 1) }) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ScrollBox_default, { ...scrollRef === void 0 ? {} : { ref: scrollRef }, flexGrow: 1, flexDirection: "column", stickyScroll: true, children: [
3476
3461
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { dimColor: true, children: [
3477
3462
  "flavor \xB7 ",
3478
3463
  model,
@@ -3521,6 +3506,23 @@ function TerminalLayout({
3521
3506
  mentionCompletion === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MentionMenu, { completion: mentionCompletion, ...onMentionSelect === void 0 ? {} : { onSelect: onMentionSelect } })
3522
3507
  ] }),
3523
3508
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { flexDirection: "column", flexShrink: 0, maxHeight: bottomMaxRows, width: "100%", overflowY: "hidden", children: [
3509
+ !focusedOpen ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3510
+ FocusedCliPanel,
3511
+ {
3512
+ rows,
3513
+ panelRows: focusedRows,
3514
+ columns,
3515
+ ...approval === void 0 ? {} : { approval },
3516
+ approvalExpanded,
3517
+ ...questions === void 0 ? {} : { questions },
3518
+ questionIndex,
3519
+ customQuestionActive,
3520
+ pendingPrompts,
3521
+ queueSelection,
3522
+ ...onQueueSelectionChange === void 0 ? {} : { onQueueSelectionChange },
3523
+ ...focusScrollRef === void 0 ? {} : { focusScrollRef }
3524
+ }
3525
+ ),
3524
3526
  memoryReviews.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MemoryReviewCards, { reviews: memoryReviews, autoDismissSeconds: memoryAutoDismissSeconds }),
3525
3527
  !activeSession ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "cyan", wrap: "truncate-end", children: columns < 48 ? "Running \xB7 Enter queues \xB7 /queue list" : "Running \xB7 /queue manage \xB7 Enter queues next \xB7 /steer acts now" }),
3526
3528
  pendingPrompts.length === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "yellow", wrap: "truncate-end", children: [
@@ -3584,11 +3586,34 @@ function ideFooterPresentation(context) {
3584
3586
  const count = Math.max(1, endLine - context.selection.start.line + 1);
3585
3587
  return `${count} ${count === 1 ? "line" : "lines"} selected`;
3586
3588
  }
3589
+ function focusedPanelNaturalRows({
3590
+ approval,
3591
+ approvalExpanded,
3592
+ questions,
3593
+ questionIndex,
3594
+ queueOpen,
3595
+ pendingPrompts,
3596
+ columns,
3597
+ rows
3598
+ }) {
3599
+ if (approval !== void 0) {
3600
+ const allowAlways = !isDestructiveTool(approval.tool) && approval.allowAlways !== false;
3601
+ const detail = approvalExpanded ? approvalDetailLines(approval) : [];
3602
+ const contentRows = wrappedRows(approval.reason ?? "This action needs permission.", columns) + detail.reduce((total, line) => total + wrappedRows(line, columns), 0);
3603
+ return 1 + Math.max(contentRows, 1) + 2 + (allowAlways && columns < 36 && rows >= 5 ? 1 : 0);
3604
+ }
3605
+ const question = questions?.[Math.min(questionIndex, questions.length - 1)];
3606
+ if (question !== void 0) {
3607
+ const contentRows = wrappedRows(question.question, columns) + question.options.reduce((total, option) => total + wrappedRows(`${option.label}${option.description ? ` ${option.description}` : ""}`, columns), 0) + 1;
3608
+ return 1 + Math.max(contentRows, 1) + 1;
3609
+ }
3610
+ if (queueOpen) return 1 + Math.max(1, pendingPrompts.length) + 2;
3611
+ return 0;
3612
+ }
3587
3613
  function FocusedCliPanel({
3588
3614
  rows,
3615
+ panelRows,
3589
3616
  columns,
3590
- input,
3591
- promptCursor,
3592
3617
  approval,
3593
3618
  approvalExpanded,
3594
3619
  questions,
@@ -3602,27 +3627,45 @@ function FocusedCliPanel({
3602
3627
  if (approval !== void 0) {
3603
3628
  const allowAlways = !isDestructiveTool(approval.tool) && approval.allowAlways !== false;
3604
3629
  const narrow = columns < 36;
3605
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { height: rows, width: "100%", flexDirection: "column", overflow: "hidden", children: [
3630
+ const detailLines = approvalExpanded ? approvalDetailLines(approval) : [];
3631
+ const chromeRows = 3 + (allowAlways && narrow && rows >= 5 ? 1 : 0);
3632
+ const thirdHint = allowAlways && narrow && rows >= 5;
3633
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { width: "100%", height: panelRows, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: [
3606
3634
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { bold: true, color: "magenta", wrap: "truncate-end", children: [
3635
+ "\u250C\u2500 ",
3607
3636
  "Approval \xB7 ",
3608
3637
  approval.tool
3609
3638
  ] }),
3610
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ScrollBox_default, { ...focusScrollRef === void 0 ? {} : { ref: focusScrollRef }, flexGrow: 1, flexDirection: "column", children: [
3611
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "magentaBright", wrap: "wrap", children: approval.reason ?? "This action needs permission." }),
3612
- approvalExpanded ? approvalDetailLines(approval).map((line, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "magentaBright", wrap: "wrap", children: line }, `${approval.id}:detail:${index}`)) : null
3639
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ScrollBox_default, { ...focusScrollRef === void 0 ? {} : { ref: focusScrollRef }, height: Math.max(1, panelRows - chromeRows), flexDirection: "column", children: [
3640
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "magentaBright", wrap: "wrap", children: [
3641
+ "\u2502 ",
3642
+ approval.reason ?? "This action needs permission."
3643
+ ] }),
3644
+ approvalExpanded ? detailLines.map((line, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "magentaBright", wrap: "wrap", children: [
3645
+ "\u2502 ",
3646
+ line
3647
+ ] }, `${approval.id}:detail:${index}`)) : null
3648
+ ] }),
3649
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { bold: true, color: "magenta", wrap: "truncate-end", children: [
3650
+ "\u2502 ",
3651
+ narrow ? "y once \xB7 n deny" : "y=once \xB7 n=deny \xB7 Esc=deny"
3613
3652
  ] }),
3614
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, color: "magenta", wrap: "truncate-end", children: narrow ? "y once \xB7 n deny" : "y=once \xB7 n=deny \xB7 Esc=deny" }),
3615
3653
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "magenta", wrap: "truncate-end", children: [
3654
+ thirdHint ? "\u2502 " : "\u2514\u2500 ",
3616
3655
  narrow ? "v details \xB7 e edits" : "v=details \xB7 e=accept edits",
3617
3656
  allowAlways && !narrow ? " \xB7 a=same-type" : ""
3618
3657
  ] }),
3619
- allowAlways && narrow && rows >= 5 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "magenta", wrap: "truncate-end", children: "a same-type \xB7 Esc deny" }) : null
3658
+ thirdHint ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "magenta", wrap: "truncate-end", children: [
3659
+ "\u2514\u2500 ",
3660
+ "a same-type \xB7 Esc deny"
3661
+ ] }) : null
3620
3662
  ] });
3621
3663
  }
3622
3664
  const question = questions?.[Math.min(questionIndex, questions.length - 1)];
3623
3665
  if (question !== void 0) {
3624
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { height: rows, width: "100%", flexDirection: "column", overflow: "hidden", children: [
3666
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { width: "100%", height: panelRows, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: [
3625
3667
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { bold: true, color: "cyan", wrap: "truncate-end", children: [
3668
+ "\u250C\u2500 ",
3626
3669
  "Question ",
3627
3670
  questionIndex + 1,
3628
3671
  "/",
@@ -3630,10 +3673,14 @@ function FocusedCliPanel({
3630
3673
  " \xB7 ",
3631
3674
  question.header
3632
3675
  ] }),
3633
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ScrollBox_default, { ...focusScrollRef === void 0 ? {} : { ref: focusScrollRef }, flexGrow: 1, flexDirection: "column", children: [
3634
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "cyanBright", wrap: "wrap", children: question.question }),
3676
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(ScrollBox_default, { ...focusScrollRef === void 0 ? {} : { ref: focusScrollRef }, height: Math.max(1, panelRows - 2), flexDirection: "column", children: [
3677
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "cyanBright", wrap: "wrap", children: [
3678
+ "\u2502 ",
3679
+ question.question
3680
+ ] }),
3635
3681
  question.options.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "cyan", wrap: "wrap", children: [
3636
3682
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { bold: true, color: "green", children: [
3683
+ "\u2502 ",
3637
3684
  index + 1,
3638
3685
  ". ",
3639
3686
  option.label
@@ -3641,21 +3688,25 @@ function FocusedCliPanel({
3641
3688
  option.description ? ` ${option.description}` : ""
3642
3689
  ] }, index)),
3643
3690
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "cyan", children: [
3691
+ "\u2502 ",
3644
3692
  question.options.length + 1,
3645
3693
  ". Custom input"
3646
3694
  ] })
3647
3695
  ] }),
3648
- customQuestionActive ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PromptLine, { input, pastedBlocks: [], cursor: promptCursor, columns, maxVisibleLines: Math.max(1, Math.min(3, Math.floor(rows / 3))) }) : null,
3649
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "cyan", wrap: "truncate-end", children: customQuestionActive ? columns < 40 ? "Enter answer \xB7 Esc choices" : "Enter=answer \xB7 Esc=choices \xB7 PgUp/PgDn=scroll" : columns < 40 ? `1-${question.options.length + 1} choose \xB7 Esc cancel` : `1-${question.options.length + 1}=choose \xB7 Esc=cancel \xB7 PgUp/PgDn=scroll` })
3696
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "cyan", wrap: "truncate-end", children: [
3697
+ "\u2514\u2500 ",
3698
+ customQuestionActive ? columns < 40 ? "Enter answer \xB7 Esc choices" : "Enter=answer \xB7 Esc=choices \xB7 PgUp/PgDn=scroll" : columns < 40 ? `1-${question.options.length + 1} choose \xB7 Esc cancel` : `1-${question.options.length + 1}=choose \xB7 Esc=cancel \xB7 PgUp/PgDn=scroll`
3699
+ ] })
3650
3700
  ] });
3651
3701
  }
3652
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { height: rows, width: "100%", flexDirection: "column", overflow: "hidden", children: [
3702
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Box_default, { width: "100%", height: panelRows, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: [
3653
3703
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { bold: true, color: "yellow", wrap: "truncate-end", children: [
3704
+ "\u250C\u2500 ",
3654
3705
  "Queued messages (",
3655
3706
  pendingPrompts.length,
3656
3707
  ")"
3657
3708
  ] }),
3658
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ScrollBox_default, { ...focusScrollRef === void 0 ? {} : { ref: focusScrollRef }, flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3709
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ScrollBox_default, { ...focusScrollRef === void 0 ? {} : { ref: focusScrollRef }, height: Math.max(1, panelRows - 3), flexDirection: "column", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
3659
3710
  QueueItems,
3660
3711
  {
3661
3712
  items: pendingPrompts,
@@ -3664,10 +3715,19 @@ function FocusedCliPanel({
3664
3715
  ...onQueueSelectionChange === void 0 ? {} : { onSelect: onQueueSelectionChange }
3665
3716
  }
3666
3717
  ) }),
3667
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { bold: true, color: "yellow", wrap: "truncate-end", children: columns < 48 ? "Enter edit \xB7 d cancel" : "Enter=edit selected \xB7 d=cancel selected" }),
3668
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { color: "yellow", wrap: "truncate-end", children: columns < 48 ? "\u2191\u2193 select \xB7 Esc close" : "\u2191/\u2193=select \xB7 PgUp/PgDn=jump \xB7 Esc=close" })
3718
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { bold: true, color: "yellow", wrap: "truncate-end", children: [
3719
+ "\u2502 ",
3720
+ columns < 48 ? "Enter edit \xB7 d cancel" : "Enter=edit selected \xB7 d=cancel selected"
3721
+ ] }),
3722
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { color: "yellow", wrap: "truncate-end", children: [
3723
+ "\u2514\u2500 ",
3724
+ columns < 48 ? "\u2191\u2193 select \xB7 Esc close" : "\u2191/\u2193=select \xB7 PgUp/PgDn=jump \xB7 Esc=close"
3725
+ ] })
3669
3726
  ] });
3670
3727
  }
3728
+ function wrappedRows(text, columns) {
3729
+ return Math.max(1, Math.ceil(stringWidth(text) / Math.max(20, columns)));
3730
+ }
3671
3731
  function QueueItems({ items, selection, scrollRef, onSelect }) {
3672
3732
  const selectedElement = (0, import_react8.useRef)(null);
3673
3733
  (0, import_react8.useEffect)(() => {
@@ -40914,6 +40914,7 @@ async function createProductionRuntime(options) {
40914
40914
  protocolVersion: 2,
40915
40915
  workspace,
40916
40916
  ...hookSessionId === void 0 ? {} : { sessionId: hookSessionId },
40917
+ ...hookSessionId === void 0 || options.islandSessionTitle === void 0 ? {} : { sessionTitle: options.islandSessionTitle(hookSessionId) },
40917
40918
  ...islandControlMetadata === void 0 ? {} : {
40918
40919
  islandControlEndpoint: islandControlMetadata.endpoint,
40919
40920
  islandControlToken: islandControlMetadata.token,
@@ -3,7 +3,7 @@ import { createRequire as createFlavorRequire } from "node:module"; const requir
3
3
  import {
4
4
  createExecutionEnvironment,
5
5
  createProductionRuntime
6
- } from "./chunk-QACFKPZP.js";
6
+ } from "./chunk-U4XTQ5PU.js";
7
7
  import "./chunk-TIEOVQW6.js";
8
8
  import "./chunk-OKHV7CTB.js";
9
9
  import {
package/dist/cli-main.js CHANGED
@@ -4081,7 +4081,7 @@ function createProgram(dependencies = {}) {
4081
4081
  registerConfigCommands(program2);
4082
4082
  registerUsageCommands(program2);
4083
4083
  program2.command("eval <spec>").option("--output <path>", "write the JSON report to a file").description("run a repeatable coding-agent evaluation").action(async (spec, command) => {
4084
- const { runEvaluationFile } = await import("./cli-MAWCV2YR.js");
4084
+ const { runEvaluationFile } = await import("./cli-V3BLZ5UW.js");
4085
4085
  process.exitCode = await runEvaluationFile(spec, command.output);
4086
4086
  });
4087
4087
  program2.action(async (options) => {
@@ -4173,7 +4173,7 @@ async function runInteractiveCli(props) {
4173
4173
  const [{ render, AlternateScreen }, { default: React }, { App }] = await Promise.all([
4174
4174
  import("./claude-ink-F554I3GJ.js"),
4175
4175
  import("./react-M6JGXSJZ.js"),
4176
- import("./app-TVY2HM5L.js")
4176
+ import("./app-C5I2TH7I.js")
4177
4177
  ]);
4178
4178
  const instance = await render(React.createElement(
4179
4179
  AlternateScreen,
@@ -4212,7 +4212,7 @@ async function runRpcMode(options) {
4212
4212
  import("./server-2KLKPHPL.js"),
4213
4213
  import("./write-stream-CGD57URC.js"),
4214
4214
  import("./recorder-FAGJJH3N.js"),
4215
- import("./production-TQROFEPV.js")
4215
+ import("./production-TD7UWWXW.js")
4216
4216
  ]);
4217
4217
  let recorder;
4218
4218
  try {
@@ -4346,7 +4346,7 @@ async function runPrint(prompt, dependencies = {}, resumeSession, memoryRestart
4346
4346
  code = 1;
4347
4347
  });
4348
4348
  try {
4349
- const createRuntime = dependencies.createRuntime ?? (await import("./production-TQROFEPV.js")).createProductionRuntime;
4349
+ const createRuntime = dependencies.createRuntime ?? (await import("./production-TD7UWWXW.js")).createProductionRuntime;
4350
4350
  runtime = await createRuntime({
4351
4351
  workspace: process.cwd(),
4352
4352
  home: homedir3(),
@@ -30210,6 +30210,7 @@ async function createProductionRuntime(options) {
30210
30210
  protocolVersion: 2,
30211
30211
  workspace,
30212
30212
  ...hookSessionId === void 0 ? {} : { sessionId: hookSessionId },
30213
+ ...hookSessionId === void 0 || options.islandSessionTitle === void 0 ? {} : { sessionTitle: options.islandSessionTitle(hookSessionId) },
30213
30214
  ...islandControlMetadata === void 0 ? {} : {
30214
30215
  islandControlEndpoint: islandControlMetadata.endpoint,
30215
30216
  islandControlToken: islandControlMetadata.token,
@@ -37174,6 +37175,14 @@ function createDesktopController(workspace, owner2) {
37174
37175
  },
37175
37176
  createRuntime: async (runtimeOptions) => createProductionRuntime({
37176
37177
  ...runtimeOptions,
37178
+ islandSessionTitle: (sessionId) => {
37179
+ const project = workspace ?? runtimeOptions.workspace;
37180
+ if (project === void 0) return void 0;
37181
+ const custom = workbench.sessionMeta[sessionMetaKey(project, sessionId)]?.title?.trim();
37182
+ if (custom) return custom;
37183
+ const summary2 = owner2?.snapshot.sessions.find((item) => item.sessionId === sessionId) ?? managedProjects.get(project)?.snapshot.sessions.find((item) => item.sessionId === sessionId);
37184
+ return summary2?.preview?.trim();
37185
+ },
37177
37186
  islandControl: {
37178
37187
  focus: () => {
37179
37188
  const target = mainWindow;
@@ -9,7 +9,7 @@ import {
9
9
  matchesHeadlessAllowlist,
10
10
  runGoalSession,
11
11
  runResumeGoalSession
12
- } from "./chunk-QACFKPZP.js";
12
+ } from "./chunk-U4XTQ5PU.js";
13
13
  import "./chunk-TIEOVQW6.js";
14
14
  import "./chunk-OKHV7CTB.js";
15
15
  import "./chunk-JQM2L2YM.js";
@@ -60,6 +60,8 @@ export interface ProductionRuntimeOptions {
60
60
  islandControl?: {
61
61
  focus?(): void | Promise<void>;
62
62
  };
63
+ /** Current desktop task title for the companion island; read on every hook. */
64
+ islandSessionTitle?: (sessionId: string) => string | undefined;
63
65
  /** Explicitly opt into CLI-local collaboration. Omit for print/RPC/eval callers. */
64
66
  collaboration?: {
65
67
  instanceId: string;
package/dist/sdk/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  WorkspaceCheckpointStore,
16
16
  buildDockerInvocation,
17
17
  createProductionRuntime
18
- } from "../chunk-QACFKPZP.js";
18
+ } from "../chunk-U4XTQ5PU.js";
19
19
  import "../chunk-TIEOVQW6.js";
20
20
  import "../chunk-OKHV7CTB.js";
21
21
  import "../chunk-JQM2L2YM.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flavor-code",
3
- "version": "1.4.3",
3
+ "version": "1.4.4-beta.2",
4
4
  "author": "YachuanWzh",
5
5
  "type": "module",
6
6
  "publishConfig": {