oasis_test_v2 2.2.119 → 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 +907 -131
  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
  });
@@ -138818,7 +138834,7 @@ ${block}
138818
138834
  }
138819
138835
  return out;
138820
138836
  }
138821
- var DEFAULT_BATCH = {
138837
+ var DEFAULT_BATCH2 = {
138822
138838
  delayMs: 600,
138823
138839
  longThresholdChars: 1e3,
138824
138840
  longDelayMs: 2e3,
@@ -138837,11 +138853,11 @@ ${block}
138837
138853
  var _a2, _b, _c5, _d, _e5, _f, _g;
138838
138854
  const t = (_b = (_a2 = cfg === null || cfg === void 0 ? void 0 : cfg.batch) === null || _a2 === void 0 ? void 0 : _a2.text) !== null && _b !== void 0 ? _b : {};
138839
138855
  return {
138840
- delayMs: (_c5 = t.delayMs) !== null && _c5 !== void 0 ? _c5 : DEFAULT_BATCH.delayMs,
138841
- longThresholdChars: (_d = t.longThresholdChars) !== null && _d !== void 0 ? _d : DEFAULT_BATCH.longThresholdChars,
138842
- longDelayMs: (_e5 = t.longDelayMs) !== null && _e5 !== void 0 ? _e5 : DEFAULT_BATCH.longDelayMs,
138843
- maxMessages: (_f = t.maxMessages) !== null && _f !== void 0 ? _f : DEFAULT_BATCH.maxMessages,
138844
- maxChars: (_g = t.maxChars) !== null && _g !== void 0 ? _g : DEFAULT_BATCH.maxChars
138856
+ delayMs: (_c5 = t.delayMs) !== null && _c5 !== void 0 ? _c5 : DEFAULT_BATCH2.delayMs,
138857
+ longThresholdChars: (_d = t.longThresholdChars) !== null && _d !== void 0 ? _d : DEFAULT_BATCH2.longThresholdChars,
138858
+ longDelayMs: (_e5 = t.longDelayMs) !== null && _e5 !== void 0 ? _e5 : DEFAULT_BATCH2.longDelayMs,
138859
+ maxMessages: (_f = t.maxMessages) !== null && _f !== void 0 ? _f : DEFAULT_BATCH2.maxMessages,
138860
+ maxChars: (_g = t.maxChars) !== null && _g !== void 0 ? _g : DEFAULT_BATCH2.maxChars
138845
138861
  };
138846
138862
  }
138847
138863
  var SeenCache = class {
@@ -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`);
257767
257929
  return todo;
257768
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");
257935
+ return todo;
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
 
@@ -279920,7 +280234,7 @@ async function drainToBuffer(body2) {
279920
280234
  for await (const chunk of body2) parts.push(chunk);
279921
280235
  return new Uint8Array(Buffer.concat(parts));
279922
280236
  }
279923
- var import_node_crypto87, fs36, BLOB_NAME2, ExternalizedBlobStore, ObjectBlobStore;
280237
+ var import_node_crypto87, fs36, BLOB_NAME2, MIGRATION_SELECTORS, ExternalizedBlobStore, ObjectBlobStore;
279924
280238
  var init_externalized_blobs = __esm({
279925
280239
  "../storage/src/externalized-blobs.ts"() {
279926
280240
  "use strict";
@@ -279930,6 +280244,12 @@ var init_externalized_blobs = __esm({
279930
280244
  init_blob_object_backend();
279931
280245
  init_postgres();
279932
280246
  BLOB_NAME2 = /^[0-9a-f]{64}$/;
280247
+ MIGRATION_SELECTORS = {
280248
+ pg: `location = 'pg'`,
280249
+ ext: `location = 'ext'`,
280250
+ "ext-with-bytes": `location = 'ext' AND bytes IS NOT NULL`,
280251
+ internalize: `location = 'ext' OR bytes IS NULL`
280252
+ };
279933
280253
  ExternalizedBlobStore = class _ExternalizedBlobStore {
279934
280254
  constructor(pool, t, namespace, backend, opts) {
279935
280255
  this.pool = pool;
@@ -280137,13 +280457,49 @@ var init_externalized_blobs = __esm({
280137
280457
  }
280138
280458
  /** 列出待处理的 hash(分批、按 hash 升序,便于中断续跑)。 */
280139
280459
  async listHashes(where, after, limit) {
280140
- const cond = where === "pg" ? `location = 'pg'` : where === "ext" ? `location = 'ext'` : `location = 'ext' AND bytes IS NOT NULL`;
280141
280460
  const res = await this.pool.query(
280142
- `SELECT hash FROM ${this.t} WHERE ${cond} AND hash > $1 ORDER BY hash LIMIT $2`,
280461
+ `SELECT hash FROM ${this.t} WHERE (${MIGRATION_SELECTORS[where]}) AND hash > $1 ORDER BY hash LIMIT $2`,
280143
280462
  [after, limit]
280144
280463
  );
280145
280464
  return res.rows.map((r5) => r5.hash);
280146
280465
  }
280466
+ /** 待处理的行数与字节数(`oasis blobs … --dry-run` 与进度分母)。 */
280467
+ async pendingStats(where) {
280468
+ const res = await this.pool.query(
280469
+ `SELECT count(*)::bigint AS rows, coalesce(sum(size), 0)::bigint AS bytes FROM ${this.t} WHERE ${MIGRATION_SELECTORS[where]}`
280470
+ );
280471
+ const r5 = res.rows[0] ?? {};
280472
+ return { rows: Number(r5.rows ?? 0), bytes: Number(r5.bytes ?? 0) };
280473
+ }
280474
+ /**
280475
+ * 清空一行的 bytea(§6.2 第 6 步 `--purge-pg-bytes` 的单行原语):**先确认对象在后端、大小与行一致**才清——
280476
+ * bytea 是清掉前最后一份副本,对象丢了(误删、恢复不全)时宁可留着。对象缺失 / 大小不符、或核对本身出错
280477
+ * (S3 超时 / 403 / 5xx、fs I/O 错)一律抛错,bytea 不动。可重复执行:已清过或已不是 `ext` 的行跳过。
280478
+ * 返回本行清掉的字节数(行 `size`);跳过返回 0。
280479
+ */
280480
+ async purgePgBytesOne(hash3) {
280481
+ const res = await this.pool.query(
280482
+ `SELECT size FROM ${this.t} WHERE hash = $1 AND location = 'ext' AND bytes IS NOT NULL`,
280483
+ [hash3]
280484
+ );
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
+ }
280497
+ const upd = await this.pool.query(
280498
+ `UPDATE ${this.t} SET bytes = NULL WHERE hash = $1 AND location = 'ext' AND bytes IS NOT NULL`,
280499
+ [hash3]
280500
+ );
280501
+ return (upd.rowCount ?? 0) > 0 ? size : 0;
280502
+ }
280147
280503
  /** 观察期后清空已外部化行的 bytea(§6.2 第 6 步),一批一条语句。返回本批清掉的行数。 */
280148
280504
  async purgePgBytes(limit) {
280149
280505
  const res = await this.pool.query(
@@ -320180,7 +320536,7 @@ var s3_object_backend_exports = {};
320180
320536
  __export(s3_object_backend_exports, {
320181
320537
  S3ObjectBackend: () => S3ObjectBackend
320182
320538
  });
320183
- var fs37, import_client_s3, import_lib_storage, import_s3_request_presigner, MULTIPART_THRESHOLD, isNotFound, S3ObjectBackend;
320539
+ var fs37, import_client_s3, import_lib_storage, import_s3_request_presigner, MULTIPART_THRESHOLD, MULTIPART_PART_SIZE, MULTIPART_QUEUE_SIZE, isNotFound, S3ObjectBackend;
320184
320540
  var init_s3_object_backend = __esm({
320185
320541
  "../storage/src/s3-object-backend.ts"() {
320186
320542
  "use strict";
@@ -320191,6 +320547,8 @@ var init_s3_object_backend = __esm({
320191
320547
  init_blob_object_backend();
320192
320548
  init_blob_backend_config();
320193
320549
  MULTIPART_THRESHOLD = 16 * 1024 * 1024;
320550
+ MULTIPART_PART_SIZE = 8 * 1024 * 1024;
320551
+ MULTIPART_QUEUE_SIZE = 2;
320194
320552
  isNotFound = (err) => {
320195
320553
  const e5 = err;
320196
320554
  return e5?.name === "NotFound" || e5?.name === "NoSuchKey" || e5?.$metadata?.httpStatusCode === 404;
@@ -320289,8 +320647,8 @@ var init_s3_object_backend = __esm({
320289
320647
  await new import_lib_storage.Upload({
320290
320648
  client: this.client,
320291
320649
  params: { Bucket: this.bucket, Key, Body: fs37.createReadStream(staged.file), ContentType, ChecksumAlgorithm: "SHA256" },
320292
- partSize: MULTIPART_THRESHOLD,
320293
- queueSize: 2,
320650
+ partSize: MULTIPART_PART_SIZE,
320651
+ queueSize: MULTIPART_QUEUE_SIZE,
320294
320652
  leavePartsOnError: false
320295
320653
  }).done();
320296
320654
  }
@@ -320476,6 +320834,91 @@ var init_blob_backend_config = __esm({
320476
320834
  }
320477
320835
  });
320478
320836
 
320837
+ // ../storage/src/blob-migration.ts
320838
+ async function migrateBlobs(store, op2, opts) {
320839
+ const selector = SELECTOR[op2];
320840
+ const pending = await store.pendingStats(selector);
320841
+ const report = {
320842
+ op: op2,
320843
+ dryRun: opts.dryRun === true,
320844
+ interrupted: false,
320845
+ batches: 0,
320846
+ candidateRows: pending.rows,
320847
+ candidateBytes: pending.bytes,
320848
+ rows: 0,
320849
+ bytes: 0,
320850
+ failed: 0,
320851
+ failures: [],
320852
+ counts: { pg: 0, ext: 0, extWithBytes: 0 }
320853
+ };
320854
+ const fail = (hash3, err) => {
320855
+ report.failed++;
320856
+ if (report.failures.length < MAX_FAILURES_KEPT) {
320857
+ const cause = err instanceof Error && err.cause instanceof Error ? `\uFF1A${err.cause.message}` : "";
320858
+ report.failures.push({ hash: hash3, error: `${err instanceof Error ? err.message : String(err)}${cause}` });
320859
+ }
320860
+ };
320861
+ const stop = () => opts.shouldStop?.() === true;
320862
+ const sleep7 = opts.sleep ?? defaultSleep2;
320863
+ const batch = Math.max(1, Math.floor(opts.batch));
320864
+ if (!report.dryRun) {
320865
+ let after = "";
320866
+ for (; ; ) {
320867
+ if (stop()) {
320868
+ report.interrupted = true;
320869
+ break;
320870
+ }
320871
+ const hashes = await store.listHashes(selector, after, batch);
320872
+ if (hashes.length === 0) break;
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);
320884
+ }
320885
+ after = hash3;
320886
+ }
320887
+ report.batches++;
320888
+ opts.onProgress?.(progressOf(report));
320889
+ if (report.interrupted || hashes.length < batch) break;
320890
+ if (opts.pauseMs > 0) await sleep7(opts.pauseMs);
320891
+ }
320892
+ }
320893
+ report.counts = await store.locationCounts();
320894
+ return report;
320895
+ }
320896
+ function progressOf(r5) {
320897
+ return { batches: r5.batches, candidateRows: r5.candidateRows, candidateBytes: r5.candidateBytes, rows: r5.rows, bytes: r5.bytes, failed: r5.failed };
320898
+ }
320899
+ async function listBlobSchemas(pool) {
320900
+ const res = await pool.query(
320901
+ `SELECT table_schema AS s FROM information_schema.tables
320902
+ WHERE table_name = 'blobs' AND table_type = 'BASE TABLE'
320903
+ AND table_schema NOT IN ('pg_catalog', 'information_schema')
320904
+ ORDER BY table_schema`
320905
+ );
320906
+ return res.rows.map((r5) => r5.s);
320907
+ }
320908
+ var SELECTOR, MAX_FAILURES_KEPT, defaultSleep2;
320909
+ var init_blob_migration = __esm({
320910
+ "../storage/src/blob-migration.ts"() {
320911
+ "use strict";
320912
+ SELECTOR = {
320913
+ externalize: "pg",
320914
+ "purge-pg-bytes": "ext-with-bytes",
320915
+ internalize: "internalize"
320916
+ };
320917
+ MAX_FAILURES_KEPT = 50;
320918
+ defaultSleep2 = (ms) => new Promise((r5) => setTimeout(r5, ms));
320919
+ }
320920
+ });
320921
+
320479
320922
  // ../storage/src/index.ts
320480
320923
  var src_exports = {};
320481
320924
  __export(src_exports, {
@@ -320608,10 +321051,12 @@ __export(src_exports, {
320608
321051
  inventoryZeroTenantTables: () => inventoryZeroTenantTables,
320609
321052
  isUniqueViolation: () => isUniqueViolation2,
320610
321053
  legacyTurnId: () => legacyTurnId,
321054
+ listBlobSchemas: () => listBlobSchemas,
320611
321055
  listChatItemsSchemas: () => listChatItemsSchemas,
320612
321056
  listTenancyCompanies: () => listTenancyCompanies,
320613
321057
  loadKnownCompanies: () => loadKnownCompanies,
320614
321058
  maxGate: () => maxGate,
321059
+ migrateBlobs: () => migrateBlobs,
320615
321060
  missingTypeNames: () => missingTypeNames,
320616
321061
  moveDefaultCompanySchema: () => moveDefaultCompanySchema,
320617
321062
  moveTenancyRows: () => moveTenancyRows,
@@ -320702,6 +321147,7 @@ var init_src10 = __esm({
320702
321147
  init_blob_object_backend();
320703
321148
  init_externalized_blobs();
320704
321149
  init_blob_backend_config();
321150
+ init_blob_migration();
320705
321151
  }
320706
321152
  });
320707
321153
 
@@ -322240,10 +322686,10 @@ __export(src_exports2, {
322240
322686
  assertBindingWritePermission: () => assertBindingWritePermission,
322241
322687
  assertCompanyManager: () => assertCompanyManager,
322242
322688
  assertCreatableVariable: () => assertCreatableVariable,
322243
- assertFirstVersionAssignee: () => assertFirstVersionAssignee,
322244
322689
  assertMayCreateAgent: () => assertMayCreateAgent,
322245
322690
  assertMayManageAgent: () => assertMayManageAgent,
322246
322691
  assertMaySelfOrManage: () => assertMaySelfOrManage,
322692
+ assignedTitle: () => assignedTitle,
322247
322693
  assistantBoundToMember: () => assistantBoundToMember,
322248
322694
  assistantNameFor: () => assistantNameFor,
322249
322695
  assistantRowRecovery: () => assistantRowRecovery,
@@ -322281,9 +322727,11 @@ __export(src_exports2, {
322281
322727
  buildWorkorderMetrics: () => buildWorkorderMetrics,
322282
322728
  buildWorkorderSummaries: () => buildWorkorderSummaries,
322283
322729
  builtinSkillToFiles: () => builtinSkillToFiles,
322284
- canAccessTodo: () => canAccessTodo,
322285
322730
  canAnnounceRuntimeSwitch: () => canAnnounceRuntimeSwitch,
322731
+ canChangeTodo: () => canChangeTodo,
322286
322732
  canRetryAllocation: () => canRetryAllocation,
322733
+ canSeeTodo: () => canSeeTodo,
322734
+ changedTitle: () => changedTitle,
322287
322735
  chatBroadcastEnabledFromEnv: () => chatBroadcastEnabledFromEnv,
322288
322736
  chatDelegationEnabledFromEnv: () => chatDelegationEnabledFromEnv,
322289
322737
  chatDispatchScope: () => chatDispatchScope,
@@ -322658,6 +323106,7 @@ __export(src_exports2, {
322658
323106
  stableChatTraceRunId: () => stableChatTraceRunId,
322659
323107
  stableJson: () => stableJson2,
322660
323108
  startOasisServer: () => startOasisServer,
323109
+ statusTitle: () => statusTitle,
322661
323110
  stopDispatchesForDeletedActor: () => stopDispatchesForDeletedActor,
322662
323111
  stripRetiredCdBotFromDrafts: () => stripRetiredCdBotFromDrafts,
322663
323112
  subagentRunEventFromLive: () => subagentRunEventFromLive,
@@ -322675,7 +323124,10 @@ __export(src_exports2, {
322675
323124
  toBundleFiles: () => toBundleFiles,
322676
323125
  toChatDispatchRequest: () => toChatDispatchRequest,
322677
323126
  toYamlDoubleQuoted: () => toYamlDoubleQuoted,
323127
+ todoAssignedSourceKey: () => todoAssignedSourceKey,
323128
+ todoChangeSourceKey: () => todoChangeSourceKey,
322678
323129
  todoReminderSourceKey: () => todoReminderSourceKey,
323130
+ todoUnseenSourceKey: () => todoUnseenSourceKey,
322679
323131
  todosDomain: () => todosDomain,
322680
323132
  touchKey: () => touchKey,
322681
323133
  traceDomain: () => traceDomain,
@@ -322684,6 +323136,7 @@ __export(src_exports2, {
322684
323136
  trustedDelegationInvocationId: () => trustedDelegationInvocationId,
322685
323137
  trustedKnowledgeInvocationId: () => trustedKnowledgeInvocationId,
322686
323138
  typesDomain: () => typesDomain,
323139
+ unseenTitle: () => unseenTitle,
322687
323140
  upgradeDefaultFieldDefs: () => upgradeDefaultFieldDefs,
322688
323141
  validateCron: () => validateCron,
322689
323142
  validateKnowledgeAnswer: () => validateKnowledgeAnswer,
@@ -323213,6 +323666,146 @@ var init_usage_ledger_backfill_command = __esm({
323213
323666
  }
323214
323667
  });
323215
323668
 
323669
+ // ../cli/src/blobs-command.ts
323670
+ var blobs_command_exports = {};
323671
+ __export(blobs_command_exports, {
323672
+ BLOBS_USAGE: () => BLOBS_USAGE,
323673
+ BlobsArgError: () => BlobsArgError,
323674
+ parseBlobsArgs: () => parseBlobsArgs,
323675
+ runBlobs: () => runBlobs
323676
+ });
323677
+ function parseBlobsArgs(positional, flags) {
323678
+ const on = (k6) => flags.has(k6) && flags.get(k6) !== "false";
323679
+ const sub = positional[0];
323680
+ if (sub !== "externalize" && sub !== "internalize") throw new BlobsArgError("\u5B50\u547D\u4EE4\u5FC5\u987B\u662F externalize \u6216 internalize");
323681
+ if (on("purge-pg-bytes") && sub !== "externalize") throw new BlobsArgError("--purge-pg-bytes \u53EA\u914D externalize");
323682
+ const op2 = on("purge-pg-bytes") ? "purge-pg-bytes" : sub;
323683
+ const schema = flags.get("schema");
323684
+ const all2 = on("all-schemas");
323685
+ if (schema !== void 0 && all2) throw new BlobsArgError("--schema \u4E0E --all-schemas \u53EA\u80FD\u9009\u4E00\u4E2A");
323686
+ if (schema === void 0 && !all2) throw new BlobsArgError("\u5FC5\u987B\u6307\u5B9A --schema <schema> \u6216 --all-schemas");
323687
+ if (schema !== void 0 && (schema === "" || schema === "true")) throw new BlobsArgError("--schema \u9700\u8981 schema \u540D\uFF08\u5982 company_oasis-prod\u3001public\uFF09");
323688
+ const int2 = (k6, dflt, min2) => {
323689
+ const raw = flags.get(k6);
323690
+ if (raw === void 0) return dflt;
323691
+ const n3 = Number(raw);
323692
+ if (!Number.isInteger(n3) || n3 < min2) throw new BlobsArgError(`--${k6} \u9700\u8981\u4E0D\u5C0F\u4E8E ${min2} \u7684\u6574\u6570`);
323693
+ return n3;
323694
+ };
323695
+ return {
323696
+ op: op2,
323697
+ scope: schema !== void 0 ? { schema } : { all: true },
323698
+ batch: int2("batch", DEFAULT_BATCH[op2], 1),
323699
+ pauseMs: int2("pause-ms", DEFAULT_PAUSE_MS, 0),
323700
+ dryRun: on("dry-run")
323701
+ };
323702
+ }
323703
+ async function runBlobs(positional, flags, io) {
323704
+ let args;
323705
+ try {
323706
+ args = parseBlobsArgs(positional, flags);
323707
+ } catch (err) {
323708
+ if (!(err instanceof BlobsArgError)) throw err;
323709
+ io.err(`${err.message}
323710
+ ${BLOBS_USAGE}`);
323711
+ return 2;
323712
+ }
323713
+ const tag = `[blobs ${args.op}]`;
323714
+ const pgDsn = io.env["OASIS_PG_DSN"] ?? io.env["DATABASE_URL"];
323715
+ if (!pgDsn) {
323716
+ io.err(`${tag} \u9700\u8981 OASIS_PG_DSN\uFF08\u6216 DATABASE_URL\uFF09\u2014\u2014\u8BF7\u4EE5 serve \u7684\u73AF\u5883\u6267\u884C\u672C\u547D\u4EE4\u3002`);
323717
+ return 2;
323718
+ }
323719
+ const storage = await Promise.resolve().then(() => (init_src10(), src_exports));
323720
+ let config2;
323721
+ try {
323722
+ config2 = storage.readBlobBackendConfig(io.env, { hasPg: true, dataDir: io.dir });
323723
+ } catch (err) {
323724
+ io.err(`${tag} \u5BF9\u8C61\u540E\u7AEF\u914D\u7F6E\u6709\u8BEF\uFF1A${err instanceof Error ? err.message : String(err)}`);
323725
+ return 2;
323726
+ }
323727
+ if (config2.kind === "pg") {
323728
+ io.err(`${tag} \u5F53\u524D OASIS_BLOB_BACKEND=pg\uFF08\u6216\u672A\u8BBE\uFF09\uFF1A\u672C\u547D\u4EE4\u8981\u4E0E serve \u540C\u4E00\u4E2A\u5BF9\u8C61\u540E\u7AEF\uFF08fs / s3\uFF09\u3002` + (args.op === "internalize" ? "\u56DE\u6EDA\u987B\u5728\u5207\u56DE pg \u4E4B\u524D\u3001\u4ECD\u662F fs / s3 \u914D\u7F6E\u65F6\u6267\u884C\uFF08ADR 0638 \xA76.3\uFF09\u3002" : ""));
323729
+ return 2;
323730
+ }
323731
+ if (args.op === "purge-pg-bytes" && config2.dualWrite && !args.dryRun) {
323732
+ io.err(`${tag} OASIS_BLOB_PG_DUAL_WRITE=on\uFF1A\u89C2\u5BDF\u671F\u8FD8\u6CA1\u7ED3\u675F\u3002\u5148\u5173\u53CC\u5199\u5E76\u91CD\u542F serve\uFF0C\u518D\u6E05 bytea\uFF08ADR 0638 \xA76.2 \u7B2C 6 \u6B65\uFF09\u3002`);
323733
+ return 2;
323734
+ }
323735
+ if (args.op === "internalize" && !config2.dualWrite) {
323736
+ io.out(`${tag} \u63D0\u793A\uFF1AOASIS_BLOB_PG_DUAL_WRITE \u672A\u5F00\u3002\u56DE\u6EDA\u671F\u95F4 serve \u65B0\u5199\u5165\u7684\u6587\u4EF6\u53EA\u5728\u5BF9\u8C61\u540E\u7AEF\uFF0C\u5207\u56DE pg \u540E\u4F1A\u8BFB\u4E0D\u5230\uFF1B\u5EFA\u8BAE\u5148\u5F00\u53CC\u5199\u5E76\u91CD\u542F serve\uFF0C\u518D\u8DD1\u672C\u547D\u4EE4\uFF08\u89C1 docs/oasis-dev-storage.md \xA72.5.4\uFF09\u3002`);
323737
+ }
323738
+ const pool = storage.createPgPool(pgDsn);
323739
+ try {
323740
+ const existing = await storage.listBlobSchemas(pool);
323741
+ let schemas;
323742
+ if ("schema" in args.scope) {
323743
+ if (!existing.includes(args.scope.schema)) {
323744
+ io.err(`${tag} schema ${args.scope.schema} \u6CA1\u6709 blobs \u8868\u3002\u6709 blobs \u8868\u7684 schema\uFF1A${existing.join(" ") || "\uFF08\u65E0\uFF09"}`);
323745
+ return 2;
323746
+ }
323747
+ schemas = [args.scope.schema];
323748
+ } else {
323749
+ schemas = existing;
323750
+ }
323751
+ const backend = await storage.createObjectBackend(config2);
323752
+ if (!backend) throw new Error("\u5BF9\u8C61\u540E\u7AEF\u88C5\u914D\u5931\u8D25");
323753
+ io.out(`${tag} \u540E\u7AEF ${storage.describeBlobBackend(config2)}\uFF1B${schemas.length} \u4E2A schema\uFF1A${schemas.join(" ")}\uFF1B\u6BCF\u6279 ${args.batch} \u884C\u3001\u6279\u95F4 ${args.pauseMs}ms${args.dryRun ? "\uFF1Bdry-run\uFF08\u4E00\u884C\u4E0D\u52A8\uFF09" : ""}`);
323754
+ const total = { rows: 0, bytes: 0, failed: 0 };
323755
+ for (const schema of schemas) {
323756
+ const store = await storage.ExternalizedBlobStore.open(pool, schema, backend, { dualWrite: config2.dualWrite });
323757
+ const r5 = await storage.migrateBlobs(store, args.op, {
323758
+ batch: args.batch,
323759
+ pauseMs: args.pauseMs,
323760
+ dryRun: args.dryRun,
323761
+ ...io.shouldStop ? { shouldStop: io.shouldStop } : {},
323762
+ ...io.sleep ? { sleep: io.sleep } : {},
323763
+ onProgress: (p4) => io.out(`${tag} ${schema} \u7B2C ${p4.batches} \u6279\uFF1A\u5DF2\u5904\u7406 ${p4.rows}/${p4.candidateRows} \u884C\u3001${fmtBytes(p4.bytes)}/${fmtBytes(p4.candidateBytes)}\uFF0C\u5931\u8D25 ${p4.failed}`)
323764
+ });
323765
+ const c5 = r5.counts;
323766
+ const tail = `\uFF08location\uFF1Apg ${c5.pg}\u3001ext ${c5.ext}\uFF0C\u5176\u4E2D bytea \u672A\u6E05 ${c5.extWithBytes}\uFF09`;
323767
+ if (r5.dryRun) {
323768
+ io.out(`${tag} ${schema} dry-run\uFF1A\u5F85\u5904\u7406 ${r5.candidateRows} \u884C\u3001${fmtBytes(r5.candidateBytes)}${tail}`);
323769
+ } else {
323770
+ io.out(`${tag} ${schema} ${r5.interrupted ? "\u5DF2\u4E2D\u65AD" : "\u5B8C\u6210"}\uFF1A\u5904\u7406 ${r5.rows} \u884C\u3001${fmtBytes(r5.bytes)}\uFF0C\u5931\u8D25 ${r5.failed}${tail}`);
323771
+ for (const f6 of r5.failures) io.out(` \u2717 ${f6.hash}\uFF1A${f6.error}`);
323772
+ if (r5.failed > r5.failures.length) io.out(` \u2026\u53E6\u6709 ${r5.failed - r5.failures.length} \u6761\u5931\u8D25\u672A\u5217\u51FA`);
323773
+ }
323774
+ total.rows += r5.rows;
323775
+ total.bytes += r5.bytes;
323776
+ total.failed += r5.failed;
323777
+ if (r5.interrupted) {
323778
+ io.err(`${tag} \u5DF2\u4E2D\u65AD\uFF1A\u5DF2\u5904\u7406\u7684\u884C\u4E0D\u4F1A\u91CD\u505A\uFF0C\u91CD\u8DD1\u540C\u4E00\u6761\u547D\u4EE4\u4ECE\u5269\u4E0B\u7684\u63A5\u7740\u505A\u3002`);
323779
+ return 130;
323780
+ }
323781
+ }
323782
+ if (!args.dryRun) {
323783
+ io.out(`${tag} \u5408\u8BA1\uFF1A\u5904\u7406 ${total.rows} \u884C\u3001${fmtBytes(total.bytes)}\uFF0C\u5931\u8D25 ${total.failed}\u3002` + (total.failed > 0 ? "\u5931\u8D25\u7684\u884C\u4FDD\u6301\u539F\u72B6\uFF0C\u6392\u67E5\u540E\u91CD\u8DD1\u540C\u4E00\u547D\u4EE4\u53EA\u91CD\u8BD5\u5B83\u4EEC\u3002" : ""));
323784
+ }
323785
+ return total.failed > 0 ? 1 : 0;
323786
+ } finally {
323787
+ await pool.end().catch(() => void 0);
323788
+ }
323789
+ }
323790
+ var BLOBS_USAGE, BlobsArgError, DEFAULT_BATCH, DEFAULT_PAUSE_MS, MB, fmtBytes;
323791
+ var init_blobs_command = __esm({
323792
+ "../cli/src/blobs-command.ts"() {
323793
+ "use strict";
323794
+ BLOBS_USAGE = [
323795
+ "\u7528\u6CD5\uFF1A",
323796
+ " oasis blobs externalize (--schema <s> | --all-schemas) [--batch 50] [--pause-ms 200] [--dry-run]",
323797
+ " oasis blobs externalize --purge-pg-bytes (--schema <s> | --all-schemas) [--batch 500] [--pause-ms 200] [--dry-run]",
323798
+ " oasis blobs internalize (--schema <s> | --all-schemas) [--batch 50] [--pause-ms 200] [--dry-run]"
323799
+ ].join("\n");
323800
+ BlobsArgError = class extends Error {
323801
+ };
323802
+ DEFAULT_BATCH = { externalize: 50, "purge-pg-bytes": 500, internalize: 50 };
323803
+ DEFAULT_PAUSE_MS = 200;
323804
+ MB = 1024 * 1024;
323805
+ fmtBytes = (n3) => n3 >= MB ? `${(n3 / MB).toFixed(1)} MiB` : `${n3} B`;
323806
+ }
323807
+ });
323808
+
323216
323809
  // src/index.ts
323217
323810
  var fs55 = __toESM(require("node:fs"));
323218
323811
  init_src();
@@ -325320,7 +325913,24 @@ function createTodoRuntime(options) {
325320
325913
  const id = companyId ?? options.defaultCompanyId();
325321
325914
  let value2 = cache5.get(id);
325322
325915
  if (!value2) {
325323
- 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
+ }));
325324
325934
  cache5.set(id, value2);
325325
325935
  void value2.catch(() => cache5.delete(id));
325326
325936
  }
@@ -325338,6 +325948,7 @@ function createTodoRuntime(options) {
325338
325948
  todos: options.store,
325339
325949
  notifications: options.notifications,
325340
325950
  timeZone: options.timeZone,
325951
+ ...options.people ? { nameOf: async (companyId, actorId) => (await options.people(companyId)).find((p4) => p4.actorId === actorId)?.name ?? actorId } : {},
325341
325952
  enabled,
325342
325953
  warn: options.warn,
325343
325954
  ...options.onReminders ? { onInserted: options.onReminders } : {}
@@ -325686,6 +326297,7 @@ function createConnectorRenewalRuntime(options) {
325686
326297
  init_store3();
325687
326298
 
325688
326299
  // ../storage/src/postgres-todos.ts
326300
+ init_src();
325689
326301
  init_pg_ident();
325690
326302
  init_schema_ddl_lock();
325691
326303
  var PostgresTodoStore = class _PostgresTodoStore {
@@ -325709,7 +326321,7 @@ var PostgresTodoStore = class _PostgresTodoStore {
325709
326321
  due_time text,
325710
326322
  start_date text,
325711
326323
  start_time text,
325712
- status text NOT NULL CHECK (status IN ('open','done','cancelled')),
326324
+ status text NOT NULL CONSTRAINT todos_status_check CHECK (status IN (${STATUS_LIST})),
325713
326325
  done_at timestamptz,
325714
326326
  cancelled_at timestamptz,
325715
326327
  source_chat_session_id text,
@@ -325717,6 +326329,17 @@ var PostgresTodoStore = class _PostgresTodoStore {
325717
326329
  created_at timestamptz NOT NULL,
325718
326330
  updated_at timestamptz NOT NULL
325719
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
+ }
325720
326343
  await pool.query(`CREATE INDEX IF NOT EXISTS todos_assignee_status_due_idx ON ${store.s}.todos (assignee_actor_id, status, due_date)`);
325721
326344
  await pool.query(`CREATE INDEX IF NOT EXISTS todos_creator_status_due_idx ON ${store.s}.todos (creator_actor_id, status, due_date)`);
325722
326345
  await pool.query(`CREATE INDEX IF NOT EXISTS todos_open_due_idx ON ${store.s}.todos (due_date) WHERE status = 'open'`);
@@ -325762,8 +326385,9 @@ var PostgresTodoStore = class _PostgresTodoStore {
325762
326385
  async create(todo, changes) {
325763
326386
  return this.tx(async (client) => {
325764
326387
  const r5 = await client.query(`INSERT INTO ${this.s}.todos (id,title,note,creator_actor_id,assignee_actor_id,recorded_by_actor_id,
325765
- due_date,due_time,status,done_at,cancelled_at,source_chat_session_id,linked_workorder_id,created_at,updated_at,start_date,start_time)
325766
- 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));
325767
326391
  await this.insertChanges(client, changes);
325768
326392
  return fromRow3(r5.rows[0]);
325769
326393
  });
@@ -325776,8 +326400,9 @@ var PostgresTodoStore = class _PostgresTodoStore {
325776
326400
  return this.tx(async (client) => {
325777
326401
  const r5 = await client.query(`UPDATE ${this.s}.todos SET title=$2,note=$3,creator_actor_id=$4,assignee_actor_id=$5,
325778
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,
325779
- linked_workorder_id=$13,created_at=$14,updated_at=$15,start_date=$16,start_time=$17
325780
- 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]);
325781
326406
  if (!r5.rows[0]) return null;
325782
326407
  await this.insertChanges(client, changes);
325783
326408
  return fromRow3(r5.rows[0]);
@@ -325789,6 +326414,31 @@ var PostgresTodoStore = class _PostgresTodoStore {
325789
326414
  ORDER BY due_date, COALESCE(due_time,'99:99'), created_at, id`, [actorId, [...statuses2]]);
325790
326415
  return r5.rows.map(fromRow3);
325791
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
+ }
325792
326442
  async listOpenReminderCandidates(fromDay, toDay) {
325793
326443
  const r5 = await this.pool.query(`SELECT * FROM ${this.s}.todos
325794
326444
  WHERE status='open' AND (due_date BETWEEN $1 AND $2 OR start_date BETWEEN $1 AND $2)
@@ -325807,6 +326457,7 @@ var PostgresTodoStore = class _PostgresTodoStore {
325807
326457
  });
325808
326458
  }
325809
326459
  };
326460
+ var STATUS_LIST = TODO_STATUSES.map((v2) => `'${v2}'`).join(",");
325810
326461
  var iso8 = (v2) => v2 instanceof Date ? v2.toISOString() : typeof v2 === "string" ? new Date(v2).toISOString() : void 0;
325811
326462
  function rowValues(t) {
325812
326463
  return [
@@ -325826,7 +326477,10 @@ function rowValues(t) {
325826
326477
  t.createdAt,
325827
326478
  t.updatedAt,
325828
326479
  t.startDate ?? null,
325829
- t.startTime ?? null
326480
+ t.startTime ?? null,
326481
+ t.sentAt ?? null,
326482
+ t.firstViewedAt ?? null,
326483
+ t.sourceRunId ?? null
325830
326484
  ];
325831
326485
  }
325832
326486
  function fromRow3(row) {
@@ -325849,7 +326503,10 @@ function fromRow3(row) {
325849
326503
  ["doneAt", iso8(row["done_at"])],
325850
326504
  ["cancelledAt", iso8(row["cancelled_at"])],
325851
326505
  ["sourceChatSessionId", row["source_chat_session_id"]],
325852
- ["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"]]
325853
326510
  ];
325854
326511
  for (const [k6, v2] of opt) if (v2 !== void 0 && v2 !== null) t[k6] = v2;
325855
326512
  return t;
@@ -329449,10 +330106,17 @@ async function startServe(opts) {
329449
330106
  timeZone: (companyId) => companies.service.effectiveTimeZoneOf(companyId),
329450
330107
  boundHuman: async (agentId, companyId) => (await (await resolveAssistantsCtx(companyId)).service.getByAgent(agentId))?.humanActorId ?? null,
329451
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),
329452
330116
  onReminders: (companyId, records) => {
329453
330117
  pushNotifier?.notify(companyId, records);
329454
330118
  const recipients = [...new Set(records.map((r5) => r5.recipient))];
329455
- 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(",")}`);
329456
330120
  auditSink.notifyNotificationsChanged(recipients);
329457
330121
  },
329458
330122
  defaultCompanyId: () => defaultCompanyId,
@@ -337190,6 +337854,46 @@ var COMMAND_DECLS = {
337190
337854
  "oasis usage-ledger-backfill --all --verify"
337191
337855
  ]
337192
337856
  },
337857
+ blobs: {
337858
+ group: "\u57FA\u7840\u8BBE\u65BD",
337859
+ usage: "oasis blobs <externalize|internalize> (--schema <s> | --all-schemas) ...",
337860
+ description: "\u4E8B\u5B9E\u67DC bytea \u8FC1\u51FA / \u6E05\u7A7A / \u56DE\u6EDA\uFF08ADR 0638 \xA76.2\u3001\xA76.3\uFF09\u3002\u4EE5 serve \u7684\u73AF\u5883\u5728\u90E8\u7F72\u673A\u4E0A\u6267\u884C\uFF0C\u5BF9\u8C61\u540E\u7AEF\u4E0E serve \u540C\u4E00\u5957\u88C5\u914D\u3002",
337861
+ subCommands: ["blobs externalize", "blobs internalize"]
337862
+ },
337863
+ "blobs externalize": {
337864
+ group: "\u57FA\u7840\u8BBE\u65BD",
337865
+ usage: "oasis blobs externalize [--purge-pg-bytes] (--schema <s> | --all-schemas) [--batch 50] [--pause-ms 200] [--dry-run]",
337866
+ description: "\u628A location='pg' \u7684\u884C\u9010\u4E2A\u5199\u8FDB\u5BF9\u8C61\u540E\u7AEF\uFF08\u8BFB\u56DE\u6821\u9A8C sha256 \u540E\u6539 location='ext'\uFF0C\u4FDD\u7559 bytea\uFF09\uFF1B\u5E26 --purge-pg-bytes \u65F6\u6539\u4E3A\u5206\u6279\u6E05\u7A7A\u5DF2\u5916\u90E8\u5316\u884C\u7684 bytea\uFF08\u5148\u6838\u5BF9\u5BF9\u8C61\u5728\u4E14\u5927\u5C0F\u4E00\u81F4\uFF09\u3002\u5206\u6279\u3001\u6279\u95F4\u9650\u901F\u3001\u6BCF\u6279\u8F93\u51FA\u8FDB\u5EA6\uFF1BCtrl-C \u5904\u7406\u5B8C\u5F53\u524D\u4E00\u884C\u518D\u505C\uFF0C\u91CD\u8DD1\u540C\u4E00\u547D\u4EE4\u7EED\u8DD1\u3002\u9000\u51FA\u7801\uFF1A0 \u5B8C\u6210\u30011 \u6709\u884C\u5931\u8D25\u30012 \u53C2\u6570/\u73AF\u5883\u9519\u8BEF\u3001130 \u88AB\u4E2D\u65AD\u3002",
337867
+ flags: [
337868
+ { name: "schema", desc: "\u53EA\u5904\u7406\u8FD9\u4E2A schema\uFF08\u5982 company_oasis-prod\u3001public\uFF09\uFF1B\u987B\u5DF2\u6709 blobs \u8868" },
337869
+ { name: "all-schemas", desc: "\u6240\u6709\u6709 blobs \u8868\u7684 schema\uFF0C\u542B\u5171\u4EAB schema public", boolean: true },
337870
+ { name: "purge-pg-bytes", desc: "\u6E05\u7A7A\u5DF2\u5916\u90E8\u5316\u884C\u7684 bytea\uFF08\u89C2\u5BDF\u671F\u7ED3\u675F\u3001\u5173\u53CC\u5199\u5E76\u91CD\u542F serve \u4E4B\u540E\uFF1B\u53CC\u5199\u5F00\u7740\u65F6\u62D2\u7EDD\uFF09", boolean: true },
337871
+ { name: "batch", desc: "\u6BCF\u6279\u884C\u6570\uFF08\u7F3A\u7701 50\uFF1B--purge-pg-bytes \u7F3A\u7701 500\uFF09" },
337872
+ { name: "pause-ms", desc: "\u6279\u95F4\u505C\u987F\u6BEB\u79D2\u6570\uFF08\u7F3A\u7701 200\uFF09" },
337873
+ { name: "dry-run", desc: "\u53EA\u62A5\u6BCF\u4E2A schema \u5F85\u5904\u7406\u7684\u884C\u6570\u548C\u5B57\u8282\u6570\uFF0C\u4E00\u884C\u4E0D\u52A8", boolean: true }
337874
+ ],
337875
+ examples: [
337876
+ "oasis blobs externalize --all-schemas --dry-run",
337877
+ "oasis blobs externalize --schema company_oasis-prod --batch 50",
337878
+ "oasis blobs externalize --purge-pg-bytes --all-schemas"
337879
+ ]
337880
+ },
337881
+ "blobs internalize": {
337882
+ group: "\u57FA\u7840\u8BBE\u65BD",
337883
+ usage: "oasis blobs internalize (--schema <s> | --all-schemas) [--batch 50] [--pause-ms 200] [--dry-run]",
337884
+ description: "\u56DE\u6EDA\uFF1A\u628A\u5BF9\u8C61\u5199\u56DE bytea\uFF08\u6821\u9A8C sha256\uFF09\u5E76\u6539 location='pg'\u3002\u987B\u5728\u5207\u56DE OASIS_BLOB_BACKEND=pg \u4E4B\u524D\u3001\u4ECD\u662F fs / s3 \u914D\u7F6E\u65F6\u6267\u884C\uFF1B\u5EFA\u8BAE\u5148\u5F00\u53CC\u5199\u5E76\u91CD\u542F serve\u3002\u5206\u6279\u3001\u9650\u901F\u3001\u8FDB\u5EA6\u3001\u53EF\u4E2D\u65AD\u7EED\u8DD1\uFF0C\u9000\u51FA\u7801\u540C externalize\u3002",
337885
+ flags: [
337886
+ { name: "schema", desc: "\u53EA\u5904\u7406\u8FD9\u4E2A schema\uFF1B\u987B\u5DF2\u6709 blobs \u8868" },
337887
+ { name: "all-schemas", desc: "\u6240\u6709\u6709 blobs \u8868\u7684 schema\uFF0C\u542B\u5171\u4EAB schema public", boolean: true },
337888
+ { name: "batch", desc: "\u6BCF\u6279\u884C\u6570\uFF08\u7F3A\u7701 50\uFF09" },
337889
+ { name: "pause-ms", desc: "\u6279\u95F4\u505C\u987F\u6BEB\u79D2\u6570\uFF08\u7F3A\u7701 200\uFF09" },
337890
+ { name: "dry-run", desc: "\u53EA\u62A5\u5F85\u5199\u56DE\u7684\u884C\u6570\u548C\u5B57\u8282\u6570\uFF0C\u4E00\u884C\u4E0D\u52A8", boolean: true }
337891
+ ],
337892
+ examples: [
337893
+ "oasis blobs internalize --all-schemas --dry-run",
337894
+ "oasis blobs internalize --all-schemas"
337895
+ ]
337896
+ },
337193
337897
  kb: {
337194
337898
  group: "\u77E5\u8BC6\u5E93",
337195
337899
  usage: "oasis kb <ask|ingest|status|maintain|lint> ...",
@@ -338683,16 +339387,19 @@ var COMMAND_DECLS = {
338683
339387
  ],
338684
339388
  examples: ["oasis var rm STALE_KEY --scope project --project oasis", "oasis var rm --id var_1234 --yes"]
338685
339389
  },
338686
- // —— 个人待办(ADR 0635):人对助理说「帮我记一下」→ 助理用这组命令记、改、勾掉。发起人 / 承接人由服务端按调用者
338687
- // 填(agent 调 = 它绑定的那个真人),命令行不收。开关 OASIS_PERSONAL_TODO=off 时服务端 404。 ——
339390
+ // —— 个人待办(ADR 0635):人对助理说「帮我记一下」→ 助理用这组命令记、改、勾掉。发起人由服务端按调用者
339391
+ // 填(agent 调 = 它绑定的那个真人),命令行不收;派给别人用 --to(ADR 0640,先成草案、人点发出才生效)。
339392
+ // 开关 OASIS_PERSONAL_TODO=off 时服务端 404。 ——
338688
339393
  todo: {
338689
339394
  group: "\u4E2A\u4EBA\u5F85\u529E",
338690
339395
  usage: "oasis todo <add|list|show|edit|done|cancel|reopen> ...",
338691
- 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**",
338692
339397
  subCommands: ["todo add", "todo list", "todo show", "todo edit", "todo done", "todo cancel", "todo reopen"],
338693
339398
  examples: [
338694
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',
338695
339401
  "oasis todo list",
339402
+ "oasis todo list --assignee \u5955\u7487",
338696
339403
  "oasis todo show td_3f9a1c2b",
338697
339404
  'oasis todo edit td_3f9a1c2b --due 2026-10-12 --reason "\u6392\u671F\u8C03\u6574"',
338698
339405
  "oasis todo done td_3f9a1c2b"
@@ -338700,9 +339407,10 @@ var COMMAND_DECLS = {
338700
339407
  },
338701
339408
  "todo add": {
338702
339409
  group: "\u4E2A\u4EBA\u5F85\u529E",
338703
- 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]",
338704
- 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",
338705
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 },
338706
339414
  { name: "title", desc: "\u6807\u9898\uFF1A\u4E00\u53E5\u8BDD\u3001\u52A8\u8BCD\u5F00\u5934", required: true, own: true },
338707
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 },
338708
339416
  { name: "due", desc: "\u622A\u6B62\u65E5 YYYY-MM-DD\uFF08\u4E0D\u5E26\u65F6\u533A\uFF09", required: true, own: true },
@@ -338710,25 +339418,35 @@ var COMMAND_DECLS = {
338710
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 },
338711
339419
  { name: "start-time", desc: "\u5F00\u59CB\u65F6\u523B HH:mm\uFF08\u914D --start\uFF09", own: true }
338712
339420
  ],
338713
- 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']
338714
339422
  },
338715
339423
  "todo list": {
338716
339424
  group: "\u4E2A\u4EBA\u5F85\u529E",
338717
- usage: "oasis todo list [--all]",
338718
- 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",
338719
- 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"]
338720
339438
  },
338721
339439
  "todo show": {
338722
339440
  group: "\u4E2A\u4EBA\u5F85\u529E",
338723
339441
  usage: "oasis todo show <id>",
338724
- 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",
338725
339443
  positional: [{ name: "<id>", required: true, desc: "\u5F85\u529E id\uFF08oasis todo list \u7B2C\u4E00\u5217\uFF09" }],
338726
339444
  examples: ["oasis todo show td_3f9a1c2b"]
338727
339445
  },
338728
339446
  "todo edit": {
338729
339447
  group: "\u4E2A\u4EBA\u5F85\u529E",
338730
- 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]",
338731
- 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',
338732
339450
  positional: [{ name: "<id>", required: true, desc: "\u5F85\u529E id\uFF08oasis todo list \u7B2C\u4E00\u5217\uFF09" }],
338733
339451
  flags: [
338734
339452
  { name: "title", desc: "\u65B0\u6807\u9898", own: true },
@@ -338738,28 +339456,31 @@ var COMMAND_DECLS = {
338738
339456
  { name: "due", desc: "\u65B0\u622A\u6B62\u65E5 YYYY-MM-DD", own: true },
338739
339457
  { name: "time", desc: "\u65B0\u622A\u6B62\u65F6\u523B HH:mm\uFF1Bnone = \u6E05\u7A7A", own: true },
338740
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 },
338741
- { 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 }
338742
339461
  ],
338743
339462
  examples: ['oasis todo edit td_3f9a1c2b --due 2026-10-12 --reason "\u5BA2\u6237\u5EF6\u671F\u4E00\u5468"']
338744
339463
  },
338745
339464
  "todo done": {
338746
339465
  group: "\u4E2A\u4EBA\u5F85\u529E",
338747
- usage: "oasis todo done <id>",
338748
- description: "\u6807\u8BB0\u5B8C\u6210\u3002\u5339\u914D\u5230\u591A\u6761\u65F6\u5148\u95EE\u7528\u6237\u662F\u54EA\u6761\uFF0C\u522B\u731C\u3002",
338749
- 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 }]
338750
339470
  },
338751
339471
  "todo cancel": {
338752
339472
  group: "\u4E2A\u4EBA\u5F85\u529E",
338753
339473
  usage: "oasis todo cancel <id> [--reason \u2026]",
338754
- 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",
338755
339475
  positional: [{ name: "<id>", required: true, desc: "\u5F85\u529E id" }],
338756
- 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 }]
338757
339477
  },
338758
339478
  "todo reopen": {
338759
339479
  group: "\u4E2A\u4EBA\u5F85\u529E",
338760
- usage: "oasis todo reopen <id>",
338761
- description: "\u628A\u5DF2\u5B8C\u6210 / \u5DF2\u53D6\u6D88\u7684\u5F85\u529E\u91CD\u65B0\u6253\u5F00\u3002",
338762
- 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 }]
338763
339484
  },
338764
339485
  // —— 技能:组织技能库的 CLI 面(Web 上是「管理 > 技能」那一页)。
338765
339486
  // agent 把自己写好的一个技能目录沉淀进公司,并决定它对谁生效。 ——
@@ -339086,7 +339807,7 @@ function formatProjectDecisions(projectId2, view, query) {
339086
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",
339087
339808
  ""
339088
339809
  ];
339089
- let shown = 0;
339810
+ let shown2 = 0;
339090
339811
  for (const t of topics) {
339091
339812
  const rows = view.decisions.filter((d5) => d5.value.topicId === t.value.topicId).filter((d5) => {
339092
339813
  if (state2 ? !stateMatches(d5.value, state2, query.now) : !["accepted", "proposed"].includes(d5.value.record.state)) return false;
@@ -339100,7 +339821,7 @@ function formatProjectDecisions(projectId2, view, query) {
339100
339821
  if (!rows.length) lines.push("\uFF08\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u51B3\u7B56\uFF09");
339101
339822
  for (const d5 of rows) {
339102
339823
  const r5 = d5.value.record;
339103
- shown += 1;
339824
+ shown2 += 1;
339104
339825
  lines.push(`- [${label(d5.value, query.now)}] ${r5.statement} \uFF08\u66F4\u65B0 ${r5.updated_at.slice(0, 10)}\uFF0C${d5.value.decisionId}\uFF09`);
339105
339826
  if (!detail) continue;
339106
339827
  lines.push(` \u539F\u56E0\uFF1A${r5.rationale || "\u672A\u8BF4\u660E"}`, ` \u786E\u8BA4\u4EBA\uFF1A${r5.confirmer ?? "\u672A\u786E\u8BA4"}`);
@@ -339111,7 +339832,7 @@ function formatProjectDecisions(projectId2, view, query) {
339111
339832
  }
339112
339833
  lines.push("");
339113
339834
  }
339114
- 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");
339115
339836
  return lines;
339116
339837
  }
339117
339838
  function writeContextSnapshot(root5, snapshot) {
@@ -339511,10 +340232,10 @@ function renderGraphLines(graph) {
339511
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]" : "";
339512
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}`);
339513
340234
  }
339514
- const shown = graph.edges.filter((e5) => !closureIds.has(e5.from));
340235
+ const shown2 = graph.edges.filter((e5) => !closureIds.has(e5.from));
339515
340236
  const folded = [...closureIds].filter((id) => graph.edges.some((e5) => e5.from === id));
339516
- 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");
339517
- 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" : ""}`);
339518
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`);
339519
340240
  return out;
339520
340241
  }
@@ -339615,7 +340336,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback2) {
339615
340336
  }
339616
340337
  }
339617
340338
  }
339618
- var CLI_VERSION = "2.2.119" ? "2.2.119" : "dev";
340339
+ var CLI_VERSION = "2.2.121" ? "2.2.121" : "dev";
339619
340340
  function apiHeaders(token2, company) {
339620
340341
  const headers = {
339621
340342
  authorization: `Bearer ${token2}`,
@@ -339977,6 +340698,31 @@ async function runCli(argv, println = console.log, progressln = console.error) {
339977
340698
  });
339978
340699
  return;
339979
340700
  }
340701
+ if (command6 === "blobs") {
340702
+ const { runBlobs: runBlobs2 } = await Promise.resolve().then(() => (init_blobs_command(), blobs_command_exports));
340703
+ let stopRequested = false;
340704
+ const onSignal = () => {
340705
+ if (stopRequested) process.exit(130);
340706
+ stopRequested = true;
340707
+ process.stderr.write("[blobs] \u6536\u5230\u4E2D\u65AD\uFF1A\u5904\u7406\u5B8C\u5F53\u524D\u8FD9\u4E00\u884C\u540E\u505C\u6B62\uFF08\u518D\u6309\u4E00\u6B21\u7ACB\u5373\u9000\u51FA\uFF09\n");
340708
+ };
340709
+ process.on("SIGINT", onSignal);
340710
+ process.on("SIGTERM", onSignal);
340711
+ try {
340712
+ process.exitCode = await runBlobs2(positional, flags, {
340713
+ out: (line2) => println(line2),
340714
+ err: (line2) => process.stderr.write(`${line2}
340715
+ `),
340716
+ env: process.env,
340717
+ dir,
340718
+ shouldStop: () => stopRequested
340719
+ });
340720
+ } finally {
340721
+ process.off("SIGINT", onSignal);
340722
+ process.off("SIGTERM", onSignal);
340723
+ }
340724
+ return;
340725
+ }
339980
340726
  if (command6 === "node-token") {
339981
340727
  const store = createNodeTokenStore(path42.join(dir, "node-tokens.json"));
339982
340728
  const sub = positional[0];
@@ -341987,6 +342733,7 @@ ${res.warning}`);
341987
342733
  }
341988
342734
  case "todo": {
341989
342735
  const sub = positional[0];
342736
+ const nameIn = (names) => (id) => names?.[id] ?? id;
341990
342737
  const due = (t) => formatTodoSpan(t);
341991
342738
  const call = async (method, url, body2) => {
341992
342739
  try {
@@ -342009,6 +342756,8 @@ ${res.warning}`);
342009
342756
  const idOf = () => needPos(positional, 1, `oasis todo ${sub} <id>`);
342010
342757
  const todoPath = (id, tail = "") => `/api/todos/${encodeURIComponent(id)}${tail}`;
342011
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");
342012
342761
  const title = need(flags, "title");
342013
342762
  const dueDate = checkDay(need(flags, "due"), "due");
342014
342763
  const time4 = flags.get("time");
@@ -342019,6 +342768,7 @@ ${res.warning}`);
342019
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`);
342020
342769
  const sourceChatSessionId = process.env["OASIS_CHAT_SESSION_ID"];
342021
342770
  const r5 = await call("POST", "/api/todos", {
342771
+ ...to !== void 0 ? { assignee: to } : {},
342022
342772
  title,
342023
342773
  dueDate,
342024
342774
  ...time4 !== void 0 ? { dueTime: checkTime(time4) } : {},
@@ -342028,22 +342778,39 @@ ${res.warning}`);
342028
342778
  ...sourceChatSessionId ? { sourceChatSessionId } : {}
342029
342779
  });
342030
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
+ }
342031
342787
  println(`\u8BB0\u4E0B\u4E86\uFF1A${r5.todo.title}\uFF0C${due(r5.todo)}`);
342032
342788
  println(`id\uFF1A${r5.todo.id}`);
342033
342789
  break;
342034
342790
  }
342035
342791
  if (sub === "list") {
342792
+ const qs = new URLSearchParams();
342036
342793
  const all2 = flags.get("all") === "true";
342037
- 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}` : ""}`);
342038
342804
  println(r5.todayLine);
342039
342805
  if (r5.items.length === 0) {
342040
- 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");
342041
342807
  break;
342042
342808
  }
342809
+ const nm = nameIn(r5.names);
342043
342810
  for (const t of r5.items) {
342044
- 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]" : "";
342045
342812
  const when = t.startDate ? formatTodoSpan(t) : formatTodoDue(t.dueDate, t.dueTime);
342046
- 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}`);
342047
342814
  }
342048
342815
  break;
342049
342816
  }
@@ -342054,11 +342821,27 @@ ${res.warning}`);
342054
342821
  call("GET", "/api/todos")
342055
342822
  ]);
342056
342823
  const t = one2.todo;
342057
- 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";
342058
342826
  println(day.todayLine);
342059
342827
  println(`${t.id} ${t.title}`);
342060
342828
  println(`\u65F6\u95F4\uFF1A${due(t)}`);
342061
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
+ }
342062
342845
  if (t.linkedWorkorderId) println(`\u5DE5\u5355\uFF1A${t.linkedWorkorderId}`);
342063
342846
  println("\u5907\u6CE8\uFF1A");
342064
342847
  println(t.note ? t.note : "\uFF08\u65E0\uFF09");
@@ -342071,24 +342854,16 @@ ${res.warning}`);
342071
342854
  startTime: "\u5F00\u59CB\u65F6\u523B",
342072
342855
  dueDate: "\u622A\u6B62\u65E5",
342073
342856
  dueTime: "\u622A\u6B62\u65F6\u523B",
342074
- linkedWorkorderId: "\u5DE5\u5355"
342857
+ linkedWorkorderId: "\u5DE5\u5355",
342858
+ assignee: "\u627F\u63A5\u4EBA"
342075
342859
  };
342076
- const STATUS = { open: "\u672A\u5B8C\u6210", done: "\u5DF2\u5B8C\u6210", cancelled: "\u5DF2\u53D6\u6D88" };
342077
- const val = (field, v2) => v2 === void 0 || v2 === "" ? "\uFF08\u7A7A\uFF09" : field === "status" ? STATUS[v2] ?? v2 : v2;
342078
- const fmtAt = (iso9) => new Intl.DateTimeFormat("sv-SE", {
342079
- timeZone: day.today.timeZone,
342080
- year: "numeric",
342081
- month: "2-digit",
342082
- day: "2-digit",
342083
- hour: "2-digit",
342084
- minute: "2-digit",
342085
- hour12: false
342086
- }).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;
342087
342862
  const changes = [...one2.changes].sort((a5, b6) => b6.at.localeCompare(a5.at));
342088
342863
  println(`\u53D8\u66F4\u8BB0\u5F55\uFF08${changes.length} \u6761\uFF0C\u65B0\u7684\u5728\u524D\uFF09\uFF1A`);
342089
342864
  for (const c5 of changes) {
342090
- const who = c5.handActorId ? `${c5.actorId}\uFF08\u7ECF\u624B ${c5.handActorId}\uFF09` : c5.actorId;
342091
- 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)}`;
342092
342867
  println(`- ${fmtAt(c5.at)} ${who} ${what}${c5.reason ? ` \u539F\u56E0\uFF1A${c5.reason}` : ""}`);
342093
342868
  }
342094
342869
  break;
@@ -342103,7 +342878,8 @@ ${res.warning}`);
342103
342878
  if (flags.has("start")) body2["startDate"] = flags.get("start") === "none" ? null : checkDay(flags.get("start"), "start");
342104
342879
  if (flags.has("start-time")) body2["startTime"] = flags.get("start-time") === "none" ? null : checkTime(flags.get("start-time"), "start-time");
342105
342880
  if (flags.has("workorder")) body2["linkedWorkorderId"] = flags.get("workorder") === "none" ? null : flags.get("workorder");
342106
- 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");
342107
342883
  if (flags.get("reason")) body2["reason"] = flags.get("reason");
342108
342884
  const r5 = await call("PATCH", todoPath(id), body2);
342109
342885
  println(r5.todayLine);
@@ -342112,7 +342888,7 @@ ${res.warning}`);
342112
342888
  }
342113
342889
  if (sub === "done" || sub === "cancel" || sub === "reopen") {
342114
342890
  const id = idOf();
342115
- const reason = sub === "cancel" ? flags.get("reason") : void 0;
342891
+ const reason = flags.get("reason");
342116
342892
  const r5 = await call("POST", todoPath(id, `/${sub}`), reason ? { reason } : {});
342117
342893
  println(r5.todayLine);
342118
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)}`);
@@ -342943,10 +343719,10 @@ ${res.warning}`);
342943
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(" ")}`);
342944
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`);
342945
343721
  const REV_LIMIT = 50;
342946
- const shown = v2.revisions.length > REV_LIMIT ? v2.revisions.slice(-REV_LIMIT) : v2.revisions;
342947
- println(`\u7248\u672C(${v2.revisions.length})${shown.length < v2.revisions.length ? `\u2014\u2014\u53EA\u5217\u6700\u8FD1 ${REV_LIMIT} \u6761` : ""}\uFF1A`);
342948
- if (shown.length === 0) println(` \uFF08\u65E0\uFF09`);
342949
- 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) {
342950
343726
  const marks = [
342951
343727
  r5.id === v2.artifact.currentRev ? "\u2190 current head" : "",
342952
343728
  r5.id === v2.concludedHead ? "\u2190 \u5DF2\u4EA4\u4ED8" : ""
@@ -343019,11 +343795,11 @@ ${res.warning}`);
343019
343795
  case "ls": {
343020
343796
  const wsFilter = flags.get("workspace");
343021
343797
  const rows = await api.view("ls");
343022
- const shown = wsFilter ? rows.filter((a5) => a5.workspace === wsFilter) : rows;
343023
- for (const a5 of shown) {
343798
+ const shown2 = wsFilter ? rows.filter((a5) => a5.workspace === wsFilter) : rows;
343799
+ for (const a5 of shown2) {
343024
343800
  println(`${a5.id} ws=${a5.workspace ?? "\u2205"} type=${a5.type} owner=${a5.owner} head=${a5.currentRev ?? "\u2205"} queue=${a5.queue} [${a5.mark}]`);
343025
343801
  }
343026
- 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`);
343027
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`);
343028
343804
  break;
343029
343805
  }
@@ -343793,7 +344569,7 @@ function shimScript() {
343793
344569
  }
343794
344570
 
343795
344571
  // src/index.ts
343796
- var PKG_VERSION = true ? "2.2.119" : "dev";
344572
+ var PKG_VERSION = true ? "2.2.121" : "dev";
343797
344573
  var LOCAL_BIN = localBin();
343798
344574
  var NPM_PREFIX = npmPrefix();
343799
344575
  var INSTANCE = DEFAULT_INSTANCE;