dsh-edge 0.2.0 → 0.3.0-alpha.1

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.
@@ -230,7 +230,7 @@ window.__ModuleLoader__.load({
230
230
  }
231
231
  //#endregion
232
232
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-user-questions/src/client/QuestionComposer.module.css.mjs
233
- const css = ".Mbwy4a_frame{padding:6px calc(var(--dsh-composer-side-clearance) + 16px) 10px;justify-content:center;display:flex}.Mbwy4a_card{width:100%;max-width:var(--dsh-chat-content-width);border:1px solid var(--dsw-alias-border-l2-darkmode-thin);background:var(--dsw-specific-input-major);max-height:min(60vh,520px);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:20px;flex-direction:column;padding:0 0 10px;display:flex;overflow:hidden}.Mbwy4a_card,.Mbwy4a_card *{box-sizing:border-box}.Mbwy4a_cardMinimized{max-height:none}.Mbwy4a_cardMinimized .Mbwy4a_header{padding-bottom:14px}.Mbwy4a_headerActions{flex-shrink:0;align-items:center;gap:4px;display:flex}.Mbwy4a_header{flex-shrink:0;justify-content:space-between;align-items:flex-start;gap:16px;padding:20px 16px 0 24px;display:flex}.Mbwy4a_headingBlock{min-width:0}.Mbwy4a_eyebrow{color:var(--dsw-alias-label-tertiary);margin-bottom:5px;font-size:11px;line-height:16px}.Mbwy4a_title{margin:0;font-size:16px;font-weight:500;line-height:22px}.Mbwy4a_detail{margin:0 2px 8px}.Mbwy4a_footerActions{flex-shrink:0;align-items:center;gap:12px;display:flex}.Mbwy4a_pager{flex-shrink:0;align-items:center;gap:6px;display:flex}.Mbwy4a_progress{color:var(--dsw-alias-label-secondary);white-space:nowrap;word-spacing:-2px;padding:0 4px;font-size:14px;font-weight:500;line-height:24px}.Mbwy4a_iconButton{width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:999px;place-items:center;padding:0;display:grid}.Mbwy4a_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.Mbwy4a_iconButton:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.Mbwy4a_body{overscroll-behavior:contain;flex-direction:column;flex:auto;min-height:0;display:flex;overflow-y:auto}.Mbwy4a_options{flex-direction:column;gap:1px;margin:8px 0 0;padding:4px 12px;display:flex}.Mbwy4a_option{width:100%;min-height:40px;color:inherit;text-align:left;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:12px;flex-shrink:0;align-items:flex-start;gap:8px;padding:8px 12px 8px 8px;transition:background-color .12s,border-color .12s;display:flex}.Mbwy4a_option:hover:not(:disabled),.Mbwy4a_optionSelected{background:var(--dsw-alias-interactive-bg-hover)}.Mbwy4a_optionSelected{border-color:var(--dsw-alias-border-l2)}.Mbwy4a_option:disabled{cursor:default}.Mbwy4a_number{background:var(--dsw-alias-bg-overlay);width:20px;height:20px;color:var(--dsw-alias-label-secondary);border-radius:6px;flex:0 0 20px;place-items:center;margin-top:2px;font-size:12px;font-weight:500;line-height:18px;display:grid}.Mbwy4a_checkbox{flex:0 0 20px;place-items:center;width:20px;height:20px;margin-top:2px;display:grid}.Mbwy4a_checkbox:before{content:\"\";border:1px solid var(--dsw-alias-border-l4);border-radius:4px;grid-area:1/1;width:14px;height:14px;transition:background-color .12s,border-color .12s}.Mbwy4a_checkbox>svg{grid-area:1/1}.Mbwy4a_checkboxChecked{color:var(--dsw-alias-label-primary-foreground)}.Mbwy4a_checkboxChecked:before{border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-label-primary)}.Mbwy4a_optionCopy{flex:1;min-width:0}.Mbwy4a_optionLine{flex-wrap:wrap;align-items:baseline;gap:2px 6px;display:flex}.Mbwy4a_optionLabel{font-size:14px;font-weight:500;line-height:24px}.Mbwy4a_badge{background:var(--dsw-specific-sidebar-nav-item-active-accent);color:var(--dsw-alias-button-info-fill);border-radius:6px;padding:0 4px;font-size:11px;font-weight:600;line-height:18px}.Mbwy4a_description{color:var(--dsw-alias-label-tertiary);font-size:14px;font-weight:400;line-height:24px}.Mbwy4a_customRow{border:1px solid #0000;border-radius:12px;flex-shrink:0;align-items:flex-start;gap:8px;width:100%;min-height:40px;padding:8px 12px 8px 8px;transition:background-color .12s,border-color .12s;display:flex}.Mbwy4a_customRow:hover,.Mbwy4a_customRow:focus-within,.Mbwy4a_customRowActive{background:var(--dsw-alias-interactive-bg-hover)}.Mbwy4a_customRow:focus-within,.Mbwy4a_customRowActive{border-color:var(--dsw-alias-border-l2)}.Mbwy4a_customInput{min-width:0;color:var(--dsw-alias-label-primary);caret-color:var(--dsw-alias-state-business-primary);font:inherit;background:0 0;border:none;outline:none;flex:1;padding:0;font-size:14px;line-height:24px}.Mbwy4a_customInput::placeholder{color:var(--dsw-alias-label-caption)}.Mbwy4a_customTextarea{resize:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);min-height:64px;max-height:140px;color:var(--dsw-alias-label-primary);caret-color:var(--dsw-alias-state-business-primary);font:inherit;border-radius:10px;outline:none;flex-shrink:0;margin:0 12px;padding:8px 12px;font-size:14px;line-height:24px;display:block}.Mbwy4a_customTextarea:focus{border-color:var(--dsw-alias-state-business-primary)}.Mbwy4a_customTextarea::placeholder{color:var(--dsw-alias-label-caption)}.Mbwy4a_footer{flex-shrink:0;justify-content:space-between;align-items:center;gap:12px;margin-top:12px;padding:0 10px 0 18px;display:flex}.Mbwy4a_feedback{min-height:16px;color:var(--dsw-alias-state-error-primary);text-align:right;flex:1;font-size:11px;line-height:16px}@media (width<=720px){.Mbwy4a_card{border-radius:16px}.Mbwy4a_header{padding:10px 12px 0 18px}.Mbwy4a_options{padding:4px 8px}.Mbwy4a_title{font-size:15px;line-height:21px}.Mbwy4a_option,.Mbwy4a_customRow{padding:8px 6px}.Mbwy4a_footer{align-items:flex-end;padding:0 10px}.Mbwy4a_footerActions{flex-shrink:0}}@media (prefers-reduced-motion:reduce){.Mbwy4a_option,.Mbwy4a_customRow{transition:none}}";
233
+ const css = ".Mbwy4a_frame{padding:6px calc(var(--dsh-composer-side-clearance) + 16px) 10px;justify-content:center;display:flex}.Mbwy4a_card{width:100%;max-width:var(--dsh-chat-content-width);border:1px solid var(--dsw-alias-border-l2-darkmode-thin);background:var(--dsw-specific-input-major);max-height:min(60vh,520px);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:20px;flex-direction:column;padding:0 0 10px;display:flex;overflow:hidden}.Mbwy4a_card,.Mbwy4a_card *{box-sizing:border-box}.Mbwy4a_cardMinimized{max-height:none}.Mbwy4a_cardMinimized .Mbwy4a_header{padding-bottom:14px}.Mbwy4a_headerActions{flex-shrink:0;align-items:center;gap:4px;display:flex}.Mbwy4a_header{flex-shrink:0;justify-content:space-between;align-items:flex-start;gap:16px;padding:20px 16px 0 24px;display:flex}.Mbwy4a_headingBlock{min-width:0}.Mbwy4a_eyebrow{color:var(--dsw-alias-label-tertiary);margin-bottom:5px;font-size:11px;line-height:16px}.Mbwy4a_title{margin:0;font-size:16px;font-weight:500;line-height:22px}.Mbwy4a_detail{margin:0 2px 8px}.Mbwy4a_footerActions{flex-shrink:0;align-items:center;gap:12px;display:flex}.Mbwy4a_pager{flex-shrink:0;align-items:center;gap:6px;display:flex}.Mbwy4a_progress{color:var(--dsw-alias-label-secondary);white-space:nowrap;word-spacing:-2px;padding:0 4px;font-size:14px;font-weight:500;line-height:24px}.Mbwy4a_iconButton{width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:999px;place-items:center;padding:0;display:grid}.Mbwy4a_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.Mbwy4a_iconButton:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.Mbwy4a_body{overscroll-behavior:contain;flex-direction:column;flex:auto;min-height:0;display:flex;overflow-y:auto}.Mbwy4a_options{flex-direction:column;gap:1px;margin:8px 0 0;padding:4px 12px;display:flex}.Mbwy4a_option{width:100%;min-height:40px;color:inherit;text-align:left;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:12px;flex-shrink:0;align-items:flex-start;gap:8px;padding:8px 12px 8px 8px;transition:background-color .12s,border-color .12s;display:flex}.Mbwy4a_option:hover:not(:disabled),.Mbwy4a_optionSelected{background:var(--dsw-alias-interactive-bg-hover)}.Mbwy4a_optionSelected{border-color:var(--dsw-alias-border-l2)}.Mbwy4a_option:disabled{cursor:default}.Mbwy4a_number{background:var(--dsw-alias-bg-overlay);width:20px;height:20px;color:var(--dsw-alias-label-secondary);border-radius:6px;flex:0 0 20px;place-items:center;margin-top:2px;font-size:12px;font-weight:500;line-height:18px;display:grid}.Mbwy4a_checkbox{flex:0 0 20px;place-items:center;width:20px;height:20px;margin-top:2px;display:grid}.Mbwy4a_checkbox:before{content:\"\";border:1px solid var(--dsw-alias-border-l4);border-radius:4px;grid-area:1/1;width:14px;height:14px;transition:background-color .12s,border-color .12s}.Mbwy4a_checkbox>svg{grid-area:1/1}.Mbwy4a_checkboxChecked{color:var(--dsw-alias-label-primary-foreground)}.Mbwy4a_checkboxChecked:before{border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-label-primary)}.Mbwy4a_optionCopy{flex:1;min-width:0}.Mbwy4a_optionLine{flex-wrap:wrap;align-items:baseline;gap:2px 6px;display:flex}.Mbwy4a_optionLabel{font-size:14px;font-weight:500;line-height:24px}.Mbwy4a_badge{background:var(--dsw-specific-sidebar-nav-item-active-accent);color:var(--dsw-alias-button-info-fill);border-radius:6px;padding:0 4px;font-size:11px;font-weight:600;line-height:18px}.Mbwy4a_description{color:var(--dsw-alias-label-tertiary);font-size:14px;font-weight:400;line-height:24px}.Mbwy4a_customRow{border:1px solid #0000;border-radius:12px;flex-shrink:0;align-items:flex-start;gap:8px;width:100%;min-height:40px;padding:8px 12px 8px 8px;transition:background-color .12s,border-color .12s;display:flex}.Mbwy4a_customRow:hover,.Mbwy4a_customRow:focus-within,.Mbwy4a_customRowActive{background:var(--dsw-alias-interactive-bg-hover)}.Mbwy4a_customRow:focus-within,.Mbwy4a_customRowActive{border-color:var(--dsw-alias-border-l2)}.Mbwy4a_field{--dsh-answer-field-padding:0;min-width:0;display:grid}.Mbwy4a_field>*{min-width:0;padding:var(--dsh-answer-field-padding);font:inherit;white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere;grid-area:1/1;font-size:14px;line-height:24px}.Mbwy4a_fieldMirror{box-sizing:content-box;visibility:hidden;max-height:144px;overflow:hidden}.Mbwy4a_fieldInput{resize:none;color:var(--dsw-alias-label-primary);caret-color:var(--dsw-alias-state-business-primary);background:0 0;border:none;outline:none;overflow-y:auto}.Mbwy4a_fieldInput::placeholder{color:var(--dsw-alias-label-caption)}.Mbwy4a_customInline{flex:1}.Mbwy4a_customBlock{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);--dsh-answer-field-padding:8px 12px;border-radius:10px;flex-shrink:0;min-height:64px;margin:0 12px}.Mbwy4a_customBlock:focus-within{border-color:var(--dsw-alias-state-business-primary)}.Mbwy4a_footer{flex-shrink:0;justify-content:space-between;align-items:center;gap:12px;margin-top:12px;padding:0 10px 0 18px;display:flex}.Mbwy4a_feedback{min-height:16px;color:var(--dsw-alias-state-error-primary);text-align:right;flex:1;font-size:11px;line-height:16px}@media (width<=720px){.Mbwy4a_card{border-radius:16px}.Mbwy4a_header{padding:10px 12px 0 18px}.Mbwy4a_options{padding:4px 8px}.Mbwy4a_title{font-size:15px;line-height:21px}.Mbwy4a_option,.Mbwy4a_customRow{padding:8px 6px}.Mbwy4a_footer{align-items:flex-end;padding:0 10px}.Mbwy4a_footerActions{flex-shrink:0}}@media (prefers-reduced-motion:reduce){.Mbwy4a_option,.Mbwy4a_customRow{transition:none}}";
234
234
  const tagId = "@deepseek-ai/dsh-client-ui-user-questions/QuestionComposer.module.css";
235
235
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
236
236
  const tag = document.createElement("style");
@@ -246,14 +246,17 @@ window.__ModuleLoader__.load({
246
246
  "cardMinimized": "Mbwy4a_cardMinimized",
247
247
  "checkbox": "Mbwy4a_checkbox",
248
248
  "checkboxChecked": "Mbwy4a_checkboxChecked",
249
- "customInput": "Mbwy4a_customInput",
249
+ "customBlock": "Mbwy4a_customBlock",
250
+ "customInline": "Mbwy4a_customInline",
250
251
  "customRow": "Mbwy4a_customRow",
251
252
  "customRowActive": "Mbwy4a_customRowActive",
252
- "customTextarea": "Mbwy4a_customTextarea",
253
253
  "description": "Mbwy4a_description",
254
254
  "detail": "Mbwy4a_detail",
255
255
  "eyebrow": "Mbwy4a_eyebrow",
256
256
  "feedback": "Mbwy4a_feedback",
257
+ "field": "Mbwy4a_field",
258
+ "fieldInput": "Mbwy4a_fieldInput",
259
+ "fieldMirror": "Mbwy4a_fieldMirror",
257
260
  "footer": "Mbwy4a_footer",
258
261
  "footerActions": "Mbwy4a_footerActions",
259
262
  "frame": "Mbwy4a_frame",
@@ -294,6 +297,42 @@ window.__ModuleLoader__.load({
294
297
  return event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
295
298
  }
296
299
  /**
300
+ * Auto-growing free-text answer: a textarea, so a long answer soft-wraps and
301
+ * Shift+Enter breaks a line, over a hidden mirror that owns the height.
302
+ *
303
+ * The mirror renders the draft plus a trailing newline in normal flow and so
304
+ * sizes the grid row (counting rows by '\n' cannot see soft wraps); the
305
+ * textarea shares that one cell and stretches to it, and `rows={1}` keeps the
306
+ * control's own intrinsic height out of the row sizing so the mirror alone
307
+ * decides. Past the mirror's cap the textarea scrolls itself — it is the only
308
+ * scrollport in the stack, there being no second glyph layer to keep aligned.
309
+ * Mirror and textarea MUST share font, line-height, padding and wrapping rules
310
+ * or the two heights diverge.
311
+ *
312
+ * @param props - field shape, draft text, and the field's event handlers.
313
+ * @returns The mirrored auto-growing field.
314
+ */
315
+ function AnswerField(props) {
316
+ return (0, react_jsx_runtime.jsxs)("div", {
317
+ className: clsx(QuestionComposer_module_css_default.field, props.variant === "inline" ? QuestionComposer_module_css_default.customInline : QuestionComposer_module_css_default.customBlock),
318
+ children: [(0, react_jsx_runtime.jsx)("div", {
319
+ "aria-hidden": true,
320
+ className: QuestionComposer_module_css_default.fieldMirror,
321
+ children: `${props.value}\n`
322
+ }), (0, react_jsx_runtime.jsx)("textarea", {
323
+ autoFocus: props.autoFocus,
324
+ className: QuestionComposer_module_css_default.fieldInput,
325
+ value: props.value,
326
+ disabled: props.disabled,
327
+ rows: 1,
328
+ placeholder: props.placeholder,
329
+ onFocus: props.onFocus,
330
+ onChange: props.onChange,
331
+ onKeyDown: props.onKeyDown
332
+ })]
333
+ });
334
+ }
335
+ /**
297
336
  * Composer takeover boundary; the carrier key keys local drafts, so a
298
337
  * same-request replay (same key, new carrier object) preserves them.
299
338
  *
@@ -538,21 +577,19 @@ window.__ModuleLoader__.load({
538
577
  className: QuestionComposer_module_css_default.number,
539
578
  "aria-hidden": "true",
540
579
  children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 12 })
541
- }), (0, react_jsx_runtime.jsx)("input", {
542
- type: "text",
543
- className: QuestionComposer_module_css_default.customInput,
580
+ }), (0, react_jsx_runtime.jsx)(AnswerField, {
581
+ variant: "inline",
544
582
  value: draft.custom,
545
583
  disabled: busy !== null,
546
584
  placeholder: t("custom.placeholder"),
547
585
  onChange: draftCustom,
548
586
  onKeyDown: continueFromCustom
549
587
  })]
550
- }) : (0, react_jsx_runtime.jsx)("textarea", {
588
+ }) : (0, react_jsx_runtime.jsx)(AnswerField, {
551
589
  autoFocus: !focusedQuestions.current.has(index),
552
- className: QuestionComposer_module_css_default.customTextarea,
590
+ variant: "block",
553
591
  value: draft.custom,
554
592
  disabled: busy !== null,
555
- rows: 2,
556
593
  placeholder: t("custom.placeholder"),
557
594
  onFocus: () => {
558
595
  focusedQuestions.current.add(index);
@@ -1657,6 +1657,32 @@ window.__ModuleLoader__.load({
1657
1657
  const groupExpansion = useStore((s) => s.groupExpansion);
1658
1658
  const sessionOrderByAccount = useStore((s) => s.sessionOrderByAccount);
1659
1659
  const sessionUpdatedAtByAccount = useStore((s) => s.sessionUpdatedAtByAccount);
1660
+ const currentBlankSessionId = useSessions((state) => {
1661
+ const current = state.current;
1662
+ return current !== void 0 && state.byId[current]?.blank === true ? current : void 0;
1663
+ });
1664
+ const currentBlankAccount = currentBlankSessionId === void 0 ? void 0 : workspaces.find((workspace) => workspace.sessionIds.includes(currentBlankSessionId))?.workspaceId ?? "";
1665
+ const promotedBlank = (0, react.useRef)(void 0);
1666
+ (0, react.useEffect)(() => {
1667
+ if (currentBlankSessionId === void 0 || currentBlankAccount === void 0) {
1668
+ promotedBlank.current = void 0;
1669
+ return;
1670
+ }
1671
+ if (promotedBlank.current?.sessionId === currentBlankSessionId && promotedBlank.current.accountKey === currentBlankAccount) return;
1672
+ promotedBlank.current = {
1673
+ sessionId: currentBlankSessionId,
1674
+ accountKey: currentBlankAccount
1675
+ };
1676
+ for (const accountKey of new Set([currentBlankAccount, FLAT_SESSION_ORDER_KEY])) {
1677
+ const previous = sessionOrderByAccount[accountKey] ?? [];
1678
+ actions.setSessionOrder(accountKey, [currentBlankSessionId, ...previous.filter((id) => id !== currentBlankSessionId)]);
1679
+ }
1680
+ }, [
1681
+ actions.setSessionOrder,
1682
+ currentBlankAccount,
1683
+ currentBlankSessionId,
1684
+ sessionOrderByAccount
1685
+ ]);
1660
1686
  (0, react.useEffect)(() => {
1661
1687
  if (workspacePhase !== "ready") return;
1662
1688
  actions.retainAccountKeys([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-edge",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-alpha.1",
4
4
  "description": "Run DeepSeek Harness on Cloudflare Workers as an independent community project",
5
5
  "author": "pawaca",
6
6
  "license": "MIT",
@@ -41,7 +41,7 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dshEdge": {
44
- "upstreamVersion": "0.1.0-rc.8"
44
+ "upstreamVersion": "0.1.1-rc.1"
45
45
  },
46
46
  "dependencies": {
47
47
  "@clack/prompts": "1.7.0",
@@ -54,27 +54,30 @@
54
54
  "@cloudflare/computer": "0.2.0",
55
55
  "@cloudflare/workers-types": "5.20260812.1",
56
56
  "@deepseek-ai/cordis": "4.0.1",
57
- "@deepseek-ai/dsh-agent": "0.1.0-rc.8",
58
- "@deepseek-ai/dsh-agent-loop": "0.1.0-rc.8",
59
- "@deepseek-ai/dsh-anonymous-user-id": "0.1.0-rc.8",
60
- "@deepseek-ai/dsh-brand": "0.1.0-rc.8",
61
- "@deepseek-ai/dsh-credentials": "0.1.0-rc.8",
62
- "@deepseek-ai/dsh-host-apiproxy": "0.1.0-rc.8",
63
- "@deepseek-ai/dsh-launch-environment": "0.1.0-rc.8",
64
- "@deepseek-ai/dsh-llm": "0.1.0-rc.8",
65
- "@deepseek-ai/dsh-llm-deepseek": "0.1.0-rc.8",
66
- "@deepseek-ai/dsh-session": "0.1.0-rc.8",
67
- "@deepseek-ai/dsh-session-persistence": "0.1.0-rc.8",
68
- "@deepseek-ai/dsh-session-query": "0.1.0-rc.8",
69
- "@deepseek-ai/dsh-session-title": "0.1.0-rc.8",
70
- "@deepseek-ai/dsh-settings": "0.1.0-rc.8",
71
- "@deepseek-ai/dsh-system-prompt": "0.1.0-rc.8",
72
- "@deepseek-ai/dsh-tool-call-timeout-policy": "0.1.0-rc.8",
73
- "@deepseek-ai/dsh-tool-web": "0.1.0-rc.8",
74
- "@deepseek-ai/dsh-tools": "0.1.0-rc.8",
75
- "@deepseek-ai/dsh-web": "0.1.0-rc.8",
76
- "@deepseek-ai/dsh-web-search-deepseek": "0.1.0-rc.8",
57
+ "@deepseek-ai/dsh-agent": "0.1.1-rc.1",
58
+ "@deepseek-ai/dsh-agent-loop": "0.1.1-rc.1",
59
+ "@deepseek-ai/dsh-anonymous-user-id": "0.1.1-rc.1",
60
+ "@deepseek-ai/dsh-attachment": "0.1.1-rc.1",
61
+ "@deepseek-ai/dsh-brand": "0.1.1-rc.1",
62
+ "@deepseek-ai/dsh-credentials": "0.1.1-rc.1",
63
+ "@deepseek-ai/dsh-host-apiproxy": "0.1.1-rc.1",
64
+ "@deepseek-ai/dsh-launch-environment": "0.1.1-rc.1",
65
+ "@deepseek-ai/dsh-llm": "0.1.1-rc.1",
66
+ "@deepseek-ai/dsh-llm-deepseek": "0.1.1-rc.1",
67
+ "@deepseek-ai/dsh-session": "0.1.1-rc.1",
68
+ "@deepseek-ai/dsh-session-persistence": "0.1.1-rc.1",
69
+ "@deepseek-ai/dsh-session-query": "0.1.1-rc.1",
70
+ "@deepseek-ai/dsh-session-title": "0.1.1-rc.1",
71
+ "@deepseek-ai/dsh-settings": "0.1.1-rc.1",
72
+ "@deepseek-ai/dsh-system-prompt": "0.1.1-rc.1",
73
+ "@deepseek-ai/dsh-tool-call-timeout-policy": "0.1.1-rc.1",
74
+ "@deepseek-ai/dsh-tool-web": "0.1.1-rc.1",
75
+ "@deepseek-ai/dsh-tools": "0.1.1-rc.1",
76
+ "@deepseek-ai/dsh-web": "0.1.1-rc.1",
77
+ "@deepseek-ai/dsh-web-search-deepseek": "0.1.1-rc.1",
77
78
  "just-bash": "3.3.0",
79
+ "fast-png": "8.0.0",
80
+ "jpeg-js": "0.4.4",
78
81
  "playwright": "^1.49.0",
79
82
  "typescript": "^6.0.3",
80
83
  "vitest": "^4.1.8",
package/scripts/cli.mjs CHANGED
@@ -166,6 +166,9 @@ export function createInstallerUi(
166
166
  `Account: ${summary.accountLabel}`,
167
167
  `Worker: ${summary.workerName}`,
168
168
  `Cost: ${summary.paid ? 'Workers Paid is required' : 'Works on Workers Free'}`,
169
+ `Images: ${summary.attachmentStorage === 'temporary-do'
170
+ ? 'stored in this instance (64 MiB limit)'
171
+ : 'stored privately in Cloudflare R2'}`,
169
172
  ...(command === 'upgrade' ? ['Existing Durable Object data is preserved.', 'The entered secrets replace the active values.'] : []),
170
173
  ].join('\n'), command === 'upgrade' ? 'Upgrade summary' : 'Installation summary')
171
174
  return await requireAnswer(await clack.confirm(withOutput({
@@ -30,6 +30,8 @@ export interface InstallerUi {
30
30
  accountLabel: string
31
31
  workerName: string
32
32
  paid: boolean
33
+ temporary: boolean
34
+ attachmentStorage: 'private-r2' | 'temporary-do'
33
35
  }): Promise<boolean>
34
36
  acceptTemporaryTerms(): Promise<boolean>
35
37
  deploymentStart?(message: string): void
@@ -58,6 +60,7 @@ export interface InstallResult {
58
60
  publicUrl: string
59
61
  versionId?: string
60
62
  account?: CloudflareAccount
63
+ attachmentStorage: 'private-r2' | 'temporary-do'
61
64
  claimUrl?: string
62
65
  mode: RuntimeMode
63
66
  ownerSecret: string
@@ -86,6 +89,17 @@ export function validateWorkerName(value: string): string | undefined
86
89
  export function validateOwnerSecret(value: string): string | undefined
87
90
  export function validateDeepSeekKey(value: string): string | undefined
88
91
  export function generateOwnerSecret(): string
92
+ export function attachmentBucketName(workerName: string): string
93
+ export function ensureR2Bucket(options: {
94
+ bucketName: string
95
+ runWrangler: (args: string[], options?: {
96
+ environment?: NodeJS.ProcessEnv
97
+ signal?: AbortSignal
98
+ }) => Promise<CommandResult>
99
+ environment?: NodeJS.ProcessEnv
100
+ profile?: string
101
+ signal?: AbortSignal
102
+ }): Promise<{ bucketName: string; created: boolean }>
89
103
  export function wranglerEnvironment(environment?: NodeJS.ProcessEnv): NodeJS.ProcessEnv
90
104
  export function unauthenticatedEnvironment(environment?: NodeJS.ProcessEnv): NodeJS.ProcessEnv
91
105
  export function wranglerDeployArgs(options: {
@@ -99,6 +113,17 @@ export function wranglerDeployArgs(options: {
99
113
  export function parseDeploymentOutput(source: string): { publicUrl: string; versionId?: string }
100
114
  export function parseClaimUrl(source: string): string | undefined
101
115
  export function parseWorkerExistence(result: CommandResult): boolean
116
+ export function detectExistingAttachmentStorage(options: {
117
+ workerName: string
118
+ mode: RuntimeMode
119
+ runWrangler: (args: string[], options?: {
120
+ environment?: NodeJS.ProcessEnv
121
+ signal?: AbortSignal
122
+ }) => Promise<CommandResult>
123
+ environment?: NodeJS.ProcessEnv
124
+ profile?: string
125
+ signal?: AbortSignal
126
+ }): Promise<'private-r2' | 'temporary-do'>
102
127
  export function truncateUtf8Tail(value: string, maxBytes: number): string
103
128
  export function createOutputForwarder(
104
129
  source: NodeJS.ReadableStream,
@@ -1,4 +1,4 @@
1
- import { randomBytes } from 'node:crypto'
1
+ import { createHash, randomBytes } from 'node:crypto'
2
2
  import { execFile } from 'node:child_process'
3
3
  import { chmod, mkdtemp, open, rm, writeFile } from 'node:fs/promises'
4
4
  import { createRequire } from 'node:module'
@@ -93,6 +93,7 @@ const SENSITIVE_ENV_KEY = /(KEY|PASSWORD|SECRET|TOKEN)/iu
93
93
  const WORKER_NAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u
94
94
  const CLAIM_URL = /https:\/\/dash\.cloudflare\.com\/claim-preview\?[^\s\u001b]+/u
95
95
  const MAX_CAPTURE_BYTES = 2 * 1024 * 1024
96
+ const ATTACHMENT_STORAGE_BINDING = 'DSH_EDGE_ATTACHMENT_STORAGE'
96
97
  const WINDOWS_ACL_TIMEOUT_MS = 30_000
97
98
  const WINDOWS_PRIVATE_DIRECTORY_SCRIPT = String.raw`
98
99
  $ErrorActionPreference = 'Stop'
@@ -232,6 +233,58 @@ export function generateOwnerSecret() {
232
233
  return randomBytes(32).toString('base64url')
233
234
  }
234
235
 
236
+ /** Derive one stable account-local R2 bucket name from the Worker service name. */
237
+ export function attachmentBucketName(workerName) {
238
+ if (validateWorkerName(workerName) !== undefined) throw new Error('Worker name is invalid.')
239
+ const suffix = '-attachments'
240
+ if (workerName.length + suffix.length <= 63) return `${workerName}${suffix}`
241
+ const digest = createHash('sha256').update(workerName).digest('hex').slice(0, 8)
242
+ const prefix = workerName.slice(0, 42).replace(/-+$/u, '')
243
+ return `${prefix}-${digest}${suffix}`
244
+ }
245
+
246
+ /** Create or reuse the permanent deployment's private R2 attachment bucket. */
247
+ export async function ensureR2Bucket({
248
+ bucketName,
249
+ runWrangler,
250
+ environment,
251
+ profile,
252
+ signal,
253
+ }) {
254
+ const infoArgs = ['r2', 'bucket', 'info', bucketName, '--json', ...profileArgs(profile)]
255
+ const initial = await runWrangler(infoArgs, { environment, signal })
256
+ if (initial.status === 0) {
257
+ requireR2BucketInfo(initial.stdout, bucketName)
258
+ return { bucketName, created: false }
259
+ }
260
+ const created = await runWrangler([
261
+ 'r2', 'bucket', 'create', bucketName, ...profileArgs(profile),
262
+ ], { environment, signal })
263
+ if (created.status === 0) return { bucketName, created: true }
264
+ // A concurrent installer may have won the create race; prove exact existence.
265
+ const recovered = await runWrangler(infoArgs, { environment, signal })
266
+ if (recovered.status === 0) {
267
+ requireR2BucketInfo(recovered.stdout, bucketName)
268
+ return { bucketName, created: false }
269
+ }
270
+ throw new Error(commandFailure(
271
+ `Could not create or access private R2 bucket "${bucketName}". Enable R2 for this Cloudflare account (or use a temporary preview), then retry`,
272
+ created,
273
+ ))
274
+ }
275
+
276
+ function requireR2BucketInfo(source, expectedName) {
277
+ let info
278
+ try {
279
+ info = JSON.parse(source)
280
+ } catch {
281
+ throw new Error('Wrangler returned malformed R2 bucket information.')
282
+ }
283
+ if (info === null || typeof info !== 'object' || info.name !== expectedName) {
284
+ throw new Error('Wrangler returned unexpected R2 bucket information.')
285
+ }
286
+ }
287
+
235
288
  /** Build the least-privilege environment used by authenticated Wrangler commands. */
236
289
  export function wranglerEnvironment(environment = process.env) {
237
290
  return pickEnvironment(environment, [
@@ -319,6 +372,94 @@ export function parseWorkerExistence(result) {
319
372
  throw new Error(commandFailure('Could not check whether the Worker already exists', result))
320
373
  }
321
374
 
375
+ /** Inspect active Worker versions so upgrades preserve the existing attachment backend. */
376
+ export async function detectExistingAttachmentStorage({
377
+ workerName,
378
+ mode,
379
+ runWrangler,
380
+ environment,
381
+ profile,
382
+ signal,
383
+ }) {
384
+ const args = [
385
+ '--name', workerName, '--json',
386
+ ...runtimeEnvironmentArgs(mode),
387
+ ...profileArgs(profile),
388
+ ]
389
+ const status = await runWrangler(['deployments', 'status', ...args], {
390
+ environment,
391
+ signal,
392
+ })
393
+ requireSuccess(status, 'Could not inspect the existing Worker deployment')
394
+ const versionIds = deploymentVersionIds(status.stdout)
395
+ const backends = new Set()
396
+ for (const versionId of versionIds) {
397
+ const version = await runWrangler([
398
+ 'versions', 'view', versionId, ...args,
399
+ ], { environment, signal })
400
+ requireSuccess(version, `Could not inspect existing Worker version ${versionId}`)
401
+ backends.add(versionAttachmentStorage(version.stdout))
402
+ }
403
+ if (backends.size !== 1) {
404
+ throw new Error('The active Worker versions use different attachment backends. Finish the existing rollout before upgrading.')
405
+ }
406
+ return backends.values().next().value
407
+ }
408
+
409
+ function deploymentVersionIds(source) {
410
+ let deployment
411
+ try {
412
+ deployment = JSON.parse(source)
413
+ } catch {
414
+ throw new Error('Wrangler returned malformed deployment status.')
415
+ }
416
+ if (!isRecord(deployment) || !Array.isArray(deployment.versions)) {
417
+ throw new Error('Wrangler returned unexpected deployment status.')
418
+ }
419
+ const ids = deployment.versions
420
+ .filter(version => isRecord(version) && typeof version.percentage === 'number'
421
+ && version.percentage > 0)
422
+ .map(version => version.version_id)
423
+ if (ids.length === 0 || ids.some(id => typeof id !== 'string' || id.length === 0)) {
424
+ throw new Error('Wrangler deployment status has no active Worker version.')
425
+ }
426
+ return [...new Set(ids)]
427
+ }
428
+
429
+ function versionAttachmentStorage(source) {
430
+ let version
431
+ try {
432
+ version = JSON.parse(source)
433
+ } catch {
434
+ throw new Error('Wrangler returned malformed Worker version details.')
435
+ }
436
+ const bindings = isRecord(version) && isRecord(version.resources)
437
+ ? version.resources.bindings
438
+ : undefined
439
+ if (!Array.isArray(bindings) || bindings.some(binding => !isRecord(binding))) {
440
+ throw new Error('Wrangler returned unexpected Worker version details.')
441
+ }
442
+ const attachment = bindings.filter(binding => binding.name === 'DSH_EDGE_ATTACHMENTS')
443
+ if (attachment.length > 1
444
+ || (attachment.length === 1 && attachment[0].type !== 'r2_bucket')) {
445
+ throw new Error('The existing Worker has an invalid attachment binding.')
446
+ }
447
+ const markers = bindings.filter(binding => binding.name === ATTACHMENT_STORAGE_BINDING)
448
+ if (markers.length > 1 || (markers.length === 1
449
+ && (markers[0].type !== 'plain_text'
450
+ || (markers[0].text !== 'temporary-do' && markers[0].text !== 'private-r2')))) {
451
+ throw new Error('The existing Worker has an invalid attachment storage marker.')
452
+ }
453
+ const marker = markers[0]?.text
454
+ if ((marker === 'temporary-do' && attachment.length !== 0)
455
+ || (marker === 'private-r2' && attachment.length !== 1)) {
456
+ throw new Error('The existing Worker attachment marker does not match its binding.')
457
+ }
458
+ // Releases before attachment support had neither binding nor marker. An
459
+ // authenticated upgrade can safely initialize those instances on private R2.
460
+ return marker ?? 'private-r2'
461
+ }
462
+
322
463
  /** Run the complete guided install with UI and Wrangler supplied as replaceable boundaries. */
323
464
  export async function installEdge({
324
465
  command = 'install',
@@ -380,6 +521,7 @@ export async function installEdge({
380
521
  : requireSelectedAccount(accountSelection, accounts)
381
522
 
382
523
  let workerName = DEFAULT_WORKER_NAME
524
+ let updatingExisting = false
383
525
  while (true) {
384
526
  workerName = await ui.workerName(workerName, validateWorkerName)
385
527
  if (temporary) break
@@ -394,15 +536,33 @@ export async function installEdge({
394
536
  }))
395
537
  if (command === 'upgrade') {
396
538
  if (!exists) throw new Error(`${workerName} does not exist in this account and runtime. Run dsh-edge install first.`)
539
+ updatingExisting = true
397
540
  break
398
541
  }
399
542
  if (!exists) break
400
543
  const action = await ui.workerConflict(workerName)
401
- if (action === 'update') break
544
+ if (action === 'update') {
545
+ updatingExisting = true
546
+ break
547
+ }
402
548
  if (action === 'cancel') throw new InstallCancelledError()
403
549
  workerName = `${workerName}-2`
404
550
  }
405
551
 
552
+ const commandEnvironment = temporary
553
+ ? unauthenticatedEnvironment(environment)
554
+ : accountEnvironment(profileEnvironment ?? environment, account.id)
555
+ const attachmentStorage = updatingExisting
556
+ ? await detectExistingAttachmentStorage({
557
+ workerName,
558
+ mode,
559
+ runWrangler,
560
+ environment: commandEnvironment,
561
+ profile,
562
+ signal,
563
+ })
564
+ : temporary ? 'temporary-do' : 'private-r2'
565
+
406
566
  const secretMode = await ui.selectOwnerSecretMode()
407
567
  const ownerSecret = secretMode === 'generate'
408
568
  ? generateOwnerSecret()
@@ -419,17 +579,34 @@ export async function installEdge({
419
579
  accountLabel: temporary ? 'Temporary account' : account.name,
420
580
  workerName,
421
581
  paid: mode === 'isolated',
582
+ temporary,
583
+ attachmentStorage,
422
584
  })
423
585
  if (!confirmed) throw new InstallCancelledError()
424
586
  if (temporary && !await ui.acceptTemporaryTerms()) {
425
587
  throw new InstallCancelledError()
426
588
  }
427
589
 
590
+ let bucketName
591
+ if (attachmentStorage === 'private-r2') {
592
+ bucketName = attachmentBucketName(workerName)
593
+ ui.step(`Preparing private image storage (${bucketName})…`)
594
+ await ensureR2Bucket({
595
+ bucketName,
596
+ runWrangler,
597
+ environment: commandEnvironment,
598
+ profile,
599
+ signal,
600
+ })
601
+ }
602
+
428
603
  temporaryDirectory = await createTemporaryDirectory()
429
604
  const secretsFile = join(temporaryDirectory, 'secrets.json')
430
605
  const configFile = join(temporaryDirectory, 'wrangler.json')
431
606
  const outputFile = join(temporaryDirectory, 'wrangler-output.ndjson')
432
- await writePrebuiltModeWranglerConfig(mode, configFile)
607
+ await writePrebuiltModeWranglerConfig(mode, configFile, {
608
+ ...bucketName === undefined ? {} : { r2BucketName: bucketName },
609
+ })
433
610
  await writeFile(secretsFile, JSON.stringify({
434
611
  DEEPSEEK_API_KEY: deepSeekKey,
435
612
  DSH_EDGE_ACCESS_KEY: ownerSecret,
@@ -439,9 +616,6 @@ export async function installEdge({
439
616
  ? 'Uploading the tested Worker release…'
440
617
  : 'Installing the tested Worker release…'
441
618
  ui.deploymentStart?.(deploymentMessage)
442
- const commandEnvironment = temporary
443
- ? unauthenticatedEnvironment(environment)
444
- : accountEnvironment(profileEnvironment ?? environment, account.id)
445
619
  if (temporary) commandEnvironment.XDG_CONFIG_HOME = temporaryDirectory
446
620
  commandEnvironment.WRANGLER_LOG_SANITIZE = 'true'
447
621
  commandEnvironment.WRANGLER_OUTPUT_FILE_PATH = outputFile
@@ -527,6 +701,7 @@ export async function installEdge({
527
701
  let result = {
528
702
  ...deployment,
529
703
  account,
704
+ attachmentStorage,
530
705
  claimUrl,
531
706
  mode,
532
707
  ownerSecret,
@@ -843,6 +1018,10 @@ function isTerminalSafe(value) {
843
1018
  return !CONTROL_CHARACTER.test(value) && !BIDI_CONTROL.test(value)
844
1019
  }
845
1020
 
1021
+ function isRecord(value) {
1022
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
1023
+ }
1024
+
846
1025
  /** Create a chunk-safe terminal filter that retains plain text only. */
847
1026
  export function createTerminalSanitizer() {
848
1027
  let state = 'text'
@@ -41,6 +41,10 @@ const repositoryRoot = fileURLToPath(new URL('../../../', import.meta.url))
41
41
  const packageRoot = fileURLToPath(new URL('../', import.meta.url))
42
42
  const standaloneRoot = join(packageRoot, 'standalone')
43
43
  const edgeManifest = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'))
44
+ const patchAudit = JSON.parse(readFileSync(join(standaloneRoot, 'patches', 'audit.json'), 'utf8'))
45
+ if (!Array.isArray(patchAudit) || patchAudit.length === 0) {
46
+ throw new Error('The retained patch audit must be a non-empty array.')
47
+ }
44
48
 
45
49
  function bundledComponents() {
46
50
  const invocation = resolvePnpmInvocation(process.env.npm_execpath ?? 'pnpm', [
@@ -212,7 +216,7 @@ function renderNotices(components) {
212
216
 
213
217
  ## DeepSeek Harness
214
218
 
215
- \`dsh-edge\` assembles published DeepSeek Harness packages and applies six version-bound adaptations to the pinned \`${edgeManifest.dshEdge.upstreamVersion}\` release. DeepSeek Harness remains under its upstream MIT license:
219
+ \`dsh-edge\` assembles published DeepSeek Harness packages and applies ${String(patchAudit.length)} version-bound adaptations to the pinned \`${edgeManifest.dshEdge.upstreamVersion}\` release. DeepSeek Harness remains under its upstream MIT license:
216
220
 
217
221
  \`\`\`text
218
222
  ${upstreamLicense}
@@ -11,6 +11,8 @@ const WORKER_ARTIFACTS = Object.freeze({
11
11
  direct: 'worker/direct/index.js',
12
12
  isolated: 'worker/isolated/index.js',
13
13
  })
14
+ const ATTACHMENT_BINDING = 'DSH_EDGE_ATTACHMENTS'
15
+ const ATTACHMENT_STORAGE_BINDING = 'DSH_EDGE_ATTACHMENT_STORAGE'
14
16
 
15
17
  /** Render one mode-specific build configuration from an already parsed source object. */
16
18
  export function renderParsedSourceModeWranglerConfig(mode, parsed, options = {}) {
@@ -34,6 +36,7 @@ export function renderParsedSourceModeWranglerConfig(mode, parsed, options = {})
34
36
  config.main = resolve(root, parsed.main)
35
37
  config.assets.directory = options.assetsDirectory ?? resolve(root, parsed.assets.directory)
36
38
  config.minify = true
39
+ applyAttachmentStorage(config, mode, options.r2BucketName)
37
40
  if (mode === 'direct') {
38
41
  config.alias = {
39
42
  ...config.alias,
@@ -63,9 +66,34 @@ export function renderParsedPrebuiltModeWranglerConfig(mode, parsed, options = {
63
66
  config.assets.directory = resolve(root, parsed.assets.directory)
64
67
  config.no_bundle = true
65
68
  config.find_additional_modules = false
69
+ applyAttachmentStorage(config, mode, options.r2BucketName)
66
70
  return `${JSON.stringify(config, undefined, 2)}\n`
67
71
  }
68
72
 
73
+ function applyAttachmentStorage(config, mode, bucketName) {
74
+ if (bucketName !== undefined && (typeof bucketName !== 'string' || bucketName.length === 0)) {
75
+ throw new Error('R2 attachment bucket name must be a non-empty string.')
76
+ }
77
+ const target = mode === 'direct' ? config : config.env?.isolated
78
+ if (!isRecord(target)) {
79
+ throw new Error('wrangler.jsonc must declare the isolated environment.')
80
+ }
81
+ if (target.vars !== undefined && !isRecord(target.vars)) {
82
+ throw new Error('wrangler.jsonc vars must be an object.')
83
+ }
84
+ target.vars = {
85
+ ...target.vars,
86
+ [ATTACHMENT_STORAGE_BINDING]: bucketName === undefined ? 'temporary-do' : 'private-r2',
87
+ }
88
+ if (bucketName === undefined) return
89
+ const binding = [{ binding: ATTACHMENT_BINDING, bucket_name: bucketName }]
90
+ if (mode === 'direct') {
91
+ config.r2_buckets = binding
92
+ return
93
+ }
94
+ target.r2_buckets = binding
95
+ }
96
+
69
97
  /** Return the released entrypoint for a runtime mode. */
70
98
  export function workerArtifactPath(mode, options = {}) {
71
99
  requireRuntimeMode(mode)
@@ -4,6 +4,7 @@ export interface WranglerConfigOptions {
4
4
  aliases?: Record<string, string>
5
5
  appDirectory?: string
6
6
  assetsDirectory?: string
7
+ r2BucketName?: string
7
8
  sourceConfigPath?: string
8
9
  }
9
10