viveworker 0.5.3 → 0.6.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/web/app.css CHANGED
@@ -611,6 +611,7 @@ code {
611
611
 
612
612
  .settings-compose-badge {
613
613
  display: inline-flex;
614
+ align-items: center;
614
615
  align-self: flex-start;
615
616
  flex: 0 0 auto;
616
617
  padding: 0.12rem 0.42rem;
@@ -622,6 +623,16 @@ code {
622
623
  white-space: nowrap;
623
624
  }
624
625
 
626
+ .settings-compose-badge svg {
627
+ width: 0.85rem;
628
+ height: 0.85rem;
629
+ display: block;
630
+ }
631
+
632
+ .settings-compose-badge:has(> svg) {
633
+ padding: 0.18rem 0.3rem;
634
+ }
635
+
625
636
  .settings-compose-badge--post {
626
637
  background: rgba(67, 160, 255, 0.18);
627
638
  color: rgba(121, 196, 255, 0.92);
@@ -2182,6 +2193,95 @@ code {
2182
2193
  padding-inline: 0.1rem;
2183
2194
  }
2184
2195
 
2196
+ .ambient-suggestions {
2197
+ display: grid;
2198
+ gap: 0.95rem;
2199
+ }
2200
+
2201
+ .ambient-suggestions__intro > :first-child {
2202
+ margin-top: 0;
2203
+ }
2204
+
2205
+ .ambient-suggestions__intro > :last-child {
2206
+ margin-bottom: 0;
2207
+ }
2208
+
2209
+ .ambient-suggestions__list {
2210
+ display: grid;
2211
+ gap: 0.8rem;
2212
+ }
2213
+
2214
+ .ambient-suggestion-card {
2215
+ display: grid;
2216
+ gap: 0.72rem;
2217
+ padding: 0.95rem 1rem;
2218
+ border-radius: 18px;
2219
+ background: rgba(10, 17, 22, 0.54);
2220
+ border: 1px solid rgba(156, 181, 197, 0.12);
2221
+ }
2222
+
2223
+ .ambient-suggestion-card__header {
2224
+ display: grid;
2225
+ grid-template-columns: minmax(0, 1fr) auto;
2226
+ gap: 0.7rem;
2227
+ align-items: start;
2228
+ }
2229
+
2230
+ .ambient-suggestion-card__title,
2231
+ .ambient-suggestion-card__description,
2232
+ .ambient-suggestion-card__prompt-label,
2233
+ .ambient-suggestion-card__prompt {
2234
+ margin: 0;
2235
+ }
2236
+
2237
+ .ambient-suggestion-card__title {
2238
+ font-size: 1rem;
2239
+ line-height: 1.4;
2240
+ color: var(--text);
2241
+ overflow-wrap: anywhere;
2242
+ }
2243
+
2244
+ .ambient-suggestion-card__description {
2245
+ color: var(--muted);
2246
+ line-height: 1.55;
2247
+ overflow-wrap: anywhere;
2248
+ }
2249
+
2250
+ .ambient-suggestion-card__copy-button {
2251
+ min-height: 2.35rem;
2252
+ padding: 0.65rem 0.9rem;
2253
+ border-radius: 14px;
2254
+ font-size: 0.84rem;
2255
+ white-space: nowrap;
2256
+ flex: 0 0 auto;
2257
+ }
2258
+
2259
+ .ambient-suggestion-card__prompt-wrap {
2260
+ display: grid;
2261
+ gap: 0.42rem;
2262
+ }
2263
+
2264
+ .ambient-suggestion-card__prompt-label {
2265
+ color: var(--muted);
2266
+ font-size: 0.78rem;
2267
+ font-weight: 700;
2268
+ letter-spacing: 0.01em;
2269
+ }
2270
+
2271
+ .ambient-suggestion-card__prompt {
2272
+ padding: 0.88rem 0.92rem;
2273
+ border-radius: 15px;
2274
+ background: rgba(255, 255, 255, 0.04);
2275
+ border: 1px solid rgba(156, 181, 197, 0.12);
2276
+ color: #eef8ff;
2277
+ font-size: 0.9rem;
2278
+ line-height: 1.55;
2279
+ white-space: pre-wrap;
2280
+ word-break: break-word;
2281
+ overflow-wrap: anywhere;
2282
+ font-family: "SFMono-Regular", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
2283
+ }
2284
+
2185
2285
  .detail-diff-thread__header {
2186
2286
  width: 100%;
2187
2287
  display: grid;
package/web/app.js CHANGED
@@ -55,6 +55,7 @@ const state = {
55
55
  a2aTaskExecutorPick: "codex",
56
56
  pushNotice: "",
57
57
  pushError: "",
58
+ ambientSuggestionCopyState: null,
58
59
  deviceNotice: "",
59
60
  deviceError: "",
60
61
  imageViewer: null,
@@ -676,6 +677,7 @@ function timelineKindFilterOptions() {
676
677
  const codexClaudeOptions = [
677
678
  allOption,
678
679
  { id: "messages", label: L("timeline.kindFilter.messages"), icon: "timeline" },
680
+ { id: "suggestions", label: L("timeline.kindFilter.suggestions"), icon: "suggestions" },
679
681
  { id: "files", label: L("timeline.kindFilter.files"), icon: "file-event" },
680
682
  { id: "approvals", label: L("timeline.kindFilter.approvals"), icon: "approval" },
681
683
  { id: "plans", label: L("timeline.kindFilter.plans"), icon: "plan" },
@@ -710,6 +712,8 @@ function timelineEntryMatchesKindFilter(entry, filterId) {
710
712
  switch (filterId) {
711
713
  case "messages":
712
714
  return TIMELINE_MESSAGE_KINDS.has(kind);
715
+ case "suggestions":
716
+ return kind === "ambient_suggestions";
713
717
  case "files":
714
718
  return kind === "file_event";
715
719
  case "approvals":
@@ -2347,6 +2351,10 @@ function timelineEntryThreadLabel(item, isMessage) {
2347
2351
  }
2348
2352
 
2349
2353
  function timelineEntryPrimaryText(item, status, { isMessageLike = false, isFileEvent = false } = {}) {
2354
+ if (item?.kind === "ambient_suggestions") {
2355
+ return item.summary || fallbackSummaryForKind(item.kind, status, item.provider);
2356
+ }
2357
+
2350
2358
  if (isMessageLike) {
2351
2359
  return item.summary || fallbackSummaryForKind(item.kind, status, item.provider);
2352
2360
  }
@@ -2359,6 +2367,10 @@ function timelineEntryPrimaryText(item, status, { isMessageLike = false, isFileE
2359
2367
  }
2360
2368
 
2361
2369
  function timelineEntrySecondaryText(item, status, primaryText, { isMessageLike = false, isFileEvent = false } = {}) {
2370
+ if (item?.kind === "ambient_suggestions") {
2371
+ return "";
2372
+ }
2373
+
2362
2374
  if (isMessageLike) {
2363
2375
  return "";
2364
2376
  }
@@ -3607,8 +3619,9 @@ function renderSettingsA2aSharePage(context) {
3607
3619
  const visible = items.slice(0, visibleCount);
3608
3620
  const hasMore = items.length > visibleCount;
3609
3621
  const filesList = visible.map((item) => {
3622
+ const passwordLabel = L("settings.a2aShare.passwordProtected");
3610
3623
  const lock = item.hasPassword
3611
- ? `<span class="settings-compose-badge settings-compose-badge--reply" title="${escapeHtml(L("settings.a2aShare.passwordProtected"))}">🔒</span>`
3624
+ ? `<span class="settings-compose-badge settings-compose-badge--reply" role="img" title="${escapeHtml(passwordLabel)}" aria-label="${escapeHtml(passwordLabel)}">${renderIcon("lock")}</span>`
3612
3625
  : "";
3613
3626
  const label = escapeHtml(item.originalName || item.slug);
3614
3627
  const link = item.url
@@ -3825,8 +3838,9 @@ function renderStandardDetailDesktop(detail) {
3825
3838
  ${renderMoltbookDraftComposer(detail)}
3826
3839
  ${renderA2ATaskDetail(detail)}
3827
3840
  ${renderThreadShareDetail(detail)}
3841
+ ${renderAmbientSuggestionsSection(detail)}
3828
3842
  ${
3829
- detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task" || detail.kind === "a2a_task_result" || detail.kind === "thread_share"
3843
+ detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task" || detail.kind === "a2a_task_result" || detail.kind === "thread_share" || detail.kind === "ambient_suggestions"
3830
3844
  ? ""
3831
3845
  : plainIntro
3832
3846
  ? plainIntro
@@ -3863,8 +3877,9 @@ function renderStandardDetailMobile(detail) {
3863
3877
  ${renderMoltbookDraftComposer(detail, { mobile: true })}
3864
3878
  ${renderA2ATaskDetail(detail, { mobile: true })}
3865
3879
  ${renderThreadShareDetail(detail, { mobile: true })}
3880
+ ${renderAmbientSuggestionsSection(detail, { mobile: true })}
3866
3881
  ${
3867
- detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task" || detail.kind === "a2a_task_result" || detail.kind === "thread_share"
3882
+ detail.kind === "moltbook_draft" || detail.kind === "moltbook_reply" || detail.kind === "a2a_task" || detail.kind === "a2a_task_result" || detail.kind === "thread_share" || detail.kind === "ambient_suggestions"
3868
3883
  ? ""
3869
3884
  : plainIntro
3870
3885
  ? plainIntro
@@ -3889,6 +3904,65 @@ function renderStandardDetailMobile(detail) {
3889
3904
  `;
3890
3905
  }
3891
3906
 
3907
+ function renderAmbientSuggestionsSection(detail, options = {}) {
3908
+ if (detail?.kind !== "ambient_suggestions") {
3909
+ return "";
3910
+ }
3911
+
3912
+ const suggestions = normalizeClientAmbientSuggestions(detail?.suggestions);
3913
+ if (suggestions.length === 0) {
3914
+ return `
3915
+ <section class="detail-card detail-card--body ${options.mobile ? "detail-card--mobile" : ""}">
3916
+ <div class="detail-body markdown">${detail.messageHtml || ""}</div>
3917
+ </section>
3918
+ `;
3919
+ }
3920
+
3921
+ return `
3922
+ <section class="detail-card detail-card--body ${options.mobile ? "detail-card--mobile" : ""}">
3923
+ <div class="ambient-suggestions">
3924
+ ${detail.messageHtml ? `<div class="ambient-suggestions__intro markdown">${detail.messageHtml}</div>` : ""}
3925
+ <div class="ambient-suggestions__list">
3926
+ ${suggestions
3927
+ .map((suggestion, index) => renderAmbientSuggestionCard(detail, suggestion, index))
3928
+ .join("")}
3929
+ </div>
3930
+ </div>
3931
+ </section>
3932
+ `;
3933
+ }
3934
+
3935
+ function renderAmbientSuggestionCard(detail, suggestion, index) {
3936
+ const copyKey = ambientSuggestionCopyKey(detail?.token || "", suggestion?.id || "", index);
3937
+ const copyStatus = state.ambientSuggestionCopyState?.key === copyKey
3938
+ ? state.ambientSuggestionCopyState?.status || ""
3939
+ : "";
3940
+ const copyLabel = copyStatus === "success"
3941
+ ? L("detail.ambientSuggestions.copyPromptDone")
3942
+ : copyStatus === "error"
3943
+ ? L("detail.ambientSuggestions.copyPromptFailed")
3944
+ : L("detail.ambientSuggestions.copyPrompt");
3945
+ return `
3946
+ <article class="ambient-suggestion-card">
3947
+ <div class="ambient-suggestion-card__header">
3948
+ <h3 class="ambient-suggestion-card__title">${escapeHtml(suggestion.title)}</h3>
3949
+ <button
3950
+ type="button"
3951
+ class="secondary ambient-suggestion-card__copy-button"
3952
+ data-copy-ambient-suggestion
3953
+ data-copy-ambient-suggestion-token="${escapeHtml(detail?.token || "")}"
3954
+ data-copy-ambient-suggestion-index="${escapeHtml(String(index))}"
3955
+ >${escapeHtml(copyLabel)}</button>
3956
+ </div>
3957
+ ${suggestion.description ? `<p class="ambient-suggestion-card__description">${escapeHtml(suggestion.description)}</p>` : ""}
3958
+ <div class="ambient-suggestion-card__prompt-wrap">
3959
+ <p class="ambient-suggestion-card__prompt-label">${escapeHtml(L("detail.ambientSuggestions.prompt"))}</p>
3960
+ <pre class="ambient-suggestion-card__prompt">${escapeHtml(suggestion.prompt)}</pre>
3961
+ </div>
3962
+ </article>
3963
+ `;
3964
+ }
3965
+
3892
3966
  function renderDetailPlainIntro(detail, options = {}) {
3893
3967
  if (!["approval", "diff_thread", "file_event"].includes(detail?.kind || "")) {
3894
3968
  return "";
@@ -5290,6 +5364,41 @@ function bindShellInteractions() {
5290
5364
  });
5291
5365
  }
5292
5366
 
5367
+ for (const button of document.querySelectorAll("[data-copy-ambient-suggestion]")) {
5368
+ button.addEventListener("click", async (event) => {
5369
+ event.preventDefault();
5370
+ event.stopPropagation();
5371
+ const detail = state.currentDetail;
5372
+ if (!detail || detail.kind !== "ambient_suggestions") {
5373
+ return;
5374
+ }
5375
+ if ((button.dataset.copyAmbientSuggestionToken || "") !== (detail.token || "")) {
5376
+ return;
5377
+ }
5378
+ const index = Math.max(0, Number(button.dataset.copyAmbientSuggestionIndex) || 0);
5379
+ const suggestions = normalizeClientAmbientSuggestions(detail.suggestions);
5380
+ const suggestion = suggestions[index];
5381
+ if (!suggestion?.prompt) {
5382
+ return;
5383
+ }
5384
+ const copyKey = ambientSuggestionCopyKey(detail.token || "", suggestion.id || "", index);
5385
+ try {
5386
+ await copyTextToClipboard(suggestion.prompt);
5387
+ state.ambientSuggestionCopyState = { key: copyKey, status: "success" };
5388
+ } catch {
5389
+ state.ambientSuggestionCopyState = { key: copyKey, status: "error" };
5390
+ }
5391
+ await renderShell();
5392
+ window.setTimeout(async () => {
5393
+ if (state.ambientSuggestionCopyState?.key !== copyKey) {
5394
+ return;
5395
+ }
5396
+ state.ambientSuggestionCopyState = null;
5397
+ await renderShell();
5398
+ }, 1600);
5399
+ });
5400
+ }
5401
+
5293
5402
  for (const button of document.querySelectorAll("[data-diff-thread-file-toggle]")) {
5294
5403
  button.addEventListener("click", async (event) => {
5295
5404
  event.preventDefault();
@@ -6314,7 +6423,7 @@ function tabForItemKind(kind, fallback) {
6314
6423
  if (kind === "diff_thread") {
6315
6424
  return "diff";
6316
6425
  }
6317
- if (kind === "file_event") {
6426
+ if (kind === "file_event" || kind === "ambient_suggestions") {
6318
6427
  return "timeline";
6319
6428
  }
6320
6429
  if (TIMELINE_MESSAGE_KINDS.has(kind)) {
@@ -6346,6 +6455,8 @@ function kindMeta(kind, item) {
6346
6455
  return { label: L("common.assistantCommentary"), tone: "plan", icon: "assistant-commentary" };
6347
6456
  case "assistant_final":
6348
6457
  return { label: L("common.assistantFinal"), tone: "completion", icon: "assistant-final" };
6458
+ case "ambient_suggestions":
6459
+ return { label: L("common.ambientSuggestions"), tone: "neutral", icon: "suggestions" };
6349
6460
  case "approval":
6350
6461
  return { label: L("common.approval"), tone: "approval", icon: "approval" };
6351
6462
  case "plan":
@@ -6391,6 +6502,9 @@ function itemIntentText(kind, status = "pending", provider) {
6391
6502
  if (kind === "file_event") {
6392
6503
  return L("intent.fileEvent");
6393
6504
  }
6505
+ if (kind === "ambient_suggestions") {
6506
+ return L("intent.ambientSuggestions");
6507
+ }
6394
6508
  if (kind === "user_message") {
6395
6509
  return L("intent.userMessage");
6396
6510
  }
@@ -6425,6 +6539,9 @@ function detailIntentText(detail) {
6425
6539
  if (detail.kind === "file_event") {
6426
6540
  return itemIntentText(detail.kind, "timeline", provider);
6427
6541
  }
6542
+ if (detail.kind === "ambient_suggestions") {
6543
+ return itemIntentText(detail.kind, "timeline", provider);
6544
+ }
6428
6545
  if (TIMELINE_MESSAGE_KINDS.has(detail.kind)) {
6429
6546
  return itemIntentText(detail.kind, "timeline", provider);
6430
6547
  }
@@ -6443,7 +6560,11 @@ function renderDetailTitle(detail) {
6443
6560
  }
6444
6561
 
6445
6562
  function detailDisplayTitle(detail) {
6446
- const threadLabel = normalizeClientText(detail?.threadLabel || "");
6563
+ const threadLabel = sanitizeThreadLabelForDisplay(detail?.threadLabel || "", detail?.threadId || "");
6564
+ if (detail?.kind === "ambient_suggestions") {
6565
+ const ambientTitle = sanitizeThreadLabelForDisplay(detail?.title || "", detail?.threadId || "");
6566
+ return ambientTitle || L("common.ambientSuggestions");
6567
+ }
6447
6568
  if (threadLabel) {
6448
6569
  return threadLabel;
6449
6570
  }
@@ -6491,6 +6612,8 @@ function fallbackSummaryForKind(kind, status, provider) {
6491
6612
  return L("summary.diffThread");
6492
6613
  case "file_event":
6493
6614
  return L("summary.fileEvent", vars);
6615
+ case "ambient_suggestions":
6616
+ return L("summary.ambientSuggestions", { count: 0, firstTitle: "", more: 0 });
6494
6617
  case "user_message":
6495
6618
  return L("summary.userMessage");
6496
6619
  case "assistant_commentary":
@@ -6707,6 +6830,8 @@ function renderIcon(name) {
6707
6830
  return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.85" stroke-linecap="round" stroke-linejoin="round"><path d="M12 6.2v5.6"/><path d="M9.2 9h5.6"/><path d="M6 14.8a6.7 6.7 0 0 0 12 0"/><path d="M8 4.8a7.6 7.6 0 0 1 8 0"/></svg>`;
6708
6831
  case "assistant-final":
6709
6832
  return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.85" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6.5h12a1.8 1.8 0 0 1 1.8 1.8v6.1A1.8 1.8 0 0 1 18 16.2h-5.3L9 19.4v-3.2H6a1.8 1.8 0 0 1-1.8-1.8V8.3A1.8 1.8 0 0 1 6 6.5Z"/><path d="m9.2 11.3 1.7 1.7 3.6-3.8"/></svg>`;
6833
+ case "suggestions":
6834
+ return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.85" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3.8 13 6.7l3 .5-2.2 2.2.5 3-2.3-1.2-2.3 1.2.5-3L8 7.2l3-.5Z"/><path d="M6.2 14.6h11.6"/><path d="M8.4 18.2h7.2"/></svg>`;
6710
6835
  case "completed":
6711
6836
  return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="m8.7 12.2 2.1 2.1 4.6-4.8"/></svg>`;
6712
6837
  case "settings":
@@ -6733,6 +6858,8 @@ function renderIcon(name) {
6733
6858
  return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M5 7h14"/><path d="M8 12h8"/><path d="M10.5 17h3"/></svg>`;
6734
6859
  case "check":
6735
6860
  return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6.8 12.5 3.2 3.2 7.2-7.4"/></svg>`;
6861
+ case "lock":
6862
+ return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><rect x="5.5" y="10.5" width="13" height="9" rx="2"/><path d="M8 10.5V7.5a4 4 0 0 1 8 0v3"/></svg>`;
6736
6863
  case "back":
6737
6864
  return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>`;
6738
6865
  case "chevron-down":
@@ -6979,6 +7106,58 @@ function normalizeClientFileRefs(fileRefs) {
6979
7106
  return deduped;
6980
7107
  }
6981
7108
 
7109
+ function normalizeClientAmbientSuggestions(value) {
7110
+ if (!Array.isArray(value)) {
7111
+ return [];
7112
+ }
7113
+ return value
7114
+ .map((entry) => {
7115
+ const title = normalizeClientText(entry?.title);
7116
+ const prompt = normalizeClientText(entry?.prompt);
7117
+ if (!title || !prompt) {
7118
+ return null;
7119
+ }
7120
+ return {
7121
+ id: normalizeClientText(entry?.id),
7122
+ title,
7123
+ prompt,
7124
+ description: normalizeClientText(entry?.description),
7125
+ };
7126
+ })
7127
+ .filter(Boolean);
7128
+ }
7129
+
7130
+ function ambientSuggestionCopyKey(token, suggestionId, index) {
7131
+ return [normalizeClientText(token), normalizeClientText(suggestionId), String(Math.max(0, Number(index) || 0))].join(":");
7132
+ }
7133
+
7134
+ async function copyTextToClipboard(text) {
7135
+ const normalized = String(text ?? "");
7136
+ if (!normalized) {
7137
+ throw new Error("empty");
7138
+ }
7139
+
7140
+ if (navigator.clipboard?.writeText) {
7141
+ await navigator.clipboard.writeText(normalized);
7142
+ return;
7143
+ }
7144
+
7145
+ const textarea = document.createElement("textarea");
7146
+ textarea.value = normalized;
7147
+ textarea.setAttribute("readonly", "true");
7148
+ textarea.style.position = "fixed";
7149
+ textarea.style.opacity = "0";
7150
+ textarea.style.pointerEvents = "none";
7151
+ document.body.appendChild(textarea);
7152
+ textarea.select();
7153
+ textarea.setSelectionRange(0, textarea.value.length);
7154
+ const copied = document.execCommand("copy");
7155
+ document.body.removeChild(textarea);
7156
+ if (!copied) {
7157
+ throw new Error("copy-failed");
7158
+ }
7159
+ }
7160
+
6982
7161
  function fileRefLabel(fileRef) {
6983
7162
  const normalized = normalizeClientText(fileRef);
6984
7163
  if (!normalized) {