@banana-peeljj12/dsh-trellis 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/lib/client.js +94 -4
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -27,6 +27,14 @@ window.__ModuleLoader__.load({
27
27
 
28
28
  let react = require('react');
29
29
 
30
+ /**
31
+ * Module-level client root context, captured in apply(ctx). Components (slot
32
+ * registrations) do not receive ctx directly, but need host services such as
33
+ * `sidebarRight` (native document preview) at click time — this is the
34
+ * bridge. Null until apply runs, which happens before any component mounts.
35
+ */
36
+ let clientCtx = null;
37
+
30
38
  // #region lib/types/client/trellis-settings-tab.js
31
39
 
32
40
  /** Settings namespace registered by the Host half (see lib/meta.js). */
@@ -36,7 +44,7 @@ window.__ModuleLoader__.load({
36
44
  const LOCALE_NS = 'settings.trellisWorkflow';
37
45
 
38
46
  /** Services required by this client half. */
39
- const inject = ['slots', 'locale', 'settingsScope'];
47
+ const inject = ['slots', 'locale', 'settingsScope', 'sidebarRight'];
40
48
 
41
49
  const zh = {
42
50
  tab: 'Trellis 工作流',
@@ -94,6 +102,7 @@ window.__ModuleLoader__.load({
94
102
  sendToChat: '推进任务',
95
103
  sendToChatSuccess: '已填入输入框,回车即可发送',
96
104
  sendToChatFailed: '自动填入失败,已复制到剪贴板',
105
+ openFailed: '打开失败,文件引用已复制到剪贴板',
97
106
  pendingVerification: '待验证',
98
107
  blocked: '阻塞',
99
108
  open: '打开',
@@ -171,6 +180,7 @@ window.__ModuleLoader__.load({
171
180
  sendToChat: 'Push to chat',
172
181
  sendToChatSuccess: 'Filled into the input — press Enter to send',
173
182
  sendToChatFailed: 'Auto-fill failed, copied to clipboard',
183
+ openFailed: 'Failed to open — file reference copied to clipboard',
174
184
  pendingVerification: 'Verifying',
175
185
  blocked: 'Blocked',
176
186
  open: 'Open',
@@ -942,8 +952,72 @@ window.__ModuleLoader__.load({
942
952
  return '@.trellis/tasks/' + task.slug + '/' + name;
943
953
  }
944
954
 
955
+ /**
956
+ * DSH-native file address for an artifact token. Mirrors the official
957
+ * `fileAddressFor` / `sessionFileAddress` contract
958
+ * (@deepseek-ai/dsh-client-ui-sidebar-files): the token's path (minus the
959
+ * leading `@`) is a workspace-relative path, so it maps to a session-scoped
960
+ * `dsh-resource://file/session/<sessionId>/<path>` address the right-sidebar
961
+ * document preview resolves against the session's workspace. Path segments
962
+ * are component-encoded, keeping `:` literal for drive letters.
963
+ * @param {string} sessionId the session whose workspace resolves the path.
964
+ * @param {string} token artifact token from {@link artifactToken}.
965
+ * @returns {string} `dsh-resource://file/session/...` address.
966
+ */
967
+ function artifactResourceUrl(sessionId, token) {
968
+ const path = (token || '').replace(/^@/, '');
969
+ const encodeSegment = (segment) => encodeURIComponent(segment).replace(/%3A/gi, ':');
970
+ const encodedPath = path
971
+ .replace(/\\/g, '/')
972
+ .split('/')
973
+ .map(encodeSegment)
974
+ .join('/');
975
+ return 'dsh-resource://file/session/' + encodeSegment(sessionId) + '/' + encodedPath;
976
+ }
977
+
978
+ /**
979
+ * Open an artifact file in the DSH-native document preview (right sidebar),
980
+ * mirroring how the official chat client opens `@file` references
981
+ * (`ctx.sidebarRight.openResource(...)`). Never touches the composer input.
982
+ * Falls back to copying the token to the clipboard when the right-sidebar
983
+ * service is unavailable or the open fails, so the click still yields
984
+ * something useful without polluting the input box.
985
+ * @param {object} ctx client root context (with `sidebarRight` injected).
986
+ * @param {string} sessionId the session owning the artifact's workspace.
987
+ * @param {string} token artifact token from {@link artifactToken}.
988
+ * @returns {boolean} true when opened natively; false when only copied.
989
+ */
990
+ function openArtifactFile(ctx, sessionId, token) {
991
+ try {
992
+ const sidebar = ctx && ctx.sidebarRight;
993
+ if (sidebar && typeof sidebar.openResource === 'function') {
994
+ sidebar.openResource(artifactResourceUrl(sessionId, token));
995
+ return true;
996
+ }
997
+ } catch {
998
+ /* fall through to clipboard copy */
999
+ }
1000
+ try {
1001
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1002
+ navigator.clipboard.writeText(token);
1003
+ } else {
1004
+ const ta = document.createElement('textarea');
1005
+ ta.value = token;
1006
+ ta.style.position = 'fixed';
1007
+ ta.style.opacity = '0';
1008
+ document.body.appendChild(ta);
1009
+ ta.select();
1010
+ document.execCommand('copy');
1011
+ document.body.removeChild(ta);
1012
+ }
1013
+ } catch {
1014
+ /* nothing more we can do */
1015
+ }
1016
+ return false;
1017
+ }
1018
+
945
1019
  function KanbanDetails(props) {
946
- const { task, t, active, busy, onActivate, onDeactivate, tracks } = props;
1020
+ const { task, t, active, busy, onActivate, onDeactivate, tracks, onOpenArtifact } = props;
947
1021
  // One-shot injection notice: 'ok' | 'fail' | null, auto-clears.
948
1022
  const [notice, setNotice] = react.useState(null);
949
1023
  react.useEffect(() => {
@@ -1276,7 +1350,11 @@ window.__ModuleLoader__.load({
1276
1350
  textAlign: 'center',
1277
1351
  },
1278
1352
  },
1279
- notice === 'ok' ? t('sendToChatSuccess') : t('sendToChatFailed'),
1353
+ notice === 'ok'
1354
+ ? t('sendToChatSuccess')
1355
+ : notice === 'open-fail'
1356
+ ? t('openFailed')
1357
+ : t('sendToChatFailed'),
1280
1358
  )
1281
1359
  : null,
1282
1360
  ),
@@ -1336,7 +1414,13 @@ window.__ModuleLoader__.load({
1336
1414
  'button',
1337
1415
  {
1338
1416
  type: 'button',
1339
- onClick: () => push(artifactToken(task, name)),
1417
+ onClick: () => {
1418
+ const token = artifactToken(task, name);
1419
+ const opened = onOpenArtifact ? onOpenArtifact(token) : false;
1420
+ // Notice only on failure: a successful native open is
1421
+ // visible in the right sidebar already, so no toast.
1422
+ setNotice(opened ? null : 'open-fail');
1423
+ },
1340
1424
  title: t('open') + ' ' + artifactToken(task, name),
1341
1425
  style: {
1342
1426
  display: 'inline-flex',
@@ -1814,6 +1898,7 @@ window.__ModuleLoader__.load({
1814
1898
  const {
1815
1899
  board,
1816
1900
  t,
1901
+ sessionId,
1817
1902
  selected,
1818
1903
  onSelect,
1819
1904
  busy,
@@ -1823,6 +1908,7 @@ window.__ModuleLoader__.load({
1823
1908
  onFilterChange,
1824
1909
  onClose,
1825
1910
  initialViewMode,
1911
+ onOpenArtifact,
1826
1912
  } = props;
1827
1913
  const [query, setQuery] = react.useState('');
1828
1914
  const [viewMode, setViewMode] = react.useState(initialViewMode === 'list' ? 'list' : 'lanes'); // 'lanes' | 'list'
@@ -2424,6 +2510,7 @@ window.__ModuleLoader__.load({
2424
2510
  onActivate: () => onActivate(selectedTask.slug),
2425
2511
  onDeactivate,
2426
2512
  tracks,
2513
+ onOpenArtifact: (token) => openArtifactFile(clientCtx, sessionId, token),
2427
2514
  }),
2428
2515
  ),
2429
2516
  ),
@@ -2812,6 +2899,7 @@ window.__ModuleLoader__.load({
2812
2899
  ? react.createElement(KanbanExpandedModal, {
2813
2900
  board,
2814
2901
  t,
2902
+ sessionId,
2815
2903
  selected,
2816
2904
  onSelect: setSelected,
2817
2905
  busy,
@@ -2820,6 +2908,7 @@ window.__ModuleLoader__.load({
2820
2908
  filter,
2821
2909
  onFilterChange: setFilter,
2822
2910
  onClose: closeExpanded,
2911
+ onOpenArtifact: (token) => openArtifactFile(clientCtx, sessionId, token),
2823
2912
  })
2824
2913
  : null;
2825
2914
 
@@ -2877,6 +2966,7 @@ window.__ModuleLoader__.load({
2877
2966
  // #endregion
2878
2967
 
2879
2968
  function apply(ctx) {
2969
+ clientCtx = ctx;
2880
2970
  ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), 'trellis-workflow: dictionaries');
2881
2971
  const t = ctx.locale.bind(LOCALE_NS);
2882
2972
  // Bound once on this plugin's fiber: the scope's disposer follows the fiber.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@banana-peeljj12/dsh-trellis",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Self-contained Trellis workflow trigger for DeepSeek Harness: per-turn breadcrumb injection via agent/pre-step, a bundled skill provider, and DSH-native task/phase state resolution. No Trellis AGPL code is vendored; workflow semantics are rewritten and licensed MIT.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",