@xirang-ai/dsh-teamflow 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
@@ -1,4 +1,4 @@
1
- /* @xirang-ai/dsh-teamflow 0.1.1 — generated by scripts/build.mjs, do not edit. */
1
+ /* @xirang-ai/dsh-teamflow 0.1.3 — generated by scripts/build.mjs, do not edit. */
2
2
  window.__ModuleLoader__.load({
3
3
  id: "@xirang-ai/dsh-teamflow",
4
4
  factory: (require) => {
@@ -341,6 +341,7 @@ window.__ModuleLoader__.load({
341
341
  "delete.statusMissing": "—",
342
342
  "delete.summary": "{total} record(s), {deletable} deletable",
343
343
  "detail.branch": "Branch",
344
+ "detail.bugDiscussion": "Discuss this bug in a new session",
344
345
  "detail.bugBody": "What happened",
345
346
  "detail.completedAt": "Completed",
346
347
  "detail.description": "Description",
@@ -356,6 +357,7 @@ window.__ModuleLoader__.load({
356
357
  "detail.discussionNewDetail": "Discuss the detailed design in a new conversation",
357
358
  "detail.discussionNewSplit": "Split Stories in a new conversation",
358
359
  "detail.discussionOpen": "Open",
360
+ "detail.storyDiscussion": "Discuss this story in a new session",
359
361
  "detail.discussionStarting": "Creating conversation…",
360
362
  "detail.docs": "Detailed design",
361
363
  "detail.docsEmpty": "No detailed design written yet.",
@@ -424,6 +426,7 @@ window.__ModuleLoader__.load({
424
426
  "page.retry": "Retry",
425
427
  "page.subtitle": "Defects come first, then running work, work waiting to start, and requirements still in discussion.",
426
428
  "page.title": "Xirang Workbench",
429
+ "page.version": "v{version}",
427
430
  panel: "Xirang Workbench",
428
431
  "remote.bugId.empty": "A bug id is required.",
429
432
  "remote.doc.notFound": "Detailed-design document {id} does not exist.",
@@ -679,6 +682,7 @@ window.__ModuleLoader__.load({
679
682
  "delete.statusMissing": "—",
680
683
  "delete.summary": "共 {total} 条,可删除 {deletable} 条",
681
684
  "detail.branch": "分支",
685
+ "detail.bugDiscussion": "在新会话中进行缺陷讨论",
682
686
  "detail.bugBody": "缺陷说明",
683
687
  "detail.completedAt": "完成时间",
684
688
  "detail.description": "故事说明",
@@ -694,6 +698,7 @@ window.__ModuleLoader__.load({
694
698
  "detail.discussionNewDetail": "在新会话中进行详细设计讨论",
695
699
  "detail.discussionNewSplit": "在新会话中进行故事拆分",
696
700
  "detail.discussionOpen": "打开",
701
+ "detail.storyDiscussion": "在新会话中进行故事讨论",
697
702
  "detail.discussionStarting": "正在创建会话…",
698
703
  "detail.docs": "详细设计",
699
704
  "detail.docsEmpty": "还没有写详细设计。",
@@ -762,6 +767,7 @@ window.__ModuleLoader__.load({
762
767
  "page.retry": "重试",
763
768
  "page.subtitle": "缺陷优先;其下依次是执行中的需求与故事、待开始的工作,以及仍在讨论的需求。",
764
769
  "page.title": "息壤工作台",
770
+ "page.version": "v{version}",
765
771
  panel: "息壤工作台",
766
772
  "remote.bugId.empty": "缺少缺陷编号。",
767
773
  "remote.doc.notFound": "详细设计 {id} 不存在。",
@@ -897,6 +903,7 @@ window.__ModuleLoader__.load({
897
903
  cancelStory: "cancelStory",
898
904
  completeBug: "completeBug",
899
905
  prepareRequirementSession: "prepareRequirementSession",
906
+ prepareDiscussionSession: "prepareDiscussionSession",
900
907
  sessionPermissionTier: "sessionPermissionTier",
901
908
  previewDelete: "previewDelete",
902
909
  deleteRecords: "deleteRecords",
@@ -1034,6 +1041,21 @@ window.__ModuleLoader__.load({
1034
1041
  ]),
1035
1042
  result: JSON_CODEC
1036
1043
  }),
1044
+ Object.freeze({
1045
+ id: `${PACKAGE_NAME}#${METHODS.prepareDiscussionSession}`,
1046
+ service: SERVICE_KEY,
1047
+ namespace: NAMESPACE,
1048
+ method: METHODS.prepareDiscussionSession,
1049
+ invocation: { kind: "direct" },
1050
+ parameters: Object.freeze([
1051
+ parameter("subjectType", "Discussion subject: requirement, story or bug"),
1052
+ parameter("subjectId", "Requirement, story or defect the discussion belongs to"),
1053
+ parameter("sessionId", "Newly created conversation to link"),
1054
+ parameter("kind", "Discussion kind: discuss for stories and defects, the requirement kinds otherwise"),
1055
+ LOCALE_PARAMETER
1056
+ ]),
1057
+ result: JSON_CODEC
1058
+ }),
1037
1059
  Object.freeze({
1038
1060
  id: `${PACKAGE_NAME}#${METHODS.sessionPermissionTier}`,
1039
1061
  service: SERVICE_KEY,
@@ -1151,6 +1173,15 @@ window.__ModuleLoader__.load({
1151
1173
  }
1152
1174
  return ["【继续讨论需求与概要设计】", "", "我的补充:"].join("\n");
1153
1175
  }
1176
+ var STORY_DISCUSSION_PRESET = "xirang-story";
1177
+ var BUG_DISCUSSION_PRESET = "xirang-bug";
1178
+ function presetForRecordDiscussion(kind) {
1179
+ return kind === "bug" ? BUG_DISCUSSION_PRESET : STORY_DISCUSSION_PRESET;
1180
+ }
1181
+ function buildRecordDiscussionPrompt(kind, title) {
1182
+ const heading = kind === "bug" ? "【继续讨论缺陷】" : "【继续讨论故事】";
1183
+ return [heading, title, "", "我的补充:"].join("\n");
1184
+ }
1154
1185
 
1155
1186
  // src/client/composer-kind.ts
1156
1187
  var COMPOSER_KINDS = [...CREATE_KINDS, "design", "detail", "split"];
@@ -1213,6 +1244,14 @@ window.__ModuleLoader__.load({
1213
1244
  .tf-head { display: flex; align-items: flex-start; gap: 16px; padding: 20px 0 14px; }
1214
1245
  .tf-head-main { min-width: 0; flex: 1 1 auto; }
1215
1246
  .tf-title { margin: 0; font-size: 18px; font-weight: 650; letter-spacing: -0.01em; }
1247
+ .tf-version {
1248
+ margin-left: 7px;
1249
+ font-size: 11px;
1250
+ font-weight: 500;
1251
+ color: var(--dsw-alias-label-tertiary, inherit);
1252
+ font-variant-numeric: tabular-nums;
1253
+ white-space: nowrap;
1254
+ }
1216
1255
  .tf-sub { margin: 3px 0 0; font-size: 12px; color: var(--dsw-alias-label-tertiary, inherit); }
1217
1256
  .tf-head-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; padding-top: 2px; }
1218
1257
  .tf-error {
@@ -2886,6 +2925,13 @@ window.__ModuleLoader__.load({
2886
2925
  const pad = (value2) => String(value2).padStart(2, "0");
2887
2926
  return `${pad(at.getMonth() + 1)}-${pad(at.getDate())} ${pad(at.getHours())}:${pad(at.getMinutes())}`;
2888
2927
  }
2928
+ function discussionNodes(sessions) {
2929
+ return sessions.map((session) => ({
2930
+ sessionId: session.sessionId,
2931
+ kind: session.kind,
2932
+ at: session.linkedAt ?? session.preparedAt ?? session.createdAt
2933
+ }));
2934
+ }
2889
2935
  var EMPTY_BOARD = { requirements: [], standalone: [], bugs: [] };
2890
2936
  var STORY_RUNNING = ["developing", "self_testing"];
2891
2937
  var STORY_DONE = ["done", "completed", "cancelled"];
@@ -3016,7 +3062,8 @@ window.__ModuleLoader__.load({
3016
3062
  acceptance: parsed.acceptance,
3017
3063
  bodyMissing: story.bodyMissing,
3018
3064
  ...last === void 0 ? {} : { lastRun: { status: last.status, at: last.at } },
3019
- runs
3065
+ runs,
3066
+ sessions: discussionNodes(story.sessions)
3020
3067
  };
3021
3068
  if (story.requirementId === null) {
3022
3069
  standalone.push({ kind: "story", id: story.id, workspaceId: node.workspaceId, story: node });
@@ -3037,12 +3084,8 @@ window.__ModuleLoader__.load({
3037
3084
  const finished = stories.length > 0 && stories.every((story) => story.status === "completed");
3038
3085
  const state = requirement.status === "archived" ? "done" : running ? "running" : requirement.status === "completed" || finished ? "done" : "ready";
3039
3086
  const branch = runs.find((run) => run.branch.length > 0)?.branch;
3040
- const sessions = snapshot.requirementSessions.filter((session) => session.requirementId === requirement.id).map(
3041
- (session) => ({
3042
- sessionId: session.sessionId,
3043
- kind: session.kind,
3044
- at: session.linkedAt ?? session.preparedAt ?? session.createdAt
3045
- })
3087
+ const sessions = discussionNodes(
3088
+ snapshot.requirementSessions.filter((session) => session.requirementId === requirement.id)
3046
3089
  );
3047
3090
  const latestDesign = sessions.find((session) => session.kind === "design");
3048
3091
  return {
@@ -3081,7 +3124,8 @@ window.__ModuleLoader__.load({
3081
3124
  updatedAt: bug.updatedAt,
3082
3125
  ...last?.sessionId === void 0 ? {} : { sessionId: last.sessionId },
3083
3126
  ...bug.createSessionId === null ? {} : { createSessionId: bug.createSessionId },
3084
- runs
3127
+ runs,
3128
+ sessions: discussionNodes(bug.sessions)
3085
3129
  };
3086
3130
  });
3087
3131
  return { requirements, standalone, bugs };
@@ -3206,6 +3250,9 @@ window.__ModuleLoader__.load({
3206
3250
  return title !== void 0 && title.trim() !== "" ? title : sessionId;
3207
3251
  }
3208
3252
 
3253
+ // src/client/version.ts
3254
+ var PLUGIN_VERSION = "0.1.3";
3255
+
3209
3256
  // src/dev.ts
3210
3257
  var DEV_PRESET = "xirang-dev";
3211
3258
  function buildDevPrompt(story, requirement) {
@@ -14108,6 +14155,7 @@ window.__ModuleLoader__.load({
14108
14155
  const [createBusy, setCreateBusy] = (0, import_react10.useState)(false);
14109
14156
  const [createError, setCreateError] = (0, import_react10.useState)(null);
14110
14157
  const [discussionBusy, setDiscussionBusy] = (0, import_react10.useState)(null);
14158
+ const [recordDiscussionBusy, setRecordDiscussionBusy] = (0, import_react10.useState)(null);
14111
14159
  const [cleanup, setCleanup] = (0, import_react10.useState)(false);
14112
14160
  const [selected2, setSelected] = (0, import_react10.useState)(/* @__PURE__ */ new Set());
14113
14161
  const [confirm, setConfirm] = (0, import_react10.useState)(null);
@@ -14611,14 +14659,59 @@ window.__ModuleLoader__.load({
14611
14659
  },
14612
14660
  [data, openConversation2, showToast, t]
14613
14661
  );
14662
+ const startRecordDiscussion = (0, import_react10.useCallback)(
14663
+ (subject) => {
14664
+ const open = openConversation2;
14665
+ if (open === void 0) {
14666
+ showToast(t("toast.startUnavailable"));
14667
+ return;
14668
+ }
14669
+ setRecordDiscussionBusy(subject.kind);
14670
+ open(subject.workspaceId, buildRecordDiscussionPrompt(subject.kind, subject.title), {
14671
+ agentPreset: presetForRecordDiscussion(subject.kind),
14672
+ autoSend: false,
14673
+ composerKind: subject.kind
14674
+ }).then(async (outcome) => {
14675
+ if (outcome.status === "unavailable") {
14676
+ if (mounted.current) {
14677
+ showToast(outcome.message ?? t("toast.discussionFailed", { title: subject.title }));
14678
+ }
14679
+ return;
14680
+ }
14681
+ if (outcome.sessionId === void 0) {
14682
+ if (mounted.current) {
14683
+ showToast(t("toast.discussionDrafted", { title: subject.title }));
14684
+ }
14685
+ return;
14686
+ }
14687
+ try {
14688
+ await data.prepareDiscussionSession(subject.kind, subject.id, outcome.sessionId, "discuss");
14689
+ if (!mounted.current) return;
14690
+ showToast(
14691
+ outcome.status === "drafted" ? t("toast.discussionDrafted", { title: subject.title }) : t("toast.discussionStarted", { title: subject.title })
14692
+ );
14693
+ } catch (failure) {
14694
+ if (!mounted.current) return;
14695
+ showToast(
14696
+ t("toast.discussionLinkFailed", {
14697
+ title: subject.title,
14698
+ message: failureMessage(failure)
14699
+ })
14700
+ );
14701
+ }
14702
+ }).catch(() => {
14703
+ if (mounted.current) showToast(t("toast.discussionFailed", { title: subject.title }));
14704
+ }).finally(() => {
14705
+ if (mounted.current) setRecordDiscussionBusy(null);
14706
+ });
14707
+ },
14708
+ [data, openConversation2, showToast, t]
14709
+ );
14614
14710
  const requirementAction = (0, import_react10.useCallback)(
14615
14711
  (requirement) => {
14616
14712
  if (requirement.state === "running") {
14617
14713
  return { label: t("row.open"), tone: "live", icon: ICONS.live };
14618
14714
  }
14619
- if (requirement.state === "done") {
14620
- return { label: t("row.history"), tone: "ghost", icon: ICONS.ghost };
14621
- }
14622
14715
  const lone = loneReadyStory(requirement);
14623
14716
  return lone === void 0 ? null : { label: t("row.start"), tone: "soft", icon: ICONS.soft };
14624
14717
  },
@@ -14713,11 +14806,7 @@ window.__ModuleLoader__.load({
14713
14806
  }
14714
14807
  };
14715
14808
  const storyAction = (0, import_react10.useCallback)(
14716
- (story) => {
14717
- if (story.state === "running") return { label: t("row.open"), tone: "live", icon: ICONS.live };
14718
- if (story.state === "done") return { label: t("row.history"), tone: "ghost", icon: ICONS.ghost };
14719
- return { label: t("row.start"), tone: "soft", icon: ICONS.soft };
14720
- },
14809
+ (story) => story.state === "ready" ? { label: t("row.start"), tone: "soft", icon: ICONS.soft } : null,
14721
14810
  [t]
14722
14811
  );
14723
14812
  const runStoryAction = (0, import_react10.useCallback)(
@@ -14998,7 +15087,10 @@ window.__ModuleLoader__.load({
14998
15087
  view.kind === "list" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
14999
15088
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("header", { className: "tf-head", children: [
15000
15089
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tf-head-main", children: [
15001
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { className: "tf-title", children: t("page.title") }),
15090
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("h1", { className: "tf-title", children: [
15091
+ t("page.title"),
15092
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "tf-version", children: t("page.version", { version: PLUGIN_VERSION }) })
15093
+ ] }),
15002
15094
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-sub", children: t("page.subtitle") })
15003
15095
  ] }),
15004
15096
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tf-head-actions", children: [
@@ -15243,7 +15335,14 @@ window.__ModuleLoader__.load({
15243
15335
  onDelete: () => openDelete([{ kind: "bug", id: activeBug.id }]),
15244
15336
  onComplete: () => setDecision({ kind: "complete-bug", bug: activeBug, busy: false, error: null }),
15245
15337
  onStartFix: (tier) => startBugFix(activeBug, tier),
15246
- onCancelFix: () => cancelBugFix(activeBug)
15338
+ onCancelFix: () => cancelBugFix(activeBug),
15339
+ onNewDiscussion: () => startRecordDiscussion({
15340
+ kind: "bug",
15341
+ id: activeBug.id,
15342
+ title: activeBug.title,
15343
+ workspaceId: activeBug.workspaceId
15344
+ }),
15345
+ discussionBusy: recordDiscussionBusy === "bug"
15247
15346
  }
15248
15347
  ) : null,
15249
15348
  view.kind === "story" && activeStory !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -15263,7 +15362,14 @@ window.__ModuleLoader__.load({
15263
15362
  onCancelRun: () => setDecision({ kind: "cancel-run", story: activeStory, busy: false, error: null }),
15264
15363
  onOpenSession: (sessionId) => openSession(sessionId),
15265
15364
  onOpenPath: openFile,
15266
- onMissing: (path) => showToast(t("doc.linkMissing", { path }))
15365
+ onMissing: (path) => showToast(t("doc.linkMissing", { path })),
15366
+ onNewDiscussion: () => startRecordDiscussion({
15367
+ kind: "story",
15368
+ id: activeStory.id,
15369
+ title: activeStory.title,
15370
+ workspaceId: activeStory.workspaceId
15371
+ }),
15372
+ discussionBusy: recordDiscussionBusy === "story"
15267
15373
  }
15268
15374
  ) : null
15269
15375
  ] }) }),
@@ -16140,6 +16246,8 @@ window.__ModuleLoader__.load({
16140
16246
  onComplete,
16141
16247
  onStartFix,
16142
16248
  onCancelFix,
16249
+ onNewDiscussion,
16250
+ discussionBusy = false,
16143
16251
  readOnly = false
16144
16252
  }) {
16145
16253
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
@@ -16166,27 +16274,42 @@ window.__ModuleLoader__.load({
16166
16274
  readOnly ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { className: "tf-btn tf-danger", "data-tone": "ghost", type: "button", onClick: onDelete, style: { flex: "0 0 auto" }, children: [
16167
16275
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TrashGlyph, {}),
16168
16276
  t("delete.action")
16169
- ] }),
16170
- readOnly || bug.sessionId === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
16171
- "button",
16172
- {
16173
- className: "tf-btn",
16174
- "data-tone": "live",
16175
- type: "button",
16176
- onClick: () => onOpenSession(bug.sessionId),
16177
- style: { flex: "0 0 auto" },
16178
- children: [
16179
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ChatGlyph, {}),
16180
- t("row.open")
16181
- ]
16182
- }
16183
- )
16277
+ ] })
16184
16278
  ] }),
16185
16279
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tf-grid", children: [
16186
16280
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
16187
16281
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
16188
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionHead, { title: t("detail.bugBody") }),
16189
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "tf-doc-body", children: bug.bodyMissing ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-card-hint", children: t("body.missingGeneric") }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarkdownBody, { text: bug.content.join("\n"), t }) })
16282
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
16283
+ SectionHead,
16284
+ {
16285
+ title: t("detail.bugBody"),
16286
+ action: readOnly || onNewDiscussion === void 0 ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
16287
+ "button",
16288
+ {
16289
+ className: "tf-btn tf-btn-sm",
16290
+ "data-tone": "soft",
16291
+ type: "button",
16292
+ disabled: discussionBusy,
16293
+ onClick: onNewDiscussion,
16294
+ children: [
16295
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PlusGlyph, {}),
16296
+ discussionBusy ? t("detail.discussionStarting") : t("detail.bugDiscussion")
16297
+ ]
16298
+ }
16299
+ )
16300
+ }
16301
+ ),
16302
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "tf-doc-body", children: bug.bodyMissing ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-card-hint", children: t("body.missingGeneric") }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MarkdownBody, { text: bug.content.join("\n"), t }) }),
16303
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
16304
+ DiscussionSessions,
16305
+ {
16306
+ t,
16307
+ sessions: bug.sessions,
16308
+ sessionTitle,
16309
+ onOpenSession,
16310
+ readOnly
16311
+ }
16312
+ )
16190
16313
  ] }),
16191
16314
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
16192
16315
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -16262,6 +16385,8 @@ window.__ModuleLoader__.load({
16262
16385
  onOpenSession,
16263
16386
  onOpenPath,
16264
16387
  onMissing,
16388
+ onNewDiscussion,
16389
+ discussionBusy = false,
16265
16390
  readOnly = false
16266
16391
  }) {
16267
16392
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
@@ -16311,7 +16436,26 @@ window.__ModuleLoader__.load({
16311
16436
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "tf-grid", children: [
16312
16437
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
16313
16438
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
16314
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SectionHead, { title: t("detail.storyBody") }),
16439
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
16440
+ SectionHead,
16441
+ {
16442
+ title: t("detail.storyBody"),
16443
+ action: readOnly || onNewDiscussion === void 0 ? void 0 : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
16444
+ "button",
16445
+ {
16446
+ className: "tf-btn tf-btn-sm",
16447
+ "data-tone": "soft",
16448
+ type: "button",
16449
+ disabled: discussionBusy,
16450
+ onClick: onNewDiscussion,
16451
+ children: [
16452
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PlusGlyph, {}),
16453
+ discussionBusy ? t("detail.discussionStarting") : t("detail.storyDiscussion")
16454
+ ]
16455
+ }
16456
+ )
16457
+ }
16458
+ ),
16315
16459
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "tf-doc-body", children: story.bodyMissing ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-card-hint", style: { margin: 0 }, children: t("body.missing", { path: story.path }) }) : story.body.trim() === "" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "tf-card-hint", style: { margin: 0 }, children: t("detail.noDescription") }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
16316
16460
  MarkdownBody,
16317
16461
  {
@@ -16321,7 +16465,17 @@ window.__ModuleLoader__.load({
16321
16465
  onOpenPath,
16322
16466
  onMissing
16323
16467
  }
16324
- ) })
16468
+ ) }),
16469
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
16470
+ DiscussionSessions,
16471
+ {
16472
+ t,
16473
+ sessions: story.sessions,
16474
+ sessionTitle,
16475
+ onOpenSession,
16476
+ readOnly
16477
+ }
16478
+ )
16325
16479
  ] }),
16326
16480
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "tf-card", children: [
16327
16481
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -17091,6 +17245,9 @@ window.__ModuleLoader__.load({
17091
17245
  prepareRequirementSession: async (requirementId, sessionId, kind) => callRemote(
17092
17246
  (await ready.promise).prepareRequirementSession(requirementId, sessionId, kind, locale())
17093
17247
  ),
17248
+ prepareDiscussionSession: async (subjectType, subjectId, sessionId, kind) => callRemote(
17249
+ (await ready.promise).prepareDiscussionSession(subjectType, subjectId, sessionId, kind, locale())
17250
+ ),
17094
17251
  sessionPermissionTier: async (sessionId) => callRemote((await ready.promise).sessionPermissionTier(sessionId, locale())),
17095
17252
  previewDelete: async (targets) => callRemote((await ready.promise).previewDelete(targets, locale())),
17096
17253
  deleteRecords: async (targets) => callRemote((await ready.promise).deleteRecords(targets, locale())),
package/lib/index.d.ts CHANGED
@@ -16,6 +16,11 @@ export interface Config {
16
16
  * which is the only location this plugin owns.
17
17
  */
18
18
  dataDir: string;
19
+ /**
20
+ * Document root override. Empty uses `<dataDir>/docs`, next to the database;
21
+ * relative paths resolve against the working directory and `~/` is expanded.
22
+ */
23
+ docsDir: string;
19
24
  /** SQLite busy timeout in milliseconds; a second instance waits this long for the write lock. */
20
25
  busyTimeoutMs: number;
21
26
  }
@@ -32,7 +37,8 @@ export declare function apply(ctx: Context, config: Config): void;
32
37
  export type { TeamflowDatabase, StartupReport, Role } from './store/db.ts';
33
38
  export type { SchemaComment } from './store/schema.ts';
34
39
  export { WorkflowStore } from './store/workflow.ts';
35
- export { DOC_ROOT, docPath, ensureDocTree, resolveDocPath } from './store/docs.ts';
40
+ export { docPath, ensureDocTree, resolveDocPath } from './store/docs.ts';
41
+ export { DOCS_DIRNAME, defaultDocsDir, resolveDocsDir } from './store/paths.ts';
36
42
  export type { DocKind as DocFileKind } from './store/docs.ts';
37
43
  export type { WorkflowStoreOptions } from './store/workflow.ts';
38
44
  export { WorkflowError } from './store/model.ts';
package/lib/index.js CHANGED
@@ -7,6 +7,9 @@ export const Config = Schema.object({
7
7
  dataDir: Schema.string()
8
8
  .default('')
9
9
  .description('Workbench data directory. Empty uses $DSH_HOME/xirang-ai/dsh-teamflow.'),
10
+ docsDir: Schema.string()
11
+ .default('')
12
+ .description('Document root for requirement, design, story and bug bodies. Empty uses <dataDir>/docs; relative paths and ~/ are supported.'),
10
13
  busyTimeoutMs: Schema.number()
11
14
  .min(0)
12
15
  .max(60_000)
@@ -28,11 +31,13 @@ export function apply(ctx, config) {
28
31
  error: (message) => logger.error(message),
29
32
  };
30
33
  ctx.effect(() => {
31
- // Document bodies live in the workspace directory, not the database, so the
32
- // panel reads them by `content_path` through the live registry. Without the
33
- // registry the store falls back to rows registered by the tool layer.
34
+ // Document bodies live below the plugin-owned document root, not the
35
+ // database and not the user's workspace; the panel reads them by
36
+ // `content_path` relative to that one root. The registry stays wired for
37
+ // workspace display only.
34
38
  const database = openTeamflowDatabase({
35
39
  dataDir: config.dataDir,
40
+ docsDir: config.docsDir,
36
41
  busyTimeoutMs: config.busyTimeoutMs,
37
42
  // Resolved lazily: the optional registry may register after this effect runs.
38
43
  workspaceRoot: (workspaceId) => ctx.get('workspaceRegistry')?.get(workspaceId)?.path,
@@ -66,6 +71,7 @@ export function apply(ctx, config) {
66
71
  }, 'teamflow: workbench database');
67
72
  }
68
73
  export { WorkflowStore } from "./store/workflow.js";
69
- export { DOC_ROOT, docPath, ensureDocTree, resolveDocPath } from "./store/docs.js";
74
+ export { docPath, ensureDocTree, resolveDocPath } from "./store/docs.js";
75
+ export { DOCS_DIRNAME, defaultDocsDir, resolveDocsDir } from "./store/paths.js";
70
76
  export { WorkflowError } from "./store/model.js";
71
77
  export { MAX_DELETE_TARGETS } from "./store/workflow.js";
@@ -36,7 +36,7 @@ declare module '@deepseek-ai/dsh-typert-protocol' {
36
36
  register(contribution: HostContribution): TypertDisposer;
37
37
  }
38
38
  }
39
- import type { BugSeverity, BugStatus, DeletePreview, DeleteReport, DeleteTarget, DocKind, RequirementSessionKind, RequirementStatus, RunKind, RunStatus, StorySize, StoryStatus } from '../store/model.ts';
39
+ import type { BugSeverity, BugStatus, DeletePreview, DiscussionSubjectType, DeleteReport, DeleteTarget, DocKind, RequirementSessionKind, RequirementStatus, RunKind, RunStatus, StorySize, StoryStatus } from '../store/model.ts';
40
40
  /** npm package that owns these Remote methods. */
41
41
  export declare const PACKAGE_NAME = "@xirang-ai/dsh-teamflow";
42
42
  /**
@@ -89,6 +89,8 @@ export interface SnapshotStory {
89
89
  readonly completedAt: string | null;
90
90
  /** Conversation that created the story; null when it was not recorded. */
91
91
  readonly createSessionId: string | null;
92
+ /** Confirmed discussions of this story, newest first. */
93
+ readonly sessions: readonly SnapshotDiscussionSession[];
92
94
  }
93
95
  /** One 详细设计 document; its body is fetched on demand by {@link readDoc}. */
94
96
  export interface SnapshotDoc {
@@ -121,6 +123,8 @@ export interface SnapshotBug {
121
123
  readonly completedAt: string | null;
122
124
  /** Conversation that created the defect; null when it was not recorded. */
123
125
  readonly createSessionId: string | null;
126
+ /** Confirmed discussions of this defect, newest first. */
127
+ readonly sessions: readonly SnapshotDiscussionSession[];
124
128
  }
125
129
  /** One development or fix run; `sessionId` is the conversation it happened in. */
126
130
  export interface SnapshotRun {
@@ -148,6 +152,17 @@ export interface SnapshotRequirementSession {
148
152
  readonly linkedAt: string | null;
149
153
  readonly createdAt: string;
150
154
  }
155
+ /** One discussion conversation attached to a story or a defect. */
156
+ export interface SnapshotDiscussionSession {
157
+ readonly sessionId: string;
158
+ /** `discuss` for stories and defects; requirements keep their own kinds. */
159
+ readonly kind: string;
160
+ /** Browser preparation time; null only for conversations that skipped it. */
161
+ readonly preparedAt: string | null;
162
+ /** Agent confirmation time; null while the discussion is pending. */
163
+ readonly linkedAt: string | null;
164
+ readonly createdAt: string;
165
+ }
151
166
  /** Everything the list, the design tree and the defect list need in one frame. */
152
167
  export interface WorkbenchSnapshot {
153
168
  /** When the Host read the rows; the panel shows it next to the refresh button. */
@@ -226,6 +241,16 @@ export interface RequirementSessionReply {
226
241
  readonly preparedAt: string | null;
227
242
  readonly linkedAt: string | null;
228
243
  }
244
+ /** One discussion row as stored; `linkedAt` is null until the Agent confirms. */
245
+ export interface DiscussionSessionReply {
246
+ readonly subjectType: DiscussionSubjectType;
247
+ readonly subjectId: string;
248
+ readonly sessionId: string;
249
+ readonly kind: string;
250
+ readonly preparedAt: string | null;
251
+ readonly linkedAt: string | null;
252
+ readonly createdAt: string;
253
+ }
229
254
  /** Success or failure envelope the browser mounts produce. */
230
255
  export type RemoteResult<Value> = {
231
256
  readonly ok: true;
@@ -323,6 +348,13 @@ export interface WorkbenchRemote {
323
348
  * of the snapshot until `teamflow_link_requirement_session` writes `linkedAt`.
324
349
  */
325
350
  prepareRequirementSession(requirementId: string, sessionId: string, kind: RequirementSessionKind, locale?: string): Promise<RemoteResult<RequirementSessionReply>>;
351
+ /**
352
+ * Register a newly created session as a pending discussion of any subject.
353
+ *
354
+ * Same two-phase contract as {@link prepareRequirementSession}, extended to
355
+ * stories and defects; this method never writes `linkedAt`.
356
+ */
357
+ prepareDiscussionSession(subjectType: DiscussionSubjectType, subjectId: string, sessionId: string, kind: string, locale?: string): Promise<RemoteResult<DiscussionSessionReply>>;
326
358
  /**
327
359
  * Read the tier a conversation was started with, for the session header.
328
360
  *
@@ -357,6 +389,7 @@ export declare const METHODS: {
357
389
  readonly cancelStory: "cancelStory";
358
390
  readonly completeBug: "completeBug";
359
391
  readonly prepareRequirementSession: "prepareRequirementSession";
392
+ readonly prepareDiscussionSession: "prepareDiscussionSession";
360
393
  readonly sessionPermissionTier: "sessionPermissionTier";
361
394
  readonly previewDelete: "previewDelete";
362
395
  readonly deleteRecords: "deleteRecords";
@@ -22,6 +22,7 @@ export const METHODS = {
22
22
  cancelStory: 'cancelStory',
23
23
  completeBug: 'completeBug',
24
24
  prepareRequirementSession: 'prepareRequirementSession',
25
+ prepareDiscussionSession: 'prepareDiscussionSession',
25
26
  sessionPermissionTier: 'sessionPermissionTier',
26
27
  previewDelete: 'previewDelete',
27
28
  deleteRecords: 'deleteRecords',
@@ -170,6 +171,21 @@ export const DESCRIPTORS = Object.freeze([
170
171
  ]),
171
172
  result: JSON_CODEC,
172
173
  }),
174
+ Object.freeze({
175
+ id: `${PACKAGE_NAME}#${METHODS.prepareDiscussionSession}`,
176
+ service: SERVICE_KEY,
177
+ namespace: NAMESPACE,
178
+ method: METHODS.prepareDiscussionSession,
179
+ invocation: { kind: 'direct' },
180
+ parameters: Object.freeze([
181
+ parameter('subjectType', 'Discussion subject: requirement, story or bug'),
182
+ parameter('subjectId', 'Requirement, story or defect the discussion belongs to'),
183
+ parameter('sessionId', 'Newly created conversation to link'),
184
+ parameter('kind', 'Discussion kind: discuss for stories and defects, the requirement kinds otherwise'),
185
+ LOCALE_PARAMETER,
186
+ ]),
187
+ result: JSON_CODEC,
188
+ }),
173
189
  Object.freeze({
174
190
  id: `${PACKAGE_NAME}#${METHODS.sessionPermissionTier}`,
175
191
  service: SERVICE_KEY,
@@ -10,9 +10,9 @@
10
10
  */
11
11
  import type { Context } from '@deepseek-ai/cordis';
12
12
  import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
13
- import type { DeletePreview, DeleteReport, DeleteTarget, RequirementSessionKind } from '../store/model.ts';
13
+ import type { DeletePreview, DeleteReport, DeleteTarget, DiscussionSubjectType, RequirementSessionKind } from '../store/model.ts';
14
14
  import type { WorkflowStore } from '../store/workflow.ts';
15
- import type { AttachmentPolicy, BugRunReply, BugStatusReply, CancelRunReply, DocBody, RequirementSessionReply, RequirementStatusReply, SessionTierReply, StartRunReply, StoryStatusReply, WorkbenchSnapshot } from './contract.ts';
15
+ import type { AttachmentPolicy, BugRunReply, BugStatusReply, CancelRunReply, DiscussionSessionReply, DocBody, RequirementSessionReply, RequirementStatusReply, SessionTierReply, StartRunReply, StoryStatusReply, WorkbenchSnapshot } from './contract.ts';
16
16
  /**
17
17
  * Remote face of the workbench.
18
18
  *
@@ -134,6 +134,19 @@ export declare class WorkbenchService extends TypertRemoteService {
134
134
  * @returns the stored pending row.
135
135
  */
136
136
  prepareRequirementSession(requirementId: string, sessionId: string, kind?: RequirementSessionKind, locale?: string): RequirementSessionReply;
137
+ /**
138
+ * Register a newly created conversation as a pending discussion of any subject.
139
+ *
140
+ * Same two-phase contract as {@link prepareRequirementSession}: the Agent
141
+ * confirms with `teamflow_link_session`, and this method never writes the
142
+ * confirmation time itself.
143
+ * @param subjectType - requirement, story or bug.
144
+ * @param subjectId - record the discussion belongs to.
145
+ * @param sessionId - session created by the browser for this discussion.
146
+ * @param kind - `discuss` for stories and defects; requirement kinds otherwise.
147
+ * @returns the stored pending row.
148
+ */
149
+ prepareDiscussionSession(subjectType: DiscussionSubjectType, subjectId: string, sessionId: string, kind: string, locale?: string): DiscussionSessionReply;
137
150
  /**
138
151
  * Compute deletion impact without writing anything.
139
152
  * @param targets - records the user selected in the panel.