@songsid/agend 2.3.2-beta.2 → 2.3.2-beta.3

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.
@@ -152,7 +152,8 @@ workspace `inbox/`; an unused upload is deleted after 30 minutes.
152
152
 
153
153
  **In replies.** Images inline; video and audio play inline; Markdown, CSV, JSON, code and text files get a **Show**
154
154
  card (200 lines, then "Show all … lines"); other files download. Code blocks have a language tag, **Wrap** and
155
- **Copy**, and fold after 30 lines. Each reply has **Copy this reply**. Markdown: headings, lists, quotes, bold/italic/
155
+ **Copy**, and fold after 30 lines. **⋯ → Full width** (desk only, off by default, this browser's choice) widens the
156
+ thread and the composer to the whole chat panel for wide tables and code; a person's bubbles keep their size. Each reply has **Copy this reply**. Markdown: headings, lists, quotes, bold/italic/
156
157
  strike, links, and tables (header row plus separator, alignment, up to 20 columns). Discord mentions show as names;
157
158
  a Discord server emoji (`<:name:id>`) of the fleet's servers shows as its picture, and any other (or one whose picture
158
159
  can't load) as `:name:`. Unicode emoji are drawn by the system's colour emoji font under every theme font (boxes on
@@ -116,7 +116,7 @@ register("chat", {
116
116
  file_empty: "empty file", file_too_large: "over {0} MB", file_too_large_public: "over {0} MB — the most the public link carries in one upload", file_too_many: "at most {0} files per message", file_too_much: "the files together would be over {0} MB", removeFile: "Remove", putBack: "Put back",
117
117
  noMessages: "Type a task below.",
118
118
  pick: "Choose an agent to start chatting.", noAgents: "Create an agent to start chatting.", newAgent: "New agent", notFound: "No instance called “{0}”", notFoundHint: "It may have been deleted or renamed.", backToList: "Choose an agent",
119
- details: "Instance details", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
119
+ details: "Instance details", fullWidth: "Full width", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
120
120
  started: "{0} started", stoppedInst: "{0} stopped", restarted: "{0} restarted", instanceDeleted: "{0} was deleted",
121
121
  deleteTitle: "Delete {0}?", deleteHelp: "This removes the instance from the fleet and stops it. Its working directory is kept. Type “delete {0}” to confirm.", deleteGo: "Delete", cancel: "Cancel",
122
122
  loadFailed: "Failed to load", tour: "Chat tour", tourBtnTitle: "Show the chat tour on an agent's chat", tourNoAgent: "Choose an agent before opening the chat tour.",
@@ -255,7 +255,7 @@ register("chat", {
255
255
  file_empty: "空檔案", file_too_large: "超過 {0} MB", file_too_large_public: "超過 {0} MB,這是公開連結單次上傳的上限", file_too_many: "每則訊息最多 {0} 個檔案", file_too_much: "檔案合計會超過 {0} MB", removeFile: "移除", putBack: "放回輸入框",
256
256
  noMessages: "直接在下方輸入任務。",
257
257
  pick: "選一個 Agent 開始聊天。", noAgents: "先建立一個 Agent,再開始聊天。", newAgent: "新增 Agent", notFound: "沒有叫「{0}」的 instance", notFoundHint: "它可能已被刪除或改名。", backToList: "選擇 Agent",
258
- details: "Instance 詳細資料", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
258
+ details: "Instance 詳細資料", fullWidth: "全寬", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
259
259
  started: "{0} 已啟動", stoppedInst: "{0} 已停止", restarted: "{0} 已重新啟動", instanceDeleted: "{0} 已刪除",
260
260
  deleteTitle: "要刪除 {0} 嗎?", deleteHelp: "這會把 instance 從 fleet 移除並停止它,工作目錄會保留。請輸入「delete {0}」確認。", deleteGo: "刪除", cancel: "取消",
261
261
  loadFailed: "載入失敗", tour: "聊天導覽", tourBtnTitle: "在 Agent 的聊天上開啟聊天導覽", tourNoAgent: "請先選一個 Agent,再開啟聊天導覽。",
@@ -44,6 +44,11 @@ const writeKey = (k, v) => { try { if (v) localStorage.setItem(k, v); else local
44
44
  export const chatCardPrefs = createStore({ open: readKey(CHAT_CARD_KEY, "open"), rowHidden: readKey(CHAT_CARD_ROW_KEY, "hidden") });
45
45
  export const setChatCardOpen = (open) => { writeKey(CHAT_CARD_KEY, open ? "open" : null); chatCardPrefs.set((s) => ({ ...s, open })); };
46
46
  export const setChatCardRowHidden = (rowHidden) => { writeKey(CHAT_CARD_ROW_KEY, rowHidden ? "hidden" : null); chatCardPrefs.set((s) => ({ ...s, rowHidden })); };
47
+ // Full width (the header's ⋯ menu): this browser's choice, like code wrap. The thread and the composer take the panel's
48
+ // width; a person's bubble keeps its cap (app.css). A phone's column is the screen already: no item there.
49
+ const CHAT_WIDE_KEY = "agend_chat_full_width";
50
+ export const chatWide = createStore({ on: readKey(CHAT_WIDE_KEY, "on") });
51
+ export const setChatWide = (on) => { writeKey(CHAT_WIDE_KEY, on ? "on" : null); chatWide.set({ on }); };
47
52
  function ChatCard({ name, card }) {
48
53
  const prefs = useStore(chatCardPrefs);
49
54
  if (!card || !prefs.open) return null;
@@ -260,6 +265,7 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
260
265
  const publishCanvasPlayback = useCallback(value => setCanvasPlayback(previous =>
261
266
  !previous && !value.playing || previous?.name === value.name && previous.playing === value.playing ? previous : value), []);
262
267
  const [wrap, setWrap] = useState(codeWrap);
268
+ const wide = useStore(chatWide).on;
263
269
  const view = useRef(null), split = useRef(null), thread = useRef(null), bg = useRef(null), effectSurface = useRef(null);
264
270
  useLayoutEffect(() => { applyTheme(view.current, theme, mode); }, [themeKey, mode]);
265
271
  useLayoutEffect(() => {
@@ -321,6 +327,7 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
321
327
  ...themeMenuItems(name),
322
328
  // #2117: its ambient — why it is not showing here (with the way to change that), and a person's Clear.
323
329
  ...ambientMenuItems(name, { publicLink: document.body.dataset.publicLink === "1", clear: clearAmbient }),
330
+ narrowScreen() ? null : { key: "fullWidth", checkbox: true, checked: wide, label: t("chat.fullWidth"), onSelect: () => setChatWide(!wide) },
324
331
  cwEnabled && canvasWorkerMenuAvailable(window) ? { key: "effects", label: t("cwControls.title"), onSelect: () => setDialog("effects") } : null,
325
332
  { key: "delete", label: t("chat.delete"), icon: "trash", danger: true, onSelect: () => setDialog("delete") },
326
333
  ];
@@ -333,7 +340,7 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
333
340
  const sub = html`<span class="status"><span class=${`dot ${cls}`} aria-hidden="true"></span>${statusLabel(inst, exec, awaiting)}</span>
334
341
  ${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", modelText(inst))} aria-label=${t("chat.chipModel", modelText(inst))} onClick=${() => pick("model")}>${modelText(inst)}</button>` : null}
335
342
  ${inst.effort ? html`<button type="button" class="hd-chip" title=${t("chat.chipEffort", inst.effort)} aria-label=${t("chat.chipEffort", inst.effort)} onClick=${() => pick("effort")}>${inst.effort}</button>` : null}`;
336
- return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} ref=${view}>
343
+ return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} data-width=${wide ? "full" : undefined} ref=${view}>
337
344
  <${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ? null : html`<${InstanceSwitch} name=${name} current="chat" />`} notice=${html`<${ChatEffects} name=${name} lease=${lease} element=${effectSurface} store=${store} open=${dialog === "effects"} onPlayback=${publishCanvasPlayback} onClose=${() => setDialog(null)} active=${fxHere} />`}>${fxHere ? html`<${ChatEffectPosters} name=${name} lease=${lease} element=${effectSurface} />` : null}<${CardMenu} card=${card} /><${HeaderTools} navKey=${navKey} />${guest ? null : html`<${Menu} items=${items} label=${t("app.more")} />`}</${PanelHeader}>
338
345
  <${ChatCard} key=${name} name=${name} card=${card} />
339
346
  <${AgentWidget} slot="chat_header" widget=${widgetFor(name, "chat_header")} binds=${instanceBinds(inst, exec, awaiting)} />
@@ -573,6 +573,9 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
573
573
  the message's overflow-wrap: anywhere that is one character wide (the #1672 status line's time, one glyph per line). */
574
574
  .msg .time { font-family: var(--font-mono); white-space: nowrap; flex: none; }
575
575
  .msg.user { align-self: flex-end; max-width: min(80%, 620px); }
576
+ /* Full width (the header's ⋯ menu, this browser's choice): the thread and the composer take the panel, less their own
577
+ padding, for wide tables and code; a person's bubble keeps its cap (above). A phone's column is the screen already. */
578
+ @media (min-width: 900px) { .p-chat[data-width="full"] .thread, .p-chat[data-width="full"] .dock-col { max-width: none; } }
576
579
  .msg.user .meta { justify-content: flex-end; }
577
580
  /* #1773: the bubble hugs its text. The message is as wide as its widest child — usually the meta row (sender, a time
578
581
  such as 「昨天 下午03:58」, ticks) — and a block body would stretch to it; fit-content keeps a short message short, on
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@songsid/agend",
3
- "version": "2.3.2-beta.2",
3
+ "version": "2.3.2-beta.3",
4
4
  "description": "Multi-agent fleet daemon — run coding CLIs (Claude, Muse, Codex, OpenCode) from Discord or Telegram",
5
5
  "type": "module",
6
6
  "bin": {