@oh-story/dsh 0.1.1 → 0.1.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.
package/lib/client.js CHANGED
@@ -24,10 +24,21 @@ __export(index_exports, {
24
24
  name: () => name
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
- var import_react = require("react"), import_react_dom = require("react-dom");
27
+ var import_client = require("@deepseek-ai/dsh-client-runtime/client"), import_react = require("react"), import_react_dom = require("react-dom");
28
28
 
29
29
  // src/client/file-activity.ts
30
30
  var STORY_DIRECTORIES = /* @__PURE__ */ new Set(["\u6B63\u6587", "\u5927\u7EB2", "\u8BBE\u5B9A", "\u8FFD\u8E2A", "\u5BF9\u6807", "\u53C2\u8003\u8D44\u6599"]), DRAMA_DIRECTORIES = /* @__PURE__ */ new Set(["\u8F93\u5165", "\u9879\u76EE\u5F00\u53D1", "\u8BBE\u5B9A\u96C6", "\u5267\u96C6", "\u4EA4\u4ED8", "\u521B\u4F5C\u8005\u51B3\u7B56", "\u5BA1\u67E5"]), EDITABLE_EXTENSION = /\.(?:md|txt|json|jsonl)$/iu, MUTATING_CALLS = /* @__PURE__ */ new Set(["write", "edit", "str_replace_editor", "bash", "run_code", "oh_story_role"]);
31
+ function streamingAssistant(timeline) {
32
+ for (let turnNumber of timeline.turnOrder.toReversed()) {
33
+ let turn = timeline.turns.get(turnNumber);
34
+ if (turn !== void 0)
35
+ for (let step of turn.steps.toReversed()) {
36
+ let assistant = step.data.get("assistant-step");
37
+ if (assistant?.status === "running") return assistant;
38
+ }
39
+ }
40
+ return null;
41
+ }
31
42
  function decodeEscape(character) {
32
43
  switch (character) {
33
44
  case '"':
@@ -167,13 +178,14 @@ function visitRunning(blocks, visit) {
167
178
  for (let block of blocks)
168
179
  "kind" in block || visit(block), visitRunning(block.subCalls, visit);
169
180
  }
170
- function fileMutations(runningCalls, projectedCalls = []) {
181
+ function fileMutations(runningCalls, partial = null) {
171
182
  let values = [];
172
183
  visitRunning(runningCalls, (call) => {
173
184
  values.push(...mutationsFromRunning(call));
174
185
  });
175
- for (let call of projectedCalls) {
176
- let value = mutationFromArgs(call.name, call.callId, call.argsRaw, "streaming");
186
+ for (let block of partial?.blocks ?? []) {
187
+ if (block.kind !== "tool-call") continue;
188
+ let value = mutationFromArgs(block.name, block.callId, block.argsRaw, "streaming");
177
189
  value !== void 0 && !values.some((candidate) => candidate.callId === value.callId) && values.push(value);
178
190
  }
179
191
  return values;
@@ -184,6 +196,26 @@ function mutatingCallIds(runningCalls) {
184
196
  MUTATING_CALLS.has(call.name) && ids.add(call.callId);
185
197
  }), ids;
186
198
  }
199
+ function settledMutationSignals(block) {
200
+ let nested = block.subCalls.flatMap(settledMutationSignals);
201
+ if (!("kind" in block) || block.isError) return nested;
202
+ let diffs = block.resultView?.card === "diff" ? block.resultView.diffs : block.callView?.card === "diff" ? block.callView.diffs : void 0;
203
+ if (diffs !== void 0) return [
204
+ ...diffs.map((diff, index) => `${block.callId}:${String(index)}\0${diff.path}`),
205
+ ...nested
206
+ ];
207
+ if (block.call === null) return nested;
208
+ let mutation = mutationFromArgs(block.call.name, block.callId, block.call.argsRaw, "running");
209
+ return mutation?.path === void 0 ? nested : [`${block.callId}\0${mutation.path}`, ...nested];
210
+ }
211
+ function latestSettledMutation(chat) {
212
+ for (let key of chat.order.toReversed()) {
213
+ let node = chat.nodes.get(key);
214
+ if (node?.kind !== "tool-call") continue;
215
+ let root = node.data.root, signal = root === void 0 ? void 0 : settledMutationSignals(root).at(-1);
216
+ if (signal !== void 0) return signal;
217
+ }
218
+ }
187
219
  function creativeRelativePath(path, cwd) {
188
220
  if (path === void 0 || path === "") return;
189
221
  let normalized = path.replaceAll("\\", "/"), root = cwd?.replaceAll("\\", "/").replace(/\/$/u, ""), insideRoot = root !== void 0 && normalized.startsWith(`${root}/`);
@@ -494,24 +526,26 @@ function MarkdownPreview({ content, label }) {
494
526
 
495
527
  // inline-css:/home/runner/work/oh-story-dsh/oh-story-dsh/packages/dsh-plugin/src/client/plugin.css
496
528
  var plugin_default = `.oh-story-bridge-marker { display: none; }
497
- .oh-story-split-surface { display: contents; }
529
+ .oh-story-split-surface { display: contents; font-family: var(--dsw-font-family); }
498
530
 
499
531
  /* DSH documents every [data-slot] wrapper as a stable addressable styling
500
532
  seam. The official Session body and composer stay mounted; this only turns
501
533
  their scroll host into a three-column writing surface. */
502
534
  [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
503
535
  --os-border: var(--dsw-alias-border-l2);
504
- --os-muted: var(--dsw-alias-label-tertiary);
536
+ --os-muted: var(--dsw-alias-label-secondary);
505
537
  display: grid;
506
- grid-template-columns: clamp(96px, 16%, 190px) minmax(180px, 1fr) minmax(252px, 38%);
538
+ grid-template-columns: clamp(184px, 16%, 200px) minmax(240px, 1fr) clamp(408px, 40%, 520px);
507
539
  grid-template-rows: minmax(0, 1fr);
508
540
  min-height: 0;
509
541
  overflow-x: hidden;
510
542
  overflow-y: auto;
511
543
  position: relative;
512
544
  scrollbar-gutter: stable;
545
+ scroll-padding-bottom: calc(var(--dsh-composer-height, 152px) + 16px);
513
546
  }
514
547
 
548
+ /* Keep the final Chat flow above the overlapping official Composer. */
515
549
  [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
516
550
  > [data-slot="conversation.session"] > :not(.oh-story-split-surface) {
517
551
  grid-column: 3;
@@ -521,6 +555,11 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
521
555
  border-left: 1px solid var(--os-border);
522
556
  }
523
557
 
558
+ [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
559
+ [data-chat-flow] {
560
+ padding-bottom: calc(var(--dsh-composer-height, 152px) + 16px);
561
+ }
562
+
524
563
  [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface)
525
564
  > [data-composer-seat] {
526
565
  grid-column: 3;
@@ -571,7 +610,10 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
571
610
  gap: 12px;
572
611
  }
573
612
 
574
- .oh-story-brand { min-height: 28px; padding: 0 5px 10px 7px; font-size: 14px; font-weight: 500; }
613
+ .oh-story-brand { min-height: 28px; gap: 8px; padding: 0 5px 8px 7px; font-size: 14px; font-weight: 500; white-space: nowrap; }
614
+ .oh-story-brand-cluster { display: flex; min-width: 0; align-items: center; gap: 6px; }
615
+ .oh-story-brand-cluster strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: inherit; }
616
+ .oh-story-kind { flex: none; padding: 1px 6px; border-radius: 5px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-button-ghost-active-fill); font-size: 11px; line-height: 16px; font-weight: 500; }
575
617
 
576
618
  .oh-story-brand button,
577
619
  .oh-story-save,
@@ -597,39 +639,26 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
597
639
  .oh-story-role button:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: -2px; }
598
640
  .oh-story-save:disabled { opacity: .48; cursor: default; }
599
641
 
600
- .oh-story-mode-tabs {
601
- display: grid;
602
- grid-template-columns: 1fr 1fr;
603
- margin: 0 4px 10px;
604
- border-bottom: 1px solid var(--os-border);
605
- }
642
+ .oh-story-mode-tabs { display: flex; margin: 0 4px 8px; padding: 2px; border: 1px solid var(--os-border); border-radius: 7px; background: var(--dsw-alias-bg-layer-1); }
606
643
 
607
644
  .oh-story-mode-tabs button,
608
645
  .oh-story-editor-tabs button {
609
646
  position: relative;
610
- min-height: 30px;
647
+ min-height: 24px;
648
+ flex: 1;
611
649
  border: 0;
612
650
  color: var(--os-muted);
613
651
  background: transparent;
652
+ border-radius: 5px;
614
653
  cursor: pointer;
615
654
  font: inherit;
616
- font-size: 12px;
655
+ font-size: 11px;
617
656
  }
618
657
 
619
658
  .oh-story-mode-tabs button:hover,
620
659
  .oh-story-editor-tabs button:hover { color: var(--dsw-alias-label-primary); }
621
660
  .oh-story-mode-tabs button[aria-selected="true"],
622
- .oh-story-editor-tabs button[aria-selected="true"] { color: var(--dsw-alias-label-primary); font-weight: 500; }
623
- .oh-story-mode-tabs button[aria-selected="true"]::after {
624
- position: absolute;
625
- right: 14px;
626
- bottom: -1px;
627
- left: 14px;
628
- height: 2px;
629
- border-radius: 2px 2px 0 0;
630
- background: var(--dsw-alias-state-business-primary);
631
- content: "";
632
- }
661
+ .oh-story-editor-tabs button[aria-selected="true"] { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-base); box-shadow: 0 1px 2px rgb(0 0 0 / 8%); font-weight: 500; }
633
662
 
634
663
  .oh-story-file-group,
635
664
  .oh-story-file-folder { margin: 0; }
@@ -647,6 +676,7 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
647
676
  font-size: 11px;
648
677
  font-weight: 600;
649
678
  }
679
+ .oh-story-file-folder > summary { padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 49px); }
650
680
  .oh-story-file-group > summary { margin-top: 3px; }
651
681
  .oh-story-file-group > summary::-webkit-details-marker,
652
682
  .oh-story-file-folder > summary::-webkit-details-marker { display: none; }
@@ -657,13 +687,13 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
657
687
  .oh-story-file-group > summary:hover,
658
688
  .oh-story-file-folder > summary:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-interactive-bg-hover); }
659
689
  .oh-story-file-group > summary span,
660
- .oh-story-file-folder > summary span { margin-left: auto; color: var(--dsw-alias-label-caption); font-weight: 400; }
690
+ .oh-story-file-folder > summary span { margin-left: auto; color: var(--dsw-alias-label-secondary); font-weight: 400; }
661
691
 
662
692
  .oh-story-tree nav button {
663
693
  display: block;
664
694
  width: 100%;
665
695
  overflow: hidden;
666
- padding: 6px 8px;
696
+ padding: 6px 8px 6px min(calc(14px + var(--oh-story-indent, 0px)), 56px);
667
697
  border: 0;
668
698
  border-radius: 6px;
669
699
  color: inherit;
@@ -686,13 +716,17 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
686
716
  border-bottom: 1px solid var(--os-border);
687
717
  }
688
718
 
689
- .oh-story-editor header span {
719
+ .oh-story-editor-path {
690
720
  overflow: hidden;
721
+ min-width: 0;
722
+ flex: 1;
691
723
  text-overflow: ellipsis;
692
724
  white-space: nowrap;
693
725
  font-size: 12px;
694
726
  color: var(--os-muted);
695
727
  }
728
+ .oh-story-editor-path > span { overflow: hidden; display: block; text-overflow: ellipsis; }
729
+ .oh-story-editor-path > strong { display: none; overflow: hidden; text-overflow: ellipsis; font: inherit; }
696
730
 
697
731
  .oh-story-editor-actions { display: flex; flex: none; align-items: center; gap: 8px; }
698
732
  .oh-story-editor-tabs {
@@ -703,7 +737,6 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
703
737
  background: var(--dsw-alias-bg-layer-1);
704
738
  }
705
739
  .oh-story-editor-tabs button { min-height: 24px; padding: 0 9px; border-radius: 5px; font-size: 11px; }
706
- .oh-story-editor-tabs button[aria-selected="true"] { background: var(--dsw-alias-bg-base); box-shadow: 0 1px 2px rgb(0 0 0 / 8%); }
707
740
  .oh-story-save { min-height: 28px; }
708
741
 
709
742
  .oh-story-editor textarea {
@@ -714,7 +747,7 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
714
747
  resize: none;
715
748
  border: 0;
716
749
  outline: 0;
717
- padding: clamp(20px, 4vw, 48px) clamp(22px, 6vw, 84px) 48px;
750
+ padding: clamp(20px, 6%, 48px) clamp(22px, 10%, 84px) 48px;
718
751
  color: var(--dsw-alias-label-primary);
719
752
  background: transparent;
720
753
  font: 16px/1.9 ui-serif, "Songti SC", "STSong", Georgia, serif;
@@ -730,7 +763,7 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
730
763
  min-height: 0;
731
764
  flex: 1;
732
765
  overflow: auto;
733
- padding: clamp(24px, 5vw, 56px) clamp(24px, 7vw, 92px) 64px;
766
+ padding: clamp(24px, 8%, 56px) clamp(24px, 10%, 92px) 64px;
734
767
  color: var(--dsw-alias-label-primary);
735
768
  font: 16px/1.85 ui-serif, "Songti SC", "STSong", Georgia, serif;
736
769
  overflow-wrap: anywhere;
@@ -742,7 +775,7 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
742
775
  .oh-story-markdown h3,
743
776
  .oh-story-markdown h4,
744
777
  .oh-story-markdown h5,
745
- .oh-story-markdown h6 { margin: 1.45em 0 .6em; font-family: ui-sans-serif, system-ui, "PingFang SC", sans-serif; line-height: 1.35; letter-spacing: -.01em; }
778
+ .oh-story-markdown h6 { margin: 1.45em 0 .6em; font-family: var(--dsw-font-family); line-height: 1.35; letter-spacing: -.01em; }
746
779
  .oh-story-markdown h1 { font-size: 1.75em; }
747
780
  .oh-story-markdown h2 { padding-bottom: .35em; border-bottom: 1px solid var(--os-border); font-size: 1.35em; }
748
781
  .oh-story-markdown h3 { font-size: 1.15em; }
@@ -787,7 +820,7 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
787
820
  .oh-story-jsonl-records { overflow: auto; padding: 14px 18px 36px; }
788
821
  .oh-story-jsonl details,
789
822
  .oh-story-jsonl-error { overflow: hidden; margin-bottom: 10px; border: 1px solid var(--os-border); border-radius: 10px; background: var(--dsw-alias-bg-layer-1); }
790
- .oh-story-jsonl summary { display: flex; min-height: 42px; align-items: center; gap: 9px; padding: 0 12px; cursor: pointer; list-style: none; font: 12px/1.4 ui-sans-serif, system-ui, "PingFang SC", sans-serif; }
823
+ .oh-story-jsonl summary { display: flex; min-height: 42px; align-items: center; gap: 9px; padding: 0 12px; cursor: pointer; list-style: none; font: 12px/1.4 var(--dsw-font-family); }
791
824
  .oh-story-jsonl summary::-webkit-details-marker { display: none; }
792
825
  .oh-story-jsonl summary::before { content: "\u203A"; color: var(--os-muted); font-size: 16px; transition: transform .12s ease; }
793
826
  .oh-story-jsonl details[open] summary::before { transform: rotate(90deg); }
@@ -843,49 +876,104 @@ var plugin_default = `.oh-story-bridge-marker { display: none; }
843
876
  .oh-story-role pre { max-height: 260px; overflow: auto; margin: 4px 0 4px 4px; padding: 12px 16px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; color: var(--dsw-alias-label-secondary); background: var(--dsw-alias-markdown-code-block); white-space: pre-wrap; font: var(--dsw-font-markdown-code-block-small); }
844
877
  .oh-story-role > button { align-self: flex-start; margin: 4px 0 2px 4px; font-size: 11px; }
845
878
 
846
- @media (max-width: 980px) {
847
- [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
848
- grid-template-columns: 104px minmax(210px, 1fr) minmax(252px, 40%);
849
- }
850
- .oh-story-editor textarea { padding-right: 28px; padding-left: 28px; }
879
+ [data-conversation-scroll][data-oh-story-layout="medium"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
880
+ grid-template-columns: clamp(104px, 18%, 184px) minmax(200px, 1fr) clamp(300px, 45%, 408px);
851
881
  }
852
882
 
853
- @media (max-width: 700px) {
854
- [data-conversation-scroll]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
855
- grid-template-columns: 72px minmax(0, 1fr) 236px;
856
- }
857
- .oh-story-tree { padding-inline: 5px; }
858
- .oh-story-brand { padding-inline: 3px; font-size: 11px; }
859
- .oh-story-brand button { width: 24px; }
860
- .oh-story-mode-tabs { margin-inline: 0; }
861
- .oh-story-mode-tabs button { font-size: 10px; }
862
- .oh-story-editor header { padding-inline: 7px; }
863
- .oh-story-editor header > span { display: none; }
864
- .oh-story-editor-tabs button { padding-inline: 6px; }
865
- .oh-story-jsonl summary > code,
866
- .oh-story-jsonl summary > em { display: none; }
883
+ [data-conversation-scroll][data-oh-story-layout="compact"]:has(> [data-slot="conversation.session"] > .oh-story-split-surface) {
884
+ grid-template-columns: clamp(80px, 16%, 104px) minmax(0, 1fr) clamp(228px, 48%, 300px);
867
885
  }
868
- `;
869
886
 
870
- // src/file-activity-projection-types.ts
871
- var FILE_ACTIVITY_PROJECTION_KEY = "ohStoryFileActivity";
887
+ [data-oh-story-layout="medium"] .oh-story-file-folder > summary { padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 35px); }
888
+ [data-oh-story-layout="medium"] .oh-story-tree nav button { padding-left: min(calc(14px + var(--oh-story-indent, 0px)), 42px); }
889
+ [data-oh-story-layout="medium"] .oh-story-editor textarea { padding-right: 28px; padding-left: 28px; }
890
+
891
+ [data-oh-story-layout="compact"] .oh-story-tree { padding-inline: 4px; }
892
+ [data-oh-story-layout="compact"] .oh-story-brand { gap: 4px; padding-inline: 2px; font-size: 11px; }
893
+ [data-oh-story-layout="compact"] .oh-story-brand-cluster { gap: 4px; }
894
+ [data-oh-story-layout="compact"] .oh-story-brand-cluster strong > span { display: none; }
895
+ [data-oh-story-layout="compact"] .oh-story-kind { padding-inline: 4px; font-size: 11px; }
896
+ [data-oh-story-layout="compact"] .oh-story-brand button { width: 24px; height: 24px; }
897
+ [data-oh-story-layout="compact"] .oh-story-mode-tabs { margin-inline: 0; }
898
+ [data-oh-story-layout="compact"] .oh-story-mode-tabs button { padding-inline: 3px; font-size: 11px; }
899
+ [data-oh-story-layout="compact"] .oh-story-file-folder > summary { padding-left: min(calc(5px + var(--oh-story-indent, 0px)), 19px); }
900
+ [data-oh-story-layout="compact"] .oh-story-tree nav button { padding-right: 4px; padding-left: min(calc(7px + var(--oh-story-indent, 0px)), 21px); }
901
+ [data-oh-story-layout="compact"] .oh-story-editor header {
902
+ display: grid;
903
+ min-height: 68px;
904
+ grid-template-columns: minmax(0, 1fr);
905
+ grid-template-rows: 24px 36px;
906
+ align-content: center;
907
+ gap: 0;
908
+ padding: 4px 3px;
909
+ }
910
+ [data-oh-story-layout="compact"] .oh-story-editor-path > span { display: none; }
911
+ [data-oh-story-layout="compact"] .oh-story-editor-path > strong { display: block; font-size: 11px; line-height: 24px; }
912
+ [data-oh-story-layout="compact"] .oh-story-editor-actions { width: 100%; min-width: 0; justify-content: flex-end; gap: 4px; }
913
+ [data-oh-story-layout="compact"] .oh-story-editor-tabs { min-width: 0; flex: 1; }
914
+ [data-oh-story-layout="compact"] .oh-story-editor-tabs button { min-width: 0; padding-inline: 2px; white-space: nowrap; }
915
+ [data-oh-story-layout="compact"] .oh-story-save { flex: none; padding-inline: 4px; white-space: nowrap; font-size: 11px; }
916
+ [data-oh-story-layout="compact"] .oh-story-editor textarea { padding: 16px 12px 40px; }
917
+ [data-oh-story-layout="compact"] .oh-story-markdown { padding: 18px 12px 48px; }
918
+ [data-oh-story-layout="compact"] .oh-story-jsonl-summary { padding-inline: 10px; }
919
+ [data-oh-story-layout="compact"] .oh-story-jsonl-records { padding: 8px 7px 28px; }
920
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary { gap: 6px; padding-inline: 7px; }
921
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > span { display: none; }
922
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > strong { min-width: 32px; }
923
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > code,
924
+ [data-oh-story-layout="compact"] .oh-story-jsonl summary > em { display: none; }
925
+ `;
872
926
 
873
927
  // src/client/index.tsx
874
- var import_jsx_runtime3 = require("react/jsx-runtime"), name = "oh-story", inject = ["sessions", "slots"], EMPTY_FILE_ACTIVITY_STORE = {
875
- subscribe: () => () => {
876
- },
877
- getSnapshot: () => {
878
- }
879
- }, WorkspaceRequestError = class extends Error {
928
+ var import_jsx_runtime3 = require("react/jsx-runtime"), name = "oh-story", inject = ["slots"];
929
+ function applyUpdate(current, update) {
930
+ return typeof update == "function" ? update(current) : update;
931
+ }
932
+ function createWorkbenchStore() {
933
+ return (0, import_client.defineStore)({
934
+ init: () => ({
935
+ buffers: {},
936
+ editorMode: "preview",
937
+ expanded: {},
938
+ selected: void 0,
939
+ workbench: "story"
940
+ }),
941
+ actions: {
942
+ setBuffers: (draft, update) => {
943
+ draft.buffers = applyUpdate(draft.buffers, update);
944
+ },
945
+ setEditorMode: (draft, update) => {
946
+ draft.editorMode = applyUpdate(draft.editorMode, update);
947
+ },
948
+ setExpanded: (draft, update) => {
949
+ draft.expanded = applyUpdate(draft.expanded, update);
950
+ },
951
+ setSelected: (draft, update) => {
952
+ draft.selected = applyUpdate(draft.selected, update);
953
+ },
954
+ setWorkbench: (draft, update) => {
955
+ draft.workbench = applyUpdate(draft.workbench, update);
956
+ }
957
+ }
958
+ });
959
+ }
960
+ var WorkspaceRequestError = class extends Error {
880
961
  constructor(status, message) {
881
962
  super(message);
882
963
  this.status = status;
883
964
  }
884
965
  status;
885
- }, workbenchMemory = /* @__PURE__ */ new Map(), GROUP_ORDER = {
966
+ }, GROUP_ORDER = {
886
967
  story: ["\u6B63\u6587", "\u5927\u7EB2", "\u8BBE\u5B9A", "\u8FFD\u8E2A", "\u5BF9\u6807", "\u53C2\u8003\u8D44\u6599"],
887
968
  drama: ["\u9879\u76EE", "\u8F93\u5165", "\u9879\u76EE\u5F00\u53D1", "\u8BBE\u5B9A\u96C6", "\u5267\u96C6", "\u5BA1\u67E5", "\u521B\u4F5C\u8005\u51B3\u7B56", "\u4EA4\u4ED8"]
888
- };
969
+ }, WORKBENCH_MODES = ["story", "drama"], EDITOR_MODES = ["preview", "source"];
970
+ function handleTabKey(event, values, current, select) {
971
+ let index;
972
+ if (event.key === "Home" ? index = 0 : event.key === "End" ? index = values.length - 1 : event.key === "ArrowRight" ? index = (values.indexOf(current) + 1) % values.length : event.key === "ArrowLeft" && (index = (values.indexOf(current) - 1 + values.length) % values.length), index === void 0) return;
973
+ event.preventDefault();
974
+ let value = values[index];
975
+ value !== void 0 && (select(value), event.currentTarget.parentElement?.querySelectorAll("[role='tab']")[index]?.focus());
976
+ }
889
977
  function groupForPath(path) {
890
978
  return path === "short-drama.json" ? "\u9879\u76EE" : path.split("/", 1)[0] ?? "\u5176\u4ED6";
891
979
  }
@@ -897,7 +985,6 @@ function preferredFile(files, mode) {
897
985
  }
898
986
  return matching[0]?.path;
899
987
  }
900
- var EMPTY_CALLS = [];
901
988
  function endpoint(path, sessionId, file) {
902
989
  let url = new URL(`/oh-story/${path}`, globalThis.location.origin);
903
990
  return url.searchParams.set("sessionId", sessionId), file !== void 0 && url.searchParams.set("path", file), url.toString();
@@ -921,7 +1008,9 @@ function FileTreeNodes({
921
1008
  "button",
922
1009
  {
923
1010
  type: "button",
924
- style: { paddingLeft: `${String(14 + depth * 14)}px` },
1011
+ style: { "--oh-story-indent": `${String(depth * 14)}px` },
1012
+ title: node.path,
1013
+ "aria-label": node.path,
925
1014
  "data-file-path": node.path,
926
1015
  "data-agent-target": node.path === activityPath || void 0,
927
1016
  "aria-current": node.path === selected ? "page" : void 0,
@@ -936,7 +1025,7 @@ function FileTreeNodes({
936
1025
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("details", { className: "oh-story-file-folder", open, onToggle: (event) => {
937
1026
  onToggle(node.path, event.currentTarget.open);
938
1027
  }, children: [
939
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("summary", { style: { paddingLeft: `${String(7 + depth * 14)}px` }, children: [
1028
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("summary", { style: { "--oh-story-indent": `${String(depth * 14)}px` }, title: node.path, children: [
940
1029
  node.name,
941
1030
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: node.fileCount })
942
1031
  ] }),
@@ -973,20 +1062,43 @@ function useWorkspace(sessionId) {
973
1062
  function CreativeWorkbench({
974
1063
  sessionId,
975
1064
  runningCalls,
976
- projectedCalls
1065
+ partial,
1066
+ settledMutation,
1067
+ useStore,
1068
+ actions
977
1069
  }) {
978
1070
  let { workspace, error, loading: workspaceLoading, reload } = useWorkspace(sessionId), activities = (0, import_react.useMemo)(
979
- () => fileMutations(runningCalls, projectedCalls),
980
- [projectedCalls, runningCalls]
1071
+ () => fileMutations(runningCalls, partial),
1072
+ [partial, runningCalls]
981
1073
  ), normalizedActivities = (0, import_react.useMemo)(() => activities.flatMap((activity2) => {
982
1074
  let path = creativeRelativePath(activity2.path, workspace?.cwd);
983
1075
  return path === void 0 ? [] : [{ activity: activity2, path }];
984
- }), [activities, workspace?.cwd]), primaryActivity = normalizedActivities.at(-1), activityPaths = (0, import_react.useMemo)(() => new Set(normalizedActivities.map((value) => value.path)), [normalizedActivities]), activity = primaryActivity?.activity, activityPath = primaryActivity?.path, remembered = (0, import_react.useMemo)(() => workbenchMemory.get(sessionId), [sessionId]), [workbench, setWorkbench] = (0, import_react.useState)(remembered?.workbench ?? "story"), initializedWorkbench = (0, import_react.useRef)(!1), [selected, setSelected] = (0, import_react.useState)(remembered?.selected), [buffers, setBuffers] = (0, import_react.useState)(remembered?.buffers ?? {}), buffersRef = (0, import_react.useRef)({}), [expanded, setExpanded] = (0, import_react.useState)(remembered?.expanded ?? {}), surfaceRef = (0, import_react.useRef)(null), navRef = (0, import_react.useRef)(null), activityBases = (0, import_react.useRef)(/* @__PURE__ */ new Map()), previousSignals = (0, import_react.useRef)(/* @__PURE__ */ new Set()), saveLocks = (0, import_react.useRef)(/* @__PURE__ */ new Set()), buffer = selected === void 0 ? void 0 : buffers[selected], dirty = buffer?.source === "human" && buffer.content !== buffer.saved, saving = buffer?.saving === !0, fileError = buffer?.error, conflict = buffer?.conflict, selectedLower = selected?.toLocaleLowerCase(), markdown = selectedLower?.endsWith(".md") === !0, jsonl = selectedLower?.endsWith(".jsonl") === !0, structured = jsonl || selectedLower?.endsWith(".json") === !0, previewable = markdown || jsonl, [editorMode, setEditorMode] = (0, import_react.useState)(remembered?.editorMode ?? "preview"), modeSelection = (0, import_react.useRef)(selected);
1076
+ }), [activities, workspace?.cwd]), primaryActivity = normalizedActivities.at(-1), activityPaths = (0, import_react.useMemo)(() => new Set(normalizedActivities.map((value) => value.path)), [normalizedActivities]), activity = primaryActivity?.activity, activityPath = primaryActivity?.path, workbench = useStore((memory) => memory.workbench), setWorkbench = actions.setWorkbench, initializedWorkbench = (0, import_react.useRef)(!1), selected = useStore((memory) => memory.selected), setSelected = actions.setSelected, buffers = useStore((memory) => memory.buffers), setBuffers = actions.setBuffers, buffersRef = (0, import_react.useRef)({}), expanded = useStore((memory) => memory.expanded), setExpanded = actions.setExpanded, surfaceRef = (0, import_react.useRef)(null), navRef = (0, import_react.useRef)(null), activityBases = (0, import_react.useRef)(/* @__PURE__ */ new Map()), previousSignals = (0, import_react.useRef)(/* @__PURE__ */ new Set()), previousSettledMutation = (0, import_react.useRef)(settledMutation), saveLocks = (0, import_react.useRef)(/* @__PURE__ */ new Set()), buffer = selected === void 0 ? void 0 : buffers[selected], dirty = buffer?.source === "human" && buffer.content !== buffer.saved, saving = buffer?.saving === !0, fileError = buffer?.error, conflict = buffer?.conflict, selectedLower = selected?.toLocaleLowerCase(), markdown = selectedLower?.endsWith(".md") === !0, jsonl = selectedLower?.endsWith(".jsonl") === !0, structured = jsonl || selectedLower?.endsWith(".json") === !0, previewable = markdown || jsonl, editorMode = useStore((memory) => memory.editorMode), setEditorMode = actions.setEditorMode, modeSelection = (0, import_react.useRef)(selected), textareaRef = (0, import_react.useRef)(null), editorPositions = (0, import_react.useRef)(/* @__PURE__ */ new Map()), editorReady = buffer !== void 0 && buffer.missing !== !0, availableModes = (0, import_react.useMemo)(() => {
1077
+ let value = /* @__PURE__ */ new Set();
1078
+ for (let file of workspace?.files ?? []) {
1079
+ let mode = workbenchModeForPath(file.path);
1080
+ mode !== void 0 && value.add(mode);
1081
+ }
1082
+ return WORKBENCH_MODES.filter((mode) => value.has(mode));
1083
+ }, [workspace?.files]), showModeTabs = workspace !== void 0 && availableModes.length !== 1, workspaceKind = availableModes.length === 1 ? availableModes[0] : void 0;
985
1084
  (0, import_react.useEffect)(() => {
986
1085
  buffersRef.current = buffers;
987
- }, [buffers]), (0, import_react.useEffect)(() => {
988
- workbenchMemory.set(sessionId, { buffers, editorMode, expanded, selected, workbench });
989
- }, [buffers, editorMode, expanded, selected, sessionId, workbench]), (0, import_react.useEffect)(() => {
1086
+ }, [buffers]);
1087
+ let rememberEditorPosition = (0, import_react.useCallback)(() => {
1088
+ let element = textareaRef.current;
1089
+ element === null || selected === void 0 || element.getAttribute("aria-label") !== selected || editorPositions.current.set(selected, {
1090
+ scrollTop: element.scrollTop,
1091
+ selectionStart: element.selectionStart,
1092
+ selectionEnd: element.selectionEnd
1093
+ });
1094
+ }, [selected]);
1095
+ (0, import_react.useLayoutEffect)(() => {
1096
+ if (editorMode !== "source" || selected === void 0 || !editorReady) return;
1097
+ let element = textareaRef.current, position = editorPositions.current.get(selected);
1098
+ if (element === null || position === void 0) return;
1099
+ let end = Math.min(position.selectionEnd, element.value.length);
1100
+ element.setSelectionRange(Math.min(position.selectionStart, end), end), element.scrollTop = position.scrollTop;
1101
+ }, [editorMode, editorReady, selected]), (0, import_react.useEffect)(() => {
990
1102
  let warn = (event) => {
991
1103
  Object.values(buffersRef.current).some((value) => value.source === "human" && value.content !== value.saved) && event.preventDefault();
992
1104
  };
@@ -994,8 +1106,7 @@ function CreativeWorkbench({
994
1106
  globalThis.removeEventListener("beforeunload", warn);
995
1107
  };
996
1108
  }, []);
997
- let revealPath = (0, import_react.useCallback)((path) => {
998
- setWorkbench(workbenchModeForPath(path) ?? "story"), setSelected(path);
1109
+ let expandPath = (0, import_react.useCallback)((path) => {
999
1110
  let segments = path.split("/"), ancestors = [groupForPath(path)];
1000
1111
  for (let index = 1; index < segments.length - 1; index += 1) ancestors.push(segments.slice(0, index + 1).join("/"));
1001
1112
  setExpanded((current) => {
@@ -1003,24 +1114,22 @@ function CreativeWorkbench({
1003
1114
  for (let ancestor of ancestors) next[ancestor] = !0;
1004
1115
  return next;
1005
1116
  });
1006
- }, []);
1117
+ }, []), revealPath = (0, import_react.useCallback)((path) => {
1118
+ rememberEditorPosition(), setWorkbench(workbenchModeForPath(path) ?? "story"), setSelected(path), expandPath(path);
1119
+ }, [expandPath, rememberEditorPosition]), followAgentPath = (0, import_react.useCallback)((path) => {
1120
+ expandPath(path);
1121
+ let current = selected === void 0 ? void 0 : buffersRef.current[selected];
1122
+ path !== selected && current?.source === "human" && current.content !== current.saved && surfaceRef.current?.ownerDocument.activeElement === textareaRef.current || revealPath(path);
1123
+ }, [expandPath, revealPath, selected]);
1007
1124
  (0, import_react.useEffect)(() => {
1008
- if (workspace === void 0 || initializedWorkbench.current) return;
1009
- let hasStory = workspace.files.some((file) => workbenchModeForPath(file.path) === "story");
1010
- workspace.shortDrama !== null && !hasStory && setWorkbench("drama"), initializedWorkbench.current = !0;
1011
- }, [workspace]), (0, import_react.useEffect)(() => {
1012
- activityPath !== void 0 && setEditorMode("source");
1013
- }, [activityPath]), (0, import_react.useEffect)(() => {
1125
+ workspace === void 0 || initializedWorkbench.current || (availableModes.length === 1 && setWorkbench(availableModes[0] ?? "story"), initializedWorkbench.current = !0);
1126
+ }, [availableModes, workspace]), (0, import_react.useEffect)(() => {
1127
+ activityPath !== void 0 && activityPath === selected && setEditorMode("source");
1128
+ }, [activityPath, selected]), (0, import_react.useEffect)(() => {
1014
1129
  modeSelection.current !== selected && (modeSelection.current = selected, setEditorMode(activityPath === selected ? "source" : previewable ? "preview" : "source"));
1015
1130
  }, [activityPath, previewable, selected]), (0, import_react.useEffect)(() => {
1016
- if (!workspaceLoading) {
1017
- if (activityPath !== void 0) {
1018
- revealPath(activityPath);
1019
- return;
1020
- }
1021
- selected !== void 0 && ((workspace?.files.some((file) => file.path === selected) ?? !1) || buffers[selected] !== void 0) && workbenchModeForPath(selected) === workbench || setSelected(workspace === void 0 ? void 0 : preferredFile(workspace.files, workbench));
1022
- }
1023
- }, [activityPath, buffers, revealPath, selected, workbench, workspace, workspaceLoading]), (0, import_react.useEffect)(() => {
1131
+ workspaceLoading || activityPath === void 0 && (selected !== void 0 && ((workspace?.files.some((file) => file.path === selected) ?? !1) || buffers[selected] !== void 0) && workbenchModeForPath(selected) === workbench || setSelected(workspace === void 0 ? void 0 : preferredFile(workspace.files, workbench)));
1132
+ }, [activityPath, buffers, selected, workbench, workspace, workspaceLoading]), (0, import_react.useEffect)(() => {
1024
1133
  if (workspace === void 0 || workspaceLoading) return;
1025
1134
  let paths = new Set(workspace.files.map((file) => file.path));
1026
1135
  setBuffers((current) => {
@@ -1068,8 +1177,8 @@ function CreativeWorkbench({
1068
1177
  };
1069
1178
  }, [activityPaths, selected, sessionId, workspace?.files]), (0, import_react.useEffect)(() => {
1070
1179
  if (normalizedActivities.length !== 0) {
1071
- for (let { path } of normalizedActivities) revealPath(path);
1072
- setBuffers((current) => {
1180
+ for (let { path } of normalizedActivities) expandPath(path);
1181
+ activityPath !== void 0 && followAgentPath(activityPath), setBuffers((current) => {
1073
1182
  let next = current;
1074
1183
  for (let { activity: currentActivity, path } of normalizedActivities) {
1075
1184
  let existing = next[path];
@@ -1099,7 +1208,7 @@ function CreativeWorkbench({
1099
1208
  return next;
1100
1209
  });
1101
1210
  }
1102
- }, [normalizedActivities, revealPath]), (0, import_react.useEffect)(() => {
1211
+ }, [activityPath, expandPath, followAgentPath, normalizedActivities]), (0, import_react.useEffect)(() => {
1103
1212
  let signals = new Set(mutatingCallIds(runningCalls));
1104
1213
  for (let { activity: currentActivity } of normalizedActivities) signals.add(currentActivity.callId.split(":", 1)[0] ?? currentActivity.callId);
1105
1214
  let settled = [...previousSignals.current].some((callId) => !signals.has(callId));
@@ -1107,6 +1216,11 @@ function CreativeWorkbench({
1107
1216
  signals.has(callId.split(":", 1)[0] ?? callId) || activityBases.current.delete(callId);
1108
1217
  previousSignals.current = signals, settled && reload();
1109
1218
  }, [normalizedActivities, reload, runningCalls]), (0, import_react.useEffect)(() => {
1219
+ if (settledMutation === void 0 || settledMutation === previousSettledMutation.current) return;
1220
+ previousSettledMutation.current = settledMutation;
1221
+ let path = creativeRelativePath(settledMutation.slice(settledMutation.indexOf("\0") + 1), workspace?.cwd);
1222
+ path !== void 0 && followAgentPath(path), reload();
1223
+ }, [followAgentPath, reload, settledMutation, workspace?.cwd]), (0, import_react.useEffect)(() => {
1110
1224
  if (selected !== void 0) {
1111
1225
  for (let button of navRef.current?.querySelectorAll("button[data-file-path]") ?? [])
1112
1226
  if (button.dataset.filePath === selected) {
@@ -1230,7 +1344,9 @@ function CreativeWorkbench({
1230
1344
  setWorkbench(next);
1231
1345
  let target = workspace === void 0 ? void 0 : preferredFile(workspace.files, next);
1232
1346
  target === void 0 ? setSelected(void 0) : revealPath(target);
1233
- }, selectedGroup = selected === void 0 ? void 0 : groupForPath(selected), toggleGroup = (key, open) => {
1347
+ }, selectEditorMode = (next) => {
1348
+ next === "preview" && rememberEditorPosition(), setEditorMode(next);
1349
+ }, selectedLabel = selected ?? `\u5728\u5F53\u524D DSH workspace \u4E2D\u9009\u62E9${workbench === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267"}\u6587\u4EF6`, selectedBasename = selected?.split("/").at(-1) ?? selectedLabel, selectedGroup = selected === void 0 ? void 0 : groupForPath(selected), toggleGroup = (key, open) => {
1234
1350
  setExpanded((current) => ({ ...current, [key]: open }));
1235
1351
  }, resolveConflict = (keepLocal) => {
1236
1352
  if (selected === void 0 || conflict?.theirs === void 0 || conflict.theirsVersion === void 0) return;
@@ -1247,17 +1363,32 @@ function CreativeWorkbench({
1247
1363
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: plugin_default }),
1248
1364
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("aside", { className: "oh-story-tree", children: [
1249
1365
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-brand", children: [
1250
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "\u2726 Oh Story" }),
1251
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: reload, title: "\u5237\u65B0", children: "\u21BB" })
1252
- ] }),
1253
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-mode-tabs", role: "tablist", "aria-label": "\u521B\u4F5C\u5DE5\u4F5C\u53F0", children: [
1254
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", role: "tab", "aria-selected": workbench === "story", onClick: () => {
1255
- selectWorkbench("story");
1256
- }, children: "\u5C0F\u8BF4" }),
1257
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", role: "tab", "aria-selected": workbench === "drama", onClick: () => {
1258
- selectWorkbench("drama");
1259
- }, children: "\u77ED\u5267" })
1366
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "oh-story-brand-cluster", children: [
1367
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("strong", { children: [
1368
+ "\u2726 ",
1369
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: "Oh Story" })
1370
+ ] }),
1371
+ workspaceKind !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "oh-story-kind", children: workspaceKind === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267" })
1372
+ ] }),
1373
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: reload, title: "\u5237\u65B0", "aria-label": "\u5237\u65B0\u9879\u76EE\u6587\u4EF6", children: "\u21BB" })
1260
1374
  ] }),
1375
+ showModeTabs && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-mode-tabs", role: "tablist", "aria-label": "\u521B\u4F5C\u5DE5\u4F5C\u53F0", children: WORKBENCH_MODES.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1376
+ "button",
1377
+ {
1378
+ type: "button",
1379
+ role: "tab",
1380
+ tabIndex: workbench === mode ? 0 : -1,
1381
+ "aria-selected": workbench === mode,
1382
+ onKeyDown: (event) => {
1383
+ handleTabKey(event, WORKBENCH_MODES, workbench, selectWorkbench);
1384
+ },
1385
+ onClick: () => {
1386
+ selectWorkbench(mode);
1387
+ },
1388
+ children: mode === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267"
1389
+ },
1390
+ mode
1391
+ )) }),
1261
1392
  error !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-error", children: error }),
1262
1393
  workspace?.metadataErrors.map((message) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-warning", children: message }, message)),
1263
1394
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("nav", { ref: navRef, "aria-label": workbench === "story" ? "\u5C0F\u8BF4\u9879\u76EE\u6587\u4EF6" : "\u77ED\u5267\u9879\u76EE\u6587\u4EF6", children: groups.map(([directory2, files]) => {
@@ -1286,16 +1417,28 @@ function CreativeWorkbench({
1286
1417
  ] }),
1287
1418
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("main", { className: "oh-story-editor", children: [
1288
1419
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { children: [
1289
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { title: selected, children: selected ?? `\u5728\u5F53\u524D DSH workspace \u4E2D\u9009\u62E9${workbench === "story" ? "\u5C0F\u8BF4" : "\u77ED\u5267"}\u6587\u4EF6` }),
1420
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "oh-story-editor-path", title: selected, children: [
1421
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: selectedLabel }),
1422
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: selectedBasename })
1423
+ ] }),
1290
1424
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-editor-actions", children: [
1291
- previewable && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "oh-story-editor-tabs", role: "tablist", "aria-label": markdown ? "Markdown \u67E5\u770B\u65B9\u5F0F" : "JSONL \u67E5\u770B\u65B9\u5F0F", children: [
1292
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", role: "tab", "aria-selected": editorMode === "preview", onClick: () => {
1293
- setEditorMode("preview");
1294
- }, children: "\u9884\u89C8" }),
1295
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", role: "tab", "aria-selected": editorMode === "source", onClick: () => {
1296
- setEditorMode("source");
1297
- }, children: "\u6E90\u7801" })
1298
- ] }),
1425
+ previewable && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "oh-story-editor-tabs", role: "tablist", "aria-label": markdown ? "Markdown \u67E5\u770B\u65B9\u5F0F" : "JSONL \u67E5\u770B\u65B9\u5F0F", children: EDITOR_MODES.map((mode) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1426
+ "button",
1427
+ {
1428
+ type: "button",
1429
+ role: "tab",
1430
+ tabIndex: editorMode === mode ? 0 : -1,
1431
+ "aria-selected": editorMode === mode,
1432
+ onKeyDown: (event) => {
1433
+ handleTabKey(event, EDITOR_MODES, editorMode, selectEditorMode);
1434
+ },
1435
+ onClick: () => {
1436
+ selectEditorMode(mode);
1437
+ },
1438
+ children: mode === "preview" ? "\u9884\u89C8" : "\u6E90\u7801"
1439
+ },
1440
+ mode
1441
+ )) }),
1299
1442
  (dirty || saving) && selected !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "oh-story-save", type: "button", disabled: saving || buffer?.missing === !0, onClick: () => {
1300
1443
  savePath(selected);
1301
1444
  }, children: saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58" })
@@ -1340,8 +1483,12 @@ function CreativeWorkbench({
1340
1483
  ] }) : previewable && editorMode === "preview" ? markdown ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MarkdownPreview, { content: buffer.content, label: selected }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(JsonlPreview, { content: buffer.content, label: selected }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1341
1484
  "textarea",
1342
1485
  {
1486
+ ref: textareaRef,
1343
1487
  value: buffer.content,
1344
1488
  "data-format": structured ? "structured" : "prose",
1489
+ onBlur: rememberEditorPosition,
1490
+ onScroll: rememberEditorPosition,
1491
+ onSelect: rememberEditorPosition,
1345
1492
  onChange: (event) => {
1346
1493
  let content = event.target.value;
1347
1494
  setBuffers((current) => ({
@@ -1363,12 +1510,8 @@ function CreativeWorkbench({
1363
1510
  ] })
1364
1511
  ] });
1365
1512
  }
1366
- function CreativeSplitBridge({ context }) {
1367
- let marker = (0, import_react.useRef)(null), [target, setTarget] = (0, import_react.useState)(), provideFace = context.sessions.currentProvideInfo, subscribeProvide = (0, import_react.useCallback)((listener) => provideFace.subscribe(listener), [provideFace]), getProvide = (0, import_react.useCallback)(() => provideFace.getSnapshot(), [provideFace]), provide = (0, import_react.useSyncExternalStore)(subscribeProvide, getProvide, getProvide), sessionId = provide.sessionId, source = provide.hooks.session, subscribe = (0, import_react.useCallback)((listener) => source?.subscribe(listener) ?? (() => {
1368
- }), [source]), getSnapshot = (0, import_react.useCallback)(() => source?.getSnapshot(), [source]), snapshot = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot), projectionSource = (0, import_react.useMemo)(
1369
- () => provide.projections?.faceOf(FILE_ACTIVITY_PROJECTION_KEY) ?? EMPTY_FILE_ACTIVITY_STORE,
1370
- [provide.projections]
1371
- ), subscribeProjection = (0, import_react.useCallback)((listener) => projectionSource.subscribe(listener), [projectionSource]), getProjection = (0, import_react.useCallback)(() => projectionSource.getSnapshot(), [projectionSource]), projection = (0, import_react.useSyncExternalStore)(subscribeProjection, getProjection, getProjection);
1513
+ function CreativeSplitBridge({ sessionId, useSession, useStore, actions }) {
1514
+ let marker = (0, import_react.useRef)(null), [target, setTarget] = (0, import_react.useState)(), runningCalls = useSession((snapshot) => snapshot.runningCalls), partial = useSession((snapshot) => streamingAssistant(snapshot.chat.timeline)), settledMutation = useSession((snapshot) => latestSettledMutation(snapshot.chat));
1372
1515
  return (0, import_react.useLayoutEffect)(() => {
1373
1516
  let document = marker.current?.ownerDocument;
1374
1517
  if (document === void 0) return;
@@ -1384,27 +1527,34 @@ function CreativeSplitBridge({ context }) {
1384
1527
  }, [sessionId]), (0, import_react.useLayoutEffect)(() => {
1385
1528
  let scroller = target?.parentElement;
1386
1529
  if (scroller == null) return;
1387
- let publishHeight = () => {
1530
+ let publishLayout = () => {
1388
1531
  scroller.style.setProperty("--oh-story-scroll-height", `${String(scroller.clientHeight)}px`);
1532
+ let layout = scroller.clientWidth < 620 ? "compact" : scroller.clientWidth < 900 ? "medium" : "wide";
1533
+ scroller.dataset.ohStoryLayout !== layout && (scroller.dataset.ohStoryLayout = layout);
1389
1534
  };
1390
- publishHeight();
1391
- let observer = new ResizeObserver(publishHeight);
1535
+ publishLayout();
1536
+ let observer = new ResizeObserver(publishLayout);
1392
1537
  return observer.observe(scroller), () => {
1393
- observer.disconnect(), scroller.style.removeProperty("--oh-story-scroll-height");
1538
+ observer.disconnect(), scroller.style.removeProperty("--oh-story-scroll-height"), delete scroller.dataset.ohStoryLayout;
1394
1539
  };
1395
1540
  }, [target]), /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1396
1541
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { ref: marker, className: "oh-story-bridge-marker", "aria-hidden": !0 }),
1397
- target === void 0 || sessionId === void 0 ? null : (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1542
+ target === void 0 ? null : (0, import_react_dom.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1398
1543
  CreativeWorkbench,
1399
1544
  {
1400
1545
  sessionId,
1401
- runningCalls: snapshot?.runningCalls ?? EMPTY_CALLS,
1402
- projectedCalls: projection?.calls ?? []
1403
- },
1404
- sessionId
1546
+ runningCalls,
1547
+ partial,
1548
+ settledMutation,
1549
+ useStore,
1550
+ actions
1551
+ }
1405
1552
  ), target)
1406
1553
  ] });
1407
1554
  }
1555
+ function WorkbenchSeat({ SessionProvider, renderSlot }) {
1556
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SessionProvider, { children: () => renderSlot("oh-story.workspace", {}) });
1557
+ }
1408
1558
  function argsOf(block) {
1409
1559
  let raw = ("kind" in block ? block.call?.argsRaw : block.argsRaw) ?? "{}";
1410
1560
  try {
@@ -1433,11 +1583,18 @@ function RoleToolView({ block, inspect }) {
1433
1583
  ] });
1434
1584
  }
1435
1585
  function apply(context) {
1436
- context.slots.inject("shell.overlay", () => context.slots.register({
1437
- name: "shell.overlay",
1438
- id: "oh-story-workspace",
1439
- order: -100
1440
- }, () => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CreativeSplitBridge, { context }))), context.slots.inject("tool.call.toolview", () => context.slots.register({
1586
+ context.slots.inject("shell.overlay", () => {
1587
+ let disposeSeat = context.slots.register({
1588
+ name: "shell.overlay",
1589
+ id: "oh-story-workspace",
1590
+ order: -100,
1591
+ children: { "oh-story.workspace": { kind: "single", scope: "session" } }
1592
+ }, WorkbenchSeat), disposeWorkbench = context.slots.register({
1593
+ name: "oh-story.workspace",
1594
+ store: createWorkbenchStore
1595
+ }, CreativeSplitBridge);
1596
+ return [disposeSeat, disposeWorkbench];
1597
+ }), context.slots.inject("tool.call.toolview", () => context.slots.register({
1441
1598
  name: "tool.call.toolview",
1442
1599
  key: "oh_story_role"
1443
1600
  }, RoleToolView));