oasis_test_v2 2.2.120 → 2.2.121

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/dist/index.js +592 -157
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1569,6 +1569,21 @@ function todoAssignees(todo) {
1569
1569
  function isTodoParty(todo, actorId) {
1570
1570
  return todo.creatorActorId === actorId || todoAssignees(todo).includes(actorId);
1571
1571
  }
1572
+ function isTodoDelegated(todo) {
1573
+ return !todoAssignees(todo).includes(todo.creatorActorId);
1574
+ }
1575
+ function isTodoUnsent(todo) {
1576
+ return isTodoDelegated(todo) && !todo.sentAt;
1577
+ }
1578
+ function canReadTodo(todo, actorId) {
1579
+ return !isTodoUnsent(todo) || todo.creatorActorId === actorId;
1580
+ }
1581
+ function canWriteTodo(todo, actorId) {
1582
+ return isTodoUnsent(todo) ? todo.creatorActorId === actorId : isTodoParty(todo, actorId);
1583
+ }
1584
+ function isTodoAssignedToByOther(todo, actorId) {
1585
+ return isTodoDelegated(todo) && todoAssignees(todo).includes(actorId);
1586
+ }
1572
1587
  function isValidTodoTime(time4) {
1573
1588
  const m4 = /^(\d{2}):(\d{2})$/.exec(time4);
1574
1589
  return !!m4 && Number(m4[1]) <= 23 && Number(m4[2]) <= 59;
@@ -1609,11 +1624,12 @@ function compareTodos(a5, b6) {
1609
1624
  if (a5.createdAt !== b6.createdAt) return a5.createdAt < b6.createdAt ? -1 : 1;
1610
1625
  return a5.id < b6.id ? -1 : a5.id > b6.id ? 1 : 0;
1611
1626
  }
1612
- var WEEKDAY_LABEL;
1627
+ var TODO_STATUSES, WEEKDAY_LABEL;
1613
1628
  var init_todos = __esm({
1614
1629
  "../contract/src/todos.ts"() {
1615
1630
  "use strict";
1616
1631
  init_time_zone();
1632
+ TODO_STATUSES = ["draft", "open", "done", "cancelled"];
1617
1633
  WEEKDAY_LABEL = ["\u5468\u65E5", "\u5468\u4E00", "\u5468\u4E8C", "\u5468\u4E09", "\u5468\u56DB", "\u5468\u4E94", "\u5468\u516D"];
1618
1634
  }
1619
1635
  });
@@ -180598,9 +180614,9 @@ var init_live_chat = __esm({
180598
180614
  * `chat_messages` 不补:二期页面只从 `chat_items` 出历史,agent 也已经读到,不需要再捎带。
180599
180615
  */
180600
180616
  recordLateConsumedInput(turn, text7, opts, ord) {
180601
- const shown = opts?.displayText ?? text7;
180617
+ const shown2 = opts?.displayText ?? text7;
180602
180618
  try {
180603
- return turn.items?.recordUserInput(shown, opts?.attachments, opts?.clientKeys, {
180619
+ return turn.items?.recordUserInput(shown2, opts?.attachments, opts?.clientKeys, {
180604
180620
  ...ord !== void 0 ? { ord } : {},
180605
180621
  // ADR 0615:认领那个乐观气泡(问出「读到了」的这一档,气泡此前没被撤掉,正好就地转成真行)。
180606
180622
  ...opts?.submittedClientKey ? { clientSubmitId: opts.submittedClientKey } : {}
@@ -180610,10 +180626,10 @@ var init_live_chat = __esm({
180610
180626
  }
180611
180627
  }
180612
180628
  commitUserInput(turn, text7, opts) {
180613
- const shown = opts?.displayText ?? text7;
180629
+ const shown2 = opts?.displayText ?? text7;
180614
180630
  const userPart = {
180615
180631
  type: "user",
180616
- text: shown,
180632
+ text: shown2,
180617
180633
  ...opts?.attachments?.length ? { attachments: opts.attachments } : {}
180618
180634
  };
180619
180635
  const segmentEnd = (() => {
@@ -180637,7 +180653,7 @@ var init_live_chat = __esm({
180637
180653
  let userItemId;
180638
180654
  const adopt = opts?.submittedClientKey ? { clientSubmitId: opts.submittedClientKey } : void 0;
180639
180655
  try {
180640
- userItemId = turn.items?.recordUserInput(shown, opts?.attachments, opts?.clientKeys, adopt);
180656
+ userItemId = turn.items?.recordUserInput(shown2, opts?.attachments, opts?.clientKeys, adopt);
180641
180657
  } catch {
180642
180658
  }
180643
180659
  const withSeq = { ...userPart, seq: ++turn.seq };
@@ -180664,12 +180680,12 @@ var init_live_chat = __esm({
180664
180680
  operation: "completed",
180665
180681
  payload: {
180666
180682
  role: "user",
180667
- text: shown,
180683
+ text: shown2,
180668
180684
  ...liveAttachments?.length ? { attachments: liveAttachments } : {}
180669
180685
  }
180670
180686
  });
180671
180687
  try {
180672
- turn.onUserInput?.(shown, opts?.attachments);
180688
+ turn.onUserInput?.(shown2, opts?.attachments);
180673
180689
  } catch {
180674
180690
  }
180675
180691
  return userItemId;
@@ -184464,7 +184480,7 @@ function buildEscalationChatContext(input) {
184464
184480
  manual: "\u4EBA\u5DE5\u4E0A\u62A5\uFF08\u6709\u4EBA\u7528 oasis escalate \u63D0\u7684\uFF09"
184465
184481
  };
184466
184482
  const others = snap.issues.filter((row) => row.kind === "escalation" && !row.resolvedAt && row.id !== issue2.id).sort((a5, b6) => String(a5.createdAt).localeCompare(String(b6.createdAt)));
184467
- const shown = others.slice(0, OTHER_OPEN_LIMIT);
184483
+ const shown2 = others.slice(0, OTHER_OPEN_LIMIT);
184468
184484
  const lines = [
184469
184485
  "\u3010\u8FD9\u6761\u4F1A\u8BDD\u662F\u4EBA\u4ECE\u4E00\u6761\u4E0A\u62A5\uFF08\u5347\u7EA7\u5361\uFF09\u70B9\u8FDB\u6765\u7684\u2014\u2014\u4F60\u662F\u8FD9\u5F20\u4EFB\u52A1\u7684\u534F\u8C03\u8005\uFF0C\u4EBA\u662F\u6765\u627E\u4F60\u4E00\u8D77\u5904\u7F6E\u8FD9\u6761\u4E0A\u62A5\u7684\u3002",
184470
184486
  "\u4EE5\u4E0B\u662F\u8FD9\u6761\u4E0A\u62A5\u6B64\u523B\u7684\u4E8B\u5B9E\uFF0C\u4E0E\u4EBA\u5728\u754C\u9762\u4E0A\u770B\u5230\u7684\u90A3\u6BB5\u5F00\u573A\u767D\u540C\u6E90\u3002\u522B\u53CD\u95EE\u300C\u4F60\u8BF4\u7684\u662F\u54EA\u4EF6\u4E8B\u300D\uFF1B\u8981\u66F4\u7EC6\u7684\u73B0\u72B6\u81EA\u5DF1\u7528 `oasis status` / `oasis content <id>` \u73B0\u67E5\u56FE\u3002\u3011",
@@ -184489,8 +184505,8 @@ function buildEscalationChatContext(input) {
184489
184505
  lines.push(
184490
184506
  "",
184491
184507
  others.length ? `\u8FD9\u5F20\u4EFB\u52A1\u4E0A\u8FD8\u5F00\u7740\u53E6\u5916 ${others.length} \u6761\u4E0A\u62A5\uFF1A` : "\u8FD9\u5F20\u4EFB\u52A1\u4E0A\u6CA1\u6709\u522B\u7684\u672A\u5173\u4E0A\u62A5\u3002",
184492
- ...shown.map((row) => ` \xB7 ${row.id}${row.aboutNodeId ? `\uFF08\u8282\u70B9 ${row.aboutNodeId}\uFF09` : ""}\uFF1A${oneLine(row.body)}`),
184493
- ...others.length > shown.length ? [` \xB7 \uFF08\u53E6\u6709 ${others.length - shown.length} \u6761\u6CA1\u5217\u51FA\u6765\uFF0C\u7528 \`oasis status\` \u770B\u5168\uFF09`] : [],
184508
+ ...shown2.map((row) => ` \xB7 ${row.id}${row.aboutNodeId ? `\uFF08\u8282\u70B9 ${row.aboutNodeId}\uFF09` : ""}\uFF1A${oneLine(row.body)}`),
184509
+ ...others.length > shown2.length ? [` \xB7 \uFF08\u53E6\u6709 ${others.length - shown2.length} \u6761\u6CA1\u5217\u51FA\u6765\uFF0C\u7528 \`oasis status\` \u770B\u5168\uFF09`] : [],
184494
184510
  "",
184495
184511
  '\u5904\u7F6E\u5B8C\u8FD9\u6761\u4E0A\u62A5\uFF0C\u5728\u672C\u4F1A\u8BDD\u91CC\u8DD1 `oasis resolve-escalation --reason "<\u7ED3\u8BBA>"` \u5373\u53EF\u2014\u2014\u4F1A\u8BDD\u5DF2\u7ECF\u7ED1\u7740\u8FD9\u6761\u4E0A\u62A5\uFF0C\u4E0D\u7528\u5E26 id\uFF08\u5B83\u4F1A\u8F6C\u6210\u4E00\u5F20\u5F85\u4EBA\u786E\u8BA4\u7684\u5361\uFF0C\u4E0D\u662F\u7ACB\u523B\u751F\u6548\uFF09\u3002',
184496
184512
  ...kind === "output_stall" && node2 ? [`\u96F6\u4EA7\u51FA\u6B62\u635F\u540C\u65F6\u6682\u505C\u4E86\u6D3E\u53D1\uFF0C\u786E\u8BA4\u6839\u56E0\u5904\u7406\u6389\u4E4B\u540E\u8FD8\u8981 \`oasis release ${node2.id}\` \u624D\u4F1A\u91CD\u65B0\u6D3E\u3002`] : [],
@@ -214604,13 +214620,13 @@ async function startOasisServer(opts) {
214604
214620
  ...clientAttached ? { clientAttached } : {}
214605
214621
  }, clientKey, attachments, retainLocallyOnFailure, resend ? { resend: true } : void 0);
214606
214622
  if ((r5.insertStatus === "uncertain" || r5.orphanedReturn) && clientKey) {
214607
- const shown = r5.batch.map((m4) => m4.text).filter(Boolean).join("\n\n") || text7;
214623
+ const shown2 = r5.batch.map((m4) => m4.text).filter(Boolean).join("\n\n") || text7;
214608
214624
  await landUndeliveredInsert({
214609
214625
  store,
214610
214626
  itemStore,
214611
214627
  chatSessionId: sid,
214612
214628
  clientKey,
214613
- text: shown,
214629
+ text: shown2,
214614
214630
  attachments: attachments ?? [],
214615
214631
  note: r5.orphanedReturn ? INSERT_RETURNED_NOTE : INSERT_UNCERTAIN_NOTE
214616
214632
  });
@@ -244306,6 +244322,10 @@ var init_notification_store = __esm({
244306
244322
  this.save();
244307
244323
  return row;
244308
244324
  }
244325
+ async findBySource(recipient, sourceKey) {
244326
+ const hits = [...this.rows.values()].filter((r5) => r5.recipient === recipient && r5.sourceKey === sourceKey).sort(newerFirst);
244327
+ return hits[0] ?? null;
244328
+ }
244309
244329
  async listPending() {
244310
244330
  return [...this.rows.values()].filter((r5) => r5.status === "pending");
244311
244331
  }
@@ -244470,6 +244490,13 @@ var init_notification_store = __esm({
244470
244490
  if (!e5.rows[0]) throw new Error(`notification insert conflicted but no live row: ${n3.recipient} ${n3.sourceKey}`);
244471
244491
  return fromRow(e5.rows[0]);
244472
244492
  }
244493
+ async findBySource(recipient, sourceKey) {
244494
+ const r5 = await this.sql.query(
244495
+ `SELECT * FROM ${this.t} WHERE recipient = $1 AND source_key = $2 ORDER BY entered_at DESC, id DESC LIMIT 1`,
244496
+ [recipient, sourceKey]
244497
+ );
244498
+ return r5.rows[0] ? fromRow(r5.rows[0]) : null;
244499
+ }
244473
244500
  async listPending() {
244474
244501
  const r5 = await this.sql.query(`SELECT * FROM ${this.t} WHERE status = 'pending'`);
244475
244502
  return r5.rows.map(fromRow);
@@ -253045,10 +253072,10 @@ function mergedPushPayload(first, queued, companyId) {
253045
253072
  const all2 = [first, ...queued];
253046
253073
  const latest = all2[all2.length - 1];
253047
253074
  const others = all2.slice(0, -1).map((r5) => r5.title.trim()).filter(Boolean);
253048
- const shown = others.slice(-OTHERS_LIMIT);
253075
+ const shown2 = others.slice(-OTHERS_LIMIT);
253049
253076
  const task = latest.workspaceTitle?.trim() || first.workspaceTitle?.trim() || OASIS;
253050
253077
  const lines = [latest.title.trim() || OASIS];
253051
- if (shown.length > 0) lines.push(`\u53E6\u6709\uFF1A${shown.join("\u3001")}${others.length > shown.length ? " \u7B49" : ""}`);
253078
+ if (shown2.length > 0) lines.push(`\u53E6\u6709\uFF1A${shown2.join("\u3001")}${others.length > shown2.length ? " \u7B49" : ""}`);
253052
253079
  return {
253053
253080
  title: `${task} \xB7 ${all2.length} \u6761\u5F85\u5904\u7406`,
253054
253081
  body: lines.join("\n"),
@@ -257647,14 +257674,57 @@ var init_flag = __esm({
257647
257674
  }
257648
257675
  });
257649
257676
 
257650
- // ../server/src/domains/todos/service.ts
257651
- function assertFirstVersionAssignee(creator, assignee) {
257652
- if (creator !== assignee) {
257653
- throw new ApiError(403, "TODO_ASSIGNEE_NOT_SELF", "\u4E2A\u4EBA\u5F85\u529E\u7B2C\u4E00\u7248\u53EA\u80FD\u8BB0\u7ED9\u81EA\u5DF1\uFF1A\u53D1\u8D77\u4EBA\u548C\u627F\u63A5\u4EBA\u5FC5\u987B\u662F\u540C\u4E00\u4E2A\u4EBA\u3002");
257677
+ // ../server/src/domains/todos/notices.ts
257678
+ function shown(field, v2, nameOf2) {
257679
+ if (v2 === void 0 || v2 === "") return "\uFF08\u7A7A\uFF09";
257680
+ if (field === "dueDate" || field === "startDate") return formatTodoDue(v2);
257681
+ if (field === "assignee") return nameOf2(v2);
257682
+ return clip4(v2);
257683
+ }
257684
+ function assignedTitle(who, todo) {
257685
+ return `${who} \u6D3E\u7ED9\u4F60\uFF1A${todo.title}\uFF0C${formatTodoSpan(todo)}`;
257686
+ }
257687
+ function unseenTitle(assigneeName, todo) {
257688
+ return `${assigneeName} \u8FD8\u6CA1\u770B\u8FC7\u8FD9\u6761\uFF1A${todo.title}\uFF0C\u660E\u5929\u622A\u6B62`;
257689
+ }
257690
+ function changedTitle(who, todo, changes, nameOf2) {
257691
+ const parts = changes.map((c5) => `${FIELD_LABEL[c5.field] ?? c5.field} ${shown(c5.field, c5.oldValue, nameOf2)} \u2192 ${shown(c5.field, c5.newValue, nameOf2)}`);
257692
+ const reason = changes.find((c5) => c5.reason)?.reason;
257693
+ return `${who} \u6539\u4E86\u300C${todo.title}\u300D\uFF1A${parts.join("\uFF1B")}${reason ? `\uFF1B\u539F\u56E0\uFF1A${clip4(reason)}` : ""}`;
257694
+ }
257695
+ function statusTitle(who, todo, kind, reason) {
257696
+ const verb = kind === "todo-done" ? "\u5B8C\u6210\u4E86" : kind === "todo-reopened" ? "\u91CD\u65B0\u6253\u5F00\u4E86" : "\u53D6\u6D88\u4E86";
257697
+ return `${who} ${verb}\u300C${todo.title}\u300D${reason ? `\uFF1B\u539F\u56E0\uFF1A${clip4(reason)}` : ""}`;
257698
+ }
257699
+ var VALUE_MAX, FIELD_LABEL, clip4, todoAssignedSourceKey, todoChangeSourceKey, todoUnseenSourceKey;
257700
+ var init_notices = __esm({
257701
+ "../server/src/domains/todos/notices.ts"() {
257702
+ "use strict";
257703
+ init_src();
257704
+ VALUE_MAX = 40;
257705
+ FIELD_LABEL = {
257706
+ title: "\u6807\u9898",
257707
+ note: "\u5907\u6CE8",
257708
+ startDate: "\u5F00\u59CB\u65E5",
257709
+ startTime: "\u5F00\u59CB\u65F6\u523B",
257710
+ dueDate: "\u622A\u6B62\u65E5",
257711
+ dueTime: "\u622A\u6B62\u65F6\u523B",
257712
+ linkedWorkorderId: "\u5173\u8054\u5DE5\u5355",
257713
+ assignee: "\u627F\u63A5\u4EBA"
257714
+ };
257715
+ clip4 = (v2) => v2.length > VALUE_MAX ? `${v2.slice(0, VALUE_MAX)}\u2026` : v2;
257716
+ todoAssignedSourceKey = (todoId) => `todo:${todoId}@assigned`;
257717
+ todoChangeSourceKey = (todoId, changeId) => `todo:${todoId}@change:${changeId}`;
257718
+ todoUnseenSourceKey = (todo) => `todo:${todo.id}@unseen:${todo.dueDate}`;
257654
257719
  }
257720
+ });
257721
+
257722
+ // ../server/src/domains/todos/service.ts
257723
+ function canSeeTodo(todo, caller) {
257724
+ return canReadTodo(todo, caller.principal);
257655
257725
  }
257656
- function canAccessTodo(todo, caller) {
257657
- return isTodoParty(todo, caller.principal);
257726
+ function canChangeTodo(todo, caller) {
257727
+ return canWriteTodo(todo, caller.principal);
257658
257728
  }
257659
257729
  function newTodoId() {
257660
257730
  return `td_${(0, import_node_crypto71.randomBytes)(4).toString("hex")}`;
@@ -257675,16 +257745,18 @@ function assertStartBeforeDue(t) {
257675
257745
  throw new ApiError(400, "TODO_START_AFTER_DUE", `\u5F00\u59CB\uFF08${fmt(t.startDate, t.startTime)}\uFF09\u4E0D\u80FD\u665A\u4E8E\u622A\u6B62\uFF08${fmt(t.dueDate, t.dueTime)}\uFF09`);
257676
257746
  }
257677
257747
  }
257678
- var import_node_crypto71, TITLE_MAX, NOTE_MAX, REASON_MAX, clean2, TodoService;
257748
+ var import_node_crypto71, TITLE_MAX, NOTE_MAX, REASON_MAX, LIST_MAX, clean2, TodoService;
257679
257749
  var init_service10 = __esm({
257680
257750
  "../server/src/domains/todos/service.ts"() {
257681
257751
  "use strict";
257682
257752
  import_node_crypto71 = require("node:crypto");
257683
257753
  init_src();
257684
257754
  init_router();
257755
+ init_notices();
257685
257756
  TITLE_MAX = 200;
257686
257757
  NOTE_MAX = 4e3;
257687
257758
  REASON_MAX = 500;
257759
+ LIST_MAX = 500;
257688
257760
  clean2 = (v2) => {
257689
257761
  const t = v2?.trim();
257690
257762
  return t ? t : void 0;
@@ -257701,7 +257773,71 @@ var init_service10 = __esm({
257701
257773
  }
257702
257774
  async respond(todo) {
257703
257775
  const today = await this.today();
257704
- return { today, todayLine: formatTodayLine(today), todo };
257776
+ return { today, todayLine: formatTodayLine(today), todo, names: await this.namesFor([todo]) };
257777
+ }
257778
+ async people() {
257779
+ return await this.deps.people?.() ?? [];
257780
+ }
257781
+ /** 列表 / 单条里出现的发起人、承接人的显示名。 */
257782
+ async namesFor(todos, people) {
257783
+ const ids2 = new Set(todos.flatMap((t) => [t.creatorActorId, ...todoAssignees(t)]));
257784
+ const out = {};
257785
+ for (const p4 of people ?? await this.people()) if (ids2.has(p4.actorId)) out[p4.actorId] = p4.name;
257786
+ return out;
257787
+ }
257788
+ /**
257789
+ * 把「人」解析成本公司唯一一个真人(ADR 0640):`me` / `我` = 调用者代表的真人;actorId 必须是本公司成员;
257790
+ * 名字先比全名(昵称、账号名、邮箱、邮箱 @ 前那段,不分大小写),没有再比「包含」——**只认唯一一个**,
257791
+ * 一个都没有或不止一个都报错、列出候选,不替人挑。
257792
+ */
257793
+ async resolvePerson(raw, caller, what = "\u627F\u63A5\u4EBA") {
257794
+ const input = raw.trim();
257795
+ if (!input) throw new ApiError(400, "TODO_INVALID", `${what}\u4E0D\u80FD\u4E3A\u7A7A`);
257796
+ if (input === "me" || input === "\u6211") return caller.principal;
257797
+ const people = await this.people();
257798
+ if (input.startsWith("actor:")) {
257799
+ if (input === caller.principal) return input;
257800
+ if (people.some((p4) => p4.actorId === input)) return input;
257801
+ throw new ApiError(400, "TODO_PERSON_NOT_MEMBER", `${what} ${input} \u4E0D\u662F\u672C\u516C\u53F8\u7684\u771F\u4EBA\u6210\u5458`);
257802
+ }
257803
+ const key2 = input.toLowerCase();
257804
+ const names = (p4) => [p4.name, ...p4.aliases ?? []].map((n3) => n3.trim().toLowerCase()).filter(Boolean);
257805
+ let hits = people.filter((p4) => names(p4).includes(key2));
257806
+ if (hits.length === 0) hits = people.filter((p4) => names(p4).some((n3) => n3.includes(key2)));
257807
+ const unique4 = [...new Map(hits.map((p4) => [p4.actorId, p4])).values()];
257808
+ if (unique4.length === 1) return unique4[0].actorId;
257809
+ if (unique4.length === 0) throw new ApiError(400, "TODO_PERSON_NOT_FOUND", `\u672C\u516C\u53F8\u6CA1\u6709\u53EB\u300C${input}\u300D\u7684\u6210\u5458\uFF08${what}\uFF09\u3002\u7528 actorId \u6307\u5B9A\uFF0C\u6216\u6362\u4E2A\u5199\u6CD5\u3002`);
257810
+ const list3 = unique4.map((p4) => `${p4.name}\uFF08${p4.actorId}\uFF09`).join("\u3001");
257811
+ throw new ApiError(400, "TODO_PERSON_AMBIGUOUS", `\u300C${input}\u300D\u5BF9\u5F97\u4E0A\u4E0D\u6B62\u4E00\u4E2A\u4EBA\uFF1A${list3}\u3002\u8BF7\u7528 actorId \u6307\u5B9A${what}\u3002`);
257812
+ }
257813
+ /** 给另一方(或承接人)发通知;失败只告警。 */
257814
+ async notice(todo, actor, recipients, kind, sourceKey, title, at2) {
257815
+ if (!this.deps.notify || recipients.length === 0) return;
257816
+ const records = recipients.map((recipient) => ({
257817
+ recipient,
257818
+ category: "todo",
257819
+ sourceKey,
257820
+ kind,
257821
+ title,
257822
+ enteredAt: at2,
257823
+ actorId: actor,
257824
+ // 来源对话是发起人那边的:只有发起人点了能跳回去;给承接人的不带(带了也打不开)。
257825
+ ...recipient === todo.creatorActorId && todo.sourceChatSessionId ? { chatSessionId: todo.sourceChatSessionId } : {}
257826
+ }));
257827
+ try {
257828
+ await this.deps.notify(records);
257829
+ } catch (error3) {
257830
+ this.deps.warn?.(new Error(`\u5F85\u529E ${todo.id} \u901A\u77E5\u6CA1\u53D1\u51FA\u53BB\uFF1A${String(error3)}`));
257831
+ }
257832
+ }
257833
+ /** 「另一方」:调用者是发起人 → 承接人们;是承接人 → 发起人。只在派给别人、且已经发出的待办上有。 */
257834
+ counterparts(todo, caller) {
257835
+ if (!isTodoDelegated(todo) || isTodoUnsent(todo)) return [];
257836
+ const parties = [todo.creatorActorId, ...todoAssignees(todo)];
257837
+ return [...new Set(parties)].filter((p4) => p4 !== caller.principal);
257838
+ }
257839
+ async nameOf(id) {
257840
+ return (await this.people()).find((p4) => p4.actorId === id)?.name ?? id;
257705
257841
  }
257706
257842
  change(todoId, caller, at2, field, oldValue, newValue, reason) {
257707
257843
  return {
@@ -257731,8 +257867,10 @@ var init_service10 = __esm({
257731
257867
  }
257732
257868
  assertStartBeforeDue({ ...input });
257733
257869
  const creator = caller.principal;
257734
- const assignee = caller.principal;
257735
- assertFirstVersionAssignee(creator, assignee);
257870
+ const assignee = input.assignee !== void 0 ? await this.resolvePerson(input.assignee, caller) : creator;
257871
+ const delegated = assignee !== creator;
257872
+ const sourceChatSessionId = clean2(input.sourceChatSessionId);
257873
+ const sourceRunId = delegated && sourceChatSessionId ? this.deps.runOf?.(sourceChatSessionId) : void 0;
257736
257874
  const at2 = new Date(this.nowMs()).toISOString();
257737
257875
  const todo = {
257738
257876
  id: newTodoId(),
@@ -257745,27 +257883,93 @@ var init_service10 = __esm({
257745
257883
  ...input.dueTime ? { dueTime: input.dueTime } : {},
257746
257884
  ...input.startDate ? { startDate: input.startDate } : {},
257747
257885
  ...input.startTime ? { startTime: input.startTime } : {},
257748
- status: "open",
257749
- ...clean2(input.sourceChatSessionId) ? { sourceChatSessionId: clean2(input.sourceChatSessionId) } : {},
257886
+ status: delegated ? "draft" : "open",
257887
+ ...sourceChatSessionId ? { sourceChatSessionId } : {},
257888
+ ...sourceRunId ? { sourceRunId } : {},
257750
257889
  createdAt: at2,
257751
257890
  updatedAt: at2
257752
257891
  };
257753
257892
  const saved = await this.deps.store.create(todo, [this.change(todo.id, caller, at2, "created", void 0, title)]);
257754
257893
  return this.respond(saved);
257755
257894
  }
257756
- async list(caller, opts = {}) {
257757
- const statuses2 = opts.all ? ["open", "done", "cancelled"] : ["open"];
257758
- const items = (await this.deps.store.listInvolving(caller.principal, statuses2)).filter((t) => canAccessTodo(t, caller));
257895
+ /**
257896
+ * 列表(ADR 0640):筛选都不给 = 「我承接的、未完成的」(与 0635 相同)。人可以写 actorId、成员名或 `me`;
257897
+ * 从没发出过的草案只列给发起人(存储层与 `canReadTodo` 配对的那条条件)。
257898
+ */
257899
+ async list(caller, query = {}) {
257900
+ const statuses2 = query.all ? TODO_STATUSES : query.statuses?.length ? query.statuses : ["open"];
257901
+ for (const st of statuses2) if (!TODO_STATUSES.includes(st)) throw new ApiError(400, "TODO_INVALID", `status \u53EA\u6536 ${TODO_STATUSES.join(" / ")}\uFF0C\u6536\u5230\uFF1A${st}`);
257902
+ if (query.dueFrom !== void 0) assertDay(query.dueFrom, "dueFrom");
257903
+ if (query.dueTo !== void 0) assertDay(query.dueTo, "dueTo");
257904
+ const assignee = query.assignee !== void 0 ? await this.resolvePerson(query.assignee, caller) : void 0;
257905
+ const creator = query.creator !== void 0 ? await this.resolvePerson(query.creator, caller, "\u53D1\u8D77\u4EBA") : void 0;
257906
+ const mine = assignee === void 0 && creator === void 0 && !query.everyone;
257907
+ const q4 = clean2(query.q);
257908
+ const items = await this.deps.store.search({
257909
+ statuses: statuses2,
257910
+ ...mine ? { assignee: caller.principal } : assignee ? { assignee } : {},
257911
+ ...creator ? { creator } : {},
257912
+ ...query.dueFrom ? { dueFrom: query.dueFrom } : {},
257913
+ ...query.dueTo ? { dueTo: query.dueTo } : {},
257914
+ ...q4 ? { q: q4 } : {},
257915
+ ...clean2(query.chatSessionId) ? { chatSessionId: clean2(query.chatSessionId) } : {},
257916
+ viewer: caller.principal,
257917
+ limit: LIST_MAX
257918
+ });
257919
+ const visible = items.filter((t) => canSeeTodo(t, caller));
257759
257920
  const today = await this.today();
257760
- return { today, todayLine: formatTodayLine(today), items };
257921
+ return { today, todayLine: formatTodayLine(today), items: visible, names: await this.namesFor(visible) };
257761
257922
  }
257762
- /** 读一条并校验权限:不存在与无权限分开报(无权限 403——任务书口径「其他人 403」)。 */
257923
+ /**
257924
+ * 读一条并校验读权限。不存在 404;从没发出过的草案对发起人以外的人也是 404(不透露有这么一条)。
257925
+ */
257763
257926
  async get(caller, id) {
257764
257927
  const todo = await this.deps.store.get(id);
257765
- if (!todo) throw new ApiError(404, "TODO_NOT_FOUND", `\u5F85\u529E ${id} \u4E0D\u5B58\u5728`);
257766
- if (!canAccessTodo(todo, caller)) throw new ApiError(403, "TODO_FORBIDDEN", "\u65E0\u6743\u67E5\u770B\u6216\u4FEE\u6539\u8FD9\u6761\u5F85\u529E");
257928
+ if (!todo || !canSeeTodo(todo, caller)) throw new ApiError(404, "TODO_NOT_FOUND", `\u5F85\u529E ${id} \u4E0D\u5B58\u5728`);
257929
+ return todo;
257930
+ }
257931
+ /** 读一条并校验改权限:读得到但不是当事人 403(任务书口径「其他人 403」)。 */
257932
+ async getForChange(caller, id) {
257933
+ const todo = await this.get(caller, id);
257934
+ if (!canChangeTodo(todo, caller)) throw new ApiError(403, "TODO_FORBIDDEN", "\u53EA\u6709\u53D1\u8D77\u4EBA\u6216\u627F\u63A5\u4EBA\u80FD\u6539\u8FD9\u6761\u5F85\u529E");
257767
257935
  return todo;
257768
257936
  }
257937
+ /**
257938
+ * `oasis todo show`:单条 + 全部变更记录。承接人(派给别人的、已发出的)第一次看时记下 `firstViewedAt`
257939
+ * (「对方还没看过」提醒用,ADR 0640);发起人、第三人看不算。
257940
+ */
257941
+ async view(caller, id) {
257942
+ let todo = await this.get(caller, id);
257943
+ if (!todo.firstViewedAt && !isTodoUnsent(todo) && isTodoAssignedToByOther(todo, caller.principal)) {
257944
+ todo = await this.deps.store.markFirstViewed(todo.id, new Date(this.nowMs()).toISOString()) ?? todo;
257945
+ }
257946
+ return { todo, changes: await this.deps.store.listChanges(id), names: await this.namesFor([todo]) };
257947
+ }
257948
+ /**
257949
+ * 发出(ADR 0640):草案 → open,记 `sentAt`,通知承接人「X 派给你:…」。
257950
+ * **只有发起人本人**能发——经助理(agent)调用一律 403:「先出草案、人点发出」就是为了不让助理替人发出去。
257951
+ * 已经发出过的再点一次原样返回(连点 / 刷新后重点不报错、不重发通知)。
257952
+ */
257953
+ async send(caller, id) {
257954
+ const before = await this.get(caller, id);
257955
+ if (before.creatorActorId !== caller.principal) throw new ApiError(403, "TODO_FORBIDDEN", "\u53EA\u6709\u53D1\u8D77\u4EBA\u80FD\u53D1\u51FA\u8FD9\u6761\u5F85\u529E");
257956
+ if (caller.hand) throw new ApiError(403, "TODO_SEND_HUMAN_ONLY", "\u6D3E\u7ED9\u522B\u4EBA\u7684\u5F85\u529E\u53EA\u80FD\u7531\u53D1\u8D77\u4EBA\u672C\u4EBA\u5728\u8349\u6848\u5361\u4E0A\u70B9\u300C\u53D1\u51FA\u300D\uFF0C\u52A9\u7406\u4E0D\u80FD\u4EE3\u53D1\u3002");
257957
+ if (before.sentAt) return this.respond(before);
257958
+ if (before.status !== "draft") throw new ApiError(409, "TODO_NOT_DRAFT", "\u8FD9\u6761\u5F85\u529E\u4E0D\u662F\u8349\u6848\uFF08\u53EF\u80FD\u5DF2\u53D6\u6D88\uFF09\uFF0C\u4E0D\u80FD\u53D1\u51FA");
257959
+ const at2 = new Date(this.nowMs()).toISOString();
257960
+ const next = { ...before, status: "open", sentAt: at2, updatedAt: at2, recordedByActorId: caller.actor };
257961
+ const out = await this.save(caller, before, next, [this.change(id, caller, at2, "sent", void 0, before.assigneeActorId)]);
257962
+ await this.notice(
257963
+ out.todo,
257964
+ caller.principal,
257965
+ todoAssignees(out.todo),
257966
+ "todo-assigned",
257967
+ todoAssignedSourceKey(id),
257968
+ assignedTitle(await this.nameOf(caller.principal), out.todo),
257969
+ at2
257970
+ );
257971
+ return out;
257972
+ }
257769
257973
  async save(caller, before, next, changes) {
257770
257974
  if (changes.length === 0) return this.respond(before);
257771
257975
  const saved = await this.deps.store.update(next, before.updatedAt, changes);
@@ -257773,7 +257977,7 @@ var init_service10 = __esm({
257773
257977
  return this.respond(saved);
257774
257978
  }
257775
257979
  async edit(caller, id, input) {
257776
- const before = await this.get(caller, id);
257980
+ const before = await this.getForChange(caller, id);
257777
257981
  const reason = clean2(input.reason);
257778
257982
  if (reason && reason.length > REASON_MAX) throw new ApiError(400, "TODO_INVALID", `reason \u4E0D\u80FD\u8D85\u8FC7 ${REASON_MAX} \u5B57`);
257779
257983
  const at2 = new Date(this.nowMs()).toISOString();
@@ -257819,29 +258023,75 @@ var init_service10 = __esm({
257819
258023
  set("dueTime", input.dueTime ?? void 0);
257820
258024
  }
257821
258025
  if (input.linkedWorkorderId !== void 0) set("linkedWorkorderId", clean2(input.linkedWorkorderId));
258026
+ if (input.assignee !== void 0) {
258027
+ if (!isTodoUnsent(before)) throw new ApiError(400, "TODO_ASSIGNEE_LOCKED", "\u5DF2\u7ECF\u53D1\u51FA\u7684\u5F85\u529E\u4E0D\u80FD\u6362\u627F\u63A5\u4EBA\uFF1A\u53D6\u6D88\u8FD9\u6761\uFF08\u5E26\u539F\u56E0\uFF09\uFF0C\u518D\u91CD\u65B0\u6D3E\u4E00\u6761\u3002");
258028
+ const assignee = await this.resolvePerson(input.assignee, caller);
258029
+ if (assignee === before.creatorActorId) throw new ApiError(400, "TODO_INVALID", "\u8349\u6848\u4E0D\u80FD\u6539\u6210\u6D3E\u7ED9\u81EA\u5DF1\uFF1A\u53D6\u6D88\u8FD9\u6761\u8349\u6848\uFF0C\u518D\u7ED9\u81EA\u5DF1\u8BB0\u4E00\u6761\u3002");
258030
+ if (assignee !== before.assigneeActorId) {
258031
+ next.assigneeActorId = assignee;
258032
+ changes.push(this.change(id, caller, at2, "assignee", before.assigneeActorId, assignee, reason));
258033
+ }
258034
+ }
257822
258035
  if (next.startTime && !next.startDate) throw new ApiError(400, "TODO_INVALID", "\u6709\u5F00\u59CB\u65F6\u523B\u5C31\u8981\u6709\u5F00\u59CB\u65E5\uFF08\u5148\u8BBE startDate\uFF09");
257823
258036
  assertStartBeforeDue(next);
258037
+ if (!reason && isTodoAssignedToByOther(before, caller.principal) && changes.some((c5) => c5.field === "dueDate" || c5.field === "dueTime")) {
258038
+ throw new ApiError(400, "TODO_REASON_REQUIRED", "\u8FD9\u6761\u662F\u522B\u4EBA\u6D3E\u7ED9\u4F60\u7684\uFF1A\u6539\u622A\u6B62\u8981\u5199\u539F\u56E0\uFF08--reason\uFF09\uFF0C\u53D1\u8D77\u4EBA\u4F1A\u6536\u5230\u901A\u77E5\u3002");
258039
+ }
257824
258040
  if (changes.length > 0) {
257825
258041
  next.updatedAt = at2;
257826
258042
  next.recordedByActorId = caller.actor;
257827
258043
  }
257828
- return this.save(caller, before, next, changes);
258044
+ const out = await this.save(caller, before, next, changes);
258045
+ if (changes.length > 0) {
258046
+ const people = await this.people();
258047
+ const nameOf2 = (a5) => people.find((p4) => p4.actorId === a5)?.name ?? a5;
258048
+ await this.notice(
258049
+ out.todo,
258050
+ caller.principal,
258051
+ this.counterparts(out.todo, caller),
258052
+ "todo-changed",
258053
+ todoChangeSourceKey(id, changes[0].id),
258054
+ changedTitle(nameOf2(caller.principal), out.todo, changes, nameOf2),
258055
+ at2
258056
+ );
258057
+ }
258058
+ return out;
257829
258059
  }
257830
- async setStatus(caller, id, status, reason) {
257831
- const before = await this.get(caller, id);
258060
+ /**
258061
+ * 完成 / 取消 / 重新打开。草案(从没发出过的):不能完成;取消了也仍只有发起人看得到;重新打开回到草案、不会因此发出去。
258062
+ * 别人派给我的,取消必须带原因(ADR 0640 D9)。派给别人且已发出的,动完通知另一方。
258063
+ */
258064
+ async setStatus(caller, id, requested, reason) {
258065
+ const before = await this.getForChange(caller, id);
257832
258066
  const why = clean2(reason);
257833
258067
  if (why && why.length > REASON_MAX) throw new ApiError(400, "TODO_INVALID", `reason \u4E0D\u80FD\u8D85\u8FC7 ${REASON_MAX} \u5B57`);
258068
+ const unsent = isTodoUnsent(before);
258069
+ if (unsent && requested === "done") throw new ApiError(400, "TODO_NOT_SENT", "\u8FD9\u6761\u8FD8\u662F\u8349\u6848\uFF08\u6CA1\u53D1\u51FA\uFF09\uFF0C\u4E0D\u80FD\u6807\u5B8C\u6210\uFF1A\u5148\u5728\u8349\u6848\u5361\u4E0A\u70B9\u300C\u53D1\u51FA\u300D\uFF0C\u6216\u53D6\u6D88\u5B83\u3002");
258070
+ if (before.status === "draft" && requested === "open") throw new ApiError(400, "TODO_NOT_SENT", "\u8FD9\u6761\u662F\u8349\u6848\uFF1A\u8981\u751F\u6548\u8BF7\u53D1\u8D77\u4EBA\u5728\u8349\u6848\u5361\u4E0A\u70B9\u300C\u53D1\u51FA\u300D\u3002");
258071
+ const status = unsent && requested === "open" ? "draft" : requested;
257834
258072
  if (before.status === status) return this.respond(before);
258073
+ if (status === "cancelled" && !why && isTodoAssignedToByOther(before, caller.principal)) {
258074
+ throw new ApiError(400, "TODO_REASON_REQUIRED", "\u8FD9\u6761\u662F\u522B\u4EBA\u6D3E\u7ED9\u4F60\u7684\uFF1A\u53D6\u6D88\u8981\u5199\u539F\u56E0\uFF08--reason\uFF09\uFF0C\u53D1\u8D77\u4EBA\u4F1A\u6536\u5230\u901A\u77E5\u3002");
258075
+ }
257835
258076
  const at2 = new Date(this.nowMs()).toISOString();
257836
258077
  const next = { ...before, status, updatedAt: at2, recordedByActorId: caller.actor };
257837
258078
  delete next.doneAt;
257838
258079
  delete next.cancelledAt;
257839
258080
  if (status === "done") next.doneAt = at2;
257840
258081
  if (status === "cancelled") next.cancelledAt = at2;
257841
- return this.save(caller, before, next, [this.change(id, caller, at2, "status", before.status, status, why)]);
257842
- }
257843
- history(caller, id) {
257844
- return this.get(caller, id).then(() => this.deps.store.listChanges(id));
258082
+ const change = this.change(id, caller, at2, "status", before.status, status, why);
258083
+ const out = await this.save(caller, before, next, [change]);
258084
+ const kind = status === "done" ? "todo-done" : status === "cancelled" ? "todo-cancelled" : "todo-reopened";
258085
+ await this.notice(
258086
+ out.todo,
258087
+ caller.principal,
258088
+ this.counterparts(out.todo, caller),
258089
+ kind,
258090
+ todoChangeSourceKey(id, change.id),
258091
+ statusTitle(await this.nameOf(caller.principal), out.todo, kind, why),
258092
+ at2
258093
+ );
258094
+ return out;
257845
258095
  }
257846
258096
  };
257847
258097
  }
@@ -257905,15 +258155,29 @@ var init_store3 = __esm({
257905
258155
  return this.write((rows) => {
257906
258156
  const current = rows.todos[next.id];
257907
258157
  if (!current || current.updatedAt !== expectedUpdatedAt) return null;
257908
- rows.todos[next.id] = structuredClone(next);
258158
+ const firstViewedAt = current.firstViewedAt ?? next.firstViewedAt;
258159
+ rows.todos[next.id] = structuredClone({ ...next, ...firstViewedAt ? { firstViewedAt } : {} });
257909
258160
  rows.changes.push(...structuredClone(changes));
257910
- return next;
258161
+ return rows.todos[next.id];
257911
258162
  });
257912
258163
  }
257913
258164
  async listInvolving(actorId, statuses2) {
257914
258165
  await this.tail;
257915
258166
  return Object.values(this.rows.todos).filter((t) => statuses2.includes(t.status) && (t.creatorActorId === actorId || todoAssignees(t).includes(actorId))).sort(compareTodos).map((t) => structuredClone(t));
257916
258167
  }
258168
+ async search(f6) {
258169
+ await this.tail;
258170
+ const q4 = f6.q?.toLowerCase();
258171
+ return Object.values(this.rows.todos).filter((t) => f6.statuses.includes(t.status) && (!f6.assignee || todoAssignees(t).includes(f6.assignee)) && (!f6.creator || t.creatorActorId === f6.creator) && (!f6.dueFrom || t.dueDate >= f6.dueFrom) && (!f6.dueTo || t.dueDate <= f6.dueTo) && (!f6.chatSessionId || t.sourceChatSessionId === f6.chatSessionId) && (!q4 || t.title.toLowerCase().includes(q4) || (t.note ?? "").toLowerCase().includes(q4)) && canReadTodo(t, f6.viewer)).sort(compareTodos).slice(0, f6.limit).map((t) => structuredClone(t));
258172
+ }
258173
+ markFirstViewed(id, at2) {
258174
+ return this.write((rows) => {
258175
+ const t = rows.todos[id];
258176
+ if (!t) return null;
258177
+ t.firstViewedAt ??= at2;
258178
+ return t;
258179
+ }).then((r5) => r5 ?? this.get(id));
258180
+ }
257917
258181
  async listOpenReminderCandidates(fromDay, toDay) {
257918
258182
  await this.tail;
257919
258183
  const within = (day) => day !== void 0 && day >= fromDay && day <= toDay;
@@ -257943,18 +258207,32 @@ function todosDomain(options) {
257943
258207
  const sourceSession = (req, fromBody) => req.auth.dispatch?.chatSessionId ?? fromBody;
257944
258208
  return (router) => {
257945
258209
  router.get("/api/todos", wrap(async (req, service, caller) => {
257946
- const all2 = ["1", "true"].includes((req.query.get("all") ?? "").toLowerCase());
257947
- return { status: 200, body: await service.list(caller, { all: all2 }) };
258210
+ const q4 = req.query;
258211
+ const flag = (k6) => ["1", "true"].includes((q4.get(k6) ?? "").toLowerCase());
258212
+ const one2 = (k6) => q4.get(k6) ?? void 0;
258213
+ const statuses2 = q4.getAll("status").flatMap((v2) => v2.split(",")).map((v2) => v2.trim()).filter(Boolean);
258214
+ return {
258215
+ status: 200,
258216
+ body: await service.list(caller, {
258217
+ all: flag("all"),
258218
+ everyone: flag("everyone"),
258219
+ ...statuses2.length ? { statuses: statuses2 } : {},
258220
+ ...one2("assignee") !== void 0 ? { assignee: one2("assignee") } : {},
258221
+ ...one2("creator") !== void 0 ? { creator: one2("creator") } : {},
258222
+ ...one2("dueFrom") !== void 0 ? { dueFrom: one2("dueFrom") } : {},
258223
+ ...one2("dueTo") !== void 0 ? { dueTo: one2("dueTo") } : {},
258224
+ ...one2("q") !== void 0 ? { q: one2("q") } : {},
258225
+ ...one2("chatSessionId") !== void 0 ? { chatSessionId: one2("chatSessionId") } : {}
258226
+ })
258227
+ };
257948
258228
  }));
257949
258229
  router.post("/api/todos", wrap(async (req, service, caller) => {
257950
258230
  const body2 = create.parse(req.body ?? {});
257951
258231
  const sourceChatSessionId = sourceSession(req, body2.sourceChatSessionId);
257952
258232
  return { status: 201, body: await service.create(caller, { ...body2, ...sourceChatSessionId ? { sourceChatSessionId } : {} }) };
257953
258233
  }));
257954
- router.get("/api/todos/:id", wrap(async (req, service, caller) => {
257955
- const todo = await service.get(caller, req.params.id);
257956
- return { status: 200, body: { todo, changes: await service.history(caller, todo.id) } };
257957
- }));
258234
+ router.get("/api/todos/:id", wrap(async (req, service, caller) => ({ status: 200, body: await service.view(caller, req.params.id) })));
258235
+ router.post("/api/todos/:id/send", wrap(async (req, service, caller) => ({ status: 200, body: await service.send(caller, req.params.id) })));
257958
258236
  router.patch("/api/todos/:id", wrap(async (req, service, caller) => {
257959
258237
  const body2 = edit.parse(req.body ?? {});
257960
258238
  return { status: 200, body: await service.edit(caller, req.params.id, body2) };
@@ -257975,6 +258253,7 @@ var init_routes11 = __esm({
257975
258253
  init_zod();
257976
258254
  init_router();
257977
258255
  create = external_exports.object({
258256
+ assignee: external_exports.string().optional(),
257978
258257
  title: external_exports.string(),
257979
258258
  note: external_exports.string().optional(),
257980
258259
  dueDate: external_exports.string(),
@@ -257991,6 +258270,7 @@ var init_routes11 = __esm({
257991
258270
  startDate: external_exports.string().nullable().optional(),
257992
258271
  startTime: external_exports.string().nullable().optional(),
257993
258272
  linkedWorkorderId: external_exports.string().nullable().optional(),
258273
+ assignee: external_exports.string().optional(),
257994
258274
  reason: external_exports.string().optional()
257995
258275
  }).strict();
257996
258276
  statusBody = external_exports.object({ reason: external_exports.string().optional() }).strict();
@@ -258029,6 +258309,7 @@ var init_reminders = __esm({
258029
258309
  "../server/src/domains/todos/reminders.ts"() {
258030
258310
  "use strict";
258031
258311
  init_src();
258312
+ init_notices();
258032
258313
  TODO_REMINDER_TIME = "09:00";
258033
258314
  TODO_REMINDER_SCAN_MS = 6e4;
258034
258315
  TODO_REMINDER_TITLE = {
@@ -258086,16 +258367,48 @@ var init_reminders = __esm({
258086
258367
  kind: r5.kind,
258087
258368
  title: TODO_REMINDER_TITLE[r5.kind](todo.title),
258088
258369
  enteredAt,
258089
- ...todo.sourceChatSessionId ? { chatSessionId: todo.sourceChatSessionId } : {}
258370
+ ...todo.sourceChatSessionId && recipient === todo.creatorActorId ? { chatSessionId: todo.sourceChatSessionId } : {}
258090
258371
  });
258091
258372
  this.seen.add(key2);
258092
258373
  if (row.enteredAt === enteredAt) fresh.push(row);
258093
258374
  }
258375
+ if (r5.kind === "todo-due-1d") {
258376
+ const row = await this.unseen(companyId, ledger, todo, enteredAt);
258377
+ if (row) fresh.push(row);
258378
+ }
258094
258379
  }
258095
258380
  }
258096
258381
  if (fresh.length > 0) this.deps.onInserted?.(companyId, fresh);
258097
258382
  return fresh.length;
258098
258383
  }
258384
+ /** 「对方还没看过」:三条都成立才发给发起人(见文件头)。返回这一拍新写的那条。 */
258385
+ async unseen(companyId, ledger, todo, enteredAt) {
258386
+ if (!isTodoDelegated(todo) || !todo.sentAt || todo.firstViewedAt) return void 0;
258387
+ const sourceKey = todoUnseenSourceKey(todo);
258388
+ const key2 = JSON.stringify([companyId, todo.creatorActorId, sourceKey]);
258389
+ if (this.seen.has(key2)) return void 0;
258390
+ const assignees = todoAssignees(todo);
258391
+ for (const a5 of assignees) {
258392
+ const assigned = await ledger.findBySource(a5, todoAssignedSourceKey(todo.id));
258393
+ if (!assigned || assigned.readAt) {
258394
+ this.seen.add(key2);
258395
+ return void 0;
258396
+ }
258397
+ }
258398
+ const names = await Promise.all(assignees.map((a5) => this.deps.nameOf?.(companyId, a5) ?? Promise.resolve(a5)));
258399
+ const row = await ledger.insert({
258400
+ recipient: todo.creatorActorId,
258401
+ category: "todo",
258402
+ sourceKey,
258403
+ kind: "todo-unseen",
258404
+ title: unseenTitle(names.join("\u3001"), todo),
258405
+ enteredAt,
258406
+ ...assignees[0] ? { actorId: assignees[0] } : {},
258407
+ ...todo.sourceChatSessionId ? { chatSessionId: todo.sourceChatSessionId } : {}
258408
+ });
258409
+ this.seen.add(key2);
258410
+ return row.enteredAt === enteredAt ? row : void 0;
258411
+ }
258099
258412
  /** 首拍延后一点(默认 10 秒,与通知对账器同款):等 serve 把推送、台账都装好再扫。 */
258100
258413
  start(intervalMs = TODO_REMINDER_SCAN_MS, firstDelayMs = 1e4) {
258101
258414
  if (this.timer) return;
@@ -258123,6 +258436,7 @@ var init_todos2 = __esm({
258123
258436
  init_store3();
258124
258437
  init_routes11();
258125
258438
  init_reminders();
258439
+ init_notices();
258126
258440
  }
258127
258441
  });
258128
258442
 
@@ -280158,29 +280472,33 @@ var init_externalized_blobs = __esm({
280158
280472
  return { rows: Number(r5.rows ?? 0), bytes: Number(r5.bytes ?? 0) };
280159
280473
  }
280160
280474
  /**
280161
- * 清空给定行的 bytea(§6.2 第 6 步 `--purge-pg-bytes` 的一批):**先确认对象在后端、大小与行一致**,
280162
- * 只清这些——bytea 是清掉前最后一份副本,对象丢了(误删、恢复不全)时宁可留着。
280163
- * 返回清掉的行数、这些行的字节数与未通过核对的 hash。
280475
+ * 清空一行的 bytea(§6.2 第 6 步 `--purge-pg-bytes` 的单行原语):**先确认对象在后端、大小与行一致**才清——
280476
+ * bytea 是清掉前最后一份副本,对象丢了(误删、恢复不全)时宁可留着。对象缺失 / 大小不符、或核对本身出错
280477
+ * (S3 超时 / 403 / 5xx、fs I/O 错)一律抛错,bytea 不动。可重复执行:已清过或已不是 `ext` 的行跳过。
280478
+ * 返回本行清掉的字节数(行 `size`);跳过返回 0。
280164
280479
  */
280165
- async purgePgBytesVerified(hashes) {
280166
- if (hashes.length === 0) return { purged: 0, bytes: 0, unverified: [] };
280480
+ async purgePgBytesOne(hash3) {
280167
280481
  const res = await this.pool.query(
280168
- `SELECT hash, size FROM ${this.t} WHERE hash = ANY($1::text[]) AND location = 'ext' AND bytes IS NOT NULL`,
280169
- [hashes]
280482
+ `SELECT size FROM ${this.t} WHERE hash = $1 AND location = 'ext' AND bytes IS NOT NULL`,
280483
+ [hash3]
280170
280484
  );
280171
- const ok3 = [];
280172
- const unverified = [];
280173
- for (const row of res.rows) {
280174
- const head = await this.backend.head(this.key(row.hash));
280175
- if (head && head.size === Number(row.size)) ok3.push(row.hash);
280176
- else unverified.push(row.hash);
280177
- }
280178
- if (ok3.length === 0) return { purged: 0, bytes: 0, unverified };
280485
+ const row = res.rows[0];
280486
+ if (!row) return 0;
280487
+ const size = Number(row.size);
280488
+ let head;
280489
+ try {
280490
+ head = await this.backend.head(this.key(hash3));
280491
+ } catch (err) {
280492
+ throw new Error("\u6838\u5BF9\u5BF9\u8C61\u51FA\u9519\uFF0Cbytea \u4FDD\u7559", { cause: err });
280493
+ }
280494
+ if (!head || head.size !== size) {
280495
+ throw new Error(`\u5BF9\u8C61\u4E0D\u5728\u540E\u7AEF\u6216\u5927\u5C0F\u4E0E\u884C\u4E0D\u7B26\uFF08\u884C ${size} B\uFF0C\u5BF9\u8C61 ${head ? `${head.size} B` : "\u4E0D\u5B58\u5728"}\uFF09\uFF0Cbytea \u4FDD\u7559`);
280496
+ }
280179
280497
  const upd = await this.pool.query(
280180
- `UPDATE ${this.t} SET bytes = NULL WHERE hash = ANY($1::text[]) AND location = 'ext' AND bytes IS NOT NULL RETURNING size`,
280181
- [ok3]
280498
+ `UPDATE ${this.t} SET bytes = NULL WHERE hash = $1 AND location = 'ext' AND bytes IS NOT NULL`,
280499
+ [hash3]
280182
280500
  );
280183
- return { purged: upd.rowCount ?? 0, bytes: upd.rows.reduce((n3, r5) => n3 + Number(r5.size), 0), unverified };
280501
+ return (upd.rowCount ?? 0) > 0 ? size : 0;
280184
280502
  }
280185
280503
  /** 观察期后清空已外部化行的 bytea(§6.2 第 6 步),一批一条语句。返回本批清掉的行数。 */
280186
280504
  async purgePgBytes(limit) {
@@ -320552,27 +320870,19 @@ async function migrateBlobs(store, op2, opts) {
320552
320870
  }
320553
320871
  const hashes = await store.listHashes(selector, after, batch);
320554
320872
  if (hashes.length === 0) break;
320555
- if (op2 === "purge-pg-bytes") {
320556
- const r5 = await store.purgePgBytesVerified(hashes);
320557
- report.rows += r5.purged;
320558
- report.bytes += r5.bytes;
320559
- for (const h5 of r5.unverified) fail(h5, new Error("\u5BF9\u8C61\u4E0D\u5728\u540E\u7AEF\u6216\u5927\u5C0F\u4E0E\u884C\u4E0D\u7B26\uFF0Cbytea \u4FDD\u7559"));
320560
- after = hashes[hashes.length - 1];
320561
- } else {
320562
- for (const hash3 of hashes) {
320563
- if (stop()) {
320564
- report.interrupted = true;
320565
- break;
320566
- }
320567
- try {
320568
- const n3 = op2 === "externalize" ? await store.externalizeOne(hash3) : await store.internalizeOne(hash3);
320569
- report.rows++;
320570
- report.bytes += n3;
320571
- } catch (err) {
320572
- fail(hash3, err);
320573
- }
320574
- after = hash3;
320873
+ for (const hash3 of hashes) {
320874
+ if (stop()) {
320875
+ report.interrupted = true;
320876
+ break;
320877
+ }
320878
+ try {
320879
+ const n3 = op2 === "externalize" ? await store.externalizeOne(hash3) : op2 === "internalize" ? await store.internalizeOne(hash3) : await store.purgePgBytesOne(hash3);
320880
+ report.rows++;
320881
+ report.bytes += n3;
320882
+ } catch (err) {
320883
+ fail(hash3, err);
320575
320884
  }
320885
+ after = hash3;
320576
320886
  }
320577
320887
  report.batches++;
320578
320888
  opts.onProgress?.(progressOf(report));
@@ -322376,10 +322686,10 @@ __export(src_exports2, {
322376
322686
  assertBindingWritePermission: () => assertBindingWritePermission,
322377
322687
  assertCompanyManager: () => assertCompanyManager,
322378
322688
  assertCreatableVariable: () => assertCreatableVariable,
322379
- assertFirstVersionAssignee: () => assertFirstVersionAssignee,
322380
322689
  assertMayCreateAgent: () => assertMayCreateAgent,
322381
322690
  assertMayManageAgent: () => assertMayManageAgent,
322382
322691
  assertMaySelfOrManage: () => assertMaySelfOrManage,
322692
+ assignedTitle: () => assignedTitle,
322383
322693
  assistantBoundToMember: () => assistantBoundToMember,
322384
322694
  assistantNameFor: () => assistantNameFor,
322385
322695
  assistantRowRecovery: () => assistantRowRecovery,
@@ -322417,9 +322727,11 @@ __export(src_exports2, {
322417
322727
  buildWorkorderMetrics: () => buildWorkorderMetrics,
322418
322728
  buildWorkorderSummaries: () => buildWorkorderSummaries,
322419
322729
  builtinSkillToFiles: () => builtinSkillToFiles,
322420
- canAccessTodo: () => canAccessTodo,
322421
322730
  canAnnounceRuntimeSwitch: () => canAnnounceRuntimeSwitch,
322731
+ canChangeTodo: () => canChangeTodo,
322422
322732
  canRetryAllocation: () => canRetryAllocation,
322733
+ canSeeTodo: () => canSeeTodo,
322734
+ changedTitle: () => changedTitle,
322423
322735
  chatBroadcastEnabledFromEnv: () => chatBroadcastEnabledFromEnv,
322424
322736
  chatDelegationEnabledFromEnv: () => chatDelegationEnabledFromEnv,
322425
322737
  chatDispatchScope: () => chatDispatchScope,
@@ -322794,6 +323106,7 @@ __export(src_exports2, {
322794
323106
  stableChatTraceRunId: () => stableChatTraceRunId,
322795
323107
  stableJson: () => stableJson2,
322796
323108
  startOasisServer: () => startOasisServer,
323109
+ statusTitle: () => statusTitle,
322797
323110
  stopDispatchesForDeletedActor: () => stopDispatchesForDeletedActor,
322798
323111
  stripRetiredCdBotFromDrafts: () => stripRetiredCdBotFromDrafts,
322799
323112
  subagentRunEventFromLive: () => subagentRunEventFromLive,
@@ -322811,7 +323124,10 @@ __export(src_exports2, {
322811
323124
  toBundleFiles: () => toBundleFiles,
322812
323125
  toChatDispatchRequest: () => toChatDispatchRequest,
322813
323126
  toYamlDoubleQuoted: () => toYamlDoubleQuoted,
323127
+ todoAssignedSourceKey: () => todoAssignedSourceKey,
323128
+ todoChangeSourceKey: () => todoChangeSourceKey,
322814
323129
  todoReminderSourceKey: () => todoReminderSourceKey,
323130
+ todoUnseenSourceKey: () => todoUnseenSourceKey,
322815
323131
  todosDomain: () => todosDomain,
322816
323132
  touchKey: () => touchKey,
322817
323133
  traceDomain: () => traceDomain,
@@ -322820,6 +323136,7 @@ __export(src_exports2, {
322820
323136
  trustedDelegationInvocationId: () => trustedDelegationInvocationId,
322821
323137
  trustedKnowledgeInvocationId: () => trustedKnowledgeInvocationId,
322822
323138
  typesDomain: () => typesDomain,
323139
+ unseenTitle: () => unseenTitle,
322823
323140
  upgradeDefaultFieldDefs: () => upgradeDefaultFieldDefs,
322824
323141
  validateCron: () => validateCron,
322825
323142
  validateKnowledgeAnswer: () => validateKnowledgeAnswer,
@@ -325596,7 +325913,24 @@ function createTodoRuntime(options) {
325596
325913
  const id = companyId ?? options.defaultCompanyId();
325597
325914
  let value2 = cache5.get(id);
325598
325915
  if (!value2) {
325599
- value2 = options.store(id).then((store) => new TodoService({ store, timeZone: () => options.timeZone(id) }));
325916
+ value2 = options.store(id).then((store) => new TodoService({
325917
+ store,
325918
+ timeZone: () => options.timeZone(id),
325919
+ ...options.people ? { people: () => options.people(id) } : {},
325920
+ ...options.runOf ? { runOf: options.runOf } : {},
325921
+ // 派给别人的待办的告知(ADR 0640):与到期提醒同一条路——直接 insert 进台账 `todo` 类,新写的推送 + 刷铃铛。
325922
+ notify: async (records) => {
325923
+ const ledger = await options.notifications(id);
325924
+ if (!ledger) return;
325925
+ const fresh = [];
325926
+ for (const r5 of records) {
325927
+ const row = await ledger.insert(r5);
325928
+ if (row.enteredAt === r5.enteredAt) fresh.push(row);
325929
+ }
325930
+ if (fresh.length > 0) options.onReminders?.(id, fresh);
325931
+ },
325932
+ warn: options.warn
325933
+ }));
325600
325934
  cache5.set(id, value2);
325601
325935
  void value2.catch(() => cache5.delete(id));
325602
325936
  }
@@ -325614,6 +325948,7 @@ function createTodoRuntime(options) {
325614
325948
  todos: options.store,
325615
325949
  notifications: options.notifications,
325616
325950
  timeZone: options.timeZone,
325951
+ ...options.people ? { nameOf: async (companyId, actorId) => (await options.people(companyId)).find((p4) => p4.actorId === actorId)?.name ?? actorId } : {},
325617
325952
  enabled,
325618
325953
  warn: options.warn,
325619
325954
  ...options.onReminders ? { onInserted: options.onReminders } : {}
@@ -325962,6 +326297,7 @@ function createConnectorRenewalRuntime(options) {
325962
326297
  init_store3();
325963
326298
 
325964
326299
  // ../storage/src/postgres-todos.ts
326300
+ init_src();
325965
326301
  init_pg_ident();
325966
326302
  init_schema_ddl_lock();
325967
326303
  var PostgresTodoStore = class _PostgresTodoStore {
@@ -325985,7 +326321,7 @@ var PostgresTodoStore = class _PostgresTodoStore {
325985
326321
  due_time text,
325986
326322
  start_date text,
325987
326323
  start_time text,
325988
- status text NOT NULL CHECK (status IN ('open','done','cancelled')),
326324
+ status text NOT NULL CONSTRAINT todos_status_check CHECK (status IN (${STATUS_LIST})),
325989
326325
  done_at timestamptz,
325990
326326
  cancelled_at timestamptz,
325991
326327
  source_chat_session_id text,
@@ -325993,6 +326329,17 @@ var PostgresTodoStore = class _PostgresTodoStore {
325993
326329
  created_at timestamptz NOT NULL,
325994
326330
  updated_at timestamptz NOT NULL
325995
326331
  )`);
326332
+ await pool.query(`ALTER TABLE ${store.s}.todos ADD COLUMN IF NOT EXISTS sent_at timestamptz`);
326333
+ await pool.query(`ALTER TABLE ${store.s}.todos ADD COLUMN IF NOT EXISTS first_viewed_at timestamptz`);
326334
+ await pool.query(`ALTER TABLE ${store.s}.todos ADD COLUMN IF NOT EXISTS source_run_id text`);
326335
+ const check2 = await pool.query(`SELECT pg_get_constraintdef(c.oid) AS def FROM pg_constraint c
326336
+ JOIN pg_class t ON t.oid = c.conrelid JOIN pg_namespace n ON n.oid = t.relnamespace
326337
+ WHERE n.nspname = $1 AND t.relname = 'todos' AND c.conname = 'todos_status_check'`, [schema]);
326338
+ const def = check2.rows[0]?.def;
326339
+ if (!def || !def.includes("'draft'")) {
326340
+ await pool.query(`ALTER TABLE ${store.s}.todos DROP CONSTRAINT IF EXISTS todos_status_check`);
326341
+ await pool.query(`ALTER TABLE ${store.s}.todos ADD CONSTRAINT todos_status_check CHECK (status IN (${STATUS_LIST}))`);
326342
+ }
325996
326343
  await pool.query(`CREATE INDEX IF NOT EXISTS todos_assignee_status_due_idx ON ${store.s}.todos (assignee_actor_id, status, due_date)`);
325997
326344
  await pool.query(`CREATE INDEX IF NOT EXISTS todos_creator_status_due_idx ON ${store.s}.todos (creator_actor_id, status, due_date)`);
325998
326345
  await pool.query(`CREATE INDEX IF NOT EXISTS todos_open_due_idx ON ${store.s}.todos (due_date) WHERE status = 'open'`);
@@ -326038,8 +326385,9 @@ var PostgresTodoStore = class _PostgresTodoStore {
326038
326385
  async create(todo, changes) {
326039
326386
  return this.tx(async (client) => {
326040
326387
  const r5 = await client.query(`INSERT INTO ${this.s}.todos (id,title,note,creator_actor_id,assignee_actor_id,recorded_by_actor_id,
326041
- due_date,due_time,status,done_at,cancelled_at,source_chat_session_id,linked_workorder_id,created_at,updated_at,start_date,start_time)
326042
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17) RETURNING *`, rowValues(todo));
326388
+ due_date,due_time,status,done_at,cancelled_at,source_chat_session_id,linked_workorder_id,created_at,updated_at,start_date,start_time,
326389
+ sent_at,first_viewed_at,source_run_id)
326390
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20) RETURNING *`, rowValues(todo));
326043
326391
  await this.insertChanges(client, changes);
326044
326392
  return fromRow3(r5.rows[0]);
326045
326393
  });
@@ -326052,8 +326400,9 @@ var PostgresTodoStore = class _PostgresTodoStore {
326052
326400
  return this.tx(async (client) => {
326053
326401
  const r5 = await client.query(`UPDATE ${this.s}.todos SET title=$2,note=$3,creator_actor_id=$4,assignee_actor_id=$5,
326054
326402
  recorded_by_actor_id=$6,due_date=$7,due_time=$8,status=$9,done_at=$10,cancelled_at=$11,source_chat_session_id=$12,
326055
- linked_workorder_id=$13,created_at=$14,updated_at=$15,start_date=$16,start_time=$17
326056
- WHERE id=$1 AND updated_at=$18::timestamptz RETURNING *`, [...rowValues(next), expectedUpdatedAt]);
326403
+ linked_workorder_id=$13,created_at=$14,updated_at=$15,start_date=$16,start_time=$17,
326404
+ sent_at=$18,first_viewed_at=COALESCE(first_viewed_at,$19),source_run_id=$20
326405
+ WHERE id=$1 AND updated_at=$21::timestamptz RETURNING *`, [...rowValues(next), expectedUpdatedAt]);
326057
326406
  if (!r5.rows[0]) return null;
326058
326407
  await this.insertChanges(client, changes);
326059
326408
  return fromRow3(r5.rows[0]);
@@ -326065,6 +326414,31 @@ var PostgresTodoStore = class _PostgresTodoStore {
326065
326414
  ORDER BY due_date, COALESCE(due_time,'99:99'), created_at, id`, [actorId, [...statuses2]]);
326066
326415
  return r5.rows.map(fromRow3);
326067
326416
  }
326417
+ async search(f6) {
326418
+ const where = ["status = ANY($1::text[])"];
326419
+ const args = [[...f6.statuses]];
326420
+ const arg = (v2) => {
326421
+ args.push(v2);
326422
+ return `$${args.length}`;
326423
+ };
326424
+ if (f6.assignee) where.push(`assignee_actor_id = ${arg(f6.assignee)}`);
326425
+ if (f6.creator) where.push(`creator_actor_id = ${arg(f6.creator)}`);
326426
+ if (f6.dueFrom) where.push(`due_date >= ${arg(f6.dueFrom)}`);
326427
+ if (f6.dueTo) where.push(`due_date <= ${arg(f6.dueTo)}`);
326428
+ if (f6.chatSessionId) where.push(`source_chat_session_id = ${arg(f6.chatSessionId)}`);
326429
+ if (f6.q) {
326430
+ const like = arg(`%${f6.q.replace(/[\\%_]/g, (c5) => `\\${c5}`)}%`);
326431
+ where.push(`(title ILIKE ${like} OR COALESCE(note,'') ILIKE ${like})`);
326432
+ }
326433
+ where.push(`NOT (creator_actor_id <> assignee_actor_id AND sent_at IS NULL AND creator_actor_id <> ${arg(f6.viewer)})`);
326434
+ const r5 = await this.pool.query(`SELECT * FROM ${this.s}.todos WHERE ${where.join(" AND ")}
326435
+ ORDER BY due_date, COALESCE(due_time,'99:99'), created_at, id LIMIT ${arg(f6.limit)}`, args);
326436
+ return r5.rows.map(fromRow3);
326437
+ }
326438
+ async markFirstViewed(id, at2) {
326439
+ await this.pool.query(`UPDATE ${this.s}.todos SET first_viewed_at=$2 WHERE id=$1 AND first_viewed_at IS NULL`, [id, at2]);
326440
+ return this.get(id);
326441
+ }
326068
326442
  async listOpenReminderCandidates(fromDay, toDay) {
326069
326443
  const r5 = await this.pool.query(`SELECT * FROM ${this.s}.todos
326070
326444
  WHERE status='open' AND (due_date BETWEEN $1 AND $2 OR start_date BETWEEN $1 AND $2)
@@ -326083,6 +326457,7 @@ var PostgresTodoStore = class _PostgresTodoStore {
326083
326457
  });
326084
326458
  }
326085
326459
  };
326460
+ var STATUS_LIST = TODO_STATUSES.map((v2) => `'${v2}'`).join(",");
326086
326461
  var iso8 = (v2) => v2 instanceof Date ? v2.toISOString() : typeof v2 === "string" ? new Date(v2).toISOString() : void 0;
326087
326462
  function rowValues(t) {
326088
326463
  return [
@@ -326102,7 +326477,10 @@ function rowValues(t) {
326102
326477
  t.createdAt,
326103
326478
  t.updatedAt,
326104
326479
  t.startDate ?? null,
326105
- t.startTime ?? null
326480
+ t.startTime ?? null,
326481
+ t.sentAt ?? null,
326482
+ t.firstViewedAt ?? null,
326483
+ t.sourceRunId ?? null
326106
326484
  ];
326107
326485
  }
326108
326486
  function fromRow3(row) {
@@ -326125,7 +326503,10 @@ function fromRow3(row) {
326125
326503
  ["doneAt", iso8(row["done_at"])],
326126
326504
  ["cancelledAt", iso8(row["cancelled_at"])],
326127
326505
  ["sourceChatSessionId", row["source_chat_session_id"]],
326128
- ["linkedWorkorderId", row["linked_workorder_id"]]
326506
+ ["linkedWorkorderId", row["linked_workorder_id"]],
326507
+ ["sentAt", iso8(row["sent_at"])],
326508
+ ["firstViewedAt", iso8(row["first_viewed_at"])],
326509
+ ["sourceRunId", row["source_run_id"]]
326129
326510
  ];
326130
326511
  for (const [k6, v2] of opt) if (v2 !== void 0 && v2 !== null) t[k6] = v2;
326131
326512
  return t;
@@ -329725,10 +330106,17 @@ async function startServe(opts) {
329725
330106
  timeZone: (companyId) => companies.service.effectiveTimeZoneOf(companyId),
329726
330107
  boundHuman: async (agentId, companyId) => (await (await resolveAssistantsCtx(companyId)).service.getByAgent(agentId))?.humanActorId ?? null,
329727
330108
  notifications: (companyId) => notificationStoreFor(companyId),
330109
+ // 派给别人(ADR 0640):承接人只能是本公司走完新手指引的真人成员(与组织成员页同一份名单)。
330110
+ people: async (companyId) => (await companies.service.listMemberViews(companyId)).map((m4) => ({
330111
+ actorId: m4.actorId,
330112
+ name: m4.displayName?.trim() || m4.name?.trim() || m4.email || m4.actorId,
330113
+ aliases: [m4.name, m4.email, m4.email?.split("@")[0]].filter((v2) => !!v2 && !!v2.trim())
330114
+ })),
330115
+ runOf: (chatSessionId) => liveChat.runFor(chatSessionId),
329728
330116
  onReminders: (companyId, records) => {
329729
330117
  pushNotifier?.notify(companyId, records);
329730
330118
  const recipients = [...new Set(records.map((r5) => r5.recipient))];
329731
- console.warn(`[todos] ${companyId} \u5230\u671F\u63D0\u9192 ${records.length} \u6761 \u2192 ${recipients.join(",")}`);
330119
+ console.warn(`[todos] ${companyId} \u5F85\u529E\u901A\u77E5 ${records.length} \u6761 \u2192 ${recipients.join(",")}`);
329732
330120
  auditSink.notifyNotificationsChanged(recipients);
329733
330121
  },
329734
330122
  defaultCompanyId: () => defaultCompanyId,
@@ -338999,16 +339387,19 @@ var COMMAND_DECLS = {
338999
339387
  ],
339000
339388
  examples: ["oasis var rm STALE_KEY --scope project --project oasis", "oasis var rm --id var_1234 --yes"]
339001
339389
  },
339002
- // —— 个人待办(ADR 0635):人对助理说「帮我记一下」→ 助理用这组命令记、改、勾掉。发起人 / 承接人由服务端按调用者
339003
- // 填(agent 调 = 它绑定的那个真人),命令行不收。开关 OASIS_PERSONAL_TODO=off 时服务端 404。 ——
339390
+ // —— 个人待办(ADR 0635):人对助理说「帮我记一下」→ 助理用这组命令记、改、勾掉。发起人由服务端按调用者
339391
+ // 填(agent 调 = 它绑定的那个真人),命令行不收;派给别人用 --to(ADR 0640,先成草案、人点发出才生效)。
339392
+ // 开关 OASIS_PERSONAL_TODO=off 时服务端 404。 ——
339004
339393
  todo: {
339005
339394
  group: "\u4E2A\u4EBA\u5F85\u529E",
339006
339395
  usage: "oasis todo <add|list|show|edit|done|cancel|reopen> ...",
339007
- description: "\u4E2A\u4EBA\u5F85\u529E\uFF1A\u66FF\u4F60\u7ED1\u5B9A\u7684\u90A3\u4E2A\u771F\u4EBA\u8BB0\u4E0B\u6709\u622A\u6B62\u65E5\u7684\u4E8B\uFF0C\u5230\u671F\u524D\u4E00\u5929 09:00\u3001\u5F53\u5929 09:00 \u7CFB\u7EDF\u4F1A\u63D0\u9192 TA\uFF08\u8BBE\u4E86\u5F00\u59CB\u65E5\u7684\uFF0C\u5F00\u59CB\u5F53\u5929 09:00 \u4E5F\u63D0\u9192\u4E00\u6B21\uFF09\u3002\u6BCF\u6761\u8F93\u51FA\u7B2C\u4E00\u884C\u662F\u300C\u4ECA\u5929\uFF1AYYYY-MM-DD \u5468X\uFF08\u65F6\u533A\uFF09\u300D\u2014\u2014\u6362\u7B97\u300C\u8FD9\u5468\u4E94\u300D\u300C\u6708\u5E95\u300D\u4E00\u5F8B\u4EE5\u5B83\u4E3A\u51C6\u3002**\u6CA1\u6709 `oasis todo` \u547D\u4EE4\u6267\u884C\u6210\u529F\uFF0C\u5C31\u4E0D\u80FD\u8BF4\u8BB0\u4E0B\u4E86\u3002**",
339396
+ description: "\u5F85\u529E\uFF1A\u66FF\u4F60\u7ED1\u5B9A\u7684\u90A3\u4E2A\u771F\u4EBA\u8BB0\u4E0B\u6709\u622A\u6B62\u65E5\u7684\u5DE5\u4F5C\uFF0C\u6216\u6D3E\u7ED9\u672C\u516C\u53F8\u7684\u540C\u4E8B\uFF08--to\uFF0C\u5148\u6210\u8349\u6848\u3001TA \u5728\u5361\u4E0A\u70B9\u300C\u53D1\u51FA\u300D\u624D\u751F\u6548\uFF09\uFF1B\u5230\u671F\u524D\u4E00\u5929 09:00\u3001\u5F53\u5929 09:00 \u7CFB\u7EDF\u4F1A\u63D0\u9192\u627F\u63A5\u4EBA\uFF08\u8BBE\u4E86\u5F00\u59CB\u65E5\u7684\uFF0C\u5F00\u59CB\u5F53\u5929 09:00 \u4E5F\u63D0\u9192\u4E00\u6B21\uFF09\u3002\u5168\u516C\u53F8\u7684\u5F85\u529E\u90FD\u80FD\u67E5\uFF08list --assignee / --creator / --everyone / --q\uFF09\u3002\u6BCF\u6761\u8F93\u51FA\u7B2C\u4E00\u884C\u662F\u300C\u4ECA\u5929\uFF1AYYYY-MM-DD \u5468X\uFF08\u65F6\u533A\uFF09\u300D\u2014\u2014\u6362\u7B97\u300C\u8FD9\u5468\u4E94\u300D\u300C\u6708\u5E95\u300D\u4E00\u5F8B\u4EE5\u5B83\u4E3A\u51C6\u3002**\u6CA1\u6709 `oasis todo` \u547D\u4EE4\u6267\u884C\u6210\u529F\uFF0C\u5C31\u4E0D\u80FD\u8BF4\u8BB0\u4E0B\u4E86\u3002**",
339008
339397
  subCommands: ["todo add", "todo list", "todo show", "todo edit", "todo done", "todo cancel", "todo reopen"],
339009
339398
  examples: [
339010
339399
  'oasis todo add --title "\u4EA4 Q3 \u590D\u76D8\u62A5\u544A" --note "\u738B\u603B\u8981\uFF0C\u5468\u4F1A\u524D\u8FC7\u4E00\u904D" --due 2026-10-09',
339400
+ 'oasis todo add --to \u5955\u7487 --title "\u6574\u7406\u7ADE\u54C1\u62A5\u4EF7" --due 2026-10-15',
339011
339401
  "oasis todo list",
339402
+ "oasis todo list --assignee \u5955\u7487",
339012
339403
  "oasis todo show td_3f9a1c2b",
339013
339404
  'oasis todo edit td_3f9a1c2b --due 2026-10-12 --reason "\u6392\u671F\u8C03\u6574"',
339014
339405
  "oasis todo done td_3f9a1c2b"
@@ -339016,9 +339407,10 @@ var COMMAND_DECLS = {
339016
339407
  },
339017
339408
  "todo add": {
339018
339409
  group: "\u4E2A\u4EBA\u5F85\u529E",
339019
- usage: "oasis todo add --title <\u4E00\u53E5\u8BDD\uFF0C\u52A8\u8BCD\u5F00\u5934> [--note <\u8C01\u8981\u3001\u4E3A\u4EC0\u4E48\u3001\u539F\u8BDD\u8981\u70B9>] [--start YYYY-MM-DD [--start-time HH:mm]] --due YYYY-MM-DD [--time HH:mm]",
339020
- description: "\u8BB0\u4E00\u6761\u5F85\u529E\u3002--due / --start \u53EA\u6536\u4E0D\u5E26\u65F6\u533A\u7684\u65E5\u671F\uFF08\u7EC4\u7EC7\u65F6\u533A\u91CC\u7684\u90A3\u4E00\u5929\uFF09\uFF0C\u975E\u6CD5\u65E5\u671F\u5F53\u573A\u62A5\u9519\uFF1B\u6CA1\u8BF4\u51E0\u70B9\u5C31\u522B\u7ED9 --time\uFF1B\u7528\u6237\u6CA1\u8BF4\u4EC0\u4E48\u65F6\u5019\u5F00\u59CB\u5C31\u522B\u7ED9 --start\uFF08\u4E0D\u8FFD\u95EE\u3001\u4E0D\u731C\uFF09\uFF0C\u5F00\u59CB\u4E0D\u80FD\u665A\u4E8E\u622A\u6B62\u3002\u6765\u6E90\u5BF9\u8BDD\u81EA\u52A8\u53D6 $OASIS_CHAT_SESSION_ID\uFF08\u63D0\u9192\u70B9\u5F00\u4F1A\u8DF3\u56DE\u8FD9\u91CC\uFF09\u3002\u6210\u529F\u8F93\u51FA\u300C\u8BB0\u4E0B\u4E86\uFF1A<\u6807\u9898>\uFF0C\u622A\u6B62 X \u6708 X \u65E5\uFF08\u5468X\uFF09\u300D\u3002",
339410
+ usage: "oasis todo add [--to <\u4EBA>] --title <\u4E00\u53E5\u8BDD\uFF0C\u52A8\u8BCD\u5F00\u5934> [--note <\u8C01\u8981\u3001\u4E3A\u4EC0\u4E48\u3001\u539F\u8BDD\u8981\u70B9>] [--start YYYY-MM-DD [--start-time HH:mm]] --due YYYY-MM-DD [--time HH:mm]",
339411
+ description: "\u8BB0\u4E00\u6761\u5F85\u529E\u3002--due / --start \u53EA\u6536\u4E0D\u5E26\u65F6\u533A\u7684\u65E5\u671F\uFF08\u7EC4\u7EC7\u65F6\u533A\u91CC\u7684\u90A3\u4E00\u5929\uFF09\uFF0C\u975E\u6CD5\u65E5\u671F\u5F53\u573A\u62A5\u9519\uFF1B\u6CA1\u8BF4\u51E0\u70B9\u5C31\u522B\u7ED9 --time\uFF1B\u7528\u6237\u6CA1\u8BF4\u4EC0\u4E48\u65F6\u5019\u5F00\u59CB\u5C31\u522B\u7ED9 --start\uFF08\u4E0D\u8FFD\u95EE\u3001\u4E0D\u731C\uFF09\uFF0C\u5F00\u59CB\u4E0D\u80FD\u665A\u4E8E\u622A\u6B62\u3002\u6765\u6E90\u5BF9\u8BDD\u81EA\u52A8\u53D6 $OASIS_CHAT_SESSION_ID\uFF08\u63D0\u9192\u70B9\u5F00\u4F1A\u8DF3\u56DE\u8FD9\u91CC\uFF09\u3002\u7ED9\u81EA\u5DF1\u8BB0\uFF1A\u6210\u529F\u8F93\u51FA\u300C\u8BB0\u4E0B\u4E86\uFF1A<\u6807\u9898>\uFF0C\u622A\u6B62 X \u6708 X \u65E5\uFF08\u5468X\uFF09\u300D\u3002--to \u6D3E\u7ED9\u522B\u4EBA\uFF1A\u53EA\u5B58\u6210**\u8349\u6848**\uFF0C\u8F93\u51FA\u300C\u8349\u6848\uFF08\u8FD8\u6CA1\u53D1\u51FA\uFF09\uFF1A\u6D3E\u7ED9 X\uFF1A\u2026\u300D\uFF0C\u5BF9\u8BDD\u91CC\u51FA\u4E00\u5F20\u8349\u6848\u5361\uFF0C\u7528\u6237\u70B9\u300C\u53D1\u51FA\u300D\u624D\u751F\u6548\u3001\u5BF9\u65B9\u624D\u6536\u5230\u901A\u77E5\u2014\u2014\u4F60\u4E0D\u80FD\u66FF TA \u53D1\u51FA\u3002--to \u8BA4 actorId \u6216\u540D\u5B57\uFF0C\u5BF9\u4E0D\u4E0A\u6216\u5BF9\u4E0A\u4E0D\u6B62\u4E00\u4E2A\u76F4\u63A5\u62A5\u9519\uFF0C\u522B\u731C\uFF0C\u95EE\u7528\u6237\u662F\u54EA\u4F4D\u3002",
339021
339412
  flags: [
339413
+ { name: "to", desc: "\u6D3E\u7ED9\u8C01\uFF1A\u672C\u516C\u53F8\u771F\u4EBA\u7684 actorId \u6216\u540D\u5B57\uFF08\u4E0D\u7ED9 = \u7ED9\u81EA\u5DF1\u8BB0\uFF09", own: true },
339022
339414
  { name: "title", desc: "\u6807\u9898\uFF1A\u4E00\u53E5\u8BDD\u3001\u52A8\u8BCD\u5F00\u5934", required: true, own: true },
339023
339415
  { name: "note", desc: "\u5907\u6CE8\uFF1A\u8C01\u8981\u3001\u4E3A\u4EC0\u4E48\u8981\u3001\u539F\u8BDD\u91CC\u7684\u5173\u952E\u4FE1\u606F", own: true },
339024
339416
  { name: "due", desc: "\u622A\u6B62\u65E5 YYYY-MM-DD\uFF08\u4E0D\u5E26\u65F6\u533A\uFF09", required: true, own: true },
@@ -339026,25 +339418,35 @@ var COMMAND_DECLS = {
339026
339418
  { name: "start", desc: "\u5F00\u59CB\u65E5 YYYY-MM-DD\uFF08\u7528\u6237\u8BF4\u4E86\u300C\u54EA\u5929\u5F00\u59CB\u5F04\u300D\u624D\u7ED9\uFF09", own: true },
339027
339419
  { name: "start-time", desc: "\u5F00\u59CB\u65F6\u523B HH:mm\uFF08\u914D --start\uFF09", own: true }
339028
339420
  ],
339029
- examples: ['oasis todo add --title "\u4EA4 Q3 \u590D\u76D8\u62A5\u544A" --due 2026-10-09 --time 18:00']
339421
+ examples: ['oasis todo add --title "\u4EA4 Q3 \u590D\u76D8\u62A5\u544A" --due 2026-10-09 --time 18:00', 'oasis todo add --to \u5955\u7487 --title "\u6574\u7406\u7ADE\u54C1\u62A5\u4EF7" --due 2026-10-15']
339030
339422
  },
339031
339423
  "todo list": {
339032
339424
  group: "\u4E2A\u4EBA\u5F85\u529E",
339033
- usage: "oasis todo list [--all]",
339034
- description: "\u5217\u51FA\u5F85\u529E\uFF0C\u6309\u622A\u6B62\u65E5\u6392\u5E8F\uFF0C\u8FC7\u671F\u7684\u6807\u300C\u5DF2\u8FC7\u671F\u300D\uFF1B\u8BBE\u4E86\u5F00\u59CB\u65E5\u7684\u663E\u793A\u300CX \u6708 X \u65E5\u5F00\u59CB \u2192 X \u6708 X \u65E5\u622A\u6B62\u300D\u3002\u9ED8\u8BA4\u53EA\u5217\u672A\u5B8C\u6210\u7684\uFF1B--all \u8FDE\u5DF2\u5B8C\u6210\u3001\u5DF2\u53D6\u6D88\u7684\u4E00\u8D77\u5217\u3002",
339035
- flags: [{ name: "all", desc: "\u8FDE\u5DF2\u5B8C\u6210 / \u5DF2\u53D6\u6D88\u7684\u4E00\u8D77\u5217", boolean: true, own: true }]
339425
+ usage: "oasis todo list [--assignee <\u4EBA>] [--creator <\u4EBA>] [--everyone] [--q <\u5173\u952E\u8BCD>] [--due-from YYYY-MM-DD] [--due-to YYYY-MM-DD] [--status <s,\u2026>] [--all]",
339426
+ description: "\u5217\u51FA\u5F85\u529E\uFF0C\u6309\u622A\u6B62\u65E5\u6392\u5E8F\uFF0C\u6BCF\u884C\u5E26\u627F\u63A5\u4EBA\u548C\u53D1\u8D77\u4EBA\uFF0C\u8FC7\u671F\u7684\u6807\u300C\u5DF2\u8FC7\u671F\u300D\u3001\u6CA1\u53D1\u51FA\u7684\u8349\u6848\u6807\u300C\u8349\u6848\xB7\u672A\u53D1\u51FA\u300D\uFF1B\u8BBE\u4E86\u5F00\u59CB\u65E5\u7684\u663E\u793A\u300CX \u6708 X \u65E5\u5F00\u59CB \u2192 X \u6708 X \u65E5\u622A\u6B62\u300D\u3002\u4EC0\u4E48\u90FD\u4E0D\u7ED9 = \u6211\u627F\u63A5\u7684\u3001\u672A\u5B8C\u6210\u7684\u3002\u5168\u516C\u53F8\u7684\u5F85\u529E\u90FD\u80FD\u67E5\uFF1A--assignee / --creator \u6309\u4EBA\uFF08actorId\u3001\u540D\u5B57\u6216 me\uFF09\uFF0C--everyone \u4E0D\u6309\u4EBA\uFF0C--q \u641C\u6807\u9898\u548C\u5907\u6CE8\uFF0C--due-from / --due-to \u622A\u6B62\u65E5\u8303\u56F4\uFF08\u542B\u4E24\u7AEF\uFF09\u3002\u522B\u4EBA\u7684\u8349\u6848\u6C38\u8FDC\u67E5\u4E0D\u5230\u3002",
339427
+ flags: [
339428
+ { name: "assignee", desc: "\u627F\u63A5\u4EBA\uFF08actorId\u3001\u540D\u5B57\u6216 me\uFF09", own: true },
339429
+ { name: "creator", desc: "\u53D1\u8D77\u4EBA\uFF08actorId\u3001\u540D\u5B57\u6216 me\uFF09", own: true },
339430
+ { name: "everyone", desc: "\u4E0D\u6309\u4EBA\u7B5B\uFF0C\u5168\u516C\u53F8", boolean: true, own: true },
339431
+ { name: "q", desc: "\u5173\u952E\u8BCD\uFF1A\u6807\u9898\u6216\u5907\u6CE8\u91CC\u5305\u542B", own: true },
339432
+ { name: "due-from", desc: "\u622A\u6B62\u65E5\u4E0D\u65E9\u4E8E YYYY-MM-DD", own: true },
339433
+ { name: "due-to", desc: "\u622A\u6B62\u65E5\u4E0D\u665A\u4E8E YYYY-MM-DD", own: true },
339434
+ { name: "status", desc: "\u72B6\u6001\uFF0C\u53EF\u591A\u9009\u9017\u53F7\u5206\u9694\uFF1Adraft / open / done / cancelled\uFF08\u9ED8\u8BA4 open\uFF09", own: true },
339435
+ { name: "all", desc: "\u56DB\u79CD\u72B6\u6001\u90FD\u5217\uFF08\u542B\u5DF2\u5B8C\u6210 / \u5DF2\u53D6\u6D88\uFF1B\u8349\u6848\u53EA\u5217\u81EA\u5DF1\u53D1\u8D77\u7684\uFF09", boolean: true, own: true }
339436
+ ],
339437
+ examples: ["oasis todo list --assignee \u5955\u7487", "oasis todo list --everyone --due-from 2026-10-12 --due-to 2026-10-18", "oasis todo list --everyone --q \u62A5\u4EF7 --all", "oasis todo list --creator me"]
339036
339438
  },
339037
339439
  "todo show": {
339038
339440
  group: "\u4E2A\u4EBA\u5F85\u529E",
339039
339441
  usage: "oasis todo show <id>",
339040
- description: "\u770B\u4E00\u6761\u5F85\u529E\u7684\u5168\u8C8C\uFF1A\u6807\u9898\u3001\u5F00\u59CB \u2192 \u622A\u6B62\u3001\u72B6\u6001\u3001\u5907\u6CE8\u5168\u6587\uFF0C\u4EE5\u53CA\u53D8\u66F4\u8BB0\u5F55\uFF08\u65F6\u95F4\u3001\u8C01\u6539\u7684 / \u7ECF\u624B agent\u3001\u5B57\u6BB5\u3001\u65E7\u503C \u2192 \u65B0\u503C\u3001\u539F\u56E0\uFF0C\u65B0\u7684\u5728\u524D\uFF09\u3002\u6539\u5907\u6CE8\u524D\u5148\u7528\u5B83\u8BFB\u51FA\u5F53\u524D\u5907\u6CE8\u5168\u6587\uFF1B\u7528\u6237\u95EE\u300C\u4E4B\u524D\u5199\u7684\u662F\u4EC0\u4E48\u300D\u4E5F\u67E5\u8FD9\u91CC\u3002",
339442
+ description: "\u770B\u4E00\u6761\u5F85\u529E\u7684\u5168\u8C8C\uFF1A\u6807\u9898\u3001\u5F00\u59CB \u2192 \u622A\u6B62\u3001\u72B6\u6001\u3001\u53D1\u8D77\u4EBA\u3001\u627F\u63A5\u4EBA\uFF08\u6D3E\u7ED9\u522B\u4EBA\u7684\u8FD8\u6709\u53D1\u51FA\u65F6\u95F4\u3001\u627F\u63A5\u4EBA\u9996\u6B21\u67E5\u770B\u65F6\u95F4\uFF09\u3001\u5907\u6CE8\u5168\u6587\uFF0C\u4EE5\u53CA\u53D8\u66F4\u8BB0\u5F55\uFF08\u65F6\u95F4\u3001\u8C01\u6539\u7684 / \u7ECF\u624B agent\u3001\u5B57\u6BB5\u3001\u65E7\u503C \u2192 \u65B0\u503C\u3001\u539F\u56E0\uFF0C\u65B0\u7684\u5728\u524D\uFF09\u3002\u6539\u5907\u6CE8\u524D\u5148\u7528\u5B83\u8BFB\u51FA\u5F53\u524D\u5907\u6CE8\u5168\u6587\uFF1B\u7528\u6237\u95EE\u300C\u4E4B\u524D\u5199\u7684\u662F\u4EC0\u4E48\u300D\u4E5F\u67E5\u8FD9\u91CC\u3002\u672C\u516C\u53F8\u8C01\u7684\u5F85\u529E\u90FD\u80FD\u770B\uFF08\u522B\u4EBA\u7684\u8349\u6848\u9664\u5916\uFF09\uFF1B\u627F\u63A5\u4EBA\u7B2C\u4E00\u6B21\u770B\u4F1A\u88AB\u8BB0\u4E0B\u3002",
339041
339443
  positional: [{ name: "<id>", required: true, desc: "\u5F85\u529E id\uFF08oasis todo list \u7B2C\u4E00\u5217\uFF09" }],
339042
339444
  examples: ["oasis todo show td_3f9a1c2b"]
339043
339445
  },
339044
339446
  "todo edit": {
339045
339447
  group: "\u4E2A\u4EBA\u5F85\u529E",
339046
- usage: "oasis todo edit <id> [--title \u2026] [--note \u2026] [--start YYYY-MM-DD|none] [--start-time HH:mm|none] [--due YYYY-MM-DD] [--time HH:mm|none] [--workorder <\u5DE5\u5355 id>|none] [--reason \u2026]",
339047
- description: '\u6539\u4E00\u6761\u5F85\u529E\u7684\u5B57\u6BB5\uFF0C\u7ED9\u4E86\u54EA\u4E2A\u6539\u54EA\u4E2A\uFF1B\u6BCF\u6539\u4E00\u4E2A\u5B57\u6BB5\u90FD\u7559\u4E00\u6761\u8BB0\u5F55\uFF08\u65E7\u503C\u3001\u65B0\u503C\u3001\u539F\u56E0\uFF09\u3002\u6539\u671F\u8981\u5199\u539F\u56E0\uFF1A\u7528\u6237\u6CA1\u8BF4\u5C31\u95EE\u4E00\u53E5\uFF0C\u7528\u6237\u8BF4\u4E0D\u7528\u5C31\u4E0D\u5199\uFF0C\u522B\u7F16\u3002--start none\uFF08\u8FDE\u540C\u5F00\u59CB\u65F6\u523B\uFF09/ --start-time none / --time none / --workorder none \u6E05\u7A7A\uFF0C--note "" \u6E05\u7A7A\u5907\u6CE8\u3002',
339448
+ usage: "oasis todo edit <id> [--title \u2026] [--note \u2026] [--start YYYY-MM-DD|none] [--start-time HH:mm|none] [--due YYYY-MM-DD] [--time HH:mm|none] [--workorder <\u5DE5\u5355 id>|none] [--to <\u4EBA>] [--reason \u2026]",
339449
+ description: '\u6539\u4E00\u6761\u5F85\u529E\u7684\u5B57\u6BB5\uFF0C\u7ED9\u4E86\u54EA\u4E2A\u6539\u54EA\u4E2A\uFF1B\u6BCF\u6539\u4E00\u4E2A\u5B57\u6BB5\u90FD\u7559\u4E00\u6761\u8BB0\u5F55\uFF08\u65E7\u503C\u3001\u65B0\u503C\u3001\u539F\u56E0\uFF09\u3002\u53EA\u6709\u53D1\u8D77\u4EBA\u548C\u627F\u63A5\u4EBA\u80FD\u6539\uFF1B\u6D3E\u7ED9\u522B\u4EBA\u4E14\u5DF2\u53D1\u51FA\u7684\uFF0C\u6539\u4E86\u4F1A\u901A\u77E5\u53E6\u4E00\u65B9\u3002\u6539\u671F\u8981\u5199\u539F\u56E0\uFF1A\u7528\u6237\u6CA1\u8BF4\u5C31\u95EE\u4E00\u53E5\uFF1B**\u522B\u4EBA\u6D3E\u7ED9\u4F60\u7684\u6539\u622A\u6B62\u5FC5\u987B\u5E26 --reason**\uFF08\u4E0D\u5E26\u76F4\u63A5\u62A5\u9519\uFF09\uFF0C\u81EA\u5DF1\u8BB0\u7684\u7528\u6237\u8BF4\u4E0D\u7528\u5C31\u4E0D\u5199\uFF0C\u522B\u7F16\u3002--to \u53EA\u80FD\u5728\u8FD8\u6CA1\u53D1\u51FA\u7684\u8349\u6848\u4E0A\u6362\u627F\u63A5\u4EBA\u3002--start none\uFF08\u8FDE\u540C\u5F00\u59CB\u65F6\u523B\uFF09/ --start-time none / --time none / --workorder none \u6E05\u7A7A\uFF0C--note "" \u6E05\u7A7A\u5907\u6CE8\u3002',
339048
339450
  positional: [{ name: "<id>", required: true, desc: "\u5F85\u529E id\uFF08oasis todo list \u7B2C\u4E00\u5217\uFF09" }],
339049
339451
  flags: [
339050
339452
  { name: "title", desc: "\u65B0\u6807\u9898", own: true },
@@ -339054,28 +339456,31 @@ var COMMAND_DECLS = {
339054
339456
  { name: "due", desc: "\u65B0\u622A\u6B62\u65E5 YYYY-MM-DD", own: true },
339055
339457
  { name: "time", desc: "\u65B0\u622A\u6B62\u65F6\u523B HH:mm\uFF1Bnone = \u6E05\u7A7A", own: true },
339056
339458
  { name: "workorder", desc: "\u5173\u8054\u7684\u5DE5\u5355 id\uFF08\u300C\u8FD9\u4EF6\u4F60\u5E2E\u6211\u505A\u4E86\u5427\u300D\u5EFA\u5355\u540E\u6302\u4E0A\uFF09\uFF1Bnone = \u6E05\u7A7A", own: true },
339057
- { name: "reason", desc: "\u4E3A\u4EC0\u4E48\u6539\uFF08\u6539\u671F\u65F6\u8981\u5199\uFF1B\u7528\u6237\u8BF4\u4E0D\u7528\u5C31\u4E0D\u5199\uFF09", own: true }
339459
+ { name: "to", desc: "\u6362\u627F\u63A5\u4EBA\uFF08\u53EA\u9650\u8FD8\u6CA1\u53D1\u51FA\u7684\u8349\u6848\uFF09", own: true },
339460
+ { name: "reason", desc: "\u4E3A\u4EC0\u4E48\u6539\uFF08\u6539\u671F\u65F6\u8981\u5199\uFF1B\u522B\u4EBA\u6D3E\u7ED9\u4F60\u7684\u6539\u622A\u6B62\u5FC5\u987B\u5199\uFF09", own: true }
339058
339461
  ],
339059
339462
  examples: ['oasis todo edit td_3f9a1c2b --due 2026-10-12 --reason "\u5BA2\u6237\u5EF6\u671F\u4E00\u5468"']
339060
339463
  },
339061
339464
  "todo done": {
339062
339465
  group: "\u4E2A\u4EBA\u5F85\u529E",
339063
- usage: "oasis todo done <id>",
339064
- description: "\u6807\u8BB0\u5B8C\u6210\u3002\u5339\u914D\u5230\u591A\u6761\u65F6\u5148\u95EE\u7528\u6237\u662F\u54EA\u6761\uFF0C\u522B\u731C\u3002",
339065
- positional: [{ name: "<id>", required: true, desc: "\u5F85\u529E id" }]
339466
+ usage: "oasis todo done <id> [--reason \u2026]",
339467
+ description: "\u6807\u8BB0\u5B8C\u6210\u3002\u5339\u914D\u5230\u591A\u6761\u65F6\u5148\u95EE\u7528\u6237\u662F\u54EA\u6761\uFF0C\u522B\u731C\u3002\u6D3E\u7ED9\u522B\u4EBA\u4E14\u5DF2\u53D1\u51FA\u7684\uFF0C\u5B8C\u6210\u540E\u901A\u77E5\u53E6\u4E00\u65B9\u3002",
339468
+ positional: [{ name: "<id>", required: true, desc: "\u5F85\u529E id" }],
339469
+ flags: [{ name: "reason", desc: "\u5907\u6CE8\u4E00\u53E5\uFF08\u53EF\u9009\uFF09", own: true }]
339066
339470
  },
339067
339471
  "todo cancel": {
339068
339472
  group: "\u4E2A\u4EBA\u5F85\u529E",
339069
339473
  usage: "oasis todo cancel <id> [--reason \u2026]",
339070
- description: "\u53D6\u6D88\uFF08\u4E0D\u7528\u505A\u4E86\uFF09\u3002\u53EA\u6539\u72B6\u6001\u3001\u4E0D\u5220\u9664\uFF0C--all \u5217\u8868\u91CC\u8FD8\u770B\u5F97\u5230\u3002",
339474
+ description: "\u53D6\u6D88\uFF08\u4E0D\u7528\u505A\u4E86\uFF09\u3002\u53EA\u6539\u72B6\u6001\u3001\u4E0D\u5220\u9664\uFF08\u5F85\u529E\u6C38\u4E0D\u5220\u9664\uFF09\uFF0C--all \u5217\u8868\u548C show \u91CC\u90FD\u8FD8\u770B\u5F97\u5230\u3002\u53D1\u8D77\u4EBA\u3001\u627F\u63A5\u4EBA\u90FD\u80FD\u53D6\u6D88\uFF0C\u6D3E\u7ED9\u522B\u4EBA\u4E14\u5DF2\u53D1\u51FA\u7684\u4F1A\u901A\u77E5\u53E6\u4E00\u65B9\uFF1B**\u522B\u4EBA\u6D3E\u7ED9\u4F60\u7684\u53D6\u6D88\u5FC5\u987B\u5E26 --reason**\u3002",
339071
339475
  positional: [{ name: "<id>", required: true, desc: "\u5F85\u529E id" }],
339072
- flags: [{ name: "reason", desc: "\u4E3A\u4EC0\u4E48\u4E0D\u7528\u505A\u4E86", own: true }]
339476
+ flags: [{ name: "reason", desc: "\u4E3A\u4EC0\u4E48\u4E0D\u7528\u505A\u4E86\uFF08\u522B\u4EBA\u6D3E\u7ED9\u4F60\u7684\u5FC5\u987B\u5199\uFF09", own: true }]
339073
339477
  },
339074
339478
  "todo reopen": {
339075
339479
  group: "\u4E2A\u4EBA\u5F85\u529E",
339076
- usage: "oasis todo reopen <id>",
339077
- description: "\u628A\u5DF2\u5B8C\u6210 / \u5DF2\u53D6\u6D88\u7684\u5F85\u529E\u91CD\u65B0\u6253\u5F00\u3002",
339078
- positional: [{ name: "<id>", required: true, desc: "\u5F85\u529E id" }]
339480
+ usage: "oasis todo reopen <id> [--reason \u2026]",
339481
+ description: "\u628A\u5DF2\u5B8C\u6210 / \u5DF2\u53D6\u6D88\u7684\u5F85\u529E\u91CD\u65B0\u6253\u5F00\uFF08\u53D1\u8D77\u4EBA\u89C9\u5F97\u6CA1\u505A\u5B8C\u4E5F\u7528\u5B83\uFF09\u3002\u6D3E\u7ED9\u522B\u4EBA\u4E14\u5DF2\u53D1\u51FA\u7684\uFF0C\u4F1A\u901A\u77E5\u53E6\u4E00\u65B9\u3002\u8349\u6848\u53D6\u6D88\u540E\u91CD\u5F00\u4ECD\u662F\u8349\u6848\uFF0C\u4E0D\u4F1A\u56E0\u6B64\u53D1\u51FA\u3002",
339482
+ positional: [{ name: "<id>", required: true, desc: "\u5F85\u529E id" }],
339483
+ flags: [{ name: "reason", desc: "\u4E3A\u4EC0\u4E48\u91CD\u5F00\uFF08\u53EF\u9009\uFF09", own: true }]
339079
339484
  },
339080
339485
  // —— 技能:组织技能库的 CLI 面(Web 上是「管理 > 技能」那一页)。
339081
339486
  // agent 把自己写好的一个技能目录沉淀进公司,并决定它对谁生效。 ——
@@ -339402,7 +339807,7 @@ function formatProjectDecisions(projectId2, view, query) {
339402
339807
  "\u5DF2\u786E\u8BA4 = \u5B9A\u4E86\u8981\u8FD9\u4E48\u505A\uFF0C\u4E0D\u4EE3\u8868\u5DF2\u7ECF\u4E0A\u7EBF\uFF1B\u6807\u300C\u5DF2\u4E0A\u7EBF\u300D\u7684\u624D\u662F\u73B0\u5728\u7CFB\u7EDF\u91CC\u7684\u6837\u5B50\u3002",
339403
339808
  ""
339404
339809
  ];
339405
- let shown = 0;
339810
+ let shown2 = 0;
339406
339811
  for (const t of topics) {
339407
339812
  const rows = view.decisions.filter((d5) => d5.value.topicId === t.value.topicId).filter((d5) => {
339408
339813
  if (state2 ? !stateMatches(d5.value, state2, query.now) : !["accepted", "proposed"].includes(d5.value.record.state)) return false;
@@ -339416,7 +339821,7 @@ function formatProjectDecisions(projectId2, view, query) {
339416
339821
  if (!rows.length) lines.push("\uFF08\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u51B3\u7B56\uFF09");
339417
339822
  for (const d5 of rows) {
339418
339823
  const r5 = d5.value.record;
339419
- shown += 1;
339824
+ shown2 += 1;
339420
339825
  lines.push(`- [${label(d5.value, query.now)}] ${r5.statement} \uFF08\u66F4\u65B0 ${r5.updated_at.slice(0, 10)}\uFF0C${d5.value.decisionId}\uFF09`);
339421
339826
  if (!detail) continue;
339422
339827
  lines.push(` \u539F\u56E0\uFF1A${r5.rationale || "\u672A\u8BF4\u660E"}`, ` \u786E\u8BA4\u4EBA\uFF1A${r5.confirmer ?? "\u672A\u786E\u8BA4"}`);
@@ -339427,7 +339832,7 @@ function formatProjectDecisions(projectId2, view, query) {
339427
339832
  }
339428
339833
  lines.push("");
339429
339834
  }
339430
- if (!shown && !detail) lines.push("\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u51B3\u7B56\u3002");
339835
+ if (!shown2 && !detail) lines.push("\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u51B3\u7B56\u3002");
339431
339836
  return lines;
339432
339837
  }
339433
339838
  function writeContextSnapshot(root5, snapshot) {
@@ -339827,10 +340232,10 @@ function renderGraphLines(graph) {
339827
340232
  const systemNote = closureIds.has(n3.id) ? " [\u672C\u5355\u672B\u7AEF\xB7\u7CFB\u7EDF\u7EF4\u62A4\uFF0C\u8FDE\u7EBF\u4E0E\u4EFB\u52A1\u4E66\u90FD\u4E0D\u7528\u4F60\u7BA1]" : "";
339828
340233
  out.push(` type=${n3.type}${n3.stage ? ` \u9636\u6BB5=${n3.stage}` : ""}${n3.blocked ? " [\u5361\u4F4F]" : ""}${n3.unresolvedEscalations ? ` [\u4E0A\u62A5 ${n3.unresolvedEscalations}]` : ""} owner=${n3.owner?.name ?? n3.owner?.id ?? "?"}${n3.label ? ` ${n3.label}` : ""}${systemNote}`);
339829
340234
  }
339830
- const shown = graph.edges.filter((e5) => !closureIds.has(e5.from));
340235
+ const shown2 = graph.edges.filter((e5) => !closureIds.has(e5.from));
339831
340236
  const folded = [...closureIds].filter((id) => graph.edges.some((e5) => e5.from === id));
339832
- out.push(shown.length > 0 || folded.length > 0 ? "\n\u4F9D\u8D56\u8FB9\uFF08\u6D88\u8D39\u8005 \u2192 \u4E0A\u6E38\uFF09:" : "\n\u4F9D\u8D56\u8FB9\uFF1A\uFF08\u65E0\uFF09");
339833
- for (const e5 of shown) out.push(` ${e5.from} \u2192 ${e5.to}${e5.required === false ? "\uFF08\u53EF\u9009\uFF09" : ""}`);
340237
+ out.push(shown2.length > 0 || folded.length > 0 ? "\n\u4F9D\u8D56\u8FB9\uFF08\u6D88\u8D39\u8005 \u2192 \u4E0A\u6E38\uFF09:" : "\n\u4F9D\u8D56\u8FB9\uFF1A\uFF08\u65E0\uFF09");
340238
+ for (const e5 of shown2) out.push(` ${e5.from} \u2192 ${e5.to}${e5.required === false ? "\uFF08\u53EF\u9009\uFF09" : ""}`);
339834
340239
  for (const id of folded) out.push(` ${id} \u2192 \uFF08\u672C\u5355\u5168\u90E8\u4E1A\u52A1\u4EA7\u7269\xB7\u7CFB\u7EDF\u7EF4\u62A4\uFF09`);
339835
340240
  return out;
339836
340241
  }
@@ -339931,7 +340336,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback2) {
339931
340336
  }
339932
340337
  }
339933
340338
  }
339934
- var CLI_VERSION = "2.2.120" ? "2.2.120" : "dev";
340339
+ var CLI_VERSION = "2.2.121" ? "2.2.121" : "dev";
339935
340340
  function apiHeaders(token2, company) {
339936
340341
  const headers = {
339937
340342
  authorization: `Bearer ${token2}`,
@@ -342328,6 +342733,7 @@ ${res.warning}`);
342328
342733
  }
342329
342734
  case "todo": {
342330
342735
  const sub = positional[0];
342736
+ const nameIn = (names) => (id) => names?.[id] ?? id;
342331
342737
  const due = (t) => formatTodoSpan(t);
342332
342738
  const call = async (method, url, body2) => {
342333
342739
  try {
@@ -342350,6 +342756,8 @@ ${res.warning}`);
342350
342756
  const idOf = () => needPos(positional, 1, `oasis todo ${sub} <id>`);
342351
342757
  const todoPath = (id, tail = "") => `/api/todos/${encodeURIComponent(id)}${tail}`;
342352
342758
  if (sub === "add") {
342759
+ const to = flags.get("to");
342760
+ if (to !== void 0 && !to.trim()) throw new Error("--to \u8981\u5199\u6D3E\u7ED9\u8C01\uFF08actorId \u6216\u540D\u5B57\uFF09");
342353
342761
  const title = need(flags, "title");
342354
342762
  const dueDate = checkDay(need(flags, "due"), "due");
342355
342763
  const time4 = flags.get("time");
@@ -342360,6 +342768,7 @@ ${res.warning}`);
342360
342768
  if (start !== void 0 && start > dueDate && isValidDay(start)) throw new Error(`--start ${start} \u665A\u4E8E --due ${dueDate}\uFF1A\u5F00\u59CB\u4E0D\u80FD\u665A\u4E8E\u622A\u6B62`);
342361
342769
  const sourceChatSessionId = process.env["OASIS_CHAT_SESSION_ID"];
342362
342770
  const r5 = await call("POST", "/api/todos", {
342771
+ ...to !== void 0 ? { assignee: to } : {},
342363
342772
  title,
342364
342773
  dueDate,
342365
342774
  ...time4 !== void 0 ? { dueTime: checkTime(time4) } : {},
@@ -342369,22 +342778,39 @@ ${res.warning}`);
342369
342778
  ...sourceChatSessionId ? { sourceChatSessionId } : {}
342370
342779
  });
342371
342780
  println(r5.todayLine);
342781
+ if (r5.todo.status === "draft") {
342782
+ println(`\u8349\u6848\uFF08\u8FD8\u6CA1\u53D1\u51FA\uFF09\uFF1A\u6D3E\u7ED9 ${nameIn(r5.names)(r5.todo.assigneeActorId)}\uFF1A${r5.todo.title}\uFF0C${due(r5.todo)}`);
342783
+ println(`id\uFF1A${r5.todo.id}`);
342784
+ println("\u5BF9\u8BDD\u91CC\u5DF2\u51FA\u8349\u6848\u5361\uFF1A\u8BF7\u7528\u6237\u6838\u5BF9\u540E\u70B9\u300C\u53D1\u51FA\u300D\u3002\u53D1\u51FA\u524D\u5BF9\u65B9\u770B\u4E0D\u5230\u3001\u4E5F\u6536\u4E0D\u5230\u901A\u77E5\uFF1B\u8981\u6539\u5C31 oasis todo edit \u8FD9\u6761\u8349\u6848\u3002");
342785
+ break;
342786
+ }
342372
342787
  println(`\u8BB0\u4E0B\u4E86\uFF1A${r5.todo.title}\uFF0C${due(r5.todo)}`);
342373
342788
  println(`id\uFF1A${r5.todo.id}`);
342374
342789
  break;
342375
342790
  }
342376
342791
  if (sub === "list") {
342792
+ const qs = new URLSearchParams();
342377
342793
  const all2 = flags.get("all") === "true";
342378
- const r5 = await call("GET", `/api/todos${all2 ? "?all=1" : ""}`);
342794
+ if (all2) qs.set("all", "1");
342795
+ if (flags.get("everyone") === "true") qs.set("everyone", "1");
342796
+ for (const [flag, key2] of [["assignee", "assignee"], ["creator", "creator"], ["q", "q"], ["status", "status"]]) {
342797
+ const v2 = flags.get(flag);
342798
+ if (v2 !== void 0) qs.set(key2, v2);
342799
+ }
342800
+ if (flags.has("due-from")) qs.set("dueFrom", checkDay(flags.get("due-from"), "due-from"));
342801
+ if (flags.has("due-to")) qs.set("dueTo", checkDay(flags.get("due-to"), "due-to"));
342802
+ const query = qs.toString();
342803
+ const r5 = await call("GET", `/api/todos${query ? `?${query}` : ""}`);
342379
342804
  println(r5.todayLine);
342380
342805
  if (r5.items.length === 0) {
342381
- println(all2 ? "\u6CA1\u6709\u5F85\u529E\u3002" : "\u6CA1\u6709\u672A\u5B8C\u6210\u7684\u5F85\u529E\u3002");
342806
+ println(query && query !== "all=1" ? "\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u5F85\u529E\u3002" : all2 ? "\u6CA1\u6709\u5F85\u529E\u3002" : "\u6CA1\u6709\u672A\u5B8C\u6210\u7684\u5F85\u529E\u3002");
342382
342807
  break;
342383
342808
  }
342809
+ const nm = nameIn(r5.names);
342384
342810
  for (const t of r5.items) {
342385
- const tag = t.status === "done" ? " [\u5DF2\u5B8C\u6210]" : t.status === "cancelled" ? " [\u5DF2\u53D6\u6D88]" : t.dueDate < r5.today.date ? " [\u5DF2\u8FC7\u671F]" : "";
342811
+ const tag = t.status === "draft" ? " [\u8349\u6848\xB7\u672A\u53D1\u51FA]" : t.status === "done" ? " [\u5DF2\u5B8C\u6210]" : t.status === "cancelled" ? " [\u5DF2\u53D6\u6D88]" : t.dueDate < r5.today.date ? " [\u5DF2\u8FC7\u671F]" : "";
342386
342812
  const when = t.startDate ? formatTodoSpan(t) : formatTodoDue(t.dueDate, t.dueTime);
342387
- println(`${t.id} ${when} ${t.title}${tag}`);
342813
+ println(`${t.id} ${when} ${t.title} \u627F\u63A5\uFF1A${nm(t.assigneeActorId)} \u53D1\u8D77\uFF1A${nm(t.creatorActorId)}${tag}`);
342388
342814
  }
342389
342815
  break;
342390
342816
  }
@@ -342395,11 +342821,27 @@ ${res.warning}`);
342395
342821
  call("GET", "/api/todos")
342396
342822
  ]);
342397
342823
  const t = one2.todo;
342398
- const statusText = t.status === "done" ? "\u5DF2\u5B8C\u6210" : t.status === "cancelled" ? "\u5DF2\u53D6\u6D88" : t.dueDate < day.today.date ? "\u672A\u5B8C\u6210\uFF08\u5DF2\u8FC7\u671F\uFF09" : "\u672A\u5B8C\u6210";
342824
+ const nm = nameIn(one2.names);
342825
+ const statusText = t.status === "draft" ? "\u8349\u6848\uFF08\u8FD8\u6CA1\u53D1\u51FA\uFF09" : t.status === "done" ? "\u5DF2\u5B8C\u6210" : t.status === "cancelled" ? "\u5DF2\u53D6\u6D88" : t.dueDate < day.today.date ? "\u672A\u5B8C\u6210\uFF08\u5DF2\u8FC7\u671F\uFF09" : "\u672A\u5B8C\u6210";
342399
342826
  println(day.todayLine);
342400
342827
  println(`${t.id} ${t.title}`);
342401
342828
  println(`\u65F6\u95F4\uFF1A${due(t)}`);
342402
342829
  println(`\u72B6\u6001\uFF1A${statusText}`);
342830
+ println(`\u53D1\u8D77\u4EBA\uFF1A${nm(t.creatorActorId)}`);
342831
+ println(`\u627F\u63A5\u4EBA\uFF1A${nm(t.assigneeActorId)}`);
342832
+ const fmtAt = (iso9) => new Intl.DateTimeFormat("sv-SE", {
342833
+ timeZone: day.today.timeZone,
342834
+ year: "numeric",
342835
+ month: "2-digit",
342836
+ day: "2-digit",
342837
+ hour: "2-digit",
342838
+ minute: "2-digit",
342839
+ hour12: false
342840
+ }).format(new Date(iso9));
342841
+ if (t.creatorActorId !== t.assigneeActorId) {
342842
+ println(`\u53D1\u51FA\u65F6\u95F4\uFF1A${t.sentAt ? fmtAt(t.sentAt) : "\uFF08\u8FD8\u6CA1\u53D1\u51FA\uFF09"}`);
342843
+ println(`\u627F\u63A5\u4EBA\u9996\u6B21\u67E5\u770B\uFF1A${t.firstViewedAt ? fmtAt(t.firstViewedAt) : "\uFF08\u8FD8\u6CA1\u770B\u8FC7\uFF09"}`);
342844
+ }
342403
342845
  if (t.linkedWorkorderId) println(`\u5DE5\u5355\uFF1A${t.linkedWorkorderId}`);
342404
342846
  println("\u5907\u6CE8\uFF1A");
342405
342847
  println(t.note ? t.note : "\uFF08\u65E0\uFF09");
@@ -342412,24 +342854,16 @@ ${res.warning}`);
342412
342854
  startTime: "\u5F00\u59CB\u65F6\u523B",
342413
342855
  dueDate: "\u622A\u6B62\u65E5",
342414
342856
  dueTime: "\u622A\u6B62\u65F6\u523B",
342415
- linkedWorkorderId: "\u5DE5\u5355"
342857
+ linkedWorkorderId: "\u5DE5\u5355",
342858
+ assignee: "\u627F\u63A5\u4EBA"
342416
342859
  };
342417
- const STATUS = { open: "\u672A\u5B8C\u6210", done: "\u5DF2\u5B8C\u6210", cancelled: "\u5DF2\u53D6\u6D88" };
342418
- const val = (field, v2) => v2 === void 0 || v2 === "" ? "\uFF08\u7A7A\uFF09" : field === "status" ? STATUS[v2] ?? v2 : v2;
342419
- const fmtAt = (iso9) => new Intl.DateTimeFormat("sv-SE", {
342420
- timeZone: day.today.timeZone,
342421
- year: "numeric",
342422
- month: "2-digit",
342423
- day: "2-digit",
342424
- hour: "2-digit",
342425
- minute: "2-digit",
342426
- hour12: false
342427
- }).format(new Date(iso9));
342860
+ const STATUS = { draft: "\u8349\u6848", open: "\u672A\u5B8C\u6210", done: "\u5DF2\u5B8C\u6210", cancelled: "\u5DF2\u53D6\u6D88" };
342861
+ const val = (field, v2) => v2 === void 0 || v2 === "" ? "\uFF08\u7A7A\uFF09" : field === "status" ? STATUS[v2] ?? v2 : field === "assignee" ? nm(v2) : v2;
342428
342862
  const changes = [...one2.changes].sort((a5, b6) => b6.at.localeCompare(a5.at));
342429
342863
  println(`\u53D8\u66F4\u8BB0\u5F55\uFF08${changes.length} \u6761\uFF0C\u65B0\u7684\u5728\u524D\uFF09\uFF1A`);
342430
342864
  for (const c5 of changes) {
342431
- const who = c5.handActorId ? `${c5.actorId}\uFF08\u7ECF\u624B ${c5.handActorId}\uFF09` : c5.actorId;
342432
- const what = c5.field === "created" ? `\u8BB0\u4E0B\uFF1A${c5.newValue ?? ""}` : `${FIELD[c5.field] ?? c5.field}\uFF1A${val(c5.field, c5.oldValue)} \u2192 ${val(c5.field, c5.newValue)}`;
342865
+ const who = c5.handActorId ? `${nm(c5.actorId)}\uFF08\u7ECF\u624B ${c5.handActorId}\uFF09` : nm(c5.actorId);
342866
+ const what = c5.field === "created" ? `\u8BB0\u4E0B\uFF1A${c5.newValue ?? ""}` : c5.field === "sent" ? `\u53D1\u51FA\uFF1A\u6D3E\u7ED9 ${nm(c5.newValue ?? "")}` : `${FIELD[c5.field] ?? c5.field}\uFF1A${val(c5.field, c5.oldValue)} \u2192 ${val(c5.field, c5.newValue)}`;
342433
342867
  println(`- ${fmtAt(c5.at)} ${who} ${what}${c5.reason ? ` \u539F\u56E0\uFF1A${c5.reason}` : ""}`);
342434
342868
  }
342435
342869
  break;
@@ -342444,7 +342878,8 @@ ${res.warning}`);
342444
342878
  if (flags.has("start")) body2["startDate"] = flags.get("start") === "none" ? null : checkDay(flags.get("start"), "start");
342445
342879
  if (flags.has("start-time")) body2["startTime"] = flags.get("start-time") === "none" ? null : checkTime(flags.get("start-time"), "start-time");
342446
342880
  if (flags.has("workorder")) body2["linkedWorkorderId"] = flags.get("workorder") === "none" ? null : flags.get("workorder");
342447
- if (Object.keys(body2).length === 0) throw new Error("\u6CA1\u6709\u8981\u6539\u7684\u5B57\u6BB5\uFF1A\u7ED9 --title / --note / --start / --start-time / --due / --time / --workorder \u91CC\u7684\u81F3\u5C11\u4E00\u4E2A");
342881
+ if (flags.has("to")) body2["assignee"] = flags.get("to");
342882
+ if (Object.keys(body2).length === 0) throw new Error("\u6CA1\u6709\u8981\u6539\u7684\u5B57\u6BB5\uFF1A\u7ED9 --title / --note / --start / --start-time / --due / --time / --workorder / --to \u91CC\u7684\u81F3\u5C11\u4E00\u4E2A");
342448
342883
  if (flags.get("reason")) body2["reason"] = flags.get("reason");
342449
342884
  const r5 = await call("PATCH", todoPath(id), body2);
342450
342885
  println(r5.todayLine);
@@ -342453,7 +342888,7 @@ ${res.warning}`);
342453
342888
  }
342454
342889
  if (sub === "done" || sub === "cancel" || sub === "reopen") {
342455
342890
  const id = idOf();
342456
- const reason = sub === "cancel" ? flags.get("reason") : void 0;
342891
+ const reason = flags.get("reason");
342457
342892
  const r5 = await call("POST", todoPath(id, `/${sub}`), reason ? { reason } : {});
342458
342893
  println(r5.todayLine);
342459
342894
  println(`${sub === "done" ? "\u5DF2\u5B8C\u6210" : sub === "cancel" ? "\u5DF2\u53D6\u6D88" : "\u5DF2\u91CD\u65B0\u6253\u5F00"}\uFF1A${r5.todo.title}\uFF0C${due(r5.todo)}`);
@@ -343284,10 +343719,10 @@ ${res.warning}`);
343284
343719
  println(`\u4F9D\u8D56: ${v2.artifact.inputs.length === 0 ? "\u65E0" : v2.artifact.inputs.map((e5) => `${e5.to}@${e5.pinned ?? "\u672A\u5C31\u7EEA"}${e5.required ? "[required]" : ""}`).join(" ")}`);
343285
343720
  println(`\u5185\u5BB9: ${kind} \u2190 \u53D6\u6B63\u6587\u7528 \`oasis content ${id} [path]\`\uFF08current head\uFF09\uFF0F\`oasis content <revisionId>\`\uFF08\u6307\u5B9A\u7248\u672C\uFF09`);
343286
343721
  const REV_LIMIT = 50;
343287
- const shown = v2.revisions.length > REV_LIMIT ? v2.revisions.slice(-REV_LIMIT) : v2.revisions;
343288
- println(`\u7248\u672C(${v2.revisions.length})${shown.length < v2.revisions.length ? `\u2014\u2014\u53EA\u5217\u6700\u8FD1 ${REV_LIMIT} \u6761` : ""}\uFF1A`);
343289
- if (shown.length === 0) println(` \uFF08\u65E0\uFF09`);
343290
- for (const r5 of shown) {
343722
+ const shown2 = v2.revisions.length > REV_LIMIT ? v2.revisions.slice(-REV_LIMIT) : v2.revisions;
343723
+ println(`\u7248\u672C(${v2.revisions.length})${shown2.length < v2.revisions.length ? `\u2014\u2014\u53EA\u5217\u6700\u8FD1 ${REV_LIMIT} \u6761` : ""}\uFF1A`);
343724
+ if (shown2.length === 0) println(` \uFF08\u65E0\uFF09`);
343725
+ for (const r5 of shown2) {
343291
343726
  const marks = [
343292
343727
  r5.id === v2.artifact.currentRev ? "\u2190 current head" : "",
343293
343728
  r5.id === v2.concludedHead ? "\u2190 \u5DF2\u4EA4\u4ED8" : ""
@@ -343360,11 +343795,11 @@ ${res.warning}`);
343360
343795
  case "ls": {
343361
343796
  const wsFilter = flags.get("workspace");
343362
343797
  const rows = await api.view("ls");
343363
- const shown = wsFilter ? rows.filter((a5) => a5.workspace === wsFilter) : rows;
343364
- for (const a5 of shown) {
343798
+ const shown2 = wsFilter ? rows.filter((a5) => a5.workspace === wsFilter) : rows;
343799
+ for (const a5 of shown2) {
343365
343800
  println(`${a5.id} ws=${a5.workspace ?? "\u2205"} type=${a5.type} owner=${a5.owner} head=${a5.currentRev ?? "\u2205"} queue=${a5.queue} [${a5.mark}]`);
343366
343801
  }
343367
- if (wsFilter && shown.length === 0) println(`\uFF08${wsFilter} \u4E0B\u6CA1\u6709\u4EA7\u7269\u2014\u2014\u5DE5\u5355 id \u7528 \`oasis workorders\` \u67E5\uFF09`);
343802
+ if (wsFilter && shown2.length === 0) println(`\uFF08${wsFilter} \u4E0B\u6CA1\u6709\u4EA7\u7269\u2014\u2014\u5DE5\u5355 id \u7528 \`oasis workorders\` \u67E5\uFF09`);
343368
343803
  else if (!wsFilter && rows.length > 30) println(`\u2014\u2014 \u5171 ${rows.length} \u4E2A\u4EA7\u7269\uFF08\u5168\u5E93\u62C9\u5E73\uFF09\u3002\u6309\u5DE5\u5355\u770B\u7528 \`oasis ls --workspace <ws:id>\`\uFF0C\u5DE5\u5355\u6E05\u5355\u7528 \`oasis workorders\`\u3002`);
343369
343804
  break;
343370
343805
  }
@@ -344134,7 +344569,7 @@ function shimScript() {
344134
344569
  }
344135
344570
 
344136
344571
  // src/index.ts
344137
- var PKG_VERSION = true ? "2.2.120" : "dev";
344572
+ var PKG_VERSION = true ? "2.2.121" : "dev";
344138
344573
  var LOCAL_BIN = localBin();
344139
344574
  var NPM_PREFIX = npmPrefix();
344140
344575
  var INSTANCE = DEFAULT_INSTANCE;